@qvac/registry-client 0.3.0 → 0.4.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/NOTICE +0 -47
- package/README.md +1 -3
- package/index.d.ts +28 -1
- package/lib/client.js +60 -5
- package/package.json +3 -3
- package/utils/retry.js +29 -0
package/NOTICE
CHANGED
|
@@ -51,42 +51,6 @@ Third-Party Model Licenses
|
|
|
51
51
|
https://huggingface.co/personalizedrefrigerator/whisper-base-fr
|
|
52
52
|
fr-tiny-ggml-model-f16
|
|
53
53
|
https://huggingface.co/JaepaX/whisper-tiny-fr
|
|
54
|
-
ggml-opus-ar-en
|
|
55
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-ar-en
|
|
56
|
-
ggml-opus-de-en
|
|
57
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-de-en
|
|
58
|
-
ggml-opus-de-fr
|
|
59
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-de-fr
|
|
60
|
-
ggml-opus-en-ar
|
|
61
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-en-ar
|
|
62
|
-
ggml-opus-en-es
|
|
63
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-en-es
|
|
64
|
-
ggml-opus-en-fr
|
|
65
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-en-fr
|
|
66
|
-
ggml-opus-en-it
|
|
67
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-en-it
|
|
68
|
-
ggml-opus-en-ru
|
|
69
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-en-ru
|
|
70
|
-
ggml-opus-en-zh
|
|
71
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-en-zh
|
|
72
|
-
ggml-opus-es-en
|
|
73
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-es-en
|
|
74
|
-
ggml-opus-es-fr
|
|
75
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-es-fr
|
|
76
|
-
ggml-opus-fr-de
|
|
77
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-fr-de
|
|
78
|
-
ggml-opus-fr-en
|
|
79
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-fr-en
|
|
80
|
-
ggml-opus-fr-es
|
|
81
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-fr-es
|
|
82
|
-
ggml-opus-it-en
|
|
83
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-it-en
|
|
84
|
-
ggml-opus-ja-en
|
|
85
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-ja-en
|
|
86
|
-
ggml-opus-mt-en-roa-f16
|
|
87
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-en-roa
|
|
88
|
-
ggml-opus-mt-roa-en-f16
|
|
89
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-roa-en
|
|
90
54
|
gpt-oss-20b-GGUF
|
|
91
55
|
https://huggingface.co/unsloth/gpt-oss-20b-GGUF
|
|
92
56
|
it-base-ggml-model-f16
|
|
@@ -128,17 +92,6 @@ Third-Party Model Licenses
|
|
|
128
92
|
SmolVLM2-500M-Video-Instruct-GGUF
|
|
129
93
|
https://huggingface.co/ggml-org/SmolVLM2-500M-Video-Instruct-GGUF
|
|
130
94
|
|
|
131
|
-
--- cc-by-4.0 (Creative Commons Attribution 4.0 International) ---
|
|
132
|
-
|
|
133
|
-
ggml-opus-en-de
|
|
134
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-en-de
|
|
135
|
-
ggml-opus-en-pt
|
|
136
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-tc-big-en-pt
|
|
137
|
-
ggml-opus-ru-en
|
|
138
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-ru-en
|
|
139
|
-
ggml-opus-zh-en
|
|
140
|
-
https://huggingface.co/Helsinki-NLP/opus-mt-zh-en
|
|
141
|
-
|
|
142
95
|
--- gemma (Gemma Terms of Use) ---
|
|
143
96
|
|
|
144
97
|
embeddinggemma-300m-GGUF
|
package/README.md
CHANGED
|
@@ -139,11 +139,9 @@ The package includes a CLI for querying and downloading models from the registry
|
|
|
139
139
|
|
|
140
140
|
### Install
|
|
141
141
|
|
|
142
|
-
The package is hosted on npm.
|
|
142
|
+
The package is hosted on npm. Install globally:
|
|
143
143
|
|
|
144
144
|
```bash
|
|
145
|
-
echo "@qvac:registry=https://registry.npmjs.org" >> ~/.npmrc
|
|
146
|
-
echo "//registry.npmjs.org/:_authToken=YOUR_NPM_TOKEN" >> ~/.npmrc
|
|
147
145
|
npm install -g @qvac/registry-client
|
|
148
146
|
```
|
|
149
147
|
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import ReadyResource = require('ready-resource')
|
|
2
|
+
|
|
3
|
+
export interface LifecycleLogOptions {
|
|
4
|
+
log?: (msg: string) => Promise<void> | void
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
export interface QVACBlobBinding {
|
|
2
8
|
coreKey: Buffer | string
|
|
3
9
|
blockOffset: number
|
|
@@ -41,6 +47,8 @@ export interface QVACDownloadOptions {
|
|
|
41
47
|
timeout?: number
|
|
42
48
|
peerTimeout?: number
|
|
43
49
|
outputFile?: string
|
|
50
|
+
onProgress?: (progress: { downloaded: number, total: number, cachedBlocks: number, totalBlocks: number }) => void
|
|
51
|
+
signal?: AbortSignal
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
export interface QVACBlobDownloadOptions {
|
|
@@ -58,6 +66,7 @@ export interface QVACRegistryClientOptions {
|
|
|
58
66
|
storage?: string
|
|
59
67
|
registryCoreKey?: string
|
|
60
68
|
logger?: any
|
|
69
|
+
corestoreOpts?: { wait?: boolean }
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
export interface QVACModelQuery {
|
|
@@ -78,11 +87,29 @@ export interface FindByParams {
|
|
|
78
87
|
includeDeprecated?: boolean
|
|
79
88
|
}
|
|
80
89
|
|
|
81
|
-
export
|
|
90
|
+
export interface LifecycleSwarmHandle {
|
|
91
|
+
readonly suspended: boolean
|
|
92
|
+
suspend (opts?: LifecycleLogOptions): Promise<void>
|
|
93
|
+
resume (opts?: LifecycleLogOptions): Promise<void>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface LifecycleStoreHandle {
|
|
97
|
+
suspend (opts?: LifecycleLogOptions): Promise<void>
|
|
98
|
+
resume (): Promise<void>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class QVACRegistryClient extends ReadyResource {
|
|
82
102
|
constructor (opts?: QVACRegistryClientOptions)
|
|
83
103
|
|
|
104
|
+
/** Valid only while the client remains open. Cached handles become stale after close(). */
|
|
105
|
+
readonly corestore: LifecycleStoreHandle | null
|
|
106
|
+
/** Valid only while the client remains open. Cached handles become stale after close(). */
|
|
107
|
+
readonly hyperswarm: LifecycleSwarmHandle | null
|
|
108
|
+
|
|
84
109
|
ready (): Promise<void>
|
|
85
110
|
close (): Promise<void>
|
|
111
|
+
suspend (opts?: LifecycleLogOptions): Promise<void>
|
|
112
|
+
resume (opts?: LifecycleLogOptions): Promise<void>
|
|
86
113
|
|
|
87
114
|
getModel (path: string, source: string): Promise<QVACModelEntry | null>
|
|
88
115
|
downloadModel (path: string, source: string, options?: QVACDownloadOptions): Promise<QVACDownloadResult>
|
package/lib/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { QvacErrorRegistryClient, ERR_CODES } = require('../utils/error')
|
|
4
|
+
const { withRetry } = require('../utils/retry')
|
|
4
5
|
const RegistryConfig = require('./config')
|
|
5
6
|
const { RegistryDatabase } = require('@qvac/registry-schema')
|
|
6
7
|
const ReadyResource = require('ready-resource')
|
|
@@ -12,6 +13,9 @@ const IdEnc = require('hypercore-id-encoding')
|
|
|
12
13
|
const path = require('#path')
|
|
13
14
|
const fs = require('#fs')
|
|
14
15
|
|
|
16
|
+
const DEFAULT_DOWNLOAD_MAX_RETRIES = 3
|
|
17
|
+
const RETRIABLE_DOWNLOAD_CODES = ['REQUEST_TIMEOUT']
|
|
18
|
+
|
|
15
19
|
class QVACRegistryClient extends ReadyResource {
|
|
16
20
|
constructor (opts = {}) {
|
|
17
21
|
super()
|
|
@@ -20,13 +24,13 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
20
24
|
this.registryConfig = new RegistryConfig({ logger: this.logger })
|
|
21
25
|
|
|
22
26
|
this.db = null
|
|
23
|
-
this.corestore = null
|
|
24
27
|
this.hyperswarm = null
|
|
25
28
|
this._connectionHandler = null
|
|
26
29
|
this._metadataReady = null
|
|
27
30
|
|
|
28
31
|
this.storage = this.registryConfig.getRegistryStorage(opts.storage)
|
|
29
32
|
this.registryCoreKey = this.registryConfig.getRegistryCoreKey(opts.registryCoreKey)
|
|
33
|
+
this.corestore = new Corestore(this.storage, opts.corestoreOpts || {})
|
|
30
34
|
|
|
31
35
|
this.logger.debug('Initializing QVAC Registry Client', {
|
|
32
36
|
mode: 'read',
|
|
@@ -39,8 +43,7 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
39
43
|
async _open () {
|
|
40
44
|
this.logger.debug('_open called')
|
|
41
45
|
|
|
42
|
-
this.logger.debug('
|
|
43
|
-
this.corestore = new Corestore(this.storage)
|
|
46
|
+
this.logger.debug('Opening corestore')
|
|
44
47
|
await this.corestore.ready()
|
|
45
48
|
|
|
46
49
|
this.logger.debug('Creating Hyperswarm for open')
|
|
@@ -257,7 +260,15 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
257
260
|
|
|
258
261
|
let artifact
|
|
259
262
|
if (options.outputFile) {
|
|
260
|
-
await
|
|
263
|
+
await withRetry(
|
|
264
|
+
() => this._streamBlobToFile(blobs, core, model.blobBinding, options.outputFile, options),
|
|
265
|
+
{
|
|
266
|
+
maxRetries: options.maxRetries != null ? options.maxRetries : DEFAULT_DOWNLOAD_MAX_RETRIES,
|
|
267
|
+
retryCodes: RETRIABLE_DOWNLOAD_CODES,
|
|
268
|
+
onRetry: () => fs.promises.unlink(options.outputFile).catch(() => {}),
|
|
269
|
+
logger: this.logger
|
|
270
|
+
}
|
|
271
|
+
)
|
|
261
272
|
artifact = { path: options.outputFile, totalSize }
|
|
262
273
|
|
|
263
274
|
rangeDownload.destroy()
|
|
@@ -381,7 +392,15 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
381
392
|
|
|
382
393
|
let artifact
|
|
383
394
|
if (options.outputFile) {
|
|
384
|
-
await
|
|
395
|
+
await withRetry(
|
|
396
|
+
() => this._streamBlobToFile(blobs, core, pointer, options.outputFile, options),
|
|
397
|
+
{
|
|
398
|
+
maxRetries: options.maxRetries != null ? options.maxRetries : DEFAULT_DOWNLOAD_MAX_RETRIES,
|
|
399
|
+
retryCodes: RETRIABLE_DOWNLOAD_CODES,
|
|
400
|
+
onRetry: () => fs.promises.unlink(options.outputFile).catch(() => {}),
|
|
401
|
+
logger: this.logger
|
|
402
|
+
}
|
|
403
|
+
)
|
|
385
404
|
artifact = { path: options.outputFile, totalSize }
|
|
386
405
|
|
|
387
406
|
rangeDownload.destroy()
|
|
@@ -526,6 +545,42 @@ class QVACRegistryClient extends ReadyResource {
|
|
|
526
545
|
}
|
|
527
546
|
}
|
|
528
547
|
|
|
548
|
+
async suspend (opts = {}) {
|
|
549
|
+
this.logger.debug('suspend called')
|
|
550
|
+
|
|
551
|
+
if (!this.opened || this.closing) {
|
|
552
|
+
this.logger.debug('Skipping suspend while client is not open or is closing')
|
|
553
|
+
return
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (this.hyperswarm && !this.hyperswarm.suspended) {
|
|
557
|
+
await this.hyperswarm.suspend(opts)
|
|
558
|
+
}
|
|
559
|
+
if (this.corestore) {
|
|
560
|
+
await this.corestore.suspend(opts)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
this.logger.debug('QVACRegistryClient suspended')
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async resume (opts = {}) {
|
|
567
|
+
this.logger.debug('resume called')
|
|
568
|
+
|
|
569
|
+
if (!this.opened || this.closing) {
|
|
570
|
+
this.logger.debug('Skipping resume while client is not open or is closing')
|
|
571
|
+
return
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (this.corestore) {
|
|
575
|
+
await this.corestore.resume()
|
|
576
|
+
}
|
|
577
|
+
if (this.hyperswarm && this.hyperswarm.suspended) {
|
|
578
|
+
await this.hyperswarm.resume(opts)
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
this.logger.debug('QVACRegistryClient resumed')
|
|
582
|
+
}
|
|
583
|
+
|
|
529
584
|
async _close () {
|
|
530
585
|
this.logger.debug('_close called')
|
|
531
586
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qvac/registry-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "QVAC Registry client library for read-only queries via Hyperswarm",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@qvac/error": "^0.1.0",
|
|
56
|
-
"@qvac/registry-schema": "^0.1.
|
|
56
|
+
"@qvac/registry-schema": "^0.1.2",
|
|
57
57
|
"b4a": "^1.6.7",
|
|
58
58
|
"bare-fs": "^4.5.2",
|
|
59
59
|
"bare-os": "^3.6.2",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"corestore": "^7.4.5",
|
|
63
63
|
"hyperblobs": "^2.8.0",
|
|
64
64
|
"hypercore-id-encoding": "^1.3.0",
|
|
65
|
-
"hyperdb": "^4.16.1",
|
|
66
65
|
"hypercore-stats": "^2.4.0",
|
|
66
|
+
"hyperdb": "^4.16.1",
|
|
67
67
|
"hyperswarm": "^4.14.0",
|
|
68
68
|
"hyperswarm-stats": "^1.3.0",
|
|
69
69
|
"paparam": "^1.10.0",
|
package/utils/retry.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Executes an async function with automatic retry on specific error codes.
|
|
5
|
+
*
|
|
6
|
+
* @param {() => Promise<unknown>} fn - The async operation to attempt
|
|
7
|
+
* @param {object} opts
|
|
8
|
+
* @param {number} [opts.maxRetries=3] - Maximum number of attempts (including the first)
|
|
9
|
+
* @param {string[]} [opts.retryCodes=[]] - Error codes that trigger a retry
|
|
10
|
+
* @param {() => Promise<void>} [opts.onRetry] - Called before each retry attempt (e.g. cleanup)
|
|
11
|
+
* @param {{ warn: Function }} [opts.logger] - Logger instance for retry warnings
|
|
12
|
+
* @returns {Promise<unknown>}
|
|
13
|
+
*/
|
|
14
|
+
async function withRetry (fn, opts = {}) {
|
|
15
|
+
const { maxRetries = 3, retryCodes = [], onRetry, logger } = opts
|
|
16
|
+
|
|
17
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
18
|
+
try {
|
|
19
|
+
return await fn()
|
|
20
|
+
} catch (err) {
|
|
21
|
+
const isRetriable = retryCodes.length > 0 && retryCodes.includes(err && err.code)
|
|
22
|
+
if (!isRetriable || attempt >= maxRetries) throw err
|
|
23
|
+
logger && logger.warn(`Retrying after ${err.code} (attempt ${attempt}/${maxRetries}): ${err.message}`)
|
|
24
|
+
if (onRetry) await onRetry()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { withRetry }
|