@enbox/dwn-sdk-js 0.3.9 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (525) hide show
  1. package/README.md +4 -4
  2. package/dist/browser.mjs +11 -11
  3. package/dist/browser.mjs.map +4 -4
  4. package/dist/esm/generated/precompiled-validators.js +783 -1206
  5. package/dist/esm/generated/precompiled-validators.js.map +1 -1
  6. package/dist/esm/src/core/dwn-constant.js +5 -0
  7. package/dist/esm/src/core/dwn-constant.js.map +1 -1
  8. package/dist/esm/src/core/dwn-error.js +13 -7
  9. package/dist/esm/src/core/dwn-error.js.map +1 -1
  10. package/dist/esm/src/core/grant-authorization.js +9 -18
  11. package/dist/esm/src/core/grant-authorization.js.map +1 -1
  12. package/dist/esm/src/core/message-reply.js.map +1 -1
  13. package/dist/esm/src/core/messages-grant-authorization.js +28 -61
  14. package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
  15. package/dist/esm/src/core/protocol-authorization-action.js +25 -27
  16. package/dist/esm/src/core/protocol-authorization-action.js.map +1 -1
  17. package/dist/esm/src/core/protocol-authorization-validation.js +31 -69
  18. package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -1
  19. package/dist/esm/src/core/protocol-authorization.js +44 -118
  20. package/dist/esm/src/core/protocol-authorization.js.map +1 -1
  21. package/dist/esm/src/core/protocols-grant-authorization.js +5 -5
  22. package/dist/esm/src/core/protocols-grant-authorization.js.map +1 -1
  23. package/dist/esm/src/core/recording-validation-state-reader.js +84 -0
  24. package/dist/esm/src/core/recording-validation-state-reader.js.map +1 -0
  25. package/dist/esm/src/core/records-grant-authorization.js +11 -11
  26. package/dist/esm/src/core/records-grant-authorization.js.map +1 -1
  27. package/dist/esm/src/core/replication-apply.js +295 -0
  28. package/dist/esm/src/core/replication-apply.js.map +1 -0
  29. package/dist/esm/src/core/resumable-task-manager.js +5 -4
  30. package/dist/esm/src/core/resumable-task-manager.js.map +1 -1
  31. package/dist/esm/src/core/validation-state-reader.js +237 -0
  32. package/dist/esm/src/core/validation-state-reader.js.map +1 -0
  33. package/dist/esm/src/dwn.js +261 -16
  34. package/dist/esm/src/dwn.js.map +1 -1
  35. package/dist/esm/src/enums/dwn-interface-method.js +0 -1
  36. package/dist/esm/src/enums/dwn-interface-method.js.map +1 -1
  37. package/dist/esm/src/event-stream/durable-event-log.js +365 -0
  38. package/dist/esm/src/event-stream/durable-event-log.js.map +1 -0
  39. package/dist/esm/src/event-stream/event-emitter-wake-publisher.js +25 -0
  40. package/dist/esm/src/event-stream/event-emitter-wake-publisher.js.map +1 -0
  41. package/dist/esm/src/handlers/messages-query.js +159 -0
  42. package/dist/esm/src/handlers/messages-query.js.map +1 -0
  43. package/dist/esm/src/handlers/messages-read.js +5 -5
  44. package/dist/esm/src/handlers/messages-read.js.map +1 -1
  45. package/dist/esm/src/handlers/messages-subscribe.js +8 -8
  46. package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
  47. package/dist/esm/src/handlers/protocols-configure.js +30 -49
  48. package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
  49. package/dist/esm/src/handlers/protocols-query.js +1 -1
  50. package/dist/esm/src/handlers/protocols-query.js.map +1 -1
  51. package/dist/esm/src/handlers/records-count.js +20 -11
  52. package/dist/esm/src/handlers/records-count.js.map +1 -1
  53. package/dist/esm/src/handlers/records-delete.js +20 -16
  54. package/dist/esm/src/handlers/records-delete.js.map +1 -1
  55. package/dist/esm/src/handlers/records-query.js +35 -11
  56. package/dist/esm/src/handlers/records-query.js.map +1 -1
  57. package/dist/esm/src/handlers/records-read.js +52 -42
  58. package/dist/esm/src/handlers/records-read.js.map +1 -1
  59. package/dist/esm/src/handlers/records-subscribe.js +107 -11
  60. package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
  61. package/dist/esm/src/handlers/records-write.js +62 -116
  62. package/dist/esm/src/handlers/records-write.js.map +1 -1
  63. package/dist/esm/src/index.js +7 -8
  64. package/dist/esm/src/index.js.map +1 -1
  65. package/dist/esm/src/interfaces/messages-query.js +49 -0
  66. package/dist/esm/src/interfaces/messages-query.js.map +1 -0
  67. package/dist/esm/src/interfaces/protocols-configure.js +7 -3
  68. package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
  69. package/dist/esm/src/interfaces/protocols-query.js +3 -4
  70. package/dist/esm/src/interfaces/protocols-query.js.map +1 -1
  71. package/dist/esm/src/interfaces/records-count.js +4 -3
  72. package/dist/esm/src/interfaces/records-count.js.map +1 -1
  73. package/dist/esm/src/interfaces/records-delete.js +21 -4
  74. package/dist/esm/src/interfaces/records-delete.js.map +1 -1
  75. package/dist/esm/src/interfaces/records-query.js +4 -3
  76. package/dist/esm/src/interfaces/records-query.js.map +1 -1
  77. package/dist/esm/src/interfaces/records-read.js +3 -3
  78. package/dist/esm/src/interfaces/records-read.js.map +1 -1
  79. package/dist/esm/src/interfaces/records-subscribe.js +4 -3
  80. package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
  81. package/dist/esm/src/interfaces/records-write.js +27 -13
  82. package/dist/esm/src/interfaces/records-write.js.map +1 -1
  83. package/dist/esm/src/protocols/permissions.js +27 -34
  84. package/dist/esm/src/protocols/permissions.js.map +1 -1
  85. package/dist/esm/src/store/index-level.js +24 -9
  86. package/dist/esm/src/store/index-level.js.map +1 -1
  87. package/dist/esm/src/store/level-wrapper.js +7 -0
  88. package/dist/esm/src/store/level-wrapper.js.map +1 -1
  89. package/dist/esm/src/store/message-store-level.js +536 -42
  90. package/dist/esm/src/store/message-store-level.js.map +1 -1
  91. package/dist/esm/src/store/storage-controller.js +58 -49
  92. package/dist/esm/src/store/storage-controller.js.map +1 -1
  93. package/dist/esm/src/types/message-types.js.map +1 -1
  94. package/dist/esm/src/types/validation-state-reader.js +2 -0
  95. package/dist/esm/src/types/validation-state-reader.js.map +1 -0
  96. package/dist/esm/src/utils/messages.js +17 -0
  97. package/dist/esm/src/utils/messages.js.map +1 -1
  98. package/dist/esm/src/utils/record-limit-occupancy.js +244 -0
  99. package/dist/esm/src/utils/record-limit-occupancy.js.map +1 -0
  100. package/dist/esm/src/utils/records.js +50 -14
  101. package/dist/esm/src/utils/records.js.map +1 -1
  102. package/dist/esm/src/utils/replication.js +85 -0
  103. package/dist/esm/src/utils/replication.js.map +1 -0
  104. package/dist/esm/tests/core/grant-authorization.spec.js +4 -4
  105. package/dist/esm/tests/core/grant-authorization.spec.js.map +1 -1
  106. package/dist/esm/tests/core/process-message-parity.spec.js +222 -0
  107. package/dist/esm/tests/core/process-message-parity.spec.js.map +1 -0
  108. package/dist/esm/tests/core/protocol-authorization.spec.js +5 -2
  109. package/dist/esm/tests/core/protocol-authorization.spec.js.map +1 -1
  110. package/dist/esm/tests/core/records-grant-authorization.spec.js +5 -5
  111. package/dist/esm/tests/core/records-grant-authorization.spec.js.map +1 -1
  112. package/dist/esm/tests/core/replication-apply.spec.js +274 -0
  113. package/dist/esm/tests/core/replication-apply.spec.js.map +1 -0
  114. package/dist/esm/tests/core/replication-replay-property.spec.js +350 -0
  115. package/dist/esm/tests/core/replication-replay-property.spec.js.map +1 -0
  116. package/dist/esm/tests/core/validation-read-closure.spec.js +469 -0
  117. package/dist/esm/tests/core/validation-read-closure.spec.js.map +1 -0
  118. package/dist/esm/tests/core/validation-state-reader.spec.js +716 -0
  119. package/dist/esm/tests/core/validation-state-reader.spec.js.map +1 -0
  120. package/dist/esm/tests/durable-event-log.spec.js +373 -0
  121. package/dist/esm/tests/durable-event-log.spec.js.map +1 -0
  122. package/dist/esm/tests/dwn.spec.js +620 -14
  123. package/dist/esm/tests/dwn.spec.js.map +1 -1
  124. package/dist/esm/tests/features/author-delegated-grant.spec.js +9 -6
  125. package/dist/esm/tests/features/author-delegated-grant.spec.js.map +1 -1
  126. package/dist/esm/tests/features/owner-delegated-grant.spec.js +1 -4
  127. package/dist/esm/tests/features/owner-delegated-grant.spec.js.map +1 -1
  128. package/dist/esm/tests/features/owner-signature.spec.js +1 -4
  129. package/dist/esm/tests/features/owner-signature.spec.js.map +1 -1
  130. package/dist/esm/tests/features/permissions.spec.js +165 -4
  131. package/dist/esm/tests/features/permissions.spec.js.map +1 -1
  132. package/dist/esm/tests/features/protocol-composition.spec.js +8 -11
  133. package/dist/esm/tests/features/protocol-composition.spec.js.map +1 -1
  134. package/dist/esm/tests/features/protocol-create-action.spec.js +1 -4
  135. package/dist/esm/tests/features/protocol-create-action.spec.js.map +1 -1
  136. package/dist/esm/tests/features/protocol-delete-action.spec.js +3 -5
  137. package/dist/esm/tests/features/protocol-delete-action.spec.js.map +1 -1
  138. package/dist/esm/tests/features/protocol-update-action.spec.js +3 -6
  139. package/dist/esm/tests/features/protocol-update-action.spec.js.map +1 -1
  140. package/dist/esm/tests/features/records-delivery.spec.js +1 -4
  141. package/dist/esm/tests/features/records-delivery.spec.js.map +1 -1
  142. package/dist/esm/tests/features/records-immutable.spec.js +1 -4
  143. package/dist/esm/tests/features/records-immutable.spec.js.map +1 -1
  144. package/dist/esm/tests/features/records-nested-query-scope.spec.js +281 -0
  145. package/dist/esm/tests/features/records-nested-query-scope.spec.js.map +1 -0
  146. package/dist/esm/tests/features/records-prune-cross-protocol.spec.js +3 -7
  147. package/dist/esm/tests/features/records-prune-cross-protocol.spec.js.map +1 -1
  148. package/dist/esm/tests/features/records-prune.spec.js +11 -22
  149. package/dist/esm/tests/features/records-prune.spec.js.map +1 -1
  150. package/dist/esm/tests/features/records-record-limit.spec.js +441 -231
  151. package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -1
  152. package/dist/esm/tests/features/records-squash.spec.js +6 -4
  153. package/dist/esm/tests/features/records-squash.spec.js.map +1 -1
  154. package/dist/esm/tests/features/records-tags.spec.js +1 -4
  155. package/dist/esm/tests/features/records-tags.spec.js.map +1 -1
  156. package/dist/esm/tests/features/resumable-tasks.spec.js +3 -5
  157. package/dist/esm/tests/features/resumable-tasks.spec.js.map +1 -1
  158. package/dist/esm/tests/fuzz/message-store.fuzz.spec.js +1 -2
  159. package/dist/esm/tests/fuzz/message-store.fuzz.spec.js.map +1 -1
  160. package/dist/esm/tests/fuzz/process-message.fuzz.spec.js +2 -4
  161. package/dist/esm/tests/fuzz/process-message.fuzz.spec.js.map +1 -1
  162. package/dist/esm/tests/fuzz/schema-validation.fuzz.spec.js +1 -1
  163. package/dist/esm/tests/fuzz/schema-validation.fuzz.spec.js.map +1 -1
  164. package/dist/esm/tests/handlers/messages-query.spec.js +246 -0
  165. package/dist/esm/tests/handlers/messages-query.spec.js.map +1 -0
  166. package/dist/esm/tests/handlers/messages-read.spec.js +2 -5
  167. package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
  168. package/dist/esm/tests/handlers/messages-subscribe.spec.js +3 -14
  169. package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
  170. package/dist/esm/tests/handlers/protocols-configure.spec.js +27 -26
  171. package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
  172. package/dist/esm/tests/handlers/protocols-query.spec.js +1 -4
  173. package/dist/esm/tests/handlers/protocols-query.spec.js.map +1 -1
  174. package/dist/esm/tests/handlers/records-count.spec.js +1 -4
  175. package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
  176. package/dist/esm/tests/handlers/records-delete.spec.js +312 -30
  177. package/dist/esm/tests/handlers/records-delete.spec.js.map +1 -1
  178. package/dist/esm/tests/handlers/records-query.spec.js +32 -9
  179. package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
  180. package/dist/esm/tests/handlers/records-read.spec.js +4 -4
  181. package/dist/esm/tests/handlers/records-read.spec.js.map +1 -1
  182. package/dist/esm/tests/handlers/records-subscribe.spec.js +33 -14
  183. package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
  184. package/dist/esm/tests/handlers/records-write.spec.js +84 -38
  185. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  186. package/dist/esm/tests/interfaces/records-delete.spec.js +69 -2
  187. package/dist/esm/tests/interfaces/records-delete.spec.js.map +1 -1
  188. package/dist/esm/tests/interfaces/records-write.spec.js +4 -3
  189. package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
  190. package/dist/esm/tests/protocols/permissions.spec.js +55 -6
  191. package/dist/esm/tests/protocols/permissions.spec.js.map +1 -1
  192. package/dist/esm/tests/scenarios/aggregator.spec.js +1 -4
  193. package/dist/esm/tests/scenarios/aggregator.spec.js.map +1 -1
  194. package/dist/esm/tests/scenarios/deleted-record.spec.js +1 -4
  195. package/dist/esm/tests/scenarios/deleted-record.spec.js.map +1 -1
  196. package/dist/esm/tests/scenarios/end-to-end-tests.spec.js +1 -4
  197. package/dist/esm/tests/scenarios/end-to-end-tests.spec.js.map +1 -1
  198. package/dist/esm/tests/scenarios/nested-roles.spec.js +1 -4
  199. package/dist/esm/tests/scenarios/nested-roles.spec.js.map +1 -1
  200. package/dist/esm/tests/scenarios/subscriptions.spec.js +1 -4
  201. package/dist/esm/tests/scenarios/subscriptions.spec.js.map +1 -1
  202. package/dist/esm/tests/store/message-store-level.spec.js +361 -5
  203. package/dist/esm/tests/store/message-store-level.spec.js.map +1 -1
  204. package/dist/esm/tests/store/message-store.spec.js +60 -0
  205. package/dist/esm/tests/store/message-store.spec.js.map +1 -1
  206. package/dist/esm/tests/test-event-stream.js +7 -3
  207. package/dist/esm/tests/test-event-stream.js.map +1 -1
  208. package/dist/esm/tests/test-stores.js +19 -9
  209. package/dist/esm/tests/test-stores.js.map +1 -1
  210. package/dist/esm/tests/test-suite.js +4 -4
  211. package/dist/esm/tests/test-suite.js.map +1 -1
  212. package/dist/esm/tests/utils/test-data-generator.js +25 -0
  213. package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
  214. package/dist/esm/tests/utils/test-stub-generator.js.map +1 -1
  215. package/dist/esm/tests/utils/test-validation-state-reader.js +16 -0
  216. package/dist/esm/tests/utils/test-validation-state-reader.js.map +1 -0
  217. package/dist/types/generated/precompiled-validators.d.ts +6 -6
  218. package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
  219. package/dist/types/src/core/core-protocol.d.ts +3 -3
  220. package/dist/types/src/core/core-protocol.d.ts.map +1 -1
  221. package/dist/types/src/core/dwn-constant.d.ts +5 -0
  222. package/dist/types/src/core/dwn-constant.d.ts.map +1 -1
  223. package/dist/types/src/core/dwn-error.d.ts +13 -7
  224. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  225. package/dist/types/src/core/grant-authorization.d.ts +5 -5
  226. package/dist/types/src/core/grant-authorization.d.ts.map +1 -1
  227. package/dist/types/src/core/message-reply.d.ts +5 -4
  228. package/dist/types/src/core/message-reply.d.ts.map +1 -1
  229. package/dist/types/src/core/messages-grant-authorization.d.ts +12 -15
  230. package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
  231. package/dist/types/src/core/protocol-authorization-action.d.ts +4 -5
  232. package/dist/types/src/core/protocol-authorization-action.d.ts.map +1 -1
  233. package/dist/types/src/core/protocol-authorization-validation.d.ts +13 -16
  234. package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -1
  235. package/dist/types/src/core/protocol-authorization.d.ts +8 -33
  236. package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
  237. package/dist/types/src/core/protocols-grant-authorization.d.ts +4 -4
  238. package/dist/types/src/core/protocols-grant-authorization.d.ts.map +1 -1
  239. package/dist/types/src/core/recording-validation-state-reader.d.ts +75 -0
  240. package/dist/types/src/core/recording-validation-state-reader.d.ts.map +1 -0
  241. package/dist/types/src/core/records-grant-authorization.d.ts +8 -8
  242. package/dist/types/src/core/records-grant-authorization.d.ts.map +1 -1
  243. package/dist/types/src/core/replication-apply.d.ts +129 -0
  244. package/dist/types/src/core/replication-apply.d.ts.map +1 -0
  245. package/dist/types/src/core/resumable-task-manager.d.ts +1 -1
  246. package/dist/types/src/core/resumable-task-manager.d.ts.map +1 -1
  247. package/dist/types/src/core/validation-state-reader.d.ts +79 -0
  248. package/dist/types/src/core/validation-state-reader.d.ts.map +1 -0
  249. package/dist/types/src/dwn.d.ts +47 -13
  250. package/dist/types/src/dwn.d.ts.map +1 -1
  251. package/dist/types/src/enums/dwn-interface-method.d.ts +0 -1
  252. package/dist/types/src/enums/dwn-interface-method.d.ts.map +1 -1
  253. package/dist/types/src/event-stream/durable-event-log.d.ts +69 -0
  254. package/dist/types/src/event-stream/durable-event-log.d.ts.map +1 -0
  255. package/dist/types/src/event-stream/event-emitter-wake-publisher.d.ts +13 -0
  256. package/dist/types/src/event-stream/event-emitter-wake-publisher.d.ts.map +1 -0
  257. package/dist/types/src/handlers/messages-query.d.ts +20 -0
  258. package/dist/types/src/handlers/messages-query.d.ts.map +1 -0
  259. package/dist/types/src/handlers/messages-read.d.ts +1 -1
  260. package/dist/types/src/handlers/messages-read.d.ts.map +1 -1
  261. package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
  262. package/dist/types/src/handlers/protocols-configure.d.ts +0 -5
  263. package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
  264. package/dist/types/src/handlers/records-count.d.ts +2 -1
  265. package/dist/types/src/handlers/records-count.d.ts.map +1 -1
  266. package/dist/types/src/handlers/records-delete.d.ts +2 -2
  267. package/dist/types/src/handlers/records-delete.d.ts.map +1 -1
  268. package/dist/types/src/handlers/records-query.d.ts +1 -1
  269. package/dist/types/src/handlers/records-query.d.ts.map +1 -1
  270. package/dist/types/src/handlers/records-read.d.ts +2 -1
  271. package/dist/types/src/handlers/records-read.d.ts.map +1 -1
  272. package/dist/types/src/handlers/records-subscribe.d.ts +4 -5
  273. package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
  274. package/dist/types/src/handlers/records-write.d.ts +3 -11
  275. package/dist/types/src/handlers/records-write.d.ts.map +1 -1
  276. package/dist/types/src/index.d.ts +16 -18
  277. package/dist/types/src/index.d.ts.map +1 -1
  278. package/dist/types/src/interfaces/messages-query.d.ts +23 -0
  279. package/dist/types/src/interfaces/messages-query.d.ts.map +1 -0
  280. package/dist/types/src/interfaces/protocols-configure.d.ts +3 -3
  281. package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
  282. package/dist/types/src/interfaces/protocols-query.d.ts +2 -2
  283. package/dist/types/src/interfaces/protocols-query.d.ts.map +1 -1
  284. package/dist/types/src/interfaces/records-count.d.ts +3 -3
  285. package/dist/types/src/interfaces/records-count.d.ts.map +1 -1
  286. package/dist/types/src/interfaces/records-delete.d.ts +11 -3
  287. package/dist/types/src/interfaces/records-delete.d.ts.map +1 -1
  288. package/dist/types/src/interfaces/records-query.d.ts +3 -3
  289. package/dist/types/src/interfaces/records-query.d.ts.map +1 -1
  290. package/dist/types/src/interfaces/records-read.d.ts +3 -3
  291. package/dist/types/src/interfaces/records-read.d.ts.map +1 -1
  292. package/dist/types/src/interfaces/records-subscribe.d.ts +3 -3
  293. package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
  294. package/dist/types/src/interfaces/records-write.d.ts +15 -7
  295. package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
  296. package/dist/types/src/protocols/permissions.d.ts +9 -12
  297. package/dist/types/src/protocols/permissions.d.ts.map +1 -1
  298. package/dist/types/src/store/index-level.d.ts +10 -1
  299. package/dist/types/src/store/index-level.d.ts.map +1 -1
  300. package/dist/types/src/store/level-wrapper.d.ts +5 -0
  301. package/dist/types/src/store/level-wrapper.d.ts.map +1 -1
  302. package/dist/types/src/store/message-store-level.d.ts +94 -14
  303. package/dist/types/src/store/message-store-level.d.ts.map +1 -1
  304. package/dist/types/src/store/storage-controller.d.ts +17 -14
  305. package/dist/types/src/store/storage-controller.d.ts.map +1 -1
  306. package/dist/types/src/types/message-store.d.ts +29 -1
  307. package/dist/types/src/types/message-store.d.ts.map +1 -1
  308. package/dist/types/src/types/message-types.d.ts +2 -0
  309. package/dist/types/src/types/message-types.d.ts.map +1 -1
  310. package/dist/types/src/types/messages-types.d.ts +21 -55
  311. package/dist/types/src/types/messages-types.d.ts.map +1 -1
  312. package/dist/types/src/types/method-handler.d.ts +2 -2
  313. package/dist/types/src/types/method-handler.d.ts.map +1 -1
  314. package/dist/types/src/types/permission-types.d.ts +1 -1
  315. package/dist/types/src/types/subscriptions.d.ts +50 -39
  316. package/dist/types/src/types/subscriptions.d.ts.map +1 -1
  317. package/dist/types/src/types/validation-state-reader.d.ts +116 -0
  318. package/dist/types/src/types/validation-state-reader.d.ts.map +1 -0
  319. package/dist/types/src/utils/messages.d.ts +10 -0
  320. package/dist/types/src/utils/messages.d.ts.map +1 -1
  321. package/dist/types/src/utils/record-limit-occupancy.d.ts +40 -0
  322. package/dist/types/src/utils/record-limit-occupancy.d.ts.map +1 -0
  323. package/dist/types/src/utils/records.d.ts +25 -3
  324. package/dist/types/src/utils/records.d.ts.map +1 -1
  325. package/dist/types/src/utils/replication.d.ts +22 -0
  326. package/dist/types/src/utils/replication.d.ts.map +1 -0
  327. package/dist/types/tests/core/process-message-parity.spec.d.ts +2 -0
  328. package/dist/types/tests/core/process-message-parity.spec.d.ts.map +1 -0
  329. package/dist/types/tests/core/replication-apply.spec.d.ts +2 -0
  330. package/dist/types/tests/core/replication-apply.spec.d.ts.map +1 -0
  331. package/dist/types/tests/core/replication-replay-property.spec.d.ts +2 -0
  332. package/dist/types/tests/core/replication-replay-property.spec.d.ts.map +1 -0
  333. package/dist/types/tests/core/validation-read-closure.spec.d.ts +2 -0
  334. package/dist/types/tests/core/validation-read-closure.spec.d.ts.map +1 -0
  335. package/dist/types/tests/core/validation-state-reader.spec.d.ts +2 -0
  336. package/dist/types/tests/core/validation-state-reader.spec.d.ts.map +1 -0
  337. package/dist/types/tests/durable-event-log.spec.d.ts +2 -0
  338. package/dist/types/tests/durable-event-log.spec.d.ts.map +1 -0
  339. package/dist/types/tests/dwn.spec.d.ts.map +1 -1
  340. package/dist/types/tests/features/author-delegated-grant.spec.d.ts.map +1 -1
  341. package/dist/types/tests/features/owner-delegated-grant.spec.d.ts.map +1 -1
  342. package/dist/types/tests/features/owner-signature.spec.d.ts.map +1 -1
  343. package/dist/types/tests/features/permissions.spec.d.ts.map +1 -1
  344. package/dist/types/tests/features/protocol-composition.spec.d.ts.map +1 -1
  345. package/dist/types/tests/features/protocol-create-action.spec.d.ts.map +1 -1
  346. package/dist/types/tests/features/protocol-delete-action.spec.d.ts.map +1 -1
  347. package/dist/types/tests/features/protocol-update-action.spec.d.ts.map +1 -1
  348. package/dist/types/tests/features/records-delivery.spec.d.ts.map +1 -1
  349. package/dist/types/tests/features/records-immutable.spec.d.ts.map +1 -1
  350. package/dist/types/tests/features/records-nested-query-scope.spec.d.ts +2 -0
  351. package/dist/types/tests/features/records-nested-query-scope.spec.d.ts.map +1 -0
  352. package/dist/types/tests/features/records-prune-cross-protocol.spec.d.ts.map +1 -1
  353. package/dist/types/tests/features/records-prune.spec.d.ts.map +1 -1
  354. package/dist/types/tests/features/records-record-limit.spec.d.ts.map +1 -1
  355. package/dist/types/tests/features/records-squash.spec.d.ts.map +1 -1
  356. package/dist/types/tests/features/records-tags.spec.d.ts.map +1 -1
  357. package/dist/types/tests/features/resumable-tasks.spec.d.ts.map +1 -1
  358. package/dist/types/tests/handlers/messages-query.spec.d.ts +2 -0
  359. package/dist/types/tests/handlers/messages-query.spec.d.ts.map +1 -0
  360. package/dist/types/tests/handlers/messages-read.spec.d.ts.map +1 -1
  361. package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
  362. package/dist/types/tests/handlers/protocols-configure.spec.d.ts.map +1 -1
  363. package/dist/types/tests/handlers/protocols-query.spec.d.ts.map +1 -1
  364. package/dist/types/tests/handlers/records-count.spec.d.ts.map +1 -1
  365. package/dist/types/tests/handlers/records-delete.spec.d.ts.map +1 -1
  366. package/dist/types/tests/handlers/records-query.spec.d.ts.map +1 -1
  367. package/dist/types/tests/handlers/records-read.spec.d.ts.map +1 -1
  368. package/dist/types/tests/handlers/records-subscribe.spec.d.ts.map +1 -1
  369. package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
  370. package/dist/types/tests/scenarios/deleted-record.spec.d.ts.map +1 -1
  371. package/dist/types/tests/scenarios/end-to-end-tests.spec.d.ts.map +1 -1
  372. package/dist/types/tests/scenarios/nested-roles.spec.d.ts.map +1 -1
  373. package/dist/types/tests/scenarios/subscriptions.spec.d.ts.map +1 -1
  374. package/dist/types/tests/store/message-store.spec.d.ts.map +1 -1
  375. package/dist/types/tests/test-event-stream.d.ts +1 -1
  376. package/dist/types/tests/test-event-stream.d.ts.map +1 -1
  377. package/dist/types/tests/test-stores.d.ts +5 -4
  378. package/dist/types/tests/test-stores.d.ts.map +1 -1
  379. package/dist/types/tests/test-suite.d.ts +1 -2
  380. package/dist/types/tests/test-suite.d.ts.map +1 -1
  381. package/dist/types/tests/utils/test-data-generator.d.ts +20 -1
  382. package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
  383. package/dist/types/tests/utils/test-validation-state-reader.d.ts +15 -0
  384. package/dist/types/tests/utils/test-validation-state-reader.d.ts.map +1 -0
  385. package/package.json +2 -2
  386. package/src/core/core-protocol.ts +3 -3
  387. package/src/core/dwn-constant.ts +7 -1
  388. package/src/core/dwn-error.ts +13 -7
  389. package/src/core/grant-authorization.ts +11 -20
  390. package/src/core/message-reply.ts +6 -5
  391. package/src/core/messages-grant-authorization.ts +37 -100
  392. package/src/core/protocol-authorization-action.ts +29 -38
  393. package/src/core/protocol-authorization-validation.ts +41 -98
  394. package/src/core/protocol-authorization.ts +56 -202
  395. package/src/core/protocols-grant-authorization.ts +9 -9
  396. package/src/core/recording-validation-state-reader.ts +130 -0
  397. package/src/core/records-grant-authorization.ts +16 -16
  398. package/src/core/replication-apply.ts +412 -0
  399. package/src/core/resumable-task-manager.ts +10 -8
  400. package/src/core/validation-state-reader.ts +350 -0
  401. package/src/dwn.ts +417 -30
  402. package/src/enums/dwn-interface-method.ts +0 -1
  403. package/src/event-stream/durable-event-log.ts +509 -0
  404. package/src/event-stream/event-emitter-wake-publisher.ts +34 -0
  405. package/src/handlers/messages-query.ts +203 -0
  406. package/src/handlers/messages-read.ts +9 -10
  407. package/src/handlers/messages-subscribe.ts +12 -13
  408. package/src/handlers/protocols-configure.ts +37 -58
  409. package/src/handlers/protocols-query.ts +1 -1
  410. package/src/handlers/records-count.ts +24 -17
  411. package/src/handlers/records-delete.ts +29 -27
  412. package/src/handlers/records-query.ts +38 -17
  413. package/src/handlers/records-read.ts +63 -50
  414. package/src/handlers/records-subscribe.ts +132 -19
  415. package/src/handlers/records-write.ts +77 -168
  416. package/src/index.ts +16 -20
  417. package/src/interfaces/messages-query.ts +70 -0
  418. package/src/interfaces/protocols-configure.ts +12 -4
  419. package/src/interfaces/protocols-query.ts +4 -5
  420. package/src/interfaces/records-count.ts +9 -4
  421. package/src/interfaces/records-delete.ts +25 -5
  422. package/src/interfaces/records-query.ts +9 -4
  423. package/src/interfaces/records-read.ts +4 -4
  424. package/src/interfaces/records-subscribe.ts +9 -4
  425. package/src/interfaces/records-write.ts +41 -13
  426. package/src/protocols/permissions.ts +32 -52
  427. package/src/store/index-level.ts +30 -9
  428. package/src/store/level-wrapper.ts +9 -1
  429. package/src/store/message-store-level.ts +757 -47
  430. package/src/store/storage-controller.ts +74 -63
  431. package/src/types/message-store.ts +45 -2
  432. package/src/types/message-types.ts +3 -1
  433. package/src/types/messages-types.ts +26 -65
  434. package/src/types/method-handler.ts +3 -3
  435. package/src/types/permission-types.ts +1 -1
  436. package/src/types/subscriptions.ts +53 -42
  437. package/src/types/validation-state-reader.ts +127 -0
  438. package/src/utils/messages.ts +25 -1
  439. package/src/utils/record-limit-occupancy.ts +377 -0
  440. package/src/utils/records.ts +69 -13
  441. package/src/utils/replication.ts +122 -0
  442. package/dist/esm/src/core/record-chain.js +0 -64
  443. package/dist/esm/src/core/record-chain.js.map +0 -1
  444. package/dist/esm/src/event-stream/event-emitter-event-log.js +0 -334
  445. package/dist/esm/src/event-stream/event-emitter-event-log.js.map +0 -1
  446. package/dist/esm/src/handlers/messages-sync.js +0 -581
  447. package/dist/esm/src/handlers/messages-sync.js.map +0 -1
  448. package/dist/esm/src/interfaces/messages-sync.js +0 -54
  449. package/dist/esm/src/interfaces/messages-sync.js.map +0 -1
  450. package/dist/esm/src/smt/smt-store-level.js +0 -103
  451. package/dist/esm/src/smt/smt-store-level.js.map +0 -1
  452. package/dist/esm/src/smt/smt-store-memory.js +0 -41
  453. package/dist/esm/src/smt/smt-store-memory.js.map +0 -1
  454. package/dist/esm/src/smt/smt-utils.js +0 -129
  455. package/dist/esm/src/smt/smt-utils.js.map +0 -1
  456. package/dist/esm/src/smt/sparse-merkle-tree.js +0 -577
  457. package/dist/esm/src/smt/sparse-merkle-tree.js.map +0 -1
  458. package/dist/esm/src/state-index/state-index-level.js +0 -191
  459. package/dist/esm/src/state-index/state-index-level.js.map +0 -1
  460. package/dist/esm/src/sync/records-projection.js +0 -228
  461. package/dist/esm/src/sync/records-projection.js.map +0 -1
  462. package/dist/esm/src/types/smt-types.js +0 -5
  463. package/dist/esm/src/types/smt-types.js.map +0 -1
  464. package/dist/esm/src/types/state-index.js +0 -2
  465. package/dist/esm/src/types/state-index.js.map +0 -1
  466. package/dist/esm/tests/event-emitter-event-log.spec.js +0 -499
  467. package/dist/esm/tests/event-emitter-event-log.spec.js.map +0 -1
  468. package/dist/esm/tests/handlers/messages-sync.spec.js +0 -1771
  469. package/dist/esm/tests/handlers/messages-sync.spec.js.map +0 -1
  470. package/dist/esm/tests/smt/smt-store-level.spec.js +0 -132
  471. package/dist/esm/tests/smt/smt-store-level.spec.js.map +0 -1
  472. package/dist/esm/tests/smt/sparse-merkle-tree.spec.js +0 -732
  473. package/dist/esm/tests/smt/sparse-merkle-tree.spec.js.map +0 -1
  474. package/dist/esm/tests/state-index/state-index-level.spec.js +0 -245
  475. package/dist/esm/tests/state-index/state-index-level.spec.js.map +0 -1
  476. package/dist/esm/tests/sync/records-projection.spec.js +0 -245
  477. package/dist/esm/tests/sync/records-projection.spec.js.map +0 -1
  478. package/dist/types/src/core/record-chain.d.ts +0 -24
  479. package/dist/types/src/core/record-chain.d.ts.map +0 -1
  480. package/dist/types/src/event-stream/event-emitter-event-log.d.ts +0 -80
  481. package/dist/types/src/event-stream/event-emitter-event-log.d.ts.map +0 -1
  482. package/dist/types/src/handlers/messages-sync.d.ts +0 -83
  483. package/dist/types/src/handlers/messages-sync.d.ts.map +0 -1
  484. package/dist/types/src/interfaces/messages-sync.d.ts +0 -23
  485. package/dist/types/src/interfaces/messages-sync.d.ts.map +0 -1
  486. package/dist/types/src/smt/smt-store-level.d.ts +0 -32
  487. package/dist/types/src/smt/smt-store-level.d.ts.map +0 -1
  488. package/dist/types/src/smt/smt-store-memory.d.ts +0 -22
  489. package/dist/types/src/smt/smt-store-memory.d.ts.map +0 -1
  490. package/dist/types/src/smt/smt-utils.d.ts +0 -58
  491. package/dist/types/src/smt/smt-utils.d.ts.map +0 -1
  492. package/dist/types/src/smt/sparse-merkle-tree.d.ts +0 -124
  493. package/dist/types/src/smt/sparse-merkle-tree.d.ts.map +0 -1
  494. package/dist/types/src/state-index/state-index-level.d.ts +0 -83
  495. package/dist/types/src/state-index/state-index-level.d.ts.map +0 -1
  496. package/dist/types/src/sync/records-projection.d.ts +0 -98
  497. package/dist/types/src/sync/records-projection.d.ts.map +0 -1
  498. package/dist/types/src/types/smt-types.d.ts +0 -81
  499. package/dist/types/src/types/smt-types.d.ts.map +0 -1
  500. package/dist/types/src/types/state-index.d.ts +0 -90
  501. package/dist/types/src/types/state-index.d.ts.map +0 -1
  502. package/dist/types/tests/event-emitter-event-log.spec.d.ts +0 -2
  503. package/dist/types/tests/event-emitter-event-log.spec.d.ts.map +0 -1
  504. package/dist/types/tests/handlers/messages-sync.spec.d.ts +0 -2
  505. package/dist/types/tests/handlers/messages-sync.spec.d.ts.map +0 -1
  506. package/dist/types/tests/smt/smt-store-level.spec.d.ts +0 -2
  507. package/dist/types/tests/smt/smt-store-level.spec.d.ts.map +0 -1
  508. package/dist/types/tests/smt/sparse-merkle-tree.spec.d.ts +0 -2
  509. package/dist/types/tests/smt/sparse-merkle-tree.spec.d.ts.map +0 -1
  510. package/dist/types/tests/state-index/state-index-level.spec.d.ts +0 -2
  511. package/dist/types/tests/state-index/state-index-level.spec.d.ts.map +0 -1
  512. package/dist/types/tests/sync/records-projection.spec.d.ts +0 -2
  513. package/dist/types/tests/sync/records-projection.spec.d.ts.map +0 -1
  514. package/src/core/record-chain.ts +0 -99
  515. package/src/event-stream/event-emitter-event-log.ts +0 -430
  516. package/src/handlers/messages-sync.ts +0 -896
  517. package/src/interfaces/messages-sync.ts +0 -86
  518. package/src/smt/smt-store-level.ts +0 -143
  519. package/src/smt/smt-store-memory.ts +0 -53
  520. package/src/smt/smt-utils.ts +0 -149
  521. package/src/smt/sparse-merkle-tree.ts +0 -698
  522. package/src/state-index/state-index-level.ts +0 -239
  523. package/src/sync/records-projection.ts +0 -328
  524. package/src/types/smt-types.ts +0 -95
  525. package/src/types/state-index.ts +0 -100
