@backtickjs/hono 0.3.0 → 0.3.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/README.md CHANGED
@@ -19,18 +19,17 @@ import { Stack } from "expo-router" with { backtick: "client" };
19
19
  import { Hono } from "hono";
20
20
  import { Home } from "./Home.js";
21
21
 
22
- // The root layout, around every screen: the navigator.
23
- const RootLayout = cs`() => <$Stack />`;
24
-
25
22
  const app = new Hono();
26
23
 
27
- app.use(backtickApp(app, RootLayout));
24
+ app.use(backtickApp(app));
25
+ // The root layout, around every screen: the navigator.
26
+ app.get("/_layout", (context) => context.render(cs`() => <$Stack />`));
28
27
  app.get("/", (context) => context.render(<Home />));
29
28
 
30
29
  export default app;
31
30
  ```
32
31
 
33
- The Backtick app opens an app from one answer, which `backtickApp(app, RootLayout)` answers: the client code that fetches each screen and keeps it while it shows, around the app's root layout, a client component that draws its navigator, as Hono's `jsxRenderer()` takes the page's. Navigation works as in Next.js. Every route answers with what it draws, and the runtime fetches each as a query keyed by its path. A route with a `_loading` (`/day/_loading`, rendered for the path, so it can use its params) pushes as soon as that's in, at once if it was prefetched, and shows it until the route's answer is in; a navigation to one without holds the current screen until its answer is in, as expo-router's `unstable_useIsNavigating()` says, for a progress bar. Going back shows a screen as it was. `<Link prefetch>` fetches a route's loading state ahead, or its answer, without one. The root layout is the only one: what screens share is a component they draw. A client with a navigator of its own uses `backtickRenderer()` alone.
32
+ The Backtick app opens an app from one answer, which `backtickApp(app)` answers: the client code that fetches each screen and keeps it while it shows. Navigation works as in Next.js, whose folders are routes here. `/_layout` is the root layout, a client component that draws the navigator; unlike Next.js's, it takes no `children`, as the navigator renders the screens. An app without one doesn't open. A screen is each segment's `_layout` (`/day/:id/_layout`, rendered for its path, answering a client component given `children`), around its `_loading` while what's under it loads, around the page. A loading state takes no params, as in Next.js: it's fetched once for its pattern, and reading a param throws; what depends on the path, such as the title, goes in a `_layout`. Every link fetches ahead what its screen needs to show, its layouts down to its first loading state and that loading state, as Next.js's `Link` prefetches; `<Link prefetch>` adds the page, and `router.prefetch(href)` fetches as a link does. A navigation pushes once those are in, at once if they were fetched ahead; with no loading state, it holds the current screen until all of it is in, as expo-router's `unstable_useIsNavigating()` says, for a progress bar. Going back shows a screen as it was. A client with a navigator of its own uses `backtickRenderer()` alone.
34
33
 
35
34
  The runtime gives the app a [TanStack Query](https://tanstack.com/query) client whose query keys are paths: a query of `"/cart/items"` fetches that route, and its answer is what the route renders. For a screen whose parts load on their own, each in its own `Suspense`, and refetch on their own:
36
35
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { Client, Spliceable, SplicesAs } from "@backtickjs/core";
2
- import type { ComponentType } from "react";
1
+ import type { Spliceable, SplicesAs } from "@backtickjs/core";
3
2
  import type { Hono, MiddlewareHandler } from "hono";
4
3
  export type { Runtime } from "./runtime.js";
5
4
  declare module "hono" {
@@ -17,30 +16,30 @@ declare module "hono" {
17
16
  *
18
17
  * The status is the context's, so `context.status(404)` makes a not-found
19
18
  * screen, and screens are `no-store`, kept out of HTTP caches: a reload or
20
- * a new open always gets the latest copy. (The Backtick app reuses a screen
21
- * it has, in memory, for 30 seconds, as Next.js's router does.) A client
19
+ * a new open always gets the latest copy. (The Backtick app fetches a page anew
20
+ * for each navigation, as Next.js's router does.) A client
22
21
  * without a module the screen needs gets a 426, and any other refusal of the
23
22
  * bundler's a 400, each with `{ name, message }`. An app the Backtick app
24
- * opens uses `backtickApp(app, RootLayout)` instead.
23
+ * opens uses `backtickApp(app)` instead.
25
24
  */
26
25
  export declare function backtickRenderer(): MiddlewareHandler;
27
26
  /**
28
27
  * Hono's renderer for an app the Backtick app opens, as `backtickRenderer()`,
29
28
  * which also answers the runtime the Backtick app fetches when it opens
30
- * `app`: the client code that fetches, keeps and shows its screens, around
31
- * its root layout, a client component that draws its `Stack`.
29
+ * `app`: the client code that fetches, keeps and shows its screens.
32
30
  *
33
- * const RootLayout = cs`() => <$Stack />`;
34
- * app.use(backtickApp(app, RootLayout));
31
+ * app.use(backtickApp(app));
32
+ * app.get("/_layout", (context) => context.render(cs`() => <$Stack />`));
35
33
  * app.get("/", (context) => context.render(<Home />));
36
34
  *
37
- * Every route answers with what it draws. As in Next.js, a route with a
38
- * `_loading` route (`/city/:id/_loading`, rendered for the path, so it can
39
- * show the city's name) pushes as soon as that's in, at once if it was
40
- * prefetched, and shows it until the route's answer is in; a navigation to
41
- * one without holds the current screen until its answer is in. The runtime
42
- * asks only for the loading states `app` has routes for. One with a layout
43
- * route (a screen has none: what screens share is a component they draw), or
44
- * with a loading route written with a trailing slash, doesn't open.
35
+ * Routes nest as Next.js's folders. `/_layout` is the root layout, a client
36
+ * component drawing the navigator; an app without one doesn't open. A screen
37
+ * is each segment's `_layout` (`/city/:id/_layout`, rendered for its path,
38
+ * answering a client component given `children`), around its `_loading`
39
+ * while what's under it loads, around the page. A loading state takes no
40
+ * params, as in Next.js: it's fetched once for its pattern, and reading a
41
+ * param throws. A navigation pushes once its layouts down to its first
42
+ * loading state, and that loading state, are in; with none, once all of it
43
+ * is. Layout and loading routes written with a trailing slash are refused.
45
44
  */
46
- export declare function backtickApp(app: Hono<any, any, any>, RootLayout: Client<ComponentType>): MiddlewareHandler;
45
+ export declare function backtickApp(app: Hono<any, any, any>): MiddlewareHandler;
package/dist/index.js CHANGED
@@ -10,11 +10,11 @@ import { clientRuntime } from "./runtime.js";
10
10
  *
11
11
  * The status is the context's, so `context.status(404)` makes a not-found
12
12
  * screen, and screens are `no-store`, kept out of HTTP caches: a reload or
13
- * a new open always gets the latest copy. (The Backtick app reuses a screen
14
- * it has, in memory, for 30 seconds, as Next.js's router does.) A client
13
+ * a new open always gets the latest copy. (The Backtick app fetches a page anew
14
+ * for each navigation, as Next.js's router does.) A client
15
15
  * without a module the screen needs gets a 426, and any other refusal of the
16
16
  * bundler's a 400, each with `{ name, message }`. An app the Backtick app
17
- * opens uses `backtickApp(app, RootLayout)` instead.
17
+ * opens uses `backtickApp(app)` instead.
18
18
  */
19
19
  export function backtickRenderer() {
20
20
  return async (context, next) => {
@@ -25,39 +25,46 @@ export function backtickRenderer() {
25
25
  /**
26
26
  * Hono's renderer for an app the Backtick app opens, as `backtickRenderer()`,
27
27
  * which also answers the runtime the Backtick app fetches when it opens
28
- * `app`: the client code that fetches, keeps and shows its screens, around
29
- * its root layout, a client component that draws its `Stack`.
28
+ * `app`: the client code that fetches, keeps and shows its screens.
30
29
  *
31
- * const RootLayout = cs`() => <$Stack />`;
32
- * app.use(backtickApp(app, RootLayout));
30
+ * app.use(backtickApp(app));
31
+ * app.get("/_layout", (context) => context.render(cs`() => <$Stack />`));
33
32
  * app.get("/", (context) => context.render(<Home />));
34
33
  *
35
- * Every route answers with what it draws. As in Next.js, a route with a
36
- * `_loading` route (`/city/:id/_loading`, rendered for the path, so it can
37
- * show the city's name) pushes as soon as that's in, at once if it was
38
- * prefetched, and shows it until the route's answer is in; a navigation to
39
- * one without holds the current screen until its answer is in. The runtime
40
- * asks only for the loading states `app` has routes for. One with a layout
41
- * route (a screen has none: what screens share is a component they draw), or
42
- * with a loading route written with a trailing slash, doesn't open.
34
+ * Routes nest as Next.js's folders. `/_layout` is the root layout, a client
35
+ * component drawing the navigator; an app without one doesn't open. A screen
36
+ * is each segment's `_layout` (`/city/:id/_layout`, rendered for its path,
37
+ * answering a client component given `children`), around its `_loading`
38
+ * while what's under it loads, around the page. A loading state takes no
39
+ * params, as in Next.js: it's fetched once for its pattern, and reading a
40
+ * param throws. A navigation pushes once its layouts down to its first
41
+ * loading state, and that loading state, are in; with none, once all of it
42
+ * is. Layout and loading routes written with a trailing slash are refused.
43
43
  */
44
- export function backtickApp(app, RootLayout) {
44
+ export function backtickApp(app) {
45
45
  const renderer = backtickRenderer();
46
46
  return async (context, next) => {
47
+ if (context.req.path.endsWith("/_loading")) {
48
+ context.req.param = () => {
49
+ throw new Error(`${context.req.routePath} reads a param: a loading state takes none, as in Next.js. What depends on the path goes in a _layout.`);
50
+ };
51
+ }
47
52
  if (context.req.method !== "GET" || context.req.path !== runtimePath) {
48
53
  return renderer(context, next);
49
54
  }
50
- const misplaced = app.routes.find(({ path }) => path.endsWith("/_loading/") || /\/_layout\/?$/.test(path));
55
+ const patterns = (name) => [
56
+ ...new Set(app.routes
57
+ .filter(({ method, path }) => method === "GET" && path.endsWith(name))
58
+ .map(({ path }) => path)),
59
+ ];
60
+ const misplaced = app.routes.find(({ path }) => path.endsWith("/_layout/") || path.endsWith("/_loading/"));
51
61
  if (misplaced !== undefined) {
52
- throw new Error(misplaced.path.endsWith("/_loading/")
53
- ? `The app's ${misplaced.path} route ends with a slash: write it ${misplaced.path.slice(0, -1)}.`
54
- : `The app's ${misplaced.path} route is a layout, which a screen doesn't have: the root layout is backtickApp(app, RootLayout)'s, and what screens share is a component they draw.`);
62
+ throw new Error(`The app's ${misplaced.path} route ends with a slash: write it ${misplaced.path.slice(0, -1)}.`);
63
+ }
64
+ if (!patterns("/_layout").includes("/_layout")) {
65
+ throw new Error("The app has no /_layout route: it's the root layout, a client component drawing the navigator, as cs`() => <$Stack />`.");
55
66
  }
