@hasna/models 0.0.6 → 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 CHANGED
@@ -3101,6 +3101,33 @@ import { mkdirSync as mkdirSync3 } from "fs";
3101
3101
  import { dirname as dirname4 } from "path";
3102
3102
  import { Database } from "bun:sqlite";
3103
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
+ }
3104
3131
 
3105
3132
  class ModelsStore {
3106
3133
  db;
@@ -3264,25 +3291,21 @@ class ModelsStore {
3264
3291
  }
3265
3292
  listInstalls() {
3266
3293
  const rows = this.db.query("SELECT * FROM installs ORDER BY updated_at DESC").all();
3267
- return rows.map((row) => ({
3268
- id: String(row.id),
3269
- provider: String(row.provider),
3270
- entityKind: String(row.entity_kind),
3271
- repoId: String(row.repo_id),
3272
- revision: String(row.revision),
3273
- installPath: String(row.install_path),
3274
- bytes: Number(row.bytes),
3275
- files: JSON.parse(String(row.files_json)),
3276
- status: String(row.status),
3277
- createdAt: String(row.created_at),
3278
- updatedAt: String(row.updated_at)
3279
- }));
3294
+ return rows.map(rowToInstall);
3280
3295
  }
3281
3296
  findInstall(repoIdOrId) {
3282
3297
  const row = this.db.query("SELECT * FROM installs WHERE id = ? OR repo_id = ? ORDER BY updated_at DESC LIMIT 1").get(repoIdOrId, repoIdOrId);
3283
- if (!row)
3298
+ if (row)
3299
+ return rowToInstall(row);
3300
+ const parsed = parseInstallRef(repoIdOrId);
3301
+ if (!parsed)
3284
3302
  return null;
3285
- return this.listInstalls().find((install) => install.id === row.id) ?? null;
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;
3286
3309
  }
3287
3310
  deleteInstall(id) {
3288
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((row) => ({
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 (!row)
293
+ if (row)
294
+ return rowToInstall(row);
295
+ const parsed = parseInstallRef(repoIdOrId);
296
+ if (!parsed)
208
297
  return null;
209
- return this.listInstalls().find((install) => install.id === row.id) ?? null;
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";
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((row) => ({
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 (!row)
293
+ if (row)
294
+ return rowToInstall(row);
295
+ const parsed = parseInstallRef(repoIdOrId);
296
+ if (!parsed)
208
297
  return null;
209
- return this.listInstalls().find((install) => install.id === row.id) ?? null;
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/models",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "CLI-first local model and dataset lifecycle tool for open-source/open-weight catalogs",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",