@@ -1,21 +1,34 @@
1
1
 
2
2
  import type { CompoundIndexDefinition } from './index-level.js';
3
+ import type {
4
+ EventLogEntry,
5
+ EventLogReadOptions,
6
+ EventLogReadResult,
7
+ ProgressGapInfo,
8
+ ProgressGapReason,
9
+ ProgressToken,
10
+ ReplicationFeedReader,
11
+ WakePublisher,
12
+ } from '../types/subscriptions.js';
3
13
  import type { Filter, KeyValues, PaginationCursor, QueryOptions } from '../types/query-types.js';
4
14
  import type { GenericMessage, MessageSort, Pagination } from '../types/message-types.js';
5
- import type { MessageStore, MessageStoreOptions } from '../types/message-store.js';
15
+ import type { LevelDatabase, LevelWrapperBatchOperation } from './level-wrapper.js';
16
+ import type { MessageStore, MessageStoreOptions, MessageStorePutResult } from '../types/message-store.js';
6
17
 
7
18
  import * as block from 'multiformats/block';
8
19
  import * as cbor from '@ipld/dag-cbor';
9
20
 
10
- import { BlockstoreLevel } from './blockstore-level.js';
11
21
  import { Cid } from '../utils/cid.js';
12
22
  import { CID } from 'multiformats/cid';
