coaction 2.0.0 → 3.0.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
@@ -1,34 +1,53 @@
1
- import { apply, create as create$1, isDraft } from "mutative";
2
1
  import { createTransport } from "data-transport";
3
- import { computed, computed as computed$1, effect, effectScope, endBatch, endBatch as endBatch$1, isComputed, isEffect, isEffectScope, isSignal, setActiveSub, signal, signal as signal$1, startBatch, startBatch as startBatch$1, trigger } from "alien-signals";
4
- import * as alienSignalsSystem from "alien-signals/system";
5
- //#region packages/core/src/global.ts
6
- const getGlobal = () => {
7
- let _global;
8
- if (typeof window !== "undefined") _global = window;
9
- else if (typeof global !== "undefined") _global = global;
10
- else if (typeof self !== "undefined") _global = self;
11
- else _global = {};
12
- return _global;
2
+ import { computed, computed as computed$1, effect, effectScope, endBatch, endBatch as endBatch$1, isComputed, isEffect, isEffectScope, isSignal, signal, signal as signal$1, startBatch, startBatch as startBatch$1, trigger } from "alien-signals";
3
+ import { apply, create as create$1, isDraft } from "mutative";
4
+ //#region packages/core/src/lifecycle.ts
5
+ const reportLifecycleError = (error) => {
6
+ if (process.env.NODE_ENV === "development") console.error(error);
13
7
  };
14
- //#endregion
15
- //#region packages/core/src/constant.ts
16
- const WorkerType = getGlobal().SharedWorkerGlobalScope ? "SharedWorkerInternal" : globalThis.WorkerGlobalScope ? "WebWorkerInternal" : null;
17
- const bindSymbol = Symbol("bind");
18
- //#endregion
19
- //#region packages/core/src/wrapStore.ts
20
- /**
21
- * Convert a store object into Coaction's callable store shape.
22
- *
23
- * @remarks
24
- * Framework bindings use this to attach selector-aware readers while
25
- * preserving the underlying store API on the returned function object. Most
26
- * applications should call {@link create} instead of using `wrapStore()`
27
- * directly.
28
- */
29
- const wrapStore = (store, getState = () => store.getState()) => {
30
- const { name, ..._store } = store;
31
- return Object.assign({ [name]: (...args) => getState(...args) }[name], _store);
8
+ const tryDestroyStore = (store) => {
9
+ try {
10
+ store.destroy?.();
11
+ } catch (error) {
12
+ reportLifecycleError(error);
13
+ }
14
+ };
15
+ const failStoreSetup = (store, error) => {
16
+ tryDestroyStore(store);
17
+ throw error;
18
+ };
19
+ const failTransportInitialization = (transport, error) => {
20
+ try {
21
+ transport?.dispose?.();
22
+ } catch (disposeError) {
23
+ reportLifecycleError(disposeError);
24
+ }
25
+ throw error;
26
+ };
27
+ const readyStores = /* @__PURE__ */ new WeakSet();
28
+ const readyCallbacks = /* @__PURE__ */ new WeakMap();
29
+ const onStoreReady = (store, callback) => {
30
+ if (readyStores.has(store)) {
31
+ callback();
32
+ return () => void 0;
33
+ }
34
+ let callbacks = readyCallbacks.get(store);
35
+ if (!callbacks) {
36
+ callbacks = /* @__PURE__ */ new Set();
37
+ readyCallbacks.set(store, callbacks);
38
+ }
39
+ callbacks.add(callback);
40
+ return () => {
41
+ callbacks?.delete(callback);
42
+ };
43
+ };
44
+ const markStoreReady = (store) => {
45
+ readyStores.add(store);
46
+ const callbacks = readyCallbacks.get(store);
47
+ if (!callbacks) return;
48
+ readyCallbacks.delete(store);
49
+ callbacks.forEach((callback) => callback());
50
+ callbacks.clear();
32
51
  };
33
52
  //#endregion
34
53
  //#region packages/core/src/utils.ts
@@ -38,13 +57,40 @@ const isEqual = (x, y) => {
38
57
  };
39
58
  const isUnsafeKey = (key) => key === "__proto__" || key === "prototype" || key === "constructor";
40
59
  const isUnsafePathSegment = (segment) => typeof segment === "string" && isUnsafeKey(segment);
60
+ var UnsafePatchPathError = class extends Error {
61
+ name = "UnsafePatchPathError";
62
+ };
63
+ var StateSchemaError = class extends Error {
64
+ name = "StateSchemaError";
65
+ };
66
+ const isStateSchemaError = (error) => error instanceof StateSchemaError || error instanceof Error && error.name === "StateSchemaError";
41
67
  const hasUnsafePatchPath = (path) => {
42
68
  return (Array.isArray(path) ? path : typeof path === "string" ? path.split("/").filter(Boolean).map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")) : []).some(isUnsafePathSegment);
43
69
  };
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);
70
+ const formatPatchPath = (path) => Array.isArray(path) ? path.map((segment) => String(segment)).join(".") : String(path);
71
+ const getUnsafePatchPaths = (patches) => patches?.filter((patch) => hasUnsafePatchPath(patch.path)) ?? [];
72
+ const assertSafePatches = (patches, source = "patches") => {
73
+ const unsafePatches = getUnsafePatchPaths(patches);
74
+ if (!unsafePatches.length) return;
75
+ const paths = unsafePatches.map((patch) => `'${formatPatchPath(patch.path)}'`).join(", ");
76
+ throw new UnsafePatchPathError(`Unsafe patch path${unsafePatches.length > 1 ? "s" : ""} ${paths} cannot be applied from ${source}.`);
77
+ };
78
+ const warnDroppedUnsafePatches = (unsafePatches, source) => {
79
+ if (process.env.NODE_ENV !== "development" || !unsafePatches.length) return;
80
+ const paths = unsafePatches.map((patch) => `'${formatPatchPath(patch.path)}'`).join(", ");
81
+ console.warn(`Coaction dropped unsafe patch path${unsafePatches.length > 1 ? "s" : ""} ${paths} from ${source}.`);
82
+ };
83
+ const sanitizePatches = (patches, options = {}) => {
84
+ if (options.warnOnDropped) warnDroppedUnsafePatches(getUnsafePatchPaths(patches), options.source ?? "patches");
85
+ return patches?.filter((patch) => !hasUnsafePatchPath(patch.path)).map((patch) => Object.prototype.hasOwnProperty.call(patch, "value") ? {
86
+ ...patch,
87
+ value: sanitizeReplacementState(patch.value)
88
+ } : patch);
89
+ };
90
+ const sanitizeCheckedPatches = (patches, source) => {
91
+ assertSafePatches(patches, source);
92
+ return sanitizePatches(patches) ?? [];
93
+ };
48
94
  const setOwnEnumerable = (target, key, value) => {
49
95
  if (typeof key === "string" && isUnsafeKey(key)) return;
50
96
  target[key] = value;
@@ -53,7 +99,59 @@ const getOwnEnumerableKeys = (source) => {
53
99
  if (typeof source !== "object" || source === null) return [];
54
100
  return Reflect.ownKeys(source).filter((key) => Object.prototype.propertyIsEnumerable.call(source, key));
55
101
  };
56
- const isArrayIndexKey$1 = (key) => {
102
+ const getOwnSchemaKeys = (source) => {
103
+ if (typeof source !== "object" || source === null) return [];
104
+ return Reflect.ownKeys(source).filter((key) => !(typeof key === "string" && isUnsafeKey(key)));
105
+ };
106
+ const formatSchemaPath = (path) => path.length ? path.map((key) => String(key)).join(".") : "<root>";
107
+ const assertKnownSchemaKey = (knownKeys, key, path) => {
108
+ if (typeof key === "string" && isUnsafeKey(key)) return;
109
+ if (knownKeys.has(key)) return;
110
+ throw new StateSchemaError(`Unknown state key '${formatSchemaPath([...path, key])}' cannot be added after store initialization. Coaction state schema is fixed.`);
111
+ };
112
+ const assertKnownSliceObject = (key, value) => {
113
+ if (typeof value === "object" && value !== null) return;
114
+ throw new StateSchemaError(`State slice '${String(key)}' must remain an object after store initialization. Coaction slice schema is fixed.`);
115
+ };
116
+ const assertKnownSlicePresent = (source, key) => {
117
+ if (Object.prototype.hasOwnProperty.call(source, key)) return;
118
+ throw new StateSchemaError(`State slice '${String(key)}' cannot be removed after store initialization. Coaction slice schema is fixed.`);
119
+ };
120
+ const createStateSchema = (rootState, isSliceStore) => {
121
+ const rootKeys = new Set(getOwnSchemaKeys(rootState));
122
+ if (!isSliceStore) return { rootKeys };
123
+ const sliceKeys = /* @__PURE__ */ new Map();
124
+ if (typeof rootState === "object" && rootState !== null) {
125
+ const rootRecord = rootState;
126
+ rootKeys.forEach((key) => {
127
+ const slice = rootRecord[key];
128
+ if (typeof slice === "object" && slice !== null) sliceKeys.set(key, new Set(getOwnSchemaKeys(slice)));
129
+ });
130
+ }
131
+ return {
132
+ rootKeys,
133
+ sliceKeys
134
+ };
135
+ };
136
+ const assertKnownStateShape = (source, rootState, schema, isSliceStore, options = {}) => {
137
+ if (typeof source !== "object" || source === null) return;
138
+ const rootKeys = schema?.rootKeys ?? new Set(getOwnSchemaKeys(rootState));
139
+ const sourceRecord = source;
140
+ const knownSliceEntries = schema?.sliceKeys;
141
+ if (isSliceStore && options.requireSliceRoots && knownSliceEntries) knownSliceEntries.forEach((_, key) => {
142
+ assertKnownSlicePresent(sourceRecord, key);
143
+ });
144
+ for (const key of getOwnEnumerableKeys(source)) {
145
+ assertKnownSchemaKey(rootKeys, key, []);
146
+ if (!isSliceStore) continue;
147
+ const slice = sourceRecord[key];
148
+ 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);
149
+ if (!knownSliceKeys) continue;
150
+ assertKnownSliceObject(key, slice);
151
+ for (const sliceKey of getOwnEnumerableKeys(slice)) assertKnownSchemaKey(knownSliceKeys, sliceKey, [key]);
152
+ }
153
+ };
154
+ const isArrayIndexKey = (key) => {
57
155
  if (typeof key !== "string") return false;
58
156
  const index = Number(key);
59
157
  return Number.isInteger(index) && index >= 0 && index < 2 ** 32 - 1 && String(index) === key;
@@ -62,20 +160,6 @@ const assignOwnEnumerable = (target, source, seen = /* @__PURE__ */ new WeakMap(
62
160
  if (!seen.has(source)) seen.set(source, target);
63
161
  for (const key of getOwnEnumerableKeys(source)) setOwnEnumerable(target, key, sanitizeReplacementState(source[key], seen));
64
162
  };
65
- const replaceOwnEnumerable = (target, source) => {
66
- const seen = /* @__PURE__ */ new WeakMap();
67
- seen.set(source, target);
68
- const nextKeys = /* @__PURE__ */ new Set();
69
- for (const key of getOwnEnumerableKeys(source)) {
70
- if (typeof key === "string" && isUnsafeKey(key)) continue;
71
- if (typeof source[key] === "function") continue;
72
- nextKeys.add(key);
73
- }
74
- for (const key of getOwnEnumerableKeys(target)) if (!nextKeys.has(key)) delete target[key];
75
- nextKeys.forEach((key) => {
76
- setOwnEnumerable(target, key, sanitizeReplacementState(source[key], seen));
77
- });
78
- };
79
163
  const cloneOwnEnumerable = (source) => {
80
164
  const target = {};
81
165
  assignOwnEnumerable(target, source);
@@ -91,7 +175,7 @@ const sanitizeReplacementState = (source, seen = /* @__PURE__ */ new WeakMap())
91
175
  seen.set(source, target);
92
176
  for (let index = 0; index < source.length; index += 1) if (Object.prototype.hasOwnProperty.call(source, index)) target[index] = sanitizeReplacementState(source[index], seen);
93
177
  for (const key of getOwnEnumerableKeys(source)) {
94
- if (isArrayIndexKey$1(key) || typeof key === "string" && isUnsafeKey(key)) continue;
178
+ if (isArrayIndexKey(key) || typeof key === "string" && isUnsafeKey(key)) continue;
95
179
  const value = source[key];
96
180
  if (typeof value === "function") continue;
97
181
  setOwnEnumerable(target, key, sanitizeReplacementState(value, seen));
@@ -120,7 +204,7 @@ const sanitizeInitialStateValue = (source, seen = /* @__PURE__ */ new WeakMap())
120
204
  seen.set(source, target);
121
205
  for (let index = 0; index < source.length; index += 1) if (Object.prototype.hasOwnProperty.call(source, index)) target[index] = sanitizeInitialStateValue(source[index], seen);
122
206
  for (const key of getOwnEnumerableKeys(source)) {
123
- if (isArrayIndexKey$1(key) || typeof key === "string" && isUnsafeKey(key)) continue;
207
+ if (isArrayIndexKey(key) || typeof key === "string" && isUnsafeKey(key)) continue;
124
208
  setOwnEnumerable(target, key, sanitizeInitialStateValue(source[key], seen));
125
209
  }
126
210
  return target;
@@ -165,319 +249,632 @@ const uuid = () => {
165
249
  });
166
250
  };
167
251
  //#endregion
252
+ //#region packages/core/src/computed.ts
253
+ const isObjectLike = (value) => typeof value === "object" && value !== null;
254
+ const runComputedRead = (internal, read) => {
255
+ internal.computedReadDepth = (internal.computedReadDepth ?? 0) + 1;
256
+ try {
257
+ return read();
258
+ } finally {
259
+ internal.computedReadDepth -= 1;
260
+ }
261
+ };
262
+ var Computed = class {
263
+ deps;
264
+ fn;
265
+ constructor(deps, fn) {
266
+ this.deps = deps;
267
+ this.fn = fn;
268
+ }
269
+ createGetter({ internal }) {
270
+ const memoByReceiver = /* @__PURE__ */ new WeakMap();
271
+ const lastArgs = /* @__PURE__ */ new WeakMap();
272
+ const lastResult = /* @__PURE__ */ new WeakMap();
273
+ const fallbackReceiver = {};
274
+ const evaluate = (receiver) => {
275
+ const args = this.deps(internal.module);
276
+ if (!lastArgs.has(receiver) || !areShallowEqualWithArray(lastArgs.get(receiver), args)) lastResult.set(receiver, this.fn.apply(receiver, args));
277
+ lastArgs.set(receiver, args);
278
+ return lastResult.get(receiver);
279
+ };
280
+ return function() {
281
+ const receiver = typeof this === "object" && this !== null ? this : fallbackReceiver;
282
+ if (internal.isBatching) return evaluate(receiver);
283
+ let accessor = memoByReceiver.get(receiver);
284
+ if (!accessor) {
285
+ accessor = computed$1(() => runComputedRead(internal, () => evaluate(receiver)));
286
+ memoByReceiver.set(receiver, accessor);
287
+ }
288
+ return accessor();
289
+ };
290
+ }
291
+ };
292
+ const createCachedGetter = (internal, getter) => {
293
+ const accessors = /* @__PURE__ */ new WeakMap();
294
+ const fallbackReceiver = {};
295
+ return function() {
296
+ const receiver = typeof this === "object" && this !== null ? this : fallbackReceiver;
297
+ if (internal.isBatching) return getter.call(receiver);
298
+ let accessor = accessors.get(receiver);
299
+ if (!accessor) {
300
+ accessor = computed$1(() => runComputedRead(internal, () => getter.call(receiver)));
301
+ accessors.set(receiver, accessor);
302
+ }
303
+ return accessor();
304
+ };
305
+ };
306
+ const createTrackedStateReader = (internal, read, initialValue) => {
307
+ const slotSignal = signal$1(initialValue);
308
+ const slotVersionSignal = signal$1(0);
309
+ let slotVersion = 0;
310
+ (internal.signalSlots ??= /* @__PURE__ */ new Set()).add({ refresh: () => {
311
+ const nextValue = read();
312
+ slotSignal(nextValue);
313
+ if (internal.mutableInstance && isObjectLike(nextValue)) {
314
+ slotVersion += 1;
315
+ slotVersionSignal(slotVersion);
316
+ }
317
+ } });
318
+ return () => {
319
+ const currentValue = slotSignal();
320
+ if (internal.mutableInstance && isObjectLike(currentValue)) slotVersionSignal();
321
+ return read();
322
+ };
323
+ };
324
+ const refreshSignalSlots = (internal) => {
325
+ if (!internal.signalSlots?.size) return;
326
+ startBatch$1();
327
+ try {
328
+ internal.signalSlots.forEach((slot) => slot.refresh());
329
+ } finally {
330
+ endBatch$1();
331
+ }
332
+ };
333
+ //#endregion
168
334
  //#region packages/core/src/sharedState.ts
169
- const formatPropertyPath = (path) => path.length ? path.map((key) => String(key)).join(".") : "<root>";
170
- const isPlainObject = (value) => {
171
- const prototype = Object.getPrototypeOf(value);
172
- return prototype === Object.prototype || prototype === null;
335
+ const formatPath = (path) => path.length ? path.map((key) => String(key)).join(".") : "<root>";
336
+ const unsupported = (label, path) => {
337
+ throw new TypeError(`${label} is not supported in shared store mode because transport synchronization uses JSON. Found unsupported value at ${formatPath(path)}.`);
338
+ };
339
+ const getDescriptors = (value, path) => {
340
+ try {
341
+ return Object.getOwnPropertyDescriptors(value);
342
+ } catch {
343
+ return unsupported("Uninspectable state", path);
344
+ }
173
345
  };
174
- const isArrayIndexKey = (key, length) => {
175
- if (key === "") return false;
346
+ const getPrototype = (value, path) => {
347
+ try {
348
+ return Object.getPrototypeOf(value);
349
+ } catch {
350
+ return unsupported("Uninspectable state prototype", path);
351
+ }
352
+ };
353
+ const assertNoInheritedToJson = (prototype, path) => {
354
+ let current = prototype;
355
+ while (current) {
356
+ let descriptor;
357
+ try {
358
+ descriptor = Object.getOwnPropertyDescriptor(current, "toJSON");
359
+ } catch {
360
+ unsupported("Uninspectable inherited toJSON state", path);
361
+ }
362
+ if (descriptor) {
363
+ if (!Object.prototype.hasOwnProperty.call(descriptor, "value") || typeof descriptor.value === "function") unsupported("Inherited toJSON state", path);
364
+ return;
365
+ }
366
+ current = getPrototype(current, path);
367
+ }
368
+ };
369
+ const isArrayIndex = (key, length) => {
176
370
  const index = Number(key);
177
- return Number.isInteger(index) && index >= 0 && index < length && String(index) === key;
178
- };
179
- const findSymbolKeyViolation = (value, path = [], seen = /* @__PURE__ */ new WeakSet()) => {
180
- if (typeof value !== "object" || value === null) return;
181
- if (seen.has(value)) return;
182
- seen.add(value);
183
- const descriptors = Object.getOwnPropertyDescriptors(value);
184
- for (const key of getOwnEnumerableKeys(value)) {
185
- const nextPath = [...path, key];
186
- if (typeof key === "symbol") return {
187
- type: "symbol-key",
188
- path: nextPath
189
- };
190
- const descriptor = descriptors[key];
191
- if (descriptor && Object.prototype.hasOwnProperty.call(descriptor, "value")) {
192
- const violation = findSymbolKeyViolation(descriptor.value, nextPath, seen);
193
- if (violation) return violation;
371
+ return key !== "" && Number.isSafeInteger(index) && index >= 0 && index < length && String(index) === key;
372
+ };
373
+ const pushDataProperty = (work, descriptor, key, path, actionRoot = false) => {
374
+ const nextPath = [...path, key];
375
+ if (!descriptor) return unsupported("Sparse array state", nextPath);
376
+ if (!descriptor.enumerable) return unsupported("Non-enumerable data state", nextPath);
377
+ if (!Object.prototype.hasOwnProperty.call(descriptor, "value")) return unsupported("Accessor-backed state", nextPath);
378
+ work.push({
379
+ actionRoot,
380
+ path: nextPath,
381
+ value: descriptor.value
382
+ });
383
+ };
384
+ const assertSharedJsonWork = (work, isSliceStore = false) => {
385
+ const seen = /* @__PURE__ */ new WeakSet();
386
+ while (work.length) {
387
+ const { actionRoot = false, path, value } = work.pop();
388
+ if (value === null) continue;
389
+ switch (typeof value) {
390
+ case "string":
391
+ case "boolean": continue;
392
+ case "number":
393
+ if (!Number.isFinite(value)) unsupported("NaN or infinite number state", path);
394
+ if (Object.is(value, -0)) unsupported("Negative zero state", path);
395
+ continue;
396
+ case "bigint": unsupported("BigInt-valued state", path);
397
+ case "undefined": unsupported("Undefined-valued state", path);
398
+ case "function": unsupported("Function-valued state", path);
399
+ case "symbol": throw new TypeError(`Symbol-valued state is not supported in shared store mode because transport synchronization uses JSON. Found symbol value at ${formatPath(path)}.`);
400
+ default: break;
401
+ }
402
+ const object = value;
403
+ if (seen.has(object)) unsupported("Repeated state reference", path);
404
+ seen.add(object);
405
+ const descriptors = getDescriptors(object, path);
406
+ if (Array.isArray(object)) {
407
+ const prototype = getPrototype(object, path);
408
+ if (prototype !== Array.prototype) unsupported("Non-plain array state", path);
409
+ assertNoInheritedToJson(prototype, path);
410
+ const length = descriptors.length?.value;
411
+ if (!Number.isSafeInteger(length) || length < 0) unsupported("Invalid array state", path);
412
+ for (const key of Reflect.ownKeys(descriptors)) {
413
+ if (key === "length") continue;
414
+ if (typeof key === "symbol") throw new TypeError(`Symbol-keyed state is not supported in shared store mode because transport synchronization uses JSON and string action paths. Found symbol key at ${formatPath([...path, key])}.`);
415
+ if (!isArrayIndex(key, length)) unsupported("Non-index array property state", [...path, key]);
416
+ }
417
+ for (let index = 0; index < length; index += 1) pushDataProperty(work, descriptors[index], index, path);
418
+ continue;
419
+ }
420
+ const prototype = getPrototype(object, path);
421
+ if (prototype !== Object.prototype && prototype !== null) unsupported("Non-plain object state", path);
422
+ assertNoInheritedToJson(prototype, path);
423
+ for (const key of Reflect.ownKeys(descriptors)) {
424
+ if (typeof key === "symbol") throw new TypeError(`Symbol-keyed state is not supported in shared store mode because transport synchronization uses JSON and string action paths. Found symbol key at ${formatPath([...path, key])}.`);
425
+ if (isUnsafeKey(key)) unsupported("Unsafe-keyed state", [...path, key]);
426
+ const descriptor = descriptors[key];
427
+ if (actionRoot && descriptor) {
428
+ const isDataProperty = Object.prototype.hasOwnProperty.call(descriptor, "value");
429
+ if (isDataProperty && typeof descriptor.value === "function") continue;
430
+ if (actionRoot === "initial" && (!isDataProperty || descriptor.value instanceof Computed)) continue;
431
+ }
432
+ pushDataProperty(work, descriptor, key, path, isSliceStore && path.length === 0 ? "initial" : false);
194
433
  }
195
434
  }
196
435
  };
197
- const findJsonViolation = (value, path = [], ancestors = /* @__PURE__ */ new WeakSet()) => {
198
- switch (typeof value) {
199
- case "symbol": return {
200
- type: "symbol-value",
201
- path
202
- };
203
- case "bigint": return {
204
- type: "bigint",
205
- path
206
- };
207
- case "undefined": return {
208
- type: "undefined",
209
- path
210
- };
211
- case "function": return {
212
- type: "function",
213
- path
214
- };
215
- case "number": return Number.isFinite(value) ? void 0 : {
216
- type: "non-finite-number",
217
- path
218
- };
219
- default: break;
436
+ const assertSharedJsonValue = (root) => {
437
+ assertSharedJsonWork([{
438
+ path: [],
439
+ value: root
440
+ }]);
441
+ };
442
+ const validateSharedInitialState = (root, isSliceStore = false) => {
443
+ assertSharedJsonWork([{
444
+ actionRoot: isSliceStore ? false : "initial",
445
+ path: [],
446
+ value: root
447
+ }], isSliceStore);
448
+ };
449
+ const validateSharedReplacementSource = (root) => {
450
+ if (typeof root !== "object" || root === null || Array.isArray(root)) unsupported("Non-record replacement state", []);
451
+ assertSharedJsonWork([{
452
+ actionRoot: "replacement",
453
+ path: [],
454
+ value: root
455
+ }]);
456
+ };
457
+ const encodeSharedJson = (value) => {
458
+ assertSharedJsonValue(value);
459
+ const encoded = JSON.stringify(value);
460
+ if (typeof encoded !== "string") throw new TypeError("Shared transport value could not be encoded as JSON.");
461
+ return encoded;
462
+ };
463
+ const decodeSharedJson = (encoded) => {
464
+ if (typeof encoded !== "string") throw new TypeError("Shared transport payload must be a JSON string.");
465
+ let value;
466
+ try {
467
+ value = JSON.parse(encoded);
468
+ } catch {
469
+ throw new TypeError("Shared transport payload is not valid JSON.");
220
470
  }
221
- if (typeof value !== "object" || value === null) return;
222
- if (ancestors.has(value)) return {
223
- type: "circular-reference",
224
- path
225
- };
226
- if (Array.isArray(value)) {
227
- ancestors.add(value);
228
- for (let index = 0; index < value.length; index += 1) if (!Object.prototype.hasOwnProperty.call(value, index)) return {
229
- type: "array-hole",
230
- path: [...path, index]
231
- };
232
- for (const key of getOwnEnumerableKeys(value)) {
233
- const nextPath = [...path, key];
234
- if (typeof key === "symbol") return {
235
- type: "symbol-key",
236
- path: nextPath
237
- };
238
- if (!isArrayIndexKey(key, value.length)) return {
239
- type: "array-property",
240
- path: nextPath
241
- };
242
- const violation = findJsonViolation(value[Number(key)], nextPath, ancestors);
243
- if (violation) return violation;
471
+ assertSharedJsonValue(value);
472
+ return value;
473
+ };
474
+ const validateSharedActionPaths = (state, isSliceStore = false) => {
475
+ const actions = /* @__PURE__ */ new Set();
476
+ const work = [{
477
+ actionRoot: !isSliceStore,
478
+ path: [],
479
+ value: state
480
+ }];
481
+ const seen = /* @__PURE__ */ new WeakSet();
482
+ while (work.length) {
483
+ const { actionRoot, path, value } = work.pop();
484
+ if (typeof value !== "object" || value === null || seen.has(value)) continue;
485
+ seen.add(value);
486
+ const descriptors = getDescriptors(value, path);
487
+ for (const key of Reflect.ownKeys(descriptors)) {
488
+ if (typeof key === "symbol") throw new TypeError(`Symbol-keyed state is not supported in shared store mode because transport synchronization uses JSON and string action paths. Found symbol key at ${formatPath([...path, key])}.`);
489
+ const descriptor = descriptors[key];
490
+ if (descriptor && Object.prototype.hasOwnProperty.call(descriptor, "value")) {
491
+ const nextPath = [...path, key];
492
+ if (actionRoot && typeof descriptor.value === "function") {
493
+ actions.add(JSON.stringify(nextPath));
494
+ continue;
495
+ }
496
+ work.push({
497
+ actionRoot: isSliceStore && path.length === 0,
498
+ path: nextPath,
499
+ value: descriptor.value
500
+ });
501
+ }
244
502
  }
245
- ancestors.delete(value);
246
- return;
247
503
  }
248
- if (!isPlainObject(value)) return {
249
- type: "non-plain-object",
250
- path
504
+ return actions;
505
+ };
506
+ const validateSharedStateSerializable = assertSharedJsonValue;
507
+ const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
508
+ const asRecord = (value, message) => {
509
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(message);
510
+ return value;
511
+ };
512
+ const decodeMessage = (encoded, type) => {
513
+ const message = asRecord(decodeSharedJson(encoded), "Invalid transport message");
514
+ if (message.v !== 1 || message.type !== type) throw new TypeError("Invalid transport message");
515
+ return message;
516
+ };
517
+ const readEpoch = (message) => {
518
+ if (typeof message.epoch !== "string" || message.epoch.length === 0) throw new TypeError("Invalid transport epoch");
519
+ return message.epoch;
520
+ };
521
+ const readSequence = (message) => {
522
+ if (typeof message.sequence !== "number" || !Number.isSafeInteger(message.sequence) || message.sequence < 0) throw new TypeError("Invalid transport sequence");
523
+ return message.sequence;
524
+ };
525
+ const readAction = (value) => {
526
+ if (!Array.isArray(value) || value.length === 0 || value.some((key) => typeof key !== "string" || isUnsafePathSegment(key))) throw new TypeError("Invalid transport action");
527
+ return [...value];
528
+ };
529
+ const readPath = (value, { allowUnsafe = false } = {}) => {
530
+ if (!Array.isArray(value) || value.length === 0) throw new TypeError("Invalid transport patch path");
531
+ const path = [];
532
+ for (const segment of value) {
533
+ if (typeof segment !== "string" && (typeof segment !== "number" || !Number.isSafeInteger(segment) || segment < 0) || !allowUnsafe && isUnsafePathSegment(segment)) throw new TypeError("Invalid transport patch path");
534
+ path.push(segment);
535
+ }
536
+ return path;
537
+ };
538
+ const readPatches = (value, options = {}) => {
539
+ if (!Array.isArray(value)) throw new TypeError("Invalid transport patches");
540
+ return value.map((candidate) => {
541
+ const patch = asRecord(candidate, "Invalid transport patch");
542
+ const path = readPath(patch.path, { allowUnsafe: options.allowUnsafePaths });
543
+ if (patch.op === "remove") {
544
+ if (hasOwn(patch, "value")) throw new TypeError("Invalid remove patch");
545
+ return {
546
+ op: "remove",
547
+ path
548
+ };
549
+ }
550
+ if (patch.op !== "add" && patch.op !== "replace" || !hasOwn(patch, "value")) throw new TypeError("Invalid transport patch");
551
+ return {
552
+ op: patch.op,
553
+ path,
554
+ value: patch.value
555
+ };
556
+ });
557
+ };
558
+ const validateUpdatePatches = (patches) => {
559
+ assertSharedJsonValue(patches);
560
+ readPatches(patches, { allowUnsafePaths: true });
561
+ };
562
+ const encodeExecuteRequest = (action, args) => encodeSharedJson({
563
+ action: readAction([...action]),
564
+ args,
565
+ type: "execute",
566
+ v: 1
567
+ });
568
+ const decodeExecuteRequest = (encoded) => {
569
+ const message = decodeMessage(encoded, "execute");
570
+ if (!Array.isArray(message.args)) throw new TypeError("Invalid transport arguments");
571
+ return {
572
+ action: readAction(message.action),
573
+ args: [...message.args]
574
+ };
575
+ };
576
+ const encodeExecuteResponse = (response) => encodeSharedJson({
577
+ ...response,
578
+ type: "execute-result",
579
+ v: 1
580
+ });
581
+ const decodeExecuteResponse = (encoded) => {
582
+ const message = decodeMessage(encoded, "execute-result");
583
+ const base = {
584
+ epoch: readEpoch(message),
585
+ sequence: readSequence(message)
251
586
  };
252
- if (typeof value.toJSON === "function") return {
253
- type: "to-json",
254
- path
587
+ if (message.ok === true) return hasOwn(message, "value") ? {
588
+ ...base,
589
+ ok: true,
590
+ value: message.value
591
+ } : {
592
+ ...base,
593
+ ok: true
255
594
  };
256
- ancestors.add(value);
257
- for (const key of getOwnEnumerableKeys(value)) {
258
- const nextPath = [...path, key];
259
- if (typeof key === "symbol") return {
260
- type: "symbol-key",
261
- path: nextPath
262
- };
263
- const child = value[key];
264
- const violation = findJsonViolation(child, nextPath, ancestors);
265
- if (violation) return violation;
266
- }
267
- ancestors.delete(value);
268
- };
269
- const getViolationLabel = (violation) => {
270
- switch (violation.type) {
271
- case "bigint": return "BigInt-valued state";
272
- case "undefined": return "Undefined-valued state";
273
- case "function": return "Function-valued state";
274
- case "non-finite-number": return "NaN or infinite number state";
275
- case "non-plain-object": return "Non-plain object state";
276
- case "circular-reference": return "Circular state reference";
277
- case "array-hole": return "Sparse array state";
278
- case "array-property": return "Non-index array property state";
279
- case "to-json": return "Custom toJSON state";
280
- default: return;
281
- }
595
+ if (message.ok !== false || typeof message.error !== "string" || message.error.length === 0) throw new TypeError("Invalid execute response");
596
+ return {
597
+ ...base,
598
+ error: message.error,
599
+ ok: false
600
+ };
601
+ };
602
+ const encodeFullSyncRequest = () => encodeSharedJson({
603
+ type: "full-sync",
604
+ v: 1
605
+ });
606
+ const decodeFullSyncRequest = (encoded) => {
607
+ decodeMessage(encoded, "full-sync");
282
608
  };
283
- const validateSharedActionPaths = (state) => {
284
- const violation = findSymbolKeyViolation(state);
285
- if (!violation) return;
286
- throw new Error(`Symbol-keyed state is not supported in shared store mode because transport synchronization uses JSON and string action paths. Found symbol key at ${formatPropertyPath(violation.path)}.`);
609
+ const encodeFullSyncResponse = (response) => encodeSharedJson({
610
+ ...response,
611
+ type: "full-sync-result",
612
+ v: 1
613
+ });
614
+ const decodeFullSyncResponse = (encoded) => {
615
+ const message = decodeMessage(encoded, "full-sync-result");
616
+ return {
617
+ epoch: readEpoch(message),
618
+ sequence: readSequence(message),
619
+ state: asRecord(message.state, "Invalid fullSync state")
620
+ };
287
621
  };
288
- const validateSharedStateSerializable = (state) => {
289
- const violation = findJsonViolation(state);
290
- if (!violation) return;
291
- if (violation.type === "symbol-key") throw new Error(`Symbol-keyed state is not supported in shared store mode because transport synchronization uses JSON and string action paths. Found symbol key at ${formatPropertyPath(violation.path)}.`);
292
- if (violation.type === "symbol-value") throw new Error(`Symbol-valued state is not supported in shared store mode because transport synchronization uses JSON. Found symbol value at ${formatPropertyPath(violation.path)}.`);
293
- throw new Error(`${getViolationLabel(violation)} is not supported in shared store mode because transport synchronization uses JSON. Found unsupported value at ${formatPropertyPath(violation.path)}.`);
622
+ const encodeUpdateMessage = (epoch, sequence, patches) => {
623
+ return encodeSharedJson({
624
+ epoch,
625
+ patches: readPatches(patches.map((patch) => patch.op === "remove" ? {
626
+ op: patch.op,
627
+ path: patch.path
628
+ } : {
629
+ op: patch.op,
630
+ path: patch.path,
631
+ value: patch.value
632
+ })),
633
+ sequence,
634
+ type: "update",
635
+ v: 1
636
+ });
637
+ };
638
+ const decodeUpdateMessage = (encoded) => {
639
+ const message = decodeMessage(encoded, "update");
640
+ return {
641
+ epoch: readEpoch(message),
642
+ patches: readPatches(message.patches),
643
+ sequence: readSequence(message)
644
+ };
294
645
  };
295
646
  //#endregion
296
- //#region packages/core/src/asyncClientStore.ts
297
- const parseFullSyncState$1 = (state) => {
298
- const parsed = JSON.parse(state);
299
- if (typeof parsed !== "object" || parsed === null) throw new Error("Invalid fullSync payload");
300
- return sanitizeReplacementState(parsed);
647
+ //#region packages/core/src/wrapStore.ts
648
+ /**
649
+ * Convert a store object into Coaction's callable store shape.
650
+ *
651
+ * @remarks
652
+ * Framework bindings use this to attach selector-aware readers while
653
+ * preserving the underlying store API on the returned function object. Most
654
+ * applications should use a public `create` entry instead of calling
655
+ * `wrapStore()` directly. Framework authors import this helper from
656
+ * `coaction/local` or `coaction/adapter`.
657
+ */
658
+ const wrapStore = (store, getState = () => store.getState()) => {
659
+ const { name, ..._store } = store;
660
+ return Object.assign({ [name]: (...args) => getState(...args) }[name], _store);
301
661
  };
662
+ //#endregion
663
+ //#region packages/core/src/asyncClientStore.ts
302
664
  const clientApplyErrorMessage = "apply() cannot be called in the client store. Client stores are mirrors; use a store method to update the main store instead.";
303
- const createAsyncClientStore = (createStore, asyncStoreClientOption) => {
304
- const { store: asyncClientStore, internal } = createStore({ share: "client" });
305
- let isApplyingClientState = false;
665
+ const createAsyncClientStore = (createStore, options) => {
666
+ let createdStore;
667
+ try {
668
+ createdStore = createStore({ share: "client" });
669
+ } catch (error) {
670
+ return failTransportInitialization(options.clientTransport, error);
671
+ }
672
+ const { store, internal } = createdStore;
673
+ let canApplyClientState = false;
306
674
  const previousAssertMutationAllowed = internal.assertMutationAllowed;
307
675
  internal.assertMutationAllowed = (operation) => {
308
- if (operation === "apply" && !isApplyingClientState) throw new Error(clientApplyErrorMessage);
676
+ if (operation === "apply") {
677
+ if (!canApplyClientState) throw new Error(clientApplyErrorMessage);
678
+ canApplyClientState = false;
679
+ }
309
680
  previousAssertMutationAllowed?.(operation);
310
681
  };
311
- const baseApply = asyncClientStore.apply.bind(asyncClientStore);
312
- asyncClientStore.apply = (state, patches) => {
313
- if (!isApplyingClientState) throw new Error(clientApplyErrorMessage);
314
- return baseApply(state, patches);
682
+ const baseApply = store.apply.bind(store);
683
+ store.apply = () => {
684
+ throw new Error(clientApplyErrorMessage);
315
685
  };
316
686
  internal.applyClientState = (...args) => {
317
- isApplyingClientState = true;
687
+ canApplyClientState = true;
318
688
  try {
319
689
  baseApply(...args);
320
690
  } finally {
321
- isApplyingClientState = false;
691
+ canApplyClientState = false;
322
692
  }
323
693
  };
324
- const isSharedWorker = typeof SharedWorker !== "undefined" && asyncStoreClientOption.worker instanceof SharedWorker;
325
- const transport = asyncStoreClientOption.worker ? createTransport(isSharedWorker ? "SharedWorkerClient" : "WebWorkerClient", {
326
- worker: asyncStoreClientOption.worker,
327
- prefix: asyncClientStore.name
328
- }) : asyncStoreClientOption.clientTransport;
329
- if (!transport) throw new Error("transport is required");
330
- asyncClientStore.transport = transport;
331
- let syncingPromise = null;
332
- let awaitingReconnectSync = false;
333
- let reconnectSequenceBaseline = null;
334
- const fullSync = async (allowLowerSequence = false) => {
335
- if (!syncingPromise) syncingPromise = (async () => {
336
- const latest = await transport.emit("fullSync");
337
- if (typeof latest !== "object" || latest === null || typeof latest.sequence !== "number" || typeof latest.state !== "string") throw new Error("Invalid fullSync payload");
338
- const canApplyLowerSequence = allowLowerSequence && awaitingReconnectSync && reconnectSequenceBaseline !== null && reconnectSequenceBaseline === internal.sequence;
339
- if (latest.sequence < internal.sequence && !canApplyLowerSequence) return;
340
- internal.applyClientState(parseFullSyncState$1(latest.state));
341
- internal.sequence = latest.sequence;
342
- awaitingReconnectSync = false;
343
- reconnectSequenceBaseline = null;
344
- })().finally(() => {
345
- syncingPromise = null;
346
- });
347
- return syncingPromise;
348
- };
349
- if (typeof transport.onConnect !== "function") throw new Error("transport.onConnect is required");
350
- transport.onConnect?.(() => {
351
- awaitingReconnectSync = true;
352
- reconnectSequenceBaseline = internal.sequence;
353
- fullSync(true).catch((error) => {
354
- if (process.env.NODE_ENV === "development") console.error(error);
355
- });
694
+ const isSharedWorker = typeof SharedWorker !== "undefined" && options.worker instanceof SharedWorker;
695
+ let transport;
696
+ try {
697
+ transport = options.worker ? createTransport(isSharedWorker ? "SharedWorkerClient" : "WebWorkerClient", {
698
+ worker: options.worker,
699
+ prefix: store.name
700
+ }) : options.clientTransport;
701
+ } catch (error) {
702
+ return failStoreSetup(store, error);
703
+ }
704
+ if (!transport) return failStoreSetup(store, /* @__PURE__ */ new Error("transport is required"));
705
+ try {
706
+ store.transport = transport;
707
+ if (typeof transport.onConnect !== "function") throw new Error("transport.onConnect is required");
708
+ } catch (error) {
709
+ return failStoreSetup(store, error);
710
+ }
711
+ const destroyedMarker = Symbol("destroyed client transport");
712
+ let resolveDestroyed;
713
+ const destroyedSignal = new Promise((resolve) => {
714
+ resolveDestroyed = () => resolve(destroyedMarker);
356
715
  });
357
- transport.listen("update", async (options) => {
358
- let shouldFullSync = false;
359
- let allowLowerSequence = false;
716
+ const disposers = /* @__PURE__ */ new Set();
717
+ let destroyed = false;
718
+ let connectGeneration = 0;
719
+ let connectSync = null;
720
+ let syncTail = Promise.resolve();
721
+ const registerDisposer = (value) => {
722
+ if (typeof value === "function") disposers.add(value);
723
+ };
724
+ const cleanup = () => {
725
+ if (destroyed) return;
726
+ destroyed = true;
727
+ connectGeneration += 1;
728
+ resolveDestroyed();
729
+ const callbacks = [...disposers];
730
+ disposers.clear();
731
+ for (const dispose of callbacks) try {
732
+ dispose();
733
+ } catch (error) {
734
+ reportLifecycleError(error);
735
+ }
736
+ };
737
+ const awaitActive = async (value) => {
738
+ const result = await Promise.race([Promise.resolve(value), destroyedSignal]);
739
+ if (result === destroyedMarker) throw new Error("Client transport was destroyed");
740
+ return result;
741
+ };
742
+ internal.awaitClientTransport = awaitActive;
743
+ const applyFullSync = (state, epoch, sequence) => {
744
+ const previousEpoch = internal.transportEpoch;
745
+ const previousSequence = internal.sequence;
746
+ internal.transportEpoch = epoch;
747
+ internal.sequence = sequence;
360
748
  try {
361
- if (typeof options.sequence !== "number") shouldFullSync = true;
362
- else if (options.sequence <= internal.sequence) if (awaitingReconnectSync) {
363
- shouldFullSync = true;
364
- allowLowerSequence = true;
365
- } else if (options.sequence === 0 && internal.sequence > 0) {
366
- awaitingReconnectSync = true;
367
- reconnectSequenceBaseline = internal.sequence;
368
- shouldFullSync = true;
369
- allowLowerSequence = true;
370
- } else return;
371
- else if (options.sequence === internal.sequence + 1) {
372
- internal.applyClientState(void 0, options.patches);
373
- internal.sequence = options.sequence;
374
- awaitingReconnectSync = false;
375
- reconnectSequenceBaseline = null;
376
- return;
377
- } else {
378
- shouldFullSync = true;
379
- allowLowerSequence = awaitingReconnectSync;
380
- }
381
- if (shouldFullSync) await fullSync(allowLowerSequence);
749
+ internal.applyClientState(state);
382
750
  } catch (error) {
383
- if (!shouldFullSync) try {
384
- await fullSync(awaitingReconnectSync);
385
- } catch (syncError) {
386
- if (process.env.NODE_ENV === "development") console.error(syncError);
387
- }
388
- if (process.env.NODE_ENV === "development") console.error(error);
751
+ internal.transportEpoch = previousEpoch;
752
+ internal.sequence = previousSequence;
753
+ throw error;
389
754
  }
390
- });
391
- return wrapStore(asyncClientStore, () => asyncClientStore.getState());
755
+ };
756
+ const fullSync = (expectedEpoch, minimumSequence = 0, generation = connectGeneration) => {
757
+ const execute = async () => {
758
+ if (destroyed || generation !== connectGeneration) return;
759
+ const encoded = await awaitActive(transport.emit("fullSync", encodeFullSyncRequest()));
760
+ if (destroyed || generation !== connectGeneration) return;
761
+ const snapshot = decodeFullSyncResponse(encoded);
762
+ if (expectedEpoch && snapshot.epoch !== expectedEpoch) throw new Error("Mismatched fullSync epoch");
763
+ if (snapshot.sequence < minimumSequence) throw new Error("Stale fullSync sequence");
764
+ if (snapshot.epoch === internal.transportEpoch && snapshot.sequence < internal.sequence) return;
765
+ applyFullSync(snapshot.state, snapshot.epoch, snapshot.sequence);
766
+ };
767
+ const run = syncTail.then(execute, execute);
768
+ syncTail = run.then(() => void 0, () => void 0);
769
+ return run;
770
+ };
771
+ internal.syncClientState = (expectedEpoch, minimumSequence) => fullSync(expectedEpoch, minimumSequence);
772
+ const applyUpdate = (update) => {
773
+ const previousEpoch = internal.transportEpoch;
774
+ const previousSequence = internal.sequence;
775
+ internal.transportEpoch = update.epoch;
776
+ internal.sequence = update.sequence;
777
+ try {
778
+ internal.applyClientState(void 0, update.patches);
779
+ } catch (error) {
780
+ internal.transportEpoch = previousEpoch;
781
+ internal.sequence = previousSequence;
782
+ throw error;
783
+ }
784
+ };
785
+ const handleUpdate = async (encoded) => {
786
+ if (destroyed) return;
787
+ const generation = connectGeneration;
788
+ const update = decodeUpdateMessage(encoded);
789
+ if (connectSync) await connectSync;
790
+ if (destroyed || generation !== connectGeneration) return;
791
+ if (update.epoch !== internal.transportEpoch) await fullSync(update.epoch, 0, generation);
792
+ if (destroyed || generation !== connectGeneration) return;
793
+ if (update.epoch !== internal.transportEpoch) throw new Error("Mismatched update epoch");
794
+ if (update.sequence <= internal.sequence) return;
795
+ if (update.sequence === internal.sequence + 1) {
796
+ applyUpdate(update);
797
+ return;
798
+ }
799
+ await fullSync(update.epoch, update.sequence, generation);
800
+ };
801
+ internal.destroyCallbacks?.add(cleanup);
802
+ try {
803
+ registerDisposer(transport.listen("update", async (encoded) => {
804
+ try {
805
+ await handleUpdate(encoded);
806
+ } catch (error) {
807
+ if (!destroyed) {
808
+ try {
809
+ await fullSync();
810
+ } catch (syncError) {
811
+ reportLifecycleError(syncError);
812
+ }
813
+ reportLifecycleError(error);
814
+ }
815
+ }
816
+ }));
817
+ registerDisposer(transport.onConnect(() => {
818
+ const pending = fullSync(void 0, 0, ++connectGeneration).finally(() => {
819
+ if (connectSync === pending) connectSync = null;
820
+ });
821
+ connectSync = pending;
822
+ pending.catch(reportLifecycleError);
823
+ return pending;
824
+ }));
825
+ markStoreReady(store);
826
+ internal.assertAlive?.("store initialization");
827
+ } catch (error) {
828
+ return failStoreSetup(store, error);
829
+ }
830
+ return wrapStore(store, () => store.getState());
392
831
  };
393
832
  const emit = (store, internal, patches) => {
394
- const safePatches = sanitizePatches(patches);
395
- if (store.transport && safePatches?.length) {
396
- validateSharedStateSerializable(internal.rootState);
397
- internal.sequence += 1;
398
- store.transport.emit({
833
+ if (!store.transport || !patches?.length || !internal.transportEpoch) return;
834
+ const sequence = internal.sequence + 1;
835
+ const encoded = encodeUpdateMessage(internal.transportEpoch, sequence, patches);
836
+ internal.sequence = sequence;
837
+ try {
838
+ const pending = store.transport.emit({
399
839
  name: "update",
400
840
  respond: false
401
- }, {
402
- patches: safePatches,
403
- sequence: internal.sequence
404
- });
405
- }
406
- };
407
- const handleDraft = (store, internal) => {
408
- internal.rootState = internal.backupState;
409
- const [nextState, patches, inversePatches] = internal.finalizeDraft();
410
- if (store.share === "main") validateSharedStateSerializable(nextState);
411
- const safePatches = sanitizePatches((store.patch ? store.patch({
412
- patches,
413
- inversePatches
414
- }) : {
415
- patches,
416
- inversePatches
417
- }).patches) ?? [];
418
- if (safePatches.length) {
419
- store.apply(internal.rootState, safePatches);
420
- emit(store, internal, safePatches);
841
+ }, encoded);
842
+ Promise.resolve(pending).catch(reportLifecycleError);
843
+ } catch (error) {
844
+ reportLifecycleError(error);
421
845
  }
422
846
  };
423
847
  //#endregion
424
- //#region packages/core/src/getInitialState.ts
425
- const isObject = (value) => typeof value === "object" && value !== null;
426
- const isStateFactory = (value) => typeof value === "function";
427
- const hasGetState = (value) => (typeof value === "object" || typeof value === "function") && value !== null && typeof value.getState === "function";
428
- const hasBindState = (value) => isObject(value) && !!value[bindSymbol];
429
- const formatInvalidStateMessage = (type, stateOrFn, key) => `Invalid state ${type} encountered in makeState: ${typeof key !== "undefined" ? `for key ${String(key)}, ` : ""}${typeof stateOrFn}`;
430
- const getInitialState = (store, createState, internal) => {
431
- const makeState = (stateOrFn, key) => {
432
- let state;
433
- if (isStateFactory(stateOrFn)) state = stateOrFn(store.setState, store.getState, store);
434
- else if (isObject(stateOrFn)) state = stateOrFn;
435
- else {
436
- if (process.env.NODE_ENV !== "production") throw new Error(formatInvalidStateMessage("value", stateOrFn, key));
437
- return {};
438
- }
439
- if (hasGetState(state)) state = state.getState();
440
- else if (typeof state === "function") state = state();
441
- if (hasBindState(state)) {
442
- if (store.isSliceStore) throw new Error("Third-party state binding does not support Slices mode. Please inject a whole store instead.");
443
- const binder = state[bindSymbol];
444
- const rawState = binder.bind(state);
445
- binder.handleStore(store, rawState, state, internal, key);
446
- delete state[bindSymbol];
447
- return rawState;
448
- }
449
- if (!isObject(state)) {
450
- if (process.env.NODE_ENV !== "production") throw new Error(formatInvalidStateMessage("result", state, key));
451
- return {};
452
- }
453
- return state;
454
- };
455
- if (!store.isSliceStore) return makeState(createState);
456
- return getOwnEnumerableKeys(createState).reduce((stateTree, key) => {
457
- if (typeof key === "string" && isUnsafeKey(key)) return stateTree;
458
- setOwnEnumerable(stateTree, key, makeState(createState[key], key));
459
- return stateTree;
460
- }, {});
848
+ //#region packages/core/src/global.ts
849
+ const getGlobal = () => {
850
+ let _global;
851
+ if (typeof window !== "undefined") _global = window;
852
+ else if (typeof global !== "undefined") _global = global;
853
+ else if (typeof self !== "undefined") _global = self;
854
+ else _global = {};
855
+ return _global;
461
856
  };
462
857
  //#endregion
858
+ //#region packages/core/src/constant.ts
859
+ const WorkerType = getGlobal().SharedWorkerGlobalScope ? "SharedWorkerInternal" : globalThis.WorkerGlobalScope ? "WebWorkerInternal" : null;
860
+ const bindSymbol = Symbol("bind");
861
+ //#endregion
463
862
  //#region packages/core/src/getRawStateClientAction.ts
464
- const transportErrorMarker$1 = "__coactionTransportError__";
465
- const parseFullSyncState = (state) => {
466
- const parsed = JSON.parse(state);
467
- if (typeof parsed !== "object" || parsed === null) throw new Error("Invalid fullSync payload");
468
- return sanitizeReplacementState(parsed);
469
- };
470
- const isTransportErrorEnvelope = (value) => {
471
- if (typeof value !== "object" || value === null) return false;
472
- return value[transportErrorMarker$1] === true && typeof value.message === "string";
473
- };
474
- const isLegacyTransportErrorEnvelope = (value) => {
475
- if (typeof value !== "object" || value === null) return false;
476
- const candidate = value;
477
- return typeof candidate.$$Error === "string" && candidate.$$Error.length > 0 && Object.keys(candidate).length === 1;
863
+ /**
864
+ * The authority changed while a remote action was in flight, so its side-effect
865
+ * outcome cannot be determined safely from the current client mirror.
866
+ */
867
+ var ActionAuthorityChangedError = class extends Error {
868
+ code = "COACTION_ACTION_AUTHORITY_CHANGED";
869
+ outcome = "unknown";
870
+ constructor(action) {
871
+ super(`The authority changed while action '${action}' was in flight. The action may have completed on the previous authority; retry only if it is idempotent.`);
872
+ this.name = "ActionAuthorityChangedError";
873
+ }
478
874
  };
479
875
  const createClientAction = ({ clientExecuteSyncTimeoutMs, internal, key, store, sliceKey }) => {
480
876
  return (...args) => {
877
+ internal.assertAlive?.(`action ${key}`);
481
878
  let actionId;
482
879
  let done;
483
880
  if (store.trace) {
@@ -515,58 +912,237 @@ const createClientAction = ({ clientExecuteSyncTimeoutMs, internal, key, store,
515
912
  }
516
913
  };
517
914
  if (typeof sliceKey === "symbol") throw new Error("Symbol-keyed slice actions are not supported in client store mode.");
518
- const keys = typeof sliceKey !== "undefined" ? [String(sliceKey), key] : [key];
519
- return traceAction(() => store.transport.emit("execute", keys, args).then(async (response) => {
520
- const result = Array.isArray(response) ? response[0] : response;
521
- const sequence = Array.isArray(response) ? typeof response[1] === "number" ? response[1] : internal.sequence : internal.sequence;
522
- if (internal.sequence < sequence) {
523
- if (process.env.NODE_ENV === "development") console.warn(`The sequence of the action is not consistent.`, sequence, internal.sequence);
524
- await new Promise((resolve, reject) => {
525
- let settled = false;
526
- let unsubscribe = () => {};
527
- const timeoutRef = {};
528
- const cleanup = () => {
529
- unsubscribe();
530
- if (typeof timeoutRef.current !== "undefined") clearTimeout(timeoutRef.current);
531
- };
532
- const finishResolve = () => {
533
- if (settled) return;
534
- settled = true;
535
- cleanup();
536
- resolve();
537
- };
538
- const finishReject = (error) => {
539
- if (settled) return;
540
- settled = true;
541
- cleanup();
542
- reject(error);
543
- };
544
- unsubscribe = store.subscribe(() => {
545
- if (internal.sequence >= sequence) finishResolve();
546
- });
547
- timeoutRef.current = setTimeout(() => {
548
- store.transport.emit("fullSync").then((latest) => {
549
- if (typeof latest !== "object" || latest === null) throw new Error("Invalid fullSync payload");
550
- const next = latest;
551
- if (typeof next.state !== "string" || typeof next.sequence !== "number") throw new Error("Invalid fullSync payload");
552
- if (next.sequence >= sequence) {
553
- (internal.applyClientState ?? store.apply.bind(store))(parseFullSyncState(next.state));
554
- internal.sequence = next.sequence;
555
- finishResolve();
556
- return;
557
- }
558
- finishReject(/* @__PURE__ */ new Error(`Stale fullSync sequence: expected >= ${sequence}, got ${next.sequence}`));
559
- }).catch((error) => {
560
- finishReject(error);
915
+ const encoded = encodeExecuteRequest(typeof sliceKey === "undefined" ? [key] : [String(sliceKey), key], args);
916
+ const requestEpoch = internal.transportEpoch;
917
+ return traceAction(() => {
918
+ const emitted = store.transport.emit("execute", encoded);
919
+ return (internal.awaitClientTransport ? internal.awaitClientTransport(emitted) : emitted).then(async (payload) => {
920
+ const response = decodeExecuteResponse(payload);
921
+ internal.assertAlive?.(`action ${key}`);
922
+ const syncClientState = internal.syncClientState;
923
+ if (!syncClientState) throw new Error("Client fullSync is not available");
924
+ if (internal.transportEpoch !== requestEpoch && response.epoch !== internal.transportEpoch) throw new ActionAuthorityChangedError(key);
925
+ if (response.epoch !== internal.transportEpoch) {
926
+ await syncClientState(response.epoch, response.sequence);
927
+ internal.assertAlive?.(`action ${key}`);
928
+ } else if (response.sequence > internal.sequence) {
929
+ await new Promise((resolve, reject) => {
930
+ let settled = false;
931
+ let unsubscribe = () => {};
932
+ let timeout;
933
+ const cancel = () => finish(/* @__PURE__ */ new Error("Client transport was destroyed"));
934
+ const finish = (error) => {
935
+ if (settled) return;
936
+ settled = true;
937
+ unsubscribe();
938
+ internal.destroyCallbacks?.delete(cancel);
939
+ if (timeout) clearTimeout(timeout);
940
+ if (typeof error === "undefined") resolve();
941
+ else reject(error);
942
+ };
943
+ unsubscribe = store.subscribe(() => {
944
+ if (internal.transportEpoch === response.epoch && internal.sequence >= response.sequence) finish();
561
945
  });
562
- }, clientExecuteSyncTimeoutMs);
946
+ internal.destroyCallbacks?.add(cancel);
947
+ timeout = setTimeout(() => {
948
+ syncClientState(response.epoch, response.sequence).then(() => finish(), (error) => finish(error));
949
+ }, clientExecuteSyncTimeoutMs);
950
+ if (internal.transportEpoch === response.epoch && internal.sequence >= response.sequence) finish();
951
+ });
952
+ internal.assertAlive?.(`action ${key}`);
953
+ }
954
+ if (!response.ok) throw new Error(response.error);
955
+ return response.value;
956
+ });
957
+ });
958
+ };
959
+ };
960
+ //#endregion
961
+ //#region packages/core/src/handleMainTransport.ts
962
+ const publicErrorMessages = /* @__PURE__ */ new Set([
963
+ "Remote action is not allowed",
964
+ "The function is not found",
965
+ "Transport request is not authorized",
966
+ "Transport request was cancelled after store destroy"
967
+ ]);
968
+ const getErrorMessage = async (error, request, policy) => {
969
+ if (error instanceof Error && publicErrorMessages.has(error.message)) return error.message;
970
+ if (request && policy?.mapError) try {
971
+ const message = await policy.mapError(error, request);
972
+ if (typeof message === "string" && message) return message;
973
+ } catch (mapError) {
974
+ if (process.env.NODE_ENV === "development") console.error(mapError);
975
+ }
976
+ return "Remote action failed";
977
+ };
978
+ const handleMainTransport = (store, internal, storeTransport, workerType, checkEnablePatches, policy) => {
979
+ const transport = storeTransport ?? (workerType === "SharedWorkerInternal" || workerType === "WebWorkerInternal" ? createTransport(workerType, { prefix: store.name }) : void 0);
980
+ if (!transport) return;
981
+ if (checkEnablePatches) throw new Error("enablePatches: true is required for the transport");
982
+ const epoch = uuid();
983
+ internal.transportEpoch = epoch;
984
+ let destroyed = false;
985
+ const disposers = /* @__PURE__ */ new Set();
986
+ const registerDisposer = (value) => {
987
+ if (typeof value === "function") disposers.add(value);
988
+ };
989
+ const cleanup = () => {
990
+ if (destroyed) return;
991
+ destroyed = true;
992
+ const callbacks = [...disposers];
993
+ disposers.clear();
994
+ for (const dispose of callbacks) try {
995
+ dispose();
996
+ } catch (error) {
997
+ if (process.env.NODE_ENV === "development") console.error(error);
998
+ }
999
+ };
1000
+ const assertActive = () => {
1001
+ if (destroyed) throw new Error("Transport request was cancelled after store destroy");
1002
+ };
1003
+ store.transport = transport;
1004
+ internal.emitPatches = (patches) => emit(store, internal, patches);
1005
+ internal.destroyCallbacks?.add(cleanup);
1006
+ try {
1007
+ registerDisposer(transport.listen("execute", async (encoded) => {
1008
+ let policyRequest;
1009
+ try {
1010
+ assertActive();
1011
+ const request = decodeExecuteRequest(encoded);
1012
+ if (!internal.sharedActionPaths?.has(JSON.stringify(request.action))) throw new Error("Remote action is not allowed");
1013
+ if (policy?.allowedActions && !policy.allowedActions.some((allowed) => allowed.length === request.action.length && allowed.every((key, index) => key === request.action[index]))) throw new Error("Remote action is not allowed");
1014
+ policyRequest = {
1015
+ ...request,
1016
+ type: "execute"
1017
+ };
1018
+ if (policy?.authorize && await policy.authorize(policyRequest) !== true) throw new Error("Transport request is not authorized");
1019
+ assertActive();
1020
+ let action = store.getState();
1021
+ let receiver;
1022
+ for (const key of request.action) {
1023
+ if (isUnsafePathSegment(key) || typeof action !== "object" && typeof action !== "function" || action === null || !Object.prototype.hasOwnProperty.call(action, key)) throw new Error("The function is not found");
1024
+ receiver = action;
1025
+ action = action[key];
1026
+ }
1027
+ if (typeof action !== "function") throw new Error("The function is not found");
1028
+ const value = await Reflect.apply(action, receiver, request.args);
1029
+ assertActive();
1030
+ return encodeExecuteResponse({
1031
+ epoch,
1032
+ ok: true,
1033
+ sequence: internal.sequence,
1034
+ ...typeof value === "undefined" ? {} : { value }
1035
+ });
1036
+ } catch (error) {
1037
+ if (process.env.NODE_ENV === "development") console.error(error);
1038
+ return encodeExecuteResponse({
1039
+ epoch,
1040
+ error: await getErrorMessage(error, policyRequest, policy),
1041
+ ok: false,
1042
+ sequence: internal.sequence
563
1043
  });
564
1044
  }
565
- if (isTransportErrorEnvelope(result)) throw new Error(result.message);
566
- if (isLegacyTransportErrorEnvelope(result)) throw new Error(result.$$Error);
567
- return result;
568
1045
  }));
1046
+ registerDisposer(transport.listen("fullSync", async (encoded) => {
1047
+ assertActive();
1048
+ decodeFullSyncRequest(encoded);
1049
+ if (policy?.authorize && await policy.authorize({ type: "fullSync" }) !== true) throw new Error("Transport request is not authorized");
1050
+ assertActive();
1051
+ const state = internal.getTransportState?.() ?? internal.rootState;
1052
+ validateSharedStateSerializable(state);
1053
+ if (typeof state !== "object" || state === null || Array.isArray(state)) throw new TypeError("Shared store state must be a JSON object");
1054
+ return encodeFullSyncResponse({
1055
+ epoch,
1056
+ sequence: internal.sequence,
1057
+ state
1058
+ });
1059
+ }));
1060
+ } catch (error) {
1061
+ internal.destroyCallbacks?.delete(cleanup);
1062
+ cleanup();
1063
+ store.transport = void 0;
1064
+ try {
1065
+ transport.dispose?.();
1066
+ } catch (disposeError) {
1067
+ if (process.env.NODE_ENV === "development") console.error(disposeError);
1068
+ }
1069
+ throw error;
1070
+ }
1071
+ };
1072
+ //#endregion
1073
+ //#region packages/core/src/applyMiddlewares.ts
1074
+ const isStoreLike = (value) => {
1075
+ if (!value || typeof value !== "object") return false;
1076
+ const candidate = value;
1077
+ return typeof candidate.setState === "function" && typeof candidate.getState === "function" && typeof candidate.subscribe === "function" && typeof candidate.destroy === "function" && typeof candidate.apply === "function" && typeof candidate.getPureState === "function";
1078
+ };
1079
+ const applyMiddlewares = (store, middlewares) => {
1080
+ return middlewares.reduce((store, middleware, index) => {
1081
+ if (process.env.NODE_ENV === "development") {
1082
+ if (typeof middleware !== "function") throw new Error(`middlewares[${index}] should be a function`);
1083
+ }
1084
+ const nextStore = middleware(store);
1085
+ if (process.env.NODE_ENV === "development") {
1086
+ if (!isStoreLike(nextStore)) throw new Error(`middlewares[${index}] should return a store-like object`);
1087
+ }
1088
+ return nextStore;
1089
+ }, store);
1090
+ };
1091
+ //#endregion
1092
+ //#region packages/core/src/getInitialState.ts
1093
+ const isObject = (value) => typeof value === "object" && value !== null;
1094
+ const isStateFactory = (value) => typeof value === "function";
1095
+ const hasGetState = (value) => (typeof value === "object" || typeof value === "function") && value !== null && typeof value.getState === "function";
1096
+ const hasBindState = (value) => isObject(value) && !!value[bindSymbol];
1097
+ const formatInvalidStateMessage = (type, stateOrFn, key) => `Invalid state ${type} encountered in makeState: ${typeof key !== "undefined" ? `for key ${String(key)}, ` : ""}${typeof stateOrFn}`;
1098
+ const getInitialState = (store, createState, internal) => {
1099
+ const makeState = (stateOrFn, key) => {
1100
+ let state;
1101
+ if (isStateFactory(stateOrFn)) state = stateOrFn(store.setState, store.getState, store);
1102
+ else if (isObject(stateOrFn)) state = stateOrFn;
1103
+ else {
1104
+ if (process.env.NODE_ENV !== "production") throw new Error(formatInvalidStateMessage("value", stateOrFn, key));
1105
+ return {};
1106
+ }
1107
+ if (hasGetState(state)) state = state.getState();
1108
+ else if (typeof state === "function") state = state();
1109
+ if (hasBindState(state)) {
1110
+ if (store.isSliceStore) throw new Error("Third-party state binding does not support Slices mode. Please inject a whole store instead.");
1111
+ const binder = state[bindSymbol];
1112
+ const rawState = binder.bind(state);
1113
+ binder.handleStore(store, rawState, state, internal, key);
1114
+ delete state[bindSymbol];
1115
+ return rawState;
1116
+ }
1117
+ if (!isObject(state)) {
1118
+ if (process.env.NODE_ENV !== "production") throw new Error(formatInvalidStateMessage("result", state, key));
1119
+ return {};
1120
+ }
1121
+ return state;
569
1122
  };
1123
+ if (!store.isSliceStore) return makeState(createState);
1124
+ return getOwnEnumerableKeys(createState).reduce((stateTree, key) => {
1125
+ if (typeof key === "string" && isUnsafeKey(key)) return stateTree;
1126
+ setOwnEnumerable(stateTree, key, makeState(createState[key], key));
1127
+ return stateTree;
1128
+ }, {});
1129
+ };
1130
+ //#endregion
1131
+ //#region packages/core/src/handleDraft.ts
1132
+ const handleDraft = (store, internal) => {
1133
+ internal.rootState = internal.backupState;
1134
+ const [, patches, inversePatches] = internal.finalizeDraft();
1135
+ const safePatches = sanitizeCheckedPatches((store.patch ? store.patch({
1136
+ patches,
1137
+ inversePatches
1138
+ }) : {
1139
+ patches,
1140
+ inversePatches
1141
+ }).patches, "store.patch()");
1142
+ if (safePatches.length) {
1143
+ store.apply(internal.rootState, safePatches);
1144
+ internal.emitPatches?.(safePatches);
1145
+ }
570
1146
  };
571
1147
  //#endregion
572
1148
  //#region packages/core/src/getRawStateLocalAction.ts
@@ -575,6 +1151,7 @@ const getActionTarget = (store, sliceKey) => {
575
1151
  };
576
1152
  const createLocalAction = ({ fn, internal, key, options, store, sliceKey }) => {
577
1153
  return (...args) => {
1154
+ internal.assertAlive?.(`action ${String(key)}`);
578
1155
  let actionId;
579
1156
  let done;
580
1157
  if (store.trace) {
@@ -650,85 +1227,139 @@ const createLocalAction = ({ fn, internal, key, options, store, sliceKey }) => {
650
1227
  return fn.apply(getActionTarget(store, sliceKey), args);
651
1228
  });
652
1229
  return fn.apply(getActionTarget(store, sliceKey), args);
653
- });
654
- };
655
- };
656
- //#endregion
657
- //#region packages/core/src/computed.ts
658
- const isObjectLike = (value) => typeof value === "object" && value !== null;
659
- var Computed = class {
660
- deps;
661
- fn;
662
- constructor(deps, fn) {
663
- this.deps = deps;
664
- this.fn = fn;
665
- }
666
- createGetter({ internal }) {
667
- const memoByReceiver = /* @__PURE__ */ new WeakMap();
668
- const lastArgs = /* @__PURE__ */ new WeakMap();
669
- const lastResult = /* @__PURE__ */ new WeakMap();
670
- const fallbackReceiver = {};
671
- const evaluate = (receiver) => {
672
- const args = this.deps(internal.module);
673
- if (!lastArgs.has(receiver) || !areShallowEqualWithArray(lastArgs.get(receiver), args)) lastResult.set(receiver, this.fn.apply(receiver, args));
674
- lastArgs.set(receiver, args);
675
- return lastResult.get(receiver);
676
- };
677
- return function() {
678
- const receiver = typeof this === "object" && this !== null ? this : fallbackReceiver;
679
- if (internal.isBatching) return evaluate(receiver);
680
- let accessor = memoByReceiver.get(receiver);
681
- if (!accessor) {
682
- accessor = computed$1(() => evaluate(receiver));
683
- memoByReceiver.set(receiver, accessor);
684
- }
685
- return accessor();
686
- };
1230
+ });
1231
+ };
1232
+ };
1233
+ //#endregion
1234
+ //#region packages/core/src/immutableState.ts
1235
+ const isImmutableStateObject = (value) => {
1236
+ if (typeof value !== "object" || value === null) return false;
1237
+ if (Array.isArray(value)) return true;
1238
+ const prototype = Object.getPrototypeOf(value);
1239
+ return prototype === Object.prototype || prototype === null;
1240
+ };
1241
+ const getImmutableStateSnapshot = (value, cache) => {
1242
+ if (!isImmutableStateObject(value)) return value;
1243
+ const cached = cache.get(value);
1244
+ if (cached) return cached;
1245
+ const isArray = Array.isArray(value);
1246
+ const snapshot = isArray ? new Array(value.length) : Object.create(Object.getPrototypeOf(value));
1247
+ cache.set(value, snapshot);
1248
+ for (const key of Reflect.ownKeys(value)) {
1249
+ if (isArray && key === "length") continue;
1250
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1251
+ if (Object.prototype.hasOwnProperty.call(descriptor, "value")) descriptor.value = getImmutableStateSnapshot(descriptor.value, cache);
1252
+ Object.defineProperty(snapshot, key, descriptor);
687
1253
  }
1254
+ return Object.freeze(snapshot);
688
1255
  };
689
- const createCachedGetter = (internal, getter) => {
690
- const accessors = /* @__PURE__ */ new WeakMap();
691
- const fallbackReceiver = {};
692
- return function() {
693
- const receiver = typeof this === "object" && this !== null ? this : fallbackReceiver;
694
- if (internal.isBatching) return getter.call(receiver);
695
- let accessor = accessors.get(receiver);
696
- if (!accessor) {
697
- accessor = computed$1(() => getter.call(receiver));
698
- accessors.set(receiver, accessor);
1256
+ const createImmutableSnapshotPatches = (patches, cache) => patches.map((patch) => Object.prototype.hasOwnProperty.call(patch, "value") ? {
1257
+ ...patch,
1258
+ value: getImmutableStateSnapshot(patch.value, cache)
1259
+ } : patch);
1260
+ const finalizeImmutableStateSnapshot = (state, snapshot, patches, cache, sources) => {
1261
+ const mapPair = (value, snapshotValue) => {
1262
+ if (isImmutableStateObject(value) && isImmutableStateObject(snapshotValue)) {
1263
+ cache.set(value, snapshotValue);
1264
+ sources?.set(snapshotValue, value);
699
1265
  }
700
- return accessor();
701
1266
  };
702
- };
703
- const createTrackedStateReader = (internal, read, initialValue) => {
704
- const slotSignal = signal$1(initialValue);
705
- const slotVersionSignal = signal$1(0);
706
- let slotVersion = 0;
707
- (internal.signalSlots ??= /* @__PURE__ */ new Set()).add({ refresh: () => {
708
- const nextValue = read();
709
- slotSignal(nextValue);
710
- if (internal.mutableInstance && isObjectLike(nextValue)) {
711
- slotVersion += 1;
712
- slotVersionSignal(slotVersion);
1267
+ mapPair(state, snapshot);
1268
+ for (const patch of patches) {
1269
+ let value = state;
1270
+ let snapshotValue = snapshot;
1271
+ const ancestors = [];
1272
+ if (isImmutableStateObject(snapshotValue)) ancestors.push(snapshotValue);
1273
+ for (const key of patch.path) {
1274
+ if (!isImmutableStateObject(value) || !isImmutableStateObject(snapshotValue)) break;
1275
+ value = value[key];
1276
+ snapshotValue = snapshotValue[key];
1277
+ mapPair(value, snapshotValue);
1278
+ if (isImmutableStateObject(snapshotValue)) ancestors.push(snapshotValue);
713
1279
  }
714
- } });
715
- return () => {
716
- const currentValue = slotSignal();
717
- if (internal.mutableInstance && isObjectLike(currentValue)) slotVersionSignal();
718
- return read();
719
- };
1280
+ for (let index = ancestors.length - 1; index >= 0; index -= 1) if (!Object.isFrozen(ancestors[index])) Object.freeze(ancestors[index]);
1281
+ }
720
1282
  };
721
- const refreshSignalSlots = (internal) => {
722
- if (!internal.signalSlots?.size) return;
723
- startBatch$1();
724
- try {
725
- internal.signalSlots.forEach((slot) => slot.refresh());
726
- } finally {
727
- endBatch$1();
1283
+ const indexImmutableStateSnapshot = (state, snapshot, sources, seen = /* @__PURE__ */ new WeakSet()) => {
1284
+ if (!isImmutableStateObject(state) || !isImmutableStateObject(snapshot) || seen.has(snapshot)) return;
1285
+ seen.add(snapshot);
1286
+ sources.set(snapshot, state);
1287
+ for (const key of Reflect.ownKeys(state)) {
1288
+ const stateDescriptor = Object.getOwnPropertyDescriptor(state, key);
1289
+ const snapshotDescriptor = Object.getOwnPropertyDescriptor(snapshot, key);
1290
+ if (stateDescriptor && snapshotDescriptor && Object.prototype.hasOwnProperty.call(stateDescriptor, "value") && Object.prototype.hasOwnProperty.call(snapshotDescriptor, "value")) indexImmutableStateSnapshot(stateDescriptor.value, snapshotDescriptor.value, sources, seen);
728
1291
  }
729
1292
  };
730
1293
  //#endregion
731
1294
  //#region packages/core/src/getRawStateStateProperty.ts
1295
+ const assertImmutableStateMutationAllowed = (internal) => {
1296
+ if (internal.mutableInstance || internal.isBatching) return;
1297
+ throw new Error("Direct state mutation is not allowed in immutable Coaction stores. Wrap mutations in set(() => { ... }).");
1298
+ };
1299
+ const readonlyProxyCache = /* @__PURE__ */ new WeakMap();
1300
+ const getReadonlyProxyCache = (internal) => {
1301
+ let cache = readonlyProxyCache.get(internal);
1302
+ if (!cache) {
1303
+ cache = /* @__PURE__ */ new WeakMap();
1304
+ readonlyProxyCache.set(internal, cache);
1305
+ }
1306
+ return cache;
1307
+ };
1308
+ const getPublicStateObject = (internal, value, sliceKey) => {
1309
+ if (value === internal.rootState) return internal.module;
1310
+ if (typeof sliceKey === "undefined" || typeof internal.rootState !== "object" || internal.rootState === null || typeof internal.module !== "object" || internal.module === null) return;
1311
+ const rootState = internal.rootState;
1312
+ const module = internal.module;
1313
+ if (rootState[sliceKey] === value) return module[sliceKey];
1314
+ };
1315
+ const toReadonlyStateValue = (internal, value, sliceKey) => {
1316
+ if (internal.mutableInstance || internal.isBatching || !isImmutableStateObject(value)) return value;
1317
+ if (internal.computedReadDepth) {
1318
+ const cache = internal.computedSnapshotCache ??= /* @__PURE__ */ new WeakMap();
1319
+ if (isImmutableStateObject(internal.rootState) && !cache.has(internal.rootState)) getImmutableStateSnapshot(internal.rootState, cache);
1320
+ return getImmutableStateSnapshot(value, cache);
1321
+ }
1322
+ const publicValue = getPublicStateObject(internal, value, sliceKey);
1323
+ if (publicValue) return publicValue;
1324
+ const cache = getReadonlyProxyCache(internal);
1325
+ const cached = cache.get(value);
1326
+ if (cached) return cached;
1327
+ const proxy = new Proxy(value, {
1328
+ get(target, key, receiver) {
1329
+ return toReadonlyStateValue(internal, Reflect.get(target, key, receiver), sliceKey);
1330
+ },
1331
+ set() {
1332
+ assertImmutableStateMutationAllowed(internal);
1333
+ return false;
1334
+ },
1335
+ deleteProperty() {
1336
+ assertImmutableStateMutationAllowed(internal);
1337
+ return false;
1338
+ },
1339
+ defineProperty() {
1340
+ assertImmutableStateMutationAllowed(internal);
1341
+ return false;
1342
+ },
1343
+ setPrototypeOf() {
1344
+ assertImmutableStateMutationAllowed(internal);
1345
+ return false;
1346
+ }
1347
+ });
1348
+ cache.set(value, proxy);
1349
+ return proxy;
1350
+ };
1351
+ const toPublicComputedValue = (internal, value, sliceKey) => {
1352
+ if (!isImmutableStateObject(value)) return value;
1353
+ const rootSnapshot = internal.computedSnapshotCache?.get(internal.rootState);
1354
+ const sources = internal.computedSnapshotSources ??= /* @__PURE__ */ new WeakMap();
1355
+ let source = sources.get(value);
1356
+ if (!source && Object.isFrozen(value) && rootSnapshot) {
1357
+ indexImmutableStateSnapshot(internal.rootState, rootSnapshot, sources);
1358
+ source = sources.get(value);
1359
+ }
1360
+ if (source) internal.computedIdentityRequired = true;
1361
+ return source ? toReadonlyStateValue(internal, source, sliceKey) : value;
1362
+ };
732
1363
  const prepareStateDescriptor = ({ descriptor, initialStateSeen, internal, key, rawState, sliceKey }) => {
733
1364
  const isComputed = descriptor.value instanceof Computed;
734
1365
  const readStateValue = () => typeof sliceKey !== "undefined" ? internal.rootState[sliceKey][key] : internal.rootState[key];
@@ -749,37 +1380,49 @@ const prepareStateDescriptor = ({ descriptor, initialStateSeen, internal, key, r
749
1380
  });
750
1381
  if (isComputed) {
751
1382
  if (internal.mutableInstance) throw new Error("Computed is not supported with mutable instance");
752
- descriptor.get = descriptor.value.createGetter({ internal });
1383
+ const getComputed = descriptor.value.createGetter({ internal });
1384
+ descriptor.get = function() {
1385
+ return toPublicComputedValue(internal, getComputed.call(this), sliceKey);
1386
+ };
753
1387
  } else if (typeof sliceKey !== "undefined") {
754
1388
  const read = createTrackedStateReader(internal, readStateValue, initialValue);
755
- descriptor.get = () => read();
1389
+ descriptor.get = () => toReadonlyStateValue(internal, read(), sliceKey);
756
1390
  descriptor.set = (value) => {
1391
+ assertImmutableStateMutationAllowed(internal);
757
1392
  internal.rootState[sliceKey][key] = value;
758
1393
  };
759
1394
  } else {
760
1395
  const read = createTrackedStateReader(internal, readStateValue, initialValue);
761
- descriptor.get = () => read();
1396
+ descriptor.get = () => toReadonlyStateValue(internal, read());
762
1397
  descriptor.set = (value) => {
1398
+ assertImmutableStateMutationAllowed(internal);
763
1399
  internal.rootState[key] = value;
764
1400
  };
765
1401
  }
766
1402
  delete descriptor.value;
767
1403
  delete descriptor.writable;
768
1404
  };
769
- const prepareAccessorDescriptor = ({ descriptor, internal }) => {
1405
+ const prepareAccessorDescriptor = ({ descriptor, internal, sliceKey }) => {
770
1406
  if (internal.mutableInstance || typeof descriptor.get !== "function") return;
771
- descriptor.get = createCachedGetter(internal, descriptor.get);
1407
+ const getComputed = createCachedGetter(internal, descriptor.get);
1408
+ descriptor.get = function() {
1409
+ return toPublicComputedValue(internal, getComputed.call(this), sliceKey);
1410
+ };
772
1411
  };
773
1412
  //#endregion
774
1413
  //#region packages/core/src/getRawState.ts
775
1414
  const defaultClientExecuteSyncTimeoutMs = 1500;
1415
+ const lockPublicStateObject = (state) => {
1416
+ Object.freeze(state);
1417
+ return state;
1418
+ };
776
1419
  const getClientExecuteSyncTimeoutMs = (options) => {
777
1420
  const timeout = options.executeSyncTimeoutMs;
778
1421
  if (typeof timeout === "undefined") return defaultClientExecuteSyncTimeoutMs;
779
1422
  if (!Number.isFinite(timeout) || timeout < 0) throw new Error("executeSyncTimeoutMs must be a finite number greater than or equal to 0");
780
1423
  return timeout;
781
1424
  };
782
- const getRawState = (store, internal, initialState, options) => {
1425
+ const getRawState = (store, internal, initialState, options, createClientAction) => {
783
1426
  const clientExecuteSyncTimeoutMs = getClientExecuteSyncTimeoutMs(options);
784
1427
  const rawState = {};
785
1428
  const handle = (_rawState, _initialState, sliceKey) => {
@@ -798,7 +1441,8 @@ const getRawState = (store, internal, initialState, options) => {
798
1441
  if (!Object.prototype.hasOwnProperty.call(descriptor, "value")) {
799
1442
  prepareAccessorDescriptor({
800
1443
  descriptor,
801
- internal
1444
+ internal,
1445
+ sliceKey
802
1446
  });
803
1447
  return;
804
1448
  }
@@ -816,6 +1460,7 @@ const getRawState = (store, internal, initialState, options) => {
816
1460
  }
817
1461
  if (store.share === "client") {
818
1462
  if (typeof key !== "string") return;
1463
+ if (!createClientAction) throw new Error("Client action runtime is not configured");
819
1464
  descriptor.value = createClientAction({
820
1465
  clientExecuteSyncTimeoutMs,
821
1466
  internal,
@@ -833,7 +1478,7 @@ const getRawState = (store, internal, initialState, options) => {
833
1478
  });
834
1479
  }
835
1480
  });
836
- return Object.defineProperties({}, safeDescriptors);
1481
+ return lockPublicStateObject(Object.defineProperties({}, safeDescriptors));
837
1482
  };
838
1483
  if (store.isSliceStore) {
839
1484
  internal.module = {};
@@ -843,19 +1488,27 @@ const getRawState = (store, internal, initialState, options) => {
843
1488
  setOwnEnumerable(rawState, key, sliceRawState);
844
1489
  setOwnEnumerable(internal.module, key, handle(sliceRawState, initialState[key], key));
845
1490
  });
1491
+ lockPublicStateObject(internal.module);
846
1492
  } else internal.module = handle(rawState, initialState);
847
1493
  return rawState;
848
1494
  };
849
1495
  //#endregion
850
1496
  //#region packages/core/src/handleState.ts
851
1497
  const handleState = (store, internal, options) => {
852
- const setState = (next, updater = (next) => {
1498
+ let defaultResultValidated = false;
1499
+ const defaultUpdater = (next) => {
1500
+ defaultResultValidated = false;
853
1501
  const merge = (_next = next) => {
1502
+ if (_next !== next) internal.validateState?.(_next);
1503
+ assertKnownStateShape(_next, internal.rootState, internal.stateSchema, store.isSliceStore);
854
1504
  mergeObject(internal.rootState, _next, store.isSliceStore);
855
1505
  };
856
1506
  const fn = typeof next === "function" ? () => {
857
1507
  const returnValue = next(internal.module);
858
- if (returnValue instanceof Promise) throw new Error("setState with async function is not supported");
1508
+ if (returnValue instanceof Promise) {
1509
+ returnValue.catch(() => void 0);
1510
+ throw new Error("setState with async function is not supported");
1511
+ }
859
1512
  if (typeof returnValue === "object" && returnValue !== null) merge(returnValue);
860
1513
  } : merge;
861
1514
  if (!(store.transport ?? options.enablePatches) && internal.mutableInstance) {
@@ -863,9 +1516,11 @@ const handleState = (store, internal, options) => {
863
1516
  internal.actMutable(() => {
864
1517
  fn.apply(null);
865
1518
  });
1519
+ defaultResultValidated = true;
866
1520
  return [];
867
1521
  }
868
1522
  fn.apply(null);
1523
+ defaultResultValidated = true;
869
1524
  return [];
870
1525
  }
871
1526
  internal.backupState = internal.rootState;
@@ -876,42 +1531,71 @@ const handleState = (store, internal, options) => {
876
1531
  internal.rootState = draft;
877
1532
  return fn.apply(null);
878
1533
  }, { enablePatches: true });
879
- if (store.share === "main") validateSharedStateSerializable(result[0]);
1534
+ assertKnownStateShape(result[0], internal.backupState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
1535
+ internal.validateState?.(internal.getTransportState?.() ?? result[0]);
880
1536
  patches = result[1];
881
1537
  inversePatches = result[2];
882
1538
  } finally {
883
1539
  internal.rootState = internal.backupState;
884
1540
  }
885
- const finalPatches = store.patch ? store.patch({
1541
+ const patch = store.patch;
1542
+ const finalPatches = patch ? patch({
886
1543
  patches,
887
1544
  inversePatches
888
1545
  }) : {
889
1546
  patches,
890
1547
  inversePatches
891
1548
  };
892
- const safePatches = sanitizePatches(finalPatches.patches) ?? [];
893
- const safeInversePatches = sanitizePatches(finalPatches.inversePatches) ?? [];
894
- if (safePatches.length) store.apply(internal.rootState, safePatches);
1549
+ if (!patch) internal.validatePatches?.(finalPatches.patches);
1550
+ const safePatches = sanitizeCheckedPatches(finalPatches.patches, "store.patch()");
1551
+ const safeInversePatches = sanitizeCheckedPatches(finalPatches.inversePatches, "store.patch() inverse patches");
1552
+ if (safePatches.length) {
1553
+ defaultResultValidated = internal.applyValidatedPatches?.(internal.rootState, safePatches, !patch) ?? false;
1554
+ if (!internal.applyValidatedPatches) store.apply(internal.rootState, safePatches);
1555
+ } else defaultResultValidated = true;
895
1556
  return [
896
1557
  internal.rootState,
897
1558
  safePatches,
898
1559
  safeInversePatches
899
1560
  ];
900
- }) => {
1561
+ };
1562
+ const setState = (next, updater = defaultUpdater) => {
1563
+ internal.assertAlive?.("setState");
901
1564
  internal.assertMutationAllowed?.("setState");
902
1565
  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
1566
  if (internal.isBatching) throw new Error("setState cannot be called within the updater");
904
1567
  if (next === null) return [];
1568
+ if (typeof next === "object") {
1569
+ internal.validateState?.(next);
1570
+ assertKnownStateShape(next, internal.rootState, internal.stateSchema, store.isSliceStore);
1571
+ }
905
1572
  internal.isBatching = true;
906
- if (!store.share && !options.enablePatches && !internal.mutableInstance) try {
1573
+ if (!store.share && !options.enablePatches && !internal.mutableInstance && updater === defaultUpdater) try {
907
1574
  if (typeof next === "function") try {
908
1575
  internal.backupState = internal.rootState;
909
- internal.rootState = create$1(internal.rootState, (draft) => {
1576
+ const snapshotCache = internal.computedSnapshotCache;
1577
+ const snapshotSources = internal.computedIdentityRequired ? internal.computedSnapshotSources : void 0;
1578
+ const snapshot = snapshotCache?.get(internal.rootState);
1579
+ const updateSnapshot = Boolean(snapshot && snapshotCache);
1580
+ const produced = create$1(internal.rootState, (draft) => {
910
1581
  internal.rootState = draft;
911
1582
  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);
914
- });
1583
+ if (returnValue instanceof Promise) {
1584
+ returnValue.catch(() => void 0);
1585
+ throw new Error("setState with async function is not supported");
1586
+ }
1587
+ if (typeof returnValue === "object" && returnValue !== null) {
1588
+ assertKnownStateShape(returnValue, internal.rootState, internal.stateSchema, store.isSliceStore);
1589
+ mergeObject(internal.rootState, returnValue, store.isSliceStore);
1590
+ }
1591
+ }, { enablePatches: updateSnapshot });
1592
+ const nextState = updateSnapshot ? produced[0] : produced;
1593
+ assertKnownStateShape(nextState, internal.backupState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
1594
+ if (updateSnapshot) {
1595
+ const patches = produced[1];
1596
+ finalizeImmutableStateSnapshot(nextState, apply(snapshot, createImmutableSnapshotPatches(patches, snapshotCache)), patches, snapshotCache, snapshotSources);
1597
+ }
1598
+ internal.rootState = nextState;
915
1599
  } catch (error) {
916
1600
  internal.rootState = internal.backupState;
917
1601
  throw error;
@@ -932,6 +1616,7 @@ const handleState = (store, internal, options) => {
932
1616
  setOwnEnumerable(copyRecord, key, sliceCopy);
933
1617
  }
934
1618
  } else mergeObject(copy, next);
1619
+ assertKnownStateShape(copy, internal.rootState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
935
1620
  internal.rootState = copy;
936
1621
  }
937
1622
  refreshSignalSlots(internal);
@@ -946,7 +1631,8 @@ const handleState = (store, internal, options) => {
946
1631
  const isDrafted = internal.mutableInstance && isDraft(internal.rootState);
947
1632
  if (isDrafted) handleDraft(store, internal);
948
1633
  result = updater(next);
949
- if (store.share === "main") validateSharedStateSerializable(internal.rootState);
1634
+ if (internal.mutableInstance) assertKnownStateShape(internal.rootState, internal.backupState ?? internal.rootState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
1635
+ if (!(updater === defaultUpdater && defaultResultValidated)) internal.validateState?.(internal.getTransportState?.() ?? internal.rootState);
950
1636
  if (isDrafted) {
951
1637
  internal.backupState = internal.rootState;
952
1638
  const [draft, finalize] = create$1(internal.rootState, { enablePatches: true });
@@ -956,12 +1642,16 @@ const handleState = (store, internal, options) => {
956
1642
  } finally {
957
1643
  internal.isBatching = false;
958
1644
  }
959
- if (result?.length) result = [
960
- result[0],
961
- sanitizePatches(result[1]) ?? [],
962
- sanitizePatches(result[2]) ?? []
963
- ];
964
- emit(store, internal, result?.[1]);
1645
+ const trustedDefaultResult = updater === defaultUpdater && defaultResultValidated;
1646
+ if (result?.length && !trustedDefaultResult) {
1647
+ internal.validatePatches?.(result[1]);
1648
+ result = [
1649
+ result[0],
1650
+ sanitizeCheckedPatches(result[1], "setState updater result"),
1651
+ sanitizeCheckedPatches(result[2], "setState updater inverse result")
1652
+ ];
1653
+ }
1654
+ if (result?.[1]) internal.emitPatches?.(result[1]);
965
1655
  return result;
966
1656
  };
967
1657
  const getState = (deps, selector) => deps && selector ? new Computed(deps, selector) : internal.module;
@@ -971,95 +1661,183 @@ const handleState = (store, internal, options) => {
971
1661
  };
972
1662
  };
973
1663
  //#endregion
974
- //#region packages/core/src/applyMiddlewares.ts
975
- const isStoreLike = (value) => {
976
- if (!value || typeof value !== "object") return false;
977
- const candidate = value;
978
- return typeof candidate.setState === "function" && typeof candidate.getState === "function" && typeof candidate.subscribe === "function" && typeof candidate.destroy === "function" && typeof candidate.apply === "function" && typeof candidate.getPureState === "function";
979
- };
980
- const applyMiddlewares = (store, middlewares) => {
981
- return middlewares.reduce((store, middleware, index) => {
982
- if (process.env.NODE_ENV === "development") {
983
- if (typeof middleware !== "function") throw new Error(`middlewares[${index}] should be a function`);
984
- }
985
- const nextStore = middleware(store);
986
- if (process.env.NODE_ENV === "development") {
987
- if (!isStoreLike(nextStore)) throw new Error(`middlewares[${index}] should return a store-like object`);
988
- }
989
- return nextStore;
990
- }, store);
991
- };
992
- //#endregion
993
- //#region packages/core/src/handleMainTransport.ts
994
- const getErrorMessage = (error) => {
995
- if (error instanceof Error) return error.message;
996
- return String(error);
1664
+ //#region packages/core/src/storeFactory.ts
1665
+ const namespaceMap = /* @__PURE__ */ new Map();
1666
+ let hasWarnedAmbiguousFunctionMap = false;
1667
+ const warnAmbiguousFunctionMap = () => {
1668
+ if (hasWarnedAmbiguousFunctionMap || process.env.NODE_ENV === "production" || process.env.NODE_ENV === "test") return;
1669
+ hasWarnedAmbiguousFunctionMap = true;
1670
+ console.warn([
1671
+ `sliceMode: 'auto' inferred slices from an object of functions.`,
1672
+ `This shape is ambiguous with a single store that only contains methods.`,
1673
+ `Use create({ ping() {} }, { sliceMode: 'single' }) for a plain method store,`,
1674
+ `or create({ counter: (set) => ({ count: 0 }) }, { sliceMode: 'slices' }) for slices.`
1675
+ ].join(" "));
997
1676
  };
998
- const transportErrorMarker = "__coactionTransportError__";
999
- const handleMainTransport = (store, internal, storeTransport, workerType, checkEnablePatches) => {
1000
- const transport = storeTransport ?? (workerType === "SharedWorkerInternal" || workerType === "WebWorkerInternal" ? createTransport(workerType, { prefix: store.name }) : void 0);
1001
- if (!transport) return;
1002
- if (typeof transport.onConnect !== "function") throw new Error("transport.onConnect is required");
1003
- if (checkEnablePatches) throw new Error(`enablePatches: true is required for the transport`);
1004
- transport.listen("execute", async (keys, args) => {
1005
- let base = store.getState();
1006
- try {
1007
- for (const key of keys) {
1008
- if (isUnsafePathSegment(key) || typeof base !== "object" && typeof base !== "function" || base === null || !Object.prototype.hasOwnProperty.call(base, key)) throw new Error("The function is not found");
1009
- const obj = base;
1010
- base = base[key];
1011
- if (typeof base === "function") base = base.bind(obj);
1677
+ const createStore = (createState, options, runtime = {}) => {
1678
+ const { share, validatePatches, validateReplacementSource, validateState } = runtime;
1679
+ const store = {};
1680
+ const internal = {
1681
+ sequence: 0,
1682
+ isBatching: false,
1683
+ listeners: /* @__PURE__ */ new Set(),
1684
+ destroyCallbacks: /* @__PURE__ */ new Set(),
1685
+ validatePatches,
1686
+ validateReplacementSource,
1687
+ validateState
1688
+ };
1689
+ internal.notifyStateChange = () => {
1690
+ refreshSignalSlots(internal);
1691
+ internal.listeners.forEach((listener) => listener());
1692
+ };
1693
+ const name = options.name ?? "default";
1694
+ const shouldTrackName = share === "main" && process.env.NODE_ENV !== "test";
1695
+ const releaseStoreName = () => {
1696
+ if (shouldTrackName) namespaceMap.delete(name);
1697
+ };
1698
+ if (shouldTrackName) {
1699
+ if (namespaceMap.get(name)) throw new Error(`Store name '${name}' is not unique.`);
1700
+ namespaceMap.set(name, true);
1701
+ }
1702
+ try {
1703
+ const { setState, getState } = handleState(store, internal, options);
1704
+ const subscribe = (listener) => {
1705
+ internal.assertAlive?.("subscribe");
1706
+ internal.listeners.add(listener);
1707
+ return () => internal.listeners.delete(listener);
1708
+ };
1709
+ let isDestroyed = false;
1710
+ internal.assertAlive = (operation) => {
1711
+ if (isDestroyed) throw new Error(`${operation} cannot be called after store.destroy().`);
1712
+ };
1713
+ const destroy = () => {
1714
+ if (isDestroyed) return;
1715
+ isDestroyed = true;
1716
+ let firstError;
1717
+ const callbacks = [...internal.destroyCallbacks ?? []];
1718
+ internal.destroyCallbacks?.clear();
1719
+ for (const callback of callbacks) try {
1720
+ callback();
1721
+ } catch (error) {
1722
+ firstError ??= error;
1012
1723
  }
1013
- if (typeof base !== "function") throw new Error("The function is not found");
1014
- return [await base(...args), internal.sequence];
1015
- } catch (error) {
1016
- if (process.env.NODE_ENV === "development") console.error(error);
1017
- return [{
1018
- [transportErrorMarker]: true,
1019
- message: getErrorMessage(error)
1020
- }, internal.sequence];
1724
+ internal.listeners.clear();
1725
+ try {
1726
+ store.transport?.dispose();
1727
+ } catch (error) {
1728
+ firstError ??= error;
1729
+ } finally {
1730
+ releaseStoreName();
1731
+ }
1732
+ if (firstError) throw firstError;
1733
+ };
1734
+ const applyState = (state, patches, prepared = false, skipFinalValidation = false) => {
1735
+ internal.assertAlive?.("apply");
1736
+ internal.assertMutationAllowed?.("apply");
1737
+ if (patches && !prepared) validatePatches?.(patches);
1738
+ if (!prepared) assertSafePatches(patches, "store.apply()");
1739
+ const safePatches = prepared ? patches : sanitizePatches(patches);
1740
+ const baseState = state === internal.module ? internal.rootState : state;
1741
+ if (baseState !== internal.rootState) validateReplacementSource?.(baseState);
1742
+ const appliedState = safePatches ? apply(baseState, safePatches) : baseState;
1743
+ const nextState = prepared ? appliedState : sanitizeReplacementState(appliedState);
1744
+ if (!skipFinalValidation) {
1745
+ assertKnownStateShape(nextState, internal.rootState, internal.stateSchema, store.isSliceStore, { requireSliceRoots: true });
1746
+ validateState?.(internal.getTransportState?.() ?? nextState);
1747
+ }
1748
+ internal.rootState = nextState;
1749
+ refreshSignalSlots(internal);
1750
+ if (internal.updateImmutable) internal.updateImmutable(internal.rootState);
1751
+ else internal.listeners.forEach((listener) => listener());
1752
+ };
1753
+ const apply$1 = (state = internal.rootState, patches) => applyState(state, patches);
1754
+ internal.applyValidatedPatches = (state, patches, skipFinalValidation) => {
1755
+ if (store.apply !== apply$1) {
1756
+ store.apply(state, patches);
1757
+ return false;
1758
+ }
1759
+ applyState(state, patches, true, skipFinalValidation);
1760
+ return true;
1761
+ };
1762
+ const getPureState = () => internal.rootState;
1763
+ const isFunctionMapObject = () => {
1764
+ if (typeof createState !== "object" || createState === null) return false;
1765
+ const values = getOwnEnumerableKeys(createState).map((key) => createState[key]);
1766
+ return values.length > 0 && values.every((value) => typeof value === "function");
1767
+ };
1768
+ const getIsSliceStore = () => {
1769
+ const sliceMode = options.sliceMode ?? "auto";
1770
+ if (sliceMode === "single") return false;
1771
+ if (sliceMode === "slices") {
1772
+ if (!isFunctionMapObject()) throw new Error(`sliceMode: 'slices' requires createState to be an object of slice functions.`);
1773
+ return true;
1774
+ }
1775
+ if (isFunctionMapObject()) {
1776
+ warnAmbiguousFunctionMap();
1777
+ return true;
1778
+ }
1779
+ return false;
1780
+ };
1781
+ const isSliceStore = getIsSliceStore();
1782
+ Object.assign(store, {
1783
+ name,
1784
+ share: share ?? false,
1785
+ setState,
1786
+ getState,
1787
+ subscribe,
1788
+ destroy,
1789
+ apply: apply$1,
1790
+ isSliceStore,
1791
+ getPureState
1792
+ });
1793
+ const middlewareStore = applyMiddlewares(store, options.middlewares ?? []);
1794
+ if (middlewareStore !== store) Object.assign(store, middlewareStore);
1795
+ internal.assertAlive?.("store initialization");
1796
+ if (validatePatches && store.patch) {
1797
+ const patch = store.patch.bind(store);
1798
+ store.patch = (options) => {
1799
+ const result = patch(options);
1800
+ validatePatches(result.patches);
1801
+ return result;
1802
+ };
1021
1803
  }
1022
- });
1023
- transport.listen("fullSync", async () => {
1024
- validateSharedStateSerializable(internal.rootState);
1804
+ const initialState = getInitialState(store, createState, internal);
1805
+ internal.assertAlive?.("store initialization");
1806
+ internal.sharedActionPaths = runtime.collectActionPaths?.(initialState, store.isSliceStore);
1807
+ if (!internal.getTransportState) runtime.validateInitialState?.(initialState, store.isSliceStore);
1808
+ store.getInitialState = () => initialState;
1809
+ internal.rootState = getRawState(store, internal, initialState, options, runtime.clientAction);
1810
+ if (validatePatches && store.apply !== apply$1) {
1811
+ const applyWithAdapter = store.apply.bind(store);
1812
+ store.apply = (state, patches) => {
1813
+ internal.assertAlive?.("apply");
1814
+ internal.assertMutationAllowed?.("apply");
1815
+ if (typeof state !== "undefined" && state !== internal.rootState && state !== internal.module) validateReplacementSource?.(state);
1816
+ if (patches) {
1817
+ validatePatches(patches);
1818
+ assertSafePatches(patches, "store.apply()");
1819
+ }
1820
+ applyWithAdapter(state, patches);
1821
+ };
1822
+ }
1823
+ internal.stateSchema = createStateSchema(internal.rootState, store.isSliceStore);
1824
+ validateState?.(internal.getTransportState?.() ?? internal.rootState);
1025
1825
  return {
1026
- state: JSON.stringify(internal.rootState),
1027
- sequence: internal.sequence
1826
+ store,
1827
+ internal
1028
1828
  };
1029
- });
1030
- store.transport = transport;
1031
- };
1032
- //#endregion
1033
- //#region packages/core/src/lifecycle.ts
1034
- const readyStores = /* @__PURE__ */ new WeakSet();
1035
- const readyCallbacks = /* @__PURE__ */ new WeakMap();
1036
- const onStoreReady = (store, callback) => {
1037
- if (readyStores.has(store)) {
1038
- callback();
1039
- return () => void 0;
1040
- }
1041
- let callbacks = readyCallbacks.get(store);
1042
- if (!callbacks) {
1043
- callbacks = /* @__PURE__ */ new Set();
1044
- readyCallbacks.set(store, callbacks);
1829
+ } catch (error) {
1830
+ try {
1831
+ store.destroy?.();
1832
+ } catch (destroyError) {
1833
+ if (process.env.NODE_ENV === "development") console.error(destroyError);
1834
+ }
1835
+ releaseStoreName();
1836
+ throw error;
1045
1837
  }
1046
- callbacks.add(callback);
1047
- return () => {
1048
- callbacks?.delete(callback);
1049
- };
1050
- };
1051
- const markStoreReady = (store) => {
1052
- readyStores.add(store);
1053
- const callbacks = readyCallbacks.get(store);
1054
- if (!callbacks) return;
1055
- readyCallbacks.delete(store);
1056
- callbacks.forEach((callback) => callback());
1057
- callbacks.clear();
1058
1838
  };
1059
1839
  //#endregion
1060
1840
  //#region packages/core/src/create.ts
1061
- const namespaceMap = /* @__PURE__ */ new Map();
1062
- let hasWarnedAmbiguousFunctionMap = false;
1063
1841
  const isMainWorkerType = (workerType) => workerType === "SharedWorkerInternal" || workerType === "WebWorkerInternal";
1064
1842
  const isClientWorkerType = (workerType) => workerType === "SharedWorkerClient" || workerType === "WebWorkerClient";
1065
1843
  const validateCreateModeOptions = (options) => {
@@ -1073,290 +1851,50 @@ const validateCreateModeOptions = (options) => {
1073
1851
  if (isMainWorkerType(explicitWorkerType) && (clientTransport || worker)) throw new Error("main workerType cannot be combined with client transport settings.");
1074
1852
  if (isClientWorkerType(explicitWorkerType) && storeTransport) throw new Error("client workerType cannot be combined with transport.");
1075
1853
  };
1076
- const warnAmbiguousFunctionMap = () => {
1077
- if (hasWarnedAmbiguousFunctionMap || process.env.NODE_ENV === "production" || process.env.NODE_ENV === "test") return;
1078
- hasWarnedAmbiguousFunctionMap = true;
1079
- console.warn([
1080
- `sliceMode: 'auto' inferred slices from an object of functions.`,
1081
- `This shape is ambiguous with a single store that only contains methods.`,
1082
- `Use create({ ping() {} }, { sliceMode: 'single' }) for a plain method store,`,
1083
- `or create({ counter: (set) => ({ count: 0 }) }, { sliceMode: 'slices' }) for slices.`
1084
- ].join(" "));
1085
- };
1086
1854
  /**
1087
1855
  * Create a local store, the main side of a shared store, or a client mirror of
1088
1856
  * a shared store.
1089
1857
  *
1090
1858
  * @remarks
1091
- * - Pass a {@link Slice} function for a single store.
1092
- * - Pass an object of slice factories for a slices store.
1093
- * - When an object input only contains functions, prefer explicit `sliceMode`
1094
- * to avoid ambiguous inference.
1095
- * - When `clientTransport` or `worker` is provided, returned store methods
1096
- * become promise-returning methods because execution happens on the main
1097
- * shared store.
1098
- * - New semantics should prefer explicit helpers or variants over adding more
1099
- * ambiguous `create()` input forms.
1859
+ * Prefer the static `coaction/local` entry when transport support is not
1860
+ * required. It excludes the JSON protocol and reconnect runtime from the
1861
+ * consumer dependency graph.
1100
1862
  */
1101
1863
  const create = (createState, options = {}) => {
1102
1864
  const checkEnablePatches = Object.hasOwnProperty.call(options, "enablePatches") && !options.enablePatches;
1103
1865
  validateCreateModeOptions(options);
1104
1866
  const workerType = options.workerType ?? WorkerType;
1105
1867
  const storeTransport = options.transport;
1106
- const share = workerType === "WebWorkerInternal" || workerType === "SharedWorkerInternal" || storeTransport ? "main" : void 0;
1107
- const createStore = ({ share }) => {
1108
- const store = {};
1109
- const internal = {
1110
- sequence: 0,
1111
- isBatching: false,
1112
- listeners: /* @__PURE__ */ new Set()
1113
- };
1114
- internal.notifyStateChange = () => {
1115
- refreshSignalSlots(internal);
1116
- internal.listeners.forEach((listener) => listener());
1117
- };
1118
- const name = options.name ?? "default";
1119
- const shouldTrackName = share === "main" && process.env.NODE_ENV !== "test";
1120
- const releaseStoreName = () => {
1121
- if (shouldTrackName) namespaceMap.delete(name);
1122
- };
1123
- if (shouldTrackName) {
1124
- if (namespaceMap.get(name)) throw new Error(`Store name '${name}' is not unique.`);
1125
- namespaceMap.set(name, true);
1126
- }
1127
- try {
1128
- const { setState, getState } = handleState(store, internal, options);
1129
- const subscribe = (listener) => {
1130
- internal.listeners.add(listener);
1131
- return () => internal.listeners.delete(listener);
1132
- };
1133
- let isDestroyed = false;
1134
- const destroy = () => {
1135
- if (isDestroyed) return;
1136
- isDestroyed = true;
1137
- internal.listeners.clear();
1138
- store.transport?.dispose();
1139
- releaseStoreName();
1140
- };
1141
- const apply$1 = (state = internal.rootState, patches) => {
1142
- internal.assertMutationAllowed?.("apply");
1143
- const safePatches = sanitizePatches(patches);
1144
- const nextState = sanitizeReplacementState(safePatches ? apply(state, safePatches) : state);
1145
- if (store.share === "main") validateSharedStateSerializable(nextState);
1146
- internal.rootState = nextState;
1147
- refreshSignalSlots(internal);
1148
- if (internal.updateImmutable) internal.updateImmutable(internal.rootState);
1149
- else internal.listeners.forEach((listener) => listener());
1150
- };
1151
- const getPureState = () => internal.rootState;
1152
- const isFunctionMapObject = () => {
1153
- if (typeof createState === "object" && createState !== null) {
1154
- const values = getOwnEnumerableKeys(createState).map((key) => createState[key]);
1155
- return values.length > 0 && values.every((value) => typeof value === "function");
1156
- }
1157
- return false;
1158
- };
1159
- const getIsSliceStore = () => {
1160
- const sliceMode = options.sliceMode ?? "auto";
1161
- if (sliceMode === "single") return false;
1162
- if (sliceMode === "slices") {
1163
- if (!isFunctionMapObject()) throw new Error(`sliceMode: 'slices' requires createState to be an object of slice functions.`);
1164
- return true;
1165
- }
1166
- if (isFunctionMapObject()) {
1167
- warnAmbiguousFunctionMap();
1168
- return true;
1169
- }
1170
- return false;
1171
- };
1172
- const isSliceStore = getIsSliceStore();
1173
- Object.assign(store, {
1174
- name,
1175
- share: share ?? false,
1176
- setState,
1177
- getState,
1178
- subscribe,
1179
- destroy,
1180
- apply: apply$1,
1181
- isSliceStore,
1182
- getPureState
1183
- });
1184
- const middlewareStore = applyMiddlewares(store, options.middlewares ?? []);
1185
- if (middlewareStore !== store) Object.assign(store, middlewareStore);
1186
- const initialState = getInitialState(store, createState, internal);
1187
- if (share) validateSharedActionPaths(initialState);
1188
- store.getInitialState = () => initialState;
1189
- internal.rootState = getRawState(store, internal, initialState, options);
1190
- if (share) validateSharedStateSerializable(internal.rootState);
1191
- markStoreReady(store);
1192
- return {
1193
- store,
1194
- internal
1195
- };
1196
- } catch (error) {
1197
- releaseStoreName();
1198
- throw error;
1199
- }
1200
- };
1201
- if (options.clientTransport || options.worker || options.workerType === "WebWorkerClient" || options.workerType === "SharedWorkerClient") {
1202
- if (checkEnablePatches) throw new Error(`enablePatches: true is required for the async store`);
1203
- return wrapStore(createAsyncClientStore(createStore, options));
1204
- }
1205
- const { store, internal } = createStore({ share });
1206
- handleMainTransport(store, internal, storeTransport, workerType, checkEnablePatches);
1207
- return wrapStore(store);
1208
- };
1209
- //#endregion
1210
- //#region packages/core/src/binder.ts
1211
- const createExternalStoreAdapter = ({ handleState, handleStore }) => ((state) => {
1212
- const { copyState, key, bind } = handleState(state);
1213
- const value = typeof key !== "undefined" ? copyState[key] : copyState;
1214
- Object.defineProperty(value, bindSymbol, {
1215
- configurable: true,
1216
- enumerable: typeof key !== "undefined",
1217
- value: {
1218
- handleStore,
1219
- bind
1220
- }
1221
- });
1222
- return copyState;
1223
- });
1224
- /**
1225
- * Build an adapter helper for bridging an external store implementation into
1226
- * Coaction.
1227
- *
1228
- * @remarks
1229
- * Official bindings use this to integrate stores such as Redux, Jotai, Pinia,
1230
- * Zustand, MobX, and Valtio. Binder-backed integrations are whole-store
1231
- * adapters; they are not compatible with Coaction slices mode.
1232
- */
1233
- function createBinder({ handleState, handleStore }) {
1234
- return createExternalStoreAdapter({
1235
- handleState,
1236
- handleStore
1868
+ const share = isMainWorkerType(workerType) || storeTransport ? "main" : void 0;
1869
+ const buildStore = ({ share }) => createStore(createState, options, {
1870
+ share,
1871
+ clientAction: share === "client" ? createClientAction : void 0,
1872
+ collectActionPaths: share === "main" ? validateSharedActionPaths : void 0,
1873
+ validateInitialState: share ? validateSharedInitialState : void 0,
1874
+ validatePatches: share === "main" ? validateUpdatePatches : void 0,
1875
+ validateReplacementSource: share ? validateSharedReplacementSource : void 0,
1876
+ validateState: share ? validateSharedStateSerializable : void 0
1237
1877
  });
1238
- }
1239
- /**
1240
- * Define a whole-store adapter for integrating an external state runtime with
1241
- * Coaction.
1242
- *
1243
- * @remarks
1244
- * This is the stable 2.x name for adapter authors. `createBinder()` remains as
1245
- * a compatibility alias for existing official and community integrations.
1246
- */
1247
- function defineExternalStoreAdapter(options) {
1248
- return createExternalStoreAdapter(options);
1249
- }
1250
- //#endregion
1251
- //#region packages/core/src/reactiveTracker.ts
1252
- const ReactiveFlags = alienSignalsSystem.ReactiveFlags;
1253
- const unwatch = (node) => {
1254
- if (!(node.flags & ReactiveFlags.Mutable)) {
1255
- node.depsTail = void 0;
1256
- node.flags = 0;
1257
- purgeDeps(node);
1258
- const sub = node.subs;
1259
- if (sub !== void 0) unlink(sub);
1260
- return;
1878
+ if (options.clientTransport || options.worker || isClientWorkerType(options.workerType)) {
1879
+ if (checkEnablePatches) throw new Error("enablePatches: true is required for the async store");
1880
+ return wrapStore(createAsyncClientStore(buildStore, options));
1261
1881
  }
1262
- if (node.depsTail !== void 0) {
1263
- node.depsTail = void 0;
1264
- node.flags = ReactiveFlags.Mutable | ReactiveFlags.Dirty;
1265
- purgeDeps(node);
1882
+ if (share === "main" && checkEnablePatches) throw new Error("enablePatches: true is required for the transport");
1883
+ let builtStore;
1884
+ try {
1885
+ builtStore = buildStore({ share });
1886
+ } catch (error) {
1887
+ return failTransportInitialization(storeTransport, error);
1266
1888
  }
1267
- };
1268
- const unlink = (link, sub = link.sub) => {
1269
- const dep = link.dep;
1270
- const prevDep = link.prevDep;
1271
- const nextDep = link.nextDep;
1272
- const nextSub = link.nextSub;
1273
- const prevSub = link.prevSub;
1274
- if (nextDep !== void 0) nextDep.prevDep = prevDep;
1275
- else sub.depsTail = prevDep;
1276
- if (prevDep !== void 0) prevDep.nextDep = nextDep;
1277
- else sub.deps = nextDep;
1278
- if (nextSub !== void 0) nextSub.prevSub = prevSub;
1279
- else dep.subsTail = prevSub;
1280
- if (prevSub !== void 0) prevSub.nextSub = nextSub;
1281
- else if ((dep.subs = nextSub) === void 0) unwatch(dep);
1282
- return nextDep;
1283
- };
1284
- const purgeDeps = (sub) => {
1285
- const depsTail = sub.depsTail;
1286
- let dep = depsTail !== void 0 ? depsTail.nextDep : sub.deps;
1287
- while (dep !== void 0) dep = unlink(dep, sub);
1288
- };
1289
- const createReactiveTracker = () => {
1290
- let version = 0;
1291
- let disposed = false;
1292
- const listeners = /* @__PURE__ */ new Set();
1293
- const node = {
1294
- deps: void 0,
1295
- depsTail: void 0,
1296
- subs: void 0,
1297
- subsTail: void 0,
1298
- flags: ReactiveFlags.Watching,
1299
- fn: () => {
1300
- if (disposed) return;
1301
- version += 1;
1302
- listeners.forEach((listener) => listener());
1303
- }
1304
- };
1305
- const dispose = () => {
1306
- if (disposed) return;
1307
- disposed = true;
1308
- listeners.clear();
1309
- node.depsTail = void 0;
1310
- purgeDeps(node);
1311
- node.flags = 0;
1312
- };
1313
- return {
1314
- getSnapshot: () => version,
1315
- subscribe(listener) {
1316
- if (disposed) return () => void 0;
1317
- listeners.add(listener);
1318
- return () => {
1319
- listeners.delete(listener);
1320
- };
1321
- },
1322
- track(fn) {
1323
- if (disposed) return fn();
1324
- node.depsTail = void 0;
1325
- node.flags = ReactiveFlags.Watching | ReactiveFlags.RecursedCheck;
1326
- const prevSub = setActiveSub(node);
1327
- try {
1328
- return fn();
1329
- } finally {
1330
- setActiveSub(prevSub);
1331
- node.flags &= ~ReactiveFlags.RecursedCheck;
1332
- purgeDeps(node);
1333
- }
1334
- },
1335
- dispose
1336
- };
1337
- };
1338
- //#endregion
1339
- //#region packages/core/src/replaceExternalStoreState.ts
1340
- const replaceExternalStoreState = (store, internal, source, { syncImmutable = true } = {}) => {
1341
- const [, patches, inversePatches] = create$1(internal.rootState, (draft) => {
1342
- replaceOwnEnumerable(draft, source);
1343
- }, { enablePatches: true });
1344
- const safePatches = sanitizePatches((store.patch ? store.patch({
1345
- patches,
1346
- inversePatches
1347
- }) : {
1348
- patches,
1349
- inversePatches
1350
- }).patches) ?? [];
1351
- if (!safePatches.length) return;
1352
- const updateImmutable = internal.updateImmutable;
1353
- if (!syncImmutable) internal.updateImmutable = void 0;
1889
+ const { store, internal } = builtStore;
1354
1890
  try {
1355
- store.apply(internal.rootState, safePatches);
1356
- } finally {
1357
- internal.updateImmutable = updateImmutable;
1891
+ handleMainTransport(store, internal, storeTransport, workerType, checkEnablePatches, options.transportPolicy);
1892
+ markStoreReady(store);
1893
+ internal.assertAlive?.("store initialization");
1894
+ } catch (error) {
1895
+ return failStoreSetup(store, error);
1358
1896
  }
1359
- emit(store, internal, safePatches);
1897
+ return wrapStore(store);
1360
1898
  };
1361
1899
  //#endregion
1362
- export { computed, create, createBinder, createReactiveTracker, defineExternalStoreAdapter, effect, effectScope, endBatch, isComputed, isEffect, isEffectScope, isSignal, onStoreReady, replaceExternalStoreState, replaceOwnEnumerable, sanitizeInitialStateValue, sanitizeReplacementState, signal, startBatch, trigger, wrapStore };
1900
+ export { ActionAuthorityChangedError, StateSchemaError, UnsafePatchPathError, assertSafePatches, computed, create, effect, effectScope, endBatch, isComputed, isEffect, isEffectScope, isSignal, isStateSchemaError, onStoreReady, sanitizeInitialStateValue, sanitizePatches, sanitizeReplacementState, signal, startBatch, trigger };