@qvac/registry-client 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/bin/cli.js +41 -1
- package/lib/profiler.js +297 -0
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -226,6 +226,47 @@ Downloading ... -> /absolute/path/ggml-tiny-q8_0.bin
|
|
|
226
226
|
Download complete: 41.52 MB
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
+
### Profile download performance
|
|
230
|
+
|
|
231
|
+
Diagnose slow downloads by collecting UDX network stats, connection info, and hypercore metrics — similar to [hyperdrive-profiler](https://github.com/holepunchto/hyperdrive-profiler) but for registry Hyperblobs:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
$ qvac-registry profile \
|
|
235
|
+
"ggerganov/whisper.cpp/resolve/5359861c739e955e79d9a303bcbc70fb988958b1/ggml-tiny.bin" hf
|
|
236
|
+
|
|
237
|
+
--- 5.0s elapsed ---
|
|
238
|
+
Network (UDX)
|
|
239
|
+
Bytes received: 42.1MB (8.4MB/s)
|
|
240
|
+
Bytes transmitted: 128kB (25.6kB/s)
|
|
241
|
+
Packets rx/tx: 29034 / 1842
|
|
242
|
+
Packets dropped: 0
|
|
243
|
+
Connection
|
|
244
|
+
Firewalled: false
|
|
245
|
+
Blob peers: 2
|
|
246
|
+
Issues: rto=0 fast-recoveries=0 retransmits=0
|
|
247
|
+
Hypercore
|
|
248
|
+
Blob core: 665 / 665 (contiguous / length)
|
|
249
|
+
Hotswaps: 0
|
|
250
|
+
...
|
|
251
|
+
==================================================
|
|
252
|
+
FINAL SUMMARY
|
|
253
|
+
==================================================
|
|
254
|
+
Download
|
|
255
|
+
Model: ggerganov/whisper.cpp/resolve/.../ggml-tiny.bin
|
|
256
|
+
Size: 73.5MB (1120 blocks)
|
|
257
|
+
Metadata: 2.15s
|
|
258
|
+
Transfer: 8.72s
|
|
259
|
+
Avg speed: 8.4MB/s
|
|
260
|
+
Total: 10.87s
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Flags:
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
--interval|-i [seconds] Stats print interval (default: 5)
|
|
267
|
+
--timeout|-t [ms] Stream read timeout (default: 120000)
|
|
268
|
+
```
|
|
269
|
+
|
|
229
270
|
### JSON output
|
|
230
271
|
|
|
231
272
|
All commands support `--json` for machine-readable output:
|
|
@@ -250,6 +291,7 @@ See the `examples/` folder for complete working examples:
|
|
|
250
291
|
- `download-model.js`: Download a single model to disk via metadata lookup
|
|
251
292
|
- `download-blob.js`: Download a blob directly using known blob coordinates
|
|
252
293
|
- `download-all-models.js`: Download all models in the registry
|
|
294
|
+
- `profile-download.js`: Profile download performance with network/connection/hypercore stats
|
|
253
295
|
|
|
254
296
|
Run examples:
|
|
255
297
|
|
|
@@ -259,6 +301,7 @@ node examples/example.js
|
|
|
259
301
|
node examples/download-model.js
|
|
260
302
|
node examples/download-blob.js
|
|
261
303
|
node examples/download-all-models.js
|
|
304
|
+
node examples/profile-download.js "model/path"
|
|
262
305
|
```
|
|
263
306
|
|
|
264
307
|
## Configuration
|
package/bin/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const { command, flag, arg, summary, header, footer, description } = require('paparam')
|
|
5
5
|
const { QVACRegistryClient } = require('../index')
|
|
6
|
+
const { profileDownload } = require('../lib/profiler')
|
|
6
7
|
const IdEnc = require('hypercore-id-encoding')
|
|
7
8
|
const path = require('#path')
|
|
8
9
|
|
|
@@ -191,6 +192,44 @@ const downloadCmd = command('download',
|
|
|
191
192
|
}
|
|
192
193
|
)
|
|
193
194
|
|
|
195
|
+
const profileCmd = command('profile',
|
|
196
|
+
summary('Profile download performance for a model'),
|
|
197
|
+
description('Downloads a model to a temp directory while collecting UDX network stats, connection info, and hypercore metrics. Similar to hyperdrive-profiler but for registry blobs.'),
|
|
198
|
+
arg('<path>', 'Model path'),
|
|
199
|
+
arg('[source]', 'Model source filter (e.g. hf, s3)'),
|
|
200
|
+
flag('--interval|-i [seconds]', 'Stats print interval in seconds (default: 5)'),
|
|
201
|
+
flag('--timeout|-t [ms]', 'Stream read timeout in ms (default: 120000)'),
|
|
202
|
+
async function (cmd) {
|
|
203
|
+
const rootFlags = getRootFlags(cmd)
|
|
204
|
+
const registryCoreKey = rootFlags.key || process.env.QVAC_REGISTRY_CORE_KEY
|
|
205
|
+
|
|
206
|
+
if (!registryCoreKey) {
|
|
207
|
+
console.error('Registry core key is required. Set QVAC_REGISTRY_CORE_KEY or use --key.')
|
|
208
|
+
process.exit(1)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const interval = cmd.flags.interval ? parseInt(cmd.flags.interval, 10) : 5
|
|
212
|
+
const timeout = cmd.flags.timeout ? parseInt(cmd.flags.timeout, 10) : 120000
|
|
213
|
+
|
|
214
|
+
if (Number.isNaN(interval) || interval <= 0) {
|
|
215
|
+
console.error('--interval must be a positive number')
|
|
216
|
+
process.exit(1)
|
|
217
|
+
}
|
|
218
|
+
if (Number.isNaN(timeout) || timeout <= 0) {
|
|
219
|
+
console.error('--timeout must be a positive number')
|
|
220
|
+
process.exit(1)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await profileDownload({
|
|
224
|
+
registryCoreKey,
|
|
225
|
+
modelPath: cmd.args.path,
|
|
226
|
+
source: cmd.args.source || undefined,
|
|
227
|
+
intervalSec: interval,
|
|
228
|
+
timeout
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
|
|
194
233
|
// --- Root command ---
|
|
195
234
|
|
|
196
235
|
const cmd = command('qvac-registry',
|
|
@@ -202,7 +241,8 @@ const cmd = command('qvac-registry',
|
|
|
202
241
|
flag('--verbose|-v', 'Enable verbose/debug logging'),
|
|
203
242
|
listCmd,
|
|
204
243
|
getCmd,
|
|
205
|
-
downloadCmd
|
|
244
|
+
downloadCmd,
|
|
245
|
+
profileCmd
|
|
206
246
|
)
|
|
207
247
|
|
|
208
248
|
cmd.parse()
|
package/lib/profiler.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Corestore = require('corestore')
|
|
4
|
+
const Hyperswarm = require('hyperswarm')
|
|
5
|
+
const Hyperblobs = require('hyperblobs')
|
|
6
|
+
const HypercoreStats = require('hypercore-stats')
|
|
7
|
+
const HyperswarmStats = require('hyperswarm-stats')
|
|
8
|
+
const IdEnc = require('hypercore-id-encoding')
|
|
9
|
+
const byteSize = require('tiny-byte-size')
|
|
10
|
+
const { RegistryDatabase } = require('@qvac/registry-schema')
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Normalize a blob core key into a Buffer regardless of input format.
|
|
14
|
+
* Handles raw Buffer, { data: [...] } objects from HyperDB, and z32/hex strings.
|
|
15
|
+
*/
|
|
16
|
+
function decodeCoreKey (key) {
|
|
17
|
+
if (Buffer.isBuffer(key)) return key
|
|
18
|
+
if (typeof key === 'object' && key !== null && key.data) {
|
|
19
|
+
return Buffer.from(key.data)
|
|
20
|
+
}
|
|
21
|
+
return IdEnc.decode(key)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Collect a snapshot of network, connection, and hypercore stats.
|
|
26
|
+
* All fields are plain values suitable for logging or formatting.
|
|
27
|
+
*/
|
|
28
|
+
function collectStats (swarmStats, hypercoreStats, blobCore, elapsedSec) {
|
|
29
|
+
const bytesRx = swarmStats.dhtStats.udxBytesReceived
|
|
30
|
+
const bytesTx = swarmStats.dhtStats.udxBytesTransmitted
|
|
31
|
+
|
|
32
|
+
const peers = []
|
|
33
|
+
for (const peer of blobCore.peers) {
|
|
34
|
+
peers.push({
|
|
35
|
+
key: IdEnc.normalize(peer.remotePublicKey),
|
|
36
|
+
remoteLength: peer.remoteLength,
|
|
37
|
+
remoteContiguous: peer.remoteContiguousLength
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
elapsedSec,
|
|
43
|
+
network: {
|
|
44
|
+
bytesRx,
|
|
45
|
+
bytesTx,
|
|
46
|
+
rxPerSec: elapsedSec > 0 ? bytesRx / elapsedSec : 0,
|
|
47
|
+
txPerSec: elapsedSec > 0 ? bytesTx / elapsedSec : 0,
|
|
48
|
+
packetsRx: swarmStats.dhtStats.udxPacketsReceived,
|
|
49
|
+
packetsTx: swarmStats.dhtStats.udxPacketsTransmitted,
|
|
50
|
+
packetsDropped: swarmStats.dhtStats.udxPacketsDropped
|
|
51
|
+
},
|
|
52
|
+
connection: {
|
|
53
|
+
firewalled: swarmStats.dhtStats.isFirewalled,
|
|
54
|
+
blobPeers: blobCore.peers.length,
|
|
55
|
+
attempted: swarmStats.connects.client.attempted,
|
|
56
|
+
opened: swarmStats.connects.client.opened,
|
|
57
|
+
closed: swarmStats.connects.client.closed,
|
|
58
|
+
rtos: swarmStats.getRTOCountAcrossAllStreams(),
|
|
59
|
+
fastRecoveries: swarmStats.getFastRecoveriesAcrossAllStreams(),
|
|
60
|
+
retransmits: swarmStats.getRetransmitsAcrossAllStreams()
|
|
61
|
+
},
|
|
62
|
+
hypercore: {
|
|
63
|
+
contiguousLength: blobCore.contiguousLength,
|
|
64
|
+
length: blobCore.length,
|
|
65
|
+
hotswaps: hypercoreStats.totalHotswaps
|
|
66
|
+
},
|
|
67
|
+
peers
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Format a stats snapshot into a human-readable string.
|
|
73
|
+
*/
|
|
74
|
+
function formatStats (stats) {
|
|
75
|
+
const n = stats.network
|
|
76
|
+
const c = stats.connection
|
|
77
|
+
const h = stats.hypercore
|
|
78
|
+
|
|
79
|
+
let lines = '--- ' + stats.elapsedSec.toFixed(1) + 's elapsed ---\n'
|
|
80
|
+
lines += 'Network (UDX)\n'
|
|
81
|
+
lines += ' Bytes received: ' + byteSize(n.bytesRx) + ' (' + byteSize(n.rxPerSec) + '/s)\n'
|
|
82
|
+
lines += ' Bytes transmitted: ' + byteSize(n.bytesTx) + ' (' + byteSize(n.txPerSec) + '/s)\n'
|
|
83
|
+
lines += ' Packets rx/tx: ' + n.packetsRx + ' / ' + n.packetsTx + '\n'
|
|
84
|
+
lines += ' Packets dropped: ' + n.packetsDropped + '\n'
|
|
85
|
+
lines += 'Connection\n'
|
|
86
|
+
lines += ' Firewalled: ' + c.firewalled + '\n'
|
|
87
|
+
lines += ' Blob peers: ' + c.blobPeers + '\n'
|
|
88
|
+
lines += ' Attempted: ' + c.attempted + '\n'
|
|
89
|
+
lines += ' Opened: ' + c.opened + '\n'
|
|
90
|
+
lines += ' Closed: ' + c.closed + '\n'
|
|
91
|
+
lines += ' Issues: rto=' + c.rtos + ' fast-recoveries=' + c.fastRecoveries + ' retransmits=' + c.retransmits + '\n'
|
|
92
|
+
lines += 'Hypercore\n'
|
|
93
|
+
lines += ' Blob core: ' + h.contiguousLength + ' / ' + h.length + ' (contiguous / length)\n'
|
|
94
|
+
lines += ' Hotswaps: ' + h.hotswaps + '\n'
|
|
95
|
+
|
|
96
|
+
if (stats.peers.length > 0) {
|
|
97
|
+
lines += ' Peers:\n'
|
|
98
|
+
for (const p of stats.peers) {
|
|
99
|
+
lines += ' ' + p.key + ' remote=' + p.remoteContiguous + '/' + p.remoteLength + '\n'
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return lines
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Format a final download summary into a human-readable string.
|
|
108
|
+
*/
|
|
109
|
+
function formatSummary (opts) {
|
|
110
|
+
let lines = '='.repeat(50) + '\n'
|
|
111
|
+
lines += 'FINAL SUMMARY\n'
|
|
112
|
+
lines += '='.repeat(50) + '\n'
|
|
113
|
+
lines += 'Download\n'
|
|
114
|
+
lines += ' Model: ' + opts.modelPath + '\n'
|
|
115
|
+
lines += ' Size: ' + byteSize(opts.totalBytes) + ' (' + opts.totalBlocks + ' blocks)\n'
|
|
116
|
+
lines += ' Metadata: ' + opts.metadataSec.toFixed(2) + 's\n'
|
|
117
|
+
lines += ' Transfer: ' + opts.transferSec.toFixed(2) + 's\n'
|
|
118
|
+
lines += ' Avg speed: ' + byteSize(opts.avgSpeed) + '/s\n'
|
|
119
|
+
lines += ' Total: ' + opts.totalSec.toFixed(2) + 's\n'
|
|
120
|
+
return lines
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Profile a blob download from the registry, printing periodic stats.
|
|
125
|
+
*
|
|
126
|
+
* @param {object} opts
|
|
127
|
+
* @param {string} opts.registryCoreKey - Registry view core key (z32 or hex)
|
|
128
|
+
* @param {string} opts.modelPath - Model path in the registry
|
|
129
|
+
* @param {string} [opts.source] - Source filter (e.g. "hf", "s3")
|
|
130
|
+
* @param {number} [opts.intervalSec=5] - Stats print interval in seconds
|
|
131
|
+
* @param {number} [opts.timeout=120000] - Stream read timeout in ms
|
|
132
|
+
* @param {function} [opts.onStats] - Called with (formattedString, rawStats) on each interval
|
|
133
|
+
* @param {function} [opts.onLog] - Called with (message) for log output; defaults to console.log
|
|
134
|
+
* @returns {Promise<object>} Summary with timing and stats
|
|
135
|
+
*/
|
|
136
|
+
async function profileDownload (opts) {
|
|
137
|
+
// Lazy-loaded: these Node builtins don't exist in Bare runtime,
|
|
138
|
+
// so they must not be required at the top level or unit tests break.
|
|
139
|
+
const os = require('#os')
|
|
140
|
+
const fs = require('#fs')
|
|
141
|
+
const path = require('#path')
|
|
142
|
+
const { performance } = require('perf_hooks')
|
|
143
|
+
const { pipeline } = require('stream/promises')
|
|
144
|
+
|
|
145
|
+
const {
|
|
146
|
+
registryCoreKey,
|
|
147
|
+
modelPath,
|
|
148
|
+
source,
|
|
149
|
+
intervalSec = 5,
|
|
150
|
+
timeout = 120000,
|
|
151
|
+
onStats,
|
|
152
|
+
onLog = console.log
|
|
153
|
+
} = opts
|
|
154
|
+
|
|
155
|
+
if (!registryCoreKey) throw new Error('registryCoreKey is required')
|
|
156
|
+
if (!modelPath) throw new Error('modelPath is required')
|
|
157
|
+
|
|
158
|
+
const tStart = performance.now()
|
|
159
|
+
const tmpdir = path.join(os.tmpdir(), 'qvac-profile-' + Date.now())
|
|
160
|
+
fs.mkdirSync(tmpdir, { recursive: true })
|
|
161
|
+
|
|
162
|
+
const store = new Corestore(tmpdir)
|
|
163
|
+
await store.ready()
|
|
164
|
+
|
|
165
|
+
const swarm = new Hyperswarm()
|
|
166
|
+
const hcStats = await HypercoreStats.fromCorestore(store, { cacheExpiryMs: 1000 })
|
|
167
|
+
const swStats = new HyperswarmStats(swarm)
|
|
168
|
+
|
|
169
|
+
swarm.on('connection', (conn, peerInfo) => {
|
|
170
|
+
const key = peerInfo?.publicKey ? IdEnc.normalize(peerInfo.publicKey) : 'unknown'
|
|
171
|
+
onLog(' [conn] peer ' + key + ' connected')
|
|
172
|
+
store.replicate(conn)
|
|
173
|
+
conn.on('error', (e) => onLog(' [conn] error: ' + e.message))
|
|
174
|
+
conn.on('close', () => onLog(' [conn] peer ' + key + ' disconnected'))
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const cleanupResources = async () => {
|
|
178
|
+
await swarm.destroy()
|
|
179
|
+
await store.close()
|
|
180
|
+
try { fs.rmSync(tmpdir, { recursive: true, force: true }) } catch {}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const viewKey = IdEnc.decode(registryCoreKey)
|
|
185
|
+
const viewCore = store.get({ key: viewKey })
|
|
186
|
+
await viewCore.ready()
|
|
187
|
+
|
|
188
|
+
onLog('View core key: ' + IdEnc.normalize(viewCore.key))
|
|
189
|
+
|
|
190
|
+
const foundPeers = viewCore.findingPeers()
|
|
191
|
+
swarm.join(viewCore.discoveryKey, { client: true, server: false })
|
|
192
|
+
swarm.flush().then(() => foundPeers())
|
|
193
|
+
|
|
194
|
+
await viewCore.update()
|
|
195
|
+
const metadataSec = (performance.now() - tStart) / 1000
|
|
196
|
+
|
|
197
|
+
const db = new RegistryDatabase(viewCore, { extension: false })
|
|
198
|
+
await db.ready()
|
|
199
|
+
|
|
200
|
+
onLog('Metadata synced in ' + metadataSec.toFixed(2) + 's (' + viewCore.length + ' blocks)')
|
|
201
|
+
|
|
202
|
+
const model = await db.getModel(modelPath, source || undefined)
|
|
203
|
+
if (!model) throw new Error('Model not found: ' + modelPath)
|
|
204
|
+
|
|
205
|
+
const blob = model.blobBinding
|
|
206
|
+
if (!blob || !blob.coreKey) throw new Error('Model has no blob binding')
|
|
207
|
+
|
|
208
|
+
onLog('\nProfiling download: ' + model.path)
|
|
209
|
+
onLog(' engine: ' + model.engine)
|
|
210
|
+
onLog(' source: ' + model.source)
|
|
211
|
+
onLog(' size: ' + byteSize(blob.byteLength) + ' (' + blob.blockLength + ' blocks)')
|
|
212
|
+
onLog('')
|
|
213
|
+
|
|
214
|
+
const coreKeyBuf = decodeCoreKey(blob.coreKey)
|
|
215
|
+
const blobCore = store.get({ key: coreKeyBuf })
|
|
216
|
+
await blobCore.ready()
|
|
217
|
+
const blobs = new Hyperblobs(blobCore)
|
|
218
|
+
await blobs.ready()
|
|
219
|
+
|
|
220
|
+
onLog('Blob core key: ' + IdEnc.normalize(blobCore.key))
|
|
221
|
+
onLog('Blob core discovery: ' + IdEnc.normalize(blobCore.discoveryKey))
|
|
222
|
+
|
|
223
|
+
const foundBlobPeers = blobCore.findingPeers()
|
|
224
|
+
swarm.join(blobCore.discoveryKey, { client: true, server: false })
|
|
225
|
+
swarm.flush().then(() => foundBlobPeers())
|
|
226
|
+
|
|
227
|
+
await blobCore.update()
|
|
228
|
+
onLog('Blob core synced: length=' + blobCore.length)
|
|
229
|
+
onLog('')
|
|
230
|
+
|
|
231
|
+
const outputFile = path.join(tmpdir, 'download.bin')
|
|
232
|
+
const tDownloadStart = performance.now()
|
|
233
|
+
|
|
234
|
+
const emitStats = () => {
|
|
235
|
+
const elapsed = (performance.now() - tDownloadStart) / 1000
|
|
236
|
+
const raw = collectStats(swStats, hcStats, blobCore, elapsed)
|
|
237
|
+
const formatted = formatStats(raw)
|
|
238
|
+
if (onStats) {
|
|
239
|
+
onStats(formatted, raw)
|
|
240
|
+
} else {
|
|
241
|
+
onLog(formatted)
|
|
242
|
+
}
|
|
243
|
+
return raw
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const statsInterval = setInterval(emitStats, intervalSec * 1000)
|
|
247
|
+
|
|
248
|
+
const rangeDownload = blobCore.download({
|
|
249
|
+
start: blob.blockOffset,
|
|
250
|
+
length: blob.blockLength
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const readStream = blobs.createReadStream(blob, { wait: true, timeout })
|
|
254
|
+
const writeStream = fs.createWriteStream(outputFile)
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
await pipeline(readStream, writeStream)
|
|
258
|
+
} finally {
|
|
259
|
+
rangeDownload.destroy()
|
|
260
|
+
clearInterval(statsInterval)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const totalSec = (performance.now() - tStart) / 1000
|
|
264
|
+
const transferSec = (performance.now() - tDownloadStart) / 1000
|
|
265
|
+
const avgSpeed = transferSec > 0 ? blob.byteLength / transferSec : 0
|
|
266
|
+
|
|
267
|
+
const finalStats = emitStats()
|
|
268
|
+
|
|
269
|
+
const summary = {
|
|
270
|
+
modelPath: model.path,
|
|
271
|
+
totalBytes: blob.byteLength,
|
|
272
|
+
totalBlocks: blob.blockLength,
|
|
273
|
+
metadataSec,
|
|
274
|
+
transferSec,
|
|
275
|
+
avgSpeed,
|
|
276
|
+
totalSec,
|
|
277
|
+
finalStats
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
onLog(formatSummary(summary))
|
|
281
|
+
|
|
282
|
+
await blobs.close()
|
|
283
|
+
await blobCore.close()
|
|
284
|
+
|
|
285
|
+
return summary
|
|
286
|
+
} finally {
|
|
287
|
+
await cleanupResources()
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
module.exports = {
|
|
292
|
+
decodeCoreKey,
|
|
293
|
+
collectStats,
|
|
294
|
+
formatStats,
|
|
295
|
+
formatSummary,
|
|
296
|
+
profileDownload
|
|
297
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qvac/registry-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "QVAC Registry client library for read-only queries via Hyperswarm",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/tetherto/qvac.git",
|
|
9
|
+
"directory": "packages/qvac-lib-registry-server/client"
|
|
10
|
+
},
|
|
11
|
+
"bugs": "https://github.com/tetherto/qvac/issues",
|
|
12
|
+
"homepage": "https://github.com/tetherto/qvac/tree/main/packages/qvac-lib-registry-server/client#readme",
|
|
6
13
|
"type": "commonjs",
|
|
7
14
|
"main": "index.js",
|
|
8
15
|
"bin": {
|
|
@@ -56,9 +63,12 @@
|
|
|
56
63
|
"hyperblobs": "^2.8.0",
|
|
57
64
|
"hypercore-id-encoding": "^1.3.0",
|
|
58
65
|
"hyperdb": "^4.16.1",
|
|
66
|
+
"hypercore-stats": "^2.4.0",
|
|
59
67
|
"hyperswarm": "^4.14.0",
|
|
68
|
+
"hyperswarm-stats": "^1.3.0",
|
|
60
69
|
"paparam": "^1.10.0",
|
|
61
|
-
"ready-resource": "^1.0.1"
|
|
70
|
+
"ready-resource": "^1.0.1",
|
|
71
|
+
"tiny-byte-size": "^1.1.0"
|
|
62
72
|
},
|
|
63
73
|
"devDependencies": {
|
|
64
74
|
"brittle": "^3.4.0",
|