pinia-react 2.1.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,106 @@
1
+ import { Draft, Patch } from 'immer';
2
+
3
+ type StateTree = Record<string, any>;
4
+ type StatePath = string[];
5
+ type MutationType = 'action' | 'patch' | 'reset' | 'restore';
6
+ /** Metadata describing why a state transition was committed. */
7
+ interface MutationMeta {
8
+ type: MutationType;
9
+ origin?: string;
10
+ action?: string;
11
+ args?: unknown[];
12
+ }
13
+ /** A committed state transition, intended for Pinia plugins. */
14
+ interface MutationEvent<S extends StateTree = StateTree> {
15
+ storeId: string;
16
+ store: StoreGeneric;
17
+ state: S;
18
+ prevState: S;
19
+ patches: Patch[];
20
+ meta: MutationMeta;
21
+ }
22
+ type MutationListener = (event: MutationEvent) => void;
23
+ interface RestoreStateOptions {
24
+ type?: Extract<MutationType, 'restore' | 'reset'>;
25
+ origin?: string;
26
+ }
27
+ type TransformGetters<G> = {
28
+ [K in keyof G]: G[K] extends (...args: any[]) => infer R ? R : never;
29
+ };
30
+ type TransformActions<A> = A;
31
+ type SubscriptionCallback<S> = (state: S, prevState: S) => void;
32
+ type DeepReadonly<T> = T extends (...args: any[]) => any ? T : T extends readonly (infer U)[] ? ReadonlyArray<DeepReadonly<U>> : T extends object ? {
33
+ readonly [K in keyof T]: DeepReadonly<T[K]>;
34
+ } : T;
35
+ interface PiniaCustomProperties<Id extends string = string, S extends StateTree = StateTree, G extends Record<string, any> = Record<string, any>, A extends Record<string, any> = Record<string, any>> {
36
+ }
37
+ interface StorePublicApi<Id extends string = string, S = StateTree> {
38
+ readonly $id: Id;
39
+ $patch: (updater: (draft: Draft<S>) => void) => void;
40
+ $reset: () => void;
41
+ $subscribe: (callback: SubscriptionCallback<S>) => () => void;
42
+ $state: DeepReadonly<S>;
43
+ }
44
+ type GetterContext<S, G> = Readonly<S> & TransformGetters<G>;
45
+ type ActionContext<S, G, A> = S & TransformGetters<G> & TransformActions<A> & StorePublicApi<string, S>;
46
+ type Store<Id extends string, S extends StateTree, G extends Record<string, any>, A extends Record<string, any>> = S & TransformGetters<G> & TransformActions<A> & StorePublicApi<Id, S> & PiniaCustomProperties<Id, S, G, A>;
47
+ type StoreGeneric = Store<string, StateTree, Record<string, any>, Record<string, any>>;
48
+ type GettersImplementation<S> = {
49
+ [K in string]: (state: S) => any;
50
+ };
51
+ /** Extension point for store-option plugins. */
52
+ interface DefineStoreOptionsBase<S extends StateTree, Store> {
53
+ }
54
+ interface DefineStoreOptions<S extends StateTree, G extends Record<string, any>, A extends Record<string, any>> extends DefineStoreOptionsBase<S, Store<string, S, G, A>> {
55
+ state: () => S;
56
+ getters?: G & ThisType<GetterContext<S, G>> & GettersImplementation<S>;
57
+ actions?: A & ThisType<ActionContext<S, G, A>>;
58
+ }
59
+ type StoreScope = {
60
+ currentState: StateTree;
61
+ listeners: Set<(state: any, prev: any, patches: Patch[]) => void>;
62
+ getterResultCache: Map<string, any>;
63
+ getterDependencies: Map<string, Set<StatePath>>;
64
+ subscribers: Map<string, Set<string>>;
65
+ createStoreProxy: (onAccess?: (path: StatePath) => void) => StoreGeneric;
66
+ };
67
+ interface Pinia {
68
+ state: Record<string, StateTree>;
69
+ use(plugin: PiniaPlugin): Pinia;
70
+ /** Subscribe to committed mutations from every store in this Pinia instance. */
71
+ onMutation(listener: MutationListener): () => void;
72
+ _p: PiniaPlugin[];
73
+ _s: Map<string, StoreGeneric>;
74
+ _scopes: Map<string, StoreScope>;
75
+ _m: Set<MutationListener>;
76
+ }
77
+ interface PiniaPlugin {
78
+ (context: PiniaPluginContext): Partial<PiniaCustomProperties> | void;
79
+ }
80
+ type PiniaPluginContext<Id extends string = string, S extends StateTree = StateTree, G extends Record<string, any> = Record<string, any>, A extends Record<string, any> = Record<string, any>> = {
81
+ id: Id;
82
+ store: Store<Id, S, G, A>;
83
+ options: DefineStoreOptions<S, G, A>;
84
+ pinia: Pinia;
85
+ /** A plugin-only, fully controlled state replacement operation. */
86
+ restoreState: (state: S, options?: RestoreStateOptions) => void;
87
+ };
88
+ interface StoreDefinition<Id extends string, S extends StateTree, G extends Record<string, any>, A extends Record<string, any>> {
89
+ useStore: () => Store<Id, S, G, A>;
90
+ getStore: () => Store<Id, S, G, A>;
91
+ }
92
+ type NamedStoreDefinition<Id extends string, S extends StateTree, G extends Record<string, any>, A extends Record<string, any>> = string extends Id ? {} : {
93
+ [K in `use${Capitalize<Id>}Store`]: () => Store<Id, S, G, A>;
94
+ } & {
95
+ [K in `get${Capitalize<Id>}Store`]: () => Store<Id, S, G, A>;
96
+ };
97
+ type StoreDefinitionWithNames<Id extends string, S extends StateTree, G extends Record<string, any>, A extends Record<string, any>> = StoreDefinition<Id, S, G, A> & NamedStoreDefinition<Id, S, G, A>;
98
+
99
+ declare function createPinia(): Pinia;
100
+
101
+ declare function setActivePinia(pinia: Pinia): void;
102
+ declare function getActivePinia(): Pinia;
103
+
104
+ declare function defineStore<Id extends string, S extends StateTree, G extends Record<string, any> = {}, A extends Record<string, any> = {}>(id: Id, options: DefineStoreOptions<S, G, A>): StoreDefinitionWithNames<Id, S, G, A>;
105
+
106
+ export { type DeepReadonly, type DefineStoreOptions, type DefineStoreOptionsBase, type MutationEvent, type MutationListener, type MutationMeta, type MutationType, type Pinia, type PiniaCustomProperties, type PiniaPlugin, type PiniaPluginContext, type RestoreStateOptions, type StatePath, type StateTree, type Store, type StoreDefinition, type StoreGeneric, type SubscriptionCallback, createPinia, defineStore, getActivePinia, setActivePinia };
package/dist/index.d.ts CHANGED
@@ -1,27 +1,57 @@
1
1
  import { Draft, Patch } from 'immer';
