/** * Path-keyed domain binding registry — the single owner of every per-domain * Drizzle wrapper in the CLEO runtime. * * ## Why this module exists (E6-L13 · T12037) * * Before this leaf, each store facade (`sqlite.ts`, `memory-sqlite.ts`, * `conduit-sqlite.ts`, `nexus-sqlite.ts`, `skills-db.ts`) owned its OWN * process-global singleton quartet: * * ```ts * let _db: NodeSQLiteDatabase | null = null; * let _nativeDb: DatabaseSync | null = null; * let _dbPath: string | null = null; * let _initPromise: Promise | null = null; * ``` * * Five independent caches over ONE shared `DatabaseSync` produced a documented * class of defects: * * - **Last-project-wins** — `_dbPath` held a single project. Touching project B * reset the singleton, so an interleaved project-A query re-opened and * re-migrated on every alternation (thrash, not just slowness). * - **Cross-domain staleness** — the tasks singleton could reference a * `DatabaseSync` that the brain domain had already closed (T12019/T12020), * surfacing as `database is not open` or, worse, a silently-nulled * `sourceSessionId`. * - **Band-aid retry loops** — both `getDb` and `getBrainDb` grew bounded * re-acquisition loops (T12035) purely to paper over the above. * * This module replaces all five with ONE registry keyed by * `${scope}::${canonical dbPath}::${domain}`, layered on the * {@link CleoRuntime} store registry (T12036). A binding is valid only while * the {@link ProjectStore} / {@link GlobalStore} instance it was established * against is still the one the runtime hands out AND its native handle is * open. Any eviction — a `close()`, a `_resetDualScopeDbCache`, an external * teardown — yields a fresh store object, which invalidates every binding * derived from it by identity comparison. No liveness polling, no retry loop, * no "last opened project". * * ## What a domain owns after this leaf * * A domain owns its `establish` function — the schema reconciliation it must * run against a native handle (legacy Drizzle wrapping, `runMigrations`, * vec0 extension loading, seed rows). It no longer owns caching, path * resolution, single-flight, or liveness. Those are this module's job. * * @packageDocumentation * @task T12037 (E6-L13) * @epic T11249 (E6) * @saga T11242 (SG-DB-SUBSTRATE-V2) */ import type { DatabaseSync } from 'node:sqlite'; import { type CleoRuntime, type GlobalStore, type ProjectStore } from '../dual-scope-db.js'; /** * Get (or lazily create) the process-wide {@link CleoRuntime}. * * @returns The shared runtime store registry. */ export declare function getCleoRuntime(): CleoRuntime; /** * Tear down the process-wide runtime: close every registry entry and drop * every domain binding. The next {@link getCleoRuntime} call builds a fresh * registry. * * Used by full-teardown paths (`closeAllDatabases`) and by tests that need a * pristine process state between cases. */ export declare function resetCleoRuntime(): void; /** * A domain's live binding to one store. * * @typeParam TDb - The domain's Drizzle handle type (its legacy schema shape). */ export interface DomainBinding { /** The store this binding was established against. */ readonly store: TStore; /** The native `DatabaseSync` extracted from {@link store}. */ readonly native: DatabaseSync; /** The domain-typed Drizzle handle produced by the domain's `establish`. */ readonly db: TDb; } /** * Bind a project-scope domain schema to the {@link ProjectStore} for `cwd`. * * This is the replacement for every `getXxxDb(cwd)` project-domain facade. * The returned binding carries the store itself, so callers that need a * cross-domain transaction hold an explicit handle rather than reaching for a * process global. * * @typeParam TDb - The domain's Drizzle handle type. * @param domain - Stable domain id; part of the registry key. * @param cwd - Project working directory. Resolved to the canonical * `cleo.db` path and forwarded as `exodusCwd` so a port open arms the * exodus-on-open auto-migration exactly like the facade it replaces. * @param establish - Reconcile the domain's schema against the native handle * and return the domain-typed Drizzle instance. Called once per * (path, domain, store instance); MUST be idempotent because a store * eviction re-runs it. * @returns The live {@link DomainBinding}. */ export declare function bindProjectDomain(domain: string, cwd: string | undefined, establish: (native: DatabaseSync, store: ProjectStore) => TDb | Promise): Promise>; /** * Bind a project-scope domain schema to an EXPLICIT database path. * * Used by domains whose legacy lifecycle API accepted an on-disk path (test * fixtures, snapshot inspection). Never arms exodus-on-open — an explicit * path must not auto-migrate a legacy fleet into a fixture. * * @typeParam TDb - The domain's Drizzle handle type. * @param domain - Stable domain id. * @param dbPath - Absolute path to a consolidated `cleo.db`. * @param establish - See {@link bindProjectDomain}. */ export declare function bindProjectDomainAtPath(domain: string, dbPath: string, establish: (native: DatabaseSync, store: ProjectStore) => TDb | Promise): Promise>; /** * Bind a global-scope domain schema to the {@link GlobalStore}. * * This is the replacement for every `getXxxDb()` global-domain facade * (nexus, skills, agents, telemetry). * * @typeParam TDb - The domain's Drizzle handle type. * @param domain - Stable domain id; part of the registry key. * @param establish - See {@link bindProjectDomain}. * @param cwd - Optional cwd forwarded as `exodusCwd` for the global scope's * own legacy-fleet auto-migration. */ export declare function bindGlobalDomain(domain: string, establish: (native: DatabaseSync, store: GlobalStore) => TDb | Promise, cwd?: string): Promise>; /** * Bind a global-scope domain schema to an EXPLICIT database path. * * The path-aware sibling of {@link bindGlobalDomain}, for domains whose * lifecycle API accepts an on-disk path — notably the skills registry's * test-sandbox `{ path }` override. Never arms exodus-on-open. * * @typeParam TDb - The domain's Drizzle handle type. * @param domain - Stable domain id. * @param dbPath - Absolute path to a global-scope `cleo.db`. * @param establish - See {@link bindProjectDomain}. */ export declare function bindGlobalDomainAtPath(domain: string, dbPath: string, establish: (native: DatabaseSync, store: GlobalStore) => TDb | Promise): Promise>; /** * Synchronously read an ALREADY-established binding at an EXPLICIT path. * * @typeParam TDb - The domain's Drizzle handle type. * @param scope - Dual-scope discriminator. * @param dbPath - Absolute database path. * @param domain - Stable domain id. */ export declare function peekDomainAtPath(scope: 'project' | 'global', dbPath: string, domain: string): DomainBinding | null; /** * Drop cached bindings. * * Dropping a binding does NOT close any connection — the runtime registry and * the dual-scope chokepoint own connection lifecycle. This only forces the * next bind to re-run the domain's `establish`. * * @param filter - Restrict the drop. Omit any field to match all values of it. * `{ scope: 'project' }` drops every project-domain binding; * `{ domain: 'brain' }` drops the brain binding for every project. */ export declare function releaseDomainBindings(filter?: { scope?: 'project' | 'global'; dbPath?: string; domain?: string; }): void; /** * Drop every cached binding in every scope. Equivalent to * `releaseDomainBindings()` with no filter; named for call-site clarity in * teardown paths. */ export declare function releaseAllDomainBindings(): void; /** * Snapshot of currently-established binding keys. * * Read-only diagnostic surface for `cleo health` / architecture tests that * assert a process holds exactly one binding per (path, domain). */ export declare function boundDomainKeys(): ReadonlySet; /** * Synchronously look up the native `DatabaseSync` of an ALREADY-established * project-domain binding. * * This exists solely to keep the legacy synchronous native getters * (`getNativeDb`, `getNativeTasksDb`, `getBrainNativeDb`) working while their * call sites migrate to the async binding. Unlike the module-global they * replace, this lookup is **path-keyed** — passing the caller's `cwd` returns * that project's handle rather than "whichever project was opened last". * * Returns `null` when nothing is bound yet or the connection has since * closed; callers must have awaited the domain's async binder first. * * @param domain - Stable domain id. * @param cwd - Project working directory; resolved to the canonical path. * @returns The live native connection, or `null`. * * @deprecated Read `.native` from the {@link DomainBinding} instead. Removed * when the last synchronous native getter is retired (T12040 · E6-L16). */ export declare function boundProjectNative(domain: string, cwd?: string): DatabaseSync | null; /** * Synchronously read an ALREADY-established project-domain binding without * opening anything. * * Returns `null` when the domain is unbound for that project or its * connection has closed. Unlike {@link bindProjectDomain} this never triggers * an open, so it is safe on synchronous code paths (accessor predicates, * health probes) that must not force a migration. * * @typeParam TDb - The domain's Drizzle handle type, as passed to * {@link bindProjectDomain}. Unchecked at runtime — the caller is * responsible for using the same type it bound with. * @param domain - Stable domain id. * @param cwd - Project working directory. */ export declare function peekProjectDomain(domain: string, cwd?: string): DomainBinding | null; /** * Synchronous sibling of {@link peekProjectDomain} for the global scope. * * @typeParam TDb - The domain's Drizzle handle type. * @param domain - Stable domain id. */ export declare function peekGlobalDomain(domain: string): DomainBinding | null; /** * Synchronous sibling of {@link boundProjectNative} for the global scope. * * @param domain - Stable domain id. * @returns The live native connection, or `null`. * * @deprecated See {@link boundProjectNative}. */ export declare function boundGlobalNative(domain: string): DatabaseSync | null; //# sourceMappingURL=domain-binding.d.ts.map