@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.
- package/CHANGELOG.md +35 -0
- package/package.json +22 -12
- package/source/assignment.ts +27 -0
- package/source/call.ts +63 -0
- package/source/cascade.ts +32 -0
- package/source/component.ts +83 -0
- package/source/composition.ts +27 -0
- package/source/connector.ts +217 -0
- package/source/context.ts +68 -0
- package/source/contract/contract.ts +23 -0
- package/source/contract/index.ts +2 -0
- package/source/contract/reply.ts +56 -0
- package/source/contract/request.ts +140 -0
- package/source/contract/schemas.ts +39 -0
- package/source/discovery.ts +53 -0
- package/source/effect.ts +14 -0
- package/source/emission.ts +21 -0
- package/source/entities/changeset.ts +37 -0
- package/source/entities/entity.ts +137 -0
- package/source/entities/factory.ts +52 -0
- package/source/entities/index.ts +4 -0
- package/source/entities/newid.ts +9 -0
- package/source/entities/set.ts +27 -0
- package/source/event.ts +57 -0
- package/source/exceptions.ts +133 -0
- package/source/exposition.ts +33 -0
- package/source/guard.ts +16 -0
- package/source/index.ts +36 -0
- package/source/locator.ts +37 -0
- package/source/observation.ts +16 -0
- package/source/operation.ts +139 -0
- package/source/outbox/index.ts +1 -0
- package/source/outbox/outbox.ts +341 -0
- package/source/query/criteria.ts +90 -0
- package/source/query/options.ts +35 -0
- package/source/query.ts +67 -0
- package/source/receiver.ts +120 -0
- package/source/reflection.ts +20 -0
- package/source/remote.ts +20 -0
- package/source/state.ts +179 -0
- package/source/transition.ts +79 -0
- package/source/transmission.ts +38 -0
- package/source/types/atomicity.ts +63 -0
- package/source/types/bindings.ts +59 -0
- package/source/types/bridges.ts +54 -0
- package/source/types/extensions.ts +89 -0
- package/source/types/index.ts +14 -0
- package/{types/message.d.ts → source/types/message.ts} +2 -0
- package/source/types/operations.ts +31 -0
- package/source/types/outbox.ts +35 -0
- package/source/types/receiver.ts +10 -0
- package/source/types/request.ts +54 -0
- package/source/types/state.ts +9 -0
- package/source/types/storages.ts +134 -0
- package/source/unmanaged.ts +8 -0
- package/test/call.fixtures.js +10 -12
- package/test/call.test.js +42 -9
- package/test/component.fixtures.js +5 -8
- package/test/component.test.js +27 -10
- package/test/connector.fixtures.js +29 -13
- package/test/connector.test.js +128 -38
- package/test/context.fixtures.js +8 -12
- package/test/context.test.js +19 -8
- package/test/contract/conditions.test.js +15 -9
- package/test/contract/contract.fixtures.js +11 -16
- package/test/contract/request.test.js +130 -46
- package/test/discovery.test.js +71 -0
- package/test/emission.fixtures.js +6 -10
- package/test/emission.test.js +37 -12
- package/test/entities/entity.fixtures.js +11 -16
- package/test/entities/entity.test.js +105 -50
- package/test/entities/factory.fixtures.js +26 -13
- package/test/entities/factory.test.js +68 -17
- package/test/entities/set.fixtures.js +6 -8
- package/test/entities/set.test.js +6 -5
- package/test/event.fixtures.js +9 -14
- package/test/event.test.js +78 -34
- package/test/locator.test.js +32 -25
- package/test/outbox.test.js +202 -0
- package/test/query.fixtures.js +1 -5
- package/test/query.test.js +111 -14
- package/test/receiver.fixtures.js +9 -12
- package/test/receiver.test.js +80 -31
- package/test/reflection.test.js +9 -8
- package/test/state.fixtures.js +33 -31
- package/test/state.test.js +135 -19
- package/test/transmission.fixtures.js +4 -6
- package/test/transmission.test.js +47 -16
- package/transpiled/assignment.d.ts +6 -0
- package/transpiled/assignment.js +20 -0
- package/transpiled/assignment.js.map +1 -0
- package/transpiled/call.d.ts +10 -0
- package/transpiled/call.js +52 -0
- package/transpiled/call.js.map +1 -0
- package/transpiled/cascade.d.ts +7 -0
- package/transpiled/cascade.js +28 -0
- package/transpiled/cascade.js.map +1 -0
- package/transpiled/component.d.ts +15 -0
- package/transpiled/component.js +61 -0
- package/transpiled/component.js.map +1 -0
- package/transpiled/composition.d.ts +6 -0
- package/transpiled/composition.js +23 -0
- package/transpiled/composition.js.map +1 -0
- package/transpiled/connector.d.ts +47 -0
- package/transpiled/connector.js +185 -0
- package/transpiled/connector.js.map +1 -0
- package/transpiled/context.d.ts +18 -0
- package/transpiled/context.js +42 -0
- package/transpiled/context.js.map +1 -0
- package/transpiled/contract/contract.d.ts +10 -0
- package/transpiled/contract/contract.js +14 -0
- package/transpiled/contract/contract.js.map +1 -0
- package/transpiled/contract/index.d.ts +2 -0
- package/transpiled/contract/index.js +3 -0
- package/transpiled/contract/index.js.map +1 -0
- package/transpiled/contract/reply.d.ts +7 -0
- package/transpiled/contract/reply.js +48 -0
- package/transpiled/contract/reply.js.map +1 -0
- package/transpiled/contract/request.d.ts +29 -0
- package/transpiled/contract/request.js +103 -0
- package/transpiled/contract/request.js.map +1 -0
- package/transpiled/contract/schemas.d.ts +4 -0
- package/transpiled/contract/schemas.js +36 -0
- package/transpiled/contract/schemas.js.map +1 -0
- package/transpiled/discovery.d.ts +12 -0
- package/transpiled/discovery.js +34 -0
- package/transpiled/discovery.js.map +1 -0
- package/transpiled/effect.d.ts +5 -0
- package/transpiled/effect.js +11 -0
- package/transpiled/effect.js.map +1 -0
- package/transpiled/emission.d.ts +8 -0
- package/transpiled/emission.js +14 -0
- package/transpiled/emission.js.map +1 -0
- package/transpiled/entities/changeset.d.ts +10 -0
- package/transpiled/entities/changeset.js +27 -0
- package/transpiled/entities/changeset.js.map +1 -0
- package/transpiled/entities/entity.d.ts +19 -0
- package/transpiled/entities/entity.js +109 -0
- package/transpiled/entities/entity.js.map +1 -0
- package/transpiled/entities/factory.d.ts +22 -0
- package/transpiled/entities/factory.js +34 -0
- package/transpiled/entities/factory.js.map +1 -0
- package/transpiled/entities/index.d.ts +4 -0
- package/transpiled/entities/index.js +5 -0
- package/transpiled/entities/index.js.map +1 -0
- package/transpiled/entities/newid.d.ts +1 -0
- package/transpiled/entities/newid.js +7 -0
- package/transpiled/entities/newid.js.map +1 -0
- package/transpiled/entities/set.d.ts +10 -0
- package/transpiled/entities/set.js +19 -0
- package/transpiled/entities/set.js.map +1 -0
- package/transpiled/event.d.ts +14 -0
- package/transpiled/event.js +38 -0
- package/transpiled/event.js.map +1 -0
- package/transpiled/exceptions.d.ts +61 -0
- package/transpiled/exceptions.js +93 -0
- package/transpiled/exceptions.js.map +1 -0
- package/transpiled/exposition.d.ts +18 -0
- package/transpiled/exposition.js +18 -0
- package/transpiled/exposition.js.map +1 -0
- package/transpiled/guard.d.ts +7 -0
- package/transpiled/guard.js +12 -0
- package/transpiled/guard.js.map +1 -0
- package/transpiled/index.d.ts +35 -0
- package/transpiled/index.js +29 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/locator.d.ts +12 -0
- package/transpiled/locator.js +32 -0
- package/transpiled/locator.js.map +1 -0
- package/transpiled/observation.d.ts +5 -0
- package/transpiled/observation.js +12 -0
- package/transpiled/observation.js.map +1 -0
- package/transpiled/operation.d.ts +49 -0
- package/transpiled/operation.js +78 -0
- package/transpiled/operation.js.map +1 -0
- package/transpiled/outbox/index.d.ts +1 -0
- package/transpiled/outbox/index.js +2 -0
- package/transpiled/outbox/index.js.map +1 -0
- package/transpiled/outbox/outbox.d.ts +46 -0
- package/transpiled/outbox/outbox.js +268 -0
- package/transpiled/outbox/outbox.js.map +1 -0
- package/transpiled/query/criteria.d.ts +6 -0
- package/transpiled/query/criteria.js +74 -0
- package/transpiled/query/criteria.js.map +1 -0
- package/transpiled/query/options.d.ts +3 -0
- package/transpiled/query/options.js +27 -0
- package/transpiled/query/options.js.map +1 -0
- package/transpiled/query.d.ts +9 -0
- package/transpiled/query.js +57 -0
- package/transpiled/query.js.map +1 -0
- package/transpiled/receiver.d.ts +20 -0
- package/transpiled/receiver.js +92 -0
- package/transpiled/receiver.js.map +1 -0
- package/transpiled/reflection.d.ts +9 -0
- package/transpiled/reflection.js +13 -0
- package/transpiled/reflection.js.map +1 -0
- package/transpiled/remote.d.ts +10 -0
- package/transpiled/remote.js +12 -0
- package/transpiled/remote.js.map +1 -0
- package/transpiled/state.d.ts +33 -0
- package/transpiled/state.js +126 -0
- package/transpiled/state.js.map +1 -0
- package/transpiled/transition.d.ts +14 -0
- package/transpiled/transition.js +51 -0
- package/transpiled/transition.js.map +1 -0
- package/transpiled/transmission.d.ts +8 -0
- package/transpiled/transmission.js +30 -0
- package/transpiled/transmission.js.map +1 -0
- package/transpiled/types/atomicity.d.ts +56 -0
- package/transpiled/types/atomicity.js +2 -0
- package/transpiled/types/atomicity.js.map +1 -0
- package/transpiled/types/bindings.d.ts +42 -0
- package/transpiled/types/bindings.js +2 -0
- package/transpiled/types/bindings.js.map +1 -0
- package/transpiled/types/bridges.d.ts +38 -0
- package/transpiled/types/bridges.js +2 -0
- package/transpiled/types/bridges.js.map +1 -0
- package/transpiled/types/extensions.d.ts +62 -0
- package/transpiled/types/extensions.js +2 -0
- package/transpiled/types/extensions.js.map +1 -0
- package/transpiled/types/index.d.ts +13 -0
- package/transpiled/types/index.js +8 -0
- package/transpiled/types/index.js.map +1 -0
- package/transpiled/types/message.d.ts +5 -0
- package/transpiled/types/message.js +2 -0
- package/transpiled/types/message.js.map +1 -0
- package/transpiled/types/operations.d.ts +15 -0
- package/transpiled/types/operations.js +2 -0
- package/transpiled/types/operations.js.map +1 -0
- package/transpiled/types/outbox.d.ts +28 -0
- package/transpiled/types/outbox.js +2 -0
- package/transpiled/types/outbox.js.map +1 -0
- package/transpiled/types/receiver.d.ts +9 -0
- package/transpiled/types/receiver.js +2 -0
- package/transpiled/types/receiver.js.map +1 -0
- package/transpiled/types/request.d.ts +55 -0
- package/transpiled/types/request.js +2 -0
- package/transpiled/types/request.js.map +1 -0
- package/transpiled/types/state.d.ts +9 -0
- package/transpiled/types/state.js +2 -0
- package/transpiled/types/state.js.map +1 -0
- package/transpiled/types/storages.d.ts +111 -0
- package/transpiled/types/storages.js +2 -0
- package/transpiled/types/storages.js.map +1 -0
- package/transpiled/unmanaged.d.ts +5 -0
- package/transpiled/unmanaged.js +7 -0
- package/transpiled/unmanaged.js.map +1 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/src/assignment.js +0 -31
- package/src/call.js +0 -42
- package/src/cascade.js +0 -36
- package/src/component.js +0 -41
- package/src/composition.js +0 -25
- package/src/connector.js +0 -183
- package/src/context.js +0 -55
- package/src/contract/conditions.js +0 -21
- package/src/contract/index.js +0 -7
- package/src/contract/reply.js +0 -25
- package/src/contract/request.js +0 -67
- package/src/contract/schemas/error.yaml +0 -9
- package/src/contract/schemas/index.js +0 -7
- package/src/contract/schemas/query.yaml +0 -30
- package/src/discovery.js +0 -44
- package/src/emission.js +0 -23
- package/src/entities/changeset.js +0 -43
- package/src/entities/entity.js +0 -73
- package/src/entities/factory.js +0 -33
- package/src/entities/index.js +0 -5
- package/src/entities/set.js +0 -15
- package/src/event.js +0 -40
- package/src/exceptions.js +0 -100
- package/src/exposition.js +0 -27
- package/src/index.js +0 -47
- package/src/locator.js +0 -54
- package/src/observation.js +0 -20
- package/src/operation.js +0 -69
- package/src/query/criteria.js +0 -41
- package/src/query/options.js +0 -40
- package/src/query.js +0 -41
- package/src/receiver.js +0 -65
- package/src/reflection.js +0 -28
- package/src/remote.js +0 -17
- package/src/state.js +0 -99
- package/src/transition.js +0 -73
- package/src/transmission.js +0 -33
- package/types/bindings.d.ts +0 -40
- package/types/bridges.ts +0 -30
- package/types/component.d.ts +0 -9
- package/types/connector.d.ts +0 -18
- package/types/context.d.ts +0 -21
- package/types/entity.d.ts +0 -46
- package/types/event.d.ts +0 -11
- package/types/exception.d.ts +0 -4
- package/types/extensions.d.ts +0 -29
- package/types/index.ts +0 -14
- package/types/locator.d.ts +0 -13
- package/types/operations.d.ts +0 -2
- package/types/query.d.ts +0 -15
- package/types/receiver.d.ts +0 -12
- package/types/reflection.d.ts +0 -14
- package/types/request.d.ts +0 -23
- package/types/state.d.ts +0 -35
- package/types/storages.d.ts +0 -71
package/test/event.test.js
CHANGED
|
@@ -1,32 +1,44 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
5
|
+
import clone from 'clone-deep'
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
35
|
+
assert.ok(dependencies(event).some((one) => isDeepStrictEqual(one, fixtures.bridge)))
|
|
36
|
+
|
|
37
|
+
const bridgeless = new Event(fixtures.definition, fixtures.binding)
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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.
|
|
44
|
-
|
|
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
|
-
|
|
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(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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.
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/test/locator.test.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
32
|
-
|
|
32
|
+
assert.deepStrictEqual(locator.id, id)
|
|
33
|
+
assert.deepStrictEqual(locator.label, label)
|
|
33
34
|
})
|
|
34
35
|
|
|
35
36
|
it('should expose uppercase', () => {
|
|
36
|
-
|
|
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
|
-
|
|
44
|
+
assert.throws(() => new Locator(undefined, namespace), TypeError)
|
|
41
45
|
|
|
42
46
|
// noinspection JSCheckFunctionSignatures
|
|
43
|
-
|
|
47
|
+
assert.throws(() => new Locator(), TypeError)
|
|
44
48
|
})
|
|
45
49
|
|
|
46
50
|
it('should expose host', () => {
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
+
assert.deepStrictEqual(locator.id, name)
|
|
65
72
|
})
|
|
66
73
|
|
|
67
74
|
it('should expose label', () => {
|
|
68
|
-
|
|
75
|
+
assert.deepStrictEqual(locator.label, name.toLowerCase())
|
|
69
76
|
})
|
|
70
77
|
|
|
71
78
|
it('should expose uppercase', () => {
|
|
72
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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
|
+
})
|