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.js CHANGED
@@ -62,13 +62,129 @@ const isEqual = (x, y) => {
62
62
  };
63
63
  const isUnsafeKey = (key) => key === "__proto__" || key === "prototype" || key === "constructor";
64
64
  const isUnsafePathSegment = (segment) => typeof segment === "string" && isUnsafeKey(segment);
65
+ var UnsafePatchPathError = class extends Error {
66
+ name = "UnsafePatchPathError";
67
+ };
68
+ var StateSchemaError = class extends Error {
69
+ name = "StateSchemaError";
70
+ };
71
+ const isStateSchemaError = (error) => error instanceof StateSchemaError || error instanceof Error && error.name === "StateSchemaError";
65
72
  const hasUnsafePatchPath = (path) => {
66
73
  return (Array.isArray(path) ? path : typeof path === "string" ? path.split("/").filter(Boolean).map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")) : []).some(isUnsafePathSegment);
67
74
  };
68
- const sanitizePatches = (patches) => patches?.filter((patch) => !hasUnsafePatchPath(patch.path)).map((patch) => Object.prototype.hasOwnProperty.call(patch, "value") ? {
69
- ...patch,
70
- value: sanitizeReplacementState(patch.value)
71
- } : patch);
75
+ const formatPatchPath = (path) => Array.isArray(path) ? path.map((segment) => String(segment)).join(".") : String(path);
76
+ const getUnsafePatchPaths = (patches) => patches?.filter((patch) => hasUnsafePatchPath(patch.path)) ?? [];
77
+ const assertSafePatches = (patches, source = "patches") => {
78
+ const unsafePatches = getUnsafePatchPaths(patches);
79
+ if (!unsafePatches.length) return;
80
+ const paths = unsafePatches.map((patch) => `'${formatPatchPath(patch.path)}'`).join(", ");
81
+ throw new UnsafePatchPathError(`Unsafe patch path${unsafePatches.length > 1 ? "s" : ""} ${paths} cannot be applied from ${source}.`);
82
+ };
83
+ const warnDroppedUnsafePatches = (unsafePatches, source) => {
84
+ if (process.env.NODE_ENV !== "development" || !unsafePatches.length) return;
85
+ const paths = unsafePatches.map((patch) => `'${formatPatchPath(patch.path)}'`).join(", ");
86
+ console.warn(`Coaction dropped unsafe patch path${unsafePatches.length > 1 ? "s" : ""} ${paths} from ${source}.`);
87
+ };
88
+ const sanitizePatches = (patches, options = {}) => {
89
+ if (options.warnOnDropped) warnDroppedUnsafePatches(getUnsafePatchPaths(patches), options.source ?? "patches");
90
+ return patches?.filter((patch) => !hasUnsafePatchPath(patch.path)).map((patch) => Object.prototype.hasOwnProperty.call(patch, "value") ? {
91
+ ...patch,
92
+ value: sanitizeReplacementState(patch.value)
93
+ } : patch);
94
+ };
95
+ const sanitizeCheckedPatches = (patches, source) => {
96
+ assertSafePatches(patches, source);
97
+ return sanitizePatches(patches) ?? [];
98
+ };
99
+ const createRootReplacementPatches = (currentState, nextState) => {
100
+ const patches = [];
101
+ const inversePatches = [];
102
+ const nextKeys = new Set(getOwnEnumerableKeys(nextState));
103
+ for (const key of getOwnEnumerableKeys(currentState)) {
104
+ if (typeof key === "string" && isUnsafeKey(key)) continue;
105
+ if (nextKeys.has(key)) continue;
106
+ patches.push({
107
+ op: "remove",
108
+ path: [key]
109
+ });
110
+ inversePatches.push({
111
+ op: "add",
112
+ path: [key],
113
+ value: currentState[key]
114
+ });
115
+ }
116
+ for (const key of nextKeys) {
117
+ if (typeof key === "string" && isUnsafeKey(key)) continue;
118
+ if (!Object.prototype.hasOwnProperty.call(currentState, key)) {
119
+ patches.push({
120
+ op: "add",
121
+ path: [key],
122
+ value: nextState[key]
123
+ });
124
+ inversePatches.push({
125
+ op: "remove",
126
+ path: [key]
127
+ });
128
+ continue;
129
+ }
130
+ if (Object.is(currentState[key], nextState[key])) continue;
131
+ patches.push({
132
+ op: "replace",
133
+ path: [key],
134
+ value: nextState[key]
135
+ });
136
+ inversePatches.push({
137
+ op: "replace",
138
+ path: [key],
139
+ value: currentState[key]
140
+ });
141
+ }
142
+ return {
143
+ patches,
144
+ inversePatches
145
+ };
146
+ };
147
+ const createRootStateFromPatches = (currentState, patches) => {
148
+ const nextState = sanitizeReplacementState(currentState);
149
+ const seen = /* @__PURE__ */ new WeakMap();
150
+ for (const patch of patches) {
151
+ if (!Array.isArray(patch.path) || patch.path.length !== 1 || ![
152
+ "add",
153
+ "remove",
154
+ "replace"
155
+ ].includes(patch.op)) return;
156
+ const key = patch.path[0];
157
+ if (patch.op === "remove") {
158
+ delete nextState[key];
159
+ continue;
160
+ }
161
+ nextState[key] = sanitizeReplacementState(patch.value, seen);
162
+ }
163
+ return nextState;
164
+ };
165
+ const applyRootReplacementWithPatches = (store, nextState, options = {}) => {
166
+ const { patches, inversePatches } = createRootReplacementPatches(store.getPureState(), nextState);
167
+ const finalPatches = store.patch ? store.patch({
168
+ patches,
169
+ inversePatches
170
+ }) : {
171
+ patches,
172
+ inversePatches
173
+ };
174
+ const safePatches = sanitizeCheckedPatches(finalPatches.patches, "store.patch()");
175
+ const safeInversePatches = sanitizeCheckedPatches(finalPatches.inversePatches, "store.patch() inverse patches");
176
+ if (safePatches.length) {
177
+ const applyExactReplacement = options.applyExactReplacement;
178
+ const exactReplacementState = applyExactReplacement ? createRootStateFromPatches(store.getPureState(), safePatches) : void 0;
179
+ if (applyExactReplacement && exactReplacementState) applyExactReplacement(exactReplacementState);
180
+ else store.apply(store.getPureState(), safePatches);
181
+ }
182
+ return [
183
+ store.getPureState(),
184
+ safePatches,
185
+ safeInversePatches
186
+ ];
187
+ };
72
188
  const setOwnEnumerable = (target, key, value) => {
73
189
  if (typeof key === "string" && isUnsafeKey(key)) return;
74
190
  target[key] = value;
@@ -77,7 +193,59 @@ const getOwnEnumerableKeys = (source) => {
77
193
  if (typeof source !== "object" || source === null) return [];
78
194
  return Reflect.ownKeys(source).filter((key) => Object.prototype.propertyIsEnumerable.call(source, key));
79
195
  };
80
- const isArrayIndexKey$1 = (key) => {
196
+ const getOwnSchemaKeys = (source) => {
197
+ if (typeof source !== "object" || source === null) return [];
198
+ return Reflect.ownKeys(source).filter((key) => !(typeof key === "string" && isUnsafeKey(key)));
199
+ };
200
+ const formatSchemaPath = (path) => path.length ? path.map((key) => String(key)).join(".") : "<root>";
201
+ const assertKnownSchemaKey = (knownKeys, key, path) => {
202
+ if (typeof key === "string" && isUnsafeKey(key)) return;
203
+ if (knownKeys.has(key)) return;
204
+ throw new StateSchemaError(`Unknown state key '${formatSchemaPath([...path, key])}' cannot be added after store initialization. Coaction state schema is fixed.`);
205
+ };
206
+ const assertKnownSliceObject = (key, value) => {
207
+ if (typeof value === "object" && value !== null) return;
208
+ throw new StateSchemaError(`State slice '${String(key)}' must remain an object after store initialization. Coaction slice schema is fixed.`);
209
+ };
210
+ const assertKnownSlicePresent = (source, key) => {
211
+ if (Object.prototype.hasOwnProperty.call(source, key)) return;
212
+ throw new StateSchemaError(`State slice '${String(key)}' cannot be removed after store initialization. Coaction slice schema is fixed.`);
213
+ };
214
+ const createStateSchema = (rootState, isSliceStore) => {
215
+ const rootKeys = new Set(getOwnSchemaKeys(rootState));
216
+ if (!isSliceStore) return { rootKeys };
217
+ const sliceKeys = /* @__PURE__ */ new Map();
218
+ if (typeof rootState === "object" && rootState !== null) {
219
+ const rootRecord = rootState;
220
+ rootKeys.forEach((key) => {
221
+ const slice = rootRecord[key];
222
+ if (typeof slice === "object" && slice !== null) sliceKeys.set(key, new Set(getOwnSchemaKeys(slice)));
223
+ });
224
+ }
225
+ return {
226
+ rootKeys,
227
+ sliceKeys
228
+ };
229
+ };
230
+ const assertKnownStateShape = (source, rootState, schema, isSliceStore, options = {}) => {
231
+ if (typeof source !== "object" || source === null) return;
232
+ const rootKeys = schema?.rootKeys ?? new Set(getOwnSchemaKeys(rootState));
233
+ const sourceRecord = source;
234
+ const knownSliceEntries = schema?.sliceKeys;
235
+ if (isSliceStore && options.requireSliceRoots && knownSliceEntries) knownSliceEntries.forEach((_, key) => {
236
+ assertKnownSlicePresent(sourceRecord, key);
237
+ });
238
+ for (const key of getOwnEnumerableKeys(source)) {
239
+ assertKnownSchemaKey(rootKeys, key, []);
240
+ if (!isSliceStore) continue;
241
+ const slice = sourceRecord[key];
242
+ 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);
243
+ if (!knownSliceKeys) continue;
244
+ assertKnownSliceObject(key, slice);
245
+ for (const sliceKey of getOwnEnumerableKeys(slice)) assertKnownSchemaKey(knownSliceKeys, sliceKey, [key]);
246
+ }
247
+ };
248
+ const isArrayIndexKey$2 = (key) => {
81
249
  if (typeof key !== "string") return false;
82
250
  const index = Number(key);
83
251
  return Number.isInteger(index) && index >= 0 && index < 2 ** 32 - 1 && String(index) === key;
@@ -115,7 +283,7 @@ const sanitizeReplacementState = (source, seen = /* @__PURE__ */ new WeakMap())
115
283
  seen.set(source, target);
116
284
  for (let index = 0; index < source.length; index += 1) if (Object.prototype.hasOwnProperty.call(source, index)) target[index] = sanitizeReplacementState(source[index], seen);
117
285
  for (const key of getOwnEnumerableKeys(source)) {
118
- if (isArrayIndexKey$1(key) || typeof key === "string" && isUnsafeKey(key)) continue;
286
+ if (isArrayIndexKey$2(key) || typeof key === "string" && isUnsafeKey(key)) continue;
119
287
  const value = source[key];
120
288
  if (typeof value === "function") continue;
121
289
  setOwnEnumerable(target, key, sanitizeReplacementState(value, seen));
@@ -144,7 +312,7 @@ const sanitizeInitialStateValue = (source, seen = /* @__PURE__ */ new WeakMap())
144
312
  seen.set(source, target);
145
313
  for (let index = 0; index < source.length; index += 1) if (Object.prototype.hasOwnProperty.call(source, index)) target[index] = sanitizeInitialStateValue(source[index], seen);
146
314
  for (const key of getOwnEnumerableKeys(source)) {
147
- if (isArrayIndexKey$1(key) || typeof key === "string" && isUnsafeKey(key)) continue;
315
+ if (isArrayIndexKey$2(key) || typeof key === "string" && isUnsafeKey(key)) continue;
148
316
  setOwnEnumerable(target, key, sanitizeInitialStateValue(source[key], seen));
149
317
  }
150
318
  return target;
@@ -195,7 +363,7 @@ const isPlainObject = (value) => {
195
363
  const prototype = Object.getPrototypeOf(value);
196
364
  return prototype === Object.prototype || prototype === null;
197
365
  };
198
- const isArrayIndexKey = (key, length) => {
366
+ const isArrayIndexKey$1 = (key, length) => {
199
367
  if (key === "") return false;
200
368
  const index = Number(key);
201
369
  return Number.isInteger(index) && index >= 0 && index < length && String(index) === key;
@@ -259,7 +427,7 @@ const findJsonViolation = (value, path = [], ancestors = /* @__PURE__ */ new Wea
259
427
  type: "symbol-key",
260
428
  path: nextPath
261
429
  };
262
- if (!isArrayIndexKey(key, value.length)) return {
430
+ if (!isArrayIndexKey$1(key, value.length)) return {
263
431
  type: "array-property",
264
432
  path: nextPath
265
433
  };
@@ -393,6 +561,7 @@ const createAsyncClientStore = (createStore, asyncStoreClientOption) => {
393
561
  allowLowerSequence = true;
394
562
  } else return;
395
563
  else if (options.sequence === internal.sequence + 1) {
564
+ assertSafePatches(options.patches, "client transport update");
396
565
  internal.applyClientState(void 0, options.patches);
397
566
  internal.sequence = options.sequence;
398
567
  awaitingReconnectSync = false;
@@ -415,7 +584,10 @@ const createAsyncClientStore = (createStore, asyncStoreClientOption) => {
415
584
  return wrapStore(asyncClientStore, () => asyncClientStore.getState());
416
585
  };
417
586
  const emit = (store, internal, patches) => {
418
- const safePatches = sanitizePatches(patches);
587
+ const safePatches = sanitizePatches(patches, {
588
+ source: "transport emit",
589
+ warnOnDropped: true
590
+ });
419
591
  if (store.transport && safePatches?.length) {
420
592
  validateSharedStateSerializable(internal.rootState);
421
593
  internal.sequence += 1;
@@ -432,13 +604,13 @@ const handleDraft = (store, internal) => {
432
604
  internal.rootState = internal.backupState;
433
605
  const [nextState, patches, inversePatches] = internal.finalizeDraft();
434
606
  if (store.share === "main") validateSharedStateSerializable(nextState);
435
- const safePatches = sanitizePatches((store.patch ? store.patch({
607
+ const safePatches = sanitizeCheckedPatches((store.patch ? store.patch({
436
608
  patches,
437
609
  inversePatches
438
610
  }) : {
439
611
  patches,
440
612
  inversePatches
441
- }).patches) ?? [];
613
+ }).patches, "store.patch()");
442
614
  if (safePatches.length) {
443
615
  store.apply(internal.rootState, safePatches);
444
616
  emit(store, internal, safePatches);
@@ -502,6 +674,7 @@ const isLegacyTransportErrorEnvelope = (value) => {
502
674
  };
503
675
  const createClientAction = ({ clientExecuteSyncTimeoutMs, internal, key, store, sliceKey }) => {
504
676
  return (...args) => {
677
+ internal.assertAlive?.(`action ${key}`);
505
678
  let actionId;
506
679
  let done;
507
680
  if (store.trace) {
@@ -599,6 +772,7 @@ const getActionTarget = (store, sliceKey) => {
599
772
  };
600
773
  const createLocalAction = ({ fn, internal, key, options, store, sliceKey }) => {
601
774
  return (...args) => {
775
+ internal.assertAlive?.(`action ${String(key)}`);
602
776
  let actionId;
603
777
  let done;
604
778
  if (store.trace) {
@@ -753,6 +927,63 @@ const refreshSignalSlots = (internal) => {
753
927
  };
754
928
  //#endregion
755
929
  //#region packages/core/src/getRawStateStateProperty.ts
930
+ const assertImmutableStateMutationAllowed = (internal) => {
931
+ if (internal.mutableInstance || internal.isBatching) return;
932
+ throw new Error("Direct state mutation is not allowed in immutable Coaction stores. Wrap mutations in set(() => { ... }).");
933
+ };
934
+ const readonlyProxyCache = /* @__PURE__ */ new WeakMap();
935
+ const isReadonlyProxyable = (value) => {
936
+ if (typeof value !== "object" || value === null) return false;
937
+ if (Array.isArray(value)) return true;
938
+ const prototype = Object.getPrototypeOf(value);
939
+ return prototype === Object.prototype || prototype === null;
940
+ };
941
+ const getReadonlyProxyCache = (internal) => {
942
+ let cache = readonlyProxyCache.get(internal);
943
+ if (!cache) {
944
+ cache = /* @__PURE__ */ new WeakMap();
945
+ readonlyProxyCache.set(internal, cache);
946
+ }
947
+ return cache;
948
+ };
949
+ const getPublicStateObject = (internal, value, sliceKey) => {
950
+ if (value === internal.rootState) return internal.module;
951
+ if (typeof sliceKey === "undefined" || typeof internal.rootState !== "object" || internal.rootState === null || typeof internal.module !== "object" || internal.module === null) return;
952
+ const rootState = internal.rootState;
953
+ const module = internal.module;
954
+ if (rootState[sliceKey] === value) return module[sliceKey];
955
+ };
956
+ const toReadonlyStateValue = (internal, value, sliceKey) => {
957
+ if (internal.mutableInstance || internal.isBatching || !isReadonlyProxyable(value)) return value;
958
+ const publicValue = getPublicStateObject(internal, value, sliceKey);
959
+ if (publicValue) return publicValue;
960
+ const cache = getReadonlyProxyCache(internal);
961
+ const cached = cache.get(value);
962
+ if (cached) return cached;
963
+ const proxy = new Proxy(value, {
964
+ get(target, key, receiver) {
965
+ return toReadonlyStateValue(internal, Reflect.get(target, key, receiver), sliceKey);
966
+ },
967
+ set() {
968
+ assertImmutableStateMutationAllowed(internal);
969
+ return false;
970
+ },
971
+ deleteProperty() {
972
+ assertImmutableStateMutationAllowed(internal);
973
+ return false;
974
+ },
975
+ defineProperty() {
976
+ assertImmutableStateMutationAllowed(internal);
977
+ return false;
978
+ },
979
+ setPrototypeOf() {
980
+ assertImmutableStateMutationAllowed(internal);
981
+ return false;
982
+ }
983
+ });
984
+ cache.set(value, proxy);
985
+ return proxy;
986
+ };
756
987
  const prepareStateDescriptor = ({ descriptor, initialStateSeen, internal, key, rawState, sliceKey }) => {
757
988
  const isComputed = descriptor.value instanceof Computed;
758
989
  const readStateValue = () => typeof sliceKey !== "undefined" ? internal.rootState[sliceKey][key] : internal.rootState[key];
@@ -776,14 +1007,16 @@ const prepareStateDescriptor = ({ descriptor, initialStateSeen, internal, key, r
776
1007
  descriptor.get = descriptor.value.createGetter({ internal });
777
1008
  } else if (typeof sliceKey !== "undefined") {
778
1009
  const read = createTrackedStateReader(internal, readStateValue, initialValue);
779
- descriptor.get = () => read();
1010
+ descriptor.get = () => toReadonlyStateValue(internal, read(), sliceKey);
780
1011
  descriptor.set = (value) => {
1012
+ assertImmutableStateMutationAllowed(internal);
781
1013
  internal.rootState[sliceKey][key] = value;
782
1014
  };
783
1015
  } else {
784
1016
  const read = createTrackedStateReader(internal, readStateValue, initialValue);
785
- descriptor.get = () => read();
1017
+ descriptor.get = () => toReadonlyStateValue(internal, read());
786
1018
  descriptor.set = (value) => {
1019
+ assertImmutableStateMutationAllowed(internal);
787
1020
  internal.rootState[key] = value;
788
1021
  };
789
1022
  }
@@ -797,6 +1030,10 @@ const prepareAccessorDescriptor = ({ descriptor, internal }) => {
797
1030
  //#endregion
798
1031
  //#region packages/core/src/getRawState.ts
799
1032
  const defaultClientExecuteSyncTimeoutMs = 1500;
1033
+ const lockPublicStateObject = (state) => {
1034
+ Object.freeze(state);
1035
+ return state;
1036
+ };
800
1037
  const getClientExecuteSyncTimeoutMs = (options) => {
801
1038
  const timeout = options.executeSyncTimeoutMs;
802
1039
  if (typeof timeout === "undefined") return defaultClientExecuteSyncTimeoutMs;
@@ -857,7 +1094,7 @@ const getRawState = (store, internal, initialState, options) => {
857
1094
  });
858
1095
  }
859
1096
  });
860
- return Object.defineProperties({}, safeDescriptors);
1097
+ return lockPublicStateObject(Object.defineProperties({}, safeDescriptors));
861
1098
  };
862
1099
  if (store.isSliceStore) {
863
1100
  internal.module = {};
@@ -867,19 +1104,24 @@ const getRawState = (store, internal, initialState, options) => {
867
1104
  setOwnEnumerable(rawState, key, sliceRawState);
868
1105
  setOwnEnumerable(internal.module, key, handle(sliceRawState, initialState[key], key));
869
1106
  });
1107
+ lockPublicStateObject(internal.module);
870
1108
  } else internal.module = handle(rawState, initialState);
871
1109
  return rawState;
872
1110
  };
873
1111
  //#endregion
874
1112
  //#region packages/core/src/handleState.ts
875
1113
  const handleState = (store, internal, options) => {
876
- const setState = (next, updater = (next) => {
1114
+ const defaultUpdater = (next) => {
877
1115
  const merge = (_next = next) => {
1116
+ assertKnownStateShape(_next, internal.rootState, internal.stateSchema, store.isSliceStore);
878
1117
  mergeObject(internal.rootState, _next, store.isSliceStore);
879
1118
  };
880
1119
  const fn = typeof next === "function" ? () => {
881
1120
  const returnValue = next(internal.module);
882
- if (returnValue instanceof Promise) throw new Error("setState with async function is not supported");
1121
+ if (returnValue instanceof Promise) {
1122
+ returnValue.catch(() => void 0);
1123
+ throw new Error("setState with async function is not supported");
1124
+ }
883
1125
  if (typeof returnValue === "object" && returnValue !== null) merge(returnValue);
884
1126
  } : merge;
885
1127
  if (!(store.transport ?? options.enablePatches) && internal.mutableInstance) {
@@ -900,6 +1142,7 @@ const handleState = (store, internal, options) => {
900
1142
  internal.rootState = draft;
901
1143
  return fn.apply(null);
902
1144
  }, { enablePatches: true });
1145
+ assertKnownStateShape(result[0], internal.backupState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
903
1146
  if (store.share === "main") validateSharedStateSerializable(result[0]);
904
1147
  patches = result[1];
905
1148
  inversePatches = result[2];
@@ -913,29 +1156,40 @@ const handleState = (store, internal, options) => {
913
1156
  patches,
914
1157
  inversePatches
915
1158
  };
916
- const safePatches = sanitizePatches(finalPatches.patches) ?? [];
917
- const safeInversePatches = sanitizePatches(finalPatches.inversePatches) ?? [];
1159
+ const safePatches = sanitizeCheckedPatches(finalPatches.patches, "store.patch()");
1160
+ const safeInversePatches = sanitizeCheckedPatches(finalPatches.inversePatches, "store.patch() inverse patches");
918
1161
  if (safePatches.length) store.apply(internal.rootState, safePatches);
919
1162
  return [
920
1163
  internal.rootState,
921
1164
  safePatches,
922
1165
  safeInversePatches
923
1166
  ];
924
- }) => {
1167
+ };
1168
+ const setState = (next, updater = defaultUpdater) => {
1169
+ internal.assertAlive?.("setState");
925
1170
  internal.assertMutationAllowed?.("setState");
926
1171
  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.`);
927
1172
  if (internal.isBatching) throw new Error("setState cannot be called within the updater");
928
1173
  if (next === null) return [];
1174
+ if (typeof next === "object") assertKnownStateShape(next, internal.rootState, internal.stateSchema, store.isSliceStore);
929
1175
  internal.isBatching = true;
930
- if (!store.share && !options.enablePatches && !internal.mutableInstance) try {
1176
+ if (!store.share && !options.enablePatches && !internal.mutableInstance && updater === defaultUpdater) try {
931
1177
  if (typeof next === "function") try {
932
1178
  internal.backupState = internal.rootState;
933
- internal.rootState = (0, mutative.create)(internal.rootState, (draft) => {
1179
+ const nextState = (0, mutative.create)(internal.rootState, (draft) => {
934
1180
  internal.rootState = draft;
935
1181
  const returnValue = next(internal.module);
936
- if (returnValue instanceof Promise) throw new Error("setState with async function is not supported");
937
- if (typeof returnValue === "object" && returnValue !== null) mergeObject(internal.rootState, returnValue, store.isSliceStore);
1182
+ if (returnValue instanceof Promise) {
1183
+ returnValue.catch(() => void 0);
1184
+ throw new Error("setState with async function is not supported");
1185
+ }
1186
+ if (typeof returnValue === "object" && returnValue !== null) {
1187
+ assertKnownStateShape(returnValue, internal.rootState, internal.stateSchema, store.isSliceStore);
1188
+ mergeObject(internal.rootState, returnValue, store.isSliceStore);
1189
+ }
938
1190
  });
1191
+ assertKnownStateShape(nextState, internal.backupState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
1192
+ internal.rootState = nextState;
939
1193
  } catch (error) {
940
1194
  internal.rootState = internal.backupState;
941
1195
  throw error;
@@ -956,6 +1210,7 @@ const handleState = (store, internal, options) => {
956
1210
  setOwnEnumerable(copyRecord, key, sliceCopy);
957
1211
  }
958
1212
  } else mergeObject(copy, next);
1213
+ assertKnownStateShape(copy, internal.rootState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
959
1214
  internal.rootState = copy;
960
1215
  }
961
1216
  refreshSignalSlots(internal);
@@ -970,6 +1225,7 @@ const handleState = (store, internal, options) => {
970
1225
  const isDrafted = internal.mutableInstance && (0, mutative.isDraft)(internal.rootState);
971
1226
  if (isDrafted) handleDraft(store, internal);
972
1227
  result = updater(next);
1228
+ if (internal.mutableInstance) assertKnownStateShape(internal.rootState, internal.backupState ?? internal.rootState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
973
1229
  if (store.share === "main") validateSharedStateSerializable(internal.rootState);
974
1230
  if (isDrafted) {
975
1231
  internal.backupState = internal.rootState;
@@ -982,8 +1238,8 @@ const handleState = (store, internal, options) => {
982
1238
  }
983
1239
  if (result?.length) result = [
984
1240
  result[0],
985
- sanitizePatches(result[1]) ?? [],
986
- sanitizePatches(result[2]) ?? []
1241
+ sanitizeCheckedPatches(result[1], "setState updater result"),
1242
+ sanitizeCheckedPatches(result[2], "setState updater inverse result")
987
1243
  ];
988
1244
  emit(store, internal, result?.[1]);
989
1245
  return result;
@@ -1151,10 +1407,14 @@ const create = (createState, options = {}) => {
1151
1407
  try {
1152
1408
  const { setState, getState } = handleState(store, internal, options);
1153
1409
  const subscribe = (listener) => {
1410
+ internal.assertAlive?.("subscribe");
1154
1411
  internal.listeners.add(listener);
1155
1412
  return () => internal.listeners.delete(listener);
1156
1413
  };
1157
1414
  let isDestroyed = false;
1415
+ internal.assertAlive = (operation) => {
1416
+ if (isDestroyed) throw new Error(`${operation} cannot be called after store.destroy().`);
1417
+ };
1158
1418
  const destroy = () => {
1159
1419
  if (isDestroyed) return;
1160
1420
  isDestroyed = true;
@@ -1163,9 +1423,13 @@ const create = (createState, options = {}) => {
1163
1423
  releaseStoreName();
1164
1424
  };
1165
1425
  const apply = (state = internal.rootState, patches) => {
1426
+ internal.assertAlive?.("apply");
1166
1427
  internal.assertMutationAllowed?.("apply");
1428
+ assertSafePatches(patches, "store.apply()");
1167
1429
  const safePatches = sanitizePatches(patches);
1168
- const nextState = sanitizeReplacementState(safePatches ? (0, mutative.apply)(state, safePatches) : state);
1430
+ const baseState = state === internal.module ? internal.rootState : state;
1431
+ const nextState = sanitizeReplacementState(safePatches ? (0, mutative.apply)(baseState, safePatches) : baseState);
1432
+ assertKnownStateShape(nextState, internal.rootState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
1169
1433
  if (store.share === "main") validateSharedStateSerializable(nextState);
1170
1434
  internal.rootState = nextState;
1171
1435
  refreshSignalSlots(internal);
@@ -1211,6 +1475,7 @@ const create = (createState, options = {}) => {
1211
1475
  if (share) validateSharedActionPaths(initialState);
1212
1476
  store.getInitialState = () => initialState;
1213
1477
  internal.rootState = getRawState(store, internal, initialState, options);
1478
+ internal.stateSchema = createStateSchema(internal.rootState, store.isSliceStore);
1214
1479
  if (share) validateSharedStateSerializable(internal.rootState);
1215
1480
  markStoreReady(store);
1216
1481
  return {
@@ -1365,13 +1630,13 @@ const replaceExternalStoreState = (store, internal, source, { syncImmutable = tr
1365
1630
  const [, patches, inversePatches] = (0, mutative.create)(internal.rootState, (draft) => {
1366
1631
  replaceOwnEnumerable(draft, source);
1367
1632
  }, { enablePatches: true });
1368
- const safePatches = sanitizePatches((store.patch ? store.patch({
1633
+ const safePatches = sanitizeCheckedPatches((store.patch ? store.patch({
1369
1634
  patches,
1370
1635
  inversePatches
1371
1636
  }) : {
1372
1637
  patches,
1373
1638
  inversePatches
1374
- }).patches) ?? [];
1639
+ }).patches, "store.patch()");
1375
1640
  if (!safePatches.length) return;
1376
1641
  const updateImmutable = internal.updateImmutable;
1377
1642
  if (!syncImmutable) internal.updateImmutable = void 0;
@@ -1383,6 +1648,136 @@ const replaceExternalStoreState = (store, internal, source, { syncImmutable = tr
1383
1648
  emit(store, internal, safePatches);
1384
1649
  };
1385
1650
  //#endregion
1651
+ //#region packages/core/src/externalMutableAdapterUtils.ts
1652
+ const getMutableAdapterOwnEnumerableKeys = (value) => Reflect.ownKeys(value).filter((key) => Object.prototype.propertyIsEnumerable.call(value, key));
1653
+ const isMutableAdapterUnsafeKey = (key) => typeof key === "string" && isUnsafeKey(key);
1654
+ const isArrayIndexKey = (key) => {
1655
+ if (typeof key !== "string") return false;
1656
+ const index = Number(key);
1657
+ return Number.isInteger(index) && index >= 0 && index < 2 ** 32 - 1 && String(index) === key;
1658
+ };
1659
+ const isObjectRecord = (value) => Object.prototype.toString.call(value) === "[object Object]";
1660
+ const assertCanSetMutableAdapterPublicStateKey = (publicState, key) => {
1661
+ if (Object.prototype.hasOwnProperty.call(publicState, key)) return;
1662
+ if (Object.isExtensible(publicState)) return;
1663
+ throw new StateSchemaError(`Unknown state key '${String(key)}' cannot be added after store initialization. Coaction state schema is fixed.`);
1664
+ };
1665
+ const ensureMutableAdapterRawDescriptor = (rawState, mutableState, publicState, key) => {
1666
+ if (rawState === mutableState) return;
1667
+ const rawDescriptor = Object.getOwnPropertyDescriptor(rawState, key);
1668
+ if (rawDescriptor?.get && rawDescriptor.set) return;
1669
+ const publicDescriptor = Object.getOwnPropertyDescriptor(publicState, key);
1670
+ if (!publicDescriptor || rawDescriptor?.configurable === false) return;
1671
+ Object.defineProperty(rawState, key, {
1672
+ get: () => mutableState[key],
1673
+ set: (value) => {
1674
+ mutableState[key] = value;
1675
+ },
1676
+ configurable: true,
1677
+ enumerable: publicDescriptor.enumerable
1678
+ });
1679
+ };
1680
+ const replaceMutableAdapterState = (rawState, mutableState, publicState, source) => {
1681
+ const nextKeys = /* @__PURE__ */ new Set();
1682
+ for (const key of getMutableAdapterOwnEnumerableKeys(source)) {
1683
+ if (isMutableAdapterUnsafeKey(key)) continue;
1684
+ if (typeof source[key] === "function") continue;
1685
+ nextKeys.add(key);
1686
+ }
1687
+ nextKeys.forEach((key) => {
1688
+ assertCanSetMutableAdapterPublicStateKey(publicState, key);
1689
+ });
1690
+ for (const key of getMutableAdapterOwnEnumerableKeys(rawState)) {
1691
+ if (isMutableAdapterUnsafeKey(key)) {
1692
+ delete rawState[key];
1693
+ delete mutableState[key];
1694
+ continue;
1695
+ }
1696
+ if (typeof rawState[key] === "function") continue;
1697
+ if (!nextKeys.has(key)) {
1698
+ delete rawState[key];
1699
+ delete mutableState[key];
1700
+ }
1701
+ }
1702
+ const rawSeen = /* @__PURE__ */ new WeakMap();
1703
+ const mutableSeen = /* @__PURE__ */ new WeakMap();
1704
+ const publicSeen = /* @__PURE__ */ new WeakMap();
1705
+ rawSeen.set(source, rawState);
1706
+ mutableSeen.set(source, mutableState);
1707
+ publicSeen.set(source, publicState);
1708
+ nextKeys.forEach((key) => {
1709
+ ensureMutableAdapterRawDescriptor(rawState, mutableState, publicState, key);
1710
+ rawState[key] = sanitizeReplacementState(source[key], rawSeen);
1711
+ mutableState[key] = sanitizeReplacementState(source[key], mutableSeen);
1712
+ publicState[key] = sanitizeReplacementState(source[key], publicSeen);
1713
+ });
1714
+ };
1715
+ const applyMutableAdapterPatches = (baseState, patches, rawState, mutableState, publicState) => {
1716
+ assertSafePatches(patches, "mutable adapter apply()");
1717
+ replaceMutableAdapterState(rawState, mutableState, publicState, (0, mutative.apply)(toMutableAdapterSnapshot(baseState === publicState ? rawState : baseState), patches));
1718
+ };
1719
+ const toMutableAdapterSnapshot = (value, visited = /* @__PURE__ */ new WeakMap()) => {
1720
+ if (Array.isArray(value)) {
1721
+ if (visited.has(value)) return visited.get(value);
1722
+ const next = [];
1723
+ next.length = value.length;
1724
+ visited.set(value, next);
1725
+ for (let index = 0; index < value.length; index += 1) if (Object.prototype.hasOwnProperty.call(value, index)) next[index] = toMutableAdapterSnapshot(value[index], visited);
1726
+ const source = value;
1727
+ const target = next;
1728
+ for (const key of getMutableAdapterOwnEnumerableKeys(value)) {
1729
+ if (isArrayIndexKey(key) || isMutableAdapterUnsafeKey(key)) continue;
1730
+ const child = source[key];
1731
+ if (typeof child !== "function") target[key] = toMutableAdapterSnapshot(child, visited);
1732
+ }
1733
+ return next;
1734
+ }
1735
+ if (typeof value === "object" && value !== null) {
1736
+ if (!isObjectRecord(value)) return value;
1737
+ if (visited.has(value)) return visited.get(value);
1738
+ const next = {};
1739
+ visited.set(value, next);
1740
+ for (const key of getMutableAdapterOwnEnumerableKeys(value)) {
1741
+ if (isMutableAdapterUnsafeKey(key)) continue;
1742
+ const child = value[key];
1743
+ if (typeof child !== "function") next[key] = toMutableAdapterSnapshot(child, visited);
1744
+ }
1745
+ return next;
1746
+ }
1747
+ return value;
1748
+ };
1749
+ const snapshotMutableAdapterPureState = (store) => toMutableAdapterSnapshot(store.getPureState());
1750
+ const isEqualMutableAdapterSnapshot = (left, right, visited = /* @__PURE__ */ new WeakMap()) => {
1751
+ if (Object.is(left, right)) return true;
1752
+ if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) return false;
1753
+ const leftIsArray = Array.isArray(left);
1754
+ const rightIsArray = Array.isArray(right);
1755
+ if (leftIsArray || rightIsArray) {
1756
+ if (!leftIsArray || !rightIsArray || left.length !== right.length) return false;
1757
+ } else if (!isObjectRecord(left) || !isObjectRecord(right)) return false;
1758
+ let seenTargets = visited.get(left);
1759
+ if (!seenTargets) {
1760
+ seenTargets = /* @__PURE__ */ new WeakSet();
1761
+ visited.set(left, seenTargets);
1762
+ } else if (seenTargets.has(right)) return true;
1763
+ seenTargets.add(right);
1764
+ const leftRecord = left;
1765
+ const rightRecord = right;
1766
+ const leftKeys = getMutableAdapterOwnEnumerableKeys(left);
1767
+ const rightKeys = getMutableAdapterOwnEnumerableKeys(right);
1768
+ if (leftKeys.length !== rightKeys.length) return false;
1769
+ for (const key of leftKeys) {
1770
+ if (!Object.prototype.hasOwnProperty.call(rightRecord, key)) return false;
1771
+ if (!isEqualMutableAdapterSnapshot(leftRecord[key], rightRecord[key], visited)) return false;
1772
+ }
1773
+ return true;
1774
+ };
1775
+ //#endregion
1776
+ exports.StateSchemaError = StateSchemaError;
1777
+ exports.UnsafePatchPathError = UnsafePatchPathError;
1778
+ exports.applyMutableAdapterPatches = applyMutableAdapterPatches;
1779
+ exports.applyRootReplacementWithPatches = applyRootReplacementWithPatches;
1780
+ exports.assertSafePatches = assertSafePatches;
1386
1781
  Object.defineProperty(exports, "computed", {
1387
1782
  enumerable: true,
1388
1783
  get: function() {
@@ -1392,6 +1787,7 @@ Object.defineProperty(exports, "computed", {
1392
1787
  exports.create = create;
1393
1788
  exports.createBinder = createBinder;
1394
1789
  exports.createReactiveTracker = createReactiveTracker;
1790
+ exports.createRootReplacementPatches = createRootReplacementPatches;
1395
1791
  exports.defineExternalStoreAdapter = defineExternalStoreAdapter;
1396
1792
  Object.defineProperty(exports, "effect", {
1397
1793
  enumerable: true,
@@ -1411,6 +1807,7 @@ Object.defineProperty(exports, "endBatch", {
1411
1807
  return alien_signals.endBatch;
1412
1808
  }
1413
1809
  });
1810
+ exports.getMutableAdapterOwnEnumerableKeys = getMutableAdapterOwnEnumerableKeys;
1414
1811
  Object.defineProperty(exports, "isComputed", {
1415
1812
  enumerable: true,
1416
1813
  get: function() {
@@ -1429,16 +1826,21 @@ Object.defineProperty(exports, "isEffectScope", {
1429
1826
  return alien_signals.isEffectScope;
1430
1827
  }
1431
1828
  });
1829
+ exports.isEqualMutableAdapterSnapshot = isEqualMutableAdapterSnapshot;
1830
+ exports.isMutableAdapterUnsafeKey = isMutableAdapterUnsafeKey;
1432
1831
  Object.defineProperty(exports, "isSignal", {
1433
1832
  enumerable: true,
1434
1833
  get: function() {
1435
1834
  return alien_signals.isSignal;
1436
1835
  }
1437
1836
  });
1837
+ exports.isStateSchemaError = isStateSchemaError;
1438
1838
  exports.onStoreReady = onStoreReady;
1439
1839
  exports.replaceExternalStoreState = replaceExternalStoreState;
1840
+ exports.replaceMutableAdapterState = replaceMutableAdapterState;
1440
1841
  exports.replaceOwnEnumerable = replaceOwnEnumerable;
1441
1842
  exports.sanitizeInitialStateValue = sanitizeInitialStateValue;
1843
+ exports.sanitizePatches = sanitizePatches;
1442
1844
  exports.sanitizeReplacementState = sanitizeReplacementState;
1443
1845
  Object.defineProperty(exports, "signal", {
1444
1846
  enumerable: true,
@@ -1446,12 +1848,14 @@ Object.defineProperty(exports, "signal", {
1446
1848
  return alien_signals.signal;
1447
1849
  }
1448
1850
  });
1851
+ exports.snapshotMutableAdapterPureState = snapshotMutableAdapterPureState;
1449
1852
  Object.defineProperty(exports, "startBatch", {
1450
1853
  enumerable: true,
1451
1854
  get: function() {
1452
1855
  return alien_signals.startBatch;
1453
1856
  }
1454
1857
  });
1858
+ exports.toMutableAdapterSnapshot = toMutableAdapterSnapshot;
1455
1859
  Object.defineProperty(exports, "trigger", {
1456
1860
  enumerable: true,
1457
1861
  get: function() {