@qvac/registry-client 0.1.8 → 0.2.1
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 +39 -10
- package/index.d.ts +13 -1
- package/lib/client.js +187 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @qvac/registry-client
|
|
2
2
|
|
|
3
3
|
Read-only client library for querying the QVAC Registry. It replicates the registry HyperDB via Hyperswarm and provides APIs for searching and retrieving model information.
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ Read-only client library for querying the QVAC Registry. It replicates the regis
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install @
|
|
16
|
+
npm install @qvac/registry-client
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Ensure the registry core key is available via environment variables or provided via options.
|
|
@@ -24,7 +24,7 @@ Ensure the registry core key is available via environment variables or provided
|
|
|
24
24
|
|
|
25
25
|
```javascript
|
|
26
26
|
'use strict'
|
|
27
|
-
const { QVACRegistryClient } = require('@
|
|
27
|
+
const { QVACRegistryClient } = require('@qvac/registry-client')
|
|
28
28
|
|
|
29
29
|
async function main () {
|
|
30
30
|
const client = new QVACRegistryClient({
|
|
@@ -106,18 +106,45 @@ const fs = require('fs')
|
|
|
106
106
|
result.artifact.stream.pipe(fs.createWriteStream('./model.ggml'))
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
#### Direct Blob Download
|
|
110
|
+
|
|
111
|
+
- `downloadBlob(blobBinding, options)`: Downloads a blob directly using known blob coordinates, bypassing the metadata core lookup. Useful when `coreKey`, `blockOffset`, `blockLength`, and `byteLength` are already known (e.g., from a previous query or generated model constants).
|
|
112
|
+
- `blobBinding`: `{ coreKey, blockOffset, blockLength, byteOffset, byteLength }` — `coreKey` accepts Buffer, hex, or z-base-32 strings
|
|
113
|
+
- `options`:
|
|
114
|
+
- `timeout`: Download timeout in ms (default: 30000)
|
|
115
|
+
- `outputFile`: Optional file path to save directly to disk
|
|
116
|
+
- `onProgress`: Callback `({ downloaded, total, cachedBlocks, totalBlocks }) => void`
|
|
117
|
+
- `signal`: `AbortSignal` for cancellation
|
|
118
|
+
- Returns: `{ artifact: { path, totalSize } | { stream, totalSize } }`
|
|
119
|
+
|
|
120
|
+
This method only waits for the network layer (Corestore + Hyperswarm) — it does not wait for the metadata core to sync, making it faster for known blob coordinates.
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const result = await client.downloadBlob({
|
|
124
|
+
coreKey: 'ey46cahego89xox118uhyryakz47bcs8bbxu97tnnpmuwmgi5wmo',
|
|
125
|
+
blockOffset: 0,
|
|
126
|
+
blockLength: 665,
|
|
127
|
+
byteOffset: 0,
|
|
128
|
+
byteLength: 43537433
|
|
129
|
+
}, {
|
|
130
|
+
outputFile: './downloaded/ggml-tiny-q8_0.bin',
|
|
131
|
+
timeout: 60000
|
|
132
|
+
})
|
|
133
|
+
console.log('Downloaded to:', result.artifact.path)
|
|
134
|
+
```
|
|
135
|
+
|
|
109
136
|
## CLI
|
|
110
137
|
|
|
111
138
|
The package includes a CLI for querying and downloading models from the registry.
|
|
112
139
|
|
|
113
140
|
### Install
|
|
114
141
|
|
|
115
|
-
The package is hosted on
|
|
142
|
+
The package is hosted on npm. Configure npm to use the registry for the `@qvac` scope, then install globally:
|
|
116
143
|
|
|
117
144
|
```bash
|
|
118
|
-
echo "@
|
|
119
|
-
echo "//
|
|
120
|
-
npm install -g @
|
|
145
|
+
echo "@qvac:registry=https://registry.npmjs.org" >> ~/.npmrc
|
|
146
|
+
echo "//registry.npmjs.org/:_authToken=YOUR_NPM_TOKEN" >> ~/.npmrc
|
|
147
|
+
npm install -g @qvac/registry-client
|
|
121
148
|
```
|
|
122
149
|
|
|
123
150
|
Verify installation:
|
|
@@ -220,7 +247,8 @@ $ qvac-registry list --engine @qvac/transcription-whispercpp --json | jq '.[0].p
|
|
|
220
247
|
See the `examples/` folder for complete working examples:
|
|
221
248
|
|
|
222
249
|
- `example.js`: List models, query by engine/name/quantization, find shards
|
|
223
|
-
- `download-model.js`: Download a single model to disk
|
|
250
|
+
- `download-model.js`: Download a single model to disk via metadata lookup
|
|
251
|
+
- `download-blob.js`: Download a blob directly using known blob coordinates
|
|
224
252
|
- `download-all-models.js`: Download all models in the registry
|
|
225
253
|
|
|
226
254
|
Run examples:
|
|
@@ -229,6 +257,7 @@ Run examples:
|
|
|
229
257
|
cd client
|
|
230
258
|
node examples/example.js
|
|
231
259
|
node examples/download-model.js
|
|
260
|
+
node examples/download-blob.js
|
|
232
261
|
node examples/download-all-models.js
|
|
233
262
|
```
|
|
234
263
|
|
|
@@ -271,8 +300,8 @@ The client uses custom error codes in the range 19001-20000. All errors extend `
|
|
|
271
300
|
### Error Handling Example
|
|
272
301
|
|
|
273
302
|
```javascript
|
|
274
|
-
const { QVACRegistryClient } = require('@
|
|
275
|
-
const { QvacErrorRegistryClient } = require('@
|
|
303
|
+
const { QVACRegistryClient } = require('@qvac/registry-client')
|
|
304
|
+
const { QvacErrorRegistryClient } = require('@qvac/registry-client/utils/error')
|
|
276
305
|
|
|
277
306
|
async function handleErrors () {
|
|
278
307
|
const client = new QVACRegistryClient({
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface QVACBlobBinding {
|
|
2
|
-
coreKey: Buffer
|
|
2
|
+
coreKey: Buffer | string
|
|
3
3
|
blockOffset: number
|
|
4
4
|
blockLength: number
|
|
5
5
|
byteOffset: number
|
|
@@ -43,6 +43,17 @@ export interface QVACDownloadOptions {
|
|
|
43
43
|
outputFile?: string
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export interface QVACBlobDownloadOptions {
|
|
47
|
+
timeout?: number
|
|
48
|
+
outputFile?: string
|
|
49
|
+
onProgress?: (progress: { downloaded: number, total: number, cachedBlocks: number, totalBlocks: number }) => void
|
|
50
|
+
signal?: AbortSignal
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface QVACBlobDownloadResult {
|
|
54
|
+
artifact: QVACDownloadedArtifact & { totalSize: number }
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
export interface QVACRegistryClientOptions {
|
|
47
58
|
storage?: string
|
|
48
59
|
registryCoreKey?: string
|
|
@@ -75,6 +86,7 @@ export class QVACRegistryClient {
|
|
|
75
86
|
|
|
76
87
|
getModel (path: string, source: string): Promise<QVACModelEntry | null>
|
|
77
88
|
downloadModel (path: string, source: string, options?: QVACDownloadOptions): Promise<QVACDownloadResult>
|
|
89
|
+
downloadBlob (blobBinding: QVACBlobBinding, options?: QVACBlobDownloadOptions): Promise<QVACBlobDownloadResult>
|
|
78
90
|
|
|
79
91
|
findBy (params?: FindByParams): Promise<QVACModelEntry[]>
|
|
80
92
|
findModels (query?: QVACModelQuery): Promise<QVACModelEntry[]>
|
package/lib/client.js
CHANGED
|
@@ -23,6 +23,7 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
23
23
|
this.corestore = null
|
|
24
24
|
this.hyperswarm = null
|
|
25
25
|
this._connectionHandler = null
|
|
26
|
+
this._metadataReady = null
|
|
26
27
|
|
|
27
28
|
this.storage = this.registryConfig.getRegistryStorage(opts.storage)
|
|
28
29
|
this.registryCoreKey = this.registryConfig.getRegistryCoreKey(opts.registryCoreKey)
|
|
@@ -38,11 +39,6 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
38
39
|
async _open () {
|
|
39
40
|
this.logger.debug('_open called')
|
|
40
41
|
|
|
41
|
-
if (!this.registryCoreKey) {
|
|
42
|
-
this.logger.error('Missing registry core key for read mode')
|
|
43
|
-
throw new QvacErrorRegistryClient({ code: ERR_CODES.FAILED_TO_CONNECT, adds: 'Missing registry core key. Set QVAC_REGISTRY_CORE_KEY environment variable.' })
|
|
44
|
-
}
|
|
45
|
-
|
|
46
42
|
this.logger.debug('Creating corestore for open')
|
|
47
43
|
this.corestore = new Corestore(this.storage)
|
|
48
44
|
await this.corestore.ready()
|
|
@@ -56,6 +52,15 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
56
52
|
}
|
|
57
53
|
this.hyperswarm.on('connection', this._connectionHandler)
|
|
58
54
|
|
|
55
|
+
this._metadataReady = this._connectMetadataCore()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async _connectMetadataCore () {
|
|
59
|
+
if (!this.registryCoreKey) {
|
|
60
|
+
this.logger.error('Missing registry core key for read mode')
|
|
61
|
+
throw new QvacErrorRegistryClient({ code: ERR_CODES.FAILED_TO_CONNECT, adds: 'Missing registry core key. Set QVAC_REGISTRY_CORE_KEY environment variable.' })
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
const viewKey = IdEnc.decode(this.registryCoreKey)
|
|
60
65
|
const viewCore = this.corestore.get({ key: viewKey })
|
|
61
66
|
await viewCore.ready()
|
|
@@ -65,28 +70,37 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
65
70
|
discoveryKey: IdEnc.normalize(viewCore.discoveryKey)
|
|
66
71
|
})
|
|
67
72
|
|
|
73
|
+
const foundPeers = viewCore.findingPeers()
|
|
68
74
|
this.hyperswarm.join(viewCore.discoveryKey, { client: true, server: false })
|
|
69
|
-
|
|
75
|
+
this.hyperswarm.flush().then(() => foundPeers())
|
|
70
76
|
|
|
71
77
|
this.logger.debug('Waiting for view core sync')
|
|
72
|
-
await viewCore.update(
|
|
78
|
+
await viewCore.update()
|
|
73
79
|
|
|
74
80
|
this.logger.debug('Creating RegistryDatabase from view core')
|
|
75
81
|
this.db = new RegistryDatabase(viewCore, { extension: false })
|
|
76
82
|
await this.db.ready()
|
|
77
83
|
|
|
78
|
-
this.logger.debug('QVACRegistryClient ready', {
|
|
84
|
+
this.logger.debug('QVACRegistryClient metadata ready', {
|
|
79
85
|
length: viewCore.length
|
|
80
86
|
})
|
|
81
87
|
}
|
|
82
88
|
|
|
89
|
+
async _ensureMetadata () {
|
|
90
|
+
await this.ready()
|
|
91
|
+
await this._metadataReady
|
|
92
|
+
if (!this.db) {
|
|
93
|
+
throw new QvacErrorRegistryClient({ code: ERR_CODES.FAILED_TO_CONNECT, adds: 'Registry database not available.' })
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
async getModel (path, source) {
|
|
84
98
|
this._validateString(path, 'path')
|
|
85
99
|
this._validateString(source, 'source')
|
|
86
100
|
|
|
87
101
|
try {
|
|
88
102
|
this.logger.info('Getting model', { path, source })
|
|
89
|
-
await this.
|
|
103
|
+
await this._ensureMetadata()
|
|
90
104
|
|
|
91
105
|
const result = await this.db.getModel(path, source)
|
|
92
106
|
this.logger.debug('getModel result', { result })
|
|
@@ -98,7 +112,7 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
async findModels (query = {}, opts = {}) {
|
|
101
|
-
await this.
|
|
115
|
+
await this._ensureMetadata()
|
|
102
116
|
const { includeDeprecated = false } = opts
|
|
103
117
|
this.logger.debug('findModels called', { query, includeDeprecated })
|
|
104
118
|
|
|
@@ -112,19 +126,19 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
112
126
|
}
|
|
113
127
|
|
|
114
128
|
async findModelsByEngine (query = {}) {
|
|
115
|
-
await this.
|
|
129
|
+
await this._ensureMetadata()
|
|
116
130
|
this.logger.debug('findModelsByEngine called', { query })
|
|
117
131
|
return this.db.findModelsByEngine(query).toArray()
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
async findModelsByName (query = {}) {
|
|
121
|
-
await this.
|
|
135
|
+
await this._ensureMetadata()
|
|
122
136
|
this.logger.debug('findModelsByName called', { query })
|
|
123
137
|
return this.db.findModelsByName(query).toArray()
|
|
124
138
|
}
|
|
125
139
|
|
|
126
140
|
async findModelsByQuantization (query = {}) {
|
|
127
|
-
await this.
|
|
141
|
+
await this._ensureMetadata()
|
|
128
142
|
this.logger.debug('findModelsByQuantization called', { query })
|
|
129
143
|
return this.db.findModelsByQuantization(query).toArray()
|
|
130
144
|
}
|
|
@@ -140,7 +154,7 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
140
154
|
* @returns {Promise<Array>} Array of matching models
|
|
141
155
|
*/
|
|
142
156
|
async findBy (params = {}) {
|
|
143
|
-
await this.
|
|
157
|
+
await this._ensureMetadata()
|
|
144
158
|
this.logger.debug('findBy called', { params })
|
|
145
159
|
return this.db.findBy(params)
|
|
146
160
|
}
|
|
@@ -169,9 +183,14 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
169
183
|
}
|
|
170
184
|
|
|
171
185
|
async _getBlobsCore (blobsCoreKey) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
186
|
+
let keyBuffer
|
|
187
|
+
if (Buffer.isBuffer(blobsCoreKey)) {
|
|
188
|
+
keyBuffer = blobsCoreKey
|
|
189
|
+
} else if (typeof blobsCoreKey === 'object' && blobsCoreKey.data) {
|
|
190
|
+
keyBuffer = Buffer.from(blobsCoreKey.data)
|
|
191
|
+
} else {
|
|
192
|
+
keyBuffer = IdEnc.decode(blobsCoreKey)
|
|
193
|
+
}
|
|
175
194
|
|
|
176
195
|
this.logger.debug('Creating hyperblobs core', {
|
|
177
196
|
blobCore: IdEnc.normalize(keyBuffer)
|
|
@@ -198,7 +217,7 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
198
217
|
|
|
199
218
|
try {
|
|
200
219
|
this.logger.info('Downloading model', { path, source })
|
|
201
|
-
await this.
|
|
220
|
+
await this._ensureMetadata()
|
|
202
221
|
|
|
203
222
|
const model = await this.getModel(path, source)
|
|
204
223
|
if (!model) {
|
|
@@ -219,19 +238,30 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
219
238
|
discoveryKey: IdEnc.normalize(core.discoveryKey)
|
|
220
239
|
})
|
|
221
240
|
|
|
241
|
+
const foundBlobPeers = core.findingPeers()
|
|
222
242
|
this.hyperswarm.join(core.discoveryKey, { client: true, server: false })
|
|
223
|
-
|
|
243
|
+
this.hyperswarm.flush().then(() => foundBlobPeers())
|
|
224
244
|
|
|
225
|
-
await core.update(
|
|
245
|
+
await core.update()
|
|
226
246
|
this.logger.debug('Blobs core updated')
|
|
227
247
|
|
|
228
248
|
const totalSize = model.blobBinding.byteLength
|
|
229
249
|
|
|
250
|
+
const rangeDownload = core.download({
|
|
251
|
+
start: model.blobBinding.blockOffset,
|
|
252
|
+
length: model.blobBinding.blockLength
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
const blockStart = model.blobBinding.blockOffset
|
|
256
|
+
const blockEnd = blockStart + model.blobBinding.blockLength
|
|
257
|
+
|
|
230
258
|
let artifact
|
|
231
259
|
if (options.outputFile) {
|
|
232
260
|
await this._streamBlobToFile(blobs, core, model.blobBinding, options.outputFile, options)
|
|
233
261
|
artifact = { path: options.outputFile, totalSize }
|
|
234
262
|
|
|
263
|
+
rangeDownload.destroy()
|
|
264
|
+
await this._clearBlobBlocks(core, blockStart, blockEnd)
|
|
235
265
|
if (blobs) await blobs.close()
|
|
236
266
|
if (core) await core.close()
|
|
237
267
|
} else {
|
|
@@ -242,6 +272,8 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
242
272
|
artifact = { stream, totalSize }
|
|
243
273
|
|
|
244
274
|
const cleanup = async () => {
|
|
275
|
+
rangeDownload.destroy()
|
|
276
|
+
await this._clearBlobBlocks(core, blockStart, blockEnd)
|
|
245
277
|
if (blobs) {
|
|
246
278
|
try {
|
|
247
279
|
await blobs.close()
|
|
@@ -259,7 +291,7 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
259
291
|
this.logger.debug('Blob resources closed after stream end')
|
|
260
292
|
}
|
|
261
293
|
|
|
262
|
-
stream.
|
|
294
|
+
stream.once('end', cleanup)
|
|
263
295
|
}
|
|
264
296
|
|
|
265
297
|
this.logger.info('Model downloaded successfully')
|
|
@@ -290,6 +322,129 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
290
322
|
}
|
|
291
323
|
}
|
|
292
324
|
|
|
325
|
+
async downloadBlob (blobBinding, options = {}) {
|
|
326
|
+
if (!blobBinding || !blobBinding.coreKey) {
|
|
327
|
+
throw new Error('Invalid blobBinding: coreKey is required')
|
|
328
|
+
}
|
|
329
|
+
if (typeof blobBinding.blockOffset !== 'number' ||
|
|
330
|
+
typeof blobBinding.blockLength !== 'number' ||
|
|
331
|
+
typeof blobBinding.byteLength !== 'number') {
|
|
332
|
+
throw new Error('Invalid blobBinding: blockOffset, blockLength, and byteLength are required numbers')
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (options && typeof options !== 'object') {
|
|
336
|
+
throw new Error(`Invalid options: ${typeof options}`)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let core, blobs
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
this.logger.info('Downloading blob directly', {
|
|
343
|
+
coreKey: typeof blobBinding.coreKey === 'string' ? blobBinding.coreKey.slice(0, 12) + '...' : '(buffer)',
|
|
344
|
+
blockOffset: blobBinding.blockOffset,
|
|
345
|
+
blockLength: blobBinding.blockLength,
|
|
346
|
+
byteLength: blobBinding.byteLength
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
await this.ready()
|
|
350
|
+
|
|
351
|
+
const blobsCore = await this._getBlobsCore(blobBinding.coreKey)
|
|
352
|
+
core = blobsCore.core
|
|
353
|
+
blobs = blobsCore.blobs
|
|
354
|
+
|
|
355
|
+
this.logger.debug('Joining swarm for blobs core (direct)', {
|
|
356
|
+
discoveryKey: IdEnc.normalize(core.discoveryKey)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
const foundBlobPeers = core.findingPeers()
|
|
360
|
+
this.hyperswarm.join(core.discoveryKey, { client: true, server: false })
|
|
361
|
+
this.hyperswarm.flush().then(() => foundBlobPeers())
|
|
362
|
+
|
|
363
|
+
await core.update()
|
|
364
|
+
this.logger.debug('Blobs core updated (direct)')
|
|
365
|
+
|
|
366
|
+
const pointer = {
|
|
367
|
+
blockOffset: blobBinding.blockOffset,
|
|
368
|
+
blockLength: blobBinding.blockLength,
|
|
369
|
+
byteOffset: blobBinding.byteOffset || 0,
|
|
370
|
+
byteLength: blobBinding.byteLength
|
|
371
|
+
}
|
|
372
|
+
const totalSize = blobBinding.byteLength
|
|
373
|
+
|
|
374
|
+
const blockStart = pointer.blockOffset
|
|
375
|
+
const blockEnd = blockStart + pointer.blockLength
|
|
376
|
+
|
|
377
|
+
const rangeDownload = core.download({
|
|
378
|
+
start: pointer.blockOffset,
|
|
379
|
+
length: pointer.blockLength
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
let artifact
|
|
383
|
+
if (options.outputFile) {
|
|
384
|
+
await this._streamBlobToFile(blobs, core, pointer, options.outputFile, options)
|
|
385
|
+
artifact = { path: options.outputFile, totalSize }
|
|
386
|
+
|
|
387
|
+
rangeDownload.destroy()
|
|
388
|
+
await this._clearBlobBlocks(core, blockStart, blockEnd)
|
|
389
|
+
if (blobs) await blobs.close()
|
|
390
|
+
if (core) await core.close()
|
|
391
|
+
} else {
|
|
392
|
+
const stream = blobs.createReadStream(pointer, {
|
|
393
|
+
wait: true,
|
|
394
|
+
timeout: options.timeout || 30000
|
|
395
|
+
})
|
|
396
|
+
artifact = { stream, totalSize }
|
|
397
|
+
|
|
398
|
+
const cleanup = async () => {
|
|
399
|
+
rangeDownload.destroy()
|
|
400
|
+
await this._clearBlobBlocks(core, blockStart, blockEnd)
|
|
401
|
+
if (blobs) {
|
|
402
|
+
try { await blobs.close() } catch (e) {
|
|
403
|
+
this.logger.warn('Error closing blob instance', { error: e.message })
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (core) {
|
|
407
|
+
try { await core.close() } catch (e) {
|
|
408
|
+
this.logger.warn('Error closing blob core', { error: e.message })
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
this.logger.debug('Blob resources closed after stream end')
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
stream.once('end', cleanup)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
this.logger.info('Blob download complete (direct)')
|
|
418
|
+
|
|
419
|
+
return { artifact }
|
|
420
|
+
} catch (error) {
|
|
421
|
+
this.logger.error('Error downloading blob directly', error)
|
|
422
|
+
|
|
423
|
+
if (blobs) {
|
|
424
|
+
try { await blobs.close() } catch (e) {
|
|
425
|
+
this.logger.warn('Error closing blob instance on error', { error: e.message })
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (core) {
|
|
429
|
+
try { await core.close() } catch (e) {
|
|
430
|
+
this.logger.warn('Error closing blob core on error', { error: e.message })
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
throw error
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async _clearBlobBlocks (core, start, end) {
|
|
439
|
+
try {
|
|
440
|
+
const cleared = await core.clear(start, end, { diff: true })
|
|
441
|
+
await core.compact()
|
|
442
|
+
this.logger.info('Cleared blob blocks from corestore', { start, end, blocks: cleared ? cleared.blocks : end - start })
|
|
443
|
+
} catch (err) {
|
|
444
|
+
this.logger.warn('Failed to clear blob blocks from corestore', { error: err.message })
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
293
448
|
async _streamBlobToFile (blobs, core, blobPointer, filePath, options) {
|
|
294
449
|
const { cachedBlocks, totalBlocks, totalBytes } = await this._checkBlobProgress(core, blobPointer)
|
|
295
450
|
|
|
@@ -329,6 +484,11 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
329
484
|
|
|
330
485
|
core.on('download', progressHandler)
|
|
331
486
|
|
|
487
|
+
const rangeDownload = core.download({
|
|
488
|
+
start: blobPointer.blockOffset,
|
|
489
|
+
length: blobPointer.blockLength
|
|
490
|
+
})
|
|
491
|
+
|
|
332
492
|
const stream = blobs.createReadStream(blobPointer, {
|
|
333
493
|
wait: true,
|
|
334
494
|
timeout: options.timeout || 30000
|
|
@@ -361,6 +521,7 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
361
521
|
}
|
|
362
522
|
})
|
|
363
523
|
} finally {
|
|
524
|
+
rangeDownload.destroy()
|
|
364
525
|
core.off('download', progressHandler)
|
|
365
526
|
}
|
|
366
527
|
}
|
|
@@ -368,6 +529,11 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
368
529
|
async _close () {
|
|
369
530
|
this.logger.debug('_close called')
|
|
370
531
|
|
|
532
|
+
if (this._metadataReady) {
|
|
533
|
+
await this._metadataReady.catch(() => {})
|
|
534
|
+
this._metadataReady = null
|
|
535
|
+
}
|
|
536
|
+
|
|
371
537
|
if (this.db) {
|
|
372
538
|
this.logger.debug('Closing database')
|
|
373
539
|
await this.db.close()
|