@enbox/dwn-sdk-js 0.0.7 → 0.1.0

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 (368) hide show
  1. package/dist/browser.mjs +8 -8
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/generated/precompiled-validators.js +817 -911
  4. package/dist/esm/generated/precompiled-validators.js.map +1 -1
  5. package/dist/esm/src/core/constants.js +11 -0
  6. package/dist/esm/src/core/constants.js.map +1 -0
  7. package/dist/esm/src/core/core-protocol.js +44 -0
  8. package/dist/esm/src/core/core-protocol.js.map +1 -0
  9. package/dist/esm/src/core/dwn-error.js +12 -12
  10. package/dist/esm/src/core/dwn-error.js.map +1 -1
  11. package/dist/esm/src/core/grant-authorization.js +16 -3
  12. package/dist/esm/src/core/grant-authorization.js.map +1 -1
  13. package/dist/esm/src/core/protocol-authorization-action.js +5 -0
  14. package/dist/esm/src/core/protocol-authorization-action.js.map +1 -1
  15. package/dist/esm/src/core/protocol-authorization-validation.js +91 -0
  16. package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -1
  17. package/dist/esm/src/core/protocol-authorization.js +53 -30
  18. package/dist/esm/src/core/protocol-authorization.js.map +1 -1
  19. package/dist/esm/src/core/records-grant-authorization.js +6 -8
  20. package/dist/esm/src/core/records-grant-authorization.js.map +1 -1
  21. package/dist/esm/src/core/resumable-task-manager.js +2 -0
  22. package/dist/esm/src/core/resumable-task-manager.js.map +1 -1
  23. package/dist/esm/src/dwn.js +42 -18
  24. package/dist/esm/src/dwn.js.map +1 -1
  25. package/dist/esm/src/event-stream/event-emitter-event-log.js +204 -0
  26. package/dist/esm/src/event-stream/event-emitter-event-log.js.map +1 -0
  27. package/dist/esm/src/handlers/messages-read.js +7 -11
  28. package/dist/esm/src/handlers/messages-read.js.map +1 -1
  29. package/dist/esm/src/handlers/messages-subscribe.js +22 -24
  30. package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
  31. package/dist/esm/src/handlers/messages-sync.js +11 -15
  32. package/dist/esm/src/handlers/messages-sync.js.map +1 -1
  33. package/dist/esm/src/handlers/protocols-configure.js +37 -27
  34. package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
  35. package/dist/esm/src/handlers/protocols-query.js +7 -11
  36. package/dist/esm/src/handlers/protocols-query.js.map +1 -1
  37. package/dist/esm/src/handlers/records-count.js +10 -12
  38. package/dist/esm/src/handlers/records-count.js.map +1 -1
  39. package/dist/esm/src/handlers/records-delete.js +10 -18
  40. package/dist/esm/src/handlers/records-delete.js.map +1 -1
  41. package/dist/esm/src/handlers/records-query.js +11 -15
  42. package/dist/esm/src/handlers/records-query.js.map +1 -1
  43. package/dist/esm/src/handlers/records-read.js +31 -26
  44. package/dist/esm/src/handlers/records-read.js.map +1 -1
  45. package/dist/esm/src/handlers/records-subscribe.js +39 -26
  46. package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
  47. package/dist/esm/src/handlers/records-write.js +128 -105
  48. package/dist/esm/src/handlers/records-write.js.map +1 -1
  49. package/dist/esm/src/index.js +5 -2
  50. package/dist/esm/src/index.js.map +1 -1
  51. package/dist/esm/src/interfaces/messages-subscribe.js +1 -0
  52. package/dist/esm/src/interfaces/messages-subscribe.js.map +1 -1
  53. package/dist/esm/src/interfaces/protocols-configure.js +33 -3
  54. package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
  55. package/dist/esm/src/interfaces/records-count.js +1 -1
  56. package/dist/esm/src/interfaces/records-count.js.map +1 -1
  57. package/dist/esm/src/interfaces/records-delete.js +1 -1
  58. package/dist/esm/src/interfaces/records-delete.js.map +1 -1
  59. package/dist/esm/src/interfaces/records-query.js +1 -1
  60. package/dist/esm/src/interfaces/records-query.js.map +1 -1
  61. package/dist/esm/src/interfaces/records-read.js +1 -1
  62. package/dist/esm/src/interfaces/records-read.js.map +1 -1
  63. package/dist/esm/src/interfaces/records-subscribe.js +2 -1
  64. package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
  65. package/dist/esm/src/interfaces/records-write-signing.js +1 -12
  66. package/dist/esm/src/interfaces/records-write-signing.js.map +1 -1
  67. package/dist/esm/src/interfaces/records-write.js +25 -41
  68. package/dist/esm/src/interfaces/records-write.js.map +1 -1
  69. package/dist/esm/src/protocols/permission-grant.js +1 -1
  70. package/dist/esm/src/protocols/permission-grant.js.map +1 -1
  71. package/dist/esm/src/protocols/permission-request.js +1 -1
  72. package/dist/esm/src/protocols/permission-request.js.map +1 -1
  73. package/dist/esm/src/protocols/permissions.js +113 -5
  74. package/dist/esm/src/protocols/permissions.js.map +1 -1
  75. package/dist/esm/src/state-index/state-index-level.js +5 -7
  76. package/dist/esm/src/state-index/state-index-level.js.map +1 -1
  77. package/dist/esm/src/store/data-store-level.js +110 -33
  78. package/dist/esm/src/store/data-store-level.js.map +1 -1
  79. package/dist/esm/src/store/index-level.js +42 -32
  80. package/dist/esm/src/store/index-level.js.map +1 -1
  81. package/dist/esm/src/store/storage-controller.js +70 -6
  82. package/dist/esm/src/store/storage-controller.js.map +1 -1
  83. package/dist/esm/src/types/permission-types.js.map +1 -1
  84. package/dist/esm/src/types/protocols-types.js +11 -0
  85. package/dist/esm/src/types/protocols-types.js.map +1 -1
  86. package/dist/esm/src/types/records-types.js.map +1 -1
  87. package/dist/esm/src/utils/hd-key.js +0 -8
  88. package/dist/esm/src/utils/hd-key.js.map +1 -1
  89. package/dist/esm/src/utils/messages.js +16 -34
  90. package/dist/esm/src/utils/messages.js.map +1 -1
  91. package/dist/esm/src/utils/records.js +5 -43
  92. package/dist/esm/src/utils/records.js.map +1 -1
  93. package/dist/esm/tests/core/protocol-authorization.spec.js +2 -1
  94. package/dist/esm/tests/core/protocol-authorization.spec.js.map +1 -1
  95. package/dist/esm/tests/dwn.spec.js +32 -43
  96. package/dist/esm/tests/dwn.spec.js.map +1 -1
  97. package/dist/esm/tests/event-emitter-event-log.spec.js +305 -0
  98. package/dist/esm/tests/event-emitter-event-log.spec.js.map +1 -0
  99. package/dist/esm/tests/features/author-delegated-grant.spec.js +14 -7
  100. package/dist/esm/tests/features/author-delegated-grant.spec.js.map +1 -1
  101. package/dist/esm/tests/features/owner-delegated-grant.spec.js +9 -5
  102. package/dist/esm/tests/features/owner-delegated-grant.spec.js.map +1 -1
  103. package/dist/esm/tests/features/owner-signature.spec.js +14 -7
  104. package/dist/esm/tests/features/owner-signature.spec.js.map +1 -1
  105. package/dist/esm/tests/features/permissions.spec.js +12 -12
  106. package/dist/esm/tests/features/permissions.spec.js.map +1 -1
  107. package/dist/esm/tests/features/protocol-composition.spec.js +636 -5
  108. package/dist/esm/tests/features/protocol-composition.spec.js.map +1 -1
  109. package/dist/esm/tests/features/protocol-create-action.spec.js +4 -4
  110. package/dist/esm/tests/features/protocol-create-action.spec.js.map +1 -1
  111. package/dist/esm/tests/features/protocol-delete-action.spec.js +7 -7
  112. package/dist/esm/tests/features/protocol-delete-action.spec.js.map +1 -1
  113. package/dist/esm/tests/features/protocol-update-action.spec.js +4 -4
  114. package/dist/esm/tests/features/protocol-update-action.spec.js.map +1 -1
  115. package/dist/esm/tests/features/records-delivery.spec.js +236 -0
  116. package/dist/esm/tests/features/records-delivery.spec.js.map +1 -0
  117. package/dist/esm/tests/features/records-immutable.spec.js +315 -0
  118. package/dist/esm/tests/features/records-immutable.spec.js.map +1 -0
  119. package/dist/esm/tests/features/records-prune.spec.js +4 -4
  120. package/dist/esm/tests/features/records-prune.spec.js.map +1 -1
  121. package/dist/esm/tests/features/records-record-limit.spec.js +542 -0
  122. package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -0
  123. package/dist/esm/tests/features/records-squash.spec.js +1055 -0
  124. package/dist/esm/tests/features/records-squash.spec.js.map +1 -0
  125. package/dist/esm/tests/features/records-tags.spec.js +16 -4
  126. package/dist/esm/tests/features/records-tags.spec.js.map +1 -1
  127. package/dist/esm/tests/features/resumable-tasks.spec.js +7 -8
  128. package/dist/esm/tests/features/resumable-tasks.spec.js.map +1 -1
  129. package/dist/esm/tests/handlers/messages-read.spec.js +11 -5
  130. package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
  131. package/dist/esm/tests/handlers/messages-subscribe.spec.js +169 -22
  132. package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
  133. package/dist/esm/tests/handlers/messages-sync.spec.js +103 -21
  134. package/dist/esm/tests/handlers/messages-sync.spec.js.map +1 -1
  135. package/dist/esm/tests/handlers/protocols-configure.spec.js +5 -5
  136. package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
  137. package/dist/esm/tests/handlers/protocols-query.spec.js +5 -5
  138. package/dist/esm/tests/handlers/protocols-query.spec.js.map +1 -1
  139. package/dist/esm/tests/handlers/records-count.spec.js +9 -4
  140. package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
  141. package/dist/esm/tests/handlers/records-delete.spec.js +24 -25
  142. package/dist/esm/tests/handlers/records-delete.spec.js.map +1 -1
  143. package/dist/esm/tests/handlers/records-query.spec.js +68 -9
  144. package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
  145. package/dist/esm/tests/handlers/records-read.spec.js +24 -138
  146. package/dist/esm/tests/handlers/records-read.spec.js.map +1 -1
  147. package/dist/esm/tests/handlers/records-subscribe.spec.js +175 -35
  148. package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
  149. package/dist/esm/tests/handlers/records-write.spec.js +176 -72
  150. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  151. package/dist/esm/tests/interfaces/records-write.spec.js +52 -68
  152. package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
  153. package/dist/esm/tests/protocols/permission-grant.spec.js +6 -6
  154. package/dist/esm/tests/protocols/permission-grant.spec.js.map +1 -1
  155. package/dist/esm/tests/protocols/permission-request.spec.js +4 -4
  156. package/dist/esm/tests/protocols/permission-request.spec.js.map +1 -1
  157. package/dist/esm/tests/protocols/permissions.spec.js +4 -4
  158. package/dist/esm/tests/protocols/permissions.spec.js.map +1 -1
  159. package/dist/esm/tests/scenarios/aggregator.spec.js +4 -4
  160. package/dist/esm/tests/scenarios/aggregator.spec.js.map +1 -1
  161. package/dist/esm/tests/scenarios/deleted-record.spec.js +350 -5
  162. package/dist/esm/tests/scenarios/deleted-record.spec.js.map +1 -1
  163. package/dist/esm/tests/scenarios/end-to-end-tests.spec.js +4 -4
  164. package/dist/esm/tests/scenarios/end-to-end-tests.spec.js.map +1 -1
  165. package/dist/esm/tests/scenarios/nested-roles.spec.js +4 -4
  166. package/dist/esm/tests/scenarios/nested-roles.spec.js.map +1 -1
  167. package/dist/esm/tests/scenarios/subscriptions.spec.js +93 -40
  168. package/dist/esm/tests/scenarios/subscriptions.spec.js.map +1 -1
  169. package/dist/esm/tests/store/data-store-level.spec.js +102 -41
  170. package/dist/esm/tests/store/data-store-level.spec.js.map +1 -1
  171. package/dist/esm/tests/test-event-stream.js +12 -13
  172. package/dist/esm/tests/test-event-stream.js.map +1 -1
  173. package/dist/esm/tests/test-suite.js +10 -4
  174. package/dist/esm/tests/test-suite.js.map +1 -1
  175. package/dist/esm/tests/utils/messages.spec.js +12 -5
  176. package/dist/esm/tests/utils/messages.spec.js.map +1 -1
  177. package/dist/esm/tests/utils/records.spec.js +8 -12
  178. package/dist/esm/tests/utils/records.spec.js.map +1 -1
  179. package/dist/esm/tests/utils/test-data-generator.js +36 -2
  180. package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
  181. package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js +37 -8
  182. package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js.map +1 -1
  183. package/dist/types/generated/precompiled-validators.d.ts +49 -40
  184. package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
  185. package/dist/types/src/core/constants.d.ts +11 -0
  186. package/dist/types/src/core/constants.d.ts.map +1 -0
  187. package/dist/types/src/core/core-protocol.d.ts +89 -0
  188. package/dist/types/src/core/core-protocol.d.ts.map +1 -0
  189. package/dist/types/src/core/dwn-error.d.ts +12 -12
  190. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  191. package/dist/types/src/core/grant-authorization.d.ts +6 -2
  192. package/dist/types/src/core/grant-authorization.d.ts.map +1 -1
  193. package/dist/types/src/core/protocol-authorization-action.d.ts.map +1 -1
  194. package/dist/types/src/core/protocol-authorization-validation.d.ts +30 -0
  195. package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -1
  196. package/dist/types/src/core/protocol-authorization.d.ts +19 -11
  197. package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
  198. package/dist/types/src/core/records-grant-authorization.d.ts.map +1 -1
  199. package/dist/types/src/core/resumable-task-manager.d.ts +2 -1
  200. package/dist/types/src/core/resumable-task-manager.d.ts.map +1 -1
  201. package/dist/types/src/dwn.d.ts +19 -7
  202. package/dist/types/src/dwn.d.ts.map +1 -1
  203. package/dist/types/src/event-stream/event-emitter-event-log.d.ts +50 -0
  204. package/dist/types/src/event-stream/event-emitter-event-log.d.ts.map +1 -0
  205. package/dist/types/src/handlers/messages-read.d.ts +3 -8
  206. package/dist/types/src/handlers/messages-read.d.ts.map +1 -1
  207. package/dist/types/src/handlers/messages-subscribe.d.ts +6 -10
  208. package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
  209. package/dist/types/src/handlers/messages-sync.d.ts +3 -8
  210. package/dist/types/src/handlers/messages-sync.d.ts.map +1 -1
  211. package/dist/types/src/handlers/protocols-configure.d.ts +3 -10
  212. package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
  213. package/dist/types/src/handlers/protocols-query.d.ts +3 -8
  214. package/dist/types/src/handlers/protocols-query.d.ts.map +1 -1
  215. package/dist/types/src/handlers/records-count.d.ts +3 -6
  216. package/dist/types/src/handlers/records-count.d.ts.map +1 -1
  217. package/dist/types/src/handlers/records-delete.d.ts +3 -8
  218. package/dist/types/src/handlers/records-delete.d.ts.map +1 -1
  219. package/dist/types/src/handlers/records-query.d.ts +3 -8
  220. package/dist/types/src/handlers/records-query.d.ts.map +1 -1
  221. package/dist/types/src/handlers/records-read.d.ts +3 -8
  222. package/dist/types/src/handlers/records-read.d.ts.map +1 -1
  223. package/dist/types/src/handlers/records-subscribe.d.ts +8 -10
  224. package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
  225. package/dist/types/src/handlers/records-write.d.ts +12 -25
  226. package/dist/types/src/handlers/records-write.d.ts.map +1 -1
  227. package/dist/types/src/index.d.ts +8 -4
  228. package/dist/types/src/index.d.ts.map +1 -1
  229. package/dist/types/src/interfaces/messages-subscribe.d.ts +5 -0
  230. package/dist/types/src/interfaces/messages-subscribe.d.ts.map +1 -1
  231. package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
  232. package/dist/types/src/interfaces/records-subscribe.d.ts +5 -0
  233. package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
  234. package/dist/types/src/interfaces/records-write-signing.d.ts +3 -4
  235. package/dist/types/src/interfaces/records-write-signing.d.ts.map +1 -1
  236. package/dist/types/src/interfaces/records-write.d.ts +11 -11
  237. package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
  238. package/dist/types/src/protocols/permission-grant.d.ts +1 -1
  239. package/dist/types/src/protocols/permission-grant.d.ts.map +1 -1
  240. package/dist/types/src/protocols/permission-request.d.ts +1 -1
  241. package/dist/types/src/protocols/permission-request.d.ts.map +1 -1
  242. package/dist/types/src/protocols/permissions.d.ts +40 -3
  243. package/dist/types/src/protocols/permissions.d.ts.map +1 -1
  244. package/dist/types/src/state-index/state-index-level.d.ts.map +1 -1
  245. package/dist/types/src/store/data-store-level.d.ts +20 -4
  246. package/dist/types/src/store/data-store-level.d.ts.map +1 -1
  247. package/dist/types/src/store/index-level.d.ts +4 -0
  248. package/dist/types/src/store/index-level.d.ts.map +1 -1
  249. package/dist/types/src/store/storage-controller.d.ts +20 -6
  250. package/dist/types/src/store/storage-controller.d.ts.map +1 -1
  251. package/dist/types/src/types/message-types.d.ts +3 -3
  252. package/dist/types/src/types/message-types.d.ts.map +1 -1
  253. package/dist/types/src/types/messages-types.d.ts +12 -3
  254. package/dist/types/src/types/messages-types.d.ts.map +1 -1
  255. package/dist/types/src/types/method-handler.d.ts +24 -3
  256. package/dist/types/src/types/method-handler.d.ts.map +1 -1
  257. package/dist/types/src/types/permission-types.d.ts +7 -0
  258. package/dist/types/src/types/permission-types.d.ts.map +1 -1
  259. package/dist/types/src/types/protocols-types.d.ts +69 -2
  260. package/dist/types/src/types/protocols-types.d.ts.map +1 -1
  261. package/dist/types/src/types/records-types.d.ts +23 -6
  262. package/dist/types/src/types/records-types.d.ts.map +1 -1
  263. package/dist/types/src/types/subscriptions.d.ts +151 -13
  264. package/dist/types/src/types/subscriptions.d.ts.map +1 -1
  265. package/dist/types/src/utils/hd-key.d.ts +1 -9
  266. package/dist/types/src/utils/hd-key.d.ts.map +1 -1
  267. package/dist/types/src/utils/messages.d.ts +7 -5
  268. package/dist/types/src/utils/messages.d.ts.map +1 -1
  269. package/dist/types/src/utils/records.d.ts +1 -11
  270. package/dist/types/src/utils/records.d.ts.map +1 -1
  271. package/dist/types/tests/dwn.spec.d.ts.map +1 -1
  272. package/dist/types/tests/event-emitter-event-log.spec.d.ts +2 -0
  273. package/dist/types/tests/event-emitter-event-log.spec.d.ts.map +1 -0
  274. package/dist/types/tests/features/author-delegated-grant.spec.d.ts.map +1 -1
  275. package/dist/types/tests/features/owner-delegated-grant.spec.d.ts.map +1 -1
  276. package/dist/types/tests/features/owner-signature.spec.d.ts.map +1 -1
  277. package/dist/types/tests/features/protocol-composition.spec.d.ts.map +1 -1
  278. package/dist/types/tests/features/records-delivery.spec.d.ts +2 -0
  279. package/dist/types/tests/features/records-delivery.spec.d.ts.map +1 -0
  280. package/dist/types/tests/features/records-immutable.spec.d.ts +2 -0
  281. package/dist/types/tests/features/records-immutable.spec.d.ts.map +1 -0
  282. package/dist/types/tests/features/records-record-limit.spec.d.ts +2 -0
  283. package/dist/types/tests/features/records-record-limit.spec.d.ts.map +1 -0
  284. package/dist/types/tests/features/records-squash.spec.d.ts +2 -0
  285. package/dist/types/tests/features/records-squash.spec.d.ts.map +1 -0
  286. package/dist/types/tests/features/records-tags.spec.d.ts.map +1 -1
  287. package/dist/types/tests/features/resumable-tasks.spec.d.ts.map +1 -1
  288. package/dist/types/tests/handlers/messages-read.spec.d.ts.map +1 -1
  289. package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
  290. package/dist/types/tests/handlers/messages-sync.spec.d.ts.map +1 -1
  291. package/dist/types/tests/handlers/records-count.spec.d.ts.map +1 -1
  292. package/dist/types/tests/handlers/records-delete.spec.d.ts.map +1 -1
  293. package/dist/types/tests/handlers/records-query.spec.d.ts.map +1 -1
  294. package/dist/types/tests/handlers/records-read.spec.d.ts.map +1 -1
  295. package/dist/types/tests/handlers/records-subscribe.spec.d.ts.map +1 -1
  296. package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
  297. package/dist/types/tests/scenarios/deleted-record.spec.d.ts.map +1 -1
  298. package/dist/types/tests/scenarios/subscriptions.spec.d.ts.map +1 -1
  299. package/dist/types/tests/test-event-stream.d.ts +11 -12
  300. package/dist/types/tests/test-event-stream.d.ts.map +1 -1
  301. package/dist/types/tests/test-suite.d.ts +2 -2
  302. package/dist/types/tests/test-suite.d.ts.map +1 -1
  303. package/dist/types/tests/utils/test-data-generator.d.ts +19 -0
  304. package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
  305. package/package.json +5 -4
  306. package/src/core/constants.ts +11 -0
  307. package/src/core/core-protocol.ts +129 -0
  308. package/src/core/dwn-error.ts +18 -12
  309. package/src/core/grant-authorization.ts +20 -3
  310. package/src/core/protocol-authorization-action.ts +5 -0
  311. package/src/core/protocol-authorization-validation.ts +133 -0
  312. package/src/core/protocol-authorization.ts +71 -23
  313. package/src/core/records-grant-authorization.ts +6 -8
  314. package/src/core/resumable-task-manager.ts +3 -1
  315. package/src/dwn.ts +58 -73
  316. package/src/event-stream/event-emitter-event-log.ts +283 -0
  317. package/src/handlers/messages-read.ts +8 -9
  318. package/src/handlers/messages-subscribe.ts +24 -28
  319. package/src/handlers/messages-sync.ts +10 -16
  320. package/src/handlers/protocols-configure.ts +47 -32
  321. package/src/handlers/protocols-query.ts +6 -9
  322. package/src/handlers/records-count.ts +11 -10
  323. package/src/handlers/records-delete.ts +12 -21
  324. package/src/handlers/records-query.ts +12 -12
  325. package/src/handlers/records-read.ts +34 -22
  326. package/src/handlers/records-subscribe.ts +47 -26
  327. package/src/handlers/records-write.ts +152 -119
  328. package/src/index.ts +9 -5
  329. package/src/interfaces/messages-subscribe.ts +7 -1
  330. package/src/interfaces/protocols-configure.ts +51 -3
  331. package/src/interfaces/records-count.ts +1 -1
  332. package/src/interfaces/records-delete.ts +1 -1
  333. package/src/interfaces/records-query.ts +1 -1
  334. package/src/interfaces/records-read.ts +1 -1
  335. package/src/interfaces/records-subscribe.ts +8 -1
  336. package/src/interfaces/records-write-signing.ts +2 -22
  337. package/src/interfaces/records-write.ts +35 -48
  338. package/src/protocols/permission-grant.ts +1 -1
  339. package/src/protocols/permission-request.ts +1 -1
  340. package/src/protocols/permissions.ts +148 -6
  341. package/src/state-index/state-index-level.ts +5 -7
  342. package/src/store/data-store-level.ts +124 -34
  343. package/src/store/index-level.ts +44 -35
  344. package/src/store/storage-controller.ts +89 -12
  345. package/src/types/message-types.ts +3 -3
  346. package/src/types/messages-types.ts +12 -3
  347. package/src/types/method-handler.ts +26 -4
  348. package/src/types/mitt.d.ts +28 -0
  349. package/src/types/permission-types.ts +7 -0
  350. package/src/types/protocols-types.ts +78 -1
  351. package/src/types/records-types.ts +24 -6
  352. package/src/types/subscriptions.ts +178 -14
  353. package/src/utils/hd-key.ts +0 -9
  354. package/src/utils/messages.ts +17 -37
  355. package/src/utils/records.ts +7 -58
  356. package/dist/esm/src/event-stream/event-emitter-stream.js +0 -46
  357. package/dist/esm/src/event-stream/event-emitter-stream.js.map +0 -1
  358. package/dist/esm/tests/event-stream/event-emitter-stream.spec.js +0 -68
  359. package/dist/esm/tests/event-stream/event-emitter-stream.spec.js.map +0 -1
  360. package/dist/esm/tests/event-stream/event-stream.spec.js +0 -114
  361. package/dist/esm/tests/event-stream/event-stream.spec.js.map +0 -1
  362. package/dist/types/src/event-stream/event-emitter-stream.d.ts +0 -23
  363. package/dist/types/src/event-stream/event-emitter-stream.d.ts.map +0 -1
  364. package/dist/types/tests/event-stream/event-emitter-stream.spec.d.ts +0 -2
  365. package/dist/types/tests/event-stream/event-emitter-stream.spec.d.ts.map +0 -1
  366. package/dist/types/tests/event-stream/event-stream.spec.d.ts +0 -2
  367. package/dist/types/tests/event-stream/event-stream.spec.d.ts.map +0 -1
  368. package/src/event-stream/event-emitter-stream.ts +0 -69
