@toa.io/boot 1.0.0-alpha.28 → 1.0.0-alpha.283

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 (50) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/package.json +10 -8
  3. package/src/atom.js +37 -0
  4. package/src/atomicity.js +11 -0
  5. package/src/bindings/broadcast.js +3 -6
  6. package/src/bindings/constants.js +1 -3
  7. package/src/bindings/consume.js +5 -8
  8. package/src/bindings/emit.js +3 -6
  9. package/src/bindings/factory.js +5 -9
  10. package/src/bindings/index.js +5 -13
  11. package/src/bindings/produce.js +32 -19
  12. package/src/bindings/receive.js +3 -6
  13. package/src/bridge.js +23 -16
  14. package/src/call.js +6 -9
  15. package/src/cascade.js +6 -10
  16. package/src/component.js +56 -32
  17. package/src/composition.js +81 -16
  18. package/src/context.js +7 -8
  19. package/src/contract.js +12 -12
  20. package/src/discovery.js +7 -11
  21. package/src/emission.js +10 -14
  22. package/src/env.js +15 -15
  23. package/src/events.js +25 -0
  24. package/src/exposition.js +3 -7
  25. package/src/extensions/aspects.js +3 -7
  26. package/src/extensions/component.js +2 -6
  27. package/src/extensions/context.js +2 -6
  28. package/src/extensions/emitter.js +4 -7
  29. package/src/extensions/index.js +9 -19
  30. package/src/extensions/instances.js +1 -3
  31. package/src/extensions/load.js +6 -8
  32. package/src/extensions/manage.js +16 -0
  33. package/src/extensions/receiver.js +4 -7
  34. package/src/extensions/resolve.js +36 -11
  35. package/src/extensions/storage.js +2 -6
  36. package/src/extensions/tenants.js +4 -8
  37. package/src/guards.js +19 -0
  38. package/src/host.js +15 -0
  39. package/src/index.js +28 -32
  40. package/src/manifest.js +19 -14
  41. package/src/manifest.test.js +7 -7
  42. package/src/operation.js +8 -10
  43. package/src/outbox.js +17 -0
  44. package/src/rc.js +5 -0
  45. package/src/receivers.js +21 -18
  46. package/src/remote.js +28 -10
  47. package/src/span.js +17 -0
  48. package/src/storage.js +19 -13
  49. package/types/index.d.ts +7 -5
  50. package/types/manifest.d.ts +1 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,188 @@
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.283](https://github.com/toa-io/toa/compare/v1.0.0-alpha.282...v1.0.0-alpha.283) (2026-09-04)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **core:** an operation returns the errors it declares, and nothing else ([1c2b819](https://github.com/toa-io/toa/commit/1c2b8193ae7fbe86bc1a25053a6044ee4dd16a4a))
11
+ * **exposition:** declare the errors the built-in components return ([27200c3](https://github.com/toa-io/toa/commit/27200c348ab09068d58c52c3e83208cb29cd7290))
12
+
13
+
14
+ # [1.0.0-alpha.282](https://github.com/toa-io/toa/compare/v1.0.0-alpha.281...v1.0.0-alpha.282) (2026-09-03)
15
+
16
+ ### Bug Fixes
17
+
18
+ * **boot:** declare host, and build extensions with it in the step definitions ([915d957](https://github.com/toa-io/toa/commit/915d9571823358f8fca6fe19ef3aabd1b62e5605))
19
+
20
+ ### Features
21
+
22
+ * **core:** a Host for extensions ([6315eac](https://github.com/toa-io/toa/commit/6315eac1e9fb05be472272de06385c54ad5934ce))
23
+
24
+
25
+ # [1.0.0-alpha.278](https://github.com/toa-io/toa/compare/v1.0.0-alpha.277...v1.0.0-alpha.278) (2026-09-03)
26
+
27
+ **Note:** Version bump only for package @toa.io/boot
28
+
29
+
30
+
31
+
32
+
33
+ # [1.0.0-alpha.277](https://github.com/toa-io/toa/compare/v1.0.0-alpha.276...v1.0.0-alpha.277) (2026-09-03)
34
+
35
+ ### Bug Fixes
36
+
37
+ * read a manifest the way js-yaml read one before ([fcea1b2](https://github.com/toa-io/toa/commit/fcea1b21996d6efa6c3198acbfc9f70f1ce0c35c))
38
+ * settle what the module loader made asynchronous ([d4ab9df](https://github.com/toa-io/toa/commit/d4ab9dfb09e382da311dba08f52214e7645f4a6c))
39
+ * what the loader resolves, and what it will not ([1b8a02f](https://github.com/toa-io/toa/commit/1b8a02ff0a5e0f18ee62e3f71bc81116f86f9af1))
40
+
41
+
42
+ # [1.0.0-alpha.274](https://github.com/toa-io/toa/compare/v1.0.0-alpha.273...v1.0.0-alpha.274) (2026-09-02)
43
+
44
+ **Note:** Version bump only for package @toa.io/boot
45
+
46
+
47
+
48
+
49
+
50
+ # [1.0.0-alpha.273](https://github.com/toa-io/toa/compare/v1.0.0-alpha.272...v1.0.0-alpha.273) (2026-09-02)
51
+
52
+ **Note:** Version bump only for package @toa.io/boot
53
+
54
+
55
+
56
+
57
+
58
+ # [1.0.0-alpha.272](https://github.com/toa-io/toa/compare/v1.0.0-alpha.271...v1.0.0-alpha.272) (2026-09-01)
59
+
60
+
61
+ ### Bug Fixes
62
+
63
+ * **amqp:** keep a receiver apart from the producers it delivers into ([4c2f43e](https://github.com/toa-io/toa/commit/4c2f43ebde68ba76a1fd277fe2a3305be1b622ef))
64
+ * **amqp:** tear a receiver down before its producers, and drop the split ([e6c1154](https://github.com/toa-io/toa/commit/e6c1154024df22f7a470c47c1a79bd170fe7e3b2))
65
+
66
+
67
+ * refactor(atomicity)!: take the lock manager from the stash ([4fd91eb](https://github.com/toa-io/toa/commit/4fd91eb2fdb68f7b87aaf16d182794bbc567ba57))
68
+ * refactor(atomicity)!: make the connector a family, not a partitioner ([06fd63a](https://github.com/toa-io/toa/commit/06fd63ad6296b724ef83afccf4e4e25d0bcaf080))
69
+ * refactor(atomicity)!: rename the connector and free it of the outbox ([21f1a41](https://github.com/toa-io/toa/commit/21f1a41aceafcfd35c7620014062fe46b54afa95))
70
+ * feat(core)!: commit events with the state that produced them ([1eb68cc](https://github.com/toa-io/toa/commit/1eb68cc435dbfa03faa16009fceb866693d22e1a)), closes [#20](https://github.com/toa-io/toa/issues/20)
71
+
72
+
73
+ ### Features
74
+
75
+ * **core:** give every component an atom aspect ([b501b9c](https://github.com/toa-io/toa/commit/b501b9cd3d5f5408d9faa71213e953fe2792cf9f))
76
+
77
+
78
+ ### BREAKING CHANGES
79
+
80
+ * `context.stash.lock` is gone; lock through `context.atom` instead.
81
+ A stash pointer resolving to several addresses now uses the first.
82
+
83
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
84
+ * `Factory.partition(group)` is `Factory.atom(group)`, and the
85
+ `Partition` it returned is an `Atom`. `slots(total)` is unchanged.
86
+
87
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
88
+ * `@toa.io/partitions.redis` is now `@toa.io/atomicity`, and its
89
+ `lanes(total)` is `slots(total)`. Redis is declared as `atomicity` in the context
90
+ rather than `outbox.redis`, and read from `TOA_ATOMICITY_REDIS`.
91
+ `TOA_OUTBOX_PARTITION_INTERVAL` is `TOA_ATOMICITY_INTERVAL`.
92
+
93
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94
+ * `event.changeset` is removed; use `origin` and `state`. `State`
95
+ takes an `Outbox` in place of an `Emission`. `difference` is dropped from
96
+ `@toa.io/generic` along with its last caller.
97
+
98
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
99
+
100
+
101
+
102
+
103
+
104
+ # [1.0.0-alpha.270](https://github.com/toa-io/toa/compare/v1.0.0-alpha.269...v1.0.0-alpha.270) (2026-08-31)
105
+
106
+
107
+ ### Performance Improvements
108
+
109
+ * **boot:** stop paying for what a composition does not need to start ([1619c27](https://github.com/toa-io/toa/commit/1619c2743072f4706939ce72f3074e615d26a91e))
110
+ * **openspan:** create a span only when something records it ([a6efb0e](https://github.com/toa-io/toa/commit/a6efb0eed38393f4c169b067f23d7447f0169bca))
111
+
112
+
113
+
114
+
115
+
116
+ # [1.0.0-alpha.266](https://github.com/toa-io/toa/compare/v1.0.0-alpha.265...v1.0.0-alpha.266) (2026-08-29)
117
+
118
+ **Note:** Version bump only for package @toa.io/boot
119
+
120
+
121
+
122
+
123
+
124
+ # [1.0.0-alpha.265](https://github.com/toa-io/toa/compare/v1.0.0-alpha.264...v1.0.0-alpha.265) (2026-08-29)
125
+
126
+
127
+ ### Features
128
+
129
+ * **bridges.node:** add the dispose run command phase ([00df715](https://github.com/toa-io/toa/commit/00df7158f638b976ab09d4007102a4ead3c696c3))
130
+
131
+
132
+
133
+
134
+
135
+ # [1.0.0-alpha.264](https://github.com/toa-io/toa/compare/v1.0.0-alpha.263...v1.0.0-alpha.264) (2026-08-29)
136
+
137
+ **Note:** Version bump only for package @toa.io/boot
138
+
139
+
140
+
141
+
142
+
143
+ # [1.0.0-alpha.263](https://github.com/toa-io/toa/compare/v1.0.0-alpha.262...v1.0.0-alpha.263) (2026-08-29)
144
+
145
+ **Note:** Version bump only for package @toa.io/boot
146
+
147
+
148
+
149
+
150
+
151
+ # [1.0.0-alpha.262](https://github.com/toa-io/toa/compare/v1.0.0-alpha.261...v1.0.0-alpha.262) (2026-08-28)
152
+
153
+
154
+ ### Bug Fixes
155
+
156
+ * **boot:** let the composition own the process discovery ([2201e88](https://github.com/toa-io/toa/commit/2201e8852517fd1d2822c775c9b126eb60399580))
157
+
158
+
159
+ ### Features
160
+
161
+ * **core:** identify the origin of a call with `request.source` ([9da6b66](https://github.com/toa-io/toa/commit/9da6b66df852690351a1e82c7b8cc176fd89774a))
162
+
163
+
164
+
165
+
166
+
167
+ # [1.0.0-alpha.259](https://github.com/toa-io/toa/compare/v1.0.0-alpha.258...v1.0.0-alpha.259) (2026-08-24)
168
+
169
+
170
+ ### Features
171
+
172
+ * **runtime:** split component RC into preflight and settle ([0d648b3](https://github.com/toa-io/toa/commit/0d648b314e4aac226c254bf01a86affa04b59b34))
173
+
174
+
175
+
176
+
177
+
178
+ # [1.0.0-alpha.257](https://github.com/toa-io/toa/compare/v1.0.0-alpha.256...v1.0.0-alpha.257) (2026-08-24)
179
+
180
+ **Note:** Version bump only for package @toa.io/boot
181
+
182
+
183
+
184
+
185
+
186
+ # [1.0.0-alpha.256](https://github.com/toa-io/toa/compare/v1.0.0-alpha.255...v1.0.0-alpha.256) (2026-08-23)
187
+
188
+ **Note:** Version bump only for package @toa.io/boot
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@toa.io/boot",
3
- "version": "1.0.0-alpha.28",
3
+ "version": "1.0.0-alpha.283",
4
+ "type": "module",
4
5
  "description": "Toa Boot",
5
6
  "author": "temich <tema.gurtovoy@gmail.com>",
6
7
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -20,13 +21,14 @@
20
21
  "test": "echo \"Error: run tests from root\" && exit 1"
21
22
  },
22
23
  "dependencies": {
23
- "@toa.io/core": "1.0.0-alpha.28",
24
- "@toa.io/filesystem": "1.0.0-alpha.28",
25
- "@toa.io/generic": "1.0.0-alpha.28",
26
- "@toa.io/norm": "1.0.0-alpha.28",
27
- "@toa.io/schema": "1.0.0-alpha.28",
24
+ "@toa.io/atomicity": "1.0.0-alpha.283",
25
+ "@toa.io/core": "1.0.0-alpha.283",
26
+ "@toa.io/generic": "1.0.0-alpha.283",
27
+ "@toa.io/norm": "1.0.0-alpha.283",
28
+ "@toa.io/schemas": "1.0.0-alpha.283",
28
29
  "clone-deep": "4.0.1",
29
- "dotenv": "16.1.1"
30
+ "dotenv": "17.4.2",
31
+ "openspan": "1.0.0-alpha.277"
30
32
  },
31
- "gitHead": "89c3b7d1314e357c9e33df8d95f8d46954cd3bcf"
33
+ "gitHead": "2f52b023bed0ccb2ecdb2b82fe59fbe38bf282e1"
32
34
  }
package/src/atom.js ADDED
@@ -0,0 +1,37 @@
1
+ import { Connector } from '@toa.io/core'
2
+
3
+ import * as boot from './index.js'
4
+
5
+ /**
6
+ * The atom as a system aspect. Every component has `context.atom`, with nothing declared in its
7
+ * manifest and nothing of its own to configure.
8
+ *
9
+ * @implements {toa.core.extensions.Aspect}
10
+ */
11
+ class Aspect extends Connector {
12
+ name = 'atom'
13
+
14
+ #atom
15
+
16
+ constructor (atom) {
17
+ super()
18
+
19
+ this.#atom = atom
20
+
21
+ this.depends(atom)
22
+ }
23
+
24
+ // the methods are named rather than forwarded, so that the connector's lifecycle is not
25
+ // reachable from an algorithm
26
+ invoke (method, ...args) {
27
+ switch (method) {
28
+ case 'slots': return this.#atom.slots(...args)
29
+ case 'meter': return this.#atom.meter(...args)
30
+ case 'lock': return this.#atom.lock(...args)
31
+ default: throw new Error(`Atom aspect has no '${method}' method`)
32
+ }
33
+ }
34
+ }
35
+
36
+ /** @param {string} group */
37
+ export const atom = (group) => new Aspect(boot.atomicity(group))
@@ -0,0 +1,11 @@
1
+ import { Factory } from '@toa.io/atomicity'
2
+
3
+ /**
4
+ * What the replicas of one group decide together. Every component has one, whether or not there
5
+ * is anything to coordinate through: without a Redis nothing is owned and nothing is metered —
6
+ * see the connector's readme.
7
+ *
8
+ * @param {string} group
9
+ * @returns {toa.core.atomicity.Atom}
10
+ */
11
+ export const atomicity = (group) => new Factory().atom(group)
@@ -1,7 +1,4 @@
1
- 'use strict'
1
+ import { factory } from './factory.js'
2
2
 
3
- const { factory } = require('./factory')
4
-
5
- const broadcast = (channel, group, binding = '@toa.io/bindings.amqp') => factory(binding).broadcast(channel, group)
6
-
7
- exports.broadcast = broadcast
3
+ export const broadcast = async (channel, group, binding = '@toa.io/bindings.amqp') =>
4
+ (await factory(binding)).broadcast(channel, group)
@@ -1,3 +1 @@
1
- 'use strict'
2
-
3
- exports.LOOP = '@toa.io/bindings.loop'
1
+ export const LOOP = '@toa.io/bindings.loop'
@@ -1,9 +1,6 @@
1
- 'use strict'
1
+ import { LOOP } from './constants.js'
2
+ import { factory } from './factory.js'
2
3
 
3
- const { LOOP } = require('./constants')
4
- const { factory } = require('./factory')
5
-
6
- const consume = (locator, endpoint, bindings) =>
7
- [LOOP].concat(bindings).map((binding) => factory(binding).consumer(locator, endpoint))
8
-
9
- exports.consume = consume
4
+ export const consume = async (locator, endpoint, bindings) =>
5
+ await Promise.all([LOOP].concat(bindings).map(async (binding) =>
6
+ (await factory(binding)).consumer(locator, endpoint)))
@@ -1,7 +1,4 @@
1
- 'use strict'
1
+ import { factory } from './factory.js'
2
2
 
3
- const { factory } = require('./factory')
4
-
5
- const emit = (binding, locator, label) => factory(binding).emitter(locator, label)
6
-
7
- exports.emit = emit
3
+ export const emit = async (binding, locator, label) =>
4
+ (await factory(binding)).emitter(locator, label)
@@ -1,15 +1,11 @@
1
- 'use strict'
2
-
3
1
  let instances = {}
4
2
 
5
- const factory = (binding) => {
6
- if (instances[binding] === undefined) instances[binding] = new (require(binding).Factory)()
3
+ // the promise is what is remembered, so two components cannot each make a factory
4
+ export const factory = async (binding) => {
5
+ instances[binding] ??= import(binding).then(({ Factory }) => new Factory())
7
6
 
8
- return instances[binding]
7
+ return await instances[binding]
9
8
  }
10
9
 
11
10
  // for testing purposes
12
- const reset = () => (instances = {})
13
-
14
- exports.factory = factory
15
- exports.reset = reset
11
+ export const reset = () => (instances = {})
@@ -1,13 +1,5 @@
1
- 'use strict'
2
-
3
- const { broadcast } = require('./broadcast')
4
- const { consume } = require('./consume')
5
- const { emit } = require('./emit')
6
- const { produce } = require('./produce')
7
- const { receive } = require('./receive')
8
-
9
- exports.broadcast = broadcast
10
- exports.consume = consume
11
- exports.emit = emit
12
- exports.produce = produce
13
- exports.receive = receive
1
+ export { broadcast } from './broadcast.js'
2
+ export { consume } from './consume.js'
3
+ export { emit } from './emit.js'
4
+ export { produce } from './produce.js'
5
+ export { receive } from './receive.js'
@@ -1,28 +1,41 @@
1
- 'use strict'
2
-
3
- const { LOOP } = require('./constants')
4
- const { factory } = require('./factory')
1
+ import { LOOP } from './constants.js'
2
+ import { factory } from './factory.js'
3
+
4
+ export const produce = async (component, operations) => {
5
+ const local = []
6
+ const other = []
7
+
8
+ for (const [binding, endpoints] of group(operations)) {
9
+ const made = await factory(binding)
10
+ const producer = made.producer(component.locator, endpoints, component)
11
+ const { properties } = await import(binding)
12
+
13
+ if (properties.local === true)
14
+ local.push(producer)
15
+ else
16
+ other.push(producer)
17
+ }
5
18
 
6
- const produce = (component, operations) => group(operations, (factory, endpoints) =>
7
- factory.producer(component.locator, endpoints, component))
19
+ return { local, other }
20
+ }
8
21
 
9
- const group = (operations, callback) => {
22
+ /** The endpoints each binding carries, as entries. */
23
+ const group = (operations) => {
10
24
  const map = {}
11
25
 
12
- for (const [endpoint, operation] of Object.entries(operations)) {
13
- // noinspection JSUnresolvedVariable
14
- const bindings = global.TOA_INTEGRATION_BINDINGS_LOOP_DISABLED
15
- ? operation.bindings
16
- : [LOOP].concat(operation.bindings)
26
+ if (operations !== undefined)
27
+ for (const [endpoint, operation] of Object.entries(operations)) {
28
+ // noinspection JSUnresolvedVariable
29
+ const bindings = global.TOA_INTEGRATION_BINDINGS_LOOP_DISABLED
30
+ ? operation.bindings
31
+ : [LOOP].concat(operation.bindings)
17
32
 
18
- for (const binding of bindings) {
19
- if (!map[binding]) map[binding] = []
33
+ for (const binding of bindings) {
34
+ if (!map[binding]) map[binding] = []
20
35
 
21
- map[binding].push(endpoint)
36
+ map[binding].push(endpoint)
37
+ }
22
38
  }
23
- }
24
39
 
25
- return Object.entries(map).map(([binding, endpoints]) => callback(factory(binding), endpoints))
40
+ return Object.entries(map)
26
41
  }
27
-
28
- exports.produce = produce
@@ -1,7 +1,4 @@
1
- 'use strict'
1
+ import { factory } from './factory.js'
2
2
 
3
- const { factory } = require('./factory')
4
-
5
- const receive = (binding, locator, label, group, receiver) => factory(binding).receiver(locator, label, group, receiver)
6
-
7
- exports.receive = receive
3
+ export const receive = async (binding, locator, label, group, receiver) =>
4
+ (await factory(binding)).receiver(locator, label, group, receiver)
package/src/bridge.js CHANGED
@@ -1,28 +1,35 @@
1
- 'use strict'
2
-
3
- const algorithm = (bridge, path, endpoint, context) => {
4
- const algorithm = resolve(bridge).algorithm(path, endpoint, context)
1
+ export async function algorithm (bridge, path, endpoint, context) {
2
+ const factory = await resolve(bridge)
3
+ const algorithm = await factory.algorithm(path, endpoint, context)
5
4
 
6
5
  algorithm.depends(context)
7
6
 
8
7
  return algorithm
9
8
  }
10
9
 
11
- const event = (bridge, path, label, context) => resolve(bridge).event(path, label, context)
12
- const receiver = (bridge, path, label) => resolve(bridge).receiver(path, label)
10
+ export const event = async (bridge, path, label, context) =>
11
+ (await resolve(bridge)).event(path, label, context)
13
12
 
14
- const factories = {}
13
+ export const receiver = async (bridge, path, label) =>
14
+ (await resolve(bridge)).receiver(path, label)
15
15
 
16
- const resolve = (bridge) => {
17
- if (factories[bridge] === undefined) {
18
- const { Factory } = require(bridge)
16
+ export const guard = async (bridge, path, label, context) =>
17
+ (await resolve(bridge)).guard(path, label, context)
19
18
 
20
- factories[bridge] = new Factory()
21
- }
19
+ export async function rc (bridge, path, context) {
20
+ const factory = await resolve(bridge)
22
21
 
23
- return factories[bridge]
22
+ if (factory.rc === undefined)
23
+ return
24
+
25
+ return factory.rc(path, context)
24
26
  }
25
27
 
26
- exports.algorithm = algorithm
27
- exports.event = event
28
- exports.receiver = receiver
28
+ const factories = {}
29
+
30
+ // the promise is what is remembered, so two boots cannot each make a factory
31
+ const resolve = async (bridge) => {
32
+ factories[bridge] ??= import(bridge).then(({ Factory }) => new Factory())
33
+
34
+ return await factories[bridge]
35
+ }
package/src/call.js CHANGED
@@ -1,15 +1,12 @@
1
- 'use strict'
1
+ import { Call, Transmission } from '@toa.io/core'
2
2
 
3
- const { Call, Transmission } = require('@toa.io/core')
3
+ import * as boot from './index.js'
4
4
 
5
- const boot = require('./index')
6
-
7
- const call = (locator, endpoint, definition, entity) => {
8
- const consumers = boot.bindings.consume(locator, endpoint, definition.bindings)
5
+ // eslint-disable-next-line max-params
6
+ export const call = async (locator, endpoint, definition, entity, source) => {
7
+ const consumers = await boot.bindings.consume(locator, endpoint, definition.bindings)
9
8
  const transmission = new Transmission(consumers)
10
9
  const contract = boot.contract.request(definition, entity)
11
10
 
12
- return new Call(transmission, contract)
11
+ return new Call(transmission, contract, source)
13
12
  }
14
-
15
- exports.call = call
package/src/cascade.js CHANGED
@@ -1,16 +1,14 @@
1
- 'use strict'
1
+ import { Cascade } from '@toa.io/core'
2
2
 
3
- const { Cascade } = require('@toa.io/core')
3
+ import * as boot from './index.js'
4
4
 
5
- const boot = require('./index')
6
-
7
- const cascade = (manifest, endpoint, definition, context) => {
5
+ export async function cascade (manifest, endpoint, definition, context, preflight) {
8
6
  const bridges = []
9
7
 
10
8
  if (definition.forward) endpoint = definition.forward
11
9
 
12
10
  if (definition.bridge) {
13
- const bridge = boot.bridge.algorithm(definition.bridge, manifest.path, endpoint, context)
11
+ const bridge = await boot.bridge.algorithm(definition.bridge, manifest.path, endpoint, context)
14
12
 
15
13
  bridges.unshift(bridge)
16
14
  }
@@ -22,12 +20,10 @@ const cascade = (manifest, endpoint, definition, context) => {
22
20
 
23
21
  if (operation === undefined) continue
24
22
 
25
- const bridge = boot.bridge.algorithm(operation.bridge, prototype.path, endpoint, context)
23
+ const bridge = await boot.bridge.algorithm(operation.bridge, prototype.path, endpoint, context)
26
24
 
27
25
  bridges.unshift(bridge)
28
26
  }
29
27
 
30
- return new Cascade(bridges)
28
+ return new Cascade(bridges, preflight)
31
29
  }
32
-
33
- exports.cascade = cascade
package/src/component.js CHANGED
@@ -1,48 +1,72 @@
1
- 'use strict'
2
-
3
- const { remap } = require('@toa.io/generic')
4
- const {
5
- Component,
6
- Locator,
7
- State,
8
- entities
9
- } = require('@toa.io/core')
10
- const { Schema } = require('@toa.io/schema')
11
-
12
- const boot = require('./index')
13
-
14
- /**
15
- * @param {toa.norm.Component} manifest
16
- * @returns {Promise<toa.core.Component>}
17
- */
18
- const component = async (manifest) => {
19
- boot.extensions.load(manifest)
1
+ import { Component, Locator, State, entities } from '@toa.io/core'
2
+ import * as schemas from '@toa.io/schemas'
20
3
 
4
+ import * as boot from './index.js'
5
+ import { span } from './span.js'
6
+
7
+ export const component = async (manifest) => {
21
8
  const locator = new Locator(manifest.name, manifest.namespace)
22
- const storage = boot.storage(manifest)
9
+
10
+ return span(`component ${locator.id}`, () => create(manifest, locator))
11
+ }
12
+
13
+ const create = async (manifest, locator) => {
14
+ await boot.extensions.load(manifest)
15
+
16
+ // the storage is told whether there will be an outbox, so the events come first
17
+ const events = boot.events(manifest)
18
+ const storage = await boot.storage(manifest, events !== undefined)
23
19
  const context = await boot.context(manifest)
24
- const emission = boot.emission(manifest.events, locator, context)
20
+ const emission = await boot.emission(events, locator, context)
21
+ const outbox = boot.outbox(manifest, storage, emission)
25
22
 
26
23
  let state
27
24
 
28
25
  if (manifest.entity !== undefined) {
29
- const schema = new Schema(manifest.entity.schema)
30
- const entity = new entities.Factory(schema)
26
+ const schema = schemas.schema(manifest.entity.schema)
27
+ const guards = await boot.guards(manifest, context)
28
+ const entity = new entities.Factory(schema, guards)
31
29
 
32
- state = new State(storage, entity, emission, manifest.entity.associated)
30
+ state = new State(storage, entity, outbox, manifest.entity.associated)
33
31
  }
34
32
 
35
- const operations = manifest.operations === undefined
36
- ? {}
37
- : remap(manifest.operations, (definition, endpoint) =>
38
- boot.operation(manifest, endpoint, definition, context, state))
39
-
33
+ const phases = await boot.rc(manifest, context)
34
+ const operations = await bootOperations(manifest, context, state, phases?.preflight)
40
35
  const component = new Component(locator, operations)
41
36
 
42
37
  if (storage) component.depends(storage)
43
- if (emission) component.depends(emission)
44
38
 
45
- return boot.extensions.component(component)
39
+ // the outbox owns the emission and the storage, so it drains before either goes down
40
+ if (outbox) component.depends(outbox)
41
+ else if (emission) component.depends(emission)
42
+
43
+ const decorated = boot.extensions.component(component)
44
+
45
+ if (phases?.settle !== undefined)
46
+ decorated.settle = phases.settle
47
+
48
+ // a dependency closes after its dependant, so the component is already closed
49
+ // when the RC releases what it opened
50
+ if (phases?.dispose !== undefined)
51
+ decorated.depends(phases.dispose)
52
+
53
+ return decorated
46
54
  }
47
55
 
48
- exports.component = component
56
+ async function bootOperations (manifest, context, state, preflight) {
57
+ if (manifest.operations === undefined)
58
+ return {}
59
+
60
+ const entries = Object.entries(manifest.operations)
61
+
62
+ // each one loads its algorithm from disk and compiles its contracts
63
+ const booted = await Promise.all(entries.map(([endpoint, definition]) =>
64
+ boot.operation(manifest, endpoint, definition, context, state, preflight)))
65
+
66
+ const operations = {}
67
+
68
+ for (let i = 0; i < entries.length; i++)
69
+ operations[entries[i][0]] = booted[i]
70
+
71
+ return operations
72
+ }