@aztec-labs/kv-store 6.0.0-nightly.20260829

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 (246) hide show
  1. package/README.md +19 -0
  2. package/dest/bench/shared_map_bench.d.ts +19 -0
  3. package/dest/bench/shared_map_bench.d.ts.map +1 -0
  4. package/dest/bench/shared_map_bench.js +91 -0
  5. package/dest/deprecated/indexeddb/array.d.ts +22 -0
  6. package/dest/deprecated/indexeddb/array.d.ts.map +1 -0
  7. package/dest/deprecated/indexeddb/array.js +121 -0
  8. package/dest/deprecated/indexeddb/index.d.ts +7 -0
  9. package/dest/deprecated/indexeddb/index.d.ts.map +1 -0
  10. package/dest/deprecated/indexeddb/index.js +8 -0
  11. package/dest/deprecated/indexeddb/map.d.ts +38 -0
  12. package/dest/deprecated/indexeddb/map.d.ts.map +1 -0
  13. package/dest/deprecated/indexeddb/map.js +121 -0
  14. package/dest/deprecated/indexeddb/multi_map.d.ts +14 -0
  15. package/dest/deprecated/indexeddb/multi_map.d.ts.map +1 -0
  16. package/dest/deprecated/indexeddb/multi_map.js +108 -0
  17. package/dest/deprecated/indexeddb/set.d.ts +17 -0
  18. package/dest/deprecated/indexeddb/set.d.ts.map +1 -0
  19. package/dest/deprecated/indexeddb/set.js +24 -0
  20. package/dest/deprecated/indexeddb/singleton.d.ts +17 -0
  21. package/dest/deprecated/indexeddb/singleton.d.ts.map +1 -0
  22. package/dest/deprecated/indexeddb/singleton.js +41 -0
  23. package/dest/deprecated/indexeddb/store.d.ts +98 -0
  24. package/dest/deprecated/indexeddb/store.d.ts.map +1 -0
  25. package/dest/deprecated/indexeddb/store.js +171 -0
  26. package/dest/interfaces/array.d.ts +81 -0
  27. package/dest/interfaces/array.d.ts.map +1 -0
  28. package/dest/interfaces/array.js +1 -0
  29. package/dest/interfaces/array_test_suite.d.ts +3 -0
  30. package/dest/interfaces/array_test_suite.d.ts.map +1 -0
  31. package/dest/interfaces/array_test_suite.js +103 -0
  32. package/dest/interfaces/common.d.ts +25 -0
  33. package/dest/interfaces/common.d.ts.map +1 -0
  34. package/dest/interfaces/common.js +8 -0
  35. package/dest/interfaces/counter.d.ts +59 -0
  36. package/dest/interfaces/counter.d.ts.map +1 -0
  37. package/dest/interfaces/counter.js +1 -0
  38. package/dest/interfaces/index.d.ts +10 -0
  39. package/dest/interfaces/index.d.ts.map +1 -0
  40. package/dest/interfaces/index.js +8 -0
  41. package/dest/interfaces/map.d.ts +106 -0
  42. package/dest/interfaces/map.d.ts.map +1 -0
  43. package/dest/interfaces/map.js +3 -0
  44. package/dest/interfaces/map_test_suite.d.ts +3 -0
  45. package/dest/interfaces/map_test_suite.d.ts.map +1 -0
  46. package/dest/interfaces/map_test_suite.js +261 -0
  47. package/dest/interfaces/multi_map.d.ts +41 -0
  48. package/dest/interfaces/multi_map.d.ts.map +1 -0
  49. package/dest/interfaces/multi_map.js +3 -0
  50. package/dest/interfaces/multi_map_test_suite.d.ts +3 -0
  51. package/dest/interfaces/multi_map_test_suite.d.ts.map +1 -0
  52. package/dest/interfaces/multi_map_test_suite.js +363 -0
  53. package/dest/interfaces/set.d.ts +44 -0
  54. package/dest/interfaces/set.d.ts.map +1 -0
  55. package/dest/interfaces/set.js +1 -0
  56. package/dest/interfaces/set_test_suite.d.ts +3 -0
  57. package/dest/interfaces/set_test_suite.d.ts.map +1 -0
  58. package/dest/interfaces/set_test_suite.js +95 -0
  59. package/dest/interfaces/singleton.d.ts +29 -0
  60. package/dest/interfaces/singleton.d.ts.map +1 -0
  61. package/dest/interfaces/singleton.js +4 -0
  62. package/dest/interfaces/singleton_test_suite.d.ts +3 -0
  63. package/dest/interfaces/singleton_test_suite.d.ts.map +1 -0
  64. package/dest/interfaces/singleton_test_suite.js +29 -0
  65. package/dest/interfaces/store.d.ts +120 -0
  66. package/dest/interfaces/store.d.ts.map +1 -0
  67. package/dest/interfaces/store.js +1 -0
  68. package/dest/interfaces/utils.d.ts +18 -0
  69. package/dest/interfaces/utils.d.ts.map +1 -0
  70. package/dest/interfaces/utils.js +18 -0
  71. package/dest/lmdb/array.d.ts +24 -0
  72. package/dest/lmdb/array.d.ts.map +1 -0
  73. package/dest/lmdb/array.js +119 -0
  74. package/dest/lmdb/counter.d.ts +19 -0
  75. package/dest/lmdb/counter.d.ts.map +1 -0
  76. package/dest/lmdb/counter.js +50 -0
  77. package/dest/lmdb/index.d.ts +12 -0
  78. package/dest/lmdb/index.d.ts.map +1 -0
  79. package/dest/lmdb/index.js +25 -0
  80. package/dest/lmdb/map.d.ts +40 -0
  81. package/dest/lmdb/map.d.ts.map +1 -0
  82. package/dest/lmdb/map.js +156 -0
  83. package/dest/lmdb/multi_map.d.ts +13 -0
  84. package/dest/lmdb/multi_map.d.ts.map +1 -0
  85. package/dest/lmdb/multi_map.js +44 -0
  86. package/dest/lmdb/set.d.ts +18 -0
  87. package/dest/lmdb/set.d.ts.map +1 -0
  88. package/dest/lmdb/set.js +32 -0
  89. package/dest/lmdb/singleton.d.ts +14 -0
  90. package/dest/lmdb/singleton.d.ts.map +1 -0
  91. package/dest/lmdb/singleton.js +26 -0
  92. package/dest/lmdb/store.d.ts +94 -0
  93. package/dest/lmdb/store.d.ts.map +1 -0
  94. package/dest/lmdb/store.js +200 -0
  95. package/dest/lmdb-v2/array.d.ts +19 -0
  96. package/dest/lmdb-v2/array.d.ts.map +1 -0
  97. package/dest/lmdb-v2/array.js +103 -0
  98. package/dest/lmdb-v2/factory.d.ts +35 -0
  99. package/dest/lmdb-v2/factory.d.ts.map +1 -0
  100. package/dest/lmdb-v2/factory.js +126 -0
  101. package/dest/lmdb-v2/index.d.ts +3 -0
  102. package/dest/lmdb-v2/index.d.ts.map +1 -0
  103. package/dest/lmdb-v2/index.js +2 -0
  104. package/dest/lmdb-v2/map.d.ts +53 -0
  105. package/dest/lmdb-v2/map.d.ts.map +1 -0
  106. package/dest/lmdb-v2/map.js +106 -0
  107. package/dest/lmdb-v2/message.d.ts +131 -0
  108. package/dest/lmdb-v2/message.d.ts.map +1 -0
  109. package/dest/lmdb-v2/message.js +20 -0
  110. package/dest/lmdb-v2/multi_map.d.ts +52 -0
  111. package/dest/lmdb-v2/multi_map.d.ts.map +1 -0
  112. package/dest/lmdb-v2/multi_map.js +123 -0
  113. package/dest/lmdb-v2/read_transaction.d.ts +16 -0
  114. package/dest/lmdb-v2/read_transaction.d.ts.map +1 -0
  115. package/dest/lmdb-v2/read_transaction.js +137 -0
  116. package/dest/lmdb-v2/set.d.ts +15 -0
  117. package/dest/lmdb-v2/set.d.ts.map +1 -0
  118. package/dest/lmdb-v2/set.js +23 -0
  119. package/dest/lmdb-v2/singleton.d.ts +12 -0
  120. package/dest/lmdb-v2/singleton.d.ts.map +1 -0
  121. package/dest/lmdb-v2/singleton.js +31 -0
  122. package/dest/lmdb-v2/store.d.ts +43 -0
  123. package/dest/lmdb-v2/store.d.ts.map +1 -0
  124. package/dest/lmdb-v2/store.js +171 -0
  125. package/dest/lmdb-v2/tx-helpers.d.ts +6 -0
  126. package/dest/lmdb-v2/tx-helpers.d.ts.map +1 -0
  127. package/dest/lmdb-v2/tx-helpers.js +21 -0
  128. package/dest/lmdb-v2/utils.d.ts +8 -0
  129. package/dest/lmdb-v2/utils.d.ts.map +1 -0
  130. package/dest/lmdb-v2/utils.js +32 -0
  131. package/dest/lmdb-v2/write_transaction.d.ts +17 -0
  132. package/dest/lmdb-v2/write_transaction.d.ts.map +1 -0
  133. package/dest/lmdb-v2/write_transaction.js +258 -0
  134. package/dest/sqlite-opfs/array.d.ts +21 -0
  135. package/dest/sqlite-opfs/array.d.ts.map +1 -0
  136. package/dest/sqlite-opfs/array.js +128 -0
  137. package/dest/sqlite-opfs/errors.d.ts +54 -0
  138. package/dest/sqlite-opfs/errors.d.ts.map +1 -0
  139. package/dest/sqlite-opfs/errors.js +77 -0
  140. package/dest/sqlite-opfs/index.d.ts +14 -0
  141. package/dest/sqlite-opfs/index.d.ts.map +1 -0
  142. package/dest/sqlite-opfs/index.js +16 -0
  143. package/dest/sqlite-opfs/internal/ordered-binary-browser.d.ts +32 -0
  144. package/dest/sqlite-opfs/internal/ordered-binary-browser.d.ts.map +1 -0
  145. package/dest/sqlite-opfs/internal/ordered-binary-browser.js +448 -0
  146. package/dest/sqlite-opfs/manage.d.ts +22 -0
  147. package/dest/sqlite-opfs/manage.d.ts.map +1 -0
  148. package/dest/sqlite-opfs/manage.js +38 -0
  149. package/dest/sqlite-opfs/map.d.ts +35 -0
  150. package/dest/sqlite-opfs/map.d.ts.map +1 -0
  151. package/dest/sqlite-opfs/map.js +163 -0
  152. package/dest/sqlite-opfs/messages.d.ts +66 -0
  153. package/dest/sqlite-opfs/messages.d.ts.map +1 -0
  154. package/dest/sqlite-opfs/messages.js +5 -0
  155. package/dest/sqlite-opfs/multi_map.d.ts +16 -0
  156. package/dest/sqlite-opfs/multi_map.d.ts.map +1 -0
  157. package/dest/sqlite-opfs/multi_map.js +67 -0
  158. package/dest/sqlite-opfs/pool_integrity.d.ts +20 -0
  159. package/dest/sqlite-opfs/pool_integrity.d.ts.map +1 -0
  160. package/dest/sqlite-opfs/pool_integrity.js +282 -0
  161. package/dest/sqlite-opfs/pool_lock.d.ts +8 -0
  162. package/dest/sqlite-opfs/pool_lock.d.ts.map +1 -0
  163. package/dest/sqlite-opfs/pool_lock.js +47 -0
  164. package/dest/sqlite-opfs/set.d.ts +13 -0
  165. package/dest/sqlite-opfs/set.d.ts.map +1 -0
  166. package/dest/sqlite-opfs/set.js +19 -0
  167. package/dest/sqlite-opfs/singleton.d.ts +13 -0
  168. package/dest/sqlite-opfs/singleton.d.ts.map +1 -0
  169. package/dest/sqlite-opfs/singleton.js +48 -0
  170. package/dest/sqlite-opfs/store.d.ts +84 -0
  171. package/dest/sqlite-opfs/store.d.ts.map +1 -0
  172. package/dest/sqlite-opfs/store.js +318 -0
  173. package/dest/sqlite-opfs/worker.d.ts +2 -0
  174. package/dest/sqlite-opfs/worker.d.ts.map +1 -0
  175. package/dest/sqlite-opfs/worker.js +275 -0
  176. package/dest/stores/index.d.ts +3 -0
  177. package/dest/stores/index.d.ts.map +1 -0
  178. package/dest/stores/index.js +2 -0
  179. package/dest/stores/l2_tips_store.d.ts +23 -0
  180. package/dest/stores/l2_tips_store.d.ts.map +1 -0
  181. package/dest/stores/l2_tips_store.js +64 -0
  182. package/dest/utils.d.ts +15 -0
  183. package/dest/utils.d.ts.map +1 -0
  184. package/dest/utils.js +59 -0
  185. package/package.json +108 -0
  186. package/src/bench/shared_map_bench.ts +111 -0
  187. package/src/deprecated/indexeddb/array.ts +122 -0
  188. package/src/deprecated/indexeddb/index.ts +12 -0
  189. package/src/deprecated/indexeddb/map.ts +137 -0
  190. package/src/deprecated/indexeddb/multi_map.ts +106 -0
  191. package/src/deprecated/indexeddb/set.ts +37 -0
  192. package/src/deprecated/indexeddb/singleton.ts +52 -0
  193. package/src/deprecated/indexeddb/store.ts +214 -0
  194. package/src/interfaces/array.ts +92 -0
  195. package/src/interfaces/array_test_suite.ts +128 -0
  196. package/src/interfaces/common.ts +31 -0
  197. package/src/interfaces/counter.ts +65 -0
  198. package/src/interfaces/index.ts +9 -0
  199. package/src/interfaces/map.ts +117 -0
  200. package/src/interfaces/map_test_suite.ts +198 -0
  201. package/src/interfaces/multi_map.ts +45 -0
  202. package/src/interfaces/multi_map_test_suite.ts +372 -0
  203. package/src/interfaces/set.ts +48 -0
  204. package/src/interfaces/set_test_suite.ts +79 -0
  205. package/src/interfaces/singleton.ts +29 -0
  206. package/src/interfaces/singleton_test_suite.ts +44 -0
  207. package/src/interfaces/store.ts +142 -0
  208. package/src/interfaces/utils.ts +23 -0
  209. package/src/lmdb/array.ts +140 -0
  210. package/src/lmdb/counter.ts +67 -0
  211. package/src/lmdb/index.ts +41 -0
  212. package/src/lmdb/map.ts +177 -0
  213. package/src/lmdb/multi_map.ts +51 -0
  214. package/src/lmdb/set.ts +45 -0
  215. package/src/lmdb/singleton.ts +35 -0
  216. package/src/lmdb/store.ts +242 -0
  217. package/src/lmdb-v2/array.ts +120 -0
  218. package/src/lmdb-v2/factory.ts +184 -0
  219. package/src/lmdb-v2/index.ts +2 -0
  220. package/src/lmdb-v2/map.ts +136 -0
  221. package/src/lmdb-v2/message.ts +169 -0
  222. package/src/lmdb-v2/multi_map.ts +150 -0
  223. package/src/lmdb-v2/read_transaction.ts +156 -0
  224. package/src/lmdb-v2/set.ts +33 -0
  225. package/src/lmdb-v2/singleton.ts +38 -0
  226. package/src/lmdb-v2/store.ts +220 -0
  227. package/src/lmdb-v2/tx-helpers.ts +29 -0
  228. package/src/lmdb-v2/utils.ts +32 -0
  229. package/src/lmdb-v2/write_transaction.ts +315 -0
  230. package/src/sqlite-opfs/array.ts +124 -0
  231. package/src/sqlite-opfs/errors.ts +94 -0
  232. package/src/sqlite-opfs/index.ts +27 -0
  233. package/src/sqlite-opfs/internal/ordered-binary-browser.js +465 -0
  234. package/src/sqlite-opfs/manage.ts +46 -0
  235. package/src/sqlite-opfs/map.ts +163 -0
  236. package/src/sqlite-opfs/messages.ts +39 -0
  237. package/src/sqlite-opfs/multi_map.ts +74 -0
  238. package/src/sqlite-opfs/pool_integrity.ts +327 -0
  239. package/src/sqlite-opfs/pool_lock.ts +62 -0
  240. package/src/sqlite-opfs/set.ts +29 -0
  241. package/src/sqlite-opfs/singleton.ts +48 -0
  242. package/src/sqlite-opfs/store.ts +331 -0
  243. package/src/sqlite-opfs/worker.ts +259 -0
  244. package/src/stores/index.ts +3 -0
  245. package/src/stores/l2_tips_store.ts +67 -0
  246. package/src/utils.ts +98 -0