13
- import { createLevelDatabase } from './level-wrapper.js';
14
23
  import { executeUnlessAborted } from '../utils/abort.js';
24
+ import { FilterUtility } from '../utils/filter.js';
15
25
  import { IndexLevel } from './index-level.js';
16
26
  import { Message } from '../core/message.js';
27
+ import { Replication } from '../utils/replication.js';
17
28
  import { sha256 } from 'multiformats/hashes/sha2';
18
29
  import { SortDirection } from '../types/query-types.js';
30
+ import { createLevelDatabase, LevelWrapper } from './level-wrapper.js';
31
+ import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
19
32
 
20
33
 
21
34
  /**
@@ -64,61 +77,217 @@ const DEFAULT_COMPOUND_INDEXES: CompoundIndexDefinition[] = [
64
77
  ];
65
78
 
66
79
  /**
67
- * A simple implementation of {@link MessageStore} that works in both the browser and server-side.
68
- * Leverages LevelDB under the hood.
80
+ * Sublevel names under the single Level root. Every mutation touching multiple partitions is
81
+ * committed as one atomic batch on the root, so store↔log divergence is impossible by construction.
69
82
  */
70
- export class MessageStoreLevel implements MessageStore {
83
+ const BLOCKS_PARTITION = 'blocks';
84
+ const INDEX_PARTITION = 'idx';
85
+ const LOG_PARTITION = 'log';
86
+ const CID_TO_SEQ_PARTITION = 'cid';
87
+ const FINGERPRINT_PARTITION = 'fp';
88
+ const HEADS_PARTITION = 'heads';
89
+ const META_PARTITION = 'meta';
90
+
91
+ const EPOCH_KEY = 'epoch';
92
+ const CURRENT_PARTITIONS = new Set([
93
+ BLOCKS_PARTITION,
94
+ INDEX_PARTITION,
95
+ LOG_PARTITION,
96
+ CID_TO_SEQ_PARTITION,
97
+ FINGERPRINT_PARTITION,
98
+ HEADS_PARTITION,
99
+ META_PARTITION,
100
+ ]);
101
+
102
+ /**
103
+ * The persisted shape of a replication log row (the value at key `log!<tenant>!<paddedSeq>`).
104
+ */
105
+ type LogEntryValue = {
106
+ /** The row's sequence number as a decimal string — also encoded in the row key. */
107
+ seq: string;
108
+ /** The CID of the stored message. */
109
+ messageCid: string;
110
+ /** The row's current query indexes (replaced by `updateIndexes`). */
111
+ indexes: KeyValues;
112
+ /**
113
+ * The row's fingerprint-domain membership, computed once from the insert-time indexes and
114
+ * persisted so deletion can fold the fingerprints without recomputing from mutated state.
115
+ */
116
+ fingerprintScopes: string[];
117
+ };
118
+
119
+ /**
120
+ * The partition handles over the single Level root, created once per store instance.
121
+ */
122
+ type StorePartitions = {
123
+ /** The root wrapper — all batches execute here. */
124
+ root: LevelWrapper<string>;
125
+ /** Message blocks (binary values), tenant-partitioned: `blocks!<tenant>!<messageCid>`. */
126
+ blocks: LevelWrapper<Uint8Array>;
127
+ /** Replication log rows, tenant-partitioned: `log!<tenant>!<paddedSeq>`. */
128
+ log: LevelWrapper<string>;
129
+ /** messageCid → seq lookup, tenant-partitioned: `cid!<tenant>!<messageCid>`. */
130
+ cidToSeq: LevelWrapper<string>;
131
+ /** Fingerprint domain values, tenant-partitioned: `fp!<tenant>!<domainKey>` → 64-char hex. */
132
+ fingerprints: LevelWrapper<string>;
133
+ /** Per-tenant counter high-water: `heads!<tenant>` → decimal string. */
134
+ heads: LevelWrapper<string>;
135
+ /** Store-level metadata: `meta!epoch` → persisted store generation. */
136
+ meta: LevelWrapper<string>;
137
+ };
138
+
139
+ /**
140
+ * A {@link MessageStore} and {@link ReplicationFeedReader} implementation that works in both the
141
+ * browser and server-side, backed by a SINGLE LevelDB root: message blocks, query indexes, the
142
+ * per-tenant replication log, the cid→seq index, fingerprint domains, the tenant counters, and
143
+ * the store epoch are all sublevels of one Level instance, so every mutation commits as one fully
144
+ * atomic batch.
145
+ *
146
+ * A per-tenant async write mutex spans seq assignment through batch write, so commit order equals
147
+ * seq order by construction. Seq assignment is gap-free (a failed batch never persists the head),
148
+ * while the readable log stays sparse after compaction and under filters — readers never assume
149
+ * contiguity.
150
+ */
151
+ export class MessageStoreLevel implements MessageStore, ReplicationFeedReader {
71
152
  config: MessageStoreLevelConfig;
72
153
 
73
- blockstore: BlockstoreLevel;
74
- index: IndexLevel;
154
+ private readonly wakePublisher?: WakePublisher;
155
+ private partitionsPromise?: Promise<StorePartitions>;
156
+ private epochPromise?: Promise<string>;
157
+ private readonly writeLocks: Map<string, Promise<void>> = new Map();
75
158
 
76
159
  /**
77
160
  * @param {MessageStoreLevelConfig} config
78
- * @param {string} config.blockstoreLocation - must be a directory path (relative or absolute) where
161
+ * @param {string} config.location - must be a directory path (relative or absolute) where
79
162
  * LevelDB will store its files, or in browsers, the name of the
80
163
  * {@link https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase IDBDatabase} to be opened.
81
- * @param {string} config.indexLocation - same as config.blockstoreLocation
82
164
  * @param {CompoundIndexDefinition[]} config.compoundIndexes - compound indexes to register.
83
165
  * Defaults to DEFAULT_COMPOUND_INDEXES which cover the most common DWN query patterns.
166
+ * @param {WakePublisher} config.wakePublisher - optional bus for store-owned wake publication.
84
167
  */
85
168
  constructor(config: MessageStoreLevelConfig = {}) {
86
169
  this.config = {
87
- blockstoreLocation : 'MESSAGESTORE',
88
- indexLocation : 'INDEX',
170
+ location: 'MESSAGESTORE',
89
171
  createLevelDatabase,
90
172
  ...config
91
173
  };
92
174
 
93
- this.blockstore = new BlockstoreLevel({
94
- location : this.config.blockstoreLocation!,
95
- createLevelDatabase : this.config.createLevelDatabase,
175
+ this.wakePublisher = this.config.wakePublisher;
176
+ }
177
+
178
+ async open(): Promise<void> {
179
+ const partitions = await this.partitions();
180
+ await partitions.root.open();
181
+ await this.assertNoPreSubstrateLayout(partitions);
182
+ await this.getEpoch();
183
+ }
184
+
185
+ async close(): Promise<void> {
186
+ if (this.partitionsPromise === undefined) {
187
+ return;
188
+ }
189
+
190
+ const partitions = await this.partitionsPromise;
191
+ await partitions.root.close();
192
+ }
193
+
194
+ /**
195
+ * Lazily creates the single Level root and its partition handles.
196
+ */
197
+ private async partitions(): Promise<StorePartitions> {
198
+ this.partitionsPromise ??= this.createPartitions().catch((error) => {
199
+ this.partitionsPromise = undefined;
200
+ throw error;
96
201
  });
202
+ return this.partitionsPromise;
203
+ }
204
+
205
+ private async createPartitions(): Promise<StorePartitions> {
206
+ const location = this.config.location!;
207
+ const db = await this.config.createLevelDatabase!<string>(location, { keyEncoding: 'utf8', valueEncoding: 'utf8' });
208
+
209
+ const root = new LevelWrapper<string>(
210
+ { location, createLevelDatabase: this.config.createLevelDatabase, keyEncoding: 'utf8', valueEncoding: 'utf8' },
211
+ db
212
+ );
213
+
214
+ // A binary-valued view over the same underlying Level instance — its partitions carry their
215
+ // own value encoding, so block bytes and JSON strings coexist in one atomic batch domain.
216
+ const binaryView = new LevelWrapper<Uint8Array>(
217
+ { location, createLevelDatabase: this.config.createLevelDatabase as never, keyEncoding: 'utf8', valueEncoding: 'binary' },
218
+ db as unknown as LevelDatabase<Uint8Array>
219
+ );
220
+
221
+ return {
222
+ root,
223
+ blocks : await binaryView.partition(BLOCKS_PARTITION),
224
+ log : await root.partition(LOG_PARTITION),
225
+ cidToSeq : await root.partition(CID_TO_SEQ_PARTITION),
226
+ fingerprints : await root.partition(FINGERPRINT_PARTITION),
227
+ heads : await root.partition(HEADS_PARTITION),
228
+ meta : await root.partition(META_PARTITION),
229
+ };
230
+ }
97
231
 
98
- this.index = new IndexLevel({
99
- location : this.config.indexLocation!,
100
- createLevelDatabase : this.config.createLevelDatabase,
101
- compoundIndexes : this.config.compoundIndexes ?? DEFAULT_COMPOUND_INDEXES,
232
+ /**
233
+ * The query index over the shared root. Constructed lazily alongside the partitions.
234
+ */
235
+ private indexPromise?: Promise<IndexLevel>;
236
+ private async index(): Promise<IndexLevel> {
237
+ this.indexPromise ??= (async (): Promise<IndexLevel> => {
238
+ const partitions = await this.partitions();
239
+ const indexRoot = await partitions.root.partition(INDEX_PARTITION);
240
+ return new IndexLevel(
241
+ {
242
+ location : this.config.location!,
243
+ createLevelDatabase : this.config.createLevelDatabase,
244
+ compoundIndexes : this.config.compoundIndexes ?? DEFAULT_COMPOUND_INDEXES,
245
+ },
246
+ indexRoot
247
+ );
248
+ })().catch((error) => {
249
+ this.indexPromise = undefined;
250
+ throw error;
102
251
  });
252
+ return this.indexPromise;
103
253
  }
104
254
 
105
- async open(): Promise<void> {
106
- await this.blockstore.open();
107
- await this.index.open();
255
+ /**
256
+ * Returns the persisted store epoch, generating and persisting a fresh `crypto.randomUUID()`
257
+ * at first open. Replaced only by `clear()` — a full local reset with no surviving cursors.
258
+ */
259
+ public async epoch(): Promise<string> {
260
+ return this.getEpoch();
108
261
  }
109
262
 
110
- async close(): Promise<void> {
111
- await this.blockstore.close();
112
- await this.index.close();
263
+ private async getEpoch(): Promise<string> {
264
+ this.epochPromise ??= this.initializeEpoch().catch((error) => {
265
+ this.epochPromise = undefined;
266
+ throw error;
267
+ });
268
+ return this.epochPromise;
269
+ }
270
+
271
+ private async initializeEpoch(): Promise<string> {
272
+ const partitions = await this.partitions();
273
+ const existing = await partitions.meta.get(EPOCH_KEY);
274
+ if (existing !== undefined) {
275
+ return existing;
276
+ }
277
+
278
+ const freshEpoch = crypto.randomUUID();
279
+ await partitions.meta.put(EPOCH_KEY, freshEpoch);
280
+ return freshEpoch;
113
281
  }
114
282
 
115
283
  async get(tenant: string, cidString: string, options?: MessageStoreOptions): Promise<GenericMessage | undefined> {
116
284
  options?.signal?.throwIfAborted();
117
285
 
118
- const partition = await executeUnlessAborted(this.blockstore.partition(tenant), options?.signal);
286
+ const partitions = await executeUnlessAborted(this.partitions(), options?.signal);
287
+ const tenantBlocks = await executeUnlessAborted(partitions.blocks.partition(tenant), options?.signal);
119
288
 
120
289
  const cid = CID.parse(cidString);
121
- const bytes = await partition.get(cid, options);
290
+ const bytes = await tenantBlocks.get(cid.toString(), options);
122
291
 
123
292
  if (!bytes) {
124
293
  return undefined;
@@ -142,7 +311,8 @@ export class MessageStoreLevel implements MessageStore {
142
311
  // creates the query options including sorting and pagination.
143
312
  // this adds 1 to the limit if provided, that way we can check to see if there are additional results and provide a return cursor.
144
313
  const queryOptions = MessageStoreLevel.buildQueryOptions(messageSort, pagination);
145
- const results = await this.index.query(tenant, filters, queryOptions, options);
314
+ const index = await this.index();
315
+ const results = await index.query(tenant, filters, queryOptions, options);
146
316
 
147
317
  let cursor: PaginationCursor | undefined;
148
318
  // checks to see if the returned results are greater than the limit, which would indicate additional results.
@@ -173,7 +343,8 @@ export class MessageStoreLevel implements MessageStore {
173
343
  options?.signal?.throwIfAborted();
174
344
 
175
345
  const queryOptions = MessageStoreLevel.buildQueryOptions(messageSort);
176
- return this.index.count(tenant, filters, queryOptions, options);
346
+ const index = await this.index();
347
+ return index.count(tenant, filters, queryOptions, options);
177
348
  }
178
349
 
179
350
  /**
@@ -208,50 +379,589 @@ export class MessageStoreLevel implements MessageStore {
208
379
  return { sortDirection, sortProperty, limit, cursor };
209
380
  }
210
381
 
211
- async delete(tenant: string, cidString: string, options?: MessageStoreOptions): Promise<void> {
382
+ async put(
383
+ tenant: string,
384
+ message: GenericMessage,
385
+ indexes: KeyValues,
386
+ options?: MessageStoreOptions
387
+ ): Promise<MessageStorePutResult> {
212
388
  options?.signal?.throwIfAborted();
213
389
 
214
- const partition = await executeUnlessAborted(this.blockstore.partition(tenant), options?.signal);
390
+ const partitions = await executeUnlessAborted(this.partitions(), options?.signal);
391
+ const index = await executeUnlessAborted(this.index(), options?.signal);
215
392
 
216
- const cid = CID.parse(cidString);
217
- await partition.delete(cid, options);
218
- await this.index.delete(tenant, cidString, options);
393
+ const encodedMessageBlock = await executeUnlessAborted(block.encode({ value: message, codec: cbor, hasher: sha256 }), options?.signal);
394
+
395
+ // MessageStore data may contain `encodedData` which is not taken into account when calculating the blockCID as it is optional data.
396
+ const messageCid = Cid.parseCid(await Message.getCid(message)).toString();
397
+
398
+ return this.withTenantWriteLock(tenant, async () => {
399
+ options?.signal?.throwIfAborted();
400
+
401
+ const tenantCidToSeq = await partitions.cidToSeq.partition(tenant);
402
+ const existingSeq = await tenantCidToSeq.get(messageCid, options);
403
+ if (existingSeq !== undefined) {
404
+ // Duplicates mutate nothing and publish no wake.
405
+ return { status: 'duplicate' as const };
406
+ }
407
+
408
+ const head = await this.getHead(partitions, tenant);
409
+ const seq = head + 1n;
410
+ const fingerprintScopes = Replication.computeFingerprintScopes(message, indexes);
411
+
412
+ const logEntry: LogEntryValue = {
413
+ seq: seq.toString(),
414
+ messageCid,
415
+ indexes,
416
+ fingerprintScopes,
417
+ };
418
+
419
+ const tenantBlocks = await partitions.blocks.partition(tenant);
420
+ const blockOperation = tenantBlocks.createOperation({ type: 'put', key: messageCid, value: encodedMessageBlock.bytes });
421
+
422
+ const tenantLog = await partitions.log.partition(tenant);
423
+ const operations: LevelWrapperBatchOperation<string>[] = [
424
+ blockOperation as unknown as LevelWrapperBatchOperation<string>,
425
+ ...await index.createPutOperations(tenant, messageCid, indexes),
426
+ tenantLog.createOperation({ type: 'put', key: Replication.encodePositionKey(seq), value: JSON.stringify(logEntry) }),
427
+ tenantCidToSeq.createOperation({ type: 'put', key: messageCid, value: seq.toString() }),
428
+ partitions.heads.createOperation({ type: 'put', key: tenant, value: seq.toString() }),
429
+ ...await this.createFingerprintFoldOperations(partitions, tenant, messageCid, fingerprintScopes),
430
+ ];
431
+
432
+ await partitions.root.batch(operations);
433
+
434
+ // Store-owned wake, post-commit, for `inserted` only.
435
+ this.publishWake(tenant, seq);
436
+
437
+ return {
438
+ status : 'inserted' as const,
439
+ position : await this.buildToken(tenant, seq, messageCid),
440
+ };
441
+ });
219
442
  }
220
443
 
221
- async put(
444
+ async updateIndexes(
445
+ tenant: string,
446
+ messageCid: string,
447
+ indexes: KeyValues,
448
+ options?: MessageStoreOptions
449
+ ): Promise<void> {
450
+ options?.signal?.throwIfAborted();
451
+
452
+ const partitions = await executeUnlessAborted(this.partitions(), options?.signal);
453
+ const index = await executeUnlessAborted(this.index(), options?.signal);
454
+
455
+ await this.withTenantWriteLock(tenant, async () => {
456
+ const { entry, positionKey, tenantLog } = await this.getLogEntryForMutation(
457
+ partitions, tenant, messageCid, DwnErrorCode.MessageStoreUpdateIndexesMessageNotFound
458
+ );
459
+
460
+ const storedMessage = await this.readStoredMessage(
461
+ partitions, tenant, messageCid, DwnErrorCode.MessageStoreUpdateIndexesMessageNotFound, options
462
+ );
463
+ Replication.assertFingerprintScopesUntouched(entry.fingerprintScopes, storedMessage, messageCid, indexes);
464
+
465
+ // Same row, same seq: indexes replaced; fingerprint scopes carried forward verbatim;
466
+ // fingerprints and head stay untouched.
467
+ const updatedEntry: LogEntryValue = { ...entry, indexes };
468
+
469
+ const operations: LevelWrapperBatchOperation<string>[] = [
470
+ ...await index.createDeleteOperations(tenant, messageCid),
471
+ ...await index.createPutOperations(tenant, messageCid, indexes),
472
+ tenantLog.createOperation({ type: 'put', key: positionKey, value: JSON.stringify(updatedEntry) }),
473
+ ];
474
+
475
+ await partitions.root.batch(operations);
476
+ });
477
+ }
478
+
479
+ async updateMessageAndIndexes(
222
480
  tenant: string,
481
+ messageCid: string,
223
482
  message: GenericMessage,
224
483
  indexes: KeyValues,
225
484
  options?: MessageStoreOptions
226
485
  ): Promise<void> {
227
486
  options?.signal?.throwIfAborted();
228
487
 
229
- const partition = await executeUnlessAborted(this.blockstore.partition(tenant), options?.signal);
488
+ const computedMessageCid = Cid.parseCid(await Message.getCid(message)).toString();
489
+ const normalizedMessageCid = Cid.parseCid(messageCid).toString();
490
+ if (computedMessageCid !== normalizedMessageCid) {
491
+ throw new DwnError(
492
+ DwnErrorCode.MessageStoreUpdateMessageAndIndexesCidMismatch,
493
+ `replacement message CID ${computedMessageCid} does not match target CID ${normalizedMessageCid}`
494
+ );
495
+ }
230
496
 
497
+ const partitions = await executeUnlessAborted(this.partitions(), options?.signal);
498
+ const index = await executeUnlessAborted(this.index(), options?.signal);
231
499
  const encodedMessageBlock = await executeUnlessAborted(block.encode({ value: message, codec: cbor, hasher: sha256 }), options?.signal);
232
500
 
233
- // MessageStore data may contain `encodedData` which is not taken into account when calculating the blockCID as it is optional data.
234
- const messageCid = Cid.parseCid(await Message.getCid(message));
235
- await partition.put(messageCid, encodedMessageBlock.bytes, options);
501
+ await this.withTenantWriteLock(tenant, async () => {
502
+ const { entry, positionKey, tenantLog } = await this.getLogEntryForMutation(
503
+ partitions, tenant, normalizedMessageCid, DwnErrorCode.MessageStoreUpdateMessageAndIndexesMessageNotFound
504
+ );
505
+
506
+ Replication.assertFingerprintScopesUntouched(entry.fingerprintScopes, message, normalizedMessageCid, indexes);
507
+
508
+ const updatedEntry: LogEntryValue = { ...entry, indexes };
236
509
 
237
- const messageCidString = messageCid.toString();
510
+ const tenantBlocks = await partitions.blocks.partition(tenant);
511
+ const blockOperation = tenantBlocks.createOperation({ type: 'put', key: normalizedMessageCid, value: encodedMessageBlock.bytes });
512
+ const operations: LevelWrapperBatchOperation<string>[] = [
513
+ blockOperation as unknown as LevelWrapperBatchOperation<string>,
514
+ ...await index.createDeleteOperations(tenant, normalizedMessageCid),
515
+ ...await index.createPutOperations(tenant, normalizedMessageCid, indexes),
516
+ tenantLog.createOperation({ type: 'put', key: positionKey, value: JSON.stringify(updatedEntry) }),
517
+ ];
238
518
 
239
- await this.index.put(tenant, messageCidString, indexes, options);
519
+ await partitions.root.batch(operations);
520
+ });
240
521
  }
241
522
 
523
+ async delete(tenant: string, cidString: string, options?: MessageStoreOptions): Promise<void> {
524
+ options?.signal?.throwIfAborted();
525
+
526
+ const partitions = await executeUnlessAborted(this.partitions(), options?.signal);
527
+ const index = await executeUnlessAborted(this.index(), options?.signal);
528
+
529
+ const messageCid = CID.parse(cidString).toString();
530
+
531
+ await this.withTenantWriteLock(tenant, async () => {
532
+ const tenantCidToSeq = await partitions.cidToSeq.partition(tenant);
533
+ const seqString = await tenantCidToSeq.get(messageCid, options);
534
+ if (seqString === undefined) {
535
+ // Idempotent no-op — the row does not exist.
536
+ return;
537
+ }
538
+
539
+ const seq = BigInt(seqString);
540
+ const positionKey = Replication.encodePositionKey(seq);
541
+ const tenantLog = await partitions.log.partition(tenant);
542
+ const serializedEntry = await tenantLog.get(positionKey);
543
+ if (serializedEntry === undefined) {
544
+ throw new DwnError(
545
+ DwnErrorCode.MessageStoreDeleteLogEntryMissing,
546
+ `cid index for tenant ${tenant} points to missing log entry at seq ${seqString} (CID ${messageCid})`
547
+ );
548
+ }
549
+
550
+ const entry = JSON.parse(serializedEntry) as LogEntryValue;
551
+
552
+ const tenantBlocks = await partitions.blocks.partition(tenant);
553
+ const blockOperation = tenantBlocks.createOperation({ type: 'del', key: messageCid });
554
+
555
+ // XOR is self-inverse: folding the persisted scopes again removes the row's contribution.
556
+ const operations: LevelWrapperBatchOperation<string>[] = [
557
+ blockOperation as unknown as LevelWrapperBatchOperation<string>,
558
+ ...await index.createDeleteOperations(tenant, messageCid),
559
+ tenantLog.createOperation({ type: 'del', key: positionKey }),
560
+ tenantCidToSeq.createOperation({ type: 'del', key: messageCid }),
561
+ ...await this.createFingerprintFoldOperations(partitions, tenant, messageCid, entry.fingerprintScopes),
562
+ ];
563
+
564
+ await partitions.root.batch(operations);
565
+ });
566
+ }
242
567
 
243
568
  /**
244
- * deletes everything in the underlying blockstore and indices.
569
+ * deletes everything in the underlying store, then persists a fresh epoch — a full local
570
+ * reset after which no previously issued cursor is valid.
245
571
  */
246
572
  async clear(): Promise<void> {
247
- await this.blockstore.clear();
248
- await this.index.clear();
573
+ const partitions = await this.partitions();
574
+ await partitions.root.clear();
575
+
576
+ this.epochPromise = undefined;
577
+ await this.getEpoch();
578
+ }
579
+
580
+ // ---------------------------------------------------------------------------
581
+ // ReplicationFeedReader
582
+ // ---------------------------------------------------------------------------
583
+
584
+ async logRead(tenant: string, options: EventLogReadOptions = {}): Promise<EventLogReadResult> {
585
+ const partitions = await this.partitions();
586
+ const { cursor, limit, filters } = options;
587
+
588
+ // Head-captured-first: the per-tenant write mutex serializes commits in position order, so an
589
+ // observed head H is a visibility barrier — every position <= H is already committed when the
590
+ // range scans below run, and anything committed afterward has a position > H and waits for
591
+ // the next page.
592
+ const head = await this.getHead(partitions, tenant);
593
+ if (cursor !== undefined) {
594
+ await this.validateCursor(partitions, tenant, cursor, head);
595
+ }
596
+
597
+ const startPosition = cursor === undefined ? 0n : BigInt(cursor.position);
598
+
599
+ if (head === 0n) {
600
+ // Nothing to scan — caught up at the input position.
601
+ return { events: [], cursor, drained: true };
602
+ }
603
+
604
+ const maxResults = limit ?? Number.MAX_SAFE_INTEGER;
605
+ if (maxResults <= 0) {
606
+ return { events: [], cursor, drained: startPosition >= head };
607
+ }
608
+
609
+ if (startPosition >= head) {
610
+ // Nothing to scan — caught up at the input position.
611
+ return { events: [], cursor, drained: true };
612
+ }
613
+
614
+ const tenantLog = await partitions.log.partition(tenant);
615
+ const tenantBlocks = await partitions.blocks.partition(tenant);
616
+
617
+ const iteratorRange = { gt: Replication.encodePositionKey(startPosition), lte: Replication.encodePositionKey(head) };
618
+ const logIterator = tenantLog.iterator(iteratorRange);
619
+
620
+ const events: EventLogEntry[] = [];
621
+ let drained = true;
622
+ let lastScannedPosition = startPosition;
623
+ let lastDeliveredPosition: bigint | undefined;
624
+ let lastDeliveredMessageCid: string | undefined;
625
+
626
+ for await (const [positionKey, serializedEntry] of logIterator) {
627
+ const position = BigInt(positionKey);
628
+ lastScannedPosition = position;
629
+ const entry = JSON.parse(serializedEntry) as LogEntryValue;
630
+
631
+ const event = await this.readEventFromLogEntry(tenantBlocks, position, entry, filters);
632
+ if (event === undefined) {
633
+ continue;
634
+ }
635
+
636
+ events.push(event);
637
+ lastDeliveredPosition = position;
638
+ lastDeliveredMessageCid = event.messageCid;
639
+
640
+ if (events.length >= maxResults) {
641
+ drained = position >= head;
642
+ break;
643
+ }
644
+ }
645
+
646
+ // High-water cursor: the highest position scanned — head when the scan completed, the stop
647
+ // position otherwise. `messageCid` is set only when the cursor position is a delivered row.
648
+ const cursorPosition = drained ? head : lastScannedPosition;
649
+ const cursorMessageCid = lastDeliveredPosition === cursorPosition ? lastDeliveredMessageCid : undefined;
650
+ const resultCursor = await this.buildToken(tenant, cursorPosition, cursorMessageCid);
651
+
652
+ return { events, cursor: resultCursor, drained };
653
+ }
654
+
655
+ private async readEventFromLogEntry(
656
+ tenantBlocks: LevelWrapper<Uint8Array>,
657
+ position: bigint,
658
+ entry: LogEntryValue,
659
+ filters: Filter[] | undefined,
660
+ ): Promise<EventLogEntry | undefined> {
661
+ if (filters !== undefined && filters.length > 0 && !FilterUtility.matchAnyFilter(entry.indexes, filters)) {
662
+ return undefined;
663
+ }
664
+
665
+ const bytes = await tenantBlocks.get(entry.messageCid);
666
+ if (bytes === undefined) {
667
+ // The row was deleted after the head capture; skip — its positions are gone with it.
668
+ return undefined;
669
+ }
670
+
671
+ const decodedBlock = await block.decode({ bytes, codec: cbor, hasher: sha256 });
672
+ const message = decodedBlock.value as GenericMessage;
673
+ return {
674
+ seq : entry.seq,
675
+ position : position.toString(),
676
+ event : { message },
677
+ indexes : entry.indexes,
678
+ messageCid : entry.messageCid,
679
+ };
680
+ }
681
+
682
+ async logBounds(tenant: string): Promise<{ oldest: ProgressToken; latest: ProgressToken } | undefined> {
683
+ const partitions = await this.partitions();
684
+ const head = await this.getHead(partitions, tenant);
685
+ if (head === 0n) {
686
+ return undefined;
687
+ }
688
+
689
+ // The log is the live set — replay from zero is always available, so the oldest resumable
690
+ // position is always 0 regardless of compaction.
691
+ const oldest = await this.buildToken(tenant, 0n);
692
+
693
+ // Resolve the head position for the latest token's messageCid.
694
+ const headKey = Replication.encodePositionKey(head);
695
+ const tenantLog = await partitions.log.partition(tenant);
696
+
697
+ let headMessageCid: string | undefined;
698
+ const headLogEntry = await tenantLog.get(headKey);
699
+ if (headLogEntry !== undefined) {
700
+ headMessageCid = (JSON.parse(headLogEntry) as LogEntryValue).messageCid;
701
+ }
702
+
703
+ const latest = await this.buildToken(tenant, head, headMessageCid);
704
+ return { oldest, latest };
705
+ }
706
+
707
+ async fingerprint(tenant: string, scopes: string[]): Promise<string> {
708
+ const partitions = await this.partitions();
709
+ const tenantFingerprints = await partitions.fingerprints.partition(tenant);
710
+
711
+ let composed = Replication.emptyFingerprint();
712
+ for (const scope of scopes) {
713
+ const storedHex = await tenantFingerprints.get(MessageStoreLevel.fingerprintKey(scope));
714
+ if (storedHex !== undefined) {
715
+ composed = Replication.xorFingerprint(composed, Replication.hexToFingerprint(storedHex));
716
+ }
717
+ }
718
+
719
+ return Replication.fingerprintToHex(composed);
720
+ }
721
+
722
+ // ---------------------------------------------------------------------------
723
+ // Internals
724
+ // ---------------------------------------------------------------------------
725
+
726
+ /**
727
+ * Serializes all log-mutating operations for a tenant: the lock spans seq assignment through
728
+ * batch write, so commit order equals seq order (synchronous assignment alone is insufficient —
729
+ * a later batch could land first).
730
+ */
731
+ private async withTenantWriteLock<T>(tenant: string, task: () => Promise<T>): Promise<T> {
732
+ const previous = this.writeLocks.get(tenant) ?? Promise.resolve();
733
+ let release!: () => void;
734
+ const current = new Promise<void>((resolve) => { release = resolve; });
735
+ this.writeLocks.set(tenant, current);
736
+
737
+ await previous;
738
+ try {
739
+ return await task();
740
+ } finally {
741
+ release();
742
+ if (this.writeLocks.get(tenant) === current) {
743
+ this.writeLocks.delete(tenant);
744
+ }
745
+ }
746
+ }
747
+
748
+ /**
749
+ * Reads the tenant's counter high-water (the highest position ever issued), `0n` when unused.
750
+ */
751
+ private async getHead(partitions: StorePartitions, tenant: string): Promise<bigint> {
752
+ const headString = await partitions.heads.get(tenant);
753
+ return headString === undefined ? 0n : BigInt(headString);
754
+ }
755
+
756
+ private async assertNoPreSubstrateLayout(partitions: StorePartitions): Promise<void> {
757
+ if (await partitions.meta.get(EPOCH_KEY) !== undefined) {
758
+ return;
759
+ }
760
+
761
+ for await (const key of partitions.root.keys()) {
762
+ const partition = MessageStoreLevel.parseSublevelPartition(key);
763
+ if (partition === undefined || CURRENT_PARTITIONS.has(partition)) {
764
+ continue;
765
+ }
766
+
767
+ throw new DwnError(
768
+ DwnErrorCode.MessageStorePreSubstrateLayout,
769
+ `message store location ${this.config.location} contains pre-substrate Level data; reset the store before opening it`
770
+ );
771
+ }
772
+ }
773
+
774
+ private static parseSublevelPartition(key: string): string | undefined {
775
+ if (!key.startsWith('!')) {
776
+ return undefined;
777
+ }
778
+
779
+ const end = key.indexOf('!', 1);
780
+ return end === -1 ? undefined : key.slice(1, end);
781
+ }
782
+
783
+ private async readStoredMessage(
784
+ partitions: StorePartitions,
785
+ tenant: string,
786
+ messageCid: string,
787
+ notFoundErrorCode: DwnErrorCode,
788
+ options?: MessageStoreOptions,
789
+ ): Promise<GenericMessage> {
790
+ const tenantBlocks = await partitions.blocks.partition(tenant);
791
+ const bytes = await tenantBlocks.get(messageCid, options);
792
+ if (bytes === undefined) {
793
+ throw new DwnError(notFoundErrorCode, `no message block found for tenant ${tenant} with CID ${messageCid}`);
794
+ }
795
+
796
+ const decodedBlock = await block.decode({ bytes, codec: cbor, hasher: sha256 });
797
+ return decodedBlock.value as GenericMessage;
798
+ }
799
+
800
+ /**
801
+ * Validates a replication cursor against the tenant stream and persisted store epoch.
802
+ * Throws `EventLogProgressGap` with bounds metadata when the cursor cannot be replayed.
803
+ */
804
+ private async validateCursor(partitions: StorePartitions, tenant: string, cursor: ProgressToken, head: bigint): Promise<void> {
805
+ const expectedStreamId = await Replication.deriveStreamId(tenant);
806
+
807
+ const reason = await this.validateCursorPosition(partitions, tenant, cursor, head, expectedStreamId);
808
+ if (reason === undefined) {
809
+ return;
810
+ }
811
+
812
+ const bounds = await this.logBounds(tenant);
813
+ const gapInfo: ProgressGapInfo = {
814
+ requested : cursor,
815
+ oldestAvailable : bounds?.oldest ?? cursor,
816
+ latestAvailable : bounds?.latest ?? cursor,
817
+ reason,
818
+ };
819
+
820
+ const error = new DwnError(
821
+ DwnErrorCode.EventLogProgressGap,
822
+ `progress token gap: ${reason}`
823
+ );
824
+ (error as any).gapInfo = gapInfo;
825
+ throw error;
826
+ }
827
+
828
+ private async validateCursorPosition(
829
+ partitions: StorePartitions,
830
+ tenant: string,
831
+ cursor: ProgressToken,
832
+ head: bigint,
833
+ expectedStreamId: string,
834
+ ): Promise<ProgressGapReason | undefined> {
835
+ if (cursor.streamId !== expectedStreamId) {
836
+ return 'stream_mismatch';
837
+ }
838
+
839
+ if (cursor.epoch !== await this.getEpoch()) {
840
+ return 'epoch_mismatch';
841
+ }
842
+
843
+ const cursorPosition = BigInt(cursor.position);
844
+ if (cursorPosition > head) {
845
+ return 'token_too_new';
846
+ }
847
+
848
+ if (cursor.messageCid === undefined) {
849
+ return undefined;
850
+ }
851
+
852
+ const positionMessageCid = await this.getMessageCidAtPosition(partitions, tenant, cursorPosition);
853
+ if (positionMessageCid !== undefined && positionMessageCid !== cursor.messageCid) {
854
+ return 'message_mismatch';
855
+ }
856
+
857
+ return undefined;
858
+ }
859
+
860
+ private async getMessageCidAtPosition(partitions: StorePartitions, tenant: string, position: bigint): Promise<string | undefined> {
861
+ if (position <= 0n) {
862
+ return undefined;
863
+ }
864
+
865
+ const positionKey = Replication.encodePositionKey(position);
866
+ const tenantLog = await partitions.log.partition(tenant);
867
+ const serializedEntry = await tenantLog.get(positionKey);
868
+ if (serializedEntry !== undefined) {
869
+ return (JSON.parse(serializedEntry) as LogEntryValue).messageCid;
870
+ }
871
+
872
+ return undefined;
873
+ }
874
+
875
+ /**
876
+ * Resolves the log row for a same-CID mutation, throwing the given code when no row exists.
877
+ */
878
+ private async getLogEntryForMutation(
879
+ partitions: StorePartitions,
880
+ tenant: string,
881
+ messageCid: string,
882
+ notFoundErrorCode: DwnErrorCode,
883
+ ): Promise<{ entry: LogEntryValue, positionKey: string, tenantLog: LevelWrapper<string> }> {
884
+ const tenantCidToSeq = await partitions.cidToSeq.partition(tenant);
885
+ const seqString = await tenantCidToSeq.get(messageCid);
886
+ if (seqString === undefined) {
887
+ throw new DwnError(notFoundErrorCode, `no message found for tenant ${tenant} with CID ${messageCid}`);
888
+ }
889
+
890
+ const positionKey = Replication.encodePositionKey(BigInt(seqString));
891
+ const tenantLog = await partitions.log.partition(tenant);
892
+ const serializedEntry = await tenantLog.get(positionKey);
893
+ if (serializedEntry === undefined) {
894
+ throw new DwnError(notFoundErrorCode, `no log entry found for tenant ${tenant} at seq ${seqString} (CID ${messageCid})`);
895
+ }
896
+
897
+ return { entry: JSON.parse(serializedEntry) as LogEntryValue, positionKey, tenantLog };
898
+ }
899
+
900
+ /**
901
+ * Creates the batch operations that fold a message CID's contribution into the given
902
+ * fingerprint domains. XOR is self-inverse, so the same operations serve insert and delete.
903
+ */
904
+ private async createFingerprintFoldOperations(
905
+ partitions: StorePartitions,
906
+ tenant: string,
907
+ messageCid: string,
908
+ scopes: string[],
909
+ ): Promise<LevelWrapperBatchOperation<string>[]> {
910
+ const contribution = await Replication.hashMessageCid(messageCid);
911
+ const tenantFingerprints = await partitions.fingerprints.partition(tenant);
912
+
913
+ const operations: LevelWrapperBatchOperation<string>[] = [];
914
+ for (const scope of scopes) {
915
+ const key = MessageStoreLevel.fingerprintKey(scope);
916
+ const storedHex = await tenantFingerprints.get(key);
917
+ const current = storedHex === undefined ? Replication.emptyFingerprint() : Replication.hexToFingerprint(storedHex);
918
+ const folded = Replication.xorFingerprint(current, contribution);
919
+ operations.push(tenantFingerprints.createOperation({ type: 'put', key, value: Replication.fingerprintToHex(folded) }));
920
+ }
921
+
922
+ return operations;
923
+ }
924
+
925
+ /**
926
+ * Encodes a fingerprint domain name as a Level key. The uniform prefix keeps the global
927
+ * domain (the empty string) a valid key.
928
+ */
929
+ private static fingerprintKey(scope: string): string {
930
+ return `d${scope}`;
931
+ }
932
+
933
+ private async buildToken(tenant: string, position: bigint, messageCid?: string): Promise<ProgressToken> {
934
+ const token: ProgressToken = {
935
+ streamId : await Replication.deriveStreamId(tenant),
936
+ epoch : await this.getEpoch(),
937
+ position : position.toString(),
938
+ };
939
+ if (messageCid !== undefined) {
940
+ token.messageCid = messageCid;
941
+ }
942
+ return token;
943
+ }
944
+
945
+ /**
946
+ * Publishes a wake post-commit. Best-effort by contract — never throws into the write path.
947
+ */
948
+ private publishWake(tenant: string, position: bigint): void {
949
+ try {
950
+ this.wakePublisher?.publish({ tenant, seq: position.toString() });
951
+ } catch {
952
+ // A lost wake only delays delivery; consumers' idle re-drain bounds the latency.
953
+ }
249
954
  }
250
955
  }
251
956
 
252
957
  export type MessageStoreLevelConfig = {
253
- blockstoreLocation?: string,
254
- indexLocation?: string,
958
+ /**
959
+ * The single directory path (or IndexedDB database name in browsers) backing the whole store —
960
+ * message blocks, query indexes, the replication log, fingerprints, counters, and epoch are all
961
+ * sublevels of one Level instance.
962
+ */
963
+ location?: string,
255
964
  createLevelDatabase?: typeof createLevelDatabase,
256
965
  compoundIndexes?: CompoundIndexDefinition[],
257
- };
966
+ wakePublisher?: WakePublisher,
967
+ };