@optimystic/db-core 0.14.1 → 0.16.2

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
@@ -29,7 +29,19 @@ import {
29
29
  encodeCohortMessage,
30
30
  DEFAULT_MAX_MESSAGE_BYTES,
31
31
  } from "../cohort-topic/wire/codec.js";
32
- import { CohortWireError } from "../cohort-topic/wire/validate.js";
32
+ import {
33
+ asObject,
34
+ b64urlField,
35
+ failWire as fail,
36
+ optBool,
37
+ optFiniteNumber,
38
+ reqBool,
39
+ reqFiniteNumber,
40
+ reqIntInRange,
41
+ reqString,
42
+ reqStringArray,
43
+ requireV1,
44
+ } from "../cohort-topic/wire/primitives.js";
33
45
  import type { TopicTrafficV1 } from "../cohort-topic/wire/types.js";
34
46
  import { QUERY_LIMIT_MAX } from "./config.js";
35
47
 
@@ -167,7 +179,7 @@ export interface AggregateBucketV1 {
167
179
  count: number;
168
180
  }
169
181
 
170
- // --- validation helpers (mirror cohort-topic/wire/validate.ts; local to keep matchmaking DRY) ---
182
+ // --- matchmaking-local wire state (generic validation primitives live in cohort-topic/wire/primitives.js) ---
171
183
 
172
184
  const utf8Encoder = new TextEncoder();
173
185
  const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
@@ -175,96 +187,6 @@ const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
175
187
  /** Ceiling for an opaque app payload (provider/seeker), guarding decode allocation. */
176
188
  export const DEFAULT_MAX_APP_PAYLOAD_BYTES = 64 * 1024;
177
189
 
178
- function fail(message: string): never {
179
- throw new CohortWireError(message);
180
- }
181
-
182
- function asObject(value: unknown, what: string): Record<string, unknown> {
183
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
184
- fail(`${what}: expected an object`);
185
- }
186
- return value as Record<string, unknown>;
187
- }
188
-
189
- function requireV1(obj: Record<string, unknown>, what: string): void {
190
- if (obj["v"] !== 1) {
191
- fail(`${what}: expected v === 1, got ${JSON.stringify(obj["v"])}`);
192
- }
193
- }
194
-
195
- function reqString(obj: Record<string, unknown>, key: string, what: string): string {
196
- const value = obj[key];
197
- if (typeof value !== "string") {
198
- fail(`${what}: field "${key}" must be a string`);
199
- }
200
- return value;
201
- }
202
-
203
- function reqBool(obj: Record<string, unknown>, key: string, what: string): boolean {
204
- const value = obj[key];
205
- if (typeof value !== "boolean") {
206
- fail(`${what}: field "${key}" must be a boolean`);
207
- }
208
- return value;
209
- }
210
-
211
- function optBool(obj: Record<string, unknown>, key: string, what: string): boolean | undefined {
212
- const value = obj[key];
213
- if (value === undefined) {
214
- return undefined;
215
- }
216
- if (typeof value !== "boolean") {
217
- fail(`${what}: field "${key}" must be a boolean when present`);
218
- }
219
- return value;
220
- }
221
-
222
- function reqFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number {
223
- const value = obj[key];
224
- if (typeof value !== "number" || !Number.isFinite(value)) {
225
- fail(`${what}: field "${key}" must be a finite number`);
226
- }
227
- return value;
228
- }
229
-
230
- function optFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number | undefined {
231
- const value = obj[key];
232
- if (value === undefined) {
233
- return undefined;
234
- }
235
- if (typeof value !== "number" || !Number.isFinite(value)) {
236
- fail(`${what}: field "${key}" must be a finite number when present`);
237
- }
238
- return value;
239
- }
240
-
241
- function reqStringArray(obj: Record<string, unknown>, key: string, what: string): string[] {
242
- const value = obj[key];
243
- if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
244
- fail(`${what}: field "${key}" must be an array of strings`);
245
- }
246
- return value as string[];
247
- }
248
-
249
- /** Assert a base64url string decodes cleanly; returns it unchanged. */
250
- function b64urlField(value: string, key: string, what: string): string {
251
- try {
252
- b64urlToBytes(value);
253
- } catch {
254
- fail(`${what}: field "${key}" is not valid base64url`);
255
- }
256
- return value;
257
- }
258
-
259
- /** Require an integer `>= min` (and `<= max` when supplied). */
260
- function reqIntInRange(value: number, key: string, what: string, min: number, max?: number): number {
261
- if (!Number.isInteger(value) || value < min || (max !== undefined && value > max)) {
262
- const bound = max === undefined ? `>= ${min}` : `in ${min}..${max}`;
263
- fail(`${what}: field "${key}" must be an integer ${bound}, got ${value}`);
264
- }
265
- return value;
266
- }
267
-
268
190
  function validateCapabilityFilter(value: unknown, what: string): CapabilityFilter {
269
191
  const obj = asObject(value, what);
270
192
  const out: CapabilityFilter = {
@@ -273,7 +195,7 @@ function validateCapabilityFilter(value: unknown, what: string): CapabilityFilte
273
195
  };
274
196
  const minBudget = optFiniteNumber(obj, "minBudget", what);
275
197
  if (minBudget !== undefined) {
276
- out.minBudget = reqIntInRange(minBudget, "minBudget", what, 0);
198
+ out.minBudget = reqIntInRange(obj, "minBudget", what, 0);
277
199
  }
278
200
  return out;
279
201
  }
