@architect/inventory 3.1.1 → 3.2.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/changelog.md +27 -0
- package/package.json +1 -1
- package/src/config/pragmas/plugins.js +21 -17
- package/src/config/pragmas/populate-lambda/index.js +23 -7
- package/src/config/pragmas/populate-other/index.js +117 -0
- package/src/config/pragmas/proxy.js +34 -10
- package/src/config/pragmas/shared.js +24 -6
- package/src/config/pragmas/static.js +20 -8
- package/src/config/pragmas/tables-indexes.js +55 -35
- package/src/config/pragmas/tables.js +53 -37
- package/src/config/pragmas/validate/_lib.js +13 -4
- package/src/config/pragmas/validate/_shared.js +1 -1
- package/src/config/pragmas/validate/_static.js +40 -0
- package/src/config/pragmas/validate/_tables.js +2 -1
- package/src/config/pragmas/validate/index.js +1 -0
- package/src/config/pragmas/views.js +27 -8
- package/src/lib/index.js +4 -0
- package/src/lib/is.js +28 -15
package/changelog.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
## [3.2.0] 2022-07-24
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added support for new setter plugin APIs, specifically: `@proxy`, `@shared`, `@static`, `@tables`, `@tables-indexes`, `@views`
|
|
10
|
+
- Added new `@static` setting: `compression`
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Lambdas defined in the userland Architect project manifest now override conflicting Lambdas returned by plugins (instead of throwing validation errors); fixes #1352
|
|
16
|
+
- Plugin developers can now use a `required` flag to enforce a validation error should their plugin conflict with userland Lambdas
|
|
17
|
+
- `@tables` and `@tables-indexes` can now accept lower case key types (e.g. `*string` instead of `*String`)
|
|
18
|
+
- `@tables` and `@tables-indexes` can also accept `*` and `**` as a shortcut for string-type primary and sort keys
|
|
19
|
+
- Changed plugin function property tags from `plugin|type` to `_plugin|_type` to indicate internal property namespacing
|
|
20
|
+
- Added `@static` pragma validation
|
|
21
|
+
- Fixed obscure case where `@static` `ignore` setting might only use the first list item
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Fixed issue where Lambdas created by plugins that returned arrays did not have their `plugin` and `type` properties set
|
|
27
|
+
- Fixed issue where an absolute path in `@shared|views` `src` would incorrectly resolve
|
|
28
|
+
- Fixed issue where `@views` might incorrectly return a validation error when only HTTP setter plugins are used to define `@http` routes
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
5
32
|
## [3.1.1] 2022-05-09
|
|
6
33
|
|
|
7
34
|
### Changed
|
package/package.json
CHANGED
|
@@ -2,10 +2,10 @@ let { join } = require('path')
|
|
|
2
2
|
let { existsSync } = require('fs')
|
|
3
3
|
let { is, normalizeSrc, pragmas, tidyError, validationPatterns } = require('../../lib')
|
|
4
4
|
let { lambdas } = pragmas
|
|
5
|
-
let nonLambdaSetters = [ 'customLambdas', 'env', 'runtimes' ]
|
|
5
|
+
let nonLambdaSetters = [ 'customLambdas', 'env', 'proxy', 'runtimes', 'shared', 'static', 'views', 'tables', 'tables-indexes' ]
|
|
6
6
|
let setters = [ ...lambdas, ...nonLambdaSetters ]
|
|
7
|
-
let pluginMethods = [ 'deploy', 'sandbox' ]
|
|
8
|
-
let reservedNames = [ '_methods'
|
|
7
|
+
let pluginMethods = [ 'deploy', 'sandbox' ]
|
|
8
|
+
let reservedNames = [ '_methods' ]
|
|
9
9
|
|
|
10
10
|
module.exports = function getPluginModules ({ arc, inventory, errors }) {
|
|
11
11
|
if (!arc?.plugins?.length && !arc?.macros?.length) return null
|
|
@@ -67,8 +67,8 @@ module.exports = function getPluginModules ({ arc, inventory, errors }) {
|
|
|
67
67
|
return
|
|
68
68
|
}
|
|
69
69
|
if (!methods.set[setter]) methods.set[setter] = []
|
|
70
|
-
fn.
|
|
71
|
-
fn.
|
|
70
|
+
fn._plugin = name
|
|
71
|
+
fn._type = type
|
|
72
72
|
methods.set[setter].push(fn)
|
|
73
73
|
}
|
|
74
74
|
})
|
|
@@ -83,8 +83,8 @@ module.exports = function getPluginModules ({ arc, inventory, errors }) {
|
|
|
83
83
|
}
|
|
84
84
|
if (!methods[method]) methods[method] = {}
|
|
85
85
|
if (!methods[method][hook]) methods[method][hook] = []
|
|
86
|
-
fn.
|
|
87
|
-
fn.
|
|
86
|
+
fn._plugin = name
|
|
87
|
+
fn._type = type
|
|
88
88
|
methods[method][hook].push(fn)
|
|
89
89
|
})
|
|
90
90
|
}
|
|
@@ -101,14 +101,18 @@ module.exports = function getPluginModules ({ arc, inventory, errors }) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
function getPath (cwd, srcDir, name) {
|
|
104
|
-
let
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (existsSync(
|
|
111
|
-
else if (existsSync(
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
let path1 = join(cwd, 'src', srcDir, `${name}.js`)
|
|
105
|
+
let path2 = join(cwd, 'src', srcDir, name)
|
|
106
|
+
let path3 = join(cwd, 'node_modules', name)
|
|
107
|
+
let path4 = join(cwd, 'node_modules', `@${name}`)
|
|
108
|
+
/**/ if (existsSync(path1)) return path1
|
|
109
|
+
else if (existsSync(path2)) return path2
|
|
110
|
+
else if (existsSync(path3)) return path3
|
|
111
|
+
else if (existsSync(path4)) return path4
|
|
112
|
+
try {
|
|
113
|
+
return require.resolve(name)
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
114
118
|
}
|
|
@@ -25,25 +25,26 @@ function populateLambda (type, params) {
|
|
|
25
25
|
var result = fn({ arc: invCopy._project.arc, inventory: { inv: invCopy } })
|
|
26
26
|
}
|
|
27
27
|
catch (err) {
|
|
28
|
-
err.message = `Setter plugin exception: plugin: ${fn.
|
|
28
|
+
err.message = `Setter plugin exception: plugin: ${fn._plugin}, method: set.${type}`
|
|
29
29
|
+ `\n` + err.message
|
|
30
30
|
throw err
|
|
31
31
|
}
|
|
32
32
|
if (!result ||
|
|
33
33
|
(!is.object(result) && !is.array(result)) ||
|
|
34
34
|
(is.array(result) && result.some(r => !is.object(r)))) {
|
|
35
|
-
errors.push(`Setter plugins must return a valid response: plugin: ${fn.
|
|
35
|
+
errors.push(`Setter plugins must return a valid response: plugin: ${fn._plugin}, method: set.${type}`)
|
|
36
36
|
return []
|
|
37
37
|
}
|
|
38
38
|
if (is.array(result)) {
|
|
39
|
-
result.forEach(item => {
|
|
40
|
-
item.
|
|
41
|
-
item.
|
|
39
|
+
result.forEach((item, i) => {
|
|
40
|
+
item._plugin = fn._plugin
|
|
41
|
+
item._type = fn._type
|
|
42
|
+
result[i] = item
|
|
42
43
|
})
|
|
43
44
|
}
|
|
44
45
|
else {
|
|
45
|
-
result.
|
|
46
|
-
result.
|
|
46
|
+
result._plugin = fn._plugin
|
|
47
|
+
result._type = fn._type
|
|
47
48
|
}
|
|
48
49
|
return result
|
|
49
50
|
})
|
|
@@ -52,6 +53,15 @@ function populateLambda (type, params) {
|
|
|
52
53
|
|
|
53
54
|
let pragmaLambda = populate(type, pragma || arc[type], inventory, errors) || []
|
|
54
55
|
let aggregate = [ ...pluginLambda, ...pragmaLambda ]
|
|
56
|
+
|
|
57
|
+
// Filter plugins, if appropriate: not required, but do conflict with a manifest entry
|
|
58
|
+
aggregate = aggregate.filter(lambda => {
|
|
59
|
+
if (lambda._plugin && !lambda.required &&
|
|
60
|
+
aggregate.some(({ name, _plugin }) => !_plugin && name === lambda.name)) {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
return true
|
|
64
|
+
})
|
|
55
65
|
return aggregate.length ? aggregate : null
|
|
56
66
|
}
|
|
57
67
|
|
|
@@ -135,6 +145,12 @@ function populate (type, pragma, inventory, errors, plugin) {
|
|
|
135
145
|
// Final tidying of any undefined properties
|
|
136
146
|
Object.keys(lambda).forEach(k => !is.defined(lambda[k]) && delete lambda[k])
|
|
137
147
|
|
|
148
|
+
// Pass through the plugin tag and required status
|
|
149
|
+
if (item._plugin) {
|
|
150
|
+
lambda._plugin = item._plugin
|
|
151
|
+
if (item.required === true) lambda.required = true
|
|
152
|
+
}
|
|
153
|
+
|
|
138
154
|
lambdas.push(lambda)
|
|
139
155
|
}
|
|
140
156
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
let { deepFrozenCopy } = require('@architect/utils')
|
|
2
|
+
let { is } = require('../../../lib')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build out resource pragmas (e.g. `@tables`) via plugins
|
|
6
|
+
* Returns an array of resources
|
|
7
|
+
*/
|
|
8
|
+
function resources (params) {
|
|
9
|
+
let { errors, template, plugins, inventory, type, valid } = params
|
|
10
|
+
if (plugins) {
|
|
11
|
+
let invCopy = deepFrozenCopy(inventory)
|
|
12
|
+
let pluginResults = plugins.flatMap(fn => {
|
|
13
|
+
try {
|
|
14
|
+
var result = fn({ arc: invCopy._project.arc, inventory: { inv: invCopy } })
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
err.message = `Setter plugin exception: plugin: ${fn._plugin}, method: set.${type}`
|
|
18
|
+
+ `\n` + err.message
|
|
19
|
+
throw err
|
|
20
|
+
}
|
|
21
|
+
if (!result ||
|
|
22
|
+
(!is.object(result) && !is.array(result)) ||
|
|
23
|
+
(is.array(result) && result.some(r => !is.object(r)))) {
|
|
24
|
+
errors.push(`Setter plugins must return a valid response: plugin: ${fn._plugin}, method: set.${type}`)
|
|
25
|
+
return []
|
|
26
|
+
}
|
|
27
|
+
if (is.array(result)) {
|
|
28
|
+
result.forEach((item, i) => {
|
|
29
|
+
item = populateTemplate(template, item)
|
|
30
|
+
item._plugin = fn._plugin
|
|
31
|
+
item._type = fn._type
|
|
32
|
+
result[i] = item
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
result = populateTemplate(template, result)
|
|
37
|
+
result._plugin = fn._plugin
|
|
38
|
+
result._type = fn._type
|
|
39
|
+
}
|
|
40
|
+
return result
|
|
41
|
+
})
|
|
42
|
+
// Validation pass
|
|
43
|
+
let validationErrors = []
|
|
44
|
+
pluginResults.forEach(item => {
|
|
45
|
+
let errors = validate(item, valid, type)
|
|
46
|
+
validationErrors.push(...errors)
|
|
47
|
+
})
|
|
48
|
+
if (validationErrors.length) {
|
|
49
|
+
errors = errors.push(...validationErrors)
|
|
50
|
+
return []
|
|
51
|
+
}
|
|
52
|
+
return pluginResults
|
|
53
|
+
}
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function populateTemplate (template, item) {
|
|
58
|
+
let newItem = JSON.parse(JSON.stringify(template))
|
|
59
|
+
Object.entries(item).forEach(([ setting, value ]) => {
|
|
60
|
+
if (is.defined(value)) newItem[setting] = value
|
|
61
|
+
})
|
|
62
|
+
return newItem
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function validate (item, valid, type) {
|
|
66
|
+
if (!valid) return []
|
|
67
|
+
return Object.entries(valid).map(([ setting, value ]) => {
|
|
68
|
+
if (!is[value](item[setting])) return `Invalid plugin-generated @${type} resource: ${setting}: ${item[setting]}`
|
|
69
|
+
}).filter(Boolean)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build out settings pragmas (e.g. `@static`) via plugins
|
|
74
|
+
* Returns an object of settings
|
|
75
|
+
*/
|
|
76
|
+
function settings (params) {
|
|
77
|
+
let { errors, settings, plugins, inventory, type, valid } = params
|
|
78
|
+
if (plugins) {
|
|
79
|
+
let invCopy = deepFrozenCopy(inventory)
|
|
80
|
+
let pluginSettings = settings
|
|
81
|
+
let foundError = false
|
|
82
|
+
plugins.forEach(fn => {
|
|
83
|
+
try {
|
|
84
|
+
var result = fn({ arc: invCopy._project.arc, inventory: { inv: invCopy } })
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
err.message = `Setter plugin exception: plugin: ${fn._plugin}, method: set.${type}`
|
|
88
|
+
+ `\n` + err.message
|
|
89
|
+
throw err
|
|
90
|
+
}
|
|
91
|
+
if (!result || !is.object(result)) {
|
|
92
|
+
errors.push(`Setter plugins must return a valid response: plugin: ${fn._plugin}, method: set.${type}`)
|
|
93
|
+
foundError = true
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
Object.entries(result).forEach(([ setting, value ]) => {
|
|
97
|
+
if (is.defined(settings[setting]) && is.defined(value)) {
|
|
98
|
+
pluginSettings[setting] = value
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
if (foundError) return null
|
|
104
|
+
|
|
105
|
+
// Validation pass
|
|
106
|
+
let validationErrors = validate(pluginSettings, valid, type)
|
|
107
|
+
if (validationErrors.length) {
|
|
108
|
+
errors = errors.push(...validationErrors)
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return pluginSettings
|
|
113
|
+
}
|
|
114
|
+
return settings
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { resources, settings }
|
|
@@ -1,19 +1,43 @@
|
|
|
1
|
+
let populate = require('./populate-other')
|
|
1
2
|
let validate = require('./validate')
|
|
2
3
|
|
|
3
|
-
module.exports = function configureProxy ({ arc, errors }) {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
module.exports = function configureProxy ({ arc, inventory, errors }) {
|
|
5
|
+
let proxySetters = inventory.plugins?._methods?.set?.proxy
|
|
6
|
+
let httpSetters = inventory.plugins?._methods?.set?.http
|
|
7
|
+
if ((arc.proxy || proxySetters) &&
|
|
8
|
+
(!arc.http && !httpSetters)) {
|
|
9
|
+
errors.push('Specifying @proxy requires specifying @http')
|
|
6
10
|
return null
|
|
7
11
|
}
|
|
8
|
-
if (!arc.proxy
|
|
12
|
+
if (!arc.proxy && !proxySetters) return null
|
|
9
13
|
|
|
10
|
-
let proxy = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
let proxy = {
|
|
15
|
+
testing: null,
|
|
16
|
+
staging: null,
|
|
17
|
+
production: null,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
proxy = populate.settings({
|
|
21
|
+
errors,
|
|
22
|
+
settings: proxy,
|
|
23
|
+
plugins: proxySetters,
|
|
24
|
+
inventory,
|
|
25
|
+
type: 'proxy',
|
|
26
|
+
valid: {
|
|
27
|
+
testing: 'string',
|
|
28
|
+
staging: 'string',
|
|
29
|
+
production: 'string',
|
|
30
|
+
},
|
|
16
31
|
})
|
|
32
|
+
if (proxy === null) return null
|
|
33
|
+
|
|
34
|
+
if (arc?.proxy?.length) {
|
|
35
|
+
Object.keys(proxy).forEach(env => {
|
|
36
|
+
let setting = arc.proxy.find(s => (s[0] && s[0] === env) && s[1])
|
|
37
|
+
if (!setting) errors.push(`@proxy ${env} environment not found or invalid`)
|
|
38
|
+
else proxy[env] = setting[1]
|
|
39
|
+
})
|
|
40
|
+
}
|
|
17
41
|
|
|
18
42
|
validate.proxy(proxy, errors)
|
|
19
43
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
let { join } = require('path')
|
|
2
|
+
let populate = require('./populate-other')
|
|
2
3
|
let validate = require('./validate')
|
|
3
4
|
let { is, pragmas } = require('../../lib')
|
|
4
5
|
let lambdas = pragmas.lambdas.concat('customLambdas')
|
|
@@ -9,20 +10,34 @@ module.exports = function configureShared ({ arc, pragmas, inventory, errors })
|
|
|
9
10
|
let { cwd, src: projSrc } = inventory._project
|
|
10
11
|
let src = join(projSrc, 'shared')
|
|
11
12
|
let shared = {
|
|
12
|
-
src,
|
|
13
|
-
shared: []
|
|
13
|
+
src: null,
|
|
14
|
+
shared: []
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let foundSrcSetting = false
|
|
18
|
+
let pluginSrc = populate.settings({
|
|
19
|
+
errors,
|
|
20
|
+
settings: shared,
|
|
21
|
+
plugins: inventory.plugins?._methods?.set?.shared,
|
|
22
|
+
inventory,
|
|
23
|
+
type: 'shared',
|
|
24
|
+
valid: { src: 'string' },
|
|
25
|
+
})
|
|
26
|
+
// Shared setters only support src, and do not support specifying Lambdas
|
|
27
|
+
// Lambda paths have not yet been reified in Inventory
|
|
28
|
+
if (is.string(pluginSrc?.src)) {
|
|
29
|
+
shared.src = pluginSrc.src
|
|
30
|
+
foundSrcSetting = true
|
|
14
31
|
}
|
|
15
32
|
|
|
16
33
|
// First pass to get + check shared folder (if any)
|
|
17
|
-
let foundSrc = false
|
|
18
34
|
if (arc?.shared?.length) {
|
|
19
35
|
for (let share of arc.shared) {
|
|
20
36
|
if (is.array(share)) {
|
|
21
37
|
let key = share[0]?.toLowerCase()
|
|
22
38
|
if (key === 'src' && is.string(share[1])) {
|
|
23
39
|
shared.src = share[1]
|
|
24
|
-
|
|
25
|
-
validate.shared(shared.src, cwd, errors)
|
|
40
|
+
foundSrcSetting = true
|
|
26
41
|
continue
|
|
27
42
|
}
|
|
28
43
|
if (key === 'src' && !is.string(share[1])) {
|
|
@@ -32,11 +47,14 @@ module.exports = function configureShared ({ arc, pragmas, inventory, errors })
|
|
|
32
47
|
}
|
|
33
48
|
}
|
|
34
49
|
|
|
50
|
+
if (foundSrcSetting) validate.shared(shared.src, cwd, errors)
|
|
51
|
+
else shared.src = src
|
|
52
|
+
|
|
35
53
|
// Exit if configured shared folder doesn't exist
|
|
36
54
|
if (!is.exists(shared.src)) return null
|
|
37
55
|
|
|
38
56
|
// Proceeding from here resets all shared config, so make sure it's only if specific shared are specified
|
|
39
|
-
let some = arc.shared?.length && !(arc?.shared?.length === 1 &&
|
|
57
|
+
let some = arc.shared?.length && !(arc?.shared?.length === 1 && foundSrcSetting)
|
|
40
58
|
if (some) {
|
|
41
59
|
// Reset shared settings
|
|
42
60
|
for (let pragma of lambdas) {
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
let populate = require('./populate-other')
|
|
1
2
|
let { asapSrc, is } = require('../../lib')
|
|
3
|
+
let validate = require('./validate')
|
|
2
4
|
|
|
3
|
-
module.exports = function configureStatic ({ arc, inventory }) {
|
|
5
|
+
module.exports = function configureStatic ({ arc, inventory, errors }) {
|
|
6
|
+
let staticSetters = inventory.plugins?._methods?.set?.static
|
|
4
7
|
let httpSetters = inventory.plugins?._methods?.set?.http
|
|
5
8
|
|
|
6
9
|
// @static is inferred by @http
|
|
7
|
-
if (!arc.static && !arc.http && !httpSetters) return null
|
|
10
|
+
if (!arc.static && !staticSetters && !arc.http && !httpSetters) return null
|
|
8
11
|
|
|
9
12
|
let staticPragma = arc.static || []
|
|
10
13
|
let _static = {
|
|
14
|
+
compression: false, // Arc applied default
|
|
11
15
|
fingerprint: null,
|
|
12
16
|
folder: 'public', // Arc applied default
|
|
13
17
|
ignore: null,
|
|
@@ -24,17 +28,23 @@ module.exports = function configureStatic ({ arc, inventory }) {
|
|
|
24
28
|
if (isDisabled) return false
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
_static = populate.settings({
|
|
32
|
+
errors,
|
|
33
|
+
settings: _static,
|
|
34
|
+
plugins: staticSetters,
|
|
35
|
+
inventory,
|
|
36
|
+
type: 'static',
|
|
37
|
+
})
|
|
38
|
+
|
|
27
39
|
let settings = Object.entries(_static).map(([ setting ]) => setting)
|
|
40
|
+
let validSetting = key => settings.includes(key)
|
|
28
41
|
for (let setting of staticPragma) {
|
|
29
|
-
|
|
42
|
+
// The ignore setting can come in a couple shapes, so we have to handle those
|
|
30
43
|
if (setting.ignore) {
|
|
31
44
|
_static.ignore = setting.ignore
|
|
32
45
|
}
|
|
33
|
-
else if (is.array(setting) &&
|
|
34
|
-
|
|
35
|
-
validSetting(setting[0])) {
|
|
36
|
-
let isIgnore = setting[0] === 'ignore'
|
|
37
|
-
_static[setting[0]] = isIgnore ? [ setting[1] ] : setting[1]
|
|
46
|
+
else if (is.array(setting) && validSetting(setting[0])) {
|
|
47
|
+
_static[setting[0]] = setting[0] === 'ignore' ? [ ...setting.slice(1) ] : setting[1]
|
|
38
48
|
}
|
|
39
49
|
}
|
|
40
50
|
|
|
@@ -44,5 +54,7 @@ module.exports = function configureStatic ({ arc, inventory }) {
|
|
|
44
54
|
inventory._project.asapSrc = asapSrc()
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
validate.static(_static, errors)
|
|
58
|
+
|
|
47
59
|
return _static
|
|
48
60
|
}
|
|
@@ -1,52 +1,72 @@
|
|
|
1
|
-
let
|
|
1
|
+
let populate = require('./populate-other')
|
|
2
|
+
let { is, capitalize } = require('../../lib')
|
|
2
3
|
let validate = require('./validate')
|
|
3
4
|
|
|
4
|
-
module.exports = function configureTablesIndexes ({ arc, errors }) {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
module.exports = function configureTablesIndexes ({ arc, inventory, errors }) {
|
|
6
|
+
let $indexes = 'tables-indexes' // It's quite long!
|
|
7
|
+
let indexesSetters = inventory.plugins?._methods?.set?.[$indexes]
|
|
8
|
+
let tablesSetters = inventory.plugins?._methods?.set?.tables
|
|
9
|
+
if ((!arc[$indexes] || !arc[$indexes].length) && !indexesSetters) return null
|
|
10
|
+
if ((arc[$indexes] || indexesSetters) &&
|
|
11
|
+
(!arc.tables && !tablesSetters)) {
|
|
7
12
|
errors.push(`Specifying @tables-indexes requires specifying corresponding @tables`)
|
|
8
13
|
return null
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
let
|
|
12
|
-
|
|
16
|
+
let indexTemplate = () => ({
|
|
17
|
+
name: undefined,
|
|
18
|
+
partitionKey: null,
|
|
19
|
+
partitionKeyType: null,
|
|
20
|
+
sortKey: null,
|
|
21
|
+
sortKeyType: null,
|
|
22
|
+
indexName: null,
|
|
23
|
+
})
|
|
13
24
|
|
|
14
|
-
|
|
15
|
-
|
|
25
|
+
let indexes = []
|
|
26
|
+
let plugins = populate.resources({
|
|
27
|
+
errors,
|
|
28
|
+
template: indexTemplate(),
|
|
29
|
+
plugins: indexesSetters,
|
|
30
|
+
inventory,
|
|
31
|
+
type: 'indexes',
|
|
32
|
+
valid: { name: 'string' }
|
|
33
|
+
})
|
|
34
|
+
if (plugins) indexes.push(...plugins)
|
|
16
35
|
|
|
17
|
-
let
|
|
18
|
-
let isCustomName = key => is.string(key) && key.toLowerCase() === 'name'
|
|
19
|
-
function error (item) { errors.push(`Invalid @${pragma} item: ${item}`) }
|
|
20
|
-
return arc[pragma].map(index => {
|
|
36
|
+
let userland = arc?.[$indexes]?.map(index => {
|
|
21
37
|
if (is.object(index)) {
|
|
22
|
-
let
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
let sortKey = null
|
|
26
|
-
let sortKeyType = null
|
|
27
|
-
let indexName = null
|
|
28
|
-
Object.entries(index[name]).forEach(([ key, value ]) => {
|
|
38
|
+
let i = indexTemplate()
|
|
39
|
+
i.name = Object.keys(index)[0]
|
|
40
|
+
Object.entries(index[i.name]).forEach(([ key, value ]) => {
|
|
29
41
|
if (is.sortKey(value)) {
|
|
30
|
-
sortKey = key
|
|
31
|
-
sortKeyType = value.replace('**', '')
|
|
42
|
+
i.sortKey = key
|
|
43
|
+
i.sortKeyType = value.replace('**', '').toLowerCase()
|
|
44
|
+
if (!i.sortKeyType) i.sortKeyType = 'string'
|
|
32
45
|
}
|
|
33
46
|
else if (is.primaryKey(value)) {
|
|
34
|
-
partitionKey = key
|
|
35
|
-
partitionKeyType = value.replace('*', '')
|
|
47
|
+
i.partitionKey = key
|
|
48
|
+
i.partitionKeyType = value.replace('*', '').toLowerCase()
|
|
49
|
+
if (!i.partitionKeyType) i.partitionKeyType = 'string'
|
|
36
50
|
}
|
|
37
|
-
else if (
|
|
38
|
-
indexName = value
|
|
51
|
+
else if (key?.toLowerCase() === 'name') {
|
|
52
|
+
i.indexName = value
|
|
39
53
|
}
|
|
40
54
|
})
|
|
41
|
-
return
|
|
42
|
-
indexName,
|
|
43
|
-
name,
|
|
44
|
-
partitionKey,
|
|
45
|
-
partitionKeyType,
|
|
46
|
-
sortKey,
|
|
47
|
-
sortKeyType,
|
|
48
|
-
}
|
|
55
|
+
return i
|
|
49
56
|
}
|
|
50
|
-
|
|
51
|
-
}).filter(Boolean) // Invalid indexes may create undefined entries in the map
|
|
57
|
+
errors.push(`Invalid @${$indexes} item: ${index}`)
|
|
58
|
+
}).filter(Boolean) // Invalid indexes or plugins may create undefined entries in the map
|
|
59
|
+
if (userland) indexes.push(...userland)
|
|
60
|
+
|
|
61
|
+
// Normalize key type casing
|
|
62
|
+
if (indexes.length) indexes = indexes.map(index => {
|
|
63
|
+
let { sortKeyType, partitionKeyType } = index
|
|
64
|
+
if (sortKeyType) index.sortKeyType = capitalize(index.sortKeyType)
|
|
65
|
+
if (partitionKeyType) index.partitionKeyType = capitalize(index.partitionKeyType)
|
|
66
|
+
return index
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
validate.tablesIndexes(indexes, '@tables-indexes', errors)
|
|
70
|
+
|
|
71
|
+
return indexes
|
|
52
72
|
}
|
|
@@ -1,54 +1,70 @@
|
|
|
1
|
-
let
|
|
1
|
+
let populate = require('./populate-other')
|
|
2
|
+
let { is, capitalize } = require('../../lib')
|
|
2
3
|
let validate = require('./validate')
|
|
3
4
|
|
|
4
|
-
module.exports = function configureTables ({ arc, errors }) {
|
|
5
|
-
|
|
5
|
+
module.exports = function configureTables ({ arc, inventory, errors }) {
|
|
6
|
+
let tablesSetters = inventory.plugins?._methods?.set?.tables
|
|
7
|
+
if ((!arc.tables || !arc.tables.length) && !tablesSetters) return null
|
|
6
8
|
|
|
7
9
|
let pitrLong = 'PointInTimeRecovery' // It's just so long
|
|
10
|
+
let tableTemplate = () => ({
|
|
11
|
+
name: undefined,
|
|
12
|
+
partitionKey: null,
|
|
13
|
+
partitionKeyType: null,
|
|
14
|
+
sortKey: null,
|
|
15
|
+
sortKeyType: null,
|
|
16
|
+
stream: null,
|
|
17
|
+
ttl: null,
|
|
18
|
+
encrypt: null,
|
|
19
|
+
pitr: null,
|
|
20
|
+
})
|
|
8
21
|
|
|
9
|
-
let tables =
|
|
22
|
+
let tables = []
|
|
23
|
+
let plugins = populate.resources({
|
|
24
|
+
errors,
|
|
25
|
+
template: tableTemplate(),
|
|
26
|
+
plugins: tablesSetters,
|
|
27
|
+
inventory,
|
|
28
|
+
type: 'tables',
|
|
29
|
+
valid: { name: 'string' }
|
|
30
|
+
})
|
|
31
|
+
if (plugins) tables.push(...plugins)
|
|
32
|
+
|
|
33
|
+
let userland = arc?.tables?.map(table => {
|
|
10
34
|
if (is.object(table)) {
|
|
11
|
-
let
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
let sortKey = null
|
|
15
|
-
let sortKeyType = null
|
|
16
|
-
let stream = null
|
|
17
|
-
let ttl = null
|
|
18
|
-
let encrypt = null
|
|
19
|
-
let pitr = null
|
|
20
|
-
let pitrOld // Old opt, remove in some future breaking change
|
|
21
|
-
Object.entries(table[name]).forEach(([ key, value ]) => {
|
|
35
|
+
let t = tableTemplate()
|
|
36
|
+
t.name = Object.keys(table)[0]
|
|
37
|
+
Object.entries(table[t.name]).forEach(([ key, value ]) => {
|
|
22
38
|
if (is.sortKey(value)) {
|
|
23
|
-
sortKey = key
|
|
24
|
-
sortKeyType = value.replace('**', '')
|
|
39
|
+
t.sortKey = key
|
|
40
|
+
t.sortKeyType = value.replace('**', '').toLowerCase()
|
|
41
|
+
if (!t.sortKeyType) t.sortKeyType = 'string'
|
|
25
42
|
}
|
|
26
43
|
else if (is.primaryKey(value)) {
|
|
27
|
-
partitionKey = key
|
|
28
|
-
partitionKeyType = value.replace('*', '')
|
|
44
|
+
t.partitionKey = key
|
|
45
|
+
t.partitionKeyType = value.replace('*', '').toLowerCase()
|
|
46
|
+
if (!t.partitionKeyType) t.partitionKeyType = 'string'
|
|
29
47
|
}
|
|
30
|
-
if (key === 'stream') stream = value
|
|
31
|
-
if (value === 'TTL') ttl = key
|
|
32
|
-
if (key === 'encrypt') encrypt = value
|
|
33
|
-
if (key === '
|
|
34
|
-
if (key ===
|
|
48
|
+
if (key === 'stream') t.stream = value
|
|
49
|
+
if (value === 'TTL') t.ttl = key
|
|
50
|
+
if (key === 'encrypt') t.encrypt = value
|
|
51
|
+
if (key === 'PITR') t.pitr = value
|
|
52
|
+
if (key === 'pitr') t.pitr = value
|
|
53
|
+
if (key === pitrLong) t.PointInTimeRecovery = value
|
|
35
54
|
})
|
|
36
|
-
let t = {
|
|
37
|
-
name,
|
|
38
|
-
partitionKey,
|
|
39
|
-
partitionKeyType,
|
|
40
|
-
sortKey,
|
|
41
|
-
sortKeyType,
|
|
42
|
-
stream,
|
|
43
|
-
ttl,
|
|
44
|
-
encrypt,
|
|
45
|
-
pitr,
|
|
46
|
-
}
|
|
47
|
-
if (pitrOld !== undefined) t.PointInTimeRecovery = pitrOld
|
|
48
55
|
return t
|
|
49
56
|
}
|
|
50
57
|
errors.push(`Invalid @tables item: ${table}`)
|
|
51
|
-
}).filter(Boolean) // Invalid tables may create undefined entries in the map
|
|
58
|
+
}).filter(Boolean) // Invalid tables or plugins may create undefined entries in the map
|
|
59
|
+
if (userland) tables.push(...userland)
|
|
60
|
+
|
|
61
|
+
// Normalize key type casing
|
|
62
|
+
if (tables.length) tables = tables.map(table => {
|
|
63
|
+
let { sortKeyType, partitionKeyType } = table
|
|
64
|
+
if (sortKeyType) table.sortKeyType = capitalize(table.sortKeyType)
|
|
65
|
+
if (partitionKeyType) table.partitionKeyType = capitalize(table.partitionKeyType)
|
|
66
|
+
return table
|
|
67
|
+
})
|
|
52
68
|
|
|
53
69
|
validate.tables(tables, '@tables', errors)
|
|
54
70
|
|
|
@@ -22,10 +22,19 @@ function size (value, min, max, pragmaName, errors) {
|
|
|
22
22
|
|
|
23
23
|
function unique (lambdas, pragmaName, errors) {
|
|
24
24
|
if (!is.array(lambdas)) throw ReferenceError(`Invalid Lambda array: ${lambdas}`)
|
|
25
|
-
let names = []
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (names.includes(name) && !
|
|
25
|
+
let names = [] // List of names we've looked at
|
|
26
|
+
let namesErrored = [] // List of any offending names
|
|
27
|
+
if (lambdas.length) lambdas.forEach(({ name, pragma }) => {
|
|
28
|
+
if (names.includes(name) && !namesErrored.includes(name)) {
|
|
29
|
+
let err = `Duplicate ${pragmaName} item: ${name}`
|
|
30
|
+
|
|
31
|
+
// If we find a plugin Lambda, that means the filter let it through because it's required by the plugin, so we must now error
|
|
32
|
+
let foundPlugin = lambdas.find(l => l._plugin && l.name === name)
|
|
33
|
+
if (foundPlugin) err = `Plugin requires conflicting ${pragmaName} item: ${name}, plugin: ${foundPlugin._plugin}, method: set.${pragma}`
|
|
34
|
+
|
|
35
|
+
namesErrored.push(name)
|
|
36
|
+
errors.push(err)
|
|
37
|
+
}
|
|
29
38
|
names.push(name)
|
|
30
39
|
})
|
|
31
40
|
}
|
|
@@ -2,7 +2,7 @@ let { join, resolve, sep } = require('path')
|
|
|
2
2
|
let { is } = require('../../../lib')
|
|
3
3
|
|
|
4
4
|
module.exports = function validateShared (src, cwd, errors) {
|
|
5
|
-
let path = src && resolve(join(cwd, src))
|
|
5
|
+
let path = src && src.startsWith(cwd) ? src : resolve(join(cwd, src))
|
|
6
6
|
|
|
7
7
|
if (!is.exists(path)) errors.push(`Directory not found: ${src}`)
|
|
8
8
|
else if (!is.folder(path)) errors.push(`Must be a directory: ${src}`)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
let { is } = require('../../../lib')
|
|
2
|
+
|
|
3
|
+
module.exports = function validateStatic (_static, errors) {
|
|
4
|
+
// `folder` validation happens elsewhere
|
|
5
|
+
let {
|
|
6
|
+
compression,
|
|
7
|
+
fingerprint,
|
|
8
|
+
ignore,
|
|
9
|
+
prefix,
|
|
10
|
+
prune,
|
|
11
|
+
spa,
|
|
12
|
+
staging,
|
|
13
|
+
production,
|
|
14
|
+
} = _static
|
|
15
|
+
|
|
16
|
+
if (compression && ![ true, 'br', 'gzip' ].includes(compression)) {
|
|
17
|
+
errors.push(`Compression must be 'br' or 'gzip'`)
|
|
18
|
+
}
|
|
19
|
+
if (fingerprint && ![ true, 'external' ].includes(fingerprint)) {
|
|
20
|
+
errors.push(`Fingerprint must be true or 'external'`)
|
|
21
|
+
}
|
|
22
|
+
if (ignore && (!is.array(ignore) || !ignore.every(i => is.string(i)))) {
|
|
23
|
+
errors.push(`Ignore must be a list of one or more strings`)
|
|
24
|
+
}
|
|
25
|
+
if (prefix && !is.string(prefix)) {
|
|
26
|
+
errors.push(`Prefix must be a string`)
|
|
27
|
+
}
|
|
28
|
+
if (!is.nullish(prune) && !is.bool(prune)) {
|
|
29
|
+
errors.push(`Prune must be a boolean`)
|
|
30
|
+
}
|
|
31
|
+
if (!is.nullish(spa) && !is.bool(spa)) {
|
|
32
|
+
errors.push(`spa must be a string`)
|
|
33
|
+
}
|
|
34
|
+
if (!is.nullish(staging) && !is.string(staging)) {
|
|
35
|
+
errors.push(`Staging must be a string`)
|
|
36
|
+
}
|
|
37
|
+
if (!is.nullish(production) && !is.string(production)) {
|
|
38
|
+
errors.push(`Production must be a string`)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -10,12 +10,13 @@ let { deepStrictEqual } = require('assert')
|
|
|
10
10
|
module.exports = function validateTablesAndIndexes (pragma, pragmaName, errors) {
|
|
11
11
|
if (pragma?.length) {
|
|
12
12
|
pragma.forEach(table => {
|
|
13
|
-
let { name, indexName, partitionKey, sortKey } = table
|
|
13
|
+
let { name, indexName, partitionKey, partitionKeyType, sortKey } = table
|
|
14
14
|
|
|
15
15
|
size(name, 3, 255, pragmaName, errors)
|
|
16
16
|
regex(name, 'veryLooseName', pragmaName, errors)
|
|
17
17
|
|
|
18
18
|
if (!partitionKey) errors.push(`Invalid ${pragmaName} item (partition key required): '${name}'`)
|
|
19
|
+
if (!partitionKeyType) errors.push(`Invalid ${pragmaName} item (partition key type required): '${name}'`)
|
|
19
20
|
if (indexName) {
|
|
20
21
|
size(indexName, 3, 255, pragmaName, errors)
|
|
21
22
|
regex(indexName, 'veryLooseName', pragmaName, errors)
|
|
@@ -8,6 +8,7 @@ module.exports = {
|
|
|
8
8
|
queues: require('./_events'), // Same ruleset as @events
|
|
9
9
|
scheduled: require('./_scheduled'),
|
|
10
10
|
shared: require('./_shared'), // Also includes @views
|
|
11
|
+
static: require('./_static'),
|
|
11
12
|
tables: require('./_tables'),
|
|
12
13
|
tablesIndexes: require('./_tables'), // Same ruleset as @tables (more or less)
|
|
13
14
|
tablesStreams: require('./_tables-streams'),
|
|
@@ -1,31 +1,47 @@
|
|
|
1
1
|
let { join } = require('path')
|
|
2
|
+
let populate = require('./populate-other')
|
|
2
3
|
let validate = require('./validate')
|
|
3
4
|
let { is } = require('../../lib')
|
|
4
5
|
|
|
5
6
|
module.exports = function configureViews ({ arc, pragmas, inventory, errors }) {
|
|
6
|
-
|
|
7
|
+
let httpSetters = inventory.plugins?._methods?.set?.http
|
|
8
|
+
if (arc.views && (!arc.http && !httpSetters)) {
|
|
7
9
|
errors.push('@views requires @http')
|
|
8
10
|
return null
|
|
9
11
|
}
|
|
10
|
-
if (!arc.http) return null
|
|
12
|
+
if (!arc.http && !httpSetters) return null
|
|
11
13
|
|
|
12
14
|
let { cwd, src: projSrc } = inventory._project
|
|
13
15
|
let src = join(projSrc, 'views')
|
|
14
16
|
let views = {
|
|
15
|
-
src,
|
|
16
|
-
views: []
|
|
17
|
+
src: null,
|
|
18
|
+
views: []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let foundSrcSetting = false
|
|
22
|
+
let pluginSrc = populate.settings({
|
|
23
|
+
errors,
|
|
24
|
+
settings: views,
|
|
25
|
+
plugins: inventory.plugins?._methods?.set?.views,
|
|
26
|
+
inventory,
|
|
27
|
+
type: 'views',
|
|
28
|
+
valid: { src: 'string' },
|
|
29
|
+
})
|
|
30
|
+
// Views setters only support src, and do not support specifying Lambdas
|
|
31
|
+
// Lambda paths have not yet been reified in Inventory
|
|
32
|
+
if (is.string(pluginSrc?.src)) {
|
|
33
|
+
views.src = pluginSrc.src
|
|
34
|
+
foundSrcSetting = true
|
|
17
35
|
}
|
|
18
36
|
|
|
19
37
|
// First pass to get + check views folder (if any)
|
|
20
|
-
let foundSrc = false
|
|
21
38
|
if (arc?.views?.length) {
|
|
22
39
|
for (let view of arc.views) {
|
|
23
40
|
if (is.array(view)) {
|
|
24
41
|
let key = view[0]?.toLowerCase()
|
|
25
42
|
if (key === 'src' && is.string(view[1])) {
|
|
26
43
|
views.src = view[1]
|
|
27
|
-
|
|
28
|
-
validate.shared(views.src, cwd, errors)
|
|
44
|
+
foundSrcSetting = true
|
|
29
45
|
continue
|
|
30
46
|
}
|
|
31
47
|
if (key === 'src' && !is.string(view[1])) {
|
|
@@ -35,11 +51,14 @@ module.exports = function configureViews ({ arc, pragmas, inventory, errors }) {
|
|
|
35
51
|
}
|
|
36
52
|
}
|
|
37
53
|
|
|
54
|
+
if (foundSrcSetting) validate.shared(views.src, cwd, errors)
|
|
55
|
+
else views.src = src
|
|
56
|
+
|
|
38
57
|
// Exit if default views folder doesn't exist
|
|
39
58
|
if (!is.exists(views.src)) return null
|
|
40
59
|
|
|
41
60
|
// Proceeding from here resets all views config, so make sure it's only if specific views are specified
|
|
42
|
-
let some = arc.views?.length && !(arc?.views?.length === 1 &&
|
|
61
|
+
let some = arc.views?.length && !(arc?.views?.length === 1 && foundSrcSetting)
|
|
43
62
|
if (some) {
|
|
44
63
|
// Reset views settings
|
|
45
64
|
for (let route of pragmas.http) {
|
package/src/lib/index.js
CHANGED
|
@@ -9,6 +9,9 @@ let pragmas = require('./pragmas')
|
|
|
9
9
|
* Why take up a whole fs block when smol libs can just live here?
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
// Capitalize a string (used to normalize table/index key types)
|
|
13
|
+
let capitalize = str => str[0].toUpperCase() + str.substr(1)
|
|
14
|
+
|
|
12
15
|
// For setting `lambda.build`, compiled + transpiled are effectively the same
|
|
13
16
|
let compiledRuntimes = [ 'compiled', 'transpiled' ]
|
|
14
17
|
|
|
@@ -29,6 +32,7 @@ let tidyError = err => err.message + `\n` + err.stack.split('\n').slice(1).join(
|
|
|
29
32
|
|
|
30
33
|
module.exports = {
|
|
31
34
|
asapSrc,
|
|
35
|
+
capitalize,
|
|
32
36
|
compiledRuntimes,
|
|
33
37
|
errorFmt,
|
|
34
38
|
getLambdaDirs,
|
package/src/lib/is.js
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
let { existsSync, lstatSync } = require('fs')
|
|
2
2
|
|
|
3
|
+
// Types
|
|
4
|
+
let array = item => Array.isArray(item)
|
|
5
|
+
let bool = item => typeof item === 'boolean'
|
|
6
|
+
let defined = item => typeof item !== 'undefined'
|
|
7
|
+
let fn = item => typeof item === 'function'
|
|
8
|
+
let nullish = item => typeof item === 'undefined' || item === null
|
|
9
|
+
let number = item => Number.isInteger(item)
|
|
10
|
+
let object = item => typeof item === 'object' && !Array.isArray(item)
|
|
11
|
+
let string = item => typeof item === 'string'
|
|
12
|
+
// Filesystem
|
|
13
|
+
let exists = path => existsSync(path)
|
|
14
|
+
let folder = path => existsSync(path) && lstatSync(path).isDirectory()
|
|
15
|
+
// Pragma-specific stuff
|
|
16
|
+
let primaryKey = val => string(val) && [ '*', '*string', '*number' ].includes(val.toLowerCase())
|
|
17
|
+
let sortKey = val => string(val) && [ '**', '**string', '**number' ].includes(val.toLowerCase())
|
|
18
|
+
|
|
3
19
|
module.exports = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// Pragma-specific stuff
|
|
17
|
-
primaryKey: val => typeof val === 'string' && (val.startsWith('*String') || val.startsWith('*Number')),
|
|
18
|
-
sortKey: val => typeof val === 'string' && (val.startsWith('**String') || val.startsWith('**Number')),
|
|
20
|
+
array,
|
|
21
|
+
bool,
|
|
22
|
+
defined,
|
|
23
|
+
fn,
|
|
24
|
+
nullish,
|
|
25
|
+
number,
|
|
26
|
+
object,
|
|
27
|
+
string,
|
|
28
|
+
exists,
|
|
29
|
+
folder,
|
|
30
|
+
primaryKey,
|
|
31
|
+
sortKey,
|
|
19
32
|
}
|