Guest User

Untitled

a guest
Aug 29th, 2024
37
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 41.93 KB | None | 0 0
  1. /*!
  2. * pinia v2.1.7
  3. * (c) 2023 Eduardo San Martin Morote
  4. * @license MIT
  5. */
  6. import { hasInjectionContext, inject, effectScope, ref, markRaw, isVue2, isRef, isReactive, set, getCurrentScope, onScopeDispose, getCurrentInstance, watch, reactive, toRef, toRaw, del, nextTick, computed, toRefs } from 'vue-demi';
  7.  
  8. /**
  9. * setActivePinia must be called to handle SSR at the top of functions like
  10. * `fetch`, `setup`, `serverPrefetch` and others
  11. */
  12. let activePinia;
  13. /**
  14. * Sets or unsets the active pinia. Used in SSR and internally when calling
  15. * actions and getters
  16. *
  17. * @param pinia - Pinia instance
  18. */
  19. // @ts-expect-error: cannot constrain the type of the return
  20. const setActivePinia = (pinia) => (activePinia = pinia);
  21. /**
  22. * Get the currently active pinia if there is any.
  23. */
  24. const getActivePinia = () => (hasInjectionContext() && inject(piniaSymbol)) || activePinia;
  25. const piniaSymbol = (Symbol('pinia') );
  26.  
  27. function isPlainObject(
  28. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  29. o) {
  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. */
  40. var 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.  
  65. const IS_CLIENT = typeof window !== 'undefined';
  66.  
  67. let runningActionId = 0;
  68. let activeAction;
  69. /**
  70. * Patches a store to enable action grouping in devtools by wrapping the store with a Proxy that is passed as the
  71. * context of all actions, allowing us to set `runningAction` on each access and effectively associating any state
  72. * mutation to the action.
  73. *
  74. * @param store - store to patch
  75. * @param actionNames - list of actionst to patch
  76. */
  77. function patchActionForGrouping(store, actionNames, wrapWithProxy) {
  78. // original actions of the store as they are given by pinia. We are going to override them
  79. const actions = actionNames.reduce((storeActions, actionName) => {
  80. // use toRaw to avoid tracking #541
  81. storeActions[actionName] = toRaw(store)[actionName];
  82. return storeActions;
  83. }, {});
  84. for (const actionName in actions) {
  85. store[actionName] = function () {
  86. // the running action id is incremented in a before action hook
  87. const _actionId = runningActionId;
  88. const trackedStore = wrapWithProxy
  89. ? new Proxy(store, {
  90. get(...args) {
  91. activeAction = _actionId;
  92. return Reflect.get(...args);
  93. },
  94. set(...args) {
  95. activeAction = _actionId;
  96. return Reflect.set(...args);
  97. },
  98. })
  99. : store;
  100. // For Setup Stores we need https://github.com/tc39/proposal-async-context
  101. activeAction = _actionId;
  102. const retValue = actions[actionName].apply(trackedStore, arguments);
  103. // this is safer as async actions in Setup Stores would associate mutations done outside of the action
  104. activeAction = undefined;
  105. return retValue;
  106. };
  107. }
  108. }
  109. function devtoolsPlugin({ app, store, options }) {
  110. // HMR module
  111. // if (store.$id.startsWith('__hot:')) {
  112. // return;
  113. // }
  114. // detect option api vs setup api
  115. store._isOptionsAPI = !!options.state;
  116. patchActionForGrouping(store, Object.keys(options.actions), store._isOptionsAPI);
  117. // Upgrade the HMR to also update the new actions
  118. // const originalHotUpdate = store._hotUpdate;
  119. // toRaw(store)._hotUpdate = function (newStore) {
  120. // originalHotUpdate.apply(this, arguments);
  121. // patchActionForGrouping(store, Object.keys(newStore._hmrPayload.actions), !!store._isOptionsAPI);
  122. // };
  123. // addStoreToDevtools(app,
  124. // FIXME: is there a way to allow the assignment from Store<Id, S, G, A> to StoreGeneric?
  125. // store);
  126. }
  127.  
  128. /**
  129. * Creates a Pinia instance to be used by the application
  130. */
  131. function createPinia() {
  132. const scope = effectScope(true);
  133. // NOTE: here we could check the window object for a state and directly set it
  134. // if there is anything like it with Vue 3 SSR
  135. const state = scope.run(() => ref({}));
  136. let _p = [];
  137. // plugins added before calling app.use(pinia)
  138. let toBeInstalled = [];
  139. const pinia = markRaw({
  140. install(app) {
  141. // this allows calling useStore() outside of a component setup after
  142. // installing pinia's plugin
  143. setActivePinia(pinia);
  144. if (!isVue2) {
  145. pinia._a = app;
  146. app.provide(piniaSymbol, pinia);
  147. app.config.globalProperties.$pinia = pinia;
  148. toBeInstalled.forEach((plugin) => _p.push(plugin));
  149. toBeInstalled = [];
  150. }
  151. },
  152. use(plugin) {
  153. if (!this._a && !isVue2) {
  154. toBeInstalled.push(plugin);
  155. }
  156. else {
  157. _p.push(plugin);
  158. }
  159. return this;
  160. },
  161. _p,
  162. // it's actually undefined here
  163. // @ts-expect-error
  164. _a: null,
  165. _e: scope,
  166. _s: new Map(),
  167. state,
  168. });
  169.  
  170. pinia.use(devtoolsPlugin);
  171.  
  172. return pinia;
  173. }
  174.  
  175. /**
  176. * Checks if a function is a `StoreDefinition`.
  177. *
  178. * @param fn - object to test
  179. * @returns true if `fn` is a StoreDefinition
  180. */
  181. const isUseStore = (fn) => {
  182. return typeof fn === 'function' && typeof fn.$id === 'string';
  183. };
  184. /**
  185. * Mutates in place `newState` with `oldState` to _hot update_ it. It will
  186. * remove any key not existing in `newState` and recursively merge plain
  187. * objects.
  188. *
  189. * @param newState - new state object to be patched
  190. * @param oldState - old state that should be used to patch newState
  191. * @returns - newState
  192. */
  193. function patchObject(newState, oldState) {
  194. // no need to go through symbols because they cannot be serialized anyway
  195. for (const key in oldState) {
  196. const subPatch = oldState[key];
  197. // skip the whole sub tree
  198. if (!(key in newState)) {
  199. continue;
  200. }
  201. const targetValue = newState[key];
  202. if (isPlainObject(targetValue) &&
  203. isPlainObject(subPatch) &&
  204. !isRef(subPatch) &&
  205. !isReactive(subPatch)) {
  206. newState[key] = patchObject(targetValue, subPatch);
  207. }
  208. else {
  209. // objects are either a bit more complex (e.g. refs) or primitives, so we
  210. // just set the whole thing
  211. if (isVue2) {
  212. set(newState, key, subPatch);
  213. }
  214. else {
  215. newState[key] = subPatch;
  216. }
  217. }
  218. }
  219. return newState;
  220. }
  221. /**
  222. * Creates an _accept_ function to pass to `import.meta.hot` in Vite applications.
  223. *
  224. * @example
  225. * ```js
  226. * const useUser = defineStore(...)
  227. * if (import.meta.hot) {
  228. * import.meta.hot.accept(acceptHMRUpdate(useUser, import.meta.hot))
  229. * }
  230. * ```
  231. *
  232. * @param initialUseStore - return of the defineStore to hot update
  233. * @param hot - `import.meta.hot`
  234. */
  235. function acceptHMRUpdate(initialUseStore, hot) {
  236. return (newModule) => {
  237. const pinia = hot.data.pinia || initialUseStore._pinia;
  238. if (!pinia) {
  239. // this store is still not used
  240. return;
  241. }
  242. // preserve the pinia instance across loads
  243. hot.data.pinia = pinia;
  244. // console.log('got data', newStore)
  245. for (const exportName in newModule) {
  246. const useStore = newModule[exportName];
  247. // console.log('checking for', exportName)
  248. if (isUseStore(useStore) && pinia._s.has(useStore.$id)) {
  249. // console.log('Accepting update for', useStore.$id)
  250. const id = useStore.$id;
  251. if (id !== initialUseStore.$id) {
  252. console.warn(`The id of the store changed from "${initialUseStore.$id}" to "${id}". Reloading.`);
  253. // return import.meta.hot.invalidate()
  254. return hot.invalidate();
  255. }
  256. const existingStore = pinia._s.get(id);
  257. if (!existingStore) {
  258. console.log(`[Pinia]: skipping hmr because store doesn't exist yet`);
  259. return;
  260. }
  261. useStore(pinia, existingStore);
  262. }
  263. }
  264. };
  265. }
  266.  
  267. const noop = () => { };
  268. function addSubscription(subscriptions, callback, detached, onCleanup = noop) {
  269. subscriptions.push(callback);
  270. const removeSubscription = () => {
  271. const idx = subscriptions.indexOf(callback);
  272. if (idx > -1) {
  273. subscriptions.splice(idx, 1);
  274. onCleanup();
  275. }
  276. };
  277. if (!detached && getCurrentScope()) {
  278. onScopeDispose(removeSubscription);
  279. }
  280. return removeSubscription;
  281. }
  282. function triggerSubscriptions(subscriptions, ...args) {
  283. subscriptions.slice().forEach((callback) => {
  284. callback(...args);
  285. });
  286. }
  287.  
  288. const fallbackRunWithContext = (fn) => fn();
  289. function mergeReactiveObjects(target, patchToApply) {
  290. // Handle Map instances
  291. if (target instanceof Map && patchToApply instanceof Map) {
  292. patchToApply.forEach((value, key) => target.set(key, value));
  293. }
  294. // Handle Set instances
  295. if (target instanceof Set && patchToApply instanceof Set) {
  296. patchToApply.forEach(target.add, target);
  297. }
  298. // no need to go through symbols because they cannot be serialized anyway
  299. for (const key in patchToApply) {
  300. if (!patchToApply.hasOwnProperty(key))
  301. continue;
  302. const subPatch = patchToApply[key];
  303. const targetValue = target[key];
  304. if (isPlainObject(targetValue) &&
  305. isPlainObject(subPatch) &&
  306. target.hasOwnProperty(key) &&
  307. !isRef(subPatch) &&
  308. !isReactive(subPatch)) {
  309. // NOTE: here I wanted to warn about inconsistent types but it's not possible because in setup stores one might
  310. // start the value of a property as a certain type e.g. a Map, and then for some reason, during SSR, change that
  311. // to `undefined`. When trying to hydrate, we want to override the Map with `undefined`.
  312. target[key] = mergeReactiveObjects(targetValue, subPatch);
  313. }
  314. else {
  315. // @ts-expect-error: subPatch is a valid value
  316. target[key] = subPatch;
  317. }
  318. }
  319. return target;
  320. }
  321. const skipHydrateSymbol = Symbol('pinia:skipHydration')
  322. ;
  323. const skipHydrateMap = /*#__PURE__*/ new WeakMap();
  324. /**
  325. * Tells Pinia to skip the hydration process of a given object. This is useful in setup stores (only) when you return a
  326. * stateful object in the store but it isn't really state. e.g. returning a router instance in a setup store.
  327. *
  328. * @param obj - target object
  329. * @returns obj
  330. */
  331. function skipHydrate(obj) {
  332. return isVue2
  333. ? // in @vue/composition-api, the refs are sealed so defineProperty doesn't work...
  334. /* istanbul ignore next */ skipHydrateMap.set(obj, 1) && obj
  335. : Object.defineProperty(obj, skipHydrateSymbol, {});
  336. }
  337. /**
  338. * Returns whether a value should be hydrated
  339. *
  340. * @param obj - target variable
  341. * @returns true if `obj` should be hydrated
  342. */
  343. function shouldHydrate(obj) {
  344. return isVue2
  345. ? /* istanbul ignore next */ !skipHydrateMap.has(obj)
  346. : !isPlainObject(obj) || !obj.hasOwnProperty(skipHydrateSymbol);
  347. }
  348. const { assign } = Object;
  349. function isComputed(o) {
  350. return !!(isRef(o) && o.effect);
  351. }
  352. function createOptionsStore(id, options, pinia, hot) {
  353. const { state, actions, getters } = options;
  354. const initialState = pinia.state.value[id];
  355. let store;
  356. function setup() {
  357. if (!initialState && (!hot)) {
  358. /* istanbul ignore if */
  359. if (isVue2) {
  360. set(pinia.state.value, id, state ? state() : {});
  361. }
  362. else {
  363. pinia.state.value[id] = state ? state() : {};
  364. }
  365. }
  366. // avoid creating a state in pinia.state.value
  367. const localState = hot
  368. ? // use ref() to unwrap refs inside state TODO: check if this is still necessary
  369. toRefs(ref(state ? state() : {}).value)
  370. : toRefs(pinia.state.value[id]);
  371. return assign(localState, actions, Object.keys(getters || {}).reduce((computedGetters, name) => {
  372. if (name in localState) {
  373. console.warn(`[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`);
  374. }
  375. computedGetters[name] = markRaw(computed(() => {
  376. setActivePinia(pinia);
  377. // it was created just before
  378. const store = pinia._s.get(id);
  379. // allow cross using stores
  380. /* istanbul ignore next */
  381. if (isVue2 && !store._r)
  382. return;
  383. // @ts-expect-error
  384. // return getters![name].call(context, context)
  385. // TODO: avoid reading the getter while assigning with a global variable
  386. return getters[name].call(store, store);
  387. }));
  388. return computedGetters;
  389. }, {}));
  390. }
  391. store = createSetupStore(id, setup, options, pinia, hot, true);
  392. return store;
  393. }
  394. function createSetupStore($id, setup, options = {}, pinia, hot, isOptionsStore) {
  395. let scope;
  396. const optionsForPlugin = assign({ actions: {} }, options);
  397. /* istanbul ignore if */
  398. if (!pinia._e.active) {
  399. throw new Error('Pinia destroyed');
  400. }
  401. // watcher options for $subscribe
  402. const $subscribeOptions = {
  403. deep: true,
  404. // flush: 'post',
  405. };
  406. /* istanbul ignore else */
  407. if (!isVue2) {
  408. $subscribeOptions.onTrigger = (event) => {
  409. /* istanbul ignore else */
  410. if (isListening) {
  411. debuggerEvents = event;
  412. // avoid triggering this while the store is being built and the state is being set in pinia
  413. }
  414. else if (isListening == false && !store._hotUpdating) {
  415. // let patch send all the events together later
  416. /* istanbul ignore else */
  417. if (Array.isArray(debuggerEvents)) {
  418. debuggerEvents.push(event);
  419. }
  420. else {
  421. console.error('🍍 debuggerEvents should be an array. This is most likely an internal Pinia bug.');
  422. }
  423. }
  424. };
  425. }
  426. // internal state
  427. let isListening; // set to true at the end
  428. let isSyncListening; // set to true at the end
  429. let subscriptions = [];
  430. let actionSubscriptions = [];
  431. let debuggerEvents;
  432. const initialState = pinia.state.value[$id];
  433. // avoid setting the state for option stores if it is set
  434. // by the setup
  435. if (!isOptionsStore && !initialState && (!hot)) {
  436. /* istanbul ignore if */
  437. if (isVue2) {
  438. set(pinia.state.value, $id, {});
  439. }
  440. else {
  441. pinia.state.value[$id] = {};
  442. }
  443. }
  444. const hotState = ref({});
  445. // avoid triggering too many listeners
  446. // https://github.com/vuejs/pinia/issues/1129
  447. let activeListener;
  448. function $patch(partialStateOrMutator) {
  449. let subscriptionMutation;
  450. isListening = isSyncListening = false;
  451. // reset the debugger events since patches are sync
  452. /* istanbul ignore else */
  453. {
  454. debuggerEvents = [];
  455. }
  456. if (typeof partialStateOrMutator === 'function') {
  457. partialStateOrMutator(pinia.state.value[$id]);
  458. subscriptionMutation = {
  459. type: MutationType.patchFunction,
  460. storeId: $id,
  461. events: debuggerEvents,
  462. };
  463. }
  464. else {
  465. mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator);
  466. subscriptionMutation = {
  467. type: MutationType.patchObject,
  468. payload: partialStateOrMutator,
  469. storeId: $id,
  470. events: debuggerEvents,
  471. };
  472. }
  473. const myListenerId = (activeListener = Symbol());
  474. nextTick().then(() => {
  475. if (activeListener === myListenerId) {
  476. isListening = true;
  477. }
  478. });
  479. isSyncListening = true;
  480. // because we paused the watcher, we need to manually call the subscriptions
  481. triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id]);
  482. }
  483. const $reset = isOptionsStore
  484. ? function $reset() {
  485. const { state } = options;
  486. const newState = state ? state() : {};
  487. // we use a patch to group all changes into one single subscription
  488. this.$patch(($state) => {
  489. assign($state, newState);
  490. });
  491. }
  492. : /* istanbul ignore next */
  493. () => {
  494. throw new Error(`🍍: Store "${$id}" is built using the setup syntax and does not implement $reset().`);
  495. }
  496. ;
  497. function $dispose() {
  498. scope.stop();
  499. subscriptions = [];
  500. actionSubscriptions = [];
  501. pinia._s.delete($id);
  502. }
  503. /**
  504. * Wraps an action to handle subscriptions.
  505. *
  506. * @param name - name of the action
  507. * @param action - action to wrap
  508. * @returns a wrapped action to handle subscriptions
  509. */
  510. function wrapAction(name, action) {
  511. return function () {
  512. setActivePinia(pinia);
  513. const args = Array.from(arguments);
  514. const afterCallbackList = [];
  515. const onErrorCallbackList = [];
  516. function after(callback) {
  517. afterCallbackList.push(callback);
  518. }
  519. function onError(callback) {
  520. onErrorCallbackList.push(callback);
  521. }
  522. // @ts-expect-error
  523. triggerSubscriptions(actionSubscriptions, {
  524. args,
  525. name,
  526. store,
  527. after,
  528. onError,
  529. });
  530. let ret;
  531. try {
  532. ret = action.apply(this && this.$id === $id ? this : store, args);
  533. // handle sync errors
  534. }
  535. catch (error) {
  536. triggerSubscriptions(onErrorCallbackList, error);
  537. throw error;
  538. }
  539. if (ret instanceof Promise) {
  540. return ret
  541. .then((value) => {
  542. triggerSubscriptions(afterCallbackList, value);
  543. return value;
  544. })
  545. .catch((error) => {
  546. triggerSubscriptions(onErrorCallbackList, error);
  547. return Promise.reject(error);
  548. });
  549. }
  550. // trigger after callbacks
  551. triggerSubscriptions(afterCallbackList, ret);
  552. return ret;
  553. };
  554. }
  555. const _hmrPayload = /*#__PURE__*/ markRaw({
  556. actions: {},
  557. getters: {},
  558. state: [],
  559. hotState,
  560. });
  561. const partialStore = {
  562. _p: pinia,
  563. // _s: scope,
  564. $id,
  565. $onAction: addSubscription.bind(null, actionSubscriptions),
  566. $patch,
  567. $reset,
  568. $subscribe(callback, options = {}) {
  569. const removeSubscription = addSubscription(subscriptions, callback, options.detached, () => stopWatcher());
  570. const stopWatcher = scope.run(() => watch(() => pinia.state.value[$id], (state) => {
  571. if (options.flush === 'sync' ? isSyncListening : isListening) {
  572. callback({
  573. storeId: $id,
  574. type: MutationType.direct,
  575. events: debuggerEvents,
  576. }, state);
  577. }
  578. }, assign({}, $subscribeOptions, options)));
  579. return removeSubscription;
  580. },
  581. $dispose,
  582. };
  583. /* istanbul ignore if */
  584. if (isVue2) {
  585. // start as non ready
  586. partialStore._r = false;
  587. }
  588. const store = reactive(assign({
  589. _hmrPayload,
  590. _customProperties: markRaw(new Set()), // devtools custom properties
  591. }, partialStore
  592. // must be added later
  593. // setupStore
  594. )
  595. );
  596. // store the partial store now so the setup of stores can instantiate each other before they are finished without
  597. // creating infinite loops.
  598. pinia._s.set($id, store);
  599. const runWithContext = (pinia._a && pinia._a.runWithContext) || fallbackRunWithContext;
  600. // TODO: idea create skipSerialize that marks properties as non serializable and they are skipped
  601. const setupStore = runWithContext(() => pinia._e.run(() => (scope = effectScope()).run(setup)));
  602. // overwrite existing actions to support $onAction
  603. for (const key in setupStore) {
  604. const prop = setupStore[key];
  605. if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
  606. // mark it as a piece of state to be serialized
  607. if (hot) {
  608. set(hotState.value, key, toRef(setupStore, key));
  609. // createOptionStore directly sets the state in pinia.state.value so we
  610. // can just skip that
  611. }
  612. else if (!isOptionsStore) {
  613. // in setup stores we must hydrate the state and sync pinia state tree with the refs the user just created
  614. if (initialState && shouldHydrate(prop)) {
  615. if (isRef(prop)) {
  616. prop.value = initialState[key];
  617. }
  618. else {
  619. // probably a reactive object, lets recursively assign
  620. // @ts-expect-error: prop is unknown
  621. mergeReactiveObjects(prop, initialState[key]);
  622. }
  623. }
  624. // transfer the ref to the pinia state to keep everything in sync
  625. /* istanbul ignore if */
  626. if (isVue2) {
  627. set(pinia.state.value[$id], key, prop);
  628. }
  629. else {
  630. pinia.state.value[$id][key] = prop;
  631. }
  632. }
  633. /* istanbul ignore else */
  634. {
  635. _hmrPayload.state.push(key);
  636. }
  637. // action
  638. }
  639. else if (typeof prop === 'function') {
  640. // @ts-expect-error: we are overriding the function we avoid wrapping if
  641. const actionValue = hot ? prop : wrapAction(key, prop);
  642. // this a hot module replacement store because the hotUpdate method needs
  643. // to do it with the right context
  644. /* istanbul ignore if */
  645. if (isVue2) {
  646. set(setupStore, key, actionValue);
  647. }
  648. else {
  649. // @ts-expect-error
  650. setupStore[key] = actionValue;
  651. }
  652. /* istanbul ignore else */
  653. {
  654. _hmrPayload.actions[key] = prop;
  655. }
  656. // list actions so they can be used in plugins
  657. // @ts-expect-error
  658. optionsForPlugin.actions[key] = prop;
  659. }
  660. else {
  661. // add getters for devtools
  662. if (isComputed(prop)) {
  663. _hmrPayload.getters[key] = isOptionsStore
  664. ? // @ts-expect-error
  665. options.getters[key]
  666. : prop;
  667. if (IS_CLIENT) {
  668. const getters = setupStore._getters ||
  669. // @ts-expect-error: same
  670. (setupStore._getters = markRaw([]));
  671. getters.push(key);
  672. }
  673. }
  674. }
  675. }
  676. // add the state, getters, and action properties
  677. /* istanbul ignore if */
  678. if (isVue2) {
  679. Object.keys(setupStore).forEach((key) => {
  680. set(store, key, setupStore[key]);
  681. });
  682. }
  683. else {
  684. assign(store, setupStore);
  685. // allows retrieving reactive objects with `storeToRefs()`. Must be called after assigning to the reactive object.
  686. // Make `storeToRefs()` work with `reactive()` #799
  687. assign(toRaw(store), setupStore);
  688. }
  689. // use this instead of a computed with setter to be able to create it anywhere
  690. // without linking the computed lifespan to wherever the store is first
  691. // created.
  692. Object.defineProperty(store, '$state', {
  693. get: () => (hot ? hotState.value : pinia.state.value[$id]),
  694. set: (state) => {
  695. /* istanbul ignore if */
  696. if (hot) {
  697. throw new Error('cannot set hotState');
  698. }
  699. $patch(($state) => {
  700. assign($state, state);
  701. });
  702. },
  703. });
  704. // add the hotUpdate before plugins to allow them to override it
  705. /* istanbul ignore else */
  706. {
  707. store._hotUpdate = markRaw((newStore) => {
  708. store._hotUpdating = true;
  709. newStore._hmrPayload.state.forEach((stateKey) => {
  710. if (stateKey in store.$state) {
  711. const newStateTarget = newStore.$state[stateKey];
  712. const oldStateSource = store.$state[stateKey];
  713. if (typeof newStateTarget === 'object' &&
  714. isPlainObject(newStateTarget) &&
  715. isPlainObject(oldStateSource)) {
  716. patchObject(newStateTarget, oldStateSource);
  717. }
  718. else {
  719. // transfer the ref
  720. newStore.$state[stateKey] = oldStateSource;
  721. }
  722. }
  723. // patch direct access properties to allow store.stateProperty to work as
  724. // store.$state.stateProperty
  725. set(store, stateKey, toRef(newStore.$state, stateKey));
  726. });
  727. // remove deleted state properties
  728. Object.keys(store.$state).forEach((stateKey) => {
  729. if (!(stateKey in newStore.$state)) {
  730. del(store, stateKey);
  731. }
  732. });
  733. // avoid devtools logging this as a mutation
  734. isListening = false;
  735. isSyncListening = false;
  736. pinia.state.value[$id] = toRef(newStore._hmrPayload, 'hotState');
  737. isSyncListening = true;
  738. nextTick().then(() => {
  739. isListening = true;
  740. });
  741. for (const actionName in newStore._hmrPayload.actions) {
  742. const action = newStore[actionName];
  743. set(store, actionName, wrapAction(actionName, action));
  744. }
  745. // TODO: does this work in both setup and option store?
  746. for (const getterName in newStore._hmrPayload.getters) {
  747. const getter = newStore._hmrPayload.getters[getterName];
  748. const getterValue = isOptionsStore
  749. ? // special handling of options api
  750. computed(() => {
  751. setActivePinia(pinia);
  752. return getter.call(store, store);
  753. })
  754. : getter;
  755. set(store, getterName, getterValue);
  756. }
  757. // remove deleted getters
  758. Object.keys(store._hmrPayload.getters).forEach((key) => {
  759. if (!(key in newStore._hmrPayload.getters)) {
  760. del(store, key);
  761. }
  762. });
  763. // remove old actions
  764. Object.keys(store._hmrPayload.actions).forEach((key) => {
  765. if (!(key in newStore._hmrPayload.actions)) {
  766. del(store, key);
  767. }
  768. });
  769. // update the values used in devtools and to allow deleting new properties later on
  770. store._hmrPayload = newStore._hmrPayload;
  771. store._getters = newStore._getters;
  772. store._hotUpdating = false;
  773. });
  774. }
  775.  
  776. const nonEnumerable = {
  777. writable: true,
  778. configurable: true,
  779. // avoid warning on devtools trying to display this property
  780. enumerable: false,
  781. };
  782.  
  783. ['_p', '_hmrPayload', '_getters', '_customProperties'].forEach((p) => {
  784. Object.defineProperty(store, p, assign({ value: store[p] }, nonEnumerable));
  785. });
  786.  
  787. /* istanbul ignore if */
  788. if (isVue2) {
  789. // mark the store as ready before plugins
  790. store._r = true;
  791. }
  792. // apply all plugins
  793. pinia._p.forEach((extender) => {
  794. /* istanbul ignore else */
  795. const extensions = scope.run(() => extender({
  796. store,
  797. app: pinia._a,
  798. pinia,
  799. options: optionsForPlugin,
  800. }));
  801. Object.keys(extensions || {}).forEach((key) => store._customProperties.add(key));
  802. assign(store, extensions);
  803. });
  804.  
  805. if (store.$state &&
  806. typeof store.$state === 'object' &&
  807. typeof store.$state.constructor === 'function' &&
  808. !store.$state.constructor.toString().includes('[native code]')) {
  809. console.warn(`[🍍]: The "state" must be a plain object. It cannot be\n` +
  810. `\tstate: () => new MyClass()\n` +
  811. `Found in store "${store.$id}".`);
  812. }
  813. // only apply hydrate to option stores with an initial state in pinia
  814. if (initialState &&
  815. isOptionsStore &&
  816. options.hydrate) {
  817. options.hydrate(store.$state, initialState);
  818. }
  819. isListening = true;
  820. isSyncListening = true;
  821. return store;
  822. }
  823. function defineStore(
  824. // TODO: add proper types from above
  825. idOrOptions, setup, setupOptions) {
  826. let id;
  827. let options;
  828. const isSetupStore = typeof setup === 'function';
  829. if (typeof idOrOptions === 'string') {
  830. id = idOrOptions;
  831. // the option store setup will contain the actual options in this case
  832. options = isSetupStore ? setupOptions : setup;
  833. }
  834. else {
  835. options = idOrOptions;
  836. id = idOrOptions.id;
  837. if (typeof id !== 'string') {
  838. throw new Error(`[🍍]: "defineStore()" must be passed a store id as its first argument.`);
  839. }
  840. }
  841. function useStore(pinia, hot) {
  842. const hasContext = hasInjectionContext();
  843. pinia =
  844. // in test mode, ignore the argument provided as we can always retrieve a
  845. // pinia instance with getActivePinia()
  846. (pinia) ||
  847. (hasContext ? inject(piniaSymbol, null) : null);
  848. if (pinia)
  849. setActivePinia(pinia);
  850. if (!activePinia) {
  851. throw new Error(`[🍍]: "getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"?\n` +
  852. `See https://pinia.vuejs.org/core-concepts/outside-component-usage.html for help.\n` +
  853. `This will fail in production.`);
  854. }
  855. pinia = activePinia;
  856. if (!pinia._s.has(id)) {
  857. // creating the store registers it in `pinia._s`
  858. if (isSetupStore) {
  859. createSetupStore(id, setup, options, pinia);
  860. }
  861. else {
  862. createOptionsStore(id, options, pinia);
  863. }
  864. /* istanbul ignore else */
  865. {
  866. // @ts-expect-error: not the right inferred type
  867. useStore._pinia = pinia;
  868. }
  869. }
  870. const store = pinia._s.get(id);
  871. if (hot) {
  872. const hotId = '__hot:' + id;
  873. const newStore = isSetupStore
  874. ? createSetupStore(hotId, setup, options, pinia, true)
  875. : createOptionsStore(hotId, assign({}, options), pinia, true);
  876. hot._hotUpdate(newStore);
  877. // cleanup the state properties and the store from the cache
  878. delete pinia.state.value[hotId];
  879. pinia._s.delete(hotId);
  880. }
  881. if (IS_CLIENT) {
  882. const currentInstance = getCurrentInstance();
  883. // save stores in instances to access them devtools
  884. if (currentInstance &&
  885. currentInstance.proxy &&
  886. // avoid adding stores that are just built for hot module replacement
  887. !hot) {
  888. const vm = currentInstance.proxy;
  889. const cache = '_pStores' in vm ? vm._pStores : (vm._pStores = {});
  890. cache[id] = store;
  891. }
  892. }
  893. // StoreGeneric cannot be casted towards Store
  894. return store;
  895. }
  896. useStore.$id = id;
  897. return useStore;
  898. }
  899.  
  900. let mapStoreSuffix = 'Store';
  901. /**
  902. * Changes the suffix added by `mapStores()`. Can be set to an empty string.
  903. * Defaults to `"Store"`. Make sure to extend the MapStoresCustomization
  904. * interface if you are using TypeScript.
  905. *
  906. * @param suffix - new suffix
  907. */
  908. function setMapStoreSuffix(suffix // could be 'Store' but that would be annoying for JS
  909. ) {
  910. mapStoreSuffix = suffix;
  911. }
  912. /**
  913. * Allows using stores without the composition API (`setup()`) by generating an
  914. * object to be spread in the `computed` field of a component. It accepts a list
  915. * of store definitions.
  916. *
  917. * @example
  918. * ```js
  919. * export default {
  920. * computed: {
  921. * // other computed properties
  922. * ...mapStores(useUserStore, useCartStore)
  923. * },
  924. *
  925. * created() {
  926. * this.userStore // store with id "user"
  927. * this.cartStore // store with id "cart"
  928. * }
  929. * }
  930. * ```
  931. *
  932. * @param stores - list of stores to map to an object
  933. */
  934. function mapStores(...stores) {
  935. if (Array.isArray(stores[0])) {
  936. console.warn(`[🍍]: Directly pass all stores to "mapStores()" without putting them in an array:\n` +
  937. `Replace\n` +
  938. `\tmapStores([useAuthStore, useCartStore])\n` +
  939. `with\n` +
  940. `\tmapStores(useAuthStore, useCartStore)\n` +
  941. `This will fail in production if not fixed.`);
  942. stores = stores[0];
  943. }
  944. return stores.reduce((reduced, useStore) => {
  945. // @ts-expect-error: $id is added by defineStore
  946. reduced[useStore.$id + mapStoreSuffix] = function () {
  947. return useStore(this.$pinia);
  948. };
  949. return reduced;
  950. }, {});
  951. }
  952. /**
  953. * Allows using state and getters from one store without using the composition
  954. * API (`setup()`) by generating an object to be spread in the `computed` field
  955. * of a component.
  956. *
  957. * @param useStore - store to map from
  958. * @param keysOrMapper - array or object
  959. */
  960. function mapState(useStore, keysOrMapper) {
  961. return Array.isArray(keysOrMapper)
  962. ? keysOrMapper.reduce((reduced, key) => {
  963. reduced[key] = function () {
  964. return useStore(this.$pinia)[key];
  965. };
  966. return reduced;
  967. }, {})
  968. : Object.keys(keysOrMapper).reduce((reduced, key) => {
  969. // @ts-expect-error
  970. reduced[key] = function () {
  971. const store = useStore(this.$pinia);
  972. const storeKey = keysOrMapper[key];
  973. // for some reason TS is unable to infer the type of storeKey to be a
  974. // function
  975. return typeof storeKey === 'function'
  976. ? storeKey.call(this, store)
  977. : store[storeKey];
  978. };
  979. return reduced;
  980. }, {});
  981. }
  982. /**
  983. * Alias for `mapState()`. You should use `mapState()` instead.
  984. * @deprecated use `mapState()` instead.
  985. */
  986. const mapGetters = mapState;
  987. /**
  988. * Allows directly using actions from your store without using the composition
  989. * API (`setup()`) by generating an object to be spread in the `methods` field
  990. * of a component.
  991. *
  992. * @param useStore - store to map from
  993. * @param keysOrMapper - array or object
  994. */
  995. function mapActions(useStore, keysOrMapper) {
  996. return Array.isArray(keysOrMapper)
  997. ? keysOrMapper.reduce((reduced, key) => {
  998. // @ts-expect-error
  999. reduced[key] = function (...args) {
  1000. return useStore(this.$pinia)[key](...args);
  1001. };
  1002. return reduced;
  1003. }, {})
  1004. : Object.keys(keysOrMapper).reduce((reduced, key) => {
  1005. // @ts-expect-error
  1006. reduced[key] = function (...args) {
  1007. return useStore(this.$pinia)[keysOrMapper[key]](...args);
  1008. };
  1009. return reduced;
  1010. }, {});
  1011. }
  1012. /**
  1013. * Allows using state and getters from one store without using the composition
  1014. * API (`setup()`) by generating an object to be spread in the `computed` field
  1015. * of a component.
  1016. *
  1017. * @param useStore - store to map from
  1018. * @param keysOrMapper - array or object
  1019. */
  1020. function mapWritableState(useStore, keysOrMapper) {
  1021. return Array.isArray(keysOrMapper)
  1022. ? keysOrMapper.reduce((reduced, key) => {
  1023. // @ts-ignore
  1024. reduced[key] = {
  1025. get() {
  1026. return useStore(this.$pinia)[key];
  1027. },
  1028. set(value) {
  1029. // it's easier to type it here as any
  1030. return (useStore(this.$pinia)[key] = value);
  1031. },
  1032. };
  1033. return reduced;
  1034. }, {})
  1035. : Object.keys(keysOrMapper).reduce((reduced, key) => {
  1036. // @ts-ignore
  1037. reduced[key] = {
  1038. get() {
  1039. return useStore(this.$pinia)[keysOrMapper[key]];
  1040. },
  1041. set(value) {
  1042. // it's easier to type it here as any
  1043. return (useStore(this.$pinia)[keysOrMapper[key]] = value);
  1044. },
  1045. };
  1046. return reduced;
  1047. }, {});
  1048. }
  1049.  
  1050. /**
  1051. * Creates an object of references with all the state, getters, and plugin-added
  1052. * state properties of the store. Similar to `toRefs()` but specifically
  1053. * designed for Pinia stores so methods and non reactive properties are
  1054. * completely ignored.
  1055. *
  1056. * @param store - store to extract the refs from
  1057. */
  1058. function storeToRefs(store) {
  1059. // See https://github.com/vuejs/pinia/issues/852
  1060. // It's easier to just use toRefs() even if it includes more stuff
  1061. if (isVue2) {
  1062. // @ts-expect-error: toRefs include methods and others
  1063. return toRefs(store);
  1064. }
  1065. else {
  1066. store = toRaw(store);
  1067. const refs = {};
  1068. for (const key in store) {
  1069. const value = store[key];
  1070. if (isRef(value) || isReactive(value)) {
  1071. // @ts-expect-error: the key is state or getter
  1072. refs[key] =
  1073. // ---
  1074. toRef(store, key);
  1075. }
  1076. }
  1077. return refs;
  1078. }
  1079. }
  1080.  
  1081. /**
  1082. * Vue 2 Plugin that must be installed for pinia to work. Note **you don't need
  1083. * this plugin if you are using Nuxt.js**. Use the `buildModule` instead:
  1084. * https://pinia.vuejs.org/ssr/nuxt.html.
  1085. *
  1086. * @example
  1087. * ```js
  1088. * import Vue from 'vue'
  1089. * import { PiniaVuePlugin, createPinia } from 'pinia'
  1090. *
  1091. * Vue.use(PiniaVuePlugin)
  1092. * const pinia = createPinia()
  1093. *
  1094. * new Vue({
  1095. * el: '#app',
  1096. * // ...
  1097. * pinia,
  1098. * })
  1099. * ```
  1100. *
  1101. * @param _Vue - `Vue` imported from 'vue'.
  1102. */
  1103. const PiniaVuePlugin = function (_Vue) {
  1104. // Equivalent of
  1105. // app.config.globalProperties.$pinia = pinia
  1106. _Vue.mixin({
  1107. beforeCreate() {
  1108. const options = this.$options;
  1109. if (options.pinia) {
  1110. const pinia = options.pinia;
  1111. // HACK: taken from provide(): https://github.com/vuejs/composition-api/blob/main/src/apis/inject.ts#L31
  1112. /* istanbul ignore else */
  1113. if (!this._provided) {
  1114. const provideCache = {};
  1115. Object.defineProperty(this, '_provided', {
  1116. get: () => provideCache,
  1117. set: (v) => Object.assign(provideCache, v),
  1118. });
  1119. }
  1120. this._provided[piniaSymbol] = pinia;
  1121. // propagate the pinia instance in an SSR friendly way
  1122. // avoid adding it to nuxt twice
  1123. /* istanbul ignore else */
  1124. if (!this.$pinia) {
  1125. this.$pinia = pinia;
  1126. }
  1127. pinia._a = this;
  1128. if (IS_CLIENT) {
  1129. // this allows calling useStore() outside of a component setup after
  1130. // installing pinia's plugin
  1131. setActivePinia(pinia);
  1132. }
  1133. }
  1134. else if (!this.$pinia && options.parent && options.parent.$pinia) {
  1135. this.$pinia = options.parent.$pinia;
  1136. }
  1137. },
  1138. destroyed() {
  1139. delete this._pStores;
  1140. },
  1141. });
  1142. };
  1143.  
  1144. export { MutationType, PiniaVuePlugin, acceptHMRUpdate, createPinia, defineStore, getActivePinia, mapActions, mapGetters, mapState, mapStores, mapWritableState, setActivePinia, setMapStoreSuffix, skipHydrate, storeToRefs };
Advertisement
Add Comment
Please, Sign In to add comment