@gjsify/model-store 0.0.0-stage → 0.64.20
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 +55 -2
- package/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/cache-dir.js +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/manifest.js +1 -0
- package/lib/esm/progress.js +1 -0
- package/lib/esm/store.js +1 -0
- package/lib/types/cache-dir.d.ts +13 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/manifest.d.ts +29 -0
- package/lib/types/manifest.spec.d.ts +2 -0
- package/lib/types/progress.d.ts +25 -0
- package/lib/types/store.d.ts +111 -0
- package/lib/types/store.spec.d.ts +2 -0
- package/package.json +81 -5
package/README.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @gjsify/model-store
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Where an AI model lives, whether it is there, and how it gets there: one store per capability (speech, embeddings, image generation), with streaming download, SHA-256 verification, progress in the WebML CG `monitor` shape, and three sources in a fixed order. Pure TypeScript on `node:*` APIs: it runs under plain Node as well as under GJS.
|
|
4
|
+
|
|
5
|
+
Part of the [gjsify](https://github.com/gjsify/gjsify) project. **Tier 3 (experimental, ADR 0003)**: tested against a temporary directory and an injected `fetch`; no test downloads a model. Design: ADR 0132 § 5.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
gjsify install @gjsify/model-store
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
The manifest belongs to the package (or app) that uses the models. Every field is measured from the real file.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { ModelStore, type ModelEntry } from '@gjsify/model-store';
|
|
19
|
+
|
|
20
|
+
const MODELS: readonly ModelEntry[] = [
|
|
21
|
+
{
|
|
22
|
+
id: 'silero-v5',
|
|
23
|
+
file: 'silero_vad_v5.onnx',
|
|
24
|
+
url: 'https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad_v5.onnx',
|
|
25
|
+
bytes: 2313101,
|
|
26
|
+
sha256: '6b99cbfd39246b6706f98ec13c7c50c6b299181f2474fa05cbc8046acc274396',
|
|
27
|
+
license: 'MIT',
|
|
28
|
+
redistributable: true,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const store = new ModelStore({ capability: 'speech', models: MODELS });
|
|
33
|
+
|
|
34
|
+
if (store.availability('silero-v5') === 'downloadable') {
|
|
35
|
+
await store.install('silero-v5', {
|
|
36
|
+
monitor: (m) => m.addEventListener('downloadprogress', (e) => console.log(e.receivedBytes, e.loaded)),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const path = await store.resolve('silero-v5');
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Sources, in order
|
|
43
|
+
|
|
44
|
+
1. **A configured path** — `resolve(id, { path })`, for a model the person already has. It must exist and have the manifest's size; otherwise `locate()` throws instead of silently downloading.
|
|
45
|
+
2. **The app's data dirs** — `appDataDirs`, read-only, for an app that ships a model whose `redistributable` is `true`.
|
|
46
|
+
3. **The cache** — `<user cache>/gjsify/models/<capability>/`, which `install()` downloads into. `<user cache>` is `$XDG_CACHE_HOME` or `~/.cache` as GLib answers it (`%LOCALAPPDATA%` on Windows).
|
|
47
|
+
|
|
48
|
+
`legacyDirs` names directories an older release cached into; a matching file is moved into the cache the first time it is looked up.
|
|
49
|
+
|
|
50
|
+
Digests: a downloaded file is hashed while it streams and only renamed into place once it matches, so the cache is checked by size afterwards. A configured or app-shipped file is hashed by `resolve()` once per process (remembered per size and mtime). `locate()` and `availability()` are synchronous and read no model bytes.
|
|
51
|
+
|
|
52
|
+
The store never answers `unavailable` itself: that is the capability's call (no engine, or `checkFit` from `@gjsify/machine-fit` says `none`).
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{homedir as e}from"node:os";import{isAbsolute as t,join as n}from"node:path";import r from"node:process";function userCacheDir(){let i=r.env.XDG_CACHE_HOME;if(i!==void 0&&i!==``&&t(i))return i;if(r.platform===`win32`){let e=r.env.LOCALAPPDATA;if(e!==void 0&&e!==``)return e}return n(e(),`.cache`)}function modelsRoot(){return n(userCacheDir(),`gjsify`,`models`)}export{modelsRoot,userCacheDir};
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{modelsRoot as e,userCacheDir as t}from"./cache-dir.js";import{validateManifest as n}from"./manifest.js";import{DownloadProgressEvent as r}from"./progress.js";import{ModelStore as i,sha256File as a}from"./store.js";export{r as DownloadProgressEvent,i as ModelStore,e as modelsRoot,a as sha256File,t as userCacheDir,n as validateManifest};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";const e=/^[0-9a-f]{64}$/,t=/^[^/\\]+$/;function validateManifest(n){let r=new Set,i=new Set;for(let a of n){let n=`model '${a.id}'`;if(r.has(a.id))throw Error(`Manifest lists ${n} twice`);if(i.has(a.file))throw Error(`Manifest: ${n} reuses file '${a.file}'`);if(r.add(a.id),i.add(a.file),!t.test(a.file)||a.file===`.`||a.file===`..`)throw Error(`Manifest: ${n} has file '${a.file}', which is not a plain file name`);if(!e.test(a.sha256))throw Error(`Manifest: ${n} has no lowercase hex sha256`);if(!Number.isSafeInteger(a.bytes)||a.bytes<=0)throw Error(`Manifest: ${n} has size ${a.bytes}`);if(!a.url.startsWith(`https://`))throw Error(`Manifest: ${n} is not fetched over https`);if(a.license.length===0)throw Error(`Manifest: ${n} names no licence`)}}export{validateManifest};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";var DownloadProgressEvent=class extends Event{modelId;receivedBytes;totalBytes;loaded;total=1;lengthComputable=!0;constructor(e,t,n){super(`downloadprogress`),this.modelId=e,this.receivedBytes=t,this.totalBytes=n,this.loaded=n===0?1:t/n}},ProgressFanout=class{modelId;#e=new Set;#t=-1;constructor(e){this.modelId=e}add(e){if(e===void 0)return;let t=new EventTarget;e(t),this.#e.add(t)}report(e,t){let n=t===0?1:e/t;if(!(!(e===0||e===t)&&n-this.#t<.001)&&n!==this.#t){this.#t=n;for(let n of this.#e)n.dispatchEvent(new DownloadProgressEvent(this.modelId,e,t))}}};export{DownloadProgressEvent,ProgressFanout};
|
package/lib/esm/store.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{modelsRoot as e}from"./cache-dir.js";import{validateManifest as t}from"./manifest.js";import{ProgressFanout as n}from"./progress.js";import{join as r}from"node:path";import{createHash as i,randomUUID as a}from"node:crypto";import{closeSync as o,copyFileSync as s,mkdirSync as c,openSync as l,renameSync as u,statSync as d,unlinkSync as f,writeSync as p}from"node:fs";import{open as m}from"node:fs/promises";const h=/^[a-z0-9][a-z0-9-]*$/;function sizeOf(e){try{let t=d(e);return t.isFile()?t.size:null}catch{return null}}function removeQuietly(e){try{f(e)}catch{}}async function sha256File(e,t){let n=i(`sha256`),r=await m(e,`r`);try{let e=new Uint8Array(1024*1024);for(;;){t?.throwIfAborted();let{bytesRead:i}=await r.read(e,0,e.length,null);if(i===0)break;n.update(e.subarray(0,i))}}finally{await r.close()}return n.digest(`hex`)}var ModelStore=class{capability;models;defaultModel;#e;#t;#n;#r;#i=new Map;#a=new Map;constructor(e){if(!h.test(e.capability))throw Error(`ModelStore: capability '${e.capability}' is not a lowercase directory name`);if(e.models.length===0)throw Error(`ModelStore '${e.capability}': no models`);t(e.models),this.capability=e.capability,this.models=e.models,this.defaultModel=e.defaultModel??e.models[0].id,this.#e=e.root,this.#t=e.appDataDirs??[];let n=e.legacyDirs??[];this.#n=typeof n==`function`?n:()=>n,this.#r=e.fetch,this.require(this.defaultModel)}get cacheDir(){return r(this.#e??e(),this.capability)}find(e=this.defaultModel){return this.models.find(t=>t.id===e)}require(e=this.defaultModel){let t=this.find(e);if(t===void 0){let t=this.models.map(e=>e.id).join(`, `);throw RangeError(`Unknown ${this.capability} model '${e}' — known ids: ${t}`)}return t}cachePath(e=this.defaultModel){return r(this.cacheDir,this.require(e).file)}locate(e=this.defaultModel,t={}){let n=this.require(e);if(t.path!==void 0){let e=sizeOf(t.path);if(e!==n.bytes){let r=e===null?`no file`:`${e} bytes`;throw Error(`${this.capability} model '${n.id}': ${t.path} has ${r}, expected ${n.bytes}`)}return{path:t.path,source:`configured`}}for(let e of this.#t){let t=r(e,n.file);if(sizeOf(t)===n.bytes)return{path:t,source:`app-data`}}let i=r(this.cacheDir,n.file);if(sizeOf(i)===n.bytes||this.#o(n,i))return{path:i,source:`cache`}}availability(e=this.defaultModel,t={}){let n=this.require(e);return this.locate(n.id,t)===void 0?this.#i.has(n.id)?`downloading`:`downloadable`:`available`}installed(){return this.models.filter(e=>this.locate(e.id)!==void 0).map(e=>e.id)}async resolve(e=this.defaultModel,t={}){let n=this.require(e),r=this.locate(n.id,t);if(r===void 0)throw Error(`${this.capability} model '${n.id}' is not installed — await install('${n.id}') first. ${n.bytes} bytes from ${n.url}.`);return r.source!==`cache`&&await this.#s(n,r.path,t.signal),r.path}install(e=this.defaultModel,t={}){let r;try{if(r=this.require(e),!t.force&&this.locate(r.id,t)!==void 0){let e=new n(r.id);return e.add(t.monitor),this.resolve(r.id,t).then(t=>(e.report(r.bytes,r.bytes),t))}}catch(e){return Promise.reject(e)}let i=this.#i.get(r.id);if(i!==void 0)return i.progress.add(t.monitor),i.promise;let a=new n(r.id);a.add(t.monitor);let o=this.#c(r,a,t).finally(()=>this.#i.delete(r.id));return this.#i.set(r.id,{promise:o,progress:a}),o}uninstall(e=this.defaultModel){let t=this.cachePath(e);return sizeOf(t)===null?!1:(f(t),!0)}#o(e,t){for(let n of this.#n()){let i=r(n,e.file);if(sizeOf(i)===e.bytes)try{c(this.cacheDir,{recursive:!0});try{u(i,t)}catch{let e=`${t}.part-${a()}`;try{s(i,e),u(e,t)}catch(t){throw removeQuietly(e),t}removeQuietly(i)}return!0}catch{return!1}}return!1}async#s(e,t,n){let r=d(t),i=`${r.size}:${r.mtimeMs}`;if(this.#a.get(t)===i)return;let a=await sha256File(t,n);if(a!==e.sha256)throw Error(`${this.capability} model '${e.id}': ${t} has sha256 ${a}, expected ${e.sha256}`);this.#a.set(t,i)}async#c(e,t,n){let s=n.signal,d=await(n.fetch??this.#r??((e,t)=>fetch(e,t)))(e.url,{signal:s,redirect:`follow`});if(!d.ok)throw Error(`Downloading ${e.id} from ${e.url} failed: HTTP ${d.status}`);let f=d.headers.get(`content-length`);if(f!==null&&Number(f)!==e.bytes)throw Error(`Downloading ${e.id} refused: ${e.url} declares ${f} bytes, the manifest says ${e.bytes}`);let m=this.cacheDir,h=r(m,e.file);c(m,{recursive:!0});let g=`${h}.part-${a()}`,_=l(g,`w`),v=!1;try{let n=i(`sha256`),r=0,write=i=>{if(r+=i.length,r>e.bytes)throw Error(`Downloading ${e.id} failed: more than the manifest's ${e.bytes} bytes`);n.update(i),p(_,i),t.report(r,e.bytes)};t.report(0,e.bytes);let a=d.body?.getReader();if(a!==void 0)try{for(;;){s?.throwIfAborted();let{done:e,value:t}=await a.read();if(e)break;t!==void 0&&t.length>0&&write(t)}}catch(e){throw await a.cancel().catch(()=>void 0),e}else write(new Uint8Array(await d.arrayBuffer()));if(r!==e.bytes)throw Error(`Downloading ${e.id} failed: got ${r} bytes, expected ${e.bytes}`);let c=n.digest(`hex`);if(c!==e.sha256)throw Error(`Downloading ${e.id} failed: sha256 ${c}, expected ${e.sha256}`);return o(_),v=!0,u(g,h),t.report(e.bytes,e.bytes),h}catch(e){throw v||o(_),removeQuietly(g),e}}};export{ModelStore,sha256File};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-user cache directory, as `GLib.get_user_cache_dir()` answers it on
|
|
3
|
+
* Linux and macOS: `$XDG_CACHE_HOME` when it is an absolute path, else
|
|
4
|
+
* `~/.cache`. Flatpak sets `XDG_CACHE_HOME` to the sandbox's own cache, so a
|
|
5
|
+
* Flatpak app lands in `~/.var/app/<id>/cache` like every GLib app does.
|
|
6
|
+
*
|
|
7
|
+
* Read from the environment instead of GLib so the package runs under plain
|
|
8
|
+
* Node too. On Windows GLib answers the Internet cache folder; this answers
|
|
9
|
+
* `%LOCALAPPDATA%`, which is where Windows apps keep caches.
|
|
10
|
+
*/
|
|
11
|
+
export declare function userCacheDir(): string;
|
|
12
|
+
/** `<user cache>/gjsify/models` — the root every capability's directory sits under. */
|
|
13
|
+
export declare function modelsRoot(): string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { modelsRoot, userCacheDir } from './cache-dir.js';
|
|
2
|
+
export { validateManifest, type ModelEntry } from './manifest.js';
|
|
3
|
+
export { DownloadProgressEvent, type CreateMonitorCallback } from './progress.js';
|
|
4
|
+
export { ModelStore, sha256File, type FetchLike, type InstallOptions, type LocatedModel, type LocateOptions, type ModelAvailability, type ModelSource, type ModelStoreOptions, type ResolveOptions, } from './store.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** One fetchable model. Capability packages extend it with their own fields. */
|
|
2
|
+
export interface ModelEntry {
|
|
3
|
+
/** Stable id, unique within one store. */
|
|
4
|
+
readonly id: string;
|
|
5
|
+
/** File name inside the cache directory. Upstream's own name, so a hand-placed file matches. */
|
|
6
|
+
readonly file: string;
|
|
7
|
+
/** Where it is fetched from: upstream, never a mirror of ours (ADR 0132 § 5). */
|
|
8
|
+
readonly url: string;
|
|
9
|
+
/** Exact size in bytes. */
|
|
10
|
+
readonly bytes: number;
|
|
11
|
+
/** SHA-256 of the file, lowercase hex. */
|
|
12
|
+
readonly sha256: string;
|
|
13
|
+
/** SPDX id of the MODEL's licence, which is not the package's. */
|
|
14
|
+
readonly license: string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the licence allows redistributing the file. An app may ship a
|
|
17
|
+
* model in its data dir only when this is `true`; the store fetches from
|
|
18
|
+
* `url` either way.
|
|
19
|
+
*/
|
|
20
|
+
readonly redistributable: boolean;
|
|
21
|
+
/** One line for a settings panel or a download prompt. */
|
|
22
|
+
readonly description?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Throws on the manifest mistakes that would otherwise surface as a refused
|
|
26
|
+
* download much later: a duplicate id or file, a malformed digest, a size of
|
|
27
|
+
* zero, a non-https URL, or a file name that would escape the cache dir.
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateManifest(models: readonly ModelEntry[]): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The `monitor` option of every capability's `create()` and of `install()`. */
|
|
2
|
+
export type CreateMonitorCallback = (monitor: EventTarget) => void;
|
|
3
|
+
/** `downloadprogress`: `loaded` is 0…1 as in the WebML CG APIs; `receivedBytes`/`totalBytes` are ours. */
|
|
4
|
+
export declare class DownloadProgressEvent extends Event {
|
|
5
|
+
readonly modelId: string;
|
|
6
|
+
readonly receivedBytes: number;
|
|
7
|
+
readonly totalBytes: number;
|
|
8
|
+
readonly loaded: number;
|
|
9
|
+
readonly total = 1;
|
|
10
|
+
readonly lengthComputable = true;
|
|
11
|
+
constructor(modelId: string, receivedBytes: number, totalBytes: number);
|
|
12
|
+
}
|
|
13
|
+
/** Fans one download's progress out to every caller waiting on it. */
|
|
14
|
+
export declare class ProgressFanout {
|
|
15
|
+
#private;
|
|
16
|
+
readonly modelId: string;
|
|
17
|
+
constructor(modelId: string);
|
|
18
|
+
add(monitor: CreateMonitorCallback | undefined): void;
|
|
19
|
+
/**
|
|
20
|
+
* Fires at 0, at 1, and between them only when the fraction moved by at
|
|
21
|
+
* least 0.1 % — a 548 MB download in 64 KiB chunks would otherwise fire
|
|
22
|
+
* eight thousand events for a progress bar that shows a thousand states.
|
|
23
|
+
*/
|
|
24
|
+
report(receivedBytes: number, totalBytes: number): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { type ModelEntry } from './manifest.js';
|
|
2
|
+
import { type CreateMonitorCallback } from './progress.js';
|
|
3
|
+
/**
|
|
4
|
+
* The WebML CG availability states. The store itself never answers
|
|
5
|
+
* `unavailable` — that is the capability's verdict (no engine, or `checkFit`
|
|
6
|
+
* says `none`) — but the type is shared so the capability can return either.
|
|
7
|
+
*/
|
|
8
|
+
export type ModelAvailability = 'available' | 'downloading' | 'downloadable' | 'unavailable';
|
|
9
|
+
/** Where {@link ModelStore.locate} found a model. */
|
|
10
|
+
export type ModelSource = 'configured' | 'app-data' | 'cache';
|
|
11
|
+
export interface LocatedModel {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly source: ModelSource;
|
|
14
|
+
}
|
|
15
|
+
/** The subset of `fetch` the store uses, so a test can inject one. */
|
|
16
|
+
export type FetchLike = (url: string, init: {
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
redirect?: 'follow';
|
|
19
|
+
}) => Promise<{
|
|
20
|
+
ok: boolean;
|
|
21
|
+
status: number;
|
|
22
|
+
headers: {
|
|
23
|
+
get(name: string): string | null;
|
|
24
|
+
};
|
|
25
|
+
body?: {
|
|
26
|
+
getReader(): {
|
|
27
|
+
read(): Promise<{
|
|
28
|
+
done: boolean;
|
|
29
|
+
value?: Uint8Array;
|
|
30
|
+
}>;
|
|
31
|
+
cancel(): Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
} | null;
|
|
34
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
35
|
+
}>;
|
|
36
|
+
export interface ModelStoreOptions<E extends ModelEntry> {
|
|
37
|
+
/** Directory name under the models root: `speech`, `embeddings`, `image`. Lowercase, digits, dashes. */
|
|
38
|
+
readonly capability: string;
|
|
39
|
+
readonly models: readonly E[];
|
|
40
|
+
/** The id `undefined` means. Default: the first entry. */
|
|
41
|
+
readonly defaultModel?: string;
|
|
42
|
+
/** Override `<user cache>/gjsify/models`. Tests, or an app that keeps models elsewhere. */
|
|
43
|
+
readonly root?: string;
|
|
44
|
+
/** Directories an app ships models in, read-only. Searched before the cache. */
|
|
45
|
+
readonly appDataDirs?: readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* Directories an older release cached into. A match is moved into the
|
|
48
|
+
* cache once. A function is called per lookup, so a directory derived from
|
|
49
|
+
* the user cache follows a changed `XDG_CACHE_HOME` the way `cacheDir` does.
|
|
50
|
+
*/
|
|
51
|
+
readonly legacyDirs?: readonly string[] | (() => readonly string[]);
|
|
52
|
+
/** Override the global `fetch`. */
|
|
53
|
+
readonly fetch?: FetchLike;
|
|
54
|
+
}
|
|
55
|
+
export interface LocateOptions {
|
|
56
|
+
/** A file the app was told to use for this id. Must exist and have the manifest's size. */
|
|
57
|
+
readonly path?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface ResolveOptions extends LocateOptions {
|
|
60
|
+
readonly signal?: AbortSignal;
|
|
61
|
+
}
|
|
62
|
+
export interface InstallOptions extends ResolveOptions {
|
|
63
|
+
/** Receives an EventTarget firing `downloadprogress`, at least at 0 and 1. */
|
|
64
|
+
readonly monitor?: CreateMonitorCallback;
|
|
65
|
+
/** Re-fetch into the cache even when a source already has the model. */
|
|
66
|
+
readonly force?: boolean;
|
|
67
|
+
/** Override the store's `fetch` for this call. */
|
|
68
|
+
readonly fetch?: FetchLike;
|
|
69
|
+
}
|
|
70
|
+
/** Hex SHA-256 of a file, read in 1 MiB slices so a 548 MB model never sits in memory. */
|
|
71
|
+
export declare function sha256File(path: string, signal?: AbortSignal): Promise<string>;
|
|
72
|
+
export declare class ModelStore<E extends ModelEntry = ModelEntry> {
|
|
73
|
+
#private;
|
|
74
|
+
readonly capability: string;
|
|
75
|
+
readonly models: readonly E[];
|
|
76
|
+
readonly defaultModel: string;
|
|
77
|
+
constructor(options: ModelStoreOptions<E>);
|
|
78
|
+
/** `<root>/<capability>`. Resolved per call, so a changed `XDG_CACHE_HOME` is honoured. */
|
|
79
|
+
get cacheDir(): string;
|
|
80
|
+
find(id?: string): E | undefined;
|
|
81
|
+
/** The entry for `id`, or a `RangeError` naming the ids that exist. */
|
|
82
|
+
require(id?: string): E;
|
|
83
|
+
/** Where `id` is cached, whether or not it is there yet. */
|
|
84
|
+
cachePath(id?: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* Where `id` is, by size only. Synchronous, reads no model bytes, never
|
|
87
|
+
* downloads. Moves a file from a legacy directory into the cache on the
|
|
88
|
+
* way. Throws for a configured path that is missing or has the wrong size:
|
|
89
|
+
* the app said the person has this file, so falling through to a download
|
|
90
|
+
* would be the surprise.
|
|
91
|
+
*/
|
|
92
|
+
locate(id?: string, options?: LocateOptions): LocatedModel | undefined;
|
|
93
|
+
/** `available` when a source has it, `downloading` while `install()` runs, else `downloadable`. */
|
|
94
|
+
availability(id?: string, options?: LocateOptions): ModelAvailability;
|
|
95
|
+
/** Ids some source has, in manifest order. */
|
|
96
|
+
installed(): string[];
|
|
97
|
+
/**
|
|
98
|
+
* The verified path of `id`. Never downloads: rejects when no source has
|
|
99
|
+
* it, and when a configured or app-shipped file fails its digest.
|
|
100
|
+
*/
|
|
101
|
+
resolve(id?: string, options?: ResolveOptions): Promise<string>;
|
|
102
|
+
/**
|
|
103
|
+
* Make `id` usable, fetching it into the cache when no source has it.
|
|
104
|
+
* Resolves to its verified path. Two overlapping calls share one download
|
|
105
|
+
* and both get progress; aborting either caller's signal aborts it for both.
|
|
106
|
+
* Every failure is a rejection, an unknown id included.
|
|
107
|
+
*/
|
|
108
|
+
install(id?: string, options?: InstallOptions): Promise<string>;
|
|
109
|
+
/** Delete the CACHED file of `id` — never a configured or app-shipped one. `false` when none was there. */
|
|
110
|
+
uninstall(id?: string): boolean;
|
|
111
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@gjsify/model-store",
|
|
3
|
+
"version": "0.64.20",
|
|
4
|
+
"description": "Where an AI model lives and how it gets there: one store per capability with streaming download, SHA-256 verification, WebML-shaped download progress, and three sources in order (a configured path, an app's read-only data dir, the per-user cache). Pure TypeScript on node:* APIs.",
|
|
5
|
+
"module": "lib/esm/index.js",
|
|
6
|
+
"types": "lib/types/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/types/index.d.ts",
|
|
12
|
+
"default": "./lib/esm/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"lib",
|
|
17
|
+
"!lib/types/**/*.spec.d.ts"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"clear": "gjsify clear lib tmp tsconfig.tsbuildinfo test.gjs.mjs test.node.mjs",
|
|
21
|
+
"check": "gjsify tsc --noEmit",
|
|
22
|
+
"build": "gjsify run build:gjsify && gjsify run build:types",
|
|
23
|
+
"build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
|
|
24
|
+
"build:types": "gjsify tsc",
|
|
25
|
+
"build:test": "gjsify run build:test:gjs && gjsify run build:test:node",
|
|
26
|
+
"build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
|
|
27
|
+
"build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
|
|
28
|
+
"test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:node && gjsify run test:gjs",
|
|
29
|
+
"test:gjs": "gjsify run test.gjs.mjs",
|
|
30
|
+
"test:node": "node test.node.mjs"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"model",
|
|
34
|
+
"cache",
|
|
35
|
+
"download",
|
|
36
|
+
"sha256",
|
|
37
|
+
"ai",
|
|
38
|
+
"onnx",
|
|
39
|
+
"gguf"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@gjsify/crypto": "^0.64.20",
|
|
43
|
+
"@gjsify/fs": "^0.64.20",
|
|
44
|
+
"@gjsify/os": "^0.64.20",
|
|
45
|
+
"@gjsify/path": "^0.64.20",
|
|
46
|
+
"@gjsify/process": "^0.64.20"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@gjsify/cli": "^0.64.20",
|
|
50
|
+
"@gjsify/unit": "^0.64.20",
|
|
51
|
+
"@types/node": "^25.9.2",
|
|
52
|
+
"typescript": "^6.0.3"
|
|
53
|
+
},
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "git+https://github.com/gjsify/gjsify.git",
|
|
58
|
+
"directory": "packages/gjs/model-store"
|
|
59
|
+
},
|
|
60
|
+
"bugs": {
|
|
61
|
+
"url": "https://github.com/gjsify/gjsify/issues"
|
|
62
|
+
},
|
|
63
|
+
"homepage": "https://github.com/gjsify/gjsify/tree/main/packages/gjs/model-store#readme",
|
|
64
|
+
"gjsify": {
|
|
65
|
+
"tier": 3,
|
|
66
|
+
"runtimes": {
|
|
67
|
+
"gjs": "polyfill",
|
|
68
|
+
"node": "none",
|
|
69
|
+
"browser": "none",
|
|
70
|
+
"nativescript": "none"
|
|
71
|
+
},
|
|
72
|
+
"os": {
|
|
73
|
+
"linux": "supported",
|
|
74
|
+
"darwin": "supported",
|
|
75
|
+
"win32": "partial"
|
|
76
|
+
},
|
|
77
|
+
"osNotes": {
|
|
78
|
+
"win32": "The cache root is %LOCALAPPDATA%\\gjsify\\models, where GLib's get_user_cache_dir() would answer the Internet cache folder."
|
|
79
|
+
},
|
|
80
|
+
"headless": true
|
|
81
|
+
}
|
|
82
|
+
}
|