@async/framework 0.10.1 → 0.11.0

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +23 -7
  3. package/browser.d.ts +4 -7
  4. package/browser.js +143 -116
  5. package/browser.min.js +1 -1
  6. package/browser.ts +143 -116
  7. package/browser.umd.js +143 -116
  8. package/browser.umd.min.js +1 -1
  9. package/{server.d.ts → framework.d.ts} +4 -7
  10. package/framework.ts +5946 -0
  11. package/package.json +25 -17
  12. package/server.js +5945 -0
  13. package/examples/cache/index.html +0 -16
  14. package/examples/cache/main.js +0 -47
  15. package/examples/components/index.html +0 -11
  16. package/examples/components/main.js +0 -26
  17. package/examples/counter/index.html +0 -15
  18. package/examples/counter/main.js +0 -17
  19. package/examples/partials/index.html +0 -15
  20. package/examples/partials/main.js +0 -43
  21. package/examples/product/index.html +0 -32
  22. package/examples/product/main.js +0 -24
  23. package/examples/router/index.html +0 -18
  24. package/examples/router/main.js +0 -52
  25. package/examples/server-call/index.html +0 -21
  26. package/examples/server-call/main.js +0 -22
  27. package/examples/ssr/index.html +0 -12
  28. package/examples/ssr/main.js +0 -89
  29. package/examples/streaming/index.html +0 -16
  30. package/examples/streaming/main.js +0 -30
  31. package/src/app.js +0 -802
  32. package/src/async-signal.js +0 -277
  33. package/src/attributes.js +0 -52
  34. package/src/boundary-receiver.js +0 -302
  35. package/src/browser.js +0 -18
  36. package/src/cache.js +0 -189
  37. package/src/component.js +0 -373
  38. package/src/delay.js +0 -30
  39. package/src/elements.js +0 -63
  40. package/src/handlers.js +0 -219
  41. package/src/html.js +0 -158
  42. package/src/index.js +0 -20
  43. package/src/lazy-registry.js +0 -204
  44. package/src/loader.js +0 -765
  45. package/src/partials.js +0 -133
  46. package/src/registry-store.js +0 -267
  47. package/src/request-context.js +0 -40
  48. package/src/router.js +0 -571
  49. package/src/scheduler.js +0 -300
  50. package/src/server-entry.js +0 -20
  51. package/src/server-registry.js +0 -97
  52. package/src/server.js +0 -357
  53. package/src/signals.js +0 -592
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.0 - 2026-06-17
4
+
5
+ - Removed the networked `ssr-spa` router mode and route-fragment fetching so
6
+ `ssr` activates server-rendered HTML and snapshots without client route
7
+ fetches.
8
+ - Changed browser navigation to render registered SPA partials locally in
9
+ `spa` and `csr` modes while leaving same-origin document navigation alone in
10
+ `ssr` and `mpa` modes.
11
+ - Replaced the server proxy's implicit `globalThis.fetch` default with an
12
+ explicit `transport` callback supplied by application code.
13
+ - Published only generated runtime artifacts and declarations:
14
+ `browser.*`, `server.js`, `framework.ts`, and `framework.d.ts`; source,
15
+ tests, and examples are no longer included in the package tarball.
16
+ - Added regression coverage for SSR snapshot activation without fetch, SPA
17
+ stale/error navigation behavior, explicit server proxy transports, and static
18
+ generated-bundle scans for implicit global fetch access.
19
+ - Bundle size from bundled TypeScript source: `browser.ts` 171,908 B raw /
20
+ 32,421 B gzip -> `browser.min.js` 72,827 B raw / 21,845 B gzip
21
+ (-99,081 B raw, -10,576 B gzip).
22
+
23
+ ## 0.10.2 - 2026-06-17
24
+
25
+ - Fixed intercepted router link, form, and popstate navigation failures so they
26
+ update router error state and do not create unhandled promise rejections.
27
+ - Preserved native same-document hash link behavior and made malformed encoded
28
+ dynamic route params fall back to the raw segment instead of throwing.
29
+ - Hardened rootless detach cleanup, lazy descriptor import retry, server JSON
30
+ transport validation, and `cache.getOrSet(...)` handling for cached
31
+ `undefined` values.
32
+ - Expanded regression coverage for scheduler reentrancy, boundary receiver
33
+ patch shapes, root lifecycle, lazy descriptors, server transport edge cases,
34
+ component lifecycle ordering, and installed package export-map shape.
35
+ - Bundle size from bundled TypeScript source: `browser.ts` 173,774 B raw /
36
+ 32,727 B gzip -> `browser.min.js` 73,680 B raw / 22,047 B gzip
37
+ (-100,094 B raw, -10,680 B gzip).
38
+
3
39
  ## 0.10.1 - 2026-06-17
