@toa.io/norm 1.0.0-alpha.30 → 1.0.0-alpha.301

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 (98) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/package.json +9 -7
  3. package/src/.component/.expand/bridge.js +2 -6
  4. package/src/.component/.expand/entity.js +2 -9
  5. package/src/.component/.expand/events.js +2 -6
  6. package/src/.component/.expand/extensions.js +5 -8
  7. package/src/.component/.expand/index.js +7 -19
  8. package/src/.component/.expand/operations.js +2 -9
  9. package/src/.component/.expand/receivers.js +4 -7
  10. package/src/.component/.expand/version.js +38 -28
  11. package/src/.component/.normalize/entity.js +7 -0
  12. package/src/.component/.normalize/events.js +16 -4
  13. package/src/.component/.normalize/index.js +4 -9
  14. package/src/.component/.normalize/operations.js +13 -6
  15. package/src/.component/.normalize/receivers.js +4 -5
  16. package/src/.component/collapse.js +60 -9
  17. package/src/.component/defaults.js +16 -10
  18. package/src/.component/dependencies.js +14 -10
  19. package/src/.component/dereference.js +5 -49
  20. package/src/.component/expand.js +3 -9
  21. package/src/.component/extensions.js +9 -10
  22. package/src/.component/index.js +10 -21
  23. package/src/.component/merge.js +25 -15
  24. package/src/.component/migrations.js +57 -0
  25. package/src/.component/normalize.js +10 -6
  26. package/src/.component/schema.yaml +137 -73
  27. package/src/.component/validate.js +37 -17
  28. package/src/.context/.dependencies/connectors.js +5 -9
  29. package/src/.context/.dependencies/describe.js +4 -5
  30. package/src/.context/.dependencies/extensions.js +93 -20
  31. package/src/.context/.dependencies/index.js +3 -9
  32. package/src/.context/.dependencies/resolve.js +44 -7
  33. package/src/.context/complete.js +1 -5
  34. package/src/.context/converge.js +31 -0
  35. package/src/.context/dependencies.js +43 -6
  36. package/src/.context/dereference.js +11 -6
  37. package/src/.context/evict.js +38 -0
  38. package/src/.context/expand.js +11 -6
  39. package/src/.context/index.js +8 -15
  40. package/src/.context/normalize.js +9 -7
  41. package/src/.context/schema.yaml +178 -11
  42. package/src/.context/validate.js +8 -14
  43. package/src/component.js +36 -17
  44. package/src/context.js +44 -15
  45. package/src/definition.js +56 -0
  46. package/src/entity.js +15 -0
  47. package/src/index.js +6 -9
  48. package/src/manifest.js +154 -0
  49. package/src/shortcuts.js +9 -14
  50. package/test/component/collapse.fixtures.js +30 -39
  51. package/test/component/collapse.test.js +90 -10
  52. package/test/component/dependencies.test.js +16 -14
  53. package/test/component/dereference.fixtures.js +10 -98
  54. package/test/component/dereference.test.js +10 -14
  55. package/test/component/dummies/extension/index.js +1 -5
  56. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
  57. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
  58. package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
  59. package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
  60. package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
  61. package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
  62. package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
  63. package/test/component/expand.fixtures.js +13 -13
  64. package/test/component/expand.test.js +17 -13
  65. package/test/component/migrations.test.js +62 -0
  66. package/test/component/normalize.fixtures.js +3 -7
  67. package/test/component/normalize.test.js +94 -28
  68. package/test/component/validate.fixtures.js +15 -13
  69. package/test/component/validate.test.js +180 -109
  70. package/test/context/complete.fixtures.js +2 -8
  71. package/test/context/complete.test.js +14 -8
  72. package/test/context/converge.test.js +57 -0
  73. package/test/context/dereference.fixtures.js +3 -9
  74. package/test/context/dereference.test.js +18 -7
  75. package/test/context/evict.fixtures.js +39 -0
  76. package/test/context/evict.test.js +72 -0
  77. package/test/context/expand.fixtures.js +1 -5
  78. package/test/context/expand.test.js +36 -8
  79. package/test/context/normalize.fixtures.js +2 -7
  80. package/test/context/normalize.test.js +33 -9
  81. package/test/context/validate.fixtures.js +1 -6
  82. package/test/context/validate.test.js +143 -35
  83. package/test/definition.test.js +35 -0
  84. package/test/dummies/bare/index.js +1 -0
  85. package/test/dummies/defined/definition.js +1 -0
  86. package/test/dummies/defined/index.js +1 -0
  87. package/test/dummies/defined/package.json +1 -0
  88. package/test/manifest.test.js +126 -0
  89. package/test/shortcuts.fixtures.js +4 -9
  90. package/test/shortcuts.test.js +17 -15
  91. package/types/component.d.ts +23 -4
  92. package/types/context/declaration.d.ts +33 -3
  93. package/types/context.d.ts +26 -13
  94. package/types/entity.d.ts +7 -0
  95. package/types/index.d.ts +12 -3
  96. package/src/.component/.expand/properties.js +0 -14
  97. package/src/.context/.dependencies/load.js +0 -36
  98. package/test/context/dependencies.load.test.js +0 -24
