UNPKG

73.6 kBJavaScriptView Raw
1/*!
2 * pinia v2.0.27
3 * (c) 2022 Eduardo San Martin Morote
4 * @license MIT
5 */
6import { getCurrentInstance, inject, toRaw, watch, unref, markRaw, effectScope, ref, isVue2, isRef, isReactive, set, getCurrentScope, onScopeDispose, reactive, toRef, del, nextTick, computed, toRefs } from 'vue-demi';
7import { setupDevtoolsPlugin } from '@vue/devtools-api';
8
9/**
10 * setActivePinia must be called to handle SSR at the top of functions like
11 * `fetch`, `setup`, `serverPrefetch` and others
12 */
13let activePinia;
14/**
15 * Sets or unsets the active pinia. Used in SSR and internally when calling
16 * actions and getters
17 *
18 * @param pinia - Pinia instance
19 */
20const setActivePinia = (pinia) => (activePinia = pinia);
21/**
22 * Get the currently active pinia if there is any.
23 */
24const getActivePinia = () => (getCurrentInstance() && inject(piniaSymbol)) || activePinia;
25const piniaSymbol = (Symbol('pinia') );
26
27function isPlainObject(
28// eslint-disable-next-line @typescript-eslint/no-explicit-any
29o) {
30 return (o &&
31 typeof o === 'object' &&
32 Object.prototype.toString.call(o) === '[object Object]' &&
33 typeof o.toJSON !== 'function');
34}
35// type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }
36// TODO: can we change these to numbers?
37/**
38 * Possible types for SubscriptionCallback
39 */
40var MutationType;
41(function (MutationType) {
42 /**
43 * Direct mutation of the state:
44 *
45 * - `store.name = 'new name'`
46 * - `store.$state.name = 'new name'`
47 * - `store.list.push('new item')`
48 */
49 MutationType["direct"] = "direct";
50 /**
51 * Mutated the state with `$patch` and an object
52 *
53 * - `store.$patch({ name: 'newName' })`
54 */
55 MutationType["patchObject"] = "patch object";
56 /**
57 * Mutated the state with `$patch` and a function
58 *
59 * - `store.$patch(state => state.name = 'newName')`
60 */
61 MutationType["patchFunction"] = "patch function";
62 // maybe reset? for $state = {} and $reset
63})(MutationType || (MutationType = {}));
64
65const IS_CLIENT = typeof window !== 'undefined';
66/**
67 * Should we add the devtools plugins.
68 * - only if dev mode or forced through the prod devtools flag
69 * - not in test
70 * - only if window exists (could change in the future)
71 */
72const USE_DEVTOOLS = IS_CLIENT;
73
74/*
75 * FileSaver.js A saveAs() FileSaver implementation.
76 *
77 * Originally by Eli Grey, adapted as an ESM module by Eduardo San Martin
78 * Morote.
79 *
80 * License : MIT
81 */
82// The one and only way of getting global scope in all environments
83// https://stackoverflow.com/q/3277182/1008999
84const _global = /*#__PURE__*/ (() => typeof window === 'object' && window.window === window
85 ? window
86 : typeof self === 'object' && self.self === self
87 ? self
88 : typeof global === 'object' && global.global === global
89 ? global
90 : typeof globalThis === 'object'
91 ? globalThis
92 : { HTMLElement: null })();
93function bom(blob, { autoBom = false } = {}) {
94 // prepend BOM for UTF-8 XML and text/* types (including HTML)
95 // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
96 if (autoBom &&
97 /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
98 return new Blob([String.fromCharCode(0xfeff), blob], { type: blob.type });
99 }
100 return blob;
101}
102function download(url, name, opts) {
103 const xhr = new XMLHttpRequest();
104 xhr.open('GET', url);
105 xhr.responseType = 'blob';
106 xhr.onload = function () {
107 saveAs(xhr.response, name, opts);
108 };
109 xhr.onerror = function () {
110 console.error('could not download file');
111 };
112 xhr.send();
113}
114function corsEnabled(url) {
115 const xhr = new XMLHttpRequest();
116 // use sync to avoid popup blocker
117 xhr.open('HEAD', url, false);
118 try {
119 xhr.send();
120 }
121 catch (e) { }
122 return xhr.status >= 200 && xhr.status <= 299;
123}
124// `a.click()` doesn't work for all browsers (#465)
125function click(node) {
126 try {
127 node.dispatchEvent(new MouseEvent('click'));
128 }
129 catch (e) {
130 const evt = document.createEvent('MouseEvents');
131 evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
132 node.dispatchEvent(evt);
133 }
134}
135const _navigator =
136 typeof navigator === 'object' ? navigator : { userAgent: '' };
137// Detect WebView inside a native macOS app by ruling out all browsers
138// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
139// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos
140const isMacOSWebView = /*#__PURE__*/ (() => /Macintosh/.test(_navigator.userAgent) &&
141 /AppleWebKit/.test(_navigator.userAgent) &&
142 !/Safari/.test(_navigator.userAgent))();
143const saveAs = !IS_CLIENT
144 ? () => { } // noop
145 : // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView or mini program
146 typeof HTMLAnchorElement !== 'undefined' &&
147 'download' in HTMLAnchorElement.prototype &&
148 !isMacOSWebView
149 ? downloadSaveAs
150 : // Use msSaveOrOpenBlob as a second approach
151 'msSaveOrOpenBlob' in _navigator
152 ? msSaveAs
153 : // Fallback to using FileReader and a popup
154 fileSaverSaveAs;
155function downloadSaveAs(blob, name = 'download', opts) {
156 const a = document.createElement('a');
157 a.download = name;
158 a.rel = 'noopener'; // tabnabbing
159 // TODO: detect chrome extensions & packaged apps
160 // a.target = '_blank'
161 if (typeof blob === 'string') {
162 // Support regular links
163 a.href = blob;
164 if (a.origin !== location.origin) {
165 if (corsEnabled(a.href)) {
166 download(blob, name, opts);
167 }
168 else {
169 a.target = '_blank';
170 click(a);
171 }
172 }
173 else {
174 click(a);
175 }
176 }
177 else {
178 // Support blobs
179 a.href = URL.createObjectURL(blob);
180 setTimeout(function () {
181 URL.revokeObjectURL(a.href);
182 }, 4e4); // 40s
183 setTimeout(function () {
184 click(a);
185 }, 0);
186 }
187}
188function msSaveAs(blob, name = 'download', opts) {
189 if (typeof blob === 'string') {
190 if (corsEnabled(blob)) {
191 download(blob, name, opts);
192 }
193 else {
194 const a = document.createElement('a');
195 a.href = blob;
196 a.target = '_blank';
197 setTimeout(function () {
198 click(a);
199 });
200 }
201 }
202 else {
203 // @ts-ignore: works on windows
204 navigator.msSaveOrOpenBlob(bom(blob, opts), name);
205 }
206}
207function fileSaverSaveAs(blob, name, opts, popup) {
208 // Open a popup immediately do go around popup blocker
209 // Mostly only available on user interaction and the fileReader is async so...
210 popup = popup || open('', '_blank');
211 if (popup) {
212 popup.document.title = popup.document.body.innerText = 'downloading...';
213 }
214 if (typeof blob === 'string')
215 return download(blob, name, opts);
216 const force = blob.type === 'application/octet-stream';
217 const isSafari = /constructor/i.test(String(_global.HTMLElement)) || 'safari' in _global;
218 const isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
219 if ((isChromeIOS || (force && isSafari) || isMacOSWebView) &&
220 typeof FileReader !== 'undefined') {
221 // Safari doesn't allow downloading of blob URLs
222 const reader = new FileReader();
223 reader.onloadend = function () {
224 let url = reader.result;
225 if (typeof url !== 'string') {
226 popup = null;
227 throw new Error('Wrong reader.result type');
228 }
229 url = isChromeIOS
230 ? url
231 : url.replace(/^data:[^;]*;/, 'data:attachment/file;');
232 if (popup) {
233 popup.location.href = url;
234 }
235 else {
236 location.assign(url);
237 }
238 popup = null; // reverse-tabnabbing #460
239 };
240 reader.readAsDataURL(blob);
241 }
242 else {
243 const url = URL.createObjectURL(blob);
244 if (popup)
245 popup.location.assign(url);
246 else
247 location.href = url;
248 popup = null; // reverse-tabnabbing #460
249 setTimeout(function () {
250 URL.revokeObjectURL(url);
251 }, 4e4); // 40s
252 }
253}
254
255/**
256 * Shows a toast or console.log
257 *
258 * @param message - message to log
259 * @param type - different color of the tooltip
260 */
261function toastMessage(message, type) {
262 const piniaMessage = '🍍 ' + message;
263 if (typeof __VUE_DEVTOOLS_TOAST__ === 'function') {
264 __VUE_DEVTOOLS_TOAST__(piniaMessage, type);
265 }
266 else if (type === 'error') {
267 console.error(piniaMessage);
268 }
269 else if (type === 'warn') {
270 console.warn(piniaMessage);
271 }
272 else {
273 console.log(piniaMessage);
274 }
275}
276function isPinia(o) {
277 return '_a' in o && 'install' in o;
278}
279
280function checkClipboardAccess() {
281 if (!('clipboard' in navigator)) {
282 toastMessage(`Your browser doesn't support the Clipboard API`, 'error');
283 return true;
284 }
285}
286function checkNotFocusedError(error) {
287 if (error instanceof Error &&
288 error.message.toLowerCase().includes('document is not focused')) {
289 toastMessage('You need to activate the "Emulate a focused page" setting in the "Rendering" panel of devtools.', 'warn');
290 return true;
291 }
292 return false;
293}
294async function actionGlobalCopyState(pinia) {
295 if (checkClipboardAccess())
296 return;
297 try {
298 await navigator.clipboard.writeText(JSON.stringify(pinia.state.value));
299 toastMessage('Global state copied to clipboard.');
300 }
301 catch (error) {
302 if (checkNotFocusedError(error))
303 return;
304 toastMessage(`Failed to serialize the state. Check the console for more details.`, 'error');
305 console.error(error);
306 }
307}
308async function actionGlobalPasteState(pinia) {
309 if (checkClipboardAccess())
310 return;
311 try {
312 pinia.state.value = JSON.parse(await navigator.clipboard.readText());
313 toastMessage('Global state pasted from clipboard.');
314 }
315 catch (error) {
316 if (checkNotFocusedError(error))
317 return;
318 toastMessage(`Failed to deserialize the state from clipboard. Check the console for more details.`, 'error');
319 console.error(error);
320 }
321}
322async function actionGlobalSaveState(pinia) {
323 try {
324 saveAs(new Blob([JSON.stringify(pinia.state.value)], {
325 type: 'text/plain;charset=utf-8',
326 }), 'pinia-state.json');
327 }
328 catch (error) {
329 toastMessage(`Failed to export the state as JSON. Check the console for more details.`, 'error');
330 console.error(error);
331 }
332}
333let fileInput;
334function getFileOpener() {
335 if (!fileInput) {
336 fileInput = document.createElement('input');
337 fileInput.type = 'file';
338 fileInput.accept = '.json';
339 }
340 function openFile() {
341 return new Promise((resolve, reject) => {
342 fileInput.onchange = async () => {
343 const files = fileInput.files;
344 if (!files)
345 return resolve(null);
346 const file = files.item(0);
347 if (!file)
348 return resolve(null);
349 return resolve({ text: await file.text(), file });
350 };
351 // @ts-ignore: TODO: changed from 4.3 to 4.4
352 fileInput.oncancel = () => resolve(null);
353 fileInput.onerror = reject;
354 fileInput.click();
355 });
356 }
357 return openFile;
358}
359async function actionGlobalOpenStateFile(pinia) {
360 try {
361 const open = await getFileOpener();
362 const result = await open();
363 if (!result)
364 return;
365 const { text, file } = result;
366 pinia.state.value = JSON.parse(text);
367 toastMessage(`Global state imported from "${file.name}".`);
368 }
369 catch (error) {
370 toastMessage(`Failed to export the state as JSON. Check the console for more details.`, 'error');
371 console.error(error);
372 }
373}
374
375function formatDisplay(display) {
376 return {
377 _custom: {
378 display,
379 },
380 };
381}
382const PINIA_ROOT_LABEL = '🍍 Pinia (root)';
383const PINIA_ROOT_ID = '_root';
384function formatStoreForInspectorTree(store) {
385 return isPinia(store)
386 ? {
387 id: PINIA_ROOT_ID,
388 label: PINIA_ROOT_LABEL,
389 }
390 : {
391 id: store.$id,
392 label: store.$id,
393 };
394}
395function formatStoreForInspectorState(store) {
396 if (isPinia(store)) {
397 const storeNames = Array.from(store._s.keys());
398 const storeMap = store._s;
399 const state = {
400 state: storeNames.map((storeId) => ({
401 editable: true,
402 key: storeId,
403 value: store.state.value[storeId],
404 })),
405 getters: storeNames
406 .filter((id) => storeMap.get(id)._getters)
407 .map((id) => {
408 const store = storeMap.get(id);
409 return {
410 editable: false,
411 key: id,
412 value: store._getters.reduce((getters, key) => {
413 getters[key] = store[key];
414 return getters;
415 }, {}),
416 };
417 }),
418 };
419 return state;
420 }
421 const state = {
422 state: Object.keys(store.$state).map((key) => ({
423 editable: true,
424 key,
425 value: store.$state[key],
426 })),
427 };
428 // avoid adding empty getters
429 if (store._getters && store._getters.length) {
430 state.getters = store._getters.map((getterName) => ({
431 editable: false,
432 key: getterName,
433 value: store[getterName],
434 }));
435 }
436 if (store._customProperties.size) {
437 state.customProperties = Array.from(store._customProperties).map((key) => ({
438 editable: true,
439 key,
440 value: store[key],
441 }));
442 }
443 return state;
444}
445function formatEventData(events) {
446 if (!events)
447 return {};
448 if (Array.isArray(events)) {
449 // TODO: handle add and delete for arrays and objects
450 return events.reduce((data, event) => {
451 data.keys.push(event.key);
452 data.operations.push(event.type);
453 data.oldValue[event.key] = event.oldValue;
454 data.newValue[event.key] = event.newValue;
455 return data;
456 }, {
457 oldValue: {},
458 keys: [],
459 operations: [],
460 newValue: {},
461 });
462 }
463 else {
464 return {
465 operation: formatDisplay(events.type),
466 key: formatDisplay(events.key),
467 oldValue: events.oldValue,
468 newValue: events.newValue,
469 };
470 }
471}
472function formatMutationType(type) {
473 switch (type) {
474 case MutationType.direct:
475 return 'mutation';
476 case MutationType.patchFunction:
477 return '$patch';
478 case MutationType.patchObject:
479 return '$patch';
480 default:
481 return 'unknown';
482 }
483}
484
485// timeline can be paused when directly changing the state
486let isTimelineActive = true;
487const componentStateTypes = [];
488const MUTATIONS_LAYER_ID = 'pinia:mutations';
489const INSPECTOR_ID = 'pinia';
490/**
491 * Gets the displayed name of a store in devtools
492 *
493 * @param id - id of the store
494 * @returns a formatted string
495 */
496const getStoreType = (id) => '🍍 ' + id;
497/**
498 * Add the pinia plugin without any store. Allows displaying a Pinia plugin tab
499 * as soon as it is added to the application.
500 *
501 * @param app - Vue application
502 * @param pinia - pinia instance
503 */
504function registerPiniaDevtools(app, pinia) {
505 setupDevtoolsPlugin({
506 id: 'dev.esm.pinia',
507 label: 'Pinia 🍍',
508 logo: 'https://pinia.vuejs.org/logo.svg',
509 packageName: 'pinia',
510 homepage: 'https://pinia.vuejs.org',
511 componentStateTypes,
512 app,
513 }, (api) => {
514 if (typeof api.now !== 'function') {
515 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.');
516 }
517 api.addTimelineLayer({
518 id: MUTATIONS_LAYER_ID,
519 label: `Pinia 🍍`,
520 color: 0xe5df88,
521 });
522 api.addInspector({
523 id: INSPECTOR_ID,
524 label: 'Pinia 🍍',
525 icon: 'storage',
526 treeFilterPlaceholder: 'Search stores',
527 actions: [
528 {
529 icon: 'content_copy',
530 action: () => {
531 actionGlobalCopyState(pinia);
532 },
533 tooltip: 'Serialize and copy the state',
534 },
535 {
536 icon: 'content_paste',
537 action: async () => {
538 await actionGlobalPasteState(pinia);
539 api.sendInspectorTree(INSPECTOR_ID);
540 api.sendInspectorState(INSPECTOR_ID);
541 },
542 tooltip: 'Replace the state with the content of your clipboard',
543 },
544 {
545 icon: 'save',
546 action: () => {
547 actionGlobalSaveState(pinia);
548 },
549 tooltip: 'Save the state as a JSON file',
550 },
551 {
552 icon: 'folder_open',
553 action: async () => {
554 await actionGlobalOpenStateFile(pinia);
555 api.sendInspectorTree(INSPECTOR_ID);
556 api.sendInspectorState(INSPECTOR_ID);
557 },
558 tooltip: 'Import the state from a JSON file',
559 },
560 ],
561 nodeActions: [
562 {
563 icon: 'restore',
564 tooltip: 'Reset the state (option store only)',
565 action: (nodeId) => {
566 const store = pinia._s.get(nodeId);
567 if (!store) {
568 toastMessage(`Cannot reset "${nodeId}" store because it wasn't found.`, 'warn');
569 }
570 else if (!store._isOptionsAPI) {
571 toastMessage(`Cannot reset "${nodeId}" store because it's a setup store.`, 'warn');
572 }
573 else {
574 store.$reset();
575 toastMessage(`Store "${nodeId}" reset.`);
576 }
577 },
578 },
579 ],
580 });
581 api.on.inspectComponent((payload, ctx) => {
582 const proxy = (payload.componentInstance &&
583 payload.componentInstance.proxy);
584 if (proxy && proxy._pStores) {
585 const piniaStores = payload.componentInstance.proxy._pStores;
586 Object.values(piniaStores).forEach((store) => {
587 payload.instanceData.state.push({
588 type: getStoreType(store.$id),
589 key: 'state',
590 editable: true,
591 value: store._isOptionsAPI
592 ? {
593 _custom: {
594 value: toRaw(store.$state),
595 actions: [
596 {
597 icon: 'restore',
598 tooltip: 'Reset the state of this store',
599 action: () => store.$reset(),
600 },
601 ],
602 },
603 }
604 : // NOTE: workaround to unwrap transferred refs
605 Object.keys(store.$state).reduce((state, key) => {
606 state[key] = store.$state[key];
607 return state;
608 }, {}),
609 });
610 if (store._getters && store._getters.length) {
611 payload.instanceData.state.push({
612 type: getStoreType(store.$id),
613 key: 'getters',
614 editable: false,
615 value: store._getters.reduce((getters, key) => {
616 try {
617 getters[key] = store[key];
618 }
619 catch (error) {
620 // @ts-expect-error: we just want to show it in devtools
621 getters[key] = error;
622 }
623 return getters;
624 }, {}),
625 });
626 }
627 });
628 }
629 });
630 api.on.getInspectorTree((payload) => {
631 if (payload.app === app && payload.inspectorId === INSPECTOR_ID) {
632 let stores = [pinia];
633 stores = stores.concat(Array.from(pinia._s.values()));
634 payload.rootNodes = (payload.filter
635 ? stores.filter((store) => '$id' in store
636 ? store.$id
637 .toLowerCase()
638 .includes(payload.filter.toLowerCase())
639 : PINIA_ROOT_LABEL.toLowerCase().includes(payload.filter.toLowerCase()))
640 : stores).map(formatStoreForInspectorTree);
641 }
642 });
643 api.on.getInspectorState((payload) => {
644 if (payload.app === app && payload.inspectorId === INSPECTOR_ID) {
645 const inspectedStore = payload.nodeId === PINIA_ROOT_ID
646 ? pinia
647 : pinia._s.get(payload.nodeId);
648 if (!inspectedStore) {
649 // this could be the selected store restored for a different project
650 // so it's better not to say anything here
651 return;
652 }
653 if (inspectedStore) {
654 payload.state = formatStoreForInspectorState(inspectedStore);
655 }
656 }
657 });
658 api.on.editInspectorState((payload, ctx) => {
659 if (payload.app === app && payload.inspectorId === INSPECTOR_ID) {
660 const inspectedStore = payload.nodeId === PINIA_ROOT_ID
661 ? pinia
662 : pinia._s.get(payload.nodeId);
663 if (!inspectedStore) {
664 return toastMessage(`store "${payload.nodeId}" not found`, 'error');
665 }
666 const { path } = payload;
667 if (!isPinia(inspectedStore)) {
668 // access only the state
669 if (path.length !== 1 ||
670 !inspectedStore._customProperties.has(path[0]) ||
671 path[0] in inspectedStore.$state) {
672 path.unshift('$state');
673 }
674 }
675 else {
676 // Root access, we can omit the `.value` because the devtools API does it for us
677 path.unshift('state');
678 }
679 isTimelineActive = false;
680 payload.set(inspectedStore, path, payload.state.value);
681 isTimelineActive = true;
682 }
683 });
684 api.on.editComponentState((payload) => {
685 if (payload.type.startsWith('🍍')) {
686 const storeId = payload.type.replace(/^🍍\s*/, '');
687 const store = pinia._s.get(storeId);
688 if (!store) {
689 return toastMessage(`store "${storeId}" not found`, 'error');
690 }
691 const { path } = payload;
692 if (path[0] !== 'state') {
693 return toastMessage(`Invalid path for store "${storeId}":\n${path}\nOnly state can be modified.`);
694 }
695 // rewrite the first entry to be able to directly set the state as
696 // well as any other path
697 path[0] = '$state';
698 isTimelineActive = false;
699 payload.set(store, path, payload.state.value);
700 isTimelineActive = true;
701 }
702 });
703 });
704}
705function addStoreToDevtools(app, store) {
706 if (!componentStateTypes.includes(getStoreType(store.$id))) {
707 componentStateTypes.push(getStoreType(store.$id));
708 }
709 setupDevtoolsPlugin({
710 id: 'dev.esm.pinia',
711 label: 'Pinia 🍍',
712 logo: 'https://pinia.vuejs.org/logo.svg',
713 packageName: 'pinia',
714 homepage: 'https://pinia.vuejs.org',
715 componentStateTypes,
716 app,
717 settings: {
718 logStoreChanges: {
719 label: 'Notify about new/deleted stores',
720 type: 'boolean',
721 defaultValue: true,
722 },
723 // useEmojis: {
724 // label: 'Use emojis in messages ⚡️',
725 // type: 'boolean',
726 // defaultValue: true,
727 // },
728 },
729 }, (api) => {
730 // gracefully handle errors
731 const now = typeof api.now === 'function' ? api.now.bind(api) : Date.now;
732 store.$onAction(({ after, onError, name, args }) => {
733 const groupId = runningActionId++;
734 api.addTimelineEvent({
735 layerId: MUTATIONS_LAYER_ID,
736 event: {
737 time: now(),
738 title: '🛫 ' + name,
739 subtitle: 'start',
740 data: {
741 store: formatDisplay(store.$id),
742 action: formatDisplay(name),
743 args,
744 },
745 groupId,
746 },
747 });
748 after((result) => {
749 activeAction = undefined;
750 api.addTimelineEvent({
751 layerId: MUTATIONS_LAYER_ID,
752 event: {
753 time: now(),
754 title: '🛬 ' + name,
755 subtitle: 'end',
756 data: {
757 store: formatDisplay(store.$id),
758 action: formatDisplay(name),
759 args,
760 result,
761 },
762 groupId,
763 },
764 });
765 });
766 onError((error) => {
767 activeAction = undefined;
768 api.addTimelineEvent({
769 layerId: MUTATIONS_LAYER_ID,
770 event: {
771 time: now(),
772 logType: 'error',
773 title: '💥 ' + name,
774 subtitle: 'end',
775 data: {
776 store: formatDisplay(store.$id),
777 action: formatDisplay(name),
778 args,
779 error,
780 },
781 groupId,
782 },
783 });
784 });
785 }, true);
786 store._customProperties.forEach((name) => {
787 watch(() => unref(store[name]), (newValue, oldValue) => {
788 api.notifyComponentUpdate();
789 api.sendInspectorState(INSPECTOR_ID);
790 if (isTimelineActive) {
791 api.addTimelineEvent({
792 layerId: MUTATIONS_LAYER_ID,
793 event: {
794 time: now(),
795 title: 'Change',
796 subtitle: name,
797 data: {
798 newValue,
799 oldValue,
800 },
801 groupId: activeAction,
802 },
803 });
804 }
805 }, { deep: true });
806 });
807 store.$subscribe(({ events, type }, state) => {
808 api.notifyComponentUpdate();
809 api.sendInspectorState(INSPECTOR_ID);
810 if (!isTimelineActive)
811 return;
812 // rootStore.state[store.id] = state
813 const eventData = {
814 time: now(),
815 title: formatMutationType(type),
816 data: {
817 store: formatDisplay(store.$id),
818 ...formatEventData(events),
819 },
820 groupId: activeAction,
821 };
822 // reset for the next mutation
823 activeAction = undefined;
824 if (type === MutationType.patchFunction) {
825 eventData.subtitle = '⤵️';
826 }
827 else if (type === MutationType.patchObject) {
828 eventData.subtitle = '🧩';
829 }
830 else if (events && !Array.isArray(events)) {
831 eventData.subtitle = events.type;
832 }
833 if (events) {
834 eventData.data['rawEvent(s)'] = {
835 _custom: {
836 display: 'DebuggerEvent',
837 type: 'object',
838 tooltip: 'raw DebuggerEvent[]',
839 value: events,
840 },
841 };
842 }
843 api.addTimelineEvent({
844 layerId: MUTATIONS_LAYER_ID,
845 event: eventData,
846 });
847 }, { detached: true, flush: 'sync' });
848 const hotUpdate = store._hotUpdate;
849 store._hotUpdate = markRaw((newStore) => {
850 hotUpdate(newStore);
851 api.addTimelineEvent({
852 layerId: MUTATIONS_LAYER_ID,
853 event: {
854 time: now(),
855 title: '🔥 ' + store.$id,
856 subtitle: 'HMR update',
857 data: {
858 store: formatDisplay(store.$id),
859 info: formatDisplay(`HMR update`),
860 },
861 },
862 });
863 // update the devtools too
864 api.notifyComponentUpdate();
865 api.sendInspectorTree(INSPECTOR_ID);
866 api.sendInspectorState(INSPECTOR_ID);
867 });
868 const { $dispose } = store;
869 store.$dispose = () => {
870 $dispose();
871 api.notifyComponentUpdate();
872 api.sendInspectorTree(INSPECTOR_ID);
873 api.sendInspectorState(INSPECTOR_ID);
874 api.getSettings().logStoreChanges &&
875 toastMessage(`Disposed "${store.$id}" store 🗑`);
876 };
877 // trigger an update so it can display new registered stores
878 api.notifyComponentUpdate();
879 api.sendInspectorTree(INSPECTOR_ID);
880 api.sendInspectorState(INSPECTOR_ID);
881 api.getSettings().logStoreChanges &&
882 toastMessage(`"${store.$id}" store installed 🆕`);
883 });
884}
885let runningActionId = 0;
886let activeAction;
887/**
888 * Patches a store to enable action grouping in devtools by wrapping the store with a Proxy that is passed as the
889 * context of all actions, allowing us to set `runningAction` on each access and effectively associating any state
890 * mutation to the action.
891 *
892 * @param store - store to patch
893 * @param actionNames - list of actionst to patch
894 */
895function patchActionForGrouping(store, actionNames) {
896 // original actions of the store as they are given by pinia. We are going to override them
897 const actions = actionNames.reduce((storeActions, actionName) => {
898 // use toRaw to avoid tracking #541
899 storeActions[actionName] = toRaw(store)[actionName];
900 return storeActions;
901 }, {});
902 for (const actionName in actions) {
903 store[actionName] = function () {
904 // setActivePinia(store._p)
905 // the running action id is incremented in a before action hook
906 const _actionId = runningActionId;
907 const trackedStore = new Proxy(store, {
908 get(...args) {
909 activeAction = _actionId;
910 return Reflect.get(...args);
911 },
912 set(...args) {
913 activeAction = _actionId;
914 return Reflect.set(...args);
915 },
916 });
917 return actions[actionName].apply(trackedStore, arguments);
918 };
919 }
920}
921/**
922 * pinia.use(devtoolsPlugin)
923 */
924function devtoolsPlugin({ app, store, options }) {
925 // HMR module
926 if (store.$id.startsWith('__hot:')) {
927 return;
928 }
929 // detect option api vs setup api
930 if (options.state) {
931 store._isOptionsAPI = true;
932 }
933 // only wrap actions in option-defined stores as this technique relies on
934 // wrapping the context of the action with a proxy
935 if (typeof options.state === 'function') {
936 patchActionForGrouping(
937 // @ts-expect-error: can cast the store...
938 store, Object.keys(options.actions));
939 const originalHotUpdate = store._hotUpdate;
940 // Upgrade the HMR to also update the new actions
941 toRaw(store)._hotUpdate = function (newStore) {
942 originalHotUpdate.apply(this, arguments);
943 patchActionForGrouping(store, Object.keys(newStore._hmrPayload.actions));
944 };
945 }
946 addStoreToDevtools(app,
947 // FIXME: is there a way to allow the assignment from Store<Id, S, G, A> to StoreGeneric?
948 store);
949}
950
951/**
952 * Creates a Pinia instance to be used by the application
953 */
954function createPinia() {
955 const scope = effectScope(true);
956 // NOTE: here we could check the window object for a state and directly set it
957 // if there is anything like it with Vue 3 SSR
958 const state = scope.run(() => ref({}));
959 let _p = [];
960 // plugins added before calling app.use(pinia)
961 let toBeInstalled = [];
962 const pinia = markRaw({
963 install(app) {
964 // this allows calling useStore() outside of a component setup after
965 // installing pinia's plugin
966 setActivePinia(pinia);
967 if (!isVue2) {
968 pinia._a = app;
969 app.provide(piniaSymbol, pinia);
970 app.config.globalProperties.$pinia = pinia;
971 /* istanbul ignore else */
972 if (USE_DEVTOOLS) {
973 registerPiniaDevtools(app, pinia);
974 }
975 toBeInstalled.forEach((plugin) => _p.push(plugin));
976 toBeInstalled = [];
977 }
978 },
979 use(plugin) {
980 if (!this._a && !isVue2) {
981 toBeInstalled.push(plugin);
982 }
983 else {
984 _p.push(plugin);
985 }
986 return this;
987 },
988 _p,
989 // it's actually undefined here
990 // @ts-expect-error
991 _a: null,
992 _e: scope,
993 _s: new Map(),
994 state,
995 });
996 // pinia devtools rely on dev only features so they cannot be forced unless
997 // the dev build of Vue is used. Avoid old browsers like IE11.
998 if (USE_DEVTOOLS && typeof Proxy !== 'undefined') {
999 pinia.use(devtoolsPlugin);
1000 }
1001 return pinia;
1002}
1003
1004/**
1005 * Checks if a function is a `StoreDefinition`.
1006 *
1007 * @param fn - object to test
1008 * @returns true if `fn` is a StoreDefinition
1009 */
1010const isUseStore = (fn) => {
1011 return typeof fn === 'function' && typeof fn.$id === 'string';
1012};
1013/**
1014 * Mutates in place `newState` with `oldState` to _hot update_ it. It will
1015 * remove any key not existing in `newState` and recursively merge plain
1016 * objects.
1017 *
1018 * @param newState - new state object to be patched
1019 * @param oldState - old state that should be used to patch newState
1020 * @returns - newState
1021 */
1022function patchObject(newState, oldState) {
1023 // no need to go through symbols because they cannot be serialized anyway
1024 for (const key in oldState) {
1025 const subPatch = oldState[key];
1026 // skip the whole sub tree
1027 if (!(key in newState)) {
1028 continue;
1029 }
1030 const targetValue = newState[key];
1031 if (isPlainObject(targetValue) &&
1032 isPlainObject(subPatch) &&
1033 !isRef(subPatch) &&
1034 !isReactive(subPatch)) {
1035 newState[key] = patchObject(targetValue, subPatch);
1036 }
1037 else {
1038 // objects are either a bit more complex (e.g. refs) or primitives, so we
1039 // just set the whole thing
1040 if (isVue2) {
1041 set(newState, key, subPatch);
1042 }
1043 else {
1044 newState[key] = subPatch;
1045 }
1046 }
1047 }
1048 return newState;
1049}
1050/**
1051 * Creates an _accept_ function to pass to `import.meta.hot` in Vite applications.
1052 *
1053 * @example
1054 * ```js
1055 * const useUser = defineStore(...)
1056 * if (import.meta.hot) {
1057 * import.meta.hot.accept(acceptHMRUpdate(useUser, import.meta.hot))
1058 * }
1059 * ```
1060 *
1061 * @param initialUseStore - return of the defineStore to hot update
1062 * @param hot - `import.meta.hot`
1063 */
1064function acceptHMRUpdate(initialUseStore, hot) {
1065 return (newModule) => {
1066 const pinia = hot.data.pinia || initialUseStore._pinia;
1067 if (!pinia) {
1068 // this store is still not used
1069 return;
1070 }
1071 // preserve the pinia instance across loads
1072 hot.data.pinia = pinia;
1073 // console.log('got data', newStore)
1074 for (const exportName in newModule) {
1075 const useStore = newModule[exportName];
1076 // console.log('checking for', exportName)
1077 if (isUseStore(useStore) && pinia._s.has(useStore.$id)) {
1078 // console.log('Accepting update for', useStore.$id)
1079 const id = useStore.$id;
1080 if (id !== initialUseStore.$id) {
1081 console.warn(`The id of the store changed from "${initialUseStore.$id}" to "${id}". Reloading.`);
1082 // return import.meta.hot.invalidate()
1083 return hot.invalidate();
1084 }
1085 const existingStore = pinia._s.get(id);
1086 if (!existingStore) {
1087 console.log(`[Pinia]: skipping hmr because store doesn't exist yet`);
1088 return;
1089 }
1090 useStore(pinia, existingStore);
1091 }
1092 }
1093 };
1094}
1095
1096const noop = () => { };
1097function addSubscription(subscriptions, callback, detached, onCleanup = noop) {
1098 subscriptions.push(callback);
1099 const removeSubscription = () => {
1100 const idx = subscriptions.indexOf(callback);
1101 if (idx > -1) {
1102 subscriptions.splice(idx, 1);
1103 onCleanup();
1104 }
1105 };
1106 if (!detached && getCurrentScope()) {
1107 onScopeDispose(removeSubscription);
1108 }
1109 return removeSubscription;
1110}
1111function triggerSubscriptions(subscriptions, ...args) {
1112 subscriptions.slice().forEach((callback) => {
1113 callback(...args);
1114 });
1115}
1116
1117function mergeReactiveObjects(target, patchToApply) {
1118 // Handle Map instances
1119 if (target instanceof Map && patchToApply instanceof Map) {
1120 patchToApply.forEach((value, key) => target.set(key, value));
1121 }
1122 // Handle Set instances
1123 if (target instanceof Set && patchToApply instanceof Set) {
1124 patchToApply.forEach(target.add, target);
1125 }
1126 // no need to go through symbols because they cannot be serialized anyway
1127 for (const key in patchToApply) {
1128 if (!patchToApply.hasOwnProperty(key))
1129 continue;
1130 const subPatch = patchToApply[key];
1131 const targetValue = target[key];
1132 if (isPlainObject(targetValue) &&
1133 isPlainObject(subPatch) &&
1134 target.hasOwnProperty(key) &&
1135 !isRef(subPatch) &&
1136 !isReactive(subPatch)) {
1137 // NOTE: here I wanted to warn about inconsistent types but it's not possible because in setup stores one might
1138 // start the value of a property as a certain type e.g. a Map, and then for some reason, during SSR, change that
1139 // to `undefined`. When trying to hydrate, we want to override the Map with `undefined`.
1140 target[key] = mergeReactiveObjects(targetValue, subPatch);
1141 }
1142 else {
1143 // @ts-expect-error: subPatch is a valid value
1144 target[key] = subPatch;
1145 }
1146 }
1147 return target;
1148}
1149const skipHydrateSymbol = Symbol('pinia:skipHydration')
1150 ;
1151const skipHydrateMap = /*#__PURE__*/ new WeakMap();
1152/**
1153 * Tells Pinia to skip the hydration process of a given object. This is useful in setup stores (only) when you return a
1154 * stateful object in the store but it isn't really state. e.g. returning a router instance in a setup store.
1155 *
1156 * @param obj - target object
1157 * @returns obj
1158 */
1159function skipHydrate(obj) {
1160 return isVue2
1161 ? // in @vue/composition-api, the refs are sealed so defineProperty doesn't work...
1162 /* istanbul ignore next */ skipHydrateMap.set(obj, 1) && obj
1163 : Object.defineProperty(obj, skipHydrateSymbol, {});
1164}
1165/**
1166 * Returns whether a value should be hydrated
1167 *
1168 * @param obj - target variable
1169 * @returns true if `obj` should be hydrated
1170 */
1171function shouldHydrate(obj) {
1172 return isVue2
1173 ? /* istanbul ignore next */ !skipHydrateMap.has(obj)
1174 : !isPlainObject(obj) || !obj.hasOwnProperty(skipHydrateSymbol);
1175}
1176const { assign } = Object;
1177function isComputed(o) {
1178 return !!(isRef(o) && o.effect);
1179}
1180function createOptionsStore(id, options, pinia, hot) {
1181 const { state, actions, getters } = options;
1182 const initialState = pinia.state.value[id];
1183 let store;
1184 function setup() {
1185 if (!initialState && (!hot)) {
1186 /* istanbul ignore if */
1187 if (isVue2) {
1188 set(pinia.state.value, id, state ? state() : {});
1189 }
1190 else {
1191 pinia.state.value[id] = state ? state() : {};
1192 }
1193 }
1194 // avoid creating a state in pinia.state.value
1195 const localState = hot
1196 ? // use ref() to unwrap refs inside state TODO: check if this is still necessary
1197 toRefs(ref(state ? state() : {}).value)
1198 : toRefs(pinia.state.value[id]);
1199 return assign(localState, actions, Object.keys(getters || {}).reduce((computedGetters, name) => {
1200 if (name in localState) {
1201 console.warn(`[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`);
1202 }
1203 computedGetters[name] = markRaw(computed(() => {
1204 setActivePinia(pinia);
1205 // it was created just before
1206 const store = pinia._s.get(id);
1207 // allow cross using stores
1208 /* istanbul ignore next */
1209 if (isVue2 && !store._r)
1210 return;
1211 // @ts-expect-error
1212 // return getters![name].call(context, context)
1213 // TODO: avoid reading the getter while assigning with a global variable
1214 return getters[name].call(store, store);
1215 }));
1216 return computedGetters;
1217 }, {}));
1218 }
1219 store = createSetupStore(id, setup, options, pinia, hot, true);
1220 store.$reset = function $reset() {
1221 const newState = state ? state() : {};
1222 // we use a patch to group all changes into one single subscription
1223 this.$patch(($state) => {
1224 assign($state, newState);
1225 });
1226 };
1227 return store;
1228}
1229function createSetupStore($id, setup, options = {}, pinia, hot, isOptionsStore) {
1230 let scope;
1231 const optionsForPlugin = assign({ actions: {} }, options);
1232 /* istanbul ignore if */
1233 // @ts-expect-error: active is an internal property
1234 if (!pinia._e.active) {
1235 throw new Error('Pinia destroyed');
1236 }
1237 // watcher options for $subscribe
1238 const $subscribeOptions = {
1239 deep: true,
1240 // flush: 'post',
1241 };
1242 /* istanbul ignore else */
1243 if (!isVue2) {
1244 $subscribeOptions.onTrigger = (event) => {
1245 /* istanbul ignore else */
1246 if (isListening) {
1247 debuggerEvents = event;
1248 // avoid triggering this while the store is being built and the state is being set in pinia
1249 }
1250 else if (isListening == false && !store._hotUpdating) {
1251 // let patch send all the events together later
1252 /* istanbul ignore else */
1253 if (Array.isArray(debuggerEvents)) {
1254 debuggerEvents.push(event);
1255 }
1256 else {
1257 console.error('🍍 debuggerEvents should be an array. This is most likely an internal Pinia bug.');
1258 }
1259 }
1260 };
1261 }
1262 // internal state
1263 let isListening; // set to true at the end
1264 let isSyncListening; // set to true at the end
1265 let subscriptions = markRaw([]);
1266 let actionSubscriptions = markRaw([]);
1267 let debuggerEvents;
1268 const initialState = pinia.state.value[$id];
1269 // avoid setting the state for option stores if it is set
1270 // by the setup
1271 if (!isOptionsStore && !initialState && (!hot)) {
1272 /* istanbul ignore if */
1273 if (isVue2) {
1274 set(pinia.state.value, $id, {});
1275 }
1276 else {
1277 pinia.state.value[$id] = {};
1278 }
1279 }
1280 const hotState = ref({});
1281 // avoid triggering too many listeners
1282 // https://github.com/vuejs/pinia/issues/1129
1283 let activeListener;
1284 function $patch(partialStateOrMutator) {
1285 let subscriptionMutation;
1286 isListening = isSyncListening = false;
1287 // reset the debugger events since patches are sync
1288 /* istanbul ignore else */
1289 {
1290 debuggerEvents = [];
1291 }
1292 if (typeof partialStateOrMutator === 'function') {
1293 partialStateOrMutator(pinia.state.value[$id]);
1294 subscriptionMutation = {
1295 type: MutationType.patchFunction,
1296 storeId: $id,
1297 events: debuggerEvents,
1298 };
1299 }
1300 else {
1301 mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator);
1302 subscriptionMutation = {
1303 type: MutationType.patchObject,
1304 payload: partialStateOrMutator,
1305 storeId: $id,
1306 events: debuggerEvents,
1307 };
1308 }
1309 const myListenerId = (activeListener = Symbol());
1310 nextTick().then(() => {
1311 if (activeListener === myListenerId) {
1312 isListening = true;
1313 }
1314 });
1315 isSyncListening = true;
1316 // because we paused the watcher, we need to manually call the subscriptions
1317 triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id]);
1318 }
1319 /* istanbul ignore next */
1320 const $reset = () => {
1321 throw new Error(`🍍: Store "${$id}" is built using the setup syntax and does not implement $reset().`);
1322 }
1323 ;
1324 function $dispose() {
1325 scope.stop();
1326 subscriptions = [];
1327 actionSubscriptions = [];
1328 pinia._s.delete($id);
1329 }
1330 /**
1331 * Wraps an action to handle subscriptions.
1332 *
1333 * @param name - name of the action
1334 * @param action - action to wrap
1335 * @returns a wrapped action to handle subscriptions
1336 */
1337 function wrapAction(name, action) {
1338 return function () {
1339 setActivePinia(pinia);
1340 const args = Array.from(arguments);
1341 const afterCallbackList = [];
1342 const onErrorCallbackList = [];
1343 function after(callback) {
1344 afterCallbackList.push(callback);
1345 }
1346 function onError(callback) {
1347 onErrorCallbackList.push(callback);
1348 }
1349 // @ts-expect-error
1350 triggerSubscriptions(actionSubscriptions, {
1351 args,
1352 name,
1353 store,
1354 after,
1355 onError,
1356 });
1357 let ret;
1358 try {
1359 ret = action.apply(this && this.$id === $id ? this : store, args);
1360 // handle sync errors
1361 }
1362 catch (error) {
1363 triggerSubscriptions(onErrorCallbackList, error);
1364 throw error;
1365 }
1366 if (ret instanceof Promise) {
1367 return ret
1368 .then((value) => {
1369 triggerSubscriptions(afterCallbackList, value);
1370 return value;
1371 })
1372 .catch((error) => {
1373 triggerSubscriptions(onErrorCallbackList, error);
1374 return Promise.reject(error);
1375 });
1376 }
1377 // allow the afterCallback to override the return value
1378 triggerSubscriptions(afterCallbackList, ret);
1379 return ret;
1380 };
1381 }
1382 const _hmrPayload = /*#__PURE__*/ markRaw({
1383 actions: {},
1384 getters: {},
1385 state: [],
1386 hotState,
1387 });
1388 const partialStore = {
1389 _p: pinia,
1390 // _s: scope,
1391 $id,
1392 $onAction: addSubscription.bind(null, actionSubscriptions),
1393 $patch,
1394 $reset,
1395 $subscribe(callback, options = {}) {
1396 const removeSubscription = addSubscription(subscriptions, callback, options.detached, () => stopWatcher());
1397 const stopWatcher = scope.run(() => watch(() => pinia.state.value[$id], (state) => {
1398 if (options.flush === 'sync' ? isSyncListening : isListening) {
1399 callback({
1400 storeId: $id,
1401 type: MutationType.direct,
1402 events: debuggerEvents,
1403 }, state);
1404 }
1405 }, assign({}, $subscribeOptions, options)));
1406 return removeSubscription;
1407 },
1408 $dispose,
1409 };
1410 /* istanbul ignore if */
1411 if (isVue2) {
1412 // start as non ready
1413 partialStore._r = false;
1414 }
1415 const store = reactive(assign({
1416 _hmrPayload,
1417 _customProperties: markRaw(new Set()), // devtools custom properties
1418 }, partialStore
1419 // must be added later
1420 // setupStore
1421 )
1422 );
1423 // store the partial store now so the setup of stores can instantiate each other before they are finished without
1424 // creating infinite loops.
1425 pinia._s.set($id, store);
1426 // TODO: idea create skipSerialize that marks properties as non serializable and they are skipped
1427 const setupStore = pinia._e.run(() => {
1428 scope = effectScope();
1429 return scope.run(() => setup());
1430 });
1431 // overwrite existing actions to support $onAction
1432 for (const key in setupStore) {
1433 const prop = setupStore[key];
1434 if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
1435 // mark it as a piece of state to be serialized
1436 if (hot) {
1437 set(hotState.value, key, toRef(setupStore, key));
1438 // createOptionStore directly sets the state in pinia.state.value so we
1439 // can just skip that
1440 }
1441 else if (!isOptionsStore) {
1442 // in setup stores we must hydrate the state and sync pinia state tree with the refs the user just created
1443 if (initialState && shouldHydrate(prop)) {
1444 if (isRef(prop)) {
1445 prop.value = initialState[key];
1446 }
1447 else {
1448 // probably a reactive object, lets recursively assign
1449 // @ts-expect-error: prop is unknown
1450 mergeReactiveObjects(prop, initialState[key]);
1451 }
1452 }
1453 // transfer the ref to the pinia state to keep everything in sync
1454 /* istanbul ignore if */
1455 if (isVue2) {
1456 set(pinia.state.value[$id], key, prop);
1457 }
1458 else {
1459 pinia.state.value[$id][key] = prop;
1460 }
1461 }
1462 /* istanbul ignore else */
1463 {
1464 _hmrPayload.state.push(key);
1465 }
1466 // action
1467 }
1468 else if (typeof prop === 'function') {
1469 // @ts-expect-error: we are overriding the function we avoid wrapping if
1470 const actionValue = hot ? prop : wrapAction(key, prop);
1471 // this a hot module replacement store because the hotUpdate method needs
1472 // to do it with the right context
1473 /* istanbul ignore if */
1474 if (isVue2) {
1475 set(setupStore, key, actionValue);
1476 }
1477 else {
1478 // @ts-expect-error
1479 setupStore[key] = actionValue;
1480 }
1481 /* istanbul ignore else */
1482 {
1483 _hmrPayload.actions[key] = prop;
1484 }
1485 // list actions so they can be used in plugins
1486 // @ts-expect-error
1487 optionsForPlugin.actions[key] = prop;
1488 }
1489 else {
1490 // add getters for devtools
1491 if (isComputed(prop)) {
1492 _hmrPayload.getters[key] = isOptionsStore
1493 ? // @ts-expect-error
1494 options.getters[key]
1495 : prop;
1496 if (IS_CLIENT) {
1497 const getters = setupStore._getters ||
1498 // @ts-expect-error: same
1499 (setupStore._getters = markRaw([]));
1500 getters.push(key);
1501 }
1502 }
1503 }
1504 }
1505 // add the state, getters, and action properties
1506 /* istanbul ignore if */
1507 if (isVue2) {
1508 Object.keys(setupStore).forEach((key) => {
1509 set(store, key, setupStore[key]);
1510 });
1511 }
1512 else {
1513 assign(store, setupStore);
1514 // allows retrieving reactive objects with `storeToRefs()`. Must be called after assigning to the reactive object.
1515 // Make `storeToRefs()` work with `reactive()` #799
1516 assign(toRaw(store), setupStore);
1517 }
1518 // use this instead of a computed with setter to be able to create it anywhere
1519 // without linking the computed lifespan to wherever the store is first
1520 // created.
1521 Object.defineProperty(store, '$state', {
1522 get: () => (hot ? hotState.value : pinia.state.value[$id]),
1523 set: (state) => {
1524 /* istanbul ignore if */
1525 if (hot) {
1526 throw new Error('cannot set hotState');
1527 }
1528 $patch(($state) => {
1529 assign($state, state);
1530 });
1531 },
1532 });
1533 // add the hotUpdate before plugins to allow them to override it
1534 /* istanbul ignore else */
1535 {
1536 store._hotUpdate = markRaw((newStore) => {
1537 store._hotUpdating = true;
1538 newStore._hmrPayload.state.forEach((stateKey) => {
1539 if (stateKey in store.$state) {
1540 const newStateTarget = newStore.$state[stateKey];
1541 const oldStateSource = store.$state[stateKey];
1542 if (typeof newStateTarget === 'object' &&
1543 isPlainObject(newStateTarget) &&
1544 isPlainObject(oldStateSource)) {
1545 patchObject(newStateTarget, oldStateSource);
1546 }
1547 else {
1548 // transfer the ref
1549 newStore.$state[stateKey] = oldStateSource;
1550 }
1551 }
1552 // patch direct access properties to allow store.stateProperty to work as
1553 // store.$state.stateProperty
1554 set(store, stateKey, toRef(newStore.$state, stateKey));
1555 });
1556 // remove deleted state properties
1557 Object.keys(store.$state).forEach((stateKey) => {
1558 if (!(stateKey in newStore.$state)) {
1559 del(store, stateKey);
1560 }
1561 });
1562 // avoid devtools logging this as a mutation
1563 isListening = false;
1564 isSyncListening = false;
1565 pinia.state.value[$id] = toRef(newStore._hmrPayload, 'hotState');
1566 isSyncListening = true;
1567 nextTick().then(() => {
1568 isListening = true;
1569 });
1570 for (const actionName in newStore._hmrPayload.actions) {
1571 const action = newStore[actionName];
1572 set(store, actionName, wrapAction(actionName, action));
1573 }
1574 // TODO: does this work in both setup and option store?
1575 for (const getterName in newStore._hmrPayload.getters) {
1576 const getter = newStore._hmrPayload.getters[getterName];
1577 const getterValue = isOptionsStore
1578 ? // special handling of options api
1579 computed(() => {
1580 setActivePinia(pinia);
1581 return getter.call(store, store);
1582 })
1583 : getter;
1584 set(store, getterName, getterValue);
1585 }
1586 // remove deleted getters
1587 Object.keys(store._hmrPayload.getters).forEach((key) => {
1588 if (!(key in newStore._hmrPayload.getters)) {
1589 del(store, key);
1590 }
1591 });
1592 // remove old actions
1593 Object.keys(store._hmrPayload.actions).forEach((key) => {
1594 if (!(key in newStore._hmrPayload.actions)) {
1595 del(store, key);
1596 }
1597 });
1598 // update the values used in devtools and to allow deleting new properties later on
1599 store._hmrPayload = newStore._hmrPayload;
1600 store._getters = newStore._getters;
1601 store._hotUpdating = false;
1602 });
1603 }
1604 if (USE_DEVTOOLS) {
1605 const nonEnumerable = {
1606 writable: true,
1607 configurable: true,
1608 // avoid warning on devtools trying to display this property
1609 enumerable: false,
1610 };
1611 ['_p', '_hmrPayload', '_getters', '_customProperties'].forEach((p) => {
1612 Object.defineProperty(store, p, {
1613 value: store[p],
1614 ...nonEnumerable,
1615 });
1616 });
1617 }
1618 /* istanbul ignore if */
1619 if (isVue2) {
1620 // mark the store as ready before plugins
1621 store._r = true;
1622 }
1623 // apply all plugins
1624 pinia._p.forEach((extender) => {
1625 /* istanbul ignore else */
1626 if (USE_DEVTOOLS) {
1627 const extensions = scope.run(() => extender({
1628 store,
1629 app: pinia._a,
1630 pinia,
1631 options: optionsForPlugin,
1632 }));
1633 Object.keys(extensions || {}).forEach((key) => store._customProperties.add(key));
1634 assign(store, extensions);
1635 }
1636 else {
1637 assign(store, scope.run(() => extender({
1638 store,
1639 app: pinia._a,
1640 pinia,
1641 options: optionsForPlugin,
1642 })));
1643 }
1644 });
1645 if (store.$state &&
1646 typeof store.$state === 'object' &&
1647 typeof store.$state.constructor === 'function' &&
1648 !store.$state.constructor.toString().includes('[native code]')) {
1649 console.warn(`[🍍]: The "state" must be a plain object. It cannot be\n` +
1650 `\tstate: () => new MyClass()\n` +
1651 `Found in store "${store.$id}".`);
1652 }
1653 // only apply hydrate to option stores with an initial state in pinia
1654 if (initialState &&
1655 isOptionsStore &&
1656 options.hydrate) {
1657 options.hydrate(store.$state, initialState);
1658 }
1659 isListening = true;
1660 isSyncListening = true;
1661 return store;
1662}
1663function defineStore(
1664// TODO: add proper types from above
1665idOrOptions, setup, setupOptions) {
1666 let id;
1667 let options;
1668 const isSetupStore = typeof setup === 'function';
1669 if (typeof idOrOptions === 'string') {
1670 id = idOrOptions;
1671 // the option store setup will contain the actual options in this case
1672 options = isSetupStore ? setupOptions : setup;
1673 }
1674 else {
1675 options = idOrOptions;
1676 id = idOrOptions.id;
1677 }
1678 function useStore(pinia, hot) {
1679 const currentInstance = getCurrentInstance();
1680 pinia =
1681 // in test mode, ignore the argument provided as we can always retrieve a
1682 // pinia instance with getActivePinia()
1683 (pinia) ||
1684 (currentInstance && inject(piniaSymbol));
1685 if (pinia)
1686 setActivePinia(pinia);
1687 if (!activePinia) {
1688 throw new Error(`[🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?\n` +
1689 `\tconst pinia = createPinia()\n` +
1690 `\tapp.use(pinia)\n` +
1691 `This will fail in production.`);
1692 }
1693 pinia = activePinia;
1694 if (!pinia._s.has(id)) {
1695 // creating the store registers it in `pinia._s`
1696 if (isSetupStore) {
1697 createSetupStore(id, setup, options, pinia);
1698 }
1699 else {
1700 createOptionsStore(id, options, pinia);
1701 }
1702 /* istanbul ignore else */
1703 {
1704 // @ts-expect-error: not the right inferred type
1705 useStore._pinia = pinia;
1706 }
1707 }
1708 const store = pinia._s.get(id);
1709 if (hot) {
1710 const hotId = '__hot:' + id;
1711 const newStore = isSetupStore
1712 ? createSetupStore(hotId, setup, options, pinia, true)
1713 : createOptionsStore(hotId, assign({}, options), pinia, true);
1714 hot._hotUpdate(newStore);
1715 // cleanup the state properties and the store from the cache
1716 delete pinia.state.value[hotId];
1717 pinia._s.delete(hotId);
1718 }
1719 // save stores in instances to access them devtools
1720 if (IS_CLIENT &&
1721 currentInstance &&
1722 currentInstance.proxy &&
1723 // avoid adding stores that are just built for hot module replacement
1724 !hot) {
1725 const vm = currentInstance.proxy;
1726 const cache = '_pStores' in vm ? vm._pStores : (vm._pStores = {});
1727 cache[id] = store;
1728 }
1729 // StoreGeneric cannot be casted towards Store
1730 return store;
1731 }
1732 useStore.$id = id;
1733 return useStore;
1734}
1735
1736let mapStoreSuffix = 'Store';
1737/**
1738 * Changes the suffix added by `mapStores()`. Can be set to an empty string.
1739 * Defaults to `"Store"`. Make sure to extend the MapStoresCustomization
1740 * interface if you are using TypeScript.
1741 *
1742 * @param suffix - new suffix
1743 */
1744function setMapStoreSuffix(suffix // could be 'Store' but that would be annoying for JS
1745) {
1746 mapStoreSuffix = suffix;
1747}
1748/**
1749 * Allows using stores without the composition API (`setup()`) by generating an
1750 * object to be spread in the `computed` field of a component. It accepts a list
1751 * of store definitions.
1752 *
1753 * @example
1754 * ```js
1755 * export default {
1756 * computed: {
1757 * // other computed properties
1758 * ...mapStores(useUserStore, useCartStore)
1759 * },
1760 *
1761 * created() {
1762 * this.userStore // store with id "user"
1763 * this.cartStore // store with id "cart"
1764 * }
1765 * }
1766 * ```
1767 *
1768 * @param stores - list of stores to map to an object
1769 */
1770function mapStores(...stores) {
1771 if (Array.isArray(stores[0])) {
1772 console.warn(`[🍍]: Directly pass all stores to "mapStores()" without putting them in an array:\n` +
1773 `Replace\n` +
1774 `\tmapStores([useAuthStore, useCartStore])\n` +
1775 `with\n` +
1776 `\tmapStores(useAuthStore, useCartStore)\n` +
1777 `This will fail in production if not fixed.`);
1778 stores = stores[0];
1779 }
1780 return stores.reduce((reduced, useStore) => {
1781 // @ts-expect-error: $id is added by defineStore
1782 reduced[useStore.$id + mapStoreSuffix] = function () {
1783 return useStore(this.$pinia);
1784 };
1785 return reduced;
1786 }, {});
1787}
1788/**
1789 * Allows using state and getters from one store without using the composition
1790 * API (`setup()`) by generating an object to be spread in the `computed` field
1791 * of a component.
1792 *
1793 * @param useStore - store to map from
1794 * @param keysOrMapper - array or object
1795 */
1796function mapState(useStore, keysOrMapper) {
1797 return Array.isArray(keysOrMapper)
1798 ? keysOrMapper.reduce((reduced, key) => {
1799 reduced[key] = function () {
1800 return useStore(this.$pinia)[key];
1801 };
1802 return reduced;
1803 }, {})
1804 : Object.keys(keysOrMapper).reduce((reduced, key) => {
1805 // @ts-expect-error
1806 reduced[key] = function () {
1807 const store = useStore(this.$pinia);
1808 const storeKey = keysOrMapper[key];
1809 // for some reason TS is unable to infer the type of storeKey to be a
1810 // function
1811 return typeof storeKey === 'function'
1812 ? storeKey.call(this, store)
1813 : store[storeKey];
1814 };
1815 return reduced;
1816 }, {});
1817}
1818/**
1819 * Alias for `mapState()`. You should use `mapState()` instead.
1820 * @deprecated use `mapState()` instead.
1821 */
1822const mapGetters = mapState;
1823/**
1824 * Allows directly using actions from your store without using the composition
1825 * API (`setup()`) by generating an object to be spread in the `methods` field
1826 * of a component.
1827 *
1828 * @param useStore - store to map from
1829 * @param keysOrMapper - array or object
1830 */
1831function mapActions(useStore, keysOrMapper) {
1832 return Array.isArray(keysOrMapper)
1833 ? keysOrMapper.reduce((reduced, key) => {
1834 // @ts-expect-error
1835 reduced[key] = function (...args) {
1836 return useStore(this.$pinia)[key](...args);
1837 };
1838 return reduced;
1839 }, {})
1840 : Object.keys(keysOrMapper).reduce((reduced, key) => {
1841 // @ts-expect-error
1842 reduced[key] = function (...args) {
1843 return useStore(this.$pinia)[keysOrMapper[key]](...args);
1844 };
1845 return reduced;
1846 }, {});
1847}
1848/**
1849 * Allows using state and getters from one store without using the composition
1850 * API (`setup()`) by generating an object to be spread in the `computed` field
1851 * of a component.
1852 *
1853 * @param useStore - store to map from
1854 * @param keysOrMapper - array or object
1855 */
1856function mapWritableState(useStore, keysOrMapper) {
1857 return Array.isArray(keysOrMapper)
1858 ? keysOrMapper.reduce((reduced, key) => {
1859 // @ts-ignore
1860 reduced[key] = {
1861 get() {
1862 return useStore(this.$pinia)[key];
1863 },
1864 set(value) {
1865 // it's easier to type it here as any
1866 return (useStore(this.$pinia)[key] = value);
1867 },
1868 };
1869 return reduced;
1870 }, {})
1871 : Object.keys(keysOrMapper).reduce((reduced, key) => {
1872 // @ts-ignore
1873 reduced[key] = {
1874 get() {
1875 return useStore(this.$pinia)[keysOrMapper[key]];
1876 },
1877 set(value) {
1878 // it's easier to type it here as any
1879 return (useStore(this.$pinia)[keysOrMapper[key]] = value);
1880 },
1881 };
1882 return reduced;
1883 }, {});
1884}
1885
1886/**
1887 * Creates an object of references with all the state, getters, and plugin-added
1888 * state properties of the store. Similar to `toRefs()` but specifically
1889 * designed for Pinia stores so methods and non reactive properties are
1890 * completely ignored.
1891 *
1892 * @param store - store to extract the refs from
1893 */
1894function storeToRefs(store) {
1895 // See https://github.com/vuejs/pinia/issues/852
1896 // It's easier to just use toRefs() even if it includes more stuff
1897 if (isVue2) {
1898 // @ts-expect-error: toRefs include methods and others
1899 return toRefs(store);
1900 }
1901 else {
1902 store = toRaw(store);
1903 const refs = {};
1904 for (const key in store) {
1905 const value = store[key];
1906 if (isRef(value) || isReactive(value)) {
1907 // @ts-expect-error: the key is state or getter
1908 refs[key] =
1909 // ---
1910 toRef(store, key);
1911 }
1912 }
1913 return refs;
1914 }
1915}
1916
1917/**
1918 * Vue 2 Plugin that must be installed for pinia to work. Note **you don't need
1919 * this plugin if you are using Nuxt.js**. Use the `buildModule` instead:
1920 * https://pinia.vuejs.org/ssr/nuxt.html.
1921 *
1922 * @example
1923 * ```js
1924 * import Vue from 'vue'
1925 * import { PiniaVuePlugin, createPinia } from 'pinia'
1926 *
1927 * Vue.use(PiniaVuePlugin)
1928 * const pinia = createPinia()
1929 *
1930 * new Vue({
1931 * el: '#app',
1932 * // ...
1933 * pinia,
1934 * })
1935 * ```
1936 *
1937 * @param _Vue - `Vue` imported from 'vue'.
1938 */
1939const PiniaVuePlugin = function (_Vue) {
1940 // Equivalent of
1941 // app.config.globalProperties.$pinia = pinia
1942 _Vue.mixin({
1943 beforeCreate() {
1944 const options = this.$options;
1945 if (options.pinia) {
1946 const pinia = options.pinia;
1947 // HACK: taken from provide(): https://github.com/vuejs/composition-api/blob/main/src/apis/inject.ts#L31
1948 /* istanbul ignore else */
1949 if (!this._provided) {
1950 const provideCache = {};
1951 Object.defineProperty(this, '_provided', {
1952 get: () => provideCache,
1953 set: (v) => Object.assign(provideCache, v),
1954 });
1955 }
1956 this._provided[piniaSymbol] = pinia;
1957 // propagate the pinia instance in an SSR friendly way
1958 // avoid adding it to nuxt twice
1959 /* istanbul ignore else */
1960 if (!this.$pinia) {
1961 this.$pinia = pinia;
1962 }
1963 pinia._a = this;
1964 if (IS_CLIENT) {
1965 // this allows calling useStore() outside of a component setup after
1966 // installing pinia's plugin
1967 setActivePinia(pinia);
1968 }
1969 if (USE_DEVTOOLS) {
1970 registerPiniaDevtools(pinia._a, pinia);
1971 }
1972 }
1973 else if (!this.$pinia && options.parent && options.parent.$pinia) {
1974 this.$pinia = options.parent.$pinia;
1975 }
1976 },
1977 destroyed() {
1978 delete this._pStores;
1979 },
1980 });
1981};
1982
1983export { MutationType, PiniaVuePlugin, acceptHMRUpdate, createPinia, defineStore, getActivePinia, mapActions, mapGetters, mapState, mapStores, mapWritableState, setActivePinia, setMapStoreSuffix, skipHydrate, storeToRefs };