Humuhumu33 commited on
Commit
60e54d3
Β·
verified Β·
1 Parent(s): b756153

holo-evicted-publish: player +2 object(s)

Browse files
b/2402cb6992ea83c6853b7ed37f2bc17101ca1c6923d1a0b975a3c8fcabe0e8f4 ADDED
The diff for this file is too large to render. See raw diff
 
b/9ce7bf36b3de9318775510922233e762acacd3c259c630bd0c6b9d9d9369477f ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // holo-library.mjs β€” THE PLANE: the ΞΊ-addressed universe catalog at runtime (HOLO-TV-LIBRARY-PROMPT.md L2).
2
+ //
3
+ // A lean resolver over the forge's shards: the tiny root (feed/library-index.json) names every shard by
4
+ // TWO hashes β€” blake3 (the ΞΊ-mirror path) and sha256 (the verify key). Reads are verify-or-refuse (Law
5
+ // L5): bytes are cached raw in CacheStorage and RE-HASHED on every read; a tampered shard is refused by
6
+ // name, never parsed. Once cached, the whole universe browses OFFLINE and every facet pivot is a plain
7
+ // in-memory scan over compact rows β€” ~19k titles filter in well under a frame, no bitset machinery needed.
8
+ //
9
+ // Dependency-injected (fetch + cache + sha256) so Node witnesses it with fake shards β€” no network.
10
+ // Browser binding: window.HoloLibrary.live() β†’ { load, query, search, person, personsIndex, rows, root }.
11
+
12
+ // createLibrary({ root, fetch, cacheGet, cachePut, sha256hex })
13
+ // root : the parsed library-index.json (or null β†’ loadRoot() fetches it)
14
+ // cacheGet : async (sha) β†’ Uint8Array | null cachePut: async (sha, bytes) β†’ void
15
+ // sha256hex : async (Uint8Array) β†’ hex
16
+ export function createLibrary({ root = null, fetch: f, cacheGet, cachePut, sha256hex, rootUrl = "feed/library-index.json" } = {}) {
17
+ const doFetch = f || (typeof fetch !== "undefined" ? fetch.bind(globalThis) : null);
18
+ if (!doFetch) throw new Error("holo-library: fetch required");
19
+ let R = root, rows = [], byKey = new Map(), loaded = false, personsCache = null, searchIdx = null;
20
+ const stats = { shardsFromCache: 0, shardsFromNet: 0, refused: 0 };
21
+
22
+ async function loadRoot() {
23
+ if (R) return R;
24
+ try { R = await (await doFetch(rootUrl + "?v=" + Date.now(), { cache: "no-store" })).json(); }
25
+ catch { R = await (await doFetch(rootUrl)).json(); } // offline: SW/HTTP cache still answers
26
+ return R;
27
+ }
28
+ // verify-or-refuse read of ONE named object { sha256, blake3 }
29
+ async function readObject(ref, name) {
30
+ if (cacheGet) {
31
+ const c = await cacheGet(ref.sha256);
32
+ if (c) {
33
+ if ((await sha256hex(c)) === ref.sha256) { stats.shardsFromCache++; return c; }
34
+ stats.refused++; console.warn("holo-library: REFUSED tampered cache object", name, ref.sha256.slice(0, 12));
35
+ }
36
+ }
37
+ const res = await doFetch(R.mirror + ref.blake3);
38
+ if (!res.ok) throw new Error("holo-library: mirror " + res.status + " for " + name);
39
+ const bytes = new Uint8Array(await res.arrayBuffer());
40
+ if ((await sha256hex(bytes)) !== ref.sha256) { stats.refused++; throw new Error("holo-library: REFUSED " + name + " β€” bytes do not re-derive"); }
41
+ stats.shardsFromNet++;
42
+ if (cachePut) await cachePut(ref.sha256, bytes);
43
+ return bytes;
44
+ }
45
+ const dec = (u8) => JSON.parse(new TextDecoder().decode(u8));
46
+
47
+ // load(onShard) β€” pull all shards (parallel), verify each, build the in-memory plane.
48
+ async function load(onShard = null) {
49
+ if (loaded) return rows;
50
+ await loadRoot();
51
+ const parts = await Promise.all(R.shards.map(async (ref, n) => {
52
+ const s = dec(await readObject(ref, "shard" + n));
53
+ if (onShard) try { onShard(n, s.rows.length); } catch {}
54
+ return s.rows;
55
+ }));
56
+ rows = parts.flat();
57
+ byKey = new Map(rows.map((r) => [r.k + r.i, r]));
58
+ loaded = true;
59
+ return rows;
60
+ }
61
+
62
+ // ── facet algebra β€” one scan, every dimension composable ────────────────────────────────────────────────
63
+ // q: { kind:"m"|"t", genres:[ids], moods:[keys], decades:[1980,…], runtimeMax, runtimeMin, ratingMin,
64
+ // votesMax (hidden gems), lang, person (id), exclude:Set(keys), limit, sort:"top"|"new"|"old"|"az" }
65
+ function query(q = {}) {
66
+ let list = rows;
67
+ if (q.person != null) { const p = personsCache && personsCache[q.person]; list = p ? p.t.map((k) => byKey.get(k)).filter(Boolean) : []; }
68
+ const out = [];
69
+ for (const r of list) {
70
+ if (q.kind && r.k !== q.kind) continue;
71
+ if (q.genres && q.genres.length && !q.genres.some((g) => r.g.includes(g))) continue;
72
+ if (q.moods && q.moods.length && !q.moods.some((m) => r.m.includes(m))) continue;
73
+ if (q.decades && q.decades.length && !q.decades.includes(r.dec)) continue;
74
+ if (q.runtimeMax && !(r.rt && r.rt <= q.runtimeMax)) continue;
75
+ if (q.runtimeMin && !(r.rt && r.rt >= q.runtimeMin)) continue;
76
+ if (q.ratingMin && !(r.r >= q.ratingMin)) continue;
77
+ if (q.votesMax && !(r.v <= q.votesMax)) continue;
78
+ if (q.lang && r.l !== q.lang) continue;
79
+ if (q.exclude && q.exclude.has(r.k + r.i)) continue;
80
+ out.push(r);
81
+ }
82
+ const score = (r) => r.r * Math.log10(1 + r.v); // quality Γ— confidence
83
+ if (q.sort === "az") out.sort((a, b) => a.t.localeCompare(b.t));
84
+ else if (q.sort === "new") out.sort((a, b) => b.y - a.y || score(b) - score(a));
85
+ else if (q.sort === "old") out.sort((a, b) => a.y - b.y || score(b) - score(a));
86
+ else out.sort((a, b) => score(b) - score(a));
87
+ return q.limit ? out.slice(0, q.limit) : out;
88
+ }
89
+
90
+ // search-as-you-type over the full index (lazy lowercase map; prefix beats substring)
91
+ function search(text, limit = 40) {
92
+ const t = String(text || "").toLowerCase().trim();
93
+ if (!t) return [];
94
+ if (!searchIdx) searchIdx = rows.map((r) => [r.t.toLowerCase(), r]);
95
+ const pre = [], sub = [];
96
+ for (const [lt, r] of searchIdx) {
97
+ if (lt.startsWith(t)) pre.push(r);
98
+ else if (lt.includes(t)) sub.push(r);
99
+ if (pre.length >= limit) break;
100
+ }
101
+ const sc = (r) => r.r * Math.log10(1 + r.v);
102
+ pre.sort((a, b) => sc(b) - sc(a)); sub.sort((a, b) => sc(b) - sc(a));
103
+ return [...pre, ...sub].slice(0, limit);
104
+ }
105
+
106
+ // persons (lazy) β€” person id β†’ { n: name, t: [rowKeys] }; enables the "tap a face β†’ filmography" pivot
107
+ async function personsIndex() {
108
+ if (personsCache) return personsCache;
109
+ await loadRoot();
110
+ personsCache = dec(await readObject(R.persons, "persons")).persons;
111
+ return personsCache;
112
+ }
113
+ async function person(id) { return (await personsIndex())[id] || null; }
114
+
115
+ return {
116
+ load, query, search, person, personsIndex, loadRoot,
117
+ get rows() { return rows; }, get root() { return R; }, get loaded() { return loaded; },
118
+ rowByKey: (k) => byKey.get(k) || null, stats: () => ({ ...stats }),
119
+ };
120
+ }
121
+
122
+ // ── browser binding β€” CacheStorage raw-bytes tier + SubtleCrypto verify ─────────────────────────────────
123
+ if (typeof window !== "undefined") {
124
+ const CACHE = "holo-library-objects"; // content-addressed: entries never change, only appear
125
+ const sha256hex = async (u8) => [...new Uint8Array(await crypto.subtle.digest("SHA-256", u8))].map((b) => b.toString(16).padStart(2, "0")).join("");
126
+ const cacheGet = async (sha) => { try { const c = await caches.open(CACHE); const r = await c.match("https://holo.local/lib/" + sha); return r ? new Uint8Array(await r.arrayBuffer()) : null; } catch { return null; } };
127
+ const cachePut = async (sha, bytes) => { try { const c = await caches.open(CACHE); await c.put("https://holo.local/lib/" + sha, new Response(bytes)); } catch {} };
128
+ let one = null;
129
+ window.HoloLibrary = { createLibrary, live() { return one || (one = createLibrary({ sha256hex, cacheGet, cachePut })); } };
130
+ }
131
+
132
+ export default { createLibrary };