@enbox/dwn-sdk-js 0.0.5 → 0.0.7

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 (363) 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 +1 -2
  4. package/dist/esm/generated/precompiled-validators.js.map +1 -1
  5. package/dist/esm/src/core/abstract-message.js +4 -0
  6. package/dist/esm/src/core/abstract-message.js.map +1 -1
  7. package/dist/esm/src/core/auth.js +22 -33
  8. package/dist/esm/src/core/auth.js.map +1 -1
  9. package/dist/esm/src/core/dwn-constant.js +7 -7
  10. package/dist/esm/src/core/dwn-constant.js.map +1 -1
  11. package/dist/esm/src/core/dwn-error.js +1 -0
  12. package/dist/esm/src/core/dwn-error.js.map +1 -1
  13. package/dist/esm/src/core/grant-authorization.js +37 -52
  14. package/dist/esm/src/core/grant-authorization.js.map +1 -1
  15. package/dist/esm/src/core/message.js +85 -116
  16. package/dist/esm/src/core/message.js.map +1 -1
  17. package/dist/esm/src/core/messages-grant-authorization.js +63 -78
  18. package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
  19. package/dist/esm/src/core/protocol-authorization-action.js +266 -0
  20. package/dist/esm/src/core/protocol-authorization-action.js.map +1 -0
  21. package/dist/esm/src/core/protocol-authorization-validation.js +254 -0
  22. package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -0
  23. package/dist/esm/src/core/protocol-authorization.js +122 -740
  24. package/dist/esm/src/core/protocol-authorization.js.map +1 -1
  25. package/dist/esm/src/core/protocols-grant-authorization.js +24 -38
  26. package/dist/esm/src/core/protocols-grant-authorization.js.map +1 -1
  27. package/dist/esm/src/core/record-chain.js +64 -0
  28. package/dist/esm/src/core/record-chain.js.map +1 -0
  29. package/dist/esm/src/core/records-grant-authorization.js +55 -72
  30. package/dist/esm/src/core/records-grant-authorization.js.map +1 -1
  31. package/dist/esm/src/core/resumable-task-manager.js +50 -65
  32. package/dist/esm/src/core/resumable-task-manager.js.map +1 -1
  33. package/dist/esm/src/core/tenant-gate.js +2 -13
  34. package/dist/esm/src/core/tenant-gate.js.map +1 -1
  35. package/dist/esm/src/dwn.js +69 -86
  36. package/dist/esm/src/dwn.js.map +1 -1
  37. package/dist/esm/src/event-stream/event-emitter-stream.js +17 -31
  38. package/dist/esm/src/event-stream/event-emitter-stream.js.map +1 -1
  39. package/dist/esm/src/handlers/messages-read.js +67 -77
  40. package/dist/esm/src/handlers/messages-read.js.map +1 -1
  41. package/dist/esm/src/handlers/messages-subscribe.js +51 -61
  42. package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
  43. package/dist/esm/src/handlers/messages-sync.js +75 -85
  44. package/dist/esm/src/handlers/messages-sync.js.map +1 -1
  45. package/dist/esm/src/handlers/protocols-configure.js +135 -155
  46. package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
  47. package/dist/esm/src/handlers/protocols-query.js +52 -51
  48. package/dist/esm/src/handlers/protocols-query.js.map +1 -1
  49. package/dist/esm/src/handlers/records-count.js +96 -82
  50. package/dist/esm/src/handlers/records-count.js.map +1 -1
  51. package/dist/esm/src/handlers/records-delete.js +78 -88
  52. package/dist/esm/src/handlers/records-delete.js.map +1 -1
  53. package/dist/esm/src/handlers/records-query.js +116 -101
  54. package/dist/esm/src/handlers/records-query.js.map +1 -1
  55. package/dist/esm/src/handlers/records-read.js +124 -131
  56. package/dist/esm/src/handlers/records-read.js.map +1 -1
  57. package/dist/esm/src/handlers/records-subscribe.js +150 -103
  58. package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
  59. package/dist/esm/src/handlers/records-write.js +250 -259
  60. package/dist/esm/src/handlers/records-write.js.map +1 -1
  61. package/dist/esm/src/interfaces/messages-read.js +24 -32
  62. package/dist/esm/src/interfaces/messages-read.js.map +1 -1
  63. package/dist/esm/src/interfaces/messages-subscribe.js +27 -41
  64. package/dist/esm/src/interfaces/messages-subscribe.js.map +1 -1
  65. package/dist/esm/src/interfaces/messages-sync.js +26 -40
  66. package/dist/esm/src/interfaces/messages-sync.js.map +1 -1
  67. package/dist/esm/src/interfaces/protocols-configure.js +63 -63
  68. package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
  69. package/dist/esm/src/interfaces/protocols-query.js +55 -68
  70. package/dist/esm/src/interfaces/protocols-query.js.map +1 -1
  71. package/dist/esm/src/interfaces/records-count.js +50 -66
  72. package/dist/esm/src/interfaces/records-count.js.map +1 -1
  73. package/dist/esm/src/interfaces/records-delete.js +45 -55
  74. package/dist/esm/src/interfaces/records-delete.js.map +1 -1
  75. package/dist/esm/src/interfaces/records-query.js +60 -76
  76. package/dist/esm/src/interfaces/records-query.js.map +1 -1
  77. package/dist/esm/src/interfaces/records-read.js +51 -67
  78. package/dist/esm/src/interfaces/records-read.js.map +1 -1
  79. package/dist/esm/src/interfaces/records-subscribe.js +52 -68
  80. package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
  81. package/dist/esm/src/interfaces/records-write-query.js +102 -0
  82. package/dist/esm/src/interfaces/records-write-query.js.map +1 -0
  83. package/dist/esm/src/interfaces/records-write-signing.js +92 -0
  84. package/dist/esm/src/interfaces/records-write-signing.js.map +1 -0
  85. package/dist/esm/src/interfaces/records-write.js +407 -602
  86. package/dist/esm/src/interfaces/records-write.js.map +1 -1
  87. package/dist/esm/src/jose/algorithms/signing/ed25519.js +10 -19
  88. package/dist/esm/src/jose/algorithms/signing/ed25519.js.map +1 -1
  89. package/dist/esm/src/jose/jws/general/builder.js +23 -35
  90. package/dist/esm/src/jose/jws/general/builder.js.map +1 -1
  91. package/dist/esm/src/jose/jws/general/verifier.js +56 -69
  92. package/dist/esm/src/jose/jws/general/verifier.js.map +1 -1
  93. package/dist/esm/src/protocols/permission-grant.js +44 -15
  94. package/dist/esm/src/protocols/permission-grant.js.map +1 -1
  95. package/dist/esm/src/protocols/permission-request.js +29 -15
  96. package/dist/esm/src/protocols/permission-request.js.map +1 -1
  97. package/dist/esm/src/protocols/permissions.js +216 -226
  98. package/dist/esm/src/protocols/permissions.js.map +1 -1
  99. package/dist/esm/src/smt/smt-store-level.js +42 -64
  100. package/dist/esm/src/smt/smt-store-level.js.map +1 -1
  101. package/dist/esm/src/smt/smt-store-memory.js +19 -45
  102. package/dist/esm/src/smt/smt-store-memory.js.map +1 -1
  103. package/dist/esm/src/smt/smt-utils.js +28 -45
  104. package/dist/esm/src/smt/smt-utils.js.map +1 -1
  105. package/dist/esm/src/smt/sparse-merkle-tree.js +426 -471
  106. package/dist/esm/src/smt/sparse-merkle-tree.js.map +1 -1
  107. package/dist/esm/src/state-index/state-index-level.js +115 -150
  108. package/dist/esm/src/state-index/state-index-level.js.map +1 -1
  109. package/dist/esm/src/store/blockstore-level.js +54 -156
  110. package/dist/esm/src/store/blockstore-level.js.map +1 -1
  111. package/dist/esm/src/store/blockstore-mock.js +48 -153
  112. package/dist/esm/src/store/blockstore-mock.js.map +1 -1
  113. package/dist/esm/src/store/data-store-level.js +59 -99
  114. package/dist/esm/src/store/data-store-level.js.map +1 -1
  115. package/dist/esm/src/store/index-level-compound.js +246 -0
  116. package/dist/esm/src/store/index-level-compound.js.map +1 -0
  117. package/dist/esm/src/store/index-level.js +295 -713
  118. package/dist/esm/src/store/index-level.js.map +1 -1
  119. package/dist/esm/src/store/level-wrapper.js +143 -244
  120. package/dist/esm/src/store/level-wrapper.js.map +1 -1
  121. package/dist/esm/src/store/message-store-level.js +71 -94
  122. package/dist/esm/src/store/message-store-level.js.map +1 -1
  123. package/dist/esm/src/store/resumable-task-store-level.js +62 -101
  124. package/dist/esm/src/store/resumable-task-store-level.js.map +1 -1
  125. package/dist/esm/src/store/storage-controller.js +129 -144
  126. package/dist/esm/src/store/storage-controller.js.map +1 -1
  127. package/dist/esm/src/utils/abort.js +8 -19
  128. package/dist/esm/src/utils/abort.js.map +1 -1
  129. package/dist/esm/src/utils/array.js +15 -49
  130. package/dist/esm/src/utils/array.js.map +1 -1
  131. package/dist/esm/src/utils/cid.js +29 -77
  132. package/dist/esm/src/utils/cid.js.map +1 -1
  133. package/dist/esm/src/utils/data-stream.js +37 -65
  134. package/dist/esm/src/utils/data-stream.js.map +1 -1
  135. package/dist/esm/src/utils/encryption.js +136 -162
  136. package/dist/esm/src/utils/encryption.js.map +1 -1
  137. package/dist/esm/src/utils/filter.js +1 -12
  138. package/dist/esm/src/utils/filter.js.map +1 -1
  139. package/dist/esm/src/utils/hd-key.js +45 -63
  140. package/dist/esm/src/utils/hd-key.js.map +1 -1
  141. package/dist/esm/src/utils/jws.js +9 -20
  142. package/dist/esm/src/utils/jws.js.map +1 -1
  143. package/dist/esm/src/utils/memory-cache.js +12 -23
  144. package/dist/esm/src/utils/memory-cache.js.map +1 -1
  145. package/dist/esm/src/utils/messages.js +9 -3
  146. package/dist/esm/src/utils/messages.js.map +1 -1
  147. package/dist/esm/src/utils/private-key-signer.js +9 -17
  148. package/dist/esm/src/utils/private-key-signer.js.map +1 -1
  149. package/dist/esm/src/utils/protocols.js +62 -70
  150. package/dist/esm/src/utils/protocols.js.map +1 -1
  151. package/dist/esm/src/utils/records.js +108 -140
  152. package/dist/esm/src/utils/records.js.map +1 -1
  153. package/dist/esm/src/utils/secp256k1.js +60 -96
  154. package/dist/esm/src/utils/secp256k1.js.map +1 -1
  155. package/dist/esm/src/utils/secp256r1.js +54 -71
  156. package/dist/esm/src/utils/secp256r1.js.map +1 -1
  157. package/dist/esm/src/utils/time.js +5 -18
  158. package/dist/esm/src/utils/time.js.map +1 -1
  159. package/dist/esm/src/utils/url.js +3 -3
  160. package/dist/esm/src/utils/url.js.map +1 -1
  161. package/dist/esm/tests/core/auth.spec.js +3 -12
  162. package/dist/esm/tests/core/auth.spec.js.map +1 -1
  163. package/dist/esm/tests/core/message.spec.js +50 -59
  164. package/dist/esm/tests/core/message.spec.js.map +1 -1
  165. package/dist/esm/tests/core/protocol-authorization.spec.js +9 -18
  166. package/dist/esm/tests/core/protocol-authorization.spec.js.map +1 -1
  167. package/dist/esm/tests/dwn.spec.js +45 -58
  168. package/dist/esm/tests/dwn.spec.js.map +1 -1
  169. package/dist/esm/tests/event-stream/event-emitter-stream.spec.js +24 -33
  170. package/dist/esm/tests/event-stream/event-emitter-stream.spec.js.map +1 -1
  171. package/dist/esm/tests/event-stream/event-stream.spec.js +46 -55
  172. package/dist/esm/tests/event-stream/event-stream.spec.js.map +1 -1
  173. package/dist/esm/tests/features/author-delegated-grant.spec.js +326 -343
  174. package/dist/esm/tests/features/author-delegated-grant.spec.js.map +1 -1
  175. package/dist/esm/tests/features/owner-delegated-grant.spec.js +153 -169
  176. package/dist/esm/tests/features/owner-delegated-grant.spec.js.map +1 -1
  177. package/dist/esm/tests/features/owner-signature.spec.js +67 -78
  178. package/dist/esm/tests/features/owner-signature.spec.js.map +1 -1
  179. package/dist/esm/tests/features/permissions.spec.js +446 -181
  180. package/dist/esm/tests/features/permissions.spec.js.map +1 -1
  181. package/dist/esm/tests/features/protocol-composition.spec.js +346 -356
  182. package/dist/esm/tests/features/protocol-composition.spec.js.map +1 -1
  183. package/dist/esm/tests/features/protocol-create-action.spec.js +42 -51
  184. package/dist/esm/tests/features/protocol-create-action.spec.js.map +1 -1
  185. package/dist/esm/tests/features/protocol-delete-action.spec.js +94 -103
  186. package/dist/esm/tests/features/protocol-delete-action.spec.js.map +1 -1
  187. package/dist/esm/tests/features/protocol-update-action.spec.js +105 -114
  188. package/dist/esm/tests/features/protocol-update-action.spec.js.map +1 -1
  189. package/dist/esm/tests/features/records-prune.spec.js +175 -191
  190. package/dist/esm/tests/features/records-prune.spec.js.map +1 -1
  191. package/dist/esm/tests/features/records-tags.spec.js +441 -460
  192. package/dist/esm/tests/features/records-tags.spec.js.map +1 -1
  193. package/dist/esm/tests/features/resumable-tasks.spec.js +82 -91
  194. package/dist/esm/tests/features/resumable-tasks.spec.js.map +1 -1
  195. package/dist/esm/tests/handlers/messages-read.spec.js +206 -207
  196. package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
  197. package/dist/esm/tests/handlers/messages-subscribe.spec.js +145 -154
  198. package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
  199. package/dist/esm/tests/handlers/messages-sync.spec.js +174 -183
  200. package/dist/esm/tests/handlers/messages-sync.spec.js.map +1 -1
  201. package/dist/esm/tests/handlers/protocols-configure.spec.js +244 -238
  202. package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
  203. package/dist/esm/tests/handlers/protocols-query.spec.js +156 -169
  204. package/dist/esm/tests/handlers/protocols-query.spec.js.map +1 -1
  205. package/dist/esm/tests/handlers/records-count.spec.js +93 -102
  206. package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
  207. package/dist/esm/tests/handlers/records-delete.spec.js +252 -264
  208. package/dist/esm/tests/handlers/records-delete.spec.js.map +1 -1
  209. package/dist/esm/tests/handlers/records-query.spec.js +917 -988
  210. package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
  211. package/dist/esm/tests/handlers/records-read.spec.js +553 -568
  212. package/dist/esm/tests/handlers/records-read.spec.js.map +1 -1
  213. package/dist/esm/tests/handlers/records-subscribe.spec.js +269 -278
  214. package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
  215. package/dist/esm/tests/handlers/records-write.spec.js +1057 -1082
  216. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  217. package/dist/esm/tests/interfaces/messages-get.spec.js +39 -48
  218. package/dist/esm/tests/interfaces/messages-get.spec.js.map +1 -1
  219. package/dist/esm/tests/interfaces/messages-subscribe.spec.js +4 -13
  220. package/dist/esm/tests/interfaces/messages-subscribe.spec.js.map +1 -1
  221. package/dist/esm/tests/interfaces/protocols-configure.spec.js +212 -88
  222. package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
  223. package/dist/esm/tests/interfaces/protocols-query.spec.js +8 -17
  224. package/dist/esm/tests/interfaces/protocols-query.spec.js.map +1 -1
  225. package/dist/esm/tests/interfaces/records-delete.spec.js +8 -17
  226. package/dist/esm/tests/interfaces/records-delete.spec.js.map +1 -1
  227. package/dist/esm/tests/interfaces/records-query.spec.js +20 -29
  228. package/dist/esm/tests/interfaces/records-query.spec.js.map +1 -1
  229. package/dist/esm/tests/interfaces/records-read.spec.js +42 -51
  230. package/dist/esm/tests/interfaces/records-read.spec.js.map +1 -1
  231. package/dist/esm/tests/interfaces/records-subscribe.spec.js +16 -25
  232. package/dist/esm/tests/interfaces/records-subscribe.spec.js.map +1 -1
  233. package/dist/esm/tests/interfaces/records-write.spec.js +152 -165
  234. package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
  235. package/dist/esm/tests/jose/jws/general.spec.js +36 -45
  236. package/dist/esm/tests/jose/jws/general.spec.js.map +1 -1
  237. package/dist/esm/tests/protocols/permission-grant.spec.js +44 -50
  238. package/dist/esm/tests/protocols/permission-grant.spec.js.map +1 -1
  239. package/dist/esm/tests/protocols/permission-request.spec.js +23 -32
  240. package/dist/esm/tests/protocols/permission-request.spec.js.map +1 -1
  241. package/dist/esm/tests/protocols/permissions.spec.js +49 -55
  242. package/dist/esm/tests/protocols/permissions.spec.js.map +1 -1
  243. package/dist/esm/tests/scenarios/aggregator.spec.js +124 -135
  244. package/dist/esm/tests/scenarios/aggregator.spec.js.map +1 -1
  245. package/dist/esm/tests/scenarios/deleted-record.spec.js +23 -32
  246. package/dist/esm/tests/scenarios/deleted-record.spec.js.map +1 -1
  247. package/dist/esm/tests/scenarios/end-to-end-tests.spec.js +52 -61
  248. package/dist/esm/tests/scenarios/end-to-end-tests.spec.js.map +1 -1
  249. package/dist/esm/tests/scenarios/nested-roles.spec.js +63 -73
  250. package/dist/esm/tests/scenarios/nested-roles.spec.js.map +1 -1
  251. package/dist/esm/tests/scenarios/subscriptions.spec.js +377 -333
  252. package/dist/esm/tests/scenarios/subscriptions.spec.js.map +1 -1
  253. package/dist/esm/tests/smt/smt-store-level.spec.js +76 -87
  254. package/dist/esm/tests/smt/smt-store-level.spec.js.map +1 -1
  255. package/dist/esm/tests/smt/sparse-merkle-tree.spec.js +344 -353
  256. package/dist/esm/tests/smt/sparse-merkle-tree.spec.js.map +1 -1
  257. package/dist/esm/tests/state-index/state-index-level.spec.js +117 -126
  258. package/dist/esm/tests/state-index/state-index-level.spec.js.map +1 -1
  259. package/dist/esm/tests/store/blockstore-level.spec.js +44 -99
  260. package/dist/esm/tests/store/blockstore-level.spec.js.map +1 -1
  261. package/dist/esm/tests/store/blockstore-mock.spec.js +40 -120
  262. package/dist/esm/tests/store/blockstore-mock.spec.js.map +1 -1
  263. package/dist/esm/tests/store/data-store-level.spec.js +86 -95
  264. package/dist/esm/tests/store/data-store-level.spec.js.map +1 -1
  265. package/dist/esm/tests/store/index-level.spec.js +404 -414
  266. package/dist/esm/tests/store/index-level.spec.js.map +1 -1
  267. package/dist/esm/tests/store/message-store-level.spec.js +13 -22
  268. package/dist/esm/tests/store/message-store-level.spec.js.map +1 -1
  269. package/dist/esm/tests/store/message-store.spec.js +229 -238
  270. package/dist/esm/tests/store/message-store.spec.js.map +1 -1
  271. package/dist/esm/tests/test-event-stream.js +3 -3
  272. package/dist/esm/tests/test-event-stream.js.map +1 -1
  273. package/dist/esm/tests/test-stores.js +16 -13
  274. package/dist/esm/tests/test-stores.js.map +1 -1
  275. package/dist/esm/tests/test-suite.js +2 -11
  276. package/dist/esm/tests/test-suite.js.map +1 -1
  277. package/dist/esm/tests/utils/cid.spec.js +24 -33
  278. package/dist/esm/tests/utils/cid.spec.js.map +1 -1
  279. package/dist/esm/tests/utils/data-stream.spec.js +48 -57
  280. package/dist/esm/tests/utils/data-stream.spec.js.map +1 -1
  281. package/dist/esm/tests/utils/encryption-callbacks.spec.js +45 -54
  282. package/dist/esm/tests/utils/encryption-callbacks.spec.js.map +1 -1
  283. package/dist/esm/tests/utils/encryption.spec.js +291 -44
  284. package/dist/esm/tests/utils/encryption.spec.js.map +1 -1
  285. package/dist/esm/tests/utils/filters.spec.js +46 -55
  286. package/dist/esm/tests/utils/filters.spec.js.map +1 -1
  287. package/dist/esm/tests/utils/hd-key.spec.js +10 -19
  288. package/dist/esm/tests/utils/hd-key.spec.js.map +1 -1
  289. package/dist/esm/tests/utils/jws.spec.js +3 -12
  290. package/dist/esm/tests/utils/jws.spec.js.map +1 -1
  291. package/dist/esm/tests/utils/memory-cache.spec.js +9 -18
  292. package/dist/esm/tests/utils/memory-cache.spec.js.map +1 -1
  293. package/dist/esm/tests/utils/messages.spec.js +6 -15
  294. package/dist/esm/tests/utils/messages.spec.js.map +1 -1
  295. package/dist/esm/tests/utils/poller.js +22 -33
  296. package/dist/esm/tests/utils/poller.js.map +1 -1
  297. package/dist/esm/tests/utils/private-key-signer.spec.js +15 -24
  298. package/dist/esm/tests/utils/private-key-signer.spec.js.map +1 -1
  299. package/dist/esm/tests/utils/records.spec.js +10 -19
  300. package/dist/esm/tests/utils/records.spec.js.map +1 -1
  301. package/dist/esm/tests/utils/secp256k1.spec.js +16 -25
  302. package/dist/esm/tests/utils/secp256k1.spec.js.map +1 -1
  303. package/dist/esm/tests/utils/secp256r1.spec.js +18 -27
  304. package/dist/esm/tests/utils/secp256r1.spec.js.map +1 -1
  305. package/dist/esm/tests/utils/test-data-generator.js +414 -468
  306. package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
  307. package/dist/esm/tests/validation/json-schemas/definitions.spec.js +2 -11
  308. package/dist/esm/tests/validation/json-schemas/definitions.spec.js.map +1 -1
  309. package/dist/esm/tests/validation/json-schemas/jwk/general-jwk.spec.js +4 -13
  310. package/dist/esm/tests/validation/json-schemas/jwk/general-jwk.spec.js.map +1 -1
  311. package/dist/esm/tests/validation/json-schemas/jwk/public-jwk.spec.js +8 -17
  312. package/dist/esm/tests/validation/json-schemas/jwk/public-jwk.spec.js.map +1 -1
  313. package/dist/esm/tests/validation/json-schemas/jwk-verification-method.spec.js +3 -12
  314. package/dist/esm/tests/validation/json-schemas/jwk-verification-method.spec.js.map +1 -1
  315. package/dist/esm/tests/validation/json-schemas/protocols/protocols-configure.spec.js +4 -13
  316. package/dist/esm/tests/validation/json-schemas/protocols/protocols-configure.spec.js.map +1 -1
  317. package/dist/esm/tests/validation/json-schemas/records/records-query.spec.js +2 -11
  318. package/dist/esm/tests/validation/json-schemas/records/records-query.spec.js.map +1 -1
  319. package/dist/esm/tests/validation/json-schemas/records/records-read.spec.js +2 -11
  320. package/dist/esm/tests/validation/json-schemas/records/records-read.spec.js.map +1 -1
  321. package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js +7 -16
  322. package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js.map +1 -1
  323. package/dist/types/src/core/protocol-authorization-action.d.ts +42 -0
  324. package/dist/types/src/core/protocol-authorization-action.d.ts.map +1 -0
  325. package/dist/types/src/core/protocol-authorization-validation.d.ts +60 -0
  326. package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -0
  327. package/dist/types/src/core/protocol-authorization.d.ts +10 -100
  328. package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
  329. package/dist/types/src/core/record-chain.d.ts +24 -0
  330. package/dist/types/src/core/record-chain.d.ts.map +1 -0
  331. package/dist/types/src/handlers/records-write.d.ts +2 -1
  332. package/dist/types/src/handlers/records-write.d.ts.map +1 -1
  333. package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
  334. package/dist/types/src/interfaces/records-write-query.d.ts +33 -0
  335. package/dist/types/src/interfaces/records-write-query.d.ts.map +1 -0
  336. package/dist/types/src/interfaces/records-write-signing.d.ts +35 -0
  337. package/dist/types/src/interfaces/records-write-signing.d.ts.map +1 -0
  338. package/dist/types/src/interfaces/records-write.d.ts +10 -44
  339. package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
  340. package/dist/types/src/store/index-level-compound.d.ts +70 -0
  341. package/dist/types/src/store/index-level-compound.d.ts.map +1 -0
  342. package/dist/types/src/store/index-level.d.ts +0 -58
  343. package/dist/types/src/store/index-level.d.ts.map +1 -1
  344. package/dist/types/src/utils/protocols.d.ts +5 -0
  345. package/dist/types/src/utils/protocols.d.ts.map +1 -1
  346. package/dist/types/src/utils/records.d.ts +3 -1
  347. package/dist/types/src/utils/records.d.ts.map +1 -1
  348. package/dist/types/tests/features/permissions.spec.d.ts.map +1 -1
  349. package/package.json +3 -3
  350. package/src/core/protocol-authorization-action.ts +377 -0
  351. package/src/core/protocol-authorization-validation.ts +391 -0
  352. package/src/core/protocol-authorization.ts +60 -849
  353. package/src/core/record-chain.ts +99 -0
  354. package/src/handlers/records-read.ts +1 -1
  355. package/src/handlers/records-write.ts +37 -21
  356. package/src/interfaces/protocols-configure.ts +33 -5
  357. package/src/interfaces/records-write-query.ts +139 -0
  358. package/src/interfaces/records-write-signing.ts +143 -0
  359. package/src/interfaces/records-write.ts +49 -221
  360. package/src/store/index-level-compound.ts +324 -0
  361. package/src/store/index-level.ts +24 -306
  362. package/src/utils/protocols.ts +8 -0
  363. package/src/utils/records.ts +9 -15
