@async/framework 0.10.2 → 0.11.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +23 -7
  3. package/browser.d.ts +4 -7
  4. package/browser.js +17 -66
  5. package/browser.min.js +1 -1
  6. package/browser.ts +17 -66
  7. package/browser.umd.js +17 -66
  8. package/browser.umd.min.js +1 -1
  9. package/{server.d.ts → framework.d.ts} +4 -7
  10. package/framework.ts +5946 -0
  11. package/package.json +25 -17
  12. package/server.js +5945 -0
  13. package/examples/cache/index.html +0 -16
  14. package/examples/cache/main.js +0 -47
  15. package/examples/components/index.html +0 -11
  16. package/examples/components/main.js +0 -26
  17. package/examples/counter/index.html +0 -15
  18. package/examples/counter/main.js +0 -17
  19. package/examples/partials/index.html +0 -15
  20. package/examples/partials/main.js +0 -43
  21. package/examples/product/index.html +0 -32
  22. package/examples/product/main.js +0 -24
  23. package/examples/router/index.html +0 -18
  24. package/examples/router/main.js +0 -52
  25. package/examples/server-call/index.html +0 -21
  26. package/examples/server-call/main.js +0 -22
  27. package/examples/ssr/index.html +0 -12
  28. package/examples/ssr/main.js +0 -89
  29. package/examples/streaming/index.html +0 -16
  30. package/examples/streaming/main.js +0 -30
  31. package/src/app.js +0 -802
  32. package/src/async-signal.js +0 -277
  33. package/src/attributes.js +0 -52
  34. package/src/boundary-receiver.js +0 -302
  35. package/src/browser.js +0 -18
  36. package/src/cache.js +0 -193
  37. package/src/component.js +0 -373
  38. package/src/delay.js +0 -30
  39. package/src/elements.js +0 -63
  40. package/src/handlers.js +0 -219
  41. package/src/html.js +0 -158
  42. package/src/index.js +0 -20
  43. package/src/lazy-registry.js +0 -218
  44. package/src/loader.js +0 -772
  45. package/src/partials.js +0 -133
  46. package/src/registry-store.js +0 -267
  47. package/src/request-context.js +0 -40
  48. package/src/router.js +0 -617
  49. package/src/scheduler.js +0 -300
  50. package/src/server-entry.js +0 -20
  51. package/src/server-registry.js +0 -97
  52. package/src/server.js +0 -362
  53. package/src/signals.js +0 -592