4
40
 
5
41
  - Added Terser-powered browser bundle minification and pointed legacy
package/README.md CHANGED
@@ -102,8 +102,11 @@ production:
102
102
  | `browser.min.js` | ESM | Compact browser module bundle |
103
103
  | `browser.umd.js` | UMD | Readable script-tag/CommonJS-style bundle |
104
104
  | `browser.umd.min.js` | UMD | Compact script-tag/CommonJS-style bundle and default CDN file |
105
- | `browser.ts` | Bundled TypeScript source | TS-aware runtimes and higher-layer tooling |
105
+ | `browser.ts` | Bundled browser TypeScript source | TS-aware runtimes and higher-layer tooling |
106
106
  | `browser.d.ts` | Type declarations | TypeScript declarations for the browser API |
107
+ | `server.js` | ESM | Server-capable Node.js bundle |
108
+ | `framework.ts` | Bundled server-capable TypeScript source | TS-aware runtimes and higher-layer tooling |
109
+ | `framework.d.ts` | Type declarations | TypeScript declarations for the server-capable API |
107
110
 
108
111
  ```html
109
112
  <main async:container>
@@ -789,8 +792,9 @@ handlers.register("addToCart", async function () {
789
792
 
790
793
  ### Server Calls
791
794
 
792
- Server registries run locally on the server and proxies call an HTTP endpoint
793
- from the browser. Both expose the same dotted call shape.
795
+ Server registries run locally on the server. Browser proxies use an explicit
796
+ transport supplied by the app, so network access is opt-in. Both expose the same
797
+ dotted call shape.
794
798
 
795
799
  ```js
