@boxxy/dispatch 0.1.0 → 0.1.1

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 (40) hide show
  1. package/README.md +4 -1
  2. package/dist/test-utils/build-mock-bind-change.d.ts +7 -0
  3. package/dist/test-utils/build-mock-claim-change.d.ts +2 -0
  4. package/dist/test-utils/build-mock-file-item-change.d.ts +2 -0
  5. package/dist/test-utils/build-mock-lever-change.d.ts +2 -0
  6. package/dist/test-utils/build-mock-link-change.d.ts +7 -0
  7. package/dist/test-utils/build-mock-pause-change.d.ts +2 -0
  8. package/dist/test-utils/build-mock-pull-request-change.d.ts +7 -0
  9. package/dist/test-utils/build-mock-settle-change.d.ts +7 -0
  10. package/dist/test-utils/build-mock-start-change.d.ts +2 -0
  11. package/dist/test-utils/claim-store-attempt-reader-cases.d.ts +2 -0
  12. package/dist/test-utils/claim-store-claim-cases.d.ts +2 -0
  13. package/dist/test-utils/claim-store-contract.d.ts +2 -0
  14. package/dist/test-utils/claim-store-exclusive-cases.d.ts +2 -0
  15. package/dist/test-utils/claim-store-guard-cases.d.ts +2 -0
  16. package/dist/test-utils/claim-store-item-reader-cases.d.ts +2 -0
  17. package/dist/test-utils/claim-store-pool-cases.d.ts +2 -0
  18. package/dist/test-utils/claim-store-rollback-cases.d.ts +2 -0
  19. package/dist/test-utils/create-memory-claim-store.d.ts +2 -0
  20. package/dist/test-utils/types.d.ts +9 -0
  21. package/package.json +8 -2
  22. package/src/test-utils/build-mock-bind-change.ts +36 -0
  23. package/src/test-utils/build-mock-claim-change.ts +42 -0
  24. package/src/test-utils/build-mock-file-item-change.ts +18 -0
  25. package/src/test-utils/build-mock-lever-change.ts +18 -0
  26. package/src/test-utils/build-mock-link-change.ts +40 -0
  27. package/src/test-utils/build-mock-pause-change.ts +27 -0
  28. package/src/test-utils/build-mock-pull-request-change.ts +53 -0
  29. package/src/test-utils/build-mock-settle-change.ts +58 -0
  30. package/src/test-utils/build-mock-start-change.ts +31 -0
  31. package/src/test-utils/claim-store-attempt-reader-cases.ts +327 -0
  32. package/src/test-utils/claim-store-claim-cases.ts +231 -0
  33. package/src/test-utils/claim-store-contract.ts +18 -0
  34. package/src/test-utils/claim-store-exclusive-cases.ts +197 -0
  35. package/src/test-utils/claim-store-guard-cases.ts +373 -0
  36. package/src/test-utils/claim-store-item-reader-cases.ts +257 -0
  37. package/src/test-utils/claim-store-pool-cases.ts +178 -0
  38. package/src/test-utils/claim-store-rollback-cases.ts +175 -0
  39. package/src/test-utils/create-memory-claim-store.ts +574 -0
  40. package/src/test-utils/types.ts +11 -0
