coaction 2.0.0 → 2.1.0

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.
package/dist/index.mjs CHANGED
@@ -38,13 +38,129 @@ const isEqual = (x, y) => {
38
38
  };
39
39
  const isUnsafeKey = (key) => key === "__proto__" || key === "prototype" || key === "constructor";
40
40
  const isUnsafePathSegment = (segment) => typeof segment === "string" && isUnsafeKey(segment);
41
+ var UnsafePatchPathError = class extends Error {
42
+ name = "UnsafePatchPathError";
43
+ };
44
+ var StateSchemaError = class extends Error {
45
+ name = "StateSchemaError";
46
+ };
47
+ const isStateSchemaError = (error) => error instanceof StateSchemaError || error instanceof Error && error.name === "StateSchemaError";
41
48
  const hasUnsafePatchPath = (path) => {
42
49
  return (Array.isArray(path) ? path : typeof path === "string" ? path.split("/").filter(Boolean).map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")) : []).some(isUnsafePathSegment);
43
50
  };
44
- const sanitizePatches = (patches) => patches?.filter((patch) => !hasUnsafePatchPath(patch.path)).map((patch) => Object.prototype.hasOwnProperty.call(patch, "value") ? {
45
- ...patch,
46
- value: sanitizeReplacementState(patch.value)
47
- } : patch);
51
+ const formatPatchPath = (path) => Array.isArray(path) ? path.map((segment) => String(segment)).join(".") : String(path);
52
+ const getUnsafePatchPaths = (patches) => patches?.filter((patch) => hasUnsafePatchPath(patch.path)) ?? [];
53
+ const assertSafePatches = (patches, source = "patches") => {
54
+ const unsafePatches = getUnsafePatchPaths(patches);
55
+ if (!unsafePatches.length) return;
56
+ const paths = unsafePatches.map((patch) => `'${formatPatchPath(patch.path)}'`).join(", ");
57
+ throw new UnsafePatchPathError(`Unsafe patch path${unsafePatches.length > 1 ? "s" : ""} ${paths} cannot be applied from ${source}.`);
58
+ };
59
+ const warnDroppedUnsafePatches = (unsafePatches, source) => {
60
+ if (process.env.NODE_ENV !== "development" || !unsafePatches.length) return;
61
+ const paths = unsafePatches.map((patch) => `'${formatPatchPath(patch.path)}'`).join(", ");
62
+ console.warn(`Coaction dropped unsafe patch path${unsafePatches.length > 1 ? "s" : ""} ${paths} from ${source}.`);
63
+ };
64
+ const sanitizePatches = (patches, options = {}) => {
65
+ if (options.warnOnDropped) warnDroppedUnsafePatches(getUnsafePatchPaths(patches), options.source ?? "patches");
66
+ return patches?.filter((patch) => !hasUnsafePatchPath(patch.path)).map((patch) => Object.prototype.hasOwnProperty.call(patch, "value") ? {
67
+ ...patch,
68
+ value: sanitizeReplacementState(patch.value)
69
+ } : patch);
70
+ };
71
+ const sanitizeCheckedPatches = (patches, source) => {
72
+ assertSafePatches(patches, source);
73
+ return sanitizePatches(patches) ?? [];
74
+ };
75
+ const createRootReplacementPatches = (currentState, nextState) => {
76
+ const patches = [];
77
+ const inversePatches = [];
78
+ const nextKeys = new Set(getOwnEnumerableKeys(nextState));
79
+ for (const key of getOwnEnumerableKeys(currentState)) {
80
+ if (typeof key === "string" && isUnsafeKey(key)) continue;
81
+ if (nextKeys.has(key)) continue;
82
+ patches.push({
83
+ op: "remove",
84
+ path: [key]
85
+ });
86
+ inversePatches.push({
87
+ op: "add",
88
+ path: [key],
89
+ value: currentState[key]
90
+ });
91
+ }
92
+ for (const key of nextKeys) {
93
+ if (typeof key === "string" && isUnsafeKey(key)) continue;
94
+ if (!Object.prototype.hasOwnProperty.call(currentState, key)) {
95
+ patches.push({
96
+ op: "add",
97
+ path: [key],
98
+ value: nextState[key]
99
+ });
100
+ inversePatches.push({
101
+ op: "remove",
102
+ path: [key]
103
+ });
104
+ continue;
105
+ }
106
+ if (Object.is(currentState[key], nextState[key])) continue;
107
+ patches.push({
108
+ op: "replace",
109
+ path: [key],
110
+ value: nextState[key]
111
+ });
112
+ inversePatches.push({
113
+ op: "replace",
114
+ path: [key],
115
+ value: currentState[key]
116
+ });
117
+ }
118
+ return {
119
+ patches,
120
+ inversePatches
121
+ };
122
+ };
123
+ const createRootStateFromPatches = (currentState, patches) => {
124
+ const nextState = sanitizeReplacementState(currentState);
125
+ const seen = /* @__PURE__ */ new WeakMap();
126
+ for (const patch of patches) {
127
+ if (!Array.isArray(patch.path) || patch.path.length !== 1 || ![
128
+ "add",
129
+ "remove",
130
+ "replace"
131
+ ].includes(patch.op)) return;
132
+ const key = patch.path[0];
133
+ if (patch.op === "remove") {
134
+ delete nextState[key];
135
+ continue;
136
+ }
137
+ nextState[key] = sanitizeReplacementState(patch.value, seen);
138
+ }
139
+ return nextState;
140
+ };
141
+ const applyRootReplacementWithPatches = (store, nextState, options = {}) => {
142
+ const { patches, inversePatches } = createRootReplacementPatches(store.getPureState(), nextState);
143
+ const finalPatches = store.patch ? store.patch({
144
+ patches,
145
+ inversePatches
146
+ }) : {
147
+ patches,
148
+ inversePatches
149
+ };
150
+ const safePatches = sanitizeCheckedPatches(finalPatches.patches, "store.patch()");
151
+ const safeInversePatches = sanitizeCheckedPatches(finalPatches.inversePatches, "store.patch() inverse patches");
152
+ if (safePatches.length) {
153
+ const applyExactReplacement = options.applyExactReplacement;
154
+ const exactReplacementState = applyExactReplacement ? createRootStateFromPatches(store.getPureState(), safePatches) : void 0;
155
+ if (applyExactReplacement && exactReplacementState) applyExactReplacement(exactReplacementState);
156
+ else store.apply(store.getPureState(), safePatches);
157
+ }
158
+ return [
159
+ store.getPureState(),
160
+ safePatches,
161
+ safeInversePatches
162
+ ];
163
+ };
48
164
  const setOwnEnumerable = (target, key, value) => {
49
165
  if (typeof key === "string" && isUnsafeKey(key)) return;
50
166
  target[key] = value;
@@ -53,7 +169,59 @@ const getOwnEnumerableKeys = (source) => {
53
169
  if (typeof source !== "object" || source === null) return [];
54
170
  return Reflect.ownKeys(source).filter((key) => Object.prototype.propertyIsEnumerable.call(source, key));
55
171
  };
56
- const isArrayIndexKey$1 = (key) => {
172
+ const getOwnSchemaKeys = (source) => {
173
+ if (typeof source !== "object" || source === null) return [];
174
+ return Reflect.ownKeys(source).filter((key) => !(typeof key === "string" && isUnsafeKey(key)));
175
+ };
176
+ const formatSchemaPath = (path) => path.length ? path.map((key) => String(key)).join(".") : "<root>";
177
+ const assertKnownSchemaKey = (knownKeys, key, path) => {
178
+ if (typeof key === "string" && isUnsafeKey(key)) return;
179
+ if (knownKeys.has(key)) return;
180
+ throw new StateSchemaError(`Unknown state key '${formatSchemaPath([...path, key])}' cannot be added after store initialization. Coaction state schema is fixed.`);
181
+ };
182
+ const assertKnownSliceObject = (key, value) => {
183
+ if (typeof value === "object" && value !== null) return;
184
+ throw new StateSchemaError(`State slice '${String(key)}' must remain an object after store initialization. Coaction slice schema is fixed.`);
185
+ };
186
+ const assertKnownSlicePresent = (source, key) => {
187
+ if (Object.prototype.hasOwnProperty.call(source, key)) return;
188
+ throw new StateSchemaError(`State slice '${String(key)}' cannot be removed after store initialization. Coaction slice schema is fixed.`);
189
+ };
190
+ const createStateSchema = (rootState, isSliceStore) => {
191
+ const rootKeys = new Set(getOwnSchemaKeys(rootState));
192
+ if (!isSliceStore) return { rootKeys };
193
+ const sliceKeys = /* @__PURE__ */ new Map();
194
+ if (typeof rootState === "object" && rootState !== null) {
195
+ const rootRecord = rootState;
196
+ rootKeys.forEach((key) => {
197
+ const slice = rootRecord[key];
198
+ if (typeof slice === "object" && slice !== null) sliceKeys.set(key, new Set(getOwnSchemaKeys(slice)));
199
+ });
200
+ }
201
+ return {
202
+ rootKeys,
203
+ sliceKeys
204
+ };
205
+ };
206
+ const assertKnownStateShape = (source, rootState, schema, isSliceStore, options = {}) => {
207
+ if (typeof source !== "object" || source === null) return;
208
+ const rootKeys = schema?.rootKeys ?? new Set(getOwnSchemaKeys(rootState));
209
+ const sourceRecord = source;
210
+ const knownSliceEntries = schema?.sliceKeys;
211
+ if (isSliceStore && options.requireSliceRoots && knownSliceEntries) knownSliceEntries.forEach((_, key) => {
212
+ assertKnownSlicePresent(sourceRecord, key);
213
+ });
214
+ for (const key of getOwnEnumerableKeys(source)) {
215
+ assertKnownSchemaKey(rootKeys, key, []);
216
+ if (!isSliceStore) continue;
217
+ const slice = sourceRecord[key];
218
+ const knownSliceKeys = schema?.sliceKeys?.get(key) ?? (typeof rootState === "object" && rootState !== null && typeof rootState[key] === "object" && rootState[key] !== null ? new Set(getOwnSchemaKeys(rootState[key])) : void 0);
219
+ if (!knownSliceKeys) continue;
220
+ assertKnownSliceObject(key, slice);
221
+ for (const sliceKey of getOwnEnumerableKeys(slice)) assertKnownSchemaKey(knownSliceKeys, sliceKey, [key]);
222
+ }
223
+ };
224
+ const isArrayIndexKey$2 = (key) => {
57
225
  if (typeof key !== "string") return false;
58
226
  const index = Number(key);
59
227
  return Number.isInteger(index) && index >= 0 && index < 2 ** 32 - 1 && String(index) === key;
@@ -91,7 +259,7 @@ const sanitizeReplacementState = (source, seen = /* @__PURE__ */ new WeakMap())
91
259
  seen.set(source, target);
92
260
  for (let index = 0; index < source.length; index += 1) if (Object.prototype.hasOwnProperty.call(source, index)) target[index] = sanitizeReplacementState(source[index], seen);
93
261
  for (const key of getOwnEnumerableKeys(source)) {
94
- if (isArrayIndexKey$1(key) || typeof key === "string" && isUnsafeKey(key)) continue;
262
+ if (isArrayIndexKey$2(key) || typeof key === "string" && isUnsafeKey(key)) continue;
95
263
  const value = source[key];
96
264
  if (typeof value === "function") continue;
97
265
  setOwnEnumerable(target, key, sanitizeReplacementState(value, seen));
@@ -120,7 +288,7 @@ const sanitizeInitialStateValue = (source, seen = /* @__PURE__ */ new WeakMap())
120
288
  seen.set(source, target);
121
289
  for (let index = 0; index < source.length; index += 1) if (Object.prototype.hasOwnProperty.call(source, index)) target[index] = sanitizeInitialStateValue(source[index], seen);
122
290
  for (const key of getOwnEnumerableKeys(source)) {
123
- if (isArrayIndexKey$1(key) || typeof key === "string" && isUnsafeKey(key)) continue;
291
+ if (isArrayIndexKey$2(key) || typeof key === "string" && isUnsafeKey(key)) continue;
124
292
  setOwnEnumerable(target, key, sanitizeInitialStateValue(source[key], seen));
125
293
  }
126
294
  return target;
@@ -171,7 +339,7 @@ const isPlainObject = (value) => {
171
339
  const prototype = Object.getPrototypeOf(value);
172
340
  return prototype === Object.prototype || prototype === null;
173
341
  };
174
- const isArrayIndexKey = (key, length) => {
342
+ const isArrayIndexKey$1 = (key, length) => {
175
343
  if (key === "") return false;
176
344
  const index = Number(key);
177
345
  return Number.isInteger(index) && index >= 0 && index < length && String(index) === key;
@@ -235,7 +403,7 @@ const findJsonViolation = (value, path = [], ancestors = /* @__PURE__ */ new Wea
235
403
  type: "symbol-key",
236
404
  path: nextPath
237
405
  };
238
- if (!isArrayIndexKey(key, value.length)) return {
406
+ if (!isArrayIndexKey$1(key, value.length)) return {
239
407
  type: "array-property",
240
408
  path: nextPath
241
409
  };
@@ -369,6 +537,7 @@ const createAsyncClientStore = (createStore, asyncStoreClientOption) => {
369
537
  allowLowerSequence = true;
370
538
  } else return;
371
539
  else if (options.sequence === internal.sequence + 1) {
540
+ assertSafePatches(options.patches, "client transport update");
372
541
  internal.applyClientState(void 0, options.patches);
373
542
  internal.sequence = options.sequence;
374
543
  awaitingReconnectSync = false;
@@ -391,7 +560,10 @@ const createAsyncClientStore = (createStore, asyncStoreClientOption) => {
391
560
  return wrapStore(asyncClientStore, () => asyncClientStore.getState());
392
561
  };
393
562
  const emit = (store, internal, patches) => {
394
- const safePatches = sanitizePatches(patches);
563
+ const safePatches = sanitizePatches(patches, {
564
+ source: "transport emit",
565
+ warnOnDropped: true
566
+ });
395
567
  if (store.transport && safePatches?.length) {
396
568
  validateSharedStateSerializable(internal.rootState);
397
569
  internal.sequence += 1;
@@ -408,13 +580,13 @@ const handleDraft = (store, internal) => {
408
580
  internal.rootState = internal.backupState;
409
581
  const [nextState, patches, inversePatches] = internal.finalizeDraft();
410
582
  if (store.share === "main") validateSharedStateSerializable(nextState);
411
- const safePatches = sanitizePatches((store.patch ? store.patch({
583
+ const safePatches = sanitizeCheckedPatches((store.patch ? store.patch({
412
584
  patches,
413
585
  inversePatches
414
586
  }) : {
415
587
  patches,
416
588
  inversePatches
417
- }).patches) ?? [];
589
+ }).patches, "store.patch()");
418
590
  if (safePatches.length) {
419
591
  store.apply(internal.rootState, safePatches);
420
592
  emit(store, internal, safePatches);
@@ -478,6 +650,7 @@ const isLegacyTransportErrorEnvelope = (value) => {
478
650
  };
479
651
  const createClientAction = ({ clientExecuteSyncTimeoutMs, internal, key, store, sliceKey }) => {
480
652
  return (...args) => {
653
+ internal.assertAlive?.(`action ${key}`);
481
654
  let actionId;
482
655
  let done;
483
656
  if (store.trace) {
@@ -575,6 +748,7 @@ const getActionTarget = (store, sliceKey) => {
575
748
  };
576
749
  const createLocalAction = ({ fn, internal, key, options, store, sliceKey }) => {
577
750
  return (...args) => {
751
+ internal.assertAlive?.(`action ${String(key)}`);
578
752
  let actionId;
579
753
  let done;
580
754
  if (store.trace) {
@@ -729,6 +903,63 @@ const refreshSignalSlots = (internal) => {
729
903
  };
730
904
  //#endregion
731
905
  //#region packages/core/src/getRawStateStateProperty.ts
906
+ const assertImmutableStateMutationAllowed = (internal) => {
907
+ if (internal.mutableInstance || internal.isBatching) return;
908
+ throw new Error("Direct state mutation is not allowed in immutable Coaction stores. Wrap mutations in set(() => { ... }).");
909
+ };
910
+ const readonlyProxyCache = /* @__PURE__ */ new WeakMap();
911
+ const isReadonlyProxyable = (value) => {
912
+ if (typeof value !== "object" || value === null) return false;
913
+ if (Array.isArray(value)) return true;
914
+ const prototype = Object.getPrototypeOf(value);
915
+ return prototype === Object.prototype || prototype === null;
916
+ };
917
+ const getReadonlyProxyCache = (internal) => {
918
+ let cache = readonlyProxyCache.get(internal);
919
+ if (!cache) {
920
+ cache = /* @__PURE__ */ new WeakMap();
921
+ readonlyProxyCache.set(internal, cache);
922
+ }
923
+ return cache;
924
+ };
925
+ const getPublicStateObject = (internal, value, sliceKey) => {
926
+ if (value === internal.rootState) return internal.module;
927
+ if (typeof sliceKey === "undefined" || typeof internal.rootState !== "object" || internal.rootState === null || typeof internal.module !== "object" || internal.module === null) return;
928
+ const rootState = internal.rootState;
929
+ const module = internal.module;
930
+ if (rootState[sliceKey] === value) return module[sliceKey];
931
+ };
932
+ const toReadonlyStateValue = (internal, value, sliceKey) => {
933
+ if (internal.mutableInstance || internal.isBatching || !isReadonlyProxyable(value)) return value;
934
+ const publicValue = getPublicStateObject(internal, value, sliceKey);
935
+ if (publicValue) return publicValue;
936
+ const cache = getReadonlyProxyCache(internal);
937
+ const cached = cache.get(value);
938
+ if (cached) return cached;
939
+ const proxy = new Proxy(value, {
940
+ get(target, key, receiver) {
941
+ return toReadonlyStateValue(internal, Reflect.get(target, key, receiver), sliceKey);
942
+ },
943
+ set() {
944
+ assertImmutableStateMutationAllowed(internal);
945
+ return false;
946
+ },
947
+ deleteProperty() {
948
+ assertImmutableStateMutationAllowed(internal);
949
+ return false;
950
+ },
951
+ defineProperty() {
952
+ assertImmutableStateMutationAllowed(internal);
953
+ return false;
954
+ },
955
+ setPrototypeOf() {
956
+ assertImmutableStateMutationAllowed(internal);
957
+ return false;
958
+ }
959
+ });
960
+ cache.set(value, proxy);
961
+ return proxy;
962
+ };
732
963
  const prepareStateDescriptor = ({ descriptor, initialStateSeen, internal, key, rawState, sliceKey }) => {
733
964
  const isComputed = descriptor.value instanceof Computed;
734
965
  const readStateValue = () => typeof sliceKey !== "undefined" ? internal.rootState[sliceKey][key] : internal.rootState[key];
@@ -752,14 +983,16 @@ const prepareStateDescriptor = ({ descriptor, initialStateSeen, internal, key, r
752
983
  descriptor.get = descriptor.value.createGetter({ internal });
753
984
  } else if (typeof sliceKey !== "undefined") {
754
985
  const read = createTrackedStateReader(internal, readStateValue, initialValue);
755
- descriptor.get = () => read();
986
+ descriptor.get = () => toReadonlyStateValue(internal, read(), sliceKey);
756
987
  descriptor.set = (value) => {
988
+ assertImmutableStateMutationAllowed(internal);
757
989
  internal.rootState[sliceKey][key] = value;
758
990
  };
759
991
  } else {
760
992
  const read = createTrackedStateReader(internal, readStateValue, initialValue);
761
- descriptor.get = () => read();
993
+ descriptor.get = () => toReadonlyStateValue(internal, read());
762
994
  descriptor.set = (value) => {
995
+ assertImmutableStateMutationAllowed(internal);
763
996
  internal.rootState[key] = value;
764
997
  };
765
998
  }
@@ -773,6 +1006,10 @@ const prepareAccessorDescriptor = ({ descriptor, internal }) => {
773
1006
  //#endregion
774
1007
  //#region packages/core/src/getRawState.ts
775
1008
  const defaultClientExecuteSyncTimeoutMs = 1500;
1009
+ const lockPublicStateObject = (state) => {
1010
+ Object.freeze(state);
1011
+ return state;
1012
+ };
776
1013
  const getClientExecuteSyncTimeoutMs = (options) => {
777
1014
  const timeout = options.executeSyncTimeoutMs;
778
1015
  if (typeof timeout === "undefined") return defaultClientExecuteSyncTimeoutMs;
@@ -833,7 +1070,7 @@ const getRawState = (store, internal, initialState, options) => {
833
1070
  });
834
1071
  }
835
1072
  });
836
- return Object.defineProperties({}, safeDescriptors);
1073
+ return lockPublicStateObject(Object.defineProperties({}, safeDescriptors));
837
1074
  };
838
1075
  if (store.isSliceStore) {
839
1076
  internal.module = {};
@@ -843,19 +1080,24 @@ const getRawState = (store, internal, initialState, options) => {
843
1080
  setOwnEnumerable(rawState, key, sliceRawState);
844
1081
  setOwnEnumerable(internal.module, key, handle(sliceRawState, initialState[key], key));
845
1082
  });
1083
+ lockPublicStateObject(internal.module);
846
1084
  } else internal.module = handle(rawState, initialState);
847
1085
  return rawState;
848
1086
  };
849
1087
  //#endregion
850
1088
  //#region packages/core/src/handleState.ts
851
1089
  const handleState = (store, internal, options) => {
852
- const setState = (next, updater = (next) => {
1090
+ const defaultUpdater = (next) => {
853
1091
  const merge = (_next = next) => {
1092
+ assertKnownStateShape(_next, internal.rootState, internal.stateSchema, store.isSliceStore);
854
1093
  mergeObject(internal.rootState, _next, store.isSliceStore);
855
1094
  };
856
1095
  const fn = typeof next === "function" ? () => {
857
1096
  const returnValue = next(internal.module);
858
- if (returnValue instanceof Promise) throw new Error("setState with async function is not supported");
1097
+ if (returnValue instanceof Promise) {
1098
+ returnValue.catch(() => void 0);
1099
+ throw new Error("setState with async function is not supported");
1100
+ }
859
1101
  if (typeof returnValue === "object" && returnValue !== null) merge(returnValue);
860
1102
  } : merge;
861
1103
  if (!(store.transport ?? options.enablePatches) && internal.mutableInstance) {
@@ -876,6 +1118,7 @@ const handleState = (store, internal, options) => {
876
1118
  internal.rootState = draft;
877
1119
  return fn.apply(null);
878
1120
  }, { enablePatches: true });
1121
+ assertKnownStateShape(result[0], internal.backupState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
879
1122
  if (store.share === "main") validateSharedStateSerializable(result[0]);
880
1123
  patches = result[1];
881
1124
  inversePatches = result[2];
@@ -889,29 +1132,40 @@ const handleState = (store, internal, options) => {
889
1132
  patches,
890
1133
  inversePatches
891
1134
  };
892
- const safePatches = sanitizePatches(finalPatches.patches) ?? [];
893
- const safeInversePatches = sanitizePatches(finalPatches.inversePatches) ?? [];
1135
+ const safePatches = sanitizeCheckedPatches(finalPatches.patches, "store.patch()");
1136
+ const safeInversePatches = sanitizeCheckedPatches(finalPatches.inversePatches, "store.patch() inverse patches");
894
1137
  if (safePatches.length) store.apply(internal.rootState, safePatches);
895
1138
  return [
896
1139
  internal.rootState,
897
1140
  safePatches,
898
1141
  safeInversePatches
899
1142
  ];
900
- }) => {
1143
+ };
1144
+ const setState = (next, updater = defaultUpdater) => {
1145
+ internal.assertAlive?.("setState");
901
1146
  internal.assertMutationAllowed?.("setState");
902
1147
  if (store.share === "client") throw new Error(`setState() cannot be called in the client store. To update the state, please trigger a store method with setState() instead.`);
903
1148
  if (internal.isBatching) throw new Error("setState cannot be called within the updater");
904
1149
  if (next === null) return [];
1150
+ if (typeof next === "object") assertKnownStateShape(next, internal.rootState, internal.stateSchema, store.isSliceStore);
905
1151
  internal.isBatching = true;
906
- if (!store.share && !options.enablePatches && !internal.mutableInstance) try {
1152
+ if (!store.share && !options.enablePatches && !internal.mutableInstance && updater === defaultUpdater) try {
907
1153
  if (typeof next === "function") try {
908
1154
  internal.backupState = internal.rootState;
909
- internal.rootState = create$1(internal.rootState, (draft) => {
1155
+ const nextState = create$1(internal.rootState, (draft) => {
910
1156
  internal.rootState = draft;
911
1157
  const returnValue = next(internal.module);
912
- if (returnValue instanceof Promise) throw new Error("setState with async function is not supported");
913
- if (typeof returnValue === "object" && returnValue !== null) mergeObject(internal.rootState, returnValue, store.isSliceStore);
1158
+ if (returnValue instanceof Promise) {
1159
+ returnValue.catch(() => void 0);
1160
+ throw new Error("setState with async function is not supported");
1161
+ }
1162
+ if (typeof returnValue === "object" && returnValue !== null) {
1163
+ assertKnownStateShape(returnValue, internal.rootState, internal.stateSchema, store.isSliceStore);
1164
+ mergeObject(internal.rootState, returnValue, store.isSliceStore);
1165
+ }
914
1166
  });
1167
+ assertKnownStateShape(nextState, internal.backupState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
1168
+ internal.rootState = nextState;
915
1169
  } catch (error) {
916
1170
  internal.rootState = internal.backupState;
917
1171
  throw error;
@@ -932,6 +1186,7 @@ const handleState = (store, internal, options) => {
932
1186
  setOwnEnumerable(copyRecord, key, sliceCopy);
933
1187
  }
934
1188
  } else mergeObject(copy, next);
1189
+ assertKnownStateShape(copy, internal.rootState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
935
1190
  internal.rootState = copy;
936
1191
  }
937
1192
  refreshSignalSlots(internal);
@@ -946,6 +1201,7 @@ const handleState = (store, internal, options) => {
946
1201
  const isDrafted = internal.mutableInstance && isDraft(internal.rootState);
947
1202
  if (isDrafted) handleDraft(store, internal);
948
1203
  result = updater(next);
1204
+ if (internal.mutableInstance) assertKnownStateShape(internal.rootState, internal.backupState ?? internal.rootState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
949
1205
  if (store.share === "main") validateSharedStateSerializable(internal.rootState);
950
1206
  if (isDrafted) {
951
1207
  internal.backupState = internal.rootState;
@@ -958,8 +1214,8 @@ const handleState = (store, internal, options) => {
958
1214
  }
959
1215
  if (result?.length) result = [
960
1216
  result[0],
961
- sanitizePatches(result[1]) ?? [],
962
- sanitizePatches(result[2]) ?? []
1217
+ sanitizeCheckedPatches(result[1], "setState updater result"),
1218
+ sanitizeCheckedPatches(result[2], "setState updater inverse result")
963
1219
  ];
964
1220
  emit(store, internal, result?.[1]);
965
1221
  return result;
@@ -1127,10 +1383,14 @@ const create = (createState, options = {}) => {
1127
1383
  try {
1128
1384
  const { setState, getState } = handleState(store, internal, options);
1129
1385
  const subscribe = (listener) => {
1386
+ internal.assertAlive?.("subscribe");
1130
1387
  internal.listeners.add(listener);
1131
1388
  return () => internal.listeners.delete(listener);
1132
1389
  };
1133
1390
  let isDestroyed = false;
1391
+ internal.assertAlive = (operation) => {
1392
+ if (isDestroyed) throw new Error(`${operation} cannot be called after store.destroy().`);
1393
+ };
1134
1394
  const destroy = () => {
1135
1395
  if (isDestroyed) return;
1136
1396
  isDestroyed = true;
@@ -1139,9 +1399,13 @@ const create = (createState, options = {}) => {
1139
1399
  releaseStoreName();
1140
1400
  };
1141
1401
  const apply$1 = (state = internal.rootState, patches) => {
1402
+ internal.assertAlive?.("apply");
1142
1403
  internal.assertMutationAllowed?.("apply");
1404
+ assertSafePatches(patches, "store.apply()");
1143
1405
  const safePatches = sanitizePatches(patches);
1144
- const nextState = sanitizeReplacementState(safePatches ? apply(state, safePatches) : state);
1406
+ const baseState = state === internal.module ? internal.rootState : state;
1407
+ const nextState = sanitizeReplacementState(safePatches ? apply(baseState, safePatches) : baseState);
1408
+ assertKnownStateShape(nextState, internal.rootState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
1145
1409
  if (store.share === "main") validateSharedStateSerializable(nextState);
1146
1410
  internal.rootState = nextState;
1147
1411
  refreshSignalSlots(internal);
@@ -1187,6 +1451,7 @@ const create = (createState, options = {}) => {
1187
1451
  if (share) validateSharedActionPaths(initialState);
1188
1452
  store.getInitialState = () => initialState;
1189
1453
  internal.rootState = getRawState(store, internal, initialState, options);
1454
+ internal.stateSchema = createStateSchema(internal.rootState, store.isSliceStore);
1190
1455
  if (share) validateSharedStateSerializable(internal.rootState);
1191
1456
  markStoreReady(store);
1192
1457
  return {
@@ -1341,13 +1606,13 @@ const replaceExternalStoreState = (store, internal, source, { syncImmutable = tr
1341
1606
  const [, patches, inversePatches] = create$1(internal.rootState, (draft) => {
1342
1607
  replaceOwnEnumerable(draft, source);
1343
1608
  }, { enablePatches: true });
1344
- const safePatches = sanitizePatches((store.patch ? store.patch({
1609
+ const safePatches = sanitizeCheckedPatches((store.patch ? store.patch({
1345
1610
  patches,
1346
1611
  inversePatches
1347
1612
  }) : {
1348
1613
  patches,
1349
1614
  inversePatches
1350
- }).patches) ?? [];
1615
+ }).patches, "store.patch()");
1351
1616
  if (!safePatches.length) return;
1352
1617
  const updateImmutable = internal.updateImmutable;
1353
1618
  if (!syncImmutable) internal.updateImmutable = void 0;
@@ -1359,4 +1624,129 @@ const replaceExternalStoreState = (store, internal, source, { syncImmutable = tr
1359
1624
  emit(store, internal, safePatches);
1360
1625
  };
1361
1626
  //#endregion
1362
- export { computed, create, createBinder, createReactiveTracker, defineExternalStoreAdapter, effect, effectScope, endBatch, isComputed, isEffect, isEffectScope, isSignal, onStoreReady, replaceExternalStoreState, replaceOwnEnumerable, sanitizeInitialStateValue, sanitizeReplacementState, signal, startBatch, trigger, wrapStore };
1627
+ //#region packages/core/src/externalMutableAdapterUtils.ts
1628
+ const getMutableAdapterOwnEnumerableKeys = (value) => Reflect.ownKeys(value).filter((key) => Object.prototype.propertyIsEnumerable.call(value, key));
1629
+ const isMutableAdapterUnsafeKey = (key) => typeof key === "string" && isUnsafeKey(key);
1630
+ const isArrayIndexKey = (key) => {
1631
+ if (typeof key !== "string") return false;
1632
+ const index = Number(key);
1633
+ return Number.isInteger(index) && index >= 0 && index < 2 ** 32 - 1 && String(index) === key;
1634
+ };
1635
+ const isObjectRecord = (value) => Object.prototype.toString.call(value) === "[object Object]";
1636
+ const assertCanSetMutableAdapterPublicStateKey = (publicState, key) => {
1637
+ if (Object.prototype.hasOwnProperty.call(publicState, key)) return;
1638
+ if (Object.isExtensible(publicState)) return;
1639
+ throw new StateSchemaError(`Unknown state key '${String(key)}' cannot be added after store initialization. Coaction state schema is fixed.`);
1640
+ };
1641
+ const ensureMutableAdapterRawDescriptor = (rawState, mutableState, publicState, key) => {
1642
+ if (rawState === mutableState) return;
1643
+ const rawDescriptor = Object.getOwnPropertyDescriptor(rawState, key);
1644
+ if (rawDescriptor?.get && rawDescriptor.set) return;
1645
+ const publicDescriptor = Object.getOwnPropertyDescriptor(publicState, key);
1646
+ if (!publicDescriptor || rawDescriptor?.configurable === false) return;
1647
+ Object.defineProperty(rawState, key, {
1648
+ get: () => mutableState[key],
1649
+ set: (value) => {
1650
+ mutableState[key] = value;
1651
+ },
1652
+ configurable: true,
1653
+ enumerable: publicDescriptor.enumerable
1654
+ });
1655
+ };
1656
+ const replaceMutableAdapterState = (rawState, mutableState, publicState, source) => {
1657
+ const nextKeys = /* @__PURE__ */ new Set();
1658
+ for (const key of getMutableAdapterOwnEnumerableKeys(source)) {
1659
+ if (isMutableAdapterUnsafeKey(key)) continue;
1660
+ if (typeof source[key] === "function") continue;
1661
+ nextKeys.add(key);
1662
+ }
1663
+ nextKeys.forEach((key) => {
1664
+ assertCanSetMutableAdapterPublicStateKey(publicState, key);
1665
+ });
1666
+ for (const key of getMutableAdapterOwnEnumerableKeys(rawState)) {
1667
+ if (isMutableAdapterUnsafeKey(key)) {
1668
+ delete rawState[key];
1669
+ delete mutableState[key];
1670
+ continue;
1671
+ }
1672
+ if (typeof rawState[key] === "function") continue;
1673
+ if (!nextKeys.has(key)) {
1674
+ delete rawState[key];
1675
+ delete mutableState[key];
1676
+ }
1677
+ }
1678
+ const rawSeen = /* @__PURE__ */ new WeakMap();
1679
+ const mutableSeen = /* @__PURE__ */ new WeakMap();
1680
+ const publicSeen = /* @__PURE__ */ new WeakMap();
1681
+ rawSeen.set(source, rawState);
1682
+ mutableSeen.set(source, mutableState);
1683
+ publicSeen.set(source, publicState);
1684
+ nextKeys.forEach((key) => {
1685
+ ensureMutableAdapterRawDescriptor(rawState, mutableState, publicState, key);
1686
+ rawState[key] = sanitizeReplacementState(source[key], rawSeen);
1687
+ mutableState[key] = sanitizeReplacementState(source[key], mutableSeen);
1688
+ publicState[key] = sanitizeReplacementState(source[key], publicSeen);
1689
+ });
1690
+ };
1691
+ const applyMutableAdapterPatches = (baseState, patches, rawState, mutableState, publicState) => {
1692
+ assertSafePatches(patches, "mutable adapter apply()");
1693
+ replaceMutableAdapterState(rawState, mutableState, publicState, apply(toMutableAdapterSnapshot(baseState === publicState ? rawState : baseState), patches));
1694
+ };
1695
+ const toMutableAdapterSnapshot = (value, visited = /* @__PURE__ */ new WeakMap()) => {
1696
+ if (Array.isArray(value)) {
1697
+ if (visited.has(value)) return visited.get(value);
1698
+ const next = [];
1699
+ next.length = value.length;
1700
+ visited.set(value, next);
1701
+ for (let index = 0; index < value.length; index += 1) if (Object.prototype.hasOwnProperty.call(value, index)) next[index] = toMutableAdapterSnapshot(value[index], visited);
1702
+ const source = value;
1703
+ const target = next;
1704
+ for (const key of getMutableAdapterOwnEnumerableKeys(value)) {
1705
+ if (isArrayIndexKey(key) || isMutableAdapterUnsafeKey(key)) continue;
1706
+ const child = source[key];
1707
+ if (typeof child !== "function") target[key] = toMutableAdapterSnapshot(child, visited);
1708
+ }
1709
+ return next;
1710
+ }
1711
+ if (typeof value === "object" && value !== null) {
1712
+ if (!isObjectRecord(value)) return value;
1713
+ if (visited.has(value)) return visited.get(value);
1714
+ const next = {};
1715
+ visited.set(value, next);
1716
+ for (const key of getMutableAdapterOwnEnumerableKeys(value)) {
1717
+ if (isMutableAdapterUnsafeKey(key)) continue;
1718
+ const child = value[key];
1719
+ if (typeof child !== "function") next[key] = toMutableAdapterSnapshot(child, visited);
1720
+ }
1721
+ return next;
1722
+ }
1723
+ return value;
1724
+ };
1725
+ const snapshotMutableAdapterPureState = (store) => toMutableAdapterSnapshot(store.getPureState());
1726
+ const isEqualMutableAdapterSnapshot = (left, right, visited = /* @__PURE__ */ new WeakMap()) => {
1727
+ if (Object.is(left, right)) return true;
1728
+ if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) return false;
1729
+ const leftIsArray = Array.isArray(left);
1730
+ const rightIsArray = Array.isArray(right);
1731
+ if (leftIsArray || rightIsArray) {
1732
+ if (!leftIsArray || !rightIsArray || left.length !== right.length) return false;
1733
+ } else if (!isObjectRecord(left) || !isObjectRecord(right)) return false;
1734
+ let seenTargets = visited.get(left);
1735
+ if (!seenTargets) {
1736
+ seenTargets = /* @__PURE__ */ new WeakSet();
1737
+ visited.set(left, seenTargets);
1738
+ } else if (seenTargets.has(right)) return true;
1739
+ seenTargets.add(right);
1740
+ const leftRecord = left;
1741
+ const rightRecord = right;
1742
+ const leftKeys = getMutableAdapterOwnEnumerableKeys(left);
1743
+ const rightKeys = getMutableAdapterOwnEnumerableKeys(right);
1744
+ if (leftKeys.length !== rightKeys.length) return false;
1745
+ for (const key of leftKeys) {
1746
+ if (!Object.prototype.hasOwnProperty.call(rightRecord, key)) return false;
1747
+ if (!isEqualMutableAdapterSnapshot(leftRecord[key], rightRecord[key], visited)) return false;
1748
+ }
1749
+ return true;
1750
+ };
1751
+ //#endregion
1752
+ export { StateSchemaError, UnsafePatchPathError, applyMutableAdapterPatches, applyRootReplacementWithPatches, assertSafePatches, computed, create, createBinder, createReactiveTracker, createRootReplacementPatches, defineExternalStoreAdapter, effect, effectScope, endBatch, getMutableAdapterOwnEnumerableKeys, isComputed, isEffect, isEffectScope, isEqualMutableAdapterSnapshot, isMutableAdapterUnsafeKey, isSignal, isStateSchemaError, onStoreReady, replaceExternalStoreState, replaceMutableAdapterState, replaceOwnEnumerable, sanitizeInitialStateValue, sanitizePatches, sanitizeReplacementState, signal, snapshotMutableAdapterPureState, startBatch, toMutableAdapterSnapshot, trigger, wrapStore };