OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-vdrive commits / 295ac67

hyper-vdrive: a Hyperdrive that stores no blob bytes
Real drive all the way up — stock hyperdrive/hyperblobs/hypercore, real merkle tree and signatures. Only the storage layer is swapped: block records hold a 32-byte content hash and the bytes are materialised on demand from a content-addressed store already on disk. The seam: Core takes its per-core storage from exactly two calls, db.resumeCore() and db.createCore() (hypercore/lib/core.js). Subclassing CorestoreStorage and wrapping those two return values sits under every block read and write with nothing above aware. Stored records carry a one-byte tag — 0x00 literal, 0x01 pointer — so the pointer commits in the same CoreTX as the tree node and truncate frees it for free. Measured on ten consecutive real 367MB release artifacts: 580.8MB of stock drive becomes 0.95MB, with an identical drive key, and publishing is faster than stock because the RocksDB block writes disappear. A real `pear install pear://<key>` installs from it — verified byte-identical against the stock pear 3.0.1 CLI over the public DHT. Gaps are documented in the README, the biggest being that GC must be paired with core.clear() — dropping bytes does not update the bitfield, so the core goes on advertising blocks it can no longer produce. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
295ac67998539ab955d72f8156cd36f7cb0d7308
by plankatron <fleeky@gmail.com> · 2026-08-24T14:08:03.000Z

Changed files (13) +2253 -0

