hypercore-storage 0.0.41 → 1.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/LICENSE +201 -0
- package/README.md +17 -0
- package/index.js +467 -783
- package/lib/block-dependency-stream.js +107 -0
- package/lib/keys.js +242 -0
- package/lib/streams.js +112 -0
- package/lib/tx.js +286 -0
- package/lib/view.js +326 -0
- package/migrations/0/index.js +628 -0
- package/migrations/0/messages.js +1069 -0
- package/package.json +33 -18
- package/spec/hyperschema/index.js +510 -0
- package/lib/dependency-stream.js +0 -111
- package/lib/memory-overlay.js +0 -438
- package/lib/messages.js +0 -190
- package/lib/tip-list.js +0 -93
package/lib/tx.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
const schema = require('../spec/hyperschema')
|
|
2
|
+
const { store, core } = require('./keys.js')
|
|
3
|
+
const View = require('./view.js')
|
|
4
|
+
const b4a = require('b4a')
|
|
5
|
+
const flat = require('flat-tree')
|
|
6
|
+
|
|
7
|
+
const CORESTORE_HEAD = schema.getEncoding('@corestore/head')
|
|
8
|
+
const CORESTORE_CORE = schema.getEncoding('@corestore/core')
|
|
9
|
+
|
|
10
|
+
const CORE_AUTH = schema.getEncoding('@core/auth')
|
|
11
|
+
const CORE_SESSIONS = schema.getEncoding('@core/sessions')
|
|
12
|
+
const CORE_HEAD = schema.getEncoding('@core/head')
|
|
13
|
+
const CORE_TREE_NODE = schema.getEncoding('@core/tree-node')
|
|
14
|
+
const CORE_DEPENDENCY = schema.getEncoding('@core/dependency')
|
|
15
|
+
const CORE_HINTS = schema.getEncoding('@core/hints')
|
|
16
|
+
|
|
17
|
+
class CoreTX {
|
|
18
|
+
constructor (core, db, view, changes) {
|
|
19
|
+
this.core = core
|
|
20
|
+
this.db = db
|
|
21
|
+
this.view = view
|
|
22
|
+
this.changes = changes
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setAuth (auth) {
|
|
26
|
+
this.changes.push([core.auth(this.core.corePointer), encode(CORE_AUTH, auth), null])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setSessions (sessions) {
|
|
30
|
+
this.changes.push([core.sessions(this.core.corePointer), encode(CORE_SESSIONS, sessions), null])
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setHead (head) {
|
|
34
|
+
this.changes.push([core.head(this.core.dataPointer), encode(CORE_HEAD, head), null])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setDependency (dep) {
|
|
38
|
+
this.changes.push([core.dependency(this.core.dataPointer), encode(CORE_DEPENDENCY, dep), null])
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setHints (hints) {
|
|
42
|
+
this.changes.push([core.hints(this.core.dataPointer), encode(CORE_HINTS, hints), null])
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
putBlock (index, data) {
|
|
46
|
+
this.changes.push([core.block(this.core.dataPointer, index), data, null])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
deleteBlock (index) {
|
|
50
|
+
this.changes.push([core.block(this.core.dataPointer, index), null, null])
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
deleteBlockRange (start, end) {
|
|
54
|
+
this.changes.push([
|
|
55
|
+
core.block(this.core.dataPointer, start),
|
|
56
|
+
null,
|
|
57
|
+
core.block(this.core.dataPointer, end === -1 ? Infinity : end)
|
|
58
|
+
])
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
putBitfieldPage (index, data) {
|
|
62
|
+
this.changes.push([core.bitfield(this.core.dataPointer, index, 0), data, null])
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
deleteBitfieldPage (index) {
|
|
66
|
+
this.changes.push([core.bitfield(this.core.dataPointer, index, 0), null, null])
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
deleteBitfieldPageRange (start, end) {
|
|
70
|
+
this.changes.push([
|
|
71
|
+
core.bitfield(this.core.dataPointer, start, 0),
|
|
72
|
+
null,
|
|
73
|
+
core.bitfield(this.core.dataPointer, end === -1 ? Infinity : end, 0)
|
|
74
|
+
])
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
putTreeNode (node) {
|
|
78
|
+
this.changes.push([core.tree(this.core.dataPointer, node.index), encode(CORE_TREE_NODE, node), null])
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
deleteTreeNode (index) {
|
|
82
|
+
this.changes.push([core.tree(this.core.dataPointer, index), null, null])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
deleteTreeNodeRange (start, end) {
|
|
86
|
+
this.changes.push([
|
|
87
|
+
core.tree(this.core.dataPointer, start),
|
|
88
|
+
null,
|
|
89
|
+
core.tree(this.core.dataPointer, end === -1 ? Infinity : end)
|
|
90
|
+
])
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
putUserData (key, value) {
|
|
94
|
+
const buffer = typeof value === 'string' ? b4a.from(value) : value
|
|
95
|
+
this.changes.push([core.userData(this.core.dataPointer, key), buffer, null])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
deleteUserData (key) {
|
|
99
|
+
this.changes.push([core.userData(this.core.dataPointer, key), null, null])
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
flush () {
|
|
103
|
+
const changes = this.changes
|
|
104
|
+
if (changes === null) return Promise.resolve(!this.view)
|
|
105
|
+
|
|
106
|
+
this.changes = null
|
|
107
|
+
|
|
108
|
+
if (this.view) {
|
|
109
|
+
this.view.apply(changes)
|
|
110
|
+
return Promise.resolve(false)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return View.flush(changes, this.db)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class CoreRX {
|
|
118
|
+
constructor (core, db, view) {
|
|
119
|
+
this.core = core
|
|
120
|
+
this.read = db.read({ autoDestroy: true })
|
|
121
|
+
this.view = view
|
|
122
|
+
|
|
123
|
+
view.readStart()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async getAuth () {
|
|
127
|
+
return await decode(CORE_AUTH, await this.view.get(this.read, core.auth(this.core.corePointer)))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async getSessions () {
|
|
131
|
+
return await decode(CORE_SESSIONS, await this.view.get(this.read, core.sessions(this.core.corePointer)))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async getHead () {
|
|
135
|
+
return await decode(CORE_HEAD, await this.view.get(this.read, core.head(this.core.dataPointer)))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async getDependency () {
|
|
139
|
+
return await decode(CORE_DEPENDENCY, await this.view.get(this.read, core.dependency(this.core.dataPointer)))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async getHints () {
|
|
143
|
+
return await decode(CORE_HINTS, await this.view.get(this.read, core.hints(this.core.dataPointer)))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getBlock (index) {
|
|
147
|
+
const dep = findBlockDependency(this.core.dependencies, index)
|
|
148
|
+
const data = dep === null ? this.core.dataPointer : dep.dataPointer
|
|
149
|
+
return this.view.get(this.read, core.block(data, index))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getBitfieldPage (index) {
|
|
153
|
+
return this.view.get(this.read, core.bitfield(this.core.dataPointer, index, 0))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async getTreeNode (index) {
|
|
157
|
+
const dep = findTreeDependency(this.core.dependencies, index)
|
|
158
|
+
const data = dep === null ? this.core.dataPointer : dep.dataPointer
|
|
159
|
+
return decode(CORE_TREE_NODE, await this.view.get(this.read, core.tree(data, index)))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async hasTreeNode (index) {
|
|
163
|
+
return (await this.getTreeNode(index)) !== null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
getUserData (key) {
|
|
167
|
+
return this.view.get(this.read, core.userData(this.core.dataPointer, key))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
tryFlush () {
|
|
171
|
+
this.read.tryFlush()
|
|
172
|
+
this._free()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
destroy () {
|
|
176
|
+
this.read.destroy()
|
|
177
|
+
this._free()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_free () {
|
|
181
|
+
if (this.view === null) return
|
|
182
|
+
this.view.readStop()
|
|
183
|
+
this.view = null
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
class CorestoreTX {
|
|
188
|
+
constructor (view) {
|
|
189
|
+
this.view = view
|
|
190
|
+
this.changes = []
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
setHead (head) {
|
|
194
|
+
this.changes.push([store.head(), encode(CORESTORE_HEAD, head), null])
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
putCore (discoveryKey, ptr) {
|
|
198
|
+
this.changes.push([store.core(discoveryKey), encode(CORESTORE_CORE, ptr), null])
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
putCoreByAlias (alias, discoveryKey) {
|
|
202
|
+
this.changes.push([store.coreByAlias(alias), discoveryKey, null])
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
clear () {
|
|
206
|
+
const [start, end] = store.clear()
|
|
207
|
+
this.changes.push([start, null, end])
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
apply () {
|
|
211
|
+
if (this.changes === null) return
|
|
212
|
+
this.view.apply(this.changes)
|
|
213
|
+
this.changes = null
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
class CorestoreRX {
|
|
218
|
+
constructor (db, view) {
|
|
219
|
+
this.read = db.read({ autoDestroy: true })
|
|
220
|
+
this.view = view
|
|
221
|
+
|
|
222
|
+
view.readStart()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async getHead () {
|
|
226
|
+
return decode(CORESTORE_HEAD, await this.view.get(this.read, store.head()))
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async getCore (discoveryKey) {
|
|
230
|
+
return decode(CORESTORE_CORE, await this.view.get(this.read, store.core(discoveryKey)))
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
getCoreByAlias (alias) {
|
|
234
|
+
return this.view.get(this.read, store.coreByAlias(alias))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
tryFlush () {
|
|
238
|
+
this.read.tryFlush()
|
|
239
|
+
this._free()
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
destroy () {
|
|
243
|
+
this.read.destroy()
|
|
244
|
+
this._free()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_free () {
|
|
248
|
+
if (this.view === null) return
|
|
249
|
+
this.view.readStop()
|
|
250
|
+
this.view = null
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = { CorestoreTX, CorestoreRX, CoreTX, CoreRX }
|
|
255
|
+
|
|
256
|
+
function findBlockDependency (dependencies, index) {
|
|
257
|
+
for (let i = 0; i < dependencies.length; i++) {
|
|
258
|
+
const dep = dependencies[i]
|
|
259
|
+
if (index < dep.length) return dep
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return null
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function findTreeDependency (dependencies, index) {
|
|
266
|
+
for (let i = 0; i < dependencies.length; i++) {
|
|
267
|
+
const dep = dependencies[i]
|
|
268
|
+
if (flat.rightSpan(index) <= (dep.length - 1) * 2) return dep
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return null
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function decode (enc, buffer) {
|
|
275
|
+
if (buffer === null) return null
|
|
276
|
+
return enc.decode({ start: 0, end: buffer.byteLength, buffer })
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function encode (enc, m) {
|
|
280
|
+
// TODO: use fancy slab for small messages
|
|
281
|
+
const state = { start: 0, end: 0, buffer: null }
|
|
282
|
+
enc.preencode(state, m)
|
|
283
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
284
|
+
enc.encode(state, m)
|
|
285
|
+
return state.buffer
|
|
286
|
+
}
|
package/lib/view.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
const { Readable, getStreamError } = require('streamx')
|
|
2
|
+
const b4a = require('b4a')
|
|
3
|
+
|
|
4
|
+
class OverlayStream extends Readable {
|
|
5
|
+
constructor (stream, start, end, reverse, changes, ranges) {
|
|
6
|
+
super()
|
|
7
|
+
|
|
8
|
+
this.start = start
|
|
9
|
+
this.end = end
|
|
10
|
+
this.reverse = reverse
|
|
11
|
+
this.changes = changes
|
|
12
|
+
this.ranges = ranges
|
|
13
|
+
this.change = 0
|
|
14
|
+
this.range = 0
|
|
15
|
+
|
|
16
|
+
this._stream = stream
|
|
17
|
+
this._drained = false
|
|
18
|
+
|
|
19
|
+
this._stream.on('readable', this._drainMaybe.bind(this))
|
|
20
|
+
this._stream.on('error', noop)
|
|
21
|
+
this._stream.on('close', this._onclose.bind(this))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_drainMaybe () {
|
|
25
|
+
if (this._drained === true) return
|
|
26
|
+
this._drained = this._onreadable()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_onclose () {
|
|
30
|
+
if (this.destroying) return
|
|
31
|
+
|
|
32
|
+
const err = getStreamError(this._stream)
|
|
33
|
+
|
|
34
|
+
if (err !== null) {
|
|
35
|
+
this.destroy(err)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
while (this.change < this.changes.length) {
|
|
40
|
+
const c = this.changes[this.change++]
|
|
41
|
+
const key = c[0]
|
|
42
|
+
const value = c[1]
|
|
43
|
+
|
|
44
|
+
if (value !== null && this._inRange(key)) this.push({ key, value })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.push(null)
|
|
48
|
+
this._stream = null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_onreadable () {
|
|
52
|
+
let data = this._stream.read()
|
|
53
|
+
|
|
54
|
+
if (data === null) return false
|
|
55
|
+
|
|
56
|
+
do {
|
|
57
|
+
this._push(data)
|
|
58
|
+
data = this._stream.read()
|
|
59
|
+
} while (data !== null)
|
|
60
|
+
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_read (cb) {
|
|
65
|
+
this._drained = this._onreadable()
|
|
66
|
+
cb(null)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_predestroy () {
|
|
70
|
+
this.stream.destroy()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_push (entry) {
|
|
74
|
+
const key = entry.key
|
|
75
|
+
|
|
76
|
+
while (this.range < this.ranges.length) {
|
|
77
|
+
const r = this.ranges[this.range]
|
|
78
|
+
|
|
79
|
+
// we moved past the range
|
|
80
|
+
if (this.reverse ? b4a.compare(key, r[0]) < 0 : b4a.compare(r[2], key) <= 0) {
|
|
81
|
+
this.range++
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// we didnt move past and are in, drop
|
|
86
|
+
if (b4a.compare(r[0], key) <= 0 && b4a.compare(key, r[2]) < 0) {
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
while (this.change < this.changes.length) {
|
|
94
|
+
const c = this.changes[this.change]
|
|
95
|
+
const key = c[0]
|
|
96
|
+
const value = typeof c[1] === 'string' ? b4a.from(c[1]) : c[1]
|
|
97
|
+
const cmp = b4a.compare(key, entry.key)
|
|
98
|
+
|
|
99
|
+
// same value, if not deleted, return new one
|
|
100
|
+
if (cmp === 0) {
|
|
101
|
+
this.change++
|
|
102
|
+
return value === null || this._inRange(key) === false ? true : this.push({ key, value })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// we moved past the change, push it
|
|
106
|
+
if (this.reverse ? cmp > 0 : cmp < 0) {
|
|
107
|
+
this.change++
|
|
108
|
+
if (value !== null && this._inRange(key) === true) this.push({ key, value })
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return this.push(entry)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return this.push(entry)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_inRange (key) {
|
|
119
|
+
return b4a.compare(this.start, key) <= 0 && b4a.compare(key, this.end) < 0
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class Overlay {
|
|
124
|
+
constructor () {
|
|
125
|
+
this.indexed = 0
|
|
126
|
+
this.changes = null
|
|
127
|
+
this.ranges = null
|
|
128
|
+
this.reverse = false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
update (view, reverse) {
|
|
132
|
+
if (view.indexed === this.indexed) return
|
|
133
|
+
|
|
134
|
+
const changes = view.map === null ? [] : [...view.map.values()]
|
|
135
|
+
const ranges = view.ranges === null ? [] : view.ranges.slice(0)
|
|
136
|
+
|
|
137
|
+
const cmp = reverse ? cmpChangeReverse : cmpChange
|
|
138
|
+
|
|
139
|
+
changes.sort(cmp)
|
|
140
|
+
ranges.sort(cmp)
|
|
141
|
+
|
|
142
|
+
this.indexed = view.indexed
|
|
143
|
+
this.changes = changes
|
|
144
|
+
this.ranges = ranges
|
|
145
|
+
this.reverse = reverse
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
createStream (stream, start, end, reverse) {
|
|
149
|
+
return new OverlayStream(
|
|
150
|
+
stream,
|
|
151
|
+
start,
|
|
152
|
+
end,
|
|
153
|
+
reverse,
|
|
154
|
+
this.reverse === reverse ? this.changes : reverseArray(this.changes),
|
|
155
|
+
this.reverse === reverse ? this.ranges : reverseArray(this.ranges)
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
class View {
|
|
161
|
+
constructor () {
|
|
162
|
+
this.map = null
|
|
163
|
+
this.indexed = 0
|
|
164
|
+
this.changes = null
|
|
165
|
+
this.ranges = null
|
|
166
|
+
this.overlay = null
|
|
167
|
+
this.snap = null
|
|
168
|
+
this.readers = 0
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
snapshot () {
|
|
172
|
+
if (this._attached()) return this.snap.snapshot()
|
|
173
|
+
|
|
174
|
+
const snap = new View()
|
|
175
|
+
|
|
176
|
+
snap.map = this.map
|
|
177
|
+
snap.indexed = this.indexed
|
|
178
|
+
snap.changes = this.changes
|
|
179
|
+
snap.ranges = this.ranges
|
|
180
|
+
|
|
181
|
+
if (this._frozen()) return snap
|
|
182
|
+
|
|
183
|
+
this.readers++
|
|
184
|
+
snap.snap = this
|
|
185
|
+
|
|
186
|
+
return snap
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
readStart () {
|
|
190
|
+
if (this.snap !== null) this.readers++
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
readStop () {
|
|
194
|
+
if (this.snap !== null && --this.readers === 0) this.snap.readers--
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
size () {
|
|
198
|
+
return this.changes === null ? 0 : this.changes.length
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
updated () {
|
|
202
|
+
return this.changes === null
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
get (read, key) {
|
|
206
|
+
return this.changes === null ? read.get(key) : this._indexAndGet(read, key)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
reset () {
|
|
210
|
+
this.indexed = 0
|
|
211
|
+
this.snap = this.map = this.changes = this.ranges = null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
iterator (db, start, end, reverse) {
|
|
215
|
+
const stream = db.iterator({ gte: start, lt: end, reverse })
|
|
216
|
+
if (this.changes === null) return stream
|
|
217
|
+
|
|
218
|
+
this._index()
|
|
219
|
+
|
|
220
|
+
if (this.overlay === null) this.overlay = new Overlay()
|
|
221
|
+
this.overlay.update(this, reverse)
|
|
222
|
+
return this.overlay.createStream(stream, start, end, reverse)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_indexAndGet (read, key) {
|
|
226
|
+
this._index()
|
|
227
|
+
const change = this.map.get(b4a.toString(key, 'hex'))
|
|
228
|
+
if (change === undefined) return read.get(key)
|
|
229
|
+
return Promise.resolve(change[1])
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
_attached () {
|
|
233
|
+
return this.snap !== null && this.changes === this.snap.changes
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
_frozen () {
|
|
237
|
+
return this.changes === null || (this.snap !== null && this.changes !== this.snap.changes)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
_index () {
|
|
241
|
+
// if we are a snap and we are still attached (ie no mutations), simply copy the refs
|
|
242
|
+
if (this._attached()) {
|
|
243
|
+
this.snap._index()
|
|
244
|
+
this.map = this.snap.map
|
|
245
|
+
this.ranges = this.snap.ranges
|
|
246
|
+
this.indexed = this.snap.indexed
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (this.changes.length === this.indexed) return
|
|
251
|
+
if (this.map === null) this.map = new Map()
|
|
252
|
+
|
|
253
|
+
while (this.indexed < this.changes.length) {
|
|
254
|
+
const c = this.changes[this.indexed++]
|
|
255
|
+
|
|
256
|
+
if (c[2] === null) this.map.set(b4a.toString(c[0], 'hex'), c)
|
|
257
|
+
else this._indexRange(c)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
_indexRange (range) {
|
|
262
|
+
const s = b4a.toString(range[0], 'hex')
|
|
263
|
+
const e = b4a.toString(range[2], 'hex')
|
|
264
|
+
|
|
265
|
+
for (const [key, c] of this.map) {
|
|
266
|
+
if (s <= key && key < e) this.map.set(key, [c[0], null, null])
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (this.ranges === null) this.ranges = []
|
|
270
|
+
this.ranges.push(range)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
apply (changes) {
|
|
274
|
+
if (this.snap !== null) throw new Error('Illegal to push changes to a snapshot')
|
|
275
|
+
|
|
276
|
+
if (this.readers !== 0 && this.changes !== null) {
|
|
277
|
+
this.changes = this.changes.slice(0)
|
|
278
|
+
this.ranges = this.ranges === null ? null : this.ranges.slice(0)
|
|
279
|
+
this.map = this.map === null ? null : new Map([...this.map])
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (this.changes === null) {
|
|
283
|
+
this.changes = changes
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
for (let i = 0; i < changes.length; i++) {
|
|
288
|
+
this.changes.push(changes[i])
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
static async flush (changes, db) {
|
|
293
|
+
if (changes === null) return true
|
|
294
|
+
|
|
295
|
+
const w = db.write({ autoDestroy: true })
|
|
296
|
+
|
|
297
|
+
for (const [start, value, end] of changes) {
|
|
298
|
+
if (end !== null) w.tryDeleteRange(start, end)
|
|
299
|
+
else if (value !== null) w.tryPut(start, value)
|
|
300
|
+
else w.tryDelete(start)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
await w.flush()
|
|
304
|
+
|
|
305
|
+
return true
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
module.exports = View
|
|
310
|
+
|
|
311
|
+
function cmpChange (a, b) {
|
|
312
|
+
const c = b4a.compare(a[0], b[0])
|
|
313
|
+
return c === 0 ? b4a.compare(a[2], b[2]) : c
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function cmpChangeReverse (a, b) {
|
|
317
|
+
return cmpChange(b, a)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function noop () {}
|
|
321
|
+
|
|
322
|
+
function reverseArray (list) {
|
|
323
|
+
const r = new Array(list.length)
|
|
324
|
+
for (let i = 0; i < list.length; i++) r[r.length - 1 - i] = list[i]
|
|
325
|
+
return r
|
|
326
|
+
}
|