@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/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.10.2 - 2026-06-17
|
|
4
|
+
|
|
5
|
+
- Fixed intercepted router link, form, and popstate navigation failures so they
|
|
6
|
+
update router error state and do not create unhandled promise rejections.
|
|
7
|
+
- Preserved native same-document hash link behavior and made malformed encoded
|
|
8
|
+
dynamic route params fall back to the raw segment instead of throwing.
|
|
9
|
+
- Hardened rootless detach cleanup, lazy descriptor import retry, server JSON
|
|
10
|
+
transport validation, and `cache.getOrSet(...)` handling for cached
|
|
11
|
+
`undefined` values.
|
|
12
|
+
- Expanded regression coverage for scheduler reentrancy, boundary receiver
|
|
13
|
+
patch shapes, root lifecycle, lazy descriptors, server transport edge cases,
|
|
14
|
+
component lifecycle ordering, and installed package export-map shape.
|
|
15
|
+
- Bundle size from bundled TypeScript source: `browser.ts` 173,774 B raw /
|
|
16
|
+
32,727 B gzip -> `browser.min.js` 73,680 B raw / 22,047 B gzip
|
|
17
|
+
(-100,094 B raw, -10,680 B gzip).
|
|
18
|
+
|
|
19
|
+
## 0.10.1 - 2026-06-17
|
|
20
|
+
|
|
21
|
+
- Added Terser-powered browser bundle minification and pointed legacy
|
|
22
|
+
`module`/`browser` analyzer fields plus the root `exports.browser` condition
|
|
23
|
+
at `browser.min.js`.
|
|
24
|
+
- Bundle size from bundled TypeScript source: `browser.ts` 171,471 B raw /
|
|
25
|
+
32,301 B gzip -> `browser.min.js` 72,753 B raw / 21,763 B gzip
|
|
26
|
+
(-98,718 B raw, -10,538 B gzip).
|
|
4
27
|
|
|
5
28
|
## 0.10.0 - 2026-06-17
|
|
6
29
|
|
package/browser.js
CHANGED
|
@@ -318,10 +318,20 @@ const __lazyRegistryModule = (() => {
|
|
|
318
318
|
const resolved = resolveDescriptorUrl(type, id, descriptor, registryAssets);
|
|
319
319
|
let modulePromise = moduleCache.get(resolved.moduleUrl);
|
|
320
320
|
if (!modulePromise) {
|
|
321
|
-
modulePromise = Promise.resolve(importModule(resolved.moduleUrl));
|
|
321
|
+
modulePromise = Promise.resolve().then(() => importModule(resolved.moduleUrl));
|
|
322
322
|
moduleCache.set(resolved.moduleUrl, modulePromise);
|
|
323
323
|
}
|
|
324
|
-
|
|
324
|
+
let module;
|
|
325
|
+
try {
|
|
326
|
+
module = await modulePromise;
|
|
327
|
+
} catch (cause) {
|
|
328
|
+
if (moduleCache.get(resolved.moduleUrl) === modulePromise) {
|
|
329
|
+
moduleCache.delete(resolved.moduleUrl);
|
|
330
|
+
}
|
|
331
|
+
throw new Error(`Lazy ${type} "${id}" failed to import ${resolved.moduleUrl}: ${errorMessage(cause)}`, {
|
|
332
|
+
cause
|
|
333
|
+
});
|
|
334
|
+
}
|
|
325
335
|
const value = resolveExport(module, resolved.exportNames, type, id);
|
|
326
336
|
exportCache.set(cacheKey, value);
|
|
327
337
|
return value;
|
|
@@ -481,6 +491,10 @@ const __lazyRegistryModule = (() => {
|
|
|
481
491
|
return /^[A-Za-z][A-Za-z\d+.-]*:/.test(value);
|
|
482
492
|
}
|
|
483
493
|
|
|
494
|
+
function errorMessage(error) {
|
|
495
|
+
return error instanceof Error ? error.message : String(error);
|
|
496
|
+
}
|
|
497
|
+
|
|
484
498
|
function stableStringify(value) {
|
|
485
499
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
486
500
|
return JSON.stringify(value);
|
|
@@ -809,15 +823,7 @@ const __cacheModule = (() => {
|
|
|
809
823
|
|
|
810
824
|
get(key) {
|
|
811
825
|
assertKey(key);
|
|
812
|
-
|
|
813
|
-
if (!entry) {
|
|
814
|
-
return undefined;
|
|
815
|
-
}
|
|
816
|
-
if (entry.expiresAt !== undefined && entry.expiresAt <= now()) {
|
|
817
|
-
entries.delete(key);
|
|
818
|
-
return undefined;
|
|
819
|
-
}
|
|
820
|
-
return entry.value;
|
|
826
|
+
return readEntry(key).value;
|
|
821
827
|
},
|
|
822
828
|
|
|
823
829
|
set(key, value, options = {}) {
|
|
@@ -835,9 +841,9 @@ const __cacheModule = (() => {
|
|
|
835
841
|
if (typeof fn !== "function") {
|
|
836
842
|
throw new TypeError("cache.getOrSet(key, fn) requires a function.");
|
|
837
843
|
}
|
|
838
|
-
const cached =
|
|
839
|
-
if (cached
|
|
840
|
-
return cached;
|
|
844
|
+
const cached = readEntry(key);
|
|
845
|
+
if (cached.found) {
|
|
846
|
+
return cached.value;
|
|
841
847
|
}
|
|
842
848
|
if (pending.has(key)) {
|
|
843
849
|
return pending.get(key);
|
|
@@ -888,8 +894,8 @@ const __cacheModule = (() => {
|
|
|
888
894
|
snapshot() {
|
|
889
895
|
const snapshot = {};
|
|
890
896
|
for (const [key] of entries) {
|
|
891
|
-
const value =
|
|
892
|
-
if (value !== undefined) {
|
|
897
|
+
const { found, value } = readEntry(key);
|
|
898
|
+
if (found && value !== undefined) {
|
|
893
899
|
snapshot[key] = value;
|
|
894
900
|
}
|
|
895
901
|
}
|
|
@@ -929,6 +935,18 @@ const __cacheModule = (() => {
|
|
|
929
935
|
const prefix = key.split(":")[0];
|
|
930
936
|
return definitions.get(prefix);
|
|
931
937
|
}
|
|
938
|
+
|
|
939
|
+
function readEntry(key) {
|
|
940
|
+
const entry = entries.get(key);
|
|
941
|
+
if (!entry) {
|
|
942
|
+
return { found: false, value: undefined };
|
|
943
|
+
}
|
|
944
|
+
if (entry.expiresAt !== undefined && entry.expiresAt <= now()) {
|
|
945
|
+
entries.delete(key);
|
|
946
|
+
return { found: false, value: undefined };
|
|
947
|
+
}
|
|
948
|
+
return { found: true, value: entry.value };
|
|
949
|
+
}
|
|
932
950
|
}
|
|
933
951
|
|
|
934
952
|
function normalizeDefinition(definition) {
|
|
@@ -2448,14 +2466,17 @@ const __serverModule = (() => {
|
|
|
2448
2466
|
return output;
|
|
2449
2467
|
}
|
|
2450
2468
|
|
|
2451
|
-
function assertJsonTransportable(value,
|
|
2469
|
+
function assertJsonTransportable(value, stack = new Set()) {
|
|
2470
|
+
if (typeof value === "bigint") {
|
|
2471
|
+
throw new Error("Server proxy JSON transport does not support BigInt values.");
|
|
2472
|
+
}
|
|
2452
2473
|
if (value == null || typeof value !== "object") {
|
|
2453
2474
|
return;
|
|
2454
2475
|
}
|
|
2455
|
-
if (
|
|
2456
|
-
|
|
2476
|
+
if (stack.has(value)) {
|
|
2477
|
+
throw new Error("Server proxy JSON transport does not support circular values.");
|
|
2457
2478
|
}
|
|
2458
|
-
|
|
2479
|
+
stack.add(value);
|
|
2459
2480
|
|
|
2460
2481
|
const tag = Object.prototype.toString.call(value);
|
|
2461
2482
|
if (tag === "[object File]" || tag === "[object Blob]" || tag === "[object FormData]") {
|
|
@@ -2463,13 +2484,15 @@ const __serverModule = (() => {
|
|
|
2463
2484
|
}
|
|
2464
2485
|
if (Array.isArray(value)) {
|
|
2465
2486
|
for (const item of value) {
|
|
2466
|
-
assertJsonTransportable(item,
|
|
2487
|
+
assertJsonTransportable(item, stack);
|
|
2467
2488
|
}
|
|
2489
|
+
stack.delete(value);
|
|
2468
2490
|
return;
|
|
2469
2491
|
}
|
|
2470
2492
|
for (const item of Object.values(value)) {
|
|
2471
|
-
assertJsonTransportable(item,
|
|
2493
|
+
assertJsonTransportable(item, stack);
|
|
2472
2494
|
}
|
|
2495
|
+
stack.delete(value);
|
|
2473
2496
|
}
|
|
2474
2497
|
|
|
2475
2498
|
function joinEndpoint(endpoint, id) {
|
|
@@ -3114,6 +3137,7 @@ const __loaderModule = (() => {
|
|
|
3114
3137
|
return;
|
|
3115
3138
|
}
|
|
3116
3139
|
destroyed = true;
|
|
3140
|
+
markDestroyedScopes(rootNode);
|
|
3117
3141
|
for (const cleanup of [...cleanups]) {
|
|
3118
3142
|
runCleanup(cleanup);
|
|
3119
3143
|
}
|
|
@@ -3564,6 +3588,12 @@ const __loaderModule = (() => {
|
|
|
3564
3588
|
});
|
|
3565
3589
|
}
|
|
3566
3590
|
|
|
3591
|
+
function markDestroyedScopes(scope) {
|
|
3592
|
+
for (const element of elementsIn(scope)) {
|
|
3593
|
+
schedulerInstance.markScopeDestroyed(element);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3567
3597
|
return api;
|
|
3568
3598
|
}
|
|
3569
3599
|
|
|
@@ -3989,7 +4019,7 @@ const __routerModule = (() => {
|
|
|
3989
4019
|
}
|
|
3990
4020
|
const params = {};
|
|
3991
4021
|
candidate.keys.forEach((key, index) => {
|
|
3992
|
-
params[key] =
|
|
4022
|
+
params[key] = safeDecodeURIComponent(match[index + 1] ?? "");
|
|
3993
4023
|
});
|
|
3994
4024
|
return {
|
|
3995
4025
|
pattern: candidate.pattern,
|
|
@@ -4104,11 +4134,11 @@ const __routerModule = (() => {
|
|
|
4104
4134
|
}
|
|
4105
4135
|
bindNavigation();
|
|
4106
4136
|
if (mode === "csr") {
|
|
4107
|
-
|
|
4137
|
+
handleNavigation(api.navigate(currentUrl(), {
|
|
4108
4138
|
replace: true,
|
|
4109
4139
|
initial: true,
|
|
4110
4140
|
source: "client"
|
|
4111
|
-
})
|
|
4141
|
+
}));
|
|
4112
4142
|
return api;
|
|
4113
4143
|
}
|
|
4114
4144
|
updateStateFromLocation();
|
|
@@ -4173,7 +4203,7 @@ const __routerModule = (() => {
|
|
|
4173
4203
|
return;
|
|
4174
4204
|
}
|
|
4175
4205
|
event.preventDefault();
|
|
4176
|
-
api.navigate(anchor.href);
|
|
4206
|
+
handleNavigation(api.navigate(anchor.href));
|
|
4177
4207
|
};
|
|
4178
4208
|
const submit = (event) => {
|
|
4179
4209
|
const form = closest(event.target, "form");
|
|
@@ -4181,9 +4211,9 @@ const __routerModule = (() => {
|
|
|
4181
4211
|
return;
|
|
4182
4212
|
}
|
|
4183
4213
|
event.preventDefault();
|
|
4184
|
-
api.navigate(formActionUrl(form));
|
|
4214
|
+
handleNavigation(api.navigate(formActionUrl(form)));
|
|
4185
4215
|
};
|
|
4186
|
-
const popstate = () => api.navigate(currentUrl(), { history: false });
|
|
4216
|
+
const popstate = () => handleNavigation(api.navigate(currentUrl(), { history: false }));
|
|
4187
4217
|
|
|
4188
4218
|
rootNode.addEventListener?.("click", click);
|
|
4189
4219
|
rootNode.addEventListener?.("submit", submit);
|
|
@@ -4376,6 +4406,19 @@ const __routerModule = (() => {
|
|
|
4376
4406
|
}
|
|
4377
4407
|
}
|
|
4378
4408
|
|
|
4409
|
+
function handleNavigation(promise) {
|
|
4410
|
+
void promise.catch((error) => {
|
|
4411
|
+
if (destroyed) {
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
setRouterState({
|
|
4415
|
+
pending: false,
|
|
4416
|
+
error
|
|
4417
|
+
});
|
|
4418
|
+
dispatchAsyncError(rootNode, error);
|
|
4419
|
+
});
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4379
4422
|
function currentUrl() {
|
|
4380
4423
|
return resolveUrl(documentRef.defaultView?.location?.href ?? "http://localhost/");
|
|
4381
4424
|
}
|
|
@@ -4392,6 +4435,33 @@ const __routerModule = (() => {
|
|
|
4392
4435
|
throw new Error("Router has been destroyed.");
|
|
4393
4436
|
}
|
|
4394
4437
|
}
|
|
4438
|
+
|
|
4439
|
+
function shouldIgnoreLink(event, anchor) {
|
|
4440
|
+
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
4441
|
+
return true;
|
|
4442
|
+
}
|
|
4443
|
+
if (anchor.target || anchor.hasAttribute("download")) {
|
|
4444
|
+
return true;
|
|
4445
|
+
}
|
|
4446
|
+
const target = resolveUrl(anchor.href);
|
|
4447
|
+
const current = currentUrl();
|
|
4448
|
+
if (target.origin !== current.origin) {
|
|
4449
|
+
return true;
|
|
4450
|
+
}
|
|
4451
|
+
return isHashOnlyNavigation(target, current, anchor);
|
|
4452
|
+
}
|
|
4453
|
+
|
|
4454
|
+
function shouldIgnoreForm(form) {
|
|
4455
|
+
const method = String(form.method || "get").toLowerCase();
|
|
4456
|
+
return method !== "get" || resolveUrl(form.action).origin !== currentUrl().origin;
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
function formActionUrl(form) {
|
|
4460
|
+
const url = resolveUrl(form.action || form.ownerDocument.defaultView.location.href);
|
|
4461
|
+
const formData = new form.ownerDocument.defaultView.FormData(form);
|
|
4462
|
+
url.search = new URLSearchParams(formData).toString();
|
|
4463
|
+
return url.href;
|
|
4464
|
+
}
|
|
4395
4465
|
}
|
|
4396
4466
|
|
|
4397
4467
|
function normalizeRoute(pattern, definition) {
|
|
@@ -4429,28 +4499,6 @@ const __routerModule = (() => {
|
|
|
4429
4499
|
return { regex: new RegExp(`^${source}$`), keys };
|
|
4430
4500
|
}
|
|
4431
4501
|
|
|
4432
|
-
function shouldIgnoreLink(event, anchor) {
|
|
4433
|
-
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
4434
|
-
return true;
|
|
4435
|
-
}
|
|
4436
|
-
if (anchor.target || anchor.hasAttribute("download")) {
|
|
4437
|
-
return true;
|
|
4438
|
-
}
|
|
4439
|
-
return toUrl(anchor.href).origin !== toUrl(anchor.ownerDocument.defaultView.location.href).origin;
|
|
4440
|
-
}
|
|
4441
|
-
|
|
4442
|
-
function shouldIgnoreForm(form) {
|
|
4443
|
-
const method = String(form.method || "get").toLowerCase();
|
|
4444
|
-
return method !== "get" || toUrl(form.action).origin !== toUrl(form.ownerDocument.defaultView.location.href).origin;
|
|
4445
|
-
}
|
|
4446
|
-
|
|
4447
|
-
function formActionUrl(form) {
|
|
4448
|
-
const url = toUrl(form.action || form.ownerDocument.defaultView.location.href);
|
|
4449
|
-
const formData = new form.ownerDocument.defaultView.FormData(form);
|
|
4450
|
-
url.search = new URLSearchParams(formData).toString();
|
|
4451
|
-
return url.href;
|
|
4452
|
-
}
|
|
4453
|
-
|
|
4454
4502
|
function closest(target, selector) {
|
|
4455
4503
|
return target?.closest?.(selector);
|
|
4456
4504
|
}
|
|
@@ -4466,6 +4514,34 @@ const __routerModule = (() => {
|
|
|
4466
4514
|
return Object.fromEntries(url.searchParams.entries());
|
|
4467
4515
|
}
|
|
4468
4516
|
|
|
4517
|
+
function safeDecodeURIComponent(value) {
|
|
4518
|
+
try {
|
|
4519
|
+
return decodeURIComponent(value);
|
|
4520
|
+
} catch {
|
|
4521
|
+
return value;
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
|
|
4525
|
+
function isHashOnlyNavigation(target, current, anchor) {
|
|
4526
|
+
if (target.origin !== current.origin || target.pathname !== current.pathname || target.search !== current.search) {
|
|
4527
|
+
return false;
|
|
4528
|
+
}
|
|
4529
|
+
return target.hash !== current.hash || anchor.getAttribute?.("href")?.startsWith("#") === true;
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
function dispatchAsyncError(element, error) {
|
|
4533
|
+
const EventCtor = element.ownerDocument?.defaultView?.CustomEvent ?? globalThis.CustomEvent;
|
|
4534
|
+
if (typeof EventCtor !== "function") {
|
|
4535
|
+
return;
|
|
4536
|
+
}
|
|
4537
|
+
element.dispatchEvent?.(
|
|
4538
|
+
new EventCtor("async:error", {
|
|
4539
|
+
bubbles: true,
|
|
4540
|
+
detail: { error }
|
|
4541
|
+
})
|
|
4542
|
+
);
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4469
4545
|
function escapeRegExp(value) {
|
|
4470
4546
|
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4471
4547
|
}
|