Full Code of vuejs/pinia for AI

v4 97dff81b3a8e cached
292 files
855.7 KB
267.3k tokens
428 symbols
1 requests
Download .txt
Showing preview only (930K chars total). Download the full file or copy to clipboard to get everything.
Repository: vuejs/pinia
Branch: v4
Commit: 97dff81b3a8e
Files: 292
Total size: 855.7 KB

Directory structure:
gitextract_roq8u4o3/

├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── ISSUE_TEMPLATE.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── commit-convention.md
│   ├── funding.yml
│   ├── settings.yml
│   └── workflows/
│       ├── ci.yml
│       ├── pkg.pr.new.yml
│       ├── release-tag.yml
│       └── update-sponsors.yml
├── .gitignore
├── .npmrc
├── .oxfmtrc.json
├── .vscode/
│   ├── launch.json
│   └── settings.json
├── LICENSE
├── README.md
├── SECURITY.md
├── codecov.yml
├── netlify.toml
├── package.json
├── packages/
│   ├── docs/
│   │   ├── .gitignore
│   │   ├── .vitepress/
│   │   │   ├── config/
│   │   │   │   ├── en.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── shared.ts
│   │   │   │   └── zh.ts
│   │   │   ├── theme/
│   │   │   │   ├── components/
│   │   │   │   │   ├── AsideSponsors.vue
│   │   │   │   │   ├── HomeSponsors.vue
│   │   │   │   │   ├── HomeSponsorsGroup.vue
│   │   │   │   │   ├── MadVueBanner.vue
│   │   │   │   │   ├── MasteringPiniaLink.vue
│   │   │   │   │   ├── PiniaLogo.vue
│   │   │   │   │   ├── RuleKitLink.vue
│   │   │   │   │   ├── VueMasteryBanner.vue
│   │   │   │   │   ├── VueMasteryHomeLink.vue
│   │   │   │   │   ├── VueMasteryLogoLink.vue
│   │   │   │   │   ├── VueSchoolLink.vue
│   │   │   │   │   ├── VuejsdeConfBanner.vue
│   │   │   │   │   └── sponsors.json
│   │   │   │   ├── index.ts
│   │   │   │   └── styles/
│   │   │   │       ├── home-links.css
│   │   │   │       ├── playground-links.css
│   │   │   │       └── vars.css
│   │   │   └── translation-status.json
│   │   ├── cookbook/
│   │   │   ├── composables.md
│   │   │   ├── composing-stores.md
│   │   │   ├── hot-module-replacement.md
│   │   │   ├── index.md
│   │   │   ├── migration-0-0-7.md
│   │   │   ├── migration-v1-v2.md
│   │   │   ├── migration-v2-v3.md
│   │   │   ├── migration-vuex.md
│   │   │   ├── options-api.md
│   │   │   ├── testing.md
│   │   │   └── vscode-snippets.md
│   │   ├── core-concepts/
│   │   │   ├── actions.md
│   │   │   ├── getters.md
│   │   │   ├── index.md
│   │   │   ├── outside-component-usage.md
│   │   │   ├── plugins.md
│   │   │   └── state.md
│   │   ├── getting-started.md
│   │   ├── index.md
│   │   ├── introduction.md
│   │   ├── package.json
│   │   ├── run-typedoc.mjs
│   │   ├── ssr/
│   │   │   ├── index.md
│   │   │   └── nuxt.md
│   │   ├── typedoc-markdown.mjs
│   │   ├── typedoc.tsconfig.json
│   │   ├── vite-typedoc-plugin.ts
│   │   ├── vite.config.ts
│   │   └── zh/
│   │       ├── api/
│   │       │   ├── enums/
│   │       │   │   └── pinia.MutationType.md
│   │       │   ├── index.md
│   │       │   ├── interfaces/
│   │       │   │   ├── pinia.DefineSetupStoreOptions.md
│   │       │   │   ├── pinia.DefineStoreOptions.md
│   │       │   │   ├── pinia.DefineStoreOptionsBase.md
│   │       │   │   ├── pinia.DefineStoreOptionsInPlugin.md
│   │       │   │   ├── pinia.MapStoresCustomization.md
│   │       │   │   ├── pinia.Pinia.md
│   │       │   │   ├── pinia.PiniaCustomProperties.md
│   │       │   │   ├── pinia.PiniaCustomStateProperties.md
│   │       │   │   ├── pinia.PiniaPlugin.md
│   │       │   │   ├── pinia.PiniaPluginContext.md
│   │       │   │   ├── pinia.StoreDefinition.md
│   │       │   │   ├── pinia.StoreProperties.md
│   │       │   │   ├── pinia.SubscriptionCallbackMutationDirect.md
│   │       │   │   ├── pinia.SubscriptionCallbackMutationPatchFunction.md
│   │       │   │   ├── pinia.SubscriptionCallbackMutationPatchObject.md
│   │       │   │   ├── pinia._StoreOnActionListenerContext.md
│   │       │   │   ├── pinia._StoreWithState.md
│   │       │   │   ├── pinia._SubscriptionCallbackMutationBase.md
│   │       │   │   ├── pinia_nuxt.ModuleOptions.md
│   │       │   │   ├── pinia_testing.TestingOptions.md
│   │       │   │   └── pinia_testing.TestingPinia.md
│   │       │   └── modules/
│   │       │       ├── pinia.md
│   │       │       ├── pinia_nuxt.md
│   │       │       └── pinia_testing.md
│   │       ├── cookbook/
│   │       │   ├── composables.md
│   │       │   ├── composing-stores.md
│   │       │   ├── hot-module-replacement.md
│   │       │   ├── index.md
│   │       │   ├── migration-0-0-7.md
│   │       │   ├── migration-v1-v2.md
│   │       │   ├── migration-vuex.md
│   │       │   ├── options-api.md
│   │       │   ├── testing.md
│   │       │   └── vscode-snippets.md
│   │       ├── core-concepts/
│   │       │   ├── actions.md
│   │       │   ├── getters.md
│   │       │   ├── index.md
│   │       │   ├── outside-component-usage.md
│   │       │   ├── plugins.md
│   │       │   └── state.md
│   │       ├── getting-started.md
│   │       ├── index.md
│   │       ├── introduction.md
│   │       └── ssr/
│   │           ├── index.md
│   │           └── nuxt.md
│   ├── nuxt/
│   │   ├── .gitignore
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── playground/
│   │   │   ├── app.vue
│   │   │   ├── domain/
│   │   │   │   └── one/
│   │   │   │       └── stores/
│   │   │   │           └── testStore.ts
│   │   │   ├── fake-main.js
│   │   │   ├── layers/
│   │   │   │   └── layer-domain/
│   │   │   │       ├── nuxt.config.ts
│   │   │   │       └── stores/
│   │   │   │           └── basic.ts
│   │   │   ├── nuxt.config.ts
│   │   │   ├── package.json
│   │   │   ├── pages/
│   │   │   │   ├── index.vue
│   │   │   │   └── skip-hydrate.vue
│   │   │   ├── stores/
│   │   │   │   ├── counter.ts
│   │   │   │   ├── nested/
│   │   │   │   │   └── some-store.ts
│   │   │   │   └── with-skip-hydrate.ts
│   │   │   └── tsconfig.json
│   │   ├── shims.d.ts
│   │   ├── src/
│   │   │   ├── auto-hmr-plugin.ts
│   │   │   ├── module.ts
│   │   │   └── runtime/
│   │   │       ├── composables.ts
│   │   │       ├── payload-plugin.ts
│   │   │       └── plugin.vue3.ts
│   │   ├── test/
│   │   │   └── nuxt.spec.ts
│   │   └── tsconfig.json
│   ├── online-playground/
│   │   ├── README.md
│   │   ├── deploy-check.sh
│   │   ├── index.html
│   │   ├── netlify.toml
│   │   ├── package.json
│   │   ├── shims-vue.d.ts
│   │   ├── src/
│   │   │   ├── App.vue
│   │   │   ├── Header.vue
│   │   │   ├── VersionSelect.vue
│   │   │   ├── defaults.ts
│   │   │   ├── download/
│   │   │   │   ├── download.ts
│   │   │   │   └── template/
│   │   │   │       ├── README.md
│   │   │   │       ├── index.html
│   │   │   │       ├── main.js
│   │   │   │       ├── package.json
│   │   │   │       └── vite.config.js
│   │   │   ├── icons/
│   │   │   │   ├── Download.vue
│   │   │   │   ├── GitHub.vue
│   │   │   │   ├── Moon.vue
│   │   │   │   ├── Share.vue
│   │   │   │   └── Sun.vue
│   │   │   ├── main.ts
│   │   │   ├── pinia-dev-proxy.ts
│   │   │   ├── vue-dev-proxy.ts
│   │   │   └── vue-server-renderer-dev-proxy.ts
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   ├── pinia/
│   │   ├── .gitignore
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── __tests__/
│   │   │   ├── actions.spec.ts
│   │   │   ├── combinedStores.spec.ts
│   │   │   ├── devtools.spec.ts
│   │   │   ├── getters.spec.ts
│   │   │   ├── hmr.spec.ts
│   │   │   ├── lifespan.spec.ts
│   │   │   ├── mapHelpers.spec.ts
│   │   │   ├── onAction.spec.ts
│   │   │   ├── pinia/
│   │   │   │   └── stores/
│   │   │   │       ├── cart.ts
│   │   │   │       ├── combined.ts
│   │   │   │       └── user.ts
│   │   │   ├── rootState.spec.ts
│   │   │   ├── ssr.spec.ts
│   │   │   ├── state.spec.ts
│   │   │   ├── store.patch.spec.ts
│   │   │   ├── store.spec.ts
│   │   │   ├── storePlugins.spec.ts
│   │   │   ├── storeSetup.spec.ts
│   │   │   ├── storeToRefs.spec.ts
│   │   │   ├── subscriptions.spec.ts
│   │   │   ├── vitest-mock-warn.ts
│   │   │   └── vitest-setup.ts
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── createPinia.ts
│   │   │   ├── devtools/
│   │   │   │   ├── actions.ts
│   │   │   │   ├── file-saver.ts
│   │   │   │   ├── formatting.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── plugin.ts
│   │   │   │   └── utils.ts
│   │   │   ├── env.ts
│   │   │   ├── global.d.ts
│   │   │   ├── globalExtensions.ts
│   │   │   ├── hmr.ts
│   │   │   ├── index.ts
│   │   │   ├── mapHelpers.ts
│   │   │   ├── rootStore.ts
│   │   │   ├── store.ts
│   │   │   ├── storeToRefs.ts
│   │   │   ├── subscriptions.ts
│   │   │   └── types.ts
│   │   ├── test-dts/
│   │   │   ├── actions.test-d.ts
│   │   │   ├── customizations.test-d.ts
│   │   │   ├── index.d.ts
│   │   │   ├── mapHelpers.test-d.ts
│   │   │   ├── onAction.test-d.ts
│   │   │   ├── plugins.test-d.ts
│   │   │   ├── state.test-d.ts
│   │   │   ├── store.test-d.ts
│   │   │   ├── storeSetup.test-d.ts
│   │   │   ├── storeToRefs.test-d.ts
│   │   │   ├── tsconfig.json
│   │   │   └── typeHelpers.test-d.ts
│   │   └── tsdown.config.ts
│   ├── playground/
│   │   ├── .gitignore
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── App.vue
│   │   │   ├── api/
│   │   │   │   ├── jokes.ts
│   │   │   │   └── nasa.ts
│   │   │   ├── composables/
│   │   │   │   └── useCachedRequest.ts
│   │   │   ├── main.ts
│   │   │   ├── router.ts
│   │   │   ├── shims-vue.d.ts
│   │   │   ├── stores/
│   │   │   │   ├── cart.ts
│   │   │   │   ├── counter.ts
│   │   │   │   ├── counterSetup.ts
│   │   │   │   ├── demo-counter.ts
│   │   │   │   ├── jokes-swrv.ts
│   │   │   │   ├── jokes.ts
│   │   │   │   ├── jokesUsePromised.ts
│   │   │   │   ├── nasa-pod.ts
│   │   │   │   ├── nasa.ts
│   │   │   │   ├── user.ts
│   │   │   │   └── wholeStore.ts
│   │   │   ├── test.ts
│   │   │   ├── views/
│   │   │   │   ├── 404.vue
│   │   │   │   ├── About.vue
│   │   │   │   ├── AllStores.vue
│   │   │   │   ├── AllStoresDispose.vue
│   │   │   │   ├── CounterSetupStore.vue
│   │   │   │   ├── CounterStore.vue
│   │   │   │   ├── DemoCounter.vue
│   │   │   │   ├── Jokes.vue
│   │   │   │   ├── JokesPromised.vue
│   │   │   │   ├── NasaPOD.vue
│   │   │   │   ├── NasaPODSwrv.vue
│   │   │   │   └── swrv.vue
│   │   │   └── vite-env.d.ts
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   ├── size-check/
│   │   ├── package.json
│   │   ├── rollup.config.mjs
│   │   ├── scripts/
│   │   │   └── check-size.mjs
│   │   └── src/
│   │       └── pinia.js
│   └── testing/
│       ├── CHANGELOG.md
│       ├── README.md
│       ├── package.json
│       ├── src/
│       │   ├── index.ts
│       │   ├── initialState.spec.ts
│       │   ├── mocked-store.spec.ts
│       │   ├── restoreGetters.spec.ts
│       │   ├── restoreGetters.ts
│       │   ├── testing.spec.ts
│       │   └── testing.ts
│       ├── tsconfig.build.json
│       └── tsdown.config.ts
├── pnpm-workspace.yaml
├── renovate.json
├── scripts/
│   ├── docs-check.sh
│   ├── release.mjs
│   └── verifyCommit.mjs
├── tsconfig.json
└── vitest.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Code of Conduct

As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.

We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.

Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.

This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing

Contributions are welcome and will be fully credited!

