@stratal/inertia 0.0.23 → 0.0.25

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
@@ -16,7 +16,7 @@ Inertia.js v3 server adapter for [Stratal](https://github.com/strataljs/stratal)
16
16
  ## Features
17
17
 
18
18
  - **InertiaModule** — Drop-in Stratal module with `forRoot()` / `forRootAsync()` configuration
19
- - **Server-Side Rendering** — SSR support with configurable per-route disabling
19
+ - **Streaming SSR** — React 19 `renderToReadableStream` streaming via `createInertiaSsrApp`, with configurable per-route disabling
20
20
  - **Shared Data** — Global shared props with static values or request-scoped resolvers
21
21
  - **@InertiaRoute Decorator** — Convention-based Inertia page routes with auto-applied response schema
22
22
  - **Partial Reloads** — Optional, deferred, and merge props for efficient data loading
@@ -82,6 +82,38 @@ export class NotesController {
82
82
  }
83
83
  ```
84
84
 
85
+ ### Streaming SSR
86
+
87
+ Enable SSR by pointing the module at a bundle that exports a streaming `render`:
88
+
89
+ ```typescript
90
+ InertiaModule.forRoot({
91
+ rootView: 'app',
92
+ ssr: { bundle: () => import('./inertia/ssr') },
93
+ })
94
+ ```
95
+
96
+ `src/inertia/ssr.tsx` (scaffolded by `quarry inertia:install`) uses
97
+ `createInertiaSsrApp`, which wires Inertia's `App`, head collection, and React 19's
98
+ `renderToReadableStream` — the shell flushes early and the body streams progressively:
99
+
100
+ ```tsx
101
+ import { createInertiaSsrApp } from '@stratal/inertia/ssr'
102
+
103
+ export const { render } = createInertiaSsrApp({
104
+ resolve: async (name) => {
105
+ const pages = import.meta.glob('./pages/**/*.tsx')
106
+ const page = await pages[`./pages/${name}.tsx`]?.()
107
+ if (!page) throw new Error(`Page not found: ${name}`)
108
+ return page
109
+ },
110
+ })
111
+ ```
112
+
113
+ There is no client-side fallback — an SSR failure surfaces as an error rather than
114
+ silently degrading. Skip SSR per route with `ctx.withoutSsr()` or globally with
115
+ `ssr.disabled: ['admin/*']`.
116
+
85
117
  ## Documentation
86
118
 
87
119
  Full guides and examples are available at **[stratal.dev](https://stratal.dev)**.
package/dist/index.d.mts CHANGED
@@ -1,13 +1,13 @@
1
1
  /// <reference path="../global.d.ts" />
2
- import { C as SeoMetaTag, E as SeoTwitter, S as SeoLinkTag, T as SeoTagDescriptor, _ as ResolvedInertiaPageProps, a as InertiaMergeProp, b as ViteManifestEntry, c as InertiaOptionalProp, d as InertiaPageRegistry, f as InertiaRenderOptions, g as InertiaTranslationKeys, h as InertiaSsrResult, i as InertiaI18nConfig, l as InertiaPage, m as InertiaSsrBundle, n as InertiaDeferredProp, o as InertiaMergeStrategy, p as InertiaSharedProps, r as InertiaFullPageProps, s as InertiaOnceProp, t as InertiaAlwaysProp, u as InertiaPageComponent, v as SharedDataResolver, w as SeoOpenGraph, x as SeoData, y as ViteManifest } from "./types--_iJ04lT.mjs";
2
+ import { a as SeoTagDescriptor, i as SeoOpenGraph, n as SeoLinkTag, o as SeoTwitter, r as SeoMetaTag, t as SeoData } from "./types-DzE1pdZs.mjs";
3
+ import { _ as ResolvedInertiaPageProps, a as InertiaMergeProp, b as ViteManifestEntry, c as InertiaOptionalProp, d as InertiaPageRegistry, f as InertiaRenderOptions, g as InertiaTranslationKeys, h as InertiaSsrResult, i as InertiaI18nConfig, l as InertiaPage, m as InertiaSsrBundle, n as InertiaDeferredProp, o as InertiaMergeStrategy, p as InertiaSharedProps, r as InertiaFullPageProps, s as InertiaOnceProp, t as InertiaAlwaysProp, u as InertiaPageComponent, v as SharedDataResolver, y as ViteManifest } from "./types-BhgXhWx6.mjs";
3
4
  import { ExceptionHandler } from "stratal/errors";
4
5
  import { AsyncModuleOptions, DynamicModule, OnException, OnInitialize } from "stratal/module";
5
6
  import { Middleware, Next, RouteConfig, RouteConfigurable, Router, RouterContext } from "stratal/router";
6
7
  import { Container } from "stratal/di";
7
8
  import { MessageKeyPrefix } from "stratal/i18n";
8
- import { LoggerService } from "stratal/logger";
9
9
  import { z } from "stratal/validation";
10
- import { InertiaAppSSRResponse, Page } from "@inertiajs/core";
10
+ import { Page } from "@inertiajs/core";
11
11
  import { CookieOptions } from "hono/utils/cookie";
12
12
 
13
13
  //#region src/flash/flash-store.d.ts
@@ -18,12 +18,9 @@ interface FlashStore {
18
18
  }
19
19
  //#endregion
20
20
  //#region src/inertia.options.d.ts
21
- interface SsrBundleModule {
22
- render(page: Page): Promise<InertiaAppSSRResponse>;
23
- }
24
21
  interface InertiaSsrOptions {
25
- bundle: () => Promise<SsrBundleModule | {
26
- default: SsrBundleModule;
22
+ bundle: () => Promise<InertiaSsrBundle | {
23
+ default: InertiaSsrBundle;
27
24
  }>;
28
25
  /**
29
26
  * Route patterns where SSR is disabled (e.g., `"admin/*"`).
@@ -353,11 +350,17 @@ declare class SeoService {
353
350
  //#region src/services/ssr-renderer.service.d.ts
354
351
  declare class SsrRendererService {
355
352
  private readonly options;
356
- private readonly logger;
357
353
  private bundle;
358
354
  private loadPromise;
359
- constructor(options: InertiaModuleOptions, logger: LoggerService);
360
- render(page: Page): Promise<InertiaAppSSRResponse>;
355
+ constructor(options: InertiaModuleOptions);
356
+ /**
357
+ * Render a page to a streaming SSR result.
358
+ *
359
+ * The SSR bundle is imported once per worker (memoized). Bundle-load and render
360
+ * errors propagate — there is no silent client-side fallback. Callers must only
361
+ * invoke this when `options.ssr` is configured.
362
+ */
363
+ render(page: Page): Promise<InertiaSsrResult>;
361
364
  private ensureBundle;
362
365
  private loadBundle;
363
366
  }
@@ -367,18 +370,37 @@ declare class ManifestService {
367
370
  private readonly manifest;
368
371
  private readonly entryClientPath;
369
372
  private readonly isDev;
373
+ private headTags;
374
+ private scriptTags;
370
375
  constructor(options: InertiaModuleOptions);
371
376
  getHeadTags(): string;
372
377
  getScriptTags(): string;
378
+ private buildHeadTags;
379
+ private buildScriptTags;
373
380
  }
374
381
  //#endregion
375
382
  //#region src/services/template.service.d.ts
376
383
  declare class TemplateService {
377
- private readonly options;
378
384
  private readonly manifest;
385
+ private readonly pre;
386
+ private readonly post;
379
387
  constructor(options: InertiaModuleOptions, manifest: ManifestService);
380
- render(page: Page, ssrHead: string[], ssrBody: string): string;
381
- private buildClientOnlyBody;
388
+ /**
389
+ * Compose the streamed HTML response: the document shell (head + opening
390
+ * `#app` wrapper) is flushed first, the React stream is piped verbatim, then
391
+ * the wrapper is closed and the trailing scripts are appended.
392
+ *
393
+ * Reproduces Inertia's `buildSSRBody` markup: a `<script data-page>` JSON tag
394
+ * (parsed before hydration) followed by `<div data-server-rendered id="app">`.
395
+ */
396
+ renderStream(page: Page, head: string[], reactStream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array>;
397
+ /**
398
+ * Buffered, client-only document used when SSR is disabled for the request.
399
+ * Emits an empty `#app` div for the client bundle to hydrate.
400
+ */
401
+ renderClientOnly(page: Page, head: string[]): string;
402
+ private fillPlaceholders;
403
+ private serialize;
382
404
  }
383
405
  //#endregion
384
406
  //#region src/services/inertia.service.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/flash/flash-store.ts","../src/inertia.options.ts","../src/inertia.module.ts","../src/inertia.tokens.ts","../src/seo/build-seo-tags.ts","../src/flash/cookie-flash-store.ts","../src/augment/router-context.ts","../src/services/hreflang.service.ts","../src/services/seo.service.ts","../src/services/ssr-renderer.service.ts","../src/services/manifest.service.ts","../src/services/template.service.ts","../src/services/inertia.service.ts","../src/decorators/inertia.decorators.ts","../src/middleware/handle-precognitive-requests.middleware.ts","../src/middleware/inertia.middleware.ts","../src/augment/router-variables.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/flash/flash-store.ts","../src/inertia.options.ts","../src/inertia.module.ts","../src/inertia.tokens.ts","../src/seo/build-seo-tags.ts","../src/flash/cookie-flash-store.ts","../src/augment/router-context.ts","../src/services/hreflang.service.ts","../src/services/seo.service.ts","../src/services/ssr-renderer.service.ts","../src/services/manifest.service.ts","../src/services/template.service.ts","../src/services/inertia.service.ts","../src/decorators/inertia.decorators.ts","../src/middleware/handle-precognitive-requests.middleware.ts","../src/middleware/inertia.middleware.ts","../src/augment/router-variables.ts"],"mappings":";;;;;;;;;;;;UAEiB,UAAA;EACf,IAAA,CAAK,GAAA,EAAK,aAAA,GAAgB,OAAA,CAAQ,MAAA;EAClC,KAAA,CAAM,GAAA,EAAK,aAAA,EAAe,IAAA,EAAM,MAAA,oBAA0B,OAAA;EAC1D,KAAA,CAAM,GAAA,EAAK,aAAA,GAAgB,OAAA;AAAA;;;UCCZ,iBAAA;EACf,MAAA,QAAc,OAAA,CAAQ,gBAAA;IAAqB,OAAA,EAAS,gBAAA;EAAA;;;;ADLtD;ECUE,QAAA;AAAA;;;;;;;;;;;;;;;;;UAmBe,kBAAA;ED3BT;;;;;;;;;AAC4B;;;;ACCpC;;EAyCE,IAAA,GAAO,gBAAgB;AAAA;AAAA,UAGR,mBAAA;EACf,KAAA,EAAO,UAAU;AAAA;;;;;;;;;AAvCT;AAmBV;;;;AAgByB;AAGzB;;;;AACmB;AAoCnB;;;;;;;;;;;;;;UAAiB,iBAAA;EAKQ;;;;EAAvB,QAAA,GAAW,OAAA,KAAY,GAAA,EAAK,aAAA,KAAkB,OAAA,GAAU,OAAA,CAAQ,OAAA;EAY1D;;;;;AAA8E;AAGtF;;;EALE,aAAA,cAEM,KAAA,sBAA2B,GAAA,EAAK,aAAA,0BAAuC,OAAA;AAAA;AAAA,UAG9D,oBAAA;EACf,QAAA;EACA,OAAA;EACA,GAAA,GAAM,iBAAA;EACN,KAAA,GAAQ,mBAAA;EACR,UAAA,GAAa,MAAA;EAHb;;;;;;;;;;;;;;EAkBA,IAAA,GAAO,kBAAA;;;;AChHT;;;;;;;;;;;;EDgIE,MAAA;ECtHgF;;;;;ED4HhF,GAAA,GAAM,iBAAA;EC3HkB;;;;EDgIxB,eAAA;AAAA;;;cCjIW,aAAA,YAAyB,iBAAA,EAAmB,YAAA,EAAc,WAAA;EAAA,OAC9D,OAAA,CAAQ,OAAA,EAAS,oBAAA,GAAuB,aAAA;EAAA,OASxC,YAAA,CAAa,OAAA,EAAS,kBAAA,CAAmB,oBAAA,IAAwB,aAAA;EAaxE,eAAA,CAAgB,MAAA,EAAQ,MAAA;EAIxB,WAAA,CAAY,OAAA,EAAS,gBAAA;EAgErB,YAAA;EAAA,QAOQ,gBAAA;;AFzHV;;;;;;;;;;;;UE0IU,aAAA;EAAA,QAKA,qBAAA;EAAA,QAIA,iCAAA;EAAA,QAmCA,+BAAA;EAAA,QAWA,YAAA;AAAA;;;cCnMG,cAAA;EAAA;;;;;;;;;;;;;;;cCOA,aAAA;;;;;;AJLb;;iBIcgB,YAAA,CAAa,IAAA,EAAM,OAAA,GAAU,gBAAgB;;iBAsF7C,gBAAA,CAAiB,CAAmB,EAAhB,gBAAgB;;;UCjGnC,uBAAA;EACf,MAAA,WAAiB,YAAA;EACjB,MAAA;EACA,aAAA,GAAgB,aAAa;AAAA;AAAA,cAGlB,gBAAA,YAA4B,UAAA;EAAA,iBACtB,UAAA;EAAA,iBACA,MAAA;EAAA,iBACA,aAAA;cAEL,OAAA,EAAS,uBAAA;EAWf,IAAA,CAAK,GAAA,EAAK,aAAA,GAAgB,OAAA,CAAQ,MAAA;EAWlC,KAAA,CAAM,GAAA,EAAK,aAAA,EAAe,IAAA,EAAM,MAAA,oBAA0B,OAAA;EAKhE,KAAA,CAAM,GAAA,EAAK,aAAA,GAAgB,OAAA;AAAA;;;UC1BZ,mBAAA;EACf,QAAA,GAAW,oBAAoB;EAC/B,OAAA;AAAA;AAAA,UAGe,kBAAA;EACf,SAAA;EACA,GAAG;AAAA;AAAA;EAAA,UAIO,aAAA;IN1Be;IM4BvB,OAAA,WAAkB,oBAAA,EAChB,SAAA,EAAW,CAAA,KACR,IAAA,QAAY,mBAAA,kBACV,KAAA,GAAQ,MAAA,mBAAyB,OAAA,GAAU,oBAAA,IAC5C,MAAA,wBAA8B,wBAAA,CAAyB,CAAA,KACtD,KAAA,GAAQ,wBAAA,CAAyB,CAAA,GAAI,OAAA,GAAU,oBAAA,KAC/C,KAAA,EAAO,wBAAA,CAAyB,CAAA,GAAI,OAAA,GAAU,oBAAA,IAClD,OAAA,CAAQ,QAAA;INlCqB;IMoChC,KAAA,IAAS,QAAA,QAAgB,CAAA,EAAG,KAAA,YAAiB,mBAAA,CAAoB,CAAA;INnCxD;IMqCT,QAAA,IAAY,QAAA,QAAgB,CAAA,GAAI,mBAAA,CAAoB,CAAA;INrCI;IMuCxD,KAAA,IAAS,QAAA,QAAgB,CAAA,EAAG,OAAA,GAAU,mBAAA,GAAsB,gBAAA,CAAiB,CAAA;INtCpD;IMwCzB,IAAA,IAAQ,QAAA,QAAgB,CAAA,EAAG,OAAA,GAAU,kBAAA,GAAqB,eAAA,CAAgB,CAAA;INxC1C;IM0ChC,MAAA,IAAU,QAAA,QAAgB,CAAA,GAAI,iBAAA,CAAkB,CAAA;IN5CxC;IM8CR,KAAA,CAAM,GAAA,UAAa,KAAA;IN9CK;;;;;IMoDxB,KAAA,CAAM,GAAA,UAAa,KAAA;INnDK;;;;;;;IM2DxB,GAAA,CAAI,IAAA,EAAM,OAAA;;IAEV,UAAA;EAAA;AAAA;;;;;;;;;;;;;AN/DJ;;;;;;;;;;cO0Ba,eAAA;EAAA,iBAEiC,SAAA;cAAA,SAAA,EAAW,SAAA;EAGvD,UAAA,CAAW,UAAA,EAAY,GAAA,GAAM,UAAA;EAAA,QAuBrB,cAAA;EAAA,QAeA,qBAAA;EAAA,QAkBA,OAAA;EAAA,QAIA,YAAA;EAAA,QAMA,OAAA;AAAA;;;;;;;;;;;cClFG,UAAA;EAAA,iBAIwC,OAAA;EAAA,iBACQ,QAAA;EAAA,QAJnD,WAAA;cAG2C,OAAA,EAAS,oBAAA,EACD,QAAA,EAAU,eAAA;ERnB3C;EQuB1B,GAAA,CAAI,IAAA,EAAM,OAAA;ERtBsB;;;;;;;;;;;;EQsC1B,OAAA,CAAQ,GAAA,EAAK,aAAA,GAAgB,OAAA,CAAQ,OAAA;ERtCrC;EQ0EN,OAAA,CAAQ,QAAA,EAAU,OAAA;AAAA;;;cCtEP,kBAAA;EAAA,iBAKwC,OAAA;EAAA,QAJ3C,MAAA;EAAA,QACA,WAAA;cAG2C,OAAA,EAAS,oBAAA;;;;;ATX9D;;;ESqBQ,MAAA,CAAO,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,gBAAA;EAAA,QASpB,YAAA;EAAA,QAaA,UAAA;AAAA;;;cC/BH,eAAA;EAAA,iBACM,QAAA;EAAA,iBACA,eAAA;EAAA,iBACA,KAAA;EAAA,QAGT,QAAA;EAAA,QACA,UAAA;cAG0B,OAAA,EAAS,oBAAoB;EAc/D,WAAA;EAIA,aAAA;EAAA,QAIQ,aAAA;EAAA,QAoBA,eAAA;AAAA;;;cCxDG,eAAA;EAAA,iBAQgD,QAAA;EAAA,iBAL1C,GAAA;EAAA,iBACA,IAAA;cAGiB,OAAA,EAAS,oBAAA,EACgB,QAAA,EAAU,eAAA;;;;;AXhBvE;;;;EWoCE,YAAA,CAAa,IAAA,EAAM,IAAA,EAAM,IAAA,YAAgB,WAAA,EAAa,cAAA,CAAe,UAAA,IAAc,cAAA,CAAe,UAAA;EXnCxE;;;;EW0E1B,gBAAA,CAAiB,IAAA,EAAM,IAAA,EAAM,IAAA;EAAA,QAWrB,gBAAA;EAAA,QAOA,SAAA;AAAA;;;cCjEG,cAAA;EAAA,iBAIwC,OAAA;EAAA,iBACQ,QAAA;EAAA,iBACJ,GAAA;EAAA,iBACD,UAAA;EAAA,QAN9C,UAAA;cAG2C,OAAA,EAAS,oBAAA,EACD,QAAA,EAAU,eAAA,EACd,GAAA,EAAK,kBAAA,EACN,UAAA,EAAY,UAAA;EAGlE,KAAA,CAAM,GAAA,UAAa,KAAA;EAInB,GAAA,CAAI,IAAA,EAAM,OAAA;EAIV,QAAA,CAAS,GAAA,WAAc,QAAA;EAOvB,QAAA,IAAY,QAAA,QAAgB,CAAA,GAAI,mBAAA,CAAoB,CAAA;EAIpD,KAAA,IAAS,QAAA,QAAgB,CAAA,EAAG,KAAA,YAAoB,mBAAA,CAAoB,CAAA;EAIpE,KAAA,IAAS,QAAA,QAAgB,CAAA,EAAG,OAAA,GAAU,mBAAA,GAAsB,gBAAA,CAAiB,CAAA;EAS7E,IAAA,IAAQ,QAAA,QAAgB,CAAA,EAAG,OAAA,GAAU,kBAAA,GAAqB,eAAA,CAAgB,CAAA;EAS1E,MAAA,IAAU,QAAA,QAAgB,CAAA,GAAI,iBAAA,CAAkB,CAAA;EAI1C,MAAA,CACJ,GAAA,EAAK,aAAA,EACL,SAAA,UACA,KAAA,GAAO,MAAA,mBACP,aAAA,GAAe,oBAAA,GACd,OAAA,CAAQ,QAAA;EZrFuB;;;;;;;EAAA,QYuLpB,iBAAA;EAAA,QA4CN,eAAA;EAAA,QAOM,YAAA;EZ3OY;;;;EAAA,QYmWlB,WAAA;EAAA,QAIA,UAAA;EAAA,QAIA,cAAA;EAAA,QAIA,cAAA;EAAA,QAIA,WAAA;EAAA,QAIA,UAAA;EAAA,QAIA,YAAA;EAAA,QAIA,eAAA;EAAA,QAgBA,aAAA;AAAA;;;KCxXE,kBAAA,GAAqB,IAAI,CAAC,WAAA;EACpC,YAAA;AAAA;;;;;;;;;;;;AbvBkC;;;;ACCpC;;;;;;iBY4DgB,YAAA,CAAa,MAAA,GAAQ,kBAAA,IAAuB,MAAA,UAAA,WAAA,UAAA,UAAA,EAAA,kBAAA,KAAA,kBAAA;;;;;;;;;AZtDlD;AAmBV;;;;AAgByB;AAGzB;;;;AACmB;AAoCnB;;;;;;iBYQgB,UAAA,CAAW,IAAA,UAAc,MAAA,GAAQ,kBAAA,IAAuB,MAAA,UAAA,WAAA,UAAA,UAAA,EAAA,kBAAA,KAAA,kBAAA;;;;;;;;;;iBAaxD,WAAA,CAAY,IAAA,UAAc,MAAA,GAAQ,kBAAA,IAAuB,MAAA,UAAA,WAAA,UAAA,UAAA,EAAA,kBAAA,KAAA,kBAAA;;;;;;;;;AZJa;iBYiBtE,UAAA,CAAW,IAAA,UAAc,MAAA,GAAQ,kBAAA,IAAuB,MAAA,UAAA,WAAA,UAAA,UAAA,EAAA,kBAAA,KAAA,kBAAA;;;;;;;;;;iBAaxD,YAAA,CAAa,IAAA,UAAc,MAAA,GAAQ,kBAAA,IAAuB,MAAA,UAAA,WAAA,UAAA,UAAA,EAAA,kBAAA,KAAA,kBAAA;;;;;;;;;;iBAa1D,aAAA,CAAc,IAAA,UAAc,MAAA,GAAQ,kBAAA,IAAuB,MAAA,UAAA,WAAA,UAAA,UAAA,EAAA,kBAAA,KAAA,kBAAA;;;cC/I9D,0BAAA,YAAsC,UAAA;EAC3C,MAAA,CAAO,GAAA,EAAK,aAAA,EAAe,IAAA,EAAM,IAAA,GAAO,OAAA;AAAA;;;cCCnC,iBAAA,YAA6B,UAAA;EAAA,iBAEW,OAAA;cAAA,OAAA,EAAS,oBAAA;EAGtD,MAAA,CAAO,GAAA,EAAK,aAAA,EAAe,IAAA,EAAM,IAAA,GAAO,OAAA;AAAA;;;;YCVpC,eAAA;IACR,OAAA;IACA,eAAA;IACA,YAAA;IACA,UAAA;IACA,YAAA,EAAc,MAAA;IACd,eAAA,EAAiB,MAAM;EAAA;AAAA"}
package/dist/index.mjs CHANGED
@@ -5,7 +5,6 @@ import { Module } from "stratal/module";
5
5
  import { Delete, Get, Patch, Post, Put, ROUTER_TOKENS, Route, RouterContext, SchemaValidationError, applyTrailingSlash } from "stratal/router";
6
6
  import { CONTAINER_TOKEN, DI_TOKENS, Request, Singleton, Transient, inject } from "stratal/di";
7
7
  import { I18N_TOKENS } from "stratal/i18n";
8
- import { LOGGER_TOKENS } from "stratal/logger";
9
8
  import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie";
10
9
  import { z } from "stratal/validation";
11
10
  //#region src/augment/router-context.ts
@@ -238,6 +237,7 @@ let InertiaService = class InertiaService {
238
237
  async render(ctx, component, props = {}, renderOptions = {}) {
239
238
  const reqUrl = new URL(ctx.c.req.url);
240
239
  const url = reqUrl.search ? `${reqUrl.pathname}${reqUrl.search}` : reqUrl.pathname;
240
+ const pathname = reqUrl.pathname;
241
241
  const isInertia = ctx.c.get("inertia");
242
242
  const { shared: resolvedShared, sharedKeys } = await this.resolveSharedData(ctx);
243
243
  const resolvedSeo = await this.seoService.resolve(ctx);
@@ -287,13 +287,17 @@ let InertiaService = class InertiaService {
287
287
  "Vary": "X-Inertia"
288
288
  }
289
289
  });
290
- const ssrResult = ctx.c.get("withoutSsr") || this.isSsrDisabled(url) ? {
291
- head: [],
292
- body: ""
293
- } : await this.ssr.render(page);
294
290
  const seoTags = this.seoService.tagsFor(resolvedSeo);
295
- const html = this.template.render(page, [...ssrResult.head, ...seoTags], ssrResult.body);
296
- return new Response(html, {
291
+ if (ctx.c.get("withoutSsr") || this.isSsrDisabled(pathname) || !this.options.ssr) {
292
+ const html = this.template.renderClientOnly(page, seoTags);
293
+ return new Response(html, {
294
+ status,
295
+ headers: { "Content-Type": "text/html; charset=utf-8" }
296
+ });
297
+ }
298
+ const { head, stream } = await this.ssr.render(page);
299
+ const body = this.template.renderStream(page, [...head, ...seoTags], stream);
300
+ return new Response(body, {
297
301
  status,
298
302
  headers: { "Content-Type": "text/html; charset=utf-8" }
299
303
  });
@@ -483,12 +487,20 @@ let ManifestService = class ManifestService {
483
487
  manifest;
484
488
  entryClientPath;
485
489
  isDev = Boolean(import.meta.env.DEV);
490
+ headTags = null;
491
+ scriptTags = null;
486
492
  constructor(options) {
487
493
  this.manifest = globalThis.__STRATAL_INERTIA_MANIFEST__ ?? null;
488
494
  this.entryClientPath = (options.entryClientPath ?? DEFAULT_ENTRY_CLIENT_PATH).replace(/^\/+/, "");
489
495
  if (!this.isDev && !this.manifest) throw new Error("@stratal/inertia: production build is missing the Vite client manifest. This is wired by stratalInertia() in vite.config.ts — confirm it is in your plugin list and that the client environment built successfully before the worker environment.");
490
496
  }
491
497
  getHeadTags() {
498
+ return this.headTags ??= this.buildHeadTags();
499
+ }
500
+ getScriptTags() {
501
+ return this.scriptTags ??= this.buildScriptTags();
502
+ }
503
+ buildHeadTags() {
492
504
  if (this.isDev) return "<link rel=\"stylesheet\" href=\"/__inertia/ssr-css\" data-ssr-css />";
493
505
  const tags = [];
494
506
  const seen = /* @__PURE__ */ new Set();
@@ -499,7 +511,7 @@ let ManifestService = class ManifestService {
499
511
  }
500
512
  return tags.join("\n");
501
513
  }
502
- getScriptTags() {
514
+ buildScriptTags() {
503
515
  if (this.isDev) return [
504
516
  "<script type=\"module\" src=\"/@vite/client\"><\/script>",
505
517
  `<script type="module">
@@ -516,7 +528,7 @@ hot.on("vite:afterUpdate", () => {
516
528
  return tags.join("\n");
517
529
  }
518
530
  };
519
- ManifestService = __decorate([Transient(), __decorateParam(0, inject(INERTIA_TOKENS.Options))], ManifestService);
531
+ ManifestService = __decorate([Singleton(), __decorateParam(0, inject(INERTIA_TOKENS.Options))], ManifestService);
520
532
  //#endregion
521
533
  //#region src/services/seo.service.ts
522
534
  let SeoService = class SeoService {
@@ -585,23 +597,21 @@ function mergeSeo(a, b) {
585
597
  //#region src/services/ssr-renderer.service.ts
586
598
  let SsrRendererService = class SsrRendererService {
587
599
  options;
588
- logger;
589
600
  bundle = null;
590
601
  loadPromise = null;
591
- constructor(options, logger) {
602
+ constructor(options) {
592
603
  this.options = options;
593
- this.logger = logger;
594
604
  }
605
+ /**
606
+ * Render a page to a streaming SSR result.
607
+ *
608
+ * The SSR bundle is imported once per worker (memoized). Bundle-load and render
609
+ * errors propagate — there is no silent client-side fallback. Callers must only
610
+ * invoke this when `options.ssr` is configured.
611
+ */
595
612
  async render(page) {
596
- if (!this.options.ssr) return {
597
- head: [],
598
- body: ""
599
- };
613
+ if (!this.options.ssr) throw new ApplicationError("[stratal:inertia] SSR bundle is not configured.");
600
614
  await this.ensureBundle();
601
- if (!this.bundle) return {
602
- head: [],
603
- body: ""
604
- };
605
615
  return this.bundle.render(page);
606
616
  }
607
617
  async ensureBundle() {
@@ -609,52 +619,84 @@ let SsrRendererService = class SsrRendererService {
609
619
  this.loadPromise ??= this.loadBundle();
610
620
  try {
611
621
  await this.loadPromise;
612
- } catch {}
613
- }
614
- async loadBundle() {
615
- if (!this.options.ssr) return;
616
- try {
617
- const mod = await this.options.ssr.bundle();
618
- const resolved = "default" in mod ? mod.default : mod;
619
- this.bundle = resolved;
620
622
  } catch (error) {
621
- this.logger.warn("[stratal:inertia] Failed to load SSR bundle. Falling back to client-side rendering.", { error });
622
623
  this.loadPromise = null;
624
+ throw error;
623
625
  }
624
626
  }
627
+ async loadBundle() {
628
+ const mod = await this.options.ssr.bundle();
629
+ this.bundle = "default" in mod ? mod.default : mod;
630
+ }
625
631
  };
626
- SsrRendererService = __decorate([
627
- Singleton(),
628
- __decorateParam(0, inject(INERTIA_TOKENS.Options)),
629
- __decorateParam(1, inject(LOGGER_TOKENS.LoggerService))
630
- ], SsrRendererService);
632
+ SsrRendererService = __decorate([Singleton(), __decorateParam(0, inject(INERTIA_TOKENS.Options))], SsrRendererService);
631
633
  //#endregion
632
634
  //#region src/services/template.service.ts
635
+ const APP_ID = "app";
633
636
  let TemplateService = class TemplateService {
634
- options;
635
637
  manifest;
638
+ pre;
639
+ post;
636
640
  constructor(options, manifest) {
637
- this.options = options;
638
641
  this.manifest = manifest;
642
+ const match = /@inertia\b/.exec(options.rootView);
643
+ if (!match) throw new ApplicationError("[stratal:inertia] rootView template is missing the @inertia placeholder.");
644
+ this.pre = options.rootView.slice(0, match.index);
645
+ this.post = options.rootView.slice(match.index + 8);
646
+ }
647
+ /**
648
+ * Compose the streamed HTML response: the document shell (head + opening
649
+ * `#app` wrapper) is flushed first, the React stream is piped verbatim, then
650
+ * the wrapper is closed and the trailing scripts are appended.
651
+ *
652
+ * Reproduces Inertia's `buildSSRBody` markup: a `<script data-page>` JSON tag
653
+ * (parsed before hydration) followed by `<div data-server-rendered id="app">`.
654
+ */
655
+ renderStream(page, head, reactStream) {
656
+ const encoder = new TextEncoder();
657
+ const shellPre = this.fillPlaceholders(this.pre, head) + `<script data-page="${APP_ID}" type="application/json">${this.serialize(page)}<\/script><div data-server-rendered="true" id="${APP_ID}">`;
658
+ const shellPost = `</div>${this.fillPlaceholders(this.post, head)}`;
659
+ let reader;
660
+ return new ReadableStream({
661
+ async start(controller) {
662
+ controller.enqueue(encoder.encode(shellPre));
663
+ reader = reactStream.getReader();
664
+ try {
665
+ for (;;) {
666
+ const { done, value } = await reader.read();
667
+ if (done) break;
668
+ controller.enqueue(value);
669
+ }
670
+ controller.enqueue(encoder.encode(shellPost));
671
+ controller.close();
672
+ } catch (error) {
673
+ controller.error(error);
674
+ } finally {
675
+ reader.releaseLock();
676
+ reader = void 0;
677
+ }
678
+ },
679
+ cancel(reason) {
680
+ return reader?.cancel(reason) ?? reactStream.cancel(reason);
681
+ }
682
+ });
683
+ }
684
+ /**
685
+ * Buffered, client-only document used when SSR is disabled for the request.
686
+ * Emits an empty `#app` div for the client bundle to hydrate.
687
+ */
688
+ renderClientOnly(page, head) {
689
+ return this.fillPlaceholders(this.pre, head) + `<script data-page="${APP_ID}" type="application/json">${this.serialize(page)}<\/script><div id="${APP_ID}"></div>` + this.fillPlaceholders(this.post, head);
639
690
  }
640
- render(page, ssrHead, ssrBody) {
641
- const appHtml = ssrBody || this.buildClientOnlyBody(page);
642
- const headTags = ssrHead.length > 0 ? ssrHead.join("\n") : "";
643
- const viteHead = this.manifest.getHeadTags();
644
- const viteScripts = this.manifest.getScriptTags();
645
- let html = this.options.rootView;
646
- html = html.replace("@inertiaHead", () => headTags);
647
- html = html.replace("@inertia", () => appHtml);
648
- html = html.replace("@viteHead", () => viteHead);
649
- html = html.replace("@viteScripts", () => viteScripts);
650
- return html;
651
- }
652
- buildClientOnlyBody(page) {
653
- return `<script data-page="app" type="application/json">${JSON.stringify(page).replace(/\//g, "\\/")}<\/script><div id="app"></div>`;
691
+ fillPlaceholders(segment, head) {
692
+ return segment.replace("@inertiaHead", () => head.join("\n")).replace("@viteHead", () => this.manifest.getHeadTags()).replace("@viteScripts", () => this.manifest.getScriptTags());
693
+ }
694
+ serialize(page) {
695
+ return JSON.stringify(page).replace(/\//g, "\\/");
654
696
  }
655
697
  };
656
698
  TemplateService = __decorate([
657
- Transient(),
699
+ Singleton(),
658
700
  __decorateParam(0, inject(INERTIA_TOKENS.Options)),
659
701
  __decorateParam(1, inject(INERTIA_TOKENS.ManifestService))
660
702
  ], TemplateService);