@toa.io/norm 1.0.0-alpha.31 → 1.0.0-alpha.310

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 (99) hide show
  1. package/CHANGELOG.md +208 -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 +6 -8
  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 +67 -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 +147 -73
  27. package/src/.component/validate.js +37 -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 +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 +181 -11
  42. package/src/.context/validate.js +8 -14
  43. package/src/component.js +33 -14
  44. package/src/context.js +44 -15
  45. package/src/definition.js +56 -0
  46. package/src/entity.js +15 -0
  47. package/src/index.js +7 -9
  48. package/src/manifest.js +154 -0
  49. package/src/map.js +24 -0
  50. package/src/shortcuts.js +9 -14
  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 +15 -13
  70. package/test/component/validate.test.js +180 -109
  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 +72 -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 +158 -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 +126 -0
  90. package/test/shortcuts.fixtures.js +4 -9
  91. package/test/shortcuts.test.js +17 -15
  92. package/types/component.d.ts +27 -4
  93. package/types/context/declaration.d.ts +34 -3
  94. package/types/context.d.ts +27 -13
  95. package/types/entity.d.ts +7 -0
  96. package/types/index.d.ts +18 -3
  97. package/src/.component/.expand/properties.js +0 -14
  98. package/src/.context/.dependencies/load.js +0 -36
  99. 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,40 +17,55 @@ 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
36
36
 
37
+ files:
38
+ # what this component's version is made of, where a build of it copies only some of it
39
+ type: array
40
+ items:
41
+ type: string
42
+
43
+ ignore:
44
+ # what its version is not made of, added to what the bridge states; `!` puts one of those back
45
+ type: array
46
+ items:
47
+ type: string
48
+
37
49
  locator:
38
50
  type: object
39
51
  properties:
40
52
  id:
