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

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/boot",
3
- "version": "1.0.0-alpha.283",
3
+ "version": "1.0.0-alpha.286",
4
4
  "type": "module",
5
5
  "description": "Toa Boot",
6
6
  "author": "temich <tema.gurtovoy@gmail.com>",
@@ -21,14 +21,14 @@
21
21
  "test": "echo \"Error: run tests from root\" && exit 1"
22
22
  },
23
23
  "dependencies": {
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",
24
+ "@toa.io/atomicity": "1.0.0-alpha.286",
25
+ "@toa.io/core": "1.0.0-alpha.286",
26
+ "@toa.io/generic": "1.0.0-alpha.286",
27
+ "@toa.io/norm": "1.0.0-alpha.286",
28
+ "@toa.io/schemas": "1.0.0-alpha.286",
29
29
  "clone-deep": "4.0.1",
30
30
  "dotenv": "17.4.2",
31
- "openspan": "1.0.0-alpha.277"
31
+ "openspan": "1.0.0-alpha.286"
32
32
  },
33
- "gitHead": "2f52b023bed0ccb2ecdb2b82fe59fbe38bf282e1"
33
+ "gitHead": "81f3d0688f17668b902be336d1749cc715edcdb6"
34
34
  }
package/src/atom.js CHANGED
@@ -6,14 +6,14 @@ import * as boot from './index.js'
6
6
  * The atom as a system aspect. Every component has `context.atom`, with nothing declared in its
7
7
  * manifest and nothing of its own to configure.
8
8
  *
9
- * @implements {toa.core.extensions.Aspect}
9
+ * @implements {import('@toa.io/core/types').extensions.Aspect}
10
10
  */
11
11
  class Aspect extends Connector {
12
12
  name = 'atom'
13
13
 
14
14
  #atom
15
15
 
16
- constructor (atom) {
16
+ constructor(atom) {
17
17
  super()
18
18
 
19
19
  this.#atom = atom
@@ -23,12 +23,16 @@ class Aspect extends Connector {
23
23
 
24
24
  // the methods are named rather than forwarded, so that the connector's lifecycle is not
25
25
  // reachable from an algorithm
26
- invoke (method, ...args) {
26
+ invoke(method, ...args) {
27
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`)
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`)
32
36
  }
33
37
  }
34
38
  }
package/src/atomicity.js CHANGED
@@ -6,6 +6,6 @@ import { Factory } from '@toa.io/atomicity'
6
6
  * see the connector's readme.
7
7
  *
8
8
  * @param {string} group
9
- * @returns {toa.core.atomicity.Atom}
9
+ * @returns {import('@toa.io/core/types').atomicity.Atom}
10
10
  */
11
11
  export const atomicity = (group) => new Factory().atom(group)
@@ -2,5 +2,8 @@ import { LOOP } from './constants.js'
2
2
  import { factory } from './factory.js'
3
3
 
4
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)))
5
+ await Promise.all(
6
+ [LOOP]
7
+ .concat(bindings)
8
+ .map(async (binding) => (await factory(binding)).consumer(locator, endpoint))
9
+ )
@@ -10,10 +10,8 @@ export const produce = async (component, operations) => {
10
10
  const producer = made.producer(component.locator, endpoints, component)
11
11
  const { properties } = await import(binding)
12
12
 
13
- if (properties.local === true)
14
- local.push(producer)
15
- else
16
- other.push(producer)
13
+ if (properties.local === true) local.push(producer)
14
+ else other.push(producer)
17
15
  }
18
16
 
19
17
  return { local, other }