2
2
 
3
3
  type StateTree = Record<string, any>;
4
+ type StatePath = string[];
5
+ type MutationType = 'action' | 'patch' | 'reset' | 'restore';
6
+ /** Metadata describing why a state transition was committed. */
7
+ interface MutationMeta {
8
+ type: MutationType;
9
+ origin?: string;
10
+ action?: string;
11
+ args?: unknown[];
12
+ }
13
+ /** A committed state transition, intended for Pinia plugins. */
14
+ interface MutationEvent<S extends StateTree = StateTree> {
15
+ storeId: string;
16
+ store: StoreGeneric;
17
+ state: S;
18
+ prevState: S;
19
+ patches: Patch[];
20
+ meta: MutationMeta;
21
+ }
22
+ type MutationListener = (event: MutationEvent) => void;
23
+ interface RestoreStateOptions {
24
+ type?: Extract<MutationType, 'restore' | 'reset'>;
25
+ origin?: string;
26
+ }
4
27
  type TransformGetters<G> = {
5
28
  [K in keyof G]: G[K] extends (...args: any[]) => infer R ? R : never;
6
29
  };
7
30
  type TransformActions<A> = A;
8
31
  type SubscriptionCallback<S> = (state: S, prevState: S) => void;
32
+ type DeepReadonly<T> = T extends (...args: any[]) => any ? T : T extends readonly (infer U)[] ? ReadonlyArray<DeepReadonly<U>> : T extends object ? {
33
+ readonly [K in keyof T]: DeepReadonly<T[K]>;
34
+ } : T;
9
35
  interface PiniaCustomProperties<Id extends string = string, S extends StateTree = StateTree, G extends Record<string, any> = Record<string, any>, A extends Record<string, any> = Record<string, any>> {
10
36
  }
