@toa.io/norm 1.0.0-alpha.33 → 1.0.0-alpha.330

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 (100) hide show
  1. package/CHANGELOG.md +381 -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 +8 -9
  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 +72 -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 +165 -70
  27. package/src/.component/validate.js +67 -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 +98 -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 +44 -6
  36. package/src/.context/dereference.js +11 -6
  37. package/src/.context/evict.js +40 -0
  38. package/src/.context/expand.js +37 -5
  39. package/src/.context/index.js +8 -15
  40. package/src/.context/normalize.js +9 -7
  41. package/src/.context/schema.yaml +183 -11
  42. package/src/.context/validate.js +11 -14
  43. package/src/component.js +33 -14
  44. package/src/context.js +47 -15
  45. package/src/definition.js +63 -0
  46. package/src/entity.js +15 -0
  47. package/src/index.js +7 -9
  48. package/src/manifest.js +159 -0
  49. package/src/map.js +26 -0
  50. package/src/shortcuts.js +12 -15
  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 +17 -15
  70. package/test/component/validate.test.js +311 -108
  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 +73 -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 +173 -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 +137 -0
  90. package/test/shortcuts.fixtures.js +4 -9
  91. package/test/shortcuts.test.js +17 -15
  92. package/test/system.test.js +31 -0
  93. package/types/component.d.ts +32 -5
  94. package/types/context/declaration.d.ts +34 -3
  95. package/types/context.d.ts +27 -13
  96. package/types/entity.d.ts +7 -0
  97. package/types/index.d.ts +18 -3
  98. package/src/.component/.expand/properties.js +0 -14
  99. package/src/.context/.dependencies/load.js +0 -36
  100. 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, TRAILERS, DISCARD]
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,60 @@ 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, stream, none]
155
+ enum: [entry, entries, 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
+ # a number is how many seconds a call is remembered, which a deployment's
161
+ # `inbox.retention` is the default for; ten minutes at least, because the broker
162
+ # alone repeats a call for about that long. One type list rather than `oneOf`: the
163
+ # manifest is validated with coercion, and a branch would coerce `true` into `1`
164
+ type: [boolean, integer]
165
+ minimum: 600
166
+ stateful:
167
+ # served by every process under an address of its own, which a call names; see documentation/stateful.md
168
+ type: boolean
169
+ unchained:
170
+ # every call begins a chain of its own, so a circle through it is refused by neither rule; see documentation/cycles.md
171
+ type: boolean
172
+ stream:
173
+ # the input property that carries a stream, read as its caller writes it; see documentation/streams.md
174
+ $ref: "#/definitions/name"
131
175
  forward:
132
- $ref: '#/definitions/name'
176
+ $ref: "#/definitions/name"
133
177
  bridge:
134
178
  type: string
135
179
  bindings:
136
- $ref: '#/properties/bindings'
180
+ $ref: "#/properties/bindings"
181
+ description:
182
+ # what the operation is, for whoever reads it: a generated type, an OPTIONS, a model
183
+ type: string
137
184
  input:
138
- $ref: 'definitions#/definitions/schema'
185
+ $ref: https://json-schema.org/draft/2019-09/schema
139
186
  output:
140
- $ref: 'definitions#/definitions/schema'
187
+ $ref: https://json-schema.org/draft/2019-09/schema
188
+ errors:
189
+ # what a reply may carry as a code: the message of the error returned
190
+ type: array
191
+ items:
192
+ type: string
141
193
  query:
142
194
  type: boolean
143
195
  required: [type, scope, bindings]
@@ -149,8 +201,8 @@ properties:
149
201
  then:
150
202
  properties:
151
203
  scope:
152
- enum: [object]
153
- if: # transition query: false
204
+ enum: [entry, entries]
205
+ if: # not query: false
154
206
  not:
155
207
  properties:
156
208
  query:
@@ -158,6 +210,17 @@ properties:
158
210
  required: [query]
159
211
  then:
160
212
  required: [concurrency]
213
+ - if: # transition and scope=entries
214
+ properties:
215
+ type:
216
+ const: transition
217
+ scope:
218
+ const: entries
219
+ then:
220
+ properties:
221
+ query:
222
+ not:
223
+ const: false
161
224
  - if: # not transition
162
225
  not:
163
226
  properties:
@@ -167,6 +230,30 @@ properties:
167
230
  properties:
168
231
  concurrency:
169
232
  const: ~ # never pass
233
+ - if: # neither a transition nor an assignment
234
+ not:
235
+ properties:
236
+ type:
237
+ enum: [transition, assignment]
238
+ then:
239
+ properties:
240
+ # An operation that writes nothing has nothing to record. An effect writes, but
241
+ # its write is get-or-create, so a second arrival of one writes nothing already —
242
+ # and it writes in `acquire`, before its algorithm has run, so its reply is not
243
+ # known where the record is made.
244
+ once:
245
+ const: ~ # never pass
246
+ - if: # commits more than one entity
247
+ properties:
248
+ type:
249
+ const: transition
250
+ scope:
251
+ const: entries
252
+ then:
253
+ properties:
254
+ # the reply is recorded, and over a set it is unbounded
255
+ once:
256
+ const: ~ # never pass
170
257
  - if: # observation
171
258
  properties:
172
259
  type:
@@ -174,7 +261,7 @@ properties:
174
261
  then:
175
262
  properties:
176
263
  scope:
177
- enum: [object, objects, stream, none]
264
+ enum: [entry, entries, stream, none]
178
265
  query:
179
266
  not:
180
267
  const: false
@@ -189,15 +276,13 @@ properties:
189
276
  - if: # computation
190
277
  properties:
191
278
  type:
192
- const: computation
279
+ enum: [computation, unmanaged]
193
280
  then:
194
281
  properties:
195
282
  scope:
196
283
  const: none
197
- default: none
198
284
  query:
199
285
  const: false
200
- default: false
201
286
  - if: # effect
202
287
  properties:
203
288
  type:
@@ -205,17 +290,23 @@ properties:
205
290
  then:
206
291
  properties:
207
292
  scope:
208
- default: none
209
- enum: [object, objects, stream, none]
293
+ enum: [entry, entries, stream, none]
294
+ - if: # takes a stream
295
+ required: [stream]
296
+ then:
297
+ properties:
298
+ type:
299
+ # a transition may run its algorithm twice over one call, and a stream is read once
300
+ enum: [effect, computation, unmanaged]
210
301
  additionalProperties: false
211
302
  additionalProperties: false
212
303
 
213
304
  events:
214
305
  type: object
215
306
  propertyNames:
216
- $ref: 'definitions#/definitions/name'
307
+ $ref: "#/definitions/name"
217
308
  patternProperties:
218
- '.*':
309
+ ".*":
219
310
  type: object
220
311
  properties:
221
312
  bridge:
@@ -223,49 +314,53 @@ properties:
223
314
  path:
224
315
  type: string
225
316
  binding:
226
- $ref: '#/definitions/binding'
317
+ $ref: "#/definitions/binding"
227
318
  conditioned:
228
319
  type: boolean
229
- default: false
230
320
  subjective:
231
321
  type: boolean
232
- default: false
233
322
  required: [bridge, path]
234
323
  additionalProperties: false
235
324
 
236
325
  receivers:
237
326
  type: object
238
327
  patternProperties:
239
- '.*':
328
+ ".*":
240
329
  type: object
241
330
  properties:
242
331
  operation:
243
- $ref: 'definitions#/definitions/name'
332
+ $ref: "#/definitions/name"
244
333
  bridge:
245
334
  type: string
246
335
  binding:
247
336
  type: string
248
337
  source:
249
- $ref: 'definitions#/definitions/name'
338
+ $ref: "#/definitions/name"
250
339
  not:
251
340
  const: context
252
341
  path:
253
342
  type: string
254
343
  conditioned:
255
344
  type: boolean
256
- default: false
257
345
  adaptive:
258
346
  type: boolean
259
- default: false
260
347
  arguments:
261
348
  type: array
262
349
  required: [operation]
263
350
  additionalProperties: false
264
351
 
265
- extensions:
352
+ guards:
266
353
  type: object
354
+ patternProperties:
355
+ ".*":
356
+ type: object
357
+ properties:
358
+ bridge: { type: string }
359
+ path: { type: string }
360
+ required: [bridge, path]
361
+ additionalProperties: false
267
362
 
268
- properties:
363
+ extensions:
269
364
  type: object
270
365
 
271
366
  additionalProperties: false
@@ -1,25 +1,73 @@
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.operations !== undefined) await streams(manifest)
20
+ if (manifest.events !== undefined) await events(manifest)
17
21
  if (manifest.receivers !== undefined) receivers(manifest)
