@architect/inventory 2.2.0 → 3.0.0-RC.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/changelog.md +40 -0
  2. package/package.json +5 -5
  3. package/src/config/_upsert.js +1 -1
  4. package/src/config/pragmas/app.js +1 -1
  5. package/src/config/pragmas/events.js +3 -2
  6. package/src/config/pragmas/http.js +8 -4
  7. package/src/config/pragmas/index.js +16 -5
  8. package/src/config/pragmas/meta/custom-lambdas.js +10 -0
  9. package/src/config/pragmas/{src-dirs.js → meta/src-dirs.js} +13 -10
  10. package/src/config/pragmas/plugins.js +104 -4
  11. package/src/config/pragmas/populate-lambda/_custom-lambdas.js +10 -0
  12. package/src/config/pragmas/populate-lambda/_events.js +17 -11
  13. package/src/config/pragmas/populate-lambda/_http.js +18 -10
  14. package/src/config/pragmas/populate-lambda/_scheduled.js +18 -10
  15. package/src/config/pragmas/populate-lambda/_tables-streams.js +20 -23
  16. package/src/config/pragmas/populate-lambda/_ws.js +26 -0
  17. package/src/config/pragmas/populate-lambda/get-handler.js +86 -13
  18. package/src/config/pragmas/populate-lambda/get-lambda.js +23 -0
  19. package/src/config/pragmas/populate-lambda/get-runtime.js +8 -7
  20. package/src/config/pragmas/populate-lambda/index.js +104 -86
  21. package/src/config/pragmas/queues.js +3 -2
  22. package/src/config/pragmas/scheduled.js +3 -2
  23. package/src/config/pragmas/shared.js +41 -44
  24. package/src/config/pragmas/sort/http.js +72 -0
  25. package/src/config/pragmas/static.js +1 -2
  26. package/src/config/pragmas/tables-indexes.js +3 -10
  27. package/src/config/pragmas/tables-streams.js +7 -5
  28. package/src/config/pragmas/tables.js +1 -1
  29. package/src/config/pragmas/validate/_events.js +1 -1
  30. package/src/config/pragmas/validate/_http.js +4 -4
  31. package/src/config/pragmas/validate/_lib.js +1 -1
  32. package/src/config/pragmas/validate/_scheduled.js +2 -2
  33. package/src/config/pragmas/validate/_shared.js +1 -1
  34. package/src/config/pragmas/validate/_tables-streams.js +6 -8
  35. package/src/config/pragmas/validate/_tables.js +2 -2
  36. package/src/config/pragmas/validate/index.js +2 -2
  37. package/src/config/pragmas/views.js +39 -37
  38. package/src/config/pragmas/ws.js +6 -4
  39. package/src/config/project/index.js +15 -10
  40. package/src/config/project/plugins/index.js +5 -0
  41. package/src/config/project/plugins/runtimes.js +56 -0
  42. package/src/config/project/prefs.js +7 -7
  43. package/src/config/project/validate/index.js +1 -1
  44. package/src/defaults/index.js +13 -11
  45. package/src/env/index.js +1 -1
  46. package/src/get.js +1 -2
  47. package/src/index.js +17 -15
  48. package/src/lib/error-fmt.js +2 -10
  49. package/src/lib/get-lambda-dirs.js +37 -0
  50. package/src/lib/index.js +26 -0
  51. package/src/lib/is.js +3 -1
  52. package/src/lib/pragmas.js +18 -3
  53. package/src/read/reader.js +1 -1
  54. package/src/validate/config.js +12 -6
  55. package/src/validate/index.js +3 -11
  56. package/src/validate/layers.js +2 -2
  57. package/src/validate/tables-children.js +5 -5
  58. package/src/config/pragmas/indexes.js +0 -19
  59. package/src/config/pragmas/macros.js +0 -5
  60. package/src/config/pragmas/populate-lambda/_plugins.js +0 -25
  61. package/src/config/pragmas/populate-lambda/_websockets.js +0 -19
  62. package/src/config/project/plugins.js +0 -31
