@toa.io/norm 1.0.0-alpha.3 → 1.0.0-alpha.300

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 (98) hide show
  1. package/CHANGELOG.md +124 -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 +5 -8
  7. package/src/.component/.expand/index.js +7 -17
  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 +55 -0
  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 +10 -6
  15. package/src/.component/.normalize/receivers.js +4 -5
  16. package/src/.component/collapse.js +60 -9
  17. package/src/.component/defaults.js +16 -25
  18. package/src/.component/dependencies.js +14 -10
  19. package/src/.component/dereference.js +20 -18
  20. package/src/.component/expand.js +6 -9
  21. package/src/.component/extensions.js +9 -10
  22. package/src/.component/index.js +10 -21
  23. package/src/.component/merge.js +25 -13
  24. package/src/.component/migrations.js +57 -0
  25. package/src/.component/normalize.js +10 -6
  26. package/src/.component/schema.yaml +139 -59
  27. package/src/.component/validate.js +37 -17
  28. package/src/.context/.dependencies/connectors.js +11 -10
  29. package/src/.context/.dependencies/describe.js +4 -5
  30. package/src/.context/.dependencies/extensions.js +93 -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 +43 -6
  36. package/src/.context/dereference.js +11 -6
  37. package/src/.context/evict.js +38 -0
  38. package/src/.context/expand.js +11 -6
  39. package/src/.context/index.js +8 -15
  40. package/src/.context/normalize.js +9 -7
  41. package/src/.context/schema.yaml +179 -11
  42. package/src/.context/validate.js +8 -14
  43. package/src/component.js +37 -18
  44. package/src/context.js +33 -14
  45. package/src/definition.js +56 -0
  46. package/src/entity.js +15 -0
  47. package/src/index.js +6 -9
  48. package/src/manifest.js +154 -0
  49. package/src/shortcuts.js +9 -14
  50. package/test/component/collapse.fixtures.js +30 -39
  51. package/test/component/collapse.test.js +90 -10
  52. package/test/component/dependencies.test.js +16 -14
  53. package/test/component/dereference.fixtures.js +15 -24
  54. package/test/component/dereference.test.js +18 -8
  55. package/test/component/dummies/extension/index.js +1 -5
  56. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
  57. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
  58. package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
  59. package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
  60. package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
  61. package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
  62. package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
  63. package/test/component/expand.fixtures.js +13 -12
  64. package/test/component/expand.test.js +17 -13
  65. package/test/component/migrations.test.js +62 -0
  66. package/test/component/normalize.fixtures.js +3 -7
  67. package/test/component/normalize.test.js +86 -28
  68. package/test/component/validate.fixtures.js +15 -13
  69. package/test/component/validate.test.js +181 -107
  70. package/test/context/complete.fixtures.js +2 -8
  71. package/test/context/complete.test.js +14 -8
  72. package/test/context/converge.test.js +57 -0
  73. package/test/context/dereference.fixtures.js +3 -9
  74. package/test/context/dereference.test.js +18 -7
  75. package/test/context/evict.fixtures.js +39 -0
  76. package/test/context/evict.test.js +72 -0
  77. package/test/context/expand.fixtures.js +1 -5
  78. package/test/context/expand.test.js +36 -8
  79. package/test/context/normalize.fixtures.js +2 -7
  80. package/test/context/normalize.test.js +33 -9
  81. package/test/context/validate.fixtures.js +1 -6
  82. package/test/context/validate.test.js +143 -34
  83. package/test/definition.test.js +35 -0
  84. package/test/dummies/bare/index.js +1 -0
  85. package/test/dummies/defined/definition.js +1 -0
  86. package/test/dummies/defined/index.js +1 -0
  87. package/test/dummies/defined/package.json +1 -0
  88. package/test/manifest.test.js +126 -0
  89. package/test/shortcuts.fixtures.js +4 -9
  90. package/test/shortcuts.test.js +17 -15
  91. package/types/component.d.ts +24 -5
  92. package/types/context/declaration.d.ts +33 -3
  93. package/types/context.d.ts +19 -13
  94. package/types/entity.d.ts +7 -0
  95. package/types/index.d.ts +12 -3
  96. package/src/.component/.expand/properties.js +0 -14
  97. package/src/.context/.dependencies/load.js +0 -36
  98. package/test/context/dependencies.load.test.js +0 -24
