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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for describing how mergeable props should be merged by the client.
|
|
3
|
+
*
|
|
4
|
+
* @typedef {import('../types').MergeOperation} MergeOperation
|
|
5
|
+
* @typedef {import('../types').MergeOptions} MergeOptions
|
|
6
|
+
* @typedef {string|string[]|Record<string, string|null>|null} MergeTargetInput
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @returns {MergeOperation}
|
|
11
|
+
*/
|
|
12
|
+
function createDefaultMergeOperation() {
|
|
13
|
+
return {
|
|
14
|
+
direction: 'append',
|
|
15
|
+
path: null,
|
|
16
|
+
matchOn: null,
|
|
17
|
+
isDefault: true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {MergeOptions|string|null|undefined} options
|
|
23
|
+
* @returns {MergeOptions}
|
|
24
|
+
*/
|
|
25
|
+
function normalizeMergeOptions(options) {
|
|
26
|
+
return typeof options === 'string' ? { matchOn: options } : options || {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {MergeTargetInput} paths
|
|
31
|
+
* @param {MergeOptions|string} [options]
|
|
32
|
+
* @returns {Array<{ path: string|null, matchOn: string|null }>}
|
|
33
|
+
*/
|
|
34
|
+
function normalizeMergeTargets(paths, options = {}) {
|
|
35
|
+
const normalizedOptions = normalizeMergeOptions(options)
|
|
36
|
+
|
|
37
|
+
if (paths === null || paths === undefined) {
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
path: null,
|
|
41
|
+
matchOn:
|
|
42
|
+
typeof normalizedOptions.matchOn === 'string'
|
|
43
|
+
? normalizedOptions.matchOn
|
|
44
|
+
: null
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (Array.isArray(paths)) {
|
|
50
|
+
return paths.map((path) => ({
|
|
51
|
+
path: normalizePath(path),
|
|
52
|
+
matchOn: resolveTargetMatchOn(path, normalizedOptions)
|
|
53
|
+
}))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof paths === 'object') {
|
|
57
|
+
return Object.entries(paths).map(([path, matchOn]) => ({
|
|
58
|
+
path: normalizePath(path),
|
|
59
|
+
matchOn: matchOn || null
|
|
60
|
+
}))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
path: normalizePath(paths),
|
|
66
|
+
matchOn: resolveTargetMatchOn(paths, normalizedOptions)
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {string} path
|
|
73
|
+
* @returns {string|null}
|
|
74
|
+
*/
|
|
75
|
+
function normalizePath(path) {
|
|
76
|
+
return path === '' ? null : path
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {string} path
|
|
81
|
+
* @param {MergeOptions} options
|
|
82
|
+
* @returns {string|null}
|
|
83
|
+
*/
|
|
84
|
+
function resolveTargetMatchOn(path, options) {
|
|
85
|
+
if (!options.matchOn) return null
|
|
86
|
+
if (typeof options.matchOn === 'object') return options.matchOn[path] || null
|
|
87
|
+
return options.matchOn
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} key
|
|
92
|
+
* @param {string|null} path
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
function resolvePropPath(key, path) {
|
|
96
|
+
return path ? `${key}.${path}` : key
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @template T
|
|
101
|
+
* @param {T[]} values
|
|
102
|
+
* @returns {T[]}
|
|
103
|
+
*/
|
|
104
|
+
function unique(values) {
|
|
105
|
+
return [...new Set(values)]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
createDefaultMergeOperation,
|
|
110
|
+
normalizeMergeOptions,
|
|
111
|
+
normalizeMergeTargets,
|
|
112
|
+
resolvePropPath,
|
|
113
|
+
unique
|
|
114
|
+
}
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
const {
|
|
2
|
+
createDefaultMergeOperation,
|
|
3
|
+
normalizeMergeOptions,
|
|
4
|
+
normalizeMergeTargets
|
|
5
|
+
} = require('./merge-targets')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('../types').MergeOperation} MergeOperation
|
|
9
|
+
* @typedef {import('../types').MergeOptions} MergeOptions
|
|
10
|
+
* @typedef {string|string[]|Record<string, string|null>|null} MergeTargetInput
|
|
11
|
+
*/
|
|
12
|
+
|
|
1
13
|
/**
|
|
2
14
|
* MergeableProp - Base class for props that can be merged during partial reloads.
|
|
3
15
|
*
|
|
@@ -12,6 +24,10 @@ module.exports = class MergeableProp {
|
|
|
12
24
|
this.shouldMerge = false
|
|
13
25
|
/** @type {boolean} - Whether to deep merge this prop */
|
|
14
26
|
this.shouldDeepMerge = false
|
|
27
|
+
/** @type {MergeOperation[]} */
|
|
28
|
+
this.mergeOperations = []
|
|
29
|
+
/** @type {string[]} - Paths to use when matching merge items */
|
|
30
|
+
this.matchOnPaths = []
|
|
15
31
|
}
|
|
16
32
|
|
|
17
33
|
/**
|
|
@@ -21,9 +37,33 @@ module.exports = class MergeableProp {
|
|
|
21
37
|
*/
|
|
22
38
|
merge() {
|
|
23
39
|
this.shouldMerge = true
|
|
40
|
+
this.shouldDeepMerge = false
|
|
41
|
+
if (this.mergeOperations.length === 0) {
|
|
42
|
+
this.mergeOperations.push(createDefaultMergeOperation())
|
|
43
|
+
}
|
|
24
44
|
return this
|
|
25
45
|
}
|
|
26
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Append this prop, or one or more nested paths, during partial reloads.
|
|
49
|
+
* @param {MergeTargetInput} [paths] - Path(s) to append, or a path-to-matchOn map
|
|
50
|
+
* @param {MergeOptions|string} [options] - Options, or a matchOn string
|
|
51
|
+
* @returns {MergeableProp} - Returns this for chaining
|
|
52
|
+
*/
|
|
53
|
+
append(paths = null, options = {}) {
|
|
54
|
+
return this._addMergeOperations('append', paths, options)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Prepend this prop, or one or more nested paths, during partial reloads.
|
|
59
|
+
* @param {MergeTargetInput} [paths] - Path(s) to prepend, or a path-to-matchOn map
|
|
60
|
+
* @param {MergeOptions|string} [options] - Options, or a matchOn string
|
|
61
|
+
* @returns {MergeableProp} - Returns this for chaining
|
|
62
|
+
*/
|
|
63
|
+
prepend(paths = null, options = {}) {
|
|
64
|
+
return this._addMergeOperations('prepend', paths, options)
|
|
65
|
+
}
|
|
66
|
+
|
|
27
67
|
/**
|
|
28
68
|
* Enable deep merging for this prop.
|
|
29
69
|
* Recursively merges nested objects instead of replacing them.
|
|
@@ -32,6 +72,53 @@ module.exports = class MergeableProp {
|
|
|
32
72
|
deepMerge() {
|
|
33
73
|
this.shouldMerge = true
|
|
34
74
|
this.shouldDeepMerge = true
|
|
75
|
+
this.mergeOperations = []
|
|
35
76
|
return this
|
|
36
77
|
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Configure one or more match-on paths for merge operations.
|
|
81
|
+
* @param {string|string[]} paths - Path(s) ending with the field to match on
|
|
82
|
+
* @returns {MergeableProp} - Returns this for chaining
|
|
83
|
+
*/
|
|
84
|
+
matchOn(paths) {
|
|
85
|
+
const normalizedPaths = Array.isArray(paths) ? paths : [paths]
|
|
86
|
+
normalizedPaths.filter(Boolean).forEach((path) => {
|
|
87
|
+
this.matchOnPaths.push(path)
|
|
88
|
+
})
|
|
89
|
+
return this
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {'append'|'prepend'} direction
|
|
94
|
+
* @param {MergeTargetInput} paths
|
|
95
|
+
* @param {MergeOptions|string} options
|
|
96
|
+
* @returns {MergeableProp}
|
|
97
|
+
*/
|
|
98
|
+
_addMergeOperations(direction, paths, options) {
|
|
99
|
+
const normalizedOptions = normalizeMergeOptions(options)
|
|
100
|
+
|
|
101
|
+
this.shouldMerge = true
|
|
102
|
+
this.shouldDeepMerge = false
|
|
103
|
+
this._clearDefaultMergeOperation()
|
|
104
|
+
|
|
105
|
+
normalizeMergeTargets(paths, normalizedOptions).forEach((target) => {
|
|
106
|
+
this.mergeOperations.push({
|
|
107
|
+
direction,
|
|
108
|
+
path: target.path,
|
|
109
|
+
matchOn: target.matchOn
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return this
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_clearDefaultMergeOperation() {
|
|
117
|
+
if (
|
|
118
|
+
this.mergeOperations.length === 1 &&
|
|
119
|
+
this.mergeOperations[0].isDefault
|
|
120
|
+
) {
|
|
121
|
+
this.mergeOperations = []
|
|
122
|
+
}
|
|
123
|
+
}
|
|
37
124
|
}
|
package/lib/props/once-prop.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../types').PropCallback} PropCallback
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* OnceProp - A prop that is resolved only once and cached across navigations.
|
|
3
7
|
*
|
|
@@ -33,12 +37,16 @@
|
|
|
33
37
|
module.exports = class OnceProp {
|
|
34
38
|
/**
|
|
35
39
|
* Create a new OnceProp instance
|
|
36
|
-
* @param {
|
|
40
|
+
* @param {PropCallback} callback - The callback function to resolve the prop value
|
|
37
41
|
*/
|
|
38
42
|
constructor(callback) {
|
|
43
|
+
/** @type {PropCallback} */
|
|
39
44
|
this.callback = callback
|
|
45
|
+
/** @type {string|null} */
|
|
40
46
|
this._key = null
|
|
47
|
+
/** @type {number|null} */
|
|
41
48
|
this._ttl = null
|
|
49
|
+
/** @type {boolean} */
|
|
42
50
|
this._refresh = false
|
|
43
51
|
}
|
|
44
52
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const ignoreFirstLoadSymbol = require('../helpers/ignore-first-load-symbol')
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../types').PropCallback} PropCallback
|
|
5
|
+
*/
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* OptionalProp - A prop that is only evaluated when explicitly requested.
|
|
5
9
|
*
|
|
@@ -20,12 +24,16 @@ const ignoreFirstLoadSymbol = require('../helpers/ignore-first-load-symbol')
|
|
|
20
24
|
module.exports = class OptionalProp {
|
|
21
25
|
/**
|
|
22
26
|
* Create a new OptionalProp instance
|
|
23
|
-
* @param {
|
|
27
|
+
* @param {PropCallback} callback - The callback function to resolve the prop value
|
|
24
28
|
*/
|
|
25
29
|
constructor(callback) {
|
|
26
|
-
/** @type {
|
|
30
|
+
/** @type {PropCallback} */
|
|
27
31
|
this.callback = callback
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
Object.defineProperty(this, ignoreFirstLoadSymbol, {
|
|
33
|
+
value: true,
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
writable: true
|
|
37
|
+
})
|
|
30
38
|
}
|
|
31
39
|
}
|
|
@@ -7,6 +7,17 @@ const resolveExceptProps = require('./resolve-except-props')
|
|
|
7
7
|
const { filterOnceProps } = require('./resolve-once-props')
|
|
8
8
|
const AlwaysProp = require('./always-prop')
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('../types').InertiaRequest} InertiaRequest
|
|
12
|
+
* @typedef {import('../types').InertiaProps} InertiaProps
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {InertiaRequest} req
|
|
17
|
+
* @param {string} component
|
|
18
|
+
* @param {InertiaProps} [props]
|
|
19
|
+
* @returns {InertiaProps}
|
|
20
|
+
*/
|
|
10
21
|
module.exports = function pickPropsToResolve(req, component, props = {}) {
|
|
11
22
|
const isPartial = isInertiaPartialRequest(req, component)
|
|
12
23
|
const isInertia = isInertiaRequest(req)
|
|
@@ -15,7 +26,9 @@ module.exports = function pickPropsToResolve(req, component, props = {}) {
|
|
|
15
26
|
if (!isPartial) {
|
|
16
27
|
newProps = Object.fromEntries(
|
|
17
28
|
Object.entries(props).filter(([_, value]) => {
|
|
18
|
-
if (value && value[ignoreFirstLoadSymbol])
|
|
29
|
+
if (value && /** @type {any} */ (value)[ignoreFirstLoadSymbol]) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
19
32
|
return true
|
|
20
33
|
})
|
|
21
34
|
)
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
const isInertiaPartialRequest = require('../helpers/is-inertia-partial-request')
|
|
2
2
|
const DeferProp = require('./defer-prop')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('../types').InertiaRequest} InertiaRequest
|
|
6
|
+
* @typedef {import('../types').InertiaProps} InertiaProps
|
|
7
|
+
*
|
|
8
|
+
* @typedef {Object} DeferredPropsMetadata
|
|
9
|
+
* @property {Record<string, string[]>} [deferredProps]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {InertiaRequest} req
|
|
14
|
+
* @param {string} component
|
|
15
|
+
* @param {InertiaProps} pageProps
|
|
16
|
+
* @returns {DeferredPropsMetadata}
|
|
17
|
+
*/
|
|
3
18
|
module.exports = function resolveDeferredProps(req, component, pageProps) {
|
|
4
19
|
if (isInertiaPartialRequest(req, component)) return {}
|
|
20
|
+
/** @type {Record<string, string[]>} */
|
|
5
21
|
const deferredProps = Object.entries(pageProps || {})
|
|
6
22
|
.filter(([_, value]) => value instanceof DeferProp)
|
|
7
23
|
.map(([key, value]) => ({ key, group: value.getGroup() }))
|
|
@@ -9,7 +25,7 @@ module.exports = function resolveDeferredProps(req, component, pageProps) {
|
|
|
9
25
|
if (!groups[group]) groups[group] = []
|
|
10
26
|
groups[group].push(key)
|
|
11
27
|
return groups
|
|
12
|
-
}, {})
|
|
28
|
+
}, /** @type {Record<string, string[]>} */ ({}))
|
|
13
29
|
|
|
14
30
|
return Object.keys(deferredProps).length ? { deferredProps } : {}
|
|
15
31
|
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
const { PARTIAL_EXCEPT } = require('../helpers/inertia-headers')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../types').InertiaRequest} InertiaRequest
|
|
5
|
+
* @typedef {import('../types').InertiaProps} InertiaProps
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {InertiaRequest} req
|
|
10
|
+
* @param {InertiaProps} props
|
|
11
|
+
* @returns {InertiaProps}
|
|
12
|
+
*/
|
|
2
13
|
module.exports = function resolveExceptProps(req, props) {
|
|
3
14
|
const partialExceptHeader = req.get(PARTIAL_EXCEPT)
|
|
4
15
|
const except = partialExceptHeader.split(',').filter(Boolean)
|
|
@@ -1,37 +1,98 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
INFINITE_SCROLL_MERGE_INTENT,
|
|
3
|
+
RESET
|
|
4
|
+
} = require('../helpers/inertia-headers')
|
|
2
5
|
const MergeableProp = require('./mergeable-prop')
|
|
6
|
+
const { resolvePropPath, unique } = require('./merge-targets')
|
|
7
|
+
const ScrollProp = require('./scroll-prop')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import('../types').InertiaProps} InertiaProps
|
|
11
|
+
* @typedef {{ get: (header: string) => any }} HeaderRequest
|
|
12
|
+
*
|
|
13
|
+
* @typedef {Object} MergePropsMetadata
|
|
14
|
+
* @property {string[]} [mergeProps]
|
|
15
|
+
* @property {string[]} [prependProps]
|
|
16
|
+
* @property {string[]} [deepMergeProps]
|
|
17
|
+
* @property {string[]} [matchPropsOn]
|
|
18
|
+
*/
|
|
3
19
|
|
|
4
20
|
/**
|
|
5
21
|
* Resolve merge props metadata for the page response.
|
|
6
22
|
* Returns mergeProps and deepMergeProps arrays for the client.
|
|
7
|
-
* @param {
|
|
8
|
-
* @param {
|
|
9
|
-
* @returns {
|
|
23
|
+
* @param {HeaderRequest} req - The request object
|
|
24
|
+
* @param {InertiaProps} pageProps - The page props
|
|
25
|
+
* @returns {MergePropsMetadata} - Object with mergeProps and/or deepMergeProps arrays
|
|
10
26
|
*/
|
|
11
27
|
module.exports = function resolveMergeProps(req, pageProps) {
|
|
12
28
|
const inertiaResetHeader = req.get(RESET)
|
|
13
29
|
const resetProps = new Set(inertiaResetHeader?.split(',').filter(Boolean))
|
|
30
|
+
const infiniteScrollMergeIntent = req.get(INFINITE_SCROLL_MERGE_INTENT)
|
|
31
|
+
|
|
32
|
+
/** @type {string[]} */
|
|
33
|
+
const mergeProps = []
|
|
34
|
+
/** @type {string[]} */
|
|
35
|
+
const prependProps = []
|
|
36
|
+
/** @type {string[]} */
|
|
37
|
+
const deepMergeProps = []
|
|
38
|
+
/** @type {string[]} */
|
|
39
|
+
const matchPropsOn = []
|
|
40
|
+
|
|
41
|
+
Object.entries(pageProps || {}).forEach(([key, value]) => {
|
|
42
|
+
if (!(value instanceof MergeableProp) || !value.shouldMerge) return
|
|
43
|
+
if (resetProps.has(key)) return
|
|
44
|
+
|
|
45
|
+
if (value instanceof ScrollProp) {
|
|
46
|
+
const propPath = resolvePropPath(key, value.wrapper)
|
|
47
|
+
if (resetProps.has(propPath)) return
|
|
48
|
+
|
|
49
|
+
if (infiniteScrollMergeIntent === 'prepend') {
|
|
50
|
+
prependProps.push(propPath)
|
|
51
|
+
} else {
|
|
52
|
+
mergeProps.push(propPath)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (value.matchOnPath) {
|
|
56
|
+
matchPropsOn.push(resolvePropPath(propPath, value.matchOnPath))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (value.shouldDeepMerge) {
|
|
63
|
+
deepMergeProps.push(key)
|
|
64
|
+
value.matchOnPaths.forEach((path) => {
|
|
65
|
+
matchPropsOn.push(resolvePropPath(key, path))
|
|
66
|
+
})
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
value.mergeOperations.forEach((operation) => {
|
|
71
|
+
const propPath = resolvePropPath(key, operation.path)
|
|
72
|
+
if (resetProps.has(propPath)) return
|
|
14
73
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
)
|
|
74
|
+
if (operation.direction === 'prepend') {
|
|
75
|
+
prependProps.push(propPath)
|
|
76
|
+
} else {
|
|
77
|
+
mergeProps.push(propPath)
|
|
78
|
+
}
|
|
21
79
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
80
|
+
if (operation.matchOn) {
|
|
81
|
+
matchPropsOn.push(resolvePropPath(propPath, operation.matchOn))
|
|
82
|
+
}
|
|
83
|
+
})
|
|
26
84
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
85
|
+
value.matchOnPaths.forEach((path) => {
|
|
86
|
+
matchPropsOn.push(resolvePropPath(key, path))
|
|
87
|
+
})
|
|
88
|
+
})
|
|
31
89
|
|
|
90
|
+
/** @type {MergePropsMetadata} */
|
|
32
91
|
const result = {}
|
|
33
|
-
if (mergeProps.length) result.mergeProps = mergeProps
|
|
34
|
-
if (
|
|
92
|
+
if (mergeProps.length) result.mergeProps = unique(mergeProps)
|
|
93
|
+
if (prependProps.length) result.prependProps = unique(prependProps)
|
|
94
|
+
if (deepMergeProps.length) result.deepMergeProps = unique(deepMergeProps)
|
|
95
|
+
if (matchPropsOn.length) result.matchPropsOn = unique(matchPropsOn)
|
|
35
96
|
|
|
36
97
|
return result
|
|
37
98
|
}
|
|
@@ -2,14 +2,22 @@ const OnceProp = require('./once-prop')
|
|
|
2
2
|
const { EXCEPT_ONCE_PROPS } = require('../helpers/inertia-headers')
|
|
3
3
|
const requestContext = require('../helpers/request-context')
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('../types').InertiaRequest} InertiaRequest
|
|
7
|
+
* @typedef {import('../types').InertiaProps} InertiaProps
|
|
8
|
+
*
|
|
9
|
+
* @typedef {Object} OncePropsMetadata
|
|
10
|
+
* @property {Record<string, { prop: string, expiresAt: number|null }>} [onceProps]
|
|
11
|
+
*/
|
|
12
|
+
|
|
5
13
|
/**
|
|
6
14
|
* Get the list of once-props that the client already has cached.
|
|
7
15
|
* These are sent via the X-Inertia-Except-Once-Props header.
|
|
8
|
-
* @param {
|
|
16
|
+
* @param {InertiaRequest} req - The request object
|
|
9
17
|
* @returns {string[]} - Array of prop keys the client already has
|
|
10
18
|
*/
|
|
11
19
|
function getExceptOnceProps(req) {
|
|
12
|
-
const header = req.get(EXCEPT_ONCE_PROPS) || ''
|
|
20
|
+
const header = String(req.get(EXCEPT_ONCE_PROPS) || '')
|
|
13
21
|
return header
|
|
14
22
|
? header
|
|
15
23
|
.split(',')
|
|
@@ -28,9 +36,9 @@ function getExceptOnceProps(req) {
|
|
|
28
36
|
* - They are marked for refresh via sails.inertia.refreshOnce()
|
|
29
37
|
* - The client doesn't have them cached
|
|
30
38
|
*
|
|
31
|
-
* @param {
|
|
32
|
-
* @param {
|
|
33
|
-
* @returns {
|
|
39
|
+
* @param {InertiaRequest} req - The request object
|
|
40
|
+
* @param {InertiaProps} props - The props object
|
|
41
|
+
* @returns {InertiaProps} - Filtered props with cached once-props removed
|
|
34
42
|
*/
|
|
35
43
|
function filterOnceProps(req, props) {
|
|
36
44
|
const exceptOnceProps = getExceptOnceProps(req)
|
|
@@ -40,6 +48,7 @@ function filterOnceProps(req, props) {
|
|
|
40
48
|
return props
|
|
41
49
|
}
|
|
42
50
|
|
|
51
|
+
/** @type {InertiaProps} */
|
|
43
52
|
const filtered = {}
|
|
44
53
|
for (const [key, value] of Object.entries(props)) {
|
|
45
54
|
// Keep the prop if it's not a OnceProp
|
|
@@ -75,10 +84,11 @@ function filterOnceProps(req, props) {
|
|
|
75
84
|
/**
|
|
76
85
|
* Build the onceProps metadata for the page response.
|
|
77
86
|
* This tells the client which props are "once" props and their expiration.
|
|
78
|
-
* @param {
|
|
79
|
-
* @returns {
|
|
87
|
+
* @param {InertiaProps} props - The props object
|
|
88
|
+
* @returns {OncePropsMetadata} - Object with onceProps key if any exist, empty object otherwise
|
|
80
89
|
*/
|
|
81
90
|
function resolveOncePropsMetadata(props) {
|
|
91
|
+
/** @type {Record<string, { prop: string, expiresAt: number|null }>} */
|
|
82
92
|
const onceProps = {}
|
|
83
93
|
|
|
84
94
|
for (const [key, value] of Object.entries(props)) {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
const { PARTIAL_DATA } = require('../helpers/inertia-headers')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../types').InertiaRequest} InertiaRequest
|
|
5
|
+
* @typedef {import('../types').InertiaProps} InertiaProps
|
|
6
|
+
*/
|
|
7
|
+
|
|
2
8
|
/**
|
|
3
9
|
* Extracts only the props specified by the partial header from the given props object.
|
|
4
10
|
*
|
|
@@ -6,13 +12,14 @@ const { PARTIAL_DATA } = require('../helpers/inertia-headers')
|
|
|
6
12
|
* which should contain a comma-separated list of property keys. It then constructs and returns
|
|
7
13
|
* a new object that includes only those keys from the provided props object.
|
|
8
14
|
*
|
|
9
|
-
* @param {
|
|
10
|
-
* @param {
|
|
11
|
-
* @returns {
|
|
15
|
+
* @param {InertiaRequest} req - The Express-style request object. It must have a `get` method to retrieve headers.
|
|
16
|
+
* @param {InertiaProps} props - The complete set of props.
|
|
17
|
+
* @returns {InertiaProps} An object containing only the properties whose keys were specified in the partial header.
|
|
12
18
|
*/
|
|
13
19
|
module.exports = function resolveOnlyProps(req, props) {
|
|
14
20
|
const partialOnlyHeader = req.get(PARTIAL_DATA)
|
|
15
21
|
const only = partialOnlyHeader.split(',').filter(Boolean)
|
|
22
|
+
/** @type {InertiaProps} */
|
|
16
23
|
let newProps = {}
|
|
17
24
|
|
|
18
25
|
for (const key of only) newProps[key] = props[key]
|
|
@@ -1,4 +1,64 @@
|
|
|
1
|
+
const DeferProp = require('./defer-prop')
|
|
1
2
|
const resolveProp = require('./resolve-prop')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('../types').InertiaProps} InertiaProps
|
|
6
|
+
* @typedef {import('../types').ResolvedPageProps} ResolvedPageProps
|
|
7
|
+
*
|
|
8
|
+
* @typedef {((props?: InertiaProps) => Promise<InertiaProps>) & {
|
|
9
|
+
* withMetadata: (props?: InertiaProps) => Promise<ResolvedPageProps>
|
|
10
|
+
* }} ResolvePageProps
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {any} value
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
function shouldRescueProp(value) {
|
|
18
|
+
return value instanceof DeferProp && value.shouldRescueProp()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {InertiaProps} [props]
|
|
23
|
+
* @returns {Promise<ResolvedPageProps>}
|
|
24
|
+
*/
|
|
25
|
+
async function resolvePagePropsWithMetadata(props = {}) {
|
|
26
|
+
const resolved = await Promise.all(
|
|
27
|
+
Object.entries(props).map(async ([key, value]) => {
|
|
28
|
+
try {
|
|
29
|
+
if (typeof value === 'function') {
|
|
30
|
+
const result = await value()
|
|
31
|
+
return { entry: await resolveProp(key, result) }
|
|
32
|
+
}
|
|
33
|
+
return { entry: await resolveProp(key, value) }
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (shouldRescueProp(value)) {
|
|
36
|
+
return { rescuedProp: key }
|
|
37
|
+
}
|
|
38
|
+
throw error
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
/** @type {Array<[string, any]>} */
|
|
44
|
+
const entries = []
|
|
45
|
+
/** @type {string[]} */
|
|
46
|
+
const rescuedProps = []
|
|
47
|
+
|
|
48
|
+
for (const result of resolved) {
|
|
49
|
+
if (result.rescuedProp) {
|
|
50
|
+
rescuedProps.push(result.rescuedProp)
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
entries.push(result.entry)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
props: Object.fromEntries(entries),
|
|
58
|
+
rescuedProps
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
2
62
|
/**
|
|
3
63
|
* Resolve all page props.
|
|
4
64
|
*
|
|
@@ -7,18 +67,17 @@ const resolveProp = require('./resolve-prop')
|
|
|
7
67
|
* Then, every property is passed to resolveProp() to see if it needs
|
|
8
68
|
* any special handling.
|
|
9
69
|
*
|
|
10
|
-
* @param {
|
|
11
|
-
* @returns {Promise<
|
|
70
|
+
* @param {InertiaProps} [props={}] - An object containing page props.
|
|
71
|
+
* @returns {Promise<InertiaProps>} A promise that resolves to a new object with resolved props.
|
|
12
72
|
*/
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
if (typeof value === 'function') {
|
|
17
|
-
const result = await value()
|
|
18
|
-
return resolveProp(key, result)
|
|
19
|
-
}
|
|
20
|
-
return resolveProp(key, value)
|
|
21
|
-
})
|
|
22
|
-
)
|
|
23
|
-
return Object.fromEntries(entries)
|
|
73
|
+
async function resolvePageProps(props = {}) {
|
|
74
|
+
const resolved = await resolvePagePropsWithMetadata(props)
|
|
75
|
+
return resolved.props
|
|
24
76
|
}
|
|
77
|
+
|
|
78
|
+
/** @type {ResolvePageProps} */
|
|
79
|
+
const exportedResolvePageProps = Object.assign(resolvePageProps, {
|
|
80
|
+
withMetadata: resolvePagePropsWithMetadata
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
module.exports = exportedResolvePageProps
|