@toa.io/norm 1.0.0-alpha.284 → 1.0.0-alpha.286

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 (67) hide show
  1. package/package.json +5 -5
  2. package/src/.component/.expand/bridge.js +1 -1
  3. package/src/.component/.expand/entity.js +1 -1
  4. package/src/.component/.expand/events.js +1 -1
  5. package/src/.component/.expand/extensions.js +1 -1
  6. package/src/.component/.expand/operations.js +1 -1
  7. package/src/.component/.expand/properties.js +1 -1
  8. package/src/.component/.expand/receivers.js +3 -2
  9. package/src/.component/.expand/version.js +11 -13
  10. package/src/.component/.normalize/entity.js +7 -0
  11. package/src/.component/.normalize/events.js +4 -1
  12. package/src/.component/.normalize/index.js +1 -0
  13. package/src/.component/.normalize/operations.js +9 -1
  14. package/src/.component/.normalize/receivers.js +4 -1
  15. package/src/.component/collapse.js +14 -6
  16. package/src/.component/defaults.js +3 -4
  17. package/src/.component/dependencies.js +7 -4
  18. package/src/.component/dereference.js +14 -13
  19. package/src/.component/expand.js +11 -2
  20. package/src/.component/extensions.js +2 -1
  21. package/src/.component/index.js +1 -0
  22. package/src/.component/merge.js +3 -5
  23. package/src/.component/migrations.js +57 -0
  24. package/src/.component/normalize.js +9 -1
  25. package/src/.component/schema.yaml +29 -39
  26. package/src/.component/validate.js +23 -3
  27. package/src/.context/.dependencies/connectors.js +3 -3
  28. package/src/.context/.dependencies/describe.js +4 -1
  29. package/src/.context/.dependencies/extensions.js +18 -13
  30. package/src/.context/.dependencies/load.js +6 -4
  31. package/src/.context/.dependencies/resolve.js +1 -1
  32. package/src/.context/dereference.js +3 -1
  33. package/src/.context/normalize.js +5 -0
  34. package/src/.context/schema.yaml +6 -4
  35. package/src/component.js +14 -5
  36. package/src/context.js +9 -5
  37. package/src/entity.js +15 -0
  38. package/src/index.js +1 -0
  39. package/src/shortcuts.js +0 -1
  40. package/test/component/collapse.fixtures.js +28 -34
  41. package/test/component/collapse.test.js +21 -0
  42. package/test/component/dependencies.test.js +6 -6
  43. package/test/component/dereference.fixtures.js +13 -17
  44. package/test/component/dereference.test.js +12 -3
  45. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
  46. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
  47. package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
  48. package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
  49. package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
  50. package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
  51. package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
  52. package/test/component/migrations.test.js +62 -0
  53. package/test/component/normalize.test.js +51 -13
  54. package/test/component/validate.fixtures.js +14 -8
  55. package/test/component/validate.test.js +113 -59
  56. package/test/context/complete.test.js +5 -1
  57. package/test/context/dereference.test.js +3 -2
  58. package/test/context/expand.test.js +4 -2
  59. package/test/context/normalize.test.js +14 -1
  60. package/test/context/validate.test.js +40 -20
  61. package/test/shortcuts.test.js +2 -1
  62. package/types/component.d.ts +14 -3
  63. package/types/context/declaration.d.ts +1 -1
  64. package/types/context.d.ts +10 -9
  65. package/types/entity.d.ts +7 -0
  66. package/types/index.d.ts +1 -0
  67. package/CHANGELOG.md +0 -203
@@ -18,6 +18,53 @@ describe('operations', () => {
18
18
 
19
19
  assert.deepStrictEqual(manifest.operations.add.bindings, manifest.bindings)
20
20
  })
