@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,177 @@
1
+ import type { Database, RangeOptions } from 'lmdb';
2
+
3
+ import type { Key, Range, Value } from '../interfaces/common.js';
4
+ import type { AztecAsyncMap, AztecMap } from '../interfaces/map.js';
5
+
6
+ /** The slot where a key-value entry would be stored */
7
+ type MapValueSlot<K extends Key | Buffer> = ['map', string, 'slot', K];
8
+
9
+ /**
10
+ * A map backed by LMDB.
11
+ */
12
+ export class LmdbAztecMap<K extends Key, V extends Value> implements AztecMap<K, V>, AztecAsyncMap<K, V> {
13
+ protected db: Database<[K, V], MapValueSlot<K>>;
14
+ protected name: string;
15
+
16
+ protected startSentinel: MapValueSlot<Buffer>;
17
+ protected endSentinel: MapValueSlot<Buffer>;
18
+
19
+ constructor(rootDb: Database, mapName: string) {
20
+ this.name = mapName;
21
+ this.db = rootDb as Database<[K, V], MapValueSlot<K>>;
22
+
23
+ // sentinels are used to define the start and end of the map
24
+ // with LMDB's key encoding, no _primitive value_ can be "less than" an empty buffer or greater than Byte 255
25
+ // these will be used later to answer range queries
26
+ this.startSentinel = ['map', this.name, 'slot', Buffer.from([])];
27
+ this.endSentinel = ['map', this.name, 'slot', Buffer.from([255])];
28
+ }
29
+
30
+ close(): Promise<void> {
31
+ return this.db.close();
32
+ }
33
+
34
+ get(key: K): V | undefined {
35
+ return this.db.get(this.slot(key))?.[1];
36
+ }
37
+
38
+ getAsync(key: K): Promise<V | undefined> {
39
+ return Promise.resolve(this.get(key));
40
+ }
41
+
42
+ has(key: K): boolean {
43
+ return this.db.doesExist(this.slot(key));
44
+ }
45
+
46
+ hasAsync(key: K): Promise<boolean> {
47
+ return Promise.resolve(this.has(key));
48
+ }
49
+
50
+ async set(key: K, val: V): Promise<void> {
51
+ await this.db.put(this.slot(key), [key, val]);
52
+ }
53
+
54
+ async setMany(entries: { key: K; value: V }[]): Promise<void> {
55
+ for (const { key, value } of entries) {
56
+ await this.set(key, value);
57
+ }
58
+ }
59
+
60
+ swap(key: K, fn: (val: V | undefined) => V): Promise<void> {
61
+ return this.db.childTransaction(() => {
62
+ const slot = this.slot(key);
63
+ const entry = this.db.get(slot);
64
+ void this.db.put(slot, [key, fn(entry?.[1])]);
65
+ });
66
+ }
67
+
68
+ setIfNotExists(key: K, val: V): Promise<boolean> {
69
+ const slot = this.slot(key);
70
+ return this.db.ifNoExists(slot, () => {
71
+ void this.db.put(slot, [key, val]);
72
+ });
73
+ }
74
+
75
+ async delete(key: K): Promise<void> {
76
+ await this.db.remove(this.slot(key));
77
+ }
78
+
79
+ *entries(range: Range<K> = {}): IterableIterator<[K, V]> {
80
+ const transaction = this.db.useReadTransaction();
81
+
82
+ try {
83
+ const { reverse = false, limit } = range;
84
+ // LMDB has a quirk where it expects start > end when reverse=true
85
+ // in that case, we need to swap the start and end sentinels
86
+ const start = reverse
87
+ ? range.end
88
+ ? this.slot(range.end)
89
+ : this.endSentinel
90
+ : range.start
91
+ ? this.slot(range.start)
92
+ : this.startSentinel;
93
+
94
+ const end = reverse
95
+ ? range.start
96
+ ? this.slot(range.start)
97
+ : this.startSentinel
98
+ : range.end
99
+ ? this.slot(range.end)
100
+ : this.endSentinel;
101
+
102
+ const lmdbRange: RangeOptions = {
103
+ start,
104
+ end,
105
+ reverse,
106
+ limit,
107
+ transaction,
108
+ };
109
+
110
+ const iterator = this.db.getRange(lmdbRange);
111
+
112
+ for (const {
113
+ value: [key, value],
114
+ } of iterator) {
115
+ yield [key, value];
116
+ }
117
+ } finally {
118
+ transaction.done();
119
+ }
120
+ }
121
+
122
+ async *entriesAsync(range?: Range<K>): AsyncIterableIterator<[K, V]> {
123
+ for (const entry of this.entries(range)) {
124
+ yield entry;
125
+ }
126
+ }
127
+
128
+ *values(range: Range<K> = {}): IterableIterator<V> {
129
+ for (const [_, value] of this.entries(range)) {
130
+ yield value;
131
+ }
132
+ }
133
+
134
+ async *valuesAsync(range: Range<K> = {}): AsyncIterableIterator<V> {
135
+ for await (const [_, value] of this.entriesAsync(range)) {
136
+ yield value;
137
+ }
138
+ }
139
+
140
+ size(): number {
141
+ const iterator = this.db.getRange({ start: this.startSentinel, end: this.endSentinel });
142
+ return iterator.asArray.length;
143
+ }
144
+
145
+ sizeAsync(): Promise<number> {
146
+ return Promise.resolve(this.size());
147
+ }
148
+
149
+ *keys(range: Range<K> = {}): IterableIterator<K> {
150
+ for (const [key, _] of this.entries(range)) {
151
+ yield key;
152
+ }
153
+ }
154
+
155
+ async *keysAsync(range: Range<K> = {}): AsyncIterableIterator<K> {
156
+ for await (const [key, _] of this.entriesAsync(range)) {
157
+ yield key;
158
+ }
159
+ }
160
+
161
+ protected slot(key: K): MapValueSlot<K> {
162
+ return ['map', this.name, 'slot', key];
163
+ }
164
+
165
+ async clear(): Promise<void> {
166
+ const lmdbRange: RangeOptions = {
167
+ start: this.startSentinel,
168
+ end: this.endSentinel,
169
+ };
170
+
171
+ const iterator = this.db.getRange(lmdbRange);
172
+
173
+ for (const { key } of iterator) {
174
+ await this.db.remove(key);
175
+ }
176
+ }
177
+ }
@@ -0,0 +1,51 @@
1
+ import type { Key, Value } from '../interfaces/common.js';
2
+ import type { AztecAsyncMultiMap, AztecMultiMap } from '../interfaces/multi_map.js';
3
+ import { LmdbAztecMap } from './map.js';
4
+
5
+ /**
6
+ * A map backed by LMDB.
7
+ */
8
+ export class LmdbAztecMultiMap<K extends Key, V extends Value>
9
+ extends LmdbAztecMap<K, V>
10
+ implements AztecMultiMap<K, V>, AztecAsyncMultiMap<K, V>
11
+ {
12
+ *getValues(key: K): IterableIterator<V> {
13
+ const transaction = this.db.useReadTransaction();
14
+ try {
15
+ const values = this.db.getValues(this.slot(key), {
16
+ transaction,
17
+ });
18
+ for (const value of values) {
19
+ yield value?.[1];
20
+ }
21
+ } finally {
22
+ transaction.done();
23
+ }
24
+ }
25
+
26
+ async *getValuesAsync(key: K): AsyncIterableIterator<V> {
27
+ for (const value of this.getValues(key)) {
28
+ yield value;
29
+ }
30
+ }
31
+
32
+ getValueCountAsync(key: K): Promise<number> {
33
+ const transaction = this.db.useReadTransaction();
34
+ try {
35
+ const values = this.db.getValues(this.slot(key), {
36
+ transaction,
37
+ });
38
+ let count = 0;
39
+ for (const _ of values) {
40
+ count++;
41
+ }
42
+ return Promise.resolve(count);
43
+ } finally {
44
+ transaction.done();
45
+ }
46
+ }
47
+
48
+ async deleteValue(key: K, val: V): Promise<void> {
49
+ await this.db.remove(this.slot(key), [key, val]);
50
+ }
51
+ }
@@ -0,0 +1,45 @@
1
+ import type { Database } from 'lmdb';
2
+
3
+ import type { Key, Range } from '../interfaces/common.js';
4
+ import type { AztecAsyncSet, AztecSet } from '../interfaces/set.js';
5
+ import { LmdbAztecMap } from './map.js';
6
+
7
+ /**
8
+ * A set backed by LMDB.
9
+ */
10
+ export class LmdbAztecSet<K extends Key> implements AztecSet<K>, AztecAsyncSet<K> {
11
+ private map: LmdbAztecMap<K, boolean>;
12
+ constructor(rootDb: Database, mapName: string) {
13
+ this.map = new LmdbAztecMap(rootDb, mapName);
14
+ }
15
+
16
+ close(): Promise<void> {
17
+ return this.map.close();
18
+ }
19
+
20
+ has(key: K): boolean {
21
+ return this.map.has(key);
22
+ }
23
+
24
+ hasAsync(key: K): Promise<boolean> {
25
+ return Promise.resolve(this.has(key));
26
+ }
27
+
28
+ add(key: K): Promise<void> {
29
+ return this.map.set(key, true);
30
+ }
31
+
32
+ delete(key: K): Promise<void> {
33
+ return this.map.delete(key);
34
+ }
35
+
36
+ entries(range: Range<K> = {}): IterableIterator<K> {
37
+ return this.map.keys(range);
38
+ }
39
+
40
+ async *entriesAsync(range: Range<K> = {}): AsyncIterableIterator<K> {
41
+ for await (const key of this.map.keysAsync(range)) {
42
+ yield key;
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,35 @@
1
+ import type { Database, Key } from 'lmdb';
2
+
3
+ import type { AztecAsyncSingleton, AztecSingleton } from '../interfaces/singleton.js';
4
+
5
+ /** The slot where this singleton will store its value */
6
+ type ValueSlot = ['singleton', string, 'value'];
7
+
8
+ /**
9
+ * Stores a single value in LMDB.
10
+ */
11
+ export class LmdbAztecSingleton<T> implements AztecSingleton<T>, AztecAsyncSingleton<T> {
12
+ #db: Database<T, ValueSlot>;
13
+ #slot: ValueSlot;
14
+
15
+ constructor(db: Database<unknown, Key>, name: string) {
16
+ this.#db = db as Database<T, ValueSlot>;
17
+ this.#slot = ['singleton', name, 'value'];
18
+ }
19
+
20
+ get(): T | undefined {
21
+ return this.#db.get(this.#slot);
22
+ }
23
+
24
+ getAsync(): Promise<T | undefined> {
25
+ return Promise.resolve(this.get());
26
+ }
27
+
28
+ set(val: T): Promise<boolean> {
29
+ return this.#db.put(this.#slot, val);
30
+ }
31
+
32
+ delete(): Promise<boolean> {
33
+ return this.#db.remove(this.#slot);
34
+ }
35
+ }
@@ -0,0 +1,242 @@
1
+ import { randomBytes } from '@aztec-labs/foundation/crypto/random';
2
+ import { createLogger } from '@aztec-labs/foundation/log';
3
+ import { promises as fs, mkdirSync } from 'fs';
4
+ import { type Database, type RootDatabase, open } from 'lmdb';
5
+ import { tmpdir } from 'os';
6
+ import { join } from 'path';
7
+
8
+ import type { AztecArray, AztecAsyncArray } from '../interfaces/array.js';
9
+ import type { Key, StoreSize, Value } from '../interfaces/common.js';
10
+ import type { AztecAsyncCounter, AztecCounter } from '../interfaces/counter.js';
11
+ import type { AztecAsyncMap, AztecMap } from '../interfaces/map.js';
12
+ import type { AztecAsyncMultiMap, AztecMultiMap } from '../interfaces/multi_map.js';
13
+ import type { AztecAsyncSet, AztecSet } from '../interfaces/set.js';
14
+ import type { AztecAsyncSingleton, AztecSingleton } from '../interfaces/singleton.js';
15
+ import type { AztecAsyncKVStore, AztecKVStore } from '../interfaces/store.js';
16
+ import { LmdbAztecArray } from './array.js';
17
+ import { LmdbAztecCounter } from './counter.js';
18
+ import { LmdbAztecMap } from './map.js';
19
+ import { LmdbAztecMultiMap } from './multi_map.js';
20
+ import { LmdbAztecSet } from './set.js';
21
+ import { LmdbAztecSingleton } from './singleton.js';
22
+
23
+ /**
24
+ * A key-value store backed by LMDB.
25
+ */
26
+ export class AztecLmdbStore implements AztecKVStore, AztecAsyncKVStore {
27
+ syncGetters = true as const;
28
+
29
+ #rootDb: RootDatabase;
30
+ #data: Database<unknown, Key>;
31
+ #multiMapData: Database<unknown, Key>;
32
+ #log = createLogger('kv-store:lmdb');
33
+
34
+ constructor(
35
+ rootDb: RootDatabase,
36
+ public readonly isEphemeral: boolean,
37
+ private path: string,
38
+ ) {
39
+ this.#rootDb = rootDb;
40
+
41
+ // big bucket to store all the data
42
+ this.#data = rootDb.openDB('data', {
43
+ encoding: 'msgpack',
44
+ keyEncoding: 'ordered-binary',
45
+ });
46
+
47
+ this.#multiMapData = rootDb.openDB('data_dup_sort', {
48
+ encoding: 'ordered-binary',
49
+ keyEncoding: 'ordered-binary',
50
+ dupSort: true,
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Creates a new AztecKVStore backed by LMDB. The path to the database is optional. If not provided,
56
+ * the database will be stored in a temporary location and be deleted when the process exists.
57
+ *
58
+ * The `rollupAddress` passed is checked against what is stored in the database. If they do not match,
59
+ * the database is cleared before returning the store. This way data is not accidentally shared between
60
+ * different rollup instances.
61
+ *
62
+ * @param path - A path on the disk to store the database. Optional
63
+ * @param ephemeral - true if the store should only exist in memory and not automatically be flushed to disk. Optional
64
+ * @param log - A logger to use. Optional
65
+ * @returns The store
66
+ */
67
+ static open(
68
+ path?: string,
69
+ mapSizeKb = 1 * 1024 * 1024, // defaults to 1 GB map size
70
+ ephemeral: boolean = false,
71
+ log = createLogger('kv-store:lmdb'),
72
+ ): AztecLmdbStore {
73
+ const dbPath = path ?? join(tmpdir(), randomBytes(8).toString('hex'));
74
+ mkdirSync(dbPath, { recursive: true });
75
+ const mapSize = 1024 * mapSizeKb;
76
+ log.debug(`Opening LMDB database at ${path || 'temporary location'} with map size ${mapSize}`);
77
+ const rootDb = open({ path: dbPath, noSync: ephemeral, mapSize });
78
+ return new AztecLmdbStore(rootDb, ephemeral, dbPath);
79
+ }
80
+
81
+ /**
82
+ * Creates a new AztecMap in the store.
83
+ * @param name - Name of the map
84
+ * @returns A new AztecMap
85
+ */
86
+ openMap<K extends Key, V extends Value>(name: string): AztecMap<K, V> & AztecAsyncMap<K, V> {
87
+ return new LmdbAztecMap(this.#data, name);
88
+ }
89
+
90
+ /**
91
+ * Creates a new AztecSet in the store.
92
+ * @param name - Name of the set
93
+ * @returns A new AztecSet
94
+ */
95
+ openSet<K extends Key>(name: string): AztecSet<K> & AztecAsyncSet<K> {
96
+ return new LmdbAztecSet(this.#data, name);
97
+ }
98
+
99
+ /**
100
+ * Creates a new AztecMultiMap in the store. A multi-map stores multiple values for a single key automatically.
101
+ * @param name - Name of the map
102
+ * @returns A new AztecMultiMap
103
+ */
104
+ openMultiMap<K extends Key, V extends Value>(name: string): AztecMultiMap<K, V> & AztecAsyncMultiMap<K, V> {
105
+ return new LmdbAztecMultiMap(this.#multiMapData, name);
106
+ }
107
+
108
+ openCounter<K extends Key>(name: string): AztecCounter<K> & AztecAsyncCounter<K> {
109
+ return new LmdbAztecCounter(this.#data, name);
110
+ }
111
+
112
+ /**
113
+ * Creates a new AztecArray in the store.
114
+ * @param name - Name of the array
115
+ * @returns A new AztecArray
116
+ */
117
+ openArray<T extends Value>(name: string): AztecArray<T> & AztecAsyncArray<T> {
118
+ return new LmdbAztecArray(this.#data, name);
119
+ }
120
+
121
+ /**
122
+ * Creates a new AztecSingleton in the store.
123
+ * @param name - Name of the singleton
124
+ * @returns A new AztecSingleton
125
+ */
126
+ openSingleton<T>(name: string): AztecSingleton<T> & AztecAsyncSingleton<T> {
127
+ return new LmdbAztecSingleton(this.#data, name);
128
+ }
129
+
130
+ /**
131
+ * Runs a callback in a transaction.
132
+ * @param callback - Function to execute in a transaction
133
+ * @returns A promise that resolves to the return value of the callback
134
+ */
135
+ transaction<T>(callback: () => T): Promise<T> {
136
+ return this.#rootDb.transaction(callback);
137
+ }
138
+
139
+ /**
140
+ * Runs a callback in a transaction.
141
+ * @param callback - Function to execute in a transaction
142
+ * @returns A promise that resolves to the return value of the callback
143
+ */
144
+ async transactionAsync<T>(callback: () => Promise<T>): Promise<T> {
145
+ return await this.#rootDb.transaction(callback);
146
+ }
147
+
148
+ /**
149
+ * Clears all entries in the store & sub DBs atomically within a single transaction.
150
+ */
151
+ async clear() {
152
+ await this.#rootDb.transaction(async () => {
153
+ await this.#data.clearAsync();
154
+ await this.#multiMapData.clearAsync();
155
+ await this.#rootDb.clearAsync();
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Drops the database & sub DBs atomically within a single transaction.
161
+ */
162
+ async drop() {
163
+ await this.#rootDb.transaction(async () => {
164
+ await this.#data.drop();
165
+ await this.#multiMapData.drop();
166
+ await this.#rootDb.drop();
167
+ });
168
+ }
169
+
170
+ /**
171
+ * Close the database. Note, once this is closed we can no longer interact with the DB.
172
+ */
173
+ async close() {
174
+ await this.#data.close();
175
+ await this.#multiMapData.close();
176
+ await this.#rootDb.close();
177
+ }
178
+
179
+ /** Deletes this store and removes the database files from disk */
180
+ async delete() {
181
+ await this.drop();
182
+ await this.close();
183
+ if (this.path) {
184
+ await fs.rm(this.path, { recursive: true, force: true, maxRetries: 3 });
185
+ this.#log.verbose(`Deleted database files at ${this.path}`);
186
+ }
187
+ }
188
+
189
+ estimateSize(): Promise<StoreSize> {
190
+ const stats = this.#rootDb.getStats();
191
+ // The 'mapSize' is the total amount of virtual address space allocated to the DB (effectively the maximum possible size)
192
+ // http://www.lmdb.tech/doc/group__mdb.html#a4bde3c8b676457342cba2fe27aed5fbd
193
+ let mapSize = 0;
194
+ if ('mapSize' in stats && typeof stats.mapSize === 'number') {
195
+ mapSize = stats.mapSize;
196
+ }
197
+ let physicalFileSize = 0;
198
+ if ('physicalFileSize' in stats && typeof stats.physicalFileSize === 'number') {
199
+ physicalFileSize = stats.physicalFileSize;
200
+ }
201
+ const dataResult = this.estimateSubDBSize(this.#data);
202
+ const multiResult = this.estimateSubDBSize(this.#multiMapData);
203
+ return Promise.resolve({
204
+ mappingSize: mapSize,
205
+ physicalFileSize: physicalFileSize,
206
+ actualSize: dataResult.actualSize + multiResult.actualSize,
207
+ numItems: dataResult.numItems + multiResult.numItems,
208
+ });
209
+ }
210
+
211
+ private estimateSubDBSize(db: Database<unknown, Key>): { actualSize: number; numItems: number } {
212
+ const stats = db.getStats();
213
+ let actualSize = 0;
214
+ let numItems = 0;
215
+ // This is the total number of key/value pairs present in the DB
216
+ if ('entryCount' in stats && typeof stats.entryCount === 'number') {
217
+ numItems = stats.entryCount;
218
+ }
219
+ // The closest value we can get to the actual size of the database is the number of consumed pages * the page size
220
+ if (
221
+ 'treeBranchPageCount' in stats &&
222
+ typeof stats.treeBranchPageCount === 'number' &&
223
+ 'treeLeafPageCount' in stats &&
224
+ typeof stats.treeLeafPageCount === 'number' &&
225
+ 'overflowPages' in stats &&
226
+ typeof stats.overflowPages === 'number' &&
227
+ 'pageSize' in stats &&
228
+ typeof stats.pageSize === 'number'
229
+ ) {
230
+ const branchPages = stats.treeBranchPageCount;
231
+ const leafPages = stats.treeLeafPageCount;
232
+ const overflowPages = stats.overflowPages;
233
+ const pageSize = stats.pageSize;
234
+ actualSize = (branchPages + leafPages + overflowPages) * pageSize;
235
+ }
236
+ return { actualSize, numItems };
237
+ }
238
+
239
+ backupTo(_dstPath: string, _compact?: boolean): Promise<void> {
240
+ throw new Error('Method not implemented.');
241
+ }
242
+ }
@@ -0,0 +1,120 @@
1
+ import { Encoder } from 'msgpackr/pack';
2
+
3
+ import type { AztecAsyncArray } from '../interfaces/array.js';
4
+ import type { Value } from '../interfaces/common.js';
5
+ import type { AztecAsyncSingleton } from '../interfaces/singleton.js';
6
+ import type { ReadTransaction } from './read_transaction.js';
7
+ import type { AztecLMDBStoreV2 } from './store.js';
8
+ import { execInReadTx, execInWriteTx } from './tx-helpers.js';
9
+ import { deserializeKey, serializeKey } from './utils.js';
10
+
11
+ export class LMDBArray<T extends Value> implements AztecAsyncArray<T> {
12
+ private length: AztecAsyncSingleton<number>;
13
+ private encoder = new Encoder();
14
+ private prefix: string;
15
+
16
+ constructor(
17
+ private store: AztecLMDBStoreV2,
18
+ name: string,
19
+ ) {
20
+ this.length = store.openSingleton(name + ':length');
21
+ this.prefix = `array:${name}`;
22
+ }
23
+
24
+ pop(): Promise<T | undefined> {
25
+ return execInWriteTx(this.store, async tx => {
26
+ const length = await this.lengthAsync();
27
+ if (length === 0) {
28
+ return;
29
+ }
30
+
31
+ const val = await tx.get(serializeKey(this.prefix, length - 1));
32
+ await tx.remove(serializeKey(this.prefix, length - 1));
33
+
34
+ await this.length.set(length - 1);
35
+
36
+ return val ? this.encoder.unpack(val) : undefined;
37
+ });
38
+ }
39
+
40
+ push(...vals: T[]): Promise<number> {
41
+ return execInWriteTx(this.store, async tx => {
42
+ let length = await this.lengthAsync();
43
+ for (const val of vals) {
44
+ await tx.set(serializeKey(this.prefix, length++), this.encoder.pack(val));
45
+ }
46
+ await this.length.set(length);
47
+ return length;
48
+ });
49
+ }
50
+
51
+ setAt(index: number, val: T): Promise<boolean> {
52
+ return execInWriteTx(this.store, async tx => {
53
+ const length = await this.lengthAsync();
54
+ if (index < 0) {
55
+ index += length;
56
+ }
57
+ if (index < 0 || index >= length) {
58
+ return false;
59
+ }
60
+ await tx.set(serializeKey(this.prefix, index), this.encoder.pack(val));
61
+ return true;
62
+ });
63
+ }
64
+
65
+ atAsync(index: number): Promise<T | undefined> {
66
+ return execInReadTx(this.store, async tx => {
67
+ const length = await this.lengthAsync();
68
+ if (index < 0) {
69
+ index += length;
70
+ }
71
+ if (index < 0 || index >= length) {
72
+ return undefined;
73
+ }
74
+
75
+ const val = await tx.get(serializeKey(this.prefix, index));
76
+ return val ? this.encoder.unpack(val) : undefined;
77
+ });
78
+ }
79
+
80
+ async lengthAsync(): Promise<number> {
81
+ return (await this.length.getAsync()) ?? 0;
82
+ }
83
+
84
+ async *entriesAsync(): AsyncIterableIterator<[number, T]> {
85
+ // pin array length so that pushes don't affect iteration
86
+ const length = await this.lengthAsync();
87
+ if (length === 0) {
88
+ return;
89
+ }
90
+
91
+ let tx: ReadTransaction | undefined = this.store.getCurrentWriteTx();
92
+ const shouldClose = !tx;
93
+ tx ??= this.store.getReadTx();
94
+
95
+ try {
96
+ for await (const [key, val] of tx.iterate(serializeKey(this.prefix, 0), undefined, false, length)) {
97
+ const deserializedKey = deserializeKey<number>(this.prefix, key);
98
+ // if pops happened while iterating we may have read too much. Terminate early
99
+ if (deserializedKey === false) {
100
+ break;
101
+ }
102
+ yield [deserializedKey, this.encoder.unpack(val)];
103
+ }
104
+ } finally {
105
+ if (shouldClose) {
106
+ tx.close();
107
+ }
108
+ }
109
+ }
110
+
111
+ async *valuesAsync(): AsyncIterableIterator<T> {
112
+ for await (const [_, value] of this.entriesAsync()) {
113
+ yield value;
114
+ }
115
+ }
116
+
117
+ [Symbol.asyncIterator](): AsyncIterableIterator<T> {
118
+ return this.valuesAsync();
119
+ }
120
+ }