Skip to main content

Overview

Factory for creating Vue plugins that safely provide dependency injection contexts at the application level. Wraps the provide function in app.runWithContext() to ensure proper execution context. Also exports createPluginContext — a higher-level factory that generates the standard context/plugin/consumer triple for plugin composables, eliminating boilerplate.

Signature

function createPlugin<Z extends Plugin = Plugin>(
  options: PluginOptions
): Z

function createPluginContext<
  O extends { namespace?: string } = Record<never, never>,
  E = unknown,
>(
  defaultNamespace: string,
  factory: (options: Omit<O, 'namespace'>) => E,
  config?: PluginContextConfig<Omit<O, 'namespace'>, E>
): readonly [
  <_E extends E = E>(_options?: O) => ContextTrinity<_E>,
  (_options?: O) => Plugin,
  <_E extends E = E>(namespace?: string) => _E
]

createPlugin

Creates a Vue plugin with proper context handling.

Parameters

options
PluginOptions
required
namespace
string
required
Unique identifier for the plugin. Used to prevent duplicate installation.
provide
(app: App) => void
required
Function that provides contexts to the Vue app. Called during plugin installation within app.runWithContext().
setup
(app: App) => void
Optional setup callback executed once per app after context provision. Use for adapter initialization, global mixins, or side effects.

Return Value

plugin
Plugin
A Vue plugin object with an install method. The plugin ensures:
  • Context provision runs in proper execution context
  • Setup callback runs only once per app (duplicate installation guard)
  • Provide always runs, setup guarded by namespace

Usage

import { createPlugin, createContext } from '#v0/composables'

interface LoggerContext {
  log: (message: string) => void
  error: (message: string) => void
}

const [useLogger, provideLogger] = createContext<LoggerContext>('v0:logger')

const logger: LoggerContext = {
  log: (msg) => console.log(msg),
  error: (msg) => console.error(msg)
}

export const LoggerPlugin = createPlugin({
  namespace: 'v0:logger',
  provide: (app) => {
    provideLogger(logger, app)
  }
})

// Install in app
import { createApp } from 'vue'
const app = createApp({})
app.use(LoggerPlugin)

createPluginContext

Higher-level factory that generates the standard context/plugin/consumer triple pattern used throughout Vuetify Zero.

Parameters

defaultNamespace
string
required
Default dependency injection namespace (e.g., 'v0:logger'). Can be overridden via options.
factory
(options: Omit<O, 'namespace'>) => E
required
Function that creates the composable context instance from options.
config
PluginContextConfig<O, E>
setup
(context: E, app: App, options: O) => void
Optional setup callback invoked once per app after context provision. Receives the context instance, app, and options.
fallback
(namespace: string) => E
Optional fallback factory. When provided, the generated useX consumer uses the defensive pattern: returns the fallback when called outside a component instance or when context is not found.Required for composables that may be consumed outside component setup (e.g., useLogger, useLocale).

Return Value

[createXContext, createXPlugin, useX]
readonly tuple
A readonly tuple containing three functions:
createXContext
<E>(_options?: O) => ContextTrinity<E>
Creates a context trinity: [useContext, provideContext, contextInstance].See createTrinity for details on the trinity pattern.
createXPlugin
(_options?: O) => Plugin
Creates a Vue plugin that provides the context at app level.
useX
<E>(namespace?: string) => E
Consumer function that retrieves the context. Falls back if fallback config is provided and no injection context exists.

Usage

import { createPluginContext } from '#v0/composables'

interface StorageContext {
  get: (key: string) => string | null
  set: (key: string, value: string) => void
}

interface StorageOptions {
  namespace?: string
  prefix?: string
}

function createStorage(options: Omit<StorageOptions, 'namespace'>): StorageContext {
  const prefix = options.prefix ?? 'app'
  return {
    get: (key) => localStorage.getItem(`${prefix}:${key}`),
    set: (key, val) => localStorage.setItem(`${prefix}:${key}`, val)
  }
}

// Generate the triple
export const [createStorageContext, createStoragePlugin, useStorage] =
  createPluginContext<StorageOptions, StorageContext>(
    'v0:storage',
    createStorage
  )

// Use the plugin
import { createApp } from 'vue'
const app = createApp({})
const plugin = createStoragePlugin({ prefix: 'myapp' })
app.use(plugin)

// Use in components
const storage = useStorage()
storage.set('user', 'alice')

Best Practices

When creating a new composable that will be installed as a plugin, prefer createPluginContext over manually wiring up createContext, createPlugin, and createTrinity.
// ✅ Good - uses createPluginContext
export const [createStorageContext, createStoragePlugin, useStorage] =
  createPluginContext('v0:storage', createStorage)

// ❌ Avoid - manual wiring (more boilerplate, error-prone)
export const [useStorage, provideStorage] = createContext('v0:storage')
export const StoragePlugin = createPlugin({ /* ... */ })
If your composable may be called outside component setup (utility functions, middleware, initialization code), provide a fallback to prevent injection errors:
createPluginContext('v0:logger', createLogger, {
  fallback: (ns) => createFallbackLogger(ns)
})
The setup callback should be idempotent and avoid global side effects that could cause issues if the plugin is accidentally installed twice.
Always use the v0: prefix for Vuetify Zero plugins, and ensure namespace uniqueness to prevent collisions:
// ✅ Good - unique namespace
'v0:logger'
'v0:storage'
'v0:theme'

// ❌ Avoid - generic names risk collision
'logger'
'app'

Type Safety

interface MyOptions {
  namespace?: string
  timeout: number
}

interface MyContext {
  execute: () => void
}

const [createMyContext, createMyPlugin, useMyComposable] =
  createPluginContext<MyOptions, MyContext>(
    'v0:my-feature',
    (options) => {
      // TypeScript knows: options = Omit<MyOptions, 'namespace'>
      // options.timeout is required, options.namespace is stripped
      return { execute: () => {} }
    }
  )

// ✅ TypeScript enforces required options
const plugin = createMyPlugin({ timeout: 1000 })

// ❌ TypeScript error: missing 'timeout'
const plugin = createMyPlugin({})

// ✅ Type-safe consumption
const composable = useMyComposable()
composable.execute() // TypeScript knows 'execute' exists

createContext

Low-level context creation

createTrinity

Trinity pattern for context composables

Build docs developers (and LLMs) love