21
+
22
+ it('should state an empty output where none is declared', async () => {
23
+ await normalize(manifest)
24
+
25
+ assert.deepStrictEqual(manifest.operations.add.output, {})
26
+ })
27
+
28
+ it('should scope a computation to none', async () => {
29
+ manifest.operations.compute = { type: 'computation' }
30
+
31
+ await normalize(manifest)
32
+
33
+ assert.deepStrictEqual(manifest.operations.compute.scope, 'none')
34
+ assert.deepStrictEqual(manifest.operations.compute.query, false)
35
+ })
36
+
37
+ it('should scope an effect to none unless it says otherwise', async () => {
38
+ manifest.operations.affect = { type: 'effect' }
39
+ manifest.operations.reach = { type: 'effect', scope: 'objects' }
40
+
41
+ await normalize(manifest)
42
+
43
+ assert.deepStrictEqual(manifest.operations.affect.scope, 'none')
44
+ assert.deepStrictEqual(manifest.operations.reach.scope, 'objects')
45
+ })
46
+ })
47
+
48
+ describe('namespace', () => {
49
+ it('should default to `default`', async () => {
50
+ delete manifest.namespace
51
+
52
+ await normalize(manifest)
53
+
54
+ assert.deepStrictEqual(manifest.namespace, 'default')
55
+ })
56
+ })
57
+
58
+ describe('entity', () => {
59
+ it('should default storage and flags', async () => {
60
+ manifest.entity = { properties: {} }
61
+
62
+ await normalize(manifest)
63
+
64
+ assert.deepStrictEqual(manifest.entity.storage, '@toa.io/storages.mongodb')
65
+ assert.deepStrictEqual(manifest.entity.associated, false)
66
+ assert.deepStrictEqual(manifest.entity.custom, false)
67
+ })
21
68
  })
22
69
 
