@toa.io/core 1.0.0-alpha.283 → 1.0.0-alpha.286

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 (290) hide show
  1. package/package.json +19 -7
  2. package/source/assignment.ts +27 -0
  3. package/{src/call.js → source/call.ts} +16 -21
  4. package/{src/cascade.js → source/cascade.ts} +6 -8
  5. package/{src/component.js → source/component.ts} +27 -25
  6. package/{src/composition.js → source/composition.ts} +9 -3
  7. package/{src/connector.js → source/connector.ts} +36 -55
  8. package/source/context.ts +67 -0
  9. package/source/contract/contract.ts +23 -0
  10. package/{src/contract/reply.js → source/contract/reply.ts} +26 -19
  11. package/source/contract/request.ts +85 -0
  12. package/source/contract/schemas.ts +39 -0
  13. package/{src/discovery.js → source/discovery.ts} +15 -8
  14. package/source/effect.ts +14 -0
  15. package/{src/emission.js → source/emission.ts} +5 -3
  16. package/source/entities/changeset.ts +37 -0
  17. package/source/entities/entity.ts +137 -0
  18. package/source/entities/factory.ts +52 -0
  19. package/source/entities/index.ts +4 -0
  20. package/{src/entities/newid.js → source/entities/newid.ts} +1 -1
  21. package/{src/entities/set.js → source/entities/set.ts} +8 -5
  22. package/source/event.ts +57 -0
  23. package/{src/exceptions.js → source/exceptions.ts} +52 -33
  24. package/source/exposition.ts +33 -0
  25. package/source/guard.ts +16 -0
  26. package/{src/index.js → source/index.ts} +8 -0
  27. package/{src/locator.js → source/locator.ts} +17 -30
  28. package/source/observation.ts +16 -0
  29. package/source/operation.ts +138 -0
  30. package/{src/outbox/outbox.js → source/outbox/outbox.ts} +95 -51
  31. package/source/query/criteria.ts +90 -0
  32. package/source/query/options.ts +35 -0
  33. package/{src/query.js → source/query.ts} +14 -17
  34. package/source/receiver.ts +120 -0
  35. package/source/reflection.ts +20 -0
  36. package/source/remote.ts +20 -0
  37. package/source/state.ts +179 -0
  38. package/source/transition.ts +79 -0
  39. package/{src/transmission.js → source/transmission.ts} +8 -8
  40. package/source/types/atomicity.ts +63 -0
  41. package/source/types/bindings.ts +59 -0
  42. package/source/types/bridges.ts +54 -0
  43. package/source/types/extensions.ts +89 -0
  44. package/{types → source/types}/index.ts +9 -16
  45. package/{types/message.d.ts → source/types/message.ts} +2 -1
  46. package/source/types/operations.ts +31 -0
  47. package/source/types/outbox.ts +35 -0
  48. package/source/types/receiver.ts +10 -0
  49. package/{types/request.d.ts → source/types/request.ts} +8 -9
  50. package/source/types/state.ts +9 -0
  51. package/source/types/storages.ts +129 -0
  52. package/source/unmanaged.ts +8 -0
  53. package/test/call.fixtures.js +3 -1
  54. package/test/call.test.js +28 -7
  55. package/test/component.test.js +10 -5
  56. package/test/connector.fixtures.js +7 -7
  57. package/test/connector.test.js +15 -2
  58. package/test/context.fixtures.js +1 -1
  59. package/test/context.test.js +4 -4
  60. package/test/contract/conditions.test.js +6 -2
  61. package/test/contract/contract.fixtures.js +3 -4
  62. package/test/contract/request.test.js +68 -34
  63. package/test/discovery.test.js +10 -3
  64. package/test/emission.test.js +16 -8
  65. package/test/entities/entity.fixtures.js +5 -6
  66. package/test/entities/entity.test.js +54 -20
  67. package/test/entities/factory.fixtures.js +11 -10
  68. package/test/entities/factory.test.js +47 -10
  69. package/test/entities/set.test.js +1 -1
  70. package/test/event.test.js +29 -11
  71. package/test/locator.test.js +10 -4
  72. package/test/outbox.test.js +95 -14
  73. package/test/query.test.js +76 -7
  74. package/test/receiver.fixtures.js +2 -2
  75. package/test/receiver.test.js +53 -27
  76. package/test/reflection.test.js +3 -3
  77. package/test/state.fixtures.js +10 -3
  78. package/test/state.test.js +77 -14
  79. package/test/transmission.test.js +25 -12
  80. package/transpiled/assignment.d.ts +6 -0
  81. package/transpiled/assignment.js +20 -0
  82. package/transpiled/assignment.js.map +1 -0
  83. package/transpiled/call.d.ts +10 -0
  84. package/transpiled/call.js +52 -0
  85. package/transpiled/call.js.map +1 -0
  86. package/transpiled/cascade.d.ts +7 -0
  87. package/transpiled/cascade.js +28 -0
  88. package/transpiled/cascade.js.map +1 -0
  89. package/transpiled/component.d.ts +15 -0
  90. package/transpiled/component.js +61 -0
  91. package/transpiled/component.js.map +1 -0
  92. package/transpiled/composition.d.ts +6 -0
  93. package/transpiled/composition.js +23 -0
  94. package/transpiled/composition.js.map +1 -0
  95. package/transpiled/connector.d.ts +47 -0
  96. package/transpiled/connector.js +143 -0
  97. package/transpiled/connector.js.map +1 -0
  98. package/transpiled/context.d.ts +18 -0
  99. package/transpiled/context.js +41 -0
  100. package/transpiled/context.js.map +1 -0
  101. package/transpiled/contract/contract.d.ts +10 -0
  102. package/transpiled/contract/contract.js +14 -0
  103. package/transpiled/contract/contract.js.map +1 -0
  104. package/transpiled/contract/index.d.ts +2 -0
  105. package/transpiled/contract/index.js +3 -0
  106. package/transpiled/contract/index.js.map +1 -0
  107. package/transpiled/contract/reply.d.ts +7 -0
  108. package/transpiled/contract/reply.js +48 -0
  109. package/transpiled/contract/reply.js.map +1 -0
  110. package/transpiled/contract/request.d.ts +24 -0
  111. package/transpiled/contract/request.js +59 -0
  112. package/transpiled/contract/request.js.map +1 -0
  113. package/transpiled/contract/schemas.d.ts +4 -0
  114. package/transpiled/contract/schemas.js +36 -0
  115. package/transpiled/contract/schemas.js.map +1 -0
  116. package/transpiled/discovery.d.ts +12 -0
  117. package/transpiled/discovery.js +34 -0
  118. package/transpiled/discovery.js.map +1 -0
  119. package/transpiled/effect.d.ts +5 -0
  120. package/transpiled/effect.js +11 -0
  121. package/transpiled/effect.js.map +1 -0
  122. package/transpiled/emission.d.ts +8 -0
  123. package/transpiled/emission.js +14 -0
  124. package/transpiled/emission.js.map +1 -0
  125. package/transpiled/entities/changeset.d.ts +10 -0
  126. package/transpiled/entities/changeset.js +27 -0
  127. package/transpiled/entities/changeset.js.map +1 -0
  128. package/transpiled/entities/entity.d.ts +19 -0
  129. package/transpiled/entities/entity.js +109 -0
  130. package/transpiled/entities/entity.js.map +1 -0
  131. package/transpiled/entities/factory.d.ts +22 -0
  132. package/transpiled/entities/factory.js +34 -0
  133. package/transpiled/entities/factory.js.map +1 -0
  134. package/transpiled/entities/index.d.ts +4 -0
  135. package/transpiled/entities/index.js +5 -0
  136. package/transpiled/entities/index.js.map +1 -0
  137. package/transpiled/entities/newid.d.ts +1 -0
  138. package/transpiled/entities/newid.js +7 -0
  139. package/transpiled/entities/newid.js.map +1 -0
  140. package/transpiled/entities/set.d.ts +10 -0
  141. package/transpiled/entities/set.js +19 -0
  142. package/transpiled/entities/set.js.map +1 -0
  143. package/transpiled/event.d.ts +14 -0
  144. package/transpiled/event.js +38 -0
  145. package/transpiled/event.js.map +1 -0
  146. package/transpiled/exceptions.d.ts +61 -0
  147. package/transpiled/exceptions.js +93 -0
  148. package/transpiled/exceptions.js.map +1 -0
  149. package/transpiled/exposition.d.ts +18 -0
  150. package/transpiled/exposition.js +18 -0
  151. package/transpiled/exposition.js.map +1 -0
  152. package/transpiled/guard.d.ts +7 -0
  153. package/transpiled/guard.js +12 -0
  154. package/transpiled/guard.js.map +1 -0
  155. package/transpiled/index.d.ts +35 -0
  156. package/transpiled/index.js +29 -0
  157. package/transpiled/index.js.map +1 -0
  158. package/transpiled/locator.d.ts +12 -0
  159. package/transpiled/locator.js +32 -0
  160. package/transpiled/locator.js.map +1 -0
  161. package/transpiled/observation.d.ts +5 -0
  162. package/transpiled/observation.js +12 -0
  163. package/transpiled/observation.js.map +1 -0
  164. package/transpiled/operation.d.ts +49 -0
  165. package/transpiled/operation.js +77 -0
  166. package/transpiled/operation.js.map +1 -0
  167. package/transpiled/outbox/index.d.ts +1 -0
  168. package/transpiled/outbox/index.js +2 -0
  169. package/transpiled/outbox/index.js.map +1 -0
  170. package/transpiled/outbox/outbox.d.ts +46 -0
  171. package/transpiled/outbox/outbox.js +267 -0
  172. package/transpiled/outbox/outbox.js.map +1 -0
  173. package/transpiled/query/criteria.d.ts +6 -0
  174. package/transpiled/query/criteria.js +74 -0
  175. package/transpiled/query/criteria.js.map +1 -0
  176. package/transpiled/query/options.d.ts +3 -0
  177. package/transpiled/query/options.js +27 -0
  178. package/transpiled/query/options.js.map +1 -0
  179. package/transpiled/query.d.ts +9 -0
  180. package/transpiled/query.js +57 -0
  181. package/transpiled/query.js.map +1 -0
  182. package/transpiled/receiver.d.ts +20 -0
  183. package/transpiled/receiver.js +92 -0
  184. package/transpiled/receiver.js.map +1 -0
  185. package/transpiled/reflection.d.ts +9 -0
  186. package/transpiled/reflection.js +13 -0
  187. package/transpiled/reflection.js.map +1 -0
  188. package/transpiled/remote.d.ts +10 -0
  189. package/transpiled/remote.js +12 -0
  190. package/transpiled/remote.js.map +1 -0
  191. package/transpiled/state.d.ts +33 -0
  192. package/transpiled/state.js +126 -0
  193. package/transpiled/state.js.map +1 -0
  194. package/transpiled/transition.d.ts +14 -0
  195. package/transpiled/transition.js +51 -0
  196. package/transpiled/transition.js.map +1 -0
  197. package/transpiled/transmission.d.ts +8 -0
  198. package/transpiled/transmission.js +30 -0
  199. package/transpiled/transmission.js.map +1 -0
  200. package/transpiled/types/atomicity.d.ts +56 -0
  201. package/transpiled/types/atomicity.js +2 -0
  202. package/transpiled/types/atomicity.js.map +1 -0
  203. package/transpiled/types/bindings.d.ts +42 -0
  204. package/transpiled/types/bindings.js +2 -0
  205. package/transpiled/types/bindings.js.map +1 -0
  206. package/transpiled/types/bridges.d.ts +38 -0
  207. package/transpiled/types/bridges.js +2 -0
  208. package/transpiled/types/bridges.js.map +1 -0
  209. package/transpiled/types/extensions.d.ts +62 -0
  210. package/transpiled/types/extensions.js +2 -0
  211. package/transpiled/types/extensions.js.map +1 -0
  212. package/transpiled/types/index.d.ts +13 -0
  213. package/transpiled/types/index.js +8 -0
  214. package/transpiled/types/index.js.map +1 -0
  215. package/transpiled/types/message.d.ts +5 -0
  216. package/transpiled/types/message.js +2 -0
  217. package/transpiled/types/message.js.map +1 -0
  218. package/transpiled/types/operations.d.ts +15 -0
  219. package/transpiled/types/operations.js +2 -0
  220. package/transpiled/types/operations.js.map +1 -0
  221. package/transpiled/types/outbox.d.ts +28 -0
  222. package/transpiled/types/outbox.js +2 -0
  223. package/transpiled/types/outbox.js.map +1 -0
  224. package/transpiled/types/receiver.d.ts +9 -0
  225. package/transpiled/types/receiver.js +2 -0
  226. package/transpiled/types/receiver.js.map +1 -0
  227. package/transpiled/types/request.d.ts +55 -0
  228. package/transpiled/types/request.js +2 -0
  229. package/transpiled/types/request.js.map +1 -0
  230. package/transpiled/types/state.d.ts +9 -0
  231. package/transpiled/types/state.js +2 -0
  232. package/transpiled/types/state.js.map +1 -0
  233. package/transpiled/types/storages.d.ts +106 -0
  234. package/transpiled/types/storages.js +2 -0
  235. package/transpiled/types/storages.js.map +1 -0
  236. package/transpiled/unmanaged.d.ts +5 -0
  237. package/transpiled/unmanaged.js +7 -0
  238. package/transpiled/unmanaged.js.map +1 -0
  239. package/tsconfig.json +10 -0
  240. package/tsconfig.tsbuildinfo +1 -0
  241. package/CHANGELOG.md +0 -180
  242. package/src/assignment.js +0 -28
  243. package/src/context.js +0 -57
  244. package/src/contract/contract.js +0 -18
  245. package/src/contract/request.js +0 -77
  246. package/src/contract/schemas/error.yaml +0 -9
  247. package/src/contract/schemas/index.js +0 -11
  248. package/src/contract/schemas/query.yaml +0 -43
  249. package/src/contract/schemas/source.yaml +0 -23
  250. package/src/effect.js +0 -15
  251. package/src/entities/changeset.js +0 -36
  252. package/src/entities/entity.js +0 -119
  253. package/src/entities/factory.js +0 -40
  254. package/src/entities/index.js +0 -1
  255. package/src/event.js +0 -54
  256. package/src/exposition.js +0 -24
  257. package/src/guard.js +0 -13
  258. package/src/observation.js +0 -8
  259. package/src/operation.js +0 -95
  260. package/src/query/criteria.js +0 -37
  261. package/src/query/options.js +0 -37
  262. package/src/receiver.js +0 -123
  263. package/src/reflection.js +0 -24
  264. package/src/remote.js +0 -14
  265. package/src/state.js +0 -138
  266. package/src/transition.js +0 -61
  267. package/src/unmanaged.js +0 -7
  268. package/types/atomicity.d.ts +0 -58
  269. package/types/bindings.d.ts +0 -42
  270. package/types/bridges.ts +0 -33
  271. package/types/call.d.ts +0 -13
  272. package/types/component.d.ts +0 -12
  273. package/types/connector.d.ts +0 -18
  274. package/types/context.d.ts +0 -24
  275. package/types/entity.d.ts +0 -46
  276. package/types/event.d.ts +0 -11
  277. package/types/exception.d.ts +0 -4
  278. package/types/extensions.d.ts +0 -73
  279. package/types/guard.d.ts +0 -5
  280. package/types/locator.d.ts +0 -13
  281. package/types/operations.d.ts +0 -8
  282. package/types/outbox.d.ts +0 -53
  283. package/types/query.d.ts +0 -17
  284. package/types/receiver.d.ts +0 -12
  285. package/types/reflection.d.ts +0 -14
  286. package/types/remote.d.ts +0 -18
  287. package/types/state.d.ts +0 -38
  288. package/types/storages.d.ts +0 -96
  289. /package/{src/contract/index.js → source/contract/index.ts} +0 -0
  290. /package/{src/outbox/index.js → source/outbox/index.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/core",
3
- "version": "1.0.0-alpha.283",
3
+ "version": "1.0.0-alpha.286",
4
4
  "type": "module",
5
5
  "description": "Toa Core",
6
6
  "author": "temich <tema.gurtovoy@gmail.com>",
@@ -12,20 +12,32 @@
12
12
  "bugs": {
13
13
  "url": "https://github.com/toa-io/toa/issues"
14
14
  },
15
- "main": "src/index.js",
16
- "types": "types/index.ts",
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
+ },
17
28
  "publishConfig": {
18
29
  "access": "public"
19
30
  },
