📄 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