@@ -290,7 +212,7 @@ export function validateProviderAppPayloadV1(value: unknown): ProviderAppPayload
290
212
  const out: ProviderAppPayloadV1 = {
291
213
  kind: "match-provider",
292
214
  capabilities: reqStringArray(obj, "capabilities", what),
293
- capacityBudget: reqIntInRange(reqFiniteNumber(obj, "capacityBudget", what), "capacityBudget", what, 0),
215
+ capacityBudget: reqIntInRange(obj, "capacityBudget", what, 0),
294
216
  contactHint: reqString(obj, "contactHint", what),
295
217
  signature: b64urlField(reqString(obj, "signature", what), "signature", what),
296
218
  };
@@ -310,7 +232,7 @@ export function validateSeekerAppPayloadV1(value: unknown): SeekerAppPayloadV1 {
310
232
  }
311
233
  const out: SeekerAppPayloadV1 = {
312
234
  kind: "match-seeker",
313
- wantCount: reqIntInRange(reqFiniteNumber(obj, "wantCount", what), "wantCount", what, 1),
235
+ wantCount: reqIntInRange(obj, "wantCount", what, 1),
314
236
  contactHint: reqString(obj, "contactHint", what),
315
237
  signature: b64urlField(reqString(obj, "signature", what), "signature", what),
316
238
  };
@@ -350,7 +272,8 @@ export type MatchAppPayloadV1 = ProviderAppPayloadV1 | SeekerAppPayloadV1;
350
272
  /**
351
273
  * Decode an opaque `RegisterV1.appPayload` to whichever matchmaking payload it carries, dispatched on
352
274
  * `kind` (parsed once — no exception-as-control-flow). The cohort query handler uses this to classify a
353
- * registration record into a provider vs. seeker entry. Throws {@link CohortWireError} on an unknown
275
+ * registration record into a provider vs. seeker entry. Throws
276
+ * {@link import("../cohort-topic/wire/primitives.js").CohortWireError} on an unknown
354
277
  * `kind` or a malformed payload.
355
278
  */