20
31
  "scripts": {
32
+ "transpile": "tsc",
21
33
  "test": "echo \"Error: run tests from root\" && exit 1"
22
34
  },
23
35
  "dependencies": {
24
36
  "@rsql/parser": "1.6.0",
25
- "@toa.io/generic": "1.0.0-alpha.283",
26
- "js-yaml": "5.4.1",
27
- "openspan": "1.0.0-alpha.277",
37
+ "@toa.io/generic": "1.0.0-alpha.286",
38
+ "@toa.io/schemas": "1.0.0-alpha.286",
39
+ "openspan": "1.0.0-alpha.286",
28
40
  "uuid": "14.0.2"
29
41
  },
30
- "gitHead": "2f52b023bed0ccb2ecdb2b82fe59fbe38bf282e1"
42
+ "gitHead": "81f3d0688f17668b902be336d1749cc715edcdb6"
31
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
+ }
@@ -1,13 +1,16 @@
1
1
  import { Readable } from 'node:stream'
2
2
  import { current, encode } from 'openspan'
3
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'
4
7
 
5
8
  export class Call extends Connector {
6
- #transmitter
7
- #contract
8
- #source
9
+ readonly #transmitter: Transmission
10
+ readonly #contract: Contract
11
+ readonly #source: Source | undefined
9
12
 
10
- constructor (transmitter, contract, source) {
13
+ public constructor(transmitter: Transmission, contract: Contract, source?: Source) {
11
14
  super()
12
15
 
13
16
  this.#transmitter = transmitter
@@ -17,15 +20,13 @@ export class Call extends Connector {
17
20
  this.depends(transmitter)
18
21
  }
19
22
 
20
- async invoke (request = {}) {
23
+ public async invoke(request: Request = {}): Promise<any> {
21
24
  // the caller may have attributed the call itself, as the node bridge does
22
- if (this.#source !== undefined)
23
- request.source ??= this.#source
25
+ if (this.#source !== undefined) request.source ??= this.#source
24
26
 
25
- // fitting first lets the input schema supply its default;
26
- // an operation that takes no input still has to send an explicit null
27
27
  this.#contract.fit(request)
28
28
 
29
+ // an operation that takes no input still has to send an explicit null
29
30
  request.input ??= null
30
31
 
31
32
  // avoid validation on the recipient's side
@@ -33,34 +34,28 @@ export class Call extends Connector {
33
34
 
34
35
  const context = current()
35
36
 
36
- if (context !== undefined)
37
- request.telemetry = encode(context)
37
+ if (context !== undefined) request.telemetry = encode(context)
38
38
 
39
39
  const reply = await this.#transmitter.request(request)
40
40
 
41
41
  if (reply === null) return null
42
42
  else if (reply instanceof Readable) return reply
43
43
  else {
44
- if (reply.exception !== undefined)
45
- throw reply.exception
44
+ if (reply.exception !== undefined) throw reply.exception
46
45
 
47
- if (reply.error !== undefined)
48
- return new RemoteError(reply.error)
49
- else
50
- return reply.output
46
+ if (reply.error !== undefined) return new RemoteError(reply.error)
47
+ else return reply.output
51
48
  }
52
49
  }
53
50
 
54
- explain () {
51
+ public explain(): any {
55
52
  return this.#contract.discovery
56
53
  }
57
54
  }
58
55
 
59
-
60
-
61
56
  // the remote error as a value: every property it carries, and nothing else enumerable
62
57
  class RemoteError extends Error {
63
- constructor (error) {
58
+ public constructor(error: object) {
64
59
  super()
65
60
 
66
61
  Object.assign(this, error)
@@ -1,22 +1,20 @@
1
1
  import { Connector } from './connector.js'
2
+ import type { Algorithm } from './types/bridges.js'
2
3
 
3
4
  export class Cascade extends Connector {
4
- // #bridges
5
- #last
5
+ readonly #last: Algorithm
6
6
 
7
- constructor (bridges, preflight) {
7
+ public constructor(bridges: Algorithm[], preflight?: Connector) {
8
8
  super()
9
9
 
10
10
  // this.#bridges = bridges
11
11
  this.#last = bridges[bridges.length - 1]
12
12
 
13
- if (preflight === undefined)
14
- this.depends(bridges)
15
- else
16
- this.depends(bridges).depends(preflight)
13
+ if (preflight === undefined) this.depends(bridges)
14
+ else this.depends(bridges).depends(preflight)
17
15
  }
18
16
 
19
- async run (...args) {
17
+ public async run(...args: [any, any?]): Promise<any> {
20
18
  // const reply = {}
21
19
  //
22
20
  // for (const bridge of this.#bridges) {
@@ -1,20 +1,25 @@
1
1
  import assert from 'node:assert'
2
- import { console, current, decode, run } from 'openspan'
2
+ import { console, current, decode, run, type SpanOptions } from 'openspan'
3
3
  import { Connector } from './connector.js'
4
+ import type { Locator } from './locator.js'
5
+ import type { Request } from './types/request.js'
4
6
 
5
- export class Component extends Connector {
6
- locator
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
7
14
 
8
- /** @protected */
9
- operations
15
+ protected readonly operations: Record<string, O>
10
16
 
11
- /** @protected */
12
- kind = 'server'
17
+ protected kind: 'server' | 'client' = 'server'
13
18
 
14
- /** @type {Record<string, object>} span options per endpoint */
15
- #spans = {}
19
+ /** span options per endpoint */
20
+ readonly #spans: Record<string, SpanOptions> = {}
16
21
 
17
- constructor (locator, operations) {
22
+ public constructor(locator: Locator, operations: Record<string, O>) {
18
23
  super()
19
24
 
20
25
  this.locator = locator
@@ -23,28 +28,27 @@ export class Component extends Connector {
23
28
  Object.values(operations).forEach((operation) => this.depends(operation))
24
29
  }
25
30
 
26
- async invoke (endpoint, request) {
31
+ public async invoke<T = any>(endpoint: string, request?: Request): Promise<T> {
27
32
  if (!(endpoint in this.operations))
28
33
  // `assert.fail`, not `assert.ok`: the message is built only when it is needed
29
34
  assert.fail(`Endpoint '${endpoint}' is not provided by '${this.locator.id}'`)
30
35
 
31
36
  // if the request carries no telemetry, the trace starts here
32
37
  const remote = request?.telemetry === undefined ? null : decode(request.telemetry)
33
- const task = () => this.process(endpoint, request)
38
+ const task = async (): Promise<any> => this.#process(endpoint, request)
34
39
 
35
- if (remote === null)
36
- return task()
37
- else
38
- return run(remote, task)
40
+ if (remote === null) return task()
41
+ else return run(remote, task)
39
42
  }
40
43
 
41
- /** @private */
42
- async process (endpoint, request) {
43
- return console.span(this.span(endpoint), async () => {
44
- const reply = await this.operations[endpoint].invoke(request)
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)
45
47
 
46
48
  if (reply?.exception !== undefined) {
47
- current().status = 'error'
49
+ const span = current()
50
+
51
+ if (span !== undefined) span.status = 'error'
48
52
 
49
53
  console.error('Failed to execute operation', {
50
54
  endpoint: `${this.locator.id}.${endpoint}`,
@@ -60,9 +64,8 @@ export class Component extends Connector {
60
64
  * The span of an endpoint never changes, so it is built once. Not in the constructor:
61
65
  * `kind` is a field of the subclass, and those are assigned after this one is built.
62
66
  *
63
- * @private
64
67
  */
65
- span (endpoint) {
68
+ #span(endpoint: string): SpanOptions {
66
69
  let options = this.#spans[endpoint]
67
70
 
68
71
  if (options === undefined) {
@@ -70,8 +73,7 @@ export class Component extends Connector {
70
73
 
71
74
  // the server span is emitted by the component itself, while the client span
72
75
  // belongs to the calling service and inherits it from the context
73
- if (this.kind === 'server')
74
- options.service = this.locator.id
76
+ if (this.kind === 'server') options.service = this.locator.id
75
77
 
76
78
  this.#spans[endpoint] = options
77
79
  }
@@ -2,7 +2,13 @@ import { console } from 'openspan'
2
2
  import { Connector } from './connector.js'
3
3
 
4
4
  export class Composition extends Connector {
5
- constructor (expositions, producers, receivers, tenants) {
5
+ // eslint-disable-next-line max-params
6
+ public constructor(
7
+ expositions: Connector[],
8
+ producers: Connector[],
9
+ receivers: Connector[],
10
+ tenants: Connector[]
11
+ ) {
6
12
  super()
7
13
 
8
14
  if (expositions.length > 0) this.depends(expositions)
@@ -11,11 +17,11 @@ export class Composition extends Connector {
11
17
  if (tenants.length > 0) this.depends(tenants)
12
18
  }
13
19
 
14
- async open () {
20
+ protected override async open(): Promise<void> {
15
21
  console.info('Composition complete')
16
22
  }
17
23
 
18
- async dispose () {
24
+ protected override async dispose(): Promise<void> {
19
25
  console.info('Composition shutdown complete')
20
26
  }
21
27
  }
@@ -1,28 +1,17 @@
1
1
  import { console } from 'openspan'
2
+ import type { Locator } from './locator.js'
2
3
 
3
- /**
4
- * Abstract connections hierarchy
5
- * @implements {toa.core.Connector}
6
- */
4
+ /** Abstract connections hierarchy */
7
5
  export class Connector {
8
- /** @type {Array<Connector>} */
9
- #dependencies = []
6
+ #dependencies: Connector[] = []
7
+ #links: Connector[] = []
8
+ #connecting: Promise<void> | undefined
9
+ #disconnecting: Promise<void> | undefined
10
10
 
11
- /** @type {Array<Connector>} */
12
- #links = []
11
+ public readonly id: string
12
+ public connected: boolean = false
13
13
 
14
- /** @type {Promise} */
15
- #connecting
16
-
17
- /** @type {Promise} */
18
- #disconnecting
19
-
20
- /** @type {string} */
21
- id
22
- /** @type {boolean} */
23
- connected = false
24
-
25
- constructor () {
14
+ public constructor() {
26
15
  this.id = this.constructor.name + '#' + Math.random().toString(36).substring(2, 8)
27
16
  }
28
17
 
@@ -31,12 +20,9 @@ export class Connector {
31
20
  *
32
21
  * See .connect() and .disconnect()
33
22
  *
34
- * @param connector {Connector | any | Array<Connector | any>}
35
- * @returns {Connector}
36
23
  */
37
- depends (connector) {
38
- /** @type {Connector} */
39
- let next
24
+ public depends(connector: Connector | Connector[]): Connector {
25
+ let next: Connector
40
26
 
41
27
  if (connector instanceof Array) {
42
28
  if (connector.length === 0) throw new Error('Connectors array must not be empty')
@@ -62,10 +48,8 @@ export class Connector {
62
48
  *
63
49
  * See .connect() and .disconnect()
64
50
  *
65
- * @param connector {Connector}
66
- * @returns {void}
67
51
  */
68
- link (connector) {
52
+ public link(connector: Connector): void {
69
53
  this.#links.push(connector)
70
54
  }
71
55
 
@@ -76,9 +60,8 @@ export class Connector {
76
60
  *
77
61
  * Method is idempotent
78
62
  *
79
- * @returns {Promise<void>}
80
63
  */
81
- async connect () {
64
+ public async connect(): Promise<void> {
82
65
  if (this.#connecting) return this.#connecting
83
66
 
84
67
  this.#disconnecting = undefined
@@ -89,12 +72,17 @@ export class Connector {
89
72
  }
90
73
 
91
74
  // 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()
75
+ this.#connecting =
76
+ TRACE_BOOT && this.constructor.name !== 'Connector'
77
+ ? console.span(
78
+ {
79
+ name: `connect ${this.constructor.name}`,
80
+ // a connector that has a locator is named by it; a grouping node has none
81
+ attributes: { id: (this as { locator?: Locator }).locator?.id ?? this.id }
82
+ },
83
+ work
84
+ )
85
+ : work()
98
86
 
99
87
  try {
100
88
  await this.#connecting
@@ -112,10 +100,8 @@ export class Connector {
112
100
  *
113
101
  * Method is idempotent
114
102
  *
115
- * @param [interrupt] {boolean}
116
- * @returns {Promise<void>}
117
103
  */
118
- async disconnect (interrupt) {
104
+ public async disconnect(interrupt?: boolean): Promise<void> {
119
105
  // a connector that has not finished connecting has nothing to close, and
120
106
  // awaiting a connection that may never settle outlives any grace period
121
107
  const pending = interrupt === true || this.connected === false
@@ -149,7 +135,7 @@ export class Connector {
149
135
 
150
136
  clearInterval(interval)
151
137
 
152
- await Promise.all(this.#dependencies.map(connector => connector.disconnect()))
138
+ await Promise.all(this.#dependencies.map((connector) => connector.disconnect()))
153
139
 
154
140
  await this.dispose()
155
141
  })()
@@ -157,33 +143,28 @@ export class Connector {
157
143
  await this.#disconnecting
158
144
  }
159
145
 
160
- async reconnect () {
146
+ public async reconnect(): Promise<void> {
161
147
  await this.disconnect()
162
148
  await this.connect()
163
149
  }
164
150
 
165
- debug (node = {}) {
151
+ public debug(node: Record<string, any> = {}): Record<string, any> {
166
152
  node[this.id] = { connected: this.connected }
167
153
 
168
- if (this.#dependencies.length > 0) for (const connector of this.#dependencies) connector.debug?.(node[this.id])
154
+ if (this.#dependencies.length > 0)
155
+ for (const connector of this.#dependencies) connector.debug?.(node[this.id])
169
156
 
170
157
  return node
171
158
  }
172
159
 
173
- /**
174
- * Called on connection
175
- */
176
- async open () {}
160
+ /** Called on connection */
161
+ protected open(): Promise<void> | void {}
177
162
 
178
- /**
179
- * Called on disconnection
180
- */
181
- async close () {}
163
+ /** Called on disconnection */
164
+ protected close(): Promise<void> | void {}
182
165
 
183
- /**
184
- * Called after self and dependants disconnection is complete
185
- */
186
- async dispose () {}
166
+ /** Called after self and dependants disconnection is complete */
167
+ protected dispose(): Promise<void> | void {}
187
168
  }
188
169
 
189
170
  const DELAY = 5000
@@ -0,0 +1,67 @@
1
+ import { Connector } from './connector.js'
2
+ import type { Locator } from './locator.js'
3
+ import type { Component } from './component.js'
4
+ import type { Remote } from './remote.js'
5
+ import type { Aspect } from './types/extensions.js'
6
+ import type { Request } from './types/request.js'
7
+
8
+ type Discover = (namespace: string, name: string) => Promise<Remote>
9
+
10
+ export class Context extends Connector {
11
+ public readonly env: string | undefined
12
+ public readonly name: string | undefined
13
+ public readonly aspects: Aspect[]
14
+ public readonly locator: Locator
15
+
16
+ readonly #local: Component
17
+ readonly #discover: Discover
18
+ readonly #remotes: Record<string, Promise<Remote>> = {}
19
+
20
+ public constructor(local: Component, discover: Discover, aspects: Aspect[] = []) {
21
+ super()
22
+
23
+ this.env = process.env.TOA_ENV
24
+ this.name = process.env.TOA_CONTEXT
25
+ this.aspects = aspects
26
+ this.locator = local?.locator
27
+
28
+ this.#local = local
29
+ this.#discover = discover
30
+
31
+ this.depends(local)
32
+
33
+ if (aspects.length > 0) this.depends(aspects)
34
+ }
35
+
36
+ public async apply(endpoint: string, request: Request): Promise<any> {
37
+ return this.#local.invoke(endpoint, request)
38
+ }
39
+
40
+ // eslint-disable-next-line max-params
41
+ public async call(
42
+ namespace: string,
43
+ name: string,
44
+ endpoint: string,
45
+ request: Request
46
+ ): Promise<any> {
47
+ const remote = await this.#remote(namespace, name)
48
+
49
+ return remote.invoke(endpoint, request)
50
+ }
51
+
52
+ async #remote(namespace: string, name: string): Promise<Remote> {
53
+ const key = namespace + '.' + name
54
+
55
+ this.#remotes[key] ??= this.#connect(namespace, name)
56
+
57
+ return this.#remotes[key]
58
+ }
59
+
60
+ async #connect(namespace: string, name: string): Promise<Remote> {
61
+ const remote = await this.#discover(namespace, name)
62
+
63
+ this.depends(remote)
64
+
65
+ return remote
66
+ }
67
+ }
@@ -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
+ }
@@ -1,23 +1,29 @@
1
1
  import { Contract } from './contract.js'
2
2
  import { ResponseContractException } from '../exceptions.js'
3
+ import type { Refusal } from './contract.js'
4
+ import type { JSONSchema } from './schemas.js'
3
5
 
4
6
  export class Reply extends Contract {
5
- static Exception = ResponseContractException
7
+ public static override Exception: Refusal =
8
+ ResponseContractException as unknown as Refusal
6
9
 
7
- static schema (output, errors) {
8
- const schema = { type: 'object', properties: {}, additionalProperties: false }
10
+ public static schema(output?: JSONSchema, errors?: Array<string | number>): JSONSchema {
11
+ const schema: JSONSchema = {
12
+ type: 'object',
13
+ properties: {},
14
+ additionalProperties: false
15
+ }
9
16
 
10
17
  if (output !== undefined) {
11
18
  /*
12
- * A reply carries more than the operation declares — `_version` and the rest of the
19
+ * A reply carries more than the operation declares — `VERSION` and the rest of the
13
20
  * record's own fields — so what it is validated against is the declaration relaxed.
14
21
  * On a copy: the declaration itself is what a component publishes when asked to
15
22
  * explain, and relaxing that would publish a contract nobody wrote.
16
23
  */
17
24
  output = structuredClone(output)
18
25
 
19
- if (output.type === 'object')
20
- output.additionalProperties = true
26
+ if (output.type === 'object') output.additionalProperties = true
21
27
  else if (output.type === 'array' && output.items?.type === 'object')
22
28
  output.items.additionalProperties = true
23
29
 
@@ -29,20 +35,21 @@ export class Reply extends Contract {
29
35
  * stated, an error is not a reply this operation makes — it is a mistake, and the
30
36
  * contract says so rather than passing an undeclared code on to whoever called.
31
37
  */
32
- schema.properties.error = errors === undefined
33
- ? false
34
- : {
35
- type: 'object',
36
- properties: {
37
- code: {
38
- enum: errors
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
+ }
39
50
  },
40
- message: {
41
- type: 'string'
42
- }
43
- },
44
- required: ['code']
45
- }
51
+ required: ['code']
52
+ }
46
53
 
47
54
  return schema
48
55
  }