/** * Boot helper — register system-owned durable embed CDC consumers for every * `.embed()` column in the Manifest. Writer flows stay free of `effects.embeds`. */ import type { Manifest } from "../../manifest/types.ts"; import type { Binding } from "../../kernel/on.ts"; import { flow } from "../../kernel/flow.ts"; import { applySearchEmbedCdc, searchEmbedFlowName, tablesNeedingSearchEmbed, } from "./search-embed-flow.ts"; /** * Adopt `_oke_search_embed_` durable CDC bindings for each embed-declared * table. Idempotent on flow name — safe to call once at boot after Manifest is * known. Prefer {@link adopt} with real CDC {@link Binding}s so * `dispatchCdc` runs them (bare `app.adopt([flow])` only registers the flow). * * @param adopt - App binding adopter (`adoptBinding` inside `oke()`) * @param manifest - Project manifest */ export function bindSearchEmbedFlows( adopt: (binding: Binding) => void, manifest: Manifest, ): readonly string[] { const tables = tablesNeedingSearchEmbed(manifest); const names: string[] = []; for (const t of tables) { const name = searchEmbedFlowName(t.table); names.push(name); const models = [...new Set(t.columns.map((c) => c.model ?? "default"))]; const sqlRef = `sql:${t.store}` as const; const embedFlow = flow(name, { plane: "operator", durable: true, effects: { reads: [sqlRef], writes: [sqlRef], embeds: models, }, do: async (payload: { before: unknown; after: unknown }, fx) => { await fx.step(`embed:${t.table}`, async () => { await applySearchEmbedCdc( fx, t.table, t.pk, t.columns, { before: (payload.before ?? null) as Record | null, after: (payload.after ?? null) as Record | null, }, sqlRef, ); }); }, }); const binding: Binding = { trigger: { kind: "cdc", table: t.table, store: t.store }, flow: embedFlow, }; adopt(binding); } return names; }