.gitignore +3 -0
……@@ -0,0 +1,3 @@
1node_modules
2work
3*.log
README.md +105 -0
……@@ -0,0 +1,105 @@
1# hyper-vdrive
2
3A **real** Hyperdrive that stores **no blob bytes**.
4
5The drive is genuine all the way up — real Hyperbee metadata, real Hyperblobs core,
6real merkle tree, real signatures, stock `hyperdrive` / `hyperblobs` / `hypercore`
7with no patches. Only the storage layer underneath is swapped: block bytes are never
8written to RocksDB. Instead each block record holds a 32-byte content hash, and the
9bytes are read on demand from a content-addressed store already on disk.
10
11Peers can't tell. `pear install pear://<key>` works against it.
12
13## Why
14
15We already store every file once, chunked and content-addressed, in `hyper-overlay`.
16Publishing the same content as a Hyperdrive meant a second full copy in RocksDB. This
17removes the second copy while keeping `pear://` compatibility.
18
19Measured on ten consecutive real 367 MB game-pack releases:
20
21| | stock drive | virtual drive |
22|---|---|---|
23| store after 10 releases | 580.8 MB | **0.95 MB** |
24| publish time per release | 3.6 s | **2.4 s** |
25| drive key | — | **identical** |
26
27See [RESULTS.md](RESULTS.md) for the full measurements and method.
28
29## How
30
31`Core` takes its per-core storage from exactly two calls
32(`hypercore/lib/core.js`): `db.resumeCore(discoveryKey)` and `db.createCore({...})`.
33Subclassing `CorestoreStorage` and wrapping those two return values puts us underneath
34every block read and write, with nothing above aware.
35
36Stored block records carry a one-byte tag:
37
38```
390x00 | <original bytes> literal — bee blocks, and anything we don't hold
400x01 | <32-byte hash> pointer — bytes live in the content store
41```
42
43Because the pointer *is* the block value, it commits in the same `CoreTX` as the tree
44node — tree and pointer land atomically, and `deleteBlock` / truncate free it with no
45extra bookkeeping. Cost: 1 byte per literal block, 33 bytes per virtualised block.
46
47The chunk hash is `hypercore-crypto`'s `data()` — the same construction hypercore uses
48for a merkle **leaf** (`merkle-tree.js`). So our existing FastCDC chunk hashes are
49already the leaf hashes, and a chunk maps directly onto a block.
50
51## Usage
52
53```js
54const { ContentStore, build } = require('hyper-vdrive')
55
56const content = new ContentStore()
57// tell it where bytes live: contentHash -> (file, offset, length)
58for (const c of chunk(buf)) content.add(c.hash, file, c.offset, c.length)
59
60const Storage = build(content)
61const store = new Corestore(new Storage(dir))
62const drive = new Hyperdrive(store)
63```
64
65Write files chunk-by-chunk so block boundaries are your chunk boundaries:
66
67```js
68const ws = drive.createWriteStream('/app.bin', { dedup: true })
69for (const c of chunks) ws.write(c.data) // honour backpressure
70ws.end()
71```
72
73## Tests
74
75Tests import the FastCDC chunker from a sibling `hyper-overlay` checkout
76(`../../hyper-overlay/lib/chunker.js`) rather than vendoring a copy that could drift.
77
78```
79npm install
80node test/pass-through.mjs ./work/passthrough # seam holds, identical drive key
81node test/redirect.mjs ./work/redirect <f1> <f2>
82node test/releases.mjs ./work/releases <f...> # N-release size curve
83node test/gc-bitfield.mjs ./work/gc # GC / bitfield pairing
84node example/publish.mjs ./work/app # pear-install-shaped drive
85node example/seed.mjs ./work/app # serve it on the DHT
86```
87
88## Status — spike
89
90Proven: identical drive key/blobs key/root hash, reopen from disk, replication to a
91stock peer, and a real `pear install` from a drive holding zero blobs.
92
93Known gaps, in the order they'd bite:
94
951. **GC must be paired.** Dropping bytes does *not* update the bitfield — the core goes
96 on advertising blocks it can't produce. GC has to clear the block range too. And with
97 `dedup: true`, old releases share blocks with new ones, so ranges must be computed
98 from the content store's refcounts, never from a version's block range.
992. **Block streams** (`createBlockStream`, used by audit/copy-prologue) yield raw tagged
100 records; they need decoding like `getBlock` does.
1013. **Encryption** sits above storage and prepends 8 bytes of padding, so block bytes stop
102 equalling chunk bytes. Run unencrypted, or teach the adapter to re-derive.
1034. **Unsupported seam.** `hypercore-storage` has no public pluggable-storage contract
104 since `random-access-storage` was dropped. Pin versions and run the tests as a canary.
1055. Untested: many small files, truncate/clear, crash during append.
RESULTS.md +125 -0
……@@ -0,0 +1,125 @@
1# Measurements
2
3All runs on tabertron, 2026-08-24. Source data: ten consecutive real `hypertps.pck`
4release artifacts (~367 MB each) from `~/.local/share/store/dependencies`, and
5synthetic files where noted. Chunker: `hyper-overlay/lib/chunker.js` (FastCDC,
6tier 2 → ~320 KB average chunk).
7
8## T0 — does stock Pear read chunk-sized blocks?
9
10The installed `pear` 3.0.1 bundle (`~/.config/pear/current/boot.bundle`) contains
11`node_modules/hyperblobs/lib/block-map.js` and the marker strings `Block map is too
12large`, `Unsupported block map version`, `_dedup`, `getBlockMap`. So the shipped
13reader supports block maps and variable-size blocks. **Pass.**
14
15## T1 — storage cost, one release pair
16
17Two consecutive releases, 367.06 MB each, into one drive at the same path.
18
19| Arm | after v1 | after v2 | v2 delta |
20|---|---|---|---|
21| overlay (control) | 360.1 MB | 378.3 MB | 18.2 MB |
22| hyperdrive, no dedup | 368.3 MB | 736.4 MB | 368.1 MB |
23| hyperdrive, `dedup:true`, fixed 64 KB | 367.6 MB | 488.7 MB | 121.2 MB |
24| hyperdrive, `dedup:true`, FastCDC | 360.5 MB | 378.7 MB | 18.2 MB |
25
26Two corrections to earlier assumptions:
27
28- RocksDB per-byte overhead is **0.3%**, not a multiplier (368.3 MB stored for
29 367.06 MB in). The corestore column family enables blob files with GC.
30- Hyperblobs **does** dedup, at block level, via `{dedup:true}` + block maps
31 (`hyperblobs/lib/streams.js:86-101`, `hyperdrive/index.js:591-596`). A stock drive
32 fed FastCDC chunks matched the overlay to within 0.1%.
33
34## T1c — ten releases, stock drive vs overlay
35
36| Release | drive | overlay | gap |
37|---|---|---|---|
38| 1 | 360.5 MB | 360.1 MB | +0.4 |
39| 5 | 456.1 MB | 453.2 MB | +2.9 |
40| 6 | 484.8 MB | 471.4 MB | +13.4 |
41| 10 | 580.8 MB | 563.3 MB | +17.5 (3.1%) |
42
43The gap appears at release 6 — a chunk set that vanished and returned. `dedup:true`
44seeds its hash table from **only the previous entry at that path**
45(`hyperdrive/index.js:592`), so resurrected chunks are re-appended. Publish time was
46flat at ~3.6 s: the seeding re-read is O(previous release), not O(history).
47
48## T2 — does the storage seam hold?
49
50Pass-through wrapper (delegates everything, redirects nothing):
51
52```
53identical drive key / blobs key / root hash / digests true
54reopen from disk true
55replication to a stock peer true
56intercepted 111 getBlock, 111 putBlock
57```
58
59Nothing escaped the wrapper. **Pass.**
60
61## T3 — redirect, two releases
62
63```
64sameDriveKey / sameBlobsKey / sameRootHash true
65blobsLength stock 2244 virtual 2244
66input 734.0 MB
67stock store 720.8 MB
68virtual store 1.03 MB (tagged-record scheme, no side file)
69peer store 720.7 MB (stock peer replicated from the virtual drive)
70blocks 2242 redirected, 5 literal (the bee's own)
71hash failures 0
72```
73
74## T3n — ten releases, virtual
75
76| Release | blocks | store | stock equivalent |
77|---|---|---|---|
78| 1 | 1122 | 0.50 MB | 360.5 MB |
79| 5 | 1368 | 0.50 MB | 456.1 MB |
80| 10 | 1682 | **0.82 MB** | **580.8 MB** |
81
823.67 GB of releases → 0.95 MB (**610×**). Publish ~2.4 s/release, *faster* than stock
83because the RocksDB block writes disappear. Pointer overhead is 36 bytes per block —
84≈0.011% of content at tier-2 chunk sizes.
85
86## T5 — real `pear install`
87
88Built a `/by-arch/linux-x64/app/` drive on virtual storage (6.29 MB payload, 120 KB
89store), seeded it on the public DHT, and ran the stock `pear` 3.0.1 CLI:
90
91```
92$ pear install pear://uimennwjwyot5cinekhb348kawufit1eq4ctyf5irurn1nfmo3so --to …
93[ Peers: 1 ] [ ⬇ 2.8MB - 2.8MB/s ]
94App: t5app Version: 1.0.0
95Target: …/t5/installed/t5app
96Installed
97```
98
99Installed file: 6,291,480 bytes, mode 755, hash identical to source, runs. Seeder
100store unchanged at 139 KB; it served 6,291,560 bytes as 89 redirected block reads
101with 0 hash failures. **Pass.**
102
103## GC / bitfield
104
105```
106host still advertises after bytes deleted TRUE ← the lie
107peer requesting it REQUEST_TIMEOUT after 5.0 s
108after core.clear(range): host advertises FALSE
109unrelated file still fetchable TRUE
110```
111
112`core.clear()` on the affected range is the correct pairing. Not demonstrated: that
113clearing turns a stall into "ask another peer" — with one seeder both look like a
114timeout, so proving it needs a two-seeder test. One run of the probe died with an
115*uncaught* `REQUEST_TIMEOUT` thrown from `hypercore/lib/replicator.js:3371` rather than
116rejecting `get()`; the rerun did not reproduce it. Racy, but a long-lived seeder in that
117state could take an uncaught exception.
118
119## Upstream bug found
120
121A 0-byte file written with `dedup: true` cannot be read back: hyperblobs writes an
122empty block map, then `seekBlockMap` dereferences `map.blocks === null`
123(`hyperblobs/lib/streams.js:327`). Reproduced on a plain store, so it is upstream.
124Matters because app trees are full of empty files. Workaround: write 0-byte files
125without dedup.
example/publish.mjs +85 -0
……@@ -0,0 +1,85 @@
1// T5 build — publish a pear-install-shaped app into a VIRTUAL drive (no blob bytes).
2// Layout required by pear-install/index.js:
3// /package.json (name, version, bin)
4// /by-arch/<platform>-<arch>/app/<bin>
5import { createRequire } from 'module'
6const require = createRequire(import.meta.url)
7
8const fs = require('fs')
9const path = require('path')
10const process = require('process')
11const crypto = require('hypercore-crypto')
12const idEnc = require('hypercore-id-encoding')
13const Corestore = require('corestore')
14const Hyperdrive = require('hyperdrive')
15const { ContentStore, build } = require('../lib/storage.cjs')
16const { chunk } = await import('../../hyper-overlay/lib/chunker.js')
17
18const ROOT = process.argv[2]
19const APP = 't5app'
20const HOST = process.platform + '-' + process.arch
21const srcDir = ROOT + '/src'
22const dir = ROOT + '/virtual'
23
24// The "overlay": real files on disk that stay canonical.
25fs.mkdirSync(srcDir, { recursive: true })
26const binBytes = Buffer.concat([
27 Buffer.from('#!/bin/sh\necho t5app-ok\n'),
28 crypto.randomBytes(6 * 1024 * 1024) // payload so replication is real work
29])
30const pkgBytes = Buffer.from(
31 JSON.stringify({ name: APP, version: '1.0.0', bin: { [APP]: APP } }, null, 2)
32)
33fs.writeFileSync(path.join(srcDir, APP), binBytes)
34fs.writeFileSync(path.join(srcDir, 'package.json'), pkgBytes)
35
36const files = [
37 { name: '/package.json', file: path.join(srcDir, 'package.json'), buf: pkgBytes },
38 { name: '/by-arch/' + HOST + '/app/' + APP, file: path.join(srcDir, APP), buf: binBytes }
39]
40
41const content = new ContentStore()
42for (const f of files) {
43 f.chunks = chunk(f.buf)
44 for (const c of f.chunks) content.add(c.hash, f.file, c.offset, c.length)
45}
46
47const Storage = build(content)
48const store = new Corestore(new Storage(dir), { primaryKey: Buffer.alloc(32, 7), unsafe: true })
49const drive = new Hyperdrive(store)
50await drive.ready()
51
52function write (ws, buf) {
53 return new Promise((resolve) => {
54 if (ws.write(buf)) return resolve()
55 ws.once('drain', resolve)
56 })
57}
58
59for (const f of files) {
60 const ws = drive.createWriteStream(f.name, { dedup: true })
61 for (const c of f.chunks) await write(ws, c.data)
62 await new Promise((resolve, reject) => {
63 ws.once('error', reject)
64 ws.once('close', resolve)
65 ws.end()
66 })
67}
68
69await drive.getBlobs()
70const key = drive.key
71const info = {
72 key: key.toString('hex'),
73 link: 'pear://' + idEnc.normalize(key),
74 blobsLength: drive.blobs.core.length,
75 binSha: crypto.hash(binBytes).toString('hex'),
76 binBytes: binBytes.length,
77 host: HOST
78}
79
80await drive.close()
81await store.close()
82content.close()
83
84fs.writeFileSync(ROOT + '/drive.json', JSON.stringify(info, null, 2))
85console.log(JSON.stringify(info, null, 2))
example/seed.mjs +65 -0
……@@ -0,0 +1,65 @@
1// T5 seed — serve the VIRTUAL drive on the DHT. Every byte a peer receives is
2// materialised on demand from the source files; the store holds no blob bytes.
3import { createRequire } from 'module'
4const require = createRequire(import.meta.url)
5
6const fs = require('fs')
7const path = require('path')
8const process = require('process')
9const Corestore = require('corestore')
10const Hyperdrive = require('hyperdrive')
11const Hyperswarm = require('hyperswarm')
12const { ContentStore, build, stats } = require('../lib/storage.cjs')
13const { chunk } = await import('../../hyper-overlay/lib/chunker.js')
14
15const ROOT = process.argv[2]
16const dir = ROOT + '/virtual'
17const srcDir = ROOT + '/src'
18
19// Rebuild the content index from the source files, exactly as the overlay would.
20const content = new ContentStore()
21for (const name of fs.readdirSync(srcDir)) {
22 const file = path.join(srcDir, name)
23 const buf = fs.readFileSync(file)
24 for (const c of chunk(buf)) content.add(c.hash, file, c.offset, c.length)
25}
26
27const Storage = build(content)
28const store = new Corestore(new Storage(dir), { primaryKey: Buffer.alloc(32, 7), unsafe: true })
29const drive = new Hyperdrive(store)
30await drive.ready()
31await drive.getBlobs()
32
33const swarm = new Hyperswarm()
34swarm.on('connection', (conn) => store.replicate(conn))
35const discovery = swarm.join(drive.discoveryKey, { server: true, client: false })
36await discovery.flushed()
37
38console.log(
39 JSON.stringify({ seeding: drive.key.toString('hex'), storeBytes: dirSize(dir) })
40)
41
42function dirSize (p) {
43 let total = 0
44 for (const e of fs.readdirSync(p, { withFileTypes: true })) {
45 const full = path.join(p, e.name)
46 total += e.isDirectory() ? dirSize(full) : fs.statSync(full).size
47 }
48 return total
49}
50
51process.on('SIGTERM', shutdown)
52process.on('SIGINT', shutdown)
53
54async function shutdown () {
55 console.log(JSON.stringify({ served: stats }))
56 await swarm.destroy()
57 await drive.close()
58 await store.close()
59 content.close()
60 process.exit(0)
61}
62
63setInterval(() => {
64 console.log(JSON.stringify({ peers: swarm.connections.size, bytesServed: stats.bytesServed }))
65}, 10000)
lib/passthrough.cjs +75 -0
……@@ -0,0 +1,75 @@
1// T2 — pass-through wrapper around hypercore-storage.
2//
3// Core takes its per-core storage from exactly two calls (hypercore/lib/core.js):
4// :162 await this.db.resumeCore(discoveryKey)
5// :232 await this.db.createCore({...})
6// so subclassing CorestoreStorage and wrapping those two return values puts us
7// under every block read/write without touching anything above.
8//
9// Objects that construct more of themselves (snapshot/atomize) must be re-wrapped,
10// and read()/write() must return wrapped RX/TX — that is where T3 will redirect
11// getBlock/putBlock. Here everything is pure delegation.
12
13const CorestoreStorage = require('hypercore-storage')
14
15const stats = { getBlock: 0, putBlock: 0, read: 0, write: 0, wrapped: 0 }
16
17function delegate (target, overrides) {
18 return new Proxy(target, {
19 get (t, prop) {
20 if (prop === '__unwrapped') return t
21 if (overrides && prop in overrides) return overrides[prop]
22 // NB: no receiver — getters must run with `this` bound to the real object
23 const v = Reflect.get(t, prop)
24 return typeof v === 'function' ? v.bind(t) : v
25 },
26 set (t, prop, value) {
27 t[prop] = value
28 return true
29 }
30 })
31}
32
33function wrapRX (rx) {
34 stats.read++
35 return delegate(rx, {
36 getBlock (index) {
37 stats.getBlock++
38 return rx.getBlock(index)
39 }
40 })
41}
42
43function wrapTX (tx) {
44 stats.write++
45 return delegate(tx, {
46 putBlock (index, data) {
47 stats.putBlock++
48 return tx.putBlock(index, data)
49 }
50 })
51}
52
53function wrapCoreStorage (inner) {
54 if (!inner) return inner
55 stats.wrapped++
56 const overrides = {
57 read: (...args) => wrapRX(inner.read(...args)),
58 write: (...args) => wrapTX(inner.write(...args)),
59 snapshot: (...args) => wrapCoreStorage(inner.snapshot(...args)),
60 atomize: (...args) => wrapCoreStorage(inner.atomize(...args))
61 }
62 return delegate(inner, overrides)
63}
64
65class WrappedCorestoreStorage extends CorestoreStorage {
66 async resumeCore (discoveryKey) {
67 return wrapCoreStorage(await super.resumeCore(discoveryKey))
68 }
69
70 async createCore (data) {
71 return wrapCoreStorage(await super.createCore(data))
72 }
73}
74
75module.exports = { WrappedCorestoreStorage, stats }
lib/storage.cjs +147 -0
……@@ -0,0 +1,147 @@
1// Step 1 — atomic pointers.
2//
3// T3 kept index->hash in a JSON side-file, so a crash between the tree write and
4// the flush orphaned the tree. Fix: the pointer IS the block value. Every stored
5// record carries a 1-byte tag:
6//
7// 0x00 | <original bytes> literal (bee blocks, anything we don't hold)
8// 0x01 | <32-byte hash> pointer (bytes live in the content store)
9//
10// Now the pointer lands in the same CoreTX as the tree node — same atom, same
11// flush — and deleteBlock/deleteBlockRange/truncate free it with no extra work.
12// Cost: 1 byte per literal block, 33 bytes per virtualised block.
13
14const fs = require('fs')
15const crypto = require('hypercore-crypto')
16const CorestoreStorage = require('hypercore-storage')
17
18const TAG_LITERAL = 0x00
19const TAG_POINTER = 0x01
20
21const stats = {
22 putRedirected: 0,
23 putStored: 0,
24 getRedirected: 0,
25 getStored: 0,
26 bytesServed: 0,
27 hashFailures: 0,
28 missingBytes: 0
29}
30
31class ContentStore {
32 constructor () {
33 this.locations = new Map()
34 this._fds = new Map()
35 }
36
37 add (hash, file, offset, length) {
38 if (!this.locations.has(hash)) this.locations.set(hash, { file, offset, length })
39 }
40
41 has (hash) {
42 return this.locations.has(hash)
43 }
44
45 read (hash, { verify = true } = {}) {
46 const loc = this.locations.get(hash)
47 if (!loc) {
48 stats.missingBytes++
49 return null
50 }
51 let fd = this._fds.get(loc.file)
52 if (fd === undefined) {
53 fd = fs.openSync(loc.file, 'r')
54 this._fds.set(loc.file, fd)
55 }
56 const buf = Buffer.allocUnsafe(loc.length)
57 fs.readSync(fd, buf, 0, loc.length, loc.offset)
58 if (verify && crypto.data(buf).toString('hex') !== hash) {
59 stats.hashFailures++
60 return null
61 }
62 stats.bytesServed += buf.length
63 return buf
64 }
65
66 close () {
67 for (const fd of this._fds.values()) fs.closeSync(fd)
68 this._fds.clear()
69 }
70}
71
72function delegate (target, overrides) {
73 return new Proxy(target, {
74 get (t, prop) {
75 if (prop === '__unwrapped') return t
76 if (overrides && prop in overrides) return overrides[prop]
77 const v = Reflect.get(t, prop)
78 return typeof v === 'function' ? v.bind(t) : v
79 },
80 set (t, prop, value) {
81 t[prop] = value
82 return true
83 }
84 })
85}
86
87function build (content) {
88 function decode (record) {
89 if (record === null) return null
90 if (record[0] === TAG_POINTER) {
91 stats.getRedirected++
92 return content.read(record.subarray(1).toString('hex'))
93 }
94 stats.getStored++
95 return record.subarray(1)
96 }
97
98 function wrapRX (rx) {
99 return delegate(rx, {
100 getBlock (index) {
101 return rx.getBlock(index).then(decode)
102 }
103 })
104 }
105
106 function wrapTX (tx) {
107 return delegate(tx, {
108 putBlock (index, data) {
109 const digest = crypto.data(data)
110 if (content.has(digest.toString('hex'))) {
111 stats.putRedirected++
112 const rec = Buffer.allocUnsafe(33)
113 rec[0] = TAG_POINTER
114 digest.copy(rec, 1)
115 return tx.putBlock(index, rec)
116 }
117 stats.putStored++
118 const rec = Buffer.allocUnsafe(data.byteLength + 1)
119 rec[0] = TAG_LITERAL
120 data.copy(rec, 1)
121 return tx.putBlock(index, rec)
122 }
123 })
124 }
125
126 function wrapCoreStorage (inner) {
127 if (!inner) return inner
128 return delegate(inner, {
129 read: (...args) => wrapRX(inner.read(...args)),
130 write: (...args) => wrapTX(inner.write(...args)),
131 snapshot: (...args) => wrapCoreStorage(inner.snapshot(...args)),
132 atomize: (...args) => wrapCoreStorage(inner.atomize(...args))
133 })
134 }
135
136 return class RedirectingCorestoreStorage extends CorestoreStorage {
137 async resumeCore (discoveryKey) {
138 return wrapCoreStorage(await super.resumeCore(discoveryKey))
139 }
140
141 async createCore (data) {
142 return wrapCoreStorage(await super.createCore(data))
143 }
144 }
145}
146
147module.exports = { ContentStore, build, stats, TAG_LITERAL, TAG_POINTER }
package-lock.json +1103 -0
……@@ -0,0 +1,1103 @@
1{
2 "name": "hyper-vdrive",
3 "version": "0.0.1",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "hyper-vdrive",
9 "version": "0.0.1",
10 "license": "AGPL-3.0-or-later",
11 "dependencies": {
12 "corestore": "^7.12.0",
13 "hypercore": "^11.35.2",
14 "hypercore-crypto": "^3.7.0",
15 "hypercore-id-encoding": "^1.3.0",
16 "hypercore-storage": "^3.2.1",
17 "hyperdrive": "^13.3.3",
18 "hyperswarm": "^4.17.0"
19 }
20 },
21 "node_modules/@hyperswarm/secret-stream": {
22 "version": "6.9.1",
23 "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz",
24 "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==",
25 "license": "Apache-2.0",
26 "dependencies": {
27 "b4a": "^1.1.0",
28 "hypercore-crypto": "^3.3.1",
29 "noise-curve-ed": "^2.0.1",
30 "noise-handshake": "^4.0.0",
31 "sodium-secretstream": "^1.1.0",
32 "sodium-universal": "^5.0.0",
33 "streamx": "^2.14.0",
34 "timeout-refresh": "^2.0.0",
35 "unslab": "^1.3.0"
36 }
37 },
38 "node_modules/adaptive-timeout": {
39 "version": "1.0.1",
40 "resolved": "https://registry.npmjs.org/adaptive-timeout/-/adaptive-timeout-1.0.1.tgz",
41 "integrity": "sha512-+seGiBtHqbnyZ0Quq/ZGxxwP66153WBABawa07/QeyuPFzbduPBjiGQAyCiriiLDzt3dkbMBskSlfqtRwblK8Q==",
42 "license": "Apache-2.0",
43 "dependencies": {
44 "xache": "^1.2.1"
45 }
46 },
47 "node_modules/b4a": {
48 "version": "1.8.1",
49 "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
50 "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==",
51 "license": "Apache-2.0",
52 "peerDependencies": {
53 "react-native-b4a": "*"
54 },
55 "peerDependenciesMeta": {
56 "react-native-b4a": {
57 "optional": true
58 }
59 }
60 },
61 "node_modules/bare-addon-resolve": {
62 "version": "1.10.1",
63 "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.1.tgz",
64 "integrity": "sha512-F/SD2du8keuYSb4xipnGz5j2E6yhNdHA8ZVxtHae6h2uOrpBIjjbhXvjzKZbr5XUOzqBzh/i8GVFycj2DlFQIA==",
65 "license": "Apache-2.0",
66 "dependencies": {
67 "bare-module-resolve": "^1.10.0",
68 "bare-semver": "^1.0.0"
69 },
70 "peerDependencies": {
71 "bare-url": "*"
72 },
73 "peerDependenciesMeta": {
74 "bare-url": {
75 "optional": true
76 }
77 }
78 },
79 "node_modules/bare-ansi-escapes": {
80 "version": "2.2.3",
81 "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz",
82 "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==",
83 "license": "Apache-2.0",
84 "dependencies": {
85 "bare-stream": "^2.6.5"
86 },
87 "peerDependencies": {
88 "bare-buffer": "*"
89 },
90 "peerDependenciesMeta": {
91 "bare-buffer": {
92 "optional": true
93 }
94 }
95 },
96 "node_modules/bare-assert": {
97 "version": "1.2.0",
98 "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz",
99 "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==",
100 "license": "Apache-2.0",
101 "dependencies": {
102 "bare-inspect": "^3.1.2"
103 }
104 },
105 "node_modules/bare-events": {
106 "version": "2.9.1",
107 "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz",
108 "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==",
109 "license": "Apache-2.0",
110 "peerDependencies": {
111 "bare-abort-controller": "*"
112 },
113 "peerDependenciesMeta": {
114 "bare-abort-controller": {
115 "optional": true
116 }
117 }
118 },
119 "node_modules/bare-fs": {
120 "version": "4.8.1",
121 "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.1.tgz",
122 "integrity": "sha512-N1nnXdHZAOSstz0XiHikGS4HGMH4CnSwhqWdGQQMqqdvp4Jybm9sE3R1WVnpWVd4SFkc8ryPDBLViNLwiEqECg==",
123 "license": "Apache-2.0",
124 "dependencies": {
125 "bare-events": "^2.5.4",
126 "bare-path": "^3.0.0",
127 "bare-stream": "^2.6.4",
128 "bare-url": "^2.2.2",
129 "fast-fifo": "^1.3.2"
130 },
131 "engines": {
132 "bare": ">=1.28.0"
133 },
134 "peerDependencies": {
135 "bare-buffer": "*"
136 },
137 "peerDependenciesMeta": {
138 "bare-buffer": {
139 "optional": true
140 }
141 }
142 },
143 "node_modules/bare-inspect": {
144 "version": "3.1.5",
145 "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.5.tgz",
146 "integrity": "sha512-6xmjxF3vPxNYNb5I6nqPgaRQlQhO1qjkRNe2lKUSTel5IZZQtHJKm59OVYFgCly4urvVqJJvRrJecZGy6rUWdQ==",
147 "license": "Apache-2.0",
148 "dependencies": {
149 "bare-ansi-escapes": "^2.1.0",
150 "bare-type": "^1.0.0"
151 },
152 "engines": {
153 "bare": ">=1.18.0"
154 }
155 },
156 "node_modules/bare-module-resolve": {
157 "version": "1.12.4",
158 "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.4.tgz",
159 "integrity": "sha512-xcfgg2u7HqgJiBmah71O9vvdFAgHCvkqC/WSC2O7Bbgosoc1eC/BWe/6IDJ4OsfKlkxuvC/TDWXC+oH5yeW8mA==",
160 "license": "Apache-2.0",
161 "dependencies": {
162 "bare-semver": "^1.0.0"
163 },
164 "peerDependencies": {
165 "bare-url": "*"
166 },
167 "peerDependenciesMeta": {
168 "bare-url": {
169 "optional": true
170 }
171 }
172 },
173 "node_modules/bare-path": {
174 "version": "3.1.1",
175 "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz",
176 "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==",
177 "license": "Apache-2.0"
178 },
179 "node_modules/bare-semver": {
180 "version": "1.1.0",
181 "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.1.0.tgz",
182 "integrity": "sha512-1Hw5qJ7hXdVt3uPUqjeFTuxyvBUJauvz5A1I2jk8gzjZMHp04n//6nV9MDbG9CMw78JHY2lGV0w6s//LrASm2w==",
183 "license": "Apache-2.0"
184 },
185 "node_modules/bare-stream": {
186 "version": "2.13.3",
187 "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz",
188 "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==",
189 "license": "Apache-2.0",
190 "dependencies": {
191 "b4a": "^1.8.1",
192 "streamx": "^2.25.0",
193 "teex": "^1.0.1"
194 },
195 "peerDependencies": {
196 "bare-abort-controller": "*",
197 "bare-buffer": "*",
198 "bare-events": "*"
199 },
200 "peerDependenciesMeta": {
201 "bare-abort-controller": {
202 "optional": true
203 },
204 "bare-buffer": {
205 "optional": true
206 },
207 "bare-events": {
208 "optional": true
209 }
210 }
211 },
212 "node_modules/bare-type": {
213 "version": "1.1.0",
214 "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz",
215 "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==",
216 "license": "Apache-2.0",
217 "engines": {
218 "bare": ">=1.2.0"
219 }
220 },
221 "node_modules/bare-url": {
222 "version": "2.5.2",
223 "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.2.tgz",
224 "integrity": "sha512-L13PCJzKG8RGvx8V1/DdMi12ERhC3tprr7/8a94BxpmnRsFqxh5XZNdhtMxu5HPkRshYOOWRGY8lDP7ZhpG9Cg==",
225 "license": "Apache-2.0",
226 "dependencies": {
227 "bare-path": "^3.0.0"
228 }
229 },
230 "node_modules/big-sparse-array": {
231 "version": "1.0.3",
232 "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz",
233 "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==",
234 "license": "MIT"
235 },
236 "node_modules/binary-stream-equals": {
237 "version": "1.0.0",
238 "resolved": "https://registry.npmjs.org/binary-stream-equals/-/binary-stream-equals-1.0.0.tgz",
239 "integrity": "sha512-xiUT5LGfD8JiLhbXiG+ByOnbgb9f2ssRLfZDQMl3nZdf89EotQZGZuMkDN8J3n46emabE7RnJ1q0r7Hv3INExw==",
240 "license": "MIT",
241 "dependencies": {
242 "b4a": "^1.3.1"
243 }
244 },
245 "node_modules/bits-to-bytes": {
246 "version": "1.3.0",
247 "resolved": "https://registry.npmjs.org/bits-to-bytes/-/bits-to-bytes-1.3.0.tgz",
248 "integrity": "sha512-OJoHTpFXS9bXHBCekGTByf3MqM8CGblBDIduKQeeVVeiU9dDWywSSirXIBYGgg3d1zbVuvnMa1vD4r6PA0kOKg==",
249 "license": "ISC",
250 "dependencies": {
251 "b4a": "^1.5.0"
252 }
253 },
254 "node_modules/blind-relay": {
255 "version": "1.6.1",
256 "resolved": "https://registry.npmjs.org/blind-relay/-/blind-relay-1.6.1.tgz",
257 "integrity": "sha512-38YmKCl/m9NcTkwueBbcGIt9Zx3IT/Q9Nsluu6C5Gur6PqfE0zWPhPwCzia1hri/g0ICYxrqkgLtEaoWD5WeSQ==",
258 "license": "Apache-2.0",
259 "dependencies": {
260 "b4a": "^1.6.4",
261 "bare-events": "^2.2.0",
262 "bits-to-bytes": "^1.3.0",
263 "compact-encoding": "^3.0.0",
264 "compact-encoding-bitfield": "^1.0.0",
265 "protomux": "^3.5.1",
266 "sodium-universal": "^5.0.0",
267 "streamx": "^2.15.1"
268 }
269 },
270 "node_modules/bogon": {
271 "version": "1.3.0",
272 "resolved": "https://registry.npmjs.org/bogon/-/bogon-1.3.0.tgz",
273 "integrity": "sha512-04tu0G/v0f2HPuQlSfhO635WwHDBCaITJ490rhxH9ttUlgPqOirZVKV02YB6NvPf5VkmbqJCm3bnZwrPk/eDSg==",
274 "license": "MIT",
275 "dependencies": {
276 "compact-encoding": "^3.0.0",
277 "compact-encoding-net": "^1.2.0"
278 }
279 },
280 "node_modules/codecs": {
281 "version": "3.1.0",
282 "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz",
283 "integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==",
284 "license": "MIT",
285 "dependencies": {
286 "b4a": "^1.6.3"
287 }
288 },
289 "node_modules/compact-encoding": {
290 "version": "3.3.2",
291 "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.3.2.tgz",
292 "integrity": "sha512-bgZ84oVSzjQmVHXBYWON+wQUkdVflrr8fM5+0X6UwQHZyzolsGIkdsjNK3vLLn22dhdASGFz5mS6Lf7rfrPdlg==",
293 "license": "Apache-2.0",
294 "dependencies": {
295 "b4a": "^1.3.0"
296 }
297 },
298 "node_modules/compact-encoding-bitfield": {
299 "version": "1.1.0",
300 "resolved": "https://registry.npmjs.org/compact-encoding-bitfield/-/compact-encoding-bitfield-1.1.0.tgz",
301 "integrity": "sha512-F6wliSKHi50BsBStmnsRJAzJgYSIYzdfSe3M0oHj4uQ1RcctJK/NQVD7wF51bj/DBMne2i5gUxrGUFkNZQns5w==",
302 "license": "Apache-2.0",
303 "dependencies": {
304 "compact-encoding": "^3.0.0"
305 }
306 },
307 "node_modules/compact-encoding-net": {
308 "version": "1.3.0",
309 "resolved": "https://registry.npmjs.org/compact-encoding-net/-/compact-encoding-net-1.3.0.tgz",
310 "integrity": "sha512-HIYjL3aMiSENApR691aMLngXt6rkTHb9HUkEukcx3YwIZpoMUVXHXyjBzoo9kVwC7KakooBA1RRWb46Cu6qtAQ==",
311 "license": "Apache-2.0",
312 "dependencies": {
313 "compact-encoding": "^3.0.0"
314 }
315 },
316 "node_modules/corestore": {
317 "version": "7.12.0",
318 "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.12.0.tgz",
319 "integrity": "sha512-yRWQ8VkjlAl7ITRo/xT8EK9NdiJ7xrsCCkRShD2TlSaoGa3B7bRVNPr1nWGE5NjiIi4HiLBCxtiI4gzmjAjJ0Q==",
320 "license": "MIT",
321 "dependencies": {
322 "b4a": "^1.6.7",
323 "bare-events": "^2.8.3",
324 "hypercore": "^11.32.0",
325 "hypercore-crypto": "^3.4.2",
326 "hypercore-errors": "^1.4.0",
327 "hypercore-id-encoding": "^1.3.0",
328 "ready-resource": "^1.1.1",
329 "sodium-universal": "^5.0.1",
330 "streamx": "^2.26.0",
331 "which-runtime": "^1.2.1"
332 }
333 },
334 "node_modules/debounceify": {
335 "version": "1.1.0",
336 "resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz",
337 "integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==",
338 "license": "MIT"
339 },
340 "node_modules/device-file": {
341 "version": "2.3.1",
342 "resolved": "https://registry.npmjs.org/device-file/-/device-file-2.3.1.tgz",
343 "integrity": "sha512-bmON44lwxJPle9N2OcH4tqM44pMGZKT8G6OkzXkz0urvqQ9LKkoQdTS6w0ztYfmLdUE27R4UUmx0+y2gEz4Jug==",
344 "license": "Apache-2.0",
345 "dependencies": {
346 "b4a": "^1.6.7",
347 "bare-fs": "^4.0.1",
348 "bare-path": "^3.0.0",
349 "fd-lock": "^2.1.0",
350 "fs-native-extensions": "^1.4.0",
351 "ready-resource": "^1.2.0"
352 }
353 },
354 "node_modules/dht-rpc": {
355 "version": "6.27.0",
356 "resolved": "https://registry.npmjs.org/dht-rpc/-/dht-rpc-6.27.0.tgz",
357 "integrity": "sha512-NsfgRlFDQnkA2+H+hJXMPLmBOn/TSIIc0cRMV8q42M4xc1uAXWtpvIIQsONF5tYcYC6px0bslrUGideN1h4S4A==",
358 "license": "MIT",
359 "dependencies": {
360 "adaptive-timeout": "^1.0.1",
361 "b4a": "^1.6.1",
362 "bare-events": "^2.2.0",
363 "compact-encoding": "^3.0.0",
364 "compact-encoding-net": "^1.2.0",
365 "fast-fifo": "^1.1.0",
366 "kademlia-routing-table": "^1.0.1",
367 "nat-sampler": "^1.0.1",
368 "sodium-universal": "^5.0.0",
369 "streamx": "^2.13.2",
370 "time-ordered-set": "^2.0.0",
371 "udx-native": "^1.5.3"
372 }
373 },
374 "node_modules/events-universal": {
375 "version": "1.0.1",
376 "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
377 "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
378 "license": "Apache-2.0",
379 "dependencies": {
380 "bare-events": "^2.7.0"
381 }
382 },
383 "node_modules/fast-fifo": {
384 "version": "1.3.2",
385 "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
386 "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
387 "license": "MIT"
388 },
389 "node_modules/fd-lock": {
390 "version": "2.2.0",
391 "resolved": "https://registry.npmjs.org/fd-lock/-/fd-lock-2.2.0.tgz",
392 "integrity": "sha512-Il4jWBhjjgvUg+z8d0bC7ncXqB42caqKTUpnZ9jpcqiPmw8bkIixt7Kic1czbZPMxAQD/9kEqfZ5Dq77nSll0w==",
393 "license": "Apache-2.0",
394 "dependencies": {
395 "bare-fs": "^4.5.0",
396 "fs-native-extensions": "^1.4.4",
397 "ready-resource": "^1.2.0",
398 "resource-on-exit": "^1.0.0"
399 }
400 },
401 "node_modules/flat-tree": {
402 "version": "1.13.0",
403 "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz",
404 "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==",
405 "license": "MIT"
406 },
407 "node_modules/fs-native-extensions": {
408 "version": "1.5.0",
409 "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.5.0.tgz",
410 "integrity": "sha512-nuZLFm9mGCxvyi7Llww/J4OyifKCS21nEUTAmnlTZp3FObPOvA32aCedCmt4Z+8yk+caqfClNaSCfn/P7T7FLQ==",
411 "license": "Apache-2.0",
412 "dependencies": {
413 "require-addon": "^1.1.0",
414 "which-runtime": "^1.2.0"
415 }
416 },
417 "node_modules/generate-object-property": {
418 "version": "2.0.0",
419 "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-2.0.0.tgz",
420 "integrity": "sha512-KwuURPyqn2Mz8DdV29pJwQu0Y7tcsbkULr82eeOcY/ZllFK6I9Wm8dsRByIu7CKWlFi9BdW1b3mcXMp/kQBQsw==",
421 "license": "MIT",
422 "dependencies": {
423 "is-property": "^1.0.0"
424 }
425 },
426 "node_modules/generate-string": {
427 "version": "1.0.1",
428 "resolved": "https://registry.npmjs.org/generate-string/-/generate-string-1.0.1.tgz",
429 "integrity": "sha512-IfTY0dKZM43ACyGvXkbG7De7WY7MxTS5VO6Juhe8oJKpCmrYYXoqp/cJMskkpi0k9H8wuXq0H+eI898/BCqvXg==",
430 "license": "MIT"
431 },
432 "node_modules/hyperbee": {
433 "version": "2.27.3",
434 "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz",
435 "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==",
436 "license": "MIT",
437 "dependencies": {
438 "b4a": "^1.6.0",
439 "codecs": "^3.0.0",
440 "debounceify": "^1.0.0",
441 "hypercore-errors": "^1.0.0",
442 "mutexify": "^1.4.0",
443 "protocol-buffers-encodings": "^1.2.0",
444 "rache": "^1.0.0",
445 "ready-resource": "^1.0.0",
446 "resolve-reject-promise": "^1.1.0",
447 "safety-catch": "^1.0.2",
448 "streamx": "^2.12.4",
449 "unslab": "^1.2.0"
450 }
451 },
452 "node_modules/hyperblobs": {
453 "version": "2.12.1",
454 "resolved": "https://registry.npmjs.org/hyperblobs/-/hyperblobs-2.12.1.tgz",
455 "integrity": "sha512-iX5sPkL/3eDphUkMxTG43xZGEER+cVLAFbuNIQpk/WVK8CTWnmaoCYP/+94RaFFh5U7HeGoDYidQbv6E7LeAvw==",
456 "license": "Apache-2.0",
457 "dependencies": {
458 "b4a": "^1.6.1",
459 "bare-events": "^2.5.0",
460 "compact-encoding": "^3.0.0",
461 "hypercore-crypto": "^3.6.1",
462 "hypercore-errors": "^1.1.1",
463 "mutexify": "^1.4.0",
464 "speedometer": "^1.1.0",
465 "streamx": "^2.13.2"
466 }
467 },
468 "node_modules/hypercore": {
469 "version": "11.35.2",
470 "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.35.2.tgz",
471 "integrity": "sha512-Zk7v5z2M7dbDqPXZzPXsVKRxLTmyc2ViPhg4rOi1bTO2KtaiKaAhvX925+bGiQ5nsKxO3qdo7k9Ns8cwpMDTJw==",
472 "license": "MIT",
473 "dependencies": {
474 "@hyperswarm/secret-stream": "^6.0.0",
475 "b4a": "^1.1.0",
476 "bare-events": "^2.2.0",
477 "big-sparse-array": "^1.0.3",
478 "compact-encoding": "^3.0.0",
479 "fast-fifo": "^1.3.0",
480 "flat-tree": "^1.9.0",
481 "hypercore-crypto": "^3.2.1",
482 "hypercore-errors": "^1.5.0",
483 "hypercore-id-encoding": "^1.2.0",
484 "hypercore-storage": "^3.2.0",
485 "is-options": "^1.0.1",
486 "nanoassert": "^2.0.0",
487 "protomux": "^3.5.0",
488 "quickbit-universal": "^2.2.0",
489 "random-array-iterator": "^1.0.0",
490 "safety-catch": "^1.0.1",
491 "sodium-universal": "^5.0.1",
492 "streamx": "^2.12.4",
493 "unslab": "^1.3.0",
494 "z32": "^1.0.0"
495 }
496 },
497 "node_modules/hypercore-crypto": {
498 "version": "3.7.0",
499 "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.7.0.tgz",
500 "integrity": "sha512-SWxobptQf2V/+ZLCCDRTXqFzGfTPIkNIuDyLKXpEMfcpJ1/ec94N9aWgylHcWOHmRt14ng/KR7z0BugebWHK9Q==",
501 "license": "MIT",
502 "dependencies": {
503 "b4a": "^1.6.6",
504 "compact-encoding": "^3.0.0",
505 "sodium-universal": "^5.0.0"
506 }
507 },
508 "node_modules/hypercore-errors": {
509 "version": "1.5.0",
510 "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz",
511 "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==",
512 "license": "Apache-2.0",
513 "dependencies": {
514 "hypercore-id-encoding": "^1.3.0"
515 }
516 },
517 "node_modules/hypercore-id-encoding": {
518 "version": "1.3.0",
519 "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz",
520 "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==",
521 "license": "Apache-2.0",
522 "dependencies": {
523 "b4a": "^1.5.3",
524 "z32": "^1.0.0"
525 }
526 },
527 "node_modules/hypercore-storage": {
528 "version": "3.2.1",
529 "resolved": "https://registry.npmjs.org/hypercore-storage/-/hypercore-storage-3.2.1.tgz",
530 "integrity": "sha512-kxGLq5TloxPVxUJn99lOBv/2VVeSh9KIyTAbh4nlAzk6eKCeR8KlbCfWtQsorYayGyQY6l7iY05ueofff3j92w==",
531 "license": "Apache-2.0",
532 "dependencies": {
533 "b4a": "^1.6.7",
534 "bare-path": "^3.0.0",
535 "compact-encoding": "^3.1.0",
536 "device-file": "^2.1.2",
537 "flat-tree": "^1.12.1",
538 "hypercore-crypto": "^3.4.2",
539 "hyperschema": "^1.21.0",
540 "index-encoder": "^3.3.2",
541 "resolve-reject-promise": "^1.0.0",
542 "rocksdb-native": "^3.11.0",
543 "scope-lock": "^1.2.4",
544 "streamx": "^2.21.1",
545 "xache": "^1.2.1"
546 }
547 },
548 "node_modules/hyperdht": {
549 "version": "6.33.2",
550 "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.33.2.tgz",
551 "integrity": "sha512-u7sj6qtHygAUNm9qjikZtqJt4Lvt4sNIVbjnV8tniqAgQXzLto/9UtlZe1G7Cz8Hl2JKxdwsEep0t+ELV4MrXQ==",
552 "license": "MIT",
553 "dependencies": {
554 "@hyperswarm/secret-stream": "^6.6.2",
555 "b4a": "^1.3.1",
556 "bare-events": "^2.2.0",
557 "blind-relay": "^1.3.0",
558 "bogon": "^1.0.0",
559 "compact-encoding": "^3.0.0",
560 "dht-rpc": "^6.15.1",
561 "hypercore-crypto": "^3.3.0",
562 "hypercore-id-encoding": "^1.2.0",
563 "hyperdht-address": "^1.0.1",
564 "noise-curve-ed": "^2.0.0",
565 "noise-handshake": "^4.0.0",
566 "record-cache": "^1.1.1",
567 "safety-catch": "^1.0.1",
568 "signal-promise": "^1.0.3",
569 "sodium-universal": "^5.0.1",
570 "streamx": "^2.16.1",
571 "unslab": "^1.3.0",
572 "xache": "^1.1.0"
573 },
574 "bin": {
575 "hyperdht": "bin.js"
576 }
577 },
578 "node_modules/hyperdht-address": {
579 "version": "1.1.1",
580 "resolved": "https://registry.npmjs.org/hyperdht-address/-/hyperdht-address-1.1.1.tgz",
581 "integrity": "sha512-Mu/+7SW2cwvHxMXswa5mOrhrS5BJyntViAuB25k8d8wNtA8eAPs4LaIq6TwN1SS/KQY02h1r+gM8cQeN/k360w==",
582 "license": "Apache-2.0",
583 "dependencies": {
584 "compact-encoding": "^3.0.0",
585 "hyperschema": "^1.20.1"
586 }
587 },
588 "node_modules/hyperdrive": {
589 "version": "13.3.3",
590 "resolved": "https://registry.npmjs.org/hyperdrive/-/hyperdrive-13.3.3.tgz",
591 "integrity": "sha512-nLqBbBUg1OPsjl8vn5T3wxhtLbzQz+JxUK1i1YOKTX4tdDFET8H4HxdA17knv5P4lC9v9eVC0mlPKdFlYxzDfw==",
592 "license": "Apache-2.0",
593 "dependencies": {
594 "hyperbee": "^2.11.1",
595 "hyperblobs": "^2.9.0",
596 "hypercore": "^11.0.0",
597 "hypercore-errors": "^1.0.0",
598 "is-options": "^1.0.2",
599 "mirror-drive": "^1.2.0",
600 "ready-resource": "^1.0.0",
601 "safety-catch": "^1.0.2",
602 "speedometer": "^1.1.0",
603 "streamx": "^2.12.4",
604 "sub-encoder": "^2.1.1",
605 "unix-path-resolve": "^1.0.2"
606 }
607 },
608 "node_modules/hyperschema": {
609 "version": "1.22.0",
610 "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.22.0.tgz",
611 "integrity": "sha512-h4D00e+tVyT4cDcb68p7ioaQxphBN5UOtiZVQv6RDPdtaKHLdngUWVoG/PL+RJvw/AS/OdZpiReGpLhx9pDHWg==",
612 "license": "Apache-2.0",
613 "dependencies": {
614 "bare-fs": "^4.0.1",
615 "compact-encoding": "^3.0.0",
616 "generate-object-property": "^2.0.0",
617 "generate-string": "^1.0.1"
618 }
619 },
620 "node_modules/hyperswarm": {
621 "version": "4.17.0",
622 "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.17.0.tgz",
623 "integrity": "sha512-oe86sK961Ueg7rvDN/veFwG8xH+Iv6vObPhGDkPJcDVxk/NduW41ZhAcVDnHzRbm7S0eLU7WaDUvehOYoKSpRQ==",
624 "license": "MIT",
625 "dependencies": {
626 "b4a": "^1.3.1",
627 "bare-events": "^2.2.0",
628 "hyperdht": "^6.21.0",
629 "safety-catch": "^1.0.2",
630 "shuffled-priority-queue": "^2.1.0",
631 "streamx": "^2.22.1",
632 "unslab": "^1.3.0"
633 }
634 },
635 "node_modules/index-encoder": {
636 "version": "3.5.0",
637 "resolved": "https://registry.npmjs.org/index-encoder/-/index-encoder-3.5.0.tgz",
638 "integrity": "sha512-idZ1cxtZz2dRV6rUiaP9Xo99UjXbSzjcMacoQmxUMu/A7fEQcNPngvwDJYeWelQUS5XFlY71/or70lKn6XnwbQ==",
639 "license": "Apache-2.0",
640 "dependencies": {
641 "b4a": "^1.6.4"
642 }
643 },
644 "node_modules/is-options": {
645 "version": "1.0.2",
646 "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz",
647 "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==",
648 "license": "MIT",
649 "dependencies": {
650 "b4a": "^1.1.1"
651 }
652 },
653 "node_modules/is-property": {
654 "version": "1.0.2",
655 "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
656 "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
657 "license": "MIT"
658 },
659 "node_modules/kademlia-routing-table": {
660 "version": "1.0.6",
661 "resolved": "https://registry.npmjs.org/kademlia-routing-table/-/kademlia-routing-table-1.0.6.tgz",
662 "integrity": "sha512-Ve6jwIlUCYvUzBnXnzVRHDZCFgXURW9gmF3r7n05kZs/2rNbLHXwGdcq0qIaSwdmJCvtosgR4JensnVU65hzNQ==",
663 "license": "MIT",
664 "dependencies": {
665 "bare-events": "^2.2.0"
666 }
667 },
668 "node_modules/mirror-drive": {
669 "version": "1.14.2",
670 "resolved": "https://registry.npmjs.org/mirror-drive/-/mirror-drive-1.14.2.tgz",
671 "integrity": "sha512-1ZtS/TonGXWX0eDAGK90JapGyzOaz8IxX7mARZWPgUuZ9eEIvaoSY7bxv6/4FOW26wL8g/YcpqJpt8mMZ/QZCA==",
672 "license": "Apache-2.0",
673 "dependencies": {
674 "bare-events": "^2.8.2",
675 "binary-stream-equals": "^1.0.0",
676 "rabin-stream": "^2.0.0",
677 "same-data": "^1.0.0",
678 "speedometer": "^1.1.0",
679 "streamx": "^2.22.1",
680 "unix-path-resolve": "^1.0.2"
681 }
682 },
683 "node_modules/mutexify": {
684 "version": "1.4.0",
685 "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz",
686 "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==",
687 "license": "MIT",
688 "dependencies": {
689 "queue-tick": "^1.0.0"
690 }
691 },
692 "node_modules/nanoassert": {
693 "version": "2.0.0",
694 "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz",
695 "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==",
696 "license": "ISC"
697 },
698 "node_modules/nat-sampler": {
699 "version": "1.0.1",
700 "resolved": "https://registry.npmjs.org/nat-sampler/-/nat-sampler-1.0.1.tgz",
701 "integrity": "sha512-yQvyNN7xbqR8crTKk3U8gRgpcV1Az+vfCEijiHu9oHHsnIl8n3x+yXNHl42M6L3czGynAVoOT9TqBfS87gDdcw==",
702 "license": "MIT"
703 },
704 "node_modules/noise-curve-ed": {
705 "version": "2.1.0",
706 "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz",
707 "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==",
708 "license": "ISC",
709 "dependencies": {
710 "b4a": "^1.1.0",
711 "nanoassert": "^2.0.0",
712 "sodium-universal": "^5.0.0"
713 }
714 },
715 "node_modules/noise-handshake": {
716 "version": "4.2.0",
717 "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz",
718 "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==",
719 "license": "Apache-2.0",
720 "dependencies": {
721 "b4a": "^1.1.0",
722 "nanoassert": "^2.0.0",
723 "sodium-universal": "^5.0.0"
724 }
725 },
726 "node_modules/protocol-buffers-encodings": {
727 "version": "1.2.0",
728 "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz",
729 "integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==",
730 "license": "MIT",
731 "dependencies": {
732 "b4a": "^1.6.0",
733 "signed-varint": "^2.0.1",
734 "varint": "5.0.0"
735 }
736 },
737 "node_modules/protomux": {
738 "version": "3.11.0",
739 "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.11.0.tgz",
740 "integrity": "sha512-Ze43XaNHbL6zQ/zJwVYvue7Aj8pDB1HNnISsrFhAur3291Y+Iu2fMhOpySD73J/32BoBIUYrhl07lJsBR/fUVA==",
741 "license": "MIT",
742 "dependencies": {
743 "b4a": "^1.3.1",
744 "compact-encoding": "^3.0.0",
745 "queue-tick": "^1.0.0",
746 "safety-catch": "^1.0.1",
747 "unslab": "^1.3.0"
748 }
749 },
750 "node_modules/queue-tick": {
751 "version": "1.0.1",
752 "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
753 "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
754 "license": "MIT"
755 },
756 "node_modules/quickbit-native": {
757 "version": "2.4.8",
758 "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz",
759 "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==",
760 "license": "Apache-2.0",
761 "optional": true,
762 "dependencies": {
763 "require-addon": "^1.1.0"
764 }
765 },
766 "node_modules/quickbit-universal": {
767 "version": "2.2.0",
768 "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz",
769 "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==",
770 "license": "ISC",
771 "dependencies": {
772 "b4a": "^1.6.0",
773 "simdle-universal": "^1.1.0"
774 },
775 "optionalDependencies": {
776 "quickbit-native": "^2.2.0"
777 }
778 },
779 "node_modules/rabin-native": {
780 "version": "2.0.0",
781 "resolved": "https://registry.npmjs.org/rabin-native/-/rabin-native-2.0.0.tgz",
782 "integrity": "sha512-x1BlYdIWh+nk9G0scvxyscrYiDPJ7vQZepqqPlVUSInIko1Zxooi8cm6lzBghEGI9aN3DXiunC8FXLgy6kke3Q==",
783 "license": "Apache-2.0",
784 "dependencies": {
785 "require-addon": "^1.1.0"
786 }
787 },
788 "node_modules/rabin-stream": {
789 "version": "2.0.0",
790 "resolved": "https://registry.npmjs.org/rabin-stream/-/rabin-stream-2.0.0.tgz",
791 "integrity": "sha512-EzS2Ig/qsMBSk1PahC0O0IPdRZe3bspHHLWChhqW1feKz+J46tlVL3g4wWr4lrQdXGzABIMsOhvdT6bjXYMuUw==",
792 "license": "Apache-2.0",
793 "dependencies": {
794 "rabin-native": "^2.0.0",
795 "streamx": "^2.23.0"
796 }
797 },
798 "node_modules/rache": {
799 "version": "1.0.0",
800 "resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz",
801 "integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==",
802 "license": "Apache-2.0"
803 },
804 "node_modules/random-array-iterator": {
805 "version": "1.0.0",
806 "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz",
807 "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==",
808 "license": "MIT"
809 },
810 "node_modules/ready-resource": {
811 "version": "1.2.0",
812 "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz",
813 "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==",
814 "license": "MIT",
815 "dependencies": {
816 "bare-events": "^2.2.0"
817 }
818 },
819 "node_modules/record-cache": {
820 "version": "1.2.0",
821 "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.2.0.tgz",
822 "integrity": "sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==",
823 "license": "MIT",
824 "dependencies": {
825 "b4a": "^1.3.1"
826 }
827 },
828 "node_modules/refcounter": {
829 "version": "1.0.0",
830 "resolved": "https://registry.npmjs.org/refcounter/-/refcounter-1.0.0.tgz",
831 "integrity": "sha512-1WosVzUy0kPUaPMEtlNDwm99UsteALIhXXR8rerELoa63WkYIXAl0hxgwPFrIYBRWZPGUyekQ04FRtPJ7dHk9w==",
832 "license": "Apache-2.0"
833 },
834 "node_modules/require-addon": {
835 "version": "1.2.0",
836 "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz",
837 "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==",
838 "license": "Apache-2.0",
839 "dependencies": {
840 "bare-addon-resolve": "^1.3.0"
841 },
842 "engines": {
843 "bare": ">=1.10.0"
844 }
845 },
846 "node_modules/resolve-reject-promise": {
847 "version": "1.1.0",
848 "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz",
849 "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==",
850 "license": "MIT"
851 },
852 "node_modules/resource-on-exit": {
853 "version": "1.0.0",
854 "resolved": "https://registry.npmjs.org/resource-on-exit/-/resource-on-exit-1.0.0.tgz",
855 "integrity": "sha512-ViJwJAknCkLRJRPR+9SISQQ7R5eRgtdIHLJsM2hHx1MweAJbJxJ5XnMjjq0Lc7ZGv44ufzAqds1nKxiVkdy4ag==",
856 "license": "Apache-2.0"
857 },
858 "node_modules/rocksdb-native": {
859 "version": "3.17.4",
860 "resolved": "https://registry.npmjs.org/rocksdb-native/-/rocksdb-native-3.17.4.tgz",
861 "integrity": "sha512-Y9iFKhgxT4mS8+uXxThBvvijCUl+ykFtNH++zbVZaE3ih30KyZ7Hz57BSwO6Q5VK08VkF3LiPzao3Xgr4Pi4NA==",
862 "license": "Apache-2.0",
863 "dependencies": {
864 "compact-encoding": "^3.0.0",
865 "ready-resource": "^1.0.0",
866 "refcounter": "^1.0.0",
867 "require-addon": "^1.0.2",
868 "resolve-reject-promise": "^1.1.0",
869 "signal-promise": "^1.0.3",
870 "streamx": "^2.16.1"
871 },
872 "engines": {
873 "bare": ">=1.16.0"
874 }
875 },
876 "node_modules/safety-catch": {
877 "version": "1.0.3",
878 "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.3.tgz",
879 "integrity": "sha512-Zq+J1TefpoEq/HTUabo0YXX5MNvttjWYODGohgPBO2jfko8Wqx3JYMgE823szDFVamdH5PlpByvfiWScTdSYDA==",
880 "license": "MIT"
881 },
882 "node_modules/same-data": {
883 "version": "1.0.0",
884 "resolved": "https://registry.npmjs.org/same-data/-/same-data-1.0.0.tgz",
885 "integrity": "sha512-Eqn7N2yV+aKMlUHTRqUwYG1Iv0cJqjlvLKj/GoP5PozJn361QaOYX14+v87r7NqQUZC22noP/LfLrSQiPwAygw==",
886 "license": "MIT"
887 },
888 "node_modules/scope-lock": {
889 "version": "1.2.4",
890 "resolved": "https://registry.npmjs.org/scope-lock/-/scope-lock-1.2.4.tgz",
891 "integrity": "sha512-BpSd8VCuCxW9ZitcdIC/vjs3gMaP9bRBL5nkHcyfX2VrS52n13/rHuBA2xJ/S/4DPuRdAO/Bk8pWd8eD/gHCIA==",
892 "license": "Apache-2.0"
893 },
894 "node_modules/shuffled-priority-queue": {
895 "version": "2.1.0",
896 "resolved": "https://registry.npmjs.org/shuffled-priority-queue/-/shuffled-priority-queue-2.1.0.tgz",
897 "integrity": "sha512-xhdh7fHyMsr0m/w2kDfRJuBFRS96b9l8ZPNWGaQ+PMvnUnZ/Eh+gJJ9NsHBd7P9k0399WYlCLzsy18EaMfyadA==",
898 "license": "MIT",
899 "dependencies": {
900 "unordered-set": "^2.0.1"
901 }
902 },
903 "node_modules/signal-promise": {
904 "version": "1.0.3",
905 "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz",
906 "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==",
907 "license": "MIT"
908 },
909 "node_modules/signed-varint": {
910 "version": "2.0.1",
911 "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz",
912 "integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==",
913 "license": "MIT",
914 "dependencies": {
915 "varint": "~5.0.0"
916 }
917 },
918 "node_modules/simdle-native": {
919 "version": "1.3.9",
920 "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz",
921 "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==",
922 "license": "Apache-2.0",
923 "optional": true,
924 "dependencies": {
925 "b4a": "^1.6.0",
926 "require-addon": "^1.1.0"
927 }
928 },
929 "node_modules/simdle-universal": {
930 "version": "1.1.2",
931 "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz",
932 "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==",
933 "license": "ISC",
934 "dependencies": {
935 "b4a": "^1.6.0"
936 },
937 "optionalDependencies": {
938 "simdle-native": "^1.1.1"
939 }
940 },
941 "node_modules/sodium-native": {
942 "version": "5.1.0",
943 "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz",
944 "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==",
945 "license": "MIT",
946 "dependencies": {
947 "bare-assert": "^1.2.0",
948 "require-addon": "^1.1.0",
949 "which-runtime": "^1.2.1"
950 },
951 "engines": {
952 "bare": ">=1.16.0"
953 }
954 },
955 "node_modules/sodium-secretstream": {
956 "version": "1.2.0",
957 "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz",
958 "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==",
959 "license": "MIT",
960 "dependencies": {
961 "b4a": "^1.1.1",
962 "sodium-universal": "^5.0.0"
963 }
964 },
965 "node_modules/sodium-universal": {
966 "version": "5.0.1",
967 "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz",
968 "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==",
969 "license": "MIT",
970 "dependencies": {
971 "sodium-native": "^5.0.1"
972 },
973 "peerDependencies": {
974 "sodium-javascript": "~0.8.0"
975 },
976 "peerDependenciesMeta": {
977 "sodium-javascript": {
978 "optional": true
979 }
980 }
981 },
982 "node_modules/speedometer": {
983 "version": "1.1.0",
984 "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.1.0.tgz",
985 "integrity": "sha512-z/wAiTESw2XVPssY2XRcme4niTc4S5FkkJ4gknudtVoc33Zil8TdTxHy5torRcgqMqksJV2Yz8HQcvtbsnw0mQ==",
986 "license": "MIT"
987 },
988 "node_modules/streamx": {
989 "version": "2.28.0",
990 "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz",
991 "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==",
992 "license": "MIT",
993 "dependencies": {
994 "events-universal": "^1.0.0",
995 "fast-fifo": "^1.3.2",
996 "text-decoder": "^1.1.0"
997 }
998 },
999 "node_modules/sub-encoder": {
1000 "version": "2.1.3",
1001 "resolved": "https://registry.npmjs.org/sub-encoder/-/sub-encoder-2.1.3.tgz",
1002 "integrity": "sha512-Xxx04ygZo/1J3yHvaSA6VhDmiSaBQkw/PmO3YnnYFXle+tfOGToC6FcDpIfMztWZXJzuKG14b/57HMkiL58C6A==",
1003 "license": "Apache-2.0",
1004 "dependencies": {
1005 "b4a": "^1.6.0",
1006 "codecs": "^3.1.0"
1007 }
1008 },
1009 "node_modules/teex": {
1010 "version": "1.0.1",
1011 "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
1012 "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
1013 "license": "MIT",
1014 "dependencies": {
1015 "streamx": "^2.12.5"
1016 }
1017 },
1018 "node_modules/text-decoder": {
1019 "version": "1.2.7",
1020 "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
1021 "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
1022 "license": "Apache-2.0",
1023 "dependencies": {
1024 "b4a": "^1.6.4"
1025 }
1026 },
1027 "node_modules/time-ordered-set": {
1028 "version": "2.0.1",
1029 "resolved": "https://registry.npmjs.org/time-ordered-set/-/time-ordered-set-2.0.1.tgz",
1030 "integrity": "sha512-VJEKmgSN2UiOLB8BpN8Sh2b9LGMHTP5OPrQRpnKjvOheOyzk0mufbjzjKTIG2gO4A+Y+vDJ+0TcLbpUmMLsg8A==",
1031 "license": "MIT"
1032 },
1033 "node_modules/timeout-refresh": {
1034 "version": "2.0.1",
1035 "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz",
1036 "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==",
1037 "license": "MIT"
1038 },
1039 "node_modules/udx-native": {
1040 "version": "1.21.1",
1041 "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.21.1.tgz",
1042 "integrity": "sha512-Z35jgnF9+4wHRaR9ulypUKOSEwNqksPxW/bJDOzNN4ERyVxzhPOvokjY3Zoi7B+rnD/LLXDYGiPMnPUxXL961g==",
1043 "license": "Apache-2.0",
1044 "dependencies": {
1045 "b4a": "^1.5.0",
1046 "bare-events": "^2.2.0",
1047 "require-addon": "^1.1.0",
1048 "streamx": "^2.22.0"
1049 },
1050 "engines": {
1051 "bare": ">=1.17.4"
1052 }
1053 },
1054 "node_modules/unix-path-resolve": {
1055 "version": "1.0.2",
1056 "resolved": "https://registry.npmjs.org/unix-path-resolve/-/unix-path-resolve-1.0.2.tgz",
1057 "integrity": "sha512-kG4g5nobBBaMnH2XbrS4sLUXEpx4nY2J3C6KAlAUcnahG2HChxSPVKWYrqEq76iTo+cyMkLUjqxGaQR2tz097Q==",
1058 "license": "MIT"
1059 },
1060 "node_modules/unordered-set": {
1061 "version": "2.0.1",
1062 "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz",
1063 "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==",
1064 "license": "MIT"
1065 },
1066 "node_modules/unslab": {
1067 "version": "1.3.0",
1068 "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz",
1069 "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==",
1070 "license": "Apache-2.0",
1071 "dependencies": {
1072 "b4a": "^1.6.6"
1073 }
1074 },
1075 "node_modules/varint": {
1076 "version": "5.0.0",
1077 "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz",
1078 "integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==",
1079 "license": "MIT"
1080 },
1081 "node_modules/which-runtime": {
1082 "version": "1.4.0",
1083 "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.4.0.tgz",
1084 "integrity": "sha512-0ugbP4CJW4e2D20jvEcC4973dCgIaHI4Rw1PT+26U9zEve7FyYdWAIwUnoeOYvoCfn+wXHoHTKb1KhkYlb60Pw==",
1085 "license": "Apache-2.0"
1086 },
1087 "node_modules/xache": {
1088 "version": "1.3.0",
1089 "resolved": "https://registry.npmjs.org/xache/-/xache-1.3.0.tgz",
1090 "integrity": "sha512-ekAknjLTiyXJcuezqwa/cOMfFenVVY2toL4J7D3AfTn7UEDYb8JSgRnv3CK8khF7u38XZ4NjS1MXEw4RwqJ4oA==",
1091 "license": "MIT"
1092 },
1093 "node_modules/z32": {
1094 "version": "1.1.0",
1095 "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz",
1096 "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==",
1097 "license": "MIT",
1098 "dependencies": {
1099 "b4a": "^1.5.3"
1100 }
1101 }
1102 }
1103}
package.json +24 -0
……@@ -0,0 +1,24 @@
1{
2 "name": "hyper-vdrive",
3 "version": "0.0.1",
4 "description": "A real Hyperdrive that stores no blob bytes — blocks are materialised on demand from a content-addressed store on disk",
5 "main": "lib/storage.cjs",
6 "exports": {
7 ".": "./lib/storage.cjs",
8 "./passthrough": "./lib/passthrough.cjs"
9 },
10 "scripts": {
11 "test:passthrough": "node test/pass-through.mjs ./work/passthrough",
12 "test:gc": "node test/gc-bitfield.mjs ./work/gc"
13 },
14 "dependencies": {
15 "corestore": "^7.12.0",
16 "hypercore": "^11.35.2",
17 "hypercore-crypto": "^3.7.0",
18 "hypercore-id-encoding": "^1.3.0",
19 "hypercore-storage": "^3.2.1",
20 "hyperdrive": "^13.3.3",
21 "hyperswarm": "^4.17.0"
22 },
23 "license": "AGPL-3.0-or-later"
24}
test/gc-bitfield.mjs +152 -0
……@@ -0,0 +1,152 @@
1// Step 2 — GC / bitfield pairing.
2// Q1: after the overlay drops bytes, does the core still advertise the block?
3// Q2: what does a PEER experience when it requests an advertised-but-missing block?
4// Q3: does core.clear() on that range make the lie go away?
5import { createRequire } from 'module'
6const require = createRequire(import.meta.url)
7
8const fs = require('fs')
9const crypto = require('hypercore-crypto')
10const Corestore = require('corestore')
11const Hyperdrive = require('hyperdrive')
12const { ContentStore, build, stats } = require('../lib/storage.cjs')
13const { chunk } = await import('../../hyper-overlay/lib/chunker.js')
14
15const ROOT = process.argv[2]
16
17// A block we advertise but cannot produce surfaces as an UNCAUGHT REQUEST_TIMEOUT
18// from the replicator's timer, not as a rejection of get(). Capture it so the
19// probe can finish and report rather than dying.
20const uncaught = []
21process.on('uncaughtException', (err) => {
22 uncaught.push(err.code || err.message)
23})
24
25const srcDir = ROOT + '/src'
26fs.mkdirSync(srcDir, { recursive: true })
27
28const FILES = [
29 { name: '/keep.bin', bytes: crypto.randomBytes(3 * 1024 * 1024) },
30 { name: '/victim.bin', bytes: crypto.randomBytes(3 * 1024 * 1024) }
31]
32for (const f of FILES) {
33 f.file = srcDir + f.name
34 fs.writeFileSync(f.file, f.bytes)
35 f.chunks = chunk(f.bytes)
36}
37
38const content = new ContentStore()
39for (const f of FILES) for (const c of f.chunks) content.add(c.hash, f.file, c.offset, c.length)
40
41const Storage = build(content)
42const hostDir = ROOT + '/host'
43
44function write (ws, buf) {
45 return new Promise((resolve) => {
46 if (ws.write(buf)) return resolve()
47 ws.once('drain', resolve)
48 })
49}
50
51async function openHost () {
52 const store = new Corestore(new Storage(hostDir), { primaryKey: Buffer.alloc(32, 7), unsafe: true })
53 const drive = new Hyperdrive(store)
54 await drive.ready()
55 await drive.getBlobs()
56 return { store, drive }
57}
58
59// publish
60{
61 const { store, drive } = await openHost()
62 for (const f of FILES) {
63 const ws = drive.createWriteStream(f.name, { dedup: true })
64 for (const c of f.chunks) await write(ws, c.data)
65 await new Promise((resolve, reject) => {
66 ws.once('error', reject)
67 ws.once('close', resolve)
68 ws.end()
69 })
70 }
71 await drive.close()
72 await store.close()
73}
74
75const host = await openHost()
76const driveKey = host.drive.key
77
78// which blocks belong to /victim.bin?
79const victimEntry = await host.drive.entry('/victim.bin')
80const victimBlob = victimEntry.value.blob
81const victimMap = await host.drive.blobs.getBlockMap(victimBlob)
82const victimBlocks = victimMap.blocks.map((b) => b.index)
83
84// peer setup
85const client = new Corestore(ROOT + '/peer', { primaryKey: Buffer.alloc(32, 9), unsafe: true })
86const clientDrive = new Hyperdrive(client, driveKey)
87await clientDrive.ready()
88const s1 = host.store.replicate(true)
89const s2 = client.replicate(false)
90s1.pipe(s2).pipe(s1)
91await clientDrive.core.update({ wait: true })
92
93const result = {}
94
95// baseline: peer can fetch a file normally
96const keep = await clientDrive.get('/keep.bin')
97result.peerFetchedKeepOk = keep !== null && keep.equals(FILES[0].bytes)
98
99// Q1 — the overlay GCs victim.bin's bytes
100for (const c of FILES[1].chunks) content.locations.delete(c.hash)
101result.hostStillAdvertises_afterBytesGone = await host.drive.blobs.core.has(victimBlocks[0])
102
103// Q2 — peer requests the advertised-but-missing block
104const t0 = Date.now()
105try {
106 const got = await clientDrive.get('/victim.bin', { timeout: 5000 })
107 result.peerResult = got === null ? 'null' : 'bytes:' + got.length
108} catch (err) {
109 result.peerResult = 'throw:' + (err.code || err.message)
110}
111result.peerWaitedMs = Date.now() - t0
112
113// Q3 — pair the GC with a clear of those blocks
114await host.drive.blobs.core.clear(victimBlocks[0], victimBlocks[victimBlocks.length - 1] + 1)
115result.hostAdvertises_afterClear = await host.drive.blobs.core.has(victimBlocks[0])
116
117// peer asks again, from a clean session
118const client2 = new Corestore(ROOT + '/peer2', { primaryKey: Buffer.alloc(32, 11), unsafe: true })
119const clientDrive2 = new Hyperdrive(client2, driveKey)
120await clientDrive2.ready()
121const s3 = host.store.replicate(true)
122const s4 = client2.replicate(false)
123s3.pipe(s4).pipe(s3)
124await clientDrive2.core.update({ wait: true })
125
126const t1 = Date.now()
127try {
128 const got = await clientDrive2.get('/victim.bin', { timeout: 5000 })
129 result.peerResultAfterClear = got === null ? 'null' : 'bytes:' + got.length
130} catch (err) {
131 result.peerResultAfterClear = 'throw:' + (err.code || err.message)
132}
133result.peerWaitedMsAfterClear = Date.now() - t1
134
135// and the file that was never GC'd still works
136const keep2 = await clientDrive2.get('/keep.bin', { timeout: 5000 })
137result.keepStillFetchableAfterClear = keep2 !== null && keep2.equals(FILES[0].bytes)
138
139result.victimBlockCount = victimBlocks.length
140result.stats = { ...stats }
141result.uncaught = uncaught
142
143for (const s of [s1, s2, s3, s4]) s.destroy()
144await clientDrive2.close()
145await client2.close()
146await clientDrive.close()
147await client.close()
148await host.drive.close()
149await host.store.close()
150content.close()
151
152console.log(JSON.stringify(result, null, 2))
test/pass-through.mjs +160 -0
……@@ -0,0 +1,160 @@
1// T2 — does a pass-through storage wrapper survive real hyperdrive use?
2// Pass = wrapped and unwrapped runs produce identical drive keys and identical
3// bytes, content survives a reopen from disk, and a plain peer can replicate
4// the wrapped drive.
5import { createRequire } from 'module'
6const require = createRequire(import.meta.url)
7
8const fs = require('fs')
9const crypto = require('hypercore-crypto')
10const Corestore = require('corestore')
11const Hyperdrive = require('hyperdrive')
12const { WrappedCorestoreStorage, stats } = require('../lib/passthrough.cjs')
13const { chunk } = await import('../../hyper-overlay/lib/chunker.js')
14
15const ROOT = process.argv[2]
16const PRIMARY = Buffer.alloc(32, 7)
17
18const FILES = [
19 ['/small.txt', Buffer.from('hello pear\n')],
20 ['/empty.bin', Buffer.alloc(0)],
21 ['/mid.bin', crypto.randomBytes(300 * 1024)],
22 ['/big.bin', crypto.randomBytes(6 * 1024 * 1024)]
23]
24
25function write (ws, buf) {
26 return new Promise((resolve) => {
27 if (ws.write(buf)) return resolve()
28 ws.once('drain', resolve)
29 })
30}
31
32function openStore (dir, wrapped) {
33 const storage = wrapped ? new WrappedCorestoreStorage(dir) : dir
34 return new Corestore(storage, { primaryKey: PRIMARY, unsafe: true })
35}
36
37async function put (drive, name, buf) {
38 // NB: 0-byte files must NOT take the dedup path — hyperblobs 2.12.1 writes an
39 // empty block map, then seekBlockMap() reads map.blocks === null and throws.
40 // Reproduced on a plain store, so it is upstream, not the wrapper.
41 const ws = drive.createWriteStream(name, buf.length === 0 ? {} : { dedup: true })
42 for (const c of chunk(buf)) await write(ws, c.data)
43 await new Promise((resolve, reject) => {
44 ws.once('error', reject)
45 ws.once('close', resolve)
46 ws.end()
47 })
48}
49
50async function build (dir, wrapped) {
51 const store = openStore(dir, wrapped)
52 const drive = new Hyperdrive(store)
53 await drive.ready()
54 for (const [name, buf] of FILES) await put(drive, name, buf)
55 await drive.getBlobs()
56
57 const out = {
58 driveKey: drive.key.toString('hex'),
59 blobsKey: drive.blobs.core.key.toString('hex'),
60 coreLength: drive.core.length,
61 blobsLength: drive.blobs.core.length,
62 rootHash: (await drive.core.treeHash()).toString('hex'),
63 digests: {}
64 }
65 for (const [name, buf] of FILES) {
66 const got = await drive.get(name)
67 const bytes = got === null ? Buffer.alloc(0) : got
68 out.digests[name] = crypto.hash(bytes).toString('hex')
69 if (!bytes.equals(buf)) throw new Error('READBACK MISMATCH ' + name + ' (' + (wrapped ? 'wrapped' : 'plain') + ')')
70 }
71 await drive.close()
72 await store.close()
73 return out
74}
75
76async function reopen (dir, wrapped) {
77 const store = openStore(dir, wrapped)
78 const drive = new Hyperdrive(store)
79 await drive.ready()
80 const digests = {}
81 for (const [name, buf] of FILES) {
82 const got = await drive.get(name)
83 const bytes = got === null ? Buffer.alloc(0) : got
84 digests[name] = crypto.hash(bytes).toString('hex')
85 if (!bytes.equals(buf)) throw new Error('REOPEN MISMATCH ' + name)
86 }
87 const key = drive.key.toString('hex')
88 await drive.close()
89 await store.close()
90 return { key, digests }
91}
92
93// replicate wrapped host -> plain client, in memory
94async function replicate (hostDir, clientDir, driveKey) {
95 const host = openStore(hostDir, true)
96 const hostDrive = new Hyperdrive(host)
97 await hostDrive.ready()
98 await hostDrive.getBlobs()
99
100 const client = openStore(clientDir, false)
101 const clientDrive = new Hyperdrive(client, Buffer.from(driveKey, 'hex'))
102 await clientDrive.ready()
103
104 const s1 = host.replicate(true)
105 const s2 = client.replicate(false)
106 s1.pipe(s2).pipe(s1)
107
108 await clientDrive.core.update({ wait: true })
109 const digests = {}
110 for (const [name, buf] of FILES) {
111 const got = await clientDrive.get(name)
112 const bytes = got === null ? Buffer.alloc(0) : got
113 digests[name] = crypto.hash(bytes).toString('hex')
114 if (!bytes.equals(buf)) throw new Error('REPLICATION MISMATCH ' + name)
115 }
116
117 s1.destroy()
118 s2.destroy()
119 await clientDrive.close()
120 await client.close()
121 await hostDrive.close()
122 await host.close()
123 return digests
124}
125
126const plain = await build(ROOT + '/plain', false)
127const before = { ...stats }
128const wrapped = await build(ROOT + '/wrapped', true)
129const after = { ...stats }
130
131const same =
132 plain.driveKey === wrapped.driveKey &&
133 plain.blobsKey === wrapped.blobsKey &&
134 plain.rootHash === wrapped.rootHash &&
135 JSON.stringify(plain.digests) === JSON.stringify(wrapped.digests)
136
137const re = await reopen(ROOT + '/wrapped', true)
138const rep = await replicate(ROOT + '/wrapped', ROOT + '/client', wrapped.driveKey)
139
140console.log(
141 JSON.stringify(
142 {
143 identical: same,
144 plainKey: plain.driveKey,
145 wrappedKey: wrapped.driveKey,
146 rootHashMatch: plain.rootHash === wrapped.rootHash,
147 reopenOk: re.key === wrapped.driveKey && JSON.stringify(re.digests) === JSON.stringify(wrapped.digests),
148 replicationOk: JSON.stringify(rep) === JSON.stringify(wrapped.digests),
149 wrapperTraffic: {
150 coreStoragesWrapped: after.wrapped - before.wrapped,
151 readBatches: after.read - before.read,
152 writeBatches: after.write - before.write,
153 getBlock: after.getBlock - before.getBlock,
154 putBlock: after.putBlock - before.putBlock
155 }
156 },
157 null,
158 2
159 )
160)
test/redirect.mjs +142 -0
……@@ -0,0 +1,142 @@
1// T3 — correctness of the block-redirecting drive.
2// Pass = same drive key as a stock drive, same bytes on read, survives reopen,
3// replicates to a stock peer, and stores no blob bytes.
4import { createRequire } from 'module'
5const require = createRequire(import.meta.url)
6
7const fs = require('fs')
8const { execFileSync } = require('child_process')
9const crypto = require('hypercore-crypto')
10const Corestore = require('corestore')
11const Hyperdrive = require('hyperdrive')
12const { ContentStore, build, stats } = require('../lib/storage.cjs')
13const { chunk } = await import('../../hyper-overlay/lib/chunker.js')
14
15const ROOT = process.argv[2]
16const SRC = process.argv.slice(3) // real files on disk = our "overlay"
17const PRIMARY = Buffer.alloc(32, 7)
18const du = (p) => Number(execFileSync('du', ['-sb', p]).toString().split('\t')[0])
19
20function write (ws, buf) {
21 return new Promise((resolve) => {
22 if (ws.write(buf)) return resolve()
23 ws.once('drain', resolve)
24 })
25}
26
27// Index the source files exactly as the overlay does: FastCDC chunks, hash -> location.
28const content = new ContentStore()
29const sources = []
30for (const [i, file] of SRC.entries()) {
31 const buf = fs.readFileSync(file)
32 const chunks = chunk(buf)
33 for (const c of chunks) content.add(c.hash, file, c.offset, c.length)
34 // distinct path per source — same basename would overwrite the earlier entry
35 sources.push({ file, name: '/v' + i + '-' + file.split('/').slice(-1)[0], buf, chunks })
36}
37
38async function makeDrive (dir, redirect) {
39 const pointers = null
40 const Storage = redirect ? build(content) : null
41 const store = new Corestore(redirect ? new Storage(dir) : dir, {
42 primaryKey: PRIMARY,
43 unsafe: true
44 })
45 const drive = new Hyperdrive(store)
46 await drive.ready()
47 return { store, drive, pointers }
48}
49
50async function publish (dir, redirect) {
51 const { store, drive, pointers } = await makeDrive(dir, redirect)
52 for (const s of sources) {
53 const ws = drive.createWriteStream(s.name, { dedup: true })
54 for (const c of s.chunks) await write(ws, c.data)
55 await new Promise((resolve, reject) => {
56 ws.once('error', reject)
57 ws.once('close', resolve)
58 ws.end()
59 })
60 }
61 await drive.getBlobs()
62 const out = {
63 driveKey: drive.key.toString('hex'),
64 blobsKey: drive.blobs.core.key.toString('hex'),
65 rootHash: (await drive.core.treeHash()).toString('hex'),
66 blobsLength: drive.blobs.core.length
67 }
68 await drive.close()
69 await store.close()
70 return out
71}
72
73async function verifyReads (dir, redirect) {
74 const { store, drive } = await makeDrive(dir, redirect)
75 for (const s of sources) {
76 const got = await drive.get(s.name)
77 if (got === null || !got.equals(s.buf)) throw new Error('MISMATCH ' + s.name)
78 }
79 await drive.close()
80 await store.close()
81 return true
82}
83
84async function replicateToStockPeer (hostDir, clientDir, driveKey) {
85 const { store: host, drive: hostDrive } = await makeDrive(hostDir, true)
86 await hostDrive.getBlobs()
87
88 const client = new Corestore(clientDir, { primaryKey: Buffer.alloc(32, 9), unsafe: true })
89 const clientDrive = new Hyperdrive(client, Buffer.from(driveKey, 'hex'))
90 await clientDrive.ready()
91
92 const s1 = host.replicate(true)
93 const s2 = client.replicate(false)
94 s1.pipe(s2).pipe(s1)
95
96 await clientDrive.core.update({ wait: true })
97 for (const s of sources) {
98 const got = await clientDrive.get(s.name)
99 if (got === null || !got.equals(s.buf)) throw new Error('REPLICATION MISMATCH ' + s.name)
100 }
101
102 s1.destroy()
103 s2.destroy()
104 await clientDrive.close()
105 await client.close()
106 await hostDrive.close()
107 await host.close()
108 return true
109}
110
111const stock = await publish(ROOT + '/stock', false)
112const stockBytes = du(ROOT + '/stock')
113
114const virt = await publish(ROOT + '/virtual', true)
115const virtBytes = du(ROOT + '/virtual')
116const pointerBytes = 0
117
118const reopenOk = await verifyReads(ROOT + '/virtual', true)
119const replicationOk = await replicateToStockPeer(ROOT + '/virtual', ROOT + '/peer', virt.driveKey)
120const peerBytes = du(ROOT + '/peer')
121content.close()
122
123console.log(
124 JSON.stringify(
125 {
126 sameDriveKey: stock.driveKey === virt.driveKey,
127 sameBlobsKey: stock.blobsKey === virt.blobsKey,
128 sameRootHash: stock.rootHash === virt.rootHash,
129 blobsLength: { stock: stock.blobsLength, virtual: virt.blobsLength },
130 reopenOk,
131 replicationOk,
132 inputBytes: sources.reduce((a, s) => a + s.buf.length, 0),
133 stockStoreBytes: stockBytes,
134 virtualStoreBytes: virtBytes,
135 pointerFileBytes: pointerBytes,
136 peerStoreBytes: peerBytes,
137 stats
138 },
139 null,
140 2
141 )
142)
test/releases.mjs +67 -0
……@@ -0,0 +1,67 @@
1// N successive releases into ONE virtual drive, at the same path.
2// Measures store size and publish time per release.
3// node test/releases.mjs <workDir> <file...>
4import { createRequire } from 'module'
5const require = createRequire(import.meta.url)
6
7const fs = require('fs')
8const { execFileSync } = require('child_process')
9const Corestore = require('corestore')
10const Hyperdrive = require('hyperdrive')
11const { ContentStore, build, stats } = require('../lib/storage.cjs')
12const { chunk } = await import('../../hyper-overlay/lib/chunker.js')
13
14const ROOT = process.argv[2]
15const SRC = process.argv.slice(3)
16const NAME = '/app.bin'
17const PRIMARY = Buffer.alloc(32, 7)
18const du = (p) => Number(execFileSync('du', ['-sb', p]).toString().split('\t')[0])
19
20const content = new ContentStore()
21const Storage = build(content)
22const dir = ROOT + '/virtual'
23
24function write (ws, buf) {
25 return new Promise((resolve) => {
26 if (ws.write(buf)) return resolve()
27 ws.once('drain', resolve)
28 })
29}
30
31for (const [i, file] of SRC.entries()) {
32 const t0 = Date.now()
33 const buf = fs.readFileSync(file)
34 const chunks = chunk(buf)
35 for (const c of chunks) content.add(c.hash, file, c.offset, c.length)
36
37 const store = new Corestore(new Storage(dir), { primaryKey: PRIMARY, unsafe: true })
38 const drive = new Hyperdrive(store)
39 await drive.ready()
40
41 const ws = drive.createWriteStream(NAME, { dedup: true })
42 for (const c of chunks) await write(ws, c.data)
43 await new Promise((resolve, reject) => {
44 ws.once('error', reject)
45 ws.once('close', resolve)
46 ws.end()
47 })
48
49 await drive.getBlobs()
50 const blobsLength = drive.blobs.core.length
51 await drive.close()
52 await store.close()
53
54 console.log(
55 JSON.stringify({
56 release: i + 1,
57 input: buf.length,
58 blobsLength,
59 redirectedBlocks: stats.putRedirected,
60 literalBlocks: stats.putStored,
61 storeBytes: du(dir),
62 elapsedMs: Date.now() - t0
63 })
64 )
65}
66
67content.close()