@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
@@ -0,0 +1,71 @@
1
+ import { it, beforeEach, afterEach, mock } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { isDeepStrictEqual } from 'node:util'
4
+
5
+ import { console } from 'openspan'
6
+ import { Connector } from '../source/connector.js'
7
+ import { Discovery } from '../source/discovery.js'
8
+
9
+ class Lookup extends Connector {
10
+ invoke = mock.fn(async () => ({ operations: {} }))
11
+ }
12
+
13
+ /** @type {Lookup} */
14
+ let lookup
15
+
16
+ /** @type {Discovery} */
17
+ let discovery
18
+
19
+ /** @type {import('node:test').Mock<any>} */
20
+ let warn
21
+
22
+ const locator = { id: 'dummies.one' }
23
+
24
+ beforeEach(async () => {
25
+ mock.timers.enable()
26
+
27
+ warn = mock.method(console, 'warn', () => undefined)
28
+ lookup = new Lookup()
29
+ discovery = new Discovery(async () => lookup)
30
+
31
+ await discovery.connect()
32
+ })
33
+
34
+ afterEach(() => {
35
+ warn.mock.restore()
36
+ mock.timers.reset()
37
+ })
38
+
39
+ it('should not warn if a lookup is answered', async () => {
40
+ await discovery.lookup(locator)
41
+
42
+ mock.timers.tick(60_000)
43
+
44
+ assert.strictEqual(warn.mock.callCount(), 0)
45
+ })
46
+
47
+ it('should keep warning while a lookup is unanswered', async () => {
48
+ lookup.invoke.mock.mockImplementation(() => new Promise(() => undefined))
49
+
50
+ void discovery.lookup(locator)
51
+
52
+ // the interval is set after the lookup is resolved, so it has to exist
53
+ // before time is advanced
54
+ await new Promise((resolve) => process.nextTick(resolve))
55
+
56
+ // node:test moves the clock before running the callbacks it releases, so the
57
+ // elapsed time each warning reports is only right when time advances by steps
58
+ mock.timers.tick(5_000)
59
+ mock.timers.tick(5_000)
60
+
61
+ assert.strictEqual(warn.mock.callCount(), 2)
62
+
63
+ assert.ok(
64
+ ((call) =>
65
+ call.arguments.length === 2 &&
66
+ isDeepStrictEqual(call.arguments[0], 'Waiting for lookup response') &&
67
+ isDeepStrictEqual(call.arguments[1], { component: locator.id, waiting: 10 }))(
68
+ warn.mock.calls.at(-1) ?? { arguments: [] }
69
+ )
70
+ )
71
+ })
@@ -1,17 +1,13 @@
1
- 'use strict'
1
+ import { mock } from 'node:test'
2
2
 
3
- const { generate } = require('randomstring')
3
+ import { generate } from 'randomstring'
4
4
 
5
5
  // noinspection JSCheckFunctionSignatures
