@adonisjs/inertia 5.0.0-next.0 → 5.0.0-next.1

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.
@@ -0,0 +1,1770 @@
1
+ import { t as InertiaHeaders } from "./headers-B-5pLwyD.js";
2
+ import { t as debug_default } from "./debug-Ca0ekg3M.js";
3
+ import { createHash } from "node:crypto";
4
+ import string from "@poppinss/utils/string";
5
+ import { BaseSerializer } from "@adonisjs/core/transformers";
6
+ //#region \0rolldown/runtime.js
7
+ var __defProp = Object.defineProperty;
8
+ var __exportAll = (all, no_symbols) => {
9
+ let target = {};
10
+ for (var name in all) __defProp(target, name, {
11
+ get: all[name],
12
+ enumerable: true
13
+ });
14
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
15
+ return target;
16
+ };
17
+ //#endregion
18
+ //#region src/symbols.ts
19
+ var symbols_exports = /* @__PURE__ */ __exportAll({
20
+ ALWAYS_PROP: () => ALWAYS_PROP,
21
+ DEEP_MERGE: () => DEEP_MERGE,
22
+ DEFERRED_PROP: () => DEFERRED_PROP,
23
+ MERGE_MATCH_ON: () => MERGE_MATCH_ON,
24
+ MERGE_PREPEND: () => MERGE_PREPEND,
25
+ ONCE_PROP: () => ONCE_PROP,
26
+ OPTIONAL_PROP: () => OPTIONAL_PROP,
27
+ SCROLL_DEFERRED: () => SCROLL_DEFERRED,
28
+ SCROLL_PROP: () => SCROLL_PROP,
29
+ TO_BE_MERGED: () => TO_BE_MERGED
30
+ });
31
+ /**
32
+ * Symbol used to mark props that should always be included in responses.
33
+ * Props marked with this symbol cannot be filtered out during cherry-picking.
34
+ */
35
+ const ALWAYS_PROP = Symbol.for("ALWAYS_PROP");
36
+ /**
37
+ * Symbol used to mark props that are optional and only included when explicitly requested.
38
+ * These props are never included in standard visits and must be cherry-picked.
39
+ */
40
+ const OPTIONAL_PROP = Symbol.for("OPTIONAL_PROP");
41
+ /**
42
+ * Symbol used to mark props that should be merged with existing props on the client.
43
+ * Props marked with this symbol will be merged rather than replaced during updates.
44
+ */
45
+ const TO_BE_MERGED = Symbol.for("TO_BE_MERGED");
46
+ /**
47
+ * Symbol used to mark props that are deferred and computed lazily.
48
+ * These props are skipped in standard visits but communicated to the client for potential loading.
49
+ */
50
+ const DEFERRED_PROP = Symbol.for("DEFERRED_PROP");
51
+ /**
52
+ * Symbol used to indicate that a mergeable prop should use deep merging behavior.
53
+ * Deep merging recursively merges nested objects and arrays.
54
+ */
55
+ const DEEP_MERGE = Symbol.for("DEEP_MERGE");
56
+ /**
57
+ * Symbol carrying the direction of a shallow array merge. When `true` the client
58
+ * prepends the incoming items (`prependProps`); when `false` it appends them
59
+ * (`mergeProps`). Ignored for deep merges — the client deep-merge path has no
60
+ * direction.
61
+ */
62
+ const MERGE_PREPEND = Symbol.for("MERGE_PREPEND");
63
+ /**
64
+ * Symbol carrying the match path (relative to the prop) used for keyed merges.
65
+ * When set, the client dedupes/replaces array items by this field rather than
66
+ * concatenating. Emitted on the wire as `"<propPath>.<matchOn>"` in `matchPropsOn`.
67
+ */
68
+ const MERGE_MATCH_ON = Symbol.for("MERGE_MATCH_ON");
69
+ /**
70
+ * Symbol used to mark props that are remembered by the client across visits.
71
+ * The server skips re-resolving a once prop when the client reports it already
72
+ * holds the value, and always emits the prop's caching metadata.
73
+ */
74
+ const ONCE_PROP = Symbol.for("ONCE_PROP");
75
+ /**
76
+ * Symbol used to mark an infinite-scroll prop. Scroll props layer pagination
77
+ * metadata (emitted under the page object's `scrollProps`) on top of the keyed
78
+ * and directional merge primitive, so the client can continuously load pages as
79
+ * the user scrolls.
80
+ */
81
+ const SCROLL_PROP = Symbol.for("SCROLL_PROP");
82
+ /**
83
+ * Type-only discriminant carrying whether a scroll prop has been deferred via
84
+ * `.deferred()`. A deferred scroll prop is absent on the initial load, so this
85
+ * flag lets the type system treat it as optional — rejecting it on a required
86
+ * client prop, exactly like `defer()`. It is never set at runtime.
87
+ */
88
+ const SCROLL_DEFERRED = Symbol.for("SCROLL_DEFERRED");
89
+ //#endregion
90
+ //#region src/props.ts
91
+ var InertiaSerializer = class extends BaseSerializer {
92
+ wrap = void 0;
93
+ definePaginationMetaData(metaData) {
94
+ return metaData;
95
+ }
96
+ };
97
+ const inertiaSerializer = new InertiaSerializer();
98
+ /**
99
+ * Type guard to check if a value is a plain object
100
+ *
101
+ * @param value - The value to check
102
+ * @returns True if the value is a plain object (not null, not array)
103
+ */
104
+ function isObject(value) {
105
+ return value !== null && typeof value === "object" && !Array.isArray(value);
106
+ }
107
+ /**
108
+ * Creates a deferred prop that is never included in standard visits but must be shared with
109
+ * the client during standard visits. Can be explicitly requested and supports merging.
110
+ *
111
+ * Deferred props are useful for expensive computations that should only be loaded when
112
+ * specifically requested by the client.
113
+ *
114
+ * @param fn - Function that computes the prop value when requested
115
+ * @param options - A group name string, or `{ group, rescue }`. Pass
116
+ * `{ rescue: true }` to catch resolution errors, omit the prop, and report its
117
+ * path via `rescuedProps` so the client can render the `<Deferred>` rescue slot.
118
+ * @returns A deferred prop object with compute and merge capabilities
119
+ *
120
+ * @example
121
+ * ```javascript
122
+ * // Create a deferred prop for expensive user statistics
123
+ * const userStats = defer(() => {
124
+ * return calculateExpensiveUserStats(userId)
125
+ * })
126
+ *
127
+ * // Use in page props
128
+ * return inertia.render('dashboard', {
129
+ * user: user,
130
+ * stats: userStats // Only loaded when explicitly requested
131
+ * })
132
+ *
133
+ * // Rescue resolution failures instead of erroring the whole reload
134
+ * const permissions = defer(() => Permission.all(), { rescue: true })
135
+ * ```
136
+ */
137
+ function defer(fn, groupOrOptions = {}) {
138
+ const { group = "default", rescue = false } = typeof groupOrOptions === "string" ? { group: groupOrOptions } : groupOrOptions;
139
+ return {
140
+ group,
141
+ rescue,
142
+ compute: fn,
143
+ merge() {
144
+ return merge(this);
145
+ },
146
+ deepMerge() {
147
+ return deepMerge(this);
148
+ },
149
+ once(options) {
150
+ return once(this, options);
151
+ },
152
+ [DEFERRED_PROP]: true
153
+ };
154
+ }
155
+ /**
156
+ * Creates an optional prop that is never included in standard visits and can only be
157
+ * explicitly requested by the client. Unlike deferred props, optional props are not
158
+ * shared with the client during standard visits.
159
+ *
160
+ * Optional props are ideal for data that is rarely needed and should only be loaded
161
+ * on demand to optimize performance.
162
+ *
163
+ * @param fn - Function that computes the prop value when requested
164
+ * @returns An optional prop object that computes values lazily
165
+ *
166
+ * @example
167
+ * ```javascript
168
+ * // Create an optional prop for detailed audit logs
169
+ * const auditLogs = optional(() => {
170
+ * return fetchDetailedAuditLogs(resourceId)
171
+ * })
172
+ *
173
+ * // Use in page props
174
+ * return inertia.render('resource/show', {
175
+ * resource: resource,
176
+ * auditLogs: auditLogs // Only loaded when explicitly requested
177
+ * })
178
+ * ```
179
+ */
180
+ function optional(fn) {
181
+ return {
182
+ compute: fn,
183
+ once(options) {
184
+ return once(this, options);
185
+ },
186
+ [OPTIONAL_PROP]: true
187
+ };
188
+ }
189
+ /**
190
+ * Creates a prop that is always included in responses and cannot be removed during
191
+ * cherry-picking. This ensures the prop is always available to the frontend component.
192
+ *
193
+ * Always props are useful for critical data that the frontend component must have
194
+ * to function properly, regardless of what props are specifically requested.
195
+ *
196
+ * @param value - The value to always include in the response
197
+ * @returns An always prop object that cannot be cherry-picked away
198
+ *
199
+ * @example
200
+ * ```javascript
201
+ * // Create an always prop for critical user permissions
202
+ * const userPermissions = always(user.permissions)
203
+ *
204
+ * // Use in page props
205
+ * return inertia.render('admin/dashboard', {
206
+ * users: users,
207
+ * permissions: userPermissions // Always included, never filtered out
208
+ * })
209
+ * ```
210
+ */
211
+ function always(value) {
212
+ return {
213
+ value,
214
+ [ALWAYS_PROP]: true
215
+ };
216
+ }
217
+ /**
218
+ * Creates a prop that should be merged with existing props on the page rather than
219
+ * replaced. This is useful for incremental updates where you want to combine new
220
+ * data with existing data on the client side.
221
+ *
222
+ * Mergeable props enable efficient partial updates by allowing the client to
223
+ * merge new prop values with existing ones instead of replacing them entirely.
224
+ *
225
+ * @param value - The value to be merged with existing props
226
+ * @returns A mergeable prop object marked for merging behavior
227
+ *
228
+ * @example
229
+ * ```javascript
230
+ * // Create a mergeable prop for incremental notifications
231
+ * const newNotifications = merge([
232
+ * { id: 1, message: 'New message received' },
233
+ * { id: 2, message: 'Task completed' }
234
+ * ])
235
+ *
236
+ * // Use in page props - will merge with existing notifications
237
+ * return inertia.render('dashboard', {
238
+ * user: user,
239
+ * notifications: newNotifications // Merges with existing notifications array
240
+ * })
241
+ * ```
242
+ */
243
+ function merge(value) {
244
+ return createMergeableProp(value, false);
245
+ }
246
+ /**
247
+ * Builds a mergeable prop wrapper shared by `merge` and `deepMerge`. The chainable
248
+ * `prepend`/`append`/`matchOn` methods mutate the wrapper's symbol-keyed config in
249
+ * place and return it, so they compose fluently (e.g. `merge(v).prepend().matchOn('id')`).
250
+ *
251
+ * @param value - The value (or inner prop wrapper) to be merged
252
+ * @param deep - Whether the merge is deep (recursive) rather than a shallow array merge
253
+ * @returns A mergeable prop wrapper carrying merge metadata
254
+ */
255
+ function createMergeableProp(value, deep) {
256
+ return {
257
+ value,
258
+ prepend() {
259
+ this[MERGE_PREPEND] = true;
260
+ return this;
261
+ },
262
+ append() {
263
+ this[MERGE_PREPEND] = false;
264
+ return this;
265
+ },
266
+ matchOn(key) {
267
+ this[MERGE_MATCH_ON] = key;
268
+ return this;
269
+ },
270
+ once(options) {
271
+ return once(this, options);
272
+ },
273
+ [TO_BE_MERGED]: true,
274
+ [DEEP_MERGE]: deep,
275
+ [MERGE_PREPEND]: false,
276
+ [MERGE_MATCH_ON]: void 0
277
+ };
278
+ }
279
+ /**
280
+ * Detects an `@adonisjs/core` transformer paginator (the result of
281
+ * `Transformer.paginate()`) via its `$type` discriminator. Its `metaData` is the
282
+ * free-form object passed to `.paginate()` — by convention an AdonisJS Lucid
283
+ * paginator's `getMeta()` output.
284
+ *
285
+ * @param value - The value to test
286
+ * @returns True when the value is a transformer paginator
287
+ */
288
+ function isTransformerPaginator(value) {
289
+ return value !== null && typeof value === "object" && value.$type === "paginator" && isObject(value.metaData);
290
+ }
291
+ /**
292
+ * Extracts the infinite-scroll cursor from a transformer paginator's `metaData`,
293
+ * mirroring `inertia-laravel`'s `ScrollMetadata::fromPaginator` for offset
294
+ * paginators: the page identifiers are the adjacent page numbers (or `null` at
295
+ * the boundaries), and `pageName` comes straight from the paginator meta.
296
+ *
297
+ * @param metaData - The paginator's metadata (Lucid `getMeta()` shape)
298
+ * @returns The extracted cursor, or `null` when the metadata is not derivable
299
+ */
300
+ function extractScrollPropsFromPaginatorMetaData(metaData) {
301
+ const currentPage = metaData.currentPage;
302
+ if (typeof currentPage !== "number") return null;
303
+ const lastPage = typeof metaData.lastPage === "number" ? metaData.lastPage : currentPage;
304
+ const firstPage = typeof metaData.firstPage === "number" ? metaData.firstPage : 1;
305
+ return {
306
+ pageName: typeof metaData.pageName === "string" ? metaData.pageName : "page",
307
+ currentPage,
308
+ nextPage: currentPage < lastPage ? currentPage + 1 : null,
309
+ previousPage: currentPage > firstPage ? currentPage - 1 : null
310
+ };
311
+ }
312
+ /**
313
+ * The default scroll-props provider used when `scroll()` is called without one.
314
+ * Auto-derives the cursor from a transformer paginator's offset metadata and
315
+ * throws when no cursor can be derived (non-paginator values, or cursor
316
+ * paginators) — so a scroll prop over a custom source fails loudly unless an
317
+ * explicit provider is supplied.
318
+ *
319
+ * @param value - The resolved scroll prop value
320
+ * @returns The pagination cursor extracted from the paginator
321
+ */
322
+ function paginatorScrollPropsProvider(value) {
323
+ if (isTransformerPaginator(value)) {
324
+ const cursor = extractScrollPropsFromPaginatorMetaData(value.metaData);
325
+ if (cursor !== null) return cursor;
326
+ }
327
+ /**
328
+ * Reached when the value is not a transformer paginator, or is one whose
329
+ * metadata is not offset-paginated (e.g. a cursor paginator). Both need an
330
+ * explicit provider, so the message stays accurate for either case.
331
+ */
332
+ throw new Error("Cannot derive an infinite-scroll cursor from the value. Pass a scrollProps callback as the second argument to \"scroll()\" for cursor or non-paginator sources.");
333
+ }
334
+ /**
335
+ * Computes the wire labels for a scroll prop's merge: the dotted `data`-array
336
+ * path the client merges directionally, and the optional keyed-match entry. The
337
+ * client splits each entry on its last dot, so `"<key>.data"` resolves to the
338
+ * `data` array under the prop and `"<key>.data.<matchOn>"` keys it.
339
+ *
340
+ * @param key - The prop's top-level path
341
+ * @param scrollProp - The scroll prop wrapper carrying the match key
342
+ * @returns The merge path and the optional `matchPropsOn` entry
343
+ */
344
+ function scrollMergeLabels(key, scrollProp) {
345
+ const mergePath = `${key}.data`;
346
+ const matchOn = scrollProp[MERGE_MATCH_ON];
347
+ return {
348
+ mergePath,
349
+ matchEntry: matchOn === void 0 ? void 0 : `${mergePath}.${matchOn}`
350
+ };
351
+ }
352
+ /**
353
+ * Creates a prop that should be deeply merged with existing props on the page.
354
+ *
355
+ * Unlike shallow merge, deep merge recursively merges nested objects and arrays,
356
+ * allowing for more granular updates to complex data structures.
357
+ *
358
+ * @param value - The value to be deeply merged with existing props
359
+ * @returns A mergeable prop object marked for deep merging behavior
360
+ *
361
+ * @example
362
+ * ```javascript
363
+ * // Create a deep mergeable prop for nested user settings
364
+ * const updatedSettings = deepMerge({
365
+ * notifications: {
366
+ * email: true,
367
+ * push: false
368
+ * },
369
+ * privacy: {
370
+ * profile: 'public'
371
+ * }
372
+ * })
373
+ *
374
+ * // Use in page props - will deeply merge with existing settings
375
+ * return inertia.render('settings', {
376
+ * user: user,
377
+ * settings: updatedSettings // Deep merges with existing settings object
378
+ * })
379
+ * ```
380
+ */
381
+ function deepMerge(value) {
382
+ return createMergeableProp(value, true);
383
+ }
384
+ /**
385
+ * Creates a once prop: a prop the server computes, the client caches across
386
+ * visits, and the server skips re-resolving on later visits where the client
387
+ * reports it already holds a fresh value.
388
+ *
389
+ * Use the `.once()` chaining method to compose with `defer`/`optional`/`merge`;
390
+ * use this standalone form for plain values and lazy callbacks.
391
+ *
392
+ * @param inner - The value, callback, or inner prop wrapper to remember
393
+ * @param options - Custom key, expiry, and force-fresh options
394
+ * @returns A once prop wrapper carrying caching metadata
395
+ *
396
+ * @example
397
+ * ```js
398
+ * // Plain value, no expiry
399
+ * return inertia.render('dashboard', {
400
+ * lookups: inertia.once(() => loadLookupTables())
401
+ * })
402
+ *
403
+ * // Custom key + expiry, composed with a deferred prop
404
+ * return inertia.render('dashboard', {
405
+ * stats: inertia.defer(() => heavyStats()).once({ key: 'stats', expiresIn: '1h' })
406
+ * })
407
+ * ```
408
+ */
409
+ function once(value, options = {}) {
410
+ return {
411
+ value,
412
+ onceKey: options.key,
413
+ expiry: {
414
+ expiresIn: options.expiresIn,
415
+ expiresAt: options.expiresAt
416
+ },
417
+ fresh: options.fresh ?? false,
418
+ [ONCE_PROP]: true
419
+ };
420
+ }
421
+ /**
422
+ * Creates an infinite-scroll prop: a mergeable, paginated value the client keeps
423
+ * extending as the user scrolls. The server labels the value's `data` array for
424
+ * keyed/directional merging (driven by the client's merge-intent header) and
425
+ * emits a pagination cursor under the page object's `scrollProps`.
426
+ *
427
+ * The value is a transformer paginator (cursor auto-derived from its metadata)
428
+ * or any `{ data, ... }`-shaped value, given directly or via a callback. For a
429
+ * non-paginator value, pass a `scrollProps` provider that computes the cursor
430
+ * from the resolved value. Chain `.deferred()` to skip the first page on the
431
+ * initial load, and `.matchOn()` to dedupe overlapping pages by a key.
432
+ *
433
+ * @param value - A transformer paginator or `{ data }` value (or a callback resolving to one)
434
+ * @param provider - Computes the cursor from the resolved value; omit for transformer paginators
435
+ * @returns A scroll prop wrapper carrying merge + pagination metadata
436
+ *
437
+ * @example
438
+ * ```js
439
+ * // Transformer paginator — cursor auto-derived from its metadata
440
+ * return inertia.render('users/index', {
441
+ * users: inertia.scroll(() => UserTransformer.paginate(rows, paginator.getMeta()))
442
+ * })
443
+ *
444
+ * // Custom / cursor source — cursor computed by a provider
445
+ * return inertia.render('feed', {
446
+ * posts: inertia.scroll(() => feed, (value) => ({
447
+ * pageName: 'cursor',
448
+ * currentPage: value.cursor,
449
+ * nextPage: value.next,
450
+ * previousPage: null,
451
+ * }))
452
+ * })
453
+ * ```
454
+ */
455
+ function scroll(value, provider) {
456
+ return {
457
+ value,
458
+ provider: provider ?? paginatorScrollPropsProvider,
459
+ deferred(group = "default") {
460
+ this.group = group;
461
+ /**
462
+ * The deferred flag is type-only (`[SCROLL_DEFERRED]`), so the runtime
463
+ * object is unchanged; the cast re-tags it as the deferred variant so it
464
+ * type-checks as optional on the client.
465
+ */
466
+ return this;
467
+ },
468
+ matchOn(key) {
469
+ this[MERGE_MATCH_ON] = key;
470
+ return this;
471
+ },
472
+ [SCROLL_PROP]: true,
473
+ [MERGE_MATCH_ON]: void 0
474
+ };
475
+ }
476
+ /**
477
+ * Type guard that checks if a prop value is a scroll prop.
478
+ *
479
+ * @param propValue - The object to check for scroll prop characteristics
480
+ * @returns True if the prop value is a scroll prop
481
+ */
482
+ function isScrollProp(propValue) {
483
+ return SCROLL_PROP in propValue;
484
+ }
485
+ /**
486
+ * Type guard that checks if a prop value is a once prop.
487
+ *
488
+ * @param propValue - The object to check for once prop characteristics
489
+ * @returns True if the prop value is a once prop
490
+ */
491
+ function isOnceProp(propValue) {
492
+ return ONCE_PROP in propValue;
493
+ }
494
+ /**
495
+ * Type guard that checks if a prop value is a deferred prop.
496
+ *
497
+ * Deferred props contain the DEFERRED_PROP symbol and have compute/merge capabilities.
498
+ * This function is useful for runtime type checking and conditional prop handling.
499
+ *
500
+ * @param propValue - The object to check for deferred prop characteristics
501
+ * @returns True if the prop value is a deferred prop
502
+ *
503
+ * @example
504
+ * ```js
505
+ * const prop = defer(() => ({ data: 'value' }))
506
+ *
507
+ * if (isDeferredProp(prop)) {
508
+ * // prop is now typed as DeferProp<T>
509
+ * const result = prop.compute()
510
+ * }
511
+ * ```
512
+ */
513
+ function isDeferredProp(propValue) {
514
+ return DEFERRED_PROP in propValue;
515
+ }
516
+ /**
517
+ * Type guard that checks if a prop value is a mergeable prop.
518
+ *
519
+ * Mergeable props contain the TO_BE_MERGED symbol and should be merged with
520
+ * existing props rather than replaced during updates.
521
+ *
522
+ * @param propValue - The object to check for mergeable prop characteristics
523
+ * @returns True if the prop value is a mergeable prop
524
+ *
525
+ * @example
526
+ * ```js
527
+ * const prop = merge({ items: [1, 2, 3] })
528
+ *
529
+ * if (isMergeableProp(prop)) {
530
+ * // prop is now typed as MergeableProp<T>
531
+ * const value = prop.value
532
+ * }
533
+ * ```
534
+ */
535
+ function isMergeableProp(propValue) {
536
+ return TO_BE_MERGED in propValue;
537
+ }
538
+ /**
539
+ * Type guard that checks if a prop value is an always prop.
540
+ *
541
+ * Always props contain the ALWAYS_PROP symbol and are always included in
542
+ * responses, regardless of cherry-picking or selective prop requests.
543
+ *
544
+ * @param propValue - The object to check for always prop characteristics
545
+ * @returns True if the prop value is an always prop
546
+ *
547
+ * @example
548
+ * ```js
549
+ * const prop = always({ userId: 123, permissions: ['read', 'write'] })
550
+ *
551
+ * if (isAlwaysProp(prop)) {
552
+ * // prop is now typed as AlwaysProp<T>
553
+ * const value = prop.value
554
+ * }
555
+ * ```
556
+ */
557
+ function isAlwaysProp(propValue) {
558
+ return ALWAYS_PROP in propValue;
559
+ }
560
+ /**
561
+ * Type guard that checks if a prop value is an optional prop.
562
+ *
563
+ * Optional props contain the OPTIONAL_PROP symbol and are only included
564
+ * when explicitly requested by the client, never in standard visits.
565
+ *
566
+ * @param propValue - The object to check for optional prop characteristics
567
+ * @returns True if the prop value is an optional prop
568
+ *
569
+ * @example
570
+ * ```js
571
+ * const prop = optional(() => ({ detailedData: 'expensive computation' }))
572
+ *
573
+ * if (isOptionalProp(prop)) {
574
+ * // prop is now typed as OptionalProp<T>
575
+ * const result = prop.compute()
576
+ * }
577
+ * ```
578
+ */
579
+ function isOptionalProp(propValue) {
580
+ return OPTIONAL_PROP in propValue;
581
+ }
582
+ /**
583
+ * Helper function to unpack prop values using the transformer serialize function.
584
+ *
585
+ * @param value - The prop value to serialize
586
+ * @param containerResolver - Container resolver for dependency injection
587
+ * @returns Promise resolving to the serialized JSON data
588
+ */
589
+ async function unpackPropValue(value, containerResolver) {
590
+ if (value === null) return null;
591
+ return inertiaSerializer.serialize(value, containerResolver);
592
+ }
593
+ /**
594
+ * Builds props for standard (non-partial) Inertia visits.
595
+ *
596
+ * This function processes page props and categorizes them based on their type:
597
+ * - Deferred props: Skipped but communicated to client
598
+ * - Optional props: Skipped entirely
599
+ * - Always props: Always included
600
+ * - Mergeable props: Included and marked for merging
601
+ * - Regular props: Included normally
602
+ *
603
+ * @param pageProps - The page props to process
604
+ * @param containerResolver - Container resolver for dependency injection
605
+ * @returns Promise resolving to object containing processed props, deferred props list, and merge props list
606
+ *
607
+ * @example
608
+ * ```js
609
+ * const result = await buildStandardVisitProps({
610
+ * user: { name: 'John' },
611
+ * posts: defer(() => getPosts()),
612
+ * settings: merge({ theme: 'dark' })
613
+ * })
614
+ * // Returns: { props: { user: {...} }, deferredProps: { default: ['posts'] }, mergeProps: ['settings'] }
615
+ * ```
616
+ */
617
+ /**
618
+ * Records a once prop's caching metadata and returns its resolved once-key. The
619
+ * entry is emitted regardless of whether the value is ultimately sent, so the
620
+ * client can keep using its cached copy.
621
+ *
622
+ * @param onceProps - The accumulator to record the metadata into
623
+ * @param key - The prop's top-level path inside the props bag
624
+ * @param prop - The once prop wrapper
625
+ * @param now - Reference timestamp (epoch-ms) used to normalize relative expiry
626
+ * @returns The once-key the entry was recorded under
627
+ */
628
+ function emitOnceMetadata(onceProps, key, prop, now) {
629
+ const onceKey = prop.onceKey ?? key;
630
+ if (onceKey in onceProps) debug_default("duplicate once-key \"%s\" in a single response; last value wins", onceKey);
631
+ /**
632
+ * Normalize the expiry to absolute epoch-ms: an absolute `expiresAt` wins over
633
+ * a relative `expiresIn` (resolved against the response-build `now`); `null`
634
+ * when no expiry is configured.
635
+ */
636
+ const { expiresIn, expiresAt } = prop.expiry;
637
+ let resolvedExpiresAt = null;
638
+ if (expiresAt !== void 0) resolvedExpiresAt = expiresAt instanceof Date ? expiresAt.getTime() : expiresAt;
639
+ else if (expiresIn !== void 0) resolvedExpiresAt = now + (typeof expiresIn === "number" ? expiresIn : string.milliseconds.parse(expiresIn));
640
+ onceProps[onceKey] = {
641
+ prop: key,
642
+ expiresAt: resolvedExpiresAt
643
+ };
644
+ return onceKey;
645
+ }
646
+ async function buildStandardVisitProps(pageProps, containerResolver, onceContext = {
647
+ exceptOnce: /* @__PURE__ */ new Set(),
648
+ now: Date.now()
649
+ }, resetProps = /* @__PURE__ */ new Set(), mergeIntent = "append") {
650
+ const mergeProps = [];
651
+ const deepMergeProps = [];
652
+ const prependProps = [];
653
+ const matchPropsOn = [];
654
+ const newProps = {};
655
+ const deferredProps = {};
656
+ const onceProps = {};
657
+ const scrollProps = {};
658
+ const unpackedValues = [];
659
+ /**
660
+ * Classifies a single prop entry, mutating the accumulators above. Extracted
661
+ * so the once-prop path can unwrap and re-classify its inner prop through the
662
+ * exact same logic as a non-once prop.
663
+ */
664
+ const classify = (key, value) => {
665
+ if (isObject(value)) {
666
+ /**
667
+ * Once props gate resolution on the client cache. We always record the
668
+ * caching metadata, then either skip the value (client already holds it)
669
+ * or unwrap and re-classify the inner prop. The client only advertises a
670
+ * once-key when it holds a present, non-expired value, so we trust the
671
+ * header and skip unless the prop is forced fresh.
672
+ */
673
+ if (isOnceProp(value)) {
674
+ const onceKey = emitOnceMetadata(onceProps, key, value, onceContext.now);
675
+ if (!onceContext.exceptOnce.has(onceKey) || value.fresh) classify(key, value.value);
676
+ return;
677
+ }
678
+ /**
679
+ * Deferred props are skipped during the standard visits.
680
+ * But we inform the client about it
681
+ */
682
+ if (isDeferredProp(value)) {
683
+ deferredProps[value.group] = deferredProps[value.group] ?? [];
684
+ deferredProps[value.group].push(key);
685
+ return;
686
+ }
687
+ /**
688
+ * Optional props are skipped during the standard visits
689
+ */
690
+ if (isOptionalProp(value)) return;
691
+ /**
692
+ * Unpack always prop value
693
+ */
694
+ if (isAlwaysProp(value)) {
695
+ unpackedValues.push({
696
+ key,
697
+ value: value.value
698
+ });
699
+ return;
700
+ }
701
+ /**
702
+ * Infinite-scroll props are mergeable, paginated props. We label the
703
+ * value's `data` array for keyed/directional merging per the client's
704
+ * merge intent, then resolve the value and its pagination cursor.
705
+ */
706
+ if (isScrollProp(value)) {
707
+ /**
708
+ * A prop named in `X-Inertia-Reset` is left unlabeled so the client
709
+ * replaces rather than merges; the `reset` flag in `scrollProps` then
710
+ * tells it to discard cached items.
711
+ */
712
+ if (!resetProps.has(key)) {
713
+ const { mergePath, matchEntry } = scrollMergeLabels(key, value);
714
+ if (mergeIntent === "prepend") prependProps.push(mergePath);
715
+ else mergeProps.push(mergePath);
716
+ if (matchEntry !== void 0) matchPropsOn.push(matchEntry);
717
+ }
718
+ /**
719
+ * A deferred scroll prop skips its first page on a standard visit; its
720
+ * cursor is emitted only once the value resolves (on the partial reload).
721
+ */
722
+ if (value.group !== void 0) {
723
+ deferredProps[value.group] = deferredProps[value.group] ?? [];
724
+ deferredProps[value.group].push(key);
725
+ return;
726
+ }
727
+ unpackedValues.push({
728
+ key,
729
+ scroll: value
730
+ });
731
+ return;
732
+ }
733
+ /**
734
+ * Inform the client about the mergeable prop and use its
735
+ * value
736
+ */
737
+ if (isMergeableProp(value)) {
738
+ /**
739
+ * A prop named in `X-Inertia-Reset` is left unlabeled so the client
740
+ * replaces it rather than merging; its value is still emitted below.
741
+ */
742
+ if (!resetProps.has(key)) {
743
+ if (value[DEEP_MERGE]) deepMergeProps.push(key);
744
+ else if (value[MERGE_PREPEND]) prependProps.push(key);
745
+ else mergeProps.push(key);
746
+ if (value[MERGE_MATCH_ON] !== void 0) matchPropsOn.push(`${key}.${value[MERGE_MATCH_ON]}`);
747
+ }
748
+ /**
749
+ * Mergeable deferred props are skipped during the standard visits.
750
+ * But we inform the client about both of them
751
+ */
752
+ if (isObject(value.value) && isDeferredProp(value.value)) {
753
+ deferredProps[value.value.group] = deferredProps[value.value.group] ?? [];
754
+ deferredProps[value.value.group].push(key);
755
+ return;
756
+ }
757
+ unpackedValues.push({
758
+ key,
759
+ value: value.value
760
+ });
761
+ return;
762
+ }
763
+ /**
764
+ * Unpack all other values
765
+ */
766
+ unpackedValues.push({
767
+ key,
768
+ value
769
+ });
770
+ } else {
771
+ /**
772
+ * Compute lazy value
773
+ */
774
+ if (typeof value === "function") {
775
+ unpackedValues.push({
776
+ key,
777
+ value
778
+ });
779
+ return;
780
+ }
781
+ newProps[key] = value;
782
+ }
783
+ };
784
+ for (const [key, value] of Object.entries(pageProps)) classify(key, value);
785
+ await Promise.all(unpackedValues.map(async (entry) => {
786
+ /**
787
+ * Scroll props resolve their value and pagination cursor together: invoke
788
+ * the value callback once, then compute the cursor via the prop's provider
789
+ * (the paginator-aware default unless an explicit one was supplied).
790
+ */
791
+ if ("scroll" in entry) {
792
+ const { key, scroll: scrollProp } = entry;
793
+ let raw = scrollProp.value;
794
+ if (typeof raw === "function") raw = await raw();
795
+ const cursor = await scrollProp.provider(raw);
796
+ newProps[key] = await unpackPropValue(raw, containerResolver);
797
+ scrollProps[key] = {
798
+ ...cursor,
799
+ reset: resetProps.has(key)
800
+ };
801
+ return;
802
+ }
803
+ const { key, value } = entry;
804
+ if (typeof value === "function") return Promise.resolve(value()).then((r) => unpackPropValue(r, containerResolver)).then((jsonValue) => {
805
+ newProps[key] = jsonValue;
806
+ });
807
+ else return unpackPropValue(value, containerResolver).then((jsonValue) => {
808
+ newProps[key] = jsonValue;
809
+ });
810
+ }));
811
+ return {
812
+ props: newProps,
813
+ mergeProps,
814
+ deepMergeProps,
815
+ prependProps,
816
+ matchPropsOn,
817
+ deferredProps,
818
+ onceProps,
819
+ scrollProps,
820
+ /**
821
+ * Deferred props are never computed on a standard visit, so nothing can be
822
+ * rescued here. Emitted for a uniform shape with the partial-reload builder
823
+ * and to match the always-present v3 `rescuedProps` wire field.
824
+ */
825
+ rescuedProps: [],
826
+ rescuedErrors: []
827
+ };
828
+ }
829
+ /**
830
+ * Builds props for partial (cherry-picked) Inertia requests.
831
+ *
832
+ * This function processes page props for partial requests where only specific
833
+ * props are requested. It handles:
834
+ * - Always props: Always included regardless of cherry picking
835
+ * - Cherry-picked props: Only included if in the cherryPickProps list
836
+ * - Mergeable props: Included and marked for merging
837
+ * - Regular props: Included if cherry-picked
838
+ *
839
+ * @param pageProps - The page props to process
840
+ * @param cherryPickProps - Array of prop names to include
841
+ * @param containerResolver - Container resolver for dependency injection
842
+ * @returns Promise resolving to object containing processed props and merge props list
843
+ *
844
+ * @example
845
+ * ```js
846
+ * const result = await buildPartialRequestProps(
847
+ * { user: { name: 'John' }, posts: defer(() => getPosts()), stats: optional(() => getStats()) },
848
+ * ['posts', 'stats']
849
+ * )
850
+ * // Returns: { props: { posts: [...], stats: [...] }, mergeProps: [], deferredProps: {} }
851
+ * ```
852
+ */
853
+ async function buildPartialRequestProps(pageProps, cherryPickProps, containerResolver, now = Date.now(), resetProps = /* @__PURE__ */ new Set(), mergeIntent = "append") {
854
+ const mergeProps = [];
855
+ const deepMergeProps = [];
856
+ const prependProps = [];
857
+ const matchPropsOn = [];
858
+ const newProps = {};
859
+ const onceProps = {};
860
+ const scrollProps = {};
861
+ const unpackedValues = [];
862
+ const rescuedProps = [];
863
+ const rescuedErrors = [];
864
+ /**
865
+ * Classifies a single prop entry, mutating the accumulators above. Extracted
866
+ * so the once-prop path can unwrap and re-classify its inner prop through the
867
+ * exact same logic as a non-once prop.
868
+ */
869
+ const classify = (key, value) => {
870
+ if (isObject(value)) {
871
+ /**
872
+ * Unpack always prop even if it is not part of
873
+ * cherry picking props
874
+ */
875
+ if (isAlwaysProp(value)) {
876
+ unpackedValues.push({
877
+ key,
878
+ value: value.value
879
+ });
880
+ return;
881
+ }
882
+ /**
883
+ * Skip key if not part of cherry picking list
884
+ */
885
+ if (!cherryPickProps.includes(key)) return;
886
+ /**
887
+ * Partial reloads always resolve a requested once prop — the
888
+ * `X-Inertia-Except-Once-Props` header is honoured on standard visits
889
+ * only (matching inertia-laravel, whose once-cache gate runs only when
890
+ * the request is not partial). We still emit the caching metadata.
891
+ */
892
+ if (isOnceProp(value)) {
893
+ emitOnceMetadata(onceProps, key, value, now);
894
+ classify(key, value.value);
895
+ return;
896
+ }
897
+ /**
898
+ * Unpack deferred prop
899
+ */
900
+ if (isDeferredProp(value)) {
901
+ unpackedValues.push({
902
+ key,
903
+ value: value.compute,
904
+ rescue: value.rescue
905
+ });
906
+ return;
907
+ }
908
+ /**
909
+ * Unpack optional prop
910
+ */
911
+ if (isOptionalProp(value)) {
912
+ unpackedValues.push({
913
+ key,
914
+ value: value.compute
915
+ });
916
+ return;
917
+ }
918
+ /**
919
+ * Resolve a requested scroll prop: label its `data` array for the merge
920
+ * per the client's intent, then resolve the value and emit its cursor.
921
+ */
922
+ if (isScrollProp(value)) {
923
+ if (!resetProps.has(key)) {
924
+ const { mergePath, matchEntry } = scrollMergeLabels(key, value);
925
+ if (mergeIntent === "prepend") prependProps.push(mergePath);
926
+ else mergeProps.push(mergePath);
927
+ if (matchEntry !== void 0) matchPropsOn.push(matchEntry);
928
+ }
929
+ unpackedValues.push({
930
+ key,
931
+ scroll: value
932
+ });
933
+ return;
934
+ }
935
+ /**
936
+ * Inform the client about the mergeable prop
937
+ */
938
+ if (isMergeableProp(value)) {
939
+ /**
940
+ * A prop named in `X-Inertia-Reset` is left unlabeled so the client
941
+ * replaces it rather than merging; its value is still emitted below.
942
+ */
943
+ if (!resetProps.has(key)) {
944
+ if (value[DEEP_MERGE]) deepMergeProps.push(key);
945
+ else if (value[MERGE_PREPEND]) prependProps.push(key);
946
+ else mergeProps.push(key);
947
+ if (value[MERGE_MATCH_ON] !== void 0) matchPropsOn.push(`${key}.${value[MERGE_MATCH_ON]}`);
948
+ }
949
+ /**
950
+ * Unpack deferred mergeable prop
951
+ */
952
+ if (isObject(value.value) && isDeferredProp(value.value)) unpackedValues.push({
953
+ key,
954
+ value: value.value.compute,
955
+ rescue: value.value.rescue
956
+ });
957
+ else unpackedValues.push({
958
+ key,
959
+ value: value.value
960
+ });
961
+ return;
962
+ }
963
+ /**
964
+ * Unpack all other values
965
+ */
966
+ unpackedValues.push({
967
+ key,
968
+ value
969
+ });
970
+ } else {
971
+ /**
972
+ * Skip key if not part of cherry picking list
973
+ */
974
+ if (!cherryPickProps.includes(key)) return;
975
+ /**
976
+ * Compute lazy value
977
+ */
978
+ if (typeof value === "function") {
979
+ unpackedValues.push({
980
+ key,
981
+ value
982
+ });
983
+ return;
984
+ }
985
+ newProps[key] = value;
986
+ }
987
+ };
988
+ for (const [key, value] of Object.entries(pageProps)) classify(key, value);
989
+ await Promise.all(unpackedValues.map(async (entry) => {
990
+ /**
991
+ * Scroll props resolve their value and pagination cursor together: invoke
992
+ * the value callback once, then compute the cursor via the prop's provider
993
+ * (the paginator-aware default unless an explicit one was supplied).
994
+ */
995
+ if ("scroll" in entry) {
996
+ const { key, scroll: scrollProp } = entry;
997
+ let raw = scrollProp.value;
998
+ if (typeof raw === "function") raw = await raw();
999
+ const cursor = await scrollProp.provider(raw);
1000
+ newProps[key] = await unpackPropValue(raw, containerResolver);
1001
+ scrollProps[key] = {
1002
+ ...cursor,
1003
+ reset: resetProps.has(key)
1004
+ };
1005
+ return;
1006
+ }
1007
+ const { key, value, rescue } = entry;
1008
+ /**
1009
+ * A rescuable deferred prop swallows a resolution error: the value is
1010
+ * omitted from `props` (never emitted as `null`), the path is recorded in
1011
+ * `rescuedProps`, and the error is collected for out-of-band reporting so
1012
+ * it never propagates into the response pipeline.
1013
+ */
1014
+ if (rescue) {
1015
+ try {
1016
+ const resolved = typeof value === "function" ? await value() : value;
1017
+ newProps[key] = await unpackPropValue(resolved, containerResolver);
1018
+ } catch (error) {
1019
+ rescuedProps.push(key);
1020
+ rescuedErrors.push({
1021
+ prop: key,
1022
+ error
1023
+ });
1024
+ }
1025
+ return;
1026
+ }
1027
+ if (typeof value === "function") return Promise.resolve(value()).then((r) => unpackPropValue(r, containerResolver)).then((jsonValue) => {
1028
+ newProps[key] = jsonValue;
1029
+ });
1030
+ else return unpackPropValue(value, containerResolver).then((jsonValue) => {
1031
+ newProps[key] = jsonValue;
1032
+ });
1033
+ }));
1034
+ return {
1035
+ props: newProps,
1036
+ mergeProps,
1037
+ deepMergeProps,
1038
+ prependProps,
1039
+ matchPropsOn,
1040
+ deferredProps: {},
1041
+ onceProps,
1042
+ scrollProps,
1043
+ rescuedProps,
1044
+ rescuedErrors
1045
+ };
1046
+ }
1047
+ //#endregion
1048
+ //#region src/inertia.ts
1049
+ /**
1050
+ * Main class used to interact with Inertia
1051
+ *
1052
+ * Provides a complete interface for handling Inertia.js requests, rendering pages,
1053
+ * managing props, and controlling page navigation behavior.
1054
+ *
1055
+ * @example
1056
+ * ```js
1057
+ * const inertia = new Inertia(ctx, config, vite, serverRenderer)
1058
+ *
1059
+ * // Render a page
1060
+ * const result = await inertia.render('Home', { user: { name: 'John' } })
1061
+ *
1062
+ * // Clear browser history
1063
+ * inertia.clearHistory()
1064
+ *
1065
+ * // Redirect to a different location
1066
+ * inertia.location('/dashboard')
1067
+ * ```
1068
+ */
1069
+ var Inertia = class Inertia {
1070
+ ctx;
1071
+ config;
1072
+ /**
1073
+ * Listener invoked when a rescuable deferred prop's resolution throws. Shared
1074
+ * across all per-request instances; defaults to logging the error through the
1075
+ * request logger.
1076
+ */
1077
+ static #rescueListener = (error, { prop, ctx }) => {
1078
+ ctx.logger.error({ err: error }, `Rescued deferred prop "${prop}"`);
1079
+ };
1080
+ /**
1081
+ * Register a global listener for rescued deferred-prop errors. The listener
1082
+ * receives the thrown error along with the failing prop path and the HTTP
1083
+ * context. Call with no argument to restore the default logger-based reporter.
1084
+ *
1085
+ * @example
1086
+ * ```ts
1087
+ * Inertia.onRescue((error, { prop, ctx }) => {
1088
+ * ctx.logger.error({ err: error }, `deferred prop "${prop}" failed`)
1089
+ * })
1090
+ * ```
1091
+ */
1092
+ static onRescue(listener) {
1093
+ Inertia.#rescueListener = listener ?? ((error, { prop, ctx }) => {
1094
+ ctx.logger.error({ err: error }, `Rescued deferred prop "${prop}"`);
1095
+ });
1096
+ }
1097
+ #sharedStateProviders;
1098
+ /**
1099
+ * Provider for the page object's top-level `flash` field. Registered by the
1100
+ * Inertia middleware from its `flash()` method; resolved once per response.
1101
+ */
1102
+ #flashProvider;
1103
+ #cachedRequestInfo;
1104
+ /**
1105
+ * Optional server-side renderer for SSR functionality
1106
+ */
1107
+ #serverRenderer;
1108
+ /**
1109
+ * Vite instance for asset management and manifest handling
1110
+ */
1111
+ #vite;
1112
+ /**
1113
+ * Flag to control whether the next navigation should clear browser history
1114
+ */
1115
+ #shouldClearHistory;
1116
+ /**
1117
+ * Flag to control whether browser history should be encrypted
1118
+ */
1119
+ #shouldEncryptHistory;
1120
+ /**
1121
+ * Cached version string/number for asset versioning
1122
+ */
1123
+ #cachedVersion;
1124
+ /**
1125
+ * Defer prop evaluation until client-side rendering
1126
+ *
1127
+ * @example
1128
+ * ```js
1129
+ * {
1130
+ * expensiveData: inertia.defer(() => computeExpensiveData())
1131
+ * }
1132
+ * ```
1133
+ */
1134
+ defer = defer;
1135
+ /**
1136
+ * Always include prop in both server and client renders
1137
+ *
1138
+ * @example
1139
+ * ```js
1140
+ * {
1141
+ * currentUser: inertia.always(() => getCurrentUser())
1142
+ * }
1143
+ * ```
1144
+ */
1145
+ always = always;
1146
+ /**
1147
+ * Merge prop with existing client-side data
1148
+ *
1149
+ * @example
1150
+ * ```js
1151
+ * {
1152
+ * posts: inertia.merge(() => getPosts())
1153
+ * }
1154
+ * ```
1155
+ */
1156
+ merge = merge;
1157
+ /**
1158
+ * Include prop only when specifically requested
1159
+ *
1160
+ * @example
1161
+ * ```js
1162
+ * {
1163
+ * optionalData: inertia.optional(() => getOptionalData())
1164
+ * }
1165
+ * ```
1166
+ */
1167
+ optional = optional;
1168
+ /**
1169
+ * Deep merge prop with existing client-side data
1170
+ *
1171
+ * @example
1172
+ * ```js
1173
+ * {
1174
+ * settings: inertia.deepMerge(() => getSettings())
1175
+ * }
1176
+ * ```
1177
+ */
1178
+ deepMerge = deepMerge;
1179
+ /**
1180
+ * Remember a prop on the client across visits. The server skips re-resolving
1181
+ * it when the client reports a fresh cached value. Compose with
1182
+ * `defer`/`optional`/`merge` via their `.once()` method.
1183
+ *
1184
+ * @example
1185
+ * ```js
1186
+ * {
1187
+ * lookups: inertia.once(() => loadLookupTables(), { expiresIn: '1h' })
1188
+ * }
1189
+ * ```
1190
+ */
1191
+ once = once;
1192
+ /**
1193
+ * Create an infinite-scroll prop: a paginated value the client keeps extending
1194
+ * as the user scrolls. The cursor is auto-derived from a transformer paginator,
1195
+ * or supplied via a provider callback for other sources.
1196
+ *
1197
+ * @example
1198
+ * ```js
1199
+ * {
1200
+ * users: inertia.scroll(() => UserTransformer.paginate(rows, paginator.getMeta()))
1201
+ * }
1202
+ * ```
1203
+ */
1204
+ scroll = scroll;
1205
+ /**
1206
+ * Creates a new Inertia instance
1207
+ *
1208
+ * @param ctx - HTTP context for the current request
1209
+ * @param config - Inertia configuration object
1210
+ * @param vite - Vite instance for asset management
1211
+ * @param serverRenderer - Optional server renderer for SSR
1212
+ *
1213
+ * @example
1214
+ * ```js
1215
+ * const inertia = new Inertia(ctx, {
1216
+ * rootView: 'app',
1217
+ * ssr: { enabled: true }
1218
+ * }, vite, serverRenderer)
1219
+ * ```
1220
+ */
1221
+ constructor(ctx, config, vite, serverRenderer) {
1222
+ this.ctx = ctx;
1223
+ this.config = config;
1224
+ if (debug_default.enabled) debug_default("instantiating inertia instance for request \"%s\" using config %O", ctx.request.url(), this.config);
1225
+ this.#shouldClearHistory = false;
1226
+ this.#vite = vite;
1227
+ this.#serverRenderer = serverRenderer;
1228
+ this.#shouldEncryptHistory = config.encryptHistory ?? false;
1229
+ this.#cachedVersion = this.config.assetsVersion ? String(this.config.assetsVersion) : void 0;
1230
+ }
1231
+ /**
1232
+ * Resolve the root view template
1233
+ *
1234
+ * Handles both static strings and dynamic functions for the root view.
1235
+ *
1236
+ * @example
1237
+ * ```js
1238
+ * const viewName = this.#resolveRootView()
1239
+ * this.ctx.view.render(viewName, { page: pageObject })
1240
+ * ```
1241
+ */
1242
+ #resolveRootView() {
1243
+ return typeof this.config.rootView === "function" ? this.config.rootView(this.ctx) : this.config.rootView;
1244
+ }
1245
+ /**
1246
+ * Constructs and serializes the page props for a given component
1247
+ *
1248
+ * Handles both full page loads and partial requests with prop filtering.
1249
+ * Merges shared state providers with page-specific props, and handles
1250
+ * prop cherry-picking for partial reloads based on the `only` and `except` parameters.
1251
+ *
1252
+ * @param component - The component name being rendered
1253
+ * @param requestInfo - Information about the current request
1254
+ * @param pageProps - Raw page props to be processed
1255
+ *
1256
+ * @example
1257
+ * ```js
1258
+ * const result = await this.#buildPageProps('Dashboard', requestInfo, {
1259
+ * user: { name: 'John' },
1260
+ * posts: defer(() => getPosts())
1261
+ * })
1262
+ * ```
1263
+ */
1264
+ async #buildPageProps(component, requestInfo, pageProps) {
1265
+ let finalProps;
1266
+ /**
1267
+ * Top-level keys registered through the `share()` pipeline, in registration
1268
+ * order. Emitted verbatim as the page object's `sharedProps` field —
1269
+ * independent of which shared props actually make it into the response.
1270
+ */
1271
+ let sharedKeys = [];
1272
+ /**
1273
+ * Shared state could be defined as functions that must be lazily evaluated.
1274
+ * Therefore we invoke all the functions and create a final merged shared
1275
+ * state.
1276
+ */
1277
+ if (this.#sharedStateProviders) {
1278
+ const sharedState = await Promise.all(this.#sharedStateProviders.map((provider) => {
1279
+ return typeof provider === "function" ? provider() : provider;
1280
+ })).then((resolvedSharedState) => {
1281
+ return resolvedSharedState.reduce((result, state) => {
1282
+ return {
1283
+ ...result,
1284
+ ...state
1285
+ };
1286
+ }, {});
1287
+ });
1288
+ sharedKeys = Object.keys(sharedState);
1289
+ finalProps = {
1290
+ ...sharedState,
1291
+ ...pageProps
1292
+ };
1293
+ } else finalProps = { ...pageProps };
1294
+ /**
1295
+ * Reference timestamp used to normalize relative once-prop expiry for this
1296
+ * response.
1297
+ */
1298
+ const now = Date.now();
1299
+ /**
1300
+ * Prop paths the client asked to reset (via `X-Inertia-Reset`). Reset props
1301
+ * are emitted unlabeled so the client replaces them instead of merging.
1302
+ */
1303
+ const resetProps = new Set(requestInfo.resetProps ?? []);
1304
+ if (requestInfo.partialComponent === component) {
1305
+ const only = requestInfo.onlyProps;
1306
+ const except = requestInfo.exceptProps ?? [];
1307
+ const cherryPickProps = Object.keys(finalProps).filter((propName) => {
1308
+ if (only) return only.includes(propName) && !except.includes(propName);
1309
+ return !except.includes(propName);
1310
+ });
1311
+ debug_default("building props for a partial reload %O", requestInfo);
1312
+ debug_default("cherry picking props %s", cherryPickProps);
1313
+ /**
1314
+ * `sharedProps` reports the registered shared keys, not a subset of the
1315
+ * emitted props — so cherry-picking does not narrow it (mirrors
1316
+ * inertia-laravel, which collects the keys before partial filtering).
1317
+ */
1318
+ return {
1319
+ ...await buildPartialRequestProps(finalProps, cherryPickProps, this.ctx.containerResolver, now, resetProps, requestInfo.mergeIntent),
1320
+ sharedProps: sharedKeys
1321
+ };
1322
+ }
1323
+ /**
1324
+ * Standard visits gate once props on the client cache: the set of once-keys
1325
+ * the client already holds, plus the reference timestamp.
1326
+ */
1327
+ const onceContext = {
1328
+ exceptOnce: new Set(requestInfo.exceptOnceProps ?? []),
1329
+ now
1330
+ };
1331
+ debug_default("building props for a standard visit %O", requestInfo);
1332
+ /**
1333
+ * Emit every registered shared key, including those skipped this visit as
1334
+ * deferred/optional — the client still treats them as shared for
1335
+ * instant-visit carry-over.
1336
+ */
1337
+ return {
1338
+ ...await buildStandardVisitProps(finalProps, this.ctx.containerResolver, onceContext, resetProps, requestInfo.mergeIntent),
1339
+ sharedProps: sharedKeys
1340
+ };
1341
+ }
1342
+ /**
1343
+ * Handle Inertia AJAX request by setting appropriate headers
1344
+ *
1345
+ * Sets the `X-Inertia` header to 'true' indicating this is an Inertia response,
1346
+ * then returns the page object which will be serialized as JSON.
1347
+ *
1348
+ * @param pageObject - The page object to return
1349
+ *
1350
+ * @example
1351
+ * ```js
1352
+ * const pageObj = await this.page('Dashboard', props)
1353
+ * return this.#handleInertiaRequest(pageObj)
1354
+ * ```
1355
+ */
1356
+ #handleInertiaRequest(pageObject) {
1357
+ this.ctx.response.header(InertiaHeaders.Inertia, "true");
1358
+ return pageObject;
1359
+ }
1360
+ /**
1361
+ * Render page with server-side rendering
1362
+ *
1363
+ * @param pageObject - The page object to render
1364
+ * @param viewProps - Additional props to pass to the root view template
1365
+ * @returns Promise resolving to the rendered HTML string
1366
+ */
1367
+ async #renderWithSSR(pageObject, viewProps) {
1368
+ if (!this.#serverRenderer) throw new Error("Cannot server render pages without a server renderer");
1369
+ debug_default("server-side rendering %O", pageObject);
1370
+ const { head, body } = await this.#serverRenderer.render(pageObject);
1371
+ return this.ctx.view.render(this.#resolveRootView(), {
1372
+ page: {
1373
+ ssrHead: head,
1374
+ ssrBody: body,
1375
+ ...pageObject
1376
+ },
1377
+ ...viewProps
1378
+ });
1379
+ }
1380
+ /**
1381
+ * Render page with client-side rendering only
1382
+ *
1383
+ * @param pageObject - The page object to render
1384
+ * @param viewProps - Additional props to pass to the root view template
1385
+ * @returns Promise resolving to the rendered HTML string
1386
+ */
1387
+ async #renderClientSide(pageObject, viewProps) {
1388
+ debug_default("rendering shell for SPA %O", pageObject);
1389
+ return this.ctx.view.render(this.#resolveRootView(), {
1390
+ page: pageObject,
1391
+ ...viewProps
1392
+ });
1393
+ }
1394
+ /**
1395
+ * Extract Inertia-specific information from request headers
1396
+ *
1397
+ * Parses various Inertia headers to determine request type and props filtering.
1398
+ *
1399
+ * @param reCompute - Whether to recompute the request info instead of using cached version
1400
+ * @returns The request information object containing Inertia-specific data
1401
+ *
1402
+ * @example
1403
+ * ```js
1404
+ * const info = inertia.requestInfo()
1405
+ * if (info.isInertiaRequest) {
1406
+ * // Handle as Inertia request
1407
+ * }
1408
+ * ```
1409
+ */
1410
+ requestInfo(reCompute) {
1411
+ if (reCompute) this.#cachedRequestInfo = void 0;
1412
+ this.#cachedRequestInfo = this.#cachedRequestInfo ?? {
1413
+ version: this.ctx.request.header(InertiaHeaders.Version),
1414
+ isInertiaRequest: !!this.ctx.request.header(InertiaHeaders.Inertia),
1415
+ isPartialRequest: !!this.ctx.request.header(InertiaHeaders.PartialComponent),
1416
+ partialComponent: this.ctx.request.header(InertiaHeaders.PartialComponent),
1417
+ onlyProps: this.ctx.request.header(InertiaHeaders.PartialOnly)?.split(","),
1418
+ exceptProps: this.ctx.request.header(InertiaHeaders.PartialExcept)?.split(","),
1419
+ resetProps: this.ctx.request.header(InertiaHeaders.Reset)?.split(","),
1420
+ errorBag: this.ctx.request.header(InertiaHeaders.ErrorBag),
1421
+ exceptOnceProps: this.ctx.request.header(InertiaHeaders.ExceptOnceProps)?.split(","),
1422
+ mergeIntent: this.ctx.request.header(InertiaHeaders.InfiniteScrollMergeIntent) === "prepend" ? "prepend" : "append"
1423
+ };
1424
+ return this.#cachedRequestInfo;
1425
+ }
1426
+ /**
1427
+ * Compute and cache the assets version
1428
+ *
1429
+ * Uses Vite manifest hash when available, otherwise defaults to '1'.
1430
+ *
1431
+ * @returns The computed version string for asset versioning
1432
+ */
1433
+ getVersion() {
1434
+ if (this.#cachedVersion) return this.#cachedVersion;
1435
+ if (this.#vite?.hasManifestFile) this.#cachedVersion = createHash("md5").update(JSON.stringify(this.#vite.manifest())).digest("hex");
1436
+ else this.#cachedVersion = "1";
1437
+ return this.#cachedVersion;
1438
+ }
1439
+ /**
1440
+ * Determine if server-side rendering is enabled for a specific component
1441
+ *
1442
+ * Checks global SSR settings and component-specific configuration.
1443
+ *
1444
+ * @param component - The component name to check
1445
+ * @returns Promise resolving to true if SSR is enabled for the component
1446
+ *
1447
+ * @example
1448
+ * ```js
1449
+ * const shouldSSR = await inertia.ssrEnabled('UserProfile')
1450
+ * if (shouldSSR) {
1451
+ * // Render on server
1452
+ * }
1453
+ * ```
1454
+ */
1455
+ async ssrEnabled(component) {
1456
+ if (!this.config.ssr.enabled) return false;
1457
+ if (typeof this.config.ssr.pages === "function") return this.config.ssr.pages(this.ctx, component);
1458
+ if (this.config.ssr.pages) return this.config.ssr.pages?.includes(component);
1459
+ return true;
1460
+ }
1461
+ /**
1462
+ * Share props across all pages
1463
+ *
1464
+ * Merges the provided props with existing shared state, making them available
1465
+ * to all pages rendered by this Inertia instance. Shared props are included
1466
+ * in every page render alongside page-specific props.
1467
+ *
1468
+ * @param sharedState - Props to share across all pages
1469
+ * @returns The Inertia instance for method chaining
1470
+ *
1471
+ * @example
1472
+ * ```js
1473
+ * // Share user data across all pages
1474
+ * inertia.share({
1475
+ * user: getCurrentUser(),
1476
+ * flash: getFlashMessages()
1477
+ * })
1478
+ *
1479
+ * // Chain multiple shares
1480
+ * inertia
1481
+ * .share({ currentUser: user })
1482
+ * .share({ permissions: userPermissions })
1483
+ * ```
1484
+ */
1485
+ share(sharedState) {
1486
+ if (!this.#sharedStateProviders) this.#sharedStateProviders = [];
1487
+ this.#sharedStateProviders.push(sharedState);
1488
+ return this;
1489
+ }
1490
+ /**
1491
+ * Register the provider for the page object's top-level `flash` field.
1492
+ *
1493
+ * Unlike shared state, the resolved value is emitted as a sibling of `props`
1494
+ * (not merged into them). The Inertia middleware calls this with its `flash()`
1495
+ * method; the last registration wins. When no provider is registered, the
1496
+ * `flash` field is omitted from the page object entirely.
1497
+ *
1498
+ * @param provider - Callback resolving the flash bag for this response
1499
+ * @returns The Inertia instance for method chaining
1500
+ *
1501
+ * @example
1502
+ * ```js
1503
+ * inertia.flash(() => ctx.session.flashMessages.all())
1504
+ * ```
1505
+ */
1506
+ flash(provider) {
1507
+ this.#flashProvider = provider;
1508
+ return this;
1509
+ }
1510
+ /**
1511
+ * Build a page object with processed props and metadata
1512
+ *
1513
+ * Creates the complete page object that will be sent to the client or used for SSR.
1514
+ *
1515
+ * @param page - The page component name
1516
+ * @param pageProps - Props to pass to the page component
1517
+ * @returns Promise resolving to the complete page object
1518
+ *
1519
+ * @example
1520
+ * ```js
1521
+ * const pageObject = await inertia.page('Dashboard', {
1522
+ * user: { name: 'John' },
1523
+ * posts: defer(() => getPosts())
1524
+ * })
1525
+ * ```
1526
+ */
1527
+ async page(page, pageProps) {
1528
+ const requestInfo = this.requestInfo();
1529
+ const { props, mergeProps, deferredProps, deepMergeProps, prependProps, matchPropsOn, onceProps, scrollProps, rescuedProps, rescuedErrors, sharedProps } = await this.#buildPageProps(page, requestInfo, pageProps);
1530
+ /**
1531
+ * Report any rescued deferred-prop errors out of band through the registered
1532
+ * listener (the logger-based default unless an app overrode it). The errors
1533
+ * were already caught during resolution, so this never affects the response.
1534
+ */
1535
+ for (const { prop, error } of rescuedErrors) Inertia.#rescueListener(error, {
1536
+ prop,
1537
+ ctx: this.ctx
1538
+ });
1539
+ const pageObject = {
1540
+ component: page,
1541
+ url: this.ctx.request.url(true),
1542
+ version: this.getVersion(),
1543
+ props,
1544
+ deferredProps,
1545
+ mergeProps,
1546
+ deepMergeProps,
1547
+ prependProps,
1548
+ matchPropsOn,
1549
+ onceProps,
1550
+ scrollProps,
1551
+ rescuedProps
1552
+ };
1553
+ /**
1554
+ * Advertise the registered shared keys so the v3 client can carry shared
1555
+ * props over during instant visits. Omitted when no shared keys exist, so the
1556
+ * default wire format is unchanged (matches inertia-laravel, which drops the
1557
+ * field when empty).
1558
+ */
1559
+ if (sharedProps.length > 0) pageObject.sharedProps = sharedProps;
1560
+ /**
1561
+ * Inertia v3 omits `clearHistory` and `encryptHistory` from the page object
1562
+ * unless they are `true` (the client defaults both to `false` when absent).
1563
+ * We only emit them when enabled to match the v3 wire format.
1564
+ */
1565
+ if (this.#shouldClearHistory) pageObject.clearHistory = true;
1566
+ if (this.#shouldEncryptHistory) pageObject.encryptHistory = true;
1567
+ /**
1568
+ * Resolve the first-class flash bag from the registered provider, if any,
1569
+ * and emit it as a top-level field. Omitted entirely when no provider is
1570
+ * registered, so the default wire format stays unchanged.
1571
+ */
1572
+ if (this.#flashProvider) pageObject.flash = await this.#flashProvider();
1573
+ return pageObject;
1574
+ }
1575
+ /**
1576
+ * Render a page using Inertia
1577
+ *
1578
+ * This method handles three distinct rendering scenarios:
1579
+ * 1. Inertia requests - Returns JSON page object for client-side navigation
1580
+ * 2. Initial page loads with SSR - Returns HTML with server-rendered content
1581
+ * 3. Initial page loads without SSR - Returns HTML for client-side hydration
1582
+ *
1583
+ * @param page - The page component name to render
1584
+ * @param pageProps - Props to pass to the page component
1585
+ * @param viewProps - Additional props to pass to the root view template
1586
+ * @returns Promise resolving to PageObject for Inertia requests, HTML string for initial page loads
1587
+ *
1588
+ * @example
1589
+ * ```js
1590
+ * // For Inertia requests, returns PageObject
1591
+ * const result = await inertia.render('Profile', {
1592
+ * user: getCurrentUser(),
1593
+ * posts: defer(() => getUserPosts())
1594
+ * })
1595
+ *
1596
+ * // For initial page loads, returns HTML string
1597
+ * const html = await inertia.render('Home', { welcome: 'Hello World' })
1598
+ * ```
1599
+ */
1600
+ async render(page, pageProps, viewProps) {
1601
+ const requestInfo = this.requestInfo();
1602
+ const pageObject = await this.page(page, pageProps);
1603
+ if (requestInfo.isInertiaRequest) return this.#handleInertiaRequest(pageObject);
1604
+ if (await this.ssrEnabled(page)) return this.#renderWithSSR(pageObject, viewProps);
1605
+ /**
1606
+ * Fallback to client-side rendering
1607
+ */
1608
+ return this.#renderClientSide(pageObject, viewProps);
1609
+ }
1610
+ /**
1611
+ * Clear the browser history on the next navigation
1612
+ *
1613
+ * Instructs the client to clear the browser history stack when navigating.
1614
+ *
1615
+ * @example
1616
+ * ```js
1617
+ * inertia.clearHistory()
1618
+ * return inertia.render('Dashboard', props)
1619
+ * ```
1620
+ */
1621
+ clearHistory() {
1622
+ this.#shouldClearHistory = true;
1623
+ }
1624
+ /**
1625
+ * Control whether browser history should be encrypted
1626
+ *
1627
+ * Enables or disables encryption of sensitive data in browser history.
1628
+ *
1629
+ * @param encrypt - Whether to encrypt history (defaults to true)
1630
+ *
1631
+ * @example
1632
+ * ```js
1633
+ * // Enable encryption for sensitive pages
1634
+ * inertia.encryptHistory(true)
1635
+ *
1636
+ * // Disable encryption for public pages
1637
+ * inertia.encryptHistory(false)
1638
+ * ```
1639
+ */
1640
+ encryptHistory(encrypt = true) {
1641
+ this.#shouldEncryptHistory = encrypt;
1642
+ }
1643
+ /**
1644
+ * Redirect to a different location
1645
+ *
1646
+ * Sets the appropriate headers to redirect the client to a new URL.
1647
+ * Uses a 409 status code which Inertia.js interprets as a redirect instruction.
1648
+ *
1649
+ * @param url - The URL to redirect to
1650
+ *
1651
+ * @example
1652
+ * ```js
1653
+ * // Redirect after login
1654
+ * inertia.location('/dashboard')
1655
+ *
1656
+ * // Redirect with full URL
1657
+ * inertia.location('https://example.com/external')
1658
+ * ```
1659
+ */
1660
+ location(url) {
1661
+ this.ctx.response.header(InertiaHeaders.Location, url);
1662
+ this.ctx.response.status(409);
1663
+ }
1664
+ };
1665
+ //#endregion
1666
+ //#region src/server_renderer.ts
1667
+ /**
1668
+ * Server-side renderer for Inertia.js applications.
1669
+ *
1670
+ * Resolves the SSR entrypoint through `vite.loadServerModule` so the same
1671
+ * code path works in dev (Vite module runner) and in production (pre-built
1672
+ * SSR bundle on disk). The entry must be declared under `serverEntrypoints`
1673
+ * on the AdonisJS Vite plugin so it gets bundled for production.
1674
+ *
1675
+ * @example
1676
+ * ```typescript
1677
+ * const renderer = new ServerRenderer(config, vite)
1678
+ * const { head, body } = await renderer.render(pageObject)
1679
+ * ```
1680
+ */
1681
+ var ServerRenderer = class {
1682
+ /**
1683
+ * Inertia configuration object containing SSR settings
1684
+ */
1685
+ #config;
1686
+ /**
1687
+ * Vite instance used to load the SSR entrypoint module
1688
+ */
1689
+ #vite;
1690
+ constructor(config, vite) {
1691
+ this.#config = config;
1692
+ this.#vite = vite;
1693
+ }
1694
+ /**
1695
+ * Renders an Inertia page on the server.
1696
+ *
1697
+ * @param pageObject - The Inertia page object containing component, props, and metadata
1698
+ * @returns Promise resolving to an object with rendered head and body HTML
1699
+ */
1700
+ async render(pageObject) {
1701
+ debug_default("rendering page through SSR entrypoint %s", this.#config.ssr.entrypoint);
1702
+ const result = await (await this.#vite.loadServerModule(this.#config.ssr.entrypoint)).default(pageObject);
1703
+ debug_default("SSR bundle %o", result);
1704
+ return {
1705
+ head: result.head,
1706
+ body: result.body
1707
+ };
1708
+ }
1709
+ };
1710
+ //#endregion
1711
+ //#region src/inertia_manager.ts
1712
+ /**
1713
+ * Manager class for managing Inertia instances
1714
+ *
1715
+ * Acts as a factory for creating Inertia instances with shared configuration
1716
+ * and Vite integration.
1717
+ *
1718
+ * @example
1719
+ * ```js
1720
+ * const manager = new InertiaManager(config, vite)
1721
+ * const inertia = manager.createForRequest(ctx)
1722
+ * ```
1723
+ */
1724
+ var InertiaManager = class {
1725
+ /**
1726
+ * Optional Vite instance for asset management
1727
+ */
1728
+ #vite;
1729
+ /**
1730
+ * Inertia configuration object
1731
+ */
1732
+ #config;
1733
+ #serverRenderer;
1734
+ /**
1735
+ * Creates a new InertiaManager instance
1736
+ *
1737
+ * @param config - Inertia configuration object
1738
+ * @param vite - Optional Vite instance for development mode and SSR
1739
+ *
1740
+ * @example
1741
+ * ```js
1742
+ * const manager = new InertiaManager({
1743
+ * rootView: 'app',
1744
+ * ssr: { enabled: true }
1745
+ * }, vite)
1746
+ * ```
1747
+ */
1748
+ constructor(config, vite) {
1749
+ this.#config = config;
1750
+ this.#vite = vite;
1751
+ this.#serverRenderer = this.#vite ? new ServerRenderer(this.#config, this.#vite) : void 0;
1752
+ }
1753
+ /**
1754
+ * Creates a new Inertia instance for a specific HTTP request
1755
+ *
1756
+ * @param ctx - HTTP context for the current request
1757
+ * @returns A new Inertia instance configured for the given request
1758
+ *
1759
+ * @example
1760
+ * ```js
1761
+ * const inertia = manager.createForRequest(ctx)
1762
+ * await inertia.render('Home', { user: ctx.auth.user })
1763
+ * ```
1764
+ */
1765
+ createForRequest(ctx) {
1766
+ return new Inertia(ctx, this.#config, this.#vite, this.#serverRenderer);
1767
+ }
1768
+ };
1769
+ //#endregion
1770
+ export { symbols_exports as i, ServerRenderer as n, Inertia as r, InertiaManager as t };