796
800
  import {
@@ -818,6 +822,7 @@ import {
818
822
 
819
823
  const server = createServerProxy({
820
824
  endpoint: "/__async/server",
825
+ transport: httpTransport,
821
826
  signals,
822
827
  loader,
823
828
  router
@@ -884,8 +889,7 @@ Router modes:
884
889
  | --- | --- |
885
890
  | `csr` | Client renders local partial into boundary | Client renders local partial and swaps |
886
891
  | `spa` | Existing HTML may already contain route | Client renders local partial and swaps |
887
- | `ssr` | Server rendered document | Browser navigates normally |
888
- | `ssr-spa` | Server rendered document/route boundary | Fetch route partial, apply effects, swap |
892
+ | `ssr` | Server-rendered document plus snapshot activation | Browser navigates normally |
889
893
  | `mpa` | Any document source | Browser navigates normally |
890
894
 
891
895
  CSR startup can use an empty route boundary:
@@ -1001,12 +1005,24 @@ The returned HTML includes a route boundary plus a JSON snapshot:
1001
1005
  ```
1002
1006
 
1003
1007
  Browser activation scans the existing HTML and attaches events. It does not
1004
- hydrate, diff, patch, or rerender:
1008
+ hydrate, diff, patch, rerender, or fetch route fragments:
1009
+
1010
+ ```js
1011
+ createApp(browserApp, {
1012
+ root: document
1013
+ }).start();
1014
+ ```
1015
+
1016
+ If browser handlers or async signals need server commands, pass a server proxy
1017
+ with an explicit transport:
1005
1018
 
1006
1019
  ```js
1007
1020
  createApp(browserApp, {
1008
1021
  root: document,
1009
- server: createServerProxy({ endpoint: "/__async/server" })
1022
+ server: createServerProxy({
1023
+ endpoint: "/__async/server",
1024
+ transport: httpTransport
1025
+ })
1010
1026
  }).start();
1011
1027
  ```
1012
1028
 
package/browser.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Browser type declarations for @async/framework/browser.
3
3
 
4
4
  export type RuntimeTarget = "browser" | "server";
5
- export type RouterMode = "csr" | "spa" | "ssr" | "ssr-spa" | "mpa";
5
+ export type RouterMode = "csr" | "spa" | "ssr" | "mpa";
6
6
  export type AsyncSignalStatus = "idle" | "loading" | "ready" | "error";
7
7
  export type MaybePromise<T> = T | Promise<T>;
8
8
  export type Cleanup = () => void;
@@ -261,6 +261,7 @@ export interface ServerContext {
261
261
  }
262
262
 
263
263
  export type ServerFunction<T = unknown> = (this: ServerContext, ...args: unknown[]) => MaybePromise<ServerResult<T>>;
264
+ export type ServerProxyTransport = (url: string, init: RequestInit) => MaybePromise<Response>;
264
265
 
265
266
  export interface ServerNamespace {
266
267
  run<T = unknown>(id: string, args?: unknown[], context?: Partial<ServerContext>): Promise<T>;
@@ -275,7 +276,7 @@ export interface ServerNamespace {
275
276
 
276
277
  export interface ServerProxyOptions {
277
278
  endpoint?: string;
278
- fetch?: typeof fetch;
279
+ transport: ServerProxyTransport;
279
280
  signals?: SignalRegistry;
280
281
  loader?: LoaderInstance;
281
282
  router?: Router;
@@ -378,8 +379,6 @@ export interface RouterOptions {
378
379
  server?: ServerNamespace;
379
380
  cache?: CacheRegistry;
380
381
  partials?: PartialRegistry;
381
- fetch?: typeof fetch;
382
- routeEndpoint?: string;
383
382
  attributes?: AttributeConfig;
384
383
  scheduler?: Scheduler;
385
384
  }
@@ -607,8 +606,6 @@ export interface CreateAppOptions extends LoaderOptions {
607
606
  routes?: RouteRegistry;
608
607
  partials?: PartialRegistry;
609
608
  components?: ComponentRegistry;
610
- fetch?: typeof fetch;
611
- routeEndpoint?: string;
612
609
  request?: Request;
613
610
  locals?: unknown;
614
611
  requestContext?: RequestContextStore;
@@ -722,7 +719,7 @@ export declare function createScheduler(options?: SchedulerOptions): Scheduler;
722
719
  export declare function defineRoute(partial: string, options?: Omit<RouteDefinition, "partial">): RouteDefinition;
723
720
  export declare const route: typeof defineRoute;
724
721
  export declare function applyServerResult(result: unknown, context?: Record<string, unknown>): Promise<unknown>;
725
- export declare function createServerProxy(options?: ServerProxyOptions): ServerNamespace;
722
+ export declare function createServerProxy(options: ServerProxyOptions): ServerNamespace;
726
723
  export declare function resolveServerCommandArguments(args: Array<{ type: "local"; name: string } | { type: "signal"; path: string }>, context?: Record<string, unknown>): { args: unknown[]; signalValues: Record<string, unknown>; signalPaths: string[] };
727
724
  export declare function unwrapServerResult<T = unknown>(result: ServerResult<T>): T | ServerResult<T>;
728
725
  export declare function computed<T = unknown>(fn: (this: { signals: SignalRegistry; id: string; server?: ServerNamespace; router?: Router; loader?: LoaderInstance; cache?: CacheRegistry; scheduler?: Scheduler }) => T): ComputedSignal<T>;
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) {
@@ -2147,7 +2165,7 @@ const __serverModule = (() => {
2147
2165
 
2148
2166
  function createServerProxy({
2149
2167
  endpoint = "/__async/server",
2150
- fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
2168
+ transport,
2151
2169
  signals,
2152
2170
  loader,
2153
2171
  router,
@@ -2155,8 +2173,8 @@ const __serverModule = (() => {
2155
2173
  scheduler,
2156
2174
  headers = {}
2157
2175
  } = {}) {
2158
- if (typeof fetchImpl !== "function") {
2159
- throw new TypeError("createServerProxy(...) requires fetch to be available.");
2176
+ if (typeof transport !== "function") {
2177
+ throw new TypeError("createServerProxy(...) requires a transport function.");
2160
2178
  }
2161
2179
 
2162
2180
  const defaults = { signals, loader, router, cache, scheduler };
@@ -2171,7 +2189,7 @@ const __serverModule = (() => {
2171
2189
  };
2172
2190
  assertJsonTransportable(body);
2173
2191
 
2174
- const response = await fetchImpl(joinEndpoint(endpoint, id), {
2192
+ const response = await transport(joinEndpoint(endpoint, id), {
2175
2193
  method: "POST",
2176
2194
  headers: {
2177
2195
  "content-type": "application/json",
@@ -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
 
@@ -3943,6 +3973,8 @@ const __routerModule = (() => {
3943
3973
 
3944
3974
  const route = defineRoute;
3945
3975
 
3976
+ const routerModes = new Set(["csr", "spa", "ssr", "mpa"]);
3977
+
3946
3978
  function createRouteRegistry(initialMap = {}, options = {}) {
3947
3979
  const registryStore = options.registry ?? createRegistryStore();
3948
3980
  const type = options.type ?? "route";
@@ -3989,7 +4021,7 @@ const __routerModule = (() => {
3989
4021
  }
3990
4022
  const params = {};
3991
4023
  candidate.keys.forEach((key, index) => {
3992
- params[key] = decodeURIComponent(match[index + 1] ?? "");
4024
+ params[key] = safeDecodeURIComponent(match[index + 1] ?? "");
3993
4025
  });
3994
4026
  return {
3995
4027
  pattern: candidate.pattern,
@@ -4038,7 +4070,7 @@ const __routerModule = (() => {
4038
4070
  }
4039
4071
 
4040
4072
  function createRouter({
4041
- mode = "ssr-spa",
4073
+ mode = "ssr",
4042
4074
  root,
4043
4075
  boundary = "route",
4044
4076
  routes = createRouteRegistry(),
@@ -4048,11 +4080,10 @@ const __routerModule = (() => {
4048
4080
  server,
4049
4081
  cache,
4050
4082
  partials,
4051
- fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
4052
- routeEndpoint = "/__async/route",
4053
4083
  attributes,
4054
4084
  scheduler
4055
4085
  } = {}) {
4086
+ assertRouterMode(mode);
4056
4087
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
4057
4088
  const rootNode = root ?? documentRef;
4058
4089
  const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
@@ -4104,11 +4135,11 @@ const __routerModule = (() => {
4104
4135
  }
4105
4136
  bindNavigation();
4106
4137
  if (mode === "csr") {
4107
- void api.navigate(currentUrl(), {
4138
+ handleNavigation(api.navigate(currentUrl(), {
4108
4139
  replace: true,
4109
4140
  initial: true,
4110
4141
  source: "client"
4111
- }).catch(() => {});
4142
+ }));
4112
4143
  return api;
4113
4144
  }
4114
4145
  updateStateFromLocation();
@@ -4121,16 +4152,13 @@ const __routerModule = (() => {
4121
4152
 
4122
4153
  prefetch(url) {
4123
4154
  assertActive();
4124
- if (mode === "ssr-spa" && typeof fetchImpl === "function") {
4125
- return fetchRoute(url, { prefetch: true });
4155
+ if (mode === "mpa" || mode === "ssr") {
4156
+ return Promise.resolve(null);
4126
4157
  }
4127
4158
  const matched = api.match(url);
4128
4159
  if (matched?.route?.partial && partials?.resolve?.(matched.route.partial)) {
4129
4160
  return partials.render(matched.route.partial, matched.params, contextFor(matched));
4130
4161
  }
4131
- if (typeof fetchImpl === "function") {
4132
- return fetchRoute(url, { prefetch: true });
4133
- }
4134
4162
  return Promise.resolve(null);
4135
4163
  },
4136
4164
 
@@ -4142,9 +4170,6 @@ const __routerModule = (() => {
4142
4170
  }
4143
4171
 
4144
4172
  const target = resolveUrl(url);
4145
- if (mode === "ssr-spa") {
4146
- return fetchRoutePartial(target, options);
4147
- }
4148
4173
  return renderLocalRoutePartial(target, options);
4149
4174
  },
4150
4175
 
@@ -4173,7 +4198,7 @@ const __routerModule = (() => {
4173
4198
  return;
4174
4199
  }
4175
4200
  event.preventDefault();
4176
- api.navigate(anchor.href);
4201
+ handleNavigation(api.navigate(anchor.href));
4177
4202
  };
4178
4203
  const submit = (event) => {
4179
4204
  const form = closest(event.target, "form");
@@ -4181,9 +4206,9 @@ const __routerModule = (() => {
4181
4206
  return;
4182
4207
  }
4183
4208
  event.preventDefault();
4184
- api.navigate(formActionUrl(form));
4209
+ handleNavigation(api.navigate(formActionUrl(form)));
4185
4210
  };
4186
- const popstate = () => api.navigate(currentUrl(), { history: false });
4211
+ const popstate = () => handleNavigation(api.navigate(currentUrl(), { history: false }));
4187
4212
 
4188
4213
  rootNode.addEventListener?.("click", click);
4189
4214
  rootNode.addEventListener?.("submit", submit);
@@ -4232,31 +4257,6 @@ const __routerModule = (() => {
4232
4257
  }
4233
4258
  }
4234
4259
 
4235
- async function fetchRoutePartial(target, options = {}) {
4236
- const matched = api.match(target);
4237
- const navigation = beginNavigation(target, matched);
4238
- setMatchedRouterState(target, matched, { pending: true, error: null });
4239
-
4240
- try {
4241
- const result = await fetchRoute(target.href, { signal: navigation.abort });
4242
- if (!isActiveNavigation(navigation)) {
4243
- return null;
4244
- }
4245
- await applyNavigationResult(result, target, options, navigation);
4246
- if (!isActiveNavigation(navigation)) {
4247
- return null;
4248
- }
4249
- setRouterState({ pending: false, error: null });
4250
- return result;
4251
- } catch (error) {
4252
- if (!isActiveNavigation(navigation)) {
4253
- return null;
4254
- }
4255
- setRouterState({ pending: false, error });
4256
- throw error;
4257
- }
4258
- }
4259
-
4260
4260
  async function applyNavigationResult(result, target, options, navigation) {
4261
4261
  if (!isActiveNavigation(navigation)) {
4262
4262
  return;
@@ -4287,29 +4287,6 @@ const __routerModule = (() => {
4287
4287
  documentRef.defaultView?.history?.pushState?.({}, "", target.href);
4288
4288
  }
4289
4289
 
4290
- async function fetchRoute(url, { prefetch = false, signal } = {}) {
4291
- if (typeof fetchImpl !== "function") {
4292
- throw new Error("Router navigation requires a partial registry or fetch.");
4293
- }
4294
- const response = await fetchImpl(`${routeEndpoint}?to=${encodeURIComponent(String(url))}`, {
4295
- headers: {
4296
- accept: "application/json, text/html"
4297
- },
4298
- signal
4299
- });
4300
- if (!response.ok) {
4301
- throw new Error(`Route "${url}" failed with ${response.status}.`);
4302
- }
4303
- if (prefetch) {
4304
- return response;
4305
- }
4306
- const type = response.headers.get("content-type") ?? "";
4307
- if (type.includes("application/json")) {
4308
- return response.json();
4309
- }
4310
- return { boundary, html: await response.text() };
4311
- }
4312
-
4313
4290
  function contextFor(matched, navigation) {
4314
4291
  return {
4315
4292
  params: matched.params,
@@ -4376,6 +4353,19 @@ const __routerModule = (() => {
4376
4353
  }
4377
4354
  }
4378
4355
 
4356
+ function handleNavigation(promise) {
4357
+ void promise.catch((error) => {
4358
+ if (destroyed) {
4359
+ return;
4360
+ }
4361
+ setRouterState({
4362
+ pending: false,
4363
+ error
4364
+ });
4365
+ dispatchAsyncError(rootNode, error);
4366
+ });
4367
+ }
4368
+
4379
4369
  function currentUrl() {
4380
4370
  return resolveUrl(documentRef.defaultView?.location?.href ?? "http://localhost/");
4381
4371
  }
@@ -4392,6 +4382,33 @@ const __routerModule = (() => {
4392
4382
  throw new Error("Router has been destroyed.");
4393
4383
  }
4394
4384
  }
4385
+
4386
+ function shouldIgnoreLink(event, anchor) {
4387
+ if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
4388
+ return true;
4389
+ }
4390
+ if (anchor.target || anchor.hasAttribute("download")) {
4391
+ return true;
4392
+ }
4393
+ const target = resolveUrl(anchor.href);
4394
+ const current = currentUrl();
4395
+ if (target.origin !== current.origin) {
4396
+ return true;
4397
+ }
4398
+ return isHashOnlyNavigation(target, current, anchor);
4399
+ }
4400
+
4401
+ function shouldIgnoreForm(form) {
4402
+ const method = String(form.method || "get").toLowerCase();
4403
+ return method !== "get" || resolveUrl(form.action).origin !== currentUrl().origin;
4404
+ }
4405
+
4406
+ function formActionUrl(form) {
4407
+ const url = resolveUrl(form.action || form.ownerDocument.defaultView.location.href);
4408
+ const formData = new form.ownerDocument.defaultView.FormData(form);
4409
+ url.search = new URLSearchParams(formData).toString();
4410
+ return url.href;
4411
+ }
4395
4412
  }
4396
4413
 
4397
4414
  function normalizeRoute(pattern, definition) {
@@ -4429,28 +4446,6 @@ const __routerModule = (() => {
4429
4446
  return { regex: new RegExp(`^${source}$`), keys };
4430
4447
  }
4431
4448
 
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
4449
  function closest(target, selector) {
4455
4450
  return target?.closest?.(selector);
4456
4451
  }
@@ -4466,6 +4461,40 @@ const __routerModule = (() => {
4466
4461
  return Object.fromEntries(url.searchParams.entries());
4467
4462
  }
4468
4463
 
4464
+ function safeDecodeURIComponent(value) {
4465
+ try {
4466
+ return decodeURIComponent(value);
4467
+ } catch {
4468
+ return value;
4469
+ }
4470
+ }
4471
+
4472
+ function isHashOnlyNavigation(target, current, anchor) {
4473
+ if (target.origin !== current.origin || target.pathname !== current.pathname || target.search !== current.search) {
4474
+ return false;
4475
+ }
4476
+ return target.hash !== current.hash || anchor.getAttribute?.("href")?.startsWith("#") === true;
4477
+ }
4478
+
4479
+ function assertRouterMode(mode) {
4480
+ if (!routerModes.has(mode)) {
4481
+ throw new TypeError(`Unknown router mode "${mode}".`);
4482
+ }
4483
+ }
4484
+
4485
+ function dispatchAsyncError(element, error) {
4486
+ const EventCtor = element.ownerDocument?.defaultView?.CustomEvent ?? globalThis.CustomEvent;
4487
+ if (typeof EventCtor !== "function") {
4488
+ return;
4489
+ }
4490
+ element.dispatchEvent?.(
4491
+ new EventCtor("async:error", {
4492
+ bubbles: true,
4493
+ detail: { error }
4494
+ })
4495
+ );
4496
+ }
4497
+
4469
4498
  function escapeRegExp(value) {
4470
4499
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4471
4500
  }
@@ -4851,7 +4880,7 @@ const __appModule = (() => {
4851
4880
  return;
4852
4881
  }
4853
4882
  router = router ?? createRouter({
4854
- mode: options.mode ?? "ssr-spa",
4883
+ mode: options.mode ?? "ssr",
4855
4884
  root,
4856
4885
  boundary: options.boundary ?? "route",
4857
4886
  routes,
@@ -4862,8 +4891,6 @@ const __appModule = (() => {
4862
4891
  cache: browserCache,
4863
4892
  partials,
4864
4893
  scheduler,
4865
- fetch: options.fetch,
4866
- routeEndpoint: options.routeEndpoint,
4867
4894
  attributes
4868
4895
  });
4869
4896
  runtime.router = router;