@@ -1,14 +1,21 @@
1
+ import type { Filter } from '../types/query-types.js';
1
2
  import type { GenericMessage } from '../types/message-types.js';
3
+ import type { MessagesFilter } from '../types/messages-types.js';
2
4
  import type { MessageSigner } from '../types/signer.js';
3
5
  import type { MessageStore } from '../types/message-store.js';
4
6
  import type { ProtocolDefinition } from '../types/protocols-types.js';
7
+ import type { CoreProtocol, CoreProtocolStores } from '../core/core-protocol.js';
5
8
  import type { DataEncodedRecordsWriteMessage, RecordsWriteMessage } from '../types/records-types.js';
6
9
  import type { PermissionConditions, PermissionGrantData, PermissionRequestData, PermissionRevocationData, PermissionScope, RecordsPermissionScope } from '../types/permission-types.js';
7
10
 
11
+ import { DwnConstant } from '../core/dwn-constant.js';
8
12
  import { Encoder } from '../utils/encoder.js';
13
+ import { FilterUtility } from '../utils/filter.js';
14
+ import { Message } from '../core/message.js';
9
15
  import { PermissionGrant } from './permission-grant.js';
10
16
  import { PermissionRequest } from './permission-request.js';
11
- import { RecordsWrite } from '../../src/interfaces/records-write.js';
17
+ import { Records } from '../utils/records.js';
18
+ import { RecordsWrite } from '../interfaces/records-write.js';
12
19
  import { Time } from '../utils/time.js';
