@async/framework 0.10.1 → 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/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] = decodeURIComponent(match[index + 1] ?? "");
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
- void api.navigate(currentUrl(), {
179
+ handleNavigation(api.navigate(currentUrl(), {
180
180
  replace: true,
181
181
  initial: true,
182
182
  source: "client"
183
- }).catch(() => {});
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, seen = new Set()) {
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 (seen.has(value)) {
313
- return;
315
+ if (stack.has(value)) {
316
+ throw new Error("Server proxy JSON transport does not support circular values.");
314
317
  }
315
- seen.add(value);
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, seen);
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, seen);
332
+ assertJsonTransportable(item, stack);
329
333
  }
334
+ stack.delete(value);
330
335
  }
331
336
 
332
337
  function joinEndpoint(endpoint, id) {