@toa.io/boot 1.0.0-alpha.29 → 1.0.0-alpha.292
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 +46 -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 +8 -8
- package/src/bindings/emit.js +3 -6
- package/src/bindings/factory.js +5 -9
- package/src/bindings/index.js +5 -13
- package/src/bindings/produce.js +30 -18
- package/src/bindings/receive.js +3 -6
- package/src/bridge.js +22 -16
- package/src/call.js +6 -9
- package/src/cascade.js +16 -10
- package/src/component.js +79 -32
- package/src/composition.js +104 -15
- package/src/context.js +7 -8
- package/src/contract.js +11 -12
- package/src/discovery.js +29 -18
- package/src/emission.js +21 -14
- package/src/env.js +19 -12
- package/src/events.js +27 -0
- package/src/extensions/aspects.js +4 -8
- package/src/extensions/component.js +4 -8
- package/src/extensions/context.js +4 -8
- package/src/extensions/emitter.js +7 -9
- package/src/extensions/index.js +9 -19
- package/src/extensions/instances.js +2 -4
- package/src/extensions/load.js +7 -7
- package/src/extensions/manage.js +15 -0
- package/src/extensions/receiver.js +5 -9
- package/src/extensions/resolve.js +36 -11
- package/src/extensions/storage.js +4 -8
- package/src/extensions/tenants.js +5 -9
- package/src/guards.js +20 -0
- package/src/host.js +15 -0
- package/src/index.js +48 -32
- package/src/manifest.js +25 -16
- package/src/manifest.test.js +12 -8
- package/src/operation.js +25 -13
- package/src/outbox.js +17 -0
- package/src/rc.js +5 -0
- package/src/receivers.js +35 -21
- package/src/remote.js +38 -17
- package/src/span.js +19 -0
- package/src/storage.js +35 -14
- package/src/storage.test.js +57 -0
- package/types/bindings.d.ts +6 -2
- package/types/index.d.ts +19 -10
- package/types/manifest.d.ts +5 -4
- package/src/exposition.js +0 -17
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { instances } = require('./instances')
|
|
1
|
+
import { instances } from './instances.js'
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
|
-
* @param {toa.core.Storage} storage
|
|
7
|
-
* @returns {toa.core.Storage}
|
|
4
|
+
* @param {import('@toa.io/core/types').storages.Storage} storage
|
|
5
|
+
* @returns {import('@toa.io/core/types').storages.Storage}
|
|
8
6
|
*/
|
|
9
|
-
const storage = (storage) => {
|
|
7
|
+
export const storage = (storage) => {
|
|
10
8
|
let decorated = storage
|
|
11
9
|
|
|
12
10
|
for (const factory of Object.values(instances)) {
|
|
@@ -15,5 +13,3 @@ const storage = (storage) => {
|
|
|
15
13
|
|
|
16
14
|
return decorated
|
|
17
15
|
}
|
|
18
|
-
|
|
19
|
-
exports.storage = storage
|
|
@@ -1,27 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { resolve } = require('./resolve')
|
|
1
|
+
import { resolve } from './resolve.js'
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* @param {toa.norm.Component} manifest
|
|
7
|
-
* @returns {toa.core.Connector[]}
|
|
5
|
+
* @returns {import('@toa.io/core').Connector[]}
|
|
8
6
|
*/
|
|
9
|
-
const tenants = (manifest) => {
|
|
7
|
+
export const tenants = async (manifest) => {
|
|
10
8
|
const tenants = []
|
|
11
9
|
|
|
12
10
|
if (manifest.extensions === undefined) return tenants
|
|
13
11
|
|
|
14
12
|
for (const [name, declaration] of Object.entries(manifest.extensions)) {
|
|
15
|
-
const factory = resolve(name, manifest.path)
|
|
13
|
+
const factory = await resolve(name, manifest.path)
|
|
16
14
|
|
|
17
15
|
if (factory.tenant === undefined) continue
|
|
18
16
|
|
|
19
|
-
const tenant = factory.tenant(manifest.locator, declaration)
|
|
17
|
+
const tenant = await factory.tenant(manifest.locator, declaration, manifest)
|
|
20
18
|
|
|
21
19
|
tenants.push(tenant)
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
return tenants
|
|
25
23
|
}
|
|
26
|
-
|
|
27
|
-
exports.tenants = tenants
|
package/src/guards.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Guard } from '@toa.io/core'
|
|
2
|
+
import * as boot from './index.js'
|
|
3
|
+
|
|
4
|
+
export async function guards(manifest, context) {
|
|
5
|
+
if (manifest.guards === undefined) return
|
|
6
|
+
|
|
7
|
+
const entries = Object.entries(manifest.guards)
|
|
8
|
+
|
|
9
|
+
return await Promise.all(
|
|
10
|
+
entries.map(async ([name, guard]) => {
|
|
11
|
+
const bridge = await load(guard, name, context)
|
|
12
|
+
|
|
13
|
+
return new Guard(name, bridge)
|
|
14
|
+
})
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function load(guard, name, context) {
|
|
19
|
+
return await boot.bridge.guard(guard.bridge, guard.path, name, context)
|
|
20
|
+
}
|
package/src/host.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as boot from './index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What the process hosting an extension provides to it. The counterpart of a component's
|
|
5
|
+
* context: an extension reaches the core through this and through nothing else.
|
|
6
|
+
*
|
|
7
|
+
* @returns {import('@toa.io/core/types').extensions.Host}
|
|
8
|
+
*/
|
|
9
|
+
export const host = () => ({
|
|
10
|
+
remote: boot.remote,
|
|
11
|
+
broadcast: boot.bindings.broadcast,
|
|
12
|
+
composition: boot.composition,
|
|
13
|
+
receive: boot.receive,
|
|
14
|
+
atom: boot.atomicity
|
|
15
|
+
})
|
package/src/index.js
CHANGED
|
@@ -1,36 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
// the environment is read at module scope elsewhere, so it is loaded first
|
|
2
|
+
import { load as env } from './env.js'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
import * as bindings from './bindings/index.js'
|
|
5
|
+
import * as bridge from './bridge.js'
|
|
6
|
+
import * as contract from './contract.js'
|
|
7
|
+
import * as discovery from './discovery.js'
|
|
8
|
+
import * as extensions from './extensions/index.js'
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
import { call } from './call.js'
|
|
11
|
+
import { cascade } from './cascade.js'
|
|
12
|
+
import { component } from './component.js'
|
|
13
|
+
import { composition } from './composition.js'
|
|
14
|
+
import { context } from './context.js'
|
|
15
|
+
import { emission } from './emission.js'
|
|
16
|
+
import { events } from './events.js'
|
|
17
|
+
import { outbox } from './outbox.js'
|
|
18
|
+
import { atomicity } from './atomicity.js'
|
|
19
|
+
import { atom } from './atom.js'
|
|
20
|
+
import { manifest } from './manifest.js'
|
|
21
|
+
import { operation } from './operation.js'
|
|
22
|
+
import { receivers, receive } from './receivers.js'
|
|
23
|
+
import { remote } from './remote.js'
|
|
24
|
+
import { storage } from './storage.js'
|
|
25
|
+
import { guards } from './guards.js'
|
|
26
|
+
import { host } from './host.js'
|
|
27
|
+
import { rc } from './rc.js'
|
|
17
28
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
export {
|
|
30
|
+
call,
|
|
31
|
+
env,
|
|
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
|
+
}
|
|
23
51
|
|
|
24
|
-
|
|
25
|
-
exports.cascade = cascade
|
|
26
|
-
exports.component = component
|
|
27
|
-
exports.composition = composition
|
|
28
|
-
exports.context = context
|
|
29
|
-
exports.emission = emission
|
|
30
|
-
exports.exposition = exposition
|
|
31
|
-
exports.manifest = manifest
|
|
32
|
-
exports.operation = operation
|
|
33
|
-
exports.receivers = receivers
|
|
34
|
-
exports.receive = receive
|
|
35
|
-
exports.remote = remote
|
|
36
|
-
exports.storage = storage
|
|
52
|
+
export { bindings, bridge, contract, discovery, extensions }
|
package/src/manifest.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import clone from 'clone-deep'
|
|
2
|
+
import { basename } from 'node:path'
|
|
3
|
+
import { merge } from '@toa.io/generic'
|
|
4
|
+
import { component as load, definition } from '@toa.io/norm'
|
|
5
|
+
import { Locator } from '@toa.io/core'
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
const { merge } = require('@toa.io/generic')
|
|
5
|
-
const { component: load } = require('@toa.io/norm')
|
|
6
|
-
const { Locator } = require('@toa.io/core')
|
|
7
|
+
import { span } from './span.js'
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
* @type {toa.boot.Manifest}
|
|
10
|
-
*/
|
|
11
|
-
const manifest = async (path, options = {}) => {
|
|
9
|
+
export const manifest = async (path, options = {}) => {
|
|
12
10
|
options = merge(clone(options), DEFAULTS)
|
|
13
11
|
|
|
14
|
-
const manifest = await
|
|
12
|
+
const manifest = await span(
|
|
13
|
+
{ name: `manifest ${basename(path)}`, attributes: { path } },
|
|
14
|
+
async () => await load(path)
|
|
15
|
+
)
|
|
15
16
|
|
|
16
17
|
if (options?.bindings !== undefined) {
|
|
17
18
|
if ('operations' in manifest) {
|
|
@@ -20,10 +21,19 @@ const manifest = async (path, options = {}) => {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
const asyncBinding = options.bindings.find(check)
|
|
24
|
+
let asyncBinding
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
for (const binding of options.bindings) {
|
|
27
|
+
const { properties } = (await definition(binding)).module
|
|
28
|
+
|
|
29
|
+
if (properties?.async === true) {
|
|
30
|
+
asyncBinding = binding
|
|
31
|
+
break
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (asyncBinding === undefined)
|
|
36
|
+
throw new Error('Bindings override must contain at least one async binding')
|
|
27
37
|
|
|
28
38
|
if ('events' in manifest) {
|
|
29
39
|
for (const event of Object.values(manifest.events)) event.binding = asyncBinding
|
|
@@ -44,7 +54,8 @@ const manifest = async (path, options = {}) => {
|
|
|
44
54
|
}
|
|
45
55
|
}
|
|
46
56
|
|
|
47
|
-
if ('storage' in options && 'entity' in manifest)
|
|
57
|
+
if ('storage' in options && 'entity' in manifest)
|
|
58
|
+
manifest.entity.storage = options.storage
|
|
48
59
|
|
|
49
60
|
manifest.locator = new Locator(manifest.name, manifest.namespace)
|
|
50
61
|
|
|
@@ -52,5 +63,3 @@ const manifest = async (path, options = {}) => {
|
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
const DEFAULTS = {}
|
|
55
|
-
|
|
56
|
-
exports.manifest = manifest
|
package/src/manifest.test.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import { it, mock } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
import { generate } from 'randomstring'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
mock.module('@toa.io/norm', {
|
|
7
|
+
namedExports: {
|
|
8
|
+
component: () => mockComponent(),
|
|
9
|
+
definition: async () => ({ module: {} })
|
|
10
|
+
}
|
|
11
|
+
})
|
|
8
12
|
|
|
9
|
-
const { manifest } =
|
|
13
|
+
const { manifest } = await import('./manifest.js')
|
|
10
14
|
|
|
11
15
|
const path = generate()
|
|
12
16
|
|
|
@@ -15,9 +19,9 @@ it('should not modify options', async () => {
|
|
|
15
19
|
|
|
16
20
|
await manifest(path, options)
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
assert.deepStrictEqual(options.extensions.length, 2)
|
|
19
23
|
})
|
|
20
24
|
|
|
21
|
-
function mockComponent
|
|
25
|
+
function mockComponent() {
|
|
22
26
|
return { name: generate(), namespace: generate() }
|
|
23
27
|
}
|
package/src/operation.js
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
export async function operation(
|
|
14
|
+
manifest,
|
|
15
|
+
endpoint,
|
|
16
|
+
definition,
|
|
17
|
+
context,
|
|
18
|
+
scope,
|
|
19
|
+
preflight
|
|
20
|
+
) {
|
|
21
|
+
const cascade = await boot.cascade(manifest, endpoint, definition, context, preflight)
|
|
22
|
+
const reply = boot.contract.reply(definition.output, definition.errors)
|
|
10
23
|
const input = definition.input
|
|
11
24
|
const request = boot.contract.request({ input }, manifest.entity)
|
|
12
25
|
const contracts = { reply, request }
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
: new Query(manifest.entity.
|
|
26
|
+
|
|
27
|
+
const query =
|
|
28
|
+
manifest.entity === undefined ? undefined : new Query(manifest.entity.properties)
|
|
16
29
|
|
|
17
30
|
const Type = TYPES[definition.type]
|
|
18
31
|
|
|
@@ -24,7 +37,6 @@ const TYPES = {
|
|
|
24
37
|
observation: Observation,
|
|
25
38
|
assignment: Assignment,
|
|
26
39
|
computation: Operation,
|
|
27
|
-
effect:
|
|
40
|
+
effect: Effect,
|
|
41
|
+
unmanaged: Unmanaged
|
|
28
42
|
}
|
|
29
|
-
|
|
30
|
-
exports.operation = operation
|
package/src/outbox.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Outbox } from '@toa.io/core'
|
|
2
|
+
|
|
3
|
+
import * as boot from './index.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A component with no declared events has nothing to publish and therefore no outbox — the
|
|
7
|
+
* static check that keeps the common case free of a transaction.
|
|
8
|
+
*
|
|
9
|
+
* @param {toa.norm.Component} manifest
|
|
10
|
+
* @param {import('@toa.io/core/types').storages.Storage} [storage]
|
|
11
|
+
* @param {import('@toa.io/core').Emission} [emission]
|
|
12
|
+
*/
|
|
13
|
+
export const outbox = (manifest, storage, emission) => {
|
|
14
|
+
if (emission === undefined) return
|
|
15
|
+
|
|
16
|
+
return new Outbox(emission, storage, boot.atomicity(manifest.locator.id), {})
|
|
17
|
+
}
|
package/src/rc.js
ADDED
package/src/receivers.js
CHANGED
|
@@ -1,34 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
import { Receiver, Locator } 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 receivers = async (manifest, runtime) => {
|
|
6
|
+
export const receivers = async (manifest, component) => {
|
|
9
7
|
if (manifest.receivers === undefined) return []
|
|
10
8
|
|
|
11
9
|
const receivers = []
|
|
12
|
-
const local = await boot.remote(manifest.locator, manifest)
|
|
10
|
+
const local = await boot.remote(manifest.locator, undefined, manifest)
|
|
13
11
|
|
|
14
12
|
for (const [label, definition] of Object.entries(manifest.receivers)) {
|
|
15
|
-
const bridge = definition.bridge !== undefined ? boot.bridge.receiver(definition.bridge, manifest.path, label) : undefined
|
|
16
|
-
const receiver = new Receiver(definition, local, bridge)
|
|
17
|
-
const decorator = extensions.receiver(receiver, manifest.locator)
|
|
18
|
-
|
|
19
13
|
const locator = Locator.parse(label)
|
|
20
|
-
const transport = definition.binding ?? await resolveBinding(locator, label)
|
|
21
14
|
const source = definition.source ? Locator.parse(definition.source) : locator
|
|
22
|
-
const
|
|
15
|
+
const event = label.split('.').pop()
|
|
16
|
+
const destination = `${source.id}.${event}`
|
|
17
|
+
|
|
18
|
+
// the origin of the calls this receiver makes to its local operation
|
|
19
|
+
const origin = { namespace: source.namespace, component: source.name, event }
|
|
23
20
|
|
|
24
|
-
|
|
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
|
+
)
|
|
30
|
+
const decorator = extensions.receiver(receiver, manifest.locator)
|
|
31
|
+
|
|
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
|
+
)
|
|
40
|
+
|
|
41
|
+
binding.depends(component)
|
|
25
42
|
receivers.push(binding)
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
return receivers
|
|
29
46
|
}
|
|
30
47
|
|
|
31
|
-
async function receive
|
|
48
|
+
export async function receive(label, group, callback) {
|
|
32
49
|
if (callback === undefined) {
|
|
33
50
|
callback = group
|
|
34
51
|
group = undefined
|
|
@@ -37,21 +54,18 @@ async function receive (label, group, callback) {
|
|
|
37
54
|
const locator = Locator.parse(label)
|
|
38
55
|
const transport = await resolveBinding(locator, label)
|
|
39
56
|
|
|
40
|
-
return boot.bindings.receive(transport, locator, label, group, callback)
|
|
57
|
+
return await boot.bindings.receive(transport, locator, label, group, callback)
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
/**
|
|
44
|
-
* @param {toa.core.Locator} locator
|
|
61
|
+
* @param {import('@toa.io/core').Locator} locator
|
|
45
62
|
* @param {string} label
|
|
46
63
|
* @return {Promise<string>}
|
|
47
64
|
*/
|
|
48
|
-
async function resolveBinding
|
|
65
|
+
async function resolveBinding(locator, label) {
|
|
49
66
|
const event = label.split('.').pop()
|
|
50
67
|
const discovery = await boot.discovery.discovery()
|
|
51
68
|
const { events } = await discovery.lookup(locator)
|
|
52
69
|
|
|
53
70
|
return events[event].binding
|
|
54
71
|
}
|
|
55
|
-
|
|
56
|
-
exports.receivers = receivers
|
|
57
|
-
exports.receive = receive
|
package/src/remote.js
CHANGED
|
@@ -1,25 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import { console } from 'openspan'
|
|
2
|
+
import { Remote } from '@toa.io/core'
|
|
3
|
+
import { remap } from '@toa.io/generic'
|
|
4
|
+
|
|
5
|
+
import * as boot from './index.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
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
|
+
* @param {toa.norm.Component} [manifest] skips discovery when the manifest is already known
|
|
11
|
+
*/
|
|
12
|
+
export const remote = async (locator, source, manifest) => {
|
|
13
|
+
let discovery
|
|
14
|
+
|
|
15
|
+
if (manifest === undefined) {
|
|
16
|
+
console.debug('Lookup', { locator: locator.id })
|
|
17
|
+
|
|
18
|
+
discovery = await boot.discovery.discovery(locator)
|
|
19
|
+
manifest = await discovery.lookup(locator)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// a call binds its consumers, which are loaded rather than required
|
|
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
|
+
)
|
|
16
31
|
|
|
17
32
|
const remote = new Remote(locator, calls)
|
|
18
33
|
|
|
19
34
|
// ensure discovery shutdown
|
|
20
|
-
remote.depends(discovery)
|
|
35
|
+
if (discovery !== undefined) remote.depends(discovery)
|
|
21
36
|
|
|
22
37
|
return remote
|
|
23
38
|
}
|
|
24
39
|
|
|
25
|
-
|
|
40
|
+
/** An object whose values are promises, as an object of what they resolve to. */
|
|
41
|
+
async function settle(object) {
|
|
42
|
+
const entries = Object.entries(object)
|
|
43
|
+
const values = await Promise.all(entries.map(([, promise]) => promise))
|
|
44
|
+
|
|
45
|
+
return Object.fromEntries(entries.map(([key], index) => [key, values[index]]))
|
|
46
|
+
}
|
package/src/span.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { console, traces } from 'openspan'
|
|
2
|
+
import { environment } from '@toa.io/generic'
|
|
3
|
+
|
|
4
|
+
const enabled = environment.get('TOA_BOOT_TRACE') === '1'
|
|
5
|
+
|
|
6
|
+
// Tracing is off until an exporter is configured, and the CLI opens its own span before
|
|
7
|
+
// any extension gets to configure one — so asking for a boot trace turns the console
|
|
8
|
+
// exporter on here. Extensions keep it: see `development()` in the telemetry factory.
|
|
9
|
+
if (enabled) traces({ exporters: { console: {} } })
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a span if boot tracing is enabled (TOA_BOOT_TRACE=1), otherwise just runs the task
|
|
13
|
+
*
|
|
14
|
+
* @param {string | object} options
|
|
15
|
+
* @param {() => Promise<any>} task
|
|
16
|
+
* @returns {Promise<any>}
|
|
17
|
+
*/
|
|
18
|
+
export const span = async (options, task) =>
|
|
19
|
+
enabled ? console.span(options, task) : task()
|
package/src/storage.js
CHANGED
|
@@ -1,26 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { pathToFileURL } from 'node:url'
|
|
3
|
+
import * as extensions from './extensions/index.js'
|
|
4
|
+
|
|
5
|
+
// import.meta.resolve takes no paths, and the connector is resolved against the
|
|
6
|
+
// component that names it
|
|
7
|
+
const require = createRequire(import.meta.url)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {toa.norm.Component} manifest
|
|
11
|
+
* @param {boolean} outbox whether this component publishes anything, and so needs a place to
|
|
12
|
+
* commit it with the entity
|
|
13
|
+
*/
|
|
14
|
+
export const storage = async (manifest, outbox) => {
|
|
7
15
|
if (manifest.entity === undefined) return
|
|
8
16
|
|
|
9
|
-
const Factory = load(manifest)
|
|
17
|
+
const Factory = await load(manifest)
|
|
10
18
|
|
|
11
|
-
/** @type {toa.core.storages.Factory} */
|
|
19
|
+
/** @type {import('@toa.io/core/types').storages.Factory} */
|
|
12
20
|
const factory = new Factory()
|
|
13
|
-
const storage = factory.storage(manifest.locator, manifest.entity)
|
|
21
|
+
const storage = factory.storage(manifest.locator, manifest.entity, { outbox })
|
|
22
|
+
|
|
23
|
+
// a component whose structure nothing will make must not start with the structure it lacks;
|
|
24
|
+
// what it inherits was written for descendants that store, and one that stores nothing has
|
|
25
|
+
// nothing to convert
|
|
26
|
+
const own = manifest.entity.migrations?.some(
|
|
27
|
+
(migration) => migration.prototype === undefined
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if (own === true && storage.migrates !== true)
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Component '${manifest.locator.id}' declares migrations, ` +
|
|
33
|
+
`which storage '${manifest.entity.storage}' does not apply`
|
|
34
|
+
)
|
|
14
35
|
|
|
15
36
|
return extensions.storage(storage)
|
|
16
37
|
}
|
|
17
38
|
|
|
18
|
-
function load
|
|
39
|
+
async function load(component) {
|
|
19
40
|
const reference = component.entity.storage
|
|
20
|
-
const path = require.resolve(reference, {
|
|
21
|
-
|
|
41
|
+
const path = require.resolve(reference, {
|
|
42
|
+
paths: [component.path, import.meta.dirname]
|
|
43
|
+
})
|
|
44
|
+
const { Factory } = await import(pathToFileURL(path).href)
|
|
22
45
|
|
|
23
46
|
return Factory
|
|
24
47
|
}
|
|
25
|
-
|
|
26
|
-
exports.storage = storage
|
|
@@ -0,0 +1,57 @@
|
|
|
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 inherited migrations a storage does not apply', async () => {
|
|
34
|
+
const entity = {
|
|
35
|
+
schema,
|
|
36
|
+
storage: '@toa.io/storages.null',
|
|
37
|
+
migrations: [{ id: 'system:0001', steps: [], prototype: 'system' }]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
assert.notEqual(await storage(manifest(entity), false), undefined)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should accept a storage with no migrations', async () => {
|
|
44
|
+
const entity = { schema, storage: '@toa.io/storages.null' }
|
|
45
|
+
|
|
46
|
+
assert.notEqual(await storage(manifest(entity), false), undefined)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should accept migrations a storage applies', async () => {
|
|
50
|
+
const entity = {
|
|
51
|
+
schema,
|
|
52
|
+
storage: '@toa.io/storages.mongodb',
|
|
53
|
+
migrations: [{ id: '0001', steps: [] }]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
assert.notEqual(await storage(manifest(entity), false), undefined)
|
|
57
|
+
})
|
package/types/bindings.d.ts
CHANGED
|
@@ -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>
|
|
3
|
+
export function broadcast<T>(
|
|
4
|
+
name: string,
|
|
5
|
+
group?: string,
|
|
6
|
+
binding?: string
|
|
7
|
+
): bindings.Broadcast<T>
|