@toa.io/core 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 +43 -0
- package/package.json +22 -12
- package/source/assignment.ts +27 -0
- package/source/call.ts +63 -0
- package/source/cascade.ts +32 -0
- package/source/component.ts +83 -0
- package/source/composition.ts +27 -0
- package/source/connector.ts +217 -0
- package/source/context.ts +68 -0
- package/source/contract/contract.ts +23 -0
- package/source/contract/index.ts +2 -0
- package/source/contract/reply.ts +56 -0
- package/source/contract/request.ts +140 -0
- package/source/contract/schemas.ts +39 -0
- package/source/discovery.ts +53 -0
- package/source/effect.ts +14 -0
- package/source/emission.ts +21 -0
- package/source/entities/changeset.ts +37 -0
- package/source/entities/entity.ts +137 -0
- package/source/entities/factory.ts +52 -0
- package/source/entities/index.ts +4 -0
- package/source/entities/newid.ts +9 -0
- package/source/entities/set.ts +27 -0
- package/source/event.ts +57 -0
- package/source/exceptions.ts +133 -0
- package/source/exposition.ts +33 -0
- package/source/guard.ts +16 -0
- package/source/index.ts +36 -0
- package/source/locator.ts +37 -0
- package/source/observation.ts +16 -0
- package/source/operation.ts +139 -0
- package/source/outbox/index.ts +1 -0
- package/source/outbox/outbox.ts +341 -0
- package/source/query/criteria.ts +90 -0
- package/source/query/options.ts +35 -0
- package/source/query.ts +67 -0
- package/source/receiver.ts +120 -0
- package/source/reflection.ts +20 -0
- package/source/remote.ts +20 -0
- package/source/state.ts +179 -0
- package/source/transition.ts +79 -0
- package/source/transmission.ts +38 -0
- package/source/types/atomicity.ts +63 -0
- package/source/types/bindings.ts +59 -0
- package/source/types/bridges.ts +54 -0
- package/source/types/extensions.ts +89 -0
- package/source/types/index.ts +14 -0
- package/{types/message.d.ts → source/types/message.ts} +2 -0
- package/source/types/operations.ts +31 -0
- package/source/types/outbox.ts +35 -0
- package/source/types/receiver.ts +10 -0
- package/source/types/request.ts +54 -0
- package/source/types/state.ts +9 -0
- package/source/types/storages.ts +134 -0
- package/source/unmanaged.ts +8 -0
- package/test/call.fixtures.js +10 -12
- package/test/call.test.js +42 -9
- package/test/component.fixtures.js +5 -8
- package/test/component.test.js +27 -10
- package/test/connector.fixtures.js +29 -13
- package/test/connector.test.js +128 -38
- package/test/context.fixtures.js +8 -12
- package/test/context.test.js +19 -8
- package/test/contract/conditions.test.js +15 -9
- package/test/contract/contract.fixtures.js +11 -16
- package/test/contract/request.test.js +130 -46
- package/test/discovery.test.js +71 -0
- package/test/emission.fixtures.js +6 -10
- package/test/emission.test.js +37 -12
- package/test/entities/entity.fixtures.js +11 -16
- package/test/entities/entity.test.js +105 -50
- package/test/entities/factory.fixtures.js +26 -13
- package/test/entities/factory.test.js +68 -17
- package/test/entities/set.fixtures.js +6 -8
- package/test/entities/set.test.js +6 -5
- package/test/event.fixtures.js +9 -14
- package/test/event.test.js +78 -34
- package/test/locator.test.js +32 -25
- package/test/outbox.test.js +202 -0
- package/test/query.fixtures.js +1 -5
- package/test/query.test.js +111 -14
- package/test/receiver.fixtures.js +9 -12
- package/test/receiver.test.js +80 -31
- package/test/reflection.test.js +9 -8
- package/test/state.fixtures.js +33 -31
- package/test/state.test.js +135 -19
- package/test/transmission.fixtures.js +4 -6
- package/test/transmission.test.js +47 -16
- package/transpiled/assignment.d.ts +6 -0
- package/transpiled/assignment.js +20 -0
- package/transpiled/assignment.js.map +1 -0
- package/transpiled/call.d.ts +10 -0
- package/transpiled/call.js +52 -0
- package/transpiled/call.js.map +1 -0
- package/transpiled/cascade.d.ts +7 -0
- package/transpiled/cascade.js +28 -0
- package/transpiled/cascade.js.map +1 -0
- package/transpiled/component.d.ts +15 -0
- package/transpiled/component.js +61 -0
- package/transpiled/component.js.map +1 -0
- package/transpiled/composition.d.ts +6 -0
- package/transpiled/composition.js +23 -0
- package/transpiled/composition.js.map +1 -0
- package/transpiled/connector.d.ts +47 -0
- package/transpiled/connector.js +185 -0
- package/transpiled/connector.js.map +1 -0
- package/transpiled/context.d.ts +18 -0
- package/transpiled/context.js +42 -0
- package/transpiled/context.js.map +1 -0
- package/transpiled/contract/contract.d.ts +10 -0
- package/transpiled/contract/contract.js +14 -0
- package/transpiled/contract/contract.js.map +1 -0
- package/transpiled/contract/index.d.ts +2 -0
- package/transpiled/contract/index.js +3 -0
- package/transpiled/contract/index.js.map +1 -0
- package/transpiled/contract/reply.d.ts +7 -0
- package/transpiled/contract/reply.js +48 -0
- package/transpiled/contract/reply.js.map +1 -0
- package/transpiled/contract/request.d.ts +29 -0
- package/transpiled/contract/request.js +103 -0
- package/transpiled/contract/request.js.map +1 -0
- package/transpiled/contract/schemas.d.ts +4 -0
- package/transpiled/contract/schemas.js +36 -0
- package/transpiled/contract/schemas.js.map +1 -0
- package/transpiled/discovery.d.ts +12 -0
- package/transpiled/discovery.js +34 -0
- package/transpiled/discovery.js.map +1 -0
- package/transpiled/effect.d.ts +5 -0
- package/transpiled/effect.js +11 -0
- package/transpiled/effect.js.map +1 -0
- package/transpiled/emission.d.ts +8 -0
- package/transpiled/emission.js +14 -0
- package/transpiled/emission.js.map +1 -0
- package/transpiled/entities/changeset.d.ts +10 -0
- package/transpiled/entities/changeset.js +27 -0
- package/transpiled/entities/changeset.js.map +1 -0
- package/transpiled/entities/entity.d.ts +19 -0
- package/transpiled/entities/entity.js +109 -0
- package/transpiled/entities/entity.js.map +1 -0
- package/transpiled/entities/factory.d.ts +22 -0
- package/transpiled/entities/factory.js +34 -0
- package/transpiled/entities/factory.js.map +1 -0
- package/transpiled/entities/index.d.ts +4 -0
- package/transpiled/entities/index.js +5 -0
- package/transpiled/entities/index.js.map +1 -0
- package/transpiled/entities/newid.d.ts +1 -0
- package/transpiled/entities/newid.js +7 -0
- package/transpiled/entities/newid.js.map +1 -0
- package/transpiled/entities/set.d.ts +10 -0
- package/transpiled/entities/set.js +19 -0
- package/transpiled/entities/set.js.map +1 -0
- package/transpiled/event.d.ts +14 -0
- package/transpiled/event.js +38 -0
- package/transpiled/event.js.map +1 -0
- package/transpiled/exceptions.d.ts +61 -0
- package/transpiled/exceptions.js +93 -0
- package/transpiled/exceptions.js.map +1 -0
- package/transpiled/exposition.d.ts +18 -0
- package/transpiled/exposition.js +18 -0
- package/transpiled/exposition.js.map +1 -0
- package/transpiled/guard.d.ts +7 -0
- package/transpiled/guard.js +12 -0
- package/transpiled/guard.js.map +1 -0
- package/transpiled/index.d.ts +35 -0
- package/transpiled/index.js +29 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/locator.d.ts +12 -0
- package/transpiled/locator.js +32 -0
- package/transpiled/locator.js.map +1 -0
- package/transpiled/observation.d.ts +5 -0
- package/transpiled/observation.js +12 -0
- package/transpiled/observation.js.map +1 -0
- package/transpiled/operation.d.ts +49 -0
- package/transpiled/operation.js +78 -0
- package/transpiled/operation.js.map +1 -0
- package/transpiled/outbox/index.d.ts +1 -0
- package/transpiled/outbox/index.js +2 -0
- package/transpiled/outbox/index.js.map +1 -0
- package/transpiled/outbox/outbox.d.ts +46 -0
- package/transpiled/outbox/outbox.js +268 -0
- package/transpiled/outbox/outbox.js.map +1 -0
- package/transpiled/query/criteria.d.ts +6 -0
- package/transpiled/query/criteria.js +74 -0
- package/transpiled/query/criteria.js.map +1 -0
- package/transpiled/query/options.d.ts +3 -0
- package/transpiled/query/options.js +27 -0
- package/transpiled/query/options.js.map +1 -0
- package/transpiled/query.d.ts +9 -0
- package/transpiled/query.js +57 -0
- package/transpiled/query.js.map +1 -0
- package/transpiled/receiver.d.ts +20 -0
- package/transpiled/receiver.js +92 -0
- package/transpiled/receiver.js.map +1 -0
- package/transpiled/reflection.d.ts +9 -0
- package/transpiled/reflection.js +13 -0
- package/transpiled/reflection.js.map +1 -0
- package/transpiled/remote.d.ts +10 -0
- package/transpiled/remote.js +12 -0
- package/transpiled/remote.js.map +1 -0
- package/transpiled/state.d.ts +33 -0
- package/transpiled/state.js +126 -0
- package/transpiled/state.js.map +1 -0
- package/transpiled/transition.d.ts +14 -0
- package/transpiled/transition.js +51 -0
- package/transpiled/transition.js.map +1 -0
- package/transpiled/transmission.d.ts +8 -0
- package/transpiled/transmission.js +30 -0
- package/transpiled/transmission.js.map +1 -0
- package/transpiled/types/atomicity.d.ts +56 -0
- package/transpiled/types/atomicity.js +2 -0
- package/transpiled/types/atomicity.js.map +1 -0
- package/transpiled/types/bindings.d.ts +42 -0
- package/transpiled/types/bindings.js +2 -0
- package/transpiled/types/bindings.js.map +1 -0
- package/transpiled/types/bridges.d.ts +38 -0
- package/transpiled/types/bridges.js +2 -0
- package/transpiled/types/bridges.js.map +1 -0
- package/transpiled/types/extensions.d.ts +62 -0
- package/transpiled/types/extensions.js +2 -0
- package/transpiled/types/extensions.js.map +1 -0
- package/transpiled/types/index.d.ts +13 -0
- package/transpiled/types/index.js +8 -0
- package/transpiled/types/index.js.map +1 -0
- package/transpiled/types/message.d.ts +5 -0
- package/transpiled/types/message.js +2 -0
- package/transpiled/types/message.js.map +1 -0
- package/transpiled/types/operations.d.ts +15 -0
- package/transpiled/types/operations.js +2 -0
- package/transpiled/types/operations.js.map +1 -0
- package/transpiled/types/outbox.d.ts +28 -0
- package/transpiled/types/outbox.js +2 -0
- package/transpiled/types/outbox.js.map +1 -0
- package/transpiled/types/receiver.d.ts +9 -0
- package/transpiled/types/receiver.js +2 -0
- package/transpiled/types/receiver.js.map +1 -0
- package/transpiled/types/request.d.ts +55 -0
- package/transpiled/types/request.js +2 -0
- package/transpiled/types/request.js.map +1 -0
- package/transpiled/types/state.d.ts +9 -0
- package/transpiled/types/state.js +2 -0
- package/transpiled/types/state.js.map +1 -0
- package/transpiled/types/storages.d.ts +111 -0
- package/transpiled/types/storages.js +2 -0
- package/transpiled/types/storages.js.map +1 -0
- package/transpiled/unmanaged.d.ts +5 -0
- package/transpiled/unmanaged.js +7 -0
- package/transpiled/unmanaged.js.map +1 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/src/assignment.js +0 -31
- package/src/call.js +0 -42
- package/src/cascade.js +0 -36
- package/src/component.js +0 -41
- package/src/composition.js +0 -25
- package/src/connector.js +0 -183
- package/src/context.js +0 -55
- package/src/contract/conditions.js +0 -21
- package/src/contract/index.js +0 -7
- package/src/contract/reply.js +0 -25
- package/src/contract/request.js +0 -67
- package/src/contract/schemas/error.yaml +0 -9
- package/src/contract/schemas/index.js +0 -7
- package/src/contract/schemas/query.yaml +0 -30
- package/src/discovery.js +0 -44
- package/src/emission.js +0 -23
- package/src/entities/changeset.js +0 -43
- package/src/entities/entity.js +0 -73
- package/src/entities/factory.js +0 -33
- package/src/entities/index.js +0 -5
- package/src/entities/set.js +0 -15
- package/src/event.js +0 -40
- package/src/exceptions.js +0 -100
- package/src/exposition.js +0 -27
- package/src/index.js +0 -47
- package/src/locator.js +0 -54
- package/src/observation.js +0 -20
- package/src/operation.js +0 -69
- package/src/query/criteria.js +0 -41
- package/src/query/options.js +0 -40
- package/src/query.js +0 -41
- package/src/receiver.js +0 -65
- package/src/reflection.js +0 -28
- package/src/remote.js +0 -17
- package/src/state.js +0 -99
- package/src/transition.js +0 -73
- package/src/transmission.js +0 -33
- package/types/bindings.d.ts +0 -40
- package/types/bridges.ts +0 -30
- package/types/component.d.ts +0 -9
- package/types/connector.d.ts +0 -18
- package/types/context.d.ts +0 -21
- package/types/entity.d.ts +0 -46
- package/types/event.d.ts +0 -11
- package/types/exception.d.ts +0 -4
- package/types/extensions.d.ts +0 -29
- package/types/index.ts +0 -14
- package/types/locator.d.ts +0 -13
- package/types/operations.d.ts +0 -2
- package/types/query.d.ts +0 -15
- package/types/receiver.d.ts +0 -12
- package/types/reflection.d.ts +0 -14
- package/types/request.d.ts +0 -23
- package/types/state.d.ts +0 -35
- package/types/storages.d.ts +0 -71
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
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.292](https://github.com/toa-io/toa/compare/v1.0.0-alpha.291...v1.0.0-alpha.292) (2026-09-07)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @toa.io/core
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [1.0.0-alpha.291](https://github.com/toa-io/toa/compare/v1.0.0-alpha.290...v1.0.0-alpha.291) (2026-09-07)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @toa.io/core
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# [1.0.0-alpha.289](https://github.com/toa-io/toa/compare/v1.0.0-alpha.288...v1.0.0-alpha.289) (2026-09-07)
|
|
23
|
+
|
|
24
|
+
* A deploy moves only what changed, and a component sees none of the runtime's environment (#1073) ([f38e3db](https://github.com/toa-io/toa/commit/f38e3db533f24db866c57bfa1ef294eff1eaf499)), closes [#1073](https://github.com/toa-io/toa/issues/1073) [#1064](https://github.com/toa-io/toa/issues/1064) [#1066](https://github.com/toa-io/toa/issues/1066) [#1067](https://github.com/toa-io/toa/issues/1067) [#1068](https://github.com/toa-io/toa/issues/1068) [#1069](https://github.com/toa-io/toa/issues/1069) [#1071](https://github.com/toa-io/toa/issues/1071) [#1070](https://github.com/toa-io/toa/issues/1070) [#1072](https://github.com/toa-io/toa/issues/1072)
|
|
25
|
+
|
|
26
|
+
### BREAKING CHANGES
|
|
27
|
+
|
|
28
|
+
* a component that read `process.env.TOA_*` reads `context` instead;
|
|
29
|
+
`echo(input)` no longer substitutes from the environment; a bash operation sees no
|
|
30
|
+
`TOA_*`; images no longer set `USER node` — see migrations/289.md.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# [1.0.0-alpha.288](https://github.com/toa-io/toa/compare/v1.0.0-alpha.287...v1.0.0-alpha.288) (2026-09-06)
|
|
34
|
+
|
|
35
|
+
**Note:** Version bump only for package @toa.io/core
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# [1.0.0-alpha.287](https://github.com/toa-io/toa/compare/v1.0.0-alpha.286...v1.0.0-alpha.287) (2026-09-06)
|
|
42
|
+
|
|
43
|
+
**Note:** Version bump only for package @toa.io/core
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/core",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.292",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "Toa Core",
|
|
5
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
7
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -11,23 +12,32 @@
|
|
|
11
12
|
"bugs": {
|
|
12
13
|
"url": "https://github.com/toa-io/toa/issues"
|
|
13
14
|
},
|
|
14
|
-
"main": "
|
|
15
|
-
"types": "
|
|
15
|
+
"main": "transpiled/index.js",
|
|
16
|
+
"types": "transpiled/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./transpiled/index.d.ts",
|
|
20
|
+
"default": "./transpiled/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./types": {
|
|
23
|
+
"types": "./transpiled/types/index.d.ts",
|
|
24
|
+
"default": "./transpiled/types/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
16
28
|
"publishConfig": {
|
|
17
29
|
"access": "public"
|
|
18
30
|
},
|
|
19
31
|
"scripts": {
|
|
32
|
+
"transpile": "tsc",
|
|
20
33
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
21
34
|
},
|
|
22
35
|
"dependencies": {
|
|
23
|
-
"@rsql/parser": "1.
|
|
24
|
-
"@toa.io/
|
|
25
|
-
"@toa.io/
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
},
|
|
29
|
-
"devDependencies": {
|
|
30
|
-
"clone-deep": "4.0.1"
|
|
36
|
+
"@rsql/parser": "1.6.0",
|
|
37
|
+
"@toa.io/generic": "1.0.0-alpha.292",
|
|
38
|
+
"@toa.io/schemas": "1.0.0-alpha.292",
|
|
39
|
+
"openspan": "1.0.0-alpha.286",
|
|
40
|
+
"uuid": "14.0.2"
|
|
31
41
|
},
|
|
32
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "0f6aa3ad9dbec86bb632a7975ea61d1dde1d8fc5"
|
|
33
43
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Operation } from './operation.js'
|
|
2
|
+
import type { Store } from './operation.js'
|
|
3
|
+
import type { Changeset } from './entities/changeset.js'
|
|
4
|
+
|
|
5
|
+
export class Assignment extends Operation {
|
|
6
|
+
protected override async acquire(store: Store): Promise<void> {
|
|
7
|
+
const changeset = this.scope.changeset(store.request.query as any)
|
|
8
|
+
|
|
9
|
+
store.scope = changeset
|
|
10
|
+
store.state = changeset.get()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
protected override async commit(store: Store): Promise<void> {
|
|
14
|
+
const { scope, state, reply, request } = store
|
|
15
|
+
|
|
16
|
+
if (reply.error !== undefined) return
|
|
17
|
+
|
|
18
|
+
const changeset = scope as Changeset
|
|
19
|
+
|
|
20
|
+
changeset.set(state)
|
|
21
|
+
|
|
22
|
+
const output = await this.scope.apply(changeset, request.input)
|
|
23
|
+
|
|
24
|
+
// assignment returns new state by default
|
|
25
|
+
if (store.reply.output === undefined) store.reply.output = output
|
|
26
|
+
}
|
|
27
|
+
}
|
package/source/call.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Readable } from 'node:stream'
|
|
2
|
+
import { current, encode } from 'openspan'
|
|
3
|
+
import { Connector } from './connector.js'
|
|
4
|
+
import type { Transmission } from './transmission.js'
|
|
5
|
+
import type { Request as Contract } from './contract/request.js'
|
|
6
|
+
import type { Request, Source } from './types/request.js'
|
|
7
|
+
|
|
8
|
+
export class Call extends Connector {
|
|
9
|
+
readonly #transmitter: Transmission
|
|
10
|
+
readonly #contract: Contract
|
|
11
|
+
readonly #source: Source | undefined
|
|
12
|
+
|
|
13
|
+
public constructor(transmitter: Transmission, contract: Contract, source?: Source) {
|
|
14
|
+
super()
|
|
15
|
+
|
|
16
|
+
this.#transmitter = transmitter
|
|
17
|
+
this.#contract = contract
|
|
18
|
+
this.#source = source
|
|
19
|
+
|
|
20
|
+
this.depends(transmitter)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public async invoke(request: Request = {}): Promise<any> {
|
|
24
|
+
// the caller may have attributed the call itself, as the node bridge does
|
|
25
|
+
if (this.#source !== undefined) request.source ??= this.#source
|
|
26
|
+
|
|
27
|
+
this.#contract.fit(request)
|
|
28
|
+
|
|
29
|
+
// an operation that takes no input still has to send an explicit null
|
|
30
|
+
request.input ??= null
|
|
31
|
+
|
|
32
|
+
// avoid validation on the recipient's side
|
|
33
|
+
request.authentic = true
|
|
34
|
+
|
|
35
|
+
const context = current()
|
|
36
|
+
|
|
37
|
+
if (context !== undefined) request.telemetry = encode(context)
|
|
38
|
+
|
|
39
|
+
const reply = await this.#transmitter.request(request)
|
|
40
|
+
|
|
41
|
+
if (reply === null) return null
|
|
42
|
+
else if (reply instanceof Readable) return reply
|
|
43
|
+
else {
|
|
44
|
+
if (reply.exception !== undefined) throw reply.exception
|
|
45
|
+
|
|
46
|
+
if (reply.error !== undefined) return new RemoteError(reply.error)
|
|
47
|
+
else return reply.output
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public explain(): any {
|
|
52
|
+
return this.#contract.discovery
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// the remote error as a value: every property it carries, and nothing else enumerable
|
|
57
|
+
class RemoteError extends Error {
|
|
58
|
+
public constructor(error: object) {
|
|
59
|
+
super()
|
|
60
|
+
|
|
61
|
+
Object.assign(this, error)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Connector } from './connector.js'
|
|
2
|
+
import type { Algorithm } from './types/bridges.js'
|
|
3
|
+
|
|
4
|
+
export class Cascade extends Connector {
|
|
5
|
+
readonly #last: Algorithm
|
|
6
|
+
|
|
7
|
+
public constructor(bridges: Algorithm[], preflight?: Connector) {
|
|
8
|
+
super()
|
|
9
|
+
|
|
10
|
+
// this.#bridges = bridges
|
|
11
|
+
this.#last = bridges[bridges.length - 1]
|
|
12
|
+
|
|
13
|
+
if (preflight === undefined) this.depends(bridges)
|
|
14
|
+
else this.depends(bridges).depends(preflight)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public async run(...args: [any, any?]): Promise<any> {
|
|
18
|
+
// const reply = {}
|
|
19
|
+
//
|
|
20
|
+
// for (const bridge of this.#bridges) {
|
|
21
|
+
// const partial = await bridge.execute(...args)
|
|
22
|
+
//
|
|
23
|
+
// if (partial.error) return { error: partial.error }
|
|
24
|
+
//
|
|
25
|
+
// merge(reply, partial)
|
|
26
|
+
// }
|
|
27
|
+
//
|
|
28
|
+
// return reply
|
|
29
|
+
|
|
30
|
+
return this.#last.execute(...args)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { console, current, decode, run, type SpanOptions } from 'openspan'
|
|
3
|
+
import { Connector } from './connector.js'
|
|
4
|
+
import type { Locator } from './locator.js'
|
|
5
|
+
import type { Request } from './types/request.js'
|
|
6
|
+
|
|
7
|
+
/** What a component holds one of per endpoint: an operation, or the call that stands for it. */
|
|
8
|
+
export interface Invocable extends Connector {
|
|
9
|
+
invoke: (request: Request) => Promise<any>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class Component<O extends Invocable = Invocable> extends Connector {
|
|
13
|
+
public readonly locator: Locator
|
|
14
|
+
|
|
15
|
+
protected readonly operations: Record<string, O>
|
|
16
|
+
|
|
17
|
+
protected kind: 'server' | 'client' = 'server'
|
|
18
|
+
|
|
19
|
+
/** span options per endpoint */
|
|
20
|
+
readonly #spans: Record<string, SpanOptions> = {}
|
|
21
|
+
|
|
22
|
+
public constructor(locator: Locator, operations: Record<string, O>) {
|
|
23
|
+
super()
|
|
24
|
+
|
|
25
|
+
this.locator = locator
|
|
26
|
+
this.operations = operations
|
|
27
|
+
|
|
28
|
+
Object.values(operations).forEach((operation) => this.depends(operation))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public async invoke<T = any>(endpoint: string, request?: Request): Promise<T> {
|
|
32
|
+
if (!(endpoint in this.operations))
|
|
33
|
+
// `assert.fail`, not `assert.ok`: the message is built only when it is needed
|
|
34
|
+
assert.fail(`Endpoint '${endpoint}' is not provided by '${this.locator.id}'`)
|
|
35
|
+
|
|
36
|
+
// if the request carries no telemetry, the trace starts here
|
|
37
|
+
const remote = request?.telemetry === undefined ? null : decode(request.telemetry)
|
|
38
|
+
const task = async (): Promise<any> => this.#process(endpoint, request)
|
|
39
|
+
|
|
40
|
+
if (remote === null) return task()
|
|
41
|
+
else return run(remote, task)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async #process(endpoint: string, request?: Request): Promise<any> {
|
|
45
|
+
return console.span(this.#span(endpoint), async () => {
|
|
46
|
+
const reply = await this.operations[endpoint].invoke(request as Request)
|
|
47
|
+
|
|
48
|
+
if (reply?.exception !== undefined) {
|
|
49
|
+
const span = current()
|
|
50
|
+
|
|
51
|
+
if (span !== undefined) span.status = 'error'
|
|
52
|
+
|
|
53
|
+
console.error('Failed to execute operation', {
|
|
54
|
+
endpoint: `${this.locator.id}.${endpoint}`,
|
|
55
|
+
exception: reply.exception
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return reply
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The span of an endpoint never changes, so it is built once. Not in the constructor:
|
|
65
|
+
* `kind` is a field of the subclass, and those are assigned after this one is built.
|
|
66
|
+
*
|
|
67
|
+
*/
|
|
68
|
+
#span(endpoint: string): SpanOptions {
|
|
69
|
+
let options = this.#spans[endpoint]
|
|
70
|
+
|
|
71
|
+
if (options === undefined) {
|
|
72
|
+
options = { name: `${this.locator.id}.${endpoint}`, kind: this.kind }
|
|
73
|
+
|
|
74
|
+
// the server span is emitted by the component itself, while the client span
|
|
75
|
+
// belongs to the calling service and inherits it from the context
|
|
76
|
+
if (this.kind === 'server') options.service = this.locator.id
|
|
77
|
+
|
|
78
|
+
this.#spans[endpoint] = options
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return options
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { console } from 'openspan'
|
|
2
|
+
import { Connector } from './connector.js'
|
|
3
|
+
|
|
4
|
+
export class Composition extends Connector {
|
|
5
|
+
// eslint-disable-next-line max-params
|
|
6
|
+
public constructor(
|
|
7
|
+
expositions: Connector[],
|
|
8
|
+
producers: Connector[],
|
|
9
|
+
receivers: Connector[],
|
|
10
|
+
tenants: Connector[]
|
|
11
|
+
) {
|
|
12
|
+
super()
|
|
13
|
+
|
|
14
|
+
if (expositions.length > 0) this.depends(expositions)
|
|
15
|
+
if (producers.length > 0) this.depends(producers)
|
|
16
|
+
if (receivers.length > 0) this.depends(receivers)
|
|
17
|
+
if (tenants.length > 0) this.depends(tenants)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected override async open(): Promise<void> {
|
|
21
|
+
console.info('Composition complete')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected override async dispose(): Promise<void> {
|
|
25
|
+
console.info('Composition shutdown complete')
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { console } from 'openspan'
|
|
2
|
+
import { environment } from '@toa.io/generic'
|
|
3
|
+
import type { Locator } from './locator.js'
|
|
4
|
+
|
|
5
|
+
/** Abstract connections hierarchy */
|
|
6
|
+
export class Connector {
|
|
7
|
+
#dependencies: Connector[] = []
|
|
8
|
+
#links: Connector[] = []
|
|
9
|
+
#connecting: Promise<void> | undefined
|
|
10
|
+
#disconnecting: Promise<void> | undefined
|
|
11
|
+
#discarded: boolean = false
|
|
12
|
+
|
|
13
|
+
public readonly id: string
|
|
14
|
+
public connected: boolean = false
|
|
15
|
+
|
|
16
|
+
public constructor() {
|
|
17
|
+
this.id = this.constructor.name + '#' + Math.random().toString(36).substring(2, 8)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a dependency and backlink with another Connector or a set of Connectors
|
|
22
|
+
*
|
|
23
|
+
* See .connect() and .disconnect()
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
public depends(connector: Connector | Connector[]): Connector {
|
|
27
|
+
let next: Connector
|
|
28
|
+
|
|
29
|
+
if (connector instanceof Array) {
|
|
30
|
+
if (connector.length === 0) throw new Error('Connectors array must not be empty')
|
|
31
|
+
|
|
32
|
+
if (connector.length > 1) {
|
|
33
|
+
next = new Connector()
|
|
34
|
+
|
|
35
|
+
for (const item of connector) {
|
|
36
|
+
this.#dependencies.push(item)
|
|
37
|
+
item.depends(next)
|
|
38
|
+
}
|
|
39
|
+
} else next = connector[0]
|
|
40
|
+
} else next = connector
|
|
41
|
+
|
|
42
|
+
this.#dependencies.push(next)
|
|
43
|
+
next.link(this)
|
|
44
|
+
|
|
45
|
+
// See .#discard()
|
|
46
|
+
if (this.#disconnecting !== undefined) next.#discard()
|
|
47
|
+
|
|
48
|
+
return next
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Disconnects, now and again if a connection is still to land.
|
|
53
|
+
*
|
|
54
|
+
* A dependency taken on after its dependant was disconnected — a lookup that returns
|
|
55
|
+
* once the teardown walk has gone by — is one nothing is left to take down, as the
|
|
56
|
+
* walk that would have reached it is over. One that stays connected holds open
|
|
57
|
+
* everything it depends on with it: a dependency reads `connected` of every dependant
|
|
58
|
+
* before it closes, and a dependant that is connected for good is a dependency that
|
|
59
|
+
* never closes.
|
|
60
|
+
*/
|
|
61
|
+
#discard(): void {
|
|
62
|
+
this.#discarded = true
|
|
63
|
+
|
|
64
|
+
// one that is up already, or on its way, is taken down here; one that is not is
|
|
65
|
+
// taken down by the connection when it comes, and until then there is nothing to do
|
|
66
|
+
if (this.#connecting !== undefined) void this.#drop()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Creates a backlink to another Connector
|
|
71
|
+
*
|
|
72
|
+
* See .connect() and .disconnect()
|
|
73
|
+
*
|
|
74
|
+
*/
|
|
75
|
+
public link(connector: Connector): void {
|
|
76
|
+
this.#links.push(connector)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Connects dependants then self
|
|
81
|
+
*
|
|
82
|
+
* In case of exception disconnects with current connection interruption
|
|
83
|
+
*
|
|
84
|
+
* Method is idempotent
|
|
85
|
+
*
|
|
86
|
+
*/
|
|
87
|
+
public async connect(): Promise<void> {
|
|
88
|
+
if (this.#connecting) return this.#connecting
|
|
89
|
+
|
|
90
|
+
this.#disconnecting = undefined
|
|
91
|
+
|
|
92
|
+
const work = async () => {
|
|
93
|
+
await Promise.all(this.#dependencies.map((connector) => connector.connect()))
|
|
94
|
+
await this.open()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// anonymous grouping nodes are not worth a span
|
|
98
|
+
const connecting =
|
|
99
|
+
TRACE_BOOT && this.constructor.name !== 'Connector'
|
|
100
|
+
? console.span(
|
|
101
|
+
{
|
|
102
|
+
name: `connect ${this.constructor.name}`,
|
|
103
|
+
// a connector that has a locator is named by it; a grouping node has none
|
|
104
|
+
attributes: { id: (this as { locator?: Locator }).locator?.id ?? this.id }
|
|
105
|
+
},
|
|
106
|
+
work
|
|
107
|
+
)
|
|
108
|
+
: work()
|
|
109
|
+
|
|
110
|
+
this.#connecting = connecting
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await connecting
|
|
114
|
+
|
|
115
|
+
/*
|
|
116
|
+
* A disconnection that did not wait for this connection emptied `#connecting` to
|
|
117
|
+
* say so. Reporting `connected` now would report it of a connector nothing takes
|
|
118
|
+
* down again — its `#disconnecting` has settled, so a later call returns at once —
|
|
119
|
+
* and what it depends on reads that of every dependant before it closes.
|
|
120
|
+
*/
|
|
121
|
+
if (this.#connecting === connecting) this.connected = true
|
|
122
|
+
} catch (e) {
|
|
123
|
+
await this.disconnect(true)
|
|
124
|
+
throw e
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// a connection asked for before this one was discarded, landing after. See .#discard()
|
|
128
|
+
if (this.#discarded) await this.#drop()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Disconnects a discarded connector, saying so rather than throwing at whoever is not waiting. */
|
|
132
|
+
async #drop(): Promise<void> {
|
|
133
|
+
this.#discarded = false
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
await this.disconnect()
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error('Discarded connector failed to disconnect', { id: this.id, error })
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Disconnects self then dependants
|
|
144
|
+
*
|
|
145
|
+
* Does nothing if there are connected linked Connectors
|
|
146
|
+
*
|
|
147
|
+
* Method is idempotent
|
|
148
|
+
*
|
|
149
|
+
*/
|
|
150
|
+
public async disconnect(interrupt?: boolean): Promise<void> {
|
|
151
|
+
// a connector that has not finished connecting has nothing to close, and
|
|
152
|
+
// awaiting a connection that may never settle outlives any grace period
|
|
153
|
+
const pending = interrupt === true || this.connected === false
|
|
154
|
+
|
|
155
|
+
if (!pending) await this.#connecting
|
|
156
|
+
|
|
157
|
+
if (this.#disconnecting) return this.#disconnecting
|
|
158
|
+
|
|
159
|
+
const linked = this.#links.reduce((acc, parent) => acc || parent.connected, false)
|
|
160
|
+
|
|
161
|
+
if (linked && interrupt !== true) return
|
|
162
|
+
|
|
163
|
+
this.#disconnecting = (async () => {
|
|
164
|
+
const start = +new Date()
|
|
165
|
+
|
|
166
|
+
const interval = setInterval(() => {
|
|
167
|
+
const delay = +new Date() - start
|
|
168
|
+
|
|
169
|
+
if (delay > DELAY)
|
|
170
|
+
console.warn(`Connector ${this.id} still disconnecting (${delay})`)
|
|
171
|
+
}, DELAY)
|
|
172
|
+
|
|
173
|
+
if (!pending) await this.close()
|
|
174
|
+
|
|
175
|
+
// said once the closing is done rather than when it starts: a dependant that is
|
|
176
|
+
// still closing is still using what it depends on, and the check above reads this
|
|
177
|
+
// of every dependant. Saying it early lets the first to start tear down a shared
|
|
178
|
+
// dependency under the others.
|
|
179
|
+
this.connected = false
|
|
180
|
+
this.#connecting = undefined
|
|
181
|
+
|
|
182
|
+
clearInterval(interval)
|
|
183
|
+
|
|
184
|
+
await Promise.all(this.#dependencies.map((connector) => connector.disconnect()))
|
|
185
|
+
|
|
186
|
+
await this.dispose()
|
|
187
|
+
})()
|
|
188
|
+
|
|
189
|
+
await this.#disconnecting
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public async reconnect(): Promise<void> {
|
|
193
|
+
await this.disconnect()
|
|
194
|
+
await this.connect()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public debug(node: Record<string, any> = {}): Record<string, any> {
|
|
198
|
+
node[this.id] = { connected: this.connected }
|
|
199
|
+
|
|
200
|
+
if (this.#dependencies.length > 0)
|
|
201
|
+
for (const connector of this.#dependencies) connector.debug?.(node[this.id])
|
|
202
|
+
|
|
203
|
+
return node
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Called on connection */
|
|
207
|
+
protected open(): Promise<void> | void {}
|
|
208
|
+
|
|
209
|
+
/** Called on disconnection */
|
|
210
|
+
protected close(): Promise<void> | void {}
|
|
211
|
+
|
|
212
|
+
/** Called after self and dependants disconnection is complete */
|
|
213
|
+
protected dispose(): Promise<void> | void {}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const DELAY = 5000
|
|
217
|
+
const TRACE_BOOT = environment.get('TOA_BOOT_TRACE') === '1'
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Connector } from './connector.js'
|
|
2
|
+
import { environment } from '@toa.io/generic'
|
|
3
|
+
import type { Locator } from './locator.js'
|
|
4
|
+
import type { Component } from './component.js'
|
|
5
|
+
import type { Remote } from './remote.js'
|
|
6
|
+
import type { Aspect } from './types/extensions.js'
|
|
7
|
+
import type { Request } from './types/request.js'
|
|
8
|
+
|
|
9
|
+
type Discover = (namespace: string, name: string) => Promise<Remote>
|
|
10
|
+
|
|
11
|
+
export class Context extends Connector {
|
|
12
|
+
public readonly env: string | undefined
|
|
13
|
+
public readonly name: string | undefined
|
|
14
|
+
public readonly aspects: Aspect[]
|
|
15
|
+
public readonly locator: Locator
|
|
16
|
+
|
|
17
|
+
readonly #local: Component
|
|
18
|
+
readonly #discover: Discover
|
|
19
|
+
readonly #remotes: Record<string, Promise<Remote>> = {}
|
|
20
|
+
|
|
21
|
+
public constructor(local: Component, discover: Discover, aspects: Aspect[] = []) {
|
|
22
|
+
super()
|
|
23
|
+
|
|
24
|
+
this.env = environment.get('TOA_ENV')
|
|
25
|
+
this.name = environment.get('TOA_CONTEXT')
|
|
26
|
+
this.aspects = aspects
|
|
27
|
+
this.locator = local?.locator
|
|
28
|
+
|
|
29
|
+
this.#local = local
|
|
30
|
+
this.#discover = discover
|
|
31
|
+
|
|
32
|
+
this.depends(local)
|
|
33
|
+
|
|
34
|
+
if (aspects.length > 0) this.depends(aspects)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async apply(endpoint: string, request: Request): Promise<any> {
|
|
38
|
+
return this.#local.invoke(endpoint, request)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// eslint-disable-next-line max-params
|
|
42
|
+
public async call(
|
|
43
|
+
namespace: string,
|
|
44
|
+
name: string,
|
|
45
|
+
endpoint: string,
|
|
46
|
+
request: Request
|
|
47
|
+
): Promise<any> {
|
|
48
|
+
const remote = await this.#remote(namespace, name)
|
|
49
|
+
|
|
50
|
+
return remote.invoke(endpoint, request)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async #remote(namespace: string, name: string): Promise<Remote> {
|
|
54
|
+
const key = namespace + '.' + name
|
|
55
|
+
|
|
56
|
+
this.#remotes[key] ??= this.#connect(namespace, name)
|
|
57
|
+
|
|
58
|
+
return this.#remotes[key]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async #connect(namespace: string, name: string): Promise<Remote> {
|
|
62
|
+
const remote = await this.#discover(namespace, name)
|
|
63
|
+
|
|
64
|
+
this.depends(remote)
|
|
65
|
+
|
|
66
|
+
return remote
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SystemException } from '../exceptions.js'
|
|
2
|
+
import type { Schema, SchemaError } from '@toa.io/schemas'
|
|
3
|
+
import type { Exception } from '../exceptions.js'
|
|
4
|
+
|
|
5
|
+
/** What a contract throws with, stated by the subclass. */
|
|
6
|
+
export type Refusal = new (error: SchemaError, cause?: unknown) => Exception
|
|
7
|
+
|
|
8
|
+
export class Contract {
|
|
9
|
+
public readonly schema: Schema
|
|
10
|
+
|
|
11
|
+
public static Exception: Refusal = SystemException as unknown as Refusal
|
|
12
|
+
|
|
13
|
+
public constructor(schema: Schema) {
|
|
14
|
+
this.schema = schema
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public fit(value: unknown): void {
|
|
18
|
+
const error = this.schema.fit(value)
|
|
19
|
+
|
|
20
|
+
if (error !== null)
|
|
21
|
+
throw new (this.constructor as typeof Contract).Exception(error, value)
|
|
22
|
+
}
|
|
23
|
+
}
|