13
20
  import { validateJsonSchema } from '../schema-validator.js';
14
21
  import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
@@ -79,12 +86,16 @@ export type PermissionRevocationCreateOptions = {
79
86
 
80
87
  /**
81
88
  * This is a first-class DWN protocol for managing permission grants of a given DWN.
89
+ *
90
+ * It implements the `CoreProtocol` interface so that its lifecycle hooks
91
+ * (validation, pre-processing, post-processing) are dispatched generically
92
+ * by the `CoreProtocolRegistry` rather than being hardcoded in handlers.
82
93
  */
83
- export class PermissionsProtocol {
94
+ export class PermissionsProtocol implements CoreProtocol {
84
95
  /**
85
96
  * The URI of the DWN Permissions protocol.
86
97
  */
87
- public static readonly uri = 'https://tbd.website/dwn/permissions';
98
+ public static readonly uri = 'https://identity.foundation/dwn/permissions';
88
99
 
89
100
  /**
90
101
  * The protocol path of the `request` record.
@@ -156,6 +167,137 @@ export class PermissionsProtocol {
156
167
  }
157
168
  };
158
169
 
170
+ // ---------- CoreProtocol instance accessors ----------
171
+
172
+ /** @inheritdoc */
173
+ public get uri(): string {
174
+ return PermissionsProtocol.uri;
175
+ }
176
+
177
+ /** @inheritdoc */
178
+ public get definition(): ProtocolDefinition {
179
+ return PermissionsProtocol.definition;
180
+ }
181
+
182
+ // ---------- CoreProtocol lifecycle hooks ----------
183
+
184
+ /** @inheritdoc */
185
+ public validateRecord(message: RecordsWriteMessage, dataBytes: Uint8Array): void {
186
+ PermissionsProtocol.validateSchema(message, dataBytes);
187
+ }
188
+
189
+ /**
190
+ * Pre-processing hook for permission revocation records.
191
+ * Validates that the revocation's `tags.protocol` matches the grant's scoped protocol.
192
+ */
193
+ public async preProcessWrite(
194
+ tenant: string,
195
+ message: RecordsWriteMessage,
196
+ messageStore: MessageStore,
197
+ ): Promise<void> {
198
+ if (message.descriptor.protocolPath !== PermissionsProtocol.revocationPath) {
199
+ return;
200
+ }
201
+
202
+ // fetch the parent grant to compare the scoped protocol against the revocation tag
203
+ const permissionGrantId = message.descriptor.parentId!;
204
+ const grant = await PermissionsProtocol.fetchGrant(tenant, messageStore, permissionGrantId);
205
+
206
+ const revokeTagProtocol = message.descriptor.tags?.protocol;
207
+ const grantProtocol = 'protocol' in grant.scope ? grant.scope.protocol : undefined;
208
+ if (grantProtocol !== revokeTagProtocol) {
209
+ throw new DwnError(
210
+ DwnErrorCode.PermissionsProtocolValidateRevocationProtocolTagMismatch,
211
+ `Revocation protocol ${revokeTagProtocol} does not match grant protocol ${grantProtocol}`
212
+ );
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Post-processing hook for permission revocation records.
218
+ * When a grant is revoked, all messages authorized by that grant and created
219
+ * after the revocation timestamp are deleted from all stores.
220
+ *
221
+ * Deletion order is deliberate to avoid orphaned data in case of crash:
222
+ * 1. data store (large blobs first)
223
+ * 2. state index (SMT entries)
224
+ * 3. message store
225
+ */
226
+ public async postProcessWrite(
227
+ tenant: string,
228
+ recordsWrite: RecordsWrite,
229
+ stores: CoreProtocolStores,
230
+ ): Promise<void> {
231
+ if (recordsWrite.message.descriptor.protocolPath !== PermissionsProtocol.revocationPath) {
232
+ return;
233
+ }
234
+
235
+ const permissionGrantId = recordsWrite.message.descriptor.parentId!;
236
+ const grantAuthorizedMessagesQuery = {
237
+ permissionGrantId,
238
+ dateCreated: { gte: recordsWrite.message.descriptor.messageTimestamp },
239
+ };
240
+ const { messages: grantAuthorizedMessages } = await stores.messageStore.query(tenant, [grantAuthorizedMessagesQuery]);
241
+
242
+ if (grantAuthorizedMessages.length === 0) {
243
+ return;
244
+ }
245
+
246
+ // 1. Delete data from the data store first to avoid orphaned data blobs in case of crash.
247
+ // Only RecordsWrite messages with data larger than maxDataSizeAllowedToBeEncoded have data in the data store.
248
+ for (const message of grantAuthorizedMessages) {
249
+ if (message.descriptor.method === DwnMethodName.Write) {
250
+ const recordsWriteMessage = message as RecordsWriteMessage;
251
+ if (recordsWriteMessage.descriptor.dataSize > DwnConstant.maxDataSizeAllowedToBeEncoded) {
252
+ await stores.dataStore.delete(tenant, recordsWriteMessage.recordId, recordsWriteMessage.descriptor.dataCid);
253
+ }
254
+ }
255
+ }
256
+
257
+ // 2. Compute CIDs and delete from state index before message store to avoid orphaned state entries.
258
+ const messageCids = await Promise.all(grantAuthorizedMessages.map((message): Promise<string> => Message.getCid(message)));
259
+ await stores.stateIndex.delete(tenant, messageCids);
260
+
261
+ // 3. Finally delete all messages from the message store.
262
+ await Promise.all(messageCids.map((cid): Promise<void> => stores.messageStore.delete(tenant, cid)));
263
+ }
264
+
265
+ /** @inheritdoc */
266
+ public mapErrorToStatusCode(errorCode: string): number | undefined {
267
+ if (errorCode.startsWith('PermissionsProtocolValidate')) {
268
+ return 400;
269
+ }
270
+ return undefined;
271
+ }
272
+
273
+ /**
274
+ * Constructs an additional filter for protocol-scoped message queries so that
275
+ * permission records (grants, requests, revocations) tagged with the target
276
+ * protocol appear alongside that protocol's own records in sync/subscribe/read results.
277
+ */
278
+ public constructAdditionalMessageFilter(filter: MessagesFilter): Filter | undefined {
279
+ const { protocol, messageTimestamp } = filter;
280
+ if (protocol === undefined) {
281
+ return undefined;
282
+ }
283
+
284
+ const taggedFilter = {
285
+ protocol: PermissionsProtocol.uri,
286
+ ...Records.convertTagsFilter({ protocol }),
287
+ } as Filter;
288
+
289
+ if (messageTimestamp !== undefined) {
290
+ const messageTimestampFilter = FilterUtility.convertRangeCriterion(messageTimestamp);
291
+ if (messageTimestampFilter) {
292
+ taggedFilter.messageTimestamp = messageTimestampFilter;
293
+ }
294
+ }
295
+
296
+ return taggedFilter;
297
+ }
298
+
299
+ // ---------- Static utility methods ----------
300
+
159
301
  public static parseRequest(base64UrlEncodedRequest: string): PermissionRequestData {
160
302
  return Encoder.base64UrlToObject(base64UrlEncodedRequest);
161
303
  }
@@ -394,7 +536,7 @@ export class PermissionsProtocol {
394
536
  }
395
537
 
396
538
  const permissionGrantMessage = possibleGrantMessage as DataEncodedRecordsWriteMessage;
397
- const permissionGrant = await PermissionGrant.parse(permissionGrantMessage);
539
+ const permissionGrant = PermissionGrant.parse(permissionGrantMessage);
398
540
 
399
541
  return permissionGrant;
400
542
  }
@@ -421,11 +563,11 @@ export class PermissionsProtocol {
421
563
  const grant = await PermissionsProtocol.fetchGrant(tenant, messageStore, incomingMessage.descriptor.parentId!);
422
564
  return grant.scope;
423
565
  } else if (incomingMessage.descriptor.protocolPath === PermissionsProtocol.grantPath) {
424
- const grant = await PermissionGrant.parse(incomingMessage);
566
+ const grant = PermissionGrant.parse(incomingMessage);
425
567
  return grant.scope;
426
568
  } else {
427
569
  // if the record is not a grant or revocation, it must be a request
428
- const request = await PermissionRequest.parse(incomingMessage);
570
+ const request = PermissionRequest.parse(incomingMessage);
429
571
  return request.scope;
430
572
  }
431
573
  }
@@ -83,7 +83,8 @@ export class StateIndexLevel implements StateIndex {
83
83
  const globalSmt = await this.getGlobalTree(tenant);
84
84
  await globalSmt.insert(messageCid);
85
85
 
86
- // If the message is associated with a protocol, insert into the protocol-scoped tree
86
+ // Insert into the protocol-scoped tree if the message has a protocol (e.g. RecordsWrite).
87
+ // Non-record messages like ProtocolsConfigure do not have a protocol.
87
88
  const protocol = indexes.protocol as string | undefined;
88
89
  if (protocol !== undefined) {
89
90
  const protoSmt = await this.getProtocolTree(tenant, protocol);
@@ -104,7 +105,7 @@ export class StateIndexLevel implements StateIndex {
104
105
  // Delete from global tree
105
106
  await globalSmt.delete(messageCid);
106
107
 
107
- // Delete from protocol tree if applicable
108
+ // Delete from protocol tree if the message had a protocol
108
109
  if (indexes !== undefined) {
109
110
  const protocol = indexes.protocol as string | undefined;
110
111
  if (protocol !== undefined) {
@@ -204,11 +205,8 @@ export class StateIndexLevel implements StateIndex {
204
205
  */
205
206
  private async storeIndexes(tenant: string, messageCid: string, indexes: KeyValues): Promise<void> {
206
207
  const metaPartition = await this.getMetaPartition(tenant);
207
- // Only store the minimal set of indexes needed for deletion (protocol)
208
- const minimalIndexes: KeyValues = {};
209
- if (indexes.protocol !== undefined) {
210
- minimalIndexes.protocol = indexes.protocol;
211
- }
208
+ // Store the protocol index needed for deletion
209
+ const minimalIndexes: KeyValues = { protocol: indexes.protocol };
212
210
  await metaPartition.put(messageCid, JSON.stringify(minimalIndexes));
213
211
  }
214
212
 
@@ -1,4 +1,5 @@
1
1
  import type { ImportResult } from 'ipfs-unixfs-importer';
2
+ import type { LevelWrapper } from './level-wrapper.js';
2
3
  import type { DataStore, DataStoreGetResult, DataStorePutResult } from '../types/data-store.js';
3
4
 
4
5
  import { BlockstoreLevel } from './blockstore-level.js';
@@ -7,12 +8,20 @@ import { DataStream } from '../utils/data-stream.js';
7
8
  import { exporter } from 'ipfs-unixfs-exporter';
8
9
  import { importer } from 'ipfs-unixfs-importer';
9
10
 
11
+ const textEncoder = new TextEncoder();
12
+ const textDecoder = new TextDecoder();
13
+
10
14
  /**
11
15
  * A simple implementation of {@link DataStore} that works in both the browser and server-side.
12
16
  * Leverages LevelDB under the hood.
13
17
  *
14
- * It has the following structure (`+` represents an additional sublevel/partition):
15
- * 'data' + <tenant> + <recordId> + <dataCid> -> <data>
18
+ * It has the following sublevel structure (`+` represents an additional sublevel):
19
+ * 'refs' + <tenant> + <recordId> : key <dataCid> JSON { dataSize }
20
+ * 'blocks' + <dataCid> : key <blockCid> → block data (shared)
21
+ * 'refcounts' : key <dataCid> → JSON { count, dataSize }
22
+ *
23
+ * Identical data (same dataCid) is stored only once in the blocks sublevel.
24
+ * Multiple (tenant, recordId) pairs can reference the same blocks.
16
25
  */
17
26
  export class DataStoreLevel implements DataStore {
18
27
  config: DataStoreLevelConfig;
@@ -41,29 +50,74 @@ export class DataStoreLevel implements DataStore {
41
50
  }
42
51
 
43
52
  async put(tenant: string, recordId: string, dataCid: string, dataStream: ReadableStream<Uint8Array>): Promise<DataStorePutResult> {
44
- const blockstoreForData = await this.getBlockstoreForStoringData(tenant, recordId, dataCid);
53
+ // Check if this exact ref already exists (idempotent re-put).
54
+ const refsPartition = await this.getRefsPartition(tenant, recordId);
55
+ const existingRef = await refsPartition.get(dataCid);
56
+ if (existingRef) {
57
+ await dataStream.cancel();
58
+ const { dataSize } = JSON.parse(textDecoder.decode(existingRef)) as { dataSize: number };
59
+ return { dataSize };
60
+ }
45
61
 
46
- const asyncDataBlocks = importer([{ content: DataStream.asAsyncIterable(dataStream) }], blockstoreForData, { cidVersion: 1 });
62
+ // Check refcount if > 0, blocks already exist for this dataCid.
63
+ const refcountsPartition = await this.getRefcountsPartition();
64
+ const rawRefcount = await refcountsPartition.get(dataCid);
65
+ const refcountData = rawRefcount
66
+ ? JSON.parse(textDecoder.decode(rawRefcount)) as { count: number; dataSize: number }
67
+ : { count: 0, dataSize: 0 };
68
+
69
+ let dataSize: number;
70
+
71
+ if (refcountData.count > 0) {
72
+ // Blocks already exist — skip import.
73
+ await dataStream.cancel();
74
+ dataSize = refcountData.dataSize;
75
+ } else {
76
+ // First write — import blocks into the shared blocks partition.
77
+ const blocksPartition = await this.getBlocksPartition(dataCid);
78
+ const asyncDataBlocks = importer(
79
+ [{ content: DataStream.asAsyncIterable(dataStream) }],
80
+ blocksPartition,
81
+ { cidVersion: 1 }
82
+ );
83
+
84
+ // NOTE: the last block contains the root CID as well as info to derive the data size.
85
+ let dataDagRoot!: ImportResult;
86
+ for await (dataDagRoot of asyncDataBlocks) { ; }
87
+ dataSize = Number(dataDagRoot.unixfs?.fileSize() ?? dataDagRoot.size);
88
+ }
47
89
 
48
- // NOTE: the last block contains the root CID as well as info to derive the data size
49
- let dataDagRoot!: ImportResult;
50
- for await (dataDagRoot of asyncDataBlocks) { ; }
90
+ // Write ref entry.
91
+ await refsPartition.put(dataCid, textEncoder.encode(JSON.stringify({ dataSize })));
51
92
 
52
- return {
53
- dataSize: Number(dataDagRoot.unixfs?.fileSize() ?? dataDagRoot.size)
54
- };
93
+ // Increment refcount.
94
+ await refcountsPartition.put(dataCid, textEncoder.encode(JSON.stringify({
95
+ count: refcountData.count + 1,
96
+ dataSize,
97
+ })));
98
+
99
+ return { dataSize };
55
100
  }
56
101
 
57
102
  public async get(tenant: string, recordId: string, dataCid: string): Promise<DataStoreGetResult | undefined> {
58
- const blockstoreForData = await this.getBlockstoreForStoringData(tenant, recordId, dataCid);
103
+ // Check ref exists.
104
+ const refsPartition = await this.getRefsPartition(tenant, recordId);
105
+ const rawRef = await refsPartition.get(dataCid);
106
+ if (!rawRef) {
107
+ return undefined;
108
+ }
109
+
110
+ const { dataSize } = JSON.parse(textDecoder.decode(rawRef)) as { dataSize: number };
59
111
 
60
- const exists = await blockstoreForData.has(dataCid);
112
+ // Export from the shared blocks partition.
113
+ const blocksPartition = await this.getBlocksPartition(dataCid);
114
+ const exists = await blocksPartition.has(dataCid);
61
115
  if (!exists) {
62
116
  return undefined;
63
117
  }
64
118
 
65
- // data is chunked into dag-pb unixfs blocks. re-inflate the chunks.
66
- const dataDagRoot = await exporter(dataCid, blockstoreForData);
119
+ // Data is chunked into DAG-PB UnixFS blocks. Re-inflate the chunks.
120
+ const dataDagRoot = await exporter(dataCid, blocksPartition);
67
121
  const contentIterator = dataDagRoot.content();
68
122
 
69
123
  const dataStream = new ReadableStream<Uint8Array>({
@@ -77,21 +131,42 @@ export class DataStoreLevel implements DataStore {
77
131
  }
78
132
  });
79
133
 
80
- let dataSize = dataDagRoot.size;
134
+ return { dataSize, dataStream };
135
+ }
81
136
 
82
- if (dataDagRoot.type === 'file' || dataDagRoot.type === 'directory') {
83
- dataSize = dataDagRoot.unixfs.fileSize();
137
+ public async delete(tenant: string, recordId: string, dataCid: string): Promise<void> {
138
+ // Check ref exists.
139
+ const refsPartition = await this.getRefsPartition(tenant, recordId);
140
+ const rawRef = await refsPartition.get(dataCid);
141
+ if (!rawRef) {
142
+ return;
84
143
  }
85
144
 
86
- return {
87
- dataSize: Number(dataSize),
88
- dataStream,
89
- };
90
- }
145
+ // Remove ref.
146
+ await refsPartition.delete(dataCid);
91
147
 
92
- public async delete(tenant: string, recordId: string, dataCid: string): Promise<void> {
93
- const blockstoreForData = await this.getBlockstoreForStoringData(tenant, recordId, dataCid);
94
- await blockstoreForData.clear();
148
+ // Decrement refcount and GC blocks if this was the last ref.
149
+ const refcountsPartition = await this.getRefcountsPartition();
150
+ const rawRefcount = await refcountsPartition.get(dataCid);
151
+ if (!rawRefcount) {
152
+ return;
153
+ }
154
+
155
+ const refcountData = JSON.parse(textDecoder.decode(rawRefcount)) as { count: number; dataSize: number };
156
+ const newCount = refcountData.count - 1;
157
+
158
+ if (newCount <= 0) {
159
+ // Last reference removed — garbage-collect blocks and refcount entry.
160
+ const blocksPartition = await this.getBlocksPartition(dataCid);
161
+ await blocksPartition.clear();
162
+ await refcountsPartition.delete(dataCid);
163
+ } else {
164
+ // Other references remain — update the count.
165
+ await refcountsPartition.put(dataCid, textEncoder.encode(JSON.stringify({
166
+ count : newCount,
167
+ dataSize : refcountData.dataSize,
168
+ })));
169
+ }
95
170
  }
96
171
 
97
172
  /**
@@ -102,15 +177,30 @@ export class DataStoreLevel implements DataStore {
102
177
  }
103
178
 
104
179
  /**
105
- * Gets the blockstore used for storing data for the given `tenant -> `recordId` -> `dataCid`.
180
+ * Gets the refs sublevel for the given tenant and recordId.
181
+ * Caller uses `dataCid` as the key within the returned partition.
182
+ */
183
+ private async getRefsPartition(tenant: string, recordId: string): Promise<LevelWrapper<Uint8Array>> {
184
+ const refsRoot = await this.blockstore.db.partition('refs');
185
+ const tenantPartition = await refsRoot.partition(tenant);
186
+ return tenantPartition.partition(recordId);
187
+ }
188
+
189
+ /**
190
+ * Gets the shared blocks sublevel for the given dataCid.
191
+ * Used as a Blockstore for ipfs-unixfs-importer/exporter.
192
+ */
193
+ private async getBlocksPartition(dataCid: string): Promise<BlockstoreLevel> {
194
+ const blocksRoot = await this.blockstore.partition('blocks');
195
+ return blocksRoot.partition(dataCid);
196
+ }
197
+
198
+ /**
199
+ * Gets the refcounts sublevel.
200
+ * Key: `dataCid` → JSON `{ count, dataSize }`.
106
201
  */
107
- private async getBlockstoreForStoringData(tenant: string, recordId: string, dataCid: string): Promise<BlockstoreLevel> {
108
- const dataPartitionName = 'data';
109
- const blockstoreForData = await this.blockstore.partition(dataPartitionName);
110
- const blockstoreOfGivenTenant = await blockstoreForData.partition(tenant);
111
- const blockstoreOfGivenRecordId = await blockstoreOfGivenTenant.partition(recordId);
112
- const blockstoreOfGivenDataCidOfRecordId = await blockstoreOfGivenRecordId.partition(dataCid);
113
- return blockstoreOfGivenDataCidOfRecordId;
202
+ private async getRefcountsPartition(): Promise<LevelWrapper<Uint8Array>> {
203
+ return this.blockstore.db.partition('refcounts');
114
204
  }
115
205
 
116
206
  }
@@ -118,4 +208,4 @@ export class DataStoreLevel implements DataStore {
118
208
  export type DataStoreLevelConfig = {
119
209
  blockstoreLocation?: string,
120
210
  createLevelDatabase?: typeof createLevelDatabase,
121
- };
211
+ };
@@ -477,9 +477,18 @@ export class IndexLevel {
477
477
  }
478
478
 
479
479
  try {
480
- await Promise.all(filters.map(filter => {
481
- return this.executeSingleFilterQuery(tenant, filter, sortProperty, matches, options );
482
- }));
480
+ // Execute filters sequentially rather than with Promise.all.
481
+ // Firefox's IndexedDB implementation has two related async issues that cause flaky failures:
482
+ // 1. Concurrent cursors/transactions can silently miss recently-committed data
483
+ // 2. A read transaction opened immediately after a write transaction's oncomplete event
484
+ // can fail to see the written data (write-read race)
485
+ // Serializing eliminates both races. Performance impact is negligible: only 3 call sites
486
+ // (non-owner RecordsQuery/Subscribe/Count) ever pass multiple filters, and each filter
487
+ // reduces to a single bounded LevelDB range scan completing in single-digit ms.
488
+ // See: https://github.com/enboxorg/enbox/issues/264
489
+ for (const filter of filters) {
490
+ await this.executeSingleFilterQuery(tenant, filter, sortProperty, matches, options);
491
+ }
483
492
  } catch (error) {
484
493
  if ((error as DwnError).code === DwnErrorCode.IndexInvalidSortPropertyInMemory) {
485
494
  // return empty results if the sort property is invalid.
@@ -501,6 +510,10 @@ export class IndexLevel {
501
510
 
502
511
  /**
503
512
  * Execute a filtered query against a single filter and return all results.
513
+ *
514
+ * Sub-queries (exact match, range, OneOf) are executed sequentially to avoid opening
515
+ * multiple concurrent IndexedDB cursors. Firefox's IDB implementation intermittently
516
+ * drops results when cursors overlap — see https://github.com/enboxorg/enbox/issues/264
504
517
  */
505
518
  private async executeSingleFilterQuery(
506
519
  tenant: string,
@@ -510,13 +523,30 @@ export class IndexLevel {
510
523
  levelOptions?: IndexLevelOptions
511
524
  ): Promise<void> {
512
525
 
513
- // Note: We have an array of Promises in order to support OR (anyOf) matches when given a list of accepted values for a property
514
- const filterPromises: Promise<IndexedItem[]>[] = [];
526
+ // Collects results from each sub-query sequentially to avoid concurrent IndexedDB cursor races in Firefox.
527
+ const processResults = (indexItems: IndexedItem[]): void => {
528
+ for (const indexedItem of indexItems) {
529
+ // short circuit: if a data is already included to the final matched key set (by a different `Filter`),
530
+ // no need to evaluate if the data satisfies this current filter being evaluated
531
+ // otherwise check that the item is a match.
532
+ if (matches.has(indexedItem.messageCid) || !FilterUtility.matchFilter(indexedItem.indexes, filter)) {
533
+ continue;
534
+ }
535
+
536
+ // ensure that each matched item has the sortProperty, otherwise fail the entire query.
537
+ if (indexedItem.indexes[sortProperty] === undefined) {
538
+ throw new DwnError(DwnErrorCode.IndexInvalidSortPropertyInMemory, `invalid sort property ${sortProperty}`);
539
+ }
540
+
541
+ matches.set(indexedItem.messageCid, indexedItem);
542
+ }
543
+ };
515
544
 
516
545
  // If the filter is empty, then we just iterate over one of the indexes that contains all the records and return all items.
517
546
  if (isEmptyObject(filter)) {
518
- const getAllItemsPromise = this.getAllItems(tenant, sortProperty);
519
- filterPromises.push(getAllItemsPromise);
547
+ const allItems = await this.getAllItems(tenant, sortProperty);
548
+ processResults(allItems);
549
+ return;
520
550
  }
521
551
 
522
552
  // else the filter is not empty
@@ -526,40 +556,19 @@ export class IndexLevel {
526
556
  // We will find the union of these many individual queries later.
527
557
  if (FilterUtility.isEqualFilter(propertyFilter)) {
528
558
  // propertyFilter is an EqualFilter, meaning it is a non-object primitive type
529
- const exactMatchesPromise = this.filterExactMatches(tenant, propertyName, propertyFilter, levelOptions);
530
- filterPromises.push(exactMatchesPromise);
559
+ const exactMatches = await this.filterExactMatches(tenant, propertyName, propertyFilter, levelOptions);
560
+ processResults(exactMatches);
531
561
  } else if (FilterUtility.isOneOfFilter(propertyFilter)) {
532
562
  // `propertyFilter` is a OneOfFilter
533
- // Support OR matches by querying for each values separately, then adding them to the promises array.
563
+ // Support OR matches by querying for each value separately and sequentially.
534
564
  for (const propertyValue of new Set(propertyFilter)) {
535
- const exactMatchesPromise = this.filterExactMatches(tenant, propertyName, propertyValue, levelOptions);
536
- filterPromises.push(exactMatchesPromise);
565
+ const exactMatches = await this.filterExactMatches(tenant, propertyName, propertyValue, levelOptions);
566
+ processResults(exactMatches);
537
567
  }
538
568
  } else if (FilterUtility.isRangeFilter(propertyFilter)) {
539
569
  // `propertyFilter` is a `RangeFilter`
540
- const rangeMatchesPromise = this.filterRangeMatches(tenant, propertyName, propertyFilter, levelOptions);
541
- filterPromises.push(rangeMatchesPromise);
542
- }
543
- }
544
-
545
- // acting as an OR match for the property, any of the promises returning a match will be treated as a property match
546
- for (const promise of filterPromises) {
547
- const indexItems = await promise;
548
- // reminder: the promise returns a list of IndexedItem satisfying a particular property match
549
- for (const indexedItem of indexItems) {
550
- // short circuit: if a data is already included to the final matched key set (by a different `Filter`),
551
- // no need to evaluate if the data satisfies this current filter being evaluated
552
- // otherwise check that the item is a match.
553
- if (matches.has(indexedItem.messageCid) || !FilterUtility.matchFilter(indexedItem.indexes, filter)) {
554
- continue;
555
- }
556
-
557
- // ensure that each matched item has the sortProperty, otherwise fail the entire query.
558
- if (indexedItem.indexes[sortProperty] === undefined) {
559
- throw new DwnError(DwnErrorCode.IndexInvalidSortPropertyInMemory, `invalid sort property ${sortProperty}`);
560
- }
561
-
562
- matches.set(indexedItem.messageCid, indexedItem);
570
+ const rangeMatches = await this.filterRangeMatches(tenant, propertyName, propertyFilter, levelOptions);
571
+ processResults(rangeMatches);
563
572
  }
564
573
  }
565
574
  }