@toa.io/norm 1.0.0-alpha.30 → 1.0.0-alpha.301
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 +136 -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 -19
- package/src/.component/.expand/operations.js +2 -9
- package/src/.component/.expand/receivers.js +4 -7
- package/src/.component/.expand/version.js +38 -28
- 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 +13 -6
- package/src/.component/.normalize/receivers.js +4 -5
- package/src/.component/collapse.js +60 -9
- package/src/.component/defaults.js +16 -10
- package/src/.component/dependencies.js +14 -10
- package/src/.component/dereference.js +5 -49
- package/src/.component/expand.js +3 -9
- package/src/.component/extensions.js +9 -10
- package/src/.component/index.js +10 -21
- package/src/.component/merge.js +25 -15
- package/src/.component/migrations.js +57 -0
- package/src/.component/normalize.js +10 -6
- package/src/.component/schema.yaml +137 -73
- package/src/.component/validate.js +37 -17
- package/src/.context/.dependencies/connectors.js +5 -9
- 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 +178 -11
- package/src/.context/validate.js +8 -14
- package/src/component.js +36 -17
- package/src/context.js +44 -15
- 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 +10 -98
- package/test/component/dereference.test.js +10 -14
- 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 -13
- 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 +94 -28
- package/test/component/validate.fixtures.js +15 -13
- package/test/component/validate.test.js +180 -109
- 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 -35
- 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 +23 -4
- package/types/context/declaration.d.ts +33 -3
- package/types/context.d.ts +26 -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,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
|
-
|
|
70
|
-
schema:
|
|
71
|
-
$ref: 'definitions#/definitions/schema'
|
|
74
|
+
properties:
|
|
72
75
|
type: object
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
127
|
+
$ref: "#/definitions/name"
|
|
121
128
|
patternProperties:
|
|
122
|
-
|
|
129
|
+
".*":
|
|
123
130
|
type: object
|
|
124
131
|
properties:
|
|
125
132
|
type:
|
|
126
|
-
enum:
|
|
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:
|
|
153
|
+
$ref: "#/definitions/name"
|
|
133
154
|
bridge:
|
|
134
155
|
type: string
|
|
135
156
|
bindings:
|
|
136
|
-
$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
|
|
137
161
|
input:
|
|
138
|
-
$ref:
|
|
162
|
+
$ref: https://json-schema.org/draft/2019-09/schema
|
|
139
163
|
output:
|
|
140
|
-
$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]
|
|
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: #
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
302
|
+
$ref: "#/definitions/name"
|
|
247
303
|
bridge:
|
|
248
304
|
type: string
|
|
249
305
|
binding:
|
|
250
306
|
type: string
|
|
251
307
|
source:
|
|
252
|
-
$ref:
|
|
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
|
-
|
|
317
|
+
arguments:
|
|
318
|
+
type: array
|
|
263
319
|
required: [operation]
|
|
264
320
|
additionalProperties: false
|
|
265
321
|
|
|
266
|
-
|
|
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
|
-
|
|
333
|
+
extensions:
|
|
270
334
|
type: object
|
|
271
335
|
|
|
272
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,14 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const connectors = (context, extracted) => {
|
|
3
|
+
export const connectors = (context, extracted) => {
|
|
6
4
|
const connectors = {}
|
|
7
5
|
|
|
8
|
-
const components =
|
|
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) => ({
|
|
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'
|