@boxxy/dispatch 0.1.1 → 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/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-claim-change.d.ts +2 -2
- 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-pull-request-change.d.ts +1 -0
- package/dist/test-utils/{build-mock-link-change.d.ts → build-mock-relation-change.d.ts} +2 -2
- package/dist/test-utils/build-mock-running-reading.d.ts +6 -0
- package/dist/test-utils/build-mock-start-change.d.ts +2 -2
- package/dist/test-utils/claim-store-close-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-copy-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-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 -2
- 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 +12 -2
- 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 +10 -3
- 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 +18 -11
- package/src/test-utils/build-mock-claim-change.ts +18 -8
- package/src/test-utils/build-mock-file-item-change.ts +3 -1
- 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 +2 -10
- package/src/test-utils/build-mock-pull-request-change.ts +19 -9
- package/src/test-utils/{build-mock-link-change.ts → build-mock-relation-change.ts} +6 -6
- package/src/test-utils/build-mock-running-reading.ts +19 -0
- package/src/test-utils/build-mock-settle-change.ts +18 -6
- package/src/test-utils/build-mock-start-change.ts +13 -6
- package/src/test-utils/claim-store-attempt-reader-cases.ts +163 -133
- package/src/test-utils/claim-store-claim-cases.ts +168 -85
- package/src/test-utils/claim-store-close-cases.ts +95 -0
- package/src/test-utils/claim-store-contract.ts +12 -2
- package/src/test-utils/claim-store-copy-cases.ts +100 -0
- package/src/test-utils/claim-store-exclusive-cases.ts +65 -19
- package/src/test-utils/claim-store-guard-cases.ts +92 -82
- package/src/test-utils/claim-store-item-reader-cases.ts +291 -77
- 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 +50 -50
- 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 +73 -484
- 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 +14 -2
- package/src/test-utils/worker-contract.ts +192 -0
- package/src/timeouts.ts +17 -0
- package/src/types.ts +32 -0
- package/dist/test-utils/claim-store-pool-cases.d.ts +0 -2
- package/src/test-utils/claim-store-pool-cases.ts +0 -178
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { brandSchemas } from '@boxxy/model';
|
|
2
|
+
import { buildMockV1ItemRow } from '@boxxy/model/test-utils/build-mock-v1-item-row';
|
|
3
|
+
import { buildMockFileItemChange } from './build-mock-file-item-change.ts';
|
|
4
|
+
import type { ClaimStoreCase } from './types.ts';
|
|
5
|
+
|
|
6
|
+
export const claimStoreOutboxCases: readonly ClaimStoreCase[] = [
|
|
7
|
+
[
|
|
8
|
+
'#markFactsSent drops the facts from the unsent facts and the counts',
|
|
9
|
+
async (store) => {
|
|
10
|
+
const first = buildMockFileItemChange(
|
|
11
|
+
buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(1000) }),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const second = buildMockFileItemChange(
|
|
15
|
+
buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(2000) }),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
await store.exclusive(async (transaction) => {
|
|
19
|
+
await transaction.applyChange(first);
|
|
20
|
+
await transaction.applyChange(second);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await store.exclusive((transaction) =>
|
|
24
|
+
transaction.markFactsSent(
|
|
25
|
+
first.facts.map((fact) => fact.id),
|
|
26
|
+
brandSchemas.instant.parse(3000),
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const outbox: unknown = await store.read(async (transaction) => {
|
|
31
|
+
const unsent = await transaction.readUnsentFacts(10);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
unsent: unsent.map((fact) => fact.factId),
|
|
35
|
+
counts: await transaction.readOutboxCounts(),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
received: outbox,
|
|
41
|
+
expected: {
|
|
42
|
+
unsent: second.facts.map((fact) => fact.id),
|
|
43
|
+
counts: { unsent: 1, parked: 0, oldestUnsentAt: 2000 },
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
'#readUnsentFacts returns at most the limit, oldest first',
|
|
50
|
+
async (store) => {
|
|
51
|
+
await store.exclusive(async (transaction) => {
|
|
52
|
+
await transaction.applyChange(buildMockFileItemChange(buildMockV1ItemRow()));
|
|
53
|
+
await transaction.applyChange(buildMockFileItemChange(buildMockV1ItemRow()));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const unsent = await store.read((transaction) => transaction.readUnsentFacts(1));
|
|
57
|
+
|
|
58
|
+
return { received: unsent.map((fact) => fact.seq), expected: [1] };
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
'#readOutboxCounts reads no row from an empty outbox',
|
|
63
|
+
async (store) => {
|
|
64
|
+
const counts: unknown = await store.read((transaction) => transaction.readOutboxCounts());
|
|
65
|
+
|
|
66
|
+
return { received: counts, expected: { unsent: 0, parked: 0, oldestUnsentAt: null } };
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
'#readOutboxCounts reads the earliest time of the unsent facts, whatever their order',
|
|
71
|
+
async (store) => {
|
|
72
|
+
const later = buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(2000) });
|
|
73
|
+
const earlier = buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(1000) });
|
|
74
|
+
|
|
75
|
+
await store.exclusive(async (transaction) => {
|
|
76
|
+
await transaction.applyChange(buildMockFileItemChange(later));
|
|
77
|
+
await transaction.applyChange(buildMockFileItemChange(earlier));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const counts: unknown = await store.read((transaction) => transaction.readOutboxCounts());
|
|
81
|
+
|
|
82
|
+
return { received: counts, expected: { unsent: 2, parked: 0, oldestUnsentAt: 1000 } };
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
'#parkFacts leaves a parked fact out of the unsent facts and counts it parked',
|
|
87
|
+
async (store) => {
|
|
88
|
+
const parked = buildMockFileItemChange(
|
|
89
|
+
buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(1000) }),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const waiting = buildMockFileItemChange(
|
|
93
|
+
buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(2000) }),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
await store.exclusive(async (transaction) => {
|
|
97
|
+
await transaction.applyChange(parked);
|
|
98
|
+
await transaction.applyChange(waiting);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await store.exclusive((transaction) =>
|
|
102
|
+
transaction.parkFacts(
|
|
103
|
+
parked.facts.map((fact) => fact.id),
|
|
104
|
+
'too-large',
|
|
105
|
+
brandSchemas.instant.parse(3000),
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const outbox: unknown = await store.read(async (transaction) => {
|
|
110
|
+
const unsent = await transaction.readUnsentFacts(10);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
unsent: unsent.map((fact) => fact.factId),
|
|
114
|
+
counts: await transaction.readOutboxCounts(),
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
received: outbox,
|
|
120
|
+
expected: {
|
|
121
|
+
unsent: waiting.facts.map((fact) => fact.id),
|
|
122
|
+
counts: { unsent: 1, parked: 1, oldestUnsentAt: 2000 },
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
[
|
|
128
|
+
'#markFactsSent leaves a parked fact parked',
|
|
129
|
+
async (store) => {
|
|
130
|
+
const change = buildMockFileItemChange(buildMockV1ItemRow());
|
|
131
|
+
const ids = change.facts.map((fact) => fact.id);
|
|
132
|
+
|
|
133
|
+
await store.exclusive(async (transaction) => {
|
|
134
|
+
await transaction.applyChange(change);
|
|
135
|
+
await transaction.parkFacts(ids, 'unknown-type', brandSchemas.instant.parse(3000));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await store.exclusive((transaction) =>
|
|
139
|
+
transaction.markFactsSent(ids, brandSchemas.instant.parse(4000)),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const counts: unknown = await store.read((transaction) => transaction.readOutboxCounts());
|
|
143
|
+
|
|
144
|
+
return { received: counts, expected: { unsent: 0, parked: 1, oldestUnsentAt: null } };
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
[
|
|
148
|
+
'#parkFacts leaves a sent fact as it is',
|
|
149
|
+
async (store) => {
|
|
150
|
+
const change = buildMockFileItemChange(buildMockV1ItemRow());
|
|
151
|
+
const ids = change.facts.map((fact) => fact.id);
|
|
152
|
+
|
|
153
|
+
await store.exclusive(async (transaction) => {
|
|
154
|
+
await transaction.applyChange(change);
|
|
155
|
+
await transaction.markFactsSent(ids, brandSchemas.instant.parse(3000));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await store.exclusive((transaction) =>
|
|
159
|
+
transaction.parkFacts(ids, 'too-large', brandSchemas.instant.parse(4000)),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const counts: unknown = await store.read((transaction) => transaction.readOutboxCounts());
|
|
163
|
+
|
|
164
|
+
return { received: counts, expected: { unsent: 0, parked: 0, oldestUnsentAt: null } };
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
[
|
|
168
|
+
'#unparkFacts clears every park mark, returns the count and the facts read unsent again',
|
|
169
|
+
async (store) => {
|
|
170
|
+
const first = buildMockFileItemChange(buildMockV1ItemRow());
|
|
171
|
+
const second = buildMockFileItemChange(buildMockV1ItemRow());
|
|
172
|
+
|
|
173
|
+
await store.exclusive(async (transaction) => {
|
|
174
|
+
await transaction.applyChange(first);
|
|
175
|
+
await transaction.applyChange(second);
|
|
176
|
+
|
|
177
|
+
await transaction.parkFacts(
|
|
178
|
+
[...first.facts, ...second.facts].map((fact) => fact.id),
|
|
179
|
+
'too-large',
|
|
180
|
+
brandSchemas.instant.parse(3000),
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const cleared = await store.exclusive((transaction) => transaction.unparkFacts());
|
|
185
|
+
const unsent = await store.read((transaction) => transaction.readUnsentFacts(10));
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
received: { cleared, unsent: unsent.map((fact) => [fact.parkedAt, fact.parkedReason]) },
|
|
189
|
+
expected: {
|
|
190
|
+
cleared: 2,
|
|
191
|
+
unsent: [
|
|
192
|
+
[null, null],
|
|
193
|
+
[null, null],
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
[
|
|
200
|
+
'#readItemBehind counts the unsent facts of one item, parked ones too',
|
|
201
|
+
async (store) => {
|
|
202
|
+
const item = buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(1000) });
|
|
203
|
+
const other = buildMockV1ItemRow();
|
|
204
|
+
const filed = buildMockFileItemChange(item);
|
|
205
|
+
|
|
206
|
+
await store.exclusive(async (transaction) => {
|
|
207
|
+
await transaction.applyChange(filed);
|
|
208
|
+
await transaction.applyChange(buildMockFileItemChange(other));
|
|
209
|
+
|
|
210
|
+
await transaction.parkFacts(
|
|
211
|
+
filed.facts.map((fact) => fact.id),
|
|
212
|
+
'too-large',
|
|
213
|
+
brandSchemas.instant.parse(3000),
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const behind: unknown = await store.read((transaction) =>
|
|
218
|
+
transaction.readItemBehind(item.id),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
return { received: behind, expected: { facts: 1, oldestAt: 1000 } };
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
[
|
|
225
|
+
'#readItemBehind reads nothing behind for an item whose facts are sent',
|
|
226
|
+
async (store) => {
|
|
227
|
+
const item = buildMockV1ItemRow();
|
|
228
|
+
const filed = buildMockFileItemChange(item);
|
|
229
|
+
|
|
230
|
+
await store.exclusive(async (transaction) => {
|
|
231
|
+
await transaction.applyChange(filed);
|
|
232
|
+
|
|
233
|
+
await transaction.markFactsSent(
|
|
234
|
+
filed.facts.map((fact) => fact.id),
|
|
235
|
+
brandSchemas.instant.parse(3000),
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const behind: unknown = await store.read((transaction) =>
|
|
240
|
+
transaction.readItemBehind(item.id),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return { received: behind, expected: { facts: 0, oldestAt: null } };
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
[
|
|
247
|
+
'#markNoticeTried counts a failed try and keeps the notice unsent',
|
|
248
|
+
async (store) => {
|
|
249
|
+
const item = buildMockV1ItemRow();
|
|
250
|
+
|
|
251
|
+
const notice = {
|
|
252
|
+
id: 0,
|
|
253
|
+
item: item.id,
|
|
254
|
+
reason: 'attempts used up',
|
|
255
|
+
at: brandSchemas.instant.parse(3000),
|
|
256
|
+
tries: 0,
|
|
257
|
+
sentAt: null,
|
|
258
|
+
} as const;
|
|
259
|
+
|
|
260
|
+
await store.exclusive(async (transaction) => {
|
|
261
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
262
|
+
|
|
263
|
+
await transaction.applyChange({
|
|
264
|
+
at: notice.at,
|
|
265
|
+
writes: [
|
|
266
|
+
{ op: 'insert', table: 'notices', row: notice },
|
|
267
|
+
{ op: 'insert', table: 'notices', row: notice },
|
|
268
|
+
],
|
|
269
|
+
facts: [],
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
await transaction.markNoticeTried(1, brandSchemas.instant.parse(3500), false);
|
|
273
|
+
await transaction.markNoticeTried(2, brandSchemas.instant.parse(3500), true);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const unsent: unknown = await store.read((transaction) => transaction.listUnsentNotices(10));
|
|
277
|
+
|
|
278
|
+
return { received: unsent, expected: [{ ...notice, id: 1, tries: 1 }] };
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
];
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { brandSchemas } from '@boxxy/model';
|
|
2
|
+
import type { AttemptRow, StoreChange } from '@boxxy/model';
|
|
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 invariant from 'tiny-invariant';
|
|
6
|
+
import { buildAttemptChange } from '../attempts/build-attempt-change.ts';
|
|
7
|
+
import type { MoveEvent } from '../attempts/types.ts';
|
|
8
|
+
import { buildMockClaimChange } from './build-mock-claim-change.ts';
|
|
9
|
+
import { buildMockFileItemChange } from './build-mock-file-item-change.ts';
|
|
10
|
+
import { buildMockNewId } from './build-mock-new-id.ts';
|
|
11
|
+
import type { ClaimStoreCase } from './types.ts';
|
|
12
|
+
|
|
13
|
+
export const claimStoreRaceCases: readonly ClaimStoreCase[] = [
|
|
14
|
+
[
|
|
15
|
+
'#applyChange applies the attempt builder claim with its lease, seats, decision and fact',
|
|
16
|
+
async (store) => {
|
|
17
|
+
const item = buildMockV1ItemRow();
|
|
18
|
+
const workers = brandSchemas.limitName.parse('workers');
|
|
19
|
+
const pool = brandSchemas.poolName.parse('main');
|
|
20
|
+
const rulesVersion = brandSchemas.rulesVersion.parse('a'.repeat(64));
|
|
21
|
+
|
|
22
|
+
const built = buildAttemptChange({
|
|
23
|
+
actor: 'boxxy:runner:runner-1',
|
|
24
|
+
now: brandSchemas.instant.parse(5000),
|
|
25
|
+
newId: buildMockNewId(),
|
|
26
|
+
attempt: null,
|
|
27
|
+
event: {
|
|
28
|
+
kind: 'claimed',
|
|
29
|
+
attempt: buildMockV1AttemptRow().id,
|
|
30
|
+
pick: {
|
|
31
|
+
kind: 'start',
|
|
32
|
+
item: item.id,
|
|
33
|
+
phase: brandSchemas.phaseName.parse('build'),
|
|
34
|
+
round: 1,
|
|
35
|
+
pool,
|
|
36
|
+
fields: { repo: ['acme/lamps'] },
|
|
37
|
+
limits: [workers],
|
|
38
|
+
},
|
|
39
|
+
number: 1,
|
|
40
|
+
runner: brandSchemas.runnerId.parse('runner-1'),
|
|
41
|
+
leaseGen: 1,
|
|
42
|
+
recipe: { hash: brandSchemas.recipeHash.parse('c'.repeat(64)), rulesVersion },
|
|
43
|
+
skillsRef: null,
|
|
44
|
+
spawnRequest: { prompt: 'Fix the flicker.' },
|
|
45
|
+
spawnRequestHash: buildMockV1AttemptRow().spawnRequestHash,
|
|
46
|
+
input: { findings: [] },
|
|
47
|
+
mode: 'agent',
|
|
48
|
+
caller: null,
|
|
49
|
+
decision: {
|
|
50
|
+
item: item.id,
|
|
51
|
+
kind: 'start',
|
|
52
|
+
pool,
|
|
53
|
+
rulesVersion,
|
|
54
|
+
snapshotHash: brandSchemas.snapshotHash.parse('b'.repeat(64)),
|
|
55
|
+
storeVersion: 1,
|
|
56
|
+
pepysOffsets: {},
|
|
57
|
+
faults: [],
|
|
58
|
+
limits: [{ limit: workers, held: 0, max: 2 }],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
invariant(built.kind === 'change', 'the attempt machine allows a claim');
|
|
64
|
+
|
|
65
|
+
await store.exclusive(async (transaction) => {
|
|
66
|
+
await transaction.syncLimits(
|
|
67
|
+
[{ name: workers, max: 2, counts: {} }],
|
|
68
|
+
brandSchemas.instant.parse(500),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const result = await store.exclusive((transaction) => transaction.applyChange(built.change));
|
|
75
|
+
|
|
76
|
+
const state: unknown = await store.read(async (transaction) => {
|
|
77
|
+
const attempts = await transaction.listItemAttempts(item.id);
|
|
78
|
+
const limits = await transaction.listLimits();
|
|
79
|
+
const facts = await transaction.readUnsentFacts(10);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
attempts: attempts.map((attempt) => [attempt.state, attempt.input]),
|
|
83
|
+
held: limits.map((limit) => limit.held),
|
|
84
|
+
facts: facts.map((fact) => fact.type),
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
received: { result, state },
|
|
90
|
+
expected: {
|
|
91
|
+
result: { kind: 'applied' },
|
|
92
|
+
state: {
|
|
93
|
+
attempts: [['starting', { findings: [] }]],
|
|
94
|
+
held: [1],
|
|
95
|
+
facts: ['boxxy.item.filed', 'boxxy.attempt.claimed', 'boxxy.decision.recorded'],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
'#applyChange refuses the second of two attempt moves built from one read',
|
|
103
|
+
async (store) => {
|
|
104
|
+
const attempt = buildMockV1AttemptRow({ state: 'running' });
|
|
105
|
+
|
|
106
|
+
await store.exclusive(async (transaction) => {
|
|
107
|
+
await transaction.applyChange(
|
|
108
|
+
buildMockFileItemChange(buildMockV1ItemRow({ id: attempt.item })),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
await transaction.applyChange(buildMockClaimChange(attempt));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const read = await store.read((transaction) => transaction.readAttempt(attempt.id));
|
|
115
|
+
|
|
116
|
+
invariant(read !== null, 'the claim stored the attempt');
|
|
117
|
+
|
|
118
|
+
const first = buildMoveChange(read, { kind: 'suspend-asked' });
|
|
119
|
+
const second = buildMoveChange(read, { kind: 'stalled' });
|
|
120
|
+
|
|
121
|
+
const results = await store.exclusive(async (transaction) => [
|
|
122
|
+
await transaction.applyChange(first),
|
|
123
|
+
await transaction.applyChange(second),
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
received: results,
|
|
128
|
+
expected: [
|
|
129
|
+
{ kind: 'applied' },
|
|
130
|
+
{ kind: 'guard-mismatch', table: 'attempts', key: { id: attempt.id } },
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
// the attempt builder's change, which the store takes as it is
|
|
138
|
+
function buildMoveChange(attempt: AttemptRow, event: MoveEvent): StoreChange {
|
|
139
|
+
const built = buildAttemptChange({
|
|
140
|
+
actor: 'boxxy:runner:runner-1',
|
|
141
|
+
now: brandSchemas.instant.parse(5000),
|
|
142
|
+
newId: buildMockNewId(),
|
|
143
|
+
attempt,
|
|
144
|
+
event,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
invariant(built.kind === 'change', 'the attempt machine allows the move');
|
|
148
|
+
|
|
149
|
+
return built.change;
|
|
150
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { brandSchemas } from '@boxxy/model';
|
|
2
2
|
import type { StoreChange } from '@boxxy/model';
|
|
3
|
-
import { buildMockAttemptRow } from '@boxxy/model/test-utils/build-mock-attempt-row';
|
|
4
3
|
import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
|
|
5
|
-
import {
|
|
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';
|
|
6
7
|
import { buildMockClaimChange } from './build-mock-claim-change.ts';
|
|
7
8
|
import { buildMockFileItemChange } from './build-mock-file-item-change.ts';
|
|
8
9
|
import { buildMockSettleChange } from './build-mock-settle-change.ts';
|
|
@@ -10,51 +11,52 @@ import type { ClaimStoreCase } from './types.ts';
|
|
|
10
11
|
|
|
11
12
|
export const claimStoreRollbackCases: readonly ClaimStoreCase[] = [
|
|
12
13
|
[
|
|
13
|
-
'#applyChange
|
|
14
|
+
'#applyChange takes back the lease and seat releases of a settle whose guard fails',
|
|
14
15
|
async (store) => {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
const workers = brandSchemas.limitName.parse('workers');
|
|
17
|
+
const item = buildMockV1ItemRow();
|
|
18
|
+
const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
|
|
18
19
|
|
|
19
20
|
await store.exclusive(async (transaction) => {
|
|
20
|
-
await transaction.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
await transaction.syncLimits(
|
|
22
|
+
[{ name: workers, max: 1, counts: {} }],
|
|
23
|
+
brandSchemas.instant.parse(500),
|
|
24
|
+
);
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
state: 'succeeded',
|
|
26
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
27
|
+
await transaction.applyChange(buildMockClaimChange(attempt, [workers]));
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
],
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const result = await store.exclusive((transaction) => transaction.applyChange(misnamed));
|
|
30
|
+
const result = await store.exclusive((transaction) =>
|
|
31
|
+
transaction.applyChange(
|
|
32
|
+
buildMockSettleChange({ attempt: { ...attempt, state: 'running' }, state: 'failed' }),
|
|
33
|
+
),
|
|
34
|
+
);
|
|
39
35
|
|
|
40
36
|
const state: unknown = await store.read(async (transaction) => {
|
|
41
37
|
const read = await transaction.readAttempt(attempt.id);
|
|
42
38
|
const facts = await transaction.readUnsentFacts(10);
|
|
39
|
+
const limits = await transaction.listLimits();
|
|
43
40
|
|
|
44
41
|
const view = await transaction.readItem(
|
|
45
42
|
item.id,
|
|
46
43
|
brandSchemas.instant.parse(5000),
|
|
47
|
-
|
|
44
|
+
buildMockItemRules(),
|
|
48
45
|
);
|
|
49
46
|
|
|
50
|
-
return {
|
|
47
|
+
return {
|
|
48
|
+
attempt: read?.state,
|
|
49
|
+
facts: facts.length,
|
|
50
|
+
held: limits.map((limit) => limit.held),
|
|
51
|
+
item: view?.derived,
|
|
52
|
+
};
|
|
51
53
|
});
|
|
52
54
|
|
|
53
55
|
return {
|
|
54
56
|
received: { result, state },
|
|
55
57
|
expected: {
|
|
56
|
-
result: { kind: '
|
|
57
|
-
state: { attempt: 'starting', facts: 2, item: { state: 'running' } },
|
|
58
|
+
result: { kind: 'guard-mismatch', table: 'attempts', key: { id: attempt.id } },
|
|
59
|
+
state: { attempt: 'starting', facts: 2, held: [1], item: { state: 'running' } },
|
|
58
60
|
},
|
|
59
61
|
};
|
|
60
62
|
},
|
|
@@ -62,15 +64,20 @@ export const claimStoreRollbackCases: readonly ClaimStoreCase[] = [
|
|
|
62
64
|
[
|
|
63
65
|
'#applyChange takes back every write of a change that throws',
|
|
64
66
|
async (store) => {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
67
|
+
const workers = brandSchemas.limitName.parse('workers');
|
|
68
|
+
const filed = buildMockV1ItemRow();
|
|
69
|
+
const claimed = buildMockV1ItemRow();
|
|
70
|
+
const attempt = buildMockV1AttemptRow({ item: claimed.id, state: 'starting' });
|
|
68
71
|
const fileChange = buildMockFileItemChange(filed);
|
|
69
|
-
const claim = buildMockClaimChange(attempt);
|
|
72
|
+
const claim = buildMockClaimChange(attempt, [workers]);
|
|
70
73
|
const reusedFactId: StoreChange = { ...claim, facts: [...claim.facts, ...fileChange.facts] };
|
|
71
74
|
|
|
72
75
|
await store.exclusive(async (transaction) => {
|
|
73
|
-
await transaction.
|
|
76
|
+
await transaction.syncLimits(
|
|
77
|
+
[{ name: workers, max: 1, counts: {} }],
|
|
78
|
+
brandSchemas.instant.parse(500),
|
|
79
|
+
);
|
|
80
|
+
|
|
74
81
|
await transaction.applyChange(fileChange);
|
|
75
82
|
await transaction.applyChange(buildMockFileItemChange(claimed));
|
|
76
83
|
await transaction.applyChange(reusedFactId).catch(() => null);
|
|
@@ -78,23 +85,17 @@ export const claimStoreRollbackCases: readonly ClaimStoreCase[] = [
|
|
|
78
85
|
|
|
79
86
|
const state: unknown = await store.read(async (transaction) => ({
|
|
80
87
|
attempt: await transaction.readAttempt(attempt.id),
|
|
81
|
-
|
|
88
|
+
seats: await transaction.listLimitSeats(),
|
|
82
89
|
}));
|
|
83
90
|
|
|
84
|
-
return {
|
|
85
|
-
received: state,
|
|
86
|
-
expected: {
|
|
87
|
-
attempt: null,
|
|
88
|
-
slots: [{ pool: attempt.pool, slot: 1, holder: null, gen: 0, retired: false }],
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
+
return { received: state, expected: { attempt: null, seats: [] } };
|
|
91
92
|
},
|
|
92
93
|
],
|
|
93
94
|
[
|
|
94
95
|
'#applyChange keeps a heartbeat started beside a change it refuses',
|
|
95
96
|
async (store) => {
|
|
96
|
-
const filed =
|
|
97
|
-
const missing =
|
|
97
|
+
const filed = buildMockV1ItemRow();
|
|
98
|
+
const missing = buildMockV1ItemRow();
|
|
98
99
|
const runner = brandSchemas.runnerId.parse('runner-1');
|
|
99
100
|
|
|
100
101
|
await store.exclusive((transaction) =>
|
|
@@ -117,12 +118,12 @@ export const claimStoreRollbackCases: readonly ClaimStoreCase[] = [
|
|
|
117
118
|
type: 'boxxy.item.edited',
|
|
118
119
|
entity: `boxxy:item:${missing.id}`,
|
|
119
120
|
actor: 'boxxy:owner:local',
|
|
120
|
-
payload: { title: 'Mend the gate' },
|
|
121
|
+
payload: { title: 'Mend the gate', byAttempt: null },
|
|
121
122
|
},
|
|
122
123
|
],
|
|
123
124
|
}),
|
|
124
125
|
transaction
|
|
125
|
-
.
|
|
126
|
+
.listLimits()
|
|
126
127
|
.then(() => transaction.writeHeartbeat(runner, brandSchemas.instant.parse(1000))),
|
|
127
128
|
]),
|
|
128
129
|
);
|
|
@@ -135,11 +136,11 @@ export const claimStoreRollbackCases: readonly ClaimStoreCase[] = [
|
|
|
135
136
|
[
|
|
136
137
|
'#applyChange runs two changes started together on one call one at a time',
|
|
137
138
|
async (store) => {
|
|
138
|
-
const claimed =
|
|
139
|
-
const later =
|
|
140
|
-
const first =
|
|
139
|
+
const claimed = buildMockV1ItemRow();
|
|
140
|
+
const later = buildMockV1ItemRow();
|
|
141
|
+
const first = buildMockV1AttemptRow({ item: claimed.id, state: 'starting', leaseGen: 1 });
|
|
141
142
|
|
|
142
|
-
const second =
|
|
143
|
+
const second = buildMockV1AttemptRow({
|
|
143
144
|
item: claimed.id,
|
|
144
145
|
state: 'starting',
|
|
145
146
|
leaseGen: 2,
|
|
@@ -147,7 +148,6 @@ export const claimStoreRollbackCases: readonly ClaimStoreCase[] = [
|
|
|
147
148
|
});
|
|
148
149
|
|
|
149
150
|
await store.exclusive(async (transaction) => {
|
|
150
|
-
await transaction.syncPoolSlots([{ name: first.pool, slots: 2 }]);
|
|
151
151
|
await transaction.applyChange(buildMockFileItemChange(claimed));
|
|
152
152
|
await transaction.applyChange(buildMockClaimChange(first));
|
|
153
153
|
});
|
|
@@ -160,13 +160,13 @@ export const claimStoreRollbackCases: readonly ClaimStoreCase[] = [
|
|
|
160
160
|
);
|
|
161
161
|
|
|
162
162
|
const read = await store.read((transaction) =>
|
|
163
|
-
transaction.readItem(later.id, brandSchemas.instant.parse(5000),
|
|
163
|
+
transaction.readItem(later.id, brandSchemas.instant.parse(5000), buildMockItemRules()),
|
|
164
164
|
);
|
|
165
165
|
|
|
166
166
|
return {
|
|
167
167
|
received: { results, item: read?.id },
|
|
168
168
|
expected: {
|
|
169
|
-
results: [{ kind: 'lease-held' }, { kind: 'applied'
|
|
169
|
+
results: [{ kind: 'lease-held' }, { kind: 'applied' }],
|
|
170
170
|
item: later.id,
|
|
171
171
|
},
|
|
172
172
|
};
|