@optimystic/db-core 0.14.1 → 0.16.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 (301) hide show
  1. package/dist/src/blocks/block-store.d.ts +18 -1
  2. package/dist/src/blocks/block-store.d.ts.map +1 -1
  3. package/dist/src/blocks/helpers.d.ts +2 -2
  4. package/dist/src/blocks/helpers.d.ts.map +1 -1
  5. package/dist/src/blocks/helpers.js +2 -2
  6. package/dist/src/blocks/helpers.js.map +1 -1
  7. package/dist/src/btree/btree.d.ts +27 -6
  8. package/dist/src/btree/btree.d.ts.map +1 -1
  9. package/dist/src/btree/btree.js +104 -61
  10. package/dist/src/btree/btree.js.map +1 -1
  11. package/dist/src/chain/chain.d.ts.map +1 -1
  12. package/dist/src/chain/chain.js +11 -1
  13. package/dist/src/chain/chain.js.map +1 -1
  14. package/dist/src/cluster/index.d.ts +1 -0
  15. package/dist/src/cluster/index.d.ts.map +1 -1
  16. package/dist/src/cluster/index.js +1 -0
  17. package/dist/src/cluster/index.js.map +1 -1
  18. package/dist/src/cluster/membership.d.ts +49 -0
  19. package/dist/src/cluster/membership.d.ts.map +1 -0
  20. package/dist/src/cluster/membership.js +75 -0
  21. package/dist/src/cluster/membership.js.map +1 -0
  22. package/dist/src/cluster/structs.d.ts +50 -1
  23. package/dist/src/cluster/structs.d.ts.map +1 -1
  24. package/dist/src/cluster/structs.js +9 -1
  25. package/dist/src/cluster/structs.js.map +1 -1
  26. package/dist/src/cohort-topic/addressing.d.ts +8 -2
  27. package/dist/src/cohort-topic/addressing.d.ts.map +1 -1
  28. package/dist/src/cohort-topic/addressing.js +11 -3
  29. package/dist/src/cohort-topic/addressing.js.map +1 -1
  30. package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts +13 -7
  31. package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts.map +1 -1
  32. package/dist/src/cohort-topic/antidos/bootstrap-evidence.js +17 -6
  33. package/dist/src/cohort-topic/antidos/bootstrap-evidence.js.map +1 -1
  34. package/dist/src/cohort-topic/antidos/replay-guard.d.ts +17 -0
  35. package/dist/src/cohort-topic/antidos/replay-guard.d.ts.map +1 -1
  36. package/dist/src/cohort-topic/antidos/replay-guard.js +33 -0
  37. package/dist/src/cohort-topic/antidos/replay-guard.js.map +1 -1
  38. package/dist/src/cohort-topic/antidos/topic-budget.d.ts +7 -0
  39. package/dist/src/cohort-topic/antidos/topic-budget.d.ts.map +1 -1
  40. package/dist/src/cohort-topic/antidos/topic-budget.js +8 -2
  41. package/dist/src/cohort-topic/antidos/topic-budget.js.map +1 -1
  42. package/dist/src/cohort-topic/coldstart.d.ts +34 -14
  43. package/dist/src/cohort-topic/coldstart.d.ts.map +1 -1
  44. package/dist/src/cohort-topic/coldstart.js +25 -11
  45. package/dist/src/cohort-topic/coldstart.js.map +1 -1
  46. package/dist/src/cohort-topic/gossip/bus.d.ts +12 -1
  47. package/dist/src/cohort-topic/gossip/bus.d.ts.map +1 -1
  48. package/dist/src/cohort-topic/gossip/bus.js +27 -1
  49. package/dist/src/cohort-topic/gossip/bus.js.map +1 -1
  50. package/dist/src/cohort-topic/gossip/view.d.ts +8 -0
  51. package/dist/src/cohort-topic/gossip/view.d.ts.map +1 -1
  52. package/dist/src/cohort-topic/gossip/view.js +17 -1
  53. package/dist/src/cohort-topic/gossip/view.js.map +1 -1
  54. package/dist/src/cohort-topic/member-engine.d.ts +10 -3
  55. package/dist/src/cohort-topic/member-engine.d.ts.map +1 -1
  56. package/dist/src/cohort-topic/member-engine.js +29 -14
  57. package/dist/src/cohort-topic/member-engine.js.map +1 -1
  58. package/dist/src/cohort-topic/membership/publisher.d.ts +14 -7
  59. package/dist/src/cohort-topic/membership/publisher.d.ts.map +1 -1
  60. package/dist/src/cohort-topic/membership/publisher.js +15 -27
  61. package/dist/src/cohort-topic/membership/publisher.js.map +1 -1
  62. package/dist/src/cohort-topic/membership/verifier.d.ts +35 -0
  63. package/dist/src/cohort-topic/membership/verifier.d.ts.map +1 -1
  64. package/dist/src/cohort-topic/membership/verifier.js +88 -3
  65. package/dist/src/cohort-topic/membership/verifier.js.map +1 -1
  66. package/dist/src/cohort-topic/promotion.d.ts +7 -0
  67. package/dist/src/cohort-topic/promotion.d.ts.map +1 -1
  68. package/dist/src/cohort-topic/promotion.js +16 -2
  69. package/dist/src/cohort-topic/promotion.js.map +1 -1
  70. package/dist/src/cohort-topic/registration/renewal.d.ts +18 -0
  71. package/dist/src/cohort-topic/registration/renewal.d.ts.map +1 -1
  72. package/dist/src/cohort-topic/registration/renewal.js +54 -0
  73. package/dist/src/cohort-topic/registration/renewal.js.map +1 -1
  74. package/dist/src/cohort-topic/registration/types.d.ts +12 -0
  75. package/dist/src/cohort-topic/registration/types.d.ts.map +1 -1
  76. package/dist/src/cohort-topic/registration/types.js +14 -0
  77. package/dist/src/cohort-topic/registration/types.js.map +1 -1
  78. package/dist/src/cohort-topic/service.d.ts +4 -3
  79. package/dist/src/cohort-topic/service.d.ts.map +1 -1
  80. package/dist/src/cohort-topic/service.js +38 -12
  81. package/dist/src/cohort-topic/service.js.map +1 -1
  82. package/dist/src/cohort-topic/sig/payloads.d.ts +23 -7
  83. package/dist/src/cohort-topic/sig/payloads.d.ts.map +1 -1
  84. package/dist/src/cohort-topic/sig/payloads.js +22 -6
  85. package/dist/src/cohort-topic/sig/payloads.js.map +1 -1
  86. package/dist/src/cohort-topic/traffic.d.ts +6 -0
  87. package/dist/src/cohort-topic/traffic.d.ts.map +1 -1
  88. package/dist/src/cohort-topic/traffic.js +16 -1
  89. package/dist/src/cohort-topic/traffic.js.map +1 -1
  90. package/dist/src/cohort-topic/walk.d.ts +22 -7
  91. package/dist/src/cohort-topic/walk.d.ts.map +1 -1
  92. package/dist/src/cohort-topic/walk.js +78 -18
  93. package/dist/src/cohort-topic/walk.js.map +1 -1
  94. package/dist/src/cohort-topic/wire/codec.d.ts +3 -1
  95. package/dist/src/cohort-topic/wire/codec.d.ts.map +1 -1
  96. package/dist/src/cohort-topic/wire/codec.js +7 -1
  97. package/dist/src/cohort-topic/wire/codec.js.map +1 -1
  98. package/dist/src/cohort-topic/wire/index.d.ts +1 -1
  99. package/dist/src/cohort-topic/wire/index.d.ts.map +1 -1
  100. package/dist/src/cohort-topic/wire/index.js +1 -1
  101. package/dist/src/cohort-topic/wire/index.js.map +1 -1
  102. package/dist/src/cohort-topic/wire/payloads.d.ts +5 -4
  103. package/dist/src/cohort-topic/wire/payloads.d.ts.map +1 -1
  104. package/dist/src/cohort-topic/wire/payloads.js +14 -5
  105. package/dist/src/cohort-topic/wire/payloads.js.map +1 -1
  106. package/dist/src/cohort-topic/wire/primitives.d.ts +65 -0
  107. package/dist/src/cohort-topic/wire/primitives.d.ts.map +1 -0
  108. package/dist/src/cohort-topic/wire/primitives.js +173 -0
  109. package/dist/src/cohort-topic/wire/primitives.js.map +1 -0
  110. package/dist/src/cohort-topic/wire/types.d.ts +132 -5
  111. package/dist/src/cohort-topic/wire/types.d.ts.map +1 -1
  112. package/dist/src/cohort-topic/wire/validate.d.ts +15 -5
  113. package/dist/src/cohort-topic/wire/validate.d.ts.map +1 -1
  114. package/dist/src/cohort-topic/wire/validate.js +146 -123
  115. package/dist/src/cohort-topic/wire/validate.js.map +1 -1
  116. package/dist/src/collection/collection.d.ts +23 -12
  117. package/dist/src/collection/collection.d.ts.map +1 -1
  118. package/dist/src/collection/collection.js +111 -35
  119. package/dist/src/collection/collection.js.map +1 -1
  120. package/dist/src/collection/struct.d.ts +37 -2
  121. package/dist/src/collection/struct.d.ts.map +1 -1
  122. package/dist/src/collection/struct.js +16 -1
  123. package/dist/src/collection/struct.js.map +1 -1
  124. package/dist/src/collections/tree/tree.d.ts +18 -1
  125. package/dist/src/collections/tree/tree.d.ts.map +1 -1
  126. package/dist/src/collections/tree/tree.js +36 -6
  127. package/dist/src/collections/tree/tree.js.map +1 -1
  128. package/dist/src/index.d.ts +1 -0
  129. package/dist/src/index.d.ts.map +1 -1
  130. package/dist/src/index.js +1 -0
  131. package/dist/src/index.js.map +1 -1
  132. package/dist/src/log/log.d.ts +26 -3
  133. package/dist/src/log/log.d.ts.map +1 -1
  134. package/dist/src/log/log.js +34 -7
  135. package/dist/src/log/log.js.map +1 -1
  136. package/dist/src/log/struct.d.ts +5 -4
  137. package/dist/src/log/struct.d.ts.map +1 -1
  138. package/dist/src/log/struct.js.map +1 -1
  139. package/dist/src/matchmaking/wire.d.ts +2 -1
  140. package/dist/src/matchmaking/wire.d.ts.map +1 -1
  141. package/dist/src/matchmaking/wire.js +13 -92
  142. package/dist/src/matchmaking/wire.js.map +1 -1
  143. package/dist/src/network/i-key-network.d.ts +9 -0
  144. package/dist/src/network/i-key-network.d.ts.map +1 -1
  145. package/dist/src/network/i-repo.d.ts +8 -0
  146. package/dist/src/network/i-repo.d.ts.map +1 -1
  147. package/dist/src/network/struct.d.ts +10 -0
  148. package/dist/src/network/struct.d.ts.map +1 -1
  149. package/dist/src/reactivity/push-state.d.ts.map +1 -1
  150. package/dist/src/reactivity/push-state.js +4 -33
  151. package/dist/src/reactivity/push-state.js.map +1 -1
  152. package/dist/src/reactivity/wire-validate.d.ts +7 -22
  153. package/dist/src/reactivity/wire-validate.d.ts.map +1 -1
  154. package/dist/src/reactivity/wire-validate.js +7 -73
  155. package/dist/src/reactivity/wire-validate.js.map +1 -1
  156. package/dist/src/reactivity/wire.js +3 -3
  157. package/dist/src/reactivity/wire.js.map +1 -1
  158. package/dist/src/testing/async-wait.d.ts +23 -0
  159. package/dist/src/testing/async-wait.d.ts.map +1 -0
  160. package/dist/src/testing/async-wait.js +42 -0
  161. package/dist/src/testing/async-wait.js.map +1 -0
  162. package/dist/src/testing/index.d.ts +3 -0
  163. package/dist/src/testing/index.d.ts.map +1 -0
  164. package/dist/src/testing/index.js +3 -0
  165. package/dist/src/testing/index.js.map +1 -0
  166. package/dist/src/testing/test-transactor.d.ts +44 -0
  167. package/dist/src/testing/test-transactor.d.ts.map +1 -0
  168. package/dist/src/testing/test-transactor.js +423 -0
  169. package/dist/src/testing/test-transactor.js.map +1 -0
  170. package/dist/src/transaction/actions-engine.d.ts +6 -3
  171. package/dist/src/transaction/actions-engine.d.ts.map +1 -1
  172. package/dist/src/transaction/actions-engine.js +8 -7
  173. package/dist/src/transaction/actions-engine.js.map +1 -1
  174. package/dist/src/transaction/coordinator.d.ts +43 -22
  175. package/dist/src/transaction/coordinator.d.ts.map +1 -1
  176. package/dist/src/transaction/coordinator.js +443 -178
  177. package/dist/src/transaction/coordinator.js.map +1 -1
  178. package/dist/src/transaction/errors.d.ts +82 -0
  179. package/dist/src/transaction/errors.d.ts.map +1 -0
  180. package/dist/src/transaction/errors.js +93 -0
  181. package/dist/src/transaction/errors.js.map +1 -0
  182. package/dist/src/transaction/index.d.ts +6 -3
  183. package/dist/src/transaction/index.d.ts.map +1 -1
  184. package/dist/src/transaction/index.js +4 -2
  185. package/dist/src/transaction/index.js.map +1 -1
  186. package/dist/src/transaction/operations-hash.d.ts +119 -0
  187. package/dist/src/transaction/operations-hash.d.ts.map +1 -0
  188. package/dist/src/transaction/operations-hash.js +180 -0
  189. package/dist/src/transaction/operations-hash.js.map +1 -0
  190. package/dist/src/transaction/read-dependency-collector.d.ts +43 -0
  191. package/dist/src/transaction/read-dependency-collector.d.ts.map +1 -0
  192. package/dist/src/transaction/read-dependency-collector.js +69 -0
  193. package/dist/src/transaction/read-dependency-collector.js.map +1 -0
  194. package/dist/src/transaction/session.d.ts +12 -3
  195. package/dist/src/transaction/session.d.ts.map +1 -1
  196. package/dist/src/transaction/session.js +35 -9
  197. package/dist/src/transaction/session.js.map +1 -1
  198. package/dist/src/transaction/transaction.d.ts +162 -1
  199. package/dist/src/transaction/transaction.d.ts.map +1 -1
  200. package/dist/src/transaction/transaction.js +85 -3
  201. package/dist/src/transaction/transaction.js.map +1 -1
  202. package/dist/src/transaction/validator.d.ts +3 -11
  203. package/dist/src/transaction/validator.d.ts.map +1 -1
  204. package/dist/src/transaction/validator.js +69 -28
  205. package/dist/src/transaction/validator.js.map +1 -1
  206. package/dist/src/transactor/network-transactor.d.ts +35 -0
  207. package/dist/src/transactor/network-transactor.d.ts.map +1 -1
  208. package/dist/src/transactor/network-transactor.js +146 -40
  209. package/dist/src/transactor/network-transactor.js.map +1 -1
  210. package/dist/src/transactor/transactor-source.d.ts +19 -5
  211. package/dist/src/transactor/transactor-source.d.ts.map +1 -1
  212. package/dist/src/transactor/transactor-source.js +43 -11
  213. package/dist/src/transactor/transactor-source.js.map +1 -1
  214. package/dist/src/transform/atomic-proxy.d.ts +35 -5
  215. package/dist/src/transform/atomic-proxy.d.ts.map +1 -1
  216. package/dist/src/transform/atomic-proxy.js +39 -7
  217. package/dist/src/transform/atomic-proxy.js.map +1 -1
  218. package/dist/src/transform/cache-source.d.ts +36 -5
  219. package/dist/src/transform/cache-source.d.ts.map +1 -1
  220. package/dist/src/transform/cache-source.js +84 -8
  221. package/dist/src/transform/cache-source.js.map +1 -1
  222. package/dist/src/transform/helpers.d.ts.map +1 -1
  223. package/dist/src/transform/helpers.js +8 -2
  224. package/dist/src/transform/helpers.js.map +1 -1
  225. package/dist/src/transform/tracker.d.ts +17 -2
  226. package/dist/src/transform/tracker.d.ts.map +1 -1
  227. package/dist/src/transform/tracker.js +66 -11
  228. package/dist/src/transform/tracker.js.map +1 -1
  229. package/dist/src/utility/backoff.d.ts +48 -0
  230. package/dist/src/utility/backoff.d.ts.map +1 -0
  231. package/dist/src/utility/backoff.js +60 -0
  232. package/dist/src/utility/backoff.js.map +1 -0
  233. package/dist/src/utility/pending.d.ts +1 -0
  234. package/dist/src/utility/pending.d.ts.map +1 -1
  235. package/dist/src/utility/pending.js +3 -1
  236. package/dist/src/utility/pending.js.map +1 -1
  237. package/package.json +4 -4
  238. package/src/blocks/block-store.ts +19 -1
  239. package/src/blocks/helpers.ts +3 -3
  240. package/src/btree/btree.ts +108 -63
  241. package/src/chain/chain.ts +11 -1
  242. package/src/cluster/index.ts +1 -0
  243. package/src/cluster/membership.ts +85 -0
  244. package/src/cluster/structs.ts +51 -1
  245. package/src/cohort-topic/addressing.ts +12 -4
  246. package/src/cohort-topic/antidos/bootstrap-evidence.ts +21 -9
  247. package/src/cohort-topic/antidos/replay-guard.ts +37 -0
  248. package/src/cohort-topic/antidos/topic-budget.ts +17 -2
  249. package/src/cohort-topic/coldstart.ts +48 -15
  250. package/src/cohort-topic/gossip/bus.ts +39 -2
  251. package/src/cohort-topic/gossip/view.ts +26 -1
  252. package/src/cohort-topic/member-engine.ts +36 -15
  253. package/src/cohort-topic/membership/publisher.ts +25 -33
  254. package/src/cohort-topic/membership/verifier.ts +119 -3
  255. package/src/cohort-topic/promotion.ts +23 -2
  256. package/src/cohort-topic/registration/renewal.ts +73 -1
  257. package/src/cohort-topic/registration/types.ts +15 -0
  258. package/src/cohort-topic/service.ts +41 -15
  259. package/src/cohort-topic/sig/payloads.ts +29 -9
  260. package/src/cohort-topic/traffic.ts +23 -1
  261. package/src/cohort-topic/walk.ts +95 -24
  262. package/src/cohort-topic/wire/codec.ts +12 -0
  263. package/src/cohort-topic/wire/index.ts +2 -0
  264. package/src/cohort-topic/wire/payloads.ts +14 -5
  265. package/src/cohort-topic/wire/primitives.ts +188 -0
  266. package/src/cohort-topic/wire/types.ts +136 -4
  267. package/src/cohort-topic/wire/validate.ts +168 -134
  268. package/src/collection/collection.ts +123 -40
  269. package/src/collection/struct.ts +44 -2
  270. package/src/collections/tree/tree.ts +42 -5
  271. package/src/index.ts +1 -0
  272. package/src/log/log.ts +37 -9
  273. package/src/log/struct.ts +5 -4
  274. package/src/matchmaking/wire.ts +25 -102
  275. package/src/network/i-key-network.ts +10 -0
  276. package/src/network/i-repo.ts +8 -0
  277. package/src/network/struct.ts +10 -0
  278. package/src/reactivity/push-state.ts +4 -37
  279. package/src/reactivity/wire-validate.ts +7 -81
  280. package/src/reactivity/wire.ts +3 -3
  281. package/src/testing/async-wait.ts +65 -0
  282. package/src/testing/index.ts +2 -0
  283. package/src/testing/test-transactor.ts +468 -0
  284. package/src/transaction/actions-engine.ts +8 -6
  285. package/src/transaction/coordinator.ts +498 -229
  286. package/src/transaction/errors.ts +91 -0
  287. package/src/transaction/index.ts +23 -3
  288. package/src/transaction/operations-hash.ts +196 -0
  289. package/src/transaction/read-dependency-collector.ts +78 -0
  290. package/src/transaction/session.ts +37 -9
  291. package/src/transaction/transaction.ts +211 -4
  292. package/src/transaction/validator.ts +73 -48
  293. package/src/transactor/network-transactor.ts +164 -49
  294. package/src/transactor/transactor-source.ts +47 -12
  295. package/src/transform/atomic-proxy.ts +92 -49
  296. package/src/transform/cache-source.ts +87 -9
  297. package/src/transform/helpers.ts +8 -2
  298. package/src/transform/tracker.ts +69 -11
  299. package/src/utility/backoff.ts +95 -0
  300. package/src/utility/pending.ts +3 -1
  301. package/src/transaction/context.ts +0 -103
