@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.
- package/CHANGELOG.md +20 -0
- package/README.md +23 -7
- package/browser.d.ts +4 -7
- package/browser.js +17 -66
- package/browser.min.js +1 -1
- package/browser.ts +17 -66
- package/browser.umd.js +17 -66
- package/browser.umd.min.js +1 -1
- package/{server.d.ts → framework.d.ts} +4 -7
- package/framework.ts +5946 -0
- package/package.json +25 -17
- package/server.js +5945 -0
- package/examples/cache/index.html +0 -16
- package/examples/cache/main.js +0 -47
- package/examples/components/index.html +0 -11
- package/examples/components/main.js +0 -26
- package/examples/counter/index.html +0 -15
- package/examples/counter/main.js +0 -17
- package/examples/partials/index.html +0 -15
- package/examples/partials/main.js +0 -43
- package/examples/product/index.html +0 -32
- package/examples/product/main.js +0 -24
- package/examples/router/index.html +0 -18
- package/examples/router/main.js +0 -52
- package/examples/server-call/index.html +0 -21
- package/examples/server-call/main.js +0 -22
- package/examples/ssr/index.html +0 -12
- package/examples/ssr/main.js +0 -89
- package/examples/streaming/index.html +0 -16
- package/examples/streaming/main.js +0 -30
- package/src/app.js +0 -802
- package/src/async-signal.js +0 -277
- package/src/attributes.js +0 -52
- package/src/boundary-receiver.js +0 -302
- package/src/browser.js +0 -18
- package/src/cache.js +0 -193
- package/src/component.js +0 -373
- package/src/delay.js +0 -30
- package/src/elements.js +0 -63
- package/src/handlers.js +0 -219
- package/src/html.js +0 -158
- package/src/index.js +0 -20
- package/src/lazy-registry.js +0 -218
- package/src/loader.js +0 -772
- package/src/partials.js +0 -133
- package/src/registry-store.js +0 -267
- package/src/request-context.js +0 -40
- package/src/router.js +0 -617
- package/src/scheduler.js +0 -300
- package/src/server-entry.js +0 -20
- package/src/server-registry.js +0 -97
- package/src/server.js +0 -362
- package/src/signals.js +0 -592
package/src/server.js
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
|
|
2
|
-
const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
|
|
3
|
-
const appliedServerValues = new WeakSet();
|
|
4
|
-
|
|
5
|
-
export function createServerProxy({
|
|
6
|
-
endpoint = "/__async/server",
|
|
7
|
-
fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
8
|
-
signals,
|
|
9
|
-
loader,
|
|
10
|
-
router,
|
|
11
|
-
cache,
|
|
12
|
-
scheduler,
|
|
13
|
-
headers = {}
|
|
14
|
-
} = {}) {
|
|
15
|
-
if (typeof fetchImpl !== "function") {
|
|
16
|
-
throw new TypeError("createServerProxy(...) requires fetch to be available.");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const defaults = { signals, loader, router, cache, scheduler };
|
|
20
|
-
|
|
21
|
-
async function run(id, args = [], context = {}) {
|
|
22
|
-
assertServerId(id);
|
|
23
|
-
const runContext = { ...defaults, ...context };
|
|
24
|
-
const body = {
|
|
25
|
-
args,
|
|
26
|
-
input: context.input ?? defaultInput(runContext),
|
|
27
|
-
signals: context.signalValues ?? snapshotSignalPaths(context.signalPaths, runContext.signals)
|
|
28
|
-
};
|
|
29
|
-
assertJsonTransportable(body);
|
|
30
|
-
|
|
31
|
-
const response = await fetchImpl(joinEndpoint(endpoint, id), {
|
|
32
|
-
method: "POST",
|
|
33
|
-
headers: {
|
|
34
|
-
"content-type": "application/json",
|
|
35
|
-
...headers
|
|
36
|
-
},
|
|
37
|
-
body: JSON.stringify(body),
|
|
38
|
-
signal: context.abort
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
if (!response.ok) {
|
|
42
|
-
throw new Error(`Server function "${id}" failed with ${response.status}.`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const result = await readServerResponse(response);
|
|
46
|
-
await applyServerResult(result, runContext);
|
|
47
|
-
return markAppliedServerValue(unwrapServerResult(result));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return createServerNamespace(run, {
|
|
51
|
-
run,
|
|
52
|
-
_setContext(context = {}) {
|
|
53
|
-
Object.assign(defaults, context);
|
|
54
|
-
}
|
|
55
|
-
}, () => defaults);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function resolveServerCommandArguments(args, context = {}) {
|
|
59
|
-
const resolved = [];
|
|
60
|
-
const signalValues = {};
|
|
61
|
-
const signalPaths = [];
|
|
62
|
-
|
|
63
|
-
for (const arg of args) {
|
|
64
|
-
if (arg.type === "local") {
|
|
65
|
-
resolved.push(resolveLocal(arg.name, context, { forServer: true }));
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const value = readSignal(context.signals, arg.path);
|
|
70
|
-
resolved.push(value);
|
|
71
|
-
signalValues[arg.path] = value;
|
|
72
|
-
signalPaths.push(arg.path);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return { args: resolved, signalValues, signalPaths };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export async function applyServerResult(result, context = {}) {
|
|
79
|
-
if (!isServerEnvelope(result)) {
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
|
-
if (result[appliedServerResult] || appliedServerValues.has(result)) {
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (result.signals && context.signals) {
|
|
87
|
-
for (const [path, value] of Object.entries(result.signals)) {
|
|
88
|
-
context.signals.set?.(path, value);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (result.cache?.browser && context.cache?.restore) {
|
|
93
|
-
context.cache.restore(result.cache.browser);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (result.boundary && Object.hasOwn(result, "html")) {
|
|
97
|
-
context.loader?.swap?.(result.boundary, result.html);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (result.redirect) {
|
|
101
|
-
await context.router?.navigate?.(result.redirect);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (result.error) {
|
|
105
|
-
throw toError(result.error);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
Object.defineProperty(result, appliedServerResult, {
|
|
109
|
-
configurable: true,
|
|
110
|
-
enumerable: false,
|
|
111
|
-
value: true
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
return result;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function unwrapServerResult(result) {
|
|
118
|
-
if (isServerEnvelope(result) && Object.hasOwn(result, "value")) {
|
|
119
|
-
return result.value;
|
|
120
|
-
}
|
|
121
|
-
return result;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function markAppliedServerValue(value) {
|
|
125
|
-
if (value && typeof value === "object") {
|
|
126
|
-
appliedServerValues.add(value);
|
|
127
|
-
}
|
|
128
|
-
return value;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function defaultInput(context = {}) {
|
|
132
|
-
const form = findForm(context);
|
|
133
|
-
if (form) {
|
|
134
|
-
return formDataToObject(new form.ownerDocument.defaultView.FormData(form));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const element = context.element ?? context.el ?? context.event?.target;
|
|
138
|
-
if (!element) {
|
|
139
|
-
return {};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
value: "value" in element ? element.value : undefined,
|
|
144
|
-
checked: "checked" in element ? element.checked : undefined,
|
|
145
|
-
dataset: element.dataset ? { ...element.dataset } : {}
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function createServerNamespace(run, root = {}, contextProvider = () => ({})) {
|
|
150
|
-
const cache = new Map();
|
|
151
|
-
|
|
152
|
-
function namespace(parts) {
|
|
153
|
-
const cacheKey = parts.join(".");
|
|
154
|
-
if (cache.has(cacheKey)) {
|
|
155
|
-
return cache.get(cacheKey);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const callable = async (...args) => {
|
|
159
|
-
if (parts.length === 0) {
|
|
160
|
-
throw new Error("Server namespace is not directly callable.");
|
|
161
|
-
}
|
|
162
|
-
const context = contextProvider() ?? {};
|
|
163
|
-
const result = await run(parts.join("."), args, context);
|
|
164
|
-
await applyServerResult(result, context);
|
|
165
|
-
return unwrapServerResult(result);
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const proxy = new Proxy(callable, {
|
|
169
|
-
get(_target, prop) {
|
|
170
|
-
if (prop === "then") {
|
|
171
|
-
return undefined;
|
|
172
|
-
}
|
|
173
|
-
if (prop in _target) {
|
|
174
|
-
return _target[prop];
|
|
175
|
-
}
|
|
176
|
-
if (parts.length === 0 && prop === "_withContext") {
|
|
177
|
-
return (context = {}) => createServerNamespace(run, root, () => ({
|
|
178
|
-
...(contextProvider() ?? {}),
|
|
179
|
-
...context
|
|
180
|
-
}));
|
|
181
|
-
}
|
|
182
|
-
if (parts.length === 0 && prop === "run" && typeof root.run === "function") {
|
|
183
|
-
return (id, args = [], context = {}) => root.run(id, args, {
|
|
184
|
-
...(contextProvider() ?? {}),
|
|
185
|
-
...context
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
if (parts.length === 0 && Object.hasOwn(root, prop)) {
|
|
189
|
-
return root[prop];
|
|
190
|
-
}
|
|
191
|
-
if (prop === Symbol.toStringTag) {
|
|
192
|
-
return "AsyncServerNamespace";
|
|
193
|
-
}
|
|
194
|
-
if (prop === "toString") {
|
|
195
|
-
return () => parts.length === 0 ? "server" : `server.${parts.join(".")}`;
|
|
196
|
-
}
|
|
197
|
-
return namespace([...parts, String(prop)]);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
cache.set(cacheKey, proxy);
|
|
202
|
-
return proxy;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return namespace([]);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async function readServerResponse(response) {
|
|
209
|
-
const type = response.headers.get("content-type") ?? "";
|
|
210
|
-
if (type.includes("application/json")) {
|
|
211
|
-
return response.json();
|
|
212
|
-
}
|
|
213
|
-
return { value: await response.text() };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function snapshotSignalPaths(paths = [], signals) {
|
|
217
|
-
const snapshot = {};
|
|
218
|
-
for (const path of paths) {
|
|
219
|
-
snapshot[path] = readSignal(signals, path);
|
|
220
|
-
}
|
|
221
|
-
return snapshot;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function readSignal(signals, path) {
|
|
225
|
-
if (!signals || typeof signals.get !== "function") {
|
|
226
|
-
throw new Error(`Signal "${path}" cannot be read without a signal registry.`);
|
|
227
|
-
}
|
|
228
|
-
return signals.get(path);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export function createSignalReader(signals) {
|
|
232
|
-
if (!signals || typeof signals.get === "function") {
|
|
233
|
-
return signals;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return {
|
|
237
|
-
get(path) {
|
|
238
|
-
return readPath(signals, path);
|
|
239
|
-
},
|
|
240
|
-
snapshot() {
|
|
241
|
-
return { ...signals };
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function readPath(source, path) {
|
|
247
|
-
return String(path)
|
|
248
|
-
.split(".")
|
|
249
|
-
.reduce((value, part) => value?.[part], source);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function resolveLocal(name, context, { forServer } = {}) {
|
|
253
|
-
if ((name === "$event" || name === "$el") && forServer) {
|
|
254
|
-
throw new Error(`${name} cannot be passed to a server command.`);
|
|
255
|
-
}
|
|
256
|
-
if (name === "$event") {
|
|
257
|
-
return context.event;
|
|
258
|
-
}
|
|
259
|
-
if (name === "$el") {
|
|
260
|
-
return context.element ?? context.el;
|
|
261
|
-
}
|
|
262
|
-
if (name === "$value") {
|
|
263
|
-
const element = context.element ?? context.el ?? context.event?.target;
|
|
264
|
-
return element?.value;
|
|
265
|
-
}
|
|
266
|
-
if (name === "$checked") {
|
|
267
|
-
const element = context.element ?? context.el ?? context.event?.target;
|
|
268
|
-
return element?.checked;
|
|
269
|
-
}
|
|
270
|
-
if (name === "$form") {
|
|
271
|
-
const form = findForm(context);
|
|
272
|
-
return form ? formDataToObject(new form.ownerDocument.defaultView.FormData(form)) : {};
|
|
273
|
-
}
|
|
274
|
-
if (name === "$dataset") {
|
|
275
|
-
const element = context.element ?? context.el ?? context.event?.target;
|
|
276
|
-
return element?.dataset ? { ...element.dataset } : {};
|
|
277
|
-
}
|
|
278
|
-
throw new Error(`Event local "${name}" is not supported.`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function findForm(context) {
|
|
282
|
-
const event = context.event;
|
|
283
|
-
const element = context.element ?? context.el ?? event?.target;
|
|
284
|
-
if (element?.tagName === "FORM") {
|
|
285
|
-
return element;
|
|
286
|
-
}
|
|
287
|
-
if (event?.type === "submit" && event.target?.tagName === "FORM") {
|
|
288
|
-
return event.target;
|
|
289
|
-
}
|
|
290
|
-
if (event?.type === "submit" && element?.form) {
|
|
291
|
-
return element.form;
|
|
292
|
-
}
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function formDataToObject(formData) {
|
|
297
|
-
const output = {};
|
|
298
|
-
for (const [key, value] of formData.entries()) {
|
|
299
|
-
if (Object.hasOwn(output, key)) {
|
|
300
|
-
output[key] = Array.isArray(output[key]) ? [...output[key], value] : [output[key], value];
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
output[key] = value;
|
|
304
|
-
}
|
|
305
|
-
return output;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function assertJsonTransportable(value, stack = new Set()) {
|
|
309
|
-
if (typeof value === "bigint") {
|
|
310
|
-
throw new Error("Server proxy JSON transport does not support BigInt values.");
|
|
311
|
-
}
|
|
312
|
-
if (value == null || typeof value !== "object") {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
if (stack.has(value)) {
|
|
316
|
-
throw new Error("Server proxy JSON transport does not support circular values.");
|
|
317
|
-
}
|
|
318
|
-
stack.add(value);
|
|
319
|
-
|
|
320
|
-
const tag = Object.prototype.toString.call(value);
|
|
321
|
-
if (tag === "[object File]" || tag === "[object Blob]" || tag === "[object FormData]") {
|
|
322
|
-
throw new Error("Server proxy JSON transport does not support File, Blob, or FormData values yet.");
|
|
323
|
-
}
|
|
324
|
-
if (Array.isArray(value)) {
|
|
325
|
-
for (const item of value) {
|
|
326
|
-
assertJsonTransportable(item, stack);
|
|
327
|
-
}
|
|
328
|
-
stack.delete(value);
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
for (const item of Object.values(value)) {
|
|
332
|
-
assertJsonTransportable(item, stack);
|
|
333
|
-
}
|
|
334
|
-
stack.delete(value);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function joinEndpoint(endpoint, id) {
|
|
338
|
-
return `${String(endpoint).replace(/\/$/, "")}/${encodeURIComponent(id)}`;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function isServerEnvelope(value) {
|
|
342
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
return Object.keys(value).some((key) => serverEnvelopeKeys.has(key));
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function toError(value) {
|
|
349
|
-
if (value instanceof Error) {
|
|
350
|
-
return value;
|
|
351
|
-
}
|
|
352
|
-
if (value && typeof value === "object" && typeof value.message === "string") {
|
|
353
|
-
return Object.assign(new Error(value.message), value);
|
|
354
|
-
}
|
|
355
|
-
return new Error(String(value));
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
export function assertServerId(id) {
|
|
359
|
-
if (typeof id !== "string" || id.length === 0) {
|
|
360
|
-
throw new TypeError("Server function id must be a non-empty string.");
|
|
361
|
-
}
|
|
362
|
-
}
|