@toa.io/norm 1.0.0-alpha.33 → 1.0.0-alpha.331

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 (100) hide show
  1. package/CHANGELOG.md +389 -0
  2. package/package.json +9 -7
  3. package/src/.component/.expand/bridge.js +2 -6
  4. package/src/.component/.expand/entity.js +2 -9
  5. package/src/.component/.expand/events.js +2 -6
  6. package/src/.component/.expand/extensions.js +8 -9
  7. package/src/.component/.expand/index.js +7 -19
  8. package/src/.component/.expand/operations.js +2 -9
  9. package/src/.component/.expand/receivers.js +4 -7
  10. package/src/.component/.expand/version.js +72 -28
  11. package/src/.component/.normalize/entity.js +7 -0
  12. package/src/.component/.normalize/events.js +16 -4
  13. package/src/.component/.normalize/index.js +4 -9
  14. package/src/.component/.normalize/operations.js +13 -6
  15. package/src/.component/.normalize/receivers.js +4 -5
  16. package/src/.component/collapse.js +60 -9
  17. package/src/.component/defaults.js +5 -11
  18. package/src/.component/dependencies.js +14 -10
  19. package/src/.component/dereference.js +5 -49
  20. package/src/.component/expand.js +3 -9
  21. package/src/.component/extensions.js +9 -10
  22. package/src/.component/index.js +10 -21
  23. package/src/.component/merge.js +25 -15
  24. package/src/.component/migrations.js +57 -0
  25. package/src/.component/normalize.js +10 -6
  26. package/src/.component/schema.yaml +165 -70
  27. package/src/.component/validate.js +67 -17
  28. package/src/.context/.dependencies/connectors.js +5 -9
  29. package/src/.context/.dependencies/describe.js +4 -5
  30. package/src/.context/.dependencies/extensions.js +98 -20
  31. package/src/.context/.dependencies/index.js +3 -9
  32. package/src/.context/.dependencies/resolve.js +44 -7
  33. package/src/.context/complete.js +1 -5
  34. package/src/.context/converge.js +31 -0
  35. package/src/.context/dependencies.js +44 -6
  36. package/src/.context/dereference.js +11 -6
  37. package/src/.context/evict.js +40 -0
  38. package/src/.context/expand.js +37 -5
  39. package/src/.context/index.js +8 -15
  40. package/src/.context/normalize.js +9 -7
  41. package/src/.context/schema.yaml +183 -11
  42. package/src/.context/validate.js +11 -14
  43. package/src/component.js +33 -14
  44. package/src/context.js +47 -15
  45. package/src/definition.js +63 -0
  46. package/src/entity.js +15 -0
  47. package/src/index.js +7 -9
  48. package/src/manifest.js +159 -0
  49. package/src/map.js +26 -0
  50. package/src/shortcuts.js +12 -15
  51. package/test/component/collapse.fixtures.js +30 -39
  52. package/test/component/collapse.test.js +90 -10
  53. package/test/component/dependencies.test.js +16 -14
  54. package/test/component/dereference.fixtures.js +10 -98
  55. package/test/component/dereference.test.js +10 -14
  56. package/test/component/dummies/extension/index.js +1 -5
  57. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
  58. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
  59. package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
  60. package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
  61. package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
  62. package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
  63. package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
  64. package/test/component/expand.fixtures.js +13 -13
  65. package/test/component/expand.test.js +17 -13
  66. package/test/component/migrations.test.js +62 -0
  67. package/test/component/normalize.fixtures.js +3 -7
  68. package/test/component/normalize.test.js +94 -28
  69. package/test/component/validate.fixtures.js +17 -15
  70. package/test/component/validate.test.js +311 -108
  71. package/test/context/complete.fixtures.js +2 -8
  72. package/test/context/complete.test.js +14 -8
  73. package/test/context/converge.test.js +57 -0
  74. package/test/context/dereference.fixtures.js +3 -9
  75. package/test/context/dereference.test.js +18 -7
  76. package/test/context/evict.fixtures.js +39 -0
  77. package/test/context/evict.test.js +73 -0
  78. package/test/context/expand.fixtures.js +1 -5
  79. package/test/context/expand.test.js +36 -8
  80. package/test/context/normalize.fixtures.js +2 -7
  81. package/test/context/normalize.test.js +33 -9
  82. package/test/context/validate.fixtures.js +1 -6
  83. package/test/context/validate.test.js +173 -35
  84. package/test/definition.test.js +35 -0
  85. package/test/dummies/bare/index.js +1 -0
  86. package/test/dummies/defined/definition.js +1 -0
  87. package/test/dummies/defined/index.js +1 -0
  88. package/test/dummies/defined/package.json +1 -0
  89. package/test/manifest.test.js +137 -0
  90. package/test/shortcuts.fixtures.js +4 -9
  91. package/test/shortcuts.test.js +17 -15
  92. package/test/system.test.js +31 -0
  93. package/types/component.d.ts +32 -5
  94. package/types/context/declaration.d.ts +34 -3
  95. package/types/context.d.ts +27 -13
  96. package/types/entity.d.ts +7 -0
  97. package/types/index.d.ts +18 -3
  98. package/src/.component/.expand/properties.js +0 -14
  99. package/src/.context/.dependencies/load.js +0 -36
  100. package/test/context/dependencies.load.test.js +0 -24