@@ -0,0 +1,324 @@
1
+ import type { Filter, KeyValues, QueryOptions } from '../types/query-types.js';
2
+ import type { IndexedItem, IndexLevelOptions } from './index-level.js';
3
+ import type { LevelWrapperBatchOperation, LevelWrapperIteratorOptions } from './level-wrapper.js';
4
+
5
+ import type { CompoundIndexDefinition } from './index-level.js';
6
+ import type { LevelWrapper } from './level-wrapper.js';
7
+
8
+ import { FilterUtility } from '../utils/filter.js';
9
+ import { SortDirection } from '../types/query-types.js';
10
+
11
+ /** Separator between compound key segments (higher than \x00 so prefix scans work correctly). */
12
+ export const COMPOUND_SEGMENT_SEPARATOR = '\x01';
13
+
14
+ /**
15
+ * Gets the compound index partition for a given compound index definition.
16
+ * Compound index sublevels use the naming convention `__compound:<name>__`.
17
+ */
18
+ export async function getCompoundIndexPartition(
19
+ db: LevelWrapper<string>, tenant: string, compoundIndex: CompoundIndexDefinition
20
+ ): Promise<LevelWrapper<string>> {
21
+ const partitionName = `__compound:${compoundIndex.name}__`;
22
+ return (await db.partition(tenant)).partition(partitionName);
23
+ }
24
+
25
+ /**
26
+ * Builds a compound index key from the given indexes and compound index definition.
27
+ *
28
+ * Key format: `<prop1>\x01<prop2>\x01...\x01<sortValue>\x00<messageCid>`
29
+ *
30
+ * @returns the compound key, or undefined if the indexes don't contain all required properties.
31
+ */
32
+ export function buildCompoundKey(
33
+ messageCid: string, indexes: KeyValues, compoundIndex: CompoundIndexDefinition,
34
+ encodeValue: (value: string | number | boolean) => string, delimiter: string
35
+ ): string | undefined {
36
+ const segments: string[] = [];
37
+
38
+ for (const property of compoundIndex.properties) {
39
+ const value = indexes[property];
40
+ if (value === undefined || Array.isArray(value)) {
41
+ return undefined; // compound indexes don't support array values or missing properties
42
+ }
43
+ segments.push(encodeValue(value));
44
+ }
45
+
46
+ const sortValue = indexes[compoundIndex.sortProperty];
47
+ if (sortValue === undefined || Array.isArray(sortValue)) {
48
+ return undefined;
49
+ }
50
+
51
+ // join prefix segments with \x01, then append sort value and messageCid with the standard delimiters
52
+ const prefixPart = segments.join(COMPOUND_SEGMENT_SEPARATOR);
53
+ const sortPart = encodeValue(sortValue);
54
+ return prefixPart + COMPOUND_SEGMENT_SEPARATOR + sortPart + delimiter + messageCid;
55
+ }
56
+
57
+ /**
58
+ * Builds the prefix portion of a compound key from filter values (without the sort/messageCid suffix).
59
+ * Used for range scans: all entries with this prefix match the filter.
60
+ */
61
+ export function buildCompoundPrefix(
62
+ filter: Filter, compoundIndex: CompoundIndexDefinition,
63
+ encodeValue: (value: string | number | boolean) => string
64
+ ): string | undefined {
65
+ const segments: string[] = [];
66
+
67
+ for (const property of compoundIndex.properties) {
68
+ const filterValue = filter[property];
69
+ if (filterValue === undefined || typeof filterValue === 'object') {
70
+ return undefined; // compound prefix only works with equality filters
71
+ }
72
+ segments.push(encodeValue(filterValue));
73
+ }
74
+
75
+ return segments.join(COMPOUND_SEGMENT_SEPARATOR) + COMPOUND_SEGMENT_SEPARATOR;
76
+ }
77
+
78
+ /**
79
+ * Creates a put operation for a compound index entry.
80
+ * Returns undefined if the indexes don't contain all required compound index properties.
81
+ */
82
+ export function createCompoundIndexPutOperation(
83
+ db: LevelWrapper<string>, tenant: string, item: IndexedItem, compoundIndex: CompoundIndexDefinition,
84
+ encodeValue: (value: string | number | boolean) => string, delimiter: string
85
+ ): Promise<LevelWrapperBatchOperation<string>> | undefined {
86
+ const key = buildCompoundKey(item.messageCid, item.indexes, compoundIndex, encodeValue, delimiter);
87
+ if (key === undefined) {
88
+ return undefined;
89
+ }
90
+
91
+ return createOperationForPartition(db, tenant, `__compound:${compoundIndex.name}__`, {
92
+ type : 'put',
93
+ key,
94
+ value : JSON.stringify(item),
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Creates a delete operation for a compound index entry.
100
+ * Returns undefined if the indexes don't contain all required compound index properties.
101
+ */
102
+ export function createCompoundIndexDeleteOperation(
103
+ db: LevelWrapper<string>, tenant: string, messageCid: string, indexes: KeyValues,
104
+ compoundIndex: CompoundIndexDefinition,
105
+ encodeValue: (value: string | number | boolean) => string, delimiter: string
106
+ ): Promise<LevelWrapperBatchOperation<string>> | undefined {
107
+ const key = buildCompoundKey(messageCid, indexes, compoundIndex, encodeValue, delimiter);
108
+ if (key === undefined) {
109
+ return undefined;
110
+ }
111
+
112
+ return createOperationForPartition(db, tenant, `__compound:${compoundIndex.name}__`, {
113
+ type: 'del',
114
+ key,
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Generic helper to create a batch operation for any named partition under a tenant.
120
+ */
121
+ export async function createOperationForPartition(
122
+ db: LevelWrapper<string>, tenant: string, partitionName: string, operation: LevelWrapperBatchOperation<string>
123
+ ): Promise<LevelWrapperBatchOperation<string>> {
124
+ const tenantPartition = await db.partition(tenant);
125
+ return tenantPartition.createPartitionOperation(partitionName, operation);
126
+ }
127
+
128
+ /**
129
+ * Selects the best compound index that covers the given filter and sort requirements.
130
+ *
131
+ * A compound index "covers" a query when:
132
+ * 1. Every property in the compound index definition is present in the filter as an equality filter.
133
+ * 2. The compound index's sort property matches the query's sort property.
134
+ *
135
+ * Among multiple matching compound indexes, the one with the most properties is preferred
136
+ * (more specific = fewer false positives in the prefix scan).
137
+ */
138
+ export function selectCompoundIndex(
139
+ filter: Filter, queryOptions: QueryOptions, compoundIndexes: CompoundIndexDefinition[]
140
+ ): CompoundIndexDefinition | undefined {
141
+ let bestMatch: CompoundIndexDefinition | undefined;
142
+ let bestPropertyCount = 0;
143
+
144
+ for (const compoundIndex of compoundIndexes) {
145
+ // check that the sort property matches
146
+ if (compoundIndex.sortProperty !== queryOptions.sortProperty) {
147
+ continue;
148
+ }
149
+
150
+ // check that all compound properties are present in the filter as equality filters
151
+ let allPropertiesMatch = true;
152
+ for (const property of compoundIndex.properties) {
153
+ const filterValue = filter[property];
154
+ if (filterValue === undefined || typeof filterValue === 'object') {
155
+ allPropertiesMatch = false;
156
+ break;
157
+ }
158
+ }
159
+
160
+ if (allPropertiesMatch && compoundIndex.properties.length > bestPropertyCount) {
161
+ bestMatch = compoundIndex;
162
+ bestPropertyCount = compoundIndex.properties.length;
163
+ }
164
+ }
165
+
166
+ return bestMatch;
167
+ }
168
+
169
+ /**
170
+ * Queries using a compound index. This is the most efficient query strategy: a single LevelDB
171
+ * range scan that filters, sorts, and paginates all at once.
172
+ *
173
+ * The compound key encodes the filter properties as a prefix and the sort property as a suffix,
174
+ * so iterating over keys with the filter prefix yields results in sort order.
175
+ *
176
+ * Any remaining filter properties not covered by the compound index are verified in memory.
177
+ *
178
+ * @param queryWithIteratorPagingFallback callback to fall back to iterator paging if compound prefix fails.
179
+ */
180
+ export async function queryWithCompoundIndex(
181
+ db: LevelWrapper<string>,
182
+ tenant: string,
183
+ filter: Filter,
184
+ queryOptions: QueryOptions,
185
+ compoundIndex: CompoundIndexDefinition,
186
+ encodeValue: (value: string | number | boolean) => string,
187
+ delimiter: string,
188
+ queryWithIteratorPagingFallback: (
189
+ tenant: string, filters: Filter[], queryOptions: QueryOptions, options?: IndexLevelOptions
190
+ ) => Promise<IndexedItem[]>,
191
+ options?: IndexLevelOptions
192
+ ): Promise<IndexedItem[]> {
193
+ const { sortDirection = SortDirection.Ascending, cursor, limit } = queryOptions;
194
+
195
+ const prefix = buildCompoundPrefix(filter, compoundIndex, encodeValue);
196
+ if (prefix === undefined) {
197
+ // should not happen since selectCompoundIndex already validated, but guard against it
198
+ return queryWithIteratorPagingFallback(tenant, [filter], queryOptions, options);
199
+ }
200
+
201
+ const partition = await getCompoundIndexPartition(db, tenant, compoundIndex);
202
+
203
+ // determine the iterator bounds from the prefix
204
+ const iteratorOptions: LevelWrapperIteratorOptions<string> = {};
205
+
206
+ if (cursor !== undefined) {
207
+ // build the full compound key for the cursor position
208
+ const cursorSortEncoded = encodeValue(cursor.value);
209
+ const cursorKey = prefix + cursorSortEncoded + delimiter + cursor.messageCid;
210
+
211
+ if (sortDirection === SortDirection.Ascending) {
212
+ iteratorOptions.gt = cursorKey;
213
+ // upper bound: everything with this prefix (prefix + \xff is past all valid compound keys with this prefix)
214
+ iteratorOptions.lt = prefix + '\xff';
215
+ } else {
216
+ iteratorOptions.lt = cursorKey;
217
+ iteratorOptions.gt = prefix;
218
+ iteratorOptions.reverse = true;
219
+ }
220
+ } else {
221
+ if (sortDirection === SortDirection.Ascending) {
222
+ iteratorOptions.gt = prefix;
223
+ iteratorOptions.lt = prefix + '\xff';
224
+ } else {
225
+ // for descending without cursor, start from the end of the prefix range
226
+ iteratorOptions.gt = prefix;
227
+ iteratorOptions.lt = prefix + '\xff';
228
+ iteratorOptions.reverse = true;
229
+ }
230
+ }
231
+
232
+ // determine which filter properties are NOT covered by the compound index
233
+ // (need in-memory verification for these)
234
+ // NOTE: the compound index equality properties are fully covered by the prefix scan,
235
+ // but the sort property is only covered for ordering — any range filter on the sort
236
+ // property must still be applied as a residual filter.
237
+ const coveredEqualityProperties = new Set(compoundIndex.properties);
238
+ const residualFilter: Filter = {};
239
+ let hasResidualFilter = false;
240
+ for (const property in filter) {
241
+ if (!coveredEqualityProperties.has(property)) {
242
+ residualFilter[property] = filter[property];
243
+ hasResidualFilter = true;
244
+ }
245
+ }
246
+
247
+ const matches: IndexedItem[] = [];
248
+ for await (const [_key, value] of partition.iterator(iteratorOptions, options)) {
249
+ if (limit !== undefined && matches.length === limit) {
250
+ break;
251
+ }
252
+
253
+ const item = JSON.parse(value) as IndexedItem;
254
+
255
+ // verify any residual filter properties in memory
256
+ if (hasResidualFilter && !FilterUtility.matchFilter(item.indexes, residualFilter)) {
257
+ continue;
258
+ }
259
+
260
+ matches.push(item);
261
+ }
262
+
263
+ return matches;
264
+ }
265
+
266
+ /**
267
+ * Counts items matching a compound index prefix without loading full records.
268
+ * Iterates only keys (not values) for maximum efficiency.
269
+ *
270
+ * @param queryFallback callback to fall back to full query if compound prefix fails.
271
+ */
272
+ export async function countWithCompoundIndex(
273
+ db: LevelWrapper<string>,
274
+ tenant: string,
275
+ filter: Filter,
276
+ compoundIndex: CompoundIndexDefinition,
277
+ encodeValue: (value: string | number | boolean) => string,
278
+ queryFallback: (tenant: string, filters: Filter[], queryOptions: QueryOptions, options?: IndexLevelOptions) => Promise<IndexedItem[]>,
279
+ options?: IndexLevelOptions
280
+ ): Promise<number> {
281
+ const prefix = buildCompoundPrefix(filter, compoundIndex, encodeValue);
282
+ if (prefix === undefined) {
283
+ // fallback
284
+ const results = await queryFallback(tenant, [filter], { sortProperty: compoundIndex.sortProperty }, options);
285
+ return results.length;
286
+ }
287
+
288
+ const partition = await getCompoundIndexPartition(db, tenant, compoundIndex);
289
+
290
+ // determine which filter properties are NOT covered by the compound index
291
+ // (same logic as queryWithCompoundIndex: sort property range filters are residual)
292
+ const coveredEqualityProperties = new Set(compoundIndex.properties);
293
+ let hasResidualFilter = false;
294
+ const residualFilter: Filter = {};
295
+ for (const property in filter) {
296
+ if (!coveredEqualityProperties.has(property)) {
297
+ residualFilter[property] = filter[property];
298
+ hasResidualFilter = true;
299
+ }
300
+ }
301
+
302
+ const iteratorOptions: LevelWrapperIteratorOptions<string> = {
303
+ gt : prefix,
304
+ lt : prefix + '\xff',
305
+ };
306
+
307
+ let count = 0;
308
+ if (hasResidualFilter) {
309
+ // must read values to check residual filter
310
+ for await (const [_key, value] of partition.iterator(iteratorOptions, options)) {
311
+ const item = JSON.parse(value) as IndexedItem;
312
+ if (FilterUtility.matchFilter(item.indexes, residualFilter)) {
313
+ count++;
314
+ }
315
+ }
316
+ } else {
317
+ // no residual filter — iterate keys via iterator without parsing values
318
+ for await (const [_key, _value] of partition.iterator(iteratorOptions, options)) {
319
+ count++;
320
+ }
321
+ }
322
+
323
+ return count;
324
+ }
@@ -4,6 +4,13 @@ import type { LevelWrapperBatchOperation, LevelWrapperIteratorOptions, } from '.
4
4
  import { isEmptyObject } from '../utils/object.js';
5
5
  import { lexicographicalCompare } from '../utils/string.js';
6
6
  import { SortDirection } from '../types/query-types.js';
7
+ import {
8
+ countWithCompoundIndex,
9
+ createCompoundIndexDeleteOperation,
10
+ createCompoundIndexPutOperation,
11
+ queryWithCompoundIndex,
12
+ selectCompoundIndex,
13
+ } from './index-level-compound.js';
7
14
  import { createLevelDatabase, LevelWrapper } from './level-wrapper.js';
8
15
  import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
9
16
  import { FilterSelector, FilterUtility } from '../utils/filter.js';
@@ -36,9 +43,6 @@ export type CompoundIndexDefinition = {
36
43
 
37
44
  const INDEX_SUBLEVEL_NAME = 'index';
38
45
 
39
- /** Separator between compound key segments (higher than \x00 so prefix scans work correctly). */
40
- const COMPOUND_SEGMENT_SEPARATOR = '\x01';
41
-
42
46
  export interface IndexLevelOptions {
43
47
  signal?: AbortSignal;
44
48
  }
@@ -121,7 +125,9 @@ export class IndexLevel {
121
125
 
122
126
  // create compound index entries for any registered compound indexes whose properties are all present in the indexes.
123
127
  for (const compoundIndex of this._compoundIndexes) {
124
- const compoundOp = this.createCompoundIndexPutOperation(tenant, item, compoundIndex);
128
+ const compoundOp = createCompoundIndexPutOperation(
129
+ this.db, tenant, item, compoundIndex, IndexLevel.encodeValue, IndexLevel.delimiter
130
+ );
125
131
  if (compoundOp !== undefined) {
126
132
  opCreationPromises.push(compoundOp);
127
133
  }
@@ -171,7 +177,9 @@ export class IndexLevel {
171
177
 
172
178
  // delete compound index entries
173
179
  for (const compoundIndex of this._compoundIndexes) {
174
- const compoundOp = this.createCompoundIndexDeleteOperation(tenant, messageCid, indexes, compoundIndex);
180
+ const compoundOp = createCompoundIndexDeleteOperation(
181
+ this.db, tenant, messageCid, indexes, compoundIndex, IndexLevel.encodeValue, IndexLevel.delimiter
182
+ );
175
183
  if (compoundOp !== undefined) {
176
184
  opCreationPromises.push(compoundOp);
177
185
  }
@@ -289,9 +297,13 @@ export class IndexLevel {
289
297
 
290
298
  // Strategy 1: try compound index for single-filter queries
291
299
  if (filters.length === 1 && !isEmptyObject(filters[0])) {
292
- const compoundResult = this.selectCompoundIndex(filters[0], queryOptions);
300
+ const compoundResult = selectCompoundIndex(filters[0], queryOptions, this._compoundIndexes);
293
301
  if (compoundResult !== undefined) {
294
- return this.queryWithCompoundIndex(tenant, filters[0], queryOptions, compoundResult, options);
302
+ return queryWithCompoundIndex(
303
+ this.db, tenant, filters[0], queryOptions, compoundResult,
304
+ IndexLevel.encodeValue, IndexLevel.delimiter,
305
+ this.queryWithIteratorPaging.bind(this), options
306
+ );
295
307
  }
296
308
  }
297
309
 
@@ -315,9 +327,12 @@ export class IndexLevel {
315
327
 
316
328
  // try compound index for single-filter queries
317
329
  if (filters.length === 1 && !isEmptyObject(filters[0])) {
318
- const compoundResult = this.selectCompoundIndex(filters[0], { ...queryOptions });
330
+ const compoundResult = selectCompoundIndex(filters[0], { ...queryOptions }, this._compoundIndexes);
319
331
  if (compoundResult !== undefined) {
320
- return this.countWithCompoundIndex(tenant, filters[0], compoundResult, options);
332
+ return countWithCompoundIndex(
333
+ this.db, tenant, filters[0], compoundResult,
334
+ IndexLevel.encodeValue, this.query.bind(this), options
335
+ );
321
336
  }
322
337
  }
323
338
 
@@ -749,303 +764,6 @@ export class IndexLevel {
749
764
  }
750
765
  }
751
766
 
752
- // =========================================================================
753
- // Compound index methods
754
- // =========================================================================
755
-
756
- /**
757
- * Gets the compound index partition for a given compound index definition.
758
- * Compound index sublevels use the naming convention `__compound:<name>__`.
759
- */
760
- private async getCompoundIndexPartition(tenant: string, compoundIndex: CompoundIndexDefinition): Promise<LevelWrapper<string>> {
761
- const partitionName = `__compound:${compoundIndex.name}__`;
762
- return (await this.db.partition(tenant)).partition(partitionName);
763
- }
764
-
765
- /**
766
- * Builds a compound index key from the given indexes and compound index definition.
767
- *
768
- * Key format: `<prop1>\x01<prop2>\x01...\x01<sortValue>\x00<messageCid>`
769
- *
770
- * @returns the compound key, or undefined if the indexes don't contain all required properties.
771
- */
772
- private static buildCompoundKey(messageCid: string, indexes: KeyValues, compoundIndex: CompoundIndexDefinition): string | undefined {
773
- const segments: string[] = [];
774
-
775
- for (const property of compoundIndex.properties) {
776
- const value = indexes[property];
777
- if (value === undefined || Array.isArray(value)) {
778
- return undefined; // compound indexes don't support array values or missing properties
779
- }
780
- segments.push(IndexLevel.encodeValue(value));
781
- }
782
-
783
- const sortValue = indexes[compoundIndex.sortProperty];
784
- if (sortValue === undefined || Array.isArray(sortValue)) {
785
- return undefined;
786
- }
787
-
788
- // join prefix segments with \x01, then append sort value and messageCid with the standard delimiters
789
- const prefixPart = segments.join(COMPOUND_SEGMENT_SEPARATOR);
790
- const sortPart = IndexLevel.encodeValue(sortValue);
791
- return prefixPart + COMPOUND_SEGMENT_SEPARATOR + sortPart + IndexLevel.delimiter + messageCid;
792
- }
793
-
794
- /**
795
- * Builds the prefix portion of a compound key from filter values (without the sort/messageCid suffix).
796
- * Used for range scans: all entries with this prefix match the filter.
797
- */
798
- private static buildCompoundPrefix(filter: Filter, compoundIndex: CompoundIndexDefinition): string | undefined {
799
- const segments: string[] = [];
800
-
801
- for (const property of compoundIndex.properties) {
802
- const filterValue = filter[property];
803
- if (filterValue === undefined || typeof filterValue === 'object') {
804
- return undefined; // compound prefix only works with equality filters
805
- }
806
- segments.push(IndexLevel.encodeValue(filterValue));
807
- }
808
-
809
- return segments.join(COMPOUND_SEGMENT_SEPARATOR) + COMPOUND_SEGMENT_SEPARATOR;
810
- }
811
-
812
- /**
813
- * Creates a put operation for a compound index entry.
814
- * Returns undefined if the indexes don't contain all required compound index properties.
815
- */
816
- private createCompoundIndexPutOperation(
817
- tenant: string,
818
- item: IndexedItem,
819
- compoundIndex: CompoundIndexDefinition
820
- ): Promise<LevelWrapperBatchOperation<string>> | undefined {
821
- const key = IndexLevel.buildCompoundKey(item.messageCid, item.indexes, compoundIndex);
822
- if (key === undefined) {
823
- return undefined;
824
- }
825
-
826
- return this.createOperationForPartition(tenant, `__compound:${compoundIndex.name}__`, {
827
- type : 'put',
828
- key,
829
- value : JSON.stringify(item),
830
- });
831
- }
832
-
833
- /**
834
- * Creates a delete operation for a compound index entry.
835
- * Returns undefined if the indexes don't contain all required compound index properties.
836
- */
837
- private createCompoundIndexDeleteOperation(
838
- tenant: string,
839
- messageCid: string,
840
- indexes: KeyValues,
841
- compoundIndex: CompoundIndexDefinition
842
- ): Promise<LevelWrapperBatchOperation<string>> | undefined {
843
- const key = IndexLevel.buildCompoundKey(messageCid, indexes, compoundIndex);
844
- if (key === undefined) {
845
- return undefined;
846
- }
847
-
848
- return this.createOperationForPartition(tenant, `__compound:${compoundIndex.name}__`, {
849
- type: 'del',
850
- key,
851
- });
852
- }
853
-
854
- /**
855
- * Generic helper to create a batch operation for any named partition under a tenant.
856
- */
857
- private async createOperationForPartition(
858
- tenant: string,
859
- partitionName: string,
860
- operation: LevelWrapperBatchOperation<string>
861
- ): Promise<LevelWrapperBatchOperation<string>> {
862
- const tenantPartition = await this.db.partition(tenant);
863
- return tenantPartition.createPartitionOperation(partitionName, operation);
864
- }
865
-
866
- /**
867
- * Selects the best compound index that covers the given filter and sort requirements.
868
- *
869
- * A compound index "covers" a query when:
870
- * 1. Every property in the compound index definition is present in the filter as an equality filter.
871
- * 2. The compound index's sort property matches the query's sort property.
872
- *
873
- * Among multiple matching compound indexes, the one with the most properties is preferred
874
- * (more specific = fewer false positives in the prefix scan).
875
- */
876
- private selectCompoundIndex(filter: Filter, queryOptions: QueryOptions): CompoundIndexDefinition | undefined {
877
- let bestMatch: CompoundIndexDefinition | undefined;
878
- let bestPropertyCount = 0;
879
-
880
- for (const compoundIndex of this._compoundIndexes) {
881
- // check that the sort property matches
882
- if (compoundIndex.sortProperty !== queryOptions.sortProperty) {
883
- continue;
884
- }
885
-
886
- // check that all compound properties are present in the filter as equality filters
887
- let allPropertiesMatch = true;
888
- for (const property of compoundIndex.properties) {
889
- const filterValue = filter[property];
890
- if (filterValue === undefined || typeof filterValue === 'object') {
891
- allPropertiesMatch = false;
892
- break;
893
- }
894
- }
895
-
896
- if (allPropertiesMatch && compoundIndex.properties.length > bestPropertyCount) {
897
- bestMatch = compoundIndex;
898
- bestPropertyCount = compoundIndex.properties.length;
899
- }
900
- }
901
-
902
- return bestMatch;
903
- }
904
-
905
- /**
906
- * Queries using a compound index. This is the most efficient query strategy: a single LevelDB
907
- * range scan that filters, sorts, and paginates all at once.
908
- *
909
- * The compound key encodes the filter properties as a prefix and the sort property as a suffix,
910
- * so iterating over keys with the filter prefix yields results in sort order.
911
- *
912
- * Any remaining filter properties not covered by the compound index are verified in memory.
913
- */
914
- private async queryWithCompoundIndex(
915
- tenant: string,
916
- filter: Filter,
917
- queryOptions: QueryOptions,
918
- compoundIndex: CompoundIndexDefinition,
919
- options?: IndexLevelOptions
920
- ): Promise<IndexedItem[]> {
921
- const { sortDirection = SortDirection.Ascending, cursor, limit } = queryOptions;
922
-
923
- const prefix = IndexLevel.buildCompoundPrefix(filter, compoundIndex);
924
- if (prefix === undefined) {
925
- // should not happen since selectCompoundIndex already validated, but guard against it
926
- return this.queryWithIteratorPaging(tenant, [filter], queryOptions, options);
927
- }
928
-
929
- const partition = await this.getCompoundIndexPartition(tenant, compoundIndex);
930
-
931
- // determine the iterator bounds from the prefix
932
- const iteratorOptions: LevelWrapperIteratorOptions<string> = {};
933
-
934
- if (cursor !== undefined) {
935
- // build the full compound key for the cursor position
936
- const cursorSortEncoded = IndexLevel.encodeValue(cursor.value);
937
- const cursorKey = prefix + cursorSortEncoded + IndexLevel.delimiter + cursor.messageCid;
938
-
939
- if (sortDirection === SortDirection.Ascending) {
940
- iteratorOptions.gt = cursorKey;
941
- // upper bound: everything with this prefix (prefix + \xff is past all valid compound keys with this prefix)
942
- iteratorOptions.lt = prefix + '\xff';
943
- } else {
944
- iteratorOptions.lt = cursorKey;
945
- iteratorOptions.gt = prefix;
946
- iteratorOptions.reverse = true;
947
- }
948
- } else {
949
- if (sortDirection === SortDirection.Ascending) {
950
- iteratorOptions.gt = prefix;
951
- iteratorOptions.lt = prefix + '\xff';
952
- } else {
953
- // for descending without cursor, start from the end of the prefix range
954
- iteratorOptions.gt = prefix;
955
- iteratorOptions.lt = prefix + '\xff';
956
- iteratorOptions.reverse = true;
957
- }
958
- }
959
-
960
- // determine which filter properties are NOT covered by the compound index
961
- // (need in-memory verification for these)
962
- // NOTE: the compound index equality properties are fully covered by the prefix scan,
963
- // but the sort property is only covered for ordering — any range filter on the sort
964
- // property must still be applied as a residual filter.
965
- const coveredEqualityProperties = new Set(compoundIndex.properties);
966
- const residualFilter: Filter = {};
967
- let hasResidualFilter = false;
968
- for (const property in filter) {
969
- if (!coveredEqualityProperties.has(property)) {
970
- residualFilter[property] = filter[property];
971
- hasResidualFilter = true;
972
- }
973
- }
974
-
975
- const matches: IndexedItem[] = [];
976
- for await (const [_key, value] of partition.iterator(iteratorOptions, options)) {
977
- if (limit !== undefined && matches.length === limit) {
978
- break;
979
- }
980
-
981
- const item = JSON.parse(value) as IndexedItem;
982
-
983
- // verify any residual filter properties in memory
984
- if (hasResidualFilter && !FilterUtility.matchFilter(item.indexes, residualFilter)) {
985
- continue;
986
- }
987
-
988
- matches.push(item);
989
- }
990
-
991
- return matches;
992
- }
993
-
994
- /**
995
- * Counts items matching a compound index prefix without loading full records.
996
- * Iterates only keys (not values) for maximum efficiency.
997
- */
998
- private async countWithCompoundIndex(
999
- tenant: string,
1000
- filter: Filter,
1001
- compoundIndex: CompoundIndexDefinition,
1002
- options?: IndexLevelOptions
1003
- ): Promise<number> {
1004
- const prefix = IndexLevel.buildCompoundPrefix(filter, compoundIndex);
1005
- if (prefix === undefined) {
1006
- // fallback
1007
- const results = await this.query(tenant, [filter], { sortProperty: compoundIndex.sortProperty }, options);
1008
- return results.length;
1009
- }
1010
-
1011
- const partition = await this.getCompoundIndexPartition(tenant, compoundIndex);
1012
-
1013
- // determine which filter properties are NOT covered by the compound index
1014
- // (same logic as queryWithCompoundIndex: sort property range filters are residual)
1015
- const coveredEqualityProperties = new Set(compoundIndex.properties);
1016
- let hasResidualFilter = false;
1017
- const residualFilter: Filter = {};
1018
- for (const property in filter) {
1019
- if (!coveredEqualityProperties.has(property)) {
1020
- residualFilter[property] = filter[property];
1021
- hasResidualFilter = true;
1022
- }
1023
- }
1024
-
1025
- const iteratorOptions: LevelWrapperIteratorOptions<string> = {
1026
- gt : prefix,
1027
- lt : prefix + '\xff',
1028
- };
1029
-
1030
- let count = 0;
1031
- if (hasResidualFilter) {
1032
- // must read values to check residual filter
1033
- for await (const [_key, value] of partition.iterator(iteratorOptions, options)) {
1034
- const item = JSON.parse(value) as IndexedItem;
1035
- if (FilterUtility.matchFilter(item.indexes, residualFilter)) {
1036
- count++;
1037
- }
1038
- }
1039
- } else {
1040
- // no residual filter — iterate keys via iterator without parsing values
1041
- for await (const [_key, _value] of partition.iterator(iteratorOptions, options)) {
1042
- count++;
1043
- }
1044
- }
1045
-
1046
- return count;
1047
- }
1048
-
1049
767
  // =========================================================================
1050
768
  // Query strategy selection
1051
769
  // =========================================================================