@toa.io/norm 1.0.0-alpha.326 → 1.0.0-alpha.327
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 +7 -0
- package/package.json +5 -5
- package/src/.component/.expand/version.js +3 -1
- package/src/.component/validate.js +3 -1
- package/src/.context/.dependencies/extensions.js +6 -1
- package/src/.context/dependencies.js +2 -1
- package/src/.context/evict.js +3 -1
- package/src/.context/expand.js +27 -0
- package/src/context.js +3 -1
- package/src/manifest.js +7 -2
- package/src/map.js +3 -1
- package/test/component/validate.test.js +12 -4
- package/test/manifest.test.js +15 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
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.327](https://github.com/toa-io/toa/compare/v1.0.0-alpha.326...v1.0.0-alpha.327) (2026-10-10)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* name a component without a namespace by its name ([abcdd9c](https://github.com/toa-io/toa/commit/abcdd9c9a3d3c5c1c68b74544791848e59a7e158))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# [1.0.0-alpha.326](https://github.com/toa-io/toa/compare/v1.0.0-alpha.325...v1.0.0-alpha.326) (2026-10-06)
|
|
7
14
|
|
|
8
15
|
* feat(core)!: deliver an operation's error as its code and its cause ([18d303b](https://github.com/toa-io/toa/commit/18d303b9143930735e4b093345db1dbf37f90052))
|
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.327",
|
|
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.327",
|
|
25
|
+
"@toa.io/definitions": "1.0.0-alpha.327",
|
|
26
26
|
"@toa.io/generic": "1.0.0-alpha.311",
|
|
27
|
-
"@toa.io/prototype": "1.0.0-alpha.
|
|
27
|
+
"@toa.io/prototype": "1.0.0-alpha.327",
|
|
28
28
|
"@toa.io/schemas": "1.0.0-alpha.326",
|
|
29
29
|
"tinyglobby": "0.2.17"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "cd6d6d77f19fbae5d7ff85d18005ca8deb9c77f3"
|
|
32
32
|
}
|
|
@@ -21,7 +21,9 @@ export async function version(manifest) {
|
|
|
21
21
|
*/
|
|
22
22
|
async function hash(manifest) {
|
|
23
23
|
const files = (await list(manifest)).sort()
|
|
24
|
-
const digests = await Promise.all(
|
|
24
|
+
const digests = await Promise.all(
|
|
25
|
+
files.map((file) => digest(join(manifest.path, file)))
|
|
26
|
+
)
|
|
25
27
|
const total = createHash('sha256')
|
|
26
28
|
|
|
27
29
|
// the path is part of it: moving a file changes the build even if no content did
|
|
@@ -49,7 +49,9 @@ const streams = async (manifest) => {
|
|
|
49
49
|
if (operation.stream === undefined || operation.bindings.length === 0) continue
|
|
50
50
|
if (await carried(operation.bindings)) continue
|
|
51
51
|
|
|
52
|
-
throw new Error(
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Operation '${endpoint}' takes a stream, which none of its bindings carries`
|
|
54
|
+
)
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
|
|
@@ -65,7 +65,12 @@ async function extractDeclaredServices(context, extensions, contributed) {
|
|
|
65
65
|
return extracted
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
async function extractExtensionComponents(
|
|
68
|
+
async function extractExtensionComponents(
|
|
69
|
+
components,
|
|
70
|
+
extensions,
|
|
71
|
+
contributed,
|
|
72
|
+
annotations
|
|
73
|
+
) {
|
|
69
74
|
const extracted = []
|
|
70
75
|
|
|
71
76
|
for (const component of components) {
|
|
@@ -46,5 +46,6 @@ function unmanaged(context, contributed) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
for (const brought of contributed.values())
|
|
49
|
-
for (const component of brought)
|
|
49
|
+
for (const component of brought)
|
|
50
|
+
if (!required.has(component)) component.evicted = true
|
|
50
51
|
}
|
package/src/.context/evict.js
CHANGED
|
@@ -31,7 +31,9 @@ export const evict = (context) => {
|
|
|
31
31
|
for (const composition of context.compositions) {
|
|
32
32
|
if (composition.services === undefined) continue
|
|
33
33
|
|
|
34
|
-
composition.services = composition.services.filter(
|
|
34
|
+
composition.services = composition.services.filter(
|
|
35
|
+
(reference) => !services.has(reference)
|
|
36
|
+
)
|
|
35
37
|
|
|
36
38
|
if (composition.services.length === 0) delete composition.services
|
|
37
39
|
}
|
package/src/.context/expand.js
CHANGED
|
@@ -15,4 +15,31 @@ export const expand = (context) => {
|
|
|
15
15
|
|
|
16
16
|
if (context.evicted?.services !== undefined)
|
|
17
17
|
context.evicted.services = context.evicted.services.map(shortcuts.resolve)
|
|
18
|
+
|
|
19
|
+
// a component that declares no namespace is written by its name, and is known by its id
|
|
20
|
+
for (const composition of context.compositions ?? [])
|
|
21
|
+
if (Array.isArray(composition.components))
|
|
22
|
+
composition.components = composition.components.map((id) => complete(id, 2))
|
|
23
|
+
|
|
24
|
+
if (Array.isArray(context.evicted?.components))
|
|
25
|
+
context.evicted.components = context.evicted.components.map((id) => complete(id, 2))
|
|
26
|
+
|
|
27
|
+
if (Array.isArray(context.events))
|
|
28
|
+
context.events = context.events.map((event) => complete(event, 3))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `name` is `default.name` where an id has two segments, `name.event` is `default.name.event`
|
|
33
|
+
* where it has three. Anything else is left for the schema to refuse as it is written.
|
|
34
|
+
*
|
|
35
|
+
* @param {unknown} id
|
|
36
|
+
* @param {number} segments how many a complete one has
|
|
37
|
+
*/
|
|
38
|
+
function complete(id, segments) {
|
|
39
|
+
if (typeof id !== 'string' || id.split('.').length !== segments - 1) return id
|
|
40
|
+
|
|
41
|
+
return `${NAMESPACE}.${id}`
|
|
18
42
|
}
|
|
43
|
+
|
|
44
|
+
/** Where a component that declares no namespace is. */
|
|
45
|
+
const NAMESPACE = 'default'
|
package/src/context.js
CHANGED
|
@@ -38,7 +38,9 @@ export const context = async (
|
|
|
38
38
|
validate(context)
|
|
39
39
|
|
|
40
40
|
// a directory is answered with the separator that ends it, and a component's path has none
|
|
41
|
-
const paths = (await glob(resolve(root, COMPONENTS), GLOB)).map((path) =>
|
|
41
|
+
const paths = (await glob(resolve(root, COMPONENTS), GLOB)).map((path) =>
|
|
42
|
+
path.slice(0, -1)
|
|
43
|
+
)
|
|
42
44
|
|
|
43
45
|
context.components = await Promise.all(paths.map(component))
|
|
44
46
|
|
package/src/manifest.js
CHANGED
|
@@ -58,7 +58,11 @@ function walk(manifest, rewrite) {
|
|
|
58
58
|
for (const item of Object.values(manifest[property] ?? {}))
|
|
59
59
|
if (item.path !== undefined) rewrite(item)
|
|
60
60
|
|
|
61
|
-
for (
|
|
61
|
+
for (
|
|
62
|
+
let prototype = manifest.prototype;
|
|
63
|
+
prototype != null;
|
|
64
|
+
prototype = prototype.prototype
|
|
65
|
+
)
|
|
62
66
|
if (prototype.path !== undefined) rewrite(prototype)
|
|
63
67
|
}
|
|
64
68
|
|
|
@@ -73,7 +77,8 @@ const SELF = '.'
|
|
|
73
77
|
|
|
74
78
|
function carried(path, root) {
|
|
75
79
|
if (path === root) return SELF
|
|
76
|
-
if (path.startsWith(root + sep))
|
|
80
|
+
if (path.startsWith(root + sep))
|
|
81
|
+
return SELF + '/' + relative(root, path).split(sep).join('/')
|
|
77
82
|
|
|
78
83
|
return specifier(path)
|
|
79
84
|
}
|
package/src/map.js
CHANGED
|
@@ -20,5 +20,7 @@ export const map = (context) => {
|
|
|
20
20
|
for (const { component } of instances)
|
|
21
21
|
components.set(component.locator.id, contract.component(component))
|
|
22
22
|
|
|
23
|
-
return Object.fromEntries(
|
|
23
|
+
return Object.fromEntries(
|
|
24
|
+
[...components].sort(([one], [other]) => one.localeCompare(other))
|
|
25
|
+
)
|
|
24
26
|
}
|
|
@@ -277,7 +277,9 @@ describe('operations', () => {
|
|
|
277
277
|
it('should refuse the names the scopes had before `entry` and `entries`', async () => {
|
|
278
278
|
for (const scope of ['object', 'objects']) {
|
|
279
279
|
manifest.operations.get.scope = scope
|
|
280
|
-
await assert.rejects(validate(manifest), (error) =>
|
|
280
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
281
|
+
/allowed values/.test(error.message)
|
|
282
|
+
)
|
|
281
283
|
}
|
|
282
284
|
})
|
|
283
285
|
|
|
@@ -309,7 +311,9 @@ describe('operations', () => {
|
|
|
309
311
|
|
|
310
312
|
it('should not be a window shorter than ten minutes', async () => {
|
|
311
313
|
manifest.operations.add.once = 599
|
|
312
|
-
await assert.rejects(validate(manifest), (error) =>
|
|
314
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
315
|
+
/must be >= 600/.test(error.message)
|
|
316
|
+
)
|
|
313
317
|
})
|
|
314
318
|
|
|
315
319
|
it('should not be a fraction of a second', async () => {
|
|
@@ -393,7 +397,9 @@ describe('stateful', () => {
|
|
|
393
397
|
|
|
394
398
|
operation.stateful = 'yes'
|
|
395
399
|
|
|
396
|
-
await assert.rejects(validate(manifest), (error) =>
|
|
400
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
401
|
+
/must be boolean/.test(error.message)
|
|
402
|
+
)
|
|
397
403
|
})
|
|
398
404
|
})
|
|
399
405
|
|
|
@@ -411,7 +417,9 @@ describe('unchained', () => {
|
|
|
411
417
|
|
|
412
418
|
operation.unchained = 'yes'
|
|
413
419
|
|
|
414
|
-
await assert.rejects(validate(manifest), (error) =>
|
|
420
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
421
|
+
/must be boolean/.test(error.message)
|
|
422
|
+
)
|
|
415
423
|
})
|
|
416
424
|
})
|
|
417
425
|
|
package/test/manifest.test.js
CHANGED
|
@@ -35,7 +35,12 @@ describe('a manifest carried through a file', () => {
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
it('does not carry a key with no value', () => {
|
|
38
|
-
const carried = plain({
|
|
38
|
+
const carried = plain({
|
|
39
|
+
name: 'a',
|
|
40
|
+
namespace: 'b',
|
|
41
|
+
path: '/x',
|
|
42
|
+
operations: { echo: { input: undefined } }
|
|
43
|
+
})
|
|
39
44
|
|
|
40
45
|
assert.deepStrictEqual(carried.operations.echo, {})
|
|
41
46
|
})
|
|
@@ -64,7 +69,10 @@ describe('a manifest carried through a file', () => {
|
|
|
64
69
|
|
|
65
70
|
it('reads what is inherited where the prototype is', async () => {
|
|
66
71
|
const prototype = (await component(COMPONENT)).prototype.path
|
|
67
|
-
const revived = revive(
|
|
72
|
+
const revived = revive(
|
|
73
|
+
carry(await component(COMPONENT)),
|
|
74
|
+
'/composition/external-consumer'
|
|
75
|
+
)
|
|
68
76
|
|
|
69
77
|
// an event of the prototype's is in the prototype, wherever the component is
|
|
70
78
|
assert.strictEqual(revived.events.created.path, prototype)
|
|
@@ -95,7 +103,10 @@ describe('what cannot be carried', () => {
|
|
|
95
103
|
it('refuses a prototype an image would not have', async () => {
|
|
96
104
|
const manifest = await component(EXTENDED)
|
|
97
105
|
|
|
98
|
-
assert.throws(
|
|
106
|
+
assert.throws(
|
|
107
|
+
() => plain(manifest),
|
|
108
|
+
/is in no package, so an image cannot carry what is in it/
|
|
109
|
+
)
|
|
99
110
|
})
|
|
100
111
|
|
|
101
112
|
it('refuses a path inside the component it does not know about', () => {
|
|
@@ -113,7 +124,7 @@ describe('what cannot be carried', () => {
|
|
|
113
124
|
assert.throws(() => plain(manifest), /carries a path of the machine that read it/)
|
|
114
125
|
})
|
|
115
126
|
|
|
116
|
-
it(
|
|
127
|
+
it("leaves a value of the application's own alone", () => {
|
|
117
128
|
const carried = plain({
|
|
118
129
|
name: 'a',
|
|
119
130
|
namespace: 'b',
|