6
- const events = [0, 1, 2].map((index) => ({
7
- emit: jest.fn(async (state) => ({ ...state, event: index }))
6
+ export const events = [0, 1, 2].map((index) => ({
7
+ emit: mock.fn(async (state) => ({ ...state, event: index }))
8
8
  }))
9
9
 
10
- const event = {
10
+ export const event = {
11
11
  origin: { [generate()]: generate() },
12
- state: { [generate()]: generate() },
13
- changeset: { [generate()]: generate() }
12
+ state: { [generate()]: generate() }
14
13
  }
15
-
16
- exports.events = events
17
- exports.event = event
@@ -1,17 +1,26 @@
1
- 'use strict'
1
+ import { it, beforeEach, mock } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { isDeepStrictEqual } from 'node:util'
2
4
 
3
- jest.mock('../src/connector')
5
+ import clone from 'clone-deep'
4
6
 
5
- const clone = require('clone-deep')
7
+ import { Connector } from '../source/connector.js'
6
8
 
7
- const { Connector } = require('../src/connector')
8
- const { Emission } = require('../src/emission')
9
- const fixtures = require('./emission.fixtures')
9
+ // the fixtures are not connectors, so a dependency is recorded rather than linked
10
+ const depends = mock.method(Connector.prototype, 'depends', () => undefined)
11
+
12
+ const dependencies = (instance) =>
13
+ depends.mock.calls
14
+ .filter((call) => call.this === instance)
15
+ .map((call) => call.arguments[0])
16
+ import { Emission } from '../source/emission.js'
17
+ import * as fixtures from './emission.fixtures.js'
10
18
 
11
19
  let emission, event
12
20
 
13
21
  beforeEach(async () => {
14
- jest.clearAllMocks()
22
+ resetCalls()
23
+ depends.mock.resetCalls()
15
24
 
16
25
  emission = new Emission(fixtures.events)
17
26
  event = clone(fixtures.event)
@@ -20,16 +29,32 @@ beforeEach(async () => {
20
29
  })
21
30
 
22
31
  it('should depend on events', () => {
23
- expect(emission).toBeInstanceOf(Connector)
24
- expect(Connector.mock.instances[0].depends).toHaveBeenCalledWith(fixtures.events)
32
+ assert.ok(emission instanceof Connector)
33
+ assert.ok(dependencies(emission).some((one) => isDeepStrictEqual(one, fixtures.events)))
25
34
  })
26
35
 
27
36
  it('should emit events', async () => {
28
- expect.assertions(fixtures.events.length)
29
-
30
37
  await emission.emit(event)
31
38
 
32
39
  for (const evt of fixtures.events) {
33
- expect(evt.emit).toHaveBeenCalledWith(event)
40
+ assert.ok(
41
+ evt.emit.mock.calls.some(
42
+ (call) =>
43
+ call.arguments.length === 1 && isDeepStrictEqual(call.arguments[0], event)
44
+ )
45
+ )
34
46
  }
35
47
  })
48
+
49
+ function resetCalls(
50
+ target = [assert, clone, depends, dependencies, fixtures],
51
+ seen = new Set()
52
+ ) {
53
+ if (target === null || typeof target !== 'object' || seen.has(target)) return
54
+
55
+ seen.add(target)
56
+
57
+ for (const value of Object.values(target))
58
+ if (typeof value === 'function' && value.mock !== undefined) value.mock.resetCalls()
59
+ else resetCalls(value, seen)
60
+ }
@@ -1,29 +1,24 @@
1
- 'use strict'
1
+ import { mock } from 'node:test'
2
2
 
3
- const { generate } = require('randomstring')
3
+ import { generate } from 'randomstring'
4
4
 
5
5
  // noinspection JSCheckFunctionSignatures
6
- const schema = {
7
- fit: jest.fn((object) =>
8
- (object.fail ? { [generate()]: generate() } : null)),
6
+ export const schema = {
7
+ fit: mock.fn((object) => (object.fail ? { [generate()]: generate() } : null)),
9
8
 
10
- defaults: jest.fn(() => ({ [generate()]: generate() }))
9
+ defaults: mock.fn(() => ({ [generate()]: generate() }))
11
10
  }
12
11
 
13
- const state = () => ({
12
+ export const state = () => ({
14
13
  id: generate(),
15
14
  foo: generate(),
16
- _created: generate(),
17
- _updated: generate(),
18
- _deleted: generate(),
19
- _version: 0
15
+ CREATED: generate(),
16
+ UPDATED: generate(),
17
+ DELETED: generate(),
18
+ VERSION: 0
20
19
  })
21
20
 
22
- const failed = () => ({
21
+ export const failed = () => ({
23
22
  ...state(),
24
23
  fail: true
25
24
  })
26
-
27
- exports.schema = schema
28
- exports.state = state
29
- exports.failed = failed
@@ -1,52 +1,118 @@
1
- 'use strict'
1
+ import { describe, it, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const { Entity } = require('../../src/entities/entity')
4
- const fixtures = require('./entity.fixtures')
4
+ import { Entity } from '../../source/entities/entity.js'
5
+ import * as fixtures from './entity.fixtures.js'
6
+
7
+ const BLANK = {}
5
8
 
6
9
  beforeEach(() => {
7
- jest.clearAllMocks()
10
+ resetCalls()
8
11
  })
9
12
 
10
- describe('new', () => {
11
- it('should throw on schema error', () => {
12
- const entity = new Entity(fixtures.schema)
13
+ describe('argument', () => {
14
+ it('should set state', () => {
15
+ const state = fixtures.state()
16
+ const entity = new Entity(fixtures.schema, BLANK, state)
13
17
 
14
- expect(() => entity.set(fixtures.failed())).toThrow()
18
+ assert.deepStrictEqual(entity.get(), state)
15
19
  })
16
20
 
17
- it('should provide state', () => {
18
- const entity = new Entity(fixtures.schema)
19
- const state = fixtures.state()
21
+ it('should snapshot the record it may commit', () => {
22
+ const record = fixtures.state()
23
+ const entity = new Entity(fixtures.schema, BLANK, record)
20
24
 
21
- entity.set(state)
25
+ assert.notStrictEqual(entity.get(), record)
26
+ assert.strictEqual(entity.event().origin, record)
27
+ })
28
+ })
29
+
30
+ describe('blank', () => {
31
+ it('should write what the component declares a record starts as', () => {
32
+ const entity = new Entity(fixtures.schema, { foo: 'declared' })
33
+ const state = entity.get()
34
+
35
+ assert.strictEqual(state.foo, 'declared')
36
+ assert.strictEqual(state.VERSION, 0)
37
+ assert.strictEqual(state.DELETED, null)
38
+ assert.strictEqual(typeof state.id, 'string')
39
+ assert.strictEqual(entity.event().origin, null)
40
+ })
41
+
42
+ it('should not validate it', () => {
43
+ const entity = new Entity(fixtures.schema, { fail: true })
44
+
45
+ assert.strictEqual(entity.get().fail, true)
46
+ assert.strictEqual(fixtures.schema.fit.mock.callCount(), 0)
47
+ })
48
+
49
+ it('should give every record its own copy', () => {
50
+ const blank = { foo: { bar: 'nested' } }
51
+ const one = new Entity(fixtures.schema, blank)
52
+ const other = new Entity(fixtures.schema, blank)
22
53
 
23
- expect(entity.get()).toEqual(state)
54
+ assert.notStrictEqual(one.get().foo, other.get().foo)
55
+ assert.notStrictEqual(one.get().foo, blank.foo)
24
56
  })
25
57
  })
26
58
 
27
- describe('argument', () => {
28
- it('should provide initial state if no argument passed', () => {
29
- const entity = new Entity(fixtures.schema)
30
- const defaults = fixtures.schema.defaults.mock.results[0].value
31
- const expected = {
32
- ...defaults,
33
- _version: 0
34
- }
35
-
36
- expect(entity.get()).toStrictEqual(expect.objectContaining(expected))
59
+ describe('read-only', () => {
60
+ it('should take the record as it came', () => {
61
+ const record = fixtures.state()
62
+ const entity = new Entity(fixtures.schema, BLANK, record, undefined, false)
63
+
64
+ // no pre-image to diff against, hence no copy of it
65
+ assert.strictEqual(entity.get(), record)
37
66
  })
38
67
 
39
- it('should set state', () => {
40
- const state = fixtures.state()
41
- const entity = new Entity(fixtures.schema, state)
68
+ it('should still report a tombstone', () => {
69
+ const record = { ...fixtures.state(), DELETED: Date.now() }
70
+ const entity = new Entity(fixtures.schema, BLANK, record, undefined, false)
71
+
72
+ assert.strictEqual(entity.deleted, true)
73
+ })
74
+
75
+ it('should refuse to be modified', () => {
76
+ const entity = new Entity(fixtures.schema, BLANK, fixtures.state(), undefined, false)
77
+
78
+ assert.throws(
79
+ () => entity.set(entity.get()),
80
+ (error) => /read-only/.test(error.message)
81
+ )
82
+ })
83
+ })
84
+
85
+ describe('tombstone', () => {
86
+ it('should lift tombstone when transition leaves DELETED untouched', () => {
87
+ const origin = fixtures.state()
88
+ const entity = new Entity(fixtures.schema, BLANK, origin)
89
+ const state = entity.get()
90
+
91
+ state.foo = 'revived'
92
+ entity.set(state)
93
+
94
+ assert.strictEqual(entity.get().DELETED, null)
95
+ assert.strictEqual(entity.deleted, false)
96
+ assert.strictEqual(entity.event().state.DELETED, null)
97
+ })
42
98
 
43
- expect(entity.get()).toEqual(state)
99
+ it('should keep tombstone written by transition', () => {
100
+ const origin = { ...fixtures.state(), DELETED: null }
101
+ const entity = new Entity(fixtures.schema, BLANK, origin)
102
+ const state = entity.get()
103
+ const timestamp = Date.now()
104
+
105
+ state.DELETED = timestamp
106
+ entity.set(state)
107
+
108
+ assert.strictEqual(entity.get().DELETED, timestamp)
109
+ assert.strictEqual(entity.deleted, true)
44
110
  })
45
111
  })
46
112
 
47
113
  it('should provide event', () => {
48
114
  const origin = fixtures.state()
49
- const entity = new Entity(fixtures.schema, origin)
115
+ const entity = new Entity(fixtures.schema, BLANK, origin)
50
116
  const state = entity.get()
51
117
 
52
118
  state.foo = 'new value'
@@ -54,29 +120,18 @@ it('should provide event', () => {
54
120
 
55
121
  const event = entity.event()
56
122
 
57
- expect(event).toEqual(expect.objectContaining({
58
- state,
59
- origin,
60
- changeset: expect.objectContaining({
61
- foo: 'new value',
62
- _version: 1
63
- })
64
- }))
123
+ assert.partialDeepStrictEqual(event, { state, origin })
124
+ assert.strictEqual(event.state.foo, 'new value')
125
+ assert.strictEqual(event.state.VERSION, 1)
126
+ assert.notStrictEqual(event.origin.foo, 'new value')
65
127
  })
66
128
 
67
- it('should define `id` as readonly', async () => {
68
- const origin = fixtures.state()
69
- const entity = new Entity(fixtures.schema, origin)
70
- const state = entity.get()
71
-
72
- expect(() => (state.id = 1)).toThrow('assign to read only property')
73
- })
129
+ function resetCalls(target = [assert, fixtures], seen = new Set()) {
130
+ if (target === null || typeof target !== 'object' || seen.has(target)) return
74
131
 
75
- it('should seal id', async () => {
76
- const origin = fixtures.state()
77
- const entity = new Entity(fixtures.schema, origin)
78
- const state = entity.get()
79
- const redefine = () => Object.defineProperty(state, 'id', { writable: true })
132
+ seen.add(target)
80
133
 
81
- expect(redefine).toThrow('redefine property')
82
- })
134
+ for (const value of Object.values(target))
135
+ if (typeof value === 'function' && value.mock !== undefined) value.mock.resetCalls()
136
+ else resetCalls(value, seen)
137
+ }
@@ -1,18 +1,31 @@
1
- 'use strict'
1
+ import { mock } from 'node:test'
2
2
 
3
- const randomstring = require('randomstring')
3
+ import randomstring from 'randomstring'
4
4
 
5
- const schema = { [randomstring.generate()]: randomstring.generate() }
6
- const storage = { id: jest.fn(() => randomstring.generate()) }
5
+ const schemas = {
6
+ entity: { [randomstring.generate()]: randomstring.generate() },
7
+ changeset: { [randomstring.generate()]: randomstring.generate() }
8
+ }
9
+
10
+ const blank = { [randomstring.generate()]: randomstring.generate() }
11
+ const storage = { id: mock.fn(() => randomstring.generate()) }
7
12
  const entity = { [randomstring.generate()]: randomstring.generate() }
8
- const set = Array.from(Array(5))
9
- .map((_, index) => ({ id: index, [randomstring.generate()]: randomstring.generate() }))
13
+ const set = Array.from(Array(5)).map((_, index) => ({
14
+ id: index,
15
+ [randomstring.generate()]: randomstring.generate()
16
+ }))
17
+
18
+ // node:test records a mock's calls but not the instances it constructed
19
+ const entities = []
20
+
21
+ const Entity = mock.fn(function () {
22
+ this.id = randomstring.generate()
23
+ entities.push(this)
24
+ })
25
+
26
+ const EntitySet = mock.fn(function () {})
10
27
 
11
- const Entity = jest.fn().mockImplementation(function () { this.id = randomstring.generate() })
12
- const EntitySet = jest.fn().mockImplementation(function () {})
28
+ // named `mock` for its consumers, which is what node:test calls its own tracker
29
+ const mocks = { Entity, EntitySet }
13
30
 
14
- exports.schema = schema
15
- exports.storage = storage
16
- exports.entity = entity
17
- exports.set = set
18
- exports.mock = { Entity, EntitySet }
31
+ export { schemas, blank, storage, entity, set, entities, mocks as mock }
@@ -1,48 +1,99 @@
1
- 'use strict'
1
+ import { it, beforeEach, mock as mocking } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { isDeepStrictEqual } from 'node:util'
2
4
 
3
- const { generate } = require('randomstring')
5
+ import { generate } from 'randomstring'
4
6
 
5
- const fixtures = require('./factory.fixtures')
7
+ import * as fixtures from './factory.fixtures.js'
6
8
  const mock = fixtures.mock
7
9
 
8
- jest.mock('../../src/entities/entity', () => ({ Entity: mock.Entity }))
9
- jest.mock('../../src/entities/set', () => ({ EntitySet: mock.EntitySet }))
10
+ mocking.module('../../source/entities/entity.js', {
11
+ namedExports: { Entity: mock.Entity }
12
+ })
13
+ mocking.module('../../source/entities/set.js', {
14
+ namedExports: { EntitySet: mock.EntitySet }
15
+ })
10
16
 
11
- const { Factory } = require('../../src/entities/factory')
17
+ const { Factory } = await import('../../source/entities/factory.js')
12
18
 
13
19
  let factory
14
20
 
15
21
  beforeEach(async () => {
16
- jest.clearAllMocks()
22
+ resetCalls()
23
+ fixtures.entities.length = 0
17
24
 
18
- factory = new Factory(fixtures.schema, () => fixtures.storage.id())
25
+ factory = new Factory(fixtures.schemas, fixtures.blank, () => fixtures.storage.id())
19
26
  })
20
27
 
21
28
  it('should create initial', () => {
22
29
  const id = generate()
23
30
  const initial = factory.init(id)
24
31
 
25
- expect(initial).toBeInstanceOf(mock.Entity)
26
- expect(initial.constructor).toHaveBeenCalledWith(fixtures.schema, id)
32
+ assert.ok(initial instanceof mock.Entity)
33
+ assert.ok(
34
+ mock.Entity.mock.calls.some(
35
+ (call) =>
36
+ call.arguments.length === 4 &&
37
+ isDeepStrictEqual(call.arguments[0], fixtures.schemas.entity) &&
38
+ isDeepStrictEqual(call.arguments[1], fixtures.blank) &&
39
+ isDeepStrictEqual(call.arguments[2], id) &&
40
+ typeof call.arguments[3] === 'function'
41
+ )
42
+ )
27
43
  })
28
44
 
29
45
  it('should create instance', () => {
30
46
  const object = factory.object(fixtures.entity)
31
47
 
32
- expect(object).toBeInstanceOf(mock.Entity)
33
- expect(object.constructor).toHaveBeenCalledWith(fixtures.schema, fixtures.entity)
48
+ assert.ok(object instanceof mock.Entity)
49
+ assert.ok(
50
+ mock.Entity.mock.calls.some(
51
+ (call) =>
52
+ call.arguments.length === 5 &&
53
+ isDeepStrictEqual(call.arguments[0], fixtures.schemas.entity) &&
54
+ isDeepStrictEqual(call.arguments[1], fixtures.blank) &&
55
+ isDeepStrictEqual(call.arguments[2], fixtures.entity) &&
56
+ typeof call.arguments[3] === 'function' &&
57
+ isDeepStrictEqual(call.arguments[4], true)
58
+ )
59
+ )
34
60
  })
35
61
 
36
62
  it('should create set', () => {
37
63
  const objects = factory.objects(fixtures.set)
38
64
 
39
- expect(objects).toBeInstanceOf(mock.EntitySet)
65
+ assert.ok(objects instanceof mock.EntitySet)
40
66
 
41
67
  const instances = fixtures.set.map((entity, index) => {
42
- expect(mock.Entity).toHaveBeenNthCalledWith(index + 1, fixtures.schema, entity)
43
-
44
- return mock.Entity.mock.instances[index]
68
+ assert.ok(
69
+ ((call) =>
70
+ call.arguments.length === 5 &&
71
+ isDeepStrictEqual(call.arguments[0], fixtures.schemas.entity) &&
72
+ isDeepStrictEqual(call.arguments[1], fixtures.blank) &&
73
+ isDeepStrictEqual(call.arguments[2], entity) &&
74
+ typeof call.arguments[3] === 'function' &&
75
+ isDeepStrictEqual(call.arguments[4], true))(
76
+ mock.Entity.mock.calls[index + 1 - 1] ?? { arguments: [] }
77
+ )
78
+ )
79
+
80
+ return fixtures.entities[index]
45
81
  })
46
82
 
47
- expect(objects.constructor).toHaveBeenCalledWith(instances)
83
+ assert.ok(
84
+ mock.EntitySet.mock.calls.some(
85
+ (call) =>
86
+ call.arguments.length === 1 && isDeepStrictEqual(call.arguments[0], instances)
87
+ )
88
+ )
48
89
  })
90
+
91
+ function resetCalls(target = [assert, fixtures, mock], seen = new Set()) {
92
+ if (target === null || typeof target !== 'object' || seen.has(target)) return
93
+
94
+ seen.add(target)
95
+
96
+ for (const value of Object.values(target))
97
+ if (typeof value === 'function' && value.mock !== undefined) value.mock.resetCalls()
98
+ else resetCalls(value, seen)
99
+ }
@@ -1,11 +1,9 @@
1
- 'use strict'
1
+ import { mock } from 'node:test'
2
2
 
3
- const { generate } = require('randomstring')
3
+ import { generate } from 'randomstring'
4
4
 
5
- const set = [
6
- { get: jest.fn(() => ({ [generate()]: generate() })) },
7
- { get: jest.fn(() => ({ [generate()]: generate() })) },
8
- { get: jest.fn(() => ({ [generate()]: generate() })) }
5
+ export const set = [
6
+ { get: mock.fn(() => ({ [generate()]: generate() })) },
7
+ { get: mock.fn(() => ({ [generate()]: generate() })) },
8
+ { get: mock.fn(() => ({ [generate()]: generate() })) }
9
9
  ]
10
-
11
- exports.set = set
@@ -1,12 +1,13 @@
1
- 'use strict'
1
+ import { it } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const { EntitySet } = require('../../src/entities/set')
4
- const fixtures = require('./set.fixtures')
4
+ import { EntitySet } from '../../source/entities/set.js'
5
+ import * as fixtures from './set.fixtures.js'
5
6
 
6
7
  it('should provide state', () => {
7
8
  const set = new EntitySet(fixtures.set)
8
9
  const state = set.get()
9
- const expected = fixtures.set.map((entity) => entity.get.mock.results[0].value)
10
+ const expected = fixtures.set.map((entity) => entity.get.mock.calls[0].result)
10
11
 
11
- expect(state).toStrictEqual(expected)
12
+ assert.deepStrictEqual(state, expected)
12
13
  })
@@ -1,29 +1,24 @@
1
- 'use strict'
1
+ import { mock } from 'node:test'
2
2
 
3
- const { generate } = require('randomstring')
3
+ import { generate } from 'randomstring'
4
4
 
5
- const definition = {
5
+ export const definition = {
6
6
  conditioned: true,
7
7
  subjective: true
8
8
  }
9
9
 
10
10
  // noinspection JSCheckFunctionSignatures
11
- const bridge = {
12
- condition: jest.fn(async (origin) => !origin.falsy),
13
- payload: jest.fn(async () => ({ [generate()]: generate() }))
11
+ export const bridge = {
12
+ condition: mock.fn(async (origin) => !origin.falsy),
13
+ payload: mock.fn(async () => ({ [generate()]: generate() }))
14
14
  }
15
15
 
16
- const binding = {
17
- emit: jest.fn()
16
+ export const binding = {
17
+ emit: mock.fn()
18
18
  }
19
19
 
20
- const event = {
20
+ export const event = {
21
21
  origin: { [generate()]: generate() },
22
22
  state: { [generate()]: generate() },
23
23
  changeset: { [generate()]: generate() }
24
24
  }
25
-
26
- exports.bridge = bridge
27
- exports.binding = binding
28
- exports.definition = definition
29
- exports.event = event