We accept contributions via Pull Requests on [Github](https://github.com/vuejs/pinia).

## Pull Requests

Here are some guidelines to make the process smoother:

- **Add a test** - New features and bugfixes need tests. If you find it difficult to test, please tell us in the pull request and we will try to help you!
- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
- **Run `npm test` locally** - This will allow you to go faster
- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
- **Send coherent history** - Make sure your commits message means something
- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.

## Creating issues

### Bug reports

Always try to provide as much information as possible. If you are reporting a bug, try to provide a repro on [this playground](https://play.pinia.vuejs.org) (or anything else) or a stacktrace at the very least. This will help us check the problem quicker.

### Feature requests

Lay out the reasoning behind it and propose an API for it. Ideally, you should have a practical example to prove the utility of the feature you're requesting.

## Contributing Docs

All the documentation files can be found in `packages/docs`. It contains the English markdown files while translation(s) are stored in their corresponding `<lang>` sub-folder(s):

- [`zh`](https://github.com/vuejs/pinia/tree/v2/packages/docs/zh): Chinese translation.

Besides that, the `.vitepress` sub-folder contains the config and theme, including the i18n information.

Contributing to the English docs is the same as contributing to the source code. You can create a pull request to our GitHub repo. However, if you would like to contribute to the translations, there are two options and some extra steps to follow:

### Translate in a `<lang>` sub-folder and host it on our official repo

If you want to start translating the docs in a _new_ language:

1. Create the corresponding `<lang>` sub-folder for your translation.
2. Modify the i18n configuration in the `.vitepress` sub-folder.
3. Translate the docs and run the doc site to self-test locally.
4. Create a checkpoint for your language by running `pnpm run docs:translation:update <lang> [<commit>]`. A checkpoint is the hash and date of the latest commit when you do the translation. The checkpoint information is stored in the status file `packages/docs/.vitepress/translation-status.json`. _It's crucial for long-term maintenance since all the further translation sync-ups are based on their previous checkpoints._
5. Commit all the changes and create a pull request to our GitHub repo.

> [!TIP]
> When you create the checkpoint, please remember to pass the second "commit" argument as `v2` since that's the base branch of Pinia now.

We will have a paragraph at the top of each translation page that shows the translation status. That way, users can quickly determine if the translation is up-to-date or lags behind the English version.

Speaking of the up-to-date translation, we also need good long-term maintenance for every language. If you want to _update_ an existing translation:

1. See what translation you need to sync up with the original docs. There are two popular ways:
   1. Via the [GitHub Compare](https://github.com/vuejs/pinia/compare/) page, only see the changes in `packages/docs/*` from the checkpoint hash to `v2` branch. You can find the checkpoint hash for your language via the translation status file `packages/docs/.vitepress/translation-status.json`. The compare page can be directly opened with the hash as part of the URL, e.g. https://github.com/vuejs/pinia/compare/c67a5c9...v2
   2. Via a local command: `pnpm run docs:translation:compare <lang> v2`.
2. Create your own branch and start the translation update, following the previous comparison.
3. Create a checkpoint for your language by running `pnpm run docs:translation:update <lang> v2`.
4. Commit all the changes and create a pull request to our GitHub repo.

<!-- TODO: add an example once we have got one -->

> [!TIP]
> Before you create the new checkpoint, please remember fetch the latest v2 branch ahead.

### Self-host the translation

You can also host the translation on your own. To create one, fork our GitHub repo and change the content and site config in `packages/docs`. To long-term maintain it, we _highly recommend_ a similar way that we do above for our officially hosted translations:

- Ensure you maintain the _checkpoint_ properly. Also, ensure the _translation status_ is well-displayed on the top of each translation page.
- Utilize the diff result between the latest official repository and your own checkpoint to guide your translation.

Tip: you can add the official repo as a remote to your forked repo. This way, you can still run `pnpm run docs:translation:update <lang> [<commit>]` and `npm run docs:translation:compare <lang> [<commit>]` to get the checkpoint and diff result:

```bash
# prepare the upstream remote
git remote add upstream git@github.com:vuejs/pinia.git
git fetch upstream v2

# set the checkpoint
pnpm run docs:translation:update <lang> upstream/v2

# get the diff result
pnpm run docs:translation:compare <lang> upstream/v2
```

<!-- TODO: add an example once we have got one -->

### Translating Search text

The search box is powered by Algolia and you will need to translate the properties. Inspire yourself from `.vitepress/config/zh.ts` `zhSearch` variable.


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: "\U0001F41E Bug report"
description: Report an issue with Pinia
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this bug report!
  - type: input
    id: reproduction
    attributes:
      label: Reproduction
      description: "If possible, provide a boiled down editable reproduction using [this playground](https://play.pinia.vuejs.org), a CodeSandbox or a GitHub repository based on [this template](https://github.com/piniajs/bug-report). A failing unit test is even better! Otherwise provide as much information as possible to reproduce the problem. There are other [examples of different environments](https://github.com/piniajs?q=example&type=source) that can be used as a bug reproduction. If no reproduction is provided and the information is not enough to reproduce the problem, we won't be able to give it a look **and the issue will be converted into a question and moved to discussions**."
      placeholder: Reproduction
    validations:
      required: true
  - type: textarea
    id: steps
    attributes:
      label: Steps to reproduce the bug
      description: |
        1. Click on ...
        2. Check log
    validations:
      required: true
  - type: textarea
    id: expected-behavior
    attributes:
      label: Expected behavior
      description: A clear and concise description of what you expected to happen.
    validations:
      required: true
  - type: textarea
    id: actual-behavior
    attributes:
      label: Actual behavior
      description: 'A clear and concise description of what actually happens.'
    validations:
      required: true
  - type: textarea
    id: other-info
    attributes:
      label: Additional information
      description: Add any other context about the problem here.
  - type: markdown
    attributes:
      value: |
        ## Before creating an issue make sure that:
        - This hasn't been [reported before](https://github.com/vuejs/pinia/issues).
        - The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug **with no external dependencies (e.g. vuetify)**


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: 👨‍💻 Support
    url: https://cal.com/posva/consultancy
    about: Get direct help from the author of Pinia with your project
  - name: 🔧 Vue 2 Official Support
    url: https://cal.com/posva/consultancy
    about: Is your project still on Vue 2? Get official support
  - name: ❓ Questions
    url: https://github.com/vuejs/pinia/discussions/new?category=Q-A
    about: Ask a question or discuss about Pinia
  - name: 💡 Ideas
    url: https://github.com/vuejs/pinia/discussions/new?category=Ideas
    about: Start a discussion to improve Pinia
  - name: 🌟 GitHub Sponsors
    url: https://github.com/sponsors/posva
    about: Like this project? Please consider supporting the author.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: "\U0001F680 New feature proposal"
description: Suggest an idea for Pinia
labels: ['feature request']
body:
  - type: markdown
    attributes:
      value: |
        Thanks for your interest in the project and taking the time to fill out this feature report!
  - type: textarea
    id: feature-description
    attributes:
      label: What problem is this solving
      description: 'A clear and concise description of what the problem is. Ex. when using the function X we cannot do Y.'
    validations:
      required: true
  - type: textarea
    id: proposed-solution
    attributes:
      label: Proposed solution
      description: 'A clear and concise description of what you want to happen with an API proposal when applicable'
    validations:
      required: true
  - type: textarea
    id: alternative
    attributes:
      label: Describe alternatives you've considered
      description: A clear and concise description of any alternative solutions or features you've considered.
  - type: markdown
    attributes:
      value: |
        ## Before creating a feature request make sure that:
        - This hasn't been [requested before](https://github.com/vuejs/pinia/issues).


================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
<!--

IMPORTANT: use https://github.com/vuejs/pinia/issues/new or the issue will be closed.

-->


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
Please make sure to include a test! If this is closing an
existing issue, reference that issue as well.
-->


================================================
FILE: .github/commit-convention.md
================================================
## Git Commit Message Convention

> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).

#### TL;DR:

Messages must be matched by the following regex:

```text
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,50}/
```

#### Examples

Appears under "Features" header, `link` subheader:

```
feat(link): add `force` option
```

Appears under "Bug Fixes" header, `view` subheader, with a link to issue #28:

```
fix(view): handle keep-alive with aborted navigations

close #28
```

Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation:

```
perf: improve guard extraction

BREAKING CHANGE: The 'beforeRouteEnter' option has been removed.
```

The following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the "Reverts" header.

```
revert: feat(compiler): add 'comments' option

This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
```

### Full Message Format

A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:

```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```

The **header** is mandatory and the **scope** of the header is optional.

### Revert

If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.

### Type

If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However, if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.

Other prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.

### Scope

The scope could be anything specifying the place of the commit change. For example `core`, `compiler`, `ssr`, `v-model`, `transition` etc...

### Subject

The subject contains a succinct description of the change:

- use the imperative, present tense: "change" not "changed" nor "changes"
- don't capitalize the first letter
- no dot (.) at the end

### Body

Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.

### Footer

The footer should contain any information about **Breaking Changes** and is also the place to
reference GitHub issues that this commit **Closes**.

**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.


================================================
FILE: .github/funding.yml
================================================
github: posva
custom: https://www.paypal.me/posva


================================================
FILE: .github/settings.yml
================================================
labels:
  - name: bug
    color: ee0701
  - name: contribution welcome
    color: 0e8a16
  - name: discussion
    color: 4935ad
  - name: docs
    color: 8be281
  - name: enhancement
    color: a2eeef
  - name: good first issue
    color: 7057ff
  - name: help wanted
    color: 008672
  - name: question
    color: d876e3
  - name: wontfix
    color: ffffff
  - name: WIP
    color: ffffff
  - name: need repro
    color: c9581c
  - name: feature request
    color: fbca04


================================================
FILE: .github/workflows/ci.yml
================================================
name: ci

on:
  push:
    paths-ignore:
      - 'packages/docs/**'
      - 'packages/playground/**'
  pull_request:
    branches: [v2, v3]
    paths-ignore:
      - 'packages/docs/**'
      - 'packages/playground/**'

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: lts/*
          cache: pnpm

      - run: pnpm install --frozen-lockfile
      - run: pnpm run lint
      - run: pnpm run test:types
      - run: pnpm run -r dev:prepare
      - run: pnpm run test:vitest
      - run: pnpm run build
      - run: pnpm run test:dts
      - run: pnpm run size

      - uses: codecov/codecov-action@v5
        with:
          token: ${{ secrets.CODECOV_TOKEN }}


================================================
FILE: .github/workflows/pkg.pr.new.yml
================================================
name: Publish Any Commit

on:
  pull_request:
    branches: [v2, v3]
    paths-ignore:
      - 'packages/docs/**'
      - 'packages/playground/**'

  push:
    branches:
      - '**'
    tags:
      - '!**'
    paths-ignore:
      - 'packages/docs/**'
      - 'packages/playground/**'

jobs:
  build:
    if: github.repository == 'vuejs/pinia'
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: lts/*
          cache: pnpm

      - name: Install
        run: pnpm install --frozen-lockfile

      - name: Build
        run: pnpm build

      - name: Release
        run: pnpm dlx pkg-pr-new publish --compact --pnpm './packages/*'


================================================
FILE: .github/workflows/release-tag.yml
================================================
on:
  push:
    tags:
      - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10

name: Create Release

jobs:
  build:
    name: Create Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
      - name: Create Release for Tag
        id: release_tag
        uses: yyx990803/release-tag@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          body: |
            Please refer to [CHANGELOG.md](https://github.com/vuejs/pinia/blob/v3/packages/pinia/CHANGELOG.md) for details.


================================================
FILE: .github/workflows/update-sponsors.yml
================================================
name: Update sponsors

on:
  schedule:
    - cron: '0 0 * * *'
  workflow_dispatch: {}

permissions:
  contents: write

jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Set up pnpm
        uses: pnpm/action-setup@v4

      - name: Set up Node
        uses: actions/setup-node@v6
        with:
          node-version: lts/*
          cache: pnpm

      - name: Fetch sponsor data
        run: |
          set -euo pipefail
          curl -fsSL https://sponsors.esm.is/sponsors-docs.json \
            -o packages/docs/.vitepress/theme/components/sponsors.json
          curl -fsSL https://sponsors.esm.is/sponsors-docs-html \
            -o /tmp/sponsors-docs.html
          python - <<'PY'
          from pathlib import Path

          readme_path = Path("README.md")
          html_path = Path("/tmp/sponsors-docs.html")

          start_marker = "<!--sponsors start-->"
          end_marker = "<!--sponsors end-->"

          content = readme_path.read_text()
          start_index = content.find(start_marker)
          end_index = content.find(end_marker)
          if start_index == -1 or end_index == -1 or end_index < start_index:
              raise SystemExit("Could not find sponsors markers in README.md")

          before = content[:start_index]
          after = content[end_index + len(end_marker):]
          html = html_path.read_text().strip()

          new_content = f"{before}{html}{after}"
          if new_content != content:
              readme_path.write_text(new_content)
          PY

      # allows oxfmt to run on commit
      - name: Install deps
        run: |
          pnpm install --frozen-lockfile

      - name: Format sponsor files
        run: |
          pnpm exec oxfmt packages/docs/.vitepress/theme/components/sponsors.json README.md

      - name: Check for changes
        id: sponsors-after
        run: |
          if git diff --quiet -- packages/docs/.vitepress/theme/components/sponsors.json README.md; then
            echo "changed=false" >> "$GITHUB_OUTPUT"
          else
            echo "changed=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Commit
        if: steps.sponsors-after.outputs.changed == 'true'
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add packages/docs/.vitepress/theme/components/sponsors.json README.md
          git commit -m "chore: update sponsors" --no-verify
          git push


================================================
FILE: .gitignore
================================================
node_modules
coverage
npm-debug.log
yarn-error.log
.nyc_output
coverage.lcov
dist
.DS_Store
temp
test-dts/tsconfig.tsbuildinfo
.env
packages/*/LICENSE
explorations
docs-api
packages/docs/api
.yalc
yalc.lock
.idea
.vitepress/cache
tsconfig.vitest-temp.json
.osgrep


================================================
FILE: .npmrc
================================================
shamefully-hoist=true
strict-peer-dependencies=false


================================================
FILE: .oxfmtrc.json
================================================
{
  "$schema": "./node_modules/oxfmt/configuration_schema.json",
  "semi": false,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "ignorePatterns": [
    "__build__",
    "dist",
    "coverage",
    "packages/docs/.vitepress/cache",
    "temp"
  ]
}


================================================
FILE: .vscode/launch.json
================================================
{
  "configurations": [
    {
      "type": "node",
      "name": "vscode-jest-tests",
      "request": "launch",
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "disableOptimisticBPs": true,
      "cwd": "${workspaceFolder}",
      "runtimeExecutable": "yarn",
      "args": [
        "jest",
        "--watch",
        "--runInBand",
        "--watchAll=false",
        "--collectCoverage=false"
      ]
    }
  ]
}


================================================
FILE: .vscode/settings.json
================================================
{
  "[javascript]": {
    "editor.formatOnSave": true
  },
  "[typescript]": {
    "editor.formatOnSave": true
  },
  "typescript.tsdk": "node_modules/typescript/lib",
  "jest.jestCommandLine": "yarn jest --watchAll",
  "editor.defaultFormatter": "esbenp.prettier-vscode"
}


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2019-present Eduardo San Martin Morote

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<p align="center">
  <a href="https://pinia.vuejs.org" target="_blank" rel="noopener noreferrer">
    <img width="180" src="https://pinia.vuejs.org/logo.svg" alt="Pinia logo">
  </a>
</p>
<br/>
<p align="center">
  <a href="https://npmx.dev/package/pinia"><img src="https://badgen.net/npm/v/pinia/^4" alt="npm package"></a>
  <a href="https://github.com/vuejs/pinia/actions/workflows/ci.yml"><img src="https://github.com/vuejs/pinia/actions/workflows/ci.yml/badge.svg" alt="build status"></a>
  <a href="https://codecov.io/gh/vuejs/pinia"><img src="https://codecov.io/gh/vuejs/pinia/branch/v4/graph/badge.svg?token=rU2xxQ6BGH"/></a>
</p>
<br/>

# Pinia

> Intuitive, type safe and flexible Store for Vue

- 💡 Intuitive
- 🔑 Type Safe
- ⚙️ Devtools support
- 🔌 Extensible
- 🏗 Modular by design
- 📦 Extremely light
- ⛰️ Nuxt Module

The latest version of pinia works with Vue 3. See the branch [v2](https://github.com/vuejs/pinia/tree/v2) for a version that works with Vue 2.

Pinia is the most similar English pronunciation of the word _pineapple_ in Spanish: _piña_. A pineapple is in reality a group of individual flowers that join together to create a multiple fruit. Similar to stores, each one is born individually, but they are all connected at the end. It's also a delicious tropical fruit indigenous to South America.

## 👉 [Demo with Vue 3 on StackBlitz](https://stackblitz.com/github/piniajs/example-vue-3-vite)

## 👉 [Demo with Nuxt 3 on StackBlitz](https://stackblitz.com/github/piniajs/example-nuxt-3)

## Help me keep working on this project 💚

- [Become a Sponsor on GitHub](https://github.com/sponsors/posva)
- [One-time donation via PayPal](https://paypal.me/posva)

<!--sponsors start-->

<h4 align="center">Gold Sponsors</h4>
<p align="center">
    <a href="https://www.coderabbit.ai/?utm_source=vuerouter&utm_medium=sponsor" target="_blank" rel="noopener noreferrer">
    <picture>
      <source srcset="https://posva-sponsors.pages.dev/logos/coderabbitai-dark.svg" media="(prefers-color-scheme: dark)" height="72px" alt="CodeRabbit" />
      <img src="https://posva-sponsors.pages.dev/logos/coderabbitai-light.svg" height="72px" alt="CodeRabbit" />
    </picture>
  </a>
</p>

<h4 align="center">Silver Sponsors</h4>
<p align="center">
    <a href="https://www.vuemastery.com/" target="_blank" rel="noopener noreferrer">
    <picture>
      <source srcset="https://posva-sponsors.pages.dev/logos/vuemastery-dark.png" media="(prefers-color-scheme: dark)" height="42px" alt="VueMastery" />
      <img src="https://posva-sponsors.pages.dev/logos/vuemastery-light.svg" height="42px" alt="VueMastery" />
    </picture>
  </a>
    <a href="https://www.controla.ai/?utm_source=posva" target="_blank" rel="noopener noreferrer">
    <picture>
      <source srcset="https://posva-sponsors.pages.dev/logos/controla-dark.png" media="(prefers-color-scheme: dark)" height="42px" alt="Controla" />
      <img src="https://posva-sponsors.pages.dev/logos/controla-light.png" height="42px" alt="Controla" />
    </picture>
  </a>
    <a href="https://route4me.com" target="_blank" rel="noopener noreferrer">
    <picture>
      <source srcset="https://posva-sponsors.pages.dev/logos/route4me.png" media="(prefers-color-scheme: dark)" height="42px" alt="Route Optimizer and Route Planner Software" />
      <img src="https://posva-sponsors.pages.dev/logos/route4me.png" height="42px" alt="Route Optimizer and Route Planner Software" />
    </picture>
  </a>
    <a href="https://jobs.sendcloud.com" target="_blank" rel="noopener noreferrer">
    <picture>
      <source srcset="https://posva-sponsors.pages.dev/logos/sendcloud-dark.svg" media="(prefers-color-scheme: dark)" height="42px" alt="SendCloud" />
      <img src="https://posva-sponsors.pages.dev/logos/sendcloud-light.svg" height="42px" alt="SendCloud" />
    </picture>
  </a>
</p>

<h4 align="center">Bronze Sponsors</h4>
<p align="center">
    <a href="https://stormier.ninja" target="_blank" rel="noopener noreferrer">
    <picture>
      <source srcset="https://avatars.githubusercontent.com/u/2486424" media="(prefers-color-scheme: dark)" height="26px" alt="Stanislas Ormières" />
      <img src="https://avatars.githubusercontent.com/u/2486424" height="26px" alt="Stanislas Ormières" />
    </picture>
  </a>
    <a href="https://www.rtvision.com/" target="_blank" rel="noopener noreferrer">
    <picture>
      <source srcset="https://avatars.githubusercontent.com/u/8292810" media="(prefers-color-scheme: dark)" height="26px" alt="RTVision" />
      <img src="https://avatars.githubusercontent.com/u/8292810" height="26px" alt="RTVision" />
    </picture>
  </a>
    <a href="https://storyblok.com" target="_blank" rel="noopener noreferrer">
    <picture>
      <source srcset="https://posva-sponsors.pages.dev/logos/storyblok.png" media="(prefers-color-scheme: dark)" height="26px" alt="Storyblok" />
      <img src="https://posva-sponsors.pages.dev/logos/storyblok.png" height="26px" alt="Storyblok" />
    </picture>
  </a>
</p>

<!--sponsors end-->
<!--sponsors end-->

---

## FAQ

A few notes about the project and possible questions:

**Q**: _Is Pinia the successor of Vuex?_

**A**: [Yes](https://vuejs.org/guide/scaling-up/state-management.html#pinia)

**Q**: _What about dynamic modules?_

**A**: Dynamic modules are not type safe, so instead [we allow creating different stores](https://pinia.vuejs.org/cookbook/composing-stores.html) that can be imported anywhere

## Installation

```bash
# or pnpm or yarn
npm install pinia
```

## Usage

### Install the plugin

Create a pinia (the root store) and pass it to app:

```js
// Vue 3
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')
```

For more detailed instructions, including [Nuxt configuration](https://pinia.vuejs.org/ssr/nuxt.html), check the [Documentation](https://pinia.vuejs.org).

### Create a Store

You can create as many stores as you want, and they should each exist in different files:

```ts
import { defineStore } from 'pinia'

// main is the name of the store. It is unique across your application
// and will appear in devtools
export const useMainStore = defineStore('main', {
  // a function that returns a fresh state
  state: () => ({
    counter: 0,
    name: 'Eduardo',
  }),
  // optional getters
  getters: {
    // getters receive the state as first parameter
    doubleCounter: (state) => state.counter * 2,
    // use getters in other getters
    doubleCounterPlusOne(): number {
      return this.doubleCounter + 1
    },
  },
  // optional actions
  actions: {
    reset() {
      // `this` is the store instance
      this.counter = 0
    },
  },
})
```

`defineStore` returns a function that has to be called to get access to the store:

```ts
import { useMainStore } from '@/stores/main'
import { storeToRefs } from 'pinia'

export default defineComponent({
  setup() {
    const main = useMainStore()

    // extract specific store properties
    const { counter, doubleCounter } = storeToRefs(main)

    return {
      // gives access to the whole store in the template
      main,
      // gives access only to specific state or getter
      counter,
      doubleCounter,
    }
  },
})
```

## Documentation

To learn more about Pinia, check [its documentation](https://pinia.vuejs.org).

## License

[MIT](http://opensource.org/licenses/MIT)


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

This is the list of versions of Pinia which are
currently being supported with security updates.

| Version   | Supported                                             |
| --------- | ----------------------------------------------------- |
| 4.0.x     | :white_check_mark:                                    |
| 3.0.x     | :white_check_mark:                                    |
| 2.2.x     | :x: [LTS](https://github.com/vuejs/pinia/issues/3099) |
| &lt;2.2.0 | :x:                                                   |

## Reporting a Vulnerability

To report a vulnerability, please email the details to <posva13@gmail.com>.

When a vulnerability is reported, it immediately becomes our top concern, with a full-time contributor dropping everything to work on it.

While discovering new vulnerabilities is rare, we recommend always using the latest versions of Vue, Pinia, and its official companion libraries to ensure your application remains as secure as possible.

Thanks for helping to keep Pinia secure.


================================================
FILE: codecov.yml
================================================
coverage:
  status:
    patch: off
    project:
      default:
        threshold: 2%


================================================
FILE: netlify.toml
================================================
[build]
command = "pnpm run docs:build"
ignore = "./scripts/docs-check.sh"
publish = "packages/docs/.vitepress/dist"


================================================
FILE: package.json
================================================
{
  "name": "@pinia/root",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "type": "module",
  "scripts": {
    "dev": "vitest --coverage --ui",
    "release": "node scripts/release.mjs",
    "size": "pnpm run -r size",
    "build": "pnpm run -C packages/pinia build && pnpm run -C packages/nuxt build && pnpm run -C packages/testing build",
    "docs": "pnpm run --filter ./packages/docs -r docs",
    "docs:api": "pnpm run --filter ./packages/docs -r docs:api",
    "docs:translation:compare": "pnpm run --filter ./packages/docs -r docs:translation:compare",
    "docs:translation:update": "pnpm run --filter ./packages/docs -r docs:translation:update",
    "docs:translation:status": "pnpm run --filter ./packages/docs -r docs:translation:status",
    "docs:build": "pnpm run docs:api && pnpm run --filter ./packages/docs -r docs:build",
    "docs:preview": "pnpm run --filter ./packages/docs -r docs:preview",
    "play": "pnpm run -r play",
    "lint": "oxfmt --check",
    "fmt": "oxfmt",
    "lint:fix": "pnpm fmt",
    "test": "pnpm run -r dev:prepare && pnpm run test:types && pnpm run test:vitest run && pnpm run -r test && pnpm run build && pnpm test:dts",
    "test:vitest": "vitest --coverage",
    "test:types": "tsc --build ./tsconfig.json",
    "test:dts": "pnpm run -r test:dts",
    "postinstall": "simple-git-hooks"
  },
  "devDependencies": {
    "@posva/prompts": "^2.4.4",
    "@rollup/plugin-alias": "^6.0.0",
    "@rollup/plugin-commonjs": "^29.0.0",
    "@rollup/plugin-node-resolve": "^16.0.3",
    "@rollup/plugin-replace": "^6.0.3",
    "@rollup/plugin-terser": "^0.4.4",
    "@types/lodash.kebabcase": "^4.1.9",
    "@types/node": "^24.10.0",
    "@vitest/coverage-v8": "^4.0.18",
    "@vitest/ui": "^4.0.18",
    "@vue/compiler-sfc": "~3.5.22",
    "@vue/server-renderer": "~3.5.22",
    "chalk": "^5.6.2",
    "conventional-changelog-cli": "^2.2.2",
    "execa": "^9.6.0",
    "globby": "^15.0.0",
    "happy-dom": "^20.4.0",
    "lint-staged": "^16.2.7",
    "lodash.kebabcase": "^4.1.1",
    "minimist": "^1.2.8",
    "oxfmt": "^0.40.0",
    "p-series": "^3.0.0",
    "pascalcase": "^2.0.0",
    "rimraf": "^6.1.0",
    "rollup": "^4.52.5",
    "rollup-plugin-typescript2": "^0.36.0",
    "semver": "^7.7.3",
    "simple-git-hooks": "^2.13.1",
    "typedoc": "^0.28.14",
    "typedoc-plugin-markdown": "~4.9.0",
    "typescript": "~5.9.3",
    "vitest": "^4.0.18",
    "vue": "~3.5.22"
  },
  "simple-git-hooks": {
    "pre-commit": "pnpm lint-staged",
    "commit-msg": "node scripts/verifyCommit.mjs"
  },
  "lint-staged": {
    "*": "oxfmt --no-error-on-unmatched-pattern"
  },
  "packageManager": "pnpm@10.32.1",
  "pnpm": {
    "onlyBuiltDependencies": [
      "esbuild",
      "simple-git-hooks",
      "vue-demi"
    ],
    "ignoredBuiltDependencies": [
      "@parcel/watcher"
    ]
  }
}


================================================
FILE: packages/docs/.gitignore
================================================
.vitepress/cache


================================================
FILE: packages/docs/.vitepress/config/en.ts
================================================
import type { DefaultTheme, LocaleSpecificConfig } from 'vitepress'
import typedocSidebar from '../../api/typedoc-sidebar.json'

export const META_URL = 'https://pinia.vuejs.org'
export const META_TITLE = 'Pinia 🍍'
export const META_DESCRIPTION =
  'Intuitive, type safe, light and flexible Store for Vue'

export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
  description: META_DESCRIPTION,
  head: [
    ['meta', { property: 'og:url', content: META_URL }],
    ['meta', { property: 'og:description', content: META_DESCRIPTION }],
    ['meta', { property: 'twitter:url', content: META_URL }],
    ['meta', { property: 'twitter:title', content: META_TITLE }],
    ['meta', { property: 'twitter:description', content: META_DESCRIPTION }],
  ],

  themeConfig: {
    editLink: {
      pattern: 'https://github.com/vuejs/pinia/edit/v3/packages/docs/:path',
      text: 'Suggest changes to this page',
    },

    nav: [
      // { text: 'Config', link: '/config/' },
      // { text: 'Plugins', link: '/plugins/' },
      {
        text: 'Guide',
        link: '/core-concepts/',
        activeMatch: '^/core-concepts/',
      },
      { text: 'API', link: '/api/', activeMatch: '^/api/' },
      { text: 'Cookbook', link: '/cookbook/', activeMatch: '^/cookbook/' },
      {
        text: 'Links',
        items: [
          {
            text: 'Discussions',
            link: 'https://github.com/vuejs/pinia/discussions',
          },
          {
            text: 'Changelog',
            link: 'https://github.com/vuejs/pinia/blob/v3/packages/pinia/CHANGELOG.md',
          },
          {
            text: 'Vue.js Certification',
            link: 'https://certificates.dev/vuejs/?friend=VUEROUTER&utm_source=pinia_vuejs&utm_medium=link&utm_campaign=pinia_vuejs_links&utm_content=navbar',
          },
        ],
      },
      {
        text: 'v3.x',
        items: [{ text: 'v2.x', link: 'https://v2.pinia.vuejs.org' }],
      },
    ],

    sidebar: {
      '/api/': [
        {
          text: 'API',
          items: typedocSidebar,
        },
      ],
      // catch-all fallback
      '/': [
        {
          text: 'Introduction',
          items: [
            {
              text: 'What is Pinia?',
              link: '/introduction.html',
            },
            {
              text: 'Getting Started',
              link: '/getting-started.html',
            },
          ],
        },
        {
          text: 'Core Concepts',
          items: [
            { text: 'Defining a Store', link: '/core-concepts/' },
            { text: 'State', link: '/core-concepts/state.html' },
            { text: 'Getters', link: '/core-concepts/getters.html' },
            { text: 'Actions', link: '/core-concepts/actions.html' },
            { text: 'Plugins', link: '/core-concepts/plugins.html' },
            {
              text: 'Stores outside of components',
              link: '/core-concepts/outside-component-usage.html',
            },
          ],
        },
        {
          text: 'Server-Side Rendering (SSR)',
          items: [
            {
              text: 'Vue and Vite',
              link: '/ssr/',
            },
            {
              text: 'Nuxt',
              link: '/ssr/nuxt.html',
            },
          ],
        },
        {
          text: 'Cookbook',
          collapsed: false,
          items: [
            {
              text: 'Index',
              link: '/cookbook/',
            },
            {
              text: 'Migration from Vuex ≤4',
              link: '/cookbook/migration-vuex.html',
            },
            {
              text: 'Hot Module Replacement',
              link: '/cookbook/hot-module-replacement.html',
            },
            {
              text: 'Testing',
              link: '/cookbook/testing.html',
            },
            {
              text: 'Usage without setup()',
              link: '/cookbook/options-api.html',
            },
            {
              text: 'Composing Stores',
              link: '/cookbook/composing-stores.html',
            },
            {
              text: 'VSCode Snippets',
              link: '/cookbook/vscode-snippets.html',
            },
            {
              text: 'Migration from v2 to v3',
              link: '/cookbook/migration-v2-v3.html',
            },
            {
              text: 'Migration from v0/v1 to v2',
              link: '/cookbook/migration-v1-v2.html',
            },
            {
              text: 'Dealing with composables',
              link: '/cookbook/composables.html',
            },
          ],
        },
      ],
    },
  },
}


================================================
FILE: packages/docs/.vitepress/config/index.ts
================================================
import { defineConfig } from 'vitepress'
import { enConfig } from './en'
import { sharedConfig } from './shared'
import { zhConfig } from './zh'

export default defineConfig({
  ...sharedConfig,

  locales: {
    root: { label: 'English', lang: 'en-US', link: '/', ...enConfig },
    zh: { label: '简体中文', lang: 'zh-CN', link: '/zh/', ...zhConfig },
    es: {
      label: 'Español',
      lang: 'es-ES',
      link: 'https://es-pinia.vercel.app/',
    },
    ko: {
      label: '한국어',
      lang: 'ko-KR',
      link: 'https://pinia.vuejs.kr/',
    },
    pt: {
      label: 'Português',
      lang: 'pt-PT',
      link: 'https://pinia-docs-pt.netlify.app/',
    },
    uk: {
      label: 'Українська',
      lang: 'uk-UA',
      link: 'https://pinia-ua.netlify.app',
    },
    ru: {
      label: 'Русский',
      lang: 'ru-RU',
      link: 'https://pinia-ru.netlify.app',
    },
  },
})


================================================
FILE: packages/docs/.vitepress/config/shared.ts
================================================
import { defineConfig, HeadConfig } from 'vitepress'
import { zhSearch } from './zh'

export const META_IMAGE = 'https://pinia.vuejs.org/social.png'
export const isProduction =
  process.env.NETLIFY && process.env.CONTEXT === 'production'

if (process.env.NETLIFY) {
  console.log('Netlify build', process.env.CONTEXT)
}

const productionHead: HeadConfig[] = []

const rControl = /[\u0000-\u001f]/g
const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'“”‘’<>,.?/]+/g
const rCombining = /[\u0300-\u036F]/g

/**
 * Default slugification function
 */
export const slugify = (str: string): string =>
  str
    .normalize('NFKD')
    // Remove accents
    .replace(rCombining, '')
    // Remove control characters
    .replace(rControl, '')
    // Replace special characters
    .replace(rSpecial, '-')
    // ensure it doesn't start with a number
    .replace(/^(\d)/, '_$1')

export const sharedConfig = defineConfig({
  title: 'Pinia',
  appearance: 'dark',

  markdown: {
    theme: {
      dark: 'dracula-soft',
      light: 'vitesse-light',
    },

    attrs: {
      leftDelimiter: '%{',
      rightDelimiter: '}%',
    },

    anchor: {
      slugify,
    },
  },

  head: [
    ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }],
    ['link', { rel: 'icon', type: 'image/png', href: '/logo.png' }],

    [
      'meta',
      { name: 'wwads-cn-verify', content: '5878a7ab84fb43402106c575658472fa' },
    ],

    [
      'meta',
      {
        property: 'og:type',
        content: 'website',
      },
    ],

    [
      'meta',
      {
        property: 'twitter:card',
        content: 'summary_large_image',
      },
    ],
    [
      'meta',
      {
        property: 'twitter:image',
        content: META_IMAGE,
      },
    ],

    [
      'script',
      {
        src: 'https://cdn.usefathom.com/script.js',
        'data-site': 'KFPPRRIS',
        'data-spa': 'auto',
        defer: '',
      },
    ],

    // Vue School Top banner
    [
      'script',
      {
        src: 'https://media.bitterbrains.com/main.js?from=pinia&type=top',
        // @ts-expect-error: vitepress bug
        async: true,
        type: 'text/javascript',
      },
    ],

    ...(isProduction ? productionHead : []),
  ],

  themeConfig: {
    logo: '/logo.svg',
    outline: [2, 3],

    socialLinks: [
      { icon: 'x', link: 'https://twitter.com/posva' },
      {
        icon: 'github',
        link: 'https://github.com/vuejs/pinia',
      },
      {
        icon: 'discord',
        link: 'https://chat.vuejs.org',
      },
    ],

    footer: {
      copyright: 'Copyright © 2019-present Eduardo San Martin Morote',
      message: 'Released under the MIT License.',
    },

    editLink: {
      pattern: 'https://github.com/vuejs/pinia/edit/v3/packages/docs/:path',
      text: 'Suggest changes',
    },

    search: {
      provider: 'algolia',
      options: {
        appId: '69Y3N7LHI2',
        apiKey: '45441f4b65a2f80329fd45c7cb371fea',
        indexName: 'pinia',
        locales: { ...zhSearch },
      },
    },

    carbonAds: {
      code: 'CEBICK3I',
      // custom: 'CEBICK3M',
      placement: 'routervuejsorg',
    },
  },
})


================================================
FILE: packages/docs/.vitepress/config/zh.ts
================================================
import type { DefaultTheme, LocaleSpecificConfig } from 'vitepress'

export const META_URL = 'https://pinia.vuejs.org'
export const META_TITLE = 'Pinia 🍍'
export const META_DESCRIPTION = '值得你喜欢的 Vue Store'
// TODO: translation of this
// 'Intuitive, type safe, light and flexible Store for Vue'

export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
  description: META_DESCRIPTION,
  head: [
    ['meta', { property: 'og:url', content: META_URL }],
    ['meta', { property: 'og:description', content: META_DESCRIPTION }],
    ['meta', { property: 'twitter:url', content: META_URL }],
    ['meta', { property: 'twitter:title', content: META_TITLE }],
    ['meta', { property: 'twitter:description', content: META_DESCRIPTION }],
  ],

  themeConfig: {
    editLink: {
      pattern: 'https://github.com/vuejs/pinia/edit/v3/packages/docs/:path',
      text: '对本页提出修改建议',
    },

    outline: {
      label: '本页内容',
    },

    docFooter: {
      prev: '上一页',
      next: '下一页',
    },

    nav: [
      // { text: 'Config', link: '/config/' },
      // { text: 'Plugins', link: '/plugins/' },
      {
        text: '指南',
        link: '/zh/core-concepts/',
        activeMatch: '^/zh/core-concepts/',
      },
      { text: 'API', link: '/zh/api/', activeMatch: '^/zh/api/' },
      { text: '手册', link: '/zh/cookbook/', activeMatch: '^/zh/cookbook/' },
      {
        text: '相关链接',
        items: [
          {
            text: '论坛',
            link: 'https://github.com/vuejs/pinia/discussions',
          },
          {
            text: '更新日志',
            link: 'https://github.com/vuejs/pinia/blob/v3/packages/pinia/CHANGELOG.md',
          },
          {
            text: 'Vue.js 认证',
            link: 'https://certificates.dev/vuejs/?friend=VUEROUTER&utm_source=pinia_vuejs&utm_medium=link&utm_campaign=pinia_vuejs_links&utm_content=navbar',
          },
        ],
      },
      {
        text: 'v3.x',
        items: [{ text: 'v2.x', link: 'https://v2.pinia.vuejs.org' }],
      },
    ],
    sidebar: {
      '/zh/api/': [
        {
          text: 'packages',
          items: [
            { text: 'pinia', link: '/zh/api/modules/pinia.html' },
            { text: '@pinia/nuxt', link: '/zh/api/modules/pinia_nuxt.html' },
            {
              text: '@pinia/testing',
              link: '/zh/api/modules/pinia_testing.html',
            },
          ],
        },
      ],
      '/zh/': [
        {
          text: '介绍',
          items: [
            {
              text: 'Pinia 是什么?',
              link: '/zh/introduction.html',
            },
            {
              text: '开始',
              link: '/zh/getting-started.html',
            },
          ],
        },
        {
          text: '核心概念',
          items: [
            { text: '定义 Store', link: '/zh/core-concepts/' },
            { text: 'State', link: '/zh/core-concepts/state.html' },
            { text: 'Getter', link: '/zh/core-concepts/getters.html' },
            { text: 'Action', link: '/zh/core-concepts/actions.html' },
            { text: '插件', link: '/zh/core-concepts/plugins.html' },
            {
              text: '组件外的 Store',
              link: '/zh/core-concepts/outside-component-usage.html',
            },
          ],
        },
        {
          text: '服务端渲染 (SSR)',
          items: [
            {
              text: 'Vue 与 Vite',
              link: '/zh/ssr/',
            },
            {
              text: 'Nuxt',
              link: '/zh/ssr/nuxt.html',
            },
          ],
        },
        {
          text: '手册',
          collapsed: false,
          items: [
            {
              text: '目录',
              link: '/zh/cookbook/',
            },
            {
              text: '从 Vuex ≤4 迁移',
              link: '/zh/cookbook/migration-vuex.html',
            },
            {
              text: '热更新',
              link: '/zh/cookbook/hot-module-replacement.html',
            },
            {
              text: '测试',
              link: '/zh/cookbook/testing.html',
            },
            {
              text: '不使用 setup() 的用法',
              link: '/zh/cookbook/options-api.html',
            },
            {
              text: '组合式 Stores',
              link: '/zh/cookbook/composing-stores.html',
            },
            {
              text: 'VSCode 代码片段',
              link: '/zh/cookbook/vscode-snippets.html',
            },
            {
              text: '从 v0/v1 迁移至 v2',
              link: '/zh/cookbook/migration-v1-v2.html',
            },
            {
              text: '处理组合式函数',
              link: '/zh/cookbook/composables.html',
            },
          ],
        },
      ],
    },
  },
}

export const zhSearch: DefaultTheme.AlgoliaSearchOptions['locales'] = {
  zh: {
    placeholder: '搜索文档',
    translations: {
      button: {
        buttonText: '搜索文档',
        buttonAriaLabel: '搜索文档',
      },
      modal: {
        searchBox: {
          resetButtonTitle: '清除查询条件',
          resetButtonAriaLabel: '清除查询条件',
          cancelButtonText: '取消',
          cancelButtonAriaLabel: '取消',
        },
        startScreen: {
          recentSearchesTitle: '搜索历史',
          noRecentSearchesText: '没有搜索历史',
          saveRecentSearchButtonTitle: '保存至搜索历史',
          removeRecentSearchButtonTitle: '从搜索历史中移除',
          favoriteSearchesTitle: '收藏',
          removeFavoriteSearchButtonTitle: '从收藏中移除',
        },
        errorScreen: {
          titleText: '无法获取结果',
          helpText: '你可能需要检查你的网络连接',
        },
        footer: {
          selectText: '选择',
          navigateText: '切换',
          closeText: '关闭',
          searchByText: '搜索供应商',
        },
        noResultsScreen: {
          noResultsText: '无法找到相关结果',
          suggestedQueryText: '你可以尝试查询',
          reportMissingResultsText: '你认为该查询应该有结果?',
          reportMissingResultsLinkText: '点击反馈',
        },
      },
    },
  },
}


================================================
FILE: packages/docs/.vitepress/theme/components/AsideSponsors.vue
================================================
<script setup lang="ts">
import { computed } from 'vue'
import { VPDocAsideSponsors } from 'vitepress/theme'
import sponsors from './sponsors.json'

// to avoid the never[] type in json
interface Sponsor {
  alt: string
  href: string
  imgSrcDark: string
  imgSrcLight: string
}

const asideSponsors = computed(() => {
  return [
    ...(sponsors.platinum.length
      ? [
          {
            items: sponsors.platinum.map((sponsor: Sponsor) => ({
              name: sponsor.alt,
              url: sponsor.href,
              img: sponsor.imgSrcLight,
            })),
          },
        ]
      : []),
    {
      size: 'mini',
      items: sponsors.gold
        .map((sponsor: Sponsor) => ({
          name: sponsor.alt,
          url: sponsor.href,
          img: sponsor.imgSrcLight,
        }))
        .concat({
          name: 'Become a sponsor',
          url: 'https://github.com/sponsors/posva',
          img: '/your-logo-here.svg',
        }),
    },
    {
      size: 'xmini',
      // TODO: use gold instead once I have some
      items: sponsors.silver.map((sponsor: Sponsor) => ({
        name: sponsor.alt,
        url: sponsor.href,
        img: sponsor.imgSrcLight,
      })),
    },
  ]
})
</script>

<template>
  <VPDocAsideSponsors :data="asideSponsors" />

  <!-- <a
    class="banner mp"
    href="https://masteringpinia.com?utm=pinia-sidebar"
    target="_blank"
  >
    <img width="22" height="22" src="/mp-pinia-logo.svg" />
    <span>
      <p class="extra-info">Complete guide to</p>
      <p class="heading">Mastering Pinia</p>
      <p class="extra-info">written by its creator</p>
    </span>
  </a> -->

  <a
    class="banner cert"
    href="https://certificates.dev/vuejs/?friend=VUEROUTER&utm_source=pinia_vuejs&utm_medium=link&utm_campaign=pinia_vuejs_links&utm_content=sidebar"
    target="_blank"
  >
    <img width="22" height="22" src="/vue-cert-logo.svg" />
    <span>
      <p class="extra-info">The official</p>
      <p class="heading">Vue.js Certification</p>
      <p class="extra-info">Get certified!</p>
    </span>
  </a>
</template>

<style scoped>
.VPDocAsideSponsors {
  margin-top: 8px !important;
}

:deep(.vp-sponsor-grid.mini .vp-sponsor-grid-image) {
  max-width: 158px;
  max-height: 48px;
}
:deep(.vp-sponsor-grid.xmini .vp-sponsor-grid-image) {
  max-width: 80px;
  max-height: 32px;
}

.banner {
  margin: 0.25rem 0;
  padding: 0.4rem 0;
  border-radius: 14px;
  position: relative;
  font-size: 0.9rem;
  font-weight: 700;
  line-height: 1.1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  gap: 1rem;
  background-color: var(--vp-c-bg-alt);
  border: 2px solid var(--vp-c-bg-alt);
  transition: border-color 0.5s;
}

.banner:last-of-type {
  margin-bottom: 1rem;
}

.banner:hover {
  border: 2px solid var(--vp-c-brand-1);
}

.banner.cert:hover {
  border: 2px solid var(--vp-c-green-1);
}

.banner img {
  transition: transform 0.5s;
  transform: scale(1.25);
}
.banner:hover img {
  transform: scale(1.75);
}

.banner .extra-info {
  color: var(--vp-c-text-1);
  opacity: 0;
  font-size: 0.7rem;
  padding-left: 0.1rem;
  transition: opacity 0.5s;
}

.banner .heading {
  background-image: linear-gradient(
    120deg,
    var(--vp-c-brand-3) 16%,
    var(--vp-c-brand-2),
    var(--vp-c-brand-1)
  );
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.banner.cert .heading {
  background-image: linear-gradient(
    120deg,
    var(--vp-c-green-3) 16%,
    var(--vp-c-green-2),
    var(--vp-c-green-1)
  );
}

.banner:hover .extra-info {
  opacity: 0.9;
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/HomeSponsors.vue
================================================
<script setup lang="ts">
import HomeSponsorsGroup from './HomeSponsorsGroup.vue'
import sponsors from './sponsors.json'
import { useData } from 'vitepress'

const { site } = useData()
const translations = {
  en: 'Become a sponsor',
  'en-US': 'Become a Sponsor!',
  'zh-CN': '成为赞助者!',
}
</script>

<template>
  <div class="sponsors_outer">
    <div>
      <HomeSponsorsGroup
        v-if="sponsors.platinum.length"
        name="Platinum"
        size="96"
      />

      <HomeSponsorsGroup v-if="sponsors.gold.length" name="Gold" size="38" />

      <HomeSponsorsGroup
        v-if="sponsors.silver.length"
        name="Silver"
        size="24"
      />

      <div class="cta">
        <a class="become-sponsor" href="https://github.com/sponsors/posva">{{
          translations[site.lang] || translations.en
        }}</a>
      </div>
    </div>
  </div>
</template>

<style scoped>
.sponsors_outer {
  text-align: center;
  padding: 35px 40px 45px;
  background-color: var(--vp-c-bg-accent);
  /* transition when toggling dark mode */
  transition:
    background-color 300ms ease-in-out,
    color 300ms ease-in-out;
}

.cta {
  margin-top: 2rem;
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/HomeSponsorsGroup.vue
================================================
<template>
  <h3>{{ name }} Sponsors</h3>

  <p>
    <a
      v-for="sponsor in list"
      :key="sponsor.href"
      :href="sponsor.href"
      :title="sponsor.alt"
      target="_blank"
      rel="sponsored noopener"
      class="sponsor_wrapper"
      :class="
        isDark && sponsor.imgSrcLight === sponsor.imgSrcDark && 'apply-bg'
      "
    >
      <img
        :src="sponsor.imgSrc"
        :class="
          isDark &&
          sponsor.imgSrcLight === sponsor.imgSrcDark &&
          'invert-colors'
        "
        :alt="sponsor.alt"
        :style="{ height: size + 'px' }"
      />
    </a>
  </p>
</template>

<script setup lang="ts">
import sponsors from './sponsors.json'
import { computed } from 'vue'
import { useData } from 'vitepress'

const props = withDefaults(
  defineProps<{
    name: 'Gold' | 'Platinum' | 'Silver' | 'Bronze'
    size?: number | string
  }>(),
  {
    size: 140,
  }
)

const { isDark } = useData()

const list = computed(() =>
  sponsors[props.name.toLowerCase()].map((sponsor) => ({
    ...sponsor,
    imgSrc: isDark.value ? sponsor.imgSrcDark : sponsor.imgSrcLight,
  }))
)
</script>

<style scoped>
.sponsor_wrapper {
  padding: 5px;
  margin: 0 3px;

  border-radius: 5px;

  display: inline-block;
  text-decoration: none;
  vertical-align: middle;

  transition: background-color 300ms ease-in-out;
}

p {
  margin: 0;
}

h3 {
  font-size: 1.35rem;
  font-weight: 600;
  margin: 0.75em 0;
}

img {
  transition: all 0.3s ease;
  filter: grayscale(100%);
  opacity: 0.66;
}

html:not(.light) img.invert-colors {
  filter: invert(1) grayscale(100%);
  background-color: transparent;
}

.sponsor_wrapper.apply-bg:hover {
  background-color: var(--c-text);
}

.sponsor_wrapper:hover img {
  filter: none !important;
  opacity: 1;
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/MadVueBanner.vue
================================================
<script setup lang="ts">
import { ref, onMounted } from 'vue'

const isVisible = ref(false)
const nameStorage = 'MADVUE-BANNER-MARCH-25'
const target = 'https://madvue.es/?utm_source=pinia&utm_content=top_banner'

function closeBanner() {
  // Hide the banner
  isVisible.value = false
  // Save action in the local storage
  localStorage.setItem(nameStorage, String(true))
  document.documentElement.classList.remove('has-banner')
}

onMounted(() => {
  if (localStorage.getItem(nameStorage) != null) {
    return
  }

  isVisible.value = true
  document.documentElement.classList.add('has-banner')

  // const keys = Object.keys(localStorage).filter(
  //   (key) => key.includes('FREEWEEKEND25') && key.endsWith('_CLOSED')
  // )

  // if (
  //   keys.length > 0 &&
  //   keys.every((key) => localStorage.getItem(key) != null)
  // ) {
  //   isVisible.value = true
  //   document.documentElement.classList.add('has-banner')
  // }
})
</script>

<template>
  <div class="banner" v-if="isVisible">
    <a target="_blank" :href="target">
      <svg
        class="logo"
        viewBox="0 0 2688 734"
        version="1.1"
        style="
          fill-rule: evenodd;
          clip-rule: evenodd;
          stroke-linejoin: round;
          stroke-miterlimit: 2;
        "
      >
        <g>
          <g>
            <g>
              <path
                d="M537.782,722.201l-115.56,-0l0,-367.774l-100,367.774l-115.56,-0l-91.102,-375.543l-0,375.543l-115.56,-0l-0,-721.094l123.329,-0l147.786,512.218l139.996,-512.218l126.671,-0l0,721.094Z"
                style="fill: #f97844; fill-rule: nonzero"
              ></path>
            </g>
          </g>
        </g>
        <g>
          <g>
            <g>
              <path
                d="M985.548,722.201l-111.111,-0l-0,-37.783c-1.498,32.596 -33.334,48.894 -95.552,48.894c-42.968,-0 -82.226,-14.085 -117.773,-42.231c-37.782,-30.361 -56.684,-66.276 -56.684,-107.769c-0,-26.671 7.422,-53.516 22.222,-80.556c14.822,-27.04 32.965,-48.698 54.449,-64.996c25.933,-19.249 60.373,-28.884 103.342,-28.884c37.022,-0 67.035,5.186 89.996,15.56l-0,-30.013c-0,-62.218 -24.089,-93.316 -72.223,-93.316c-20.746,-0 -36.306,5.555 -46.679,16.666c-7.401,8.138 -14.801,23.698 -22.223,46.658l-109.982,0c-0,-45.92 16.298,-85.178 48.871,-117.773c32.596,-32.596 71.854,-48.893 117.795,-48.893l28.885,-0c45.92,-0 85.178,16.297 117.773,48.893c32.596,32.595 48.894,71.853 48.894,117.773l-0,357.77Zm-111.111,-154.449c-0,-19.987 -7.965,-37.023 -23.894,-51.107c-15.928,-14.063 -33.897,-21.115 -53.884,-21.115c-20.009,-0 -39.453,7.964 -58.334,23.893c-18.901,15.929 -28.342,33.528 -28.342,52.778c0,19.27 10.005,35.177 30.013,47.786c17.774,11.849 36.654,17.773 56.663,17.773c19.987,0 37.956,-6.857 53.884,-20.551c15.929,-13.715 23.894,-30.186 23.894,-49.457Z"
                style="fill: #f97844; fill-rule: nonzero"
              ></path>
            </g>
          </g>
        </g>
        <g>
          <g>
            <g>
              <path
                d="M1417.74,-0l-1.107,722.201l-106.662,-0l-0,-32.227c-14.822,28.885 -45.183,43.338 -91.103,43.338c-45.92,-0 -85.178,-16.298 -117.773,-48.894c-32.596,-32.595 -48.893,-71.853 -48.893,-117.773l-0,-202.214c-0,-45.92 16.297,-85.178 48.893,-117.773c32.595,-32.596 71.853,-48.893 117.773,-48.893l28.885,-0c20.746,-0 39.258,10.373 55.555,31.12l-1.106,-227.778l115.538,-1.107Zm-115.538,359.983c-0,-42.21 -22.223,-63.325 -66.667,-63.325c-48.156,-0 -72.222,31.098 -72.222,93.338l-0,146.658c-0,62.218 22.591,93.337 67.773,93.337c18.511,0 34.809,-6.315 48.893,-18.902c14.063,-12.586 21.094,-28.146 21.094,-46.658l1.129,-204.448Z"
                style="fill: #f97844; fill-rule: nonzero"
              ></path>
            </g>
          </g>
        </g>
        <g>
          <g>
            <g>
              <path
                d="M1845.57,1.107l-127.778,722.2l-118.88,-1.106l-123.329,-721.094l115.538,-0l67.795,488.867l71.094,-488.867l115.56,-0Z"
                style="fill: #c4d141; fill-rule: nonzero"
              ></path>
            </g>
          </g>
        </g>
        <g>
          <g>
            <g>
              <path
                d="M2262.22,723.307l-115.56,-1.106l1.129,-27.778c-17.036,20.746 -48.893,31.12 -95.551,31.12c-45.942,-0 -83.334,-16.125 -112.218,-48.351c-28.906,-32.205 -43.338,-71.658 -43.338,-118.316l1.107,-368.88l108.876,-0l1.128,343.316c0,62.218 24.067,93.337 72.222,93.337c43.685,0 65.539,-21.115 65.539,-63.324l-1.107,-373.329l116.667,-0l1.106,533.311Z"
                style="fill: #c4d141; fill-rule: nonzero"
              ></path>
            </g>
          </g>
        </g>
        <g>
          <g>
            <g>
              <path
                d="M2687.76,566.645c-0,45.92 -16.298,85.178 -48.872,117.773c-32.595,32.596 -71.853,48.894 -117.773,48.894l-28.906,-0c-45.921,-0 -85.178,-16.298 -117.774,-48.894c-32.595,-32.595 -48.871,-71.853 -48.871,-117.773l-0,-202.214c-0,-45.92 16.276,-85.178 48.871,-117.773c32.596,-32.596 71.853,-48.893 117.774,-48.893l28.906,-0c45.92,-0 85.178,16.297 117.773,48.893c32.574,32.595 48.872,71.853 48.872,117.773l-0,153.321l-251.107,-0l0,18.902c0,34.809 7.791,59.244 23.351,73.329c15.538,14.062 41.102,21.115 76.649,21.115c20.009,0 33.702,-21.484 41.124,-64.453l109.983,-0Zm-111.111,-141.102l-0,-35.547c-0,-62.24 -24.067,-93.338 -72.201,-93.338c-45.204,-0 -67.795,31.098 -67.795,93.338l0,34.44l139.996,1.107Z"
                style="fill: #c4d141; fill-rule: nonzero"
              ></path>
            </g>
          </g>
        </g>
      </svg>
    </a>

    <div>
      <div class="headline">
        <a target="_blank" :href="target" class="vt-tagline"
          >The Vue.js Event in Madrid</a
        >
        <span class="place"> · Spain</span>
        <span class="vt-date"> · 29 May 2025</span>
      </div>

      <div class="claim">
        <span class="mv-text-primary">Discounted</span> tickets available
        <span class="mv-text-primary">Get 10% off</span>
      </div>
    </div>
    <a target="_blank" class="action" :href="target">
      More Info
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="15"
        height="15"
        style="margin-left: 10px"
        viewBox="0 0 15 15"
      >
        <path
          fill="currentColor"
          d="M8.293 2.293a1 1 0 0 1 1.414 0l4.5 4.5a1 1 0 0 1 0 1.414l-4.5 4.5a1 1 0 0 1-1.414-1.414L11 8.5H1.5a1 1 0 0 1 0-2H11L8.293 3.707a1 1 0 0 1 0-1.414"
        />
      </svg>
    </a>
    <div class="close-btn" @click.stop.prevent="closeBanner">
      <span class="close">&times;</span>
    </div>
  </div>
</template>

<style>
html.has-banner {
  --vp-layout-top-height: 72px;
}
</style>

<style scoped>
.banner {
  position: fixed;
  box-sizing: border-box;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  height: var(--vp-layout-top-height);
  font-weight: 600;
  color: #fff;
  background: #0f172a;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
}

.banner .logo {
  height: 16px;
}

.banner .headline {
  font-size: 14px;
}

.banner .claim {
  display: none;
}

.banner-dismissed .banner {
  display: none;
}

.mv-text-primary {
  color: #c4d141;
}

.banner .action {
  display: none;
  background: #f97844;
  color: #fff;
  padding: 3px 8px;
  font-size: 13px;
  align-items: center;
  border-radius: 8px;
  text-decoration: none;
}

.banner .action svg {
  display: none;
}

.action:hover {
  text-decoration: none;
  background: #c4d141;
}

.close {
  font-size: 24px;
  line-height: 24px;
  height: 24px;
}

.banner .close-btn {
  top: 50%;
  transform: translateY(-50%);
  height: 24px;
  left: 16px;
  z-index: 99;
  position: absolute;
  cursor: pointer;
}

.banner .place {
  display: none;
}

@media (min-width: 768px) {
  .banner {
    gap: 20px;
    text-align: center;
    flex-direction: row;
  }

  .banner .place {
    display: inline;
  }

  .banner .action {
    display: flex;
  }

  .banner .action svg {
    display: block;
  }

  .banner .logo {
    height: 20px;
  }

  .banner .headline {
    font-size: 15px;
  }

  .banner .claim {
    font-size: 13px;
    display: block;
  }

  .banner .action {
    padding: 0 10px;
    height: 32px;
    font-size: 13px;
  }
}

@media (min-width: 960px) {
  .banner {
    gap: 40px;
  }

  .banner .logo {
    height: 32px;
  }

  .banner .headline {
    font-size: 20px;
  }

  .banner .claim {
    font-size: 18px;
    margin-top: 4px;
  }

  .banner .action {
    padding: 0 14px;
    height: 40px;
    font-size: 16px;
  }
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/MasteringPiniaLink.vue
================================================
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, ref } from 'vue'

// TODO: split into 2 components

const { site } = useData()
const translations = {
  videoLink: {
    'en-US':
      'Master this and much more with the official video course by the author of Pinia',
    'zh-CN': '通过 Pinia 作者的官方视频课程掌握更多内容',
  },
  videoEmbed: {
    'en-US': 'Master this and much more with a free video from Mastering Pinia',
    'zh-CN': '通过 Mastering Pinia 的免费视频掌握更多内容',
  },
  watchMoreA: {
    'en-US': 'Watch more on ',
    'zh-CN': '在 ',
  },
  watchMoreB: {
    'en-US': '',
    'zh-CN': ' 上观看更多内容',
  },
}
const props = defineProps<{ href: string; title: string; mpLink?: string }>()
const isVideo = computed(() => props.href.startsWith('https://play.gumlet.io/'))
const isVideoOpen = ref(false)
</script>

<template>
  <div class="mp">
    <template v-if="isVideo">
      <div class="video-embed" v-if="isVideoOpen">
        <div style="padding: 56.25% 0 0 0; position: relative">
          <iframe
            loading="lazy"
            title="Gumlet video player"
            false
            :src="
              href +
              '?preload=false&autoplay=true&loop=false&disable_player_controls=false'
            "
            style="
              border: none;
              position: absolute;
              top: 0;
              left: 0;
              height: 100%;
              width: 100%;
            "
            allow="
              accelerometer;
              gyroscope;
              autoplay;
              encrypted-media;
              picture-in-picture;
              fullscreen;
            "
            frameborder="0"
            allowfullscreen
          >
          </iframe>
        </div>

        <div class="watch-more">
          {{ translations.watchMoreA[site.lang] }}
          <a
            :href="mpLink || 'https://masteringpinia.com'"
            target="_blank"
            rel="noopener"
          >
            Mastering Pinia
            <img src="/mp-pinia-logo.svg" alt="mastering pinia logo" />
          </a>
          {{ translations.watchMoreB[site.lang] }}
        </div>
      </div>

      <button class="cta" :title v-else @click="isVideoOpen = true">
        <div class="text">
          <slot>{{ translations.videoEmbed[site.lang] }}</slot>
        </div>
      </button>
    </template>
    <a
      v-else
      :href="href"
      target="_blank"
      rel="noopener"
      :title="title"
      class="no-icon cta"
    >
      <div class="text">
        <slot>{{ translations.videoLink[site.lang] }}</slot>
      </div>
    </a>
  </div>
</template>

<style scoped>
.mp {
  margin-top: 20px;
}
.mp:has(.cta) {
  position: relative;
  transition: border-color 0.5s;
  padding: 1em 1.25em;
  background-color: var(--vp-code-block-bg);
  border-radius: 1em;
  border: 2px solid var(--vp-c-bg-alt);
}
.mp:has(.cta):hover {
  border: 2px solid var(--vp-c-brand-1);
}
.cta:hover {
  color: var(--vp-c-brand-1);
  text-decoration: underline;
}

.watch-more {
  text-align: end;
  font-size: 0.8em;
  background-color: var(--vp-code-block-bg);
  border-radius: 0 0 1em 1em;
  padding-right: 1em;
}

.watch-more img {
  height: 1em;
  display: inline-block;
  /* vertical-align: middle; */
}

.cta {
  font-size: inherit;
  color: var(--c-text);
  position: relative;
  display: flex;
  gap: 1em;
  grid-template-columns: 1fr auto;
  align-items: center;
  justify-content: space-between;
  padding-left: 36px;
}
.cta .content {
  flex-grow: 1;
}
.cta:before {
  content: '';
  position: absolute;
  display: block;
  width: 30px;
  height: 30px;
  top: calc(50% - 15px);
  left: -4px;
  border-radius: 50%;
  background-color: var(--vp-c-brand-1);
}
html.dark .cta:after {
  --play-icon-color: #000;
}
.cta:after {
  content: '';
  position: absolute;
  display: block;
  width: 0;
  height: 0;
  top: calc(50% - 5px);
  left: 8px;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 9px solid var(--play-icon-color, #fff);
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/PiniaLogo.vue
================================================
<template>
  <svg
    id="pinia-logo"
    viewBox="0 0 408 520"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    style="z-index: 1; width: 100%"
    ref="svgEl"
  >
    <g class="leaves">
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M178.604 253.684C210.877 222.936 201.029 184.907 171.8 138.825C142.571 92.7423 83.7343 76.3478 72.7579 86.8057C61.7814 97.2637 59.3484 172.034 88.577 218.116C117.806 264.198 146.331 284.432 178.604 253.684Z"
        fill="url(#paint0_linear)"
      />
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M206.508 257.94C230.384 295.582 262.303 281.819 301.811 244.176C341.32 206.534 357.702 134.428 349.582 121.625C341.462 108.822 279.752 109.464 240.243 147.107C200.734 184.749 182.633 220.298 206.508 257.94Z"
        fill="url(#paint1_linear)"
      />
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M193.454 237.231C233.099 242.099 248.689 205.474 255.958 146.272C263.227 87.0698 233.262 24.497 219.778 22.8414C206.295 21.1858 160.995 74.5172 153.726 133.719C146.457 192.922 153.81 232.364 193.454 237.231Z"
        fill="url(#paint2_linear)"
      />
    </g>

    <g class="body">
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M192.63 519.038C275.748 519.038 343.156 494.893 343.156 385.393C343.156 275.893 275.748 186.038 192.63 186.038C109.511 186.038 42.156 275.893 42.156 385.393C42.156 494.893 109.511 519.038 192.63 519.038Z"
        fill="url(#paint3_linear)"
      />
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M310.811 401.034C308.621 398.93 305.139 398.999 303.034 401.189L229.034 478.189C226.93 480.379 226.999 483.861 229.189 485.966C231.379 488.07 234.861 488.001 236.966 485.811L310.966 408.811C313.07 406.621 313.001 403.139 310.811 401.034Z"
        fill="#ECB732"
      />
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M239.233 409.993C237.02 412.073 236.912 415.554 238.993 417.767L285.993 467.767C288.073 469.98 291.554 470.088 293.767 468.007C295.98 465.927 296.088 462.446 294.007 460.233L247.007 410.233C244.927 408.02 241.446 407.912 239.233 409.993Z"
        fill="#ECB732"
      />
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M225.889 225.111C228.037 227.259 228.037 230.741 225.889 232.889L176.889 281.889C174.741 284.037 171.259 284.037 169.111 281.889C166.963 279.741 166.963 276.259 169.111 274.111L218.111 225.111C220.259 222.963 223.741 222.963 225.889 225.111Z"
        fill="#FFC73B"
      />
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M215.889 281.889C218.037 279.741 218.037 276.259 215.889 274.111L166.889 225.111C164.741 222.963 161.259 222.963 159.111 225.111C156.963 227.259 156.963 230.741 159.111 232.889L208.111 281.889C210.259 284.037 213.741 284.037 215.889 281.889Z"
        fill="#FFC73B"
      />

      <g class="eye-left">
        <path
          d="M111.34 359.471C123.125 360.918 133.225 357.648 133.898 352.166C134.571 346.684 125.563 341.067 113.777 339.62C101.992 338.173 91.8918 341.444 91.2187 346.925C90.5456 352.407 99.5541 358.024 111.34 359.471Z"
          fill="#EAADCC"
        />
        <template v-if="!blinking || blinking === 'open'">
          <path
            d="M150.023 321.156C149.513 335.783 137.241 347.226 122.615 346.715C107.988 346.205 96.545 333.933 97.0557 319.307C97.5665 304.68 109.838 293.237 124.464 293.748C139.091 294.258 150.534 306.53 150.023 321.156Z"
            fill="white"
          />
          <g clip-path="url(#eye-left-mask)">
            <g class="eyeball">
              <path
                d="M141.046 320.343C140.719 329.726 132.847 337.067 123.463 336.739C114.08 336.411 106.739 328.539 107.067 319.156C107.395 309.773 115.267 302.432 124.65 302.76C134.033 303.087 141.374 310.959 141.046 320.343Z"
                fill="black"
              />
              <path
                d="M125.161 316.786C125.026 320.65 121.784 323.672 117.921 323.537C114.057 323.403 111.034 320.161 111.169 316.297C111.304 312.434 114.546 309.411 118.409 309.546C122.273 309.681 125.296 312.922 125.161 316.786Z"
                fill="white"
              />
            </g>
          </g>
        </template>
        <path
          fill-rule="evenodd"
          v-else
          clip-rule="evenodd"
          d="M98.4768 310.811C97.1028 311.737 96.7396 313.602 97.6656 314.976C102.329 321.896 109.474 325.785 118.663 326.835C127.921 327.893 136.204 325.529 143.318 319.748C144.603 318.703 144.799 316.813 143.754 315.527C142.709 314.242 140.819 314.046 139.534 315.091C133.705 319.828 127.041 321.754 119.344 320.874C111.579 319.987 106.157 316.839 102.641 311.623C101.715 310.249 99.8508 309.885 98.4768 310.811Z"
          fill="#755400"
        />
      </g>

      <g class="eye-right">
        <path
          d="M263.558 365.546C275.433 365.546 285.058 361.069 285.058 355.546C285.058 350.023 275.433 345.546 263.558 345.546C251.684 345.546 242.058 350.023 242.058 355.546C242.058 361.069 251.684 365.546 263.558 365.546Z"
          fill="#EAADCC"
        />

        <template v-if="!blinking || blinking === 'open'">
          <path
            d="M279.944 325.693C279.433 340.32 267.162 351.763 252.536 351.252C237.909 350.742 226.466 338.47 226.977 323.844C227.487 309.217 239.759 297.774 254.385 298.285C269.012 298.795 280.455 311.067 279.944 325.693Z"
            fill="white"
          />
          <g clip-path="url(#eye-right-mask)">
            <g class="eyeball">
              <path
                d="M270.967 324.879C270.64 334.263 262.767 341.604 253.384 341.276C244.001 340.948 236.66 333.076 236.988 323.693C237.316 314.31 245.188 306.969 254.571 307.297C263.954 307.624 271.295 315.496 270.967 324.879Z"
                fill="black"
              />
              <path
                d="M255.082 321.323C254.947 325.187 251.705 328.209 247.842 328.074C243.978 327.939 240.955 324.698 241.09 320.834C241.225 316.971 244.467 313.948 248.33 314.083C252.194 314.218 255.217 317.459 255.082 321.323Z"
                fill="white"
              />
            </g>
          </g>
        </template>
        <path
          fill-rule="evenodd"
          v-else
          clip-rule="evenodd"
          d="M231.477 319.811C230.103 320.737 229.74 322.602 230.666 323.976C235.329 330.896 242.474 334.785 251.663 335.835C260.921 336.893 269.204 334.529 276.318 328.748C277.603 327.703 277.799 325.813 276.754 324.527C275.709 323.242 273.819 323.046 272.534 324.091C266.705 328.828 260.041 330.754 252.344 329.874C244.579 328.987 239.157 325.839 235.641 320.623C234.715 319.249 232.851 318.885 231.477 319.811Z"
          fill="#755400"
        />
      </g>

      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M70.1889 401.034C72.379 398.93 75.8608 398.999 77.9656 401.189L151.966 478.189C154.07 480.379 154.001 483.861 151.811 485.966C149.621 488.07 146.139 488.001 144.034 485.811L70.0344 408.811C67.9296 406.621 67.9988 403.139 70.1889 401.034Z"
        fill="#ECB732"
      />
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M141.767 409.993C143.98 412.073 144.088 415.554 142.007 417.767L95.0074 467.767C92.927 469.98 89.4462 470.088 87.233 468.007C85.0197 465.927 84.9121 462.446 86.9925 460.233L133.993 410.233C136.073 408.02 139.554 407.912 141.767 409.993Z"
        fill="#ECB732"
      />
      <g class="mouth">
        <path
          class="smile"
          fill-rule="evenodd"
          clip-rule="evenodd"
          d="M163.323 337.658C161.949 338.584 161.586 340.448 162.512 341.822C167.176 348.743 174.321 352.632 183.51 353.682C192.767 354.74 201.051 352.375 208.164 346.594C209.45 345.549 209.645 343.66 208.6 342.374C207.555 341.088 205.666 340.893 204.38 341.938C198.552 346.675 191.887 348.6 184.191 347.721C176.425 346.834 171.003 343.686 167.488 338.469C166.562 337.095 164.697 336.732 163.323 337.658Z"
          fill="black"
        />
        <path
          class="open"
          d="M213.046 343.089C213.046 356.089 199.012 367.537 186.862 367.537C174.712 367.537 164.177 356.078 164.177 343.078C164.177 335.899 175.857 332.075 188.008 332.075C200.158 332.075 213.046 335.909 213.046 343.089Z"
          fill="#E77777"
        />
      </g>
    </g>

    <defs>
      <linearGradient
        id="paint0_linear"
        x1="68.5172"
        y1="90.0774"
        x2="85.0979"
        y2="170.543"
        gradientUnits="userSpaceOnUse"
      >
        <stop stop-color="#52CE63" />
        <stop offset="1" stop-color="#51A256" />
      </linearGradient>
      <linearGradient
        id="paint1_linear"
        x1="359.841"
        y1="134.702"
        x2="279.366"
        y2="151.265"
        gradientUnits="userSpaceOnUse"
      >
        <stop stop-color="#52CE63" />
        <stop offset="1" stop-color="#51A256" />
      </linearGradient>
      <linearGradient
        id="paint2_linear"
        x1="219.235"
        y1="22.7747"
        x2="203.754"
        y2="148.86"
        gradientUnits="userSpaceOnUse"
      >
        <stop stop-color="#8AE99C" />
        <stop offset="1" stop-color="#52CE63" />
      </linearGradient>
      <linearGradient
        id="paint3_linear"
        x1="196.803"
        y1="244.222"
        x2="171.815"
        y2="518.625"
        gradientUnits="userSpaceOnUse"
      >
        <stop stop-color="#FFE56C" />
        <stop offset="1" stop-color="#FFC63A" />
      </linearGradient>

      <clipPath id="eye-right-mask">
        <circle cy="325px" cx="254" r="27" />
      </clipPath>
      <clipPath id="eye-left-mask">
        <circle cy="320" cx="124" r="27" />
      </clipPath>
    </defs>
  </svg>
</template>

<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
import { useSpring } from 'vue-use-spring'
import {
  useMouse,
  useEventListener,
  useDebounceFn,
  useIdle,
  whenever,
} from '@vueuse/core'

const { x: mouseX, y: mouseY } = useMouse()
const mousePos = useSpring(
  reactive({
    x: mouseX,
    y: mouseY,
  }),
  {
    mass: 1,
    tension: 120,
    friction: 34,
    precision: 1,
  }
)

const { idle } = useIdle(3000)

// make it like it's looking at the user when they are idle
whenever(idle, () => {
  const l = leftEyeCenter.value
  const r = rightEyeCenter.value
  mousePos.x = l.x + (r.x - l.x) / 2
  mousePos.y = r.y
})

const svgEl = ref<SVGElement>()
const leftEyeCenter = ref({ x: 0, y: 0 })
const rightEyeCenter = ref({ x: 0, y: 0 })

function computedEyesCenter() {
  const svg = svgEl.value
  if (svg) {
    const leftEye = svg.querySelector<SVGElement>('.eye-left .eyeball')!
    const leftEyeRect = leftEye.getBoundingClientRect()
    leftEyeCenter.value = {
      x: leftEyeRect.x + leftEyeRect.width / 2,
      y: leftEyeRect.y + leftEyeRect.height / 2,
    }

    const rightEye = svg.querySelector<SVGElement>('.eye-right .eyeball')!
    const rightEyeRect = rightEye.getBoundingClientRect()
    rightEyeCenter.value = {
      x: rightEyeRect.x + rightEyeRect.width / 2,
      y: rightEyeRect.y + rightEyeRect.height / 2,
    }
  }
}

useEventListener('resize', useDebounceFn(computedEyesCenter, 750))

const leftEyeHorizontalDistance = computed(() => {
  return Math.min(1, Math.max(-1, (mousePos.x - leftEyeCenter.value.x) / 150))
})
const rightEyeHorizontalDistance = computed(() => {
  return Math.min(1, Math.max(-1, (mousePos.x - rightEyeCenter.value.x) / 150))
})
const eyeVerticalDistance = computed(() => {
  return Math.min(1, Math.max(-1, (mousePos.y - leftEyeCenter.value.y) / 100))
})

const blinking = ref<'open' | 'closed'>('open')

const blinkTimer = 100
// use a randomized blink interval for a more natural look
const minBlinkInterval = 2000
const maxBlinkInterval = 10000

onMounted(() => {
  nextTick(() => {
    computedEyesCenter()
    idle.value = true
  })

  let timerId = setInterval(
    () => {
      let blinkState = 0
      function blinkHandler() {
        blinkState++

        if (blinkState % 2) {
          blinking.value = 'closed'
          setTimeout(blinkHandler, blinkTimer * 1.7)
        } else if (blinkState < 4) {
          blinking.value = 'open'
          setTimeout(blinkHandler, blinkTimer)
        } else {
          blinking.value = 'open'
        }
      }
      setTimeout(blinkHandler, 0)
    },
    (maxBlinkInterval - minBlinkInterval) / 2
  )

  onUnmounted(() => {
    clearInterval(timerId)
  })
})
</script>

<style scoped>
.eye-left .eyeball {
  transform: translate(
    calc(12px * v-bind(leftEyeHorizontalDistance)),
    calc(10px * v-bind(eyeVerticalDistance))
  );
}

.eye-right .eyeball {
  transform: translate(
    calc(12px * v-bind(rightEyeHorizontalDistance)),
    calc(10px * v-bind(eyeVerticalDistance))
  );
}

#pinia-logo .mouth .open {
  visibility: hidden;
}
#pinia-logo:hover .mouth .open {
  visibility: unset;
}
#pinia-logo:hover .mouth .smile {
  visibility: hidden;
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/RuleKitLink.vue
================================================
<script setup lang="ts">
import { useData } from 'vitepress'

const { site } = useData()

const translations = {
  'en-US': 'Vibe code Vue apps with confidence',
  'zh-CN': '自信地编写 Vue 应用的 Vibe 代码',
}
</script>

<template>
  <div class="rulekit">
    <a
      href="https://rulekit.dev?from=pinia"
      target="_blank"
      rel="sponsored noopener"
      aria-label="Visit RuleKit - curated rules for Cursor, Claude Code, and more (opens in new tab)"
      class="rulekit-link"
    >
      <div class="rulekit-content">
        <div class="rulekit-left">
          <span class="rulekit-sparkles" aria-hidden="true">✨</span>
          <span class="rulekit-text">
            <slot>{{ translations[site.lang] || translations['en-US'] }}</slot>
          </span>
        </div>
        <div class="rulekit-right">
          <svg
            width="1024"
            height="1024"
            viewBox="0 0 1024 1024"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
            role="img"
            alt="RuleKit logo"
            class="rulekit-logo"
          >
            <path
              d="M335 694L353.947 697.225V737L335 733.775V694Z"
              fill="currentColor"
            />
            <path
              d="M355 538.234V673L335 669.766V536.078L355 538.234Z"
              fill="currentColor"
            />
            <path
              d="M259.255 329.5L313.424 338.585V414.78L374.576 425.512V350.39L422.5 359.457L422.529 391.816L399.251 388.374V443.756L375.649 440.537V460.314L313.424 449.576V430.878L287.675 426.585L286.602 367.561L259.255 364V329.5Z"
              fill="currentColor"
            />
            <path
              d="M398.897 113.5L647.031 158.597V199L676.034 205.5V276L836.085 304.623L896.239 362.605V498.968L873.681 516.147V778.5L914.5 792.095L755.522 914.5L180.84 802.832L130.354 738.409V472.125L103.5 457.092V336.835L164.728 270.264L291.48 205.5L321.557 211V176.85L350.56 165.5V139L398.897 113.5ZM379.562 148.933V184L350.56 182.218L349.485 259.527L379.562 263.822V184L582.581 218.725V300.328L622.325 307V224.094L598.693 220.873V188.661L379.562 148.933ZM433.271 218.725V234.831L561.097 257.379V240.2L433.271 218.725ZM291.48 234.831L223.807 272.412L740.484 364.752L802.786 325.024L673.885 302.476L622.325 330L561.097 319V282L433.271 259.527L389.23 285.296L321.557 275V240.2L291.48 234.831ZM180.84 288.517L128.206 343.277V440.537L233.475 461.387L232.401 360.457L259.255 364V446L284.103 449.576V476.42L311.889 480.714V750L373.117 762L374.191 691.165V493.599L399.251 497V472.125L422.5 476.42L422.529 391.816L449.383 396.964V500L543.911 516V414.143L571.839 418.438V388.374L624.474 396.964V423.807L652.402 428V534.5L778.08 551.5V443.756L731 392.244L180.84 288.517ZM831.789 336.835L757.671 383.005L796.341 423.807L871.533 374.416L831.789 336.835ZM807.083 445.281V529.032L871.533 486.083V404.48L807.083 445.281ZM571.839 449.576V521.516L587 524.5V592L606.213 595.603V527.5L624.474 531V458.5L571.839 449.576ZM155.06 472.125V729.5L191.5 778.5L713 880.5L754.448 853.298V579.497L635.215 560.17V601L682.479 609.562V649.29L663.144 647V682.575L682.479 684.723L683.5 814.5L652.402 808.201V839L543.911 819.5V788.874L511.685 783.505V654.5L532.095 658.5V623.52L511.685 620.299V578.424L557.875 585.94V545.138L422.5 521.516L402.12 538.234V654.658V766.326L373.117 762V791.021L311.889 778.5V750L285.035 744.851V512.926L264.5 493.599L155.06 472.125ZM778.08 579.497L780.228 833.971L847.901 781.358V538.234L778.08 579.497ZM561.097 627.5V664.5L543.911 661V788.874L565.394 793.169L567 669L584.729 672V632.5L561.097 627.5ZM611.583 639.626V676.5L628.77 679V802.832L652.402 808.201V682.575L635.215 680.428V643L611.583 639.626Z"
              fill="currentColor"
            />
          </svg>
          <span class="rulekit-title">RuleKit</span>
        </div>
      </div>
    </a>
  </div>
</template>

<style scoped>
.rulekit {
  margin: 1rem 0;
}

.rulekit-title {
  font-family:
    'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
    'Liberation Mono', 'Courier New', monospace;
}

.rulekit-link {
  display: block;
  padding: 1rem 1.5rem;
  border-radius: 12px;
  background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
  border: 1px solid #dee2e6;
  text-decoration: none;
  color: #212529;
  transition: all 0.3s ease;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  position: relative;
  overflow: hidden;
}

.rulekit-link:visited {
  color: #212529;
}

.rulekit-link:focus {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}

.rulekit-link:focus:not(:focus-visible) {
  outline: none;
}

.rulekit-link::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.4),
    transparent
  );
  transition: left 0.6s ease;
}

.rulekit-link:hover::before {
  left: 100%;
}

.rulekit-link:hover {
  text-decoration: none !important;
  transform: translateY(-2px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  background: linear-gradient(135deg, #f1f3f4 0%, #e2e6ea 100%);
}

.rulekit-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  position: relative;
  z-index: 2;
}

.rulekit-left {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.rulekit-right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.rulekit-sparkles {
  font-size: 1.5rem;
}

.rulekit-logo {
  width: 2rem;
  height: 2rem;
  flex-shrink: 0;
  color: #495057;
}

.rulekit-text {
  font-weight: 500;
  font-family:
    'Spectral', ui-serif, system-ui, sans-serif, 'Apple Color Emoji',
    'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
  font-size: 0.95rem;
  line-height: 1.4;
}

/* Dark mode styles */
.dark .rulekit-link {
  background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
  border-color: #404040;
  color: #e5e5e5;
}

.dark .rulekit-link::before {
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.15),
    transparent
  );
}

.dark .rulekit-link:hover {
  text-decoration: none !important;
  background: linear-gradient(135deg, #262626 0%, #363636 100%);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}

.dark .rulekit-logo {
  color: #ffffff;
}

.dark .rulekit-text {
  color: #e5e5e5;
}

.dark .rulekit-link:visited {
  color: #e5e5e5;
}

.dark .rulekit-link:focus {
  outline-color: #66b3ff;
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/VueMasteryBanner.vue
================================================
<template>
  <div class="vuemastery-banner-wrapper" role="banner" v-if="isVisible">
    <div
      :class="{ 'show-flash': showFlash }"
      class="vuemastery-background-dim"
      ref="vuemastery-banner-flash"
    ></div>
    <a id="vm-banner" href="https://www.vuemastery.com/holiday" target="_blank">
      <img
        id="vm-logo-full"
        src="/vuemastery/vuemastery-white.svg"
        alt="vuemastery"
      />
      <img
        id="vm-logo-small"
        src="https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2Fvue-mastery-logo-small.png?alt=media&token=941fcc3a-2b6f-40e9-b4c8-56b3890da108"
        alt="vuemastery"
      />
      <div class="vm-banner-wrapper">
        <div class="vm-banner-content">
          <h1 class="vm-banner-title">Learn Vue with Evan You</h1>
          <p class="vm-banner-sub">Get 60% off a year of Vue courses</p>
        </div>
        <button id="vm-banner-cta">Unlock your discount</button>
      </div>
      <button id="vm-banner-close" @click.prevent="closeBanner">
        <span class="close">&times;</span>
      </button>
    </a>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const isVisible = ref(false)
const showFlash = ref(false)
const nameStorage = 'VUEMASTERY-BANNER-DECEMBER-2023'

const closeBanner = () => {
  // Hide the banner
  isVisible.value = false
  // Save action in the local storage
  localStorage.setItem(nameStorage, String(true))
  document.documentElement.classList.remove('vuemastery-menu-fixed')
}

onMounted(() => {
  isVisible.value = !localStorage.getItem(nameStorage)
  if (isVisible.value) {
    document.documentElement.classList.add('vuemastery-menu-fixed')
    setTimeout(() => {
      showFlash.value = true
    }, 2000)
  }
})
</script>
<style scoped>
.vuemastery-banner-wrapper {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 61;
  width: 100%;
  height: 100%;
  max-height: 70px;
  background: linear-gradient(45deg, #0a2b4e, #835ec2);
  background-size: 110%;
  background-position: 50% 50%;
  overflow: hidden;
  padding: 12px;
  margin: 0;
  transition: background-size 0.25s cubic-bezier(0.39, 0.575, 0.565, 1);
}

.vuemastery-banner-wrapper:hover {
  background-size: 100%;
}

.vuemastery-banner-wrapper:before {
  content: '';
  background: url(/vuemastery/background-bubbles-vuemastery.svg) left center
    no-repeat;
  background-size: cover;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  transition: all 0.3s ease-out 0.1s;
  transform: scale(1.1);
  width: 100%;
  height: 100%;
}
.vuemastery-banner-wrapper:after {
  content: '';
  background: url(/vuemastery/lock-vuemastery.svg) right center no-repeat;
  background-size: auto 100%;
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  pointer-events: none;
}

.vuemastery-banner-wrapper:hover:after {
  background-image: url(/vuemastery/unlock-vuemastery.svg);
}

#vm-banner {
  position: relative;
  width: 100%;
  height: 100%;
  text-decoration: none;
  color: white;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;
}

#vm-logo-full {
  position: absolute;
  left: 15px;
  width: 120px;
}

#vm-logo-small {
  display: none;
}

.vm-banner-wrapper {
  display: flex;
  align-items: center;
}

.vm-banner-content {
  display: flex;
}

.vm-banner-title {
  margin: 0;
  padding: 0;
  font-weight: bold;
  font-size: 24px;
  text-align: center;
  background: linear-gradient(145deg, #c3ffac, #86ec87, #38a56a);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.vm-banner-sub {
  margin: 0 2em;
  padding: 0;
  font-size: 16px;
  text-align: center;
  color: #fff;
}

#vm-banner-cta {
  position: relative;
  margin-left: 10px;
  padding: 10px 24px;
  background: linear-gradient(to top right, #41b782, #86d169);
  border: none;
  border-radius: 30px;
  color: #fff;
  font-size: 12px;
  font-weight: bold;
  text-decoration: none;
  text-transform: uppercase;
}

#vm-banner-cta:hover {
  background: linear-gradient(to bottom right, #41b782, #86d169);
}

#vm-banner-close {
  position: absolute;
  right: 12px;
  color: #fff;
  font-size: 20px;
  font-weight: bold;
  display: flex;
  align-items: center;
  justify-content: center;
}

#vm-banner-close > .close {
  font-size: 20px;
  font-weight: 600;
}

@media (max-width: 1200px) {
  #vm-banner-cta {
    display: none;
  }

  .vm-banner-content {
    flex-direction: column;
  }

  .vm-banner-sub {
    margin: 0 1em;
  }
}

@media (max-width: 850px) {
  .vuemastery-banner-wrapper:after {
    background: none;
  }
}
@media (max-width: 767px) {
  #vm-logo-full {
    left: 10px;
    width: 100px;
  }
}
@media (max-width: 767px) {
  #vm-logo-full {
    display: none;
  }
  #vm-logo-small {
    position: absolute;
    display: block;
    left: 10px;
    width: 40px;
  }
  .vm-banner-title {
    font-size: 14px;
  }
  .vm-banner-sub {
    font-size: 12px;
    margin: 0;
  }
}

.vuemastery-background-dim {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
}
.vuemastery-background-dim:after {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.4),
    transparent
  );
  transition: 0.5s;
  transition-delay: 0.5s;
}
.vuemastery-background-dim.show-flash:after {
  left: 100%;
}
</style>

<style>
html.vuemastery-menu-fixed {
  --vp-layout-top-height: 70px;
}
html.vuemastery-menu-fixed .VPNav,
html.vuemastery-menu-fixed .VPSidebar {
  top: 70px;
}
html.vuemastery-menu-fixed {
  scroll-padding-top: 134px;
  overflow: auto;
}
html.vuemastery-menu-fixed {
  margin-top: 72px;
}
@media (max-width: 960px) {
  html.vuemastery-menu-fixed .VPNav,
  html.vuemastery-menu-fixed .VPSidebar {
    top: 0;
  }
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/VueMasteryHomeLink.vue
================================================
<script setup lang="ts"></script>

<template>
  <div class="container">
    <div class="inside">
      <a
        href="https://www.vuemastery.com/pinia?coupon=PINIA-DOCS&via=eduardo"
        target="_blank"
      >
        <span class="logo-wrapper">
          <img
            alt="Vue Mastery Logo"
            width="25px"
            src="https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2Fvue-mastery-logo-small.png?alt=media&token=941fcc3a-2b6f-40e9-b4c8-56b3890da108"
          />
        </span>
        <span class="description">
          Get the <span class="highlight">Pinia Cheat Sheet</span> from Vue
          Mastery
        </span>
      </a>
    </div>
  </div>
</template>

<style scoped>
.container {
  text-align: center;
  margin-top: 30px;
}
.inside {
  width: 960px;
  border-bottom: 1px solid var(--c-divider);
  padding-bottom: 50px;
  margin: 0 auto;
}
a {
  background-color: var(--c-bg-accent);
  border-radius: 8px;
  padding: 8px 16px 8px 8px;
}
.description {
  line-height: 20px;
  color: var(--c-text);
  margin-left: 10px;
  transition: color 0.5s;
}
a:hover {
  text-decoration: none !important;
}
a:hover .highlight {
  text-decoration: underline;
}
.highlight {
  color: var(--c-brand);
}
@media (max-width: 960px) {
  .inside {
    width: 100%;
  }
}
@media (max-width: 420px) {
  a {
    display: block;
    margin-left: 10px;
    margin-right: 10px;
  }
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/VueMasteryLogoLink.vue
================================================
<script setup lang="ts">
const props = defineProps<{
  for: string
}>()

const links = {
  'pinia-cheat-sheet':
    'https://www.vuemastery.com/pinia?coupon=PINIA-DOCS&via=eduardo',
}

const link = links[props.for]
const forPiniaCheatSheet = props.for === 'pinia-cheat-sheet'
</script>

<template>
  <a :href="link" target="_blank">
    <span class="logo-wrapper">
      <img
        alt="Vue Mastery Logo"
        width="25px"
        src="https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2Fvue-mastery-logo-small.png?alt=media&token=941fcc3a-2b6f-40e9-b4c8-56b3890da108"
      />
    </span>
    <span v-if="forPiniaCheatSheet" class="description">
      Get the <span class="highlight">Pinia Cheat Sheet</span> from Vue Mastery
    </span>
  </a>
</template>

<style scoped>
a {
  background-color: var(--c-bg-accent);
  border-radius: 8px;
  padding: 8px 16px 8px 8px;
  display: flex;
  align-items: center;
  margin-top: 10px;
  margin-bottom: 10px;
}
.description {
  flex: 1;
  line-height: 20px;
  color: var(--c-text);
  margin: 0 0 0 12px;
  transition: color 0.5s;
}
a:hover {
  text-decoration: none !important;
}
a:hover .highlight {
  text-decoration: underline;
}
.highlight {
  color: var(--c-brand);
}
.logo-wrapper {
  position: relative;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background-color: white;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-left: 5px;
}
.logo-wrapper img {
  width: 25px;
  object-fit: contain;
}
@media (max-width: 576px) {
  .description {
    font-size: 12px;
    line-height: 18px;
  }
  .logo-wrapper {
    position: relative;
    width: 32px;
    height: 32px;
  }
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/VueSchoolLink.vue
================================================
<template>
  <div class="vueschool">
    <a
      :href="`${href}?friend=vuerouter`"
      target="_blank"
      rel="sponsored noopener"
      :title="title"
      class="no-icon"
    >
      <slot>{{ translations[site.lang] }}</slot>
    </a>
  </div>
</template>

<script setup lang="ts">
import { useData } from 'vitepress'

const { site } = useData()
const translations = {
  'en-US': 'Watch a free video lesson on Vue School',
  'zh-CN': '在 Vue School 上观看免费视频课程',
}
defineProps<{ href: string; title: string }>()
</script>

<style scoped>
.vueschool {
  margin-top: 20px;
  background-color: var(--vp-code-block-bg);
  padding: 1em 1.25em;
  border-radius: 2px;
  position: relative;
  display: flex;
}
.vueschool a {
  color: var(--c-text);
  position: relative;
  padding-left: 36px;
}
.vueschool a:before {
  content: '';
  position: absolute;
  display: block;
  width: 30px;
  height: 30px;
  top: calc(50% - 15px);
  left: -4px;
  border-radius: 50%;
  background-color: #73abfe;
}
.vueschool a:after {
  content: '';
  position: absolute;
  display: block;
  width: 0;
  height: 0;
  top: calc(50% - 5px);
  left: 8px;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 8px solid #fff;
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/VuejsdeConfBanner.vue
================================================
<template>
  <div class="banner banner-vuejsconf" v-if="isVisible">
    <a
      href="https://conf.vuejs.de/tickets/?voucher=COMMUNITY&utm_source=vuejs&utm_medium=referral&utm_campaign=banner-placement&utm_content=banner"
      target="_blank"
    >
      <picture>
        <source
          media="(min-width:1260px)"
          srcset="
            /vuejsde-conf/vuejsdeconf_banner_large.png,
            /vuejsde-conf/vuejsdeconf_banner_large_2x.png 2x
          "
        />
        <source
          media="(min-width:970px)"
          srcset="
            /vuejsde-conf/vuejsdeconf_banner_medium.png,
            /vuejsde-conf/vuejsdeconf_banner_medium_2x.png 2x
          "
        />
        <source
          media="(min-width:576px)"
          srcset="
            /vuejsde-conf/vuejsdeconf_banner_small.png,
            /vuejsde-conf/vuejsdeconf_banner_small_2x.png 2x
          "
        />
        <source
          media="(min-width:320px)"
          srcset="
            /vuejsde-conf/vuejsdeconf_banner_smallest.png,
            /vuejsde-conf/vuejsdeconf_banner_smallest_2x.png 2x
          "
          alt=""
        />
        <img src="/vuejsde-conf/vuejsdeconf_banner_smallest_2x.png" alt="" />
      </picture>
    </a>
    <div class="close-btn" @click.stop.prevent="closeBanner">
      <span class="close">&times;</span>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const isVisible = ref(false)

const nameStorage = 'VUEJSDECONF-BANNER-SEPTEMBER-2024'

const resetLayoutTopHeight = () => {
  document.documentElement.classList.add('banner-dismissed')
}

const closeBanner = () => {
  // Hide the banner
  isVisible.value = false
  // Save action in the local storage
  localStorage.setItem(nameStorage, String(true))
  resetLayoutTopHeight()
}

onMounted(() => {
  isVisible.value = !localStorage.getItem(nameStorage)

  if (!isVisible.value) {
    resetLayoutTopHeight()
  }
})
</script>
<style>
html:not(.banner-dismissed) {
  --vp-layout-top-height: 72px;
}
</style>
<style scoped>
.banner {
  position: fixed;
  z-index: var(--vp-z-index-layout-top);
  box-sizing: border-box;
  top: 0;
  left: 0;
  right: 0;
  height: var(--vp-layout-top-height);
  line-height: 0;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  color: #000;
}

.banner-dismissed .banner {
  display: none;
}

a {
  text-decoration: underline;
}

.close {
  font-size: 24px;
}

.banner-vuejsconf {
  background: linear-gradient(90deg, #fff 50%, #6f97c4 50%);
}

.banner-vuejsconf a {
  display: inline-block;
  margin: 0 auto;
}

.banner-vuejsconf .close-btn {
  top: 0;
  left: 0;
  z-index: 99;
  position: absolute;
  border-radius: 50%;
  padding: 10px;
  cursor: pointer;
}
</style>


================================================
FILE: packages/docs/.vitepress/theme/components/sponsors.json
================================================
{
  "platinum": [],
  "gold": [
    {
      "alt": "CodeRabbit",
      "href": "https://www.coderabbit.ai/?utm_source=vuerouter&utm_medium=sponsor",
      "imgSrcDark": "https://posva-sponsors.pages.dev/logos/coderabbitai-dark.svg",
      "imgSrcLight": "https://posva-sponsors.pages.dev/logos/coderabbitai-light.svg"
    }
  ],
  "silver": [
    {
      "alt": "VueMastery",
      "href": "https://www.vuemastery.com/",
      "imgSrcDark": "https://posva-sponsors.pages.dev/logos/vuemastery-dark.png",
      "imgSrcLight": "https://posva-sponsors.pages.dev/logos/vuemastery-light.svg"
    },
    {
      "alt": "Controla",
      "href": "https://www.controla.ai/?utm_source=posva",
      "imgSrcDark": "https://posva-sponsors.pages.dev/logos/controla-dark.png",
      "imgSrcLight": "https://posva-sponsors.pages.dev/logos/controla-light.png"
    },
    {
      "alt": "Route Optimizer and Route Planner Software",
      "href": "https://route4me.com",
      "imgSrcDark": "https://posva-sponsors.pages.dev/logos/route4me.png",
      "imgSrcLight": "https://posva-sponsors.pages.dev/logos/route4me.png"
    },
    {
      "alt": "SendCloud",
      "href": "https://jobs.sendcloud.com",
      "imgSrcDark": "https://posva-sponsors.pages.dev/logos/sendcloud-dark.svg",
      "imgSrcLight": "https://posva-sponsors.pages.dev/logos/sendcloud-light.svg"
    }
  ],
  "bronze": [
    {
      "alt": "Stanislas Ormières",
      "href": "https://stormier.ninja",
      "imgSrcDark": "https://avatars.githubusercontent.com/u/2486424",
      "imgSrcLight": "https://avatars.githubusercontent.com/u/2486424"
    },
    {
      "alt": "RTVision",
      "href": "https://www.rtvision.com/",
      "imgSrcDark": "https://avatars.githubusercontent.com/u/8292810",
      "imgSrcLight": "https://avatars.githubusercontent.com/u/8292810"
    },
    {
      "alt": "Storyblok",
      "href": "https://storyblok.com",
      "imgSrcDark": "https://posva-sponsors.pages.dev/logos/storyblok.png",
      "imgSrcLight": "https://posva-sponsors.pages.dev/logos/storyblok.png"
    }
  ]
}


================================================
FILE: packages/docs/.vitepress/theme/index.ts
================================================
import { h } from 'vue'
import { type Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import AsideSponsors from './components/AsideSponsors.vue'
// import AsideSponsors from './components/AsideSponsors.vue'
import TranslationStatus from 'vitepress-translation-helper/ui/TranslationStatus.vue'
// import HomeSponsors from './components/HomeSponsors.vue'
import PiniaLogo from './components/PiniaLogo.vue'
import './styles/vars.css'
import './styles/playground-links.css'
import VueSchoolLink from './components/VueSchoolLink.vue'
import VueMasteryLogoLink from './components/VueMasteryLogoLink.vue'
import MasteringPiniaLink from './components/MasteringPiniaLink.vue'
import RuleKitLink from './components/RuleKitLink.vue'
import status from '../translation-status.json'

const i18nLabels = {
  zh: '该翻译已同步到了 ${date} 的版本,其对应的 commit hash 是 <code>${hash}</code>。',
}

const theme: Theme = {
  ...DefaultTheme,
  Layout() {
    return h(DefaultTheme.Layout, null, {
      'home-hero-image': () => h('div', { class: 'image-src' }, h(PiniaLogo)),
      // 'home-features-after': () => h(HomeSponsors),
      'aside-ads-before': () => h(AsideSponsors),
      // 'layout-top': () => h(VuejsdeConfBanner),
      'doc-before': () => h(TranslationStatus, { status, i18nLabels }),
    })
  },

  enhanceApp({ app }) {
    app.component('VueSchoolLink', VueSchoolLink)
    app.component('VueMasteryLogoLink', VueMasteryLogoLink)
    app.component('MasteringPiniaLink', MasteringPiniaLink)
    app.component('RuleKitLink', RuleKitLink)
  },
}

export default theme


================================================
FILE: packages/docs/.vitepress/theme/styles/home-links.css
================================================
/* Style to get the cheat sheet link in the home page */

a.cta {
  text-align: center;
  border-radius: 8px;
}

a.cta:hover {
  border-color: var(--vp-c-brand);
  background-color: var(--c-bg-accent);
}

a.cta.vue-mastery::before {
  content: '';
  display: inline-block;
  width: 25px;
  height: 25px;
  background-image: url('https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2Fvue-mastery-logo-small.png?alt=media&token=941fcc3a-2b6f-40e9-b4c8-56b3890da108');
  background-size: 25px;
  background-repeat: no-repeat;
  background-position: bottom;
  margin-right: 0.5em;
}

a.cta.mastering-pinia {
  height: 100%;
  line-height: 100%;
  display: flex;
  justify-content: center;
  white-space: pre;
  min-height: 41px;
  position: relative;
  background-color: var(--c-black);
}
a.cta.mastering-pinia::before {
  content: '';
  width: 156px;
  height: 100%;
  display: inline-block;
  background-image: url('/mp-logo.svg');
  background-size: 156px;
  background-repeat: no-repeat;
  vertical-align: bottom;
  line-height: normal;
  transform: translateY(6px);
}
a.cta.mastering-pinia:hover::after {
  animation: none;
}
a.cta.mastering-pinia::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* background-color: var(--vp-button-brand-border); */
  border: 1px solid var(--vp-button-brand-border);
  border-radius: 20px;
  animation: ping 3s cubic-bezier(0, 0, 0.2, 1) infinite;
  z-index: -1;
}

@keyframes ping {
  15%,
  to {
    transform: scale(1.25, 2);
    opacity: 0;
  }
}

a.cta.vueschool {
  position: relative;
  color: currentColor;
  padding-left: 38px !important;
}
a.cta.vueschool::before {
  content: '';
  position: absolute;
  display: block;
  width: 20px;
  height: 20px;
  top: calc(50% - 10px);
  left: 12px;
  border-radius: 50%;
  border: 1px solid currentColor;
}

a.cta.vueschool::after {
  content: '';
  position: absolute;
  display: block;
  width: 0;
  height: 0;
  top: calc(50% - 4px);
  left: 20px;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  border-left: 7px solid currentColor;
}

a.cta.rulekit {
  font-family:
    'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
    'Liberation Mono', 'Courier New', monospace;
}

a.cta.rulekit::before {
  content: '';
  display: inline-block;
  width: 16px;
  height: 16px;
  background-image: url('/rulekit-logo.svg');
  background-size: 16px;
  background-repeat: no-repeat;
  background-position: center;
  margin-right: 0.5em;
  vertical-align: middle;
  filter: brightness(0); /* Make it black by default */
}

html.dark a.cta.rulekit::before {
  filter: brightness(0) invert(1); /* Make it white in dark mode */
}

@media (max-width: 420px) {
  a.cta.cta.vue-mastery {
    max-width: 320px;
    line-height: 1.5em;
    white-space: normal;
    display: block;
    padding-bottom: 10px;
    margin-left: 8px;
    margin-right: 8px;
  }
}


================================================
FILE: packages/docs/.vitepress/theme/styles/playground-links.css
================================================
.vp-doc a[href^='https://play.pinia.vuejs.org']:before {
  content: '▶';
  width: 20px;
  height: 20px;
  display: inline-block;
  border-radius: 10px;
  vertical-align: middle;
  position: relative;
  top: -2px;
  border: 2px solid;
  margin-right: 8px;
  margin-left: 4px;
  line-height: 15px;
  padding-left: 4.5px;
  font-size: 11px;
  font-family: system-ui, BlinkMacSystemFont, sans-serif;
}


================================================
FILE: packages/docs/.vitepress/theme/styles/vars.css
================================================
/**
 * Colors
 * -------------------------------------------------------------------------- */

:root {
  --c-yellow-1: #ffd859;
  --c-yellow-2: #f7d336;
  --c-yellow-3: #dec96e;
  --c-yellow-soft-1: #ecb732;
  --c-yellow-soft-2: #c99513;

  --c-teal: #086367;
  --c-teal-light: #33898d;

  --c-white-dark: #f8f8f8;
  --c-black-darker: #0d121b;
  --c-black: #111827;
  --c-black-light: #161f32;
  --c-black-lighter: #262a44;

  --c-green-1: #52ce63;
  --c-green-2: #8ae99c;
  --c-green-3: #51a256;
  --c-green-soft: #316334;

  /* light theme is a bit different */
  --vp-c-brand-1: var(--vp-c-green-1);
  --vp-c-brand-2: var(--vp-c-green-2);
  --vp-c-brand-3: var(--vp-c-green-3);
  --vp-c-brand-soft: var(--vp-c-green-soft);

  --c-text-dark-1: #d9e6eb;
  --c-text-dark-2: #c4dde6;
  --c-text-dark-3: #abc4cc;
  --c-text-light-1: #2c3e50;
  --c-text-light-2: #476582;
  --c-text-light-3: #90a4b7;

  --vp-c-brand-dark: var(--c-green-soft);
  --vp-c-brand-darker: var(--c-green-soft);
  --vp-c-brand-dimm: rgba(100, 108, 255, 0.08);
  --vp-c-brand-text: var(--c-text-light-1);
  --c-bg-accent: var(--c-white-dark);
  --code-bg-color: var(--c-white-dark);
  --code-inline-bg-color: var(--c-white-dark);
  --code-font-family:
    'dm', source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
  --code-font-size: 16px;

  --vp-code-block-bg: var(--vp-c-bg-alt);
  --vp-code-line-highlight-color: rgba(0, 0, 0, 0.075);
  --vp-code-color: var(--vp-text-color);
}

html.dark:root {
  /* --c-black: #ffffff;
  --c-white: #000000; */
  /* --c-divider-light: rgba(60, 60, 67, 0.12);
  --c-divider-dark: rgba(84, 84, 88, 0.48); */
  /* --c-brand-light: var(--c-yellow-light); */

  --vp-c-brand-1: var(--c-yellow-1);
  --vp-c-brand-2: var(--c-yellow-2);
  --vp-c-brand-3: var(--c-yellow-3);

  --vp-c-bg-alpha-with-backdrop: rgba(20, 25, 36, 0.7);
  --vp-c-bg-alpha-without-backdrop: rgba(20, 25, 36, 0.9);

  --vp-code-line-highlight-color: rgba(0, 0, 0, 0.5);

  --vp-c-text-1: var(--c-text-dark-1);
  --vp-c-brand-text: var(--c-text-light-1);
  --c-text-light: var(--c-text-dark-2);
  --c-text-lighter: var(--c-text-dark-3);
  --c-divider: var(--c-divider-dark);
  --c-bg-accent: var(--c-black-light);
  /* --vp-code-inline-bg: var(--vp-c-black-light); */

  --vp-c-bg: var(--c-black);
  --vp-c-bg-soft: var(--c-black-light);
  --vp-c-bg-soft-up: var(--c-black-lighter);
  --vp-c-bg-mute: var(--c-black-light);
  --vp-c-bg-soft-mute: var(--c-black-lighter);
  --vp-c-bg-alt: #0d121b;
  --vp-c-bg-elv: var(--vp-c-bg-soft);
  --vp-c-bg-elv-mute: var(--vp-c-bg-soft-mute);
  --vp-c-mute: var(--vp-c-bg-mute);
  --vp-c-mute-dark: var(--c-black-lighter);
  --vp-c-mute-darker: var(--c-black-darker);

  --vp-home-hero-name-background: -webkit-linear-gradient(
    78deg,
    var(--c-yellow-2) 30%,
    var(--c-green-2)
  );
}

html.dark .DocSearch {
  --docsearch-hit-active-color: var(--c-text-light-1);
}

/**
 * Component: Button
 * -------------------------------------------------------------------------- */

:root {
  --vp-button-brand-border: var(--c-yellow-soft-1);
  --vp-button-brand-text: var(--c-black);
  --vp-button-brand-bg: var(--c-yellow-1);
  --vp-button-brand-hover-border: var(--c-yellow-2);
  --vp-button-brand-hover-text: var(--c-black-darker);
  --vp-button-brand-hover-bg: var(--c-yellow-2);
  --vp-button-brand-active-border: var(--c-yellow-soft-1);
  --vp-button-brand-active-text: var(--c-black-darker);
  --vp-button-brand-active-bg: var(--vp-button-brand-bg);
}

/**
 * Component: Home
 * -------------------------------------------------------------------------- */

:root {
  --vp-home-hero-name-color: transparent;
  --vp-home-hero-name-background: linear-gradient(
    292deg,
    var(--c-green-1) 50%,
    var(--c-green-2)
  );
  --vp-home-hero-image-background-image: linear-gradient(
    15deg,
    var(--c-yellow-2) 65%,
    var(--c-green-1) 30%
  );
  --vp-home-hero-image-filter: blur(40px);
}

.VPHero .VPImage.image-src {
  max-height: 192px;
}

@media (min-width: 640px) {
  :root {
    --vp-home-hero-image-filter: blur(56px);
  }
  .VPHero .VPImage.image-src {
    max-height: 256px;
  }
}

@media (min-width: 960px) {
  :root {
    --vp-home-hero-image-filter: blur(72px);
  }
  .VPHero .VPImage.image-src {
    max-height: 320px;
  }
}

.become-sponsor {
  font-size: 0.9em;
  font-weight: 700;
  width: auto;
  text-align: center;
  background-color: transparent;
  padding: 0.75em 2em;
  border-radius: 2em;
  transition: all 0.15s ease;
  box-sizing: border-box;
  border: 2px solid var(--c-yellow-2);
}

.become-sponsor.become-sponsor:hover {
  background-color: var(--c-yellow-1);
  text-decoration: none;
  border-color: var(--c-yellow-1);
  color: var(--c-text-light-1);
}

.vp-doc a {
  text-decoration: none;
}
.vp-doc a:hover {
  text-decoration: underline;
}

.sponsors-top .become-sponsor {
  font-size: 0.75em;
  padding: 0.2em;
  width: auto;
  max-width: 150px;
}

kbd {
  display: inline-block;
  padding: 3px 5px;
  font-size: 0.65em;
  color: var(--vp-c-text-1);
  vertical-align: middle;
  background-color: var(--vp-c-bg-mute);
  border: solid 1px var(--vp-c-bg-soft-mute);
  border-radius: 6px;
  box-shadow: inset 0 -1px 0 var(--vp-c-bg-soft-mute);
  line-height: 0.95em;
}


================================================
FILE: packages/docs/.vitepress/translation-status.json
================================================
{
  "zh": {
    "hash": "02a476d",
    "date": "2024-05-20"
  }
}


================================================
FILE: packages/docs/cookbook/composables.md
================================================
# Dealing with Composables

[Composables](https://vuejs.org/guide/reusability/composables.html#composables) are functions that leverage Vue Composition API to encapsulate and reuse stateful logic. Whether you write your own, you use [external libraries](https://vueuse.org/) or do both, you can fully use the power of Composables in your pinia stores.

## Option Stores

<MasteringPiniaLink
  href="https://masteringpinia.com/lessons/using-composables-in-option-stores"
  title="Using Composables in Option Stores"
/>

When defining an option store, you can call a composable inside of the `state` property:

```ts
export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: useLocalStorage('pinia/auth/login', 'bob'),
  }),
})
```

Keep in mind that **you can only return writable state** (e.g. a `ref()`). Here are some examples of composables that you can use:

<RuleKitLink />

- [useLocalStorage](https://vueuse.org/core/useLocalStorage/)
- [useAsyncState](https://vueuse.org/core/useAsyncState/)

Here are some examples of composables that cannot be used in an option stores (but can be used with setup stores):

- [useMediaControls](https://vueuse.org/core/useMediaControls/): exposes functions
- [useMemoryInfo](https://vueuse.org/core/useMemory/): exposes readonly data
- [useEyeDropper](https://vueuse.org/core/useEyeDropper/): exposes readonly data and functions

## Setup Stores

<MasteringPiniaLink
  href="https://masteringpinia.com/lessons/using-composables-in-setup-stores"
  title="Using Composables in Setup Stores"
/>

On the other hand, when defining a setup store, you can use almost any composable since every property gets discerned into state, action, or getter:

```ts
import { defineStore } from 'pinia'
import { useMediaControls } from '@vueuse/core'

export const useVideoPlayer = defineStore('video', () => {
  // we won't expose (return) this element directly
  const videoElement = ref<HTMLVideoElement>()
  const src = ref('/data/video.mp4')
  const { playing, volume, currentTime, togglePictureInPicture } =
    useMediaControls(videoElement, { src })

  function loadVideo(element: HTMLVideoElement, src: string) {
    videoElement.value = element
    src.value = src
  }

  return {
    src,
    playing,
    volume,
    currentTime,

    loadVideo,
    togglePictureInPicture,
  }
})
```

:::warning
Differently from regular state, `ref<HTMLVideoElement>()` contains a non-serializable reference to the DOM element. This is why we don't return it directly. Since it's client-only state, we know it won't be set on the server and will **always** start as `undefined` on the client.
:::

## SSR

When dealing with [Server Side Rendering](../ssr/index.md), you need to take care of some extra steps in order to use composables within your stores.

In [Option Stores](#option-stores), you need to define a `hydrate()` function. This function is called when the store is instantiated on the client (the browser) when there is an initial state available at the time the store is created. The reason we need to define this function is because in such scenario, `state()` is not called.

```ts
import { defineStore } from 'pinia'
import { useLocalStorage } from '@vueuse/core'

export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: useLocalStorage('pinia/auth/login', 'bob'),
  }),

  hydrate(state, initialState) {
    // in this case we can completely ignore the initial state since we
    // want to read the value from the browser
    state.user = useLocalStorage('pinia/auth/login', 'bob')
  },
})
```

In [Setup Stores](#setup-stores), you need to use a helper named `skipHydrate()` on any state property that shouldn't be picked up from the initial state. Differently from option stores, setup stores cannot just _skip calling `state()`_, so we mark properties that cannot be hydrated with `skipHydrate()`. Note that this only applies to state properties:

```ts
import { defineStore, skipHydrate } from 'pinia'
import { useEyeDropper, useLocalStorage } from '@vueuse/core'

export const useColorStore = defineStore('colors', () => {
  const { isSupported, open, sRGBHex } = useEyeDropper()
  const lastColor = useLocalStorage('lastColor', sRGBHex)
  // ...
  return {
    lastColor: skipHydrate(lastColor), // Ref<string>
    open, // Function
    isSupported, // boolean (not even reactive)
  }
})
```


================================================
FILE: packages/docs/cookbook/composing-stores.md
================================================
# Composing Stores

<RuleKitLink />

Composing stores is about having stores that use each other, and this is supported in Pinia. There is one rule to follow:

If **two or more stores use each other**, they cannot create an infinite loop through _getters_ or _actions_. They cannot **both** directly read each other's state in their setup function:

```js
const useX = defineStore('x', () => {
  const y = useY()

  // ❌ This is not possible because y also tries to read x.name
  y.name

  function doSomething() {
    // ✅ Read y properties in computed or actions
    const yName = y.name
    // ...
  }

  return {
    name: ref('I am X'),
  }
})

const useY = defineStore('y', () => {
  const x = useX()

  // ❌ This is not possible because x also tries to read y.name
  x.name

  function doSomething() {
    // ✅ Read x properties in computed or actions
    const xName = x.name
    // ...
  }

  return {
    name: ref('I am Y'),
  }
})
```

## Nested Stores

Note that if one store uses another store, you can directly import and call the `useStore()` function within _actions_ and _getters_. Then you can interact with the store just like you would from within a Vue component. See [Shared Getters](#Shared-Getters) and [Shared Actions](#Shared-Actions).

When it comes to _setup stores_, you can simply use one of the stores **at the top** of the store function:

```ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { apiPurchase } from './api'

export const useCartStore = defineStore('cart', () => {
  const user = useUserStore()
  const list = ref([])

  const summary = computed(() => {
    return `Hi ${user.name}, you have ${list.value.length} items in your cart. It costs ${price.value}.`
  })

  function purchase() {
    return apiPurchase(user.id, list.value)
  }

  return { list, summary, purchase }
})
```

## Shared Getters

You can simply call `useUserStore()` inside a _getter_:

```js
import { defineStore } from 'pinia'
import { useUserStore } from './user'

export const useCartStore = defineStore('cart', {
  getters: {
    summary(state) {
      const user = useUserStore()

      return `Hi ${user.name}, you have ${state.list.length} items in your cart. It costs ${state.price}.`
    },
  },
})
```

## Shared Actions

The same applies to _actions_:

```js
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { apiOrderCart } from './api'

export const useCartStore = defineStore('cart', {
  actions: {
    async orderCart() {
      const user = useUserStore()

      try {
        await apiOrderCart(user.token, this.items)
        // another action
        this.emptyCart()
      } catch (err) {
        displayError(err)
      }
    },
  },
})
```

Since actions can be asynchronous, make sure **all of your `useStore()` calls appear before any `await`**. Otherwise, this could lead to using the wrong pinia instance _in SSR apps_:

```js{7-8,11-13}
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { apiOrderCart } from './api'

export const useCartStore = defineStore('cart', {
  actions: {
    async orderCart() {
      // ✅ call at the top of the action before any `await`
      const user = useUserStore()

      try {
        await apiOrderCart(user.token, this.items)
        // ❌ called after an `await` statement
        const otherStore = useOtherStore()
        // another action
        this.emptyCart()
      } catch (err) {
        displayError(err)
      }
    },
  },
})
```


================================================
FILE: packages/docs/cookbook/hot-module-replacement.md
================================================
# HMR (Hot Module Replacement)

<RuleKitLink />

Pinia supports Hot Module replacement so you can edit your stores and interact with them directly in your app without reloading the page, allowing you to keep the existing state, add, or even remove state, actions, and getters.

At the moment, only [Vite](https://vitejs.dev/guide/api-hmr.html#hmr-api) is officially supported but any bundler implementing the `import.meta.hot` spec should work (e.g. [webpack](https://webpack.js.org/api/module-variables/#importmetawebpackhot) seems to use `import.meta.webpackHot` instead of `import.meta.hot`).
You need to add this snippet of code next to any store declaration. Let's say you have three stores: `auth.js`, `cart.js`, and `chat.js`, you will have to add (and adapt) this after the creation of the _store definition_:

```js
// auth.js
import { defineStore, acceptHMRUpdate } from 'pinia'

export const useAuth = defineStore('auth', {
  // options...
})

// make sure to pass the right store definition, `useAuth` in this case.
if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useAuth, import.meta.hot))
}
```


================================================
FILE: packages/docs/cookbook/index.md
================================================
# Cookbook

<RuleKitLink />

- [Migrating from Vuex ≤4](./migration-vuex.md): A migration guide for converting Vuex ≤4 projects.
- [HMR](./hot-module-replacement.md): How to activate hot module replacement and improve the developer experience.
- [Testing Stores (WIP)](./testing.md): How to unit test Stores and mock them in component unit tests.
- [Composing Stores](./composing-stores.md): How to cross use multiple stores. e.g. using the user store in the cart store.
- [Options API](./options-api.md): How to use Pinia without the composition API, outside of `setup()`.
- [Migrating from 0.0.7](./migration-0-0-7.md): A migration guide with more examples than the changelog.


================================================
FILE: packages/docs/cookbook/migration-0-0-7.md
================================================
# Migrating from 0.0.7

The versions after `0.0.7`: `0.1.0`, and `0.2.0`, came with a few big breaking changes. This guide helps you migrate whether you use Vue 2 or Vue 3. The whole changelog can be found in the repository:

- [For Pinia <= 1 for Vue 2](https://github.com/vuejs/pinia/blob/v1/CHANGELOG.md)
- [For Pinia >= 2 for Vue 3](https://github.com/vuejs/pinia/blob/v3/packages/pinia/CHANGELOG.md)

If you have questions or issues regarding the migration, feel free to [open a discussion](https://github.com/vuejs/pinia/discussions/categories/q-a) to ask for help.

## No more `store.state`

You no longer access the store state via a `state` property, you can directly access any state property.

Given a store defined with:

```js
const useStore({
  id: 'main',
  state: () => ({ count: 0 })
})
```

Do

```diff
 const store = useStore()

-store.state.count++
+store.count.++
```

You can still access the whole store state with `$state` when needed:

```diff
-store.state = newState
+store.$state = newState
```

## Rename of store properties

All store properties (`id`, `patch`, `reset`, etc) are now prefixed with `$` to allow properties defined on the store with the same names. Tip: you can refactor your whole codebase with F2 (or right-click + Refactor) on each of the store's properties

```diff
 const store = useStore()
-store.patch({ count: 0 })
+store.$patch({ count: 0 })

-store.reset()
+store.$reset()

-store.id
+store.$id
```

## The Pinia instance

It's now necessary to create a pinia instance and install it:

If you are using Vue 2 (Pinia <= 1):

```js
import Vue from 'vue'
import { createPinia, PiniaVuePlugin } from 'pinia'

const pinia = createPinia()
Vue.use(PiniaVuePlugin)
new Vue({
  el: '#app',
  pinia,
  // ...
})
```

If you are using Vue 3 (Pinia >= 2):

```js
import { createApp } from 'vue'
import { createPinia, PiniaVuePlugin } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
createApp(App).use(pinia).mount('#app')
```

The `pinia` instance is what holds the state and should **be unique per application**. Check the SSR section of the docs for more details.

## SSR changes

The SSR plugin `PiniaSsr` is no longer necessary and has been removed.
With the introduction of pinia instances, `getRootState()` is no longer necessary and should be replaced with `pinia.state.value`:

If you are using Vue 2 (Pinia <= 1):

```diff
// entry-server.js
-import { getRootState, PiniaSsr } from 'pinia',
+import { createPinia, PiniaVuePlugin } from 'pinia',


-// install plugin to automatically use correct context in setup and onServerPrefetch
-Vue.use(PiniaSsr);
+Vue.use(PiniaVuePlugin)

 export default context => {
+  const pinia = createPinia()
   const app = new Vue({
     // other options
+    pinia
   })

   context.rendered = () => {
     // pass state to context
-    context.piniaState = getRootState(context.req)
+    context.piniaState = pinia.state.value
   };

-   return { app }
+   return { app, pinia }
 }
```

`setActiveReq()` and `getActiveReq()` have been replaced with `setActivePinia()` and `getActivePinia()` respectively. `setActivePinia()` can only be passed a `pinia` instance created with `createPinia()`. **Note that most of the time you won't directly use these functions**.


================================================
FILE: packages/docs/cookbook/migration-v1-v2.md
================================================
# Migrating from 0.x (v1) to v2

<RuleKitLink />

Starting at version `2.0.0-rc.4`, pinia supports both Vue 2 and Vue 3! This means, all new updates will be applied to this version 2 so both Vue 2 and Vue 3 users can benefit from it. If you are using Vue 3, this doesn't change anything for you as you were already using the rc and you can check [the CHANGELOG](https://github.com/vuejs/pinia/blob/v2/packages/pinia/CHANGELOG.md) for a detailed explanation of everything that changed. Otherwise, **this guide is for you**!

## Deprecations

Let's take a look at all the changes you need to apply to your code. First, make sure you are already running the latest 0.x version to see any deprecations:

```shell
npm i 'pinia@^0.x.x'
# or with yarn
yarn add 'pinia@^0.x.x'
```

If you are using ESLint, consider using [this plugin](https://github.com/gund/eslint-plugin-deprecation) to find all deprecated usages. Otherwise, you should be able to see them as they appear crossed. These are the APIs that were deprecated that were removed:

- `createStore()` becomes `defineStore()`
- In subscriptions, `storeName` becomes `storeId`
- `PiniaPlugin` was renamed `PiniaVuePlugin` (Pinia plugin for Vue 2)
- `$subscribe()` no longer accepts a _boolean_ as second parameter, pass an object with `detached: true` instead.
- Pinia plugins no longer directly receive the `id` of the store. Use `store.$id` instead.

## Breaking changes

After removing these, you can upgrade to v2 with:

```shell
npm i 'pinia@^2.x.x'
# or with yarn
yarn add 'pinia@^2.x.x'
```

And start updating your code.

### Generic Store type

Added in [2.0.0-rc.0](https://github.com/vuejs/pinia/blob/v2/packages/pinia/CHANGELOG.md#200-rc0-2021-07-28)

Replace any usage of the type `GenericStore` with `StoreGeneric`. This is the new generic store type that should accept any kind of store. If you were writing functions using the type `Store` without passing its generics (e.g. `Store<Id, State, Getters, Actions>`), you should also use `StoreGeneric` as the `Store` type without generics creates an empty store type.

```ts
function takeAnyStore(store: Store) {} // [!code --]
function takeAnyStore(store: StoreGeneric) {} // [!code ++]

function takeAnyStore(store: GenericStore) {} // [!code --]
function takeAnyStore(store: StoreGeneric) {} // [!code ++]
```

## `DefineStoreOptions` for plugins

If you were writing plugins, using TypeScript, and extending the type `DefineStoreOptions` to add custom options, you should rename it to `DefineStoreOptionsBase`. This type will apply to both setup and options stores.

```ts
declare module 'pinia' {
  export interface DefineStoreOptions<S, Store> { // [!code --]
  export interface DefineStoreOptionsBase<S, Store> { // [!code ++]
    debounce?: {
      [k in keyof StoreActions<Store>]?: number
    }
  }
}
```

## `PiniaStorePlugin` was renamed

The type `PiniaStorePlugin` was renamed to `PiniaPlugin`.

```ts
import { PiniaStorePlugin } from 'pinia' // [!code --]
import { PiniaPlugin } from 'pinia' // [!code ++]

const piniaPlugin: PiniaStorePlugin = () => { // [!code --]
const piniaPlugin: PiniaPlugin = () => { // [!code ++]
  // ...
}
```

**Note this change can only be done after upgrading to the latest version of Pinia without deprecations**.

## `@vue/composition-api` version

Since pinia now relies on `effectScope()`, you must use at least the version `1.1.0` of `@vue/composition-api`:

```shell
npm i @vue/composition-api@latest
# or with yarn
yarn add @vue/composition-api@latest
```

## webpack 4 support

If you are using webpack 4 (Vue CLI uses webpack 4), you might encounter an error like this:

```
ERROR  Failed to compile with 18 errors

 error  in ./node_modules/pinia/dist/pinia.mjs

Can't import the named export 'computed' from non EcmaScript module (only default export is available)
```

This is due to the modernization of dist files to support native ESM modules in Node.js. Files are now using the extension `.mjs` and `.cjs` to let Node benefit from this. To fix this issue you have two possibilities:

- If you are using Vue CLI 4.x, upgrade your dependencies. This should include the fix below.
  - If upgrading is not possible for you, add this to your `vue.config.js`:

    ```js
    // vue.config.js
    module.exports = {
      configureWebpack: {
        module: {
          rules: [
            {
              test: /\.mjs$/,
              include: /node_modules/,
              type: 'javascript/auto',
            },
          ],
        },
      },
    }
    ```

- If you are manually handling webpack, you will have to let it know how to handle `.mjs` files:

  ```js
  // webpack.config.js
  module.exports = {
    module: {
      rules: [
        {
          test: /\.mjs$/,
          include: /node_modules/,
          type: 'javascript/auto',
        },
      ],
    },
  }
  ```

## Devtools

Pinia v2 no longer hijacks Vue Devtools v5, it requires Vue Devtools v6. Find the download link on the [Vue Devtools documentation](https://devtools.vuejs.org/guide/installation.html#chrome) for the **beta channel** of the extension.

## Nuxt

If you are using Nuxt, pinia has now its own dedicated Nuxt package 🎉. Install it with:

```bash
npm i @pinia/nuxt
# or with yarn
yarn add @pinia/nuxt
```

Also make sure to **update your `@nuxtjs/composition-api` package**.

Then adapt your `nuxt.config.js` and your `tsconfig.json` if you are using TypeScript:

```js
// nuxt.config.js
module.exports {
  buildModules: [
    '@nuxtjs/composition-api/module',
    'pinia/nuxt', // [!code --]
    '@pinia/nuxt', // [!code ++]
  ],
}
```

```json
// tsconfig.json
{
  "types": [
    // ...
    "pinia/nuxt/types" // [!code --]
    "@pinia/nuxt" // [!code ++]
  ]
}
```

It is also recommended to give [the dedicated Nuxt section](../ssr/nuxt.md) a read.


================================================
FILE: packages/docs/cookbook/migration-v2-v3.md
================================================
# Migrating from v2 to v3

<RuleKitLink />

Pinia v3 is a _boring_ major release with no new features. It drops deprecated APIs and updates major dependencies. It only supports Vue 3. If you are using Vue 2, you can keep using v2. If you need help, [book help with Pinia's author](https://cal.com/posva/consultancy).

For most users, the migration should require **no change**. This guide is here to help you in case you encounter any issues.

## Deprecations

### `defineStore({ id })`

The `defineStore()` signature that accepts an object with an `id` property is deprecated. You should use the `id` parameter instead:

```ts
defineStore({ // [!code --]
  id: 'storeName', // [!code --]
defineStore('storeName', { // [!code ++]
  // ...
})
```

### `PiniaStorePlugin`

This deprecated type alias has been removed in favor of `PiniaPlugin`.

## New versions

- Only Vue 3 is supported.
- TypeScript 5 or newer is required.
- The devtools API has been upgraded to [v7](https://devtools.vuejs.org).

## Nuxt

The Nuxt module has been updated to support Nuxt 3. If you are using Nuxt 2 or Nuxt bridge, you can keep using the old version of Pinia.


================================================
FILE: packages/docs/cookbook/migration-vuex.md
================================================
# Migrating from Vuex ≤4

Although the structure of Vuex and Pinia stores is different, a lot of the logic can be reused. This guide serves to help you through the process and point out some common gotchas that can appear.

## Preparation

First, follow the [Getting Started guide](../getting-started.md) to install Pinia.

## Restructuring Modules to Stores

Vuex has the concept of a single store with multiple _modules_. These modules can optionally be namespaced and even nested within each other.

The easiest way to transition that concept to be used with Pinia is that each module you used previously is now a _store_. Each store requires an `id` which is similar to a namespace in Vuex. This means that each store is namespaced by design. Nested modules can also each become their own store. Stores that depend on each other will simply import the other store.

How you choose to restructure your Vuex modules into Pinia stores is entirely up to you, but here is one suggestion:

<RuleKitLink />

```bash
# Vuex example (assuming namespaced modules)
src
└── store
    ├── index.js           # Initializes Vuex, imports modules
    └── modules
        ├── module1.js     # 'module1' namespace
        └── nested
            ├── index.js   # 'nested' namespace, imports module2 & module3
            ├── module2.js # 'nested/module2' namespace
            └── module3.js # 'nested/module3' namespace

# Pinia equivalent, note ids match previous namespaces
src
└── stores
    ├── index.js          # (Optional) Initializes Pinia, does not import stores
    ├── module1.js        # 'module1' id
    ├── nested-module2.js # 'nestedModule2' id
    ├── nested-module3.js # 'nestedModule3' id
    └── nested.js         # 'nested' id
```

This creates a flat structure for stores but also preserves the previous namespacing with equivalent `id`s. If you had some state/getters/actions/mutations in the root of the store (in the `store/index.js` file of Vuex) you may wish to create another store called something like `root` which holds all that information.

The directory for Pinia is generally called `stores` instead of `store`. This is to emphasize that Pinia uses multiple stores, instead of a single store in Vuex.

For large projects you may wish to do this conversion module by module rather than converting everything at once. You can actually mix Pinia and Vuex together during the migration so this approach can also work and is another reason for naming the Pinia directory `stores` instead.

## Converting a Single Module

Here is a complete example of the before and after of converting a Vuex module to a Pinia store, see below for a step-by-step guide. The Pinia example uses an option store as the structure is most similar to Vuex:

```ts
// Vuex module in the 'auth/user' namespace
import { Module } from 'vuex'
import { api } from '@/api'
import { RootState } from '@/types' // if using a Vuex type definition

interface State {
  firstName: string
  lastName: string
  userId: number | null
}

const storeModule: Module<State, RootState> = {
  namespaced: true,
  state: {
    firstName: '',
    lastName: '',
    userId: null,
  },
  getters: {
    firstName: (state) => state.firstName,
    fullName: (state) => `${state.firstName} ${state.lastName}`,
    loggedIn: (state) => state.userId !== null,
    // combine with some state from other modules
    fullUserDetails: (state, getters, rootState, rootGetters) => {
      return {
        ...state,
        fullName: getters.fullName,
        // read the state from another module named `auth`
        ...rootState.auth.preferences,
        // read a getter from a namespaced module called `email` nested under `auth`
        ...rootGetters['auth/email'].details,
      }
    },
  },
  actions: {
    async loadUser({ state, commit }, id: number) {
      if (state.userId !== null) throw new Error('Already logged in')
      const res = await api.user.load(id)
      commit('updateUser', res)
    },
  },
  mutations: {
    updateUser(state, payload) {
      state.firstName = payload.firstName
      state.lastName = payload.lastName
      state.userId = payload.userId
    },
    clearUser(state) {
      state.firstName = ''
      state.lastName = ''
      state.userId = null
    },
  },
}

export default storeModule
```

```ts
// Pinia Store
import { defineStore } from 'pinia'
import { useAuthPreferencesStore } from './auth-preferences'
import { useAuthEmailStore } from './auth-email'
import vuexStore from '@/store' // for gradual conversion, see fullUserDetails

interface State {
  firstName: string
  lastName: string
  userId: number | null
}

export const useAuthUserStore = defineStore('authUser', {
  // convert to a function
  state: (): State => ({
    firstName: '',
    lastName: '',
    userId: null,
  }),
  getters: {
    // firstName getter removed, no longer needed
    fullName: (state) => `${state.firstName} ${state.lastName}`,
    loggedIn: (state) => state.userId !== null,
    // must define return type because of using `this`
    fullUserDetails(state): FullUserDetails {
      // import from other stores
      const authPreferencesStore = useAuthPreferencesStore()
      const authEmailStore = useAuthEmailStore()
      return {
        ...state,
        // other getters now on `this`
        fullName: this.fullName,
        ...authPreferencesStore.$state,
        ...authEmailStore.details,
      }

      // alternative if other modules are still in Vuex
      // return {
      //   ...state,
      //   fullName: this.fullName,
      //   ...vuexStore.state.auth.preferences,
      //   ...vuexStore.getters['auth/email'].details
      // }
    },
  },
  actions: {
    // no context as first argument, use `this` instead
    async loadUser(id: number) {
      if (this.userId !== null) throw new Error('Already logged in')
      const res = await api.user.load(id)
      this.updateUser(res)
    },
    // mutations can now become actions, instead of `state` as first argument use `this`
    updateUser(payload) {
      this.firstName = payload.firstName
      this.lastName = payload.lastName
      this.userId = payload.userId
    },
    // easily reset state using `$reset`
    clearUser() {
      this.$reset()
    },
  },
})
```

Let's break the above down into steps:

1. Add a required `id` for the store, you may wish to keep this the same as the namespace before. It is also recommended to make sure the `id` is in _camelCase_ as it makes it easier to use with `mapStores()`.
2. Convert `state` to a function if it was not one already
3. Convert `getters`
   1. Remove any getters that return state under the same name (eg. `firstName: (state) => state.firstName`), these are not necessary as you can access any state directly from the store instance
   2. If you need to access other getters, they are on `this` instead of using the second argument. Remember that if you are using `this` then you will have to use a regular function instead of an arrow function. Also note that you will need to specify a return type because of TS limitations, see [here](../core-concepts/getters.md#accessing-other-getters) for more details
   3. If using `rootState` or `rootGetters` arguments, replace them by importing the other store directly, or if they still exist in Vuex then access them directly from Vuex
4. Convert `actions`
   1. Remove the first `context` argument from each action. Everything should be accessible from `this` instead
   2. If using other stores either import them directly or access them on Vuex, the same as for getters
5. Convert `mutations`
   1. Mutations do not exist any more. These can be converted to `actions` instead, or you can just assign directly to the store within your components (eg. `userStore.firstName = 'First'`)
   2. If converting to actions, remove the first `state` argument and replace any assignments with `this` instead
   3. A common mutation is to reset the state back to its initial state. This is built in functionality with the store's `$reset` method. Note that this functionality only exists for option stores.

As you can see most of your code can be reused. Type safety should also help you identify what needs to be changed if anything is missed.

## Usage Inside Components

Now that your Vuex module has been converted to a Pinia store, any component or other file that uses that module needs to be updated too.

If you were using `map` helpers from Vuex before, it's worth looking at the [Usage without setup() guide](./options-api.md) as most of those helpers can be reused.

If you were using `useStore` then instead import the new store directly and access the state on it. For example:

```ts
// Vuex
import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex'

export default defineComponent({
  setup() {
    const store = useStore()

    const firstName = computed(() => store.state.auth.user.firstName)
    const fullName = computed(() => store.getters['auth/user/fullName'])

    return {
      firstName,
      fullName,
    }
  },
})
```

```ts
// Pinia
import { defineComponent, computed } from 'vue'
import { useAuthUserStore } from '@/stores/auth-user'

export default defineComponent({
  setup() {
    const authUserStore = useAuthUserStore()

    const firstName = computed(() => authUserStore.firstName)
    const fullName = computed(() => authUserStore.fullName)

    return {
      // you can also access the whole store in your component by returning it
      authUserStore,
      firstName,
      fullName,
    }
  },
})
```

## Usage Outside Components

Updating usage outside of components should be simple as long as you're careful to _not use a store outside of functions_. Here is an example of using the store in a Vue Router navigation guard:

```ts
// Vuex
import vuexStore from '@/store'

router.beforeEach((to, from, next) => {
  if (vuexStore.getters['auth/user/loggedIn']) next()
  else next('/login')
})
```

```ts
// Pinia
import { useAuthUserStore } from '@/stores/auth-user'

router.beforeEach((to, from, next) => {
  // Must be used within the function!
  const authUserStore = useAuthUserStore()
  if (authUserStore.loggedIn) next()
  else next('/login')
})
```

More details can be found [here](../core-concepts/outside-component-usage.md).

## Advanced Vuex Usage

In the case your Vuex store using some of the more advanced features it offers, here is some guidance on how to accomplish the same in Pinia. Some of these points are already covered in [this comparison summary](../introduction.md#Comparison-with-Vuex-3-x-4-x).

### Dynamic Modules

There is no need to dynamically register modules in Pinia. Stores are dynamic by design and are only registered when they are needed. If a store is never used, it will never be "registered".

### Hot Module Replacement

HMR is also supported but will need to be replaced, see the [HMR Guide](./hot-module-replacement.md).

### Plugins

If you use a public Vuex plugin then check if there is a Pinia alternative. If not you will need to write your own or evaluate whether the plugin is still necessary.

If you have written a plugin of your own, then it can likely be updated to work with Pinia. See the [Plugin Guide](../core-concepts/plugins.md).


================================================
FILE: packages/docs/cookbook/options-api.md
================================================
# Usage without `setup()`

Pinia can be used even if you are not using the composition API (if you are using Vue <2.7, you still need to install the `@vue/composition-api` plugin though). While we recommend you give the Composition API a try and learn it, it might not be the time for you and your team yet, you might be in the process of migrating an application, or any other reason. There are a few functions:

- [mapStores](#giving-access-to-the-whole-store)
- [mapState](../core-concepts/state.md#usage-with-the-options-api)
- [mapWritableState](../core-concepts/state.md#modifiable-state)
- ⚠️ [mapGetters](../core-concepts/getters.md#without-setup) (just for migration convenience, use `mapState()` instead)
- [mapActions](../core-concepts/actions.md#without-setup)

<RuleKitLink />

## Giving access to the whole store

If you need to access pretty much everything from the store, it might be too much to map every single property of the store... Instead you can get access to the whole store with `mapStores()`:

```js
import { mapStores } from 'pinia'

// given two stores with the following ids
const useUserStore = defineStore('user', {
  // ...
})
const useCartStore = defineStore('cart', {
  // ...
})

export default {
  computed: {
    // note we are not passing an array, just one store after the other
    // each store will be accessible as its id + 'Store'
    ...mapStores(useCartStore, useUserStore),
  },

  methods: {
    async buyStuff() {
      // use them anywhere!
      if (this.userStore.isAuthenticated()) {
        await this.cartStore.buy()
        this.$router.push('/purchased')
      }
    },
  },
}
```

By default, Pinia will add the `"Store"` suffix to the `id` of each store. You can customize this behavior by calling the `setMapStoreSuffix()`:

```js
import { createPinia, setMapStoreSuffix } from 'pinia'

// completely remove the suffix: this.user, this.cart
setMapStoreSuffix('')
// this.user_store, this.cart_store (it's okay, I won't judge you)
setMapStoreSuffix('_store')
export const pinia = createPinia()
```

## TypeScript

By default, all map helpers support autocompletion and you don't need to do anything. If you call `setMapStoreSuffix()` to change the `"Store"` suffix, you will need to also add it somewhere in a TS file or your `global.d.ts` file. The most convenient place would be the same place where you call `setMapStoreSuffix()`:

```ts
import { createPinia, setMapStoreSuffix } from 'pinia'

setMapStoreSuffix('') // completely remove the suffix
export const pinia = createPinia()

declare module 'pinia' {
  export interface MapStoresCustomization {
    // set it to the same value as above
    suffix: ''
  }
}
```

:::warning
If you are using a TypeScript declaration file (like `global.d.ts`), make sure to `import 'pinia'` at the top of it to expose all existing types.
:::


================================================
FILE: packages/docs/cookbook/testing.md
================================================
# Testing stores

<MasteringPiniaLink
  href="https://play.gumlet.io/embed/65f9a9c10bfab01f414c25dc"
  title="Watch a free video of Mastering Pinia about testing stores"
/>

Stores will, by design, be used at many places and can make testing much harder than it should be. Fortunately, this doesn't have to be the case. We need to take care of three things when testing stores:

- The `pinia` instance: Stores cannot work without it
- `actions`: most of the time, they contain the most complex logic of our stores. Wouldn't it be nice if they were mocked by default?
- Plugins: If you rely on plugins, you will have to install them for tests too

Depending on what or how you are testing, we need to take care of these three things differently.

<RuleKitLink />

## Unit testing a store

To unit test a store, the most important part is creating a `pinia` instance:

```js
// stores/counter.spec.ts
import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '../src/stores/counter'

describe('Counter Store', () => {
  beforeEach(() => {
    // creates a fresh pinia and makes it active
    // so it's automatically picked up by any useStore() call
    // without having to pass it to it: `useStore(pinia)`
    setActivePinia(createPinia())
  })

  it('increments', () => {
    const counter = useCounterStore()
    expect(counter.n).toBe(0)
    counter.increment()
    expect(counter.n).toBe(1)
  })

  it('increments by amount', () => {
    const counter = useCounterStore()
    counter.increment(10)
    expect(counter.n).toBe(10)
  })
})
```

If you have any store plugins, there is one important thing to know: **plugins won't be used until `pinia` is installed in an App**. This can be solved by creating an empty App or a fake one:

```js
import { setActivePinia, createPinia } from 'pinia'
import { createApp } from 'vue'
import { somePlugin } from '../src/stores/plugin'

// same code as above...

// you don't need to create one app per test
const app = createApp({})
beforeEach(() => {
  const pinia = createPinia().use(somePlugin)
  app.use(pinia)
  setActivePinia(pinia)
})
```

## Unit testing components

<!-- NOTE: too long maybe but good value -->
<!-- <MasteringPiniaLink
  href="https://play.gumlet.io/embed/6630f540c418f8419b73b2b2?t1=1715867840&t2=1715867570609?preload=false&autoplay=false&loop=false&disable_player_controls=false"
  title="Watch a free video of Mastering Pinia about testing stores"
/> -->

This can be achieved with `createTestingPinia()`, which returns a pinia instance designed to help unit tests components.

Start by installing `@pinia/testing`:

```shell
npm i -D @pinia/testing
```

And make sure to create a testing pinia in your tests when mounting a component:

```js
import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
// import any store you want to interact with in tests
import { useSomeStore } from '@/stores/myStore'

const wrapper = mount(Counter, {
  global: {
    plugins: [createTestingPinia()],
  },
})

const store = useSomeStore() // uses the testing pinia!

// state can be directly manipulated
store.name = 'my new name'
// can also be done through patch
store.$patch({ name: 'new name' })
expect(store.name).toBe('new name')

// actions are stubbed by default, meaning they don't execute their code by default.
// See below to customize this behavior.
store.someAction()

expect(store.someAction).toHaveBeenCalledTimes(1)
expect(store.someAction).toHaveBeenLastCalledWith()
```

### Initial State

You can set the initial state of **all of your stores** when creating a testing pinia by passing an `initialState` object. This object will be used by the testing pinia to _patch_ stores when they are created. Let's say you want to initialize the state of this store:

```ts
import { defineStore } from 'pinia'

const useCounterStore = defineStore('counter', {
  state: () => ({ n: 0 }),
  // ...
})
```

Since the store is named _"counter"_, you need to add a matching object to `initialState`:

```ts
// somewhere in your test
const wrapper = mount(Counter, {
  global: {
    plugins: [
      createTestingPinia({
        initialState: {
          counter: { n: 20 }, // start the counter at 20 instead of 0
        },
      }),
    ],
  },
})

const store = useSomeStore() // uses the testing pinia!
store.n // 20
```

### Customizing behavior of actions

`createTestingPinia` stubs out all store actions unless told otherwise. This allows you to test your components and stores separately.

If you want to revert this behavior and normally execute your actions during tests, specify `stubActions: false` when calling `createTestingPinia`:

```js
const wrapper = mount(Counter, {
  global: {
    plugins: [createTestingPinia({ stubActions: false })],
  },
})

const store = useSomeStore()

// Now this call WILL execute the implementation defined by the store
store.someAction()

// ...but it's still wrapped with a spy, so you can inspect calls
expect(store.someAction).toHaveBeenCalledTimes(1)
```

### Selective action stubbing

Sometimes you may want to stub only specific actions while allowing others to execute normally. You can achieve this by passing an array of action names to the `stubActions` option:

```js
// Only stub the 'increment' and 'reset' actions
const wrapper = mount(Counter, {
  global: {
    plugins: [
      createTestingPinia({
        stubActions: ['increment', 'reset'],
      }),
    ],
  },
})

const store = useSomeStore()

// These actions will be stubbed (not executed)
store.increment() // stubbed
store.reset() // stubbed

// Other actions will execute normally but still be spied
store.fetchData() // executed normally
expect(store.fetchData).toHaveBeenCalledTimes(1)
```

For more complex scenarios, you can pass a function that receives the action name and store instance, and returns whether the action should be stubbed:

```js
// Stub actions based on custom logic
const wrapper = mount(Counter, {
  global: {
    plugins: [
      createTestingPinia({
        stubActions: (actionName, store) => {
          // Stub all actions that start with 'set'
          if (actionName.startsWith('set')) return true

          // Stub actions based on initial store state
          if (store.isPremium) return false

          return true
        },
      }),
    ],
  },
})

const store = useSomeStore()

// Actions starting with 'set' are stubbed
store.setValue(42) // stubbed

// Other actions may execute based on the initial store state
store.fetchData() // executed or stubbed based on initial store.isPremium
```

::: tip

- An empty array `[]` means no actions will be stubbed (same as `false`)
- The function is evaluated once at store setup time, receiving the store instance in its initial state

:::

You can also manually mock specific actions after creating the store:

```ts
const store = useSomeStore()
vi.spyOn(store, 'increment').mockImplementation(() => {})
// or if using testing pinia with stubbed actions
store.increment.mockImplementation(() => {})
```

### Mocking the returned value of an action

Actions are automatically spied but type-wise, they are still the regular actions. In order to get the correct type, we must implement a custom type-wrapper that applies the `Mock` type to each action. **This type depends on the testing framework you are using**. Here is an example with Vitest:

```ts
import type { Mock } from 'vitest'
import type { UnwrapRef } from 'vue'
import type { Store, StoreDefinition } from 'pinia'

function mockedStore<TStoreDef extends () => unknown>(
  useStore: TStoreDef
): TStoreDef extends StoreDefinition<
  infer Id,
  infer State,
  infer Getters,
  infer Actions
>
  ? Store<
      Id,
      State,
      Record<string, never>,
      {
        [K in keyof Actions]: Actions[K] extends (...args: any[]) => any
          ? // 👇 depends on your testing framework
            Mock<Actions[K]>
          : Actions[K]
      }
    > & {
      [K in keyof Getters]: UnwrapRef<Getters[K]>
    }
  : ReturnType<TStoreDef> {
  return useStore() as any
}
```

This can be used in tests to get a correctly typed store:

```ts
import { mockedStore } from './mockedStore'
import { useSomeStore } from '@/stores/myStore'

const store = mockedStore(useSomeStore)
// typed!
store.someAction.mockResolvedValue('some value')
```

If you are interested in learning more tricks like this, you should check out the Testing lessons on [Mastering Pinia](https://masteringpinia.com/lessons/exercise-mocking-stores-introduction).

### Specifying the createSpy function

When using Jest, or vitest with `globals: true`, `createTestingPinia` automatically stubs actions using the spy function based on the existing test framework (`jest.fn` or `vitest.fn`). If you are not using `globals: true` or using a different framework, you'll need to provide a [createSpy](../api/@pinia/testing/interfaces/TestingOptions.html#createSpy-) option:

::: code-group

```ts [vitest]
// NOTE: not needed with `globals: true`
import { vi } from 'vitest'

createTestingPinia({
  createSpy: vi.fn,
})
```

```ts [sinon]
import sinon from 'sinon'

createTestingPinia({
  createSpy: sinon.spy,
})
```

:::

You can find more examples in [the tests of the testing package](https://github.com/vuejs/pinia/blob/v3/packages/testing/src/testing.spec.ts).

### Mocking getters

By default, any getter will be computed like regular usage but you can manually force a value by setting the getter to anything you want:

```ts
import { defineStore } from 'pinia'
import { createTestingPinia } from '@pinia/testing'

const useCounterStore = defineStore('counter', {
  state: () => ({ n: 1 }),
  getters: {
    double: (state) => state.n * 2,
  },
})

const pinia = createTestingPinia()
const counter = useCounterStore(pinia)

counter.double = 3 // 🪄 getters are writable only in tests

// set to undefined to reset the default behavior
// @ts-expect-error: usually it's a number
counter.double = undefined
counter.double // 2 (=1 x 2)
```

### Pinia Plugins

If you have any pinia plugins, make sure to pass them when calling `createTestingPinia()` so they are properly applied. **Do not add them with `testingPinia.use(MyPlugin)`** like you would do with a regular pinia:

```js
import { createTestingPinia } from '@pinia/testing'
import { somePlugin } from '../src/stores/plugin'

// inside some test
const wrapper = mount(Counter, {
  global: {
    plugins: [
      createTestingPinia({
        stubActions: false,
        plugins: [somePlugin],
      }),
    ],
  },
})
```

## E2E tests

When it comes to Pinia, you don't need to change anything for E2E tests, that's the whole point of these tests! You could maybe test HTTP requests, but that's way beyond the scope of this guide 😄.


================================================
FILE: packages/docs/cookbook/vscode-snippets.md
================================================
# VS Code Snippets

<RuleKitLink />

These are some snippets that I use in VS Code to make my life easier.

Manage user snippets with <kbd>⇧ Shift</kbd>+<kbd>⌘ Command</kbd>+<kbd>P</kbd> / <kbd>⇧ Shift</kbd>+<kbd>⌃ Control</kbd>+<kbd>P</kbd> and then `Snippets: Configure User Snippets`.

```json
{
  "Pinia Options Store Boilerplate": {
    "scope": "javascript,typescript",
    "prefix": "pinia-options",
    "body": [
      "import { defineStore, acceptHMRUpdate } from 'pinia'",
      "",
      "export const use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store = defineStore('$TM_FILENAME_BASE', {",
      " state: () => ({",
      "   $0",
      " }),",
      " getters: {},",
      " actions: {},",
      "})",
      "",
      "if (import.meta.hot) {",
      " import.meta.hot.accept(acceptHMRUpdate(use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store, import.meta.hot))",
      "}",
      ""
    ],
    "description": "Bootstrap the code needed for a Vue.js Pinia Options Store file"
  },
  "Pinia Setup Store Boilerplate": {
    "scope": "javascript,typescript",
    "prefix": "pinia-setup",
    "body": [
      "import { defineStore, acceptHMRUpdate } from 'pinia'",
      "",
      "export const use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store = defineStore('$TM_FILENAME_BASE', () => {",
      " $0",
      " return {}",
      "})",
      "",
      "if (import.meta.hot) {",
      " import.meta.hot.accept(acceptHMRUpdate(use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store, import.meta.hot))",
      "}",
      ""
    ],
    "description": "Bootstrap the code needed for a Vue.js Pinia Setup Store file"
  }
}
```


================================================
FILE: packages/docs/core-concepts/actions.md
================================================
# Actions

<!-- <VueSchoolLink
  href="https://vueschool.io/lessons/synchronous-and-asynchronous-actions-in-pinia"
  title="Learn all about actions in Pinia"
/> -->

<MasteringPiniaLink
  href="https://masteringpinia.com/lessons/the-3-pillars-of-pinia-actions"
  title="Learn all about actions in Pinia"
/>

Actions are the equivalent of [methods](https://vuejs.org/api/options-state.html#methods) in components. They can be defined with the `actions` property in `defineStore()` and **they are perfect to define business logic**:

```js
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  actions: {
    // since we rely on `this`, we cannot use an arrow function
    increment() {
      this.count++
    },
    randomizeCounter() {
      this.count = Math.round(100 * Math.random())
    },
  },
})
```

<RuleKitLink />

Like [getters](./getters.md), actions get access to the _whole store instance_ through `this` with **full typing (and autocompletion ✨) support**. **Unlike getters, `actions` can be asynchronous**, you can `await` inside of actions any API call or even other actions! Here is an example using [Mande](https://github.com/posva/mande). Note the library you use doesn't matter as long as you get a `Promise`. You could even use the native `fetch` function (browser only):

```js
import { mande } from 'mande'

const api = mande('/api/users')

export const useUsers = defineStore('users', {
  state: () => ({
    userData: null,
    // ...
  }),

  actions: {
    async registerUser(login, password) {
      try {
        this.userData = await api.post({ login, password })
        showTooltip(`Welcome back ${this.userData.name}!`)
      } catch (error) {
        showTooltip(error)
        // let the form component display the error
        return error
      }
    },
  },
})
```

You are also completely free to set whatever arguments you want and return anything. When calling actions, everything will be automatically inferred!

Actions are invoked like regular functions and methods:

```vue
<script setup>
const store = useCounterStore()
// call the action as a method of the store
store.randomizeCounter()
</script>

<template>
  <!-- Even on the template -->
  <button @click="store.randomizeCounter()">Randomize</button>
</template>
```

## Accessing other stores actions

To consume another store, you can directly _use it_ inside of the _action_:

```js
import { useAuthStore } from './auth-store'

export const useSettingsStore = defineStore('settings', {
  state: () => ({
    preferences: null,
    // ...
  }),
  actions: {
    async fetchUserPreferences() {
      const auth = useAuthStore()
      if (auth.isAuthenticated) {
        this.preferences = await fetchPreferences()
      } else {
        throw new Error('User must be authenticated')
      }
    },
  },
})
```

## Usage with the Options API

<VueSchoolLink
  href="https://vueschool.io/lessons/access-pinia-actions-in-the-options-api"
  title="Access Pinia Getters via the Options API"
/>

For the following examples, you can assume the following store was created:

```js
// Example File Path:
// ./src/stores/counter.js

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++
    },
  },
})
```

### With `setup()`

While Composition API is not for everyone, the `setup()` hook can make Pinia easier to work with while using the Options API. No extra map helper functions needed!

```vue
<script>
import { useCounterStore } from '../stores/counter'

export default defineComponent({
  setup() {
    const counterStore = useCounterStore()

    return { counterStore }
  },
  methods: {
    incrementAndPrint() {
      this.counterStore.increment()
      console.log('New Count:', this.counterStore.count)
    },
  },
})
</script>
```

### Without `setup()`

If you would prefer not to use Composition API at all, you can use the `mapActions()` helper to map actions properties as methods in your component:

```js
import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counter'

export default {
  methods: {
    // gives access to this.increment() inside the component
    // same as calling from store.increment()
    ...mapActions(useCounterStore, ['increment']),
    // same as above but registers it as this.myOwnName()
    ...mapActions(useCounterStore, { myOwnName: 'increment' }),
  },
}
```

## Subscribing to actions

It is possible to observe actions and their outcome with `store.$onAction()`. The callback passed to it is executed before the action itself. `after` handles promises and allows you to execute a function after the action resolves. In a similar way, `onError` allows you to execute a function if the action throws or rejects. These are useful for tracking errors at runtime, similar to [this tip in the Vue docs](https://vuejs.org/guide/best-practices/production-deployment#tracking-runtime-errors).

Here is an example that logs before running actions and after they resolve/reject.

```js
const unsubscribe = someStore.$onAction(
  ({
    name, // name of the action
    store, // store instance, same as `someStore`
    args, // array of parameters passed to the action
    after, // hook after the action returns or resolves
    onError, // hook if the action throws or rejects
  }) => {
    // a shared variable for this specific action call
    const startTime = Date.now()
    // this will trigger before an action on `store` is executed
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    // this will trigger if the action succeeds and after it has fully run.
    // it waits for any returned promise
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // this will trigger if the action throws or returns a promise that rejects
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

// manually remove the listener
unsubscribe()
```

By default, _action subscriptions_ are bound to the component where they are added (if the store is inside a component's `setup()`). Meaning, they will be automatically removed when the component is unmounted. If you also want to keep them after the component is unmounted, pass `true` as the second argument to _detach_ the _action subscription_ from the current component:

```vue
<script setup>
const someStore = useSomeStore()

// this subscription will be kept even after the component is unmounted
someStore.$onAction(callback, true)
</script>
```


================================================
FILE: packages/docs/core-concepts/getters.md
================================================
# Getters

<!-- <VueSchoolLink
  href="https://vueschool.io/lessons/getters-in-pinia"
  title="Learn all about getters in Pinia"
/> -->

<MasteringPiniaLink
  href="https://masteringpinia.com/lessons/the-3-pillars-of-pinia-getters"
  title="Learn all about getters in Pinia"
/>

Getters are exactly the equivalent of [computed values](https://vuejs.org/guide/essentials/computed.html) for the state of a Store. They can be defined with the `getters` property in `defineStore()`. They receive the `state` as the first parameter **to encourage** the usage of arrow function:

```js
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
})
```

<RuleKitLink />

Most of the time, getters will only rely on the state. However, they might need to use other getters. Because of this, we can get access to the _whole store instance_ through `this` when defining a regular function **but it is necessary to define the type of the return type (in TypeScript)**. This is due to a known limitation in TypeScript and **doesn't affect getters defined with an arrow function nor getters not using `this`**:

```ts
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    // automatically infers the return type as a number
    doubleCount(state) {
      return state.count * 2
    },
    // the return type **must** be explicitly set
    doublePlusOne(): number {
      // autocompletion and typings for the whole store ✨
      return this.doubleCount + 1
    },
  },
})
```

Then you can access the getter directly on the store instance:

```vue
<script setup>
import { useCounterStore } from './counterStore'

const store = useCounterStore()
</script>

<template>
  <p>Double count is {{ store.doubleCount }}</p>
</template>
```

## Accessing other getters

As with computed properties, you can combine multiple getters. Access any other getter via `this`. In this scenario, **you will need to specify a return type** for the getter.

::: code-group

```ts [counterStore.ts]
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    doubleCount(state) {
      return state.count * 2
    },
    doubleCountPlusOne(): number {
      return this.doubleCount + 1
    },
  },
})
```

```js [counterStore.js]
// You can use JSDoc (https://jsdoc.app/tags-returns.html) in JavaScript
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    // type is automatically inferred because we are not using `this`
    doubleCount: (state) => state.count * 2,
    // here we need to add the type ourselves (using JSDoc in JS). We can also
    // use this to document the getter
    /**
     * Returns the count value times two plus one.
     *
     * @returns {number}
     */
    doubleCountPlusOne() {
      // autocompletion ✨
      return this.doubleCount + 1
    },
  },
})
```

:::

## Passing arguments to getters

_Getters_ are just _computed_ properties behind the scenes, so it's not possible to pass any parameters to them. However, you can return a function from the _getter_ to accept any arguments:

```js
export const useStore = defineStore('main', {
  getters: {
    getUserById: (state) => {
      return (userId) => state.users.find((user) => user.id === userId)
    },
  },
})
```

and use in component:

```vue
<script setup>
import { storeToRefs } from 'pinia'
import { useUserListStore } from './store'

const userList = useUserListStore()
const { getUserById } = storeToRefs(userList)
// note you will have to use `getUserById.value` to access
// the function within the <script setup>
</script>

<template>
  <p>User 2: {{ getUserById(2) }}</p>
</template>
```

Note that when doing this, **getters are not cached anymore**. They are simply functions you invoke. You can, however, cache some results inside of the getter itself, which is uncommon but should prove more performant:

```js
export const useStore = defineStore('main', {
  getters: {
    getActiveUserById(state) {
      const activeUsers = state.users.filter((user) => user.active)
      return (userId) => activeUsers.find((user) => user.id === userId)
    },
  },
})
```

## Accessing other stores getters

To use another store's getters, you can directly _use it_ inside of the _getter_:

```js
import { useOtherStore } from './other-store'

export const useStore = defineStore('main', {
  state: () => ({
    // ...
  }),
  getters: {
    otherGetter(state) {
      const otherStore = useOtherStore()
      return state.localData + otherStore.data
    },
  },
})
```

## Usage with `setup()`

You can directly access any getter as a property of the store (exactly like state properties):

```vue
<script setup>
const store = useCounterStore()

store.count = 3
store.doubleCount // 6
</script>
```

## Usage with the Options API

<VueSchoolLink
  href="https://vueschool.io/lessons/access-pinia-getters-in-the-options-api"
  title="Access Pinia Getters via the Options API"
/>

For the following examples, you can assume the following store was created:

```js
// Example File Path:
// ./src/stores/counter.js

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    doubleCount(state) {
      return state.count * 2
    },
  },
})
```

### With `setup()`

While Composition API is not for everyone, the `setup()` hook can make using Pinia easier to work with in the Options API. No extra map helper functions needed!

```vue
<script>
import { useCounterStore } from '../stores/counter'

export default defineComponent({
  setup() {
    const counterStore = useCounterStore()

    // **only return the whole store** instead of destructuring
    return { counterStore }
  },
  computed: {
    quadrupleCounter() {
      return this.counterStore.doubleCount * 2
    },
  },
})
</script>
```

This is useful while migrating a component from the Options API to the Composition API but **should only be a migration step**. Always try not to mix both API styles within the same component.

### Without `setup()`

You can use the same `mapState()` function used in the [previous section of state](./state.md#options-api) to map to getters:

```js
import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counter'

export default {
  computed: {
    // gives access to this.doubleCount inside the component
    // same as reading from store.doubleCount
    ...mapState(useCounterStore, ['doubleCount']),
    // same as above but registers it as this.myOwnName
    ...mapState(useCounterStore, {
      myOwnName: 'doubleCount',
      // you can also write a function that gets access to the store
      double: (store) => store.doubleCount,
    }),
  },
}
```


================================================
FILE: packages/docs/core-concepts/index.md
================================================
# Defining a Store

<!-- <VueSchoolLink
  href="https://vueschool.io/lessons/define-your-first-pinia-store"
  title="Learn how to define and use stores in Pinia"
/> -->

<MasteringPiniaLink
  href="https://play.gumlet.io/embed/651ecff2e4c322668b0a17af"
  mp-link="https://masteringpinia.com/lessons/quick-start-with-pinia"
  title="Get started with Pinia"
/>

Before diving into core concepts, we need to know that a store is defined using `defineStore()` and that it requires a **unique** name, passed as the first argument:

```js
import { defineStore } from 'pinia'

// You can name the return value of `defineStore()` anything you want,
// but it's best to use the name of the store and surround it with `use`
// and `Store` (e.g. `useUserStore`, `useCartStore`, `useProductStore`)
// the first argument is a unique id of the store across your application
export const useAlertsStore = defineStore('alerts', {
  // other options...
})
```

This _name_, also referred to as _id_, is necessary and is used by Pinia to connect the store to the devtools. Naming the returned function _use..._ is a convention across composables to make its usage idiomatic.

`defineStore()` accepts two distinct values for its second argument: a Setup function or an Options object.

<RuleKitLink />

## Option Stores

Similar to Vue's Options API, we can also pass an Options Object with `state`, `actions`, and `getters` properties.

```js {2-10}
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0, name: 'Eduardo' }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})
```

You can think of `state` as the `data` of the store, `getters` as the `computed` properties of the store, and `actions` as the `methods`.

Option stores should feel intuitive and simple to get started with.

## Setup Stores

There is also another possible syntax to define stores. Similar to the Vue Composition API's [setup function](https://vuejs.org/api/composition-api-setup.html), we can pass in a function that defines reactive properties and methods and returns an object with the properties and methods we want to expose.

```js
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const name = ref('Eduardo')
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, name, doubleCount, increment }
})
```

In _Setup Stores_:

- `ref()`s become `state` properties
- `computed()`s become `getters`
- `function()`s become `actions`

Note that you **must** return **all state properties** in setup stores for Pinia to pick them up as state. In other words, you cannot have [_private_ state properties in stores](https://masteringpinia.com/blog/how-to-create-private-state-in-stores). Not returning all state properties or **making them readonly** will break [SSR](../cookbook/composables.md), devtools, and other plugins.

Setup stores bring a lot more flexibility than [Option Stores](#Option-Stores) as you can create watchers within a store and freely use any [composable](https://vuejs.org/guide/reusability/composables.html#composables). However, keep in mind that using composables will get more complex when using SSR.

Setup stores are also able to rely on globally _provided_ properties like the Router or the Route. Any property [provided at the App level](https://vuejs.org/api/application.html#app-provide) can be accessed from the store using `inject()`, just like in components:

```ts
import { inject } from 'vue'
import { useRoute } from 'vue-router'
import { defineStore } from 'pinia'

export const useSearchFilters = defineStore('search-filters', () => {
  const route = useRoute()
  // this assumes `app.provide('appProvided', 'value')` was called
  const appProvided = inject('appProvided')

  // ...

  return {
    // ...
  }
})
```

:::warning
Do not return properties like `route` or `appProvided` (from the example above) as they do not belong to the store itself and you can directly access them within components with `useRoute()` and `inject('appProvided')`.
:::

## What syntax should I pick?

As with [Vue's Composition API and Options API](https://vuejs.org/guide/introduction.html#which-to-choose), pick the one that you feel the most comfortable with. Both have their strengths and weaknesses. Options stores are easier to work with while Setup stores are more flexible and powerful. If you want to dive deeper into the differences, check the [Option Stores vs Setup Stores chapter](https://masteringpinia.com/lessons/when-to-choose-one-syntax-over-the-other) in Mastering Pinia.

## Using the store

We are _defining_ a store because the store won't be created until `use...Store()` is called within a component `<script setup>` (or within `setup()` **like all composables**):

```vue
<script setup>
import { useCounterStore } from '@/stores/counter'

// access the `store` variable anywhere in the component ✨
const store = useCounterStore()
</script>
```

:::tip
If you are not using `setup` components yet, [you can still use Pinia with _map helpers_](../cookbook/options-api.md).
:::

You can define as many stores as you want and **you should define each store in a different file** to get the most out of Pinia (like automatically allowing your bundler to code split and providing TypeScript inference).

Once the store is instantiated, you can access any property defined in `state`, `getters`, and `actions` directly on the store. We will look at these in detail in the next pages but autocompletion will help you.

Note that `store` is an object wrapped with `reactive`, meaning there is no need to write `.value` after getters but, like `props` in `setup`, **we cannot destructure it**:

```vue
<script setup>
import { useCounterStore } from '@/stores/counter'
import { computed } from 'vue'

const store = useCounterStore()
// ❌ This won't work because it breaks reactivity
// same as reactive: https://vuejs.org/guide/essentials/reactivity-fundamentals.html#limitations-of-reactive
const { name, doubleCount } = store // [!code warning]
name // will always be "Eduardo" // [!code warning]
doubleCount // will always be 0 // [!code warning]

setTimeout(() => {
  store.increment()
}, 1000)

// ✅ this one will be reactive
// 💡 but you could also just use `store.doubleCount` directly
const doubleValue = computed(() => store.doubleCount)
</script>
```

## Destructuring from a Store

In order to extract properties from the store while keeping its reactivity, you need to use `storeToRefs()`. It will create refs for every reactive property. This is useful when you are only using state from the store but not calling any action. Note you can destructure actions directly from the store as they are bound to the store itself too:

```vue
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const store = useCounterStore()
// `name` and `doubleCount` are reactive refs
// This will also extract refs for properties added by plugins
// but skip any action or non reactive (non ref/reactive) property
const { name, doubleCount } = storeToRefs(store)
// the increment action can just be destructured
const { increment } = store
</script>
```


================================================
FILE: packages/docs/core-concepts/outside-component-usage.md
================================================
# Using a store outside of a component

<MasteringPiniaLink
  href="https://play.gumlet.io/embed/651ed1ec4c2f339c6860fd06"
  mp-link="https://masteringpinia.com/lessons/how-does-usestore-work"
  title="Using stores outside of components"
/>

Pinia stores rely on the `pinia` instance to share the same store instance across all calls. Most of the time, this works out of the box by just calling your `useStore()` function. For example, in `setup()`, you don't need to do anything else. But things are a bit different outside of a component.
Behind the scenes, `useStore()` _injects_ the `pinia` instance you gave to your `app`. This means that if the `pinia` instance cannot be automatically injected, you have to manually provide it to the `useStore()` function.
You can solve this differently depending on the kind of application you are writing.

<RuleKitLink />

## Single Page Applications

If you are not doing any SSR (Server Side Rendering), any call of `useStore()` after installing the pinia plugin with `app.use(pinia)` will work:

```js
import { useUserStore } from '@/stores/user'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './App.vue'

// ❌  fails because it's called before the pinia is created
const userStore = useUserStore()

const pinia = createPinia()
const app = createApp(App)
app.use(pinia)

// ✅ works because the pinia instance is now active
const userStore = useUserStore()
```

The easiest way to ensure this is always applied is to _defer_ calls of `useStore()` by placing them inside functions that will always run after pinia is installed.

Let's take a look at this example of using a store inside of a navigation guard with Vue Router:

```js
import { createRouter } from 'vue-router'
const router = createRouter({
  // ...
})

// ❌ Depending on the order of imports this will fail
const store = useUserStore()

router.beforeEach((to, from, next) => {
  // we wanted to use the store here
  if (store.isLoggedIn) next()
  else next('/login')
})

router.beforeEach((to) => {
  // ✅ This will work because the router starts its navigation after
  // the router is installed and pinia will be installed too
  const store = useUserStore()

  if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
})
```

## SSR Apps

When dealing with Server Side Rendering, you will have to pass the `pinia` instance to `useStore()`. This prevents pinia from sharing global state between different application instances.

There is a whole section dedicated to it in the [SSR guide](/ssr/index.md), this is just a short explanation.


================================================
FILE: packages/docs/core-concepts/plugins.md
================================================
# Plugins

<MasteringPiniaLink
  href="https://masteringpinia.com/lessons/What-is-a-pinia-plugin"
  title="Learn all about Pinia plugins"
/>

Pinia stores can be fully extended thanks to a low level API. Here is a list of things you can do:

- Add new properties to stores
- Add new options when defining stores
- Add new methods to stores
- Wrap existing methods
- Intercept actions and its results
- Implement side effects like [Local Storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)
- Apply **only** to specific stores

<RuleKitLink />

Plugins are added to the pinia instance with `pinia.use()`. The simplest example is adding a static property to all stores by returning an object:

```js
import { createPinia } from 'pinia'

// add a property named `secret` to every store that is created
// after this plugin is installed this could be in a different file
function SecretPiniaPlugin() {
  return { secret: 'the cake is a lie' }
}

const pinia = createPinia()
// give the plugin to pinia
pinia.use(SecretPiniaPlugin)

// in another file
const store = useStore()
store.secret // 'the cake is a lie'
```

This is useful to add global objects like the router, modal, or toast managers.

## Introduction

A Pinia plugin is a function that optionally returns properties to be added to a store. It takes one optional argument, a _context_:

```js
export function myPiniaPlugin(context) {
  context.pinia // the pinia created with `createPinia()`
  context.app // the current app created with `createApp()`
  context.store // the store the plugin is augmenting
  context.options // the options object defining the store passed to `defineStore()`
  // ...
}
```

This function is then passed to `pinia` with `pinia.use()`:

```js
pinia.use(myPiniaPlugin)
```

Plugins are only applied to stores created **after the plugins themselves, and after `pinia` is passed to the app**, otherwise they won't be applied.

## Augmenting a Store

You can add properties to every store by simply returning an object of them in a plugin:

```js
pinia.use(() => ({ hello: 'world' }))
```

You can also set the property directly on the `store` but **if possible use the return version so they can be automatically tracked by devtools**:

```js
pinia.use(({ store }) => {
  store.hello = 'world'
})
```

Any property _returned_ by a plugin will be automatically tracked by devtools so in order to make `hello` visible in devtools, make sure to add it to `store._customProperties` **in dev mode only** if you want to debug it in devtools:

```js
// from the example above
pinia.use(({ store }) => {
  store.hello = 'world'
  // make sure your bundler handles this. webpack and vite should do it by default
  if (process.env.NODE_ENV === 'development') {
    // add any keys you set on the store
    store._customProperties.add('hello')
  }
})
```

Note that every store is wrapped with [`reactive`](https://vuejs.org/api/reactivity-core#reactive), automatically unwrapping any Ref (`ref()`, `computed()`, ...) it contains:

```js
const sharedRef = ref('shared')
pinia.use(({ store }) => {
  // each store has its individual `hello` property
  store.hello = ref('secret')
  // it gets automatically unwrapped
  store.hello // 'secret'

  // all stores are sharing the value `shared` property
  store.shared = sharedRef
  store.shared // 'shared'
})
```

This is why you can access all computed properties without `.value` and why they are reactive.

### Adding new state

If you want to add new state properties to a store or properties that are meant to be used during hydration, **you will have to add it in two places**:

- On the `store` so you can access it with `store.myState`
- On `store.$state` so it can be used in devtools and **be serialized during SSR**.

On top of that, you will certainly have to use a `ref()` (or other reactive API) in order to share the value across different accesses:

```js
import { toRef, ref } from 'vue'

pinia.use(({ store }) => {
  // to correctly handle SSR, we need to make sure we are not overriding an
  // existing value
  if (!Object.hasOwn(store.$state, 'hasError')) {
    // hasError is defined within the plugin, so each store has their individual
    // state property
    const hasError = ref(false)
    // setting the variable on `$state`, allows it be serialized during SSR
    store.$state.hasError = hasError
  }
  // we need to transfer the ref from the state to the store, this way
  // both accesses: store.hasError and store.$state.hasError will work
  // and share the same variable
  // See https://vuejs.org/api/reactivity-utilities.html#toref
  store.hasError = toRef(store.$state, 'hasError')

  // in this case it's better not to return `hasError` since it
  // will be displayed in the `state` section in the devtools
  // anyway and if we return it, devtools will display it twice.
})
```

Note that state changes or additions that occur within a plugin (that includes calling `store.$patch()`) happen before the store is active and therefore **do not trigger any subscriptions**.

#### Resetting state added in plugins

By default, `$reset()` will not reset state added by plugins but you can override it to also reset the state you add:

```js
import { toRef, ref } from 'vue'

pinia.use(({ store }) => {
  // this is the same code as above for reference
  if (!Object.hasOwn(store.$state, 'hasError')) {
    const hasError = ref(false)
    store.$state.hasError = hasError
  }
  store.hasError = toRef(store.$state, 'hasError')

  // make sure to set the context (`this`) to the store
  const originalReset = store.$reset.bind(store)

  // override the $reset function
  return {
    $reset() {
      originalReset()
      store.hasError = false
    },
  }
})
```

## Adding new external properties

When adding external properties, class instances that come from other libraries, or simply things that are not reactive, you should wrap the object with `markRaw()` before passing it to pinia. Here is an example adding the router to every store:

```js
import { markRaw } from 'vue'
// adapt this based on where your router is
import { router } from './router'

pinia.use(({ store }) => {
  store.router = markRaw(router)
})
```

## Calling `$subscribe` inside plugins

You can use [store.$subscribe](./state.md#Subscribing-to-the-state) and [store.$onAction](./actions.md#Subscribing-to-actions) inside plugins too:

```ts
pinia.use(({ store }) => {
  store.$subscribe(() => {
    // react to store changes
  })
  store.$onAction(() => {
    // react to store actions
  })
})
```

## Adding new options

It is possible to create new options when defining stores to later on consume them from plugins. For example, you could create a `debounce` option that allows you to debounce any action:

```js
defineStore('search', {
  actions: {
    searchContacts() {
      // ...
    },
  },

  // this will be read by a plugin later on
  debounce: {
    // debounce the action searchContacts by 300ms
    searchContacts: 300,
  },
})
```

The plugin can then read that option to wrap actions and replace the original ones:

```js
// use any debounce library
import debounce from 'lodash/debounce'

pinia.use(({ options, store }) => {
  if (options.debounce) {
    // we are overriding the actions with new ones
    return Object.keys(options.debounce).reduce((debouncedActions, action) => {
      debouncedActions[action] = debounce(
        store[action],
        options.debounce[action]
      )
      return debouncedActions
    }, {})
  }
})
```

Note that custom options are passed as the 3rd argument when using the setup syntax:

```js
defineStore(
  'search',
  () => {
    // ...
  },
  {
    // this will be read by a plugin later on
    debounce: {
      // debounce the action searchContacts by 300ms
      searchContacts: 300,
    },
  }
)
```

## TypeScript

Everything shown above can be done with typing support, so you don't ever need to use `any` or `@ts-ignore`.

### Typing plugins

A Pinia plugin can be typed as follows:

```ts
import { PiniaPluginContext } from 'pinia'

export function myPiniaPlugin(context: PiniaPluginContext) {
  // ...
}
```

### Typing new store properties

When adding new properties to stores, you should also extend the `PiniaCustomProperties` interface.

```ts
import 'pinia'
import type { Router } from 'vue-router'

declare module 'pinia' {
  export interface PiniaCustomProperties {
    // by using a setter we can allow both strings and refs
    set hello(value: string | Ref<strin
Download .txt
gitextract_roq8u4o3/

├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── ISSUE_TEMPLATE.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── commit-convention.md
│   ├── funding.yml
│   ├── settings.yml
│   └── workflows/
│       ├── ci.yml
│       ├── pkg.pr.new.yml
│       ├── release-tag.yml
│       └── update-sponsors.yml
├── .gitignore
├── .npmrc
├── .oxfmtrc.json
├── .vscode/
│   ├── launch.json
│   └── settings.json
├── LICENSE
├── README.md
├── SECURITY.md
├── codecov.yml
├── netlify.toml
├── package.json
├── packages/
│   ├── docs/
│   │   ├── .gitignore
│   │   ├── .vitepress/
│   │   │   ├── config/
│   │   │   │   ├── en.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── shared.ts
│   │   │   │   └── zh.ts
│   │   │   ├── theme/
│   │   │   │   ├── components/
│   │   │   │   │   ├── AsideSponsors.vue
│   │   │   │   │   ├── HomeSponsors.vue
│   │   │   │   │   ├── HomeSponsorsGroup.vue
│   │   │   │   │   ├── MadVueBanner.vue
│   │   │   │   │   ├── MasteringPiniaLink.vue
│   │   │   │   │   ├── PiniaLogo.vue
│   │   │   │   │   ├── RuleKitLink.vue
│   │   │   │   │   ├── VueMasteryBanner.vue
│   │   │   │   │   ├── VueMasteryHomeLink.vue
│   │   │   │   │   ├── VueMasteryLogoLink.vue
│   │   │   │   │   ├── VueSchoolLink.vue
│   │   │   │   │   ├── VuejsdeConfBanner.vue
│   │   │   │   │   └── sponsors.json
│   │   │   │   ├── index.ts
│   │   │   │   └── styles/
│   │   │   │       ├── home-links.css
│   │   │   │       ├── playground-links.css
│   │   │   │       └── vars.css
│   │   │   └── translation-status.json
│   │   ├── cookbook/
│   │   │   ├── composables.md
│   │   │   ├── composing-stores.md
│   │   │   ├── hot-module-replacement.md
│   │   │   ├── index.md
│   │   │   ├── migration-0-0-7.md
│   │   │   ├── migration-v1-v2.md
│   │   │   ├── migration-v2-v3.md
│   │   │   ├── migration-vuex.md
│   │   │   ├── options-api.md
│   │   │   ├── testing.md
│   │   │   └── vscode-snippets.md
│   │   ├── core-concepts/
│   │   │   ├── actions.md
│   │   │   ├── getters.md
│   │   │   ├── index.md
│   │   │   ├── outside-component-usage.md
│   │   │   ├── plugins.md
│   │   │   └── state.md
│   │   ├── getting-started.md
│   │   ├── index.md
│   │   ├── introduction.md
│   │   ├── package.json
│   │   ├── run-typedoc.mjs
│   │   ├── ssr/
│   │   │   ├── index.md
│   │   │   └── nuxt.md
│   │   ├── typedoc-markdown.mjs
│   │   ├── typedoc.tsconfig.json
│   │   ├── vite-typedoc-plugin.ts
│   │   ├── vite.config.ts
│   │   └── zh/
│   │       ├── api/
│   │       │   ├── enums/
│   │       │   │   └── pinia.MutationType.md
│   │       │   ├── index.md
│   │       │   ├── interfaces/
│   │       │   │   ├── pinia.DefineSetupStoreOptions.md
│   │       │   │   ├── pinia.DefineStoreOptions.md
│   │       │   │   ├── pinia.DefineStoreOptionsBase.md
│   │       │   │   ├── pinia.DefineStoreOptionsInPlugin.md
│   │       │   │   ├── pinia.MapStoresCustomization.md
│   │       │   │   ├── pinia.Pinia.md
│   │       │   │   ├── pinia.PiniaCustomProperties.md
│   │       │   │   ├── pinia.PiniaCustomStateProperties.md
│   │       │   │   ├── pinia.PiniaPlugin.md
│   │       │   │   ├── pinia.PiniaPluginContext.md
│   │       │   │   ├── pinia.StoreDefinition.md
│   │       │   │   ├── pinia.StoreProperties.md
│   │       │   │   ├── pinia.SubscriptionCallbackMutationDirect.md
│   │       │   │   ├── pinia.SubscriptionCallbackMutationPatchFunction.md
│   │       │   │   ├── pinia.SubscriptionCallbackMutationPatchObject.md
│   │       │   │   ├── pinia._StoreOnActionListenerContext.md
│   │       │   │   ├── pinia._StoreWithState.md
│   │       │   │   ├── pinia._SubscriptionCallbackMutationBase.md
│   │       │   │   ├── pinia_nuxt.ModuleOptions.md
│   │       │   │   ├── pinia_testing.TestingOptions.md
│   │       │   │   └── pinia_testing.TestingPinia.md
│   │       │   └── modules/
│   │       │       ├── pinia.md
│   │       │       ├── pinia_nuxt.md
│   │       │       └── pinia_testing.md
│   │       ├── cookbook/
│   │       │   ├── composables.md
│   │       │   ├── composing-stores.md
│   │       │   ├── hot-module-replacement.md
│   │       │   ├── index.md
│   │       │   ├── migration-0-0-7.md
│   │       │   ├── migration-v1-v2.md
│   │       │   ├── migration-vuex.md
│   │       │   ├── options-api.md
│   │       │   ├── testing.md
│   │       │   └── vscode-snippets.md
│   │       ├── core-concepts/
│   │       │   ├── actions.md
│   │       │   ├── getters.md
│   │       │   ├── index.md
│   │       │   ├── outside-component-usage.md
│   │       │   ├── plugins.md
│   │       │   └── state.md
│   │       ├── getting-started.md
│   │       ├── index.md
│   │       ├── introduction.md
│   │       └── ssr/
│   │           ├── index.md
│   │           └── nuxt.md
│   ├── nuxt/
│   │   ├── .gitignore
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── playground/
│   │   │   ├── app.vue
│   │   │   ├── domain/
│   │   │   │   └── one/
│   │   │   │       └── stores/
│   │   │   │           └── testStore.ts
│   │   │   ├── fake-main.js
│   │   │   ├── layers/
│   │   │   │   └── layer-domain/
│   │   │   │       ├── nuxt.config.ts
│   │   │   │       └── stores/
│   │   │   │           └── basic.ts
│   │   │   ├── nuxt.config.ts
│   │   │   ├── package.json
│   │   │   ├── pages/
│   │   │   │   ├── index.vue
│   │   │   │   └── skip-hydrate.vue
│   │   │   ├── stores/
│   │   │   │   ├── counter.ts
│   │   │   │   ├── nested/
│   │   │   │   │   └── some-store.ts
│   │   │   │   └── with-skip-hydrate.ts
│   │   │   └── tsconfig.json
│   │   ├── shims.d.ts
│   │   ├── src/
│   │   │   ├── auto-hmr-plugin.ts
│   │   │   ├── module.ts
│   │   │   └── runtime/
│   │   │       ├── composables.ts
│   │   │       ├── payload-plugin.ts
│   │   │       └── plugin.vue3.ts
│   │   ├── test/
│   │   │   └── nuxt.spec.ts
│   │   └── tsconfig.json
│   ├── online-playground/
│   │   ├── README.md
│   │   ├── deploy-check.sh
│   │   ├── index.html
│   │   ├── netlify.toml
│   │   ├── package.json
│   │   ├── shims-vue.d.ts
│   │   ├── src/
│   │   │   ├── App.vue
│   │   │   ├── Header.vue
│   │   │   ├── VersionSelect.vue
│   │   │   ├── defaults.ts
│   │   │   ├── download/
│   │   │   │   ├── download.ts
│   │   │   │   └── template/
│   │   │   │       ├── README.md
│   │   │   │       ├── index.html
│   │   │   │       ├── main.js
│   │   │   │       ├── package.json
│   │   │   │       └── vite.config.js
│   │   │   ├── icons/
│   │   │   │   ├── Download.vue
│   │   │   │   ├── GitHub.vue
│   │   │   │   ├── Moon.vue
│   │   │   │   ├── Share.vue
│   │   │   │   └── Sun.vue
│   │   │   ├── main.ts
│   │   │   ├── pinia-dev-proxy.ts
│   │   │   ├── vue-dev-proxy.ts
│   │   │   └── vue-server-renderer-dev-proxy.ts
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   ├── pinia/
│   │   ├── .gitignore
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── __tests__/
│   │   │   ├── actions.spec.ts
│   │   │   ├── combinedStores.spec.ts
│   │   │   ├── devtools.spec.ts
│   │   │   ├── getters.spec.ts
│   │   │   ├── hmr.spec.ts
│   │   │   ├── lifespan.spec.ts
│   │   │   ├── mapHelpers.spec.ts
│   │   │   ├── onAction.spec.ts
│   │   │   ├── pinia/
│   │   │   │   └── stores/
│   │   │   │       ├── cart.ts
│   │   │   │       ├── combined.ts
│   │   │   │       └── user.ts
│   │   │   ├── rootState.spec.ts
│   │   │   ├── ssr.spec.ts
│   │   │   ├── state.spec.ts
│   │   │   ├── store.patch.spec.ts
│   │   │   ├── store.spec.ts
│   │   │   ├── storePlugins.spec.ts
│   │   │   ├── storeSetup.spec.ts
│   │   │   ├── storeToRefs.spec.ts
│   │   │   ├── subscriptions.spec.ts
│   │   │   ├── vitest-mock-warn.ts
│   │   │   └── vitest-setup.ts
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── createPinia.ts
│   │   │   ├── devtools/
│   │   │   │   ├── actions.ts
│   │   │   │   ├── file-saver.ts
│   │   │   │   ├── formatting.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── plugin.ts
│   │   │   │   └── utils.ts
│   │   │   ├── env.ts
│   │   │   ├── global.d.ts
│   │   │   ├── globalExtensions.ts
│   │   │   ├── hmr.ts
│   │   │   ├── index.ts
│   │   │   ├── mapHelpers.ts
│   │   │   ├── rootStore.ts
│   │   │   ├── store.ts
│   │   │   ├── storeToRefs.ts
│   │   │   ├── subscriptions.ts
│   │   │   └── types.ts
│   │   ├── test-dts/
│   │   │   ├── actions.test-d.ts
│   │   │   ├── customizations.test-d.ts
│   │   │   ├── index.d.ts
│   │   │   ├── mapHelpers.test-d.ts
│   │   │   ├── onAction.test-d.ts
│   │   │   ├── plugins.test-d.ts
│   │   │   ├── state.test-d.ts
│   │   │   ├── store.test-d.ts
│   │   │   ├── storeSetup.test-d.ts
│   │   │   ├── storeToRefs.test-d.ts
│   │   │   ├── tsconfig.json
│   │   │   └── typeHelpers.test-d.ts
│   │   └── tsdown.config.ts
│   ├── playground/
│   │   ├── .gitignore
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── App.vue
│   │   │   ├── api/
│   │   │   │   ├── jokes.ts
│   │   │   │   └── nasa.ts
│   │   │   ├── composables/
│   │   │   │   └── useCachedRequest.ts
│   │   │   ├── main.ts
│   │   │   ├── router.ts
│   │   │   ├── shims-vue.d.ts
│   │   │   ├── stores/
│   │   │   │   ├── cart.ts
│   │   │   │   ├── counter.ts
│   │   │   │   ├── counterSetup.ts
│   │   │   │   ├── demo-counter.ts
│   │   │   │   ├── jokes-swrv.ts
│   │   │   │   ├── jokes.ts
│   │   │   │   ├── jokesUsePromised.ts
│   │   │   │   ├── nasa-pod.ts
│   │   │   │   ├── nasa.ts
│   │   │   │   ├── user.ts
│   │   │   │   └── wholeStore.ts
│   │   │   ├── test.ts
│   │   │   ├── views/
│   │   │   │   ├── 404.vue
│   │   │   │   ├── About.vue
│   │   │   │   ├── AllStores.vue
│   │   │   │   ├── AllStoresDispose.vue
│   │   │   │   ├── CounterSetupStore.vue
│   │   │   │   ├── CounterStore.vue
│   │   │   │   ├── DemoCounter.vue
│   │   │   │   ├── Jokes.vue
│   │   │   │   ├── JokesPromised.vue
│   │   │   │   ├── NasaPOD.vue
│   │   │   │   ├── NasaPODSwrv.vue
│   │   │   │   └── swrv.vue
│   │   │   └── vite-env.d.ts
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   ├── size-check/
│   │   ├── package.json
│   │   ├── rollup.config.mjs
│   │   ├── scripts/
│   │   │   └── check-size.mjs
│   │   └── src/
│   │       └── pinia.js
│   └── testing/
│       ├── CHANGELOG.md
│       ├── README.md
│       ├── package.json
│       ├── src/
│       │   ├── index.ts
│       │   ├── initialState.spec.ts
│       │   ├── mocked-store.spec.ts
│       │   ├── restoreGetters.spec.ts
│       │   ├── restoreGetters.ts
│       │   ├── testing.spec.ts
│       │   └── testing.ts
│       ├── tsconfig.build.json
│       └── tsdown.config.ts
├── pnpm-workspace.yaml
├── renovate.json
├── scripts/
│   ├── docs-check.sh
│   ├── release.mjs
│   └── verifyCommit.mjs
├── tsconfig.json
└── vitest.config.ts
Download .txt
SYMBOL INDEX (428 symbols across 80 files)

FILE: packages/docs/.vitepress/config/en.ts
  constant META_URL (line 4) | const META_URL = 'https://pinia.vuejs.org'
  constant META_TITLE (line 5) | const META_TITLE = 'Pinia 🍍'
  constant META_DESCRIPTION (line 6) | const META_DESCRIPTION =

FILE: packages/docs/.vitepress/config/shared.ts
  constant META_IMAGE (line 4) | const META_IMAGE = 'https://pinia.vuejs.org/social.png'

FILE: packages/docs/.vitepress/config/zh.ts
  constant META_URL (line 3) | const META_URL = 'https://pinia.vuejs.org'
  constant META_TITLE (line 4) | const META_TITLE = 'Pinia 🍍'
  constant META_DESCRIPTION (line 5) | const META_DESCRIPTION = '值得你喜欢的 Vue Store'

FILE: packages/docs/.vitepress/theme/index.ts
  method Layout (line 23) | Layout() {
  method enhanceApp (line 33) | enhanceApp({ app }) {

FILE: packages/docs/typedoc-markdown.mjs
  constant DEFAULT_OPTIONS (line 9) | const DEFAULT_OPTIONS = {
  function createTypeDocApp (line 22) | async function createTypeDocApp(config = {}) {
  function exists (line 89) | async function exists(path) {
  function prependYAML (line 107) | function prependYAML(contents, vars) {
  function toYAML (line 117) | function toYAML(vars) {
  function escapeDoubleQuotes (line 135) | function escapeDoubleQuotes(str) {

FILE: packages/docs/vite-typedoc-plugin.ts
  function TypeDocPlugin (line 6) | function TypeDocPlugin(

FILE: packages/docs/vite.config.ts
  function copyPiniaPlugin (line 31) | function copyPiniaPlugin(): Plugin {

FILE: packages/nuxt/playground/stores/counter.ts
  method increment (line 8) | increment() {
  method asyncIncrement (line 12) | async asyncIncrement() {

FILE: packages/nuxt/shims.d.ts
  type Process (line 2) | interface Process {

FILE: packages/nuxt/src/auto-hmr-plugin.ts
  function getStoreDeclaration (line 4) | function getStoreDeclaration(nodes?: VariableDeclarator[]) {
  function nameFromDeclaration (line 13) | function nameFromDeclaration(node?: VariableDeclarator) {
  function autoRegisterHMRPlugin (line 17) | function autoRegisterHMRPlugin(rootDir: string) {

FILE: packages/nuxt/src/module.ts
  type ModuleOptions (line 17) | interface ModuleOptions {
  method setup (line 38) | setup(options, nuxt) {

FILE: packages/nuxt/src/runtime/plugin.vue3.ts
  method setup (line 8) | setup(nuxtApp) {
  method 'app:rendered' (line 25) | 'app:rendered'() {

FILE: packages/nuxt/test/nuxt.spec.ts
  method 'vite:extendConfig' (line 11) | 'vite:extendConfig'(config, { isClient }) {

FILE: packages/online-playground/src/download/download.ts
  function downloadProject (line 10) | async function downloadProject(store: ReplStore) {

FILE: packages/online-playground/vite.config.ts
  function copyPiniaPlugin (line 31) | function copyPiniaPlugin(): Plugin {

FILE: packages/pinia/__tests__/actions.spec.ts
  method nonA (line 17) | nonA(): boolean {
  method otherComputed (line 20) | otherComputed() {
  method getNonA (line 25) | async getNonA() {
  method simple (line 28) | simple() {
  method toggle (line 33) | toggle() {
  method setFoo (line 37) | setFoo(foo: string) {
  method combined (line 41) | combined() {
  method throws (line 46) | throws() {
  method rejects (line 50) | async rejects() {
  method swap (line 62) | swap() {
  method toggle (line 170) | toggle() {}

FILE: packages/pinia/__tests__/combinedStores.spec.ts
  function useCounter (line 10) | function useCounter() {
  function changeB (line 25) | function changeB() {
  function getInnerB (line 29) | function getInnerB() {
  function changeA (line 40) | function changeA() {
  function getInnerA (line 44) | function getInnerA() {

FILE: packages/pinia/__tests__/devtools.spec.ts
  method myAction (line 9) | myAction() {

FILE: packages/pinia/__tests__/getters.spec.ts
  function expectType (line 5) | function expectType<T>(_value: T): void {}
  method upperCaseName (line 17) | upperCaseName(store) {
  method doubleName (line 20) | doubleName(): string {
  method composed (line 23) | composed(): string {
  method o (line 33) | o() {
  method fromB (line 47) | fromB(): string {
  method get (line 108) | get() {
  method set (line 111) | set(value: string) {
  method get (line 128) | get() {
  method set (line 131) | set(value: string | number) {
  function increment (line 156) | function increment() {
  function incrementB (line 160) | function incrementB() {
  function increment (line 173) | function increment() {
  function incrementA (line 177) | function incrementA() {

FILE: packages/pinia/__tests__/hmr.spec.ts
  function defineOptions (line 11) | function defineOptions<
  method increment (line 31) | increment(amount = 1) {
  function increment (line 59) | function increment(amount = 1) {
  function increment (line 81) | function increment(amount = 1) {
  function increment (line 100) | function increment(amount = 1) {
  function increment (line 132) | function increment(amount = 1) {
  function increment (line 157) | function increment(amount = 1) {
  function decrement (line 186) | function decrement() {
  method decrement (line 410) | decrement() {

FILE: packages/pinia/__tests__/lifespan.spec.ts
  function defineMyStore (line 21) | function defineMyStore() {
  method setup (line 60) | setup() {
  method setup (line 77) | setup() {
  method setup (line 129) | setup() {

FILE: packages/pinia/__tests__/mapHelpers.spec.ts
  method double (line 27) | double(state) {
  method notA (line 30) | notA(state) {
  method doubleCount (line 35) | doubleCount() {
  function testComponent (line 78) | async function testComponent(
  method n (line 127) | n(store) {
  method increment (line 152) | increment() {
  method setN (line 155) | setN(newN: number) {
  function testComponent (line 193) | async function testComponent(

FILE: packages/pinia/__tests__/onAction.spec.ts
  method direct (line 15) | direct(name: string) {
  method patchObject (line 18) | patchObject(user: string) {
  method patchFn (line 21) | patchFn(name: string) {
  method asyncUpperName (line 26) | async asyncUpperName() {
  method upperName (line 29) | upperName() {
  method throws (line 32) | throws(e: any) {
  method rejects (line 35) | async rejects(e: any) {
  method changeName (line 176) | changeName(name: string) {
  method setup (line 211) | setup() {

FILE: packages/pinia/__tests__/pinia/stores/cart.ts
  method addItem (line 28) | addItem(name: string) {
  method removeItem (line 32) | removeItem(name: string) {
  method purchaseItems (line 39) | async purchaseItems() {
  type CartStore (line 52) | type CartStore = ReturnType<typeof useCartStore>
  function addItem (line 54) | function addItem(name: string) {
  function removeItem (line 59) | function removeItem(name: string) {
  function purchaseItems (line 65) | async function purchaseItems() {

FILE: packages/pinia/__tests__/pinia/stores/combined.ts
  function _test (line 8) | function _test() {

FILE: packages/pinia/__tests__/pinia/stores/user.ts
  function apiLogin (line 3) | function apiLogin(a: string, p: string) {
  method login (line 14) | async login(user: string, password: string) {
  method logout (line 23) | logout() {
  method test (line 33) | test(state) {
  type UserStore (line 39) | type UserStore = ReturnType<typeof useUserStore>
  function logout (line 43) | function logout() {

FILE: packages/pinia/__tests__/ssr.spec.ts
  method ssrRender (line 23) | ssrRender(ctx: any, push: any, _parent: any) {
  method setup (line 30) | setup() {
  function createMyApp (line 39) | function createMyApp(MyApp: Component = App) {
  method ssrRender (line 73) | ssrRender(ctx: any, push: any, _parent: any) {
  method setup (line 80) | setup() {
  function useCustomRef (line 101) | function useCustomRef() {
  method ssrRender (line 122) | ssrRender(ctx: any, push: any, _parent: any) {
  method setup (line 125) | setup() {
  function increment (line 188) | function increment() {
  method ssrRender (line 196) | ssrRender(ctx: any, push: any, _parent: any) {
  method setup (line 203) | setup() {

FILE: packages/pinia/__tests__/state.spec.ts
  method set (line 147) | set(val) {
  function useCustomRef (line 374) | function useCustomRef() {
  method hydrate (line 400) | hydrate(storeState, initialState) {

FILE: packages/pinia/__tests__/store.spec.ts
  method setup (line 63) | setup() {
  method setup (line 183) | setup() {
  method setup (line 225) | setup() {
  method setup (line 253) | setup() {
  method setup (line 261) | setup() {
  method setup (line 280) | setup() {
  class MyState (line 335) | class MyState {}
  class MyState (line 343) | class MyState {}

FILE: packages/pinia/__tests__/storePlugins.spec.ts
  type PiniaCustomProperties (line 7) | interface PiniaCustomProperties<Id> {
  type PiniaCustomStateProperties (line 18) | interface PiniaCustomStateProperties<S> {
  method incrementN (line 28) | incrementN() {
  method $reset (line 85) | $reset() {
  method increment (line 199) | increment() {
  method a (line 205) | a() {
  function increment (line 227) | function increment() {
  method setup (line 269) | setup() {

FILE: packages/pinia/__tests__/storeSetup.spec.ts
  function expectType (line 6) | function expectType<T>(_value: T): void {}
  function mainFn (line 9) | function mainFn() {
  function action (line 44) | function action() {}
  method set (line 98) | set(val) {
  method setup (line 161) | setup() {
  method setup (line 169) | setup() {

FILE: packages/pinia/__tests__/storeToRefs.spec.ts
  function objectOfRefs (line 10) | function objectOfRefs<O extends Record<any, any>>(o: O): ToRefs<O> {
  method get (line 157) | get() {
  method set (line 160) | set(value: string | number) {
  function tds (line 213) | function tds(_fn: Function) {}

FILE: packages/pinia/__tests__/subscriptions.spec.ts
  method setup (line 80) | setup() {
  method setup (line 263) | setup() {
  function once (line 332) | function once() {

FILE: packages/pinia/__tests__/vitest-mock-warn.ts
  type CustomMatchers (line 5) | interface CustomMatchers<R = unknown> {
  type Assertion (line 15) | interface Assertion<T = any> extends CustomMatchers<T> {}
  type AsymmetricMatchersContaining (line 16) | interface AsymmetricMatchersContaining extends CustomMatchers {}
  function createMockConsoleMethod (line 19) | function createMockConsoleMethod(method: 'warn' | 'error') {
  function mockWarn (line 123) | function mockWarn() {
  function mockConsoleError (line 127) | function mockConsoleError() {

FILE: packages/pinia/src/createPinia.ts
  function createPinia (line 10) | function createPinia(): Pinia {
  function disposePinia (line 72) | function disposePinia(pinia: Pinia) {

FILE: packages/pinia/src/devtools/actions.ts
  function checkClipboardAccess (line 11) | function checkClipboardAccess() {
  function checkNotFocusedError (line 18) | function checkNotFocusedError(error: unknown): error is Error {
  function actionGlobalCopyState (line 32) | async function actionGlobalCopyState(pinia: Pinia) {
  function actionGlobalPasteState (line 47) | async function actionGlobalPasteState(pinia: Pinia) {
  function actionGlobalSaveState (line 62) | async function actionGlobalSaveState(pinia: Pinia) {
  function getFileOpener (line 80) | function getFileOpener() {
  function actionGlobalOpenStateFile (line 105) | async function actionGlobalOpenStateFile(pinia: Pinia) {
  function loadStoresState (line 122) | function loadStoresState(pinia: Pinia, state: Record<string, unknown>) {

FILE: packages/pinia/src/devtools/file-saver.ts
  type Options (line 25) | interface Options {
  function bom (line 29) | function bom(blob: Blob, { autoBom = false }: Options = {}) {
  function download (line 43) | function download(url: string, name: string, opts?: Options) {
  function corsEnabled (line 56) | function corsEnabled(url: string) {
  function click (line 67) | function click(node: Element) {
  type SaveAs (line 101) | type SaveAs =
  function downloadSaveAs (line 123) | function downloadSaveAs(blob: Blob, name: string = 'download', opts?: Op...
  function msSaveAs (line 157) | function msSaveAs(blob: Blob, name: string = 'download', opts?: Options) {
  function fileSaverSaveAs (line 175) | function fileSaverSaveAs(

FILE: packages/pinia/src/devtools/formatting.ts
  type StateBase (line 7) | interface StateBase {
  type ComponentStateBase (line 14) | interface ComponentStateBase extends StateBase {
  type ComponentBuiltinCustomStateTypes (line 17) | type ComponentBuiltinCustomStateTypes =
  type CustomState (line 26) | interface CustomState {
  type ComponentPropState (line 51) | interface ComponentPropState extends ComponentStateBase {
  type ComponentState (line 59) | type ComponentState =
  type ComponentCustomState (line 63) | interface ComponentCustomState extends ComponentStateBase {
  type InspectorNodeTag (line 67) | interface InspectorNodeTag {
  type CustomInspectorNode (line 73) | interface CustomInspectorNode {
  type CustomInspectorState (line 82) | interface CustomInspectorState {
  function formatDisplay (line 86) | function formatDisplay(display: string) {
  constant PINIA_ROOT_LABEL (line 94) | const PINIA_ROOT_LABEL = '🍍 Pinia (root)'
  constant PINIA_ROOT_ID (line 95) | const PINIA_ROOT_ID = '_root'
  function formatStoreForInspectorTree (line 97) | function formatStoreForInspectorTree(
  function formatStoreForInspectorState (line 111) | function formatStoreForInspectorState(
  function formatEventData (line 173) | function formatEventData(
  function formatMutationType (line 204) | function formatMutationType(type: MutationType): string {

FILE: packages/pinia/src/devtools/plugin.ts
  constant MUTATIONS_LAYER_ID (line 32) | const MUTATIONS_LAYER_ID = 'pinia:mutations'
  constant INSPECTOR_ID (line 33) | const INSPECTOR_ID = 'pinia'
  type TimelineEvent (line 37) | interface TimelineEvent<TData = any, TMeta = any> {
  function registerPiniaDevtools (line 62) | function registerPiniaDevtools(app: App, pinia: Pinia) {
  function addStoreToDevtools (line 311) | function addStoreToDevtools(app: App, store: StoreGeneric) {
  function patchActionForGrouping (line 523) | function patchActionForGrouping(
  function devtoolsPlugin (line 568) | function devtoolsPlugin<

FILE: packages/pinia/src/devtools/utils.ts
  function toastMessage (line 9) | function toastMessage(
  function isPinia (line 24) | function isPinia(o: any): o is Pinia {

FILE: packages/pinia/src/env.ts
  constant IS_CLIENT (line 1) | const IS_CLIENT = typeof window !== 'undefined'

FILE: packages/pinia/src/globalExtensions.ts
  type GlobalComponents (line 7) | interface GlobalComponents {}
  type ComponentCustomProperties (line 8) | interface ComponentCustomProperties {

FILE: packages/pinia/src/hmr.ts
  function patchObject (line 32) | function patchObject(
  function acceptHMRUpdate (line 77) | function acceptHMRUpdate<

FILE: packages/pinia/src/mapHelpers.ts
  type MapStoresCustomization (line 17) | interface MapStoresCustomization {
  type _StoreObject (line 25) | type _StoreObject<S> =
  type _Spread (line 57) | type _Spread<A extends readonly any[]> = A extends [infer L, ...infer R]
  function setMapStoreSuffix (line 70) | function setMapStoreSuffix(
  function mapStores (line 100) | function mapStores<Stores extends any[]>(
  type _MapStateReturn (line 129) | type _MapStateReturn<
  type _MapStateObjectReturn (line 147) | type _MapStateObjectReturn<
  function mapState (line 256) | function mapState<
  type _MapActionsReturn (line 307) | type _MapActionsReturn<A> = {
  type _MapActionsObjectReturn (line 314) | type _MapActionsObjectReturn<A, T extends Record<string, keyof A>> = {
  function mapActions (line 393) | function mapActions<
  type _MapWritableStateKeys (line 434) | type _MapWritableStateKeys<S extends StateTree, G> =
  type _MapWritableStateReturn (line 441) | type _MapWritableStateReturn<
  type _MapWritableStateObjectReturn (line 455) | type _MapWritableStateObjectReturn<
  function mapWritableState (line 510) | function mapWritableState<

FILE: packages/pinia/src/rootStore.ts
  type _SetActivePinia (line 37) | interface _SetActivePinia {
  type Pinia (line 65) | interface Pinia {
  type PiniaPluginContext (line 123) | interface PiniaPluginContext<
  type PiniaPlugin (line 153) | interface PiniaPlugin {

FILE: packages/pinia/src/store.ts
  type _SetType (line 56) | type _SetType<AT> = AT extends Set<infer T> ? T : never
  constant ACTION_MARKER (line 62) | const ACTION_MARKER = Symbol()
  constant ACTION_NAME (line 67) | const ACTION_NAME = Symbol()
  type MarkedAction (line 72) | interface MarkedAction<Fn extends _Method = _Method> {
  function mergeReactiveObjects (line 78) | function mergeReactiveObjects<
  function skipHydrate (line 125) | function skipHydrate<T = any>(obj: T): T {
  function shouldHydrate (line 135) | function shouldHydrate(obj: any) {
  function isComputed (line 142) | function isComputed(o: any): o is ComputedRef {
  function createOptionsStore (line 146) | function createOptionsStore<
  function createSetupStore (line 213) | function createSetupStore<
  type StoreActions (line 759) | type StoreActions<SS> =
  type StoreGetters (line 768) | type StoreGetters<SS> =
  type StoreState (line 777) | type StoreState<SS> =
  type SetupStoreHelpers (line 782) | interface SetupStoreHelpers {
  function defineStore (line 835) | function defineStore(
  type SetupStoreDefinition (line 938) | interface SetupStoreDefinition<

FILE: packages/pinia/src/storeToRefs.ts
  type _IfEquals (line 26) | type _IfEquals<X, Y, A = true, B = false> =
  type _IsReadonly (line 32) | type _IsReadonly<T, K extends keyof T> = _IfEquals<
  type _ToComputedRefs (line 42) | type _ToComputedRefs<SS> = {
  type _ToStateRefs (line 52) | type _ToStateRefs<SS> =
  type StoreToRefs (line 70) | type StoreToRefs<SS extends StoreGeneric> =
  function storeToRefs (line 87) | function storeToRefs<SS extends StoreGeneric>(

FILE: packages/pinia/src/subscriptions.ts
  function addSubscription (line 6) | function addSubscription<T extends _Method>(
  function triggerSubscriptions (line 26) | function triggerSubscriptions<T extends _Method>(

FILE: packages/pinia/src/types.ts
  type StateTree (line 14) | type StateTree = Record<PropertyKey, any>
  function isPlainObject (line 19) | function isPlainObject(
  type _DeepPartial (line 36) | type _DeepPartial<T> = { [K in keyof T]?: _DeepPartial<T[K]> }
  type MutationType (line 43) | enum MutationType {
  type _SubscriptionCallbackMutationBase (line 73) | interface _SubscriptionCallbackMutationBase {
  type SubscriptionCallbackMutationDirect (line 97) | interface SubscriptionCallbackMutationDirect extends _SubscriptionCallba...
  type SubscriptionCallbackMutationPatchObject (line 107) | interface SubscriptionCallbackMutationPatchObject<
  type SubscriptionCallbackMutationPatchFunction (line 124) | interface SubscriptionCallbackMutationPatchFunction extends _Subscriptio...
  type SubscriptionCallbackMutation (line 138) | type SubscriptionCallbackMutation<S> =
  type SubscriptionCallback (line 146) | type SubscriptionCallback<S> = (
  type _StoreOnActionListenerContext (line 165) | interface _StoreOnActionListenerContext<
  type StoreOnActionListenerContext (line 208) | type StoreOnActionListenerContext<
  type StoreOnActionListener (line 224) | type StoreOnActionListener<
  type StoreProperties (line 242) | interface StoreProperties<Id extends string> {
  type _StoreWithState (line 307) | interface _StoreWithState<
  type _Method (line 414) | type _Method = (...args: any[]) => any
  type _StoreWithActions (line 426) | type _StoreWithActions<A> = {
  type _StoreWithGetters (line 436) | type _StoreWithGetters<G> = _StoreWithGetters_Readonly<G> &
  type _StoreWithGetters_Readonly (line 442) | type _StoreWithGetters_Readonly<G> = {
  type _StoreWithGetters_Writable (line 453) | type _StoreWithGetters_Writable<G> = {
  type Store (line 464) | type Store<
  type StoreGeneric (line 483) | type StoreGeneric = Store<
  type StoreDefinition (line 493) | interface StoreDefinition<
  type PiniaCustomProperties (line 523) | interface PiniaCustomProperties<
  type PiniaCustomStateProperties (line 533) | interface PiniaCustomStateProperties<S extends StateTree = StateTree> {}
  type _GettersTree (line 539) | type _GettersTree<S extends StateTree> = Record<
  type _ActionsTree (line 549) | type _ActionsTree = Record<string, _Method>
  type _ExtractStateFromSetupStore_Keys (line 555) | type _ExtractStateFromSetupStore_Keys<SS> = keyof {
  type _ExtractActionsFromSetupStore_Keys (line 563) | type _ExtractActionsFromSetupStore_Keys<SS> = keyof {
  type _ExtractGettersFromSetupStore_Keys (line 571) | type _ExtractGettersFromSetupStore_Keys<SS> = keyof {
  type _UnwrapAll (line 579) | type _UnwrapAll<SS> = { [K in keyof SS]: UnwrapRef<SS[K]> }
  type _ExtractStateFromSetupStore (line 584) | type _ExtractStateFromSetupStore<SS> = SS extends undefined | void
  type _ExtractActionsFromSetupStore (line 591) | type _ExtractActionsFromSetupStore<SS> = SS extends undefined | void
  type _ExtractGettersFromSetupStore (line 598) | type _ExtractGettersFromSetupStore<SS> = SS extends undefined | void
  type DefineStoreOptionsBase (line 607) | interface DefineStoreOptionsBase<S extends StateTree, Store> {}
  type DefineStoreOptions (line 613) | interface DefineStoreOptions<
  type DefineSetupStoreOptions (line 680) | interface DefineSetupStoreOptions<
  type DefineStoreOptionsInPlugin (line 698) | interface DefineStoreOptionsInPlugin<
  type _Empty (line 715) | interface _Empty {}
  type _Simplify (line 721) | type _Simplify<T> = _Empty extends T

FILE: packages/pinia/test-dts/actions.test-d.ts
  method useOtherAction (line 6) | useOtherAction() {
  method useExternalFunction (line 9) | useExternalFunction() {
  method returnStuff (line 12) | returnStuff() {
  method factorial (line 17) | factorial(n?: number): number {
  method crossA (line 21) | crossA(): 'A' | 'B' {
  method crossB (line 28) | crossB() {
  function outer (line 38) | function outer(store: ReturnType<typeof useStore>): number {

FILE: packages/pinia/test-dts/customizations.test-d.ts
  type MapStoresCustomization (line 13) | interface MapStoresCustomization {
  type PiniaCustomProperties (line 17) | interface PiniaCustomProperties<Id, S, G, A> {
  type PiniaCustomStateProperties (line 25) | interface PiniaCustomStateProperties<S> {
  type DefineStoreOptionsBase (line 30) | interface DefineStoreOptionsBase<S, Store> {
  method one (line 63) | one() {}
  method two (line 64) | two() {
  method three (line 69) | three() {
  method two (line 75) | two(state): boolean {
  function one (line 97) | function one() {}
  function two (line 98) | function two() {}
  function three (line 99) | function three() {}
  type Procedure (line 111) | type Procedure = (...args: any[]) => any
  function debounce (line 113) | function debounce<F extends Procedure>(fn: F, time: number = 200) {
  function plusOne (line 231) | function plusOne() {
  method plusOne (line 262) | plusOne() {

FILE: packages/pinia/test-dts/index.d.ts
  type TypeEqual (line 4) | type TypeEqual<Target, Value> =

FILE: packages/pinia/test-dts/mapHelpers.test-d.ts
  method toggleA (line 18) | toggleA() {
  method setToggle (line 22) | setToggle(a: 'on' | 'off') {
  function toggleA (line 35) | function toggleA() {
  function setToggle (line 38) | function setToggle(aVal: 'on' | 'off') {
  type MainStore (line 48) | type MainStore = ReturnType<typeof useOptionsStore>
  type DosStore (line 49) | type DosStore = ReturnType<typeof useStoreDos>
  type CounterStore (line 50) | type CounterStore = ReturnType<typeof useCounter>

FILE: packages/pinia/test-dts/onAction.test-d.ts
  method direct (line 8) | direct(name: string) {
  method patchObject (line 11) | patchObject(user: string) {
  method patchFn (line 14) | patchFn(name: string) {
  method asyncUpperName (line 19) | async asyncUpperName() {
  method upperName (line 22) | upperName() {
  method throws (line 25) | throws(e: any) {
  method rejects (line 28) | async rejects(e: any) {

FILE: packages/pinia/test-dts/state.test-d.ts
  method set (line 8) | set(val) {
  method other (line 33) | other(): undefined {
  method some (line 43) | some() {

FILE: packages/pinia/test-dts/store.test-d.ts
  method upperThis (line 17) | upperThis(): 'ON' | 'OFF' {
  method other (line 21) | other(): false {
  method doStuff (line 32) | doStuff() {
  method otherOne (line 38) | otherOne() {
  method a (line 54) | a() {
  method a (line 64) | a() {
  method a (line 74) | a() {
  type Model (line 81) | interface Model {
  function init (line 86) | function init<User extends Model>(name = 'settings') {
  method a (line 110) | a(): number {
  method a (line 126) | a(): number {
  function takeStore (line 206) | function takeStore<TStore extends StoreGeneric>(store: TStore): TStore['...

FILE: packages/pinia/test-dts/storeSetup.test-d.ts
  function increment (line 18) | function increment(amount = 1) {

FILE: packages/pinia/test-dts/typeHelpers.test-d.ts
  function increment (line 17) | function increment(amount = 1) {
  method increment (line 30) | increment(amount = 1) {
  function increment (line 77) | function increment() {

FILE: packages/playground/src/api/jokes.ts
  type Joke (line 9) | interface Joke {
  function getRandomJoke (line 16) | function getRandomJoke() {

FILE: packages/playground/src/api/nasa.ts
  constant API_KEY (line 9) | const API_KEY = import.meta.env.VITE_API_KEY_NASA || 'DEMO_KEY'
  type NASAPOD (line 15) | interface NASAPOD {
  function getNASAPOD (line 25) | function getNASAPOD(date: Date | string = new Date()) {

FILE: packages/playground/src/composables/useCachedRequest.ts
  function useCachedRequest (line 3) | function useCachedRequest<T, U>(

FILE: packages/playground/src/main.ts
  type PiniaCustomProperties (line 16) | interface PiniaCustomProperties {

FILE: packages/playground/src/stores/cart.ts
  method addItem (line 26) | addItem(name: string) {
  method removeItem (line 30) | removeItem(name: string) {
  method purchaseItems (line 35) | async purchaseItems() {

FILE: packages/playground/src/stores/counter.ts
  method increment (line 18) | increment(amount = 1) {
  method changeMe (line 26) | changeMe() {
  method fail (line 30) | async fail() {
  method decrementToZero (line 42) | async decrementToZero(interval: number = 300, usePatch = true) {

FILE: packages/playground/src/stores/counterSetup.ts
  function increment (line 19) | function increment(amount = 1) {
  function changeMe (line 27) | function changeMe() {
  function fail (line 31) | async function fail() {
  function decrementToZero (line 43) | async function decrementToZero(interval: number = 300) {

FILE: packages/playground/src/stores/jokes.ts
  method fetchJoke (line 12) | async fetchJoke() {
  function fetchJoke (line 32) | async function fetchJoke() {

FILE: packages/playground/src/stores/jokesUsePromised.ts
  method waitForJoke (line 24) | waitForJoke() {
  method fetchJoke (line 28) | fetchJoke() {
  function fetchJoke (line 56) | function fetchJoke() {

FILE: packages/playground/src/stores/nasa-pod.ts
  function fetchPOD (line 25) | function fetchPOD(date: string) {
  function incrementDay (line 41) | function incrementDay(date: string) {
  function decrementDay (line 49) | function decrementDay(date: string) {

FILE: packages/playground/src/stores/user.ts
  method login (line 12) | async login(user: string, password: string) {
  method logout (line 20) | logout() {
  function apiLogin (line 34) | function apiLogin(a: string, p: string) {

FILE: packages/playground/src/stores/wholeStore.ts
  type User (line 1) | interface User {
  type State (line 4) | type State<T extends string> = { type: T }
  type AuthStateLoggingIn (line 5) | type AuthStateLoggingIn = State<'loggingIn'>
  type AuthStateLoggedIn (line 6) | type AuthStateLoggedIn = State<'loggedIn'> & { user: User }
  type AuthStateError (line 7) | type AuthStateError = State<'error'> & { errorMsg: string }
  type AuhtStateLoggedOut (line 8) | type AuhtStateLoggedOut = State<'loggedOut'>
  type AuthState (line 10) | type AuthState =
  method login (line 28) | async login() {
  method logout (line 38) | logout() {

FILE: packages/playground/vite.config.ts
  function copyPiniaPlugin (line 26) | function copyPiniaPlugin(): Plugin {

FILE: packages/size-check/rollup.config.mjs
  function createConfig (line 23) | function createConfig(file) {

FILE: packages/size-check/scripts/check-size.mjs
  function checkFileSize (line 13) | async function checkFileSize(filePath) {
  function main (line 30) | async function main() {

FILE: packages/testing/src/initialState.spec.ts
  method increment (line 11) | increment(amount = 1) {
  method setup (line 18) | setup() {
  function factory (line 29) | function factory(options?: TestingOptions) {

FILE: packages/testing/src/mocked-store.spec.ts
  function mockedStore (line 7) | function mockedStore<TStoreDef extends () => unknown>(
  function increment (line 41) | function increment(amount = 1) {
  function decrement (line 44) | function decrement() {
  function setValue (line 47) | function setValue(newValue: number) {
  function $reset (line 50) | function $reset() {
  method setup (line 67) | setup() {
  function factory (line 78) | function factory(options?: TestingOptions) {

FILE: packages/testing/src/restoreGetters.spec.ts
  function tds (line 48) | function tds(_fn: Function) {}

FILE: packages/testing/src/restoreGetters.ts
  function restoreGetter (line 13) | function restoreGetter<G>(store: Store, getter: any): void {

FILE: packages/testing/src/testing.spec.ts
  method doublePlusOne (line 16) | doublePlusOne(): number {
  method increment (line 21) | increment(amount = 1) {
  method decrement (line 24) | decrement() {
  method setValue (line 27) | setValue(newValue: number) {
  function increment (line 41) | function increment(amount = 1) {
  function decrement (line 44) | function decrement() {
  function setValue (line 47) | function setValue(newValue: number) {
  function $reset (line 50) | function $reset() {
  type CounterStore (line 66) | type CounterStore =
  method setup (line 77) | setup() {
  function factory (line 88) | function factory(
  method setup (line 497) | setup() {

FILE: packages/testing/src/testing.ts
  type TestingOptions (line 17) | interface TestingOptions {
  type TestingPinia (line 81) | interface TestingPinia extends Pinia {
  function createTestingPinia (line 103) | function createTestingPinia({
  function mergeReactiveObjects (line 182) | function mergeReactiveObjects<T extends StateTree>(
  function isPlainObject (line 211) | function isPlainObject(
  function isComputed (line 223) | function isComputed<T>(
  function WritableComputed (line 229) | function WritableComputed({ store }: PiniaPluginContext) {
  function shouldStubAction (line 273) | function shouldStubAction(

FILE: scripts/release.mjs
  constant EXPECTED_BRANCH (line 50) | const EXPECTED_BRANCH = 'v3'
  constant MAIN_PKG_NAME (line 52) | const MAIN_PKG_NAME = 'pinia'
  constant IS_MAIN_PKG_ROOT (line 54) | const IS_MAIN_PKG_ROOT = false
  constant PKG_FOLDERS (line 56) | const PKG_FOLDERS = [
  constant FILES_TO_COMMIT (line 64) | const FILES_TO_COMMIT = [
  function main (line 89) | async function main() {
  function updateVersions (line 375) | async function updateVersions(packageList) {
  function updateDeps (line 395) | function updateDeps(pkg, depType, updatedPackages) {
  function publishPackage (line 422) | async function publishPackage(pkg) {
  function getLastTag (line 460) | async function getLastTag(pkgName) {
  function getChangedPackages (line 503) | async function getChangedPackages(...folders) {
Condensed preview — 292 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (931K chars).
[
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "chars": 1423,
    "preview": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project, we pledge to respect all people who cont"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 5670,
    "preview": "# Contributing\n\nContributions are welcome and will be fully credited!\n\nWe accept contributions via Pull Requests on [Git"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 2179,
    "preview": "name: \"\\U0001F41E Bug report\"\ndescription: Report an issue with Pinia\nbody:\n  - type: markdown\n    attributes:\n      val"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 736,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: 👨‍💻 Support\n    url: https://cal.com/posva/consultancy\n    about: G"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 1193,
    "preview": "name: \"\\U0001F680 New feature proposal\"\ndescription: Suggest an idea for Pinia\nlabels: ['feature request']\nbody:\n  - typ"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 97,
    "preview": "<!--\n\nIMPORTANT: use https://github.com/vuejs/pinia/issues/new or the issue will be closed.\n\n-->\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 113,
    "preview": "<!--\nPlease make sure to include a test! If this is closing an\nexisting issue, reference that issue as well.\n-->\n"
  },
  {
    "path": ".github/commit-convention.md",
    "chars": 2930,
    "preview": "## Git Commit Message Convention\n\n> This is adapted from [Angular's commit convention](https://github.com/conventional-c"
  },
  {
    "path": ".github/funding.yml",
    "chars": 50,
    "preview": "github: posva\ncustom: https://www.paypal.me/posva\n"
  },
  {
    "path": ".github/settings.yml",
    "chars": 474,
    "preview": "labels:\n  - name: bug\n    color: ee0701\n  - name: contribution welcome\n    color: 0e8a16\n  - name: discussion\n    color:"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 806,
    "preview": "name: ci\n\non:\n  push:\n    paths-ignore:\n      - 'packages/docs/**'\n      - 'packages/playground/**'\n  pull_request:\n    "
  },
  {
    "path": ".github/workflows/pkg.pr.new.yml",
    "chars": 829,
    "preview": "name: Publish Any Commit\n\non:\n  pull_request:\n    branches: [v2, v3]\n    paths-ignore:\n      - 'packages/docs/**'\n      "
  },
  {
    "path": ".github/workflows/release-tag.yml",
    "chars": 612,
    "preview": "on:\n  push:\n    tags:\n      - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10\n\nname: Create Release\n\njobs:\n  bui"
  },
  {
    "path": ".github/workflows/update-sponsors.yml",
    "chars": 2610,
    "preview": "name: Update sponsors\n\non:\n  schedule:\n    - cron: '0 0 * * *'\n  workflow_dispatch: {}\n\npermissions:\n  contents: write\n\n"
  },
  {
    "path": ".gitignore",
    "chars": 264,
    "preview": "node_modules\ncoverage\nnpm-debug.log\nyarn-error.log\n.nyc_output\ncoverage.lcov\ndist\n.DS_Store\ntemp\ntest-dts/tsconfig.tsbui"
  },
  {
    "path": ".npmrc",
    "chars": 53,
    "preview": "shamefully-hoist=true\nstrict-peer-dependencies=false\n"
  },
  {
    "path": ".oxfmtrc.json",
    "chars": 273,
    "preview": "{\n  \"$schema\": \"./node_modules/oxfmt/configuration_schema.json\",\n  \"semi\": false,\n  \"trailingComma\": \"es5\",\n  \"singleQuo"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 460,
    "preview": "{\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"name\": \"vscode-jest-tests\",\n      \"request\": \"launch\",\n      "
  },
  {
    "path": ".vscode/settings.json",
    "chars": 274,
    "preview": "{\n  \"[javascript]\": {\n    \"editor.formatOnSave\": true\n  },\n  \"[typescript]\": {\n    \"editor.formatOnSave\": true\n  },\n  \"t"
  },
  {
    "path": "LICENSE",
    "chars": 1100,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2019-present Eduardo San Martin Morote\n\nPermission is hereby granted, free of charg"
  },
  {
    "path": "README.md",
    "chars": 7451,
    "preview": "<p align=\"center\">\n  <a href=\"https://pinia.vuejs.org\" target=\"_blank\" rel=\"noopener noreferrer\">\n    <img width=\"180\" s"
  },
  {
    "path": "SECURITY.md",
    "chars": 1052,
    "preview": "# Security Policy\n\n## Supported Versions\n\nThis is the list of versions of Pinia which are\ncurrently being supported with"
  },
  {
    "path": "codecov.yml",
    "chars": 85,
    "preview": "coverage:\n  status:\n    patch: off\n    project:\n      default:\n        threshold: 2%\n"
  },
  {
    "path": "netlify.toml",
    "chars": 117,
    "preview": "[build]\ncommand = \"pnpm run docs:build\"\nignore = \"./scripts/docs-check.sh\"\npublish = \"packages/docs/.vitepress/dist\"\n"
  },
  {
    "path": "package.json",
    "chars": 2843,
    "preview": "{\n  \"name\": \"@pinia/root\",\n  \"private\": true,\n  \"workspaces\": [\n    \"packages/*\"\n  ],\n  \"type\": \"module\",\n  \"scripts\": {"
  },
  {
    "path": "packages/docs/.gitignore",
    "chars": 17,
    "preview": ".vitepress/cache\n"
  },
  {
    "path": "packages/docs/.vitepress/config/en.ts",
    "chars": 4645,
    "preview": "import type { DefaultTheme, LocaleSpecificConfig } from 'vitepress'\nimport typedocSidebar from '../../api/typedoc-sideba"
  },
  {
    "path": "packages/docs/.vitepress/config/index.ts",
    "chars": 889,
    "preview": "import { defineConfig } from 'vitepress'\nimport { enConfig } from './en'\nimport { sharedConfig } from './shared'\nimport "
  },
  {
    "path": "packages/docs/.vitepress/config/shared.ts",
    "chars": 3162,
    "preview": "import { defineConfig, HeadConfig } from 'vitepress'\nimport { zhSearch } from './zh'\n\nexport const META_IMAGE = 'https:/"
  },
  {
    "path": "packages/docs/.vitepress/config/zh.ts",
    "chars": 5918,
    "preview": "import type { DefaultTheme, LocaleSpecificConfig } from 'vitepress'\n\nexport const META_URL = 'https://pinia.vuejs.org'\ne"
  },
  {
    "path": "packages/docs/.vitepress/theme/components/AsideSponsors.vue",
    "chars": 3630,
    "preview": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { VPDocAsideSponsors } from 'vitepress/theme'\nimport spon"
  },
  {
    "path": "packages/docs/.vitepress/theme/components/HomeSponsors.vue",
    "chars": 1168,
    "preview": "<script setup lang=\"ts\">\nimport HomeSponsorsGroup from './HomeSponsorsGroup.vue'\nimport sponsors from './sponsors.json'\n"
  },
  {
    "path": "packages/docs/.vitepress/theme/components/HomeSponsorsGroup.vue",
    "chars": 1793,
    "preview": "<template>\n  <h3>{{ name }} Sponsors</h3>\n\n  <p>\n    <a\n      v-for=\"sponsor in list\"\n      :key=\"sponsor.href\"\n      :h"
  },
  {
    "path": "packages/docs/.vitepress/theme/components/MadVueBanner.vue",
    "chars": 8527,
    "preview": "<script setup lang=\"ts\">\nimport { ref, onMounted } from 'vue'\n\nconst isVisible = ref(false)\nconst nameStorage = 'MADVUE-"
  },
  {
    "path": "packages/docs/.vitepress/theme/components/MasteringPiniaLink.vue",
    "chars": 4058,
    "preview": "<script setup lang=\"ts\">\nimport { useData } from 'vitepress'\nimport { computed, ref } from 'vue'\n\n// TODO: split into 2 "
  },
  {
    "path": "packages/docs/.vitepress/theme/components/PiniaLogo.vue",
    "chars": 13008,
    "preview": "<template>\n  <svg\n    id=\"pinia-logo\"\n    viewBox=\"0 0 408 520\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n "
  },
  {
    "path": "packages/docs/.vitepress/theme/components/RuleKitLink.vue",
    "chars": 6412,
    "preview": "<script setup lang=\"ts\">\nimport { useData } from 'vitepress'\n\nconst { site } = useData()\n\nconst translations = {\n  'en-U"
  },
  {
    "path": "packages/docs/.vitepress/theme/components/VueMasteryBanner.vue",
    "chars": 5921,
    "preview": "<template>\n  <div class=\"vuemastery-banner-wrapper\" role=\"banner\" v-if=\"isVisible\">\n    <div\n      :class=\"{ 'show-flash"
  },
  {
    "path": "packages/docs/.vitepress/theme/components/VueMasteryHomeLink.vue",
    "chars": 1446,
    "preview": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div class=\"container\">\n    <div class=\"inside\">\n      <a\n        href=\""
  },
  {
    "path": "packages/docs/.vitepress/theme/components/VueMasteryLogoLink.vue",
    "chars": 1711,
    "preview": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  for: string\n}>()\n\nconst links = {\n  'pinia-cheat-sheet':\n    'htt"
  },
  {
    "path": "packages/docs/.vitepress/theme/components/VueSchoolLink.vue",
    "chars": 1251,
    "preview": "<template>\n  <div class=\"vueschool\">\n    <a\n      :href=\"`${href}?friend=vuerouter`\"\n      target=\"_blank\"\n      rel=\"sp"
  },
  {
    "path": "packages/docs/.vitepress/theme/components/VuejsdeConfBanner.vue",
    "chars": 2750,
    "preview": "<template>\n  <div class=\"banner banner-vuejsconf\" v-if=\"isVisible\">\n    <a\n      href=\"https://conf.vuejs.de/tickets/?vo"
  },
  {
    "path": "packages/docs/.vitepress/theme/components/sponsors.json",
    "chars": 2063,
    "preview": "{\n  \"platinum\": [],\n  \"gold\": [\n    {\n      \"alt\": \"CodeRabbit\",\n      \"href\": \"https://www.coderabbit.ai/?utm_source=vu"
  },
  {
    "path": "packages/docs/.vitepress/theme/index.ts",
    "chars": 1572,
    "preview": "import { h } from 'vue'\nimport { type Theme } from 'vitepress'\nimport DefaultTheme from 'vitepress/theme'\nimport AsideSp"
  },
  {
    "path": "packages/docs/.vitepress/theme/styles/home-links.css",
    "chars": 2970,
    "preview": "/* Style to get the cheat sheet link in the home page */\n\na.cta {\n  text-align: center;\n  border-radius: 8px;\n}\n\na.cta:h"
  },
  {
    "path": "packages/docs/.vitepress/theme/styles/playground-links.css",
    "chars": 398,
    "preview": ".vp-doc a[href^='https://play.pinia.vuejs.org']:before {\n  content: '▶';\n  width: 20px;\n  height: 20px;\n  display: inlin"
  },
  {
    "path": "packages/docs/.vitepress/theme/styles/vars.css",
    "chars": 5244,
    "preview": "/**\n * Colors\n * -------------------------------------------------------------------------- */\n\n:root {\n  --c-yellow-1: "
  },
  {
    "path": "packages/docs/.vitepress/translation-status.json",
    "chars": 66,
    "preview": "{\n  \"zh\": {\n    \"hash\": \"02a476d\",\n    \"date\": \"2024-05-20\"\n  }\n}\n"
  },
  {
    "path": "packages/docs/cookbook/composables.md",
    "chars": 4381,
    "preview": "# Dealing with Composables\n\n[Composables](https://vuejs.org/guide/reusability/composables.html#composables) are function"
  },
  {
    "path": "packages/docs/cookbook/composing-stores.md",
    "chars": 3522,
    "preview": "# Composing Stores\n\n<RuleKitLink />\n\nComposing stores is about having stores that use each other, and this is supported "
  },
  {
    "path": "packages/docs/cookbook/hot-module-replacement.md",
    "chars": 1125,
    "preview": "# HMR (Hot Module Replacement)\n\n<RuleKitLink />\n\nPinia supports Hot Module replacement so you can edit your stores and i"
  },
  {
    "path": "packages/docs/cookbook/index.md",
    "chars": 679,
    "preview": "# Cookbook\n\n<RuleKitLink />\n\n- [Migrating from Vuex ≤4](./migration-vuex.md): A migration guide for converting Vuex ≤4 p"
  },
  {
    "path": "packages/docs/cookbook/migration-0-0-7.md",
    "chars": 3269,
    "preview": "# Migrating from 0.0.7\n\nThe versions after `0.0.7`: `0.1.0`, and `0.2.0`, came with a few big breaking changes. This gui"
  },
  {
    "path": "packages/docs/cookbook/migration-v1-v2.md",
    "chars": 5815,
    "preview": "# Migrating from 0.x (v1) to v2\n\n<RuleKitLink />\n\nStarting at version `2.0.0-rc.4`, pinia supports both Vue 2 and Vue 3!"
  },
  {
    "path": "packages/docs/cookbook/migration-v2-v3.md",
    "chars": 1145,
    "preview": "# Migrating from v2 to v3\n\n<RuleKitLink />\n\nPinia v3 is a _boring_ major release with no new features. It drops deprecat"
  },
  {
    "path": "packages/docs/cookbook/migration-vuex.md",
    "chars": 11241,
    "preview": "# Migrating from Vuex ≤4\n\nAlthough the structure of Vuex and Pinia stores is different, a lot of the logic can be reused"
  },
  {
    "path": "packages/docs/cookbook/options-api.md",
    "chars": 2845,
    "preview": "# Usage without `setup()`\n\nPinia can be used even if you are not using the composition API (if you are using Vue <2.7, y"
  },
  {
    "path": "packages/docs/cookbook/testing.md",
    "chars": 10758,
    "preview": "# Testing stores\n\n<MasteringPiniaLink\n  href=\"https://play.gumlet.io/embed/65f9a9c10bfab01f414c25dc\"\n  title=\"Watch a fr"
  },
  {
    "path": "packages/docs/cookbook/vscode-snippets.md",
    "chars": 1634,
    "preview": "# VS Code Snippets\n\n<RuleKitLink />\n\nThese are some snippets that I use in VS Code to make my life easier.\n\nManage user "
  },
  {
    "path": "packages/docs/core-concepts/actions.md",
    "chars": 6749,
    "preview": "# Actions\n\n<!-- <VueSchoolLink\n  href=\"https://vueschool.io/lessons/synchronous-and-asynchronous-actions-in-pinia\"\n  tit"
  },
  {
    "path": "packages/docs/core-concepts/getters.md",
    "chars": 6889,
    "preview": "# Getters\n\n<!-- <VueSchoolLink\n  href=\"https://vueschool.io/lessons/getters-in-pinia\"\n  title=\"Learn all about getters i"
  },
  {
    "path": "packages/docs/core-concepts/index.md",
    "chars": 7305,
    "preview": "# Defining a Store\n\n<!-- <VueSchoolLink\n  href=\"https://vueschool.io/lessons/define-your-first-pinia-store\"\n  title=\"Lea"
  },
  {
    "path": "packages/docs/core-concepts/outside-component-usage.md",
    "chars": 2594,
    "preview": "# Using a store outside of a component\n\n<MasteringPiniaLink\n  href=\"https://play.gumlet.io/embed/651ed1ec4c2f339c6860fd0"
  },
  {
    "path": "packages/docs/core-concepts/plugins.md",
    "chars": 12019,
    "preview": "# Plugins\n\n<MasteringPiniaLink\n  href=\"https://masteringpinia.com/lessons/What-is-a-pinia-plugin\"\n  title=\"Learn all abo"
  },
  {
    "path": "packages/docs/core-concepts/state.md",
    "chars": 9343,
    "preview": "# State\n\n<!-- <VueSchoolLink\n  href=\"https://vueschool.io/lessons/access-state-from-a-pinia-store\"\n  title=\"Learn all ab"
  },
  {
    "path": "packages/docs/getting-started.md",
    "chars": 2526,
    "preview": "# Getting Started\n\n## Installation\n\n<VueMasteryLogoLink for=\"pinia-cheat-sheet\">\n</VueMasteryLogoLink>\n\nInstall `pinia` "
  },
  {
    "path": "packages/docs/index.md",
    "chars": 1888,
    "preview": "---\nlayout: home\n\ntitle: Pinia\ntitleTemplate: The intuitive store for Vue.js\n\nhero:\n  name: Pinia\n  text: The intuitive "
  },
  {
    "path": "packages/docs/introduction.md",
    "chars": 13515,
    "preview": "# Introduction\n\n<!-- <VueSchoolLink\n  href=\"https://vueschool.io/lessons/introduction-to-pinia\"\n  title=\"Get started wit"
  },
  {
    "path": "packages/docs/package.json",
    "chars": 740,
    "preview": "{\n  \"name\": \"@pinia/docs\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"predocs\": \"n"
  },
  {
    "path": "packages/docs/run-typedoc.mjs",
    "chars": 799,
    "preview": "import path from 'node:path'\nimport { createTypeDocApp } from './typedoc-markdown.mjs'\n\nconst __dirname = path.dirname(n"
  },
  {
    "path": "packages/docs/ssr/index.md",
    "chars": 3679,
    "preview": "# Server Side Rendering (SSR)\n\n<MasteringPiniaLink\n  href=\"https://masteringpinia.com/lessons/ssr-friendly-state\"\n  titl"
  },
  {
    "path": "packages/docs/ssr/nuxt.md",
    "chars": 3419,
    "preview": "# Nuxt\n\n<MasteringPiniaLink\n  href=\"https://masteringpinia.com/lessons/ssr-friendly-state\"\n  title=\"Learn about SSR best"
  },
  {
    "path": "packages/docs/typedoc-markdown.mjs",
    "chars": 3005,
    "preview": "// @ts-check\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { Application, TSConfigReader, PageEv"
  },
  {
    "path": "packages/docs/typedoc.tsconfig.json",
    "chars": 1089,
    "preview": "{\n  \"include\": [\"../pinia/src/global.d.ts\", \"../*/src/**/*.ts\"],\n  \"exclude\": [\n    \"../test-vue-2\",\n    \"../pinia/__tes"
  },
  {
    "path": "packages/docs/vite-typedoc-plugin.ts",
    "chars": 428,
    "preview": "import { Plugin } from 'vite'\nimport _fs from 'fs'\nimport { TypeDocOptions } from 'typedoc'\nimport { createTypeDocApp } "
  },
  {
    "path": "packages/docs/vite.config.ts",
    "chars": 1168,
    "preview": "import { defineConfig, type Plugin } from 'vite'\nimport _fs from 'fs'\nimport path from 'path'\n// import TypeDocPlugin fr"
  },
  {
    "path": "packages/docs/zh/api/enums/pinia.MutationType.md",
    "chars": 791,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / Mutatio"
  },
  {
    "path": "packages/docs/zh/api/index.md",
    "chars": 173,
    "preview": "API 文档\n\n# API 文档 %{#api-documentation}%\n\n## 模块 %{#modules}%\n\n- [@pinia/nuxt](modules/pinia_nuxt.md)\n- [@pinia/testing](m"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.DefineSetupStoreOptions.md",
    "chars": 1190,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / DefineS"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.DefineStoreOptions.md",
    "chars": 3165,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / DefineS"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.DefineStoreOptionsBase.md",
    "chars": 850,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / DefineS"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.DefineStoreOptionsInPlugin.md",
    "chars": 2713,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / DefineS"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.MapStoresCustomization.md",
    "chars": 336,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / MapStor"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.Pinia.md",
    "chars": 1109,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / Pinia\n\n"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.PiniaCustomProperties.md",
    "chars": 1457,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / PiniaCu"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.PiniaCustomStateProperties.md",
    "chars": 682,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / PiniaCu"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.PiniaPlugin.md",
    "chars": 2030,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / PiniaPl"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.PiniaPluginContext.md",
    "chars": 1427,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / PiniaPl"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.StoreDefinition.md",
    "chars": 1693,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / StoreDe"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.StoreProperties.md",
    "chars": 688,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / StorePr"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.SubscriptionCallbackMutationDirect.md",
    "chars": 1144,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / Subscri"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.SubscriptionCallbackMutationPatchFunction.md",
    "chars": 1144,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / Subscri"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia.SubscriptionCallbackMutationPatchObject.md",
    "chars": 1314,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / Subscri"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia._StoreOnActionListenerContext.md",
    "chars": 2419,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / \\_Store"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia._StoreWithState.md",
    "chars": 5903,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / \\_Store"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia._SubscriptionCallbackMutationBase.md",
    "chars": 869,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [pinia](../modules/pinia.md) / \\_Subsc"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia_nuxt.ModuleOptions.md",
    "chars": 744,
    "preview": "---\neditLink: false\n---\n\n[API 文档](../index.md) / [@pinia/nuxt](../modules/pinia_nuxt.md) / ModuleOptions\n\n# 接口:ModuleOpt"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia_testing.TestingOptions.md",
    "chars": 1790,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [@pinia/testing](../modules/pinia_test"
  },
  {
    "path": "packages/docs/zh/api/interfaces/pinia_testing.TestingPinia.md",
    "chars": 1383,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / [@pinia/testing](../modules/pinia_test"
  },
  {
    "path": "packages/docs/zh/api/modules/pinia.md",
    "chars": 42117,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / pinia\n\n# 模块:pinia %{#module-pinia}%\n\n#"
  },
  {
    "path": "packages/docs/zh/api/modules/pinia_nuxt.md",
    "chars": 808,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / @pinia/nuxt\n\n# 模块: @pinia/nuxt %{#modu"
  },
  {
    "path": "packages/docs/zh/api/modules/pinia_testing.md",
    "chars": 1084,
    "preview": "---\nsidebar: 'auto'\neditLinks: false\nsidebarDepth: 3\n---\n\n[API 文档](../index.md) / @pinia/testing\n\n# 模块:@pinia/testing %{"
  },
  {
    "path": "packages/docs/zh/cookbook/composables.md",
    "chars": 3393,
    "preview": "# 处理组合式函数 %{#dealing-with-composables}%\n\n<RuleKitLink />\n\n[组合式函数](https://cn.vuejs.org/guide/reusability/composables.htm"
  },
  {
    "path": "packages/docs/zh/cookbook/composing-stores.md",
    "chars": 2215,
    "preview": "# 组合式 Store %{#composing-stores}%\n\n<RuleKitLink />\n\n组合式 store 是可以相互使用,Pinia 当然也支持它。但有一个规则需要遵循:\n\n如果**两个或更多的 store 相互使用**,"
  },
  {
    "path": "packages/docs/zh/cookbook/hot-module-replacement.md",
    "chars": 791,
    "preview": "# HMR (Hot Module Replacement) %{#hmr-hot-module-replacement}%\n\n<RuleKitLink />\n\nPinia 支持热更新,所以你可以编辑你的 store,并直接在你的应用中与它"
  },
  {
    "path": "packages/docs/zh/cookbook/index.md",
    "chars": 427,
    "preview": "# 手册 %{#cookbook}%\n\n<RuleKitLink />\n\n- [从 Vuex ≤4 迁移](./migration-vuex.md)。用于转换 Vuex ≤4 项目的迁移指南。\n- [HMR](./hot-module-re"
  },
  {
    "path": "packages/docs/zh/cookbook/migration-0-0-7.md",
    "chars": 3393,
    "preview": "# Migrating from 0.0.7 %{#migrating-from-0-0-7}%\n\nThe versions after `0.0.7`: `0.1.0`, and `0.2.0`, came with a few big "
  },
  {
    "path": "packages/docs/zh/cookbook/migration-v1-v2.md",
    "chars": 4456,
    "preview": "# 从 0.x (v1) 迁移至 v2 %{#migrating-from-0-x-v1-to-v2}%\n\n<RuleKitLink />\n\n从 `2.0.0-rc.4` 版本开始,pinia 同时支持 Vue 2 和 Vue 3!这意味着"
  },
  {
    "path": "packages/docs/zh/cookbook/migration-vuex.md",
    "chars": 7992,
    "preview": "# 从 Vuex ≤4 迁移 %{#migrating-from-vuex-≤4}%\n\n<RuleKitLink />\n\n虽然 Vuex 和 Pinia store 的结构不同,但很多逻辑都可以复用。本指南的作用是帮助你完成迁移,并指出一些"
  },
  {
    "path": "packages/docs/zh/cookbook/options-api.md",
    "chars": 2060,
    "preview": "# 不使用 `setup()` 的用法 %{#usage-without-setup}%\n\n<RuleKitLink />\n\n即使你没有使用组合式 API,也可以使用 Pinia(如果你使用 Vue 2,你仍然需要安装 `@vue/comp"
  },
  {
    "path": "packages/docs/zh/cookbook/testing.md",
    "chars": 8801,
    "preview": "# store 测试 %{#testing-stores}%\n\n<RuleKitLink />\n\n<MasteringPiniaLink\n  href=\"https://play.gumlet.io/embed/65f9a9c10bfab0"
  },
  {
    "path": "packages/docs/zh/cookbook/vscode-snippets.md",
    "chars": 1553,
    "preview": "# VS Code 代码片段\n\n<RuleKitLink />\n\n有一些代码片段可以让你在 VS Code 中更轻松地使用 Pinia。\n\n通过 <kbd>⇧</kbd> <kbd>⌘</kbd> <kbd>P</kbd> / <kbd>⇧"
  },
  {
    "path": "packages/docs/zh/core-concepts/actions.md",
    "chars": 5215,
    "preview": "# Action %{#actions}%\n\n<RuleKitLink />\n\n<!-- <VueSchoolLink\n  href=\"https://vueschool.io/lessons/synchronous-and-asynchr"
  },
  {
    "path": "packages/docs/zh/core-concepts/getters.md",
    "chars": 5484,
    "preview": "# Getter %{#getters}%\n\n<RuleKitLink />\n\n<!-- <VueSchoolLink\n  href=\"https://vueschool.io/lessons/getters-in-pinia\"\n  tit"
  },
  {
    "path": "packages/docs/zh/core-concepts/index.md",
    "chars": 5069,
    "preview": "# 定义 Store %{#defining-a-store}%\n\n<RuleKitLink />\n\n<!-- <VueSchoolLink\n  href=\"https://vueschool.io/lessons/define-your-"
  },
  {
    "path": "packages/docs/zh/core-concepts/outside-component-usage.md",
    "chars": 1697,
    "preview": "# 在组件外使用 store %{#using-a-store-outside-of-a-component}%\n\n<RuleKitLink />\n\n<MasteringPiniaLink\n  href=\"https://play.guml"
  },
  {
    "path": "packages/docs/zh/core-concepts/plugins.md",
    "chars": 9908,
    "preview": "# 插件 %{#plugins}%\n\n<RuleKitLink />\n\n<MasteringPiniaLink\n  href=\"https://masteringpinia.com/lessons/What-is-a-pinia-plugi"
  },
  {
    "path": "packages/docs/zh/core-concepts/state.md",
    "chars": 6626,
    "preview": "# State %{#state}%\n\n<RuleKitLink />\n\n<!-- <VueSchoolLink\n  href=\"https://vueschool.io/lessons/access-state-from-a-pinia-"
  },
  {
    "path": "packages/docs/zh/getting-started.md",
    "chars": 1893,
    "preview": "# 开始\n\n<RuleKitLink />\n\n## 安装 %{#installation}%\n\n<VueMasteryLogoLink for=\"pinia-cheat-sheet\">\n</VueMasteryLogoLink>\n\n用你喜欢"
  },
  {
    "path": "packages/docs/zh/index.md",
    "chars": 1463,
    "preview": "---\nlayout: home\n\ntitle: Pinia\ntitleTemplate: The intuitive store for Vue.js\n\nhero:\n  name: Pinia\n  text: \"符合直觉的 \\nVue.j"
  },
  {
    "path": "packages/docs/zh/introduction.md",
    "chars": 10413,
    "preview": "# 简介 %{#introduction}%\n\n<!-- <VueSchoolLink\n  href=\"https://vueschool.io/lessons/introduction-to-pinia\"\n  title=\"Get sta"
  },
  {
    "path": "packages/docs/zh/ssr/index.md",
    "chars": 2750,
    "preview": "# 服务端渲染 (SSR) %{#server-side-rendering-ssr}%\n\n<RuleKitLink />\n\n<MasteringPiniaLink\n  href=\"https://masteringpinia.com/le"
  },
  {
    "path": "packages/docs/zh/ssr/nuxt.md",
    "chars": 3015,
    "preview": "# Nuxt %{#nuxt}%\n\n<MasteringPiniaLink\n  href=\"https://masteringpinia.com/lessons/ssr-friendly-state\"\n  title=\"Learn abou"
  },
  {
    "path": "packages/nuxt/.gitignore",
    "chars": 14,
    "preview": ".nuxt\n.output\n"
  },
  {
    "path": "packages/nuxt/CHANGELOG.md",
    "chars": 19970,
    "preview": "### [0.11.3](https://github.com/vuejs/pinia/compare/@pinia/nuxt@0.11.2...@pinia/nuxt@0.11.3) (2025-11-05)\n\nThis version "
  },
  {
    "path": "packages/nuxt/README.md",
    "chars": 1306,
    "preview": "# `@pinia/nuxt`\n\n> Nuxt module for Pinia\n\n## Automatic Installation\n\nUse `nuxi` to automatically add this module to your"
  },
  {
    "path": "packages/nuxt/package.json",
    "chars": 1535,
    "preview": "{\n  \"name\": \"@pinia/nuxt\",\n  \"version\": \"0.11.3\",\n  \"description\": \"Nuxt Module for pinia\",\n  \"keywords\": [\n    \"nuxt\",\n"
  },
  {
    "path": "packages/nuxt/playground/app.vue",
    "chars": 38,
    "preview": "<template>\n  <NuxtPage />\n</template>\n"
  },
  {
    "path": "packages/nuxt/playground/domain/one/stores/testStore.ts",
    "chars": 130,
    "preview": "export const useTestStore = defineStore('test', () => {\n  // console.log('I was defined within a store directory')\n  ret"
  },
  {
    "path": "packages/nuxt/playground/fake-main.js",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "packages/nuxt/playground/layers/layer-domain/nuxt.config.ts",
    "chars": 36,
    "preview": "export default defineNuxtConfig({})\n"
  },
  {
    "path": "packages/nuxt/playground/layers/layer-domain/stores/basic.ts",
    "chars": 110,
    "preview": "export const useBasicStore = defineStore('layer-basic', () => {\n  const count = ref(0)\n\n  return { count }\n})\n"
  },
  {
    "path": "packages/nuxt/playground/nuxt.config.ts",
    "chars": 628,
    "preview": "import { fileURLToPath } from 'node:url'\nimport { defineNuxtConfig } from 'nuxt/config'\nimport piniaModule from '../src/"
  },
  {
    "path": "packages/nuxt/playground/package.json",
    "chars": 103,
    "preview": "{\n  \"name\": \"pinia-nuxt-playground\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"main\": \"fake-main.js\"\n}\n"
  },
  {
    "path": "packages/nuxt/playground/pages/index.vue",
    "chars": 683,
    "preview": "<script lang=\"ts\" setup>\n// import {useCounter }from '~/stores/counter'\n\nconst counter = useCounter()\n\nuseTestStore() //"
  },
  {
    "path": "packages/nuxt/playground/pages/skip-hydrate.vue",
    "chars": 372,
    "preview": "<script lang=\"ts\" setup>\nconst store = useWithSkipHydrateStore()\nconst skipHydrateState = computed(() => {\n  return stor"
  },
  {
    "path": "packages/nuxt/playground/stores/counter.ts",
    "chars": 438,
    "preview": "const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n\nexport const useCounter = defineStore('"
  },
  {
    "path": "packages/nuxt/playground/stores/nested/some-store.ts",
    "chars": 148,
    "preview": "export const useSomeStoreStore = defineStore('some-store', () => {\n  // console.log('I was defined within a nested store"
  },
  {
    "path": "packages/nuxt/playground/stores/with-skip-hydrate.ts",
    "chars": 247,
    "preview": "import { skipHydrate } from 'pinia'\n\nexport const useWithSkipHydrateStore = defineStore('with-skip-hydrate', () => {\n  c"
  },
  {
    "path": "packages/nuxt/playground/tsconfig.json",
    "chars": 41,
    "preview": "{\n  \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/nuxt/shims.d.ts",
    "chars": 82,
    "preview": "declare namespace NodeJS {\n  export interface Process {\n    server: boolean\n  }\n}\n"
  },
  {
    "path": "packages/nuxt/src/auto-hmr-plugin.ts",
    "chars": 1871,
    "preview": "import type { VariableDeclarator } from 'estree'\nimport type { Plugin } from 'vite'\n\nfunction getStoreDeclaration(nodes?"
  },
  {
    "path": "packages/nuxt/src/module.ts",
    "chars": 2756,
    "preview": "/**\n * @module @pinia/nuxt\n */\nimport {\n  defineNuxtModule,\n  addPlugin,\n  addImports,\n  createResolver,\n  addImportsDir"
  },
  {
    "path": "packages/nuxt/src/runtime/composables.ts",
    "chars": 151,
    "preview": "import { useNuxtApp } from '#app'\nimport type { Pinia } from 'pinia'\nexport * from 'pinia'\n\nexport const usePinia = () ="
  },
  {
    "path": "packages/nuxt/src/runtime/payload-plugin.ts",
    "chars": 574,
    "preview": "import {\n  definePayloadPlugin,\n  definePayloadReducer,\n  definePayloadReviver,\n} from '#imports'\nimport {} from 'nuxt/a"
  },
  {
    "path": "packages/nuxt/src/runtime/plugin.vue3.ts",
    "chars": 864,
    "preview": "import { createPinia, setActivePinia } from 'pinia'\nimport type { Pinia } from 'pinia'\nimport { defineNuxtPlugin, useNux"
  },
  {
    "path": "packages/nuxt/test/nuxt.spec.ts",
    "chars": 1126,
    "preview": "import { fileURLToPath } from 'node:url'\nimport { describe, it, expect } from 'vitest'\nimport { setup, $fetch } from '@n"
  },
  {
    "path": "packages/nuxt/tsconfig.json",
    "chars": 137,
    "preview": "{\n  \"extends\": \"./playground/.nuxt/tsconfig.json\",\n  \"include\": [\n    \"./shims.d.ts\",\n    // missing in the playground\n "
  },
  {
    "path": "packages/online-playground/README.md",
    "chars": 233,
    "preview": "# SFC Playground\n\nThis is continuously deployed at [https://play.vuejs.org](https://play.vuejs.org).\n\n## Run Locally in "
  },
  {
    "path": "packages/online-playground/deploy-check.sh",
    "chars": 227,
    "preview": "#!/bin/bash\n\n# check for netlify to skip deploy\n# needed because we cannot use && in netlify.toml\n\n# exit 0 will skip th"
  },
  {
    "path": "packages/online-playground/index.html",
    "chars": 873,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"I"
  },
  {
    "path": "packages/online-playground/netlify.toml",
    "chars": 81,
    "preview": "[build]\ncommand = \"pnpm run build\"\nignore = \"./deploy-check.sh\"\npublish = \"dist\"\n"
  },
  {
    "path": "packages/online-playground/package.json",
    "chars": 480,
    "preview": "{\n  \"name\": \"@pinia/playground\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": "
  },
  {
    "path": "packages/online-playground/shims-vue.d.ts",
    "chars": 142,
    "preview": "declare module '*.vue' {\n  import { DefineComponent } from 'vue'\n  const component: DefineComponent<{}, {}, any>\n  expor"
  },
  {
    "path": "packages/online-playground/src/App.vue",
    "chars": 5261,
    "preview": "<script setup lang=\"ts\">\nimport Header from './Header.vue'\nimport { Repl, ReplStore, SFCOptions, ReplProps } from '@vue/"
  },
  {
    "path": "packages/online-playground/src/Header.vue",
    "chars": 6253,
    "preview": "<script setup lang=\"ts\">\nimport { downloadProject } from './download/download'\nimport { inject, ref } from 'vue'\nimport "
  },
  {
    "path": "packages/online-playground/src/VersionSelect.vue",
    "chars": 2842,
    "preview": "<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\n\nconst expanded = ref(false)\nconst versions = ref<string[]"
  },
  {
    "path": "packages/online-playground/src/defaults.ts",
    "chars": 549,
    "preview": "import { InjectionKey, Ref } from 'vue'\n\nexport const AppVue = `\n<script setup lang=\"ts\">\nimport { useStore } from './co"
  },
  {
    "path": "packages/online-playground/src/download/download.ts",
    "chars": 1024,
    "preview": "import { saveAs } from 'file-saver'\n\nimport index from './template/index.html?raw'\nimport main from './template/main.js?"
  },
  {
    "path": "packages/online-playground/src/download/template/README.md",
    "chars": 208,
    "preview": "# Vite Vue Starter\n\nThis is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.o"
  },
  {
    "path": "packages/online-playground/src/download/template/index.html",
    "chars": 337,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <"
  },
  {
    "path": "packages/online-playground/src/download/template/main.js",
    "chars": 90,
    "preview": "import { createApp } from 'vue'\nimport App from './App.vue'\n\ncreateApp(App).mount('#app')\n"
  },
  {
    "path": "packages/online-playground/src/download/template/package.json",
    "chars": 307,
    "preview": "{\n  \"name\": \"vite-vue-starter\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \""
  },
  {
    "path": "packages/online-playground/src/download/template/vite.config.js",
    "chars": 157,
    "preview": "import { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vitejs.dev/config/\nexport default d"
  },
  {
    "path": "packages/online-playground/src/icons/Download.vue",
    "chars": 733,
    "preview": "<template>\n  <svg width=\"1.7em\" height=\"1.7em\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n    <g>\n      <rect x=\"4\" y=\"18\""
  },
  {
    "path": "packages/online-playground/src/icons/GitHub.vue",
    "chars": 750,
    "preview": "<template>\n  <svg width=\"1.7em\" height=\"1.7em\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n    <path\n      d=\"M10.9,2.1c-4."
  },
  {
    "path": "packages/online-playground/src/icons/Moon.vue",
    "chars": 558,
    "preview": "<template>\n  <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">\n    <path\n      fill=\"currentColor\"\n      d=\"M"
  },
  {
    "path": "packages/online-playground/src/icons/Share.vue",
    "chars": 437,
    "preview": "<template>\n  <svg width=\"1.4em\" height=\"1.4em\" viewBox=\"0 0 24 24\">\n    <g\n      fill=\"none\"\n      stroke=\"currentColor\""
  },
  {
    "path": "packages/online-playground/src/icons/Sun.vue",
    "chars": 1513,
    "preview": "<template>\n  <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">\n    <path\n      fill=\"currentColor\"\n      d=\"M"
  },
  {
    "path": "packages/online-playground/src/main.ts",
    "chars": 228,
    "preview": "import { createApp } from 'vue'\nimport App from './App.vue'\nimport '@vue/repl/style.css'\n\n// @ts-expect-error Custom win"
  },
  {
    "path": "packages/online-playground/src/pinia-dev-proxy.ts",
    "chars": 71,
    "preview": "// serve pinia to the iframe sandbox during dev.\nexport * from 'pinia'\n"
  },
  {
    "path": "packages/online-playground/src/vue-dev-proxy.ts",
    "chars": 67,
    "preview": "// serve vue to the iframe sandbox during dev.\nexport * from 'vue'\n"
  },
  {
    "path": "packages/online-playground/src/vue-server-renderer-dev-proxy.ts",
    "chars": 99,
    "preview": "// serve vue/server-renderer to the iframe sandbox during dev.\nexport * from 'vue/server-renderer'\n"
  },
  {
    "path": "packages/online-playground/tsconfig.json",
    "chars": 476,
    "preview": "{\n  \"files\": [\"src/**/*.ts\"],\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"mo"
  },
  {
    "path": "packages/online-playground/vite.config.ts",
    "chars": 1241,
    "preview": "import fs from 'node:fs'\nimport path from 'node:path'\nimport { defineConfig, Plugin } from 'vite'\nimport vue from '@vite"
  },
  {
    "path": "packages/pinia/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "packages/pinia/CHANGELOG.md",
    "chars": 56981,
    "preview": "## [3.0.4](https://github.com/vuejs/pinia/compare/v3.0.3...v3.0.4) (2025-11-05)\n\n### Features\n\n- **warn:** detect global"
  },
  {
    "path": "packages/pinia/README.md",
    "chars": 665,
    "preview": "<p align=\"center\">\n  <a href=\"https://pinia.vuejs.org\" target=\"_blank\" rel=\"noopener noreferrer\">\n    <img width=\"180\" s"
  },
  {
    "path": "packages/pinia/__tests__/actions.spec.ts",
    "chars": 4221,
    "preview": "import { describe, it, expect, vi } from 'vitest'\nimport { createPinia, defineStore, setActivePinia } from '../src'\n\ndes"
  },
  {
    "path": "packages/pinia/__tests__/combinedStores.spec.ts",
    "chars": 1416,
    "preview": "import { beforeEach, describe, it, expect } from 'vitest'\nimport { computed, ref } from 'vue'\nimport { createPinia, defi"
  },
  {
    "path": "packages/pinia/__tests__/devtools.spec.ts",
    "chars": 1060,
    "preview": "import { describe, it, expect } from 'vitest'\nimport { mount } from '@vue/test-utils'\nimport { createPinia, defineStore "
  },
  {
    "path": "packages/pinia/__tests__/getters.spec.ts",
    "chars": 4920,
    "preview": "import { beforeEach, describe, it, expect } from 'vitest'\nimport { ref, computed } from 'vue'\nimport { createPinia, defi"
  },
  {
    "path": "packages/pinia/__tests__/hmr.spec.ts",
    "chars": 12817,
    "preview": "import { beforeEach, describe, it, expect, vi } from 'vitest'\nimport { computed, reactive, ref, toRefs, watch } from 'vu"
  },
  {
    "path": "packages/pinia/__tests__/lifespan.spec.ts",
    "chars": 3893,
    "preview": "import { describe, it, expect, vi } from 'vitest'\nimport {\n  createPinia,\n  defineStore,\n  disposePinia,\n  getActivePini"
  },
  {
    "path": "packages/pinia/__tests__/mapHelpers.spec.ts",
    "chars": 6474,
    "preview": "import { describe, it, expect } from 'vitest'\nimport {\n  createPinia,\n  defineStore,\n  mapActions,\n  mapGetters,\n  mapSt"
  },
  {
    "path": "packages/pinia/__tests__/onAction.spec.ts",
    "chars": 6170,
    "preview": "import { beforeEach, describe, it, expect, vi } from 'vitest'\nimport { createPinia, defineStore, setActivePinia } from '"
  },
  {
    "path": "packages/pinia/__tests__/pinia/stores/cart.ts",
    "chars": 1731,
    "preview": "import { defineStore } from '../../../src'\nimport { useUserStore } from './user'\n\nexport const useCartStore = defineStor"
  },
  {
    "path": "packages/pinia/__tests__/pinia/stores/combined.ts",
    "chars": 506,
    "preview": "// @ts-nocheck TODO: implement it or do something different for combined stores\nimport { useUserStore } from './user'\nim"
  },
  {
    "path": "packages/pinia/__tests__/pinia/stores/user.ts",
    "chars": 1065,
    "preview": "import { defineStore } from '../../../src'\n\nfunction apiLogin(a: string, p: string) {\n  if (a === 'ed' && p === 'ed') re"
  },
  {
    "path": "packages/pinia/__tests__/rootState.spec.ts",
    "chars": 1234,
    "preview": "import { describe, it, expect } from 'vitest'\nimport { createPinia, defineStore } from '../src'\nimport { mockWarn } from"
  },
  {
    "path": "packages/pinia/__tests__/ssr.spec.ts",
    "chars": 6302,
    "preview": "/**\n * @vitest-environment node\n */\nimport { describe, it, expect } from 'vitest'\nimport {\n  createPinia,\n  defineStore,"
  },
  {
    "path": "packages/pinia/__tests__/state.spec.ts",
    "chars": 12270,
    "preview": "import { beforeEach, describe, it, expect, vi, Mock } from 'vitest'\nimport { createPinia, defineStore, setActivePinia, s"
  },
  {
    "path": "packages/pinia/__tests__/store.patch.spec.ts",
    "chars": 6026,
    "preview": "import { describe, it, expect } from 'vitest'\nimport { reactive, ref } from 'vue'\nimport { createPinia, defineStore, Pin"
  },
  {
    "path": "packages/pinia/__tests__/store.spec.ts",
    "chars": 8625,
    "preview": "import { beforeEach, describe, it, expect, vi } from 'vitest'\nimport { createPinia, defineStore, setActivePinia } from '"
  },
  {
    "path": "packages/pinia/__tests__/storePlugins.spec.ts",
    "chars": 7494,
    "preview": "import { describe, it, expect, vi } from 'vitest'\nimport { createPinia, defineStore } from '../src'\nimport { mount } fro"
  },
  {
    "path": "packages/pinia/__tests__/storeSetup.spec.ts",
    "chars": 5001,
    "preview": "import { beforeEach, describe, it, expect, vi } from 'vitest'\nimport { createPinia, defineStore, setActivePinia } from '"
  },
  {
    "path": "packages/pinia/__tests__/storeToRefs.spec.ts",
    "chars": 5041,
    "preview": "import { describe, beforeEach, it, expect, vi } from 'vitest'\nimport { computed, reactive, ref, ToRefs } from 'vue'\nimpo"
  },
  {
    "path": "packages/pinia/__tests__/subscriptions.spec.ts",
    "chars": 11242,
    "preview": "import { beforeEach, describe, it, expect, vi } from 'vitest'\nimport { createPinia, defineStore, MutationType, setActive"
  },
  {
    "path": "packages/pinia/__tests__/vitest-mock-warn.ts",
    "chars": 3776,
    "preview": "// https://github.com/posva/jest-mock-warn/blob/master/src/index.js\nimport type { MockInstance } from 'vitest'\nimport { "
  },
  {
    "path": "packages/pinia/__tests__/vitest-setup.ts",
    "chars": 127,
    "preview": "import { beforeEach } from 'vitest'\nimport { setActivePinia } from '../src'\n\nbeforeEach(() => {\n  setActivePinia(undefin"
  }
]

// ... and 92 more files (download for full content)

About this extraction

This page contains the full source code of the vuejs/pinia GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 292 files (855.7 KB), approximately 267.3k tokens, and a symbol index with 428 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!