package/CHANGELOG.md ADDED
@@ -0,0 +1,136 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ # [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)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **norm:** an operation that takes no input states none ([3b69b4c](https://github.com/toa-io/toa/commit/3b69b4c92b3f06361a3552f036a927f4ff32e58b))
11
+
12
+ ### Features
13
+
14
+ * **norm:** an environment name may fall back along a colon-separated chain ([a267c1c](https://github.com/toa-io/toa/commit/a267c1caae816a75ada8352d5361b99817c72eb1))
15
+ * **operations:** an evicted component is given its environment and configuration ([6cf7890](https://github.com/toa-io/toa/commit/6cf7890a632a2185d2b7d9790890a04187e5a897))
16
+
17
+
18
+ # [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)
19
+
20
+ * feat(boot)!: a process is a thing, and an extension may keep something in one ([528d6cd](https://github.com/toa-io/toa/commit/528d6cdd307b45a6c30b6a184d7e06b3328cecf8))
21
+ * feat(core)!: an operation may ask to run once ([dca616c](https://github.com/toa-io/toa/commit/dca616cb380a7a389e277875d3bd5ca55a6e4d45))
22
+
23
+ ### Features
24
+
25
+ * **core:** a call may name the process it goes to, and wait for a set time ([7d8adb2](https://github.com/toa-io/toa/commit/7d8adb2772a95ff45ae5374efdedfb5a857fbbe9))
26
+ * **core:** an assignment may ask to run once as well ([10fbcbd](https://github.com/toa-io/toa/commit/10fbcbd86a5e82960e65f62c09d0a465ae8d0353))
27
+ * **norm:** a context says what it does not deploy ([135e6fd](https://github.com/toa-io/toa/commit/135e6fdfbc4270762028e9e5a0516766960ce59c))
28
+
29
+ ### BREAKING CHANGES
30
+
31
+ * `Storage.store`, `.upsert` and `.ensure` take one more
32
+ argument, and `Storage` has two more members. A connector that ignores them
33
+ works exactly as it did; a component that asks it for `once` is refused at
34
+ boot. See migrations/299.md.
35
+ * a composition booted directly has no readiness probe. Readiness is
36
+ the process's, and a composition is not a process. `stage.workload` boots one where a
37
+ suite needs it.
38
+
39
+
40
+ # [1.0.0-alpha.299](https://github.com/toa-io/toa/compare/v1.0.0-alpha.298...v1.0.0-alpha.299) (2026-09-10)
41
+
42
+ ### Features
43
+
44
+ * **convergence:** deployments of one context converge ([8a70343](https://github.com/toa-io/toa/commit/8a70343cf6953aab58d4a25bac215508ff8d9a37))
45
+ * **convergence:** what an extension ships converges too ([b3be33d](https://github.com/toa-io/toa/commit/b3be33d2a1db45cfd844288467441aea2f4a722d))
46
+ * **core:** a record carries the region that wrote it ([13ed187](https://github.com/toa-io/toa/commit/13ed187604f06dec66feec2e26ccf9dae30bb048))
47
+
48
+
49
+ # [1.0.0-alpha.298](https://github.com/toa-io/toa/compare/v1.0.0-alpha.297...v1.0.0-alpha.298) (2026-09-08)
50
+
51
+ **Note:** Version bump only for package @toa.io/norm
52
+
53
+
54
+
55
+
56
+
57
+ # [1.0.0-alpha.297](https://github.com/toa-io/toa/compare/v1.0.0-alpha.296...v1.0.0-alpha.297) (2026-09-08)
58
+
59
+ **Note:** Version bump only for package @toa.io/norm
60
+
61
+
62
+
63
+
64
+
65
+ # [1.0.0-alpha.296](https://github.com/toa-io/toa/compare/v1.0.0-alpha.295...v1.0.0-alpha.296) (2026-09-08)
66
+
67
+ **Note:** Version bump only for package @toa.io/norm
68
+
69
+
70
+
71
+
72
+
73
+ # [1.0.0-alpha.295](https://github.com/toa-io/toa/compare/v1.0.0-alpha.294...v1.0.0-alpha.295) (2026-09-07)
74
+
75
+ **Note:** Version bump only for package @toa.io/norm
76
+
77
+
78
+
79
+
80
+
81
+ # [1.0.0-alpha.294](https://github.com/toa-io/toa/compare/v1.0.0-alpha.293...v1.0.0-alpha.294) (2026-09-07)
82
+
83
+ **Note:** Version bump only for package @toa.io/norm
84
+
85
+
86
+
87
+
88
+
89
+ # [1.0.0-alpha.293](https://github.com/toa-io/toa/compare/v1.0.0-alpha.292...v1.0.0-alpha.293) (2026-09-07)
90
+
91
+ **Note:** Version bump only for package @toa.io/norm
92
+
93
+
94
+
95
+
96
+
97
+ # [1.0.0-alpha.292](https://github.com/toa-io/toa/compare/v1.0.0-alpha.291...v1.0.0-alpha.292) (2026-09-07)
98
+
99
+ ### Features
100
+
101
+ * **cli:** the deployment library is a peer, and the binary is the CLI's ([a923f81](https://github.com/toa-io/toa/commit/a923f8173a55ed1c091213a7c7db1863ca1d0ba6))
102
+ * **definitions:** the version of Toa is the definitions package's ([3d0272e](https://github.com/toa-io/toa/commit/3d0272ea0ab862f4b6c2e58a2f003f92d0f8a87f))
103
+ * **definitions:** what a package declares is read from one package ([7052341](https://github.com/toa-io/toa/commit/70523411b5d9e2b204c999aa02365173f3bb518d))
104
+ * **norm:** a package's definition is read by one resolver ([bffd41c](https://github.com/toa-io/toa/commit/bffd41c8d18c42786b0bf321623088b9dabc4724))
105
+
106
+
107
+ # [1.0.0-alpha.291](https://github.com/toa-io/toa/compare/v1.0.0-alpha.290...v1.0.0-alpha.291) (2026-09-07)
108
+
109
+ **Note:** Version bump only for package @toa.io/norm
110
+
111
+
112
+
113
+
114
+
115
+ # [1.0.0-alpha.289](https://github.com/toa-io/toa/compare/v1.0.0-alpha.288...v1.0.0-alpha.289) (2026-09-07)
116
+
117
+ * A deploy moves only what changed, and a component sees none of the runtime's environment (#1073) ([f38e3db](https://github.com/toa-io/toa/commit/f38e3db533f24db866c57bfa1ef294eff1eaf499)), closes [#1073](https://github.com/toa-io/toa/issues/1073) [#1064](https://github.com/toa-io/toa/issues/1064) [#1066](https://github.com/toa-io/toa/issues/1066) [#1067](https://github.com/toa-io/toa/issues/1067) [#1068](https://github.com/toa-io/toa/issues/1068) [#1069](https://github.com/toa-io/toa/issues/1069) [#1071](https://github.com/toa-io/toa/issues/1071) [#1070](https://github.com/toa-io/toa/issues/1070) [#1072](https://github.com/toa-io/toa/issues/1072)
118
+
119
+ ### BREAKING CHANGES
120
+
121
+ * a component that read `process.env.TOA_*` reads `context` instead;
122
+ `echo(input)` no longer substitutes from the environment; a bash operation sees no
123
+ `TOA_*`; images no longer set `USER node` — see migrations/289.md.
124
+
125
+
126
+ # [1.0.0-alpha.288](https://github.com/toa-io/toa/compare/v1.0.0-alpha.287...v1.0.0-alpha.288) (2026-09-06)
127
+
128
+ **Note:** Version bump only for package @toa.io/norm
129
+
130
+
131
+
132
+
133
+
134
+ # [1.0.0-alpha.287](https://github.com/toa-io/toa/compare/v1.0.0-alpha.286...v1.0.0-alpha.287) (2026-09-06)
135
+
136
+ **Note:** Version bump only for package @toa.io/norm
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@toa.io/norm",
3
- "version": "1.0.0-alpha.30",
3
+ "version": "1.0.0-alpha.301",
4
+ "type": "module",
4
5
  "description": "Toa declarations normalization and validation",
5
6
  "author": "temich <tema.gurtovoy@gmail.com>",
6
7
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -20,11 +21,12 @@
20
21
  "test": "echo \"Error: run tests from root\" && exit 1"
21
22
  },
22
23
  "dependencies": {
23
- "@toa.io/core": "1.0.0-alpha.30",
24
- "@toa.io/generic": "1.0.0-alpha.30",
25
- "@toa.io/schema": "1.0.0-alpha.30",
26
- "@toa.io/schemas": "1.0.0-alpha.30",
27
- "@toa.io/yaml": "1.0.0-alpha.30"
24
+ "@toa.io/core": "1.0.0-alpha.301",
25
+ "@toa.io/definitions": "1.0.0-alpha.301",
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
+ "fast-glob": "3.3.3"
28
30
  },
29
- "gitHead": "29cc19358f35fbe9229dced979724f7a26538013"
31
+ "gitHead": "cc49bc90efd35464b2ad7803246786c607a44685"
30
32
  }
@@ -1,9 +1,5 @@
1
- 'use strict'
1
+ import { resolve } from '../../shortcuts.js'
2
2
 
3
- const { resolve } = require('../../shortcuts')
4
-
5
- function bridge (manifest) {
3
+ export function bridge(manifest) {
6
4
  manifest.bridge = resolve(manifest.bridge)
7
5
  }
8
-
9
- exports.bridge = bridge
@@ -1,14 +1,7 @@
1
- 'use strict'
1
+ import { resolve } from '../../shortcuts.js'
2
2
 
3
- const { expand } = require('@toa.io/schemas')
4
-
5
- const { resolve } = require('../../shortcuts')
6
-
7
- function entity (manifest) {
3
+ export function entity(manifest) {
8
4
  if (!('entity' in manifest)) return
9
- if ('schema' in manifest.entity) manifest.entity.schema = expand(manifest.entity.schema)
10
5
 
11
6
  manifest.entity.storage = resolve(manifest.entity.storage)
12
7
  }
13
-
14
- exports.entity = entity
@@ -1,8 +1,6 @@
1
- 'use strict'
1
+ import { resolve } from '../../shortcuts.js'
2
2
 
3
- const { resolve } = require('../../shortcuts')
4
-
5
- function events (manifest) {
3
+ export function events(manifest) {
6
4
  if (manifest.events === undefined) return
7
5
 
8
6
  for (const event of Object.values(manifest.events)) {
@@ -10,5 +8,3 @@ function events (manifest) {
10
8
  if (event.bridge !== undefined) event.bridge = resolve(event.bridge)
11
9
  }
12
10
  }
13
-
14
- exports.events = events
@@ -1,8 +1,6 @@
1
- 'use strict'
1
+ import { recognize } from '../../shortcuts.js'
2
2
 
3
- const { recognize } = require('../../shortcuts')
4
-
5
- function extensions (manifest) {
3
+ export function extensions(manifest) {
6
4
  recognize(SHORTCUTS, manifest, 'extensions')
7
5
  recognize(SHORTCUTS, manifest.extensions)
8
6
  }
@@ -10,11 +8,10 @@ function extensions (manifest) {
10
8
  const SHORTCUTS = {
11
9
  exposition: '@toa.io/extensions.exposition',
12
10
  realtime: '@toa.io/extensions.realtime',
13
- origins: '@toa.io/extensions.origins',
14
11
  configuration: '@toa.io/extensions.configuration',
15
12
  state: '@toa.io/extensions.state',
16
13
  stash: '@toa.io/extensions.stash',
17
- storages: '@toa.io/extensions.storages'
14
+ storages: '@toa.io/extensions.storages',
15
+ cadence: '@toa.io/extensions.cadence',
16
+ introspection: '@toa.io/extensions.introspection'
18
17
  }
19
-
20
- exports.extensions = extensions
@@ -1,19 +1,7 @@
1
- 'use strict'
2
-
3
- const { bridge } = require('./bridge')
4
- const { entity } = require('./entity')
5
- const { events } = require('./events')
6
- const { extensions } = require('./extensions')
7
- const { operations } = require('./operations')
8
- const { properties } = require('./properties')
9
- const { receivers } = require('./receivers')
10
- const { version } = require('./version')
11
-
12
- exports.bridge = bridge
13
- exports.entity = entity
14
- exports.events = events
15
- exports.extensions = extensions
16
- exports.operations = operations
17
- exports.properties = properties
18
- exports.receivers = receivers
19
- exports.version = version
1
+ export { bridge } from './bridge.js'
2
+ export { entity } from './entity.js'
3
+ export { events } from './events.js'
4
+ export { extensions } from './extensions.js'
5
+ export { operations } from './operations.js'
6
+ export { receivers } from './receivers.js'
7
+ export { version } from './version.js'
@@ -1,14 +1,9 @@
1
- 'use strict'
1
+ import { resolve } from '../../shortcuts.js'
2
2
 
3
- const { expand } = require('@toa.io/schemas')
4
- const { resolve } = require('../../shortcuts')
5
-
6
- function operations (manifest) {
3
+ export function operations(manifest) {
7
4
  if (manifest.operations === undefined) return
8
5
 
9
6
  for (const operation of Object.values(manifest.operations)) {
10
- if (operation.input !== undefined) operation.input = expand(operation.input)
11
- if (operation.output !== undefined) operation.output = expand(operation.output)
12
7
  if (operation.bridge !== undefined) operation.bridge = resolve(operation.bridge)
13
8
 
14
9
  if (operation.bindings !== undefined && operation.bindings !== null) {
@@ -16,5 +11,3 @@ function operations (manifest) {
16
11
  }
17
12
  }
18
13
  }
19
-
20
- exports.operations = operations
@@ -1,16 +1,13 @@
1
- 'use strict'
1
+ import { resolve } from '../../shortcuts.js'
2
2
 
3
- const { resolve } = require('../../shortcuts')
4
-
5
- function receivers (manifest) {
3
+ export function receivers(manifest) {
6
4
  if (manifest.receivers === undefined) return
7
5
 
8
6
  for (const [locator, receiver] of Object.entries(manifest.receivers)) {
9
- if (typeof receiver === 'string') manifest.receivers[locator] = { operation: receiver }
7
+ if (typeof receiver === 'string')
8
+ manifest.receivers[locator] = { operation: receiver }
10
9
 
11
10
  if (receiver.binding !== undefined) receiver.binding = resolve(receiver.binding)
12
11
  if (receiver.bridge !== undefined) receiver.bridge = resolve(receiver.bridge)
13
12
  }
14
13
  }
15
-
16
- exports.receivers = receivers
@@ -1,45 +1,55 @@
1
- 'use strict'
1
+ import { join } from 'node:path'
2
+ import { createHash } from 'node:crypto'
3
+ import fs from 'node:fs/promises'
2
4
 
3
- const { join } = require('node:path')
4
- const { createHash } = require('node:crypto')
5
- const fs = require('node:fs/promises')
6
- const { createReadStream } = require('node:fs')
7
- const { once } = require('node:events')
8
-
9
- async function version (manifest) {
5
+ export async function version(manifest) {
10
6
  manifest.version ??= await hash(manifest.path)
11
7
  }
12
8
 
13
- async function hash (path) {
14
- const hash = await hashd(path)
9
+ /**
10
+ * Identifies a build of a component: it becomes the tag of its image, so it must be the
11
+ * same for the same sources and different for different ones — on any machine.
12
+ *
13
+ * What the image excludes cannot change it: `node_modules` is in the `.dockerignore` the
14
+ * build writes (see `operations/src/deployment/images/image.js`), so hashing it would only
15
+ * retag an identical image after a local install.
16
+ */
17
+ async function hash(path) {
18
+ const files = (await list(path)).sort()
19
+ const digests = await Promise.all(files.map((file) => digest(join(path, file))))
20
+ const total = createHash('sha256')
21
+
22
+ // the path is part of it: moving a file changes the build even if no content did
23
+ for (let i = 0; i < files.length; i++) total.update(files[i]).update(digests[i])
15
24
 
16
- return hash.digest('hex').slice(0, 8)
25
+ return total.digest('hex').slice(0, 8)
17
26
  }
18
27
 
19
28
  /**
20
- * @param {string} path
21
- * @param {import('node:crypto').Hash} hash
29
+ * @param {string} root
30
+ * @param {string} [path] relative
31
+ * @param {string[]} [acc]
32
+ * @returns {Promise<string[]>} paths relative to `root`
22
33
  */
23
- async function hashd (path, hash = createHash('sha256')) {
24
- const stat = await fs.stat(path)
34
+ async function list(root, path = '', acc = []) {
35
+ const entries = await fs.readdir(join(root, path), { withFileTypes: true })
25
36
 
26
- if (stat.isFile()) {
27
- const stream = createReadStream(path)
37
+ for (const entry of entries) {
38
+ if (EXCLUDED.has(entry.name)) continue
28
39
 
29
- stream.pipe(hash, { end: false })
40
+ const relative = path === '' ? entry.name : `${path}/${entry.name}`
30
41
 
31
- return await once(stream, 'end')
42
+ if (entry.isDirectory()) await list(root, relative, acc)
43
+ else if (entry.isFile()) acc.push(relative)
32
44
  }
33
45
 
34
- if (stat.isDirectory()) {
35
- const entries = await fs.opendir(path)
36
-
37
- for await (const entry of entries) {
38
- await hashd(join(path, entry.name), hash)
39
- }
46
+ return acc
47
+ }
40
48
 
41
- return hash
42
- }
49
+ async function digest(path) {
50
+ return createHash('sha256')
51
+ .update(await fs.readFile(path))
52
+ .digest()
43
53
  }
44
54
 
45
- exports.version = version
55
+ const EXCLUDED = new Set(['node_modules', '.git'])
@@ -0,0 +1,7 @@
1
+ export function entity(component) {
2
+ if (component.entity === undefined) return
3
+
4
+ component.entity.storage ??= '@toa.io/storages.mongodb'
5
+ component.entity.associated ??= false
6
+ component.entity.custom ??= false
7
+ }
@@ -1,13 +1,25 @@
1
- 'use strict'
1
+ import { definition } from '../../definition.js'
2
2
 
3
- const events = (component) => {
3
+ export const events = async (component) => {
4
4
  if (component.events === undefined) return
5
5
 
6
- const binding = component.bindings.find((binding) => require(binding).properties.async === true)
6
+ const binding = await asynchronous(component.bindings)
7
7
 
8
8
  for (const event of Object.values(component.events)) {
9
9
  if (event.binding === undefined) event.binding = binding
10
+
11
+ event.conditioned ??= false
12
+ event.subjective ??= false
10
13
  }
11
14
  }
12
15
 
13
- exports.events = events
16
+ /** The first binding that carries events, which only its module can say. */
17
+ async function asynchronous(bindings) {
18
+ for (const binding of bindings) {
19
+ const { properties } = (await definition(binding)).module
20
+
21
+ if (properties.async === true) return binding
22
+ }
23
+
24
+ return undefined
25
+ }
@@ -1,9 +1,4 @@
1
- 'use strict'
2
-
3
- const { events } = require('./events')
4
- const { operations } = require('./operations')
5
- const { receivers } = require('./receivers')
6
-
7
- exports.events = events
8
- exports.operations = operations
9
- exports.receivers = receivers
1
+ export { entity } from './entity.js'
2
+ export { events } from './events.js'
3
+ export { operations } from './operations.js'
4
+ export { receivers } from './receivers.js'
@@ -1,16 +1,23 @@
1
- 'use strict'
2
-
3
- const operations = (component) => {
1
+ export const operations = (component) => {
4
2
  if (component.operations === undefined) return
5
3
 
6
4
  for (const [endpoint, operation] of Object.entries(component.operations)) {
7
- if (operation.type === 'computation' || operation.type === 'effect')
5
+ // what an operation of this type acquires, where it did not say
6
+ if (operation.type === 'computation' || operation.type === 'unmanaged') {
7
+ operation.scope = 'none'
8
8
  operation.query = false
9
+ } else if (operation.type === 'effect') operation.scope ??= 'none'
10
+
11
+ if (operation.scope === 'none') operation.query = false
12
+
13
+ // an operation that states no output states an empty schema, which every reply fits
14
+ operation.output ??= {}
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
9
18
 
10
19
  if (operation.bindings === undefined) operation.bindings = component.bindings
11
20
  if (operation.bindings === null) operation.bindings = []
12
21
  if (operation.virtual === true) delete component.operations[endpoint]
13
22
  }
14
23
  }
15
-
16
- exports.operations = operations
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- function receivers (component) {
1
+ export function receivers(component) {
4
2
  if (component.receivers === undefined) return
5
3
 
6
4
  const receivers = component.receivers
@@ -9,6 +7,9 @@ function receivers (component) {
9
7
  const segments = key.split('.')
10
8
  const source = value.source ?? 'default'
11
9
 
10
+ value.conditioned ??= false
11
+ value.adaptive ??= false
12
+
12
13
  if (source !== 'default') continue
13
14
  if (segments.length === 3) continue // already with a namespace
14
15
 
@@ -18,5 +19,3 @@ function receivers (component) {
18
19
  receivers[newKey] = value
19
20
  }
20
21
  }
21
-
22
- exports.receivers = receivers
@@ -1,8 +1,6 @@
1
- 'use strict'
1
+ import { merge } from '@toa.io/generic'
2
2
 
3
- const { merge } = require('@toa.io/generic')
4
-
5
- const collapse = (manifest, prototype) => {
3
+ export const collapse = (manifest, prototype) => {
6
4
  delete manifest.prototype
7
5
 
8
6
  if (prototype.operations) {
@@ -14,7 +12,8 @@ const collapse = (manifest, prototype) => {
14
12
  if (manifest.operations === undefined) manifest.operations = {}
15
13
 
16
14
  for (let [endpoint, operation] of operations) {
17
- if (manifest.operations[endpoint] === undefined) manifest.operations[endpoint] = {}
15
+ if (manifest.operations[endpoint] === undefined)
16
+ manifest.operations[endpoint] = {}
18
17
  else {
19
18
  const { virtual, ...real } = operation
20
19
 
@@ -26,7 +25,8 @@ const collapse = (manifest, prototype) => {
26
25
  merge(manifest.operations[endpoint], declaration)
27
26
 
28
27
  if (bridge !== undefined) {
29
- if (manifest.prototype.operations === undefined) manifest.prototype.operations = {}
28
+ if (manifest.prototype.operations === undefined)
29
+ manifest.prototype.operations = {}
30
30
 
31
31
  manifest.prototype.operations[endpoint] = { bridge }
32
32
  }
@@ -36,13 +36,64 @@ const collapse = (manifest, prototype) => {
36
36
 
37
37
  const { entity, events, extensions } = prototype
38
38
 
39
- if (manifest.entity?.schema?.properties.id !== undefined && entity?.schema?.properties.id !== undefined) {
39
+ if (
40
+ manifest.entity?.properties?.id !== undefined &&
41
+ entity?.properties?.id !== undefined
42
+ ) {
40
43
  manifest.entity.custom = true
41
44
 
42
- delete prototype.entity.schema.properties.id
45
+ delete prototype.entity.properties.id
43
46
  }
44
47
 
48
+ /*
49
+ * `id` is the only one a component may state for itself. The rest are written by the runtime
50
+ * and read by it, so one redeclared here would be merged over what the prototype says and
51
+ * change what the record holds, or what a criterion against it compares — without the
52
+ * component that wrote it having anything to do with either.
53
+ */
54
+ for (const name of SYSTEM)
55
+ if (
56
+ manifest.entity?.properties?.[name] !== undefined &&
57
+ entity?.properties?.[name] !== undefined
58
+ )
59
+ throw new Error(`System property '${name}' cannot be overridden`)
60
+
61
+ if (prototype.events !== undefined && manifest.events !== undefined)
62
+ for (const event of Object.keys(prototype.events))
63
+ if (event in manifest.events) delete prototype.events[event]
64
+
65
+ if (entity?.migrations !== undefined) inherit(manifest, prototype)
66
+
45
67
  merge(manifest, { entity, events, extensions })
46
68
  }
47
69
 
48
- exports.collapse = collapse
70
+ /**
71
+ * What a prototype declares is in every record of its descendants, so its migrations are applied
72
+ * to each descendant's collection — ahead of the descendant's own, which are written against
73
+ * what they leave. An inherited one is recorded under the prototype's name, so that it collides
74
+ * with nothing the component names itself; one inherited from further up already carries its
75
+ * name and is passed on as it is.
76
+ */
77
+ function inherit(manifest, prototype) {
78
+ const inherited = prototype.entity.migrations.map((migration) =>
79
+ migration.prototype === undefined
80
+ ? {
81
+ ...migration,
82
+ id: `${prototype.name}:${migration.id}`,
83
+ prototype: prototype.name
84
+ }
85
+ : migration
86
+ )
87
+
88
+ const own = manifest.entity?.migrations ?? []
89
+
90
+ if (manifest.entity === undefined) manifest.entity = {}
91
+
92
+ manifest.entity.migrations = [...inherited, ...own]
93
+
94
+ // merged below by `merge`, which would append them a second time
95
+ delete prototype.entity.migrations
96
+ }
97
+
98
+ /** What the runtime writes into every record. `id` is not among them: a component may own it. */
99
+ const SYSTEM = ['VERSION', 'CREATED', 'UPDATED', 'DELETED', 'REGION']
@@ -1,11 +1,11 @@
1
- 'use strict'
2
-
3
- const { hash } = require('@toa.io/generic')
4
- const assert = require('node:assert')
1
+ import { hash } from '@toa.io/generic'
5
2
 
6
3
  // these defaults are required before validation
7
- const defaults = (manifest) => {
8
- if (manifest.name === undefined) manifest.name = protoName(manifest)
4
+ export const defaults = (manifest, proto) => {
5
+ if (manifest.name === undefined)
6
+ if (proto) manifest.name = protoName(manifest)
7
+ else nameAfterDir(manifest)
8
+
9
9
  if (manifest.bindings === undefined) manifest.bindings = ['@toa.io/bindings.amqp']
10
10
  if (manifest.bridge === undefined) manifest.bridge = '@toa.io/bridges.node'
11
11
 
@@ -13,15 +13,21 @@ const defaults = (manifest) => {
13
13
  if (manifest.entity.storage === null)
14
14
  manifest.entity.storage = '@toa.io/storages.null'
15
15
  } else {
16
- if (manifest.prototype === undefined)
17
- manifest.prototype = null
16
+ if (manifest.prototype === undefined) manifest.prototype = null
18
17
  }
19
18
 
20
19
  if (manifest.prototype === undefined) manifest.prototype = '@toa.io/prototype'
21
20
  }
22
21
 
23
- function protoName (manifest) {
22
+ function protoName(manifest) {
24
23
  return 'proto' + hash(manifest.path)
25
24
  }
26
25
 
27
- exports.defaults = defaults
26
+ function nameAfterDir(manifest) {
27
+ const parts = manifest.path.split('/')
28
+ const dirname = parts[parts.length - 1]
29
+ const [name, namespace] = dirname.split('.').reverse()
30
+
31
+ manifest.name = name
32
+ manifest.namespace = namespace
33
+ }