@async/framework 0.10.2 → 0.11.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/CHANGELOG.md +25 -0
- package/README.md +23 -7
- package/browser.d.ts +4 -7
- package/browser.js +17 -66
- package/browser.min.js +1 -1
- package/browser.ts +17 -66
- package/browser.umd.js +17 -66
- package/browser.umd.min.js +1 -1
- package/{server.d.ts → framework.d.ts} +4 -7
- package/framework.ts +5946 -0
- package/package.json +25 -17
- package/server.js +5945 -0
- package/examples/cache/index.html +0 -16
- package/examples/cache/main.js +0 -47
- package/examples/components/index.html +0 -11
- package/examples/components/main.js +0 -26
- package/examples/counter/index.html +0 -15
- package/examples/counter/main.js +0 -17
- package/examples/partials/index.html +0 -15
- package/examples/partials/main.js +0 -43
- package/examples/product/index.html +0 -32
- package/examples/product/main.js +0 -24
- package/examples/router/index.html +0 -18
- package/examples/router/main.js +0 -52
- package/examples/server-call/index.html +0 -21
- package/examples/server-call/main.js +0 -22
- package/examples/ssr/index.html +0 -12
- package/examples/ssr/main.js +0 -89
- package/examples/streaming/index.html +0 -16
- package/examples/streaming/main.js +0 -30
- package/src/app.js +0 -802
- package/src/async-signal.js +0 -277
- package/src/attributes.js +0 -52
- package/src/boundary-receiver.js +0 -302
- package/src/browser.js +0 -18
- package/src/cache.js +0 -193
- package/src/component.js +0 -373
- package/src/delay.js +0 -30
- package/src/elements.js +0 -63
- package/src/handlers.js +0 -219
- package/src/html.js +0 -158
- package/src/index.js +0 -20
- package/src/lazy-registry.js +0 -218
- package/src/loader.js +0 -772
- package/src/partials.js +0 -133
- package/src/registry-store.js +0 -267
- package/src/request-context.js +0 -40
- package/src/router.js +0 -617
- package/src/scheduler.js +0 -300
- package/src/server-entry.js +0 -20
- package/src/server-registry.js +0 -97
- package/src/server.js +0 -362
- package/src/signals.js +0 -592
package/src/signals.js
DELETED
|
@@ -1,592 +0,0 @@
|
|
|
1
|
-
import { asyncSignal as createAsyncSignal, isAsyncSignal } from "./async-signal.js";
|
|
2
|
-
import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
|
|
3
|
-
import { createLazyRegistry, isLazyDescriptor } from "./lazy-registry.js";
|
|
4
|
-
|
|
5
|
-
const signalKind = Symbol.for("@async/framework.signal");
|
|
6
|
-
const computedKind = Symbol.for("@async/framework.computed");
|
|
7
|
-
const effectKind = Symbol.for("@async/framework.effect");
|
|
8
|
-
const refKind = Symbol.for("@async/framework.signalRef");
|
|
9
|
-
const dependencyFrames = [];
|
|
10
|
-
|
|
11
|
-
export function createSignal(initial) {
|
|
12
|
-
let value = initial;
|
|
13
|
-
const subscribers = new Set();
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
[signalKind]: true,
|
|
17
|
-
kind: "signal",
|
|
18
|
-
|
|
19
|
-
get value() {
|
|
20
|
-
return value;
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
set value(nextValue) {
|
|
24
|
-
this.set(nextValue);
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
set(nextValue) {
|
|
28
|
-
if (Object.is(value, nextValue)) {
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
|
-
value = nextValue;
|
|
32
|
-
notify();
|
|
33
|
-
return value;
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
update(fn) {
|
|
37
|
-
return this.set(fn(value));
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
subscribe(fn) {
|
|
41
|
-
if (typeof fn !== "function") {
|
|
42
|
-
throw new TypeError("subscribe(fn) requires a function.");
|
|
43
|
-
}
|
|
44
|
-
subscribers.add(fn);
|
|
45
|
-
return () => subscribers.delete(fn);
|
|
46
|
-
},
|
|
47
|
-
|
|
48
|
-
snapshot() {
|
|
49
|
-
return value;
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
function notify() {
|
|
54
|
-
for (const subscriber of [...subscribers]) {
|
|
55
|
-
subscriber(value);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export const signal = createSignal;
|
|
61
|
-
|
|
62
|
-
export function computed(fn) {
|
|
63
|
-
if (typeof fn !== "function") {
|
|
64
|
-
throw new TypeError("computed(fn) requires a function.");
|
|
65
|
-
}
|
|
66
|
-
const backing = createSignal(undefined);
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
[computedKind]: true,
|
|
70
|
-
kind: "computed",
|
|
71
|
-
|
|
72
|
-
get value() {
|
|
73
|
-
return backing.value;
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
set(nextValue) {
|
|
77
|
-
return backing.set(nextValue);
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
update(fn) {
|
|
81
|
-
return backing.update(fn);
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
subscribe(fn) {
|
|
85
|
-
return backing.subscribe(fn);
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
snapshot() {
|
|
89
|
-
return backing.snapshot();
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
_bindRegistry(registry, id) {
|
|
93
|
-
return registry.effect(() => {
|
|
94
|
-
backing.set(fn.call({
|
|
95
|
-
signals: registry,
|
|
96
|
-
id,
|
|
97
|
-
server: registry._context?.().server,
|
|
98
|
-
router: registry._context?.().router,
|
|
99
|
-
loader: registry._context?.().loader,
|
|
100
|
-
cache: registry._context?.().cache,
|
|
101
|
-
scheduler: registry._context?.().scheduler
|
|
102
|
-
}));
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function effect(fn) {
|
|
109
|
-
if (typeof fn !== "function") {
|
|
110
|
-
throw new TypeError("effect(fn) requires a function.");
|
|
111
|
-
}
|
|
112
|
-
return {
|
|
113
|
-
[effectKind]: true,
|
|
114
|
-
kind: "effect",
|
|
115
|
-
fn,
|
|
116
|
-
_bindRegistry(registry) {
|
|
117
|
-
return registry.effect(fn);
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function createSignalRegistry(initialMap = {}, options = {}) {
|
|
123
|
-
const registryStore = options.registry ?? createRegistryStore();
|
|
124
|
-
const type = options.type ?? "signal";
|
|
125
|
-
const entries = registryStore._map(type);
|
|
126
|
-
const asyncDescriptors = registryStore._map("asyncSignal");
|
|
127
|
-
const lazyRegistry = options.lazyRegistry ?? createLazyRegistry(options);
|
|
128
|
-
const registryCleanups = new Map();
|
|
129
|
-
const runtimeContext = {};
|
|
130
|
-
const boundEntries = new Set();
|
|
131
|
-
let subscriptionCounter = 0;
|
|
132
|
-
let effectCounter = 0;
|
|
133
|
-
|
|
134
|
-
const registry = attachRegistryInspection({
|
|
135
|
-
register(id, signalLike) {
|
|
136
|
-
assertId(id);
|
|
137
|
-
if (entries.has(id)) {
|
|
138
|
-
throw new Error(`Signal "${id}" is already registered.`);
|
|
139
|
-
}
|
|
140
|
-
const entry = normalizeSignal(signalLike);
|
|
141
|
-
entries.set(id, entry);
|
|
142
|
-
bindEntry(id, entry);
|
|
143
|
-
return registry.ref(id);
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
registerMany(map) {
|
|
147
|
-
for (const [id, signalLike] of Object.entries(map ?? {})) {
|
|
148
|
-
registry.register(id, signalLike);
|
|
149
|
-
}
|
|
150
|
-
return registry;
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
unregister(id) {
|
|
154
|
-
assertId(id);
|
|
155
|
-
if (!entries.has(id)) {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
registryCleanups.get(id)?.();
|
|
159
|
-
registryCleanups.delete(id);
|
|
160
|
-
entries.get(id)?._dispose?.();
|
|
161
|
-
entries.delete(id);
|
|
162
|
-
boundEntries.delete(id);
|
|
163
|
-
return true;
|
|
164
|
-
},
|
|
165
|
-
|
|
166
|
-
ensure(id, initial) {
|
|
167
|
-
assertId(id);
|
|
168
|
-
materializeAsyncSignal(id);
|
|
169
|
-
if (!entries.has(id)) {
|
|
170
|
-
registry.register(id, createSignal(initial));
|
|
171
|
-
}
|
|
172
|
-
return registry.ref(id);
|
|
173
|
-
},
|
|
174
|
-
|
|
175
|
-
has(id) {
|
|
176
|
-
return entries.has(id) || asyncDescriptors.has(id);
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
get(path) {
|
|
180
|
-
const parsed = parseRegistryPath(path);
|
|
181
|
-
track(parsed.path);
|
|
182
|
-
const entry = requireEntry(entries, parsed.id);
|
|
183
|
-
return readEntry(entry, parsed.parts);
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
set(path, value) {
|
|
187
|
-
const parsed = parseRegistryPath(path);
|
|
188
|
-
const entry = requireEntry(entries, parsed.id);
|
|
189
|
-
if (parsed.parts.length === 0) {
|
|
190
|
-
return entry.set(value);
|
|
191
|
-
}
|
|
192
|
-
const nextValue = setPath(entry.value, parsed.parts, value);
|
|
193
|
-
entry.set(nextValue);
|
|
194
|
-
return value;
|
|
195
|
-
},
|
|
196
|
-
|
|
197
|
-
update(path, fn) {
|
|
198
|
-
if (typeof fn !== "function") {
|
|
199
|
-
throw new TypeError("update(path, fn) requires a function.");
|
|
200
|
-
}
|
|
201
|
-
return registry.set(path, fn(registry.get(path)));
|
|
202
|
-
},
|
|
203
|
-
|
|
204
|
-
ref(id) {
|
|
205
|
-
assertId(id);
|
|
206
|
-
materializeAsyncSignal(id);
|
|
207
|
-
return createRef(registry, id);
|
|
208
|
-
},
|
|
209
|
-
|
|
210
|
-
subscribe(path, fn, options = {}) {
|
|
211
|
-
if (typeof fn !== "function") {
|
|
212
|
-
throw new TypeError("subscribe(path, fn) requires a function.");
|
|
213
|
-
}
|
|
214
|
-
const parsed = parseRegistryPath(path);
|
|
215
|
-
const entry = requireEntry(entries, parsed.id);
|
|
216
|
-
const subscriptionId = ++subscriptionCounter;
|
|
217
|
-
return entry.subscribe(() => {
|
|
218
|
-
scheduleCallback(() => fn(registry.get(parsed.path), {
|
|
219
|
-
id: parsed.id,
|
|
220
|
-
path: parsed.path,
|
|
221
|
-
signal: entry
|
|
222
|
-
}), {
|
|
223
|
-
...options,
|
|
224
|
-
key: options.key ?? `signal:${parsed.path}:${subscriptionId}`
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
snapshot() {
|
|
230
|
-
const snapshot = {};
|
|
231
|
-
for (const [id, entry] of entries) {
|
|
232
|
-
snapshot[id] = typeof entry.snapshot === "function" ? entry.snapshot() : entry.value;
|
|
233
|
-
}
|
|
234
|
-
return snapshot;
|
|
235
|
-
},
|
|
236
|
-
|
|
237
|
-
asyncSignal(id, fn) {
|
|
238
|
-
registry.register(id, createAsyncSignal(id, fn));
|
|
239
|
-
return registry.ref(id);
|
|
240
|
-
},
|
|
241
|
-
|
|
242
|
-
effect(fn, options = {}) {
|
|
243
|
-
let cleanup;
|
|
244
|
-
let dependencyCleanups = [];
|
|
245
|
-
let stopped = false;
|
|
246
|
-
const scheduler = options.scheduler;
|
|
247
|
-
const effectId = ++effectCounter;
|
|
248
|
-
|
|
249
|
-
const run = () => {
|
|
250
|
-
if (stopped) {
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
if (typeof cleanup === "function") {
|
|
254
|
-
cleanup();
|
|
255
|
-
}
|
|
256
|
-
for (const stop of dependencyCleanups) {
|
|
257
|
-
stop();
|
|
258
|
-
}
|
|
259
|
-
dependencyCleanups = [];
|
|
260
|
-
|
|
261
|
-
const outcome = registry._collectDependencies(() => fn.call({
|
|
262
|
-
signals: registry,
|
|
263
|
-
server: runtimeContext.server,
|
|
264
|
-
router: runtimeContext.router,
|
|
265
|
-
loader: runtimeContext.loader,
|
|
266
|
-
cache: runtimeContext.cache,
|
|
267
|
-
scheduler: runtimeContext.scheduler
|
|
268
|
-
}));
|
|
269
|
-
cleanup = outcome.value;
|
|
270
|
-
dependencyCleanups = outcome.dependencies.map((dependency) => registry.subscribe(dependency, scheduleRun));
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
const scheduleRun = () => {
|
|
274
|
-
if (!scheduler) {
|
|
275
|
-
run();
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
scheduler.enqueue(options.phase ?? "effect", run, {
|
|
279
|
-
scope: options.scope,
|
|
280
|
-
key: options.key ?? `effect:${effectId}`
|
|
281
|
-
});
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
run();
|
|
285
|
-
|
|
286
|
-
return () => {
|
|
287
|
-
stopped = true;
|
|
288
|
-
if (typeof cleanup === "function") {
|
|
289
|
-
cleanup();
|
|
290
|
-
}
|
|
291
|
-
for (const stop of dependencyCleanups) {
|
|
292
|
-
stop();
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
destroy() {
|
|
298
|
-
for (const cleanup of registryCleanups.values()) {
|
|
299
|
-
cleanup();
|
|
300
|
-
}
|
|
301
|
-
registryCleanups.clear();
|
|
302
|
-
for (const entry of entries.values()) {
|
|
303
|
-
entry._dispose?.();
|
|
304
|
-
}
|
|
305
|
-
entries.clear();
|
|
306
|
-
},
|
|
307
|
-
|
|
308
|
-
_collectDependencies(fn) {
|
|
309
|
-
const frame = new Set();
|
|
310
|
-
dependencyFrames.push(frame);
|
|
311
|
-
try {
|
|
312
|
-
const value = fn();
|
|
313
|
-
return { value, dependencies: [...frame] };
|
|
314
|
-
} finally {
|
|
315
|
-
dependencyFrames.pop();
|
|
316
|
-
}
|
|
317
|
-
},
|
|
318
|
-
|
|
319
|
-
_entry(id) {
|
|
320
|
-
materializeAsyncSignal(id);
|
|
321
|
-
return requireEntry(entries, id);
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
_setContext(context = {}) {
|
|
325
|
-
Object.assign(runtimeContext, context);
|
|
326
|
-
return registry;
|
|
327
|
-
},
|
|
328
|
-
|
|
329
|
-
_context() {
|
|
330
|
-
return runtimeContext;
|
|
331
|
-
},
|
|
332
|
-
|
|
333
|
-
_adoptMany(map = {}) {
|
|
334
|
-
for (const id of Object.keys(map ?? {})) {
|
|
335
|
-
if (entries.has(id)) {
|
|
336
|
-
bindEntry(id, entries.get(id));
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
return registry;
|
|
340
|
-
}
|
|
341
|
-
}, registryStore, type);
|
|
342
|
-
|
|
343
|
-
for (const [id, entry] of entries) {
|
|
344
|
-
bindEntry(id, entry);
|
|
345
|
-
}
|
|
346
|
-
registry.registerMany(initialMap);
|
|
347
|
-
return registry;
|
|
348
|
-
|
|
349
|
-
function bindEntry(id, entry) {
|
|
350
|
-
if (boundEntries.has(id) || typeof entry?._bindRegistry !== "function") {
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
boundEntries.add(id);
|
|
354
|
-
const cleanup = entry._bindRegistry(registry, id);
|
|
355
|
-
if (typeof cleanup === "function") {
|
|
356
|
-
registryCleanups.set(id, cleanup);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function parseRegistryPath(path) {
|
|
361
|
-
if (typeof path !== "string" || path.length === 0) {
|
|
362
|
-
throw new TypeError("Signal path must be a non-empty string.");
|
|
363
|
-
}
|
|
364
|
-
const segments = path.split(".");
|
|
365
|
-
for (let end = segments.length; end > 0; end -= 1) {
|
|
366
|
-
const id = segments.slice(0, end).join(".");
|
|
367
|
-
if (entries.has(id) || asyncDescriptors.has(id)) {
|
|
368
|
-
materializeAsyncSignal(id);
|
|
369
|
-
return { id, parts: segments.slice(end), path };
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
const [id, ...parts] = segments;
|
|
373
|
-
return { id, parts, path };
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function materializeAsyncSignal(id) {
|
|
377
|
-
if (entries.has(id) || !asyncDescriptors.has(id)) {
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
const descriptor = asyncDescriptors.get(id);
|
|
381
|
-
if (!isLazyDescriptor(descriptor) && typeof descriptor !== "function") {
|
|
382
|
-
throw new TypeError(`Async signal "${id}" must be a function or lazy descriptor.`);
|
|
383
|
-
}
|
|
384
|
-
const loader = async function runLazyAsyncSignal(...args) {
|
|
385
|
-
const resolved = await lazyRegistry.resolve("asyncSignal", id, descriptor);
|
|
386
|
-
if (typeof resolved !== "function") {
|
|
387
|
-
throw new TypeError(`Async signal "${id}" did not resolve to a function.`);
|
|
388
|
-
}
|
|
389
|
-
return resolved.apply(this, args);
|
|
390
|
-
};
|
|
391
|
-
const entry = createAsyncSignal(id, loader);
|
|
392
|
-
entries.set(id, entry);
|
|
393
|
-
bindEntry(id, entry);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function scheduleCallback(fn, options = {}) {
|
|
397
|
-
const scheduler = options.scheduler;
|
|
398
|
-
if (!scheduler || options.phase === "sync") {
|
|
399
|
-
return fn();
|
|
400
|
-
}
|
|
401
|
-
return scheduler.enqueue(options.phase ?? "effect", fn, {
|
|
402
|
-
scope: options.scope,
|
|
403
|
-
key: options.key
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function normalizeSignal(signalLike) {
|
|
409
|
-
if (isSignalLike(signalLike)) {
|
|
410
|
-
return signalLike;
|
|
411
|
-
}
|
|
412
|
-
return createSignal(signalLike);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function isSignalLike(value) {
|
|
416
|
-
return Boolean(value && typeof value === "object" && typeof value.subscribe === "function");
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
function createRef(registry, id) {
|
|
420
|
-
return {
|
|
421
|
-
[refKind]: true,
|
|
422
|
-
kind: "signal-ref",
|
|
423
|
-
id,
|
|
424
|
-
|
|
425
|
-
get value() {
|
|
426
|
-
return registry.get(id);
|
|
427
|
-
},
|
|
428
|
-
|
|
429
|
-
set value(nextValue) {
|
|
430
|
-
registry.set(id, nextValue);
|
|
431
|
-
},
|
|
432
|
-
|
|
433
|
-
get loading() {
|
|
434
|
-
return registry._entry(id).loading ?? false;
|
|
435
|
-
},
|
|
436
|
-
|
|
437
|
-
get error() {
|
|
438
|
-
return registry._entry(id).error ?? null;
|
|
439
|
-
},
|
|
440
|
-
|
|
441
|
-
get status() {
|
|
442
|
-
return registry._entry(id).status ?? "ready";
|
|
443
|
-
},
|
|
444
|
-
|
|
445
|
-
get version() {
|
|
446
|
-
return registry._entry(id).version ?? 0;
|
|
447
|
-
},
|
|
448
|
-
|
|
449
|
-
get() {
|
|
450
|
-
return registry.get(id);
|
|
451
|
-
},
|
|
452
|
-
|
|
453
|
-
set(nextValue) {
|
|
454
|
-
return registry.set(id, nextValue);
|
|
455
|
-
},
|
|
456
|
-
|
|
457
|
-
update(fn) {
|
|
458
|
-
return registry.update(id, fn);
|
|
459
|
-
},
|
|
460
|
-
|
|
461
|
-
subscribe(fn) {
|
|
462
|
-
return registry.subscribe(id, fn);
|
|
463
|
-
},
|
|
464
|
-
|
|
465
|
-
refresh() {
|
|
466
|
-
const entry = registry._entry(id);
|
|
467
|
-
if (typeof entry.refresh !== "function") {
|
|
468
|
-
throw new Error(`Signal "${id}" cannot refresh.`);
|
|
469
|
-
}
|
|
470
|
-
return entry.refresh();
|
|
471
|
-
},
|
|
472
|
-
|
|
473
|
-
cancel(reason) {
|
|
474
|
-
const entry = registry._entry(id);
|
|
475
|
-
if (typeof entry.cancel !== "function") {
|
|
476
|
-
throw new Error(`Signal "${id}" cannot cancel.`);
|
|
477
|
-
}
|
|
478
|
-
return entry.cancel(reason);
|
|
479
|
-
},
|
|
480
|
-
|
|
481
|
-
toString() {
|
|
482
|
-
return id;
|
|
483
|
-
},
|
|
484
|
-
|
|
485
|
-
[Symbol.toPrimitive]() {
|
|
486
|
-
return id;
|
|
487
|
-
}
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
export function isSignalRef(value) {
|
|
492
|
-
return Boolean(value?.[refKind]);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function parsePath(path, entries) {
|
|
496
|
-
if (typeof path !== "string" || path.length === 0) {
|
|
497
|
-
throw new TypeError("Signal path must be a non-empty string.");
|
|
498
|
-
}
|
|
499
|
-
const segments = path.split(".");
|
|
500
|
-
for (let end = segments.length; end > 0; end -= 1) {
|
|
501
|
-
const id = segments.slice(0, end).join(".");
|
|
502
|
-
if (entries.has(id)) {
|
|
503
|
-
return { id, parts: segments.slice(end), path };
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
const [id, ...parts] = segments;
|
|
507
|
-
return { id, parts, path };
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function readEntry(entry, parts) {
|
|
511
|
-
if (isAsyncSignal(entry) && parts[0]?.startsWith("$")) {
|
|
512
|
-
const metadata = readAsyncMetadata(entry, parts[0]);
|
|
513
|
-
return readPath(metadata, parts.slice(1));
|
|
514
|
-
}
|
|
515
|
-
return readPath(entry.value, parts);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function readAsyncMetadata(entry, part) {
|
|
519
|
-
switch (part) {
|
|
520
|
-
case "$value":
|
|
521
|
-
return entry.value;
|
|
522
|
-
case "$loading":
|
|
523
|
-
return entry.loading;
|
|
524
|
-
case "$error":
|
|
525
|
-
return entry.error;
|
|
526
|
-
case "$status":
|
|
527
|
-
return entry.status;
|
|
528
|
-
case "$version":
|
|
529
|
-
return entry.version;
|
|
530
|
-
default:
|
|
531
|
-
return undefined;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
function readPath(value, parts) {
|
|
536
|
-
let cursor = value;
|
|
537
|
-
for (const part of parts) {
|
|
538
|
-
if (cursor == null) {
|
|
539
|
-
return undefined;
|
|
540
|
-
}
|
|
541
|
-
cursor = cursor[part];
|
|
542
|
-
}
|
|
543
|
-
return cursor;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
function setPath(value, parts, nextValue) {
|
|
547
|
-
const root = cloneContainer(value, parts[0]);
|
|
548
|
-
let cursor = root;
|
|
549
|
-
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
550
|
-
const part = parts[index];
|
|
551
|
-
const nextPart = parts[index + 1];
|
|
552
|
-
cursor[part] = cloneContainer(cursor[part], nextPart);
|
|
553
|
-
cursor = cursor[part];
|
|
554
|
-
}
|
|
555
|
-
cursor[parts.at(-1)] = nextValue;
|
|
556
|
-
return root;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function cloneContainer(value, nextPart) {
|
|
560
|
-
if (Array.isArray(value)) {
|
|
561
|
-
return [...value];
|
|
562
|
-
}
|
|
563
|
-
if (value && typeof value === "object") {
|
|
564
|
-
return { ...value };
|
|
565
|
-
}
|
|
566
|
-
return isArrayIndex(nextPart) ? [] : {};
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function isArrayIndex(part) {
|
|
570
|
-
return String(Number(part)) === String(part);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
function requireEntry(entries, id) {
|
|
574
|
-
const entry = entries.get(id);
|
|
575
|
-
if (!entry) {
|
|
576
|
-
throw new Error(`Signal "${id}" is not registered.`);
|
|
577
|
-
}
|
|
578
|
-
return entry;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
function assertId(id) {
|
|
582
|
-
if (typeof id !== "string" || id.length === 0) {
|
|
583
|
-
throw new TypeError("Signal id must be a non-empty string.");
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
function track(path) {
|
|
588
|
-
const frame = dependencyFrames.at(-1);
|
|
589
|
-
if (frame) {
|
|
590
|
-
frame.add(path);
|
|
591
|
-
}
|
|
592
|
-
}
|