@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.
Files changed (98) hide show
  1. package/CHANGELOG.md +168 -0
  2. package/package.json +8 -10
  3. package/src/assignment.js +5 -8
  4. package/src/call.js +35 -9
  5. package/src/cascade.js +9 -11
  6. package/src/component.js +61 -21
  7. package/src/composition.js +3 -7
  8. package/src/connector.js +29 -22
  9. package/src/context.js +8 -6
  10. package/src/contract/contract.js +18 -0
  11. package/src/contract/index.js +2 -7
  12. package/src/contract/reply.js +36 -15
  13. package/src/contract/request.js +20 -10
  14. package/src/contract/schemas/index.js +9 -5
  15. package/src/contract/schemas/query.yaml +14 -1
  16. package/src/contract/schemas/source.yaml +23 -0
  17. package/src/discovery.js +15 -13
  18. package/src/effect.js +15 -0
  19. package/src/emission.js +2 -6
  20. package/src/entities/changeset.js +7 -14
  21. package/src/entities/entity.js +75 -29
  22. package/src/entities/factory.js +21 -14
  23. package/src/entities/index.js +1 -5
  24. package/src/entities/newid.js +9 -0
  25. package/src/entities/set.js +13 -4
  26. package/src/event.js +21 -7
  27. package/src/exceptions.js +53 -39
  28. package/src/exposition.js +5 -8
  29. package/src/guard.js +13 -0
  30. package/src/index.js +28 -47
  31. package/src/locator.js +2 -6
  32. package/src/observation.js +3 -15
  33. package/src/operation.js +40 -14
  34. package/src/outbox/index.js +1 -0
  35. package/src/outbox/outbox.js +296 -0
  36. package/src/query/criteria.js +3 -7
  37. package/src/query/options.js +5 -8
  38. package/src/query.js +36 -7
  39. package/src/receiver.js +76 -18
  40. package/src/reflection.js +2 -6
  41. package/src/remote.js +9 -12
  42. package/src/state.js +89 -50
  43. package/src/transition.js +13 -25
  44. package/src/transmission.js +15 -10
  45. package/src/unmanaged.js +7 -0
  46. package/test/call.fixtures.js +8 -12
  47. package/test/call.test.js +21 -9
  48. package/test/component.fixtures.js +5 -8
  49. package/test/component.test.js +22 -10
  50. package/test/connector.fixtures.js +10 -8
  51. package/test/connector.test.js +59 -38
  52. package/test/context.fixtures.js +8 -12
  53. package/test/context.test.js +18 -7
  54. package/test/contract/conditions.test.js +11 -9
  55. package/test/contract/contract.fixtures.js +12 -16
  56. package/test/contract/request.test.js +84 -34
  57. package/test/discovery.test.js +64 -0
  58. package/test/emission.fixtures.js +6 -10
  59. package/test/emission.test.js +29 -12
  60. package/test/entities/entity.fixtures.js +7 -11
  61. package/test/entities/entity.test.js +70 -49
  62. package/test/entities/factory.fixtures.js +22 -10
  63. package/test/entities/factory.test.js +29 -15
  64. package/test/entities/set.fixtures.js +6 -8
  65. package/test/entities/set.test.js +6 -5
  66. package/test/event.fixtures.js +9 -14
  67. package/test/event.test.js +57 -31
  68. package/test/locator.test.js +25 -24
  69. package/test/outbox.test.js +121 -0
  70. package/test/query.fixtures.js +1 -5
  71. package/test/query.test.js +42 -14
  72. package/test/receiver.fixtures.js +9 -12
  73. package/test/receiver.test.js +45 -22
  74. package/test/reflection.test.js +7 -6
  75. package/test/state.fixtures.js +25 -30
  76. package/test/state.test.js +72 -19
  77. package/test/transmission.fixtures.js +4 -6
  78. package/test/transmission.test.js +33 -15
  79. package/types/atomicity.d.ts +58 -0
  80. package/types/bindings.d.ts +8 -6
  81. package/types/bridges.ts +5 -2
  82. package/types/component.d.ts +7 -4
  83. package/types/context.d.ts +6 -3
  84. package/types/entity.d.ts +4 -4
  85. package/types/event.d.ts +1 -1
  86. package/types/extensions.d.ts +35 -10
  87. package/types/index.ts +16 -13
  88. package/types/message.d.ts +1 -0
  89. package/types/operations.d.ts +6 -0
  90. package/types/outbox.d.ts +53 -0
  91. package/types/query.d.ts +2 -0
  92. package/types/receiver.d.ts +2 -2
  93. package/types/reflection.d.ts +1 -1
  94. package/types/remote.d.ts +18 -0
  95. package/types/request.d.ts +16 -1
  96. package/types/state.d.ts +11 -8
  97. package/types/storages.d.ts +36 -11
  98. 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.28",
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.2.4",
24
- "@toa.io/console": "1.0.0-alpha.28",
25
- "@toa.io/generic": "1.0.0-alpha.28",
26
- "@toa.io/yaml": "1.0.0-alpha.28",
27
- "error-value": "0.3.0"
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
- "devDependencies": {
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
- 'use strict'
1
+ import { Operation } from './operation.js'
2
2
 
3
- const { Operation } = require('./operation')
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
- 'use strict'
1
+ import { Readable } from 'node:stream'
2
+ import { current, encode } from 'openspan'
3
+ import { Connector } from './connector.js'
2
4
 
3
- const { Readable } = require('node:stream')
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 Err(reply.error.code, reply.error)
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
- exports.Call = Call
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
- 'use strict'
1
+ import { Connector } from './connector.js'
2
2
 
3
- const { merge } = require('@toa.io/generic')
4
- const { Connector } = require('./connector')
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
- this.depends(bridges)
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
- 'use strict'
1
+ import assert from 'node:assert'
2
+ import { console, current, decode, run } from 'openspan'
3
+ import { Connector } from './connector.js'
2
4
 
3
- const { console } = require('@toa.io/console')
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
- #operations
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.#operations = operations
21
+ this.operations = operations
20
22
 
21
23
  Object.values(operations).forEach((operation) => this.depends(operation))
22
24
  }