356
279
  export function decodeMatchAppPayload(bytes: Uint8Array, maxBytes: number = DEFAULT_MAX_APP_PAYLOAD_BYTES): MatchAppPayloadV1 {
@@ -395,7 +318,7 @@ export function validateQueryV1(value: unknown): QueryV1 {
395
318
  topicId: b64urlField(reqString(obj, "topicId", what), "topicId", what),
396
319
  includeProviders: reqBool(obj, "includeProviders", what),
397
320
  includeSeekers: reqBool(obj, "includeSeekers", what),
398
- limit: reqIntInRange(reqFiniteNumber(obj, "limit", what), "limit", what, 1, QUERY_LIMIT_MAX),
321
+ limit: reqIntInRange(obj, "limit", what, 1, QUERY_LIMIT_MAX),
399
322
  requesterId: reqString(obj, "requesterId", what),
400
323
  timestamp: reqFiniteNumber(obj, "timestamp", what),
401
324
  signature: b64urlField(reqString(obj, "signature", what), "signature", what),
@@ -423,7 +346,7 @@ function validateProviderEntryV1(value: unknown): ProviderEntryV1 {
423
346
  return {
424
347
  participantId: reqString(obj, "participantId", what),
425
348
  capabilities: reqStringArray(obj, "capabilities", what),
426
- capacityBudget: reqIntInRange(reqFiniteNumber(obj, "capacityBudget", what), "capacityBudget", what, 0),
349
+ capacityBudget: reqIntInRange(obj, "capacityBudget", what, 0),
427
350
  contactHint: reqString(obj, "contactHint", what),
428
351
  attachedAt: reqFiniteNumber(obj, "attachedAt", what),
429
352
  registrationSig: b64urlField(reqString(obj, "registrationSig", what), "registrationSig", what),
@@ -435,7 +358,7 @@ function validateSeekerEntryV1(value: unknown): SeekerEntryV1 {
435
358
  const obj = asObject(value, what);
436
359
  return {
437
360
  participantId: reqString(obj, "participantId", what),
438
- wantCount: reqIntInRange(reqFiniteNumber(obj, "wantCount", what), "wantCount", what, 1),
361
+ wantCount: reqIntInRange(obj, "wantCount", what, 1),
439
362
  contactHint: reqString(obj, "contactHint", what),
440
363
  attachedAt: reqFiniteNumber(obj, "attachedAt", what),
441
364
  registrationSig: b64urlField(reqString(obj, "registrationSig", what), "registrationSig", what),
@@ -473,9 +396,9 @@ function validateAggregateBucketV1(value: unknown): AggregateBucketV1 {
473
396
  const what = "AggregateBucketV1";
474
397
  const obj = asObject(value, what);
475
398
  return {
476
- targetTier: reqIntInRange(reqFiniteNumber(obj, "targetTier", what), "targetTier", what, 0),
477
- prefixSlot: reqIntInRange(reqFiniteNumber(obj, "prefixSlot", what), "prefixSlot", what, 0),
478
- count: reqIntInRange(reqFiniteNumber(obj, "count", what), "count", what, 0),
399
+ targetTier: reqIntInRange(obj, "targetTier", what, 0),
400
+ prefixSlot: reqIntInRange(obj, "prefixSlot", what, 0),
401
+ count: reqIntInRange(obj, "count", what, 0),
479
402
  };
480
403
  }
481
404
 
@@ -21,4 +21,14 @@ export type IKeyNetwork = {
21
21
  * @returns Promise resolving to the peers in the cluster
22
22
  */
23
23
  findCluster(key: Uint8Array): Promise<ClusterPeers>;
24
+
25
+ /**
26
+ * Optionally cache a resolved coordinator for a key, so a follow-up operation
27
+ * (e.g. commit after pend) reuses the same peer. Implementations that don't
28
+ * cache coordinators simply omit this. `ttlMs` bounds how long the hint lives.
29
+ * @param key The key the coordinator was resolved for
30
+ * @param peerId The coordinating peer
31
+ * @param ttlMs Optional time-to-live for the cached hint, in ms
32
+ */
33
+ recordCoordinator?(key: Uint8Array, peerId: PeerId, ttlMs?: number): void;
24
34
  }
@@ -13,6 +13,14 @@ export type MessageOptions = {
13
13
  * impose a separate dial cap; the overall budget is the cap".
14
14
  */
15
15
  dialTimeoutMs?: number;
16
+ /**
17
+ * Blocks this coordinator is responsible for driving through consensus. Threaded
18
+ * from {@link NetworkTransactor.pend}'s batch (which consolidates several blocks
19
+ * onto one coordinating peer) into {@link CoordinatorRepo.pend}, which uses it to
20
+ * pick the cluster anchor. Absent on bare per-block paths, which fall back to the
21
+ * transforms' own block ids.
22
+ */
23
+ coordinatingBlockIds?: BlockId[];
16
24
  }
17
25
 
18
26
  export type RepoCommitRequest = {
@@ -39,6 +39,16 @@ export type PendRequest = ActionTransforms & {
39
39
  operationsHash?: string;
40
40
  /** For multi-collection transactions: supercluster nominees for consensus */
41
41
  superclusterNominees?: PeerId[];
42
+ /**
43
+ * Aged, advisory retry priority for the *single-collection* pend path (default 0 when absent).
44
+ * The multi-collection path instead carries priority on {@link PendRequest.transaction}
45
+ * ({@link Transaction.priority}); this top-level field is the carrier for a `Collection.sync`
46
+ * pend, which has no `transaction`. A cluster member reads whichever is present as the first
47
+ * `resolveRace` tiebreak. FAIRNESS-ONLY: it rides inside the signed cluster `message` (so it is
48
+ * integrity-protected in transit) but MUST NOT affect the operations hash, stale-read checks, or
49
+ * validity — a stale pend is still rejected regardless of priority.
50
+ */
51
+ priority?: number;
42
52
  };
43
53
 
44
54
  export type BlockActionStatus = ActionBlocks & {
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  import { bytesToB64url, b64urlToBytes, decodeCohortMessage, encodeCohortMessage, DEFAULT_MAX_MESSAGE_BYTES } from "../cohort-topic/wire/codec.js";
21
- import { CohortWireError } from "../cohort-topic/wire/validate.js";
21
+ import { asObject, b64urlField, failWire as fail, reqFiniteNumber, reqString } from "../cohort-topic/wire/primitives.js";
22
22
  import { DEDUPE_WINDOW_DEFAULT, QUEUE_MAX_DEFAULT, W_DEFAULT } from "./config.js";
23
23
  import { createSubscriberBackpressure, type SubscriberBackpressure, type SubscriberEnqueueResult } from "./backpressure.js";
24
24
  import { RollingCheckpoint, type CheckpointSummary, type DeltaCoalesce, type DigestFold } from "./checkpoint.js";
@@ -204,33 +204,9 @@ export class PushState {
204
204
  }
205
205
 
206
206
  // --- gossip codec (length-framed JSON, mirroring cohort-topic conventions) ---
207
-
208
- function fail(message: string): never {
209
- throw new CohortWireError(message);
210
- }
211
-
212
- function asObject(value: unknown, what: string): Record<string, unknown> {
213
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
214
- fail(`${what}: expected an object`);
215
- }
216
- return value as Record<string, unknown>;
217
- }
218
-
219
- function reqString(obj: Record<string, unknown>, key: string, what: string): string {
220
- const value = obj[key];
221
- if (typeof value !== "string") {
222
- fail(`${what}: field "${key}" must be a string`);
223
- }
224
- return value;
225
- }
226
-
227
- function reqFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number {
228
- const value = obj[key];
229
- if (typeof value !== "number" || !Number.isFinite(value)) {
230
- fail(`${what}: field "${key}" must be a finite number`);
231
- }
232
- return value;
233
- }
207
+ // Generic structural primitives (fail/asObject/reqString/reqFiniteNumber/b64urlField) are imported from
208
+ // cohort-topic/wire/primitives.js; only `reqInt` stays local — it is a range-free integer check (no
209
+ // lower bound), unlike the shared `reqIntInRange`, so folding it in would change behavior.
234
210
 
235
211
  function reqInt(value: number, key: string, what: string): number {
236
212
  if (!Number.isInteger(value)) {
@@ -239,15 +215,6 @@ function reqInt(value: number, key: string, what: string): number {
239
215
  return value;
240
216
  }
241
217
 
242
- function b64urlField(value: string, key: string, what: string): string {
243
- try {
244
- b64urlToBytes(value);
245
- } catch {
246
- fail(`${what}: field "${key}" is not valid base64url`);
247
- }
248
- return value;
249
- }
250
-
251
218
  function validateCohortRef(value: unknown, what: string): CohortRef {
252
219
  const obj = asObject(value, what);
253
220
  const coord = b64urlField(reqString(obj, "coord", what), "coord", what);
@@ -2,86 +2,12 @@
2
2
  * Reactivity — shared wire-validation primitives.
3
3
  *
4
4
  * The reactivity message codecs ({@link import("./wire.js")}, {@link import("./push-state.js")},
5
- * {@link import("./backfill.js")}, {@link import("./resume.js")}) all decode untrusted JSON into
6
- * validated V1 shapes with the same structural checks. Those primitives live here so each codec module
7
- * narrows fields the same way (DRY) every helper throws {@link CohortWireError} on a defect, matching
8
- * the cohort-topic wire conventions (base64url byte fields, finite/integer numbers, `v: 1`).
5
+ * {@link import("./backfill.js")}, {@link import("./resume.js")}, plus checkpoint / rotation / recover)
6
+ * decode untrusted JSON into validated V1 shapes with the same structural checks the rest of the
7
+ * substrate uses. Those generic primitives now live in one place
8
+ * {@link import("../cohort-topic/wire/primitives.js")} so a hardening tweak lands once; this module is
9
+ * a thin re-export kept so the reactivity codecs import from a sibling path. Every helper throws
10
+ * {@link CohortWireError} on a defect (base64url byte fields, finite/integer numbers, `v: 1`).
9
11
  */
10
12
 
11
- import { b64urlToBytes } from "../cohort-topic/wire/codec.js";
12
- import { CohortWireError } from "../cohort-topic/wire/validate.js";
13
-
14
- /** Throw a {@link CohortWireError} with `message`. */
15
- export function failWire(message: string): never {
16
- throw new CohortWireError(message);
17
- }
18
-
19
- /** Narrow `value` to a plain object (not null, not an array), or throw. */
20
- export function asObject(value: unknown, what: string): Record<string, unknown> {
21
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
22
- failWire(`${what}: expected an object`);
23
- }
24
- return value as Record<string, unknown>;
25
- }
26
-
27
- /** Require `obj.v === 1`. */
28
- export function requireV1(obj: Record<string, unknown>, what: string): void {
29
- if (obj["v"] !== 1) {
30
- failWire(`${what}: expected v === 1, got ${JSON.stringify(obj["v"])}`);
31
- }
32
- }
33
-
34
- /** Require `obj[key]` to be a string; returns it. */
35
- export function reqString(obj: Record<string, unknown>, key: string, what: string): string {
36
- const value = obj[key];
37
- if (typeof value !== "string") {
38
- failWire(`${what}: field "${key}" must be a string`);
39
- }
40
- return value;
41
- }
42
-
43
- /** Require `obj[key]` to be a boolean; returns it. */
44
- export function reqBoolean(obj: Record<string, unknown>, key: string, what: string): boolean {
45
- const value = obj[key];
46
- if (typeof value !== "boolean") {
47
- failWire(`${what}: field "${key}" must be a boolean`);
48
- }
49
- return value;
50
- }
51
-
52
- /** Require `obj[key]` to be a finite number; returns it. */
53
- export function reqFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number {
54
- const value = obj[key];
55
- if (typeof value !== "number" || !Number.isFinite(value)) {
56
- failWire(`${what}: field "${key}" must be a finite number`);
57
- }
58
- return value;
59
- }
60
-
61
- /** Require `obj[key]` to be an integer `>= min` (and `<= max` when supplied); returns it. */
62
- export function reqIntInRange(obj: Record<string, unknown>, key: string, what: string, min: number, max?: number): number {
63
- const value = reqFiniteNumber(obj, key, what);
64
- if (!Number.isInteger(value) || value < min || (max !== undefined && value > max)) {
65
- const bound = max === undefined ? `>= ${min}` : `in ${min}..${max}`;
66
- failWire(`${what}: field "${key}" must be an integer ${bound}, got ${value}`);
67
- }
68
- return value;
69
- }
70
-
71
- /** Assert a base64url string decodes cleanly; returns it unchanged. */
72
- export function b64urlField(value: string, key: string, what: string): string {
73
- try {
74
- b64urlToBytes(value);
75
- } catch {
76
- failWire(`${what}: field "${key}" is not valid base64url`);
77
- }
78
- return value;
79
- }
80
-
81
- /** Require `value` to be an array of strings; returns it (callers map each element through {@link b64urlField}). */
82
- export function reqStringArray(value: unknown, key: string, what: string): string[] {
83
- if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
84
- failWire(`${what}: field "${key}" must be an array of strings`);
85
- }
86
- return value as string[];
87
- }
13
+ export * from "../cohort-topic/wire/primitives.js";
@@ -31,7 +31,7 @@ import {
31
31
  asObject,
32
32
  b64urlField,
33
33
  failWire as fail,
34
- reqBoolean,
34
+ reqBool,
35
35
  reqFiniteNumber,
36
36
  reqIntInRange,
37
37
  reqString,
@@ -179,7 +179,7 @@ export function validateNotificationV1(value: unknown): NotificationV1 {
179
179
  const what = "NotificationV1";
180
180
  const obj = asObject(value, what);
181
181
  requireV1(obj, what);
182
- const signers = reqStringArray(obj["signers"], "signers", what);
182
+ const signers = reqStringArray(obj, "signers", what);
183
183
  const out: NotificationV1 = {
184
184
  v: 1,
185
185
  collectionId: b64urlField(reqString(obj, "collectionId", what), "collectionId", what),
@@ -197,7 +197,7 @@ export function validateNotificationV1(value: unknown): NotificationV1 {
197
197
  out.rotationHint = validateRotationHint(obj["rotationHint"], `${what}.rotationHint`);
198
198
  }
199
199
  if (obj["invalidation"] !== undefined) {
200
- out.invalidation = reqBoolean(obj, "invalidation", what);
200
+ out.invalidation = reqBool(obj, "invalidation", what);
201
201
  }
202
202
  if (obj["invalidatedActionId"] !== undefined) {
203
203
  out.invalidatedActionId = reqString(obj, "invalidatedActionId", what);
@@ -0,0 +1,65 @@
1
+ export interface WaitForOptions {
2
+ // NOTE: default is tuned for in-process settles; integration callers over real libp2p
3
+ // pass explicit 10k–90k bounds. A new caller that omits timeoutMs gets 2s — fine for
4
+ // fast local conditions, but if a slow CI machine flakes on a legitimately-slow wait,
5
+ // give that call site an explicit timeoutMs rather than raising this default.
6
+ /** Upper bound before the poll gives up. Default 2_000. */
7
+ timeoutMs?: number;
8
+ /** Poll cadence. Default 10. */
9
+ intervalMs?: number;
10
+ /** Included in the thrown message so a failure says WHAT never became true. */
11
+ description?: string;
12
+ }
13
+
14
+ /** Real-time sleep. Retain ONLY for residual cases a condition poll cannot cover. */
15
+ export const delay = (ms: number): Promise<void> =>
16
+ new Promise((resolve) => setTimeout(resolve, ms));
17
+
18
+ /**
19
+ * Poll `predicate` until it returns true or `timeoutMs` elapses; throws on timeout.
20
+ * `predicate` may be sync or async; a thrown/rejected predicate propagates immediately.
21
+ * `undefined` is treated as `false` (not ready yet).
22
+ */
23
+ export async function waitFor(
24
+ predicate: () => boolean | Promise<boolean>,
25
+ opts?: WaitForOptions,
26
+ ): Promise<void> {
27
+ const { timeoutMs = 2_000, intervalMs = 10, description } = opts ?? {};
28
+ const deadline = Date.now() + timeoutMs;
29
+ for (;;) {
30
+ if (await predicate()) return;
31
+ if (Date.now() >= deadline) {
32
+ throw new Error(
33
+ description
34
+ ? `waitFor timed out after ${timeoutMs}ms: ${description}`
35
+ : `waitFor timed out after ${timeoutMs}ms`,
36
+ );
37
+ }
38
+ await delay(intervalMs);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Poll `fn` until it returns a non-undefined value or `timeoutMs` elapses; throws on timeout.
44
+ * `undefined` means "not ready yet" — callers needing to wait for an actual `undefined`
45
+ * should use `waitFor` on a separate flag.
46
+ */
47
+ export async function waitForValue<T>(
48
+ fn: () => T | undefined | Promise<T | undefined>,
49
+ opts?: WaitForOptions,
50
+ ): Promise<T> {
51
+ const { timeoutMs = 2_000, intervalMs = 10, description } = opts ?? {};
52
+ const deadline = Date.now() + timeoutMs;
53
+ for (;;) {
54
+ const value = await fn();
55
+ if (value !== undefined) return value;
56
+ if (Date.now() >= deadline) {
57
+ throw new Error(
58
+ description
59
+ ? `waitForValue timed out after ${timeoutMs}ms: ${description}`
60
+ : `waitForValue timed out after ${timeoutMs}ms`,
61
+ );
62
+ }
63
+ await delay(intervalMs);
64
+ }
65
+ }
@@ -0,0 +1,2 @@
1
+ export * from './test-transactor.js';
2
+ export * from './async-wait.js';