@sladg/apex-state 3.3.0 → 3.4.1
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 +447 -96
- package/dist/apex_state_wasm-WOOTVUVC.js +674 -0
- package/dist/apex_state_wasm-WOOTVUVC.js.map +1 -0
- package/dist/chunk-SLJZLVMQ.js +2390 -0
- package/dist/chunk-SLJZLVMQ.js.map +1 -0
- package/dist/chunk-VR6T6OJS.js +26 -0
- package/dist/chunk-VR6T6OJS.js.map +1 -0
- package/dist/index.d.ts +19 -1
- package/dist/index.js +25 -3053
- package/dist/index.js.map +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +38 -2022
- package/dist/testing/index.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,2390 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__export
|
|
3
|
+
} from "./chunk-VR6T6OJS.js";
|
|
4
|
+
|
|
5
|
+
// src/store/create-store.ts
|
|
6
|
+
import { useCallback, useLayoutEffect as useLayoutEffect2 } from "react";
|
|
7
|
+
import { snapshot as snapshot3, useSnapshot } from "valtio";
|
|
8
|
+
|
|
9
|
+
// src/concerns/registration.ts
|
|
10
|
+
import { effect } from "valtio-reactive";
|
|
11
|
+
|
|
12
|
+
// src/utils/is.ts
|
|
13
|
+
var isNil = (value) => value == null;
|
|
14
|
+
var isUndefined = (value) => value === void 0;
|
|
15
|
+
var isNull = (value) => value === null;
|
|
16
|
+
var isObject = (value) => {
|
|
17
|
+
if (value == null || typeof value !== "object" || Array.isArray(value))
|
|
18
|
+
return false;
|
|
19
|
+
const proto = Object.getPrototypeOf(value);
|
|
20
|
+
return proto === Object.prototype || proto === null;
|
|
21
|
+
};
|
|
22
|
+
var isArray = (value) => Array.isArray(value);
|
|
23
|
+
var isString = (value) => typeof value === "string";
|
|
24
|
+
var isNumber = (value) => typeof value === "number";
|
|
25
|
+
var isBoolean = (value) => typeof value === "boolean";
|
|
26
|
+
var isFunction = (value) => typeof value === "function";
|
|
27
|
+
var isSymbol = (value) => typeof value === "symbol";
|
|
28
|
+
var isDate = (value) => value instanceof Date;
|
|
29
|
+
var isRegExp = (value) => value instanceof RegExp;
|
|
30
|
+
var isPrimitive = (value) => {
|
|
31
|
+
const type = typeof value;
|
|
32
|
+
return type === "string" || type === "number" || type === "boolean" || type === "symbol" || type === "bigint" || value == null;
|
|
33
|
+
};
|
|
34
|
+
var isEmptyObject = (value) => {
|
|
35
|
+
for (const key in value) {
|
|
36
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
var isEmpty = (value) => {
|
|
43
|
+
if (isNil(value)) return true;
|
|
44
|
+
if (isNumber(value) || isBoolean(value)) return false;
|
|
45
|
+
if (isString(value)) return value.length === 0;
|
|
46
|
+
if (isArray(value)) return value.length === 0;
|
|
47
|
+
if (isObject(value)) return isEmptyObject(value);
|
|
48
|
+
return false;
|
|
49
|
+
};
|
|
50
|
+
var isEqualArray = (a, b) => {
|
|
51
|
+
if (a.length !== b.length) return false;
|
|
52
|
+
for (let i = 0; i < a.length; i++) {
|
|
53
|
+
if (!iEqual(a[i], b[i])) return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
};
|
|
57
|
+
var isEqualObject = (a, b) => {
|
|
58
|
+
const keysA = Object.keys(a);
|
|
59
|
+
const keysB = Object.keys(b);
|
|
60
|
+
if (keysA.length !== keysB.length) return false;
|
|
61
|
+
for (const key of keysA) {
|
|
62
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
|
|
63
|
+
if (!iEqual(a[key], b[key])) return false;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
};
|
|
67
|
+
var iEqual = (a, b) => {
|
|
68
|
+
if (a === b) return true;
|
|
69
|
+
if (isArray(a) && isArray(b)) {
|
|
70
|
+
return isEqualArray(a, b);
|
|
71
|
+
}
|
|
72
|
+
if (isDate(a) && isDate(b)) {
|
|
73
|
+
return a.getTime() === b.getTime();
|
|
74
|
+
}
|
|
75
|
+
if (isRegExp(a) && isRegExp(b)) {
|
|
76
|
+
return a.toString() === b.toString();
|
|
77
|
+
}
|
|
78
|
+
if (isObject(a) && isObject(b)) {
|
|
79
|
+
return isEqualObject(a, b);
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
};
|
|
83
|
+
var isNotNil = (value) => value != null;
|
|
84
|
+
var isNotUndefined = (value) => value !== void 0;
|
|
85
|
+
var isNotNull = (value) => value !== null;
|
|
86
|
+
var isNotObject = (value) => !isObject(value);
|
|
87
|
+
var isNotArray = (value) => !Array.isArray(value);
|
|
88
|
+
var isNotString = (value) => typeof value !== "string";
|
|
89
|
+
var isNotNumber = (value) => typeof value !== "number";
|
|
90
|
+
var isNotBoolean = (value) => typeof value !== "boolean";
|
|
91
|
+
var isNotFunction = (value) => typeof value !== "function";
|
|
92
|
+
var isNotSymbol = (value) => typeof value !== "symbol";
|
|
93
|
+
var isNotDate = (value) => !(value instanceof Date);
|
|
94
|
+
var isNotRegExp = (value) => !(value instanceof RegExp);
|
|
95
|
+
var isNotPrimitive = (value) => {
|
|
96
|
+
const type = typeof value;
|
|
97
|
+
return !(type === "string" || type === "number" || type === "boolean" || type === "symbol" || type === "bigint" || value == null);
|
|
98
|
+
};
|
|
99
|
+
var isNotEmpty = (value) => !isEmpty(value);
|
|
100
|
+
var isNotEqual = (a, b) => !iEqual(a, b);
|
|
101
|
+
var is = {
|
|
102
|
+
nil: isNil,
|
|
103
|
+
undefined: isUndefined,
|
|
104
|
+
null: isNull,
|
|
105
|
+
object: isObject,
|
|
106
|
+
array: isArray,
|
|
107
|
+
string: isString,
|
|
108
|
+
number: isNumber,
|
|
109
|
+
boolean: isBoolean,
|
|
110
|
+
function: isFunction,
|
|
111
|
+
symbol: isSymbol,
|
|
112
|
+
date: isDate,
|
|
113
|
+
regexp: isRegExp,
|
|
114
|
+
primitive: isPrimitive,
|
|
115
|
+
empty: isEmpty,
|
|
116
|
+
equal: iEqual,
|
|
117
|
+
not: {
|
|
118
|
+
nil: isNotNil,
|
|
119
|
+
undefined: isNotUndefined,
|
|
120
|
+
null: isNotNull,
|
|
121
|
+
object: isNotObject,
|
|
122
|
+
array: isNotArray,
|
|
123
|
+
string: isNotString,
|
|
124
|
+
number: isNotNumber,
|
|
125
|
+
boolean: isNotBoolean,
|
|
126
|
+
function: isNotFunction,
|
|
127
|
+
symbol: isNotSymbol,
|
|
128
|
+
date: isNotDate,
|
|
129
|
+
regexp: isNotRegExp,
|
|
130
|
+
primitive: isNotPrimitive,
|
|
131
|
+
empty: isNotEmpty,
|
|
132
|
+
equal: isNotEqual
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// src/utils/dot.ts
|
|
137
|
+
var pathCache = /* @__PURE__ */ new Map();
|
|
138
|
+
var MAX_CACHE_SIZE = 1e3;
|
|
139
|
+
var getPathParts = (path) => {
|
|
140
|
+
if (typeof path !== "string") {
|
|
141
|
+
throw new TypeError(
|
|
142
|
+
`[apex-state] Path must be a string, received ${typeof path}: ${JSON.stringify(path)}. Paths must be dot-notation strings like "user.email" or "items.0.name".`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
let parts = pathCache.get(path);
|
|
146
|
+
if (!parts) {
|
|
147
|
+
parts = path.split(".");
|
|
148
|
+
if (pathCache.size >= MAX_CACHE_SIZE) {
|
|
149
|
+
pathCache.clear();
|
|
150
|
+
}
|
|
151
|
+
pathCache.set(path, parts);
|
|
152
|
+
}
|
|
153
|
+
return parts;
|
|
154
|
+
};
|
|
155
|
+
var get = (obj, path) => {
|
|
156
|
+
const parts = getPathParts(path);
|
|
157
|
+
let current = obj;
|
|
158
|
+
for (const part of parts) {
|
|
159
|
+
if (is.not.object(current)) {
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
current = Reflect.get(current, part);
|
|
163
|
+
}
|
|
164
|
+
return current;
|
|
165
|
+
};
|
|
166
|
+
var get__unsafe = (obj, path) => {
|
|
167
|
+
const parts = getPathParts(path);
|
|
168
|
+
let current = obj;
|
|
169
|
+
for (const part of parts) {
|
|
170
|
+
if (is.not.object(current)) {
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
current = Reflect.get(current, part);
|
|
174
|
+
}
|
|
175
|
+
return current;
|
|
176
|
+
};
|
|
177
|
+
var set = (obj, path, value) => {
|
|
178
|
+
const keys = getPathParts(path);
|
|
179
|
+
const last = keys.length - 1;
|
|
180
|
+
let current = obj;
|
|
181
|
+
for (let i = 0; i < last; i++) {
|
|
182
|
+
let next = Reflect.get(current, keys[i]);
|
|
183
|
+
if (!is.object(next)) {
|
|
184
|
+
next = {};
|
|
185
|
+
Reflect.set(current, keys[i], next);
|
|
186
|
+
}
|
|
187
|
+
current = next;
|
|
188
|
+
}
|
|
189
|
+
Reflect.set(current, keys[last], value);
|
|
190
|
+
};
|
|
191
|
+
var set__unsafe = (obj, path, value) => {
|
|
192
|
+
const keys = getPathParts(path);
|
|
193
|
+
const last = keys.length - 1;
|
|
194
|
+
let current = obj;
|
|
195
|
+
for (let i = 0; i < last; i++) {
|
|
196
|
+
let next = Reflect.get(current, keys[i]);
|
|
197
|
+
if (!is.object(next)) {
|
|
198
|
+
next = {};
|
|
199
|
+
Reflect.set(current, keys[i], next);
|
|
200
|
+
}
|
|
201
|
+
current = next;
|
|
202
|
+
}
|
|
203
|
+
Reflect.set(current, keys[last], value);
|
|
204
|
+
};
|
|
205
|
+
var has = (obj, path) => {
|
|
206
|
+
const parts = getPathParts(path);
|
|
207
|
+
let current = obj;
|
|
208
|
+
for (const part of parts) {
|
|
209
|
+
if (is.not.object(current)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
current = Reflect.get(current, part);
|
|
213
|
+
}
|
|
214
|
+
return is.not.undefined(current);
|
|
215
|
+
};
|
|
216
|
+
var same = (obj, ...paths) => {
|
|
217
|
+
if (paths.length === 0) return true;
|
|
218
|
+
if (paths.length === 1) return true;
|
|
219
|
+
const firstValue = get__unsafe(obj, paths[0]);
|
|
220
|
+
for (let i = 1; i < paths.length; i++) {
|
|
221
|
+
if (!is.equal(get__unsafe(obj, paths[i]), firstValue)) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
};
|
|
227
|
+
var dot = {
|
|
228
|
+
get,
|
|
229
|
+
get__unsafe,
|
|
230
|
+
set,
|
|
231
|
+
set__unsafe,
|
|
232
|
+
has,
|
|
233
|
+
same
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/utils/bool-logic.ts
|
|
237
|
+
var evaluateNumericComparison = (logic, state) => {
|
|
238
|
+
if ("GT" in logic) {
|
|
239
|
+
const [path, threshold] = logic.GT;
|
|
240
|
+
const value = dot.get__unsafe(state, path);
|
|
241
|
+
return is.number(value) && value > threshold;
|
|
242
|
+
}
|
|
243
|
+
if ("LT" in logic) {
|
|
244
|
+
const [path, threshold] = logic.LT;
|
|
245
|
+
const value = dot.get__unsafe(state, path);
|
|
246
|
+
return is.number(value) && value < threshold;
|
|
247
|
+
}
|
|
248
|
+
if ("GTE" in logic) {
|
|
249
|
+
const [path, threshold] = logic.GTE;
|
|
250
|
+
const value = dot.get__unsafe(state, path);
|
|
251
|
+
return is.number(value) && value >= threshold;
|
|
252
|
+
}
|
|
253
|
+
if ("LTE" in logic) {
|
|
254
|
+
const [path, threshold] = logic.LTE;
|
|
255
|
+
const value = dot.get__unsafe(state, path);
|
|
256
|
+
return is.number(value) && value <= threshold;
|
|
257
|
+
}
|
|
258
|
+
return void 0;
|
|
259
|
+
};
|
|
260
|
+
var evaluateBoolLogic = (logic, state) => {
|
|
261
|
+
if ("IS_EQUAL" in logic) {
|
|
262
|
+
const [path, expected] = logic.IS_EQUAL;
|
|
263
|
+
return dot.get__unsafe(state, path) === expected;
|
|
264
|
+
}
|
|
265
|
+
if ("EXISTS" in logic) {
|
|
266
|
+
return is.not.nil(dot.get__unsafe(state, logic.EXISTS));
|
|
267
|
+
}
|
|
268
|
+
if ("IS_EMPTY" in logic) {
|
|
269
|
+
return is.empty(dot.get__unsafe(state, logic.IS_EMPTY));
|
|
270
|
+
}
|
|
271
|
+
if ("AND" in logic) {
|
|
272
|
+
return logic.AND.every((subLogic) => evaluateBoolLogic(subLogic, state));
|
|
273
|
+
}
|
|
274
|
+
if ("OR" in logic) {
|
|
275
|
+
return logic.OR.some((subLogic) => evaluateBoolLogic(subLogic, state));
|
|
276
|
+
}
|
|
277
|
+
if ("NOT" in logic) {
|
|
278
|
+
return !evaluateBoolLogic(logic.NOT, state);
|
|
279
|
+
}
|
|
280
|
+
const numericResult = evaluateNumericComparison(logic, state);
|
|
281
|
+
if (is.not.undefined(numericResult)) {
|
|
282
|
+
return numericResult;
|
|
283
|
+
}
|
|
284
|
+
if ("IN" in logic) {
|
|
285
|
+
const [path, allowed] = logic.IN;
|
|
286
|
+
return allowed.includes(dot.get__unsafe(state, path));
|
|
287
|
+
}
|
|
288
|
+
return false;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/concerns/prebuilts/index.ts
|
|
292
|
+
var prebuilts_exports = {};
|
|
293
|
+
__export(prebuilts_exports, {
|
|
294
|
+
disabledWhen: () => disabledWhen,
|
|
295
|
+
dynamicLabel: () => dynamicLabel,
|
|
296
|
+
dynamicPlaceholder: () => dynamicPlaceholder,
|
|
297
|
+
dynamicTooltip: () => dynamicTooltip,
|
|
298
|
+
prebuilts: () => prebuilts,
|
|
299
|
+
prebuiltsNamespace: () => prebuiltsNamespace,
|
|
300
|
+
readonlyWhen: () => readonlyWhen,
|
|
301
|
+
validationState: () => validationState,
|
|
302
|
+
visibleWhen: () => visibleWhen
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// src/concerns/prebuilts/disabled-when.ts
|
|
306
|
+
var disabledWhen = {
|
|
307
|
+
name: "disabledWhen",
|
|
308
|
+
description: "Boolean logic for disabled state",
|
|
309
|
+
evaluate: (props) => {
|
|
310
|
+
return evaluateBoolLogic(props.boolLogic, props.state);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/utils/interpolation.ts
|
|
315
|
+
var extractPlaceholders = (template) => {
|
|
316
|
+
const regex = /\{\{([^}]+)\}\}/g;
|
|
317
|
+
const matches = [];
|
|
318
|
+
let match;
|
|
319
|
+
while ((match = regex.exec(template)) !== null) {
|
|
320
|
+
if (match[1]) {
|
|
321
|
+
matches.push(match[1]);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return matches;
|
|
325
|
+
};
|
|
326
|
+
var interpolateTemplate = (template, state) => {
|
|
327
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (match, path) => {
|
|
328
|
+
const value = dot.get__unsafe(state, path);
|
|
329
|
+
if (is.string(value)) return value;
|
|
330
|
+
if (is.number(value)) return String(value);
|
|
331
|
+
if (is.boolean(value)) return String(value);
|
|
332
|
+
return match;
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/concerns/prebuilts/dynamic-label.ts
|
|
337
|
+
var dynamicLabel = {
|
|
338
|
+
name: "dynamicLabel",
|
|
339
|
+
description: "Template string interpolation for labels",
|
|
340
|
+
evaluate: (props) => {
|
|
341
|
+
return interpolateTemplate(props.template, props.state);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// src/concerns/prebuilts/dynamic-placeholder.ts
|
|
346
|
+
var dynamicPlaceholder = {
|
|
347
|
+
name: "dynamicPlaceholder",
|
|
348
|
+
description: "Template string interpolation for placeholders",
|
|
349
|
+
evaluate: (props) => {
|
|
350
|
+
return interpolateTemplate(props.template, props.state);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// src/concerns/prebuilts/dynamic-tooltip.ts
|
|
355
|
+
var dynamicTooltip = {
|
|
356
|
+
name: "dynamicTooltip",
|
|
357
|
+
description: "Template string interpolation for tooltips",
|
|
358
|
+
evaluate: (props) => {
|
|
359
|
+
return interpolateTemplate(props.template, props.state);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// src/concerns/prebuilts/readonly-when.ts
|
|
364
|
+
var readonlyWhen = {
|
|
365
|
+
name: "readonlyWhen",
|
|
366
|
+
description: "Boolean logic for readonly state",
|
|
367
|
+
evaluate: (props) => {
|
|
368
|
+
return evaluateBoolLogic(props.boolLogic, props.state);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// src/concerns/prebuilts/validation-state.ts
|
|
373
|
+
var validationState = {
|
|
374
|
+
name: "validationState",
|
|
375
|
+
description: "Schema validation with isError flag and detailed errors",
|
|
376
|
+
evaluate: (props) => {
|
|
377
|
+
const valueToValidate = "scope" in props && props.scope ? dot.get__unsafe(props.state, props.scope) : props.value;
|
|
378
|
+
const result = props.schema.safeParse(valueToValidate);
|
|
379
|
+
if (result.success) {
|
|
380
|
+
return {
|
|
381
|
+
isError: false,
|
|
382
|
+
errors: []
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
const errors = result.error.errors.map((err) => ({
|
|
386
|
+
field: err.path.length > 0 ? err.path.join(".") : ".",
|
|
387
|
+
message: err.message
|
|
388
|
+
}));
|
|
389
|
+
return {
|
|
390
|
+
isError: true,
|
|
391
|
+
errors
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/concerns/prebuilts/visible-when.ts
|
|
397
|
+
var visibleWhen = {
|
|
398
|
+
name: "visibleWhen",
|
|
399
|
+
description: "Boolean logic for visibility",
|
|
400
|
+
evaluate: (props) => {
|
|
401
|
+
return evaluateBoolLogic(props.boolLogic, props.state);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// src/concerns/prebuilts/index.ts
|
|
406
|
+
var prebuilts = [
|
|
407
|
+
validationState,
|
|
408
|
+
disabledWhen,
|
|
409
|
+
readonlyWhen,
|
|
410
|
+
visibleWhen,
|
|
411
|
+
dynamicTooltip,
|
|
412
|
+
dynamicLabel,
|
|
413
|
+
dynamicPlaceholder
|
|
414
|
+
];
|
|
415
|
+
var prebuiltsNamespace = {
|
|
416
|
+
validationState,
|
|
417
|
+
disabledWhen,
|
|
418
|
+
readonlyWhen,
|
|
419
|
+
visibleWhen,
|
|
420
|
+
dynamicTooltip,
|
|
421
|
+
dynamicLabel,
|
|
422
|
+
dynamicPlaceholder
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// src/concerns/registry.ts
|
|
426
|
+
var findConcern = (name, concerns = prebuilts) => {
|
|
427
|
+
return concerns.find((c) => c.name === name);
|
|
428
|
+
};
|
|
429
|
+
var defaultConcerns = prebuilts;
|
|
430
|
+
|
|
431
|
+
// src/concerns/registration.ts
|
|
432
|
+
var registerConcernEffectsImpl = (store, registration, concerns) => {
|
|
433
|
+
const disposeCallbacks = [];
|
|
434
|
+
const resultCache = /* @__PURE__ */ new Map();
|
|
435
|
+
const concernRefs = /* @__PURE__ */ new Map();
|
|
436
|
+
Object.keys(registration).forEach((path) => {
|
|
437
|
+
if (!store._concerns[path]) {
|
|
438
|
+
store._concerns[path] = {};
|
|
439
|
+
}
|
|
440
|
+
concernRefs.set(path, store._concerns[path]);
|
|
441
|
+
});
|
|
442
|
+
Object.entries(registration).forEach(([path, concernConfigs]) => {
|
|
443
|
+
if (!concernConfigs) return;
|
|
444
|
+
const concernsAtPath = concernRefs.get(path);
|
|
445
|
+
Object.entries(concernConfigs).forEach(([concernName, config]) => {
|
|
446
|
+
if (!config) return;
|
|
447
|
+
let concern = findConcern(concernName, concerns);
|
|
448
|
+
if (!concern) {
|
|
449
|
+
if ("evaluate" in config && typeof config.evaluate === "function") {
|
|
450
|
+
concern = {
|
|
451
|
+
name: concernName,
|
|
452
|
+
description: `Custom concern: ${concernName}`,
|
|
453
|
+
evaluate: config.evaluate
|
|
454
|
+
};
|
|
455
|
+
} else if ("boolLogic" in config && config.boolLogic) {
|
|
456
|
+
concern = {
|
|
457
|
+
name: concernName,
|
|
458
|
+
description: `Ad-hoc BoolLogic concern: ${concernName}`,
|
|
459
|
+
evaluate: (props) => evaluateBoolLogic(props.boolLogic, props.state)
|
|
460
|
+
};
|
|
461
|
+
} else {
|
|
462
|
+
console.warn(`Concern "${concernName}" not found`);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const cacheKey = `${path}.${concernName}`;
|
|
467
|
+
const dispose = effect(() => {
|
|
468
|
+
const value = dot.get__unsafe(store.state, path);
|
|
469
|
+
const evalProps = Object.assign({ state: store.state, path, value }, config);
|
|
470
|
+
const evaluateFn = "evaluate" in config && typeof config.evaluate === "function" ? config.evaluate : concern.evaluate;
|
|
471
|
+
const result = store._internal.timing.run(
|
|
472
|
+
"concerns",
|
|
473
|
+
() => evaluateFn(evalProps),
|
|
474
|
+
{ path, name: concernName }
|
|
475
|
+
);
|
|
476
|
+
const prev = resultCache.get(cacheKey);
|
|
477
|
+
if (prev !== result) {
|
|
478
|
+
resultCache.set(cacheKey, result);
|
|
479
|
+
concernsAtPath[concernName] = result;
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
disposeCallbacks.push(dispose);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
return () => {
|
|
486
|
+
disposeCallbacks.forEach((dispose) => dispose());
|
|
487
|
+
resultCache.clear();
|
|
488
|
+
concernRefs.clear();
|
|
489
|
+
Object.keys(registration).forEach((path) => {
|
|
490
|
+
const concernConfigs = registration[path];
|
|
491
|
+
if (!concernConfigs) return;
|
|
492
|
+
Object.keys(concernConfigs).forEach((concernName) => {
|
|
493
|
+
if (store._concerns[path]) {
|
|
494
|
+
Reflect.deleteProperty(store._concerns[path], concernName);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
if (store._concerns[path] && Object.keys(store._concerns[path]).length === 0) {
|
|
498
|
+
Reflect.deleteProperty(store._concerns, path);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
};
|
|
502
|
+
};
|
|
503
|
+
var registerConcernEffects = (store, registration, concerns) => store._internal.timing.run(
|
|
504
|
+
"registration",
|
|
505
|
+
() => registerConcernEffectsImpl(store, registration, concerns),
|
|
506
|
+
{ path: Object.keys(registration).join(","), name: "concerns" }
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
// src/concerns/registration.wasm-impl.ts
|
|
510
|
+
import { effect as effect2 } from "valtio-reactive";
|
|
511
|
+
var isBoolLogicConfig = (config) => "boolLogic" in config && config["boolLogic"] != null;
|
|
512
|
+
var isValueLogicConfig = (config) => "valueLogic" in config && config["valueLogic"] != null;
|
|
513
|
+
var isSchemaValidation = (concernName, config) => concernName === "validationState" && "schema" in config && !("evaluate" in config);
|
|
514
|
+
var nextValidatorId = 0;
|
|
515
|
+
var nextRegistrationId = 0;
|
|
516
|
+
var registerWasmBatch = (pipeline, boolLogics, validators, valueLogics, validatorConfigs, concernRefs, disposeCallbacks) => {
|
|
517
|
+
const registrationId = `concerns-${nextRegistrationId++}`;
|
|
518
|
+
const result = pipeline.registerConcerns({
|
|
519
|
+
registration_id: registrationId,
|
|
520
|
+
...boolLogics.length > 0 && { bool_logics: boolLogics },
|
|
521
|
+
...validators.length > 0 && { validators },
|
|
522
|
+
...valueLogics.length > 0 && { value_logics: valueLogics }
|
|
523
|
+
});
|
|
524
|
+
for (const change of result.bool_logic_changes) {
|
|
525
|
+
const lastDot = change.path.lastIndexOf(".");
|
|
526
|
+
const basePath = change.path.slice(0, lastDot);
|
|
527
|
+
const concernName = change.path.slice(lastDot + 1);
|
|
528
|
+
const concernsAtPath = concernRefs.get(basePath);
|
|
529
|
+
if (concernsAtPath && concernName) {
|
|
530
|
+
concernsAtPath[concernName] = change.value;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
for (const change of result.value_logic_changes) {
|
|
534
|
+
const lastDot = change.path.lastIndexOf(".");
|
|
535
|
+
const basePath = change.path.slice(0, lastDot);
|
|
536
|
+
const concernName = change.path.slice(lastDot + 1);
|
|
537
|
+
const concernsAtPath = concernRefs.get(basePath);
|
|
538
|
+
if (concernsAtPath && concernName) {
|
|
539
|
+
concernsAtPath[concernName] = change.value;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
disposeCallbacks.push(() => {
|
|
543
|
+
pipeline.unregisterConcerns(registrationId);
|
|
544
|
+
});
|
|
545
|
+
validatorConfigs.forEach((config, validatorId) => {
|
|
546
|
+
const parseResult = config.schema.safeParse(config.initialValue);
|
|
547
|
+
const validationResult = {
|
|
548
|
+
isError: !parseResult.success,
|
|
549
|
+
errors: parseResult.success ? [] : parseResult.error.errors.map((e) => ({
|
|
550
|
+
field: e.path.length > 0 ? e.path.join(".") : ".",
|
|
551
|
+
message: e.message
|
|
552
|
+
}))
|
|
553
|
+
};
|
|
554
|
+
config.concernsAtPath[config.concernName] = validationResult;
|
|
555
|
+
pipeline.validatorSchemas.set(validatorId, config.schema);
|
|
556
|
+
});
|
|
557
|
+
};
|
|
558
|
+
var createConcernEffect = (store, item, resultCache) => {
|
|
559
|
+
const { path, concernName, config, concern, concernsAtPath } = item;
|
|
560
|
+
const cacheKey = `${path}.${concernName}`;
|
|
561
|
+
const evaluateFn = "evaluate" in config && typeof config["evaluate"] === "function" ? config["evaluate"] : concern.evaluate;
|
|
562
|
+
return effect2(() => {
|
|
563
|
+
const value = dot.get__unsafe(store.state, path);
|
|
564
|
+
const evalProps = Object.assign({ state: store.state, path, value }, config);
|
|
565
|
+
const result = store._internal.timing.run(
|
|
566
|
+
"concerns",
|
|
567
|
+
() => evaluateFn(evalProps),
|
|
568
|
+
{ path, name: concernName }
|
|
569
|
+
);
|
|
570
|
+
const prev = resultCache.get(cacheKey);
|
|
571
|
+
if (prev !== result) {
|
|
572
|
+
resultCache.set(cacheKey, result);
|
|
573
|
+
concernsAtPath[concernName] = result;
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
};
|
|
577
|
+
var isAdHocConcern = (config) => "evaluate" in config && typeof config["evaluate"] === "function";
|
|
578
|
+
var collectValidator = (store, path, concernName, config, concernsAtPath, validators, validatorConfigs) => {
|
|
579
|
+
const validatorId = nextValidatorId++;
|
|
580
|
+
const depPaths = "scope" in config && config["scope"] ? [config["scope"]] : [path];
|
|
581
|
+
validators.push({
|
|
582
|
+
validator_id: validatorId,
|
|
583
|
+
output_path: `_concerns.${path}.${concernName}`,
|
|
584
|
+
dependency_paths: depPaths,
|
|
585
|
+
scope: ""
|
|
586
|
+
});
|
|
587
|
+
const primaryValue = dot.get__unsafe(store.state, depPaths[0]);
|
|
588
|
+
validatorConfigs.set(validatorId, {
|
|
589
|
+
schema: config["schema"],
|
|
590
|
+
initialValue: primaryValue,
|
|
591
|
+
concernName,
|
|
592
|
+
concernsAtPath
|
|
593
|
+
});
|
|
594
|
+
};
|
|
595
|
+
var classifyConcern = (store, path, concernName, config, concernsAtPath, concernMap, result) => {
|
|
596
|
+
if (isBoolLogicConfig(config)) {
|
|
597
|
+
result.boolLogics.push({
|
|
598
|
+
output_path: `${path}.${concernName}`,
|
|
599
|
+
tree_json: JSON.stringify(config.boolLogic)
|
|
600
|
+
});
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (isValueLogicConfig(config)) {
|
|
604
|
+
result.valueLogics.push({
|
|
605
|
+
output_path: `${path}.${concernName}`,
|
|
606
|
+
tree_json: JSON.stringify(config.valueLogic)
|
|
607
|
+
});
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (isSchemaValidation(concernName, config)) {
|
|
611
|
+
collectValidator(
|
|
612
|
+
store,
|
|
613
|
+
path,
|
|
614
|
+
concernName,
|
|
615
|
+
config,
|
|
616
|
+
concernsAtPath,
|
|
617
|
+
result.validators,
|
|
618
|
+
result.validatorConfigs
|
|
619
|
+
);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
const concern = concernMap.get(concernName) ?? (isAdHocConcern(config) ? {
|
|
623
|
+
name: concernName,
|
|
624
|
+
description: `Custom concern: ${concernName}`,
|
|
625
|
+
evaluate: config.evaluate
|
|
626
|
+
} : void 0);
|
|
627
|
+
if (!concern) {
|
|
628
|
+
console.warn(`Concern "${concernName}" not found`);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
result.jsEffects.push({ path, concernName, config, concern, concernsAtPath });
|
|
632
|
+
};
|
|
633
|
+
var collectRegistrations = (store, registrationEntries, concernRefs, concernMap) => {
|
|
634
|
+
const result = {
|
|
635
|
+
boolLogics: [],
|
|
636
|
+
validators: [],
|
|
637
|
+
valueLogics: [],
|
|
638
|
+
validatorConfigs: /* @__PURE__ */ new Map(),
|
|
639
|
+
jsEffects: []
|
|
640
|
+
};
|
|
641
|
+
for (const [path, concernConfigs] of registrationEntries) {
|
|
642
|
+
if (!concernConfigs) continue;
|
|
643
|
+
const concernsAtPath = concernRefs.get(path);
|
|
644
|
+
for (const [concernName, config] of Object.entries(concernConfigs)) {
|
|
645
|
+
if (!config) continue;
|
|
646
|
+
classifyConcern(
|
|
647
|
+
store,
|
|
648
|
+
path,
|
|
649
|
+
concernName,
|
|
650
|
+
config,
|
|
651
|
+
concernsAtPath,
|
|
652
|
+
concernMap,
|
|
653
|
+
result
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return result;
|
|
658
|
+
};
|
|
659
|
+
var cleanupConcerns = (store, registrationEntries) => {
|
|
660
|
+
for (const [path, concernConfigs] of registrationEntries) {
|
|
661
|
+
if (!concernConfigs) continue;
|
|
662
|
+
const concernsObj = store._concerns[path];
|
|
663
|
+
if (!concernsObj) continue;
|
|
664
|
+
for (const concernName of Object.keys(concernConfigs)) {
|
|
665
|
+
Reflect.deleteProperty(concernsObj, concernName);
|
|
666
|
+
}
|
|
667
|
+
if (Object.keys(concernsObj).length === 0) {
|
|
668
|
+
Reflect.deleteProperty(store._concerns, path);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
var registerConcernEffectsImpl2 = (store, registration, concerns) => {
|
|
673
|
+
const disposeCallbacks = [];
|
|
674
|
+
const resultCache = /* @__PURE__ */ new Map();
|
|
675
|
+
const concernRefs = /* @__PURE__ */ new Map();
|
|
676
|
+
const concernMap = /* @__PURE__ */ new Map();
|
|
677
|
+
for (const c of concerns) {
|
|
678
|
+
concernMap.set(c.name, c);
|
|
679
|
+
}
|
|
680
|
+
const registrationEntries = Object.entries(registration);
|
|
681
|
+
for (const [path] of registrationEntries) {
|
|
682
|
+
if (!store._concerns[path]) {
|
|
683
|
+
store._concerns[path] = {};
|
|
684
|
+
}
|
|
685
|
+
concernRefs.set(path, store._concerns[path]);
|
|
686
|
+
}
|
|
687
|
+
const { boolLogics, validators, valueLogics, validatorConfigs, jsEffects } = collectRegistrations(store, registrationEntries, concernRefs, concernMap);
|
|
688
|
+
if (boolLogics.length > 0 || validators.length > 0 || valueLogics.length > 0) {
|
|
689
|
+
registerWasmBatch(
|
|
690
|
+
store._internal.pipeline,
|
|
691
|
+
boolLogics,
|
|
692
|
+
validators,
|
|
693
|
+
valueLogics,
|
|
694
|
+
validatorConfigs,
|
|
695
|
+
concernRefs,
|
|
696
|
+
disposeCallbacks
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
for (const item of jsEffects) {
|
|
700
|
+
disposeCallbacks.push(createConcernEffect(store, item, resultCache));
|
|
701
|
+
}
|
|
702
|
+
return () => {
|
|
703
|
+
for (const dispose of disposeCallbacks) dispose();
|
|
704
|
+
resultCache.clear();
|
|
705
|
+
concernRefs.clear();
|
|
706
|
+
cleanupConcerns(store, registrationEntries);
|
|
707
|
+
};
|
|
708
|
+
};
|
|
709
|
+
var registerConcernEffects2 = (store, registration, concerns) => store._internal.timing.run(
|
|
710
|
+
"registration",
|
|
711
|
+
() => registerConcernEffectsImpl2(store, registration, concerns),
|
|
712
|
+
{ path: Object.keys(registration).join(","), name: "concerns" }
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
// src/core/context.ts
|
|
716
|
+
import { createContext, useContext } from "react";
|
|
717
|
+
var StoreContext = createContext(null);
|
|
718
|
+
StoreContext.displayName = "StoreContext";
|
|
719
|
+
var useStoreContext = () => {
|
|
720
|
+
const store = useContext(StoreContext);
|
|
721
|
+
if (!store) {
|
|
722
|
+
throw new Error(
|
|
723
|
+
"useStoreContext must be used within a Store Provider. Make sure your component is wrapped in <store.Provider>."
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
return store;
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
// src/pipeline/process-changes.ts
|
|
730
|
+
import { snapshot } from "valtio";
|
|
731
|
+
|
|
732
|
+
// src/pipeline/apply-batch.ts
|
|
733
|
+
var applyBatch = (changes, state) => {
|
|
734
|
+
for (const [path, value] of changes) {
|
|
735
|
+
const pathStr = path;
|
|
736
|
+
const current = dot.get__unsafe(state, pathStr);
|
|
737
|
+
if (current !== value) {
|
|
738
|
+
dot.set__unsafe(state, pathStr, value);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
// src/pipeline/queue.ts
|
|
744
|
+
var queueChange = (props) => {
|
|
745
|
+
props.queue.push([props.path, props.value, props.meta]);
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
// src/pipeline/processors/aggregation-writes.ts
|
|
749
|
+
var distributeToSources = (aggregation, targetPath, changePath, value, meta, queue) => {
|
|
750
|
+
const isExactMatch = changePath === targetPath;
|
|
751
|
+
for (const sourcePath of aggregation.sourcePaths) {
|
|
752
|
+
const finalPath = isExactMatch ? sourcePath : `${sourcePath}.${changePath.substring(targetPath.length + 1)}`;
|
|
753
|
+
queueChange({
|
|
754
|
+
queue,
|
|
755
|
+
path: finalPath,
|
|
756
|
+
value,
|
|
757
|
+
meta: { ...meta, isAggregationChange: true }
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
var processAggregationWrites = (changes, store) => {
|
|
762
|
+
const { aggregations } = store._internal.registrations;
|
|
763
|
+
const { queue } = store._internal.processing;
|
|
764
|
+
for (let i = changes.length - 1; i >= 0; i--) {
|
|
765
|
+
const [path, value, meta = {}] = changes[i];
|
|
766
|
+
for (const [targetPath, items] of aggregations) {
|
|
767
|
+
const isExactMatch = path === targetPath;
|
|
768
|
+
const isChildPath = path.startsWith(targetPath + ".");
|
|
769
|
+
if (isExactMatch || isChildPath) {
|
|
770
|
+
for (const aggregation of items) {
|
|
771
|
+
distributeToSources(aggregation, targetPath, path, value, meta, queue);
|
|
772
|
+
}
|
|
773
|
+
changes.splice(i, 1);
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
// src/core/path-groups.ts
|
|
781
|
+
var makeEdgeKey = (path1, path2) => path1 < path2 ? `${path1}--${path2}` : `${path2}--${path1}`;
|
|
782
|
+
var createPathGroups = (wasmGraphType) => {
|
|
783
|
+
const groups = {
|
|
784
|
+
pathToGroup: /* @__PURE__ */ new Map(),
|
|
785
|
+
groupToPaths: /* @__PURE__ */ new Map(),
|
|
786
|
+
edges: /* @__PURE__ */ new Set(),
|
|
787
|
+
adjacency: /* @__PURE__ */ new Map(),
|
|
788
|
+
nextGroupId: 0
|
|
789
|
+
};
|
|
790
|
+
if (wasmGraphType !== void 0) {
|
|
791
|
+
groups.wasmGraphType = wasmGraphType;
|
|
792
|
+
}
|
|
793
|
+
return groups;
|
|
794
|
+
};
|
|
795
|
+
var bfsCollect = (adjacency, startPath) => {
|
|
796
|
+
const visited = /* @__PURE__ */ new Set([startPath]);
|
|
797
|
+
const queue = [startPath];
|
|
798
|
+
while (queue.length > 0) {
|
|
799
|
+
const current = queue.shift();
|
|
800
|
+
const neighbors = adjacency.get(current);
|
|
801
|
+
if (!neighbors) continue;
|
|
802
|
+
for (const neighbor of neighbors) {
|
|
803
|
+
if (!visited.has(neighbor)) {
|
|
804
|
+
visited.add(neighbor);
|
|
805
|
+
queue.push(neighbor);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return visited;
|
|
810
|
+
};
|
|
811
|
+
var removeIsolatedPath = (groups, path) => {
|
|
812
|
+
const groupId = groups.pathToGroup.get(path);
|
|
813
|
+
if (groupId !== void 0) {
|
|
814
|
+
groups.groupToPaths.get(groupId)?.delete(path);
|
|
815
|
+
if (groups.groupToPaths.get(groupId)?.size === 0) {
|
|
816
|
+
groups.groupToPaths.delete(groupId);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
groups.pathToGroup.delete(path);
|
|
820
|
+
groups.adjacency.delete(path);
|
|
821
|
+
};
|
|
822
|
+
var handleComponentSplit = (groups, path1, path2) => {
|
|
823
|
+
const reachableFromPath1 = bfsCollect(groups.adjacency, path1);
|
|
824
|
+
if (reachableFromPath1.has(path2)) return;
|
|
825
|
+
const newGroupId = groups.nextGroupId++;
|
|
826
|
+
const oldGroupId = groups.pathToGroup.get(path2);
|
|
827
|
+
const newComponent = bfsCollect(groups.adjacency, path2);
|
|
828
|
+
for (const path of newComponent) {
|
|
829
|
+
groups.pathToGroup.set(path, newGroupId);
|
|
830
|
+
groups.groupToPaths.get(oldGroupId)?.delete(path);
|
|
831
|
+
}
|
|
832
|
+
groups.groupToPaths.set(newGroupId, newComponent);
|
|
833
|
+
if (groups.groupToPaths.get(oldGroupId)?.size === 0) {
|
|
834
|
+
groups.groupToPaths.delete(oldGroupId);
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
var addEdge = (groups, path1, path2) => {
|
|
838
|
+
const edgeKey = makeEdgeKey(path1, path2);
|
|
839
|
+
if (groups.edges.has(edgeKey)) return;
|
|
840
|
+
groups.edges.add(edgeKey);
|
|
841
|
+
if (!groups.adjacency.has(path1)) groups.adjacency.set(path1, /* @__PURE__ */ new Set());
|
|
842
|
+
if (!groups.adjacency.has(path2)) groups.adjacency.set(path2, /* @__PURE__ */ new Set());
|
|
843
|
+
groups.adjacency.get(path1).add(path2);
|
|
844
|
+
groups.adjacency.get(path2).add(path1);
|
|
845
|
+
const g1 = groups.pathToGroup.get(path1);
|
|
846
|
+
const g2 = groups.pathToGroup.get(path2);
|
|
847
|
+
if (g1 === void 0 && g2 === void 0) {
|
|
848
|
+
const id = groups.nextGroupId++;
|
|
849
|
+
groups.pathToGroup.set(path1, id);
|
|
850
|
+
groups.pathToGroup.set(path2, id);
|
|
851
|
+
groups.groupToPaths.set(id, /* @__PURE__ */ new Set([path1, path2]));
|
|
852
|
+
} else if (g1 !== void 0 && g2 === void 0) {
|
|
853
|
+
groups.pathToGroup.set(path2, g1);
|
|
854
|
+
groups.groupToPaths.get(g1).add(path2);
|
|
855
|
+
} else if (g1 === void 0 && g2 !== void 0) {
|
|
856
|
+
groups.pathToGroup.set(path1, g2);
|
|
857
|
+
groups.groupToPaths.get(g2).add(path1);
|
|
858
|
+
} else if (g1 !== g2) {
|
|
859
|
+
const set1 = groups.groupToPaths.get(g1);
|
|
860
|
+
const set2 = groups.groupToPaths.get(g2);
|
|
861
|
+
const [smallerId, smallerSet, largerId, largerSet] = set1.size < set2.size ? [g1, set1, g2, set2] : [g2, set2, g1, set1];
|
|
862
|
+
for (const path of smallerSet) {
|
|
863
|
+
groups.pathToGroup.set(path, largerId);
|
|
864
|
+
largerSet.add(path);
|
|
865
|
+
}
|
|
866
|
+
groups.groupToPaths.delete(smallerId);
|
|
867
|
+
} else {
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
var removeEdge = (groups, path1, path2) => {
|
|
871
|
+
const edgeKey = makeEdgeKey(path1, path2);
|
|
872
|
+
if (!groups.edges.has(edgeKey)) return;
|
|
873
|
+
groups.edges.delete(edgeKey);
|
|
874
|
+
groups.adjacency.get(path1)?.delete(path2);
|
|
875
|
+
groups.adjacency.get(path2)?.delete(path1);
|
|
876
|
+
const adj1 = groups.adjacency.get(path1);
|
|
877
|
+
const adj2 = groups.adjacency.get(path2);
|
|
878
|
+
const path1Isolated = !adj1 || adj1.size === 0;
|
|
879
|
+
const path2Isolated = !adj2 || adj2.size === 0;
|
|
880
|
+
if (path1Isolated) removeIsolatedPath(groups, path1);
|
|
881
|
+
if (path2Isolated) removeIsolatedPath(groups, path2);
|
|
882
|
+
if (!path1Isolated && !path2Isolated) {
|
|
883
|
+
handleComponentSplit(groups, path1, path2);
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
var getAllGroups = (groups) => [...groups.groupToPaths.values()].map((set2) => [...set2]);
|
|
887
|
+
var getGroupPaths = (groups, path) => {
|
|
888
|
+
const groupId = groups.pathToGroup.get(path);
|
|
889
|
+
if (groupId === void 0) return [];
|
|
890
|
+
return [...groups.groupToPaths.get(groupId)];
|
|
891
|
+
};
|
|
892
|
+
var hasPath = (groups, path) => groups.pathToGroup.has(path);
|
|
893
|
+
var hasEdge = (groups, path1, path2) => groups.edges.has(makeEdgeKey(path1, path2));
|
|
894
|
+
var getPathDegree = (groups, path) => groups.adjacency.get(path)?.size ?? 0;
|
|
895
|
+
|
|
896
|
+
// src/pipeline/normalize-changes.ts
|
|
897
|
+
var normalizeChange = (props) => {
|
|
898
|
+
const { changePath, changeValue, changeMeta, registeredPath, matchMode } = props;
|
|
899
|
+
if (matchMode === "all" && changePath === registeredPath) {
|
|
900
|
+
return { relativePath: null, value: changeValue, meta: changeMeta };
|
|
901
|
+
}
|
|
902
|
+
if (matchMode === "all" && registeredPath.startsWith(changePath + ".")) {
|
|
903
|
+
if (is.not.object(changeValue)) {
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
const nestedPath = registeredPath.slice(changePath.length + 1);
|
|
907
|
+
const extractedValue = dot.get__unsafe(changeValue, nestedPath);
|
|
908
|
+
if (extractedValue === void 0) {
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
return { relativePath: null, value: extractedValue, meta: changeMeta };
|
|
912
|
+
}
|
|
913
|
+
if (changePath.startsWith(registeredPath + ".")) {
|
|
914
|
+
const relativePath = changePath.slice(registeredPath.length + 1);
|
|
915
|
+
return { relativePath, value: changeValue, meta: changeMeta };
|
|
916
|
+
}
|
|
917
|
+
return null;
|
|
918
|
+
};
|
|
919
|
+
var normalizeChangesForGroups = (props) => {
|
|
920
|
+
const result = [];
|
|
921
|
+
const matchMode = props.matchMode ?? "all";
|
|
922
|
+
for (const change of props.changes) {
|
|
923
|
+
const [changePath, changeValue, changeMeta = {}] = change;
|
|
924
|
+
for (const group of props.pathGroups) {
|
|
925
|
+
let match = null;
|
|
926
|
+
for (const registeredPath of group) {
|
|
927
|
+
const normalized = normalizeChange({
|
|
928
|
+
changePath,
|
|
929
|
+
changeValue,
|
|
930
|
+
changeMeta,
|
|
931
|
+
registeredPath,
|
|
932
|
+
matchMode
|
|
933
|
+
});
|
|
934
|
+
if (normalized) {
|
|
935
|
+
match = { path: registeredPath, normalized };
|
|
936
|
+
break;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (match) {
|
|
940
|
+
result.push({
|
|
941
|
+
matchedPath: match.path,
|
|
942
|
+
relativePath: match.normalized.relativePath,
|
|
943
|
+
value: match.normalized.value,
|
|
944
|
+
meta: match.normalized.meta,
|
|
945
|
+
connectedPaths: group
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return result;
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
// src/pipeline/processors/flip-paths.ts
|
|
954
|
+
var processFlipPaths = (changes, store) => {
|
|
955
|
+
const { flip } = store._internal.graphs;
|
|
956
|
+
const { queue } = store._internal.processing;
|
|
957
|
+
const pathGroups = getAllGroups(flip);
|
|
958
|
+
if (pathGroups.length === 0) return;
|
|
959
|
+
const normalizedChanges = normalizeChangesForGroups({
|
|
960
|
+
changes,
|
|
961
|
+
pathGroups,
|
|
962
|
+
matchMode: "all"
|
|
963
|
+
});
|
|
964
|
+
for (const match of normalizedChanges) {
|
|
965
|
+
if (typeof match.value !== "boolean") continue;
|
|
966
|
+
const meta = { isFlipPathChange: true, ...match.meta };
|
|
967
|
+
for (const neighborPath of match.connectedPaths) {
|
|
968
|
+
if (neighborPath === match.matchedPath) continue;
|
|
969
|
+
const targetPath = match.relativePath ? `${neighborPath}.${match.relativePath}` : neighborPath;
|
|
970
|
+
queueChange({ queue, path: targetPath, value: !match.value, meta });
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
// src/utils/path-utils.ts
|
|
976
|
+
var getPathDepth = (path) => {
|
|
977
|
+
if (!path) return 0;
|
|
978
|
+
return path.split(".").length;
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
// src/pipeline/processors/listeners.ts
|
|
982
|
+
var processListener = (props) => {
|
|
983
|
+
if (props.relevantChanges.length === 0) return;
|
|
984
|
+
const scope = props.registration.scope ?? "";
|
|
985
|
+
const scopedState = scope === "" ? props.currentState : dot.get__unsafe(props.currentState, scope);
|
|
986
|
+
const result = props.registration.fn(props.relevantChanges, scopedState);
|
|
987
|
+
if (!result || result.length === 0) return;
|
|
988
|
+
for (const [path, value, changeMeta = {}] of result) {
|
|
989
|
+
const meta = { isListenerChange: true, ...changeMeta };
|
|
990
|
+
queueChange({ queue: props.queue, path, value, meta });
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
var filterAndRelativize = (changes, listenerPath) => {
|
|
994
|
+
const result = [];
|
|
995
|
+
if (listenerPath === "") {
|
|
996
|
+
for (const change of changes) {
|
|
997
|
+
if (!change[0].includes(".")) {
|
|
998
|
+
result.push([change[0], change[1], change[2] ?? {}]);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return result;
|
|
1002
|
+
}
|
|
1003
|
+
const prefix = listenerPath + ".";
|
|
1004
|
+
for (const change of changes) {
|
|
1005
|
+
const meta = change[2] ?? {};
|
|
1006
|
+
if (change[0] === listenerPath) {
|
|
1007
|
+
result.push([change[0], change[1], meta]);
|
|
1008
|
+
} else if (change[0].startsWith(prefix)) {
|
|
1009
|
+
result.push([change[0].slice(prefix.length), change[1], meta]);
|
|
1010
|
+
} else {
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return result;
|
|
1014
|
+
};
|
|
1015
|
+
var processListeners = (changes, store, currentState) => {
|
|
1016
|
+
const { listeners, sortedListenerPaths } = store._internal.graphs;
|
|
1017
|
+
const { queue } = store._internal.processing;
|
|
1018
|
+
const effectiveChanges = [];
|
|
1019
|
+
for (const change of changes) {
|
|
1020
|
+
const current = dot.get__unsafe(currentState, change[0]);
|
|
1021
|
+
if (current !== change[1]) {
|
|
1022
|
+
effectiveChanges.push(change);
|
|
1023
|
+
} else {
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (effectiveChanges.length === 0) return;
|
|
1027
|
+
const sortedChanges = effectiveChanges.sort(
|
|
1028
|
+
(a, b) => getPathDepth(b[0]) - getPathDepth(a[0])
|
|
1029
|
+
);
|
|
1030
|
+
for (const listenerPath of sortedListenerPaths) {
|
|
1031
|
+
const pathListeners = listeners.get(listenerPath);
|
|
1032
|
+
const relevantChanges = filterAndRelativize(sortedChanges, listenerPath);
|
|
1033
|
+
for (const registration of pathListeners) {
|
|
1034
|
+
processListener({
|
|
1035
|
+
// Safe: ListenerRegistration and ListenerRegistrationInternal have same runtime shape
|
|
1036
|
+
// The function signature difference (ArrayOfChanges vs AnyChange[]) is a compile-time distinction only
|
|
1037
|
+
registration,
|
|
1038
|
+
relevantChanges,
|
|
1039
|
+
currentState,
|
|
1040
|
+
queue
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
// src/pipeline/processors/sync-paths.ts
|
|
1047
|
+
var processSyncPaths = (changes, store) => {
|
|
1048
|
+
const { sync } = store._internal.graphs;
|
|
1049
|
+
const { queue } = store._internal.processing;
|
|
1050
|
+
const pathGroups = getAllGroups(sync);
|
|
1051
|
+
if (pathGroups.length === 0) return;
|
|
1052
|
+
const normalizedChanges = normalizeChangesForGroups({
|
|
1053
|
+
changes,
|
|
1054
|
+
pathGroups,
|
|
1055
|
+
matchMode: "all"
|
|
1056
|
+
});
|
|
1057
|
+
for (const match of normalizedChanges) {
|
|
1058
|
+
const meta = { isSyncPathChange: true, ...match.meta };
|
|
1059
|
+
for (const neighborPath of match.connectedPaths) {
|
|
1060
|
+
if (neighborPath === match.matchedPath) continue;
|
|
1061
|
+
const targetPath = match.relativePath ? `${neighborPath}.${match.relativePath}` : neighborPath;
|
|
1062
|
+
queueChange({ queue, path: targetPath, value: match.value, meta });
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
// src/pipeline/process-changes.ts
|
|
1068
|
+
var processChangesJS = (store, initialChanges) => {
|
|
1069
|
+
const { processing } = store._internal;
|
|
1070
|
+
const filtered = [];
|
|
1071
|
+
for (const change of initialChanges) {
|
|
1072
|
+
const current = dot.get__unsafe(store.state, change[0]);
|
|
1073
|
+
if (current !== change[1]) {
|
|
1074
|
+
filtered.push(change);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
if (filtered.length === 0) return;
|
|
1078
|
+
processing.queue = [...filtered];
|
|
1079
|
+
const currentState = snapshot(store.state);
|
|
1080
|
+
processAggregationWrites(processing.queue, store);
|
|
1081
|
+
processSyncPaths(processing.queue, store);
|
|
1082
|
+
processFlipPaths(processing.queue, store);
|
|
1083
|
+
const preListenerQueue = [];
|
|
1084
|
+
for (const change of processing.queue) {
|
|
1085
|
+
const current = dot.get__unsafe(store.state, change[0]);
|
|
1086
|
+
if (current !== change[1]) {
|
|
1087
|
+
preListenerQueue.push(change);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
if (preListenerQueue.length === 0) {
|
|
1091
|
+
processing.queue = [];
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
processing.queue = preListenerQueue;
|
|
1095
|
+
processListeners(processing.queue, store, currentState);
|
|
1096
|
+
if (store._debug) {
|
|
1097
|
+
const trackEntry = {
|
|
1098
|
+
input: initialChanges.map(([p, v, m]) => [p, v, m]),
|
|
1099
|
+
applied: processing.queue.map(([path, value]) => ({
|
|
1100
|
+
path,
|
|
1101
|
+
value
|
|
1102
|
+
})),
|
|
1103
|
+
appliedConcerns: [],
|
|
1104
|
+
timestamp: Date.now()
|
|
1105
|
+
};
|
|
1106
|
+
store._debug.calls.push(trackEntry);
|
|
1107
|
+
}
|
|
1108
|
+
applyBatch(processing.queue, store.state);
|
|
1109
|
+
};
|
|
1110
|
+
var processChangesLegacy = processChangesJS;
|
|
1111
|
+
var processChanges = processChangesLegacy;
|
|
1112
|
+
|
|
1113
|
+
// src/pipeline/process-changes.wasm-impl.ts
|
|
1114
|
+
import { snapshot as snapshot2 } from "valtio";
|
|
1115
|
+
var ORIGIN_TO_META = {
|
|
1116
|
+
sync: "isSyncPathChange",
|
|
1117
|
+
flip: "isFlipPathChange",
|
|
1118
|
+
aggregation: "isAggregationChange",
|
|
1119
|
+
computation: "isComputationChange",
|
|
1120
|
+
clear: "isClearPathChange",
|
|
1121
|
+
listener: "isListenerChange"
|
|
1122
|
+
};
|
|
1123
|
+
var tuplesToBridgeChanges = (changes) => changes.map(([path, value]) => ({
|
|
1124
|
+
path,
|
|
1125
|
+
value
|
|
1126
|
+
}));
|
|
1127
|
+
var buildUserMetaByPath = (changes) => {
|
|
1128
|
+
const map = /* @__PURE__ */ new Map();
|
|
1129
|
+
for (const change of changes) {
|
|
1130
|
+
const meta = change[2];
|
|
1131
|
+
if (meta && Object.keys(meta).length > 0) {
|
|
1132
|
+
map.set(change[0], meta);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return map;
|
|
1136
|
+
};
|
|
1137
|
+
var bridgeChangesToTuples = (changes, userMetaByPath) => changes.map((c) => {
|
|
1138
|
+
const baseMeta = userMetaByPath?.get(c.path) ?? {};
|
|
1139
|
+
const originKey = c.origin ? ORIGIN_TO_META[c.origin] : void 0;
|
|
1140
|
+
const meta = originKey ? { ...baseMeta, [originKey]: true } : baseMeta;
|
|
1141
|
+
return [c.path, c.value, meta];
|
|
1142
|
+
});
|
|
1143
|
+
var buildDispatchInput = (d, stateChanges, extra, userMetaByPath) => {
|
|
1144
|
+
const input = [];
|
|
1145
|
+
for (const id of d.input_change_ids) {
|
|
1146
|
+
const change = stateChanges[id];
|
|
1147
|
+
if (change !== void 0) {
|
|
1148
|
+
const baseMeta = userMetaByPath?.get(change.path) ?? {};
|
|
1149
|
+
const originKey = change.origin ? ORIGIN_TO_META[change.origin] : void 0;
|
|
1150
|
+
const meta = originKey ? { ...baseMeta, [originKey]: true } : baseMeta;
|
|
1151
|
+
input.push([change.path, change.value, meta]);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const extraChanges = extra.get(d.dispatch_id);
|
|
1155
|
+
if (extraChanges) {
|
|
1156
|
+
for (const c of extraChanges) {
|
|
1157
|
+
const baseMeta = userMetaByPath?.get(c.path) ?? {};
|
|
1158
|
+
const originKey = c.origin ? ORIGIN_TO_META[c.origin] : void 0;
|
|
1159
|
+
const meta = originKey ? { ...baseMeta, [originKey]: true } : baseMeta;
|
|
1160
|
+
input.push([c.path, c.value, meta]);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
return input;
|
|
1164
|
+
};
|
|
1165
|
+
var remapPath = (path, remapPrefix) => {
|
|
1166
|
+
if (!remapPrefix) return path;
|
|
1167
|
+
return path === "" ? remapPrefix : `${remapPrefix}.${path}`;
|
|
1168
|
+
};
|
|
1169
|
+
var propagateChanges = (dispatchId, producedChanges, propagationMap, extra) => {
|
|
1170
|
+
const targets = propagationMap[dispatchId];
|
|
1171
|
+
if (!targets) return;
|
|
1172
|
+
for (const t of targets) {
|
|
1173
|
+
let existing = extra.get(t.target_dispatch_id);
|
|
1174
|
+
if (!existing) {
|
|
1175
|
+
existing = [];
|
|
1176
|
+
extra.set(t.target_dispatch_id, existing);
|
|
1177
|
+
}
|
|
1178
|
+
for (const c of producedChanges) {
|
|
1179
|
+
existing.push({
|
|
1180
|
+
path: remapPath(c.path, t.remap_prefix),
|
|
1181
|
+
value: c.value,
|
|
1182
|
+
...c.origin ? { origin: c.origin } : {}
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
var executeFullExecutionPlan = (plan, stateChanges, store, userMetaByPath) => {
|
|
1188
|
+
if (!plan || plan.groups.length === 0) {
|
|
1189
|
+
return [];
|
|
1190
|
+
}
|
|
1191
|
+
const { listenerHandlers } = store._internal.graphs;
|
|
1192
|
+
const allProducedChanges = [];
|
|
1193
|
+
const extra = /* @__PURE__ */ new Map();
|
|
1194
|
+
const currentState = snapshot2(store.state);
|
|
1195
|
+
for (const group of plan.groups) {
|
|
1196
|
+
for (const d of group.dispatches) {
|
|
1197
|
+
const registration = listenerHandlers.get(d.subscriber_id);
|
|
1198
|
+
if (!registration) continue;
|
|
1199
|
+
const scope = registration.scope ?? "";
|
|
1200
|
+
const scopedState = scope === "" ? currentState : dot.get__unsafe(currentState, scope);
|
|
1201
|
+
const input = buildDispatchInput(d, stateChanges, extra, userMetaByPath);
|
|
1202
|
+
const result = registration.fn(input, scopedState);
|
|
1203
|
+
if (!result || !result.length) continue;
|
|
1204
|
+
const producedChanges = result.map(
|
|
1205
|
+
([path, value]) => ({ path, value, origin: "listener" })
|
|
1206
|
+
);
|
|
1207
|
+
allProducedChanges.push(...producedChanges);
|
|
1208
|
+
propagateChanges(
|
|
1209
|
+
d.dispatch_id,
|
|
1210
|
+
producedChanges,
|
|
1211
|
+
plan.propagation_map,
|
|
1212
|
+
extra
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return allProducedChanges;
|
|
1217
|
+
};
|
|
1218
|
+
var applyConcernChanges = (changes, concerns) => {
|
|
1219
|
+
for (const c of changes) {
|
|
1220
|
+
const lastDot = c.path.lastIndexOf(".");
|
|
1221
|
+
const basePath = c.path.slice(0, lastDot);
|
|
1222
|
+
const concernName = c.path.slice(lastDot + 1);
|
|
1223
|
+
if (!concerns[basePath]) {
|
|
1224
|
+
concerns[basePath] = {};
|
|
1225
|
+
}
|
|
1226
|
+
concerns[basePath][concernName] = c.value;
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
var runValidators = (validatorsToRun, pipeline) => {
|
|
1230
|
+
const validationResults = [];
|
|
1231
|
+
for (const validator of validatorsToRun) {
|
|
1232
|
+
const schema = pipeline.validatorSchemas.get(validator.validator_id);
|
|
1233
|
+
if (!schema) continue;
|
|
1234
|
+
const entries = validator.dependency_values instanceof Map ? Array.from(validator.dependency_values.entries()) : Object.entries(validator.dependency_values);
|
|
1235
|
+
const values = Object.fromEntries(
|
|
1236
|
+
entries.map(([k, v]) => [k, JSON.parse(v)])
|
|
1237
|
+
);
|
|
1238
|
+
const primaryValue = Object.values(values)[0];
|
|
1239
|
+
const parseResult = schema.safeParse(primaryValue);
|
|
1240
|
+
validationResults.push({
|
|
1241
|
+
path: validator.output_path,
|
|
1242
|
+
// Already has _concerns. prefix
|
|
1243
|
+
value: {
|
|
1244
|
+
isError: !parseResult.success,
|
|
1245
|
+
errors: parseResult.success ? [] : parseResult.error.errors.map((e) => ({
|
|
1246
|
+
field: e.path.length > 0 ? e.path.join(".") : ".",
|
|
1247
|
+
message: e.message
|
|
1248
|
+
}))
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
return validationResults;
|
|
1253
|
+
};
|
|
1254
|
+
var CONCERNS_PREFIX = "_concerns.";
|
|
1255
|
+
var CONCERNS_PREFIX_LEN = CONCERNS_PREFIX.length;
|
|
1256
|
+
var partitionChanges = (changes) => {
|
|
1257
|
+
const stateChanges = [];
|
|
1258
|
+
const concernChanges = [];
|
|
1259
|
+
for (const change of changes) {
|
|
1260
|
+
if (change.path.startsWith(CONCERNS_PREFIX)) {
|
|
1261
|
+
concernChanges.push({
|
|
1262
|
+
path: change.path.slice(CONCERNS_PREFIX_LEN),
|
|
1263
|
+
value: change.value,
|
|
1264
|
+
...change.origin ? { origin: change.origin } : {}
|
|
1265
|
+
});
|
|
1266
|
+
} else {
|
|
1267
|
+
stateChanges.push(change);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return { stateChanges, concernChanges };
|
|
1271
|
+
};
|
|
1272
|
+
var pushDebugChanges = (target, changes) => {
|
|
1273
|
+
for (const c of changes) {
|
|
1274
|
+
target.push({ path: c.path, value: c.value });
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
var processChangesWasm = (store, initialChanges) => {
|
|
1278
|
+
const pipeline = store._internal.pipeline;
|
|
1279
|
+
const userMetaByPath = buildUserMetaByPath(initialChanges);
|
|
1280
|
+
const bridgeChanges = tuplesToBridgeChanges(initialChanges);
|
|
1281
|
+
const trackEntry = store._debug ? {
|
|
1282
|
+
input: initialChanges.map(([p, v, m]) => [p, v, m]),
|
|
1283
|
+
applied: [],
|
|
1284
|
+
appliedConcerns: [],
|
|
1285
|
+
timestamp: Date.now()
|
|
1286
|
+
} : null;
|
|
1287
|
+
const { state_changes, execution_plan, validators_to_run, has_work } = pipeline.processChanges(bridgeChanges);
|
|
1288
|
+
if (!has_work) {
|
|
1289
|
+
if (trackEntry) store._debug.calls.push(trackEntry);
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const early = partitionChanges(state_changes);
|
|
1293
|
+
if (early.stateChanges.length > 0) {
|
|
1294
|
+
applyBatch(
|
|
1295
|
+
bridgeChangesToTuples(early.stateChanges, userMetaByPath),
|
|
1296
|
+
store.state
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
const produced = executeFullExecutionPlan(
|
|
1300
|
+
execution_plan,
|
|
1301
|
+
state_changes,
|
|
1302
|
+
store,
|
|
1303
|
+
userMetaByPath
|
|
1304
|
+
);
|
|
1305
|
+
if (early.concernChanges.length > 0) {
|
|
1306
|
+
applyConcernChanges(early.concernChanges, store._concerns);
|
|
1307
|
+
}
|
|
1308
|
+
const validationResults = runValidators(validators_to_run, pipeline);
|
|
1309
|
+
const jsChanges = produced.concat(validationResults);
|
|
1310
|
+
const final = pipeline.pipelineFinalize(jsChanges);
|
|
1311
|
+
const late = partitionChanges(final.state_changes);
|
|
1312
|
+
if (late.stateChanges.length > 0) {
|
|
1313
|
+
applyBatch(
|
|
1314
|
+
bridgeChangesToTuples(late.stateChanges, userMetaByPath),
|
|
1315
|
+
store.state
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
if (late.concernChanges.length > 0) {
|
|
1319
|
+
applyConcernChanges(late.concernChanges, store._concerns);
|
|
1320
|
+
}
|
|
1321
|
+
if (trackEntry) {
|
|
1322
|
+
pushDebugChanges(trackEntry.applied, early.stateChanges);
|
|
1323
|
+
pushDebugChanges(trackEntry.applied, late.stateChanges);
|
|
1324
|
+
pushDebugChanges(trackEntry.appliedConcerns, early.concernChanges);
|
|
1325
|
+
pushDebugChanges(trackEntry.appliedConcerns, late.concernChanges);
|
|
1326
|
+
store._debug.calls.push(trackEntry);
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
|
|
1330
|
+
// src/sideEffects/prebuilts/aggregation.ts
|
|
1331
|
+
import { effect as effect3 } from "valtio-reactive";
|
|
1332
|
+
var registerAggregations = (store, id, aggregationPairs) => {
|
|
1333
|
+
const { aggregations } = store._internal.registrations;
|
|
1334
|
+
const disposeCallbacks = [];
|
|
1335
|
+
const targets = /* @__PURE__ */ new Set();
|
|
1336
|
+
const sources = /* @__PURE__ */ new Set();
|
|
1337
|
+
for (const [target, source] of aggregationPairs) {
|
|
1338
|
+
targets.add(target);
|
|
1339
|
+
sources.add(source);
|
|
1340
|
+
}
|
|
1341
|
+
for (const target of targets) {
|
|
1342
|
+
if (sources.has(target)) {
|
|
1343
|
+
throw new Error(
|
|
1344
|
+
`[apex-state] Circular aggregation: "${target}" cannot be both target and source`
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
1349
|
+
for (const [target, source] of aggregationPairs) {
|
|
1350
|
+
const existing = byTarget.get(target) ?? [];
|
|
1351
|
+
existing.push(source);
|
|
1352
|
+
byTarget.set(target, existing);
|
|
1353
|
+
}
|
|
1354
|
+
const createdAggregations = [];
|
|
1355
|
+
for (const [targetPath, sourcePaths] of byTarget) {
|
|
1356
|
+
const aggregation = {
|
|
1357
|
+
targetPath,
|
|
1358
|
+
sourcePaths,
|
|
1359
|
+
id: `${id}:${targetPath}`
|
|
1360
|
+
// Optional: for debugging
|
|
1361
|
+
};
|
|
1362
|
+
const existing = aggregations.get(targetPath) ?? [];
|
|
1363
|
+
existing.push(aggregation);
|
|
1364
|
+
aggregations.set(targetPath, existing);
|
|
1365
|
+
createdAggregations.push(aggregation);
|
|
1366
|
+
const dispose = effect3(() => {
|
|
1367
|
+
if (sourcePaths.length === 0) {
|
|
1368
|
+
dot.set__unsafe(store.state, targetPath, null);
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
const allEqual = dot.same(store.state, ...sourcePaths);
|
|
1372
|
+
const result = allEqual ? dot.get__unsafe(store.state, sourcePaths[0]) : void 0;
|
|
1373
|
+
dot.set__unsafe(store.state, targetPath, result);
|
|
1374
|
+
});
|
|
1375
|
+
disposeCallbacks.push(dispose);
|
|
1376
|
+
}
|
|
1377
|
+
return () => {
|
|
1378
|
+
disposeCallbacks.forEach((dispose) => dispose());
|
|
1379
|
+
for (const aggregation of createdAggregations) {
|
|
1380
|
+
const items = aggregations.get(aggregation.targetPath) ?? [];
|
|
1381
|
+
const filtered = items.filter((item) => item !== aggregation);
|
|
1382
|
+
if (filtered.length === 0) {
|
|
1383
|
+
aggregations.delete(aggregation.targetPath);
|
|
1384
|
+
} else {
|
|
1385
|
+
aggregations.set(aggregation.targetPath, filtered);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
// src/sideEffects/prebuilts/flip.ts
|
|
1392
|
+
var registerFlipPair = (store, path1, path2) => {
|
|
1393
|
+
const { flip } = store._internal.graphs;
|
|
1394
|
+
addEdge(flip, path1, path2);
|
|
1395
|
+
return () => {
|
|
1396
|
+
if (hasEdge(flip, path1, path2)) {
|
|
1397
|
+
removeEdge(flip, path1, path2);
|
|
1398
|
+
}
|
|
1399
|
+
if (hasPath(flip, path1) && getPathDegree(flip, path1) === 0) {
|
|
1400
|
+
removeEdge(flip, path1, path1);
|
|
1401
|
+
}
|
|
1402
|
+
if (hasPath(flip, path2) && getPathDegree(flip, path2) === 0) {
|
|
1403
|
+
removeEdge(flip, path2, path2);
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
};
|
|
1407
|
+
|
|
1408
|
+
// src/sideEffects/prebuilts/listeners.ts
|
|
1409
|
+
var nextSubscriberId = 0;
|
|
1410
|
+
var updateSortedListenerPaths = (graphs) => {
|
|
1411
|
+
graphs.sortedListenerPaths = Array.from(graphs.listeners.keys()).sort(
|
|
1412
|
+
(a, b) => getPathDepth(b) - getPathDepth(a)
|
|
1413
|
+
);
|
|
1414
|
+
};
|
|
1415
|
+
var validateScopeAndPath = (path, scope) => {
|
|
1416
|
+
if (path === null || scope === null) return;
|
|
1417
|
+
if (path === scope) return;
|
|
1418
|
+
if (!path.startsWith(scope + ".")) {
|
|
1419
|
+
throw new Error(
|
|
1420
|
+
`Invalid listener: scope '${scope}' must be a parent/ancestor of path '${path}', or one must be null`
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
var registerListenerLegacy = (store, registration) => {
|
|
1425
|
+
const { graphs } = store._internal;
|
|
1426
|
+
const { listeners, listenerHandlers } = graphs;
|
|
1427
|
+
validateScopeAndPath(registration.path, registration.scope);
|
|
1428
|
+
const subscriberId = nextSubscriberId++;
|
|
1429
|
+
const mapKey = registration.path ?? "";
|
|
1430
|
+
const existing = listeners.get(mapKey) ?? [];
|
|
1431
|
+
const originalFn = registration.fn;
|
|
1432
|
+
registration.fn = (changes, state) => store._internal.timing.run("listeners", () => originalFn(changes, state), {
|
|
1433
|
+
path: mapKey,
|
|
1434
|
+
name: "listener"
|
|
1435
|
+
});
|
|
1436
|
+
listeners.set(mapKey, [...existing, registration]);
|
|
1437
|
+
listenerHandlers.set(subscriberId, {
|
|
1438
|
+
scope: registration.scope,
|
|
1439
|
+
fn: registration.fn
|
|
1440
|
+
});
|
|
1441
|
+
updateSortedListenerPaths(graphs);
|
|
1442
|
+
return () => {
|
|
1443
|
+
const list = listeners.get(mapKey);
|
|
1444
|
+
if (list) {
|
|
1445
|
+
const filtered = list.filter((l) => l !== registration);
|
|
1446
|
+
if (filtered.length > 0) {
|
|
1447
|
+
listeners.set(mapKey, filtered);
|
|
1448
|
+
} else {
|
|
1449
|
+
listeners.delete(mapKey);
|
|
1450
|
+
}
|
|
1451
|
+
updateSortedListenerPaths(graphs);
|
|
1452
|
+
}
|
|
1453
|
+
listenerHandlers.delete(subscriberId);
|
|
1454
|
+
};
|
|
1455
|
+
};
|
|
1456
|
+
|
|
1457
|
+
// src/sideEffects/prebuilts/sync.ts
|
|
1458
|
+
var collectGroupSyncChanges = (store, component) => {
|
|
1459
|
+
const valueCounts = /* @__PURE__ */ new Map();
|
|
1460
|
+
for (const path of component) {
|
|
1461
|
+
const value = dot.get__unsafe(store.state, path);
|
|
1462
|
+
if (is.not.nil(value)) {
|
|
1463
|
+
const count = valueCounts.get(value) ?? 0;
|
|
1464
|
+
valueCounts.set(value, count + 1);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
let mostCommonValue = void 0;
|
|
1468
|
+
let maxCount = 0;
|
|
1469
|
+
for (const [value, count] of valueCounts) {
|
|
1470
|
+
if (count > maxCount) {
|
|
1471
|
+
maxCount = count;
|
|
1472
|
+
mostCommonValue = value;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
const changes = [];
|
|
1476
|
+
if (is.not.undefined(mostCommonValue)) {
|
|
1477
|
+
for (const path of component) {
|
|
1478
|
+
const currentValue = dot.get__unsafe(store.state, path);
|
|
1479
|
+
if (currentValue !== mostCommonValue) {
|
|
1480
|
+
changes.push([
|
|
1481
|
+
path,
|
|
1482
|
+
mostCommonValue,
|
|
1483
|
+
{ isSyncPathChange: true }
|
|
1484
|
+
]);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
return changes;
|
|
1489
|
+
};
|
|
1490
|
+
var makeSyncEdgeCleanup = (sync, path1, path2) => () => {
|
|
1491
|
+
if (hasEdge(sync, path1, path2)) {
|
|
1492
|
+
removeEdge(sync, path1, path2);
|
|
1493
|
+
}
|
|
1494
|
+
if (hasPath(sync, path1) && getPathDegree(sync, path1) === 0) {
|
|
1495
|
+
removeEdge(sync, path1, path1);
|
|
1496
|
+
}
|
|
1497
|
+
if (hasPath(sync, path2) && getPathDegree(sync, path2) === 0) {
|
|
1498
|
+
removeEdge(sync, path2, path2);
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
var registerSyncPairsBatch = (store, pairs) => {
|
|
1502
|
+
const { sync } = store._internal.graphs;
|
|
1503
|
+
const edgeCleanups = [];
|
|
1504
|
+
for (const [path1, path2] of pairs) {
|
|
1505
|
+
addEdge(sync, path1, path2);
|
|
1506
|
+
edgeCleanups.push(makeSyncEdgeCleanup(sync, path1, path2));
|
|
1507
|
+
}
|
|
1508
|
+
const processedGroups = /* @__PURE__ */ new Set();
|
|
1509
|
+
const allChanges = [];
|
|
1510
|
+
for (const [path1] of pairs) {
|
|
1511
|
+
const groupId = sync.pathToGroup.get(path1);
|
|
1512
|
+
if (groupId === void 0 || processedGroups.has(groupId)) continue;
|
|
1513
|
+
processedGroups.add(groupId);
|
|
1514
|
+
const component = getGroupPaths(sync, path1);
|
|
1515
|
+
const changes = collectGroupSyncChanges(store, component);
|
|
1516
|
+
allChanges.push(...changes);
|
|
1517
|
+
}
|
|
1518
|
+
if (allChanges.length > 0) {
|
|
1519
|
+
processChanges(store, allChanges);
|
|
1520
|
+
}
|
|
1521
|
+
return () => edgeCleanups.forEach((fn) => fn());
|
|
1522
|
+
};
|
|
1523
|
+
|
|
1524
|
+
// src/sideEffects/registration.ts
|
|
1525
|
+
var registerSideEffectsImpl = (store, id, effects) => {
|
|
1526
|
+
const cleanups = [];
|
|
1527
|
+
if (effects.syncPaths) {
|
|
1528
|
+
const cleanup = registerSyncPairsBatch(store, effects.syncPaths);
|
|
1529
|
+
cleanups.push(cleanup);
|
|
1530
|
+
}
|
|
1531
|
+
if (effects.flipPaths) {
|
|
1532
|
+
for (const [path1, path2] of effects.flipPaths) {
|
|
1533
|
+
const cleanup = registerFlipPair(store, path1, path2);
|
|
1534
|
+
cleanups.push(cleanup);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
if (effects.aggregations) {
|
|
1538
|
+
const cleanup = registerAggregations(store, id, effects.aggregations);
|
|
1539
|
+
cleanups.push(cleanup);
|
|
1540
|
+
}
|
|
1541
|
+
if (effects.listeners) {
|
|
1542
|
+
for (const listener of effects.listeners) {
|
|
1543
|
+
const cleanup = registerListenerLegacy(store, listener);
|
|
1544
|
+
cleanups.push(cleanup);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
const combinedCleanup = () => cleanups.forEach((fn) => fn());
|
|
1548
|
+
store._internal.registrations.sideEffectCleanups.set(id, combinedCleanup);
|
|
1549
|
+
return () => {
|
|
1550
|
+
combinedCleanup();
|
|
1551
|
+
store._internal.registrations.sideEffectCleanups.delete(id);
|
|
1552
|
+
};
|
|
1553
|
+
};
|
|
1554
|
+
var registerSideEffects = (store, id, effects) => store._internal.timing.run(
|
|
1555
|
+
"registration",
|
|
1556
|
+
() => registerSideEffectsImpl(store, id, effects),
|
|
1557
|
+
{ path: id, name: "sideEffects" }
|
|
1558
|
+
);
|
|
1559
|
+
|
|
1560
|
+
// src/sideEffects/registration.wasm-impl.ts
|
|
1561
|
+
var nextSubscriberId2 = 0;
|
|
1562
|
+
var registerSideEffects2 = (store, id, effects) => {
|
|
1563
|
+
const syncPairs = effects.syncPaths ?? [];
|
|
1564
|
+
const flipPairs = effects.flipPaths ?? [];
|
|
1565
|
+
const aggregationPairs = (effects.aggregations ?? []).map(
|
|
1566
|
+
([target, source, condition]) => condition ? [target, source, JSON.stringify(condition)] : [target, source]
|
|
1567
|
+
);
|
|
1568
|
+
const computationPairs = (effects.computations ?? []).map(
|
|
1569
|
+
([op, target, source, condition]) => condition ? [
|
|
1570
|
+
op,
|
|
1571
|
+
target,
|
|
1572
|
+
source,
|
|
1573
|
+
JSON.stringify(condition)
|
|
1574
|
+
] : [op, target, source]
|
|
1575
|
+
);
|
|
1576
|
+
const clearPaths = effects.clearPaths?.map(([triggers, targets, opts]) => ({
|
|
1577
|
+
triggers,
|
|
1578
|
+
targets: opts?.expandMatch ? targets.map((t) => t.replace(/\[\*\]/g, "[**]")) : targets
|
|
1579
|
+
}));
|
|
1580
|
+
const listeners = effects.listeners?.map((listener) => {
|
|
1581
|
+
const { listenerHandlers } = store._internal.graphs;
|
|
1582
|
+
const subscriberId = nextSubscriberId2++;
|
|
1583
|
+
const originalFn = listener.fn;
|
|
1584
|
+
const mapKey = listener.path ?? "";
|
|
1585
|
+
const wrappedFn = (changes, state) => store._internal.timing.run(
|
|
1586
|
+
"listeners",
|
|
1587
|
+
() => originalFn(changes, state),
|
|
1588
|
+
{
|
|
1589
|
+
path: mapKey,
|
|
1590
|
+
name: "listener"
|
|
1591
|
+
}
|
|
1592
|
+
);
|
|
1593
|
+
listenerHandlers.set(subscriberId, {
|
|
1594
|
+
scope: listener.scope,
|
|
1595
|
+
fn: wrappedFn
|
|
1596
|
+
});
|
|
1597
|
+
return {
|
|
1598
|
+
subscriber_id: subscriberId,
|
|
1599
|
+
topic_path: listener.path ?? "",
|
|
1600
|
+
scope_path: listener.scope ?? ""
|
|
1601
|
+
};
|
|
1602
|
+
});
|
|
1603
|
+
const pipeline = store._internal.pipeline;
|
|
1604
|
+
const registrationId = `sideEffects-${id}`;
|
|
1605
|
+
const result = pipeline.registerSideEffects({
|
|
1606
|
+
registration_id: registrationId,
|
|
1607
|
+
...syncPairs.length > 0 && { sync_pairs: syncPairs },
|
|
1608
|
+
...flipPairs.length > 0 && { flip_pairs: flipPairs },
|
|
1609
|
+
...aggregationPairs.length > 0 && { aggregation_pairs: aggregationPairs },
|
|
1610
|
+
...computationPairs.length > 0 && {
|
|
1611
|
+
computation_pairs: computationPairs
|
|
1612
|
+
},
|
|
1613
|
+
...clearPaths && clearPaths.length > 0 && { clear_paths: clearPaths },
|
|
1614
|
+
...listeners && listeners.length > 0 && { listeners }
|
|
1615
|
+
});
|
|
1616
|
+
if (result.sync_changes.length > 0) {
|
|
1617
|
+
applyBatch(
|
|
1618
|
+
result.sync_changes.map((c) => [c.path, c.value, {}]),
|
|
1619
|
+
store.state
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
if (result.aggregation_changes.length > 0) {
|
|
1623
|
+
applyBatch(
|
|
1624
|
+
result.aggregation_changes.map((c) => [c.path, c.value, {}]),
|
|
1625
|
+
store.state
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
if (result.computation_changes.length > 0) {
|
|
1629
|
+
applyBatch(
|
|
1630
|
+
result.computation_changes.map((c) => [c.path, c.value, {}]),
|
|
1631
|
+
store.state
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
const cleanup = () => {
|
|
1635
|
+
pipeline.unregisterSideEffects(registrationId);
|
|
1636
|
+
effects.listeners?.forEach((_listener, index) => {
|
|
1637
|
+
if (listeners && listeners[index]) {
|
|
1638
|
+
store._internal.graphs.listenerHandlers.delete(
|
|
1639
|
+
listeners[index].subscriber_id
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
store._internal.registrations.sideEffectCleanups.delete(id);
|
|
1644
|
+
};
|
|
1645
|
+
store._internal.registrations.sideEffectCleanups.set(id, cleanup);
|
|
1646
|
+
return cleanup;
|
|
1647
|
+
};
|
|
1648
|
+
|
|
1649
|
+
// src/store/provider.tsx
|
|
1650
|
+
import { useLayoutEffect, useRef } from "react";
|
|
1651
|
+
import { proxy, ref } from "valtio";
|
|
1652
|
+
|
|
1653
|
+
// src/core/defaults.ts
|
|
1654
|
+
var DEFAULT_STORE_CONFIG = {
|
|
1655
|
+
errorStorePath: "_errors",
|
|
1656
|
+
maxIterations: 100,
|
|
1657
|
+
debug: {
|
|
1658
|
+
timing: false,
|
|
1659
|
+
timingThreshold: 5,
|
|
1660
|
+
track: false
|
|
1661
|
+
},
|
|
1662
|
+
useLegacyImplementation: false
|
|
1663
|
+
};
|
|
1664
|
+
|
|
1665
|
+
// src/utils/deep-clone.ts
|
|
1666
|
+
import _deepClone from "@jsbits/deep-clone";
|
|
1667
|
+
var assertNoCycles = (value) => {
|
|
1668
|
+
if (value === null || typeof value !== "object") return;
|
|
1669
|
+
const ancestors = /* @__PURE__ */ new WeakSet();
|
|
1670
|
+
const walk = (obj, path) => {
|
|
1671
|
+
if (ancestors.has(obj)) {
|
|
1672
|
+
throw new Error(
|
|
1673
|
+
`[deepClone] Circular reference detected at "${path}". State objects must not contain self-references.`
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
ancestors.add(obj);
|
|
1677
|
+
const descriptors = Object.getOwnPropertyDescriptors(obj);
|
|
1678
|
+
for (const [key, desc] of Object.entries(descriptors)) {
|
|
1679
|
+
if (desc.get) continue;
|
|
1680
|
+
if (desc.value !== null && typeof desc.value === "object") {
|
|
1681
|
+
walk(desc.value, path ? `${path}.${key}` : key);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
ancestors.delete(obj);
|
|
1685
|
+
};
|
|
1686
|
+
walk(value, "");
|
|
1687
|
+
};
|
|
1688
|
+
var deepClone = (value) => {
|
|
1689
|
+
assertNoCycles(value);
|
|
1690
|
+
return _deepClone(value, true);
|
|
1691
|
+
};
|
|
1692
|
+
|
|
1693
|
+
// src/utils/deep-merge.ts
|
|
1694
|
+
var deepMerge = (target, source) => {
|
|
1695
|
+
if (!source) return target;
|
|
1696
|
+
const result = { ...target };
|
|
1697
|
+
for (const key in source) {
|
|
1698
|
+
if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
|
|
1699
|
+
const sourceValue = source[key];
|
|
1700
|
+
const targetValue = target[key];
|
|
1701
|
+
if (is.undefined(sourceValue)) {
|
|
1702
|
+
continue;
|
|
1703
|
+
}
|
|
1704
|
+
if (is.object(sourceValue) && is.object(targetValue)) {
|
|
1705
|
+
result[key] = deepMerge(
|
|
1706
|
+
targetValue,
|
|
1707
|
+
sourceValue
|
|
1708
|
+
);
|
|
1709
|
+
} else {
|
|
1710
|
+
result[key] = sourceValue;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
return result;
|
|
1714
|
+
};
|
|
1715
|
+
|
|
1716
|
+
// src/utils/derive-values.ts
|
|
1717
|
+
import { computed } from "valtio-reactive";
|
|
1718
|
+
var extractGetters = (obj, prefix = "") => {
|
|
1719
|
+
const base = {};
|
|
1720
|
+
const computed2 = {};
|
|
1721
|
+
const descriptors = Object.getOwnPropertyDescriptors(obj);
|
|
1722
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
1723
|
+
if (!descriptor.enumerable || is.symbol(key)) continue;
|
|
1724
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
1725
|
+
if (descriptor.get) {
|
|
1726
|
+
computed2[fullPath] = { get: descriptor.get, parentPath: prefix };
|
|
1727
|
+
} else if (is.object(descriptor.value)) {
|
|
1728
|
+
const nested = extractGetters(descriptor.value, fullPath);
|
|
1729
|
+
base[key] = nested.base;
|
|
1730
|
+
Object.assign(computed2, nested.computed);
|
|
1731
|
+
} else {
|
|
1732
|
+
base[key] = descriptor.value;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
return { base, computed: computed2 };
|
|
1736
|
+
};
|
|
1737
|
+
var prepareInitialState = (rawState) => {
|
|
1738
|
+
const { base, computed: getterMap } = extractGetters(rawState);
|
|
1739
|
+
return { initialState: base, getterMap };
|
|
1740
|
+
};
|
|
1741
|
+
var attachComputedGetters = (stateProxy, getterMap) => {
|
|
1742
|
+
if (Object.keys(getterMap).length === 0) return;
|
|
1743
|
+
for (const [dotPath, { get: getterFn, parentPath }] of Object.entries(
|
|
1744
|
+
getterMap
|
|
1745
|
+
)) {
|
|
1746
|
+
const parts = dotPath.split(".");
|
|
1747
|
+
const propName = parts.pop();
|
|
1748
|
+
const parentObj = parts.length > 0 ? dot.get__unsafe(stateProxy, parts.join(".")) : stateProxy;
|
|
1749
|
+
Object.defineProperty(parentObj, propName, {
|
|
1750
|
+
get() {
|
|
1751
|
+
const ctx = parentPath ? dot.get__unsafe(stateProxy, parentPath) : stateProxy;
|
|
1752
|
+
return getterFn.call(ctx);
|
|
1753
|
+
},
|
|
1754
|
+
enumerable: true,
|
|
1755
|
+
configurable: true
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
const computedDefs = {};
|
|
1759
|
+
for (const [dotPath, { get: getterFn, parentPath }] of Object.entries(
|
|
1760
|
+
getterMap
|
|
1761
|
+
)) {
|
|
1762
|
+
computedDefs[dotPath] = () => {
|
|
1763
|
+
const parent = parentPath ? dot.get__unsafe(stateProxy, parentPath) : stateProxy;
|
|
1764
|
+
return getterFn.call(parent);
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
const computedProxy = computed(computedDefs);
|
|
1768
|
+
for (const dotPath of Object.keys(getterMap)) {
|
|
1769
|
+
const parts = dotPath.split(".");
|
|
1770
|
+
const propName = parts.pop();
|
|
1771
|
+
const parentObj = parts.length > 0 ? dot.get__unsafe(stateProxy, parts.join(".")) : stateProxy;
|
|
1772
|
+
Object.defineProperty(parentObj, propName, {
|
|
1773
|
+
get: () => computedProxy[dotPath],
|
|
1774
|
+
enumerable: true,
|
|
1775
|
+
configurable: true
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
|
|
1780
|
+
// src/utils/timing.ts
|
|
1781
|
+
var defaultOnSlowOperation = (event) => {
|
|
1782
|
+
console.warn(
|
|
1783
|
+
`[apex-state] Slow ${event.type}: ${event.path}/${event.name} took ${event.duration.toFixed(2)}ms (threshold: ${event.threshold}ms)`
|
|
1784
|
+
);
|
|
1785
|
+
};
|
|
1786
|
+
var defaultOnTimingSummary = (summary) => {
|
|
1787
|
+
if (summary.slowOperations.length > 0 || summary.totalDuration > 16) {
|
|
1788
|
+
console.warn(
|
|
1789
|
+
`[apex-state] ${summary.type}: ${summary.operationCount} ops in ${summary.totalDuration.toFixed(2)}ms (${summary.slowOperations.length} slow)`
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
var createTypeState = () => ({
|
|
1794
|
+
totalDuration: 0,
|
|
1795
|
+
operationCount: 0,
|
|
1796
|
+
slowOperations: [],
|
|
1797
|
+
warnedOperations: /* @__PURE__ */ new Set()
|
|
1798
|
+
});
|
|
1799
|
+
var createTiming = (options) => {
|
|
1800
|
+
const {
|
|
1801
|
+
timing,
|
|
1802
|
+
timingThreshold,
|
|
1803
|
+
onSlowOperation = defaultOnSlowOperation,
|
|
1804
|
+
onSummary = defaultOnTimingSummary
|
|
1805
|
+
} = options;
|
|
1806
|
+
if (!timing) {
|
|
1807
|
+
return {
|
|
1808
|
+
run: (_type, fn) => fn(),
|
|
1809
|
+
reportBatch: () => {
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
const state = {
|
|
1814
|
+
concerns: createTypeState(),
|
|
1815
|
+
listeners: createTypeState(),
|
|
1816
|
+
registration: createTypeState()
|
|
1817
|
+
};
|
|
1818
|
+
return {
|
|
1819
|
+
run: (type, fn, meta) => {
|
|
1820
|
+
const start = performance.now();
|
|
1821
|
+
const result = fn();
|
|
1822
|
+
const duration = performance.now() - start;
|
|
1823
|
+
const typeState = state[type];
|
|
1824
|
+
typeState.totalDuration += duration;
|
|
1825
|
+
typeState.operationCount++;
|
|
1826
|
+
if (duration > timingThreshold) {
|
|
1827
|
+
const event = {
|
|
1828
|
+
...meta,
|
|
1829
|
+
type,
|
|
1830
|
+
duration,
|
|
1831
|
+
threshold: timingThreshold
|
|
1832
|
+
};
|
|
1833
|
+
typeState.slowOperations.push(event);
|
|
1834
|
+
const key = `${type}:${meta.path}:${meta.name}`;
|
|
1835
|
+
if (!typeState.warnedOperations.has(key)) {
|
|
1836
|
+
typeState.warnedOperations.add(key);
|
|
1837
|
+
onSlowOperation(event);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
return result;
|
|
1841
|
+
},
|
|
1842
|
+
reportBatch: (type) => {
|
|
1843
|
+
const typeState = state[type];
|
|
1844
|
+
onSummary({
|
|
1845
|
+
type,
|
|
1846
|
+
totalDuration: typeState.totalDuration,
|
|
1847
|
+
operationCount: typeState.operationCount,
|
|
1848
|
+
slowOperations: typeState.slowOperations
|
|
1849
|
+
});
|
|
1850
|
+
typeState.totalDuration = 0;
|
|
1851
|
+
typeState.operationCount = 0;
|
|
1852
|
+
typeState.slowOperations = [];
|
|
1853
|
+
}
|
|
1854
|
+
};
|
|
1855
|
+
};
|
|
1856
|
+
|
|
1857
|
+
// src/wasm/lifecycle.tsx
|
|
1858
|
+
import { useEffect, useState } from "react";
|
|
1859
|
+
|
|
1860
|
+
// src/utils/json.ts
|
|
1861
|
+
var createFastJson = (placeholders = []) => {
|
|
1862
|
+
const encodeMap = /* @__PURE__ */ new Map();
|
|
1863
|
+
const decodeMap = /* @__PURE__ */ new Map();
|
|
1864
|
+
for (const p of placeholders) {
|
|
1865
|
+
encodeMap.set(p.value, p.encoded);
|
|
1866
|
+
decodeMap.set(p.encoded, p.value);
|
|
1867
|
+
}
|
|
1868
|
+
const stringify = (value) => {
|
|
1869
|
+
if (encodeMap.has(value)) return encodeMap.get(value);
|
|
1870
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
1871
|
+
return String(value);
|
|
1872
|
+
if (value === null) return "null";
|
|
1873
|
+
return JSON.stringify(value);
|
|
1874
|
+
};
|
|
1875
|
+
const parse = (json) => {
|
|
1876
|
+
if (decodeMap.has(json)) return decodeMap.get(json);
|
|
1877
|
+
const c = json.charCodeAt(0);
|
|
1878
|
+
if (c >= 48 && c <= 57 || c === 45) return Number(json);
|
|
1879
|
+
if (json === "true") return true;
|
|
1880
|
+
if (json === "false") return false;
|
|
1881
|
+
if (json === "null") return null;
|
|
1882
|
+
return JSON.parse(json);
|
|
1883
|
+
};
|
|
1884
|
+
return { stringify, parse };
|
|
1885
|
+
};
|
|
1886
|
+
|
|
1887
|
+
// src/wasm/bridge.ts
|
|
1888
|
+
var UNDEFINED_SENTINEL_JSON = '"__APEX_UNDEFINED__"';
|
|
1889
|
+
var { stringify: fastStringify, parse: fastParse } = createFastJson([
|
|
1890
|
+
{ value: void 0, encoded: UNDEFINED_SENTINEL_JSON }
|
|
1891
|
+
]);
|
|
1892
|
+
var changesToWasm = (changes) => changes.map(({ path, value }) => ({ path, value_json: fastStringify(value) }));
|
|
1893
|
+
var wasmChangesToJs = (wasmChanges) => wasmChanges.map(({ path, value_json, origin }) => ({
|
|
1894
|
+
path,
|
|
1895
|
+
value: fastParse(value_json),
|
|
1896
|
+
...origin ? { origin } : {}
|
|
1897
|
+
}));
|
|
1898
|
+
var createWasmPipeline = () => {
|
|
1899
|
+
const wasm = getWasmInstance();
|
|
1900
|
+
const id = wasm.pipeline_create();
|
|
1901
|
+
const schemas = /* @__PURE__ */ new Map();
|
|
1902
|
+
return {
|
|
1903
|
+
id,
|
|
1904
|
+
shadowInit: (state) => {
|
|
1905
|
+
wasm.shadow_init(id, state);
|
|
1906
|
+
},
|
|
1907
|
+
shadowDump: () => JSON.parse(wasm.shadow_dump(id)),
|
|
1908
|
+
processChanges: (changes) => {
|
|
1909
|
+
const result = wasm.process_changes(
|
|
1910
|
+
id,
|
|
1911
|
+
changesToWasm(changes)
|
|
1912
|
+
);
|
|
1913
|
+
const stateChanges = wasmChangesToJs(result.state_changes);
|
|
1914
|
+
return {
|
|
1915
|
+
state_changes: stateChanges,
|
|
1916
|
+
changes: stateChanges,
|
|
1917
|
+
validators_to_run: result.validators_to_run,
|
|
1918
|
+
execution_plan: result.execution_plan,
|
|
1919
|
+
has_work: result.has_work
|
|
1920
|
+
};
|
|
1921
|
+
},
|
|
1922
|
+
pipelineFinalize: (jsChanges) => {
|
|
1923
|
+
const result = wasm.pipeline_finalize(
|
|
1924
|
+
id,
|
|
1925
|
+
changesToWasm(jsChanges)
|
|
1926
|
+
);
|
|
1927
|
+
return {
|
|
1928
|
+
state_changes: wasmChangesToJs(result.state_changes)
|
|
1929
|
+
};
|
|
1930
|
+
},
|
|
1931
|
+
registerSideEffects: (reg) => {
|
|
1932
|
+
const resultJson = wasm.register_side_effects(
|
|
1933
|
+
id,
|
|
1934
|
+
JSON.stringify(reg)
|
|
1935
|
+
);
|
|
1936
|
+
return {
|
|
1937
|
+
sync_changes: wasmChangesToJs(resultJson.sync_changes),
|
|
1938
|
+
aggregation_changes: wasmChangesToJs(resultJson.aggregation_changes),
|
|
1939
|
+
computation_changes: wasmChangesToJs(resultJson.computation_changes),
|
|
1940
|
+
registered_listener_ids: resultJson.registered_listener_ids
|
|
1941
|
+
};
|
|
1942
|
+
},
|
|
1943
|
+
unregisterSideEffects: (registrationId) => {
|
|
1944
|
+
wasm.unregister_side_effects(id, registrationId);
|
|
1945
|
+
},
|
|
1946
|
+
registerConcerns: (reg) => {
|
|
1947
|
+
const resultJson = wasm.register_concerns(
|
|
1948
|
+
id,
|
|
1949
|
+
JSON.stringify(reg)
|
|
1950
|
+
);
|
|
1951
|
+
return {
|
|
1952
|
+
bool_logic_changes: wasmChangesToJs(resultJson.bool_logic_changes),
|
|
1953
|
+
registered_logic_ids: resultJson.registered_logic_ids,
|
|
1954
|
+
registered_validator_ids: resultJson.registered_validator_ids,
|
|
1955
|
+
value_logic_changes: wasmChangesToJs(resultJson.value_logic_changes),
|
|
1956
|
+
registered_value_logic_ids: resultJson.registered_value_logic_ids
|
|
1957
|
+
};
|
|
1958
|
+
},
|
|
1959
|
+
unregisterConcerns: (registrationId) => {
|
|
1960
|
+
wasm.unregister_concerns(id, registrationId);
|
|
1961
|
+
},
|
|
1962
|
+
registerBoolLogic: (outputPath, tree) => wasm.register_boollogic(id, outputPath, JSON.stringify(tree)),
|
|
1963
|
+
unregisterBoolLogic: (logicId) => {
|
|
1964
|
+
wasm.unregister_boollogic(id, logicId);
|
|
1965
|
+
},
|
|
1966
|
+
pipelineReset: () => {
|
|
1967
|
+
wasm.pipeline_reset(id);
|
|
1968
|
+
},
|
|
1969
|
+
destroy: () => {
|
|
1970
|
+
wasm.pipeline_destroy(id);
|
|
1971
|
+
schemas.clear();
|
|
1972
|
+
},
|
|
1973
|
+
validatorSchemas: schemas
|
|
1974
|
+
};
|
|
1975
|
+
};
|
|
1976
|
+
|
|
1977
|
+
// src/wasm/lifecycle.tsx
|
|
1978
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
1979
|
+
var wasmInstance = null;
|
|
1980
|
+
var loadingPromise = null;
|
|
1981
|
+
var loadWasm = async () => {
|
|
1982
|
+
if (wasmInstance) return wasmInstance;
|
|
1983
|
+
if (loadingPromise) {
|
|
1984
|
+
await loadingPromise;
|
|
1985
|
+
return wasmInstance;
|
|
1986
|
+
}
|
|
1987
|
+
loadingPromise = (async () => {
|
|
1988
|
+
const wasmModule = await import("./apex_state_wasm-WOOTVUVC.js");
|
|
1989
|
+
if (typeof wasmModule.default === "function") {
|
|
1990
|
+
await wasmModule.default();
|
|
1991
|
+
}
|
|
1992
|
+
wasmInstance = wasmModule;
|
|
1993
|
+
})();
|
|
1994
|
+
await loadingPromise;
|
|
1995
|
+
return wasmInstance;
|
|
1996
|
+
};
|
|
1997
|
+
var isWasmLoaded = () => wasmInstance !== null;
|
|
1998
|
+
var initPipeline = (internal, initialState) => {
|
|
1999
|
+
const pipeline = createWasmPipeline();
|
|
2000
|
+
pipeline.shadowInit(initialState);
|
|
2001
|
+
internal.pipeline = pipeline;
|
|
2002
|
+
};
|
|
2003
|
+
var WasmGate = ({ children }) => {
|
|
2004
|
+
const [loaded, setLoaded] = useState(isWasmLoaded);
|
|
2005
|
+
useEffect(() => {
|
|
2006
|
+
if (loaded) return;
|
|
2007
|
+
let cancelled = false;
|
|
2008
|
+
loadWasm().then(() => {
|
|
2009
|
+
if (!cancelled) setLoaded(true);
|
|
2010
|
+
}).catch((error) => {
|
|
2011
|
+
console.error("[apex-state] Failed to load WASM:", error);
|
|
2012
|
+
});
|
|
2013
|
+
return () => {
|
|
2014
|
+
cancelled = true;
|
|
2015
|
+
};
|
|
2016
|
+
}, [loaded]);
|
|
2017
|
+
if (!loaded) return null;
|
|
2018
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
2019
|
+
};
|
|
2020
|
+
WasmGate.displayName = "WasmGate";
|
|
2021
|
+
var getWasmInstance = () => {
|
|
2022
|
+
if (!wasmInstance) {
|
|
2023
|
+
throw new Error("WASM not loaded. Call loadWasm() first.");
|
|
2024
|
+
}
|
|
2025
|
+
return wasmInstance;
|
|
2026
|
+
};
|
|
2027
|
+
|
|
2028
|
+
// src/store/provider.tsx
|
|
2029
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
2030
|
+
var createInternalState = (config) => ({
|
|
2031
|
+
graphs: {
|
|
2032
|
+
sync: createPathGroups("sync"),
|
|
2033
|
+
flip: createPathGroups("flip"),
|
|
2034
|
+
listeners: /* @__PURE__ */ new Map(),
|
|
2035
|
+
sortedListenerPaths: [],
|
|
2036
|
+
listenerHandlers: /* @__PURE__ */ new Map()
|
|
2037
|
+
},
|
|
2038
|
+
registrations: {
|
|
2039
|
+
concerns: /* @__PURE__ */ new Map(),
|
|
2040
|
+
effectCleanups: /* @__PURE__ */ new Set(),
|
|
2041
|
+
sideEffectCleanups: /* @__PURE__ */ new Map(),
|
|
2042
|
+
aggregations: /* @__PURE__ */ new Map()
|
|
2043
|
+
},
|
|
2044
|
+
processing: {
|
|
2045
|
+
queue: []
|
|
2046
|
+
},
|
|
2047
|
+
timing: createTiming(config.debug),
|
|
2048
|
+
config,
|
|
2049
|
+
pipeline: null
|
|
2050
|
+
});
|
|
2051
|
+
var createProvider = (storeConfig) => {
|
|
2052
|
+
const resolvedConfig = deepMerge(DEFAULT_STORE_CONFIG, storeConfig);
|
|
2053
|
+
const buildStore = (rawInitialState) => {
|
|
2054
|
+
const prepared = prepareInitialState(deepClone(rawInitialState));
|
|
2055
|
+
const internal = createInternalState(resolvedConfig);
|
|
2056
|
+
initPipeline(internal, prepared.initialState);
|
|
2057
|
+
const debugTrack = resolvedConfig.debug.track ? {
|
|
2058
|
+
calls: [],
|
|
2059
|
+
clear: () => {
|
|
2060
|
+
debugTrack.calls.length = 0;
|
|
2061
|
+
}
|
|
2062
|
+
} : null;
|
|
2063
|
+
const stateProxy = proxy(prepared.initialState);
|
|
2064
|
+
attachComputedGetters(stateProxy, prepared.getterMap);
|
|
2065
|
+
return {
|
|
2066
|
+
// state: Application data (tracked by valtio)
|
|
2067
|
+
// User actions WRITE to this, effects READ from this
|
|
2068
|
+
state: stateProxy,
|
|
2069
|
+
// _concerns: Computed concern values (tracked by valtio)
|
|
2070
|
+
// Effects WRITE to this, UI components READ from this
|
|
2071
|
+
_concerns: proxy({}),
|
|
2072
|
+
// _internal: Graphs, registrations, processing (NOT tracked)
|
|
2073
|
+
// Wrapped in ref() to prevent tracking even if store is later wrapped in a proxy
|
|
2074
|
+
_internal: ref(internal),
|
|
2075
|
+
// _debug: Tracking data for testing/debugging (only when debug.track enabled)
|
|
2076
|
+
_debug: debugTrack ? ref(debugTrack) : null
|
|
2077
|
+
};
|
|
2078
|
+
};
|
|
2079
|
+
const StoreProvider = ({ initialState, children }) => {
|
|
2080
|
+
const storeRef = useRef(buildStore(initialState));
|
|
2081
|
+
const internal = storeRef.current._internal;
|
|
2082
|
+
useLayoutEffect(() => {
|
|
2083
|
+
if (internal._destroyTimer) {
|
|
2084
|
+
clearTimeout(internal._destroyTimer);
|
|
2085
|
+
internal._destroyTimer = void 0;
|
|
2086
|
+
}
|
|
2087
|
+
return () => {
|
|
2088
|
+
internal._destroyTimer = setTimeout(() => {
|
|
2089
|
+
internal.pipeline?.destroy();
|
|
2090
|
+
internal.pipeline = null;
|
|
2091
|
+
internal._destroyTimer = void 0;
|
|
2092
|
+
}, 1e4);
|
|
2093
|
+
};
|
|
2094
|
+
}, [internal]);
|
|
2095
|
+
return /* @__PURE__ */ jsx2(
|
|
2096
|
+
StoreContext.Provider,
|
|
2097
|
+
{
|
|
2098
|
+
value: storeRef.current,
|
|
2099
|
+
children
|
|
2100
|
+
}
|
|
2101
|
+
);
|
|
2102
|
+
};
|
|
2103
|
+
StoreProvider.displayName = "StoreProvider";
|
|
2104
|
+
const Provider = (props) => /* @__PURE__ */ jsx2(WasmGate, { children: /* @__PURE__ */ jsx2(StoreProvider, { ...props }) });
|
|
2105
|
+
Provider.displayName = "StoreProvider";
|
|
2106
|
+
return Provider;
|
|
2107
|
+
};
|
|
2108
|
+
|
|
2109
|
+
// src/store/create-store.ts
|
|
2110
|
+
var createGenericStore = (config) => {
|
|
2111
|
+
const Provider = createProvider(config);
|
|
2112
|
+
const _useFieldValue = (path) => {
|
|
2113
|
+
const store = useStoreContext();
|
|
2114
|
+
const snap = useSnapshot(store.state);
|
|
2115
|
+
const value = dot.get(snap, path);
|
|
2116
|
+
const setValue = useCallback(
|
|
2117
|
+
(newValue, meta) => {
|
|
2118
|
+
const changes = [
|
|
2119
|
+
[path, newValue, meta || {}]
|
|
2120
|
+
];
|
|
2121
|
+
const processChanges2 = store._internal.config.useLegacyImplementation ? processChangesLegacy : processChangesWasm;
|
|
2122
|
+
processChanges2(store, changes);
|
|
2123
|
+
},
|
|
2124
|
+
[store, path]
|
|
2125
|
+
);
|
|
2126
|
+
return { store, value, setValue };
|
|
2127
|
+
};
|
|
2128
|
+
const useFieldStore = (path) => {
|
|
2129
|
+
const { store, value, setValue } = _useFieldValue(path);
|
|
2130
|
+
const concernsSnap = useSnapshot(store._concerns);
|
|
2131
|
+
const allConcerns = concernsSnap[path] || {};
|
|
2132
|
+
return { value, setValue, ...allConcerns };
|
|
2133
|
+
};
|
|
2134
|
+
const useStore = (path) => {
|
|
2135
|
+
const { value, setValue } = _useFieldValue(path);
|
|
2136
|
+
return [value, setValue];
|
|
2137
|
+
};
|
|
2138
|
+
const useJitStore = () => {
|
|
2139
|
+
const store = useStoreContext();
|
|
2140
|
+
const proxyValue = useSnapshot(store.state);
|
|
2141
|
+
const setChanges = useCallback(
|
|
2142
|
+
(changes) => {
|
|
2143
|
+
const processChanges2 = store._internal.config.useLegacyImplementation ? processChangesLegacy : processChangesWasm;
|
|
2144
|
+
processChanges2(store, changes);
|
|
2145
|
+
},
|
|
2146
|
+
[store]
|
|
2147
|
+
);
|
|
2148
|
+
const getState = useCallback(() => {
|
|
2149
|
+
return snapshot3(store.state);
|
|
2150
|
+
}, [store]);
|
|
2151
|
+
return { proxyValue, setChanges, getState };
|
|
2152
|
+
};
|
|
2153
|
+
const useSideEffects = (id, effects) => {
|
|
2154
|
+
const store = useStoreContext();
|
|
2155
|
+
useLayoutEffect2(() => {
|
|
2156
|
+
const registerSideEffects3 = store._internal.config.useLegacyImplementation ? registerSideEffects : registerSideEffects2;
|
|
2157
|
+
return registerSideEffects3(store, id, effects);
|
|
2158
|
+
}, [store, id, effects]);
|
|
2159
|
+
};
|
|
2160
|
+
const useConcerns = (id, registration, customConcerns) => {
|
|
2161
|
+
const store = useStoreContext();
|
|
2162
|
+
const concerns = customConcerns || defaultConcerns;
|
|
2163
|
+
useLayoutEffect2(() => {
|
|
2164
|
+
const registerConcernEffects3 = store._internal.config.useLegacyImplementation ? registerConcernEffects : registerConcernEffects2;
|
|
2165
|
+
return registerConcernEffects3(store, registration, concerns);
|
|
2166
|
+
}, [store, id, registration, customConcerns]);
|
|
2167
|
+
};
|
|
2168
|
+
const withConcerns = (selection) => {
|
|
2169
|
+
return {
|
|
2170
|
+
useFieldStore: (path) => {
|
|
2171
|
+
const { store, value, setValue } = _useFieldValue(path);
|
|
2172
|
+
const concernsSnap = useSnapshot(store._concerns);
|
|
2173
|
+
const allConcerns = concernsSnap[path] || {};
|
|
2174
|
+
const selectedConcerns = Object.keys(selection).reduce(
|
|
2175
|
+
(acc, key) => {
|
|
2176
|
+
if (selection[key] && Object.prototype.hasOwnProperty.call(allConcerns, key)) {
|
|
2177
|
+
acc[key] = allConcerns[key];
|
|
2178
|
+
}
|
|
2179
|
+
return acc;
|
|
2180
|
+
},
|
|
2181
|
+
{}
|
|
2182
|
+
);
|
|
2183
|
+
return { value, setValue, ...selectedConcerns };
|
|
2184
|
+
}
|
|
2185
|
+
};
|
|
2186
|
+
};
|
|
2187
|
+
const withMeta = (presetMeta) => ({
|
|
2188
|
+
useFieldStore: (path) => {
|
|
2189
|
+
const { store, value, setValue: originalSetValue } = _useFieldValue(path);
|
|
2190
|
+
const concernsSnap = useSnapshot(store._concerns);
|
|
2191
|
+
const allConcerns = concernsSnap[path] || {};
|
|
2192
|
+
const setValue = useCallback(
|
|
2193
|
+
(newValue, meta) => {
|
|
2194
|
+
originalSetValue(newValue, { ...presetMeta, ...meta });
|
|
2195
|
+
},
|
|
2196
|
+
[originalSetValue]
|
|
2197
|
+
);
|
|
2198
|
+
return { value, setValue, ...allConcerns };
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
return {
|
|
2202
|
+
Provider,
|
|
2203
|
+
useFieldStore,
|
|
2204
|
+
useStore,
|
|
2205
|
+
useJitStore,
|
|
2206
|
+
useSideEffects,
|
|
2207
|
+
useConcerns,
|
|
2208
|
+
withConcerns,
|
|
2209
|
+
withMeta
|
|
2210
|
+
};
|
|
2211
|
+
};
|
|
2212
|
+
|
|
2213
|
+
// src/hooks/use-buffered-field.ts
|
|
2214
|
+
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
2215
|
+
var useBufferedField = (field) => {
|
|
2216
|
+
const { value: storedValue, setValue: setStoredValue } = field;
|
|
2217
|
+
const [localValue, setLocalValue] = useState2(null);
|
|
2218
|
+
const isEditing = localValue !== null;
|
|
2219
|
+
const isDirty = isEditing && localValue !== storedValue;
|
|
2220
|
+
const setValue = useCallback2((newValue) => {
|
|
2221
|
+
setLocalValue(newValue);
|
|
2222
|
+
}, []);
|
|
2223
|
+
const commit = useCallback2(() => {
|
|
2224
|
+
if (localValue !== null) {
|
|
2225
|
+
setStoredValue(localValue);
|
|
2226
|
+
}
|
|
2227
|
+
setLocalValue(null);
|
|
2228
|
+
}, [localValue, setStoredValue]);
|
|
2229
|
+
const cancel = useCallback2(() => {
|
|
2230
|
+
setLocalValue(null);
|
|
2231
|
+
}, []);
|
|
2232
|
+
const value = isEditing ? localValue : storedValue;
|
|
2233
|
+
return { value, setValue, commit, cancel, isDirty };
|
|
2234
|
+
};
|
|
2235
|
+
|
|
2236
|
+
// src/hooks/use-keyboard-select.ts
|
|
2237
|
+
import { useCallback as useCallback3, useRef as useRef2 } from "react";
|
|
2238
|
+
var useKeyboardSelect = (field, config) => {
|
|
2239
|
+
const { setValue } = field;
|
|
2240
|
+
const { options, debounceMs = 500, matchFromStart = true } = config;
|
|
2241
|
+
const searchRef = useRef2("");
|
|
2242
|
+
const timeoutRef = useRef2(null);
|
|
2243
|
+
const onKeyDown = useCallback3(
|
|
2244
|
+
(e) => {
|
|
2245
|
+
if (e.key.length !== 1) return;
|
|
2246
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
2247
|
+
const char = e.key.toLowerCase();
|
|
2248
|
+
if (timeoutRef.current) {
|
|
2249
|
+
clearTimeout(timeoutRef.current);
|
|
2250
|
+
}
|
|
2251
|
+
searchRef.current += char;
|
|
2252
|
+
const search = searchRef.current;
|
|
2253
|
+
const match = options.find((opt) => {
|
|
2254
|
+
const label = opt.label.toLowerCase();
|
|
2255
|
+
return matchFromStart ? label.startsWith(search) : label.includes(search);
|
|
2256
|
+
});
|
|
2257
|
+
if (match) {
|
|
2258
|
+
setValue(match.value);
|
|
2259
|
+
}
|
|
2260
|
+
timeoutRef.current = setTimeout(() => {
|
|
2261
|
+
searchRef.current = "";
|
|
2262
|
+
}, debounceMs);
|
|
2263
|
+
},
|
|
2264
|
+
[options, setValue, debounceMs, matchFromStart]
|
|
2265
|
+
);
|
|
2266
|
+
return { ...field, onKeyDown };
|
|
2267
|
+
};
|
|
2268
|
+
|
|
2269
|
+
// src/hooks/use-throttled-field.ts
|
|
2270
|
+
import { useCallback as useCallback4, useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
2271
|
+
var useThrottledField = (field, config) => {
|
|
2272
|
+
const { setValue: originalSetValue, ...rest } = field;
|
|
2273
|
+
const { ms } = config;
|
|
2274
|
+
const throttleRef = useRef3({
|
|
2275
|
+
timeoutId: null,
|
|
2276
|
+
lastFlushTime: 0,
|
|
2277
|
+
pendingValue: void 0,
|
|
2278
|
+
pendingArgs: void 0,
|
|
2279
|
+
hasPending: false
|
|
2280
|
+
});
|
|
2281
|
+
const setValue = useCallback4(
|
|
2282
|
+
(newValue, ...args) => {
|
|
2283
|
+
const now = Date.now();
|
|
2284
|
+
const ref2 = throttleRef.current;
|
|
2285
|
+
const timeSinceLastFlush = now - ref2.lastFlushTime;
|
|
2286
|
+
if (timeSinceLastFlush >= ms) {
|
|
2287
|
+
ref2.lastFlushTime = now;
|
|
2288
|
+
originalSetValue(newValue, ...args);
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
ref2.pendingValue = newValue;
|
|
2292
|
+
ref2.pendingArgs = args;
|
|
2293
|
+
ref2.hasPending = true;
|
|
2294
|
+
if (!ref2.timeoutId) {
|
|
2295
|
+
const remainingTime = ms - timeSinceLastFlush;
|
|
2296
|
+
ref2.timeoutId = setTimeout(() => {
|
|
2297
|
+
ref2.timeoutId = null;
|
|
2298
|
+
ref2.lastFlushTime = Date.now();
|
|
2299
|
+
if (ref2.hasPending) {
|
|
2300
|
+
originalSetValue(
|
|
2301
|
+
ref2.pendingValue,
|
|
2302
|
+
...ref2.pendingArgs
|
|
2303
|
+
);
|
|
2304
|
+
ref2.hasPending = false;
|
|
2305
|
+
ref2.pendingValue = void 0;
|
|
2306
|
+
ref2.pendingArgs = void 0;
|
|
2307
|
+
}
|
|
2308
|
+
}, remainingTime);
|
|
2309
|
+
}
|
|
2310
|
+
},
|
|
2311
|
+
[originalSetValue, ms]
|
|
2312
|
+
);
|
|
2313
|
+
useEffect2(() => {
|
|
2314
|
+
return () => {
|
|
2315
|
+
if (throttleRef.current.timeoutId) {
|
|
2316
|
+
clearTimeout(throttleRef.current.timeoutId);
|
|
2317
|
+
}
|
|
2318
|
+
};
|
|
2319
|
+
}, []);
|
|
2320
|
+
return { ...rest, setValue };
|
|
2321
|
+
};
|
|
2322
|
+
|
|
2323
|
+
// src/hooks/use-transformed-field.ts
|
|
2324
|
+
import { useCallback as useCallback5, useMemo } from "react";
|
|
2325
|
+
var useTransformedField = (field, config) => {
|
|
2326
|
+
const { value: storedValue, setValue: setStoredValue, ...rest } = field;
|
|
2327
|
+
const { to, from } = config;
|
|
2328
|
+
const value = useMemo(() => to(storedValue), [storedValue, to]);
|
|
2329
|
+
const setValue = useCallback5(
|
|
2330
|
+
(displayValue) => {
|
|
2331
|
+
const storedVal = from(displayValue);
|
|
2332
|
+
setStoredValue(storedVal);
|
|
2333
|
+
},
|
|
2334
|
+
[from, setStoredValue]
|
|
2335
|
+
);
|
|
2336
|
+
return { ...rest, value, setValue };
|
|
2337
|
+
};
|
|
2338
|
+
|
|
2339
|
+
// src/utils/hash-key.ts
|
|
2340
|
+
var _ = (id) => id;
|
|
2341
|
+
var rejectDynamic = (path) => {
|
|
2342
|
+
const parts = path.split(".");
|
|
2343
|
+
for (const part of parts) {
|
|
2344
|
+
if (part === "[*]") {
|
|
2345
|
+
throw new Error(
|
|
2346
|
+
`Path contains [*] hash key which is not allowed in store operations. Use concrete paths only.`
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
};
|
|
2351
|
+
var hashKey = {
|
|
2352
|
+
/** Reject paths with dynamic hash keys */
|
|
2353
|
+
rejectDynamic,
|
|
2354
|
+
/** Alias for _ function */
|
|
2355
|
+
_
|
|
2356
|
+
};
|
|
2357
|
+
|
|
2358
|
+
// src/utils/apply-changes.ts
|
|
2359
|
+
var applyChangesToObject = (obj, changes) => {
|
|
2360
|
+
const result = structuredClone(obj);
|
|
2361
|
+
for (const [path, value] of changes) {
|
|
2362
|
+
dot.set__unsafe(result, path, value);
|
|
2363
|
+
}
|
|
2364
|
+
return result;
|
|
2365
|
+
};
|
|
2366
|
+
|
|
2367
|
+
export {
|
|
2368
|
+
is,
|
|
2369
|
+
dot,
|
|
2370
|
+
evaluateBoolLogic,
|
|
2371
|
+
extractPlaceholders,
|
|
2372
|
+
interpolateTemplate,
|
|
2373
|
+
prebuilts_exports,
|
|
2374
|
+
findConcern,
|
|
2375
|
+
defaultConcerns,
|
|
2376
|
+
registerFlipPair,
|
|
2377
|
+
registerListenerLegacy,
|
|
2378
|
+
registerSyncPairsBatch,
|
|
2379
|
+
registerSideEffects,
|
|
2380
|
+
deepClone,
|
|
2381
|
+
createGenericStore,
|
|
2382
|
+
useBufferedField,
|
|
2383
|
+
useKeyboardSelect,
|
|
2384
|
+
useThrottledField,
|
|
2385
|
+
useTransformedField,
|
|
2386
|
+
_,
|
|
2387
|
+
hashKey,
|
|
2388
|
+
applyChangesToObject
|
|
2389
|
+
};
|
|
2390
|
+
//# sourceMappingURL=chunk-SLJZLVMQ.js.map
|