@blacklizard/inertia-express 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.d.ts +19 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +50 -1
- package/dist/core/index.js.map +1 -1
- package/dist/express/index.d.ts +12 -2
- package/dist/express/index.d.ts.map +1 -1
- package/dist/express/index.js +14 -2
- package/dist/express/index.js.map +1 -1
- package/package.json +1 -1
package/dist/core/index.d.ts
CHANGED
|
@@ -306,6 +306,24 @@ interface DefaultRootViewInput {
|
|
|
306
306
|
* — see {@link DefaultRootViewInput}.
|
|
307
307
|
*/
|
|
308
308
|
declare function renderDefaultHtml(input: DefaultRootViewInput): string;
|
|
309
|
+
/**
|
|
310
|
+
* Inputs to {@link renderErrorPage}.
|
|
311
|
+
*/
|
|
312
|
+
interface ErrorPageInput {
|
|
313
|
+
/** HTTP status code to surface to the user. */
|
|
314
|
+
status: number;
|
|
315
|
+
/** Optional message; falls back to a default for the status, then a generic line. */
|
|
316
|
+
message?: string;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Render a minimal, SPA-less standalone HTML error page. Used as the hard
|
|
320
|
+
* fallback when no client `Error` component can be resolved (e.g. a plain
|
|
321
|
+
* browser load, or an SSR/render failure on the Inertia path), so a user
|
|
322
|
+
* never receives a raw JSON error or a blank page.
|
|
323
|
+
*
|
|
324
|
+
* @param input HTTP status plus an optional override message — see {@link ErrorPageInput}.
|
|
325
|
+
*/
|
|
326
|
+
declare function renderErrorPage(input: ErrorPageInput): string;
|
|
309
327
|
//#endregion
|
|
310
328
|
//#region src/core/page.d.ts
|
|
311
329
|
/**
|
|
@@ -516,5 +534,5 @@ declare function resolveVersion<TReq>(resolver: VersionResolver<TReq> | undefine
|
|
|
516
534
|
*/
|
|
517
535
|
declare function isVersionMismatch(serverVersion: string | null, clientVersion: string | null): boolean;
|
|
518
536
|
//#endregion
|
|
519
|
-
export { type AlwaysProp, BuildPageInput, ComputeCacheKeyInput, CreatePageInput, type DeepMergeProp, DefaultRootViewInput, type DeferredProp, INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, type InertiaPage, type InertiaRequestInfo, type LazyProp, type MergeProp, type OptionalProp, type PageProps, type PropValue, type RenderOptions, ResolvePropsInput, ResolvePropsResult, type SharedPropsInput, SsrCacheEntry, SsrCacheStore, ValidationErrors, type VersionResolver, always, buildPage, canonicalStringify, computeSsrCacheKey, createPage, deepMerge, defer, encodePageScript, inertiaRedirectStatus, isVersionMismatch, lazy, merge, optional, parseInertiaRequest, renderDefaultHtml, resolveProps, resolveVersion, scopeErrors };
|
|
537
|
+
export { type AlwaysProp, BuildPageInput, ComputeCacheKeyInput, CreatePageInput, type DeepMergeProp, DefaultRootViewInput, type DeferredProp, ErrorPageInput, INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, type InertiaPage, type InertiaRequestInfo, type LazyProp, type MergeProp, type OptionalProp, type PageProps, type PropValue, type RenderOptions, ResolvePropsInput, ResolvePropsResult, type SharedPropsInput, SsrCacheEntry, SsrCacheStore, ValidationErrors, type VersionResolver, always, buildPage, canonicalStringify, computeSsrCacheKey, createPage, deepMerge, defer, encodePageScript, inertiaRedirectStatus, isVersionMismatch, lazy, merge, optional, parseInertiaRequest, renderDefaultHtml, renderErrorPage, resolveProps, resolveVersion, scopeErrors };
|
|
520
538
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/core/types.ts","../../src/core/cache.ts","../../src/core/errors.ts","../../src/core/headers.ts","../../src/core/html.ts","../../src/core/page.ts","../../src/core/props.ts","../../src/core/redirects.ts","../../src/core/render.ts","../../src/core/versioning.ts"],"mappings":";;AASA;;;;AAA8B;AAM9B;;AAN8B,KAAlB,SAAA,GAAY,MAAM;;;;;UAMb,WAAA,gBAA2B,SAAA,GAAY,SAAA;EACtD,SAAA;EACA,KAAA,EAAO,MAAA;EACP,GAAA;EACA,OAAA;EAHA;;;EAOA,YAAA;EAJA;;;EAQA,cAAA;EAKgB;;;;EAAhB,aAAA,GAAgB,MAAA;EAcJ;AAMd;;EAhBE,UAAA;EAgByC;;;;EAXzC,cAAA;EAWmB;;;;EANnB,YAAA;AAAA;AAOc;AAQhB;;AARgB,KADJ,QAAA,uBAA+B,CAAA,GAAI,OAAA,CAAQ,CAAA;EACrD,cAAA;AAAA;;;;;;KAQU,YAAA,uBAAmC,CAAA,GAAI,OAAA,CAAQ,CAAA;EACzD,kBAAA;AAAA;;AAAkB;AAQpB;;;KAAY,YAAA,uBAAmC,CAAA,GAAI,OAAA,CAAQ,CAAA;EACzD,kBAAA;EACA,KAAA;AAAA;;;;;;KAQU,UAAA,uBAAiC,CAAA,GAAI,OAAA,CAAQ,CAAA;EACvD,gBAAA;AAAA;AATK;AAQP;;;AARO,KAgBK,SAAA,gBAAyB,CAAC;EACpC,eAAA;EACA,kBAAA;AAAA;;;;;KAOU,aAAA,gBAA6B,CAAC;EACxC,oBAAA;EACA,kBAAA;AAAA;AAXF;;;;;;AAAA,KAoBY,SAAA;;;AAlBQ;AAOpB;KAiBY,gBAAA,mBAAmC,SAAA,KAAc,GAAA,EAAK,IAAA,KAAS,SAAA,GAAY,OAAA,CAAQ,SAAA;;;;UAK9E,aAAA;EArBf;;;EAyBA,YAAA;EAfU;;;EAmBV,cAAc;AAAA;AAbhB;;;;AAAA,UAoBiB,kBAAA;EACf,SAAA;EACA,OAAA;EACA,gBAAA;EACA,WAAA;EACA,aAAA;EACA,QAAA;EACA,SAAA;EACA,MAAA;EACA,GAAA;AAAA;;;;AA7BsG;KAoC5F,eAAA,0DAEe,OAAA,qBACrB,GAAA,EAAK,IAAA,qBAAyB,OAAA;;;AAhJpC;;;;AAA8B;AAM9B;;;;;AANA,UCKiB,aAAA;EACf,GAAA,GAAM,GAAA,aAAgB,OAAA,CAAQ,aAAA;EAC9B,GAAA,GAAM,GAAA,UAAa,KAAA,EAAO,aAAA,EAAe,UAAA,aAAuB,OAAA;EAChE,MAAA,GAAS,GAAA,aAAgB,OAAA;AAAA;;;;;UAOV,aAAA;EACf,IAAA;EACA,IAAA;EACA,cAAA;EDAA;ECEA,QAAA;EDGgB;;;;ECEhB,IAAA,EAAM,WAAW;AAAA;ADkBnB;;;;;;;AAAA,iBC0BgB,kBAAA,CAAmB,KAAc;;;;UAOhC,oBAAA;EDhCf;ECkCA,MAAA;EDlCc;ECoCd,OAAA;ED5BsB;EC8BtB,SAAA;ED9B6C;ECgC7C,KAAA,EAAO,SAAS;EDhCiC;ECkCjD,aAAA;AAAA;;;;;;;ADjCkB;AAQpB;;;iBCsCgB,kBAAA,CAAmB,KAA2B,EAApB,oBAAoB;;;ADnG9D;;;;AAAA,KEHY,gBAAA,GAAmB,MAAM;AFSrC;;;;;;;;AAAA,iBECgB,WAAA,CAAY,MAAA,EAAQ,gBAAA,EAAkB,GAAA,kBAAqB,SAAS;;;AFPpF;;;;AAA8B;AAM9B;;AANA,cGAa,eAAA;EAAA;;;;;;;;;KAWR,YAAA,GAAe,MAAM;;;;;;;iBAiDV,mBAAA,CAAoB,KAAA;EAAS,OAAA,EAAS,YAAA;EAAc,MAAA;EAAgB,GAAA;AAAA,IAAgB,kBAAkB;;;AH5DtH;;;;AAA8B;AAM9B;;;;;;AANA,iBIqBgB,gBAAA,CAAiB,IAAiB,EAAX,WAAW;;;;UAOjC,oBAAA;EACf,IAAA,EAAM,WAAW;EJtBjB;EIwBA,KAAA;EJvBO;EIyBP,IAAA;EJvBA;EIyBA,MAAA;EJjBA;EImBA,SAAA;EJdgB;;;;;EIoBhB,OAAA;EJAU;;;;;;;EIQV,OAAA;AAAA;;;;;;AJPc;AAQhB;;;;;;iBIcgB,iBAAA,CAAkB,KAA2B,EAApB,oBAAoB;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/core/types.ts","../../src/core/cache.ts","../../src/core/errors.ts","../../src/core/headers.ts","../../src/core/html.ts","../../src/core/page.ts","../../src/core/props.ts","../../src/core/redirects.ts","../../src/core/render.ts","../../src/core/versioning.ts"],"mappings":";;AASA;;;;AAA8B;AAM9B;;AAN8B,KAAlB,SAAA,GAAY,MAAM;;;;;UAMb,WAAA,gBAA2B,SAAA,GAAY,SAAA;EACtD,SAAA;EACA,KAAA,EAAO,MAAA;EACP,GAAA;EACA,OAAA;EAHA;;;EAOA,YAAA;EAJA;;;EAQA,cAAA;EAKgB;;;;EAAhB,aAAA,GAAgB,MAAA;EAcJ;AAMd;;EAhBE,UAAA;EAgByC;;;;EAXzC,cAAA;EAWmB;;;;EANnB,YAAA;AAAA;AAOc;AAQhB;;AARgB,KADJ,QAAA,uBAA+B,CAAA,GAAI,OAAA,CAAQ,CAAA;EACrD,cAAA;AAAA;;;;;;KAQU,YAAA,uBAAmC,CAAA,GAAI,OAAA,CAAQ,CAAA;EACzD,kBAAA;AAAA;;AAAkB;AAQpB;;;KAAY,YAAA,uBAAmC,CAAA,GAAI,OAAA,CAAQ,CAAA;EACzD,kBAAA;EACA,KAAA;AAAA;;;;;;KAQU,UAAA,uBAAiC,CAAA,GAAI,OAAA,CAAQ,CAAA;EACvD,gBAAA;AAAA;AATK;AAQP;;;AARO,KAgBK,SAAA,gBAAyB,CAAC;EACpC,eAAA;EACA,kBAAA;AAAA;;;;;KAOU,aAAA,gBAA6B,CAAC;EACxC,oBAAA;EACA,kBAAA;AAAA;AAXF;;;;;;AAAA,KAoBY,SAAA;;;AAlBQ;AAOpB;KAiBY,gBAAA,mBAAmC,SAAA,KAAc,GAAA,EAAK,IAAA,KAAS,SAAA,GAAY,OAAA,CAAQ,SAAA;;;;UAK9E,aAAA;EArBf;;;EAyBA,YAAA;EAfU;;;EAmBV,cAAc;AAAA;AAbhB;;;;AAAA,UAoBiB,kBAAA;EACf,SAAA;EACA,OAAA;EACA,gBAAA;EACA,WAAA;EACA,aAAA;EACA,QAAA;EACA,SAAA;EACA,MAAA;EACA,GAAA;AAAA;;;;AA7BsG;KAoC5F,eAAA,0DAEe,OAAA,qBACrB,GAAA,EAAK,IAAA,qBAAyB,OAAA;;;AAhJpC;;;;AAA8B;AAM9B;;;;;AANA,UCKiB,aAAA;EACf,GAAA,GAAM,GAAA,aAAgB,OAAA,CAAQ,aAAA;EAC9B,GAAA,GAAM,GAAA,UAAa,KAAA,EAAO,aAAA,EAAe,UAAA,aAAuB,OAAA;EAChE,MAAA,GAAS,GAAA,aAAgB,OAAA;AAAA;;;;;UAOV,aAAA;EACf,IAAA;EACA,IAAA;EACA,cAAA;EDAA;ECEA,QAAA;EDGgB;;;;ECEhB,IAAA,EAAM,WAAW;AAAA;ADkBnB;;;;;;;AAAA,iBC0BgB,kBAAA,CAAmB,KAAc;;;;UAOhC,oBAAA;EDhCf;ECkCA,MAAA;EDlCc;ECoCd,OAAA;ED5BsB;EC8BtB,SAAA;ED9B6C;ECgC7C,KAAA,EAAO,SAAS;EDhCiC;ECkCjD,aAAA;AAAA;;;;;;;ADjCkB;AAQpB;;;iBCsCgB,kBAAA,CAAmB,KAA2B,EAApB,oBAAoB;;;ADnG9D;;;;AAAA,KEHY,gBAAA,GAAmB,MAAM;AFSrC;;;;;;;;AAAA,iBECgB,WAAA,CAAY,MAAA,EAAQ,gBAAA,EAAkB,GAAA,kBAAqB,SAAS;;;AFPpF;;;;AAA8B;AAM9B;;AANA,cGAa,eAAA;EAAA;;;;;;;;;KAWR,YAAA,GAAe,MAAM;;;;;;;iBAiDV,mBAAA,CAAoB,KAAA;EAAS,OAAA,EAAS,YAAA;EAAc,MAAA;EAAgB,GAAA;AAAA,IAAgB,kBAAkB;;;AH5DtH;;;;AAA8B;AAM9B;;;;;;AANA,iBIqBgB,gBAAA,CAAiB,IAAiB,EAAX,WAAW;;;;UAOjC,oBAAA;EACf,IAAA,EAAM,WAAW;EJtBjB;EIwBA,KAAA;EJvBO;EIyBP,IAAA;EJvBA;EIyBA,MAAA;EJjBA;EImBA,SAAA;EJdgB;;;;;EIoBhB,OAAA;EJAU;;;;;;;EIQV,OAAA;AAAA;;;;;;AJPc;AAQhB;;;;;;iBIcgB,iBAAA,CAAkB,KAA2B,EAApB,oBAAoB;;;;UAmD5C,cAAA;EJjE0C;EImEzD,MAAA;EJlEkB;EIoElB,OAAO;AAAA;;;;;;;;;iBAWO,eAAA,CAAgB,KAAqB,EAAd,cAAc;;;AJpIrD;;;;AAAA,UKHiB,eAAA,gBAA+B,SAAA,GAAY,SAAA;EAC1D,SAAA;EACA,KAAA,EAAO,MAAA;EACP,GAAA;EACA,OAAA;EACA,YAAA;EACA,cAAA;EACA,aAAA,GAAgB,MAAA;EAChB,UAAA;EACA,cAAA;EACA,YAAA;AAAA;;;;;;;;;iBAWc,UAAA,gBAA0B,SAAA,CAAA,CAAW,KAAA,EAAO,eAAA,CAAgB,MAAA,IAAU,WAAA,CAAY,MAAA;;;ALlBlG;;;;AAA8B;AAM9B;;;AANA,iBMkCgB,IAAA,GAAA,CAAQ,EAAA,QAAU,CAAA,GAAI,OAAA,CAAQ,CAAA,IAAK,QAAA,CAAS,CAAA;;;;;;;;;iBAe5C,QAAA,GAAA,CAAY,EAAA,QAAU,CAAA,GAAI,OAAA,CAAQ,CAAA,IAAK,YAAA,CAAa,CAAA;;;;;;;;;;;iBAiBpD,KAAA,GAAA,CAAS,EAAA,QAAU,CAAA,GAAI,OAAA,CAAQ,CAAA,GAAI,KAAA,YAAoB,YAAA,CAAa,CAAA;;AN7BtE;AAMd;;;;;;;iBMwCgB,MAAA,GAAA,CAAU,EAAA,QAAU,CAAA,GAAI,OAAA,CAAQ,CAAA,IAAK,UAAA,CAAW,CAAA;;;;;;;ANvChD;AAQhB;;;iBMgDgB,KAAA,kBAAA,CAAwB,KAAA,EAAO,CAAA,EAAG,OAAA,uBAA8B,SAAA,CAAU,CAAA;;;;;;;;;;;iBAkB1E,SAAA,kBAAA,CAA4B,KAAA,EAAO,CAAA,EAAG,OAAA,uBAA8B,aAAA,CAAc,CAAA;ANzDlG;;;AAAA,UMsJiB,iBAAA;ENtJ0C;EMwJzD,KAAA,EAAO,SAAA;ENxJiD;EM0JxD,MAAA,EAAQ,SAAA;EN1Je;EM4JvB,SAAA;EN5JiD;EM8JjD,OAAA,EAAS,kBAAA;AAAA;;;AN5JJ;AAQP;UM2JiB,kBAAA;EACf,KAAA,EAAO,SAAA;EN5JoC;EM8J3C,QAAA,EAAU,MAAM;EN9J+B;EMgK/C,KAAA;ENhKsD;EMkKtD,SAAA;ENlK2C;EMoK3C,OAAA;AAAA;;;ANnKgB;AAOlB;;;;;;;;;AAEoB;AAOpB;;;;;;;iBM0KsB,YAAA,CAAa,KAAA,EAAO,iBAAA,GAAoB,OAAA,CAAQ,kBAAA;;;;ANlQtE;;;cOLa,uBAAA;APKiB;AAM9B;;;;AAN8B,cOEjB,uBAAA;;;;;;;;;iBAUG,qBAAA,CAAsB,MAAA,UAAgB,QAAc;;;APZpE;;;;AAAA,UQEiB,cAAA;EACf,SAAA;EACA,KAAA,EAAO,SAAA;EACP,OAAA,EAAS,kBAAA;EACT,GAAA,EAAK,IAAA;EACL,MAAA,EAAQ,gBAAA,CAAiB,IAAA;EACzB,OAAA;EACA,OAAA,GAAU,aAAA;AAAA;;;;;;;;;;;;iBAmCU,SAAA,MAAA,CAAgB,KAAA,EAAO,cAAA,CAAe,IAAA,IAAQ,OAAA,CAAQ,WAAA;;;AR5C5E;;;;AAA8B;AAM9B;;;;AANA,iBSEsB,cAAA,MAAA,CACpB,QAAA,EAAU,eAAA,CAAgB,IAAA,eAC1B,GAAA,EAAK,IAAA,GACJ,OAAA;;;;;;;;;iBAsBa,iBAAA,CAAkB,aAAA,iBAA8B,aAA4B"}
|
package/dist/core/index.js
CHANGED
|
@@ -195,6 +195,55 @@ function renderDefaultHtml(input) {
|
|
|
195
195
|
</body>
|
|
196
196
|
</html>`;
|
|
197
197
|
}
|
|
198
|
+
/** Default user-facing messages keyed by HTTP status, used when none is given. */
|
|
199
|
+
const ERROR_STATUS_MESSAGES = {
|
|
200
|
+
400: "Bad Request",
|
|
201
|
+
401: "Unauthorized",
|
|
202
|
+
403: "Forbidden",
|
|
203
|
+
404: "Page Not Found",
|
|
204
|
+
409: "Conflict",
|
|
205
|
+
419: "Page Expired",
|
|
206
|
+
422: "Unprocessable Entity",
|
|
207
|
+
429: "Too Many Requests",
|
|
208
|
+
500: "Server Error",
|
|
209
|
+
502: "Bad Gateway",
|
|
210
|
+
503: "Service Unavailable",
|
|
211
|
+
504: "Gateway Timeout"
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Render a minimal, SPA-less standalone HTML error page. Used as the hard
|
|
215
|
+
* fallback when no client `Error` component can be resolved (e.g. a plain
|
|
216
|
+
* browser load, or an SSR/render failure on the Inertia path), so a user
|
|
217
|
+
* never receives a raw JSON error or a blank page.
|
|
218
|
+
*
|
|
219
|
+
* @param input HTTP status plus an optional override message — see {@link ErrorPageInput}.
|
|
220
|
+
*/
|
|
221
|
+
function renderErrorPage(input) {
|
|
222
|
+
const { status } = input;
|
|
223
|
+
const message = input.message ?? ERROR_STATUS_MESSAGES[status] ?? "Something went wrong";
|
|
224
|
+
const safeStatus = escapeHtmlAttribute(String(status));
|
|
225
|
+
const safeMessage = escapeHtmlAttribute(message);
|
|
226
|
+
return `<!DOCTYPE html>
|
|
227
|
+
<html>
|
|
228
|
+
<head>
|
|
229
|
+
<meta charset="utf-8" />
|
|
230
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
231
|
+
<title>${safeStatus} ${safeMessage}</title>
|
|
232
|
+
<style>
|
|
233
|
+
body { font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f8f9fa; color: #212529; }
|
|
234
|
+
main { text-align: center; padding: 2rem; }
|
|
235
|
+
h1 { font-size: 4rem; margin: 0; font-weight: 600; }
|
|
236
|
+
p { font-size: 1.25rem; margin: 0.5rem 0 0; color: #6c757d; }
|
|
237
|
+
</style>
|
|
238
|
+
</head>
|
|
239
|
+
<body>
|
|
240
|
+
<main>
|
|
241
|
+
<h1>${safeStatus}</h1>
|
|
242
|
+
<p>${safeMessage}</p>
|
|
243
|
+
</main>
|
|
244
|
+
</body>
|
|
245
|
+
</html>`;
|
|
246
|
+
}
|
|
198
247
|
//#endregion
|
|
199
248
|
//#region src/core/page.ts
|
|
200
249
|
/**
|
|
@@ -592,6 +641,6 @@ function isVersionMismatch(serverVersion, clientVersion) {
|
|
|
592
641
|
return serverVersion !== (clientVersion ?? "");
|
|
593
642
|
}
|
|
594
643
|
//#endregion
|
|
595
|
-
export { INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, always, buildPage, canonicalStringify, computeSsrCacheKey, createPage, deepMerge, defer, encodePageScript, inertiaRedirectStatus, isVersionMismatch, lazy, merge, optional, parseInertiaRequest, renderDefaultHtml, resolveProps, resolveVersion, scopeErrors };
|
|
644
|
+
export { INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, always, buildPage, canonicalStringify, computeSsrCacheKey, createPage, deepMerge, defer, encodePageScript, inertiaRedirectStatus, isVersionMismatch, lazy, merge, optional, parseInertiaRequest, renderDefaultHtml, renderErrorPage, resolveProps, resolveVersion, scopeErrors };
|
|
596
645
|
|
|
597
646
|
//# sourceMappingURL=index.js.map
|
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/core/cache.ts","../../src/core/errors.ts","../../src/core/headers.ts","../../src/core/html.ts","../../src/core/page.ts","../../src/core/props.ts","../../src/core/redirects.ts","../../src/core/render.ts","../../src/core/versioning.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport type { InertiaPage, PageProps } from './types.js';\n\n/**\n * Pluggable storage interface for the SSR view cache.\n *\n * Implementations live outside the core package — see\n * `createMemoryCacheStore` in `@blacklizard/inertia-express` and\n * `createRedisCacheStore` in `@blacklizard/inertia-cache-redis`.\n *\n * Values are arbitrary JSON — usually `SsrCacheEntry` (`{ head, body, ... }`).\n * The store should not interpret them.\n */\nexport interface SsrCacheStore {\n get: (key: string) => Promise<SsrCacheEntry | null>;\n set: (key: string, value: SsrCacheEntry, ttlSeconds: number) => Promise<void>;\n delete: (key: string) => Promise<void>;\n}\n\n/**\n * What we actually store per cache hit. Includes everything needed to\n * reconstruct the HTML response without re-running the SSR renderer.\n */\nexport interface SsrCacheEntry {\n head: string | string[];\n body: string;\n bodyIsFullRoot?: boolean;\n /** ISO timestamp when this entry was stored. Useful for debugging. */\n storedAt: string;\n /**\n * The page object the entry corresponds to. Lets a custom rootView see\n * the same structure on a cache hit as on a fresh render.\n */\n page: InertiaPage;\n}\n\n/**\n * Recursively normalize a value so equal-by-value inputs serialize identically:\n * object keys are sorted, `undefined` properties are dropped, and non-finite\n * numbers collapse to `null`.\n *\n * @param value Arbitrary JSON-shaped input.\n */\nfunction canonicalize(value: unknown): unknown {\n if (value === null) {\n return null;\n }\n\n if (typeof value !== 'object') {\n if (typeof value === 'number' && !Number.isFinite(value)) {\n return null;\n }\n\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map(canonicalize);\n }\n\n const obj = value as Record<string, unknown>;\n\n return Object.fromEntries(\n Object.keys(obj)\n .sort()\n .filter((key) => obj[key] !== undefined)\n .map((key) => [key, canonicalize(obj[key])]),\n );\n}\n\n/**\n * Stable JSON encoding: keys sorted recursively, undefined dropped, NaN/Inf\n * normalized. Two equal-by-value objects always produce the same string,\n * so they hash to the same cache key.\n *\n * @param value Arbitrary JSON-shaped input.\n */\nexport function canonicalStringify(value: unknown): string {\n return JSON.stringify(canonicalize(value));\n}\n\n/**\n * Inputs to {@link computeSsrCacheKey}.\n */\nexport interface ComputeCacheKeyInput {\n /** Cache namespace, e.g. `\"inertia:ssr\"`. */\n prefix: string;\n /** Asset version. Becomes part of the key so deploys auto-invalidate. */\n version: string | null;\n /** Component name being rendered. */\n component: string;\n /** Final, fully-resolved props (post shared-merge, post-lazy evaluation). */\n props: PageProps;\n /** Optional extra discriminator (locale, theme, A/B bucket, etc.). */\n discriminator?: string;\n}\n\n/**\n * Build the cache key for an SSR cache entry.\n *\n * Shape: `${prefix}:${version}:${component}:${sha256(props[+discriminator])}`\n *\n * Including `version` in the key namespace means a deploy that bumps the\n * asset version atomically retires every old key — no flush, no race.\n *\n * @param input Prefix, version, component, resolved props, optional discriminator.\n */\nexport function computeSsrCacheKey(input: ComputeCacheKeyInput): string {\n const versionPart = input.version ?? '_';\n const propsHash = createHash('sha256')\n .update(canonicalStringify(input.props))\n .update(input.discriminator ?? '')\n .digest('hex')\n .slice(0, 32);\n\n return `${input.prefix}:${versionPart}:${input.component}:${propsHash}`;\n}\n","import type { PageProps } from './types.js';\n\n/**\n * Flat map of `field → first error message`, the shape the Inertia client\n * expects under `props.errors`.\n */\nexport type ValidationErrors = Record<string, string>;\n\n/**\n * Apply error-bag scoping to a flat errors object, matching the official\n * Inertia behavior. When `bag` is set, errors are namespaced under\n * `props.errors[bag]`. When `bag` is null, errors live at `props.errors`.\n *\n * @param errors Flat field-to-message map.\n * @param bag Optional error-bag name (e.g. `\"createUser\"`); null for top-level.\n */\nexport function scopeErrors(errors: ValidationErrors, bag: string | null): PageProps {\n if (bag) {\n return { errors: { [bag]: errors } };\n }\n\n return { errors };\n}\n","import type { InertiaRequestInfo } from './types.js';\n\n/**\n * Canonical Inertia v3 protocol header names.\n *\n * The Inertia client sends these headers on every visit. The server uses\n * them to decide between a full HTML response and a JSON page response,\n * and to scope partial reloads.\n */\nexport const INERTIA_HEADERS = {\n inertia: 'x-inertia',\n version: 'x-inertia-version',\n partialComponent: 'x-inertia-partial-component',\n partialData: 'x-inertia-partial-data',\n partialExcept: 'x-inertia-partial-except',\n errorBag: 'x-inertia-error-bag',\n reset: 'x-inertia-reset',\n location: 'x-inertia-location',\n} as const;\n\ntype HeaderRecord = Record<string, string | string[] | undefined>;\n\n/**\n * Read a header value as a single string, looking up the lowercase variant as\n * a fallback. Returns the first entry of an array value and `null` when the\n * header is absent.\n *\n * @param headers Raw header record from the HTTP layer.\n * @param name Header name to read.\n */\nfunction readHeader(headers: HeaderRecord, name: string): string | null {\n const value = headers[name] ?? headers[name.toLowerCase()];\n\n if (value === undefined || value === null) {\n return null;\n }\n\n if (Array.isArray(value)) {\n return value[0] ?? null;\n }\n\n return value;\n}\n\n/**\n * Split a comma-separated header value into a trimmed, non-empty string list.\n * Returns `null` when the input is null or yields no entries.\n *\n * @param value Raw header value, or null when absent.\n */\nfunction parseList(value: string | null): string[] | null {\n if (value === null) {\n return null;\n }\n\n const parts = value\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part.length > 0);\n\n return parts.length > 0 ? parts : null;\n}\n\n/**\n * Parse a request's headers into a normalized {@link InertiaRequestInfo}.\n *\n * @param input Headers, HTTP method, and request URL. Method is uppercased in\n * the returned object; the URL is passed through unchanged.\n */\nexport function parseInertiaRequest(input: { headers: HeaderRecord; method: string; url: string }): InertiaRequestInfo {\n const { headers } = input;\n const inertia = readHeader(headers, INERTIA_HEADERS.inertia);\n const version = readHeader(headers, INERTIA_HEADERS.version);\n const partialComponent = readHeader(headers, INERTIA_HEADERS.partialComponent);\n const partialOnly = parseList(readHeader(headers, INERTIA_HEADERS.partialData));\n const partialExcept = parseList(readHeader(headers, INERTIA_HEADERS.partialExcept));\n const errorBag = readHeader(headers, INERTIA_HEADERS.errorBag);\n const resetKeys = parseList(readHeader(headers, INERTIA_HEADERS.reset));\n\n return {\n isInertia: inertia === 'true' || inertia === '1',\n version,\n partialComponent,\n partialOnly,\n partialExcept,\n errorBag,\n resetKeys,\n method: input.method.toUpperCase(),\n url: input.url,\n };\n}\n","import type { InertiaPage } from './types.js';\n\nconst HTML_ESCAPES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n/**\n * Escape characters that would break out of a double-quoted HTML attribute.\n *\n * @param value Raw attribute value.\n */\nfunction escapeHtmlAttribute(value: string): string {\n return value.replace(/[&<>\"']/g, (ch) => HTML_ESCAPES[ch] ?? ch);\n}\n\n/**\n * Encode an Inertia page object for embedding inside a\n * `<script type=\"application/json\" data-page=\"...\">` tag — the Inertia v3\n * initial-page transport that the client reads via `getInitialPageFromDOM`.\n *\n * Every `<` is replaced with its JSON unicode escape so the payload cannot\n * terminate the surrounding `</script>` element or open an HTML comment. The\n * escape is transparent — JSON.parse decodes it back to `<`.\n *\n * @param page Resolved Inertia page object.\n */\nexport function encodePageScript(page: InertiaPage): string {\n return JSON.stringify(page).replace(/</g, '\\\\u003C');\n}\n\n/**\n * Inputs to {@link renderDefaultHtml}.\n */\nexport interface DefaultRootViewInput {\n page: InertiaPage;\n /** Optional `<title>` for the document. */\n title?: string;\n /** Optional <head> content (meta tags, links, etc.). */\n head?: string;\n /** ID of the SPA root element. Defaults to `app`. */\n rootId?: string;\n /** Path to the client entry script, e.g. `/assets/app.js`. */\n scriptSrc?: string;\n /**\n * Pre-rendered SSR HTML for the root element's *inner* content. When set,\n * the inner HTML of the root div is replaced with this string so the\n * client hydrates over server-rendered markup.\n */\n ssrBody?: string;\n /**\n * Pre-rendered SSR HTML representing the entire root element. When set,\n * `ssrBody`, `rootId`, and the page `<script>` tag are ignored — the\n * provided string is inserted into `<body>` as-is. Use this when your\n * SSR renderer already returns the full root element + page script (the\n * Inertia v3 SSR servers do).\n */\n ssrFull?: string;\n}\n\n/**\n * Minimal default HTML shell for use when no custom root view is provided.\n * Real apps should plug in a template engine (pug, ejs, react-dom/server,\n * etc.) and embed `encodePageScript(page)` themselves.\n *\n * The initial page object is emitted as a\n * `<script type=\"application/json\" data-page=\"...\">` tag, the Inertia v3\n * transport that the client reads on boot.\n *\n * @param input Page object plus optional title/head/rootId/scriptSrc/SSR markup\n * — see {@link DefaultRootViewInput}.\n */\nexport function renderDefaultHtml(input: DefaultRootViewInput): string {\n const rootId = input.rootId ?? 'app';\n const title = input.title ?? 'Inertia App';\n const head = input.head ?? '';\n const script = input.scriptSrc ? `<script type=\"module\" src=\"${escapeHtmlAttribute(input.scriptSrc)}\"></script>` : '';\n\n let rootMarkup: string;\n\n if (input.ssrFull) {\n rootMarkup = input.ssrFull;\n } else {\n const idAttr = escapeHtmlAttribute(rootId);\n const inner = input.ssrBody ?? '';\n const pageScript = `<script data-page=\"${idAttr}\" type=\"application/json\">${encodePageScript(input.page)}</script>`;\n rootMarkup = `<div id=\"${idAttr}\">${inner}</div>\\n ${pageScript}`;\n }\n\n return `<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${escapeHtmlAttribute(title)}</title>\n ${head}\n </head>\n <body>\n ${rootMarkup}\n ${script}\n </body>\n</html>`;\n}\n","import type { InertiaPage, PageProps } from './types.js';\n\n/**\n * Inputs to {@link createPage} — the fields of an {@link InertiaPage} plus\n * optional history flags and v3 prop-key metadata.\n */\nexport interface CreatePageInput<TProps extends PageProps = PageProps> {\n component: string;\n props: TProps;\n url: string;\n version: string | null;\n clearHistory?: boolean;\n encryptHistory?: boolean;\n deferredProps?: Record<string, string[]>;\n mergeProps?: string[];\n deepMergeProps?: string[];\n matchPropsOn?: string[];\n}\n\n/**\n * Build a normalized Inertia v3 page object. `clearHistory` and `encryptHistory`\n * default to `false`. Empty/absent `deferredProps` / `mergeProps` /\n * `deepMergeProps` / `matchPropsOn` are dropped so the JSON over the wire stays\n * compact.\n *\n * @param input Required page fields plus the optional v3 extras.\n */\nexport function createPage<TProps extends PageProps>(input: CreatePageInput<TProps>): InertiaPage<TProps> {\n const page: InertiaPage<TProps> = {\n component: input.component,\n props: input.props,\n url: input.url,\n version: input.version,\n clearHistory: input.clearHistory ?? false,\n encryptHistory: input.encryptHistory ?? false,\n };\n\n if (input.deferredProps && Object.keys(input.deferredProps).length > 0) {\n page.deferredProps = input.deferredProps;\n }\n\n if (input.mergeProps && input.mergeProps.length > 0) {\n page.mergeProps = input.mergeProps;\n }\n\n if (input.deepMergeProps && input.deepMergeProps.length > 0) {\n page.deepMergeProps = input.deepMergeProps;\n }\n\n if (input.matchPropsOn && input.matchPropsOn.length > 0) {\n page.matchPropsOn = input.matchPropsOn;\n }\n\n return page;\n}\n","import type {\n AlwaysProp,\n DeepMergeProp,\n DeferredProp,\n InertiaRequestInfo,\n LazyProp,\n MergeProp,\n OptionalProp,\n PageProps,\n} from './types.js';\n\nconst LAZY = '__inertia_lazy' as const;\nconst OPTIONAL = '__inertia_optional' as const;\nconst DEFERRED = '__inertia_deferred' as const;\nconst ALWAYS = '__inertia_always' as const;\nconst MERGE = '__inertia_merge' as const;\nconst DEEP_MERGE = '__inertia_deep_merge' as const;\nconst MATCH_ON = '__inertia_match_on' as const;\n\n/**\n * Attach the (non-enumerable) `MATCH_ON` symbol-key to a merge/deep-merge\n * prop value so `resolveProps` can emit the dotted `prop.field` paths.\n *\n * @param value Object being tagged (mutated in place).\n * @param matchOn Single field name or list; no-op when undefined.\n */\nfunction tagMatchOn(value: object, matchOn: string | string[] | undefined): void {\n if (matchOn === undefined) {\n return;\n }\n\n const fields = Array.isArray(matchOn) ? matchOn : [matchOn];\n Object.defineProperty(value, MATCH_ON, { value: fields, enumerable: false });\n}\n\n/**\n * Wrap a value-producing function as a \"lazy\" prop.\n *\n * Lazy props are evaluated on every visit (full or partial) when included.\n * They are NOT skipped on the initial visit — use {@link optional} for that.\n *\n * @param fn Producer called (and awaited) when the prop is included.\n */\nexport function lazy<T>(fn: () => T | Promise<T>): LazyProp<T> {\n const wrapped = (async () => fn()) as LazyProp<T>;\n Object.defineProperty(wrapped, LAZY, { value: true, enumerable: false });\n\n return wrapped;\n}\n\n/**\n * Wrap a value-producing function as an \"optional\" prop.\n *\n * Optional props are omitted from the initial response and only included\n * when explicitly requested via `X-Inertia-Partial-Data`.\n *\n * @param fn Producer called (and awaited) on partial reloads that request the key.\n */\nexport function optional<T>(fn: () => T | Promise<T>): OptionalProp<T> {\n const wrapped = (async () => fn()) as OptionalProp<T>;\n Object.defineProperty(wrapped, OPTIONAL, { value: true, enumerable: false });\n\n return wrapped;\n}\n\n/**\n * Wrap a value-producing function as a \"deferred\" prop.\n *\n * Deferred props are omitted from the initial visit. The client receives\n * the list of deferred prop keys (grouped by `group`) in the page object\n * and triggers an automatic partial reload to fetch them.\n *\n * @param fn Producer called (and awaited) on the deferred follow-up reload.\n * @param group Load-group name; keys in the same group are fetched in one round-trip.\n */\nexport function defer<T>(fn: () => T | Promise<T>, group = 'default'): DeferredProp<T> {\n const wrapped = (async () => fn()) as DeferredProp<T>;\n Object.defineProperty(wrapped, DEFERRED, { value: true, enumerable: false });\n Object.defineProperty(wrapped, 'group', { value: group, enumerable: false });\n\n return wrapped;\n}\n\n/**\n * Wrap a value-producing function as an \"always\" prop.\n *\n * Always props are included on every response — full visits and partial\n * reloads alike — regardless of `X-Inertia-Partial-Data` /\n * `X-Inertia-Partial-Except` filtering.\n *\n * @param fn Producer called (and awaited) on every visit.\n */\nexport function always<T>(fn: () => T | Promise<T>): AlwaysProp<T> {\n const wrapped = (async () => fn()) as AlwaysProp<T>;\n Object.defineProperty(wrapped, ALWAYS, { value: true, enumerable: false });\n\n return wrapped;\n}\n\n/**\n * Mark a value as a \"merge\" prop. The Inertia client will append the new\n * value to the existing prop (typically used for paginated arrays).\n *\n * Pass `matchOn` (a field name or list of field names) to have the client\n * dedupe array items on that field instead of blindly appending.\n *\n * @param value Object or array to tag (mutated in place via non-enumerable properties).\n * @param matchOn Optional field name(s) to dedupe on.\n */\nexport function merge<T extends object>(value: T, matchOn?: string | string[]): MergeProp<T> {\n const tagged = value as MergeProp<T>;\n Object.defineProperty(tagged, MERGE, { value: true, enumerable: false });\n tagMatchOn(tagged, matchOn);\n\n return tagged;\n}\n\n/**\n * Mark a value as a \"deep merge\" prop. The Inertia client recursively merges\n * the new value into the existing prop rather than shallow-appending.\n *\n * Pass `matchOn` (a field name or list of field names) to have the client\n * dedupe array items on that field.\n *\n * @param value Object or array to tag (mutated in place via non-enumerable properties).\n * @param matchOn Optional field name(s) to dedupe on.\n */\nexport function deepMerge<T extends object>(value: T, matchOn?: string | string[]): DeepMergeProp<T> {\n const tagged = value as DeepMergeProp<T>;\n Object.defineProperty(tagged, DEEP_MERGE, { value: true, enumerable: false });\n tagMatchOn(tagged, matchOn);\n\n return tagged;\n}\n\n/**\n * Type guard: `true` when `v` is a {@link lazy} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isLazy(v: unknown): v is LazyProp {\n return typeof v === 'function' && (v as { [LAZY]?: boolean })[LAZY] === true;\n}\n\n/**\n * Type guard: `true` when `v` is an {@link optional} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isOptional(v: unknown): v is OptionalProp {\n return typeof v === 'function' && (v as { [OPTIONAL]?: boolean })[OPTIONAL] === true;\n}\n\n/**\n * Type guard: `true` when `v` is a {@link defer} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isDeferred(v: unknown): v is DeferredProp {\n return typeof v === 'function' && (v as { [DEFERRED]?: boolean })[DEFERRED] === true;\n}\n\n/**\n * Type guard: `true` when `v` is an {@link always} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isAlways(v: unknown): v is AlwaysProp {\n return typeof v === 'function' && (v as { [ALWAYS]?: boolean })[ALWAYS] === true;\n}\n\n/**\n * Type guard: `true` when `v` is a {@link merge} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isMerge(v: unknown): v is MergeProp {\n return v !== null && typeof v === 'object' && (v as { [MERGE]?: boolean })[MERGE] === true;\n}\n\n/**\n * Type guard: `true` when `v` is a {@link deepMerge} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isDeepMerge(v: unknown): v is DeepMergeProp {\n return v !== null && typeof v === 'object' && (v as { [DEEP_MERGE]?: boolean })[DEEP_MERGE] === true;\n}\n\n/**\n * Match a prop key against a partial-reload key list. A pattern matches when\n * it equals the key, or when it is a dotted path into the key (`users.data`\n * matches the `users` prop). Mirrors the official adapter's partial filtering.\n *\n * @param patterns Key patterns from `X-Inertia-Partial-Data` / `-Partial-Except`.\n * @param key Prop key being tested.\n */\nfunction partialKeyMatches(patterns: string[], key: string): boolean {\n return patterns.some((pattern) => pattern === key || pattern.startsWith(`${key}.`));\n}\n\n/**\n * Read the non-enumerable `MATCH_ON` field-list off a tagged merge value,\n * returning an empty array when absent.\n *\n * @param v Possibly-tagged merge / deep-merge prop value.\n */\nfunction readMatchOn(v: unknown): string[] {\n if (v === null || typeof v !== 'object') {\n return [];\n }\n\n const fields = (v as { [MATCH_ON]?: string[] })[MATCH_ON];\n\n return Array.isArray(fields) ? fields : [];\n}\n\n/**\n * Inputs to {@link resolveProps}.\n */\nexport interface ResolvePropsInput {\n /** Props passed to `res.inertia(component, props)`. */\n props: PageProps;\n /** Globally shared props after evaluation. */\n shared: PageProps;\n /** The component name being rendered. */\n component: string;\n /** Parsed Inertia request info. */\n request: InertiaRequestInfo;\n}\n\n/**\n * Result of {@link resolveProps}: the evaluated props plus the per-key\n * metadata the caller needs to populate the page object.\n */\nexport interface ResolvePropsResult {\n props: PageProps;\n /** Deferred prop key map by group, for the initial visit. */\n deferred: Record<string, string[]>;\n /** Shallow-merge prop key list. */\n merge: string[];\n /** Deep-merge prop key list. */\n deepMerge: string[];\n /** Dotted `prop.field` match-on paths for merge/deep-merge props. */\n matchOn: string[];\n}\n\n/**\n * Combine shared + page props, then apply partial-reload filtering and\n * lazy/optional/deferred evaluation per the Inertia v3 protocol.\n *\n * Resolution order matches the official adapters:\n * 1. Merge shared and page props (page wins on collision).\n * 2. Determine if this is a partial reload for the same component.\n * 3. For non-partial visits: drop optional and deferred props; keep everything\n * else (lazy props are evaluated).\n * 4. For partial visits: keep only keys in `partialOnly` (if set), then drop\n * keys in `partialExcept`. Optional props are now eligible.\n * 5. Evaluate every remaining function-shaped prop\n * (lazy/optional/deferred/always) by calling it and awaiting the result.\n * Independent props are evaluated in parallel via `Promise.all`.\n * 6. Collect merge / deep-merge / match-on keys, excluding any key the client\n * listed in `X-Inertia-Reset` — those props are reset (replaced) rather\n * than appended.\n *\n * @param input Page + shared props, the component name, and parsed request info.\n */\nexport async function resolveProps(input: ResolvePropsInput): Promise<ResolvePropsResult> {\n const {\n props, shared, component, request,\n } = input;\n\n const combined: PageProps = { ...shared, ...props };\n\n const isPartial = request.isInertia && request.partialComponent !== null && request.partialComponent === component;\n\n const deferred: Record<string, string[]> = {};\n const mergeKeys: string[] = [];\n const deepMergeKeys: string[] = [];\n const matchOnKeys: string[] = [];\n const filtered: PageProps = {};\n\n const isExcludedFromPartial = (key: string, value: unknown): boolean => {\n if (!isPartial || isAlways(value)) {\n return false;\n }\n\n if (request.partialOnly !== null && !partialKeyMatches(request.partialOnly, key)) {\n return true;\n }\n\n return request.partialExcept !== null && partialKeyMatches(request.partialExcept, key);\n };\n\n Object.entries(combined).forEach(([key, value]) => {\n // Track deferred prop keys for the page object (only on initial visits).\n // On a partial reload that requests the key, it is evaluated like a lazy prop.\n if (isDeferred(value) && !isPartial) {\n (deferred[value.group] ??= []).push(key);\n\n return;\n }\n\n // Optional props never appear on full visits.\n if (isOptional(value) && !isPartial) {\n return;\n }\n\n if (isExcludedFromPartial(key, value)) {\n return;\n }\n\n filtered[key] = value;\n });\n\n const { resetKeys } = request;\n const evaluated: PageProps = {};\n\n // Evaluate every function-shaped prop; independent props resolve in parallel.\n const resolvedEntries = await Promise.all(\n Object.entries(filtered).map(async ([key, value]): Promise<readonly [string, unknown]> => {\n const needsEval = isLazy(value) || isOptional(value) || isDeferred(value) || isAlways(value);\n const resolved: unknown = needsEval ? await (value as () => unknown)() : value;\n\n return [key, resolved] as const;\n }),\n );\n\n resolvedEntries.forEach(([key, resolved]) => {\n evaluated[key] = resolved;\n\n const isReset = resetKeys?.includes(key) ?? false;\n\n if (isReset) {\n return;\n }\n\n const merging = isMerge(resolved);\n const deepMerging = isDeepMerge(resolved);\n\n if (merging) {\n mergeKeys.push(key);\n } else if (deepMerging) {\n deepMergeKeys.push(key);\n }\n\n if (merging || deepMerging) {\n readMatchOn(resolved).forEach((field) => {\n matchOnKeys.push(`${key}.${field}`);\n });\n }\n });\n\n return {\n props: evaluated,\n deferred,\n merge: mergeKeys,\n deepMerge: deepMergeKeys,\n matchOn: matchOnKeys,\n };\n}\n","/**\n * Status code Inertia uses for \"soft\" redirects after PUT/PATCH/DELETE so the\n * follow-up GET is forced to also be a GET on every browser.\n */\nexport const INERTIA_REDIRECT_STATUS = 303;\n\n/**\n * Status code used for the `X-Inertia-Location` external redirect\n * mechanism. The client intercepts the 409 and performs a window.location\n * navigation rather than a fetch.\n */\nexport const INERTIA_LOCATION_STATUS = 409;\n\n/**\n * Decide which status code to use for a redirect produced by an Inertia\n * request. After a non-GET/HEAD form submission the response must be 303\n * so the client's follow-up request becomes a GET.\n *\n * @param method HTTP method of the inbound request (case-insensitive).\n * @param fallback Status code to use for GET/HEAD/POST. Defaults to 302.\n */\nexport function inertiaRedirectStatus(method: string, fallback = 302): number {\n const m = method.toUpperCase();\n\n if (m === 'PUT' || m === 'PATCH' || m === 'DELETE') {\n return INERTIA_REDIRECT_STATUS;\n }\n\n return fallback;\n}\n","import { createPage } from './page.js';\nimport { resolveProps } from './props.js';\n\nimport type {\n InertiaPage, InertiaRequestInfo, PageProps, RenderOptions, SharedPropsInput,\n} from './types.js';\n\n/**\n * Inputs to {@link buildPage}. `TReq` is the framework's request type so\n * `shared` (when a function) receives the correct argument shape.\n */\nexport interface BuildPageInput<TReq = unknown> {\n component: string;\n props: PageProps;\n request: InertiaRequestInfo;\n req: TReq;\n shared: SharedPropsInput<TReq> | undefined;\n version: string | null;\n options?: RenderOptions;\n}\n\n/**\n * Resolve a `SharedPropsInput` to a plain props object. Returns `{}` for\n * undefined/null inputs or when the resolver returns nullish.\n *\n * @param shared Static props object, a (possibly async) function, or undefined.\n * @param req Request passed to the resolver when `shared` is a function.\n */\nasync function evaluateShared<TReq>(shared: SharedPropsInput<TReq> | undefined, req: TReq): Promise<PageProps> {\n if (shared === undefined || shared === null) {\n return {};\n }\n\n if (typeof shared === 'function') {\n const result = await shared(req);\n\n return result ?? {};\n }\n\n return shared;\n}\n\n/**\n * Build the final {@link InertiaPage} object given a controller's component\n * + props, the request info, and configured shared props / version.\n *\n * Returned page is suitable for either:\n * - JSON serialization on `X-Inertia` requests, or\n * - embedding into the root HTML view via a `<script>` page tag.\n *\n * @param input Component, props, parsed request, shared resolver, version, and\n * optional `clearHistory` / `encryptHistory` flags.\n */\nexport async function buildPage<TReq>(input: BuildPageInput<TReq>): Promise<InertiaPage> {\n const sharedProps = await evaluateShared(input.shared, input.req);\n\n const resolved = await resolveProps({\n props: input.props,\n shared: sharedProps,\n component: input.component,\n request: input.request,\n });\n\n return createPage({\n component: input.component,\n props: resolved.props,\n url: input.request.url,\n version: input.version,\n clearHistory: input.options?.clearHistory,\n encryptHistory: input.options?.encryptHistory,\n deferredProps: resolved.deferred,\n mergeProps: resolved.merge,\n deepMergeProps: resolved.deepMerge,\n matchPropsOn: resolved.matchOn,\n });\n}\n","import type { VersionResolver } from './types.js';\n\n/**\n * Resolve a version value (string, async function, or null) against a request.\n *\n * Returns `null` when no version is configured — in which case asset version\n * mismatch detection is disabled.\n *\n * @param resolver Static version string, a (possibly async) function, or undefined.\n * @param req Request passed to the resolver when it is a function.\n */\nexport async function resolveVersion<TReq>(\n resolver: VersionResolver<TReq> | undefined,\n req: TReq,\n): Promise<string | null> {\n if (resolver === undefined || resolver === null) {\n return null;\n }\n\n if (typeof resolver === 'string') {\n return resolver;\n }\n\n const result = await (resolver)(req);\n\n return result ?? null;\n}\n\n/**\n * Returns true if the client's version (`X-Inertia-Version`) does not match\n * the server's current version. Mismatches on GET requests should trigger a\n * full reload via the `X-Inertia-Location` mechanism.\n *\n * @param serverVersion Currently configured server version; `null` disables checks.\n * @param clientVersion Value of the `X-Inertia-Version` header; `null` is treated as empty.\n */\nexport function isVersionMismatch(serverVersion: string | null, clientVersion: string | null): boolean {\n if (serverVersion === null) {\n return false;\n }\n\n // A missing client header is treated as an empty version, matching the\n // official adapters: an empty client version still mismatches a non-empty\n // server version and must trigger a full reload.\n return serverVersion !== (clientVersion ?? '');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/core/cache.ts","../../src/core/errors.ts","../../src/core/headers.ts","../../src/core/html.ts","../../src/core/page.ts","../../src/core/props.ts","../../src/core/redirects.ts","../../src/core/render.ts","../../src/core/versioning.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport type { InertiaPage, PageProps } from './types.js';\n\n/**\n * Pluggable storage interface for the SSR view cache.\n *\n * Implementations live outside the core package — see\n * `createMemoryCacheStore` in `@blacklizard/inertia-express` and\n * `createRedisCacheStore` in `@blacklizard/inertia-cache-redis`.\n *\n * Values are arbitrary JSON — usually `SsrCacheEntry` (`{ head, body, ... }`).\n * The store should not interpret them.\n */\nexport interface SsrCacheStore {\n get: (key: string) => Promise<SsrCacheEntry | null>;\n set: (key: string, value: SsrCacheEntry, ttlSeconds: number) => Promise<void>;\n delete: (key: string) => Promise<void>;\n}\n\n/**\n * What we actually store per cache hit. Includes everything needed to\n * reconstruct the HTML response without re-running the SSR renderer.\n */\nexport interface SsrCacheEntry {\n head: string | string[];\n body: string;\n bodyIsFullRoot?: boolean;\n /** ISO timestamp when this entry was stored. Useful for debugging. */\n storedAt: string;\n /**\n * The page object the entry corresponds to. Lets a custom rootView see\n * the same structure on a cache hit as on a fresh render.\n */\n page: InertiaPage;\n}\n\n/**\n * Recursively normalize a value so equal-by-value inputs serialize identically:\n * object keys are sorted, `undefined` properties are dropped, and non-finite\n * numbers collapse to `null`.\n *\n * @param value Arbitrary JSON-shaped input.\n */\nfunction canonicalize(value: unknown): unknown {\n if (value === null) {\n return null;\n }\n\n if (typeof value !== 'object') {\n if (typeof value === 'number' && !Number.isFinite(value)) {\n return null;\n }\n\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map(canonicalize);\n }\n\n const obj = value as Record<string, unknown>;\n\n return Object.fromEntries(\n Object.keys(obj)\n .sort()\n .filter((key) => obj[key] !== undefined)\n .map((key) => [key, canonicalize(obj[key])]),\n );\n}\n\n/**\n * Stable JSON encoding: keys sorted recursively, undefined dropped, NaN/Inf\n * normalized. Two equal-by-value objects always produce the same string,\n * so they hash to the same cache key.\n *\n * @param value Arbitrary JSON-shaped input.\n */\nexport function canonicalStringify(value: unknown): string {\n return JSON.stringify(canonicalize(value));\n}\n\n/**\n * Inputs to {@link computeSsrCacheKey}.\n */\nexport interface ComputeCacheKeyInput {\n /** Cache namespace, e.g. `\"inertia:ssr\"`. */\n prefix: string;\n /** Asset version. Becomes part of the key so deploys auto-invalidate. */\n version: string | null;\n /** Component name being rendered. */\n component: string;\n /** Final, fully-resolved props (post shared-merge, post-lazy evaluation). */\n props: PageProps;\n /** Optional extra discriminator (locale, theme, A/B bucket, etc.). */\n discriminator?: string;\n}\n\n/**\n * Build the cache key for an SSR cache entry.\n *\n * Shape: `${prefix}:${version}:${component}:${sha256(props[+discriminator])}`\n *\n * Including `version` in the key namespace means a deploy that bumps the\n * asset version atomically retires every old key — no flush, no race.\n *\n * @param input Prefix, version, component, resolved props, optional discriminator.\n */\nexport function computeSsrCacheKey(input: ComputeCacheKeyInput): string {\n const versionPart = input.version ?? '_';\n const propsHash = createHash('sha256')\n .update(canonicalStringify(input.props))\n .update(input.discriminator ?? '')\n .digest('hex')\n .slice(0, 32);\n\n return `${input.prefix}:${versionPart}:${input.component}:${propsHash}`;\n}\n","import type { PageProps } from './types.js';\n\n/**\n * Flat map of `field → first error message`, the shape the Inertia client\n * expects under `props.errors`.\n */\nexport type ValidationErrors = Record<string, string>;\n\n/**\n * Apply error-bag scoping to a flat errors object, matching the official\n * Inertia behavior. When `bag` is set, errors are namespaced under\n * `props.errors[bag]`. When `bag` is null, errors live at `props.errors`.\n *\n * @param errors Flat field-to-message map.\n * @param bag Optional error-bag name (e.g. `\"createUser\"`); null for top-level.\n */\nexport function scopeErrors(errors: ValidationErrors, bag: string | null): PageProps {\n if (bag) {\n return { errors: { [bag]: errors } };\n }\n\n return { errors };\n}\n","import type { InertiaRequestInfo } from './types.js';\n\n/**\n * Canonical Inertia v3 protocol header names.\n *\n * The Inertia client sends these headers on every visit. The server uses\n * them to decide between a full HTML response and a JSON page response,\n * and to scope partial reloads.\n */\nexport const INERTIA_HEADERS = {\n inertia: 'x-inertia',\n version: 'x-inertia-version',\n partialComponent: 'x-inertia-partial-component',\n partialData: 'x-inertia-partial-data',\n partialExcept: 'x-inertia-partial-except',\n errorBag: 'x-inertia-error-bag',\n reset: 'x-inertia-reset',\n location: 'x-inertia-location',\n} as const;\n\ntype HeaderRecord = Record<string, string | string[] | undefined>;\n\n/**\n * Read a header value as a single string, looking up the lowercase variant as\n * a fallback. Returns the first entry of an array value and `null` when the\n * header is absent.\n *\n * @param headers Raw header record from the HTTP layer.\n * @param name Header name to read.\n */\nfunction readHeader(headers: HeaderRecord, name: string): string | null {\n const value = headers[name] ?? headers[name.toLowerCase()];\n\n if (value === undefined || value === null) {\n return null;\n }\n\n if (Array.isArray(value)) {\n return value[0] ?? null;\n }\n\n return value;\n}\n\n/**\n * Split a comma-separated header value into a trimmed, non-empty string list.\n * Returns `null` when the input is null or yields no entries.\n *\n * @param value Raw header value, or null when absent.\n */\nfunction parseList(value: string | null): string[] | null {\n if (value === null) {\n return null;\n }\n\n const parts = value\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part.length > 0);\n\n return parts.length > 0 ? parts : null;\n}\n\n/**\n * Parse a request's headers into a normalized {@link InertiaRequestInfo}.\n *\n * @param input Headers, HTTP method, and request URL. Method is uppercased in\n * the returned object; the URL is passed through unchanged.\n */\nexport function parseInertiaRequest(input: { headers: HeaderRecord; method: string; url: string }): InertiaRequestInfo {\n const { headers } = input;\n const inertia = readHeader(headers, INERTIA_HEADERS.inertia);\n const version = readHeader(headers, INERTIA_HEADERS.version);\n const partialComponent = readHeader(headers, INERTIA_HEADERS.partialComponent);\n const partialOnly = parseList(readHeader(headers, INERTIA_HEADERS.partialData));\n const partialExcept = parseList(readHeader(headers, INERTIA_HEADERS.partialExcept));\n const errorBag = readHeader(headers, INERTIA_HEADERS.errorBag);\n const resetKeys = parseList(readHeader(headers, INERTIA_HEADERS.reset));\n\n return {\n isInertia: inertia === 'true' || inertia === '1',\n version,\n partialComponent,\n partialOnly,\n partialExcept,\n errorBag,\n resetKeys,\n method: input.method.toUpperCase(),\n url: input.url,\n };\n}\n","import type { InertiaPage } from './types.js';\n\nconst HTML_ESCAPES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n/**\n * Escape characters that would break out of a double-quoted HTML attribute.\n *\n * @param value Raw attribute value.\n */\nfunction escapeHtmlAttribute(value: string): string {\n return value.replace(/[&<>\"']/g, (ch) => HTML_ESCAPES[ch] ?? ch);\n}\n\n/**\n * Encode an Inertia page object for embedding inside a\n * `<script type=\"application/json\" data-page=\"...\">` tag — the Inertia v3\n * initial-page transport that the client reads via `getInitialPageFromDOM`.\n *\n * Every `<` is replaced with its JSON unicode escape so the payload cannot\n * terminate the surrounding `</script>` element or open an HTML comment. The\n * escape is transparent — JSON.parse decodes it back to `<`.\n *\n * @param page Resolved Inertia page object.\n */\nexport function encodePageScript(page: InertiaPage): string {\n return JSON.stringify(page).replace(/</g, '\\\\u003C');\n}\n\n/**\n * Inputs to {@link renderDefaultHtml}.\n */\nexport interface DefaultRootViewInput {\n page: InertiaPage;\n /** Optional `<title>` for the document. */\n title?: string;\n /** Optional <head> content (meta tags, links, etc.). */\n head?: string;\n /** ID of the SPA root element. Defaults to `app`. */\n rootId?: string;\n /** Path to the client entry script, e.g. `/assets/app.js`. */\n scriptSrc?: string;\n /**\n * Pre-rendered SSR HTML for the root element's *inner* content. When set,\n * the inner HTML of the root div is replaced with this string so the\n * client hydrates over server-rendered markup.\n */\n ssrBody?: string;\n /**\n * Pre-rendered SSR HTML representing the entire root element. When set,\n * `ssrBody`, `rootId`, and the page `<script>` tag are ignored — the\n * provided string is inserted into `<body>` as-is. Use this when your\n * SSR renderer already returns the full root element + page script (the\n * Inertia v3 SSR servers do).\n */\n ssrFull?: string;\n}\n\n/**\n * Minimal default HTML shell for use when no custom root view is provided.\n * Real apps should plug in a template engine (pug, ejs, react-dom/server,\n * etc.) and embed `encodePageScript(page)` themselves.\n *\n * The initial page object is emitted as a\n * `<script type=\"application/json\" data-page=\"...\">` tag, the Inertia v3\n * transport that the client reads on boot.\n *\n * @param input Page object plus optional title/head/rootId/scriptSrc/SSR markup\n * — see {@link DefaultRootViewInput}.\n */\nexport function renderDefaultHtml(input: DefaultRootViewInput): string {\n const rootId = input.rootId ?? 'app';\n const title = input.title ?? 'Inertia App';\n const head = input.head ?? '';\n const script = input.scriptSrc ? `<script type=\"module\" src=\"${escapeHtmlAttribute(input.scriptSrc)}\"></script>` : '';\n\n let rootMarkup: string;\n\n if (input.ssrFull) {\n rootMarkup = input.ssrFull;\n } else {\n const idAttr = escapeHtmlAttribute(rootId);\n const inner = input.ssrBody ?? '';\n const pageScript = `<script data-page=\"${idAttr}\" type=\"application/json\">${encodePageScript(input.page)}</script>`;\n rootMarkup = `<div id=\"${idAttr}\">${inner}</div>\\n ${pageScript}`;\n }\n\n return `<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${escapeHtmlAttribute(title)}</title>\n ${head}\n </head>\n <body>\n ${rootMarkup}\n ${script}\n </body>\n</html>`;\n}\n\n/** Default user-facing messages keyed by HTTP status, used when none is given. */\nconst ERROR_STATUS_MESSAGES: Record<number, string> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Page Not Found',\n 409: 'Conflict',\n 419: 'Page Expired',\n 422: 'Unprocessable Entity',\n 429: 'Too Many Requests',\n 500: 'Server Error',\n 502: 'Bad Gateway',\n 503: 'Service Unavailable',\n 504: 'Gateway Timeout',\n};\n\n/**\n * Inputs to {@link renderErrorPage}.\n */\nexport interface ErrorPageInput {\n /** HTTP status code to surface to the user. */\n status: number;\n /** Optional message; falls back to a default for the status, then a generic line. */\n message?: string;\n}\n\n/**\n * Render a minimal, SPA-less standalone HTML error page. Used as the hard\n * fallback when no client `Error` component can be resolved (e.g. a plain\n * browser load, or an SSR/render failure on the Inertia path), so a user\n * never receives a raw JSON error or a blank page.\n *\n * @param input HTTP status plus an optional override message — see {@link ErrorPageInput}.\n */\nexport function renderErrorPage(input: ErrorPageInput): string {\n const { status } = input;\n const message = input.message ?? ERROR_STATUS_MESSAGES[status] ?? 'Something went wrong';\n const safeStatus = escapeHtmlAttribute(String(status));\n const safeMessage = escapeHtmlAttribute(message);\n\n return `<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${safeStatus} ${safeMessage}</title>\n <style>\n body { font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f8f9fa; color: #212529; }\n main { text-align: center; padding: 2rem; }\n h1 { font-size: 4rem; margin: 0; font-weight: 600; }\n p { font-size: 1.25rem; margin: 0.5rem 0 0; color: #6c757d; }\n </style>\n </head>\n <body>\n <main>\n <h1>${safeStatus}</h1>\n <p>${safeMessage}</p>\n </main>\n </body>\n</html>`;\n}\n","import type { InertiaPage, PageProps } from './types.js';\n\n/**\n * Inputs to {@link createPage} — the fields of an {@link InertiaPage} plus\n * optional history flags and v3 prop-key metadata.\n */\nexport interface CreatePageInput<TProps extends PageProps = PageProps> {\n component: string;\n props: TProps;\n url: string;\n version: string | null;\n clearHistory?: boolean;\n encryptHistory?: boolean;\n deferredProps?: Record<string, string[]>;\n mergeProps?: string[];\n deepMergeProps?: string[];\n matchPropsOn?: string[];\n}\n\n/**\n * Build a normalized Inertia v3 page object. `clearHistory` and `encryptHistory`\n * default to `false`. Empty/absent `deferredProps` / `mergeProps` /\n * `deepMergeProps` / `matchPropsOn` are dropped so the JSON over the wire stays\n * compact.\n *\n * @param input Required page fields plus the optional v3 extras.\n */\nexport function createPage<TProps extends PageProps>(input: CreatePageInput<TProps>): InertiaPage<TProps> {\n const page: InertiaPage<TProps> = {\n component: input.component,\n props: input.props,\n url: input.url,\n version: input.version,\n clearHistory: input.clearHistory ?? false,\n encryptHistory: input.encryptHistory ?? false,\n };\n\n if (input.deferredProps && Object.keys(input.deferredProps).length > 0) {\n page.deferredProps = input.deferredProps;\n }\n\n if (input.mergeProps && input.mergeProps.length > 0) {\n page.mergeProps = input.mergeProps;\n }\n\n if (input.deepMergeProps && input.deepMergeProps.length > 0) {\n page.deepMergeProps = input.deepMergeProps;\n }\n\n if (input.matchPropsOn && input.matchPropsOn.length > 0) {\n page.matchPropsOn = input.matchPropsOn;\n }\n\n return page;\n}\n","import type {\n AlwaysProp,\n DeepMergeProp,\n DeferredProp,\n InertiaRequestInfo,\n LazyProp,\n MergeProp,\n OptionalProp,\n PageProps,\n} from './types.js';\n\nconst LAZY = '__inertia_lazy' as const;\nconst OPTIONAL = '__inertia_optional' as const;\nconst DEFERRED = '__inertia_deferred' as const;\nconst ALWAYS = '__inertia_always' as const;\nconst MERGE = '__inertia_merge' as const;\nconst DEEP_MERGE = '__inertia_deep_merge' as const;\nconst MATCH_ON = '__inertia_match_on' as const;\n\n/**\n * Attach the (non-enumerable) `MATCH_ON` symbol-key to a merge/deep-merge\n * prop value so `resolveProps` can emit the dotted `prop.field` paths.\n *\n * @param value Object being tagged (mutated in place).\n * @param matchOn Single field name or list; no-op when undefined.\n */\nfunction tagMatchOn(value: object, matchOn: string | string[] | undefined): void {\n if (matchOn === undefined) {\n return;\n }\n\n const fields = Array.isArray(matchOn) ? matchOn : [matchOn];\n Object.defineProperty(value, MATCH_ON, { value: fields, enumerable: false });\n}\n\n/**\n * Wrap a value-producing function as a \"lazy\" prop.\n *\n * Lazy props are evaluated on every visit (full or partial) when included.\n * They are NOT skipped on the initial visit — use {@link optional} for that.\n *\n * @param fn Producer called (and awaited) when the prop is included.\n */\nexport function lazy<T>(fn: () => T | Promise<T>): LazyProp<T> {\n const wrapped = (async () => fn()) as LazyProp<T>;\n Object.defineProperty(wrapped, LAZY, { value: true, enumerable: false });\n\n return wrapped;\n}\n\n/**\n * Wrap a value-producing function as an \"optional\" prop.\n *\n * Optional props are omitted from the initial response and only included\n * when explicitly requested via `X-Inertia-Partial-Data`.\n *\n * @param fn Producer called (and awaited) on partial reloads that request the key.\n */\nexport function optional<T>(fn: () => T | Promise<T>): OptionalProp<T> {\n const wrapped = (async () => fn()) as OptionalProp<T>;\n Object.defineProperty(wrapped, OPTIONAL, { value: true, enumerable: false });\n\n return wrapped;\n}\n\n/**\n * Wrap a value-producing function as a \"deferred\" prop.\n *\n * Deferred props are omitted from the initial visit. The client receives\n * the list of deferred prop keys (grouped by `group`) in the page object\n * and triggers an automatic partial reload to fetch them.\n *\n * @param fn Producer called (and awaited) on the deferred follow-up reload.\n * @param group Load-group name; keys in the same group are fetched in one round-trip.\n */\nexport function defer<T>(fn: () => T | Promise<T>, group = 'default'): DeferredProp<T> {\n const wrapped = (async () => fn()) as DeferredProp<T>;\n Object.defineProperty(wrapped, DEFERRED, { value: true, enumerable: false });\n Object.defineProperty(wrapped, 'group', { value: group, enumerable: false });\n\n return wrapped;\n}\n\n/**\n * Wrap a value-producing function as an \"always\" prop.\n *\n * Always props are included on every response — full visits and partial\n * reloads alike — regardless of `X-Inertia-Partial-Data` /\n * `X-Inertia-Partial-Except` filtering.\n *\n * @param fn Producer called (and awaited) on every visit.\n */\nexport function always<T>(fn: () => T | Promise<T>): AlwaysProp<T> {\n const wrapped = (async () => fn()) as AlwaysProp<T>;\n Object.defineProperty(wrapped, ALWAYS, { value: true, enumerable: false });\n\n return wrapped;\n}\n\n/**\n * Mark a value as a \"merge\" prop. The Inertia client will append the new\n * value to the existing prop (typically used for paginated arrays).\n *\n * Pass `matchOn` (a field name or list of field names) to have the client\n * dedupe array items on that field instead of blindly appending.\n *\n * @param value Object or array to tag (mutated in place via non-enumerable properties).\n * @param matchOn Optional field name(s) to dedupe on.\n */\nexport function merge<T extends object>(value: T, matchOn?: string | string[]): MergeProp<T> {\n const tagged = value as MergeProp<T>;\n Object.defineProperty(tagged, MERGE, { value: true, enumerable: false });\n tagMatchOn(tagged, matchOn);\n\n return tagged;\n}\n\n/**\n * Mark a value as a \"deep merge\" prop. The Inertia client recursively merges\n * the new value into the existing prop rather than shallow-appending.\n *\n * Pass `matchOn` (a field name or list of field names) to have the client\n * dedupe array items on that field.\n *\n * @param value Object or array to tag (mutated in place via non-enumerable properties).\n * @param matchOn Optional field name(s) to dedupe on.\n */\nexport function deepMerge<T extends object>(value: T, matchOn?: string | string[]): DeepMergeProp<T> {\n const tagged = value as DeepMergeProp<T>;\n Object.defineProperty(tagged, DEEP_MERGE, { value: true, enumerable: false });\n tagMatchOn(tagged, matchOn);\n\n return tagged;\n}\n\n/**\n * Type guard: `true` when `v` is a {@link lazy} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isLazy(v: unknown): v is LazyProp {\n return typeof v === 'function' && (v as { [LAZY]?: boolean })[LAZY] === true;\n}\n\n/**\n * Type guard: `true` when `v` is an {@link optional} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isOptional(v: unknown): v is OptionalProp {\n return typeof v === 'function' && (v as { [OPTIONAL]?: boolean })[OPTIONAL] === true;\n}\n\n/**\n * Type guard: `true` when `v` is a {@link defer} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isDeferred(v: unknown): v is DeferredProp {\n return typeof v === 'function' && (v as { [DEFERRED]?: boolean })[DEFERRED] === true;\n}\n\n/**\n * Type guard: `true` when `v` is an {@link always} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isAlways(v: unknown): v is AlwaysProp {\n return typeof v === 'function' && (v as { [ALWAYS]?: boolean })[ALWAYS] === true;\n}\n\n/**\n * Type guard: `true` when `v` is a {@link merge} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isMerge(v: unknown): v is MergeProp {\n return v !== null && typeof v === 'object' && (v as { [MERGE]?: boolean })[MERGE] === true;\n}\n\n/**\n * Type guard: `true` when `v` is a {@link deepMerge} prop wrapper.\n *\n * @param v Value to test.\n */\nfunction isDeepMerge(v: unknown): v is DeepMergeProp {\n return v !== null && typeof v === 'object' && (v as { [DEEP_MERGE]?: boolean })[DEEP_MERGE] === true;\n}\n\n/**\n * Match a prop key against a partial-reload key list. A pattern matches when\n * it equals the key, or when it is a dotted path into the key (`users.data`\n * matches the `users` prop). Mirrors the official adapter's partial filtering.\n *\n * @param patterns Key patterns from `X-Inertia-Partial-Data` / `-Partial-Except`.\n * @param key Prop key being tested.\n */\nfunction partialKeyMatches(patterns: string[], key: string): boolean {\n return patterns.some((pattern) => pattern === key || pattern.startsWith(`${key}.`));\n}\n\n/**\n * Read the non-enumerable `MATCH_ON` field-list off a tagged merge value,\n * returning an empty array when absent.\n *\n * @param v Possibly-tagged merge / deep-merge prop value.\n */\nfunction readMatchOn(v: unknown): string[] {\n if (v === null || typeof v !== 'object') {\n return [];\n }\n\n const fields = (v as { [MATCH_ON]?: string[] })[MATCH_ON];\n\n return Array.isArray(fields) ? fields : [];\n}\n\n/**\n * Inputs to {@link resolveProps}.\n */\nexport interface ResolvePropsInput {\n /** Props passed to `res.inertia(component, props)`. */\n props: PageProps;\n /** Globally shared props after evaluation. */\n shared: PageProps;\n /** The component name being rendered. */\n component: string;\n /** Parsed Inertia request info. */\n request: InertiaRequestInfo;\n}\n\n/**\n * Result of {@link resolveProps}: the evaluated props plus the per-key\n * metadata the caller needs to populate the page object.\n */\nexport interface ResolvePropsResult {\n props: PageProps;\n /** Deferred prop key map by group, for the initial visit. */\n deferred: Record<string, string[]>;\n /** Shallow-merge prop key list. */\n merge: string[];\n /** Deep-merge prop key list. */\n deepMerge: string[];\n /** Dotted `prop.field` match-on paths for merge/deep-merge props. */\n matchOn: string[];\n}\n\n/**\n * Combine shared + page props, then apply partial-reload filtering and\n * lazy/optional/deferred evaluation per the Inertia v3 protocol.\n *\n * Resolution order matches the official adapters:\n * 1. Merge shared and page props (page wins on collision).\n * 2. Determine if this is a partial reload for the same component.\n * 3. For non-partial visits: drop optional and deferred props; keep everything\n * else (lazy props are evaluated).\n * 4. For partial visits: keep only keys in `partialOnly` (if set), then drop\n * keys in `partialExcept`. Optional props are now eligible.\n * 5. Evaluate every remaining function-shaped prop\n * (lazy/optional/deferred/always) by calling it and awaiting the result.\n * Independent props are evaluated in parallel via `Promise.all`.\n * 6. Collect merge / deep-merge / match-on keys, excluding any key the client\n * listed in `X-Inertia-Reset` — those props are reset (replaced) rather\n * than appended.\n *\n * @param input Page + shared props, the component name, and parsed request info.\n */\nexport async function resolveProps(input: ResolvePropsInput): Promise<ResolvePropsResult> {\n const {\n props, shared, component, request,\n } = input;\n\n const combined: PageProps = { ...shared, ...props };\n\n const isPartial = request.isInertia && request.partialComponent !== null && request.partialComponent === component;\n\n const deferred: Record<string, string[]> = {};\n const mergeKeys: string[] = [];\n const deepMergeKeys: string[] = [];\n const matchOnKeys: string[] = [];\n const filtered: PageProps = {};\n\n const isExcludedFromPartial = (key: string, value: unknown): boolean => {\n if (!isPartial || isAlways(value)) {\n return false;\n }\n\n if (request.partialOnly !== null && !partialKeyMatches(request.partialOnly, key)) {\n return true;\n }\n\n return request.partialExcept !== null && partialKeyMatches(request.partialExcept, key);\n };\n\n Object.entries(combined).forEach(([key, value]) => {\n // Track deferred prop keys for the page object (only on initial visits).\n // On a partial reload that requests the key, it is evaluated like a lazy prop.\n if (isDeferred(value) && !isPartial) {\n (deferred[value.group] ??= []).push(key);\n\n return;\n }\n\n // Optional props never appear on full visits.\n if (isOptional(value) && !isPartial) {\n return;\n }\n\n if (isExcludedFromPartial(key, value)) {\n return;\n }\n\n filtered[key] = value;\n });\n\n const { resetKeys } = request;\n const evaluated: PageProps = {};\n\n // Evaluate every function-shaped prop; independent props resolve in parallel.\n const resolvedEntries = await Promise.all(\n Object.entries(filtered).map(async ([key, value]): Promise<readonly [string, unknown]> => {\n const needsEval = isLazy(value) || isOptional(value) || isDeferred(value) || isAlways(value);\n const resolved: unknown = needsEval ? await (value as () => unknown)() : value;\n\n return [key, resolved] as const;\n }),\n );\n\n resolvedEntries.forEach(([key, resolved]) => {\n evaluated[key] = resolved;\n\n const isReset = resetKeys?.includes(key) ?? false;\n\n if (isReset) {\n return;\n }\n\n const merging = isMerge(resolved);\n const deepMerging = isDeepMerge(resolved);\n\n if (merging) {\n mergeKeys.push(key);\n } else if (deepMerging) {\n deepMergeKeys.push(key);\n }\n\n if (merging || deepMerging) {\n readMatchOn(resolved).forEach((field) => {\n matchOnKeys.push(`${key}.${field}`);\n });\n }\n });\n\n return {\n props: evaluated,\n deferred,\n merge: mergeKeys,\n deepMerge: deepMergeKeys,\n matchOn: matchOnKeys,\n };\n}\n","/**\n * Status code Inertia uses for \"soft\" redirects after PUT/PATCH/DELETE so the\n * follow-up GET is forced to also be a GET on every browser.\n */\nexport const INERTIA_REDIRECT_STATUS = 303;\n\n/**\n * Status code used for the `X-Inertia-Location` external redirect\n * mechanism. The client intercepts the 409 and performs a window.location\n * navigation rather than a fetch.\n */\nexport const INERTIA_LOCATION_STATUS = 409;\n\n/**\n * Decide which status code to use for a redirect produced by an Inertia\n * request. After a non-GET/HEAD form submission the response must be 303\n * so the client's follow-up request becomes a GET.\n *\n * @param method HTTP method of the inbound request (case-insensitive).\n * @param fallback Status code to use for GET/HEAD/POST. Defaults to 302.\n */\nexport function inertiaRedirectStatus(method: string, fallback = 302): number {\n const m = method.toUpperCase();\n\n if (m === 'PUT' || m === 'PATCH' || m === 'DELETE') {\n return INERTIA_REDIRECT_STATUS;\n }\n\n return fallback;\n}\n","import { createPage } from './page.js';\nimport { resolveProps } from './props.js';\n\nimport type {\n InertiaPage, InertiaRequestInfo, PageProps, RenderOptions, SharedPropsInput,\n} from './types.js';\n\n/**\n * Inputs to {@link buildPage}. `TReq` is the framework's request type so\n * `shared` (when a function) receives the correct argument shape.\n */\nexport interface BuildPageInput<TReq = unknown> {\n component: string;\n props: PageProps;\n request: InertiaRequestInfo;\n req: TReq;\n shared: SharedPropsInput<TReq> | undefined;\n version: string | null;\n options?: RenderOptions;\n}\n\n/**\n * Resolve a `SharedPropsInput` to a plain props object. Returns `{}` for\n * undefined/null inputs or when the resolver returns nullish.\n *\n * @param shared Static props object, a (possibly async) function, or undefined.\n * @param req Request passed to the resolver when `shared` is a function.\n */\nasync function evaluateShared<TReq>(shared: SharedPropsInput<TReq> | undefined, req: TReq): Promise<PageProps> {\n if (shared === undefined || shared === null) {\n return {};\n }\n\n if (typeof shared === 'function') {\n const result = await shared(req);\n\n return result ?? {};\n }\n\n return shared;\n}\n\n/**\n * Build the final {@link InertiaPage} object given a controller's component\n * + props, the request info, and configured shared props / version.\n *\n * Returned page is suitable for either:\n * - JSON serialization on `X-Inertia` requests, or\n * - embedding into the root HTML view via a `<script>` page tag.\n *\n * @param input Component, props, parsed request, shared resolver, version, and\n * optional `clearHistory` / `encryptHistory` flags.\n */\nexport async function buildPage<TReq>(input: BuildPageInput<TReq>): Promise<InertiaPage> {\n const sharedProps = await evaluateShared(input.shared, input.req);\n\n const resolved = await resolveProps({\n props: input.props,\n shared: sharedProps,\n component: input.component,\n request: input.request,\n });\n\n return createPage({\n component: input.component,\n props: resolved.props,\n url: input.request.url,\n version: input.version,\n clearHistory: input.options?.clearHistory,\n encryptHistory: input.options?.encryptHistory,\n deferredProps: resolved.deferred,\n mergeProps: resolved.merge,\n deepMergeProps: resolved.deepMerge,\n matchPropsOn: resolved.matchOn,\n });\n}\n","import type { VersionResolver } from './types.js';\n\n/**\n * Resolve a version value (string, async function, or null) against a request.\n *\n * Returns `null` when no version is configured — in which case asset version\n * mismatch detection is disabled.\n *\n * @param resolver Static version string, a (possibly async) function, or undefined.\n * @param req Request passed to the resolver when it is a function.\n */\nexport async function resolveVersion<TReq>(\n resolver: VersionResolver<TReq> | undefined,\n req: TReq,\n): Promise<string | null> {\n if (resolver === undefined || resolver === null) {\n return null;\n }\n\n if (typeof resolver === 'string') {\n return resolver;\n }\n\n const result = await (resolver)(req);\n\n return result ?? null;\n}\n\n/**\n * Returns true if the client's version (`X-Inertia-Version`) does not match\n * the server's current version. Mismatches on GET requests should trigger a\n * full reload via the `X-Inertia-Location` mechanism.\n *\n * @param serverVersion Currently configured server version; `null` disables checks.\n * @param clientVersion Value of the `X-Inertia-Version` header; `null` is treated as empty.\n */\nexport function isVersionMismatch(serverVersion: string | null, clientVersion: string | null): boolean {\n if (serverVersion === null) {\n return false;\n }\n\n // A missing client header is treated as an empty version, matching the\n // official adapters: an empty client version still mismatches a non-empty\n // server version and must trigger a full reload.\n return serverVersion !== (clientVersion ?? '');\n}\n"],"mappings":"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"}
|
package/dist/express/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AlwaysProp, ComputeCacheKeyInput, DeepMergeProp, DeferredProp, INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, InertiaPage, InertiaRequestInfo, LazyProp, MergeProp, OptionalProp, PageProps, RenderOptions, SharedPropsInput, SsrCacheEntry, SsrCacheStore, ValidationErrors, VersionResolver, always, canonicalStringify, computeSsrCacheKey, deepMerge, defer, encodePageScript, lazy, merge, optional } from "../core/index.js";
|
|
1
|
+
import { AlwaysProp, ComputeCacheKeyInput, DeepMergeProp, DeferredProp, ErrorPageInput, INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, InertiaPage, InertiaRequestInfo, LazyProp, MergeProp, OptionalProp, PageProps, RenderOptions, SharedPropsInput, SsrCacheEntry, SsrCacheStore, ValidationErrors, VersionResolver, always, canonicalStringify, computeSsrCacheKey, deepMerge, defer, encodePageScript, lazy, merge, optional, renderErrorPage } from "../core/index.js";
|
|
2
2
|
import { NextFunction, Request, Response } from "express";
|
|
3
3
|
|
|
4
4
|
//#region src/express/edge-cache.d.ts
|
|
@@ -208,6 +208,8 @@ interface InertiaResponseOptions extends RenderOptions {
|
|
|
208
208
|
type ResInertiaApi = <TProps extends PageProps>(component: string, props?: TProps, options?: InertiaResponseOptions) => Promise<void>;
|
|
209
209
|
/** Helper to declare typed redirects on the response. */
|
|
210
210
|
type InertiaLocation = (url: string) => void;
|
|
211
|
+
/** Render an error page at the given HTTP status. See {@link Response.inertiaError}. */
|
|
212
|
+
type InertiaErrorRenderer = (status: number, message?: string) => Promise<void>;
|
|
211
213
|
declare module 'express-serve-static-core' {
|
|
212
214
|
interface Request {
|
|
213
215
|
/** Parsed Inertia request info, populated by the middleware. */
|
|
@@ -251,6 +253,13 @@ declare module 'express-serve-static-core' {
|
|
|
251
253
|
* No-ops when no session is wired.
|
|
252
254
|
*/
|
|
253
255
|
inertiaFlash: (data: Record<string, unknown>) => void;
|
|
256
|
+
/**
|
|
257
|
+
* Render an error page at the given HTTP status. Inertia requests render
|
|
258
|
+
* the client `Error` component with `status` as a prop; plain loads and
|
|
259
|
+
* render failures fall back to a standalone HTML page. See
|
|
260
|
+
* {@link InertiaErrorRenderer}.
|
|
261
|
+
*/
|
|
262
|
+
inertiaError: InertiaErrorRenderer;
|
|
254
263
|
}
|
|
255
264
|
}
|
|
256
265
|
//#endregion
|
|
@@ -265,6 +274,7 @@ declare module 'express-serve-static-core' {
|
|
|
265
274
|
* - Adds `res.inertiaLocation(url)` for external redirects.
|
|
266
275
|
* - Adds `res.inertiaErrors(errors, bag?)` to flash validation errors.
|
|
267
276
|
* - Adds `res.inertiaFlash(data)` to flash arbitrary data.
|
|
277
|
+
* - Adds `res.inertiaError(status, message?)` to render an error page.
|
|
268
278
|
* - Auto-promotes redirects after PUT/PATCH/DELETE to 303 for Inertia visits.
|
|
269
279
|
*
|
|
270
280
|
* Mount before your route handlers:
|
|
@@ -534,5 +544,5 @@ declare function viteManifestVersion(options: ViteManifestVersionOptions): () =>
|
|
|
534
544
|
*/
|
|
535
545
|
declare function viteManifestAssets(options: ViteManifestAssetsOptions): () => Promise<ViteManifestAssets>;
|
|
536
546
|
//#endregion
|
|
537
|
-
export { type AlwaysProp, type ComputeCacheKeyInput, type DeepMergeProp, type DeferredProp, type EdgeCacheDecision, type EdgeCachePolicy, INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, type InertiaCacheOptions, type InertiaMiddlewareOptions, type InertiaPage, type InertiaRequestInfo, type InertiaResponseOptions, type InertiaSsrFetcherOptions, type LazyProp, type MemoryCacheStoreOptions, type MergeProp, type OptionalProp, type PageProps, type PrerenderOptions, type PrerenderRouteResult, type PrerenderSummary, type RootViewInput, type RootViewRenderer, type SharedPropsInput, type SsrCacheEntry, type SsrCacheStore, type SsrRenderer, type SsrRendererInput, type SsrResult, type ValidationErrors, type VersionResolver, type ViteManifestAssets, type ViteManifestAssetsOptions, type ViteManifestVersionOptions, always, applyEdgeCache, canonicalStringify, computeSsrCacheKey, createInertiaSsrFetcher, createMemoryCacheStore, deepMerge, inertia as default, inertia, defer, encodePageScript, lazy, merge, optional, prerender, sendInertiaLocation, sendInertiaResponse, sendVersionMismatch, viteManifestAssets, viteManifestVersion };
|
|
547
|
+
export { type AlwaysProp, type ComputeCacheKeyInput, type DeepMergeProp, type DeferredProp, type EdgeCacheDecision, type EdgeCachePolicy, type ErrorPageInput, INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, type InertiaCacheOptions, type InertiaMiddlewareOptions, type InertiaPage, type InertiaRequestInfo, type InertiaResponseOptions, type InertiaSsrFetcherOptions, type LazyProp, type MemoryCacheStoreOptions, type MergeProp, type OptionalProp, type PageProps, type PrerenderOptions, type PrerenderRouteResult, type PrerenderSummary, type RootViewInput, type RootViewRenderer, type SharedPropsInput, type SsrCacheEntry, type SsrCacheStore, type SsrRenderer, type SsrRendererInput, type SsrResult, type ValidationErrors, type VersionResolver, type ViteManifestAssets, type ViteManifestAssetsOptions, type ViteManifestVersionOptions, always, applyEdgeCache, canonicalStringify, computeSsrCacheKey, createInertiaSsrFetcher, createMemoryCacheStore, deepMerge, inertia as default, inertia, defer, encodePageScript, lazy, merge, optional, prerender, renderErrorPage, sendInertiaLocation, sendInertiaResponse, sendVersionMismatch, viteManifestAssets, viteManifestVersion };
|
|
538
548
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/express/edge-cache.ts","../../src/express/types.ts","../../src/express/middleware.ts","../../src/express/memory-store.ts","../../src/express/prerender.ts","../../src/express/response.ts","../../src/express/ssr-fetcher.ts","../../src/express/vite-manifest.ts"],"mappings":";;;;;;AASA;;;UAAiB,iBAAA;EAEf;EAAA,OAAA;EAOA;EALA,MAAA;EAUI;AAAA;AAON;;EAZE,oBAAA;EAY2C;;;;EAP3C,IAAA;AAAA;;;;;KAOU,eAAA,IAAmB,KAAA;EAAS,GAAA,EAAK,OAAA;EAAS,GAAA,EAAK,QAAA;EAAU,IAAA,EAAM,WAAA;AAAA,MAAkB,iBAAA;AAAiB;AAkB9G;;;;;;;;;AAAgF;;;;AC3BhF;;ADS8G,iBAkB9F,cAAA,CAAe,QAAA,EAAU,iBAAA,SAA0B,GAAA,EAAK,QAAQ;;;;AAvChF;;;;;;KCYY,gBAAA,IAAoB,KAAA,EAAO,aAAA,cAA2B,OAAO;;;ADEnE;UCGW,aAAA;EACf,IAAA,EAAM,WAAA;EACN,GAAA,EAAK,OAAA;EACL,GAAA,EAAK,QAAA;AAAA;;;;;;;;;;;;;;UAgBU,SAAA;EACf,IAAA;EACA,IAAA;EACA,cAAA;AAAA;;;;;KAOU,WAAA,IAAe,KAAA,EAAO,gBAAA,KAAqB,OAAA,CAAQ,SAAA;ADPiB;;;AAAA,UCY/D,gBAAA;EACf,IAAA,EAAM,WAAA;EACN,GAAA,EAAK,OAAA;EACL,GAAA,EAAK,QAAA;AAAA;;;;;AA1CkE;AAKzE;;;;;UAkDiB,mBAAA;EA/CK;EAiDpB,KAAA,EAAO,aAAA;EAnDP;EAqDA,UAAA;EApDA;EAsDA,SAAA;EArDA;;;AAAoB;AAgBtB;;EA4CE,IAAA,IAAQ,KAAA;IAAS,GAAA,EAAK,OAAA;IAAgB,IAAA,EAAM,WAAA;EAAA;EAzC5C;;AAAc;AAOhB;;EAwCE,aAAA,IAAiB,GAAA,EAAK,OAAA;EAxCU;EA0ChC,OAAA,IAAW,EAAA,4BAA8B,GAAA;AAAA;;;;UAM1B,wBAAA;EAhDsC;;;AAAiB;AAKxE;;;;EAoDE,OAAA,GAAU,eAAA,CAAgB,OAAA;EAjDrB;;;;;;;EA0DL,WAAA,GAAc,gBAAA,CAAiB,OAAA;EA1D1B;;AAAe;AAatB;EAmDE,QAAA,GAAW,gBAAA;;;;;;;;;;EAWX,GAAA,GAAM,WAAA;EAxDN;;;;;EA+DA,KAAA,GAAQ,mBAAA;EAxDA;;;;;EA+DR,SAAA,GAAY,eAAA;EAvD6B;;AAAY;AAMvD;;;EAyDE,gBAAA;AAAA;;;;;UAOe,sBAAA,SAA+B,aAAa;EAf/C;;;EAmBZ,GAAG;AAAA;;;;;;KAQO,aAAA,mBAAgC,SAAA,EAC1C,SAAA,UACA,KAAA,GAAQ,MAAA,EACR,OAAA,GAAU,sBAAA,KACP,OAAA;;KAGO,eAAA,IAAmB,GAAW;AAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/express/edge-cache.ts","../../src/express/types.ts","../../src/express/middleware.ts","../../src/express/memory-store.ts","../../src/express/prerender.ts","../../src/express/response.ts","../../src/express/ssr-fetcher.ts","../../src/express/vite-manifest.ts"],"mappings":"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"}
|
package/dist/express/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, always, buildPage, canonicalStringify, computeSsrCacheKey, deepMerge, defer, encodePageScript, inertiaRedirectStatus, isVersionMismatch, lazy, merge, optional, parseInertiaRequest, renderDefaultHtml, resolveVersion, scopeErrors } from "../core/index.js";
|
|
1
|
+
import { INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, always, buildPage, canonicalStringify, computeSsrCacheKey, deepMerge, defer, encodePageScript, inertiaRedirectStatus, isVersionMismatch, lazy, merge, optional, parseInertiaRequest, renderDefaultHtml, renderErrorPage, resolveVersion, scopeErrors } from "../core/index.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { format } from "node:util";
|
|
4
4
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
@@ -385,6 +385,7 @@ function createFlash(req) {
|
|
|
385
385
|
* - Adds `res.inertiaLocation(url)` for external redirects.
|
|
386
386
|
* - Adds `res.inertiaErrors(errors, bag?)` to flash validation errors.
|
|
387
387
|
* - Adds `res.inertiaFlash(data)` to flash arbitrary data.
|
|
388
|
+
* - Adds `res.inertiaError(status, message?)` to render an error page.
|
|
388
389
|
* - Auto-promotes redirects after PUT/PATCH/DELETE to 303 for Inertia visits.
|
|
389
390
|
*
|
|
390
391
|
* Mount before your route handlers:
|
|
@@ -428,6 +429,17 @@ function inertia(options = {}) {
|
|
|
428
429
|
const { session } = req;
|
|
429
430
|
if (session) session.flash = data;
|
|
430
431
|
};
|
|
432
|
+
res.inertiaError = async (status, message) => {
|
|
433
|
+
res.status(status);
|
|
434
|
+
if (req.inertia?.isInertia) try {
|
|
435
|
+
await sendInertiaResponse(req, res, ctx, "Error", { status }, {});
|
|
436
|
+
return;
|
|
437
|
+
} catch {}
|
|
438
|
+
if (!res.headersSent) res.status(status).type("html").send(renderErrorPage({
|
|
439
|
+
status,
|
|
440
|
+
message
|
|
441
|
+
}));
|
|
442
|
+
};
|
|
431
443
|
if (info.isInertia) {
|
|
432
444
|
const originalRedirect = res.redirect.bind(res);
|
|
433
445
|
res.redirect = ((arg1, arg2) => {
|
|
@@ -921,6 +933,6 @@ function viteManifestAssets(options) {
|
|
|
921
933
|
*/
|
|
922
934
|
var express_default = inertia;
|
|
923
935
|
//#endregion
|
|
924
|
-
export { INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, always, applyEdgeCache, canonicalStringify, computeSsrCacheKey, createInertiaSsrFetcher, createMemoryCacheStore, deepMerge, express_default as default, defer, encodePageScript, inertia, lazy, merge, optional, prerender, sendInertiaLocation, sendInertiaResponse, sendVersionMismatch, viteManifestAssets, viteManifestVersion };
|
|
936
|
+
export { INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, always, applyEdgeCache, canonicalStringify, computeSsrCacheKey, createInertiaSsrFetcher, createMemoryCacheStore, deepMerge, express_default as default, defer, encodePageScript, inertia, lazy, merge, optional, prerender, renderErrorPage, sendInertiaLocation, sendInertiaResponse, sendVersionMismatch, viteManifestAssets, viteManifestVersion };
|
|
925
937
|
|
|
926
938
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/express/edge-cache.ts","../../src/express/response.ts","../../src/express/middleware.ts","../../src/express/memory-store.ts","../../src/express/prerender.ts","../../src/express/ssr-fetcher.ts","../../src/express/vite-manifest.ts","../../src/express/index.ts"],"sourcesContent":["import type { Request, Response } from 'express';\n\nimport type { InertiaPage } from '../core/index.js';\n\n/**\n * Decision returned by an `edgeCache` policy. Returning `null` (or a\n * negative number) opts out of edge caching — no `Cache-Control` is set\n * beyond what your handler set itself.\n */\nexport interface EdgeCacheDecision {\n /** `s-maxage` value for shared/CDN caches, in seconds. */\n sMaxAge: number;\n /** `max-age` for the browser cache. Default 0 (revalidate immediately). */\n maxAge?: number;\n /**\n * `stale-while-revalidate` window. Lets CDNs serve a slightly stale\n * response while fetching a fresh one in the background.\n */\n staleWhileRevalidate?: number;\n /**\n * Additional `Vary` values. The middleware always emits `Vary: Accept,\n * X-Inertia` — this lets you append more (e.g. `Cookie`, `Accept-Language`).\n */\n vary?: string[];\n}\n\n/**\n * Per-response edge-cache decision: receives the request, response, and\n * resolved page, returns an {@link EdgeCacheDecision} or `null` to opt out.\n */\nexport type EdgeCachePolicy = (input: { req: Request; res: Response; page: InertiaPage }) => EdgeCacheDecision | null;\n\n/**\n * Apply an edge-cache decision to the response. Called automatically by\n * the middleware when an `edgeCache` policy is configured. Exported so\n * you can call it from custom code paths too.\n *\n * No-op when `decision` is `null`, when `sMaxAge <= 0`, or when the response\n * is 3xx+ (4xx/5xx must not be cached at the edge). The middleware also skips\n * Inertia XHR responses before calling this — that filter lives at the call\n * site, not here, since `applyEdgeCache` only sees the decision + status.\n *\n * `Vary` values from the decision are merged into the existing header,\n * de-duplicated by trimmed string.\n *\n * @param decision Policy output (or `null` to opt out).\n * @param res Outgoing Express response (mutated: sets `Cache-Control`, `Vary`).\n */\nexport function applyEdgeCache(decision: EdgeCacheDecision | null, res: Response): void {\n if (decision === null || decision.sMaxAge <= 0) {\n return;\n }\n\n if (res.statusCode >= 300) {\n return;\n }\n\n const parts: string[] = ['public', `s-maxage=${decision.sMaxAge}`, `max-age=${decision.maxAge ?? 0}`];\n\n if (decision.staleWhileRevalidate && decision.staleWhileRevalidate > 0) {\n parts.push(`stale-while-revalidate=${decision.staleWhileRevalidate}`);\n }\n\n res.setHeader('Cache-Control', parts.join(', '));\n\n if (decision.vary && decision.vary.length > 0) {\n const existing = res.getHeader('Vary');\n const all = new Set<string>();\n\n if (typeof existing === 'string') {\n existing.split(',').forEach((v) => all.add(v.trim()));\n }\n\n decision.vary.forEach((v) => {\n const trimmed = v.trim();\n\n if (trimmed) {\n all.add(trimmed);\n }\n });\n\n res.setHeader('Vary', Array.from(all).join(', '));\n }\n}\n","import {\n buildPage,\n computeSsrCacheKey,\n encodePageScript,\n INERTIA_HEADERS,\n INERTIA_LOCATION_STATUS,\n isVersionMismatch,\n parseInertiaRequest,\n renderDefaultHtml,\n resolveVersion,\n} from '../core';\nimport { applyEdgeCache } from './edge-cache.js';\n\nimport type { Request, Response } from 'express';\n\nimport type {\n InertiaPage, InertiaRequestInfo, PageProps, SharedPropsInput, SsrCacheEntry,\n} from '../core';\nimport type {\n InertiaCacheOptions, InertiaMiddlewareOptions, InertiaResponseOptions, SsrResult,\n} from './types.js';\n\ninterface RenderContext {\n options: InertiaMiddlewareOptions;\n}\n\n/**\n * Resolve the shared-props input for a request. When `flashFromSession` is\n * enabled, session-stored validation errors and flash data are merged in\n * automatically (read-once — they are cleared after being read). Explicit\n * `sharedProps` keys win on collision.\n *\n * @param ctx Render context carrying the middleware options.\n * @param res Express response, scanned for `res.locals.inertiaErrors` as a\n * fallback when no session is wired.\n */\nfunction composeSharedProps(ctx: RenderContext, res: Response): SharedPropsInput<Request> {\n const base = ctx.options.sharedProps;\n\n if (!ctx.options.flashFromSession) {\n return base ?? {};\n }\n\n return async (req: Request): Promise<PageProps> => {\n let baseProps: PageProps = {};\n\n if (base) {\n baseProps = typeof base === 'function' ? ((await base(req)) ?? {}) : base;\n }\n\n const { session } = (req as unknown as { session?: Record<string, unknown> });\n const errors = (session?.errors as PageProps | undefined)\n ?? (res.locals.inertiaErrors as PageProps | undefined)\n ?? {};\n const flash = session?.flash ?? null;\n\n if (session) {\n // Flash data is read-once: clear it so it does not leak into later visits.\n session.errors = undefined;\n session.flash = undefined;\n }\n\n return { errors, flash, ...baseProps };\n };\n}\n\n/**\n * Send the Inertia version-mismatch response: 409 with `X-Inertia-Location`\n * pointing back at the same URL so the client performs a hard reload.\n *\n * @param res Outgoing Express response.\n * @param url Location to send the client to (typically the requested URL).\n */\nexport function sendVersionMismatch(res: Response, url: string): void {\n res.status(INERTIA_LOCATION_STATUS).setHeader(INERTIA_HEADERS.location, url).end();\n}\n\n/**\n * Send an external Inertia redirect. For Inertia (XHR) requests this returns\n * 409 + `X-Inertia-Location` so the client performs a window.location nav.\n * For plain browser requests it falls back to a normal 302 redirect.\n *\n * @param req Incoming Express request; `req.inertia` is reused when present.\n * @param res Outgoing Express response.\n * @param target Destination URL.\n */\nexport function sendInertiaLocation(req: Request, res: Response, target: string): void {\n const info = req.inertia\n ?? parseInertiaRequest({\n headers: req.headers,\n method: req.method,\n url: req.originalUrl ?? req.url,\n });\n\n if (info.isInertia) {\n res.status(INERTIA_LOCATION_STATUS).setHeader(INERTIA_HEADERS.location, target).end();\n\n return;\n }\n\n res.redirect(302, target);\n}\n\n/**\n * Merge `Accept` and `X-Inertia` into an existing `Vary` header value,\n * deduplicating case-insensitively so the header never accumulates repeats\n * when the middleware and the response both contribute tokens.\n *\n * @param existing Current header value as Node returns it (string, number,\n * string[], or undefined).\n */\nfunction mergeVary(existing: string | number | string[] | undefined): string {\n const tokens: string[] = [];\n const seen = new Set<string>();\n\n const add = (token: string): void => {\n const trimmed = token.trim();\n const key = trimmed.toLowerCase();\n\n if (trimmed && !seen.has(key)) {\n seen.add(key);\n tokens.push(trimmed);\n }\n };\n\n if (existing !== undefined) {\n String(existing).split(',').forEach((part) => add(part));\n }\n\n add('Accept');\n add('X-Inertia');\n\n return tokens.join(', ');\n}\n\n/**\n * Coerce SSR head output (string, string[], or absent) to a single string\n * suitable for injection into the document `<head>`.\n *\n * @param head Head fragment(s) from the SSR renderer.\n */\nfunction flattenSsrHead(head: string | string[] | undefined): string | undefined {\n if (head === undefined) {\n return undefined;\n }\n\n if (Array.isArray(head)) {\n return head.join('');\n }\n\n return head;\n}\n\n/**\n * Default rule deciding whether a rendered page is safe to cache. Cacheable\n * iff the request is a non-Inertia GET and the page carries no flash\n * messages or validation errors (those are per-visit state).\n *\n * @param input Express request and the resolved page object.\n */\nfunction defaultCacheVary(input: { req: Request; page: InertiaPage }): boolean {\n const { req, page } = input;\n\n if (req.method !== 'GET') {\n return false;\n }\n\n // Inertia XHR (JSON) responses vary per visit — caching them at the view\n // layer would serve stale partial reloads to the wrong client.\n if (req.inertia?.isInertia) {\n return false;\n }\n\n const props = page.props as Record<string, unknown>;\n\n if (props && typeof props === 'object') {\n const flash = props.flash as Record<string, unknown> | undefined;\n\n if (flash && Object.keys(flash).length > 0) {\n return false;\n }\n\n const errors = props.errors as Record<string, unknown> | undefined;\n\n if (errors && Object.keys(errors).length > 0) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Wrap `cache.store.get` so a thrown store error is reported via `onError`\n * and reported as a miss instead of bubbling out to the request handler.\n *\n * @param cache Resolved cache options.\n * @param key Cache key.\n */\nasync function safeCacheGet(cache: InertiaCacheOptions, key: string): Promise<SsrCacheEntry | null> {\n try {\n return await cache.store.get(key);\n } catch (err) {\n cache.onError?.('get', err);\n\n return null;\n }\n}\n\n/**\n * Wrap `cache.store.set` so a thrown store error is reported via `onError`\n * but never propagates — cache writes are fire-and-forget.\n *\n * @param cache Resolved cache options.\n * @param key Cache key.\n * @param entry Value to store.\n * @param ttl Time-to-live in seconds.\n */\nasync function safeCacheSet(cache: InertiaCacheOptions, key: string, entry: SsrCacheEntry, ttl: number): Promise<void> {\n try {\n await cache.store.set(key, entry, ttl);\n } catch (err) {\n cache.onError?.('set', err);\n }\n}\n\n/**\n * Render the final HTML body. When an SSR result is available, expose it via\n * `res.locals.ssr` so a custom `rootView` can read it, then either call\n * `rootView` or fall back to {@link renderDefaultHtml}.\n *\n * @param ctx Render context carrying the middleware options.\n * @param page Resolved page object.\n * @param ssrResult SSR renderer output, or `null` when SSR is skipped.\n * @param req Incoming request.\n * @param res Outgoing response.\n */\nasync function renderRootViewWithSsr(\n ctx: RenderContext,\n page: InertiaPage,\n ssrResult: SsrResult | null,\n req: Request,\n res: Response,\n): Promise<string> {\n if (ssrResult) {\n res.locals.ssr = ssrResult;\n\n if (ctx.options.rootView) {\n return ctx.options.rootView({ page, req, res });\n }\n\n return renderDefaultHtml({\n page,\n head: flattenSsrHead(ssrResult.head),\n ssrBody: ssrResult.bodyIsFullRoot ? undefined : ssrResult.body,\n ssrFull: ssrResult.bodyIsFullRoot ? ssrResult.body : undefined,\n });\n }\n\n if (ctx.options.rootView) {\n return ctx.options.rootView({ page, req, res });\n }\n\n return renderDefaultHtml({ page });\n}\n\n/**\n * Apply the configured `edgeCache` policy unless this is an Inertia XHR\n * response (those vary per visit and must not be cached at the edge).\n *\n * @param ctx Render context carrying the middleware options.\n * @param page Resolved page object.\n * @param req Incoming request.\n * @param res Outgoing response.\n */\nfunction maybeApplyEdgeCache(ctx: RenderContext, page: InertiaPage, req: Request, res: Response): void {\n if (!ctx.options.edgeCache) {\n return;\n }\n\n if (req.inertia?.isInertia) {\n return;\n }\n\n const decision = ctx.options.edgeCache({ req, res, page });\n applyEdgeCache(decision, res);\n}\n\n/**\n * Build and send an Inertia response. Handles version-mismatch detection,\n * partial-reload filtering, JSON vs HTML, SSR, view caching, and edge\n * cache headers. The status code already on `res` (e.g. `res.status(404)`)\n * is preserved on both the JSON and HTML branches.\n *\n * @param req Incoming Express request.\n * @param res Outgoing Express response.\n * @param ctx Render context carrying the middleware options.\n * @param component Inertia component name to render.\n * @param props Page-specific props (merged with shared props).\n * @param options Optional per-response overrides (url, clearHistory, encryptHistory).\n */\nexport async function sendInertiaResponse(\n req: Request,\n res: Response,\n ctx: RenderContext,\n component: string,\n props: PageProps,\n options: InertiaResponseOptions = {},\n): Promise<void> {\n const info: InertiaRequestInfo = req.inertia\n ?? parseInertiaRequest({\n headers: req.headers,\n method: req.method,\n url: options.url ?? req.originalUrl ?? req.url,\n });\n\n const url = options.url ?? info.url;\n const version = await resolveVersion(ctx.options.version, req);\n\n if (info.isInertia && info.method === 'GET' && isVersionMismatch(version, info.version)) {\n sendVersionMismatch(res, url);\n\n return;\n }\n\n const page = await buildPage<Request>({\n component,\n props,\n request: { ...info, url },\n req,\n shared: composeSharedProps(ctx, res),\n version,\n options: {\n clearHistory: options.clearHistory,\n encryptHistory: options.encryptHistory,\n },\n });\n\n res.setHeader('Vary', mergeVary(res.getHeader('Vary')));\n\n if (info.isInertia) {\n res\n .status(res.statusCode)\n .setHeader('Content-Type', 'application/json; charset=utf-8')\n .setHeader(INERTIA_HEADERS.inertia, 'true');\n res.send(JSON.stringify(page));\n\n return;\n }\n\n // HTML branch — SSR + cache.\n const { cache } = ctx.options;\n const cacheVary = cache?.vary ?? defaultCacheVary;\n const cacheable = cache !== undefined && cacheVary({ req, page });\n let cacheKey: string | null = null;\n\n if (cacheable && cache) {\n cacheKey = computeSsrCacheKey({\n prefix: cache.keyPrefix ?? 'inertia:ssr',\n version,\n component,\n props: page.props,\n discriminator: cache.discriminator?.(req),\n });\n const hit = await safeCacheGet(cache, cacheKey);\n\n if (hit) {\n res.setHeader('X-Inertia-Cache', 'HIT');\n const html = await renderRootViewWithSsr(\n ctx,\n hit.page,\n {\n head: hit.head,\n body: hit.body,\n bodyIsFullRoot: hit.bodyIsFullRoot,\n },\n req,\n res,\n );\n maybeApplyEdgeCache(ctx, hit.page, req, res);\n res.status(res.statusCode).setHeader('Content-Type', 'text/html; charset=utf-8').send(html);\n\n return;\n }\n\n res.setHeader('X-Inertia-Cache', 'MISS');\n }\n\n let ssrResult: SsrResult | null = null;\n\n if (ctx.options.ssr) {\n ssrResult = await ctx.options.ssr({ page, req, res });\n }\n\n const html = await renderRootViewWithSsr(ctx, page, ssrResult, req, res);\n\n if (cacheable && cache && cacheKey && ssrResult) {\n const entry: SsrCacheEntry = {\n head: ssrResult.head ?? '',\n body: ssrResult.body,\n bodyIsFullRoot: ssrResult.bodyIsFullRoot,\n storedAt: new Date().toISOString(),\n page,\n };\n // Fire-and-forget — safeCacheSet swallows its own errors.\n safeCacheSet(cache, cacheKey, entry, cache.ttlSeconds ?? 300).catch(() => {});\n }\n\n maybeApplyEdgeCache(ctx, page, req, res);\n res.status(res.statusCode).setHeader('Content-Type', 'text/html; charset=utf-8').send(html);\n}\n\nexport { encodePageScript };\n","import { format } from 'node:util';\n\nimport { sendInertiaLocation, sendInertiaResponse } from './response.js';\nimport { inertiaRedirectStatus, parseInertiaRequest, scopeErrors } from '../core/index.js';\n\nimport type { NextFunction, Request, Response } from 'express';\n\nimport type { InertiaMiddlewareOptions, InertiaResponseOptions } from './types.js';\nimport type { PageProps, ValidationErrors } from '../core/index.js';\n\n/**\n * Build the `connect-flash`-compatible `req.flash()` method for a request.\n * Provided so this package is a drop-in replacement for `connect-flash`: the\n * returned function matches its semantics exactly — a setter call appends to\n * a per-type bucket in `req.session.flash` and returns the new bucket length,\n * a getter call reads and clears. Throws when no session is wired, just like\n * `connect-flash`.\n *\n * @param req Express request whose `session` holds the flash buckets.\n */\nfunction createFlash(req: Request): Request['flash'] {\n return ((type?: string, msg?: unknown, ...rest: unknown[]): number | string[] | Record<string, string[]> => {\n const { session } = (req as unknown as { session?: Record<string, unknown> });\n\n if (session === undefined) {\n throw new Error('req.flash() requires sessions');\n }\n\n const msgs = (session.flash as Record<string, string[]> | undefined) ?? {};\n session.flash = msgs;\n\n // Setter: `req.flash(type, msg)` appends and returns the new count.\n if (type && msg) {\n let bucket = msgs[type];\n\n if (bucket === undefined) {\n bucket = [];\n msgs[type] = bucket;\n }\n\n if (rest.length > 0) {\n // util.format interpolation: req.flash('info', 'Hello %s', name).\n return bucket.push(format(msg, ...rest));\n }\n\n if (Array.isArray(msg)) {\n msg.forEach((val) => bucket.push(val as string));\n\n return bucket.length;\n }\n\n return bucket.push(msg as string);\n }\n\n // Getter: `req.flash(type)` returns and clears that one bucket.\n if (type) {\n const arr = msgs[type] ?? [];\n delete msgs[type];\n\n return arr;\n }\n\n // Getter: `req.flash()` returns and clears every bucket.\n session.flash = {};\n\n return msgs;\n }) as Request['flash'];\n}\n\n/**\n * Create the Inertia.js Express middleware.\n *\n * The middleware:\n * - Parses Inertia headers into `req.inertia`.\n * - Adds `req.flash(type?, msg?)` — a `connect-flash`-compatible API.\n * - Adds `res.inertia(component, props, options)`.\n * - Adds `res.inertiaLocation(url)` for external redirects.\n * - Adds `res.inertiaErrors(errors, bag?)` to flash validation errors.\n * - Adds `res.inertiaFlash(data)` to flash arbitrary data.\n * - Auto-promotes redirects after PUT/PATCH/DELETE to 303 for Inertia visits.\n *\n * Mount before your route handlers:\n *\n * ```ts\n * app.use(inertia({ rootView: ..., sharedProps: ... }));\n * ```\n *\n * @param options Middleware configuration; defaults to an empty object.\n */\nexport function inertia(options: InertiaMiddlewareOptions = {}) {\n const ctx = { options };\n\n return function inertiaMiddleware(req: Request, res: Response, next: NextFunction): void {\n const info = parseInertiaRequest({\n headers: req.headers,\n method: req.method,\n url: req.originalUrl ?? req.url,\n });\n req.inertia = info;\n\n // req.flash() — connect-flash compatible. See createFlash above.\n req.flash = createFlash(req);\n\n // res.inertia() — the main render helper.\n res.inertia = async (\n component: string,\n props: PageProps = {},\n opts: InertiaResponseOptions = {},\n ): Promise<void> => {\n try {\n await sendInertiaResponse(req, res, ctx, component, props, opts);\n } catch (err) {\n next(err);\n }\n };\n\n // res.inertiaLocation() — external redirect.\n res.inertiaLocation = (url: string): void => {\n sendInertiaLocation(req, res, url);\n };\n\n // res.inertiaErrors() — stash validation errors for the next request.\n // No-ops gracefully when no session is configured.\n res.inertiaErrors = (errors: ValidationErrors, bag?: string): void => {\n const { session } = (req as unknown as { session?: Record<string, unknown> });\n const scoped = scopeErrors(errors, bag ?? null);\n\n if (session) {\n const existing = (session.errors as PageProps | undefined) ?? {};\n session.errors = { ...existing, ...(scoped.errors as PageProps) };\n } else {\n // Without a session, expose them on res.locals so a custom shared-prop\n // resolver can pick them up for the same response.\n res.locals.inertiaErrors = scoped.errors;\n }\n };\n\n // res.inertiaFlash() — stash flash data for the next request.\n // No-ops gracefully when no session is configured.\n res.inertiaFlash = (data: Record<string, unknown>): void => {\n const { session } = (req as unknown as { session?: Record<string, unknown> });\n\n if (session) {\n session.flash = data;\n }\n };\n\n // Promote redirects after PUT/PATCH/DELETE to 303 so the follow-up GET\n // is correctly issued by every browser.\n if (info.isInertia) {\n const originalRedirect = res.redirect.bind(res);\n // Express's `res.redirect` signature is either (url) or (status, url) —\n // handle both. The deprecated (url, status) form was removed in v5.\n res.redirect = ((arg1: number | string, arg2?: string | number) => {\n let status: number;\n let url: string;\n\n if (typeof arg1 === 'number') {\n status = arg1;\n url = String(arg2);\n } else {\n status = inertiaRedirectStatus(req.method, 302);\n url = arg1;\n }\n\n // If the user explicitly passed a 302 from a non-GET, bump to 303.\n if (status === 302 && (req.method === 'PUT' || req.method === 'PATCH' || req.method === 'DELETE')) {\n status = 303;\n }\n\n originalRedirect(status, url);\n });\n }\n\n // Make sure CDNs / shared caches don't mix Inertia JSON and HTML variants.\n const existingVary = res.getHeader('Vary');\n\n if (!existingVary) {\n res.setHeader('Vary', 'Accept, X-Inertia');\n }\n\n next();\n };\n}\n\nexport default inertia;\n","import type { SsrCacheEntry, SsrCacheStore } from '../core/index.js';\n\n/**\n * In-process LRU + TTL cache for SSR responses.\n *\n * Suitable as L1 cache in front of Redis (`@blacklizard/inertia-cache-redis`)\n * or by itself for low-traffic / single-instance deployments.\n *\n * Notes:\n * - Bounded by entry count (`max`), not bytes. Keep it conservative.\n * - TTL is enforced lazily on `get()` — expired entries are removed at\n * read time and never returned.\n * - Not safe across processes. For multi-pod web tiers, layer Redis on top.\n */\nexport interface MemoryCacheStoreOptions {\n /** Maximum number of entries before LRU eviction kicks in. Default 500. */\n max?: number;\n}\n\ninterface Slot {\n entry: SsrCacheEntry;\n expiresAt: number;\n}\n\n/**\n * Build an in-process LRU + TTL {@link SsrCacheStore}. See\n * {@link MemoryCacheStoreOptions} for the full contract.\n *\n * @param options Store configuration; defaults applied when omitted.\n */\nexport function createMemoryCacheStore(options: MemoryCacheStoreOptions = {}): SsrCacheStore {\n const max = options.max ?? 500;\n const map = new Map<string, Slot>();\n\n /**\n * Drop oldest-inserted entries until `map.size <= max`. Relies on `Map`'s\n * insertion-order iteration to identify the LRU entry.\n */\n function evict(): void {\n while (map.size > max) {\n const oldest = map.keys().next().value;\n\n if (oldest === undefined) {\n return;\n }\n\n map.delete(oldest);\n }\n }\n\n return {\n /**\n * Return the cached entry or `null` on miss / expired. Touched entries\n * are reinserted so they become the most-recently-used.\n *\n * @param key Cache key.\n */\n async get(key) {\n const slot = map.get(key);\n\n if (!slot) {\n return null;\n }\n\n if (slot.expiresAt < Date.now()) {\n map.delete(key);\n\n return null;\n }\n\n // LRU bump: re-insert to move to end of insertion order.\n map.delete(key);\n map.set(key, slot);\n\n return slot.entry;\n },\n /**\n * Write an entry with the given TTL, replacing any prior value. Runs LRU\n * eviction afterward so `max` is never exceeded.\n *\n * @param key Cache key.\n * @param entry Value to store.\n * @param ttlSeconds Time-to-live in seconds.\n */\n async set(key, entry, ttlSeconds) {\n const expiresAt = Date.now() + ttlSeconds * 1000;\n map.delete(key);\n map.set(key, { entry, expiresAt });\n evict();\n },\n /**\n * Remove an entry. Idempotent — missing keys are silently ignored.\n *\n * @param key Cache key.\n */\n async delete(key) {\n map.delete(key);\n },\n };\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\n/**\n * Inputs to {@link prerender}.\n */\nexport interface PrerenderOptions {\n /** Origin to fetch from, e.g. `http://127.0.0.1:3000`. */\n baseUrl: string;\n /** List of paths to render. Each must start with `/`. */\n routes: string[];\n /** Output mode. Default `\"warmup\"`. */\n mode?: 'static' | 'warmup' | 'both';\n /** Output directory for static HTML. Required when mode includes `\"static\"`. */\n outDir?: string;\n /** Concurrency limit. Default 4. */\n concurrency?: number;\n /** Per-request timeout, ms. Default 30000. */\n timeoutMs?: number;\n /** Extra request headers (auth, host override, A/B bucket pinning, etc.). */\n headers?: Record<string, string>;\n /** Optional fetch override (for testing). */\n fetch?: typeof fetch;\n}\n\n/**\n * Per-route outcome from a prerender run. `status` is `null` and `error` is\n * populated when the request failed before producing a response.\n */\nexport interface PrerenderRouteResult {\n route: string;\n status: number | null;\n bytes: number;\n outputPath?: string;\n error?: string;\n durationMs: number;\n}\n\n/**\n * Aggregate result returned by {@link prerender}. `ok + failed === total`;\n * a route counts as `ok` when it returned a 2xx/3xx status and no error.\n */\nexport interface PrerenderSummary {\n total: number;\n ok: number;\n failed: number;\n results: PrerenderRouteResult[];\n}\n\n/**\n * Map an HTTP route to an on-disk static-output path. `/` maps to\n * `<outDir>/index.html`; `/foo/bar` to `<outDir>/foo/bar/index.html`.\n * Query strings are stripped before computing the path — reverse proxies\n * (nginx, Caddy, CloudFront) split the URL at `?` when looking up static\n * files, so embedding the query string in a directory name produces a path\n * that can never be served.\n *\n * @param outDir Static-output root.\n * @param route HTTP path (with optional query string) being prerendered.\n */\nfunction routeToFile(outDir: string, route: string): string {\n // Extract path-only; discard query string and fragment.\n const { pathname } = new URL(route, 'http://x');\n const trimmed = pathname.replace(/^\\/+/, '').replace(/\\/+$/, '');\n\n if (!trimmed) {\n return join(outDir, 'index.html');\n }\n\n return join(outDir, trimmed, 'index.html');\n}\n\n/**\n * Write the rendered HTML to disk when the mode includes static output.\n * Returns the file path written, or `undefined` for warmup-only modes.\n * Throws when static output is requested without an `outDir`.\n *\n * @param options Shared prerender options — `outDir` lives here.\n * @param route HTTP path being rendered; mapped to the on-disk path.\n * @param html Rendered response body to write.\n * @param mode Resolved output mode for this run.\n */\nasync function maybeWriteStatic(\n options: PrerenderOptions,\n route: string,\n html: string,\n mode: 'static' | 'warmup' | 'both',\n): Promise<string | undefined> {\n if (mode !== 'static' && mode !== 'both') {\n return undefined;\n }\n\n if (!options.outDir) {\n throw new Error(`outDir is required when mode is \"${mode}\"`);\n }\n\n const outputPath = routeToFile(options.outDir, route);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, html, 'utf8');\n\n return outputPath;\n}\n\n/**\n * Fetch a single route, optionally write its body to disk, and return a\n * {@link PrerenderRouteResult}. Never throws — failures are returned with\n * `status: null` and an `error` message so the calling loop keeps going.\n *\n * @param options Shared prerender options (baseUrl, mode, outDir, etc.).\n * @param route HTTP path to render.\n */\nasync function runOne(options: PrerenderOptions, route: string): Promise<PrerenderRouteResult> {\n const fetchImpl = options.fetch ?? fetch;\n const mode = options.mode ?? 'warmup';\n const start = Date.now();\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), options.timeoutMs ?? 30000);\n\n try {\n const url = new URL(route, options.baseUrl).toString();\n const res = await fetchImpl(url, {\n method: 'GET',\n headers: { Accept: 'text/html', ...options.headers },\n signal: ctrl.signal,\n });\n const html = await res.text();\n const outputPath = await maybeWriteStatic(options, route, html, mode);\n\n return {\n route,\n status: res.status,\n bytes: Buffer.byteLength(html),\n outputPath,\n durationMs: Date.now() - start,\n };\n } catch (err) {\n return {\n route,\n status: null,\n bytes: 0,\n error: (err as Error).message,\n durationMs: Date.now() - start,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Run the prerender pipeline. Programmatic entry-point for the\n * `bin/inertia-prerender` CLI.\n *\n * Two output modes (which can be combined via `\"both\"`):\n *\n * - **`warmup`** (default) — fetches each route, discards the body. Use to\n * populate the SSR view cache after a deploy so the first real visitor\n * doesn't pay the SSR latency cost.\n * - **`static`** — fetches each route and writes the response body to\n * `<outDir>/<route>/index.html`. Serve those files directly via\n * nginx/CDN, bypassing Express entirely.\n *\n * Bounded-concurrency: `concurrency` workers consume routes from a shared\n * counter; the returned `results` preserve input order. Failures on\n * individual routes never halt the run — the per-route summary lets a CI\n * step choose to fail-loud.\n *\n * @param options Pipeline configuration — see {@link PrerenderOptions}.\n */\nexport async function prerender(options: PrerenderOptions): Promise<PrerenderSummary> {\n const concurrency = Math.max(1, options.concurrency ?? 4);\n const results: PrerenderRouteResult[] = new Array(options.routes.length);\n let next = 0;\n\n /**\n * Claim the next route index and process it, then recurse. Recursion\n * (instead of a loop) avoids both `while`-spin and `no-await-in-loop`.\n */\n async function worker(): Promise<void> {\n const i = next;\n next += 1;\n\n if (i >= options.routes.length) {\n return;\n }\n\n const route = options.routes[i] ?? '';\n results[i] = await runOne(options, route);\n await worker();\n }\n\n await Promise.all(Array.from({ length: Math.min(concurrency, options.routes.length) }, async () => worker()));\n\n const ok = results.filter((r) => r && r.status !== null && r.status < 400 && !r.error).length;\n const failed = results.length - ok;\n\n return {\n total: results.length, ok, failed, results,\n };\n}\n","import type { SsrRenderer, SsrResult } from './types.js';\nimport type { InertiaPage } from '../core/index.js';\n\n/**\n * Inputs to {@link createInertiaSsrFetcher}.\n */\nexport interface InertiaSsrFetcherOptions {\n /** SSR endpoint URL — typically your internal LB DNS, e.g. `http://ssr.internal/render`. */\n url: string;\n /** Per-attempt timeout in milliseconds. Default 5000. */\n timeoutMs?: number;\n /** Number of retry attempts after the first failure. Default 2 (so up to 3 total tries). */\n retries?: number;\n /** Initial backoff in ms; doubles per retry. Default 100. */\n retryBaseMs?: number;\n /**\n * Circuit breaker: open after this many consecutive failures.\n * Set to `0` (or `Infinity`) to disable. Default 5.\n */\n breakerThreshold?: number;\n /** How long the breaker stays open before allowing one probe. Default 30000. */\n breakerCooldownMs?: number;\n /** Failure mode. Default `\"client\"`. */\n fallback?: 'client' | 'throw';\n /** Override fetch implementation (testing). */\n fetch?: typeof fetch;\n /** Extra headers sent with each render request. */\n headers?: Record<string, string>;\n}\n\nconst EMPTY_RESULT: SsrResult = { body: '', head: '' };\n\ninterface BreakerState {\n failures: number;\n openedAt: number | null;\n}\n\n/**\n * Resolve after the given number of milliseconds.\n *\n * @param ms Delay in milliseconds.\n */\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\n/**\n * Build an {@link SsrRenderer} that POSTs the page object to a separate Node\n * SSR process (or load-balanced pool) and returns the rendered\n * `{ head, body }`.\n *\n * Matches the wire protocol used by `@inertiajs/{vue3,react,svelte}/server`\n * `createServer()`: `POST <url>` with `application/json` body containing the\n * page object, response is `{ head: string[], body: string }`.\n *\n * Adds production hardening on top of the bare protocol:\n * - Per-attempt timeout (default 5000ms) via `AbortController`.\n * - Recursive retry with exponential backoff (`retryBaseMs * 2^attempt`).\n * - Circuit breaker — opens after `breakerThreshold` consecutive failed\n * *requests* (not attempts); half-opens after `breakerCooldownMs` so a\n * probe request can re-close it on success.\n * - `fallback`: `\"client\"` returns an empty SSR result so the client\n * renders alone; `\"throw\"` rethrows so Express's error handler can react.\n *\n * @param options Fetcher configuration — see {@link InertiaSsrFetcherOptions}.\n */\nexport function createInertiaSsrFetcher(options: InertiaSsrFetcherOptions): SsrRenderer {\n const fetchImpl = options.fetch ?? fetch;\n const timeoutMs = options.timeoutMs ?? 5000;\n const retries = options.retries ?? 2;\n const retryBaseMs = options.retryBaseMs ?? 100;\n const breakerThreshold = options.breakerThreshold ?? 5;\n const breakerCooldownMs = options.breakerCooldownMs ?? 30000;\n const fallback = options.fallback ?? 'client';\n const breakerEnabled = breakerThreshold > 0 && Number.isFinite(breakerThreshold);\n\n const state: BreakerState = { failures: 0, openedAt: null };\n\n /**\n * Returns `true` while the breaker is open and within its cooldown window;\n * a return of `false` after a previous failure means we are half-open and\n * the caller may attempt a probe request.\n */\n function breakerOpen(): boolean {\n if (!breakerEnabled || state.openedAt === null) {\n return false;\n }\n\n if (Date.now() - state.openedAt >= breakerCooldownMs) {\n // Half-open: let the next request probe.\n return false;\n }\n\n return true;\n }\n\n /**\n * Reset the breaker after a successful render.\n */\n function recordSuccess(): void {\n state.failures = 0;\n state.openedAt = null;\n }\n\n /**\n * Increment the failure counter; open the breaker when the threshold is hit.\n */\n function recordFailure(): void {\n state.failures += 1;\n\n if (breakerEnabled && state.failures >= breakerThreshold) {\n state.openedAt = Date.now();\n }\n }\n\n /**\n * Fire one SSR request — the unit the retry loop wraps. Throws on\n * timeout, network error, or non-2xx response.\n *\n * @param page Page object to send to the SSR server.\n */\n async function attempt(page: InertiaPage): Promise<SsrResult> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const res = await fetchImpl(options.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...options.headers,\n },\n body: JSON.stringify(page),\n signal: controller.signal,\n });\n\n if (!res.ok) {\n throw new Error(`Inertia SSR request failed: ${res.status} ${res.statusText}`);\n }\n\n const json = (await res.json()) as Partial<SsrResult>;\n\n return {\n body: json.body ?? '',\n head: json.head,\n bodyIsFullRoot: json.bodyIsFullRoot,\n };\n } finally {\n clearTimeout(timer);\n }\n }\n\n return async ({ page }: { page: InertiaPage }): Promise<SsrResult> => {\n if (breakerOpen()) {\n if (fallback === 'throw') {\n throw new Error('Inertia SSR circuit breaker is open');\n }\n\n return EMPTY_RESULT;\n }\n\n /**\n * Run one render attempt, recursing with exponential backoff until the\n * retry budget is spent. Records breaker success on the first win.\n *\n * @param remaining Retry attempts left before the error propagates.\n */\n const attemptWithRetry = async (remaining: number): Promise<SsrResult> => {\n try {\n const result = await attempt(page);\n recordSuccess();\n\n return result;\n } catch (err) {\n if (remaining <= 0) {\n throw err;\n }\n\n await sleep(retryBaseMs * 2 ** (retries - remaining));\n\n return attemptWithRetry(remaining - 1);\n }\n };\n\n try {\n return await attemptWithRetry(retries);\n } catch (lastError) {\n recordFailure();\n\n if (fallback === 'throw') {\n throw lastError;\n }\n\n return EMPTY_RESULT;\n }\n };\n}\n","import { createHash } from 'node:crypto';\nimport { readFile, stat } from 'node:fs/promises';\n\n/**\n * Inputs to {@link viteManifestVersion}.\n */\nexport interface ViteManifestVersionOptions {\n /** Path to `manifest.json`. */\n manifestPath: string;\n /**\n * If `true` (default), the manifest is re-read on every call when its\n * mtime has advanced. Set to `false` to read once on boot — slightly\n * faster but requires a process restart to pick up new builds.\n */\n watchMtime?: boolean;\n}\n\n/**\n * Inputs to {@link viteManifestAssets}.\n */\nexport interface ViteManifestAssetsOptions {\n /** Path to `manifest.json`. */\n manifestPath: string;\n /**\n * Manifest key for the entry to resolve. Typically the source path of\n * the client entry, e.g. `src/app.ts` or `src/main.tsx`.\n */\n entry: string;\n /**\n * If `true` (default), the manifest is re-read on every call when its\n * mtime has advanced. Set to `false` to read once on boot.\n */\n watchMtime?: boolean;\n}\n\n/**\n * Resolved asset URLs for a single Vite manifest entry. URLs are web paths\n * with a leading `/`, ready to drop into the HTML shell.\n */\nexport interface ViteManifestAssets {\n /** First CSS file declared by the entry, or `null` when absent. */\n css: string | null;\n /** Compiled JS file for the entry, or `null` when absent. */\n js: string | null;\n}\n\ninterface CacheEntry {\n mtimeMs: number;\n version: string;\n}\n\ninterface AssetsCacheEntry {\n mtimeMs: number;\n assets: ViteManifestAssets;\n}\n\nconst EMPTY_ASSETS: ViteManifestAssets = { css: null, js: null };\n\n/**\n * Build a stable asset version from a Vite-generated `manifest.json`.\n *\n * Compatible with both `@inertiajs/vite-plugin` output and the standard\n * `--manifest` option from `vite build`. The returned function is suitable\n * to pass directly as the `version` option of the Inertia middleware.\n *\n * Strategy:\n * - Read the manifest file.\n * - Hash a canonicalized list of `(key -> file)` entries (only the `file`\n * field per entry, so unrelated manifest metadata never perturbs the hash).\n * - Cache the result; re-read only after the file's mtime changes (when\n * `watchMtime` is true) or never re-read (when false).\n * - Returns `null` when the manifest file is absent or unparseable.\n *\n * A new manifest (different hashed output paths) yields a new version, which\n * the middleware embeds in the SSR cache key — so a deploy atomically\n * invalidates every existing cache entry.\n *\n * @param options Path to the manifest plus the mtime-watch flag.\n */\nexport function viteManifestVersion(options: ViteManifestVersionOptions): () => Promise<string | null> {\n const watchMtime = options.watchMtime ?? true;\n let cached: CacheEntry | null = null;\n\n /**\n * Resolve to the current version string, or `null` when the manifest can't\n * be read or parsed. Hits the in-memory cache when the mtime is unchanged.\n */\n async function read(): Promise<string | null> {\n let mtimeMs: number;\n\n try {\n const s = await stat(options.manifestPath);\n mtimeMs = s.mtimeMs;\n } catch {\n return null;\n }\n\n if (cached && (!watchMtime || cached.mtimeMs === mtimeMs)) {\n return cached.version;\n }\n\n let raw: string;\n\n try {\n raw = await readFile(options.manifestPath, 'utf8');\n } catch {\n return null;\n }\n\n let parsed: Record<string, unknown>;\n\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n\n // Canonical input: keys sorted, value extracts the `file` property when\n // the entry is a Vite manifest object (so unrelated metadata doesn't\n // perturb the hash).\n const lines: string[] = Object.keys(parsed)\n .sort()\n .map((key) => {\n const entry = parsed[key];\n\n if (entry && typeof entry === 'object' && 'file' in entry) {\n return `${key}=${(entry as { file: string }).file}`;\n }\n\n return `${key}=${JSON.stringify(entry)}`;\n });\n\n const version = createHash('sha256').update(lines.join('\\n')).digest('hex').slice(0, 16);\n\n cached = { mtimeMs, version };\n\n return version;\n }\n\n return read;\n}\n\n/**\n * Web-path normalize a manifest-relative file string. Adds a leading `/`\n * and collapses any pre-existing leading slashes so the result is always\n * a single-rooted absolute web path.\n *\n * @param file Manifest-relative file string.\n */\nfunction toWebPath(file: string): string {\n return `/${file.replace(/^\\/+/, '')}`;\n}\n\n/**\n * Resolve the asset URLs (`css`, `js`) for a single Vite manifest entry.\n *\n * Reads the manifest produced by `vite build --manifest` (or\n * `@inertiajs/vite-plugin`), locates the entry by manifest key, and\n * returns its compiled JS file plus the first declared CSS file as web\n * paths suitable to drop into the HTML shell's `<link>` and `<script>`\n * tags. Caches by mtime the same way as {@link viteManifestVersion}.\n *\n * Returns `{ css: null, js: null }` when the manifest is missing,\n * unparseable, or the entry key is absent — the root view stays\n * renderable while assets are still being built.\n *\n * @param options Path to the manifest, entry key, and the mtime-watch flag.\n */\nexport function viteManifestAssets(\n options: ViteManifestAssetsOptions,\n): () => Promise<ViteManifestAssets> {\n const watchMtime = options.watchMtime ?? true;\n let cached: AssetsCacheEntry | null = null;\n\n /**\n * Resolve to the current asset URLs, or `{ css: null, js: null }` when\n * the manifest can't be read, parsed, or the entry is missing.\n */\n async function read(): Promise<ViteManifestAssets> {\n let mtimeMs: number;\n\n try {\n const s = await stat(options.manifestPath);\n mtimeMs = s.mtimeMs;\n } catch {\n return EMPTY_ASSETS;\n }\n\n if (cached && (!watchMtime || cached.mtimeMs === mtimeMs)) {\n return cached.assets;\n }\n\n let raw: string;\n\n try {\n raw = await readFile(options.manifestPath, 'utf8');\n } catch {\n return EMPTY_ASSETS;\n }\n\n let parsed: Record<string, unknown>;\n\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return EMPTY_ASSETS;\n }\n\n const entry = parsed[options.entry];\n\n if (!entry || typeof entry !== 'object') {\n return EMPTY_ASSETS;\n }\n\n const { file, css: cssList } = entry as { file?: unknown; css?: unknown };\n const firstCss = Array.isArray(cssList) && typeof cssList[0] === 'string' ? cssList[0] : null;\n\n const assets: ViteManifestAssets = {\n css: firstCss !== null ? toWebPath(firstCss) : null,\n js: typeof file === 'string' ? toWebPath(file) : null,\n };\n\n cached = { mtimeMs, assets };\n\n return assets;\n }\n\n return read;\n}\n","/**\n * Express adapter for Inertia.js v3.\n *\n * Default entry point of `@blacklizard/inertia-express`. The framework-agnostic\n * core is available at `@blacklizard/inertia-express/core`.\n */\n\nimport { inertia } from './middleware.js';\n\nexport type {\n AlwaysProp,\n ComputeCacheKeyInput,\n DeepMergeProp,\n DeferredProp,\n InertiaPage,\n InertiaRequestInfo,\n LazyProp,\n MergeProp,\n OptionalProp,\n PageProps,\n SharedPropsInput,\n SsrCacheEntry,\n SsrCacheStore,\n ValidationErrors,\n VersionResolver,\n} from '../core/index.js';\n// Re-export the most commonly used core helpers so users only need a single\n// import path for typical apps.\nexport {\n always,\n canonicalStringify,\n computeSsrCacheKey,\n deepMerge,\n defer,\n INERTIA_HEADERS,\n INERTIA_LOCATION_STATUS,\n INERTIA_REDIRECT_STATUS,\n lazy,\n merge,\n optional,\n} from '../core/index.js';\nexport type { EdgeCacheDecision, EdgeCachePolicy } from './edge-cache.js';\nexport { applyEdgeCache } from './edge-cache.js';\nexport type { MemoryCacheStoreOptions } from './memory-store.js';\nexport { createMemoryCacheStore } from './memory-store.js';\nexport { inertia };\nexport default inertia;\nexport type {\n PrerenderOptions,\n PrerenderRouteResult,\n PrerenderSummary,\n} from './prerender.js';\nexport { prerender } from './prerender.js';\nexport {\n encodePageScript,\n sendInertiaLocation,\n sendInertiaResponse,\n sendVersionMismatch,\n} from './response.js';\nexport type { InertiaSsrFetcherOptions } from './ssr-fetcher.js';\nexport { createInertiaSsrFetcher } from './ssr-fetcher.js';\nexport type {\n InertiaCacheOptions,\n InertiaMiddlewareOptions,\n InertiaResponseOptions,\n RootViewInput,\n RootViewRenderer,\n SsrRenderer,\n SsrRendererInput,\n SsrResult,\n} from './types.js';\nexport type {\n ViteManifestAssets,\n ViteManifestAssetsOptions,\n ViteManifestVersionOptions,\n} from './vite-manifest.js';\nexport { viteManifestAssets, viteManifestVersion } from './vite-manifest.js';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/express/edge-cache.ts","../../src/express/response.ts","../../src/express/middleware.ts","../../src/express/memory-store.ts","../../src/express/prerender.ts","../../src/express/ssr-fetcher.ts","../../src/express/vite-manifest.ts","../../src/express/index.ts"],"sourcesContent":["import type { Request, Response } from 'express';\n\nimport type { InertiaPage } from '../core/index.js';\n\n/**\n * Decision returned by an `edgeCache` policy. Returning `null` (or a\n * negative number) opts out of edge caching — no `Cache-Control` is set\n * beyond what your handler set itself.\n */\nexport interface EdgeCacheDecision {\n /** `s-maxage` value for shared/CDN caches, in seconds. */\n sMaxAge: number;\n /** `max-age` for the browser cache. Default 0 (revalidate immediately). */\n maxAge?: number;\n /**\n * `stale-while-revalidate` window. Lets CDNs serve a slightly stale\n * response while fetching a fresh one in the background.\n */\n staleWhileRevalidate?: number;\n /**\n * Additional `Vary` values. The middleware always emits `Vary: Accept,\n * X-Inertia` — this lets you append more (e.g. `Cookie`, `Accept-Language`).\n */\n vary?: string[];\n}\n\n/**\n * Per-response edge-cache decision: receives the request, response, and\n * resolved page, returns an {@link EdgeCacheDecision} or `null` to opt out.\n */\nexport type EdgeCachePolicy = (input: { req: Request; res: Response; page: InertiaPage }) => EdgeCacheDecision | null;\n\n/**\n * Apply an edge-cache decision to the response. Called automatically by\n * the middleware when an `edgeCache` policy is configured. Exported so\n * you can call it from custom code paths too.\n *\n * No-op when `decision` is `null`, when `sMaxAge <= 0`, or when the response\n * is 3xx+ (4xx/5xx must not be cached at the edge). The middleware also skips\n * Inertia XHR responses before calling this — that filter lives at the call\n * site, not here, since `applyEdgeCache` only sees the decision + status.\n *\n * `Vary` values from the decision are merged into the existing header,\n * de-duplicated by trimmed string.\n *\n * @param decision Policy output (or `null` to opt out).\n * @param res Outgoing Express response (mutated: sets `Cache-Control`, `Vary`).\n */\nexport function applyEdgeCache(decision: EdgeCacheDecision | null, res: Response): void {\n if (decision === null || decision.sMaxAge <= 0) {\n return;\n }\n\n if (res.statusCode >= 300) {\n return;\n }\n\n const parts: string[] = ['public', `s-maxage=${decision.sMaxAge}`, `max-age=${decision.maxAge ?? 0}`];\n\n if (decision.staleWhileRevalidate && decision.staleWhileRevalidate > 0) {\n parts.push(`stale-while-revalidate=${decision.staleWhileRevalidate}`);\n }\n\n res.setHeader('Cache-Control', parts.join(', '));\n\n if (decision.vary && decision.vary.length > 0) {\n const existing = res.getHeader('Vary');\n const all = new Set<string>();\n\n if (typeof existing === 'string') {\n existing.split(',').forEach((v) => all.add(v.trim()));\n }\n\n decision.vary.forEach((v) => {\n const trimmed = v.trim();\n\n if (trimmed) {\n all.add(trimmed);\n }\n });\n\n res.setHeader('Vary', Array.from(all).join(', '));\n }\n}\n","import {\n buildPage,\n computeSsrCacheKey,\n encodePageScript,\n INERTIA_HEADERS,\n INERTIA_LOCATION_STATUS,\n isVersionMismatch,\n parseInertiaRequest,\n renderDefaultHtml,\n resolveVersion,\n} from '../core';\nimport { applyEdgeCache } from './edge-cache.js';\n\nimport type { Request, Response } from 'express';\n\nimport type {\n InertiaPage, InertiaRequestInfo, PageProps, SharedPropsInput, SsrCacheEntry,\n} from '../core';\nimport type {\n InertiaCacheOptions, InertiaMiddlewareOptions, InertiaResponseOptions, SsrResult,\n} from './types.js';\n\ninterface RenderContext {\n options: InertiaMiddlewareOptions;\n}\n\n/**\n * Resolve the shared-props input for a request. When `flashFromSession` is\n * enabled, session-stored validation errors and flash data are merged in\n * automatically (read-once — they are cleared after being read). Explicit\n * `sharedProps` keys win on collision.\n *\n * @param ctx Render context carrying the middleware options.\n * @param res Express response, scanned for `res.locals.inertiaErrors` as a\n * fallback when no session is wired.\n */\nfunction composeSharedProps(ctx: RenderContext, res: Response): SharedPropsInput<Request> {\n const base = ctx.options.sharedProps;\n\n if (!ctx.options.flashFromSession) {\n return base ?? {};\n }\n\n return async (req: Request): Promise<PageProps> => {\n let baseProps: PageProps = {};\n\n if (base) {\n baseProps = typeof base === 'function' ? ((await base(req)) ?? {}) : base;\n }\n\n const { session } = (req as unknown as { session?: Record<string, unknown> });\n const errors = (session?.errors as PageProps | undefined)\n ?? (res.locals.inertiaErrors as PageProps | undefined)\n ?? {};\n const flash = session?.flash ?? null;\n\n if (session) {\n // Flash data is read-once: clear it so it does not leak into later visits.\n session.errors = undefined;\n session.flash = undefined;\n }\n\n return { errors, flash, ...baseProps };\n };\n}\n\n/**\n * Send the Inertia version-mismatch response: 409 with `X-Inertia-Location`\n * pointing back at the same URL so the client performs a hard reload.\n *\n * @param res Outgoing Express response.\n * @param url Location to send the client to (typically the requested URL).\n */\nexport function sendVersionMismatch(res: Response, url: string): void {\n res.status(INERTIA_LOCATION_STATUS).setHeader(INERTIA_HEADERS.location, url).end();\n}\n\n/**\n * Send an external Inertia redirect. For Inertia (XHR) requests this returns\n * 409 + `X-Inertia-Location` so the client performs a window.location nav.\n * For plain browser requests it falls back to a normal 302 redirect.\n *\n * @param req Incoming Express request; `req.inertia` is reused when present.\n * @param res Outgoing Express response.\n * @param target Destination URL.\n */\nexport function sendInertiaLocation(req: Request, res: Response, target: string): void {\n const info = req.inertia\n ?? parseInertiaRequest({\n headers: req.headers,\n method: req.method,\n url: req.originalUrl ?? req.url,\n });\n\n if (info.isInertia) {\n res.status(INERTIA_LOCATION_STATUS).setHeader(INERTIA_HEADERS.location, target).end();\n\n return;\n }\n\n res.redirect(302, target);\n}\n\n/**\n * Merge `Accept` and `X-Inertia` into an existing `Vary` header value,\n * deduplicating case-insensitively so the header never accumulates repeats\n * when the middleware and the response both contribute tokens.\n *\n * @param existing Current header value as Node returns it (string, number,\n * string[], or undefined).\n */\nfunction mergeVary(existing: string | number | string[] | undefined): string {\n const tokens: string[] = [];\n const seen = new Set<string>();\n\n const add = (token: string): void => {\n const trimmed = token.trim();\n const key = trimmed.toLowerCase();\n\n if (trimmed && !seen.has(key)) {\n seen.add(key);\n tokens.push(trimmed);\n }\n };\n\n if (existing !== undefined) {\n String(existing).split(',').forEach((part) => add(part));\n }\n\n add('Accept');\n add('X-Inertia');\n\n return tokens.join(', ');\n}\n\n/**\n * Coerce SSR head output (string, string[], or absent) to a single string\n * suitable for injection into the document `<head>`.\n *\n * @param head Head fragment(s) from the SSR renderer.\n */\nfunction flattenSsrHead(head: string | string[] | undefined): string | undefined {\n if (head === undefined) {\n return undefined;\n }\n\n if (Array.isArray(head)) {\n return head.join('');\n }\n\n return head;\n}\n\n/**\n * Default rule deciding whether a rendered page is safe to cache. Cacheable\n * iff the request is a non-Inertia GET and the page carries no flash\n * messages or validation errors (those are per-visit state).\n *\n * @param input Express request and the resolved page object.\n */\nfunction defaultCacheVary(input: { req: Request; page: InertiaPage }): boolean {\n const { req, page } = input;\n\n if (req.method !== 'GET') {\n return false;\n }\n\n // Inertia XHR (JSON) responses vary per visit — caching them at the view\n // layer would serve stale partial reloads to the wrong client.\n if (req.inertia?.isInertia) {\n return false;\n }\n\n const props = page.props as Record<string, unknown>;\n\n if (props && typeof props === 'object') {\n const flash = props.flash as Record<string, unknown> | undefined;\n\n if (flash && Object.keys(flash).length > 0) {\n return false;\n }\n\n const errors = props.errors as Record<string, unknown> | undefined;\n\n if (errors && Object.keys(errors).length > 0) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Wrap `cache.store.get` so a thrown store error is reported via `onError`\n * and reported as a miss instead of bubbling out to the request handler.\n *\n * @param cache Resolved cache options.\n * @param key Cache key.\n */\nasync function safeCacheGet(cache: InertiaCacheOptions, key: string): Promise<SsrCacheEntry | null> {\n try {\n return await cache.store.get(key);\n } catch (err) {\n cache.onError?.('get', err);\n\n return null;\n }\n}\n\n/**\n * Wrap `cache.store.set` so a thrown store error is reported via `onError`\n * but never propagates — cache writes are fire-and-forget.\n *\n * @param cache Resolved cache options.\n * @param key Cache key.\n * @param entry Value to store.\n * @param ttl Time-to-live in seconds.\n */\nasync function safeCacheSet(cache: InertiaCacheOptions, key: string, entry: SsrCacheEntry, ttl: number): Promise<void> {\n try {\n await cache.store.set(key, entry, ttl);\n } catch (err) {\n cache.onError?.('set', err);\n }\n}\n\n/**\n * Render the final HTML body. When an SSR result is available, expose it via\n * `res.locals.ssr` so a custom `rootView` can read it, then either call\n * `rootView` or fall back to {@link renderDefaultHtml}.\n *\n * @param ctx Render context carrying the middleware options.\n * @param page Resolved page object.\n * @param ssrResult SSR renderer output, or `null` when SSR is skipped.\n * @param req Incoming request.\n * @param res Outgoing response.\n */\nasync function renderRootViewWithSsr(\n ctx: RenderContext,\n page: InertiaPage,\n ssrResult: SsrResult | null,\n req: Request,\n res: Response,\n): Promise<string> {\n if (ssrResult) {\n res.locals.ssr = ssrResult;\n\n if (ctx.options.rootView) {\n return ctx.options.rootView({ page, req, res });\n }\n\n return renderDefaultHtml({\n page,\n head: flattenSsrHead(ssrResult.head),\n ssrBody: ssrResult.bodyIsFullRoot ? undefined : ssrResult.body,\n ssrFull: ssrResult.bodyIsFullRoot ? ssrResult.body : undefined,\n });\n }\n\n if (ctx.options.rootView) {\n return ctx.options.rootView({ page, req, res });\n }\n\n return renderDefaultHtml({ page });\n}\n\n/**\n * Apply the configured `edgeCache` policy unless this is an Inertia XHR\n * response (those vary per visit and must not be cached at the edge).\n *\n * @param ctx Render context carrying the middleware options.\n * @param page Resolved page object.\n * @param req Incoming request.\n * @param res Outgoing response.\n */\nfunction maybeApplyEdgeCache(ctx: RenderContext, page: InertiaPage, req: Request, res: Response): void {\n if (!ctx.options.edgeCache) {\n return;\n }\n\n if (req.inertia?.isInertia) {\n return;\n }\n\n const decision = ctx.options.edgeCache({ req, res, page });\n applyEdgeCache(decision, res);\n}\n\n/**\n * Build and send an Inertia response. Handles version-mismatch detection,\n * partial-reload filtering, JSON vs HTML, SSR, view caching, and edge\n * cache headers. The status code already on `res` (e.g. `res.status(404)`)\n * is preserved on both the JSON and HTML branches.\n *\n * @param req Incoming Express request.\n * @param res Outgoing Express response.\n * @param ctx Render context carrying the middleware options.\n * @param component Inertia component name to render.\n * @param props Page-specific props (merged with shared props).\n * @param options Optional per-response overrides (url, clearHistory, encryptHistory).\n */\nexport async function sendInertiaResponse(\n req: Request,\n res: Response,\n ctx: RenderContext,\n component: string,\n props: PageProps,\n options: InertiaResponseOptions = {},\n): Promise<void> {\n const info: InertiaRequestInfo = req.inertia\n ?? parseInertiaRequest({\n headers: req.headers,\n method: req.method,\n url: options.url ?? req.originalUrl ?? req.url,\n });\n\n const url = options.url ?? info.url;\n const version = await resolveVersion(ctx.options.version, req);\n\n if (info.isInertia && info.method === 'GET' && isVersionMismatch(version, info.version)) {\n sendVersionMismatch(res, url);\n\n return;\n }\n\n const page = await buildPage<Request>({\n component,\n props,\n request: { ...info, url },\n req,\n shared: composeSharedProps(ctx, res),\n version,\n options: {\n clearHistory: options.clearHistory,\n encryptHistory: options.encryptHistory,\n },\n });\n\n res.setHeader('Vary', mergeVary(res.getHeader('Vary')));\n\n if (info.isInertia) {\n res\n .status(res.statusCode)\n .setHeader('Content-Type', 'application/json; charset=utf-8')\n .setHeader(INERTIA_HEADERS.inertia, 'true');\n res.send(JSON.stringify(page));\n\n return;\n }\n\n // HTML branch — SSR + cache.\n const { cache } = ctx.options;\n const cacheVary = cache?.vary ?? defaultCacheVary;\n const cacheable = cache !== undefined && cacheVary({ req, page });\n let cacheKey: string | null = null;\n\n if (cacheable && cache) {\n cacheKey = computeSsrCacheKey({\n prefix: cache.keyPrefix ?? 'inertia:ssr',\n version,\n component,\n props: page.props,\n discriminator: cache.discriminator?.(req),\n });\n const hit = await safeCacheGet(cache, cacheKey);\n\n if (hit) {\n res.setHeader('X-Inertia-Cache', 'HIT');\n const html = await renderRootViewWithSsr(\n ctx,\n hit.page,\n {\n head: hit.head,\n body: hit.body,\n bodyIsFullRoot: hit.bodyIsFullRoot,\n },\n req,\n res,\n );\n maybeApplyEdgeCache(ctx, hit.page, req, res);\n res.status(res.statusCode).setHeader('Content-Type', 'text/html; charset=utf-8').send(html);\n\n return;\n }\n\n res.setHeader('X-Inertia-Cache', 'MISS');\n }\n\n let ssrResult: SsrResult | null = null;\n\n if (ctx.options.ssr) {\n ssrResult = await ctx.options.ssr({ page, req, res });\n }\n\n const html = await renderRootViewWithSsr(ctx, page, ssrResult, req, res);\n\n if (cacheable && cache && cacheKey && ssrResult) {\n const entry: SsrCacheEntry = {\n head: ssrResult.head ?? '',\n body: ssrResult.body,\n bodyIsFullRoot: ssrResult.bodyIsFullRoot,\n storedAt: new Date().toISOString(),\n page,\n };\n // Fire-and-forget — safeCacheSet swallows its own errors.\n safeCacheSet(cache, cacheKey, entry, cache.ttlSeconds ?? 300).catch(() => {});\n }\n\n maybeApplyEdgeCache(ctx, page, req, res);\n res.status(res.statusCode).setHeader('Content-Type', 'text/html; charset=utf-8').send(html);\n}\n\nexport { encodePageScript };\n","import { format } from 'node:util';\n\nimport { sendInertiaLocation, sendInertiaResponse } from './response.js';\nimport {\n inertiaRedirectStatus, parseInertiaRequest, renderErrorPage, scopeErrors,\n} from '../core/index.js';\n\nimport type { NextFunction, Request, Response } from 'express';\n\nimport type { InertiaMiddlewareOptions, InertiaResponseOptions } from './types.js';\nimport type { PageProps, ValidationErrors } from '../core/index.js';\n\n/**\n * Build the `connect-flash`-compatible `req.flash()` method for a request.\n * Provided so this package is a drop-in replacement for `connect-flash`: the\n * returned function matches its semantics exactly — a setter call appends to\n * a per-type bucket in `req.session.flash` and returns the new bucket length,\n * a getter call reads and clears. Throws when no session is wired, just like\n * `connect-flash`.\n *\n * @param req Express request whose `session` holds the flash buckets.\n */\nfunction createFlash(req: Request): Request['flash'] {\n return ((type?: string, msg?: unknown, ...rest: unknown[]): number | string[] | Record<string, string[]> => {\n const { session } = (req as unknown as { session?: Record<string, unknown> });\n\n if (session === undefined) {\n throw new Error('req.flash() requires sessions');\n }\n\n const msgs = (session.flash as Record<string, string[]> | undefined) ?? {};\n session.flash = msgs;\n\n // Setter: `req.flash(type, msg)` appends and returns the new count.\n if (type && msg) {\n let bucket = msgs[type];\n\n if (bucket === undefined) {\n bucket = [];\n msgs[type] = bucket;\n }\n\n if (rest.length > 0) {\n // util.format interpolation: req.flash('info', 'Hello %s', name).\n return bucket.push(format(msg, ...rest));\n }\n\n if (Array.isArray(msg)) {\n msg.forEach((val) => bucket.push(val as string));\n\n return bucket.length;\n }\n\n return bucket.push(msg as string);\n }\n\n // Getter: `req.flash(type)` returns and clears that one bucket.\n if (type) {\n const arr = msgs[type] ?? [];\n delete msgs[type];\n\n return arr;\n }\n\n // Getter: `req.flash()` returns and clears every bucket.\n session.flash = {};\n\n return msgs;\n }) as Request['flash'];\n}\n\n/**\n * Create the Inertia.js Express middleware.\n *\n * The middleware:\n * - Parses Inertia headers into `req.inertia`.\n * - Adds `req.flash(type?, msg?)` — a `connect-flash`-compatible API.\n * - Adds `res.inertia(component, props, options)`.\n * - Adds `res.inertiaLocation(url)` for external redirects.\n * - Adds `res.inertiaErrors(errors, bag?)` to flash validation errors.\n * - Adds `res.inertiaFlash(data)` to flash arbitrary data.\n * - Adds `res.inertiaError(status, message?)` to render an error page.\n * - Auto-promotes redirects after PUT/PATCH/DELETE to 303 for Inertia visits.\n *\n * Mount before your route handlers:\n *\n * ```ts\n * app.use(inertia({ rootView: ..., sharedProps: ... }));\n * ```\n *\n * @param options Middleware configuration; defaults to an empty object.\n */\nexport function inertia(options: InertiaMiddlewareOptions = {}) {\n const ctx = { options };\n\n return function inertiaMiddleware(req: Request, res: Response, next: NextFunction): void {\n const info = parseInertiaRequest({\n headers: req.headers,\n method: req.method,\n url: req.originalUrl ?? req.url,\n });\n req.inertia = info;\n\n // req.flash() — connect-flash compatible. See createFlash above.\n req.flash = createFlash(req);\n\n // res.inertia() — the main render helper.\n res.inertia = async (\n component: string,\n props: PageProps = {},\n opts: InertiaResponseOptions = {},\n ): Promise<void> => {\n try {\n await sendInertiaResponse(req, res, ctx, component, props, opts);\n } catch (err) {\n next(err);\n }\n };\n\n // res.inertiaLocation() — external redirect.\n res.inertiaLocation = (url: string): void => {\n sendInertiaLocation(req, res, url);\n };\n\n // res.inertiaErrors() — stash validation errors for the next request.\n // No-ops gracefully when no session is configured.\n res.inertiaErrors = (errors: ValidationErrors, bag?: string): void => {\n const { session } = (req as unknown as { session?: Record<string, unknown> });\n const scoped = scopeErrors(errors, bag ?? null);\n\n if (session) {\n const existing = (session.errors as PageProps | undefined) ?? {};\n session.errors = { ...existing, ...(scoped.errors as PageProps) };\n } else {\n // Without a session, expose them on res.locals so a custom shared-prop\n // resolver can pick them up for the same response.\n res.locals.inertiaErrors = scoped.errors;\n }\n };\n\n // res.inertiaFlash() — stash flash data for the next request.\n // No-ops gracefully when no session is configured.\n res.inertiaFlash = (data: Record<string, unknown>): void => {\n const { session } = (req as unknown as { session?: Record<string, unknown> });\n\n if (session) {\n session.flash = data;\n }\n };\n\n // res.inertiaError() — render an error page at the given HTTP status.\n // Inertia requests render the client `Error` component (status passed as a\n // prop); on a render/SSR failure, or for plain browser loads, it falls back\n // to a standalone HTML page so the user never sees a raw JSON error.\n res.inertiaError = async (status: number, message?: string): Promise<void> => {\n res.status(status);\n\n if (req.inertia?.isInertia) {\n try {\n await sendInertiaResponse(req, res, ctx, 'Error', { status }, {});\n\n return;\n } catch {\n // Error component unresolved or SSR failed — fall through to the\n // standalone HTML fallback below.\n }\n }\n\n if (!res.headersSent) {\n res.status(status).type('html').send(renderErrorPage({ status, message }));\n }\n };\n\n // Promote redirects after PUT/PATCH/DELETE to 303 so the follow-up GET\n // is correctly issued by every browser.\n if (info.isInertia) {\n const originalRedirect = res.redirect.bind(res);\n // Express's `res.redirect` signature is either (url) or (status, url) —\n // handle both. The deprecated (url, status) form was removed in v5.\n res.redirect = ((arg1: number | string, arg2?: string | number) => {\n let status: number;\n let url: string;\n\n if (typeof arg1 === 'number') {\n status = arg1;\n url = String(arg2);\n } else {\n status = inertiaRedirectStatus(req.method, 302);\n url = arg1;\n }\n\n // If the user explicitly passed a 302 from a non-GET, bump to 303.\n if (status === 302 && (req.method === 'PUT' || req.method === 'PATCH' || req.method === 'DELETE')) {\n status = 303;\n }\n\n originalRedirect(status, url);\n });\n }\n\n // Make sure CDNs / shared caches don't mix Inertia JSON and HTML variants.\n const existingVary = res.getHeader('Vary');\n\n if (!existingVary) {\n res.setHeader('Vary', 'Accept, X-Inertia');\n }\n\n next();\n };\n}\n\nexport default inertia;\n","import type { SsrCacheEntry, SsrCacheStore } from '../core/index.js';\n\n/**\n * In-process LRU + TTL cache for SSR responses.\n *\n * Suitable as L1 cache in front of Redis (`@blacklizard/inertia-cache-redis`)\n * or by itself for low-traffic / single-instance deployments.\n *\n * Notes:\n * - Bounded by entry count (`max`), not bytes. Keep it conservative.\n * - TTL is enforced lazily on `get()` — expired entries are removed at\n * read time and never returned.\n * - Not safe across processes. For multi-pod web tiers, layer Redis on top.\n */\nexport interface MemoryCacheStoreOptions {\n /** Maximum number of entries before LRU eviction kicks in. Default 500. */\n max?: number;\n}\n\ninterface Slot {\n entry: SsrCacheEntry;\n expiresAt: number;\n}\n\n/**\n * Build an in-process LRU + TTL {@link SsrCacheStore}. See\n * {@link MemoryCacheStoreOptions} for the full contract.\n *\n * @param options Store configuration; defaults applied when omitted.\n */\nexport function createMemoryCacheStore(options: MemoryCacheStoreOptions = {}): SsrCacheStore {\n const max = options.max ?? 500;\n const map = new Map<string, Slot>();\n\n /**\n * Drop oldest-inserted entries until `map.size <= max`. Relies on `Map`'s\n * insertion-order iteration to identify the LRU entry.\n */\n function evict(): void {\n while (map.size > max) {\n const oldest = map.keys().next().value;\n\n if (oldest === undefined) {\n return;\n }\n\n map.delete(oldest);\n }\n }\n\n return {\n /**\n * Return the cached entry or `null` on miss / expired. Touched entries\n * are reinserted so they become the most-recently-used.\n *\n * @param key Cache key.\n */\n async get(key) {\n const slot = map.get(key);\n\n if (!slot) {\n return null;\n }\n\n if (slot.expiresAt < Date.now()) {\n map.delete(key);\n\n return null;\n }\n\n // LRU bump: re-insert to move to end of insertion order.\n map.delete(key);\n map.set(key, slot);\n\n return slot.entry;\n },\n /**\n * Write an entry with the given TTL, replacing any prior value. Runs LRU\n * eviction afterward so `max` is never exceeded.\n *\n * @param key Cache key.\n * @param entry Value to store.\n * @param ttlSeconds Time-to-live in seconds.\n */\n async set(key, entry, ttlSeconds) {\n const expiresAt = Date.now() + ttlSeconds * 1000;\n map.delete(key);\n map.set(key, { entry, expiresAt });\n evict();\n },\n /**\n * Remove an entry. Idempotent — missing keys are silently ignored.\n *\n * @param key Cache key.\n */\n async delete(key) {\n map.delete(key);\n },\n };\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\n/**\n * Inputs to {@link prerender}.\n */\nexport interface PrerenderOptions {\n /** Origin to fetch from, e.g. `http://127.0.0.1:3000`. */\n baseUrl: string;\n /** List of paths to render. Each must start with `/`. */\n routes: string[];\n /** Output mode. Default `\"warmup\"`. */\n mode?: 'static' | 'warmup' | 'both';\n /** Output directory for static HTML. Required when mode includes `\"static\"`. */\n outDir?: string;\n /** Concurrency limit. Default 4. */\n concurrency?: number;\n /** Per-request timeout, ms. Default 30000. */\n timeoutMs?: number;\n /** Extra request headers (auth, host override, A/B bucket pinning, etc.). */\n headers?: Record<string, string>;\n /** Optional fetch override (for testing). */\n fetch?: typeof fetch;\n}\n\n/**\n * Per-route outcome from a prerender run. `status` is `null` and `error` is\n * populated when the request failed before producing a response.\n */\nexport interface PrerenderRouteResult {\n route: string;\n status: number | null;\n bytes: number;\n outputPath?: string;\n error?: string;\n durationMs: number;\n}\n\n/**\n * Aggregate result returned by {@link prerender}. `ok + failed === total`;\n * a route counts as `ok` when it returned a 2xx/3xx status and no error.\n */\nexport interface PrerenderSummary {\n total: number;\n ok: number;\n failed: number;\n results: PrerenderRouteResult[];\n}\n\n/**\n * Map an HTTP route to an on-disk static-output path. `/` maps to\n * `<outDir>/index.html`; `/foo/bar` to `<outDir>/foo/bar/index.html`.\n * Query strings are stripped before computing the path — reverse proxies\n * (nginx, Caddy, CloudFront) split the URL at `?` when looking up static\n * files, so embedding the query string in a directory name produces a path\n * that can never be served.\n *\n * @param outDir Static-output root.\n * @param route HTTP path (with optional query string) being prerendered.\n */\nfunction routeToFile(outDir: string, route: string): string {\n // Extract path-only; discard query string and fragment.\n const { pathname } = new URL(route, 'http://x');\n const trimmed = pathname.replace(/^\\/+/, '').replace(/\\/+$/, '');\n\n if (!trimmed) {\n return join(outDir, 'index.html');\n }\n\n return join(outDir, trimmed, 'index.html');\n}\n\n/**\n * Write the rendered HTML to disk when the mode includes static output.\n * Returns the file path written, or `undefined` for warmup-only modes.\n * Throws when static output is requested without an `outDir`.\n *\n * @param options Shared prerender options — `outDir` lives here.\n * @param route HTTP path being rendered; mapped to the on-disk path.\n * @param html Rendered response body to write.\n * @param mode Resolved output mode for this run.\n */\nasync function maybeWriteStatic(\n options: PrerenderOptions,\n route: string,\n html: string,\n mode: 'static' | 'warmup' | 'both',\n): Promise<string | undefined> {\n if (mode !== 'static' && mode !== 'both') {\n return undefined;\n }\n\n if (!options.outDir) {\n throw new Error(`outDir is required when mode is \"${mode}\"`);\n }\n\n const outputPath = routeToFile(options.outDir, route);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, html, 'utf8');\n\n return outputPath;\n}\n\n/**\n * Fetch a single route, optionally write its body to disk, and return a\n * {@link PrerenderRouteResult}. Never throws — failures are returned with\n * `status: null` and an `error` message so the calling loop keeps going.\n *\n * @param options Shared prerender options (baseUrl, mode, outDir, etc.).\n * @param route HTTP path to render.\n */\nasync function runOne(options: PrerenderOptions, route: string): Promise<PrerenderRouteResult> {\n const fetchImpl = options.fetch ?? fetch;\n const mode = options.mode ?? 'warmup';\n const start = Date.now();\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), options.timeoutMs ?? 30000);\n\n try {\n const url = new URL(route, options.baseUrl).toString();\n const res = await fetchImpl(url, {\n method: 'GET',\n headers: { Accept: 'text/html', ...options.headers },\n signal: ctrl.signal,\n });\n const html = await res.text();\n const outputPath = await maybeWriteStatic(options, route, html, mode);\n\n return {\n route,\n status: res.status,\n bytes: Buffer.byteLength(html),\n outputPath,\n durationMs: Date.now() - start,\n };\n } catch (err) {\n return {\n route,\n status: null,\n bytes: 0,\n error: (err as Error).message,\n durationMs: Date.now() - start,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Run the prerender pipeline. Programmatic entry-point for the\n * `bin/inertia-prerender` CLI.\n *\n * Two output modes (which can be combined via `\"both\"`):\n *\n * - **`warmup`** (default) — fetches each route, discards the body. Use to\n * populate the SSR view cache after a deploy so the first real visitor\n * doesn't pay the SSR latency cost.\n * - **`static`** — fetches each route and writes the response body to\n * `<outDir>/<route>/index.html`. Serve those files directly via\n * nginx/CDN, bypassing Express entirely.\n *\n * Bounded-concurrency: `concurrency` workers consume routes from a shared\n * counter; the returned `results` preserve input order. Failures on\n * individual routes never halt the run — the per-route summary lets a CI\n * step choose to fail-loud.\n *\n * @param options Pipeline configuration — see {@link PrerenderOptions}.\n */\nexport async function prerender(options: PrerenderOptions): Promise<PrerenderSummary> {\n const concurrency = Math.max(1, options.concurrency ?? 4);\n const results: PrerenderRouteResult[] = new Array(options.routes.length);\n let next = 0;\n\n /**\n * Claim the next route index and process it, then recurse. Recursion\n * (instead of a loop) avoids both `while`-spin and `no-await-in-loop`.\n */\n async function worker(): Promise<void> {\n const i = next;\n next += 1;\n\n if (i >= options.routes.length) {\n return;\n }\n\n const route = options.routes[i] ?? '';\n results[i] = await runOne(options, route);\n await worker();\n }\n\n await Promise.all(Array.from({ length: Math.min(concurrency, options.routes.length) }, async () => worker()));\n\n const ok = results.filter((r) => r && r.status !== null && r.status < 400 && !r.error).length;\n const failed = results.length - ok;\n\n return {\n total: results.length, ok, failed, results,\n };\n}\n","import type { SsrRenderer, SsrResult } from './types.js';\nimport type { InertiaPage } from '../core/index.js';\n\n/**\n * Inputs to {@link createInertiaSsrFetcher}.\n */\nexport interface InertiaSsrFetcherOptions {\n /** SSR endpoint URL — typically your internal LB DNS, e.g. `http://ssr.internal/render`. */\n url: string;\n /** Per-attempt timeout in milliseconds. Default 5000. */\n timeoutMs?: number;\n /** Number of retry attempts after the first failure. Default 2 (so up to 3 total tries). */\n retries?: number;\n /** Initial backoff in ms; doubles per retry. Default 100. */\n retryBaseMs?: number;\n /**\n * Circuit breaker: open after this many consecutive failures.\n * Set to `0` (or `Infinity`) to disable. Default 5.\n */\n breakerThreshold?: number;\n /** How long the breaker stays open before allowing one probe. Default 30000. */\n breakerCooldownMs?: number;\n /** Failure mode. Default `\"client\"`. */\n fallback?: 'client' | 'throw';\n /** Override fetch implementation (testing). */\n fetch?: typeof fetch;\n /** Extra headers sent with each render request. */\n headers?: Record<string, string>;\n}\n\nconst EMPTY_RESULT: SsrResult = { body: '', head: '' };\n\ninterface BreakerState {\n failures: number;\n openedAt: number | null;\n}\n\n/**\n * Resolve after the given number of milliseconds.\n *\n * @param ms Delay in milliseconds.\n */\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\n/**\n * Build an {@link SsrRenderer} that POSTs the page object to a separate Node\n * SSR process (or load-balanced pool) and returns the rendered\n * `{ head, body }`.\n *\n * Matches the wire protocol used by `@inertiajs/{vue3,react,svelte}/server`\n * `createServer()`: `POST <url>` with `application/json` body containing the\n * page object, response is `{ head: string[], body: string }`.\n *\n * Adds production hardening on top of the bare protocol:\n * - Per-attempt timeout (default 5000ms) via `AbortController`.\n * - Recursive retry with exponential backoff (`retryBaseMs * 2^attempt`).\n * - Circuit breaker — opens after `breakerThreshold` consecutive failed\n * *requests* (not attempts); half-opens after `breakerCooldownMs` so a\n * probe request can re-close it on success.\n * - `fallback`: `\"client\"` returns an empty SSR result so the client\n * renders alone; `\"throw\"` rethrows so Express's error handler can react.\n *\n * @param options Fetcher configuration — see {@link InertiaSsrFetcherOptions}.\n */\nexport function createInertiaSsrFetcher(options: InertiaSsrFetcherOptions): SsrRenderer {\n const fetchImpl = options.fetch ?? fetch;\n const timeoutMs = options.timeoutMs ?? 5000;\n const retries = options.retries ?? 2;\n const retryBaseMs = options.retryBaseMs ?? 100;\n const breakerThreshold = options.breakerThreshold ?? 5;\n const breakerCooldownMs = options.breakerCooldownMs ?? 30000;\n const fallback = options.fallback ?? 'client';\n const breakerEnabled = breakerThreshold > 0 && Number.isFinite(breakerThreshold);\n\n const state: BreakerState = { failures: 0, openedAt: null };\n\n /**\n * Returns `true` while the breaker is open and within its cooldown window;\n * a return of `false` after a previous failure means we are half-open and\n * the caller may attempt a probe request.\n */\n function breakerOpen(): boolean {\n if (!breakerEnabled || state.openedAt === null) {\n return false;\n }\n\n if (Date.now() - state.openedAt >= breakerCooldownMs) {\n // Half-open: let the next request probe.\n return false;\n }\n\n return true;\n }\n\n /**\n * Reset the breaker after a successful render.\n */\n function recordSuccess(): void {\n state.failures = 0;\n state.openedAt = null;\n }\n\n /**\n * Increment the failure counter; open the breaker when the threshold is hit.\n */\n function recordFailure(): void {\n state.failures += 1;\n\n if (breakerEnabled && state.failures >= breakerThreshold) {\n state.openedAt = Date.now();\n }\n }\n\n /**\n * Fire one SSR request — the unit the retry loop wraps. Throws on\n * timeout, network error, or non-2xx response.\n *\n * @param page Page object to send to the SSR server.\n */\n async function attempt(page: InertiaPage): Promise<SsrResult> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const res = await fetchImpl(options.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...options.headers,\n },\n body: JSON.stringify(page),\n signal: controller.signal,\n });\n\n if (!res.ok) {\n throw new Error(`Inertia SSR request failed: ${res.status} ${res.statusText}`);\n }\n\n const json = (await res.json()) as Partial<SsrResult>;\n\n return {\n body: json.body ?? '',\n head: json.head,\n bodyIsFullRoot: json.bodyIsFullRoot,\n };\n } finally {\n clearTimeout(timer);\n }\n }\n\n return async ({ page }: { page: InertiaPage }): Promise<SsrResult> => {\n if (breakerOpen()) {\n if (fallback === 'throw') {\n throw new Error('Inertia SSR circuit breaker is open');\n }\n\n return EMPTY_RESULT;\n }\n\n /**\n * Run one render attempt, recursing with exponential backoff until the\n * retry budget is spent. Records breaker success on the first win.\n *\n * @param remaining Retry attempts left before the error propagates.\n */\n const attemptWithRetry = async (remaining: number): Promise<SsrResult> => {\n try {\n const result = await attempt(page);\n recordSuccess();\n\n return result;\n } catch (err) {\n if (remaining <= 0) {\n throw err;\n }\n\n await sleep(retryBaseMs * 2 ** (retries - remaining));\n\n return attemptWithRetry(remaining - 1);\n }\n };\n\n try {\n return await attemptWithRetry(retries);\n } catch (lastError) {\n recordFailure();\n\n if (fallback === 'throw') {\n throw lastError;\n }\n\n return EMPTY_RESULT;\n }\n };\n}\n","import { createHash } from 'node:crypto';\nimport { readFile, stat } from 'node:fs/promises';\n\n/**\n * Inputs to {@link viteManifestVersion}.\n */\nexport interface ViteManifestVersionOptions {\n /** Path to `manifest.json`. */\n manifestPath: string;\n /**\n * If `true` (default), the manifest is re-read on every call when its\n * mtime has advanced. Set to `false` to read once on boot — slightly\n * faster but requires a process restart to pick up new builds.\n */\n watchMtime?: boolean;\n}\n\n/**\n * Inputs to {@link viteManifestAssets}.\n */\nexport interface ViteManifestAssetsOptions {\n /** Path to `manifest.json`. */\n manifestPath: string;\n /**\n * Manifest key for the entry to resolve. Typically the source path of\n * the client entry, e.g. `src/app.ts` or `src/main.tsx`.\n */\n entry: string;\n /**\n * If `true` (default), the manifest is re-read on every call when its\n * mtime has advanced. Set to `false` to read once on boot.\n */\n watchMtime?: boolean;\n}\n\n/**\n * Resolved asset URLs for a single Vite manifest entry. URLs are web paths\n * with a leading `/`, ready to drop into the HTML shell.\n */\nexport interface ViteManifestAssets {\n /** First CSS file declared by the entry, or `null` when absent. */\n css: string | null;\n /** Compiled JS file for the entry, or `null` when absent. */\n js: string | null;\n}\n\ninterface CacheEntry {\n mtimeMs: number;\n version: string;\n}\n\ninterface AssetsCacheEntry {\n mtimeMs: number;\n assets: ViteManifestAssets;\n}\n\nconst EMPTY_ASSETS: ViteManifestAssets = { css: null, js: null };\n\n/**\n * Build a stable asset version from a Vite-generated `manifest.json`.\n *\n * Compatible with both `@inertiajs/vite-plugin` output and the standard\n * `--manifest` option from `vite build`. The returned function is suitable\n * to pass directly as the `version` option of the Inertia middleware.\n *\n * Strategy:\n * - Read the manifest file.\n * - Hash a canonicalized list of `(key -> file)` entries (only the `file`\n * field per entry, so unrelated manifest metadata never perturbs the hash).\n * - Cache the result; re-read only after the file's mtime changes (when\n * `watchMtime` is true) or never re-read (when false).\n * - Returns `null` when the manifest file is absent or unparseable.\n *\n * A new manifest (different hashed output paths) yields a new version, which\n * the middleware embeds in the SSR cache key — so a deploy atomically\n * invalidates every existing cache entry.\n *\n * @param options Path to the manifest plus the mtime-watch flag.\n */\nexport function viteManifestVersion(options: ViteManifestVersionOptions): () => Promise<string | null> {\n const watchMtime = options.watchMtime ?? true;\n let cached: CacheEntry | null = null;\n\n /**\n * Resolve to the current version string, or `null` when the manifest can't\n * be read or parsed. Hits the in-memory cache when the mtime is unchanged.\n */\n async function read(): Promise<string | null> {\n let mtimeMs: number;\n\n try {\n const s = await stat(options.manifestPath);\n mtimeMs = s.mtimeMs;\n } catch {\n return null;\n }\n\n if (cached && (!watchMtime || cached.mtimeMs === mtimeMs)) {\n return cached.version;\n }\n\n let raw: string;\n\n try {\n raw = await readFile(options.manifestPath, 'utf8');\n } catch {\n return null;\n }\n\n let parsed: Record<string, unknown>;\n\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n\n // Canonical input: keys sorted, value extracts the `file` property when\n // the entry is a Vite manifest object (so unrelated metadata doesn't\n // perturb the hash).\n const lines: string[] = Object.keys(parsed)\n .sort()\n .map((key) => {\n const entry = parsed[key];\n\n if (entry && typeof entry === 'object' && 'file' in entry) {\n return `${key}=${(entry as { file: string }).file}`;\n }\n\n return `${key}=${JSON.stringify(entry)}`;\n });\n\n const version = createHash('sha256').update(lines.join('\\n')).digest('hex').slice(0, 16);\n\n cached = { mtimeMs, version };\n\n return version;\n }\n\n return read;\n}\n\n/**\n * Web-path normalize a manifest-relative file string. Adds a leading `/`\n * and collapses any pre-existing leading slashes so the result is always\n * a single-rooted absolute web path.\n *\n * @param file Manifest-relative file string.\n */\nfunction toWebPath(file: string): string {\n return `/${file.replace(/^\\/+/, '')}`;\n}\n\n/**\n * Resolve the asset URLs (`css`, `js`) for a single Vite manifest entry.\n *\n * Reads the manifest produced by `vite build --manifest` (or\n * `@inertiajs/vite-plugin`), locates the entry by manifest key, and\n * returns its compiled JS file plus the first declared CSS file as web\n * paths suitable to drop into the HTML shell's `<link>` and `<script>`\n * tags. Caches by mtime the same way as {@link viteManifestVersion}.\n *\n * Returns `{ css: null, js: null }` when the manifest is missing,\n * unparseable, or the entry key is absent — the root view stays\n * renderable while assets are still being built.\n *\n * @param options Path to the manifest, entry key, and the mtime-watch flag.\n */\nexport function viteManifestAssets(\n options: ViteManifestAssetsOptions,\n): () => Promise<ViteManifestAssets> {\n const watchMtime = options.watchMtime ?? true;\n let cached: AssetsCacheEntry | null = null;\n\n /**\n * Resolve to the current asset URLs, or `{ css: null, js: null }` when\n * the manifest can't be read, parsed, or the entry is missing.\n */\n async function read(): Promise<ViteManifestAssets> {\n let mtimeMs: number;\n\n try {\n const s = await stat(options.manifestPath);\n mtimeMs = s.mtimeMs;\n } catch {\n return EMPTY_ASSETS;\n }\n\n if (cached && (!watchMtime || cached.mtimeMs === mtimeMs)) {\n return cached.assets;\n }\n\n let raw: string;\n\n try {\n raw = await readFile(options.manifestPath, 'utf8');\n } catch {\n return EMPTY_ASSETS;\n }\n\n let parsed: Record<string, unknown>;\n\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return EMPTY_ASSETS;\n }\n\n const entry = parsed[options.entry];\n\n if (!entry || typeof entry !== 'object') {\n return EMPTY_ASSETS;\n }\n\n const { file, css: cssList } = entry as { file?: unknown; css?: unknown };\n const firstCss = Array.isArray(cssList) && typeof cssList[0] === 'string' ? cssList[0] : null;\n\n const assets: ViteManifestAssets = {\n css: firstCss !== null ? toWebPath(firstCss) : null,\n js: typeof file === 'string' ? toWebPath(file) : null,\n };\n\n cached = { mtimeMs, assets };\n\n return assets;\n }\n\n return read;\n}\n","/**\n * Express adapter for Inertia.js v3.\n *\n * Default entry point of `@blacklizard/inertia-express`. The framework-agnostic\n * core is available at `@blacklizard/inertia-express/core`.\n */\n\nimport { inertia } from './middleware.js';\n\nexport type {\n AlwaysProp,\n ComputeCacheKeyInput,\n DeepMergeProp,\n DeferredProp,\n InertiaPage,\n InertiaRequestInfo,\n LazyProp,\n MergeProp,\n OptionalProp,\n PageProps,\n SharedPropsInput,\n SsrCacheEntry,\n SsrCacheStore,\n ValidationErrors,\n VersionResolver,\n} from '../core/index.js';\n// Re-export the most commonly used core helpers so users only need a single\n// import path for typical apps.\nexport {\n always,\n canonicalStringify,\n computeSsrCacheKey,\n deepMerge,\n defer,\n INERTIA_HEADERS,\n INERTIA_LOCATION_STATUS,\n INERTIA_REDIRECT_STATUS,\n lazy,\n merge,\n optional,\n} from '../core/index.js';\nexport type { ErrorPageInput } from '../core/index.js';\nexport { renderErrorPage } from '../core/index.js';\nexport type { EdgeCacheDecision, EdgeCachePolicy } from './edge-cache.js';\nexport { applyEdgeCache } from './edge-cache.js';\nexport type { MemoryCacheStoreOptions } from './memory-store.js';\nexport { createMemoryCacheStore } from './memory-store.js';\nexport { inertia };\nexport default inertia;\nexport type {\n PrerenderOptions,\n PrerenderRouteResult,\n PrerenderSummary,\n} from './prerender.js';\nexport { prerender } from './prerender.js';\nexport {\n encodePageScript,\n sendInertiaLocation,\n sendInertiaResponse,\n sendVersionMismatch,\n} from './response.js';\nexport type { InertiaSsrFetcherOptions } from './ssr-fetcher.js';\nexport { createInertiaSsrFetcher } from './ssr-fetcher.js';\nexport type {\n InertiaCacheOptions,\n InertiaMiddlewareOptions,\n InertiaResponseOptions,\n RootViewInput,\n RootViewRenderer,\n SsrRenderer,\n SsrRendererInput,\n SsrResult,\n} from './types.js';\nexport type {\n ViteManifestAssets,\n ViteManifestAssetsOptions,\n ViteManifestVersionOptions,\n} from './vite-manifest.js';\nexport { viteManifestAssets, viteManifestVersion } from './vite-manifest.js';\n"],"mappings":"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"}
|