11
- interface StorePublicApi<S> {
37
+ interface StorePublicApi<Id extends string = string, S = StateTree> {
38
+ readonly $id: Id;
12
39
  $patch: (updater: (draft: Draft<S>) => void) => void;
13
40
  $reset: () => void;
14
41
  $subscribe: (callback: SubscriptionCallback<S>) => () => void;
15
- $state: S;
42
+ $state: DeepReadonly<S>;
16
43
  }
17
44
  type GetterContext<S, G> = Readonly<S> & TransformGetters<G>;
18
- type ActionContext<S, G, A> = S & TransformGetters<G> & TransformActions<A> & StorePublicApi<S>;
19
- type Store<Id extends string, S extends StateTree, G extends Record<string, any>, A extends Record<string, any>> = S & TransformGetters<G> & TransformActions<A> & StorePublicApi<S> & PiniaCustomProperties<Id, S, G, A>;
45
+ type ActionContext<S, G, A> = S & TransformGetters<G> & TransformActions<A> & StorePublicApi<string, S>;
46
+ type Store<Id extends string, S extends StateTree, G extends Record<string, any>, A extends Record<string, any>> = S & TransformGetters<G> & TransformActions<A> & StorePublicApi<Id, S> & PiniaCustomProperties<Id, S, G, A>;
20
47
  type StoreGeneric = Store<string, StateTree, Record<string, any>, Record<string, any>>;
21
48
  type GettersImplementation<S> = {
22
49
  [K in string]: (state: S) => any;
23
50
  };
24
- interface DefineStoreOptions<S extends StateTree, G extends Record<string, any>, A extends Record<string, any>> {
51
+ /** Extension point for store-option plugins. */
52
+ interface DefineStoreOptionsBase<S extends StateTree, Store> {
53
+ }
54
+ interface DefineStoreOptions<S extends StateTree, G extends Record<string, any>, A extends Record<string, any>> extends DefineStoreOptionsBase<S, Store<string, S, G, A>> {
25
55
  state: () => S;
26
56
  getters?: G & ThisType<GetterContext<S, G>> & GettersImplementation<S>;
27
57
  actions?: A & ThisType<ActionContext<S, G, A>>;
@@ -30,16 +60,19 @@ type StoreScope = {
30
60
  currentState: StateTree;
31
61
  listeners: Set<(state: any, prev: any, patches: Patch[]) => void>;
32
62
  getterResultCache: Map<string, any>;
33
- getterDependencies: Map<string, Set<string>>;
63
+ getterDependencies: Map<string, Set<StatePath>>;
34
64
  subscribers: Map<string, Set<string>>;
35
- createStoreProxy: (onAccess?: (path: string[]) => void) => StoreGeneric;
65
+ createStoreProxy: (onAccess?: (path: StatePath) => void) => StoreGeneric;
36
66
  };
37
67
  interface Pinia {
38
68
  state: Record<string, StateTree>;
39
69
  use(plugin: PiniaPlugin): Pinia;
70
+ /** Subscribe to committed mutations from every store in this Pinia instance. */
71
+ onMutation(listener: MutationListener): () => void;
40
72
  _p: PiniaPlugin[];
41
73
  _s: Map<string, StoreGeneric>;
42
74
  _scopes: Map<string, StoreScope>;
75
+ _m: Set<MutationListener>;
43
76
  }
44
77
  interface PiniaPlugin {
45
78
  (context: PiniaPluginContext): Partial<PiniaCustomProperties> | void;
@@ -48,6 +81,9 @@ type PiniaPluginContext<Id extends string = string, S extends StateTree = StateT
48
81
  id: Id;
49
82
  store: Store<Id, S, G, A>;
50
83
  options: DefineStoreOptions<S, G, A>;
84
+ pinia: Pinia;
85
+ /** A plugin-only, fully controlled state replacement operation. */
86
+ restoreState: (state: S, options?: RestoreStateOptions) => void;
51
87
  };
52
88
  interface StoreDefinition<Id extends string, S extends StateTree, G extends Record<string, any>, A extends Record<string, any>> {
53
89
  useStore: () => Store<Id, S, G, A>;
@@ -67,4 +103,4 @@ declare function getActivePinia(): Pinia;
67
103
 
68
104
  declare function defineStore<Id extends string, S extends StateTree, G extends Record<string, any> = {}, A extends Record<string, any> = {}>(id: Id, options: DefineStoreOptions<S, G, A>): StoreDefinitionWithNames<Id, S, G, A>;
69
105
 
70
- export { type DefineStoreOptions, type Pinia, type PiniaCustomProperties, type PiniaPlugin, type PiniaPluginContext, type StateTree, type Store, type StoreDefinition, type StoreGeneric, type SubscriptionCallback, createPinia, defineStore, getActivePinia, setActivePinia };
106
+ export { type DeepReadonly, type DefineStoreOptions, type DefineStoreOptionsBase, type MutationEvent, type MutationListener, type MutationMeta, type MutationType, type Pinia, type PiniaCustomProperties, type PiniaPlugin, type PiniaPluginContext, type RestoreStateOptions, type StatePath, type StateTree, type Store, type StoreDefinition, type StoreGeneric, type SubscriptionCallback, createPinia, defineStore, getActivePinia, setActivePinia };
package/dist/index.js CHANGED
@@ -18,14 +18,25 @@ function createPinia() {
18
18
  const _p = [];
19
19
  const _s = /* @__PURE__ */ new Map();
20
20
  const _scopes = /* @__PURE__ */ new Map();
21
+ const _m = /* @__PURE__ */ new Set();
21
22
  const pinia = {
22
23
  use(plugin) {
24
+ if (_s.size > 0) {
25
+ console.warn(
26
+ "[pinia-react] A plugin was registered after stores were created. The new plugin will not be applied to the stores that already exist."
27
+ );
28
+ }
23
29
  _p.push(plugin);
24
30
  return this;
25
31
  },
32
+ onMutation(listener) {
33
+ _m.add(listener);
34
+ return () => _m.delete(listener);
35
+ },
26
36
  _p,
27
37
  _s,
28
38
  _scopes,
39
+ _m,
29
40
  state
30
41
  };
31
42
  setActivePinia(pinia);
@@ -33,15 +44,16 @@ function createPinia() {
33
44
  }
34
45
 
35
46
  // src/store.ts
36
- import { enablePatches, produce, setAutoFreeze } from "immer";
47
+ import { enablePatches, Immer } from "immer";
37
48
  import { useCallback, useRef, useSyncExternalStore } from "react";
38
49
  enablePatches();
39
- setAutoFreeze(false);
50
+ var immer = new Immer({ autoFreeze: false });
40
51
  var activeListenerId = null;
41
52
  var activeGetterKey = null;
53
+ var storeDefinitionByPinia = /* @__PURE__ */ new WeakMap();
42
54
  function isAffected(patches, trackedPaths) {
43
55
  if (trackedPaths.size === 0) return false;
44
- const tracked = Array.from(trackedPaths).map((p) => p.split("."));
56
+ const tracked = Array.from(trackedPaths);
45
57
  for (const patch of patches) {
46
58
  const patchPath = patch.path.map(String);
47
59
  for (const trackedPath of tracked) {
@@ -58,8 +70,23 @@ function isAffected(patches, trackedPaths) {
58
70
  }
59
71
  return false;
60
72
  }
73
+ function isPlainObjectOrArray(value) {
74
+ if (Array.isArray(value)) return true;
75
+ if (value === null || typeof value !== "object") return false;
76
+ const proto = Object.getPrototypeOf(value);
77
+ return proto === Object.prototype || proto === null;
78
+ }
61
79
  function defineStore(id, options) {
62
80
  const getters = options.getters || {};
81
+ function ensureStoreInstance(pinia) {
82
+ if (pinia._s.has(id)) {
83
+ if (storeDefinitionByPinia.get(pinia)?.get(id) === options) return;
84
+ console.warn(
85
+ `[pinia-react] Duplicate store id "${id}" detected. The new definition replaces the previous store with the same id.`
86
+ );
87
+ }
88
+ createStoreInstance();
89
+ }
63
90
  function resolveGetterDependencies(getterName, getterDepsMap, visited = /* @__PURE__ */ new Set()) {
64
91
  if (visited.has(getterName)) {
65
92
  console.warn(`[pinia-react] Circular dependency in getters detected involving: ${getterName}`);
@@ -70,8 +97,9 @@ function defineStore(id, options) {
70
97
  const directDeps = getterDepsMap.get(getterName);
71
98
  if (!directDeps) return finalDeps;
72
99
  for (const dep of directDeps) {
73
- if (dep in getters) {
74
- const nestedDeps = resolveGetterDependencies(dep, getterDepsMap, visited);
100
+ const getterKey = dep.length === 1 ? dep[0] : void 0;
101
+ if (getterKey && getterKey in getters) {
102
+ const nestedDeps = resolveGetterDependencies(getterKey, getterDepsMap, visited);
75
103
  nestedDeps.forEach((d) => finalDeps.add(d));
76
104
  } else {
77
105
  finalDeps.add(dep);
@@ -83,8 +111,6 @@ function defineStore(id, options) {
83
111
  const pinia = getActivePinia();
84
112
  const initialState = options.state();
85
113
  let storePublicApi;
86
- let devTools;
87
- let isTimeTraveling = false;
88
114
  const localScope = {
89
115
  currentState: initialState,
90
116
  listeners: /* @__PURE__ */ new Set(),
@@ -94,6 +120,12 @@ function defineStore(id, options) {
94
120
  createStoreProxy: (_onAccess) => storePublicApi
95
121
  };
96
122
  pinia._scopes.set(id, localScope);
123
+ let definitionsById = storeDefinitionByPinia.get(pinia);
124
+ if (!definitionsById) {
125
+ definitionsById = /* @__PURE__ */ new Map();
126
+ storeDefinitionByPinia.set(pinia, definitionsById);
127
+ }
128
+ definitionsById.set(id, options);
97
129
  const isGetterComputing = /* @__PURE__ */ new Set();
98
130
  const emit = (nextState, oldState, patches) => {
99
131
  localScope.listeners.forEach((fn) => fn(nextState, oldState, patches));
@@ -116,28 +148,42 @@ function defineStore(id, options) {
116
148
  }
117
149
  });
118
150
  };
119
- const internalPatch = (updater, actionName, isReset = false) => {
120
- if (isTimeTraveling) return;
151
+ const internalPatch = (updater, meta) => {
121
152
  const oldState = localScope.currentState;
122
153
  let patches = [];
123
- const nextState = produce(oldState, updater, (p) => {
154
+ const nextState = immer.produce(oldState, updater, (p) => {
124
155
  patches = p;
125
156
  });
126
- if (patches.length > 0 || isReset) {
157
+ if (patches.length > 0 || meta.type === "reset") {
127
158
  localScope.currentState = nextState;
128
159
  pinia.state[id] = nextState;
129
- if (devTools) {
130
- devTools.send({ type: actionName, payload: patches }, nextState);
131
- }
132
160
  emit(nextState, oldState, patches);
161
+ const event = {
162
+ storeId: id,
163
+ store: storePublicApi,
164
+ state: nextState,
165
+ prevState: oldState,
166
+ patches,
167
+ meta
168
+ };
169
+ pinia._m.forEach((listener) => listener(event));
133
170
  }
134
171
  };
135
172
  const $patch = (updater) => {
136
- internalPatch((draft) => {
137
- updater(draft);
138
- }, "@patch");
173
+ internalPatch(
174
+ (draft) => {
175
+ updater(draft);
176
+ },
177
+ { type: "patch" }
178
+ );
179
+ };
180
+ const $reset = () => internalPatch(() => options.state(), { type: "reset" });
181
+ const restoreState = (state, options2 = {}) => {
182
+ internalPatch(() => state, {
183
+ type: options2.type ?? "restore",
184
+ origin: options2.origin
185
+ });
139
186
  };
140
- const $reset = () => internalPatch(() => options.state(), "@reset", true);
141
187
  const $subscribe = (callback) => {
142
188
  const listener = (state, prev) => callback(state, prev);
143
189
  localScope.listeners.add(listener);
@@ -155,37 +201,92 @@ function defineStore(id, options) {
155
201
  const originalActions = options.actions || {};
156
202
  const wrappedActions = {};
157
203
  const proxyTarget = {};
204
+ const readonlyStateProxyCache = /* @__PURE__ */ new WeakMap();
205
+ const readonlyWarning = () => {
206
+ console.warn(`[${id}] Store is read-only. Use actions for mutations.`);
207
+ throw new TypeError(`[${id}] Store is read-only. Use actions for mutations.`);
208
+ };
209
+ const createReadonlyStateProxy = (stateTarget) => {
210
+ const cached = readonlyStateProxyCache.get(stateTarget);
211
+ if (cached) return cached;
212
+ const proxy = new Proxy(stateTarget, {
213
+ get(obj, key) {
214
+ const value = Reflect.get(obj, key);
215
+ if (isPlainObjectOrArray(value)) return createReadonlyStateProxy(value);
216
+ return value;
217
+ },
218
+ set: readonlyWarning,
219
+ deleteProperty: readonlyWarning
220
+ });
221
+ readonlyStateProxyCache.set(stateTarget, proxy);
222
+ return proxy;
223
+ };
224
+ const getAtPath = (path) => {
225
+ let value = localScope.currentState;
226
+ for (const key of path) value = value[key];
227
+ return value;
228
+ };
229
+ const setAtPath = (draft, path, value) => {
230
+ let target = draft;
231
+ for (let i = 0; i < path.length - 1; i++) target = target[path[i]];
232
+ target[path[path.length - 1]] = value;
233
+ };
234
+ const deleteAtPath = (draft, path) => {
235
+ let target = draft;
236
+ for (let i = 0; i < path.length - 1; i++) target = target[path[i]];
237
+ delete target[path[path.length - 1]];
238
+ };
239
+ const createActionStateProxy = (path, meta) => {
240
+ return new Proxy(Array.isArray(getAtPath(path)) ? [] : {}, {
241
+ get(_target, key, receiver) {
242
+ const current = getAtPath(path);
243
+ const value = Reflect.get(current, key, receiver);
244
+ if (typeof key === "symbol" || !isPlainObjectOrArray(value)) return value;
245
+ return createActionStateProxy([...path, String(key)], meta);
246
+ },
247
+ set(_target, key, value) {
248
+ internalPatch((draft) => setAtPath(draft, [...path, String(key)], value), meta);
249
+ return true;
250
+ },
251
+ deleteProperty(_target, key) {
252
+ internalPatch((draft) => deleteAtPath(draft, [...path, String(key)]), meta);
253
+ return true;
254
+ }
255
+ });
256
+ };
158
257
  function createStoreProxy(onAccess) {
159
- const readonlyWarning = () => {
160
- console.warn(`[${id}] Store is read-only. Use actions for mutations.`);
161
- return false;
162
- };
163
- const createStateProxy = (stateTarget, path, onDeepAccess) => {
258
+ const createStateProxy = (stateTarget, path, onDeepAccess, trackObjectAccess = false) => {
164
259
  return new Proxy(stateTarget, {
165
260
  get(obj, key) {
166
261
  if (typeof key === "symbol") return Reflect.get(obj, key);
167
262
  const currentPath = [...path, String(key)];
168
263
  const value = Reflect.get(obj, key);
169
- if (typeof value === "object" && value !== null) {
170
- return createStateProxy(value, currentPath, onDeepAccess);
264
+ if (isPlainObjectOrArray(value)) {
265
+ if (trackObjectAccess) onDeepAccess?.(currentPath);
266
+ return createStateProxy(value, currentPath, onDeepAccess, trackObjectAccess);
171
267
  }
172
268
  onDeepAccess?.(currentPath);
173
269
  return value;
174
270
  },
175
- set: readonlyWarning
271
+ set: readonlyWarning,
272
+ deleteProperty: readonlyWarning
176
273
  });
177
274
  };
178
275
  return new Proxy(proxyTarget, {
179
276
  get(_target, key, receiver) {
180
277
  const strKey = String(key);
181
- if (strKey === "$state") return localScope.currentState;
278
+ if (strKey === "$state") {
279
+ onAccess?.(["$state"]);
280
+ return createReadonlyStateProxy(localScope.currentState);
281
+ }
282
+ if (strKey === "$id") return id;
182
283
  if (strKey === "$patch") return $patch;
183
284
  if (strKey === "$reset") return $reset;
184
285
  if (strKey === "$subscribe") return $subscribe;
185
286
  const state = localScope.currentState;
186
287
  if (strKey in state) {
187
288
  const value = state[strKey];
188
- if (typeof value === "object" && value !== null) {
289
+ if (isPlainObjectOrArray(value)) {
189
290
  return createStateProxy(value, [strKey], onAccess);
190
291
  }
191
292
  onAccess?.([strKey]);
@@ -206,10 +307,10 @@ function defineStore(id, options) {
206
307
  activeGetterKey = strKey;
207
308
  try {
208
309
  const onGetterAccess = (path) => {
209
- dependencies.add(path[0]);
310
+ dependencies.add(path);
210
311
  };
211
312
  const trackingProxyForThis = createStoreProxy(onGetterAccess);
212
- const trackingStateProxy = createStateProxy(state, [], onGetterAccess);
313
+ const trackingStateProxy = createStateProxy(state, [], onGetterAccess, true);
213
314
  const result = getters[strKey].call(trackingProxyForThis, trackingStateProxy);
214
315
  localScope.getterDependencies.set(strKey, dependencies);
215
316
  localScope.getterResultCache.set(strKey, result);
@@ -231,6 +332,7 @@ function defineStore(id, options) {
231
332
  console.warn(`[${id}] Do not replace "$state" directly. Use "$patch()" to replace the whole state.`);
232
333
  return false;
233
334
  }
335
+ if (strKey === "$id") return readonlyWarning();
234
336
  if (strKey in localScope.currentState || strKey in getters || strKey in wrappedActions) {
235
337
  return readonlyWarning();
236
338
  }
@@ -243,92 +345,63 @@ function defineStore(id, options) {
243
345
  const originalAction = originalActions[actionName];
244
346
  wrappedActions[actionName] = (...args) => {
245
347
  let returnValue;
348
+ let draftActive = true;
349
+ const meta = { type: "action", action: actionName, args };
246
350
  const recipe = (draft) => {
247
351
  const actionContextProxy = new Proxy({}, {
248
352
  get(_, key) {
249
353
  const strKey = String(key);
250
- if (Reflect.has(draft, strKey)) return draft[strKey];
354
+ if (draftActive && Reflect.has(draft, strKey)) return draft[strKey];
355
+ if (!draftActive && Reflect.has(localScope.currentState, strKey)) {
356
+ const value = localScope.currentState[strKey];
357
+ if (value !== null && typeof value === "object") return createActionStateProxy([strKey], meta);
358
+ return value;
359
+ }
251
360
  if (strKey in getters) {
252
- return getters[strKey].call(actionContextProxy, draft);
361
+ if (draftActive) return getters[strKey].call(actionContextProxy, draft);
362
+ return Reflect.get(storePublicApi, key, storePublicApi);
253
363
  }
254
364
  return Reflect.get(storePublicApi, key, storePublicApi);
255
365
  },
256
366
  set(_, key, value) {
257
- ;
258
- draft[String(key)] = value;
367
+ const path = [String(key)];
368
+ if (draftActive) draft[path[0]] = value;
369
+ else internalPatch((currentDraft) => setAtPath(currentDraft, path, value), meta);
370
+ return true;
371
+ },
372
+ deleteProperty(_, key) {
373
+ const path = [String(key)];
374
+ if (draftActive) delete draft[path[0]];
375
+ else internalPatch((currentDraft) => deleteAtPath(currentDraft, path), meta);
259
376
  return true;
260
377
  }
261
378
  });
262
379
  returnValue = originalAction.apply(actionContextProxy, args);
263
380
  };
264
- internalPatch(recipe, actionName);
381
+ internalPatch(recipe, meta);
382
+ draftActive = false;
265
383
  return returnValue;
266
384
  };
267
385
  });
268
386
  localScope.createStoreProxy = createStoreProxy;
269
387
  pinia._p.forEach((plugin) => {
270
- const pluginResult = plugin({ id, store: storePublicApi, options });
388
+ const pluginResult = plugin({
389
+ id,
390
+ store: storePublicApi,
391
+ options,
392
+ pinia,
393
+ restoreState
394
+ });
271
395
  if (pluginResult) {
272
396
  Object.defineProperties(proxyTarget, Object.getOwnPropertyDescriptors(pluginResult));
273
397
  }
274
398
  });
275
399
  pinia._s.set(id, storePublicApi);
276
- if (typeof window !== "undefined" && window.__REDUX_DEVTOOLS_EXTENSION__) {
277
- devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name: id });
278
- devTools.init(localScope.currentState);
279
- devTools.subscribe((message) => {
280
- if (message.type === "DISPATCH") {
281
- const payloadType = message.payload?.type;
282
- switch (payloadType) {
283
- case "JUMP_TO_STATE":
284
- case "JUMP_TO_ACTION":
285
- case "IMPORT_STATE": {
286
- const newState = typeof message.state === "string" ? JSON.parse(message.state) : message.state;
287
- if (!newState || typeof newState !== "object") return;
288
- isTimeTraveling = true;
289
- const oldState = localScope.currentState;
290
- localScope.currentState = newState;
291
- pinia.state[id] = newState;
292
- localScope.getterResultCache.clear();
293
- emit(newState, oldState, []);
294
- isTimeTraveling = false;
295
- break;
296
- }
297
- case "COMMIT": {
298
- devTools.init(localScope.currentState);
299
- break;
300
- }
301
- case "ROLLBACK": {
302
- const newState = typeof message.state === "string" ? JSON.parse(message.state) : message.state;
303
- if (!newState || typeof newState !== "object") return;
304
- isTimeTraveling = true;
305
- const oldState = localScope.currentState;
306
- localScope.currentState = newState;
307
- pinia.state[id] = newState;
308
- localScope.getterResultCache.clear();
309
- emit(newState, oldState, []);
310
- isTimeTraveling = false;
311
- break;
312
- }
313
- case "RESET": {
314
- const originalState = options.state();
315
- devTools.init(originalState);
316
- internalPatch(() => originalState, "@reset", true);
317
- break;
318
- }
319
- default:
320
- break;
321
- }
322
- }
323
- });
324
- }
325
400
  return storePublicApi;
326
401
  }
327
402
  function getStore() {
328
403
  const pinia = getActivePinia();
329
- if (!pinia._s.has(id)) {
330
- createStoreInstance();
331
- }
404
+ ensureStoreInstance(pinia);
332
405
  if (activeListenerId && activeGetterKey && activeListenerId !== id) {
333
406
  const accessedStoreScope = pinia._scopes.get(id);
334
407
  if (accessedStoreScope) {
@@ -344,9 +417,7 @@ function defineStore(id, options) {
344
417
  }
345
418
  function useStore() {
346
419
  const pinia = getActivePinia();
347
- if (!pinia._s.has(id)) {
348
- createStoreInstance();
349
- }
420
+ ensureStoreInstance(pinia);
350
421
  const currentScope = pinia._scopes.get(id);
351
422
  const trackedPaths = useRef(/* @__PURE__ */ new Set());
352
423
  trackedPaths.current.clear();
@@ -355,8 +426,12 @@ function defineStore(id, options) {
355
426
  const listener = (_state, _prevState, patches) => {
356
427
  let shouldUpdate = false;
357
428
  for (const path of trackedPaths.current) {
358
- const topKey = path.split(".")[0];
359
- if (topKey in getters) {
429
+ if (path.length === 1 && path[0] === "$state") {
430
+ shouldUpdate = patches.length > 0;
431
+ if (shouldUpdate) break;
432
+ }
433
+ const topKey = path[0];
434
+ if (path.length === 1 && topKey in getters) {
360
435
  if (!currentScope.getterResultCache.has(topKey)) {
361
436
  shouldUpdate = true;
362
437
  break;
@@ -380,7 +455,7 @@ function defineStore(id, options) {
380
455
  const getSnapshot = useCallback(() => currentScope.currentState, [currentScope]);
381
456
  useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
382
457
  const trackingProxy = currentScope.createStoreProxy((path) => {
383
- trackedPaths.current.add(path.join("."));
458
+ trackedPaths.current.add(path);
384
459
  });
385
460
  return trackingProxy;
386
461
  }