Skip to content

Conversation

doubledare704
Copy link

@doubledare704 doubledare704 commented Sep 14, 2025

  • Add StoreOptionsAccess utility type for accessing custom store options
  • Modify store creation to include _options property with store options
  • Export StoreOptionsAccess type from main module
  • Add comprehensive tests for plugin access to custom store options
  • Support both option stores and setup stores
  • Maintain backward compatibility with existing plugins

Fixes #1247

Summary by CodeRabbit

  • New Features

    • Store instances now expose per-store custom options to plugins (works for both options- and setup-style stores) while preserving backward compatibility.
    • Added a public TypeScript utility type to surface store option typings for plugins and consumers.
  • Tests

    • Added comprehensive runtime and type-level tests validating plugin access, typings, and behavior across multiple store configurations.

- Add StoreOptionsAccess utility type for accessing custom store options
- Modify store creation to include _options property with store options
- Export StoreOptionsAccess type from main module
- Add comprehensive tests for plugin access to custom store options
- Support both option stores and setup stores
- Maintain backward compatibility with existing plugins

Fixes vuejs#1247
Copy link

netlify bot commented Sep 14, 2025

Deploy Preview for pinia-official canceled.

Name Link
🔨 Latest commit c904a34
🔍 Latest deploy log https://app.netlify.com/projects/pinia-official/deploys/68c9ba20f9a0750008c659cb

Copy link

coderabbitai bot commented Sep 14, 2025

Walkthrough

Exposes per-store defineStore options at runtime via a new _options field on store instances, adds a public StoreOptionsAccess type and re-exports it, updates createSetupStore signature to support options stores, and adds runtime and DTS tests that validate plugin access to custom store options for options- and setup-style stores.

Changes

Cohort / File(s) Summary
Runtime store internals
packages/pinia/src/store.ts
Adds a new _options property to created store instances (populated from optionsForPlugin) and updates createSetupStore signature (generics, $id rename, added isOptionsStore param) so options are attached in both DEVTOOLS and non-DEV paths.
Types and public surface
packages/pinia/src/types.ts, packages/pinia/src/index.ts
Adds optional _options?: any to StoreProperties, introduces export type StoreOptionsAccess<Store, Key extends keyof any> = any in types.ts, and re-exports StoreOptionsAccess from src/index.ts.
Runtime unit tests (with augmentation)
packages/pinia/__tests__/storeOptionsAccess.spec.ts
New test suite augmenting Pinia typings to exercise plugin access to per-store custom options (stores, customOption, debounce) for Options API and setup stores and asserting runtime behavior and backward compatibility.
DTS / declaration tests
packages/pinia/test-dts/storeOptionsAccess.test-d.ts
New declaration test that augments ../dist/pinia with DefineStoreOptionsBase and PiniaCustomProperties and asserts type-level access to custom options via StoreOptionsAccess across multiple store definitions (including setup-style and stores without custom options).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant Pinia
  participant Store
  participant Plugin

  App->>Pinia: createPinia()
  App->>Pinia: pinia.use(Plugin)
  App->>Pinia: defineStore(options)  %% options include customOption / stores / debounce
  App->>Store: useMainStore(pinia)
  Note right of Store #D3E4CD: createSetupStore constructs instance\nand assigns _options = optionsForPlugin
  Pinia-->>Plugin: initialize plugin with { store, options }
  Plugin->>Store: read context.options / store._options
  Plugin-->>App: expose computed getters based on options
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I nibble through code with curious paws,
Found tiny _options behind the drawers.
Plugins peek in, maps and strings galore,
Types nod kindly and tests explore.
Hooray — a hop, a patch, and one more chore! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: expose store options in PiniaCustomProperties for plugin access" concisely and accurately summarizes the primary change of exposing store options to plugins via PiniaCustomProperties and aligns with the linked issue #1247, making it clear to reviewers what the feature enables. It is specific, readable, and focused on the main intent rather than implementation details.
Linked Issues Check ✅ Passed The changes address issue #1247 by introducing a StoreOptionsAccess type and exporting it, adding a per-store _options field at creation, updating typings (including PiniaCustomProperties), and adding both runtime and DTS tests that validate plugin access for option and setup stores, which directly satisfies the issue objective of making DefineStoreOptionsBase accessible inside PiniaCustomProperties. The tests also exercise backward compatibility and the different store creation paths, so the implementation and verification map to the linked issue requirements.
Out of Scope Changes Check ✅ Passed All modified files and added types are directly related to exposing store options to plugins (type export, _options on stores, createSetupStore adjustments, and tests) and no unrelated features or unrelated modules are changed. The createSetupStore signature/generic updates and the id→$id parameter rename appear to be implementation details required to support Options API typing and the new behavior, so they are within scope.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/pinia/src/store.ts (2)

