@async/framework 0.9.0 → 0.10.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/src/browser.js CHANGED
@@ -5,8 +5,10 @@ export { createBoundaryReceiver } from "./boundary-receiver.js";
5
5
  export { createCacheRegistry, defineCache } from "./cache.js";
6
6
  export { component, createComponentRegistry, defineComponent } from "./component.js";
7
7
  export { delay } from "./delay.js";
8
+ export { defineAsyncContainerElement, defineAsyncSuspenseElement } from "./elements.js";
8
9
  export { createHandlerRegistry } from "./handlers.js";
9
10
  export { html } from "./html.js";
11
+ export { createLazyRegistry, defineRegistrySnapshot } from "./lazy-registry.js";
10
12
  export { Loader, AsyncLoader } from "./loader.js";
11
13
  export { createPartialRegistry } from "./partials.js";
12
14
  export { createRegistryStore } from "./registry-store.js";
package/src/component.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { attributeName } from "./attributes.js";
2
2
  import { escapeHtml, rawHtml, renderTemplate } from "./html.js";
3
3
  import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
4
+ import { createLazyRegistry, isLazyDescriptor } from "./lazy-registry.js";
4
5
 
5
6
  const componentKind = Symbol.for("@async/framework.component");
6
7
  let componentCounter = 0;
