@async/framework 0.2.2 → 0.3.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 +13 -0
- package/README.md +153 -24
- package/examples/cache/index.html +3 -3
- package/examples/components/main.js +2 -2
- package/examples/counter/index.html +2 -2
- package/examples/partials/index.html +2 -2
- package/examples/product/index.html +9 -9
- package/examples/router/index.html +2 -2
- package/examples/router/main.js +2 -2
- package/examples/server-call/index.html +2 -2
- package/examples/ssr/index.html +1 -1
- package/examples/ssr/main.js +2 -2
- package/examples/streaming/index.html +2 -2
- package/examples/streaming/main.js +2 -2
- package/package.json +9 -2
- package/src/app.js +73 -53
- package/src/attributes.js +50 -0
- package/src/cache.js +31 -16
- package/src/component.js +11 -4
- package/src/handlers.js +24 -5
- package/src/index.js +2 -0
- package/src/loader.js +71 -45
- package/src/partials.js +11 -4
- package/src/registry-store.js +257 -0
- package/src/router.js +42 -3
- package/src/server.js +12 -4
- package/src/signals.js +32 -10
package/src/loader.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { renderComponent } from "./component.js";
|
|
2
2
|
import { createHandlerRegistry } from "./handlers.js";
|
|
3
3
|
import { createSignalRegistry } from "./signals.js";
|
|
4
|
+
import { matchAttribute, normalizeAttributeConfig, readAttribute } from "./attributes.js";
|
|
4
5
|
|
|
5
|
-
export function AsyncLoader({ root, signals, handlers, server, router, cache } = {}) {
|
|
6
|
+
export function AsyncLoader({ root, signals, handlers, server, router, cache, attributes } = {}) {
|
|
6
7
|
const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
|
|
7
8
|
const rootNode = root ?? documentRef;
|
|
8
9
|
const signalRegistry = signals ?? createSignalRegistry();
|
|
9
10
|
const handlerRegistry = handlers ?? createHandlerRegistry();
|
|
11
|
+
const attributeConfig = normalizeAttributeConfig(attributes);
|
|
10
12
|
const cleanups = new Set();
|
|
11
13
|
const eventBindings = new WeakMap();
|
|
12
14
|
const signalBindings = new WeakMap();
|
|
@@ -23,6 +25,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache } =
|
|
|
23
25
|
server,
|
|
24
26
|
router,
|
|
25
27
|
cache,
|
|
28
|
+
attributes: attributeConfig,
|
|
26
29
|
|
|
27
30
|
start() {
|
|
28
31
|
assertActive();
|
|
@@ -41,7 +44,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache } =
|
|
|
41
44
|
|
|
42
45
|
swap(boundaryId, fragmentOrTemplate) {
|
|
43
46
|
assertActive();
|
|
44
|
-
const boundary = findBoundary(rootNode, boundaryId);
|
|
47
|
+
const boundary = findBoundary(rootNode, boundaryId, attributeConfig);
|
|
45
48
|
if (!boundary) {
|
|
46
49
|
throw new Error(`Boundary "${boundaryId}" was not found.`);
|
|
47
50
|
}
|
|
@@ -58,7 +61,8 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache } =
|
|
|
58
61
|
loader: api,
|
|
59
62
|
server: api.server,
|
|
60
63
|
router: api.router,
|
|
61
|
-
cache: api.cache
|
|
64
|
+
cache: api.cache,
|
|
65
|
+
attributes: attributeConfig
|
|
62
66
|
});
|
|
63
67
|
target.replaceChildren(toFragment(rendered.html, target.ownerDocument));
|
|
64
68
|
api.scan(target);
|
|
@@ -87,15 +91,15 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache } =
|
|
|
87
91
|
signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
|
|
88
92
|
|
|
89
93
|
function bindEventAttributes(scope) {
|
|
90
|
-
for (const element of
|
|
94
|
+
for (const element of elementsIn(scope)) {
|
|
91
95
|
if (typeof element.getAttributeNames !== "function") {
|
|
92
96
|
continue;
|
|
93
97
|
}
|
|
94
98
|
for (const name of element.getAttributeNames()) {
|
|
95
|
-
|
|
99
|
+
const eventName = matchAttribute(name, attributeConfig, "on");
|
|
100
|
+
if (!eventName) {
|
|
96
101
|
continue;
|
|
97
102
|
}
|
|
98
|
-
const eventName = name.slice(3);
|
|
99
103
|
if (eventName === "mount" || eventName === "visible") {
|
|
100
104
|
continue;
|
|
101
105
|
}
|
|
@@ -137,33 +141,39 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache } =
|
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
function bindSignalAttributes(scope) {
|
|
140
|
-
for (const element of
|
|
141
|
-
bindSignal(element, `text:${element.getAttribute("data-async-text")}`, element.getAttribute("data-async-text"), (value) => {
|
|
142
|
-
element.textContent = value ?? "";
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
for (const element of selectAll(scope, "[data-async-value]")) {
|
|
147
|
-
const path = element.getAttribute("data-async-value");
|
|
148
|
-
bindSignal(element, `value:${path}`, path, (value) => {
|
|
149
|
-
if ("value" in element && element.value !== String(value ?? "")) {
|
|
150
|
-
element.value = value ?? "";
|
|
151
|
-
} else if (!("value" in element)) {
|
|
152
|
-
element.setAttribute("value", value ?? "");
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
bindValueWriter(element, path);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
for (const element of selectAll(scope, "*")) {
|
|
144
|
+
for (const element of elementsIn(scope)) {
|
|
159
145
|
for (const name of element.getAttributeNames?.() ?? []) {
|
|
160
|
-
|
|
161
|
-
|
|
146
|
+
const signalName = matchAttribute(name, attributeConfig, "signal");
|
|
147
|
+
if (!signalName) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (signalName === "text") {
|
|
151
|
+
const path = element.getAttribute(name);
|
|
152
|
+
bindSignal(element, `text:${path}`, path, (value) => {
|
|
153
|
+
element.textContent = value ?? "";
|
|
154
|
+
});
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (signalName === "value") {
|
|
158
|
+
const path = element.getAttribute(name);
|
|
159
|
+
bindSignal(element, `value:${path}`, path, (value) => {
|
|
160
|
+
if ("value" in element && element.value !== String(value ?? "")) {
|
|
161
|
+
element.value = value ?? "";
|
|
162
|
+
} else if (!("value" in element)) {
|
|
163
|
+
element.setAttribute("value", value ?? "");
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
bindValueWriter(element, path);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (signalName.startsWith("attr:")) {
|
|
170
|
+
const attr = signalName.slice("attr:".length);
|
|
162
171
|
const path = element.getAttribute(name);
|
|
163
172
|
bindSignal(element, `attr:${attr}:${path}`, path, (value) => updateAttribute(element, attr, value));
|
|
173
|
+
continue;
|
|
164
174
|
}
|
|
165
|
-
if (
|
|
166
|
-
const className =
|
|
175
|
+
if (signalName.startsWith("class:")) {
|
|
176
|
+
const className = signalName.slice("class:".length);
|
|
167
177
|
const path = element.getAttribute(name);
|
|
168
178
|
bindSignal(element, `class:${className}:${path}`, path, (value) => {
|
|
169
179
|
element.classList.toggle(className, Boolean(value));
|
|
@@ -196,13 +206,16 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache } =
|
|
|
196
206
|
}
|
|
197
207
|
|
|
198
208
|
function bindBoundaries(scope) {
|
|
199
|
-
for (const boundary of
|
|
209
|
+
for (const boundary of elementsIn(scope)) {
|
|
200
210
|
if (renderingBoundaries.has(boundary)) {
|
|
201
211
|
continue;
|
|
202
212
|
}
|
|
203
|
-
const id = boundary
|
|
213
|
+
const id = readAttribute(boundary, attributeConfig, "async", "boundary");
|
|
214
|
+
if (id == null) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
204
217
|
if (!boundaryState.has(boundary)) {
|
|
205
|
-
const templates = collectBoundaryTemplates(boundary, id);
|
|
218
|
+
const templates = collectBoundaryTemplates(boundary, id, attributeConfig);
|
|
206
219
|
if (Object.keys(templates).length === 0 || !signalRegistry.has(id)) {
|
|
207
220
|
continue;
|
|
208
221
|
}
|
|
@@ -238,20 +251,28 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache } =
|
|
|
238
251
|
}
|
|
239
252
|
|
|
240
253
|
function runPseudoEvents(scope) {
|
|
241
|
-
for (const element of
|
|
254
|
+
for (const element of elementsIn(scope)) {
|
|
255
|
+
const ref = readAttribute(element, attributeConfig, "on", "mount");
|
|
256
|
+
if (ref == null) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
242
259
|
if (mountedElements.has(element)) {
|
|
243
260
|
continue;
|
|
244
261
|
}
|
|
245
262
|
mountedElements.add(element);
|
|
246
|
-
runPseudo(element,
|
|
263
|
+
runPseudo(element, ref);
|
|
247
264
|
}
|
|
248
265
|
|
|
249
|
-
for (const element of
|
|
266
|
+
for (const element of elementsIn(scope)) {
|
|
267
|
+
const ref = readAttribute(element, attributeConfig, "on", "visible");
|
|
268
|
+
if (ref == null) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
250
271
|
if (visibleElements.has(element)) {
|
|
251
272
|
continue;
|
|
252
273
|
}
|
|
253
274
|
visibleElements.add(element);
|
|
254
|
-
cleanups.add(observeVisible(element, () => runPseudo(element,
|
|
275
|
+
cleanups.add(observeVisible(element, () => runPseudo(element, ref)));
|
|
255
276
|
}
|
|
256
277
|
}
|
|
257
278
|
|
|
@@ -309,16 +330,16 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache } =
|
|
|
309
330
|
return api;
|
|
310
331
|
}
|
|
311
332
|
|
|
312
|
-
function collectBoundaryTemplates(boundary, id) {
|
|
333
|
+
function collectBoundaryTemplates(boundary, id, attributeConfig) {
|
|
313
334
|
const templates = {};
|
|
314
335
|
for (const template of [...boundary.children].filter((child) => child.tagName === "TEMPLATE")) {
|
|
315
|
-
if (template
|
|
336
|
+
if (readAttribute(template, attributeConfig, "async", "loading") === id) {
|
|
316
337
|
templates.loading = template;
|
|
317
338
|
}
|
|
318
|
-
if (template
|
|
339
|
+
if (readAttribute(template, attributeConfig, "async", "ready") === id) {
|
|
319
340
|
templates.ready = template;
|
|
320
341
|
}
|
|
321
|
-
if (template
|
|
342
|
+
if (readAttribute(template, attributeConfig, "async", "error") === id) {
|
|
322
343
|
templates.error = template;
|
|
323
344
|
}
|
|
324
345
|
}
|
|
@@ -358,12 +379,17 @@ function selectAll(scope, selector) {
|
|
|
358
379
|
return elements;
|
|
359
380
|
}
|
|
360
381
|
|
|
361
|
-
function
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
382
|
+
function elementsIn(scope) {
|
|
383
|
+
return selectAll(scope, "*");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function findBoundary(root, boundaryId, attributeConfig) {
|
|
387
|
+
for (const element of elementsIn(root)) {
|
|
388
|
+
if (readAttribute(element, attributeConfig, "async", "boundary") === String(boundaryId)) {
|
|
389
|
+
return element;
|
|
390
|
+
}
|
|
365
391
|
}
|
|
366
|
-
return
|
|
392
|
+
return null;
|
|
367
393
|
}
|
|
368
394
|
|
|
369
395
|
function toFragment(value, documentRef) {
|
package/src/partials.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { isTemplateResult, renderTemplate } from "./html.js";
|
|
2
|
+
import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
|
|
2
3
|
|
|
3
|
-
export function createPartialRegistry(initialMap = {}) {
|
|
4
|
-
const
|
|
4
|
+
export function createPartialRegistry(initialMap = {}, options = {}) {
|
|
5
|
+
const registryStore = options.registry ?? createRegistryStore();
|
|
6
|
+
const type = options.type ?? "partial";
|
|
7
|
+
const entries = registryStore._map(type);
|
|
5
8
|
|
|
6
|
-
const registry = {
|
|
9
|
+
const registry = attachRegistryInspection({
|
|
7
10
|
register(id, fn) {
|
|
8
11
|
assertId(id);
|
|
9
12
|
if (typeof fn !== "function") {
|
|
@@ -44,8 +47,12 @@ export function createPartialRegistry(initialMap = {}) {
|
|
|
44
47
|
};
|
|
45
48
|
const result = await fn.call(partialContext, props);
|
|
46
49
|
return normalizePartialResult(result);
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
_adoptMany() {
|
|
53
|
+
return registry;
|
|
47
54
|
}
|
|
48
|
-
};
|
|
55
|
+
}, registryStore, type);
|
|
49
56
|
|
|
50
57
|
registry.registerMany(initialMap);
|
|
51
58
|
return registry;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
const declarationTypes = new Set(["signal", "handler", "server", "partial", "route", "component"]);
|
|
2
|
+
const cacheTypes = new Set(["cache.browser", "cache.server"]);
|
|
3
|
+
const cacheEntryTypes = new Set(["cache.browser.entries", "cache.server.entries"]);
|
|
4
|
+
const allTypes = new Set([...declarationTypes, ...cacheTypes, ...cacheEntryTypes]);
|
|
5
|
+
|
|
6
|
+
export function createRegistryStore(initial = {}, options = {}) {
|
|
7
|
+
const backing = options.backing ?? createBacking();
|
|
8
|
+
const target = options.target ?? "server";
|
|
9
|
+
|
|
10
|
+
const registry = {
|
|
11
|
+
target,
|
|
12
|
+
|
|
13
|
+
register(type, id, value) {
|
|
14
|
+
const map = registry._map(type);
|
|
15
|
+
assertId(type, id);
|
|
16
|
+
if (map.has(id)) {
|
|
17
|
+
throw new Error(`${type} "${id}" is already registered.`);
|
|
18
|
+
}
|
|
19
|
+
map.set(id, value);
|
|
20
|
+
return id;
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
registerMany(type, map = {}) {
|
|
24
|
+
for (const [id, value] of Object.entries(map ?? {})) {
|
|
25
|
+
registry.register(type, id, value);
|
|
26
|
+
}
|
|
27
|
+
return registry;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
set(type, id, value) {
|
|
31
|
+
const map = registry._map(type);
|
|
32
|
+
assertId(type, id);
|
|
33
|
+
map.set(id, value);
|
|
34
|
+
return value;
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
delete(type, id) {
|
|
38
|
+
return registry._map(type).delete(id);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
keys(type) {
|
|
42
|
+
if (isHiddenInBrowser(type, target)) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return [...registry._map(type).keys()];
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
entries(type, entryOptions = {}) {
|
|
49
|
+
const normalized = normalizeType(type);
|
|
50
|
+
if (isHiddenInBrowser(normalized, entryOptions.target ?? target)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
return [...registry._map(normalized)].map(([id, value]) => [
|
|
54
|
+
id,
|
|
55
|
+
publicValue(normalized, id, value, { target, ...entryOptions })
|
|
56
|
+
]);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
has(type, id) {
|
|
60
|
+
assertId(type, id);
|
|
61
|
+
if (isHiddenInBrowser(type, target)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return registry._map(type).has(id);
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
get(type, id, getOptions = {}) {
|
|
68
|
+
assertId(type, id);
|
|
69
|
+
const normalized = normalizeType(type);
|
|
70
|
+
if (isHiddenInBrowser(normalized, getOptions.target ?? target)) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const value = registry._map(normalized).get(id);
|
|
74
|
+
if (value === undefined) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return publicValue(normalized, id, value, { target, ...getOptions });
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
snapshot(snapshotOptions = {}) {
|
|
81
|
+
const snapshotTarget = snapshotOptions.target ?? target;
|
|
82
|
+
return {
|
|
83
|
+
signal: snapshotSignals(backing.signal),
|
|
84
|
+
handler: snapshotDescriptors(backing.handler, "handler"),
|
|
85
|
+
server: snapshotDescriptors(backing.server, "server"),
|
|
86
|
+
partial: snapshotDescriptors(backing.partial, "partial"),
|
|
87
|
+
route: snapshotPlain(backing.route),
|
|
88
|
+
component: snapshotDescriptors(backing.component, "component"),
|
|
89
|
+
cache: {
|
|
90
|
+
browser: snapshotPlain(backing.cache.browser),
|
|
91
|
+
server: snapshotPlain(backing.cache.server)
|
|
92
|
+
},
|
|
93
|
+
entries: {
|
|
94
|
+
browser: snapshotCacheEntries(backing.cacheEntries.browser),
|
|
95
|
+
server: snapshotTarget === "browser" ? {} : snapshotCacheEntries(backing.cacheEntries.server)
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
rawSnapshot() {
|
|
101
|
+
return {
|
|
102
|
+
signal: Object.fromEntries(backing.signal),
|
|
103
|
+
handler: Object.fromEntries(backing.handler),
|
|
104
|
+
server: Object.fromEntries(backing.server),
|
|
105
|
+
partial: Object.fromEntries(backing.partial),
|
|
106
|
+
route: Object.fromEntries(backing.route),
|
|
107
|
+
component: Object.fromEntries(backing.component),
|
|
108
|
+
cache: {
|
|
109
|
+
browser: Object.fromEntries(backing.cache.browser),
|
|
110
|
+
server: Object.fromEntries(backing.cache.server)
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
view(viewOptions = {}) {
|
|
116
|
+
return createRegistryStore(undefined, {
|
|
117
|
+
backing,
|
|
118
|
+
target: viewOptions.target ?? target
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
_map(type) {
|
|
123
|
+
const normalized = normalizeType(type);
|
|
124
|
+
if (declarationTypes.has(normalized)) {
|
|
125
|
+
return backing[normalized];
|
|
126
|
+
}
|
|
127
|
+
if (normalized === "cache.browser") {
|
|
128
|
+
return backing.cache.browser;
|
|
129
|
+
}
|
|
130
|
+
if (normalized === "cache.server") {
|
|
131
|
+
return backing.cache.server;
|
|
132
|
+
}
|
|
133
|
+
if (normalized === "cache.browser.entries") {
|
|
134
|
+
return backing.cacheEntries.browser;
|
|
135
|
+
}
|
|
136
|
+
if (normalized === "cache.server.entries") {
|
|
137
|
+
return backing.cacheEntries.server;
|
|
138
|
+
}
|
|
139
|
+
throw new Error(`Unknown Async registry type "${type}".`);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
applyInitial(registry, initial);
|
|
144
|
+
return registry;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function attachRegistryInspection(target, registry, type) {
|
|
148
|
+
Object.defineProperty(target, "registry", {
|
|
149
|
+
configurable: true,
|
|
150
|
+
enumerable: true,
|
|
151
|
+
value: registry
|
|
152
|
+
});
|
|
153
|
+
target.keys = () => registry.keys(type);
|
|
154
|
+
target.entries = () => registry.entries(type);
|
|
155
|
+
target.inspect = () => registry.entries(type);
|
|
156
|
+
return target;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function createBacking() {
|
|
160
|
+
return {
|
|
161
|
+
signal: new Map(),
|
|
162
|
+
handler: new Map(),
|
|
163
|
+
server: new Map(),
|
|
164
|
+
partial: new Map(),
|
|
165
|
+
route: new Map(),
|
|
166
|
+
component: new Map(),
|
|
167
|
+
cache: {
|
|
168
|
+
browser: new Map(),
|
|
169
|
+
server: new Map()
|
|
170
|
+
},
|
|
171
|
+
cacheEntries: {
|
|
172
|
+
browser: new Map(),
|
|
173
|
+
server: new Map()
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function applyInitial(registry, initial = {}) {
|
|
179
|
+
registry.registerMany("signal", initial.signal);
|
|
180
|
+
registry.registerMany("handler", initial.handler);
|
|
181
|
+
registry.registerMany("server", initial.server);
|
|
182
|
+
registry.registerMany("partial", initial.partial);
|
|
183
|
+
registry.registerMany("route", initial.route);
|
|
184
|
+
registry.registerMany("component", initial.component);
|
|
185
|
+
registry.registerMany("cache.browser", initial.cache?.browser);
|
|
186
|
+
registry.registerMany("cache.server", initial.cache?.server);
|
|
187
|
+
|
|
188
|
+
const entries = initial.entries ?? {};
|
|
189
|
+
for (const [key, value] of Object.entries(entries.browser ?? {})) {
|
|
190
|
+
registry.set("cache.browser.entries", key, cacheEntry(value));
|
|
191
|
+
}
|
|
192
|
+
for (const [key, value] of Object.entries(entries.server ?? {})) {
|
|
193
|
+
registry.set("cache.server.entries", key, cacheEntry(value));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function normalizeType(type) {
|
|
198
|
+
if (!allTypes.has(type)) {
|
|
199
|
+
throw new Error(`Unknown Async registry type "${type}".`);
|
|
200
|
+
}
|
|
201
|
+
return type;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function assertId(type, id) {
|
|
205
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
206
|
+
throw new TypeError(`${type} id must be a non-empty string.`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function publicValue(type, id, value, options) {
|
|
211
|
+
if (type === "server" && options.target === "browser") {
|
|
212
|
+
return { id, kind: "server" };
|
|
213
|
+
}
|
|
214
|
+
if (cacheEntryTypes.has(type)) {
|
|
215
|
+
return value?.value;
|
|
216
|
+
}
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isHiddenInBrowser(type, target) {
|
|
221
|
+
return type === "cache.server.entries" && target === "browser";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function snapshotSignals(map) {
|
|
225
|
+
const snapshot = {};
|
|
226
|
+
for (const [id, entry] of map) {
|
|
227
|
+
snapshot[id] = typeof entry?.snapshot === "function" ? entry.snapshot() : entry?.value ?? entry;
|
|
228
|
+
}
|
|
229
|
+
return snapshot;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function snapshotDescriptors(map, kind) {
|
|
233
|
+
const snapshot = {};
|
|
234
|
+
for (const id of map.keys()) {
|
|
235
|
+
snapshot[id] = { id, kind };
|
|
236
|
+
}
|
|
237
|
+
return snapshot;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function snapshotPlain(map) {
|
|
241
|
+
return Object.fromEntries(map);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function snapshotCacheEntries(map) {
|
|
245
|
+
const snapshot = {};
|
|
246
|
+
for (const [id, entry] of map) {
|
|
247
|
+
snapshot[id] = entry?.value;
|
|
248
|
+
}
|
|
249
|
+
return snapshot;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function cacheEntry(value) {
|
|
253
|
+
if (value && typeof value === "object" && Object.hasOwn(value, "value")) {
|
|
254
|
+
return value;
|
|
255
|
+
}
|
|
256
|
+
return { value };
|
|
257
|
+
}
|
package/src/router.js
CHANGED
|
@@ -2,6 +2,8 @@ import { AsyncLoader } from "./loader.js";
|
|
|
2
2
|
import { createHandlerRegistry } from "./handlers.js";
|
|
3
3
|
import { createSignalRegistry } from "./signals.js";
|
|
4
4
|
import { applyServerResult } from "./server.js";
|
|
5
|
+
import { createRegistryStore } from "./registry-store.js";
|
|
6
|
+
import { normalizeAttributeConfig } from "./attributes.js";
|
|
5
7
|
|
|
6
8
|
export function defineRoute(partial, options = {}) {
|
|
7
9
|
return {
|
|
@@ -12,16 +14,22 @@ export function defineRoute(partial, options = {}) {
|
|
|
12
14
|
|
|
13
15
|
export const route = defineRoute;
|
|
14
16
|
|
|
15
|
-
export function createRouteRegistry(initialMap = {}) {
|
|
17
|
+
export function createRouteRegistry(initialMap = {}, options = {}) {
|
|
18
|
+
const registryStore = options.registry ?? createRegistryStore();
|
|
19
|
+
const type = options.type ?? "route";
|
|
20
|
+
const entries = registryStore._map(type);
|
|
16
21
|
const routes = [];
|
|
17
22
|
|
|
18
23
|
const registry = {
|
|
24
|
+
registry: registryStore,
|
|
25
|
+
|
|
19
26
|
register(pattern, definition) {
|
|
20
27
|
assertPattern(pattern);
|
|
21
28
|
if (routes.some((candidate) => candidate.pattern === pattern)) {
|
|
22
29
|
throw new Error(`Route "${pattern}" is already registered.`);
|
|
23
30
|
}
|
|
24
31
|
const nextRoute = normalizeRoute(pattern, definition);
|
|
32
|
+
entries.set(pattern, nextRoute.definition);
|
|
25
33
|
routes.push(nextRoute);
|
|
26
34
|
return nextRoute;
|
|
27
35
|
},
|
|
@@ -55,11 +63,38 @@ export function createRouteRegistry(initialMap = {}) {
|
|
|
55
63
|
|
|
56
64
|
entries() {
|
|
57
65
|
return routes.map(({ pattern, definition }) => ({ pattern, route: definition }));
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
keys() {
|
|
69
|
+
return [...entries.keys()];
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
inspect() {
|
|
73
|
+
return registryStore.entries(type);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
_adoptMany(map = {}) {
|
|
77
|
+
for (const pattern of Object.keys(map ?? {})) {
|
|
78
|
+
adoptRoute(pattern, entries.get(pattern));
|
|
79
|
+
}
|
|
80
|
+
return registry;
|
|
58
81
|
}
|
|
59
82
|
};
|
|
60
83
|
|
|
84
|
+
for (const [pattern, definition] of entries) {
|
|
85
|
+
adoptRoute(pattern, definition);
|
|
86
|
+
}
|
|
61
87
|
registry.registerMany(initialMap);
|
|
62
88
|
return registry;
|
|
89
|
+
|
|
90
|
+
function adoptRoute(pattern, definition) {
|
|
91
|
+
if (routes.some((candidate) => candidate.pattern === pattern)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const nextRoute = normalizeRoute(pattern, definition);
|
|
95
|
+
entries.set(pattern, nextRoute.definition);
|
|
96
|
+
routes.push(nextRoute);
|
|
97
|
+
}
|
|
63
98
|
}
|
|
64
99
|
|
|
65
100
|
export function createRouter({
|
|
@@ -74,12 +109,14 @@ export function createRouter({
|
|
|
74
109
|
cache,
|
|
75
110
|
partials,
|
|
76
111
|
fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
77
|
-
routeEndpoint = "/__async/route"
|
|
112
|
+
routeEndpoint = "/__async/route",
|
|
113
|
+
attributes
|
|
78
114
|
} = {}) {
|
|
79
115
|
const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
|
|
80
116
|
const rootNode = root ?? documentRef;
|
|
81
117
|
const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
|
|
82
118
|
const handlerRegistry = handlers ?? loader?.handlers ?? createHandlerRegistry();
|
|
119
|
+
const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
|
|
83
120
|
const loaderInstance =
|
|
84
121
|
loader ??
|
|
85
122
|
AsyncLoader({
|
|
@@ -87,7 +124,8 @@ export function createRouter({
|
|
|
87
124
|
signals: signalRegistry,
|
|
88
125
|
handlers: handlerRegistry,
|
|
89
126
|
server,
|
|
90
|
-
cache
|
|
127
|
+
cache,
|
|
128
|
+
attributes: attributeConfig
|
|
91
129
|
});
|
|
92
130
|
const ownsLoader = !loader;
|
|
93
131
|
const cleanups = new Set();
|
|
@@ -104,6 +142,7 @@ export function createRouter({
|
|
|
104
142
|
server,
|
|
105
143
|
cache,
|
|
106
144
|
partials,
|
|
145
|
+
attributes: attributeConfig,
|
|
107
146
|
|
|
108
147
|
start() {
|
|
109
148
|
assertActive();
|
package/src/server.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
|
|
2
|
+
|
|
1
3
|
const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
|
|
2
4
|
|
|
3
|
-
export function createServerRegistry(initialMap = {}) {
|
|
4
|
-
const
|
|
5
|
+
export function createServerRegistry(initialMap = {}, options = {}) {
|
|
6
|
+
const registryStore = options.registry ?? createRegistryStore();
|
|
7
|
+
const type = options.type ?? "server";
|
|
8
|
+
const entries = registryStore._map(type);
|
|
5
9
|
const defaults = {};
|
|
6
10
|
|
|
7
|
-
const registry = {
|
|
11
|
+
const registry = attachRegistryInspection({
|
|
8
12
|
register(id, fn) {
|
|
9
13
|
assertServerId(id);
|
|
10
14
|
if (typeof fn !== "function") {
|
|
@@ -64,8 +68,12 @@ export function createServerRegistry(initialMap = {}) {
|
|
|
64
68
|
_setContext(context = {}) {
|
|
65
69
|
Object.assign(defaults, context);
|
|
66
70
|
return registry;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
_adoptMany() {
|
|
74
|
+
return registry;
|
|
67
75
|
}
|
|
68
|
-
};
|
|
76
|
+
}, registryStore, type);
|
|
69
77
|
|
|
70
78
|
registry.registerMany(initialMap);
|
|
71
79
|
return createServerNamespace((id, args, context) => registry.run(id, args, context), registry);
|