23
70
  describe('extensions', () => {
@@ -44,30 +91,21 @@ describe('extensions', () => {
44
91
  describe('receivers', () => {
45
92
  it('should substitute default namespace', async () => {
46
93
  manifest.receivers = {
47
- 'messages.created': 'add'
94
+ 'messages.created': { operation: 'add' }
48
95
  }
49
96
 
50
97
  await normalize(manifest)
51
98
 
52
- assert.deepStrictEqual(manifest.receivers, {
53
- 'default.messages.created': 'add'
54
- })
99
+ assert.deepStrictEqual(Object.keys(manifest.receivers), ['default.messages.created'])
55
100
  })
56
101
 
57
102
  it('should not substitute default namespace for foreign events', async () => {
58
- const receiver = {
59
- transition: 'add',
60
- source: 'test'
61
- }
62
-
63
103
  manifest.receivers = {
64
- 'messages.created': receiver
104
+ 'messages.created': { transition: 'add', source: 'test' }
65
105
  }
66
106
 
67
107
  await normalize(manifest)
68
108
 
69
- assert.deepStrictEqual(manifest.receivers, {
70
- 'messages.created': receiver
71
- })
109
+ assert.deepStrictEqual(Object.keys(manifest.receivers), ['messages.created'])
72
110
  })
73
111
  })
@@ -3,13 +3,13 @@ export const ok = {
3
3
  name: 'bar',
4
4
  entity: {
5
5
  storage: 'whatever',
6
- schema: {
7
- properties: {
8
- name: {
9
- type: 'string',
10
- minLength: 1,
11
- maxLength: 128
12
- }
6
+ associated: false,
7
+ custom: false,
8
+ properties: {
9
+ name: {
10
+ type: 'string',
11
+ minLength: 1,
12
+ maxLength: 128
13
13
  }
14
14
  }
15
15
  },
@@ -35,11 +35,15 @@ export const ok = {
35
35
  },
36
36
  compute: {
37
37
  type: 'computation',
38
+ scope: 'none',
39
+ query: false,
38
40
  bridge: 'whatever',
39
41
  bindings: ['@toa.io/bindings.amqp']
40
42
  },
41
43
  affect: {
42
44
  type: 'effect',
45
+ scope: 'none',
46
+ query: false,
43
47
  bridge: 'whatever',
44
48
  bindings: ['@toa.io/bindings.amqp']
45
49
  }
@@ -58,7 +62,9 @@ export const ok = {
58
62
  operation: 'add',
59
63
  bridge: 'whatever',
60
64
  binding: 'amqp',
61
- path: '/somewhere'
65
+ path: '/somewhere',
66
+ conditioned: false,
67
+ adaptive: false
62
68
  }
63
69
  }
64
70
  }
@@ -31,34 +31,39 @@ it('should not have additional properties', async () => {
31
31
  describe('namespace', () => {
32
32
  it('should match token pattern', async () => {
33
33
  manifest.namespace = '1'
34
- await assert.rejects(validate(manifest), (error) => /must match pattern/.test(error.message))
34
+ await assert.rejects(validate(manifest), (error) =>
35
+ /must match pattern/.test(error.message)
36
+ )
35
37
 
36
38
  manifest.namespace = 'foo_'
37
- await assert.rejects(validate(manifest), (error) => /must match pattern/.test(error.message))
39
+ await assert.rejects(validate(manifest), (error) =>
40
+ /must match pattern/.test(error.message)
41
+ )
38
42
 
39
43
  manifest.namespace = 'foo_bar'
40
- await assert.rejects(validate(manifest), (error) => /must match pattern/.test(error.message))
44
+ await assert.rejects(validate(manifest), (error) =>
45
+ /must match pattern/.test(error.message)
46
+ )
41
47
 
42
48
  manifest.namespace = 'foo-'
43
- await assert.rejects(validate(manifest), (error) => /must match pattern/.test(error.message))
49
+ await assert.rejects(validate(manifest), (error) =>
50
+ /must match pattern/.test(error.message)
51
+ )
44
52
 
45
53
  manifest.namespace = 'foo-bar'
46
- await assert.rejects(validate(manifest), (error) => /must match pattern/.test(error.message))
54
+ await assert.rejects(validate(manifest), (error) =>
55
+ /must match pattern/.test(error.message)
56
+ )
47
57
 
48
58
  manifest.namespace = 'FooBar12'
49
59
  await assert.doesNotReject(validate(manifest))
50
60
  })
51
61
 
52
- it('should forbid \'system\' namespace', async () => {
62
+ it("should forbid 'system' namespace", async () => {
53
63
  manifest.namespace = 'system'
54
- await assert.rejects(validate(manifest), (error) => /must NOT be valid/.test(error.message))
55
- })
56
-
57
- it('should set `default` namespace', async () => {
58
- delete manifest.namespace
59
-
60
- await assert.doesNotReject(validate(manifest))
61
- assert.deepStrictEqual(manifest.namespace, 'default')
64
+ await assert.rejects(validate(manifest), (error) =>
65
+ /must NOT be valid/.test(error.message)
66
+ )
62
67
  })
63
68
  })
64
69
 
@@ -77,7 +82,9 @@ describe('entity', () => {
77
82
 
78
83
  it('should be object', async () => {
79
84
  manifest.entity = 'foo'
80
- await assert.rejects(validate(manifest), (error) => /must be object/.test(error.message))
85
+ await assert.rejects(validate(manifest), (error) =>
86
+ /must be object/.test(error.message)
87
+ )
81
88
  })
82
89
 
83
90
  it('should not have additional properties', async () => {
@@ -85,33 +92,24 @@ describe('entity', () => {
85
92
  await assert.rejects(validate(manifest))
86
93
  })
87
94
 
88
- describe('schema', () => {
95
+ describe('properties', () => {
89
96
  it('should be required', async () => {
90
- delete manifest.entity.schema
97
+ delete manifest.entity.properties
91
98
  await assert.rejects(validate(manifest))
92
99
  })
93
100
 
94
- it('should be JSON schema object', async () => {
95
- manifest.entity.schema = { properties: { foo: 'bar' } }
101
+ it('should be JSON schemas', async () => {
102
+ manifest.entity.properties.foo = 'bar'
96
103
  await assert.rejects(validate(manifest))
97
104
  })
98
105
 
99
- it('should be JSON schema object of type object', async () => {
100
- manifest.entity.schema = { type: 'integer' }
101
- await assert.rejects(validate(manifest), (error) => /must be equal to constant/.test(error.message))
102
-
103
- manifest.entity.schema = {}
104
- validate(manifest)
105
- assert.strictEqual(manifest.entity.schema.type, 'object')
106
- })
107
-
108
- it('should have property names matching token pattern', async () => {
109
- manifest.entity.schema.properties._foo = { type: 'string' }
106
+ it('should have names matching token pattern', async () => {
107
+ manifest.entity.properties._foo = { type: 'string' }
110
108
  await assert.rejects(validate(manifest), (error) => /pattern/.test(error.message))
111
109
  })
112
110
 
113
- it('should allow default id', async () => {
114
- manifest.entity.schema.properties.id = {
111
+ it('should allow own id', async () => {
112
+ manifest.entity.properties.id = {
115
113
  type: 'string',
116
114
  pattern: '^[a-fA-F0-9]+$'
117
115
  }
@@ -119,10 +117,34 @@ describe('entity', () => {
119
117
  })
120
118
  })
121
119
 
122
- describe('associated', () => {
123
- it('should provide default', async () => {
120
+ describe('required', () => {
121
+ it('should name declared properties', async () => {
122
+ manifest.entity.required = ['name']
124
123
  await assert.doesNotReject(validate(manifest))
125
- assert.strictEqual(manifest.entity.associated, false)
124
+
125
+ manifest.entity.required = ['nope']
126
+ await assert.rejects(validate(manifest), (error) =>
127
+ /requires property 'nope'/.test(error.message)
128
+ )
129
+ })
130
+ })
131
+
132
+ describe('blank', () => {
133
+ it('should name declared properties', async () => {
134
+ manifest.entity.blank = { name: 'whatever' }
135
+ await assert.doesNotReject(validate(manifest))
136
+
137
+ manifest.entity.blank = { nope: 1 }
138
+ await assert.rejects(validate(manifest), (error) =>
139
+ /names property 'nope'/.test(error.message)
140
+ )
141
+ })
142
+
143
+ it('should not name a system property', async () => {
144
+ manifest.entity.blank = { VERSION: 1 }
145
+ await assert.rejects(validate(manifest), (error) =>
146
+ /must NOT be valid/.test(error.message)
147
+ )
126
148
  })
127
149
  })
128
150
  })
@@ -130,25 +152,35 @@ describe('entity', () => {
130
152
  describe('bindings', () => {
131
153
  it('should be array of unique strings', async () => {
132
154
  manifest.bindings = 'oops'
133
- await assert.rejects(validate(manifest), (error) => /must be array/.test(error.message))
155
+ await assert.rejects(validate(manifest), (error) =>
156
+ /must be array/.test(error.message)
157
+ )
134
158
 
135
159
  manifest.bindings = ['oops', 'oops']
136
- await assert.rejects(validate(manifest), (error) => /duplicate items/.test(error.message))
160
+ await assert.rejects(validate(manifest), (error) =>
161
+ /duplicate items/.test(error.message)
162
+ )
137
163
 
138
164
  manifest.bindings = ['oops', {}]
139
- await assert.rejects(validate(manifest), (error) => /must be string/.test(error.message))
165
+ await assert.rejects(validate(manifest), (error) =>
166
+ /must be string/.test(error.message)
167
+ )
140
168
  })
141
169
 
142
170
  it('should forbid explicit loop', async () => {
143
171
  manifest.bindings = ['@toa.io/bindings.loop']
144
- await assert.rejects(validate(manifest), (error) => /must NOT be valid/.test(error.message))
172
+ await assert.rejects(validate(manifest), (error) =>
173
+ /must NOT be valid/.test(error.message)
174
+ )
145
175
  })
146
176
  })
147
177
 
148
178
  describe('operations', () => {
149
179
  it('should be object', async () => {
150
180
  manifest.operations.get = 'bar'
151
- await assert.rejects(validate(manifest), (error) => /must be object/.test(error.message))
181
+ await assert.rejects(validate(manifest), (error) =>
182
+ /must be object/.test(error.message)
183
+ )
152
184
  })
153
185
 
154
186
  it('should not have additional properties', async () => {
@@ -161,45 +193,57 @@ describe('operations', () => {
161
193
  await assert.rejects(validate(manifest))
162
194
 
163
195
  manifest.operations.get.type = 'foo'
164
- await assert.rejects(validate(manifest), (error) => /one of the allowed values/.test(error.message))
196
+ await assert.rejects(validate(manifest), (error) =>
197
+ /one of the allowed values/.test(error.message)
198
+ )
165
199
  })
166
200
 
167
201
  it('should forbid explicit loop', async () => {
168
202
  manifest.operations.get.bindings = ['@toa.io/bindings.loop']
169
- await assert.rejects(validate(manifest), (error) => /must NOT be valid/.test(error.message))
203
+ await assert.rejects(validate(manifest), (error) =>
204
+ /must NOT be valid/.test(error.message)
205
+ )
170
206
  })
171
207
 
172
208
  it('should forbid query: false for observations', async () => {
173
209
  manifest.operations.get.query = false
174
- await assert.rejects(validate(manifest), (error) => /must NOT be valid/.test(error.message))
210
+ await assert.rejects(validate(manifest), (error) =>
211
+ /must NOT be valid/.test(error.message)
212
+ )
175
213
  })
176
214
 
177
- for (const [_, operation] of [
178
- ['computation', 'compute']
179
- ])
180
- it(`should set query: false for ${_}`, async () => {
181
- validate(manifest)
215
+ for (const [_, operation] of [['computation', 'compute']])
216
+ it(`should set query: false for ${_}`, async () => {
217
+ validate(manifest)
182
218
 
183
- assert.strictEqual(manifest.operations[operation].query, false)
184
- })
219
+ assert.strictEqual(manifest.operations[operation].query, false)
220
+ })
185
221
 
186
222
  describe('scope', () => {
187
223
  it('should have scope', async () => {
188
224
  delete manifest.operations.get.scope
189
- await assert.rejects(validate(manifest), (error) => /required property/.test(error.message))
225
+ await assert.rejects(validate(manifest), (error) =>
226
+ /required property/.test(error.message)
227
+ )
190
228
  })
191
229
 
192
230
  it('should allow only entity or set for observations', async () => {
193
231
  manifest.operations.get.scope = 'changeset'
194
- await assert.rejects(validate(manifest), (error) => /allowed values/.test(error.message))
232
+ await assert.rejects(validate(manifest), (error) =>
233
+ /allowed values/.test(error.message)
234
+ )
195
235
  })
196
236
 
197
237
  it('should allow only entity for transitions', async () => {
198
238
  manifest.operations.add.scope = 'changeset'
199
- await assert.rejects(validate(manifest), (error) => /allowed values/.test(error.message))
239
+ await assert.rejects(validate(manifest), (error) =>
240
+ /allowed values/.test(error.message)
241
+ )
200
242
 
201
243
  manifest.operations.add.scope = 'set'
202
- await assert.rejects(validate(manifest), (error) => /allowed values/.test(error.message))
244
+ await assert.rejects(validate(manifest), (error) =>
245
+ /allowed values/.test(error.message)
246
+ )
203
247
  })
204
248
 
205
249
  it('should allow only changeset for assignments', async () => {
@@ -207,14 +251,18 @@ describe('operations', () => {
207
251
  await assert.doesNotReject(validate(manifest))
208
252
 
209
253
  manifest.operations.set.scope = 'set'
210
- await assert.rejects(validate(manifest), (error) => /allowed values/.test(error.message))
254
+ await assert.rejects(validate(manifest), (error) =>
255
+ /allowed values/.test(error.message)
256
+ )
211
257
  })
212
258
  })
213
259
 
214
260
  describe('concurrency', () => {
215
261
  it('should be required for transitions', async () => {
216
262
  delete manifest.operations.add.concurrency
217
- await assert.rejects(validate(manifest), (error) => /required property/.test(error.message))
263
+ await assert.rejects(validate(manifest), (error) =>
264
+ /required property/.test(error.message)
265
+ )
218
266
  })
219
267
 
220
268
  it('should throw for observations, assignments', async () => {
@@ -243,18 +291,24 @@ describe('receivers', () => {
243
291
  it('should throw if transition points to undefined operation', async () => {
244
292
  manifest.receivers['foo.bar.happened'].operation = 'notExists'
245
293
 
246
- await assert.rejects(validate(manifest), (error) => /refers to undefined operation/.test(error.message))
294
+ await assert.rejects(validate(manifest), (error) =>
295
+ /refers to undefined operation/.test(error.message)
296
+ )
247
297
  })
248
298
 
249
299
  it('should throw if transition points to observation', async () => {
250
300
  manifest.receivers['foo.bar.happened'].operation = 'get'
251
301
 
252
- await assert.rejects(validate(manifest), (error) => /of the allowed types/.test(error.message))
302
+ await assert.rejects(validate(manifest), (error) =>
303
+ /of the allowed types/.test(error.message)
304
+ )
253
305
  })
254
306
 
255
307
  it('should throw if source has a name `context`', async () => {
256
308
  manifest.receivers['foo.bar.happened'].source = 'context'
257
309
 
258
- await assert.rejects(validate(manifest), (error) => /must NOT be valid/.test(error.message))
310
+ await assert.rejects(validate(manifest), (error) =>
311
+ /must NOT be valid/.test(error.message)
312
+ )
259
313
  })
260
314
  })
@@ -17,7 +17,11 @@ beforeEach(() => {
17
17
 
18
18
  it('should complete compositions', () => {
19
19
  assert.deepStrictEqual(context.compositions.length, fixtures.compositions.length)
20
- assert.ok(fixtures.compositions.every((item) => context.compositions.some((candidate) => isDeepStrictEqual(candidate, item))))
20
+ assert.ok(
21
+ fixtures.compositions.every((item) =>
22
+ context.compositions.some((candidate) => isDeepStrictEqual(candidate, item))
23
+ )
24
+ )
21
25
  })
22
26
 
23
27
  it('should create if compositions are not set', () => {
@@ -29,6 +29,7 @@ it('should throw on an unknown component', () => {
29
29
 
30
30
  broken.compositions[1].components.push('nope.here')
31
31
 
32
- assert.throws(() => dereference(broken),
33
- { message: "Composition 'bar' lists an unknown component 'nope.here'." })
32
+ assert.throws(() => dereference(broken), {
33
+ message: "Composition 'bar' lists an unknown component 'nope.here'."
34
+ })
34
35
  })
@@ -47,8 +47,10 @@ describe('composition services', () => {
47
47
 
48
48
  expand(context)
49
49
 
50
- assert.deepStrictEqual(context.compositions[0].services,
51
- ['@toa.io/extensions.exposition', '@toa.io/extensions.configuration'])
50
+ assert.deepStrictEqual(context.compositions[0].services, [
51
+ '@toa.io/extensions.exposition',
52
+ '@toa.io/extensions.configuration'
53
+ ])
52
54
  })
53
55
 
54
56
  it('should leave a package reference alone', () => {
@@ -31,6 +31,19 @@ it('should expand registry', () => {
31
31
  normalize(context)
32
32
 
33
33
  assert.deepStrictEqual(context.registry, {
34
- base
34
+ base,
35
+ platforms: ['linux/amd64', 'linux/arm/v7', 'linux/arm64']
35
36
  })
36
37
  })
38
+
39
+ it('should set default platforms', () => {
40
+ context.registry = { base: generate() }
41
+
42
+ normalize(context)
43
+
44
+ assert.deepStrictEqual(context.registry.platforms, [
45
+ 'linux/amd64',
46
+ 'linux/arm/v7',
47
+ 'linux/arm64'
48
+ ])
49
+ })
@@ -19,12 +19,18 @@ beforeEach(() => {
19
19
  describe('runtime', () => {
20
20
  it('should require', () => {
21
21
  delete context.runtime
22
- assert.throws(() => validate(context), (error) => /required/.test(error.message))
22
+ assert.throws(
23
+ () => validate(context),
24
+ (error) => /required/.test(error.message)
25
+ )
23
26
  })
24
27
 
25
28
  it('should require registry to match uri format', () => {
26
29
  context.runtime.registry = 'not-a-uri'
27
- assert.throws(() => validate(context), (error) => /must match format/.test(error.message))
30
+ assert.throws(
31
+ () => validate(context),
32
+ (error) => /must match format/.test(error.message)
33
+ )
28
34
 
29
35
  context.runtime.registry = 'http://localhost'
30
36
  assert.doesNotThrow(() => validate(context))
@@ -32,7 +38,10 @@ describe('runtime', () => {
32
38
 
33
39
  it('should require proxy to match uri format', () => {
34
40
  context.runtime.proxy = 'not-a-uri'
35
- assert.throws(() => validate(context), (error) => /must match format/.test(error.message))
41
+ assert.throws(
42
+ () => validate(context),
43
+ (error) => /must match format/.test(error.message)
44
+ )
36
45
 
37
46
  context.runtime.proxy = 'http://localhost'
38
47
  assert.doesNotThrow(() => validate(context))
@@ -43,25 +52,25 @@ describe('registry', () => {
43
52
  it('should require', () => {
44
53
  delete context.registry
45
54
 
46
- assert.throws(() => validate(context), (error) => /required property 'registry'/.test(error.message))
47
- })
48
-
49
- it('should set default platforms', () => {
50
- delete context.registry.platforms
51
-
52
- validate(context)
53
-
54
- assert.ok(context.registry.platforms instanceof Array)
55
- assert.deepStrictEqual(context.registry.platforms, ['linux/amd64', 'linux/arm/v7', 'linux/arm64'])
55
+ assert.throws(
56
+ () => validate(context),
57
+ (error) => /required property 'registry'/.test(error.message)
58
+ )
56
59
  })
57
60
  })
58
61
 
59
62
  it('should require name as label', () => {
60
63
  delete context.name
61
- assert.throws(() => validate(context), (error) => /required/.test(error.message))
64
+ assert.throws(
65
+ () => validate(context),
66
+ (error) => /required/.test(error.message)
67
+ )
62
68
 
63
69
  context.name = 'foo bar'
64
- assert.throws(() => validate(context), (error) => /pattern/.test(error.message))
70
+ assert.throws(
71
+ () => validate(context),
72
+ (error) => /pattern/.test(error.message)
73
+ )
65
74
 
66
75
  context.name = 'foo-bar'
67
76
  assert.doesNotThrow(() => validate(context))
@@ -69,7 +78,10 @@ it('should require name as label', () => {
69
78
 
70
79
  it('should require registry url', () => {
71
80
  delete context.registry
72
- assert.throws(() => validate(context), (error) => /required/.test(error.message))
81
+ assert.throws(
82
+ () => validate(context),
83
+ (error) => /required/.test(error.message)
84
+ )
73
85
  })
74
86
 
75
87
  it('should allow mono replicas and resources', () => {
@@ -93,16 +105,24 @@ describe('compositions', () => {
93
105
 
94
106
  it('should require services to be a non-empty array of strings', () => {
95
107
  context.compositions[0].services = []
96
- assert.throws(() => validate(context), (error) => /fewer than 1 items/.test(error.message))
108
+ assert.throws(
109
+ () => validate(context),
110
+ (error) => /fewer than 1 items/.test(error.message)
111
+ )
97
112
 
98
113
  context.compositions[0].services = 'exposition'
99
- assert.throws(() => validate(context), (error) => /must be array/.test(error.message))
114
+ assert.throws(
115
+ () => validate(context),
116
+ (error) => /must be array/.test(error.message)
117
+ )
100
118
  })
101
119
 
102
120
  it('should reject an unknown property', () => {
103
121
  context.compositions[0].compoments = ['a.b']
104
122
 
105
- assert.throws(() => validate(context),
106
- (error) => /Property compoments is not expected to be here/.test(error.message))
123
+ assert.throws(
124
+ () => validate(context),
125
+ (error) => /Property compoments is not expected to be here/.test(error.message)
126
+ )
107
127
  })
108
128
  })
@@ -55,7 +55,8 @@ describe('recognize', () => {
55
55
 
56
56
  assert.deepStrictEqual(object[group], known)
57
57
 
58
- for (const alias of Object.keys(fixtures.SHORTCUTS)) assert.strictEqual(object[alias], undefined)
58
+ for (const alias of Object.keys(fixtures.SHORTCUTS))
59
+ assert.strictEqual(object[alias], undefined)
59
60
  })
60
61
 
61
62
  it('should not overwrite group', () => {
@@ -1,4 +1,5 @@
1
- import type { Locator, operations } from '@toa.io/core'
1
+ import type { Locator } from '@toa.io/core'
2
+ import type { operations } from '@toa.io/core/types'
2
3
 
3
4
  type Map = {
4
5
  [id: string]: Component
@@ -35,10 +36,20 @@ type Receiver = {
35
36
  source?: string
36
37
  }
37
38
 
38
- type Entity = {
39
- schema: Object
39
+ type Migration = {
40
+ id: string
41
+ steps: unknown[]
42
+ }
43
+
44
+ export type Entity = {
45
+ properties: Record<string, Object>
46
+ required?: string[]
47
+ /** what a record holds before anything is written to it */
48
+ blank?: Record<string, unknown>
40
49
  storage?: string
41
50
  associated?: boolean
51
+ custom?: boolean
52
+ migrations?: Migration[]
42
53
  }
43
54
 
44
55
  type Declaration = {
@@ -2,7 +2,7 @@ import { Registry, Runtime } from '../context.js'
2
2
 
3
3
  /** A composition as declared, before its members are resolved. */
4
4
  interface Composition {
5
- name: string,
5
+ name: string
6
6
  components: string[]
7
7
  services?: string[]
8
8
  }