@@ -0,0 +1,327 @@
1
+ import { expect } from 'bun:test';
2
+ import { brandSchemas } from '@boxxy/model';
3
+ import { buildMockAttemptRow } from '@boxxy/model/test-utils/build-mock-attempt-row';
4
+ import { buildMockItemRow } from '@boxxy/model/test-utils/build-mock-item-row';
5
+ import { buildMockClaimChange } from './build-mock-claim-change.ts';
6
+ import { buildMockFileItemChange } from './build-mock-file-item-change.ts';
7
+ import { buildMockPauseChange } from './build-mock-pause-change.ts';
8
+ import { buildMockPullRequestChange } from './build-mock-pull-request-change.ts';
9
+ import { buildMockSettleChange } from './build-mock-settle-change.ts';
10
+ import { buildMockStartChange } from './build-mock-start-change.ts';
11
+ import type { ClaimStoreCase } from './types.ts';
12
+
13
+ export const claimStoreAttemptReaderCases: readonly ClaimStoreCase[] = [
14
+ [
15
+ '#readLastLeaseGen returns 0 for an item phase with no lease',
16
+ async (store) => {
17
+ const generation = await store.read((transaction) =>
18
+ transaction.readLastLeaseGen(buildMockItemRow().id, brandSchemas.phaseName.parse('build')),
19
+ );
20
+
21
+ return { received: generation, expected: 0 };
22
+ },
23
+ ],
24
+ [
25
+ '#readLastLeaseGen returns the highest gen after a released lease',
26
+ async (store) => {
27
+ const item = buildMockItemRow();
28
+ const attempt = buildMockAttemptRow({ item: item.id, state: 'starting', leaseGen: 3 });
29
+
30
+ await store.exclusive(async (transaction) => {
31
+ await transaction.syncPoolSlots([{ name: attempt.pool, slots: 1 }]);
32
+ await transaction.applyChange(buildMockFileItemChange(item));
33
+ await transaction.applyChange(buildMockClaimChange(attempt));
34
+
35
+ await transaction.applyChange(
36
+ buildMockSettleChange({ attempt: { ...attempt, slot: 1, slotGen: 1 }, state: 'failed' }),
37
+ );
38
+ });
39
+
40
+ const generation = await store.read((transaction) =>
41
+ transaction.readLastLeaseGen(item.id, attempt.phase),
42
+ );
43
+
44
+ return { received: generation, expected: 3 };
45
+ },
46
+ ],
47
+ [
48
+ '#listLiveAttempts returns the attempts that have not settled',
49
+ async (store) => {
50
+ const first = buildMockItemRow();
51
+ const second = buildMockItemRow();
52
+ const live = buildMockAttemptRow({ item: first.id, state: 'starting' });
53
+ const settled = buildMockAttemptRow({ item: second.id, state: 'starting' });
54
+
55
+ await store.exclusive(async (transaction) => {
56
+ await transaction.syncPoolSlots([{ name: live.pool, slots: 2 }]);
57
+ await transaction.applyChange(buildMockFileItemChange(first));
58
+ await transaction.applyChange(buildMockFileItemChange(second));
59
+ await transaction.applyChange(buildMockClaimChange(live));
60
+ await transaction.applyChange(buildMockClaimChange(settled));
61
+
62
+ await transaction.applyChange(
63
+ buildMockSettleChange({ attempt: { ...settled, slot: 2, slotGen: 1 }, state: 'failed' }),
64
+ );
65
+ });
66
+
67
+ const listed = await store.read((transaction) => transaction.listLiveAttempts());
68
+
69
+ return { received: listed.map((attempt) => attempt.id), expected: [live.id] };
70
+ },
71
+ ],
72
+ [
73
+ '#listSucceededAttemptsWithoutPr skips an attempt with a linked PR',
74
+ async (store) => {
75
+ const first = buildMockItemRow();
76
+ const second = buildMockItemRow();
77
+ const linked = buildMockAttemptRow({ item: first.id, state: 'starting' });
78
+ const unlinked = buildMockAttemptRow({ item: second.id, state: 'starting' });
79
+
80
+ await store.exclusive(async (transaction) => {
81
+ await transaction.syncPoolSlots([{ name: linked.pool, slots: 2 }]);
82
+ await transaction.applyChange(buildMockFileItemChange(first));
83
+ await transaction.applyChange(buildMockFileItemChange(second));
84
+ await transaction.applyChange(buildMockClaimChange(linked));
85
+ await transaction.applyChange(buildMockClaimChange(unlinked));
86
+
87
+ await transaction.applyChange(
88
+ buildMockSettleChange({
89
+ attempt: { ...linked, slot: 1, slotGen: 1 },
90
+ state: 'succeeded',
91
+ }),
92
+ );
93
+
94
+ await transaction.applyChange(
95
+ buildMockSettleChange({
96
+ attempt: { ...unlinked, slot: 2, slotGen: 1 },
97
+ state: 'succeeded',
98
+ }),
99
+ );
100
+
101
+ await transaction.applyChange(
102
+ buildMockPullRequestChange({ attempt: linked, state: 'open' }),
103
+ );
104
+ });
105
+
106
+ const listed = await store.read((transaction) =>
107
+ transaction.listSucceededAttemptsWithoutPr(),
108
+ );
109
+
110
+ return { received: listed.map((attempt) => attempt.id), expected: [unlinked.id] };
111
+ },
112
+ ],
113
+ [
114
+ '#findAttemptBySession returns the attempt with an atc session id',
115
+ async (store) => {
116
+ const item = buildMockItemRow();
117
+ const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
118
+
119
+ await store.exclusive(async (transaction) => {
120
+ await transaction.syncPoolSlots([{ name: attempt.pool, slots: 1 }]);
121
+ await transaction.applyChange(buildMockFileItemChange(item));
122
+ await transaction.applyChange(buildMockClaimChange(attempt));
123
+ await transaction.applyChange(buildMockStartChange(attempt));
124
+ });
125
+
126
+ const found = await store.read((transaction) =>
127
+ transaction.findAttemptBySession(brandSchemas.atcSessionId.parse('atc-session-1')),
128
+ );
129
+
130
+ return { received: found?.id, expected: attempt.id };
131
+ },
132
+ ],
133
+ [
134
+ '#markPullRequestChecked sets the checked time of a watched PR',
135
+ async (store) => {
136
+ const item = buildMockItemRow();
137
+ const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
138
+
139
+ await store.exclusive(async (transaction) => {
140
+ await transaction.syncPoolSlots([{ name: attempt.pool, slots: 1 }]);
141
+ await transaction.applyChange(buildMockFileItemChange(item));
142
+ await transaction.applyChange(buildMockClaimChange(attempt));
143
+ await transaction.applyChange(buildMockPullRequestChange({ attempt, state: 'open' }));
144
+ });
145
+
146
+ await store.exclusive((transaction) =>
147
+ transaction.markPullRequestChecked(
148
+ { repo: brandSchemas.repoName.parse('acme/lamps'), number: 7 },
149
+ brandSchemas.instant.parse(9000),
150
+ ),
151
+ );
152
+
153
+ const watched = await store.read((transaction) => transaction.listWatchedPullRequests());
154
+
155
+ return { received: watched[0]?.checkedAt, expected: brandSchemas.instant.parse(9000) };
156
+ },
157
+ ],
158
+ [
159
+ '#listWatchedPullRequests skips a merged PR',
160
+ async (store) => {
161
+ const item = buildMockItemRow();
162
+ const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
163
+
164
+ await store.exclusive(async (transaction) => {
165
+ await transaction.syncPoolSlots([{ name: attempt.pool, slots: 1 }]);
166
+ await transaction.applyChange(buildMockFileItemChange(item));
167
+ await transaction.applyChange(buildMockClaimChange(attempt));
168
+ await transaction.applyChange(buildMockPullRequestChange({ attempt, state: 'merged' }));
169
+ });
170
+
171
+ const watched = await store.read((transaction) => transaction.listWatchedPullRequests());
172
+
173
+ return { received: watched, expected: [] };
174
+ },
175
+ ],
176
+ [
177
+ '#listEvidence returns the evidence of one attempt',
178
+ async (store) => {
179
+ const item = buildMockItemRow();
180
+ const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
181
+
182
+ await store.exclusive(async (transaction) => {
183
+ await transaction.syncPoolSlots([{ name: attempt.pool, slots: 1 }]);
184
+ await transaction.applyChange(buildMockFileItemChange(item));
185
+ await transaction.applyChange(buildMockClaimChange(attempt));
186
+ await transaction.applyChange(buildMockPullRequestChange({ attempt, state: 'open' }));
187
+ });
188
+
189
+ const evidence: unknown = await store.read((transaction) =>
190
+ transaction.listEvidence(attempt.id),
191
+ );
192
+
193
+ return {
194
+ received: evidence,
195
+ expected: [
196
+ {
197
+ factId: expect.stringMatching(/^manual:/u),
198
+ attempt: attempt.id,
199
+ kind: 'pr',
200
+ ref: 'https://github.com/acme/lamps/pull/7',
201
+ observed: null,
202
+ source: 'hook',
203
+ at: 4000,
204
+ },
205
+ ],
206
+ };
207
+ },
208
+ ],
209
+ [
210
+ '#markFactsSent drops the facts from the unsent facts and the outbox lag',
211
+ async (store) => {
212
+ const first = buildMockFileItemChange(
213
+ buildMockItemRow({ filedAt: brandSchemas.instant.parse(1000) }),
214
+ );
215
+
216
+ const second = buildMockFileItemChange(
217
+ buildMockItemRow({ filedAt: brandSchemas.instant.parse(2000) }),
218
+ );
219
+
220
+ await store.exclusive(async (transaction) => {
221
+ await transaction.applyChange(first);
222
+ await transaction.applyChange(second);
223
+ });
224
+
225
+ await store.exclusive((transaction) =>
226
+ transaction.markFactsSent(
227
+ first.facts.map((fact) => fact.id),
228
+ brandSchemas.instant.parse(3000),
229
+ ),
230
+ );
231
+
232
+ const outbox: unknown = await store.read(async (transaction) => {
233
+ const unsent = await transaction.readUnsentFacts(10);
234
+
235
+ return {
236
+ unsent: unsent.map((fact) => fact.factId),
237
+ lag: await transaction.readOutboxLag(),
238
+ };
239
+ });
240
+
241
+ return {
242
+ received: outbox,
243
+ expected: {
244
+ unsent: second.facts.map((fact) => fact.id),
245
+ lag: { unsent: 1, oldestUnsentAt: 2000 },
246
+ },
247
+ };
248
+ },
249
+ ],
250
+ [
251
+ '#readUnsentFacts returns at most the limit, oldest first',
252
+ async (store) => {
253
+ await store.exclusive(async (transaction) => {
254
+ await transaction.applyChange(buildMockFileItemChange(buildMockItemRow()));
255
+ await transaction.applyChange(buildMockFileItemChange(buildMockItemRow()));
256
+ });
257
+
258
+ const unsent = await store.read((transaction) => transaction.readUnsentFacts(1));
259
+
260
+ return { received: unsent.map((fact) => fact.seq), expected: [1] };
261
+ },
262
+ ],
263
+ [
264
+ '#readOutboxLag reads no lag from an empty outbox',
265
+ async (store) => {
266
+ const lag: unknown = await store.read((transaction) => transaction.readOutboxLag());
267
+
268
+ return { received: lag, expected: { unsent: 0, oldestUnsentAt: null } };
269
+ },
270
+ ],
271
+ [
272
+ '#readOutboxLag reads the earliest time of the unsent facts, whatever their order',
273
+ async (store) => {
274
+ const later = buildMockItemRow({ filedAt: brandSchemas.instant.parse(2000) });
275
+ const earlier = buildMockItemRow({ filedAt: brandSchemas.instant.parse(1000) });
276
+
277
+ await store.exclusive(async (transaction) => {
278
+ await transaction.applyChange(buildMockFileItemChange(later));
279
+ await transaction.applyChange(buildMockFileItemChange(earlier));
280
+ });
281
+
282
+ const lag: unknown = await store.read((transaction) => transaction.readOutboxLag());
283
+
284
+ return { received: lag, expected: { unsent: 2, oldestUnsentAt: 1000 } };
285
+ },
286
+ ],
287
+ [
288
+ '#writeHeartbeat inserts the runner row on its first beat, not paused',
289
+ async (store) => {
290
+ const runner = brandSchemas.runnerId.parse('runner-1');
291
+
292
+ await store.exclusive((transaction) =>
293
+ transaction.writeHeartbeat(runner, brandSchemas.instant.parse(1000)),
294
+ );
295
+
296
+ const read: unknown = await store.read((transaction) => transaction.readRunner(runner));
297
+
298
+ return { received: read, expected: { id: 'runner-1', heartbeatAt: 1000, paused: false } };
299
+ },
300
+ ],
301
+ [
302
+ '#writeHeartbeat moves the heartbeat and keeps the paused flag',
303
+ async (store) => {
304
+ const runner = brandSchemas.runnerId.parse('runner-1');
305
+
306
+ await store.exclusive(async (transaction) => {
307
+ await transaction.writeHeartbeat(runner, brandSchemas.instant.parse(1000));
308
+ await transaction.applyChange(buildMockPauseChange(runner));
309
+ await transaction.writeHeartbeat(runner, brandSchemas.instant.parse(2000));
310
+ });
311
+
312
+ const read: unknown = await store.read((transaction) => transaction.readRunner(runner));
313
+
314
+ return { received: read, expected: { id: 'runner-1', heartbeatAt: 2000, paused: true } };
315
+ },
316
+ ],
317
+ [
318
+ '#readRunner returns null before the first heartbeat',
319
+ async (store) => {
320
+ const read = await store.read((transaction) =>
321
+ transaction.readRunner(brandSchemas.runnerId.parse('runner-1')),
322
+ );
323
+
324
+ return { received: read, expected: null };
325
+ },
326
+ ],
327
+ ];
@@ -0,0 +1,231 @@
1
+ import { brandSchemas } from '@boxxy/model';
2
+ import { buildMockAttemptRow } from '@boxxy/model/test-utils/build-mock-attempt-row';
3
+ import { buildMockItemRow } from '@boxxy/model/test-utils/build-mock-item-row';
4
+ import { buildMockLeverRow } from '@boxxy/model/test-utils/build-mock-lever-row';
5
+ import { buildMockClaimChange } from './build-mock-claim-change.ts';
6
+ import { buildMockFileItemChange } from './build-mock-file-item-change.ts';
7
+ import { buildMockLeverChange } from './build-mock-lever-change.ts';
8
+ import type { ClaimStoreCase } from './types.ts';
9
+
10
+ export const claimStoreClaimCases: readonly ClaimStoreCase[] = [
11
+ [
12
+ '#applyChange returns lease-held for a second live lease on one item phase',
13
+ async (store) => {
14
+ const item = buildMockItemRow();
15
+ const first = buildMockAttemptRow({ item: item.id, state: 'starting', leaseGen: 1 });
16
+
17
+ const second = buildMockAttemptRow({
18
+ item: item.id,
19
+ state: 'starting',
20
+ leaseGen: 2,
21
+ number: 2,
22
+ });
23
+
24
+ await store.exclusive(async (transaction) => {
25
+ await transaction.syncPoolSlots([{ name: first.pool, slots: 2 }]);
26
+ await transaction.applyChange(buildMockFileItemChange(item));
27
+ await transaction.applyChange(buildMockClaimChange(first));
28
+ });
29
+
30
+ const result = await store.exclusive((transaction) =>
31
+ transaction.applyChange(buildMockClaimChange(second)),
32
+ );
33
+
34
+ return { received: result, expected: { kind: 'lease-held' } };
35
+ },
36
+ ],
37
+ [
38
+ '#applyChange writes no row of a change that returns lease-held',
39
+ async (store) => {
40
+ const item = buildMockItemRow();
41
+ const first = buildMockAttemptRow({ item: item.id, state: 'starting', leaseGen: 1 });
42
+
43
+ const second = buildMockAttemptRow({
44
+ item: item.id,
45
+ state: 'starting',
46
+ leaseGen: 2,
47
+ number: 2,
48
+ });
49
+
50
+ await store.exclusive(async (transaction) => {
51
+ await transaction.syncPoolSlots([{ name: first.pool, slots: 2 }]);
52
+ await transaction.applyChange(buildMockFileItemChange(item));
53
+ await transaction.applyChange(buildMockClaimChange(first));
54
+ await transaction.applyChange(buildMockClaimChange(second));
55
+ });
56
+
57
+ const state: unknown = await store.read(async (transaction) => {
58
+ const snapshot = await transaction.readSnapshot(
59
+ brandSchemas.instant.parse(5000),
60
+ new Map(),
61
+ );
62
+
63
+ return {
64
+ attempt: await transaction.readAttempt(second.id),
65
+ slots: await transaction.listPoolSlots(),
66
+ lastGen: await transaction.readLastLeaseGen(item.id, first.phase),
67
+ version: snapshot.storeVersion,
68
+ };
69
+ });
70
+
71
+ return {
72
+ received: state,
73
+ expected: {
74
+ attempt: null,
75
+ slots: [
76
+ { pool: first.pool, slot: 1, holder: first.id, gen: 1, retired: false },
77
+ { pool: first.pool, slot: 2, holder: null, gen: 0, retired: false },
78
+ ],
79
+ lastGen: 1,
80
+ version: 2,
81
+ },
82
+ };
83
+ },
84
+ ],
85
+ [
86
+ '#applyChange returns no-free-slot and changes no row when every slot is held',
87
+ async (store) => {
88
+ const held = buildMockItemRow();
89
+ const waiting = buildMockItemRow();
90
+ const first = buildMockAttemptRow({ item: held.id, state: 'starting' });
91
+ const second = buildMockAttemptRow({ item: waiting.id, state: 'starting' });
92
+
93
+ await store.exclusive(async (transaction) => {
94
+ await transaction.syncPoolSlots([{ name: first.pool, slots: 1 }]);
95
+ await transaction.applyChange(buildMockFileItemChange(held));
96
+ await transaction.applyChange(buildMockFileItemChange(waiting));
97
+ await transaction.applyChange(buildMockClaimChange(first));
98
+ });
99
+
100
+ const result = await store.exclusive((transaction) =>
101
+ transaction.applyChange(buildMockClaimChange(second)),
102
+ );
103
+
104
+ const state: unknown = await store.read(async (transaction) => {
105
+ const snapshot = await transaction.readSnapshot(
106
+ brandSchemas.instant.parse(5000),
107
+ new Map(),
108
+ );
109
+
110
+ return {
111
+ attempt: await transaction.readAttempt(second.id),
112
+ lastGen: await transaction.readLastLeaseGen(waiting.id, second.phase),
113
+ version: snapshot.storeVersion,
114
+ };
115
+ });
116
+
117
+ return {
118
+ received: { result, state },
119
+ expected: {
120
+ result: { kind: 'no-free-slot' },
121
+ state: { attempt: null, lastGen: 0, version: 3 },
122
+ },
123
+ };
124
+ },
125
+ ],
126
+ [
127
+ '#applyChange gives a claim the lowest free slot and its new gen',
128
+ async (store) => {
129
+ const item = buildMockItemRow();
130
+ const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
131
+
132
+ await store.exclusive(async (transaction) => {
133
+ await transaction.syncPoolSlots([{ name: attempt.pool, slots: 2 }]);
134
+ await transaction.applyChange(buildMockFileItemChange(item));
135
+ });
136
+
137
+ const result = await store.exclusive((transaction) =>
138
+ transaction.applyChange(buildMockClaimChange(attempt)),
139
+ );
140
+
141
+ return {
142
+ received: result,
143
+ expected: {
144
+ kind: 'applied',
145
+ slot: { pool: attempt.pool, slot: 1, gen: 1 },
146
+ },
147
+ };
148
+ },
149
+ ],
150
+ [
151
+ '#applyChange writes the taken slot and slot gen into the attempt row',
152
+ async (store) => {
153
+ const item = buildMockItemRow();
154
+ const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
155
+
156
+ await store.exclusive(async (transaction) => {
157
+ await transaction.syncPoolSlots([{ name: attempt.pool, slots: 2 }]);
158
+ await transaction.applyChange(buildMockFileItemChange(item));
159
+ await transaction.applyChange(buildMockClaimChange(attempt));
160
+ });
161
+
162
+ const read = await store.read((transaction) => transaction.readAttempt(attempt.id));
163
+
164
+ return { received: read, expected: { ...attempt, slot: 1, slotGen: 1 } };
165
+ },
166
+ ],
167
+ [
168
+ '#applyChange writes the taken slot and slot gen into the claimed fact',
169
+ async (store) => {
170
+ const item = buildMockItemRow();
171
+ const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
172
+
173
+ await store.exclusive(async (transaction) => {
174
+ await transaction.syncPoolSlots([{ name: attempt.pool, slots: 2 }]);
175
+ await transaction.applyChange(buildMockFileItemChange(item));
176
+ await transaction.applyChange(buildMockClaimChange(attempt));
177
+ });
178
+
179
+ const facts = await store.read((transaction) => transaction.readUnsentFacts(10));
180
+
181
+ return {
182
+ received: facts[1]?.payload,
183
+ expected: {
184
+ item: item.id,
185
+ phase: attempt.phase,
186
+ number: attempt.number,
187
+ pool: attempt.pool,
188
+ slot: 1,
189
+ gen: 1,
190
+ recipeHash: attempt.recipeHash,
191
+ rulesVersion: attempt.rulesVersion,
192
+ },
193
+ };
194
+ },
195
+ ],
196
+ [
197
+ '#applyChange gives each lever insert its seq, in the row and the fact',
198
+ async (store) => {
199
+ const item = buildMockItemRow();
200
+ const defer = buildMockLeverRow({ seq: 0, item: item.id, kind: 'plan.defer' });
201
+ const expedite = buildMockLeverRow({ seq: 0, item: item.id, kind: 'plan.expedite' });
202
+
203
+ await store.exclusive(async (transaction) => {
204
+ await transaction.applyChange(buildMockFileItemChange(item));
205
+ await transaction.applyChange(buildMockLeverChange(defer));
206
+ await transaction.applyChange(buildMockLeverChange(expedite));
207
+ });
208
+
209
+ const state: unknown = await store.read(async (transaction) => {
210
+ const items = await transaction.listItems({}, brandSchemas.instant.parse(5000), new Map());
211
+ const facts = await transaction.readUnsentFacts(10);
212
+
213
+ return { levers: items[0]?.levers, payloads: facts.slice(1).map((fact) => fact.payload) };
214
+ });
215
+
216
+ return {
217
+ received: state,
218
+ expected: {
219
+ levers: [
220
+ { ...defer, seq: 1 },
221
+ { ...expedite, seq: 2 },
222
+ ],
223
+ payloads: [
224
+ { ...defer, seq: 1 },
225
+ { ...expedite, seq: 2 },
226
+ ],
227
+ },
228
+ };
229
+ },
230
+ ],
231
+ ];
@@ -0,0 +1,18 @@
1
+ import { claimStoreAttemptReaderCases } from './claim-store-attempt-reader-cases.ts';
2
+ import { claimStoreClaimCases } from './claim-store-claim-cases.ts';
3
+ import { claimStoreExclusiveCases } from './claim-store-exclusive-cases.ts';
4
+ import { claimStoreGuardCases } from './claim-store-guard-cases.ts';
5
+ import { claimStoreItemReaderCases } from './claim-store-item-reader-cases.ts';
6
+ import { claimStorePoolCases } from './claim-store-pool-cases.ts';
7
+ import { claimStoreRollbackCases } from './claim-store-rollback-cases.ts';
8
+ import type { ClaimStoreCase } from './types.ts';
9
+
10
+ export const claimStoreContract: readonly ClaimStoreCase[] = [
11
+ ...claimStoreExclusiveCases,
12
+ ...claimStoreClaimCases,
13
+ ...claimStoreGuardCases,
14
+ ...claimStoreRollbackCases,
15
+ ...claimStorePoolCases,
16
+ ...claimStoreItemReaderCases,
17
+ ...claimStoreAttemptReaderCases,
18
+ ];