corestore 6.0.0-beta2 → 6.0.1-alpha.8
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 +195 -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/lib/errors.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
const CustomError = require('custom-error-class')
|
|
2
|
-
|
|
3
|
-
class InvalidKeyError extends CustomError {
|
|
4
|
-
constructor () {
|
|
5
|
-
super('Invalid hypercore key')
|
|
6
|
-
this.invalidKey = true
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
class InvalidOptionsError extends CustomError {
|
|
11
|
-
constructor () {
|
|
12
|
-
super('Invalid get options')
|
|
13
|
-
this.invalidOptions = true
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
class InvalidStorageError extends CustomError {
|
|
18
|
-
constructor () {
|
|
19
|
-
super('Storage should be a function or a string')
|
|
20
|
-
this.invalidStorage = true
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
class InvalidNamespaceError extends CustomError {
|
|
25
|
-
constructor () {
|
|
26
|
-
super('Invalid namespace')
|
|
27
|
-
this.invalidNamespace = true
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
class MalformedManifestError extends CustomError {
|
|
32
|
-
constructor () {
|
|
33
|
-
super('Invalid backup/restore manifest')
|
|
34
|
-
this.invalidManifest = true
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
module.exports = {
|
|
39
|
-
InvalidStorageError,
|
|
40
|
-
InvalidKeyError,
|
|
41
|
-
InvalidOptionsError,
|
|
42
|
-
InvalidNamespaceError,
|
|
43
|
-
MalformedManifestError
|
|
44
|
-
}
|
package/lib/loader.js
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
const { NanoresourcePromise: Nanoresource } = require('nanoresource-promise/emitter')
|
|
2
|
-
const hypercore = require('hypercore')
|
|
3
|
-
const hypercoreCrypto = require('hypercore-crypto')
|
|
4
|
-
const deriveSeed = require('derive-key')
|
|
5
|
-
const RefPool = require('refpool')
|
|
6
|
-
|
|
7
|
-
const BufferFile = require('./buffer-file')
|
|
8
|
-
const PendingFile = require('./pending-file')
|
|
9
|
-
const errors = require('./errors')
|
|
10
|
-
|
|
11
|
-
const SEED_NAMESPACE = 'corestore'
|
|
12
|
-
const NAMESPACE_SEPARATOR = ':'
|
|
13
|
-
const MASTER_KEY_FILENAME = 'master_key'
|
|
14
|
-
const REF_TOKEN = '@corestore/ref-token'
|
|
15
|
-
|
|
16
|
-
module.exports = class Loader extends Nanoresource {
|
|
17
|
-
constructor (storage, db, opts = {}) {
|
|
18
|
-
super()
|
|
19
|
-
|
|
20
|
-
this.storage = storage
|
|
21
|
-
this.db = db
|
|
22
|
-
this.masterKey = opts.masterKey
|
|
23
|
-
this.overwriteMasterKey = opts.overwriteMasterKey
|
|
24
|
-
this.opts = opts
|
|
25
|
-
|
|
26
|
-
this.readyCache = new Map()
|
|
27
|
-
this.cache = new RefPool({
|
|
28
|
-
maxSize: opts.cacheSize ?? 1000,
|
|
29
|
-
close: core => {
|
|
30
|
-
core.close(err => {
|
|
31
|
-
if (err) this.emit('error', err)
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
this.registry = new FinalizationRegistry(id => {
|
|
36
|
-
this.cache.decrement(id)
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
_loadMasterKey (cb) {
|
|
41
|
-
if (this.masterKey && !this.overwriteMasterKey) return cb(null)
|
|
42
|
-
const keyStorage = this.storage(MASTER_KEY_FILENAME)
|
|
43
|
-
keyStorage.stat((err, st) => {
|
|
44
|
-
if (err && err.code !== 'ENOENT') return cb(err)
|
|
45
|
-
if (err || st.size < 32 || this.overwriteMasterKey) {
|
|
46
|
-
this.masterKey = this.masterKey ?? hypercoreCrypto.randomBytes(32)
|
|
47
|
-
return keyStorage.write(0, this.masterKey, err => {
|
|
48
|
-
if (err) return cb(err)
|
|
49
|
-
keyStorage.close(cb)
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
keyStorage.read(0, 32, (err, key) => {
|
|
53
|
-
if (err) return cb(err)
|
|
54
|
-
this.masterKey = key
|
|
55
|
-
keyStorage.close(cb)
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
_flushReadyCache () {
|
|
61
|
-
for (const { refs, core, opts, namespace } of this.readyCache.values()) {
|
|
62
|
-
const keys = this._generateKeys(namespace, opts)
|
|
63
|
-
const id = this._getCacheId(keys)
|
|
64
|
-
this.cache.set(id, core)
|
|
65
|
-
for (let i = 0; i < refs; i++) this.cache.increment(id)
|
|
66
|
-
}
|
|
67
|
-
this.readyCache.clear()
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
_generateKeyPair (namespace, name) {
|
|
71
|
-
if (namespace) name = [...namespace, ...name].join(NAMESPACE_SEPARATOR)
|
|
72
|
-
const seed = deriveSeed(SEED_NAMESPACE, this.masterKey, name)
|
|
73
|
-
const keyPair = hypercoreCrypto.keyPair(seed)
|
|
74
|
-
const discoveryKey = hypercoreCrypto.discoveryKey(keyPair.publicKey)
|
|
75
|
-
return { name, publicKey: keyPair.publicKey, secretKey: keyPair.secretKey, discoveryKey }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
_generateKeys (namespace, opts) {
|
|
79
|
-
// The full name is stored in the index, so if we're loading from disk it should override.
|
|
80
|
-
if (opts.fullName) return this._generateKeyPair(null, opts.fullName)
|
|
81
|
-
if (opts.name) return this._generateKeyPair(namespace, opts.name)
|
|
82
|
-
if (opts.keyPair) {
|
|
83
|
-
const publicKey = opts.keyPair.publicKey
|
|
84
|
-
const secretKey = opts.keyPair.secretKey
|
|
85
|
-
return {
|
|
86
|
-
publicKey,
|
|
87
|
-
secretKey,
|
|
88
|
-
discoveryKey: hypercoreCrypto.discoveryKey(publicKey),
|
|
89
|
-
name: null
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (opts.key || opts.publicKey) {
|
|
93
|
-
const publicKey = opts.key || opts.publicKey
|
|
94
|
-
return {
|
|
95
|
-
publicKey,
|
|
96
|
-
secretKey: null,
|
|
97
|
-
discoveryKey: hypercoreCrypto.discoveryKey(publicKey),
|
|
98
|
-
name: null
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
throw new errors.InvalidOptionsError()
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
_getCacheId (keys) {
|
|
105
|
-
return keys.discoveryKey.toString('hex')
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Nanoresource Methods
|
|
109
|
-
|
|
110
|
-
async _open () {
|
|
111
|
-
await this.db.open()
|
|
112
|
-
await new Promise((resolve, reject) => {
|
|
113
|
-
this._loadMasterKey(err => {
|
|
114
|
-
if (err) return reject(err)
|
|
115
|
-
return resolve()
|
|
116
|
-
})
|
|
117
|
-
})
|
|
118
|
-
await this._flushReadyCache()
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async _close () {
|
|
122
|
-
if (!this.cache.size) return
|
|
123
|
-
const activeCores = this.getOpenCores()
|
|
124
|
-
const closePromises = activeCores.map(core => new Promise((resolve, reject) => {
|
|
125
|
-
core.close(err => {
|
|
126
|
-
if (err) return reject(err)
|
|
127
|
-
return resolve(null)
|
|
128
|
-
})
|
|
129
|
-
}))
|
|
130
|
-
return Promise.allSettled(closePromises)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Hypercore Loading
|
|
134
|
-
|
|
135
|
-
_createStorage (namespace, opts, keys) {
|
|
136
|
-
let storageRoot = null
|
|
137
|
-
const configure = () => {
|
|
138
|
-
keys = keys || this._generateKeys(namespace, opts)
|
|
139
|
-
const id = this._getCacheId(keys)
|
|
140
|
-
storageRoot = [id.slice(0, 2), id.slice(2, 4), id].join('/')
|
|
141
|
-
}
|
|
142
|
-
if (keys) configure()
|
|
143
|
-
const loadSecretKey = cb => {
|
|
144
|
-
this.db.saveKeys(keys, NAMESPACE_SEPARATOR)
|
|
145
|
-
.then(() => keys.name ? keys.name : this.db.getName(keys))
|
|
146
|
-
.then(name => keys.secretKey
|
|
147
|
-
? keys
|
|
148
|
-
: this._generateKeys(namespace, {
|
|
149
|
-
...opts,
|
|
150
|
-
name,
|
|
151
|
-
fullName: name
|
|
152
|
-
}))
|
|
153
|
-
.then(keys => cb(null, keys.secretKey), err => cb(err))
|
|
154
|
-
}
|
|
155
|
-
const storage = name => {
|
|
156
|
-
if (!keys) configure()
|
|
157
|
-
if (name === 'key') return new BufferFile(keys.publicKey)
|
|
158
|
-
if (name === 'secret_key') return new BufferFile(loadSecretKey)
|
|
159
|
-
else return this.storage(storageRoot + '/' + name)
|
|
160
|
-
}
|
|
161
|
-
return name => {
|
|
162
|
-
if (this.opened) return storage(name)
|
|
163
|
-
return new PendingFile(cb => {
|
|
164
|
-
this.open().then(() => cb(null, storage(name)), err => cb(err))
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
_create (id, namespace, opts, keys) {
|
|
170
|
-
const cacheOpts = { ...this.opts.cache }
|
|
171
|
-
if (opts.cache) {
|
|
172
|
-
if (opts.cache.data === false) delete cacheOpts.data
|
|
173
|
-
if (opts.cache.tree === false) delete cacheOpts.tree
|
|
174
|
-
}
|
|
175
|
-
if (cacheOpts.data) cacheOpts.data = cacheOpts.data.namespace()
|
|
176
|
-
if (cacheOpts.tree) cacheOpts.tree = cacheOpts.tree.namespace()
|
|
177
|
-
|
|
178
|
-
const publicKey = keys && keys.publicKey
|
|
179
|
-
const storage = this._createStorage(namespace, opts, keys)
|
|
180
|
-
const core = hypercore(storage, publicKey, {
|
|
181
|
-
...this.opts,
|
|
182
|
-
...opts,
|
|
183
|
-
cache: cacheOpts,
|
|
184
|
-
storeSecretKey: false,
|
|
185
|
-
createIfMissing: !!publicKey
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
if (this.opened) {
|
|
189
|
-
this.cache.set(id, core)
|
|
190
|
-
} else {
|
|
191
|
-
this.readyCache.set(id, { core, opts, namespace, refs: 0 })
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
core.ifAvailable.wait()
|
|
195
|
-
let errored = false
|
|
196
|
-
|
|
197
|
-
const onerror = () => {
|
|
198
|
-
errored = true
|
|
199
|
-
core.ifAvailable.continue()
|
|
200
|
-
this.readyCache.delete(id)
|
|
201
|
-
this.cache.delete(id)
|
|
202
|
-
}
|
|
203
|
-
const onready = () => {
|
|
204
|
-
if (errored) return
|
|
205
|
-
this.emit('core', core, opts)
|
|
206
|
-
core.removeListener('error', onerror)
|
|
207
|
-
core.ifAvailable.continue()
|
|
208
|
-
}
|
|
209
|
-
const onclose = () => {
|
|
210
|
-
this.readyCache.delete(id)
|
|
211
|
-
this.cache.delete(id)
|
|
212
|
-
}
|
|
213
|
-
core.once('ready', onready)
|
|
214
|
-
core.once('error', onerror)
|
|
215
|
-
core.once('close', onclose)
|
|
216
|
-
|
|
217
|
-
return core
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
_makeRef (id, core, isActive) {
|
|
221
|
-
const token = { id }
|
|
222
|
-
const close = cb => {
|
|
223
|
-
if (!isActive) return process.nextTick(cb, null)
|
|
224
|
-
this.registry.unregister(token)
|
|
225
|
-
core.ready(() => {
|
|
226
|
-
const id = core.discoveryKey.toString('hex')
|
|
227
|
-
this.cache.decrement(id)
|
|
228
|
-
return cb(null)
|
|
229
|
-
})
|
|
230
|
-
}
|
|
231
|
-
const ref = new Proxy(core, {
|
|
232
|
-
get (obj, prop) {
|
|
233
|
-
if (prop === REF_TOKEN) return token
|
|
234
|
-
if (prop === 'close') return close
|
|
235
|
-
return obj[prop]
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
if (isActive) this.registry.register(token, id)
|
|
239
|
-
return ref
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
_getBeforeReady (namespace, opts) {
|
|
243
|
-
let id = null
|
|
244
|
-
if (opts.name) {
|
|
245
|
-
id = [...namespace, ...opts.name].join(NAMESPACE_SEPARATOR)
|
|
246
|
-
} else {
|
|
247
|
-
id = opts.key ?? opts
|
|
248
|
-
if (Buffer.isBuffer(id)) id = id.toString('hex')
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
let cached = this.readyCache.get(id)
|
|
252
|
-
if (cached) cached.refs++
|
|
253
|
-
|
|
254
|
-
const core = (cached && cached.core) || this._create(id, namespace, opts)
|
|
255
|
-
cached = this.readyCache.get(id)
|
|
256
|
-
cached.refs++
|
|
257
|
-
|
|
258
|
-
return this._makeRef(id, core, !opts.passive)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
_get (namespace, opts) {
|
|
262
|
-
const keys = this._generateKeys(namespace, opts)
|
|
263
|
-
const id = this._getCacheId(keys)
|
|
264
|
-
|
|
265
|
-
const cached = this.cache.get(id)
|
|
266
|
-
const core = cached ?? this._create(id, namespace, opts, keys)
|
|
267
|
-
if (!opts.passive) this.cache.increment(id)
|
|
268
|
-
|
|
269
|
-
return this._makeRef(id, core, !opts.passive)
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
get (namespace, opts) {
|
|
273
|
-
if (!this.opened) return this._getBeforeReady(namespace, opts)
|
|
274
|
-
return this._get(namespace, opts)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async getPassiveCore (dkey) {
|
|
278
|
-
if (Buffer.isBuffer(dkey)) dkey = dkey.toString('hex')
|
|
279
|
-
const keys = await this.db.getPassiveCoreKeys(dkey)
|
|
280
|
-
if (!keys) return null
|
|
281
|
-
return this._get(null, { ...keys, passive: true })
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
getOpenCores () {
|
|
285
|
-
return [...this.cache.entries.values()].map(({ value }) => value)
|
|
286
|
-
}
|
|
287
|
-
}
|
package/lib/pending-file.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
const RAS = require('random-access-storage')
|
|
2
|
-
|
|
3
|
-
module.exports = class PendingFile extends RAS {
|
|
4
|
-
constructor (wait) {
|
|
5
|
-
super()
|
|
6
|
-
this._wait = wait
|
|
7
|
-
this._storage = null
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
_open (req) {
|
|
11
|
-
this._wait((err, storage) => {
|
|
12
|
-
if (err) return req.callback(err)
|
|
13
|
-
this._storage = storage
|
|
14
|
-
req.callback(null)
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
_stat (req) {
|
|
19
|
-
this._storage.stat(req.callback.bind(req))
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
_read (req) {
|
|
23
|
-
this._storage.read(req.offset, req.size, req.callback.bind(req))
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
_write (req) {
|
|
27
|
-
this._storage.write(req.offset, req.data, req.callback.bind(req))
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
_del (req) {
|
|
31
|
-
this._storage.delete(req.offset, req.size, req.callback.bind(req))
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
_close (req) {
|
|
35
|
-
this._storage.close(req.callback.bind(req))
|
|
36
|
-
}
|
|
37
|
-
}
|
package/lib/replicator.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
const { EventEmitter } = require('events')
|
|
2
|
-
const HypercoreProtocol = require('hypercore-protocol')
|
|
3
|
-
const eos = require('end-of-stream')
|
|
4
|
-
|
|
5
|
-
module.exports = class Replicator extends EventEmitter {
|
|
6
|
-
constructor (loader, opts = {}) {
|
|
7
|
-
super()
|
|
8
|
-
|
|
9
|
-
this.loader = loader
|
|
10
|
-
this.opts = opts
|
|
11
|
-
this.streams = []
|
|
12
|
-
|
|
13
|
-
this.loader.on('core', core => {
|
|
14
|
-
this.inject(core)
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
_replicateCore (isInitiator, core, mainStream, opts) {
|
|
19
|
-
core.ready(function (err) {
|
|
20
|
-
if (err) return
|
|
21
|
-
core.replicate(isInitiator, {
|
|
22
|
-
...opts,
|
|
23
|
-
stream: mainStream
|
|
24
|
-
})
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
inject (core) {
|
|
29
|
-
for (const { stream, opts } of this.streams) {
|
|
30
|
-
this._replicateCore(false, core, stream, { ...opts })
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
replicate (isInitiator, replicationOpts = {}) {
|
|
35
|
-
const finalOpts = { ...this.opts, ...replicationOpts }
|
|
36
|
-
const mainStream = replicationOpts.stream || new HypercoreProtocol(isInitiator, { ...finalOpts })
|
|
37
|
-
|
|
38
|
-
for (const core of this.loader.getOpenCores()) {
|
|
39
|
-
this._replicateCore(isInitiator, core, mainStream, { ...finalOpts })
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const streamState = { stream: mainStream, opts: finalOpts }
|
|
43
|
-
this.streams.push(streamState)
|
|
44
|
-
|
|
45
|
-
mainStream.on('discovery-key', async (dkey) => {
|
|
46
|
-
const core = await this.loader.getPassiveCore(dkey)
|
|
47
|
-
if (!core) return mainStream.close(dkey)
|
|
48
|
-
this._replicateCore(false, core, mainStream, { ...finalOpts })
|
|
49
|
-
})
|
|
50
|
-
eos(mainStream, err => {
|
|
51
|
-
if (err) this.emit('replication-error', err)
|
|
52
|
-
const idx = this.streams.indexOf(streamState)
|
|
53
|
-
if (idx === -1) return
|
|
54
|
-
this.streams.splice(idx, 1)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
return mainStream
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
close () {
|
|
61
|
-
if (!this.streams.length) return
|
|
62
|
-
for (const stream of this.streams) {
|
|
63
|
-
stream.destroy()
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|