23
25
 
24
- async open () {
25
- console.info(`Runtime '${this.locator.id}' connected`)
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
- async dispose () {
29
- console.info(`Runtime '${this.locator.id}' disconnected`)
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
- async invoke (endpoint, request) {
33
- if (!(endpoint in this.#operations)) {
34
- throw new NotImplementedException(`Endpoint '${endpoint}' not found in '${this.locator.id}'`)
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 this.#operations[endpoint].invoke(request)
79
+ return options
38
80
  }
39
81
  }
40
-
41
- exports.Component = Component
@@ -1,9 +1,7 @@
1
- 'use strict'
1
+ import { console } from 'openspan'
2
+ import { Connector } from './connector.js'
2
3
 
3
- const { console } = require('@toa.io/console')
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
- 'use strict'
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 + '#' + newid().substring(0, 8)
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
- console.debug(`Connecting '${this.id}' with ${this.#dependencies.length} dependencies...`)
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
- if (interrupt !== true) await this.#connecting
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) console.warn(`Connector ${this.id} still disconnecting (${delay})`)
137
+ if (delay > DELAY)
138
+ console.warn(`Connector ${this.id} still disconnecting (${delay})`)
136
139
  }, DELAY)
137
140
 
138
- if (interrupt !== true) await this.close()
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
- 'use strict'
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
+ }
@@ -1,7 +1,2 @@
1
- 'use strict'
2
-
3
- const { Request } = require('./request')
4
- const { Reply } = require('./reply')
5
-
6
- exports.Request = Request
7
- exports.Reply = Reply
1
+ export { Request } from './request.js'
2
+ export { Reply } from './reply.js'