@@ -1,14 +1,87 @@
1
- module.exports = function getHandler (config, src, errors) {
2
- let { handler, runtime } = config
3
- let parts = handler.split('.')
4
- if (parts.length !== 2) errors.push(`Invalid handler: ${handler}. Expected {file}.{function}, example: index.handler`)
5
- let ext
6
- if (runtime.startsWith('nodejs')) ext = 'js'
7
- if (runtime.startsWith('python')) ext = 'py'
8
- if (runtime.startsWith('ruby')) ext = 'rb'
9
- if (runtime.startsWith('deno')) ext = 'ts'
10
- // TODO add Go, Java, .NET, etc.
11
- let handlerFile = `${src}/${parts[0]}${ext ? '.' + ext : ''}`
12
- let handlerFunction = parts[1]
13
- return { handlerFile, handlerFunction }
1
+ let { join } = require('path')
2
+ let { existsSync, readFileSync } = require('fs')
3
+
4
+ module.exports = function getHandler ({ config, src, build, errors }) {
5
+ let { handler, runtime, runtimeConfig } = config
6
+ let parts = handler.split('.') // Default is 'index.handler'
7
+ if (parts.length !== 2) {
8
+ errors.push(`Invalid handler: ${handler}. Expected {file}.{method}, example: index.handler`)
9
+ }
10
+
11
+ let { file, ext, handlerModuleSystem } = getExt({ runtime, src, errors })
12
+ file = file || parts[0] // Falls back to 'index'
13
+ ext = ext ? '.' + ext : ''
14
+ let handlerFile = join(src, `${file}${ext}`)
15
+ let handlerMethod = parts[1]
16
+ let customRuntimeType = runtimeConfig?.type
17
+
18
+ // Compiled to an interpreted runtime (eg TypeScript)
19
+ if (customRuntimeType === 'transpiled') {
20
+ handlerFile = join(build, runtimeConfig?.handlerFile || 'index.js')
21
+ }
22
+ // Compiled to a binary
23
+ else if (customRuntimeType === 'compiled') {
24
+ handlerFile = join(build, runtimeConfig.handlerFile || 'handler')
25
+ }
26
+ // Interpreted
27
+ else if (customRuntimeType === 'interpreted') {
28
+ handlerFile = join(src, runtimeConfig.handlerFile || 'index')
29
+ }
30
+
31
+ let handlerConfig = { handlerFile, handlerMethod, handlerModuleSystem }
32
+ return handlerConfig
33
+ }
34
+
35
+ let nodeHandlers = [ 'index.js', 'index.mjs', 'index.cjs' ]
36
+ let denoHandlers = [ 'mod.ts', 'mod.js' ]
37
+ // TODO: these are all prob going away
38
+ .concat([ 'mod.tsx', 'index.ts', 'index.js', 'index.tsx' ])
39
+
40
+ function getExt ({ runtime, src, errors }) {
41
+ try {
42
+ if (runtime.startsWith('node')) {
43
+ if (runtime === 'nodejs12.x') {
44
+ return { ext: 'js', handlerModuleSystem: 'cjs' }
45
+ }
46
+ // This presumes Node.js 14.x+ Lambda releases use the same CJS/ESM pattern
47
+ else {
48
+ let { file, ext = 'js' } = findHandler(nodeHandlers, src)
49
+ let handlerModuleSystem = ext === 'mjs' ? 'esm' : 'cjs'
50
+ let pkgFile = join(src, 'package.json')
51
+ if (existsSync(pkgFile)) {
52
+ let pkg = JSON.parse(readFileSync(pkgFile))
53
+ handlerModuleSystem = getModSystem(pkg)
54
+ }
55
+ return { file, ext, handlerModuleSystem }
56
+ }
57
+ }
58
+ if (runtime.startsWith('python')) return { ext: 'py' }
59
+ if (runtime.startsWith('ruby')) return { ext: 'rb' }
60
+ if (runtime.startsWith('deno')) {
61
+ let { file = 'mod', ext = 'ts' } = findHandler(denoHandlers, src)
62
+ return { file, ext }
63
+ }
64
+ return { ext: '' }
65
+ }
66
+ catch (err) {
67
+ errors.push(`Error getting Lambda handler in ${src}: ${err.message}`)
68
+ return {}
69
+ }
70
+ }
71
+
72
+ function findHandler (arr, src){
73
+ for (let handler of arr) {
74
+ if (existsSync(join(src, handler))) {
75
+ let bits = handler.split('.')
76
+ return { file: bits[0], ext: bits[1] }
77
+ }
78
+ }
79
+ return {}
80
+ }
81
+
82
+ function getModSystem (pkg) {
83
+ if (pkg?.type === 'module') return 'esm'
84
+ else if (pkg?.type === 'commonjs') return 'cjs'
85
+ else if (pkg?.type) throw Error(`Invalid 'type' field: ${pkg.type}`)
86
+ return 'cjs'
14
87
  }
@@ -0,0 +1,23 @@
1
+ // Pragma-specific Lambda constructors
2
+ let getHTTP = require('./_http')
3
+ let getEvents = require('./_events')
4
+ let getCustomLambdas = require('./_custom-lambdas')
5
+ let getScheduled = require('./_scheduled')
6
+ let getWS = require('./_ws')
7
+ let getTablesStreams = require('./_tables-streams')
8
+
9
+ let cl = 'customLambdas'
10
+ let ts = 'tables-streams'
11
+
12
+ module.exports = function getLambda (params) {
13
+ let { type } = params
14
+ if (type === 'http') return getHTTP(params)
15
+ if (type === 'events') return getEvents(params)
16
+ if (type === cl) return getCustomLambdas(params)
17
+ if (type === 'queues') return getEvents(params) // Effectively the same as events
18
+ if (type === 'scheduled') return getScheduled(params)
19
+ if (type === ts) return getTablesStreams(params)
20
+ if (type === 'tables') return getTablesStreams(params) // Shortcut for creating streams
21
+ /* istanbul ignore else: clearer to be explicit here */
22
+ if (type === 'ws') return getWS(params)
23
+ }
@@ -1,16 +1,13 @@
1
- let is = require('../../../lib/is')
2
- let { aliases, runtimes, runtimeList } = require('lambda-runtimes')
1
+ let { is } = require('../../../lib')
2
+ let { aliases, runtimes } = require('lambda-runtimes')
3
3
 
4
4
  // Runtime interpolater
5
- module.exports = function getRuntime (config) {
5
+ module.exports = function getRuntime ({ config, inventory }) {
6
6
  let { runtime } = config
7
7
 
8
- if (runtimeList.includes(runtime) || runtime === 'deno') {
9
- return config
10
- }
11
-
12
8
  if (typeof runtime === 'string') {
13
9
  runtime = runtime.toLowerCase()
10
+ let customRuntime = inventory._project?.customRuntimes?.[runtime]
14
11
 
15
12
  // Runtime is not actually an AWS value, but a shorthand/aliased name
16
13
  if (aliases[runtime]) {
@@ -18,6 +15,10 @@ module.exports = function getRuntime (config) {
18
15
  config.runtime = runtimes[aliased][0]
19
16
  config.runtimeAlias = runtime
20
17
  }
18
+ // Runtime is custom via plugin
19
+ else if (customRuntime) {
20
+ config.runtimeConfig = customRuntime
21
+ }
21
22
  }
22
23
  else if (is.defined(runtime)) {
23
24
  // Someone did something funky like specify a number or bool, so coerce and let it fail validation
@@ -1,113 +1,131 @@
1
1
  let read = require('../../../read')
2
+ let getLambda = require('./get-lambda')
2
3
  let getRuntime = require('./get-runtime')
3
4
  let getHandler = require('./get-handler')
4
5
  let upsert = require('../../_upsert')
5
- let is = require('../../../lib/is')
6
-
7
- // Pragma-specific Lambda constructors
8
- let getHTTP = require('./_http')
9
- let getEvents = require('./_events')
10
- let getPlugins = require('./_plugins')
11
- let getScheduled = require('./_scheduled')
12
- let getWS = require('./_websockets')
13
- let getTablesStreams = require('./_tables-streams')
6
+ let defaultFunctionConfig = require('../../../defaults/function-config')
7
+ let { compiledRuntimes, is } = require('../../../lib')
14
8
 
15
9
  /**
16
- * Build out the Lambda tree
10
+ * Build out the Lambda tree from the Arc manifest or a passed pragma, and plugins
17
11
  */
18
- function populateLambda (type, pragma, inventory, errors) {
19
- if (!pragma || !pragma.length) return null // Jic
12
+ function populateLambda (type, { arc, inventory, errors, pragma }) {
13
+ let plugins = inventory.plugins?._methods?.set?.[type]
14
+ let pluginLambda = []
15
+ if (plugins) {
16
+ let pluginResults = plugins.flatMap(fn => {
17
+ let result = fn({ arc, inventory })
18
+ if (!result) {
19
+ errors.push(`Setter plugins must return a valid response: plugin: ${fn.plugin}, method: set.${type}`)
20
+ return []
21
+ }
22
+ result.plugin = fn.plugin
23
+ result.type = fn.type
24
+ return result
25
+ })
26
+ pluginLambda = populate(type, pluginResults, inventory, errors, true) || []
27
+ }
28
+ let pragmaLambda = populate(type, pragma || arc[type], inventory, errors) || []
29
+ let aggregate = [ ...pluginLambda, ...pragmaLambda ]
30
+ return aggregate.length ? aggregate : null
31
+ }
32
+
33
+ function populate (type, pragma, inventory, errors, plugin) {
34
+ if (!pragma || !pragma.length) return
20
35
 
21
- let createDefaultConfig = () => JSON.parse(JSON.stringify(inventory._project.defaultFunctionConfig))
22
- let cwd = inventory._project.src
36
+ let defaultProjectConfig = () => JSON.parse(JSON.stringify(inventory._project.defaultFunctionConfig))
37
+ let { cwd, src: projSrc, build: projBuild } = inventory._project
23
38
 
24
39
  // Fill er up
25
40
  let lambdas = []
26
41
 
27
42
  for (let item of pragma) {
28
43
  // Get name, source dir, and any pragma-specific properties
29
- let results = getLambda({ type, item, cwd, inventory, errors })
30
- // Some lambda populators (e.g. plugins) may return empty results
31
- if (!results) continue
32
- // Some lambda populators (e.g. plugins) may return multiple results
33
- if (!is.array(results)) results = [ results ]
34
-
35
- results.forEach(result => {
36
- let { name, src } = result
37
- // Set up fresh config
38
- let config = createDefaultConfig()
39
-
40
- // Knock out any pragma-specific early
41
- if (type === 'queues') {
42
- config.fifo = config.fifo === undefined ? true : config.fifo
43
- }
44
- if (type === 'http') {
45
- if (name.startsWith('get ') || name.startsWith('any ')) config.views = true
46
- }
47
-
48
- // Now let's check in on the function config
49
- let { arc: arcConfig, filepath } = read({ type: 'functionConfig', cwd: src, errors })
50
-
51
- // Set function config file path (if one is present)
52
- let configFile = filepath ? filepath : null
53
-
54
- // Layer any function config over Arc / project defaults
55
- if (arcConfig && arcConfig.aws) {
56
- config = upsert(config, arcConfig.aws)
57
- }
58
- if (arcConfig && arcConfig.arc) {
59
- config = upsert(config, arcConfig.arc)
60
- }
61
-
62
- // Interpolate runtimes
63
- config = getRuntime(config)
64
-
65
- // Tidy up any irrelevant params
66
- if (type !== 'http') {
67
- delete config.apigateway
68
- }
69
-
70
- // Now we know the final source dir + runtime + handler: assemble handler props
71
- let { handlerFile, handlerFunction } = getHandler(config, src, errors)
72
-
73
- let lambda = {
74
- name,
75
- config,
76
- src,
77
- handlerFile,
78
- handlerFunction,
79
- configFile,
80
- ...result, // Any other pragma-specific stuff
81
- }
82
-
83
- lambdas.push(lambda)
84
- })
44
+ let result = getLambda({ type, item, cwd, projSrc, projBuild, inventory, errors, plugin })
45
+ // Some Lambda populators (e.g. plugins) may return empty result
46
+ if (!result) continue
47
+
48
+ let { name, src, build } = result
49
+ // Set up fresh config, then overlay plugin config
50
+ let config = defaultProjectConfig()
51
+ config = { ...config, ...getKnownProps(configProps, result.config) }
52
+
53
+ // Knock out any pragma-specific early
54
+ if (type === 'queues') {
55
+ config.fifo = config.fifo === undefined ? true : config.fifo
56
+ }
57
+ if (type === 'http') {
58
+ if (name.startsWith('get ') || name.startsWith('any ')) config.views = true
59
+ }
60
+
61
+ // Now let's check in on the function config
62
+ let { arc: arcConfig, filepath } = read({ type: 'functionConfig', cwd: src, errors })
63
+
64
+ // Set function config file path (if one is present)
65
+ let configFile = filepath ? filepath : null
66
+
67
+ // Layer any function config over Arc / project defaults
68
+ if (arcConfig && arcConfig.aws) {
69
+ config = upsert(config, arcConfig.aws)
70
+ }
71
+ if (arcConfig && arcConfig.arc) {
72
+ config = upsert(config, arcConfig.arc)
73
+ }
74
+
75
+ // Interpolate runtimes
76
+ config = getRuntime({ config, inventory })
77
+
78
+ // Disable code sharing on [trans|com]piled functions
79
+ if (compiledRuntimes.includes(config.runtimeConfig?.type)) {
80
+ config.shared = config.views = false
81
+ }
82
+
83
+ // Tidy up any irrelevant properties
84
+ if (!compiledRuntimes.includes(config.runtimeConfig?.type)) {
85
+ // Important: if we don't clean up the build prop, many explosions will explode
86
+ build = undefined
87
+ }
88
+ if (type !== 'http') {
89
+ delete config.apigateway
90
+ }
91
+
92
+ // Now we know the final source dir + runtime + handler: assemble handler props
93
+ let handlerProps = getHandler({ config, src, build, errors })
94
+
95
+ let lambda = {
96
+ name,
97
+ ...getKnownProps(lambdaProps, result), // Pragma-specific stuff
98
+ config,
99
+ src,
100
+ build,
101
+ ...handlerProps,
102
+ configFile,
103
+ pragma: type !== 'customLambdas' ? type : null,
104
+ }
105
+ // Final tidying of any undefined properties
106
+ Object.keys(lambda).forEach(k => !is.defined(lambda[k]) && delete lambda[k])
107
+
108
+ lambdas.push(lambda)
85
109
  }
86
110
 
87
111
  return lambdas
88
112
  }
89
113
 
90
- let ts = 'tables-streams'
91
-
92
- function getLambda (params) {
93
- let { type } = params
94
- params.dir = `src/${type}/`
95
-
96
- if (type === 'http') return getHTTP(params)
97
- if (type === 'events') return getEvents(params)
98
- if (type === 'plugins') return getPlugins(params)
99
- if (type === 'queues') return getEvents(params) // Effectively the same as events
100
- if (type === 'scheduled') return getScheduled(params)
101
- if (type === ts) return getTablesStreams(params)
102
- if (type === 'tables') return getTablesStreams(params) // Shortcut for creating streams
103
- /* istanbul ignore else */ /* Clearer to be explicit here */
104
- if (type === 'ws') return getWS(params)
114
+ // Lambda setter plugins can technically return anything, so this ensures everything is tidy
115
+ let lambdaProps = [ 'cron', 'method', 'path', 'plugin', 'rate', 'route', 'table' ]
116
+ let configProps = [ ...Object.keys(defaultFunctionConfig()), 'fifo', 'views' ]
117
+ let getKnownProps = (knownProps, raw = {}) => {
118
+ let props = knownProps.flatMap(prop => is.defined(raw[prop]) ? [ [ prop, raw[prop] ] ] : [])
119
+ return Object.fromEntries(props)
105
120
  }
106
121
 
122
+ let cl = 'customLambdas'
123
+ let ts = 'tables-streams'
124
+
107
125
  module.exports = {
108
126
  events: populateLambda.bind({}, 'events'),
109
127
  http: populateLambda.bind({}, 'http'),
110
- plugins: populateLambda.bind({}, 'plugins'),
128
+ [cl]: populateLambda.bind({}, cl),
111
129
  queues: populateLambda.bind({}, 'queues'),
112
130
  scheduled: populateLambda.bind({}, 'scheduled'),
113
131
  tables: populateLambda.bind({}, 'tables'),
@@ -2,9 +2,10 @@ let populate = require('./populate-lambda')
2
2
  let validate = require('./validate')
3
3
 
4
4
  module.exports = function configureQueues ({ arc, inventory, errors }) {
5
- if (!arc.queues || !arc.queues.length) return null
5
+ let queuesPlugins = inventory.plugins?._methods?.set?.queues
6
+ if (!arc?.queues?.length && !queuesPlugins?.length) return null
6
7
 
7
- let queues = populate.queues(arc.queues, inventory, errors)
8
+ let queues = populate.queues({ arc, inventory, errors })
8
9
 
9
10
  validate.queues(queues, '@queues', errors)
10
11
 
@@ -2,9 +2,10 @@ let populate = require('./populate-lambda')
2
2
  let validate = require('./validate')
3
3
 
4
4
  module.exports = function configureScheduled ({ arc, inventory, errors }) {
5
- if (!arc.scheduled || !arc.scheduled.length) return null
5
+ let scheduledPlugins = inventory.plugins?._methods?.set?.scheduled
6
+ if (!arc?.scheduled?.length && !scheduledPlugins?.length) return null
6
7
 
7
- let scheduled = populate.scheduled(arc.scheduled, inventory, errors)
8
+ let scheduled = populate.scheduled({ arc, inventory, errors })
8
9
 
9
10
  validate.scheduled(scheduled, errors)
10
11
 
@@ -1,24 +1,24 @@
1
1
  let { join } = require('path')
2
2
  let validate = require('./validate')
3
- let is = require('../../lib/is')
4
- let { lambdas } = require('../../lib/pragmas')
3
+ let { is, pragmas } = require('../../lib')
4
+ let lambdas = pragmas.lambdas.concat('customLambdas')
5
5
 
6
6
  module.exports = function configureShared ({ arc, pragmas, inventory, errors }) {
7
7
  if (!pragmas.lambdaSrcDirs) return null
8
8
 
9
- let cwd = inventory._project.src
10
- let src = join(cwd, 'src', 'shared')
9
+ let { cwd, src: projSrc } = inventory._project
10
+ let src = join(projSrc, 'shared')
11
11
  let shared = {
12
12
  src,
13
13
  shared: [] // Revert to null later if none are defined
14
14
  }
15
- if (arc.shared && arc.shared.length) {
16
- let foundSrc = false
17
15
 
18
- // First pass to get + check views folder (if any)
16
+ // First pass to get + check shared folder (if any)
17
+ let foundSrc = false
18
+ if (arc?.shared?.length) {
19
19
  for (let share of arc.shared) {
20
20
  if (is.array(share)) {
21
- let key = share[0].toLowerCase()
21
+ let key = share[0]?.toLowerCase()
22
22
  if (key === 'src' && is.string(share[1])) {
23
23
  shared.src = share[1]
24
24
  foundSrc = true
@@ -30,49 +30,49 @@ module.exports = function configureShared ({ arc, pragmas, inventory, errors })
30
30
  }
31
31
  }
32
32
  }
33
+ }
33
34
 
34
- // Proceeding from here resets all views config, so make sure it's only if specific views are specified
35
- let some = !(arc.shared.length === 1 && foundSrc)
36
- if (some) {
37
- // Reset shared settings
38
- for (let pragma of lambdas) {
39
- if (!pragmas[pragma]) continue
40
- for (let { config } of pragmas[pragma]) {
41
- config.shared = false
42
- }
35
+ // Exit if configured shared folder doesn't exist
36
+ if (!is.exists(shared.src)) return null
37
+
38
+ // 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 && foundSrc)
40
+ if (some) {
41
+ // Reset shared settings
42
+ for (let pragma of lambdas) {
43
+ if (!pragmas[pragma]) continue
44
+ for (let { config } of pragmas[pragma]) {
45
+ config.shared = false
43
46
  }
47
+ }
44
48
 
45
- // Set new shared settings
46
- for (let pragma of arc.shared) {
47
- if (is.array(pragma)) continue // Bail on src setting
48
- if (!is.object(pragma)) {
49
- return errors.push(`@shared invalid setting: ${pragma}`)
50
- }
49
+ // Set new shared settings
50
+ for (let pragma of arc.shared) {
51
+ if (is.array(pragma)) continue // Bail on src setting
52
+ if (!is.object(pragma)) {
53
+ return errors.push(`@shared invalid setting: ${pragma}`)
54
+ }
51
55
 
52
- let p = Object.keys(pragma)[0]
53
- if (!lambdas.includes(p)) {
54
- return errors.push(`${p} is not a valid @shared pragma`)
55
- }
56
+ let p = Object.keys(pragma)[0]
57
+ if (!lambdas.includes(p)) {
58
+ return errors.push(`${p} is not a valid @shared pragma`)
59
+ }
56
60
 
57
- let entries = is.object(pragma[p])
58
- ? Object.entries(pragma[p])
59
- : pragma[p]
60
- for (let lambda of entries) {
61
- let name = p === 'http' ? lambda.join(' ') : lambda
62
- let fn = pragmas[p].find(n => n.name === name)
63
- if (!fn) {
64
- return errors.push(`@shared ${name} not found in @${p} Lambdas`)
65
- }
66
- // Ignore shared into ASAP
67
- if (!fn.arcStaticAssetProxy) fn.config.shared = true
61
+ let entries = is.object(pragma[p])
62
+ ? Object.entries(pragma[p])
63
+ : pragma[p]
64
+ for (let lambda of entries) {
65
+ let name = p === 'http' ? lambda.join(' ') : lambda
66
+ let fn = pragmas[p].find(n => n.name === name)
67
+ if (!fn) {
68
+ return errors.push(`@shared ${name} not found in @${p} Lambdas`)
68
69
  }
70
+ // Ignore shared into ASAP
71
+ if (!fn.arcStaticAssetProxy) fn.config.shared = true
69
72
  }
70
73
  }
71
74
  }
72
75
 
73
- // Exit if default views folder doesn't exist
74
- if (!is.exists(shared.src)) return null
75
-
76
76
  // lambda.config.shared was added by function config defaults, or added above
77
77
  for (let pragma of lambdas) {
78
78
  if (!pragmas[pragma]) continue
@@ -83,8 +83,5 @@ module.exports = function configureShared ({ arc, pragmas, inventory, errors })
83
83
  }
84
84
  }
85
85
 
86
- // De-dupe (in case multiple functions live at the same src path)
87
- shared.shared = [ ...new Set(shared.shared) ]
88
-
89
86
  return shared
90
87
  }
@@ -0,0 +1,72 @@
1
+ let { httpMethods } = require('../../../lib')
2
+
3
+ /**
4
+ * HTTP route sorter; this is a multifurcating tree, so we'll do a few passes
5
+ * Broadly in the theme of most to least specific:
6
+ * - First, by method, with `any` last
7
+ * - Then by path depth (descending)
8
+ * - Within each depth downrank for contained params
9
+ * - Then sort alphabetically
10
+ * - Then, ensure trailing captures are last
11
+ * - Finally, ensure root captures rank below a root literal
12
+ */
13
+ module.exports = function sortHTTP (http) {
14
+ // Construct the tree from HTTP methods
15
+ let tree = {}
16
+ http.forEach(({ method, path }) => {
17
+ if (!tree[method]) tree[method] = []
18
+ let parts = path.split('/').filter(Boolean)
19
+ let depth = parts.length
20
+ let item = { depth, path }
21
+ let param = /\/:/
22
+ if (path.match(param)) {
23
+ item.hasParam = true
24
+ item.paramIndex = path.match(param).index // If multiple, we want the earliest
25
+ }
26
+ if (parts.length) {
27
+ if (parts[depth - 1] === '*') item.trailingCapture = 'catchall'
28
+ if (parts[depth - 1].startsWith(':')) item.trailingCapture = 'param'
29
+ }
30
+ tree[method].push(item)
31
+ })
32
+
33
+ // Multi-pass route sort
34
+ let sorted = []
35
+ httpMethods.forEach(method => {
36
+ if (!tree[method]) return
37
+ /* istanbul ignore next: random test shuffles may not trigger all paths */
38
+ tree[method]
39
+ // Sort by depth
40
+ .sort((a, b) => b.depth - a.depth)
41
+ // Sort within a given depth
42
+ .sort((a, b) => {
43
+ // Handle root (depth: 0)
44
+ if (a.depth - b.depth < 0) return
45
+ if (a.hasParam && b.hasParam) {
46
+ // Sort at the earliest param
47
+ if (a.paramIndex < b.paramIndex) return 1
48
+ if (a.paramIndex > b.paramIndex) return -1
49
+ // Then sort alphabetically
50
+ if (a.path < b.path) return -1
51
+ if (a.path > b.path) return 1
52
+ }
53
+ if (a.hasParam) return 1
54
+ if (b.hasParam) return -1
55
+ if (a.path < b.path) return -1
56
+ if (a.path > b.path) return 1
57
+ })
58
+ // Trailing capture sort
59
+ .sort((a, b) => {
60
+ if (!a.depth && b.depth === 1 && b.trailingCapture) return -1
61
+ if (a.depth - b.depth < 0) return
62
+ if (a.trailingCapture) return 1
63
+ if (b.trailingCapture) return -1
64
+ })
65
+ tree[method].forEach(({ path }) => {
66
+ let route = http.find(i => i.method === method && i.path === path)
67
+ sorted.push(route)
68
+ })
69
+ })
70
+
71
+ return sorted
72
+ }
@@ -1,5 +1,4 @@
1
- let asapSrc = require('../../lib/asap-src')
2
- let is = require('../../lib/is')
1
+ let { asapSrc, is } = require('../../lib')
3
2
 
4
3
  module.exports = function configureStatic ({ arc, inventory }) {
5
4
  // @static is inferred by @http
@@ -1,19 +1,15 @@
1
- let is = require('../../lib/is')
1
+ let { is } = require('../../lib')
2
2
  let validate = require('./validate')
3
3
 
4
- function configureTablesIndexes ({ arc, errors }) {
4
+ module.exports = function configureTablesIndexes ({ arc, errors }) {
5
5
  if (!arc['tables-indexes'] || !arc['tables-indexes'].length) return null
6
6
  if (arc['tables-indexes'] && !arc.tables) {
7
7
  errors.push(`Specifying @tables-indexes requires specifying corresponding @tables`)
8
8
  return null
9
9
  }
10
- if (arc['tables-indexes']?.length && arc.indexes?.length) {
11
- errors.push(`Either @tables-indexes or @indexes can be specified, but not both`)
12
- return null
13
- }
14
10
 
15
11
  let indexes = getIndexes(arc, 'tables-indexes', errors)
16
- validate.indexes(indexes, '@tables-indexes', errors)
12
+ validate.tablesIndexes(indexes, '@tables-indexes', errors)
17
13
 
18
14
  return indexes
19
15
  }
@@ -54,6 +50,3 @@ let getIndexes = (arc, pragma, errors) => {
54
50
  error(index)
55
51
  }).filter(Boolean) // Invalid indexes may create undefined entries in the map
56
52
  }
57
-
58
- configureTablesIndexes.getIndexes = getIndexes
59
- module.exports = configureTablesIndexes