471-489: Store options should be non-reactive to avoid needless tracking

_options currently references a reactive object. Mark it raw when storing on the reactive store to avoid proxying user options and functions.

   const store: Store<Id, S, G, A> = reactive(
     __DEV__ || (__USE_DEVTOOLS__ && IS_CLIENT)
       ? assign(
           {
             _hmrPayload,
             _customProperties: markRaw(new Set<string>()), // devtools custom properties
-            _options: optionsForPlugin, // store options for plugins
+            _options: markRaw(optionsForPlugin), // store options for plugins
           },
           partialStore
         )
       : assign(
           {
-            _options: optionsForPlugin, // store options for plugins
+            _options: markRaw(optionsForPlugin), // store options for plugins
           },
           partialStore
         )
   ) as unknown as Store<Id, S, G, A>

682-701: Hide _options from devtools/object enumeration

Internal fields are made non-enumerable for devtools; _options should be included to prevent leaking internal shapes.

-    ;(['_p', '_hmrPayload', '_getters', '_customProperties'] as const).forEach(
+    ;(['_p', '_hmrPayload', '_getters', '_customProperties', '_options'] as const).forEach(
       (p) => {
         Object.defineProperty(
           store,
           p,
           assign({ value: store[p] }, nonEnumerable)
         )
       }
     )
🧹 Nitpick comments (4)
packages/pinia/src/types.ts (1)

277-284: Avoid any on internal _options

Typing _options as any leaks unsoundness into the public store type. Prefer unknown (safer), since plugins should access options via context.options and type utilities, not through _options.

-  _options?: any
+  _options?: unknown
packages/pinia/src/store.ts (1)

592-679: HMR: keep _options in sync

During HMR, _options is not updated, so plugins reading store._options will see stale values in dev. Copy it from newStore.

       // update the values used in devtools and to allow deleting new properties later on
       store._hmrPayload = newStore._hmrPayload
       store._getters = newStore._getters
+      // keep plugin options in sync during HMR
+      // @ts-expect-error: internal field
+      store._options = newStore._options
       store._hotUpdating = false
packages/pinia/__tests__/storeOptionsAccess.spec.ts (2)

2-2: Import StoreOptionsAccess for stronger runtime test typings

Use the new utility in the module augmentation to validate types here as well (mirrors the dts test).

-import { createPinia, defineStore, StoreDefinition } from '../src'
+import { createPinia, defineStore, StoreDefinition, StoreOptionsAccess } from '../src'

14-18: Use StoreOptionsAccess in augmentation instead of any

This exercise ensures the new utility works in real tests, not only in dts.

   export interface PiniaCustomProperties<Id, S, G, A> {
-    readonly stores: any
-    readonly customOption: any
-    readonly debounce: any
+    readonly stores: StoreOptionsAccess<this, 'stores'>
+    readonly customOption: StoreOptionsAccess<this, 'customOption'>
+    readonly debounce: StoreOptionsAccess<this, 'debounce'>
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57bec95 and 538e98e.

📒 Files selected for processing (5)
  • packages/pinia/__tests__/storeOptionsAccess.spec.ts (1 hunks)
  • packages/pinia/src/index.ts (1 hunks)
  • packages/pinia/src/store.ts (1 hunks)
  • packages/pinia/src/types.ts (2 hunks)
  • packages/pinia/test-dts/storeOptionsAccess.test-d.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
packages/pinia/src/types.ts (1)
packages/pinia/src/index.ts (2)
  • StoreOptionsAccess (31-31)
  • Store (18-18)
packages/pinia/__tests__/storeOptionsAccess.spec.ts (2)
packages/pinia/src/types.ts (4)
  • DefineStoreOptionsBase (635-635)
  • Store (473-485)
  • StoreDefinition (502-527)
  • PiniaCustomProperties (532-537)
packages/pinia/src/store.ts (1)
  • defineStore (844-939)
packages/pinia/test-dts/storeOptionsAccess.test-d.ts (2)
packages/pinia/__tests__/storeOptionsAccess.spec.ts (2)
  • DefineStoreOptionsBase (8-12)
  • PiniaCustomProperties (14-18)
packages/pinia/src/types.ts (5)
  • DefineStoreOptionsBase (635-635)
  • Store (473-485)
  • StoreDefinition (502-527)
  • PiniaCustomProperties (532-537)
  • StoreOptionsAccess (556-556)
🔇 Additional comments (2)
packages/pinia/src/index.ts (1)

31-31: LGTM: new public type export

Re-exporting StoreOptionsAccess from ./types looks correct and non-breaking.

packages/pinia/test-dts/storeOptionsAccess.test-d.ts (1)

1-24: LGTM: dts coverage is solid

Augmentation and StoreOptionsAccess usage validate the intended typing surface.

Comment on lines +539 to +557
/**
* Utility type to access store options within PiniaCustomProperties.
* This allows plugins to access custom options defined in DefineStoreOptionsBase.
*
* @example
* ```ts
* declare module 'pinia' {
* export interface DefineStoreOptionsBase<S, Store> {
* stores?: Record<string, StoreDefinition>;
* }
*
* export interface PiniaCustomProperties<Id, S, G, A> {
* readonly stores: any; // Use any for now, will be properly typed by plugins
* }
* }
* ```
*/
export type StoreOptionsAccess<Store, Key extends keyof any> = any

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Implement StoreOptionsAccess to actually resolve plugin-augmented option types

Right now it’s any, defeating the PR goal. Make it map a Store instance to its DefineStoreOptionsBase augmentation and pick Key, falling back to undefined when absent.

-export type StoreOptionsAccess<Store, Key extends keyof any> = any
+/**
+ * Resolves the type of a custom option declared via `DefineStoreOptionsBase`
+ * for the given Store instance type and option `Key`.
+ * Returns `undefined` if the option is not declared.
+ */
+export type StoreOptionsAccess<
+  This,
+  Key extends keyof any,
+  Fallback = undefined,
+> =
+  This extends Store<infer Id, infer S, infer G, infer A>
+    ? DefineStoreOptionsBase<S, Store<Id, S, G, A>> extends infer O
+      ? O extends Record<PropertyKey, unknown>
+        ? Key extends keyof O
+          ? O[Key]
+          : Fallback
+        : Fallback
+      : Fallback
+    : Fallback
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Utility type to access store options within PiniaCustomProperties.
* This allows plugins to access custom options defined in DefineStoreOptionsBase.
*
* @example
* ```ts
* declare module 'pinia' {
* export interface DefineStoreOptionsBase<S, Store> {
* stores?: Record<string, StoreDefinition>;
* }
*
* export interface PiniaCustomProperties<Id, S, G, A> {
* readonly stores: any; // Use any for now, will be properly typed by plugins
* }
* }
* ```
*/
export type StoreOptionsAccess<Store, Key extends keyof any> = any
/**
* Utility type to access store options within PiniaCustomProperties.
* This allows plugins to access custom options defined in DefineStoreOptionsBase.
*
* @example
*
🤖 Prompt for AI Agents
In packages/pinia/src/types.ts around lines 539 to 557, StoreOptionsAccess is
currently typed as any; replace it with a conditional/lookup type that, given a
Store instance type, resolves the corresponding augmented DefineStoreOptionsBase
for that store and picks the property Key (returning that property type or
undefined if missing). Implement this using TypeScript conditional/lookup types
that extract the appropriate DefineStoreOptionsBase augmentation for the
provided Store type (e.g., by inferring the store identifier or matching the
Store instance type to the augmented interface) and return the property at Key
or undefined as a fallback.

@doubledare704
Copy link
Author

@coderabbitai generate docstrings

Copy link

coderabbitai bot commented Sep 15, 2025

✅ Actions performed

Docstrings generation started.

coderabbitai bot added a commit that referenced this pull request Sep 15, 2025
…operties`

Docstrings generation was requested by @doubledare704.

* #3042 (comment)

The following files were modified:

* `packages/pinia/src/store.ts`
Copy link

coderabbitai bot commented Sep 15, 2025

Note

Generated docstrings for this pull request at #3044

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/pinia/src/store.ts (3)

487-505: Make _options non-reactive to avoid devtools/serialization churn.

Right now _options becomes reactive (proxied) and enumerable. This can:

  • Surface large, non-serializable trees/functions in devtools and snapshots.
  • Add needless proxy work.
  • Allow accidental mutation by plugins/userland.

Minimal safe change: mark it raw.

       ? assign(
           {
             _hmrPayload,
             _customProperties: markRaw(new Set<string>()), // devtools custom properties
-            _options: optionsForPlugin, // store options for plugins
+            _options: markRaw(optionsForPlugin), // store options for plugins
           },
           partialStore
         )
       : assign(
           {
-            _options: optionsForPlugin, // store options for plugins
+            _options: markRaw(optionsForPlugin), // store options for plugins
           },
           partialStore
         )

If you want the stronger guarantee (recommended): define _options as non-enumerable and readonly right after store creation instead of including it in the assign() object.

// place right after the reactive(store) creation, once optionsForPlugin is fully prepared
Object.defineProperty(store, '_options', {
  value: markRaw(optionsForPlugin),
  enumerable: false,
  configurable: true, // allow HMR to redefine
  writable: false,
})

706-716: Hide _options from devtools property listing.

For consistency with other internals and to avoid noisy/unsafe serialization, include _options in the non-enumerable set.

-    ;(['_p', '_hmrPayload', '_getters', '_customProperties'] as const).forEach(
+    ;(['_p', '_hmrPayload', '_getters', '_customProperties', '_options'] as const).forEach(

609-695: Sync _options during HMR

HMR copies actions/getters/state but not store._options, so plugins reading per-store options after HMR will see stale values — update _options near the end of store._hotUpdate (before updating _hmrPayload/_getters):

// sync plugin options
if ('_options' in newStore) {
  Object.defineProperty(store, '_options', {
    value: newStore._options,
    enumerable: false,
    configurable: true,
    writable: false,
  })
}

File: packages/pinia/src/store.ts — inside store._hotUpdate (approx. lines 609–695).

🧹 Nitpick comments (1)
packages/pinia/src/store.ts (1)

216-231: Docblock LGTM (tiny nits optional).

Reads clearly and matches the function behavior. If you want, tighten wording: “Returns a reactive store instance that exposes …” instead of “The reactive Store instance is exposing …”.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 538e98e and fcf686b.

📒 Files selected for processing (1)
  • packages/pinia/src/store.ts (2 hunks)

- Update docstring for `createSetupStore` return type for clarity
- Mark `_options` as raw to prevent reactivity issues
- Add syncing of `_options` property during HMR
- Ensure `_options` is non-enumerable and consistent in internal properties
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
packages/pinia/src/store.ts (4)

216-231: Good docblock; consider marking isOptionsStore as internal.

To avoid implying public support, add an @internal note for isOptionsStore in the TSDoc.


493-493: Avoid enumerating _options on the public store in non-devtools builds.

Inline assign(..., { _options: ... }) makes _options enumerable (SSR/plugins iterating keys will see it). Prefer defining it as non-enumerable right after store creation.

Apply this diff to stop setting _options here:

-            _customProperties: markRaw(new Set<string>()), // devtools custom properties
-            _options: markRaw(optionsForPlugin), // store options for plugins
+            _customProperties: markRaw(new Set<string>()), // devtools custom properties

Then, right after the reactive store is created (below Line 505), add:

Object.defineProperty(store as any, '_options', {
  value: markRaw(optionsForPlugin),
  enumerable: false,
  configurable: true,
  writable: true, // keep parity with other internals; see devtools block
})

499-504: Same enumerability concern for non-devtools path.

Make _options non-enumerable at definition time for consistency with the devtools path and to avoid leaking into key iterations.

Apply this diff:

-      : assign(
-          {
-            _options: markRaw(optionsForPlugin), // store options for plugins
-          },
-          partialStore
-        )
+      : assign({}, partialStore)

(Then keep the single Object.defineProperty addition suggested above to define _options once.)


691-699: HMR redefines _options with different writability/enumerability; preserve prior descriptor.

Currently HMR sets writable: false and enumerable: false, which may diverge from initial flags and surprise plugins. Reuse the existing descriptor’s flags.

Apply this diff:

-      if ('_options' in newStore) {
-        Object.defineProperty(store, '_options', {
-          value: newStore._options,
-          enumerable: false,
-          configurable: true,
-          writable: false,
-        })
-      }
+      if ('_options' in newStore) {
+        const prev = Object.getOwnPropertyDescriptor(store, '_options')
+        Object.defineProperty(store, '_options', {
+          value: newStore._options,
+          enumerable: prev?.enumerable ?? false,
+          configurable: true,
+          writable: prev?.writable ?? true,
+        })
+      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcf686b and c904a34.

📒 Files selected for processing (1)
  • packages/pinia/src/store.ts (4 hunks)
🔇 Additional comments (1)
packages/pinia/src/store.ts (1)

716-731: Hiding _options from devtools is correct — verify locally.

packages/pinia/src/store.ts (≈lines 700–740) shows '_options' included in the non-enumerable list; keeping it non-enumerable is appropriate. Sandbox could not run workspace tests (pnpm test failed due to missing dev tooling/node_modules). Run locally and confirm no regressions and no unintended writes to _options:

pnpm -w install
pnpm -w test
pnpm -w -r test-dts
rg -n --hidden -S '._options\s*=' -g '!/node_modules/' -C2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Plugins: Expose DefineStoreOptionsBase in PiniaCustomProperties
1 participant