Pinia Internals — Series 3

Preview — 3 of 10 questions

What does this plugin change about how store.someAction behaves, for every store it applies to?

javascript
function loggingPlugin({ store }) {
  return {
    // no-op — the actual wrapping happens below
  }
}

function actionWrappingPlugin({ store }) {
  const actionNames = Object.keys(store.$state).length ? [] : [] // illustrative
  for (const key in store) {
    if (typeof store[key] === 'function' && !key.startsWith('$')) {
      const original = store[key]
      store[key] = function (...args) {
        console.log(`calling ${key}`, args)
        return original.apply(store, args)
      }
    }
  }
}
AThis plugin iterates over the store's own methods (its actions) and replaces each one with a wrapper function that logs the call before delegating to the original — effectively intercepting every action call across every store the plugin is applied to, without needing to modify any individual store's own action definitions. This is a real (if somewhat blunt) technique for adding cross-cutting behavior — logging, timing, permission checks — uniformly, as an alternative to the more targeted $onAction-based interception shown elsewhere
BReassigning store[key] inside a plugin like this has no effect — Pinia freezes every action reference after store creation, making them unreplaceable
CThis only affects actions defined using the options-syntax actions: {} block — setup-syntax store functions returned from the store body are immutable and can't be replaced this way
DReassigning store[key] like this silently creates a brand-new, disconnected function that components calling store.someAction() never actually reach — the original unwrapped action keeps running instead

What does this early return accomplish, and why might a plugin author want it?

javascript
function devToolsBridgePlugin({ store }) {
  if (!import.meta.env.DEV) {
    return // no-op in production
  }
  store.$onAction(({ name, args }) => {
    window.__PINIA_DEBUG__?.log(store.$id, name, args)
  })
}
Aimport.meta.env.DEV has no special build-time meaning — this check runs identically in both development and production, making the early return pointless
BThis is required syntax — every Pinia plugin must include an import.meta.env.DEV check or Pinia refuses to register it
Cimport.meta.env.DEV is a build-time constant a bundler like Vite substitutes and can dead-code-eliminate around, similar to the __VUE_OPTIONS_API__-style feature flags covered for Vue itself — guarding a plugin's debug-only behavior (subscribing to every action just to forward it to a devtools bridge) behind this check means that code, and the overhead of the extra $onAction subscription itself, is entirely stripped from the production bundle rather than merely skipped at runtime, keeping shipped code lighter and avoiding always-on debug instrumentation in what real users actually run
DThis check only affects whether console.log statements are visible — the underlying $onAction subscription and its overhead still run identically in production

What does injecting the API client through a plugin (rather than importing it directly inside the store) buy for testability here?

javascript
// plugin
function apiClientPlugin(apiClient) {
  return ({ store }) => {
    store.api = apiClient
  }
}

// store
defineStore('products', {
  actions: {
    async fetchAll() {
      this.items = await this.api.get('/products')
    },
  },
})

// production
pinia.use(apiClientPlugin(realApiClient))

// test
pinia.use(apiClientPlugin(mockApiClient))
AThere's no testability benefit — this.api inside the action would need to be mocked with vi.mock regardless of whether it came from a plugin or a direct import
BThis pattern only works for synchronous API clients; anything involving async/await, as fetchAll does here, requires mocking at the module level instead
CThis pattern requires the store to be defined with the setup syntax; options-syntax stores cannot have plugin-injected properties accessed via this
DBecause the store's action reads this.api (whatever a plugin happened to attach) rather than importing a concrete API client module directly, swapping in a mockApiClient for tests is as simple as registering a different plugin configuration on the test's own Pinia instance — no module-level mocking (vi.mock('./apiClient')) is needed at all; the store's own code never hardcodes which implementation it's talking to

Sign up free to play

Answer all 10 questions (7 more), see explanations for every answer, and track your score.