UNPKG

trellis

Version:

Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications

212 lines (209 loc) 6.27 kB
import { bindingEntityId, bindingToEntity, entitiesQuery, entityQuery, isSparseBinding, resolveRelations } from "./chunk-JUEMDWLU.js"; import { Signal } from "./chunk-UBGUDMUV.js"; // src/client/live.ts function liveQuery(client, eql) { const signal = new Signal({ data: [], loading: true, error: null }); let sub = null; return { signal, start() { if (sub) return () => stop(); try { sub = client.subscribe(eql, (rows) => { signal.value = { data: rows, loading: false, error: null }; }); } catch (err) { signal.value = { data: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }; } return () => stop(); } }; function stop() { sub?.unsubscribe(); sub = null; } } function normalizeLiveEntitiesOpts(whereOrOpts) { if (!whereOrOpts) return {}; if ("resolve" in whereOrOpts || "where" in whereOrOpts) { return whereOrOpts; } return { where: whereOrOpts }; } function liveEntities(client, typeOrSchema, whereOrOpts) { const schema = typeof typeOrSchema === "string" ? void 0 : typeOrSchema; const type = typeof typeOrSchema === "string" ? typeOrSchema : typeOrSchema.type; const { where, resolve } = normalizeLiveEntitiesOpts(whereOrOpts); const signal = new Signal({ data: [], loading: true, error: null }); let sub = null; let token = 0; return { signal, start() { if (sub) return () => stop(); try { const serverResolve = schema && resolve && Object.keys(resolve).length > 0; sub = client.subscribe( entitiesQuery(type, where), (rows, _diff, meta) => { const turn = ++token; const hydrate = async () => hydrateSubscriptionRows(client, rows, meta); hydrate().then(async (entities) => { if (turn !== token) return; let data = entities; if (schema && resolve && Object.keys(resolve).length > 0 && !meta?.resolved) { data = await resolveRelations( client, schema, data, resolve ); } if (turn !== token) return; signal.value = { data, loading: false, error: null }; }).catch((err) => { if (turn !== token) return; signal.value = { data: signal.peek().data, loading: false, error: err instanceof Error ? err : new Error(String(err)) }; }); }, serverResolve ? { entityType: type, resolve } : void 0 ); } catch (err) { signal.value = { data: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }; } return () => stop(); } }; function stop() { token++; sub?.unsubscribe(); sub = null; } } async function hydrateSubscriptionRows(client, rows, meta) { if (rows.length === 0) return []; if (meta?.resolved) return rows; if (rows.some((r) => isSparseBinding(r))) { const ids = rows.map((r) => bindingEntityId(r)).filter((id) => Boolean(id)); const loaded = await Promise.all(ids.map((id) => client.read(id))); return loaded.filter((e) => e != null); } return rows.map((r) => bindingToEntity(r)); } async function pickEntityFromRows(client, rows, entityId, meta) { const hydrated = await hydrateSubscriptionRows(client, rows, meta); const hit = hydrated.find((r) => r.id === entityId); if (hit) return hit; if (hydrated.length === 0) return client.read(entityId); return null; } function liveEntity(client, typeOrSchema, id, opts) { const schema = typeof typeOrSchema === "string" ? void 0 : typeOrSchema; const type = typeof typeOrSchema === "string" ? typeOrSchema : typeOrSchema.type; const { resolve } = opts ?? {}; const signal = new Signal({ data: null, loading: Boolean(id), error: null }); let sub = null; let token = 0; return { signal, start() { if (!id) { signal.value = { data: null, loading: false, error: null }; return () => { }; } if (sub) return () => stop(); const entityId = id; const serverResolve = Boolean(schema) && Boolean(resolve && Object.keys(resolve).length > 0); const applyRow = async (row, turn) => { if (turn !== token) return; if (!row) { signal.value = { data: null, loading: false, error: null }; return; } let data = row; if (schema && resolve && Object.keys(resolve).length > 0 && !serverResolve) { const [resolved] = await resolveRelations(client, schema, [data], resolve); data = resolved ?? data; } if (turn !== token) return; signal.value = { data, loading: false, error: null }; }; const readGen = token; client.read(entityId).then((row) => applyRow(row, readGen)).catch((err) => { signal.value = { data: signal.peek().data, loading: false, error: err instanceof Error ? err : new Error(String(err)) }; }); try { sub = client.subscribe( entityQuery(type, entityId), (rows, _diff, meta) => { const turn = ++token; pickEntityFromRows(client, rows, entityId, meta).then((row) => applyRow(row, turn)).catch((err) => { if (turn !== token) return; signal.value = { data: signal.peek().data, loading: false, error: err instanceof Error ? err : new Error(String(err)) }; }); }, serverResolve ? { entityType: type, resolve } : void 0 ); } catch (err) { signal.value = { data: null, loading: false, error: err instanceof Error ? err : new Error(String(err)) }; } return () => stop(); } }; function stop() { token++; sub?.unsubscribe(); sub = null; } } export { liveQuery, liveEntities, liveEntity };