Comparing version 2.0.0-beta.5 to 2.0.0-rc.0
@@ -0,1 +1,42 @@ | ||
# [2.0.0-rc.0](https://github.com/posva/pinia/compare/v2.0.0-beta.5...v2.0.0-rc.0) (2021-07-28) | ||
## Required Vue version ‼️ | ||
This release requires Vue 3.2.0, which is currently only available under the `beta` dist tag (`npm i vue@beta` or `yarn add vue@beta` + the corresponding packages like `@vue/compiler-sfc@beta`). | ||
It contains major improvements: | ||
- Performance: Pinia now uses `effectScope()`, effectively reducing memory consumption and removing the drawbacks mentioned in the Plugin section about `useStore()` creating multiple store instances (still sharing the state). | ||
- Devtools: Many improvements over the information displayed in devtools as well as a few bugfixes | ||
- HMR (Hot Module Replacement): You can now modify your stores without reloading the page and losing the state, making development much easier. Until 3.2.0 (stable) is released, you can find an example [in the playground](https://github.com/posva/pinia/blob/2b98eafe441ea7e9a3ff3cef122c24eb5fa03f1d/playground/src/stores/counter.ts#L66-L68). After that, you can read up to date instructions [in the documentation](https://pinia.esm.dev/cookbook/hot-module-replacement.html). | ||
- Setup syntax: You can now define stores with a function instead of options. This enables more complex patterns. See an example [in the playground](https://github.com/posva/pinia/blob/75f1fe6aa4ef2629ae1c9840a2d4542ac6e62686/playground/src/stores/jokes-swrv.ts). Setup Stores are unable to group actions like Option Stores due to their very permissive syntax. | ||
- Option syntax: we can now pass the `id` as the first parameter. This syntax is preferred over the object syntax to be consistent with the Setup syntax. | ||
### Bug Fixes | ||
- avoid modifying options argument ([59ac9b9](https://github.com/posva/pinia/commit/59ac9b962778f730ade9c2a8b1a575922957d907)) | ||
- **devtools:** avoid grouping patches and mutations with finished actions ([18a87fe](https://github.com/posva/pinia/commit/18a87fe260317c679732d0ec271c036b9806448f)) | ||
- **errors:** allow async errors to propagate ([17ee4e8](https://github.com/posva/pinia/commit/17ee4e85fb2c084ba27730dae4f21683686156c6)), closes [#576](https://github.com/posva/pinia/issues/576) | ||
- **ssr:** delay getters read ([2f3bd53](https://github.com/posva/pinia/commit/2f3bd5330e853b8ef11b6364a3a86e780c5f309f)) | ||
- **types:** actual generic store ([e4c541f](https://github.com/posva/pinia/commit/e4c541fdd17ea97e25dfd45bd3378732ff6a344d)) | ||
- **types:** stricter types for mapState ([f702356](https://github.com/posva/pinia/commit/f702356a5549dfe184c4d3805757c494a7088b19)) | ||
### Features | ||
- allow actions to be destructured ([859d094](https://github.com/posva/pinia/commit/859d094bd993f4714093af17182ed73dd98659c5)) | ||
- **devtools:** display pinia without stores ([ca59257](https://github.com/posva/pinia/commit/ca59257a4ca3a37f54d6b9690a2ceedbc545dedd)) | ||
- **devtools:** show hot update in timeline ([3b9ed17](https://github.com/posva/pinia/commit/3b9ed1777621b1c8c0f781f5c974357da042c6e7)) | ||
- **types:** add StorState, StoreGetters, and StoreActions helpers ([47c0610](https://github.com/posva/pinia/commit/47c06101555328b6ca24e2f574f8f402b3bf1675)) | ||
### BREAKING CHANGES | ||
- **types:** The existing `Store<Id, S, G, A>` types was trying to be generic when no types were specified but failing at it. Now, `Store` without any type will default to an empty Store. This enables a stricter version of `defineStore` when any of state, getters, and actions are missing. If you were using `Store` as a type, you should now use `StoreGeneric` instead, which also replaces `GenericStore` (marked as deprecated). | ||
```diff | ||
-function takeAnyStore(store: Store) {} | ||
+function takeAnyStore(store: StoreGeneric) {} | ||
``` | ||
- **types** The existing `DefineStoreOptions` is no longer the one that should be extended to add custom options unless you only want them to be applied to Option Stores. Use `DefineStoreOptionsBase` instead. | ||
# [2.0.0-beta.5](https://github.com/posva/pinia/compare/v2.0.0-beta.3...v2.0.0-beta.5) (2021-07-10) | ||
@@ -2,0 +43,0 @@ |
/*! | ||
* pinia v2.0.0-beta.5 | ||
* pinia v2.0.0-rc.0 | ||
* (c) 2021 Eduardo San Martin Morote | ||
@@ -37,7 +37,2 @@ * @license MIT | ||
}; | ||
/** | ||
* Map of stores based on a Pinia instance. Allows setting and retrieving stores | ||
* for the current running application (with its pinia). | ||
*/ | ||
const storesMap = new WeakMap(); | ||
const piniaSymbol = ((process.env.NODE_ENV !== 'production') ? Symbol('pinia') : /* istanbul ignore next */ Symbol()); | ||
@@ -426,3 +421,2 @@ | ||
key, | ||
// @ts-expect-error | ||
value: store.$state[key], | ||
@@ -436,3 +430,2 @@ })), | ||
key: getterName, | ||
// @ts-expect-error | ||
value: store[getterName], | ||
@@ -445,3 +438,2 @@ })); | ||
key, | ||
// @ts-expect-error | ||
value: store[key], | ||
@@ -492,7 +484,2 @@ })); | ||
/** | ||
* Registered stores used for devtools. | ||
*/ | ||
const registeredStores = /*#__PURE__*/ new Map(); | ||
let isAlreadyInstalled; | ||
// timeline can be paused when directly changing the state | ||
@@ -503,14 +490,17 @@ let isTimelineActive = true; | ||
const INSPECTOR_ID = 'pinia'; | ||
function addDevtools(app, store) { | ||
// TODO: we probably need to ensure the latest version of the store is kept: | ||
// without effectScope, multiple stores will be created and will have a | ||
// limited lifespan for getters. | ||
// add a dev only variable that is removed in unmounted and replace the store | ||
let hasSubscribed = true; | ||
const storeType = '🍍 ' + store.$id; | ||
if (!registeredStores.has(store.$id)) { | ||
registeredStores.set(store.$id, store); | ||
componentStateTypes.push(storeType); | ||
hasSubscribed = false; | ||
} | ||
/** | ||
* Gets the displayed name of a store in devtools | ||
* | ||
* @param id - id of the store | ||
* @returns a formatted string | ||
*/ | ||
const getStoreType = (id) => '🍍 ' + id; | ||
/** | ||
* Add the pinia plugin without any store. Allows displaying a Pinia plugin tab | ||
* as soon as it is added to the application. | ||
* | ||
* @param app - Vue application | ||
* @param pinia - pinia instance | ||
*/ | ||
function registerPiniaDevtools(app, pinia) { | ||
devtoolsApi.setupDevtoolsPlugin({ | ||
@@ -525,157 +515,161 @@ id: 'dev.esm.pinia', | ||
}, (api) => { | ||
if (!isAlreadyInstalled) { | ||
api.addTimelineLayer({ | ||
id: MUTATIONS_LAYER_ID, | ||
label: `Pinia 🍍`, | ||
color: 0xe5df88, | ||
}); | ||
api.addInspector({ | ||
id: INSPECTOR_ID, | ||
label: 'Pinia 🍍', | ||
icon: 'storage', | ||
treeFilterPlaceholder: 'Search stores', | ||
actions: [ | ||
{ | ||
icon: 'content_copy', | ||
action: () => { | ||
actionGlobalCopyState(store._p); | ||
}, | ||
tooltip: 'Serialize and copy the state', | ||
api.addTimelineLayer({ | ||
id: MUTATIONS_LAYER_ID, | ||
label: `Pinia 🍍`, | ||
color: 0xe5df88, | ||
}); | ||
api.addInspector({ | ||
id: INSPECTOR_ID, | ||
label: 'Pinia 🍍', | ||
icon: 'storage', | ||
treeFilterPlaceholder: 'Search stores', | ||
actions: [ | ||
{ | ||
icon: 'content_copy', | ||
action: () => { | ||
actionGlobalCopyState(pinia); | ||
}, | ||
{ | ||
icon: 'content_paste', | ||
action: async () => { | ||
await actionGlobalPasteState(store._p); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
tooltip: 'Replace the state with the content of your clipboard', | ||
tooltip: 'Serialize and copy the state', | ||
}, | ||
{ | ||
icon: 'content_paste', | ||
action: async () => { | ||
await actionGlobalPasteState(pinia); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
{ | ||
icon: 'save', | ||
action: () => { | ||
actionGlobalSaveState(store._p); | ||
}, | ||
tooltip: 'Save the state as a JSON file', | ||
tooltip: 'Replace the state with the content of your clipboard', | ||
}, | ||
{ | ||
icon: 'save', | ||
action: () => { | ||
actionGlobalSaveState(pinia); | ||
}, | ||
{ | ||
icon: 'folder_open', | ||
action: async () => { | ||
await actionGlobalOpenStateFile(store._p); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
tooltip: 'Import the state from a JSON file', | ||
tooltip: 'Save the state as a JSON file', | ||
}, | ||
{ | ||
icon: 'folder_open', | ||
action: async () => { | ||
await actionGlobalOpenStateFile(pinia); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
], | ||
}); | ||
api.on.inspectComponent((payload, ctx) => { | ||
const proxy = (payload.componentInstance && | ||
payload.componentInstance.proxy); | ||
if (proxy && proxy._pStores) { | ||
const piniaStores = payload.componentInstance.proxy._pStores; | ||
Object.values(piniaStores).forEach((store) => { | ||
tooltip: 'Import the state from a JSON file', | ||
}, | ||
], | ||
}); | ||
api.on.inspectComponent((payload, ctx) => { | ||
const proxy = (payload.componentInstance && | ||
payload.componentInstance.proxy); | ||
if (proxy && proxy._pStores) { | ||
const piniaStores = payload.componentInstance.proxy._pStores; | ||
Object.values(piniaStores).forEach((store) => { | ||
payload.instanceData.state.push({ | ||
type: getStoreType(store.$id), | ||
key: 'state', | ||
editable: true, | ||
value: store.$state, | ||
}); | ||
if (store._getters && store._getters.length) { | ||
payload.instanceData.state.push({ | ||
type: storeType, | ||
key: 'state', | ||
editable: true, | ||
value: store.$state, | ||
type: getStoreType(store.$id), | ||
key: 'getters', | ||
editable: false, | ||
value: store._getters.reduce((getters, key) => { | ||
getters[key] = store[key]; | ||
return getters; | ||
}, {}), | ||
}); | ||
if (store._getters && store._getters.length) { | ||
payload.instanceData.state.push({ | ||
type: storeType, | ||
key: 'getters', | ||
editable: false, | ||
value: store._getters.reduce((getters, key) => { | ||
// @ts-expect-error | ||
getters[key] = store[key]; | ||
return getters; | ||
}, {}), | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
api.on.getInspectorTree((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
let stores = [pinia]; | ||
stores = stores.concat(Array.from(pinia._s.values())); | ||
payload.rootNodes = (payload.filter | ||
? stores.filter((store) => '$id' in store | ||
? store.$id | ||
.toLowerCase() | ||
.includes(payload.filter.toLowerCase()) | ||
: PINIA_ROOT_LABEL.toLowerCase().includes(payload.filter.toLowerCase())) | ||
: stores).map(formatStoreForInspectorTree); | ||
} | ||
}); | ||
api.on.getInspectorState((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? pinia | ||
: pinia._s.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
// this could be the selected store restored for a different project | ||
// so it's better not to say anything here | ||
return; | ||
} | ||
}); | ||
api.on.getInspectorTree((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
let stores = [store._p]; | ||
stores = stores.concat(Array.from(registeredStores.values())); | ||
payload.rootNodes = (payload.filter | ||
? stores.filter((store) => '$id' in store | ||
? store.$id | ||
.toLowerCase() | ||
.includes(payload.filter.toLowerCase()) | ||
: PINIA_ROOT_LABEL.toLowerCase().includes(payload.filter.toLowerCase())) | ||
: stores).map(formatStoreForInspectorTree); | ||
if (inspectedStore) { | ||
payload.state = formatStoreForInspectorState(inspectedStore); | ||
} | ||
}); | ||
api.on.getInspectorState((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? store._p | ||
: registeredStores.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
// this could be the selected store restored for a different project | ||
// so it's better not to say anything here | ||
return; | ||
} | ||
if (inspectedStore) { | ||
payload.state = formatStoreForInspectorState(inspectedStore); | ||
} | ||
} | ||
}); | ||
api.on.editInspectorState((payload, ctx) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? pinia | ||
: pinia._s.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
return toastMessage(`store "${payload.nodeId}" not found`, 'error'); | ||
} | ||
}); | ||
api.on.editInspectorState((payload, ctx) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? store._p | ||
: registeredStores.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
return toastMessage(`store "${payload.nodeId}" not found`, 'error'); | ||
const { path } = payload; | ||
if (!isPinia(inspectedStore)) { | ||
// access only the state | ||
if (path.length !== 1 || | ||
!inspectedStore._customProperties.has(path[0]) || | ||
path[0] in inspectedStore.$state) { | ||
path.unshift('$state'); | ||
} | ||
const { path } = payload; | ||
if (!isPinia(store)) { | ||
// access only the state | ||
if (path.length !== 1 || | ||
!store._customProperties.has(path[0]) || | ||
path[0] in store.$state) { | ||
path.unshift('$state'); | ||
} | ||
} | ||
else { | ||
path.unshift('state', 'value'); | ||
} | ||
isTimelineActive = false; | ||
payload.set(inspectedStore, path, payload.state.value); | ||
isTimelineActive = true; | ||
} | ||
}); | ||
api.on.editComponentState((payload) => { | ||
if (payload.type.startsWith('🍍')) { | ||
const storeId = payload.type.replace(/^🍍\s*/, ''); | ||
const store = registeredStores.get(storeId); | ||
if (!store) { | ||
return toastMessage(`store "${storeId}" not found`, 'error'); | ||
} | ||
const { path } = payload; | ||
if (path[0] !== 'state') { | ||
return toastMessage(`Invalid path for store "${storeId}":\n${path}\nOnly state can be modified.`); | ||
} | ||
// rewrite the first entry to be able to directly set the state as | ||
// well as any other path | ||
path[0] = '$state'; | ||
isTimelineActive = false; | ||
payload.set(store, path, payload.state.value); | ||
isTimelineActive = true; | ||
else { | ||
path.unshift('state', 'value'); | ||
} | ||
}); | ||
isAlreadyInstalled = true; | ||
} | ||
else { | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
} | ||
// avoid subscribing to mutations and actions twice | ||
if (hasSubscribed) | ||
return; | ||
store.$onAction(({ after, onError, name, args, store }) => { | ||
isTimelineActive = false; | ||
payload.set(inspectedStore, path, payload.state.value); | ||
isTimelineActive = true; | ||
} | ||
}); | ||
api.on.editComponentState((payload) => { | ||
if (payload.type.startsWith('🍍')) { | ||
const storeId = payload.type.replace(/^🍍\s*/, ''); | ||
const store = pinia._s.get(storeId); | ||
if (!store) { | ||
return toastMessage(`store "${storeId}" not found`, 'error'); | ||
} | ||
const { path } = payload; | ||
if (path[0] !== 'state') { | ||
return toastMessage(`Invalid path for store "${storeId}":\n${path}\nOnly state can be modified.`); | ||
} | ||
// rewrite the first entry to be able to directly set the state as | ||
// well as any other path | ||
path[0] = '$state'; | ||
isTimelineActive = false; | ||
payload.set(store, path, payload.state.value); | ||
isTimelineActive = true; | ||
} | ||
}); | ||
}); | ||
} | ||
function addStoreToDevtools(app, store) { | ||
if (!componentStateTypes.includes(getStoreType(store.$id))) { | ||
componentStateTypes.push(getStoreType(store.$id)); | ||
} | ||
devtoolsApi.setupDevtoolsPlugin({ | ||
id: 'dev.esm.pinia', | ||
label: 'Pinia 🍍', | ||
logo: 'https://pinia.esm.dev/logo.svg', | ||
packageName: 'pinia', | ||
homepage: 'https://pinia.esm.dev', | ||
componentStateTypes, | ||
app, | ||
}, (api) => { | ||
store.$onAction(({ after, onError, name, args }) => { | ||
const groupId = runningActionId++; | ||
@@ -689,2 +683,3 @@ api.addTimelineEvent({ | ||
data: { | ||
store: formatDisplay(store.$id), | ||
action: formatDisplay(name), | ||
@@ -697,2 +692,3 @@ args, | ||
after((result) => { | ||
activeAction = undefined; | ||
api.addTimelineEvent({ | ||
@@ -705,2 +701,3 @@ layerId: MUTATIONS_LAYER_ID, | ||
data: { | ||
store: formatDisplay(store.$id), | ||
action: formatDisplay(name), | ||
@@ -715,2 +712,3 @@ args, | ||
onError((error) => { | ||
activeAction = undefined; | ||
api.addTimelineEvent({ | ||
@@ -724,2 +722,3 @@ layerId: MUTATIONS_LAYER_ID, | ||
data: { | ||
store: formatDisplay(store.$id), | ||
action: formatDisplay(name), | ||
@@ -733,3 +732,3 @@ args, | ||
}); | ||
}); | ||
}, true); | ||
store.$subscribe(({ events, type }, state) => { | ||
@@ -744,3 +743,6 @@ if (!isTimelineActive) | ||
title: formatMutationType(type), | ||
data: formatEventData(events), | ||
data: { | ||
store: formatDisplay(store.$id), | ||
...formatEventData(events), | ||
}, | ||
groupId: activeAction, | ||
@@ -773,6 +775,23 @@ }; | ||
}); | ||
}, true); | ||
const hotUpdate = store._hotUpdate; | ||
store._hotUpdate = vue.markRaw((newStore) => { | ||
hotUpdate(newStore); | ||
api.addTimelineEvent({ | ||
layerId: MUTATIONS_LAYER_ID, | ||
event: { | ||
time: Date.now(), | ||
title: '🔥 ' + store.$id, | ||
subtitle: 'HMR update', | ||
data: { | ||
store: formatDisplay(store.$id), | ||
info: formatDisplay(`HMR update`), | ||
}, | ||
}, | ||
}); | ||
}); | ||
// trigger an update so it can display new registered stores | ||
// @ts-ignore | ||
api.notifyComponentUpdate(); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
toastMessage(`"${store.$id}" store installed`); | ||
@@ -784,8 +803,10 @@ }); | ||
/** | ||
* pinia.use(devtoolsPlugin) | ||
* Patches a store to enable action grouping in devtools by wrapping the store with a Proxy that is passed as the context of all actions, allowing us to set `runningAction` on each access and effectively associating any state mutation to the action. | ||
* | ||
* @param store - store to patch | ||
* @param actionNames - list of actionst to patch | ||
*/ | ||
function devtoolsPlugin({ app, store, options, pinia }) { | ||
function patchActionForGrouping(store, actionNames) { | ||
// original actions of the store as they are given by pinia. We are going to override them | ||
const actions = Object.keys(options.actions || {}).reduce((storeActions, actionName) => { | ||
// @ts-expect-error | ||
const actions = actionNames.reduce((storeActions, actionName) => { | ||
// use toRaw to avoid tracking #541 | ||
@@ -796,5 +817,4 @@ storeActions[actionName] = vue.toRaw(store)[actionName]; | ||
for (const actionName in actions) { | ||
// @ts-expect-error | ||
store[actionName] = function () { | ||
setActivePinia(pinia); | ||
// setActivePinia(store._p) | ||
// the running action id is incremented in a before action hook | ||
@@ -815,4 +835,26 @@ const _actionId = runningActionId; | ||
} | ||
addDevtools(app, | ||
// @ts-expect-error: FIXME: if possible... | ||
} | ||
/** | ||
* pinia.use(devtoolsPlugin) | ||
*/ | ||
function devtoolsPlugin({ app, store, options }) { | ||
// HMR module | ||
if (store.$id.startsWith('__hot:')) { | ||
return; | ||
} | ||
// only wrap actions in option-defined stores as this technique relies on | ||
// wrapping the context of the action with a proxy | ||
if ('id' in options) { | ||
patchActionForGrouping( | ||
// @ts-expect-error: can cast the store... | ||
store, Object.keys(options.actions)); | ||
const originalHotUpdate = store._hotUpdate; | ||
// Upgrade the HMR to also update the new actions | ||
vue.toRaw(store)._hotUpdate = function (newStore) { | ||
originalHotUpdate.apply(this, arguments); | ||
patchActionForGrouping(store, Object.keys(newStore._hmrPayload.actions)); | ||
}; | ||
} | ||
addStoreToDevtools(app, | ||
// FIXME: is there a way to allow the assignment from Store<Id, S, G, A> to StoreGeneric? | ||
store); | ||
@@ -825,6 +867,6 @@ } | ||
function createPinia() { | ||
const scope = vue.effectScope(true); | ||
// NOTE: here we could check the window object for a state and directly set it | ||
// if there is anything like it with Vue 3 SSR | ||
const state = vue.ref({}); | ||
let localApp; | ||
const state = scope.run(() => vue.ref({})); | ||
let _p = []; | ||
@@ -835,3 +877,3 @@ // plugins added before calling app.use(pinia) | ||
install(app) { | ||
pinia._a = localApp = app; | ||
pinia._a = app; | ||
app.provide(piniaSymbol, pinia); | ||
@@ -843,2 +885,5 @@ app.config.globalProperties.$pinia = pinia; | ||
setActivePinia(pinia); | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
registerPiniaDevtools(app, pinia); | ||
} | ||
} | ||
@@ -848,3 +893,3 @@ toBeInstalled.forEach((plugin) => _p.push(plugin)); | ||
use(plugin) { | ||
if (!localApp) { | ||
if (!this._a) { | ||
toBeInstalled.push(plugin); | ||
@@ -859,3 +904,6 @@ } | ||
// it's actually undefined here | ||
_a: localApp, | ||
// @ts-expect-error | ||
_a: null, | ||
_e: scope, | ||
_s: new Map(), | ||
state, | ||
@@ -871,4 +919,110 @@ }); | ||
/** | ||
* Checks if a function is a `StoreDefinition` | ||
* | ||
* @param fn - object to test | ||
* @returns true if `fn` is a StoreDefinition | ||
*/ | ||
const isUseStore = (fn) => { | ||
return typeof fn === 'function' && typeof fn.$id === 'string'; | ||
}; | ||
/** | ||
* Mutates in place `newState` with `oldState` to _hot update_ it. It will | ||
* remove any key not existing in `newState` and recursively merge plain | ||
* objects. | ||
* | ||
* @param newState - new state object to be patched | ||
* @param oldState - old state that should be used to patch newState | ||
* @returns - newState | ||
*/ | ||
function patchObject(newState, oldState) { | ||
// no need to go through symbols because they cannot be serialized anyway | ||
for (const key in oldState) { | ||
const subPatch = oldState[key]; | ||
// skip the whole sub tree | ||
if (!(key in newState)) { | ||
continue; | ||
} | ||
const targetValue = newState[key]; | ||
if (isPlainObject(targetValue) && | ||
isPlainObject(subPatch) && | ||
!vue.isRef(subPatch) && | ||
!vue.isReactive(subPatch)) { | ||
newState[key] = patchObject(targetValue, subPatch); | ||
} | ||
else { | ||
// objects are either a bit more complex (e.g. refs) or primitives, so we | ||
// just set the whole thing | ||
newState[key] = subPatch; | ||
} | ||
} | ||
return newState; | ||
} | ||
/** | ||
* Creates an _accept_ function to pass to `import.meta.hot` in Vite applications. | ||
* | ||
* @example | ||
* ```js | ||
* const useUser = defineStore(...) | ||
* if (import.meta.hot) { | ||
* import.meta.hot.accept(acceptHMRUpdate(useUser, import.meta.hot)) | ||
* } | ||
* ``` | ||
* | ||
* @param initialUseStore - return of the defineStore to hot update | ||
* @param hot - `import.meta.hot` | ||
*/ | ||
function acceptHMRUpdate(initialUseStore, hot) { | ||
return (newModule) => { | ||
const pinia = hot.data.pinia || initialUseStore._pinia; | ||
if (!pinia) { | ||
// this store is still not used | ||
return; | ||
} | ||
// preserve the pinia instance across loads | ||
hot.data.pinia = pinia; | ||
// console.log('got data', newStore) | ||
for (const exportName in newModule) { | ||
const useStore = newModule[exportName]; | ||
// console.log('checking for', exportName) | ||
if (isUseStore(useStore) && pinia._s.has(useStore.$id)) { | ||
// console.log('Accepting update for', useStore.$id) | ||
const id = useStore.$id; | ||
if (id !== initialUseStore.$id) { | ||
console.warn(`The id of the store changed from "${initialUseStore.$id}" to "${id}". Reloading.`); | ||
// return import.meta.hot.invalidate() | ||
return hot.invalidate(); | ||
} | ||
const existingStore = pinia._s.get(id); | ||
if (!existingStore) { | ||
console.log(`skipping hmr because store doesn't exist yet`); | ||
return; | ||
} | ||
useStore(pinia, existingStore); | ||
} | ||
} | ||
}; | ||
} | ||
function addSubscription(subscriptions, callback, detached) { | ||
subscriptions.push(callback); | ||
const removeSubscription = () => { | ||
const idx = subscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
subscriptions.splice(idx, 1); | ||
} | ||
}; | ||
if (!detached && vue.getCurrentInstance()) { | ||
vue.onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function triggerSubscriptions(subscriptions, ...args) { | ||
subscriptions.forEach((callback) => { | ||
callback(...args); | ||
}); | ||
} | ||
function innerPatch(target, patchToApply) { | ||
// TODO: get all keys like symbols as well | ||
// no need to go through symbols because they cannot be serialized anyway | ||
for (const key in patchToApply) { | ||
@@ -891,36 +1045,98 @@ const subPatch = patchToApply[key]; | ||
const { assign } = Object; | ||
/** | ||
* Create an object of computed properties referring to | ||
* | ||
* @param rootStateRef - pinia.state | ||
* @param id - unique name | ||
*/ | ||
function computedFromState(rootStateRef, id) { | ||
// let asComputed = computed<T>() | ||
const reactiveObject = {}; | ||
const state = rootStateRef.value[id]; | ||
for (const key in state) { | ||
// @ts-expect-error: the key matches | ||
reactiveObject[key] = vue.computed({ | ||
get: () => rootStateRef.value[id][key], | ||
set: (value) => (rootStateRef.value[id][key] = value), | ||
}); | ||
function isComputed(o) { | ||
return o && o.effect; | ||
} | ||
function createOptionsStore(id, options, pinia, hot) { | ||
const { state, actions, getters } = options; | ||
function $reset() { | ||
pinia.state.value[id] = state ? state() : {}; | ||
} | ||
return reactiveObject; | ||
const initialState = pinia.state.value[id]; | ||
let store; | ||
function setup() { | ||
if (!initialState && (!(process.env.NODE_ENV !== 'production') || !hot)) { | ||
$reset(); | ||
} | ||
// pinia.state.value[id] = state ? state() : {} | ||
// avoid creating a state in pinia.state.value | ||
const localState = (process.env.NODE_ENV !== 'production') && hot | ||
? vue.toRefs(vue.ref(state ? state() : {}).value) | ||
: initialState || vue.toRefs(pinia.state.value[id]); | ||
return assign(localState, actions, Object.keys(getters || {}).reduce((computedGetters, name) => { | ||
computedGetters[name] = vue.markRaw(vue.computed(() => { | ||
setActivePinia(pinia); | ||
// const context = store || ref(localState).value | ||
// @ts-expect-error | ||
// return getters![name].call(context, context) | ||
return store && getters[name].call(store, store); | ||
})); | ||
return computedGetters; | ||
}, {})); | ||
} | ||
store = createSetupStore(id, setup, options, pinia, hot); | ||
store.$reset = $reset; | ||
return store; | ||
} | ||
/** | ||
* Creates a store with its state object. This is meant to be augmented with getters and actions | ||
* | ||
* @param id - unique identifier of the store, like a name. eg: main, cart, user | ||
* @param buildState - function to build the initial state | ||
* @param initialState - initial state applied to the store, Must be correctly typed to infer typings | ||
*/ | ||
function initStore($id, buildState = () => ({}), initialState) { | ||
const pinia = getActivePinia(); | ||
pinia.state.value[$id] = initialState || buildState(); | ||
// const state: Ref<S> = toRef(_p.state.value, $id) | ||
let isListening = true; | ||
const noop = () => { }; | ||
function createSetupStore($id, setup, options = {}, pinia, hot) { | ||
let scope; | ||
const buildState = options.state; | ||
const optionsForPlugin = { | ||
actions: {}, | ||
...options, | ||
}; | ||
// watcher options for $subscribe | ||
const $subscribeOptions = { deep: true, flush: 'sync' }; | ||
/* istanbul ignore else */ | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
$subscribeOptions.onTrigger = (event) => { | ||
if (isListening) { | ||
debuggerEvents = event; | ||
// avoid triggering this while the store is being built and the state is being set in pinia | ||
} | ||
else if (isListening == false && !store._hotUpdating) { | ||
// let patch send all the events together later | ||
/* istanbul ignore else */ | ||
if (Array.isArray(debuggerEvents)) { | ||
debuggerEvents.push(event); | ||
} | ||
else { | ||
console.error('🍍 debuggerEvents should be an array. This is most likely an internal Pinia bug.'); | ||
} | ||
} | ||
}; | ||
} | ||
// internal state | ||
let isListening; // set to true at the end | ||
let subscriptions = vue.markRaw([]); | ||
let actionSubscriptions = vue.markRaw([]); | ||
let debuggerEvents; | ||
const initialState = pinia.state.value[$id]; | ||
if (!initialState && (process.env.NODE_ENV !== 'production') && !hot) { | ||
// should be set in Vue 2 | ||
pinia.state.value[$id] = {}; | ||
} | ||
const hotState = vue.ref({}); | ||
if ((process.env.NODE_ENV !== 'production') && !pinia._e.active) { | ||
throw new Error('Pinia destroyed'); | ||
} | ||
// TODO: idea create skipSerialize that marks properties as non serializable and they are skipped | ||
const setupStore = pinia._e.run(() => { | ||
scope = vue.effectScope(); | ||
return scope.run(() => { | ||
// skip setting up the watcher on HMR | ||
if (!(process.env.NODE_ENV !== 'production') || !hot) { | ||
vue.watch(() => pinia.state.value[$id], (state, oldState) => { | ||
if (isListening) { | ||
triggerSubscriptions(subscriptions, { | ||
storeId: $id, | ||
type: exports.MutationType.direct, | ||
events: debuggerEvents, | ||
}, state); | ||
} | ||
}, $subscribeOptions); | ||
} | ||
return setup(); | ||
}); | ||
}); | ||
function $patch(partialStateOrMutator) { | ||
@@ -953,124 +1169,20 @@ let subscriptionMutation; | ||
// because we paused the watcher, we need to manually call the subscriptions | ||
subscriptions.forEach((callback) => { | ||
callback(subscriptionMutation, pinia.state.value[$id]); | ||
}); | ||
triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id]); | ||
} | ||
function $subscribe(callback) { | ||
subscriptions.push(callback); | ||
// watch here to link the subscription to the current active instance | ||
// e.g. inside the setup of a component | ||
const options = { deep: true, flush: 'sync' }; | ||
/* istanbul ignore else */ | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
options.onTrigger = (event) => { | ||
if (isListening) { | ||
debuggerEvents = event; | ||
} | ||
else { | ||
// let patch send all the events together later | ||
/* istanbul ignore else */ | ||
if (Array.isArray(debuggerEvents)) { | ||
debuggerEvents.push(event); | ||
} | ||
else { | ||
console.error('🍍 debuggerEvents should be an array. This is most likely an internal Pinia bug.'); | ||
} | ||
} | ||
}; | ||
const $reset = (process.env.NODE_ENV !== 'production') | ||
? () => { | ||
throw new Error(`🍍: Store "${$id}" is build using the setup syntax and does not implement $reset().`); | ||
} | ||
const stopWatcher = vue.watch(() => pinia.state.value[$id], (state, oldState) => { | ||
if (isListening) { | ||
callback({ | ||
storeId: $id, | ||
type: exports.MutationType.direct, | ||
events: debuggerEvents, | ||
}, state); | ||
} | ||
}, options); | ||
const removeSubscription = () => { | ||
const idx = subscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
subscriptions.splice(idx, 1); | ||
stopWatcher(); | ||
} | ||
}; | ||
if (vue.getCurrentInstance()) { | ||
vue.onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function $onAction(callback) { | ||
actionSubscriptions.push(callback); | ||
const removeSubscription = () => { | ||
const idx = actionSubscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
actionSubscriptions.splice(idx, 1); | ||
} | ||
}; | ||
if (vue.getCurrentInstance()) { | ||
vue.onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function $reset() { | ||
pinia.state.value[$id] = buildState(); | ||
} | ||
const storeWithState = { | ||
$id, | ||
_p: pinia, | ||
_as: actionSubscriptions, | ||
// $state is added underneath | ||
$patch, | ||
$subscribe, | ||
$onAction, | ||
$reset, | ||
}; | ||
const injectionSymbol = (process.env.NODE_ENV !== 'production') | ||
? Symbol(`PiniaStore(${$id})`) | ||
: /* istanbul ignore next */ | ||
Symbol(); | ||
return [ | ||
storeWithState, | ||
{ | ||
get: () => pinia.state.value[$id], | ||
set: (newState) => { | ||
isListening = false; | ||
pinia.state.value[$id] = newState; | ||
isListening = true; | ||
}, | ||
}, | ||
injectionSymbol, | ||
]; | ||
} | ||
const noop = () => { }; | ||
/** | ||
* Creates a store bound to the lifespan of where the function is called. This | ||
* means creating the store inside of a component's setup will bound it to the | ||
* lifespan of that component while creating it outside of a component will | ||
* create an ever living store | ||
* | ||
* @param partialStore - store with state returned by initStore | ||
* @param descriptor - descriptor to setup $state property | ||
* @param $id - unique name of the store | ||
* @param getters - getters of the store | ||
* @param actions - actions of the store | ||
*/ | ||
function buildStoreToUse(partialStore, descriptor, $id, getters = {}, actions = {}, options) { | ||
const pinia = getActivePinia(); | ||
const computedGetters = {}; | ||
for (const getterName in getters) { | ||
// @ts-ignore: it's only readonly for the users | ||
computedGetters[getterName] = vue.computed(() => { | ||
: noop; | ||
/** | ||
* Wraps an action to handle subscriptions. | ||
* | ||
* @param name - name of the action | ||
* @param action - action to wrap | ||
* @returns a wrapped action to handle subscriptions | ||
*/ | ||
function wrapAction(name, action) { | ||
return function () { | ||
setActivePinia(pinia); | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
// @ts-expect-error: the argument count is correct | ||
return getters[getterName].call(store, store); | ||
}); | ||
} | ||
const wrappedActions = {}; | ||
for (const actionName in actions) { | ||
wrappedActions[actionName] = function () { | ||
setActivePinia(pinia); | ||
const args = Array.from(arguments); | ||
const localStore = this || store; | ||
let afterCallback = noop; | ||
@@ -1084,18 +1196,99 @@ let onErrorCallback = noop; | ||
} | ||
partialStore._as.forEach((callback) => { | ||
// @ts-expect-error | ||
callback({ args, name: actionName, store: localStore, after, onError }); | ||
// @ts-expect-error | ||
triggerSubscriptions(actionSubscriptions, { | ||
args, | ||
name, | ||
store, | ||
after, | ||
onError, | ||
}); | ||
let ret; | ||
try { | ||
ret = actions[actionName].apply(localStore, args); | ||
Promise.resolve(ret).then(afterCallback).catch(onErrorCallback); | ||
ret = action.apply(this && this.$id === $id ? this : store, args); | ||
// handle sync errors | ||
} | ||
catch (error) { | ||
onErrorCallback(error); | ||
throw error; | ||
if (onErrorCallback(error) !== false) { | ||
throw error; | ||
} | ||
} | ||
return ret; | ||
if (ret instanceof Promise) { | ||
return ret | ||
.then((value) => { | ||
const newRet = afterCallback(value); | ||
// allow the afterCallback to override the return value | ||
return newRet === undefined ? value : newRet; | ||
}) | ||
.catch((error) => { | ||
if (onErrorCallback(error) !== false) { | ||
return Promise.reject(error); | ||
} | ||
}); | ||
} | ||
// allow the afterCallback to override the return value | ||
const newRet = afterCallback(ret); | ||
return newRet === undefined ? ret : newRet; | ||
}; | ||
} | ||
const _hmrPayload = /*#__PURE__*/ vue.markRaw({ | ||
actions: {}, | ||
getters: {}, | ||
state: [], | ||
hotState, | ||
}); | ||
// overwrite existing actions to support $onAction | ||
for (const key in setupStore) { | ||
const prop = setupStore[key]; | ||
if ((vue.isRef(prop) && !isComputed(prop)) || vue.isReactive(prop)) { | ||
// mark it as a piece of state to be serialized | ||
if ((process.env.NODE_ENV !== 'production') && hot) { | ||
hotState.value[key] = vue.toRef(setupStore, key); | ||
// createOptionStore already did this | ||
} | ||
else if (!buildState) { | ||
pinia.state.value[$id][key] = prop; | ||
// TODO: avoid if state exists for SSR | ||
} | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
_hmrPayload.state.push(key); | ||
} | ||
// action | ||
} | ||
else if (typeof prop === 'function') { | ||
// @ts-expect-error: we are overriding the function we avoid wrapping if | ||
// this a hot module replacement store because the hotUpdate method needs | ||
// to do it with the right context | ||
setupStore[key] = (process.env.NODE_ENV !== 'production') && hot ? prop : wrapAction(key, prop); | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
_hmrPayload.actions[key] = prop; | ||
} | ||
// list actions so they can be used in plugins | ||
// @ts-expect-error | ||
optionsForPlugin.actions[key] = prop; | ||
} | ||
else if ((process.env.NODE_ENV !== 'production')) { | ||
// add getters for devtools | ||
if (isComputed(prop)) { | ||
_hmrPayload.getters[key] = buildState | ||
? // @ts-expect-error | ||
options.getters[key] | ||
: prop; | ||
if (IS_CLIENT) { | ||
const getters = | ||
// @ts-expect-error: it should be on the store | ||
setupStore._getters || (setupStore._getters = vue.markRaw([])); | ||
getters.push(key); | ||
} | ||
} | ||
} | ||
} | ||
const partialStore = { | ||
_p: pinia, | ||
// _s: scope, | ||
$id, | ||
$onAction: addSubscription.bind(null, actionSubscriptions), | ||
$patch, | ||
$reset, | ||
$subscribe: addSubscription.bind(null, subscriptions), | ||
}; | ||
const store = vue.reactive(assign((process.env.NODE_ENV !== 'production') && IS_CLIENT | ||
@@ -1105,13 +1298,102 @@ ? // devtools custom properties | ||
_customProperties: vue.markRaw(new Set()), | ||
_hmrPayload, | ||
} | ||
: {}, partialStore, | ||
// using this means no new properties can be added as state | ||
computedFromState(pinia.state, $id), computedGetters, wrappedActions)); | ||
: {}, partialStore, setupStore)); | ||
// use this instead of a computed with setter to be able to create it anywhere | ||
// without linking the computed lifespan to wherever the store is first | ||
// created. | ||
Object.defineProperty(store, '$state', descriptor); | ||
// add getters for devtools | ||
if ((process.env.NODE_ENV !== 'production') && IS_CLIENT) { | ||
store._getters = vue.markRaw(Object.keys(getters)); | ||
Object.defineProperty(store, '$state', { | ||
get: () => ((process.env.NODE_ENV !== 'production') && hot ? hotState.value : pinia.state.value[$id]), | ||
set: (state) => { | ||
if ((process.env.NODE_ENV !== 'production') && hot) { | ||
throw new Error('cannot set hotState'); | ||
} | ||
pinia.state.value[$id] = state; | ||
}, | ||
}); | ||
// add the hotUpdate before plugins to allow them to override it | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
store._hotUpdate = vue.markRaw((newStore) => { | ||
store._hotUpdating = true; | ||
newStore._hmrPayload.state.forEach((stateKey) => { | ||
if (stateKey in store.$state) { | ||
const newStateTarget = newStore.$state[stateKey]; | ||
const oldStateSource = store.$state[stateKey]; | ||
if (typeof newStateTarget === 'object' && | ||
isPlainObject(newStateTarget) && | ||
isPlainObject(oldStateSource)) { | ||
patchObject(newStateTarget, oldStateSource); | ||
} | ||
else { | ||
// transfer the ref | ||
newStore.$state[stateKey] = oldStateSource; | ||
} | ||
} | ||
// patch direct access properties to allow store.stateProperty to work as | ||
// store.$state.stateProperty | ||
// @ts-expect-error | ||
store[stateKey] = vue.toRef(newStore.$state, stateKey); | ||
}); | ||
// remove deleted state properties | ||
Object.keys(store.$state).forEach((stateKey) => { | ||
if (!(stateKey in newStore.$state)) { | ||
delete store[stateKey]; | ||
} | ||
}); | ||
// avoid devtools logging this as a mutation | ||
isListening = false; | ||
pinia.state.value[$id] = vue.toRef(newStore._hmrPayload, 'hotState'); | ||
isListening = true; | ||
for (const actionName in newStore._hmrPayload.actions) { | ||
const action = newStore[actionName]; | ||
// @ts-expect-error: new key | ||
store[actionName] = | ||
// new line forced for TS | ||
wrapAction(actionName, action); | ||
} | ||
// TODO: does this work in both setup and option store? | ||
for (const getterName in newStore._hmrPayload.getters) { | ||
const getter = newStore._hmrPayload.getters[getterName]; | ||
// @ts-expect-error | ||
store[getterName] = | ||
// --- | ||
buildState | ||
? // special handling of options api | ||
vue.computed(() => { | ||
setActivePinia(pinia); | ||
return getter.call(store, store); | ||
}) | ||
: getter; | ||
} | ||
// remove deleted getters | ||
Object.keys(store._hmrPayload.getters).forEach((key) => { | ||
if (!(key in newStore._hmrPayload.getters)) { | ||
delete store[key]; | ||
} | ||
}); | ||
// remove old actions | ||
Object.keys(store._hmrPayload.actions).forEach((key) => { | ||
if (!(key in newStore._hmrPayload.actions)) { | ||
delete store[key]; | ||
} | ||
}); | ||
// update the values used in devtools and to allow deleting new properties later on | ||
store._hmrPayload = newStore._hmrPayload; | ||
store._getters = newStore._getters; | ||
store._hotUpdating = false; | ||
}); | ||
const nonEnumerable = { | ||
writable: true, | ||
configurable: true, | ||
// avoid warning on devtools trying to display this property | ||
enumerable: false, | ||
}; | ||
if (IS_CLIENT) { | ||
['_p', '_hmrPayload', '_getters', '_customProperties'].forEach((p) => { | ||
Object.defineProperty(store, p, { | ||
value: store[p], | ||
...nonEnumerable, | ||
}); | ||
}); | ||
} | ||
} | ||
@@ -1121,4 +1403,9 @@ // apply all plugins | ||
if ((process.env.NODE_ENV !== 'production') && IS_CLIENT) { | ||
// @ts-expect-error: conflict between A and ActionsTree | ||
const extensions = extender({ store, app: pinia._a, pinia, options }); | ||
const extensions = extender({ | ||
store, | ||
app: pinia._a, | ||
pinia, | ||
// @ts-expect-error | ||
options: optionsForPlugin, | ||
}); | ||
Object.keys(extensions || {}).forEach((key) => store._customProperties.add(key)); | ||
@@ -1128,19 +1415,34 @@ assign(store, extensions); | ||
else { | ||
// @ts-expect-error: conflict between A and ActionsTree | ||
assign(store, extender({ store, app: pinia._a, pinia, options })); | ||
assign(store, extender({ | ||
store, | ||
app: pinia._a, | ||
pinia, | ||
// @ts-expect-error | ||
options: optionsForPlugin, | ||
})); | ||
} | ||
}); | ||
if (initialState) { | ||
(options.hydrate || innerPatch)(store, initialState); | ||
} | ||
isListening = true; | ||
return store; | ||
} | ||
/** | ||
* Creates a `useStore` function that retrieves the store instance | ||
* @param options - options to define the store | ||
*/ | ||
function defineStore(options) { | ||
const { id, state, getters, actions } = options; | ||
function useStore(pinia) { | ||
function defineStore( | ||
// TODO: add proper types from above | ||
idOrOptions, setup, setupOptions) { | ||
let id; | ||
let options; | ||
const isSetupStore = typeof setup === 'function'; | ||
if (typeof idOrOptions === 'string') { | ||
id = idOrOptions; | ||
// the option store setup will contain the actual options in this case | ||
options = isSetupStore ? setupOptions : setup; | ||
} | ||
else { | ||
options = idOrOptions; | ||
id = idOrOptions.id; | ||
} | ||
function useStore(pinia, hot) { | ||
const currentInstance = vue.getCurrentInstance(); | ||
// only run provide when pinia hasn't been manually passed | ||
const shouldProvide = currentInstance && !pinia; | ||
// avoid injecting if `useStore` when not possible | ||
pinia = | ||
@@ -1155,33 +1457,36 @@ // in test mode, ignore the argument provided as we can always retrieve a | ||
pinia = getActivePinia(); | ||
let storeCache = storesMap.get(pinia); | ||
if (!storeCache) | ||
storesMap.set(pinia, (storeCache = new Map())); | ||
let storeAndDescriptor = storeCache.get(id); | ||
let store; | ||
if (!storeAndDescriptor) { | ||
storeAndDescriptor = initStore(id, state, pinia.state.value[id]); | ||
// @ts-expect-error: annoying to type | ||
storeCache.set(id, storeAndDescriptor); | ||
store = buildStoreToUse(storeAndDescriptor[0], storeAndDescriptor[1], id, getters, actions, options); | ||
// allow children to reuse this store instance to avoid creating a new | ||
// store for each child | ||
if (shouldProvide) { | ||
vue.provide(storeAndDescriptor[2], store); | ||
if (!pinia._s.has(id)) { | ||
pinia._s.set(id, isSetupStore | ||
? createSetupStore(id, setup, options, pinia) | ||
: createOptionsStore(id, options, pinia)); | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
// @ts-expect-error: not the right inferred type | ||
useStore._pinia = pinia; | ||
} | ||
} | ||
else { | ||
store = | ||
(currentInstance && vue.inject(storeAndDescriptor[2], null)) || | ||
buildStoreToUse(storeAndDescriptor[0], storeAndDescriptor[1], id, getters, actions, options); | ||
const store = pinia._s.get(id); | ||
if ((process.env.NODE_ENV !== 'production') && hot) { | ||
const hotId = '__hot:' + id; | ||
const newStore = isSetupStore | ||
? createSetupStore(hotId, setup, options, pinia, true) | ||
: createOptionsStore(hotId, assign({}, options), pinia, true); | ||
hot._hotUpdate(newStore); | ||
// cleanup the state properties and the store from the cache | ||
delete pinia.state.value[hotId]; | ||
pinia._s.delete(hotId); | ||
} | ||
// save stores in instances to access them devtools | ||
if ((process.env.NODE_ENV !== 'production') && IS_CLIENT && currentInstance && currentInstance.proxy) { | ||
if ((process.env.NODE_ENV !== 'production') && | ||
IS_CLIENT && | ||
currentInstance && | ||
currentInstance.proxy && | ||
// avoid adding stores that are just built for hot module replacement | ||
!hot) { | ||
const vm = currentInstance.proxy; | ||
const cache = '_pStores' in vm ? vm._pStores : (vm._pStores = {}); | ||
// @ts-expect-error: still can't cast Store with generics to Store | ||
cache[store.$id] = store; | ||
cache[id] = store; | ||
} | ||
// StoreGeneric cannot be casted towards Store | ||
return store; | ||
} | ||
// needed by map helpers | ||
useStore.$id = id; | ||
@@ -1191,8 +1496,2 @@ return useStore; | ||
function getCachedStore(vm, useStore) { | ||
const cache = '_pStores' in vm ? vm._pStores : (vm._pStores = {}); | ||
const id = useStore.$id; | ||
return (cache[id] || | ||
(cache[id] = useStore(vm.$pinia))); | ||
} | ||
let mapStoreSuffix = 'Store'; | ||
@@ -1245,3 +1544,3 @@ /** | ||
reduced[useStore.$id + mapStoreSuffix] = function () { | ||
return getCachedStore(this, useStore); | ||
return useStore(this.$pinia); | ||
}; | ||
@@ -1263,4 +1562,3 @@ return reduced; | ||
reduced[key] = function () { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key]; | ||
return useStore(this.$pinia)[key]; | ||
}; | ||
@@ -1270,4 +1568,5 @@ return reduced; | ||
: Object.keys(keysOrMapper).reduce((reduced, key) => { | ||
// @ts-expect-error | ||
reduced[key] = function () { | ||
const store = getCachedStore(this, useStore); | ||
const store = useStore(this.$pinia); | ||
const storeKey = keysOrMapper[key]; | ||
@@ -1301,4 +1600,3 @@ // for some reason TS is unable to infer the type of storeKey to be a | ||
reduced[key] = function (...args) { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key](...args); | ||
return useStore(this.$pinia)[key](...args); | ||
}; | ||
@@ -1310,4 +1608,3 @@ return reduced; | ||
reduced[key] = function (...args) { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[keysOrMapper[key]](...args); | ||
return useStore(this.$pinia)[keysOrMapper[key]](...args); | ||
}; | ||
@@ -1331,9 +1628,7 @@ return reduced; | ||
get() { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key]; | ||
return useStore(this.$pinia)[key]; | ||
}, | ||
set(value) { | ||
// it's easier to type it here as any | ||
// @ts-expect-error | ||
return (getCachedStore(this, useStore)[key] = value); | ||
return (useStore(this.$pinia)[key] = value); | ||
}, | ||
@@ -1347,10 +1642,7 @@ }; | ||
get() { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[keysOrMapper[key]]; | ||
return useStore(this.$pinia)[keysOrMapper[key]]; | ||
}, | ||
set(value) { | ||
// it's easier to type it here as any | ||
// @ts-expect-error | ||
return (getCachedStore(this, useStore)[keysOrMapper[key]] = | ||
value); | ||
return (useStore(this.$pinia)[keysOrMapper[key]] = value); | ||
}, | ||
@@ -1370,3 +1662,3 @@ }; | ||
* | ||
* @internal - STILL NOT RELEASED, DO NOT USE. It will be likely moved to its | ||
* @alpha - STILL NOT RELEASED, DO NOT USE. It will be likely moved to its | ||
* own package. | ||
@@ -1380,3 +1672,3 @@ * | ||
plugins.forEach((plugin) => pinia.use(plugin)); | ||
// @ts-ignore | ||
// @ts-ignore: this can fail in TS depending of the existence of jest | ||
createSpy = createSpy || (typeof jest !== undefined && jest.fn); | ||
@@ -1389,14 +1681,10 @@ if (!createSpy) { | ||
pinia.use(({ store, options }) => { | ||
if (!spiedActions.has(options.id)) { | ||
spiedActions.set(options.id, {}); | ||
if (!spiedActions.has(store.$id)) { | ||
spiedActions.set(store.$id, {}); | ||
} | ||
const actionsCache = spiedActions.get(options.id); | ||
const actionsCache = spiedActions.get(store.$id); | ||
Object.keys(options.actions || {}).forEach((action) => { | ||
actionsCache[action] = | ||
actionsCache[action] || | ||
(stubActions | ||
? createSpy() | ||
: // @ts-expect-error: | ||
createSpy(store[action])); | ||
// @ts-expect-error: | ||
(stubActions ? createSpy() : createSpy(store[action])); | ||
store[action] = actionsCache[action]; | ||
@@ -1422,2 +1710,3 @@ }); | ||
exports.acceptHMRUpdate = acceptHMRUpdate; | ||
exports.createPinia = createPinia; | ||
@@ -1424,0 +1713,0 @@ exports.createTestingPinia = createTestingPinia; |
/*! | ||
* pinia v2.0.0-beta.5 | ||
* pinia v2.0.0-rc.0 | ||
* (c) 2021 Eduardo San Martin Morote | ||
@@ -31,7 +31,2 @@ * @license MIT | ||
}; | ||
/** | ||
* Map of stores based on a Pinia instance. Allows setting and retrieving stores | ||
* for the current running application (with its pinia). | ||
*/ | ||
const storesMap = new WeakMap(); | ||
const piniaSymbol = (/* istanbul ignore next */ Symbol()); | ||
@@ -83,6 +78,6 @@ | ||
function createPinia() { | ||
const scope = vue.effectScope(true); | ||
// NOTE: here we could check the window object for a state and directly set it | ||
// if there is anything like it with Vue 3 SSR | ||
const state = vue.ref({}); | ||
let localApp; | ||
const state = scope.run(() => vue.ref({})); | ||
let _p = []; | ||
@@ -93,3 +88,3 @@ // plugins added before calling app.use(pinia) | ||
install(app) { | ||
pinia._a = localApp = app; | ||
pinia._a = app; | ||
app.provide(piniaSymbol, pinia); | ||
@@ -105,3 +100,3 @@ app.config.globalProperties.$pinia = pinia; | ||
use(plugin) { | ||
if (!localApp) { | ||
if (!this._a) { | ||
toBeInstalled.push(plugin); | ||
@@ -116,3 +111,6 @@ } | ||
// it's actually undefined here | ||
_a: localApp, | ||
// @ts-expect-error | ||
_a: null, | ||
_e: scope, | ||
_s: new Map(), | ||
state, | ||
@@ -123,4 +121,78 @@ }); | ||
/** | ||
* Checks if a function is a `StoreDefinition` | ||
* | ||
* @param fn - object to test | ||
* @returns true if `fn` is a StoreDefinition | ||
*/ | ||
const isUseStore = (fn) => { | ||
return typeof fn === 'function' && typeof fn.$id === 'string'; | ||
}; | ||
/** | ||
* Creates an _accept_ function to pass to `import.meta.hot` in Vite applications. | ||
* | ||
* @example | ||
* ```js | ||
* const useUser = defineStore(...) | ||
* if (import.meta.hot) { | ||
* import.meta.hot.accept(acceptHMRUpdate(useUser, import.meta.hot)) | ||
* } | ||
* ``` | ||
* | ||
* @param initialUseStore - return of the defineStore to hot update | ||
* @param hot - `import.meta.hot` | ||
*/ | ||
function acceptHMRUpdate(initialUseStore, hot) { | ||
return (newModule) => { | ||
const pinia = hot.data.pinia || initialUseStore._pinia; | ||
if (!pinia) { | ||
// this store is still not used | ||
return; | ||
} | ||
// preserve the pinia instance across loads | ||
hot.data.pinia = pinia; | ||
// console.log('got data', newStore) | ||
for (const exportName in newModule) { | ||
const useStore = newModule[exportName]; | ||
// console.log('checking for', exportName) | ||
if (isUseStore(useStore) && pinia._s.has(useStore.$id)) { | ||
// console.log('Accepting update for', useStore.$id) | ||
const id = useStore.$id; | ||
if (id !== initialUseStore.$id) { | ||
console.warn(`The id of the store changed from "${initialUseStore.$id}" to "${id}". Reloading.`); | ||
// return import.meta.hot.invalidate() | ||
return hot.invalidate(); | ||
} | ||
const existingStore = pinia._s.get(id); | ||
if (!existingStore) { | ||
console.log(`skipping hmr because store doesn't exist yet`); | ||
return; | ||
} | ||
useStore(pinia, existingStore); | ||
} | ||
} | ||
}; | ||
} | ||
function addSubscription(subscriptions, callback, detached) { | ||
subscriptions.push(callback); | ||
const removeSubscription = () => { | ||
const idx = subscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
subscriptions.splice(idx, 1); | ||
} | ||
}; | ||
if (!detached && vue.getCurrentInstance()) { | ||
vue.onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function triggerSubscriptions(subscriptions, ...args) { | ||
subscriptions.forEach((callback) => { | ||
callback(...args); | ||
}); | ||
} | ||
function innerPatch(target, patchToApply) { | ||
// TODO: get all keys like symbols as well | ||
// no need to go through symbols because they cannot be serialized anyway | ||
for (const key in patchToApply) { | ||
@@ -143,36 +215,74 @@ const subPatch = patchToApply[key]; | ||
const { assign } = Object; | ||
/** | ||
* Create an object of computed properties referring to | ||
* | ||
* @param rootStateRef - pinia.state | ||
* @param id - unique name | ||
*/ | ||
function computedFromState(rootStateRef, id) { | ||
// let asComputed = computed<T>() | ||
const reactiveObject = {}; | ||
const state = rootStateRef.value[id]; | ||
for (const key in state) { | ||
// @ts-expect-error: the key matches | ||
reactiveObject[key] = vue.computed({ | ||
get: () => rootStateRef.value[id][key], | ||
set: (value) => (rootStateRef.value[id][key] = value), | ||
}); | ||
function isComputed(o) { | ||
return o && o.effect; | ||
} | ||
function createOptionsStore(id, options, pinia, hot) { | ||
const { state, actions, getters } = options; | ||
function $reset() { | ||
pinia.state.value[id] = state ? state() : {}; | ||
} | ||
return reactiveObject; | ||
const initialState = pinia.state.value[id]; | ||
let store; | ||
function setup() { | ||
if (!initialState && (!false )) { | ||
$reset(); | ||
} | ||
// pinia.state.value[id] = state ? state() : {} | ||
// avoid creating a state in pinia.state.value | ||
const localState = initialState || vue.toRefs(pinia.state.value[id]); | ||
return assign(localState, actions, Object.keys(getters || {}).reduce((computedGetters, name) => { | ||
computedGetters[name] = vue.markRaw(vue.computed(() => { | ||
setActivePinia(pinia); | ||
// const context = store || ref(localState).value | ||
// @ts-expect-error | ||
// return getters![name].call(context, context) | ||
return store && getters[name].call(store, store); | ||
})); | ||
return computedGetters; | ||
}, {})); | ||
} | ||
store = createSetupStore(id, setup, options, pinia, hot); | ||
store.$reset = $reset; | ||
return store; | ||
} | ||
/** | ||
* Creates a store with its state object. This is meant to be augmented with getters and actions | ||
* | ||
* @param id - unique identifier of the store, like a name. eg: main, cart, user | ||
* @param buildState - function to build the initial state | ||
* @param initialState - initial state applied to the store, Must be correctly typed to infer typings | ||
*/ | ||
function initStore($id, buildState = () => ({}), initialState) { | ||
const pinia = getActivePinia(); | ||
pinia.state.value[$id] = initialState || buildState(); | ||
// const state: Ref<S> = toRef(_p.state.value, $id) | ||
let isListening = true; | ||
const noop = () => { }; | ||
function createSetupStore($id, setup, options = {}, pinia, hot) { | ||
let scope; | ||
const buildState = options.state; | ||
const optionsForPlugin = { | ||
actions: {}, | ||
...options, | ||
}; | ||
// watcher options for $subscribe | ||
const $subscribeOptions = { deep: true, flush: 'sync' }; | ||
// internal state | ||
let isListening; // set to true at the end | ||
let subscriptions = vue.markRaw([]); | ||
let actionSubscriptions = vue.markRaw([]); | ||
let debuggerEvents; | ||
const initialState = pinia.state.value[$id]; | ||
if (!initialState && false && !hot) { | ||
// should be set in Vue 2 | ||
pinia.state.value[$id] = {}; | ||
} | ||
vue.ref({}); | ||
// TODO: idea create skipSerialize that marks properties as non serializable and they are skipped | ||
const setupStore = pinia._e.run(() => { | ||
scope = vue.effectScope(); | ||
return scope.run(() => { | ||
// skip setting up the watcher on HMR | ||
{ | ||
vue.watch(() => pinia.state.value[$id], (state, oldState) => { | ||
if (isListening) { | ||
triggerSubscriptions(subscriptions, { | ||
storeId: $id, | ||
type: exports.MutationType.direct, | ||
events: debuggerEvents, | ||
}, state); | ||
} | ||
}, $subscribeOptions); | ||
} | ||
return setup(); | ||
}); | ||
}); | ||
function $patch(partialStateOrMutator) { | ||
@@ -200,104 +310,16 @@ let subscriptionMutation; | ||
// because we paused the watcher, we need to manually call the subscriptions | ||
subscriptions.forEach((callback) => { | ||
callback(subscriptionMutation, pinia.state.value[$id]); | ||
}); | ||
triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id]); | ||
} | ||
function $subscribe(callback) { | ||
subscriptions.push(callback); | ||
// watch here to link the subscription to the current active instance | ||
// e.g. inside the setup of a component | ||
const options = { deep: true, flush: 'sync' }; | ||
const stopWatcher = vue.watch(() => pinia.state.value[$id], (state, oldState) => { | ||
if (isListening) { | ||
callback({ | ||
storeId: $id, | ||
type: exports.MutationType.direct, | ||
events: debuggerEvents, | ||
}, state); | ||
} | ||
}, options); | ||
const removeSubscription = () => { | ||
const idx = subscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
subscriptions.splice(idx, 1); | ||
stopWatcher(); | ||
} | ||
}; | ||
if (vue.getCurrentInstance()) { | ||
vue.onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function $onAction(callback) { | ||
actionSubscriptions.push(callback); | ||
const removeSubscription = () => { | ||
const idx = actionSubscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
actionSubscriptions.splice(idx, 1); | ||
} | ||
}; | ||
if (vue.getCurrentInstance()) { | ||
vue.onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function $reset() { | ||
pinia.state.value[$id] = buildState(); | ||
} | ||
const storeWithState = { | ||
$id, | ||
_p: pinia, | ||
_as: actionSubscriptions, | ||
// $state is added underneath | ||
$patch, | ||
$subscribe, | ||
$onAction, | ||
$reset, | ||
}; | ||
const injectionSymbol = /* istanbul ignore next */ | ||
Symbol(); | ||
return [ | ||
storeWithState, | ||
{ | ||
get: () => pinia.state.value[$id], | ||
set: (newState) => { | ||
isListening = false; | ||
pinia.state.value[$id] = newState; | ||
isListening = true; | ||
}, | ||
}, | ||
injectionSymbol, | ||
]; | ||
} | ||
const noop = () => { }; | ||
/** | ||
* Creates a store bound to the lifespan of where the function is called. This | ||
* means creating the store inside of a component's setup will bound it to the | ||
* lifespan of that component while creating it outside of a component will | ||
* create an ever living store | ||
* | ||
* @param partialStore - store with state returned by initStore | ||
* @param descriptor - descriptor to setup $state property | ||
* @param $id - unique name of the store | ||
* @param getters - getters of the store | ||
* @param actions - actions of the store | ||
*/ | ||
function buildStoreToUse(partialStore, descriptor, $id, getters = {}, actions = {}, options) { | ||
const pinia = getActivePinia(); | ||
const computedGetters = {}; | ||
for (const getterName in getters) { | ||
// @ts-ignore: it's only readonly for the users | ||
computedGetters[getterName] = vue.computed(() => { | ||
const $reset = noop; | ||
/** | ||
* Wraps an action to handle subscriptions. | ||
* | ||
* @param name - name of the action | ||
* @param action - action to wrap | ||
* @returns a wrapped action to handle subscriptions | ||
*/ | ||
function wrapAction(name, action) { | ||
return function () { | ||
setActivePinia(pinia); | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
// @ts-expect-error: the argument count is correct | ||
return getters[getterName].call(store, store); | ||
}); | ||
} | ||
const wrappedActions = {}; | ||
for (const actionName in actions) { | ||
wrappedActions[actionName] = function () { | ||
setActivePinia(pinia); | ||
const args = Array.from(arguments); | ||
const localStore = this || store; | ||
let afterCallback = noop; | ||
@@ -311,45 +333,114 @@ let onErrorCallback = noop; | ||
} | ||
partialStore._as.forEach((callback) => { | ||
// @ts-expect-error | ||
callback({ args, name: actionName, store: localStore, after, onError }); | ||
// @ts-expect-error | ||
triggerSubscriptions(actionSubscriptions, { | ||
args, | ||
name, | ||
store, | ||
after, | ||
onError, | ||
}); | ||
let ret; | ||
try { | ||
ret = actions[actionName].apply(localStore, args); | ||
Promise.resolve(ret).then(afterCallback).catch(onErrorCallback); | ||
ret = action.apply(this && this.$id === $id ? this : store, args); | ||
// handle sync errors | ||
} | ||
catch (error) { | ||
onErrorCallback(error); | ||
throw error; | ||
if (onErrorCallback(error) !== false) { | ||
throw error; | ||
} | ||
} | ||
return ret; | ||
if (ret instanceof Promise) { | ||
return ret | ||
.then((value) => { | ||
const newRet = afterCallback(value); | ||
// allow the afterCallback to override the return value | ||
return newRet === undefined ? value : newRet; | ||
}) | ||
.catch((error) => { | ||
if (onErrorCallback(error) !== false) { | ||
return Promise.reject(error); | ||
} | ||
}); | ||
} | ||
// allow the afterCallback to override the return value | ||
const newRet = afterCallback(ret); | ||
return newRet === undefined ? ret : newRet; | ||
}; | ||
} | ||
const store = vue.reactive(assign({}, partialStore, | ||
// using this means no new properties can be added as state | ||
computedFromState(pinia.state, $id), computedGetters, wrappedActions)); | ||
// overwrite existing actions to support $onAction | ||
for (const key in setupStore) { | ||
const prop = setupStore[key]; | ||
if ((vue.isRef(prop) && !isComputed(prop)) || vue.isReactive(prop)) { | ||
// mark it as a piece of state to be serialized | ||
if (!buildState) { | ||
pinia.state.value[$id][key] = prop; | ||
// TODO: avoid if state exists for SSR | ||
} | ||
// action | ||
} | ||
else if (typeof prop === 'function') { | ||
// @ts-expect-error: we are overriding the function we avoid wrapping if | ||
// this a hot module replacement store because the hotUpdate method needs | ||
// to do it with the right context | ||
setupStore[key] = wrapAction(key, prop); | ||
// list actions so they can be used in plugins | ||
// @ts-expect-error | ||
optionsForPlugin.actions[key] = prop; | ||
} | ||
else ; | ||
} | ||
const partialStore = { | ||
_p: pinia, | ||
// _s: scope, | ||
$id, | ||
$onAction: addSubscription.bind(null, actionSubscriptions), | ||
$patch, | ||
$reset, | ||
$subscribe: addSubscription.bind(null, subscriptions), | ||
}; | ||
const store = vue.reactive(assign({}, partialStore, setupStore)); | ||
// use this instead of a computed with setter to be able to create it anywhere | ||
// without linking the computed lifespan to wherever the store is first | ||
// created. | ||
Object.defineProperty(store, '$state', descriptor); | ||
Object.defineProperty(store, '$state', { | ||
get: () => (pinia.state.value[$id]), | ||
set: (state) => { | ||
pinia.state.value[$id] = state; | ||
}, | ||
}); | ||
// apply all plugins | ||
pinia._p.forEach((extender) => { | ||
{ | ||
// @ts-expect-error: conflict between A and ActionsTree | ||
assign(store, extender({ store, app: pinia._a, pinia, options })); | ||
assign(store, extender({ | ||
store, | ||
app: pinia._a, | ||
pinia, | ||
// @ts-expect-error | ||
options: optionsForPlugin, | ||
})); | ||
} | ||
}); | ||
if (initialState) { | ||
(options.hydrate || innerPatch)(store, initialState); | ||
} | ||
isListening = true; | ||
return store; | ||
} | ||
/** | ||
* Creates a `useStore` function that retrieves the store instance | ||
* @param options - options to define the store | ||
*/ | ||
function defineStore(options) { | ||
const { id, state, getters, actions } = options; | ||
function useStore(pinia) { | ||
function defineStore( | ||
// TODO: add proper types from above | ||
idOrOptions, setup, setupOptions) { | ||
let id; | ||
let options; | ||
const isSetupStore = typeof setup === 'function'; | ||
if (typeof idOrOptions === 'string') { | ||
id = idOrOptions; | ||
// the option store setup will contain the actual options in this case | ||
options = isSetupStore ? setupOptions : setup; | ||
} | ||
else { | ||
options = idOrOptions; | ||
id = idOrOptions.id; | ||
} | ||
function useStore(pinia, hot) { | ||
const currentInstance = vue.getCurrentInstance(); | ||
// only run provide when pinia hasn't been manually passed | ||
const shouldProvide = currentInstance && !pinia; | ||
// avoid injecting if `useStore` when not possible | ||
pinia = | ||
@@ -364,26 +455,11 @@ // in test mode, ignore the argument provided as we can always retrieve a | ||
pinia = getActivePinia(); | ||
let storeCache = storesMap.get(pinia); | ||
if (!storeCache) | ||
storesMap.set(pinia, (storeCache = new Map())); | ||
let storeAndDescriptor = storeCache.get(id); | ||
let store; | ||
if (!storeAndDescriptor) { | ||
storeAndDescriptor = initStore(id, state, pinia.state.value[id]); | ||
// @ts-expect-error: annoying to type | ||
storeCache.set(id, storeAndDescriptor); | ||
store = buildStoreToUse(storeAndDescriptor[0], storeAndDescriptor[1], id, getters, actions, options); | ||
// allow children to reuse this store instance to avoid creating a new | ||
// store for each child | ||
if (shouldProvide) { | ||
vue.provide(storeAndDescriptor[2], store); | ||
} | ||
if (!pinia._s.has(id)) { | ||
pinia._s.set(id, isSetupStore | ||
? createSetupStore(id, setup, options, pinia) | ||
: createOptionsStore(id, options, pinia)); | ||
} | ||
else { | ||
store = | ||
(currentInstance && vue.inject(storeAndDescriptor[2], null)) || | ||
buildStoreToUse(storeAndDescriptor[0], storeAndDescriptor[1], id, getters, actions, options); | ||
} | ||
const store = pinia._s.get(id); | ||
// StoreGeneric cannot be casted towards Store | ||
return store; | ||
} | ||
// needed by map helpers | ||
useStore.$id = id; | ||
@@ -393,8 +469,2 @@ return useStore; | ||
function getCachedStore(vm, useStore) { | ||
const cache = '_pStores' in vm ? vm._pStores : (vm._pStores = {}); | ||
const id = useStore.$id; | ||
return (cache[id] || | ||
(cache[id] = useStore(vm.$pinia))); | ||
} | ||
let mapStoreSuffix = 'Store'; | ||
@@ -438,3 +508,3 @@ /** | ||
reduced[useStore.$id + mapStoreSuffix] = function () { | ||
return getCachedStore(this, useStore); | ||
return useStore(this.$pinia); | ||
}; | ||
@@ -456,4 +526,3 @@ return reduced; | ||
reduced[key] = function () { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key]; | ||
return useStore(this.$pinia)[key]; | ||
}; | ||
@@ -463,4 +532,5 @@ return reduced; | ||
: Object.keys(keysOrMapper).reduce((reduced, key) => { | ||
// @ts-expect-error | ||
reduced[key] = function () { | ||
const store = getCachedStore(this, useStore); | ||
const store = useStore(this.$pinia); | ||
const storeKey = keysOrMapper[key]; | ||
@@ -494,4 +564,3 @@ // for some reason TS is unable to infer the type of storeKey to be a | ||
reduced[key] = function (...args) { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key](...args); | ||
return useStore(this.$pinia)[key](...args); | ||
}; | ||
@@ -503,4 +572,3 @@ return reduced; | ||
reduced[key] = function (...args) { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[keysOrMapper[key]](...args); | ||
return useStore(this.$pinia)[keysOrMapper[key]](...args); | ||
}; | ||
@@ -524,9 +592,7 @@ return reduced; | ||
get() { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key]; | ||
return useStore(this.$pinia)[key]; | ||
}, | ||
set(value) { | ||
// it's easier to type it here as any | ||
// @ts-expect-error | ||
return (getCachedStore(this, useStore)[key] = value); | ||
return (useStore(this.$pinia)[key] = value); | ||
}, | ||
@@ -540,10 +606,7 @@ }; | ||
get() { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[keysOrMapper[key]]; | ||
return useStore(this.$pinia)[keysOrMapper[key]]; | ||
}, | ||
set(value) { | ||
// it's easier to type it here as any | ||
// @ts-expect-error | ||
return (getCachedStore(this, useStore)[keysOrMapper[key]] = | ||
value); | ||
return (useStore(this.$pinia)[keysOrMapper[key]] = value); | ||
}, | ||
@@ -563,3 +626,3 @@ }; | ||
* | ||
* @internal - STILL NOT RELEASED, DO NOT USE. It will be likely moved to its | ||
* @alpha - STILL NOT RELEASED, DO NOT USE. It will be likely moved to its | ||
* own package. | ||
@@ -573,3 +636,3 @@ * | ||
plugins.forEach((plugin) => pinia.use(plugin)); | ||
// @ts-ignore | ||
// @ts-ignore: this can fail in TS depending of the existence of jest | ||
createSpy = createSpy || (typeof jest !== undefined && jest.fn); | ||
@@ -582,14 +645,10 @@ if (!createSpy) { | ||
pinia.use(({ store, options }) => { | ||
if (!spiedActions.has(options.id)) { | ||
spiedActions.set(options.id, {}); | ||
if (!spiedActions.has(store.$id)) { | ||
spiedActions.set(store.$id, {}); | ||
} | ||
const actionsCache = spiedActions.get(options.id); | ||
const actionsCache = spiedActions.get(store.$id); | ||
Object.keys(options.actions || {}).forEach((action) => { | ||
actionsCache[action] = | ||
actionsCache[action] || | ||
(stubActions | ||
? createSpy() | ||
: // @ts-expect-error: | ||
createSpy(store[action])); | ||
// @ts-expect-error: | ||
(stubActions ? createSpy() : createSpy(store[action])); | ||
store[action] = actionsCache[action]; | ||
@@ -615,2 +674,3 @@ }); | ||
exports.acceptHMRUpdate = acceptHMRUpdate; | ||
exports.createPinia = createPinia; | ||
@@ -617,0 +677,0 @@ exports.createTestingPinia = createTestingPinia; |
import { App } from 'vue'; | ||
import { ComputedRef } from 'vue'; | ||
import { DebuggerEvent } from 'vue'; | ||
import { EffectScope } from 'vue'; | ||
import { Plugin as Plugin_2 } from 'vue'; | ||
@@ -8,4 +10,20 @@ import { Ref } from 'vue'; | ||
/** | ||
* Type of an object of Actions | ||
* Creates an _accept_ function to pass to `import.meta.hot` in Vite applications. | ||
* | ||
* @example | ||
* ```js | ||
* const useUser = defineStore(...) | ||
* if (import.meta.hot) { | ||
* import.meta.hot.accept(acceptHMRUpdate(useUser, import.meta.hot)) | ||
* } | ||
* ``` | ||
* | ||
* @param initialUseStore - return of the defineStore to hot update | ||
* @param hot - `import.meta.hot` | ||
*/ | ||
export declare function acceptHMRUpdate(initialUseStore: StoreDefinition, hot: any): (newModule: any) => any; | ||
/** | ||
* Type of an object of Actions. | ||
* | ||
* @internal | ||
@@ -28,3 +46,3 @@ */ | ||
* | ||
* @internal - STILL NOT RELEASED, DO NOT USE. It will be likely moved to its | ||
* @alpha - STILL NOT RELEASED, DO NOT USE. It will be likely moved to its | ||
* own package. | ||
@@ -37,2 +55,7 @@ * | ||
/** | ||
* Recursive `Partial<T>`. Used by {@link Store.$patch}. | ||
* | ||
* @internal | ||
*/ | ||
declare type DeepPartial<T> = { | ||
@@ -43,12 +66,43 @@ [K in keyof T]?: DeepPartial<T[K]>; | ||
/** | ||
* Options parameter of `defineStore()` for setup stores. Can be extended to | ||
* augment stores with the plugin API. @see {@link DefineStoreOptionsBase}. | ||
*/ | ||
export declare interface DefineSetupStoreOptions<Id extends string, S extends StateTree, G, A> extends DefineStoreOptionsBase<S, Store<Id, S, G, A>> { | ||
/** | ||
* Extracted actions. Added by useStore(). SHOULD NOT be added by the user when | ||
* creating the store. Can be used in plugins to get the list of actions in a | ||
* store defined with a setup function. Note this is always defined | ||
*/ | ||
actions?: A; | ||
} | ||
/** | ||
* Creates a `useStore` function that retrieves the store instance | ||
* | ||
* @param id - id of the store (must be unique) | ||
* @param options - options to define the store | ||
*/ | ||
export declare function defineStore<Id extends string, S extends StateTree, G extends GettersTree<S>, A>(options: DefineStoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A>; | ||
export declare function defineStore<Id extends string, S extends StateTree = {}, G extends GettersTree<S> = {}, A = {}>(id: Id, options: Omit<DefineStoreOptions<Id, S, G, A>, 'id'>): StoreDefinition<Id, S, G, A>; | ||
/** | ||
* Options parameter of `defineStore()`. Can be extended to augment stores with | ||
* the plugin API. | ||
* Creates a `useStore` function that retrieves the store instance | ||
* | ||
* @param options - options to define the store | ||
*/ | ||
export declare interface DefineStoreOptions<Id extends string, S extends StateTree, G extends GettersTree<S>, A> { | ||
export declare function defineStore<Id extends string, S extends StateTree = {}, G extends GettersTree<S> = {}, A = {}>(options: DefineStoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A>; | ||
/** | ||
* Creates a `useStore` function that retrieves the store instance | ||
* | ||
* @param id - id of the store (must be unique) | ||
* @param storeSetup - function that defines the store | ||
* @param options - extra options | ||
*/ | ||
export declare function defineStore<Id extends string, SS>(id: Id, storeSetup: () => SS, options?: DefineSetupStoreOptions<Id, _ExtractStateFromSetupStore<SS>, _ExtractGettersFromSetupStore<SS>, _ExtractActionsFromSetupStore<SS>>): StoreDefinition<Id, _ExtractStateFromSetupStore<SS>, _ExtractGettersFromSetupStore<SS>, _ExtractActionsFromSetupStore<SS>>; | ||
/** | ||
* Options parameter of `defineStore()` for option stores. Can be extended to | ||
* augment stores with the plugin API. @see {@link DefineStoreOptionsBase}. | ||
*/ | ||
export declare interface DefineStoreOptions<Id extends string, S extends StateTree, G, A> extends DefineStoreOptionsBase<S, Store<Id, S, G, A>> { | ||
/** | ||
@@ -65,22 +119,72 @@ * Unique string key to identify the store across the application. | ||
*/ | ||
getters?: G & ThisType<UnwrapRef<StateTree extends S ? {} : S> & StoreWithGetters<G> & PiniaCustomProperties>; | ||
getters?: G & ThisType<UnwrapRef<S> & StoreWithGetters<G> & PiniaCustomProperties> & GettersTree<S>; | ||
/** | ||
* Optional object of actions. | ||
*/ | ||
actions?: A & ThisType<A & UnwrapRef<StateTree extends S ? {} : S> & StoreWithState<Id, S, G, A> & StoreWithGetters<GettersTree<S> extends G ? {} : G> & PiniaCustomProperties>; | ||
actions?: A & ThisType<A & UnwrapRef<S> & StoreWithState<Id, S, G, A> & StoreWithGetters<G> & PiniaCustomProperties>; | ||
} | ||
/** | ||
* Options passed to `defineStore()` that are common between option and setup | ||
* stores. Extend this interface if you want to add custom options to both kinds | ||
* of stores. | ||
*/ | ||
export declare interface DefineStoreOptionsBase<S extends StateTree, Store> { | ||
/** | ||
* Allows hydrating the store during SSR when there is an available state in | ||
* pinia.state. | ||
* | ||
* @param store - the store | ||
* @param initialState - initialState | ||
*/ | ||
hydrate?(store: Store, initialState: UnwrapRef<S>): void; | ||
} | ||
/** | ||
* Available `options` when creating a pinia plugin. | ||
*/ | ||
export declare interface DefineStoreOptionsInPlugin<Id extends string, S extends StateTree, G, A> extends Omit<DefineStoreOptions<Id, S, G, A>, 'id' | 'actions'> { | ||
/** | ||
* Extracted object of actions. Added by useStore() when the store is built | ||
* using the setup API, otherwise uses the one passed to `defineStore()`. | ||
* Defaults to an empty object if no actions are defined. | ||
*/ | ||
actions: A; | ||
/** | ||
* Id of the store. Only available when the options API is used. | ||
* | ||
* @deprecated Use `store.$id` instead. | ||
*/ | ||
id?: Id; | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
declare type _ExtractActionsFromSetupStore<SS> = _SpreadPropertiesFromObject<SS, _UnionToTuple<keyof SS>, _Method>; | ||
/** | ||
* @internal | ||
*/ | ||
declare type _ExtractGettersFromSetupStore<SS> = _SpreadPropertiesFromObject<SS, _UnionToTuple<keyof SS>, ComputedRef<any>>; | ||
/** | ||
* @internal | ||
*/ | ||
declare type _ExtractStateFromSetupStore<SS> = _SpreadStateFromStore<SS, _UnionToTuple<keyof SS>>; | ||
/** | ||
* Generic and type-unsafe version of Store. Doesn't fail on access with | ||
* strings, making it much easier to write generic functions that do not care | ||
* about the kind of store that is passed. | ||
* @deprecated Use `StoreGeneric` instead | ||
*/ | ||
export declare type GenericStore<Id extends string = string, S extends StateTree = any, G extends GettersTree<S> = GettersTree<S>, A = ActionsTree> = StoreWithState<Id, S, G, A> & UnwrapRef<S> & StoreWithGetters<G> & StoreWithActions<A> & PiniaCustomProperties<Id, S, G, A> & PiniaCustomStateProperties<S>; | ||
export declare type GenericStore<Id extends string = string, S extends StateTree = StateTree, G = GettersTree<S>, A = ActionsTree> = StoreWithState<Id, S, G, A> & UnwrapRef<S> & StoreWithGetters<G> & A & PiniaCustomProperties<Id, S, G, A> & PiniaCustomStateProperties<S>; | ||
/** | ||
* Type of an object of Getters that infers the argument | ||
* Type of an object of Getters that infers the argument. | ||
* | ||
* @internal | ||
*/ | ||
export declare type GettersTree<S extends StateTree> = Record<string, ((state: UnwrapRef<(StateTree extends S ? {} : S) & PiniaCustomStateProperties<S>>) => any) | (() => any)>; | ||
export declare type GettersTree<S extends StateTree> = Record<string, ((state: UnwrapRef<S> & UnwrapRef<PiniaCustomStateProperties<S>>) => any) | (() => any)>; | ||
@@ -220,3 +324,3 @@ /** | ||
*/ | ||
export declare function mapState<Id extends string, S extends StateTree, G extends GettersTree<S>, A>(useStore: StoreDefinition<Id, S, G, A>, keys: Array<keyof S | keyof G>): _MapStateReturn<S, G>; | ||
export declare function mapState<Id extends string, S extends StateTree, G extends GettersTree<S>, A, Keys extends keyof S | keyof G>(useStore: StoreDefinition<Id, S, G, A>, keys: readonly Keys[]): _MapStateReturn<S, G, Keys>; | ||
@@ -226,4 +330,4 @@ /** | ||
*/ | ||
export declare type _MapStateObjectReturn<Id extends string, S extends StateTree, G extends GettersTree<S>, A, T extends Record<string, keyof S | keyof G | ((store: Store<Id, S, G, A>) => any)>> = { | ||
[key in keyof T]: () => T[key] extends (store: Store) => infer R ? R : T[key] extends keyof Store<Id, S, G, A> ? Store<Id, S, G, A>[T[key]] : never; | ||
export declare type _MapStateObjectReturn<Id extends string, S extends StateTree, G extends GettersTree<S>, A, T extends Record<string, keyof S | keyof G | ((store: Store<Id, S, G, A>) => any)> = {}> = { | ||
[key in keyof T]: () => T[key] extends (store: any) => infer R ? R : T[key] extends keyof Store<Id, S, G, A> ? Store<Id, S, G, A>[T[key]] : never; | ||
}; | ||
@@ -234,4 +338,4 @@ | ||
*/ | ||
export declare type _MapStateReturn<S extends StateTree, G extends GettersTree<S>> = { | ||
[key in keyof S | keyof G]: () => key extends keyof Store<string, S, G, {}> ? Store<string, S, G, {}>[key] : never; | ||
export declare type _MapStateReturn<S extends StateTree, G extends GettersTree<S>, Keys extends keyof S | keyof G = keyof S | keyof G> = { | ||
[key in Keys]: () => Store<string, S, G, {}>[key]; | ||
}; | ||
@@ -345,2 +449,6 @@ | ||
declare type _Overwrite<T, S extends any> = { | ||
[P in keyof T]: P extends keyof S ? S[P] : never; | ||
}; | ||
/** | ||
@@ -374,2 +482,14 @@ * Every application must own its own pinia to be able to create stores | ||
/** | ||
* Effect scope the pinia is attached to | ||
* | ||
* @internal | ||
*/ | ||
_e: EffectScope; | ||
/** | ||
* Registry of stores used by this pinia. | ||
* | ||
* @internal | ||
*/ | ||
_s: Map<string, StoreGeneric>; | ||
/** | ||
* Added by `createTestingPinia()` to bypass `useStore(pinia)`. | ||
@@ -383,9 +503,9 @@ * | ||
/** | ||
* Properties that are added to every store by `pinia.use()` | ||
* Properties that are added to every store by `pinia.use()`. | ||
*/ | ||
export declare interface PiniaCustomProperties<Id extends string = string, S extends StateTree = StateTree, G extends GettersTree<S> = GettersTree<S>, A = ActionsTree> { | ||
export declare interface PiniaCustomProperties<Id extends string = string, S extends StateTree = StateTree, G = GettersTree<S>, A = ActionsTree> { | ||
} | ||
/** | ||
* Properties that are added to every `store.$state` by `pinia.use()` | ||
* Properties that are added to every `store.$state` by `pinia.use()`. | ||
*/ | ||
@@ -398,3 +518,3 @@ export declare interface PiniaCustomStateProperties<S extends StateTree = StateTree> { | ||
*/ | ||
export declare interface PiniaPluginContext<Id extends string = string, S extends StateTree = StateTree, G extends GettersTree<S> = GettersTree<S>, A = ActionsTree> { | ||
export declare interface PiniaPluginContext<Id extends string = string, S extends StateTree = StateTree, G = GettersTree<S>, A = ActionsTree> { | ||
/** | ||
@@ -415,3 +535,3 @@ * pinia instance. | ||
*/ | ||
options: DefineStoreOptions<Id, S, G, A>; | ||
options: DefineStoreOptionsInPlugin<Id, S, G, A>; | ||
} | ||
@@ -432,2 +552,4 @@ | ||
declare type _PopUnion<U> = _UnionToOvlds<U> extends (a: infer A) => void ? A : never; | ||
/** | ||
@@ -456,2 +578,15 @@ * Sets or unsets the active pinia. Used in SSR and internally when calling | ||
/** | ||
* @internal | ||
*/ | ||
declare type _SpreadPropertiesFromObject<SS, K extends readonly any[], T> = K extends readonly [infer A, ...infer Rest] ? A extends string | number | symbol ? SS extends Record<A, T> ? Record<A, UnwrapRef<SS[A]>> & _SpreadPropertiesFromObject<SS, Rest, T> : _SpreadPropertiesFromObject<SS, Rest, T> : {} : {}; | ||
/** | ||
* @internal | ||
*/ | ||
declare type _SpreadStateFromStore<SS, K extends readonly any[]> = K extends readonly [ | ||
infer A, | ||
...infer Rest | ||
] ? A extends string | number | symbol ? SS extends Record<A, _Method | ComputedRef<any>> ? _SpreadStateFromStore<SS, Rest> : SS extends Record<A, any> ? Record<A, UnwrapRef<SS[A]>> & _SpreadStateFromStore<SS, Rest> : never : {} : {}; | ||
/** | ||
* Generic state of a Store | ||
@@ -464,8 +599,14 @@ */ | ||
*/ | ||
export declare type Store<Id extends string = string, S extends StateTree = StateTree, G extends GettersTree<S> = GettersTree<S>, A = ActionsTree> = StoreWithState<Id, StateTree extends S ? {} : S, G, A> & (StateTree extends S ? {} : UnwrapRef<S>) & (GettersTree<S> extends G ? {} : StoreWithGetters<G>) & (ActionsTree extends A ? {} : StoreWithActions<A>) & PiniaCustomProperties<Id, S, G, A> & PiniaCustomStateProperties<S>; | ||
export declare type Store<Id extends string = string, S extends StateTree = {}, G = {}, A = {}> = StoreWithState<Id, S, G, A> & UnwrapRef<S> & StoreWithGetters<G> & (ActionsTree extends A ? {} : A) & PiniaCustomProperties<Id, S, G, A> & PiniaCustomStateProperties<S>; | ||
/** | ||
* Extract the actions of a store type. Works with both a Setup Store or an | ||
* Options Store. | ||
*/ | ||
export declare type StoreActions<SS> = SS extends Store<string, StateTree, GettersTree<StateTree>, infer A> ? A : _ExtractActionsFromSetupStore<SS>; | ||
/** | ||
* Return type of `defineStore()`. Function that allows instantiating a store. | ||
*/ | ||
export declare interface StoreDefinition<Id extends string = string, S extends StateTree = StateTree, G extends GettersTree<S> = GettersTree<S>, A = ActionsTree> { | ||
export declare interface StoreDefinition<Id extends string = string, S extends StateTree = StateTree, G = GettersTree<S>, A = ActionsTree> { | ||
/** | ||
@@ -475,4 +616,5 @@ * Returns a store, creates it if necessary. | ||
* @param pinia - Pinia instance to retrieve the store | ||
* @param hot - dev only hot module replacement | ||
*/ | ||
(pinia?: Pinia | null | undefined): Store<Id, S, G, A>; | ||
(pinia?: Pinia | null | undefined, hot?: StoreGeneric): Store<Id, S, G, A>; | ||
/** | ||
@@ -482,5 +624,24 @@ * Id of the store. Used by map helpers. | ||
$id: Id; | ||
/** | ||
* Dev only pinia for HMR. | ||
* | ||
* @internal | ||
*/ | ||
_pinia?: Pinia; | ||
} | ||
/** | ||
* Generic and type-unsafe version of Store. Doesn't fail on access with | ||
* strings, making it much easier to write generic functions that do not care | ||
* about the kind of store that is passed. | ||
*/ | ||
export declare type StoreGeneric = Store<string, StateTree, GettersTree<StateTree>, ActionsTree>; | ||
/** | ||
* Extract the getters of a store type. Works with both a Setup Store or an | ||
* Options Store. | ||
*/ | ||
export declare type StoreGetters<SS> = SS extends Store<string, StateTree, infer G, ActionsTree> ? StoreWithGetters<G> : _ExtractGettersFromSetupStore<SS>; | ||
/** | ||
* @internal | ||
@@ -495,34 +656,99 @@ */ | ||
*/ | ||
export declare type StoreOnActionListener<Id extends string = string, S extends StateTree = StateTree, G extends GettersTree<S> = GettersTree<S>, A = ActionsTree> = (context: StoreOnActionListenerContext<Id, S, G, A>) => void; | ||
export declare type StoreOnActionListener<Id extends string, S extends StateTree, G, A> = (context: StoreOnActionListenerContext<Id, S, G, {} extends A ? ActionsTree : A>) => void; | ||
/** | ||
* Context object passed to callbacks of `store.$onAction(context => {})` | ||
* TODO: should have only the Id, the Store and Actions to generate the proper object | ||
*/ | ||
export declare type StoreOnActionListenerContext<Id extends string, S extends StateTree, G extends GettersTree<S>, A> = { | ||
[Name in keyof A]: { | ||
/** | ||
* Name of the action | ||
*/ | ||
name: Name; | ||
/** | ||
* Store that is invoking the action | ||
*/ | ||
store: Store<Id, S, G, A>; | ||
/** | ||
* Parameters passed to the action | ||
*/ | ||
args: A[Name] extends _Method ? Parameters<A[Name]> : unknown[]; | ||
/** | ||
* Sets up a hook once the action is finished. It receives the return value of | ||
* the action, if it's a Promise, it will be unwrapped. | ||
*/ | ||
after: (callback: A[Name] extends _Method ? (resolvedReturn: UnwrapPromise<ReturnType<A[Name]>>) => void : () => void) => void; | ||
/** | ||
* Sets up a hook if the action fails. | ||
*/ | ||
onError: (callback: (error: unknown) => void) => void; | ||
}; | ||
export declare type StoreOnActionListenerContext<Id extends string, S extends StateTree, G, A> = ActionsTree extends A ? _StoreOnActionListenerContext<StoreGeneric, string, ActionsTree> : { | ||
[Name in keyof A]: Name extends string ? _StoreOnActionListenerContext<Store<Id, S, G, A>, Name, A> : never; | ||
}[keyof A]; | ||
declare type _StoreOnActionListenerContext<Store, ActionName extends string, A> = { | ||
/** | ||
* Name of the action | ||
*/ | ||
name: ActionName; | ||
/** | ||
* Store that is invoking the action | ||
*/ | ||
store: Store; | ||
/** | ||
* Parameters passed to the action | ||
*/ | ||
args: A extends Record<ActionName, _Method> ? Parameters<A[ActionName]> : unknown[]; | ||
/** | ||
* Sets up a hook once the action is finished. It receives the return value | ||
* of the action, if it's a Promise, it will be unwrapped. Can return a | ||
* value (other than `undefined`) to **override** the returned value. | ||
*/ | ||
after: (callback: A extends Record<ActionName, _Method> ? (resolvedReturn: UnwrapPromise<ReturnType<A[ActionName]>>) => void | ReturnType<A[ActionName]> | UnwrapPromise<ReturnType<A[ActionName]>> : () => void) => void; | ||
/** | ||
* Sets up a hook if the action fails. Return `false` to catch the error and | ||
* stop it fro propagating. | ||
*/ | ||
onError: (callback: (error: unknown) => unknown | false) => void; | ||
}; | ||
/** | ||
* Properties of a store. | ||
*/ | ||
export declare interface StoreProperties<Id extends string> { | ||
/** | ||
* Unique identifier of the store | ||
*/ | ||
$id: Id; | ||
/** | ||
* Private property defining the pinia the store is attached to. | ||
* | ||
* @internal | ||
*/ | ||
_p: Pinia; | ||
/** | ||
* Used by devtools plugin to retrieve getters. Removed in production. | ||
* | ||
* @internal | ||
*/ | ||
_getters?: string[]; | ||
/** | ||
* Used by devtools plugin to retrieve properties added with plugins. Removed | ||
* in production. Can be used by the user to add property keys of the store | ||
* that should be displayed in devtools. | ||
* | ||
* @internal | ||
*/ | ||
_customProperties: Set<string>; | ||
/** | ||
* Handles a HMR replacement of this store. Dev Only. | ||
* | ||
* @internal | ||
*/ | ||
_hotUpdate(useStore: StoreGeneric): void; | ||
/** | ||
* Allows pausing some of the watching mechanisms while the store is being | ||
* patched with a newer version. | ||
* | ||
* @internal | ||
*/ | ||
_hotUpdating: boolean; | ||
/** | ||
* Payload of the hmr update. Dev only. | ||
* | ||
* @internal | ||
*/ | ||
_hmrPayload: { | ||
state: string[]; | ||
hotState: Ref<StateTree>; | ||
actions: ActionsTree; | ||
getters: ActionsTree; | ||
}; | ||
} | ||
/** | ||
* Extract the state of a store type. Works with both a Setup Store or an | ||
* Options Store. Note this unwraps refs. | ||
*/ | ||
export declare type StoreState<SS> = SS extends Store<string, infer S, GettersTree<StateTree>, ActionsTree> ? UnwrapRef<S> : _ExtractStateFromSetupStore<SS>; | ||
/** | ||
* Store augmented for actions | ||
@@ -549,30 +775,8 @@ * | ||
*/ | ||
export declare interface StoreWithState<Id extends string, S extends StateTree, G extends GettersTree<StateTree> = GettersTree<S>, A = ActionsTree> { | ||
export declare interface StoreWithState<Id extends string, S extends StateTree, G, A> extends StoreProperties<Id> { | ||
/** | ||
* Unique identifier of the store | ||
*/ | ||
$id: Id; | ||
/** | ||
* State of the Store. Setting it will replace the whole state. | ||
*/ | ||
$state: UnwrapRef<StateTree extends S ? {} : S> & PiniaCustomStateProperties<S>; | ||
$state: UnwrapRef<S> & PiniaCustomStateProperties<S>; | ||
/** | ||
* Private property defining the pinia the store is attached to. | ||
* | ||
* @internal | ||
*/ | ||
_p: Pinia; | ||
/** | ||
* Used by devtools plugin to retrieve getters. Removed in production. | ||
* | ||
* @internal | ||
*/ | ||
_getters?: string[]; | ||
/** | ||
* Used by devtools plugin to retrieve properties added with plugins. Removed in production. | ||
* | ||
* @internal | ||
*/ | ||
_customProperties: Set<string>; | ||
/** | ||
* Applies a state patch to current state. Allows passing nested values | ||
@@ -593,2 +797,3 @@ * | ||
* Resets the store to its initial state by building a new state object. | ||
* TODO: make this options only | ||
*/ | ||
@@ -600,16 +805,11 @@ $reset(): void; | ||
* `store.$subscribe()` inside of a component, it will be automatically | ||
* cleanup up when the component gets unmounted. | ||
* cleanup up when the component gets unmounted unless `detached` is set to | ||
* true. | ||
* | ||
* @param callback - callback passed to the watcher | ||
* @param detached - detach the subscription from the context this is called from | ||
* @returns function that removes the watcher | ||
*/ | ||
$subscribe(callback: SubscriptionCallback<S>): () => void; | ||
$subscribe(callback: SubscriptionCallback<S>, detached?: boolean): () => void; | ||
/** | ||
* Array of registered action subscriptions.Set without the generics to avoid | ||
* errors between the generic version of Store and specific stores. | ||
* | ||
* @internal | ||
*/ | ||
_as: StoreOnActionListener[]; | ||
/** | ||
* @alpha Please send feedback at https://github.com/posva/pinia/issues/240 | ||
@@ -628,3 +828,3 @@ * Setups a callback to be called every time an action is about to get | ||
* `store.$onAction()` inside of a component, it will be automatically cleanup | ||
* up when the component gets unmounted. | ||
* up when the component gets unmounted unless `detached` is set to true. | ||
* | ||
@@ -649,5 +849,6 @@ * @example | ||
* @param callback - callback called before every action | ||
* @param detached - detach the subscription from the context this is called from | ||
* @returns function that removes the watcher | ||
*/ | ||
$onAction(callback: StoreOnActionListener<Id, S, G, A>): () => void; | ||
$onAction(callback: StoreOnActionListener<Id, S, G, A>, detached?: boolean): () => void; | ||
} | ||
@@ -766,3 +967,7 @@ | ||
declare interface TestingPinia extends Pinia { | ||
/** | ||
* Pinia instance specifically designed for testing. Extends a regular | ||
* {@link Pinia} instance with test specific properties. | ||
*/ | ||
export declare interface TestingPinia extends Pinia { | ||
/** | ||
@@ -776,4 +981,21 @@ * Clears the cache of spies used for actions. | ||
declare type TuplePush<T extends any[], X> = T extends any ? _Overwrite<_TupleUnshift<T, any>, T & { | ||
[x: string]: X; | ||
}> : never; | ||
declare type _TupleUnshift<T extends any[], X> = T extends any ? ((x: X, ...t: T) => void) extends (...t: infer R) => void ? R : never : never; | ||
declare type _UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; | ||
declare type _UnionToOvlds<U> = _UnionToIntersection<U extends any ? (f: U) => void : never>; | ||
declare type _UnionToTuple<U> = _UnionToTupleRecursively<[], U>; | ||
declare type _UnionToTupleRecursively<T extends any[], U> = { | ||
1: T; | ||
0: _PopUnion<U> extends infer SELF ? _UnionToTupleRecursively<TuplePush<T, SELF>, Exclude<U, SELF>> : never; | ||
}[[U] extends [never] ? 1 : 0]; | ||
declare type UnwrapPromise<T> = T extends Promise<infer V> ? V : T; | ||
export { } |
/*! | ||
* pinia v2.0.0-beta.5 | ||
* pinia v2.0.0-rc.0 | ||
* (c) 2021 Eduardo San Martin Morote | ||
* @license MIT | ||
*/ | ||
import { warn, toRaw, ref, markRaw, getCurrentInstance, inject, provide, computed, reactive, watch, onUnmounted, isRef, isReactive, createApp } from 'vue'; | ||
import { warn, toRaw, markRaw, effectScope, ref, isRef, isReactive, getCurrentInstance, onUnmounted, inject, watch, toRef, reactive, computed, toRefs, createApp } from 'vue'; | ||
import { setupDevtoolsPlugin } from '@vue/devtools-api'; | ||
@@ -33,7 +33,2 @@ | ||
}; | ||
/** | ||
* Map of stores based on a Pinia instance. Allows setting and retrieving stores | ||
* for the current running application (with its pinia). | ||
*/ | ||
const storesMap = new WeakMap(); | ||
const piniaSymbol = (Symbol('pinia') ); | ||
@@ -422,3 +417,2 @@ | ||
key, | ||
// @ts-expect-error | ||
value: store.$state[key], | ||
@@ -432,3 +426,2 @@ })), | ||
key: getterName, | ||
// @ts-expect-error | ||
value: store[getterName], | ||
@@ -441,3 +434,2 @@ })); | ||
key, | ||
// @ts-expect-error | ||
value: store[key], | ||
@@ -488,7 +480,2 @@ })); | ||
/** | ||
* Registered stores used for devtools. | ||
*/ | ||
const registeredStores = /*#__PURE__*/ new Map(); | ||
let isAlreadyInstalled; | ||
// timeline can be paused when directly changing the state | ||
@@ -499,14 +486,17 @@ let isTimelineActive = true; | ||
const INSPECTOR_ID = 'pinia'; | ||
function addDevtools(app, store) { | ||
// TODO: we probably need to ensure the latest version of the store is kept: | ||
// without effectScope, multiple stores will be created and will have a | ||
// limited lifespan for getters. | ||
// add a dev only variable that is removed in unmounted and replace the store | ||
let hasSubscribed = true; | ||
const storeType = '🍍 ' + store.$id; | ||
if (!registeredStores.has(store.$id)) { | ||
registeredStores.set(store.$id, store); | ||
componentStateTypes.push(storeType); | ||
hasSubscribed = false; | ||
} | ||
/** | ||
* Gets the displayed name of a store in devtools | ||
* | ||
* @param id - id of the store | ||
* @returns a formatted string | ||
*/ | ||
const getStoreType = (id) => '🍍 ' + id; | ||
/** | ||
* Add the pinia plugin without any store. Allows displaying a Pinia plugin tab | ||
* as soon as it is added to the application. | ||
* | ||
* @param app - Vue application | ||
* @param pinia - pinia instance | ||
*/ | ||
function registerPiniaDevtools(app, pinia) { | ||
setupDevtoolsPlugin({ | ||
@@ -521,157 +511,161 @@ id: 'dev.esm.pinia', | ||
}, (api) => { | ||
if (!isAlreadyInstalled) { | ||
api.addTimelineLayer({ | ||
id: MUTATIONS_LAYER_ID, | ||
label: `Pinia 🍍`, | ||
color: 0xe5df88, | ||
}); | ||
api.addInspector({ | ||
id: INSPECTOR_ID, | ||
label: 'Pinia 🍍', | ||
icon: 'storage', | ||
treeFilterPlaceholder: 'Search stores', | ||
actions: [ | ||
{ | ||
icon: 'content_copy', | ||
action: () => { | ||
actionGlobalCopyState(store._p); | ||
}, | ||
tooltip: 'Serialize and copy the state', | ||
api.addTimelineLayer({ | ||
id: MUTATIONS_LAYER_ID, | ||
label: `Pinia 🍍`, | ||
color: 0xe5df88, | ||
}); | ||
api.addInspector({ | ||
id: INSPECTOR_ID, | ||
label: 'Pinia 🍍', | ||
icon: 'storage', | ||
treeFilterPlaceholder: 'Search stores', | ||
actions: [ | ||
{ | ||
icon: 'content_copy', | ||
action: () => { | ||
actionGlobalCopyState(pinia); | ||
}, | ||
{ | ||
icon: 'content_paste', | ||
action: async () => { | ||
await actionGlobalPasteState(store._p); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
tooltip: 'Replace the state with the content of your clipboard', | ||
tooltip: 'Serialize and copy the state', | ||
}, | ||
{ | ||
icon: 'content_paste', | ||
action: async () => { | ||
await actionGlobalPasteState(pinia); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
{ | ||
icon: 'save', | ||
action: () => { | ||
actionGlobalSaveState(store._p); | ||
}, | ||
tooltip: 'Save the state as a JSON file', | ||
tooltip: 'Replace the state with the content of your clipboard', | ||
}, | ||
{ | ||
icon: 'save', | ||
action: () => { | ||
actionGlobalSaveState(pinia); | ||
}, | ||
{ | ||
icon: 'folder_open', | ||
action: async () => { | ||
await actionGlobalOpenStateFile(store._p); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
tooltip: 'Import the state from a JSON file', | ||
tooltip: 'Save the state as a JSON file', | ||
}, | ||
{ | ||
icon: 'folder_open', | ||
action: async () => { | ||
await actionGlobalOpenStateFile(pinia); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
], | ||
}); | ||
api.on.inspectComponent((payload, ctx) => { | ||
const proxy = (payload.componentInstance && | ||
payload.componentInstance.proxy); | ||
if (proxy && proxy._pStores) { | ||
const piniaStores = payload.componentInstance.proxy._pStores; | ||
Object.values(piniaStores).forEach((store) => { | ||
tooltip: 'Import the state from a JSON file', | ||
}, | ||
], | ||
}); | ||
api.on.inspectComponent((payload, ctx) => { | ||
const proxy = (payload.componentInstance && | ||
payload.componentInstance.proxy); | ||
if (proxy && proxy._pStores) { | ||
const piniaStores = payload.componentInstance.proxy._pStores; | ||
Object.values(piniaStores).forEach((store) => { | ||
payload.instanceData.state.push({ | ||
type: getStoreType(store.$id), | ||
key: 'state', | ||
editable: true, | ||
value: store.$state, | ||
}); | ||
if (store._getters && store._getters.length) { | ||
payload.instanceData.state.push({ | ||
type: storeType, | ||
key: 'state', | ||
editable: true, | ||
value: store.$state, | ||
type: getStoreType(store.$id), | ||
key: 'getters', | ||
editable: false, | ||
value: store._getters.reduce((getters, key) => { | ||
getters[key] = store[key]; | ||
return getters; | ||
}, {}), | ||
}); | ||
if (store._getters && store._getters.length) { | ||
payload.instanceData.state.push({ | ||
type: storeType, | ||
key: 'getters', | ||
editable: false, | ||
value: store._getters.reduce((getters, key) => { | ||
// @ts-expect-error | ||
getters[key] = store[key]; | ||
return getters; | ||
}, {}), | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
api.on.getInspectorTree((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
let stores = [pinia]; | ||
stores = stores.concat(Array.from(pinia._s.values())); | ||
payload.rootNodes = (payload.filter | ||
? stores.filter((store) => '$id' in store | ||
? store.$id | ||
.toLowerCase() | ||
.includes(payload.filter.toLowerCase()) | ||
: PINIA_ROOT_LABEL.toLowerCase().includes(payload.filter.toLowerCase())) | ||
: stores).map(formatStoreForInspectorTree); | ||
} | ||
}); | ||
api.on.getInspectorState((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? pinia | ||
: pinia._s.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
// this could be the selected store restored for a different project | ||
// so it's better not to say anything here | ||
return; | ||
} | ||
}); | ||
api.on.getInspectorTree((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
let stores = [store._p]; | ||
stores = stores.concat(Array.from(registeredStores.values())); | ||
payload.rootNodes = (payload.filter | ||
? stores.filter((store) => '$id' in store | ||
? store.$id | ||
.toLowerCase() | ||
.includes(payload.filter.toLowerCase()) | ||
: PINIA_ROOT_LABEL.toLowerCase().includes(payload.filter.toLowerCase())) | ||
: stores).map(formatStoreForInspectorTree); | ||
if (inspectedStore) { | ||
payload.state = formatStoreForInspectorState(inspectedStore); | ||
} | ||
}); | ||
api.on.getInspectorState((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? store._p | ||
: registeredStores.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
// this could be the selected store restored for a different project | ||
// so it's better not to say anything here | ||
return; | ||
} | ||
if (inspectedStore) { | ||
payload.state = formatStoreForInspectorState(inspectedStore); | ||
} | ||
} | ||
}); | ||
api.on.editInspectorState((payload, ctx) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? pinia | ||
: pinia._s.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
return toastMessage(`store "${payload.nodeId}" not found`, 'error'); | ||
} | ||
}); | ||
api.on.editInspectorState((payload, ctx) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? store._p | ||
: registeredStores.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
return toastMessage(`store "${payload.nodeId}" not found`, 'error'); | ||
const { path } = payload; | ||
if (!isPinia(inspectedStore)) { | ||
// access only the state | ||
if (path.length !== 1 || | ||
!inspectedStore._customProperties.has(path[0]) || | ||
path[0] in inspectedStore.$state) { | ||
path.unshift('$state'); | ||
} | ||
const { path } = payload; | ||
if (!isPinia(store)) { | ||
// access only the state | ||
if (path.length !== 1 || | ||
!store._customProperties.has(path[0]) || | ||
path[0] in store.$state) { | ||
path.unshift('$state'); | ||
} | ||
} | ||
else { | ||
path.unshift('state', 'value'); | ||
} | ||
isTimelineActive = false; | ||
payload.set(inspectedStore, path, payload.state.value); | ||
isTimelineActive = true; | ||
} | ||
}); | ||
api.on.editComponentState((payload) => { | ||
if (payload.type.startsWith('🍍')) { | ||
const storeId = payload.type.replace(/^🍍\s*/, ''); | ||
const store = registeredStores.get(storeId); | ||
if (!store) { | ||
return toastMessage(`store "${storeId}" not found`, 'error'); | ||
} | ||
const { path } = payload; | ||
if (path[0] !== 'state') { | ||
return toastMessage(`Invalid path for store "${storeId}":\n${path}\nOnly state can be modified.`); | ||
} | ||
// rewrite the first entry to be able to directly set the state as | ||
// well as any other path | ||
path[0] = '$state'; | ||
isTimelineActive = false; | ||
payload.set(store, path, payload.state.value); | ||
isTimelineActive = true; | ||
else { | ||
path.unshift('state', 'value'); | ||
} | ||
}); | ||
isAlreadyInstalled = true; | ||
} | ||
else { | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
} | ||
// avoid subscribing to mutations and actions twice | ||
if (hasSubscribed) | ||
return; | ||
store.$onAction(({ after, onError, name, args, store }) => { | ||
isTimelineActive = false; | ||
payload.set(inspectedStore, path, payload.state.value); | ||
isTimelineActive = true; | ||
} | ||
}); | ||
api.on.editComponentState((payload) => { | ||
if (payload.type.startsWith('🍍')) { | ||
const storeId = payload.type.replace(/^🍍\s*/, ''); | ||
const store = pinia._s.get(storeId); | ||
if (!store) { | ||
return toastMessage(`store "${storeId}" not found`, 'error'); | ||
} | ||
const { path } = payload; | ||
if (path[0] !== 'state') { | ||
return toastMessage(`Invalid path for store "${storeId}":\n${path}\nOnly state can be modified.`); | ||
} | ||
// rewrite the first entry to be able to directly set the state as | ||
// well as any other path | ||
path[0] = '$state'; | ||
isTimelineActive = false; | ||
payload.set(store, path, payload.state.value); | ||
isTimelineActive = true; | ||
} | ||
}); | ||
}); | ||
} | ||
function addStoreToDevtools(app, store) { | ||
if (!componentStateTypes.includes(getStoreType(store.$id))) { | ||
componentStateTypes.push(getStoreType(store.$id)); | ||
} | ||
setupDevtoolsPlugin({ | ||
id: 'dev.esm.pinia', | ||
label: 'Pinia 🍍', | ||
logo: 'https://pinia.esm.dev/logo.svg', | ||
packageName: 'pinia', | ||
homepage: 'https://pinia.esm.dev', | ||
componentStateTypes, | ||
app, | ||
}, (api) => { | ||
store.$onAction(({ after, onError, name, args }) => { | ||
const groupId = runningActionId++; | ||
@@ -685,2 +679,3 @@ api.addTimelineEvent({ | ||
data: { | ||
store: formatDisplay(store.$id), | ||
action: formatDisplay(name), | ||
@@ -693,2 +688,3 @@ args, | ||
after((result) => { | ||
activeAction = undefined; | ||
api.addTimelineEvent({ | ||
@@ -701,2 +697,3 @@ layerId: MUTATIONS_LAYER_ID, | ||
data: { | ||
store: formatDisplay(store.$id), | ||
action: formatDisplay(name), | ||
@@ -711,2 +708,3 @@ args, | ||
onError((error) => { | ||
activeAction = undefined; | ||
api.addTimelineEvent({ | ||
@@ -720,2 +718,3 @@ layerId: MUTATIONS_LAYER_ID, | ||
data: { | ||
store: formatDisplay(store.$id), | ||
action: formatDisplay(name), | ||
@@ -729,3 +728,3 @@ args, | ||
}); | ||
}); | ||
}, true); | ||
store.$subscribe(({ events, type }, state) => { | ||
@@ -740,3 +739,6 @@ if (!isTimelineActive) | ||
title: formatMutationType(type), | ||
data: formatEventData(events), | ||
data: { | ||
store: formatDisplay(store.$id), | ||
...formatEventData(events), | ||
}, | ||
groupId: activeAction, | ||
@@ -769,6 +771,23 @@ }; | ||
}); | ||
}, true); | ||
const hotUpdate = store._hotUpdate; | ||
store._hotUpdate = markRaw((newStore) => { | ||
hotUpdate(newStore); | ||
api.addTimelineEvent({ | ||
layerId: MUTATIONS_LAYER_ID, | ||
event: { | ||
time: Date.now(), | ||
title: '🔥 ' + store.$id, | ||
subtitle: 'HMR update', | ||
data: { | ||
store: formatDisplay(store.$id), | ||
info: formatDisplay(`HMR update`), | ||
}, | ||
}, | ||
}); | ||
}); | ||
// trigger an update so it can display new registered stores | ||
// @ts-ignore | ||
api.notifyComponentUpdate(); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
toastMessage(`"${store.$id}" store installed`); | ||
@@ -780,8 +799,10 @@ }); | ||
/** | ||
* pinia.use(devtoolsPlugin) | ||
* Patches a store to enable action grouping in devtools by wrapping the store with a Proxy that is passed as the context of all actions, allowing us to set `runningAction` on each access and effectively associating any state mutation to the action. | ||
* | ||
* @param store - store to patch | ||
* @param actionNames - list of actionst to patch | ||
*/ | ||
function devtoolsPlugin({ app, store, options, pinia }) { | ||
function patchActionForGrouping(store, actionNames) { | ||
// original actions of the store as they are given by pinia. We are going to override them | ||
const actions = Object.keys(options.actions || {}).reduce((storeActions, actionName) => { | ||
// @ts-expect-error | ||
const actions = actionNames.reduce((storeActions, actionName) => { | ||
// use toRaw to avoid tracking #541 | ||
@@ -792,5 +813,4 @@ storeActions[actionName] = toRaw(store)[actionName]; | ||
for (const actionName in actions) { | ||
// @ts-expect-error | ||
store[actionName] = function () { | ||
setActivePinia(pinia); | ||
// setActivePinia(store._p) | ||
// the running action id is incremented in a before action hook | ||
@@ -811,4 +831,26 @@ const _actionId = runningActionId; | ||
} | ||
addDevtools(app, | ||
// @ts-expect-error: FIXME: if possible... | ||
} | ||
/** | ||
* pinia.use(devtoolsPlugin) | ||
*/ | ||
function devtoolsPlugin({ app, store, options }) { | ||
// HMR module | ||
if (store.$id.startsWith('__hot:')) { | ||
return; | ||
} | ||
// only wrap actions in option-defined stores as this technique relies on | ||
// wrapping the context of the action with a proxy | ||
if ('id' in options) { | ||
patchActionForGrouping( | ||
// @ts-expect-error: can cast the store... | ||
store, Object.keys(options.actions)); | ||
const originalHotUpdate = store._hotUpdate; | ||
// Upgrade the HMR to also update the new actions | ||
toRaw(store)._hotUpdate = function (newStore) { | ||
originalHotUpdate.apply(this, arguments); | ||
patchActionForGrouping(store, Object.keys(newStore._hmrPayload.actions)); | ||
}; | ||
} | ||
addStoreToDevtools(app, | ||
// FIXME: is there a way to allow the assignment from Store<Id, S, G, A> to StoreGeneric? | ||
store); | ||
@@ -821,6 +863,6 @@ } | ||
function createPinia() { | ||
const scope = effectScope(true); | ||
// NOTE: here we could check the window object for a state and directly set it | ||
// if there is anything like it with Vue 3 SSR | ||
const state = ref({}); | ||
let localApp; | ||
const state = scope.run(() => ref({})); | ||
let _p = []; | ||
@@ -831,3 +873,3 @@ // plugins added before calling app.use(pinia) | ||
install(app) { | ||
pinia._a = localApp = app; | ||
pinia._a = app; | ||
app.provide(piniaSymbol, pinia); | ||
@@ -839,2 +881,5 @@ app.config.globalProperties.$pinia = pinia; | ||
setActivePinia(pinia); | ||
{ | ||
registerPiniaDevtools(app, pinia); | ||
} | ||
} | ||
@@ -844,3 +889,3 @@ toBeInstalled.forEach((plugin) => _p.push(plugin)); | ||
use(plugin) { | ||
if (!localApp) { | ||
if (!this._a) { | ||
toBeInstalled.push(plugin); | ||
@@ -855,3 +900,6 @@ } | ||
// it's actually undefined here | ||
_a: localApp, | ||
// @ts-expect-error | ||
_a: null, | ||
_e: scope, | ||
_s: new Map(), | ||
state, | ||
@@ -867,4 +915,110 @@ }); | ||
/** | ||
* Checks if a function is a `StoreDefinition` | ||
* | ||
* @param fn - object to test | ||
* @returns true if `fn` is a StoreDefinition | ||
*/ | ||
const isUseStore = (fn) => { | ||
return typeof fn === 'function' && typeof fn.$id === 'string'; | ||
}; | ||
/** | ||
* Mutates in place `newState` with `oldState` to _hot update_ it. It will | ||
* remove any key not existing in `newState` and recursively merge plain | ||
* objects. | ||
* | ||
* @param newState - new state object to be patched | ||
* @param oldState - old state that should be used to patch newState | ||
* @returns - newState | ||
*/ | ||
function patchObject(newState, oldState) { | ||
// no need to go through symbols because they cannot be serialized anyway | ||
for (const key in oldState) { | ||
const subPatch = oldState[key]; | ||
// skip the whole sub tree | ||
if (!(key in newState)) { | ||
continue; | ||
} | ||
const targetValue = newState[key]; | ||
if (isPlainObject(targetValue) && | ||
isPlainObject(subPatch) && | ||
!isRef(subPatch) && | ||
!isReactive(subPatch)) { | ||
newState[key] = patchObject(targetValue, subPatch); | ||
} | ||
else { | ||
// objects are either a bit more complex (e.g. refs) or primitives, so we | ||
// just set the whole thing | ||
newState[key] = subPatch; | ||
} | ||
} | ||
return newState; | ||
} | ||
/** | ||
* Creates an _accept_ function to pass to `import.meta.hot` in Vite applications. | ||
* | ||
* @example | ||
* ```js | ||
* const useUser = defineStore(...) | ||
* if (import.meta.hot) { | ||
* import.meta.hot.accept(acceptHMRUpdate(useUser, import.meta.hot)) | ||
* } | ||
* ``` | ||
* | ||
* @param initialUseStore - return of the defineStore to hot update | ||
* @param hot - `import.meta.hot` | ||
*/ | ||
function acceptHMRUpdate(initialUseStore, hot) { | ||
return (newModule) => { | ||
const pinia = hot.data.pinia || initialUseStore._pinia; | ||
if (!pinia) { | ||
// this store is still not used | ||
return; | ||
} | ||
// preserve the pinia instance across loads | ||
hot.data.pinia = pinia; | ||
// console.log('got data', newStore) | ||
for (const exportName in newModule) { | ||
const useStore = newModule[exportName]; | ||
// console.log('checking for', exportName) | ||
if (isUseStore(useStore) && pinia._s.has(useStore.$id)) { | ||
// console.log('Accepting update for', useStore.$id) | ||
const id = useStore.$id; | ||
if (id !== initialUseStore.$id) { | ||
console.warn(`The id of the store changed from "${initialUseStore.$id}" to "${id}". Reloading.`); | ||
// return import.meta.hot.invalidate() | ||
return hot.invalidate(); | ||
} | ||
const existingStore = pinia._s.get(id); | ||
if (!existingStore) { | ||
console.log(`skipping hmr because store doesn't exist yet`); | ||
return; | ||
} | ||
useStore(pinia, existingStore); | ||
} | ||
} | ||
}; | ||
} | ||
function addSubscription(subscriptions, callback, detached) { | ||
subscriptions.push(callback); | ||
const removeSubscription = () => { | ||
const idx = subscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
subscriptions.splice(idx, 1); | ||
} | ||
}; | ||
if (!detached && getCurrentInstance()) { | ||
onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function triggerSubscriptions(subscriptions, ...args) { | ||
subscriptions.forEach((callback) => { | ||
callback(...args); | ||
}); | ||
} | ||
function innerPatch(target, patchToApply) { | ||
// TODO: get all keys like symbols as well | ||
// no need to go through symbols because they cannot be serialized anyway | ||
for (const key in patchToApply) { | ||
@@ -887,36 +1041,98 @@ const subPatch = patchToApply[key]; | ||
const { assign } = Object; | ||
/** | ||
* Create an object of computed properties referring to | ||
* | ||
* @param rootStateRef - pinia.state | ||
* @param id - unique name | ||
*/ | ||
function computedFromState(rootStateRef, id) { | ||
// let asComputed = computed<T>() | ||
const reactiveObject = {}; | ||
const state = rootStateRef.value[id]; | ||
for (const key in state) { | ||
// @ts-expect-error: the key matches | ||
reactiveObject[key] = computed({ | ||
get: () => rootStateRef.value[id][key], | ||
set: (value) => (rootStateRef.value[id][key] = value), | ||
}); | ||
function isComputed(o) { | ||
return o && o.effect; | ||
} | ||
function createOptionsStore(id, options, pinia, hot) { | ||
const { state, actions, getters } = options; | ||
function $reset() { | ||
pinia.state.value[id] = state ? state() : {}; | ||
} | ||
return reactiveObject; | ||
const initialState = pinia.state.value[id]; | ||
let store; | ||
function setup() { | ||
if (!initialState && (!hot)) { | ||
$reset(); | ||
} | ||
// pinia.state.value[id] = state ? state() : {} | ||
// avoid creating a state in pinia.state.value | ||
const localState = hot | ||
? toRefs(ref(state ? state() : {}).value) | ||
: initialState || toRefs(pinia.state.value[id]); | ||
return assign(localState, actions, Object.keys(getters || {}).reduce((computedGetters, name) => { | ||
computedGetters[name] = markRaw(computed(() => { | ||
setActivePinia(pinia); | ||
// const context = store || ref(localState).value | ||
// @ts-expect-error | ||
// return getters![name].call(context, context) | ||
return store && getters[name].call(store, store); | ||
})); | ||
return computedGetters; | ||
}, {})); | ||
} | ||
store = createSetupStore(id, setup, options, pinia, hot); | ||
store.$reset = $reset; | ||
return store; | ||
} | ||
/** | ||
* Creates a store with its state object. This is meant to be augmented with getters and actions | ||
* | ||
* @param id - unique identifier of the store, like a name. eg: main, cart, user | ||
* @param buildState - function to build the initial state | ||
* @param initialState - initial state applied to the store, Must be correctly typed to infer typings | ||
*/ | ||
function initStore($id, buildState = () => ({}), initialState) { | ||
const pinia = getActivePinia(); | ||
pinia.state.value[$id] = initialState || buildState(); | ||
// const state: Ref<S> = toRef(_p.state.value, $id) | ||
let isListening = true; | ||
const noop = () => { }; | ||
function createSetupStore($id, setup, options = {}, pinia, hot) { | ||
let scope; | ||
const buildState = options.state; | ||
const optionsForPlugin = { | ||
actions: {}, | ||
...options, | ||
}; | ||
// watcher options for $subscribe | ||
const $subscribeOptions = { deep: true, flush: 'sync' }; | ||
/* istanbul ignore else */ | ||
{ | ||
$subscribeOptions.onTrigger = (event) => { | ||
if (isListening) { | ||
debuggerEvents = event; | ||
// avoid triggering this while the store is being built and the state is being set in pinia | ||
} | ||
else if (isListening == false && !store._hotUpdating) { | ||
// let patch send all the events together later | ||
/* istanbul ignore else */ | ||
if (Array.isArray(debuggerEvents)) { | ||
debuggerEvents.push(event); | ||
} | ||
else { | ||
console.error('🍍 debuggerEvents should be an array. This is most likely an internal Pinia bug.'); | ||
} | ||
} | ||
}; | ||
} | ||
// internal state | ||
let isListening; // set to true at the end | ||
let subscriptions = markRaw([]); | ||
let actionSubscriptions = markRaw([]); | ||
let debuggerEvents; | ||
const initialState = pinia.state.value[$id]; | ||
if (!initialState && true && !hot) { | ||
// should be set in Vue 2 | ||
pinia.state.value[$id] = {}; | ||
} | ||
const hotState = ref({}); | ||
if (!pinia._e.active) { | ||
throw new Error('Pinia destroyed'); | ||
} | ||
// TODO: idea create skipSerialize that marks properties as non serializable and they are skipped | ||
const setupStore = pinia._e.run(() => { | ||
scope = effectScope(); | ||
return scope.run(() => { | ||
// skip setting up the watcher on HMR | ||
if (!hot) { | ||
watch(() => pinia.state.value[$id], (state, oldState) => { | ||
if (isListening) { | ||
triggerSubscriptions(subscriptions, { | ||
storeId: $id, | ||
type: MutationType.direct, | ||
events: debuggerEvents, | ||
}, state); | ||
} | ||
}, $subscribeOptions); | ||
} | ||
return setup(); | ||
}); | ||
}); | ||
function $patch(partialStateOrMutator) { | ||
@@ -949,122 +1165,19 @@ let subscriptionMutation; | ||
// because we paused the watcher, we need to manually call the subscriptions | ||
subscriptions.forEach((callback) => { | ||
callback(subscriptionMutation, pinia.state.value[$id]); | ||
}); | ||
triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id]); | ||
} | ||
function $subscribe(callback) { | ||
subscriptions.push(callback); | ||
// watch here to link the subscription to the current active instance | ||
// e.g. inside the setup of a component | ||
const options = { deep: true, flush: 'sync' }; | ||
/* istanbul ignore else */ | ||
{ | ||
options.onTrigger = (event) => { | ||
if (isListening) { | ||
debuggerEvents = event; | ||
} | ||
else { | ||
// let patch send all the events together later | ||
/* istanbul ignore else */ | ||
if (Array.isArray(debuggerEvents)) { | ||
debuggerEvents.push(event); | ||
} | ||
else { | ||
console.error('🍍 debuggerEvents should be an array. This is most likely an internal Pinia bug.'); | ||
} | ||
} | ||
}; | ||
const $reset = () => { | ||
throw new Error(`🍍: Store "${$id}" is build using the setup syntax and does not implement $reset().`); | ||
} | ||
const stopWatcher = watch(() => pinia.state.value[$id], (state, oldState) => { | ||
if (isListening) { | ||
callback({ | ||
storeId: $id, | ||
type: MutationType.direct, | ||
events: debuggerEvents, | ||
}, state); | ||
} | ||
}, options); | ||
const removeSubscription = () => { | ||
const idx = subscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
subscriptions.splice(idx, 1); | ||
stopWatcher(); | ||
} | ||
}; | ||
if (getCurrentInstance()) { | ||
onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function $onAction(callback) { | ||
actionSubscriptions.push(callback); | ||
const removeSubscription = () => { | ||
const idx = actionSubscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
actionSubscriptions.splice(idx, 1); | ||
} | ||
}; | ||
if (getCurrentInstance()) { | ||
onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function $reset() { | ||
pinia.state.value[$id] = buildState(); | ||
} | ||
const storeWithState = { | ||
$id, | ||
_p: pinia, | ||
_as: actionSubscriptions, | ||
// $state is added underneath | ||
$patch, | ||
$subscribe, | ||
$onAction, | ||
$reset, | ||
}; | ||
const injectionSymbol = Symbol(`PiniaStore(${$id})`) | ||
; | ||
return [ | ||
storeWithState, | ||
{ | ||
get: () => pinia.state.value[$id], | ||
set: (newState) => { | ||
isListening = false; | ||
pinia.state.value[$id] = newState; | ||
isListening = true; | ||
}, | ||
}, | ||
injectionSymbol, | ||
]; | ||
} | ||
const noop = () => { }; | ||
/** | ||
* Creates a store bound to the lifespan of where the function is called. This | ||
* means creating the store inside of a component's setup will bound it to the | ||
* lifespan of that component while creating it outside of a component will | ||
* create an ever living store | ||
* | ||
* @param partialStore - store with state returned by initStore | ||
* @param descriptor - descriptor to setup $state property | ||
* @param $id - unique name of the store | ||
* @param getters - getters of the store | ||
* @param actions - actions of the store | ||
*/ | ||
function buildStoreToUse(partialStore, descriptor, $id, getters = {}, actions = {}, options) { | ||
const pinia = getActivePinia(); | ||
const computedGetters = {}; | ||
for (const getterName in getters) { | ||
// @ts-ignore: it's only readonly for the users | ||
computedGetters[getterName] = computed(() => { | ||
/** | ||
* Wraps an action to handle subscriptions. | ||
* | ||
* @param name - name of the action | ||
* @param action - action to wrap | ||
* @returns a wrapped action to handle subscriptions | ||
*/ | ||
function wrapAction(name, action) { | ||
return function () { | ||
setActivePinia(pinia); | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
// @ts-expect-error: the argument count is correct | ||
return getters[getterName].call(store, store); | ||
}); | ||
} | ||
const wrappedActions = {}; | ||
for (const actionName in actions) { | ||
wrappedActions[actionName] = function () { | ||
setActivePinia(pinia); | ||
const args = Array.from(arguments); | ||
const localStore = this || store; | ||
let afterCallback = noop; | ||
@@ -1078,18 +1191,99 @@ let onErrorCallback = noop; | ||
} | ||
partialStore._as.forEach((callback) => { | ||
// @ts-expect-error | ||
callback({ args, name: actionName, store: localStore, after, onError }); | ||
// @ts-expect-error | ||
triggerSubscriptions(actionSubscriptions, { | ||
args, | ||
name, | ||
store, | ||
after, | ||
onError, | ||
}); | ||
let ret; | ||
try { | ||
ret = actions[actionName].apply(localStore, args); | ||
Promise.resolve(ret).then(afterCallback).catch(onErrorCallback); | ||
ret = action.apply(this && this.$id === $id ? this : store, args); | ||
// handle sync errors | ||
} | ||
catch (error) { | ||
onErrorCallback(error); | ||
throw error; | ||
if (onErrorCallback(error) !== false) { | ||
throw error; | ||
} | ||
} | ||
return ret; | ||
if (ret instanceof Promise) { | ||
return ret | ||
.then((value) => { | ||
const newRet = afterCallback(value); | ||
// allow the afterCallback to override the return value | ||
return newRet === undefined ? value : newRet; | ||
}) | ||
.catch((error) => { | ||
if (onErrorCallback(error) !== false) { | ||
return Promise.reject(error); | ||
} | ||
}); | ||
} | ||
// allow the afterCallback to override the return value | ||
const newRet = afterCallback(ret); | ||
return newRet === undefined ? ret : newRet; | ||
}; | ||
} | ||
const _hmrPayload = /*#__PURE__*/ markRaw({ | ||
actions: {}, | ||
getters: {}, | ||
state: [], | ||
hotState, | ||
}); | ||
// overwrite existing actions to support $onAction | ||
for (const key in setupStore) { | ||
const prop = setupStore[key]; | ||
if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) { | ||
// mark it as a piece of state to be serialized | ||
if (hot) { | ||
hotState.value[key] = toRef(setupStore, key); | ||
// createOptionStore already did this | ||
} | ||
else if (!buildState) { | ||
pinia.state.value[$id][key] = prop; | ||
// TODO: avoid if state exists for SSR | ||
} | ||
{ | ||
_hmrPayload.state.push(key); | ||
} | ||
// action | ||
} | ||
else if (typeof prop === 'function') { | ||
// @ts-expect-error: we are overriding the function we avoid wrapping if | ||
// this a hot module replacement store because the hotUpdate method needs | ||
// to do it with the right context | ||
setupStore[key] = hot ? prop : wrapAction(key, prop); | ||
{ | ||
_hmrPayload.actions[key] = prop; | ||
} | ||
// list actions so they can be used in plugins | ||
// @ts-expect-error | ||
optionsForPlugin.actions[key] = prop; | ||
} | ||
else { | ||
// add getters for devtools | ||
if (isComputed(prop)) { | ||
_hmrPayload.getters[key] = buildState | ||
? // @ts-expect-error | ||
options.getters[key] | ||
: prop; | ||
if (IS_CLIENT) { | ||
const getters = | ||
// @ts-expect-error: it should be on the store | ||
setupStore._getters || (setupStore._getters = markRaw([])); | ||
getters.push(key); | ||
} | ||
} | ||
} | ||
} | ||
const partialStore = { | ||
_p: pinia, | ||
// _s: scope, | ||
$id, | ||
$onAction: addSubscription.bind(null, actionSubscriptions), | ||
$patch, | ||
$reset, | ||
$subscribe: addSubscription.bind(null, subscriptions), | ||
}; | ||
const store = reactive(assign(IS_CLIENT | ||
@@ -1099,13 +1293,102 @@ ? // devtools custom properties | ||
_customProperties: markRaw(new Set()), | ||
_hmrPayload, | ||
} | ||
: {}, partialStore, | ||
// using this means no new properties can be added as state | ||
computedFromState(pinia.state, $id), computedGetters, wrappedActions)); | ||
: {}, partialStore, setupStore)); | ||
// use this instead of a computed with setter to be able to create it anywhere | ||
// without linking the computed lifespan to wherever the store is first | ||
// created. | ||
Object.defineProperty(store, '$state', descriptor); | ||
// add getters for devtools | ||
if (IS_CLIENT) { | ||
store._getters = markRaw(Object.keys(getters)); | ||
Object.defineProperty(store, '$state', { | ||
get: () => (hot ? hotState.value : pinia.state.value[$id]), | ||
set: (state) => { | ||
if (hot) { | ||
throw new Error('cannot set hotState'); | ||
} | ||
pinia.state.value[$id] = state; | ||
}, | ||
}); | ||
// add the hotUpdate before plugins to allow them to override it | ||
{ | ||
store._hotUpdate = markRaw((newStore) => { | ||
store._hotUpdating = true; | ||
newStore._hmrPayload.state.forEach((stateKey) => { | ||
if (stateKey in store.$state) { | ||
const newStateTarget = newStore.$state[stateKey]; | ||
const oldStateSource = store.$state[stateKey]; | ||
if (typeof newStateTarget === 'object' && | ||
isPlainObject(newStateTarget) && | ||
isPlainObject(oldStateSource)) { | ||
patchObject(newStateTarget, oldStateSource); | ||
} | ||
else { | ||
// transfer the ref | ||
newStore.$state[stateKey] = oldStateSource; | ||
} | ||
} | ||
// patch direct access properties to allow store.stateProperty to work as | ||
// store.$state.stateProperty | ||
// @ts-expect-error | ||
store[stateKey] = toRef(newStore.$state, stateKey); | ||
}); | ||
// remove deleted state properties | ||
Object.keys(store.$state).forEach((stateKey) => { | ||
if (!(stateKey in newStore.$state)) { | ||
delete store[stateKey]; | ||
} | ||
}); | ||
// avoid devtools logging this as a mutation | ||
isListening = false; | ||
pinia.state.value[$id] = toRef(newStore._hmrPayload, 'hotState'); | ||
isListening = true; | ||
for (const actionName in newStore._hmrPayload.actions) { | ||
const action = newStore[actionName]; | ||
// @ts-expect-error: new key | ||
store[actionName] = | ||
// new line forced for TS | ||
wrapAction(actionName, action); | ||
} | ||
// TODO: does this work in both setup and option store? | ||
for (const getterName in newStore._hmrPayload.getters) { | ||
const getter = newStore._hmrPayload.getters[getterName]; | ||
// @ts-expect-error | ||
store[getterName] = | ||
// --- | ||
buildState | ||
? // special handling of options api | ||
computed(() => { | ||
setActivePinia(pinia); | ||
return getter.call(store, store); | ||
}) | ||
: getter; | ||
} | ||
// remove deleted getters | ||
Object.keys(store._hmrPayload.getters).forEach((key) => { | ||
if (!(key in newStore._hmrPayload.getters)) { | ||
delete store[key]; | ||
} | ||
}); | ||
// remove old actions | ||
Object.keys(store._hmrPayload.actions).forEach((key) => { | ||
if (!(key in newStore._hmrPayload.actions)) { | ||
delete store[key]; | ||
} | ||
}); | ||
// update the values used in devtools and to allow deleting new properties later on | ||
store._hmrPayload = newStore._hmrPayload; | ||
store._getters = newStore._getters; | ||
store._hotUpdating = false; | ||
}); | ||
const nonEnumerable = { | ||
writable: true, | ||
configurable: true, | ||
// avoid warning on devtools trying to display this property | ||
enumerable: false, | ||
}; | ||
if (IS_CLIENT) { | ||
['_p', '_hmrPayload', '_getters', '_customProperties'].forEach((p) => { | ||
Object.defineProperty(store, p, { | ||
value: store[p], | ||
...nonEnumerable, | ||
}); | ||
}); | ||
} | ||
} | ||
@@ -1115,4 +1398,9 @@ // apply all plugins | ||
if (IS_CLIENT) { | ||
// @ts-expect-error: conflict between A and ActionsTree | ||
const extensions = extender({ store, app: pinia._a, pinia, options }); | ||
const extensions = extender({ | ||
store, | ||
app: pinia._a, | ||
pinia, | ||
// @ts-expect-error | ||
options: optionsForPlugin, | ||
}); | ||
Object.keys(extensions || {}).forEach((key) => store._customProperties.add(key)); | ||
@@ -1122,19 +1410,34 @@ assign(store, extensions); | ||
else { | ||
// @ts-expect-error: conflict between A and ActionsTree | ||
assign(store, extender({ store, app: pinia._a, pinia, options })); | ||
assign(store, extender({ | ||
store, | ||
app: pinia._a, | ||
pinia, | ||
// @ts-expect-error | ||
options: optionsForPlugin, | ||
})); | ||
} | ||
}); | ||
if (initialState) { | ||
(options.hydrate || innerPatch)(store, initialState); | ||
} | ||
isListening = true; | ||
return store; | ||
} | ||
/** | ||
* Creates a `useStore` function that retrieves the store instance | ||
* @param options - options to define the store | ||
*/ | ||
function defineStore(options) { | ||
const { id, state, getters, actions } = options; | ||
function useStore(pinia) { | ||
function defineStore( | ||
// TODO: add proper types from above | ||
idOrOptions, setup, setupOptions) { | ||
let id; | ||
let options; | ||
const isSetupStore = typeof setup === 'function'; | ||
if (typeof idOrOptions === 'string') { | ||
id = idOrOptions; | ||
// the option store setup will contain the actual options in this case | ||
options = isSetupStore ? setupOptions : setup; | ||
} | ||
else { | ||
options = idOrOptions; | ||
id = idOrOptions.id; | ||
} | ||
function useStore(pinia, hot) { | ||
const currentInstance = getCurrentInstance(); | ||
// only run provide when pinia hasn't been manually passed | ||
const shouldProvide = currentInstance && !pinia; | ||
// avoid injecting if `useStore` when not possible | ||
pinia = | ||
@@ -1149,33 +1452,35 @@ // in test mode, ignore the argument provided as we can always retrieve a | ||
pinia = getActivePinia(); | ||
let storeCache = storesMap.get(pinia); | ||
if (!storeCache) | ||
storesMap.set(pinia, (storeCache = new Map())); | ||
let storeAndDescriptor = storeCache.get(id); | ||
let store; | ||
if (!storeAndDescriptor) { | ||
storeAndDescriptor = initStore(id, state, pinia.state.value[id]); | ||
// @ts-expect-error: annoying to type | ||
storeCache.set(id, storeAndDescriptor); | ||
store = buildStoreToUse(storeAndDescriptor[0], storeAndDescriptor[1], id, getters, actions, options); | ||
// allow children to reuse this store instance to avoid creating a new | ||
// store for each child | ||
if (shouldProvide) { | ||
provide(storeAndDescriptor[2], store); | ||
if (!pinia._s.has(id)) { | ||
pinia._s.set(id, isSetupStore | ||
? createSetupStore(id, setup, options, pinia) | ||
: createOptionsStore(id, options, pinia)); | ||
{ | ||
// @ts-expect-error: not the right inferred type | ||
useStore._pinia = pinia; | ||
} | ||
} | ||
else { | ||
store = | ||
(currentInstance && inject(storeAndDescriptor[2], null)) || | ||
buildStoreToUse(storeAndDescriptor[0], storeAndDescriptor[1], id, getters, actions, options); | ||
const store = pinia._s.get(id); | ||
if (hot) { | ||
const hotId = '__hot:' + id; | ||
const newStore = isSetupStore | ||
? createSetupStore(hotId, setup, options, pinia, true) | ||
: createOptionsStore(hotId, assign({}, options), pinia, true); | ||
hot._hotUpdate(newStore); | ||
// cleanup the state properties and the store from the cache | ||
delete pinia.state.value[hotId]; | ||
pinia._s.delete(hotId); | ||
} | ||
// save stores in instances to access them devtools | ||
if (IS_CLIENT && currentInstance && currentInstance.proxy) { | ||
if (IS_CLIENT && | ||
currentInstance && | ||
currentInstance.proxy && | ||
// avoid adding stores that are just built for hot module replacement | ||
!hot) { | ||
const vm = currentInstance.proxy; | ||
const cache = '_pStores' in vm ? vm._pStores : (vm._pStores = {}); | ||
// @ts-expect-error: still can't cast Store with generics to Store | ||
cache[store.$id] = store; | ||
cache[id] = store; | ||
} | ||
// StoreGeneric cannot be casted towards Store | ||
return store; | ||
} | ||
// needed by map helpers | ||
useStore.$id = id; | ||
@@ -1185,8 +1490,2 @@ return useStore; | ||
function getCachedStore(vm, useStore) { | ||
const cache = '_pStores' in vm ? vm._pStores : (vm._pStores = {}); | ||
const id = useStore.$id; | ||
return (cache[id] || | ||
(cache[id] = useStore(vm.$pinia))); | ||
} | ||
let mapStoreSuffix = 'Store'; | ||
@@ -1239,3 +1538,3 @@ /** | ||
reduced[useStore.$id + mapStoreSuffix] = function () { | ||
return getCachedStore(this, useStore); | ||
return useStore(this.$pinia); | ||
}; | ||
@@ -1257,4 +1556,3 @@ return reduced; | ||
reduced[key] = function () { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key]; | ||
return useStore(this.$pinia)[key]; | ||
}; | ||
@@ -1264,4 +1562,5 @@ return reduced; | ||
: Object.keys(keysOrMapper).reduce((reduced, key) => { | ||
// @ts-expect-error | ||
reduced[key] = function () { | ||
const store = getCachedStore(this, useStore); | ||
const store = useStore(this.$pinia); | ||
const storeKey = keysOrMapper[key]; | ||
@@ -1295,4 +1594,3 @@ // for some reason TS is unable to infer the type of storeKey to be a | ||
reduced[key] = function (...args) { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key](...args); | ||
return useStore(this.$pinia)[key](...args); | ||
}; | ||
@@ -1304,4 +1602,3 @@ return reduced; | ||
reduced[key] = function (...args) { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[keysOrMapper[key]](...args); | ||
return useStore(this.$pinia)[keysOrMapper[key]](...args); | ||
}; | ||
@@ -1325,9 +1622,7 @@ return reduced; | ||
get() { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key]; | ||
return useStore(this.$pinia)[key]; | ||
}, | ||
set(value) { | ||
// it's easier to type it here as any | ||
// @ts-expect-error | ||
return (getCachedStore(this, useStore)[key] = value); | ||
return (useStore(this.$pinia)[key] = value); | ||
}, | ||
@@ -1341,10 +1636,7 @@ }; | ||
get() { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[keysOrMapper[key]]; | ||
return useStore(this.$pinia)[keysOrMapper[key]]; | ||
}, | ||
set(value) { | ||
// it's easier to type it here as any | ||
// @ts-expect-error | ||
return (getCachedStore(this, useStore)[keysOrMapper[key]] = | ||
value); | ||
return (useStore(this.$pinia)[keysOrMapper[key]] = value); | ||
}, | ||
@@ -1364,3 +1656,3 @@ }; | ||
* | ||
* @internal - STILL NOT RELEASED, DO NOT USE. It will be likely moved to its | ||
* @alpha - STILL NOT RELEASED, DO NOT USE. It will be likely moved to its | ||
* own package. | ||
@@ -1374,3 +1666,3 @@ * | ||
plugins.forEach((plugin) => pinia.use(plugin)); | ||
// @ts-ignore | ||
// @ts-ignore: this can fail in TS depending of the existence of jest | ||
createSpy = createSpy || (typeof jest !== undefined && jest.fn); | ||
@@ -1383,14 +1675,10 @@ if (!createSpy) { | ||
pinia.use(({ store, options }) => { | ||
if (!spiedActions.has(options.id)) { | ||
spiedActions.set(options.id, {}); | ||
if (!spiedActions.has(store.$id)) { | ||
spiedActions.set(store.$id, {}); | ||
} | ||
const actionsCache = spiedActions.get(options.id); | ||
const actionsCache = spiedActions.get(store.$id); | ||
Object.keys(options.actions || {}).forEach((action) => { | ||
actionsCache[action] = | ||
actionsCache[action] || | ||
(stubActions | ||
? createSpy() | ||
: // @ts-expect-error: | ||
createSpy(store[action])); | ||
// @ts-expect-error: | ||
(stubActions ? createSpy() : createSpy(store[action])); | ||
store[action] = actionsCache[action]; | ||
@@ -1416,2 +1704,2 @@ }); | ||
export { MutationType, createPinia, createTestingPinia, defineStore, mapActions, mapGetters, mapState, mapStores, mapWritableState, setActivePinia, setMapStoreSuffix }; | ||
export { MutationType, acceptHMRUpdate, createPinia, createTestingPinia, defineStore, mapActions, mapGetters, mapState, mapStores, mapWritableState, setActivePinia, setMapStoreSuffix }; |
/*! | ||
* pinia v2.0.0-beta.5 | ||
* pinia v2.0.0-rc.0 | ||
* (c) 2021 Eduardo San Martin Morote | ||
* @license MIT | ||
*/ | ||
import { warn, toRaw, ref, markRaw, getCurrentInstance, inject, provide, computed, reactive, watch, onUnmounted, isRef, isReactive, createApp } from 'vue'; | ||
import { warn, toRaw, markRaw, effectScope, ref, isRef, isReactive, getCurrentInstance, onUnmounted, inject, watch, toRef, reactive, computed, toRefs, createApp } from 'vue'; | ||
import { setupDevtoolsPlugin } from '@vue/devtools-api'; | ||
@@ -33,7 +33,2 @@ | ||
}; | ||
/** | ||
* Map of stores based on a Pinia instance. Allows setting and retrieving stores | ||
* for the current running application (with its pinia). | ||
*/ | ||
const storesMap = new WeakMap(); | ||
const piniaSymbol = ((process.env.NODE_ENV !== 'production') ? Symbol('pinia') : /* istanbul ignore next */ Symbol()); | ||
@@ -422,3 +417,2 @@ | ||
key, | ||
// @ts-expect-error | ||
value: store.$state[key], | ||
@@ -432,3 +426,2 @@ })), | ||
key: getterName, | ||
// @ts-expect-error | ||
value: store[getterName], | ||
@@ -441,3 +434,2 @@ })); | ||
key, | ||
// @ts-expect-error | ||
value: store[key], | ||
@@ -488,7 +480,2 @@ })); | ||
/** | ||
* Registered stores used for devtools. | ||
*/ | ||
const registeredStores = /*#__PURE__*/ new Map(); | ||
let isAlreadyInstalled; | ||
// timeline can be paused when directly changing the state | ||
@@ -499,14 +486,17 @@ let isTimelineActive = true; | ||
const INSPECTOR_ID = 'pinia'; | ||
function addDevtools(app, store) { | ||
// TODO: we probably need to ensure the latest version of the store is kept: | ||
// without effectScope, multiple stores will be created and will have a | ||
// limited lifespan for getters. | ||
// add a dev only variable that is removed in unmounted and replace the store | ||
let hasSubscribed = true; | ||
const storeType = '🍍 ' + store.$id; | ||
if (!registeredStores.has(store.$id)) { | ||
registeredStores.set(store.$id, store); | ||
componentStateTypes.push(storeType); | ||
hasSubscribed = false; | ||
} | ||
/** | ||
* Gets the displayed name of a store in devtools | ||
* | ||
* @param id - id of the store | ||
* @returns a formatted string | ||
*/ | ||
const getStoreType = (id) => '🍍 ' + id; | ||
/** | ||
* Add the pinia plugin without any store. Allows displaying a Pinia plugin tab | ||
* as soon as it is added to the application. | ||
* | ||
* @param app - Vue application | ||
* @param pinia - pinia instance | ||
*/ | ||
function registerPiniaDevtools(app, pinia) { | ||
setupDevtoolsPlugin({ | ||
@@ -521,157 +511,161 @@ id: 'dev.esm.pinia', | ||
}, (api) => { | ||
if (!isAlreadyInstalled) { | ||
api.addTimelineLayer({ | ||
id: MUTATIONS_LAYER_ID, | ||
label: `Pinia 🍍`, | ||
color: 0xe5df88, | ||
}); | ||
api.addInspector({ | ||
id: INSPECTOR_ID, | ||
label: 'Pinia 🍍', | ||
icon: 'storage', | ||
treeFilterPlaceholder: 'Search stores', | ||
actions: [ | ||
{ | ||
icon: 'content_copy', | ||
action: () => { | ||
actionGlobalCopyState(store._p); | ||
}, | ||
tooltip: 'Serialize and copy the state', | ||
api.addTimelineLayer({ | ||
id: MUTATIONS_LAYER_ID, | ||
label: `Pinia 🍍`, | ||
color: 0xe5df88, | ||
}); | ||
api.addInspector({ | ||
id: INSPECTOR_ID, | ||
label: 'Pinia 🍍', | ||
icon: 'storage', | ||
treeFilterPlaceholder: 'Search stores', | ||
actions: [ | ||
{ | ||
icon: 'content_copy', | ||
action: () => { | ||
actionGlobalCopyState(pinia); | ||
}, | ||
{ | ||
icon: 'content_paste', | ||
action: async () => { | ||
await actionGlobalPasteState(store._p); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
tooltip: 'Replace the state with the content of your clipboard', | ||
tooltip: 'Serialize and copy the state', | ||
}, | ||
{ | ||
icon: 'content_paste', | ||
action: async () => { | ||
await actionGlobalPasteState(pinia); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
{ | ||
icon: 'save', | ||
action: () => { | ||
actionGlobalSaveState(store._p); | ||
}, | ||
tooltip: 'Save the state as a JSON file', | ||
tooltip: 'Replace the state with the content of your clipboard', | ||
}, | ||
{ | ||
icon: 'save', | ||
action: () => { | ||
actionGlobalSaveState(pinia); | ||
}, | ||
{ | ||
icon: 'folder_open', | ||
action: async () => { | ||
await actionGlobalOpenStateFile(store._p); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
tooltip: 'Import the state from a JSON file', | ||
tooltip: 'Save the state as a JSON file', | ||
}, | ||
{ | ||
icon: 'folder_open', | ||
action: async () => { | ||
await actionGlobalOpenStateFile(pinia); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
}, | ||
], | ||
}); | ||
api.on.inspectComponent((payload, ctx) => { | ||
const proxy = (payload.componentInstance && | ||
payload.componentInstance.proxy); | ||
if (proxy && proxy._pStores) { | ||
const piniaStores = payload.componentInstance.proxy._pStores; | ||
Object.values(piniaStores).forEach((store) => { | ||
tooltip: 'Import the state from a JSON file', | ||
}, | ||
], | ||
}); | ||
api.on.inspectComponent((payload, ctx) => { | ||
const proxy = (payload.componentInstance && | ||
payload.componentInstance.proxy); | ||
if (proxy && proxy._pStores) { | ||
const piniaStores = payload.componentInstance.proxy._pStores; | ||
Object.values(piniaStores).forEach((store) => { | ||
payload.instanceData.state.push({ | ||
type: getStoreType(store.$id), | ||
key: 'state', | ||
editable: true, | ||
value: store.$state, | ||
}); | ||
if (store._getters && store._getters.length) { | ||
payload.instanceData.state.push({ | ||
type: storeType, | ||
key: 'state', | ||
editable: true, | ||
value: store.$state, | ||
type: getStoreType(store.$id), | ||
key: 'getters', | ||
editable: false, | ||
value: store._getters.reduce((getters, key) => { | ||
getters[key] = store[key]; | ||
return getters; | ||
}, {}), | ||
}); | ||
if (store._getters && store._getters.length) { | ||
payload.instanceData.state.push({ | ||
type: storeType, | ||
key: 'getters', | ||
editable: false, | ||
value: store._getters.reduce((getters, key) => { | ||
// @ts-expect-error | ||
getters[key] = store[key]; | ||
return getters; | ||
}, {}), | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
api.on.getInspectorTree((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
let stores = [pinia]; | ||
stores = stores.concat(Array.from(pinia._s.values())); | ||
payload.rootNodes = (payload.filter | ||
? stores.filter((store) => '$id' in store | ||
? store.$id | ||
.toLowerCase() | ||
.includes(payload.filter.toLowerCase()) | ||
: PINIA_ROOT_LABEL.toLowerCase().includes(payload.filter.toLowerCase())) | ||
: stores).map(formatStoreForInspectorTree); | ||
} | ||
}); | ||
api.on.getInspectorState((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? pinia | ||
: pinia._s.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
// this could be the selected store restored for a different project | ||
// so it's better not to say anything here | ||
return; | ||
} | ||
}); | ||
api.on.getInspectorTree((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
let stores = [store._p]; | ||
stores = stores.concat(Array.from(registeredStores.values())); | ||
payload.rootNodes = (payload.filter | ||
? stores.filter((store) => '$id' in store | ||
? store.$id | ||
.toLowerCase() | ||
.includes(payload.filter.toLowerCase()) | ||
: PINIA_ROOT_LABEL.toLowerCase().includes(payload.filter.toLowerCase())) | ||
: stores).map(formatStoreForInspectorTree); | ||
if (inspectedStore) { | ||
payload.state = formatStoreForInspectorState(inspectedStore); | ||
} | ||
}); | ||
api.on.getInspectorState((payload) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? store._p | ||
: registeredStores.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
// this could be the selected store restored for a different project | ||
// so it's better not to say anything here | ||
return; | ||
} | ||
if (inspectedStore) { | ||
payload.state = formatStoreForInspectorState(inspectedStore); | ||
} | ||
} | ||
}); | ||
api.on.editInspectorState((payload, ctx) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? pinia | ||
: pinia._s.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
return toastMessage(`store "${payload.nodeId}" not found`, 'error'); | ||
} | ||
}); | ||
api.on.editInspectorState((payload, ctx) => { | ||
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) { | ||
const inspectedStore = payload.nodeId === PINIA_ROOT_ID | ||
? store._p | ||
: registeredStores.get(payload.nodeId); | ||
if (!inspectedStore) { | ||
return toastMessage(`store "${payload.nodeId}" not found`, 'error'); | ||
const { path } = payload; | ||
if (!isPinia(inspectedStore)) { | ||
// access only the state | ||
if (path.length !== 1 || | ||
!inspectedStore._customProperties.has(path[0]) || | ||
path[0] in inspectedStore.$state) { | ||
path.unshift('$state'); | ||
} | ||
const { path } = payload; | ||
if (!isPinia(store)) { | ||
// access only the state | ||
if (path.length !== 1 || | ||
!store._customProperties.has(path[0]) || | ||
path[0] in store.$state) { | ||
path.unshift('$state'); | ||
} | ||
} | ||
else { | ||
path.unshift('state', 'value'); | ||
} | ||
isTimelineActive = false; | ||
payload.set(inspectedStore, path, payload.state.value); | ||
isTimelineActive = true; | ||
} | ||
}); | ||
api.on.editComponentState((payload) => { | ||
if (payload.type.startsWith('🍍')) { | ||
const storeId = payload.type.replace(/^🍍\s*/, ''); | ||
const store = registeredStores.get(storeId); | ||
if (!store) { | ||
return toastMessage(`store "${storeId}" not found`, 'error'); | ||
} | ||
const { path } = payload; | ||
if (path[0] !== 'state') { | ||
return toastMessage(`Invalid path for store "${storeId}":\n${path}\nOnly state can be modified.`); | ||
} | ||
// rewrite the first entry to be able to directly set the state as | ||
// well as any other path | ||
path[0] = '$state'; | ||
isTimelineActive = false; | ||
payload.set(store, path, payload.state.value); | ||
isTimelineActive = true; | ||
else { | ||
path.unshift('state', 'value'); | ||
} | ||
}); | ||
isAlreadyInstalled = true; | ||
} | ||
else { | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
} | ||
// avoid subscribing to mutations and actions twice | ||
if (hasSubscribed) | ||
return; | ||
store.$onAction(({ after, onError, name, args, store }) => { | ||
isTimelineActive = false; | ||
payload.set(inspectedStore, path, payload.state.value); | ||
isTimelineActive = true; | ||
} | ||
}); | ||
api.on.editComponentState((payload) => { | ||
if (payload.type.startsWith('🍍')) { | ||
const storeId = payload.type.replace(/^🍍\s*/, ''); | ||
const store = pinia._s.get(storeId); | ||
if (!store) { | ||
return toastMessage(`store "${storeId}" not found`, 'error'); | ||
} | ||
const { path } = payload; | ||
if (path[0] !== 'state') { | ||
return toastMessage(`Invalid path for store "${storeId}":\n${path}\nOnly state can be modified.`); | ||
} | ||
// rewrite the first entry to be able to directly set the state as | ||
// well as any other path | ||
path[0] = '$state'; | ||
isTimelineActive = false; | ||
payload.set(store, path, payload.state.value); | ||
isTimelineActive = true; | ||
} | ||
}); | ||
}); | ||
} | ||
function addStoreToDevtools(app, store) { | ||
if (!componentStateTypes.includes(getStoreType(store.$id))) { | ||
componentStateTypes.push(getStoreType(store.$id)); | ||
} | ||
setupDevtoolsPlugin({ | ||
id: 'dev.esm.pinia', | ||
label: 'Pinia 🍍', | ||
logo: 'https://pinia.esm.dev/logo.svg', | ||
packageName: 'pinia', | ||
homepage: 'https://pinia.esm.dev', | ||
componentStateTypes, | ||
app, | ||
}, (api) => { | ||
store.$onAction(({ after, onError, name, args }) => { | ||
const groupId = runningActionId++; | ||
@@ -685,2 +679,3 @@ api.addTimelineEvent({ | ||
data: { | ||
store: formatDisplay(store.$id), | ||
action: formatDisplay(name), | ||
@@ -693,2 +688,3 @@ args, | ||
after((result) => { | ||
activeAction = undefined; | ||
api.addTimelineEvent({ | ||
@@ -701,2 +697,3 @@ layerId: MUTATIONS_LAYER_ID, | ||
data: { | ||
store: formatDisplay(store.$id), | ||
action: formatDisplay(name), | ||
@@ -711,2 +708,3 @@ args, | ||
onError((error) => { | ||
activeAction = undefined; | ||
api.addTimelineEvent({ | ||
@@ -720,2 +718,3 @@ layerId: MUTATIONS_LAYER_ID, | ||
data: { | ||
store: formatDisplay(store.$id), | ||
action: formatDisplay(name), | ||
@@ -729,3 +728,3 @@ args, | ||
}); | ||
}); | ||
}, true); | ||
store.$subscribe(({ events, type }, state) => { | ||
@@ -740,3 +739,6 @@ if (!isTimelineActive) | ||
title: formatMutationType(type), | ||
data: formatEventData(events), | ||
data: { | ||
store: formatDisplay(store.$id), | ||
...formatEventData(events), | ||
}, | ||
groupId: activeAction, | ||
@@ -769,6 +771,23 @@ }; | ||
}); | ||
}, true); | ||
const hotUpdate = store._hotUpdate; | ||
store._hotUpdate = markRaw((newStore) => { | ||
hotUpdate(newStore); | ||
api.addTimelineEvent({ | ||
layerId: MUTATIONS_LAYER_ID, | ||
event: { | ||
time: Date.now(), | ||
title: '🔥 ' + store.$id, | ||
subtitle: 'HMR update', | ||
data: { | ||
store: formatDisplay(store.$id), | ||
info: formatDisplay(`HMR update`), | ||
}, | ||
}, | ||
}); | ||
}); | ||
// trigger an update so it can display new registered stores | ||
// @ts-ignore | ||
api.notifyComponentUpdate(); | ||
api.sendInspectorTree(INSPECTOR_ID); | ||
api.sendInspectorState(INSPECTOR_ID); | ||
toastMessage(`"${store.$id}" store installed`); | ||
@@ -780,8 +799,10 @@ }); | ||
/** | ||
* pinia.use(devtoolsPlugin) | ||
* Patches a store to enable action grouping in devtools by wrapping the store with a Proxy that is passed as the context of all actions, allowing us to set `runningAction` on each access and effectively associating any state mutation to the action. | ||
* | ||
* @param store - store to patch | ||
* @param actionNames - list of actionst to patch | ||
*/ | ||
function devtoolsPlugin({ app, store, options, pinia }) { | ||
function patchActionForGrouping(store, actionNames) { | ||
// original actions of the store as they are given by pinia. We are going to override them | ||
const actions = Object.keys(options.actions || {}).reduce((storeActions, actionName) => { | ||
// @ts-expect-error | ||
const actions = actionNames.reduce((storeActions, actionName) => { | ||
// use toRaw to avoid tracking #541 | ||
@@ -792,5 +813,4 @@ storeActions[actionName] = toRaw(store)[actionName]; | ||
for (const actionName in actions) { | ||
// @ts-expect-error | ||
store[actionName] = function () { | ||
setActivePinia(pinia); | ||
// setActivePinia(store._p) | ||
// the running action id is incremented in a before action hook | ||
@@ -811,4 +831,26 @@ const _actionId = runningActionId; | ||
} | ||
addDevtools(app, | ||
// @ts-expect-error: FIXME: if possible... | ||
} | ||
/** | ||
* pinia.use(devtoolsPlugin) | ||
*/ | ||
function devtoolsPlugin({ app, store, options }) { | ||
// HMR module | ||
if (store.$id.startsWith('__hot:')) { | ||
return; | ||
} | ||
// only wrap actions in option-defined stores as this technique relies on | ||
// wrapping the context of the action with a proxy | ||
if ('id' in options) { | ||
patchActionForGrouping( | ||
// @ts-expect-error: can cast the store... | ||
store, Object.keys(options.actions)); | ||
const originalHotUpdate = store._hotUpdate; | ||
// Upgrade the HMR to also update the new actions | ||
toRaw(store)._hotUpdate = function (newStore) { | ||
originalHotUpdate.apply(this, arguments); | ||
patchActionForGrouping(store, Object.keys(newStore._hmrPayload.actions)); | ||
}; | ||
} | ||
addStoreToDevtools(app, | ||
// FIXME: is there a way to allow the assignment from Store<Id, S, G, A> to StoreGeneric? | ||
store); | ||
@@ -821,6 +863,6 @@ } | ||
function createPinia() { | ||
const scope = effectScope(true); | ||
// NOTE: here we could check the window object for a state and directly set it | ||
// if there is anything like it with Vue 3 SSR | ||
const state = ref({}); | ||
let localApp; | ||
const state = scope.run(() => ref({})); | ||
let _p = []; | ||
@@ -831,3 +873,3 @@ // plugins added before calling app.use(pinia) | ||
install(app) { | ||
pinia._a = localApp = app; | ||
pinia._a = app; | ||
app.provide(piniaSymbol, pinia); | ||
@@ -839,2 +881,5 @@ app.config.globalProperties.$pinia = pinia; | ||
setActivePinia(pinia); | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
registerPiniaDevtools(app, pinia); | ||
} | ||
} | ||
@@ -844,3 +889,3 @@ toBeInstalled.forEach((plugin) => _p.push(plugin)); | ||
use(plugin) { | ||
if (!localApp) { | ||
if (!this._a) { | ||
toBeInstalled.push(plugin); | ||
@@ -855,3 +900,6 @@ } | ||
// it's actually undefined here | ||
_a: localApp, | ||
// @ts-expect-error | ||
_a: null, | ||
_e: scope, | ||
_s: new Map(), | ||
state, | ||
@@ -867,4 +915,110 @@ }); | ||
/** | ||
* Checks if a function is a `StoreDefinition` | ||
* | ||
* @param fn - object to test | ||
* @returns true if `fn` is a StoreDefinition | ||
*/ | ||
const isUseStore = (fn) => { | ||
return typeof fn === 'function' && typeof fn.$id === 'string'; | ||
}; | ||
/** | ||
* Mutates in place `newState` with `oldState` to _hot update_ it. It will | ||
* remove any key not existing in `newState` and recursively merge plain | ||
* objects. | ||
* | ||
* @param newState - new state object to be patched | ||
* @param oldState - old state that should be used to patch newState | ||
* @returns - newState | ||
*/ | ||
function patchObject(newState, oldState) { | ||
// no need to go through symbols because they cannot be serialized anyway | ||
for (const key in oldState) { | ||
const subPatch = oldState[key]; | ||
// skip the whole sub tree | ||
if (!(key in newState)) { | ||
continue; | ||
} | ||
const targetValue = newState[key]; | ||
if (isPlainObject(targetValue) && | ||
isPlainObject(subPatch) && | ||
!isRef(subPatch) && | ||
!isReactive(subPatch)) { | ||
newState[key] = patchObject(targetValue, subPatch); | ||
} | ||
else { | ||
// objects are either a bit more complex (e.g. refs) or primitives, so we | ||
// just set the whole thing | ||
newState[key] = subPatch; | ||
} | ||
} | ||
return newState; | ||
} | ||
/** | ||
* Creates an _accept_ function to pass to `import.meta.hot` in Vite applications. | ||
* | ||
* @example | ||
* ```js | ||
* const useUser = defineStore(...) | ||
* if (import.meta.hot) { | ||
* import.meta.hot.accept(acceptHMRUpdate(useUser, import.meta.hot)) | ||
* } | ||
* ``` | ||
* | ||
* @param initialUseStore - return of the defineStore to hot update | ||
* @param hot - `import.meta.hot` | ||
*/ | ||
function acceptHMRUpdate(initialUseStore, hot) { | ||
return (newModule) => { | ||
const pinia = hot.data.pinia || initialUseStore._pinia; | ||
if (!pinia) { | ||
// this store is still not used | ||
return; | ||
} | ||
// preserve the pinia instance across loads | ||
hot.data.pinia = pinia; | ||
// console.log('got data', newStore) | ||
for (const exportName in newModule) { | ||
const useStore = newModule[exportName]; | ||
// console.log('checking for', exportName) | ||
if (isUseStore(useStore) && pinia._s.has(useStore.$id)) { | ||
// console.log('Accepting update for', useStore.$id) | ||
const id = useStore.$id; | ||
if (id !== initialUseStore.$id) { | ||
console.warn(`The id of the store changed from "${initialUseStore.$id}" to "${id}". Reloading.`); | ||
// return import.meta.hot.invalidate() | ||
return hot.invalidate(); | ||
} | ||
const existingStore = pinia._s.get(id); | ||
if (!existingStore) { | ||
console.log(`skipping hmr because store doesn't exist yet`); | ||
return; | ||
} | ||
useStore(pinia, existingStore); | ||
} | ||
} | ||
}; | ||
} | ||
function addSubscription(subscriptions, callback, detached) { | ||
subscriptions.push(callback); | ||
const removeSubscription = () => { | ||
const idx = subscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
subscriptions.splice(idx, 1); | ||
} | ||
}; | ||
if (!detached && getCurrentInstance()) { | ||
onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function triggerSubscriptions(subscriptions, ...args) { | ||
subscriptions.forEach((callback) => { | ||
callback(...args); | ||
}); | ||
} | ||
function innerPatch(target, patchToApply) { | ||
// TODO: get all keys like symbols as well | ||
// no need to go through symbols because they cannot be serialized anyway | ||
for (const key in patchToApply) { | ||
@@ -887,36 +1041,98 @@ const subPatch = patchToApply[key]; | ||
const { assign } = Object; | ||
/** | ||
* Create an object of computed properties referring to | ||
* | ||
* @param rootStateRef - pinia.state | ||
* @param id - unique name | ||
*/ | ||
function computedFromState(rootStateRef, id) { | ||
// let asComputed = computed<T>() | ||
const reactiveObject = {}; | ||
const state = rootStateRef.value[id]; | ||
for (const key in state) { | ||
// @ts-expect-error: the key matches | ||
reactiveObject[key] = computed({ | ||
get: () => rootStateRef.value[id][key], | ||
set: (value) => (rootStateRef.value[id][key] = value), | ||
}); | ||
function isComputed(o) { | ||
return o && o.effect; | ||
} | ||
function createOptionsStore(id, options, pinia, hot) { | ||
const { state, actions, getters } = options; | ||
function $reset() { | ||
pinia.state.value[id] = state ? state() : {}; | ||
} | ||
return reactiveObject; | ||
const initialState = pinia.state.value[id]; | ||
let store; | ||
function setup() { | ||
if (!initialState && (!(process.env.NODE_ENV !== 'production') || !hot)) { | ||
$reset(); | ||
} | ||
// pinia.state.value[id] = state ? state() : {} | ||
// avoid creating a state in pinia.state.value | ||
const localState = (process.env.NODE_ENV !== 'production') && hot | ||
? toRefs(ref(state ? state() : {}).value) | ||
: initialState || toRefs(pinia.state.value[id]); | ||
return assign(localState, actions, Object.keys(getters || {}).reduce((computedGetters, name) => { | ||
computedGetters[name] = markRaw(computed(() => { | ||
setActivePinia(pinia); | ||
// const context = store || ref(localState).value | ||
// @ts-expect-error | ||
// return getters![name].call(context, context) | ||
return store && getters[name].call(store, store); | ||
})); | ||
return computedGetters; | ||
}, {})); | ||
} | ||
store = createSetupStore(id, setup, options, pinia, hot); | ||
store.$reset = $reset; | ||
return store; | ||
} | ||
/** | ||
* Creates a store with its state object. This is meant to be augmented with getters and actions | ||
* | ||
* @param id - unique identifier of the store, like a name. eg: main, cart, user | ||
* @param buildState - function to build the initial state | ||
* @param initialState - initial state applied to the store, Must be correctly typed to infer typings | ||
*/ | ||
function initStore($id, buildState = () => ({}), initialState) { | ||
const pinia = getActivePinia(); | ||
pinia.state.value[$id] = initialState || buildState(); | ||
// const state: Ref<S> = toRef(_p.state.value, $id) | ||
let isListening = true; | ||
const noop = () => { }; | ||
function createSetupStore($id, setup, options = {}, pinia, hot) { | ||
let scope; | ||
const buildState = options.state; | ||
const optionsForPlugin = { | ||
actions: {}, | ||
...options, | ||
}; | ||
// watcher options for $subscribe | ||
const $subscribeOptions = { deep: true, flush: 'sync' }; | ||
/* istanbul ignore else */ | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
$subscribeOptions.onTrigger = (event) => { | ||
if (isListening) { | ||
debuggerEvents = event; | ||
// avoid triggering this while the store is being built and the state is being set in pinia | ||
} | ||
else if (isListening == false && !store._hotUpdating) { | ||
// let patch send all the events together later | ||
/* istanbul ignore else */ | ||
if (Array.isArray(debuggerEvents)) { | ||
debuggerEvents.push(event); | ||
} | ||
else { | ||
console.error('🍍 debuggerEvents should be an array. This is most likely an internal Pinia bug.'); | ||
} | ||
} | ||
}; | ||
} | ||
// internal state | ||
let isListening; // set to true at the end | ||
let subscriptions = markRaw([]); | ||
let actionSubscriptions = markRaw([]); | ||
let debuggerEvents; | ||
const initialState = pinia.state.value[$id]; | ||
if (!initialState && (process.env.NODE_ENV !== 'production') && !hot) { | ||
// should be set in Vue 2 | ||
pinia.state.value[$id] = {}; | ||
} | ||
const hotState = ref({}); | ||
if ((process.env.NODE_ENV !== 'production') && !pinia._e.active) { | ||
throw new Error('Pinia destroyed'); | ||
} | ||
// TODO: idea create skipSerialize that marks properties as non serializable and they are skipped | ||
const setupStore = pinia._e.run(() => { | ||
scope = effectScope(); | ||
return scope.run(() => { | ||
// skip setting up the watcher on HMR | ||
if (!(process.env.NODE_ENV !== 'production') || !hot) { | ||
watch(() => pinia.state.value[$id], (state, oldState) => { | ||
if (isListening) { | ||
triggerSubscriptions(subscriptions, { | ||
storeId: $id, | ||
type: MutationType.direct, | ||
events: debuggerEvents, | ||
}, state); | ||
} | ||
}, $subscribeOptions); | ||
} | ||
return setup(); | ||
}); | ||
}); | ||
function $patch(partialStateOrMutator) { | ||
@@ -949,124 +1165,20 @@ let subscriptionMutation; | ||
// because we paused the watcher, we need to manually call the subscriptions | ||
subscriptions.forEach((callback) => { | ||
callback(subscriptionMutation, pinia.state.value[$id]); | ||
}); | ||
triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id]); | ||
} | ||
function $subscribe(callback) { | ||
subscriptions.push(callback); | ||
// watch here to link the subscription to the current active instance | ||
// e.g. inside the setup of a component | ||
const options = { deep: true, flush: 'sync' }; | ||
/* istanbul ignore else */ | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
options.onTrigger = (event) => { | ||
if (isListening) { | ||
debuggerEvents = event; | ||
} | ||
else { | ||
// let patch send all the events together later | ||
/* istanbul ignore else */ | ||
if (Array.isArray(debuggerEvents)) { | ||
debuggerEvents.push(event); | ||
} | ||
else { | ||
console.error('🍍 debuggerEvents should be an array. This is most likely an internal Pinia bug.'); | ||
} | ||
} | ||
}; | ||
const $reset = (process.env.NODE_ENV !== 'production') | ||
? () => { | ||
throw new Error(`🍍: Store "${$id}" is build using the setup syntax and does not implement $reset().`); | ||
} | ||
const stopWatcher = watch(() => pinia.state.value[$id], (state, oldState) => { | ||
if (isListening) { | ||
callback({ | ||
storeId: $id, | ||
type: MutationType.direct, | ||
events: debuggerEvents, | ||
}, state); | ||
} | ||
}, options); | ||
const removeSubscription = () => { | ||
const idx = subscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
subscriptions.splice(idx, 1); | ||
stopWatcher(); | ||
} | ||
}; | ||
if (getCurrentInstance()) { | ||
onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function $onAction(callback) { | ||
actionSubscriptions.push(callback); | ||
const removeSubscription = () => { | ||
const idx = actionSubscriptions.indexOf(callback); | ||
if (idx > -1) { | ||
actionSubscriptions.splice(idx, 1); | ||
} | ||
}; | ||
if (getCurrentInstance()) { | ||
onUnmounted(removeSubscription); | ||
} | ||
return removeSubscription; | ||
} | ||
function $reset() { | ||
pinia.state.value[$id] = buildState(); | ||
} | ||
const storeWithState = { | ||
$id, | ||
_p: pinia, | ||
_as: actionSubscriptions, | ||
// $state is added underneath | ||
$patch, | ||
$subscribe, | ||
$onAction, | ||
$reset, | ||
}; | ||
const injectionSymbol = (process.env.NODE_ENV !== 'production') | ||
? Symbol(`PiniaStore(${$id})`) | ||
: /* istanbul ignore next */ | ||
Symbol(); | ||
return [ | ||
storeWithState, | ||
{ | ||
get: () => pinia.state.value[$id], | ||
set: (newState) => { | ||
isListening = false; | ||
pinia.state.value[$id] = newState; | ||
isListening = true; | ||
}, | ||
}, | ||
injectionSymbol, | ||
]; | ||
} | ||
const noop = () => { }; | ||
/** | ||
* Creates a store bound to the lifespan of where the function is called. This | ||
* means creating the store inside of a component's setup will bound it to the | ||
* lifespan of that component while creating it outside of a component will | ||
* create an ever living store | ||
* | ||
* @param partialStore - store with state returned by initStore | ||
* @param descriptor - descriptor to setup $state property | ||
* @param $id - unique name of the store | ||
* @param getters - getters of the store | ||
* @param actions - actions of the store | ||
*/ | ||
function buildStoreToUse(partialStore, descriptor, $id, getters = {}, actions = {}, options) { | ||
const pinia = getActivePinia(); | ||
const computedGetters = {}; | ||
for (const getterName in getters) { | ||
// @ts-ignore: it's only readonly for the users | ||
computedGetters[getterName] = computed(() => { | ||
: noop; | ||
/** | ||
* Wraps an action to handle subscriptions. | ||
* | ||
* @param name - name of the action | ||
* @param action - action to wrap | ||
* @returns a wrapped action to handle subscriptions | ||
*/ | ||
function wrapAction(name, action) { | ||
return function () { | ||
setActivePinia(pinia); | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
// @ts-expect-error: the argument count is correct | ||
return getters[getterName].call(store, store); | ||
}); | ||
} | ||
const wrappedActions = {}; | ||
for (const actionName in actions) { | ||
wrappedActions[actionName] = function () { | ||
setActivePinia(pinia); | ||
const args = Array.from(arguments); | ||
const localStore = this || store; | ||
let afterCallback = noop; | ||
@@ -1080,18 +1192,99 @@ let onErrorCallback = noop; | ||
} | ||
partialStore._as.forEach((callback) => { | ||
// @ts-expect-error | ||
callback({ args, name: actionName, store: localStore, after, onError }); | ||
// @ts-expect-error | ||
triggerSubscriptions(actionSubscriptions, { | ||
args, | ||
name, | ||
store, | ||
after, | ||
onError, | ||
}); | ||
let ret; | ||
try { | ||
ret = actions[actionName].apply(localStore, args); | ||
Promise.resolve(ret).then(afterCallback).catch(onErrorCallback); | ||
ret = action.apply(this && this.$id === $id ? this : store, args); | ||
// handle sync errors | ||
} | ||
catch (error) { | ||
onErrorCallback(error); | ||
throw error; | ||
if (onErrorCallback(error) !== false) { | ||
throw error; | ||
} | ||
} | ||
return ret; | ||
if (ret instanceof Promise) { | ||
return ret | ||
.then((value) => { | ||
const newRet = afterCallback(value); | ||
// allow the afterCallback to override the return value | ||
return newRet === undefined ? value : newRet; | ||
}) | ||
.catch((error) => { | ||
if (onErrorCallback(error) !== false) { | ||
return Promise.reject(error); | ||
} | ||
}); | ||
} | ||
// allow the afterCallback to override the return value | ||
const newRet = afterCallback(ret); | ||
return newRet === undefined ? ret : newRet; | ||
}; | ||
} | ||
const _hmrPayload = /*#__PURE__*/ markRaw({ | ||
actions: {}, | ||
getters: {}, | ||
state: [], | ||
hotState, | ||
}); | ||
// overwrite existing actions to support $onAction | ||
for (const key in setupStore) { | ||
const prop = setupStore[key]; | ||
if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) { | ||
// mark it as a piece of state to be serialized | ||
if ((process.env.NODE_ENV !== 'production') && hot) { | ||
hotState.value[key] = toRef(setupStore, key); | ||
// createOptionStore already did this | ||
} | ||
else if (!buildState) { | ||
pinia.state.value[$id][key] = prop; | ||
// TODO: avoid if state exists for SSR | ||
} | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
_hmrPayload.state.push(key); | ||
} | ||
// action | ||
} | ||
else if (typeof prop === 'function') { | ||
// @ts-expect-error: we are overriding the function we avoid wrapping if | ||
// this a hot module replacement store because the hotUpdate method needs | ||
// to do it with the right context | ||
setupStore[key] = (process.env.NODE_ENV !== 'production') && hot ? prop : wrapAction(key, prop); | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
_hmrPayload.actions[key] = prop; | ||
} | ||
// list actions so they can be used in plugins | ||
// @ts-expect-error | ||
optionsForPlugin.actions[key] = prop; | ||
} | ||
else if ((process.env.NODE_ENV !== 'production')) { | ||
// add getters for devtools | ||
if (isComputed(prop)) { | ||
_hmrPayload.getters[key] = buildState | ||
? // @ts-expect-error | ||
options.getters[key] | ||
: prop; | ||
if (IS_CLIENT) { | ||
const getters = | ||
// @ts-expect-error: it should be on the store | ||
setupStore._getters || (setupStore._getters = markRaw([])); | ||
getters.push(key); | ||
} | ||
} | ||
} | ||
} | ||
const partialStore = { | ||
_p: pinia, | ||
// _s: scope, | ||
$id, | ||
$onAction: addSubscription.bind(null, actionSubscriptions), | ||
$patch, | ||
$reset, | ||
$subscribe: addSubscription.bind(null, subscriptions), | ||
}; | ||
const store = reactive(assign((process.env.NODE_ENV !== 'production') && IS_CLIENT | ||
@@ -1101,13 +1294,102 @@ ? // devtools custom properties | ||
_customProperties: markRaw(new Set()), | ||
_hmrPayload, | ||
} | ||
: {}, partialStore, | ||
// using this means no new properties can be added as state | ||
computedFromState(pinia.state, $id), computedGetters, wrappedActions)); | ||
: {}, partialStore, setupStore)); | ||
// use this instead of a computed with setter to be able to create it anywhere | ||
// without linking the computed lifespan to wherever the store is first | ||
// created. | ||
Object.defineProperty(store, '$state', descriptor); | ||
// add getters for devtools | ||
if ((process.env.NODE_ENV !== 'production') && IS_CLIENT) { | ||
store._getters = markRaw(Object.keys(getters)); | ||
Object.defineProperty(store, '$state', { | ||
get: () => ((process.env.NODE_ENV !== 'production') && hot ? hotState.value : pinia.state.value[$id]), | ||
set: (state) => { | ||
if ((process.env.NODE_ENV !== 'production') && hot) { | ||
throw new Error('cannot set hotState'); | ||
} | ||
pinia.state.value[$id] = state; | ||
}, | ||
}); | ||
// add the hotUpdate before plugins to allow them to override it | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
store._hotUpdate = markRaw((newStore) => { | ||
store._hotUpdating = true; | ||
newStore._hmrPayload.state.forEach((stateKey) => { | ||
if (stateKey in store.$state) { | ||
const newStateTarget = newStore.$state[stateKey]; | ||
const oldStateSource = store.$state[stateKey]; | ||
if (typeof newStateTarget === 'object' && | ||
isPlainObject(newStateTarget) && | ||
isPlainObject(oldStateSource)) { | ||
patchObject(newStateTarget, oldStateSource); | ||
} | ||
else { | ||
// transfer the ref | ||
newStore.$state[stateKey] = oldStateSource; | ||
} | ||
} | ||
// patch direct access properties to allow store.stateProperty to work as | ||
// store.$state.stateProperty | ||
// @ts-expect-error | ||
store[stateKey] = toRef(newStore.$state, stateKey); | ||
}); | ||
// remove deleted state properties | ||
Object.keys(store.$state).forEach((stateKey) => { | ||
if (!(stateKey in newStore.$state)) { | ||
delete store[stateKey]; | ||
} | ||
}); | ||
// avoid devtools logging this as a mutation | ||
isListening = false; | ||
pinia.state.value[$id] = toRef(newStore._hmrPayload, 'hotState'); | ||
isListening = true; | ||
for (const actionName in newStore._hmrPayload.actions) { | ||
const action = newStore[actionName]; | ||
// @ts-expect-error: new key | ||
store[actionName] = | ||
// new line forced for TS | ||
wrapAction(actionName, action); | ||
} | ||
// TODO: does this work in both setup and option store? | ||
for (const getterName in newStore._hmrPayload.getters) { | ||
const getter = newStore._hmrPayload.getters[getterName]; | ||
// @ts-expect-error | ||
store[getterName] = | ||
// --- | ||
buildState | ||
? // special handling of options api | ||
computed(() => { | ||
setActivePinia(pinia); | ||
return getter.call(store, store); | ||
}) | ||
: getter; | ||
} | ||
// remove deleted getters | ||
Object.keys(store._hmrPayload.getters).forEach((key) => { | ||
if (!(key in newStore._hmrPayload.getters)) { | ||
delete store[key]; | ||
} | ||
}); | ||
// remove old actions | ||
Object.keys(store._hmrPayload.actions).forEach((key) => { | ||
if (!(key in newStore._hmrPayload.actions)) { | ||
delete store[key]; | ||
} | ||
}); | ||
// update the values used in devtools and to allow deleting new properties later on | ||
store._hmrPayload = newStore._hmrPayload; | ||
store._getters = newStore._getters; | ||
store._hotUpdating = false; | ||
}); | ||
const nonEnumerable = { | ||
writable: true, | ||
configurable: true, | ||
// avoid warning on devtools trying to display this property | ||
enumerable: false, | ||
}; | ||
if (IS_CLIENT) { | ||
['_p', '_hmrPayload', '_getters', '_customProperties'].forEach((p) => { | ||
Object.defineProperty(store, p, { | ||
value: store[p], | ||
...nonEnumerable, | ||
}); | ||
}); | ||
} | ||
} | ||
@@ -1117,4 +1399,9 @@ // apply all plugins | ||
if ((process.env.NODE_ENV !== 'production') && IS_CLIENT) { | ||
// @ts-expect-error: conflict between A and ActionsTree | ||
const extensions = extender({ store, app: pinia._a, pinia, options }); | ||
const extensions = extender({ | ||
store, | ||
app: pinia._a, | ||
pinia, | ||
// @ts-expect-error | ||
options: optionsForPlugin, | ||
}); | ||
Object.keys(extensions || {}).forEach((key) => store._customProperties.add(key)); | ||
@@ -1124,19 +1411,34 @@ assign(store, extensions); | ||
else { | ||
// @ts-expect-error: conflict between A and ActionsTree | ||
assign(store, extender({ store, app: pinia._a, pinia, options })); | ||
assign(store, extender({ | ||
store, | ||
app: pinia._a, | ||
pinia, | ||
// @ts-expect-error | ||
options: optionsForPlugin, | ||
})); | ||
} | ||
}); | ||
if (initialState) { | ||
(options.hydrate || innerPatch)(store, initialState); | ||
} | ||
isListening = true; | ||
return store; | ||
} | ||
/** | ||
* Creates a `useStore` function that retrieves the store instance | ||
* @param options - options to define the store | ||
*/ | ||
function defineStore(options) { | ||
const { id, state, getters, actions } = options; | ||
function useStore(pinia) { | ||
function defineStore( | ||
// TODO: add proper types from above | ||
idOrOptions, setup, setupOptions) { | ||
let id; | ||
let options; | ||
const isSetupStore = typeof setup === 'function'; | ||
if (typeof idOrOptions === 'string') { | ||
id = idOrOptions; | ||
// the option store setup will contain the actual options in this case | ||
options = isSetupStore ? setupOptions : setup; | ||
} | ||
else { | ||
options = idOrOptions; | ||
id = idOrOptions.id; | ||
} | ||
function useStore(pinia, hot) { | ||
const currentInstance = getCurrentInstance(); | ||
// only run provide when pinia hasn't been manually passed | ||
const shouldProvide = currentInstance && !pinia; | ||
// avoid injecting if `useStore` when not possible | ||
pinia = | ||
@@ -1151,33 +1453,36 @@ // in test mode, ignore the argument provided as we can always retrieve a | ||
pinia = getActivePinia(); | ||
let storeCache = storesMap.get(pinia); | ||
if (!storeCache) | ||
storesMap.set(pinia, (storeCache = new Map())); | ||
let storeAndDescriptor = storeCache.get(id); | ||
let store; | ||
if (!storeAndDescriptor) { | ||
storeAndDescriptor = initStore(id, state, pinia.state.value[id]); | ||
// @ts-expect-error: annoying to type | ||
storeCache.set(id, storeAndDescriptor); | ||
store = buildStoreToUse(storeAndDescriptor[0], storeAndDescriptor[1], id, getters, actions, options); | ||
// allow children to reuse this store instance to avoid creating a new | ||
// store for each child | ||
if (shouldProvide) { | ||
provide(storeAndDescriptor[2], store); | ||
if (!pinia._s.has(id)) { | ||
pinia._s.set(id, isSetupStore | ||
? createSetupStore(id, setup, options, pinia) | ||
: createOptionsStore(id, options, pinia)); | ||
if ((process.env.NODE_ENV !== 'production')) { | ||
// @ts-expect-error: not the right inferred type | ||
useStore._pinia = pinia; | ||
} | ||
} | ||
else { | ||
store = | ||
(currentInstance && inject(storeAndDescriptor[2], null)) || | ||
buildStoreToUse(storeAndDescriptor[0], storeAndDescriptor[1], id, getters, actions, options); | ||
const store = pinia._s.get(id); | ||
if ((process.env.NODE_ENV !== 'production') && hot) { | ||
const hotId = '__hot:' + id; | ||
const newStore = isSetupStore | ||
? createSetupStore(hotId, setup, options, pinia, true) | ||
: createOptionsStore(hotId, assign({}, options), pinia, true); | ||
hot._hotUpdate(newStore); | ||
// cleanup the state properties and the store from the cache | ||
delete pinia.state.value[hotId]; | ||
pinia._s.delete(hotId); | ||
} | ||
// save stores in instances to access them devtools | ||
if ((process.env.NODE_ENV !== 'production') && IS_CLIENT && currentInstance && currentInstance.proxy) { | ||
if ((process.env.NODE_ENV !== 'production') && | ||
IS_CLIENT && | ||
currentInstance && | ||
currentInstance.proxy && | ||
// avoid adding stores that are just built for hot module replacement | ||
!hot) { | ||
const vm = currentInstance.proxy; | ||
const cache = '_pStores' in vm ? vm._pStores : (vm._pStores = {}); | ||
// @ts-expect-error: still can't cast Store with generics to Store | ||
cache[store.$id] = store; | ||
cache[id] = store; | ||
} | ||
// StoreGeneric cannot be casted towards Store | ||
return store; | ||
} | ||
// needed by map helpers | ||
useStore.$id = id; | ||
@@ -1187,8 +1492,2 @@ return useStore; | ||
function getCachedStore(vm, useStore) { | ||
const cache = '_pStores' in vm ? vm._pStores : (vm._pStores = {}); | ||
const id = useStore.$id; | ||
return (cache[id] || | ||
(cache[id] = useStore(vm.$pinia))); | ||
} | ||
let mapStoreSuffix = 'Store'; | ||
@@ -1241,3 +1540,3 @@ /** | ||
reduced[useStore.$id + mapStoreSuffix] = function () { | ||
return getCachedStore(this, useStore); | ||
return useStore(this.$pinia); | ||
}; | ||
@@ -1259,4 +1558,3 @@ return reduced; | ||
reduced[key] = function () { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key]; | ||
return useStore(this.$pinia)[key]; | ||
}; | ||
@@ -1266,4 +1564,5 @@ return reduced; | ||
: Object.keys(keysOrMapper).reduce((reduced, key) => { | ||
// @ts-expect-error | ||
reduced[key] = function () { | ||
const store = getCachedStore(this, useStore); | ||
const store = useStore(this.$pinia); | ||
const storeKey = keysOrMapper[key]; | ||
@@ -1297,4 +1596,3 @@ // for some reason TS is unable to infer the type of storeKey to be a | ||
reduced[key] = function (...args) { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key](...args); | ||
return useStore(this.$pinia)[key](...args); | ||
}; | ||
@@ -1306,4 +1604,3 @@ return reduced; | ||
reduced[key] = function (...args) { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[keysOrMapper[key]](...args); | ||
return useStore(this.$pinia)[keysOrMapper[key]](...args); | ||
}; | ||
@@ -1327,9 +1624,7 @@ return reduced; | ||
get() { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[key]; | ||
return useStore(this.$pinia)[key]; | ||
}, | ||
set(value) { | ||
// it's easier to type it here as any | ||
// @ts-expect-error | ||
return (getCachedStore(this, useStore)[key] = value); | ||
return (useStore(this.$pinia)[key] = value); | ||
}, | ||
@@ -1343,10 +1638,7 @@ }; | ||
get() { | ||
// @ts-expect-error | ||
return getCachedStore(this, useStore)[keysOrMapper[key]]; | ||
return useStore(this.$pinia)[keysOrMapper[key]]; | ||
}, | ||
set(value) { | ||
// it's easier to type it here as any | ||
// @ts-expect-error | ||
return (getCachedStore(this, useStore)[keysOrMapper[key]] = | ||
value); | ||
return (useStore(this.$pinia)[keysOrMapper[key]] = value); | ||
}, | ||
@@ -1366,3 +1658,3 @@ }; | ||
* | ||
* @internal - STILL NOT RELEASED, DO NOT USE. It will be likely moved to its | ||
* @alpha - STILL NOT RELEASED, DO NOT USE. It will be likely moved to its | ||
* own package. | ||
@@ -1376,3 +1668,3 @@ * | ||
plugins.forEach((plugin) => pinia.use(plugin)); | ||
// @ts-ignore | ||
// @ts-ignore: this can fail in TS depending of the existence of jest | ||
createSpy = createSpy || (typeof jest !== undefined && jest.fn); | ||
@@ -1385,14 +1677,10 @@ if (!createSpy) { | ||
pinia.use(({ store, options }) => { | ||
if (!spiedActions.has(options.id)) { | ||
spiedActions.set(options.id, {}); | ||
if (!spiedActions.has(store.$id)) { | ||
spiedActions.set(store.$id, {}); | ||
} | ||
const actionsCache = spiedActions.get(options.id); | ||
const actionsCache = spiedActions.get(store.$id); | ||
Object.keys(options.actions || {}).forEach((action) => { | ||
actionsCache[action] = | ||
actionsCache[action] || | ||
(stubActions | ||
? createSpy() | ||
: // @ts-expect-error: | ||
createSpy(store[action])); | ||
// @ts-expect-error: | ||
(stubActions ? createSpy() : createSpy(store[action])); | ||
store[action] = actionsCache[action]; | ||
@@ -1418,2 +1706,2 @@ }); | ||
export { MutationType, createPinia, createTestingPinia, defineStore, mapActions, mapGetters, mapState, mapStores, mapWritableState, setActivePinia, setMapStoreSuffix }; | ||
export { MutationType, acceptHMRUpdate, createPinia, createTestingPinia, defineStore, mapActions, mapGetters, mapState, mapStores, mapWritableState, setActivePinia, setMapStoreSuffix }; |
/*! | ||
* pinia v2.0.0-beta.5 | ||
* pinia v2.0.0-rc.0 | ||
* (c) 2021 Eduardo San Martin Morote | ||
* @license MIT | ||
*/ | ||
var Pinia=function(t,e){"use strict";let n;const r=t=>n=t,o=()=>n,c=new WeakMap,s=Symbol();function i(t){return t&&"object"==typeof t&&"[object Object]"===Object.prototype.toString.call(t)&&"function"!=typeof t.toJSON}var a;t.MutationType=void 0,(a=t.MutationType||(t.MutationType={})).direct="direct",a.patchObject="patch object",a.patchFunction="patch function";const u="undefined"!=typeof window;function p(){const t=e.ref({});let n,o=[];const c=[],i=e.markRaw({install(t){i._a=n=t,t.provide(s,i),t.config.globalProperties.$pinia=i,u&&r(i),c.forEach((t=>o.push(t)))},use(t){return n?o.push(t):c.push(t),this},_p:o,_a:n,state:t});return i}function f(t,n){for(const r in n){const o=n[r],c=t[r];t[r]=i(c)&&i(o)&&!e.isRef(o)&&!e.isReactive(o)?f(c,o):o}return t}const{assign:l}=Object;function h(n,r=(()=>({})),c){const s=o();s.state.value[n]=c||r();let i,a=!0,u=e.markRaw([]),p=e.markRaw([]);return[{$id:n,_p:s,_as:p,$patch:function(e){let r;a=!1,"function"==typeof e?(e(s.state.value[n]),r={type:t.MutationType.patchFunction,storeId:n,events:i}):(f(s.state.value[n],e),r={type:t.MutationType.patchObject,payload:e,storeId:n,events:i}),a=!0,u.forEach((t=>{t(r,s.state.value[n])}))},$subscribe:function(r){u.push(r);const o=e.watch((()=>s.state.value[n]),((e,o)=>{a&&r({storeId:n,type:t.MutationType.direct,events:i},e)}),{deep:!0,flush:"sync"}),c=()=>{const t=u.indexOf(r);t>-1&&(u.splice(t,1),o())};return e.getCurrentInstance()&&e.onUnmounted(c),c},$onAction:function(t){p.push(t);const n=()=>{const e=p.indexOf(t);e>-1&&p.splice(e,1)};return e.getCurrentInstance()&&e.onUnmounted(n),n},$reset:function(){s.state.value[n]=r()}},{get:()=>s.state.value[n],set:t=>{a=!1,s.state.value[n]=t,a=!0}},Symbol()]}const d=()=>{};function y(t,n,c,s={},i={},a){const u=o(),p={};for(const t in s)p[t]=e.computed((()=>(r(u),s[t].call(h,h))));const f={};for(const e in i)f[e]=function(){r(u);const n=Array.from(arguments),o=this||h;let c,s=d,a=d;function p(t){s=t}function f(t){a=t}t._as.forEach((t=>{t({args:n,name:e,store:o,after:p,onError:f})}));try{c=i[e].apply(o,n),Promise.resolve(c).then(s).catch(a)}catch(t){throw a(t),t}return c};const h=e.reactive(l({},t,function(t,n){const r={},o=t.value[n];for(const c in o)r[c]=e.computed({get:()=>t.value[n][c],set:e=>t.value[n][c]=e});return r}(u.state,c),p,f));return Object.defineProperty(h,"$state",n),u._p.forEach((t=>{l(h,t({store:h,app:u._a,pinia:u,options:a}))})),h}function v(t,e){const n="_pStores"in t?t._pStores:t._pStores={},r=e.$id;return n[r]||(n[r]=e(t.$pinia))}let b="Store";function g(t,e){return Array.isArray(e)?e.reduce(((e,n)=>(e[n]=function(){return v(this,t)[n]},e)),{}):Object.keys(e).reduce(((n,r)=>(n[r]=function(){const n=v(this,t),o=e[r];return"function"==typeof o?o.call(this,n):n[o]},n)),{})}const j=g;return t.createPinia=p,t.createTestingPinia=function({plugins:t=[],stubActions:n=!0,stubPatch:o=!1,fakeApp:c=!1,createSpy:s}={}){const i=p();if(t.forEach((t=>i.use(t))),!(s=s||void 0!==typeof jest&&jest.fn))throw new Error("You must configure the `createSpy` option.");const a=new Map;if(i.use((({store:t,options:e})=>{a.has(e.id)||a.set(e.id,{});const r=a.get(e.id);Object.keys(e.actions||{}).forEach((e=>{r[e]=r[e]||(n?s():s(t[e])),t[e]=r[e]})),t.$patch=o?s():s(t.$patch)})),c){e.createApp({}).use(i)}return i._testing=!0,r(i),Object.assign({resetSpyCache(){a.clear()},get app(){return this._a}},i)},t.defineStore=function(t){const{id:n,state:i,getters:a,actions:u}=t;function p(p){const f=e.getCurrentInstance(),l=f&&!p;(p=p||f&&e.inject(s))&&r(p),p=o();let d=c.get(p);d||c.set(p,d=new Map);let v,b=d.get(n);return b?v=f&&e.inject(b[2],null)||y(b[0],b[1],n,a,u,t):(b=h(n,i,p.state.value[n]),d.set(n,b),v=y(b[0],b[1],n,a,u,t),l&&e.provide(b[2],v)),v}return p.$id=n,p},t.mapActions=function(t,e){return Array.isArray(e)?e.reduce(((e,n)=>(e[n]=function(...e){return v(this,t)[n](...e)},e)),{}):Object.keys(e).reduce(((n,r)=>(n[r]=function(...n){return v(this,t)[e[r]](...n)},n)),{})},t.mapGetters=j,t.mapState=g,t.mapStores=function(...t){return t.reduce(((t,e)=>(t[e.$id+b]=function(){return v(this,e)},t)),{})},t.mapWritableState=function(t,e){return Array.isArray(e)?e.reduce(((e,n)=>(e[n]={get(){return v(this,t)[n]},set(e){return v(this,t)[n]=e}},e)),{}):Object.keys(e).reduce(((n,r)=>(n[r]={get(){return v(this,t)[e[r]]},set(n){return v(this,t)[e[r]]=n}},n)),{})},t.setActivePinia=r,t.setMapStoreSuffix=function(t){b=t},Object.defineProperty(t,"__esModule",{value:!0}),t}({},Vue); | ||
var Pinia=function(t,e){"use strict";let n;const i=t=>n=t,r=Symbol();function o(t){return t&&"object"==typeof t&&"[object Object]"===Object.prototype.toString.call(t)&&"function"!=typeof t.toJSON}var c;t.MutationType=void 0,(c=t.MutationType||(t.MutationType={})).direct="direct",c.patchObject="patch object",c.patchFunction="patch function";const s="undefined"!=typeof window;function a(){const t=e.effectScope(!0),n=t.run((()=>e.ref({})));let o=[];const c=[],a=e.markRaw({install(t){a._a=t,t.provide(r,a),t.config.globalProperties.$pinia=a,s&&i(a),c.forEach((t=>o.push(t)))},use(t){return this._a?o.push(t):c.push(t),this},_p:o,_a:null,_e:t,_s:new Map,state:n});return a}function u(t,n,i){t.push(n);const r=()=>{const e=t.indexOf(n);e>-1&&t.splice(e,1)};return!i&&e.getCurrentInstance()&&e.onUnmounted(r),r}function f(t,...e){t.forEach((t=>{t(...e)}))}function p(t,n){for(const i in n){const r=n[i],c=t[i];t[i]=o(c)&&o(r)&&!e.isRef(r)&&!e.isReactive(r)?p(c,r):r}return t}const{assign:d}=Object;const h=()=>{};function l(n,r,o={},c,s){let a;const l=o.state,y={actions:{},...o},$={deep:!0,flush:"sync"};let v,b,g=e.markRaw([]),j=e.markRaw([]);const _=c.state.value[n];e.ref({});const m=c._e.run((()=>(a=e.effectScope(),a.run((()=>(e.watch((()=>c.state.value[n]),((e,i)=>{v&&f(g,{storeId:n,type:t.MutationType.direct,events:b},e)}),$),r()))))));const O=h;function S(t,e){return function(){i(c);const r=Array.from(arguments);let o,s=h,a=h;function u(t){s=t}function p(t){a=t}f(j,{args:r,name:t,store:k,after:u,onError:p});try{o=e.apply(this&&this.$id===n?this:k,r)}catch(t){if(!1!==a(t))throw t}if(o instanceof Promise)return o.then((t=>{const e=s(t);return void 0===e?t:e})).catch((t=>{if(!1!==a(t))return Promise.reject(t)}));const d=s(o);return void 0===d?o:d}}for(const t in m){const i=m[t];e.isRef(i)&&(!(w=i)||!w.effect)||e.isReactive(i)?l||(c.state.value[n][t]=i):"function"==typeof i&&(m[t]=S(t,i),y.actions[t]=i)}var w;const A={_p:c,$id:n,$onAction:u.bind(null,j),$patch:function(e){let i;v=!1,"function"==typeof e?(e(c.state.value[n]),i={type:t.MutationType.patchFunction,storeId:n,events:b}):(p(c.state.value[n],e),i={type:t.MutationType.patchObject,payload:e,storeId:n,events:b}),v=!0,f(g,i,c.state.value[n])},$reset:O,$subscribe:u.bind(null,g)},k=e.reactive(d({},A,m));return Object.defineProperty(k,"$state",{get:()=>c.state.value[n],set:t=>{c.state.value[n]=t}}),c._p.forEach((t=>{d(k,t({store:k,app:c._a,pinia:c,options:y}))})),_&&(o.hydrate||p)(k,_),v=!0,k}let y="Store";function $(t,e){return Array.isArray(e)?e.reduce(((e,n)=>(e[n]=function(){return t(this.$pinia)[n]},e)),{}):Object.keys(e).reduce(((n,i)=>(n[i]=function(){const n=t(this.$pinia),r=e[i];return"function"==typeof r?r.call(this,n):n[r]},n)),{})}const v=$;return t.acceptHMRUpdate=function(t,e){return n=>{const i=e.data.pinia||t._pinia;if(i){e.data.pinia=i;for(const o in n){const c=n[o];if("function"==typeof(r=c)&&"string"==typeof r.$id&&i._s.has(c.$id)){const n=c.$id;if(n!==t.$id)return console.warn(`The id of the store changed from "${t.$id}" to "${n}". Reloading.`),e.invalidate();const r=i._s.get(n);if(!r)return void console.log("skipping hmr because store doesn't exist yet");c(i,r)}}var r}}},t.createPinia=a,t.createTestingPinia=function({plugins:t=[],stubActions:n=!0,stubPatch:r=!1,fakeApp:o=!1,createSpy:c}={}){const s=a();if(t.forEach((t=>s.use(t))),!(c=c||void 0!==typeof jest&&jest.fn))throw new Error("You must configure the `createSpy` option.");const u=new Map;if(s.use((({store:t,options:e})=>{u.has(t.$id)||u.set(t.$id,{});const i=u.get(t.$id);Object.keys(e.actions||{}).forEach((e=>{i[e]=i[e]||(n?c():c(t[e])),t[e]=i[e]})),t.$patch=r?c():c(t.$patch)})),o){e.createApp({}).use(s)}return s._testing=!0,i(s),Object.assign({resetSpyCache(){u.clear()},get app(){return this._a}},s)},t.defineStore=function(t,o,c){let s,a;const u="function"==typeof o;function f(t,c){const f=e.getCurrentInstance();(t=t||f&&e.inject(r))&&i(t),(t=n)._s.has(s)||t._s.set(s,u?l(s,o,a,t):function(t,n,r,o){const{state:c,actions:s,getters:a}=n;function u(){r.state.value[t]=c?c():{}}const f=r.state.value[t];let p;return p=l(t,(function(){f||u();const n=f||e.toRefs(r.state.value[t]);return d(n,s,Object.keys(a||{}).reduce(((t,n)=>(t[n]=e.markRaw(e.computed((()=>(i(r),p&&a[n].call(p,p))))),t)),{}))}),n,r),p.$reset=u,p}(s,a,t));return t._s.get(s)}return"string"==typeof t?(s=t,a=u?c:o):(a=t,s=t.id),f.$id=s,f},t.mapActions=function(t,e){return Array.isArray(e)?e.reduce(((e,n)=>(e[n]=function(...e){return t(this.$pinia)[n](...e)},e)),{}):Object.keys(e).reduce(((n,i)=>(n[i]=function(...n){return t(this.$pinia)[e[i]](...n)},n)),{})},t.mapGetters=v,t.mapState=$,t.mapStores=function(...t){return t.reduce(((t,e)=>(t[e.$id+y]=function(){return e(this.$pinia)},t)),{})},t.mapWritableState=function(t,e){return Array.isArray(e)?e.reduce(((e,n)=>(e[n]={get(){return t(this.$pinia)[n]},set(e){return t(this.$pinia)[n]=e}},e)),{}):Object.keys(e).reduce(((n,i)=>(n[i]={get(){return t(this.$pinia)[e[i]]},set(n){return t(this.$pinia)[e[i]]=n}},n)),{})},t.setActivePinia=i,t.setMapStoreSuffix=function(t){y=t},Object.defineProperty(t,"__esModule",{value:!0}),t}({},Vue); |
{ | ||
"name": "pinia", | ||
"version": "2.0.0-beta.5", | ||
"version": "2.0.0-rc.0", | ||
"description": "Intuitive, type safe and flexible Store for Vue", | ||
@@ -29,3 +29,3 @@ "main": "dist/pinia.cjs.js", | ||
"build:dts": "api-extractor run --local --verbose", | ||
"size": "rollup -c size-checks/rollup.config.js && node scripts/check-size.js", | ||
"size": "rollup -c size-checks/rollup.config.js && node scripts/check-size.mjs", | ||
"release": "bash scripts/release.sh", | ||
@@ -39,2 +39,3 @@ "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1", | ||
"dev": "yarn run test:unit --watchAll", | ||
"playground": "yarn vite", | ||
"pretest": "yarn run lint", | ||
@@ -67,28 +68,38 @@ "test": "yarn run test:types && yarn run test:unit && yarn run build && yarn run build:dts && yarn test:dts" | ||
"devDependencies": { | ||
"@microsoft/api-extractor": "7.18.0", | ||
"@rollup/plugin-alias": "^3.1.2", | ||
"@rollup/plugin-commonjs": "^19.0.0", | ||
"@rollup/plugin-node-resolve": "^13.0.0", | ||
"@rollup/plugin-replace": "^2.4.2", | ||
"@microsoft/api-extractor": "7.18.4", | ||
"@rollup/plugin-alias": "^3.1.4", | ||
"@rollup/plugin-commonjs": "^19.0.1", | ||
"@rollup/plugin-node-resolve": "^13.0.4", | ||
"@rollup/plugin-replace": "^3.0.0", | ||
"@sucrase/jest-plugin": "^2.1.0", | ||
"@types/jest": "^26.0.24", | ||
"@types/node": "^16.0.1", | ||
"@vue/server-renderer": "^3.1.4", | ||
"@vue/test-utils": "^2.0.0-rc.9", | ||
"@vueuse/core": "^5.1.3", | ||
"brotli": "^1.3.2", | ||
"@types/lodash.kebabcase": "^4.1.6", | ||
"@types/node": "^16.4.3", | ||
"@vitejs/plugin-vue": "^1.2.4", | ||
"@vue/compiler-sfc": "^3.2.0-beta.1", | ||
"@vue/server-renderer": "^3.2.0-beta.1", | ||
"@vue/test-utils": "^2.0.0-rc.10", | ||
"@vueuse/core": "^5.2.0", | ||
"brotli-wasm": "^1.1.0", | ||
"codecov": "^3.8.2", | ||
"conventional-changelog-cli": "^2.1.1", | ||
"globby": "^12.0.0", | ||
"jest": "^26.6.3", | ||
"jest-mock-warn": "^1.1.0", | ||
"lint-staged": "^11.0.0", | ||
"lint-staged": "^11.1.1", | ||
"lodash.kebabcase": "^4.1.1", | ||
"mande": "^1.0.0", | ||
"pascalcase": "^1.0.0", | ||
"prettier": "^2.3.2", | ||
"rimraf": "^3.0.2", | ||
"rollup": "^2.52.8", | ||
"rollup": "^2.54.0", | ||
"rollup-plugin-terser": "^7.0.2", | ||
"rollup-plugin-typescript2": "^0.30.0", | ||
"swrv": "^1.0.0-beta.8", | ||
"typescript": "~4.3.5", | ||
"vite": "^2.4.1", | ||
"vitepress": "^0.15.6", | ||
"vue": "^3.1.4", | ||
"vue": "^3.2.0-beta.1", | ||
"vue-promised": "^2.1.0", | ||
"vue-router": "^4.0.10", | ||
"yorkie": "^2.0.0" | ||
@@ -100,3 +111,4 @@ }, | ||
"peerDependencies": { | ||
"typescript": "^4.3.5" | ||
"typescript": "^4.3.5", | ||
"vue": "^3.2.0 || ^3.2.0-beta.4" | ||
}, | ||
@@ -103,0 +115,0 @@ "peerDependenciesMeta": { |
@@ -133,6 +133,5 @@ <p align="center"> | ||
export const useMainStore = defineStore({ | ||
// name of the store | ||
// it is used in devtools and allows restoring state | ||
id: 'main', | ||
// main is the name of the store. It is unique across your application | ||
// and will appear in devtools | ||
export const useMainStore = defineStore('main', { | ||
// a function that returns a fresh state | ||
@@ -139,0 +138,0 @@ state: () => ({ |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
342949
8221
3
37
189
55