@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.
- package/build/commands/make_page.js +15 -0
- package/build/debug-Ca0ekg3M.js +25 -0
- package/build/factories/inertia_factory.d.ts +25 -0
- package/build/factories/main.js +144 -5
- package/build/headers-B-5pLwyD.js +77 -0
- package/build/index.js +3 -4
- package/build/inertia-ZqLiRJME.js +141 -0
- package/build/inertia_manager-BeqpMsuN.js +1770 -0
- package/build/providers/inertia_provider.js +69 -3
- package/build/src/client/helpers.js +28 -0
- package/build/src/client/react/index.js +144 -1
- package/build/src/client/vue/index.js +95 -1
- package/build/src/headers.d.ts +13 -0
- package/build/src/inertia.d.ts +58 -2
- package/build/src/inertia_middleware.d.ts +37 -1
- package/build/src/inertia_middleware.js +72 -5
- package/build/src/plugins/edge/plugin.js +122 -1
- package/build/src/plugins/japa/api_client.js +50 -1
- package/build/src/props.d.ts +118 -28
- package/build/src/symbols.d.ts +33 -0
- package/build/src/types.d.ts +398 -8
- package/build/tests/helpers.d.ts +26 -0
- package/build/tests/merges.spec.d.ts +1 -0
- package/build/tests/once_props.spec.d.ts +1 -0
- package/build/tests/rescued_deferred_props.spec.d.ts +1 -0
- package/build/tests/scroll.spec.d.ts +1 -0
- package/package.json +28 -26
- package/build/debug-CBMTuPUm.js +0 -3
- package/build/define_config-Dv9Dx2Yq.js +0 -56
- package/build/headers-DafWEpBh.js +0 -11
- package/build/inertia_manager-BdcjpKFf.js +0 -402
|
@@ -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 };
|