Overview
Factory for creating the trinity pattern tuple used throughout Vuetify Zero. The trinity pattern returns a readonly tuple of [useContext, provideContext, defaultContext], enabling flexible dependency injection with sensible defaults.
This pattern is fundamental to all registry-based composables and plugin systems.
Signature
function createTrinity < Z = unknown >(
useContext : () => Z ,
provideContext : ( _context ?: Z , app ?: App ) => Z ,
context : Z
) : ContextTrinity < Z >
type ContextTrinity < Z = unknown > = readonly [
() => Z , // useContext
( context ?: Z , app ?: App ) => Z , // provideContext wrapper
Z // default context instance
]
Parameters
Function that retrieves/uses the context (typically from createContext).
provideContext
(_context?: Z, app?: App) => Z
required
Function that provides the context to descendants. Should accept an optional context value and optional Vue app instance.
The default context instance to use when no custom context is provided to provideContext.
Return Value
A readonly tuple containing: Retrieves the context from the injection tree.
[1] provideContext
(context?: Z, app?: App) => Z
Provides the context to descendants. If context is omitted, uses the default context from the third element. Returns the provided context value.
The default context instance. Useful for accessing default values or creating custom instances.
Usage
Basic Trinity
With Registry Pattern
Custom Context Instance
Extending Context
App-Level Provision
import { createContext , createTrinity } from '#v0/composables'
import type { App } from 'vue'
interface FeatureContext {
enabled : boolean
toggle : () => void
}
export function createFeature < E extends FeatureContext = FeatureContext >() {
// Create low-level inject/provide functions
const [ useContext , _provideContext ] = createContext < E >( 'v0:feature' )
// Create default context instance
const context : E = {
enabled: false ,
toggle : () => { context . enabled = ! context . enabled }
} as E
// Wrap provideContext to use default when no context is passed
function provideContext ( _context : E = context , app ?: App ) : E {
return _provideContext ( _context , app )
}
// Return trinity
return createTrinity < E >( useContext , provideContext , context )
}
// Usage
const [ useFeature , provideFeature , defaultFeature ] = createFeature ()
// Provide default context
provideFeature () // uses defaultFeature
// Provide custom context
const customContext = { enabled: true , toggle : () => {} }
provideFeature ( customContext )
// Access default values
console . log ( defaultFeature . enabled ) // false
Trinity Pattern Benefits
The third element provides a default context instance, eliminating the need to pass context when the default is sufficient: const [ useTheme , provideTheme , defaultTheme ] = createTheme ()
// Use default
provideTheme ()
// Or customize
provideTheme ({ ... defaultTheme , dark: true })
All Vuetify Zero composables that use dependency injection follow the trinity pattern, creating a consistent API: // All follow the same pattern
const [ use , provide , defaults ] = createFeature ()
const [ use , provide , defaults ] = createRegistry ()
const [ use , provide , defaults ] = createSelection ()
The third element allows direct access to default values without injection: const [, , defaults ] = createBreakpoints ()
console . log ( defaults . xs ) // Access default breakpoint values
The readonly tuple preserves exact types through generic parameters: interface MyContext { value : number }
const trinity = createTrinity < MyContext >(
useContext ,
provideContext ,
context
)
const [ use ] = trinity
const ctx = use () // ctx is typed as MyContext
Integration with createPlugin
The trinity pattern integrates seamlessly with createPlugin for plugin development:
import { createContext , createTrinity , createPlugin } from '#v0/composables'
interface LoggerContext {
log : ( msg : string ) => void
}
function createLogger () {
const [ useContext , _provideContext ] = createContext < LoggerContext >( 'v0:logger' )
const context : LoggerContext = {
log : ( msg ) => console . log ( `[LOG] ${ msg } ` )
}
function provideContext ( _context : LoggerContext = context , app ?: App ) {
return _provideContext ( _context , app )
}
return createTrinity ( useContext , provideContext , context )
}
// Create trinity
const [ useLogger , provideLogger , loggerContext ] = createLogger ()
// Create plugin using trinity
export const LoggerPlugin = createPlugin ({
namespace: 'v0:logger' ,
provide : ( app ) => {
provideLogger ( loggerContext , app )
}
})
// Export for consumers
export { useLogger , provideLogger , loggerContext }
Or use the higher-level createPluginContext which handles this automatically:
import { createPluginContext } from '#v0/composables'
// Automatically creates trinity + plugin + consumer
export const [ createLoggerContext , createLoggerPlugin , useLogger ] =
createPluginContext ( 'v0:logger' , () => createLogger ())
// Returns trinity from createLoggerContext
const [ use , provide , context ] = createLoggerContext ()
Best Practices
Make context reactive when needed
Use Vue’s reactive or ref for contexts that need reactivity: import { reactive } from 'vue'
const context = reactive ({
count: 0 ,
increment () { this . count ++ }
})
Provide type parameters for extensibility
Use generic type parameters to allow context extension: export function createFeature < E extends FeatureContext = FeatureContext >() {
// ...
return createTrinity < E >( useContext , provideContext , context )
}
// Consumers can extend
interface CustomFeature extends FeatureContext {
extra : string
}
const [ use , provide ] = createFeature < CustomFeature >()
Export all three elements
Export the entire trinity for maximum flexibility: // ✅ Export destructured trinity
export const [ useFeature , provideFeature , featureContext ] = createFeature ()
// ❌ Don't export just the function
export function useFeature () { /* ... */ }
createContext Create type-safe injection contexts
createPlugin Create Vue plugins with trinity pattern