56
- // The app's loading states, by pattern.
57
- const loadings = app.routes
58
- .filter(({ method, path }) => method === "GET" && path.endsWith("/_loading"))
59
- .map(({ path }) => path);
60
- return bundled(context, clientRuntime(new URL(context.req.url).origin, context.req.header("x-backtick-modules") ?? "{}", RootLayout, [...new Set(loadings)]));
67
+ return bundled(context, clientRuntime(new URL(context.req.url).origin, context.req.header("x-backtick-modules") ?? "{}", { layouts: patterns("/_layout"), loadings: patterns("/_loading") }));
61
68
  };
62
69
  }
63
70
  // Where the Backtick app fetches the runtime it opens an app with.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAgB,MAAM,qBAAqB,CAAC;AAG1E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAc7C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC7B,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,KAAmB,CAAC,CAAC,CAAC;QAC5E,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,WAAW,CAAC,GAAwB;IAClD,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC7B,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,GAAG,EAAE;gBACvB,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,wGAAwG,CACjI,CAAC;YACJ,CAAC,CAAC;QACJ,CAAC;QACD,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACrE,OAAO,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACjC,CAAC;QACD,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC;YACjC,GAAG,IAAI,GAAG,CACR,GAAG,CAAC,MAAM;iBACP,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,MAAM,KAAK,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;iBACrE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAC3B;SACF,CAAC;QACF,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAC/B,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CACxE,CAAC;QACF,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CACb,aAAa,SAAS,CAAC,IAAI,sCAAsC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAChG,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CACb,yHAAyH,CAC1H,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CACZ,OAAO,EACP,aAAa,CACX,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,EAC/B,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,oBAAoB,CAAC,IAAI,IAAI,EAChD,EAAE,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,CACnE,CACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,WAAW,GAAG,+BAA+B,CAAC;AAEpD,kEAAkE;AAClE,KAAK,UAAU,OAAO,CAAC,OAAgB,EAAE,KAAiB;IACxD,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,oBAAoB,CAAC,IAAI,IAAI,CAAC,CAAC;IACzE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,KAAoB,CAAC;QAC/C,OAAO,OAAO,CAAC,IAAI,CACjB,EAAE,IAAI,EAAE,OAAO,EAAE,kCAAkC,OAAO,EAAE,EAAE,EAC9D,GAAG,CACJ,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACtD,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;QAClD,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAC5C,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;YAClC,OAAO,OAAO,CAAC,IAAI,CACjB,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EAC5C,KAAK,CAAC,IAAI,KAAK,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CACnD,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