18
22
  }
19
23
 
20
- const events = (manifest) => {
24
+ /** What only the entity's own declaration can answer: whether a name it uses is one it declares. */
25
+ const entity = (manifest) => {
26
+ const { properties, required, blank } = manifest.entity
27
+
28
+ // what an algorithm writes beside the record: for the event of its commit, and to have
29
+ // nothing of it committed; neither is ever stored
30
+ for (const name of ['TRAILERS', 'DISCARD'])
31
+ if (properties[name] !== undefined)
32
+ throw new Error(`System property '${name}' cannot be declared`)
33
+
34
+ for (const name of required ?? [])
35
+ if (properties[name] === undefined)
36
+ throw new Error(`Entity requires property '${name}', which is not defined`)
37
+
38
+ for (const name of Object.keys(blank ?? {}))
39
+ if (properties[name] === undefined)
40
+ throw new Error(`Entity blank names property '${name}', which is not defined`)
41
+ }
42
+
43
+ /**
44
+ * An operation that takes a stream is reachable over a binding that carries one. A binding that
45
+ * declares none is served in this process alone, which is a component saying so.
46
+ */
47
+ const streams = async (manifest) => {
48
+ for (const [endpoint, operation] of Object.entries(manifest.operations)) {
49
+ if (operation.stream === undefined || operation.bindings.length === 0) continue
50
+ if (await carried(operation.bindings)) continue
51
+
52
+ throw new Error(
53
+ `Operation '${endpoint}' takes a stream, which none of its bindings carries`
54
+ )
55
+ }
56
+ }
57
+
58
+ /** Whether any of these bindings carries a call whose input holds a stream. */
59
+ const carried = async (bindings) => {
60
+ for (const binding of bindings)
61
+ if ((await definition(binding)).module.properties.streams === true) return true
62
+
63
+ return false
64
+ }
65
+
66
+ const events = async (manifest) => {
21
67
  for (const [label, event] of Object.entries(manifest.events)) {
22
- if (require(event.binding).properties.async !== true) {
68
+ const { properties } = (await definition(event.binding)).module
69
+
70
+ if (properties.async !== true) {
23
71
  throw new Error(`Event '${label}' binding '${event.binding}' is not async`)
24
72
  }
25
73
  }
@@ -28,15 +76,17 @@ const events = (manifest) => {
28
76
  const receivers = (manifest) => {
29
77
  for (const [locator, receiver] of Object.entries(manifest.receivers)) {
30
78
  if (manifest.operations?.[receiver.operation] === undefined) {
31
- throw new Error(`Receiver '${locator}' refers to undefined operation '${receiver.operation}'`)
79
+ throw new Error(
80
+ `Receiver '${locator}' refers to undefined operation '${receiver.operation}'`
81
+ )
32
82
  }
33
83
 
34
84
  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(', ')}`)
85
+ throw new Error(
86
+ `Receiver '${locator}' must refer to an operation of the allowed types: ${Array.from(TYPES).join(', ')}`
87
+ )
36
88
  }
37
89
  }
38
90
  }
39
91
 
40
- const TYPES = new Set(['transition', 'effect'])
41
-
42
- exports.validate = validate
92
+ 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
+ })