@@ -0,0 +1,468 @@
1
+ import { type ITransactor, type GetBlockResults, type ActionBlocks, type BlockActionStatus, type PendResult, type CommitResult, type PendRequest, type BlockId, type CommitRequest, type BlockGets, type IBlock, type ActionId, type ActionTransforms, type Transform, type Transforms, ensuredMap, Latches, type ClusterNomineesResult } from "../index.js";
2
+ import { applyTransform, blockIdsForTransforms, transformForBlockId, emptyTransforms, concatTransform, transformsFromTransform } from "../transform/index.js";
3
+
4
+ type RevisionNumber = number;
5
+
6
+ type BlockState = {
7
+ /** The current materialized block at each revision */
8
+ materializedBlocks: Map<RevisionNumber, IBlock>;
9
+ /** The latest revision number */
10
+ latestRev: RevisionNumber;
11
+ /** The action that created each revision */
12
+ revisionActions: Map<RevisionNumber, ActionId>;
13
+ /** Currently pending actions */
14
+ pendingActions: Map<ActionId, Transform>;
15
+ /** Committed actions */
16
+ committedActions: Map<ActionId, Transform>;
17
+ }
18
+
19
+ // Simple in-memory transactor for testing that maintains materialized blocks for every revision
20
+ export class TestTransactor implements ITransactor {
21
+ private blocks = new Map<BlockId, BlockState>();
22
+ available = true;
23
+ private getLocks = new Map<BlockId, Promise<() => void>>(); // Track lock releases
24
+
25
+ constructor() {}
26
+
27
+ async get(blockGets: BlockGets): Promise<GetBlockResults> {
28
+ this.checkAvailable();
29
+ const results: GetBlockResults = {};
30
+ const uniqueBlockIds = [...new Set(blockGets.blockIds)].sort(); // Ensure consistent lock order if needed, though get is read-only
31
+ const releases: (() => void)[] = [];
32
+
33
+ try {
34
+ // Acquire locks for all requested blocks to ensure consistent read
35
+ for (const blockId of uniqueBlockIds) {
36
+ const lockId = `TestTransactor.commit:${blockId}`; // Use the same lock as commit
37
+ // Wait for any existing lock promise to resolve before acquiring the next
38
+ let release = await this.getLocks.get(lockId);
39
+ if (release) await Promise.resolve(release); // Ensure previous lock released if overlapping calls happen
40
+
41
+ const releasePromise = Latches.acquire(lockId);
42
+ this.getLocks.set(blockId, releasePromise.then(r => () => {
43
+ r();
44
+ this.getLocks.delete(blockId); // Clean up map entry after release
45
+ }));
46
+ release = await releasePromise;
47
+ releases.push(release);
48
+ }
49
+
50
+ // --- Start of Critical Section (Read) ---
51
+ for (const blockId of blockGets.blockIds) {
52
+ const blockState = this.blocks.get(blockId);
53
+ if (!blockState) {
54
+ // Block doesn't exist yet
55
+ results[blockId] = {
56
+ block: undefined,
57
+ state: { latest: undefined, pendings: [] }
58
+ };
59
+ continue;
60
+ }
61
+
62
+ // Get the appropriate materialized block based on context
63
+ let block: IBlock | undefined;
64
+ if (blockGets.context?.actionId !== undefined) {
65
+ // If requesting a specific action, apply pending transform if it exists
66
+ const pendingTransform = blockState.pendingActions.get(blockGets.context.actionId);
67
+ if (pendingTransform) {
68
+ // Read latest committed block as base for pending transform
69
+ const baseBlock = blockState.materializedBlocks.get(blockState.latestRev);
70
+ block = applyTransformSafe(baseBlock, pendingTransform);
71
+ } else {
72
+ // Action not pending, maybe committed? Or maybe invalid actionId for context.
73
+ // For simplicity, return undefined block if specific pending action not found.
74
+ // A more complex impl might check committedActions history.
75
+ block = undefined;
76
+ }
77
+ } else if (blockGets.context?.committed) {
78
+ // Check context.committed for matching pending actions — mirrors coordinator
79
+ // behavior: context.committed proves the action succeeded, so pending blocks
80
+ // for that action should be served.
81
+ for (const { actionId: cId } of blockGets.context.committed) {
82
+ const pendingTransform = blockState.pendingActions.get(cId);
83
+ if (pendingTransform) {
84
+ const baseBlock = blockState.materializedBlocks.get(blockState.latestRev);
85
+ block = applyTransformSafe(baseBlock, pendingTransform);
86
+ break;
87
+ }
88
+ }
89
+ // Fall through to standard resolution if no pending match
90
+ if (block === undefined) {
91
+ if (blockGets.context.rev !== undefined) {
92
+ block = structuredClone(latestMaterializedAt(blockState, blockGets.context.rev));
93
+ } else {
94
+ block = structuredClone(blockState.materializedBlocks.get(blockState.latestRev));
95
+ }
96
+ }
97
+ } else if (blockGets.context?.rev !== undefined) {
98
+ // Return the materialized block at the highest revision ≤ requested
99
+ block = structuredClone(latestMaterializedAt(blockState, blockGets.context.rev));
100
+ } else {
101
+ // Otherwise return latest materialized block
102
+ block = structuredClone(blockState.materializedBlocks.get(blockState.latestRev));
103
+ }
104
+
105
+
106
+ const actionId = blockState.revisionActions.get(blockState.latestRev);
107
+ results[blockId] = {
108
+ block,
109
+ state: {
110
+ latest: actionId !== undefined ? {
111
+ rev: blockState.latestRev,
112
+ actionId
113
+ } : undefined,
114
+ pendings: Array.from(blockState.pendingActions.keys())
115
+ }
116
+ };
117
+ }
118
+ // --- End of Critical Section (Read) ---
119
+
120
+ } finally {
121
+ // Release locks in reverse order
122
+ releases.reverse().forEach(release => release());
123
+ }
124
+ return results;
125
+ }
126
+
127
+ async getStatus(actionRefs: ActionBlocks[]): Promise<BlockActionStatus[]> {
128
+ return actionRefs.map(ref => ({
129
+ ...ref,
130
+ statuses: ref.blockIds.map(blockId => {
131
+ const blockState = this.blocks.get(blockId);
132
+ if (!blockState) return 'aborted';
133
+ return blockState.pendingActions.has(ref.actionId) ? 'pending'
134
+ : Array.from(blockState.revisionActions.values()).some(actionId => actionId === ref.actionId) ? 'committed'
135
+ : 'aborted';
136
+ })
137
+ }));
138
+ }
139
+
140
+ async pend(request: PendRequest): Promise<PendResult> {
141
+ this.checkAvailable();
142
+ const { actionId, transforms, policy, rev } = request;
143
+ const blockIds = blockIdsForTransforms(transforms);
144
+ const conflictingPendings: { blockId: BlockId, actionId: ActionId }[] = [];
145
+ const missing: ActionTransforms[] = [];
146
+
147
+ // Check for conflicts (pending or committed based on rev/insert)
148
+ for (const blockId of blockIds) {
149
+ const blockState = this.blocks.get(blockId);
150
+ const blockTransform = transformForBlockId(transforms, blockId);
151
+ if (!blockTransform) continue; // Should not happen
152
+
153
+ if (blockState) {
154
+ // Check for existing pending actions
155
+ if (blockState.pendingActions.size > 0) {
156
+ blockState.pendingActions.forEach((_, pendingActionId) => {
157
+ conflictingPendings.push({ blockId, actionId: pendingActionId });
158
+ });
159
+ }
160
+
161
+ // Check for conflicting committed revisions (if rev specified or it's an insert)
162
+ if (rev !== undefined || blockTransform.insert) {
163
+ const checkRev = rev ?? 0; // Check from revision 0 if it's an insert
164
+ if (blockState.latestRev >= checkRev) {
165
+ // Collect conflicting committed actions
166
+ const missingForBlock = new Map<ActionId, { rev: number, transform: Transform }>();
167
+ for (let r = checkRev as number; r <= blockState.latestRev; r++) {
168
+ const committedActionId = blockState.revisionActions.get(r);
169
+ if (committedActionId !== undefined) {
170
+ const committedTransform = blockState.committedActions.get(committedActionId);
171
+ if (committedTransform) {
172
+ missingForBlock.set(committedActionId, { rev: r, transform: committedTransform });
173
+ }
174
+ }
175
+ }
176
+
177
+ // Add collected missing transforms for this block to the main missing list
178
+ for (const [mActionId, data] of missingForBlock.entries()) {
179
+ let existing = missing.find(m => m.actionId === mActionId);
180
+ if (!existing) {
181
+ existing = { actionId: mActionId, rev: data.rev, transforms: emptyTransforms() };
182
+ missing.push(existing);
183
+ }
184
+ existing.rev = Math.max(existing.rev ?? 0, data.rev);
185
+ existing.transforms = concatTransform(existing.transforms, blockId, data.transform);
186
+ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ // Handle failure due to committed conflicts first
193
+ if (missing.length > 0) {
194
+ return {
195
+ success: false,
196
+ missing
197
+ };
198
+ }
199
+
200
+ // Handle failure/retry due to pending conflicts
201
+ if (conflictingPendings.length > 0) {
202
+ if (policy === 'f') {
203
+ return { success: false, pending: conflictingPendings };
204
+ } else if (policy === 'r') {
205
+ // Simulate fetching pending transforms for 'r' policy
206
+ const pendingWithTransforms = conflictingPendings
207
+ .map(({ blockId: pBlockId, actionId: pActionId }) => {
208
+ const pBlockState = this.blocks.get(pBlockId);
209
+ const pTransform = pBlockState?.pendingActions.get(pActionId)
210
+ ?? pBlockState?.committedActions.get(pActionId); // Might have been committed since check
211
+ if (pTransform) {
212
+ return { blockId: pBlockId, actionId: pActionId, transform: pTransform };
213
+ }
214
+ return null; // Handle case where it disappeared (cancelled?)
215
+ })
216
+ .filter(p => p !== null) as { blockId: BlockId, actionId: ActionId, transform: Transform }[];
217
+
218
+ return {
219
+ success: false,
220
+ pending: pendingWithTransforms
221
+ };
222
+ }
223
+ // Policy 'w' allows proceeding despite pending transactions
224
+ }
225
+
226
+ // No fatal conflicts found, proceed to pend
227
+ for (const blockId of blockIds) {
228
+ const blockTransform = transformForBlockId(transforms, blockId);
229
+ if (blockTransform) {
230
+ const blockState = ensuredMap(this.blocks, blockId, () => newBlockState());
231
+ blockState.pendingActions.set(actionId, blockTransform);
232
+ }
233
+ }
234
+
235
+ // Return success, include pending list as per StorageRepo behavior
236
+ return {
237
+ success: true,
238
+ pending: conflictingPendings,
239
+ blockIds
240
+ } as PendResult;
241
+ }
242
+
243
+ async cancel(actionRef: ActionBlocks): Promise<void> {
244
+ this.checkAvailable();
245
+ for (const blockId of actionRef.blockIds) {
246
+ const blockState = this.blocks.get(blockId);
247
+ if (blockState) {
248
+ blockState.pendingActions.delete(actionRef.actionId);
249
+ }
250
+ }
251
+ }
252
+
253
+ async commit(request: CommitRequest): Promise<CommitResult> {
254
+ this.checkAvailable();
255
+ const { actionId, rev, blockIds } = request;
256
+ const uniqueBlockIds = [...new Set(blockIds)].sort();
257
+ const releases: (() => void)[] = [];
258
+
259
+ try {
260
+ // Simulate acquiring locks sequentially like StorageRepo
261
+ for (const id of uniqueBlockIds) {
262
+ const lockId = `TestTransactor.commit:${id}`;
263
+ const release = await Latches.acquire(lockId);
264
+ releases.push(release);
265
+ }
266
+
267
+ // --- Start of Critical Section (Simulated) ---
268
+
269
+ // Check for stale revisions
270
+ const staleBlocks = blockIds.filter(blockId => {
271
+ const blockState = this.blocks.get(blockId);
272
+ return blockState && blockState.latestRev >= rev;
273
+ });
274
+
275
+ if (staleBlocks.length > 0) {
276
+ // Collect missing actions for stale blocks
277
+ const missingByAction = new Map<ActionId, Transforms>();
278
+ for (const blockId of staleBlocks) {
279
+ const blockState = this.blocks.get(blockId)!;
280
+ for (let r = rev; r <= blockState.latestRev; r++) {
281
+ const committedActionId = blockState.revisionActions.get(r);
282
+ if (committedActionId) {
283
+ const transform = blockState.committedActions.get(committedActionId);
284
+ if (transform) {
285
+ const existing = missingByAction.get(committedActionId) ?? emptyTransforms();
286
+ missingByAction.set(committedActionId, concatTransform(existing, blockId, transform));
287
+ }
288
+ }
289
+ }
290
+ }
291
+
292
+ const missing: ActionTransforms[] = Array.from(missingByAction.entries()).map(([actionId, transforms]) => ({
293
+ actionId,
294
+ rev: Array.from(this.blocks.values())
295
+ .flatMap(bs => Array.from(bs.revisionActions.entries()))
296
+ .find(([, aId]) => aId === actionId)?.[0] ?? rev,
297
+ transforms
298
+ }));
299
+ return { success: false, missing };
300
+ }
301
+
302
+ // Verify all blocks have the pending action
303
+ for (const blockId of blockIds) {
304
+ const blockState = this.blocks.get(blockId);
305
+ if (!blockState || !blockState.pendingActions.has(actionId)) {
306
+ return {
307
+ success: false,
308
+ reason: `Action ${actionId} not found or not pending for block ${blockId}`
309
+ };
310
+ }
311
+ }
312
+
313
+ // Commit the action for each block
314
+ for (const blockId of blockIds) {
315
+ const blockState = this.blocks.get(blockId)!;
316
+ const transform = blockState.pendingActions.get(actionId)!;
317
+
318
+ // Get base block to apply transform to
319
+ const baseBlock = blockState.materializedBlocks.get(blockState.latestRev);
320
+
321
+ let newBlock: IBlock | undefined;
322
+ if (!baseBlock) {
323
+ if (!transform.insert) {
324
+ throw new Error(`Commit Error: Action ${actionId} has no insert for new block ${blockId}`);
325
+ }
326
+ newBlock = structuredClone(transform.insert);
327
+ } else {
328
+ newBlock = applyTransformSafe(baseBlock, transform);
329
+ if (!newBlock && !transform.delete) {
330
+ throw new Error(`Commit Error: Action ${actionId} resulted in undefined block but had no delete flag for block ${blockId}`);
331
+ }
332
+ }
333
+
334
+ if (newBlock) {
335
+ blockState.materializedBlocks.set(rev, newBlock);
336
+ }
337
+
338
+ // Update block state
339
+ blockState.latestRev = rev;
340
+ blockState.revisionActions.set(rev, actionId);
341
+ blockState.committedActions.set(actionId, transform);
342
+ blockState.pendingActions.delete(actionId);
343
+ }
344
+
345
+ // --- End of Critical Section (Simulated) ---
346
+
347
+ return { success: true };
348
+
349
+ } finally {
350
+ // Release locks in reverse order
351
+ releases.reverse().forEach(release => release());
352
+ }
353
+ }
354
+
355
+ // Helper methods for testing
356
+ reset() {
357
+ this.blocks.clear();
358
+ }
359
+
360
+ getPendingActions(): Map<ActionId, ActionTransforms> {
361
+ const allPending = new Map<ActionId, ActionTransforms>();
362
+ for (const [blockId, blockState] of this.blocks.entries()) {
363
+ for (const [actionId, transform] of blockState.pendingActions) {
364
+ const existing = allPending.get(actionId);
365
+ if (!existing) {
366
+ allPending.set(actionId, { actionId, transforms: transformsFromTransform(transform, blockId) });
367
+ } else {
368
+ existing.transforms = concatTransform(existing.transforms, blockId, transform);
369
+ }
370
+ }
371
+ }
372
+ return allPending;
373
+ }
374
+
375
+ getCommittedActions(): Map<ActionId, ActionTransforms> {
376
+ const allCommitted = new Map<ActionId, ActionTransforms>();
377
+ for (const [blockId, blockState] of this.blocks.entries()) {
378
+ for (const [rev, actionId] of blockState.revisionActions) {
379
+ const transform = blockState.committedActions.get(actionId);
380
+ if (transform) {
381
+ const existing = allCommitted.get(actionId);
382
+ if (!existing) {
383
+ allCommitted.set(actionId, {
384
+ actionId,
385
+ rev,
386
+ transforms: transformsFromTransform(transform, blockId)
387
+ });
388
+ } else {
389
+ existing.transforms = concatTransform(existing.transforms, blockId, transform);
390
+ }
391
+ }
392
+ }
393
+ }
394
+ return allCommitted;
395
+ }
396
+
397
+ setAvailable(available: boolean) {
398
+ this.available = available;
399
+ }
400
+
401
+ checkAvailable() {
402
+ if (!this.available) {
403
+ throw new Error('Transactor is not available');
404
+ }
405
+ }
406
+
407
+ /** Optional method for querying cluster nominees (used in GATHER phase for multi-collection transactions) */
408
+ queryClusterNominees?: (blockId: BlockId) => Promise<ClusterNomineesResult>;
409
+ }
410
+
411
+ /**
412
+ * Wraps a {@link TestTransactor} and forces its commit phase to fail a bounded (or unbounded)
413
+ * number of times before delegating, so tests can exercise the sync retry / backoff / give-up
414
+ * path deterministically. pend/get/getStatus/cancel delegate unchanged, so the pend→commit→cancel
415
+ * round-trip runs exactly as in production.
416
+ */
417
+ export class FlakyCommitTransactor implements ITransactor {
418
+ /** Number of commit() calls observed so far (across success and forced failure). */
419
+ commitAttempts = 0;
420
+
421
+ /**
422
+ * @param inner delegate transactor
423
+ * @param failFirstN number of initial commit() calls to fail; Infinity to always fail
424
+ * @param reason the StaleFailure.reason returned on a forced failure
425
+ */
426
+ constructor(
427
+ private readonly inner: TestTransactor,
428
+ private readonly failFirstN: number,
429
+ private readonly reason = 'forced stale',
430
+ ) {}
431
+
432
+ get(b: BlockGets): Promise<GetBlockResults> { return this.inner.get(b); }
433
+ getStatus(a: ActionBlocks[]): Promise<BlockActionStatus[]> { return this.inner.getStatus(a); }
434
+ pend(r: PendRequest): Promise<PendResult> { return this.inner.pend(r); }
435
+ cancel(a: ActionBlocks): Promise<void> { return this.inner.cancel(a); }
436
+
437
+ async commit(request: CommitRequest): Promise<CommitResult> {
438
+ this.commitAttempts++;
439
+ if (this.commitAttempts <= this.failFirstN) {
440
+ return { success: false, reason: this.reason };
441
+ }
442
+ return this.inner.commit(request);
443
+ }
444
+ }
445
+
446
+ function newBlockState(): BlockState {
447
+ return {
448
+ materializedBlocks: new Map(),
449
+ latestRev: 0,
450
+ revisionActions: new Map(),
451
+ pendingActions: new Map(),
452
+ committedActions: new Map()
453
+ };
454
+ }
455
+
456
+ /** Returns the materialized block at the highest revision ≤ the given revision */
457
+ function latestMaterializedAt(blockState: BlockState, maxRev: number): IBlock | undefined {
458
+ for (let rev = maxRev; rev >= 0; rev--) {
459
+ const block = blockState.materializedBlocks.get(rev);
460
+ if (block) return block;
461
+ }
462
+ return undefined;
463
+ }
464
+
465
+ function applyTransformSafe(block: IBlock | undefined, transform: Transform): IBlock | undefined {
466
+ if (!block) return undefined;
467
+ return applyTransform(structuredClone(block), transform);
468
+ }
@@ -1,5 +1,4 @@
1
1
  import type { ITransactionEngine, Transaction, ExecutionResult, CollectionActions } from './transaction.js';
2
- import type { TransactionCoordinator } from './coordinator.js';
3
2
 
4
3
  export const ACTIONS_ENGINE_ID = "actions@1.0.0";
5
4
 
@@ -9,6 +8,11 @@ export const ACTIONS_ENGINE_ID = "actions@1.0.0";
9
8
  * This engine treats each statement as a JSON-encoded CollectionActions object.
10
9
  * It's useful for testing the transaction infrastructure without needing SQL.
11
10
  *
11
+ * This is a PURE TRANSLATOR (model (a) of the {@link ITransactionEngine} contract):
12
+ * execute() parses statements into CollectionActions[] and RETURNS them. It never
13
+ * touches a coordinator or mutates any collection state — application is the sole
14
+ * responsibility of the session/coordinator that receives the returned actions.
15
+ *
12
16
  * Each statement format:
13
17
  * ```json
14
18
  * {
@@ -20,7 +24,7 @@ export const ACTIONS_ENGINE_ID = "actions@1.0.0";
20
24
  * ```
21
25
  */
22
26
  export class ActionsEngine implements ITransactionEngine {
23
- constructor(private coordinator: TransactionCoordinator) {}
27
+ readonly id = ACTIONS_ENGINE_ID;
24
28
 
25
29
  async execute(transaction: Transaction): Promise<ExecutionResult> {
26
30
  try {
@@ -46,12 +50,10 @@ export class ActionsEngine implements ITransactionEngine {
46
50
  }
47
51
 
48
52
  allActions.push(collectionActions);
49
-
50
- // Apply actions through coordinator (for validation/replay)
51
- await this.coordinator.applyActions([collectionActions], transaction.stamp.id);
52
53
  }
53
54
 
54
- // Return success (actions already applied)
55
+ // Pure translation: return parsed actions WITHOUT applying them. The caller
56
+ // (session.execute / coordinator.execute) applies them exactly once.
55
57
  return {
56
58
  success: true,
57
59
  actions: allActions