package/dist/runtime.d.ts CHANGED
@@ -8,16 +8,18 @@ export interface Runtime {
8
8
  /**
9
9
  * The app's root layout, given the href on top. A navigation holds the
10
10
  * current screen until the next can show, as navigations are transitions
11
- * (`router.setTransitionMode("always")`): a route with a `_loading` once
12
- * that's in, at once if it was prefetched; one without, once its page is
13
- * in. Suspends until the first screen can show.
11
+ * (`router.setTransitionMode("always")`): once its layouts down to its
12
+ * first loading state, and that loading state, are in, at once if they
13
+ * were prefetched; with no loading state, once all of it is in. Suspends
14
+ * until the first screen can show.
14
15
  */
15
16
  Navigator?: ComponentType<{
16
17
  href: string;
17
18
  }>;
18
19
  /**
19
- * A route's screen: what it renders, and meanwhile what its `_loading`
20
- * renders. One not `active` yet shows nothing.
20
+ * A route's screen, nested as Next.js nests a page: each segment's layout,
21
+ * around its loading state while what's under it loads. One not `active`
22
+ * yet shows nothing.
21
23
  */
22
24
  Screen?: ComponentType<{
23
25
  href: string;
@@ -29,21 +31,29 @@ export interface Runtime {
29
31
  */
30
32
  reload?(href: string): Promise<void>;
31
33
  /**
32
- * Fetches an href ahead, as Next.js prefetches a link: its loading state,
33
- * or, without one, its page. For each `<Link prefetch>`, which the shell
34
- * sees as it's prefetched.
34
+ * Fetches an href ahead, as Next.js's `Link` prefetches: its layouts down
35
+ * to its first loading state, and that loading state (nothing if it has
36
+ * none), as Next.js's `kind: "auto"`; with `kind: "full"`, its page too,
37
+ * as `<Link prefetch>`. At most 4 run at a time, `urgent` ones (a pressed
38
+ * link) first. Returns a cancel, for a link that goes away before its
39
+ * turn.
35
40
  */
36
- prefetch?(href: string): void;
41
+ prefetch?(href: string, options?: {
42
+ kind?: "auto" | "full";
43
+ urgent?: boolean;
44
+ }): () => void;
37
45
  }
38
46
  /**
39
47
  * A route's answer, run. Any bundle is an answer, whatever its status: an
40
- * app's not-found screen is one. A refusal ({ name, message }) says why. For
41
- * a route that may not exist (`notFoundIsNull`), a 404 is null.
48
+ * app's not-found screen is one. A refusal ({ name, message }) says why.
42
49
  */
43
50
  export declare const fetchAnswer: Client<unknown>;
44
51
  /**
45
- * The runtime of an open: for the phone at `origin` with `modules`, around
46
- * the app's root layout, with the patterns of its `_loading` routes
47
- * (`/city/:id/_loading`), so a screen asks only for those it has.
52
+ * The runtime of an open: for the phone at `origin` with `modules`, with the
53
+ * patterns of the app's `_layout` and `_loading` routes (`/city/:id/_layout`),
54
+ * so a screen asks only for those it has.
48
55
  */
49
- export declare function clientRuntime(origin: string, modules: string, RootLayout: Client<ComponentType>, loadingPatterns: readonly string[]): Client<Runtime>;
56
+ export declare function clientRuntime(origin: string, modules: string, routes: {
57
+ layouts: readonly string[];
58
+ loadings: readonly string[];
59
+ }): Client<Runtime>;
package/dist/runtime.js CHANGED
@@ -3,42 +3,40 @@
3
3
  // `/.well-known/backtick/runtime`.
4
4
  import { cs, exec } from "@backtickjs/core";
5
5
  const QueryClient = { "@backtickjs": "ClientImport", name: "QueryClient", from: "@tanstack/react-query", version: "5.104.0" };
6
- const QueryClientProvider = { "@backtickjs": "ClientImport", name: "QueryClientProvider", from: "@tanstack/react-query", version: "5.104.0" };
6
+ const queryOptions = { "@backtickjs": "ClientImport", name: "queryOptions", from: "@tanstack/react-query", version: "5.104.0" };
7
7
  const usePrefetchQuery = { "@backtickjs": "ClientImport", name: "usePrefetchQuery", from: "@tanstack/react-query", version: "5.104.0" };
8
- const useSuspenseQueries = { "@backtickjs": "ClientImport", name: "useSuspenseQueries", from: "@tanstack/react-query", version: "5.104.0" };
9
8
  const useSuspenseQuery = { "@backtickjs": "ClientImport", name: "useSuspenseQuery", from: "@tanstack/react-query", version: "5.104.0" };
10
9
  const PatternRouter = { "@backtickjs": "ClientImport", name: "PatternRouter", from: "hono/router/pattern-router", version: "4.13.13" };
11
- const Component = { "@backtickjs": "ClientImport", name: "Component", from: "react", version: "19.3.0" };
12
10
  const Suspense = { "@backtickjs": "ClientImport", name: "Suspense", from: "react", version: "19.3.0" };
11
+ const use = { "@backtickjs": "ClientImport", name: "use", from: "react", version: "19.3.0" };
13
12
  const useDeferredValue = { "@backtickjs": "ClientImport", name: "useDeferredValue", from: "react", version: "19.3.0" };
14
13
  const $module0 = {
15
- id: "2hmayizp567y2:59:27",
16
- code: "(module, exports, require) => {\n\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.default = $splice0 => async (url, modules, notFoundIsNull) => {\n var response = await fetch(url, {\n headers: {\n \"x-backtick-modules\": modules\n }\n });\n if (notFoundIsNull && response.status === 404) {\n return null;\n }\n var body = await response.text();\n if (!(response.headers.get(\"content-type\") ?? \"\").startsWith(\"text/javascript\")) {\n var reason = body;\n try {\n reason = JSON.parse(body).message ?? body;\n }\n catch { }\n throw new Error(\"The screen couldn't be opened (\" + response.status + \"): \" + reason);\n }\n return await $splice0()(body);\n};\n}",
17
- map: "{\"version\":3,\"file\":\"module.jsx\",\"mappings\":\";;;kBA0D8BA,QAAA,WAC5BC,GAAW,EACXC,OAAe,EACfC,cAAuB;IAEvB,IAAMC,QAAQ,GAAG,MAAMC,KAAK,CAACJ,GAAG,EAAE;QAChCK,OAAO,EAAE;YAAE,oBAAoB,EAAEJ;SAAO;KACzC,CAAC;IACF,IAAIC,cAAc,IAAIC,QAAQ,CAACG,MAAM,KAAK,GAAG,EAAE;QAC7C,OAAO,IAAI;IACb;IACA,IAAMC,IAAI,GAAG,MAAMJ,QAAQ,CAACK,IAAI,EAAE;IAClC,IACE,CAAC,CAACL,QAAQ,CAACE,OAAO,CAACI,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,EAAEC,UAAU,CAAC,iBAAiB,CAAC,EAC3E;QACA,IAAIC,MAAM,GAAGJ,IAAI;QACjB,IAAI;YACFI,MAAM,GAAGC,IAAI,CAACC,KAAK,CAACN,IAAI,CAAC,CAACO,OAAO,IAAIP,IAAI;QAC3C,CAAC;QAAC,MAAM,CAAC;QACT,MAAM,IAAIQ,KAAK,CACb,iCAAiC,GAAGZ,QAAQ,CAACG,MAAM,GAAG,KAAK,GAAGK,MAAM,CACrE;IACH;IACA,OAAQ,MAAMZ,QAAA,EAAK,CAACQ,IAAa,CAAC;AACpC,CAAC\",\"names\":[\"$splice0\",\"url\",\"modules\",\"notFoundIsNull\",\"response\",\"fetch\",\"headers\",\"status\",\"body\",\"text\",\"get\",\"startsWith\",\"reason\",\"JSON\",\"parse\",\"message\",\"Error\"],\"ignoreList\":[],\"sources\":[\"@backtickjs/hono/src/runtime.tsx\"]}",
14
+ id: "1nni9w5x4wwcx:65:27",
15
+ code: "(module, exports, require) => {\n\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.default = $splice0 => async (url, modules) => {\n var aborter = new AbortController();\n var timer = setTimeout(() => aborter.abort(), 15000);\n var response = await fetch(url, {\n headers: {\n \"x-backtick-modules\": modules\n },\n signal: aborter.signal\n }).finally(() => clearTimeout(timer));\n var body = await response.text();\n if (!(response.headers.get(\"content-type\") ?? \"\").startsWith(\"text/javascript\")) {\n var reason = body;\n try {\n reason = JSON.parse(body).message ?? body;\n }\n catch { }\n throw new Error(\"The screen couldn't be opened (\" + response.status + \"): \" + reason);\n }\n return await $splice0()(body);\n};\n}",
16
+ map: "{\"version\":3,\"file\":\"module.jsx\",\"mappings\":\";;;kBAgE8BA,QAAA,WAAOC,GAAW,EAAEC,OAAe;IAI/D,IAAMC,OAAO,GAAG,IAAIC,eAAe,EAAE;IACrC,IAAMC,KAAK,GAAGC,UAAU,CAAC,MAAMH,OAAO,CAACI,KAAK,EAAE,EAAE,KAAK,CAAC;IACtD,IAAMC,QAAQ,GAAG,MAAMC,KAAK,CAACR,GAAG,EAAE;QAChCS,OAAO,EAAE;YAAE,oBAAoB,EAAER;SAAS;QAC1CS,MAAM,EAAER,OAAO,CAACQ;KACjB,CAAC,CAACC,OAAO,CAAC,MAAMC,YAAY,CAACR,KAAK,CAAC,CAAC;IACrC,IAAMS,IAAI,GAAG,MAAMN,QAAQ,CAACO,IAAI,EAAE;IAClC,IACE,CAAC,CAACP,QAAQ,CAACE,OAAO,CAACM,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,EAAEC,UAAU,CAAC,iBAAiB,CAAC,EAC3E;QACA,IAAIC,MAAM,GAAGJ,IAAI;QACjB,IAAI;YACFI,MAAM,GAAGC,IAAI,CAACC,KAAK,CAACN,IAAI,CAAC,CAACO,OAAO,IAAIP,IAAI;QAC3C,CAAC;QAAC,MAAM,CAAC;QACT,MAAM,IAAIQ,KAAK,CACb,iCAAiC,GAAGd,QAAQ,CAACe,MAAM,GAAG,KAAK,GAAGL,MAAM,CACrE;IACH;IACA,OAAQ,MAAMlB,QAAA,EAAK,CAACc,IAAa,CAAC;AACpC,CAAC\",\"names\":[\"$splice0\",\"url\",\"modules\",\"aborter\",\"AbortController\",\"timer\",\"setTimeout\",\"abort\",\"response\",\"fetch\",\"headers\",\"signal\",\"finally\",\"clearTimeout\",\"body\",\"text\",\"get\",\"startsWith\",\"reason\",\"JSON\",\"parse\",\"message\",\"Error\",\"status\"],\"ignoreList\":[],\"sources\":[\"@backtickjs/hono/src/runtime.tsx\"]}",
18
17
  dependencies: [],
19
18
  params: [{ kind: "splice", bindings: [] }],
20
19
  kind: "function"
21
20
  };
22
21
  const $module1 = {
23
- id: "2hmayizp567y2:96:9",
24
- code: "(module, exports, require) => {\n\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst jsx_runtime_1 = require(\"react/jsx-runtime\");\nexports.default = ($splice0, $splice1, $splice2, $splice3, $splice4, $splice5, $splice6, $splice7, $splice8, $splice9, $splice10, $splice11, $splice12, $splice13, $splice14, $splice15, $splice16, $splice17, $splice18) => {\n var pageStaleTime = 30000;\n var loadingStaleTime = 300000;\n var queryClient = new ($splice0())({\n defaultOptions: {\n queries: {\n queryFn: async (context) => $splice1()($splice2() + context.queryKey[0], $splice3(), false),\n staleTime: pageStaleTime,\n structuralSharing: false,\n retry: false\n }\n }\n });\n var loadingQueryOptions = path => ({\n queryKey: [path],\n staleTime: loadingStaleTime,\n queryFn: async () => ({\n value: await $splice4()($splice5() + path, $splice6(), true)\n })\n });\n var loadingRouter = new ($splice7())();\n for (var pattern of $splice8()) {\n loadingRouter.add(\"GET\", pattern, true);\n }\n var loadingPathOf = href => {\n var searchStart = href.indexOf(\"?\");\n var path = searchStart === -1 ? href : href.slice(0, searchStart);\n var loading = (path === \"/\" ? \"\" : path) + \"/_loading\";\n return loadingRouter.match(\"GET\", loading)[0].length > 0 ? loading : undefined;\n };\n var dropStaleAnswer = (path, startedAt) => {\n var query = queryClient.getQueryCache().find({\n queryKey: [path],\n exact: true\n });\n if (query !== undefined && query.getObserversCount() === 0 && query.state.data !== undefined && query.state.dataUpdatedAt < startedAt && query.isStaleByTime(pageStaleTime)) {\n queryClient.removeQueries({\n queryKey: [path],\n exact: true\n });\n }\n };\n var Page = props => $splice9()({\n queryKey: [props.href]\n }).data;\n var PageWithLoading = props => {\n var loading = loadingPathOf(props.href);\n $splice10()({\n queryKey: [props.href]\n });\n var queries = loading === undefined ? [] : [loadingQueryOptions(loading)];\n var loadingResult = $splice11()({\n queries\n })[0];\n return loadingResult === undefined ? (0, jsx_runtime_1.jsx)(Page, {\n href: props.href\n }) : ($Suspense => (0, jsx_runtime_1.jsx)($Suspense, {\n fallback: loadingResult.data.value,\n children: (0, jsx_runtime_1.jsx)(Page, {\n href: props.href\n })\n }))($splice12());\n };\n var prefetch = href => {\n var loading = loadingPathOf(href);\n if (loading === undefined) {\n void queryClient.query({\n queryKey: [href]\n }).catch(() => { });\n return;\n }\n void queryClient.query(loadingQueryOptions(loading)).then(answer => answer.value === null ? queryClient.query({\n queryKey: [href]\n }) : undefined).catch(() => { });\n };\n var Screen = props => {\n var href = $splice13()(props.href);\n return props.active ? ($Suspense => (0, jsx_runtime_1.jsx)($Suspense, {\n fallback: null,\n children: (0, jsx_runtime_1.jsx)(PageWithLoading, {\n href: href\n })\n }))($splice14()) : null;\n };\n var AwaitQuery = props => {\n $splice15()({\n ...props.query,\n refetchOnMount: false\n });\n return null;\n };\n var currentNavigation = {\n href: \"\",\n startedAt: 0\n };\n var NavigationHold = props => {\n if (currentNavigation.href !== props.href) {\n currentNavigation = {\n href: props.href,\n startedAt: Date.now()\n };\n }\n dropStaleAnswer(props.href, currentNavigation.startedAt);\n var loading = loadingPathOf(props.href);\n if (loading !== undefined) {\n void queryClient.query({\n queryKey: [props.href]\n }).catch(() => { });\n return (0, jsx_runtime_1.jsx)(AwaitQuery, {\n query: loadingQueryOptions(loading)\n });\n }\n return (0, jsx_runtime_1.jsx)(AwaitQuery, {\n query: {\n queryKey: [props.href]\n }\n });\n };\n class SilentErrorBoundary extends $splice16() {\n state = {\n failed: false\n };\n static getDerivedStateFromError() {\n return {\n failed: true\n };\n }\n render() {\n return this.state.failed ? null : this.props.children;\n }\n }\n var Navigator = props => ($QueryClientProvider => (0, jsx_runtime_1.jsxs)($QueryClientProvider, {\n client: queryClient,\n children: [(0, jsx_runtime_1.jsx)(SilentErrorBoundary, {\n children: (0, jsx_runtime_1.jsx)(NavigationHold, {\n href: props.href\n })\n }, props.href), ($RootLayout => (0, jsx_runtime_1.jsx)($RootLayout, {}))($splice18())]\n }))($splice17());\n var runtime = {\n Navigator,\n Screen,\n prefetch,\n reload: async (href) => {\n var loading = loadingPathOf(href);\n await Promise.allSettled((loading === undefined ? [href] : [href, loading]).map(path => queryClient.getQueryState([path])?.status === \"error\" ? queryClient.resetQueries({\n queryKey: [path],\n exact: true\n }) : queryClient.refetchQueries({\n queryKey: [path],\n exact: true\n })));\n }\n };\n return runtime;\n};\n}",
25
- map: "{\"version\":3,\"file\":\"module.jsx\",\"mappings\":\"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\",\"names\":[\"$splice0\",\"$splice1\",\"$splice2\",\"$splice3\",\"$splice4\",\"$splice5\",\"$splice6\",\"$splice7\",\"$splice8\",\"$splice9\",\"$splice10\",\"$splice11\",\"$splice12\",\"$splice13\",\"$splice14\",\"$splice15\",\"$splice16\",\"$splice17\",\"$splice18\",\"pageStaleTime\",\"loadingStaleTime\",\"queryClient\",\"defaultOptions\",\"queries\",\"queryFn\",\"context\",\"queryKey\",\"staleTime\",\"structuralSharing\",\"retry\",\"loadingQueryOptions\",\"path\",\"value\",\"loadingRouter\",\"pattern\",\"add\",\"loadingPathOf\",\"href\",\"searchStart\",\"indexOf\",\"slice\",\"loading\",\"match\",\"length\",\"undefined\",\"dropStaleAnswer\",\"startedAt\",\"query\",\"getQueryCache\",\"find\",\"exact\",\"getObserversCount\",\"state\",\"data\",\"dataUpdatedAt\",\"isStaleByTime\",\"removeQueries\",\"Page\",\"props\",\"PageWithLoading\",\"loadingResult\",\"_jsx\",\"$Suspense\",\"fallback\",\"children\",\"prefetch\",\"catch\",\"then\",\"answer\",\"Screen\",\"active\",\"AwaitQuery\",\"refetchOnMount\",\"currentNavigation\",\"NavigationHold\",\"Date\",\"now\",\"SilentErrorBoundary\",\"failed\",\"getDerivedStateFromError\",\"render\",\"Navigator\",\"$QueryClientProvider\",\"_jsxs\",\"client\",\"$RootLayout\",\"runtime\",\"reload\",\"Promise\",\"allSettled\",\"map\",\"getQueryState\",\"status\",\"resetQueries\",\"refetchQueries\"],\"ignoreList\":[],\"sources\":[\"@backtickjs/hono/src/runtime.tsx\"]}",
22
+ id: "1nni9w5x4wwcx:100:9",
23
+ code: "(module, exports, require) => {\n\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst jsx_runtime_1 = require(\"react/jsx-runtime\");\nexports.default = ($splice0, $splice1, $splice2, $splice3, $splice4, $splice5, $splice6, $splice7, $splice8, $splice9, $splice10, $splice11, $splice12, $splice13, $splice14, $splice15, $splice16, $splice17, $splice18, $splice19) => {\n var segmentStaleTime = 300000;\n var navigationStartedAt = 0;\n var fetchedAhead = new Map();\n var shown = query => query.getObserversCount() > 0;\n var segmentStale = query => shown(query) ? Infinity : segmentStaleTime;\n var pageStale = query => {\n var updatedAt = query.state.dataUpdatedAt;\n if (shown(query) || updatedAt >= navigationStartedAt) {\n return Infinity;\n }\n return fetchedAhead.get(query.queryKey[0]) === updatedAt ? segmentStaleTime : 0;\n };\n var fetchPath = path => () => $splice0()($splice1() + path, $splice2());\n var queryClient = new ($splice3())({\n defaultOptions: {\n queries: {\n structuralSharing: false,\n retry: false\n }\n }\n });\n var pageQuery = href => $splice4()({\n queryKey: [href],\n queryFn: fetchPath(href),\n staleTime: pageStale\n });\n var layoutQuery = path => $splice5()({\n queryKey: [path],\n queryFn: fetchPath(path),\n staleTime: segmentStale\n });\n var loadingQuery = pattern => $splice6()({\n queryKey: [pattern],\n queryFn: fetchPath(pattern),\n staleTime: segmentStale\n });\n var layoutRouter = new ($splice7())();\n for (var pattern of $splice8().layouts) {\n layoutRouter.add(\"GET\", pattern, pattern);\n }\n var loadingRouter = new ($splice9())();\n for (var _pattern of $splice10().loadings) {\n loadingRouter.add(\"GET\", _pattern, _pattern);\n }\n var segmentsOf = href => {\n var searchStart = href.indexOf(\"?\");\n var path = searchStart === -1 ? href : href.slice(0, searchStart);\n var names = path.split(\"/\").filter(Boolean);\n var bases = [\"\", ...names.map((_, i) => \"/\" + names.slice(0, i + 1).join(\"/\"))];\n return bases.map(base => {\n var layout = base + \"/_layout\";\n return {\n layout: base !== \"\" && layoutRouter.match(\"GET\", layout)[0].length > 0 ? layout : undefined,\n loading: loadingRouter.match(\"GET\", base + \"/_loading\")[0][0]?.[0]\n };\n });\n };\n var shellOf = href => {\n var queries = [];\n for (var segment of segmentsOf(href)) {\n if (segment.layout !== undefined) {\n queries.push(layoutQuery(segment.layout));\n }\n if (segment.loading !== undefined) {\n queries.push(loadingQuery(segment.loading));\n return {\n queries,\n loads: true\n };\n }\n }\n queries.push(pageQuery(href));\n return {\n queries,\n loads: false\n };\n };\n var Page = props => $splice11()(pageQuery(props.href), queryClient).data;\n var Loading = props => $splice12()(loadingQuery(props.pattern), queryClient).data;\n var Layout = props => {\n var answer = $splice13()(layoutQuery(props.path), queryClient).data;\n if (typeof answer !== \"function\") {\n return answer;\n }\n var Answer = answer;\n return (0, jsx_runtime_1.jsx)(Answer, {\n children: props.children\n });\n };\n var Segments = props => {\n $splice14()(pageQuery(props.href), queryClient);\n var node = (0, jsx_runtime_1.jsx)(Page, {\n href: props.href\n });\n var _loop = function (segment) {\n if (segment.loading !== undefined) {\n node = ($Suspense => (0, jsx_runtime_1.jsx)($Suspense, {\n fallback: (0, jsx_runtime_1.jsx)(Loading, {\n pattern: segment.loading\n }),\n children: node\n }))($splice15());\n }\n if (segment.layout !== undefined) {\n node = (0, jsx_runtime_1.jsx)(Layout, {\n path: segment.layout,\n children: node\n });\n }\n };\n for (var segment of segmentsOf(props.href).reverse()) {\n _loop(segment);\n }\n return node;\n };\n var maxPrefetches = 4;\n var prefetching = 0;\n var waiting = [];\n var startWaiting = () => {\n while (prefetching < maxPrefetches && waiting.length > 0) {\n waiting.shift()();\n }\n };\n var schedule = (task, urgent) => {\n var start = () => {\n prefetching += 1;\n void task().catch(() => { }).finally(() => {\n prefetching -= 1;\n startWaiting();\n });\n };\n if (urgent) {\n start();\n return () => { };\n }\n waiting.push(start);\n startWaiting();\n return () => {\n var index = waiting.indexOf(start);\n if (index !== -1) {\n waiting.splice(index, 1);\n }\n };\n };\n var prefetch = (href, options = {}) => {\n var full = options.kind === \"full\";\n var shell = shellOf(href);\n if (!full && !shell.loads) {\n return () => { };\n }\n return schedule(async () => {\n await Promise.all(shell.queries.map(query => queryClient.query(query)));\n if (full) {\n await queryClient.query(pageQuery(href));\n fetchedAhead.set(href, queryClient.getQueryState([href])?.dataUpdatedAt ?? 0);\n }\n }, options.urgent ?? false);\n };\n var Screen = props => {\n var href = $splice16()(props.href);\n return props.active ? ($Suspense => (0, jsx_runtime_1.jsx)($Suspense, {\n fallback: null,\n children: (0, jsx_runtime_1.jsx)(Segments, {\n href: href\n })\n }))($splice17()) : null;\n };\n var isIn = query => {\n var cached = queryClient.getQueryCache().find({\n queryKey: query.queryKey,\n exact: true\n });\n return cached !== undefined && cached.state.data !== undefined && !cached.isStaleByTime(query.staleTime(cached));\n };\n var hold;\n var NavigationHold = props => {\n if (hold?.href !== props.href) {\n navigationStartedAt = Date.now();\n var page = queryClient.getQueryCache().find({\n queryKey: [props.href],\n exact: true\n });\n if (page !== undefined && page.state.data !== undefined && page.isStaleByTime(pageStale(page))) {\n queryClient.removeQueries({\n queryKey: [props.href],\n exact: true\n });\n }\n var queries = shellOf(props.href).queries;\n hold = {\n href: props.href,\n ready: page !== undefined && shown(page) || queries.every(isIn) ? undefined : Promise.allSettled(queries.map(query => queryClient.query(query)))\n };\n void queryClient.query(pageQuery(props.href)).catch(() => { });\n }\n if (hold.ready !== undefined) {\n $splice18()(hold.ready);\n }\n return null;\n };\n var RootLayout = () => {\n var answer = $splice19()({\n queryKey: [\"/_layout\"],\n queryFn: fetchPath(\"/_layout\"),\n staleTime: Infinity\n }, queryClient).data;\n if (typeof answer !== \"function\") {\n throw new Error(\"The app's /_layout answers no component: it's the root layout, a client component drawing the navigator (a Stack).\");\n }\n var Answer = answer;\n return (0, jsx_runtime_1.jsx)(Answer, {});\n };\n var Navigator = props => (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, {\n children: [(0, jsx_runtime_1.jsx)(NavigationHold, {\n href: props.href\n }), (0, jsx_runtime_1.jsx)(RootLayout, {})]\n });\n var runtime = {\n Navigator,\n Screen,\n prefetch,\n reload: async (href) => {\n var paths = [href, ...segmentsOf(href).flatMap(segment => [...(segment.layout === undefined ? [] : [segment.layout]), ...(segment.loading === undefined ? [] : [segment.loading])])];\n await Promise.allSettled(paths.map(path => queryClient.getQueryState([path])?.status === \"error\" ? queryClient.resetQueries({\n queryKey: [path],\n exact: true\n }) : queryClient.refetchQueries({\n queryKey: [path],\n exact: true\n })));\n }\n };\n return runtime;\n};\n}",
24
+ map: "{\"version\":3,\"file\":\"module.jsx\",\"mappings\":\"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\",\"names\":[\"$splice0\",\"$splice1\",\"$splice2\",\"$splice3\",\"$splice4\",\"$splice5\",\"$splice6\",\"$splice7\",\"$splice8\",\"$splice9\",\"$splice10\",\"$splice11\",\"$splice12\",\"$splice13\",\"$splice14\",\"$splice15\",\"$splice16\",\"$splice17\",\"$splice18\",\"$splice19\",\"segmentStaleTime\",\"navigationStartedAt\",\"fetchedAhead\",\"Map\",\"shown\",\"query\",\"getObserversCount\",\"segmentStale\",\"Infinity\",\"pageStale\",\"updatedAt\",\"state\",\"dataUpdatedAt\",\"get\",\"queryKey\",\"fetchPath\",\"path\",\"queryClient\",\"defaultOptions\",\"queries\",\"structuralSharing\",\"retry\",\"pageQuery\",\"href\",\"queryFn\",\"staleTime\",\"layoutQuery\",\"loadingQuery\",\"pattern\",\"layoutRouter\",\"layouts\",\"add\",\"loadingRouter\",\"loadings\",\"segmentsOf\",\"searchStart\",\"indexOf\",\"slice\",\"names\",\"split\",\"filter\",\"Boolean\",\"bases\",\"map\",\"_\",\"i\",\"join\",\"base\",\"layout\",\"match\",\"length\",\"undefined\",\"loading\",\"shellOf\",\"segment\",\"push\",\"loads\",\"Page\",\"props\",\"data\",\"Loading\",\"Layout\",\"answer\",\"Answer\",\"_jsx\",\"children\",\"Segments\",\"node\",\"_loop\",\"$Suspense\",\"fallback\",\"reverse\",\"maxPrefetches\",\"prefetching\",\"waiting\",\"startWaiting\",\"shift\",\"schedule\",\"task\",\"urgent\",\"start\",\"catch\",\"finally\",\"index\",\"splice\",\"prefetch\",\"options\",\"full\",\"kind\",\"shell\",\"Promise\",\"all\",\"set\",\"getQueryState\",\"Screen\",\"active\",\"isIn\",\"cached\",\"getQueryCache\",\"find\",\"exact\",\"isStaleByTime\",\"hold\",\"NavigationHold\",\"Date\",\"now\",\"page\",\"removeQueries\",\"ready\",\"every\",\"allSettled\",\"RootLayout\",\"Error\",\"Navigator\",\"_jsxs\",\"_Fragment\",\"runtime\",\"reload\",\"paths\",\"flatMap\",\"status\",\"resetQueries\",\"refetchQueries\"],\"ignoreList\":[],\"sources\":[\"@backtickjs/hono/src/runtime.tsx\"]}",
26
25
  dependencies: ["react/jsx-runtime"],
27
- params: [{ kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }],
26
+ params: [{ kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }, { kind: "splice", bindings: [] }],
28
27
  kind: "block"
29
28
  };
30
29
  /**
31
30
  * A route's answer, run. Any bundle is an answer, whatever its status: an
32
- * app's not-found screen is one. A refusal ({ name, message }) says why. For
33
- * a route that may not exist (`notFoundIsNull`), a 404 is null.
31
+ * app's not-found screen is one. A refusal ({ name, message }) says why.
34
32
  */
35
33
  export const fetchAnswer = cs.create($module0, [exec]);
36
34
  /**
37
- * The runtime of an open: for the phone at `origin` with `modules`, around
38
- * the app's root layout, with the patterns of its `_loading` routes
39
- * (`/city/:id/_loading`), so a screen asks only for those it has.
35
+ * The runtime of an open: for the phone at `origin` with `modules`, with the
36
+ * patterns of the app's `_layout` and `_loading` routes (`/city/:id/_layout`),
37
+ * so a screen asks only for those it has.
40
38
  */
41
- export function clientRuntime(origin, modules, RootLayout, loadingPatterns) {
42
- return cs.create($module1, [QueryClient, fetchAnswer, origin, modules, fetchAnswer, origin, modules, PatternRouter, loadingPatterns, useSuspenseQuery, usePrefetchQuery, useSuspenseQueries, Suspense, useDeferredValue, Suspense, useSuspenseQuery, Component, QueryClientProvider, RootLayout]);
39
+ export function clientRuntime(origin, modules, routes) {
40
+ return cs.create($module1, [fetchAnswer, origin, modules, QueryClient, queryOptions, queryOptions, queryOptions, PatternRouter, routes, PatternRouter, routes, useSuspenseQuery, useSuspenseQuery, useSuspenseQuery, usePrefetchQuery, Suspense, useDeferredValue, Suspense, use, useSuspenseQuery]);
43
41
  }
44
42
  //# sourceMappingURL=runtime.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.tsx"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,2EAA2E;AAC3E,mCAAmC;AACnC,OAAO,EAAe,EAAE,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,8BAwBtB,CAAC;AAEH;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAc,EACd,OAAe,EACf,UAAiC,EACjC,eAAkC;IAElC,kSAyLG;AACL,CAAC"}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.tsx"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,2EAA2E;AAC3E,mCAAmC;AACnC,OAAO,EAAe,EAAE,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AAyDzD;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,8BAuBtB,CAAC;AAEH;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAc,EACd,OAAe,EACf,MAAmE;IAEnE,qSA8UG;AACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtickjs/hono",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Hono routes as Backtick screens: a route's answer is its server components, bundled for the client that asked",
5
5
  "keywords": [
6
6
  "backtick",
@@ -37,18 +37,18 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@backtickjs/bundler": "0.3.0",
41
- "@backtickjs/core": "0.3.0"
40
+ "@backtickjs/bundler": "0.3.2",
41
+ "@backtickjs/core": "0.3.2"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "hono": "^4.13.13"
45
45
  },
46
46
  "devDependencies": {
47
- "@backtickjs/expo": "0.3.0",
48
- "@backtickjs/jsx-react": "0.3.0",
49
- "@backtickjs/prettier-plugin": "0.3.0",
50
- "@backtickjs/tsc": "0.3.0",
51
- "@backtickjs/tspatch-plugin": "0.3.0",
47
+ "@backtickjs/expo": "0.3.2",
48
+ "@backtickjs/jsx-react": "0.3.2",
49
+ "@backtickjs/prettier-plugin": "0.3.2",
50
+ "@backtickjs/tsc": "0.3.2",
51
+ "@backtickjs/tspatch-plugin": "0.3.2",
52
52
  "@tanstack/react-query": "5.104.0",
53
53
  "@types/react": "19.3.0",
54
54
  "hono": "^4.13.13",
package/src/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { bundler, BundlerError, type Modules } from "@backtickjs/bundler";
2
- import type { Client, Spliceable, SplicesAs } from "@backtickjs/core";
3
- import type { ComponentType } from "react";
2
+ import type { Spliceable, SplicesAs } from "@backtickjs/core";
4
3
  import type { Context, Hono, MiddlewareHandler } from "hono";
5
4
  import { clientRuntime } from "./runtime.js";
6
5
 
@@ -26,11 +25,11 @@ declare module "hono" {
26
25
  *
27
26
  * The status is the context's, so `context.status(404)` makes a not-found
28
27
  * screen, and screens are `no-store`, kept out of HTTP caches: a reload or
29
- * a new open always gets the latest copy. (The Backtick app reuses a screen
30
- * it has, in memory, for 30 seconds, as Next.js's router does.) A client
28
+ * a new open always gets the latest copy. (The Backtick app fetches a page anew
29
+ * for each navigation, as Next.js's router does.) A client
31
30
  * without a module the screen needs gets a 426, and any other refusal of the
32
31
  * bundler's a 400, each with `{ name, message }`. An app the Backtick app
33
- * opens uses `backtickApp(app, RootLayout)` instead.
32
+ * opens uses `backtickApp(app)` instead.
34
33
  */
35
34
  export function backtickRenderer(): MiddlewareHandler {
36
35
  return async (context, next) => {
@@ -42,54 +41,61 @@ export function backtickRenderer(): MiddlewareHandler {
42
41
  /**
43
42
  * Hono's renderer for an app the Backtick app opens, as `backtickRenderer()`,
44
43
  * which also answers the runtime the Backtick app fetches when it opens
45
- * `app`: the client code that fetches, keeps and shows its screens, around
46
- * its root layout, a client component that draws its `Stack`.
44
+ * `app`: the client code that fetches, keeps and shows its screens.
47
45
  *
48
- * const RootLayout = cs`() => <$Stack />`;
49
- * app.use(backtickApp(app, RootLayout));
46
+ * app.use(backtickApp(app));
47
+ * app.get("/_layout", (context) => context.render(cs`() => <$Stack />`));
50
48
  * app.get("/", (context) => context.render(<Home />));
51
49
  *
52
- * Every route answers with what it draws. As in Next.js, a route with a
53
- * `_loading` route (`/city/:id/_loading`, rendered for the path, so it can
54
- * show the city's name) pushes as soon as that's in, at once if it was
55
- * prefetched, and shows it until the route's answer is in; a navigation to
56
- * one without holds the current screen until its answer is in. The runtime
57
- * asks only for the loading states `app` has routes for. One with a layout
58
- * route (a screen has none: what screens share is a component they draw), or
59
- * with a loading route written with a trailing slash, doesn't open.
50
+ * Routes nest as Next.js's folders. `/_layout` is the root layout, a client
51
+ * component drawing the navigator; an app without one doesn't open. A screen
52
+ * is each segment's `_layout` (`/city/:id/_layout`, rendered for its path,
53
+ * answering a client component given `children`), around its `_loading`
54
+ * while what's under it loads, around the page. A loading state takes no
55
+ * params, as in Next.js: it's fetched once for its pattern, and reading a
56
+ * param throws. A navigation pushes once its layouts down to its first
57
+ * loading state, and that loading state, are in; with none, once all of it
58
+ * is. Layout and loading routes written with a trailing slash are refused.
60
59
  */
61
- export function backtickApp(
62
- app: Hono<any, any, any>,
63
- RootLayout: Client<ComponentType>,
64
- ): MiddlewareHandler {
60
+ export function backtickApp(app: Hono<any, any, any>): MiddlewareHandler {
65
61
  const renderer = backtickRenderer();
66
62
  return async (context, next) => {
63
+ if (context.req.path.endsWith("/_loading")) {
64
+ context.req.param = () => {
65
+ throw new Error(
66
+ `${context.req.routePath} reads a param: a loading state takes none, as in Next.js. What depends on the path goes in a _layout.`,
67
+ );
68
+ };
69
+ }
67
70
  if (context.req.method !== "GET" || context.req.path !== runtimePath) {
68
71
  return renderer(context, next);
69
72
  }
73
+ const patterns = (name: string) => [
74
+ ...new Set(
75
+ app.routes
76
+ .filter(({ method, path }) => method === "GET" && path.endsWith(name))
77
+ .map(({ path }) => path),
78
+ ),
79
+ ];
70
80
  const misplaced = app.routes.find(
71
- ({ path }) => path.endsWith("/_loading/") || /\/_layout\/?$/.test(path),
81
+ ({ path }) => path.endsWith("/_layout/") || path.endsWith("/_loading/"),
72
82
  );
73
83
  if (misplaced !== undefined) {
74
84
  throw new Error(
75
- misplaced.path.endsWith("/_loading/")
76
- ? `The app's ${misplaced.path} route ends with a slash: write it ${misplaced.path.slice(0, -1)}.`
77
- : `The app's ${misplaced.path} route is a layout, which a screen doesn't have: the root layout is backtickApp(app, RootLayout)'s, and what screens share is a component they draw.`,
85
+ `The app's ${misplaced.path} route ends with a slash: write it ${misplaced.path.slice(0, -1)}.`,
86
+ );
87
+ }
88
+ if (!patterns("/_layout").includes("/_layout")) {
89
+ throw new Error(
90
+ "The app has no /_layout route: it's the root layout, a client component drawing the navigator, as cs`() => <$Stack />`.",
78
91
  );
79
92
  }
80
- // The app's loading states, by pattern.
81
- const loadings = app.routes
82
- .filter(
83
- ({ method, path }) => method === "GET" && path.endsWith("/_loading"),
84
- )
85
- .map(({ path }) => path);
86
93
  return bundled(
87
94
  context,
88
95
  clientRuntime(
89
96
  new URL(context.req.url).origin,
90
97
  context.req.header("x-backtick-modules") ?? "{}",
91
- RootLayout,
92
- [...new Set(loadings)],
98
+ { layouts: patterns("/_layout"), loadings: patterns("/_loading") },
93
99
  ),
94
100
  );
95
101
  };
package/src/runtime.tsx CHANGED
@@ -4,19 +4,18 @@
4
4
  import { type Client, cs, exec } from "@backtickjs/core";
5
5
  import {
6
6
  QueryClient,
7
- QueryClientProvider,
7
+ queryOptions,
8
8
  usePrefetchQuery,
9
- useSuspenseQueries,
10
9
  useSuspenseQuery,
11
10
  } from "@tanstack/react-query" with { backtick: "client" };
12
11
  import { PatternRouter } from "hono/router/pattern-router" with {
13
12
  backtick: "client",
14
13
  };
15
14
  import {
16
- Component,
17
15
  type ComponentType,
18
16
  type ReactNode,
19
17
  Suspense,
18
+ use,
20
19
  useDeferredValue,
21
20
  } from "react" with { backtick: "client" };
22
21
 
@@ -28,14 +27,16 @@ export interface Runtime {
28
27
  /**
29
28
  * The app's root layout, given the href on top. A navigation holds the
30
29
  * current screen until the next can show, as navigations are transitions
31
- * (`router.setTransitionMode("always")`): a route with a `_loading` once
32
- * that's in, at once if it was prefetched; one without, once its page is
33
- * in. Suspends until the first screen can show.
30
+ * (`router.setTransitionMode("always")`): once its layouts down to its
31
+ * first loading state, and that loading state, are in, at once if they
32
+ * were prefetched; with no loading state, once all of it is in. Suspends
33
+ * until the first screen can show.
34
34
  */
35
35
  Navigator?: ComponentType<{ href: string }>;
36
36
  /**
37
- * A route's screen: what it renders, and meanwhile what its `_loading`
38
- * renders. One not `active` yet shows nothing.
37
+ * A route's screen, nested as Next.js nests a page: each segment's layout,
38
+ * around its loading state while what's under it loads. One not `active`
39
+ * yet shows nothing.
39
40
  */
40
41
  Screen?: ComponentType<{ href: string; active: boolean }>;
41
42
  /**
@@ -44,29 +45,33 @@ export interface Runtime {
44
45
  */
45
46
  reload?(href: string): Promise<void>;
46
47
  /**
47
- * Fetches an href ahead, as Next.js prefetches a link: its loading state,
48
- * or, without one, its page. For each `<Link prefetch>`, which the shell
49
- * sees as it's prefetched.
48
+ * Fetches an href ahead, as Next.js's `Link` prefetches: its layouts down
49
+ * to its first loading state, and that loading state (nothing if it has
50
+ * none), as Next.js's `kind: "auto"`; with `kind: "full"`, its page too,
51
+ * as `<Link prefetch>`. At most 4 run at a time, `urgent` ones (a pressed
52
+ * link) first. Returns a cancel, for a link that goes away before its
53
+ * turn.
50
54
  */
51
- prefetch?(href: string): void;
55
+ prefetch?(
56
+ href: string,
57
+ options?: { kind?: "auto" | "full"; urgent?: boolean },
58
+ ): () => void;
52
59
  }
53
60
 
54
61
  /**
55
62
  * A route's answer, run. Any bundle is an answer, whatever its status: an
56
- * app's not-found screen is one. A refusal ({ name, message }) says why. For
57
- * a route that may not exist (`notFoundIsNull`), a 404 is null.
63
+ * app's not-found screen is one. A refusal ({ name, message }) says why.
58
64
  */
59
- export const fetchAnswer = cs`async (
60
- url: string,
61
- modules: string,
62
- notFoundIsNull: boolean,
63
- ) => {
65
+ export const fetchAnswer = cs`async (url: string, modules: string) => {
66
+ // One that doesn't answer within 15 seconds fails, as a stalled
67
+ // connection would otherwise hold a navigation, or a prefetch's turn, for
68
+ // good.
69
+ const aborter = new AbortController();
70
+ const timer = setTimeout(() => aborter.abort(), 15000);
64
71
  const response = await fetch(url, {
65
72
  headers: { "x-backtick-modules": modules },
66
- });
67
- if (notFoundIsNull && response.status === 404) {
68
- return null;
69
- }
73
+ signal: aborter.signal,
74
+ }).finally(() => clearTimeout(timer));
70
75
  const body = await response.text();
71
76
  if (
72
77
  !(response.headers.get("content-type") ?? "").startsWith("text/javascript")
@@ -83,120 +88,224 @@ export const fetchAnswer = cs`async (
83
88
  }`;
84
89
 
85
90
  /**
86
- * The runtime of an open: for the phone at `origin` with `modules`, around
87
- * the app's root layout, with the patterns of its `_loading` routes
88
- * (`/city/:id/_loading`), so a screen asks only for those it has.
91
+ * The runtime of an open: for the phone at `origin` with `modules`, with the
92
+ * patterns of the app's `_layout` and `_loading` routes (`/city/:id/_layout`),
93
+ * so a screen asks only for those it has.
89
94
  */
90
95
  export function clientRuntime(
91
96
  origin: string,
92
97
  modules: string,
93
- RootLayout: Client<ComponentType>,
94
- loadingPatterns: readonly string[],
98
+ routes: { layouts: readonly string[]; loadings: readonly string[] },
95
99
  ): Client<Runtime> {
96
100
  return cs`{
97
- // Every answer is a query, keyed by its path: what the route renders. A
98
- // screen is its page's, and its loading state's while the page loads.
99
- // Each is reused, in memory, as Next.js's router cache does: a page for
100
- // 30 seconds, a loading state for 5 minutes. A screen's no-store keeps it
101
- // out of HTTP caches, not this one.
102
- const pageStaleTime = 30000;
103
- const loadingStaleTime = 300000;
101
+ // Every answer is a query, keyed by where it was fetched, and reused in
102
+ // memory as Next.js's router cache does: a layout or a loading state for 5
103
+ // minutes; a page only for the navigation it was fetched for (0 s), or,
104
+ // fetched ahead by <Link prefetch>, for 5 minutes. What a screen shows
105
+ // never goes stale, so going back shows it as it was. A screen's no-store
106
+ // keeps it out of HTTP caches, not this one.
107
+ const segmentStaleTime = 300000;
108
+ // When the navigation under way started, and the answers <Link prefetch>
109
+ // fetched ahead (each href's, by when it came in).
110
+ let navigationStartedAt = 0;
111
+ const fetchedAhead = new Map<string, number>();
112
+ type Cached = {
113
+ queryKey: readonly unknown[];
114
+ state: { dataUpdatedAt: number };
115
+ getObserversCount(): number;
116
+ };
117
+ const shown = (query: Cached) => query.getObserversCount() > 0;
118
+ const segmentStale = (query: Cached) =>
119
+ shown(query) ? Infinity : segmentStaleTime;
120
+ const pageStale = (query: Cached) => {
121
+ const updatedAt = query.state.dataUpdatedAt;
122
+ if (shown(query) || updatedAt >= navigationStartedAt) {
123
+ return Infinity;
124
+ }
125
+ return fetchedAhead.get(query.queryKey[0] as string) === updatedAt
126
+ ? segmentStaleTime
127
+ : 0;
128
+ };
129
+ const fetchPath = (path: string) => () =>
130
+ $fetchAnswer($origin + path, $modules);
104
131
  const queryClient = new $QueryClient({
105
132
  defaultOptions: {
106
- queries: {
107
- queryFn: async (context: { queryKey: readonly unknown[] }) =>
108
- $fetchAnswer(
109
- $origin + (context.queryKey[0] as string),
110
- $modules,
111
- false,
112
- ),
113
- staleTime: pageStaleTime,
114
- structuralSharing: false,
115
- retry: false,
116
- },
133
+ queries: { structuralSharing: false, retry: false },
117
134
  },
118
135
  });
119
- // A loading state: none if it answers 404, for a param it has none for.
120
- const loadingQueryOptions = (path: string) => ({
121
- queryKey: [path],
122
- staleTime: loadingStaleTime,
123
- queryFn: async () => ({
124
- value: (await $fetchAnswer(
125
- $origin + path,
126
- $modules,
127
- true,
128
- )) as ReactNode,
129
- }),
130
- });
131
- // An href's loading state, if the app has a _loading route for it,
132
- // matched by Hono's own router.
133
- const loadingRouter = new $PatternRouter<true>();
134
- for (const pattern of $loadingPatterns) {
135
- loadingRouter.add("GET", pattern, true);
136
+ const pageQuery = (href: string) =>
137
+ $queryOptions({
138
+ queryKey: [href],
139
+ queryFn: fetchPath(href),
140
+ staleTime: pageStale,
141
+ });
142
+ // A layout answers a client component, given what's under it; or, for a
143
+ // path with none (its not-found screen), what the screen shows.
144
+ const layoutQuery = (path: string) =>
145
+ $queryOptions({
146
+ queryKey: [path],
147
+ queryFn: fetchPath(path),
148
+ staleTime: segmentStale,
149
+ });
150
+ // A loading state takes no params, as in Next.js: it's fetched at its
151
+ // pattern (/city/:id/_loading), once for every href it applies to.
152
+ const loadingQuery = (pattern: string) =>
153
+ $queryOptions({
154
+ queryKey: [pattern],
155
+ queryFn: fetchPath(pattern),
156
+ staleTime: segmentStale,
157
+ });
158
+ type Query =
159
+ | ReturnType<typeof pageQuery>
160
+ | ReturnType<typeof layoutQuery>
161
+ | ReturnType<typeof loadingQuery>;
162
+
163
+ // The app's layouts and loading states, matched by Hono's own router.
164
+ const layoutRouter = new $PatternRouter<string>();
165
+ for (const pattern of $routes.layouts) {
166
+ layoutRouter.add("GET", pattern, pattern);
167
+ }
168
+ const loadingRouter = new $PatternRouter<string>();
169
+ for (const pattern of $routes.loadings) {
170
+ loadingRouter.add("GET", pattern, pattern);
136
171
  }
137
- const loadingPathOf = (href: string) => {
172
+ // An href's segments, as Next.js's folders: each path prefix, with its
173
+ // layout's path and its loading state's pattern, where the app has them.
174
+ // The root's layout is the app's RootLayout.
175
+ const segmentsOf = (href: string) => {
138
176
  const searchStart = href.indexOf("?");
139
177
  const path = searchStart === -1 ? href : href.slice(0, searchStart);
140
- const loading = (path === "/" ? "" : path) + "/_loading";
141
- return loadingRouter.match("GET", loading)[0].length > 0
142
- ? loading
143
- : undefined;
178
+ const names = path.split("/").filter(Boolean);
179
+ const bases = [
180
+ "",
181
+ ...names.map((_, i) => "/" + names.slice(0, i + 1).join("/")),
182
+ ];
183
+ return bases.map((base) => {
184
+ const layout = base + "/_layout";
185
+ return {
186
+ layout:
187
+ base !== "" && layoutRouter.match("GET", layout)[0].length > 0
188
+ ? layout
189
+ : undefined,
190
+ loading: loadingRouter.match("GET", base + "/_loading")[0][0]?.[0],
191
+ };
192
+ });
144
193
  };
145
- // A stale answer no screen shows, in before a navigation started, is
146
- // dropped as the navigation reads it, so the navigation waits for a fresh
147
- // one, as Next.js's does; one a screen still shows, going back to it, is
148
- // kept.
149
- const dropStaleAnswer = (path: string, startedAt: number) => {
150
- const query = queryClient
151
- .getQueryCache()
152
- .find({ queryKey: [path], exact: true });
153
- if (
154
- query !== undefined &&
155
- query.getObserversCount() === 0 &&
156
- query.state.data !== undefined &&
157
- query.state.dataUpdatedAt < startedAt &&
158
- query.isStaleByTime(pageStaleTime)
159
- ) {
160
- queryClient.removeQueries({ queryKey: [path], exact: true });
194
+ // What an href's screen needs before it can show, as Next.js prefetches
195
+ // and holds: its layouts down to the first loading state, and that
196
+ // loading state; or, with none, all its layouts and its page.
197
+ const shellOf = (href: string) => {
198
+ const queries: Query[] = [];
199
+ for (const segment of segmentsOf(href)) {
200
+ if (segment.layout !== undefined) {
201
+ queries.push(layoutQuery(segment.layout));
202
+ }
203
+ if (segment.loading !== undefined) {
204
+ queries.push(loadingQuery(segment.loading));
205
+ return { queries, loads: true };
206
+ }
161
207
  }
208
+ queries.push(pageQuery(href));
209
+ return { queries, loads: false };
162
210
  };
163
-
211
+ // A page or a loading state answers what it draws.
164
212
  const Page = (props: { href: string }) =>
165
- $useSuspenseQuery({ queryKey: [props.href] }).data as ReactNode;
213
+ $useSuspenseQuery(pageQuery(props.href), queryClient).data as ReactNode;
214
+ const Loading = (props: { pattern: string }) =>
215
+ $useSuspenseQuery(loadingQuery(props.pattern), queryClient)
216
+ .data as ReactNode;
217
+ const Layout = (props: { path: string; children: ReactNode }) => {
218
+ const answer = $useSuspenseQuery(
219
+ layoutQuery(props.path),
220
+ queryClient,
221
+ ).data;
222
+ if (typeof answer !== "function") {
223
+ return answer as ReactNode;
224
+ }
225
+ const Answer = answer as ComponentType<{ children: ReactNode }>;
226
+ return <Answer>{props.children}</Answer>;
227
+ };
228
+ // A screen as Next.js nests a page: each segment's layout, around a
229
+ // Suspense showing its loading state, around what's under it. The page
230
+ // is fetched meanwhile.
231
+ const Segments = (props: { href: string }) => {
232
+ $usePrefetchQuery(pageQuery(props.href), queryClient);
233
+ let node: ReactNode = <Page href={props.href} />;
234
+ for (const segment of segmentsOf(props.href).reverse()) {
235
+ if (segment.loading !== undefined) {
236
+ node = (
237
+ <$Suspense fallback={<Loading pattern={segment.loading} />}>
238
+ {node}
239
+ </$Suspense>
240
+ );
241
+ }
242
+ if (segment.layout !== undefined) {
243
+ node = <Layout path={segment.layout}>{node}</Layout>;
244
+ }
245
+ }
246
+ return node;
247
+ };
166
248
 
167
- // A screen as Next.js shows a page: with a loading state, inside a
168
- // Suspense showing it; the page fetched meanwhile.
169
- const PageWithLoading = (props: { href: string }) => {
170
- const loading = loadingPathOf(props.href);
171
- $usePrefetchQuery({ queryKey: [props.href] });
172
- const queries: ReturnType<typeof loadingQueryOptions>[] =
173
- loading === undefined ? [] : [loadingQueryOptions(loading)];
174
- const loadingResult = $useSuspenseQueries({ queries })[0];
175
- return loadingResult === undefined ? (
176
- <Page href={props.href} />
177
- ) : (
178
- <$Suspense fallback={loadingResult.data.value}>
179
- <Page href={props.href} />
180
- </$Suspense>
181
- );
249
+ // Prefetches run at most 4 at a time, as Next.js's: one whose link goes
250
+ // away before its turn is dropped, and an urgent one (a pressed link)
251
+ // starts at once, past the limit. query() fetches only what isn't in, or
252
+ // on its way, when a turn comes.
253
+ const maxPrefetches = 4;
254
+ let prefetching = 0;
255
+ const waiting: (() => void)[] = [];
256
+ const startWaiting = () => {
257
+ while (prefetching < maxPrefetches && waiting.length > 0) {
258
+ waiting.shift()!();
259
+ }
260
+ };
261
+ const schedule = (task: () => Promise<unknown>, urgent: boolean) => {
262
+ const start = () => {
263
+ prefetching += 1;
264
+ void task()
265
+ .catch(() => {})
266
+ .finally(() => {
267
+ prefetching -= 1;
268
+ startWaiting();
269
+ });
270
+ };
271
+ if (urgent) {
272
+ start();
273
+ return () => {};
274
+ }
275
+ waiting.push(start);
276
+ startWaiting();
277
+ return () => {
278
+ const index = waiting.indexOf(start);
279
+ if (index !== -1) {
280
+ waiting.splice(index, 1);
281
+ }
282
+ };
182
283
  };
183
284
 
184
- // Fetches an href ahead, as Next.js prefetches: its loading state, or,
185
- // without one, its page.
186
- const prefetch = (href: string) => {
187
- const loading = loadingPathOf(href);
188
- if (loading === undefined) {
189
- void queryClient.query({ queryKey: [href] }).catch(() => {});
190
- return;
285
+ // Fetches an href ahead, as Next.js's Link: what its screen needs to
286
+ // show, if that ends at a loading state ("auto"), and its page too
287
+ // ("full", <Link prefetch>), whose answer is then kept 5 minutes.
288
+ const prefetch = (
289
+ href: string,
290
+ options: { kind?: "auto" | "full"; urgent?: boolean } = {},
291
+ ) => {
292
+ const full = options.kind === "full";
293
+ const shell = shellOf(href);
294
+ if (!full && !shell.loads) {
295
+ return () => {};
191
296
  }
192
- void queryClient
193
- .query(loadingQueryOptions(loading))
194
- .then((answer) =>
195
- answer.value === null
196
- ? queryClient.query({ queryKey: [href] })
197
- : undefined,
198
- )
199
- .catch(() => {});
297
+ return schedule(async () => {
298
+ await Promise.all(
299
+ shell.queries.map((query) => queryClient.query(query)),
300
+ );
301
+ if (full) {
302
+ await queryClient.query(pageQuery(href));
303
+ fetchedAhead.set(
304
+ href,
305
+ queryClient.getQueryState([href])?.dataUpdatedAt ?? 0,
306
+ );
307
+ }
308
+ }, options.urgent ?? false);
200
309
  };
201
310
 
202
311
  // An href changed in place (router.setParams) shows once it's in: the
@@ -205,71 +314,115 @@ export function clientRuntime(
205
314
  const href = $useDeferredValue(props.href);
206
315
  return props.active ? (
207
316
  <$Suspense fallback={null}>
208
- <PageWithLoading href={href} />
317
+ <Segments href={href} />
209
318
  </$Suspense>
210
319
  ) : null;
211
320
  };
212
321
 
213
- // Suspends until a query is in. One that is, however old, holds nothing:
214
- // a stale page was dropped above, and a stale loading state is refreshed
215
- // by the screen, as it's only shown until the page is in.
216
- const AwaitQuery = (props: { query: { queryKey: string[] } }) => {
217
- $useSuspenseQuery({ ...props.query, refetchOnMount: false });
218
- return null;
322
+ // A query whose answer is in and fresh: nothing to wait for.
323
+ const isIn = (query: Query) => {
324
+ const cached = queryClient
325
+ .getQueryCache()
326
+ .find({ queryKey: query.queryKey, exact: true });
327
+ return (
328
+ cached !== undefined &&
329
+ cached.state.data !== undefined &&
330
+ !cached.isStaleByTime(
331
+ (query.staleTime as (cached: Cached) => number)(cached),
332
+ )
333
+ );
219
334
  };
220
- // When the navigation to the href on top started: kept outside React, as
221
- // a hold that suspends keeps no state.
222
- let currentNavigation = { href: "", startedAt: 0 };
335
+ // The navigation on top, and what it waits for, if anything: kept
336
+ // outside React, as a render that suspends keeps no state.
337
+ let hold: { href: string; ready?: Promise<unknown> } | undefined;
223
338
  // Suspends a navigation's render, above the Stack, until the href's
224
- // loading state is in, or, without one, its page. As navigations are
225
- // transitions, the screen on top stays. One that fails ends the hold, for
226
- // the screen to show why.
339
+ // screen can show (shellOf), fetching whatever of it isn't in, stale or
340
+ // failed; the page alongside. As navigations are transitions, the screen
341
+ // on top stays meanwhile. One that fails ends the hold too, for the
342
+ // screen to show why.
227
343
  const NavigationHold = (props: { href: string }) => {
228
- if (currentNavigation.href !== props.href) {
229
- currentNavigation = { href: props.href, startedAt: Date.now() };
344
+ if (hold?.href !== props.href) {
345
+ navigationStartedAt = Date.now();
346
+ // A page from before, which no screen shows, would show as it was:
347
+ // dropped, so the screen shows its loading state, then the page.
348
+ const page = queryClient
349
+ .getQueryCache()
350
+ .find({ queryKey: [props.href], exact: true });
351
+ if (
352
+ page !== undefined &&
353
+ page.state.data !== undefined &&
354
+ page.isStaleByTime(pageStale(page))
355
+ ) {
356
+ queryClient.removeQueries({ queryKey: [props.href], exact: true });
357
+ }
358
+ // Only a navigation with something to fetch suspends. Going back,
359
+ // to a page a screen still shows, renders at once, however old its
360
+ // shell: it's no transition, so a suspension would reach the app's
361
+ // own Suspense.
362
+ const queries = shellOf(props.href).queries;
363
+ hold = {
364
+ href: props.href,
365
+ ready:
366
+ (page !== undefined && shown(page)) || queries.every(isIn)
367
+ ? undefined
368
+ : Promise.allSettled(
369
+ queries.map((query) => queryClient.query(query)),
370
+ ),
371
+ };
372
+ void queryClient.query(pageQuery(props.href)).catch(() => {});
230
373
  }
231
- dropStaleAnswer(props.href, currentNavigation.startedAt);
232
- // A route with a loading state pushes as soon as its loading state is
233
- // in, at once if it was prefetched; its page is fetched meanwhile.
234
- const loading = loadingPathOf(props.href);
235
- if (loading !== undefined) {
236
- void queryClient.query({ queryKey: [props.href] }).catch(() => {});
237
- return <AwaitQuery query={loadingQueryOptions(loading)} />;
374
+ if (hold.ready !== undefined) {
375
+ $use(hold.ready);
238
376
  }
239
- return <AwaitQuery query={{ queryKey: [props.href] }} />;
377
+ return null;
240
378
  };
241
- class SilentErrorBoundary extends $Component<
242
- { children: ReactNode },
243
- { failed: boolean }
244
- > {
245
- state = { failed: false };
246
- static getDerivedStateFromError() {
247
- return { failed: true };
248
- }
249
- render() {
250
- return this.state.failed ? null : this.props.children;
379
+
380
+ // The root layout, /_layout, which draws the navigator: fetched once per
381
+ // open, as a new one would remount the navigator, and its screens with it.
382
+ const RootLayout = () => {
383
+ const answer = $useSuspenseQuery(
384
+ {
385
+ queryKey: ["/_layout"],
386
+ queryFn: fetchPath("/_layout"),
387
+ staleTime: Infinity,
388
+ },
389
+ queryClient,
390
+ ).data;
391
+ if (typeof answer !== "function") {
392
+ throw new Error(
393
+ "The app's /_layout answers no component: it's the root layout, a client component drawing the navigator (a Stack).",
394
+ );
251
395
  }
252
- }
396
+ const Answer = answer as ComponentType;
397
+ return <Answer />;
398
+ };
253
399
 
400
+ // The runtime's hooks name its client, so an app's own React Query, with
401
+ // or without a provider, never shares the runtime's cache.
254
402
  const Navigator = (props: { href: string }) => (
255
- <$QueryClientProvider client={queryClient}>
256
- <SilentErrorBoundary key={props.href}>
257
- <NavigationHold href={props.href} />
258
- </SilentErrorBoundary>
259
- <$RootLayout />
260
- </$QueryClientProvider>
403
+ <>
404
+ <NavigationHold href={props.href} />
405
+ <RootLayout />
406
+ </>
261
407
  );
262
408
 
263
409
  const runtime: Runtime = {
264
410
  Navigator,
265
411
  Screen,
266
412
  prefetch,
267
- // Each of its queries again: one that failed, for its screen to wait
268
- // for it; the others kept until the new answer is in.
413
+ // Its page, layouts and loading states again: one that failed, for
414
+ // its screen to wait for it; the others kept until the new answer is
415
+ // in.
269
416
  reload: async (href) => {
270
- const loading = loadingPathOf(href);
417
+ const paths = [
418
+ href,
419
+ ...segmentsOf(href).flatMap((segment) => [
420
+ ...(segment.layout === undefined ? [] : [segment.layout]),
421
+ ...(segment.loading === undefined ? [] : [segment.loading]),
422
+ ]),
423
+ ];
271
424
  await Promise.allSettled(
272
- (loading === undefined ? [href] : [href, loading]).map((path) =>
425
+ paths.map((path) =>
273
426
  queryClient.getQueryState([path])?.status === "error"
274
427
  ? queryClient.resetQueries({ queryKey: [path], exact: true })
275
428
  : queryClient.refetchQueries({ queryKey: [path], exact: true }),