@@ -1,45 +1,89 @@
1
- 'use strict'
1
+ import { join } from 'node:path'
2
+ import { createHash } from 'node:crypto'
3
+ import fs from 'node:fs/promises'
4
+ import { glob } from 'tinyglobby'
2
5
 
3
- const { join } = require('node:path')
4
- const { createHash } = require('node:crypto')
5
- const fs = require('node:fs/promises')
6
- const { createReadStream } = require('node:fs')
7
- const { once } = require('node:events')
6
+ import { definition } from '../../definition.js'
8
7
 
9
- async function version (manifest) {
10
- manifest.version ??= await hash(manifest.path)
8
+ export async function version(manifest) {
9
+ manifest.version ??= await hash(manifest)
11
10
  }
12
11
 
13
- async function hash (path) {
14
- const hash = await hashd(path)
12
+ /**
13
+ * Identifies a build of a component: it becomes the tag of its image, so it must be the
14
+ * same for the same sources and different for different ones — on any machine, and whichever
15
+ * build produced them.
16
+ *
17
+ * What a build leaves out cannot change it: the bridge states what no build ships, a component
18
+ * adds to that with `ignore` and puts one back with `!`, and `files` names what its version is
19
+ * made of where a build copies only some of it. Its manifest is always one of them, because
20
+ * what it declares is what a caller is held to.
21
+ */
22
+ async function hash(manifest) {
23
+ const files = (await list(manifest)).sort()
24
+ const digests = await Promise.all(
25
+ files.map((file) => digest(join(manifest.path, file)))
26
+ )
27
+ const total = createHash('sha256')
15
28
 
16
- return hash.digest('hex').slice(0, 8)
29
+ // the path is part of it: moving a file changes the build even if no content did
30
+ for (let i = 0; i < files.length; i++) total.update(files[i]).update(digests[i])
31
+
32
+ return total.digest('hex').slice(0, 8)
17
33
  }
18
34
 
19
35
  /**
20
- * @param {string} path
21
- * @param {import('node:crypto').Hash} hash
36
+ * @param {toa.norm.Component} manifest
37
+ * @returns {Promise<string[]>} paths relative to the component
22
38
  */
23
- async function hashd (path, hash = createHash('sha256')) {
24
- const stat = await fs.stat(path)
39
+ async function list(manifest) {
40
+ const files = await glob(patterns(manifest.files), {
41
+ cwd: manifest.path,
42
+ dot: true,
43
+ onlyFiles: true,
44
+ expandDirectories: false,
45
+ // sources a component keeps outside its own directory are its own: what a link points to is
46
+ // hashed as the file it stands for, so changing it changes the version
47
+ followSymbolicLinks: true,
48
+ ignore: await ignore(manifest)
49
+ })
50
+
51
+ // what a component declares is what a caller is held to, so its manifest is always one of them
52
+ if (!files.includes(MANIFEST) && (await exists(join(manifest.path, MANIFEST))))
53
+ files.push(MANIFEST)
25
54
 
26
- if (stat.isFile()) {
27
- const stream = createReadStream(path)
55
+ return files
56
+ }
28
57
 
29
- stream.pipe(hash, { end: false })
58
+ /** A path names what is under it, and a pattern is taken as it is. */
59
+ function patterns(files = ['**']) {
60
+ return files.flatMap((file) => (file.includes('*') ? [file] : [file, file + '/**']))
61
+ }
30
62
 
31
- return await once(stream, 'end')
32
- }
63
+ async function ignore(manifest) {
64
+ const { module } = await definition(manifest.bridge)
65
+ const stated = manifest.ignore ?? []
66
+ const back = new Set(
67
+ stated.filter((pattern) => pattern[0] === '!').map((pattern) => pattern.slice(1))
68
+ )
33
69
 
34
- if (stat.isDirectory()) {
35
- const entries = await fs.opendir(path)
70
+ return [
71
+ ...(module.ignore ?? []).filter((pattern) => !back.has(pattern)),
72
+ ...stated.filter((pattern) => pattern[0] !== '!')
73
+ ]
74
+ }
36
75
 
37
- for await (const entry of entries) {
38
- await hashd(join(path, entry.name), hash)
39
- }
76
+ async function exists(path) {
77
+ return await fs
78
+ .access(path)
79
+ .then(() => true)
80
+ .catch(() => false)
81
+ }
40
82
 
41
- return hash
42
- }
83
+ async function digest(path) {
84
+ return createHash('sha256')
85
+ .update(await fs.readFile(path))
86
+ .digest()
43
87
  }
44
88
 
45
- exports.version = version
89
+ const MANIFEST = 'manifest.toa.yaml'
@@ -0,0 +1,7 @@
1
+ export function entity(component) {
2
+ if (component.entity === undefined) return
3
+
4
+ component.entity.storage ??= '@toa.io/storages.mongodb'
5
+ component.entity.associated ??= false
6
+ component.entity.custom ??= false
7
+ }
@@ -1,13 +1,25 @@
1
- 'use strict'
1
+ import { definition } from '../../definition.js'
2
2
 
3
- const events = (component) => {
3
+ export const events = async (component) => {
4
4
  if (component.events === undefined) return
5
5
 
6
- const binding = component.bindings.find((binding) => require(binding).properties.async === true)
6
+ const binding = await asynchronous(component.bindings)
7
7
 
8
8
  for (const event of Object.values(component.events)) {
9
9
  if (event.binding === undefined) event.binding = binding
10
+
11
+ event.conditioned ??= false
12
+ event.subjective ??= false
10
13
  }
11
14
  }
12
15
 
13
- exports.events = events
16
+ /** The first binding that carries events, which only its module can say. */
17
+ async function asynchronous(bindings) {
18
+ for (const binding of bindings) {
19
+ const { properties } = (await definition(binding)).module
20
+
21
+ if (properties.async === true) return binding
22
+ }
23
+
24
+ return undefined
25
+ }
@@ -1,9 +1,4 @@
1
- 'use strict'
2
-
3
- const { events } = require('./events')
4
- const { operations } = require('./operations')
5
- const { receivers } = require('./receivers')
6
-
7
- exports.events = events
8
- exports.operations = operations
9
- exports.receivers = receivers
1
+ export { entity } from './entity.js'
2
+ export { events } from './events.js'
3
+ export { operations } from './operations.js'
4
+ export { receivers } from './receivers.js'
@@ -1,16 +1,23 @@
1
- 'use strict'
2
-
3
- const operations = (component) => {
1
+ export const operations = (component) => {
4
2
  if (component.operations === undefined) return
5
3
 
6
4
  for (const [endpoint, operation] of Object.entries(component.operations)) {
7
- if (operation.scope === 'none')
5
+ // what an operation of this type acquires, where it did not say
6
+ if (operation.type === 'computation' || operation.type === 'unmanaged') {
7
+ operation.scope = 'none'
8
8
  operation.query = false
9
+ } else if (operation.type === 'effect') operation.scope ??= 'none'
10
+
11
+ if (operation.scope === 'none') operation.query = false
12
+
13
+ // an operation that states no output states an empty schema, which every reply fits
14
+ operation.output ??= {}
15
+
16
+ // a bridge says an algorithm takes no input with `null`, which the manifest states as none
17
+ if (operation.input === null) delete operation.input
9
18
 
10
19
  if (operation.bindings === undefined) operation.bindings = component.bindings
11
20
  if (operation.bindings === null) operation.bindings = []
12
21
  if (operation.virtual === true) delete component.operations[endpoint]
13
22
  }
14
23
  }
15
-
16
- exports.operations = operations
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- function receivers (component) {
1
+ export function receivers(component) {
4
2
  if (component.receivers === undefined) return
5
3
 
6
4
  const receivers = component.receivers
@@ -9,6 +7,9 @@ function receivers (component) {
9
7
  const segments = key.split('.')
10
8
  const source = value.source ?? 'default'
11
9
 
10
+ value.conditioned ??= false
11
+ value.adaptive ??= false
12
+
12
13
  if (source !== 'default') continue
13
14
  if (segments.length === 3) continue // already with a namespace
14
15
 
@@ -18,5 +19,3 @@ function receivers (component) {
18
19
  receivers[newKey] = value
19
20
  }
20
21
  }
21
-
22
- exports.receivers = receivers
@@ -1,8 +1,6 @@
1
- 'use strict'
1
+ import { merge } from '@toa.io/generic'
2
2
 
3
- const { merge } = require('@toa.io/generic')
4
-
5
- const collapse = (manifest, prototype) => {
3
+ export const collapse = (manifest, prototype) => {
6
4
  delete manifest.prototype
7
5
 
8
6
  if (prototype.operations) {
@@ -14,7 +12,8 @@ const collapse = (manifest, prototype) => {
14
12
  if (manifest.operations === undefined) manifest.operations = {}
15
13
 
16
14
  for (let [endpoint, operation] of operations) {
17
- if (manifest.operations[endpoint] === undefined) manifest.operations[endpoint] = {}
15
+ if (manifest.operations[endpoint] === undefined)
16
+ manifest.operations[endpoint] = {}
18
17
  else {
19
18
  const { virtual, ...real } = operation
20
19
 
@@ -26,7 +25,8 @@ const collapse = (manifest, prototype) => {
26
25
  merge(manifest.operations[endpoint], declaration)
27
26
 
28
27
  if (bridge !== undefined) {
29
- if (manifest.prototype.operations === undefined) manifest.prototype.operations = {}
28
+ if (manifest.prototype.operations === undefined)
29
+ manifest.prototype.operations = {}
30
30
 
31
31
  manifest.prototype.operations[endpoint] = { bridge }
32
32
  }
@@ -36,13 +36,64 @@ const collapse = (manifest, prototype) => {
36
36
 
37
37
  const { entity, events, extensions } = prototype
38
38
 
39
- if (manifest.entity?.schema?.properties.id !== undefined && entity?.schema?.properties.id !== undefined) {
39
+ if (
40
+ manifest.entity?.properties?.id !== undefined &&
41
+ entity?.properties?.id !== undefined
42
+ ) {
40
43
  manifest.entity.custom = true
41
44
 
42
- delete prototype.entity.schema.properties.id
45
+ delete prototype.entity.properties.id
43
46
  }
44
47
 
48
+ /*
49
+ * `id` is the only one a component may state for itself. The rest are written by the runtime
50
+ * and read by it, so one redeclared here would be merged over what the prototype says and
51
+ * change what the record holds, or what a criterion against it compares — without the
52
+ * component that wrote it having anything to do with either.
53
+ */
54
+ for (const name of SYSTEM)
55
+ if (
56
+ manifest.entity?.properties?.[name] !== undefined &&
57
+ entity?.properties?.[name] !== undefined
58
+ )
59
+ throw new Error(`System property '${name}' cannot be overridden`)
60
+
61
+ if (prototype.events !== undefined && manifest.events !== undefined)
62
+ for (const event of Object.keys(prototype.events))
63
+ if (event in manifest.events) delete prototype.events[event]
64
+
65
+ if (entity?.migrations !== undefined) inherit(manifest, prototype)
66
+
45
67
  merge(manifest, { entity, events, extensions })
46
68
  }
47
69
 
48
- exports.collapse = collapse
70
+ /**
71
+ * What a prototype declares is in every record of its descendants, so its migrations are applied
72
+ * to each descendant's collection — ahead of the descendant's own, which are written against
73
+ * what they leave. An inherited one is recorded under the prototype's name, so that it collides
74
+ * with nothing the component names itself; one inherited from further up already carries its
75
+ * name and is passed on as it is.
76
+ */
77
+ function inherit(manifest, prototype) {
78
+ const inherited = prototype.entity.migrations.map((migration) =>
79
+ migration.prototype === undefined
80
+ ? {
81
+ ...migration,
82
+ id: `${prototype.name}:${migration.id}`,
83
+ prototype: prototype.name
84
+ }
85
+ : migration
86
+ )
87
+
88
+ const own = manifest.entity?.migrations ?? []
89
+
90
+ if (manifest.entity === undefined) manifest.entity = {}
91
+
92
+ manifest.entity.migrations = [...inherited, ...own]
93
+
94
+ // merged below by `merge`, which would append them a second time
95
+ delete prototype.entity.migrations
96
+ }
97
+
98
+ /** What the runtime writes into every record. `id` is not among them: a component may own it. */
99
+ const SYSTEM = ['VERSION', 'CREATED', 'UPDATED', 'DELETED', 'REGION']
@@ -1,10 +1,7 @@
1
- 'use strict'
2
-
3
- const { hash } = require('@toa.io/generic')
4
- const assert = require('node:assert')
1
+ import { hash } from '@toa.io/generic'
5
2
 
6
3
  // these defaults are required before validation
7
- const defaults = (manifest, proto) => {
4
+ export const defaults = (manifest, proto) => {
8
5
  if (manifest.name === undefined)
9
6
  if (proto) manifest.name = protoName(manifest)
10
7
  else nameAfterDir(manifest)
@@ -16,18 +13,17 @@ const defaults = (manifest, proto) => {
16
13
  if (manifest.entity.storage === null)
17
14
  manifest.entity.storage = '@toa.io/storages.null'
18
15
  } else {
19
- if (manifest.prototype === undefined)
20
- manifest.prototype = null
16
+ if (manifest.prototype === undefined) manifest.prototype = null
21
17
  }
22
18
 
23
19
  if (manifest.prototype === undefined) manifest.prototype = '@toa.io/prototype'
24
20
  }
25
21
 
26
- function protoName (manifest) {
22
+ function protoName(manifest) {
27
23
  return 'proto' + hash(manifest.path)
28
24
  }
29
25
 
30
- function nameAfterDir (manifest) {
26
+ function nameAfterDir(manifest) {
31
27
  const parts = manifest.path.split('/')
32
28
  const dirname = parts[parts.length - 1]
33
29
  const [name, namespace] = dirname.split('.').reverse()
@@ -35,5 +31,3 @@ function nameAfterDir (manifest) {
35
31
  manifest.name = name
36
32
  manifest.namespace = namespace
37
33
  }
38
-
39
- exports.defaults = defaults
@@ -1,29 +1,33 @@
1
- 'use strict'
1
+ import { createRequire } from 'node:module'
2
+ import { join, dirname } from 'node:path'
2
3
 
3
- const { join, dirname } = require('node:path')
4
+ // import.meta.resolve takes no paths, and a storage is resolved against the
5
+ // component that names it
6
+ const require = createRequire(import.meta.url)
4
7
 
5
8
  /**
6
9
  * @param {toa.norm.Component} component
7
10
  */
8
- const dependencies = (component) => {
9
- if ('entity' in component) component.entity.storage = resolve(component.path, component.entity.storage)
11
+ export const dependencies = (component) => {
12
+ if ('entity' in component)
13
+ component.entity.storage = resolve(component.path, component.entity.storage)
10
14
  }
11
15
 
12
- function resolve (root, reference) {
13
- const paths = [root, __dirname]
16
+ function resolve(root, reference) {
17
+ const paths = [root, import.meta.dirname]
14
18
  const options = { paths }
15
19
 
16
20
  let path
17
21
 
18
- try { // as package
22
+ try {
23
+ // as package
19
24
  const packageJsonRef = join(reference, 'package.json')
20
25
 
21
26
  path = require.resolve(packageJsonRef, options)
22
- } catch { // as directory
27
+ } catch {
28
+ // as directory
23
29
  path = require.resolve(reference, options)
24
30
  }
25
31
 
26
32
  return dirname(path)
27
33
  }
28
-
29
- exports.dependencies = dependencies
@@ -1,34 +1,8 @@
1
- 'use strict'
1
+ import { merge } from '@toa.io/generic'
2
2
 
3
- const { merge } = require('@toa.io/generic')
3
+ export const dereference = (manifest) => {
4
+ if (!('operations' in manifest)) return
4
5
 
5
- const dereference = (manifest) => {
6
- // schemas
7
- const resolver = createResolver(manifest.entity?.schema?.properties)
8
-
9
- if (manifest.entity !== undefined)
10
- schema(manifest.entity.schema, resolver)
11
-
12
- if ('operations' in manifest)
13
- operations(manifest, resolver)
14
-
15
- }
16
-
17
- const createResolver = (properties) => (property) => {
18
- if (properties?.[property] === undefined) {
19
- throw new Error(`Referenced property '${property}' is not defined`)
20
- }
21
-
22
- return properties[property]
23
- }
24
-
25
- function operations (manifest, resolver) {
26
- for (const operation of Object.values(manifest.operations)) {
27
- if (operation.input !== undefined) operation.input = schema(operation.input, resolver)
28
- if (operation.output !== undefined) operation.output = schema(operation.output, resolver)
29
- }
30
-
31
- // forwarding
32
6
  for (const operation of Object.values(manifest.operations)) {
33
7
  if (operation.forward !== undefined) forward(operation, manifest.operations)
34
8
  }
@@ -36,29 +10,13 @@ function operations (manifest, resolver) {
36
10
  for (const operation of Object.values(manifest.operations)) {
37
11
  delete operation.forwarded
38
12
  }
39
-
40
- }
41
-
42
- const schema = (object, resolve) => {
43
- if (object === undefined || object === null || typeof object !== 'object') return
44
- if (object.type === 'string' && object.default?.[0] === '.') return resolve(object.default.substring(1))
45
-
46
- if (object.type === 'array') {
47
- object.items = schema(object.items, resolve)
48
- } else if (object.properties !== undefined) {
49
- for (const [name, value] of Object.entries(object.properties)) {
50
- if (value?.type === 'string' && value.default === '.') object.properties[name] = resolve(name)
51
- else object.properties[name] = schema(value, resolve)
52
- }
53
- }
54
-
55
- return object
56
13
  }
57
14
 
58
15
  const forward = (operation, operations) => {
59
16
  const target = operations[operation.forward]
60
17
 
61
- if (target === undefined) throw new Error(`Referenced operation '${operation.forward}' is not defined`)
18
+ if (target === undefined)
19
+ throw new Error(`Referenced operation '${operation.forward}' is not defined`)
62
20
 
63
21
  if (target.forward !== undefined) {
64
22
  if (target.forwarded !== true) forward(target, operations)
@@ -72,5 +30,3 @@ const forward = (operation, operations) => {
72
30
 
73
31
  merge(operation, real)
74
32
  }
75
-
76
- exports.dereference = dereference
@@ -1,26 +1,20 @@
1
- 'use strict'
2
-
3
- const {
1
+ import {
4
2
  entity,
5
3
  bridge,
6
4
  operations,
7
5
  events,
8
6
  receivers,
9
7
  extensions,
10
- properties,
11
8
  version
12
- } = require('./.expand')
9
+ } from './.expand/index.js'
13
10
 
14
- async function expand (manifest) {
11
+ export async function expand(manifest) {
15
12
  entity(manifest)
16
13
  bridge(manifest)
17
14
  operations(manifest)
18
15
  events(manifest)
19
16
  receivers(manifest)
20
- properties(manifest)
21
17
  extensions(manifest)
22
18
 
23
19
  await version(manifest)
24
20
  }
25
-
26
- exports.expand = expand
@@ -1,10 +1,10 @@
1
- 'use strict'
1
+ import { find } from '@toa.io/generic'
2
+ import { PREDEFINED } from '@toa.io/definitions'
3
+ import { resolve } from '../shortcuts.js'
4
+ import { definition } from '../definition.js'
2
5
 
3
- const { directory: { find } } = require('@toa.io/filesystem')
4
- const { resolve } = require('../shortcuts')
5
-
6
- const extensions = (manifest) => {
7
- if (manifest.extensions === undefined) return
6
+ export const extensions = async (manifest) => {
7
+ manifest.extensions = Object.assign({}, PREDEFINED, manifest.extensions)
8
8
 
9
9
  const extensions = manifest.extensions
10
10
 
@@ -14,12 +14,13 @@ const extensions = (manifest) => {
14
14
  // relative path
15
15
  if (key[0] === '.') key = find(key, manifest.path)
16
16
 
17
- const extension = require(key)
17
+ const { module: extension } = await definition(key)
18
18
 
19
19
  if (extension.manifest !== undefined) {
20
20
  declaration = extension.manifest(declaration, manifest)
21
21
 
22
- if (declaration === undefined) throw new Error(`Extension '${reference}' hasn't returned manifest`)
22
+ if (declaration === undefined)
23
+ throw new Error(`Extension '${reference}' hasn't returned manifest`)
23
24
  }
24
25
 
25
26
  extensions[key] = declaration
@@ -28,5 +29,3 @@ const extensions = (manifest) => {
28
29
  if (reference !== key) delete extensions[reference]
29
30
  }
30
31
  }
31
-
32
- exports.extensions = extensions
@@ -1,21 +1,10 @@
1
- 'use strict'
2
-
3
- const { collapse } = require('./collapse')
4
- const { defaults } = require('./defaults')
5
- const { dependencies } = require('./dependencies')
6
- const { dereference } = require('./dereference')
7
- const { expand } = require('./expand')
8
- const { merge } = require('./merge')
9
- const { normalize } = require('./normalize')
10
- const { validate } = require('./validate')
11
- const { extensions } = require('./extensions')
12
-
13
- exports.collapse = collapse
14
- exports.defaults = defaults
15
- exports.dependencies = dependencies
16
- exports.dereference = dereference
17
- exports.expand = expand
18
- exports.merge = merge
19
- exports.normalize = normalize
20
- exports.validate = validate
21
- exports.extensions = extensions
1
+ export { collapse } from './collapse.js'
2
+ export { defaults } from './defaults.js'
3
+ export { dependencies } from './dependencies.js'
4
+ export { dereference } from './dereference.js'
5
+ export { expand } from './expand.js'
6
+ export { merge } from './merge.js'
7
+ export { migrations } from './migrations.js'
8
+ export { normalize } from './normalize.js'
9
+ export { validate } from './validate.js'
10
+ export { extensions } from './extensions.js'
@@ -1,12 +1,12 @@
1
- 'use strict'
1
+ import { merge as combine } from '@toa.io/generic'
2
+ import { definition } from '../definition.js'
2
3
 
3
- const { merge } = require('@toa.io/generic')
4
-
5
- const bridge = async (root, manifest) => {
4
+ export const merge = async (root, manifest) => {
6
5
  await Promise.all([
7
6
  define(root, manifest, 'operations'),
8
7
  define(root, manifest, 'events'),
9
- define(root, manifest, 'receivers')
8
+ define(root, manifest, 'receivers'),
9
+ define(root, manifest, 'guards')
10
10
  ])
11
11
  }
12
12
 
@@ -19,18 +19,17 @@ const define = async (root, manifest, property) => {
19
19
  if (item.bridge === undefined || item.bridge === manifest.bridge) continue // default bridge later
20
20
 
21
21
  const bridge = item.bridge || manifest.bridge
22
- const { define } = require(bridge)
22
+ const { define } = await bridged(bridge)
23
23
  const definition = await define[singular](root, endpoint)
24
24
 
25
- merge(item, definition)
25
+ combine(item, definition)
26
26
  }
27
27
  }
28
28
 
29
29
  // default bridge
30
30
  const definition = await scan(manifest.bridge, root, property)
31
31
 
32
- if (definition === undefined)
33
- return
32
+ if (definition === undefined) return
34
33
 
35
34
  const items = Object.entries(definition)
36
35
 
@@ -44,18 +43,29 @@ const define = async (root, manifest, property) => {
44
43
 
45
44
  const declared = manifest[property][endpoint]
46
45
 
47
- if (declared === undefined) manifest[property][endpoint] = item
48
- else merge(declared, item)
46
+ try {
47
+ if (declared === undefined) manifest[property][endpoint] = item
48
+ else combine(declared, item)
49
+ } catch (error) {
50
+ console.error(`Error merging ${singular} '${endpoint}' in ${root}`)
51
+ throw error
52
+ }
49
53
  }
50
54
  }
51
55
  }
52
56
 
53
57
  const scan = async (bridge, root, property) => {
54
- const { define } = require(bridge)
58
+ const { define } = await bridged(bridge)
55
59
 
56
- if (property in define)
57
- return define[property](root)
60
+ if (property in define) return define[property](root)
58
61
  else return undefined
59
62
  }
60
63
 
61
- exports.merge = bridge
64
+ async function bridged(reference) {
65
+ const { module } = await definition(reference)
66
+
67
+ if (module.define === undefined)
68
+ throw new Error(`Bridge '${reference}' defines nothing: it exports no 'define'`)
69
+
70
+ return module
71
+ }