package/src/handlers.js DELETED
@@ -1,219 +0,0 @@
1
- import {
2
- applyServerResult,
3
- defaultInput,
4
- resolveServerCommandArguments,
5
- unwrapServerResult
6
- } from "./server.js";
7
- import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
8
- import { createLazyRegistry, isLazyDescriptor } from "./lazy-registry.js";
9
-
10
- const builtInTokens = new Set(["prevent", "preventDefault", "stopPropagation", "stopImmediatePropagation"]);
11
- const builtInHandlers = {
12
- prevent: preventDefault,
13
- preventDefault,
14
- stopPropagation() {
15
- this.event?.stopPropagation?.();
16
- },
17
- stopImmediatePropagation() {
18
- this.event?.stopImmediatePropagation?.();
19
- }
20
- };
21
-
22
- function preventDefault() {
23
- this.event?.preventDefault?.();
24
- }
25
-
26
- export function createHandlerRegistry(initialMap = {}, options = {}) {
27
- const registryStore = options.registry ?? createRegistryStore();
28
- const type = options.type ?? "handler";
29
- const handlers = registryStore._map(type);
30
- const lazyRegistry = options.lazyRegistry ?? createLazyRegistry(options);
31
- const lazyHandlers = new Map();
32
-
33
- const registry = attachRegistryInspection({
34
- register(id, fn) {
35
- assertId(id);
36
- if (typeof fn !== "function" && !isLazyDescriptor(fn)) {
37
- throw new TypeError(`Handler "${id}" must be a function.`);
38
- }
39
- if (handlers.has(id)) {
40
- throw new Error(`Handler "${id}" is already registered.`);
41
- }
42
- handlers.set(id, fn);
43
- return id;
44
- },
45
-
46
- registerMany(map) {
47
- for (const [id, fn] of Object.entries(map ?? {})) {
48
- registry.register(id, fn);
49
- }
50
- return registry;
51
- },
52
-
53
- unregister(id) {
54
- assertId(id);
55
- lazyHandlers.delete(id);
56
- return handlers.delete(id);
57
- },
58
-
59
- resolve(id) {
60
- assertId(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);
75
- },
76
-
77
- async run(ref, context = {}) {
78
- const steps = parseHandlerRef(ref);
79
- const results = [];
80
- let stopped = false;
81
- const runContext = {
82
- ...context,
83
- handlers: registry,
84
- input: context.input ?? defaultInput(context),
85
- stop() {
86
- stopped = true;
87
- }
88
- };
89
-
90
- for (const step of steps) {
91
- if (stopped) {
92
- break;
93
- }
94
-
95
- if (step.type === "server") {
96
- if (!runContext.server || typeof runContext.server.run !== "function") {
97
- throw new Error(`Server command "${step.id}" cannot run without a server registry.`);
98
- }
99
- const resolved = resolveServerCommandArguments(step.args, runContext);
100
- const result = await runContext.server.run(step.id, resolved.args, {
101
- ...runContext,
102
- signalPaths: resolved.signalPaths,
103
- signalValues: resolved.signalValues
104
- });
105
- await applyServerResult(result, runContext);
106
- results.push(unwrapServerResult(result));
107
- continue;
108
- }
109
-
110
- const handler = registry.resolve(step.id);
111
- if (!handler) {
112
- throw new Error(`Handler "${step.id}" is not registered.`);
113
- }
114
- const value = await handler.call(runContext, runContext);
115
- if (!(builtInTokens.has(step.id) && handler === builtInHandlers[step.id])) {
116
- results.push(value);
117
- }
118
- }
119
-
120
- return results;
121
- },
122
-
123
- _adoptMany() {
124
- return registry;
125
- }
126
- }, registryStore, type);
127
-
128
- registerBuiltIns(registry, handlers);
129
- registry.registerMany(initialMap);
130
- return registry;
131
- }
132
-
133
- function registerBuiltIns(registry, handlers) {
134
- for (const [id, fn] of Object.entries(builtInHandlers)) {
135
- if (!handlers.has(id)) {
136
- registry.register(id, fn);
137
- continue;
138
- }
139
- if (handlers.get(id) !== fn) {
140
- throw new Error(`Handler "${id}" is already registered.`);
141
- }
142
- }
143
- }
144
-
145
- export function parseHandlerRef(ref) {
146
- if (typeof ref !== "string" || ref.trim().length === 0) {
147
- throw new TypeError("Handler ref must be a non-empty string.");
148
- }
149
-
150
- return ref
151
- .split(";")
152
- .map((part) => part.trim())
153
- .filter(Boolean)
154
- .map(parseCommand);
155
- }
156
-
157
- export function isHandlerToken(value) {
158
- return builtInTokens.has(value);
159
- }
160
-
161
- function assertId(id) {
162
- if (typeof id !== "string" || id.length === 0) {
163
- throw new TypeError("Handler id must be a non-empty string.");
164
- }
165
- }
166
-
167
- function parseCommand(command) {
168
- if (command.startsWith("server.")) {
169
- return parseServerCommand(command);
170
- }
171
- if (command.includes("(") || command.includes(")")) {
172
- throw new Error(`Command "${command}" is not supported.`);
173
- }
174
- return { type: "handler", id: command };
175
- }
176
-
177
- function parseServerCommand(command) {
178
- const open = command.indexOf("(");
179
- if (open === -1 || !command.endsWith(")")) {
180
- throw new Error(`Server command "${command}" must be called with parentheses.`);
181
- }
182
-
183
- const id = command.slice("server.".length, open).trim();
184
- if (!isServerCommandId(id)) {
185
- throw new Error(`Server command "${command}" has an invalid function id.`);
186
- }
187
-
188
- return {
189
- type: "server",
190
- id,
191
- args: parseArguments(command.slice(open + 1, -1))
192
- };
193
- }
194
-
195
- function parseArguments(source) {
196
- if (source.trim().length === 0) {
197
- return [];
198
- }
199
-
200
- return source
201
- .split(",")
202
- .map((part) => part.trim())
203
- .filter(Boolean)
204
- .map(parseArgument);
205
- }
206
-
207
- function parseArgument(token) {
208
- if (!/^[^\s,();]+$/.test(token)) {
209
- throw new Error(`Argument "${token}" is not supported.`);
210
- }
211
- if (token.startsWith("$")) {
212
- return { type: "local", name: token };
213
- }
214
- return { type: "signal", path: token };
215
- }
216
-
217
- function isServerCommandId(id) {
218
- return /^[^.\s();]+(?:\.[^.\s();]+)*$/.test(id);
219
- }
package/src/html.js DELETED
@@ -1,158 +0,0 @@
1
- import { isSignalRef } from "./signals.js";
2
- import { attributeName, matchAttribute, normalizeAttributeConfig } from "./attributes.js";
3
-
4
- const templateKind = Symbol.for("@async/framework.template");
5
- const rawKind = Symbol.for("@async/framework.rawHtml");
6
-
7
- export function html(strings, ...values) {
8
- return {
9
- [templateKind]: true,
10
- strings,
11
- values
12
- };
13
- }
14
-
15
- export function isTemplateResult(value) {
16
- return Boolean(value?.[templateKind]);
17
- }
18
-
19
- export function rawHtml(value) {
20
- return {
21
- [rawKind]: true,
22
- html: String(value ?? "")
23
- };
24
- }
25
-
26
- export function renderTemplate(value, options = {}) {
27
- if (isTemplateResult(value)) {
28
- const context = createRenderContext(options);
29
- let output = "";
30
- for (let index = 0; index < value.strings.length; index += 1) {
31
- output += value.strings[index];
32
- if (index < value.values.length) {
33
- output += renderValue(value.values[index], {
34
- ...context,
35
- attribute: readAttributeContext(value.strings[index])
36
- });
37
- }
38
- }
39
- return output;
40
- }
41
- return renderValue(value, createRenderContext(options));
42
- }
43
-
44
- function renderValue(value, context = createRenderContext()) {
45
- if (value?.[rawKind]) {
46
- return value.html;
47
- }
48
- if (isTemplateResult(value)) {
49
- return renderTemplate(value, context);
50
- }
51
- if (context.attribute) {
52
- return renderAttributeValue(value, context);
53
- }
54
- if (Array.isArray(value)) {
55
- return value.map((item) => renderValue(item, context)).join("");
56
- }
57
- if (isSignalRef(value)) {
58
- return escapeHtml(value.value);
59
- }
60
- if (value == null || value === false) {
61
- return "";
62
- }
63
- return escapeHtml(value);
64
- }
65
-
66
- function renderAttributeValue(value, context) {
67
- const signalName = matchAttribute(context.attribute.name, context.attributes, "signal");
68
- const className = matchAttribute(context.attribute.name, context.attributes, "class");
69
- const signalPath = signalPathFor(value, context);
70
-
71
- if (context.attribute.name === "value" && signalPath) {
72
- const currentValue = readSignalValue(value, context);
73
- const signalValueAttribute = attributeName(context.attributes, "signal", "value");
74
- return `${escapeHtml(currentValue)}${context.attribute.quote} ${signalValueAttribute}=${context.attribute.quote}${escapeHtml(signalPath)}`;
75
- }
76
-
77
- if (signalName != null || className != null) {
78
- if (signalPath) {
79
- return escapeHtml(signalPath);
80
- }
81
- if (isInlineBindingValue(value)) {
82
- return escapeHtml(registerInlineBinding(value, context));
83
- }
84
- }
85
-
86
- return renderValueAsAttributeLiteral(value, context);
87
- }
88
-
89
- function renderValueAsAttributeLiteral(value, context) {
90
- if (Array.isArray(value)) {
91
- return value.map((item) => renderValueAsAttributeLiteral(item, context)).join("");
92
- }
93
- if (isSignalRef(value)) {
94
- return escapeHtml(value.value);
95
- }
96
- if (value == null || value === false) {
97
- return "";
98
- }
99
- return escapeHtml(value);
100
- }
101
-
102
- function createRenderContext(options = {}) {
103
- return {
104
- ...options,
105
- attributes: normalizeAttributeConfig(options.attributes)
106
- };
107
- }
108
-
109
- function readAttributeContext(source) {
110
- const match = source.match(/(?:^|[\s<])([^\s"'=<>`]+)\s*=\s*(["'])$/);
111
- if (!match) {
112
- return null;
113
- }
114
- return {
115
- name: match[1],
116
- quote: match[2]
117
- };
118
- }
119
-
120
- function signalPathFor(value, context) {
121
- if (isSignalRef(value)) {
122
- return value.id;
123
- }
124
- if (typeof value === "string" && context.signals?.has?.(value)) {
125
- return value;
126
- }
127
- return null;
128
- }
129
-
130
- function readSignalValue(value, context) {
131
- if (isSignalRef(value)) {
132
- return value.value;
133
- }
134
- if (typeof value === "string" && context.signals?.has?.(value)) {
135
- return context.signals.get(value);
136
- }
137
- return value;
138
- }
139
-
140
- function isInlineBindingValue(value) {
141
- return Boolean(value && typeof value === "object");
142
- }
143
-
144
- function registerInlineBinding(value, context) {
145
- if (typeof context.bind !== "function") {
146
- return value;
147
- }
148
- return context.bind(value);
149
- }
150
-
151
- export function escapeHtml(value) {
152
- return String(value)
153
- .replaceAll("&", "&amp;")
154
- .replaceAll("<", "&lt;")
155
- .replaceAll(">", "&gt;")
156
- .replaceAll('"', "&quot;")
157
- .replaceAll("'", "&#39;");
158
- }
package/src/index.js DELETED
@@ -1,20 +0,0 @@
1
- export { asyncSignal } from "./async-signal.js";
2
- export { Async, createApp, defineApp, readSnapshot } from "./server-entry.js";
3
- export { attributeName, defineAttributeConfig } from "./attributes.js";
4
- export { createBoundaryReceiver } from "./boundary-receiver.js";
5
- export { createCacheRegistry, defineCache } from "./cache.js";
6
- export { component, createComponentRegistry, defineComponent } from "./component.js";
7
- export { delay } from "./delay.js";
8
- export { defineAsyncContainerElement, defineAsyncSuspenseElement } from "./elements.js";
9
- export { createHandlerRegistry } from "./handlers.js";
10
- export { html } from "./html.js";
11
- export { createLazyRegistry, defineRegistrySnapshot } from "./lazy-registry.js";
12
- export { Loader, AsyncLoader } from "./loader.js";
13
- export { createPartialRegistry } from "./partials.js";
14
- export { createRegistryStore } from "./registry-store.js";
15
- export { createRouteRegistry, createRouter, defineRoute, route } from "./router.js";
16
- export { createScheduler } from "./scheduler.js";
17
- export { createRequestContextStore } from "./request-context.js";
18
- export { applyServerResult, createServerProxy, resolveServerCommandArguments, unwrapServerResult } from "./server.js";
19
- export { createServerRegistry } from "./server-registry.js";
20
- export { computed, createSignal, createSignalRegistry, effect, signal } from "./signals.js";
@@ -1,218 +0,0 @@
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().then(() => importModule(resolved.moduleUrl));
37
- moduleCache.set(resolved.moduleUrl, modulePromise);
38
- }
39
- let module;
40
- try {
41
- module = await modulePromise;
42
- } catch (cause) {
43
- if (moduleCache.get(resolved.moduleUrl) === modulePromise) {
44
- moduleCache.delete(resolved.moduleUrl);
45
- }
46
- throw new Error(`Lazy ${type} "${id}" failed to import ${resolved.moduleUrl}: ${errorMessage(cause)}`, {
47
- cause
48
- });
49
- }
50
- const value = resolveExport(module, resolved.exportNames, type, id);
51
- exportCache.set(cacheKey, value);
52
- return value;
53
- },
54
-
55
- inspect() {
56
- return {
57
- registryAssets,
58
- modules: [...moduleCache.keys()],
59
- exports: [...exportCache.keys()]
60
- };
61
- }
62
- };
63
- }
64
-
65
- export function normalizeRegistryAssets(options = {}) {
66
- const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultBaseUrl);
67
- const paths = {
68
- component: "component",
69
- handler: "handler",
70
- asyncSignal: "asyncSignal",
71
- partial: "partial",
72
- route: "route",
73
- ...(options.paths ?? {})
74
- };
75
-
76
- for (const [type, value] of Object.entries(paths)) {
77
- if (!descriptorTypes.has(type)) {
78
- continue;
79
- }
80
- if (typeof value !== "string" || value.length === 0) {
81
- throw new TypeError(`Registry asset path for "${type}" must be a non-empty string.`);
82
- }
83
- }
84
-
85
- return {
86
- baseUrl,
87
- paths
88
- };
89
- }
90
-
91
- export function isLazyDescriptor(value) {
92
- return Boolean(
93
- value &&
94
- typeof value === "object" &&
95
- !Array.isArray(value) &&
96
- typeof value.url === "string"
97
- );
98
- }
99
-
100
- export function sameRegistryValue(left, right) {
101
- if (left === right) {
102
- return true;
103
- }
104
- if (isLazyDescriptor(left) && isLazyDescriptor(right)) {
105
- return stableStringify(left) === stableStringify(right);
106
- }
107
- return false;
108
- }
109
-
110
- export function publicRegistryValue(value, id) {
111
- if (isLazyDescriptor(value)) {
112
- return { ...value };
113
- }
114
- return { id };
115
- }
116
-
117
- function resolveDescriptorUrl(type, id, descriptor, registryAssets) {
118
- if (!descriptorTypes.has(type)) {
119
- throw new Error(`Registry type "${type}" does not support lazy descriptors.`);
120
- }
121
- if (!isLazyDescriptor(descriptor)) {
122
- throw new TypeError(`Registry descriptor for "${type}:${id}" requires a url.`);
123
- }
124
-
125
- const { path, hash } = splitHash(descriptor.url);
126
- const moduleUrl = resolveModuleUrl(type, path, registryAssets);
127
- const exportNames = hash
128
- ? [hash]
129
- : inferredExportNames(id, path);
130
-
131
- return {
132
- moduleUrl,
133
- exportNames,
134
- url: hash ? `${moduleUrl}#${hash}` : moduleUrl
135
- };
136
- }
137
-
138
- function resolveModuleUrl(type, path, registryAssets) {
139
- if (isAbsoluteUrl(path) || path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) {
140
- return path;
141
- }
142
- const typePath = registryAssets.paths[type] ?? type;
143
- return joinUrl(registryAssets.baseUrl, typePath, path);
144
- }
145
-
146
- function resolveExport(module, exportNames, type, id) {
147
- for (const name of exportNames) {
148
- if (name in module) {
149
- return module[name];
150
- }
151
- }
152
- throw new Error(`Lazy ${type} "${id}" did not export ${exportNames.map((name) => `"${name}"`).join(", ")}.`);
153
- }
154
-
155
- function inferredExportNames(id, path) {
156
- const names = [];
157
- const leaf = id.split(".").filter(Boolean).at(-1);
158
- const basename = path
159
- .split("/")
160
- .filter(Boolean)
161
- .at(-1)
162
- ?.replace(/\.[^.]+$/, "");
163
- for (const name of [leaf, basename, "default"]) {
164
- if (name && !names.includes(name)) {
165
- names.push(name);
166
- }
167
- }
168
- return names;
169
- }
170
-
171
- function splitHash(url) {
172
- const index = url.indexOf("#");
173
- if (index === -1) {
174
- return { path: url, hash: "" };
175
- }
176
- return {
177
- path: url.slice(0, index),
178
- hash: url.slice(index + 1)
179
- };
180
- }
181
-
182
- function normalizeBaseUrl(baseUrl) {
183
- if (typeof baseUrl !== "string" || baseUrl.length === 0) {
184
- throw new TypeError("registryAssets.baseUrl must be a non-empty string.");
185
- }
186
- if (isAbsoluteUrl(baseUrl) || baseUrl.startsWith("/") || baseUrl.startsWith("./") || baseUrl.startsWith("../")) {
187
- return stripTrailingSlash(baseUrl);
188
- }
189
- return `/${stripSlashes(baseUrl)}`;
190
- }
191
-
192
- function joinUrl(...parts) {
193
- const [first, ...rest] = parts;
194
- return [stripTrailingSlash(first), ...rest.map(stripSlashes)].filter(Boolean).join("/");
195
- }
196
-
197
- function stripSlashes(value) {
198
- return String(value).replace(/^\/+|\/+$/g, "");
199
- }
200
-
201
- function stripTrailingSlash(value) {
202
- return String(value).replace(/\/+$/g, "");
203
- }
204
-
205
- function isAbsoluteUrl(value) {
206
- return /^[A-Za-z][A-Za-z\d+.-]*:/.test(value);
207
- }
208
-
209
- function errorMessage(error) {
210
- return error instanceof Error ? error.message : String(error);
211
- }
212
-
213
- function stableStringify(value) {
214
- if (!value || typeof value !== "object" || Array.isArray(value)) {
215
- return JSON.stringify(value);
216
- }
217
- return JSON.stringify(Object.keys(value).sort().map((key) => [key, value[key]]));
218
- }