@@ -0,0 +1,315 @@
1
+ import {
2
+ dedupeSortedArray,
3
+ findInSortedArray,
4
+ findIndexInSortedArray,
5
+ insertIntoSortedArray,
6
+ merge,
7
+ removeAnyOf,
8
+ removeFromSortedArray,
9
+ } from '@aztec-labs/foundation/array';
10
+
11
+ import { type Batch, Database, LMDBMessageType } from './message.js';
12
+ import { ReadTransaction } from './read_transaction.js';
13
+ import { keyCmp, singleKeyCmp } from './utils.js';
14
+
15
+ export class WriteTransaction extends ReadTransaction {
16
+ // exposed for tests
17
+ public readonly dataBatch: Batch = {
18
+ addEntries: [],
19
+ removeEntries: [],
20
+ };
21
+ public readonly indexBatch: Batch = {
22
+ addEntries: [],
23
+ removeEntries: [],
24
+ };
25
+
26
+ set(key: Uint8Array, value: Uint8Array): Promise<void> {
27
+ this.assertIsOpen();
28
+
29
+ const addEntry = findInSortedArray(this.dataBatch.addEntries, key, singleKeyCmp);
30
+ if (!addEntry) {
31
+ insertIntoSortedArray(this.dataBatch.addEntries, [key, [value]], keyCmp);
32
+ } else {
33
+ addEntry[1] = [value];
34
+ }
35
+
36
+ const removeEntryIndex = findIndexInSortedArray(this.dataBatch.removeEntries, key, singleKeyCmp);
37
+ if (removeEntryIndex > -1) {
38
+ this.dataBatch.removeEntries.splice(removeEntryIndex, 1);
39
+ }
40
+
41
+ return Promise.resolve();
42
+ }
43
+
44
+ remove(key: Uint8Array): Promise<void> {
45
+ const removeEntryIndex = findIndexInSortedArray(this.dataBatch.removeEntries, key, singleKeyCmp);
46
+ if (removeEntryIndex === -1) {
47
+ insertIntoSortedArray(this.dataBatch.removeEntries, [key, null], keyCmp);
48
+ }
49
+
50
+ const addEntryIndex = findIndexInSortedArray(this.dataBatch.addEntries, key, singleKeyCmp);
51
+ if (addEntryIndex > -1) {
52
+ this.dataBatch.addEntries.splice(addEntryIndex, 1);
53
+ }
54
+
55
+ return Promise.resolve();
56
+ }
57
+
58
+ public override async get(key: Buffer): Promise<Uint8Array | undefined> {
59
+ this.assertIsOpen();
60
+
61
+ const addEntry = findInSortedArray(this.dataBatch.addEntries, key, singleKeyCmp);
62
+ if (addEntry) {
63
+ return addEntry[1][0];
64
+ }
65
+ const removeEntryIdx = findIndexInSortedArray(this.dataBatch.removeEntries, key, singleKeyCmp);
66
+ if (removeEntryIdx > -1) {
67
+ return undefined;
68
+ }
69
+
70
+ return await super.get(key);
71
+ }
72
+
73
+ setIndex(key: Buffer, ...values: Buffer[]): Promise<void> {
74
+ this.assertIsOpen();
75
+
76
+ const addEntries = findInSortedArray(this.indexBatch.addEntries, key, singleKeyCmp);
77
+ const removeEntries = findInSortedArray(this.indexBatch.removeEntries, key, singleKeyCmp);
78
+
79
+ if (removeEntries) {
80
+ if (removeEntries[1]) {
81
+ // check if we were deleting these values and update
82
+ removeAnyOf(removeEntries[1], values, Buffer.compare);
83
+ }
84
+
85
+ if (!removeEntries[1] || removeEntries[1].length === 0) {
86
+ // either we were deleting the entire key previously
87
+ // or after cleaning up duplicates, we don't have anything else to delete
88
+ removeFromSortedArray(this.indexBatch.removeEntries, removeEntries, keyCmp);
89
+ }
90
+ }
91
+
92
+ if (addEntries) {
93
+ merge(addEntries[1], values, Buffer.compare);
94
+ dedupeSortedArray(addEntries[1], Buffer.compare);
95
+ } else {
96
+ insertIntoSortedArray(this.indexBatch.addEntries, [key, values], keyCmp);
97
+ }
98
+
99
+ return Promise.resolve();
100
+ }
101
+
102
+ removeIndex(key: Buffer, ...values: Buffer[]): Promise<void> {
103
+ this.assertIsOpen();
104
+
105
+ const addEntries = findInSortedArray(this.indexBatch.addEntries, key, singleKeyCmp);
106
+ const removeEntries = findInSortedArray(this.indexBatch.removeEntries, key, singleKeyCmp);
107
+
108
+ if (values.length === 0) {
109
+ // special case, we're deleting the entire key
110
+ if (addEntries) {
111
+ removeFromSortedArray(this.indexBatch.addEntries, addEntries, keyCmp);
112
+ }
113
+
114
+ if (removeEntries) {
115
+ removeEntries[1] = null;
116
+ } else {
117
+ insertIntoSortedArray(this.indexBatch.removeEntries, [key, null], keyCmp);
118
+ }
119
+
120
+ return Promise.resolve();
121
+ }
122
+
123
+ if (addEntries) {
124
+ removeAnyOf(addEntries[1], values, Buffer.compare);
125
+ if (addEntries[1].length === 0) {
126
+ removeFromSortedArray(this.indexBatch.addEntries, addEntries, keyCmp);
127
+ }
128
+ }
129
+
130
+ if (removeEntries) {
131
+ removeEntries[1] ??= [];
132
+ merge(removeEntries[1], values, Buffer.compare);
133
+ dedupeSortedArray(removeEntries[1], Buffer.compare);
134
+ } else {
135
+ insertIntoSortedArray(this.indexBatch.removeEntries, [key, values], keyCmp);
136
+ }
137
+
138
+ return Promise.resolve();
139
+ }
140
+
141
+ public override async getIndex(key: Buffer): Promise<Uint8Array[]> {
142
+ this.assertIsOpen();
143
+
144
+ const removeEntries = findInSortedArray(this.indexBatch.removeEntries, key, singleKeyCmp);
145
+ if (removeEntries && removeEntries[1] === null) {
146
+ return [];
147
+ }
148
+
149
+ const addEntries = findInSortedArray(this.indexBatch.addEntries, key, singleKeyCmp);
150
+ const results = await super.getIndex(key);
151
+
152
+ if (addEntries) {
153
+ merge(results, addEntries[1], Buffer.compare);
154
+ dedupeSortedArray(results, Buffer.compare);
155
+ }
156
+
157
+ if (removeEntries && Array.isArray(removeEntries[1])) {
158
+ removeAnyOf(results, removeEntries[1], Buffer.compare);
159
+ }
160
+
161
+ return results;
162
+ }
163
+
164
+ public override async *iterate(
165
+ startKey: Uint8Array,
166
+ endKey?: Uint8Array,
167
+ reverse?: boolean,
168
+ limit?: number,
169
+ ): AsyncIterable<[Uint8Array, Uint8Array]> {
170
+ yield* this.#iterate(
171
+ super.iterate(startKey, endKey, reverse),
172
+ this.dataBatch,
173
+ startKey,
174
+ endKey,
175
+ reverse,
176
+ limit,
177
+ (committed, toAdd) => (toAdd.length > 0 ? toAdd[0] : committed),
178
+ vals => vals[0],
179
+ );
180
+ }
181
+
182
+ public override async *iterateIndex(
183
+ startKey: Uint8Array,
184
+ endKey?: Uint8Array,
185
+ reverse?: boolean,
186
+ limit?: number,
187
+ ): AsyncIterable<[Uint8Array, Uint8Array[]]> {
188
+ yield* this.#iterate(
189
+ super.iterateIndex(startKey, endKey, reverse),
190
+ this.indexBatch,
191
+ startKey,
192
+ endKey,
193
+ reverse,
194
+ limit,
195
+ (committed, toAdd, toRemove) => {
196
+ if (toAdd.length > 0) {
197
+ merge(committed, toAdd, Buffer.compare);
198
+ dedupeSortedArray(committed, Buffer.compare);
199
+ }
200
+ if (toRemove.length > 0) {
201
+ removeAnyOf(committed, toRemove, Buffer.compare);
202
+ }
203
+ return committed;
204
+ },
205
+ vals => vals,
206
+ );
207
+ }
208
+
209
+ async *#iterate<T>(
210
+ iterator: AsyncIterable<[Uint8Array, T]>,
211
+ batch: Batch,
212
+ startKey: Uint8Array,
213
+ endKey: Uint8Array | undefined,
214
+ reverse: boolean = false,
215
+ limit: number | undefined,
216
+ merge: (committed: T, toAdd: Uint8Array[], toRemove: Uint8Array[]) => T,
217
+ map: (vals: Uint8Array[]) => T,
218
+ ): AsyncIterable<[Uint8Array, T]> {
219
+ this.assertIsOpen();
220
+
221
+ // Snapshot both add and remove entries at the start of iteration to ensure consistency
222
+ const uncommittedEntries = [...batch.addEntries];
223
+ const removeEntries = [...batch.removeEntries];
224
+ // used to check we're in the right order when comparing between a key and uncommittedEntries
225
+ let cmpDirection = -1;
226
+ if (reverse) {
227
+ cmpDirection = 1;
228
+ uncommittedEntries.reverse();
229
+ }
230
+
231
+ let uncommittedEntriesIdx = 0;
232
+ while (uncommittedEntriesIdx < uncommittedEntries.length) {
233
+ const entry = uncommittedEntries[uncommittedEntriesIdx];
234
+ // go to the first key in our cache that would be captured by the iterator
235
+ if (Buffer.compare(entry[0], startKey) !== cmpDirection) {
236
+ break;
237
+ }
238
+ uncommittedEntriesIdx++;
239
+ }
240
+
241
+ let count = 0;
242
+ // helper to early return if we've reached our limit
243
+ const checkLimit = typeof limit === 'number' ? () => count < limit : () => true;
244
+ for await (const [key, values] of iterator) {
245
+ // yield every key that we have cached that's captured by the iterator
246
+ while (uncommittedEntriesIdx < uncommittedEntries.length && checkLimit()) {
247
+ const entry = uncommittedEntries[uncommittedEntriesIdx];
248
+ if (endKey && Buffer.compare(entry[0], endKey) !== cmpDirection) {
249
+ break;
250
+ }
251
+
252
+ if (Buffer.compare(entry[0], key) === cmpDirection) {
253
+ count++;
254
+ yield [entry[0], map(entry[1])];
255
+ } else {
256
+ break;
257
+ }
258
+ uncommittedEntriesIdx++;
259
+ }
260
+
261
+ if (!checkLimit()) {
262
+ // we reached the imposed `limit`
263
+ break;
264
+ }
265
+
266
+ const toRemove = findInSortedArray(removeEntries, key, singleKeyCmp);
267
+
268
+ // at this point we've either exhausted all uncommitted entries,
269
+ // we reached a key strictly greater/smaller than `key`
270
+ // or we found the key itself
271
+ // check if it's the key and use the uncommitted value
272
+ let toAdd: Uint8Array[] = [];
273
+ if (
274
+ uncommittedEntriesIdx < uncommittedEntries.length &&
275
+ Buffer.compare(uncommittedEntries[uncommittedEntriesIdx][0], key) === 0
276
+ ) {
277
+ toAdd = uncommittedEntries[uncommittedEntriesIdx][1];
278
+ uncommittedEntriesIdx++;
279
+ }
280
+
281
+ if (toRemove && !toRemove[1]) {
282
+ // we were told to delete this key entirely
283
+ continue;
284
+ } else {
285
+ const mergedValues = merge(values, toAdd, toRemove?.[1] ?? []);
286
+ if (mergedValues) {
287
+ count++;
288
+ yield [key, mergedValues];
289
+ }
290
+ }
291
+ }
292
+
293
+ // emit all the uncommitted data that would be captured by this iterator
294
+ while (uncommittedEntriesIdx < uncommittedEntries.length && checkLimit()) {
295
+ const entry = uncommittedEntries[uncommittedEntriesIdx];
296
+ if (endKey && Buffer.compare(entry[0], endKey) !== cmpDirection) {
297
+ break;
298
+ }
299
+ count++;
300
+ yield [entry[0], map(entry[1])];
301
+ uncommittedEntriesIdx++;
302
+ }
303
+ }
304
+
305
+ public async commit() {
306
+ this.assertIsOpen();
307
+ this.close();
308
+ await this.channel.sendMessage(LMDBMessageType.BATCH, {
309
+ batches: new Map([
310
+ [Database.DATA, this.dataBatch],
311
+ [Database.INDEX, this.indexBatch],
312
+ ]),
313
+ });
314
+ }
315
+ }
@@ -0,0 +1,124 @@
1
+ import { Encoder } from '#msgpackr';
2
+ import { toBufferKey } from '#ordered-binary';
3
+ import { hash } from 'ohash';
4
+
5
+ import type { AztecAsyncArray } from '../interfaces/array.js';
6
+ import type { Value } from '../interfaces/common.js';
7
+ import type { AztecSQLiteOPFSStore } from './store.js';
8
+
9
+ /**
10
+ * Persistent array backed by SQLite. Entries share a common `key` (the array name)
11
+ * and are ordered by `key_count`, which doubles as the 1-indexed slot number.
12
+ */
13
+ export class SQLiteOPFSAztecArray<T extends Value> implements AztecAsyncArray<T> {
14
+ readonly #name: string;
15
+ readonly #container: string;
16
+ readonly #encoder = new Encoder();
17
+
18
+ constructor(
19
+ private readonly store: AztecSQLiteOPFSStore,
20
+ name: string,
21
+ ) {
22
+ this.#name = name;
23
+ this.#container = `array:${name}`;
24
+ }
25
+
26
+ async lengthAsync(): Promise<number> {
27
+ const rows = await this.store.allAsync('SELECT COUNT(*) FROM data WHERE container = ? AND key = ?', [
28
+ this.#container,
29
+ this.#encodedKey(),
30
+ ]);
31
+ return Number(rows[0]?.[0] ?? 0);
32
+ }
33
+
34
+ async push(...vals: T[]): Promise<number> {
35
+ if (vals.length === 0) {
36
+ return this.lengthAsync();
37
+ }
38
+ return await this.store.transactionAsync(async () => {
39
+ let length = await this.lengthAsync();
40
+ for (const val of vals) {
41
+ await this.store.runAsync(
42
+ `INSERT INTO data (slot, container, key, key_count, hash, value)
43
+ VALUES (?, ?, ?, ?, ?, ?)`,
44
+ [this.#slot(length), this.#container, this.#encodedKey(), length + 1, hash(val), this.#encoder.pack(val)],
45
+ );
46
+ length += 1;
47
+ }
48
+ return length;
49
+ });
50
+ }
51
+
52
+ async pop(): Promise<T | undefined> {
53
+ return await this.store.transactionAsync(async () => {
54
+ const length = await this.lengthAsync();
55
+ if (length === 0) {
56
+ return undefined;
57
+ }
58
+ const slot = this.#slot(length - 1);
59
+ const rows = await this.store.allAsync('SELECT value FROM data WHERE slot = ? LIMIT 1', [slot]);
60
+ await this.store.runAsync('DELETE FROM data WHERE slot = ?', [slot]);
61
+ const raw = rows[0]?.[0];
62
+ return raw instanceof Uint8Array ? (this.#encoder.unpack(raw) as T) : undefined;
63
+ });
64
+ }
65
+
66
+ async atAsync(index: number): Promise<T | undefined> {
67
+ const length = await this.lengthAsync();
68
+ const resolved = index < 0 ? length + index : index;
69
+ if (resolved < 0 || resolved >= length) {
70
+ return undefined;
71
+ }
72
+ const rows = await this.store.allAsync('SELECT value FROM data WHERE slot = ? LIMIT 1', [this.#slot(resolved)]);
73
+ const raw = rows[0]?.[0];
74
+ return raw instanceof Uint8Array ? (this.#encoder.unpack(raw) as T) : undefined;
75
+ }
76
+
77
+ async setAt(index: number, val: T): Promise<boolean> {
78
+ return await this.store.transactionAsync(async () => {
79
+ const length = await this.lengthAsync();
80
+ const resolved = index < 0 ? length + index : index;
81
+ if (resolved < 0 || resolved >= length) {
82
+ return false;
83
+ }
84
+ await this.store.runAsync(
85
+ `INSERT OR REPLACE INTO data (slot, container, key, key_count, hash, value)
86
+ VALUES (?, ?, ?, ?, ?, ?)`,
87
+ [this.#slot(resolved), this.#container, this.#encodedKey(), resolved + 1, hash(val), this.#encoder.pack(val)],
88
+ );
89
+ return true;
90
+ });
91
+ }
92
+
93
+ async *entriesAsync(): AsyncIterableIterator<[number, T]> {
94
+ const rows = await this.store.allAsync(
95
+ 'SELECT key_count, value FROM data WHERE container = ? AND key = ? ORDER BY key_count ASC',
96
+ [this.#container, this.#encodedKey()],
97
+ );
98
+ for (const row of rows) {
99
+ const keyCount = Number(row[0]);
100
+ const raw = row[1];
101
+ if (raw instanceof Uint8Array) {
102
+ yield [keyCount - 1, this.#encoder.unpack(raw) as T];
103
+ }
104
+ }
105
+ }
106
+
107
+ async *valuesAsync(): AsyncIterableIterator<T> {
108
+ for await (const [, val] of this.entriesAsync()) {
109
+ yield val;
110
+ }
111
+ }
112
+
113
+ [Symbol.asyncIterator](): AsyncIterableIterator<T> {
114
+ return this.valuesAsync();
115
+ }
116
+
117
+ #encodedKey(): Buffer {
118
+ return toBufferKey([this.#name]);
119
+ }
120
+
121
+ #slot(index: number): string {
122
+ return `array:${this.#name}:slot:${index}`;
123
+ }
124
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Typed error surface for sqlite3mc-backed page-level encryption failures.
3
+ *
4
+ * Three concrete failure modes are surfaced:
5
+ *
6
+ * - `invalid_key_length`: caller-side pre-flight (key not 32 bytes).
7
+ * - `encryption_not_supported_for_ephemeral`: caller-side pre-flight (encryption was requested on an ephemeral
8
+ * `:memory:` store, which sqlite3mc does not support).
9
+ * - `decrypt_failed`: runtime failure raised when sqlite3mc cannot decode page 1 of an existing database. Covers
10
+ * both "wrong key supplied" and "no key supplied to an encrypted DB".
11
+ */
12
+ export type SqliteEncryptionErrorCode =
13
+ | 'invalid_key_length'
14
+ | 'encryption_not_supported_for_ephemeral'
15
+ | 'decrypt_failed';
16
+
17
+ /**
18
+ * Error thrown by sqlite-opfs when an encryption operation fails.
19
+ **/
20
+ export class SqliteEncryptionError extends Error {
21
+ readonly code: SqliteEncryptionErrorCode;
22
+
23
+ constructor(code: SqliteEncryptionErrorCode, message: string, opts?: { cause?: unknown }) {
24
+ super(message, opts?.cause !== undefined ? { cause: opts.cause } : undefined);
25
+ this.name = 'SqliteEncryptionError';
26
+ this.code = code;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Strings raised by sqlite3mc when page 1 cannot be decoded.
32
+ **/
33
+ const SQLITE3MC_DECRYPT_ERROR_PATTERNS: readonly RegExp[] = [
34
+ /file is not a database/i,
35
+ /file is encrypted or is not a database/i,
36
+ ];
37
+
38
+ /**
39
+ * Returns `true` if `message` matches one of the known sqlite3mc decrypt-failure
40
+ * strings.
41
+ **/
42
+ export function isDecryptFailureMessage(message: string): boolean {
43
+ return SQLITE3MC_DECRYPT_ERROR_PATTERNS.some(p => p.test(message));
44
+ }
45
+
46
+ /**
47
+ * Error thrown by sqlite-opfs when the on-disk database image is corrupt
48
+ * (`SQLITE_CORRUPT`, result code 11 — "database disk image is malformed").
49
+ *
50
+ * Distinct from {@link SqliteEncryptionError}: no key recovers a corrupt image,
51
+ * so there is nothing to retry. The only escape is to delete the store and start
52
+ * fresh, so consumers pattern-match on `instanceof SqliteCorruptionError` to wipe
53
+ * and reopen rather than surfacing a dead-end error.
54
+ **/
55
+ export class SqliteCorruptionError extends Error {
56
+ constructor(message: string, opts?: { cause?: unknown }) {
57
+ super(message, opts?.cause !== undefined ? { cause: opts.cause } : undefined);
58
+ this.name = 'SqliteCorruptionError';
59
+ }
60
+ }
61
+
62
+ /** Error thrown when another browser context already owns a store's OPFS pool. */
63
+ export class SqlitePoolBusyError extends Error {
64
+ constructor(public readonly poolDirectory: string) {
65
+ super(`SQLite-OPFS pool "${poolDirectory}" is already in use by another store instance`);
66
+ this.name = 'SqlitePoolBusyError';
67
+ }
68
+ }
69
+
70
+ /** Error thrown when the browser context does not expose the Web Locks API required by the OPFS SAH pool. */
71
+ export class SqliteWebLocksUnavailableError extends Error {
72
+ constructor() {
73
+ super('SQLite-OPFS requires the Web Locks API, but it is unavailable in this browser context');
74
+ this.name = 'SqliteWebLocksUnavailableError';
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Strings/codes raised by SQLite when a database image is corrupt. Kept disjoint
80
+ * from {@link SQLITE3MC_DECRYPT_ERROR_PATTERNS}: "file is not a database"
81
+ * (SQLITE_NOTADB) is the decrypt signal, whereas a malformed image is the
82
+ * genuinely-unrecoverable SQLITE_CORRUPT.
83
+ **/
84
+ const SQLITE_CORRUPTION_ERROR_PATTERNS: readonly RegExp[] = [
85
+ /database disk image is malformed/i,
86
+ /\bSQLITE_CORRUPT\b/i,
87
+ ];
88
+
89
+ /**
90
+ * Returns `true` if `message` matches one of the known SQLite corruption strings.
91
+ **/
92
+ export function isCorruptionMessage(message: string): boolean {
93
+ return SQLITE_CORRUPTION_ERROR_PATTERNS.some(p => p.test(message));
94
+ }
@@ -0,0 +1,27 @@
1
+ import { createLogger } from '@aztec-labs/foundation/log';
2
+
3
+ import { AztecSQLiteOPFSStore } from './store.js';
4
+
5
+ export { AztecSQLiteOPFSStore } from './store.js';
6
+ export {
7
+ SqliteCorruptionError,
8
+ SqliteEncryptionError,
9
+ SqlitePoolBusyError,
10
+ SqliteWebLocksUnavailableError,
11
+ } from './errors.js';
12
+ export type { SqliteEncryptionErrorCode } from './errors.js';
13
+ export { OPFS_POOL_DIR_PREFIX, deletePoolDirectory, deleteStore, listStores, storePoolDirectory } from './manage.js';
14
+
15
+ export function openTmpStore(ephemeral: boolean = false): Promise<AztecSQLiteOPFSStore> {
16
+ return AztecSQLiteOPFSStore.open(createLogger('kv-store:sqlite-opfs'), undefined, ephemeral);
17
+ }
18
+
19
+ /**
20
+ * Convenience helper for tests and consumers that want an encrypted sqlite-opfs
21
+ * store without dealing with the full `open()` parameter order. Key must be 32
22
+ * bytes. Creates a fresh persistent store (sqlite3mc does not support encryption
23
+ * on ephemeral `:memory:` databases) in an auto-generated OPFS directory.
24
+ */
25
+ export function openEncryptedStore(encryptionKey: Uint8Array, name?: string, poolDirectory?: string) {
26
+ return AztecSQLiteOPFSStore.open(createLogger('kv-store:sqlite-opfs'), name, false, poolDirectory, encryptionKey);
27
+ }