@toa.io/norm 1.0.0-alpha.326 → 1.0.0-alpha.328

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 CHANGED
@@ -3,6 +3,21 @@
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.328](https://github.com/toa-io/toa/compare/v1.0.0-alpha.327...v1.0.0-alpha.328) (2026-10-10)
7
+
8
+ **Note:** Version bump only for package @toa.io/norm
9
+
10
+
11
+
12
+
13
+
14
+ # [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)
15
+
16
+ ### Features
17
+
18
+ * name a component without a namespace by its name ([abcdd9c](https://github.com/toa-io/toa/commit/abcdd9c9a3d3c5c1c68b74544791848e59a7e158))
19
+
20
+
6
21
  # [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
22
 
8
23
  * 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.326",
3
+ "version": "1.0.0-alpha.328",
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.326",
25
- "@toa.io/definitions": "1.0.0-alpha.326",
24
+ "@toa.io/core": "1.0.0-alpha.328",
25
+ "@toa.io/definitions": "1.0.0-alpha.328",
26
26
  "@toa.io/generic": "1.0.0-alpha.311",
27
- "@toa.io/prototype": "1.0.0-alpha.323",
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": "60f11bdc805d0c39bcb06064b2218b11114a35ad"
31
+ "gitHead": "c4fcb94548759b069014187ef4fa238179d2eca3"
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(files.map((file) => digest(join(manifest.path, file))))
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(`Operation '${endpoint}' takes a stream, which none of its bindings carries`)
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(components, extensions, contributed, annotations) {
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) if (!required.has(component)) component.evicted = true
49
+ for (const component of brought)
50
+ if (!required.has(component)) component.evicted = true
50
51
  }
@@ -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((reference) => !services.has(reference))
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
  }
@@ -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) => path.slice(0, -1))
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 (let prototype = manifest.prototype; prototype != null; prototype = prototype.prototype)
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)) return SELF + '/' + relative(root, path).split(sep).join('/')
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([...components].sort(([one], [other]) => one.localeCompare(other)))
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) => /allowed values/.test(error.message))
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) => /must be >= 600/.test(error.message))
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) => /must be boolean/.test(error.message))
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) => /must be boolean/.test(error.message))
420
+ await assert.rejects(validate(manifest), (error) =>
421
+ /must be boolean/.test(error.message)
422
+ )
415
423
  })
416
424
  })
417
425
 
@@ -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({ name: 'a', namespace: 'b', path: '/x', operations: { echo: { input: undefined } } })
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(carry(await component(COMPONENT)), '/composition/external-consumer')
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(() => plain(manifest), /is in no package, so an image cannot carry what is in it/)
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('leaves a value of the application\'s own alone', () => {
127
+ it("leaves a value of the application's own alone", () => {
117
128
  const carried = plain({
118
129
  name: 'a',
119
130
  namespace: 'b',