@toa.io/norm 1.0.0-alpha.32 → 1.0.0-alpha.320

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