@toa.io/norm 1.0.0-alpha.30 → 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 -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 +38 -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 +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 -10
  18. package/src/.component/dependencies.js +14 -10
  19. package/src/.component/dereference.js +20 -18
  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 -13
  24. package/src/.component/migrations.js +57 -0
  25. package/src/.component/normalize.js +10 -6
  26. package/src/.component/schema.yaml +137 -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 +178 -11
  42. package/src/.context/validate.js +8 -14
  43. package/src/component.js +36 -17
  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 -13
  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 +180 -109
  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 -35
  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 +23 -4
  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,56 +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
77
- properties:
78
- type: object
79
- propertyNames:
80
- oneOf:
81
- - $ref: '#/definitions/name'
82
- - enum: [_version, _created, _updated, _deleted]
83
- unique:
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
84
89
  type: object
85
- patternProperties:
86
- '.*':
87
- type: array
88
- items:
90
+ propertyNames:
91
+ not:
92
+ enum: [id, VERSION, CREATED, UPDATED, DELETED, REGION]
93
+ migrations:
94
+ type: array
95
+ items:
96
+ type: object
97
+ properties:
98
+ id:
89
99
  type: string
90
- index:
91
- type: object
92
- patternProperties:
93
- '.*':
94
- type: object
95
- patternProperties:
96
- '.*':
97
- type: string
98
- enum: [asc, desc, hash]
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
99
108
  associated:
100
109
  type: boolean
101
- default: false
102
110
  custom:
103
111
  type: boolean
104
- default: false
105
- required: [schema]
112
+ required: [properties]
106
113
  additionalProperties: false
107
114
 
108
115
  bindings:
109
116
  type: array
110
117
  uniqueItems: true
111
118
  items:
112
- $ref: '#/definitions/binding'
119
+ $ref: "#/definitions/binding"
113
120
 
114
121
  bridge:
115
122
  type: string
@@ -117,27 +124,49 @@ properties:
117
124
  operations:
118
125
  type: object
119
126
  propertyNames:
120
- $ref: '#/definitions/name'
127
+ $ref: "#/definitions/name"
121
128
  patternProperties:
122
- '.*':
129
+ ".*":
123
130
  type: object
124
131
  properties:
125
132
  type:
126
- enum: [transition, observation, assignment, computation, effect]
133
+ enum:
134
+ [
135
+ transition,
136
+ observation,
137
+ assignment,
138
+ computation,
139
+ effect,
140
+ unmanaged,
141
+ ]
127
142
  scope:
128
- enum: [object, objects, changeset, none]
143
+ enum: [object, objects, changeset, stream, none]
129
144
  concurrency:
130
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
131
152
  forward:
132
- $ref: '#/definitions/name'
153
+ $ref: "#/definitions/name"
133
154
  bridge:
134
155
  type: string
135
156
  bindings:
136
- $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
137
161
  input:
138
- $ref: 'definitions#/definitions/schema'
162
+ $ref: https://json-schema.org/draft/2019-09/schema
139
163
  output:
140
- $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]
141
170
  query:
142
171
  type: boolean
143
172
  required: [type, scope, bindings]
@@ -149,8 +178,8 @@ properties:
149
178
  then:
150
179
  properties:
151
180
  scope:
152
- enum: [object]
153
- if: # transition query: false
181
+ enum: [object, objects]
182
+ if: # not query: false
154
183
  not:
155
184
  properties:
156
185
  query:
@@ -158,6 +187,17 @@ properties:
158
187
  required: [query]
159
188
  then:
160
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
161
201
  - if: # not transition
162
202
  not:
163
203
  properties:
@@ -167,6 +207,30 @@ properties:
167
207
  properties:
168
208
  concurrency:
169
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
170
234
  - if: # observation
171
235
  properties:
172
236
  type:
@@ -174,7 +238,7 @@ properties:
174
238
  then:
175
239
  properties:
176
240
  scope:
177
- enum: [object, objects, none]
241
+ enum: [object, objects, stream, none]
178
242
  query:
179
243
  not:
180
244
  const: false
@@ -189,15 +253,13 @@ properties:
189
253
  - if: # computation
190
254
  properties:
191
255
  type:
192
- const: computation
256
+ enum: [computation, unmanaged]
193
257
  then:
194
258
  properties:
195
259
  scope:
196
260
  const: none
197
- default: none
198
261
  query:
199
262
  const: false
200
- default: false
201
263
  - if: # effect
202
264
  properties:
203
265
  type:
@@ -205,20 +267,16 @@ properties:
205
267
  then:
206
268
  properties:
207
269
  scope:
208
- const: none
209
- default: none
210
- query:
211
- const: false
212
- default: false
270
+ enum: [object, objects, stream, none]
213
271
  additionalProperties: false
214
272
  additionalProperties: false
215
273
 
216
274
  events:
217
275
  type: object
218
276
  propertyNames:
219
- $ref: 'definitions#/definitions/name'
277
+ $ref: "#/definitions/name"
220
278
  patternProperties:
221
- '.*':
279
+ ".*":
222
280
  type: object
223
281
  properties:
224
282
  bridge:
@@ -226,47 +284,53 @@ properties:
226
284
  path:
227
285
  type: string
228
286
  binding:
229
- $ref: '#/definitions/binding'
287
+ $ref: "#/definitions/binding"
230
288
  conditioned:
231
289
  type: boolean
232
- default: false
233
290
  subjective:
234
291
  type: boolean
235
- default: false
236
292
  required: [bridge, path]
237
293
  additionalProperties: false
238
294
 
239
295
  receivers:
240
296
  type: object
241
297
  patternProperties:
242
- '.*':
298
+ ".*":
243
299
  type: object
244
300
  properties:
245
301
  operation:
246
- $ref: 'definitions#/definitions/name'
302
+ $ref: "#/definitions/name"
247
303
  bridge:
248
304
  type: string
249
305
  binding:
250
306
  type: string
251
307
  source:
252
- $ref: 'definitions#/definitions/name'
308
+ $ref: "#/definitions/name"
253
309
  not:
254
310
  const: context
255
311
  path:
256
312
  type: string
257
313
  conditioned:
258
314
  type: boolean
259
- default: false
260
315
  adaptive:
261
316
  type: boolean
262
- default: false
317
+ arguments:
318
+ type: array
263
319
  required: [operation]
264
320
  additionalProperties: false
265
321
 
266
- extensions:
322
+ guards:
267
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
268
332
 
269
- properties:
333
+ extensions:
270
334
  type: object
271
335
 
272
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,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'