📄 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