corestore 6.0.0-beta1 → 6.0.1-alpha.7
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/.github/workflows/test-node.yml +4 -4
- package/README.md +59 -2
- package/index.js +188 -81
- package/lib/keys.js +104 -0
- package/package.json +15 -24
- package/test/all.js +98 -255
- package/test/helpers/index.js +2 -7
- package/test/keys.js +65 -0
- package/lib/buffer-file.js +0 -26
- package/lib/db.js +0 -160
- package/lib/errors.js +0 -44
- package/lib/loader.js +0 -287
- package/lib/pending-file.js +0 -37
- package/lib/replicator.js +0 -66
package/test/all.js
CHANGED
|
@@ -1,302 +1,145 @@
|
|
|
1
|
-
const
|
|
1
|
+
const test = require('brittle')
|
|
2
|
+
const crypto = require('hypercore-crypto')
|
|
2
3
|
const ram = require('random-access-memory')
|
|
3
|
-
const
|
|
4
|
-
const test = require('tape')
|
|
5
|
-
const hypercoreCrypto = require('hypercore-crypto')
|
|
6
|
-
const { toPromises } = require('hypercore-promisifier')
|
|
7
|
-
const OldCorestore = require('@andrewosh/corestore-migration')
|
|
4
|
+
const tmp = require('tmp-promise')
|
|
8
5
|
|
|
9
6
|
const Corestore = require('..')
|
|
10
|
-
const { cleanup } = require('./helpers')
|
|
11
7
|
|
|
12
|
-
test('
|
|
13
|
-
const store =
|
|
8
|
+
test('basic get with caching', async function (t) {
|
|
9
|
+
const store = new Corestore(ram)
|
|
10
|
+
const core1a = store.get({ name: 'core-1' })
|
|
11
|
+
const core1b = store.get({ name: 'core-1' })
|
|
12
|
+
const core2 = store.get({ name: 'core-2' })
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
const core1 = store.get({ name: 'default' })
|
|
17
|
-
await toPromises(core1).ready()
|
|
14
|
+
await Promise.all([core1a.ready(), core1b.ready(), core2.ready()])
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const core = store.get(core1.key)
|
|
22
|
-
await toPromises(core).ready()
|
|
23
|
-
t.same(core, core1)
|
|
24
|
-
}
|
|
16
|
+
t.alike(core1a.key, core1b.key)
|
|
17
|
+
t.unlike(core1a.key, core2.key)
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const core = store.get(core1.key.toString('hex'))
|
|
29
|
-
await toPromises(core).ready()
|
|
30
|
-
t.same(core, core1)
|
|
31
|
-
}
|
|
19
|
+
t.ok(core1a.writable)
|
|
20
|
+
t.ok(core1b.writable)
|
|
32
21
|
|
|
33
|
-
|
|
34
|
-
// Object arg
|
|
35
|
-
const core = store.get({ key: core1.key })
|
|
36
|
-
await toPromises(core).ready()
|
|
37
|
-
t.same(core, core1)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
{
|
|
41
|
-
// Object arg with string key
|
|
42
|
-
const core = store.get({ key: core1.key.toString('hex') })
|
|
43
|
-
await toPromises(core).ready()
|
|
44
|
-
t.same(core, core1)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
// Custom keypair
|
|
49
|
-
const core = store.get({ keyPair: { secretKey: core1.secretKey, publicKey: core1.key } })
|
|
50
|
-
await toPromises(core).ready()
|
|
51
|
-
t.same(core, core1)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
t.end()
|
|
22
|
+
t.is(store.cores.size, 2)
|
|
55
23
|
})
|
|
56
24
|
|
|
57
|
-
test('
|
|
58
|
-
const store =
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
() => store.get('abc'),
|
|
62
|
-
() => store.get({ name: null }),
|
|
63
|
-
() => store.get({ key: null })
|
|
64
|
-
]
|
|
65
|
-
for (const get of badGets) {
|
|
66
|
-
try {
|
|
67
|
-
get()
|
|
68
|
-
t.fail('get did not throw correctly')
|
|
69
|
-
} catch (err) {
|
|
70
|
-
t.true(err)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
test('ram-based corestore, many gets before ready', async t => {
|
|
76
|
-
const store = create(ram)
|
|
77
|
-
const core1 = store.get({ name: 'core1' })
|
|
78
|
-
const core2 = store.get({ name: 'core1' })
|
|
79
|
-
const core3 = store.get({ name: 'core3' })
|
|
80
|
-
|
|
81
|
-
await Promise.all([
|
|
82
|
-
toPromises(core1).ready(),
|
|
83
|
-
toPromises(core2).ready(),
|
|
84
|
-
toPromises(core3).ready()
|
|
85
|
-
])
|
|
25
|
+
test('basic get with custom keypair', async function (t) {
|
|
26
|
+
const store = new Corestore(ram)
|
|
27
|
+
const kp1 = crypto.keyPair()
|
|
28
|
+
const kp2 = crypto.keyPair()
|
|
86
29
|
|
|
87
|
-
|
|
88
|
-
|
|
30
|
+
const core1 = store.get(kp1)
|
|
31
|
+
const core2 = store.get(kp2)
|
|
32
|
+
await Promise.all([core1.ready(), core2.ready()])
|
|
89
33
|
|
|
90
|
-
|
|
91
|
-
t.
|
|
92
|
-
|
|
93
|
-
t.
|
|
34
|
+
t.alike(core1.key, kp1.publicKey)
|
|
35
|
+
t.alike(core2.key, kp2.publicKey)
|
|
36
|
+
t.ok(core1.writable)
|
|
37
|
+
t.ok(core2.writable)
|
|
94
38
|
})
|
|
95
39
|
|
|
96
|
-
test('
|
|
97
|
-
const store =
|
|
98
|
-
const
|
|
99
|
-
|
|
40
|
+
test('basic namespaces', async function (t) {
|
|
41
|
+
const store = new Corestore(ram)
|
|
42
|
+
const ns1 = store.namespace('ns1')
|
|
43
|
+
const ns2 = store.namespace('ns2')
|
|
44
|
+
const ns3 = store.namespace('ns1') // Duplicate namespace
|
|
100
45
|
|
|
101
|
-
const
|
|
102
|
-
|
|
46
|
+
const core1 = ns1.get({ name: 'main' })
|
|
47
|
+
const core2 = ns2.get({ name: 'main' })
|
|
48
|
+
const core3 = ns3.get({ name: 'main' })
|
|
49
|
+
await Promise.all([core1.ready(), core2.ready(), core3.ready()])
|
|
103
50
|
|
|
104
|
-
|
|
105
|
-
t.
|
|
51
|
+
t.absent(core1.key.equals(core2.key))
|
|
52
|
+
t.ok(core1.key.equals(core3.key))
|
|
53
|
+
t.ok(core1.writable)
|
|
54
|
+
t.ok(core2.writable)
|
|
55
|
+
t.ok(core3.writable)
|
|
56
|
+
t.is(store.cores.size, 2)
|
|
106
57
|
|
|
107
58
|
t.end()
|
|
108
59
|
})
|
|
109
60
|
|
|
110
|
-
test('
|
|
111
|
-
const store1 =
|
|
112
|
-
const store2 =
|
|
61
|
+
test('basic replication', async function (t) {
|
|
62
|
+
const store1 = new Corestore(ram)
|
|
63
|
+
const store2 = new Corestore(ram)
|
|
113
64
|
|
|
114
|
-
const core1 =
|
|
65
|
+
const core1 = store1.get({ name: 'core-1' })
|
|
66
|
+
const core2 = store1.get({ name: 'core-2' })
|
|
115
67
|
await core1.append('hello')
|
|
116
|
-
|
|
117
|
-
const s1 = store1.replicate(true, { live: true })
|
|
118
|
-
s1.pipe(store2.replicate(false, { live: true })).pipe(s1)
|
|
119
|
-
|
|
120
|
-
const clone1 = toPromises(store2.get({ key: core1.key, valueEncoding: 'utf-8' }))
|
|
121
|
-
t.same(await clone1.get(0), 'hello')
|
|
122
|
-
|
|
123
|
-
const core2 = toPromises(store1.get({ name: 'core2', valueEncoding: 'utf-8' }))
|
|
124
68
|
await core2.append('world')
|
|
125
69
|
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
t.end()
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
test('ram-based corestore, sparse replication', async t => {
|
|
133
|
-
const store1 = create(ram, { sparse: true })
|
|
134
|
-
const store2 = create(ram, { sparse: true })
|
|
135
|
-
|
|
136
|
-
const core1 = toPromises(store1.get({ name: 'core1', valueEncoding: 'utf-8' }))
|
|
137
|
-
await core1.append('hello')
|
|
138
|
-
|
|
139
|
-
const s1 = store1.replicate(true, { live: true })
|
|
140
|
-
s1.pipe(store2.replicate(false, { live: true })).pipe(s1)
|
|
141
|
-
|
|
142
|
-
const clone1 = toPromises(store2.get({ key: core1.key, valueEncoding: 'utf-8' }))
|
|
143
|
-
t.same(await clone1.get(0), 'hello')
|
|
144
|
-
|
|
145
|
-
const core2 = toPromises(store1.get({ name: 'core2', valueEncoding: 'utf-8' }))
|
|
146
|
-
await core2.append('world')
|
|
70
|
+
const core3 = store2.get({ key: core1.key })
|
|
71
|
+
const core4 = store2.get({ key: core2.key })
|
|
147
72
|
|
|
148
|
-
const
|
|
149
|
-
|
|
73
|
+
const s = store1.replicate(true)
|
|
74
|
+
s.pipe(store2.replicate(false)).pipe(s)
|
|
150
75
|
|
|
151
|
-
t.
|
|
76
|
+
t.alike(await core3.get(0), Buffer.from('hello'))
|
|
77
|
+
t.alike(await core4.get(0), Buffer.from('world'))
|
|
152
78
|
})
|
|
153
79
|
|
|
154
|
-
test('
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
const core1 = toPromises(store1.get({ name: 'core1', valueEncoding: 'utf-8' }))
|
|
159
|
-
await core1.append('hello')
|
|
160
|
-
|
|
161
|
-
const s1 = store1.replicate(true, { live: true })
|
|
162
|
-
s1.pipe(store2.replicate(false, { live: true })).pipe(s1)
|
|
80
|
+
test('nested namespaces', async function (t) {
|
|
81
|
+
const store = new Corestore(ram)
|
|
82
|
+
const ns1a = store.namespace('ns1').namespace('a')
|
|
83
|
+
const ns1b = store.namespace('ns1').namespace('b')
|
|
163
84
|
|
|
164
|
-
const
|
|
165
|
-
|
|
85
|
+
const core1 = ns1a.get({ name: 'main' })
|
|
86
|
+
const core2 = ns1b.get({ name: 'main' })
|
|
87
|
+
await Promise.all([core1.ready(), core2.ready()])
|
|
166
88
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
t.same(await clone2.get(0), 'world')
|
|
172
|
-
|
|
173
|
-
await cleanup(['store1', 'store2'])
|
|
174
|
-
t.end()
|
|
89
|
+
t.not(core1.key.equals(core2.key))
|
|
90
|
+
t.ok(core1.writable)
|
|
91
|
+
t.ok(core2.writable)
|
|
92
|
+
t.is(store.cores.size, 2)
|
|
175
93
|
})
|
|
176
94
|
|
|
177
|
-
test('
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
t.
|
|
184
|
-
|
|
185
|
-
await store.close()
|
|
186
|
-
store = create('test-store')
|
|
187
|
-
core1 = toPromises(store.get({ name: 'core1', valueEncoding: 'utf-8' }))
|
|
188
|
-
|
|
189
|
-
t.same(await core1.get(0), 'hello')
|
|
190
|
-
|
|
191
|
-
await cleanup(['test-store'])
|
|
192
|
-
t.end()
|
|
95
|
+
test('core uncached when all sessions close', async function (t) {
|
|
96
|
+
const store = new Corestore(ram)
|
|
97
|
+
const core1 = store.get({ name: 'main' })
|
|
98
|
+
await core1.ready()
|
|
99
|
+
t.is(store.cores.size, 1)
|
|
100
|
+
await core1.close()
|
|
101
|
+
t.is(store.cores.size, 0)
|
|
193
102
|
})
|
|
194
103
|
|
|
195
|
-
test('
|
|
196
|
-
|
|
197
|
-
const keyPair = hypercoreCrypto.keyPair()
|
|
104
|
+
test('writable core loaded from name userData', async function (t) {
|
|
105
|
+
const dir = await tmp.dir({ unsafeCleanup: true })
|
|
198
106
|
|
|
199
|
-
let
|
|
200
|
-
|
|
107
|
+
let store = new Corestore(dir.path)
|
|
108
|
+
let core = store.get({ name: 'main' })
|
|
109
|
+
await core.ready()
|
|
110
|
+
const key = core.key
|
|
201
111
|
|
|
202
|
-
t.
|
|
112
|
+
t.ok(core.writable)
|
|
113
|
+
await core.append('hello')
|
|
114
|
+
t.is(core.length, 1)
|
|
203
115
|
|
|
204
116
|
await store.close()
|
|
205
|
-
store =
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
t.same(await core1.get(0), 'hello')
|
|
209
|
-
|
|
210
|
-
await cleanup(['test-store'])
|
|
211
|
-
t.end()
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
test('namespace method is equivalent to name array', async t => {
|
|
215
|
-
const store = create(ram)
|
|
216
|
-
|
|
217
|
-
const core1 = store.get({ name: ['a', 'b', 'c'] })
|
|
218
|
-
const core2 = store.namespace('a').namespace('b').get({ name: 'c' })
|
|
219
|
-
|
|
220
|
-
await toPromises(core1).ready()
|
|
221
|
-
await toPromises(core2).ready()
|
|
222
|
-
|
|
223
|
-
t.same(core1, core2)
|
|
224
|
-
t.end()
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
test('can backup/restore', async t => {
|
|
228
|
-
const firstStore = create(ram)
|
|
229
|
-
const core1 = firstStore.get({ name: 'hello-world' })
|
|
230
|
-
await toPromises(core1).ready()
|
|
231
|
-
const manifest = await firstStore.backup()
|
|
232
|
-
|
|
233
|
-
const secondStore = await Corestore.restore(manifest, ram)
|
|
234
|
-
const core2 = secondStore.get({ key: core1.key })
|
|
235
|
-
await toPromises(core2).ready()
|
|
117
|
+
store = new Corestore(dir.path)
|
|
118
|
+
core = store.get(key)
|
|
119
|
+
await core.ready()
|
|
236
120
|
|
|
237
|
-
t.
|
|
121
|
+
t.ok(core.writable)
|
|
122
|
+
await core.append('world')
|
|
123
|
+
t.is(core.length, 2)
|
|
124
|
+
t.alike(await core.get(0), Buffer.from('hello'))
|
|
125
|
+
t.alike(await core.get(1), Buffer.from('world'))
|
|
238
126
|
|
|
239
|
-
|
|
127
|
+
await dir.cleanup()
|
|
240
128
|
})
|
|
241
129
|
|
|
242
|
-
test('
|
|
243
|
-
const
|
|
244
|
-
await oldStore.ready()
|
|
245
|
-
|
|
246
|
-
const oldCores = [
|
|
247
|
-
oldStore.default(),
|
|
248
|
-
oldStore.namespace('hello').default(),
|
|
249
|
-
oldStore.namespace('hello').namespace('world').default(),
|
|
250
|
-
oldStore.get({ name: 'test-name' }),
|
|
251
|
-
// Randomly-generated names should also be migrated.
|
|
252
|
-
oldStore.get()
|
|
253
|
-
]
|
|
254
|
-
await Promise.all(oldCores.map(c => new Promise(resolve => c.ready(resolve))))
|
|
255
|
-
|
|
256
|
-
const names = [
|
|
257
|
-
'default',
|
|
258
|
-
['hello'],
|
|
259
|
-
['hello', 'world'],
|
|
260
|
-
'test-name'
|
|
261
|
-
]
|
|
262
|
-
const keys = oldCores.map(c => c.key)
|
|
263
|
-
await new Promise(resolve => oldStore.close(resolve))
|
|
264
|
-
|
|
265
|
-
{
|
|
266
|
-
const store = create('test-store', {
|
|
267
|
-
migrationRoot: 'test-store'
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
// Cores are writable when loaded by name
|
|
271
|
-
for (let i = 0; i < names.length; i++) {
|
|
272
|
-
const core = store.get({ name: names[i] })
|
|
273
|
-
await toPromises(core).ready()
|
|
274
|
-
t.true(core.writable)
|
|
275
|
-
t.same(core.key, keys[i])
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
await store.close()
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
{
|
|
282
|
-
// The second time should work without a migration.
|
|
283
|
-
const store = create('test-store')
|
|
130
|
+
test('storage locking', async function (t) {
|
|
131
|
+
const dir = await tmp.dir({ unsafeCleanup: true })
|
|
284
132
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const core = store.get({ key: keys[i] })
|
|
288
|
-
await toPromises(core).ready()
|
|
289
|
-
t.true(core.writable)
|
|
290
|
-
}
|
|
133
|
+
const store1 = new Corestore(dir.path)
|
|
134
|
+
await store1.ready()
|
|
291
135
|
|
|
292
|
-
|
|
136
|
+
const store2 = new Corestore(dir.path)
|
|
137
|
+
try {
|
|
138
|
+
await store2.ready()
|
|
139
|
+
t.fail('dir should have been locked')
|
|
140
|
+
} catch {
|
|
141
|
+
t.pass('dir was locked')
|
|
293
142
|
}
|
|
294
143
|
|
|
295
|
-
await cleanup(
|
|
296
|
-
t.end()
|
|
144
|
+
await dir.cleanup()
|
|
297
145
|
})
|
|
298
|
-
|
|
299
|
-
function create (storage, opts) {
|
|
300
|
-
const store = new Corestore(storage, opts)
|
|
301
|
-
return store
|
|
302
|
-
}
|
package/test/helpers/index.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
1
|
+
const fs = require('fs').promises
|
|
2
2
|
|
|
3
3
|
async function cleanup (dirs) {
|
|
4
|
-
return Promise.
|
|
5
|
-
fs.rmdir(dir, { recursive: true }, err => {
|
|
6
|
-
if (err) return reject(err)
|
|
7
|
-
return resolve()
|
|
8
|
-
})
|
|
9
|
-
})))
|
|
4
|
+
return Promise.allSettled(dirs.map(dir => fs.rmdir(dir, { recursive: true })))
|
|
10
5
|
}
|
|
11
6
|
|
|
12
7
|
function delay (ms, cb) {
|
package/test/keys.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const p = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
|
|
4
|
+
const test = require('brittle')
|
|
5
|
+
const ram = require('random-access-memory')
|
|
6
|
+
const raf = require('random-access-file')
|
|
7
|
+
|
|
8
|
+
const KeyManager = require('../lib/keys')
|
|
9
|
+
|
|
10
|
+
test('can create hypercore keypairs', async t => {
|
|
11
|
+
const keys = await KeyManager.fromStorage(ram)
|
|
12
|
+
|
|
13
|
+
const kp1 = await keys.createHypercoreKeyPair('core1')
|
|
14
|
+
const kp2 = await keys.createHypercoreKeyPair('core2')
|
|
15
|
+
|
|
16
|
+
t.is(kp1.publicKey.length, 32)
|
|
17
|
+
t.is(kp2.publicKey.length, 32)
|
|
18
|
+
t.unlike(kp1.publicKey, kp2.publicKey)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('distinct tokens create distinct hypercore keypairs', async t => {
|
|
22
|
+
const keys = await KeyManager.fromStorage(ram)
|
|
23
|
+
const token1 = KeyManager.createToken()
|
|
24
|
+
const token2 = KeyManager.createToken()
|
|
25
|
+
|
|
26
|
+
const kp1 = await keys.createHypercoreKeyPair('core1', token1)
|
|
27
|
+
const kp2 = await keys.createHypercoreKeyPair('core1', token2)
|
|
28
|
+
|
|
29
|
+
t.unlike(kp1.publicKey, kp2.publicKey)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('short user-provided token will throw', async t => {
|
|
33
|
+
const keys = await KeyManager.fromStorage(ram)
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await keys.createHypercoreKeyPair('core1', Buffer.from('hello'))
|
|
37
|
+
t.fail('did not throw')
|
|
38
|
+
} catch {
|
|
39
|
+
t.pass('threw correctly')
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('persistent storage regenerates keys correctly', async t => {
|
|
44
|
+
const testPath = p.resolve(__dirname, 'test-data')
|
|
45
|
+
|
|
46
|
+
const keys1 = await KeyManager.fromStorage((name) => raf(testPath, { directory: testPath }))
|
|
47
|
+
const kp1 = await keys1.createHypercoreKeyPair('core1')
|
|
48
|
+
|
|
49
|
+
const keys2 = await KeyManager.fromStorage((name) => raf(testPath, { directory: testPath }))
|
|
50
|
+
const kp2 = await keys2.createHypercoreKeyPair('core1')
|
|
51
|
+
|
|
52
|
+
t.alike(kp1.publicKey, kp2.publicKey)
|
|
53
|
+
|
|
54
|
+
await fs.promises.rm(testPath, { recursive: true })
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('different master keys -> different keys', async t => {
|
|
58
|
+
const keys1 = await KeyManager.fromStorage(ram)
|
|
59
|
+
const keys2 = await KeyManager.fromStorage(ram)
|
|
60
|
+
|
|
61
|
+
const kp1 = await keys1.createHypercoreKeyPair('core1')
|
|
62
|
+
const kp2 = await keys2.createHypercoreKeyPair('core1')
|
|
63
|
+
|
|
64
|
+
t.unlike(kp1.publicKey, kp2.publicKey)
|
|
65
|
+
})
|
package/lib/buffer-file.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const RAS = require('random-access-storage')
|
|
2
|
-
|
|
3
|
-
module.exports = class BufferFile extends RAS {
|
|
4
|
-
constructor (buf) {
|
|
5
|
-
super()
|
|
6
|
-
this._buf = buf
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
_open (req) {
|
|
10
|
-
if (typeof this._buf !== 'function') return req.callback(null)
|
|
11
|
-
this._buf((err, b) => {
|
|
12
|
-
if (err) return req.callback(err)
|
|
13
|
-
this._buf = b
|
|
14
|
-
req.callback(null)
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
_stat (req) {
|
|
19
|
-
req.callback(null, { size: this._buf ? this._buf.length : 0 })
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
_read (req) {
|
|
23
|
-
if (!this._buf) return req.callback(new Error('Could not satisfy length'))
|
|
24
|
-
req.callback(null, this._buf.slice(req.offset, req.offset + req.size))
|
|
25
|
-
}
|
|
26
|
-
}
|
package/lib/db.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
const p = require('path')
|
|
2
|
-
const fs = require('fs').promises
|
|
3
|
-
|
|
4
|
-
const varint = require('varint')
|
|
5
|
-
const Hyperbee = require('hyperbee')
|
|
6
|
-
const hypercore = require('hypercore')
|
|
7
|
-
const hypercoreCrypto = require('hypercore-crypto')
|
|
8
|
-
const readdir = require('@jsdevtools/readdir-enhanced')
|
|
9
|
-
const { NanoresourcePromise: Nanoresource } = require('nanoresource-promise/emitter')
|
|
10
|
-
|
|
11
|
-
const INDEX_PATH = 'index'
|
|
12
|
-
const INDEX_VERSION = '@corestore/v1'
|
|
13
|
-
const MIGRATION_KEY = '@migrated'
|
|
14
|
-
const KEYS_NAMESPACE = 'by-key'
|
|
15
|
-
const DKEYS_NAMESPACE = 'by-dkey'
|
|
16
|
-
|
|
17
|
-
module.exports = class Index extends Nanoresource {
|
|
18
|
-
constructor (storage, opts = {}) {
|
|
19
|
-
super()
|
|
20
|
-
|
|
21
|
-
this.storage = storage
|
|
22
|
-
this._migrationRoot = opts.migrationRoot
|
|
23
|
-
this._core = hypercore(p => this.storage(INDEX_PATH + '/' + p))
|
|
24
|
-
this._db = new Hyperbee(this._core, {
|
|
25
|
-
keyEncoding: 'utf-8',
|
|
26
|
-
valueEncoding: 'json'
|
|
27
|
-
}).sub(INDEX_VERSION)
|
|
28
|
-
|
|
29
|
-
this._byKey = this._db.sub(KEYS_NAMESPACE)
|
|
30
|
-
this._byDKey = this._db.sub(DKEYS_NAMESPACE)
|
|
31
|
-
|
|
32
|
-
this._core.on('error', err => this.emit('error', err))
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Nanoresource Methods
|
|
36
|
-
|
|
37
|
-
async _open () {
|
|
38
|
-
await new Promise((resolve, reject) => {
|
|
39
|
-
this._core.open(err => {
|
|
40
|
-
if (err) return reject(err)
|
|
41
|
-
return resolve()
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
return this._maybeMigrate()
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
_close () {
|
|
48
|
-
return new Promise((resolve, reject) => {
|
|
49
|
-
this._core.close(err => {
|
|
50
|
-
if (err) return reject(err)
|
|
51
|
-
return resolve()
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Private Methods
|
|
57
|
-
|
|
58
|
-
async _maybeMigrate () {
|
|
59
|
-
if (!this._migrationRoot) return
|
|
60
|
-
const isMigrated = await this._db.get(MIGRATION_KEY)
|
|
61
|
-
if (isMigrated) return
|
|
62
|
-
|
|
63
|
-
const basePath = p.resolve(this._migrationRoot)
|
|
64
|
-
const nameIterator = readdir.iterator(basePath, {
|
|
65
|
-
basePath,
|
|
66
|
-
deep: true,
|
|
67
|
-
filter: /.*name$/
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
const b = this._db.batch()
|
|
71
|
-
await b.put(MIGRATION_KEY, { migrated: true })
|
|
72
|
-
|
|
73
|
-
for await (const namePath of nameIterator) {
|
|
74
|
-
let name = await fs.readFile(namePath)
|
|
75
|
-
if (!varint.decode(name)) continue
|
|
76
|
-
name = name.slice(varint.decode.bytes).toString('utf-8')
|
|
77
|
-
|
|
78
|
-
const keyPath = p.join(p.dirname(namePath), 'key')
|
|
79
|
-
const publicKey = await loadKey(keyPath)
|
|
80
|
-
if (!publicKey) continue
|
|
81
|
-
|
|
82
|
-
const discoveryKey = hypercoreCrypto.discoveryKey(publicKey)
|
|
83
|
-
await this._putCoreBatch({
|
|
84
|
-
name,
|
|
85
|
-
publicKey,
|
|
86
|
-
discoveryKey
|
|
87
|
-
}, b)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return b.flush()
|
|
91
|
-
|
|
92
|
-
async function loadKey (path) {
|
|
93
|
-
try {
|
|
94
|
-
const key = await fs.readFile(path)
|
|
95
|
-
return key
|
|
96
|
-
} catch (err) {
|
|
97
|
-
if (err.code !== 'ENOENT') throw err
|
|
98
|
-
return null
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async _putCoreBatch (keys, batch) {
|
|
104
|
-
const record = {
|
|
105
|
-
name: keys.name,
|
|
106
|
-
publicKey: toString(keys.publicKey),
|
|
107
|
-
discoveryKey: toString(keys.discoveryKey)
|
|
108
|
-
}
|
|
109
|
-
const b = batch || this._db.batch()
|
|
110
|
-
await b.put(KEYS_NAMESPACE + this._db.sep + record.publicKey, record)
|
|
111
|
-
await b.put(DKEYS_NAMESPACE + this._db.sep + record.discoveryKey, record)
|
|
112
|
-
if (!batch) await b.flush()
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Public Methods
|
|
116
|
-
|
|
117
|
-
async getName (keys) {
|
|
118
|
-
const node = await this._byKey.get(toString(keys.publicKey))
|
|
119
|
-
return node && node.value.name
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async getPassiveCoreKeys (dkey) {
|
|
123
|
-
const node = await this._byDKey.get(toString(dkey))
|
|
124
|
-
return node && toKeys(node.value)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async getAllCores () {
|
|
128
|
-
const allCores = []
|
|
129
|
-
for await (const { value } of this._byKey.createReadStream()) {
|
|
130
|
-
allCores.push(value)
|
|
131
|
-
}
|
|
132
|
-
return allCores
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async saveKeys (keys) {
|
|
136
|
-
const existing = await this.getName(keys)
|
|
137
|
-
if (existing) return
|
|
138
|
-
return this._putCoreBatch(keys)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async restore (manifest) {
|
|
142
|
-
if (!manifest.cores || !Array.isArray(manifest.cores)) throw new Error('Malformed manifest.')
|
|
143
|
-
for (const keys of manifest.cores) {
|
|
144
|
-
await this.saveKeys(keys)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function toString (buf) {
|
|
150
|
-
if (typeof buf === 'string') return buf
|
|
151
|
-
return buf.toString('hex')
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function toKeys (record) {
|
|
155
|
-
return {
|
|
156
|
-
name: record.name,
|
|
157
|
-
publicKey: Buffer.from(record.publicKey, 'hex'),
|
|
158
|
-
discoveryKey: Buffer.from(record.discoveryKey, 'hex')
|
|
159
|
-
}
|
|
160
|
-
}
|