@@ -8,7 +8,7 @@ definitions:
8
8
  binding:
9
9
  type: string
10
10
  not:
11
- const: '@toa.io/bindings.loop' # loop is for system use only
11
+ const: "@toa.io/bindings.loop" # loop is for system use only
12
12
 
13
13
  type: object
14
14
  properties:
@@ -17,19 +17,19 @@ properties:
17
17
  nullable: true
18
18
  properties:
19
19
  prototype:
20
- $ref: '#/properties/prototype'
20
+ $ref: "#/properties/prototype"
21
21
  path:
22
22
  type: string
23
23
  operations:
24
24
  type: object
25
25
  propertyNames:
26
- $ref: '#/definitions/name'
26
+ $ref: "#/definitions/name"
27
27
  patternProperties:
28
- '.*':
28
+ ".*":
29
29
  type: object
30
30
  properties:
31
31
  bridge:
32
- $ref: '#/properties/bridge'
32
+ $ref: "#/properties/bridge"
33
33
 
34
34
  path:
35
35
  type: string
@@ -38,19 +38,22 @@ properties:
38
38
  type: object
39
39
  properties:
40
40
  id:
41
- $ref: 'definitions#/definitions/locator'
41
+ type: string
42
+ pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(\.([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))$
42
43
  label:
43
- $ref: 'definitions#/definitions/label'
44
+ type: string
45
+ pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(-([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))*$
44
46
 
45
47
  name:
46
- $ref: 'definitions#/definitions/token'
48
+ type: string
49
+ pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
47
50
 
48
51
  namespace:
49
- $ref: 'definitions#/definitions/token'
50
- default: 'default'
52
+ type: string
53
+ pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
51
54
  not:
52
55
  oneOf:
53
- - const: 'system'
56
+ - const: "system"
54
57
 
55
58
  version:
56
59
  type: string
@@ -60,40 +63,60 @@ properties:
60
63
  properties:
61
64
  image:
62
65
  type: string
66
+ run:
67
+ type: string
63
68
 
64
69
  entity:
65
70
  type: object
66
71
  properties:
67
72
  storage:
68
73
  type: string
69
- default: '@toa.io/storages.mongodb'
70
- schema:
71
- $ref: 'definitions#/definitions/schema'
74
+ properties:
72
75
  type: object
73
- properties:
74
- type:
75
- default: object
76
- const: object
76
+ # a system property is named like any other, so nothing is enumerated beside them
77
+ propertyNames:
78
+ $ref: "#/definitions/name"
79
+ additionalProperties:
80
+ $ref: https://json-schema.org/draft/2019-09/schema
81
+ required:
82
+ type: array
83
+ uniqueItems: true
84
+ items:
85
+ $ref: "#/definitions/name"
86
+ blank:
87
+ # what a record holds before anything is written to it; a system property is the
88
+ # runtime's to write, so none may be named here
89
+ type: object
90
+ propertyNames:
91
+ not:
92
+ enum: [id, VERSION, CREATED, UPDATED, DELETED, REGION]
93
+ migrations:
94
+ type: array
95
+ items:
96
+ type: object
77
97
  properties:
78
- type: object
79
- propertyNames:
80
- oneOf:
81
- - $ref: '#/definitions/name'
82
- - enum: [_version]
83
- dependent:
98
+ id:
99
+ type: string
100
+ # what a step means belongs to the storage, which is the only thing that reads it
101
+ steps:
102
+ type: array
103
+ # the prototype it is inherited from; absent on the component's own
104
+ prototype:
105
+ type: string
106
+ required: [id, steps]
107
+ additionalProperties: false
108
+ associated:
84
109
  type: boolean
85
- default: false
86
110
  custom:
87
111
  type: boolean
88
- default: false
89
- required: [schema]
112
+ required: [properties]
90
113
  additionalProperties: false
91
114
 
92
115
  bindings:
93
116
  type: array
94
117
  uniqueItems: true
95
118
  items:
96
- $ref: '#/definitions/binding'
119
+ $ref: "#/definitions/binding"
97
120
 
98
121
  bridge:
99
122
  type: string
@@ -101,27 +124,49 @@ properties:
101
124
  operations:
102
125
  type: object
103
126
  propertyNames:
104
- $ref: '#/definitions/name'
127
+ $ref: "#/definitions/name"
105
128
  patternProperties:
106
- '.*':
129
+ ".*":
107
130
  type: object
108
131
  properties:
109
132
  type:
110
- enum: [transition, observation, assignment, computation, effect]
133
+ enum:
134
+ [
135
+ transition,
136
+ observation,
137
+ assignment,
138
+ computation,
139
+ effect,
140
+ unmanaged,
141
+ ]
111
142
  scope:
112
- enum: [object, objects, changeset, none]
143
+ enum: [object, objects, changeset, stream, none]
113
144
  concurrency:
114
145
  enum: [none, retry]
146
+ once:
147
+ # the same call arriving twice changes state once; see documentation/inbox.md
148
+ type: boolean
149
+ stateful:
150
+ # served by every process under an address of its own, which a call names; see documentation/stateful.md
151
+ type: boolean
115
152
  forward:
116
- $ref: '#/definitions/name'
153
+ $ref: "#/definitions/name"
117
154
  bridge:
118
155
  type: string
119
156
  bindings:
120
- $ref: '#/properties/bindings'
157
+ $ref: "#/properties/bindings"
158
+ description:
159
+ # what the operation is, for whoever reads it: a generated type, an OPTIONS, a model
160
+ type: string
121
161
  input:
122
- $ref: 'definitions#/definitions/schema'
162
+ $ref: https://json-schema.org/draft/2019-09/schema
123
163
  output:
124
- $ref: 'definitions#/definitions/schema'
164
+ $ref: https://json-schema.org/draft/2019-09/schema
165
+ errors:
166
+ # what a reply may carry as a code: the error schema allows either
167
+ type: array
168
+ items:
169
+ type: [string, integer]
125
170
  query:
126
171
  type: boolean
127
172
  required: [type, scope, bindings]
@@ -133,8 +178,8 @@ properties:
133
178
  then:
134
179
  properties:
135
180
  scope:
136
- enum: [object]
137
- if: # transition query: false
181
+ enum: [object, objects]
182
+ if: # not query: false
138
183
  not:
139
184
  properties:
140
185
  query:
@@ -142,6 +187,17 @@ properties:
142
187
  required: [query]
143
188
  then:
144
189
  required: [concurrency]
190
+ - if: # transition and scope=objects
191
+ properties:
192
+ type:
193
+ const: transition
194
+ scope:
195
+ const: objects
196
+ then:
197
+ properties:
198
+ query:
199
+ not:
200
+ const: false
145
201
  - if: # not transition
146
202
  not:
147
203
  properties:
@@ -151,6 +207,30 @@ properties:
151
207
  properties:
152
208
  concurrency:
153
209
  const: ~ # never pass
210
+ - if: # neither a transition nor an assignment
211
+ not:
212
+ properties:
213
+ type:
214
+ enum: [transition, assignment]
215
+ then:
216
+ properties:
217
+ # An operation that writes nothing has nothing to record. An effect writes, but
218
+ # its write is get-or-create, so a second arrival of one writes nothing already —
219
+ # and it writes in `acquire`, before its algorithm has run, so its reply is not
220
+ # known where the record is made.
221
+ once:
222
+ const: ~ # never pass
223
+ - if: # commits more than one entity
224
+ properties:
225
+ type:
226
+ const: transition
227
+ scope:
228
+ const: objects
229
+ then:
230
+ properties:
231
+ # the reply is recorded, and over a set it is unbounded
232
+ once:
233
+ const: ~ # never pass
154
234
  - if: # observation
155
235
  properties:
156
236
  type:
@@ -158,7 +238,7 @@ properties:
158
238
  then:
159
239
  properties:
160
240
  scope:
161
- enum: [object, objects, none]
241
+ enum: [object, objects, stream, none]
162
242
  query:
163
243
  not:
164
244
  const: false
@@ -173,15 +253,13 @@ properties:
173
253
  - if: # computation
174
254
  properties:
175
255
  type:
176
- const: computation
256
+ enum: [computation, unmanaged]
177
257
  then:
178
258
  properties:
179
259
  scope:
180
260
  const: none
181
- default: none
182
261
  query:
183
262
  const: false
184
- default: false
185
263
  - if: # effect
186
264
  properties:
187
265
  type:
@@ -189,20 +267,16 @@ properties:
189
267
  then:
190
268
  properties:
191
269
  scope:
192
- const: none
193
- default: none
194
- query:
195
- const: false
196
- default: false
270
+ enum: [object, objects, stream, none]
197
271
  additionalProperties: false
198
272
  additionalProperties: false
199
273
 
200
274
  events:
201
275
  type: object
202
276
  propertyNames:
203
- $ref: 'definitions#/definitions/name'
277
+ $ref: "#/definitions/name"
204
278
  patternProperties:
205
- '.*':
279
+ ".*":
206
280
  type: object
207
281
  properties:
208
282
  bridge:
@@ -210,47 +284,53 @@ properties:
210
284
  path:
211
285
  type: string
212
286
  binding:
213
- $ref: '#/definitions/binding'
287
+ $ref: "#/definitions/binding"
214
288
  conditioned:
215
289
  type: boolean
216
- default: false
217
290
  subjective:
218
291
  type: boolean
219
- default: false
220
292
  required: [bridge, path]
221
293
  additionalProperties: false
222
294
 
223
295
  receivers:
224
296
  type: object
225
297
  patternProperties:
226
- '.*':
298
+ ".*":
227
299
  type: object
228
300
  properties:
229
301
  operation:
230
- $ref: 'definitions#/definitions/name'
302
+ $ref: "#/definitions/name"
231
303
  bridge:
232
304
  type: string
233
305
  binding:
234
306
  type: string
235
307
  source:
236
- $ref: 'definitions#/definitions/name'
308
+ $ref: "#/definitions/name"
237
309
  not:
238
310
  const: context
239
311
  path:
240
312
  type: string
241
313
  conditioned:
242
314
  type: boolean
243
- default: false
244
315
  adaptive:
245
316
  type: boolean
246
- default: false
317
+ arguments:
318
+ type: array
247
319
  required: [operation]
248
320
  additionalProperties: false
249
321
 
250
- extensions:
322
+ guards:
251
323
  type: object
324
+ patternProperties:
325
+ ".*":
326
+ type: object
327
+ properties:
328
+ bridge: { type: string }
329
+ path: { type: string }
330
+ required: [bridge, path]
331
+ additionalProperties: false
252
332
 
253
- properties:
333
+ extensions:
254
334
  type: object
255
335
 
256
336
  additionalProperties: false
@@ -1,25 +1,43 @@
1
- 'use strict'
1
+ import path from 'node:path'
2
2
 
3
- const path = require('node:path')
3
+ import { readFileSync } from 'node:fs'
4
+ import { yaml } from '@toa.io/generic'
5
+ import * as schemas from '@toa.io/schemas'
6
+ import { definition } from '../definition.js'
4
7
 
5
- const { load } = require('@toa.io/yaml')
6
- const { Schema } = require('@toa.io/schema')
8
+ const object = yaml.load(
9
+ readFileSync(path.resolve(import.meta.dirname, 'schema.yaml'), 'utf8')
10
+ )
11
+ const schema = schemas.schema(object)
7
12
 
8
- const object = load.sync(path.resolve(__dirname, 'schema.yaml'))
9
- const schema = new Schema(object)
10
-
11
- const validate = (manifest) => {
13
+ export const validate = async (manifest) => {
12
14
  const error = schema.fit(manifest)
13
15
 
14
- if (error) throw new Error(error.message)
16
+ if (error) throw error
15
17
 
16
- if (manifest.events !== undefined) events(manifest)
18
+ if (manifest.entity !== undefined) entity(manifest)
19
+ if (manifest.events !== undefined) await events(manifest)
17
20
  if (manifest.receivers !== undefined) receivers(manifest)
18
21
  }
19
22
 
20
- const events = (manifest) => {
23
+ /** What only the entity's own declaration can answer: whether a name it uses is one it declares. */
24
+ const entity = (manifest) => {
25
+ const { properties, required, blank } = manifest.entity
26
+
27
+ for (const name of required ?? [])
28
+ if (properties[name] === undefined)
29
+ throw new Error(`Entity requires property '${name}', which is not defined`)
30
+
31
+ for (const name of Object.keys(blank ?? {}))
32
+ if (properties[name] === undefined)
33
+ throw new Error(`Entity blank names property '${name}', which is not defined`)
34
+ }
35
+
36
+ const events = async (manifest) => {
21
37
  for (const [label, event] of Object.entries(manifest.events)) {
22
- if (require(event.binding).properties.async !== true) {
38
+ const { properties } = (await definition(event.binding)).module
39
+
40
+ if (properties.async !== true) {
23
41
  throw new Error(`Event '${label}' binding '${event.binding}' is not async`)
24
42
  }
25
43
  }
@@ -28,15 +46,17 @@ const events = (manifest) => {
28
46
  const receivers = (manifest) => {
29
47
  for (const [locator, receiver] of Object.entries(manifest.receivers)) {
30
48
  if (manifest.operations?.[receiver.operation] === undefined) {
31
- throw new Error(`Receiver '${locator}' refers to undefined operation '${receiver.operation}'`)
49
+ throw new Error(
50
+ `Receiver '${locator}' refers to undefined operation '${receiver.operation}'`
51
+ )
32
52
  }
33
53
 
34
54
  if (!TYPES.has(manifest.operations[receiver.operation].type)) {
35
- throw new Error(`Receiver '${locator}' must refer to an operation of one of the allowed types: ${Array.from(TYPES).join(', ')}`)
55
+ throw new Error(
56
+ `Receiver '${locator}' must refer to an operation of the allowed types: ${Array.from(TYPES).join(', ')}`
57
+ )
36
58
  }
37
59
  }
38
60
  }
39
61
 
40
- const TYPES = new Set(['transition', 'effect'])
41
-
42
- exports.validate = validate
62
+ const TYPES = new Set(['transition', 'assignment', 'effect', 'unmanaged'])
@@ -1,20 +1,23 @@
1
- 'use strict'
1
+ import { resolve } from 'node:path'
2
2
 
3
- const connectors = (context, extracted) => {
3
+ export const connectors = (context, extracted) => {
4
4
  const connectors = {}
5
5
 
6
- const components = (context.components === undefined
6
+ const components =
7
+ (context.components === undefined
7
8
  ? extracted
8
- : context.components.concat(extracted)
9
- ) ?? []
9
+ : context.components.concat(extracted)) ?? []
10
10
 
11
11
  for (const component of components) {
12
12
  if (component.entity !== undefined) {
13
- if (connectors[component.entity.storage] === undefined) {
14
- connectors[component.entity.storage] = []
13
+ let storage = component.entity.storage
14
+
15
+ if (storage[0] === '.') {
16
+ storage = resolve(component.path, storage)
15
17
  }
16
18
 
17
- connectors[component.entity.storage].push(component)
19
+ connectors[storage] ??= []
20
+ connectors[storage].push(component)
18
21
  }
19
22
 
20
23
  const bindings = new Set()
@@ -42,5 +45,3 @@ const connectors = (context, extracted) => {
42
45
 
43
46
  return connectors
44
47
  }
45
-
46
- exports.connectors = connectors
@@ -1,10 +1,9 @@
1
- 'use strict'
2
-
3
1
  /**
4
2
  * @param {toa.norm.Component} component
5
3
  * @param {Object} manifest
6
4
  * @returns {toa.norm.context.dependencies.Instance}
7
5
  */
8
- const describe = (component, manifest = undefined) => ({ locator: component.locator, manifest })
9
-
10
- exports.describe = describe
6
+ export const describe = (component, manifest = undefined) => ({
7
+ locator: component.locator,
8
+ manifest
9
+ })
@@ -1,12 +1,34 @@
1
- 'use strict'
1
+ import { Locator } from '@toa.io/core'
2
+ import { converge } from '../converge.js'
3
+ import { component as load } from '../../component.js'
4
+ import { definition } from '../../definition.js'
2
5
 
3
- const extensions = async (context) => {
6
+ export const extensions = async (context) => {
4
7
  const extensions = {}
5
8
  const components = context.components?.slice() ?? []
6
- const extracted = await extractExtensionComponents(components, extensions)
9
+
10
+ /** the components each extension brings, by reference
11
+ * @type {Map<string, toa.norm.Component[]>} */
12
+ const contributed = new Map()
13
+
14
+ // an extension a composition runs may be referenced by no component of this context,
15
+ // and its own components must be extracted all the same
16
+ const declared = await extractDeclaredServices(context, extensions, contributed)
17
+
18
+ const extracted = declared.concat(
19
+ await extractExtensionComponents(
20
+ components.concat(declared),
21
+ extensions,
22
+ contributed,
23
+ context.annotations
24
+ )
25
+ )
7
26
 
8
27
  components.push(...extracted)
9
28
 
29
+ // after the extraction, because what an extension ships converges like anything else
30
+ converge(context, components)
31
+
10
32
  for (const component of components) {
11
33
  if (component.extensions === undefined) continue
12
34
 
@@ -17,12 +39,33 @@ const extensions = async (context) => {
17
39
  }
18
40
  }
19
41
 
20
- return { extensions, components: extracted }
42
+ return { extensions, components: extracted, contributed }
21
43
  }
22
44
 
23
- async function extractExtensionComponents (components, extensions) {
24
- const { component: load } = require('../../component')
45
+ async function extractDeclaredServices(context, extensions, contributed) {
46
+ const extracted = []
47
+
48
+ for (const composition of context.compositions ?? [])
49
+ for (const reference of composition.services ?? []) {
50
+ if (reference in extensions) continue
25
51
 
52
+ try {
53
+ extracted.push(
54
+ ...(await extract(reference, extensions, contributed, context.annotations))
55
+ )
56
+ } catch (e) {
57
+ throw new Error(
58
+ `Composition '${composition.name}' lists '${reference}', ` +
59
+ `which cannot be resolved: ${e.message}`,
60
+ { cause: e }
61
+ )
62
+ }
63
+ }
64
+
65
+ return extracted
66
+ }
67
+
68
+ async function extractExtensionComponents(components, extensions, contributed, annotations) {
26
69
  const extracted = []
27
70
 
28
71
  for (const component of components) {
@@ -31,26 +74,56 @@ async function extractExtensionComponents (components, extensions) {
31
74
  for (const reference of Object.keys(component.extensions)) {
32
75
  if (reference in extensions) continue
33
76
 
34
- extensions[reference] = []
77
+ extracted.push(...(await extract(reference, extensions, contributed, annotations)))
78
+ }
79
+ }
80
+
81
+ if (extracted.length === 0) return extracted
35
82
 
36
- const mod = require(reference)
83
+ const deeper = await extractExtensionComponents(
84
+ extracted,
85
+ extensions,
86
+ contributed,
87
+ annotations
88
+ )
37
89
 
38
- if (mod.components === undefined) continue
90
+ return extracted.concat(deeper)
91
+ }
39
92
 
40
- for (const path of mod.components().paths) {
41
- const component = await load(path)
93
+ /**
94
+ * The components an extension contributes, if it contributes any.
95
+ *
96
+ * @param {string} reference
97
+ * @param {object} extensions
98
+ * @param {Map<string, toa.norm.Component[]>} contributed
99
+ * @param {object} [annotations]
100
+ * @returns {Promise<Array<toa.norm.Component>>}
101
+ */
102
+ async function extract(reference, extensions, contributed, annotations) {
103
+ extensions[reference] = []
42
104
 
43
- extracted.push(component)
44
- }
45
- }
46
- }
105
+ const { name, module: mod } = await definition(reference)
47
106
 
48
- if (extracted.length === 0)
49
- return extracted
107
+ if (mod.components === undefined) return []
50
108
 
51
- const deeper = await extractExtensionComponents(extracted, extensions)
109
+ // the annotation decides whether an extension contributes components at all
110
+ const annotation = annotations?.[name]
111
+ const { manifests, paths } = mod.components(annotation)
52
112
 
53
- return extracted.concat(deeper)
113
+ // a definition carries the manifests, read where the extension is not installed;
114
+ // an entry names the directories, where it is
115
+ const extracted = manifests === undefined ? [] : manifests.map(revive)
116
+
117
+ if (manifests === undefined) for (const path of paths) extracted.push(await load(path))
118
+
119
+ contributed.set(reference, extracted)
120
+
121
+ return extracted
54
122
  }
55
123
 
56
- exports.extensions = extensions
124
+ /** A manifest as a digest carries it, which is one norm read, without the locator. */
125
+ function revive(manifest) {
126
+ manifest.locator = new Locator(manifest.name, manifest.namespace)
127
+
128
+ return manifest
129
+ }
@@ -1,9 +1,3 @@
1
- 'use strict'
2
-
3
- const { connectors } = require('./connectors')
4
- const { extensions } = require('./extensions')
5
- const { resolve } = require('./resolve')
6
-
7
- exports.connectors = connectors
8
- exports.extensions = extensions
9
- exports.resolve = resolve
1
+ export { connectors } from './connectors.js'
2
+ export { extensions } from './extensions.js'
3
+ export { resolve } from './resolve.js'