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