├── packages ├── pinia │ ├── .gitignore │ ├── src │ │ ├── env.ts │ │ ├── devtools │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ ├── global.d.ts │ │ ├── subscriptions.ts │ │ ├── globalExtensions.ts │ │ └── index.ts │ ├── __tests__ │ │ ├── vitest-setup.ts │ │ ├── pinia │ │ │ └── stores │ │ │ │ ├── combined.ts │ │ │ │ ├── user.ts │ │ │ │ └── cart.ts │ │ ├── devtools.spec.ts │ │ ├── rootState.spec.ts │ │ └── combinedStores.spec.ts │ ├── index.cjs │ ├── test-dts │ │ ├── tsconfig.json │ │ ├── index.d.ts │ │ ├── plugins.test-d.ts │ │ ├── storeSetup.test-d.ts │ │ ├── actions.test-d.ts │ │ ├── onAction.test-d.ts │ │ ├── storeToRefs.test-d.ts │ │ └── typeHelpers.test-d.ts │ ├── README.md │ └── api-extractor.json ├── nuxt │ ├── playground │ │ ├── fake-main.js │ │ ├── app.vue │ │ ├── tsconfig.json │ │ ├── layers │ │ │ └── layer-domain │ │ │ │ ├── nuxt.config.ts │ │ │ │ └── stores │ │ │ │ └── basic.ts │ │ ├── package.json │ │ ├── domain │ │ │ └── one │ │ │ │ └── stores │ │ │ │ └── testStore.ts │ │ ├── stores │ │ │ ├── nested │ │ │ │ └── some-store.ts │ │ │ ├── with-skip-hydrate.ts │ │ │ └── counter.ts │ │ ├── pages │ │ │ ├── skip-hydrate.vue │ │ │ └── index.vue │ │ └── nuxt.config.ts │ ├── .gitignore │ ├── shims.d.ts │ ├── tsconfig.json │ ├── src │ │ ├── runtime │ │ │ ├── composables.ts │ │ │ ├── payload-plugin.ts │ │ │ └── plugin.vue3.ts │ │ └── auto-hmr-plugin.ts │ ├── test │ │ └── nuxt.spec.ts │ ├── README.md │ └── package.json ├── docs │ ├── .gitignore │ ├── public │ │ ├── logo.png │ │ ├── social.png │ │ ├── vuejsde-conf │ │ │ ├── vuejsdeconf_banner_large.png │ │ │ ├── vuejsdeconf_banner_medium.png │ │ │ ├── vuejsdeconf_banner_small.png │ │ │ ├── vuejsdeconf_banner_large_2x.png │ │ │ ├── vuejsdeconf_banner_small_2x.png │ │ │ ├── vuejsdeconf_banner_smallest.png │ │ │ ├── vuejsdeconf_banner_medium_2x.png │ │ │ └── vuejsdeconf_banner_smallest_2x.png │ │ ├── vue-cert-logo.svg │ │ ├── rulekit-logo.svg │ │ └── sponsors │ │ │ ├── vuetify-logo-dark-text.svg │ │ │ └── vuetify-logo-light-text.svg │ ├── .vitepress │ │ ├── translation-status.json │ │ ├── theme │ │ │ ├── styles │ │ │ │ └── playground-links.css │ │ │ ├── components │ │ │ │ ├── HomeSponsors.vue │ │ │ │ ├── VueSchoolLink.vue │ │ │ │ ├── VueMasteryHomeLink.vue │ │ │ │ ├── VueMasteryLogoLink.vue │ │ │ │ ├── sponsors.json │ │ │ │ └── HomeSponsorsGroup.vue │ │ │ └── index.ts │ │ └── config │ │ │ └── index.ts │ ├── zh │ │ ├── api │ │ │ ├── index.md │ │ │ ├── interfaces │ │ │ │ ├── pinia.MapStoresCustomization.md │ │ │ │ ├── pinia.PiniaCustomStateProperties.md │ │ │ │ ├── pinia.StoreProperties.md │ │ │ │ ├── pinia_nuxt.ModuleOptions.md │ │ │ │ ├── pinia.DefineStoreOptionsBase.md │ │ │ │ ├── pinia._SubscriptionCallbackMutationBase.md │ │ │ │ ├── pinia.DefineSetupStoreOptions.md │ │ │ │ ├── pinia.Pinia.md │ │ │ │ ├── pinia.SubscriptionCallbackMutationDirect.md │ │ │ │ ├── pinia.SubscriptionCallbackMutationPatchFunction.md │ │ │ │ ├── pinia.SubscriptionCallbackMutationPatchObject.md │ │ │ │ ├── pinia.PiniaCustomProperties.md │ │ │ │ ├── pinia.PiniaPluginContext.md │ │ │ │ ├── pinia_testing.TestingPinia.md │ │ │ │ ├── pinia.StoreDefinition.md │ │ │ │ ├── pinia.PiniaPlugin.md │ │ │ │ └── pinia_testing.TestingOptions.md │ │ │ ├── modules │ │ │ │ ├── pinia_nuxt.md │ │ │ │ └── pinia_testing.md │ │ │ └── enums │ │ │ │ └── pinia.MutationType.md │ │ ├── cookbook │ │ │ ├── index.md │ │ │ ├── hot-module-replacement.md │ │ │ ├── vscode-snippets.md │ │ │ └── options-api.md │ │ ├── index.md │ │ ├── core-concepts │ │ │ └── outside-component-usage.md │ │ └── getting-started.md │ ├── vite-typedoc-plugin.ts │ ├── cookbook │ │ ├── index.md │ │ ├── hot-module-replacement.md │ │ ├── migration-v2-v3.md │ │ └── vscode-snippets.md │ ├── package.json │ ├── run-typedoc.mjs │ ├── typedoc.tsconfig.json │ ├── vite.config.ts │ └── index.md ├── testing │ ├── README.md │ ├── src │ │ ├── index.ts │ │ ├── restoreGetters.ts │ │ ├── restoreGetters.spec.ts │ │ └── initialState.spec.ts │ ├── tsconfig.build.json │ ├── tsup.config.ts │ └── package.json ├── playground │ ├── src │ │ ├── test.ts │ │ ├── views │ │ │ ├── About.vue │ │ │ ├── DemoCounter.vue │ │ │ ├── 404.vue │ │ │ ├── AllStores.vue │ │ │ ├── AllStoresDispose.vue │ │ │ ├── CounterStore.vue │ │ │ ├── CounterSetupStore.vue │ │ │ ├── NasaPODSwrv.vue │ │ │ ├── Jokes.vue │ │ │ ├── NasaPOD.vue │ │ │ ├── swrv.vue │ │ │ └── JokesPromised.vue │ │ ├── shims-vue.d.ts │ │ ├── api │ │ │ ├── jokes.ts │ │ │ └── nasa.ts │ │ ├── vite-env.d.ts │ │ ├── stores │ │ │ ├── demo-counter.ts │ │ │ ├── jokes-swrv.ts │ │ │ ├── user.ts │ │ │ ├── cart.ts │ │ │ ├── wholeStore.ts │ │ │ ├── nasa.ts │ │ │ ├── jokes.ts │ │ │ ├── counterSetup.ts │ │ │ ├── counter.ts │ │ │ ├── nasa-pod.ts │ │ │ └── jokesUsePromised.ts │ │ ├── router.ts │ │ ├── composables │ │ │ └── useCachedRequest.ts │ │ ├── App.vue │ │ └── main.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ ├── index.html │ └── vite.config.ts ├── online-playground │ ├── src │ │ ├── vue-dev-proxy.ts │ │ ├── pinia-dev-proxy.ts │ │ ├── download │ │ │ ├── template │ │ │ │ ├── main.js │ │ │ │ ├── vite.config.js │ │ │ │ ├── README.md │ │ │ │ ├── package.json │ │ │ │ └── index.html │ │ │ └── download.ts │ │ ├── vue-server-renderer-dev-proxy.ts │ │ ├── main.ts │ │ ├── icons │ │ │ ├── Share.vue │ │ │ ├── Moon.vue │ │ │ ├── GitHub.vue │ │ │ ├── Download.vue │ │ │ └── Sun.vue │ │ └── defaults.ts │ ├── netlify.toml │ ├── public │ │ ├── logo-mp.png │ │ ├── logo-vue.svg │ │ └── logo-ts.svg │ ├── shims-vue.d.ts │ ├── deploy-check.sh │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ ├── index.html │ └── vite.config.ts └── size-check │ ├── src │ └── pinia.js │ ├── package.json │ └── scripts │ └── check-size.mjs ├── pnpm-workspace.yaml ├── .npmrc ├── .github ├── funding.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── settings.yml ├── workflows │ ├── release-tag.yml │ ├── pkg.pr.new.yml │ └── ci.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ └── bug_report.yml └── CODE_OF_CONDUCT.md ├── renovate.json ├── .prettierignore ├── .prettierrc.js ├── codecov.yml ├── netlify.toml ├── .vscode ├── settings.json └── launch.json ├── .gitignore ├── scripts ├── docs-check.sh └── verifyCommit.mjs ├── SECURITY.md ├── LICENSE ├── tsconfig.json └── vitest.config.ts /packages/pinia/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/nuxt/playground/fake-main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/docs/.gitignore: -------------------------------------------------------------------------------- 1 | .vitepress/cache 2 | -------------------------------------------------------------------------------- /packages/nuxt/.gitignore: -------------------------------------------------------------------------------- 1 | .nuxt 2 | .output 3 | -------------------------------------------------------------------------------- /packages/testing/README.md: -------------------------------------------------------------------------------- 1 | # Pinia testing module 2 | -------------------------------------------------------------------------------- /packages/playground/src/test.ts: -------------------------------------------------------------------------------- 1 | export const ha = 3 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | github: posva 2 | custom: https://www.paypal.me/posva 3 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>posva/renovate-config"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/nuxt/playground/app.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/pinia/src/env.ts: -------------------------------------------------------------------------------- 1 | export const IS_CLIENT = typeof window !== 'undefined' 2 | -------------------------------------------------------------------------------- /packages/nuxt/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | __build__ 2 | dist 3 | coverage 4 | packages/docs/.vitepress/cache 5 | temp 6 | -------------------------------------------------------------------------------- /packages/playground/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /packages/nuxt/playground/layers/layer-domain/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({}) 2 | -------------------------------------------------------------------------------- /packages/docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/pinia/HEAD/packages/docs/public/logo.png -------------------------------------------------------------------------------- /packages/pinia/src/devtools/index.ts: -------------------------------------------------------------------------------- 1 | export { devtoolsPlugin, registerPiniaDevtools } from './plugin' 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | semi: false, 3 | trailingComma: 'es5', 4 | singleQuote: true, 5 | } 6 | -------------------------------------------------------------------------------- /packages/docs/public/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/pinia/HEAD/packages/docs/public/social.png -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: off 4 | project: 5 | default: 6 | threshold: 2% 7 | -------------------------------------------------------------------------------- /packages/online-playground/src/vue-dev-proxy.ts: -------------------------------------------------------------------------------- 1 | // serve vue to the iframe sandbox during dev. 2 | export * from 'vue' 3 | -------------------------------------------------------------------------------- /packages/nuxt/shims.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | export interface Process { 3 | server: boolean 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/online-playground/src/pinia-dev-proxy.ts: -------------------------------------------------------------------------------- 1 | // serve pinia to the iframe sandbox during dev. 2 | export * from 'pinia' 3 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/translation-status.json: -------------------------------------------------------------------------------- 1 | { 2 | "zh": { 3 | "hash": "02a476d", 4 | "date": "2024-05-20" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/online-playground/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "pnpm run build" 3 | ignore = "./deploy-check.sh" 4 | publish = "dist" 5 | -------------------------------------------------------------------------------- /packages/online-playground/public/logo-mp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/pinia/HEAD/packages/online-playground/public/logo-mp.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "pnpm run docs:build" 3 | ignore = "./scripts/docs-check.sh" 4 | publish = "packages/docs/.vitepress/dist" 5 | -------------------------------------------------------------------------------- /packages/playground/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/online-playground/src/download/template/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | createApp(App).mount('#app') 5 | -------------------------------------------------------------------------------- /packages/nuxt/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "main": "fake-main.js", 5 | "name": "pinia-nuxt-playground" 6 | } 7 | -------------------------------------------------------------------------------- /packages/docs/public/vuejsde-conf/vuejsdeconf_banner_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/pinia/HEAD/packages/docs/public/vuejsde-conf/vuejsdeconf_banner_large.png -------------------------------------------------------------------------------- /packages/docs/public/vuejsde-conf/vuejsdeconf_banner_medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/pinia/HEAD/packages/docs/public/vuejsde-conf/vuejsdeconf_banner_medium.png -------------------------------------------------------------------------------- /packages/docs/public/vuejsde-conf/vuejsdeconf_banner_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/pinia/HEAD/packages/docs/public/vuejsde-conf/vuejsdeconf_banner_small.png -------------------------------------------------------------------------------- /packages/online-playground/src/vue-server-renderer-dev-proxy.ts: -------------------------------------------------------------------------------- 1 | // serve vue/server-renderer to the iframe sandbox during dev. 2 | export * from 'vue/server-renderer' 3 | -------------------------------------------------------------------------------- /packages/docs/public/vuejsde-conf/vuejsdeconf_banner_large_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/pinia/HEAD/packages/docs/public/vuejsde-conf/vuejsdeconf_banner_large_2x.png -------------------------------------------------------------------------------- /packages/docs/public/vuejsde-conf/vuejsdeconf_banner_small_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/pinia/HEAD/packages/docs/public/vuejsde-conf/vuejsdeconf_banner_small_2x.png -------------------------------------------------------------------------------- /packages/docs/public/vuejsde-conf/vuejsdeconf_banner_smallest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/pinia/HEAD/packages/docs/public/vuejsde-conf/vuejsdeconf_banner_smallest.png -------------------------------------------------------------------------------- /packages/docs/public/vuejsde-conf/vuejsdeconf_banner_medium_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/pinia/HEAD/packages/docs/public/vuejsde-conf/vuejsdeconf_banner_medium_2x.png -------------------------------------------------------------------------------- /packages/docs/public/vuejsde-conf/vuejsdeconf_banner_smallest_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/pinia/HEAD/packages/docs/public/vuejsde-conf/vuejsdeconf_banner_smallest_2x.png -------------------------------------------------------------------------------- /packages/nuxt/playground/layers/layer-domain/stores/basic.ts: -------------------------------------------------------------------------------- 1 | export const useBasicStore = defineStore('layer-basic', () => { 2 | const count = ref(0) 3 | 4 | return { count } 5 | }) 6 | -------------------------------------------------------------------------------- /packages/pinia/__tests__/vitest-setup.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach } from 'vitest' 2 | import { setActivePinia } from '../src' 3 | 4 | beforeEach(() => { 5 | setActivePinia(undefined) 6 | }) 7 | -------------------------------------------------------------------------------- /packages/size-check/src/pinia.js: -------------------------------------------------------------------------------- 1 | import { createPinia, defineStore } from 'pinia' 2 | 3 | export const pinia = createPinia() 4 | // @ts-ignore 5 | export const useStore = defineStore() 6 | -------------------------------------------------------------------------------- /packages/testing/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module @pinia/testing 3 | */ 4 | export { createTestingPinia } from './testing' 5 | export type { TestingPinia, TestingOptions } from './testing' 6 | -------------------------------------------------------------------------------- /packages/nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json", 3 | "include": [ 4 | "./shims.d.ts", 5 | // missing in the playground 6 | "./src" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/nuxt/playground/domain/one/stores/testStore.ts: -------------------------------------------------------------------------------- 1 | export const useTestStore = defineStore('test', () => { 2 | // console.log('I was defined within a store directory') 3 | return {} 4 | }) 5 | -------------------------------------------------------------------------------- /packages/online-playground/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /packages/playground/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /packages/nuxt/src/runtime/composables.ts: -------------------------------------------------------------------------------- 1 | import { useNuxtApp } from '#app' 2 | import type { Pinia } from 'pinia' 3 | export * from 'pinia' 4 | 5 | export const usePinia = () => useNuxtApp().$pinia as Pinia 6 | -------------------------------------------------------------------------------- /packages/nuxt/playground/stores/nested/some-store.ts: -------------------------------------------------------------------------------- 1 | export const useSomeStoreStore = defineStore('some-store', () => { 2 | // console.log('I was defined within a nested store directory') 3 | return {} 4 | }) 5 | -------------------------------------------------------------------------------- /packages/pinia/index.cjs: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./dist/pinia.prod.cjs') 5 | } else { 6 | module.exports = require('./dist/pinia.cjs') 7 | } 8 | -------------------------------------------------------------------------------- /packages/docs/zh/api/index.md: -------------------------------------------------------------------------------- 1 | API 文档 2 | 3 | # API 文档 %{#api-documentation}% 4 | 5 | ## 模块 %{#modules}% 6 | 7 | - [@pinia/nuxt](modules/pinia_nuxt.md) 8 | - [@pinia/testing](modules/pinia_testing.md) 9 | - [pinia](modules/pinia.md) 10 | -------------------------------------------------------------------------------- /packages/online-playground/src/download/template/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | }) 8 | -------------------------------------------------------------------------------- /packages/pinia/test-dts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "declaration": true, 6 | "noImplicitReturns": false 7 | }, 8 | "include": ["./"], 9 | "exclude": ["../__tests__", "../src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/online-playground/deploy-check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # check for netlify to skip deploy 4 | # needed because we cannot use && in netlify.toml 5 | 6 | # exit 0 will skip the build while exit 1 will build 7 | 8 | git diff --quiet 'HEAD^' HEAD . && git diff --quiet 'HEAD^' HEAD ../pinia 9 | -------------------------------------------------------------------------------- /packages/online-playground/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import '@vue/repl/style.css' 4 | 5 | // @ts-expect-error Custom window property 6 | window.VUE_DEVTOOLS_CONFIG = { 7 | defaultSelectedAppId: 'repl', 8 | } 9 | 10 | createApp(App).mount('#app') 11 | -------------------------------------------------------------------------------- /packages/testing/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "files": ["./src/**/*.ts"], 4 | "exclude": ["./src/**/*.spec.ts"], 5 | "compilerOptions": { 6 | "rootDir": "../..", 7 | "target": "esnext", 8 | "module": "esnext", 9 | "strict": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/online-playground/src/download/template/README.md: -------------------------------------------------------------------------------- 1 | # Vite Vue Starter 2 | 3 | This is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.org) v12+. 4 | 5 | To start: 6 | 7 | ```sh 8 | npm install 9 | npm run dev 10 | 11 | # if using yarn: 12 | yarn 13 | yarn dev 14 | ``` 15 | -------------------------------------------------------------------------------- /packages/testing/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['src/index.ts'], 5 | clean: true, 6 | format: ['cjs', 'esm'], 7 | dts: true, 8 | external: ['vue', 'pinia'], 9 | tsconfig: './tsconfig.build.json', 10 | // onSuccess: 'npm run build:fix', 11 | }) 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[javascript]": { 3 | "editor.formatOnSave": true 4 | }, 5 | "[typescript]": { 6 | "editor.formatOnSave": true 7 | }, 8 | "typescript.tsdk": "node_modules/typescript/lib", 9 | "jest.jestCommandLine": "yarn jest --watchAll", 10 | "editor.defaultFormatter": "esbenp.prettier-vscode" 11 | } 12 | -------------------------------------------------------------------------------- /packages/nuxt/playground/stores/with-skip-hydrate.ts: -------------------------------------------------------------------------------- 1 | import { skipHydrate } from 'pinia' 2 | 3 | export const useWithSkipHydrateStore = defineStore('with-skip-hydrate', () => { 4 | const skipped = skipHydrate( 5 | ref({ 6 | text: 'I should not be serialized or hydrated', 7 | }) 8 | ) 9 | return { skipped } 10 | }) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | npm-debug.log 4 | yarn-error.log 5 | .nyc_output 6 | coverage.lcov 7 | dist 8 | .DS_Store 9 | temp 10 | test-dts/tsconfig.tsbuildinfo 11 | .env 12 | packages/*/LICENSE 13 | explorations 14 | docs-api 15 | packages/docs/api 16 | .yalc 17 | yalc.lock 18 | .idea 19 | .vitepress/cache 20 | tsconfig.vitest-temp.json 21 | -------------------------------------------------------------------------------- /packages/online-playground/README.md: -------------------------------------------------------------------------------- 1 | # SFC Playground 2 | 3 | This is continuously deployed at [https://play.vuejs.org](https://play.vuejs.org). 4 | 5 | ## Run Locally in Dev 6 | 7 | In repo root: 8 | 9 | ```sh 10 | pnpm dev-sfc 11 | ``` 12 | 13 | ## Build for Prod 14 | 15 | In repo root 16 | 17 | ```sh 18 | pnpm build-sfc-playground 19 | ``` 20 | -------------------------------------------------------------------------------- /packages/pinia/src/global.d.ts: -------------------------------------------------------------------------------- 1 | // Global compile-time constants 2 | declare var __DEV__: boolean 3 | declare var __TEST__: boolean 4 | declare var __FEATURE_PROD_DEVTOOLS__: boolean 5 | declare var __BROWSER__: boolean 6 | declare var __USE_DEVTOOLS__: boolean 7 | declare var __CI__: boolean 8 | declare var __VUE_DEVTOOLS_TOAST__: ( 9 | message: string, 10 | type?: 'normal' | 'error' | 'warn' 11 | ) => void 12 | -------------------------------------------------------------------------------- /packages/playground/src/api/jokes.ts: -------------------------------------------------------------------------------- 1 | import { mande } from 'mande' 2 | 3 | export const jokes = mande('https://official-joke-api.appspot.com', { 4 | headers: { 5 | 'Content-Type': null, 6 | }, 7 | }) 8 | 9 | export interface Joke { 10 | id: number 11 | type: string 12 | setup: string 13 | punchline: string 14 | } 15 | 16 | export function getRandomJoke() { 17 | return jokes.get('/jokes/random') 18 | } 19 | -------------------------------------------------------------------------------- /packages/online-playground/src/download/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-vue-starter", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "pinia": "workspace:*", 11 | "vue": "^3.3.0" 12 | }, 13 | "devDependencies": { 14 | "@vitejs/plugin-vue": "^4.3.4", 15 | "vite": "^4.4.9" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/online-playground/src/download/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.MapStoresCustomization.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / MapStoresCustomization 8 | 9 | # 接口:MapStoresCustomization %{#interface-mapstorescustomization}% 10 | 11 | [pinia](../modules/pinia.md).MapStoresCustomization 12 | 13 | 允许自定义映射辅助函数的接口。用以下属性来扩展这个接口: 14 | 15 | - `suffix`: 字符串。影响 `mapStores()` 的后缀,默认为`Store`。 16 | -------------------------------------------------------------------------------- /packages/nuxt/playground/pages/skip-hydrate.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 14 | -------------------------------------------------------------------------------- /packages/playground/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Global compile-time constants 4 | declare var __DEV__: boolean 5 | declare var __TEST__: boolean 6 | declare var __FEATURE_PROD_DEVTOOLS__: boolean 7 | declare var __BROWSER__: boolean 8 | declare var __USE_DEVTOOLS__: boolean 9 | declare var __CI__: boolean 10 | declare var __VUE_DEVTOOLS_TOAST__: ( 11 | message: string, 12 | type?: 'normal' | 'error' | 'warn' 13 | ) => void 14 | -------------------------------------------------------------------------------- /packages/docs/zh/cookbook/index.md: -------------------------------------------------------------------------------- 1 | # 手册 %{#cookbook}% 2 | 3 | 4 | 5 | - [从 Vuex ≤4 迁移](./migration-vuex.md)。用于转换 Vuex ≤4 项目的迁移指南。 6 | - [HMR](./hot-module-replacement.md):如何激活热更新并改善开发者体验。 7 | - [测试 Stores (WIP)](./testing.md): 如何对 Store 进行单元测试并在组件单元测试中模拟它们。 8 | - [Composing Stores](./composing-stores.md): 如何交叉使用多个 store,例如在购物车 store 中使用用户 store。 9 | - [选项式 API](./options-api.md): 如何在 `setup()` 外部使用 Pinia 而不使用组合式 API。 10 | - [从 0.0.7 迁移](./migration-0-0-7.md)。迁移指南,比更新日志有更多的例子。 11 | -------------------------------------------------------------------------------- /packages/pinia/test-dts/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/pinia' 2 | // export * from '../src' 3 | 4 | export type TypeEqual = 5 | (() => T extends Target ? 1 : 2) extends () => T extends Value ? 1 : 2 6 | ? true 7 | : false 8 | export function describe(_name: string, _fn: () => void): void 9 | export function expectType(value: T): void 10 | export function expectError(value: T): void 11 | export function expectAssignable(value: T2): void 12 | -------------------------------------------------------------------------------- /packages/online-playground/public/logo-vue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/playground/src/views/DemoCounter.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /packages/playground/src/views/404.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/styles/playground-links.css: -------------------------------------------------------------------------------- 1 | .vp-doc a[href^="https://play.pinia.vuejs.org"]:before { 2 | content: '▶'; 3 | width: 20px; 4 | height: 20px; 5 | display: inline-block; 6 | border-radius: 10px; 7 | vertical-align: middle; 8 | position: relative; 9 | top: -2px; 10 | border: 2px solid; 11 | margin-right: 8px; 12 | margin-left: 4px; 13 | line-height: 15px; 14 | padding-left: 4.5px; 15 | font-size: 11px; 16 | font-family: system-ui, BlinkMacSystemFont, sans-serif; 17 | } 18 | -------------------------------------------------------------------------------- /packages/playground/src/stores/demo-counter.ts: -------------------------------------------------------------------------------- 1 | import { defineStore, acceptHMRUpdate } from 'pinia' 2 | 3 | const delay = (t: number) => new Promise((r) => setTimeout(r, t)) 4 | // just to ignore the not used error 5 | delay(0) 6 | 7 | export const useCounter = defineStore('demo-counter', { 8 | state: () => ({ 9 | n: 0, 10 | }), 11 | getters: { 12 | double: (state) => state.n * 2, 13 | }, 14 | }) 15 | 16 | if (import.meta.hot) { 17 | import.meta.hot.accept(acceptHMRUpdate(useCounter, import.meta.hot)) 18 | } 19 | -------------------------------------------------------------------------------- /packages/docs/vite-typedoc-plugin.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'vite' 2 | import _fs from 'fs' 3 | import { TypeDocOptions } from 'typedoc' 4 | import { createTypeDocApp } from './typedoc-markdown' 5 | 6 | export default function TypeDocPlugin( 7 | config: Partial = {} 8 | ): Plugin { 9 | const { serve, setTargetMode } = createTypeDocApp(config) 10 | setTargetMode('serve') 11 | 12 | return { 13 | name: 'typedoc', 14 | apply: 'serve', 15 | buildStart() { 16 | return serve() 17 | }, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/size-check/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pinia/size-check", 3 | "private": true, 4 | "type": "module", 5 | "description": "size checks", 6 | "version": "0.0.0", 7 | "scripts": { 8 | "build:size": "rollup -c rollup.config.mjs", 9 | "size": "pnpm run build:size && pnpm run size:check", 10 | "size:check": "node scripts/check-size.mjs" 11 | }, 12 | "devDependencies": { 13 | "brotli-wasm": "~1.2.0", 14 | "zlib": "^1.0.5" 15 | }, 16 | "dependencies": { 17 | "pinia": "workspace:*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/online-playground/src/icons/Share.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "module": "esnext", 6 | "moduleResolution": "Bundler", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "esModuleInterop": true, 11 | "lib": ["esnext", "dom"], 12 | "types": ["vite/client"], 13 | "paths": { 14 | "pinia": ["../pinia/src/index.ts"] 15 | } 16 | } 17 | // "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 18 | } 19 | -------------------------------------------------------------------------------- /packages/testing/src/restoreGetters.ts: -------------------------------------------------------------------------------- 1 | import { Store, StateTree, _ExtractGettersFromSetupStore_Keys } from 'pinia' 2 | 3 | // TODO: more testing, document and release 4 | 5 | export function restoreGetter( 6 | store: Store, 7 | getter: keyof G 8 | ): void 9 | export function restoreGetter( 10 | store: SS, 11 | getter: _ExtractGettersFromSetupStore_Keys 12 | ): void 13 | export function restoreGetter(store: Store, getter: any): void { 14 | // @ts-expect-error: private api 15 | store[getter] = undefined 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "node", 5 | "name": "vscode-jest-tests", 6 | "request": "launch", 7 | "console": "integratedTerminal", 8 | "internalConsoleOptions": "neverOpen", 9 | "disableOptimisticBPs": true, 10 | "cwd": "${workspaceFolder}", 11 | "runtimeExecutable": "yarn", 12 | "args": [ 13 | "jest", 14 | "--watch", 15 | "--runInBand", 16 | "--watchAll=false", 17 | "--collectCoverage=false" 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/nuxt/playground/stores/counter.ts: -------------------------------------------------------------------------------- 1 | const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) 2 | 3 | export const useCounter = defineStore('counter', { 4 | state: () => ({ 5 | count: 100, 6 | }), 7 | actions: { 8 | increment() { 9 | this.count += 1 10 | }, 11 | 12 | async asyncIncrement() { 13 | console.log('asyncIncrement called') 14 | await sleep(300) 15 | this.count++ 16 | return true 17 | }, 18 | }, 19 | getters: { 20 | double: (state) => state.count * 2, 21 | }, 22 | }) 23 | -------------------------------------------------------------------------------- /packages/online-playground/src/icons/Moon.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /packages/online-playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": ["src/**/*.ts"], 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "target": "esnext", 6 | "module": "esnext", 7 | "moduleResolution": "Bundler", 8 | "strict": true, 9 | "jsx": "preserve", 10 | "sourceMap": true, 11 | "esModuleInterop": true, 12 | "lib": ["esnext", "dom"], 13 | "types": ["vite/client"], 14 | "paths": { 15 | "pinia": ["../pinia/src/index.ts"] 16 | } 17 | } 18 | // "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 19 | } 20 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | labels: 2 | - name: bug 3 | color: ee0701 4 | - name: contribution welcome 5 | color: 0e8a16 6 | - name: discussion 7 | color: 4935ad 8 | - name: docs 9 | color: 8be281 10 | - name: enhancement 11 | color: a2eeef 12 | - name: good first issue 13 | color: 7057ff 14 | - name: help wanted 15 | color: 008672 16 | - name: question 17 | color: d876e3 18 | - name: wontfix 19 | color: ffffff 20 | - name: WIP 21 | color: ffffff 22 | - name: need repro 23 | color: c9581c 24 | - name: feature request 25 | color: fbca04 26 | -------------------------------------------------------------------------------- /packages/pinia/test-dts/plugins.test-d.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | import { 3 | expectType, 4 | createPinia, 5 | StoreGeneric, 6 | Pinia, 7 | StateTree, 8 | DefineStoreOptionsInPlugin, 9 | } from './' 10 | 11 | const pinia = createPinia() 12 | 13 | pinia.use(({ store, app, options, pinia }) => { 14 | expectType(store) 15 | expectType(pinia) 16 | expectType(app) 17 | expectType< 18 | DefineStoreOptionsInPlugin< 19 | string, 20 | StateTree, 21 | Record, 22 | Record 23 | > 24 | >(options) 25 | }) 26 | -------------------------------------------------------------------------------- /scripts/docs-check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # check if doc files changes for netlify 4 | # needed because we cannot use && in netlify.toml 5 | 6 | # exit 0 will skip the build while exit 1 will build 7 | 8 | # - check any change in docs 9 | # - check for new version of vite related deps 10 | # - changes in netlify conf 11 | # - a commit message that starts with docs like docs: ... or docs(nuxt): ... 12 | 13 | git diff --quiet 'HEAD^' HEAD ./packages/docs/ && ! git diff 'HEAD^' HEAD ./pnpm-lock.yaml | grep --quiet vitepress && git diff --quiet 'HEAD^' HEAD netlify.toml && ! git log -1 --pretty=%B | grep '^docs' 14 | -------------------------------------------------------------------------------- /packages/online-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pinia/playground", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "pnpm -C ../pinia run build && vite build", 9 | "serve": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@vitejs/plugin-vue": "^6.0.1", 13 | "execa": "^9.6.0", 14 | "vite": "^7.1.12" 15 | }, 16 | "dependencies": { 17 | "@vue/repl": "^3.0.0", 18 | "file-saver": "^2.0.5", 19 | "jszip": "^3.10.1", 20 | "pinia": "workspace:*", 21 | "vue": "^3.5.22" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/pinia/__tests__/pinia/stores/combined.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck TODO: implement it or do something different for combined stores 2 | import { useUserStore } from './user' 3 | import { useCartStore } from './cart' 4 | import { pinia, CombinedState } from '../../../src/pinia' 5 | // in this file we could import other stores that use one each other while 6 | // avoiding any recursive import 7 | 8 | export function _test() { 9 | type S = CombinedState<{ 10 | user: typeof useUserStore 11 | cart: typeof useCartStore 12 | }> 13 | 14 | let a: S 15 | 16 | a.user.isAdmin = false 17 | a.cart.rawItems.push() 18 | } 19 | -------------------------------------------------------------------------------- /packages/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pinia/private-playground", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "play": "vite", 8 | "play:build": "vite build", 9 | "serve": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@vitejs/plugin-vue": "^6.0.1", 13 | "vite": "^7.1.12", 14 | "vite-plugin-vue-devtools": "^7.7.7" 15 | }, 16 | "dependencies": { 17 | "@vueuse/core": "^14.0.0", 18 | "mande": "^2.0.9", 19 | "pinia": "workspace:*", 20 | "swrv": "^1.1.0", 21 | "vue-promised": "^2.2.0", 22 | "vue-router": "^4.6.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/nuxt/src/runtime/payload-plugin.ts: -------------------------------------------------------------------------------- 1 | import { 2 | definePayloadPlugin, 3 | definePayloadReducer, 4 | definePayloadReviver, 5 | } from '#imports' 6 | import {} from 'nuxt/app' 7 | import { shouldHydrate } from 'pinia' 8 | 9 | /** 10 | * Removes properties marked with `skipHydrate()` to avoid sending unused data to the client. 11 | */ 12 | const payloadPlugin = definePayloadPlugin(() => { 13 | definePayloadReducer( 14 | 'skipHydrate', 15 | // We need to return something truthy to be treated as a match 16 | (data: unknown) => !shouldHydrate(data) && 1 17 | ) 18 | definePayloadReviver('skipHydrate', (_data: 1) => undefined) 19 | }) 20 | 21 | export default payloadPlugin 22 | -------------------------------------------------------------------------------- /packages/online-playground/src/defaults.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, Ref } from 'vue' 2 | 3 | export const AppVue = ` 4 | 9 | 10 | 13 | `.trimStart() 14 | 15 | export const counterTs = ` 16 | import { defineStore } from 'pinia' 17 | import { ref } from 'vue' 18 | 19 | export const useStore = defineStore('counter', () => { 20 | const n = ref(0) 21 | 22 | return { n } 23 | }) 24 | `.trimStart() 25 | 26 | export const PiniaVersionKey: InjectionKey> = 27 | Symbol('pinia-version') 28 | -------------------------------------------------------------------------------- /packages/docs/cookbook/index.md: -------------------------------------------------------------------------------- 1 | # Cookbook 2 | 3 | 4 | 5 | - [Migrating from Vuex ≤4](./migration-vuex.md): A migration guide for converting Vuex ≤4 projects. 6 | - [HMR](./hot-module-replacement.md): How to activate hot module replacement and improve the developer experience. 7 | - [Testing Stores (WIP)](./testing.md): How to unit test Stores and mock them in component unit tests. 8 | - [Composing Stores](./composing-stores.md): How to cross use multiple stores. e.g. using the user store in the cart store. 9 | - [Options API](./options-api.md): How to use Pinia without the composition API, outside of `setup()`. 10 | - [Migrating from 0.0.7](./migration-0-0-7.md): A migration guide with more examples than the changelog. 11 | -------------------------------------------------------------------------------- /.github/workflows/release-tag.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 5 | 6 | name: Create Release 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | - name: Create Release for Tag 16 | id: release_tag 17 | uses: yyx990803/release-tag@master 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | tag_name: ${{ github.ref }} 22 | body: | 23 | Please refer to [CHANGELOG.md](https://github.com/vuejs/pinia/blob/v3/packages/pinia/CHANGELOG.md) for details. 24 | -------------------------------------------------------------------------------- /packages/playground/src/router.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' 2 | import kebabcase from 'lodash.kebabcase' 3 | 4 | const viewModules = import.meta.glob('./views/*.vue') 5 | 6 | const nameFromPath = (path: string) => path.replace(/^.*\/(\w+)\.vue$/, '$1') 7 | 8 | const pages: RouteRecordRaw[] = Object.keys(viewModules).map((path) => { 9 | const name = nameFromPath(path) 10 | return { 11 | name, 12 | path: name === '404' ? '/:patchMatch(.*)*' : '/' + kebabcase(name), 13 | component: viewModules[path], 14 | meta: { 15 | hide: name === '404', 16 | }, 17 | } 18 | }) 19 | 20 | export const router = createRouter({ 21 | history: createWebHistory(), 22 | routes: [...pages], 23 | }) 24 | -------------------------------------------------------------------------------- /packages/pinia/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Pinia logo 4 | 5 |

