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

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 (304) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/package.json +22 -12
  3. package/source/assignment.ts +27 -0
  4. package/source/call.ts +63 -0
  5. package/source/cascade.ts +32 -0
  6. package/source/component.ts +83 -0
  7. package/source/composition.ts +27 -0
  8. package/source/connector.ts +217 -0
  9. package/source/context.ts +68 -0
  10. package/source/contract/contract.ts +23 -0
  11. package/source/contract/index.ts +2 -0
  12. package/source/contract/reply.ts +56 -0
  13. package/source/contract/request.ts +140 -0
  14. package/source/contract/schemas.ts +39 -0
  15. package/source/discovery.ts +53 -0
  16. package/source/effect.ts +14 -0
  17. package/source/emission.ts +21 -0
  18. package/source/entities/changeset.ts +37 -0
  19. package/source/entities/entity.ts +137 -0
  20. package/source/entities/factory.ts +52 -0
  21. package/source/entities/index.ts +4 -0
  22. package/source/entities/newid.ts +9 -0
  23. package/source/entities/set.ts +27 -0
  24. package/source/event.ts +57 -0
  25. package/source/exceptions.ts +133 -0
  26. package/source/exposition.ts +33 -0
  27. package/source/guard.ts +16 -0
  28. package/source/index.ts +36 -0
  29. package/source/locator.ts +37 -0
  30. package/source/observation.ts +16 -0
  31. package/source/operation.ts +139 -0
  32. package/source/outbox/index.ts +1 -0
  33. package/source/outbox/outbox.ts +341 -0
  34. package/source/query/criteria.ts +90 -0
  35. package/source/query/options.ts +35 -0
  36. package/source/query.ts +67 -0
  37. package/source/receiver.ts +120 -0
  38. package/source/reflection.ts +20 -0
  39. package/source/remote.ts +20 -0
  40. package/source/state.ts +179 -0
  41. package/source/transition.ts +79 -0
  42. package/source/transmission.ts +38 -0
  43. package/source/types/atomicity.ts +63 -0
  44. package/source/types/bindings.ts +59 -0
  45. package/source/types/bridges.ts +54 -0
  46. package/source/types/extensions.ts +89 -0
  47. package/source/types/index.ts +14 -0
  48. package/{types/message.d.ts → source/types/message.ts} +2 -0
  49. package/source/types/operations.ts +31 -0
  50. package/source/types/outbox.ts +35 -0
  51. package/source/types/receiver.ts +10 -0
  52. package/source/types/request.ts +54 -0
  53. package/source/types/state.ts +9 -0
  54. package/source/types/storages.ts +134 -0
  55. package/source/unmanaged.ts +8 -0
  56. package/test/call.fixtures.js +10 -12
  57. package/test/call.test.js +42 -9
  58. package/test/component.fixtures.js +5 -8
  59. package/test/component.test.js +27 -10
  60. package/test/connector.fixtures.js +29 -13
  61. package/test/connector.test.js +128 -38
  62. package/test/context.fixtures.js +8 -12
  63. package/test/context.test.js +19 -8
  64. package/test/contract/conditions.test.js +15 -9
  65. package/test/contract/contract.fixtures.js +11 -16
  66. package/test/contract/request.test.js +130 -46
  67. package/test/discovery.test.js +71 -0
  68. package/test/emission.fixtures.js +6 -10
  69. package/test/emission.test.js +37 -12
  70. package/test/entities/entity.fixtures.js +11 -16
  71. package/test/entities/entity.test.js +105 -50
  72. package/test/entities/factory.fixtures.js +26 -13
  73. package/test/entities/factory.test.js +68 -17
  74. package/test/entities/set.fixtures.js +6 -8
  75. package/test/entities/set.test.js +6 -5
  76. package/test/event.fixtures.js +9 -14
  77. package/test/event.test.js +78 -34
  78. package/test/locator.test.js +32 -25
  79. package/test/outbox.test.js +202 -0
  80. package/test/query.fixtures.js +1 -5
  81. package/test/query.test.js +111 -14
  82. package/test/receiver.fixtures.js +9 -12
  83. package/test/receiver.test.js +80 -31
  84. package/test/reflection.test.js +9 -8
  85. package/test/state.fixtures.js +33 -31
  86. package/test/state.test.js +135 -19
  87. package/test/transmission.fixtures.js +4 -6
  88. package/test/transmission.test.js +47 -16
  89. package/transpiled/assignment.d.ts +6 -0
  90. package/transpiled/assignment.js +20 -0
  91. package/transpiled/assignment.js.map +1 -0
  92. package/transpiled/call.d.ts +10 -0
  93. package/transpiled/call.js +52 -0
  94. package/transpiled/call.js.map +1 -0
  95. package/transpiled/cascade.d.ts +7 -0
  96. package/transpiled/cascade.js +28 -0
  97. package/transpiled/cascade.js.map +1 -0
  98. package/transpiled/component.d.ts +15 -0
  99. package/transpiled/component.js +61 -0
  100. package/transpiled/component.js.map +1 -0
  101. package/transpiled/composition.d.ts +6 -0
  102. package/transpiled/composition.js +23 -0
  103. package/transpiled/composition.js.map +1 -0
  104. package/transpiled/connector.d.ts +47 -0
  105. package/transpiled/connector.js +185 -0
  106. package/transpiled/connector.js.map +1 -0
  107. package/transpiled/context.d.ts +18 -0
  108. package/transpiled/context.js +42 -0
  109. package/transpiled/context.js.map +1 -0
  110. package/transpiled/contract/contract.d.ts +10 -0
  111. package/transpiled/contract/contract.js +14 -0
  112. package/transpiled/contract/contract.js.map +1 -0
  113. package/transpiled/contract/index.d.ts +2 -0
  114. package/transpiled/contract/index.js +3 -0
  115. package/transpiled/contract/index.js.map +1 -0
  116. package/transpiled/contract/reply.d.ts +7 -0
  117. package/transpiled/contract/reply.js +48 -0
  118. package/transpiled/contract/reply.js.map +1 -0
  119. package/transpiled/contract/request.d.ts +29 -0
  120. package/transpiled/contract/request.js +103 -0
  121. package/transpiled/contract/request.js.map +1 -0
  122. package/transpiled/contract/schemas.d.ts +4 -0
  123. package/transpiled/contract/schemas.js +36 -0
  124. package/transpiled/contract/schemas.js.map +1 -0
  125. package/transpiled/discovery.d.ts +12 -0
  126. package/transpiled/discovery.js +34 -0
  127. package/transpiled/discovery.js.map +1 -0
  128. package/transpiled/effect.d.ts +5 -0
  129. package/transpiled/effect.js +11 -0
  130. package/transpiled/effect.js.map +1 -0
  131. package/transpiled/emission.d.ts +8 -0
  132. package/transpiled/emission.js +14 -0
  133. package/transpiled/emission.js.map +1 -0
  134. package/transpiled/entities/changeset.d.ts +10 -0
  135. package/transpiled/entities/changeset.js +27 -0
  136. package/transpiled/entities/changeset.js.map +1 -0
  137. package/transpiled/entities/entity.d.ts +19 -0
  138. package/transpiled/entities/entity.js +109 -0
  139. package/transpiled/entities/entity.js.map +1 -0
  140. package/transpiled/entities/factory.d.ts +22 -0
  141. package/transpiled/entities/factory.js +34 -0
  142. package/transpiled/entities/factory.js.map +1 -0
  143. package/transpiled/entities/index.d.ts +4 -0
  144. package/transpiled/entities/index.js +5 -0
  145. package/transpiled/entities/index.js.map +1 -0
  146. package/transpiled/entities/newid.d.ts +1 -0
  147. package/transpiled/entities/newid.js +7 -0
  148. package/transpiled/entities/newid.js.map +1 -0
  149. package/transpiled/entities/set.d.ts +10 -0
  150. package/transpiled/entities/set.js +19 -0
  151. package/transpiled/entities/set.js.map +1 -0
  152. package/transpiled/event.d.ts +14 -0
  153. package/transpiled/event.js +38 -0
  154. package/transpiled/event.js.map +1 -0
  155. package/transpiled/exceptions.d.ts +61 -0
  156. package/transpiled/exceptions.js +93 -0
  157. package/transpiled/exceptions.js.map +1 -0
  158. package/transpiled/exposition.d.ts +18 -0
  159. package/transpiled/exposition.js +18 -0
  160. package/transpiled/exposition.js.map +1 -0
  161. package/transpiled/guard.d.ts +7 -0
  162. package/transpiled/guard.js +12 -0
  163. package/transpiled/guard.js.map +1 -0
  164. package/transpiled/index.d.ts +35 -0
  165. package/transpiled/index.js +29 -0
  166. package/transpiled/index.js.map +1 -0
  167. package/transpiled/locator.d.ts +12 -0
  168. package/transpiled/locator.js +32 -0
  169. package/transpiled/locator.js.map +1 -0
  170. package/transpiled/observation.d.ts +5 -0
  171. package/transpiled/observation.js +12 -0
  172. package/transpiled/observation.js.map +1 -0
  173. package/transpiled/operation.d.ts +49 -0
  174. package/transpiled/operation.js +78 -0
  175. package/transpiled/operation.js.map +1 -0
  176. package/transpiled/outbox/index.d.ts +1 -0
  177. package/transpiled/outbox/index.js +2 -0
  178. package/transpiled/outbox/index.js.map +1 -0
  179. package/transpiled/outbox/outbox.d.ts +46 -0
  180. package/transpiled/outbox/outbox.js +268 -0
  181. package/transpiled/outbox/outbox.js.map +1 -0
  182. package/transpiled/query/criteria.d.ts +6 -0
  183. package/transpiled/query/criteria.js +74 -0
  184. package/transpiled/query/criteria.js.map +1 -0
  185. package/transpiled/query/options.d.ts +3 -0
  186. package/transpiled/query/options.js +27 -0
  187. package/transpiled/query/options.js.map +1 -0
  188. package/transpiled/query.d.ts +9 -0
  189. package/transpiled/query.js +57 -0
  190. package/transpiled/query.js.map +1 -0
  191. package/transpiled/receiver.d.ts +20 -0
  192. package/transpiled/receiver.js +92 -0
  193. package/transpiled/receiver.js.map +1 -0
  194. package/transpiled/reflection.d.ts +9 -0
  195. package/transpiled/reflection.js +13 -0
  196. package/transpiled/reflection.js.map +1 -0
  197. package/transpiled/remote.d.ts +10 -0
  198. package/transpiled/remote.js +12 -0
  199. package/transpiled/remote.js.map +1 -0
  200. package/transpiled/state.d.ts +33 -0
  201. package/transpiled/state.js +126 -0
  202. package/transpiled/state.js.map +1 -0
  203. package/transpiled/transition.d.ts +14 -0
  204. package/transpiled/transition.js +51 -0
  205. package/transpiled/transition.js.map +1 -0
  206. package/transpiled/transmission.d.ts +8 -0
  207. package/transpiled/transmission.js +30 -0
  208. package/transpiled/transmission.js.map +1 -0
  209. package/transpiled/types/atomicity.d.ts +56 -0
  210. package/transpiled/types/atomicity.js +2 -0
  211. package/transpiled/types/atomicity.js.map +1 -0
  212. package/transpiled/types/bindings.d.ts +42 -0
  213. package/transpiled/types/bindings.js +2 -0
  214. package/transpiled/types/bindings.js.map +1 -0
  215. package/transpiled/types/bridges.d.ts +38 -0
  216. package/transpiled/types/bridges.js +2 -0
  217. package/transpiled/types/bridges.js.map +1 -0
  218. package/transpiled/types/extensions.d.ts +62 -0
  219. package/transpiled/types/extensions.js +2 -0
  220. package/transpiled/types/extensions.js.map +1 -0
  221. package/transpiled/types/index.d.ts +13 -0
  222. package/transpiled/types/index.js +8 -0
  223. package/transpiled/types/index.js.map +1 -0
  224. package/transpiled/types/message.d.ts +5 -0
  225. package/transpiled/types/message.js +2 -0
  226. package/transpiled/types/message.js.map +1 -0
  227. package/transpiled/types/operations.d.ts +15 -0
  228. package/transpiled/types/operations.js +2 -0
  229. package/transpiled/types/operations.js.map +1 -0
  230. package/transpiled/types/outbox.d.ts +28 -0
  231. package/transpiled/types/outbox.js +2 -0
  232. package/transpiled/types/outbox.js.map +1 -0
  233. package/transpiled/types/receiver.d.ts +9 -0
  234. package/transpiled/types/receiver.js +2 -0
  235. package/transpiled/types/receiver.js.map +1 -0
  236. package/transpiled/types/request.d.ts +55 -0
  237. package/transpiled/types/request.js +2 -0
  238. package/transpiled/types/request.js.map +1 -0
  239. package/transpiled/types/state.d.ts +9 -0
  240. package/transpiled/types/state.js +2 -0
  241. package/transpiled/types/state.js.map +1 -0
  242. package/transpiled/types/storages.d.ts +111 -0
  243. package/transpiled/types/storages.js +2 -0
  244. package/transpiled/types/storages.js.map +1 -0
  245. package/transpiled/unmanaged.d.ts +5 -0
  246. package/transpiled/unmanaged.js +7 -0
  247. package/transpiled/unmanaged.js.map +1 -0
  248. package/tsconfig.json +10 -0
  249. package/tsconfig.tsbuildinfo +1 -0
  250. package/src/assignment.js +0 -31
  251. package/src/call.js +0 -42
  252. package/src/cascade.js +0 -36
  253. package/src/component.js +0 -41
  254. package/src/composition.js +0 -25
  255. package/src/connector.js +0 -183
  256. package/src/context.js +0 -55
  257. package/src/contract/conditions.js +0 -21
  258. package/src/contract/index.js +0 -7
  259. package/src/contract/reply.js +0 -25
  260. package/src/contract/request.js +0 -67
  261. package/src/contract/schemas/error.yaml +0 -9
  262. package/src/contract/schemas/index.js +0 -7
  263. package/src/contract/schemas/query.yaml +0 -30
  264. package/src/discovery.js +0 -44
  265. package/src/emission.js +0 -23
  266. package/src/entities/changeset.js +0 -43
  267. package/src/entities/entity.js +0 -73
  268. package/src/entities/factory.js +0 -33
  269. package/src/entities/index.js +0 -5
  270. package/src/entities/set.js +0 -15
  271. package/src/event.js +0 -40
  272. package/src/exceptions.js +0 -100
  273. package/src/exposition.js +0 -27
  274. package/src/index.js +0 -47
  275. package/src/locator.js +0 -54
  276. package/src/observation.js +0 -20
  277. package/src/operation.js +0 -69
  278. package/src/query/criteria.js +0 -41
  279. package/src/query/options.js +0 -40
  280. package/src/query.js +0 -41
  281. package/src/receiver.js +0 -65
  282. package/src/reflection.js +0 -28
  283. package/src/remote.js +0 -17
  284. package/src/state.js +0 -99
  285. package/src/transition.js +0 -73
  286. package/src/transmission.js +0 -33
  287. package/types/bindings.d.ts +0 -40
  288. package/types/bridges.ts +0 -30
  289. package/types/component.d.ts +0 -9
  290. package/types/connector.d.ts +0 -18
  291. package/types/context.d.ts +0 -21
  292. package/types/entity.d.ts +0 -46
  293. package/types/event.d.ts +0 -11
  294. package/types/exception.d.ts +0 -4
  295. package/types/extensions.d.ts +0 -29
  296. package/types/index.ts +0 -14
  297. package/types/locator.d.ts +0 -13
  298. package/types/operations.d.ts +0 -2
  299. package/types/query.d.ts +0 -15
  300. package/types/receiver.d.ts +0 -12
  301. package/types/reflection.d.ts +0 -14
  302. package/types/request.d.ts +0 -23
  303. package/types/state.d.ts +0 -35
  304. package/types/storages.d.ts +0 -71
