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