📄 src/models/adapters/react.ts
D-OPEN SOVEREIGN
Documentation & Insights
Per-record update listeners — shared by the installer and `useModelUpdate`.**Composes** the React reactivity bus into the models_v2 record tracker
(coexists with other frameworks — see `addReactivitySystem`). React doesn't
auto-track reads during render, so `track` is a no-op and components subscribe
explicitly via `useModelUpdate(record)`. Idempotent; returns an unregister.Re-renders the calling component whenever the given ORM record mutates.Persistent models_v2 session for React — full front-end boot + React update
bus, retained across URL navigation. Thin wrapper over the shared
`bootstrapSession()` glue with React's reactivity installer.Returns the current LevelState for the given username, re-rendering the
component whenever that user's level changes.
@example
const { level, progress } = useLevel('mine');Re-renders the calling component whenever the global trash store
(models/trash.ts) changes for `objectType`, 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']);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 { useSyncExternalStore, useEffect, useState } from 'react';
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';
20export { resetModelsV2Session };
21
22/** Per-record update listeners — shared by the installer and `useModelUpdate`. */
23const listeners = new WeakMap<object, Set<() => void>>();
24
25let _reactUnregister: (() => void) | null = null;
26const _reactSystem: ReactivitySystem = {
27 track() { /* React subscribes explicitly via useModelUpdate */ },
28 trigger(o: object) {
29 listeners.get(o)?.forEach(cb => cb());
30 },
31};
32
33/**
34 * **Composes** the React reactivity bus into the models_v2 record tracker
35 * (coexists with other frameworks — see `addReactivitySystem`). React doesn't
36 * auto-track reads during render, so `track` is a no-op and components subscribe
37 * explicitly via `useModelUpdate(record)`. Idempotent; returns an unregister.
38 */
39export const installReactReactivity = (): (() => void) => {
40 if (!_reactUnregister) _reactUnregister = addReactivitySystem(_reactSystem);
41 return _reactUnregister;
42};
43
44/** Re-renders the calling component whenever the given ORM record mutates. */
45export const useModelUpdate = (record: object): void => {
46 const [, force] = useState(0);
47 useEffect(() => {
48 let set = listeners.get(record);
49 if (!set) { set = new Set(); listeners.set(record, set); }
50 const cb = () => force(n => n + 1);
51 set.add(cb);
52 return () => { set!.delete(cb); if (set!.size === 0) listeners.delete(record); };
53 }, [record]);
54};
55
56/**
57 * Persistent models_v2 session for React — full front-end boot + React update
58 * bus, retained across URL navigation. Thin wrapper over the shared
59 * `bootstrapSession()` glue with React's reactivity installer.
60 */
61export const bootstrapModelsV2 = (opts: SessionBootstrapOptions): Promise<void> =>
62 bootstrapSession(opts, installReactReactivity);
63
64/**
65 * Returns the current LevelState for the given username, re-rendering the
66 * component whenever that user's level changes.
67 *
68 * @example
69 * const { level, progress } = useLevel('mine');
70 */
71export const useLevel = (username: string) =>
72 useSyncExternalStore(
73 cb => subscribeLevel(u => { if (u === username) cb(); }),
74 () => getLevel(username),
75 () => getLevel(username), // server snapshot (SSR safe)
76 );
77
78/**
79 * Re-renders the calling component whenever the global trash store
80 * (models/trash.ts) changes for `objectType`, merged across `usernames`
81 * (empty = every known username). Population and live updates are entirely
82 * automatic — nothing to register, nothing to feed it.
83 *
84 * @example
85 * const { count, getIds } = useTrash(Tag.type, ['mine']);
86 */
87export const useTrash = (objectType: number, usernames: string[] = []) => {
88 const [, force] = useState(0);
89 useEffect(() => trashStore.subscribe(objectType, () => force(n => n + 1)), [objectType]);
90 return {
91 count: trashStore.count(objectType, usernames),
92 getIds: () => trashStore.list(objectType, usernames),
93 };
94};
95