@async/framework 0.10.0 → 0.10.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 +24 -1
- package/browser.js +126 -50
- package/browser.min.js +1 -1
- package/browser.ts +126 -50
- package/browser.umd.js +126 -50
- package/browser.umd.min.js +1 -1
- package/package.json +11 -4
- package/src/cache.js +18 -14
- package/src/lazy-registry.js +16 -2
- package/src/loader.js +7 -0
- package/src/router.js +74 -28
- package/src/server.js +11 -6
package/src/cache.js
CHANGED
|
@@ -47,15 +47,7 @@ export function createCacheRegistry(initialMap = {}, { now = () => Date.now(), r
|
|
|
47
47
|
|
|
48
48
|
get(key) {
|
|
49
49
|
assertKey(key);
|
|
50
|
-
|
|
51
|
-
if (!entry) {
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
if (entry.expiresAt !== undefined && entry.expiresAt <= now()) {
|
|
55
|
-
entries.delete(key);
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
return entry.value;
|
|
50
|
+
return readEntry(key).value;
|
|
59
51
|
},
|
|
60
52
|
|
|
61
53
|
set(key, value, options = {}) {
|
|
@@ -73,9 +65,9 @@ export function createCacheRegistry(initialMap = {}, { now = () => Date.now(), r
|
|
|
73
65
|
if (typeof fn !== "function") {
|
|
74
66
|
throw new TypeError("cache.getOrSet(key, fn) requires a function.");
|
|
75
67
|
}
|
|
76
|
-
const cached =
|
|
77
|
-
if (cached
|
|
78
|
-
return cached;
|
|
68
|
+
const cached = readEntry(key);
|
|
69
|
+
if (cached.found) {
|
|
70
|
+
return cached.value;
|
|
79
71
|
}
|
|
80
72
|
if (pending.has(key)) {
|
|
81
73
|
return pending.get(key);
|
|
@@ -126,8 +118,8 @@ export function createCacheRegistry(initialMap = {}, { now = () => Date.now(), r
|
|
|
126
118
|
snapshot() {
|
|
127
119
|
const snapshot = {};
|
|
128
120
|
for (const [key] of entries) {
|
|
129
|
-
const value =
|
|
130
|
-
if (value !== undefined) {
|
|
121
|
+
const { found, value } = readEntry(key);
|
|
122
|
+
if (found && value !== undefined) {
|
|
131
123
|
snapshot[key] = value;
|
|
132
124
|
}
|
|
133
125
|
}
|
|
@@ -167,6 +159,18 @@ export function createCacheRegistry(initialMap = {}, { now = () => Date.now(), r
|
|
|
167
159
|
const prefix = key.split(":")[0];
|
|
168
160
|
return definitions.get(prefix);
|
|
169
161
|
}
|
|
162
|
+
|
|
163
|
+
function readEntry(key) {
|
|
164
|
+
const entry = entries.get(key);
|
|
165
|
+
if (!entry) {
|
|
166
|
+
return { found: false, value: undefined };
|
|
167
|
+
}
|
|
168
|
+
if (entry.expiresAt !== undefined && entry.expiresAt <= now()) {
|
|
169
|
+
entries.delete(key);
|
|
170
|
+
return { found: false, value: undefined };
|
|
171
|
+
}
|
|
172
|
+
return { found: true, value: entry.value };
|
|
173
|
+
}
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
function normalizeDefinition(definition) {
|
package/src/lazy-registry.js
CHANGED
|
@@ -33,10 +33,20 @@ export function createLazyRegistry(options = {}) {
|
|
|
33
33
|
const resolved = resolveDescriptorUrl(type, id, descriptor, registryAssets);
|
|
34
34
|
let modulePromise = moduleCache.get(resolved.moduleUrl);
|
|
35
35
|
if (!modulePromise) {
|
|
36
|
-
modulePromise = Promise.resolve(importModule(resolved.moduleUrl));
|
|
36
|
+
modulePromise = Promise.resolve().then(() => importModule(resolved.moduleUrl));
|
|
37
37
|
moduleCache.set(resolved.moduleUrl, modulePromise);
|
|
38
38
|
}
|
|
39
|
-
|
|
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
|
+
}
|
|
40
50
|
const value = resolveExport(module, resolved.exportNames, type, id);
|
|
41
51
|
exportCache.set(cacheKey, value);
|
|
42
52
|
return value;
|
|
@@ -196,6 +206,10 @@ function isAbsoluteUrl(value) {
|
|
|
196
206
|
return /^[A-Za-z][A-Za-z\d+.-]*:/.test(value);
|
|
197
207
|
}
|
|
198
208
|
|
|
209
|
+
function errorMessage(error) {
|
|
210
|
+
return error instanceof Error ? error.message : String(error);
|
|
211
|
+
}
|
|
212
|
+
|
|
199
213
|
function stableStringify(value) {
|
|
200
214
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
201
215
|
return JSON.stringify(value);
|
package/src/loader.js
CHANGED
|
@@ -90,6 +90,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
destroyed = true;
|
|
93
|
+
markDestroyedScopes(rootNode);
|
|
93
94
|
for (const cleanup of [...cleanups]) {
|
|
94
95
|
runCleanup(cleanup);
|
|
95
96
|
}
|
|
@@ -540,6 +541,12 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
540
541
|
});
|
|
541
542
|
}
|
|
542
543
|
|
|
544
|
+
function markDestroyedScopes(scope) {
|
|
545
|
+
for (const element of elementsIn(scope)) {
|
|
546
|
+
schedulerInstance.markScopeDestroyed(element);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
543
550
|
return api;
|
|
544
551
|
}
|
|
545
552
|
|
package/src/router.js
CHANGED
|
@@ -61,7 +61,7 @@ export function createRouteRegistry(initialMap = {}, options = {}) {
|
|
|
61
61
|
}
|
|
62
62
|
const params = {};
|
|
63
63
|
candidate.keys.forEach((key, index) => {
|
|
64
|
-
params[key] =
|
|
64
|
+
params[key] = safeDecodeURIComponent(match[index + 1] ?? "");
|
|
65
65
|
});
|
|
66
66
|
return {
|
|
67
67
|
pattern: candidate.pattern,
|
|
@@ -176,11 +176,11 @@ export function createRouter({
|
|
|
176
176
|
}
|
|
177
177
|
bindNavigation();
|
|
178
178
|
if (mode === "csr") {
|
|
179
|
-
|
|
179
|
+
handleNavigation(api.navigate(currentUrl(), {
|
|
180
180
|
replace: true,
|
|
181
181
|
initial: true,
|
|
182
182
|
source: "client"
|
|
183
|
-
})
|
|
183
|
+
}));
|
|
184
184
|
return api;
|
|
185
185
|
}
|
|
186
186
|
updateStateFromLocation();
|
|
@@ -245,7 +245,7 @@ export function createRouter({
|
|
|
245
245
|
return;
|
|
246
246
|
}
|
|
247
247
|
event.preventDefault();
|
|
248
|
-
api.navigate(anchor.href);
|
|
248
|
+
handleNavigation(api.navigate(anchor.href));
|
|
249
249
|
};
|
|
250
250
|
const submit = (event) => {
|
|
251
251
|
const form = closest(event.target, "form");
|
|
@@ -253,9 +253,9 @@ export function createRouter({
|
|
|
253
253
|
return;
|
|
254
254
|
}
|
|
255
255
|
event.preventDefault();
|
|
256
|
-
api.navigate(formActionUrl(form));
|
|
256
|
+
handleNavigation(api.navigate(formActionUrl(form)));
|
|
257
257
|
};
|
|
258
|
-
const popstate = () => api.navigate(currentUrl(), { history: false });
|
|
258
|
+
const popstate = () => handleNavigation(api.navigate(currentUrl(), { history: false }));
|
|
259
259
|
|
|
260
260
|
rootNode.addEventListener?.("click", click);
|
|
261
261
|
rootNode.addEventListener?.("submit", submit);
|
|
@@ -448,6 +448,19 @@ export function createRouter({
|
|
|
448
448
|
}
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
+
function handleNavigation(promise) {
|
|
452
|
+
void promise.catch((error) => {
|
|
453
|
+
if (destroyed) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
setRouterState({
|
|
457
|
+
pending: false,
|
|
458
|
+
error
|
|
459
|
+
});
|
|
460
|
+
dispatchAsyncError(rootNode, error);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
451
464
|
function currentUrl() {
|
|
452
465
|
return resolveUrl(documentRef.defaultView?.location?.href ?? "http://localhost/");
|
|
453
466
|
}
|
|
@@ -464,6 +477,33 @@ export function createRouter({
|
|
|
464
477
|
throw new Error("Router has been destroyed.");
|
|
465
478
|
}
|
|
466
479
|
}
|
|
480
|
+
|
|
481
|
+
function shouldIgnoreLink(event, anchor) {
|
|
482
|
+
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
if (anchor.target || anchor.hasAttribute("download")) {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
const target = resolveUrl(anchor.href);
|
|
489
|
+
const current = currentUrl();
|
|
490
|
+
if (target.origin !== current.origin) {
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
return isHashOnlyNavigation(target, current, anchor);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function shouldIgnoreForm(form) {
|
|
497
|
+
const method = String(form.method || "get").toLowerCase();
|
|
498
|
+
return method !== "get" || resolveUrl(form.action).origin !== currentUrl().origin;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function formActionUrl(form) {
|
|
502
|
+
const url = resolveUrl(form.action || form.ownerDocument.defaultView.location.href);
|
|
503
|
+
const formData = new form.ownerDocument.defaultView.FormData(form);
|
|
504
|
+
url.search = new URLSearchParams(formData).toString();
|
|
505
|
+
return url.href;
|
|
506
|
+
}
|
|
467
507
|
}
|
|
468
508
|
|
|
469
509
|
function normalizeRoute(pattern, definition) {
|
|
@@ -501,28 +541,6 @@ function compilePattern(pattern) {
|
|
|
501
541
|
return { regex: new RegExp(`^${source}$`), keys };
|
|
502
542
|
}
|
|
503
543
|
|
|
504
|
-
function shouldIgnoreLink(event, anchor) {
|
|
505
|
-
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
506
|
-
return true;
|
|
507
|
-
}
|
|
508
|
-
if (anchor.target || anchor.hasAttribute("download")) {
|
|
509
|
-
return true;
|
|
510
|
-
}
|
|
511
|
-
return toUrl(anchor.href).origin !== toUrl(anchor.ownerDocument.defaultView.location.href).origin;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function shouldIgnoreForm(form) {
|
|
515
|
-
const method = String(form.method || "get").toLowerCase();
|
|
516
|
-
return method !== "get" || toUrl(form.action).origin !== toUrl(form.ownerDocument.defaultView.location.href).origin;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function formActionUrl(form) {
|
|
520
|
-
const url = toUrl(form.action || form.ownerDocument.defaultView.location.href);
|
|
521
|
-
const formData = new form.ownerDocument.defaultView.FormData(form);
|
|
522
|
-
url.search = new URLSearchParams(formData).toString();
|
|
523
|
-
return url.href;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
544
|
function closest(target, selector) {
|
|
527
545
|
return target?.closest?.(selector);
|
|
528
546
|
}
|
|
@@ -538,6 +556,34 @@ function queryObject(url) {
|
|
|
538
556
|
return Object.fromEntries(url.searchParams.entries());
|
|
539
557
|
}
|
|
540
558
|
|
|
559
|
+
function safeDecodeURIComponent(value) {
|
|
560
|
+
try {
|
|
561
|
+
return decodeURIComponent(value);
|
|
562
|
+
} catch {
|
|
563
|
+
return value;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function isHashOnlyNavigation(target, current, anchor) {
|
|
568
|
+
if (target.origin !== current.origin || target.pathname !== current.pathname || target.search !== current.search) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
return target.hash !== current.hash || anchor.getAttribute?.("href")?.startsWith("#") === true;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function dispatchAsyncError(element, error) {
|
|
575
|
+
const EventCtor = element.ownerDocument?.defaultView?.CustomEvent ?? globalThis.CustomEvent;
|
|
576
|
+
if (typeof EventCtor !== "function") {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
element.dispatchEvent?.(
|
|
580
|
+
new EventCtor("async:error", {
|
|
581
|
+
bubbles: true,
|
|
582
|
+
detail: { error }
|
|
583
|
+
})
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
541
587
|
function escapeRegExp(value) {
|
|
542
588
|
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
543
589
|
}
|
package/src/server.js
CHANGED
|
@@ -305,14 +305,17 @@ function formDataToObject(formData) {
|
|
|
305
305
|
return output;
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
function assertJsonTransportable(value,
|
|
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
|
+
}
|
|
309
312
|
if (value == null || typeof value !== "object") {
|
|
310
313
|
return;
|
|
311
314
|
}
|
|
312
|
-
if (
|
|
313
|
-
|
|
315
|
+
if (stack.has(value)) {
|
|
316
|
+
throw new Error("Server proxy JSON transport does not support circular values.");
|
|
314
317
|
}
|
|
315
|
-
|
|
318
|
+
stack.add(value);
|
|
316
319
|
|
|
317
320
|
const tag = Object.prototype.toString.call(value);
|
|
318
321
|
if (tag === "[object File]" || tag === "[object Blob]" || tag === "[object FormData]") {
|
|
@@ -320,13 +323,15 @@ function assertJsonTransportable(value, seen = new Set()) {
|
|
|
320
323
|
}
|
|
321
324
|
if (Array.isArray(value)) {
|
|
322
325
|
for (const item of value) {
|
|
323
|
-
assertJsonTransportable(item,
|
|
326
|
+
assertJsonTransportable(item, stack);
|
|
324
327
|
}
|
|
328
|
+
stack.delete(value);
|
|
325
329
|
return;
|
|
326
330
|
}
|
|
327
331
|
for (const item of Object.values(value)) {
|
|
328
|
-
assertJsonTransportable(item,
|
|
332
|
+
assertJsonTransportable(item, stack);
|
|
329
333
|
}
|
|
334
|
+
stack.delete(value);
|
|
330
335
|
}
|
|
331
336
|
|
|
332
337
|
function joinEndpoint(endpoint, id) {
|