@@ -1,32 +1,44 @@
1
- 'use strict'
1
+ import { describe, it, beforeEach, mock } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { isDeepStrictEqual } from 'node:util'
2
4
 
3
- const clone = require('clone-deep')
5
+ import clone from 'clone-deep'
4
6
 
5
- jest.mock('../src/connector')
6
-
7
- const { Connector } = require('../src/connector')
8
- const { Event } = require('../src/event')
9
- const fixtures = require('./event.fixtures')
7
+ import { Connector } from '../source/connector.js'
8
+ import { Event } from '../source/event.js'
9
+ import * as fixtures from './event.fixtures.js'
10
10
 
11
11
  let event, emit
12
12
 
13
+ // the fixtures are not connectors, so the dependency is recorded rather than linked
14
+ const depends = mock.method(Connector.prototype, 'depends', () => undefined)
15
+
16
+ const dependencies = (instance) =>
17
+ depends.mock.calls
18
+ .filter((call) => call.this === instance)
19
+ .map((call) => call.arguments[0])
20
+
13
21
  beforeEach(() => {
14
- jest.clearAllMocks()
22
+ resetCalls()
23
+ depends.mock.resetCalls()
15
24
 
16
25
  event = new Event(fixtures.definition, fixtures.binding, fixtures.bridge)
17
26
  emit = () => event.emit(fixtures.event)
18
27
  })
