@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.
- package/CHANGELOG.md +124 -0
- package/package.json +9 -7
- package/src/.component/.expand/bridge.js +2 -6
- package/src/.component/.expand/entity.js +2 -9
- package/src/.component/.expand/events.js +2 -6
- package/src/.component/.expand/extensions.js +5 -8
- package/src/.component/.expand/index.js +7 -17
- package/src/.component/.expand/operations.js +2 -9
- package/src/.component/.expand/receivers.js +4 -7
- package/src/.component/.expand/version.js +55 -0
- package/src/.component/.normalize/entity.js +7 -0
- package/src/.component/.normalize/events.js +16 -4
- package/src/.component/.normalize/index.js +4 -9
- package/src/.component/.normalize/operations.js +10 -6
- package/src/.component/.normalize/receivers.js +4 -5
- package/src/.component/collapse.js +60 -9
- package/src/.component/defaults.js +16 -25
- package/src/.component/dependencies.js +14 -10
- package/src/.component/dereference.js +20 -18
- package/src/.component/expand.js +6 -9
- package/src/.component/extensions.js +9 -10
- package/src/.component/index.js +10 -21
- package/src/.component/merge.js +25 -13
- package/src/.component/migrations.js +57 -0
- package/src/.component/normalize.js +10 -6
- package/src/.component/schema.yaml +139 -59
- package/src/.component/validate.js +37 -17
- package/src/.context/.dependencies/connectors.js +11 -10
- package/src/.context/.dependencies/describe.js +4 -5
- package/src/.context/.dependencies/extensions.js +93 -20
- package/src/.context/.dependencies/index.js +3 -9
- package/src/.context/.dependencies/resolve.js +44 -7
- package/src/.context/complete.js +1 -5
- package/src/.context/converge.js +31 -0
- package/src/.context/dependencies.js +43 -6
- package/src/.context/dereference.js +11 -6
- package/src/.context/evict.js +38 -0
- package/src/.context/expand.js +11 -6
- package/src/.context/index.js +8 -15
- package/src/.context/normalize.js +9 -7
- package/src/.context/schema.yaml +179 -11
- package/src/.context/validate.js +8 -14
- package/src/component.js +37 -18
- package/src/context.js +33 -14
- package/src/definition.js +56 -0
- package/src/entity.js +15 -0
- package/src/index.js +6 -9
- package/src/manifest.js +154 -0
- package/src/shortcuts.js +9 -14
- package/test/component/collapse.fixtures.js +30 -39
- package/test/component/collapse.test.js +90 -10
- package/test/component/dependencies.test.js +16 -14
- package/test/component/dereference.fixtures.js +15 -24
- package/test/component/dereference.test.js +18 -8
- package/test/component/dummies/extension/index.js +1 -5
- package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
- package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
- package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
- package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
- package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
- package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
- package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
- package/test/component/expand.fixtures.js +13 -12
- package/test/component/expand.test.js +17 -13
- package/test/component/migrations.test.js +62 -0
- package/test/component/normalize.fixtures.js +3 -7
- package/test/component/normalize.test.js +86 -28
- package/test/component/validate.fixtures.js +15 -13
- package/test/component/validate.test.js +181 -107
- package/test/context/complete.fixtures.js +2 -8
- package/test/context/complete.test.js +14 -8
- package/test/context/converge.test.js +57 -0
- package/test/context/dereference.fixtures.js +3 -9
- package/test/context/dereference.test.js +18 -7
- package/test/context/evict.fixtures.js +39 -0
- package/test/context/evict.test.js +72 -0
- package/test/context/expand.fixtures.js +1 -5
- package/test/context/expand.test.js +36 -8
- package/test/context/normalize.fixtures.js +2 -7
- package/test/context/normalize.test.js +33 -9
- package/test/context/validate.fixtures.js +1 -6
- package/test/context/validate.test.js +143 -34
- package/test/definition.test.js +35 -0
- package/test/dummies/bare/index.js +1 -0
- package/test/dummies/defined/definition.js +1 -0
- package/test/dummies/defined/index.js +1 -0
- package/test/dummies/defined/package.json +1 -0
- package/test/manifest.test.js +126 -0
- package/test/shortcuts.fixtures.js +4 -9
- package/test/shortcuts.test.js +17 -15
- package/types/component.d.ts +24 -5
- package/types/context/declaration.d.ts +33 -3
- package/types/context.d.ts +19 -13
- package/types/entity.d.ts +7 -0
- package/types/index.d.ts +12 -3
- package/src/.component/.expand/properties.js +0 -14
- package/src/.context/.dependencies/load.js +0 -36
- 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:
|
|
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:
|
|
20
|
+
$ref: "#/properties/prototype"
|
|
21
21
|
path:
|
|
22
22
|
type: string
|
|
23
23
|
operations:
|
|
24
24
|
type: object
|
|
25
25
|
propertyNames:
|
|
26
|
-
$ref:
|
|
26
|
+
$ref: "#/definitions/name"
|
|
27
27
|
patternProperties:
|
|
28
|
-
|
|
28
|
+
".*":
|
|
29
29
|
type: object
|
|
30
30
|
properties:
|
|
31
31
|
bridge:
|
|
32
|
-
$ref:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
+
type: string
|
|
49
|
+
pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
|
|
47
50
|
|
|
48
51
|
namespace:
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
type: string
|
|
53
|
+
pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
|
|
51
54
|
not:
|
|
52
55
|
oneOf:
|
|
53
|
-
- const:
|
|
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
|
-
|
|
70
|
-
schema:
|
|
71
|
-
$ref: 'definitions#/definitions/schema'
|
|
74
|
+
properties:
|
|
72
75
|
type: object
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
127
|
+
$ref: "#/definitions/name"
|
|
105
128
|
patternProperties:
|
|
106
|
-
|
|
129
|
+
".*":
|
|
107
130
|
type: object
|
|
108
131
|
properties:
|
|
109
132
|
type:
|
|
110
|
-
enum:
|
|
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:
|
|
153
|
+
$ref: "#/definitions/name"
|
|
117
154
|
bridge:
|
|
118
155
|
type: string
|
|
119
156
|
bindings:
|
|
120
|
-
$ref:
|
|
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:
|
|
162
|
+
$ref: https://json-schema.org/draft/2019-09/schema
|
|
123
163
|
output:
|
|
124
|
-
$ref:
|
|
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: #
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
302
|
+
$ref: "#/definitions/name"
|
|
231
303
|
bridge:
|
|
232
304
|
type: string
|
|
233
305
|
binding:
|
|
234
306
|
type: string
|
|
235
307
|
source:
|
|
236
|
-
$ref:
|
|
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
|
-
|
|
317
|
+
arguments:
|
|
318
|
+
type: array
|
|
247
319
|
required: [operation]
|
|
248
320
|
additionalProperties: false
|
|
249
321
|
|
|
250
|
-
|
|
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
|
-
|
|
333
|
+
extensions:
|
|
254
334
|
type: object
|
|
255
335
|
|
|
256
336
|
additionalProperties: false
|
|
@@ -1,25 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
import path from 'node:path'
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
6
|
-
|
|
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
|
|
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
|
|
16
|
+
if (error) throw error
|
|
15
17
|
|
|
16
|
-
if (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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
14
|
-
|
|
13
|
+
let storage = component.entity.storage
|
|
14
|
+
|
|
15
|
+
if (storage[0] === '.') {
|
|
16
|
+
storage = resolve(component.path, storage)
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
connectors[
|
|
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) => ({
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
export const describe = (component, manifest = undefined) => ({
|
|
7
|
+
locator: component.locator,
|
|
8
|
+
manifest
|
|
9
|
+
})
|
|
@@ -1,12 +1,34 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
|
24
|
-
const
|
|
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
|
-
|
|
77
|
+
extracted.push(...(await extract(reference, extensions, contributed, annotations)))
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (extracted.length === 0) return extracted
|
|
35
82
|
|
|
36
|
-
|
|
83
|
+
const deeper = await extractExtensionComponents(
|
|
84
|
+
extracted,
|
|
85
|
+
extensions,
|
|
86
|
+
contributed,
|
|
87
|
+
annotations
|
|
88
|
+
)
|
|
37
89
|
|
|
38
|
-
|
|
90
|
+
return extracted.concat(deeper)
|
|
91
|
+
}
|
|
39
92
|
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
105
|
+
const { name, module: mod } = await definition(reference)
|
|
47
106
|
|
|
48
|
-
if (
|
|
49
|
-
return extracted
|
|
107
|
+
if (mod.components === undefined) return []
|
|
50
108
|
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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'
|