@gratiaos/pad-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/events.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Garden Pads — event helpers & lightweight in-memory bus.
3
+ *
4
+ * - Framework-agnostic signal bus (padEvents) for local adapters/tests.
5
+ * - DOM CustomEvent utilities for cross-frame / cross-app communication.
6
+ *
7
+ * CustomEvent names are stable and kebab/colon cased:
8
+ * - "pad:open" — request the host to open a pad by id
9
+ * - "pad:close" — request the host to close a pad (optional id)
10
+ * - "pad:bulletin:updated" — pad announces its bulletin/feed changed
11
+ */
12
+ /* ──────────────────────────────────────────────────────────────────────────
13
+ * 1) Lightweight in-memory bus (no DOM required)
14
+ * -------------------------------------------------------------------------- */
15
+ const listeners = new Set();
16
+ /**
17
+ * Local, synchronous pub/sub for pads. Useful in tests or headless contexts.
18
+ * Host shells may bridge this to DOM CustomEvents if needed.
19
+ */
20
+ export const padEvents = {
21
+ /** Broadcast a signal to all listeners. */
22
+ send(msg) {
23
+ listeners.forEach((l) => {
24
+ try {
25
+ l(msg);
26
+ }
27
+ catch {
28
+ // ignore listener errors by design
29
+ }
30
+ });
31
+ },
32
+ /** Subscribe to signals; returns an unsubscribe function. */
33
+ on(fn) {
34
+ listeners.add(fn);
35
+ return () => {
36
+ listeners.delete(fn);
37
+ };
38
+ },
39
+ /** Clear all listeners — primarily for tests. */
40
+ clear() {
41
+ listeners.clear();
42
+ },
43
+ };
44
+ /* ──────────────────────────────────────────────────────────────────────────
45
+ * 2) DOM CustomEvent helpers
46
+ * -------------------------------------------------------------------------- */
47
+ export const PAD_OPEN_EVENT = 'pad:open';
48
+ export const PAD_CLOSE_EVENT = 'pad:close';
49
+ export const PAD_BULLETIN_UPDATED_EVENT = 'pad:bulletin:updated';
50
+ /** Safe default target for dispatching/observing pad events. */
51
+ function getTarget(target) {
52
+ // Prefer an explicit target; otherwise window if present; else fallback to a simple EventTarget.
53
+ if (target)
54
+ return target;
55
+ const maybeWindow = typeof globalThis !== 'undefined' && typeof globalThis.window !== 'undefined'
56
+ ? globalThis.window
57
+ : undefined;
58
+ if (maybeWindow)
59
+ return maybeWindow;
60
+ // Last resort for SSR/tests without JSDOM:
61
+ // Creating one per call is OK; callers in SSR typically pass a target anyway.
62
+ return new EventTarget();
63
+ }
64
+ /** Dispatch a `pad:open` request. */
65
+ export function dispatchPadOpen(detail, target) {
66
+ getTarget(target).dispatchEvent(new CustomEvent(PAD_OPEN_EVENT, { detail, bubbles: true }));
67
+ }
68
+ /** Dispatch a `pad:close` request. */
69
+ export function dispatchPadClose(detail = {}, target) {
70
+ getTarget(target).dispatchEvent(new CustomEvent(PAD_CLOSE_EVENT, { detail, bubbles: true }));
71
+ }
72
+ /** Dispatch a `pad:bulletin:updated` announcement. */
73
+ export function dispatchPadBulletinUpdated(detail = {}, target) {
74
+ getTarget(target).dispatchEvent(new CustomEvent(PAD_BULLETIN_UPDATED_EVENT, { detail, bubbles: true }));
75
+ }
76
+ /** Listen for `pad:open`. */
77
+ export function onPadOpen(handler, target) {
78
+ const t = getTarget(target);
79
+ const fn = (ev) => handler(ev.detail, ev);
80
+ t.addEventListener(PAD_OPEN_EVENT, fn);
81
+ return () => t.removeEventListener(PAD_OPEN_EVENT, fn);
82
+ }
83
+ /** Listen for `pad:close`. */
84
+ export function onPadClose(handler, target) {
85
+ const t = getTarget(target);
86
+ const fn = (ev) => handler(ev.detail, ev);
87
+ t.addEventListener(PAD_CLOSE_EVENT, fn);
88
+ return () => t.removeEventListener(PAD_CLOSE_EVENT, fn);
89
+ }
90
+ /** Listen for `pad:bulletin:updated`. */
91
+ export function onPadBulletinUpdated(handler, target) {
92
+ const t = getTarget(target);
93
+ const fn = (ev) => handler(ev.detail, ev);
94
+ t.addEventListener(PAD_BULLETIN_UPDATED_EVENT, fn);
95
+ return () => t.removeEventListener(PAD_BULLETIN_UPDATED_EVENT, fn);
96
+ }
@@ -0,0 +1,15 @@
1
+ import { type PadMood } from '..';
2
+ /**
3
+ * usePadMood
4
+ * ----------
5
+ * Minimal mood state that stays in sync with the global pad event bus.
6
+ *
7
+ * • Local state follows PAD.MOOD.SET messages.
8
+ * • The setter also broadcasts a PAD.MOOD.SET so other listeners can attune.
9
+ * • Default mood is 'soft' (can be overridden).
10
+ *
11
+ * Example:
12
+ * const [mood, setMood] = usePadMood();
13
+ * // setMood('focused')
14
+ */
15
+ export declare function usePadMood(defaultMood?: PadMood): readonly [PadMood, (next: PadMood) => void];
@@ -0,0 +1,46 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { padEvents } from '..';
3
+ /**
4
+ * usePadMood
5
+ * ----------
6
+ * Minimal mood state that stays in sync with the global pad event bus.
7
+ *
8
+ * • Local state follows PAD.MOOD.SET messages.
9
+ * • The setter also broadcasts a PAD.MOOD.SET so other listeners can attune.
10
+ * • Default mood is 'soft' (can be overridden).
11
+ *
12
+ * Example:
13
+ * const [mood, setMood] = usePadMood();
14
+ * // setMood('focused')
15
+ */
16
+ export function usePadMood(defaultMood = 'soft') {
17
+ const [mood, setMood] = useState(defaultMood);
18
+ const mounted = useRef(true);
19
+ // Track mount to avoid setState after unmount
20
+ useEffect(() => {
21
+ mounted.current = true;
22
+ return () => {
23
+ mounted.current = false;
24
+ };
25
+ }, []);
26
+ // Subscribe to global pad signals
27
+ useEffect(() => {
28
+ const off = padEvents.on((msg) => {
29
+ if (msg.type === 'PAD.MOOD.SET' && mounted.current) {
30
+ setMood(msg.mood);
31
+ }
32
+ });
33
+ return off;
34
+ }, []);
35
+ // Local setter that also broadcasts
36
+ const set = useCallback((next) => {
37
+ setMood(next);
38
+ try {
39
+ padEvents.send({ type: 'PAD.MOOD.SET', mood: next });
40
+ }
41
+ catch {
42
+ // no-op: keep local state even if broadcasting fails
43
+ }
44
+ }, []);
45
+ return [mood, set];
46
+ }
package/dist/id.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @gratiaos/pad-core — ID & slug helpers
3
+ *
4
+ * Goals:
5
+ * - Stable, URL-safe IDs without external deps.
6
+ * - Prefer cryptographically strong randomness when available.
7
+ * - Human-friendly slugs (lowercase, hyphenated).
8
+ *
9
+ * These helpers are used by pads for local keys, DOM ids, and
10
+ * deep-linking (e.g. `/pads/:slug-id` or `#pad=slug-id`).
11
+ */
12
+ /**
13
+ * Generate a compact, time-ordered id.
14
+ *
15
+ * Shape: `<ts36><rand10>` — e.g., `lrl7y3q3r7j2a4m1z9`
16
+ * - `ts36`: Date.now() in base36 so newer ids sort after older ones.
17
+ * - `rand10`: 10 base36 characters for collision resistance.
18
+ */
19
+ export declare function uid(): string;
20
+ /** Short random token (default 6 chars) — URL/DOM safe. */
21
+ export declare function shortid(len?: number): string;
22
+ /**
23
+ * Convert arbitrary text to a URL/DOM-safe slug.
24
+ * - lowercases
25
+ * - strips accents/diacritics when possible
26
+ * - replaces non-alphanumerics with single `-`
27
+ * - trims leading/trailing `-`
28
+ */
29
+ export declare function slug(s: string): string;
30
+ /**
31
+ * Build a human-friendly stable key that includes a slug plus a short random suffix.
32
+ * Useful for deep-links and list keys (e.g., `focus-marker-8k2b9c`).
33
+ */
34
+ export declare function slugId(title: string, suffixLen?: number): string;
package/dist/id.js ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @gratiaos/pad-core — ID & slug helpers
3
+ *
4
+ * Goals:
5
+ * - Stable, URL-safe IDs without external deps.
6
+ * - Prefer cryptographically strong randomness when available.
7
+ * - Human-friendly slugs (lowercase, hyphenated).
8
+ *
9
+ * These helpers are used by pads for local keys, DOM ids, and
10
+ * deep-linking (e.g. `/pads/:slug-id` or `#pad=slug-id`).
11
+ */
12
+ /** Internal: generate `n` base36 chars using crypto when available. */
13
+ function randomBase36(n) {
14
+ const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
15
+ // Use Web Crypto if present for stronger randomness.
16
+ const cryptoObj = typeof globalThis !== 'undefined' && globalThis.crypto ? globalThis.crypto : undefined;
17
+ if (cryptoObj?.getRandomValues) {
18
+ const buf = new Uint8Array(n);
19
+ cryptoObj.getRandomValues(buf);
20
+ let out = '';
21
+ for (let i = 0; i < n; i++) {
22
+ // Map uniformly to 0..35
23
+ out += alphabet[buf[i] % 36];
24
+ }
25
+ return out;
26
+ }
27
+ // Fallback: Math.random (less strong, still fine for UI ids).
28
+ let out = '';
29
+ for (let i = 0; i < n; i++) {
30
+ out += alphabet[Math.floor(Math.random() * 36)];
31
+ }
32
+ return out;
33
+ }
34
+ /**
35
+ * Generate a compact, time-ordered id.
36
+ *
37
+ * Shape: `<ts36><rand10>` — e.g., `lrl7y3q3r7j2a4m1z9`
38
+ * - `ts36`: Date.now() in base36 so newer ids sort after older ones.
39
+ * - `rand10`: 10 base36 characters for collision resistance.
40
+ */
41
+ export function uid() {
42
+ const ts = Date.now().toString(36);
43
+ return ts + randomBase36(10);
44
+ }
45
+ /** Short random token (default 6 chars) — URL/DOM safe. */
46
+ export function shortid(len = 6) {
47
+ const n = Math.max(1, Math.min(32, Math.floor(len)));
48
+ return randomBase36(n);
49
+ }
50
+ /**
51
+ * Convert arbitrary text to a URL/DOM-safe slug.
52
+ * - lowercases
53
+ * - strips accents/diacritics when possible
54
+ * - replaces non-alphanumerics with single `-`
55
+ * - trims leading/trailing `-`
56
+ */
57
+ export function slug(s) {
58
+ const base = (s ?? '')
59
+ // Strip diacritics if available
60
+ .normalize?.('NFKD')
61
+ ?.replace(/[\u0300-\u036f]/g, '') ??
62
+ s ??
63
+ '';
64
+ return base
65
+ .toLowerCase()
66
+ .replace(/[^a-z0-9]+/g, '-')
67
+ .replace(/^-+|-+$/g, '');
68
+ }
69
+ /**
70
+ * Build a human-friendly stable key that includes a slug plus a short random suffix.
71
+ * Useful for deep-links and list keys (e.g., `focus-marker-8k2b9c`).
72
+ */
73
+ export function slugId(title, suffixLen = 6) {
74
+ const head = slug(title) || 'item';
75
+ return `${head}-${shortid(suffixLen)}`;
76
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Garden Pads Core — Public API barrel.
3
+ *
4
+ * This package provides:
5
+ * - Types that describe pads, scenes, registry metadata, and events.
6
+ * - A simple in-memory registry for discoverability & ordering.
7
+ * - Hash/URL helpers to deep-link into a pad/scene.
8
+ * - A tiny event bus for host ↔ pad coordination.
9
+ * - Catalog utilities to build shelf-ready rows and grouped views.
10
+ *
11
+ * Keep this file small and stable: consumers import from here.
12
+ */
13
+ export type * from './types.js';
14
+ export { createRegistry, sortPads, registerAll, globalRegistry } from './registry.js';
15
+ export { DEFAULT_HASH_KEY, getActivePadId, setActivePadId, clearActivePadId, hrefForPad, onPadRouteChange } from './route.js';
16
+ export { padEvents, dispatchPadOpen, dispatchPadClose, dispatchPadBulletinUpdated, onPadOpen, onPadClose, onPadBulletinUpdated } from './events.js';
17
+ export { SCENE_ENTER, SCENE_COMPLETE, dispatchSceneEnter, dispatchSceneComplete, onSceneEnter, onSceneComplete, type SceneEnterDetail, type SceneCompleteDetail, type SceneVia, } from './scene-events.js';
18
+ export { uid, slug } from './id.js';
19
+ export { type CatalogEntry, toCatalogEntry, buildCatalog, mergeRegistries, buildCatalogFromMany, filterCatalog, groupCatalog, pathForPad, findPad, ensurePad, } from './catalog.js';
20
+ export * from './hooks/usePadMood';
21
+ export { setRealtimePort, getRealtimePort, getRealtimeCircleId, onRealtimePortChange } from './realtime/registry.js';
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Garden Pads Core — Public API barrel.
3
+ *
4
+ * This package provides:
5
+ * - Types that describe pads, scenes, registry metadata, and events.
6
+ * - A simple in-memory registry for discoverability & ordering.
7
+ * - Hash/URL helpers to deep-link into a pad/scene.
8
+ * - A tiny event bus for host ↔ pad coordination.
9
+ * - Catalog utilities to build shelf-ready rows and grouped views.
10
+ *
11
+ * Keep this file small and stable: consumers import from here.
12
+ */
13
+ export { createRegistry, sortPads, registerAll, globalRegistry } from './registry.js';
14
+ export { DEFAULT_HASH_KEY, getActivePadId, setActivePadId, clearActivePadId, hrefForPad, onPadRouteChange } from './route.js';
15
+ export { padEvents, dispatchPadOpen, dispatchPadClose, dispatchPadBulletinUpdated, onPadOpen, onPadClose, onPadBulletinUpdated } from './events.js';
16
+ // Scene events (Pad & Scene coordination)
17
+ export { SCENE_ENTER, SCENE_COMPLETE, dispatchSceneEnter, dispatchSceneComplete, onSceneEnter, onSceneComplete, } from './scene-events.js';
18
+ export { uid, slug } from './id.js';
19
+ // Catalog helpers (used by shelves/browsers in UIs)
20
+ export { toCatalogEntry, buildCatalog, mergeRegistries, buildCatalogFromMany, filterCatalog, groupCatalog, pathForPad, findPad, ensurePad, } from './catalog.js';
21
+ export * from './hooks/usePadMood';
22
+ // Realtime registry (optional integration used by scene-events)
23
+ export { setRealtimePort, getRealtimePort, getRealtimeCircleId, onRealtimePortChange } from './realtime/registry.js';
@@ -0,0 +1,24 @@
1
+ import { type RealtimePort } from './port';
2
+ import { type WebRtcConfig } from './webrtc-adapter';
3
+ /**
4
+ * Realtime adapter factory
5
+ * --------------------------------------------------------
6
+ * Provides a single entry point for Garden Core to create
7
+ * a realtime adapter based on environment or explicit config.
8
+ */
9
+ export type RealtimeKind = 'sim' | 'webrtc' | 'none';
10
+ export interface RealtimeOptions {
11
+ kind?: RealtimeKind;
12
+ webrtc?: Partial<WebRtcConfig>;
13
+ }
14
+ /**
15
+ * createRealtime — main factory
16
+ *
17
+ * Auto-detects environment by checking NODE_ENV / import.meta.env
18
+ * or explicit `kind` parameter. Returns a valid RealtimePort.
19
+ */
20
+ export declare function createRealtime(opts?: RealtimeOptions): RealtimePort;
21
+ /** Convenience re-exports */
22
+ export * from './port';
23
+ export * from './sim-adapter';
24
+ export * from './webrtc-adapter';
@@ -0,0 +1,38 @@
1
+ import { NotImplementedPort } from './port';
2
+ import { createSimAdapter } from './sim-adapter';
3
+ import { createWebRtcAdapter } from './webrtc-adapter';
4
+ /**
5
+ * createRealtime — main factory
6
+ *
7
+ * Auto-detects environment by checking NODE_ENV / import.meta.env
8
+ * or explicit `kind` parameter. Returns a valid RealtimePort.
9
+ */
10
+ export function createRealtime(opts = {}) {
11
+ const env = (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.MODE) ||
12
+ (typeof globalThis !== 'undefined' && globalThis.process && globalThis.process.env?.NODE_ENV) ||
13
+ 'development';
14
+ const kind = opts.kind || (env === 'development' ? 'sim' : 'webrtc');
15
+ try {
16
+ if (kind === 'sim') {
17
+ return createSimAdapter();
18
+ }
19
+ if (kind === 'webrtc') {
20
+ const cfg = {
21
+ signalUrl: opts.webrtc?.signalUrl || 'wss://signal.firecircle.dev',
22
+ circleId: opts.webrtc?.circleId || 'firecircle',
23
+ peerId: opts.webrtc?.peerId,
24
+ iceServers: opts.webrtc?.iceServers,
25
+ };
26
+ return createWebRtcAdapter(cfg);
27
+ }
28
+ return NotImplementedPort;
29
+ }
30
+ catch (err) {
31
+ console.error('[realtime] failed to create adapter', err);
32
+ return NotImplementedPort;
33
+ }
34
+ }
35
+ /** Convenience re-exports */
36
+ export * from './port';
37
+ export * from './sim-adapter';
38
+ export * from './webrtc-adapter';
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Realtime Port (Pad Core)
3
+ * -------------------------------------------------------
4
+ * A small interface that abstracts the network layer so the Garden can run
5
+ * over simulation, WebRTC, libp2p, etc., without changing UI code.
6
+ */
7
+ export type CircleId = string;
8
+ export type PeerId = string;
9
+ export type Topic = 'presence' | 'scenes' | 'pads' | 'assets';
10
+ export declare const TOPIC_PRESENCE: Topic;
11
+ export declare const TOPIC_SCENES: Topic;
12
+ export declare const TOPIC_PADS: Topic;
13
+ export declare const TOPIC_ASSETS: Topic;
14
+ /** Compose a namespaced topic for a circle (e.g., `presence:firecircle`). */
15
+ export declare function ns(circleId: CircleId, topic: Topic): string;
16
+ /**
17
+ * Versioned envelope for all realtime messages.
18
+ */
19
+ export type MessageEnvelope<T = unknown> = {
20
+ v: 1;
21
+ type: Topic;
22
+ circleId: CircleId;
23
+ sender: PeerId;
24
+ ts: number;
25
+ body: T;
26
+ };
27
+ /**
28
+ * RealtimePort — the single contract the app talks to.
29
+ *
30
+ * Implementations:
31
+ * - SimAdapter (playground, offline)
32
+ * - WebRtcAdapter (signaling via WS, data via WebRTC)
33
+ * - Future: Libp2pAdapter, etc.
34
+ */
35
+ export interface RealtimePort {
36
+ /** current connection status */
37
+ status(): 'disconnected' | 'connecting' | 'connected';
38
+ /** stable peer id for this device/session */
39
+ myPeerId(): PeerId;
40
+ /** Join a circle (room). Subsequent pub/sub calls refer to the joined circle. */
41
+ joinCircle(circleId: CircleId): Promise<void>;
42
+ /** Leave the currently joined circle (if any). */
43
+ leaveCircle(): Promise<void>;
44
+ /**
45
+ * Publish a message to a topic in the current circle.
46
+ * `payload` can be an object or a pre-encoded Uint8Array.
47
+ */
48
+ publish<T = unknown>(topic: Topic, payload: T | Uint8Array): void;
49
+ /**
50
+ * Subscribe to a topic in the current circle. Returns an unsubscribe fn.
51
+ * Handler receives the *decoded* payload if JSON, plus the raw envelope.
52
+ */
53
+ subscribe<T = unknown>(topic: Topic, handler: (payload: T, envelope: MessageEnvelope<T>) => void): () => void;
54
+ }
55
+ export declare function encodeJson(obj: unknown): Uint8Array;
56
+ export declare function decodeJson<T = unknown>(bytesOrObj: Uint8Array | T): T;
57
+ /** Small helper to stamp an envelope. */
58
+ export declare function envelope<T = unknown>(circleId: CircleId, sender: PeerId, type: Topic, body: T): MessageEnvelope<T>;
59
+ /** Type guards */
60
+ export declare function isUint8Array(v: unknown): v is Uint8Array;
61
+ export declare function isObject(v: unknown): v is Record<string, unknown>;
62
+ /**
63
+ * Factory hook-up placeholder — real adapters will live in sibling files.
64
+ * Keeping here for callers that want a single import site.
65
+ */
66
+ export type PortFactory = () => RealtimePort;
67
+ export declare const NotImplementedPort: RealtimePort;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Realtime Port (Pad Core)
3
+ * -------------------------------------------------------
4
+ * A small interface that abstracts the network layer so the Garden can run
5
+ * over simulation, WebRTC, libp2p, etc., without changing UI code.
6
+ */
7
+ export const TOPIC_PRESENCE = 'presence';
8
+ export const TOPIC_SCENES = 'scenes';
9
+ export const TOPIC_PADS = 'pads';
10
+ export const TOPIC_ASSETS = 'assets';
11
+ /** Compose a namespaced topic for a circle (e.g., `presence:firecircle`). */
12
+ export function ns(circleId, topic) {
13
+ return `${topic}:${circleId}`;
14
+ }
15
+ /** -----------------------------------------------------
16
+ * JSON/Binary helpers (loose, minimal)
17
+ * ----------------------------------------------------*/
18
+ const enc = typeof TextEncoder !== 'undefined' ? new TextEncoder() : undefined;
19
+ const dec = typeof TextDecoder !== 'undefined' ? new TextDecoder() : undefined;
20
+ export function encodeJson(obj) {
21
+ const text = JSON.stringify(obj);
22
+ return enc ? enc.encode(text) : new TextEncoder().encode(text);
23
+ }
24
+ export function decodeJson(bytesOrObj) {
25
+ if (bytesOrObj && typeof bytesOrObj === 'object' && !(bytesOrObj instanceof Uint8Array)) {
26
+ return bytesOrObj; // already an object
27
+ }
28
+ const str = dec ? dec.decode(bytesOrObj) : new TextDecoder().decode(bytesOrObj);
29
+ try {
30
+ return JSON.parse(str);
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ /** Small helper to stamp an envelope. */
37
+ export function envelope(circleId, sender, type, body) {
38
+ return { v: 1, type, circleId, sender, ts: Date.now(), body };
39
+ }
40
+ /** Type guards */
41
+ export function isUint8Array(v) {
42
+ return v instanceof Uint8Array;
43
+ }
44
+ export function isObject(v) {
45
+ return typeof v === 'object' && v !== null && !isUint8Array(v);
46
+ }
47
+ export const NotImplementedPort = {
48
+ status: () => 'disconnected',
49
+ myPeerId: () => 'peer:noop',
50
+ async joinCircle() {
51
+ throw new Error('RealtimePort not configured');
52
+ },
53
+ async leaveCircle() {
54
+ /* noop */
55
+ },
56
+ publish() {
57
+ /* noop */
58
+ },
59
+ subscribe() {
60
+ return () => {
61
+ /* noop */
62
+ };
63
+ },
64
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Realtime Registry (Pad Core)
3
+ * -------------------------------------------------------
4
+ * A tiny in-memory registry used by pad-core to *optionally* bridge
5
+ * DOM-based events with a realtime transport (Sim/WebRTC/etc.).
6
+ *
7
+ * Responsibilities:
8
+ * - Hold a single active RealtimePort (+ circleId) selected by the host app.
9
+ * - Notify listeners when the active port changes (connect/disconnect/swap).
10
+ * - Let features (e.g., scene-events) *query* the current port and circle.
11
+ *
12
+ * Notes:
13
+ * - This registry does not create connections. The host (e.g., Playground)
14
+ * should call `setRealtimePort(rt, circleId)` after a successful join.
15
+ * - Consumers can subscribe via `onRealtimePortChange` to attach topic
16
+ * subscriptions when a port becomes available.
17
+ */
18
+ import type { RealtimePort, CircleId } from './port.js';
19
+ /**
20
+ * Set the active realtime adapter and circle context.
21
+ * Call this from the host app (e.g., Playground) *after* joining a circle.
22
+ * Passing `null` disconnects the registry and notifies listeners.
23
+ */
24
+ export declare function setRealtimePort(port: RealtimePort | null, circleId?: CircleId | null): void;
25
+ /** Get the current active realtime adapter (or null if none). */
26
+ export declare function getRealtimePort(): RealtimePort | null;
27
+ /** Get the current circle id (or null if none is registered). */
28
+ export declare function getRealtimeCircleId(): CircleId | null;
29
+ /**
30
+ * Subscribe to changes of the active realtime port/circle.
31
+ * Returns an unsubscribe function. Safe to call multiple times.
32
+ */
33
+ export declare function onRealtimePortChange(listener: (port: RealtimePort | null, circleId: CircleId | null) => void): () => void;
@@ -0,0 +1,36 @@
1
+ // Singleton state for the current realtime adapter and circle context
2
+ let _port = null;
3
+ let _circleId = null;
4
+ // Subscribers notified whenever `setRealtimePort(...)` changes state
5
+ const listeners = new Set();
6
+ /**
7
+ * Set the active realtime adapter and circle context.
8
+ * Call this from the host app (e.g., Playground) *after* joining a circle.
9
+ * Passing `null` disconnects the registry and notifies listeners.
10
+ */
11
+ export function setRealtimePort(port, circleId) {
12
+ _port = port ?? null;
13
+ _circleId = circleId ?? null;
14
+ for (const listener of listeners) {
15
+ try {
16
+ listener(_port, _circleId);
17
+ }
18
+ catch { }
19
+ }
20
+ }
21
+ /** Get the current active realtime adapter (or null if none). */
22
+ export function getRealtimePort() {
23
+ return _port;
24
+ }
25
+ /** Get the current circle id (or null if none is registered). */
26
+ export function getRealtimeCircleId() {
27
+ return _circleId;
28
+ }
29
+ /**
30
+ * Subscribe to changes of the active realtime port/circle.
31
+ * Returns an unsubscribe function. Safe to call multiple times.
32
+ */
33
+ export function onRealtimePortChange(listener) {
34
+ listeners.add(listener);
35
+ return () => listeners.delete(listener);
36
+ }
@@ -0,0 +1,16 @@
1
+ import { type CircleId, type Topic, type MessageEnvelope, type RealtimePort } from './port';
2
+ export declare class SimAdapter implements RealtimePort {
3
+ private _peerId;
4
+ private _circleId;
5
+ private _status;
6
+ constructor();
7
+ status(): "disconnected" | "connecting" | "connected";
8
+ myPeerId(): string;
9
+ joinCircle(circleId: CircleId): Promise<void>;
10
+ leaveCircle(): Promise<void>;
11
+ publish<T = unknown>(topic: Topic, payload: T | Uint8Array): void;
12
+ subscribe<T = unknown>(topic: Topic, handler: (payload: T, envelope: MessageEnvelope<T>) => void): () => void;
13
+ private _emitSystem;
14
+ }
15
+ /** Factory to create a new SimAdapter easily */
16
+ export declare function createSimAdapter(): RealtimePort;