@async/framework 0.10.2 → 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 +20 -0
  2. package/README.md +23 -7
  3. package/browser.d.ts +4 -7
  4. package/browser.js +17 -66
  5. package/browser.min.js +1 -1
  6. package/browser.ts +17 -66
  7. package/browser.umd.js +17 -66
  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 -193
  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 -218
  44. package/src/loader.js +0 -772
  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 -617
  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 -362
  53. package/src/signals.js +0 -592
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
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
+
3
23
  ## 0.10.2 - 2026-06-17
4
24
 
5
25
  - Fixed intercepted router link, form, and popstate navigation failures so they
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
@@ -2165,7 +2165,7 @@ const __serverModule = (() => {
2165
2165
 
2166
2166
  function createServerProxy({
2167
2167
  endpoint = "/__async/server",
2168
- fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
2168
+ transport,
2169
2169
  signals,
2170
2170
  loader,
2171
2171
  router,
@@ -2173,8 +2173,8 @@ const __serverModule = (() => {
2173
2173
  scheduler,
2174
2174
  headers = {}
2175
2175
  } = {}) {
2176
- if (typeof fetchImpl !== "function") {
2177
- throw new TypeError("createServerProxy(...) requires fetch to be available.");
2176
+ if (typeof transport !== "function") {
2177
+ throw new TypeError("createServerProxy(...) requires a transport function.");
2178
2178
  }
2179
2179
 
2180
2180
  const defaults = { signals, loader, router, cache, scheduler };
@@ -2189,7 +2189,7 @@ const __serverModule = (() => {
2189
2189
  };
2190
2190
  assertJsonTransportable(body);
2191
2191
 
2192
- const response = await fetchImpl(joinEndpoint(endpoint, id), {
2192
+ const response = await transport(joinEndpoint(endpoint, id), {
2193
2193
  method: "POST",
2194
2194
  headers: {
2195
2195
  "content-type": "application/json",
@@ -3973,6 +3973,8 @@ const __routerModule = (() => {
3973
3973
 
3974
3974
  const route = defineRoute;
3975
3975
 
3976
+ const routerModes = new Set(["csr", "spa", "ssr", "mpa"]);
3977
+
3976
3978
  function createRouteRegistry(initialMap = {}, options = {}) {
3977
3979
  const registryStore = options.registry ?? createRegistryStore();
3978
3980
  const type = options.type ?? "route";
@@ -4068,7 +4070,7 @@ const __routerModule = (() => {
4068
4070
  }
4069
4071
 
4070
4072
  function createRouter({
4071
- mode = "ssr-spa",
4073
+ mode = "ssr",
4072
4074
  root,
4073
4075
  boundary = "route",
4074
4076
  routes = createRouteRegistry(),
@@ -4078,11 +4080,10 @@ const __routerModule = (() => {
4078
4080
  server,
4079
4081
  cache,
4080
4082
  partials,
4081
- fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
4082
- routeEndpoint = "/__async/route",
4083
4083
  attributes,
4084
4084
  scheduler
4085
4085
  } = {}) {
4086
+ assertRouterMode(mode);
4086
4087
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
4087
4088
  const rootNode = root ?? documentRef;
4088
4089
  const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
@@ -4151,16 +4152,13 @@ const __routerModule = (() => {
4151
4152
 
4152
4153
  prefetch(url) {
4153
4154
  assertActive();
4154
- if (mode === "ssr-spa" && typeof fetchImpl === "function") {
4155
- return fetchRoute(url, { prefetch: true });
4155
+ if (mode === "mpa" || mode === "ssr") {
4156
+ return Promise.resolve(null);
4156
4157
  }
4157
4158
  const matched = api.match(url);
4158
4159
  if (matched?.route?.partial && partials?.resolve?.(matched.route.partial)) {
4159
4160
  return partials.render(matched.route.partial, matched.params, contextFor(matched));
4160
4161
  }
4161
- if (typeof fetchImpl === "function") {
4162
- return fetchRoute(url, { prefetch: true });
4163
- }
4164
4162
  return Promise.resolve(null);
4165
4163
  },
4166
4164
 
@@ -4172,9 +4170,6 @@ const __routerModule = (() => {
4172
4170
  }
4173
4171
 
4174
4172
  const target = resolveUrl(url);
4175
- if (mode === "ssr-spa") {
4176
- return fetchRoutePartial(target, options);
4177
- }
4178
4173
  return renderLocalRoutePartial(target, options);
4179
4174
  },
4180
4175
 
@@ -4262,31 +4257,6 @@ const __routerModule = (() => {
4262
4257
  }
4263
4258
  }
4264
4259
 
4265
- async function fetchRoutePartial(target, options = {}) {
4266
- const matched = api.match(target);
4267
- const navigation = beginNavigation(target, matched);
4268
- setMatchedRouterState(target, matched, { pending: true, error: null });
4269
-
4270
- try {
4271
- const result = await fetchRoute(target.href, { signal: navigation.abort });
4272
- if (!isActiveNavigation(navigation)) {
4273
- return null;
4274
- }
4275
- await applyNavigationResult(result, target, options, navigation);
4276
- if (!isActiveNavigation(navigation)) {
4277
- return null;
4278
- }
4279
- setRouterState({ pending: false, error: null });
4280
- return result;
4281
- } catch (error) {
4282
- if (!isActiveNavigation(navigation)) {
4283
- return null;
4284
- }
4285
- setRouterState({ pending: false, error });
4286
- throw error;
4287
- }
4288
- }
4289
-
4290
4260
  async function applyNavigationResult(result, target, options, navigation) {
4291
4261
  if (!isActiveNavigation(navigation)) {
4292
4262
  return;
@@ -4317,29 +4287,6 @@ const __routerModule = (() => {
4317
4287
  documentRef.defaultView?.history?.pushState?.({}, "", target.href);
4318
4288
  }
4319
4289
 
4320
- async function fetchRoute(url, { prefetch = false, signal } = {}) {
4321
- if (typeof fetchImpl !== "function") {
4322
- throw new Error("Router navigation requires a partial registry or fetch.");
4323
- }
4324
- const response = await fetchImpl(`${routeEndpoint}?to=${encodeURIComponent(String(url))}`, {
4325
- headers: {
4326
- accept: "application/json, text/html"
4327
- },
4328
- signal
4329
- });
4330
- if (!response.ok) {
4331
- throw new Error(`Route "${url}" failed with ${response.status}.`);
4332
- }
4333
- if (prefetch) {
4334
- return response;
4335
- }
4336
- const type = response.headers.get("content-type") ?? "";
4337
- if (type.includes("application/json")) {
4338
- return response.json();
4339
- }
4340
- return { boundary, html: await response.text() };
4341
- }
4342
-
4343
4290
  function contextFor(matched, navigation) {
4344
4291
  return {
4345
4292
  params: matched.params,
@@ -4529,6 +4476,12 @@ const __routerModule = (() => {
4529
4476
  return target.hash !== current.hash || anchor.getAttribute?.("href")?.startsWith("#") === true;
4530
4477
  }
4531
4478
 
4479
+ function assertRouterMode(mode) {
4480
+ if (!routerModes.has(mode)) {
4481
+ throw new TypeError(`Unknown router mode "${mode}".`);
4482
+ }
4483
+ }
4484
+
4532
4485
  function dispatchAsyncError(element, error) {
4533
4486
  const EventCtor = element.ownerDocument?.defaultView?.CustomEvent ?? globalThis.CustomEvent;
4534
4487
  if (typeof EventCtor !== "function") {
@@ -4927,7 +4880,7 @@ const __appModule = (() => {
4927
4880
  return;
4928
4881
  }
4929
4882
  router = router ?? createRouter({
4930
- mode: options.mode ?? "ssr-spa",
4883
+ mode: options.mode ?? "ssr",
4931
4884
  root,
4932
4885
  boundary: options.boundary ?? "route",
4933
4886
  routes,
@@ -4938,8 +4891,6 @@ const __appModule = (() => {
4938
4891
  cache: browserCache,
4939
4892
  partials,
4940
4893
  scheduler,
4941
- fetch: options.fetch,
4942
- routeEndpoint: options.routeEndpoint,
4943
4894
  attributes
4944
4895
  });
4945
4896
  runtime.router = router;