@@ -22,13 +23,15 @@ export function createComponentRegistry(initialMap = {}, options = {}) {
22
23
  const registryStore = options.registry ?? createRegistryStore();
23
24
  const type = options.type ?? "component";
24
25
  const entries = registryStore._map(type);
26
+ const lazyRegistry = options.lazyRegistry ?? createLazyRegistry(options);
27
+ const lazyComponents = new Map();
25
28
 
26
29
  const registry = attachRegistryInspection({
27
30
  register(id, Component) {
28
31
  if (typeof id !== "string" || id.length === 0) {
29
32
  throw new TypeError("Component id must be a non-empty string.");
30
33
  }
31
- if (!isComponent(Component) && typeof Component !== "function") {
34
+ if (!isComponent(Component) && typeof Component !== "function" && !isLazyDescriptor(Component)) {
32
35
  throw new TypeError(`Component "${id}" must be a component function.`);
33
36
  }
34
37
  if (entries.has(id)) {
@@ -49,6 +52,7 @@ export function createComponentRegistry(initialMap = {}, options = {}) {
49
52
  if (typeof id !== "string" || id.length === 0) {
50
53
  throw new TypeError("Component id must be a non-empty string.");
51
54
  }
55
+ lazyComponents.delete(id);
52
56
  return entries.delete(id);
53
57
  },
54
58
 
@@ -56,7 +60,20 @@ export function createComponentRegistry(initialMap = {}, options = {}) {
56
60
  if (typeof id !== "string" || id.length === 0) {
57
61
  throw new TypeError("Component id must be a non-empty string.");
58
62
  }
59
- return entries.get(id);
63
+ const Component = entries.get(id);
64
+ if (!isLazyDescriptor(Component)) {
65
+ return Component;
66
+ }
67
+ if (!lazyComponents.has(id)) {
68
+ lazyComponents.set(id, async function LazyComponent(...args) {
69
+ const resolved = await lazyRegistry.resolve(type, id, Component);
70
+ if (typeof resolved !== "function") {
71
+ throw new TypeError(`Component "${id}" did not resolve to a function.`);
72
+ }
73
+ return resolved.apply(this, args);
74
+ });
75
+ }
76
+ return lazyComponents.get(id);
60
77
  },
61
78
 
62
79
  _adoptMany() {
@@ -0,0 +1,63 @@
1
+ import { Async } from "./app.js";
2
+
3
+ export function defineAsyncContainerElement(options = {}) {
4
+ const tagName = options.tagName ?? "async-container";
5
+ const registry = options.customElements ?? globalThis.customElements;
6
+ if (!registry) {
7
+ throw new Error("defineAsyncContainerElement(...) requires customElements.");
8
+ }
9
+ const existing = registry.get(tagName);
10
+ if (existing) {
11
+ return existing;
12
+ }
13
+ const app = options.app ?? options.Async ?? Async;
14
+ const HTMLElementBase = options.HTMLElement ?? options.window?.HTMLElement ?? globalThis.HTMLElement;
15
+ if (!HTMLElementBase) {
16
+ throw new Error("defineAsyncContainerElement(...) requires HTMLElement.");
17
+ }
18
+
19
+ class AsyncContainerElement extends HTMLElementBase {
20
+ connectedCallback() {
21
+ if (this.__asyncAttached) {
22
+ return;
23
+ }
24
+ const runtime = app.runtime ?? app.start?.();
25
+ runtime?.attachRoot?.(this);
26
+ this.__asyncRuntime = runtime;
27
+ this.__asyncAttached = true;
28
+ }
29
+
30
+ disconnectedCallback() {
31
+ if (!this.__asyncAttached) {
32
+ return;
33
+ }
34
+ this.__asyncRuntime?.detachRoot?.(this);
35
+ this.__asyncRuntime = undefined;
36
+ this.__asyncAttached = false;
37
+ }
38
+ }
39
+
40
+ registry.define(tagName, AsyncContainerElement);
41
+ return AsyncContainerElement;
42
+ }
43
+
44
+ export function defineAsyncSuspenseElement(options = {}) {
45
+ const tagName = options.tagName ?? "async-suspense";
46
+ const registry = options.customElements ?? globalThis.customElements;
47
+ if (!registry) {
48
+ throw new Error("defineAsyncSuspenseElement(...) requires customElements.");
49
+ }
50
+ const existing = registry.get(tagName);
51
+ if (existing) {
52
+ return existing;
53
+ }
54
+ const HTMLElementBase = options.HTMLElement ?? options.window?.HTMLElement ?? globalThis.HTMLElement;
55
+ if (!HTMLElementBase) {
56
+ throw new Error("defineAsyncSuspenseElement(...) requires HTMLElement.");
57
+ }
58
+
59
+ class AsyncSuspenseElement extends HTMLElementBase {}
60
+
61
+ registry.define(tagName, AsyncSuspenseElement);
62
+ return AsyncSuspenseElement;
63
+ }
package/src/handlers.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  unwrapServerResult
6
6
  } from "./server.js";
7
7
  import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
8
+ import { createLazyRegistry, isLazyDescriptor } from "./lazy-registry.js";
8
9
 
9
10
  const builtInTokens = new Set(["prevent", "preventDefault", "stopPropagation", "stopImmediatePropagation"]);
10
11
  const builtInHandlers = {
@@ -26,11 +27,13 @@ export function createHandlerRegistry(initialMap = {}, options = {}) {
26
27
  const registryStore = options.registry ?? createRegistryStore();
27
28
  const type = options.type ?? "handler";
28
29
  const handlers = registryStore._map(type);
30
+ const lazyRegistry = options.lazyRegistry ?? createLazyRegistry(options);
31
+ const lazyHandlers = new Map();
29
32
 
30
33
  const registry = attachRegistryInspection({
31
34
  register(id, fn) {
32
35
  assertId(id);
33
- if (typeof fn !== "function") {
36
+ if (typeof fn !== "function" && !isLazyDescriptor(fn)) {
34
37
  throw new TypeError(`Handler "${id}" must be a function.`);
35
38
  }
36
39
  if (handlers.has(id)) {
@@ -49,12 +52,26 @@ export function createHandlerRegistry(initialMap = {}, options = {}) {
49
52
 
50
53
  unregister(id) {
51
54
  assertId(id);
55
+ lazyHandlers.delete(id);
52
56
  return handlers.delete(id);
53
57
  },
54
58
 
55
59
  resolve(id) {
56
60
  assertId(id);
57
- return handlers.get(id);
61
+ const handler = handlers.get(id);
62
+ if (!isLazyDescriptor(handler)) {
63
+ return handler;
64
+ }
65
+ if (!lazyHandlers.has(id)) {
66
+ lazyHandlers.set(id, async function runLazyHandler(...args) {
67
+ const resolved = await lazyRegistry.resolve(type, id, handler);
68
+ if (typeof resolved !== "function") {
69
+ throw new TypeError(`Handler "${id}" did not resolve to a function.`);
70
+ }
71
+ return resolved.apply(this, args);
72
+ });
73
+ }
74
+ return lazyHandlers.get(id);
58
75
  },
59
76
 
60
77
  async run(ref, context = {}) {
package/src/index.js CHANGED
@@ -5,8 +5,10 @@ export { createBoundaryReceiver } from "./boundary-receiver.js";
5
5
  export { createCacheRegistry, defineCache } from "./cache.js";
6
6
  export { component, createComponentRegistry, defineComponent } from "./component.js";
7
7
  export { delay } from "./delay.js";
8
+ export { defineAsyncContainerElement, defineAsyncSuspenseElement } from "./elements.js";
8
9
  export { createHandlerRegistry } from "./handlers.js";
9
10
  export { html } from "./html.js";
11
+ export { createLazyRegistry, defineRegistrySnapshot } from "./lazy-registry.js";
10
12
  export { Loader, AsyncLoader } from "./loader.js";
11
13
  export { createPartialRegistry } from "./partials.js";
12
14
  export { createRegistryStore } from "./registry-store.js";
@@ -0,0 +1,204 @@
1
+ const descriptorTypes = new Set(["handler", "component", "asyncSignal", "partial", "route"]);
2
+ const defaultBaseUrl = "_async";
3
+
4
+ export function defineRegistrySnapshot(snapshot = {}) {
5
+ if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) {
6
+ throw new TypeError("defineRegistrySnapshot(snapshot) requires an object.");
7
+ }
8
+ return snapshot;
9
+ }
10
+
11
+ export function createLazyRegistry(options = {}) {
12
+ const registryAssets = normalizeRegistryAssets(options.registryAssets ?? options.assets);
13
+ const importModule = options.importModule ?? ((url) => import(url));
14
+ const moduleCache = new Map();
15
+ const exportCache = new Map();
16
+
17
+ return {
18
+ registryAssets,
19
+
20
+ resolveUrl(type, id, descriptor) {
21
+ return resolveDescriptorUrl(type, id, descriptor, registryAssets);
22
+ },
23
+
24
+ async resolve(type, id, descriptor) {
25
+ if (!isLazyDescriptor(descriptor)) {
26
+ return descriptor;
27
+ }
28
+ const cacheKey = `${type}:${id}`;
29
+ if (exportCache.has(cacheKey)) {
30
+ return exportCache.get(cacheKey);
31
+ }
32
+
33
+ const resolved = resolveDescriptorUrl(type, id, descriptor, registryAssets);
34
+ let modulePromise = moduleCache.get(resolved.moduleUrl);
35
+ if (!modulePromise) {
36
+ modulePromise = Promise.resolve(importModule(resolved.moduleUrl));
37
+ moduleCache.set(resolved.moduleUrl, modulePromise);
38
+ }
39
+ const module = await modulePromise;
40
+ const value = resolveExport(module, resolved.exportNames, type, id);
41
+ exportCache.set(cacheKey, value);
42
+ return value;
43
+ },
44
+
45
+ inspect() {
46
+ return {
47
+ registryAssets,
48
+ modules: [...moduleCache.keys()],
49
+ exports: [...exportCache.keys()]
50
+ };
51
+ }
52
+ };
53
+ }
54
+
55
+ export function normalizeRegistryAssets(options = {}) {
56
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultBaseUrl);
57
+ const paths = {
58
+ component: "component",
59
+ handler: "handler",
60
+ asyncSignal: "asyncSignal",
61
+ partial: "partial",
62
+ route: "route",
63
+ ...(options.paths ?? {})
64
+ };
65
+
66
+ for (const [type, value] of Object.entries(paths)) {
67
+ if (!descriptorTypes.has(type)) {
68
+ continue;
69
+ }
70
+ if (typeof value !== "string" || value.length === 0) {
71
+ throw new TypeError(`Registry asset path for "${type}" must be a non-empty string.`);
72
+ }
73
+ }
74
+
75
+ return {
76
+ baseUrl,
77
+ paths
78
+ };
79
+ }
80
+
81
+ export function isLazyDescriptor(value) {
82
+ return Boolean(
83
+ value &&
84
+ typeof value === "object" &&
85
+ !Array.isArray(value) &&
86
+ typeof value.url === "string"
87
+ );
88
+ }
89
+
90
+ export function sameRegistryValue(left, right) {
91
+ if (left === right) {
92
+ return true;
93
+ }
94
+ if (isLazyDescriptor(left) && isLazyDescriptor(right)) {
95
+ return stableStringify(left) === stableStringify(right);
96
+ }
97
+ return false;
98
+ }
99
+
100
+ export function publicRegistryValue(value, id) {
101
+ if (isLazyDescriptor(value)) {
102
+ return { ...value };
103
+ }
104
+ return { id };
105
+ }
106
+
107
+ function resolveDescriptorUrl(type, id, descriptor, registryAssets) {
108
+ if (!descriptorTypes.has(type)) {
109
+ throw new Error(`Registry type "${type}" does not support lazy descriptors.`);
110
+ }
111
+ if (!isLazyDescriptor(descriptor)) {
112
+ throw new TypeError(`Registry descriptor for "${type}:${id}" requires a url.`);
113
+ }
114
+
115
+ const { path, hash } = splitHash(descriptor.url);
116
+ const moduleUrl = resolveModuleUrl(type, path, registryAssets);
117
+ const exportNames = hash
118
+ ? [hash]
119
+ : inferredExportNames(id, path);
120
+
121
+ return {
122
+ moduleUrl,
123
+ exportNames,
124
+ url: hash ? `${moduleUrl}#${hash}` : moduleUrl
125
+ };
126
+ }
127
+
128
+ function resolveModuleUrl(type, path, registryAssets) {
129
+ if (isAbsoluteUrl(path) || path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) {
130
+ return path;
131
+ }
132
+ const typePath = registryAssets.paths[type] ?? type;
133
+ return joinUrl(registryAssets.baseUrl, typePath, path);
134
+ }
135
+
136
+ function resolveExport(module, exportNames, type, id) {
137
+ for (const name of exportNames) {
138
+ if (name in module) {
139
+ return module[name];
140
+ }
141
+ }
142
+ throw new Error(`Lazy ${type} "${id}" did not export ${exportNames.map((name) => `"${name}"`).join(", ")}.`);
143
+ }
144
+
145
+ function inferredExportNames(id, path) {
146
+ const names = [];
147
+ const leaf = id.split(".").filter(Boolean).at(-1);
148
+ const basename = path
149
+ .split("/")
150
+ .filter(Boolean)
151
+ .at(-1)
152
+ ?.replace(/\.[^.]+$/, "");
153
+ for (const name of [leaf, basename, "default"]) {
154
+ if (name && !names.includes(name)) {
155
+ names.push(name);
156
+ }
157
+ }
158
+ return names;
159
+ }
160
+
161
+ function splitHash(url) {
162
+ const index = url.indexOf("#");
163
+ if (index === -1) {
164
+ return { path: url, hash: "" };
165
+ }
166
+ return {
167
+ path: url.slice(0, index),
168
+ hash: url.slice(index + 1)
169
+ };
170
+ }
171
+
172
+ function normalizeBaseUrl(baseUrl) {
173
+ if (typeof baseUrl !== "string" || baseUrl.length === 0) {
174
+ throw new TypeError("registryAssets.baseUrl must be a non-empty string.");
175
+ }
176
+ if (isAbsoluteUrl(baseUrl) || baseUrl.startsWith("/") || baseUrl.startsWith("./") || baseUrl.startsWith("../")) {
177
+ return stripTrailingSlash(baseUrl);
178
+ }
179
+ return `/${stripSlashes(baseUrl)}`;
180
+ }
181
+
182
+ function joinUrl(...parts) {
183
+ const [first, ...rest] = parts;
184
+ return [stripTrailingSlash(first), ...rest.map(stripSlashes)].filter(Boolean).join("/");
185
+ }
186
+
187
+ function stripSlashes(value) {
188
+ return String(value).replace(/^\/+|\/+$/g, "");
189
+ }
190
+
191
+ function stripTrailingSlash(value) {
192
+ return String(value).replace(/\/+$/g, "");
193
+ }
194
+
195
+ function isAbsoluteUrl(value) {
196
+ return /^[A-Za-z][A-Za-z\d+.-]*:/.test(value);
197
+ }
198
+
199
+ function stableStringify(value) {
200
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
201
+ return JSON.stringify(value);
202
+ }
203
+ return JSON.stringify(Object.keys(value).sort().map((key) => [key, value[key]]));
204
+ }
package/src/loader.js CHANGED
@@ -335,7 +335,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
335
335
  if (renderingBoundaries.has(boundary)) {
336
336
  continue;
337
337
  }
338
- const id = readAttribute(boundary, attributeConfig, "async", "boundary");
338
+ const id = boundaryIdFor(boundary, attributeConfig);
339
339
  if (id == null) {
340
340
  continue;
341
341
  }
@@ -652,19 +652,26 @@ function writeClassTokens(element, tokens) {
652
652
  function collectBoundaryTemplates(boundary, id, attributeConfig) {
653
653
  const templates = {};
654
654
  for (const template of [...boundary.children].filter((child) => child.tagName === "TEMPLATE")) {
655
- if (readAttribute(template, attributeConfig, "async", "loading") === id) {
655
+ if (templateMatchesState(template, "loading", id, boundary, attributeConfig)) {
656
656
  templates.loading = template;
657
657
  }
658
- if (readAttribute(template, attributeConfig, "async", "ready") === id) {
658
+ if (templateMatchesState(template, "ready", id, boundary, attributeConfig)) {
659
659
  templates.ready = template;
660
660
  }
661
- if (readAttribute(template, attributeConfig, "async", "error") === id) {
661
+ if (templateMatchesState(template, "error", id, boundary, attributeConfig)) {
662
662
  templates.error = template;
663
663
  }
664
664
  }
665
665
  return templates;
666
666
  }
667
667
 
668
+ function templateMatchesState(template, state, id, boundary, attributeConfig) {
669
+ if (readAttribute(template, attributeConfig, "async", state) === id) {
670
+ return true;
671
+ }
672
+ return isAsyncSuspense(boundary) && template.hasAttribute?.(state);
673
+ }
674
+
668
675
  function chooseBoundaryTemplate(templates, status) {
669
676
  if (status === "ready") {
670
677
  return templates.ready ?? templates.loading ?? templates.error;
@@ -712,13 +719,24 @@ function elementsIn(scope) {
712
719
 
713
720
  function findBoundary(root, boundaryId, attributeConfig) {
714
721
  for (const element of elementsIn(root)) {
715
- if (readAttribute(element, attributeConfig, "async", "boundary") === String(boundaryId)) {
722
+ if (boundaryIdFor(element, attributeConfig) === String(boundaryId)) {
716
723
  return element;
717
724
  }
718
725
  }
719
726
  return null;
720
727
  }
721
728
 
729
+ function boundaryIdFor(element, attributeConfig) {
730
+ if (isAsyncSuspense(element) && element.hasAttribute?.("for")) {
731
+ return element.getAttribute("for");
732
+ }
733
+ return readAttribute(element, attributeConfig, "async", "boundary");
734
+ }
735
+
736
+ function isAsyncSuspense(element) {
737
+ return element?.tagName === "ASYNC-SUSPENSE";
738
+ }
739
+
722
740
  function toFragment(value, documentRef) {
723
741
  if (value?.nodeType === 11) {
724
742
  return value;
package/src/partials.js CHANGED
@@ -1,15 +1,18 @@
1
1
  import { isTemplateResult, renderTemplate } from "./html.js";
2
2
  import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
3
+ import { createLazyRegistry, isLazyDescriptor } from "./lazy-registry.js";
3
4
 
4
5
  export function createPartialRegistry(initialMap = {}, options = {}) {
5
6
  const registryStore = options.registry ?? createRegistryStore();
6
7
  const type = options.type ?? "partial";
7
8
  const entries = registryStore._map(type);
9
+ const lazyRegistry = options.lazyRegistry ?? createLazyRegistry(options);
10
+ const lazyPartials = new Map();
8
11
 
9
12
  const registry = attachRegistryInspection({
10
13
  register(id, fn) {
11
14
  assertId(id);
12
- if (typeof fn !== "function") {
15
+ if (typeof fn !== "function" && !isLazyDescriptor(fn)) {
13
16
  throw new TypeError(`Partial "${id}" must be a function.`);
14
17
  }
15
18
  if (entries.has(id)) {
@@ -28,12 +31,26 @@ export function createPartialRegistry(initialMap = {}, options = {}) {
28
31
 
29
32
  unregister(id) {
30
33
  assertId(id);
34
+ lazyPartials.delete(id);
31
35
  return entries.delete(id);
32
36
  },
33
37
 
34
38
  resolve(id) {
35
39
  assertId(id);
36
- return entries.get(id);
40
+ const partial = entries.get(id);
41
+ if (!isLazyDescriptor(partial)) {
42
+ return partial;
43
+ }
44
+ if (!lazyPartials.has(id)) {
45
+ lazyPartials.set(id, async function runLazyPartial(...args) {
46
+ const resolved = await lazyRegistry.resolve(type, id, partial);
47
+ if (typeof resolved !== "function") {
48
+ throw new TypeError(`Partial "${id}" did not resolve to a function.`);
49
+ }
50
+ return resolved.apply(this, args);
51
+ });
52
+ }
53
+ return lazyPartials.get(id);
37
54
  },
38
55
 
39
56
  async render(id, props = {}, context = {}) {
@@ -1,4 +1,6 @@
1
- const declarationTypes = new Set(["signal", "handler", "server", "partial", "route", "component"]);
1
+ import { publicRegistryValue } from "./lazy-registry.js";
2
+
3
+ const declarationTypes = new Set(["signal", "handler", "server", "partial", "route", "component", "asyncSignal"]);
2
4
  const cacheTypes = new Set(["cache.browser", "cache.server"]);
3
5
  const cacheEntryTypes = new Set(["cache.browser.entries", "cache.server.entries"]);
4
6
  const allTypes = new Set([...declarationTypes, ...cacheTypes, ...cacheEntryTypes]);
@@ -85,11 +87,12 @@ export function createRegistryStore(initial = {}, options = {}) {
85
87
  const snapshotTarget = snapshotOptions.target ?? target;
86
88
  return {
87
89
  signal: snapshotSignals(backing.signal),
88
- handler: snapshotDescriptors(backing.handler, "handler"),
89
- server: snapshotDescriptors(backing.server, "server"),
90
- partial: snapshotDescriptors(backing.partial, "partial"),
90
+ handler: snapshotDescriptors(backing.handler),
91
+ server: snapshotDescriptors(backing.server),
92
+ partial: snapshotDescriptors(backing.partial),
91
93
  route: snapshotPlain(backing.route),
92
- component: snapshotDescriptors(backing.component, "component"),
94
+ component: snapshotDescriptors(backing.component),
95
+ asyncSignal: snapshotDescriptors(backing.asyncSignal),
93
96
  cache: {
94
97
  browser: snapshotPlain(backing.cache.browser),
95
98
  server: snapshotPlain(backing.cache.server)
@@ -109,6 +112,7 @@ export function createRegistryStore(initial = {}, options = {}) {
109
112
  partial: Object.fromEntries(backing.partial),
110
113
  route: Object.fromEntries(backing.route),
111
114
  component: Object.fromEntries(backing.component),
115
+ asyncSignal: Object.fromEntries(backing.asyncSignal),
112
116
  cache: {
113
117
  browser: Object.fromEntries(backing.cache.browser),
114
118
  server: Object.fromEntries(backing.cache.server)
@@ -168,6 +172,7 @@ function createBacking() {
168
172
  partial: new Map(),
169
173
  route: new Map(),
170
174
  component: new Map(),
175
+ asyncSignal: new Map(),
171
176
  cache: {
172
177
  browser: new Map(),
173
178
  server: new Map()
@@ -186,6 +191,7 @@ function applyInitial(registry, initial = {}) {
186
191
  registry.registerMany("partial", initial.partial);
187
192
  registry.registerMany("route", initial.route);
188
193
  registry.registerMany("component", initial.component);
194
+ registry.registerMany("asyncSignal", initial.asyncSignal);
189
195
  registry.registerMany("cache.browser", initial.cache?.browser);
190
196
  registry.registerMany("cache.server", initial.cache?.server);
191
197
 
@@ -213,7 +219,7 @@ function assertId(type, id) {
213
219
 
214
220
  function publicValue(type, id, value, options) {
215
221
  if (type === "server" && options.target === "browser") {
216
- return { id, kind: "server" };
222
+ return publicRegistryValue(value, id);
217
223
  }
218
224
  if (cacheEntryTypes.has(type)) {
219
225
  return value?.value;
@@ -233,10 +239,10 @@ function snapshotSignals(map) {
233
239
  return snapshot;
234
240
  }
235
241
 
236
- function snapshotDescriptors(map, kind) {
242
+ function snapshotDescriptors(map) {
237
243
  const snapshot = {};
238
- for (const id of map.keys()) {
239
- snapshot[id] = { id, kind };
244
+ for (const [id, value] of map) {
245
+ snapshot[id] = publicRegistryValue(value, id);
240
246
  }
241
247
  return snapshot;
242
248
  }