index.jsView |
---|
3 | 3 … | const TextEncoder = require('text-encoding').TextEncoder |
4 | 4 … | |
5 | 5 … | const encoder = new TextEncoder('utf-8') |
6 | 6 … | |
7 | | -const EXTENSION = 0 |
8 | | -const BRANCH = 1 |
9 | | -const VALUE = 2 |
| 7 … | +const LBRANCH = 0 |
| 8 … | +const RBRANCH = 1 |
| 9 … | +const EXTENSION = 2 |
| 10 … | +const VALUE = 3 |
10 | 11 … | |
11 | 12 … | const RadixTree = module.exports = class RadixTree { |
| 13 … | + |
| 14 … | + * @param opts |
| 15 … | + * @param opts.root {object} a merkle root to a radix tree. If none, RadixTree will create an new root. |
| 16 … | + * @param opts.graph {object} an instance of [ipld-graph-builder](https: |
| 17 … | + * @param opts.dag {object} an instance if [ipfs.dag](https: |
| 18 … | + */ |
12 | 19 … | constructor (opts) { |
13 | 20 … | this.root = opts.root || {'/': undefined} |
14 | 21 … | this.graph = opts.graph || new Graph(opts.dag) |
15 | 22 … | } |
16 | 23 … | |
| 24 … | + |
| 25 … | + * returns an Uint1Array constructir which is used to repersent keys |
| 26 … | + * @returns {object} |
| 27 … | + */ |
17 | 28 … | static get ArrayConstructor () { |
18 | 29 … | return Uint1Array |
19 | 30 … | } |
20 | 31 … | |
| 32 … | + |
| 33 … | + * converts a TypedArray or Buffer to an Uint1Array |
| 34 … | + * @param {TypedArray} array - the array to convert |
| 35 … | + * @returns {TypedArray} |
| 36 … | + */ |
21 | 37 … | static toTypedArray (array) { |
22 | 38 … | return new RadixTree.ArrayConstructor(new Uint8Array(array).buffer) |
23 | 39 … | } |
24 | 40 … | |
27 | 43 … | let root = this.root |
28 | 44 … | let parent |
29 | 45 … | while (1) { |
30 | 46 … | |
31 | | - await this.graph.get(root, 0, true) |
32 | | - if (hasExtension(root)) { |
| 47 … | + const exNode = await this.graph.get(root, EXTENSION, true) |
| 48 … | + if (exNode) { |
33 | 49 … | let extensionIndex = 0 |
34 | 50 … | const extensionLen = getExLength(root) |
35 | 51 … | const extension = getExtension(root) |
36 | 52 … | let subKey |
86 | 102 … | index: index |
87 | 103 … | } |
88 | 104 … | } |
89 | 105 … | |
| 106 … | + |
| 107 … | + * gets a value given a key |
| 108 … | + * @param {*} key |
| 109 … | + * @return {Promise} |
| 110 … | + */ |
90 | 111 … | async get (key) { |
91 | 112 … | key = RadixTree.formatKey(key) |
92 | 113 … | const result = await this._get(key) |
93 | 114 … | return result.value |
94 | 115 … | } |
95 | 116 … | |
| 117 … | + |
| 118 … | + * stores a value at a given key |
| 119 … | + * @param {*} key |
| 120 … | + * @return {Promise} |
| 121 … | + */ |
96 | 122 … | async set (key, value) { |
97 | 123 … | key = RadixTree.formatKey(key) |
98 | 124 … | |
99 | 125 … | if (value.length > 32) { |
129 | 155 … | const extension = key.subarray(result.index + 1, key.length) |
130 | 156 … | const newNode = createNode(extension, [], value) |
131 | 157 … | const rootBranch = getBranch(root) |
132 | 158 … | rootBranch[keySegment] = newNode |
| 159 … | + setBranch(root, rootBranch) |
133 | 160 … | } else { |
134 | 161 … | setValue(root, value) |
135 | 162 … | } |
136 | 163 … | } |
137 | 164 … | } |
138 | 165 … | } |
139 | 166 … | |
| 167 … | + |
| 168 … | + * deletes a value at a given key |
| 169 … | + * @param {*} key |
| 170 … | + * @return {Promise} |
| 171 … | + */ |
140 | 172 … | async delete (key) { |
141 | 173 … | key = RadixTree.formatKey(key) |
142 | 174 … | const results = await this._get(key) |
143 | | - if (results.value) { |
| 175 … | + if (results.value !== undefined) { |
144 | 176 … | const root = results.root |
145 | 177 … | const parent = results.parent |
146 | 178 … | |
147 | 179 … | deleteValue(root) |
148 | 180 … | |
149 | | - if (getBranch(root).length) { |
| 181 … | + const branch = getBranch(root) |
| 182 … | + if (branch.some(el => el !== undefined)) { |
150 | 183 … | joinNodes(root) |
151 | 184 … | } else { |
152 | 185 … | if (!parent) { |
153 | 186 … | root['/'] = undefined |
188 | 221 … | } |
189 | 222 … | } |
190 | 223 … | } |
191 | 224 … | |
| 225 … | + |
| 226 … | + * creates a merkle root for the current tree |
| 227 … | + * @returns {Promise} |
| 228 … | + */ |
192 | 229 … | createMerkleRoot () { |
193 | 230 … | return this.graph.flush(this.root) |
194 | 231 … | } |
195 | 232 … | |
196 | 233 … | static formatKey (key) { |
197 | 234 … | if (typeof key === 'string') { |
198 | 235 … | key = encoder.encode(key) |
199 | 236 … | } |
200 | | - return new RadixTree.ArrayConstructor(key.buffer) |
| 237 … | + |
| 238 … | + if (key.constructor !== RadixTree.ArrayConstructor) { |
| 239 … | + return new RadixTree.ArrayConstructor(key.buffer) |
| 240 … | + } else { |
| 241 … | + return key |
| 242 … | + } |
201 | 243 … | } |
202 | 244 … | } |
203 | 245 … | |
| 246 … | +function createNode (ex, branch, value) { |
| 247 … | + const node = { |
| 248 … | + '/': [] |
| 249 … | + } |
| 250 … | + |
| 251 … | + setValue(node, value) |
| 252 … | + setExtension(node, ex) |
| 253 … | + setBranch(node, branch) |
| 254 … | + |
| 255 … | + return node |
| 256 … | +} |
| 257 … | + |
| 258 … | + |
204 | 259 … | function setBranch (node, branch) { |
205 | | - node['/'][BRANCH] = branch |
| 260 … | + node['/'][LBRANCH] = branch[0] |
| 261 … | + node['/'][RBRANCH] = branch[1] |
206 | 262 … | } |
207 | 263 … | |
208 | 264 … | function getBranch (node) { |
209 | | - return node['/'][BRANCH] |
| 265 … | + return node['/'].slice(LBRANCH, 2) |
210 | 266 … | } |
211 | 267 … | |
212 | 268 … | function getValue (node) { |
213 | 269 … | return node['/'][VALUE] |
216 | 272 … | function deleteValue (node) { |
217 | 273 … | node['/'].pop() |
218 | 274 … | } |
219 | 275 … | |
220 | | -function hasExtension (node) { |
221 | | - return node['/'][EXTENSION].length === 2 |
222 | | -} |
223 | | - |
224 | 276 … | function getExtension (node) { |
225 | | - return RadixTree.toTypedArray(node['/'][EXTENSION][1]).subarray(0, getExLength(node)) |
| 277 … | + if (node['/'][EXTENSION]) { |
| 278 … | + return RadixTree.toTypedArray(node['/'][EXTENSION][1]).subarray(0, getExLength(node)) |
| 279 … | + } else { |
| 280 … | + return [] |
| 281 … | + } |
226 | 282 … | } |
227 | 283 … | |
228 | 284 … | function getExLength (node) { |
229 | 285 … | return node['/'][EXTENSION][0] |
232 | 288 … | function setExtension (node, ex) { |
233 | 289 … | if (ex && ex.length) { |
234 | 290 … | node['/'][EXTENSION] = [ex.length, new Buffer(ex.buffer)] |
235 | 291 … | } else { |
236 | | - node['/'][EXTENSION] = [] |
| 292 … | + if (getValue(node) === undefined && node['/'][EXTENSION] !== undefined) { |
| 293 … | + node['/'].pop() |
| 294 … | + } else if (node['/'][EXTENSION] !== undefined) { |
| 295 … | + node['/'][EXTENSION] = undefined |
| 296 … | + } |
237 | 297 … | } |
238 | 298 … | } |
239 | 299 … | |
240 | 300 … | function setValue (node, val) { |
241 | | - node['/'][VALUE] = val |
242 | | -} |
243 | | - |
244 | | -function createNode (ex, branch, value) { |
245 | | - if (ex && ex.length) { |
246 | | - ex = [ex.length, new Buffer(ex.buffer)] |
247 | | - } else { |
248 | | - ex = [] |
| 301 … | + if (val !== undefined) { |
| 302 … | + node['/'][VALUE] = val |
249 | 303 … | } |
250 | | - |
251 | | - const node = { |
252 | | - '/': [ex, branch] |
253 | | - } |
254 | | - |
255 | | - if (value !== undefined) { |
256 | | - node['/'].push(value) |
257 | | - } |
258 | | - return node |
259 | 304 … | } |