@toa.io/norm 1.0.0-alpha.312 → 1.0.0-alpha.314
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/package.json +4 -4
- package/src/.component/.expand/extensions.js +1 -1
- package/src/.component/schema.yaml +7 -2
- package/src/.component/validate.js +4 -0
- package/src/.context/schema.yaml +4 -2
- package/src/definition.js +10 -3
- package/src/shortcuts.js +1 -1
- package/test/component/validate.test.js +45 -0
- package/test/context/evict.fixtures.js +1 -1
- package/test/context/evict.test.js +6 -5
- package/test/context/validate.test.js +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [1.0.0-alpha.314](https://github.com/toa-io/toa/compare/v1.0.0-alpha.313...v1.0.0-alpha.314) (2026-09-19)
|
|
7
|
+
|
|
8
|
+
* feat(core)!: let an operation state how long its calls are remembered ([af177c2](https://github.com/toa-io/toa/commit/af177c2ab3d00d475840b5ef9e612f04e82b72bd))
|
|
9
|
+
* feat(core)!: rename trailers to the TRAILERS system property ([8d59ef9](https://github.com/toa-io/toa/commit/8d59ef90d04b7329578203fe10bd21fe41dfe2f5))
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **core:** a reference may name a declaration its package claims ([8040f60](https://github.com/toa-io/toa/commit/8040f60976549ab90497675bb54e664261f5b6bb))
|
|
14
|
+
|
|
15
|
+
### BREAKING CHANGES
|
|
16
|
+
|
|
17
|
+
* `inbox.retention` under 600 is refused. See
|
|
18
|
+
migrations/313.md.
|
|
19
|
+
* `state._trailers` is `state.TRAILERS`; see migrations/313.md.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# [1.0.0-alpha.313](https://github.com/toa-io/toa/compare/v1.0.0-alpha.312...v1.0.0-alpha.313) (2026-09-18)
|
|
23
|
+
|
|
24
|
+
**Note:** Version bump only for package @toa.io/norm
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
6
30
|
# [1.0.0-alpha.312](https://github.com/toa-io/toa/compare/v1.0.0-alpha.311...v1.0.0-alpha.312) (2026-09-18)
|
|
7
31
|
|
|
8
32
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/norm",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.314",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa declarations normalization and validation",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
25
|
-
"@toa.io/definitions": "1.0.0-alpha.
|
|
24
|
+
"@toa.io/core": "1.0.0-alpha.314",
|
|
25
|
+
"@toa.io/definitions": "1.0.0-alpha.314",
|
|
26
26
|
"@toa.io/generic": "1.0.0-alpha.311",
|
|
27
27
|
"@toa.io/prototype": "1.0.0-alpha.301",
|
|
28
28
|
"@toa.io/schemas": "1.0.0-alpha.311",
|
|
29
29
|
"fast-glob": "3.3.3"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "4b7c0f4468e6fa89f95a69f43fed85c648fb033d"
|
|
32
32
|
}
|
|
@@ -7,7 +7,7 @@ export function extensions(manifest) {
|
|
|
7
7
|
|
|
8
8
|
const SHORTCUTS = {
|
|
9
9
|
exposition: '@toa.io/extensions.exposition',
|
|
10
|
-
realtime: '@toa.io/extensions.realtime',
|
|
10
|
+
realtime: '@toa.io/extensions.exposition#realtime',
|
|
11
11
|
configuration: '@toa.io/extensions.configuration',
|
|
12
12
|
state: '@toa.io/extensions.state',
|
|
13
13
|
stash: '@toa.io/extensions.stash',
|
|
@@ -101,7 +101,7 @@ properties:
|
|
|
101
101
|
type: object
|
|
102
102
|
propertyNames:
|
|
103
103
|
not:
|
|
104
|
-
enum: [id, VERSION, CREATED, UPDATED, DELETED, REGION]
|
|
104
|
+
enum: [id, VERSION, CREATED, UPDATED, DELETED, REGION, TRAILERS]
|
|
105
105
|
migrations:
|
|
106
106
|
type: array
|
|
107
107
|
items:
|
|
@@ -157,7 +157,12 @@ properties:
|
|
|
157
157
|
enum: [none, retry]
|
|
158
158
|
once:
|
|
159
159
|
# the same call arriving twice changes state once; see documentation/inbox.md
|
|
160
|
-
|
|
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
|
|
161
166
|
stateful:
|
|
162
167
|
# served by every process under an address of its own, which a call names; see documentation/stateful.md
|
|
163
168
|
type: boolean
|
|
@@ -25,6 +25,10 @@ export const validate = async (manifest) => {
|
|
|
25
25
|
const entity = (manifest) => {
|
|
26
26
|
const { properties, required, blank } = manifest.entity
|
|
27
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
|
+
|
|
28
32
|
for (const name of required ?? [])
|
|
29
33
|
if (properties[name] === undefined)
|
|
30
34
|
throw new Error(`Entity requires property '${name}', which is not defined`)
|
package/src/.context/schema.yaml
CHANGED
|
@@ -164,9 +164,11 @@ properties:
|
|
|
164
164
|
Seconds a call is remembered, which is the window a duplicate of it is caught in. Budget
|
|
165
165
|
it for how late a duplicate can arrive — a redelivery, the attempts the broker library
|
|
166
166
|
makes of a message, whatever a client retries on — rather than for how long the change
|
|
167
|
-
matters. Every call of an operation that declares `once` is a record for this
|
|
167
|
+
matters. Every call of an operation that declares `once: true` is a record for this
|
|
168
|
+
long; an operation that states a number of its own is kept for that instead. Ten minutes
|
|
169
|
+
at least, because the broker alone repeats a call for about that long.
|
|
168
170
|
type: integer
|
|
169
|
-
minimum:
|
|
171
|
+
minimum: 600
|
|
170
172
|
addressed:
|
|
171
173
|
type: object
|
|
172
174
|
additionalProperties: false
|
package/src/definition.js
CHANGED
|
@@ -31,10 +31,17 @@ async function load(reference) {
|
|
|
31
31
|
|
|
32
32
|
if (known !== undefined) return { name: reference, module: known }
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
const
|
|
34
|
+
// `package#key` is a declaration of its own that a package claims beside its main one
|
|
35
|
+
const [path, key] = reference.split('#')
|
|
36
|
+
const name = (metadata(path)?.name ?? path) + (key === undefined ? '' : '#' + key)
|
|
37
|
+
const module = await import(pathToFileURL(resolve(path)).href)
|
|
36
38
|
|
|
37
|
-
return { name, module }
|
|
39
|
+
if (key === undefined) return { name, module }
|
|
40
|
+
|
|
41
|
+
if (module.keys?.[key] === undefined)
|
|
42
|
+
throw new Error(`'${reference}' names a key that '${path}' does not claim`)
|
|
43
|
+
|
|
44
|
+
return { name, module: module.keys[key] }
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
function resolve(reference) {
|
package/src/shortcuts.js
CHANGED
|
@@ -43,7 +43,7 @@ export const SHORTCUTS = {
|
|
|
43
43
|
bash: '@toa.io/bridges.bash',
|
|
44
44
|
mongodb: '@toa.io/storages.mongodb',
|
|
45
45
|
exposition: '@toa.io/extensions.exposition',
|
|
46
|
-
realtime: '@toa.io/extensions.realtime',
|
|
46
|
+
realtime: '@toa.io/extensions.exposition#realtime',
|
|
47
47
|
configuration: '@toa.io/extensions.configuration',
|
|
48
48
|
stash: '@toa.io/extensions.stash',
|
|
49
49
|
storages: '@toa.io/extensions.storages',
|
|
@@ -115,6 +115,13 @@ describe('entity', () => {
|
|
|
115
115
|
}
|
|
116
116
|
await assert.doesNotReject(validate(manifest))
|
|
117
117
|
})
|
|
118
|
+
|
|
119
|
+
it('should not declare TRAILERS', async () => {
|
|
120
|
+
manifest.entity.properties.TRAILERS = { type: 'object' }
|
|
121
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
122
|
+
/System property 'TRAILERS' cannot be declared/.test(error.message)
|
|
123
|
+
)
|
|
124
|
+
})
|
|
118
125
|
})
|
|
119
126
|
|
|
120
127
|
describe('required', () => {
|
|
@@ -146,6 +153,13 @@ describe('entity', () => {
|
|
|
146
153
|
/must NOT be valid/.test(error.message)
|
|
147
154
|
)
|
|
148
155
|
})
|
|
156
|
+
|
|
157
|
+
it('should not name TRAILERS', async () => {
|
|
158
|
+
manifest.entity.blank = { TRAILERS: {} }
|
|
159
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
160
|
+
/must NOT be valid/.test(error.message)
|
|
161
|
+
)
|
|
162
|
+
})
|
|
149
163
|
})
|
|
150
164
|
})
|
|
151
165
|
|
|
@@ -257,6 +271,37 @@ describe('operations', () => {
|
|
|
257
271
|
})
|
|
258
272
|
})
|
|
259
273
|
|
|
274
|
+
describe('once', () => {
|
|
275
|
+
it('should be true, false or a number of seconds', async () => {
|
|
276
|
+
for (const once of [true, false, 600, 86400]) {
|
|
277
|
+
manifest.operations.add.once = once
|
|
278
|
+
await assert.doesNotReject(validate(manifest), `once: ${once}`)
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('should stay true rather than be coerced into a number', async () => {
|
|
283
|
+
manifest.operations.add.once = true
|
|
284
|
+
await validate(manifest)
|
|
285
|
+
|
|
286
|
+
assert.strictEqual(manifest.operations.add.once, true)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('should not be a window shorter than ten minutes', async () => {
|
|
290
|
+
manifest.operations.add.once = 599
|
|
291
|
+
await assert.rejects(validate(manifest), (error) => /must be >= 600/.test(error.message))
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('should not be a fraction of a second', async () => {
|
|
295
|
+
manifest.operations.add.once = 600.5
|
|
296
|
+
await assert.rejects(validate(manifest))
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('should throw for an operation that takes none', async () => {
|
|
300
|
+
manifest.operations.get.once = 600
|
|
301
|
+
await assert.rejects(validate(manifest))
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
260
305
|
describe('concurrency', () => {
|
|
261
306
|
it('should be required for transitions', async () => {
|
|
262
307
|
delete manifest.operations.add.concurrency
|
|
@@ -25,7 +25,10 @@ it('should mark an evicted component and keep it', () => {
|
|
|
25
25
|
evict(context)
|
|
26
26
|
|
|
27
27
|
assert.deepStrictEqual(
|
|
28
|
-
context.components.map((component) => [
|
|
28
|
+
context.components.map((component) => [
|
|
29
|
+
component.locator.id,
|
|
30
|
+
component.evicted === true
|
|
31
|
+
]),
|
|
29
32
|
[
|
|
30
33
|
['a.b', true],
|
|
31
34
|
['b.a', false],
|
|
@@ -56,14 +59,12 @@ it('should drop an evicted service from a composition that runs it', () => {
|
|
|
56
59
|
|
|
57
60
|
evict(context)
|
|
58
61
|
|
|
59
|
-
assert.deepStrictEqual(context.compositions[0].services, [
|
|
60
|
-
'@toa.io/extensions.realtime'
|
|
61
|
-
])
|
|
62
|
+
assert.deepStrictEqual(context.compositions[0].services, ['@toa.io/extensions.cadence'])
|
|
62
63
|
})
|
|
63
64
|
|
|
64
65
|
it('should leave no services where every one is evicted', () => {
|
|
65
66
|
context.evicted = {
|
|
66
|
-
services: ['@toa.io/extensions.exposition', '@toa.io/extensions.
|
|
67
|
+
services: ['@toa.io/extensions.exposition', '@toa.io/extensions.cadence']
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
evict(context)
|
|
@@ -84,6 +84,21 @@ it('should require registry url', () => {
|
|
|
84
84
|
)
|
|
85
85
|
})
|
|
86
86
|
|
|
87
|
+
describe('inbox', () => {
|
|
88
|
+
it('should allow a retention of ten minutes or more', () => {
|
|
89
|
+
context.inbox = { retention: 600 }
|
|
90
|
+
assert.doesNotThrow(() => validate(context))
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should refuse a retention shorter than ten minutes', () => {
|
|
94
|
+
context.inbox = { retention: 599 }
|
|
95
|
+
assert.throws(
|
|
96
|
+
() => validate(context),
|
|
97
|
+
(error) => /must be >= 600/.test(error.message)
|
|
98
|
+
)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
87
102
|
it('should allow mono replicas and resources', () => {
|
|
88
103
|
context.mono = {
|
|
89
104
|
replicas: 2,
|