hypercore-storage 0.0.41 → 1.0.1

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/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,345 @@
1
+ const { Readable, getStreamError } = require('streamx')
2
+ const b4a = require('b4a')
3
+
4
+ class OverlayStream extends Readable {
5
+ constructor (stream, start, end, reverse, changes, cleared) {
6
+ super()
7
+
8
+ this.start = start
9
+ this.end = end
10
+ this.reverse = reverse
11
+ this.changes = changes
12
+ this.cleared = cleared
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.cleared.length) {
77
+ const c = this.cleared[this.range]
78
+
79
+ // we moved past the range
80
+ if (this.reverse ? b4a.compare(key, c[0]) < 0 : b4a.compare(c[2], key) <= 0) {
81
+ this.range++
82
+ continue
83
+ }
84
+
85
+ // we didnt move past and are in, drop
86
+ if (b4a.compare(c[0], key) <= 0 && b4a.compare(key, c[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.cleared = 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 cleared = view.cleared === null ? [] : view.cleared.slice(0)
136
+
137
+ const cmp = reverse ? cmpChangeReverse : cmpChange
138
+
139
+ changes.sort(cmp)
140
+ cleared.sort(cmp)
141
+
142
+ this.indexed = view.indexed
143
+ this.changes = changes
144
+ this.cleared = cleared
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.cleared : reverseArray(this.cleared)
156
+ )
157
+ }
158
+ }
159
+
160
+ class View {
161
+ constructor () {
162
+ this.map = null
163
+ this.indexed = 0
164
+ this.changes = null
165
+ this.cleared = 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.cleared = this.cleared
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.cleared = 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
+
229
+ if (change === undefined) {
230
+ return this.cleared === null
231
+ ? read.get(key)
232
+ : this._readAndMaybeDrop(read, key)
233
+ }
234
+
235
+ return Promise.resolve(change[1])
236
+ }
237
+
238
+ async _readAndMaybeDrop (read, key) {
239
+ const value = await read.get(key)
240
+ if (value === null) return null
241
+
242
+ for (let i = 0; i < this.cleared.length; i++) {
243
+ const c = this.cleared[i]
244
+ // check if in range
245
+ if (b4a.compare(c[0], key) <= 0 && b4a.compare(key, c[2]) < 0) return null
246
+ }
247
+
248
+ return value
249
+ }
250
+
251
+ _attached () {
252
+ return this.snap !== null && this.changes === this.snap.changes
253
+ }
254
+
255
+ _frozen () {
256
+ return this.changes === null || (this.snap !== null && this.changes !== this.snap.changes)
257
+ }
258
+
259
+ _index () {
260
+ // if we are a snap and we are still attached (ie no mutations), simply copy the refs
261
+ if (this._attached()) {
262
+ this.snap._index()
263
+ this.map = this.snap.map
264
+ this.cleared = this.snap.cleared
265
+ this.indexed = this.snap.indexed
266
+ return
267
+ }
268
+
269
+ if (this.changes.length === this.indexed) return
270
+ if (this.map === null) this.map = new Map()
271
+
272
+ while (this.indexed < this.changes.length) {
273
+ const c = this.changes[this.indexed++]
274
+
275
+ if (c[2] === null) this.map.set(b4a.toString(c[0], 'hex'), c)
276
+ else this._indexRange(c)
277
+ }
278
+ }
279
+
280
+ _indexRange (range) {
281
+ const s = b4a.toString(range[0], 'hex')
282
+ const e = b4a.toString(range[2], 'hex')
283
+
284
+ for (const [key, c] of this.map) {
285
+ if (s <= key && key < e) this.map.set(key, [c[0], null, null])
286
+ }
287
+
288
+ if (this.cleared === null) this.cleared = []
289
+ this.cleared.push(range)
290
+ }
291
+
292
+ apply (changes) {
293
+ if (this.snap !== null) throw new Error('Illegal to push changes to a snapshot')
294
+
295
+ if (this.readers !== 0 && this.changes !== null) {
296
+ this.changes = this.changes.slice(0)
297
+ this.cleared = this.cleared === null ? null : this.cleared.slice(0)
298
+ this.map = this.map === null ? null : new Map([...this.map])
299
+ }
300
+
301
+ if (this.changes === null) {
302
+ this.changes = changes
303
+ return
304
+ }
305
+
306
+ for (let i = 0; i < changes.length; i++) {
307
+ this.changes.push(changes[i])
308
+ }
309
+ }
310
+
311
+ static async flush (changes, db) {
312
+ if (changes === null) return true
313
+
314
+ const w = db.write({ autoDestroy: true })
315
+
316
+ for (const [start, value, end] of changes) {
317
+ if (end !== null) w.tryDeleteRange(start, end)
318
+ else if (value !== null) w.tryPut(start, value)
319
+ else w.tryDelete(start)
320
+ }
321
+
322
+ await w.flush()
323
+
324
+ return true
325
+ }
326
+ }
327
+
328
+ module.exports = View
329
+
330
+ function cmpChange (a, b) {
331
+ const c = b4a.compare(a[0], b[0])
332
+ return c === 0 ? b4a.compare(a[2], b[2]) : c
333
+ }
334
+
335
+ function cmpChangeReverse (a, b) {
336
+ return cmpChange(b, a)
337
+ }
338
+
339
+ function noop () {}
340
+
341
+ function reverseArray (list) {
342
+ const r = new Array(list.length)
343
+ for (let i = 0; i < list.length; i++) r[r.length - 1 - i] = list[i]
344
+ return r
345
+ }