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/index.js CHANGED
@@ -1,1013 +1,697 @@
1
1
  const RocksDB = require('rocksdb-native')
2
- const c = require('compact-encoding')
3
- const { UINT } = require('index-encoder')
4
- const RW = require('read-write-mutexify')
5
- const b4a = require('b4a')
6
- const flat = require('flat-tree')
7
2
  const rrp = require('resolve-reject-promise')
8
- const queueTick = require('queue-tick')
9
- const assert = require('nanoassert')
10
-
11
- const m = require('./lib/messages')
12
- const DependencyStream = require('./lib/dependency-stream')
13
- const MemoryOverlay = require('./lib/memory-overlay')
14
-
15
- const INF = b4a.from([0xff])
16
-
17
- // <TL_INFO> = { version, free, total }
18
- // <TL_LOCAL_SEED> = seed
19
- // <TL_CORE_INFO><discovery-key-32-bytes> = { version, owner, core, data }
20
-
21
- // <core><CORE_MANIFEST> = { key, manifest? }
22
- // <core><CORE_LOCAL_SEED> = seed
23
- // <core><CORE_ENCRYPTION_KEY> = encryptionKey // should come later, not important initially
24
- // <core><CORE_HEAD><data> = { fork, length, byteLength, signature }
25
- // <core><CORE_BATCHES><name> = <data>
26
-
27
- // <data><CORE_INFO> = { version }
28
- // <data><CORE_UPDATES> = { contiguousLength, blocks }
29
- // <data><CORE_DEPENDENCY = { data, length, roots }
30
- // <data><CORE_HINTS> = { reorg } // should come later, not important initially
31
- // <data><CORE_TREE><index> = { index, size, hash }
32
- // <data><CORE_BITFIELD><index> = <4kb buffer>
33
- // <data><CORE_BLOCKS><index> = <buffer>
34
- // <data><CORE_USER_DATA><key> = <value>
35
-
36
- // top level prefixes
37
- const TL = {
38
- STORAGE_INFO: 0,
39
- LOCAL_SEED: 1,
40
- DKEYS: 2,
41
- CORE: 3,
42
- DATA: 4,
43
- DEFAULT_KEY: 5
44
- }
45
-
46
- // core prefixes
47
- const CORE = {
48
- MANIFEST: 0,
49
- LOCAL_SEED: 1,
50
- ENCRYPTION_KEY: 2,
51
- HEAD: 3,
52
- BATCHES: 4
53
- }
54
-
55
- // data prefixes
56
- const DATA = {
57
- INFO: 0,
58
- UPDATES: 1,
59
- DEPENDENCY: 2,
60
- HINTS: 3,
61
- TREE: 4,
62
- BITFIELD: 5,
63
- BLOCK: 6,
64
- USER_DATA: 7
65
- }
66
-
67
- const SLAB = {
68
- start: 0,
69
- end: 65536,
70
- buffer: b4a.allocUnsafe(65536)
71
- }
72
-
73
- // PREFIX + BATCH + TYPE + INDEX
74
-
75
- class WriteBatch {
76
- constructor (storage, write, atom) {
77
- this.storage = storage
78
- this.write = write
79
- this.atom = atom
80
- }
81
-
82
- setCoreHead (head) {
83
- this.write.tryPut(encodeCoreIndex(this.storage.corePointer, CORE.HEAD, this.storage.dataPointer), c.encode(m.CoreHead, head))
84
- }
85
-
86
- setCoreAuth ({ key, manifest }) {
87
- this.write.tryPut(encodeCoreIndex(this.storage.corePointer, CORE.MANIFEST), c.encode(m.CoreAuth, { key, manifest }))
88
- }
89
-
90
- setBatchPointer (name, pointer) {
91
- this.write.tryPut(encodeBatch(this.storage.corePointer, CORE.BATCHES, name), encode(m.DataPointer, pointer))
92
- }
93
-
94
- setDataDependency ({ data, length }) {
95
- this.write.tryPut(encodeDataIndex(this.storage.dataPointer, DATA.DEPENDENCY), encode(m.DataDependency, { data, length }))
96
- }
97
-
98
- setLocalKeyPair (keyPair) {
99
- this.write.tryPut(encodeCoreIndex(this.storage.corePointer, CORE.LOCAL_SEED), encode(m.KeyPair, keyPair))
100
- }
101
-
102
- setEncryptionKey (encryptionKey) {
103
- this.write.tryPut(encodeCoreIndex(this.storage.corePointer, CORE.ENCRYPTION_KEY), encryptionKey)
104
- }
105
-
106
- setDataInfo (info) {
107
- if (info.version !== 0) throw new Error('Version > 0 is not supported')
108
- this.write.tryPut(encodeDataIndex(this.storage.dataPointer, DATA.INFO), encode(m.DataInfo, info))
109
- }
110
-
111
- setUserData (key, value) {
112
- this.write.tryPut(encodeUserDataIndex(this.storage.dataPointer, DATA.USER_DATA, key), value)
113
- }
114
-
115
- putBlock (index, data) {
116
- this.write.tryPut(encodeDataIndex(this.storage.dataPointer, DATA.BLOCK, index), data)
117
- }
118
-
119
- deleteBlock (index) {
120
- this.write.tryDelete(encodeDataIndex(this.storage.dataPointer, DATA.BLOCK, index))
121
- }
122
-
123
- deleteBlockRange (start, end) {
124
- return this._deleteRange(DATA.BLOCK, start, end)
125
- }
3
+ const ScopeLock = require('scope-lock')
4
+ const View = require('./lib/view.js')
5
+
6
+ const VERSION = 1
7
+ const COLUMN_FAMILY = 'corestore'
8
+
9
+ const {
10
+ CorestoreRX,
11
+ CorestoreTX,
12
+ CoreTX,
13
+ CoreRX
14
+ } = require('./lib/tx.js')
15
+
16
+ const {
17
+ createCoreStream,
18
+ createAliasStream,
19
+ createBlockStream,
20
+ createBitfieldStream,
21
+ createUserDataStream,
22
+ createTreeNodeStream
23
+ } = require('./lib/streams.js')
24
+
25
+ const EMPTY = new View()
126
26
 
127
- putTreeNode (node) {
128
- this.write.tryPut(encodeDataIndex(this.storage.dataPointer, DATA.TREE, node.index), encode(m.TreeNode, node))
129
- }
130
-
131
- deleteTreeNode (index) {
132
- this.write.tryDelete(encodeDataIndex(this.storage.dataPointer, DATA.TREE, index))
133
- }
134
-
135
- deleteTreeNodeRange (start, end) {
136
- return this._deleteRange(DATA.TREE, start, end)
137
- }
138
-
139
- putBitfieldPage (index, page) {
140
- this.write.tryPut(encodeDataIndex(this.storage.dataPointer, DATA.BITFIELD, index), page)
141
- }
142
-
143
- deleteBitfieldPage (index) {
144
- this.write.tryDelete(encodeDataIndex(this.storage.dataPointer, DATA.BITFIELD, index))
27
+ class Atom {
28
+ constructor (db) {
29
+ this.db = db
30
+ this.view = new View()
31
+ this.flushes = []
145
32
  }
146
33
 
147
- deleteBitfieldPageRange (start, end) {
148
- return this._deleteRange(DATA.BITFIELD, start, end)
34
+ onflush (fn) {
35
+ this.flushes.push(fn)
149
36
  }
150
37
 
151
- _deleteRange (type, start, end) {
152
- const s = encodeDataIndex(this.storage.dataPointer, type, start)
153
- const e = encodeDataIndex(this.storage.dataPointer, type, end === -1 ? Infinity : end)
154
-
155
- return this.write.tryDeleteRange(s, e)
156
- }
38
+ async flush () {
39
+ await View.flush(this.view.changes, this.db)
40
+ this.view.reset()
157
41
 
158
- destroy () {
159
- if (this.atom) this.atom.destroy()
160
- else this.write.destroy()
161
- }
42
+ const promises = []
43
+ while (this.flushes.length) promises.push(this.flushes.pop()())
162
44
 
163
- flush () {
164
- if (this.atom) return this.atom.flush()
165
- return flushAndDestroy(this.write)
45
+ await Promise.all(promises)
166
46
  }
167
47
  }
168
48
 
169
- class ReadBatch {
170
- constructor (storage, read) {
171
- this.storage = storage
172
- this.read = read
173
- }
174
-
175
- async getCoreHead () {
176
- return this._get(encodeCoreIndex(this.storage.corePointer, CORE.HEAD, this.storage.dataPointer), m.CoreHead)
177
- }
178
-
179
- async getCoreAuth () {
180
- return this._get(encodeCoreIndex(this.storage.corePointer, CORE.MANIFEST), m.CoreAuth)
181
- }
182
-
183
- async getLocalKeyPair () {
184
- return this._get(encodeCoreIndex(this.storage.corePointer, CORE.LOCAL_SEED), m.KeyPair)
185
- }
186
-
187
- async getEncryptionKey () {
188
- return this._get(encodeCoreIndex(this.storage.corePointer, CORE.ENCRYPTION_KEY), null)
189
- }
190
-
191
- getDataDependency () {
192
- return this._get(encodeDataIndex(this.storage.dataPointer, DATA.DEPENDENCY), m.DataDependency)
193
- }
49
+ class HypercoreStorage {
50
+ constructor (store, db, core, view, atomic) {
51
+ this.store = store
52
+ this.db = db
53
+ this.core = core
54
+ this.view = view
55
+ this.atomic = atomic
194
56
 
195
- getDataInfo (info) {
196
- return this._get(encodeDataIndex(this.storage.dataPointer, DATA.INFO), m.DataInfo)
57
+ this.view.readStart()
197
58
  }
198
59
 
199
- getUserData (key) {
200
- return this._get(encodeUserDataIndex(this.storage.dataPointer, DATA.USER_DATA, key), null)
60
+ get dependencies () {
61
+ return this.core.dependencies
201
62
  }
202
63
 
203
- async hasBlock (index) {
204
- return this._has(encodeDataIndex(this.storage.dataPointer, DATA.BLOCK, index))
64
+ getDependencyLength () {
65
+ return this.core.dependencies.length
66
+ ? this.core.dependencies[this.core.dependencies.length - 1].length
67
+ : -1
205
68
  }
206
69
 
207
- async getBlock (index, error) {
208
- const dependency = findBlockDependency(this.storage.dependencies, index)
209
- const dataPointer = dependency !== null ? dependency : this.storage.dataPointer
210
-
211
- const key = encodeDataIndex(dataPointer, DATA.BLOCK, index)
212
- const block = await this._get(key, null)
213
-
214
- if (block === null && error === true) {
215
- throw new Error('Node not found: ' + index)
70
+ getDependency (length) {
71
+ for (let i = this.core.dependencies.length - 1; i >= 0; i--) {
72
+ const dep = this.core.dependencies[i]
73
+ if (dep.length < length) return dep
216
74
  }
217
75
 
218
- return block
219
- }
220
-
221
- async hasTreeNode (index) {
222
- return this._has(encodeDataIndex(this.storage.dataPointer, DATA.TREE, index))
76
+ return null
223
77
  }
224
78
 
225
- async getTreeNode (index, error) {
226
- const dependency = findTreeDependency(this.storage.dependencies, index)
227
- const dataPointer = dependency !== null ? dependency : this.storage.dataPointer
228
-
229
- const key = encodeDataIndex(dataPointer, DATA.TREE, index)
230
- const node = await this._get(key, m.TreeNode)
79
+ // TODO: this might have to be async if the dependents have changed, but prop ok for now
80
+ updateDependencyLength (length) {
81
+ const deps = this.core.dependencies
231
82
 
232
- if (node === null && error === true) {
233
- throw new Error('Node not found: ' + index)
83
+ for (let i = deps.length - 1; i >= 0; i--) {
84
+ if (deps[i].length >= length) continue
85
+ deps[i].length = length
86
+ this.core.dependencies = deps.slice(0, i + 1)
87
+ return
234
88
  }
235
89
 
236
- return node
90
+ throw new Error('Dependency not found')
237
91
  }
238
92
 
239
- async getBitfieldPage (index) {
240
- const key = encodeDataIndex(this.storage.dataPointer, DATA.BITFIELD, index)
241
- return this._get(key, null)
93
+ get snapshotted () {
94
+ return this.db._snapshot !== null
242
95
  }
243
96
 
244
- async _has (key) {
245
- return (await this.read.get(key)) !== null
97
+ snapshot () {
98
+ return new HypercoreStorage(this.store, this.db.snapshot(), this.core, this.view.snapshot(), this.atomic)
246
99
  }
247
100
 
248
- async _get (key, enc) {
249
- const buffer = await this.read.get(key)
250
- if (buffer === null) return null
251
-
252
- if (enc) return c.decode(enc, buffer)
253
-
254
- return buffer
101
+ atomize (atom) {
102
+ return new HypercoreStorage(this.store, this.db.session(), this.core, atom.view, true)
255
103
  }
256
104
 
257
- destroy () {
258
- this.read.destroy()
105
+ atom () {
106
+ return this.store.atom()
259
107
  }
260
108
 
261
- flush () {
262
- return flushAndDestroy(this.read)
109
+ createBlockStream (opts) {
110
+ return createBlockStream(this.core, this.db, this.view, opts)
263
111
  }
264
112
 
265
- tryFlush () {
266
- tryFlushAndDestroy(this.read)
113
+ createTreeNodeStream (opts) {
114
+ return createTreeNodeStream(this.core, this.db, this.view, opts)
267
115
  }
268
- }
269
116
 
270
- class Lock {
271
- constructor (mutex) {
272
- this.mutex = mutex
273
- this.refs = 0
274
- this.promise = null
117
+ createBitfieldStream (opts) {
118
+ return createBitfieldStream(this.core, this.db, this.view, opts)
275
119
  }
276
120
 
277
- acquire () {
278
- if (this.refs++ === 0) this.promise = this.mutex.lock()
279
- return this
121
+ createUserDataStream (opts) {
122
+ return createUserDataStream(this.core, this.db, this.view, opts)
280
123
  }
281
124
 
282
- release () {
283
- if (--this.refs === 0) return this.mutex.unlock()
284
- }
285
- }
125
+ async resumeSession (name) {
126
+ const rx = this.read()
127
+ const existingSessionsPromise = rx.getSessions()
286
128
 
287
- class Atom {
288
- constructor (db) {
289
- this.db = db
290
- this.batch = null
291
- this.refs = 0
292
- this.destroyed = false
293
- this.flushing = null
294
- this.resolve = null
295
- this.reject = null
296
-
297
- this._executing = null
298
- this._waiting = []
299
- this._queue = []
300
- this._enqueue = (lock, resolve, reject) => this._queue.push({ lock, resolve, reject })
301
- }
129
+ rx.tryFlush()
130
+ const existingSessions = await existingSessionsPromise
302
131
 
303
- enter () {
304
- this.refs++
305
- }
132
+ const sessions = existingSessions || []
133
+ const session = getBatch(sessions, name, false)
306
134
 
307
- exit () {
308
- if (--this.refs === 0) this._commit()
309
- }
135
+ if (session === null) return null
310
136
 
311
- acquire (mutex) {
312
- const lock = this._lock(mutex)
313
- return new Promise(this._enqueue.bind(this, lock))
314
- }
315
-
316
- _lock (mutex) {
317
- for (const lock of this._waiting) {
318
- if (lock.mutex === mutex) return lock.acquire()
137
+ const core = {
138
+ version: this.core.version,
139
+ corePointer: this.core.corePointer,
140
+ dataPointer: session.dataPointer,
141
+ dependencies: []
319
142
  }
320
143
 
321
- const lock = new Lock(mutex)
322
- this._waiting.push(lock)
144
+ const coreRx = new CoreRX(core, this.db, this.view)
323
145
 
324
- if (this._executing === null) this._executing = this._execute()
146
+ const dependencyPromise = coreRx.getDependency()
147
+ coreRx.tryFlush()
325
148
 
326
- return lock.acquire()
327
- }
149
+ const dependency = await dependencyPromise
150
+ if (dependency) core.dependencies = this._addDependency(dependency)
328
151
 
329
- async _execute () {
330
- this.enter()
331
- for (const { promise } of this._waiting) await promise
152
+ return new HypercoreStorage(this.store, this.db.session(), core, this.atomic ? this.view : new View(), this.atomic)
153
+ }
332
154
 
333
- const queue = this._queue
155
+ async createSession (name, head, atom) {
156
+ const rx = this.read(atom)
334
157
 
335
- this._waiting = []
336
- this._queue = []
337
- this._executing = null
158
+ const existingSessionsPromise = rx.getSessions()
159
+ const existingHeadPromise = rx.getHead()
338
160
 
339
- for (const { lock, resolve, reject } of queue) {
340
- if (this.destroyed) reject(new Error('Atomizer destroyed'))
341
- else resolve(lock)
342
- }
161
+ rx.tryFlush()
343
162
 
344
- this._ensureTick() // allow caller to enter
345
- this.exit()
346
- }
347
-
348
- createBatch () {
349
- if (this.refs === 0) this._ensureTick()
350
- this.enter()
351
- if (this.batch === null) this.batch = this.db.write()
352
- return this.batch
353
- }
163
+ const [existingSessions, existingHead] = await Promise.all([existingSessionsPromise, existingHeadPromise])
164
+ if (head === null) head = existingHead
354
165
 
355
- _ensureTick () {
356
- this.enter()
357
- queueTick(() => this.exit())
358
- }
166
+ if (existingHead !== null && head.length > existingHead.length) {
167
+ throw new Error('Invalid head passed, ahead of core')
168
+ }
359
169
 
360
- async _commit () {
361
- if (this.batch === null) return
170
+ const sessions = existingSessions || []
171
+ const session = getBatch(sessions, name, true)
362
172
 
363
- const batch = this.batch
364
- const resolve = this.resolve
365
- const reject = this.reject
173
+ if (session.dataPointer === -1) {
174
+ session.dataPointer = await this.store._allocData()
175
+ }
366
176
 
367
- this.batch = null
368
- this.flushing = null
369
- this.resolve = this.reject = null
177
+ const tx = this.write(atom)
370
178
 
371
- if (this.destroyed) {
372
- this.destroyed = false
373
- batch.destroy()
374
- if (reject !== null) reject(new Error('Atomic batch destroyed'))
375
- return
376
- }
179
+ tx.setSessions(sessions)
377
180
 
378
- try {
379
- await batch.flush()
380
- } catch (err) {
381
- batch.destroy()
382
- reject(err)
383
- return
181
+ const length = head === null ? 0 : head.length
182
+ const core = {
183
+ version: this.core.version,
184
+ corePointer: this.core.corePointer,
185
+ dataPointer: session.dataPointer,
186
+ dependencies: this._addDependency({ dataPointer: this.core.dataPointer, length })
384
187
  }
385
188
 
386
- batch.destroy()
387
- resolve()
388
- }
389
-
390
- _createFlushing () {
391
- if (this.flushing !== null) return this.flushing
189
+ const coreTx = new CoreTX(core, this.db, tx.view, tx.changes)
392
190
 
393
- const { resolve, reject, promise } = rrp()
191
+ if (length > 0) coreTx.setHead(head)
192
+ coreTx.setDependency(core.dependencies[core.dependencies.length - 1])
394
193
 
395
- this.flushing = promise
396
- this.resolve = resolve
397
- this.reject = reject
194
+ await tx.flush()
398
195
 
399
- return this.flushing
196
+ return new HypercoreStorage(this.store, this.db.session(), core, atom ? atom.view : this.atomic ? this.view : new View(), !!atom || this.atomic)
400
197
  }
401
198
 
402
- destroy () {
403
- this.destroyed = true
404
- this.exit()
405
- }
199
+ async createAtomicSession (atom, head) {
200
+ const length = head === null ? 0 : head.length
201
+ const core = {
202
+ version: this.core.version,
203
+ corePointer: this.core.corePointer,
204
+ dataPointer: this.core.dataPointer,
205
+ dependencies: this._addDependency({ dataPointer: this.core.dataPointer, length })
206
+ }
406
207
 
407
- flush () {
408
- const flushing = this._createFlushing()
409
- this.exit()
410
- return flushing
411
- }
412
- }
208
+ const coreTx = new CoreTX(core, this.db, atom.view, [])
413
209
 
414
- module.exports = class CoreStorage {
415
- constructor (dir) {
416
- this.db = typeof dir === 'object' ? dir : new RocksDB(dir)
417
- this.mutex = new RW()
418
- }
210
+ if (length > 0) coreTx.setHead(head)
211
+ coreTx.setDependency(core.dependencies[core.dependencies.length - 1])
419
212
 
420
- static isCoreStorage (s) {
421
- return !!s && typeof s.setLocalSeed === 'function'
422
- }
213
+ await coreTx.flush()
423
214
 
424
- // just a helper to make tests easier
425
- static async clear (dir) {
426
- const s = new this(dir)
427
- await s.clear()
428
- return s
215
+ return this.atomize(atom)
429
216
  }
430
217
 
431
- async setLocalSeed (seed, overwrite) {
432
- if (!overwrite) {
433
- const existing = await getLocalSeed(this.db)
434
- if (existing) return b4a.equals(existing, seed)
435
- }
218
+ _addDependency (dep) {
219
+ const deps = []
436
220
 
437
- await this.mutex.write.lock()
221
+ for (let i = 0; i < this.core.dependencies.length; i++) {
222
+ const d = this.core.dependencies[i]
438
223
 
439
- try {
440
- const b = this.db.write()
441
- b.tryPut(b4a.from([TL.LOCAL_SEED]), seed)
442
- await flushAndDestroy(b)
224
+ if (d.length > dep.length) {
225
+ deps.push({ dataPointer: d.dataPointer, length: dep.length })
226
+ return deps
227
+ }
443
228
 
444
- return true
445
- } finally {
446
- this.mutex.write.unlock()
229
+ deps.push(d)
447
230
  }
448
- }
449
231
 
450
- getLocalSeed () {
451
- return getLocalSeed(this.db)
232
+ deps.push(dep)
233
+ return deps
452
234
  }
453
235
 
454
- info () {
455
- return getStorageInfo(this.db)
236
+ read (atom) {
237
+ return new CoreRX(this.core, this.db, atom ? atom.view : this.view)
456
238
  }
457
239
 
458
- list () {
459
- const s = this.db.iterator({
460
- gt: b4a.from([TL.DKEYS]),
461
- lt: b4a.from([TL.DKEYS + 1])
462
- })
463
-
464
- s._readableState.map = mapOnlyDiscoveryKey
465
- return s
240
+ write (atom) {
241
+ return new CoreTX(this.core, this.db, atom ? atom.view : this.atomic ? this.view : null, [])
466
242
  }
467
243
 
468
- async idle () {
469
- if (this.isIdle()) return
244
+ close () {
245
+ if (this.view !== null) {
246
+ this.view.readStop()
247
+ this.view = null
248
+ }
470
249
 
471
- do {
472
- await new Promise(setImmediate)
473
- await this.db.idle()
474
- await new Promise(setImmediate)
475
- } while (!this.isIdle())
250
+ return this.db.close()
476
251
  }
252
+ }
477
253
 
478
- isIdle () {
479
- return this.db.isIdle()
254
+ class CorestoreStorage {
255
+ constructor (db) {
256
+ this.path = typeof db === 'string' ? db : db.path
257
+ this.rocks = typeof db === 'string' ? new RocksDB(db) : db
258
+ this.db = createColumnFamily(this.rocks)
259
+ this.view = null
260
+ this.enters = 0
261
+ this.lock = new ScopeLock()
262
+ this.flushing = null
263
+ this.version = 0
264
+ this.migrating = null
480
265
  }
481
266
 
482
- ready () {
483
- return this.db.ready()
267
+ get opened () {
268
+ return this.db.opened
484
269
  }
485
270
 
486
- close () {
487
- return this.db.close()
271
+ get closed () {
272
+ return this.db.closed
488
273
  }
489
274
 
490
- atom () {
491
- return new Atom(this.db)
275
+ async ready () {
276
+ if (this.version === 0) await this._migrateStore()
277
+ return this.db.ready()
492
278
  }
493
279
 
494
- async clear () {
495
- const b = this.db.write()
496
- b.tryDeleteRange(b4a.from([TL.STORAGE_INFO]), INF)
497
- await flushAndDestroy(b)
280
+ static isCoreStorage (db) {
281
+ return isCorestoreStorage(db)
498
282
  }
499
283
 
500
- async has (discoveryKey) {
501
- return !!(await this.db.get(encodeDiscoveryKey(discoveryKey)))
284
+ static from (db) {
285
+ if (isCorestoreStorage(db)) return db
286
+ return new this(db)
502
287
  }
503
288
 
504
- async resume (discoveryKey) {
505
- if (!discoveryKey) {
506
- discoveryKey = await getDefaultKey(this.db)
507
- if (!discoveryKey) return null
289
+ async _flush () {
290
+ while (this.enters > 0) {
291
+ await this.lock.lock()
292
+ await this.lock.unlock()
508
293
  }
509
-
510
- const val = await this.db.get(encodeDiscoveryKey(discoveryKey))
511
- if (val === null) return null
512
-
513
- const { core, data } = c.decode(m.CorePointer, val)
514
-
515
- return new HypercoreStorage(this, discoveryKey, core, data, null)
516
294
  }
517
295
 
518
- async create ({ key, manifest, keyPair, encryptionKey, discoveryKey, userData }) {
519
- await this.mutex.write.lock()
296
+ // runs pre any other mutation and read
297
+ async _migrateStore () {
298
+ const view = await this._enter()
520
299
 
521
300
  try {
522
- const existing = await this.resume(discoveryKey)
301
+ if (this.version === VERSION) return
523
302
 
524
- if (existing) {
525
- // todo: verify key/manifest etc.
526
- return existing
527
- }
303
+ const rx = new CorestoreRX(this.db, view)
304
+ const headPromise = rx.getHead()
528
305
 
529
- if (!key) throw new Error('No key was provided')
306
+ rx.tryFlush()
307
+ const head = await headPromise
530
308
 
531
- let info = await getStorageInfo(this.db)
309
+ const version = head === null ? 0 : head.version
310
+ if (version === VERSION) return
532
311
 
533
- const write = this.db.write()
312
+ const target = { version: VERSION, dryRun: false }
534
313
 
535
- if (!info) {
536
- write.tryPut(b4a.from([TL.DEFAULT_KEY]), discoveryKey)
537
- info = { free: 0, total: 0 }
314
+ switch (version) {
315
+ case 0: {
316
+ await require('./migrations/0').store(this, target)
317
+ break
318
+ }
319
+ default: {
320
+ throw new Error('Unsupported version: ' + version + ' - you should probably upgrade your dependencies')
321
+ }
538
322
  }
539
323
 
540
- const core = info.total++
541
- const data = info.free++
542
-
543
- write.tryPut(encodeDiscoveryKey(discoveryKey), encode(m.CorePointer, { core, data }))
544
- write.tryPut(b4a.from([TL.STORAGE_INFO]), encode(m.StorageInfo, info))
545
-
546
- const storage = new HypercoreStorage(this, discoveryKey, core, data, null)
547
- const batch = new WriteBatch(storage, write, null)
548
-
549
- initialiseCoreInfo(batch, { key, manifest, keyPair, encryptionKey })
550
- initialiseCoreData(batch, { userData })
551
-
552
- await batch.flush()
553
- return storage
324
+ this.version = VERSION
554
325
  } finally {
555
- this.mutex.write.unlock()
326
+ await this._exit()
556
327
  }
557
328
  }
558
- }
559
-
560
- class HypercoreStorage {
561
- constructor (root, discoveryKey, core, data, snapshot) {
562
- this.root = root
563
- this.db = root.db
564
- this.dbSnapshot = snapshot
565
- this.dbRead = snapshot || this.db
566
- this.mutex = root.mutex
567
-
568
- this.discoveryKey = discoveryKey
569
-
570
- this.dependencies = []
571
-
572
- // pointers
573
- this.corePointer = core
574
- this.dataPointer = data
575
-
576
- this.destroyed = false
577
- }
578
-
579
- get snapshotted () {
580
- return this.dbSnapshot !== null
581
- }
582
329
 
583
- atom () {
584
- return this.root.atom()
585
- }
330
+ // runs pre the core is returned to the user
331
+ async _migrateCore (core, discoveryKey, locked) {
332
+ const view = locked ? this.view : await this._enter()
586
333
 
587
- dependencyLength () {
588
- return this.dependencies.length
589
- ? this.dependencies[this.dependencies.length - 1].length
590
- : -1
591
- }
592
-
593
- async openBatch (name) {
594
- const existing = await this.db.get(encodeBatch(this.corePointer, CORE.BATCHES, name))
595
- if (!existing) return null
596
-
597
- const storage = new HypercoreStorage(this.root, this.discoveryKey, this.corePointer, this.dataPointer, this.dbSnapshot)
598
- const dataPointer = c.decode(m.DataPointer, existing)
599
-
600
- storage.dataPointer = dataPointer
601
- storage.dependencies = await addDependencies(this.db, storage.dataPointer, -1)
602
-
603
- return storage
604
- }
605
-
606
- async registerBatch (name, head, atom) {
607
- await this.mutex.write.lock()
608
-
609
- const storage = new HypercoreStorage(this.root, this.discoveryKey, this.corePointer, this.dataPointer, null)
334
+ const version = core.core.version
610
335
 
611
336
  try {
612
- const info = await getStorageInfo(this.db)
613
- const write = atom ? atom.createBatch() : this.db.write()
614
-
615
- storage.dataPointer = info.free++
337
+ if (version === VERSION) return
338
+
339
+ const target = { version: VERSION, dryRun: false }
340
+
341
+ switch (version) {
342
+ case 0: {
343
+ await require('./migrations/0').core(core, target)
344
+ break
345
+ }
346
+ default: {
347
+ throw new Error('Unsupported version: ' + version + ' - you should probably upgrade your dependencies')
348
+ }
349
+ }
616
350
 
617
- write.tryPut(b4a.from([TL.STORAGE_INFO]), encode(m.StorageInfo, info))
351
+ core.core.version = VERSION
618
352
 
619
- const batch = new WriteBatch(storage, write, null)
353
+ if (locked === false) return
620
354
 
621
- initialiseCoreData(batch)
355
+ // if its locked, then move the core state into the memview
356
+ // in case the core is reopened from the memview, pre flush
622
357
 
623
- batch.setDataDependency({ data: this.dataPointer, length: head.length })
624
- batch.setBatchPointer(name, storage.dataPointer)
625
- if (head.rootHash) batch.setCoreHead(head) // if no root hash its the empty core - no head yet
358
+ const rx = new CorestoreRX(this.db, EMPTY)
359
+ const tx = new CorestoreTX(view)
626
360
 
627
- if (atom) await atom.flush()
628
- else await flushAndDestroy(write)
361
+ const corePromise = rx.getCore(discoveryKey)
362
+ rx.tryFlush()
629
363
 
630
- storage.dependencies = await addDependencies(this.db, storage.dataPointer, head.length)
631
- return storage
364
+ tx.putCore(discoveryKey, await corePromise)
365
+ tx.apply()
632
366
  } finally {
633
- this.mutex.write.unlock()
367
+ if (!locked) await this._exit()
634
368
  }
635
369
  }
636
370
 
637
- async registerOverlay (head) {
638
- const storage = new MemoryOverlay(this)
639
- const batch = storage.createWriteBatch()
640
-
641
- batch.setDataDependency({ data: this.dataPointer, length: head.length })
642
- if (head.rootHash) batch.setCoreHead(head) // if no root hash its the empty core - no head yet
643
-
644
- await batch.flush()
645
-
646
- return storage
371
+ async _enter () {
372
+ this.enters++
373
+ await this.lock.lock()
374
+ if (this.view === null) this.view = new View()
375
+ return this.view
647
376
  }
648
377
 
649
- createMemoryOverlay () {
650
- return new MemoryOverlay(this)
651
- }
378
+ async _exit () {
379
+ this.enters--
652
380
 
653
- snapshot () {
654
- assert(this.destroyed === false)
655
- const s = new HypercoreStorage(this.root, this.discoveryKey, this.corePointer, this.dataPointer, this.dbRead.snapshot())
381
+ if (this.flushing === null) this.flushing = rrp()
382
+ const flushed = this.flushing.promise
656
383
 
657
- for (const { data, length } of this.dependencies) s.dependencies.push({ data, length })
384
+ if (this.enters === 0 || this.view.size() > 128) {
385
+ try {
386
+ await View.flush(this.view.changes, this.db)
387
+ this.flushing.resolve()
388
+ } catch (err) {
389
+ this.flushing.reject(err)
390
+ } finally {
391
+ this.flushing = null
392
+ this.view = null
393
+ }
394
+ }
658
395
 
659
- return s
396
+ this.lock.unlock()
397
+ return flushed
660
398
  }
661
399
 
662
- findDependency (length) {
663
- for (let i = this.dependencies.length - 1; i >= 0; i--) {
664
- const dep = this.dependencies[i]
665
- if (dep.length < length) return dep
666
- }
400
+ // when used with core catches this isnt transactional for simplicity, HOWEVER, its just a number
401
+ // so worth the tradeoff
402
+ async _allocData () {
403
+ let dataPointer = 0
667
404
 
668
- return null
669
- }
405
+ const view = await this._enter()
406
+ const tx = new CorestoreTX(view)
670
407
 
671
- updateDependencies (length) {
672
- const deps = this.dependencies
408
+ try {
409
+ const head = await this._getHead(view)
673
410
 
674
- for (let i = deps.length - 1; i >= 0; i--) {
675
- if (deps[i].length < length) {
676
- deps[i].length = length
677
- this.dependencies = deps.slice(0, i + 1)
678
- return
679
- }
411
+ dataPointer = head.allocated.datas++
412
+
413
+ tx.setHead(head)
414
+ tx.apply()
415
+ } finally {
416
+ await this._exit()
680
417
  }
681
418
 
682
- throw new Error('Dependency not found')
419
+ return dataPointer
683
420
  }
684
421
 
685
- createReadBatch (opts) {
686
- assert(this.destroyed === false)
422
+ // exposes here so migrations can easily access the head in an init state
423
+ async _getHead (view) {
424
+ const rx = new CorestoreRX(this.db, view)
425
+ const headPromise = rx.getHead()
426
+ rx.tryFlush()
687
427
 
688
- return new ReadBatch(this, this.dbRead.read())
428
+ const head = await headPromise
429
+ return head === null ? initStoreHead() : head
689
430
  }
690
431
 
691
- createWriteBatch (atom) {
692
- assert(this.destroyed === false)
693
-
694
- if (atom) return new WriteBatch(this, atom.createBatch(), atom)
695
-
696
- return new WriteBatch(this, this.db.write(), null)
432
+ atom () {
433
+ return new Atom(this.db)
697
434
  }
698
435
 
699
- // helper for atomic flows
700
- async createWriteBatchAndLock (atomizer, lock) {
701
- assert(this.destroyed === false)
702
- if (atomizer) atomizer.enter()
436
+ async close () {
437
+ if (this.db.closed) return
438
+ await this._flush()
439
+ await this.db.close()
440
+ await this.rocks.close()
441
+ }
703
442
 
704
- try {
705
- await lock.lock()
706
- } catch (err) {
707
- if (atomizer) atomizer.exit()
708
- throw err
709
- }
443
+ async clear () {
444
+ if (this.version === 0) await this._migrateStore()
710
445
 
711
- const batch = this.createWriteBatch(atomizer)
712
- if (atomizer) atomizer.exit()
446
+ const view = await this._enter()
447
+ const tx = new CorestoreTX(view)
713
448
 
714
- return batch
715
- }
449
+ tx.clear()
450
+ tx.apply()
716
451
 
717
- createBlockStream (opts = {}) {
718
- assert(this.destroyed === false)
719
- return createStream(this, createBlockStream, opts)
452
+ await this._exit()
720
453
  }
721
454
 
722
- createUserDataStream (opts = {}) {
723
- assert(this.destroyed === false)
724
-
725
- const r = encodeUserDataRange(this.dataPointer, DATA.USER_DATA, opts)
726
- const s = this.dbRead.iterator(r)
727
- s._readableState.map = mapStreamUserData
728
- return s
455
+ createCoreStream () {
456
+ // TODO: be nice to run the mgiration here also, but too much plumbing atm
457
+ return createCoreStream(this.db, EMPTY)
729
458
  }
730
459
 
731
- createTreeNodeStream (opts = {}) {
732
- assert(this.destroyed === false)
733
-
734
- const r = encodeIndexRange(this.dataPointer, DATA.TREE, opts)
735
- const s = this.dbRead.iterator(r)
736
- s._readableState.map = mapStreamTreeNode
737
- return s
460
+ createAliasStream (namespace) {
461
+ // TODO: be nice to run the mgiration here also, but too much plumbing atm
462
+ return createAliasStream(this.db, EMPTY, namespace)
738
463
  }
739
464
 
740
- createBitfieldPageStream (opts = {}) {
741
- assert(this.destroyed === false)
465
+ async getAlias (alias) {
466
+ if (this.version === 0) await this._migrateStore()
742
467
 
743
- const r = encodeIndexRange(this.dataPointer, DATA.BITFIELD, opts)
744
- const s = this.dbRead.iterator(r)
745
- s._readableState.map = mapStreamBitfieldPage
746
- return s
468
+ const rx = new CorestoreRX(this.db, EMPTY)
469
+ const discoveryKeyPromise = rx.getCoreByAlias(alias)
470
+ rx.tryFlush()
471
+ return discoveryKeyPromise
747
472
  }
748
473
 
749
- async peekLastTreeNode () {
750
- assert(this.destroyed === false)
474
+ async getSeed () {
475
+ if (this.version === 0) await this._migrateStore()
751
476
 
752
- const last = await this.dbRead.peek(encodeIndexRange(this.dataPointer, DATA.TREE, { reverse: true }))
753
- if (last === null) return null
754
- return c.decode(m.TreeNode, last.value)
755
- }
477
+ const rx = new CorestoreRX(this.db, EMPTY)
478
+ const headPromise = rx.getHead()
756
479
 
757
- async peekLastBitfieldPage () {
758
- assert(this.destroyed === false)
480
+ rx.tryFlush()
759
481
 
760
- const last = await this.dbRead.peek(encodeIndexRange(this.dataPointer, DATA.BITFIELD, { reverse: true }))
761
- if (last === null) return null
762
- return mapStreamBitfieldPage(last)
482
+ const head = await headPromise
483
+ return head === null ? null : head.seed
763
484
  }
764
485
 
765
- destroy () {
766
- if (this.destroyed) return
767
- this.destroyed = true
486
+ async setSeed (seed, { overwrite = true } = {}) {
487
+ if (this.version === 0) await this._migrateStore()
768
488
 
769
- if (this.dbSnapshot) this.dbSnapshot.close().catch(noop)
770
- this.dbSnapshot = null
771
- }
772
- }
489
+ const view = await this._enter()
490
+ const tx = new CorestoreTX(view)
773
491
 
774
- function createStream (storage, createStreamType, opts) {
775
- return storage.dependencies.length === 0
776
- ? createStreamType(storage.db, storage.dataPointer, opts)
777
- : new DependencyStream(storage, createStreamType, opts)
778
- }
492
+ try {
493
+ const rx = new CorestoreRX(this.db, view)
494
+ const headPromise = rx.getHead()
779
495
 
780
- function createBlockStream (db, data, opts) {
781
- const r = encodeIndexRange(data, DATA.BLOCK, opts)
782
- const s = db.iterator(r)
783
- s._readableState.map = mapStreamBlock
784
- return s
785
- }
496
+ rx.tryFlush()
786
497
 
787
- function mapStreamUserData (data) {
788
- const state = { start: 0, end: data.key.byteLength, buffer: data.key }
498
+ const head = (await headPromise) || initStoreHead()
789
499
 
790
- UINT.decode(state) // TL.DATA
791
- UINT.decode(state) // pointer
792
- UINT.decode(state) // DATA.USER_DATA
500
+ if (head.seed === null || overwrite) head.seed = seed
501
+ tx.setHead(head)
502
+ tx.apply()
793
503
 
794
- const key = c.string.decode(state)
504
+ return head.seed
505
+ } finally {
506
+ await this._exit()
507
+ }
508
+ }
795
509
 
796
- if (data.value.byteLength === 0) return null
510
+ async getDefaultDiscoveryKey () {
511
+ if (this.version === 0) await this._migrateStore()
797
512
 
798
- return { key, value: data.value }
799
- }
513
+ const rx = new CorestoreRX(this.db, EMPTY)
514
+ const headPromise = rx.getHead()
800
515
 
801
- function mapStreamTreeNode (data) {
802
- return c.decode(m.TreeNode, data.value)
803
- }
516
+ rx.tryFlush()
804
517
 
805
- function mapStreamBitfieldPage (data) {
806
- const state = { start: 0, end: data.key.byteLength, buffer: data.key }
518
+ const head = await headPromise
519
+ return head === null ? null : head.defaultDiscoveryKey
520
+ }
807
521
 
808
- UINT.decode(state) // TL.DATA
809
- UINT.decode(state) // pointer
810
- UINT.decode(state) // DATA.BITFIELD
522
+ async setDefaultDiscoveryKey (discoveryKey, { overwrite = true } = {}) {
523
+ if (this.version === 0) await this._migrateStore()
811
524
 
812
- const index = UINT.decode(state)
525
+ const view = await this._enter()
526
+ const tx = new CorestoreTX(view)
813
527
 
814
- return { index, page: data.value }
815
- }
528
+ try {
529
+ const rx = new CorestoreRX(this.db, view)
530
+ const headPromise = rx.getHead()
816
531
 
817
- function mapStreamBlock (data) {
818
- const state = { start: 0, end: data.key.byteLength, buffer: data.key }
532
+ rx.tryFlush()
819
533
 
820
- UINT.decode(state) // TL.DATA
821
- UINT.decode(state) // pointer
822
- UINT.decode(state) // DATA.BITFIELD
534
+ const head = (await headPromise) || initStoreHead()
823
535
 
824
- const index = UINT.decode(state)
825
- return { index, value: data.value }
826
- }
536
+ if (head.defaultDiscoveryKey === null || overwrite) head.defaultDiscoveryKey = discoveryKey
537
+ tx.setHead(head)
538
+ tx.apply()
827
539
 
828
- function mapOnlyDiscoveryKey (data) {
829
- return data.key.subarray(1)
830
- }
540
+ return head.defaultDiscoveryKey
541
+ } finally {
542
+ await this._exit()
543
+ }
544
+ }
831
545
 
832
- async function getDefaultKey (db) {
833
- return db.get(b4a.from([TL.DEFAULT_KEY]))
834
- }
546
+ async has (discoveryKey) {
547
+ if (this.version === 0) await this._migrateStore()
835
548
 
836
- async function getLocalSeed (db) {
837
- return db.get(b4a.from([TL.LOCAL_SEED]))
838
- }
549
+ const rx = new CorestoreRX(this.db, EMPTY)
550
+ const promise = rx.getCore(discoveryKey)
839
551
 
840
- async function getStorageInfo (db) {
841
- const value = await db.get(b4a.from([TL.STORAGE_INFO]))
842
- if (value === null) return null
843
- return c.decode(m.StorageInfo, value)
844
- }
552
+ rx.tryFlush()
845
553
 
846
- function ensureSlab (size) {
847
- if (SLAB.buffer.byteLength - SLAB.start < size) {
848
- SLAB.buffer = b4a.allocUnsafe(SLAB.end)
849
- SLAB.start = 0
554
+ return (await promise) !== null
850
555
  }
851
556
 
852
- return SLAB
853
- }
557
+ async resume (discoveryKey) {
558
+ if (this.version === 0) await this._migrateStore()
854
559
 
855
- function encodeIndexRange (pointer, type, opts) {
856
- const bounded = { gt: null, gte: null, lte: null, lt: null, reverse: !!opts.reverse, limit: toLimit(opts.limit) }
560
+ if (!discoveryKey) {
561
+ discoveryKey = await this.getDefaultDiscoveryKey()
562
+ if (!discoveryKey) return null
563
+ }
857
564
 
858
- if (opts.gt || opts.gt === 0) bounded.gt = encodeDataIndex(pointer, type, opts.gt)
859
- else if (opts.gte) bounded.gte = encodeDataIndex(pointer, type, opts.gte)
860
- else bounded.gte = encodeDataIndex(pointer, type, 0)
565
+ const rx = new CorestoreRX(this.db, EMPTY)
566
+ const corePromise = rx.getCore(discoveryKey)
861
567
 
862
- if (opts.lt || opts.lt === 0) bounded.lt = encodeDataIndex(pointer, type, opts.lt)
863
- else if (opts.lte) bounded.lte = encodeDataIndex(pointer, type, opts.lte)
864
- else bounded.lte = encodeDataIndex(pointer, type, Infinity) // infinity
568
+ rx.tryFlush()
569
+ const core = await corePromise
865
570
 
866
- return bounded
867
- }
571
+ if (core === null) return null
572
+ return this._resumeFromPointers(EMPTY, discoveryKey, false, core)
573
+ }
868
574
 
869
- function encodeUserDataRange (pointer, type, opts) {
870
- const bounded = { gt: null, gte: null, lte: null, lt: null, reverse: !!opts.reverse, limit: toLimit(opts.limit) }
575
+ async _resumeFromPointers (view, discoveryKey, create, { version, corePointer, dataPointer }) {
576
+ const core = { version, corePointer, dataPointer, dependencies: [] }
871
577
 
872
- if (opts.gt || opts.gt === 0) bounded.gt = encodeUserDataIndex(pointer, type, opts.gt)
873
- else if (opts.gte) bounded.gte = encodeUserDataIndex(pointer, type, opts.gte)
874
- else bounded.gte = encodeDataIndex(pointer, type, 0)
578
+ while (true) {
579
+ const rx = new CoreRX({ version, dataPointer, corePointer: 0, dependencies: [] }, this.db, view)
580
+ const dependencyPromise = rx.getDependency()
581
+ rx.tryFlush()
582
+ const dependency = await dependencyPromise
583
+ if (!dependency) break
584
+ core.dependencies.push(dependency)
585
+ dataPointer = dependency.dataPointer
586
+ }
875
587
 
876
- if (opts.lt || opts.lt === 0) bounded.lt = encodeUserDataIndex(pointer, type, opts.lt)
877
- else if (opts.lte) bounded.lte = encodeUserDataIndex(pointer, type, opts.lte)
878
- else bounded.lte = encodeDataIndex(pointer, type, Infinity)
588
+ const result = new HypercoreStorage(this, this.db.session(), core, EMPTY, false)
879
589
 
880
- return bounded
881
- }
590
+ if (result.core.version === 0) await this._migrateCore(result, discoveryKey, create)
591
+ return result
592
+ }
882
593
 
883
- function toLimit (n) {
884
- return n === 0 ? 0 : (n || Infinity)
885
- }
594
+ // not allowed to throw validation errors as its a shared tx!
595
+ async _create (view, { key, manifest, keyPair, encryptionKey, discoveryKey, alias, userData }) {
596
+ const rx = new CorestoreRX(this.db, view)
597
+ const tx = new CorestoreTX(view)
886
598
 
887
- function encode (encoding, value) {
888
- const state = ensureSlab(128)
889
- const start = state.start
890
- encoding.encode(state, value)
599
+ const corePromise = rx.getCore(discoveryKey)
600
+ const headPromise = rx.getHead()
891
601
 
892
- assert(state.start <= state.end)
602
+ rx.tryFlush()
893
603
 
894
- return state.buffer.subarray(start, state.start)
895
- }
604
+ let [core, head] = await Promise.all([corePromise, headPromise])
605
+ if (core) return this._resumeFromPointers(view, discoveryKey, true, core)
896
606
 
897
- function encodeBatch (pointer, type, name) {
898
- const end = 128 + name.length
899
- const state = { start: 0, end, buffer: b4a.allocUnsafe(end) }
900
- const start = state.start
901
- UINT.encode(state, TL.CORE)
902
- UINT.encode(state, pointer)
903
- UINT.encode(state, type)
904
- c.string.encode(state, name)
607
+ if (head === null) head = initStoreHead()
608
+ if (head.defaultDiscoveryKey === null) head.defaultDiscoveryKey = discoveryKey
905
609
 
906
- return state.buffer.subarray(start, state.start)
907
- }
610
+ const corePointer = head.allocated.cores++
611
+ const dataPointer = head.allocated.datas++
908
612
 
909
- function encodeCoreIndex (pointer, type, index) {
910
- const state = ensureSlab(128)
911
- const start = state.start
912
- UINT.encode(state, TL.CORE)
913
- UINT.encode(state, pointer)
914
- UINT.encode(state, type)
915
- if (index !== undefined) UINT.encode(state, index)
613
+ core = { version: VERSION, corePointer, dataPointer, alias }
916
614
 
917
- return state.buffer.subarray(start, state.start)
918
- }
615
+ tx.setHead(head)
616
+ tx.putCore(discoveryKey, core)
617
+ if (alias) tx.putCoreByAlias(alias, discoveryKey)
919
618
 
920
- function encodeDataIndex (pointer, type, index) {
921
- const state = ensureSlab(128)
922
- const start = state.start
923
- UINT.encode(state, TL.DATA)
924
- UINT.encode(state, pointer)
925
- UINT.encode(state, type)
926
- if (index !== undefined) UINT.encode(state, index)
619
+ const ptr = { corePointer, dataPointer, dependencies: [] }
620
+ const ctx = new CoreTX(ptr, this.db, view, tx.changes)
927
621
 
928
- return state.buffer.subarray(start, state.start)
929
- }
622
+ ctx.setAuth({
623
+ key,
624
+ discoveryKey,
625
+ manifest,
626
+ keyPair,
627
+ encryptionKey
628
+ })
930
629
 
931
- function encodeUserDataIndex (pointer, type, key) {
932
- const end = 128 + key.length
933
- const state = { start: 0, end, buffer: b4a.allocUnsafe(end) }
934
- const start = state.start
935
- UINT.encode(state, TL.DATA)
936
- UINT.encode(state, pointer)
937
- UINT.encode(state, type)
938
- c.string.encode(state, key)
630
+ if (userData) {
631
+ for (const { key, value } of userData) {
632
+ ctx.putUserData(key, value)
633
+ }
634
+ }
939
635
 
940
- return state.buffer.subarray(start, state.start)
941
- }
636
+ tx.apply()
942
637
 
943
- function encodeDiscoveryKey (discoveryKey) {
944
- const state = ensureSlab(128)
945
- const start = state.start
946
- UINT.encode(state, TL.DKEYS)
947
- c.fixed32.encode(state, discoveryKey)
948
- return state.buffer.subarray(start, state.start)
949
- }
638
+ return new HypercoreStorage(this, this.db.session(), ptr, EMPTY, false)
639
+ }
950
640
 
951
- async function addDependencies (db, dataPointer, treeLength) {
952
- const dependencies = []
641
+ async create (data) {
642
+ if (this.version === 0) await this._migrateStore()
953
643
 
954
- let dep = await db.get(encodeDataIndex(dataPointer, DATA.DEPENDENCY))
955
- while (dep) {
956
- const { data, length } = c.decode(m.DataDependency, dep)
957
- if (treeLength === -1 || length <= treeLength) dependencies.push({ data, length })
644
+ const view = await this._enter()
958
645
 
959
- dep = await db.get(encodeDataIndex(data, DATA.DEPENDENCY))
646
+ try {
647
+ return await this._create(view, data)
648
+ } finally {
649
+ await this._exit()
650
+ }
960
651
  }
961
-
962
- return dependencies
963
652
  }
964
653
 
965
- function findBlockDependency (dependencies, index) {
966
- for (const { data, length } of dependencies) {
967
- if (index < length) return data
968
- }
969
- return null
970
- }
654
+ module.exports = CorestoreStorage
971
655
 
972
- function findTreeDependency (dependencies, index) {
973
- for (const { data, length } of dependencies) {
974
- if (flat.rightSpan(index) <= (length - 1) * 2) return data
656
+ function initStoreHead () {
657
+ return {
658
+ version: 0, // cause we wanna run the migration
659
+ allocated: {
660
+ datas: 0,
661
+ cores: 0
662
+ },
663
+ seed: null,
664
+ defaultDiscoveryKey: null
975
665
  }
976
- return null
977
- }
978
-
979
- function initialiseCoreInfo (db, { key, manifest, keyPair, encryptionKey }) {
980
- db.setCoreAuth({ key, manifest })
981
- if (keyPair) db.setLocalKeyPair(keyPair)
982
- if (encryptionKey) db.setEncryptionKey(encryptionKey)
983
666
  }
984
667
 
985
- function initialiseCoreData (db, { userData } = {}) {
986
- db.setDataInfo({ version: 0 })
987
- if (userData) {
988
- for (const { key, value } of userData) {
989
- db.setUserData(key, value)
990
- }
668
+ function getBatch (sessions, name, alloc) {
669
+ for (let i = 0; i < sessions.length; i++) {
670
+ if (sessions[i].name === name) return sessions[i]
991
671
  }
992
- }
993
672
 
994
- function noop () {}
673
+ if (!alloc) return null
995
674
 
996
- async function tryFlushAndDestroy (batch) {
997
- try {
998
- await batch.flush()
999
- } catch {}
675
+ const result = { name, dataPointer: -1 }
676
+ sessions.push(result)
677
+ return result
678
+ }
1000
679
 
1001
- batch.destroy()
680
+ function isCorestoreStorage (s) {
681
+ return typeof s === 'object' && !!s && typeof s.setDefaultDiscoveryKey === 'function'
1002
682
  }
1003
683
 
1004
- async function flushAndDestroy (batch) {
1005
- try {
1006
- await batch.flush()
1007
- } catch (err) {
1008
- batch.destroy()
1009
- throw err
1010
- }
684
+ function createColumnFamily (db) {
685
+ const col = new RocksDB.ColumnFamily(COLUMN_FAMILY, {
686
+ // tuning! atm just the default tuning from rocks, TODO: tweak
687
+ enableBlobFiles: false,
688
+ minBlobSize: 0,
689
+ blobFileSize: 0,
690
+ enableBlobGarbageCollection: true,
691
+ tableBlockSize: 16384,
692
+ tableCacheIndexAndFilterBlocks: true,
693
+ tableFormatVersion: 4
694
+ })
1011
695
 
1012
- batch.destroy()
696
+ return db.columnFamily(col)
1013
697
  }