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

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 +305 -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 +165 -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 +289 -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 +31 -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,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, 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 error schema allows either
190
+ type: array
191
+ items:
192
+ type: [string, integer]
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, 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,20 +290,23 @@ properties:
205
290
  then:
206
291
  properties:
207
292
  scope:
208
- const: none
209
- default: none
210
- query:
211
- const: false
212
- default: false
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]
213
301
  additionalProperties: false
214
302
  additionalProperties: false
215
303
 
216
304
  events:
217
305
  type: object
218
306
  propertyNames:
219
- $ref: 'definitions#/definitions/name'
307
+ $ref: "#/definitions/name"
220
308
  patternProperties:
221
- '.*':
309
+ ".*":
222
310
  type: object
223
311
  properties:
224
312
  bridge:
@@ -226,49 +314,53 @@ properties:
226
314
  path:
227
315
  type: string
228
316
  binding:
229
- $ref: '#/definitions/binding'
317
+ $ref: "#/definitions/binding"
230
318
  conditioned:
231
319
  type: boolean
232
- default: false
233
320
  subjective:
234
321
  type: boolean
235
- default: false
236
322
  required: [bridge, path]
237
323
  additionalProperties: false
238
324
 
239
325
  receivers:
240
326
  type: object
241
327
  patternProperties:
242
- '.*':
328
+ ".*":
243
329
  type: object
244
330
  properties:
245
331
  operation:
246
- $ref: 'definitions#/definitions/name'
332
+ $ref: "#/definitions/name"
247
333
  bridge:
248
334
  type: string
249
335
  binding:
250
336
  type: string
251
337
  source:
252
- $ref: 'definitions#/definitions/name'
338
+ $ref: "#/definitions/name"
253
339
  not:
254
340
  const: context
255
341
  path:
256
342
  type: string
257
343
  conditioned:
258
344
  type: boolean
259
- default: false
260
345
  adaptive:
261
346
  type: boolean
262
- default: false
263
347
  arguments:
264
348
  type: array
265
349
  required: [operation]
266
350
  additionalProperties: false
267
351
 
268
- extensions:
352
+ guards:
269
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
270
362
 
271
- properties:
363
+ extensions:
272
364
  type: object
273
365
 
274
366
  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
+ })