import type { Effect } from "effect"; import { Data } from "effect"; // --------------------------------------------------------------------------- // ScopeDb — the per-scope app database (the shared primitive). // // One SQL database per scope: tools write it, workflows schedule tools, UI // reads it, skills route the agent to it. The self-hosted backing is one libSQL // (SQLite) file per scope; the cloud backing (future) is a Durable Object // facet. The seam carries a template-tag `sql` (the author-facing shape) plus // per-table version counters: every write bumps the touched table's version so // the LiveChannel can publish an invalidation and widgets refetch. // // Scope isolation is structural: `forScope(a)` and `forScope(b)` are distinct // databases; there is no cross-scope query path. // --------------------------------------------------------------------------- export class ScopeDbError extends Data.TaggedError("ScopeDbError")<{ readonly message: string; readonly cause?: unknown; }> {} /** The author-facing db handle (matches `executor:app`'s `ScopeDb`): a tagged * template returning rows. Also exposes the tables a statement touched so the * runtime can bump version counters and publish invalidations. */ export interface ScopeDbHandle { readonly sql: >( strings: TemplateStringsArray, ...values: unknown[] ) => Effect.Effect; /** Run a raw statement (used by the runtime for probes/migrations). */ readonly exec: >( sql: string, params?: readonly unknown[], ) => Effect.Effect; /** Current version counter for a table (0 if never written). */ readonly tableVersion: (table: string) => Effect.Effect; /** Snapshot of every tracked table's version. */ readonly versions: () => Effect.Effect, ScopeDbError>; } /** A write that touched tables, with the versions after the bump. Emitted so * the runtime can drive `LiveChannel.publish`. */ export interface ScopeWriteEvent { readonly scope: string; readonly tables: readonly { readonly table: string; readonly version: number }[]; } export interface ScopeDb { readonly forScope: (scope: string) => Effect.Effect; /** * Subscribe to writes across scopes (the runtime wires this into LiveChannel). * Returns an unsubscribe thunk. Best-effort in-process; the cloud backing * makes the storage owner the notifier. */ readonly onWrite: (listener: (event: ScopeWriteEvent) => void) => () => void; readonly close: () => Effect.Effect; }