@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.
Files changed (62) hide show
  1. package/CHANGELOG.md +388 -0
  2. package/package.json +11 -8
  3. package/src/atom.js +41 -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 +18 -6
  8. package/src/bindings/emit.js +3 -6
  9. package/src/bindings/factory.js +5 -9
  10. package/src/bindings/inbound.js +11 -0
  11. package/src/bindings/index.js +7 -13
  12. package/src/bindings/outbound.js +10 -0
  13. package/src/bindings/produce.js +62 -17
  14. package/src/bindings/receive.js +3 -6
  15. package/src/bridge.js +22 -16
  16. package/src/call.js +29 -9
  17. package/src/cascade.js +16 -10
  18. package/src/component.js +101 -32
  19. package/src/composition.js +115 -15
  20. package/src/context.js +7 -8
  21. package/src/contract.js +11 -12
  22. package/src/emission.js +21 -14
  23. package/src/env.js +19 -12
  24. package/src/events.js +27 -0
  25. package/src/extensions/aspects.js +4 -8
  26. package/src/extensions/component.js +4 -8
  27. package/src/extensions/context.js +4 -8
  28. package/src/extensions/destinations.js +27 -0
  29. package/src/extensions/emitter.js +7 -9
  30. package/src/extensions/index.js +10 -19
  31. package/src/extensions/instances.js +2 -4
  32. package/src/extensions/load.js +7 -7
  33. package/src/extensions/manage.js +15 -0
  34. package/src/extensions/receiver.js +5 -9
  35. package/src/extensions/resolve.js +62 -13
  36. package/src/extensions/storage.js +6 -9
  37. package/src/extensions/tenants.js +5 -9
  38. package/src/guards.js +20 -0
  39. package/src/host.js +29 -0
  40. package/src/inbox.js +12 -0
  41. package/src/index.js +29 -34
  42. package/src/manifest.js +78 -36
  43. package/src/manifest.test.js +89 -8
  44. package/src/map.js +108 -0
  45. package/src/operation.js +29 -14
  46. package/src/outbox.js +22 -0
  47. package/src/rc.js +5 -0
  48. package/src/receivers.js +53 -23
  49. package/src/regional.js +18 -0
  50. package/src/regional.test.js +28 -0
  51. package/src/remote.js +41 -22
  52. package/src/rendering.js +50 -0
  53. package/src/span.js +19 -0
  54. package/src/storage.js +50 -16
  55. package/src/storage.test.js +57 -0
  56. package/src/workload.js +283 -0
  57. package/src/workload.test.js +321 -0
  58. package/types/bindings.d.ts +6 -2
  59. package/types/index.d.ts +62 -10
  60. package/types/manifest.d.ts +5 -4
  61. package/src/discovery.js +0 -43
  62. package/src/exposition.js +0 -17
@@ -1,28 +1,73 @@
1
- 'use strict'
1
+ import { definition } from '@toa.io/norm'
2
+ import { LOOP } from './constants.js'
3
+ import { factory } from './factory.js'
2
4
 
3
- const { LOOP } = require('./constants')
4
- const { factory } = require('./factory')
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 produce = (component, operations) => group(operations, (factory, endpoints) =>
7
- factory.producer(component.locator, endpoints, component))
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
- const group = (operations, callback) => {
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
- 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)
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
- for (const binding of bindings) {
19
- if (!map[binding]) map[binding] = []
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
- map[binding].push(endpoint)
62
+ if (!map[binding]) map[binding] = []
63
+
64
+ map[binding].push(endpoint)
65
+ }
22
66
  }
23
- }
24
67
 
25
- return Object.entries(map).map(([binding, endpoints]) => callback(factory(binding), endpoints))
68
+ return Object.entries(map)
26
69
  }
27
70
 
28
- exports.produce = produce
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
@@ -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,34 @@
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) return
23
+
24
+ return factory.rc(path, context)
24
25
  }
25
26
 
26
- exports.algorithm = algorithm
27
- exports.event = event
28
- exports.receiver = receiver
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
- 'use strict'
1
+ import { Call, safe, 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(
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
- return new Call(transmission, contract)
13
- }
16
+ const stateful = definition.stateful === true
14
17
 
15
- exports.call = call
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
- '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(
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(operation.bridge, prototype.path, endpoint, context)
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
- '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 { 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
- const storage = boot.storage(manifest)
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(manifest.events, locator, context)
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 schema = new Schema(manifest.entity.schema)
30
- const entity = new entities.Factory(schema)
49
+ const schemas = compile(manifest.entity)
50
+ const guards = await boot.guards(manifest, context)
51
+ const blank = manifest.entity.blank ?? {}
31
52
 
32
- state = new State(storage, entity, emission, manifest.entity.associated)
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
- const operations = manifest.operations === undefined
36
- ? {}
37
- : remap(manifest.operations, (definition, endpoint) =>
38
- boot.operation(manifest, endpoint, definition, context, state))
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
- return boot.extensions.component(component)
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
- exports.component = component
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
+ })
@@ -1,24 +1,124 @@
1
- 'use strict'
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
- const { Composition } = require('@toa.io/core')
6
+ import * as boot from './index.js'
7
+ import { span } from './span.js'
4
8
 
5
- const boot = require('./index')
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
- const manifests = await Promise.all(paths.map((path) => boot.manifest(path, options)))
11
- const tenants = await Promise.all(manifests.map(boot.extensions.tenants))
12
- const expositions = await Promise.all(manifests.map(boot.discovery.expose))
13
- const components = await Promise.all(manifests.map(boot.component))
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
- const producers = components.map((component, index) =>
16
- boot.bindings.produce(component, manifests[index].operations))
26
+ console.info('Starting composition', {
27
+ runtime: version,
28
+ components: manifests.map((manifest) => manifest.locator.id)
29
+ })
17
30
 
18
- const receivers = await Promise.all(components.map((component, index) =>
19
- boot.receivers(manifests[index], component)))
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
- return new Composition(expositions.flat(), producers.flat(), receivers.flat(), tenants.flat())
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
- exports.composition = composition
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
- 'use strict'
1
+ import { Context, Locator } from '@toa.io/core'
2
2
 
3
- const { Context, Locator } = require('@toa.io/core')
3
+ import * as boot from './index.js'
4
4
 
5
- const boot = require('./index')
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
- 'use strict'
1
+ import { contract } from '@toa.io/core'
2
+ import * as schemas from '@toa.io/schemas'
2
3
 
3
- const { contract: { Request, Reply } } = require('@toa.io/core')
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 = new Schema(request, { removeAdditional: true }) // soft inputs
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, error) => {
14
- const reply = Reply.schema(output, error)
15
- const schema = new Schema(reply) // outputs strict
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
- 'use strict'
1
+ import { Emission, Event } from '@toa.io/core'
2
2
 
3
- const { Emission, Event } = require('@toa.io/core')
3
+ import * as boot from './index.js'
4
+ import * as extensions from './extensions/index.js'
4
5
 
5
- const boot = require('./index')
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 = Object.entries(definitions).map(([label, definition]) => {
12
- const emitter = boot.bindings.emit(definition.binding, locator, label)
13
- const decorator = extensions.emitter(emitter, label)
14
- const bridge = boot.bridge.event(definition.bridge, definition.path, label, context)
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
- return new Event(definition, decorator, bridge)
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