@toa.io/boot 1.0.0-alpha.33 → 1.0.0-alpha.330
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 +388 -0
- package/package.json +11 -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 +18 -6
- package/src/bindings/emit.js +3 -6
- package/src/bindings/factory.js +5 -9
- package/src/bindings/inbound.js +11 -0
- package/src/bindings/index.js +7 -13
- package/src/bindings/outbound.js +10 -0
- package/src/bindings/produce.js +62 -17
- package/src/bindings/receive.js +3 -6
- package/src/bridge.js +22 -16
- package/src/call.js +29 -9
- package/src/cascade.js +16 -10
- package/src/component.js +101 -32
- package/src/composition.js +115 -15
- package/src/context.js +7 -8
- package/src/contract.js +11 -12
- 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/destinations.js +27 -0
- package/src/extensions/emitter.js +7 -9
- package/src/extensions/index.js +10 -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 +62 -13
- package/src/extensions/storage.js +6 -9
- package/src/extensions/tenants.js +5 -9
- package/src/guards.js +20 -0
- package/src/host.js +29 -0
- package/src/inbox.js +12 -0
- package/src/index.js +29 -34
- package/src/manifest.js +78 -36
- package/src/manifest.test.js +89 -8
- package/src/map.js +108 -0
- package/src/operation.js +29 -14
- package/src/outbox.js +22 -0
- package/src/rc.js +5 -0
- package/src/receivers.js +53 -23
- package/src/regional.js +18 -0
- package/src/regional.test.js +28 -0
- package/src/remote.js +41 -22
- package/src/rendering.js +50 -0
- package/src/span.js +19 -0
- package/src/storage.js +50 -16
- package/src/storage.test.js +57 -0
- package/src/workload.js +283 -0
- package/src/workload.test.js +321 -0
- package/types/bindings.d.ts +6 -2
- package/types/index.d.ts +62 -10
- package/types/manifest.d.ts +5 -4
- package/src/discovery.js +0 -43
- package/src/exposition.js +0 -17
package/src/bindings/produce.js
CHANGED
|
@@ -1,28 +1,73 @@
|
|
|
1
|
-
|
|
1
|
+
import { definition } from '@toa.io/norm'
|
|
2
|
+
import { LOOP } from './constants.js'
|
|
3
|
+
import { factory } from './factory.js'
|
|
2
4
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
+
export const produce = async (component, operations) => {
|
|
6
|
+
const local = []
|
|
7
|
+
const other = []
|
|
8
|
+
const stateful = addressed(operations)
|
|
9
|
+
const streams = streamed(operations)
|
|
5
10
|
|
|
6
|
-
const
|
|
7
|
-
|
|
11
|
+
for (const [binding, endpoints] of await group(operations)) {
|
|
12
|
+
const made = await factory(binding)
|
|
13
|
+
const carried = stateful.filter((endpoint) => endpoints.includes(endpoint))
|
|
14
|
+
const carries = streams.filter((endpoint) => endpoints.includes(endpoint))
|
|
15
|
+
const producer = made.producer(
|
|
16
|
+
component.locator,
|
|
17
|
+
endpoints,
|
|
18
|
+
component,
|
|
19
|
+
carried,
|
|
20
|
+
carries
|
|
21
|
+
)
|
|
22
|
+
const { properties } = (await definition(binding)).module
|
|
8
23
|
|
|
9
|
-
|
|
24
|
+
if (properties.local === true) local.push(producer)
|
|
25
|
+
else other.push(producer)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { local, other }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The endpoints that take one of their input properties as a stream. */
|
|
32
|
+
const streamed = (operations) =>
|
|
33
|
+
operations === undefined
|
|
34
|
+
? []
|
|
35
|
+
: Object.entries(operations)
|
|
36
|
+
.filter(([, operation]) => operation.stream !== undefined)
|
|
37
|
+
.map(([endpoint]) => endpoint)
|
|
38
|
+
|
|
39
|
+
/** The endpoints that take addressed calls only. */
|
|
40
|
+
const addressed = (operations) =>
|
|
41
|
+
operations === undefined
|
|
42
|
+
? []
|
|
43
|
+
: Object.entries(operations)
|
|
44
|
+
.filter(([, operation]) => operation.stateful === true)
|
|
45
|
+
.map(([endpoint]) => endpoint)
|
|
46
|
+
|
|
47
|
+
/** The endpoints each binding carries, as entries. */
|
|
48
|
+
const group = async (operations) => {
|
|
10
49
|
const map = {}
|
|
11
50
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
51
|
+
if (operations !== undefined)
|
|
52
|
+
for (const [endpoint, operation] of Object.entries(operations)) {
|
|
53
|
+
// noinspection JSUnresolvedVariable
|
|
54
|
+
const bindings = global.TOA_INTEGRATION_BINDINGS_LOOP_DISABLED
|
|
55
|
+
? operation.bindings
|
|
56
|
+
: [LOOP].concat(operation.bindings)
|
|
17
57
|
|
|
18
|
-
|
|
19
|
-
|
|
58
|
+
for (const binding of bindings) {
|
|
59
|
+
// an endpoint that takes a stream is served by the bindings that carry one, and by no other
|
|
60
|
+
if (operation.stream !== undefined && !(await carries(binding))) continue
|
|
20
61
|
|
|
21
|
-
|
|
62
|
+
if (!map[binding]) map[binding] = []
|
|
63
|
+
|
|
64
|
+
map[binding].push(endpoint)
|
|
65
|
+
}
|
|
22
66
|
}
|
|
23
|
-
}
|
|
24
67
|
|
|
25
|
-
return Object.entries(map)
|
|
68
|
+
return Object.entries(map)
|
|
26
69
|
}
|
|
27
70
|
|
|
28
|
-
|
|
71
|
+
/** Whether a binding carries a call whose input holds a stream, which only its module says. */
|
|
72
|
+
export const carries = async (binding) =>
|
|
73
|
+
(await definition(binding)).module.properties.streams === true
|
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,35 @@
|
|
|
1
|
-
|
|
1
|
+
import { Call, safe, Transmission } from '@toa.io/core'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import * as boot from './index.js'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
5
|
+
// eslint-disable-next-line max-params
|
|
6
|
+
export const call = async (locator, endpoint, definition, entity, source) => {
|
|
7
|
+
const consumers = await boot.bindings.consume(
|
|
8
|
+
locator,
|
|
9
|
+
endpoint,
|
|
10
|
+
definition.bindings,
|
|
11
|
+
definition.stream !== undefined
|
|
12
|
+
)
|
|
9
13
|
const transmission = new Transmission(consumers)
|
|
10
14
|
const contract = boot.contract.request(definition, entity)
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
}
|
|
16
|
+
const stateful = definition.stateful === true
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
/*
|
|
19
|
+
* An endpoint beginning with `.` is the runtime's own rather than an operation — a lookup, an
|
|
20
|
+
* exposition — so the safety of operations has nothing to say about it. `Component.invoke`
|
|
21
|
+
* counts it as no hop in a call chain for the same reason.
|
|
22
|
+
*/
|
|
23
|
+
const readable = endpoint[0] === '.' || safe(definition.type)
|
|
24
|
+
|
|
25
|
+
return new Call(
|
|
26
|
+
transmission,
|
|
27
|
+
contract,
|
|
28
|
+
`${locator.id}.${endpoint}`,
|
|
29
|
+
source,
|
|
30
|
+
stateful,
|
|
31
|
+
readable,
|
|
32
|
+
definition.stream,
|
|
33
|
+
definition.once === true || typeof definition.once === 'number'
|
|
34
|
+
)
|
|
35
|
+
}
|
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,117 @@
|
|
|
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
|
+
// the suffix is what it is now, `.env` having been read: whatever this process sets for the
|
|
21
|
+
// processes it starts later does not rename what it has
|
|
22
|
+
environment.suffix()
|
|
23
|
+
|
|
24
|
+
await boot.extensions.load(manifest)
|
|
25
|
+
|
|
26
|
+
// the storage is told whether there will be an outbox, so what it publishes to comes first:
|
|
27
|
+
// its own events, and whatever an extension adds
|
|
28
|
+
const events = boot.events(manifest)
|
|
29
|
+
const destinations = await boot.extensions.destinations(manifest)
|
|
30
|
+
const storage = await boot.storage(
|
|
31
|
+
manifest,
|
|
32
|
+
events !== undefined || destinations.length > 0,
|
|
33
|
+
boot.inbox(manifest)
|
|
34
|
+
)
|
|
23
35
|
const context = await boot.context(manifest)
|
|
24
|
-
const emission = boot.emission(
|
|
36
|
+
const emission = await boot.emission(events, locator, context)
|
|
37
|
+
|
|
38
|
+
// a destination that renders the component's events renders them with its context
|
|
39
|
+
await boot.rendering(manifest, destinations, context)
|
|
40
|
+
|
|
41
|
+
// and one that carries a change to other regions carries what the regional ones write
|
|
42
|
+
boot.regional(destinations)
|
|
43
|
+
|
|
44
|
+
const outbox = boot.outbox(manifest, storage, emission, destinations)
|
|
25
45
|
|
|
26
46
|
let state
|
|
27
47
|
|
|
28
48
|
if (manifest.entity !== undefined) {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
49
|
+
const schemas = compile(manifest.entity)
|
|
50
|
+
const guards = await boot.guards(manifest, context)
|
|
51
|
+
const blank = manifest.entity.blank ?? {}
|
|
31
52
|
|
|
32
|
-
|
|
33
|
-
|
|
53
|
+
// fitted here rather than where the first record is written, so a component that
|
|
54
|
+
// declares a blank nothing can hold does not boot
|
|
55
|
+
const error = schemas.changeset.fit(blank)
|
|
56
|
+
|
|
57
|
+
if (error !== null)
|
|
58
|
+
throw new Error(`Component '${locator.id}' entity blank: ${error.message}`)
|
|
34
59
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
60
|
+
state = new State(
|
|
61
|
+
storage,
|
|
62
|
+
new entities.Factory(schemas, blank, guards),
|
|
63
|
+
outbox,
|
|
64
|
+
manifest.entity.associated
|
|
65
|
+
)
|
|
66
|
+
}
|
|
39
67
|
|
|
68
|
+
const phases = await boot.rc(manifest, context)
|
|
69
|
+
const operations = await bootOperations(manifest, context, state, phases?.preflight)
|
|
40
70
|
const component = new Component(locator, operations)
|
|
41
71
|
|
|
42
72
|
if (storage) component.depends(storage)
|
|
43
|
-
if (emission) component.depends(emission)
|
|
44
73
|
|
|
45
|
-
|
|
74
|
+
// the outbox owns the emission and the storage, so it drains before either goes down
|
|
75
|
+
if (outbox) component.depends(outbox)
|
|
76
|
+
else if (emission) component.depends(emission)
|
|
77
|
+
|
|
78
|
+
const decorated = boot.extensions.component(component)
|
|
79
|
+
|
|
80
|
+
if (phases?.settle !== undefined) decorated.settle = phases.settle
|
|
81
|
+
|
|
82
|
+
if (phases?.ready !== undefined) decorated.ready = phases.ready
|
|
83
|
+
|
|
84
|
+
// a dependency closes after its dependant, so the component is already closed
|
|
85
|
+
// when the RC releases what it opened
|
|
86
|
+
if (phases?.dispose !== undefined) decorated.depends(phases.dispose)
|
|
87
|
+
|
|
88
|
+
// a dependency of the component, so the walk that stops a tree reaches it there
|
|
89
|
+
if (phases?.quiescence !== undefined) decorated.depends(phases.quiescence)
|
|
90
|
+
|
|
91
|
+
return decorated
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function bootOperations(manifest, context, state, preflight) {
|
|
95
|
+
if (manifest.operations === undefined) return {}
|
|
96
|
+
|
|
97
|
+
const entries = Object.entries(manifest.operations)
|
|
98
|
+
|
|
99
|
+
// each one loads its algorithm from disk and compiles its contracts
|
|
100
|
+
const booted = await Promise.all(
|
|
101
|
+
entries.map(([endpoint, definition]) =>
|
|
102
|
+
boot.operation(manifest, endpoint, definition, context, state, preflight)
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
const operations = {}
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < entries.length; i++) operations[entries[i][0]] = booted[i]
|
|
109
|
+
|
|
110
|
+
return operations
|
|
46
111
|
}
|
|
47
112
|
|
|
48
|
-
|
|
113
|
+
/** What a stored record must fit, and what a changeset may. */
|
|
114
|
+
const compile = (entity) => ({
|
|
115
|
+
entity: compileSchema(declaration.schema(entity)),
|
|
116
|
+
changeset: compileSchema(declaration.changeset(entity))
|
|
117
|
+
})
|
package/src/composition.js
CHANGED
|
@@ -1,24 +1,124 @@
|
|
|
1
|
-
|
|
1
|
+
import { console } from 'openspan'
|
|
2
|
+
import { Composition } from '@toa.io/core'
|
|
3
|
+
import { version } from '@toa.io/definitions'
|
|
4
|
+
import { environment } from '@toa.io/generic'
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
import * as boot from './index.js'
|
|
7
|
+
import { span } from './span.js'
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
export async function composition(paths, options) {
|
|
10
|
+
// what a test set after this package loaded goes to the store now, before a module of a
|
|
11
|
+
// component is imported: nothing of it is left in `process.env` for that module to read
|
|
12
|
+
environment.absorb()
|
|
13
|
+
|
|
14
|
+
// the suffix is what it is now, `.env` having been read: whatever this process sets for the
|
|
15
|
+
// processes it starts later does not rename what it has
|
|
16
|
+
environment.suffix()
|
|
6
17
|
|
|
7
|
-
async function composition (paths, options) {
|
|
8
18
|
options = Object.assign({}, options)
|
|
9
19
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
return span('boot composition', async () => {
|
|
21
|
+
const manifests = await span(
|
|
22
|
+
'load manifests',
|
|
23
|
+
async () => await Promise.all(paths.map((path) => boot.manifest(path, options)))
|
|
24
|
+
)
|
|
14
25
|
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
console.info('Starting composition', {
|
|
27
|
+
runtime: version,
|
|
28
|
+
components: manifests.map((manifest) => manifest.locator.id)
|
|
29
|
+
})
|
|
17
30
|
|
|
18
|
-
|
|
19
|
-
boot.
|
|
31
|
+
// what this process composes it knows, and the map it was given says the same
|
|
32
|
+
await boot.map.compose(manifests)
|
|
20
33
|
|
|
21
|
-
|
|
22
|
-
|
|
34
|
+
const tenants = await span(
|
|
35
|
+
'create tenants',
|
|
36
|
+
async () => await Promise.all(manifests.map(boot.extensions.tenants))
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const components = await span(
|
|
40
|
+
'create components',
|
|
41
|
+
async () => await Promise.all(manifests.map(boot.component))
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const groups = await Promise.all(
|
|
45
|
+
components.map(
|
|
46
|
+
async (component, index) =>
|
|
47
|
+
await boot.bindings.produce(component, manifests[index].operations)
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const receivers = await span(
|
|
52
|
+
'create receivers',
|
|
53
|
+
async () =>
|
|
54
|
+
await Promise.all(
|
|
55
|
+
components.map((component, index) =>
|
|
56
|
+
boot.receivers(manifests[index], component)
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const producers = []
|
|
62
|
+
const settles = []
|
|
63
|
+
const readies = []
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < components.length; i++) {
|
|
66
|
+
const { local, other } = groups[i]
|
|
67
|
+
const { settle, ready } = components[i]
|
|
68
|
+
const serving = [...local, ...other]
|
|
69
|
+
|
|
70
|
+
producers.push(...serving)
|
|
71
|
+
|
|
72
|
+
/*
|
|
73
|
+
* A receiver turns an event into a call to an operation of its own component, and
|
|
74
|
+
* that call is served by these — the loop producer first, the broker's if it has
|
|
75
|
+
* already gone. They are siblings under the composition otherwise, so a receiver
|
|
76
|
+
* still draining a delivery would be left calling something already torn down.
|
|
77
|
+
*/
|
|
78
|
+
// one at a time: `depends` given an array links the group it makes rather than its
|
|
79
|
+
// members, and it is the members whose teardown has to wait for this one
|
|
80
|
+
for (const receiver of receivers[i])
|
|
81
|
+
for (const producer of serving) receiver.depends(producer)
|
|
82
|
+
|
|
83
|
+
/*
|
|
84
|
+
* A tenant calls its own component as well — a pulse calls an operation, the dispatcher of
|
|
85
|
+
* delayed calls writes the rows it has made the calls of — and what it has in hand when it
|
|
86
|
+
* closes it finishes through these. A sibling, it would be left waiting for a reply from
|
|
87
|
+
* something torn down beside it, which is a shutdown that never ends.
|
|
88
|
+
*/
|
|
89
|
+
for (const tenant of tenants[i])
|
|
90
|
+
for (const producer of serving) tenant.depends(producer)
|
|
23
91
|
|
|
24
|
-
|
|
92
|
+
/*
|
|
93
|
+
* The broker producers serve the stateful endpoints under the process's name, and the
|
|
94
|
+
* name is held once they are open — before then a call to it is refused. `ready` is where
|
|
95
|
+
* a component hands the name out, so it waits for every producer and receiver of its own.
|
|
96
|
+
*/
|
|
97
|
+
if (ready !== undefined) {
|
|
98
|
+
for (const producer of serving) ready.depends(producer)
|
|
99
|
+
|
|
100
|
+
for (const receiver of receivers[i]) ready.depends(receiver)
|
|
101
|
+
|
|
102
|
+
readies.push(ready)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (settle === undefined) continue
|
|
106
|
+
|
|
107
|
+
if (local.length > 0) settle.depends(local)
|
|
108
|
+
|
|
109
|
+
for (const producer of other) producer.depends(settle)
|
|
110
|
+
|
|
111
|
+
for (const receiver of receivers[i]) receiver.depends(settle)
|
|
112
|
+
|
|
113
|
+
settles.push(settle)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const composition = new Composition(
|
|
117
|
+
producers.concat(settles, readies),
|
|
118
|
+
receivers.flat(),
|
|
119
|
+
tenants.flat()
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return boot.extensions.manage(composition)
|
|
123
|
+
})
|
|
124
|
+
}
|
package/src/context.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { Context, Locator } from '@toa.io/core'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import * as boot from './index.js'
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const context = async (manifest) => {
|
|
8
|
-
const local = await boot.remote(manifest.locator, manifest)
|
|
5
|
+
export const context = async (manifest) => {
|
|
6
|
+
const local = await boot.remote(manifest.locator, undefined, { contract: manifest })
|
|
9
7
|
const aspects = boot.extensions.aspects(manifest)
|
|
10
8
|
|
|
9
|
+
// a system aspect: it is there in every component, declared by none of them
|
|
10
|
+
aspects.push(boot.atom(manifest.locator.id))
|
|
11
|
+
|
|
11
12
|
const lookup = async (namespace, name) => {
|
|
12
13
|
const locator = new Locator(name, namespace)
|
|
13
14
|
const remote = await boot.remote(locator)
|
|
@@ -21,5 +22,3 @@ const context = async (manifest) => {
|
|
|
21
22
|
|
|
22
23
|
return boot.extensions.context(context)
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
exports.context = context
|
package/src/contract.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
import { contract } from '@toa.io/core'
|
|
2
|
+
import * as schemas from '@toa.io/schemas'
|
|
2
3
|
|
|
3
|
-
const {
|
|
4
|
-
const { Schema } = require('@toa.io/schema')
|
|
4
|
+
const { Request, Reply } = contract
|
|
5
5
|
|
|
6
|
-
const request = (definition, entity) => {
|
|
6
|
+
export const request = (definition, entity) => {
|
|
7
7
|
const request = Request.schema(definition, entity)
|
|
8
|
-
const schema =
|
|
8
|
+
const schema = schemas.schema(request)
|
|
9
9
|
|
|
10
|
-
return new Request(schema)
|
|
10
|
+
return new Request(schema, definition, entity)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const reply = (output,
|
|
14
|
-
const reply = Reply.schema(output,
|
|
15
|
-
|
|
13
|
+
export const reply = (output, errors) => {
|
|
14
|
+
const reply = Reply.schema(output, errors)
|
|
15
|
+
|
|
16
|
+
// a reply is read, not shaped: a value that only fits once coerced does not fit
|
|
17
|
+
const schema = schemas.schema(reply, { coerceTypes: false })
|
|
16
18
|
|
|
17
19
|
return new Reply(schema)
|
|
18
20
|
}
|
|
19
|
-
|
|
20
|
-
exports.request = request
|
|
21
|
-
exports.reply = reply
|
package/src/emission.js
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
import { Emission, Event } from '@toa.io/core'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import * as boot from './index.js'
|
|
4
|
+
import * as extensions from './extensions/index.js'
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
-
const extensions = require('./extensions')
|
|
7
|
-
|
|
8
|
-
const emission = (definitions, locator, context) => {
|
|
6
|
+
export const emission = async (definitions, locator, context) => {
|
|
9
7
|
if (definitions === undefined) return
|
|
10
8
|
|
|
11
|
-
const events =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const events = await Promise.all(
|
|
10
|
+
Object.entries(definitions).map(async ([label, definition]) => {
|
|
11
|
+
const emitter = await boot.bindings.emit(definition.binding, locator, label)
|
|
12
|
+
const decorator = extensions.emitter(emitter, label, locator)
|
|
13
|
+
const bridge = await boot.bridge.event(
|
|
14
|
+
definition.bridge,
|
|
15
|
+
definition.path,
|
|
16
|
+
label,
|
|
17
|
+
context
|
|
18
|
+
)
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
return new Event(
|
|
21
|
+
{ ...definition, label: `${locator.id}.${label}` },
|
|
22
|
+
decorator,
|
|
23
|
+
bridge
|
|
24
|
+
)
|
|
25
|
+
})
|
|
26
|
+
)
|
|
18
27
|
|
|
19
28
|
return new Emission(events)
|
|
20
29
|
}
|
|
21
|
-
|
|
22
|
-
exports.emission = emission
|