3 * (c) 2022 Eduardo San Martin Morote
6 var Pinia = (function (exports, vueDemi) {
10 * setActivePinia must be called to handle SSR at the top of functions like
\r
11 * `fetch`, `setup`, `serverPrefetch` and others
\r
15 * Sets or unsets the active pinia. Used in SSR and internally when calling
\r
16 * actions and getters
\r
18 * @param pinia - Pinia instance
\r
20 const setActivePinia = (pinia) => (activePinia = pinia);
\r
22 * Get the currently active pinia if there is any.
\r
24 const getActivePinia = () => (vueDemi.getCurrentInstance() && vueDemi.inject(piniaSymbol)) || activePinia;
\r
25 const piniaSymbol = (Symbol('pinia') );
27 function getDevtoolsGlobalHook() {
28 return getTarget().__VUE_DEVTOOLS_GLOBAL_HOOK__;
30 function getTarget() {
32 return (typeof navigator !== 'undefined' && typeof window !== 'undefined')
34 : typeof global !== 'undefined'
38 const isProxyAvailable = typeof Proxy === 'function';
40 const HOOK_SETUP = 'devtools-plugin:setup';
41 const HOOK_PLUGIN_SETTINGS_SET = 'plugin:settings:set';
45 function isPerformanceSupported() {
47 if (supported !== undefined) {
50 if (typeof window !== 'undefined' && window.performance) {
52 perf = window.performance;
54 else if (typeof global !== 'undefined' && ((_a = global.perf_hooks) === null || _a === void 0 ? void 0 : _a.performance)) {
56 perf = global.perf_hooks.performance;
64 return isPerformanceSupported() ? perf.now() : Date.now();
68 constructor(plugin, hook) {
70 this.targetQueue = [];
74 const defaultSettings = {};
75 if (plugin.settings) {
76 for (const id in plugin.settings) {
77 const item = plugin.settings[id];
78 defaultSettings[id] = item.defaultValue;
81 const localSettingsSaveId = `__vue-devtools-plugin-settings__${plugin.id}`;
82 let currentSettings = Object.assign({}, defaultSettings);
84 const raw = localStorage.getItem(localSettingsSaveId);
85 const data = JSON.parse(raw);
86 Object.assign(currentSettings, data);
93 return currentSettings;
97 localStorage.setItem(localSettingsSaveId, JSON.stringify(value));
102 currentSettings = value;
109 hook.on(HOOK_PLUGIN_SETTINGS_SET, (pluginId, value) => {
110 if (pluginId === this.plugin.id) {
111 this.fallbacks.setSettings(value);
115 this.proxiedOn = new Proxy({}, {
116 get: (_target, prop) => {
118 return this.target.on[prop];
121 return (...args) => {
130 this.proxiedTarget = new Proxy({}, {
131 get: (_target, prop) => {
133 return this.target[prop];
135 else if (prop === 'on') {
136 return this.proxiedOn;
138 else if (Object.keys(this.fallbacks).includes(prop)) {
139 return (...args) => {
140 this.targetQueue.push({
145 return this.fallbacks[prop](...args);
149 return (...args) => {
150 return new Promise(resolve => {
151 this.targetQueue.push({
162 async setRealTarget(target) {
163 this.target = target;
164 for (const item of this.onQueue) {
165 this.target.on[item.method](...item.args);
167 for (const item of this.targetQueue) {
168 item.resolve(await this.target[item.method](...item.args));
173 function setupDevtoolsPlugin(pluginDescriptor, setupFn) {
174 const descriptor = pluginDescriptor;
175 const target = getTarget();
176 const hook = getDevtoolsGlobalHook();
177 const enableProxy = isProxyAvailable && descriptor.enableEarlyProxy;
178 if (hook && (target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ || !enableProxy)) {
179 hook.emit(HOOK_SETUP, pluginDescriptor, setupFn);
182 const proxy = enableProxy ? new ApiProxy(descriptor, hook) : null;
183 const list = target.__VUE_DEVTOOLS_PLUGINS__ = target.__VUE_DEVTOOLS_PLUGINS__ || [];
185 pluginDescriptor: descriptor,
190 setupFn(proxy.proxiedTarget);
194 function isPlainObject(
\r
195 // eslint-disable-next-line @typescript-eslint/no-explicit-any
\r
198 typeof o === 'object' &&
\r
199 Object.prototype.toString.call(o) === '[object Object]' &&
\r
200 typeof o.toJSON !== 'function');
\r
202 // type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }
\r
203 // TODO: can we change these to numbers?
\r
205 * Possible types for SubscriptionCallback
\r
207 exports.MutationType = void 0;
\r
208 (function (MutationType) {
\r
210 * Direct mutation of the state:
\r
212 * - `store.name = 'new name'`
\r
213 * - `store.$state.name = 'new name'`
\r
214 * - `store.list.push('new item')`
\r
216 MutationType["direct"] = "direct";
\r
218 * Mutated the state with `$patch` and an object
\r
220 * - `store.$patch({ name: 'newName' })`
\r
222 MutationType["patchObject"] = "patch object";
\r
224 * Mutated the state with `$patch` and a function
\r
226 * - `store.$patch(state => state.name = 'newName')`
\r
228 MutationType["patchFunction"] = "patch function";
\r
229 // maybe reset? for $state = {} and $reset
\r
230 })(exports.MutationType || (exports.MutationType = {}));
232 const IS_CLIENT = typeof window !== 'undefined';
235 * FileSaver.js A saveAs() FileSaver implementation.
\r
237 * Originally by Eli Grey, adapted as an ESM module by Eduardo San Martin
\r
242 // The one and only way of getting global scope in all environments
\r
243 // https://stackoverflow.com/q/3277182/1008999
\r
244 const _global = /*#__PURE__*/ (() => typeof window === 'object' && window.window === window
\r
246 : typeof self === 'object' && self.self === self
\r
248 : typeof global === 'object' && global.global === global
\r
250 : typeof globalThis === 'object'
\r
252 : { HTMLElement: null })();
\r
253 function bom(blob, { autoBom = false } = {}) {
\r
254 // prepend BOM for UTF-8 XML and text/* types (including HTML)
\r
255 // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
\r
257 /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
\r
258 return new Blob([String.fromCharCode(0xfeff), blob], { type: blob.type });
\r
262 function download(url, name, opts) {
\r
263 const xhr = new XMLHttpRequest();
\r
264 xhr.open('GET', url);
\r
265 xhr.responseType = 'blob';
\r
266 xhr.onload = function () {
\r
267 saveAs(xhr.response, name, opts);
\r
269 xhr.onerror = function () {
\r
270 console.error('could not download file');
\r
274 function corsEnabled(url) {
\r
275 const xhr = new XMLHttpRequest();
\r
276 // use sync to avoid popup blocker
\r
277 xhr.open('HEAD', url, false);
\r
282 return xhr.status >= 200 && xhr.status <= 299;
\r
284 // `a.click()` doesn't work for all browsers (#465)
\r
285 function click(node) {
\r
287 node.dispatchEvent(new MouseEvent('click'));
\r
290 const evt = document.createEvent('MouseEvents');
\r
291 evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
\r
292 node.dispatchEvent(evt);
\r
295 const _navigator =
\r
296 typeof navigator === 'object' ? navigator : { userAgent: '' };
\r
297 // Detect WebView inside a native macOS app by ruling out all browsers
\r
298 // We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
\r
299 // https://www.whatismybrowser.com/guides/the-latest-user-agent/macos
\r
300 const isMacOSWebView = /*#__PURE__*/ (() => /Macintosh/.test(_navigator.userAgent) &&
\r
301 /AppleWebKit/.test(_navigator.userAgent) &&
\r
302 !/Safari/.test(_navigator.userAgent))();
\r
303 const saveAs = !IS_CLIENT
\r
304 ? () => { } // noop
\r
305 : // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView or mini program
\r
306 typeof HTMLAnchorElement !== 'undefined' &&
\r
307 'download' in HTMLAnchorElement.prototype &&
\r
310 : // Use msSaveOrOpenBlob as a second approach
\r
311 'msSaveOrOpenBlob' in _navigator
\r
313 : // Fallback to using FileReader and a popup
\r
315 function downloadSaveAs(blob, name = 'download', opts) {
\r
316 const a = document.createElement('a');
\r
318 a.rel = 'noopener'; // tabnabbing
\r
319 // TODO: detect chrome extensions & packaged apps
\r
320 // a.target = '_blank'
\r
321 if (typeof blob === 'string') {
\r
322 // Support regular links
\r
324 if (a.origin !== location.origin) {
\r
325 if (corsEnabled(a.href)) {
\r
326 download(blob, name, opts);
\r
329 a.target = '_blank';
\r
339 a.href = URL.createObjectURL(blob);
\r
340 setTimeout(function () {
\r
341 URL.revokeObjectURL(a.href);
\r
343 setTimeout(function () {
\r
348 function msSaveAs(blob, name = 'download', opts) {
\r
349 if (typeof blob === 'string') {
\r
350 if (corsEnabled(blob)) {
\r
351 download(blob, name, opts);
\r
354 const a = document.createElement('a');
\r
356 a.target = '_blank';
\r
357 setTimeout(function () {
\r
363 // @ts-ignore: works on windows
\r
364 navigator.msSaveOrOpenBlob(bom(blob, opts), name);
\r
367 function fileSaverSaveAs(blob, name, opts, popup) {
\r
368 // Open a popup immediately do go around popup blocker
\r
369 // Mostly only available on user interaction and the fileReader is async so...
\r
370 popup = popup || open('', '_blank');
\r
372 popup.document.title = popup.document.body.innerText = 'downloading...';
\r
374 if (typeof blob === 'string')
\r
375 return download(blob, name, opts);
\r
376 const force = blob.type === 'application/octet-stream';
\r
377 const isSafari = /constructor/i.test(String(_global.HTMLElement)) || 'safari' in _global;
\r
378 const isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
\r
379 if ((isChromeIOS || (force && isSafari) || isMacOSWebView) &&
\r
380 typeof FileReader !== 'undefined') {
\r
381 // Safari doesn't allow downloading of blob URLs
\r
382 const reader = new FileReader();
\r
383 reader.onloadend = function () {
\r
384 let url = reader.result;
\r
385 if (typeof url !== 'string') {
\r
387 throw new Error('Wrong reader.result type');
\r
391 : url.replace(/^data:[^;]*;/, 'data:attachment/file;');
\r
393 popup.location.href = url;
\r
396 location.assign(url);
\r
398 popup = null; // reverse-tabnabbing #460
\r
400 reader.readAsDataURL(blob);
\r
403 const url = URL.createObjectURL(blob);
\r
405 popup.location.assign(url);
\r
407 location.href = url;
\r
408 popup = null; // reverse-tabnabbing #460
\r
409 setTimeout(function () {
\r
410 URL.revokeObjectURL(url);
\r
416 * Shows a toast or console.log
\r
418 * @param message - message to log
\r
419 * @param type - different color of the tooltip
\r
421 function toastMessage(message, type) {
\r
422 const piniaMessage = '🍍 ' + message;
\r
423 if (typeof __VUE_DEVTOOLS_TOAST__ === 'function') {
\r
424 __VUE_DEVTOOLS_TOAST__(piniaMessage, type);
\r
426 else if (type === 'error') {
\r
427 console.error(piniaMessage);
\r
429 else if (type === 'warn') {
\r
430 console.warn(piniaMessage);
\r
433 console.log(piniaMessage);
\r
436 function isPinia(o) {
\r
437 return '_a' in o && 'install' in o;
\r
440 function checkClipboardAccess() {
\r
441 if (!('clipboard' in navigator)) {
\r
442 toastMessage(`Your browser doesn't support the Clipboard API`, 'error');
\r
446 function checkNotFocusedError(error) {
\r
447 if (error instanceof Error &&
\r
448 error.message.toLowerCase().includes('document is not focused')) {
\r
449 toastMessage('You need to activate the "Emulate a focused page" setting in the "Rendering" panel of devtools.', 'warn');
\r
454 async function actionGlobalCopyState(pinia) {
\r
455 if (checkClipboardAccess())
\r
458 await navigator.clipboard.writeText(JSON.stringify(pinia.state.value));
\r
459 toastMessage('Global state copied to clipboard.');
\r
462 if (checkNotFocusedError(error))
\r
464 toastMessage(`Failed to serialize the state. Check the console for more details.`, 'error');
\r
465 console.error(error);
\r
468 async function actionGlobalPasteState(pinia) {
\r
469 if (checkClipboardAccess())
\r
472 pinia.state.value = JSON.parse(await navigator.clipboard.readText());
\r
473 toastMessage('Global state pasted from clipboard.');
\r
476 if (checkNotFocusedError(error))
\r
478 toastMessage(`Failed to deserialize the state from clipboard. Check the console for more details.`, 'error');
\r
479 console.error(error);
\r
482 async function actionGlobalSaveState(pinia) {
\r
484 saveAs(new Blob([JSON.stringify(pinia.state.value)], {
\r
485 type: 'text/plain;charset=utf-8',
\r
486 }), 'pinia-state.json');
\r
489 toastMessage(`Failed to export the state as JSON. Check the console for more details.`, 'error');
\r
490 console.error(error);
\r
494 function getFileOpener() {
\r
496 fileInput = document.createElement('input');
\r
497 fileInput.type = 'file';
\r
498 fileInput.accept = '.json';
\r
500 function openFile() {
\r
501 return new Promise((resolve, reject) => {
\r
502 fileInput.onchange = async () => {
\r
503 const files = fileInput.files;
\r
505 return resolve(null);
\r
506 const file = files.item(0);
\r
508 return resolve(null);
\r
509 return resolve({ text: await file.text(), file });
\r
511 // @ts-ignore: TODO: changed from 4.3 to 4.4
\r
512 fileInput.oncancel = () => resolve(null);
\r
513 fileInput.onerror = reject;
\r
519 async function actionGlobalOpenStateFile(pinia) {
\r
521 const open = await getFileOpener();
\r
522 const result = await open();
\r
525 const { text, file } = result;
\r
526 pinia.state.value = JSON.parse(text);
\r
527 toastMessage(`Global state imported from "${file.name}".`);
\r
530 toastMessage(`Failed to export the state as JSON. Check the console for more details.`, 'error');
\r
531 console.error(error);
\r
535 function formatDisplay(display) {
\r
542 const PINIA_ROOT_LABEL = '🍍 Pinia (root)';
\r
543 const PINIA_ROOT_ID = '_root';
\r
544 function formatStoreForInspectorTree(store) {
\r
545 return isPinia(store)
\r
548 label: PINIA_ROOT_LABEL,
\r
555 function formatStoreForInspectorState(store) {
\r
556 if (isPinia(store)) {
\r
557 const storeNames = Array.from(store._s.keys());
\r
558 const storeMap = store._s;
\r
560 state: storeNames.map((storeId) => ({
\r
563 value: store.state.value[storeId],
\r
565 getters: storeNames
\r
566 .filter((id) => storeMap.get(id)._getters)
\r
568 const store = storeMap.get(id);
\r
572 value: store._getters.reduce((getters, key) => {
\r
573 getters[key] = store[key];
\r
582 state: Object.keys(store.$state).map((key) => ({
\r
585 value: store.$state[key],
\r
588 // avoid adding empty getters
\r
589 if (store._getters && store._getters.length) {
\r
590 state.getters = store._getters.map((getterName) => ({
\r
593 value: store[getterName],
\r
596 if (store._customProperties.size) {
\r
597 state.customProperties = Array.from(store._customProperties).map((key) => ({
\r
605 function formatEventData(events) {
\r
608 if (Array.isArray(events)) {
\r
609 // TODO: handle add and delete for arrays and objects
\r
610 return events.reduce((data, event) => {
\r
611 data.keys.push(event.key);
\r
612 data.operations.push(event.type);
\r
613 data.oldValue[event.key] = event.oldValue;
\r
614 data.newValue[event.key] = event.newValue;
\r
625 operation: formatDisplay(events.type),
\r
626 key: formatDisplay(events.key),
\r
627 oldValue: events.oldValue,
\r
628 newValue: events.newValue,
\r
632 function formatMutationType(type) {
\r
634 case exports.MutationType.direct:
\r
636 case exports.MutationType.patchFunction:
\r
638 case exports.MutationType.patchObject:
\r
645 // timeline can be paused when directly changing the state
\r
646 let isTimelineActive = true;
\r
647 const componentStateTypes = [];
\r
648 const MUTATIONS_LAYER_ID = 'pinia:mutations';
\r
649 const INSPECTOR_ID = 'pinia';
\r
651 * Gets the displayed name of a store in devtools
\r
653 * @param id - id of the store
\r
654 * @returns a formatted string
\r
656 const getStoreType = (id) => '🍍 ' + id;
\r
658 * Add the pinia plugin without any store. Allows displaying a Pinia plugin tab
\r
659 * as soon as it is added to the application.
\r
661 * @param app - Vue application
\r
662 * @param pinia - pinia instance
\r
664 function registerPiniaDevtools(app, pinia) {
\r
665 setupDevtoolsPlugin({
\r
666 id: 'dev.esm.pinia',
\r
668 logo: 'https://pinia.vuejs.org/logo.svg',
\r
669 packageName: 'pinia',
\r
670 homepage: 'https://pinia.vuejs.org',
\r
671 componentStateTypes,
\r
674 if (typeof api.now !== 'function') {
\r
675 toastMessage('You seem to be using an outdated version of Vue Devtools. Are you still using the Beta release instead of the stable one? You can find the links at https://devtools.vuejs.org/guide/installation.html.');
\r
677 api.addTimelineLayer({
\r
678 id: MUTATIONS_LAYER_ID,
\r
686 treeFilterPlaceholder: 'Search stores',
\r
689 icon: 'content_copy',
\r
691 actionGlobalCopyState(pinia);
\r
693 tooltip: 'Serialize and copy the state',
\r
696 icon: 'content_paste',
\r
697 action: async () => {
\r
698 await actionGlobalPasteState(pinia);
\r
699 api.sendInspectorTree(INSPECTOR_ID);
\r
700 api.sendInspectorState(INSPECTOR_ID);
\r
702 tooltip: 'Replace the state with the content of your clipboard',
\r
707 actionGlobalSaveState(pinia);
\r
709 tooltip: 'Save the state as a JSON file',
\r
712 icon: 'folder_open',
\r
713 action: async () => {
\r
714 await actionGlobalOpenStateFile(pinia);
\r
715 api.sendInspectorTree(INSPECTOR_ID);
\r
716 api.sendInspectorState(INSPECTOR_ID);
\r
718 tooltip: 'Import the state from a JSON file',
\r
722 api.on.inspectComponent((payload, ctx) => {
\r
723 const proxy = (payload.componentInstance &&
\r
724 payload.componentInstance.proxy);
\r
725 if (proxy && proxy._pStores) {
\r
726 const piniaStores = payload.componentInstance.proxy._pStores;
\r
727 Object.values(piniaStores).forEach((store) => {
\r
728 payload.instanceData.state.push({
\r
729 type: getStoreType(store.$id),
\r
732 value: store._isOptionsAPI
\r
735 value: store.$state,
\r
739 tooltip: 'Reset the state of this store',
\r
740 action: () => store.$reset(),
\r
747 if (store._getters && store._getters.length) {
\r
748 payload.instanceData.state.push({
\r
749 type: getStoreType(store.$id),
\r
752 value: store._getters.reduce((getters, key) => {
\r
754 getters[key] = store[key];
\r
757 // @ts-expect-error: we just want to show it in devtools
\r
758 getters[key] = error;
\r
767 api.on.getInspectorTree((payload) => {
\r
768 if (payload.app === app && payload.inspectorId === INSPECTOR_ID) {
\r
769 let stores = [pinia];
\r
770 stores = stores.concat(Array.from(pinia._s.values()));
\r
771 payload.rootNodes = (payload.filter
\r
772 ? stores.filter((store) => '$id' in store
\r
775 .includes(payload.filter.toLowerCase())
\r
776 : PINIA_ROOT_LABEL.toLowerCase().includes(payload.filter.toLowerCase()))
\r
777 : stores).map(formatStoreForInspectorTree);
\r
780 api.on.getInspectorState((payload) => {
\r
781 if (payload.app === app && payload.inspectorId === INSPECTOR_ID) {
\r
782 const inspectedStore = payload.nodeId === PINIA_ROOT_ID
\r
784 : pinia._s.get(payload.nodeId);
\r
785 if (!inspectedStore) {
\r
786 // this could be the selected store restored for a different project
\r
787 // so it's better not to say anything here
\r
790 if (inspectedStore) {
\r
791 payload.state = formatStoreForInspectorState(inspectedStore);
\r
795 api.on.editInspectorState((payload, ctx) => {
\r
796 if (payload.app === app && payload.inspectorId === INSPECTOR_ID) {
\r
797 const inspectedStore = payload.nodeId === PINIA_ROOT_ID
\r
799 : pinia._s.get(payload.nodeId);
\r
800 if (!inspectedStore) {
\r
801 return toastMessage(`store "${payload.nodeId}" not found`, 'error');
\r
803 const { path } = payload;
\r
804 if (!isPinia(inspectedStore)) {
\r
805 // access only the state
\r
806 if (path.length !== 1 ||
\r
807 !inspectedStore._customProperties.has(path[0]) ||
\r
808 path[0] in inspectedStore.$state) {
\r
809 path.unshift('$state');
\r
813 // Root access, we can omit the `.value` because the devtools API does it for us
\r
814 path.unshift('state');
\r
816 isTimelineActive = false;
\r
817 payload.set(inspectedStore, path, payload.state.value);
\r
818 isTimelineActive = true;
\r
821 api.on.editComponentState((payload) => {
\r
822 if (payload.type.startsWith('🍍')) {
\r
823 const storeId = payload.type.replace(/^🍍\s*/, '');
\r
824 const store = pinia._s.get(storeId);
\r
826 return toastMessage(`store "${storeId}" not found`, 'error');
\r
828 const { path } = payload;
\r
829 if (path[0] !== 'state') {
\r
830 return toastMessage(`Invalid path for store "${storeId}":\n${path}\nOnly state can be modified.`);
\r
832 // rewrite the first entry to be able to directly set the state as
\r
833 // well as any other path
\r
834 path[0] = '$state';
\r
835 isTimelineActive = false;
\r
836 payload.set(store, path, payload.state.value);
\r
837 isTimelineActive = true;
\r
842 function addStoreToDevtools(app, store) {
\r
843 if (!componentStateTypes.includes(getStoreType(store.$id))) {
\r
844 componentStateTypes.push(getStoreType(store.$id));
\r
846 setupDevtoolsPlugin({
\r
847 id: 'dev.esm.pinia',
\r
849 logo: 'https://pinia.vuejs.org/logo.svg',
\r
850 packageName: 'pinia',
\r
851 homepage: 'https://pinia.vuejs.org',
\r
852 componentStateTypes,
\r
856 label: 'Notify about new/deleted stores',
\r
858 defaultValue: true,
\r
861 // label: 'Use emojis in messages ⚡️',
\r
862 // type: 'boolean',
\r
863 // defaultValue: true,
\r
867 // gracefully handle errors
\r
868 const now = typeof api.now === 'function' ? api.now.bind(api) : Date.now;
\r
869 store.$onAction(({ after, onError, name, args }) => {
\r
870 const groupId = runningActionId++;
\r
871 api.addTimelineEvent({
\r
872 layerId: MUTATIONS_LAYER_ID,
\r
875 title: '🛫 ' + name,
\r
878 store: formatDisplay(store.$id),
\r
879 action: formatDisplay(name),
\r
885 after((result) => {
\r
886 activeAction = undefined;
\r
887 api.addTimelineEvent({
\r
888 layerId: MUTATIONS_LAYER_ID,
\r
891 title: '🛬 ' + name,
\r
894 store: formatDisplay(store.$id),
\r
895 action: formatDisplay(name),
\r
903 onError((error) => {
\r
904 activeAction = undefined;
\r
905 api.addTimelineEvent({
\r
906 layerId: MUTATIONS_LAYER_ID,
\r
910 title: '💥 ' + name,
\r
913 store: formatDisplay(store.$id),
\r
914 action: formatDisplay(name),
\r
923 store._customProperties.forEach((name) => {
\r
924 vueDemi.watch(() => vueDemi.unref(store[name]), (newValue, oldValue) => {
\r
925 api.notifyComponentUpdate();
\r
926 api.sendInspectorState(INSPECTOR_ID);
\r
927 if (isTimelineActive) {
\r
928 api.addTimelineEvent({
\r
929 layerId: MUTATIONS_LAYER_ID,
\r
938 groupId: activeAction,
\r
942 }, { deep: true });
\r
944 store.$subscribe(({ events, type }, state) => {
\r
945 api.notifyComponentUpdate();
\r
946 api.sendInspectorState(INSPECTOR_ID);
\r
947 if (!isTimelineActive)
\r
949 // rootStore.state[store.id] = state
\r
950 const eventData = {
\r
952 title: formatMutationType(type),
\r
954 store: formatDisplay(store.$id),
\r
955 ...formatEventData(events),
\r
957 groupId: activeAction,
\r
959 // reset for the next mutation
\r
960 activeAction = undefined;
\r
961 if (type === exports.MutationType.patchFunction) {
\r
962 eventData.subtitle = '⤵️';
\r
964 else if (type === exports.MutationType.patchObject) {
\r
965 eventData.subtitle = '🧩';
\r
967 else if (events && !Array.isArray(events)) {
\r
968 eventData.subtitle = events.type;
\r
971 eventData.data['rawEvent(s)'] = {
\r
973 display: 'DebuggerEvent',
\r
975 tooltip: 'raw DebuggerEvent[]',
\r
980 api.addTimelineEvent({
\r
981 layerId: MUTATIONS_LAYER_ID,
\r
984 }, { detached: true, flush: 'sync' });
\r
985 const hotUpdate = store._hotUpdate;
\r
986 store._hotUpdate = vueDemi.markRaw((newStore) => {
\r
987 hotUpdate(newStore);
\r
988 api.addTimelineEvent({
\r
989 layerId: MUTATIONS_LAYER_ID,
\r
992 title: '🔥 ' + store.$id,
\r
993 subtitle: 'HMR update',
\r
995 store: formatDisplay(store.$id),
\r
996 info: formatDisplay(`HMR update`),
\r
1000 // update the devtools too
\r
1001 api.notifyComponentUpdate();
\r
1002 api.sendInspectorTree(INSPECTOR_ID);
\r
1003 api.sendInspectorState(INSPECTOR_ID);
\r
1005 const { $dispose } = store;
\r
1006 store.$dispose = () => {
\r
1008 api.notifyComponentUpdate();
\r
1009 api.sendInspectorTree(INSPECTOR_ID);
\r
1010 api.sendInspectorState(INSPECTOR_ID);
\r
1011 api.getSettings().logStoreChanges &&
\r
1012 toastMessage(`Disposed "${store.$id}" store 🗑`);
\r
1014 // trigger an update so it can display new registered stores
\r
1015 api.notifyComponentUpdate();
\r
1016 api.sendInspectorTree(INSPECTOR_ID);
\r
1017 api.sendInspectorState(INSPECTOR_ID);
\r
1018 api.getSettings().logStoreChanges &&
\r
1019 toastMessage(`"${store.$id}" store installed 🆕`);
\r
1022 let runningActionId = 0;
\r
1025 * Patches a store to enable action grouping in devtools by wrapping the store with a Proxy that is passed as the
\r
1026 * context of all actions, allowing us to set `runningAction` on each access and effectively associating any state
\r
1027 * mutation to the action.
\r
1029 * @param store - store to patch
\r
1030 * @param actionNames - list of actionst to patch
\r
1032 function patchActionForGrouping(store, actionNames) {
\r
1033 // original actions of the store as they are given by pinia. We are going to override them
\r
1034 const actions = actionNames.reduce((storeActions, actionName) => {
\r
1035 // use toRaw to avoid tracking #541
\r
1036 storeActions[actionName] = vueDemi.toRaw(store)[actionName];
\r
1037 return storeActions;
\r
1039 for (const actionName in actions) {
\r
1040 store[actionName] = function () {
\r
1041 // setActivePinia(store._p)
\r
1042 // the running action id is incremented in a before action hook
\r
1043 const _actionId = runningActionId;
\r
1044 const trackedStore = new Proxy(store, {
\r
1046 activeAction = _actionId;
\r
1047 return Reflect.get(...args);
\r
1050 activeAction = _actionId;
\r
1051 return Reflect.set(...args);
\r
1054 return actions[actionName].apply(trackedStore, arguments);
\r
1059 * pinia.use(devtoolsPlugin)
\r
1061 function devtoolsPlugin({ app, store, options }) {
\r
1063 if (store.$id.startsWith('__hot:')) {
\r
1066 // detect option api vs setup api
\r
1067 if (options.state) {
\r
1068 store._isOptionsAPI = true;
\r
1070 // only wrap actions in option-defined stores as this technique relies on
\r
1071 // wrapping the context of the action with a proxy
\r
1072 if (typeof options.state === 'function') {
\r
1073 patchActionForGrouping(
\r
1074 // @ts-expect-error: can cast the store...
\r
1075 store, Object.keys(options.actions));
\r
1076 const originalHotUpdate = store._hotUpdate;
\r
1077 // Upgrade the HMR to also update the new actions
\r
1078 vueDemi.toRaw(store)._hotUpdate = function (newStore) {
\r
1079 originalHotUpdate.apply(this, arguments);
\r
1080 patchActionForGrouping(store, Object.keys(newStore._hmrPayload.actions));
\r
1083 addStoreToDevtools(app,
\r
1084 // FIXME: is there a way to allow the assignment from Store<Id, S, G, A> to StoreGeneric?
\r
1089 * Creates a Pinia instance to be used by the application
\r
1091 function createPinia() {
\r
1092 const scope = vueDemi.effectScope(true);
\r
1093 // NOTE: here we could check the window object for a state and directly set it
\r
1094 // if there is anything like it with Vue 3 SSR
\r
1095 const state = scope.run(() => vueDemi.ref({}));
\r
1097 // plugins added before calling app.use(pinia)
\r
1098 let toBeInstalled = [];
\r
1099 const pinia = vueDemi.markRaw({
\r
1101 // this allows calling useStore() outside of a component setup after
\r
1102 // installing pinia's plugin
\r
1103 setActivePinia(pinia);
\r
1104 if (!vueDemi.isVue2) {
\r
1106 app.provide(piniaSymbol, pinia);
\r
1107 app.config.globalProperties.$pinia = pinia;
\r
1108 /* istanbul ignore else */
\r
1110 registerPiniaDevtools(app, pinia);
\r
1112 toBeInstalled.forEach((plugin) => _p.push(plugin));
\r
1113 toBeInstalled = [];
\r
1117 if (!this._a && !vueDemi.isVue2) {
\r
1118 toBeInstalled.push(plugin);
\r
1126 // it's actually undefined here
\r
1127 // @ts-expect-error
\r
1133 // pinia devtools rely on dev only features so they cannot be forced unless
\r
1134 // the dev build of Vue is used
\r
1135 // We also don't need devtools in test mode
\r
1136 if (IS_CLIENT && !false) {
\r
1137 pinia.use(devtoolsPlugin);
\r
1143 * Checks if a function is a `StoreDefinition`.
\r
1145 * @param fn - object to test
\r
1146 * @returns true if `fn` is a StoreDefinition
\r
1148 const isUseStore = (fn) => {
\r
1149 return typeof fn === 'function' && typeof fn.$id === 'string';
\r
1152 * Mutates in place `newState` with `oldState` to _hot update_ it. It will
\r
1153 * remove any key not existing in `newState` and recursively merge plain
\r
1156 * @param newState - new state object to be patched
\r
1157 * @param oldState - old state that should be used to patch newState
\r
1158 * @returns - newState
\r
1160 function patchObject(newState, oldState) {
\r
1161 // no need to go through symbols because they cannot be serialized anyway
\r
1162 for (const key in oldState) {
\r
1163 const subPatch = oldState[key];
\r
1164 // skip the whole sub tree
\r
1165 if (!(key in newState)) {
\r
1168 const targetValue = newState[key];
\r
1169 if (isPlainObject(targetValue) &&
\r
1170 isPlainObject(subPatch) &&
\r
1171 !vueDemi.isRef(subPatch) &&
\r
1172 !vueDemi.isReactive(subPatch)) {
\r
1173 newState[key] = patchObject(targetValue, subPatch);
\r
1176 // objects are either a bit more complex (e.g. refs) or primitives, so we
\r
1177 // just set the whole thing
\r
1178 if (vueDemi.isVue2) {
\r
1179 vueDemi.set(newState, key, subPatch);
\r
1182 newState[key] = subPatch;
\r
1189 * Creates an _accept_ function to pass to `import.meta.hot` in Vite applications.
\r
1193 * const useUser = defineStore(...)
\r
1194 * if (import.meta.hot) {
\r
1195 * import.meta.hot.accept(acceptHMRUpdate(useUser, import.meta.hot))
\r
1199 * @param initialUseStore - return of the defineStore to hot update
\r
1200 * @param hot - `import.meta.hot`
\r
1202 function acceptHMRUpdate(initialUseStore, hot) {
\r
1203 return (newModule) => {
\r
1204 const pinia = hot.data.pinia || initialUseStore._pinia;
\r
1206 // this store is still not used
\r
1209 // preserve the pinia instance across loads
\r
1210 hot.data.pinia = pinia;
\r
1211 // console.log('got data', newStore)
\r
1212 for (const exportName in newModule) {
\r
1213 const useStore = newModule[exportName];
\r
1214 // console.log('checking for', exportName)
\r
1215 if (isUseStore(useStore) && pinia._s.has(useStore.$id)) {
\r
1216 // console.log('Accepting update for', useStore.$id)
\r
1217 const id = useStore.$id;
\r
1218 if (id !== initialUseStore.$id) {
\r
1219 console.warn(`The id of the store changed from "${initialUseStore.$id}" to "${id}". Reloading.`);
\r
1220 // return import.meta.hot.invalidate()
\r
1221 return hot.invalidate();
\r
1223 const existingStore = pinia._s.get(id);
\r
1224 if (!existingStore) {
\r
1225 console.log(`[Pinia]: skipping hmr because store doesn't exist yet`);
\r
1228 useStore(pinia, existingStore);
\r
1234 const noop = () => { };
\r
1235 function addSubscription(subscriptions, callback, detached, onCleanup = noop) {
\r
1236 subscriptions.push(callback);
\r
1237 const removeSubscription = () => {
\r
1238 const idx = subscriptions.indexOf(callback);
\r
1240 subscriptions.splice(idx, 1);
\r
1244 if (!detached && vueDemi.getCurrentInstance()) {
\r
1245 vueDemi.onUnmounted(removeSubscription);
\r
1247 return removeSubscription;
\r
1249 function triggerSubscriptions(subscriptions, ...args) {
\r
1250 subscriptions.slice().forEach((callback) => {
\r
1251 callback(...args);
\r
1255 function mergeReactiveObjects(target, patchToApply) {
\r
1256 // no need to go through symbols because they cannot be serialized anyway
\r
1257 for (const key in patchToApply) {
\r
1258 if (!patchToApply.hasOwnProperty(key))
\r
1260 const subPatch = patchToApply[key];
\r
1261 const targetValue = target[key];
\r
1262 if (isPlainObject(targetValue) &&
\r
1263 isPlainObject(subPatch) &&
\r
1264 target.hasOwnProperty(key) &&
\r
1265 !vueDemi.isRef(subPatch) &&
\r
1266 !vueDemi.isReactive(subPatch)) {
\r
1267 target[key] = mergeReactiveObjects(targetValue, subPatch);
\r
1270 // @ts-expect-error: subPatch is a valid value
\r
1271 target[key] = subPatch;
\r
1276 const skipHydrateSymbol = Symbol('pinia:skipHydration')
\r
1278 const skipHydrateMap = /*#__PURE__*/ new WeakMap();
\r
1280 * Tells Pinia to skip the hydration process of a given object. This is useful in setup stores (only) when you return a
\r
1281 * stateful object in the store but it isn't really state. e.g. returning a router instance in a setup store.
\r
1283 * @param obj - target object
\r
1286 function skipHydrate(obj) {
\r
1287 return vueDemi.isVue2
\r
1288 ? // in @vue/composition-api, the refs are sealed so defineProperty doesn't work...
\r
1289 /* istanbul ignore next */ skipHydrateMap.set(obj, 1) && obj
\r
1290 : Object.defineProperty(obj, skipHydrateSymbol, {});
\r
1292 function shouldHydrate(obj) {
\r
1293 return vueDemi.isVue2
\r
1294 ? /* istanbul ignore next */ !skipHydrateMap.has(obj)
\r
1295 : !isPlainObject(obj) || !obj.hasOwnProperty(skipHydrateSymbol);
\r
1297 const { assign } = Object;
\r
1298 function isComputed(o) {
\r
1299 return !!(vueDemi.isRef(o) && o.effect);
\r
1301 function createOptionsStore(id, options, pinia, hot) {
\r
1302 const { state, actions, getters } = options;
\r
1303 const initialState = pinia.state.value[id];
\r
1305 function setup() {
\r
1306 if (!initialState && (!hot)) {
\r
1307 /* istanbul ignore if */
\r
1308 if (vueDemi.isVue2) {
\r
1309 vueDemi.set(pinia.state.value, id, state ? state() : {});
\r
1312 pinia.state.value[id] = state ? state() : {};
\r
1315 // avoid creating a state in pinia.state.value
\r
1316 const localState = hot
\r
1317 ? // use ref() to unwrap refs inside state TODO: check if this is still necessary
\r
1318 vueDemi.toRefs(vueDemi.ref(state ? state() : {}).value)
\r
1319 : vueDemi.toRefs(pinia.state.value[id]);
\r
1320 return assign(localState, actions, Object.keys(getters || {}).reduce((computedGetters, name) => {
\r
1321 if (name in localState) {
\r
1322 console.warn(`[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`);
\r
1324 computedGetters[name] = vueDemi.markRaw(vueDemi.computed(() => {
\r
1325 setActivePinia(pinia);
\r
1326 // it was created just before
\r
1327 const store = pinia._s.get(id);
\r
1328 // allow cross using stores
\r
1329 /* istanbul ignore next */
\r
1330 if (vueDemi.isVue2 && !store._r)
\r
1332 // @ts-expect-error
\r
1333 // return getters![name].call(context, context)
\r
1334 // TODO: avoid reading the getter while assigning with a global variable
\r
1335 return getters[name].call(store, store);
\r
1337 return computedGetters;
\r
1340 store = createSetupStore(id, setup, options, pinia, hot, true);
\r
1341 store.$reset = function $reset() {
\r
1342 const newState = state ? state() : {};
\r
1343 // we use a patch to group all changes into one single subscription
\r
1344 this.$patch(($state) => {
\r
1345 assign($state, newState);
\r
1350 function createSetupStore($id, setup, options = {}, pinia, hot, isOptionsStore) {
\r
1352 const optionsForPlugin = assign({ actions: {} }, options);
\r
1353 /* istanbul ignore if */
\r
1354 // @ts-expect-error: active is an internal property
\r
1355 if (!pinia._e.active) {
\r
1356 throw new Error('Pinia destroyed');
\r
1358 // watcher options for $subscribe
\r
1359 const $subscribeOptions = {
\r
1363 /* istanbul ignore else */
\r
1364 if (!vueDemi.isVue2) {
\r
1365 $subscribeOptions.onTrigger = (event) => {
\r
1366 /* istanbul ignore else */
\r
1367 if (isListening) {
\r
1368 debuggerEvents = event;
\r
1369 // avoid triggering this while the store is being built and the state is being set in pinia
\r
1371 else if (isListening == false && !store._hotUpdating) {
\r
1372 // let patch send all the events together later
\r
1373 /* istanbul ignore else */
\r
1374 if (Array.isArray(debuggerEvents)) {
\r
1375 debuggerEvents.push(event);
\r
1378 console.error('🍍 debuggerEvents should be an array. This is most likely an internal Pinia bug.');
\r
1384 let isListening; // set to true at the end
\r
1385 let isSyncListening; // set to true at the end
\r
1386 let subscriptions = vueDemi.markRaw([]);
\r
1387 let actionSubscriptions = vueDemi.markRaw([]);
\r
1388 let debuggerEvents;
\r
1389 const initialState = pinia.state.value[$id];
\r
1390 // avoid setting the state for option stores if it is set
\r
1392 if (!isOptionsStore && !initialState && (!hot)) {
\r
1393 /* istanbul ignore if */
\r
1394 if (vueDemi.isVue2) {
\r
1395 vueDemi.set(pinia.state.value, $id, {});
\r
1398 pinia.state.value[$id] = {};
\r
1401 const hotState = vueDemi.ref({});
\r
1402 // avoid triggering too many listeners
\r
1403 // https://github.com/vuejs/pinia/issues/1129
\r
1404 let activeListener;
\r
1405 function $patch(partialStateOrMutator) {
\r
1406 let subscriptionMutation;
\r
1407 isListening = isSyncListening = false;
\r
1408 // reset the debugger events since patches are sync
\r
1409 /* istanbul ignore else */
\r
1411 debuggerEvents = [];
\r
1413 if (typeof partialStateOrMutator === 'function') {
\r
1414 partialStateOrMutator(pinia.state.value[$id]);
\r
1415 subscriptionMutation = {
\r
1416 type: exports.MutationType.patchFunction,
\r
1418 events: debuggerEvents,
\r
1422 mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator);
\r
1423 subscriptionMutation = {
\r
1424 type: exports.MutationType.patchObject,
\r
1425 payload: partialStateOrMutator,
\r
1427 events: debuggerEvents,
\r
1430 const myListenerId = (activeListener = Symbol());
\r
1431 vueDemi.nextTick().then(() => {
\r
1432 if (activeListener === myListenerId) {
\r
1433 isListening = true;
\r
1436 isSyncListening = true;
\r
1437 // because we paused the watcher, we need to manually call the subscriptions
\r
1438 triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id]);
\r
1440 /* istanbul ignore next */
\r
1441 const $reset = () => {
\r
1442 throw new Error(`🍍: Store "${$id}" is built using the setup syntax and does not implement $reset().`);
\r
1445 function $dispose() {
\r
1447 subscriptions = [];
\r
1448 actionSubscriptions = [];
\r
1449 pinia._s.delete($id);
\r
1452 * Wraps an action to handle subscriptions.
\r
1454 * @param name - name of the action
\r
1455 * @param action - action to wrap
\r
1456 * @returns a wrapped action to handle subscriptions
\r
1458 function wrapAction(name, action) {
\r
1459 return function () {
\r
1460 setActivePinia(pinia);
\r
1461 const args = Array.from(arguments);
\r
1462 const afterCallbackList = [];
\r
1463 const onErrorCallbackList = [];
\r
1464 function after(callback) {
\r
1465 afterCallbackList.push(callback);
\r
1467 function onError(callback) {
\r
1468 onErrorCallbackList.push(callback);
\r
1470 // @ts-expect-error
\r
1471 triggerSubscriptions(actionSubscriptions, {
\r
1480 ret = action.apply(this && this.$id === $id ? this : store, args);
\r
1481 // handle sync errors
\r
1484 triggerSubscriptions(onErrorCallbackList, error);
\r
1487 if (ret instanceof Promise) {
\r
1489 .then((value) => {
\r
1490 triggerSubscriptions(afterCallbackList, value);
\r
1493 .catch((error) => {
\r
1494 triggerSubscriptions(onErrorCallbackList, error);
\r
1495 return Promise.reject(error);
\r
1498 // allow the afterCallback to override the return value
\r
1499 triggerSubscriptions(afterCallbackList, ret);
\r
1503 const _hmrPayload = /*#__PURE__*/ vueDemi.markRaw({
\r
1509 const partialStore = {
\r
1513 $onAction: addSubscription.bind(null, actionSubscriptions),
\r
1516 $subscribe(callback, options = {}) {
\r
1517 const removeSubscription = addSubscription(subscriptions, callback, options.detached, () => stopWatcher());
\r
1518 const stopWatcher = scope.run(() => vueDemi.watch(() => pinia.state.value[$id], (state) => {
\r
1519 if (options.flush === 'sync' ? isSyncListening : isListening) {
\r
1522 type: exports.MutationType.direct,
\r
1523 events: debuggerEvents,
\r
1526 }, assign({}, $subscribeOptions, options)));
\r
1527 return removeSubscription;
\r
1531 /* istanbul ignore if */
\r
1532 if (vueDemi.isVue2) {
\r
1533 // start as non ready
\r
1534 partialStore._r = false;
\r
1536 const store = vueDemi.reactive(assign(IS_CLIENT
\r
1537 ? // devtools custom properties
\r
1539 _customProperties: vueDemi.markRaw(new Set()),
\r
1542 : {}, partialStore
\r
1543 // must be added later
\r
1546 // store the partial store now so the setup of stores can instantiate each other before they are finished without
\r
1547 // creating infinite loops.
\r
1548 pinia._s.set($id, store);
\r
1549 // TODO: idea create skipSerialize that marks properties as non serializable and they are skipped
\r
1550 const setupStore = pinia._e.run(() => {
\r
1551 scope = vueDemi.effectScope();
\r
1552 return scope.run(() => setup());
\r
1554 // overwrite existing actions to support $onAction
\r
1555 for (const key in setupStore) {
\r
1556 const prop = setupStore[key];
\r
1557 if ((vueDemi.isRef(prop) && !isComputed(prop)) || vueDemi.isReactive(prop)) {
\r
1558 // mark it as a piece of state to be serialized
\r
1560 vueDemi.set(hotState.value, key, vueDemi.toRef(setupStore, key));
\r
1561 // createOptionStore directly sets the state in pinia.state.value so we
\r
1562 // can just skip that
\r
1564 else if (!isOptionsStore) {
\r
1565 // in setup stores we must hydrate the state and sync pinia state tree with the refs the user just created
\r
1566 if (initialState && shouldHydrate(prop)) {
\r
1567 if (vueDemi.isRef(prop)) {
\r
1568 prop.value = initialState[key];
\r
1571 // probably a reactive object, lets recursively assign
\r
1572 mergeReactiveObjects(prop, initialState[key]);
\r
1575 // transfer the ref to the pinia state to keep everything in sync
\r
1576 /* istanbul ignore if */
\r
1577 if (vueDemi.isVue2) {
\r
1578 vueDemi.set(pinia.state.value[$id], key, prop);
\r
1581 pinia.state.value[$id][key] = prop;
\r
1584 /* istanbul ignore else */
\r
1586 _hmrPayload.state.push(key);
\r
1590 else if (typeof prop === 'function') {
\r
1591 // @ts-expect-error: we are overriding the function we avoid wrapping if
\r
1592 const actionValue = hot ? prop : wrapAction(key, prop);
\r
1593 // this a hot module replacement store because the hotUpdate method needs
\r
1594 // to do it with the right context
\r
1595 /* istanbul ignore if */
\r
1596 if (vueDemi.isVue2) {
\r
1597 vueDemi.set(setupStore, key, actionValue);
\r
1600 // @ts-expect-error
\r
1601 setupStore[key] = actionValue;
\r
1603 /* istanbul ignore else */
\r
1605 _hmrPayload.actions[key] = prop;
\r
1607 // list actions so they can be used in plugins
\r
1608 // @ts-expect-error
\r
1609 optionsForPlugin.actions[key] = prop;
\r
1612 // add getters for devtools
\r
1613 if (isComputed(prop)) {
\r
1614 _hmrPayload.getters[key] = isOptionsStore
\r
1615 ? // @ts-expect-error
\r
1616 options.getters[key]
\r
1620 // @ts-expect-error: it should be on the store
\r
1621 setupStore._getters || (setupStore._getters = vueDemi.markRaw([]));
\r
1622 getters.push(key);
\r
1627 // add the state, getters, and action properties
\r
1628 /* istanbul ignore if */
\r
1629 if (vueDemi.isVue2) {
\r
1630 Object.keys(setupStore).forEach((key) => {
\r
1631 vueDemi.set(store, key,
\r
1632 // @ts-expect-error: valid key indexing
\r
1637 assign(store, setupStore);
\r
1638 // allows retrieving reactive objects with `storeToRefs()`. Must be called after assigning to the reactive object.
\r
1639 // Make `storeToRefs()` work with `reactive()` #799
\r
1640 assign(vueDemi.toRaw(store), setupStore);
\r
1642 // use this instead of a computed with setter to be able to create it anywhere
\r
1643 // without linking the computed lifespan to wherever the store is first
\r
1645 Object.defineProperty(store, '$state', {
\r
1646 get: () => (hot ? hotState.value : pinia.state.value[$id]),
\r
1648 /* istanbul ignore if */
\r
1650 throw new Error('cannot set hotState');
\r
1652 $patch(($state) => {
\r
1653 assign($state, state);
\r
1657 // add the hotUpdate before plugins to allow them to override it
\r
1658 /* istanbul ignore else */
\r
1660 store._hotUpdate = vueDemi.markRaw((newStore) => {
\r
1661 store._hotUpdating = true;
\r
1662 newStore._hmrPayload.state.forEach((stateKey) => {
\r
1663 if (stateKey in store.$state) {
\r
1664 const newStateTarget = newStore.$state[stateKey];
\r
1665 const oldStateSource = store.$state[stateKey];
\r
1666 if (typeof newStateTarget === 'object' &&
\r
1667 isPlainObject(newStateTarget) &&
\r
1668 isPlainObject(oldStateSource)) {
\r
1669 patchObject(newStateTarget, oldStateSource);
\r
1672 // transfer the ref
\r
1673 newStore.$state[stateKey] = oldStateSource;
\r
1676 // patch direct access properties to allow store.stateProperty to work as
\r
1677 // store.$state.stateProperty
\r
1678 vueDemi.set(store, stateKey, vueDemi.toRef(newStore.$state, stateKey));
\r
1680 // remove deleted state properties
\r
1681 Object.keys(store.$state).forEach((stateKey) => {
\r
1682 if (!(stateKey in newStore.$state)) {
\r
1683 vueDemi.del(store, stateKey);
\r
1686 // avoid devtools logging this as a mutation
\r
1687 isListening = false;
\r
1688 isSyncListening = false;
\r
1689 pinia.state.value[$id] = vueDemi.toRef(newStore._hmrPayload, 'hotState');
\r
1690 isSyncListening = true;
\r
1691 vueDemi.nextTick().then(() => {
\r
1692 isListening = true;
\r
1694 for (const actionName in newStore._hmrPayload.actions) {
\r
1695 const action = newStore[actionName];
\r
1696 vueDemi.set(store, actionName, wrapAction(actionName, action));
\r
1698 // TODO: does this work in both setup and option store?
\r
1699 for (const getterName in newStore._hmrPayload.getters) {
\r
1700 const getter = newStore._hmrPayload.getters[getterName];
\r
1701 const getterValue = isOptionsStore
\r
1702 ? // special handling of options api
\r
1703 vueDemi.computed(() => {
\r
1704 setActivePinia(pinia);
\r
1705 return getter.call(store, store);
\r
1708 vueDemi.set(store, getterName, getterValue);
\r
1710 // remove deleted getters
\r
1711 Object.keys(store._hmrPayload.getters).forEach((key) => {
\r
1712 if (!(key in newStore._hmrPayload.getters)) {
\r
1713 vueDemi.del(store, key);
\r
1716 // remove old actions
\r
1717 Object.keys(store._hmrPayload.actions).forEach((key) => {
\r
1718 if (!(key in newStore._hmrPayload.actions)) {
\r
1719 vueDemi.del(store, key);
\r
1722 // update the values used in devtools and to allow deleting new properties later on
\r
1723 store._hmrPayload = newStore._hmrPayload;
\r
1724 store._getters = newStore._getters;
\r
1725 store._hotUpdating = false;
\r
1727 const nonEnumerable = {
\r
1729 configurable: true,
\r
1730 // avoid warning on devtools trying to display this property
\r
1731 enumerable: false,
\r
1734 ['_p', '_hmrPayload', '_getters', '_customProperties'].forEach((p) => {
\r
1735 Object.defineProperty(store, p, {
\r
1742 /* istanbul ignore if */
\r
1743 if (vueDemi.isVue2) {
\r
1744 // mark the store as ready before plugins
\r
1747 // apply all plugins
\r
1748 pinia._p.forEach((extender) => {
\r
1749 /* istanbul ignore else */
\r
1751 const extensions = scope.run(() => extender({
\r
1755 options: optionsForPlugin,
\r
1757 Object.keys(extensions || {}).forEach((key) => store._customProperties.add(key));
\r
1758 assign(store, extensions);
\r
1761 assign(store, scope.run(() => extender({
\r
1765 options: optionsForPlugin,
\r
1769 if (store.$state &&
\r
1770 typeof store.$state === 'object' &&
\r
1771 typeof store.$state.constructor === 'function' &&
\r
1772 !store.$state.constructor.toString().includes('[native code]')) {
\r
1773 console.warn(`[🍍]: The "state" must be a plain object. It cannot be\n` +
\r
1774 `\tstate: () => new MyClass()\n` +
\r
1775 `Found in store "${store.$id}".`);
\r
1777 // only apply hydrate to option stores with an initial state in pinia
\r
1778 if (initialState &&
\r
1780 options.hydrate) {
\r
1781 options.hydrate(store.$state, initialState);
\r
1783 isListening = true;
\r
1784 isSyncListening = true;
\r
1787 function defineStore(
\r
1788 // TODO: add proper types from above
\r
1789 idOrOptions, setup, setupOptions) {
\r
1792 const isSetupStore = typeof setup === 'function';
\r
1793 if (typeof idOrOptions === 'string') {
\r
1795 // the option store setup will contain the actual options in this case
\r
1796 options = isSetupStore ? setupOptions : setup;
\r
1799 options = idOrOptions;
\r
1800 id = idOrOptions.id;
\r
1802 function useStore(pinia, hot) {
\r
1803 const currentInstance = vueDemi.getCurrentInstance();
\r
1805 // in test mode, ignore the argument provided as we can always retrieve a
\r
1806 // pinia instance with getActivePinia()
\r
1808 (currentInstance && vueDemi.inject(piniaSymbol));
\r
1810 setActivePinia(pinia);
\r
1811 if (!activePinia) {
\r
1812 throw new Error(`[🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?\n` +
\r
1813 `\tconst pinia = createPinia()\n` +
\r
1814 `\tapp.use(pinia)\n` +
\r
1815 `This will fail in production.`);
\r
1817 pinia = activePinia;
\r
1818 if (!pinia._s.has(id)) {
\r
1819 // creating the store registers it in `pinia._s`
\r
1820 if (isSetupStore) {
\r
1821 createSetupStore(id, setup, options, pinia);
\r
1824 createOptionsStore(id, options, pinia);
\r
1826 /* istanbul ignore else */
\r
1828 // @ts-expect-error: not the right inferred type
\r
1829 useStore._pinia = pinia;
\r
1832 const store = pinia._s.get(id);
\r
1834 const hotId = '__hot:' + id;
\r
1835 const newStore = isSetupStore
\r
1836 ? createSetupStore(hotId, setup, options, pinia, true)
\r
1837 : createOptionsStore(hotId, assign({}, options), pinia, true);
\r
1838 hot._hotUpdate(newStore);
\r
1839 // cleanup the state properties and the store from the cache
\r
1840 delete pinia.state.value[hotId];
\r
1841 pinia._s.delete(hotId);
\r
1843 // save stores in instances to access them devtools
\r
1845 currentInstance &&
\r
1846 currentInstance.proxy &&
\r
1847 // avoid adding stores that are just built for hot module replacement
\r
1849 const vm = currentInstance.proxy;
\r
1850 const cache = '_pStores' in vm ? vm._pStores : (vm._pStores = {});
\r
1851 cache[id] = store;
\r
1853 // StoreGeneric cannot be casted towards Store
\r
1856 useStore.$id = id;
\r
1860 let mapStoreSuffix = 'Store';
\r
1862 * Changes the suffix added by `mapStores()`. Can be set to an empty string.
\r
1863 * Defaults to `"Store"`. Make sure to extend the MapStoresCustomization
\r
1864 * interface if you are using TypeScript.
\r
1866 * @param suffix - new suffix
\r
1868 function setMapStoreSuffix(suffix // could be 'Store' but that would be annoying for JS
\r
1870 mapStoreSuffix = suffix;
\r
1873 * Allows using stores without the composition API (`setup()`) by generating an
\r
1874 * object to be spread in the `computed` field of a component. It accepts a list
\r
1875 * of store definitions.
\r
1879 * export default {
\r
1881 * // other computed properties
\r
1882 * ...mapStores(useUserStore, useCartStore)
\r
1886 * this.userStore // store with id "user"
\r
1887 * this.cartStore // store with id "cart"
\r
1892 * @param stores - list of stores to map to an object
\r
1894 function mapStores(...stores) {
\r
1895 if (Array.isArray(stores[0])) {
\r
1896 console.warn(`[🍍]: Directly pass all stores to "mapStores()" without putting them in an array:\n` +
\r
1898 `\tmapStores([useAuthStore, useCartStore])\n` +
\r
1900 `\tmapStores(useAuthStore, useCartStore)\n` +
\r
1901 `This will fail in production if not fixed.`);
\r
1902 stores = stores[0];
\r
1904 return stores.reduce((reduced, useStore) => {
\r
1905 // @ts-expect-error: $id is added by defineStore
\r
1906 reduced[useStore.$id + mapStoreSuffix] = function () {
\r
1907 return useStore(this.$pinia);
\r
1913 * Allows using state and getters from one store without using the composition
\r
1914 * API (`setup()`) by generating an object to be spread in the `computed` field
\r
1917 * @param useStore - store to map from
\r
1918 * @param keysOrMapper - array or object
\r
1920 function mapState(useStore, keysOrMapper) {
\r
1921 return Array.isArray(keysOrMapper)
\r
1922 ? keysOrMapper.reduce((reduced, key) => {
\r
1923 reduced[key] = function () {
\r
1924 return useStore(this.$pinia)[key];
\r
1928 : Object.keys(keysOrMapper).reduce((reduced, key) => {
\r
1929 // @ts-expect-error
\r
1930 reduced[key] = function () {
\r
1931 const store = useStore(this.$pinia);
\r
1932 const storeKey = keysOrMapper[key];
\r
1933 // for some reason TS is unable to infer the type of storeKey to be a
\r
1935 return typeof storeKey === 'function'
\r
1936 ? storeKey.call(this, store)
\r
1937 : store[storeKey];
\r
1943 * Alias for `mapState()`. You should use `mapState()` instead.
\r
1944 * @deprecated use `mapState()` instead.
\r
1946 const mapGetters = mapState;
\r
1948 * Allows directly using actions from your store without using the composition
\r
1949 * API (`setup()`) by generating an object to be spread in the `methods` field
\r
1952 * @param useStore - store to map from
\r
1953 * @param keysOrMapper - array or object
\r
1955 function mapActions(useStore, keysOrMapper) {
\r
1956 return Array.isArray(keysOrMapper)
\r
1957 ? keysOrMapper.reduce((reduced, key) => {
\r
1958 // @ts-expect-error
\r
1959 reduced[key] = function (...args) {
\r
1960 return useStore(this.$pinia)[key](...args);
\r
1964 : Object.keys(keysOrMapper).reduce((reduced, key) => {
\r
1965 // @ts-expect-error
\r
1966 reduced[key] = function (...args) {
\r
1967 return useStore(this.$pinia)[keysOrMapper[key]](...args);
\r
1973 * Allows using state and getters from one store without using the composition
\r
1974 * API (`setup()`) by generating an object to be spread in the `computed` field
\r
1977 * @param useStore - store to map from
\r
1978 * @param keysOrMapper - array or object
\r
1980 function mapWritableState(useStore, keysOrMapper) {
\r
1981 return Array.isArray(keysOrMapper)
\r
1982 ? keysOrMapper.reduce((reduced, key) => {
\r
1986 return useStore(this.$pinia)[key];
\r
1989 // it's easier to type it here as any
\r
1990 return (useStore(this.$pinia)[key] = value);
\r
1995 : Object.keys(keysOrMapper).reduce((reduced, key) => {
\r
1999 return useStore(this.$pinia)[keysOrMapper[key]];
\r
2002 // it's easier to type it here as any
\r
2003 return (useStore(this.$pinia)[keysOrMapper[key]] = value);
\r
2011 * Creates an object of references with all the state, getters, and plugin-added
\r
2012 * state properties of the store. Similar to `toRefs()` but specifically
\r
2013 * designed for Pinia stores so methods and non reactive properties are
\r
2014 * completely ignored.
\r
2016 * @param store - store to extract the refs from
\r
2018 function storeToRefs(store) {
\r
2019 // See https://github.com/vuejs/pinia/issues/852
\r
2020 // It's easier to just use toRefs() even if it includes more stuff
\r
2021 if (vueDemi.isVue2) {
\r
2022 // @ts-expect-error: toRefs include methods and others
\r
2023 return vueDemi.toRefs(store);
\r
2026 store = vueDemi.toRaw(store);
\r
2028 for (const key in store) {
\r
2029 const value = store[key];
\r
2030 if (vueDemi.isRef(value) || vueDemi.isReactive(value)) {
\r
2031 // @ts-expect-error: the key is state or getter
\r
2034 vueDemi.toRef(store, key);
\r
2042 * Vue 2 Plugin that must be installed for pinia to work. Note **you don't need
\r
2043 * this plugin if you are using Nuxt.js**. Use the `buildModule` instead:
\r
2044 * https://pinia.vuejs.org/ssr/nuxt.html.
\r
2048 * import Vue from 'vue'
\r
2049 * import { PiniaVuePlugin, createPinia } from 'pinia'
\r
2051 * Vue.use(PiniaVuePlugin)
\r
2052 * const pinia = createPinia()
\r
2061 * @param _Vue - `Vue` imported from 'vue'.
\r
2063 const PiniaVuePlugin = function (_Vue) {
\r
2065 // app.config.globalProperties.$pinia = pinia
\r
2068 const options = this.$options;
\r
2069 if (options.pinia) {
\r
2070 const pinia = options.pinia;
\r
2071 // HACK: taken from provide(): https://github.com/vuejs/composition-api/blob/main/src/apis/inject.ts#L31
\r
2072 /* istanbul ignore else */
\r
2073 if (!this._provided) {
\r
2074 const provideCache = {};
\r
2075 Object.defineProperty(this, '_provided', {
\r
2076 get: () => provideCache,
\r
2077 set: (v) => Object.assign(provideCache, v),
\r
2080 this._provided[piniaSymbol] = pinia;
\r
2081 // propagate the pinia instance in an SSR friendly way
\r
2082 // avoid adding it to nuxt twice
\r
2083 /* istanbul ignore else */
\r
2084 if (!this.$pinia) {
\r
2085 this.$pinia = pinia;
\r
2089 // this allows calling useStore() outside of a component setup after
\r
2090 // installing pinia's plugin
\r
2091 setActivePinia(pinia);
\r
2093 registerPiniaDevtools(pinia._a, pinia);
\r
2097 else if (!this.$pinia && options.parent && options.parent.$pinia) {
\r
2098 this.$pinia = options.parent.$pinia;
\r
2102 delete this._pStores;
\r
2107 exports.PiniaVuePlugin = PiniaVuePlugin;
2108 exports.acceptHMRUpdate = acceptHMRUpdate;
2109 exports.createPinia = createPinia;
2110 exports.defineStore = defineStore;
2111 exports.getActivePinia = getActivePinia;
2112 exports.mapActions = mapActions;
2113 exports.mapGetters = mapGetters;
2114 exports.mapState = mapState;
2115 exports.mapStores = mapStores;
2116 exports.mapWritableState = mapWritableState;
2117 exports.setActivePinia = setActivePinia;
2118 exports.setMapStoreSuffix = setMapStoreSuffix;
2119 exports.skipHydrate = skipHydrate;
2120 exports.storeToRefs = storeToRefs;
2122 Object.defineProperty(exports, '__esModule', { value: true });