@boxxy/dispatch 0.1.0 → 0.1.3
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/README.md +4 -1
- package/dist/attempts/attempt-machine.d.ts +804 -0
- package/dist/attempts/attempt-moves.d.ts +2 -0
- package/dist/attempts/bind-attempt-session.d.ts +14 -0
- package/dist/attempts/build-attempt-change.d.ts +73 -0
- package/dist/attempts/build-attempt-fact.d.ts +3 -0
- package/dist/attempts/end-item-attempts.d.ts +18 -0
- package/dist/attempts/is-same-guard.d.ts +5 -0
- package/dist/attempts/pick-call-attempt.d.ts +10 -0
- package/dist/attempts/stop-stray-session.d.ts +11 -0
- package/dist/attempts/types.d.ts +142 -0
- package/dist/attempts/write-attempt-move.d.ts +7 -0
- package/dist/attempts/write-session-bind.d.ts +3 -0
- package/dist/claims/build-snapshot-hash.d.ts +2 -0
- package/dist/claims/claim-next.d.ts +6 -0
- package/dist/claims/pick-kept-session.d.ts +9 -0
- package/dist/claims/request-wake.d.ts +3 -0
- package/dist/claims/resume-claim.d.ts +3 -0
- package/dist/claims/start-claim.d.ts +3 -0
- package/dist/claims/types.d.ts +44 -0
- package/dist/closeouts/build-settle-plans.d.ts +17 -0
- package/dist/closeouts/close-out-attempt.d.ts +3 -0
- package/dist/closeouts/effect-handlers.d.ts +2 -0
- package/dist/closeouts/pick-settlement.d.ts +14 -0
- package/dist/closeouts/settle-attempt.d.ts +3 -0
- package/dist/closeouts/types.d.ts +50 -0
- package/dist/index.d.ts +38 -0
- package/dist/notices/constants.d.ts +4 -0
- package/dist/notices/create-null-notifier.d.ts +2 -0
- package/dist/notices/send-notices.d.ts +2 -0
- package/dist/notices/types.d.ts +11 -0
- package/dist/outbox/build-backoff-ms.d.ts +2 -0
- package/dist/outbox/build-initial-sender-state.d.ts +2 -0
- package/dist/outbox/build-outbox-status.d.ts +15 -0
- package/dist/outbox/constants.d.ts +2 -0
- package/dist/outbox/measure-wire-bytes.d.ts +2 -0
- package/dist/outbox/pick-sender-move.d.ts +10 -0
- package/dist/outbox/run-sender-once.d.ts +2 -0
- package/dist/outbox/take-batch.d.ts +3 -0
- package/dist/outbox/types.d.ts +61 -0
- package/dist/pull-requests/build-link-change.d.ts +11 -0
- package/dist/pull-requests/build-state-fact.d.ts +8 -0
- package/dist/pull-requests/collect-attempt-trailers.d.ts +2 -0
- package/dist/pull-requests/collect-seen-state.d.ts +7 -0
- package/dist/pull-requests/constants.d.ts +3 -0
- package/dist/pull-requests/link-pull-request.d.ts +2 -0
- package/dist/pull-requests/parse-pull-request-url.d.ts +2 -0
- package/dist/pull-requests/types.d.ts +37 -0
- package/dist/pull-requests/watch-pull-requests.d.ts +2 -0
- package/dist/readings/act-on-reading.d.ts +3 -0
- package/dist/readings/find-waiting-since.d.ts +9 -0
- package/dist/readings/pick-reading-action.d.ts +2 -0
- package/dist/readings/types.d.ts +79 -0
- package/dist/sessions/build-keep-writes.d.ts +9 -0
- package/dist/sessions/build-kept-clear.d.ts +2 -0
- package/dist/sessions/forget-sessions.d.ts +2 -0
- package/dist/sessions/is-kept-due.d.ts +2 -0
- package/dist/sessions/resume-kept-session.d.ts +6 -0
- package/dist/sessions/types.d.ts +19 -0
- package/dist/test-utils/apply-memory-change.d.ts +3 -0
- package/dist/test-utils/build-memory-reader.d.ts +3 -0
- package/dist/test-utils/build-mock-bind-change.d.ts +7 -0
- package/dist/test-utils/build-mock-claim-change.d.ts +2 -0
- package/dist/test-utils/build-mock-file-item-change.d.ts +2 -0
- package/dist/test-utils/build-mock-lever-change.d.ts +2 -0
- package/dist/test-utils/build-mock-new-id.d.ts +2 -0
- package/dist/test-utils/build-mock-note-change.d.ts +2 -0
- package/dist/test-utils/build-mock-pause-change.d.ts +2 -0
- package/dist/test-utils/build-mock-pull-request-change.d.ts +8 -0
- package/dist/test-utils/build-mock-relation-change.d.ts +7 -0
- package/dist/test-utils/build-mock-running-reading.d.ts +6 -0
- package/dist/test-utils/build-mock-settle-change.d.ts +7 -0
- package/dist/test-utils/build-mock-start-change.d.ts +2 -0
- package/dist/test-utils/claim-store-attempt-reader-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-claim-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-close-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-contract.d.ts +2 -0
- package/dist/test-utils/claim-store-copy-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-exclusive-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-guard-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-item-reader-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-outbox-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-race-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-rollback-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-seat-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-session-cases.d.ts +2 -0
- package/dist/test-utils/collect-memory-limit-fills.d.ts +3 -0
- package/dist/test-utils/compare-text.d.ts +1 -0
- package/dist/test-utils/create-fake-backoff.d.ts +2 -0
- package/dist/test-utils/create-fake-clock.d.ts +5 -0
- package/dist/test-utils/create-fake-fact-sink.d.ts +8 -0
- package/dist/test-utils/create-fake-forge.d.ts +17 -0
- package/dist/test-utils/create-fake-worker.d.ts +42 -0
- package/dist/test-utils/create-memory-claim-store.d.ts +2 -0
- package/dist/test-utils/create-test-dispatch.d.ts +46 -0
- package/dist/test-utils/forge-contract.d.ts +15 -0
- package/dist/test-utils/is-same-held-seat.d.ts +2 -0
- package/dist/test-utils/types.d.ts +19 -0
- package/dist/test-utils/worker-contract.d.ts +14 -0
- package/dist/timeouts.d.ts +2 -0
- package/dist/types.d.ts +13 -0
- package/package.json +15 -2
- package/src/attempts/attempt-machine.ts +147 -0
- package/src/attempts/attempt-moves.ts +218 -0
- package/src/attempts/bind-attempt-session.ts +61 -0
- package/src/attempts/build-attempt-change.ts +540 -0
- package/src/attempts/build-attempt-fact.ts +135 -0
- package/src/attempts/end-item-attempts.ts +95 -0
- package/src/attempts/is-same-guard.ts +22 -0
- package/src/attempts/pick-call-attempt.ts +32 -0
- package/src/attempts/stop-stray-session.ts +48 -0
- package/src/attempts/types.ts +231 -0
- package/src/attempts/write-attempt-move.ts +41 -0
- package/src/attempts/write-session-bind.ts +47 -0
- package/src/claims/build-snapshot-hash.ts +9 -0
- package/src/claims/claim-next.ts +306 -0
- package/src/claims/pick-kept-session.ts +41 -0
- package/src/claims/request-wake.ts +17 -0
- package/src/claims/resume-claim.ts +56 -0
- package/src/claims/start-claim.ts +99 -0
- package/src/claims/types.ts +67 -0
- package/src/closeouts/build-settle-plans.ts +173 -0
- package/src/closeouts/close-out-attempt.ts +156 -0
- package/src/closeouts/effect-handlers.ts +232 -0
- package/src/closeouts/pick-settlement.ts +80 -0
- package/src/closeouts/settle-attempt.ts +161 -0
- package/src/closeouts/types.ts +87 -0
- package/src/index.ts +61 -0
- package/src/notices/constants.ts +7 -0
- package/src/notices/create-null-notifier.ts +6 -0
- package/src/notices/send-notices.ts +41 -0
- package/src/notices/types.ts +14 -0
- package/src/outbox/build-backoff-ms.ts +14 -0
- package/src/outbox/build-initial-sender-state.ts +14 -0
- package/src/outbox/build-outbox-status.ts +43 -0
- package/src/outbox/constants.ts +15 -0
- package/src/outbox/measure-wire-bytes.ts +31 -0
- package/src/outbox/pick-sender-move.ts +37 -0
- package/src/outbox/run-sender-once.ts +174 -0
- package/src/outbox/take-batch.ts +35 -0
- package/src/outbox/types.ts +76 -0
- package/src/pull-requests/build-link-change.ts +95 -0
- package/src/pull-requests/build-state-fact.ts +39 -0
- package/src/pull-requests/collect-attempt-trailers.ts +20 -0
- package/src/pull-requests/collect-seen-state.ts +19 -0
- package/src/pull-requests/constants.ts +5 -0
- package/src/pull-requests/link-pull-request.ts +119 -0
- package/src/pull-requests/parse-pull-request-url.ts +30 -0
- package/src/pull-requests/types.ts +63 -0
- package/src/pull-requests/watch-pull-requests.ts +308 -0
- package/src/readings/act-on-reading.ts +318 -0
- package/src/readings/find-waiting-since.ts +46 -0
- package/src/readings/pick-reading-action.ts +315 -0
- package/src/readings/types.ts +98 -0
- package/src/sessions/build-keep-writes.ts +55 -0
- package/src/sessions/build-kept-clear.ts +12 -0
- package/src/sessions/forget-sessions.ts +106 -0
- package/src/sessions/is-kept-due.ts +27 -0
- package/src/sessions/resume-kept-session.ts +25 -0
- package/src/sessions/types.ts +39 -0
- package/src/test-utils/apply-memory-change.ts +252 -0
- package/src/test-utils/build-memory-reader.ts +364 -0
- package/src/test-utils/build-mock-bind-change.ts +43 -0
- package/src/test-utils/build-mock-claim-change.ts +52 -0
- package/src/test-utils/build-mock-file-item-change.ts +20 -0
- package/src/test-utils/build-mock-lever-change.ts +18 -0
- package/src/test-utils/build-mock-new-id.ts +12 -0
- package/src/test-utils/build-mock-note-change.ts +18 -0
- package/src/test-utils/build-mock-pause-change.ts +19 -0
- package/src/test-utils/build-mock-pull-request-change.ts +63 -0
- package/src/test-utils/build-mock-relation-change.ts +40 -0
- package/src/test-utils/build-mock-running-reading.ts +19 -0
- package/src/test-utils/build-mock-settle-change.ts +70 -0
- package/src/test-utils/build-mock-start-change.ts +38 -0
- package/src/test-utils/claim-store-attempt-reader-cases.ts +357 -0
- package/src/test-utils/claim-store-claim-cases.ts +314 -0
- package/src/test-utils/claim-store-close-cases.ts +95 -0
- package/src/test-utils/claim-store-contract.ts +28 -0
- package/src/test-utils/claim-store-copy-cases.ts +100 -0
- package/src/test-utils/claim-store-exclusive-cases.ts +243 -0
- package/src/test-utils/claim-store-guard-cases.ts +383 -0
- package/src/test-utils/claim-store-item-reader-cases.ts +471 -0
- package/src/test-utils/claim-store-outbox-cases.ts +281 -0
- package/src/test-utils/claim-store-race-cases.ts +150 -0
- package/src/test-utils/claim-store-rollback-cases.ts +175 -0
- package/src/test-utils/claim-store-seat-cases.ts +270 -0
- package/src/test-utils/claim-store-session-cases.ts +289 -0
- package/src/test-utils/collect-memory-limit-fills.ts +15 -0
- package/src/test-utils/compare-text.ts +8 -0
- package/src/test-utils/create-fake-backoff.ts +16 -0
- package/src/test-utils/create-fake-clock.ts +20 -0
- package/src/test-utils/create-fake-fact-sink.ts +74 -0
- package/src/test-utils/create-fake-forge.ts +117 -0
- package/src/test-utils/create-fake-worker.ts +303 -0
- package/src/test-utils/create-memory-claim-store.ts +163 -0
- package/src/test-utils/create-test-dispatch.ts +597 -0
- package/src/test-utils/forge-contract.ts +171 -0
- package/src/test-utils/is-same-held-seat.ts +11 -0
- package/src/test-utils/types.ts +23 -0
- package/src/test-utils/worker-contract.ts +192 -0
- package/src/timeouts.ts +17 -0
- package/src/types.ts +32 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { brandSchemas } from '@boxxy/model';
|
|
2
|
+
import { buildMockItemRules } from '@boxxy/model/test-utils/build-mock-item-rules';
|
|
3
|
+
import { buildMockV1AttemptRow } from '@boxxy/model/test-utils/build-mock-v1-attempt-row';
|
|
4
|
+
import { buildMockV1ItemRow } from '@boxxy/model/test-utils/build-mock-v1-item-row';
|
|
5
|
+
import { buildMockV1LeverRow } from '@boxxy/model/test-utils/build-mock-v1-lever-row';
|
|
6
|
+
import { buildMockClaimChange } from './build-mock-claim-change.ts';
|
|
7
|
+
import { buildMockFileItemChange } from './build-mock-file-item-change.ts';
|
|
8
|
+
import { buildMockLeverChange } from './build-mock-lever-change.ts';
|
|
9
|
+
import { buildMockNoteChange } from './build-mock-note-change.ts';
|
|
10
|
+
import { buildMockPauseChange } from './build-mock-pause-change.ts';
|
|
11
|
+
import type { ClaimStoreCase } from './types.ts';
|
|
12
|
+
|
|
13
|
+
export const claimStoreClaimCases: readonly ClaimStoreCase[] = [
|
|
14
|
+
[
|
|
15
|
+
'#applyChange returns lease-held for a second live lease on one item phase',
|
|
16
|
+
async (store) => {
|
|
17
|
+
const item = buildMockV1ItemRow();
|
|
18
|
+
const first = buildMockV1AttemptRow({ item: item.id, state: 'starting', leaseGen: 1 });
|
|
19
|
+
|
|
20
|
+
const second = buildMockV1AttemptRow({
|
|
21
|
+
item: item.id,
|
|
22
|
+
state: 'starting',
|
|
23
|
+
leaseGen: 2,
|
|
24
|
+
number: 2,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await store.exclusive(async (transaction) => {
|
|
28
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
29
|
+
await transaction.applyChange(buildMockClaimChange(first));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const result = await store.exclusive((transaction) =>
|
|
33
|
+
transaction.applyChange(buildMockClaimChange(second)),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return { received: result, expected: { kind: 'lease-held' } };
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
'#applyChange writes no row and takes no seat for a change that returns lease-held',
|
|
41
|
+
async (store) => {
|
|
42
|
+
const workers = brandSchemas.limitName.parse('workers');
|
|
43
|
+
const item = buildMockV1ItemRow();
|
|
44
|
+
const first = buildMockV1AttemptRow({ item: item.id, state: 'starting', leaseGen: 1 });
|
|
45
|
+
|
|
46
|
+
const second = buildMockV1AttemptRow({
|
|
47
|
+
item: item.id,
|
|
48
|
+
state: 'starting',
|
|
49
|
+
leaseGen: 2,
|
|
50
|
+
number: 2,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await store.exclusive(async (transaction) => {
|
|
54
|
+
await transaction.syncLimits(
|
|
55
|
+
[{ name: workers, max: 2, counts: {} }],
|
|
56
|
+
brandSchemas.instant.parse(500),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
60
|
+
await transaction.applyChange(buildMockClaimChange(first, [workers]));
|
|
61
|
+
await transaction.applyChange(buildMockClaimChange(second, [workers]));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const state: unknown = await store.read(async (transaction) => {
|
|
65
|
+
const snapshot = await transaction.readSnapshot(
|
|
66
|
+
brandSchemas.instant.parse(5000),
|
|
67
|
+
buildMockItemRules(),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const seats = await transaction.listLimitSeats();
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
attempt: await transaction.readAttempt(second.id),
|
|
74
|
+
seats: seats.map((seat) => seat.attempt),
|
|
75
|
+
lastGen: await transaction.readLastLeaseGen(item.id, first.phase),
|
|
76
|
+
version: snapshot.storeVersion,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
received: state,
|
|
82
|
+
expected: { attempt: null, seats: [first.id], lastGen: 1, version: 2 },
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
'#applyChange returns limit-full and changes no row when a limit has no free seat',
|
|
88
|
+
async (store) => {
|
|
89
|
+
const workers = brandSchemas.limitName.parse('workers');
|
|
90
|
+
const held = buildMockV1ItemRow();
|
|
91
|
+
const waiting = buildMockV1ItemRow();
|
|
92
|
+
const first = buildMockV1AttemptRow({ item: held.id, state: 'starting' });
|
|
93
|
+
const second = buildMockV1AttemptRow({ item: waiting.id, state: 'starting' });
|
|
94
|
+
|
|
95
|
+
await store.exclusive(async (transaction) => {
|
|
96
|
+
await transaction.syncLimits(
|
|
97
|
+
[{ name: workers, max: 1, counts: {} }],
|
|
98
|
+
brandSchemas.instant.parse(500),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
await transaction.applyChange(buildMockFileItemChange(held));
|
|
102
|
+
await transaction.applyChange(buildMockFileItemChange(waiting));
|
|
103
|
+
await transaction.applyChange(buildMockClaimChange(first, [workers]));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const result = await store.exclusive((transaction) =>
|
|
107
|
+
transaction.applyChange(buildMockClaimChange(second, [workers])),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const state: unknown = await store.read(async (transaction) => {
|
|
111
|
+
const snapshot = await transaction.readSnapshot(
|
|
112
|
+
brandSchemas.instant.parse(5000),
|
|
113
|
+
buildMockItemRules(),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
attempt: await transaction.readAttempt(second.id),
|
|
118
|
+
lastGen: await transaction.readLastLeaseGen(waiting.id, second.phase),
|
|
119
|
+
version: snapshot.storeVersion,
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
received: { result, state },
|
|
125
|
+
expected: {
|
|
126
|
+
result: { kind: 'limit-full', limit: 'workers' },
|
|
127
|
+
state: { attempt: null, lastGen: 0, version: 3 },
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
[
|
|
133
|
+
'#applyChange takes the seats of a claim all or none',
|
|
134
|
+
async (store) => {
|
|
135
|
+
const workers = brandSchemas.limitName.parse('workers');
|
|
136
|
+
const ownerDesign = brandSchemas.limitName.parse('owner-design');
|
|
137
|
+
|
|
138
|
+
const first = buildMockV1AttemptRow({
|
|
139
|
+
state: 'starting',
|
|
140
|
+
phase: brandSchemas.phaseName.parse('design'),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const second = buildMockV1AttemptRow({
|
|
144
|
+
state: 'starting',
|
|
145
|
+
phase: brandSchemas.phaseName.parse('design'),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
await store.exclusive(async (transaction) => {
|
|
149
|
+
await transaction.syncLimits(
|
|
150
|
+
[
|
|
151
|
+
{ name: workers, max: 7, counts: {} },
|
|
152
|
+
{ name: ownerDesign, max: 1, counts: { designMode: ['attended'] } },
|
|
153
|
+
],
|
|
154
|
+
brandSchemas.instant.parse(500),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
await transaction.applyChange(
|
|
158
|
+
buildMockFileItemChange(buildMockV1ItemRow({ id: first.item })),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
await transaction.applyChange(
|
|
162
|
+
buildMockFileItemChange(buildMockV1ItemRow({ id: second.item })),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
await transaction.applyChange(buildMockClaimChange(first, [ownerDesign, workers]));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const result = await store.exclusive((transaction) =>
|
|
169
|
+
transaction.applyChange(buildMockClaimChange(second, [workers, ownerDesign])),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const limits = await store.read((transaction) => transaction.listLimits());
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
received: { result, held: limits.map((limit) => [limit.name, limit.held]) },
|
|
176
|
+
expected: {
|
|
177
|
+
result: { kind: 'limit-full', limit: 'owner-design' },
|
|
178
|
+
held: [
|
|
179
|
+
['owner-design', 1],
|
|
180
|
+
['workers', 1],
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
[
|
|
187
|
+
'#applyChange returns unknown-limit for a take that names a limit the store does not hold',
|
|
188
|
+
async (store) => {
|
|
189
|
+
const attempt = buildMockV1AttemptRow({ state: 'starting' });
|
|
190
|
+
|
|
191
|
+
await store.exclusive(async (transaction) => {
|
|
192
|
+
await transaction.applyChange(
|
|
193
|
+
buildMockFileItemChange(buildMockV1ItemRow({ id: attempt.item })),
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const result = await store.exclusive((transaction) =>
|
|
198
|
+
transaction.applyChange(
|
|
199
|
+
buildMockClaimChange(attempt, [brandSchemas.limitName.parse('reviews')]),
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
return { received: result, expected: { kind: 'unknown-limit', limit: 'reviews' } };
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
[
|
|
207
|
+
'#applyChange gives each lever insert its seq, in the row and the fact',
|
|
208
|
+
async (store) => {
|
|
209
|
+
const item = buildMockV1ItemRow();
|
|
210
|
+
const defer = buildMockV1LeverRow({ seq: 0, item: item.id, kind: 'plan.defer' });
|
|
211
|
+
const expedite = buildMockV1LeverRow({ seq: 0, item: item.id, kind: 'plan.expedite' });
|
|
212
|
+
|
|
213
|
+
await store.exclusive(async (transaction) => {
|
|
214
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
215
|
+
await transaction.applyChange(buildMockLeverChange(defer));
|
|
216
|
+
await transaction.applyChange(buildMockLeverChange(expedite));
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const state: unknown = await store.read(async (transaction) => {
|
|
220
|
+
const items = await transaction.listItems(
|
|
221
|
+
{},
|
|
222
|
+
brandSchemas.instant.parse(5000),
|
|
223
|
+
buildMockItemRules(),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const facts = await transaction.readUnsentFacts(10);
|
|
227
|
+
|
|
228
|
+
return { levers: items[0]?.levers, payloads: facts.slice(1).map((fact) => fact.payload) };
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
received: state,
|
|
233
|
+
expected: {
|
|
234
|
+
levers: [
|
|
235
|
+
{ ...defer, seq: 1 },
|
|
236
|
+
{ ...expedite, seq: 2 },
|
|
237
|
+
],
|
|
238
|
+
payloads: [
|
|
239
|
+
{ ...defer, seq: 1 },
|
|
240
|
+
{ ...expedite, seq: 2 },
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
[
|
|
247
|
+
'#applyChange throws and writes nothing for a fact whose payload its type refuses',
|
|
248
|
+
async (store) => {
|
|
249
|
+
const item = buildMockV1ItemRow();
|
|
250
|
+
|
|
251
|
+
await store.exclusive((transaction) =>
|
|
252
|
+
transaction.applyChange(buildMockFileItemChange(item)),
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const outcome = await Promise.allSettled([
|
|
256
|
+
store.exclusive((transaction) =>
|
|
257
|
+
transaction.applyChange(
|
|
258
|
+
buildMockNoteChange({
|
|
259
|
+
item: item.id,
|
|
260
|
+
seq: 1,
|
|
261
|
+
text: '',
|
|
262
|
+
by: 'boxxy:owner:local',
|
|
263
|
+
at: brandSchemas.instant.parse(4500),
|
|
264
|
+
}),
|
|
265
|
+
),
|
|
266
|
+
),
|
|
267
|
+
]);
|
|
268
|
+
|
|
269
|
+
const state: unknown = await store.read(async (transaction) => {
|
|
270
|
+
const facts = await transaction.readUnsentFacts(10);
|
|
271
|
+
|
|
272
|
+
return { notes: await transaction.listItemNotes(item.id), facts: facts.length };
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
received: { status: outcome[0]?.status, state },
|
|
277
|
+
expected: { status: 'rejected', state: { notes: [], facts: 1 } },
|
|
278
|
+
};
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
[
|
|
282
|
+
'#applyChange applies a change with no fact and moves no store version',
|
|
283
|
+
async (store) => {
|
|
284
|
+
const runner = brandSchemas.runnerId.parse('runner-1');
|
|
285
|
+
const item = buildMockV1ItemRow();
|
|
286
|
+
|
|
287
|
+
await store.exclusive(async (transaction) => {
|
|
288
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
289
|
+
await transaction.writeHeartbeat(runner, brandSchemas.instant.parse(1000));
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const result = await store.exclusive((transaction) =>
|
|
293
|
+
transaction.applyChange(buildMockPauseChange(runner)),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const state: unknown = await store.read(async (transaction) => {
|
|
297
|
+
const snapshot = await transaction.readSnapshot(
|
|
298
|
+
brandSchemas.instant.parse(5000),
|
|
299
|
+
buildMockItemRules(),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
return { runner: await transaction.readRunner(runner), version: snapshot.storeVersion };
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
received: { result, state },
|
|
307
|
+
expected: {
|
|
308
|
+
result: { kind: 'applied' },
|
|
309
|
+
state: { runner: { id: 'runner-1', heartbeatAt: 1000, paused: true }, version: 1 },
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { setImmediate } from 'node:timers/promises';
|
|
2
|
+
import type { ClaimStoreCase } from './types.ts';
|
|
3
|
+
|
|
4
|
+
export const claimStoreCloseCases: readonly ClaimStoreCase[] = [
|
|
5
|
+
[
|
|
6
|
+
'#read rejects after the store closes',
|
|
7
|
+
async (store) => {
|
|
8
|
+
await store.close();
|
|
9
|
+
|
|
10
|
+
const settled = await store
|
|
11
|
+
.read((transaction) => transaction.listLimits())
|
|
12
|
+
.then(
|
|
13
|
+
() => 'resolved',
|
|
14
|
+
() => 'rejected',
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
return { received: settled, expected: 'rejected' };
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
[
|
|
21
|
+
'#exclusive rejects after the store closes',
|
|
22
|
+
async (store) => {
|
|
23
|
+
await store.close();
|
|
24
|
+
|
|
25
|
+
const settled = await store
|
|
26
|
+
.exclusive((transaction) => transaction.listLimits())
|
|
27
|
+
.then(
|
|
28
|
+
() => 'resolved',
|
|
29
|
+
() => 'rejected',
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return { received: settled, expected: 'rejected' };
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
'#close waits for a read that is running',
|
|
37
|
+
async (store) => {
|
|
38
|
+
const events: string[] = [];
|
|
39
|
+
const gate = Promise.withResolvers<void>();
|
|
40
|
+
|
|
41
|
+
const reading = store.read(async () => {
|
|
42
|
+
await gate.promise;
|
|
43
|
+
|
|
44
|
+
events.push('read end');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const close = async () => {
|
|
48
|
+
await store.close();
|
|
49
|
+
|
|
50
|
+
events.push('close end');
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const closing = close();
|
|
54
|
+
|
|
55
|
+
// one turn of the event loop lets a store that doesn't wait finish the close
|
|
56
|
+
await setImmediate();
|
|
57
|
+
|
|
58
|
+
gate.resolve();
|
|
59
|
+
|
|
60
|
+
await Promise.all([reading, closing]);
|
|
61
|
+
|
|
62
|
+
return { received: events, expected: ['read end', 'close end'] };
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
'#close waits for an exclusive call that is running',
|
|
67
|
+
async (store) => {
|
|
68
|
+
const events: string[] = [];
|
|
69
|
+
const gate = Promise.withResolvers<void>();
|
|
70
|
+
|
|
71
|
+
const writing = store.exclusive(async () => {
|
|
72
|
+
await gate.promise;
|
|
73
|
+
|
|
74
|
+
events.push('exclusive end');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const close = async () => {
|
|
78
|
+
await store.close();
|
|
79
|
+
|
|
80
|
+
events.push('close end');
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const closing = close();
|
|
84
|
+
|
|
85
|
+
// one turn of the event loop lets a store that doesn't wait finish the close
|
|
86
|
+
await setImmediate();
|
|
87
|
+
|
|
88
|
+
gate.resolve();
|
|
89
|
+
|
|
90
|
+
await Promise.all([writing, closing]);
|
|
91
|
+
|
|
92
|
+
return { received: events, expected: ['exclusive end', 'close end'] };
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { claimStoreAttemptReaderCases } from './claim-store-attempt-reader-cases.ts';
|
|
2
|
+
import { claimStoreClaimCases } from './claim-store-claim-cases.ts';
|
|
3
|
+
import { claimStoreCloseCases } from './claim-store-close-cases.ts';
|
|
4
|
+
import { claimStoreCopyCases } from './claim-store-copy-cases.ts';
|
|
5
|
+
import { claimStoreExclusiveCases } from './claim-store-exclusive-cases.ts';
|
|
6
|
+
import { claimStoreGuardCases } from './claim-store-guard-cases.ts';
|
|
7
|
+
import { claimStoreItemReaderCases } from './claim-store-item-reader-cases.ts';
|
|
8
|
+
import { claimStoreOutboxCases } from './claim-store-outbox-cases.ts';
|
|
9
|
+
import { claimStoreRaceCases } from './claim-store-race-cases.ts';
|
|
10
|
+
import { claimStoreRollbackCases } from './claim-store-rollback-cases.ts';
|
|
11
|
+
import { claimStoreSeatCases } from './claim-store-seat-cases.ts';
|
|
12
|
+
import { claimStoreSessionCases } from './claim-store-session-cases.ts';
|
|
13
|
+
import type { ClaimStoreCase } from './types.ts';
|
|
14
|
+
|
|
15
|
+
export const claimStoreContract: readonly ClaimStoreCase[] = [
|
|
16
|
+
...claimStoreExclusiveCases,
|
|
17
|
+
...claimStoreClaimCases,
|
|
18
|
+
...claimStoreGuardCases,
|
|
19
|
+
...claimStoreRollbackCases,
|
|
20
|
+
...claimStoreCopyCases,
|
|
21
|
+
...claimStoreCloseCases,
|
|
22
|
+
...claimStoreSeatCases,
|
|
23
|
+
...claimStoreItemReaderCases,
|
|
24
|
+
...claimStoreAttemptReaderCases,
|
|
25
|
+
...claimStoreSessionCases,
|
|
26
|
+
...claimStoreOutboxCases,
|
|
27
|
+
...claimStoreRaceCases,
|
|
28
|
+
];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { brandSchemas } from '@boxxy/model';
|
|
2
|
+
import type { Criterion } from '@boxxy/model';
|
|
3
|
+
import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
|
|
4
|
+
import { buildMockItemRules } from '@boxxy/model/test-utils/build-mock-item-rules';
|
|
5
|
+
import { buildMockV1AttemptRow } from '@boxxy/model/test-utils/build-mock-v1-attempt-row';
|
|
6
|
+
import { buildMockV1ItemRow } from '@boxxy/model/test-utils/build-mock-v1-item-row';
|
|
7
|
+
import { buildMockClaimChange } from './build-mock-claim-change.ts';
|
|
8
|
+
import { buildMockFileItemChange } from './build-mock-file-item-change.ts';
|
|
9
|
+
import { buildMockStartChange } from './build-mock-start-change.ts';
|
|
10
|
+
import type { ClaimStoreCase } from './types.ts';
|
|
11
|
+
|
|
12
|
+
export const claimStoreCopyCases: readonly ClaimStoreCase[] = [
|
|
13
|
+
[
|
|
14
|
+
'#readAttempt returns a row that a later change in the same call leaves as it was',
|
|
15
|
+
async (store) => {
|
|
16
|
+
const item = buildMockV1ItemRow();
|
|
17
|
+
const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
|
|
18
|
+
|
|
19
|
+
await store.exclusive(async (transaction) => {
|
|
20
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
21
|
+
await transaction.applyChange(buildMockClaimChange(attempt));
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const before = await store.exclusive(async (transaction) => {
|
|
25
|
+
const read = await transaction.readAttempt(attempt.id);
|
|
26
|
+
|
|
27
|
+
await transaction.applyChange(buildMockStartChange(attempt));
|
|
28
|
+
|
|
29
|
+
return read;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return { received: before?.state, expected: 'starting' };
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
'#read returns rows whose edits by the caller leave the store as it was',
|
|
37
|
+
async (store) => {
|
|
38
|
+
const item = buildMockV1ItemRow();
|
|
39
|
+
const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
|
|
40
|
+
|
|
41
|
+
await store.exclusive(async (transaction) => {
|
|
42
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
43
|
+
await transaction.applyChange(buildMockClaimChange(attempt));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const read = await store.read((transaction) => transaction.readAttempt(attempt.id));
|
|
47
|
+
|
|
48
|
+
Object.assign(read ?? {}, { state: 'failed' });
|
|
49
|
+
|
|
50
|
+
const reread = await store.read((transaction) => transaction.readAttempt(attempt.id));
|
|
51
|
+
|
|
52
|
+
return { received: reread?.state, expected: 'starting' };
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
'#applyChange stores a copy of a list that an update sets',
|
|
57
|
+
async (store) => {
|
|
58
|
+
const item = buildMockV1ItemRow();
|
|
59
|
+
|
|
60
|
+
const acceptance: Criterion[] = [
|
|
61
|
+
{ id: brandSchemas.criterionId.parse('a1'), text: 'The gate latches', evidence: [] },
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
await store.exclusive(async (transaction) => {
|
|
65
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
66
|
+
|
|
67
|
+
await transaction.applyChange({
|
|
68
|
+
at: brandSchemas.instant.parse(3000),
|
|
69
|
+
writes: [
|
|
70
|
+
{ op: 'update', table: 'items', key: { id: item.id }, expect: {}, set: { acceptance } },
|
|
71
|
+
],
|
|
72
|
+
facts: [
|
|
73
|
+
{
|
|
74
|
+
id: buildMockFactId(),
|
|
75
|
+
type: 'boxxy.item.edited',
|
|
76
|
+
entity: `boxxy:item:${item.id}`,
|
|
77
|
+
actor: 'boxxy:owner:local',
|
|
78
|
+
payload: { acceptance: [...acceptance], byAttempt: null },
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
acceptance.push({
|
|
85
|
+
id: brandSchemas.criterionId.parse('a2'),
|
|
86
|
+
text: 'The hinge is oiled',
|
|
87
|
+
evidence: [],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const reread = await store.read((transaction) =>
|
|
91
|
+
transaction.readItem(item.id, brandSchemas.instant.parse(5000), buildMockItemRules()),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
received: reread?.acceptance,
|
|
96
|
+
expected: [{ id: 'a1', text: 'The gate latches', evidence: [] }],
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
];
|