package/src/bridge.js CHANGED
@@ -1,4 +1,4 @@
1
- export async function algorithm (bridge, path, endpoint, context) {
1
+ export async function algorithm(bridge, path, endpoint, context) {
2
2
  const factory = await resolve(bridge)
3
3
  const algorithm = await factory.algorithm(path, endpoint, context)
4
4
 
@@ -16,11 +16,10 @@ export const receiver = async (bridge, path, label) =>
16
16
  export const guard = async (bridge, path, label, context) =>
17
17
  (await resolve(bridge)).guard(path, label, context)
18
18
 
19
- export async function rc (bridge, path, context) {
19
+ export async function rc(bridge, path, context) {
20
20
  const factory = await resolve(bridge)
21
21
 
22
- if (factory.rc === undefined)
23
- return
22
+ if (factory.rc === undefined) return
24
23
 
25
24
  return factory.rc(path, context)
26
25
  }
package/src/cascade.js CHANGED
@@ -2,13 +2,18 @@ import { Cascade } from '@toa.io/core'
2
2
 
3
3
  import * as boot from './index.js'
4
4
 
5
- export async function cascade (manifest, endpoint, definition, context, preflight) {
5
+ export async function cascade(manifest, endpoint, definition, context, preflight) {
6
6
  const bridges = []
7
7
 
8
8
  if (definition.forward) endpoint = definition.forward
9
9
 
10
10
  if (definition.bridge) {
11
- const bridge = await 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
+ )
12
17
 
13
18
  bridges.unshift(bridge)
14
19
  }
@@ -20,7 +25,12 @@ export async function cascade (manifest, endpoint, definition, context, prefligh
20
25
 
21
26
  if (operation === undefined) continue
22
27
 
23
- const bridge = await 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
+ )
24
34
 
25
35
  bridges.unshift(bridge)
26
36
  }
package/src/component.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Component, Locator, State, entities } from '@toa.io/core'
2
- import * as schemas from '@toa.io/schemas'
2
+ import { entity as declaration } from '@toa.io/norm'
3
+ import { schema as compileSchema } from '@toa.io/schemas'
3
4
 
4
5
  import * as boot from './index.js'
5
6
  import { span } from './span.js'
@@ -23,11 +24,23 @@ const create = async (manifest, locator) => {
23
24
  let state
24
25
 
25
26
  if (manifest.entity !== undefined) {
26
- const schema = schemas.schema(manifest.entity.schema)
27
+ const schemas = compile(manifest.entity)
27
28
  const guards = await boot.guards(manifest, context)
28
- const entity = new entities.Factory(schema, guards)
29
+ const blank = manifest.entity.blank ?? {}
29
30
 
30
- state = new State(storage, entity, outbox, manifest.entity.associated)
31
+ // fitted here rather than where the first record is written, so a component that
32
+ // declares a blank nothing can hold does not boot
33
+ const error = schemas.changeset.fit(blank)
34
+
35
+ if (error !== null)
36
+ throw new Error(`Component '${locator.id}' entity blank: ${error.message}`)
37
+
38
+ state = new State(
39
+ storage,
40
+ new entities.Factory(schemas, blank, guards),
41
+ outbox,
42
+ manifest.entity.associated
43
+ )
31
44
  }
32
45
 
33
46
  const phases = await boot.rc(manifest, context)
@@ -42,31 +55,36 @@ const create = async (manifest, locator) => {
42
55
 
43
56
  const decorated = boot.extensions.component(component)
44
57
 
45
- if (phases?.settle !== undefined)
46
- decorated.settle = phases.settle
58
+ if (phases?.settle !== undefined) decorated.settle = phases.settle
47
59
 
48
60
  // a dependency closes after its dependant, so the component is already closed
49
61
  // when the RC releases what it opened
50
- if (phases?.dispose !== undefined)
51
- decorated.depends(phases.dispose)
62
+ if (phases?.dispose !== undefined) decorated.depends(phases.dispose)
52
63
 
53
64
  return decorated
54
65
  }
55
66
 
56
- async function bootOperations (manifest, context, state, preflight) {
57
- if (manifest.operations === undefined)
58
- return {}
67
+ async function bootOperations(manifest, context, state, preflight) {
68
+ if (manifest.operations === undefined) return {}
59
69
 
60
70
  const entries = Object.entries(manifest.operations)
61
71
 
62
72
  // 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)))
73
+ const booted = await Promise.all(
74
+ entries.map(([endpoint, definition]) =>
75
+ boot.operation(manifest, endpoint, definition, context, state, preflight)
76
+ )
77
+ )
65
78
 
66
79
  const operations = {}
67
80
 
68
- for (let i = 0; i < entries.length; i++)
69
- operations[entries[i][0]] = booted[i]
81
+ for (let i = 0; i < entries.length; i++) operations[entries[i][0]] = booted[i]
70
82
 
71
83
  return operations
72
84
  }
85
+
86
+ /** What a stored record must fit, and what a changeset may. */
87
+ const compile = (entity) => ({
88
+ entity: compileSchema(declaration.schema(entity)),
89
+ changeset: compileSchema(declaration.changeset(entity))
90
+ })
@@ -5,34 +5,52 @@ import { version } from '@toa.io/runtime'
5
5
  import * as boot from './index.js'
6
6
  import { span } from './span.js'
7
7
 
8
- export async function composition (paths, options) {
8
+ export async function composition(paths, options) {
9
9
  options = Object.assign({}, options)
10
10
 
11
11
  return span('boot composition', async () => {
12
- const manifests = await span('load manifests',
13
- async () => await Promise.all(paths.map((path) => boot.manifest(path, options))))
12
+ const manifests = await span(
13
+ 'load manifests',
14
+ async () => await Promise.all(paths.map((path) => boot.manifest(path, options)))
15
+ )
14
16
 
15
17
  console.info('Starting composition', {
16
18
  runtime: version,
17
19
  components: manifests.map((manifest) => manifest.locator.id)
18
20
  })
19
21
 
20
- const tenants = await span('create tenants',
21
- async () => await Promise.all(manifests.map(boot.extensions.tenants)))
22
+ const tenants = await span(
23
+ 'create tenants',
24
+ async () => await Promise.all(manifests.map(boot.extensions.tenants))
25
+ )
22
26
 
23
- const expositions = await span('expose discovery',
24
- async () => await Promise.all(manifests.map(boot.discovery.expose)))
27
+ const expositions = await span(
28
+ 'expose discovery',
29
+ async () => await Promise.all(manifests.map(boot.discovery.expose))
30
+ )
25
31
 
26
32
  try {
27
- const components = await span('create components',
28
- async () => await Promise.all(manifests.map(boot.component)))
29
-
30
- const groups = await Promise.all(components.map(async (component, index) =>
31
- await boot.bindings.produce(component, manifests[index].operations)))
32
-
33
- const receivers = await span('create receivers',
34
- async () => await Promise.all(components.map((component, index) =>
35
- boot.receivers(manifests[index], component))))
33
+ const components = await span(
34
+ 'create components',
35
+ async () => await Promise.all(manifests.map(boot.component))
36
+ )
37
+
38
+ const groups = await Promise.all(
39
+ components.map(
40
+ async (component, index) =>
41
+ await boot.bindings.produce(component, manifests[index].operations)
42
+ )
43
+ )
44
+
45
+ const receivers = await span(
46
+ 'create receivers',
47
+ async () =>
48
+ await Promise.all(
49
+ components.map((component, index) =>
50
+ boot.receivers(manifests[index], component)
51
+ )
52
+ )
53
+ )
36
54
 
37
55
  const producers = []
38
56
  const settles = []
@@ -55,22 +73,23 @@ export async function composition (paths, options) {
55
73
  for (const receiver of receivers[i])
56
74
  for (const producer of serving) receiver.depends(producer)
57
75
 
58
- if (settle === undefined)
59
- continue
76
+ if (settle === undefined) continue
60
77
 
61
- if (local.length > 0)
62
- settle.depends(local)
78
+ if (local.length > 0) settle.depends(local)
63
79
 
64
- for (const producer of other)
65
- producer.depends(settle)
80
+ for (const producer of other) producer.depends(settle)
66
81
 
67
- for (const receiver of receivers[i])
68
- receiver.depends(settle)
82
+ for (const receiver of receivers[i]) receiver.depends(settle)
69
83
 
70
84
  settles.push(settle)
71
85
  }
72
86
 
73
- const composition = new Composition(expositions.flat(), producers.concat(settles), receivers.flat(), tenants.flat())
87
+ const composition = new Composition(
88
+ expositions.flat(),
89
+ producers.concat(settles),
90
+ receivers.flat(),
91
+ tenants.flat()
92
+ )
74
93
 
75
94
  /*
76
95
  * A lookup that is never answered holds a connection open, and whoever
package/src/contract.js CHANGED
@@ -5,7 +5,7 @@ const { Request, Reply } = contract
5
5
 
6
6
  export const request = (definition, entity) => {
7
7
  const request = Request.schema(definition, entity)
8
- const schema = schemas.schema(request, { removeAdditional: true })
8
+ const schema = schemas.schema(request)
9
9
 
10
10
  return new Request(schema, definition)
11
11
  }
@@ -13,9 +13,8 @@ export const request = (definition, entity) => {
13
13
  export const reply = (output, errors) => {
14
14
  const reply = Reply.schema(output, errors)
15
15
 
16
- // a reply is read, not shaped: what the operation returned is what the caller gets,
17
- // and a value that only fits once coerced does not fit
18
- const schema = schemas.schema(reply, { coerceTypes: false, useDefaults: false })
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 })
19
18
 
20
19
  return new Reply(schema)
21
20
  }
package/src/emission.js CHANGED
@@ -6,13 +6,24 @@ import * as extensions from './extensions/index.js'
6
6
  export const emission = async (definitions, locator, context) => {
7
7
  if (definitions === undefined) return
8
8
 
9
- const events = await Promise.all(Object.entries(definitions).map(async ([label, definition]) => {
10
- const emitter = await boot.bindings.emit(definition.binding, locator, label)
11
- const decorator = extensions.emitter(emitter, label, locator)
12
- const bridge = await 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
+ )
13
19
 
14
- return new Event({ ...definition, label: `${locator.id}.${label}` }, decorator, bridge)
15
- }))
20
+ return new Event(
21
+ { ...definition, label: `${locator.id}.${label}` },
22
+ decorator,
23
+ bridge
24
+ )
25
+ })
26
+ )
16
27
 
17
28
  return new Emission(events)
18
29
  }
@@ -2,7 +2,7 @@ import { instance } from './resolve.js'
2
2
 
3
3
  /**
4
4
  * @param {toa.norm.Component} manifest
5
- * @returns {toa.core.extensions.Aspect[]}
5
+ * @returns {import('@toa.io/core/types').extensions.Aspect[]}
6
6
  */
7
7
  export const aspects = (manifest) => {
8
8
  const aspects = []
@@ -1,8 +1,8 @@
1
1
  import { instances } from './instances.js'
2
2
 
3
3
  /**
4
- * @param {toa.core.Component} component
5
- * @returns {toa.core.Component}
4
+ * @param {import('@toa.io/core').Component} component
5
+ * @returns {import('@toa.io/core').Component}
6
6
  */
7
7
  export const component = (component) => {
8
8
  let decorated = component
@@ -1,8 +1,8 @@
1
1
  import { instances } from './instances.js'
2
2
 
3
3
  /**
4
- * @param {toa.core.Context} component
5
- * @returns {toa.core.Context}
4
+ * @param {import('@toa.io/core').Context} component
5
+ * @returns {import('@toa.io/core').Context}
6
6
  */
7
7
  export const context = (component) => {
8
8
  let decorated = component
@@ -1,16 +1,17 @@
1
1
  import { instances } from './instances.js'
2
2
 
3
3
  /**
4
- * @param {toa.core.bindings.Emitter} emitter
4
+ * @param {import('@toa.io/core/types').bindings.Emitter} emitter
5
5
  * @param {string} label
6
- * @param {toa.core.Locator} locator
7
- * @returns {toa.core.bindings.Emitter}
6
+ * @param {import('@toa.io/core').Locator} locator
7
+ * @returns {import('@toa.io/core/types').bindings.Emitter}
8
8
  */
9
9
  export const emitter = (emitter, label, locator) => {
10
10
  let decorated = emitter
11
11
 
12
12
  for (const factory of Object.values(instances)) {
13
- if (factory.emitter !== undefined) decorated = factory.emitter(decorated, label, locator)
13
+ if (factory.emitter !== undefined)
14
+ decorated = factory.emitter(decorated, label, locator)
14
15
  }
15
16
 
16
17
  return decorated
@@ -1,4 +1,4 @@
1
1
  /**
2
- * @type {{ [key: string]: toa.core.extensions.Factory}}
2
+ * @type {{ [key: string]: import('@toa.io/core/types').extensions.Factory}}
3
3
  */
4
4
  export const instances = {}
@@ -4,9 +4,11 @@ import { resolve } from './resolve.js'
4
4
  * @param {toa.norm.Component} manifest
5
5
  */
6
6
  export const load = async (manifest) => {
7
- if (manifest.extensions === undefined)
8
- return
7
+ if (manifest.extensions === undefined) return
9
8
 
10
- await Promise.all(Object.keys(manifest.extensions)
11
- .map(async (name) => await resolve(name, manifest.path)))
9
+ await Promise.all(
10
+ Object.keys(manifest.extensions).map(
11
+ async (name) => await resolve(name, manifest.path)
12
+ )
13
+ )
12
14
  }
@@ -1,15 +1,14 @@
1
1
  import { instances } from './instances.js'
2
2
 
3
3
  /**
4
- * @param {toa.core.Connector} composition
5
- * @returns {toa.core.Connector}
4
+ * @param {import('@toa.io/core').Connector} composition
5
+ * @returns {import('@toa.io/core').Connector}
6
6
  */
7
7
  export const manage = (composition) => {
8
8
  let managed = composition
9
9
 
10
10
  for (const factory of Object.values(instances)) {
11
- if (factory.manage !== undefined)
12
- managed = factory.manage(managed)
11
+ if (factory.manage !== undefined) managed = factory.manage(managed)
13
12
  }
14
13
 
15
14
  return managed
@@ -1,16 +1,15 @@
1
1
  import { instances } from './instances.js'
2
2
 
3
3
  /**
4
- * @param {toa.core.Receiver} receiver
5
- * @param {toa.core.Locator} locator
6
- * @returns {toa.core.Receiver}
4
+ * @param {import('@toa.io/core/types').Receiver} receiver
5
+ * @param {import('@toa.io/core').Locator} locator
6
+ * @returns {import('@toa.io/core/types').Receiver}
7
7
  */
8
8
  export const receiver = (receiver, locator) => {
9
9
  let decorated = receiver
10
10
 
11
11
  for (const factory of Object.values(instances)) {
12
- if (factory.receiver !== undefined)
13
- decorated = factory.receiver(decorated, locator)
12
+ if (factory.receiver !== undefined) decorated = factory.receiver(decorated, locator)
14
13
  }
15
14
 
16
15
  return decorated
@@ -17,7 +17,7 @@ const loading = {}
17
17
  *
18
18
  * @param {string} reference
19
19
  * @param {string} base
20
- * @returns {Promise<toa.core.extensions.Factory>}
20
+ * @returns {Promise<import('@toa.io/core/types').extensions.Factory>}
21
21
  */
22
22
  export const resolve = async (reference, base = process.cwd()) => {
23
23
  const path = find(reference, base)
@@ -35,7 +35,7 @@ export const resolve = async (reference, base = process.cwd()) => {
35
35
  *
36
36
  * @param {string} reference
37
37
  * @param {string} base
38
- * @returns {toa.core.extensions.Factory}
38
+ * @returns {import('@toa.io/core/types').extensions.Factory}
39
39
  */
40
40
  export const instance = (reference, base = process.cwd()) => {
41
41
  const factory = instances[find(reference, base)]
@@ -1,8 +1,8 @@
1
1
  import { instances } from './instances.js'
2
2
 
3
3
  /**
4
- * @param {toa.core.Storage} storage
5
- * @returns {toa.core.Storage}
4
+ * @param {import('@toa.io/core/types').storages.Storage} storage
5
+ * @returns {import('@toa.io/core/types').storages.Storage}
6
6
  */
7
7
  export const storage = (storage) => {
8
8
  let decorated = storage
@@ -2,7 +2,7 @@ import { resolve } from './resolve.js'
2
2
 
3
3
  /**
4
4
  * @param {toa.norm.Component} manifest
5
- * @returns {toa.core.Connector[]}
5
+ * @returns {import('@toa.io/core').Connector[]}
6
6
  */
7
7
  export const tenants = async (manifest) => {
8
8
  const tenants = []
package/src/guards.js CHANGED
@@ -2,16 +2,17 @@ import { Guard } from '@toa.io/core'
2
2
  import * as boot from './index.js'
3
3
 
4
4
  export async function guards(manifest, context) {
5
- if (manifest.guards === undefined)
6
- return
5
+ if (manifest.guards === undefined) return
7
6
 
8
7
  const entries = Object.entries(manifest.guards)
9
8
 
10
- return await Promise.all(entries.map(async ([name, guard]) => {
11
- const bridge = await load(guard, name, context)
9
+ return await Promise.all(
10
+ entries.map(async ([name, guard]) => {
11
+ const bridge = await load(guard, name, context)
12
12
 
13
- return new Guard(name, bridge)
14
- }))
13
+ return new Guard(name, bridge)
14
+ })
15
+ )
15
16
  }
16
17
 
17
18
  async function load(guard, name, context) {
package/src/host.js CHANGED
@@ -4,7 +4,7 @@ import * as boot from './index.js'
4
4
  * What the process hosting an extension provides to it. The counterpart of a component's
5
5
  * context: an extension reaches the core through this and through nothing else.
6
6
  *
7
- * @returns {toa.core.extensions.Host}
7
+ * @returns {import('@toa.io/core/types').extensions.Host}
8
8
  */
9
9
  export const host = () => ({
10
10
  remote: boot.remote,
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // the environment is read at module scope elsewhere, so it is loaded first
2
+ // oxlint-disable-next-line import/no-unassigned-import
2
3
  import './env.js'
3
4
 
4
5
  import * as bindings from './bindings/index.js'
@@ -17,7 +18,6 @@ import { events } from './events.js'
17
18
  import { outbox } from './outbox.js'
18
19
  import { atomicity } from './atomicity.js'
19
20
  import { atom } from './atom.js'
20
- import { exposition } from './exposition.js'
21
21
  import { manifest } from './manifest.js'
22
22
  import { operation } from './operation.js'
23
23
  import { receivers, receive } from './receivers.js'
@@ -27,6 +27,26 @@ import { guards } from './guards.js'
27
27
  import { host } from './host.js'
28
28
  import { rc } from './rc.js'
29
29
 
30
- export { call, cascade, component, composition, context, emission, events, outbox, atomicity, atom, exposition, host, manifest, operation, receivers, receive, remote, storage, guards, rc }
30
+ export {
31
+ call,
32
+ cascade,
33
+ component,
34
+ composition,
35
+ context,
36
+ emission,
37
+ events,
38
+ outbox,
39
+ atomicity,
40
+ atom,
41
+ host,
42
+ manifest,
43
+ operation,
44
+ receivers,
45
+ receive,
46
+ remote,
47
+ storage,
48
+ guards,
49
+ rc
50
+ }
31
51
 
32
52
  export { bindings, bridge, contract, discovery, extensions }
package/src/manifest.js CHANGED
@@ -9,8 +9,10 @@ import { span } from './span.js'
9
9
  export const manifest = async (path, options = {}) => {
10
10
  options = merge(clone(options), DEFAULTS)
11
11
 
12
- const manifest = await span({ name: `manifest ${basename(path)}`, attributes: { path } },
13
- async () => await load(path))
12
+ const manifest = await span(
13
+ { name: `manifest ${basename(path)}`, attributes: { path } },
14
+ async () => await load(path)
15
+ )
14
16
 
15
17
  if (options?.bindings !== undefined) {
16
18
  if ('operations' in manifest) {
@@ -30,7 +32,8 @@ export const manifest = async (path, options = {}) => {
30
32
  }
31
33
  }
32
34
 
33
- if (asyncBinding === undefined) throw new Error('Bindings override must contain at least one async binding')
35
+ if (asyncBinding === undefined)
36
+ throw new Error('Bindings override must contain at least one async binding')
34
37
 
35
38
  if ('events' in manifest) {
36
39
  for (const event of Object.values(manifest.events)) event.binding = asyncBinding
@@ -51,7 +54,8 @@ export const manifest = async (path, options = {}) => {
51
54
  }
52
55
  }
53
56
 
54
- if ('storage' in options && 'entity' in manifest) manifest.entity.storage = options.storage
57
+ if ('storage' in options && 'entity' in manifest)
58
+ manifest.entity.storage = options.storage
55
59
 
56
60
  manifest.locator = new Locator(manifest.name, manifest.namespace)
57
61
 
@@ -7,7 +7,6 @@ mock.module('@toa.io/norm', { namedExports: { component: () => mockComponent() }
7
7
 
8
8
  const { manifest } = await import('./manifest.js')
9
9
 
10
-
11
10
  const path = generate()
12
11
 
13
12
  it('should not modify options', async () => {
@@ -18,6 +17,6 @@ it('should not modify options', async () => {
18
17
  assert.deepStrictEqual(options.extensions.length, 2)
19
18
  })
20
19
 
21
- function mockComponent () {
20
+ function mockComponent() {
22
21
  return { name: generate(), namespace: generate() }
23
22
  }
package/src/operation.js CHANGED
@@ -1,17 +1,31 @@
1
- import { Transition, Observation, Assignment, Operation, Query, Effect, Unmanaged } from '@toa.io/core'
1
+ import {
2
+ Transition,
3
+ Observation,
4
+ Assignment,
5
+ Operation,
6
+ Query,
7
+ Effect,
8
+ Unmanaged
9
+ } from '@toa.io/core'
2
10
 
3
11
  import * as boot from './index.js'
4
12
 
5
- export async function operation (manifest, endpoint, definition, context, scope, preflight) {
13
+ export async function operation(
14
+ manifest,
15
+ endpoint,
16
+ definition,
17
+ context,
18
+ scope,
19
+ preflight
20
+ ) {
6
21
  const cascade = await boot.cascade(manifest, endpoint, definition, context, preflight)
7
22
  const reply = boot.contract.reply(definition.output, definition.errors)
8
23
  const input = definition.input
9
24
  const request = boot.contract.request({ input }, manifest.entity)
10
25
  const contracts = { reply, request }
11
26
 
12
- const query = manifest.entity === undefined
13
- ? undefined
14
- : new Query(manifest.entity.schema.properties)
27
+ const query =
28
+ manifest.entity === undefined ? undefined : new Query(manifest.entity.properties)
15
29
 
16
30
  const Type = TYPES[definition.type]
17
31
 
package/src/outbox.js CHANGED
@@ -7,8 +7,8 @@ import * as boot from './index.js'
7
7
  * static check that keeps the common case free of a transaction.
8
8
  *
9
9
  * @param {toa.norm.Component} manifest
10
- * @param {toa.core.Storage} [storage]
11
- * @param {toa.core.Emission} [emission]
10
+ * @param {import('@toa.io/core/types').storages.Storage} [storage]
11
+ * @param {import('@toa.io/core').Emission} [emission]
12
12
  */
13
13
  export const outbox = (manifest, storage, emission) => {
14
14
  if (emission === undefined) return
package/src/rc.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as boot from './index.js'
2
2
 
3
- export async function rc (manifest, context) {
3
+ export async function rc(manifest, context) {
4
4
  return boot.bridge.rc(manifest.bridge, manifest.path, context)
5
5
  }
package/src/receivers.js CHANGED
@@ -18,14 +18,25 @@ export const receivers = async (manifest, component) => {
18
18
  // the origin of the calls this receiver makes to its local operation
19
19
  const origin = { namespace: source.namespace, component: source.name, event }
20
20
 
21
- const bridge = definition.bridge !== undefined
22
- ? await boot.bridge.receiver(definition.bridge, manifest.path, label)
23
- : undefined
24
- const receiver = new Receiver({ ...definition, label, destination, origin }, local, bridge)
21
+ const bridge =
22
+ definition.bridge !== undefined
23
+ ? await boot.bridge.receiver(definition.bridge, manifest.path, label)
24
+ : undefined
25
+ const receiver = new Receiver(
26
+ { ...definition, label, destination, origin },
27
+ local,
28
+ bridge
29
+ )
25
30
  const decorator = extensions.receiver(receiver, manifest.locator)
26
31
 
27
- const transport = definition.binding ?? await resolveBinding(locator, label)
28
- const binding = await boot.bindings.receive(transport, source, label, manifest.locator.id, decorator)
32
+ const transport = definition.binding ?? (await resolveBinding(locator, label))
33
+ const binding = await boot.bindings.receive(
34
+ transport,
35
+ source,
36
+ label,
37
+ manifest.locator.id,
38
+ decorator
39
+ )
29
40
 
30
41
  binding.depends(component)
31
42
  receivers.push(binding)
@@ -34,7 +45,7 @@ export const receivers = async (manifest, component) => {
34
45
  return receivers
35
46
  }
36
47
 
37
- export async function receive (label, group, callback) {
48
+ export async function receive(label, group, callback) {
38
49
  if (callback === undefined) {
39
50
  callback = group
40
51
  group = undefined
@@ -47,11 +58,11 @@ export async function receive (label, group, callback) {
47
58
  }
48
59
 
49
60
  /**
50
- * @param {toa.core.Locator} locator
61
+ * @param {import('@toa.io/core').Locator} locator
51
62
  * @param {string} label
52
63
  * @return {Promise<string>}
53
64
  */
54
- async function resolveBinding (locator, label) {
65
+ async function resolveBinding(locator, label) {
55
66
  const event = label.split('.').pop()
56
67
  const discovery = await boot.discovery.discovery()
57
68
  const { events } = await discovery.lookup(locator)
package/src/remote.js CHANGED
@@ -5,8 +5,8 @@ import { remap } from '@toa.io/generic'
5
5
  import * as boot from './index.js'
6
6
 
7
7
  /**
8
- * @param {toa.core.Locator} locator
9
- * @param {toa.core.Source} [source] the origin stamped on every call made through this remote
8
+ * @param {import('@toa.io/core').Locator} locator
9
+ * @param {import('@toa.io/core/types').Source} [source] the origin stamped on every call made through this remote
10
10
  * @param {toa.norm.Component} [manifest] skips discovery when the manifest is already known
11
11
  */
12
12
  export const remote = async (locator, source, manifest) => {
@@ -20,22 +20,25 @@ export const remote = async (locator, source, manifest) => {
20
20
  }
21
21
 
22
22
  // a call binds its consumers, which are loaded rather than required
23
- const calls = manifest.operations === undefined
24
- ? {}
25
- : await settle(remap(manifest.operations,
26
- (definition, endpoint) => boot.call(locator, endpoint, definition, manifest.entity, source)))
23
+ const calls =
24
+ manifest.operations === undefined
25
+ ? {}
26
+ : await settle(
27
+ remap(manifest.operations, (definition, endpoint) =>
28
+ boot.call(locator, endpoint, definition, manifest.entity, source)
29
+ )
30
+ )
27
31
 
28
32
  const remote = new Remote(locator, calls)
29
33
 
30
34
  // ensure discovery shutdown
31
- if (discovery !== undefined)
32
- remote.depends(discovery)
35
+ if (discovery !== undefined) remote.depends(discovery)
33
36
 
34
37
  return remote
35
38
  }
36
39
 
37
40
  /** An object whose values are promises, as an object of what they resolve to. */
38
- async function settle (object) {
41
+ async function settle(object) {
39
42
  const entries = Object.entries(object)
40
43
  const values = await Promise.all(entries.map(([, promise]) => promise))
41
44
 
package/src/span.js CHANGED
@@ -14,4 +14,5 @@ if (enabled) traces({ exporters: { console: {} } })
14
14
  * @param {() => Promise<any>} task
15
15
  * @returns {Promise<any>}
16
16
  */
17
- export const span = async (options, task) => enabled ? console.span(options, task) : task()
17
+ export const span = async (options, task) =>
18
+ enabled ? console.span(options, task) : task()
package/src/storage.js CHANGED
@@ -16,16 +16,25 @@ export const storage = async (manifest, outbox) => {
16
16
 
17
17
  const Factory = await load(manifest)
18
18
 
19
- /** @type {toa.core.storages.Factory} */
19
+ /** @type {import('@toa.io/core/types').storages.Factory} */
20
20
  const factory = new Factory()
21
21
  const storage = factory.storage(manifest.locator, manifest.entity, { outbox })
22
22
 
23
+ // a component whose structure nothing will make must not start with the structure it lacks
24
+ if (manifest.entity.migrations?.length > 0 && storage.migrates !== true)
25
+ throw new Error(
26
+ `Component '${manifest.locator.id}' declares migrations, ` +
27
+ `which storage '${manifest.entity.storage}' does not apply`
28
+ )
29
+
23
30
  return extensions.storage(storage)
24
31
  }
25
32
 
26
- async function load (component) {
33
+ async function load(component) {
27
34
  const reference = component.entity.storage
28
- const path = require.resolve(reference, { paths: [component.path, import.meta.dirname] })
35
+ const path = require.resolve(reference, {
36
+ paths: [component.path, import.meta.dirname]
37
+ })
29
38
  const { Factory } = await import(pathToFileURL(path).href)
30
39
 
31
40
  return Factory
@@ -0,0 +1,47 @@
1
+ import { it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import { Locator } from '@toa.io/core'
5
+
6
+ import { storage } from './storage.js'
7
+
8
+ const manifest = (entity) => ({
9
+ locator: new Locator('one', 'test'),
10
+ path: import.meta.dirname,
11
+ entity
12
+ })
13
+
14
+ const schema = { properties: {} }
15
+
16
+ it('should not create a storage for a component that stores nothing', async () => {
17
+ assert.equal(await storage(manifest(undefined), false), undefined)
18
+ })
19
+
20
+ it('should refuse migrations a storage does not apply', async () => {
21
+ const entity = {
22
+ schema,
23
+ storage: '@toa.io/storages.null',
24
+ migrations: [{ id: '0001', steps: [] }]
25
+ }
26
+
27
+ await assert.rejects(
28
+ storage(manifest(entity), false),
29
+ /declares migrations, which storage '@toa.io\/storages.null' does not apply/
30
+ )
31
+ })
32
+
33
+ it('should accept a storage with no migrations', async () => {
34
+ const entity = { schema, storage: '@toa.io/storages.null' }
35
+
36
+ assert.notEqual(await storage(manifest(entity), false), undefined)
37
+ })
38
+
39
+ it('should accept migrations a storage applies', async () => {
40
+ const entity = {
41
+ schema,
42
+ storage: '@toa.io/storages.mongodb',
43
+ migrations: [{ id: '0001', steps: [] }]
44
+ }
45
+
46
+ assert.notEqual(await storage(manifest(entity), false), undefined)
47
+ })
@@ -1,3 +1,7 @@
1
- import type { bindings } from '@toa.io/core'
1
+ import type { bindings } from '@toa.io/core/types'
2
2
 
3
- export function broadcast<T> (name: string, group?: string, binding?: string): bindings.Broadcast<T>
3
+ export function broadcast<T>(
4
+ name: string,
5
+ group?: string,
6
+ binding?: string
7
+ ): bindings.Broadcast<T>
package/types/index.d.ts CHANGED
@@ -1,23 +1,30 @@
1
- import * as core from '@toa.io/core'
1
+ import type * as core from '@toa.io/core'
2
+ import type * as types from '@toa.io/core/types'
2
3
  import * as composition from './composition.js'
3
4
 
4
5
  export * as bindings from './bindings.js'
5
6
 
6
- export async function composition (paths: string[], options?: composition.Options): Promise<core.Connector>
7
+ export async function composition(
8
+ paths: string[],
9
+ options?: composition.Options
10
+ ): Promise<core.Connector>
7
11
 
8
- export async function remote (locator: core.Locator, source?: core.Source): Promise<core.Remote>
12
+ export async function remote(
13
+ locator: core.Locator,
14
+ source?: types.Source
15
+ ): Promise<core.Remote>
9
16
 
10
- export async function receive<T = any> (
17
+ export async function receive<T = any>(
11
18
  label: string,
12
19
  receiver: Receiver
13
20
  ): Promise<core.Connector>
14
21
 
15
- export async function receive<T = any> (
22
+ export async function receive<T = any>(
16
23
  label: string,
17
24
  group: string | undefined,
18
25
  receiver: Receiver
19
26
  ): Promise<core.Connector>
20
27
 
21
- type Receiver = { receive: (message: core.Message<T>) => void | Promise<void> }
28
+ type Receiver = { receive: (message: types.Message<T>) => void | Promise<void> }
22
29
 
23
- export function host (): core.extensions.Host
30
+ export function host(): types.extensions.Host
@@ -2,7 +2,8 @@ import * as _norm from '@toa.io/norm/types'
2
2
  import * as _composition from './composition.js'
3
3
 
4
4
  declare namespace toa.boot {
5
-
6
- type Manifest = (path: string, options?: _composition.Options) => Promise<_norm.Component>
7
-
5
+ type Manifest = (
6
+ path: string,
7
+ options?: _composition.Options
8
+ ) => Promise<_norm.Component>
8
9
  }
package/CHANGELOG.md DELETED
@@ -1,188 +0,0 @@
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/src/exposition.js DELETED
@@ -1,13 +0,0 @@
1
- import { Exposition, Locator } from '@toa.io/core'
2
-
3
- import * as boot from './index.js'
4
-
5
- export const exposition = async (manifest) => {
6
- const locator = new Locator('', '')
7
- const exposition = new Exposition(locator, manifest)
8
- const producers = boot.bindings.expose(exposition)
9
-
10
- await Promise.all(producers.map((producer) => producer.connect()))
11
-
12
- return producers
13
- }