19
28
 
20
29
  it('should depend on binding', () => {
21
- expect(event).toBeInstanceOf(Connector)
22
- expect(Connector.mock.instances[0].depends).toHaveBeenCalledWith(fixtures.binding)
30
+ assert.ok(event instanceof Connector)
31
+ assert.ok(dependencies(event).some((one) => isDeepStrictEqual(one, fixtures.binding)))
23
32
  })
24
33
 
25
34
  it('should depend on bridge if provided', () => {
26
- expect(Connector.mock.instances[0].depends).toHaveBeenCalledWith(fixtures.bridge)
35
+ assert.ok(dependencies(event).some((one) => isDeepStrictEqual(one, fixtures.bridge)))
36
+
37
+ const bridgeless = new Event(fixtures.definition, fixtures.binding)
27
38
 
28
- event = new Event(fixtures.definition, fixtures.binding)
29
- expect(Connector.mock.instances[1].depends).not.toHaveBeenCalledWith(fixtures.bridge)
39
+ assert.ok(
40
+ !dependencies(bridgeless).some((one) => isDeepStrictEqual(one, fixtures.bridge))
41
+ )
30
42
  })
31
43
 
32
44
  describe('condition', () => {
@@ -34,15 +46,20 @@ describe('condition', () => {
34
46
  it('should call condition', async () => {
35
47
  await emit()
36
48
 
37
- expect(fixtures.bridge.condition).toHaveBeenCalledWith(fixtures.event)
49
+ assert.ok(
50
+ fixtures.bridge.condition.mock.calls.some(
51
+ (call) =>
52
+ call.arguments.length === 1 &&
53
+ isDeepStrictEqual(call.arguments[0], fixtures.event)
54
+ )
55
+ )
38
56
  })
39
57
 
40
58
  it('should emit if condition returns true', async () => {
41
59
  await emit()
42
60
 
43
- const payload = await fixtures.bridge.payload.mock.results[0].value
44
- const message = { payload }
45
- expect(fixtures.binding.emit).toHaveBeenCalledWith(message)
61
+ const payload = await fixtures.bridge.payload.mock.calls[0].result
62
+ assertEmitted(payload)
46
63
  })
47
64
 
48
65
  it('should not emit if condition returns false', async () => {
@@ -52,7 +69,7 @@ describe('condition', () => {
52
69
 
53
70
  await event.emit(origin, fixtures.event.changeset, fixtures.event.state)
54
71
 
55
- expect(fixtures.binding.emit).not.toHaveBeenCalled()
72
+ assert.strictEqual(fixtures.binding.emit.mock.callCount(), 0)
56
73
  })
57
74
  })
58
75
 
@@ -66,14 +83,18 @@ describe('condition', () => {
66
83
  })
67
84
 
68
85
  it('should not call condition', async () => {
69
- await event.emit(fixtures.event.origin, fixtures.event.changeset, fixtures.event.state)
70
-
71
- expect(fixtures.bridge.condition).not.toHaveBeenCalledWith()
72
-
73
- const payload = await fixtures.bridge.payload.mock.results[0].value
74
- const message = { payload }
75
-
76
- expect(fixtures.binding.emit).toHaveBeenCalledWith(message)
86
+ await event.emit(
87
+ fixtures.event.origin,
88
+ fixtures.event.changeset,
89
+ fixtures.event.state
90
+ )
91
+
92
+ assert.ok(
93
+ !fixtures.bridge.condition.mock.calls.some((call) => call.arguments.length === 0)
94
+ )
95
+
96
+ const payload = await fixtures.bridge.payload.mock.calls[0].result
97
+ assertEmitted(payload)
77
98
  })
78
99
  })
79
100
  })
