@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 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 ?? "main"),
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 raw = await hfJson(`/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}`);
3005
- return normalizeEntry(raw, ref.entityKind);
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((row) => ({
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 (!row)
3298
+ if (row)
3299
+ return rowToInstall(row);
3300
+ const parsed = parseInstallRef(repoIdOrId);
3301
+ if (!parsed)
3283
3302
  return null;
3284
- 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;
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((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";
@@ -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 ?? "main"),
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 raw = await hfJson(`/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}`);
629
- return normalizeEntry(raw, ref.entityKind);
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((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.5",
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",