@blacklizard/inertia-express 1.2.0 → 3.0.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.
@@ -1,646 +1,2 @@
1
- import { createHash } from "node:crypto";
2
- //#region src/core/cache.ts
3
- /**
4
- * Recursively normalize a value so equal-by-value inputs serialize identically:
5
- * object keys are sorted, `undefined` properties are dropped, and non-finite
6
- * numbers collapse to `null`.
7
- *
8
- * @param value Arbitrary JSON-shaped input.
9
- */
10
- function canonicalize(value) {
11
- if (value === null) return null;
12
- if (typeof value !== "object") {
13
- if (typeof value === "number" && !Number.isFinite(value)) return null;
14
- return value;
15
- }
16
- if (Array.isArray(value)) return value.map(canonicalize);
17
- const obj = value;
18
- return Object.fromEntries(Object.keys(obj).sort().filter((key) => obj[key] !== void 0).map((key) => [key, canonicalize(obj[key])]));
19
- }
20
- /**
21
- * Stable JSON encoding: keys sorted recursively, undefined dropped, NaN/Inf
22
- * normalized. Two equal-by-value objects always produce the same string,
23
- * so they hash to the same cache key.
24
- *
25
- * @param value Arbitrary JSON-shaped input.
26
- */
27
- function canonicalStringify(value) {
28
- return JSON.stringify(canonicalize(value));
29
- }
30
- /**
31
- * Build the cache key for an SSR cache entry.
32
- *
33
- * Shape: `${prefix}:${version}:${component}:${sha256(props[+discriminator])}`
34
- *
35
- * Including `version` in the key namespace means a deploy that bumps the
36
- * asset version atomically retires every old key — no flush, no race.
37
- *
38
- * @param input Prefix, version, component, resolved props, optional discriminator.
39
- */
40
- function computeSsrCacheKey(input) {
41
- const versionPart = input.version ?? "_";
42
- const propsHash = createHash("sha256").update(canonicalStringify(input.props)).update(input.discriminator ?? "").digest("hex").slice(0, 32);
43
- return `${input.prefix}:${versionPart}:${input.component}:${propsHash}`;
44
- }
45
- //#endregion
46
- //#region src/core/errors.ts
47
- /**
48
- * Apply error-bag scoping to a flat errors object, matching the official
49
- * Inertia behavior. When `bag` is set, errors are namespaced under
50
- * `props.errors[bag]`. When `bag` is null, errors live at `props.errors`.
51
- *
52
- * @param errors Flat field-to-message map.
53
- * @param bag Optional error-bag name (e.g. `"createUser"`); null for top-level.
54
- */
55
- function scopeErrors(errors, bag) {
56
- if (bag) return { errors: { [bag]: errors } };
57
- return { errors };
58
- }
59
- //#endregion
60
- //#region src/core/headers.ts
61
- /**
62
- * Canonical Inertia v3 protocol header names.
63
- *
64
- * The Inertia client sends these headers on every visit. The server uses
65
- * them to decide between a full HTML response and a JSON page response,
66
- * and to scope partial reloads.
67
- */
68
- const INERTIA_HEADERS = {
69
- inertia: "x-inertia",
70
- version: "x-inertia-version",
71
- partialComponent: "x-inertia-partial-component",
72
- partialData: "x-inertia-partial-data",
73
- partialExcept: "x-inertia-partial-except",
74
- errorBag: "x-inertia-error-bag",
75
- reset: "x-inertia-reset",
76
- location: "x-inertia-location"
77
- };
78
- /**
79
- * Read a header value as a single string, looking up the lowercase variant as
80
- * a fallback. Returns the first entry of an array value and `null` when the
81
- * header is absent.
82
- *
83
- * @param headers Raw header record from the HTTP layer.
84
- * @param name Header name to read.
85
- */
86
- function readHeader(headers, name) {
87
- const value = headers[name] ?? headers[name.toLowerCase()];
88
- if (value === void 0 || value === null) return null;
89
- if (Array.isArray(value)) return value[0] ?? null;
90
- return value;
91
- }
92
- /**
93
- * Split a comma-separated header value into a trimmed, non-empty string list.
94
- * Returns `null` when the input is null or yields no entries.
95
- *
96
- * @param value Raw header value, or null when absent.
97
- */
98
- function parseList(value) {
99
- if (value === null) return null;
100
- const parts = value.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
101
- return parts.length > 0 ? parts : null;
102
- }
103
- /**
104
- * Parse a request's headers into a normalized {@link InertiaRequestInfo}.
105
- *
106
- * @param input Headers, HTTP method, and request URL. Method is uppercased in
107
- * the returned object; the URL is passed through unchanged.
108
- */
109
- function parseInertiaRequest(input) {
110
- const { headers } = input;
111
- const inertia = readHeader(headers, INERTIA_HEADERS.inertia);
112
- const version = readHeader(headers, INERTIA_HEADERS.version);
113
- const partialComponent = readHeader(headers, INERTIA_HEADERS.partialComponent);
114
- const partialOnly = parseList(readHeader(headers, INERTIA_HEADERS.partialData));
115
- const partialExcept = parseList(readHeader(headers, INERTIA_HEADERS.partialExcept));
116
- const errorBag = readHeader(headers, INERTIA_HEADERS.errorBag);
117
- const resetKeys = parseList(readHeader(headers, INERTIA_HEADERS.reset));
118
- return {
119
- isInertia: inertia === "true" || inertia === "1",
120
- version,
121
- partialComponent,
122
- partialOnly,
123
- partialExcept,
124
- errorBag,
125
- resetKeys,
126
- method: input.method.toUpperCase(),
127
- url: input.url
128
- };
129
- }
130
- //#endregion
131
- //#region src/core/html.ts
132
- const HTML_ESCAPES = {
133
- "&": "&",
134
- "<": "&lt;",
135
- ">": "&gt;",
136
- "\"": "&quot;",
137
- "'": "&#39;"
138
- };
139
- /**
140
- * Escape characters that would break out of a double-quoted HTML attribute.
141
- *
142
- * @param value Raw attribute value.
143
- */
144
- function escapeHtmlAttribute(value) {
145
- return value.replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch] ?? ch);
146
- }
147
- /**
148
- * Encode an Inertia page object for embedding inside a
149
- * `<script type="application/json" data-page="...">` tag — the Inertia v3
150
- * initial-page transport that the client reads via `getInitialPageFromDOM`.
151
- *
152
- * Every `<` is replaced with its JSON unicode escape so the payload cannot
153
- * terminate the surrounding `<\/script>` element or open an HTML comment. The
154
- * escape is transparent — JSON.parse decodes it back to `<`.
155
- *
156
- * @param page Resolved Inertia page object.
157
- */
158
- function encodePageScript(page) {
159
- return JSON.stringify(page).replace(/</g, "\\u003C");
160
- }
161
- /**
162
- * Minimal default HTML shell for use when no custom root view is provided.
163
- * Real apps should plug in a template engine (pug, ejs, react-dom/server,
164
- * etc.) and embed `encodePageScript(page)` themselves.
165
- *
166
- * The initial page object is emitted as a
167
- * `<script type="application/json" data-page="...">` tag, the Inertia v3
168
- * transport that the client reads on boot.
169
- *
170
- * @param input Page object plus optional title/head/rootId/scriptSrc/SSR markup
171
- * — see {@link DefaultRootViewInput}.
172
- */
173
- function renderDefaultHtml(input) {
174
- const rootId = input.rootId ?? "app";
175
- const title = input.title ?? "Inertia App";
176
- const head = input.head ?? "";
177
- const script = input.scriptSrc ? `<script type="module" src="${escapeHtmlAttribute(input.scriptSrc)}"><\/script>` : "";
178
- let rootMarkup;
179
- if (input.ssrFull) rootMarkup = input.ssrFull;
180
- else {
181
- const idAttr = escapeHtmlAttribute(rootId);
182
- rootMarkup = `<div id="${idAttr}">${input.ssrBody ?? ""}</div>\n ${`<script data-page="${idAttr}" type="application/json">${encodePageScript(input.page)}<\/script>`}`;
183
- }
184
- return `<!DOCTYPE html>
185
- <html>
186
- <head>
187
- <meta charset="utf-8" />
188
- <meta name="viewport" content="width=device-width, initial-scale=1" />
189
- <title>${escapeHtmlAttribute(title)}</title>
190
- ${head}
191
- </head>
192
- <body>
193
- ${rootMarkup}
194
- ${script}
195
- </body>
196
- </html>`;
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
- }
247
- //#endregion
248
- //#region src/core/page.ts
249
- /**
250
- * Build a normalized Inertia v3 page object. `clearHistory` and `encryptHistory`
251
- * default to `false`. Empty/absent `deferredProps` / `mergeProps` /
252
- * `deepMergeProps` / `matchPropsOn` are dropped so the JSON over the wire stays
253
- * compact.
254
- *
255
- * @param input Required page fields plus the optional v3 extras.
256
- */
257
- function createPage(input) {
258
- const page = {
259
- component: input.component,
260
- props: input.props,
261
- url: input.url,
262
- version: input.version,
263
- clearHistory: input.clearHistory ?? false,
264
- encryptHistory: input.encryptHistory ?? false
265
- };
266
- if (input.deferredProps && Object.keys(input.deferredProps).length > 0) page.deferredProps = input.deferredProps;
267
- if (input.mergeProps && input.mergeProps.length > 0) page.mergeProps = input.mergeProps;
268
- if (input.deepMergeProps && input.deepMergeProps.length > 0) page.deepMergeProps = input.deepMergeProps;
269
- if (input.matchPropsOn && input.matchPropsOn.length > 0) page.matchPropsOn = input.matchPropsOn;
270
- return page;
271
- }
272
- //#endregion
273
- //#region src/core/props.ts
274
- const LAZY = "__inertia_lazy";
275
- const OPTIONAL = "__inertia_optional";
276
- const DEFERRED = "__inertia_deferred";
277
- const ALWAYS = "__inertia_always";
278
- const MERGE = "__inertia_merge";
279
- const DEEP_MERGE = "__inertia_deep_merge";
280
- const MATCH_ON = "__inertia_match_on";
281
- /**
282
- * Attach the (non-enumerable) `MATCH_ON` symbol-key to a merge/deep-merge
283
- * prop value so `resolveProps` can emit the dotted `prop.field` paths.
284
- *
285
- * @param value Object being tagged (mutated in place).
286
- * @param matchOn Single field name or list; no-op when undefined.
287
- */
288
- function tagMatchOn(value, matchOn) {
289
- if (matchOn === void 0) return;
290
- Object.defineProperty(value, MATCH_ON, {
291
- value: Array.isArray(matchOn) ? matchOn : [matchOn],
292
- enumerable: false
293
- });
294
- }
295
- /**
296
- * Wrap a value-producing function as a "lazy" prop.
297
- *
298
- * Lazy props are evaluated on every visit (full or partial) when included.
299
- * They are NOT skipped on the initial visit — use {@link optional} for that.
300
- *
301
- * @param fn Producer called (and awaited) when the prop is included.
302
- */
303
- function lazy(fn) {
304
- const wrapped = (async () => fn());
305
- Object.defineProperty(wrapped, LAZY, {
306
- value: true,
307
- enumerable: false
308
- });
309
- return wrapped;
310
- }
311
- /**
312
- * Wrap a value-producing function as an "optional" prop.
313
- *
314
- * Optional props are omitted from the initial response and only included
315
- * when explicitly requested via `X-Inertia-Partial-Data`.
316
- *
317
- * @param fn Producer called (and awaited) on partial reloads that request the key.
318
- */
319
- function optional(fn) {
320
- const wrapped = (async () => fn());
321
- Object.defineProperty(wrapped, OPTIONAL, {
322
- value: true,
323
- enumerable: false
324
- });
325
- return wrapped;
326
- }
327
- /**
328
- * Wrap a value-producing function as a "deferred" prop.
329
- *
330
- * Deferred props are omitted from the initial visit. The client receives
331
- * the list of deferred prop keys (grouped by `group`) in the page object
332
- * and triggers an automatic partial reload to fetch them.
333
- *
334
- * @param fn Producer called (and awaited) on the deferred follow-up reload.
335
- * @param group Load-group name; keys in the same group are fetched in one round-trip.
336
- */
337
- function defer(fn, group = "default") {
338
- const wrapped = (async () => fn());
339
- Object.defineProperty(wrapped, DEFERRED, {
340
- value: true,
341
- enumerable: false
342
- });
343
- Object.defineProperty(wrapped, "group", {
344
- value: group,
345
- enumerable: false
346
- });
347
- return wrapped;
348
- }
349
- /**
350
- * Wrap a value-producing function as an "always" prop.
351
- *
352
- * Always props are included on every response — full visits and partial
353
- * reloads alike — regardless of `X-Inertia-Partial-Data` /
354
- * `X-Inertia-Partial-Except` filtering.
355
- *
356
- * @param fn Producer called (and awaited) on every visit.
357
- */
358
- function always(fn) {
359
- const wrapped = (async () => fn());
360
- Object.defineProperty(wrapped, ALWAYS, {
361
- value: true,
362
- enumerable: false
363
- });
364
- return wrapped;
365
- }
366
- /**
367
- * Mark a value as a "merge" prop. The Inertia client will append the new
368
- * value to the existing prop (typically used for paginated arrays).
369
- *
370
- * Pass `matchOn` (a field name or list of field names) to have the client
371
- * dedupe array items on that field instead of blindly appending.
372
- *
373
- * @param value Object or array to tag (mutated in place via non-enumerable properties).
374
- * @param matchOn Optional field name(s) to dedupe on.
375
- */
376
- function merge(value, matchOn) {
377
- const tagged = value;
378
- Object.defineProperty(tagged, MERGE, {
379
- value: true,
380
- enumerable: false
381
- });
382
- tagMatchOn(tagged, matchOn);
383
- return tagged;
384
- }
385
- /**
386
- * Mark a value as a "deep merge" prop. The Inertia client recursively merges
387
- * the new value into the existing prop rather than shallow-appending.
388
- *
389
- * Pass `matchOn` (a field name or list of field names) to have the client
390
- * dedupe array items on that field.
391
- *
392
- * @param value Object or array to tag (mutated in place via non-enumerable properties).
393
- * @param matchOn Optional field name(s) to dedupe on.
394
- */
395
- function deepMerge(value, matchOn) {
396
- const tagged = value;
397
- Object.defineProperty(tagged, DEEP_MERGE, {
398
- value: true,
399
- enumerable: false
400
- });
401
- tagMatchOn(tagged, matchOn);
402
- return tagged;
403
- }
404
- /**
405
- * Type guard: `true` when `v` is a {@link lazy} prop wrapper.
406
- *
407
- * @param v Value to test.
408
- */
409
- function isLazy(v) {
410
- return typeof v === "function" && v[LAZY] === true;
411
- }
412
- /**
413
- * Type guard: `true` when `v` is an {@link optional} prop wrapper.
414
- *
415
- * @param v Value to test.
416
- */
417
- function isOptional(v) {
418
- return typeof v === "function" && v[OPTIONAL] === true;
419
- }
420
- /**
421
- * Type guard: `true` when `v` is a {@link defer} prop wrapper.
422
- *
423
- * @param v Value to test.
424
- */
425
- function isDeferred(v) {
426
- return typeof v === "function" && v[DEFERRED] === true;
427
- }
428
- /**
429
- * Type guard: `true` when `v` is an {@link always} prop wrapper.
430
- *
431
- * @param v Value to test.
432
- */
433
- function isAlways(v) {
434
- return typeof v === "function" && v[ALWAYS] === true;
435
- }
436
- /**
437
- * Type guard: `true` when `v` is a {@link merge} prop wrapper.
438
- *
439
- * @param v Value to test.
440
- */
441
- function isMerge(v) {
442
- return v !== null && typeof v === "object" && v[MERGE] === true;
443
- }
444
- /**
445
- * Type guard: `true` when `v` is a {@link deepMerge} prop wrapper.
446
- *
447
- * @param v Value to test.
448
- */
449
- function isDeepMerge(v) {
450
- return v !== null && typeof v === "object" && v[DEEP_MERGE] === true;
451
- }
452
- /**
453
- * Match a prop key against a partial-reload key list. A pattern matches when
454
- * it equals the key, or when it is a dotted path into the key (`users.data`
455
- * matches the `users` prop). Mirrors the official adapter's partial filtering.
456
- *
457
- * @param patterns Key patterns from `X-Inertia-Partial-Data` / `-Partial-Except`.
458
- * @param key Prop key being tested.
459
- */
460
- function partialKeyMatches(patterns, key) {
461
- return patterns.some((pattern) => pattern === key || pattern.startsWith(`${key}.`));
462
- }
463
- /**
464
- * Read the non-enumerable `MATCH_ON` field-list off a tagged merge value,
465
- * returning an empty array when absent.
466
- *
467
- * @param v Possibly-tagged merge / deep-merge prop value.
468
- */
469
- function readMatchOn(v) {
470
- if (v === null || typeof v !== "object") return [];
471
- const fields = v[MATCH_ON];
472
- return Array.isArray(fields) ? fields : [];
473
- }
474
- /**
475
- * Combine shared + page props, then apply partial-reload filtering and
476
- * lazy/optional/deferred evaluation per the Inertia v3 protocol.
477
- *
478
- * Resolution order matches the official adapters:
479
- * 1. Merge shared and page props (page wins on collision).
480
- * 2. Determine if this is a partial reload for the same component.
481
- * 3. For non-partial visits: drop optional and deferred props; keep everything
482
- * else (lazy props are evaluated).
483
- * 4. For partial visits: keep only keys in `partialOnly` (if set), then drop
484
- * keys in `partialExcept`. Optional props are now eligible.
485
- * 5. Evaluate every remaining function-shaped prop
486
- * (lazy/optional/deferred/always) by calling it and awaiting the result.
487
- * Independent props are evaluated in parallel via `Promise.all`.
488
- * 6. Collect merge / deep-merge / match-on keys, excluding any key the client
489
- * listed in `X-Inertia-Reset` — those props are reset (replaced) rather
490
- * than appended.
491
- *
492
- * @param input Page + shared props, the component name, and parsed request info.
493
- */
494
- async function resolveProps(input) {
495
- const { props, shared, component, request } = input;
496
- const combined = {
497
- ...shared,
498
- ...props
499
- };
500
- const isPartial = request.isInertia && request.partialComponent !== null && request.partialComponent === component;
501
- const deferred = {};
502
- const mergeKeys = [];
503
- const deepMergeKeys = [];
504
- const matchOnKeys = [];
505
- const filtered = {};
506
- const isExcludedFromPartial = (key, value) => {
507
- if (!isPartial || isAlways(value)) return false;
508
- if (request.partialOnly !== null && !partialKeyMatches(request.partialOnly, key)) return true;
509
- return request.partialExcept !== null && partialKeyMatches(request.partialExcept, key);
510
- };
511
- Object.entries(combined).forEach(([key, value]) => {
512
- if (isDeferred(value) && !isPartial) {
513
- (deferred[value.group] ??= []).push(key);
514
- return;
515
- }
516
- if (isOptional(value) && !isPartial) return;
517
- if (isExcludedFromPartial(key, value)) return;
518
- filtered[key] = value;
519
- });
520
- const { resetKeys } = request;
521
- const evaluated = {};
522
- (await Promise.all(Object.entries(filtered).map(async ([key, value]) => {
523
- return [key, isLazy(value) || isOptional(value) || isDeferred(value) || isAlways(value) ? await value() : value];
524
- }))).forEach(([key, resolved]) => {
525
- evaluated[key] = resolved;
526
- if (resetKeys?.includes(key) ?? false) return;
527
- const merging = isMerge(resolved);
528
- const deepMerging = isDeepMerge(resolved);
529
- if (merging) mergeKeys.push(key);
530
- else if (deepMerging) deepMergeKeys.push(key);
531
- if (merging || deepMerging) readMatchOn(resolved).forEach((field) => {
532
- matchOnKeys.push(`${key}.${field}`);
533
- });
534
- });
535
- return {
536
- props: evaluated,
537
- deferred,
538
- merge: mergeKeys,
539
- deepMerge: deepMergeKeys,
540
- matchOn: matchOnKeys
541
- };
542
- }
543
- //#endregion
544
- //#region src/core/redirects.ts
545
- /**
546
- * Status code Inertia uses for "soft" redirects after PUT/PATCH/DELETE so the
547
- * follow-up GET is forced to also be a GET on every browser.
548
- */
549
- const INERTIA_REDIRECT_STATUS = 303;
550
- /**
551
- * Status code used for the `X-Inertia-Location` external redirect
552
- * mechanism. The client intercepts the 409 and performs a window.location
553
- * navigation rather than a fetch.
554
- */
555
- const INERTIA_LOCATION_STATUS = 409;
556
- /**
557
- * Decide which status code to use for a redirect produced by an Inertia
558
- * request. After a non-GET/HEAD form submission the response must be 303
559
- * so the client's follow-up request becomes a GET.
560
- *
561
- * @param method HTTP method of the inbound request (case-insensitive).
562
- * @param fallback Status code to use for GET/HEAD/POST. Defaults to 302.
563
- */
564
- function inertiaRedirectStatus(method, fallback = 302) {
565
- const m = method.toUpperCase();
566
- if (m === "PUT" || m === "PATCH" || m === "DELETE") return 303;
567
- return fallback;
568
- }
569
- //#endregion
570
- //#region src/core/render.ts
571
- /**
572
- * Resolve a `SharedPropsInput` to a plain props object. Returns `{}` for
573
- * undefined/null inputs or when the resolver returns nullish.
574
- *
575
- * @param shared Static props object, a (possibly async) function, or undefined.
576
- * @param req Request passed to the resolver when `shared` is a function.
577
- */
578
- async function evaluateShared(shared, req) {
579
- if (shared === void 0 || shared === null) return {};
580
- if (typeof shared === "function") return await shared(req) ?? {};
581
- return shared;
582
- }
583
- /**
584
- * Build the final {@link InertiaPage} object given a controller's component
585
- * + props, the request info, and configured shared props / version.
586
- *
587
- * Returned page is suitable for either:
588
- * - JSON serialization on `X-Inertia` requests, or
589
- * - embedding into the root HTML view via a `<script>` page tag.
590
- *
591
- * @param input Component, props, parsed request, shared resolver, version, and
592
- * optional `clearHistory` / `encryptHistory` flags.
593
- */
594
- async function buildPage(input) {
595
- const sharedProps = await evaluateShared(input.shared, input.req);
596
- const resolved = await resolveProps({
597
- props: input.props,
598
- shared: sharedProps,
599
- component: input.component,
600
- request: input.request
601
- });
602
- return createPage({
603
- component: input.component,
604
- props: resolved.props,
605
- url: input.request.url,
606
- version: input.version,
607
- clearHistory: input.options?.clearHistory,
608
- encryptHistory: input.options?.encryptHistory,
609
- deferredProps: resolved.deferred,
610
- mergeProps: resolved.merge,
611
- deepMergeProps: resolved.deepMerge,
612
- matchPropsOn: resolved.matchOn
613
- });
614
- }
615
- //#endregion
616
- //#region src/core/versioning.ts
617
- /**
618
- * Resolve a version value (string, async function, or null) against a request.
619
- *
620
- * Returns `null` when no version is configured — in which case asset version
621
- * mismatch detection is disabled.
622
- *
623
- * @param resolver Static version string, a (possibly async) function, or undefined.
624
- * @param req Request passed to the resolver when it is a function.
625
- */
626
- async function resolveVersion(resolver, req) {
627
- if (resolver === void 0 || resolver === null) return null;
628
- if (typeof resolver === "string") return resolver;
629
- return await resolver(req) ?? null;
630
- }
631
- /**
632
- * Returns true if the client's version (`X-Inertia-Version`) does not match
633
- * the server's current version. Mismatches on GET requests should trigger a
634
- * full reload via the `X-Inertia-Location` mechanism.
635
- *
636
- * @param serverVersion Currently configured server version; `null` disables checks.
637
- * @param clientVersion Value of the `X-Inertia-Version` header; `null` is treated as empty.
638
- */
639
- function isVersionMismatch(serverVersion, clientVersion) {
640
- if (serverVersion === null) return false;
641
- return serverVersion !== (clientVersion ?? "");
642
- }
643
- //#endregion
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 };
645
-
646
- //# sourceMappingURL=index.js.map
1
+ import { A as canonicalStringify, C as defer, D as optional, E as once, O as scroll, S as deepMerge, T as merge, _ as INERTIA_HEADERS, a as INERTIA_REDIRECT_STATUS, b as unpackDotProps, c as resolveProps, d as matchesOnly, f as pathIsSameOrDescendant, g as renderErrorPage, h as renderDefaultHtml, i as INERTIA_LOCATION_STATUS, j as computeSsrCacheKey, k as PropDescriptor, l as leadsToOnly, m as encodePageScript, n as resolveVersion, o as inertiaRedirectStatus, p as createPage, r as buildPage, s as PropsResolver, t as isVersionMismatch, u as matchesExcept, v as parseInertiaRequest, w as isPropDescriptor, x as always, y as scopeErrors } from "../core-ChQbBw-k.js";
2
+ export { INERTIA_HEADERS, INERTIA_LOCATION_STATUS, INERTIA_REDIRECT_STATUS, PropDescriptor, PropsResolver, always, buildPage, canonicalStringify, computeSsrCacheKey, createPage, deepMerge, defer, encodePageScript, inertiaRedirectStatus, isPropDescriptor, isVersionMismatch, leadsToOnly, matchesExcept, matchesOnly, merge, once, optional, parseInertiaRequest, pathIsSameOrDescendant, renderDefaultHtml, renderErrorPage, resolveProps, resolveVersion, scopeErrors, scroll, unpackDotProps };