@async/framework 0.11.1 → 0.11.2
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 +16 -0
- package/browser.js +184 -28
- package/browser.min.js +1 -1
- package/browser.ts +184 -28
- package/browser.umd.js +184 -28
- package/browser.umd.min.js +1 -1
- package/framework.ts +184 -28
- package/package.json +4 -3
- package/server.js +184 -28
package/browser.umd.js
CHANGED
|
@@ -196,6 +196,24 @@
|
|
|
196
196
|
};
|
|
197
197
|
},
|
|
198
198
|
|
|
199
|
+
_restore(snapshot = {}) {
|
|
200
|
+
if (!isAsyncSignalSnapshot(snapshot)) {
|
|
201
|
+
return state.set(snapshot);
|
|
202
|
+
}
|
|
203
|
+
if (activeAbort && !activeAbort.aborted) {
|
|
204
|
+
activeAbort.cancel(new Error(`Async signal "${registeredId}" restored from snapshot.`));
|
|
205
|
+
}
|
|
206
|
+
value = snapshot.value;
|
|
207
|
+
loading = Boolean(snapshot.loading);
|
|
208
|
+
error = snapshot.error ?? null;
|
|
209
|
+
status = typeof snapshot.status === "string" ? snapshot.status : inferStatus({ value, loading, error });
|
|
210
|
+
if (Number.isFinite(snapshot.version)) {
|
|
211
|
+
version = snapshot.version;
|
|
212
|
+
}
|
|
213
|
+
notify();
|
|
214
|
+
return state;
|
|
215
|
+
},
|
|
216
|
+
|
|
199
217
|
_bindRegistry(nextRegistry, nextId) {
|
|
200
218
|
registry = nextRegistry;
|
|
201
219
|
registeredId = nextId;
|
|
@@ -281,6 +299,27 @@
|
|
|
281
299
|
return Boolean(value?.[asyncSignalKind]);
|
|
282
300
|
}
|
|
283
301
|
|
|
302
|
+
function isAsyncSignalSnapshot(value) {
|
|
303
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
return Object.hasOwn(value, "value")
|
|
307
|
+
&& (Object.hasOwn(value, "loading")
|
|
308
|
+
|| Object.hasOwn(value, "error")
|
|
309
|
+
|| Object.hasOwn(value, "status")
|
|
310
|
+
|| Object.hasOwn(value, "version"));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function inferStatus({ value, loading, error }) {
|
|
314
|
+
if (loading) {
|
|
315
|
+
return "loading";
|
|
316
|
+
}
|
|
317
|
+
if (error) {
|
|
318
|
+
return "error";
|
|
319
|
+
}
|
|
320
|
+
return value === undefined ? "idle" : "ready";
|
|
321
|
+
}
|
|
322
|
+
|
|
284
323
|
function attachCancel(signal, controller) {
|
|
285
324
|
Object.defineProperty(signal, "cancel", {
|
|
286
325
|
configurable: true,
|
|
@@ -1919,12 +1958,16 @@
|
|
|
1919
1958
|
});
|
|
1920
1959
|
|
|
1921
1960
|
const output = Component.call(context, props);
|
|
1961
|
+
if (output && typeof output.then === "function") {
|
|
1962
|
+
throw new TypeError(`Component "${componentName(Component)}" returned a Promise. Async components are not supported by synchronous renderComponent(). Use an async partial or handler instead.`);
|
|
1963
|
+
}
|
|
1922
1964
|
const html = renderScopedTemplate(output);
|
|
1923
1965
|
|
|
1924
1966
|
return {
|
|
1925
1967
|
html,
|
|
1926
1968
|
attach(target) {
|
|
1927
|
-
for (
|
|
1969
|
+
for (let index = 0; index < attachHooks.length; index += 1) {
|
|
1970
|
+
const hook = attachHooks[index];
|
|
1928
1971
|
runtime.scheduler?.enqueue("lifecycle", () => {
|
|
1929
1972
|
const cleanup = hook(target);
|
|
1930
1973
|
if (typeof cleanup === "function") {
|
|
@@ -1932,7 +1975,7 @@
|
|
|
1932
1975
|
}
|
|
1933
1976
|
}, {
|
|
1934
1977
|
scope,
|
|
1935
|
-
key: `attach:${
|
|
1978
|
+
key: `attach:${index}`
|
|
1936
1979
|
}) ?? runAttachHook(hook, target);
|
|
1937
1980
|
}
|
|
1938
1981
|
},
|
|
@@ -1940,8 +1983,12 @@
|
|
|
1940
1983
|
this.attach(target);
|
|
1941
1984
|
},
|
|
1942
1985
|
visible(target, observeVisible) {
|
|
1943
|
-
|
|
1944
|
-
|
|
1986
|
+
if (visibleHooks.length === 0) {
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
const cleanup = observeVisible(target, () => {
|
|
1990
|
+
for (let index = 0; index < visibleHooks.length; index += 1) {
|
|
1991
|
+
const hook = visibleHooks[index];
|
|
1945
1992
|
runtime.scheduler?.enqueue("lifecycle", () => {
|
|
1946
1993
|
const hookCleanup = hook(target);
|
|
1947
1994
|
if (typeof hookCleanup === "function") {
|
|
@@ -1949,12 +1996,12 @@
|
|
|
1949
1996
|
}
|
|
1950
1997
|
}, {
|
|
1951
1998
|
scope,
|
|
1952
|
-
key: `visible:${
|
|
1999
|
+
key: `visible:${index}`
|
|
1953
2000
|
}) ?? runVisibleHook(hook, target);
|
|
1954
|
-
});
|
|
1955
|
-
if (typeof cleanup === "function") {
|
|
1956
|
-
cleanups.push(cleanup);
|
|
1957
2001
|
}
|
|
2002
|
+
});
|
|
2003
|
+
if (typeof cleanup === "function") {
|
|
2004
|
+
cleanups.push(cleanup);
|
|
1958
2005
|
}
|
|
1959
2006
|
},
|
|
1960
2007
|
cleanup() {
|
|
@@ -2210,11 +2257,12 @@
|
|
|
2210
2257
|
signal: context.abort
|
|
2211
2258
|
});
|
|
2212
2259
|
|
|
2260
|
+
assertTransportResponse(id, response);
|
|
2213
2261
|
if (!response.ok) {
|
|
2214
2262
|
throw new Error(`Server function "${id}" failed with ${response.status}.`);
|
|
2215
2263
|
}
|
|
2216
2264
|
|
|
2217
|
-
const result = await readServerResponse(response);
|
|
2265
|
+
const result = await readServerResponse(id, response);
|
|
2218
2266
|
await applyServerResult(result, runContext);
|
|
2219
2267
|
return markAppliedServerValue(unwrapServerResult(result));
|
|
2220
2268
|
}
|
|
@@ -2255,6 +2303,11 @@
|
|
|
2255
2303
|
return result;
|
|
2256
2304
|
}
|
|
2257
2305
|
|
|
2306
|
+
if (result.error) {
|
|
2307
|
+
markAppliedServerResult(result);
|
|
2308
|
+
throw toError(result.error);
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2258
2311
|
if (result.signals && context.signals) {
|
|
2259
2312
|
for (const [path, value] of Object.entries(result.signals)) {
|
|
2260
2313
|
context.signals.set?.(path, value);
|
|
@@ -2273,15 +2326,7 @@
|
|
|
2273
2326
|
await context.router?.navigate?.(result.redirect);
|
|
2274
2327
|
}
|
|
2275
2328
|
|
|
2276
|
-
|
|
2277
|
-
throw toError(result.error);
|
|
2278
|
-
}
|
|
2279
|
-
|
|
2280
|
-
Object.defineProperty(result, appliedServerResult, {
|
|
2281
|
-
configurable: true,
|
|
2282
|
-
enumerable: false,
|
|
2283
|
-
value: true
|
|
2284
|
-
});
|
|
2329
|
+
markAppliedServerResult(result);
|
|
2285
2330
|
|
|
2286
2331
|
return result;
|
|
2287
2332
|
}
|
|
@@ -2300,6 +2345,15 @@
|
|
|
2300
2345
|
return value;
|
|
2301
2346
|
}
|
|
2302
2347
|
|
|
2348
|
+
function markAppliedServerResult(result) {
|
|
2349
|
+
Object.defineProperty(result, appliedServerResult, {
|
|
2350
|
+
configurable: true,
|
|
2351
|
+
enumerable: false,
|
|
2352
|
+
value: true
|
|
2353
|
+
});
|
|
2354
|
+
return result;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2303
2357
|
function defaultInput(context = {}) {
|
|
2304
2358
|
const form = findForm(context);
|
|
2305
2359
|
if (form) {
|
|
@@ -2377,10 +2431,37 @@
|
|
|
2377
2431
|
return namespace([]);
|
|
2378
2432
|
}
|
|
2379
2433
|
|
|
2380
|
-
|
|
2434
|
+
function assertTransportResponse(id, response) {
|
|
2435
|
+
if (!response || typeof response !== "object") {
|
|
2436
|
+
throw new Error(`Server function "${id}" transport returned an invalid response: expected a fetch Response-like object.`);
|
|
2437
|
+
}
|
|
2438
|
+
if (typeof response.ok !== "boolean") {
|
|
2439
|
+
throw new Error(`Server function "${id}" transport returned an invalid response: missing boolean ok.`);
|
|
2440
|
+
}
|
|
2441
|
+
if (!response.headers || typeof response.headers.get !== "function") {
|
|
2442
|
+
throw new Error(`Server function "${id}" transport returned an invalid response: missing headers.get(name).`);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
async function readServerResponse(id, response) {
|
|
2447
|
+
if (response.status === 204) {
|
|
2448
|
+
return { value: undefined };
|
|
2449
|
+
}
|
|
2381
2450
|
const type = response.headers.get("content-type") ?? "";
|
|
2382
2451
|
if (type.includes("application/json")) {
|
|
2383
|
-
|
|
2452
|
+
if (typeof response.json !== "function") {
|
|
2453
|
+
throw new Error(`Server function "${id}" transport returned an invalid response: missing json().`);
|
|
2454
|
+
}
|
|
2455
|
+
try {
|
|
2456
|
+
return await response.json();
|
|
2457
|
+
} catch (cause) {
|
|
2458
|
+
throw new Error(`Server function "${id}" returned invalid JSON: ${errorMessage(cause)}`, {
|
|
2459
|
+
cause
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
if (typeof response.text !== "function") {
|
|
2464
|
+
throw new Error(`Server function "${id}" transport returned an invalid response: missing text().`);
|
|
2384
2465
|
}
|
|
2385
2466
|
return { value: await response.text() };
|
|
2386
2467
|
}
|
|
@@ -2493,6 +2574,9 @@
|
|
|
2493
2574
|
if (tag === "[object File]" || tag === "[object Blob]" || tag === "[object FormData]") {
|
|
2494
2575
|
throw new Error("Server proxy JSON transport does not support File, Blob, or FormData values yet.");
|
|
2495
2576
|
}
|
|
2577
|
+
if (isUnsupportedJsonTransportObject(value, tag)) {
|
|
2578
|
+
throw new Error("Server proxy JSON transport does not support URLSearchParams, Headers, Request, Response, ReadableStream, ArrayBuffer, or typed array values yet.");
|
|
2579
|
+
}
|
|
2496
2580
|
if (Array.isArray(value)) {
|
|
2497
2581
|
for (const item of value) {
|
|
2498
2582
|
assertJsonTransportable(item, stack);
|
|
@@ -2506,6 +2590,16 @@
|
|
|
2506
2590
|
stack.delete(value);
|
|
2507
2591
|
}
|
|
2508
2592
|
|
|
2593
|
+
function isUnsupportedJsonTransportObject(value, tag = Object.prototype.toString.call(value)) {
|
|
2594
|
+
return tag === "[object URLSearchParams]"
|
|
2595
|
+
|| tag === "[object Headers]"
|
|
2596
|
+
|| tag === "[object Request]"
|
|
2597
|
+
|| tag === "[object Response]"
|
|
2598
|
+
|| tag === "[object ReadableStream]"
|
|
2599
|
+
|| tag === "[object ArrayBuffer]"
|
|
2600
|
+
|| ArrayBuffer.isView(value);
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2509
2603
|
function joinEndpoint(endpoint, id) {
|
|
2510
2604
|
return `${String(endpoint).replace(/\/$/, "")}/${encodeURIComponent(id)}`;
|
|
2511
2605
|
}
|
|
@@ -2527,6 +2621,10 @@
|
|
|
2527
2621
|
return new Error(String(value));
|
|
2528
2622
|
}
|
|
2529
2623
|
|
|
2624
|
+
function errorMessage(error) {
|
|
2625
|
+
return error instanceof Error ? error.message : String(error);
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2530
2628
|
function assertServerId(id) {
|
|
2531
2629
|
if (typeof id !== "string" || id.length === 0) {
|
|
2532
2630
|
throw new TypeError("Server function id must be a non-empty string.");
|
|
@@ -2759,7 +2857,8 @@
|
|
|
2759
2857
|
const phases = [...(options.phases ?? defaultPhases)];
|
|
2760
2858
|
const queues = new Map(phases.map((phase) => [phase, []]));
|
|
2761
2859
|
const keyedJobs = new Map();
|
|
2762
|
-
const
|
|
2860
|
+
const destroyedObjectScopes = new WeakSet();
|
|
2861
|
+
const destroyedPrimitiveScopes = new Set();
|
|
2763
2862
|
const objectScopeIds = new WeakMap();
|
|
2764
2863
|
const onError = typeof options.onError === "function" ? options.onError : undefined;
|
|
2765
2864
|
const maxDepth = options.maxDepth ?? 100;
|
|
@@ -2807,7 +2906,7 @@
|
|
|
2807
2906
|
throw new TypeError("scheduler.enqueue(phase, fn) requires a function.");
|
|
2808
2907
|
}
|
|
2809
2908
|
const scope = options.scope;
|
|
2810
|
-
if (
|
|
2909
|
+
if (isScopeDestroyed(scope)) {
|
|
2811
2910
|
return noop;
|
|
2812
2911
|
}
|
|
2813
2912
|
|
|
@@ -2911,14 +3010,29 @@
|
|
|
2911
3010
|
|
|
2912
3011
|
markScopeDestroyed(scope) {
|
|
2913
3012
|
if (scope !== undefined) {
|
|
2914
|
-
|
|
3013
|
+
if (isObjectScope(scope)) {
|
|
3014
|
+
destroyedObjectScopes.add(scope);
|
|
3015
|
+
} else {
|
|
3016
|
+
destroyedPrimitiveScopes.add(scope);
|
|
3017
|
+
}
|
|
2915
3018
|
api.cancelScope(scope);
|
|
2916
3019
|
}
|
|
2917
3020
|
return api;
|
|
2918
3021
|
},
|
|
2919
3022
|
|
|
3023
|
+
reviveScope(scope) {
|
|
3024
|
+
if (scope !== undefined) {
|
|
3025
|
+
if (isObjectScope(scope)) {
|
|
3026
|
+
destroyedObjectScopes.delete(scope);
|
|
3027
|
+
} else {
|
|
3028
|
+
destroyedPrimitiveScopes.delete(scope);
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
return api;
|
|
3032
|
+
},
|
|
3033
|
+
|
|
2920
3034
|
isScopeDestroyed(scope) {
|
|
2921
|
-
return
|
|
3035
|
+
return isScopeDestroyed(scope);
|
|
2922
3036
|
},
|
|
2923
3037
|
|
|
2924
3038
|
inspect() {
|
|
@@ -2930,7 +3044,7 @@
|
|
|
2930
3044
|
strategy,
|
|
2931
3045
|
phases: [...phases],
|
|
2932
3046
|
pending: counts,
|
|
2933
|
-
scopesDestroyed:
|
|
3047
|
+
scopesDestroyed: destroyedPrimitiveScopes.size,
|
|
2934
3048
|
flushing,
|
|
2935
3049
|
scheduled
|
|
2936
3050
|
};
|
|
@@ -2945,7 +3059,7 @@
|
|
|
2945
3059
|
queue.length = 0;
|
|
2946
3060
|
}
|
|
2947
3061
|
keyedJobs.clear();
|
|
2948
|
-
|
|
3062
|
+
destroyedPrimitiveScopes.clear();
|
|
2949
3063
|
}
|
|
2950
3064
|
};
|
|
2951
3065
|
|
|
@@ -2985,7 +3099,7 @@
|
|
|
2985
3099
|
if (job.key) {
|
|
2986
3100
|
keyedJobs.delete(job.key);
|
|
2987
3101
|
}
|
|
2988
|
-
if (job.canceled || (job.scope
|
|
3102
|
+
if (job.canceled || isScopeDestroyed(job.scope)) {
|
|
2989
3103
|
continue;
|
|
2990
3104
|
}
|
|
2991
3105
|
try {
|
|
@@ -3042,6 +3156,20 @@
|
|
|
3042
3156
|
}
|
|
3043
3157
|
return String(scope);
|
|
3044
3158
|
}
|
|
3159
|
+
|
|
3160
|
+
function isScopeDestroyed(scope) {
|
|
3161
|
+
if (scope === undefined) {
|
|
3162
|
+
return false;
|
|
3163
|
+
}
|
|
3164
|
+
if (isObjectScope(scope)) {
|
|
3165
|
+
return destroyedObjectScopes.has(scope);
|
|
3166
|
+
}
|
|
3167
|
+
return destroyedPrimitiveScopes.has(scope);
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
function isObjectScope(scope) {
|
|
3172
|
+
return (typeof scope === "object" && scope !== null) || typeof scope === "function";
|
|
3045
3173
|
}
|
|
3046
3174
|
|
|
3047
3175
|
function scheduleMicrotask(fn) {
|
|
@@ -3102,6 +3230,7 @@
|
|
|
3102
3230
|
|
|
3103
3231
|
scan(rootOrFragment = rootNode) {
|
|
3104
3232
|
assertActive();
|
|
3233
|
+
reviveScopes(rootOrFragment);
|
|
3105
3234
|
bindSignalAttributes(rootOrFragment);
|
|
3106
3235
|
bindClassAttributes(rootOrFragment);
|
|
3107
3236
|
bindEventAttributes(rootOrFragment);
|
|
@@ -3605,6 +3734,12 @@
|
|
|
3605
3734
|
}
|
|
3606
3735
|
}
|
|
3607
3736
|
|
|
3737
|
+
function reviveScopes(scope) {
|
|
3738
|
+
for (const element of elementsIn(scope)) {
|
|
3739
|
+
schedulerInstance.reviveScope?.(element);
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3608
3743
|
return api;
|
|
3609
3744
|
}
|
|
3610
3745
|
|
|
@@ -4168,7 +4303,10 @@
|
|
|
4168
4303
|
}
|
|
4169
4304
|
const matched = api.match(url);
|
|
4170
4305
|
if (matched?.route?.partial && partials?.resolve?.(matched.route.partial)) {
|
|
4171
|
-
return partials.render(matched.route.partial, matched.params,
|
|
4306
|
+
return partials.render(matched.route.partial, matched.params, {
|
|
4307
|
+
...contextFor(matched),
|
|
4308
|
+
prefetch: true
|
|
4309
|
+
});
|
|
4172
4310
|
}
|
|
4173
4311
|
return Promise.resolve(null);
|
|
4174
4312
|
},
|
|
@@ -5204,6 +5342,13 @@
|
|
|
5204
5342
|
function setOrRegisterSignal(signals, path, value) {
|
|
5205
5343
|
const id = String(path).split(".")[0];
|
|
5206
5344
|
if (signals.has?.(id)) {
|
|
5345
|
+
if (path === id) {
|
|
5346
|
+
const entry = signals._entry?.(id);
|
|
5347
|
+
if (typeof entry?._restore === "function" && isAsyncSignalSnapshot(value)) {
|
|
5348
|
+
entry._restore(value);
|
|
5349
|
+
return;
|
|
5350
|
+
}
|
|
5351
|
+
}
|
|
5207
5352
|
signals.set(path, value);
|
|
5208
5353
|
return;
|
|
5209
5354
|
}
|
|
@@ -5213,6 +5358,17 @@
|
|
|
5213
5358
|
}
|
|
5214
5359
|
}
|
|
5215
5360
|
|
|
5361
|
+
function isAsyncSignalSnapshot(value) {
|
|
5362
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5363
|
+
return false;
|
|
5364
|
+
}
|
|
5365
|
+
return Object.hasOwn(value, "value")
|
|
5366
|
+
&& (Object.hasOwn(value, "loading")
|
|
5367
|
+
|| Object.hasOwn(value, "error")
|
|
5368
|
+
|| Object.hasOwn(value, "status")
|
|
5369
|
+
|| Object.hasOwn(value, "version"));
|
|
5370
|
+
}
|
|
5371
|
+
|
|
5216
5372
|
function attachServerCache(server, cache) {
|
|
5217
5373
|
try {
|
|
5218
5374
|
server.cache = cache;
|