41
- $ref: 'definitions#/definitions/locator'
53
+ type: string
54
+ pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(\.([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))$
42
55
  label:
43
- $ref: 'definitions#/definitions/label'
56
+ type: string
57
+ pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(-([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))*$
44
58
 
45
59
  name:
46
- $ref: 'definitions#/definitions/token'
60
+ type: string
61
+ pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
47
62
 
48
63
  namespace:
49
- $ref: 'definitions#/definitions/token'
50
- default: 'default'
64
+ type: string
65
+ pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
51
66
  not:
52
67
  oneOf:
53
- - const: 'system'
68
+ - const: "system"
54
69
 
55
70
  version:
56
71
  type: string
@@ -60,56 +75,60 @@ properties:
60
75
  properties:
61
76
  image:
62
77
  type: string
78
+ run:
79
+ type: string
63
80
 
64
81
  entity:
65
82
  type: object
66
83
  properties:
67
84
  storage:
68
85
  type: string
69
- default: '@toa.io/storages.mongodb'
70
- schema:
71
- $ref: 'definitions#/definitions/schema'
86
+ properties:
72
87
  type: object
73
- properties:
74
- type:
75
- default: object
76
- const: object
77
- properties:
78
- type: object
79
- propertyNames:
80
- oneOf:
81
- - $ref: '#/definitions/name'
82
- - enum: [_version, _created, _updated, _deleted]
83
- unique:
88
+ # a system property is named like any other, so nothing is enumerated beside them
89
+ propertyNames:
90
+ $ref: "#/definitions/name"
91
+ additionalProperties:
92
+ $ref: https://json-schema.org/draft/2019-09/schema
93
+ required:
94
+ type: array
95
+ uniqueItems: true
96
+ items:
97
+ $ref: "#/definitions/name"
98
+ blank:
99
+ # what a record holds before anything is written to it; a system property is the
100
+ # runtime's to write, so none may be named here
84
101
  type: object
85
- patternProperties:
86
- '.*':
87
- type: array
88
- items:
102
+ propertyNames:
103
+ not:
104
+ enum: [id, VERSION, CREATED, UPDATED, DELETED, REGION]
105
+ migrations:
106
+ type: array
107
+ items:
108
+ type: object
109
+ properties:
110
+ id:
89
111
  type: string
90
- index:
91
- type: object
92
- patternProperties:
93
- '.*':
94
- type: object
95
- patternProperties:
96
- '.*':
97
- type: string
98
- enum: [asc, desc, hash]
112
+ # what a step means belongs to the storage, which is the only thing that reads it
113
+ steps:
114
+ type: array
115
+ # the prototype it is inherited from; absent on the component's own
116
+ prototype:
117
+ type: string
118
+ required: [id, steps]
119
+ additionalProperties: false
99
120
  associated:
100
121
  type: boolean
101
- default: false
102
122
  custom:
103
123
  type: boolean
104
- default: false
105
- required: [schema]
124
+ required: [properties]
106
125
  additionalProperties: false
107
126
 
108
127
  bindings:
109
128
  type: array
110
129
  uniqueItems: true
111
130
  items:
112
- $ref: '#/definitions/binding'
131
+ $ref: "#/definitions/binding"
113
132
 
114
133
  bridge:
115
134
  type: string
@@ -117,27 +136,49 @@ properties:
117
136
  operations:
118
137
  type: object
119
138
  propertyNames:
120
- $ref: '#/definitions/name'
139
+ $ref: "#/definitions/name"
121
140
  patternProperties:
122
- '.*':
141
+ ".*":
123
142
  type: object
124
143
  properties:
125
144
  type:
126
- enum: [transition, observation, assignment, computation, effect]
145
+ enum:
146
+ [
147
+ transition,
148
+ observation,
149
+ assignment,
150
+ computation,
151
+ effect,
152
+ unmanaged,
153
+ ]
127
154
  scope:
128
- enum: [object, objects, changeset, none]
155
+ enum: [object, objects, changeset, stream, none]
129
156
  concurrency:
130
157
  enum: [none, retry]
158
+ once:
159
+ # the same call arriving twice changes state once; see documentation/inbox.md
160
+ type: boolean
161
+ stateful:
162
+ # served by every process under an address of its own, which a call names; see documentation/stateful.md
163
+ type: boolean
131
164
  forward:
132
- $ref: '#/definitions/name'
165
+ $ref: "#/definitions/name"
133
166
  bridge:
134
167
  type: string
135
168
  bindings:
136
- $ref: '#/properties/bindings'
169
+ $ref: "#/properties/bindings"
170
+ description:
171
+ # what the operation is, for whoever reads it: a generated type, an OPTIONS, a model
172
+ type: string
137
173
  input:
138
- $ref: 'definitions#/definitions/schema'
174
+ $ref: https://json-schema.org/draft/2019-09/schema
139
175
  output:
140
- $ref: 'definitions#/definitions/schema'
176
+ $ref: https://json-schema.org/draft/2019-09/schema
177
+ errors:
178
+ # what a reply may carry as a code: the error schema allows either
179
+ type: array
180
+ items:
181
+ type: [string, integer]
141
182
  query:
142
183
  type: boolean
143
184
  required: [type, scope, bindings]
@@ -149,8 +190,8 @@ properties:
149
190
  then:
150
191
  properties:
151
192
  scope:
152
- enum: [object]
153
- if: # transition query: false
193
+ enum: [object, objects]
194
+ if: # not query: false
154
195
  not:
155
196
  properties:
156
197
  query:
@@ -158,6 +199,17 @@ properties:
158
199
  required: [query]
159
200
  then:
160
201
  required: [concurrency]
202
+ - if: # transition and scope=objects
203
+ properties:
204
+ type:
205
+ const: transition
206
+ scope:
207
+ const: objects
208
+ then:
209
+ properties:
210
+ query:
211
+ not:
212
+ const: false
161
213
  - if: # not transition
162
214
  not:
163
215
  properties:
@@ -167,6 +219,30 @@ properties:
167
219
  properties:
168
220
  concurrency:
169
221
  const: ~ # never pass
222
+ - if: # neither a transition nor an assignment
223
+ not:
224
+ properties:
225
+ type:
226
+ enum: [transition, assignment]
227
+ then:
228
+ properties:
229
+ # An operation that writes nothing has nothing to record. An effect writes, but
230
+ # its write is get-or-create, so a second arrival of one writes nothing already —
231
+ # and it writes in `acquire`, before its algorithm has run, so its reply is not
232
+ # known where the record is made.
233
+ once:
234
+ const: ~ # never pass
235
+ - if: # commits more than one entity
236
+ properties:
237
+ type:
238
+ const: transition
239
+ scope:
240
+ const: objects
241
+ then:
242
+ properties:
243
+ # the reply is recorded, and over a set it is unbounded
244
+ once:
245
+ const: ~ # never pass
170
246
  - if: # observation
171
247
  properties:
172
248
  type:
@@ -174,7 +250,7 @@ properties:
174
250
  then:
175
251
  properties:
176
252
  scope:
177
- enum: [object, objects, none]
253
+ enum: [object, objects, stream, none]
178
254
  query:
179
255
  not:
180
256
  const: false
@@ -189,15 +265,13 @@ properties:
189
265
  - if: # computation
190
266
  properties:
191
267
  type:
192
- const: computation
268
+ enum: [computation, unmanaged]
193
269
  then:
194
270
  properties:
195
271
  scope:
196
272
  const: none
197
- default: none
198
273
  query:
199
274
  const: false
200
- default: false
201
275
  - if: # effect
202
276
  properties:
203
277
  type:
@@ -205,20 +279,16 @@ properties:
205
279
  then:
206
280
  properties:
207
281
  scope:
208
- const: none
209
- default: none
210
- query:
211
- const: false
212
- default: false
282
+ enum: [object, objects, stream, none]
213
283
  additionalProperties: false
214
284
  additionalProperties: false
215
285
 
216
286
  events:
217
287
  type: object
218
288
  propertyNames:
219
- $ref: 'definitions#/definitions/name'
289
+ $ref: "#/definitions/name"
220
290
  patternProperties:
221
- '.*':
291
+ ".*":
222
292
  type: object
223
293
  properties:
224
294
  bridge:
@@ -226,49 +296,53 @@ properties:
226
296
  path:
227
297
  type: string
228
298
  binding:
229
- $ref: '#/definitions/binding'
299
+ $ref: "#/definitions/binding"
230
300
  conditioned:
231
301
  type: boolean
232
- default: false
233
302
  subjective:
234
303
  type: boolean
235
- default: false
236
304
  required: [bridge, path]
237
305
  additionalProperties: false
238
306
 
239
307
  receivers:
240
308
  type: object
241
309
  patternProperties:
242
- '.*':
310
+ ".*":
243
311
  type: object
244
312
  properties:
245
313
  operation:
246
- $ref: 'definitions#/definitions/name'
314
+ $ref: "#/definitions/name"
247
315
  bridge:
248
316
  type: string
249
317
  binding:
250
318
  type: string
251
319
  source:
252
- $ref: 'definitions#/definitions/name'
320
+ $ref: "#/definitions/name"
253
321
  not:
254
322
  const: context
255
323
  path:
256
324
  type: string
257
325
  conditioned:
258
326
  type: boolean
259
- default: false
260
327
  adaptive:
261
328
  type: boolean
262
- default: false
263
329
  arguments:
264
330
  type: array
265
331
  required: [operation]
266
332
  additionalProperties: false
267
333
 
268
- extensions:
334
+ guards:
269
335
  type: object
336
+ patternProperties:
337
+ ".*":
338
+ type: object
339
+ properties:
340
+ bridge: { type: string }
341
+ path: { type: string }
342
+ required: [bridge, path]
343
+ additionalProperties: false
270
344
 
271
- properties:
345
+ extensions:
272
346
  type: object
273
347
 
274
348
  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,14 +1,12 @@
1
- 'use strict'
1
+ import { resolve } from 'node:path'
2
2
 
3
- const { resolve } = require('node:path')
4
-
5
- const connectors = (context, extracted) => {
3
+ export const connectors = (context, extracted) => {
6
4
  const connectors = {}
7
5
 
8
- const components = (context.components === undefined
6
+ const components =
7
+ (context.components === undefined
9
8
  ? extracted
10
- : context.components.concat(extracted)
11
- ) ?? []
9
+ : context.components.concat(extracted)) ?? []
12
10
 
13
11
  for (const component of components) {
14
12
  if (component.entity !== undefined) {
@@ -47,5 +45,3 @@ const connectors = (context, extracted) => {
47
45
 
48
46
  return connectors
49
47
  }
50
-
51
- 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'