@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 CHANGED
@@ -1,6 +1,29 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
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
- const module = await modulePromise;
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
- const entry = entries.get(key);
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 = registryApi.get(key);
839
- if (cached !== undefined) {
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 = registryApi.get(key);
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, seen = new Set()) {
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 (seen.has(value)) {
2456
- return;
2476
+ if (stack.has(value)) {
2477
+ throw new Error("Server proxy JSON transport does not support circular values.");
2457
2478
  }
2458
- seen.add(value);
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, seen);
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, seen);
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] = decodeURIComponent(match[index + 1] ?? "");
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
- void api.navigate(currentUrl(), {
4137
+ handleNavigation(api.navigate(currentUrl(), {
4108
4138
  replace: true,
4109
4139
  initial: true,
4110
4140
  source: "client"
4111
- }).catch(() => {});
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
  }