6 | 7 | # Pinia 8 | 9 | > Intuitive, type safe and flexible Store for Vue 10 | 11 | ## 👉 [Demo with Vue 3 on StackBlitz](https://stackblitz.com/github/piniajs/example-vue-3-vite) 12 | 13 | ## Help me keep working on this project 💚 14 | 15 | - [Become a Sponsor on GitHub](https://github.com/sponsors/posva) 16 | - [One-time donation via PayPal](https://paypal.me/posva) 17 | 18 | ## Documentation 19 | 20 | To learn more about Pinia, check [its documentation](https://pinia.vuejs.org). 21 | 22 | ## License 23 | 24 | [MIT](http://opensource.org/licenses/MIT) 25 | -------------------------------------------------------------------------------- /packages/nuxt/playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import { defineNuxtConfig } from 'nuxt/config' 3 | import piniaModule from '../src/module' 4 | 5 | export default defineNuxtConfig({ 6 | devtools: { enabled: true }, 7 | alias: { 8 | pinia: fileURLToPath(new URL('../../pinia/src/index.ts', import.meta.url)), 9 | }, 10 | 11 | modules: [piniaModule], 12 | 13 | telemetry: { 14 | enabled: false, 15 | }, 16 | 17 | pinia: { 18 | storesDirs: ['./stores/**', './domain/*/stores'], 19 | }, 20 | 21 | vite: { 22 | define: { 23 | __DEV__: JSON.stringify(process.env.NODE_ENV !== 'production'), 24 | __USE_DEVTOOLS__: true, 25 | __TEST__: false, 26 | }, 27 | }, 28 | 29 | compatibilityDate: '2024-09-26', 30 | }) 31 | -------------------------------------------------------------------------------- /packages/online-playground/src/icons/GitHub.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.PiniaCustomStateProperties.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / PiniaCustomStateProperties 8 | 9 | # 接口:PiniaCustomStateProperties %{#interface-piniacustomstateproperties-s}% 10 | 11 | [pinia](../modules/pinia.md).PiniaCustomStateProperties 12 | 13 | 通过 `pinia.use()` 添加到每个 `store.$state` 的属性。 14 | 15 | ## 类型参数 %{#type-parameters}% 16 | 17 | | 名称 | 类型 | 18 | | :--- | :-------------------------------------------------------------------------------------------------- | 19 | | `S` | extends [`StateTree`](../modules/pinia.md#statetree) = [`StateTree`](../modules/pinia.md#statetree) | 20 | -------------------------------------------------------------------------------- /packages/playground/src/views/AllStores.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 👨‍💻 Support 4 | url: https://cal.com/posva/consultancy 5 | about: Get direct help from the author of Pinia with your project 6 | - name: 🔧 Vue 2 Official Support 7 | url: https://cal.com/posva/consultancy 8 | about: Is your project still on Vue 2? Get official support 9 | - name: ❓ Questions 10 | url: https://github.com/vuejs/pinia/discussions/new?category=Q-A 11 | about: Ask a question or discuss about Pinia 12 | - name: 💡 Ideas 13 | url: https://github.com/vuejs/pinia/discussions/new?category=Ideas 14 | about: Start a discussion to improve Pinia 15 | - name: 🌟 GitHub Sponsors 16 | url: https://github.com/sponsors/posva 17 | about: Like this project? Please consider supporting the author. 18 | -------------------------------------------------------------------------------- /packages/nuxt/playground/pages/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 28 | -------------------------------------------------------------------------------- /packages/pinia/src/devtools/utils.ts: -------------------------------------------------------------------------------- 1 | import { Pinia } from '../rootStore' 2 | 3 | /** 4 | * Shows a toast or console.log 5 | * 6 | * @param message - message to log 7 | * @param type - different color of the tooltip 8 | */ 9 | export function toastMessage( 10 | message: string, 11 | type?: 'normal' | 'error' | 'warn' | undefined 12 | ) { 13 | const piniaMessage = '🍍 ' + message 14 | 15 | if (typeof __VUE_DEVTOOLS_TOAST__ === 'function') { 16 | // No longer available :( 17 | __VUE_DEVTOOLS_TOAST__(piniaMessage, type) 18 | } else if (type === 'error') { 19 | console.error(piniaMessage) 20 | } else if (type === 'warn') { 21 | console.warn(piniaMessage) 22 | } else { 23 | console.log(piniaMessage) 24 | } 25 | } 26 | 27 | export function isPinia(o: any): o is Pinia { 28 | return '_a' in o && 'install' in o 29 | } 30 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | This is the list of versions of Pinia which are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | --------- | ------------------ | 10 | | 2.2.x | :white_check_mark: | 11 | | <2.2.0 | :x: | 12 | 13 | ## Reporting a Vulnerability 14 | 15 | To report a vulnerability, please email the details to . 16 | 17 | When a vulnerability is reported, it immediately becomes our top concern, with a full-time contributor dropping everything to work on it. 18 | 19 | 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. 20 | 21 | Thanks for helping to keep Pinia secure. 22 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pinia/docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "predocs": "node run-typedoc.mjs", 8 | "docs": "vitepress dev .", 9 | "docs:api": "node run-typedoc.mjs", 10 | "docs:translation:compare": "v-translation compare", 11 | "docs:translation:update": "v-translation update", 12 | "docs:translation:status": "v-translation status", 13 | "docs:build": "vitepress build .", 14 | "docs:preview": "vitepress preview ." 15 | }, 16 | "dependencies": { 17 | "@chenfengyuan/vue-countdown": "^2.1.3", 18 | "@vueuse/core": "^14.0.0", 19 | "pinia": "workspace:*", 20 | "typedoc-vitepress-theme": "^1.1.2", 21 | "vitepress": "1.6.3", 22 | "vitepress-translation-helper": "^0.2.2", 23 | "vue-use-spring": "^0.3.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/pinia/src/subscriptions.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentScope, onScopeDispose } from 'vue' 2 | import { _Method } from './types' 3 | 4 | export const noop = () => {} 5 | 6 | export function addSubscription( 7 | subscriptions: Set, 8 | callback: T, 9 | detached?: boolean, 10 | onCleanup: () => void = noop 11 | ) { 12 | subscriptions.add(callback) 13 | 14 | const removeSubscription = () => { 15 | const isDel = subscriptions.delete(callback) 16 | isDel && onCleanup() 17 | } 18 | 19 | if (!detached && getCurrentScope()) { 20 | onScopeDispose(removeSubscription) 21 | } 22 | 23 | return removeSubscription 24 | } 25 | 26 | export function triggerSubscriptions( 27 | subscriptions: Set, 28 | ...args: Parameters 29 | ) { 30 | subscriptions.forEach((callback) => { 31 | callback(...args) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /packages/online-playground/src/icons/Download.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /packages/playground/src/api/nasa.ts: -------------------------------------------------------------------------------- 1 | import { mande } from 'mande' 2 | 3 | /** 4 | * Go to https://api.nasa.gov/ and generate a key. Put it in your `.env` file 5 | * next to the `package.json` file: 6 | * 7 | * VITE_API_KEY_NASA= 8 | */ 9 | const API_KEY = import.meta.env.VITE_API_KEY_NASA || 'DEMO_KEY' 10 | 11 | const nasaPlanetary = mande('https://api.nasa.gov/planetary', { 12 | query: { api_key: API_KEY, thumbs: true }, 13 | }) 14 | 15 | export interface NASAPOD { 16 | copyright: string 17 | date: string 18 | explanation: string 19 | hdurl: string 20 | media_type: 'image' | 'video' 21 | title: string 22 | url: string 23 | } 24 | 25 | export function getNASAPOD(date: Date | string = new Date()) { 26 | if (typeof date !== 'string') { 27 | date = date.toISOString().slice(0, 10) 28 | } 29 | 30 | return nasaPlanetary.get('/apod', { query: { date } }) 31 | } 32 | -------------------------------------------------------------------------------- /packages/playground/src/stores/jokes-swrv.ts: -------------------------------------------------------------------------------- 1 | import { ref, toRaw, watch } from 'vue' 2 | import { acceptHMRUpdate, defineStore } from 'pinia' 3 | import { getRandomJoke, Joke } from '../api/jokes' 4 | import useSWRV from 'swrv' 5 | 6 | export const useJokesSetup = defineStore('jokes-swrv-setup', () => { 7 | // const current = ref(null) 8 | const history = ref([]) 9 | 10 | const { data, error, mutate } = useSWRV('jokes', getRandomJoke) 11 | 12 | watch(data, (joke) => { 13 | console.log('changed from within the store', joke) 14 | if (joke) { 15 | history.value.push(toRaw(joke)) 16 | } 17 | }) 18 | 19 | return { current: data, error, history, fetchJoke: mutate } 20 | }) 21 | 22 | if (import.meta.hot) { 23 | // import.meta.hot.accept(acceptHMRUpdate(useJokes, import.meta.hot)) 24 | import.meta.hot.accept(acceptHMRUpdate(useJokesSetup, import.meta.hot)) 25 | } 26 | -------------------------------------------------------------------------------- /packages/docs/zh/cookbook/hot-module-replacement.md: -------------------------------------------------------------------------------- 1 | # HMR (Hot Module Replacement) %{#hmr-hot-module-replacement}% 2 | 3 | 4 | 5 | Pinia 支持热更新,所以你可以编辑你的 store,并直接在你的应用中与它们互动,而不需要重新加载页面,允许你保持当前的 state、并添加甚至删除 state、action 和 getter。 6 | 7 | 目前,只有 [Vite](https://cn.vitejs.dev/guide/api-hmr#hmr-api) 被官方支持,不过任何实现 `import.meta.hot` 规范的构建工具都应该能正常工作。(例外的是,[webpack](https://webpack.js.org/api/module-variables/#importmetawebpackhot) 似乎使用的是 `import.meta.webpackHot` 而不是 `import.meta.hot` ) 8 | 你只需要在任何 store 声明旁边添加这段代码。比方说,你有三个 store:`auth.js`、 `cart.js` 和 `chat.js`, 你必须在每个 **store 声明**后都添加(和调整)这段代码。 9 | 10 | ```js 11 | // auth.js 12 | import { defineStore, acceptHMRUpdate } from 'pinia' 13 | 14 | const useAuth = defineStore('auth', { 15 | // 配置... 16 | }) 17 | 18 | // 确保传递正确的 store 声明,本例中为 `useAuth` 19 | if (import.meta.hot) { 20 | import.meta.hot.accept(acceptHMRUpdate(useAuth, import.meta.hot)) 21 | } 22 | ``` 23 | -------------------------------------------------------------------------------- /packages/docs/run-typedoc.mjs: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { createTypeDocApp } from './typedoc-markdown.mjs' 3 | 4 | const __dirname = path.dirname(new URL(import.meta.url).pathname) 5 | 6 | createTypeDocApp({ 7 | textContentMappings: { 8 | 'title.indexPage': 'API Reference', 9 | 'title.memberPage': '{name}', 10 | }, 11 | tsconfig: path.resolve(__dirname, './typedoc.tsconfig.json'), 12 | // entryPointStrategy: 'packages', 13 | categorizeByGroup: true, 14 | githubPages: false, 15 | readme: 'none', 16 | indexFormat: 'table', 17 | disableSources: true, 18 | plugin: ['typedoc-plugin-markdown', 'typedoc-vitepress-theme'], 19 | useCodeBlocks: true, 20 | entryPoints: [ 21 | path.resolve(__dirname, '../pinia/src/index.ts'), 22 | path.resolve(__dirname, '../testing/src/index.ts'), 23 | path.resolve(__dirname, '../nuxt/src/module.ts'), 24 | ], 25 | }).then((app) => app.build()) 26 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.StoreProperties.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / StoreProperties 8 | 9 | # 接口:StoreProperties %{#interface-storeproperties-id}% 10 | 11 | [pinia](../modules/pinia.md).StoreProperties 12 | 13 | store 的属性。 14 | 15 | ## 类型参数 %{#type-parameters}% 16 | 17 | | 名称 | 类型 | 18 | | :--- | :--------------- | 19 | | `Id` | extends `string` | 20 | 21 | ## 层次结构 %{#hierarchy}% 22 | 23 | - **`StoreProperties`** 24 | 25 | ↳ [`_StoreWithState`](pinia._StoreWithState.md) 26 | 27 | ## 属性 28 | 29 | ### $id %{#id}% 30 | 31 | • **$id**: `Id` 32 | 33 | store 的唯一标识符 34 | 35 | --- 36 | 37 | ### \_customProperties %{#customproperties}% 38 | 39 | • **\_customProperties**: `Set`<`string`\> 40 | 41 | 供 devtools 插件使用,用于检索插件添加的属性。 42 | 在生产环境中会被移除。 43 | 开发者可用于添加应在 devtools 中显示的 store 的属性键。 44 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia_nuxt.ModuleOptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | editLink: false 3 | --- 4 | 5 | [API 文档](../index.md) / [@pinia/nuxt](../modules/pinia_nuxt.md) / ModuleOptions 6 | 7 | # 接口:ModuleOptions 8 | 9 | [@pinia/nuxt](../modules/pinia_nuxt.md).ModuleOptions 10 | 11 | ## 属性 %{#Properties}% 12 | 13 | ### autoImports %{#Properties-autoImports}% 14 | 15 | • `Optional` **autoImports**: (`string` \| [`string`, `string`])[] 16 | 17 | 将被添加到 nuxt.config.js 文件的自动导入数组。 18 | 19 | **`Example`** 20 | 21 | ```js 22 | autoImports: [ 23 | // automatically import `defineStore` 24 | 'defineStore', 25 | // automatically import `defineStore` as `definePiniaStore` 26 | ['defineStore', 'definePiniaStore', 27 | ] 28 | ``` 29 | 30 | --- 31 | 32 | ### disableVuex %{#Properties-disableVuex}% 33 | 34 | • `Optional` **disableVuex**: `boolean` 35 | 36 | 默认情况下,Pinia 会禁用 Vuex,将此选项设置为 `false` 可启用 Vuex,然后便可同时使用 Pinia 和 Vuex(仅在 Nuxt 2 中支持)。 37 | 38 | **`Default`** 39 | 40 | `true` 41 | -------------------------------------------------------------------------------- /packages/docs/zh/api/modules/pinia_nuxt.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / @pinia/nuxt 8 | 9 | # 模块: @pinia/nuxt %{#module-pinia-nuxt}% 10 | 11 | ## 接口 %{#interfaces}% 12 | 13 | - [ModuleOptions](../interfaces/pinia_nuxt.ModuleOptions.md) 14 | 15 | ## 函数 %{#functions}% 16 | 17 | ### 默认值 %{#default}% 18 | 19 | ▸ **default**(`this`, `inlineOptions`, `nuxt`): `void` \| `Promise`<`void`\> 20 | 21 | #### 参数 22 | 23 | | Name | Type | 24 | | :-------------- | :----------------------------------------------------------- | 25 | | `this` | `void` | 26 | | `inlineOptions` | [`ModuleOptions`](../interfaces/pinia_nuxt.ModuleOptions.md) | 27 | | `nuxt` | `Nuxt` | 28 | 29 | #### 返回值 30 | 31 | `void` \| `Promise`<`void`\> 32 | -------------------------------------------------------------------------------- /.github/workflows/pkg.pr.new.yml: -------------------------------------------------------------------------------- 1 | name: Publish Any Commit 2 | 3 | on: 4 | pull_request: 5 | branches: [v2, v3] 6 | paths-ignore: 7 | - 'packages/docs/**' 8 | - 'packages/playground/**' 9 | 10 | push: 11 | branches: 12 | - '**' 13 | tags: 14 | - '!**' 15 | paths-ignore: 16 | - 'packages/docs/**' 17 | - 'packages/playground/**' 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - name: Checkout code 25 | uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 0 28 | - uses: pnpm/action-setup@v4 29 | - uses: actions/setup-node@v4 30 | with: 31 | node-version: lts/* 32 | cache: pnpm 33 | 34 | - name: Install 35 | run: pnpm install --frozen-lockfile 36 | 37 | - name: Build 38 | run: pnpm build 39 | 40 | - name: Release 41 | run: pnpm dlx pkg-pr-new publish --compact --pnpm './packages/*' 42 | -------------------------------------------------------------------------------- /packages/playground/src/views/AllStoresDispose.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'packages/docs/**' 7 | - 'packages/playground/**' 8 | pull_request: 9 | branches: [v2, v3] 10 | paths-ignore: 11 | - 'packages/docs/**' 12 | - 'packages/playground/**' 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: pnpm/action-setup@v4 21 | - uses: actions/setup-node@v4 22 | with: 23 | node-version: lts/* 24 | cache: pnpm 25 | 26 | - run: pnpm install --frozen-lockfile 27 | - run: pnpm run lint 28 | - run: pnpm run test:types 29 | - run: pnpm run -r dev:prepare 30 | - run: pnpm run test:vitest 31 | - run: pnpm run build 32 | - run: pnpm run build:dts 33 | - run: pnpm run test:dts 34 | - run: pnpm run size 35 | 36 | - uses: codecov/codecov-action@v4 37 | with: 38 | token: ${{ secrets.CODECOV_TOKEN }} 39 | -------------------------------------------------------------------------------- /packages/online-playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Pinia Playground 9 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /packages/playground/src/stores/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const useUserStore = defineStore('user', { 4 | state: () => ({ 5 | name: 'Eduardo', 6 | isAdmin: true, 7 | }), 8 | actions: { 9 | /** 10 | * Attempt to login a user 11 | */ 12 | async login(user: string, password: string) { 13 | const userData = await apiLogin(user, password) 14 | 15 | this.$patch({ 16 | name: user, 17 | ...userData, 18 | }) 19 | }, 20 | logout() { 21 | this.$patch({ 22 | name: '', 23 | isAdmin: false, 24 | }) 25 | 26 | // we could do other stuff like redirecting the user 27 | }, 28 | }, 29 | }) 30 | 31 | /** 32 | * Simulate a login 33 | */ 34 | function apiLogin(a: string, p: string) { 35 | if (a === 'ed' && p === 'ed') return Promise.resolve({ isAdmin: true }) 36 | if (p === 'ed') return Promise.resolve({ isAdmin: false }) 37 | return Promise.reject(new Error('invalid credentials')) 38 | } 39 | -------------------------------------------------------------------------------- /packages/docs/zh/api/enums/pinia.MutationType.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / MutationType 8 | 9 | # Enumeration: MutationType %{#enumeration-mutationtype}% 10 | 11 | [pinia](../modules/pinia.md).MutationType 12 | 13 | SubscriptionCallback 的可能类型 14 | 15 | ## Enumeration Members %{#enumeration-members}% 16 | 17 | ### direct %{#direct}% 18 | 19 | • **direct** = `"direct"` 20 | 21 | Direct mutation of the state: 22 | 23 | - `store.name = 'new name'` 24 | - `store.$state.name = 'new name'` 25 | - `store.list.push('new item')` 26 | 27 | --- 28 | 29 | ### patchFunction %{#patchfunction}% 30 | 31 | • **patchFunction** = `"patch function"` 32 | 33 | 通过 `$patch` 和一个函数更改 state: 34 | 35 | - `store.$patch(state => state.name = 'newName')` 36 | 37 | --- 38 | 39 | ### patchObject %{#patchobject}% 40 | 41 | • **patchObject** = `"patch object"` 42 | 43 | 通过 `$patch` 和一个对象更改 state: 44 | 45 | - `store.$patch({ name: 'newName' })` 46 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.DefineStoreOptionsBase.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / DefineStoreOptionsBase 8 | 9 | # 接口:DefineStoreOptionsBase %{#interface-definestoreoptionsbase-s-store}% 10 | 11 | [pinia](../modules/pinia.md).DefineStoreOptionsBase 12 | 13 | 传递给 `defineStore()` 的选项,在 option store 和 setup store 之间是通用的。 14 | 如果你想为这两种 store 添加自定义的选项, 15 | 请扩展这个接口。 16 | 17 | ## 类型参数 %{#type-parameters}% 18 | 19 | | Name | Type | 20 | | :------ | :--------------------------------------------------- | 21 | | `S` | extends [`StateTree`](../modules/pinia.md#statetree) | 22 | | `Store` | `Store` | 23 | 24 | ## 层次结构 %{#hierarchy}% 25 | 26 | - **`DefineStoreOptionsBase`** 27 | 28 | ↳ [`DefineStoreOptions`](pinia.DefineStoreOptions.md) 29 | 30 | ↳ [`DefineSetupStoreOptions`](pinia.DefineSetupStoreOptions.md) 31 | -------------------------------------------------------------------------------- /packages/nuxt/src/runtime/plugin.vue3.ts: -------------------------------------------------------------------------------- 1 | import { createPinia, setActivePinia } from 'pinia' 2 | import type { Pinia } from 'pinia' 3 | import { defineNuxtPlugin, useNuxtApp, type Plugin } from '#app' 4 | import { toRaw } from 'vue' 5 | 6 | const plugin: Plugin<{ pinia: Pinia }> = defineNuxtPlugin({ 7 | name: 'pinia', 8 | setup(nuxtApp) { 9 | const pinia = createPinia() 10 | nuxtApp.vueApp.use(pinia) 11 | setActivePinia(pinia) 12 | 13 | if (nuxtApp.payload && nuxtApp.payload.pinia) { 14 | pinia.state.value = nuxtApp.payload.pinia as any 15 | } 16 | 17 | // Inject $pinia 18 | return { 19 | provide: { 20 | pinia, 21 | }, 22 | } 23 | }, 24 | hooks: { 25 | 'app:rendered'() { 26 | const nuxtApp = useNuxtApp() 27 | nuxtApp.payload.pinia = toRaw(nuxtApp.$pinia as Pinia).state.value 28 | // clear up the reference to pinia on server to avoid holding onto the variable 29 | setActivePinia(undefined) 30 | }, 31 | }, 32 | }) 33 | 34 | export default plugin 35 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/config/index.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | import { enConfig } from './en' 3 | import { sharedConfig } from './shared' 4 | import { zhConfig } from './zh' 5 | 6 | export default defineConfig({ 7 | ...sharedConfig, 8 | 9 | locales: { 10 | root: { label: 'English', lang: 'en-US', link: '/', ...enConfig }, 11 | zh: { label: '简体中文', lang: 'zh-CN', link: '/zh/', ...zhConfig }, 12 | es: { 13 | label: 'Español', 14 | lang: 'es-ES', 15 | link: 'https://es-pinia.vercel.app/', 16 | }, 17 | ko: { 18 | label: '한국어', 19 | lang: 'ko-KR', 20 | link: 'https://pinia.vuejs.kr/', 21 | }, 22 | pt: { 23 | label: 'Português', 24 | lang: 'pt-PT', 25 | link: 'https://pinia-docs-pt.netlify.app/', 26 | }, 27 | uk: { 28 | label: 'Українська', 29 | lang: 'uk-UA', 30 | link: 'https://pinia-ua.netlify.app', 31 | }, 32 | ru: { 33 | label: 'Русский', 34 | lang: 'ru-RU', 35 | link: 'https://pinia-ru.netlify.app', 36 | }, 37 | }, 38 | }) 39 | -------------------------------------------------------------------------------- /packages/pinia/test-dts/storeSetup.test-d.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref } from 'vue' 2 | import { defineStore, expectType } from './' 3 | 4 | const useSetupStore = defineStore('name', () => { 5 | const count = ref(0) 6 | const double = computed(() => count.value * 2) 7 | const triple = computed({ 8 | get: () => count.value * 3, 9 | set: (tripled) => { 10 | count.value = Math.round(tripled / 3) 11 | }, 12 | }) 13 | 14 | const multiply = computed(() => (n: number) => { 15 | return count.value * n 16 | }) 17 | 18 | function increment(amount = 1) { 19 | count.value += amount 20 | 21 | return count.value 22 | } 23 | 24 | return { count, double, increment, triple, multiply } 25 | }) 26 | 27 | const setupStore = useSetupStore() 28 | expectType<'name'>(setupStore.$id) 29 | expectType(setupStore.count) 30 | expectType(setupStore.$state.count) 31 | expectType(setupStore.double) 32 | expectType(setupStore.triple) 33 | expectType<(n: number) => number>(setupStore.multiply) 34 | expectType<(amount?: number) => number>(setupStore.increment) 35 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia._SubscriptionCallbackMutationBase.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / \_SubscriptionCallbackMutationBase 8 | 9 | # 接口:\_SubscriptionCallbackMutationBase %{#interface-subscriptioncallbackmutationbase}% 10 | 11 | [pinia](../modules/pinia.md).\_SubscriptionCallbackMutationBase 12 | 13 | 传递给订阅回调的上下文的基本类型。内部类型。 14 | 15 | ## 层次结构 %{#hierarchy}% 16 | 17 | - **`_SubscriptionCallbackMutationBase`** 18 | 19 | ↳ [`SubscriptionCallbackMutationDirect`](pinia.SubscriptionCallbackMutationDirect.md) 20 | 21 | ↳ [`SubscriptionCallbackMutationPatchFunction`](pinia.SubscriptionCallbackMutationPatchFunction.md) 22 | 23 | ↳ [`SubscriptionCallbackMutationPatchObject`](pinia.SubscriptionCallbackMutationPatchObject.md) 24 | 25 | ## 属性 26 | 27 | ### storeId %{#storeid}% 28 | 29 | • **storeId**: `string` 30 | 31 | 执行 mutation 的 store 的`id`。 32 | 33 | --- 34 | 35 | ### 类型 %{#type}% 36 | 37 | • **type**: [`MutationType`](../enums/pinia.MutationType.md) 38 | 39 | mutation 的类型 40 | -------------------------------------------------------------------------------- /scripts/verifyCommit.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { readFileSync } from 'node:fs' 3 | import path from 'node:path' 4 | 5 | // Define raw escape codes for colors 6 | const reset = '\x1b[0m' 7 | const red = '\x1b[31m' 8 | const green = '\x1b[32m' 9 | const bgRedWhite = '\x1b[41m\x1b[37m' 10 | 11 | const msgPath = path.resolve('.git/COMMIT_EDITMSG') 12 | const msg = readFileSync(msgPath, 'utf-8').trim() 13 | 14 | const commitRE = 15 | /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?: .{1,50}/ 16 | 17 | if (!commitRE.test(msg)) { 18 | console.log() 19 | console.error( 20 | ` ${bgRedWhite} ERROR ${reset} ${red}invalid commit message format.${reset}\n\n` + 21 | `${red} Proper commit message format is required for automated changelog generation. Examples:\n\n` + 22 | ` ${green}feat: add disableRoot option${reset}\n` + 23 | ` ${green}fix(view): handle keep-alive with aborted navigations (close #28)${reset}\n\n` + 24 | `${red} See .github/commit-convention.md for more details.${reset}\n` 25 | ) 26 | process.exit(1) 27 | } 28 | -------------------------------------------------------------------------------- /packages/playground/src/composables/useCachedRequest.ts: -------------------------------------------------------------------------------- 1 | import { unref, ref, watchEffect, Ref, onScopeDispose } from 'vue' 2 | 3 | export function useCachedRequest( 4 | keySource: Ref, 5 | getter: (key: U) => Promise 6 | ) { 7 | const data = ref() 8 | const isLoading = ref(false) 9 | const isReady = ref(false) 10 | const error = ref() 11 | 12 | const cache = new Map() 13 | 14 | onScopeDispose(() => { 15 | cache.clear() 16 | }) 17 | 18 | watchEffect(async () => { 19 | const key = unref(keySource) 20 | isReady.value = false 21 | isLoading.value = true 22 | 23 | if (cache.has(key)) { 24 | data.value = cache.get(key)! 25 | isReady.value = true 26 | } 27 | 28 | getter(key) 29 | .then((newData) => { 30 | cache.set(key, newData) 31 | data.value = newData 32 | isReady.value = true 33 | }) 34 | .catch((err) => { 35 | error.value = err 36 | }) 37 | .finally(() => { 38 | isLoading.value = false 39 | }) 40 | }) 41 | 42 | return { data, error, isLoading, isReady } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-present Eduardo San Martin Morote 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 🍍 Pinia playground 7 | 8 | 12 | 13 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/online-playground/src/download/download.ts: -------------------------------------------------------------------------------- 1 | import { saveAs } from 'file-saver' 2 | 3 | import index from './template/index.html?raw' 4 | import main from './template/main.js?raw' 5 | import pkg from './template/package.json?raw' 6 | import config from './template/vite.config.js?raw' 7 | import readme from './template/README.md?raw' 8 | import { ReplStore } from '@vue/repl' 9 | 10 | export async function downloadProject(store: ReplStore) { 11 | if (!confirm('Download project files?')) { 12 | return 13 | } 14 | 15 | const { default: JSZip } = await import('jszip') 16 | const zip = new JSZip() 17 | 18 | // basic structure 19 | zip.file('index.html', index) 20 | zip.file('package.json', pkg) 21 | zip.file('vite.config.js', config) 22 | zip.file('README.md', readme) 23 | 24 | // project src 25 | const src = zip.folder('src')! 26 | src.file('main.js', main) 27 | 28 | const files = store.getFiles() 29 | for (const file in files) { 30 | if (file !== 'import-map.json') { 31 | src.file(file, files[file]) 32 | } else { 33 | zip.file(file, files[file]) 34 | } 35 | } 36 | 37 | const blob = await zip.generateAsync({ type: 'blob' }) 38 | saveAs(blob, 'vue-project.zip') 39 | } 40 | -------------------------------------------------------------------------------- /packages/pinia/__tests__/devtools.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | import { mount } from '@vue/test-utils' 3 | import { createPinia, defineStore } from '../src' 4 | import { devtoolsPlugin } from '../src/devtools' 5 | 6 | describe('devtoolsPlugin', () => { 7 | const useStore = defineStore('test', { 8 | actions: { 9 | myAction() { 10 | return 42 11 | }, 12 | }, 13 | }) 14 | 15 | it('preserves mocked actions during testing', () => { 16 | const pinia = createPinia() 17 | // Simulate using createTestingPinia 18 | pinia._testing = true 19 | 20 | mount({ template: 'none' }, { global: { plugins: [pinia] } }) 21 | 22 | // Simulate mocking with @pinia/testing createSpy 23 | pinia.use(({ store, options }) => { 24 | Object.keys(options.actions).forEach((action) => { 25 | store[action]._mockImplementation = () => {} 26 | }) 27 | }) 28 | // Previously the mocked actions would be wrapped again 29 | pinia.use(devtoolsPlugin) 30 | 31 | const store = useStore(pinia) 32 | 33 | // @ts-expect-error we have not actually loaded @pinia/testing and mocked actions 34 | expect(store.myAction._mockImplementation).toBeDefined() 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /packages/docs/cookbook/hot-module-replacement.md: -------------------------------------------------------------------------------- 1 | # HMR (Hot Module Replacement) 2 | 3 | 4 | 5 | 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. 6 | 7 | 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`). 8 | 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_: 9 | 10 | ```js 11 | // auth.js 12 | import { defineStore, acceptHMRUpdate } from 'pinia' 13 | 14 | export const useAuth = defineStore('auth', { 15 | // options... 16 | }) 17 | 18 | // make sure to pass the right store definition, `useAuth` in this case. 19 | if (import.meta.hot) { 20 | import.meta.hot.accept(acceptHMRUpdate(useAuth, import.meta.hot)) 21 | } 22 | ``` 23 | -------------------------------------------------------------------------------- /packages/size-check/scripts/check-size.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import fs from 'node:fs' 3 | import { globby } from 'globby' 4 | import path from 'node:path' 5 | import chalk from 'chalk' 6 | import { gzipSync } from 'node:zlib' 7 | import { fileURLToPath } from 'node:url' 8 | import { compress } from 'brotli-wasm' 9 | 10 | const __filename = fileURLToPath(import.meta.url) 11 | const __dirname = path.dirname(__filename) 12 | 13 | async function checkFileSize(filePath) { 14 | if (!fs.existsSync(filePath)) { 15 | return 16 | } 17 | const file = fs.readFileSync(filePath) 18 | const minSize = (file.length / 1024).toFixed(2) + 'kb' 19 | const gzipped = gzipSync(file) 20 | const gzippedSize = (gzipped.length / 1024).toFixed(2) + 'kb' 21 | const compressed = await compress(file) 22 | const compressedSize = (compressed.length / 1024).toFixed(2) + 'kb' 23 | console.log( 24 | `${chalk.gray( 25 | chalk.bold(path.basename(filePath)) 26 | )} min:${minSize} / gzip:${gzippedSize} / brotli:${compressedSize}` 27 | ) 28 | } 29 | 30 | async function main() { 31 | const paths = await globby(path.resolve(__dirname, '../dist/*.js')) 32 | 33 | for (const file of paths) { 34 | checkFileSize(file) 35 | } 36 | } 37 | 38 | main() 39 | -------------------------------------------------------------------------------- /packages/playground/src/stores/cart.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { useUserStore } from './user' 3 | 4 | export const useCartStore = defineStore('cart', { 5 | state: () => ({ 6 | rawItems: [] as string[], 7 | }), 8 | getters: { 9 | items: (state) => 10 | state.rawItems.reduce( 11 | (items, item) => { 12 | const existingItem = items.find((it) => it.name === item) 13 | 14 | if (!existingItem) { 15 | items.push({ name: item, amount: 1 }) 16 | } else { 17 | existingItem.amount++ 18 | } 19 | 20 | return items 21 | }, 22 | [] as Array<{ name: string; amount: number }> 23 | ), 24 | }, 25 | actions: { 26 | addItem(name: string) { 27 | this.rawItems.push(name) 28 | }, 29 | 30 | removeItem(name: string) { 31 | const i = this.rawItems.lastIndexOf(name) 32 | if (i > -1) this.rawItems.splice(i, 1) 33 | }, 34 | 35 | async purchaseItems() { 36 | const user = useUserStore() 37 | if (!user.name) return 38 | 39 | console.log('Purchasing', this.items) 40 | const n = this.items.length 41 | this.rawItems = [] 42 | 43 | return n 44 | }, 45 | }, 46 | }) 47 | -------------------------------------------------------------------------------- /packages/docs/zh/api/modules/pinia_testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / @pinia/testing 8 | 9 | # 模块:@pinia/testing %{#module-pinia-testing}% 10 | 11 | ## 接口 %{#interfaces}% 12 | 13 | - [TestingOptions](../interfaces/pinia_testing.TestingOptions.md) 14 | - [TestingPinia](../interfaces/pinia_testing.TestingPinia.md) 15 | 16 | ## 函数 %{#functions}% 17 | 18 | ### createTestingPinia %{#createtestingpinia}% 19 | 20 | ▸ **createTestingPinia**(`options?`): [`TestingPinia`](../interfaces/pinia_testing.TestingPinia.md) 21 | 22 | 创建一个为单元测试设计的 pinia 实例,**需要 mocking** store。 23 | 默认情况下,**所有的 action 都是 mocked 的**,因此不会执行。 24 | 这可以让你对 store 和组件进行单独的单元测试。 25 | 你可以通过 `stubActions` 选项来改变这一点。 26 | 如果你使用 jest,将它们替换为 `jest.fn()`, 27 | 否则,你必须提供你自己的 `createSpy` 选项。 28 | 29 | #### 参数 30 | 31 | | 名称 | 类型 | 描述 | 32 | | :-------- | :---------------------------------------------------------------- | :------------------------ | 33 | | `options` | [`TestingOptions`](../interfaces/pinia_testing.TestingOptions.md) | 配置 pinia 测试实例的选项 | 34 | 35 | #### 返回值 36 | 37 | [`TestingPinia`](../interfaces/pinia_testing.TestingPinia.md) 38 | 39 | 一个增强的 Pinia 实例 40 | -------------------------------------------------------------------------------- /packages/playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, Plugin } from 'vite' 2 | import Vue from '@vitejs/plugin-vue' 3 | import fs from 'node:fs/promises' 4 | import { fileURLToPath, URL } from 'node:url' 5 | import VueDevtools from 'vite-plugin-vue-devtools' 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [Vue(), copyPiniaPlugin(), VueDevtools()], 10 | define: { 11 | __DEV__: 'true', 12 | // __BROWSER__: 'true', 13 | __TEST__: 'false', 14 | }, 15 | resolve: { 16 | dedupe: ['vue', 'pinia'], 17 | alias: { 18 | pinia: fileURLToPath(new URL('../pinia/src/index.ts', import.meta.url)), 19 | }, 20 | }, 21 | optimizeDeps: { 22 | exclude: ['@vueuse/shared', '@vueuse/core', 'pinia'], 23 | }, 24 | }) 25 | 26 | function copyPiniaPlugin(): Plugin { 27 | return { 28 | name: 'copy-pinia', 29 | async generateBundle() { 30 | const filePath = fileURLToPath( 31 | new URL('../pinia/dist/pinia.mjs', import.meta.url) 32 | ) 33 | 34 | // throws if file doesn't exist 35 | await fs.access(filePath) 36 | 37 | this.emitFile({ 38 | type: 'asset', 39 | fileName: 'pinia.mjs', 40 | source: await fs.readFile(filePath, 'utf-8'), 41 | }) 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/nuxt/test/nuxt.spec.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import { describe, it, expect } from 'vitest' 3 | import { setup, $fetch } from '@nuxt/test-utils/e2e' 4 | 5 | describe('Nuxt', async () => { 6 | await setup({ 7 | server: true, 8 | rootDir: fileURLToPath(new URL('../playground', import.meta.url)), 9 | nuxtConfig: { 10 | hooks: { 11 | 'vite:extendConfig'(config, { isClient }) { 12 | config.define!.__BROWSER__ = isClient 13 | }, 14 | }, 15 | vite: { 16 | define: { 17 | __DEV__: false, 18 | __TEST__: true, 19 | __FEATURE_PROD_DEVTOOLS__: false, 20 | __USE_DEVTOOLS__: false, 21 | }, 22 | }, 23 | }, 24 | }) 25 | 26 | it('works on ssr', async () => { 27 | const html = await $fetch('/') 28 | expect(html).toContain('Count: 101') 29 | }) 30 | 31 | it('drops state that is marked with skipHydrate', async () => { 32 | const html = await $fetch('/skip-hydrate') 33 | expect(html).not.toContain('I should not be serialized or hydrated') 34 | expect(html).toContain('skipHydrate-wrapped state is correct') 35 | }) 36 | 37 | it('can auto import from layers', async () => { 38 | expect(await $fetch('/')).toContain('Layer store: 0') 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /packages/pinia/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | 4 | "mainEntryPointFilePath": "./dist/packages/pinia/src/index.d.ts", 5 | 6 | "apiReport": { 7 | "enabled": true, 8 | "reportFolder": "/temp/" 9 | }, 10 | 11 | "docModel": { 12 | "enabled": true 13 | }, 14 | 15 | "dtsRollup": { 16 | "enabled": true, 17 | "publicTrimmedFilePath": "./dist/.d.ts" 18 | }, 19 | 20 | "tsdocMetadata": { 21 | "enabled": false 22 | }, 23 | 24 | "messages": { 25 | "compilerMessageReporting": { 26 | "default": { 27 | "logLevel": "warning" 28 | } 29 | }, 30 | 31 | "extractorMessageReporting": { 32 | "default": { 33 | "logLevel": "warning", 34 | "addToApiReportFile": true 35 | }, 36 | 37 | "ae-missing-release-tag": { 38 | "logLevel": "none" 39 | }, 40 | 41 | "ae-incompatible-release-tags": { 42 | "logLevel": "none" 43 | } 44 | }, 45 | 46 | "tsdocMessageReporting": { 47 | "default": { 48 | "logLevel": "warning" 49 | }, 50 | "tsdoc-code-span-missing-delimiter": { 51 | "logLevel": "none" 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/docs/cookbook/migration-v2-v3.md: -------------------------------------------------------------------------------- 1 | # Migrating from v2 to v3 2 | 3 | 4 | 5 | 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). 6 | 7 | For most users, the migration should require **no change**. This guide is here to help you in case you encounter any issues. 8 | 9 | ## Deprecations 10 | 11 | ### `defineStore({ id })` 12 | 13 | The `defineStore()` signature that accepts an object with an `id` property is deprecated. You should use the `id` parameter instead: 14 | 15 | ```ts 16 | defineStore({ // [!code --] 17 | id: 'storeName', // [!code --] 18 | defineStore('storeName', { // [!code ++] 19 | // ... 20 | }) 21 | ``` 22 | 23 | ### `PiniaStorePlugin` 24 | 25 | This deprecated type alias has been removed in favor of `PiniaPlugin`. 26 | 27 | ## New versions 28 | 29 | - Only Vue 3 is supported. 30 | - TypeScript 5 or newer is required. 31 | - The devtools API has been upgraded to [v7](https://devtools.vuejs.org). 32 | 33 | ## Nuxt 34 | 35 | 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. 36 | -------------------------------------------------------------------------------- /packages/docs/typedoc.tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["../pinia/src/global.d.ts", "../*/src/**/*.ts"], 3 | "exclude": [ 4 | "../test-vue-2", 5 | "../pinia/__tests__/test-utils.ts", 6 | "../pinia/test-dts", 7 | "../*/__tests__/**/*.ts", 8 | "../*/src/*.spec.ts", 9 | "../nuxt/playground", 10 | "../playground", 11 | "../online-playground", 12 | "../nuxt/src/runtime", 13 | "**/*.spec.ts" 14 | ], 15 | "compilerOptions": { 16 | "baseUrl": ".", 17 | "rootDir": "..", 18 | "outDir": "dist", 19 | "sourceMap": false, 20 | "noEmit": true, 21 | "paths": { 22 | "@pinia/*": ["../*/src"], 23 | "pinia": ["../pinia/src"] 24 | }, 25 | 26 | "target": "esnext", 27 | "module": "esnext", 28 | "moduleResolution": "node", 29 | "allowJs": false, 30 | "skipLibCheck": true, 31 | 32 | "noUnusedLocals": true, 33 | "strictNullChecks": true, 34 | "noImplicitAny": true, 35 | "noImplicitThis": true, 36 | "noImplicitReturns": false, 37 | "strict": true, 38 | "isolatedModules": true, 39 | 40 | "experimentalDecorators": true, 41 | "esModuleInterop": true, 42 | "removeComments": false, 43 | "jsx": "preserve", 44 | "lib": ["esnext", "dom"], 45 | "types": ["vitest", "node", "vite/client"] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/docs/public/vue-cert-logo.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /packages/pinia/__tests__/pinia/stores/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from '../../../src' 2 | 3 | function apiLogin(a: string, p: string) { 4 | if (a === 'ed' && p === 'ed') return Promise.resolve({ isAdmin: true }) 5 | return Promise.reject(new Error('invalid credentials')) 6 | } 7 | 8 | export const useUserStore = defineStore('user', { 9 | state: () => ({ 10 | name: 'Eduardo', 11 | isAdmin: true, 12 | }), 13 | actions: { 14 | async login(user: string, password: string) { 15 | const userData = await apiLogin(user, password) 16 | 17 | this.$patch({ 18 | name: user, 19 | ...userData, 20 | }) 21 | }, 22 | 23 | logout() { 24 | this.login('a', 'b').then(() => {}) 25 | 26 | this.$patch({ 27 | name: '', 28 | isAdmin: false, 29 | }) 30 | }, 31 | }, 32 | getters: { 33 | test(state) { 34 | return state.name.toUpperCase() 35 | }, 36 | }, 37 | }) 38 | 39 | export type UserStore = ReturnType 40 | 41 | // let a: WrapStoreWithId 42 | 43 | export function logout() { 44 | const store = useUserStore() 45 | 46 | store.login('e', 'e').then(() => {}) 47 | 48 | store.$patch({ 49 | name: '', 50 | isAdmin: false, 51 | }) 52 | 53 | // we could do other stuff like redirecting the user 54 | } 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F680 New feature proposal" 2 | description: Suggest an idea for Pinia 3 | labels: ['feature request'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for your interest in the project and taking the time to fill out this feature report! 9 | - type: textarea 10 | id: feature-description 11 | attributes: 12 | label: What problem is this solving 13 | description: 'A clear and concise description of what the problem is. Ex. when using the function X we cannot do Y.' 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: proposed-solution 18 | attributes: 19 | label: Proposed solution 20 | description: 'A clear and concise description of what you want to happen with an API proposal when applicable' 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: alternative 25 | attributes: 26 | label: Describe alternatives you've considered 27 | description: A clear and concise description of any alternative solutions or features you've considered. 28 | - type: markdown 29 | attributes: 30 | value: | 31 | ## Before creating a feature request make sure that: 32 | - This hasn't been [requested before](https://github.com/vuejs/pinia/issues). 33 | -------------------------------------------------------------------------------- /packages/docs/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type Plugin } from 'vite' 2 | import _fs from 'fs' 3 | import path from 'path' 4 | // import TypeDocPlugin from './vite-typedoc-plugin' 5 | 6 | const fs = _fs.promises 7 | 8 | export default defineConfig({ 9 | clearScreen: false, 10 | plugins: [ 11 | ...(process.env.NETLIFY ? [] : [copyPiniaPlugin()]), 12 | // TODO: actual plugin that works well 13 | // TypeDocPlugin({ 14 | // name: 'Pinia', 15 | // entryPoints: [ 16 | // path.resolve(__dirname, '../pinia/src/index.ts'), 17 | // path.resolve(__dirname, '../testing/src/index.ts'), 18 | // path.resolve(__dirname, '../nuxt/src/index.ts'), 19 | // ], 20 | // }), 21 | ], 22 | define: { 23 | __DEV__: 'true', 24 | __BROWSER__: 'true', 25 | }, 26 | optimizeDeps: { 27 | exclude: ['@vueuse/shared', '@vueuse/core', 'pinia'], 28 | }, 29 | }) 30 | 31 | function copyPiniaPlugin(): Plugin { 32 | return { 33 | name: 'copy-pinia', 34 | async generateBundle() { 35 | const filePath = path.resolve(__dirname, '../pinia/dist/pinia.mjs') 36 | 37 | // throws if file doesn't exist 38 | await fs.access(filePath) 39 | 40 | this.emitFile({ 41 | type: 'asset', 42 | fileName: 'pinia.mjs', 43 | source: await fs.readFile(filePath, 'utf-8'), 44 | }) 45 | }, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/pinia/test-dts/actions.test-d.ts: -------------------------------------------------------------------------------- 1 | import { defineStore, expectType } from './' 2 | 3 | const useStore = defineStore('name', { 4 | state: () => ({ count: 0 }), 5 | actions: { 6 | useOtherAction() { 7 | return this.returnStuff() 8 | }, 9 | useExternalFunction() { 10 | return outer(this as any) 11 | }, 12 | returnStuff() { 13 | this.useOtherAction() 14 | return this.count * 2 15 | }, 16 | // return type is necessary 17 | factorial(n?: number): number { 18 | n ??= this.count 19 | return n > 1 ? this.factorial(n - 1) * n : 1 20 | }, 21 | crossA(): 'A' | 'B' { 22 | if (this.count < 3) { 23 | return 'A' 24 | } else { 25 | return this.crossB() 26 | } 27 | }, 28 | crossB() { 29 | if (this.count > 2) { 30 | return this.crossA() 31 | } else { 32 | return 'B' 33 | } 34 | }, 35 | }, 36 | }) 37 | 38 | function outer(store: ReturnType): number { 39 | return store.count * 2 40 | } 41 | 42 | const store = useStore() 43 | 44 | expectType<'A' | 'B'>(store.crossA()) 45 | expectType<'A' | 'B'>(store.crossB()) 46 | expectType(store.factorial()) 47 | expectType(store.returnStuff()) 48 | expectType(store.useExternalFunction()) 49 | expectType(store.useOtherAction()) 50 | expectType(outer(store)) 51 | -------------------------------------------------------------------------------- /packages/playground/src/stores/wholeStore.ts: -------------------------------------------------------------------------------- 1 | interface User { 2 | id: string 3 | } 4 | type State = { type: T } 5 | type AuthStateLoggingIn = State<'loggingIn'> 6 | type AuthStateLoggedIn = State<'loggedIn'> & { user: User } 7 | type AuthStateError = State<'error'> & { errorMsg: string } 8 | type AuhtStateLoggedOut = State<'loggedOut'> 9 | 10 | export type AuthState = 11 | | AuthStateLoggingIn 12 | | AuthStateLoggedIn 13 | | AuthStateError 14 | | AuhtStateLoggedOut 15 | 16 | import { acceptHMRUpdate, defineStore } from 'pinia' 17 | 18 | const delay = (t: number) => new Promise((r) => setTimeout(r, t)) 19 | 20 | export const useWholeStore = defineStore('whole', { 21 | state: (): AuthState => ({ type: 'loggedIn', user: { id: '1' } }), 22 | 23 | getters: { 24 | errorMsg: (state) => (state.type === 'error' ? state.errorMsg : ''), 25 | }, 26 | 27 | actions: { 28 | async login() { 29 | try { 30 | await delay(1000) 31 | this.$patch({ type: 'loggedIn', user: { id: '1' } }) 32 | } catch (e) { 33 | const error = e as Error 34 | this.$patch({ type: 'error', errorMsg: error.message }) 35 | } 36 | }, 37 | 38 | logout() { 39 | this.$patch({ type: 'loggedOut' }) 40 | }, 41 | }, 42 | }) 43 | 44 | if (import.meta.hot) { 45 | import.meta.hot.accept(acceptHMRUpdate(useWholeStore, import.meta.hot)) 46 | } 47 | -------------------------------------------------------------------------------- /packages/testing/src/restoreGetters.spec.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { computed, ref } from 'vue' 3 | import { describe, expect, it } from 'vitest' 4 | import { createTestingPinia } from './testing' 5 | import { restoreGetter } from './restoreGetters' 6 | 7 | describe('restoreGetters', () => { 8 | it('allows overriding getters', () => { 9 | const useStore = defineStore('lol', { 10 | state: () => ({ n: 0 }), 11 | getters: { 12 | double: (state) => state.n * 2, 13 | }, 14 | }) 15 | const pinia = createTestingPinia() 16 | const store = useStore(pinia) 17 | 18 | store.double = 3 19 | expect(store.double).toBe(3) 20 | restoreGetter(store, 'double') 21 | expect(store.double).toBe(0) 22 | }) 23 | 24 | tds(() => { 25 | const s1 = defineStore('s1', () => { 26 | const n = ref(0) 27 | const double = computed(() => n.value * 2) 28 | return { n, double } 29 | })() 30 | 31 | const s2 = defineStore('s2', { 32 | state: () => ({ n: 0 }), 33 | getters: { 34 | double: (state) => state.n * 2, 35 | }, 36 | })() 37 | 38 | restoreGetter(s1, 'double') 39 | restoreGetter(s2, 'double') 40 | 41 | // @ts-expect-error: not a getter 42 | restoreGetter(s1, 'n') 43 | // @ts-expect-error: not a getter 44 | restoreGetter(s2, 'n') 45 | }) 46 | }) 47 | 48 | function tds(_fn: Function) {} 49 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.DefineSetupStoreOptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / DefineSetupStoreOptions 8 | 9 | # 接口:DefineSetupStoreOptions %{#interface-definesetupstoreoptions-id-s-g-a}% 10 | 11 | [pinia](../modules/pinia.md).DefineSetupStoreOptions 12 | 13 | `defineStore()` 的选项参数,用于设置 store。 14 | 可以通过插件 API 扩展来增强 store 的功能。 15 | 16 | **`See`** 17 | 18 | [DefineStoreOptionsBase](pinia.DefineStoreOptionsBase.md). 19 | 20 | ## 类型参数 %{#type-parameters}% 21 | 22 | | 名称 | 类型 | 23 | | :--- | :--------------------------------------------------- | 24 | | `Id` | extends `string` | 25 | | `S` | extends [`StateTree`](../modules/pinia.md#statetree) | 26 | | `G` | `G` | 27 | | `A` | `A` | 28 | 29 | ## 层次结构 %{#hierarchy}% 30 | 31 | - [`DefineStoreOptionsBase`](pinia.DefineStoreOptionsBase.md)<`S`, [`Store`](../modules/pinia.md#store)<`Id`, `S`, `G`, `A`\>\> 32 | 33 | ↳ **`DefineSetupStoreOptions`** 34 | 35 | ## 属性 36 | 37 | ### actions %{#actions}% 38 | 39 | • `Optional` **actions**: `A` 40 | 41 | 提取的 action。由 useStore() 添加。不应该由用户在创建 store 时添加。 42 | 可以在插件中使用,以获得用 setup 函数定义的 store 中的 action 列表。 43 | 注意这个属性一定是会定义的。 44 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "packages/pinia/src/global.d.ts", 4 | "packages/*/src/**/*.ts", 5 | "packages/*/__tests__/**/*.ts" 6 | ], 7 | "exclude": [ 8 | "packages/test-vue-2", 9 | "packages/pinia/__tests__/test-utils.ts", 10 | "packages/pinia/test-dts", 11 | "packages/playground", 12 | "packages/online-playground", 13 | "packages/nuxt" 14 | ], 15 | "compilerOptions": { 16 | "baseUrl": ".", 17 | "rootDir": ".", 18 | "outDir": "dist", 19 | "sourceMap": false, 20 | "noEmit": true, 21 | "paths": { 22 | "@pinia/*": [ 23 | "packages/*/src" 24 | ], 25 | "pinia": [ 26 | "packages/pinia/src" 27 | ] 28 | }, 29 | "target": "esnext", 30 | "module": "esnext", 31 | "moduleResolution": "node", 32 | "allowJs": false, 33 | "skipLibCheck": true, 34 | "noUnusedLocals": true, 35 | "strictNullChecks": true, 36 | "noImplicitAny": true, 37 | "noImplicitThis": true, 38 | "noImplicitReturns": false, 39 | "strict": true, 40 | "isolatedModules": true, 41 | "experimentalDecorators": true, 42 | "resolveJsonModule": true, 43 | "esModuleInterop": true, 44 | "removeComments": false, 45 | "jsx": "preserve", 46 | "lib": [ 47 | "esnext", 48 | "dom" 49 | ], 50 | "types": [ 51 | "vitest", 52 | "node", 53 | "vite/client" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/components/HomeSponsors.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 39 | 40 | 53 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.Pinia.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / Pinia 8 | 9 | # 接口:Pinia %{#interface-pinia}% 10 | 11 | [pinia](../modules/pinia.md).Pinia 12 | 13 | Every application must own its own pinia to be able to create stores 14 | 15 | ## 层次结构 %{#hierarchy}% 16 | 17 | - **`Pinia`** 18 | 19 | ↳ [`TestingPinia`](pinia_testing.TestingPinia.md) 20 | 21 | ## 属性 22 | 23 | ### 安装 %{#install}% 24 | 25 | • **install**: (`app`: `App`<`any`\>) => `void` 26 | 27 | #### 类型声明 %{#type-declaration}% 28 | 29 | ▸ (`app`): `void` 30 | 31 | ##### 参数 32 | 33 | | Name | Type | 34 | | :---- | :------------ | 35 | | `app` | `App`<`any`\> | 36 | 37 | ##### 返回值 38 | 39 | `void` 40 | 41 | --- 42 | 43 | ### state %{#state}% 44 | 45 | • **state**: `Ref`<`Record`<`string`, [`StateTree`](../modules/pinia.md#statetree)\>\> 46 | 47 | 根 state 48 | 49 | ## 方法 %{#methods}% 50 | 51 | ### use %{#use}% 52 | 53 | ▸ **use**(`plugin`): [`Pinia`](pinia.Pinia.md) 54 | 55 | 添加 store 插件来扩展每一个 store 56 | 57 | #### 参数 %{#paramters}% 58 | 59 | | Name | Type | Description | 60 | | :------- | :------------------------------------ | :------------------ | 61 | | `plugin` | [`PiniaPlugin`](pinia.PiniaPlugin.md) | store plugin to add | 62 | 63 | #### 返回值 64 | 65 | [`Pinia`](pinia.Pinia.md) 66 | -------------------------------------------------------------------------------- /packages/online-playground/src/icons/Sun.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.SubscriptionCallbackMutationDirect.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / SubscriptionCallbackMutationDirect 8 | 9 | # 接口:SubscriptionCallbackMutationDirect %{#interface-subscriptioncallbackmutationdirect}% 10 | 11 | [pinia](../modules/pinia.md).SubscriptionCallbackMutationDirect 12 | 13 | 当用 `store.someState = newValue` 14 | 或 `store.$state.someState = newValue` 直接改变 store 的状态时, 15 | 传递给订阅回调的上下文。 16 | 17 | ## 层次结构 %{#hierarchy}% 18 | 19 | - [`_SubscriptionCallbackMutationBase`](pinia._SubscriptionCallbackMutationBase.md) 20 | 21 | ↳ **`SubscriptionCallbackMutationDirect`** 22 | 23 | ## 属性 24 | 25 | ### 事件 %{#events}% 26 | 27 | • **events**: `DebuggerEvent` 28 | 29 | 只支持开发环境。不同的 mutation 调用。 30 | 31 | --- 32 | 33 | ### storeId %{#storeid}% 34 | 35 | • **storeId**: `string` 36 | 37 | 执行 mutation 的 store 的 `id` 38 | 39 | #### 继承于 40 | 41 | [\_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[storeId](pinia._SubscriptionCallbackMutationBase.md#storeid) 42 | 43 | --- 44 | 45 | ### 类型 %{#type}% 46 | 47 | • **type**: [`direct`](../enums/pinia.MutationType.md#direct) 48 | 49 | mutation 的类型 50 | 51 | #### 重写 %{#overrides}% 52 | 53 | [\_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[type](pinia._SubscriptionCallbackMutationBase.md#type) 54 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.SubscriptionCallbackMutationPatchFunction.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / SubscriptionCallbackMutationPatchFunction 8 | 9 | # 接口:SubscriptionCallbackMutationPatchFunction %{#interface-subscriptioncallbackmutationpatchfunction}% 10 | 11 | [pinia](../modules/pinia.md).SubscriptionCallbackMutationPatchFunction 12 | 13 | 当 `store.$patch()` 被一个函数调用时, 14 | 传递给订阅回调的上下文。 15 | 16 | ## 层次结构 %{#hierarchy}% 17 | 18 | - [`_SubscriptionCallbackMutationBase`](pinia._SubscriptionCallbackMutationBase.md) 19 | 20 | ↳ **`SubscriptionCallbackMutationPatchFunction`** 21 | 22 | ## 属性 23 | 24 | ### 事件 %{#events}% 25 | 26 | • **events**: `DebuggerEvent`[] 27 | 28 | 仅限开发环境。在回调中所有已完成的 mutation 的数组。 29 | 30 | --- 31 | 32 | ### storeId %{#storeid}% 33 | 34 | • **storeId**: `string` 35 | 36 | 执行 mutation 的 store 的 `id` 37 | 38 | #### 继承于 39 | 40 | [\_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[storeId](pinia._SubscriptionCallbackMutationBase.md#storeid) 41 | 42 | --- 43 | 44 | ### 类型 %{#type}% 45 | 46 | • **type**: [`patchFunction`](../enums/pinia.MutationType.md#patchfunction) 47 | 48 | mutation 的类型 49 | 50 | #### 重写 %{#overrides}% 51 | 52 | [\_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[type](pinia._SubscriptionCallbackMutationBase.md#type) 53 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | 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. 4 | 5 | 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. 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | 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/) 14 | -------------------------------------------------------------------------------- /packages/playground/src/App.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 51 | 52 | 58 | -------------------------------------------------------------------------------- /packages/online-playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { defineConfig, Plugin } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import { execaSync } from 'execa' 6 | 7 | const commit = execaSync('git', ['rev-parse', '--short', 'HEAD']) 8 | 9 | export default defineConfig({ 10 | plugins: [ 11 | vue({ 12 | script: { 13 | defineModel: true, 14 | fs: { 15 | fileExists: fs.existsSync, 16 | readFile: (file) => fs.readFileSync(file, 'utf-8'), 17 | }, 18 | }, 19 | }), 20 | copyPiniaPlugin(), 21 | ], 22 | define: { 23 | __COMMIT__: JSON.stringify(commit), 24 | __VUE_PROD_DEVTOOLS__: JSON.stringify(true), 25 | }, 26 | optimizeDeps: { 27 | exclude: ['@vue/repl'], 28 | }, 29 | }) 30 | 31 | function copyPiniaPlugin(): Plugin { 32 | return { 33 | name: 'copy-pinia', 34 | generateBundle() { 35 | const copyFile = (file: string) => { 36 | const filePath = path.resolve(__dirname, file) 37 | const basename = path.basename(file) 38 | if (!fs.existsSync(filePath)) { 39 | throw new Error(`${basename} not built. ` + `Run "nr build`) 40 | } 41 | this.emitFile({ 42 | type: 'asset', 43 | fileName: basename, 44 | source: fs.readFileSync(filePath, 'utf-8'), 45 | }) 46 | } 47 | 48 | copyFile(`../pinia/dist/pinia.esm-browser.js`) 49 | }, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/pinia/__tests__/rootState.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | import { createPinia, defineStore } from '../src' 3 | import { mockWarn } from './vitest-mock-warn' 4 | 5 | describe('Root State', () => { 6 | mockWarn() 7 | const useA = defineStore('a', { 8 | state: () => ({ a: 'a' }), 9 | }) 10 | 11 | const useB = defineStore('b', { 12 | state: () => ({ b: 'b' }), 13 | }) 14 | 15 | it('warns if creating a store without a pinia', () => { 16 | expect(() => useA()).toThrowError(/there was no active Pinia/) 17 | }) 18 | 19 | it('works with no stores', () => { 20 | expect(createPinia().state.value).toEqual({}) 21 | }) 22 | 23 | it('retrieves the root state of one store', () => { 24 | const pinia = createPinia() 25 | useA(pinia) 26 | expect(pinia.state.value).toEqual({ 27 | a: { a: 'a' }, 28 | }) 29 | }) 30 | 31 | it('does not mix up different applications', () => { 32 | const pinia1 = createPinia() 33 | const pinia2 = createPinia() 34 | useA(pinia1) 35 | useB(pinia2) 36 | expect(pinia1.state.value).toEqual({ 37 | a: { a: 'a' }, 38 | }) 39 | expect(pinia2.state.value).toEqual({ 40 | b: { b: 'b' }, 41 | }) 42 | }) 43 | 44 | it('can hold multiple stores', () => { 45 | const pinia1 = createPinia() 46 | useA(pinia1) 47 | useB(pinia1) 48 | expect(pinia1.state.value).toEqual({ 49 | a: { a: 'a' }, 50 | b: { b: 'b' }, 51 | }) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /packages/testing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pinia/testing", 3 | "version": "1.0.3", 4 | "description": "Testing module for Pinia", 5 | "keywords": [ 6 | "vue", 7 | "vuex", 8 | "store", 9 | "pinia", 10 | "tests", 11 | "mock", 12 | "testing" 13 | ], 14 | "homepage": "https://pinia.vuejs.org/cookbook/testing.html", 15 | "bugs": { 16 | "url": "https://github.com/vuejs/pinia/issues" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/vuejs/pinia.git" 21 | }, 22 | "funding": "https://github.com/sponsors/posva", 23 | "license": "MIT", 24 | "author": { 25 | "name": "Eduardo San Martin Morote", 26 | "email": "posva13@gmail.com" 27 | }, 28 | "sideEffects": false, 29 | "exports": { 30 | "types": "./dist/index.d.ts", 31 | "require": "./dist/index.js", 32 | "import": "./dist/index.mjs" 33 | }, 34 | "main": "./dist/index.js", 35 | "module": "./dist/index.mjs", 36 | "types": "./dist/index.d.ts", 37 | "files": [ 38 | "dist/*.js", 39 | "dist/*.mjs", 40 | "dist/*.d.ts" 41 | ], 42 | "scripts": { 43 | "build": "tsup", 44 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path . -l @pinia/testing -r 1" 45 | }, 46 | "devDependencies": { 47 | "pinia": "workspace:*", 48 | "tsup": "^8.5.0" 49 | }, 50 | "peerDependencies": { 51 | "pinia": ">=3.0.4" 52 | }, 53 | "publishConfig": { 54 | "access": "public" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/pinia/test-dts/onAction.test-d.ts: -------------------------------------------------------------------------------- 1 | import { defineStore, expectType } from './' 2 | 3 | const useStore = defineStore('main', { 4 | state: () => ({ 5 | user: 'Eduardo', 6 | }), 7 | actions: { 8 | direct(name: string) { 9 | this.user = name 10 | }, 11 | patchObject(user: string) { 12 | this.$patch({ user }) 13 | }, 14 | patchFn(name: string) { 15 | this.$patch((state) => { 16 | state.user = name 17 | }) 18 | }, 19 | async asyncUpperName() { 20 | return this.user.toUpperCase() 21 | }, 22 | upperName() { 23 | return this.user.toUpperCase() 24 | }, 25 | throws(e: any) { 26 | throw e 27 | }, 28 | async rejects(e: any) { 29 | throw e 30 | }, 31 | }, 32 | }) 33 | 34 | let store = useStore() 35 | 36 | store.$onAction((context) => { 37 | expectType<{ user: string }>(context.store.$state) 38 | expectType<(name: string) => void>(context.store.direct) 39 | 40 | if (context.name === 'upperName') { 41 | expectType<[]>(context.args) 42 | context.after((ret) => { 43 | expectType(ret) 44 | }) 45 | } else if (context.name === 'asyncUpperName') { 46 | context.after((ret) => { 47 | expectType(ret) 48 | }) 49 | } else if (context.name === 'throws') { 50 | context.after((ret) => { 51 | expectType(ret) 52 | }) 53 | expectType<[any]>(context.args) 54 | } else if (context.name === 'direct') { 55 | expectType<[string]>(context.args) 56 | } 57 | }) 58 | -------------------------------------------------------------------------------- /packages/pinia/test-dts/storeToRefs.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType, defineStore, storeToRefs } from './' 2 | import { computed, ComputedRef, ref, Ref, shallowRef } from 'vue' 3 | 4 | const useOptionsStore = defineStore('main', { 5 | state: () => ({ 6 | n: 0, 7 | ref: ref({ 8 | n: 0, 9 | ref: ref(0), 10 | }), 11 | shallowRef: shallowRef({ 12 | n: 0, 13 | ref: ref(0), 14 | }), 15 | }), 16 | }) 17 | 18 | const optionsStore = useOptionsStore() 19 | const optionsRefs = storeToRefs(optionsStore) 20 | 21 | expectType>(optionsRefs.n) 22 | expectType>(optionsRefs.ref) 23 | expectType }>>(optionsRefs.shallowRef) 24 | 25 | const useSetupStore = defineStore('main', () => { 26 | return { 27 | n: ref(0), 28 | ref: ref({ 29 | n: 0, 30 | ref: ref(0), 31 | }), 32 | shallowRef: shallowRef({ 33 | n: 0, 34 | ref: ref(0), 35 | }), 36 | // https://github.com/vuejs/pinia/issues/2658 37 | accidentallyNestedComputed: computed(() => computed(() => 'a')), 38 | computedRef: computed(() => ref(0)), 39 | } 40 | }) 41 | 42 | const setupStore = useSetupStore() 43 | const setupRefs = storeToRefs(setupStore) 44 | 45 | expectType>(setupRefs.n) 46 | expectType>(setupRefs.ref) 47 | expectType }>>(setupRefs.shallowRef) 48 | expectType>(setupRefs.accidentallyNestedComputed.value) 49 | expectType>(setupRefs.computedRef.value) 50 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/components/VueSchoolLink.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | 26 | 64 | -------------------------------------------------------------------------------- /packages/playground/src/views/CounterStore.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 59 | -------------------------------------------------------------------------------- /packages/nuxt/README.md: -------------------------------------------------------------------------------- 1 | # `@pinia/nuxt` 2 | 3 | > Nuxt module for Pinia 4 | 5 | ## Automatic Installation 6 | 7 | Use `nuxi` to automatically add this module to your Nuxt project: 8 | 9 | ```shell 10 | npx nuxi@latest module add pinia 11 | ``` 12 | 13 | ## Manual Installation 14 | 15 | Add dependencies to your Nuxt project: 16 | 17 | ```shell 18 | npm i pinia @pinia/nuxt 19 | ``` 20 | 21 | Enable the `@pinia/nuxt` module in `nuxt.config.ts`: 22 | 23 | ```js 24 | export default defineNuxtConfig({ 25 | modules: ['@pinia/nuxt'], 26 | }) 27 | ``` 28 | 29 | ## Configuring the Module 30 | 31 | By default, this module adds `stores` folder to auto imports, in which you can organize code related to Pinia stores in one place. 32 | > [!TIP] 33 | > In the new directory structure introduced since Nuxt 4, this directory is `app/stores`. 34 | 35 | You can customize this behaviour using the `pinia` property in `nuxt.config.ts`: 36 | 37 | ```js 38 | export default defineNuxtConfig({ 39 | modules: ['@pinia/nuxt'], 40 | // configure the module using `pinia` property 41 | pinia: { 42 | /** 43 | * Automatically add stores dirs to the auto imports. This is the same as 44 | * directly adding the dirs to the `imports.dirs` option. If you want to 45 | * also import nested stores, you can use the glob pattern `./stores/**` 46 | * (on Nuxt 3) or `app/stores/**` (on Nuxt 4+) 47 | * 48 | * @default `['stores']` 49 | */ 50 | storesDirs: [] 51 | } 52 | }) 53 | ``` 54 | 55 | ## License 56 | 57 | [MIT](http://opensource.org/licenses/MIT) 58 | -------------------------------------------------------------------------------- /packages/playground/src/stores/nasa.ts: -------------------------------------------------------------------------------- 1 | import useSWRV from 'swrv' 2 | import { ref } from 'vue' 3 | import { acceptHMRUpdate, defineStore } from 'pinia' 4 | import { getNASAPOD } from '../api/nasa' 5 | 6 | export const useNasaStore = defineStore('nasa-pod-swrv', ({ action }) => { 7 | // can't go past today 8 | const today = new Date().toISOString().slice(0, 10) 9 | 10 | // const currentDate = computed({ 11 | // get: () => image.value?.date || today, 12 | // set: (date) => { 13 | // // TODO: router push 14 | // } 15 | // }) 16 | 17 | const currentDate = ref(today) 18 | 19 | const { 20 | data: image, 21 | error, 22 | isValidating, 23 | } = useSWRV( 24 | () => `nasa-pod-${currentDate.value}`, 25 | () => getNASAPOD(currentDate.value), 26 | { 27 | // refreshInterval: 0, 28 | // ttl: 0, 29 | revalidateOnFocus: false, 30 | } 31 | ) 32 | 33 | const incrementDay = action((date: string) => { 34 | const from = new Date(date).getTime() 35 | 36 | currentDate.value = new Date(from + 1000 * 60 * 60 * 24) 37 | .toISOString() 38 | .slice(0, 10) 39 | }) 40 | 41 | const decrementDay = action((date: string) => { 42 | const from = new Date(date).getTime() 43 | 44 | currentDate.value = new Date(from - 1000 * 60 * 60 * 24) 45 | .toISOString() 46 | .slice(0, 10) 47 | }) 48 | 49 | return { image, currentDate, incrementDay, decrementDay, error, isValidating } 50 | }) 51 | 52 | if (import.meta.hot) { 53 | import.meta.hot.accept(acceptHMRUpdate(useNasaStore, import.meta.hot)) 54 | } 55 | -------------------------------------------------------------------------------- /packages/playground/src/stores/jokes.ts: -------------------------------------------------------------------------------- 1 | import { ref, unref } from 'vue' 2 | import { acceptHMRUpdate, defineStore } from 'pinia' 3 | import { getRandomJoke, Joke } from '../api/jokes' 4 | 5 | export const useJokes = defineStore('jokes', { 6 | state: () => ({ 7 | current: null as null | Joke, 8 | jokes: [] as Joke[], 9 | // hello: true, 10 | }), 11 | actions: { 12 | async fetchJoke() { 13 | if ( 14 | this.current && 15 | // if the request below fails, avoid adding it twice 16 | !this.jokes.includes(this.current) 17 | ) { 18 | this.jokes.push(this.current) 19 | } 20 | 21 | // NOTE: Avoid patching an object because it's recursive 22 | // this.$patch({ current: await getRandomJoke() }) 23 | this.current = await getRandomJoke() 24 | }, 25 | }, 26 | }) 27 | 28 | export const useJokesSetup = defineStore('jokes-setup', () => { 29 | const current = ref(null) 30 | const history = ref([]) 31 | 32 | async function fetchJoke() { 33 | const cur = unref(current.value) 34 | if ( 35 | cur && 36 | // if the request below fails, avoid adding it twice 37 | !history.value.find((joke) => joke.id === cur.id) 38 | ) { 39 | history.value.push(cur) 40 | } 41 | 42 | current.value = await getRandomJoke() 43 | return current.value 44 | } 45 | 46 | return { current, history, fetchJoke } 47 | }) 48 | 49 | if (import.meta.hot) { 50 | import.meta.hot.accept(acceptHMRUpdate(useJokes, import.meta.hot)) 51 | // import.meta.hot.accept(acceptHMRUpdate(useJokesSetup, import.meta.hot)) 52 | } 53 | -------------------------------------------------------------------------------- /packages/playground/src/views/CounterSetupStore.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 61 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.SubscriptionCallbackMutationPatchObject.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / SubscriptionCallbackMutationPatchObject 8 | 9 | # 接口:SubscriptionCallbackMutationPatchObject %{#interface-subscriptioncallbackmutationpatchobject-s}% 10 | 11 | [pinia](../modules/pinia.md).SubscriptionCallbackMutationPatchObject 12 | 13 | 当 `store.$patch()` 与一个对象一起被调用时, 14 | 传递给订阅回调的上下文。 15 | 16 | ## 类型参数 %{#type-parameters}% 17 | 18 | | 名称 | 19 | | :--- | 20 | | `S` | 21 | 22 | ## 层次结构 %{#hierarchy}% 23 | 24 | - [`_SubscriptionCallbackMutationBase`](pinia._SubscriptionCallbackMutationBase.md) 25 | 26 | ↳ **`SubscriptionCallbackMutationPatchObject`** 27 | 28 | ## 属性 29 | 30 | ### 事件 %{#events}% 31 | 32 | • **events**: `DebuggerEvent`[] 33 | 34 | 仅限 DEV, patch 调用的数组。 35 | 36 | --- 37 | 38 | ### payload %{#payload}% 39 | 40 | • **payload**: [`_DeepPartial`](../modules/pinia.md#_deeppartial)<`S`\> 41 | 42 | 传递给 `store.$patch()` 的对象 43 | 44 | --- 45 | 46 | ### storeId %{#storeid}% 47 | 48 | • **storeId**: `string` 49 | 50 | 执行 mutation 的 store 的 `id` 51 | 52 | #### 继承于 53 | 54 | [\_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[storeId](pinia._SubscriptionCallbackMutationBase.md#storeid) 55 | 56 | --- 57 | 58 | ### 类型 %{#type}% 59 | 60 | • **type**: [`patchObject`](../enums/pinia.MutationType.md#patchobject) 61 | 62 | mutation 的类型 63 | 64 | #### 重写 %{#overrides}% 65 | 66 | [\_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[type](pinia._SubscriptionCallbackMutationBase.md#type) 67 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/components/VueMasteryHomeLink.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.PiniaCustomProperties.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / PiniaCustomProperties 8 | 9 | # 接口:PiniaCustomProperties %{#interface-piniacustomproperties-id-s-g-a}% 10 | 11 | [pinia](../modules/pinia.md).PiniaCustomProperties 12 | 13 | 当用户通过插件添加属性时,接口可被扩展。 14 | 15 | ## 类型参数 %{#type-parameters}% 16 | 17 | | 名称 | 类型 | 18 | | :--- | :-------------------------------------------------------------------------------------------------- | 19 | | `Id` | extends `string` = `string` | 20 | | `S` | extends [`StateTree`](../modules/pinia.md#statetree) = [`StateTree`](../modules/pinia.md#statetree) | 21 | | `G` | [`_GettersTree`](../modules/pinia.md#_getterstree)<`S`\> | 22 | | `A` | [`_ActionsTree`](../modules/pinia.md#_actionstree) | 23 | 24 | ## Accessors %{#accessors}% 25 | 26 | ### route %{#route}% 27 | 28 | • `get` **route**(): `RouteLocationNormalized` 29 | 30 | #### 返回值 31 | 32 | `RouteLocationNormalized` 33 | 34 | • `set` **route**(`value`): `void` 35 | 36 | #### 参数 37 | 38 | | Name | Type | 39 | | :------ | :------------------------------------------------------------------------- | 40 | | `value` | `RouteLocationNormalizedLoaded` \| `Ref`<`RouteLocationNormalizedLoaded`\> | 41 | 42 | #### 返回值 43 | 44 | `void` 45 | -------------------------------------------------------------------------------- /packages/playground/src/stores/counterSetup.ts: -------------------------------------------------------------------------------- 1 | import { computed, toRefs, reactive, inject } from 'vue' 2 | import { acceptHMRUpdate, defineStore } from 'pinia' 3 | 4 | const delay = (t: number) => new Promise((r) => setTimeout(r, t)) 5 | 6 | export const useCounter = defineStore('counter-setup', () => { 7 | const state = reactive({ 8 | n: 0, 9 | incrementedTimes: 0, 10 | decrementedTimes: 0, 11 | numbers: [] as number[], 12 | }) 13 | 14 | const injected = inject('injected', 'fallback value') 15 | console.log('injected (should be global)', injected) 16 | 17 | const double = computed(() => state.n * 2) 18 | 19 | function increment(amount = 1) { 20 | if (typeof amount !== 'number') { 21 | amount = 1 22 | } 23 | state.incrementedTimes++ 24 | state.n += amount 25 | } 26 | 27 | function changeMe() { 28 | console.log('change me to test HMR') 29 | } 30 | 31 | async function fail() { 32 | const n = state.n 33 | await delay(1000) 34 | state.numbers.push(n) 35 | await delay(1000) 36 | if (state.n !== n) { 37 | throw new Error('Someone changed n!') 38 | } 39 | 40 | return n 41 | } 42 | 43 | async function decrementToZero(interval: number = 300) { 44 | if (state.n <= 0) return 45 | 46 | while (state.n > 0) { 47 | state.n -= 1 48 | state.decrementedTimes += 1 49 | await delay(interval) 50 | } 51 | } 52 | 53 | return { 54 | ...toRefs(state), 55 | double, 56 | increment, 57 | fail, 58 | changeMe, 59 | decrementToZero, 60 | } 61 | }) 62 | 63 | if (import.meta.hot) { 64 | import.meta.hot.accept(acceptHMRUpdate(useCounter, import.meta.hot)) 65 | } 66 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.PiniaPluginContext.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / PiniaPluginContext 8 | 9 | # 接口:PiniaPluginContext %{#interface-piniaplugincontext-id-s-g-a}% 10 | 11 | [pinia](../modules/pinia.md).PiniaPluginContext 12 | 13 | 传递给 Pinia 插件的上下文参数。 14 | 15 | ## 类型参数 %{#type-parameters}% 16 | 17 | | Name | Type | 18 | | :--- | :-------------------------------------------------------------------------------------------------- | 19 | | `Id` | extends `string` = `string` | 20 | | `S` | extends [`StateTree`](../modules/pinia.md#statetree) = [`StateTree`](../modules/pinia.md#statetree) | 21 | | `G` | [`_GettersTree`](../modules/pinia.md#_getterstree)<`S`\> | 22 | | `A` | [`_ActionsTree`](../modules/pinia.md#_actionstree) | 23 | 24 | ## 属性 25 | 26 | ### app %{#app}% 27 | 28 | • **app**: `App`<`any`\> 29 | 30 | 用 `Vue.createApp()`创建的当前应用。 31 | 32 | --- 33 | 34 | ### options %{#options}% 35 | 36 | • **options**: [`DefineStoreOptionsInPlugin`](pinia.DefineStoreOptionsInPlugin.md)<`Id`, `S`, `G`, `A`\> 37 | 38 | 调用 `defineStore()` 时定义 store 的初始选项。 39 | 40 | --- 41 | 42 | ### pinia %{#pinia}% 43 | 44 | • **pinia**: [`Pinia`](pinia.Pinia.md) 45 | 46 | pinia 实例 47 | 48 | --- 49 | 50 | ### store %{#store}% 51 | 52 | • **store**: [`Store`](../modules/pinia.md#store)<`Id`, `S`, `G`, `A`\> 53 | 54 | 目前正在扩展的 store 55 | -------------------------------------------------------------------------------- /packages/docs/zh/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | title: Pinia 5 | titleTemplate: The intuitive store for Vue.js 6 | 7 | hero: 8 | name: Pinia 9 | text: "符合直觉的 \nVue.js 状态管理库" 10 | tagline: "类型安全、可扩展性以及模块化设计。\n甚至让你忘记正在使用的是一个状态库。" 11 | image: 12 | src: /logo.svg 13 | alt: Pinia 14 | actions: 15 | - theme: brand 16 | text: 开始使用 17 | link: /zh/introduction 18 | - theme: alt 19 | text: Demo 演示 20 | link: https://stackblitz.com/github/piniajs/example-vue-3-vite 21 | - theme: cta rulekit 22 | text: RuleKit 23 | link: https://rulekit.dev?from=pinia 24 | - theme: cta mastering-pinia 25 | text: ' ' 26 | link: https://masteringpinia.com 27 | - theme: cta vueschool 28 | text: 观看视频介绍 29 | link: https://vueschool.io/lessons/introduction-to-pinia?friend=vuerouter&utm_source=pinia&utm_medium=link&utm_campaign=homepage 30 | - theme: cta vue-mastery 31 | text: 获取 Pinia 速查表 32 | link: https://www.vuemastery.com/pinia?coupon=PINIA-DOCS&via=eduardo 33 | 34 | features: 35 | - title: 💡 所见即所得 36 | details: 与组件类似的 Store。其 API 的设计旨在让你编写出更易组织的 store。 37 | - title: 🔑 类型安全 38 | details: 类型可自动推断,即使在 JavaScript 中亦可为你提供自动补全功能! 39 | - title: ⚙️ 开发工具支持 40 | details: 不管是 Vue 2 还是 Vue 3,支持 Vue devtools 钩子的 Pinia 都能给你更好的开发体验。 41 | - title: 🔌 可扩展性 42 | details: 可通过事务、同步本地存储等方式扩展 Pinia,以响应 store 的变更以及 action。 43 | - title: 🏗 模块化设计 44 | details: 可构建多个 Store 并允许你的打包工具自动拆分它们。 45 | - title: 📦 极致轻量化 46 | details: Pinia 大小只有 1kb 左右,你甚至可能忘记它的存在! 47 | --- 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /packages/playground/src/stores/counter.ts: -------------------------------------------------------------------------------- 1 | import { acceptHMRUpdate, defineStore } from 'pinia' 2 | 3 | const delay = (t: number) => new Promise((r) => setTimeout(r, t)) 4 | 5 | export const useCounter = defineStore('counter', { 6 | state: () => ({ 7 | n: 2, 8 | incrementedTimes: 0, 9 | decrementedTimes: 0, 10 | numbers: [] as number[], 11 | }), 12 | 13 | getters: { 14 | double: (state) => state.n * 2, 15 | }, 16 | 17 | actions: { 18 | increment(amount = 1) { 19 | if (typeof amount !== 'number') { 20 | amount = 1 21 | } 22 | this.incrementedTimes++ 23 | this.n += amount 24 | }, 25 | 26 | changeMe() { 27 | console.log('change me to test HMR') 28 | }, 29 | 30 | async fail() { 31 | const n = this.n 32 | await delay(1000) 33 | this.numbers.push(n) 34 | await delay(1000) 35 | if (this.n !== n) { 36 | throw new Error('Someone changed n!') 37 | } 38 | 39 | return n 40 | }, 41 | 42 | async decrementToZero(interval: number = 300, usePatch = true) { 43 | if (this.n <= 0) return 44 | 45 | while (this.n > 0) { 46 | if (usePatch) { 47 | this.$patch({ 48 | n: this.n - 1, 49 | decrementedTimes: this.decrementedTimes + 1, 50 | }) 51 | // this.$patch(state => { 52 | // state.n-- 53 | // state.decrementedTimes++ 54 | // }) 55 | } else { 56 | this.n -= 1 57 | } 58 | await delay(interval) 59 | } 60 | }, 61 | }, 62 | }) 63 | 64 | if (import.meta.hot) { 65 | import.meta.hot.accept(acceptHMRUpdate(useCounter, import.meta.hot)) 66 | } 67 | -------------------------------------------------------------------------------- /packages/online-playground/public/logo-ts.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue' 2 | import { type Theme } from 'vitepress' 3 | import DefaultTheme from 'vitepress/theme' 4 | import AsideSponsors from './components/AsideSponsors.vue' 5 | // import AsideSponsors from './components/AsideSponsors.vue' 6 | import TranslationStatus from 'vitepress-translation-helper/ui/TranslationStatus.vue' 7 | // import HomeSponsors from './components/HomeSponsors.vue' 8 | import PiniaLogo from './components/PiniaLogo.vue' 9 | import './styles/vars.css' 10 | import './styles/playground-links.css' 11 | import VueSchoolLink from './components/VueSchoolLink.vue' 12 | import VueMasteryLogoLink from './components/VueMasteryLogoLink.vue' 13 | import MasteringPiniaLink from './components/MasteringPiniaLink.vue' 14 | import RuleKitLink from './components/RuleKitLink.vue' 15 | import status from '../translation-status.json' 16 | 17 | const i18nLabels = { 18 | zh: '该翻译已同步到了 ${date} 的版本,其对应的 commit hash 是 ${hash}。', 19 | } 20 | 21 | const theme: Theme = { 22 | ...DefaultTheme, 23 | Layout() { 24 | return h(DefaultTheme.Layout, null, { 25 | 'home-hero-image': () => h('div', { class: 'image-src' }, h(PiniaLogo)), 26 | // 'home-features-after': () => h(HomeSponsors), 27 | 'aside-ads-before': () => h(AsideSponsors), 28 | // 'layout-top': () => h(VuejsdeConfBanner), 29 | 'doc-before': () => h(TranslationStatus, { status, i18nLabels }), 30 | }) 31 | }, 32 | 33 | enhanceApp({ app }) { 34 | app.component('VueSchoolLink', VueSchoolLink) 35 | app.component('VueMasteryLogoLink', VueMasteryLogoLink) 36 | app.component('MasteringPiniaLink', MasteringPiniaLink) 37 | app.component('RuleKitLink', RuleKitLink) 38 | }, 39 | } 40 | 41 | export default theme 42 | -------------------------------------------------------------------------------- /packages/pinia/__tests__/combinedStores.spec.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, it, expect } from 'vitest' 2 | import { computed, ref } from 'vue' 3 | import { createPinia, defineStore, setActivePinia } from '../src' 4 | 5 | describe('Composing stores', () => { 6 | beforeEach(() => { 7 | setActivePinia(createPinia()) 8 | }) 9 | 10 | function useCounter() { 11 | const n = ref(0) 12 | const double = computed(() => n.value) 13 | 14 | function increment(amount = 1) { 15 | n.value += amount 16 | } 17 | 18 | return { n, double, increment } 19 | } 20 | 21 | const useStoreA = defineStore('a', () => { 22 | const { n, double, increment } = useCounter() 23 | const b = useStoreB() 24 | 25 | function changeB() { 26 | b.n++ 27 | } 28 | 29 | function getInnerB() { 30 | return b 31 | } 32 | 33 | return { n, increment, double, changeB, getInnerB } 34 | }) 35 | 36 | const useStoreB = defineStore('b', () => { 37 | const { n, double, increment } = useCounter() 38 | const a = useStoreA() 39 | 40 | function changeA() { 41 | a.n++ 42 | } 43 | 44 | function getInnerA() { 45 | return a 46 | } 47 | 48 | return { n, increment, double, changeA, getInnerA } 49 | }) 50 | 51 | it('works', () => { 52 | expect(() => { 53 | useStoreA() 54 | useStoreB() 55 | }).not.toThrow() 56 | }) 57 | 58 | it('they can use each other', () => { 59 | const b = useStoreB() 60 | const a = useStoreA() 61 | 62 | expect(a).toBe(b.getInnerA()) 63 | expect(b).toBe(a.getInnerB()) 64 | 65 | expect(a.n).toBe(0) 66 | b.changeA() 67 | expect(a.n).toBe(1) 68 | 69 | expect(b.n).toBe(0) 70 | a.changeB() 71 | expect(b.n).toBe(1) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /packages/docs/zh/cookbook/vscode-snippets.md: -------------------------------------------------------------------------------- 1 | # VS Code 代码片段 2 | 3 | 4 | 5 | 有一些代码片段可以让你在 VS Code 中更轻松地使用 Pinia。 6 | 7 | 通过 P / P 然后输入 `Snippets: Configure User Snippets` 就可以管理用户代码片段。 8 | 9 | ```json 10 | { 11 | "Pinia Options Store Boilerplate": { 12 | "scope": "javascript,typescript", 13 | "prefix": "pinia-options", 14 | "body": [ 15 | "import { defineStore, acceptHMRUpdate } from 'pinia'", 16 | "", 17 | "export const use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store = defineStore('$TM_FILENAME_BASE', {", 18 | " state: () => ({", 19 | " $0", 20 | " }),", 21 | " getters: {},", 22 | " actions: {},", 23 | "})", 24 | "", 25 | "if (import.meta.hot) {", 26 | " import.meta.hot.accept(acceptHMRUpdate(use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store, import.meta.hot))", 27 | "}", 28 | "" 29 | ], 30 | "description": "Bootstrap the code needed for a Vue.js Pinia Options Store file" 31 | }, 32 | "Pinia Setup Store Boilerplate": { 33 | "scope": "javascript,typescript", 34 | "prefix": "pinia-setup", 35 | "body": [ 36 | "import { defineStore, acceptHMRUpdate } from 'pinia'", 37 | "", 38 | "export const use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store = defineStore('$TM_FILENAME_BASE', () => {", 39 | " $0", 40 | " return {}", 41 | "})", 42 | "", 43 | "if (import.meta.hot) {", 44 | " import.meta.hot.accept(acceptHMRUpdate(use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store, import.meta.hot))", 45 | "}", 46 | "" 47 | ], 48 | "description": "Bootstrap the code needed for a Vue.js Pinia Setup Store file" 49 | } 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /packages/nuxt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pinia/nuxt", 3 | "version": "0.11.3", 4 | "description": "Nuxt Module for pinia", 5 | "keywords": [ 6 | "pinia", 7 | "nuxt", 8 | "vue", 9 | "vuex", 10 | "store" 11 | ], 12 | "homepage": "https://pinia.vuejs.org/ssr/nuxt.html", 13 | "bugs": { 14 | "url": "https://github.com/vuejs/pinia/issues" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/vuejs/pinia.git" 19 | }, 20 | "funding": "https://github.com/sponsors/posva", 21 | "license": "MIT", 22 | "author": { 23 | "name": "Eduardo San Martin Morote", 24 | "email": "posva13@gmail.com" 25 | }, 26 | "sideEffects": false, 27 | "type": "module", 28 | "exports": { 29 | ".": "./dist/module.mjs" 30 | }, 31 | "main": "./dist/module.mjs", 32 | "types": "./dist/module.d.mts", 33 | "files": [ 34 | "dist" 35 | ], 36 | "scripts": { 37 | "build": "pnpm run dev:prepare && nuxt-module-build build", 38 | "dev": "nuxi dev playground", 39 | "dev:build": "nuxi build playground", 40 | "dev:prepare": "nuxt-module-build build --stub . && nuxi prepare playground", 41 | "test:types": "pnpm dev:prepare && nuxi typecheck", 42 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path . -l @pinia/nuxt -r 1" 43 | }, 44 | "dependencies": { 45 | "@nuxt/kit": "^4.2.0" 46 | }, 47 | "peerDependencies": { 48 | "pinia": "workspace:^" 49 | }, 50 | "devDependencies": { 51 | "@nuxt/module-builder": "1.0.2", 52 | "@nuxt/schema": "^4.2.0", 53 | "@nuxt/test-utils": "^3.20.1", 54 | "nuxt": "^4.2.0", 55 | "pinia": "workspace:^", 56 | "typescript": "^5.9.3", 57 | "vue-tsc": "^3.1.3" 58 | }, 59 | "publishConfig": { 60 | "access": "public" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia_testing.TestingPinia.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [@pinia/testing](../modules/pinia_testing.md) / TestingPinia 8 | 9 | # 接口:TestingPinia %{#interface-testingpinia}% 10 | 11 | [@pinia/testing](../modules/pinia_testing.md).TestingPinia 12 | 13 | 专门为测试设计的 Pinia 实例。 14 | 用测试中的特定属性扩展普通的 [Pinia](pinia.Pinia.md) 实例。 15 | 16 | ## 层次结构 %{#hierarchy}% 17 | 18 | - [`Pinia`](pinia.Pinia.md) 19 | 20 | ↳ **`TestingPinia`** 21 | 22 | ## 属性 23 | 24 | ### app %{#app}% 25 | 26 | • **app**: `App`<`any`\> 27 | 28 | Pinia 使用的应用 29 | 30 | --- 31 | 32 | ### 安装 %{#install}% 33 | 34 | • **install**: (`app`: `App`<`any`\>) => `void` 35 | 36 | #### 类型声明 %{#type-declaration}% 37 | 38 | ▸ (`app`): `void` 39 | 40 | ##### 参数 41 | 42 | | 名称 | 类型 | 43 | | :---- | :------------ | 44 | | `app` | `App`<`any`\> | 45 | 46 | ##### 返回值 47 | 48 | `void` 49 | 50 | #### 继承于 51 | 52 | [Pinia](pinia.Pinia.md).[install](pinia.Pinia.md#install) 53 | 54 | --- 55 | 56 | ### state %{#state}% 57 | 58 | • **state**: `Ref`<`Record`<`string`, [`StateTree`](../modules/pinia.md#statetree)\>\> 59 | 60 | 根 state 61 | 62 | #### 继承于 63 | 64 | [Pinia](pinia.Pinia.md).[state](pinia.Pinia.md#state) 65 | 66 | ## 方法 %{#methods}% 67 | 68 | ### use %{#use}% 69 | 70 | ▸ **use**(`plugin`): [`Pinia`](pinia.Pinia.md) 71 | 72 | 增加了一个 store 插件来扩展每个 store 73 | 74 | #### 参数 75 | 76 | | 名称 | 类型 | 描述 | 77 | | :------- | :------------------------------------ | :------------------ | 78 | | `plugin` | [`PiniaPlugin`](pinia.PiniaPlugin.md) | store plugin to add | 79 | 80 | #### 返回值 81 | 82 | [`Pinia`](pinia.Pinia.md) 83 | 84 | #### 继承于 85 | 86 | [Pinia](pinia.Pinia.md).[use](pinia.Pinia.md#use) 87 | -------------------------------------------------------------------------------- /packages/docs/cookbook/vscode-snippets.md: -------------------------------------------------------------------------------- 1 | # VS Code Snippets 2 | 3 | 4 | 5 | These are some snippets that I use in VS Code to make my life easier. 6 | 7 | Manage user snippets with ⇧ Shift+⌘ Command+P / ⇧ Shift+⌃ Control+P and then `Snippets: Configure User Snippets`. 8 | 9 | ```json 10 | { 11 | "Pinia Options Store Boilerplate": { 12 | "scope": "javascript,typescript", 13 | "prefix": "pinia-options", 14 | "body": [ 15 | "import { defineStore, acceptHMRUpdate } from 'pinia'", 16 | "", 17 | "export const use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store = defineStore('$TM_FILENAME_BASE', {", 18 | " state: () => ({", 19 | " $0", 20 | " }),", 21 | " getters: {},", 22 | " actions: {},", 23 | "})", 24 | "", 25 | "if (import.meta.hot) {", 26 | " import.meta.hot.accept(acceptHMRUpdate(use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store, import.meta.hot))", 27 | "}", 28 | "" 29 | ], 30 | "description": "Bootstrap the code needed for a Vue.js Pinia Options Store file" 31 | }, 32 | "Pinia Setup Store Boilerplate": { 33 | "scope": "javascript,typescript", 34 | "prefix": "pinia-setup", 35 | "body": [ 36 | "import { defineStore, acceptHMRUpdate } from 'pinia'", 37 | "", 38 | "export const use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store = defineStore('$TM_FILENAME_BASE', () => {", 39 | " $0", 40 | " return {}", 41 | "})", 42 | "", 43 | "if (import.meta.hot) {", 44 | " import.meta.hot.accept(acceptHMRUpdate(use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store, import.meta.hot))", 45 | "}", 46 | "" 47 | ], 48 | "description": "Bootstrap the code needed for a Vue.js Pinia Setup Store file" 49 | } 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /packages/playground/src/stores/nasa-pod.ts: -------------------------------------------------------------------------------- 1 | import { ref, watch } from 'vue' 2 | import { acceptHMRUpdate, defineStore } from 'pinia' 3 | import { getNASAPOD } from '../api/nasa' 4 | import { useCachedRequest } from '../composables/useCachedRequest' 5 | 6 | export const useNasaPOD = defineStore('nasa-pod', () => { 7 | // can't go past today 8 | const today = new Date().toISOString().slice(0, 10) 9 | 10 | const currentDate = ref(today) 11 | 12 | // const image = ref() 13 | // const error = ref() 14 | // const isLoading = ref(false) 15 | 16 | watch(currentDate, fetchPOD) 17 | 18 | const { 19 | data: image, 20 | error, 21 | isLoading, 22 | isReady, 23 | } = useCachedRequest(currentDate, getNASAPOD) 24 | 25 | function fetchPOD(date: string) { 26 | error.value = undefined 27 | isLoading.value = true 28 | 29 | return getNASAPOD(date) 30 | .then((imageData) => { 31 | image.value = imageData 32 | }) 33 | .catch((err) => { 34 | error.value = err 35 | }) 36 | .finally(() => { 37 | isLoading.value = false 38 | }) 39 | } 40 | 41 | function incrementDay(date: string) { 42 | const from = new Date(date).getTime() 43 | 44 | currentDate.value = new Date(from + 1000 * 60 * 60 * 24) 45 | .toISOString() 46 | .slice(0, 10) 47 | } 48 | 49 | function decrementDay(date: string) { 50 | const from = new Date(date).getTime() 51 | 52 | currentDate.value = new Date(from - 1000 * 60 * 60 * 24) 53 | .toISOString() 54 | .slice(0, 10) 55 | } 56 | 57 | return { 58 | image, 59 | currentDate, 60 | incrementDay, 61 | decrementDay, 62 | error, 63 | isLoading, 64 | isReady, 65 | } 66 | }) 67 | 68 | if (import.meta.hot) { 69 | import.meta.hot.accept(acceptHMRUpdate(useNasaPOD, import.meta.hot)) 70 | } 71 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.StoreDefinition.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / StoreDefinition 8 | 9 | # 接口:StoreDefinition %{#interface-storedefinition-id-s-g-a}% 10 | 11 | [pinia](../modules/pinia.md).StoreDefinition 12 | 13 | ## 类型参数 %{#type-parameters}% 14 | 15 | | 名称 | 类型 | 16 | | :--- | :-------------------------------------------------------------------------------------------------- | 17 | | `Id` | extends `string` = `string` | 18 | | `S` | extends [`StateTree`](../modules/pinia.md#statetree) = [`StateTree`](../modules/pinia.md#statetree) | 19 | | `G` | [`_GettersTree`](../modules/pinia.md#_getterstree)<`S`\> | 20 | | `A` | [`_ActionsTree`](../modules/pinia.md#_actionstree) | 21 | 22 | ## Callable %{#callable}% 23 | 24 | ### StoreDefinition %{#storedefinition}% 25 | 26 | ▸ **StoreDefinition**(`pinia?`, `hot?`): [`Store`](../modules/pinia.md#store)<`Id`, `S`, `G`, `A`\> 27 | 28 | 返回一个 store,有需要才创建它。 29 | 30 | #### 参数 31 | 32 | | 名称 | 类型 | 描述 | 33 | | :------- | :------------------------------------------------- | :----------------------------------- | 34 | | `pinia?` | `null` \| [`Pinia`](pinia.Pinia.md) | Pinia instance to retrieve the store | 35 | | `hot?` | [`StoreGeneric`](../modules/pinia.md#storegeneric) | dev only hot module replacement | 36 | 37 | #### 返回值 38 | 39 | [`Store`](../modules/pinia.md#store)<`Id`, `S`, `G`, `A`\> 40 | 41 | ## 属性 42 | 43 | ### $id %{#id}% 44 | 45 | • **$id**: `Id` 46 | 47 | store 的 id。供映射辅助函数使用。 48 | -------------------------------------------------------------------------------- /packages/playground/src/views/NasaPODSwrv.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 61 | 62 | 77 | -------------------------------------------------------------------------------- /packages/playground/src/views/Jokes.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 66 | 67 | 83 | -------------------------------------------------------------------------------- /packages/docs/zh/core-concepts/outside-component-usage.md: -------------------------------------------------------------------------------- 1 | # 在组件外使用 store %{#using-a-store-outside-of-a-component}% 2 | 3 | 4 | 5 | 10 | 11 | Pinia store 依靠 `pinia` 实例在所有调用中共享同一个 store 实例。大多数时候,只需调用你定义的 `useStore()` 函数,完全开箱即用。例如,在 `setup()` 中,你不需要再做任何事情。但在组件之外,情况就有点不同了。 12 | 实际上,`useStore()` 给你的 `app` 自动注入了 `pinia` 实例。这意味着,如果 `pinia` 实例不能自动注入,你必须手动提供给 `useStore()` 函数。 13 | 你可以根据不同的应用,以不同的方式解决这个问题。 14 | 15 | ## 单页面应用 %{#single-page-applications}% 16 | 17 | 如果你不做任何 SSR(服务器端渲染),在用 `app.use(pinia)` 安装 pinia 插件后,对 `useStore()` 的任何调用都会正常执行: 18 | 19 | ```js 20 | import { useUserStore } from '@/stores/user' 21 | import { createPinia } from 'pinia' 22 | import { createApp } from 'vue' 23 | import App from './App.vue' 24 | 25 | // ❌ 失败,因为它是在创建 pinia 之前被调用的 26 | const userStore = useUserStore() 27 | 28 | const pinia = createPinia() 29 | const app = createApp(App) 30 | app.use(pinia) 31 | 32 | // ✅ 成功,因为 pinia 实例现在激活了 33 | const userStore = useUserStore() 34 | ``` 35 | 36 | 为确保 pinia 实例被激活,最简单的方法就是将 `useStore()` 的调用放在 pinia 安装后才会执行的函数中。 37 | 38 | 让我们来看看这个在 Vue Router 的导航守卫中使用 store 的例子。 39 | 40 | ```js 41 | import { createRouter } from 'vue-router' 42 | const router = createRouter({ 43 | // ... 44 | }) 45 | 46 | // ❌ 由于引入顺序的问题,这将失败 47 | const store = useStore() 48 | 49 | router.beforeEach((to, from, next) => { 50 | // 我们想要在这里使用 store 51 | if (store.isLoggedIn) next() 52 | else next('/login') 53 | }) 54 | 55 | router.beforeEach((to) => { 56 | // ✅ 这样做是可行的,因为路由器是在其被安装之后开始导航的, 57 | // 而此时 Pinia 也已经被安装。 58 | const store = useStore() 59 | 60 | if (to.meta.requiresAuth && !store.isLoggedIn) return '/login' 61 | }) 62 | ``` 63 | 64 | ## 服务端渲染应用 %{#ssr-apps}% 65 | 66 | 当处理服务端渲染时,你将必须把 `pinia` 实例传递给 `useStore()`。这可以防止 pinia 在不同的应用实例之间共享全局状态。 67 | 68 | 在[SSR 指南](../ssr/index.md)中有一整节专门讨论这个问题,这里只是一个简短的解释。 69 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/components/VueMasteryLogoLink.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | 25 | -------------------------------------------------------------------------------- /packages/pinia/__tests__/pinia/stores/cart.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from '../../../src' 2 | import { useUserStore } from './user' 3 | 4 | export const useCartStore = defineStore('cart', { 5 | state: () => ({ 6 | id: 2, 7 | rawItems: [] as string[], 8 | }), 9 | getters: { 10 | items: (state) => 11 | state.rawItems.reduce( 12 | (items, item) => { 13 | const existingItem = items.find((it) => it.name === item) 14 | 15 | if (!existingItem) { 16 | items.push({ name: item, amount: 1 }) 17 | } else { 18 | existingItem.amount++ 19 | } 20 | 21 | return items 22 | }, 23 | [] as { name: string; amount: number }[] 24 | ), 25 | }, 26 | 27 | actions: { 28 | addItem(name: string) { 29 | this.rawItems.push(name) 30 | }, 31 | 32 | removeItem(name: string) { 33 | const i = this.rawItems.indexOf(name) 34 | if (i > -1) this.$state.rawItems.splice(i, 1) 35 | }, 36 | 37 | // TODO: use multiple stores 38 | // https://github.com/vuejs/vue-next-internal-discussions/issues/22 39 | async purchaseItems() { 40 | const user = useUserStore() 41 | if (!user.name) return 42 | 43 | // console.log('Purchasing', this.items) 44 | const n = this.items.length 45 | this.rawItems = [] 46 | 47 | return { amount: n, user: user.name } 48 | }, 49 | }, 50 | }) 51 | 52 | export type CartStore = ReturnType 53 | 54 | export function addItem(name: string) { 55 | const store = useCartStore() 56 | store.rawItems.push(name) 57 | } 58 | 59 | export function removeItem(name: string) { 60 | const store = useCartStore() 61 | const i = store.rawItems.indexOf(name) 62 | if (i > -1) store.rawItems.splice(i, 1) 63 | } 64 | 65 | export async function purchaseItems() { 66 | const cart = useCartStore() 67 | const user = useUserStore() 68 | if (!user.name) return 69 | 70 | console.log('Purchasing', cart.items) 71 | const n = cart.items.length 72 | cart.rawItems = [] 73 | 74 | return n 75 | } 76 | -------------------------------------------------------------------------------- /packages/pinia/test-dts/typeHelpers.test-d.ts: -------------------------------------------------------------------------------- 1 | import { ComputedRef, Ref, computed, ref } from 'vue' 2 | import { 3 | StoreDefinition, 4 | SetupStoreDefinition, 5 | StoreState, 6 | StoreGetters, 7 | StoreActions, 8 | defineStore, 9 | expectType, 10 | } from './' 11 | 12 | const useSetupStore = defineStore('main', () => { 13 | const n = ref(0) 14 | 15 | const double = computed(() => n.value * 2) 16 | 17 | function increment(amount = 1) { 18 | n.value += amount 19 | } 20 | 21 | return { n, increment, double } 22 | }) 23 | 24 | const useOptionsStore = defineStore('main', { 25 | state: () => ({ n: 0 }), 26 | getters: { 27 | double: (state) => state.n * 2, 28 | }, 29 | actions: { 30 | increment(amount = 1) { 31 | this.n += amount 32 | }, 33 | }, 34 | }) 35 | 36 | declare function storeActions( 37 | useStore: T 38 | ): StoreActions> 39 | 40 | declare function storeState( 41 | useStore: T 42 | ): StoreState> 43 | 44 | declare function storeGetters( 45 | useStore: T 46 | ): StoreGetters> 47 | 48 | expectType<{ 49 | increment: (amount?: number) => void 50 | }>(storeActions(useSetupStore)) 51 | 52 | expectType<{ n: number }>(storeState(useSetupStore)) 53 | 54 | expectType<{ double: number }>(storeGetters(useSetupStore)) 55 | 56 | expectType<{ 57 | increment: (amount?: number) => void 58 | }>(storeActions(useOptionsStore)) 59 | 60 | expectType<{ n: number }>(storeState(useOptionsStore)) 61 | 62 | expectType<{ double: number }>(storeGetters(useOptionsStore)) 63 | 64 | expectType< 65 | SetupStoreDefinition< 66 | 'a', 67 | { 68 | n: Ref 69 | double: ComputedRef 70 | increment: () => void 71 | } 72 | > 73 | >( 74 | defineStore('a', () => { 75 | const n = ref(0) 76 | const double = computed(() => n.value * 2) 77 | function increment() { 78 | n.value++ 79 | } 80 | return { 81 | double, 82 | increment, 83 | n, 84 | } 85 | }) 86 | ) 87 | -------------------------------------------------------------------------------- /packages/playground/src/views/NasaPOD.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 59 | 60 | 75 | -------------------------------------------------------------------------------- /packages/playground/src/views/swrv.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 71 | 72 | 88 | -------------------------------------------------------------------------------- /packages/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | title: Pinia 5 | titleTemplate: The intuitive store for Vue.js 6 | 7 | hero: 8 | name: Pinia 9 | text: The intuitive store for Vue.js 10 | tagline: Type Safe, Extensible, and Modular by design. Forget you are even using a store. 11 | image: 12 | src: /logo.svg 13 | alt: Pinia 14 | actions: 15 | - theme: brand 16 | text: Get Started 17 | link: /introduction 18 | - theme: alt 19 | text: Demo 20 | link: https://stackblitz.com/github/piniajs/example-vue-3-vite 21 | - theme: cta rulekit 22 | text: RuleKit 23 | link: https://rulekit.dev?from=pinia 24 | - theme: cta mastering-pinia 25 | text: ' ' 26 | link: https://masteringpinia.com 27 | - theme: cta vueschool 28 | text: Watch Video Introduction 29 | link: https://vueschool.io/lessons/introduction-to-pinia?friend=vuerouter&utm_source=pinia&utm_medium=link&utm_campaign=homepage 30 | - theme: cta vue-mastery 31 | text: Get the Pinia Cheat Sheet 32 | link: https://www.vuemastery.com/pinia?coupon=PINIA-DOCS&via=eduardo 33 | 34 | features: 35 | - title: 💡 Intuitive 36 | details: Stores are as familiar as components. API designed to let you write well organized stores. 37 | - title: 🔑 Type Safe 38 | details: Types are inferred, which means stores provide you with autocompletion even in JavaScript! 39 | - title: ⚙️ Devtools support 40 | details: Pinia hooks into Vue devtools to give you an enhanced development experience. 41 | - title: 🔌 Extensible 42 | details: React to store changes and actions to extend Pinia with transactions, local storage synchronization, etc. 43 | - title: 🏗 Modular by design 44 | details: Build multiple stores and let your bundler code split them automatically. 45 | - title: 📦 Extremely light 46 | details: Pinia weighs ~1.5kb, you will forget it's even there! 47 | --- 48 | 49 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /packages/pinia/src/globalExtensions.ts: -------------------------------------------------------------------------------- 1 | import type { Pinia } from './rootStore' 2 | import type { Store, StoreGeneric } from './types' 3 | 4 | // Extensions of Vue types to be appended manually 5 | // https://github.com/microsoft/rushstack/issues/2090 6 | // https://github.com/microsoft/rushstack/issues/1709 7 | 8 | // @ts-ignore: works on Vue 2, fails in Vue 3 9 | declare module 'vue/types/vue' { 10 | interface Vue { 11 | /** 12 | * Currently installed pinia instance. 13 | */ 14 | $pinia: Pinia 15 | 16 | /** 17 | * Cache of stores instantiated by the current instance. Used by map 18 | * helpers. Used internally by Pinia. 19 | * 20 | * @internal 21 | */ 22 | _pStores?: Record 23 | } 24 | } 25 | 26 | // @ts-ignore: works on Vue 2, fails in Vue 3 27 | declare module 'vue/types/options' { 28 | interface ComponentOptions { 29 | /** 30 | * Pinia instance to install in your application. Should be passed to the 31 | * root Vue. 32 | */ 33 | pinia?: Pinia 34 | } 35 | } 36 | 37 | /** 38 | * NOTE: Used to be `@vue/runtime-core` but it break types from time to time. Then, in Vue docs, we started recommending 39 | * to use `vue` instead of `@vue/runtime-core` but that broke others' types so we reverted it. Now, local types do not 40 | * work if we use `@vue/runtime-core` so we are using `vue` again. 41 | */ 42 | // @ts-ignore: works on Vue 3, fails in Vue 2 43 | declare module 'vue' { 44 | // This seems to be needed to not break auto import types based on the order 45 | // https://github.com/vuejs/pinia/pull/2730 46 | interface GlobalComponents {} 47 | interface ComponentCustomProperties { 48 | /** 49 | * Access to the application's Pinia 50 | */ 51 | $pinia: Pinia 52 | 53 | /** 54 | * Cache of stores instantiated by the current instance. Used by devtools to 55 | * list currently used stores. Used internally by Pinia. 56 | * 57 | * @internal 58 | */ 59 | _pStores?: Record 60 | } 61 | } 62 | 63 | // normally this is only needed in .d.ts files 64 | export {} 65 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia.PiniaPlugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [pinia](../modules/pinia.md) / PiniaPlugin 8 | 9 | # 接口:PiniaPlugin %{#interface-piniaplugin}% 10 | 11 | [pinia](../modules/pinia.md).PiniaPlugin 12 | 13 | ## Callable %{#callable}% 14 | 15 | ### PiniaPlugin %{#piniaplugin}% 16 | 17 | ▸ **PiniaPlugin**(`context`): `void` \| `Partial`<[`PiniaCustomProperties`](pinia.PiniaCustomProperties.md)<`string`, [`StateTree`](../modules/pinia.md#statetree), [`_GettersTree`](../modules/pinia.md#_getterstree)<[`StateTree`](../modules/pinia.md#statetree)\>, [`_ActionsTree`](../modules/pinia.md#_actionstree)\> & [`PiniaCustomStateProperties`](pinia.PiniaCustomStateProperties.md)<[`StateTree`](../modules/pinia.md#statetree)\>\> 18 | 19 | 用于扩展每个 store 的插件。返回一个扩展 store 的对象或 20 | 没有返回值。 21 | 22 | #### 参数 23 | 24 | | 名称 | 类型 | 描述 | 25 | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------ | 26 | | `context` | [`PiniaPluginContext`](pinia.PiniaPluginContext.md)<`string`, [`StateTree`](../modules/pinia.md#statetree), [`_GettersTree`](../modules/pinia.md#_getterstree)<[`StateTree`](../modules/pinia.md#statetree)\>, [`_ActionsTree`](../modules/pinia.md#_actionstree)\> | Context | 27 | 28 | #### 返回值 29 | 30 | `void` \| `Partial`<[`PiniaCustomProperties`](pinia.PiniaCustomProperties.md)<`string`, [`StateTree`](../modules/pinia.md#statetree), [`_GettersTree`](../modules/pinia.md#_getterstree)<[`StateTree`](../modules/pinia.md#statetree)\>, [`_ActionsTree`](../modules/pinia.md#_actionstree)\> & [`PiniaCustomStateProperties`](pinia.PiniaCustomStateProperties.md)<[`StateTree`](../modules/pinia.md#statetree)\>\> 31 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/components/sponsors.json: -------------------------------------------------------------------------------- 1 | { 2 | "platinum": [], 3 | "gold": [ 4 | { 5 | "alt": "CodeRabbit", 6 | "href": "https://www.coderabbit.ai/?utm_source=vuerouter&utm_medium=sponsor", 7 | "imgSrcDark": "https://posva-sponsors.pages.dev/logos/coderabbitai-dark.svg", 8 | "imgSrcLight": "https://posva-sponsors.pages.dev/logos/coderabbitai-light.svg" 9 | } 10 | ], 11 | "silver": [ 12 | { 13 | "alt": "Controla", 14 | "href": "https://www.controla.ai/?utm_source=posva", 15 | "imgSrcDark": "https://posva-sponsors.pages.dev/logos/controla-dark.png", 16 | "imgSrcLight": "https://posva-sponsors.pages.dev/logos/controla-light.png" 17 | }, 18 | { 19 | "alt": "VueMastery", 20 | "href": "https://www.vuemastery.com/", 21 | "imgSrcDark": "https://posva-sponsors.pages.dev/logos/vuemastery-dark.png", 22 | "imgSrcLight": "https://posva-sponsors.pages.dev/logos/vuemastery-light.svg" 23 | }, 24 | { 25 | "alt": "SendCloud", 26 | "href": " https://jobs.sendcloud.com", 27 | "imgSrcDark": "https://posva-sponsors.pages.dev/logos/sendcloud-dark.svg", 28 | "imgSrcLight": "https://posva-sponsors.pages.dev/logos/sendcloud-light.svg" 29 | }, 30 | { 31 | "alt": "Route Optimizer and Route Planner Software", 32 | "href": "https://route4me.com", 33 | "imgSrcDark": "https://posva-sponsors.pages.dev/logos/route4me.png", 34 | "imgSrcLight": "https://posva-sponsors.pages.dev/logos/route4me.png" 35 | } 36 | ], 37 | "bronze": [ 38 | { 39 | "alt": "Storyblok", 40 | "href": "https://storyblok.com", 41 | "imgSrcDark": "https://posva-sponsors.pages.dev/logos/storyblok.png", 42 | "imgSrcLight": "https://posva-sponsors.pages.dev/logos/storyblok.png" 43 | }, 44 | { 45 | "alt": "Stanislas Ormières", 46 | "href": "https://stormier.ninja", 47 | "imgSrcDark": "https://avatars.githubusercontent.com/u/2486424?u=7b0c73ae5d090ce53bf59473094e9606fe082c59&v=4", 48 | "imgSrcLight": "https://avatars.githubusercontent.com/u/2486424?u=7b0c73ae5d090ce53bf59473094e9606fe082c59&v=4" 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /packages/pinia/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module pinia 3 | */ 4 | export { setActivePinia, getActivePinia } from './rootStore' 5 | export { createPinia, disposePinia } from './createPinia' 6 | export type { Pinia, PiniaPlugin, PiniaPluginContext } from './rootStore' 7 | 8 | export { defineStore, skipHydrate, shouldHydrate } from './store' 9 | export type { 10 | StoreActions, 11 | StoreGetters, 12 | StoreState, 13 | SetupStoreDefinition, 14 | } from './store' 15 | 16 | export type { 17 | StateTree, 18 | Store, 19 | StoreGeneric, 20 | StoreDefinition, 21 | _StoreWithGetters, 22 | _GettersTree, 23 | _ActionsTree, 24 | _Method, 25 | _StoreWithActions, 26 | _StoreWithState, 27 | StoreProperties, 28 | StoreOnActionListener, 29 | _StoreOnActionListenerContext, 30 | StoreOnActionListenerContext, 31 | SubscriptionCallback, 32 | SubscriptionCallbackMutation, 33 | SubscriptionCallbackMutationDirect, 34 | SubscriptionCallbackMutationPatchFunction, 35 | SubscriptionCallbackMutationPatchObject, 36 | _SubscriptionCallbackMutationBase, 37 | PiniaCustomProperties, 38 | PiniaCustomStateProperties, 39 | DefineStoreOptionsBase, 40 | DefineStoreOptions, 41 | DefineSetupStoreOptions, 42 | DefineStoreOptionsInPlugin, 43 | _ExtractActionsFromSetupStore, 44 | _ExtractGettersFromSetupStore, 45 | _ExtractStateFromSetupStore, 46 | _DeepPartial, 47 | _ExtractActionsFromSetupStore_Keys, 48 | _ExtractGettersFromSetupStore_Keys, 49 | _ExtractStateFromSetupStore_Keys, 50 | _UnwrapAll, 51 | } from './types' 52 | export { MutationType } from './types' 53 | 54 | export { 55 | mapActions, 56 | mapStores, 57 | mapState, 58 | mapWritableState, 59 | mapGetters, 60 | setMapStoreSuffix, 61 | } from './mapHelpers' 62 | 63 | export { storeToRefs } from './storeToRefs' 64 | 65 | export type { 66 | MapStoresCustomization, 67 | _MapActionsObjectReturn, 68 | _MapActionsReturn, 69 | _MapStateObjectReturn, 70 | _MapStateReturn, 71 | _MapWritableStateObjectReturn, 72 | _MapWritableStateReturn, 73 | _Spread, 74 | _StoreObject, 75 | } from './mapHelpers' 76 | 77 | export { acceptHMRUpdate } from './hmr' 78 | 79 | export * from './globalExtensions' 80 | -------------------------------------------------------------------------------- /packages/nuxt/src/auto-hmr-plugin.ts: -------------------------------------------------------------------------------- 1 | import type { VariableDeclarator } from 'estree' 2 | import type { Nuxt } from 'nuxt/schema' 3 | import type { Plugin } from 'vite' 4 | 5 | function getStoreDeclaration(nodes?: VariableDeclarator[]) { 6 | return nodes?.find( 7 | (x) => 8 | x.init?.type === 'CallExpression' && 9 | x.init.callee.type === 'Identifier' && 10 | x.init.callee.name === 'defineStore' 11 | ) 12 | } 13 | 14 | function nameFromDeclaration(node?: VariableDeclarator) { 15 | return node?.id.type === 'Identifier' && node.id.name 16 | } 17 | 18 | export function autoRegisterHMRPlugin(rootDir: string) { 19 | return { 20 | name: 'pinia:auto-hmr-registration', 21 | 22 | transform(code, id) { 23 | if (id.startsWith('\x00')) return 24 | if (!id.startsWith(rootDir)) return 25 | if (!code.includes('defineStore') || code.includes('acceptHMRUpdate')) { 26 | return 27 | } 28 | 29 | const ast = this.parse(code) 30 | 31 | // walk top-level nodes 32 | for (const n of ast.body) { 33 | if ( 34 | n.type === 'VariableDeclaration' || 35 | n.type === 'ExportNamedDeclaration' 36 | ) { 37 | // find export or variable declaration that uses `defineStore` 38 | const storeDeclaration = getStoreDeclaration( 39 | n.type === 'VariableDeclaration' 40 | ? n.declarations 41 | : n.declaration?.type === 'VariableDeclaration' 42 | ? n.declaration?.declarations 43 | : undefined 44 | ) 45 | 46 | // retrieve the variable name 47 | const storeName = nameFromDeclaration(storeDeclaration) 48 | if (storeName) { 49 | // append HMR code 50 | return { 51 | code: [ 52 | `import { acceptHMRUpdate } from 'pinia'`, 53 | code, 54 | 'if (import.meta.hot) {', 55 | ` import.meta.hot.accept(acceptHMRUpdate(${storeName}, import.meta.hot))`, 56 | '}', 57 | ].join('\n'), 58 | } 59 | } 60 | } 61 | } 62 | }, 63 | } satisfies Plugin 64 | } 65 | -------------------------------------------------------------------------------- /packages/docs/zh/getting-started.md: -------------------------------------------------------------------------------- 1 | # 开始 2 | 3 | 4 | 5 | ## 安装 %{#installation}% 6 | 7 | 8 | 9 | 10 | 用你喜欢的包管理器安装 `pinia`: 11 | 12 | ```bash 13 | yarn add pinia 14 | # 或者使用 npm 15 | npm install pinia 16 | ``` 17 | 18 | :::tip 19 | 如果你的应用使用的 Vue 版本低于 2.7,你还需要安装组合式 API 包:`@vue/composition-api`。如果你使用的是 Nuxt,你应该参考[这篇指南](/ssr/nuxt.md)。 20 | ::: 21 | 22 | 如果你正在使用 Vue CLI,你可以试试这个[**非官方插件**](https://github.com/wobsoriano/vue-cli-plugin-pinia)。 23 | 24 | 创建一个 pinia 实例 (根 store) 并将其传递给应用: 25 | 26 | ```js {2,5-6,8} 27 | import { createApp } from 'vue' 28 | import { createPinia } from 'pinia' 29 | import App from './App.vue' 30 | 31 | const pinia = createPinia() 32 | const app = createApp(App) 33 | 34 | app.use(pinia) 35 | app.mount('#app') 36 | ``` 37 | 38 | 如果你使用的是 Vue 2,你还需要安装一个插件,并在应用的根部注入创建的 `pinia`: 39 | 40 | ```js {1,3-4,12} 41 | import { createPinia, PiniaVuePlugin } from 'pinia' 42 | 43 | Vue.use(PiniaVuePlugin) 44 | const pinia = createPinia() 45 | 46 | new Vue({ 47 | el: '#app', 48 | // 其他配置... 49 | // ... 50 | // 请注意,同一个`pinia'实例 51 | // 可以在同一个页面的多个 Vue 应用中使用。 52 | pinia, 53 | }) 54 | ``` 55 | 56 | 这样才能提供 devtools 的支持。在 Vue 3 中,一些功能仍然不被支持,如 time traveling 和编辑,这是因为 vue-devtools 还没有相关的 API,但 devtools 也有很多针对 Vue 3 的专属功能,而且就开发者的体验来说,Vue 3 整体上要好得多。在 Vue 2 中,Pinia 使用的是 Vuex 的现有接口 (因此不能与 Vuex 一起使用) 。 57 | 58 | ## Store 是什么?%{#what-is-a-store}% 59 | 60 | Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,**它承载着全局状态**。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有**三个概念**,[state](./core-concepts/state.md)、[getter](./core-concepts/getters.md) 和 [action](./core-concepts/actions.md),我们可以假设这些概念相当于组件中的 `data`、 `computed` 和 `methods`。 61 | 62 | ## 应该在什么时候使用 Store? %{#when-should-i-use-a-store}% 63 | 64 | 一个 Store 应该包含可以在整个应用中访问的数据。这包括在许多地方使用的数据,例如显示在导航栏中的用户信息,以及需要通过页面保存的数据,例如一个非常复杂的多步骤表单。 65 | 66 | 另一方面,你应该避免在 Store 中引入那些原本可以在组件中保存的本地数据,例如,一个元素在页面中的可见性。 67 | 68 | 并非所有的应用都需要访问全局状态,但如果你的应用确实需要一个全局状态,那 Pinia 将使你的开发过程更轻松。 69 | 70 | ## 什么时候**不**应该使用 Store 71 | 72 | 有的时候我们会过度使用 store。如果觉得应用程序的 store 过多,你可能需要重新考虑使用 store 的目的。例如其中一些逻辑应该只是组合式函数,或者应该只是组件的本地状态。这在 Mastering Pinia 的 [(不要) 滥用 store](https://masteringpinia.com/lessons/not-overusing-stores) 课程中有详细介绍。 73 | -------------------------------------------------------------------------------- /packages/testing/src/initialState.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | import { createTestingPinia, TestingOptions } from './testing' 3 | import { defineStore } from 'pinia' 4 | import { mount } from '@vue/test-utils' 5 | import { defineComponent } from 'vue' 6 | 7 | describe('Testing: initial state', () => { 8 | const useCounter = defineStore('counter', { 9 | state: () => ({ n: 0, nested: { n: 0, other: false } }), 10 | actions: { 11 | increment(amount = 1) { 12 | this.n += amount 13 | }, 14 | }, 15 | }) 16 | 17 | const Counter = defineComponent({ 18 | setup() { 19 | const counter = useCounter() 20 | return { counter } 21 | }, 22 | template: ` 23 | 24 | {{ counter.n }} 25 | 26 | `, 27 | }) 28 | 29 | function factory(options?: TestingOptions) { 30 | const wrapper = mount(Counter, { 31 | global: { 32 | plugins: [createTestingPinia(options)], 33 | }, 34 | }) 35 | 36 | const counter = useCounter() 37 | 38 | return { wrapper, counter } 39 | } 40 | 41 | it('can set an initial state', () => { 42 | const { counter } = factory({ 43 | initialState: { counter: { n: 10 } }, 44 | }) 45 | expect(counter.nested).toEqual({ n: 0, other: false }) 46 | expect(counter.n).toBe(10) 47 | counter.n++ 48 | expect(counter.n).toBe(11) 49 | }) 50 | 51 | it('can provide objects', () => { 52 | const { counter } = factory({ 53 | initialState: { counter: { nested: { n: 10 } } }, 54 | }) 55 | expect(counter.n).toBe(0) 56 | expect(counter.nested.other).toBe(false) 57 | expect(counter.nested.n).toBe(10) 58 | counter.nested.n++ 59 | expect(counter.nested.n).toBe(11) 60 | }) 61 | 62 | it('can set an initial state with no app', () => { 63 | const pinia = createTestingPinia({ 64 | initialState: { 65 | counter: { n: 20 }, 66 | }, 67 | }) 68 | const counter = useCounter(pinia) 69 | expect(counter.nested).toEqual({ n: 0, other: false }) 70 | expect(counter.n).toBe(20) 71 | counter.n++ 72 | expect(counter.n).toBe(21) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import { fileURLToPath } from 'node:url' 3 | 4 | export default defineConfig({ 5 | define: { 6 | __DEV__: true, 7 | __TEST__: true, 8 | __BROWSER__: true, 9 | __USE_DEVTOOLS__: false, 10 | }, 11 | resolve: { 12 | alias: [ 13 | { 14 | find: /^@pinia\/(.*?)$/, 15 | replacement: fileURLToPath( 16 | new URL('./packages/packages/$1/src', import.meta.url) 17 | ), 18 | }, 19 | { 20 | find: /^pinia$/, 21 | replacement: fileURLToPath( 22 | new URL('./packages/pinia/src', import.meta.url) 23 | ), 24 | }, 25 | ], 26 | }, 27 | 28 | test: { 29 | include: ['src/**/*.{test,spec}.ts', '__tests__/**/*.{test,spec}.ts'], 30 | setupFiles: [ 31 | fileURLToPath( 32 | new URL('./packages/pinia/__tests__/vitest-setup.ts', import.meta.url) 33 | ), 34 | ], 35 | environment: 'happy-dom', 36 | fakeTimers: { 37 | // easier to read, some date in 2001 38 | now: 1_000_000_000_000, 39 | }, 40 | typecheck: { 41 | enabled: true, 42 | }, 43 | coverage: { 44 | enabled: true, 45 | provider: 'v8', 46 | reporter: ['text', 'lcovonly', 'html'], 47 | include: [ 48 | 'packages/pinia/src', 49 | 'packages/nuxt/src', 50 | 'packages/testing/src', 51 | ], 52 | exclude: [ 53 | '**/src/index.ts', 54 | '**/*.test-d.ts', 55 | 'packages/pinia/src/devtools', 56 | 'packages/pinia/src/hmr.ts', 57 | ], 58 | }, 59 | projects: [ 60 | { 61 | extends: true, 62 | test: { 63 | name: 'pinia', 64 | root: './packages/pinia', 65 | }, 66 | }, 67 | { 68 | extends: true, 69 | test: { 70 | name: '@pinia/nuxt', 71 | root: './packages/nuxt', 72 | environment: 'node', 73 | include: ['test/**/*.{spec,test}.ts'], 74 | }, 75 | }, 76 | { 77 | extends: true, 78 | test: { 79 | name: '@pinia/testing', 80 | root: './packages/testing', 81 | globals: true, 82 | }, 83 | }, 84 | ], 85 | }, 86 | }) 87 | -------------------------------------------------------------------------------- /packages/docs/zh/api/interfaces/pinia_testing.TestingOptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: 'auto' 3 | editLinks: false 4 | sidebarDepth: 3 5 | --- 6 | 7 | [API 文档](../index.md) / [@pinia/testing](../modules/pinia_testing.md) / TestingOptions 8 | 9 | # 接口:TestingOptions %{#interface-testingoptions}% 10 | 11 | [@pinia/testing](../modules/pinia_testing.md).TestingOptions 12 | 13 | ## 属性 14 | 15 | ### createSpy %{#createspy}% 16 | 17 | • `Optional` **createSpy**: (`fn?`: (...`args`: `any`[]) => `any`) => (...`args`: `any`[]) => `any` 18 | 19 | #### 类型声明 %{#type-declaration}% 20 | 21 | ▸ (`fn?`): (...`args`: `any`[]) => `any` 22 | 23 | 用于创建 action 和 `$patch()` 的 spy 的函数。 24 | 在 jest 项目中默认为 `jest.fn()`,在 vitest 项目中默认为 `vi.fn()`。 25 | 26 | ##### 参数 27 | 28 | | 名称 | 类型 | 29 | | :---- | :---------------------------- | 30 | | `fn?` | (...`args`: `any`[]) => `any` | 31 | 32 | ##### 返回值 33 | 34 | `fn` 35 | 36 | ▸ (...`args`): `any` 37 | 38 | ##### 参数 39 | 40 | | 名称 | 类型 | 41 | | :-------- | :------ | 42 | | `...args` | `any`[] | 43 | 44 | ##### 返回值 45 | 46 | `any` 47 | 48 | --- 49 | 50 | ### fakeApp %{#fakeapp}% 51 | 52 | • `Optional` **fakeApp**: `boolean` 53 | 54 | 创建一个空的 App,并通过创建的测试 pinia 调用 `app.use(pinia)`。 55 | 这样可以让你在单元测试时使用插件, 56 | 因为插件**必须等待 pinia 安装好后才会执行**。 57 | 默认为 false。 58 | 59 | --- 60 | 61 | ### initialState %{#initialstate}% 62 | 63 | • `Optional` **initialState**: [`StateTree`](../modules/pinia.md#statetree) 64 | 65 | 允许你定义每个 store 的部分初始 state。 66 | 这个 state 会在 store 创建后被应用,这样可以让你只设置测试中需要的几个属性。 67 | 68 | --- 69 | 70 | ### 插件 %{#plugins}% 71 | 72 | • `Optional` **plugins**: [`PiniaPlugin`](pinia.PiniaPlugin.md)[] 73 | 74 | 在测试插件之前必装的插件。 75 | 可以向你的应用添加测试时使用的任意插件。 76 | 77 | --- 78 | 79 | ### stubActions %{#stubactions}% 80 | 81 | • `Optional` **stubActions**: `boolean` 82 | 83 | 当设置为 false 时, actions 只会被监听,它们仍然会执行。 84 | 当设置为 true 时,actions 将被替换为 spies,导致其代码不被执行。 85 | 默认为 true。 86 | 注意:当提供 `createSpy()` 时,它将**只**给 `fn` 参数 传递 `undefined`。 87 | 你仍然需要在 `createSpy()` 中处理这个问题。 88 | 89 | --- 90 | 91 | ### stubPatch %{#stubpatch}% 92 | 93 | • `Optional` **stubPatch**: `boolean` 94 | 95 | 当设置为 true 时,对 `$patch()` 的调用将不会改变状态。 96 | 默认为 false。注意:当提供 `createSpy()` 时,它将**只**给 `fn` 参数 传递 `undefined`。 97 | 你仍然需要在 `createSpy()` 中处理这个问题。 98 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/components/HomeSponsorsGroup.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 55 | 56 | 100 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41E Bug report" 2 | description: Report an issue with Pinia 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thanks for taking the time to fill out this bug report! 8 | - type: input 9 | id: reproduction 10 | attributes: 11 | label: Reproduction 12 | 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**." 13 | placeholder: Reproduction 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: steps 18 | attributes: 19 | label: Steps to reproduce the bug 20 | description: | 21 | 1. Click on ... 22 | 2. Check log 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: expected-behavior 27 | attributes: 28 | label: Expected behavior 29 | description: A clear and concise description of what you expected to happen. 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: actual-behavior 34 | attributes: 35 | label: Actual behavior 36 | description: 'A clear and concise description of what actually happens.' 37 | validations: 38 | required: true 39 | - type: textarea 40 | id: other-info 41 | attributes: 42 | label: Additional information 43 | description: Add any other context about the problem here. 44 | - type: markdown 45 | attributes: 46 | value: | 47 | ## Before creating an issue make sure that: 48 | - This hasn't been [reported before](https://github.com/vuejs/pinia/issues). 49 | - 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)** 50 | -------------------------------------------------------------------------------- /packages/playground/src/stores/jokesUsePromised.ts: -------------------------------------------------------------------------------- 1 | import { acceptHMRUpdate, defineStore } from 'pinia' 2 | import { getRandomJoke, Joke } from '../api/jokes' 3 | import { usePromise } from 'vue-promised' 4 | import { ref, watch } from 'vue' 5 | 6 | export const useJokes = defineStore('jokes-vue-promised', { 7 | state: () => { 8 | const promise = ref(getRandomJoke()) 9 | 10 | watch(promise, () => { 11 | console.log('promise changed') 12 | }) 13 | 14 | const promised = usePromise(promise) 15 | 16 | return { 17 | promise, 18 | ...promised, 19 | history: [] as Joke[], 20 | } 21 | }, 22 | 23 | actions: { 24 | waitForJoke() { 25 | return this.promise 26 | }, 27 | 28 | fetchJoke() { 29 | if ( 30 | this.data && 31 | // if the request below fails, avoid adding it twice 32 | !this.history.includes(this.data) 33 | ) { 34 | this.history.push(this.data) 35 | } 36 | 37 | console.log('fetching') 38 | // this.$state.promise = getRandomJoke() 39 | // Will fail because we initially had a ref 40 | this.promise = getRandomJoke() 41 | 42 | return this.$state.promise 43 | }, 44 | }, 45 | }) 46 | 47 | export const useSetupJokes = defineStore('jokes-setup-vue-promised', () => { 48 | const history = ref([]) 49 | const promise = ref(getRandomJoke()) 50 | watch(promise, () => { 51 | console.log('promise changed') 52 | }) 53 | 54 | const promised = usePromise(promise) 55 | 56 | function fetchJoke() { 57 | if ( 58 | promised.data.value && 59 | // if the request below fails, avoid adding it twice 60 | !history.value.includes(promised.data.value) 61 | ) { 62 | history.value.push(promised.data.value) 63 | } 64 | 65 | console.log('fetching') 66 | // this.$state.promise = getRandomJoke() 67 | // Will fail because we initially had a ref 68 | promise.value = getRandomJoke() 69 | 70 | promise.value.then((joke) => { 71 | console.log('got', joke) 72 | }) 73 | 74 | return promise.value 75 | } 76 | 77 | return { 78 | promise, 79 | ...promised, 80 | history, 81 | fetchJoke, 82 | } 83 | }) 84 | 85 | if (import.meta.hot) { 86 | import.meta.hot.accept(acceptHMRUpdate(useJokes, import.meta.hot)) 87 | // import.meta.hot.accept(acceptHMRUpdate(useSetupJokes, import.meta.hot)) 88 | } 89 | -------------------------------------------------------------------------------- /packages/playground/src/views/JokesPromised.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 78 | 79 | 95 | -------------------------------------------------------------------------------- /packages/docs/zh/cookbook/options-api.md: -------------------------------------------------------------------------------- 1 | # 不使用 `setup()` 的用法 %{#usage-without-setup}% 2 | 3 | 4 | 5 | 即使你没有使用组合式 API,也可以使用 Pinia(如果你使用 Vue 2,你仍然需要安装 `@vue/composition-api` 插件)。虽然我们推荐你试着学习一下组合式 API,但对你和你的团队来说目前可能还不是时候,你可能正在迁移一个应用,或者有其他原因。你可以试试下面几个函数: 6 | 7 | - [mapStores](#giving-access-to-the-whole-store) 8 | - [mapState](../core-concepts/state.md#usage-with-the-options-api) 9 | - [mapWritableState](../core-concepts/state.md#modifiable-state) 10 | - ⚠️ [mapGetters](../core-concepts/getters.md#without-setup) (只是为了迁移方便,请用 `mapState()` 代替) 11 | - [mapActions](../core-concepts/actions.md#without-setup) 12 | 13 | ## 给予整个 store 的访问权 %{#giving-access-to-the-whole-store}% 14 | 15 | 如果你需要访问 store 里的大部分内容,映射 store 的每一个属性可能太麻烦。你可以试试用 `mapStores()` 来访问整个 store: 16 | 17 | ```js 18 | import { mapStores } from 'pinia' 19 | 20 | // 给出具有以下 id 的两个 store 21 | const useUserStore = defineStore('user', { 22 | // ... 23 | }) 24 | const useCartStore = defineStore('cart', { 25 | // ... 26 | }) 27 | 28 | export default { 29 | computed: { 30 | // 注意,我们不是在传递一个数组,而是一个接一个的 store。 31 | // 可以 id+'Store' 的形式访问每个 store 。 32 | ...mapStores(useCartStore, useUserStore), 33 | }, 34 | 35 | methods: { 36 | async buyStuff() { 37 | // 可以在任何地方使用他们! 38 | if (this.userStore.isAuthenticated()) { 39 | await this.cartStore.buy() 40 | this.$router.push('/purchased') 41 | } 42 | }, 43 | }, 44 | } 45 | ``` 46 | 47 | 默认情况下,Pinia 会在每个 store 的 `id` 后面加上 `"Store"` 的后缀。你可以通过调用 `setMapStoreSuffix()` 来自定义: 48 | 49 | ```js 50 | import { createPinia, setMapStoreSuffix } from 'pinia' 51 | 52 | // 完全删除后缀:this.user, this.cart 53 | setMapStoreSuffix('') 54 | // this.user_store, this.cart_store (没关系,我不会批评你的) 55 | setMapStoreSuffix('_store') 56 | export const pinia = createPinia() 57 | ``` 58 | 59 | ## TypeScript %{#typescript}% 60 | 61 | 默认情况下,所有映射辅助函数都支持自动补全,你不需要做任何事情。如果你调用 `setMapStoreSuffix()` 修改了 `"Store"` 的后缀,你还需要在 TS 文件或 `global.d.ts` 文件的某个地方添加它。最方便的地方就是你调用 `setMapStoreSuffix()` 的地方: 62 | 63 | ```ts 64 | import { createPinia, setMapStoreSuffix } from 'pinia' 65 | 66 | setMapStoreSuffix('') // 完全删除后缀 67 | export const pinia = createPinia() 68 | 69 | declare module 'pinia' { 70 | export interface MapStoresCustomization { 71 | // 设置成和上面一样的值 72 | suffix: '' 73 | } 74 | } 75 | ``` 76 | 77 | :::warning 78 | 如果你使用的是 TypeScript 声明文件(如 `global.d.ts`),请确保在文件顶部 `import 'pinia'`,以暴露所有现有类型。 79 | ::: 80 | -------------------------------------------------------------------------------- /packages/docs/public/rulekit-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/docs/public/sponsors/vuetify-logo-dark-text.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/docs/public/sponsors/vuetify-logo-light-text.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/playground/src/main.ts: -------------------------------------------------------------------------------- 1 | import { computed, createApp, markRaw, Ref } from 'vue' 2 | import App from './App.vue' 3 | import { createPinia } from 'pinia' 4 | import { router } from './router' 5 | import { 6 | RouteLocationNormalized, 7 | RouteLocationNormalizedLoaded, 8 | } from 'vue-router' 9 | 10 | // for local development 11 | window.__USE_DEVTOOLS__ = true 12 | 13 | const pinia = createPinia() 14 | 15 | declare module 'pinia' { 16 | export interface PiniaCustomProperties { 17 | set route( 18 | value: RouteLocationNormalizedLoaded | Ref 19 | ) 20 | get route(): RouteLocationNormalized 21 | } 22 | } 23 | 24 | pinia.use(() => ({ 25 | route: computed(() => markRaw(router.currentRoute.value)), 26 | })) 27 | 28 | if (import.meta.hot) { 29 | // const isUseStore = (fn: any): fn is StoreDefinition => { 30 | // return typeof fn === 'function' && typeof fn.$id === 'string' 31 | // } 32 | // // import.meta.hot.accept( 33 | // // './stores/counter.ts', 34 | // // (newStore: Record) => { 35 | // // console.log('haha', newStore) 36 | // // } 37 | // // ) 38 | // import.meta.hot.accept('./test.ts', (newTest) => { 39 | // console.log('test updated', newTest) 40 | // }) 41 | // const stores = import.meta.glob('./stores/*.ts') 42 | // for (const storeId in stores) { 43 | // console.log('configuring HMR for', storeId) 44 | // const oldUseStore = await stores[storeId]() 45 | // console.log('got', oldUseStore) 46 | // import.meta.hot!.accept(storeId, (newStore: Record) => { 47 | // console.log('Accepting update for', storeId) 48 | // for (const exportName in newStore) { 49 | // const useStore = newStore[exportName] 50 | // if (isUseStore(useStore) && pinia._s.has(useStore.$id)) { 51 | // const id = useStore.$id 52 | // const existingStore = pinia._s.get(id)! 53 | // // remove the existing store from the cache to force a new one 54 | // pinia._s.delete(id) 55 | // // this adds any new state to pinia and then runs the `hydrate` function 56 | // // which, by default, will reuse the existing state in pinia 57 | // const newStore = useStore(pinia) 58 | // // pinia._s.set(id, existingStore) 59 | // } 60 | // } 61 | // }) 62 | // } 63 | } 64 | 65 | const app = createApp(App) 66 | .use(pinia) 67 | .use(router) 68 | // used in counter setup for tests 69 | .provide('injected', 'global') 70 | 71 | app.mount('#app') 72 | --------------------------------------------------------------------------------