๐Ÿ“„ src/models/db.ts
D-OPEN SOVEREIGN

Documentation & Insights

Registers a reactivity system, returning an unregister function. Use this to
**compose** systems (multiple frameworks live at once). Idempotent for the
same object.
Replaces *all* registered systems with `sys` (or clears them with `null`).
Single-framework convenience / test reset. For coexisting frameworks use
`addReactivitySystem`.
Set the per-objectType LRU limit (call before loading; 2 000 on mobile).
Synchronous read. Throws on LRU miss โ€” use the model's LoadAsync for re-hydration.
1/*
2 * Copyright (c) 2026 DataPond D-Library Pty Ltd. ("The Sanctuary")
3 * Non-Profit Public Library โ€” The World Library
4 * 
5 * D-SAFE Certification issued by POND Enterprise.
6 * Published under the D-Open Code Sovereign Licence v1.0 framework
7 * DataPond D-Library All exclusive Right reserved on modifiying the code - CC Attribution to data pond, Non modifiable, Non Commercial.
8 * https://registry.world.bibliotech.com/licence
9 * Source code donated to DataPond D-Library by it's author: data pond.
10 * 
11 * Technical Guardian ("The Shield"): Pond Enterprise Pty Ltd. (ACN 694 747 987)
12 * All liability claims about D-SAFE certification issues coming from the published D-Safe direction must be addressed with Pond Enterprise Pty Ltd.
13 * Code Modification automatically voids the certified liability protection provided by Pond Enterprise.
14 */
15import type { uid } from "./interfaces.ts";
16import { isUserCreated } from "./uidFactory.ts";
17
18// โ”€โ”€โ”€ Lazy WeakMap Reactivity System (SPEC ยง2.4) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
19//
20// Framework-agnostic dependency tracker. Adapters (Vue's shallowRef, Solid's
21// createSignal) register via setReactivitySystem; non-accessed records remain
22// plain JS objects with zero reactivity overhead.
23
24export interface ReactivitySystem {
25    track: (obj: any) => void;
26    trigger: (obj: any) => void;
27}
28
29// Multiple reactivity systems can be active at once โ€” a `track`/`trigger` fans
30// out to all of them. This lets several framework scopes on one page (e.g. a
31// Solid island AND a vanilla Astro island) react to the same shared `db()`
32// records simultaneously. Systems are deduped by object identity, so an
33// adapter's idempotent re-install is a no-op.
34const systems = new Set<ReactivitySystem>();
35
36/**
37 * Registers a reactivity system, returning an unregister function. Use this to
38 * **compose** systems (multiple frameworks live at once). Idempotent for the
39 * same object.
40 */
41export function addReactivitySystem(sys: ReactivitySystem): () => void {
42    systems.add(sys);
43    return () => { systems.delete(sys); };
44}
45
46/**
47 * Replaces *all* registered systems with `sys` (or clears them with `null`).
48 * Single-framework convenience / test reset. For coexisting frameworks use
49 * `addReactivitySystem`.
50 */
51export function setReactivitySystem(sys: ReactivitySystem | null) {
52    systems.clear();
53    if (sys) systems.add(sys);
54}
55
56export const dbReactivity = {
57    track(obj: any) {
58        for (const s of systems) s.track(obj);
59    },
60    trigger(obj: any) {
61        for (const s of systems) s.trigger(obj);
62    }
63};
64
65// โ”€โ”€โ”€ LRU Cache (SPEC ยง2.5) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
66//
67// Exploits the ordered-insertion property of Map keys: a get() re-inserts the
68// entry at the end, so the first key is always the least recently used.
69
70export class LruCache<K, V> {
71    private map = new Map<K, V>();
72    private _pinned = new Set<K>();
73    private _warnedOverflow = false;
74    constructor(public limit: number) { }
75
76    get size(): number {
77        return this.map.size;
78    }
79
80    get(key: K): V | undefined {
81        if (!this.map.has(key)) return undefined;
82        const val = this.map.get(key)!;
83        this.map.delete(key);
84        this.map.set(key, val); // Move to end (most recently used)
85        return val;
86    }
87
88    set(key: K, val: V) {
89        if (this.map.has(key)) {
90            this.map.delete(key);
91        }
92        this.map.set(key, val);
93        if (this.map.size > this.limit) {
94            let evicted = false;
95            for (const k of this.map.keys()) {
96                if (!this._pinned.has(k)) {
97                    this.map.delete(k);
98                    evicted = true;
99                    break;
100                }
101            }
102            if (!evicted && !this._warnedOverflow) {
103                // Every entry is pinned (draft/submitted/rejected โ€” not yet Canonical,
104                // see unpinRecord in orm.ts). Evicting one anyway would make it
105                // unreadable via the synchronous Load()/getRecord() path, since a
106                // pinned record isn't guaranteed reconstructible via LoadAsync's
107                // worker-storage round trip (SPEC ยง2.5) โ€” that's the whole point of
108                // pinning it. Growing past `limit` risks unbounded memory instead,
109                // but that's the safer failure mode of the two, so warn once and
110                // let the cache grow rather than crash the caller.
111                this._warnedOverflow = true;
112                console.warn(
113                    `[LruCache] limit (${this.limit}) exceeded with every entry pinned โ€” ` +
114                    `growing past limit instead of evicting/crashing. This usually means ` +
115                    `too many draft/unsynced records are open at once.`
116                );
117            }
118        }
119    }
120
121    pin(key: K): void { this._pinned.add(key); }
122    unpin(key: K): void { this._pinned.delete(key); }
123
124    has(key: K): boolean {
125        return this.map.has(key);
126    }
127
128    delete(key: K) {
129        this.map.delete(key);
130    }
131
132    clear() {
133        this.map.clear();
134    }
135
136    keys(): IterableIterator<K> {
137        return this.map.keys();
138    }
139
140    values(): IterableIterator<V> {
141        return this.map.values();
142    }
143}
144
145// โ”€โ”€โ”€ In-memory database โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
146
147const DEFAULT_CACHE_LIMIT = 10_000;
148
149const dbConfig = {
150    cacheLimit: DEFAULT_CACHE_LIMIT,
151};
152
153const memory: Map<number, LruCache<string, any>> = new Map()
154
155/** Set the per-objectType LRU limit (call before loading; 2 000 on mobile). */
156export const configureDb = (opts: { cacheLimit?: number }) => {
157    if (opts.cacheLimit && opts.cacheLimit > 0) {
158        dbConfig.cacheLimit = opts.cacheLimit;
159        for (const cache of memory.values()) {
160            cache.limit = opts.cacheLimit;
161        }
162    }
163}
164
165const cacheFor = (objectType: number): LruCache<string, any> => {
166    let cache = memory.get(objectType);
167    if (!cache) {
168        cache = new LruCache(dbConfig.cacheLimit);
169        memory.set(objectType, cache);
170    }
171    return cache;
172}
173
174export const clear = () => {
175    memory.clear()
176}
177
178export const dump = (): Map<number, LruCache<string, any>> => {
179    return memory
180}
181
182export function getRecordKey(wireId: number, creatorUsername?: string): string {
183    if (isUserCreated(wireId)) {
184        if (!creatorUsername) throw new Error('[models_v2] creatorUsername required for user-created records');
185        return `${creatorUsername}:${wireId}`;
186    }
187    return `canonical:${wireId}`;
188}
189
190export const hasRecord = (objectType: number, id: uid, creatorUsername?: string): boolean => {
191    // A user-created id with no creatorUsername can never resolve to a cache key
192    // (see getRecordKey) โ€” callers that don't track ownership (e.g. relation
193    // traversal) need "not found" rather than a thrown error here.
194    if (isUserCreated(id) && !creatorUsername) return false;
195    return cacheFor(objectType).has(getRecordKey(id, creatorUsername));
196}
197
198/** Synchronous read. Throws on LRU miss โ€” use the model's LoadAsync for re-hydration. */
199export const getRecord = (objectType: number, id: uid, creatorUsername?: string): any => {
200    const key = getRecordKey(id, creatorUsername);
201    const record = cacheFor(objectType).get(key);
202    if (typeof record === 'undefined') {
203        throw new Error(`cannot find ${objectType} with id ${id} (evicted or never loaded โ€” use LoadAsync)`)
204    }
205    return record
206}
207
208export const addRecord = (objectType: number, obj: { id: uid }, creatorUsername?: string) => {
209    cacheFor(objectType).set(getRecordKey(obj.id, creatorUsername), obj)
210}
211
212export const removeRecord = (objectType: number, id: uid, creatorUsername?: string) => {
213    cacheFor(objectType).delete(getRecordKey(id, creatorUsername))
214}
215
216export const pinRecord = (objectType: number, id: uid, creatorUsername?: string) => {
217    cacheFor(objectType).pin(getRecordKey(id, creatorUsername))
218}
219
220export const unpinRecord = (objectType: number, id: uid, creatorUsername?: string) => {
221    cacheFor(objectType).unpin(getRecordKey(id, creatorUsername))
222}
223