@toa.io/boot 1.0.0-alpha.29 → 1.0.0-alpha.291
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 +37 -0
- package/package.json +10 -8
- package/src/atom.js +41 -0
- package/src/atomicity.js +11 -0
- package/src/bindings/broadcast.js +3 -6
- package/src/bindings/constants.js +1 -3
- package/src/bindings/consume.js +8 -8
- package/src/bindings/emit.js +3 -6
- package/src/bindings/factory.js +5 -9
- package/src/bindings/index.js +5 -13
- package/src/bindings/produce.js +29 -18
- package/src/bindings/receive.js +3 -6
- package/src/bridge.js +22 -16
- package/src/call.js +6 -9
- package/src/cascade.js +16 -10
- package/src/component.js +79 -32
- package/src/composition.js +104 -15
- package/src/context.js +7 -8
- package/src/contract.js +11 -12
- package/src/discovery.js +29 -18
- package/src/emission.js +21 -14
- package/src/env.js +19 -12
- package/src/events.js +27 -0
- package/src/extensions/aspects.js +4 -8
- package/src/extensions/component.js +4 -8
- package/src/extensions/context.js +4 -8
- package/src/extensions/emitter.js +7 -9
- package/src/extensions/index.js +9 -19
- package/src/extensions/instances.js +2 -4
- package/src/extensions/load.js +7 -7
- package/src/extensions/manage.js +15 -0
- package/src/extensions/receiver.js +5 -9
- package/src/extensions/resolve.js +36 -11
- package/src/extensions/storage.js +4 -8
- package/src/extensions/tenants.js +5 -9
- package/src/guards.js +20 -0
- package/src/host.js +15 -0
- package/src/index.js +48 -32
- package/src/manifest.js +25 -16
- package/src/manifest.test.js +7 -8
- package/src/operation.js +25 -13
- package/src/outbox.js +17 -0
- package/src/rc.js +5 -0
- package/src/receivers.js +35 -21
- package/src/remote.js +38 -17
- package/src/span.js +19 -0
- package/src/storage.js +35 -14
- package/src/storage.test.js +57 -0
- package/types/bindings.d.ts +6 -2
- package/types/index.d.ts +19 -10
- package/types/manifest.d.ts +5 -4
- package/src/exposition.js +0 -17
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
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.291](https://github.com/toa-io/toa/compare/v1.0.0-alpha.290...v1.0.0-alpha.291) (2026-09-07)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @toa.io/boot
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [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)
|
|
15
|
+
|
|
16
|
+
* 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)
|
|
17
|
+
|
|
18
|
+
### BREAKING CHANGES
|
|
19
|
+
|
|
20
|
+
* a component that read `process.env.TOA_*` reads `context` instead;
|
|
21
|
+
`echo(input)` no longer substitutes from the environment; a bash operation sees no
|
|
22
|
+
`TOA_*`; images no longer set `USER node` — see migrations/289.md.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# [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)
|
|
26
|
+
|
|
27
|
+
**Note:** Version bump only for package @toa.io/boot
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# [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)
|
|
34
|
+
|
|
35
|
+
### Bug Fixes
|
|
36
|
+
|
|
37
|
+
* **boot:** a discovery belongs to the connection that made it ([48e1c8f](https://github.com/toa-io/toa/commit/48e1c8f668b1853134ae9fb2349c485e6da66c28))
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/boot",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.291",
|
|
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/
|
|
24
|
-
"@toa.io/
|
|
25
|
-
"@toa.io/generic": "1.0.0-alpha.
|
|
26
|
-
"@toa.io/norm": "1.0.0-alpha.
|
|
27
|
-
"@toa.io/
|
|
24
|
+
"@toa.io/atomicity": "1.0.0-alpha.291",
|
|
25
|
+
"@toa.io/core": "1.0.0-alpha.291",
|
|
26
|
+
"@toa.io/generic": "1.0.0-alpha.291",
|
|
27
|
+
"@toa.io/norm": "1.0.0-alpha.291",
|
|
28
|
+
"@toa.io/schemas": "1.0.0-alpha.291",
|
|
28
29
|
"clone-deep": "4.0.1",
|
|
29
|
-
"dotenv": "
|
|
30
|
+
"dotenv": "17.4.2",
|
|
31
|
+
"openspan": "1.0.0-alpha.286"
|
|
30
32
|
},
|
|
31
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "bcd8d8d07bd3e64fc826af8719472404662cc37f"
|
|
32
34
|
}
|
package/src/atom.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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 {import('@toa.io/core/types').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':
|
|
29
|
+
return this.#atom.slots(...args)
|
|
30
|
+
case 'meter':
|
|
31
|
+
return this.#atom.meter(...args)
|
|
32
|
+
case 'lock':
|
|
33
|
+
return this.#atom.lock(...args)
|
|
34
|
+
default:
|
|
35
|
+
throw new Error(`Atom aspect has no '${method}' method`)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @param {string} group */
|
|
41
|
+
export const atom = (group) => new Aspect(boot.atomicity(group))
|
package/src/atomicity.js
ADDED
|
@@ -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 {import('@toa.io/core/types').atomicity.Atom}
|
|
10
|
+
*/
|
|
11
|
+
export const atomicity = (group) => new Factory().atom(group)
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { factory } from './factory.js'
|
|
2
2
|
|
|
3
|
-
const
|
|
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)
|
package/src/bindings/consume.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { LOOP } from './constants.js'
|
|
2
|
+
import { factory } from './factory.js'
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
exports.consume = consume
|
|
4
|
+
export const consume = async (locator, endpoint, bindings) =>
|
|
5
|
+
await Promise.all(
|
|
6
|
+
[LOOP]
|
|
7
|
+
.concat(bindings)
|
|
8
|
+
.map(async (binding) => (await factory(binding)).consumer(locator, endpoint))
|
|
9
|
+
)
|
package/src/bindings/emit.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { factory } from './factory.js'
|
|
2
2
|
|
|
3
|
-
const
|
|
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)
|
package/src/bindings/factory.js
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
1
|
let instances = {}
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
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 = {})
|
package/src/bindings/index.js
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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'
|
package/src/bindings/produce.js
CHANGED
|
@@ -1,28 +1,39 @@
|
|
|
1
|
-
|
|
1
|
+
import { LOOP } from './constants.js'
|
|
2
|
+
import { factory } from './factory.js'
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
4
|
+
export const produce = async (component, operations) => {
|
|
5
|
+
const local = []
|
|
6
|
+
const other = []
|
|
5
7
|
|
|
6
|
-
const
|
|
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)
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
if (properties.local === true) local.push(producer)
|
|
14
|
+
else other.push(producer)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return { local, other }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The endpoints each binding carries, as entries. */
|
|
21
|
+
const group = (operations) => {
|
|
10
22
|
const map = {}
|
|
11
23
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
if (operations !== undefined)
|
|
25
|
+
for (const [endpoint, operation] of Object.entries(operations)) {
|
|
26
|
+
// noinspection JSUnresolvedVariable
|
|
27
|
+
const bindings = global.TOA_INTEGRATION_BINDINGS_LOOP_DISABLED
|
|
28
|
+
? operation.bindings
|
|
29
|
+
: [LOOP].concat(operation.bindings)
|
|
17
30
|
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
for (const binding of bindings) {
|
|
32
|
+
if (!map[binding]) map[binding] = []
|
|
20
33
|
|
|
21
|
-
|
|
34
|
+
map[binding].push(endpoint)
|
|
35
|
+
}
|
|
22
36
|
}
|
|
23
|
-
}
|
|
24
37
|
|
|
25
|
-
return Object.entries(map)
|
|
38
|
+
return Object.entries(map)
|
|
26
39
|
}
|
|
27
|
-
|
|
28
|
-
exports.produce = produce
|
package/src/bindings/receive.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { factory } from './factory.js'
|
|
2
2
|
|
|
3
|
-
const
|
|
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,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const algorithm = (
|
|
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) =>
|
|
12
|
-
|
|
10
|
+
export const event = async (bridge, path, label, context) =>
|
|
11
|
+
(await resolve(bridge)).event(path, label, context)
|
|
13
12
|
|
|
14
|
-
const
|
|
13
|
+
export const receiver = async (bridge, path, label) =>
|
|
14
|
+
(await resolve(bridge)).receiver(path, label)
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
|
|
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
|
-
|
|
21
|
-
|
|
19
|
+
export async function rc(bridge, path, context) {
|
|
20
|
+
const factory = await resolve(bridge)
|
|
22
21
|
|
|
23
|
-
return
|
|
22
|
+
if (factory.rc === undefined) return
|
|
23
|
+
|
|
24
|
+
return factory.rc(path, context)
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const factories = {}
|
|
28
|
+
|
|
29
|
+
// the promise is what is remembered, so two boots cannot each make a factory
|
|
30
|
+
const resolve = async (bridge) => {
|
|
31
|
+
factories[bridge] ??= import(bridge).then(({ Factory }) => new Factory())
|
|
32
|
+
|
|
33
|
+
return await factories[bridge]
|
|
34
|
+
}
|
package/src/call.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import { Call, Transmission } from '@toa.io/core'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import * as boot from './index.js'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
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,19 @@
|
|
|
1
|
-
|
|
1
|
+
import { Cascade } from '@toa.io/core'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import * as boot from './index.js'
|
|
4
4
|
|
|
5
|
-
|
|
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(
|
|
11
|
+
const bridge = await boot.bridge.algorithm(
|
|
12
|
+
definition.bridge,
|
|
13
|
+
manifest.path,
|
|
14
|
+
endpoint,
|
|
15
|
+
context
|
|
16
|
+
)
|
|
14
17
|
|
|
15
18
|
bridges.unshift(bridge)
|
|
16
19
|
}
|
|
@@ -22,12 +25,15 @@ const cascade = (manifest, endpoint, definition, context) => {
|
|
|
22
25
|
|
|
23
26
|
if (operation === undefined) continue
|
|
24
27
|
|
|
25
|
-
const bridge = boot.bridge.algorithm(
|
|
28
|
+
const bridge = await boot.bridge.algorithm(
|
|
29
|
+
operation.bridge,
|
|
30
|
+
prototype.path,
|
|
31
|
+
endpoint,
|
|
32
|
+
context
|
|
33
|
+
)
|
|
26
34
|
|
|
27
35
|
bridges.unshift(bridge)
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
return new Cascade(bridges)
|
|
38
|
+
return new Cascade(bridges, preflight)
|
|
31
39
|
}
|
|
32
|
-
|
|
33
|
-
exports.cascade = cascade
|
package/src/component.js
CHANGED
|
@@ -1,48 +1,95 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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 { entity as declaration } from '@toa.io/norm'
|
|
3
|
+
import { schema as compileSchema } from '@toa.io/schemas'
|
|
4
|
+
import { environment } from '@toa.io/generic'
|
|
20
5
|
|
|
6
|
+
import * as boot from './index.js'
|
|
7
|
+
import { span } from './span.js'
|
|
8
|
+
|
|
9
|
+
export const component = async (manifest) => {
|
|
21
10
|
const locator = new Locator(manifest.name, manifest.namespace)
|
|
22
|
-
|
|
11
|
+
|
|
12
|
+
return span(`component ${locator.id}`, () => create(manifest, locator))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const create = async (manifest, locator) => {
|
|
16
|
+
// what a test set after this package loaded goes to the store now, before a module of the
|
|
17
|
+
// component is imported: nothing of it is left in `process.env` for that module to read
|
|
18
|
+
environment.absorb()
|
|
19
|
+
|
|
20
|
+
await boot.extensions.load(manifest)
|
|
21
|
+
|
|
22
|
+
// the storage is told whether there will be an outbox, so the events come first
|
|
23
|
+
const events = boot.events(manifest)
|
|
24
|
+
const storage = await boot.storage(manifest, events !== undefined)
|
|
23
25
|
const context = await boot.context(manifest)
|
|
24
|
-
const emission = boot.emission(
|
|
26
|
+
const emission = await boot.emission(events, locator, context)
|
|
27
|
+
const outbox = boot.outbox(manifest, storage, emission)
|
|
25
28
|
|
|
26
29
|
let state
|
|
27
30
|
|
|
28
31
|
if (manifest.entity !== undefined) {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
32
|
+
const schemas = compile(manifest.entity)
|
|
33
|
+
const guards = await boot.guards(manifest, context)
|
|
34
|
+
const blank = manifest.entity.blank ?? {}
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
// fitted here rather than where the first record is written, so a component that
|
|
37
|
+
// declares a blank nothing can hold does not boot
|
|
38
|
+
const error = schemas.changeset.fit(blank)
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
: remap(manifest.operations, (definition, endpoint) =>
|
|
38
|
-
boot.operation(manifest, endpoint, definition, context, state))
|
|
40
|
+
if (error !== null)
|
|
41
|
+
throw new Error(`Component '${locator.id}' entity blank: ${error.message}`)
|
|
39
42
|
|
|
43
|
+
state = new State(
|
|
44
|
+
storage,
|
|
45
|
+
new entities.Factory(schemas, blank, guards),
|
|
46
|
+
outbox,
|
|
47
|
+
manifest.entity.associated
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const phases = await boot.rc(manifest, context)
|
|
52
|
+
const operations = await bootOperations(manifest, context, state, phases?.preflight)
|
|
40
53
|
const component = new Component(locator, operations)
|
|
41
54
|
|
|
42
55
|
if (storage) component.depends(storage)
|
|
43
|
-
if (emission) component.depends(emission)
|
|
44
56
|
|
|
45
|
-
|
|
57
|
+
// the outbox owns the emission and the storage, so it drains before either goes down
|
|
58
|
+
if (outbox) component.depends(outbox)
|
|
59
|
+
else if (emission) component.depends(emission)
|
|
60
|
+
|
|
61
|
+
const decorated = boot.extensions.component(component)
|
|
62
|
+
|
|
63
|
+
if (phases?.settle !== undefined) decorated.settle = phases.settle
|
|
64
|
+
|
|
65
|
+
// a dependency closes after its dependant, so the component is already closed
|
|
66
|
+
// when the RC releases what it opened
|
|
67
|
+
if (phases?.dispose !== undefined) decorated.depends(phases.dispose)
|
|
68
|
+
|
|
69
|
+
return decorated
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function bootOperations(manifest, context, state, preflight) {
|
|
73
|
+
if (manifest.operations === undefined) return {}
|
|
74
|
+
|
|
75
|
+
const entries = Object.entries(manifest.operations)
|
|
76
|
+
|
|
77
|
+
// each one loads its algorithm from disk and compiles its contracts
|
|
78
|
+
const booted = await Promise.all(
|
|
79
|
+
entries.map(([endpoint, definition]) =>
|
|
80
|
+
boot.operation(manifest, endpoint, definition, context, state, preflight)
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const operations = {}
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < entries.length; i++) operations[entries[i][0]] = booted[i]
|
|
87
|
+
|
|
88
|
+
return operations
|
|
46
89
|
}
|
|
47
90
|
|
|
48
|
-
|
|
91
|
+
/** What a stored record must fit, and what a changeset may. */
|
|
92
|
+
const compile = (entity) => ({
|
|
93
|
+
entity: compileSchema(declaration.schema(entity)),
|
|
94
|
+
changeset: compileSchema(declaration.changeset(entity))
|
|
95
|
+
})
|