@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.
Files changed (190) hide show
  1. package/dist/attempts/attempt-machine.d.ts +804 -0
  2. package/dist/attempts/attempt-moves.d.ts +2 -0
  3. package/dist/attempts/bind-attempt-session.d.ts +14 -0
  4. package/dist/attempts/build-attempt-change.d.ts +73 -0
  5. package/dist/attempts/build-attempt-fact.d.ts +3 -0
  6. package/dist/attempts/end-item-attempts.d.ts +18 -0
  7. package/dist/attempts/is-same-guard.d.ts +5 -0
  8. package/dist/attempts/pick-call-attempt.d.ts +10 -0
  9. package/dist/attempts/stop-stray-session.d.ts +11 -0
  10. package/dist/attempts/types.d.ts +142 -0
  11. package/dist/attempts/write-attempt-move.d.ts +7 -0
  12. package/dist/attempts/write-session-bind.d.ts +3 -0
  13. package/dist/claims/build-snapshot-hash.d.ts +2 -0
  14. package/dist/claims/claim-next.d.ts +6 -0
  15. package/dist/claims/pick-kept-session.d.ts +9 -0
  16. package/dist/claims/request-wake.d.ts +3 -0
  17. package/dist/claims/resume-claim.d.ts +3 -0
  18. package/dist/claims/start-claim.d.ts +3 -0
  19. package/dist/claims/types.d.ts +44 -0
  20. package/dist/closeouts/build-settle-plans.d.ts +17 -0
  21. package/dist/closeouts/close-out-attempt.d.ts +3 -0
  22. package/dist/closeouts/effect-handlers.d.ts +2 -0
  23. package/dist/closeouts/pick-settlement.d.ts +14 -0
  24. package/dist/closeouts/settle-attempt.d.ts +3 -0
  25. package/dist/closeouts/types.d.ts +50 -0
  26. package/dist/index.d.ts +38 -0
  27. package/dist/notices/constants.d.ts +4 -0
  28. package/dist/notices/create-null-notifier.d.ts +2 -0
  29. package/dist/notices/send-notices.d.ts +2 -0
  30. package/dist/notices/types.d.ts +11 -0
  31. package/dist/outbox/build-backoff-ms.d.ts +2 -0
  32. package/dist/outbox/build-initial-sender-state.d.ts +2 -0
  33. package/dist/outbox/build-outbox-status.d.ts +15 -0
  34. package/dist/outbox/constants.d.ts +2 -0
  35. package/dist/outbox/measure-wire-bytes.d.ts +2 -0
  36. package/dist/outbox/pick-sender-move.d.ts +10 -0
  37. package/dist/outbox/run-sender-once.d.ts +2 -0
  38. package/dist/outbox/take-batch.d.ts +3 -0
  39. package/dist/outbox/types.d.ts +61 -0
  40. package/dist/pull-requests/build-link-change.d.ts +11 -0
  41. package/dist/pull-requests/build-state-fact.d.ts +8 -0
  42. package/dist/pull-requests/collect-attempt-trailers.d.ts +2 -0
  43. package/dist/pull-requests/collect-seen-state.d.ts +7 -0
  44. package/dist/pull-requests/constants.d.ts +3 -0
  45. package/dist/pull-requests/link-pull-request.d.ts +2 -0
  46. package/dist/pull-requests/parse-pull-request-url.d.ts +2 -0
  47. package/dist/pull-requests/types.d.ts +37 -0
  48. package/dist/pull-requests/watch-pull-requests.d.ts +2 -0
  49. package/dist/readings/act-on-reading.d.ts +3 -0
  50. package/dist/readings/find-waiting-since.d.ts +9 -0
  51. package/dist/readings/pick-reading-action.d.ts +2 -0
  52. package/dist/readings/types.d.ts +79 -0
  53. package/dist/sessions/build-keep-writes.d.ts +9 -0
  54. package/dist/sessions/build-kept-clear.d.ts +2 -0
  55. package/dist/sessions/forget-sessions.d.ts +2 -0
  56. package/dist/sessions/is-kept-due.d.ts +2 -0
  57. package/dist/sessions/resume-kept-session.d.ts +6 -0
  58. package/dist/sessions/types.d.ts +19 -0
  59. package/dist/test-utils/apply-memory-change.d.ts +3 -0
  60. package/dist/test-utils/build-memory-reader.d.ts +3 -0
  61. package/dist/test-utils/build-mock-claim-change.d.ts +2 -2
  62. package/dist/test-utils/build-mock-new-id.d.ts +2 -0
  63. package/dist/test-utils/build-mock-note-change.d.ts +2 -0
  64. package/dist/test-utils/build-mock-pull-request-change.d.ts +1 -0
  65. package/dist/test-utils/{build-mock-link-change.d.ts → build-mock-relation-change.d.ts} +2 -2
  66. package/dist/test-utils/build-mock-running-reading.d.ts +6 -0
  67. package/dist/test-utils/build-mock-start-change.d.ts +2 -2
  68. package/dist/test-utils/claim-store-close-cases.d.ts +2 -0
  69. package/dist/test-utils/claim-store-copy-cases.d.ts +2 -0
  70. package/dist/test-utils/claim-store-outbox-cases.d.ts +2 -0
  71. package/dist/test-utils/claim-store-race-cases.d.ts +2 -0
  72. package/dist/test-utils/claim-store-seat-cases.d.ts +2 -0
  73. package/dist/test-utils/claim-store-session-cases.d.ts +2 -0
  74. package/dist/test-utils/collect-memory-limit-fills.d.ts +3 -0
  75. package/dist/test-utils/compare-text.d.ts +1 -0
  76. package/dist/test-utils/create-fake-backoff.d.ts +2 -0
  77. package/dist/test-utils/create-fake-clock.d.ts +5 -0
  78. package/dist/test-utils/create-fake-fact-sink.d.ts +8 -0
  79. package/dist/test-utils/create-fake-forge.d.ts +17 -0
  80. package/dist/test-utils/create-fake-worker.d.ts +42 -0
  81. package/dist/test-utils/create-memory-claim-store.d.ts +2 -2
  82. package/dist/test-utils/create-test-dispatch.d.ts +46 -0
  83. package/dist/test-utils/forge-contract.d.ts +15 -0
  84. package/dist/test-utils/is-same-held-seat.d.ts +2 -0
  85. package/dist/test-utils/types.d.ts +12 -2
  86. package/dist/test-utils/worker-contract.d.ts +14 -0
  87. package/dist/timeouts.d.ts +2 -0
  88. package/dist/types.d.ts +13 -0
  89. package/package.json +10 -3
  90. package/src/attempts/attempt-machine.ts +147 -0
  91. package/src/attempts/attempt-moves.ts +218 -0
  92. package/src/attempts/bind-attempt-session.ts +61 -0
  93. package/src/attempts/build-attempt-change.ts +540 -0
  94. package/src/attempts/build-attempt-fact.ts +135 -0
  95. package/src/attempts/end-item-attempts.ts +95 -0
  96. package/src/attempts/is-same-guard.ts +22 -0
  97. package/src/attempts/pick-call-attempt.ts +32 -0
  98. package/src/attempts/stop-stray-session.ts +48 -0
  99. package/src/attempts/types.ts +231 -0
  100. package/src/attempts/write-attempt-move.ts +41 -0
  101. package/src/attempts/write-session-bind.ts +47 -0
  102. package/src/claims/build-snapshot-hash.ts +9 -0
  103. package/src/claims/claim-next.ts +306 -0
  104. package/src/claims/pick-kept-session.ts +41 -0
  105. package/src/claims/request-wake.ts +17 -0
  106. package/src/claims/resume-claim.ts +56 -0
  107. package/src/claims/start-claim.ts +99 -0
  108. package/src/claims/types.ts +67 -0
  109. package/src/closeouts/build-settle-plans.ts +173 -0
  110. package/src/closeouts/close-out-attempt.ts +156 -0
  111. package/src/closeouts/effect-handlers.ts +232 -0
  112. package/src/closeouts/pick-settlement.ts +80 -0
  113. package/src/closeouts/settle-attempt.ts +161 -0
  114. package/src/closeouts/types.ts +87 -0
  115. package/src/index.ts +61 -0
  116. package/src/notices/constants.ts +7 -0
  117. package/src/notices/create-null-notifier.ts +6 -0
  118. package/src/notices/send-notices.ts +41 -0
  119. package/src/notices/types.ts +14 -0
  120. package/src/outbox/build-backoff-ms.ts +14 -0
  121. package/src/outbox/build-initial-sender-state.ts +14 -0
  122. package/src/outbox/build-outbox-status.ts +43 -0
  123. package/src/outbox/constants.ts +15 -0
  124. package/src/outbox/measure-wire-bytes.ts +31 -0
  125. package/src/outbox/pick-sender-move.ts +37 -0
  126. package/src/outbox/run-sender-once.ts +174 -0
  127. package/src/outbox/take-batch.ts +35 -0
  128. package/src/outbox/types.ts +76 -0
  129. package/src/pull-requests/build-link-change.ts +95 -0
  130. package/src/pull-requests/build-state-fact.ts +39 -0
  131. package/src/pull-requests/collect-attempt-trailers.ts +20 -0
  132. package/src/pull-requests/collect-seen-state.ts +19 -0
  133. package/src/pull-requests/constants.ts +5 -0
  134. package/src/pull-requests/link-pull-request.ts +119 -0
  135. package/src/pull-requests/parse-pull-request-url.ts +30 -0
  136. package/src/pull-requests/types.ts +63 -0
  137. package/src/pull-requests/watch-pull-requests.ts +308 -0
  138. package/src/readings/act-on-reading.ts +318 -0
  139. package/src/readings/find-waiting-since.ts +46 -0
  140. package/src/readings/pick-reading-action.ts +315 -0
  141. package/src/readings/types.ts +98 -0
  142. package/src/sessions/build-keep-writes.ts +55 -0
  143. package/src/sessions/build-kept-clear.ts +12 -0
  144. package/src/sessions/forget-sessions.ts +106 -0
  145. package/src/sessions/is-kept-due.ts +27 -0
  146. package/src/sessions/resume-kept-session.ts +25 -0
  147. package/src/sessions/types.ts +39 -0
  148. package/src/test-utils/apply-memory-change.ts +252 -0
  149. package/src/test-utils/build-memory-reader.ts +364 -0
  150. package/src/test-utils/build-mock-bind-change.ts +18 -11
  151. package/src/test-utils/build-mock-claim-change.ts +18 -8
  152. package/src/test-utils/build-mock-file-item-change.ts +3 -1
  153. package/src/test-utils/build-mock-new-id.ts +12 -0
  154. package/src/test-utils/build-mock-note-change.ts +18 -0
  155. package/src/test-utils/build-mock-pause-change.ts +2 -10
  156. package/src/test-utils/build-mock-pull-request-change.ts +19 -9
  157. package/src/test-utils/{build-mock-link-change.ts → build-mock-relation-change.ts} +6 -6
  158. package/src/test-utils/build-mock-running-reading.ts +19 -0
  159. package/src/test-utils/build-mock-settle-change.ts +18 -6
  160. package/src/test-utils/build-mock-start-change.ts +13 -6
  161. package/src/test-utils/claim-store-attempt-reader-cases.ts +163 -133
  162. package/src/test-utils/claim-store-claim-cases.ts +168 -85
  163. package/src/test-utils/claim-store-close-cases.ts +95 -0
  164. package/src/test-utils/claim-store-contract.ts +12 -2
  165. package/src/test-utils/claim-store-copy-cases.ts +100 -0
  166. package/src/test-utils/claim-store-exclusive-cases.ts +65 -19
  167. package/src/test-utils/claim-store-guard-cases.ts +92 -82
  168. package/src/test-utils/claim-store-item-reader-cases.ts +291 -77
  169. package/src/test-utils/claim-store-outbox-cases.ts +281 -0
  170. package/src/test-utils/claim-store-race-cases.ts +150 -0
  171. package/src/test-utils/claim-store-rollback-cases.ts +50 -50
  172. package/src/test-utils/claim-store-seat-cases.ts +270 -0
  173. package/src/test-utils/claim-store-session-cases.ts +289 -0
  174. package/src/test-utils/collect-memory-limit-fills.ts +15 -0
  175. package/src/test-utils/compare-text.ts +8 -0
  176. package/src/test-utils/create-fake-backoff.ts +16 -0
  177. package/src/test-utils/create-fake-clock.ts +20 -0
  178. package/src/test-utils/create-fake-fact-sink.ts +74 -0
  179. package/src/test-utils/create-fake-forge.ts +117 -0
  180. package/src/test-utils/create-fake-worker.ts +303 -0
  181. package/src/test-utils/create-memory-claim-store.ts +73 -484
  182. package/src/test-utils/create-test-dispatch.ts +597 -0
  183. package/src/test-utils/forge-contract.ts +171 -0
  184. package/src/test-utils/is-same-held-seat.ts +11 -0
  185. package/src/test-utils/types.ts +14 -2
  186. package/src/test-utils/worker-contract.ts +192 -0
  187. package/src/timeouts.ts +17 -0
  188. package/src/types.ts +32 -0
  189. package/dist/test-utils/claim-store-pool-cases.d.ts +0 -2
  190. package/src/test-utils/claim-store-pool-cases.ts +0 -178
