@toa.io/core 1.0.0-alpha.291 → 1.0.0-alpha.293

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 (90) hide show
  1. package/package.json +11 -5
  2. package/CHANGELOG.md +0 -35
  3. package/source/assignment.ts +0 -27
  4. package/source/call.ts +0 -63
  5. package/source/cascade.ts +0 -32
  6. package/source/component.ts +0 -83
  7. package/source/composition.ts +0 -27
  8. package/source/connector.ts +0 -217
  9. package/source/context.ts +0 -68
  10. package/source/contract/contract.ts +0 -23
  11. package/source/contract/index.ts +0 -2
  12. package/source/contract/reply.ts +0 -56
  13. package/source/contract/request.ts +0 -140
  14. package/source/contract/schemas.ts +0 -39
  15. package/source/discovery.ts +0 -53
  16. package/source/effect.ts +0 -14
  17. package/source/emission.ts +0 -21
  18. package/source/entities/changeset.ts +0 -37
  19. package/source/entities/entity.ts +0 -137
  20. package/source/entities/factory.ts +0 -52
  21. package/source/entities/index.ts +0 -4
  22. package/source/entities/newid.ts +0 -9
  23. package/source/entities/set.ts +0 -27
  24. package/source/event.ts +0 -57
  25. package/source/exceptions.ts +0 -133
  26. package/source/exposition.ts +0 -33
  27. package/source/guard.ts +0 -16
  28. package/source/index.ts +0 -36
  29. package/source/locator.ts +0 -37
  30. package/source/observation.ts +0 -16
  31. package/source/operation.ts +0 -139
  32. package/source/outbox/index.ts +0 -1
  33. package/source/outbox/outbox.ts +0 -341
  34. package/source/query/criteria.ts +0 -90
  35. package/source/query/options.ts +0 -35
  36. package/source/query.ts +0 -67
  37. package/source/receiver.ts +0 -120
  38. package/source/reflection.ts +0 -20
  39. package/source/remote.ts +0 -20
  40. package/source/state.ts +0 -179
  41. package/source/transition.ts +0 -79
  42. package/source/transmission.ts +0 -38
  43. package/source/types/atomicity.ts +0 -63
  44. package/source/types/bindings.ts +0 -59
  45. package/source/types/bridges.ts +0 -54
  46. package/source/types/extensions.ts +0 -89
  47. package/source/types/index.ts +0 -14
  48. package/source/types/message.ts +0 -5
  49. package/source/types/operations.ts +0 -31
  50. package/source/types/outbox.ts +0 -35
  51. package/source/types/receiver.ts +0 -10
  52. package/source/types/request.ts +0 -54
  53. package/source/types/state.ts +0 -9
  54. package/source/types/storages.ts +0 -134
  55. package/source/unmanaged.ts +0 -8
  56. package/test/call.fixtures.js +0 -25
  57. package/test/call.test.js +0 -79
  58. package/test/component.fixtures.js +0 -18
  59. package/test/component.test.js +0 -57
  60. package/test/connector.fixtures.js +0 -56
  61. package/test/connector.test.js +0 -266
  62. package/test/context.fixtures.js +0 -14
  63. package/test/context.test.js +0 -40
  64. package/test/contract/conditions.test.js +0 -32
  65. package/test/contract/contract.fixtures.js +0 -28
  66. package/test/contract/request.test.js +0 -196
  67. package/test/discovery.test.js +0 -71
  68. package/test/emission.fixtures.js +0 -13
  69. package/test/emission.test.js +0 -60
  70. package/test/entities/entity.fixtures.js +0 -24
  71. package/test/entities/entity.test.js +0 -137
  72. package/test/entities/factory.fixtures.js +0 -31
  73. package/test/entities/factory.test.js +0 -99
  74. package/test/entities/set.fixtures.js +0 -9
  75. package/test/entities/set.test.js +0 -13
  76. package/test/event.fixtures.js +0 -24
  77. package/test/event.test.js +0 -162
  78. package/test/locator.test.js +0 -120
  79. package/test/outbox.test.js +0 -202
  80. package/test/query.fixtures.js +0 -96
  81. package/test/query.test.js +0 -183
  82. package/test/receiver.fixtures.js +0 -20
  83. package/test/receiver.test.js +0 -144
  84. package/test/reflection.test.js +0 -31
  85. package/test/state.fixtures.js +0 -49
  86. package/test/state.test.js +0 -162
  87. package/test/transmission.fixtures.js +0 -13
  88. package/test/transmission.test.js +0 -77
  89. package/tsconfig.json +0 -10
  90. package/tsconfig.tsbuildinfo +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/core",
3
- "version": "1.0.0-alpha.291",
3
+ "version": "1.0.0-alpha.293",
4
4
  "type": "module",
5
5
  "description": "Toa Core",
