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
|
@@ -4,23 +4,103 @@ const resolveMergeProps = require('../props/resolve-merge-props')
|
|
|
4
4
|
const { resolveOncePropsMetadata } = require('../props/resolve-once-props')
|
|
5
5
|
const resolvePageProps = require('../props/resolve-page-props')
|
|
6
6
|
const resolveScrollProps = require('../props/resolve-scroll-props')
|
|
7
|
+
const resolveAssetVersion = require('./resolve-asset-version')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} InertiaHookApi
|
|
11
|
+
* @property {() => Object.<string, *>} getShared
|
|
12
|
+
* @property {() => boolean} shouldClearHistory
|
|
13
|
+
* @property {() => boolean} shouldEncryptHistory
|
|
14
|
+
* @property {(req: BuildPageObjectRequest) => boolean} consumePreserveFragment
|
|
15
|
+
* @property {(req: BuildPageObjectRequest) => Object.<string, *>} consumeFlash
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} SailsLike
|
|
20
|
+
* @property {Object.<string, *>} config
|
|
21
|
+
* @property {InertiaHookApi} inertia
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} BuildPageObjectRequest
|
|
26
|
+
* @property {string} [url]
|
|
27
|
+
* @property {string} [originalUrl]
|
|
28
|
+
* @property {SailsLike} _sails
|
|
29
|
+
* @property {(header: string) => string|undefined} get
|
|
30
|
+
*/
|
|
7
31
|
|
|
8
32
|
/**
|
|
9
33
|
* @typedef {Object} InertiaPageObject
|
|
10
34
|
* @property {string} component - The component name to render
|
|
11
35
|
* @property {string} url - The current URL
|
|
12
|
-
* @property {string|number} version - Asset version for cache busting
|
|
36
|
+
* @property {string|number|null} version - Asset version for cache busting
|
|
13
37
|
* @property {Object.<string, *>} props - Resolved page props
|
|
14
|
-
* @property {boolean} clearHistory - Whether to clear browser history
|
|
15
|
-
* @property {boolean} encryptHistory - Whether to encrypt history state
|
|
38
|
+
* @property {boolean} [clearHistory] - Whether to clear browser history
|
|
39
|
+
* @property {boolean} [encryptHistory] - Whether to encrypt history state
|
|
40
|
+
* @property {boolean} [preserveFragment] - Whether to preserve URL fragments across redirects
|
|
41
|
+
* @property {string[]} [sharedProps] - Shared prop keys included in this response
|
|
16
42
|
* @property {string[]} [mergeProps] - Props that should be merged on client
|
|
43
|
+
* @property {string[]} [prependProps] - Props that should be prepended on client
|
|
17
44
|
* @property {string[]} [deepMergeProps] - Props that should be deep merged
|
|
45
|
+
* @property {string[]} [matchPropsOn] - Prop paths to use for matching merge items
|
|
18
46
|
* @property {Object.<string, string[]>} [deferredProps] - Deferred props by group
|
|
47
|
+
* @property {string[]} [rescuedProps] - Deferred props rescued after callback failures
|
|
19
48
|
* @property {Object.<string, *>} [onceProps] - Once-prop metadata
|
|
20
49
|
* @property {Object.<string, *>} [scrollProps] - Scroll props for InfiniteScroll component
|
|
21
50
|
* @property {Object.<string, *>} [flash] - Flash data (not persisted in history)
|
|
22
51
|
*/
|
|
23
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {'mergeProps'|'prependProps'|'deepMergeProps'|'matchPropsOn'} PathMetadataKey
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {InertiaPageObject} page
|
|
59
|
+
* @param {PathMetadataKey} key
|
|
60
|
+
*/
|
|
61
|
+
function removeEmptyArrayMetadata(page, key) {
|
|
62
|
+
if (Array.isArray(page[key]) && page[key].length === 0) {
|
|
63
|
+
delete page[key]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {InertiaPageObject} page
|
|
69
|
+
* @param {string[]} rescuedProps
|
|
70
|
+
*/
|
|
71
|
+
function removeRescuedPropMetadata(page, rescuedProps) {
|
|
72
|
+
if (rescuedProps.length === 0) return
|
|
73
|
+
|
|
74
|
+
const rescuedRootProps = new Set(rescuedProps)
|
|
75
|
+
/** @param {string} path */
|
|
76
|
+
const isNotRescuedPath = (path) => !rescuedRootProps.has(path.split('.')[0])
|
|
77
|
+
/** @type {PathMetadataKey[]} */
|
|
78
|
+
const pathMetadataKeys = [
|
|
79
|
+
'mergeProps',
|
|
80
|
+
'prependProps',
|
|
81
|
+
'deepMergeProps',
|
|
82
|
+
'matchPropsOn'
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
pathMetadataKeys.forEach((key) => {
|
|
86
|
+
if (Array.isArray(page[key])) {
|
|
87
|
+
page[key] = page[key].filter(isNotRescuedPath)
|
|
88
|
+
removeEmptyArrayMetadata(page, key)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const scrollProps = page.scrollProps
|
|
93
|
+
if (scrollProps) {
|
|
94
|
+
rescuedProps.forEach((key) => {
|
|
95
|
+
delete scrollProps[key]
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
if (Object.keys(scrollProps).length === 0) {
|
|
99
|
+
delete page.scrollProps
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
24
104
|
/**
|
|
25
105
|
* Build the Inertia page object for a response.
|
|
26
106
|
*
|
|
@@ -30,42 +110,67 @@ const resolveScrollProps = require('../props/resolve-scroll-props')
|
|
|
30
110
|
* Uses request-scoped shared props (via AsyncLocalStorage) merged with
|
|
31
111
|
* global shared props to prevent data leaking between concurrent requests.
|
|
32
112
|
*
|
|
33
|
-
* @param {
|
|
113
|
+
* @param {BuildPageObjectRequest} req - Express/Sails request object
|
|
34
114
|
* @param {string} component - The component name to render
|
|
35
115
|
* @param {Object.<string, *>} pageProps - Props specific to this page
|
|
36
116
|
* @returns {Promise<InertiaPageObject>} - The complete page object
|
|
37
117
|
*/
|
|
38
118
|
module.exports = async function buildPageObject(req, component, pageProps) {
|
|
39
119
|
const sails = req._sails
|
|
40
|
-
let url = req.url || req.originalUrl
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
120
|
+
let url = req.url || req.originalUrl || '/'
|
|
121
|
+
const currentVersion = resolveAssetVersion(sails)
|
|
122
|
+
|
|
123
|
+
const sharedProps = sails.inertia.getShared()
|
|
124
|
+
const sharedPropKeys = Object.keys(sharedProps)
|
|
44
125
|
|
|
45
126
|
// Merge props: global shared → request-scoped shared → page-specific
|
|
46
127
|
// This ensures user-specific data (from share()) doesn't leak between requests
|
|
47
128
|
const allProps = {
|
|
48
|
-
...
|
|
129
|
+
...sharedProps, // Merges global + request-scoped
|
|
49
130
|
...pageProps
|
|
50
131
|
}
|
|
51
132
|
|
|
52
133
|
const propsToResolve = pickPropsToResolve(req, component, allProps)
|
|
134
|
+
const clearHistory = sails.inertia.shouldClearHistory()
|
|
135
|
+
const encryptHistory = sails.inertia.shouldEncryptHistory()
|
|
136
|
+
const preserveFragment = sails.inertia.consumePreserveFragment(req)
|
|
137
|
+
const resolvedPageProps = await resolvePageProps.withMetadata(propsToResolve)
|
|
53
138
|
|
|
54
139
|
// Build the page object with all metadata
|
|
55
140
|
// Use request-scoped history settings (prevents race conditions)
|
|
141
|
+
/** @type {InertiaPageObject} */
|
|
56
142
|
const page = {
|
|
57
143
|
component,
|
|
58
144
|
url,
|
|
59
145
|
version: currentVersion,
|
|
60
|
-
props:
|
|
61
|
-
clearHistory: sails.inertia.shouldClearHistory(),
|
|
62
|
-
encryptHistory: sails.inertia.shouldEncryptHistory(),
|
|
146
|
+
props: resolvedPageProps.props,
|
|
63
147
|
...resolveMergeProps(req, allProps),
|
|
64
148
|
...resolveDeferredProps(req, component, allProps),
|
|
65
149
|
...resolveOncePropsMetadata(allProps),
|
|
66
150
|
...resolveScrollProps(allProps)
|
|
67
151
|
}
|
|
68
152
|
|
|
153
|
+
if (clearHistory) {
|
|
154
|
+
page.clearHistory = true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (encryptHistory) {
|
|
158
|
+
page.encryptHistory = true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (preserveFragment) {
|
|
162
|
+
page.preserveFragment = true
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (resolvedPageProps.rescuedProps.length > 0) {
|
|
166
|
+
page.rescuedProps = resolvedPageProps.rescuedProps
|
|
167
|
+
removeRescuedPropMetadata(page, resolvedPageProps.rescuedProps)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (sharedPropKeys.length > 0) {
|
|
171
|
+
page.sharedProps = sharedPropKeys
|
|
172
|
+
}
|
|
173
|
+
|
|
69
174
|
// Consume flash data from session and add to props
|
|
70
175
|
// Flash data is included in props.flash so it's accessible via usePage().props.flash
|
|
71
176
|
// Note: Unlike regular props, flash data should NOT be persisted in browser history
|
|
@@ -23,6 +23,8 @@ const PARTIAL_COMPONENT = 'X-Inertia-Partial-Component'
|
|
|
23
23
|
const LOCATION = 'X-Inertia-Location'
|
|
24
24
|
/** @type {string} - Comma-separated list of once-props client already has */
|
|
25
25
|
const EXCEPT_ONCE_PROPS = 'X-Inertia-Except-Once-Props'
|
|
26
|
+
/** @type {string} - InfiniteScroll merge direction */
|
|
27
|
+
const INFINITE_SCROLL_MERGE_INTENT = 'X-Inertia-Infinite-Scroll-Merge-Intent'
|
|
26
28
|
|
|
27
29
|
module.exports = {
|
|
28
30
|
INERTIA,
|
|
@@ -33,5 +35,6 @@ module.exports = {
|
|
|
33
35
|
RESET,
|
|
34
36
|
PARTIAL_COMPONENT,
|
|
35
37
|
LOCATION,
|
|
36
|
-
EXCEPT_ONCE_PROPS
|
|
38
|
+
EXCEPT_ONCE_PROPS,
|
|
39
|
+
INFINITE_SCROLL_MERGE_INTENT
|
|
37
40
|
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
const { PARTIAL_COMPONENT } = require('./inertia-headers')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../types').InertiaRequest} InertiaRequest
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {InertiaRequest} req
|
|
9
|
+
* @param {string} component
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
2
12
|
module.exports = function isInertiaPartialRequest(req, component) {
|
|
3
13
|
return req.get(PARTIAL_COMPONENT) === component
|
|
4
14
|
}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
const { INERTIA } = require('./inertia-headers')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../types').InertiaRequest} InertiaRequest
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {InertiaRequest} req
|
|
9
|
+
* @returns {any}
|
|
10
|
+
*/
|
|
2
11
|
module.exports = function isInertiaRequest(req) {
|
|
3
12
|
return req.get(INERTIA)
|
|
4
13
|
}
|
|
@@ -14,12 +14,31 @@
|
|
|
14
14
|
* sharedLocals: {}, // Request-scoped locals for root EJS template
|
|
15
15
|
* encryptHistory: null, // Request-scoped history encryption (null = use default)
|
|
16
16
|
* clearHistory: false, // Request-scoped clear history flag
|
|
17
|
+
* preserveFragment: false, // Request-scoped fragment preservation flag
|
|
17
18
|
* refreshOnceProps: [], // Props to force-refresh for this request
|
|
18
19
|
* rootView: null // Request-scoped root view template (null = use default)
|
|
19
20
|
* }
|
|
20
21
|
*/
|
|
21
22
|
const { AsyncLocalStorage } = require('async_hooks')
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {import('../types').InertiaRequest} InertiaRequest
|
|
26
|
+
* @typedef {import('../types').InertiaResponse} InertiaResponse
|
|
27
|
+
* @typedef {import('../types').InertiaProps} InertiaProps
|
|
28
|
+
*
|
|
29
|
+
* @typedef {Object} RequestContext
|
|
30
|
+
* @property {InertiaRequest} req
|
|
31
|
+
* @property {InertiaResponse} res
|
|
32
|
+
* @property {InertiaProps} sharedProps
|
|
33
|
+
* @property {InertiaProps} sharedLocals
|
|
34
|
+
* @property {boolean|null} encryptHistory
|
|
35
|
+
* @property {boolean} clearHistory
|
|
36
|
+
* @property {boolean} preserveFragment
|
|
37
|
+
* @property {string[]} refreshOnceProps
|
|
38
|
+
* @property {string|null} rootView
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/** @type {AsyncLocalStorage<RequestContext>} */
|
|
23
42
|
const requestContext = new AsyncLocalStorage()
|
|
24
43
|
|
|
25
44
|
module.exports = {
|
|
@@ -30,12 +49,13 @@ module.exports = {
|
|
|
30
49
|
|
|
31
50
|
/**
|
|
32
51
|
* Run a callback with request context stored
|
|
33
|
-
* @param {
|
|
34
|
-
* @param {
|
|
35
|
-
* @param {
|
|
52
|
+
* @param {InertiaRequest} req - The request object
|
|
53
|
+
* @param {InertiaResponse} res - The response object
|
|
54
|
+
* @param {() => any} callback - The callback to run
|
|
36
55
|
* @returns {*} - The result of the callback
|
|
37
56
|
*/
|
|
38
57
|
run(req, res, callback) {
|
|
58
|
+
/** @type {RequestContext} */
|
|
39
59
|
const context = {
|
|
40
60
|
req,
|
|
41
61
|
res,
|
|
@@ -43,6 +63,7 @@ module.exports = {
|
|
|
43
63
|
sharedLocals: {},
|
|
44
64
|
encryptHistory: null,
|
|
45
65
|
clearHistory: false,
|
|
66
|
+
preserveFragment: false,
|
|
46
67
|
refreshOnceProps: [], // Props to force-refresh for this request
|
|
47
68
|
rootView: null // Request-scoped root view template
|
|
48
69
|
}
|
|
@@ -51,7 +72,7 @@ module.exports = {
|
|
|
51
72
|
|
|
52
73
|
/**
|
|
53
74
|
* Get the full context object
|
|
54
|
-
* @returns {
|
|
75
|
+
* @returns {RequestContext|undefined} - The current context or undefined if not in context
|
|
55
76
|
*/
|
|
56
77
|
getContext() {
|
|
57
78
|
return requestContext.getStore()
|
|
@@ -59,7 +80,7 @@ module.exports = {
|
|
|
59
80
|
|
|
60
81
|
/**
|
|
61
82
|
* Get the current request from context
|
|
62
|
-
* @returns {
|
|
83
|
+
* @returns {InertiaRequest|undefined} - The current request or undefined if not in context
|
|
63
84
|
*/
|
|
64
85
|
getRequest() {
|
|
65
86
|
const context = requestContext.getStore()
|
|
@@ -68,7 +89,7 @@ module.exports = {
|
|
|
68
89
|
|
|
69
90
|
/**
|
|
70
91
|
* Get the current response from context
|
|
71
|
-
* @returns {
|
|
92
|
+
* @returns {InertiaResponse|undefined} - The current response or undefined if not in context
|
|
72
93
|
*/
|
|
73
94
|
getResponse() {
|
|
74
95
|
const context = requestContext.getStore()
|
|
@@ -77,7 +98,7 @@ module.exports = {
|
|
|
77
98
|
|
|
78
99
|
/**
|
|
79
100
|
* Get request-scoped shared props
|
|
80
|
-
* @returns {
|
|
101
|
+
* @returns {InertiaProps} - The shared props for this request
|
|
81
102
|
*/
|
|
82
103
|
getSharedProps() {
|
|
83
104
|
const context = requestContext.getStore()
|
|
@@ -98,7 +119,7 @@ module.exports = {
|
|
|
98
119
|
|
|
99
120
|
/**
|
|
100
121
|
* Get request-scoped shared locals
|
|
101
|
-
* @returns {
|
|
122
|
+
* @returns {InertiaProps} - The shared locals for this request
|
|
102
123
|
*/
|
|
103
124
|
getSharedLocals() {
|
|
104
125
|
const context = requestContext.getStore()
|
|
@@ -157,6 +178,26 @@ module.exports = {
|
|
|
157
178
|
}
|
|
158
179
|
},
|
|
159
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Get request-scoped preserve fragment flag
|
|
183
|
+
* @returns {boolean} - Whether to preserve the URL fragment
|
|
184
|
+
*/
|
|
185
|
+
getPreserveFragment() {
|
|
186
|
+
const context = requestContext.getStore()
|
|
187
|
+
return context?.preserveFragment || false
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Set request-scoped preserve fragment flag
|
|
192
|
+
* @param {boolean} preserve - Whether to preserve the URL fragment
|
|
193
|
+
*/
|
|
194
|
+
setPreserveFragment(preserve) {
|
|
195
|
+
const context = requestContext.getStore()
|
|
196
|
+
if (context) {
|
|
197
|
+
context.preserveFragment = preserve
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
|
|
160
201
|
/**
|
|
161
202
|
* Get the list of once props to force-refresh
|
|
162
203
|
* @returns {string[]} - Array of prop keys to refresh
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../types').SailsLike} SailsLike
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {SailsLike} sails
|
|
7
|
+
* @returns {any}
|
|
8
|
+
*/
|
|
9
|
+
module.exports = function resolveAssetVersion(sails) {
|
|
10
|
+
const assetVersion = sails.config.inertia.version
|
|
11
|
+
return typeof assetVersion === 'function' ? assetVersion() : assetVersion
|
|
12
|
+
}
|
|
@@ -1,29 +1,36 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
const { ERROR_BAG } = require('./inertia-headers')
|
|
3
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../types').InertiaRequest} InertiaRequest
|
|
5
|
+
* @typedef {Record<string, string|string[]>} ValidationErrors
|
|
6
|
+
*/
|
|
7
|
+
|
|
4
8
|
/**
|
|
5
9
|
* @module resolveValidationErrors
|
|
6
10
|
* @description Resolves and formats validation errors from the session for Inertia responses.
|
|
7
11
|
*
|
|
8
|
-
* @param {
|
|
9
|
-
* @returns {
|
|
12
|
+
* @param {InertiaRequest} req - The current HTTP request object.
|
|
13
|
+
* @returns {ValidationErrors} An object representing the validation errors in the desired format.
|
|
10
14
|
*/
|
|
11
15
|
module.exports = function resolveValidationErrors(req) {
|
|
12
16
|
if (!req.session || !req.session.errors) {
|
|
13
17
|
return {}
|
|
14
18
|
}
|
|
15
19
|
|
|
16
|
-
const flashedErrors =
|
|
20
|
+
const flashedErrors = /** @type {Record<string, string[]>} */ (
|
|
21
|
+
req.session.errors
|
|
22
|
+
)
|
|
17
23
|
|
|
18
24
|
const collectedErrors = Object.keys(flashedErrors).reduce((result, bag) => {
|
|
19
25
|
const errorsForBag = flashedErrors[bag]
|
|
26
|
+
/** @type {string[]} */
|
|
20
27
|
const mappedErrors = errorsForBag.map((error) =>
|
|
21
28
|
error.replace(/"([^"]+)"/, '$1')
|
|
22
29
|
)
|
|
23
30
|
// Ensure that single errors are wrapped in an array
|
|
24
31
|
result[bag] = mappedErrors.length > 1 ? mappedErrors : mappedErrors[0]
|
|
25
32
|
return result
|
|
26
|
-
}, {})
|
|
33
|
+
}, /** @type {ValidationErrors} */ ({}))
|
|
27
34
|
|
|
28
35
|
const inertiaErrorBag = req.headers[ERROR_BAG]
|
|
29
36
|
|
package/lib/location.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
const { INERTIA, LOCATION } = require('./helpers/inertia-headers')
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('./types').InertiaRequest} InertiaRequest
|
|
5
|
+
* @typedef {import('./types').InertiaResponse} InertiaResponse
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {InertiaRequest} req
|
|
10
|
+
* @param {InertiaResponse} res
|
|
11
|
+
* @param {string} url
|
|
12
|
+
* @returns {any}
|
|
13
|
+
*/
|
|
3
14
|
module.exports = function (req, res, url) {
|
|
4
15
|
if (req.get(INERTIA)) {
|
|
5
16
|
// If the method is PUT, PATCH, or DELETE, force a 303 redirect (so the next request is a GET)
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const resolveValidationErrors = require('../helpers/resolve-validation-errors')
|
|
2
2
|
const requestContext = require('../helpers/request-context')
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('../types').InertiaRequest} InertiaRequest
|
|
6
|
+
* @typedef {import('../types').InertiaResponse} InertiaResponse
|
|
7
|
+
*/
|
|
8
|
+
|
|
4
9
|
/**
|
|
5
10
|
* Inertia middleware that handles validation errors.
|
|
6
11
|
*
|
|
@@ -11,8 +16,8 @@ const requestContext = require('../helpers/request-context')
|
|
|
11
16
|
* This middleware handles:
|
|
12
17
|
* - Validation errors from redirects (shared as 'errors' prop)
|
|
13
18
|
*
|
|
14
|
-
* @param {
|
|
15
|
-
* @returns {
|
|
19
|
+
* @param {Record<string, any>} hook - The inertia-sails hook instance
|
|
20
|
+
* @returns {(req: InertiaRequest, res: InertiaResponse, next: () => any) => any} Express/Sails middleware function
|
|
16
21
|
*/
|
|
17
22
|
function inertia(hook) {
|
|
18
23
|
return function inertiaMiddleware(req, res, next) {
|
package/lib/props/always-prop.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const MergeableProp = require('./mergeable-prop')
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../types').PropCallback} PropCallback
|
|
5
|
+
*/
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* AlwaysProp - A prop that is always resolved, even during partial reloads.
|
|
5
9
|
*
|
|
@@ -22,11 +26,11 @@ const MergeableProp = require('./mergeable-prop')
|
|
|
22
26
|
module.exports = class AlwaysProp extends MergeableProp {
|
|
23
27
|
/**
|
|
24
28
|
* Create a new AlwaysProp instance
|
|
25
|
-
* @param {
|
|
29
|
+
* @param {PropCallback} callback - The callback function to resolve the prop value
|
|
26
30
|
*/
|
|
27
31
|
constructor(callback) {
|
|
28
32
|
super()
|
|
29
|
-
/** @type {
|
|
33
|
+
/** @type {PropCallback} */
|
|
30
34
|
this.callback = callback
|
|
31
35
|
}
|
|
32
36
|
}
|
package/lib/props/defer-prop.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
const ignoreFirstLoadSymbol = require('../helpers/ignore-first-load-symbol')
|
|
2
2
|
const MergeableProp = require('./mergeable-prop')
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('../types').PropCallback} PropCallback
|
|
6
|
+
*
|
|
7
|
+
* @typedef {Object} DeferPropOptions
|
|
8
|
+
* @property {boolean} [rescue=false] - Whether callback failures should be rescued
|
|
9
|
+
*/
|
|
10
|
+
|
|
4
11
|
/**
|
|
5
12
|
* DeferProp - A prop that loads after the initial page render.
|
|
6
13
|
*
|
|
@@ -30,17 +37,30 @@ const MergeableProp = require('./mergeable-prop')
|
|
|
30
37
|
module.exports = class DeferProp extends MergeableProp {
|
|
31
38
|
/**
|
|
32
39
|
* Create a new DeferProp instance
|
|
33
|
-
* @param {
|
|
34
|
-
* @param {string} [group='default'] - The group name for loading props together
|
|
40
|
+
* @param {PropCallback} callback - The callback function to resolve the prop value
|
|
41
|
+
* @param {string|DeferPropOptions} [group='default'] - The group name for loading props together, or options
|
|
42
|
+
* @param {DeferPropOptions} [options] - Deferred prop options
|
|
35
43
|
*/
|
|
36
|
-
constructor(callback, group) {
|
|
44
|
+
constructor(callback, group = 'default', options = {}) {
|
|
37
45
|
super()
|
|
38
|
-
|
|
46
|
+
if (typeof group === 'object' && group !== null) {
|
|
47
|
+
options = group
|
|
48
|
+
group = 'default'
|
|
49
|
+
}
|
|
50
|
+
const groupName = typeof group === 'string' ? group : 'default'
|
|
51
|
+
|
|
52
|
+
/** @type {PropCallback} */
|
|
39
53
|
this.callback = callback
|
|
40
54
|
/** @type {string} */
|
|
41
|
-
this.group =
|
|
55
|
+
this.group = groupName
|
|
42
56
|
/** @type {boolean} */
|
|
43
|
-
this
|
|
57
|
+
this.shouldRescue = options.rescue === true
|
|
58
|
+
Object.defineProperty(this, ignoreFirstLoadSymbol, {
|
|
59
|
+
value: true,
|
|
60
|
+
enumerable: true,
|
|
61
|
+
configurable: true,
|
|
62
|
+
writable: true
|
|
63
|
+
})
|
|
44
64
|
}
|
|
45
65
|
|
|
46
66
|
/**
|
|
@@ -50,4 +70,24 @@ module.exports = class DeferProp extends MergeableProp {
|
|
|
50
70
|
getGroup() {
|
|
51
71
|
return this.group
|
|
52
72
|
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Rescue callback failures instead of failing the whole deferred response.
|
|
76
|
+
* The failed prop is omitted from props and reported in rescuedProps.
|
|
77
|
+
*
|
|
78
|
+
* @param {boolean} rescue - Whether callback failures should be rescued
|
|
79
|
+
* @returns {DeferProp} - Returns this for chaining
|
|
80
|
+
*/
|
|
81
|
+
rescue(rescue = true) {
|
|
82
|
+
this.shouldRescue = rescue
|
|
83
|
+
return this
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get whether this deferred prop should rescue callback failures.
|
|
88
|
+
* @returns {boolean} - Whether callback failures should be rescued
|
|
89
|
+
*/
|
|
90
|
+
shouldRescueProp() {
|
|
91
|
+
return this.shouldRescue
|
|
92
|
+
}
|
|
53
93
|
}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../types').InertiaProps} InertiaProps
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {InertiaProps} props
|
|
7
|
+
* @param {string[]} [only]
|
|
8
|
+
* @returns {InertiaProps}
|
|
9
|
+
*/
|
|
1
10
|
module.exports = function getPartialData(props, only = []) {
|
|
2
11
|
return Object.assign({}, ...only.map((key) => ({ [key]: props[key] })))
|
|
3
12
|
}
|
package/lib/props/merge-prop.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const MergeableProp = require('./mergeable-prop')
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../types').PropCallback} PropCallback
|
|
5
|
+
*/
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* MergeProp - A prop that merges with existing data during partial reloads.
|
|
5
9
|
*
|
|
@@ -22,14 +26,13 @@ const MergeableProp = require('./mergeable-prop')
|
|
|
22
26
|
module.exports = class MergeProp extends MergeableProp {
|
|
23
27
|
/**
|
|
24
28
|
* Create a new MergeProp instance
|
|
25
|
-
* @param {
|
|
29
|
+
* @param {PropCallback} callback - The callback function to resolve the prop value
|
|
26
30
|
*/
|
|
27
31
|
constructor(callback) {
|
|
28
32
|
super()
|
|
29
|
-
/** @type {
|
|
33
|
+
/** @type {PropCallback} */
|
|
30
34
|
this.callback = callback
|
|
31
|
-
|
|
32
|
-
this.shouldMerge = true
|
|
35
|
+
this.merge()
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|