@hasna/models 0.0.5 → 0.0.7
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/dist/cli/index.js +43 -19
- package/dist/index.js +114 -89
- package/dist/storage.js +109 -15
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2910,7 +2910,7 @@ function safeDestinationPath(root, filePath) {
|
|
|
2910
2910
|
}
|
|
2911
2911
|
return destination;
|
|
2912
2912
|
}
|
|
2913
|
-
function normalizeEntry(raw, kind) {
|
|
2913
|
+
function normalizeEntry(raw, kind, fallbackRevision = "main") {
|
|
2914
2914
|
const repoId = String(raw.id ?? raw.modelId ?? "");
|
|
2915
2915
|
const tags = Array.isArray(raw.tags) ? raw.tags.map(String) : [];
|
|
2916
2916
|
const cardData = raw.cardData;
|
|
@@ -2918,7 +2918,7 @@ function normalizeEntry(raw, kind) {
|
|
|
2918
2918
|
provider: "huggingface",
|
|
2919
2919
|
entityKind: kind,
|
|
2920
2920
|
repoId,
|
|
2921
|
-
revision: String(raw.sha ??
|
|
2921
|
+
revision: String(raw.sha ?? fallbackRevision),
|
|
2922
2922
|
canonicalUrl: canonicalUrl(kind, repoId),
|
|
2923
2923
|
title: repoId,
|
|
2924
2924
|
author: typeof raw.author === "string" ? raw.author : repoId.split("/")[0],
|
|
@@ -3001,8 +3001,9 @@ async function searchHuggingFace(input = {}) {
|
|
|
3001
3001
|
}
|
|
3002
3002
|
async function getHuggingFaceInfo(refOrInput, defaultKind = "model") {
|
|
3003
3003
|
const ref = typeof refOrInput === "string" ? parseProviderRef(refOrInput, defaultKind) : refOrInput;
|
|
3004
|
-
const
|
|
3005
|
-
|
|
3004
|
+
const revision = ref.revision || "main";
|
|
3005
|
+
const raw = await hfJson(`/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}/revision/${encodeURIComponent(revision)}`);
|
|
3006
|
+
return normalizeEntry(raw, ref.entityKind, revision);
|
|
3006
3007
|
}
|
|
3007
3008
|
async function listHuggingFaceFiles(refOrInput, defaultKind = "model") {
|
|
3008
3009
|
const ref = typeof refOrInput === "string" ? parseProviderRef(refOrInput, defaultKind) : refOrInput;
|
|
@@ -3100,6 +3101,33 @@ import { mkdirSync as mkdirSync3 } from "fs";
|
|
|
3100
3101
|
import { dirname as dirname4 } from "path";
|
|
3101
3102
|
import { Database } from "bun:sqlite";
|
|
3102
3103
|
var SCHEMA_VERSION = 1;
|
|
3104
|
+
function rowToInstall(row) {
|
|
3105
|
+
return {
|
|
3106
|
+
id: String(row.id),
|
|
3107
|
+
provider: String(row.provider),
|
|
3108
|
+
entityKind: String(row.entity_kind),
|
|
3109
|
+
repoId: String(row.repo_id),
|
|
3110
|
+
revision: String(row.revision),
|
|
3111
|
+
installPath: String(row.install_path),
|
|
3112
|
+
bytes: Number(row.bytes),
|
|
3113
|
+
files: JSON.parse(String(row.files_json)),
|
|
3114
|
+
status: String(row.status),
|
|
3115
|
+
createdAt: String(row.created_at),
|
|
3116
|
+
updatedAt: String(row.updated_at)
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
function parseInstallRef(input) {
|
|
3120
|
+
try {
|
|
3121
|
+
const trimmed = input.trim();
|
|
3122
|
+
const ref = parseProviderRef(trimmed);
|
|
3123
|
+
return {
|
|
3124
|
+
ref,
|
|
3125
|
+
hasExplicitRevision: trimmed.includes("@")
|
|
3126
|
+
};
|
|
3127
|
+
} catch {
|
|
3128
|
+
return null;
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3103
3131
|
|
|
3104
3132
|
class ModelsStore {
|
|
3105
3133
|
db;
|
|
@@ -3263,25 +3291,21 @@ class ModelsStore {
|
|
|
3263
3291
|
}
|
|
3264
3292
|
listInstalls() {
|
|
3265
3293
|
const rows = this.db.query("SELECT * FROM installs ORDER BY updated_at DESC").all();
|
|
3266
|
-
return rows.map(
|
|
3267
|
-
id: String(row.id),
|
|
3268
|
-
provider: String(row.provider),
|
|
3269
|
-
entityKind: String(row.entity_kind),
|
|
3270
|
-
repoId: String(row.repo_id),
|
|
3271
|
-
revision: String(row.revision),
|
|
3272
|
-
installPath: String(row.install_path),
|
|
3273
|
-
bytes: Number(row.bytes),
|
|
3274
|
-
files: JSON.parse(String(row.files_json)),
|
|
3275
|
-
status: String(row.status),
|
|
3276
|
-
createdAt: String(row.created_at),
|
|
3277
|
-
updatedAt: String(row.updated_at)
|
|
3278
|
-
}));
|
|
3294
|
+
return rows.map(rowToInstall);
|
|
3279
3295
|
}
|
|
3280
3296
|
findInstall(repoIdOrId) {
|
|
3281
3297
|
const row = this.db.query("SELECT * FROM installs WHERE id = ? OR repo_id = ? ORDER BY updated_at DESC LIMIT 1").get(repoIdOrId, repoIdOrId);
|
|
3282
|
-
if (
|
|
3298
|
+
if (row)
|
|
3299
|
+
return rowToInstall(row);
|
|
3300
|
+
const parsed = parseInstallRef(repoIdOrId);
|
|
3301
|
+
if (!parsed)
|
|
3283
3302
|
return null;
|
|
3284
|
-
|
|
3303
|
+
const refRow = parsed.hasExplicitRevision ? this.db.query(`SELECT * FROM installs
|
|
3304
|
+
WHERE provider = ? AND entity_kind = ? AND repo_id = ? AND revision = ?
|
|
3305
|
+
ORDER BY updated_at DESC LIMIT 1`).get(parsed.ref.provider, parsed.ref.entityKind, parsed.ref.repoId, parsed.ref.revision) : this.db.query(`SELECT * FROM installs
|
|
3306
|
+
WHERE provider = ? AND entity_kind = ? AND repo_id = ?
|
|
3307
|
+
ORDER BY updated_at DESC LIMIT 1`).get(parsed.ref.provider, parsed.ref.entityKind, parsed.ref.repoId);
|
|
3308
|
+
return refRow ? rowToInstall(refRow) : null;
|
|
3285
3309
|
}
|
|
3286
3310
|
deleteInstall(id) {
|
|
3287
3311
|
const result = this.db.prepare("DELETE FROM installs WHERE id = ?").run(id);
|
package/dist/index.js
CHANGED
|
@@ -23,8 +23,106 @@ function getInstallRoot() {
|
|
|
23
23
|
return process.env["HASNA_MODELS_INSTALLS"] || join(getModelsHome(), "installs");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// src/ref.ts
|
|
27
|
+
var ENTITY_KINDS = new Set(["model", "dataset", "space"]);
|
|
28
|
+
var PROVIDER_ALIASES = new Map([
|
|
29
|
+
["hf", "huggingface"],
|
|
30
|
+
["huggingface", "huggingface"]
|
|
31
|
+
]);
|
|
32
|
+
function parseEntityKind(value) {
|
|
33
|
+
if (ENTITY_KINDS.has(value))
|
|
34
|
+
return value;
|
|
35
|
+
throw new Error(`Unsupported entity kind: ${value}. Expected one of: ${[...ENTITY_KINDS].join(", ")}`);
|
|
36
|
+
}
|
|
37
|
+
function parseProviderRef(input, defaultKind = "model") {
|
|
38
|
+
const trimmed = input.trim();
|
|
39
|
+
if (!trimmed)
|
|
40
|
+
throw new Error("model or dataset ref is required");
|
|
41
|
+
let provider = "huggingface";
|
|
42
|
+
let entityKind = parseEntityKind(defaultKind);
|
|
43
|
+
let rest = trimmed;
|
|
44
|
+
const prefixMatch = rest.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):(.*)$/);
|
|
45
|
+
if (prefixMatch && !prefixMatch[1].includes("/")) {
|
|
46
|
+
const alias = PROVIDER_ALIASES.get(prefixMatch[1]);
|
|
47
|
+
if (alias) {
|
|
48
|
+
provider = alias;
|
|
49
|
+
rest = prefixMatch[2];
|
|
50
|
+
} else if (["model", "dataset", "space"].includes(prefixMatch[1])) {
|
|
51
|
+
entityKind = parseEntityKind(prefixMatch[1]);
|
|
52
|
+
rest = prefixMatch[2];
|
|
53
|
+
} else {
|
|
54
|
+
throw new Error(`Unsupported provider: ${prefixMatch[1]}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (rest.startsWith("model:")) {
|
|
58
|
+
entityKind = parseEntityKind("model");
|
|
59
|
+
rest = rest.slice("model:".length);
|
|
60
|
+
} else if (rest.startsWith("dataset:")) {
|
|
61
|
+
entityKind = parseEntityKind("dataset");
|
|
62
|
+
rest = rest.slice("dataset:".length);
|
|
63
|
+
} else if (rest.startsWith("space:")) {
|
|
64
|
+
entityKind = parseEntityKind("space");
|
|
65
|
+
rest = rest.slice("space:".length);
|
|
66
|
+
}
|
|
67
|
+
if (rest.includes(":")) {
|
|
68
|
+
throw new Error(`Unsupported ref prefix in: ${input}`);
|
|
69
|
+
}
|
|
70
|
+
let revision = "main";
|
|
71
|
+
const atIndex = rest.lastIndexOf("@");
|
|
72
|
+
if (atIndex > 0) {
|
|
73
|
+
revision = rest.slice(atIndex + 1);
|
|
74
|
+
rest = rest.slice(0, atIndex);
|
|
75
|
+
if (!revision.trim())
|
|
76
|
+
throw new Error(`Revision cannot be empty in ref: ${input}`);
|
|
77
|
+
} else if (rest.endsWith("@")) {
|
|
78
|
+
throw new Error(`Revision cannot be empty in ref: ${input}`);
|
|
79
|
+
}
|
|
80
|
+
if (!rest.includes("/")) {
|
|
81
|
+
throw new Error(`Expected a namespaced repo id such as owner/name, got ${input}`);
|
|
82
|
+
}
|
|
83
|
+
return { provider, entityKind, repoId: rest, revision };
|
|
84
|
+
}
|
|
85
|
+
function formatProviderRef(ref) {
|
|
86
|
+
const prefix = ref.provider === "huggingface" ? "hf" : ref.provider;
|
|
87
|
+
const kind = ref.entityKind === "model" ? "" : `${ref.entityKind}:`;
|
|
88
|
+
return `${prefix}:${kind}${ref.repoId}@${ref.revision}`;
|
|
89
|
+
}
|
|
90
|
+
function encodeRepoId(repoId) {
|
|
91
|
+
return repoId.split("/").map(encodeURIComponent).join("/");
|
|
92
|
+
}
|
|
93
|
+
function safePathSegment(value) {
|
|
94
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "__").replace(/^_+|_+$/g, "") || "unnamed";
|
|
95
|
+
}
|
|
96
|
+
|
|
26
97
|
// src/storage.ts
|
|
27
98
|
var SCHEMA_VERSION = 1;
|
|
99
|
+
function rowToInstall(row) {
|
|
100
|
+
return {
|
|
101
|
+
id: String(row.id),
|
|
102
|
+
provider: String(row.provider),
|
|
103
|
+
entityKind: String(row.entity_kind),
|
|
104
|
+
repoId: String(row.repo_id),
|
|
105
|
+
revision: String(row.revision),
|
|
106
|
+
installPath: String(row.install_path),
|
|
107
|
+
bytes: Number(row.bytes),
|
|
108
|
+
files: JSON.parse(String(row.files_json)),
|
|
109
|
+
status: String(row.status),
|
|
110
|
+
createdAt: String(row.created_at),
|
|
111
|
+
updatedAt: String(row.updated_at)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function parseInstallRef(input) {
|
|
115
|
+
try {
|
|
116
|
+
const trimmed = input.trim();
|
|
117
|
+
const ref = parseProviderRef(trimmed);
|
|
118
|
+
return {
|
|
119
|
+
ref,
|
|
120
|
+
hasExplicitRevision: trimmed.includes("@")
|
|
121
|
+
};
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
28
126
|
|
|
29
127
|
class ModelsStore {
|
|
30
128
|
db;
|
|
@@ -188,25 +286,21 @@ class ModelsStore {
|
|
|
188
286
|
}
|
|
189
287
|
listInstalls() {
|
|
190
288
|
const rows = this.db.query("SELECT * FROM installs ORDER BY updated_at DESC").all();
|
|
191
|
-
return rows.map(
|
|
192
|
-
id: String(row.id),
|
|
193
|
-
provider: String(row.provider),
|
|
194
|
-
entityKind: String(row.entity_kind),
|
|
195
|
-
repoId: String(row.repo_id),
|
|
196
|
-
revision: String(row.revision),
|
|
197
|
-
installPath: String(row.install_path),
|
|
198
|
-
bytes: Number(row.bytes),
|
|
199
|
-
files: JSON.parse(String(row.files_json)),
|
|
200
|
-
status: String(row.status),
|
|
201
|
-
createdAt: String(row.created_at),
|
|
202
|
-
updatedAt: String(row.updated_at)
|
|
203
|
-
}));
|
|
289
|
+
return rows.map(rowToInstall);
|
|
204
290
|
}
|
|
205
291
|
findInstall(repoIdOrId) {
|
|
206
292
|
const row = this.db.query("SELECT * FROM installs WHERE id = ? OR repo_id = ? ORDER BY updated_at DESC LIMIT 1").get(repoIdOrId, repoIdOrId);
|
|
207
|
-
if (
|
|
293
|
+
if (row)
|
|
294
|
+
return rowToInstall(row);
|
|
295
|
+
const parsed = parseInstallRef(repoIdOrId);
|
|
296
|
+
if (!parsed)
|
|
208
297
|
return null;
|
|
209
|
-
|
|
298
|
+
const refRow = parsed.hasExplicitRevision ? this.db.query(`SELECT * FROM installs
|
|
299
|
+
WHERE provider = ? AND entity_kind = ? AND repo_id = ? AND revision = ?
|
|
300
|
+
ORDER BY updated_at DESC LIMIT 1`).get(parsed.ref.provider, parsed.ref.entityKind, parsed.ref.repoId, parsed.ref.revision) : this.db.query(`SELECT * FROM installs
|
|
301
|
+
WHERE provider = ? AND entity_kind = ? AND repo_id = ?
|
|
302
|
+
ORDER BY updated_at DESC LIMIT 1`).get(parsed.ref.provider, parsed.ref.entityKind, parsed.ref.repoId);
|
|
303
|
+
return refRow ? rowToInstall(refRow) : null;
|
|
210
304
|
}
|
|
211
305
|
deleteInstall(id) {
|
|
212
306
|
const result = this.db.prepare("DELETE FROM installs WHERE id = ?").run(id);
|
|
@@ -249,76 +343,6 @@ class ModelsStore {
|
|
|
249
343
|
function createStore(path) {
|
|
250
344
|
return new ModelsStore(path);
|
|
251
345
|
}
|
|
252
|
-
// src/ref.ts
|
|
253
|
-
var ENTITY_KINDS = new Set(["model", "dataset", "space"]);
|
|
254
|
-
var PROVIDER_ALIASES = new Map([
|
|
255
|
-
["hf", "huggingface"],
|
|
256
|
-
["huggingface", "huggingface"]
|
|
257
|
-
]);
|
|
258
|
-
function parseEntityKind(value) {
|
|
259
|
-
if (ENTITY_KINDS.has(value))
|
|
260
|
-
return value;
|
|
261
|
-
throw new Error(`Unsupported entity kind: ${value}. Expected one of: ${[...ENTITY_KINDS].join(", ")}`);
|
|
262
|
-
}
|
|
263
|
-
function parseProviderRef(input, defaultKind = "model") {
|
|
264
|
-
const trimmed = input.trim();
|
|
265
|
-
if (!trimmed)
|
|
266
|
-
throw new Error("model or dataset ref is required");
|
|
267
|
-
let provider = "huggingface";
|
|
268
|
-
let entityKind = parseEntityKind(defaultKind);
|
|
269
|
-
let rest = trimmed;
|
|
270
|
-
const prefixMatch = rest.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):(.*)$/);
|
|
271
|
-
if (prefixMatch && !prefixMatch[1].includes("/")) {
|
|
272
|
-
const alias = PROVIDER_ALIASES.get(prefixMatch[1]);
|
|
273
|
-
if (alias) {
|
|
274
|
-
provider = alias;
|
|
275
|
-
rest = prefixMatch[2];
|
|
276
|
-
} else if (["model", "dataset", "space"].includes(prefixMatch[1])) {
|
|
277
|
-
entityKind = parseEntityKind(prefixMatch[1]);
|
|
278
|
-
rest = prefixMatch[2];
|
|
279
|
-
} else {
|
|
280
|
-
throw new Error(`Unsupported provider: ${prefixMatch[1]}`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
if (rest.startsWith("model:")) {
|
|
284
|
-
entityKind = parseEntityKind("model");
|
|
285
|
-
rest = rest.slice("model:".length);
|
|
286
|
-
} else if (rest.startsWith("dataset:")) {
|
|
287
|
-
entityKind = parseEntityKind("dataset");
|
|
288
|
-
rest = rest.slice("dataset:".length);
|
|
289
|
-
} else if (rest.startsWith("space:")) {
|
|
290
|
-
entityKind = parseEntityKind("space");
|
|
291
|
-
rest = rest.slice("space:".length);
|
|
292
|
-
}
|
|
293
|
-
if (rest.includes(":")) {
|
|
294
|
-
throw new Error(`Unsupported ref prefix in: ${input}`);
|
|
295
|
-
}
|
|
296
|
-
let revision = "main";
|
|
297
|
-
const atIndex = rest.lastIndexOf("@");
|
|
298
|
-
if (atIndex > 0) {
|
|
299
|
-
revision = rest.slice(atIndex + 1);
|
|
300
|
-
rest = rest.slice(0, atIndex);
|
|
301
|
-
if (!revision.trim())
|
|
302
|
-
throw new Error(`Revision cannot be empty in ref: ${input}`);
|
|
303
|
-
} else if (rest.endsWith("@")) {
|
|
304
|
-
throw new Error(`Revision cannot be empty in ref: ${input}`);
|
|
305
|
-
}
|
|
306
|
-
if (!rest.includes("/")) {
|
|
307
|
-
throw new Error(`Expected a namespaced repo id such as owner/name, got ${input}`);
|
|
308
|
-
}
|
|
309
|
-
return { provider, entityKind, repoId: rest, revision };
|
|
310
|
-
}
|
|
311
|
-
function formatProviderRef(ref) {
|
|
312
|
-
const prefix = ref.provider === "huggingface" ? "hf" : ref.provider;
|
|
313
|
-
const kind = ref.entityKind === "model" ? "" : `${ref.entityKind}:`;
|
|
314
|
-
return `${prefix}:${kind}${ref.repoId}@${ref.revision}`;
|
|
315
|
-
}
|
|
316
|
-
function encodeRepoId(repoId) {
|
|
317
|
-
return repoId.split("/").map(encodeURIComponent).join("/");
|
|
318
|
-
}
|
|
319
|
-
function safePathSegment(value) {
|
|
320
|
-
return value.replace(/[^a-zA-Z0-9._-]+/g, "__").replace(/^_+|_+$/g, "") || "unnamed";
|
|
321
|
-
}
|
|
322
346
|
// src/auth.ts
|
|
323
347
|
import { mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
324
348
|
import { dirname as dirname2 } from "path";
|
|
@@ -534,7 +558,7 @@ function safeDestinationPath(root, filePath) {
|
|
|
534
558
|
}
|
|
535
559
|
return destination;
|
|
536
560
|
}
|
|
537
|
-
function normalizeEntry(raw, kind) {
|
|
561
|
+
function normalizeEntry(raw, kind, fallbackRevision = "main") {
|
|
538
562
|
const repoId = String(raw.id ?? raw.modelId ?? "");
|
|
539
563
|
const tags = Array.isArray(raw.tags) ? raw.tags.map(String) : [];
|
|
540
564
|
const cardData = raw.cardData;
|
|
@@ -542,7 +566,7 @@ function normalizeEntry(raw, kind) {
|
|
|
542
566
|
provider: "huggingface",
|
|
543
567
|
entityKind: kind,
|
|
544
568
|
repoId,
|
|
545
|
-
revision: String(raw.sha ??
|
|
569
|
+
revision: String(raw.sha ?? fallbackRevision),
|
|
546
570
|
canonicalUrl: canonicalUrl(kind, repoId),
|
|
547
571
|
title: repoId,
|
|
548
572
|
author: typeof raw.author === "string" ? raw.author : repoId.split("/")[0],
|
|
@@ -625,8 +649,9 @@ async function searchHuggingFace(input = {}) {
|
|
|
625
649
|
}
|
|
626
650
|
async function getHuggingFaceInfo(refOrInput, defaultKind = "model") {
|
|
627
651
|
const ref = typeof refOrInput === "string" ? parseProviderRef(refOrInput, defaultKind) : refOrInput;
|
|
628
|
-
const
|
|
629
|
-
|
|
652
|
+
const revision = ref.revision || "main";
|
|
653
|
+
const raw = await hfJson(`/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}/revision/${encodeURIComponent(revision)}`);
|
|
654
|
+
return normalizeEntry(raw, ref.entityKind, revision);
|
|
630
655
|
}
|
|
631
656
|
async function listHuggingFaceFiles(refOrInput, defaultKind = "model") {
|
|
632
657
|
const ref = typeof refOrInput === "string" ? parseProviderRef(refOrInput, defaultKind) : refOrInput;
|
package/dist/storage.js
CHANGED
|
@@ -23,8 +23,106 @@ function getInstallRoot() {
|
|
|
23
23
|
return process.env["HASNA_MODELS_INSTALLS"] || join(getModelsHome(), "installs");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// src/ref.ts
|
|
27
|
+
var ENTITY_KINDS = new Set(["model", "dataset", "space"]);
|
|
28
|
+
var PROVIDER_ALIASES = new Map([
|
|
29
|
+
["hf", "huggingface"],
|
|
30
|
+
["huggingface", "huggingface"]
|
|
31
|
+
]);
|
|
32
|
+
function parseEntityKind(value) {
|
|
33
|
+
if (ENTITY_KINDS.has(value))
|
|
34
|
+
return value;
|
|
35
|
+
throw new Error(`Unsupported entity kind: ${value}. Expected one of: ${[...ENTITY_KINDS].join(", ")}`);
|
|
36
|
+
}
|
|
37
|
+
function parseProviderRef(input, defaultKind = "model") {
|
|
38
|
+
const trimmed = input.trim();
|
|
39
|
+
if (!trimmed)
|
|
40
|
+
throw new Error("model or dataset ref is required");
|
|
41
|
+
let provider = "huggingface";
|
|
42
|
+
let entityKind = parseEntityKind(defaultKind);
|
|
43
|
+
let rest = trimmed;
|
|
44
|
+
const prefixMatch = rest.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):(.*)$/);
|
|
45
|
+
if (prefixMatch && !prefixMatch[1].includes("/")) {
|
|
46
|
+
const alias = PROVIDER_ALIASES.get(prefixMatch[1]);
|
|
47
|
+
if (alias) {
|
|
48
|
+
provider = alias;
|
|
49
|
+
rest = prefixMatch[2];
|
|
50
|
+
} else if (["model", "dataset", "space"].includes(prefixMatch[1])) {
|
|
51
|
+
entityKind = parseEntityKind(prefixMatch[1]);
|
|
52
|
+
rest = prefixMatch[2];
|
|
53
|
+
} else {
|
|
54
|
+
throw new Error(`Unsupported provider: ${prefixMatch[1]}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (rest.startsWith("model:")) {
|
|
58
|
+
entityKind = parseEntityKind("model");
|
|
59
|
+
rest = rest.slice("model:".length);
|
|
60
|
+
} else if (rest.startsWith("dataset:")) {
|
|
61
|
+
entityKind = parseEntityKind("dataset");
|
|
62
|
+
rest = rest.slice("dataset:".length);
|
|
63
|
+
} else if (rest.startsWith("space:")) {
|
|
64
|
+
entityKind = parseEntityKind("space");
|
|
65
|
+
rest = rest.slice("space:".length);
|
|
66
|
+
}
|
|
67
|
+
if (rest.includes(":")) {
|
|
68
|
+
throw new Error(`Unsupported ref prefix in: ${input}`);
|
|
69
|
+
}
|
|
70
|
+
let revision = "main";
|
|
71
|
+
const atIndex = rest.lastIndexOf("@");
|
|
72
|
+
if (atIndex > 0) {
|
|
73
|
+
revision = rest.slice(atIndex + 1);
|
|
74
|
+
rest = rest.slice(0, atIndex);
|
|
75
|
+
if (!revision.trim())
|
|
76
|
+
throw new Error(`Revision cannot be empty in ref: ${input}`);
|
|
77
|
+
} else if (rest.endsWith("@")) {
|
|
78
|
+
throw new Error(`Revision cannot be empty in ref: ${input}`);
|
|
79
|
+
}
|
|
80
|
+
if (!rest.includes("/")) {
|
|
81
|
+
throw new Error(`Expected a namespaced repo id such as owner/name, got ${input}`);
|
|
82
|
+
}
|
|
83
|
+
return { provider, entityKind, repoId: rest, revision };
|
|
84
|
+
}
|
|
85
|
+
function formatProviderRef(ref) {
|
|
86
|
+
const prefix = ref.provider === "huggingface" ? "hf" : ref.provider;
|
|
87
|
+
const kind = ref.entityKind === "model" ? "" : `${ref.entityKind}:`;
|
|
88
|
+
return `${prefix}:${kind}${ref.repoId}@${ref.revision}`;
|
|
89
|
+
}
|
|
90
|
+
function encodeRepoId(repoId) {
|
|
91
|
+
return repoId.split("/").map(encodeURIComponent).join("/");
|
|
92
|
+
}
|
|
93
|
+
function safePathSegment(value) {
|
|
94
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "__").replace(/^_+|_+$/g, "") || "unnamed";
|
|
95
|
+
}
|
|
96
|
+
|
|
26
97
|
// src/storage.ts
|
|
27
98
|
var SCHEMA_VERSION = 1;
|
|
99
|
+
function rowToInstall(row) {
|
|
100
|
+
return {
|
|
101
|
+
id: String(row.id),
|
|
102
|
+
provider: String(row.provider),
|
|
103
|
+
entityKind: String(row.entity_kind),
|
|
104
|
+
repoId: String(row.repo_id),
|
|
105
|
+
revision: String(row.revision),
|
|
106
|
+
installPath: String(row.install_path),
|
|
107
|
+
bytes: Number(row.bytes),
|
|
108
|
+
files: JSON.parse(String(row.files_json)),
|
|
109
|
+
status: String(row.status),
|
|
110
|
+
createdAt: String(row.created_at),
|
|
111
|
+
updatedAt: String(row.updated_at)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function parseInstallRef(input) {
|
|
115
|
+
try {
|
|
116
|
+
const trimmed = input.trim();
|
|
117
|
+
const ref = parseProviderRef(trimmed);
|
|
118
|
+
return {
|
|
119
|
+
ref,
|
|
120
|
+
hasExplicitRevision: trimmed.includes("@")
|
|
121
|
+
};
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
28
126
|
|
|
29
127
|
class ModelsStore {
|
|
30
128
|
db;
|
|
@@ -188,25 +286,21 @@ class ModelsStore {
|
|
|
188
286
|
}
|
|
189
287
|
listInstalls() {
|
|
190
288
|
const rows = this.db.query("SELECT * FROM installs ORDER BY updated_at DESC").all();
|
|
191
|
-
return rows.map(
|
|
192
|
-
id: String(row.id),
|
|
193
|
-
provider: String(row.provider),
|
|
194
|
-
entityKind: String(row.entity_kind),
|
|
195
|
-
repoId: String(row.repo_id),
|
|
196
|
-
revision: String(row.revision),
|
|
197
|
-
installPath: String(row.install_path),
|
|
198
|
-
bytes: Number(row.bytes),
|
|
199
|
-
files: JSON.parse(String(row.files_json)),
|
|
200
|
-
status: String(row.status),
|
|
201
|
-
createdAt: String(row.created_at),
|
|
202
|
-
updatedAt: String(row.updated_at)
|
|
203
|
-
}));
|
|
289
|
+
return rows.map(rowToInstall);
|
|
204
290
|
}
|
|
205
291
|
findInstall(repoIdOrId) {
|
|
206
292
|
const row = this.db.query("SELECT * FROM installs WHERE id = ? OR repo_id = ? ORDER BY updated_at DESC LIMIT 1").get(repoIdOrId, repoIdOrId);
|
|
207
|
-
if (
|
|
293
|
+
if (row)
|
|
294
|
+
return rowToInstall(row);
|
|
295
|
+
const parsed = parseInstallRef(repoIdOrId);
|
|
296
|
+
if (!parsed)
|
|
208
297
|
return null;
|
|
209
|
-
|
|
298
|
+
const refRow = parsed.hasExplicitRevision ? this.db.query(`SELECT * FROM installs
|
|
299
|
+
WHERE provider = ? AND entity_kind = ? AND repo_id = ? AND revision = ?
|
|
300
|
+
ORDER BY updated_at DESC LIMIT 1`).get(parsed.ref.provider, parsed.ref.entityKind, parsed.ref.repoId, parsed.ref.revision) : this.db.query(`SELECT * FROM installs
|
|
301
|
+
WHERE provider = ? AND entity_kind = ? AND repo_id = ?
|
|
302
|
+
ORDER BY updated_at DESC LIMIT 1`).get(parsed.ref.provider, parsed.ref.entityKind, parsed.ref.repoId);
|
|
303
|
+
return refRow ? rowToInstall(refRow) : null;
|
|
210
304
|
}
|
|
211
305
|
deleteInstall(id) {
|
|
212
306
|
const result = this.db.prepare("DELETE FROM installs WHERE id = ?").run(id);
|