@async/framework 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +73 -0
- package/framework.js +313 -92
- package/package.json +1 -1
- package/src/async-signal.js +12 -1
- package/src/cache.js +5 -0
- package/src/component.js +79 -15
- package/src/handlers.js +12 -4
- package/src/loader.js +89 -7
- package/src/partials.js +5 -0
- package/src/registry-store.js +4 -0
- package/src/router.js +9 -0
- package/src/server.js +26 -6
- package/src/signals.js +16 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@async/framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "No-build AsyncLoader app runtime with signals, command events, server calls, route partials, cache split, SSR activation, and streaming boundaries.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.1.0",
|
package/src/async-signal.js
CHANGED
|
@@ -82,7 +82,18 @@ export function asyncSignal(id, fn) {
|
|
|
82
82
|
signals: registry,
|
|
83
83
|
id: registeredId,
|
|
84
84
|
get server() {
|
|
85
|
-
|
|
85
|
+
const context = registry._context?.() ?? {};
|
|
86
|
+
const server = context.server;
|
|
87
|
+
if (typeof server?._withContext === "function") {
|
|
88
|
+
return server._withContext({
|
|
89
|
+
signals: registry,
|
|
90
|
+
router: context.router,
|
|
91
|
+
loader: context.loader,
|
|
92
|
+
cache: context.cache,
|
|
93
|
+
abort: activeAbort
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return server;
|
|
86
97
|
},
|
|
87
98
|
get router() {
|
|
88
99
|
return registry._context?.().router;
|
package/src/cache.js
CHANGED
|
@@ -34,6 +34,11 @@ export function createCacheRegistry(initialMap = {}, { now = () => Date.now(), r
|
|
|
34
34
|
return registryApi;
|
|
35
35
|
},
|
|
36
36
|
|
|
37
|
+
unregister(id) {
|
|
38
|
+
assertId(id);
|
|
39
|
+
return definitions.delete(id);
|
|
40
|
+
},
|
|
41
|
+
|
|
37
42
|
resolve(id) {
|
|
38
43
|
assertId(id);
|
|
39
44
|
return definitions.get(id);
|
package/src/component.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { attributeName } from "./attributes.js";
|
|
2
|
+
import { escapeHtml, rawHtml, renderTemplate } from "./html.js";
|
|
2
3
|
import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
|
|
3
4
|
|
|
4
5
|
const componentKind = Symbol.for("@async/framework.component");
|
|
@@ -44,6 +45,13 @@ export function createComponentRegistry(initialMap = {}, options = {}) {
|
|
|
44
45
|
return registry;
|
|
45
46
|
},
|
|
46
47
|
|
|
48
|
+
unregister(id) {
|
|
49
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
50
|
+
throw new TypeError("Component id must be a non-empty string.");
|
|
51
|
+
}
|
|
52
|
+
return entries.delete(id);
|
|
53
|
+
},
|
|
54
|
+
|
|
47
55
|
resolve(id) {
|
|
48
56
|
if (typeof id !== "string" || id.length === 0) {
|
|
49
57
|
throw new TypeError("Component id must be a non-empty string.");
|
|
@@ -75,17 +83,7 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
|
|
|
75
83
|
const visibleHooks = [];
|
|
76
84
|
const destroyHooks = [];
|
|
77
85
|
const bindingIds = [];
|
|
78
|
-
const
|
|
79
|
-
runtime,
|
|
80
|
-
scope,
|
|
81
|
-
cleanups,
|
|
82
|
-
attachHooks,
|
|
83
|
-
visibleHooks,
|
|
84
|
-
destroyHooks
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const output = Component.call(context, props);
|
|
88
|
-
const html = renderTemplate(output, {
|
|
86
|
+
const templateOptions = {
|
|
89
87
|
attributes: runtime.attributes,
|
|
90
88
|
signals: runtime.signals,
|
|
91
89
|
bind(value) {
|
|
@@ -96,8 +94,21 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
|
|
|
96
94
|
bindingIds.push(id);
|
|
97
95
|
return id;
|
|
98
96
|
}
|
|
97
|
+
};
|
|
98
|
+
const renderScopedTemplate = (value) => renderTemplate(value, templateOptions);
|
|
99
|
+
const context = createComponentContext({
|
|
100
|
+
runtime,
|
|
101
|
+
scope,
|
|
102
|
+
cleanups,
|
|
103
|
+
attachHooks,
|
|
104
|
+
visibleHooks,
|
|
105
|
+
destroyHooks,
|
|
106
|
+
renderScopedTemplate
|
|
99
107
|
});
|
|
100
108
|
|
|
109
|
+
const output = Component.call(context, props);
|
|
110
|
+
const html = renderScopedTemplate(output);
|
|
111
|
+
|
|
101
112
|
return {
|
|
102
113
|
html,
|
|
103
114
|
attach(target) {
|
|
@@ -133,7 +144,7 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
|
|
|
133
144
|
};
|
|
134
145
|
}
|
|
135
146
|
|
|
136
|
-
function createComponentContext({ runtime, scope, cleanups, attachHooks, visibleHooks, destroyHooks }) {
|
|
147
|
+
function createComponentContext({ runtime, scope, cleanups, attachHooks, visibleHooks, destroyHooks, renderScopedTemplate }) {
|
|
137
148
|
const { signals, handlers, loader, server, router, cache } = runtime;
|
|
138
149
|
const generatedHandlers = new WeakMap();
|
|
139
150
|
let generatedHandlerCounter = 0;
|
|
@@ -149,14 +160,27 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
|
|
|
149
160
|
|
|
150
161
|
signal(name, initial) {
|
|
151
162
|
if (arguments.length === 1) {
|
|
152
|
-
|
|
163
|
+
const id = scoped(scope, `signal.${++generatedSignalCounter}`);
|
|
164
|
+
const ref = signals.ensure(id, name);
|
|
165
|
+
cleanups.push(() => signals.unregister?.(id));
|
|
166
|
+
return ref;
|
|
167
|
+
}
|
|
168
|
+
const id = scoped(scope, name);
|
|
169
|
+
const created = !signals.has(id);
|
|
170
|
+
const ref = signals.ensure(id, initial);
|
|
171
|
+
if (created) {
|
|
172
|
+
cleanups.push(() => signals.unregister?.(id));
|
|
153
173
|
}
|
|
154
|
-
return
|
|
174
|
+
return ref;
|
|
155
175
|
},
|
|
156
176
|
|
|
157
177
|
computed(name, fn) {
|
|
158
178
|
const id = scoped(scope, name);
|
|
179
|
+
const created = !signals.has(id);
|
|
159
180
|
const ref = signals.ensure(id, undefined);
|
|
181
|
+
if (created) {
|
|
182
|
+
cleanups.push(() => signals.unregister?.(id));
|
|
183
|
+
}
|
|
160
184
|
const cleanup = signals.effect(() => {
|
|
161
185
|
signals.set(id, fn.call(context));
|
|
162
186
|
});
|
|
@@ -166,9 +190,13 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
|
|
|
166
190
|
|
|
167
191
|
asyncSignal(name, fn) {
|
|
168
192
|
const id = scoped(scope, name);
|
|
193
|
+
const created = !signals.has(id);
|
|
169
194
|
if (!signals.has(id)) {
|
|
170
195
|
signals.asyncSignal(id, fn);
|
|
171
196
|
}
|
|
197
|
+
if (created) {
|
|
198
|
+
cleanups.push(() => signals.unregister?.(id));
|
|
199
|
+
}
|
|
172
200
|
return signals.ref(id);
|
|
173
201
|
},
|
|
174
202
|
|
|
@@ -202,6 +230,26 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
|
|
|
202
230
|
return rawHtml(child.html);
|
|
203
231
|
},
|
|
204
232
|
|
|
233
|
+
suspense(signalRef, views) {
|
|
234
|
+
const id = signalRef?.id;
|
|
235
|
+
if (!id) {
|
|
236
|
+
throw new TypeError("this.suspense(signalRef, views) requires a signal ref.");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const normalized = normalizeSuspenseViews(views);
|
|
240
|
+
const chunks = [];
|
|
241
|
+
for (const state of ["loading", "ready", "error"]) {
|
|
242
|
+
const view = normalized[state];
|
|
243
|
+
if (!view) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const attr = attributeName(runtime.attributes, "async", state);
|
|
247
|
+
const body = renderScopedTemplate(view.call(context, signalRef));
|
|
248
|
+
chunks.push(`<template ${attr}="${escapeHtml(id)}">${body}</template>`);
|
|
249
|
+
}
|
|
250
|
+
return rawHtml(chunks.join(""));
|
|
251
|
+
},
|
|
252
|
+
|
|
205
253
|
on(eventName, fn) {
|
|
206
254
|
if (typeof eventName !== "string" || eventName.length === 0) {
|
|
207
255
|
throw new TypeError("Component lifecycle event must be a non-empty string.");
|
|
@@ -241,6 +289,7 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
|
|
|
241
289
|
handlers.register(id, function runComponentHandler(handlerContext) {
|
|
242
290
|
return fn.call({ ...context, ...handlerContext }, handlerContext);
|
|
243
291
|
});
|
|
292
|
+
cleanups.push(() => handlers.unregister?.(id));
|
|
244
293
|
return id;
|
|
245
294
|
}
|
|
246
295
|
}
|
|
@@ -252,6 +301,21 @@ function scoped(scope, name) {
|
|
|
252
301
|
return `${scope}.${name}`;
|
|
253
302
|
}
|
|
254
303
|
|
|
304
|
+
function normalizeSuspenseViews(views) {
|
|
305
|
+
const normalized = typeof views === "function" ? { ready: views } : views;
|
|
306
|
+
if (!normalized || typeof normalized !== "object" || Array.isArray(normalized)) {
|
|
307
|
+
throw new TypeError("this.suspense(signalRef, views) requires views to be a function or object.");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for (const state of ["loading", "ready", "error"]) {
|
|
311
|
+
if (Object.hasOwn(normalized, state) && normalized[state] !== undefined && typeof normalized[state] !== "function") {
|
|
312
|
+
throw new TypeError(`this.suspense(signalRef, views) view "${state}" must be a function.`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return normalized;
|
|
317
|
+
}
|
|
318
|
+
|
|
255
319
|
function componentName(Component) {
|
|
256
320
|
return Component.displayName || Component.name || "anonymous";
|
|
257
321
|
}
|
package/src/handlers.js
CHANGED
|
@@ -6,11 +6,10 @@ import {
|
|
|
6
6
|
} from "./server.js";
|
|
7
7
|
import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
|
|
8
8
|
|
|
9
|
-
const builtInTokens = new Set(["preventDefault", "stopPropagation", "stopImmediatePropagation"]);
|
|
9
|
+
const builtInTokens = new Set(["prevent", "preventDefault", "stopPropagation", "stopImmediatePropagation"]);
|
|
10
10
|
const builtInHandlers = {
|
|
11
|
-
preventDefault
|
|
12
|
-
|
|
13
|
-
},
|
|
11
|
+
prevent: preventDefault,
|
|
12
|
+
preventDefault,
|
|
14
13
|
stopPropagation() {
|
|
15
14
|
this.event?.stopPropagation?.();
|
|
16
15
|
},
|
|
@@ -19,6 +18,10 @@ const builtInHandlers = {
|
|
|
19
18
|
}
|
|
20
19
|
};
|
|
21
20
|
|
|
21
|
+
function preventDefault() {
|
|
22
|
+
this.event?.preventDefault?.();
|
|
23
|
+
}
|
|
24
|
+
|
|
22
25
|
export function createHandlerRegistry(initialMap = {}, options = {}) {
|
|
23
26
|
const registryStore = options.registry ?? createRegistryStore();
|
|
24
27
|
const type = options.type ?? "handler";
|
|
@@ -44,6 +47,11 @@ export function createHandlerRegistry(initialMap = {}, options = {}) {
|
|
|
44
47
|
return registry;
|
|
45
48
|
},
|
|
46
49
|
|
|
50
|
+
unregister(id) {
|
|
51
|
+
assertId(id);
|
|
52
|
+
return handlers.delete(id);
|
|
53
|
+
},
|
|
54
|
+
|
|
47
55
|
resolve(id) {
|
|
48
56
|
assertId(id);
|
|
49
57
|
return handlers.get(id);
|
package/src/loader.js
CHANGED
|
@@ -19,6 +19,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
19
19
|
const boundaryState = new WeakMap();
|
|
20
20
|
const renderingBoundaries = new WeakSet();
|
|
21
21
|
const inlineBindings = new Map();
|
|
22
|
+
const scopedCleanups = new WeakMap();
|
|
22
23
|
let inlineBindingCounter = 0;
|
|
23
24
|
let destroyed = false;
|
|
24
25
|
|
|
@@ -53,6 +54,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
53
54
|
if (!boundary) {
|
|
54
55
|
throw new Error(`Boundary "${boundaryId}" was not found.`);
|
|
55
56
|
}
|
|
57
|
+
cleanupChildren(boundary);
|
|
56
58
|
boundary.replaceChildren(toFragment(fragmentOrTemplate, documentRef));
|
|
57
59
|
api.scan(boundary);
|
|
58
60
|
return boundary;
|
|
@@ -69,11 +71,12 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
69
71
|
cache: api.cache,
|
|
70
72
|
attributes: attributeConfig
|
|
71
73
|
});
|
|
74
|
+
cleanupChildren(target);
|
|
72
75
|
target.replaceChildren(toFragment(rendered.html, target.ownerDocument));
|
|
73
76
|
api.scan(target);
|
|
74
77
|
rendered.mount(target);
|
|
75
78
|
rendered.visible(target, api._observeVisible);
|
|
76
|
-
|
|
79
|
+
addCleanup(rendered.cleanup, target, "children");
|
|
77
80
|
return rendered;
|
|
78
81
|
},
|
|
79
82
|
|
|
@@ -83,7 +86,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
83
86
|
}
|
|
84
87
|
destroyed = true;
|
|
85
88
|
for (const cleanup of [...cleanups]) {
|
|
86
|
-
cleanup
|
|
89
|
+
runCleanup(cleanup);
|
|
87
90
|
}
|
|
88
91
|
cleanups.clear();
|
|
89
92
|
},
|
|
@@ -104,6 +107,13 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
104
107
|
};
|
|
105
108
|
|
|
106
109
|
signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
|
|
110
|
+
api.server?._setContext?.({
|
|
111
|
+
signals: signalRegistry,
|
|
112
|
+
handlers: handlerRegistry,
|
|
113
|
+
loader: api,
|
|
114
|
+
router: api.router,
|
|
115
|
+
cache: api.cache
|
|
116
|
+
});
|
|
107
117
|
|
|
108
118
|
function bindEventAttributes(scope) {
|
|
109
119
|
for (const element of elementsIn(scope)) {
|
|
@@ -152,7 +162,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
152
162
|
};
|
|
153
163
|
|
|
154
164
|
element.addEventListener(eventName, listener);
|
|
155
|
-
|
|
165
|
+
addCleanup(() => element.removeEventListener(eventName, listener), element);
|
|
156
166
|
}
|
|
157
167
|
|
|
158
168
|
function bindSignalAttributes(scope) {
|
|
@@ -187,6 +197,12 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
187
197
|
bindSignal(element, `attr:${attr}:${path}`, path, (value) => updateAttribute(element, attr, value));
|
|
188
198
|
continue;
|
|
189
199
|
}
|
|
200
|
+
if (signalName.startsWith("prop:")) {
|
|
201
|
+
const prop = signalName.slice("prop:".length);
|
|
202
|
+
const path = element.getAttribute(name);
|
|
203
|
+
bindSignal(element, `prop:${prop}:${path}`, path, (value) => updateProperty(element, prop, value));
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
190
206
|
if (signalName.startsWith("class:")) {
|
|
191
207
|
const className = signalName.slice("class:".length);
|
|
192
208
|
const path = element.getAttribute(name);
|
|
@@ -255,7 +271,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
255
271
|
|
|
256
272
|
const read = () => readBinding(path, options);
|
|
257
273
|
apply(read());
|
|
258
|
-
|
|
274
|
+
addCleanup(subscribeBinding(path, () => apply(read())), element);
|
|
259
275
|
}
|
|
260
276
|
|
|
261
277
|
function bindValueWriter(element, path) {
|
|
@@ -319,7 +335,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
319
335
|
cleanup: signalRegistry.subscribe(`${id}.$status`, () => renderBoundary(boundary))
|
|
320
336
|
};
|
|
321
337
|
boundaryState.set(boundary, state);
|
|
322
|
-
|
|
338
|
+
addCleanup(state.cleanup, boundary);
|
|
323
339
|
}
|
|
324
340
|
renderBoundary(boundary);
|
|
325
341
|
}
|
|
@@ -335,6 +351,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
335
351
|
if (!template) {
|
|
336
352
|
return;
|
|
337
353
|
}
|
|
354
|
+
cleanupChildren(boundary);
|
|
338
355
|
boundary.replaceChildren(template.content.cloneNode(true));
|
|
339
356
|
renderingBoundaries.add(boundary);
|
|
340
357
|
try {
|
|
@@ -368,7 +385,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
368
385
|
continue;
|
|
369
386
|
}
|
|
370
387
|
visibleElements.add(element);
|
|
371
|
-
|
|
388
|
+
addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
|
|
372
389
|
}
|
|
373
390
|
}
|
|
374
391
|
|
|
@@ -398,7 +415,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
398
415
|
});
|
|
399
416
|
for (const result of results) {
|
|
400
417
|
if (typeof result === "function") {
|
|
401
|
-
|
|
418
|
+
addCleanup(result, element);
|
|
402
419
|
}
|
|
403
420
|
}
|
|
404
421
|
} catch (error) {
|
|
@@ -434,6 +451,63 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
434
451
|
}
|
|
435
452
|
}
|
|
436
453
|
|
|
454
|
+
function addCleanup(cleanup, owner, mode = "self") {
|
|
455
|
+
if (typeof cleanup !== "function") {
|
|
456
|
+
return cleanup;
|
|
457
|
+
}
|
|
458
|
+
cleanups.add(cleanup);
|
|
459
|
+
if (owner) {
|
|
460
|
+
const records = scopedCleanups.get(owner) ?? [];
|
|
461
|
+
records.push({ cleanup, mode });
|
|
462
|
+
scopedCleanups.set(owner, records);
|
|
463
|
+
}
|
|
464
|
+
return cleanup;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function runCleanup(cleanup) {
|
|
468
|
+
if (typeof cleanup !== "function" || !cleanups.has(cleanup)) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
cleanups.delete(cleanup);
|
|
472
|
+
cleanup();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function cleanupChildren(container) {
|
|
476
|
+
runScopedCleanups(container, "children");
|
|
477
|
+
for (const child of [...(container.childNodes ?? [])]) {
|
|
478
|
+
cleanupNode(child);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function cleanupNode(node) {
|
|
483
|
+
if (node.nodeType !== 1) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
for (const element of elementsIn(node)) {
|
|
487
|
+
runScopedCleanups(element);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function runScopedCleanups(element, mode) {
|
|
492
|
+
const records = scopedCleanups.get(element);
|
|
493
|
+
if (!records) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const remaining = [];
|
|
497
|
+
for (const record of records) {
|
|
498
|
+
if (mode && record.mode !== mode) {
|
|
499
|
+
remaining.push(record);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
runCleanup(record.cleanup);
|
|
503
|
+
}
|
|
504
|
+
if (remaining.length > 0) {
|
|
505
|
+
scopedCleanups.set(element, remaining);
|
|
506
|
+
} else {
|
|
507
|
+
scopedCleanups.delete(element);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
437
511
|
return api;
|
|
438
512
|
}
|
|
439
513
|
|
|
@@ -581,6 +655,14 @@ function updateAttribute(element, attr, value) {
|
|
|
581
655
|
}
|
|
582
656
|
}
|
|
583
657
|
|
|
658
|
+
function updateProperty(element, prop, value) {
|
|
659
|
+
if (value == null) {
|
|
660
|
+
element[prop] = "";
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
element[prop] = value;
|
|
664
|
+
}
|
|
665
|
+
|
|
584
666
|
function selectAll(scope, selector) {
|
|
585
667
|
const elements = [];
|
|
586
668
|
if (scope?.nodeType === 1 && scope.matches?.(selector)) {
|
package/src/partials.js
CHANGED
package/src/registry-store.js
CHANGED
package/src/router.js
CHANGED
|
@@ -41,6 +41,15 @@ export function createRouteRegistry(initialMap = {}, options = {}) {
|
|
|
41
41
|
return registry;
|
|
42
42
|
},
|
|
43
43
|
|
|
44
|
+
unregister(pattern) {
|
|
45
|
+
assertPattern(pattern);
|
|
46
|
+
const index = routes.findIndex((candidate) => candidate.pattern === pattern);
|
|
47
|
+
if (index !== -1) {
|
|
48
|
+
routes.splice(index, 1);
|
|
49
|
+
}
|
|
50
|
+
return entries.delete(pattern);
|
|
51
|
+
},
|
|
52
|
+
|
|
44
53
|
match(url) {
|
|
45
54
|
const path = toUrl(url).pathname;
|
|
46
55
|
for (const candidate of routes) {
|
package/src/server.js
CHANGED
|
@@ -28,6 +28,11 @@ export function createServerRegistry(initialMap = {}, options = {}) {
|
|
|
28
28
|
return registry;
|
|
29
29
|
},
|
|
30
30
|
|
|
31
|
+
unregister(id) {
|
|
32
|
+
assertServerId(id);
|
|
33
|
+
return entries.delete(id);
|
|
34
|
+
},
|
|
35
|
+
|
|
31
36
|
resolve(id) {
|
|
32
37
|
assertServerId(id);
|
|
33
38
|
return entries.get(id);
|
|
@@ -43,7 +48,7 @@ export function createServerRegistry(initialMap = {}, options = {}) {
|
|
|
43
48
|
let runContext;
|
|
44
49
|
const server = createServerNamespace((childId, childArgs, childContext = {}) => {
|
|
45
50
|
return registry.run(childId, childArgs, { ...runContext, ...childContext });
|
|
46
|
-
});
|
|
51
|
+
}, {}, () => runContext);
|
|
47
52
|
|
|
48
53
|
const mergedContext = {
|
|
49
54
|
...defaults,
|
|
@@ -76,7 +81,7 @@ export function createServerRegistry(initialMap = {}, options = {}) {
|
|
|
76
81
|
}, registryStore, type);
|
|
77
82
|
|
|
78
83
|
registry.registerMany(initialMap);
|
|
79
|
-
return createServerNamespace((id, args, context) => registry.run(id, args, context), registry);
|
|
84
|
+
return createServerNamespace((id, args, context) => registry.run(id, args, context), registry, () => defaults);
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
export function createServerProxy({
|
|
@@ -127,7 +132,7 @@ export function createServerProxy({
|
|
|
127
132
|
_setContext(context = {}) {
|
|
128
133
|
Object.assign(defaults, context);
|
|
129
134
|
}
|
|
130
|
-
});
|
|
135
|
+
}, () => defaults);
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
export function resolveServerCommandArguments(args, context = {}) {
|
|
@@ -205,7 +210,7 @@ export function defaultInput(context = {}) {
|
|
|
205
210
|
};
|
|
206
211
|
}
|
|
207
212
|
|
|
208
|
-
function createServerNamespace(run, root = {}) {
|
|
213
|
+
function createServerNamespace(run, root = {}, contextProvider = () => ({})) {
|
|
209
214
|
const cache = new Map();
|
|
210
215
|
|
|
211
216
|
function namespace(parts) {
|
|
@@ -214,11 +219,14 @@ function createServerNamespace(run, root = {}) {
|
|
|
214
219
|
return cache.get(cacheKey);
|
|
215
220
|
}
|
|
216
221
|
|
|
217
|
-
const callable = (...args) => {
|
|
222
|
+
const callable = async (...args) => {
|
|
218
223
|
if (parts.length === 0) {
|
|
219
224
|
throw new Error("Server namespace is not directly callable.");
|
|
220
225
|
}
|
|
221
|
-
|
|
226
|
+
const context = contextProvider() ?? {};
|
|
227
|
+
const result = await run(parts.join("."), args, context);
|
|
228
|
+
await applyServerResult(result, context);
|
|
229
|
+
return unwrapServerResult(result);
|
|
222
230
|
};
|
|
223
231
|
|
|
224
232
|
const proxy = new Proxy(callable, {
|
|
@@ -229,6 +237,18 @@ function createServerNamespace(run, root = {}) {
|
|
|
229
237
|
if (prop in _target) {
|
|
230
238
|
return _target[prop];
|
|
231
239
|
}
|
|
240
|
+
if (parts.length === 0 && prop === "_withContext") {
|
|
241
|
+
return (context = {}) => createServerNamespace(run, root, () => ({
|
|
242
|
+
...(contextProvider() ?? {}),
|
|
243
|
+
...context
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
if (parts.length === 0 && prop === "run" && typeof root.run === "function") {
|
|
247
|
+
return (id, args = [], context = {}) => root.run(id, args, {
|
|
248
|
+
...(contextProvider() ?? {}),
|
|
249
|
+
...context
|
|
250
|
+
});
|
|
251
|
+
}
|
|
232
252
|
if (parts.length === 0 && Object.hasOwn(root, prop)) {
|
|
233
253
|
return root[prop];
|
|
234
254
|
}
|
package/src/signals.js
CHANGED
|
@@ -121,7 +121,7 @@ export function createSignalRegistry(initialMap = {}, options = {}) {
|
|
|
121
121
|
const registryStore = options.registry ?? createRegistryStore();
|
|
122
122
|
const type = options.type ?? "signal";
|
|
123
123
|
const entries = registryStore._map(type);
|
|
124
|
-
const registryCleanups = new
|
|
124
|
+
const registryCleanups = new Map();
|
|
125
125
|
const runtimeContext = {};
|
|
126
126
|
const boundEntries = new Set();
|
|
127
127
|
|
|
@@ -144,6 +144,19 @@ export function createSignalRegistry(initialMap = {}, options = {}) {
|
|
|
144
144
|
return registry;
|
|
145
145
|
},
|
|
146
146
|
|
|
147
|
+
unregister(id) {
|
|
148
|
+
assertId(id);
|
|
149
|
+
if (!entries.has(id)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
registryCleanups.get(id)?.();
|
|
153
|
+
registryCleanups.delete(id);
|
|
154
|
+
entries.get(id)?._dispose?.();
|
|
155
|
+
entries.delete(id);
|
|
156
|
+
boundEntries.delete(id);
|
|
157
|
+
return true;
|
|
158
|
+
},
|
|
159
|
+
|
|
147
160
|
ensure(id, initial) {
|
|
148
161
|
assertId(id);
|
|
149
162
|
if (!entries.has(id)) {
|
|
@@ -256,7 +269,7 @@ export function createSignalRegistry(initialMap = {}, options = {}) {
|
|
|
256
269
|
},
|
|
257
270
|
|
|
258
271
|
destroy() {
|
|
259
|
-
for (const cleanup of registryCleanups) {
|
|
272
|
+
for (const cleanup of registryCleanups.values()) {
|
|
260
273
|
cleanup();
|
|
261
274
|
}
|
|
262
275
|
registryCleanups.clear();
|
|
@@ -313,7 +326,7 @@ export function createSignalRegistry(initialMap = {}, options = {}) {
|
|
|
313
326
|
boundEntries.add(id);
|
|
314
327
|
const cleanup = entry._bindRegistry(registry, id);
|
|
315
328
|
if (typeof cleanup === "function") {
|
|
316
|
-
registryCleanups.
|
|
329
|
+
registryCleanups.set(id, cleanup);
|
|
317
330
|
}
|
|
318
331
|
}
|
|
319
332
|
}
|