@toa.io/core 1.0.0-alpha.28 → 1.0.0-alpha.282
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 +168 -0
- package/package.json +8 -10
- package/src/assignment.js +5 -8
- package/src/call.js +35 -9
- package/src/cascade.js +9 -11
- package/src/component.js +61 -21
- package/src/composition.js +3 -7
- package/src/connector.js +29 -22
- package/src/context.js +8 -6
- package/src/contract/contract.js +18 -0
- package/src/contract/index.js +2 -7
- package/src/contract/reply.js +36 -15
- package/src/contract/request.js +20 -10
- package/src/contract/schemas/index.js +9 -5
- package/src/contract/schemas/query.yaml +14 -1
- package/src/contract/schemas/source.yaml +23 -0
- package/src/discovery.js +15 -13
- package/src/effect.js +15 -0
- package/src/emission.js +2 -6
- package/src/entities/changeset.js +7 -14
- package/src/entities/entity.js +75 -29
- package/src/entities/factory.js +21 -14
- package/src/entities/index.js +1 -5
- package/src/entities/newid.js +9 -0
- package/src/entities/set.js +13 -4
- package/src/event.js +21 -7
- package/src/exceptions.js +53 -39
- package/src/exposition.js +5 -8
- package/src/guard.js +13 -0
- package/src/index.js +28 -47
- package/src/locator.js +2 -6
- package/src/observation.js +3 -15
- package/src/operation.js +40 -14
- package/src/outbox/index.js +1 -0
- package/src/outbox/outbox.js +296 -0
- package/src/query/criteria.js +3 -7
- package/src/query/options.js +5 -8
- package/src/query.js +36 -7
- package/src/receiver.js +76 -18
- package/src/reflection.js +2 -6
- package/src/remote.js +9 -12
- package/src/state.js +89 -50
- package/src/transition.js +13 -25
- package/src/transmission.js +15 -10
- package/src/unmanaged.js +7 -0
- package/test/call.fixtures.js +8 -12
- package/test/call.test.js +21 -9
- package/test/component.fixtures.js +5 -8
- package/test/component.test.js +22 -10
- package/test/connector.fixtures.js +10 -8
- package/test/connector.test.js +59 -38
- package/test/context.fixtures.js +8 -12
- package/test/context.test.js +18 -7
- package/test/contract/conditions.test.js +11 -9
- package/test/contract/contract.fixtures.js +12 -16
- package/test/contract/request.test.js +84 -34
- package/test/discovery.test.js +64 -0
- package/test/emission.fixtures.js +6 -10
- package/test/emission.test.js +29 -12
- package/test/entities/entity.fixtures.js +7 -11
- package/test/entities/entity.test.js +70 -49
- package/test/entities/factory.fixtures.js +22 -10
- package/test/entities/factory.test.js +29 -15
- 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 +57 -31
- package/test/locator.test.js +25 -24
- package/test/outbox.test.js +121 -0
- package/test/query.fixtures.js +1 -5
- package/test/query.test.js +42 -14
- package/test/receiver.fixtures.js +9 -12
- package/test/receiver.test.js +45 -22
- package/test/reflection.test.js +7 -6
- package/test/state.fixtures.js +25 -30
- package/test/state.test.js +72 -19
- package/test/transmission.fixtures.js +4 -6
- package/test/transmission.test.js +33 -15
- package/types/atomicity.d.ts +58 -0
- package/types/bindings.d.ts +8 -6
- package/types/bridges.ts +5 -2
- package/types/component.d.ts +7 -4
- package/types/context.d.ts +6 -3
- package/types/entity.d.ts +4 -4
- package/types/event.d.ts +1 -1
- package/types/extensions.d.ts +35 -10
- package/types/index.ts +16 -13
- package/types/message.d.ts +1 -0
- package/types/operations.d.ts +6 -0
- package/types/outbox.d.ts +53 -0
- package/types/query.d.ts +2 -0
- package/types/receiver.d.ts +2 -2
- package/types/reflection.d.ts +1 -1
- package/types/remote.d.ts +18 -0
- package/types/request.d.ts +16 -1
- package/types/state.d.ts +11 -8
- package/types/storages.d.ts +36 -11
- package/src/contract/conditions.js +0 -21
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
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.282](https://github.com/toa-io/toa/compare/v1.0.0-alpha.281...v1.0.0-alpha.282) (2026-09-03)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **core:** a Host for extensions ([6315eac](https://github.com/toa-io/toa/commit/6315eac1e9fb05be472272de06385c54ad5934ce))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# [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)
|
|
14
|
+
|
|
15
|
+
**Note:** Version bump only for package @toa.io/core
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# [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)
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
* read a manifest the way js-yaml read one before ([fcea1b2](https://github.com/toa-io/toa/commit/fcea1b21996d6efa6c3198acbfc9f70f1ce0c35c))
|
|
26
|
+
* settle what the module loader made asynchronous ([d4ab9df](https://github.com/toa-io/toa/commit/d4ab9dfb09e382da311dba08f52214e7645f4a6c))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# [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)
|
|
30
|
+
|
|
31
|
+
**Note:** Version bump only for package @toa.io/core
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# [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)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Bug Fixes
|
|
41
|
+
|
|
42
|
+
* **core:** stop the reply contract from relaxing the declaration ([6222893](https://github.com/toa-io/toa/commit/622289326630b94deff93499d6a851bfaa57c69a))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
* feat(atomicity)!: take a quorum of independent servers ([862562f](https://github.com/toa-io/toa/commit/862562f24d77ec00127dbbd88d43802b208c643b))
|
|
46
|
+
* refactor(atomicity)!: take the lock manager from the stash ([4fd91eb](https://github.com/toa-io/toa/commit/4fd91eb2fdb68f7b87aaf16d182794bbc567ba57))
|
|
47
|
+
* refactor(core)!: pump the outbox in one cycle ([bf590fe](https://github.com/toa-io/toa/commit/bf590fe8ed59c701b5fd1a91ba4874685b79242f))
|
|
48
|
+
* refactor(atomicity)!: make the connector a family, not a partitioner ([06fd63a](https://github.com/toa-io/toa/commit/06fd63ad6296b724ef83afccf4e4e25d0bcaf080))
|
|
49
|
+
* refactor(atomicity)!: rename the connector and free it of the outbox ([21f1a41](https://github.com/toa-io/toa/commit/21f1a41aceafcfd35c7620014062fe46b54afa95))
|
|
50
|
+
* 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)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Features
|
|
54
|
+
|
|
55
|
+
* **atomicity:** meter what the group has spent ([bb1118a](https://github.com/toa-io/toa/commit/bb1118aa60bfeb43d3212f2495b7797445f003c3))
|
|
56
|
+
* **core:** give every component an atom aspect ([b501b9c](https://github.com/toa-io/toa/commit/b501b9cd3d5f5408d9faa71213e953fe2792cf9f))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
### BREAKING CHANGES
|
|
60
|
+
|
|
61
|
+
* `atomicity.redis` accepts a list again, of independent servers rather
|
|
62
|
+
than cluster nodes, and refuses an even number of them.
|
|
63
|
+
|
|
64
|
+
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
65
|
+
* `context.stash.lock` is gone; lock through `context.atom` instead.
|
|
66
|
+
A stash pointer resolving to several addresses now uses the first.
|
|
67
|
+
|
|
68
|
+
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
69
|
+
* `Storage.outbox.pending` takes a fourth argument, the id to
|
|
70
|
+
continue from.
|
|
71
|
+
|
|
72
|
+
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
73
|
+
* `Factory.partition(group)` is `Factory.atom(group)`, and the
|
|
74
|
+
`Partition` it returned is an `Atom`. `slots(total)` is unchanged.
|
|
75
|
+
|
|
76
|
+
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
77
|
+
* `@toa.io/partitions.redis` is now `@toa.io/atomicity`, and its
|
|
78
|
+
`lanes(total)` is `slots(total)`. Redis is declared as `atomicity` in the context
|
|
79
|
+
rather than `outbox.redis`, and read from `TOA_ATOMICITY_REDIS`.
|
|
80
|
+
`TOA_OUTBOX_PARTITION_INTERVAL` is `TOA_ATOMICITY_INTERVAL`.
|
|
81
|
+
|
|
82
|
+
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
83
|
+
* `event.changeset` is removed; use `origin` and `state`. `State`
|
|
84
|
+
takes an `Outbox` in place of an `Emission`. `difference` is dropped from
|
|
85
|
+
`@toa.io/generic` along with its last caller.
|
|
86
|
+
|
|
87
|
+
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# [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)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
### Bug Fixes
|
|
97
|
+
|
|
98
|
+
* **core:** apply the input default on a remote call ([341699b](https://github.com/toa-io/toa/commit/341699b26c9299963a898368671633a9b3fb97ed))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
### Performance Improvements
|
|
102
|
+
|
|
103
|
+
* **core:** do not snapshot a record an operation cannot commit ([a5e695b](https://github.com/toa-io/toa/commit/a5e695b0b1d4b00a91e40336deecd476c9646c89))
|
|
104
|
+
* **core:** keep the parsed criteria of a query ([7efc694](https://github.com/toa-io/toa/commit/7efc694814c2a071ae7e48dbf1c6014db6025a9f))
|
|
105
|
+
* stop rebuilding per-call values that never change ([4202504](https://github.com/toa-io/toa/commit/4202504c33fb9ef9694f9995f3d0397d3a186438))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# [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)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
### Bug Fixes
|
|
115
|
+
|
|
116
|
+
* **core:** say a connector is disconnected once it has closed, not before ([d67b48e](https://github.com/toa-io/toa/commit/d67b48eaafaacd5c635d8b066413f84b8e6a3771))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# [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)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
### Features
|
|
126
|
+
|
|
127
|
+
* **bridges.node:** add the dispose run command phase ([00df715](https://github.com/toa-io/toa/commit/00df7158f638b976ab09d4007102a4ead3c696c3))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# [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)
|
|
134
|
+
|
|
135
|
+
**Note:** Version bump only for package @toa.io/core
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# [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)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
### Features
|
|
145
|
+
|
|
146
|
+
* **core:** identify the origin of a call with `request.source` ([9da6b66](https://github.com/toa-io/toa/commit/9da6b66df852690351a1e82c7b8cc176fd89774a))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# [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)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
### Features
|
|
156
|
+
|
|
157
|
+
* **runtime:** split component RC into preflight and settle ([0d648b3](https://github.com/toa-io/toa/commit/0d648b314e4aac226c254bf01a86affa04b59b34))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# [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)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
### Bug Fixes
|
|
167
|
+
|
|
168
|
+
* **core:** lift tombstone on commit when transition leaves it untouched ([38c0d15](https://github.com/toa-io/toa/commit/38c0d15e946c3e2d559d4b18aecc33e0fda2471a))
|
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.282",
|
|
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",
|
|
@@ -20,14 +21,11 @@
|
|
|
20
21
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
|
-
"@rsql/parser": "1.
|
|
24
|
-
"@toa.io/
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
24
|
+
"@rsql/parser": "1.6.0",
|
|
25
|
+
"@toa.io/generic": "1.0.0-alpha.282",
|
|
26
|
+
"js-yaml": "5.4.1",
|
|
27
|
+
"openspan": "1.0.0-alpha.277",
|
|
28
|
+
"uuid": "14.0.2"
|
|
28
29
|
},
|
|
29
|
-
"
|
|
30
|
-
"clone-deep": "4.0.1"
|
|
31
|
-
},
|
|
32
|
-
"gitHead": "89c3b7d1314e357c9e33df8d95f8d46954cd3bcf"
|
|
30
|
+
"gitHead": "4007c814725dd015d3bbebb9a28e28a247645c4f"
|
|
33
31
|
}
|
package/src/assignment.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { Operation } from './operation.js'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class Assignment extends Operation {
|
|
3
|
+
export class Assignment extends Operation {
|
|
6
4
|
async acquire (store) {
|
|
7
5
|
store.scope = this.scope.changeset(store.request.query)
|
|
8
6
|
store.state = store.scope.get()
|
|
@@ -12,14 +10,15 @@ class Assignment extends Operation {
|
|
|
12
10
|
const {
|
|
13
11
|
scope,
|
|
14
12
|
state,
|
|
15
|
-
reply
|
|
13
|
+
reply,
|
|
14
|
+
request
|
|
16
15
|
} = store
|
|
17
16
|
|
|
18
17
|
if (reply.error !== undefined) return
|
|
19
18
|
|
|
20
19
|
scope.set(state)
|
|
21
20
|
|
|
22
|
-
const output = await this.scope.apply(scope)
|
|
21
|
+
const output = await this.scope.apply(scope, request.input)
|
|
23
22
|
|
|
24
23
|
// assignment returns new state by default
|
|
25
24
|
if (store.reply.output === undefined) {
|
|
@@ -27,5 +26,3 @@ class Assignment extends Operation {
|
|
|
27
26
|
}
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
|
-
|
|
31
|
-
exports.Assignment = Assignment
|
package/src/call.js
CHANGED
|
@@ -1,28 +1,41 @@
|
|
|
1
|
-
|
|
1
|
+
import { Readable } from 'node:stream'
|
|
2
|
+
import { current, encode } from 'openspan'
|
|
3
|
+
import { Connector } from './connector.js'
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
const { Connector } = require('./connector')
|
|
5
|
-
const { Err } = require('error-value')
|
|
6
|
-
|
|
7
|
-
class Call extends Connector {
|
|
5
|
+
export class Call extends Connector {
|
|
8
6
|
#transmitter
|
|
9
7
|
#contract
|
|
8
|
+
#source
|
|
10
9
|
|
|
11
|
-
constructor (transmitter, contract) {
|
|
10
|
+
constructor (transmitter, contract, source) {
|
|
12
11
|
super()
|
|
13
12
|
|
|
14
13
|
this.#transmitter = transmitter
|
|
15
14
|
this.#contract = contract
|
|
15
|
+
this.#source = source
|
|
16
16
|
|
|
17
17
|
this.depends(transmitter)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
async invoke (request = {}) {
|
|
21
|
+
// the caller may have attributed the call itself, as the node bridge does
|
|
22
|
+
if (this.#source !== undefined)
|
|
23
|
+
request.source ??= this.#source
|
|
24
|
+
|
|
25
|
+
// fitting first lets the input schema supply its default;
|
|
26
|
+
// an operation that takes no input still has to send an explicit null
|
|
21
27
|
this.#contract.fit(request)
|
|
22
28
|
|
|
29
|
+
request.input ??= null
|
|
30
|
+
|
|
23
31
|
// avoid validation on the recipient's side
|
|
24
32
|
request.authentic = true
|
|
25
33
|
|
|
34
|
+
const context = current()
|
|
35
|
+
|
|
36
|
+
if (context !== undefined)
|
|
37
|
+
request.telemetry = encode(context)
|
|
38
|
+
|
|
26
39
|
const reply = await this.#transmitter.request(request)
|
|
27
40
|
|
|
28
41
|
if (reply === null) return null
|
|
@@ -32,11 +45,24 @@ class Call extends Connector {
|
|
|
32
45
|
throw reply.exception
|
|
33
46
|
|
|
34
47
|
if (reply.error !== undefined)
|
|
35
|
-
return
|
|
48
|
+
return new RemoteError(reply.error)
|
|
36
49
|
else
|
|
37
50
|
return reply.output
|
|
38
51
|
}
|
|
39
52
|
}
|
|
53
|
+
|
|
54
|
+
explain () {
|
|
55
|
+
return this.#contract.discovery
|
|
56
|
+
}
|
|
40
57
|
}
|
|
41
58
|
|
|
42
|
-
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
// the remote error as a value: every property it carries, and nothing else enumerable
|
|
62
|
+
class RemoteError extends Error {
|
|
63
|
+
constructor (error) {
|
|
64
|
+
super()
|
|
65
|
+
|
|
66
|
+
Object.assign(this, error)
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/cascade.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import { Connector } from './connector.js'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Cascade extends Connector {
|
|
7
|
-
#bridges
|
|
3
|
+
export class Cascade extends Connector {
|
|
4
|
+
// #bridges
|
|
8
5
|
#last
|
|
9
6
|
|
|
10
|
-
constructor (bridges) {
|
|
7
|
+
constructor (bridges, preflight) {
|
|
11
8
|
super()
|
|
12
9
|
|
|
13
|
-
this.#bridges = bridges
|
|
10
|
+
// this.#bridges = bridges
|
|
14
11
|
this.#last = bridges[bridges.length - 1]
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
if (preflight === undefined)
|
|
14
|
+
this.depends(bridges)
|
|
15
|
+
else
|
|
16
|
+
this.depends(bridges).depends(preflight)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async run (...args) {
|
|
@@ -32,5 +32,3 @@ class Cascade extends Connector {
|
|
|
32
32
|
return this.#last.execute(...args)
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
exports.Cascade = Cascade
|
package/src/component.js
CHANGED
|
@@ -1,41 +1,81 @@
|
|
|
1
|
-
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { console, current, decode, run } from 'openspan'
|
|
3
|
+
import { Connector } from './connector.js'
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
const { Connector } = require('./connector')
|
|
5
|
-
const { NotImplementedException } = require('./exceptions')
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @implements {toa.core.Component}
|
|
9
|
-
*/
|
|
10
|
-
class Component extends Connector {
|
|
5
|
+
export class Component extends Connector {
|
|
11
6
|
locator
|
|
12
7
|
|
|
13
|
-
|
|
8
|
+
/** @protected */
|
|
9
|
+
operations
|
|
10
|
+
|
|
11
|
+
/** @protected */
|
|
12
|
+
kind = 'server'
|
|
13
|
+
|
|
14
|
+
/** @type {Record<string, object>} span options per endpoint */
|
|
15
|
+
#spans = {}
|
|
14
16
|
|
|
15
17
|
constructor (locator, operations) {
|
|
16
18
|
super()
|
|
17
19
|
|
|
18
20
|
this.locator = locator
|
|
19
|
-
this
|
|
21
|
+
this.operations = operations
|
|
20
22
|
|
|
21
23
|
Object.values(operations).forEach((operation) => this.depends(operation))
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
async
|
|
25
|
-
|
|
26
|
+
async invoke (endpoint, request) {
|
|
27
|
+
if (!(endpoint in this.operations))
|
|
28
|
+
// `assert.fail`, not `assert.ok`: the message is built only when it is needed
|
|
29
|
+
assert.fail(`Endpoint '${endpoint}' is not provided by '${this.locator.id}'`)
|
|
30
|
+
|
|
31
|
+
// if the request carries no telemetry, the trace starts here
|
|
32
|
+
const remote = request?.telemetry === undefined ? null : decode(request.telemetry)
|
|
33
|
+
const task = () => this.process(endpoint, request)
|
|
34
|
+
|
|
35
|
+
if (remote === null)
|
|
36
|
+
return task()
|
|
37
|
+
else
|
|
38
|
+
return run(remote, task)
|
|
26
39
|
}
|
|
27
40
|
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
/** @private */
|
|
42
|
+
async process (endpoint, request) {
|
|
43
|
+
return console.span(this.span(endpoint), async () => {
|
|
44
|
+
const reply = await this.operations[endpoint].invoke(request)
|
|
45
|
+
|
|
46
|
+
if (reply?.exception !== undefined) {
|
|
47
|
+
current().status = 'error'
|
|
48
|
+
|
|
49
|
+
console.error('Failed to execute operation', {
|
|
50
|
+
endpoint: `${this.locator.id}.${endpoint}`,
|
|
51
|
+
exception: reply.exception
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return reply
|
|
56
|
+
})
|
|
30
57
|
}
|
|
31
58
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The span of an endpoint never changes, so it is built once. Not in the constructor:
|
|
61
|
+
* `kind` is a field of the subclass, and those are assigned after this one is built.
|
|
62
|
+
*
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
span (endpoint) {
|
|
66
|
+
let options = this.#spans[endpoint]
|
|
67
|
+
|
|
68
|
+
if (options === undefined) {
|
|
69
|
+
options = { name: `${this.locator.id}.${endpoint}`, kind: this.kind }
|
|
70
|
+
|
|
71
|
+
// the server span is emitted by the component itself, while the client span
|
|
72
|
+
// belongs to the calling service and inherits it from the context
|
|
73
|
+
if (this.kind === 'server')
|
|
74
|
+
options.service = this.locator.id
|
|
75
|
+
|
|
76
|
+
this.#spans[endpoint] = options
|
|
35
77
|
}
|
|
36
78
|
|
|
37
|
-
return
|
|
79
|
+
return options
|
|
38
80
|
}
|
|
39
81
|
}
|
|
40
|
-
|
|
41
|
-
exports.Component = Component
|
package/src/composition.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { console } from 'openspan'
|
|
2
|
+
import { Connector } from './connector.js'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
const { Connector } = require('./connector')
|
|
5
|
-
|
|
6
|
-
class Composition extends Connector {
|
|
4
|
+
export class Composition extends Connector {
|
|
7
5
|
constructor (expositions, producers, receivers, tenants) {
|
|
8
6
|
super()
|
|
9
7
|
|
|
@@ -21,5 +19,3 @@ class Composition extends Connector {
|
|
|
21
19
|
console.info('Composition shutdown complete')
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
|
-
|
|
25
|
-
exports.Composition = Composition
|
package/src/connector.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { console } = require('@toa.io/console')
|
|
4
|
-
const { newid } = require('@toa.io/generic')
|
|
1
|
+
import { console } from 'openspan'
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
4
|
* Abstract connections hierarchy
|
|
8
5
|
* @implements {toa.core.Connector}
|
|
9
6
|
*/
|
|
10
|
-
class Connector {
|
|
7
|
+
export class Connector {
|
|
11
8
|
/** @type {Array<Connector>} */
|
|
12
9
|
#dependencies = []
|
|
13
10
|
|
|
@@ -26,7 +23,7 @@ class Connector {
|
|
|
26
23
|
connected = false
|
|
27
24
|
|
|
28
25
|
constructor () {
|
|
29
|
-
this.id = this.constructor.name + '#' +
|
|
26
|
+
this.id = this.constructor.name + '#' + Math.random().toString(36).substring(2, 8)
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
/**
|
|
@@ -86,12 +83,18 @@ class Connector {
|
|
|
86
83
|
|
|
87
84
|
this.#disconnecting = undefined
|
|
88
85
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.#connecting = (async () => {
|
|
86
|
+
const work = async () => {
|
|
92
87
|
await Promise.all(this.#dependencies.map((connector) => connector.connect()))
|
|
93
88
|
await this.open()
|
|
94
|
-
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// anonymous grouping nodes are not worth a span
|
|
92
|
+
this.#connecting = TRACE_BOOT && this.constructor.name !== 'Connector'
|
|
93
|
+
? console.span({
|
|
94
|
+
name: `connect ${this.constructor.name}`,
|
|
95
|
+
attributes: { id: this.locator?.id ?? this.id }
|
|
96
|
+
}, work)
|
|
97
|
+
: work()
|
|
95
98
|
|
|
96
99
|
try {
|
|
97
100
|
await this.#connecting
|
|
@@ -100,8 +103,6 @@ class Connector {
|
|
|
100
103
|
await this.disconnect(true)
|
|
101
104
|
throw e
|
|
102
105
|
}
|
|
103
|
-
|
|
104
|
-
console.debug(`Connector '${this.id}' connected`)
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
/**
|
|
@@ -115,7 +116,11 @@ class Connector {
|
|
|
115
116
|
* @returns {Promise<void>}
|
|
116
117
|
*/
|
|
117
118
|
async disconnect (interrupt) {
|
|
118
|
-
|
|
119
|
+
// a connector that has not finished connecting has nothing to close, and
|
|
120
|
+
// awaiting a connection that may never settle outlives any grace period
|
|
121
|
+
const pending = interrupt === true || this.connected === false
|
|
122
|
+
|
|
123
|
+
if (!pending) await this.#connecting
|
|
119
124
|
|
|
120
125
|
if (this.#disconnecting) return this.#disconnecting
|
|
121
126
|
|
|
@@ -123,19 +128,24 @@ class Connector {
|
|
|
123
128
|
|
|
124
129
|
if (linked && interrupt !== true) return
|
|
125
130
|
|
|
126
|
-
this.connected = false
|
|
127
|
-
this.#connecting = undefined
|
|
128
|
-
|
|
129
131
|
this.#disconnecting = (async () => {
|
|
130
132
|
const start = +new Date()
|
|
131
133
|
|
|
132
134
|
const interval = setInterval(() => {
|
|
133
135
|
const delay = +new Date() - start
|
|
134
136
|
|
|
135
|
-
if (delay > DELAY)
|
|
137
|
+
if (delay > DELAY)
|
|
138
|
+
console.warn(`Connector ${this.id} still disconnecting (${delay})`)
|
|
136
139
|
}, DELAY)
|
|
137
140
|
|
|
138
|
-
if (
|
|
141
|
+
if (!pending) await this.close()
|
|
142
|
+
|
|
143
|
+
// said once the closing is done rather than when it starts: a dependant that is
|
|
144
|
+
// still closing is still using what it depends on, and the check above reads this
|
|
145
|
+
// of every dependant. Saying it early lets the first to start tear down a shared
|
|
146
|
+
// dependency under the others.
|
|
147
|
+
this.connected = false
|
|
148
|
+
this.#connecting = undefined
|
|
139
149
|
|
|
140
150
|
clearInterval(interval)
|
|
141
151
|
|
|
@@ -145,8 +155,6 @@ class Connector {
|
|
|
145
155
|
})()
|
|
146
156
|
|
|
147
157
|
await this.#disconnecting
|
|
148
|
-
|
|
149
|
-
console.debug(`Connector '${this.id}' disconnected`)
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
async reconnect () {
|
|
@@ -179,5 +187,4 @@ class Connector {
|
|
|
179
187
|
}
|
|
180
188
|
|
|
181
189
|
const DELAY = 5000
|
|
182
|
-
|
|
183
|
-
exports.Connector = Connector
|
|
190
|
+
const TRACE_BOOT = process.env.TOA_BOOT_TRACE === '1'
|
package/src/context.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { Connector } = require('./connector')
|
|
1
|
+
import { Connector } from './connector.js'
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* @implements {toa.core.Context}
|
|
7
5
|
*/
|
|
8
|
-
class Context extends Connector {
|
|
6
|
+
export class Context extends Connector {
|
|
7
|
+
env
|
|
8
|
+
name
|
|
9
9
|
aspects
|
|
10
|
+
locator
|
|
10
11
|
|
|
11
12
|
#local
|
|
12
13
|
#discover
|
|
@@ -15,7 +16,10 @@ class Context extends Connector {
|
|
|
15
16
|
constructor (local, discover, aspects = []) {
|
|
16
17
|
super()
|
|
17
18
|
|
|
19
|
+
this.env = process.env.TOA_ENV
|
|
20
|
+
this.name = process.env.TOA_CONTEXT
|
|
18
21
|
this.aspects = aspects
|
|
22
|
+
this.locator = local?.locator
|
|
19
23
|
|
|
20
24
|
this.#local = local
|
|
21
25
|
this.#discover = discover
|
|
@@ -51,5 +55,3 @@ class Context extends Connector {
|
|
|
51
55
|
return remote
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
|
-
|
|
55
|
-
exports.Context = Context
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SystemException } from '../exceptions.js'
|
|
2
|
+
|
|
3
|
+
export class Contract {
|
|
4
|
+
schema
|
|
5
|
+
|
|
6
|
+
constructor (schema) {
|
|
7
|
+
this.schema = schema
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
fit (value) {
|
|
11
|
+
const error = this.schema.fit(value)
|
|
12
|
+
|
|
13
|
+
if (error !== null)
|
|
14
|
+
throw new this.constructor.Exception(error, value)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static Exception = SystemException
|
|
18
|
+
}
|
package/src/contract/index.js
CHANGED