hyperbee2 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +156 -22
- package/lib/cache.js +7 -0
- package/lib/compat.js +47 -43
- package/lib/compression.js +163 -0
- package/lib/context.js +38 -9
- package/lib/diff.js +20 -10
- package/lib/encoding.js +132 -7
- package/lib/ranges.js +10 -5
- package/lib/tree.js +97 -48
- package/lib/write.js +239 -98
- package/package.json +4 -2
- package/spec/hyperschema/index.js +356 -47
- package/spec/hyperschema/schema.json +208 -5
package/index.js
CHANGED
|
@@ -6,15 +6,24 @@ const { ChangesStream } = require('./lib/changes.js')
|
|
|
6
6
|
const NodeCache = require('./lib/cache.js')
|
|
7
7
|
const WriteBatch = require('./lib/write.js')
|
|
8
8
|
const CoreContext = require('./lib/context.js')
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
Pointer,
|
|
11
|
+
KeyPointer,
|
|
12
|
+
ValuePointer,
|
|
13
|
+
TreeNode,
|
|
14
|
+
TreeNodePointer,
|
|
15
|
+
EMPTY
|
|
16
|
+
} = require('./lib/tree.js')
|
|
17
|
+
const { DeltaOp, DeltaCohort, OP_COHORT } = require('./lib/compression.js')
|
|
10
18
|
|
|
11
19
|
class Hyperbee {
|
|
12
20
|
constructor(store, options = {}) {
|
|
13
21
|
const {
|
|
22
|
+
t = 128, // legacy number for now, should be 128 now
|
|
14
23
|
key = null,
|
|
15
24
|
encryption = null,
|
|
16
25
|
core = key ? store.get(key) : store.get({ key, name: 'bee', encryption }),
|
|
17
|
-
context = new CoreContext(store, core, core, encryption),
|
|
26
|
+
context = new CoreContext(store, core, core, encryption, t),
|
|
18
27
|
maxCacheSize = 4096,
|
|
19
28
|
cache = new NodeCache(maxCacheSize),
|
|
20
29
|
root = null,
|
|
@@ -111,12 +120,12 @@ class Hyperbee {
|
|
|
111
120
|
if (!this.view) await this.store.close()
|
|
112
121
|
}
|
|
113
122
|
|
|
114
|
-
createReadStream(
|
|
115
|
-
return new RangeStream(this,
|
|
123
|
+
createReadStream(options) {
|
|
124
|
+
return new RangeStream(this, options)
|
|
116
125
|
}
|
|
117
126
|
|
|
118
|
-
createDiffStream(right,
|
|
119
|
-
return new DiffStream(this, right,
|
|
127
|
+
createDiffStream(right, options) {
|
|
128
|
+
return new DiffStream(this, right, options)
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
createChangesStream(options) {
|
|
@@ -124,12 +133,21 @@ class Hyperbee {
|
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
async peek(range = {}) {
|
|
127
|
-
const rs = new
|
|
136
|
+
const rs = new RangeStream(this, { ...range, limit: 1 })
|
|
128
137
|
let entry = null
|
|
129
138
|
for await (const data of rs) entry = data
|
|
130
139
|
return entry
|
|
131
140
|
}
|
|
132
141
|
|
|
142
|
+
download(range = {}) {
|
|
143
|
+
const rs = new RangeStream(this, range)
|
|
144
|
+
rs.resume()
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
rs.once('error', reject)
|
|
147
|
+
rs.once('close', resolve)
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
133
151
|
bump(ptr) {
|
|
134
152
|
if (ptr.changed) return ptr.value
|
|
135
153
|
this.cache.bump(ptr)
|
|
@@ -137,40 +155,75 @@ class Hyperbee {
|
|
|
137
155
|
return ptr.value
|
|
138
156
|
}
|
|
139
157
|
|
|
158
|
+
// TODO: unslab these and parallize
|
|
140
159
|
async inflate(ptr, activeRequests = this.activeRequests) {
|
|
141
160
|
if (ptr.value) {
|
|
142
161
|
this.bump(ptr)
|
|
143
162
|
return ptr.value
|
|
144
163
|
}
|
|
145
164
|
|
|
146
|
-
const block = await
|
|
147
|
-
|
|
165
|
+
const [block, context] = await Promise.all([
|
|
166
|
+
ptr.context.getBlock(ptr.seq, ptr.core, activeRequests),
|
|
167
|
+
ptr.context.getContext(ptr.core, activeRequests)
|
|
168
|
+
])
|
|
169
|
+
|
|
148
170
|
const tree = block.tree[ptr.offset]
|
|
149
171
|
|
|
150
172
|
const keys = new Array(tree.keys.length)
|
|
151
173
|
const children = new Array(tree.children.length)
|
|
152
174
|
|
|
153
175
|
for (let i = 0; i < keys.length; i++) {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
k.seq === ptr.seq && k.core === 0 && ptr.core === 0
|
|
157
|
-
? block
|
|
158
|
-
: await context.getBlock(k.seq, k.core, activeRequests)
|
|
159
|
-
const d = blk.data[k.offset]
|
|
160
|
-
keys[i] = new DataPointer(context, k.core, k.seq, k.offset, false, d.key, d.value)
|
|
176
|
+
const d = tree.keys[i]
|
|
177
|
+
keys[i] = inflateKey(context, d, ptr, block, activeRequests)
|
|
161
178
|
}
|
|
162
179
|
|
|
163
180
|
for (let i = 0; i < children.length; i++) {
|
|
164
|
-
const
|
|
165
|
-
children[i] =
|
|
181
|
+
const d = tree.children[i]
|
|
182
|
+
children[i] = inflateChild(context, d, ptr, block, activeRequests)
|
|
166
183
|
}
|
|
167
184
|
|
|
168
|
-
ptr.value = new TreeNode(keys, children)
|
|
185
|
+
ptr.value = new TreeNode(await Promise.all(keys), await Promise.all(children))
|
|
169
186
|
this.bump(ptr)
|
|
170
187
|
|
|
171
188
|
return ptr.value
|
|
172
189
|
}
|
|
173
190
|
|
|
191
|
+
async finalizeKeyPointer(key, activeRequests = this.activeRequests) {
|
|
192
|
+
const value = key.value || (await this.inflateValue(key, activeRequests))
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
core: key.context.getCore(key.core),
|
|
196
|
+
offset: key.offset,
|
|
197
|
+
seq: key.seq,
|
|
198
|
+
key: key.key,
|
|
199
|
+
value
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async inflateValue(key, activeRequests = this.activeRequests) {
|
|
204
|
+
if (key.value) return key.value
|
|
205
|
+
if (!key.valuePointer) return null
|
|
206
|
+
|
|
207
|
+
const ptr = key.valuePointer
|
|
208
|
+
|
|
209
|
+
if (ptr.split === 0) {
|
|
210
|
+
const block = await ptr.context.getBlock(ptr.seq, ptr.core, activeRequests)
|
|
211
|
+
return block.values[ptr.offset]
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const blockPromises = new Array(ptr.split + 1)
|
|
215
|
+
for (let i = 0; i < blockPromises.length; i++) {
|
|
216
|
+
blockPromises[i] = ptr.context.getBlock(ptr.seq - ptr.split + i, ptr.core, activeRequests)
|
|
217
|
+
}
|
|
218
|
+
const blocks = await Promise.all(blockPromises)
|
|
219
|
+
const splitValue = new Array(blockPromises.length)
|
|
220
|
+
for (let i = 0; i < splitValue.length - 1; i++) {
|
|
221
|
+
splitValue[i] = blocks[i].values[0]
|
|
222
|
+
}
|
|
223
|
+
splitValue[splitValue.length - 1] = blocks[blocks.length - 1].buffer[ptr.offset]
|
|
224
|
+
return b4a.concat(splitValue)
|
|
225
|
+
}
|
|
226
|
+
|
|
174
227
|
async bootstrap(activeRequests = this.activeRequests) {
|
|
175
228
|
if (!this.root) await this.ready()
|
|
176
229
|
if (this.unbatch) await this._rollback(activeRequests)
|
|
@@ -223,11 +276,11 @@ class Hyperbee {
|
|
|
223
276
|
|
|
224
277
|
while (s < e) {
|
|
225
278
|
const mid = (s + e) >> 1
|
|
226
|
-
const m = v.keys
|
|
279
|
+
const m = v.keys.get(mid)
|
|
227
280
|
|
|
228
281
|
c = b4a.compare(key, m.key)
|
|
229
282
|
|
|
230
|
-
if (c === 0) return m
|
|
283
|
+
if (c === 0) return this.finalizeKeyPointer(m, activeRequests)
|
|
231
284
|
|
|
232
285
|
if (c < 0) e = mid
|
|
233
286
|
else s = mid + 1
|
|
@@ -236,7 +289,7 @@ class Hyperbee {
|
|
|
236
289
|
if (!v.children.length) return null
|
|
237
290
|
|
|
238
291
|
const i = c < 0 ? e : s
|
|
239
|
-
ptr = v.children
|
|
292
|
+
ptr = v.children.get(i)
|
|
240
293
|
}
|
|
241
294
|
}
|
|
242
295
|
}
|
|
@@ -244,3 +297,84 @@ class Hyperbee {
|
|
|
244
297
|
module.exports = Hyperbee
|
|
245
298
|
|
|
246
299
|
function noop() {}
|
|
300
|
+
|
|
301
|
+
function inflateKey(context, d, ptr, block, activeRequests) {
|
|
302
|
+
if (d.type === OP_COHORT) return inflateKeyCohort(context, d, ptr, block, activeRequests)
|
|
303
|
+
return inflateKeyDelta(context, d, ptr, block, activeRequests)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function inflateKeyDelta(context, d, ptr, block, activeRequests) {
|
|
307
|
+
const k = d.pointer
|
|
308
|
+
|
|
309
|
+
if (!k) return new DeltaOp(false, d.type, d.index, null)
|
|
310
|
+
|
|
311
|
+
const blk =
|
|
312
|
+
k.seq === ptr.seq && k.core === 0 && ptr.core === 0
|
|
313
|
+
? block
|
|
314
|
+
: await context.getBlock(k.seq, k.core, activeRequests)
|
|
315
|
+
|
|
316
|
+
const bk = blk.keys[k.offset]
|
|
317
|
+
|
|
318
|
+
let vp = null
|
|
319
|
+
|
|
320
|
+
if (bk.valuePointer) {
|
|
321
|
+
const p = bk.valuePointer
|
|
322
|
+
const ctx = await context.getContext(k.core, activeRequests)
|
|
323
|
+
vp = new ValuePointer(ctx, p.core, p.seq, p.offset, p.split)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const kp = new KeyPointer(context, k.core, k.seq, k.offset, false, bk.key, bk.value, vp)
|
|
327
|
+
return new DeltaOp(false, d.type, d.index, kp)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function inflateKeyCohort(context, d, ptr, block, activeRequests) {
|
|
331
|
+
const co = d.pointer
|
|
332
|
+
|
|
333
|
+
const blk =
|
|
334
|
+
co.seq === ptr.seq && co.core === 0 && ptr.core === 0
|
|
335
|
+
? block
|
|
336
|
+
: await context.getBlock(co.seq, co.core, activeRequests)
|
|
337
|
+
|
|
338
|
+
const cohort = blk.cohorts[co.offset]
|
|
339
|
+
const promises = new Array(cohort.length)
|
|
340
|
+
|
|
341
|
+
for (let i = 0; i < cohort.length; i++) {
|
|
342
|
+
const p = cohort[i]
|
|
343
|
+
const k = inflateKeyDelta(context, p, co, blk, activeRequests)
|
|
344
|
+
promises[i] = k
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const p = new Pointer(context, co.core, co.seq, co.offset)
|
|
348
|
+
return new DeltaCohort(false, p, await Promise.all(promises))
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function inflateChild(context, d, ptr, block, activeRequests) {
|
|
352
|
+
if (d.type === OP_COHORT) return inflateChildCohort(context, d, ptr, block, activeRequests)
|
|
353
|
+
return Promise.resolve(inflateChildDelta(context, d, ptr, block, activeRequests))
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function inflateChildDelta(context, d, ptr, block, activeRequests) {
|
|
357
|
+
const p = d.pointer
|
|
358
|
+
const c = p && new TreeNodePointer(context, p.core, p.seq, p.offset, false, null)
|
|
359
|
+
return new DeltaOp(false, d.type, d.index, c)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function inflateChildCohort(context, d, ptr, block, activeRequests) {
|
|
363
|
+
const co = d.pointer
|
|
364
|
+
|
|
365
|
+
const blk =
|
|
366
|
+
co.seq === ptr.seq && co.core === 0 && ptr.core === 0
|
|
367
|
+
? block
|
|
368
|
+
: await context.getBlock(co.seq, co.core, activeRequests)
|
|
369
|
+
|
|
370
|
+
const cohort = blk.cohorts[co.offset]
|
|
371
|
+
const deltas = new Array(cohort.length)
|
|
372
|
+
|
|
373
|
+
for (let i = 0; i < cohort.length; i++) {
|
|
374
|
+
const c = cohort[i]
|
|
375
|
+
deltas[i] = inflateChildDelta(context, c, co, blk, activeRequests)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const p = new Pointer(context, co.core, co.seq, co.offset)
|
|
379
|
+
return new DeltaCohort(false, p, deltas)
|
|
380
|
+
}
|
package/lib/cache.js
CHANGED
|
@@ -9,6 +9,13 @@ module.exports = class NodeCache {
|
|
|
9
9
|
return this.latest ? this.latest.next : null
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
empty() {
|
|
13
|
+
const maxSize = this.maxSize
|
|
14
|
+
this.maxSize = 0
|
|
15
|
+
this.gc()
|
|
16
|
+
this.maxSize = maxSize
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
gc() {
|
|
13
20
|
while (this.size > this.maxSize) {
|
|
14
21
|
const old = this.oldest()
|
package/lib/compat.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const encodings = require('protocol-buffers-encodings')
|
|
4
4
|
const b4a = require('b4a')
|
|
5
|
+
const { OP_COHORT, OP_INSERT } = require('./compression')
|
|
5
6
|
const varint = encodings.varint
|
|
6
7
|
const skip = encodings.skip
|
|
7
8
|
|
|
@@ -12,26 +13,43 @@ function decodeCompat(buffer, seq) {
|
|
|
12
13
|
const node = buffer.length > 1 ? decodeNode(buffer) : null
|
|
13
14
|
const index = node ? decodeYoloIndex(node.index) : { levels: [] }
|
|
14
15
|
const morphed = {
|
|
15
|
-
type:
|
|
16
|
+
type: 1,
|
|
16
17
|
checkpoint: 0,
|
|
17
18
|
batch: { start: 0, end: 0 },
|
|
18
19
|
previous: seq > 1 ? { core: 0, seq: seq - 1 } : null,
|
|
20
|
+
metadata: null,
|
|
19
21
|
tree: [],
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
keys: node ? [{ key: node.key, value: node.value, valuePointer: null }] : [],
|
|
23
|
+
values: [],
|
|
24
|
+
cohorts: []
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
for (const lvl of index.levels) {
|
|
25
28
|
const t = { keys: [], children: [] }
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
+
if (lvl.keys.length) {
|
|
31
|
+
const cohort = []
|
|
32
|
+
const offset = morphed.cohorts.push(cohort) - 1
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < lvl.keys.length; i++) {
|
|
35
|
+
const seq = lvl.keys[i]
|
|
36
|
+
cohort.push({ type: OP_INSERT, index: i, pointer: { core: 0, seq, offset: 0 } })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
t.keys.push({ type: OP_COHORT, index: 0, pointer: { core: 0, seq, offset } })
|
|
30
40
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
41
|
+
|
|
42
|
+
if (lvl.children.length) {
|
|
43
|
+
const cohort = []
|
|
44
|
+
const offset = morphed.cohorts.push(cohort) - 1
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < lvl.children.length; i += 2) {
|
|
47
|
+
const seq = lvl.children[i]
|
|
48
|
+
const offset = lvl.children[i + 1]
|
|
49
|
+
cohort.push({ type: OP_INSERT, index: i / 2, pointer: { core: 0, seq, offset } })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
t.children.push({ type: OP_COHORT, index: 0, pointer: { core: 0, seq, offset } })
|
|
35
53
|
}
|
|
36
54
|
|
|
37
55
|
morphed.tree.push(t)
|
|
@@ -46,8 +64,16 @@ function encodeCompat(node) {
|
|
|
46
64
|
if (node.tree) {
|
|
47
65
|
for (const t of node.tree) {
|
|
48
66
|
const lvl = { keys: [], children: [] }
|
|
49
|
-
for (const k of t.keys)
|
|
50
|
-
|
|
67
|
+
for (const k of t.keys) {
|
|
68
|
+
for (const kk of node.cohorts[k.pointer.offset]) {
|
|
69
|
+
lvl.keys.push(kk.pointer.seq)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (const c of t.children) {
|
|
73
|
+
for (const cc of node.cohorts[c.pointer.offset]) {
|
|
74
|
+
lvl.children.push(cc.pointer.seq, cc.pointer.offset)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
51
77
|
index.levels.push(lvl)
|
|
52
78
|
}
|
|
53
79
|
}
|
|
@@ -56,8 +82,8 @@ function encodeCompat(node) {
|
|
|
56
82
|
encodeYoloIndex(index, bufIndex, 0)
|
|
57
83
|
|
|
58
84
|
const n = {
|
|
59
|
-
key: node.
|
|
60
|
-
value: node.
|
|
85
|
+
key: node.keys[0].key,
|
|
86
|
+
value: node.keys[0].value,
|
|
61
87
|
index: bufIndex
|
|
62
88
|
}
|
|
63
89
|
|
|
@@ -65,28 +91,6 @@ function encodeCompat(node) {
|
|
|
65
91
|
return encodeNode(n, buf, 0)
|
|
66
92
|
}
|
|
67
93
|
|
|
68
|
-
function deflateIndex(index) {
|
|
69
|
-
const levels = index.map((l) => {
|
|
70
|
-
const keys = []
|
|
71
|
-
const children = []
|
|
72
|
-
|
|
73
|
-
for (let i = 0; i < l.value.keys.length; i++) {
|
|
74
|
-
keys.push(l.value.keys[i].seq)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
for (let i = 0; i < l.value.children.length; i++) {
|
|
78
|
-
children.push(l.value.children[i].seq, l.value.children[i].offset)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return { keys, children }
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const idx = { levels }
|
|
85
|
-
const buf = b4a.allocUnsafe(encodingLengthYoloIndex(idx))
|
|
86
|
-
|
|
87
|
-
return encodeYoloIndex(idx, buf, 0)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
94
|
function decodeLevel(buf, offset, end) {
|
|
91
95
|
if (!offset) offset = 0
|
|
92
96
|
if (!end) end = buf.length
|
|
@@ -141,7 +145,7 @@ function encodingLengthLevel(obj) {
|
|
|
141
145
|
let packedLen = 0
|
|
142
146
|
for (let i = 0; i < obj.keys.length; i++) {
|
|
143
147
|
if (!obj.keys[i]) continue
|
|
144
|
-
|
|
148
|
+
const len = encodings.varint.encodingLength(obj.keys[i])
|
|
145
149
|
packedLen += len
|
|
146
150
|
}
|
|
147
151
|
if (packedLen) {
|
|
@@ -152,7 +156,7 @@ function encodingLengthLevel(obj) {
|
|
|
152
156
|
let packedLen = 0
|
|
153
157
|
for (let i = 0; i < obj.children.length; i++) {
|
|
154
158
|
if (!obj.children[i]) continue
|
|
155
|
-
|
|
159
|
+
const len = encodings.varint.encodingLength(obj.children[i])
|
|
156
160
|
packedLen += len
|
|
157
161
|
}
|
|
158
162
|
if (packedLen) {
|
|
@@ -164,7 +168,7 @@ function encodingLengthLevel(obj) {
|
|
|
164
168
|
|
|
165
169
|
function encodeLevel(obj, buf, offset) {
|
|
166
170
|
if (!offset) offset = 0
|
|
167
|
-
|
|
171
|
+
const oldOffset = offset
|
|
168
172
|
if (obj.keys) {
|
|
169
173
|
let packedLen = 0
|
|
170
174
|
for (let i = 0; i < obj.keys.length; i++) {
|
|
@@ -239,7 +243,7 @@ function encodingLengthYoloIndex(obj) {
|
|
|
239
243
|
if (obj.levels) {
|
|
240
244
|
for (let i = 0; i < obj.levels.length; i++) {
|
|
241
245
|
if (!obj.levels[i]) continue
|
|
242
|
-
|
|
246
|
+
const len = encodingLengthLevel(obj.levels[i])
|
|
243
247
|
length += varint.encodingLength(len)
|
|
244
248
|
length += 1 + len
|
|
245
249
|
}
|
|
@@ -249,7 +253,7 @@ function encodingLengthYoloIndex(obj) {
|
|
|
249
253
|
|
|
250
254
|
function encodeYoloIndex(obj, buf, offset) {
|
|
251
255
|
if (!offset) offset = 0
|
|
252
|
-
|
|
256
|
+
const oldOffset = offset
|
|
253
257
|
if (obj.levels) {
|
|
254
258
|
for (let i = 0; i < obj.levels.length; i++) {
|
|
255
259
|
if (!obj.levels[i]) continue
|
|
@@ -318,7 +322,7 @@ function encodingLengthNode(obj) {
|
|
|
318
322
|
len = encodings.bytes.encodingLength(obj.key)
|
|
319
323
|
length += 1 + len
|
|
320
324
|
if (obj.value) {
|
|
321
|
-
|
|
325
|
+
const len = encodings.bytes.encodingLength(obj.value)
|
|
322
326
|
length += 1 + len
|
|
323
327
|
}
|
|
324
328
|
return length
|
|
@@ -326,7 +330,7 @@ function encodingLengthNode(obj) {
|
|
|
326
330
|
|
|
327
331
|
function encodeNode(obj, buf, offset) {
|
|
328
332
|
if (!offset) offset = 0
|
|
329
|
-
|
|
333
|
+
const oldOffset = offset
|
|
330
334
|
buf[offset++] = 10
|
|
331
335
|
encodings.bytes.encode(obj.index, buf, offset)
|
|
332
336
|
offset += encodings.bytes.encode.bytes
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const OP_SET = 0
|
|
2
|
+
const OP_INSERT = 1
|
|
3
|
+
const OP_DEL = 2
|
|
4
|
+
const OP_COHORT = 3
|
|
5
|
+
|
|
6
|
+
class DeltaOp {
|
|
7
|
+
constructor(changed, type, index, pointer) {
|
|
8
|
+
this.type = type
|
|
9
|
+
this.index = index
|
|
10
|
+
this.pointer = pointer
|
|
11
|
+
this.changed = changed
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class DeltaCohort extends DeltaOp {
|
|
16
|
+
constructor(changed, pointer, deltas) {
|
|
17
|
+
super(changed, OP_COHORT, 0, pointer)
|
|
18
|
+
this.deltas = deltas
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class CompressedArray {
|
|
23
|
+
constructor(delta) {
|
|
24
|
+
this.entries = []
|
|
25
|
+
this.delta = delta
|
|
26
|
+
|
|
27
|
+
for (const d of delta) {
|
|
28
|
+
if (d.type === OP_COHORT) {
|
|
29
|
+
for (const dd of d.deltas) {
|
|
30
|
+
apply(this.entries, dd.type, dd.index, dd.pointer)
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
apply(this.entries, d.type, d.index, d.pointer)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get length() {
|
|
39
|
+
return this.entries.length
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
touch(index) {
|
|
43
|
+
const pointer = this.entries[index]
|
|
44
|
+
if (pointer.changedBy === this) return
|
|
45
|
+
this.set(index, pointer)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get(index) {
|
|
49
|
+
return this.entries[index]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
push(pointer) {
|
|
53
|
+
this.insert(this.entries.length, pointer)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
unshift(pointer) {
|
|
57
|
+
this.insert(0, pointer)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pop() {
|
|
61
|
+
if (this.entries.length === 0) return
|
|
62
|
+
const head = this.entries[this.entries.length - 1]
|
|
63
|
+
this.delete(this.entries.length - 1)
|
|
64
|
+
return head
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
shift() {
|
|
68
|
+
if (this.entries.length === 0) return
|
|
69
|
+
const tail = this.entries[0]
|
|
70
|
+
this.delete(0)
|
|
71
|
+
return tail
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_touch(pointer) {
|
|
75
|
+
if (pointer) pointer.changedBy = this
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
insert(index, pointer) {
|
|
79
|
+
if (!insert(this.entries, index, pointer)) return
|
|
80
|
+
this._touch(pointer)
|
|
81
|
+
this.delta.push(new DeltaOp(true, OP_INSERT, index, pointer))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
delete(index) {
|
|
85
|
+
if (!del(this.entries, index)) return
|
|
86
|
+
this._touch(null)
|
|
87
|
+
this.delta.push(new DeltaOp(true, OP_DEL, index, null))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
set(index, pointer) {
|
|
91
|
+
if (!set(this.entries, index, pointer)) return
|
|
92
|
+
this._touch(pointer)
|
|
93
|
+
this.delta.push(new DeltaOp(true, OP_SET, index, pointer))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
flush(max, min) {
|
|
97
|
+
if (this.delta.length <= max) return this.delta
|
|
98
|
+
|
|
99
|
+
const direct = []
|
|
100
|
+
while (this.delta.length && this.delta[this.delta.length - 1].type !== OP_COHORT) {
|
|
101
|
+
direct.push(this.delta.pop())
|
|
102
|
+
}
|
|
103
|
+
direct.reverse()
|
|
104
|
+
|
|
105
|
+
if (direct.length > min && direct.length < this.entries.length) {
|
|
106
|
+
const co = new DeltaCohort(true, null, [])
|
|
107
|
+
for (const d of direct) {
|
|
108
|
+
co.deltas.push(d)
|
|
109
|
+
}
|
|
110
|
+
this.delta.push(co)
|
|
111
|
+
} else {
|
|
112
|
+
const co = new DeltaCohort(true, null, [])
|
|
113
|
+
for (let i = 0; i < this.entries.length; i++) {
|
|
114
|
+
const d = new DeltaOp(true, OP_INSERT, i, this.entries[i])
|
|
115
|
+
co.deltas.push(d)
|
|
116
|
+
}
|
|
117
|
+
this.delta = [co]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return this.delta
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
exports.CompressedArray = CompressedArray
|
|
125
|
+
exports.DeltaOp = DeltaOp
|
|
126
|
+
exports.DeltaCohort = DeltaCohort
|
|
127
|
+
|
|
128
|
+
exports.OP_SET = OP_SET
|
|
129
|
+
exports.OP_INSERT = OP_INSERT
|
|
130
|
+
exports.OP_DEL = OP_DEL
|
|
131
|
+
exports.OP_COHORT = OP_COHORT
|
|
132
|
+
|
|
133
|
+
function del(entries, index) {
|
|
134
|
+
if (index >= entries.length) return false
|
|
135
|
+
entries.splice(index, 1)
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function insert(entries, index, pointer) {
|
|
140
|
+
if (index >= entries.length + 1) return false
|
|
141
|
+
entries.splice(index, 0, pointer)
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function set(entries, index, pointer) {
|
|
146
|
+
if (index >= entries.length) return false
|
|
147
|
+
// if (entries[index] === pointer) return false
|
|
148
|
+
entries[index] = pointer
|
|
149
|
+
return true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function apply(entries, type, index, pointer) {
|
|
153
|
+
if (type === OP_INSERT) {
|
|
154
|
+
return insert(entries, index, pointer)
|
|
155
|
+
}
|
|
156
|
+
if (type === OP_DEL) {
|
|
157
|
+
return del(entries, index)
|
|
158
|
+
}
|
|
159
|
+
if (type === OP_SET) {
|
|
160
|
+
return set(entries, index, pointer)
|
|
161
|
+
}
|
|
162
|
+
return false
|
|
163
|
+
}
|