@@ -83,10 +104,8 @@ describe('payload', () => {
83
104
  it('should emit payload', async () => {
84
105
  await emit()
85
106
 
86
- const payload = await fixtures.bridge.payload.mock.results[0].value
87
- const message = { payload }
88
-
89
- expect(fixtures.binding.emit).toHaveBeenCalledWith(message)
107
+ const payload = await fixtures.bridge.payload.mock.calls[0].result
108
+ assertEmitted(payload)
90
109
  })
91
110
  })
92
111
 
@@ -103,16 +122,41 @@ describe('payload', () => {
103
122
  it('should not call payload', async () => {
104
123
  await emit()
105
124
 
106
- expect(fixtures.bridge.payload).not.toHaveBeenCalled()
125
+ assert.strictEqual(fixtures.bridge.payload.mock.callCount(), 0)
107
126
  })
108
127
 
109
128
  it('should return state as payload', async () => {
110
129
  await emit()
111
130
 
112
131
  const payload = fixtures.event.state
113
- const message = { payload }
114
-
115
- expect(fixtures.binding.emit).toHaveBeenCalledWith(message)
132
+ assertEmitted(payload)
116
133
  })
117
134
  })
118
135
  })
136
+
137
+ function resetCalls(
138
+ target = [assert, clone, fixtures, depends, dependencies],
139
+ seen = new Set()
140
+ ) {
141
+ if (target === null || typeof target !== 'object' || seen.has(target)) return
142
+
143
+ seen.add(target)
144
+
145
+ for (const value of Object.values(target))
146
+ if (typeof value === 'function' && value.mock !== undefined) value.mock.resetCalls()
147
+ else resetCalls(value, seen)
148
+ }
149
+
150
+ /** The message carries the payload and a telemetry token generated per emission. */
151
+ function assertEmitted(payload) {
152
+ const emitted = fixtures.binding.emit.mock.calls
153
+ .map((call) => call.arguments[0])
154
+ .filter((message) => isDeepStrictEqual(message.payload, payload))
155
+
156
+ assert.notStrictEqual(emitted.length, 0)
157
+
158
+ for (const message of emitted) {
159
+ assert.strictEqual(typeof message.telemetry, 'string')
160
+ assert.deepStrictEqual(Object.keys(message).sort(), ['payload', 'telemetry'])
161
+ }
162
+ }
@@ -1,7 +1,8 @@
1
- 'use strict'
1
+ import { describe, it, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const { generate } = require('randomstring')
4
- const { Locator } = require('../src/locator')
4
+ import { generate } from 'randomstring'
5
+ import { Locator } from '../source/locator.js'
5
6
 
6
7
  /** @type {string} */
7
8
  let name
@@ -9,7 +10,7 @@ let name
9
10
  /** @type {string} */
10
11
  let namespace
11
12
 
12
- /** @type {toa.core.Locator} */
13
+ /** @type {import('@toa.io/core').Locator} */
13
14
  let locator
14
15
 
15
16
  beforeEach(() => {
@@ -20,37 +21,43 @@ beforeEach(() => {
20
21
  })
21
22
 
22
23
  it('should expose name and namespace', () => {
23
- expect(locator.name).toStrictEqual(name)
24
- expect(locator.namespace).toStrictEqual(namespace)
24
+ assert.deepStrictEqual(locator.name, name)
25
+ assert.deepStrictEqual(locator.namespace, namespace)
25
26
  })
26
27
 
27
28
  it('should expose id, label', () => {
28
29
  const id = locator.namespace + '.' + locator.name
29
30
  const label = locator.namespace.toLowerCase() + '-' + locator.name.toLowerCase()
30
31
 
31
- expect(locator.id).toStrictEqual(id)
32
- expect(locator.label).toStrictEqual(label)
32
+ assert.deepStrictEqual(locator.id, id)
33
+ assert.deepStrictEqual(locator.label, label)
33
34
  })
34
35
 
35
36
  it('should expose uppercase', () => {
36
- expect(locator.uppercase).toStrictEqual((locator.namespace + '_' + locator.name).toUpperCase())
37
+ assert.deepStrictEqual(
38
+ locator.uppercase,
39
+ (locator.namespace + '_' + locator.name).toUpperCase()
40
+ )
37
41
  })
38
42
 
39
43
  it('should throw if name is undefined', () => {
40
- expect(() => new Locator(undefined, namespace)).toThrow(TypeError)
44
+ assert.throws(() => new Locator(undefined, namespace), TypeError)
41
45
 
42
46
  // noinspection JSCheckFunctionSignatures
43
- expect(() => new Locator()).toThrow(TypeError)
47
+ assert.throws(() => new Locator(), TypeError)
44
48
  })
45
49
 
46
50
  it('should expose host', () => {
47
- expect(locator.hostname()).toStrictEqual((namespace + '-' + name).toLowerCase())
51
+ assert.deepStrictEqual(locator.hostname(), (namespace + '-' + name).toLowerCase())
48
52
  })
49
53
 
50
54
  it('should expose host with given prefix', () => {
51
55
  const prefix = generate()
52
56
 
53
- expect(locator.hostname(prefix)).toStrictEqual((prefix + '-' + namespace + '-' + name).toLowerCase())
57
+ assert.deepStrictEqual(
58
+ locator.hostname(prefix),
59
+ (prefix + '-' + namespace + '-' + name).toLowerCase()
60
+ )
54
61
  })
55
62
 
56
63
  describe('global', () => {
@@ -61,28 +68,28 @@ describe('global', () => {
61
68
  it('should not throw', () => undefined)
62
69
 
63
70
  it('should expose id', () => {
64
- expect(locator.id).toStrictEqual(name)
71
+ assert.deepStrictEqual(locator.id, name)
65
72
  })
66
73
 
67
74
  it('should expose label', () => {
68
- expect(locator.label).toStrictEqual(name.toLowerCase())
75
+ assert.deepStrictEqual(locator.label, name.toLowerCase())
69
76
  })
70
77
 
71
78
  it('should expose uppercase', () => {
72
- expect(locator.uppercase).toStrictEqual(name.toUpperCase())
79
+ assert.deepStrictEqual(locator.uppercase, name.toUpperCase())
73
80
  })
74
81
 
75
82
  it('should expose hostname', () => {
76
83
  const type = generate()
77
84
 
78
- expect(locator.hostname(type)).toStrictEqual((type + '-' + name).toLowerCase())
79
- expect(locator.hostname()).toStrictEqual(name.toLowerCase())
85
+ assert.deepStrictEqual(locator.hostname(type), (type + '-' + name).toLowerCase())
86
+ assert.deepStrictEqual(locator.hostname(), name.toLowerCase())
80
87
  })
81
88
  })
82
89
 
83
90
  describe('parse', () => {
84
91
  it('should be', async () => {
85
- expect(Locator.parse).toBeInstanceOf(Function)
92
+ assert.ok(Locator.parse instanceof Function)
86
93
  })
87
94
 
88
95
  const namespace = generate()
@@ -92,22 +99,22 @@ describe('parse', () => {
92
99
  it('should parse id', async () => {
93
100
  const locator = Locator.parse(id)
94
101
 
95
- expect(locator.namespace).toStrictEqual(namespace)
96
- expect(locator.name).toStrictEqual(name)
102
+ assert.deepStrictEqual(locator.namespace, namespace)
103
+ assert.deepStrictEqual(locator.name, name)
97
104
  })
98
105
 
99
106
  it('should parse endpoint', async () => {
100
107
  const endpoint = `${id}.${generate()}`
101
108
  const locator = Locator.parse(endpoint)
102
109
 
103
- expect(locator.namespace).toStrictEqual(namespace)
104
- expect(locator.name).toStrictEqual(name)
110
+ assert.deepStrictEqual(locator.namespace, namespace)
111
+ assert.deepStrictEqual(locator.name, name)
105
112
  })
106
113
 
107
114
  it('should parse token as global', async () => {
108
115
  const locator = Locator.parse(name)
109
116
 
110
- expect(locator.namespace).toBeUndefined()
111
- expect(locator.name).toStrictEqual(name)
117
+ assert.strictEqual(locator.namespace, undefined)
118
+ assert.deepStrictEqual(locator.name, name)
112
119
  })
113
120
  })
@@ -0,0 +1,202 @@
1
+ import { it, beforeEach, afterEach, mock } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import { Outbox } from '../source/outbox/index.js'
5
+
6
+ let emission, storage, atom, outbox, listeners
7
+
8
+ const BATCH = 4
9
+
10
+ /** rows as the storage hands them back, ids ascending like the uuid v7 they are */
11
+ const page = (from, count) =>
12
+ Array.from({ length: count }, (_, i) => ({
13
+ id: String(from + i).padStart(4, '0'),
14
+ event: { state: {} }
15
+ }))
16
+
17
+ beforeEach(() => {
18
+ resetCalls()
19
+ mock.timers.enable()
20
+
21
+ emission = { emit: mock.fn(async () => undefined), link: mock.fn() }
22
+
23
+ storage = {
24
+ link: mock.fn(),
25
+ outbox: {
26
+ pending: mock.fn(async () => []),
27
+ settle: mock.fn(async () => undefined)
28
+ }
29
+ }
30
+
31
+ listeners = []
32
+
33
+ atom = {
34
+ slots: mock.fn(() => [0]),
35
+ onassigned: (listener) => {
36
+ listeners.push(listener)
37
+ listener({ i: 0, n: 1 })
38
+
39
+ return () => {
40
+ listeners = listeners.filter((one) => one !== listener)
41
+ }
42
+ },
43
+ link: mock.fn()
44
+ }
45
+ outbox = new Outbox(emission, storage, atom, { interval: 1000, batch: BATCH })
46
+ })
47
+
48
+ afterEach(() => {
49
+ mock.timers.reset()
50
+ })
51
+
52
+ /** everything the current turn awaited, without moving the clock */
53
+ const settled = async () => {
54
+ for (let i = 0; i < 20; i++) await Promise.resolve()
55
+ }
56
+
57
+ /** one cycle, and everything it awaited */
58
+ const cycle = async () => {
59
+ mock.timers.tick(1000)
60
+
61
+ for (let i = 0; i < 20; i++) await Promise.resolve()
62
+ }
63
+
64
+ it('should read nothing more when the first page is short', async () => {
65
+ storage.outbox.pending.mock.mockImplementationOnce(async () => page(0, 3))
66
+
67
+ await outbox.open()
68
+ await cycle()
69
+
70
+ assert.strictEqual(storage.outbox.pending.mock.callCount(), 1)
71
+ assert.strictEqual(emission.emit.mock.callCount(), 3)
72
+ })
73
+
74
+ it('should keep reading while a page comes back full', async () => {
75
+ storage.outbox.pending.mock.mockImplementationOnce(
76
+ async () => page(0, BATCH),
77
+ storage.outbox.pending.mock.callCount()
78
+ )
79
+ storage.outbox.pending.mock.mockImplementationOnce(
80
+ async () => page(BATCH, BATCH),
81
+ storage.outbox.pending.mock.callCount() + 1
82
+ )
83
+ storage.outbox.pending.mock.mockImplementationOnce(
84
+ async () => page(2 * BATCH, 5),
85
+ storage.outbox.pending.mock.callCount() + 2
86
+ )
87
+
88
+ await outbox.open()
89
+ await cycle()
90
+
91
+ assert.strictEqual(storage.outbox.pending.mock.callCount(), 3)
92
+ assert.strictEqual(emission.emit.mock.callCount(), 2 * BATCH + 5)
93
+ })
94
+
95
+ it('should continue each page from the id the one before ended on', async () => {
96
+ storage.outbox.pending.mock.mockImplementationOnce(
97
+ async () => page(0, BATCH),
98
+ storage.outbox.pending.mock.callCount()
99
+ )
100
+ storage.outbox.pending.mock.mockImplementationOnce(
101
+ async () => page(BATCH, 1),
102
+ storage.outbox.pending.mock.callCount() + 1
103
+ )
104
+
105
+ await outbox.open()
106
+ await cycle()
107
+
108
+ const [, second] = storage.outbox.pending.mock.calls
109
+
110
+ assert.strictEqual(storage.outbox.pending.mock.calls[0].arguments[3], undefined)
111
+ assert.deepStrictEqual(second.arguments[3], String(BATCH - 1).padStart(4, '0'))
112
+ })
113
+
114
+ it('should mark what it published, once, after the last page', async () => {
115
+ storage.outbox.pending.mock.mockImplementationOnce(
116
+ async () => page(0, BATCH),
117
+ storage.outbox.pending.mock.callCount()
118
+ )
119
+ storage.outbox.pending.mock.mockImplementationOnce(
120
+ async () => page(BATCH, 2),
121
+ storage.outbox.pending.mock.callCount() + 1
122
+ )
123
+
124
+ await outbox.open()
125
+ await cycle()
126
+
127
+ assert.strictEqual(storage.outbox.settle.mock.callCount(), 1)
128
+ assert.strictEqual(storage.outbox.settle.mock.calls[0].arguments[0].length, BATCH + 2)
129
+ })
130
+
131
+ it('should not publish a row it has published and not yet marked', async () => {
132
+ storage.outbox.settle.mock.mockImplementationOnce(async () => {
133
+ throw new Error('mongo is out')
134
+ })
135
+ storage.outbox.pending.mock.mockImplementationOnce(
136
+ async () => page(0, 2),
137
+ storage.outbox.pending.mock.callCount()
138
+ )
139
+ storage.outbox.pending.mock.mockImplementationOnce(
140
+ async () => page(0, 2),
141
+ storage.outbox.pending.mock.callCount() + 1
142
+ )
143
+
144
+ await outbox.open()
145
+ await cycle()
146
+ await cycle()
147
+
148
+ assert.strictEqual(emission.emit.mock.callCount(), 2)
149
+ })
150
+
151
+ it('should read nothing while it owns no slots', async () => {
152
+ atom.slots.mock.mockImplementation(() => null)
153
+
154
+ await outbox.open()
155
+ await cycle()
156
+
157
+ assert.strictEqual(storage.outbox.pending.mock.callCount(), 0)
158
+ })
159
+
160
+ function resetCalls(target = [assert, BATCH, page, cycle], seen = new Set()) {
161
+ if (target === null || typeof target !== 'object' || seen.has(target)) return
162
+
163
+ seen.add(target)
164
+
165
+ for (const value of Object.values(target))
166
+ if (typeof value === 'function' && value.mock !== undefined) value.mock.resetCalls()
167
+ else resetCalls(value, seen)
168
+ }
169
+
170
+ it('should read as soon as a claim arrives, rather than on its next cycle', async () => {
171
+ atom.slots.mock.mockImplementation(() => null)
172
+
173
+ await outbox.open()
174
+ await settled()
175
+
176
+ assert.strictEqual(storage.outbox.pending.mock.callCount(), 0, 'nothing is owned yet')
177
+
178
+ atom.slots.mock.mockImplementation(() => [0])
179
+
180
+ for (const listener of listeners) listener({ i: 0, n: 1 })
181
+
182
+ await settled()
183
+
184
+ assert.strictEqual(
185
+ storage.outbox.pending.mock.callCount(),
186
+ 1,
187
+ 'the lane is its own now, and the cycle is up to five seconds away'
188
+ )
189
+ })
190
+
191
+ it('should read the lanes it inherits when the group resizes', async () => {
192
+ await outbox.open()
193
+ await settled()
194
+
195
+ const before = storage.outbox.pending.mock.callCount()
196
+
197
+ for (const listener of listeners) listener({ i: 0, n: 2 })
198
+
199
+ await settled()
200
+
201
+ assert.strictEqual(storage.outbox.pending.mock.callCount(), before + 1)
202
+ })
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- const samples = {
1
+ export const samples = {
4
2
  simple: {
5
3
  query: {
6
4
  criteria: 'name==Eddie'
@@ -96,5 +94,3 @@ const samples = {
96
94
  }
97
95
  }
98
96
  }
99
-
100
- exports.samples = samples