inertia-sails 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -2
- package/index.js +133 -81
- package/lib/handle-bad-request.js +20 -6
- package/lib/helpers/build-page-object.js +117 -12
- package/lib/helpers/inertia-headers.js +4 -1
- package/lib/helpers/is-inertia-partial-request.js +10 -0
- package/lib/helpers/is-inertia-request.js +9 -0
- package/lib/helpers/request-context.js +49 -8
- package/lib/helpers/resolve-asset-version.js +12 -0
- package/lib/helpers/resolve-validation-errors.js +12 -5
- package/lib/location.js +11 -0
- package/lib/middleware/inertia-middleware.js +7 -2
- package/lib/props/always-prop.js +6 -2
- package/lib/props/defer-prop.js +46 -6
- package/lib/props/get-partial-data.js +9 -0
- package/lib/props/merge-prop.js +7 -4
- package/lib/props/merge-targets.js +114 -0
- package/lib/props/mergeable-prop.js +87 -0
- package/lib/props/once-prop.js +9 -1
- package/lib/props/optional-prop.js +12 -4
- package/lib/props/pick-props-to-resolve.js +14 -1
- package/lib/props/resolve-deferred-props.js +17 -1
- package/lib/props/resolve-except-props.js +11 -0
- package/lib/props/resolve-merge-props.js +81 -20
- package/lib/props/resolve-once-props.js +17 -7
- package/lib/props/resolve-only-props.js +10 -3
- package/lib/props/resolve-page-props.js +72 -13
- package/lib/props/resolve-scroll-props.js +17 -3
- package/lib/props/scroll-prop.js +30 -8
- package/lib/render.js +68 -9
- package/lib/responses/server-error.js +23 -6
- package/lib/types.js +68 -0
- package/package.json +3 -2
- package/test.js +53 -12
- package/tests/helpers/build-page-object.test.js +183 -0
- package/tests/helpers/preserve-fragment.test.js +97 -0
- package/tests/props/merge-targets.test.js +74 -0
- package/tests/props/resolve-merge-props.test.js +151 -0
- package/tests/props/resolve-page-props.test.js +70 -0
- package/tests/props/resolve-scroll-props.test.js +51 -0
- package/tests/render.test.js +197 -0
package/README.md
CHANGED
|
@@ -38,7 +38,10 @@ module.exports.inertia = {
|
|
|
38
38
|
<%- shipwright.styles() %>
|
|
39
39
|
</head>
|
|
40
40
|
<body>
|
|
41
|
-
<div id="app"
|
|
41
|
+
<div id="app"></div>
|
|
42
|
+
<script type="application/json" data-page="app">
|
|
43
|
+
<%- JSON.stringify(page).replace(/</g, '\\u003c') %>
|
|
44
|
+
</script>
|
|
42
45
|
<%- shipwright.scripts() %>
|
|
43
46
|
</body>
|
|
44
47
|
</html>
|
|
@@ -87,6 +90,19 @@ Return a URL string to redirect:
|
|
|
87
90
|
return '/dashboard'
|
|
88
91
|
```
|
|
89
92
|
|
|
93
|
+
#### Preserving URL fragments
|
|
94
|
+
|
|
95
|
+
When a standard Inertia redirect should carry the current hash to the next
|
|
96
|
+
page, mark the redirect before returning the URL:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
sails.inertia.preserveFragment()
|
|
100
|
+
return '/articles/new-slug'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If the user started from `/articles/old-slug#comments`, the Inertia client can
|
|
104
|
+
carry `#comments` to the redirected page.
|
|
105
|
+
|
|
90
106
|
### Sharing Data
|
|
91
107
|
|
|
92
108
|
#### `share(key, value)`
|
|
@@ -191,6 +207,25 @@ return {
|
|
|
191
207
|
}
|
|
192
208
|
```
|
|
193
209
|
|
|
210
|
+
Deferred props can also be rescued when a non-critical callback fails:
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
return {
|
|
214
|
+
page: 'dashboard',
|
|
215
|
+
props: {
|
|
216
|
+
analytics: sails.inertia
|
|
217
|
+
.defer(async () => {
|
|
218
|
+
return await Analytics.getExpensiveReport()
|
|
219
|
+
})
|
|
220
|
+
.rescue()
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
When a rescued deferred prop throws, it is omitted from `props` and its key is
|
|
226
|
+
reported in `rescuedProps`, allowing the client `<Deferred>` component to show
|
|
227
|
+
its rescue slot instead of failing the whole deferred response.
|
|
228
|
+
|
|
194
229
|
### Merge Props
|
|
195
230
|
|
|
196
231
|
Merge with existing client-side data (useful for infinite scroll):
|
|
@@ -199,13 +234,29 @@ Merge with existing client-side data (useful for infinite scroll):
|
|
|
199
234
|
// Shallow merge
|
|
200
235
|
messages: sails.inertia.merge(() => newMessages)
|
|
201
236
|
|
|
237
|
+
// Prepend new items instead of appending
|
|
238
|
+
notifications: sails.inertia.merge(() => newNotifications).prepend()
|
|
239
|
+
|
|
240
|
+
// Merge a nested array inside a paginated object
|
|
241
|
+
users: sails.inertia.merge(() => paginatedUsers).append('data')
|
|
242
|
+
|
|
243
|
+
// Match existing items by ID when merging
|
|
244
|
+
users: sails.inertia
|
|
245
|
+
.merge(() => paginatedUsers)
|
|
246
|
+
.append('data', {
|
|
247
|
+
matchOn: 'id'
|
|
248
|
+
})
|
|
249
|
+
|
|
202
250
|
// Deep merge (nested objects)
|
|
203
251
|
settings: sails.inertia.deepMerge(() => updatedSettings)
|
|
252
|
+
|
|
253
|
+
// Deep merge with item matching
|
|
254
|
+
chat: sails.inertia.deepMerge(() => chatState).matchOn('messages.id')
|
|
204
255
|
```
|
|
205
256
|
|
|
206
257
|
### Infinite Scroll
|
|
207
258
|
|
|
208
|
-
Paginate data with automatic merge behavior. Works with Inertia
|
|
259
|
+
Paginate data with automatic merge behavior. Works with Inertia's `<InfiniteScroll>` component:
|
|
209
260
|
|
|
210
261
|
```js
|
|
211
262
|
// Controller
|
|
@@ -244,6 +295,8 @@ defineProps({ invoices: Object })
|
|
|
244
295
|
</template>
|
|
245
296
|
```
|
|
246
297
|
|
|
298
|
+
`scroll()` targets the wrapped array for merging, such as `invoices.data`, and follows Inertia's infinite-scroll merge intent header so previous-page requests prepend while next-page requests append.
|
|
299
|
+
|
|
247
300
|
### History Encryption
|
|
248
301
|
|
|
249
302
|
Encrypt sensitive data in browser history:
|
package/index.js
CHANGED
|
@@ -9,32 +9,31 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* @typedef {import('
|
|
13
|
-
* @typedef {import('
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* @typedef {
|
|
18
|
-
* @
|
|
19
|
-
* @
|
|
20
|
-
* @
|
|
21
|
-
*
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @typedef {Object} InertiaPageProps
|
|
26
|
-
* @property {Object.<string, *>} [props] - Page props to pass to the component
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
/**
|
|
12
|
+
* @typedef {import('./lib/types').InertiaRequest} Request
|
|
13
|
+
* @typedef {import('./lib/types').InertiaResponse} Response
|
|
14
|
+
* @typedef {import('./lib/types').InertiaProps} InertiaProps
|
|
15
|
+
* @typedef {import('./lib/types').SailsLike} SailsLike
|
|
16
|
+
* @typedef {import('./lib/types').PropCallback} PropCallback
|
|
17
|
+
* @typedef {import('./lib/types').BadRequestData} BadRequestData
|
|
18
|
+
* @typedef {(req: Request, res: Response, next: () => any) => any} Middleware
|
|
19
|
+
* @typedef {Record<string, any>} InertiaHook
|
|
20
|
+
* @typedef {{ message?: string, stack?: string, name?: string }} ErrorLike
|
|
21
|
+
*
|
|
30
22
|
* @typedef {Object} InertiaRenderData
|
|
31
23
|
* @property {string} page - The component name to render
|
|
32
|
-
* @property {
|
|
33
|
-
* @property {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*
|
|
24
|
+
* @property {InertiaProps} [props] - Props to pass to the component
|
|
25
|
+
* @property {InertiaProps} [locals] - Additional locals for the root EJS template
|
|
26
|
+
*
|
|
27
|
+
* @typedef {Object} DeferOptions
|
|
28
|
+
* @property {boolean} [rescue=false] - Rescue callback failures
|
|
29
|
+
*
|
|
30
|
+
* @typedef {Object} ScrollOptions
|
|
31
|
+
* @property {number} [page=0] - Current page index (0-based)
|
|
32
|
+
* @property {number} [perPage=10] - Items per page
|
|
33
|
+
* @property {number} [total=0] - Total number of items
|
|
34
|
+
* @property {string} [pageName='page'] - Query parameter name for pagination
|
|
35
|
+
* @property {string} [wrapper='data'] - Key to wrap the data in
|
|
36
|
+
* @property {string|null} [matchOn] - Optional field used to match items when merging
|
|
38
37
|
*/
|
|
39
38
|
|
|
40
39
|
const inertia = require('./lib/middleware/inertia-middleware')
|
|
@@ -51,7 +50,12 @@ const ScrollProp = require('./lib/props/scroll-prop')
|
|
|
51
50
|
const handleBadRequest = require('./lib/handle-bad-request')
|
|
52
51
|
const handleServerError = require('./lib/responses/server-error')
|
|
53
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @param {SailsLike} sails
|
|
55
|
+
* @returns {InertiaHook}
|
|
56
|
+
*/
|
|
54
57
|
module.exports = function defineInertiaHook(sails) {
|
|
58
|
+
/** @type {InertiaHook} */
|
|
55
59
|
let hook
|
|
56
60
|
const routesToBindInertiaTo = [
|
|
57
61
|
'GET r|^((?![^?]*\\/[^?\\/]+\\.[^?\\/]+(\\?.*)?).)*$|',
|
|
@@ -66,6 +70,12 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
66
70
|
// Using startup timestamp ensures fresh assets on each server restart
|
|
67
71
|
const startupVersion = Date.now().toString(36)
|
|
68
72
|
|
|
73
|
+
/** @type {Middleware} */
|
|
74
|
+
const runWithRequestContext = (req, res, next) => {
|
|
75
|
+
if (requestContext.getContext()) return next()
|
|
76
|
+
requestContext.run(req, res, next)
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
/**
|
|
70
80
|
* Get asset version from Shipwright manifest.
|
|
71
81
|
* Automatically hashes the manifest content for cache busting.
|
|
@@ -128,16 +138,29 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
128
138
|
// the request is already wrapped in AsyncLocalStorage context.
|
|
129
139
|
if (sails.config.http && sails.config.http.middleware) {
|
|
130
140
|
const mw = sails.config.http.middleware
|
|
131
|
-
|
|
141
|
+
// When order is not explicitly configured, Sails uses a default order
|
|
142
|
+
// internally. We need to set it here so we can inject inertiaContext
|
|
143
|
+
// before the router middleware.
|
|
144
|
+
if (!mw.order) {
|
|
145
|
+
mw.order = [
|
|
146
|
+
'cookieParser',
|
|
147
|
+
'session',
|
|
148
|
+
'bodyParser',
|
|
149
|
+
'compress',
|
|
150
|
+
'poweredBy',
|
|
151
|
+
'router',
|
|
152
|
+
'www',
|
|
153
|
+
'favicon'
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
if (mw.order.indexOf('inertiaContext') === -1) {
|
|
132
157
|
const routerIdx = mw.order.indexOf('router')
|
|
133
158
|
if (routerIdx !== -1) {
|
|
134
159
|
mw.order.splice(routerIdx, 0, 'inertiaContext')
|
|
135
160
|
}
|
|
136
161
|
}
|
|
137
162
|
if (!mw.inertiaContext) {
|
|
138
|
-
mw.inertiaContext =
|
|
139
|
-
requestContext.run(req, res, next)
|
|
140
|
-
}
|
|
163
|
+
mw.inertiaContext = runWithRequestContext
|
|
141
164
|
}
|
|
142
165
|
}
|
|
143
166
|
},
|
|
@@ -167,28 +190,12 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
167
190
|
before: {
|
|
168
191
|
'GET /*': {
|
|
169
192
|
skipAssets: true,
|
|
170
|
-
fn:
|
|
171
|
-
// Skip if context already set up by HTTP middleware
|
|
172
|
-
if (requestContext.getContext()) return next()
|
|
173
|
-
requestContext.run(req, res, next)
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
'POST /*': (req, res, next) => {
|
|
177
|
-
if (requestContext.getContext()) return next()
|
|
178
|
-
requestContext.run(req, res, next)
|
|
193
|
+
fn: runWithRequestContext
|
|
179
194
|
},
|
|
180
|
-
'
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
'PATCH /*': (req, res, next) => {
|
|
185
|
-
if (requestContext.getContext()) return next()
|
|
186
|
-
requestContext.run(req, res, next)
|
|
187
|
-
},
|
|
188
|
-
'DELETE /*': (req, res, next) => {
|
|
189
|
-
if (requestContext.getContext()) return next()
|
|
190
|
-
requestContext.run(req, res, next)
|
|
191
|
-
}
|
|
195
|
+
'POST /*': runWithRequestContext,
|
|
196
|
+
'PUT /*': runWithRequestContext,
|
|
197
|
+
'PATCH /*': runWithRequestContext,
|
|
198
|
+
'DELETE /*': runWithRequestContext
|
|
192
199
|
}
|
|
193
200
|
},
|
|
194
201
|
|
|
@@ -310,7 +317,7 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
310
317
|
* Create an optional prop
|
|
311
318
|
* This allows you to define properties that are only evaluated when accessed.
|
|
312
319
|
* @docs https://docs.sailscasts.com/boring-stack/partial-reloads#lazy-data-evaluation
|
|
313
|
-
* @param {
|
|
320
|
+
* @param {PropCallback} callback - The callback function to execute
|
|
314
321
|
* @returns {OptionalProp} - The optional prop
|
|
315
322
|
*/
|
|
316
323
|
optional(callback) {
|
|
@@ -321,7 +328,7 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
321
328
|
* Create a mergeable prop
|
|
322
329
|
* This allows you to merge multiple props together.
|
|
323
330
|
* @docs https://docs.sailscasts.com/boring-stack/merging-props
|
|
324
|
-
* @param {
|
|
331
|
+
* @param {PropCallback} callback - The callback function to execute
|
|
325
332
|
* @returns {MergeProp} - The mergeable prop
|
|
326
333
|
*/
|
|
327
334
|
merge(callback) {
|
|
@@ -332,7 +339,7 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
332
339
|
* Create an always prop
|
|
333
340
|
* Always props are resolved on every request, whether partial or not.
|
|
334
341
|
* @docs https://docs.sailscasts.com/boring-stack/partial-reloads#lazy-data-evaluation
|
|
335
|
-
* @param {
|
|
342
|
+
* @param {PropCallback} callback - The callback function
|
|
336
343
|
* @returns {AlwaysProp} - The always prop
|
|
337
344
|
*/
|
|
338
345
|
always(callback) {
|
|
@@ -342,12 +349,13 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
342
349
|
* Create a deferred prop
|
|
343
350
|
* This allows you to load certain page data after the initial render.
|
|
344
351
|
* @docs https://docs.sailscasts.com/boring-stack/deferred-props
|
|
345
|
-
* @param {
|
|
346
|
-
* @param {string} group - The group name
|
|
352
|
+
* @param {PropCallback} cb - The callback function to execute
|
|
353
|
+
* @param {string|DeferOptions} group - The group name, or options when no group is needed
|
|
354
|
+
* @param {DeferOptions} options - Deferred prop options
|
|
347
355
|
* @returns {DeferProp} - The deferred prop
|
|
348
356
|
*/
|
|
349
|
-
defer(cb, group = 'default') {
|
|
350
|
-
return new DeferProp(cb, group)
|
|
357
|
+
defer(cb, group = 'default', options = {}) {
|
|
358
|
+
return new DeferProp(cb, group, options)
|
|
351
359
|
},
|
|
352
360
|
|
|
353
361
|
/**
|
|
@@ -356,7 +364,7 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
356
364
|
* The client tracks which props it has via X-Inertia-Except-Once-Props header.
|
|
357
365
|
* Useful for expensive computations that don't change often.
|
|
358
366
|
* @docs https://docs.sailscasts.com/boring-stack/once-props
|
|
359
|
-
* @param {
|
|
367
|
+
* @param {PropCallback} callback - The callback function to execute
|
|
360
368
|
* @returns {OnceProp} - The once prop
|
|
361
369
|
* @example
|
|
362
370
|
* // Basic usage
|
|
@@ -379,7 +387,7 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
379
387
|
* Combines share() and once() - the prop is shared and only resolved once.
|
|
380
388
|
* @docs https://docs.sailscasts.com/boring-stack/once-props#share-once
|
|
381
389
|
* @param {string} key - The key of the property
|
|
382
|
-
* @param {
|
|
390
|
+
* @param {PropCallback} callback - The callback function to execute
|
|
383
391
|
* @returns {OnceProp} - The once prop (for chaining)
|
|
384
392
|
* @example
|
|
385
393
|
* // In a policy or middleware
|
|
@@ -430,7 +438,7 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
430
438
|
* This prevents "phantom" toasts/notifications when users navigate back.
|
|
431
439
|
* Flash data is stored in the session so it persists across redirects.
|
|
432
440
|
* @docs https://docs.sailscasts.com/boring-stack/flash
|
|
433
|
-
* @param {string|
|
|
441
|
+
* @param {string|Record<string, any>} key - The key or an object of key-value pairs
|
|
434
442
|
* @param {*} [value] - The value (if key is a string)
|
|
435
443
|
* @returns {Object} - The hook instance for chaining
|
|
436
444
|
* @example
|
|
@@ -454,7 +462,8 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
454
462
|
if (typeof key === 'object' && key !== null) {
|
|
455
463
|
req.session._inertiaFlash = { ...req.session._inertiaFlash, ...key }
|
|
456
464
|
} else {
|
|
457
|
-
|
|
465
|
+
const flashKey = /** @type {string} */ (key)
|
|
466
|
+
req.session._inertiaFlash[flashKey] = value
|
|
458
467
|
}
|
|
459
468
|
return this
|
|
460
469
|
},
|
|
@@ -471,8 +480,8 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
471
480
|
/**
|
|
472
481
|
* Consume and clear flash data from the session.
|
|
473
482
|
* Called internally by build-page-object after adding to response.
|
|
474
|
-
* @param {
|
|
475
|
-
* @returns {
|
|
483
|
+
* @param {Request} req - The request object
|
|
484
|
+
* @returns {InertiaProps} - The flash data that was consumed
|
|
476
485
|
*/
|
|
477
486
|
consumeFlash(req) {
|
|
478
487
|
const flash = req?.session?._inertiaFlash || {}
|
|
@@ -486,7 +495,7 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
486
495
|
* Create a deep merge prop
|
|
487
496
|
* Like merge(), but recursively merges nested objects instead of replacing them.
|
|
488
497
|
* @docs https://docs.sailscasts.com/boring-stack/merging-props#deep-merge
|
|
489
|
-
* @param {
|
|
498
|
+
* @param {PropCallback} callback - The callback function to execute
|
|
490
499
|
* @returns {MergeProp} - The mergeable prop with deep merge enabled
|
|
491
500
|
* @example
|
|
492
501
|
* // Deep merge nested user preferences
|
|
@@ -500,9 +509,9 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
500
509
|
|
|
501
510
|
/**
|
|
502
511
|
* Render the response
|
|
503
|
-
* @param {
|
|
504
|
-
* @param {
|
|
505
|
-
* @param {
|
|
512
|
+
* @param {Request} req - The request object
|
|
513
|
+
* @param {Response} res - The response object
|
|
514
|
+
* @param {InertiaRenderData} data - The data to render
|
|
506
515
|
* @returns {*} - The rendered response
|
|
507
516
|
*/
|
|
508
517
|
render(req, res, data) {
|
|
@@ -512,8 +521,8 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
512
521
|
* Handle Inertia redirects (external URLs or non-Inertia pages)
|
|
513
522
|
* Forces a full page visit instead of an Inertia XHR request.
|
|
514
523
|
* See https://docs.sailscasts.com/boring-stack/redirects
|
|
515
|
-
* @param {
|
|
516
|
-
* @param {
|
|
524
|
+
* @param {Request} req - The request object
|
|
525
|
+
* @param {Response} res - The response object
|
|
517
526
|
* @param {string} url - The URL to redirect to
|
|
518
527
|
* @returns {Object} - The response object with the redirect
|
|
519
528
|
*/
|
|
@@ -570,12 +579,60 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
570
579
|
return requestContext.getClearHistory()
|
|
571
580
|
},
|
|
572
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Preserve the current URL fragment across a standard Inertia redirect.
|
|
584
|
+
* The flag is stored in the session so it survives the redirect request,
|
|
585
|
+
* then it is consumed by the next Inertia page response.
|
|
586
|
+
*
|
|
587
|
+
* @docs https://docs.sailscasts.com/boring-stack/redirects#preserving-fragments
|
|
588
|
+
* @param {boolean} preserve - Whether to preserve the URL fragment
|
|
589
|
+
* @returns {Object} - The hook instance for chaining
|
|
590
|
+
* @example
|
|
591
|
+
* sails.inertia.preserveFragment()
|
|
592
|
+
* return '/article/new-slug'
|
|
593
|
+
*/
|
|
594
|
+
preserveFragment(preserve = true) {
|
|
595
|
+
const context = requestContext.getContext()
|
|
596
|
+
const req = requestContext.getRequest()
|
|
597
|
+
|
|
598
|
+
if (context) {
|
|
599
|
+
requestContext.setPreserveFragment(preserve)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (req?.session) {
|
|
603
|
+
if (preserve) {
|
|
604
|
+
req.session._inertiaPreserveFragment = true
|
|
605
|
+
} else {
|
|
606
|
+
delete req.session._inertiaPreserveFragment
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return this
|
|
611
|
+
},
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Consume the preserve fragment flag for the current request.
|
|
615
|
+
* @param {Request} req - The request object
|
|
616
|
+
* @returns {boolean} - Whether to preserve the URL fragment
|
|
617
|
+
*/
|
|
618
|
+
consumePreserveFragment(req) {
|
|
619
|
+
const preserve =
|
|
620
|
+
requestContext.getPreserveFragment() ||
|
|
621
|
+
Boolean(req?.session?._inertiaPreserveFragment)
|
|
622
|
+
|
|
623
|
+
if (req?.session) {
|
|
624
|
+
delete req.session._inertiaPreserveFragment
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return preserve
|
|
628
|
+
},
|
|
629
|
+
|
|
573
630
|
/**
|
|
574
631
|
* Handle bad request responses for Inertia.js
|
|
575
632
|
* For Inertia requests with validation errors, redirects back with errors in session.
|
|
576
|
-
* @param {
|
|
577
|
-
* @param {
|
|
578
|
-
* @param {
|
|
633
|
+
* @param {Request} req - The request object
|
|
634
|
+
* @param {Response} res - The response object
|
|
635
|
+
* @param {BadRequestData|Error|Record<string, any>} [optionalData] - Optional error data or Error object
|
|
579
636
|
* @returns {*} - Response (redirect for Inertia, status code for non-Inertia)
|
|
580
637
|
*/
|
|
581
638
|
handleBadRequest(req, res, optionalData) {
|
|
@@ -587,9 +644,9 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
587
644
|
* For Inertia requests in development, displays a styled error modal with stack trace.
|
|
588
645
|
* In production, redirects back with a flash error message.
|
|
589
646
|
* @docs https://docs.sailscasts.com/boring-stack/error-handling
|
|
590
|
-
* @param {
|
|
591
|
-
* @param {
|
|
592
|
-
* @param {
|
|
647
|
+
* @param {Request} req - The request object
|
|
648
|
+
* @param {Response} res - The response object
|
|
649
|
+
* @param {ErrorLike} [error] - Optional error data or Error object
|
|
593
650
|
* @returns {*} - Response (HTML modal for dev Inertia, redirect for prod)
|
|
594
651
|
*/
|
|
595
652
|
handleServerError(req, res, error) {
|
|
@@ -605,13 +662,8 @@ module.exports = function defineInertiaHook(sails) {
|
|
|
605
662
|
* to 1-based for the Inertia client.
|
|
606
663
|
*
|
|
607
664
|
* @docs https://docs.sailscasts.com/boring-stack/infinite-scroll
|
|
608
|
-
* @param {
|
|
609
|
-
* @param {
|
|
610
|
-
* @param {number} [options.page=0] - Current page index (0-based)
|
|
611
|
-
* @param {number} [options.perPage=10] - Items per page
|
|
612
|
-
* @param {number} [options.total=0] - Total number of items
|
|
613
|
-
* @param {string} [options.pageName='page'] - Query parameter name for pagination
|
|
614
|
-
* @param {string} [options.wrapper='data'] - Key to wrap the data in
|
|
665
|
+
* @param {PropCallback} callback - Callback returning the paginated data array
|
|
666
|
+
* @param {ScrollOptions} [options] - Pagination options
|
|
615
667
|
* @returns {ScrollProp} - The scroll prop
|
|
616
668
|
* @example
|
|
617
669
|
* // Basic usage
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('./types').InertiaRequest} InertiaRequest
|
|
3
|
+
* @typedef {import('./types').InertiaResponse} InertiaResponse
|
|
4
|
+
* @typedef {import('./types').BadRequestData} BadRequestData
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* Handle bad request responses for Inertia.js
|
|
3
9
|
*
|
|
@@ -5,9 +11,9 @@
|
|
|
5
11
|
* previous page with errors stored in the session. For non-Inertia requests,
|
|
6
12
|
* it returns a standard 400 response.
|
|
7
13
|
*
|
|
8
|
-
* @param {
|
|
9
|
-
* @param {
|
|
10
|
-
* @param {
|
|
14
|
+
* @param {InertiaRequest} req - Express/Sails request object
|
|
15
|
+
* @param {InertiaResponse} res - Express/Sails response object
|
|
16
|
+
* @param {BadRequestData|Error|Record<string, any>} [optionalData] - Optional error data or Error object
|
|
11
17
|
* @returns {*} - Response (redirect for Inertia, status code for non-Inertia)
|
|
12
18
|
*
|
|
13
19
|
* @example
|
|
@@ -22,13 +28,21 @@ module.exports = function handleBadRequest(req, res, optionalData) {
|
|
|
22
28
|
const statusCodeToSet = 400
|
|
23
29
|
|
|
24
30
|
// Check if it's an Inertia request
|
|
25
|
-
if (req.header('X-Inertia')) {
|
|
26
|
-
if (
|
|
31
|
+
if (req.header?.('X-Inertia')) {
|
|
32
|
+
if (
|
|
33
|
+
optionalData &&
|
|
34
|
+
!(optionalData instanceof Error) &&
|
|
35
|
+
Array.isArray(optionalData.problems)
|
|
36
|
+
) {
|
|
37
|
+
/** @type {Record<string, string[]>} */
|
|
27
38
|
const errors = {}
|
|
28
39
|
optionalData.problems.forEach((problem) => {
|
|
29
40
|
if (typeof problem === 'object') {
|
|
30
41
|
Object.keys(problem).forEach((propertyName) => {
|
|
31
|
-
const sanitizedProblem = problem[propertyName].replace(
|
|
42
|
+
const sanitizedProblem = String(problem[propertyName]).replace(
|
|
43
|
+
/\.$/,
|
|
44
|
+
''
|
|
45
|
+
) // Trim trailing dot
|
|
32
46
|
if (!errors[propertyName]) {
|
|
33
47
|
errors[propertyName] = [sanitizedProblem]
|
|
34
48
|
} else {
|