6
6
  "author": "temich <tema.gurtovoy@gmail.com>",
@@ -28,16 +28,22 @@
28
28
  "publishConfig": {
29
29
  "access": "public"
30
30
  },
31
+ "files": [
32
+ "transpiled",
33
+ "!**/tsconfig.tsbuildinfo",
34
+ "!**/*.test.*",
35
+ "!**/.env*"
36
+ ],
31
37
  "scripts": {
32
38
  "transpile": "tsc",
33
39
  "test": "echo \"Error: run tests from root\" && exit 1"
34
40
  },
35
41
  "dependencies": {
36
42
  "@rsql/parser": "1.6.0",
37
- "@toa.io/generic": "1.0.0-alpha.291",
38
- "@toa.io/schemas": "1.0.0-alpha.291",
39
- "openspan": "1.0.0-alpha.286",
43
+ "@toa.io/generic": "1.0.0-alpha.292",
44
+ "@toa.io/schemas": "1.0.0-alpha.293",
45
+ "openspan": "1.0.0-alpha.293",
40
46
  "uuid": "14.0.2"
41
47
  },
42
- "gitHead": "bcd8d8d07bd3e64fc826af8719472404662cc37f"
48
+ "gitHead": "1e05b3ab64758324c58c425f1f8e52e9f35f1da8"
43
49
  }
package/CHANGELOG.md DELETED
@@ -1,35 +0,0 @@
1
- # Change Log
2
-
3
- All notable changes to this project will be documented in this file.
4
- See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
-
6
- # [1.0.0-alpha.291](https://github.com/toa-io/toa/compare/v1.0.0-alpha.290...v1.0.0-alpha.291) (2026-09-07)
7
-
8
- **Note:** Version bump only for package @toa.io/core
9
-
10
-
11
-
12
-
13
-
14
- # [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)
15
-
16
- * 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)
17
-
18
- ### BREAKING CHANGES
19
-
20
- * a component that read `process.env.TOA_*` reads `context` instead;
21
- `echo(input)` no longer substitutes from the environment; a bash operation sees no
22
- `TOA_*`; images no longer set `USER node` — see migrations/289.md.
23
-
24
-
25
- # [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)
26
-
27
- **Note:** Version bump only for package @toa.io/core
28
-
29
-
30
-
31
-
32
-
33
- # [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)
34
-
35
- **Note:** Version bump only for package @toa.io/core
@@ -1,27 +0,0 @@
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 DELETED
@@ -1,63 +0,0 @@
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
- }
package/source/cascade.ts DELETED
@@ -1,32 +0,0 @@
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
- }
@@ -1,83 +0,0 @@
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
- }
@@ -1,27 +0,0 @@
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
- }
@@ -1,217 +0,0 @@
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'
package/source/context.ts DELETED
@@ -1,68 +0,0 @@
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
- }
@@ -1,23 +0,0 @@
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
- }
@@ -1,2 +0,0 @@
1
- export { Request } from './request.js'
2
- export { Reply } from './reply.js'
@@ -1,56 +0,0 @@
1
- import { Contract } from './contract.js'
2
- import { ResponseContractException } from '../exceptions.js'
3
- import type { Refusal } from './contract.js'
4
- import type { JSONSchema } from './schemas.js'
5
-
6
- export class Reply extends Contract {
7
- public static override Exception: Refusal =
8
- ResponseContractException as unknown as Refusal
9
-
10
- public static schema(output?: JSONSchema, errors?: Array<string | number>): JSONSchema {
11
- const schema: JSONSchema = {
12
- type: 'object',
13
- properties: {},
14
- additionalProperties: false
15
- }
16
-
17
- if (output !== undefined) {
18
- /*
19
- * A reply carries more than the operation declares — `VERSION` and the rest of the
20
- * record's own fields — so what it is validated against is the declaration relaxed.
21
- * On a copy: the declaration itself is what a component publishes when asked to
22
- * explain, and relaxing that would publish a contract nobody wrote.
23
- */
24
- output = structuredClone(output)
25
-
26
- if (output.type === 'object') output.additionalProperties = true
27
- else if (output.type === 'array' && output.items?.type === 'object')
28
- output.items.additionalProperties = true
29
-
30
- schema.properties.output = output
31
- }
32
-
33
- /*
34
- * An error a caller is meant to handle is one the operation states. Where none are
35
- * stated, an error is not a reply this operation makes — it is a mistake, and the
36
- * contract says so rather than passing an undeclared code on to whoever called.
37
- */
38
- schema.properties.error =
39
- errors === undefined
40
- ? false
41
- : {
42
- type: 'object',
43
- properties: {
44
- code: {
45
- enum: errors
46
- },
47
- message: {
48
- type: 'string'
49
- }
50
- },
51
- required: ['code']
52
- }
53
-
54
- return schema
55
- }
56
- }