@toa.io/norm 1.0.0-alpha.300 → 1.0.0-alpha.302

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,26 @@
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.302](https://github.com/toa-io/toa/compare/v1.0.0-alpha.301...v1.0.0-alpha.302) (2026-09-11)
7
+
8
+ **Note:** Version bump only for package @toa.io/norm
9
+
10
+
11
+
12
+
13
+
14
+ # [1.0.0-alpha.301](https://github.com/toa-io/toa/compare/v1.0.0-alpha.300...v1.0.0-alpha.301) (2026-09-11)
15
+
16
+ ### Bug Fixes
17
+
18
+ * **norm:** an operation that takes no input states none ([3b69b4c](https://github.com/toa-io/toa/commit/3b69b4c92b3f06361a3552f036a927f4ff32e58b))
19
+
20
+ ### Features
21
+
22
+ * **norm:** an environment name may fall back along a colon-separated chain ([a267c1c](https://github.com/toa-io/toa/commit/a267c1caae816a75ada8352d5361b99817c72eb1))
23
+ * **operations:** an evicted component is given its environment and configuration ([6cf7890](https://github.com/toa-io/toa/commit/6cf7890a632a2185d2b7d9790890a04187e5a897))
24
+
25
+
6
26
  # [1.0.0-alpha.300](https://github.com/toa-io/toa/compare/v1.0.0-alpha.299...v1.0.0-alpha.300) (2026-09-11)
7
27
 
8
28
  * feat(boot)!: a process is a thing, and an extension may keep something in one ([528d6cd](https://github.com/toa-io/toa/commit/528d6cdd307b45a6c30b6a184d7e06b3328cecf8))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/norm",
3
- "version": "1.0.0-alpha.300",
3
+ "version": "1.0.0-alpha.302",
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.300",
25
- "@toa.io/definitions": "1.0.0-alpha.300",
26
- "@toa.io/generic": "1.0.0-alpha.292",
27
- "@toa.io/prototype": "1.0.0-alpha.300",
28
- "@toa.io/schemas": "1.0.0-alpha.299",
24
+ "@toa.io/core": "1.0.0-alpha.302",
25
+ "@toa.io/definitions": "1.0.0-alpha.302",
26
+ "@toa.io/generic": "1.0.0-alpha.301",
27
+ "@toa.io/prototype": "1.0.0-alpha.301",
28
+ "@toa.io/schemas": "1.0.0-alpha.301",
29
29
  "fast-glob": "3.3.3"
30
30
  },
31
- "gitHead": "f91131a9aeb29aa271f177c4055ddcf0cda8c75e"
31
+ "gitHead": "3cbbca55a61711db78f98b13e50cf4d1afaf5c08"
32
32
  }
@@ -13,6 +13,9 @@ export const operations = (component) => {
13
13
  // an operation that states no output states an empty schema, which every reply fits
14
14
  operation.output ??= {}
15
15
 
16
+ // a bridge says an algorithm takes no input with `null`, which the manifest states as none
17
+ if (operation.input === null) delete operation.input
18
+
16
19
  if (operation.bindings === undefined) operation.bindings = component.bindings
17
20
  if (operation.bindings === null) operation.bindings = []
18
21
  if (operation.virtual === true) delete component.operations[endpoint]
@@ -1,36 +1,8 @@
1
1
  import { merge } from '@toa.io/generic'
2
2
 
3
3
  export const dereference = (manifest) => {
4
- // schemas
5
- const properties = manifest.entity?.properties
6
- const resolver = createResolver(properties)
4
+ if (!('operations' in manifest)) return
7
5
 
8
- if (properties !== undefined)
9
- for (const [name, property] of Object.entries(properties))
10
- properties[name] = schema(property, resolver)
11
-
12
- if ('operations' in manifest) operations(manifest, resolver)
13
- }
14
-
15
- const createResolver = (properties) => (property) => {
16
- if (properties?.[property] === undefined) {
17
- throw new Error(`Referenced property '${property}' is not defined`)
18
- }
19
-
20
- return properties[property]
21
- }
22
-
23
- function operations(manifest, resolver) {
24
- for (const operation of Object.values(manifest.operations)) {
25
- if (operation.input !== undefined) operation.input = schema(operation.input, resolver)
26
-
27
- if (operation.output !== undefined)
28
- if (Array.isArray(operation.output) && operation.output.length === 1)
29
- operation.output = [schema(operation.output[0], resolver)]
30
- else operation.output = schema(operation.output, resolver)
31
- }
32
-
33
- // forwarding
34
6
  for (const operation of Object.values(manifest.operations)) {
35
7
  if (operation.forward !== undefined) forward(operation, manifest.operations)
36
8
  }
@@ -40,24 +12,6 @@ function operations(manifest, resolver) {
40
12
  }
41
13
  }
42
14
 
43
- const schema = (object, resolve) => {
44
- if (object === undefined || object === null || typeof object !== 'object') return
45
- if (object.type === 'string' && object.default?.[0] === '.')
46
- return resolve(object.default.substring(1))
47
-
48
- if (object.type === 'array') {
49
- object.items = schema(object.items, resolve)
50
- } else if (object.properties !== undefined) {
51
- for (const [name, value] of Object.entries(object.properties)) {
52
- if (value?.type === 'string' && value.default === '.')
53
- object.properties[name] = resolve(name)
54
- else object.properties[name] = schema(value, resolve)
55
- }
56
- }
57
-
58
- return object
59
- }
60
-
61
15
  const forward = (operation, operations) => {
62
16
  const target = operations[operation.forward]
63
17
 
@@ -1,7 +1,7 @@
1
- import { merge } from '@toa.io/generic'
1
+ import { merge as combine } from '@toa.io/generic'
2
2
  import { definition } from '../definition.js'
3
3
 
4
- const bridge = async (root, manifest) => {
4
+ export const merge = async (root, manifest) => {
5
5
  await Promise.all([
6
6
  define(root, manifest, 'operations'),
7
7
  define(root, manifest, 'events'),
@@ -22,7 +22,7 @@ const define = async (root, manifest, property) => {
22
22
  const { define } = await bridged(bridge)
23
23
  const definition = await define[singular](root, endpoint)
24
24
 
25
- merge(item, definition)
25
+ combine(item, definition)
26
26
  }
27
27
  }
28
28
 
@@ -45,7 +45,7 @@ const define = async (root, manifest, property) => {
45
45
 
46
46
  try {
47
47
  if (declared === undefined) manifest[property][endpoint] = item
48
- else merge(declared, item)
48
+ else combine(declared, item)
49
49
  } catch (error) {
50
50
  console.error(`Error merging ${singular} '${endpoint}' in ${root}`)
51
51
  throw error
@@ -69,5 +69,3 @@ async function bridged(reference) {
69
69
 
70
70
  return module
71
71
  }
72
-
73
- export { bridge as merge }
package/src/context.js CHANGED
@@ -16,11 +16,20 @@ import {
16
16
  validate
17
17
  } from './.context/index.js'
18
18
 
19
- export const context = async (root, environment = variables.get('TOA_ENV')) => {
19
+ export const context = async (
20
+ root,
21
+ environment = variables.get('TOA_ENV'),
22
+ options = {}
23
+ ) => {
20
24
  const path = resolve(root, CONTEXT)
21
25
  const context = /** @type {toa.norm.Context} */ await read(path)
22
26
 
23
- context.environment = environment
27
+ const names = environment?.split(':')
28
+
29
+ if (names !== undefined && names.length > 1 && names.some((name) => name.length === 0))
30
+ throw new Error(`Environment '${environment}' contains an empty name.`)
31
+
32
+ context.environment = names?.[0]
24
33
 
25
34
  convolve(context, environment)
26
35
  expand(context)
@@ -32,8 +41,9 @@ export const context = async (root, environment = variables.get('TOA_ENV')) => {
32
41
 
33
42
  context.components = await Promise.all(paths.map(component))
34
43
 
35
- // before the dependencies, which mark what only evicted components require
36
- evict(context)
44
+ // before the dependencies, which mark what only evicted components require.
45
+ // `evicted: false` leaves that off: a local run of a component still needs its variables
46
+ if (options.evicted !== false) evict(context)
37
47
 
38
48
  // what a context declares of every component that stores anything, its own and the ones its
39
49
  // extensions bring, is given to them where those are known: inside `dependencies`
@@ -3,10 +3,6 @@ export const source = {
3
3
  properties: {
4
4
  foo: {
5
5
  type: 'string'
6
- },
7
- bar: {
8
- type: 'string',
9
- default: '.foo'
10
6
  }
11
7
  }
12
8
  },
@@ -15,42 +11,14 @@ export const source = {
15
11
  input: {
16
12
  properties: {
17
13
  foo: {
18
- type: 'string',
19
- default: '.'
20
- },
21
- bar: {
22
- type: 'string',
23
- default: '.foo'
24
- },
25
- baz1: {
26
- type: 'array',
27
- items: {
28
- type: 'string',
29
- default: '.foo'
30
- }
31
- },
32
- baz2: {
33
- type: 'array',
34
- items: {
35
- properties: {
36
- foo: {
37
- type: 'string',
38
- default: '.'
39
- },
40
- bar: {
41
- type: 'string',
42
- default: '.foo'
43
- }
44
- }
45
- }
14
+ type: 'string'
46
15
  }
47
16
  }
48
17
  },
49
18
  output: {
50
19
  properties: {
51
20
  bar: {
52
- type: 'string',
53
- default: '.foo'
21
+ type: 'string'
54
22
  }
55
23
  }
56
24
  }
@@ -67,9 +35,6 @@ export const target = {
67
35
  properties: {
68
36
  foo: {
69
37
  type: 'string'
70
- },
71
- bar: {
72
- type: 'string'
73
38
  }
74
39
  }
75
40
  },
@@ -79,28 +44,6 @@ export const target = {
79
44
  properties: {
80
45
  foo: {
81
46
  type: 'string'
82
- },
83
- bar: {
84
- type: 'string'
85
- },
86
- baz1: {
87
- type: 'array',
88
- items: {
89
- type: 'string'
90
- }
91
- },
92
- baz2: {
93
- type: 'array',
94
- items: {
95
- properties: {
96
- foo: {
97
- type: 'string'
98
- },
99
- bar: {
100
- type: 'string'
101
- }
102
- }
103
- }
104
47
  }
105
48
  }
106
49
  },
@@ -119,28 +62,6 @@ export const target = {
119
62
  properties: {
120
63
  foo: {
121
64
  type: 'string'
122
- },
123
- bar: {
124
- type: 'string'
125
- },
126
- baz1: {
127
- type: 'array',
128
- items: {
129
- type: 'string'
130
- }
131
- },
132
- baz2: {
133
- type: 'array',
134
- items: {
135
- properties: {
136
- foo: {
137
- type: 'string'
138
- },
139
- bar: {
140
- type: 'string'
141
- }
142
- }
143
- }
144
65
  }
145
66
  }
146
67
  },
@@ -18,20 +18,6 @@ it('should dereference', () => {
18
18
  assert.deepStrictEqual(source, fixtures.target)
19
19
  })
20
20
 
21
- it('should throw on invalid schema reference', () => {
22
- source.operations.transit.output.properties.baz = { type: 'string', default: '.' }
23
- assert.throws(
24
- () => dereference(source),
25
- (error) => /is not defined/.test(error.message)
26
- )
27
-
28
- source.operations.transit.output.properties.baz = { type: 'string', default: '.baz' }
29
- assert.throws(
30
- () => dereference(source),
31
- (error) => /is not defined/.test(error.message)
32
- )
33
- })
34
-
35
21
  it('should throw on invalid forwarding', () => {
36
22
  source.operations.create.forward = 'foo'
37
23
  assert.throws(
@@ -25,6 +25,14 @@ describe('operations', () => {
25
25
  assert.deepStrictEqual(manifest.operations.add.output, {})
26
26
  })
27
27
 
28
+ it('should state no input for an operation that takes none', async () => {
29
+ manifest.operations.compute = { type: 'computation', input: null }
30
+
31
+ await normalize(manifest)
32
+
33
+ assert.equal('input' in manifest.operations.compute, false)
34
+ })
35
+
28
36
  it('should scope a computation to none', async () => {
29
37
  manifest.operations.compute = { type: 'computation' }
30
38
 
@@ -1,4 +1,4 @@
1
- import { Registry, Runtime } from '../context.js'
1
+ import { Registry, Runtime } from '../context.d.ts'
2
2
 
3
3
  /** A composition as declared, before its members are resolved. */
4
4
  interface Composition {
@@ -1,6 +1,6 @@
1
- import { Manifest } from './component.js'
1
+ import { Manifest } from './component.d.ts'
2
2
  import { Locator } from '@toa.io/core'
3
- import type { Declaration } from './context/declaration.js'
3
+ import type { Declaration } from './context/declaration.d.ts'
4
4
 
5
5
  interface Runtime {
6
6
  version: string
@@ -44,4 +44,11 @@ interface Context extends Declaration {
44
44
  packages?: Record<string, string>
45
45
  }
46
46
 
47
- export function context(path: string, environment?: string): Promise<Context>
47
+ export function context(
48
+ path: string,
49
+ environment?: string,
50
+ options?: {
51
+ /** `false` does not apply eviction: every component is treated as deployed */
52
+ evicted?: boolean
53
+ }
54
+ ): Promise<Context>
package/types/entity.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Entity } from './component.js'
1
+ import type { Entity } from './component.d.ts'
2
2
 
3
3
  /** The schema a stored record must fit. */
4
4
  export function schema(entity: Entity): object
package/types/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export * as context from './context.js'
2
- export * as component from './component.js'
1
+ export * as context from './context.d.ts'
2
+ export * as component from './component.d.ts'
3
3
 
4
- export { Manifest } from './component.js'
5
- export * as entity from './entity.js'
4
+ export { Manifest } from './component.d.ts'
5
+ export * as entity from './entity.d.ts'
6
6
 
7
7
  export interface Definition {
8
8
  /** The package's name, or the reference where it has no package.json */