File size: 3,016 Bytes
1944112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
 * IndexedDB StorageAdapter — the default for an ordinary page.
 *
 * IndexedDB rather than `localStorage` for two reasons that both matter here:
 * `localStorage` is synchronous and blocks the thread that is also driving
 * WebGPU, and it does not exist in a worker. The registry itself is small (one
 * record per model, a few dozen cache URLs each), so this is a key/value store
 * and nothing more.
 *
 * `persist()` is exported alongside because the page origin, unlike an
 * extension with `unlimitedStorage`, holds a multi-GB model in *evictable*
 * storage until persistence is granted. See ARCHIVE.md, "Zero-download".
 */
const DB_NAME = "everything-webgpu";
const DB_VERSION = 1;
const STORE = "kv";

function open() {
  return new Promise((resolve, reject) => {
    const req = indexedDB.open(DB_NAME, DB_VERSION);
    req.onupgradeneeded = () => req.result.createObjectStore(STORE);
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}

function run(db, mode, fn) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction(STORE, mode);
    const out = fn(tx.objectStore(STORE));
    tx.oncomplete = () => resolve(out);
    tx.onerror = () => reject(tx.error);
    tx.onabort = () => reject(tx.error);
  });
}

const get1 = (store, key) =>
  new Promise((resolve, reject) => {
    const req = store.get(key);
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });

/** @returns {Promise<import("../engine/model-store.js").StorageAdapter>} */
export async function indexedDBStorage() {
  const db = await open();
  return {
    async get(key) {
      const keys = Array.isArray(key) ? key : [key];
      const out = {};
      await run(db, "readonly", async (store) => {
        for (const k of keys) {
          const value = await get1(store, k);
          if (value !== undefined) out[k] = value;
        }
      });
      return out;
    },
    async set(items) {
      await run(db, "readwrite", (store) => {
        for (const [k, v] of Object.entries(items)) store.put(v, k);
      });
    },
  };
}

/**
 * Ask the browser to stop treating this origin's storage as evictable.
 *
 * Firefox prompts the user (or grants silently for a site with the permission
 * already); Chrome decides from engagement heuristics without a prompt. Either
 * way a caller must handle `persisted: false` — the model still works, it can
 * just be dropped under disk pressure, which `ModelStore.verify()` will catch
 * on the next load.
 *
 * @returns {Promise<{persisted: boolean, quota?: number, usage?: number}>}
 */
export async function ensurePersistent() {
  const storage = globalThis.navigator?.storage;
  if (!storage?.persist) return { persisted: false };
  const persisted = (await storage.persisted?.()) || (await storage.persist());
  const estimate = await storage.estimate?.().catch(() => ({}));
  return { persisted: Boolean(persisted), quota: estimate?.quota, usage: estimate?.usage };
}