๐ 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