📄 src/models/adapters/solid.ts
D-OPEN SOVEREIGN

Documentation & Insights

**Composes** the Solid.js reactivity system into the models_v2 record tracker
(does not replace other frameworks' systems — see `addReactivitySystem`).
Each tracked ORM record maps to a Solid signal in a `WeakMap`, so a read
inside a reactive scope subscribes and a mutation (`record.patch()` /
generated setters) triggers a fine-grained update. Signals are created in a
detached `createRoot` so they survive component unmount / navigation.

Idempotent — repeated calls (e.g. per `astro:after-swap`) keep the single
registration. Returns an unregister function.
Establishes a **persistent models_v2 session** for Solid — the full front-end
data load (resolve sidecars → worker → `hydrateAll` → replay) plus Solid
reactivity, retained across every live URL navigation.

Thin wrapper over the shared `bootstrapSession()` glue (worker boot, once-only
promise, Astro `<ClientRouter />` persistence) with Solid's reactivity
installer injected — the same one-call entry exists in the vue/react adapters.

@example
import { Book, Tag, Person, NewBookWithId, NewTagWithId, NewPersonWithId } from '@the_library/public/models/models';
await bootstrapModelsV2({
  models: [
    { type: Book.type,   create: NewBookWithId },
    { type: Tag.type,    create: NewTagWithId },
    { type: Person.type, create: NewPersonWithId },
  ],
  localMode: '/corpus_v2',        // dev: serve bin/ + corpus-index.json locally
  // registry: { dataBlock },     // prod: resolve Arweave TX-ids from registry_v2
  selectedLangs: ['eng'],
});
Returns a Solid signal accessor for the given username's LevelState.
The signal updates whenever that user's level changes.

@example
const level = useLevel('mine');
return <div>{level().level}</div>;
Solid signal accessor over the global trash store (models/trash.ts) for one
object type, merged across `usernames` (empty = every known username).
Population and live updates are entirely automatic — nothing to register,
nothing to feed it.

@example
const { count, getIds } = useTrash(Tag.type, ['mine']);
return <span>{count()}</span>;
Creates a reactive draft for a models_v2 record in Solid.
Returns the draft proxy, and reactive helpers.
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 { createSignal, onCleanup, createRoot } from 'solid-js';
16import { getLevel, subscribeLevel } from '../level-state.ts';
17import { trashStore } from '../trash.ts';
18import { addReactivitySystem, type ReactivitySystem } from '../db.ts';
19import { bootstrapSession, type SessionBootstrapOptions, resetModelsV2Session } from '../session.ts';
20
21export { resetModelsV2Session };
22
23let _solidUnregister: (() => void) | null = null;
24
25/**
26 * **Composes** the Solid.js reactivity system into the models_v2 record tracker
27 * (does not replace other frameworks' systems — see `addReactivitySystem`).
28 * Each tracked ORM record maps to a Solid signal in a `WeakMap`, so a read
29 * inside a reactive scope subscribes and a mutation (`record.patch()` /
30 * generated setters) triggers a fine-grained update. Signals are created in a
31 * detached `createRoot` so they survive component unmount / navigation.
32 *
33 * Idempotent — repeated calls (e.g. per `astro:after-swap`) keep the single
34 * registration. Returns an unregister function.
35 */
36export const installSolidReactivity = (): (() => void) => {
37    if (_solidUnregister) return _solidUnregister;
38    const signals = new WeakMap<object, { get: () => number; set: (v: number) => void }>();
39    const system: ReactivitySystem = {
40        track(o: object) {
41            let s = signals.get(o);
42            if (!s) {
43                // Detached owner so the signal is not disposed with the reading component.
44                s = createRoot(() => { const [get, set] = createSignal(0); return { get, set }; });
45                signals.set(o, s);
46            }
47            s.get();
48        },
49        trigger(o: object) {
50            const s = signals.get(o);
51            if (s) s.set(s.get() + 1);
52        },
53    };
54    _solidUnregister = addReactivitySystem(system);
55    return _solidUnregister;
56};
57
58/**
59 * Establishes a **persistent models_v2 session** for Solid — the full front-end
60 * data load (resolve sidecars → worker → `hydrateAll` → replay) plus Solid
61 * reactivity, retained across every live URL navigation.
62 *
63 * Thin wrapper over the shared `bootstrapSession()` glue (worker boot, once-only
64 * promise, Astro `<ClientRouter />` persistence) with Solid's reactivity
65 * installer injected — the same one-call entry exists in the vue/react adapters.
66 *
67 * @example
68 * import { Book, Tag, Person, NewBookWithId, NewTagWithId, NewPersonWithId } from '@the_library/public/models/models';
69 * await bootstrapModelsV2({
70 *   models: [
71 *     { type: Book.type,   create: NewBookWithId },
72 *     { type: Tag.type,    create: NewTagWithId },
73 *     { type: Person.type, create: NewPersonWithId },
74 *   ],
75 *   localMode: '/corpus_v2',        // dev: serve bin/ + corpus-index.json locally
76 *   // registry: { dataBlock },     // prod: resolve Arweave TX-ids from registry_v2
77 *   selectedLangs: ['eng'],
78 * });
79 */
80export const bootstrapModelsV2 = (opts: SessionBootstrapOptions): Promise<void> =>
81    bootstrapSession(opts, installSolidReactivity);
82
83/**
84 * Returns a Solid signal accessor for the given username's LevelState.
85 * The signal updates whenever that user's level changes.
86 *
87 * @example
88 * const level = useLevel('mine');
89 * return <div>{level().level}</div>;
90 */
91export const useLevel = (username: string) => {
92    const [state, setState] = createSignal(getLevel(username));
93    const unsub = subscribeLevel(u => { if (u === username) setState(getLevel(username)); });
94    onCleanup(unsub);
95    return state;
96};
97
98/**
99 * Solid signal accessor over the global trash store (models/trash.ts) for one
100 * object type, merged across `usernames` (empty = every known username).
101 * Population and live updates are entirely automatic — nothing to register,
102 * nothing to feed it.
103 *
104 * @example
105 * const { count, getIds } = useTrash(Tag.type, ['mine']);
106 * return <span>{count()}</span>;
107 */
108export const useTrash = (objectType: number, usernames: string[] = []) => {
109    const [count, setCount] = createSignal(trashStore.count(objectType, usernames));
110    const unsub = trashStore.subscribe(objectType, () => setCount(trashStore.count(objectType, usernames)));
111    onCleanup(unsub);
112    return {
113        count,
114        getIds: () => trashStore.list(objectType, usernames),
115    };
116};
117
118/**
119 * Creates a reactive draft for a models_v2 record in Solid.
120 * Returns the draft proxy, and reactive helpers.
121 */
122import { createMemo } from 'solid-js';
123import { createDraft, isDraftDirty, commitDraft, rollbackDraft } from '../draft.ts';
124import type { Orm } from '../orm.ts';
125
126export const useDraft = <T extends Orm>(model: () => T) => {
127    const [tick, setTick] = createSignal(0);
128    // recreate draft when underlying model changes, but keep it stable otherwise
129    const draft = createMemo(() => createDraft(model(), () => setTick(t => t + 1)));
130    
131    return {
132        draft,
133        isDirty: () => {
134            tick(); // depend on edits
135            return isDraftDirty(draft());
136        },
137        commit: () => {
138            commitDraft(draft());
139            setTick(t => t + 1);
140        },
141        rollback: () => {
142            rollbackDraft(draft());
143            setTick(t => t + 1);
144        }
145    };
146};
147