📄 src/models/adapters/astro.ts
D-OPEN SOVEREIGN
Documentation & Insights
Astro adapter — framework-free reactivity for **vanilla-JS islands** plus the
shared persistent-session boot.
Astro islands hydrated with Solid/Vue/React should import `bootstrapModelsV2`
from `@the_library/public/models/{solid,vue,react}` instead — the
`<ClientRouter />` persistence glue is shared either way. This adapter is for
islands that run plain JavaScript (an inline `<script>` in a `.astro` file)
and still need models_v2 data to react to mutations.Per-record listeners — shared by the installer and `subscribeRecord`.**Composes** a framework-free reactivity bus into the record tracker (coexists
with framework systems — see `addReactivitySystem`). There is no automatic
read-tracking for vanilla JS, so `track` is a no-op and DOM updaters subscribe
explicitly with `subscribeRecord`. Idempotent; returns an unregister.Subscribe a plain callback (e.g. a DOM updater) to a record's mutations.
Returns an unsubscribe function.
@example
const book = await Book.LoadAsync(10);
const stop = subscribeRecord(book, () => { titleEl.textContent = book.title; });Persistent models_v2 session for Astro vanilla islands — the full front-end
boot (resolve sidecars → worker → `hydrateAll` → replay) plus framework-free
reactivity, retained across `<ClientRouter />` navigations. Thin wrapper over
the shared `bootstrapSession()` glue (identical to the solid/vue/react entries
— only the reactivity installer differs).
**`await` it** before reading records: `Book.Load()` throws on an un-hydrated
miss, so gate data access on the returned promise (or use `LoadAsync`).
@example
// src/layouts/Base.astro — <head><ClientRouter /></head>, then in a client script:
import { bootstrapModelsV2, subscribeRecord } from '@the_library/public/models/astro';
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',
selectedLangs: ['eng'],
});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 { addReactivitySystem, type ReactivitySystem } from '../db.ts';
16import { bootstrapSession, type SessionBootstrapOptions, resetModelsV2Session } from '../session.ts';
17
18export { resetModelsV2Session };
19
20/**
21 * Astro adapter — framework-free reactivity for **vanilla-JS islands** plus the
22 * shared persistent-session boot.
23 *
24 * Astro islands hydrated with Solid/Vue/React should import `bootstrapModelsV2`
25 * from `@the_library/public/models/{solid,vue,react}` instead — the
26 * `<ClientRouter />` persistence glue is shared either way. This adapter is for
27 * islands that run plain JavaScript (an inline `<script>` in a `.astro` file)
28 * and still need models_v2 data to react to mutations.
29 */
30
31/** Per-record listeners — shared by the installer and `subscribeRecord`. */
32const listeners = new WeakMap<object, Set<() => void>>();
33
34let _vanillaUnregister: (() => void) | null = null;
35const _vanillaSystem: ReactivitySystem = {
36 track() { /* vanilla JS subscribes explicitly via subscribeRecord */ },
37 trigger(o: object) {
38 listeners.get(o)?.forEach(cb => cb());
39 },
40};
41
42/**
43 * **Composes** a framework-free reactivity bus into the record tracker (coexists
44 * with framework systems — see `addReactivitySystem`). There is no automatic
45 * read-tracking for vanilla JS, so `track` is a no-op and DOM updaters subscribe
46 * explicitly with `subscribeRecord`. Idempotent; returns an unregister.
47 */
48export const installVanillaReactivity = (): (() => void) => {
49 if (!_vanillaUnregister) _vanillaUnregister = addReactivitySystem(_vanillaSystem);
50 return _vanillaUnregister;
51};
52
53/**
54 * Subscribe a plain callback (e.g. a DOM updater) to a record's mutations.
55 * Returns an unsubscribe function.
56 *
57 * @example
58 * const book = await Book.LoadAsync(10);
59 * const stop = subscribeRecord(book, () => { titleEl.textContent = book.title; });
60 */
61export const subscribeRecord = (record: object, cb: () => void): (() => void) => {
62 let set = listeners.get(record);
63 if (!set) { set = new Set(); listeners.set(record, set); }
64 set.add(cb);
65 return () => { set!.delete(cb); if (set!.size === 0) listeners.delete(record); };
66};
67
68/**
69 * Persistent models_v2 session for Astro vanilla islands — the full front-end
70 * boot (resolve sidecars → worker → `hydrateAll` → replay) plus framework-free
71 * reactivity, retained across `<ClientRouter />` navigations. Thin wrapper over
72 * the shared `bootstrapSession()` glue (identical to the solid/vue/react entries
73 * — only the reactivity installer differs).
74 *
75 * **`await` it** before reading records: `Book.Load()` throws on an un-hydrated
76 * miss, so gate data access on the returned promise (or use `LoadAsync`).
77 *
78 * @example
79 * // src/layouts/Base.astro — <head><ClientRouter /></head>, then in a client script:
80 * import { bootstrapModelsV2, subscribeRecord } from '@the_library/public/models/astro';
81 * import { Book, Tag, Person, NewBookWithId, NewTagWithId, NewPersonWithId } from '@the_library/public/models/models';
82 * await bootstrapModelsV2({
83 * models: [
84 * { type: Book.type, create: NewBookWithId },
85 * { type: Tag.type, create: NewTagWithId },
86 * { type: Person.type, create: NewPersonWithId },
87 * ],
88 * localMode: '/corpus_v2',
89 * selectedLangs: ['eng'],
90 * });
91 */
92export const bootstrapModelsV2 = (opts: SessionBootstrapOptions): Promise<void> =>
93 bootstrapSession(opts, installVanillaReactivity);
94