@@ -1,20 +1,23 @@
1
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';
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';
5
6
  import { buildMockClaimChange } from './build-mock-claim-change.ts';
6
7
  import { buildMockFileItemChange } from './build-mock-file-item-change.ts';
7
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';
8
11
  import type { ClaimStoreCase } from './types.ts';
9
12
 
10
13
  export const claimStoreClaimCases: readonly ClaimStoreCase[] = [
11
14
  [
12
15
  '#applyChange returns lease-held for a second live lease on one item phase',
13
16
  async (store) => {
14
- const item = buildMockItemRow();
15
- const first = buildMockAttemptRow({ item: item.id, state: 'starting', leaseGen: 1 });
17
+ const item = buildMockV1ItemRow();
18
+ const first = buildMockV1AttemptRow({ item: item.id, state: 'starting', leaseGen: 1 });
16
19
 
17
- const second = buildMockAttemptRow({
20
+ const second = buildMockV1AttemptRow({
18
21
  item: item.id,
19
22
  state: 'starting',
20
23
  leaseGen: 2,
@@ -22,7 +25,6 @@ export const claimStoreClaimCases: readonly ClaimStoreCase[] = [
22
25
  });
23
26
 
24
27
  await store.exclusive(async (transaction) => {
25
- await transaction.syncPoolSlots([{ name: first.pool, slots: 2 }]);
26
28
  await transaction.applyChange(buildMockFileItemChange(item));
27
29
  await transaction.applyChange(buildMockClaimChange(first));
28
30
  });
@@ -35,12 +37,13 @@ export const claimStoreClaimCases: readonly ClaimStoreCase[] = [
35
37
  },
36
38
  ],
37
39
  [
38
- '#applyChange writes no row of a change that returns lease-held',
40
+ '#applyChange writes no row and takes no seat for a change that returns lease-held',
39
41
  async (store) => {
40
- const item = buildMockItemRow();
41
- const first = buildMockAttemptRow({ item: item.id, state: 'starting', leaseGen: 1 });
42
+ const workers = brandSchemas.limitName.parse('workers');
43
+ const item = buildMockV1ItemRow();
44
+ const first = buildMockV1AttemptRow({ item: item.id, state: 'starting', leaseGen: 1 });
42
45
 
43
- const second = buildMockAttemptRow({
46
+ const second = buildMockV1AttemptRow({
44
47
  item: item.id,
45
48
  state: 'starting',
46
49
  leaseGen: 2,
@@ -48,21 +51,27 @@ export const claimStoreClaimCases: readonly ClaimStoreCase[] = [
48
51
  });
49
52
 
50
53
  await store.exclusive(async (transaction) => {
51
- await transaction.syncPoolSlots([{ name: first.pool, slots: 2 }]);
54
+ await transaction.syncLimits(
55
+ [{ name: workers, max: 2, counts: {} }],
56
+ brandSchemas.instant.parse(500),
57
+ );
58
+
52
59
  await transaction.applyChange(buildMockFileItemChange(item));
53
- await transaction.applyChange(buildMockClaimChange(first));
54
- await transaction.applyChange(buildMockClaimChange(second));
60
+ await transaction.applyChange(buildMockClaimChange(first, [workers]));
61
+ await transaction.applyChange(buildMockClaimChange(second, [workers]));
55
62
  });
56
63
 
57
64
  const state: unknown = await store.read(async (transaction) => {
58
65
  const snapshot = await transaction.readSnapshot(
59
66
  brandSchemas.instant.parse(5000),
60
- new Map(),
67
+ buildMockItemRules(),
61
68
  );
62
69
 
70
+ const seats = await transaction.listLimitSeats();
71
+
63
72
  return {
64
73
  attempt: await transaction.readAttempt(second.id),
65
- slots: await transaction.listPoolSlots(),
74
+ seats: seats.map((seat) => seat.attempt),
66
75
  lastGen: await transaction.readLastLeaseGen(item.id, first.phase),
67
76
  version: snapshot.storeVersion,
68
77
  };
@@ -70,41 +79,38 @@ export const claimStoreClaimCases: readonly ClaimStoreCase[] = [
70
79
 
71
80
  return {
72
81
  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
+ expected: { attempt: null, seats: [first.id], lastGen: 1, version: 2 },
82
83
  };
83
84
  },
84
85
  ],
85
86
  [
86
- '#applyChange returns no-free-slot and changes no row when every slot is held',
87
+ '#applyChange returns limit-full and changes no row when a limit has no free seat',
87
88
  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' });
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' });
92
94
 
93
95
  await store.exclusive(async (transaction) => {
94
- await transaction.syncPoolSlots([{ name: first.pool, slots: 1 }]);
96
+ await transaction.syncLimits(
97
+ [{ name: workers, max: 1, counts: {} }],
98
+ brandSchemas.instant.parse(500),
99
+ );
100
+
95
101
  await transaction.applyChange(buildMockFileItemChange(held));
96
102
  await transaction.applyChange(buildMockFileItemChange(waiting));
97
- await transaction.applyChange(buildMockClaimChange(first));
103
+ await transaction.applyChange(buildMockClaimChange(first, [workers]));
98
104
  });
99
105
 
100
106
  const result = await store.exclusive((transaction) =>
101
- transaction.applyChange(buildMockClaimChange(second)),
107
+ transaction.applyChange(buildMockClaimChange(second, [workers])),
102
108
  );
103
109
 
104
110
  const state: unknown = await store.read(async (transaction) => {
105
111
  const snapshot = await transaction.readSnapshot(
106
112
  brandSchemas.instant.parse(5000),
107
- new Map(),
113
+ buildMockItemRules(),
108
114
  );
109
115
 
110
116
  return {
@@ -117,88 +123,92 @@ export const claimStoreClaimCases: readonly ClaimStoreCase[] = [
117
123
  return {
118
124
  received: { result, state },
119
125
  expected: {
120
- result: { kind: 'no-free-slot' },
126
+ result: { kind: 'limit-full', limit: 'workers' },
121
127
  state: { attempt: null, lastGen: 0, version: 3 },
122
128
  },
123
129
  };
124
130
  },
125
131
  ],
126
132
  [
127
- '#applyChange gives a claim the lowest free slot and its new gen',
133
+ '#applyChange takes the seats of a claim all or none',
128
134
  async (store) => {
129
- const item = buildMockItemRow();
130
- const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
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
+ });
131
147
 
132
148
  await store.exclusive(async (transaction) => {
133
- await transaction.syncPoolSlots([{ name: attempt.pool, slots: 2 }]);
134
- await transaction.applyChange(buildMockFileItemChange(item));
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]));
135
166
  });
136
167
 
137
168
  const result = await store.exclusive((transaction) =>
138
- transaction.applyChange(buildMockClaimChange(attempt)),
169
+ transaction.applyChange(buildMockClaimChange(second, [workers, ownerDesign])),
139
170
  );
140
171
 
172
+ const limits = await store.read((transaction) => transaction.listLimits());
173
+
141
174
  return {
142
- received: result,
175
+ received: { result, held: limits.map((limit) => [limit.name, limit.held]) },
143
176
  expected: {
144
- kind: 'applied',
145
- slot: { pool: attempt.pool, slot: 1, gen: 1 },
177
+ result: { kind: 'limit-full', limit: 'owner-design' },
178
+ held: [
179
+ ['owner-design', 1],
180
+ ['workers', 1],
181
+ ],
146
182
  },
147
183
  };
148
184
  },
149
185
  ],
150
186
  [
151
- '#applyChange writes the taken slot and slot gen into the attempt row',
187
+ '#applyChange returns unknown-limit for a take that names a limit the store does not hold',
152
188
  async (store) => {
153
- const item = buildMockItemRow();
154
- const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
189
+ const attempt = buildMockV1AttemptRow({ state: 'starting' });
155
190
 
156
191
  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));
192
+ await transaction.applyChange(
193
+ buildMockFileItemChange(buildMockV1ItemRow({ id: attempt.item })),
194
+ );
160
195
  });
161
196
 
162
- const read = await store.read((transaction) => transaction.readAttempt(attempt.id));
197
+ const result = await store.exclusive((transaction) =>
198
+ transaction.applyChange(
199
+ buildMockClaimChange(attempt, [brandSchemas.limitName.parse('reviews')]),
200
+ ),
201
+ );
163
202
 
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
- };
203
+ return { received: result, expected: { kind: 'unknown-limit', limit: 'reviews' } };
194
204
  },
195
205
  ],
196
206
  [
197
207
  '#applyChange gives each lever insert its seq, in the row and the fact',
198
208
  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' });
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' });
202
212
 
203
213
  await store.exclusive(async (transaction) => {
204
214
  await transaction.applyChange(buildMockFileItemChange(item));
@@ -207,7 +217,12 @@ export const claimStoreClaimCases: readonly ClaimStoreCase[] = [
207
217
  });
208
218
 
209
219
  const state: unknown = await store.read(async (transaction) => {
210
- const items = await transaction.listItems({}, brandSchemas.instant.parse(5000), new Map());
220
+ const items = await transaction.listItems(
221
+ {},
222
+ brandSchemas.instant.parse(5000),
223
+ buildMockItemRules(),
224
+ );
225
+
211
226
  const facts = await transaction.readUnsentFacts(10);
212
227
 
213
228
  return { levers: items[0]?.levers, payloads: facts.slice(1).map((fact) => fact.payload) };
@@ -228,4 +243,72 @@ export const claimStoreClaimCases: readonly ClaimStoreCase[] = [
228
243
  };
229
244
  },
230
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
+ ],
231
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
+ ];
@@ -1,10 +1,15 @@
1
1
  import { claimStoreAttemptReaderCases } from './claim-store-attempt-reader-cases.ts';
2
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';
3
5
  import { claimStoreExclusiveCases } from './claim-store-exclusive-cases.ts';
4
6
  import { claimStoreGuardCases } from './claim-store-guard-cases.ts';
5
7
  import { claimStoreItemReaderCases } from './claim-store-item-reader-cases.ts';
6
- import { claimStorePoolCases } from './claim-store-pool-cases.ts';
8
+ import { claimStoreOutboxCases } from './claim-store-outbox-cases.ts';
9
+ import { claimStoreRaceCases } from './claim-store-race-cases.ts';
7
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';
8
13
  import type { ClaimStoreCase } from './types.ts';
9
14
 
10
15
  export const claimStoreContract: readonly ClaimStoreCase[] = [
@@ -12,7 +17,12 @@ export const claimStoreContract: readonly ClaimStoreCase[] = [
12
17
  ...claimStoreClaimCases,
13
18
  ...claimStoreGuardCases,
14
19
  ...claimStoreRollbackCases,
15
- ...claimStorePoolCases,
20
+ ...claimStoreCopyCases,
21
+ ...claimStoreCloseCases,
22
+ ...claimStoreSeatCases,
16
23
  ...claimStoreItemReaderCases,
17
24
  ...claimStoreAttemptReaderCases,
25
+ ...claimStoreSessionCases,
26
+ ...claimStoreOutboxCases,
27
+ ...claimStoreRaceCases,
18
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
+ ];