import updateShape from "src/ng-mock/wasm-land/updateShape"; import type { Connection, Diff, Scope, Shape } from "../types"; import requestShape from "src/ng-mock/wasm-land/requestShape"; import { applyDiff } from "./applyDiff"; import { deepSignal, watch, batch } from "alien-deepsignals"; import type { DeepPatch, DeepSignalObject } from "alien-deepsignals"; import type { CompactShapeType } from "node_modules/@ldo/ldo/dist/types/ShapeType"; import type { LdoCompactBase } from "@ldo/ldo"; interface PoolEntry { key: string; shape: CompactShapeType; scopeKey: string; signalObject: DeepSignalObject; refCount: number; stopListening: (() => void) | null; registerCleanup?: (fn: () => void) => void; connectionId?: string; ready: Promise; // resolves to connectionId resolveReady: (id: string) => void; suspendDeepWatcher: boolean; } const pool = new Map>(); function canonicalScope(scope: Scope | undefined): string { if (scope == null) return ""; return Array.isArray(scope) ? scope.slice().sort().join(",") : String(scope); } export function deepPatchesToDiff(patches: DeepPatch[]): Diff { return patches.map((patch) => { const path = "/" + patch.path.join("/"); return { ...patch, path }; }) as Diff; } export function createSignalObjectForShape( shape: CompactShapeType, scope?: Scope, poolSignal = true ) { const scopeKey = canonicalScope(scope); const key = `${shape}::${scopeKey}`; if (poolSignal) { const existing = pool.get(key); if (existing) { existing.refCount++; return buildReturn(existing); } } const signalObject = deepSignal({}); let resolveReady!: (id: string) => void; const ready = new Promise((res) => (resolveReady = res)); const entry: PoolEntry = { key, shape, scopeKey, signalObject, refCount: 1, stopListening: null, registerCleanup: undefined, connectionId: undefined, ready, resolveReady, suspendDeepWatcher: false, }; if (poolSignal) pool.set(key, entry); const onUpdateFromDb = (diff: Diff, connectionId: Connection["id"]) => { console.debug("[shape][diff] applying", connectionId, diff); entry.suspendDeepWatcher = true; batch(() => applyDiff(signalObject, diff)); queueMicrotask(() => { entry.suspendDeepWatcher = false; }); }; requestShape(shape, scope, onUpdateFromDb).then( ({ connectionId, shapeObject }) => { entry.connectionId = connectionId; entry.suspendDeepWatcher = true; batch(() => { for (const k of Object.keys(shapeObject)) { (signalObject as any)[k] = (shapeObject as any)[k]; } }); const watcher = watch(signalObject, ({ patches }) => { if (entry.suspendDeepWatcher || !patches.length) return; const diff = deepPatchesToDiff(patches); updateShape(connectionId as any, diff as any); }); entry.stopListening = watcher.stopListening; entry.registerCleanup = watcher.registerCleanup; queueMicrotask(() => { entry.suspendDeepWatcher = false; }); entry.resolveReady(connectionId); } ); function buildReturn(entry: PoolEntry) { const release = () => { if (entry.refCount > 0) entry.refCount--; if (entry.refCount === 0) { entry.stopListening?.(); if (poolSignal) pool.delete(entry.key); } }; return { signalObject: entry.signalObject, stop: release, ready: entry.ready, // Promise get connectionId() { return entry.connectionId; }, registerCleanup: entry.registerCleanup, }; } return buildReturn(entry); }