import { ChangeEvent } from '@voltro/database'; import { ChangeStrategy } from '@voltro/database'; import { ConfigError } from 'effect'; import { ConfigError as ConfigError_2 } from 'effect/ConfigError'; import { ConnectionConfig } from '@voltro/database'; import { Context } from 'effect'; import { DataStore } from '@voltro/database'; import { Effect } from 'effect'; import { EventEmitter } from 'node:events'; import { InsertIgnoreResult } from '@voltro/database'; import { Layer } from 'effect'; import { ManagedRuntime } from 'effect'; import { Predicate } from '@voltro/database'; import { QueryDescriptor } from '@voltro/database'; import { QueryNamespace } from '@voltro/database'; import { RawSqlFragment } from '@voltro/database/sql'; import { RetryDecision } from '@voltro/database'; import { Row } from '@voltro/database'; import { RowKey } from '@voltro/database'; import { SqlClient } from '@effect/sql'; import { SqlClient as SqlClient_2 } from '@effect/sql/SqlClient'; import { SqlDialect } from '@voltro/database'; import { SqlError } from '@effect/sql'; import { SqlError as SqlError_2 } from '@effect/sql/SqlError'; import { SqliteClient } from '@effect/sql-sqlite-node'; import { TransactionConnection } from '@effect/sql/SqlClient'; import { UpsertOptions } from '@voltro/database'; import { WriteAttribution } from '@voltro/database'; /** * Parse a `ConnectionConfig` into the sqlite-specific `SqliteConnection` * shape. Sqlite is the simplest dialect — it only needs a filename. * * Resolution order: * 1. `url` — accepted forms documented at file head * 2. `database` — treated as the filename * 3. Throw with a clear message */ export declare const connectionFromConfig: (config: ConnectionConfig) => SqliteConnection; export declare const makeSqliteSqlLayer: (options: SqliteConnection) => SqliteSqlLayer; export declare const makeSqliteSqlLayerFromConfig: (config: ConnectionConfig) => SqliteSqlLayer; export declare const makeSqliteStore: (options: SqliteStoreOptions) => Promise; export { SqliteClient } export declare interface SqliteConnection { /** Absolute or `file:` path, or `:memory:` for ephemeral. */ readonly filename: string; readonly readonly?: boolean; } export declare const sqliteDialect: SqlDialect; export declare const sqliteRetryFilter: (err: unknown) => RetryDecision; /** * The connection layer this package produces: the driver's client layer plus * the per-connection `PRAGMA foreign_keys = ON` tap (which adds `SqlError` * to the error channel — matching the cross-dialect `sqlLayer` shape in * `@voltro/database`). */ declare type SqliteSqlLayer = Layer.Layer; export declare class SqliteStore implements DataStore { private readonly sql; private readonly runtime; private readonly namespace; private readonly isRetryable; private readonly systemName; private readonly wrapTransaction; private readonly emitter; private inflightTxns; /** PERF-23 — counts EVERY eager JSON-agg → walker degradation and logs a * rate-limited line. Instance-scoped so a second store in one process cannot * silence this one's first warning. */ private readonly reportEagerFallback; constructor(sql: SqlClient.SqlClient, runtime: ManagedRuntime.ManagedRuntime, namespace?: QueryNamespace, sharedEmitter?: EventEmitter, isRetryable?: (e: unknown) => boolean, systemName?: string, wrapTransaction?: (e: Effect.Effect) => Effect.Effect); /** * Bind a per-request tenant namespace, returning a view that qualifies * every table reference to `.`. The view SHARES this * store's connection + emitter (sqlite is single-process). The * namespace's attached database must already exist — see * `attachNamespace` / the migrator's namespace fan-out. */ withNamespace(namespace: string | null): DataStore; /** Qualify a table to this view's namespace (no-op when unset). */ private nsT; /** * Provision a tenant's namespace: `ATTACH DATABASE '' AS * `. Idempotent enough for first-use lazy-create — sqlite * errors if the alias is already attached, which the caller treats as * "already provisioned". The file path defaults to `.db` * beside the main database. */ attachNamespace(namespace: string, file?: string): Promise; private executeQuery; private executeInsert; private executeUpdate; private executeInsertMany; private executePatchJson; private executeDelete; /** * Append a row inside the caller's transaction — what an in-transaction * recorder is handed. Deliberately NOT `executeInsert`: that would re-enter * `routeEvent` and emit a change event for the trail's own table. This is the * narrow insert-only path, bound to the same `txn`, so the trail row commits * or rolls back with the write it records. */ private appendInTxn; /** * The other half of the recorder port: ONE aggregate on the caller's * connection, so an append-only trail can number its own entries. Equality * filter only — see `writeRecorder.ts` for why it is this narrow. */ private maxInTxn; private routeEvent; private executeUpsert; private executeInsertIgnore; private findByConflict; query(descriptor: QueryDescriptor): Promise>; raw(fragment: RawSqlFragment, _opts?: { dependsOn?: ReadonlyArray; }): Promise>; /* Excluded from this release type: rawOn */ private runWithEager; getInternalRunWithEager(): (d: QueryDescriptor, txn: TxnContext | null) => Promise>; /** * Bracket a NON-transactional write: the attribution capture every public * write already did, plus the ONE metrics funnel for this dialect's writes * (`voltro_db_queries_total{op}`). * * Named to match postgres' and mysql's `localWrite` on purpose. Those two * additionally hold the table's transport echoes for the write's duration — * a CDC-only concern this dialect does not have (sqlite emits inline, one * process) — so this one is the attribution + metrics half only. One name for * the write funnel across four stores is what makes the parity test able to * ask the question at all. */ private localWrite; /** * A write and its write-recorders, as ONE unit — see `MysqlStore. * writeWithRecorders` for the failure this closes. * * The same divergence exists here even though sqlite is one process: the row's * statement commits on its own and the recorder's INSERT is a second one, so a * recorder that throws leaves a committed row behind a write that reported * failure — and a caller that retries meets its own row. */ private writeWithRecorders; insert(table: string, row: Row): Promise; insertMany(table: string, rows: ReadonlyArray): Promise>; patchJson(table: string, primaryKey: RowKey, path: string, value: unknown, additionalPatch?: Readonly>): Promise; update(table: string, primaryKey: RowKey, patch: Readonly>): Promise; delete(table: string, primaryKey: RowKey): Promise; updateMany(table: string, patch: Readonly>, options: { where: Predicate; }): Promise; deleteMany(table: string, options: { where: Predicate; }): Promise; /* Excluded from this release type: executeUpdateMany */ /* Excluded from this release type: executeDeleteMany */ upsert(table: string, row: Row, options: UpsertOptions): Promise; insertIgnore(table: string, row: Row, options: { conflictColumns: ReadonlyArray; }): Promise; insertIgnoreWithOutcome(table: string, row: Row, options: { conflictColumns: ReadonlyArray; }): Promise; /* Excluded from this release type: emitChange */ /* Excluded from this release type: getInternalExecuteQuery */ /* Excluded from this release type: getInternalExecuteInsert */ /* Excluded from this release type: getInternalExecuteInsertMany */ /* Excluded from this release type: getInternalExecutePatchJson */ /* Excluded from this release type: getInternalExecuteUpdate */ getInternalExecuteUpdateMany(): (table: string, patch: Readonly>, options: { where: Predicate; }, txn: TxnContext | null, events: Array | null, attr?: WriteAttribution | undefined) => Promise; getInternalExecuteDeleteMany(): (table: string, options: { where: Predicate; }, txn: TxnContext | null, events: Array | null, attr?: WriteAttribution | undefined) => Promise; /* Excluded from this release type: getInternalExecuteDelete */ /* Excluded from this release type: getInternalExecuteUpsert */ /* Excluded from this release type: getInternalExecuteInsertIgnore */ /** * Run `work` inside a real transaction. Same architecture as the * postgres impl: per-call view + captured TxnContext + events buffer; * commit drains, throw discards. Each attempt builds a FRESH view, so a * retried attempt cannot double-emit (events drain only on success). * * Retry uses the injected `isRetryable` predicate — sqlite's * `SQLITE_BUSY` / `SQLITE_LOCKED`, or (Turso dialect) the MVCC * `"Write-write conflict"` from `BEGIN CONCURRENT`. A conflict can * surface from the COMMIT, which `@effect/sql` runs as `Effect.orDie` * → it arrives as a DEFECT, not a typed failure, and `Effect.retry` * does not retry defects. So we promote a retryable defect back to a * typed failure before the schedule sees it (and re-die on a genuine * defect, preserving crash semantics). Without this the Turso MVCC * retry — the whole point of `BEGIN CONCURRENT` — would never fire. */ /** * Empty `tables` as one unit — see `DataStore.emptyTables`. * * SQLite ships the exact switch for this: `defer_foreign_keys` holds every * foreign-key check until COMMIT, and unlike `PRAGMA foreign_keys` it is * settable INSIDE a transaction (that one is a silent no-op there, which is * the trap to avoid). So the whole set empties in any order and integrity is * verified once, at the end — which is what makes a self-referencing table * possible to clear at all. * * The pragma resets itself at COMMIT, so there is nothing to restore; the * explicit reset below covers the ROLLBACK path, where it does not. */ emptyTables(tables: ReadonlyArray): Promise; /** See `DataStore.incomingForeignKeys`. SQLite has no catalog VIEW of foreign * keys — `PRAGMA foreign_key_list(t)` answers per table — so this walks the * table list once. That is fine at the one call site (a pre-flight, once per * import) and is why it is not a general-purpose helper. */ incomingForeignKeys(tables: ReadonlyArray): Promise>; /* Excluded from this release type: emptyTablesOn */ /* Excluded from this release type: transactionEndingFailure */ transactional(work: (tx: DataStore) => Promise): Promise; /** * The DIALECT half of the shared transaction bracket * (`runStoreTransaction` in `@voltro/database`). * * sqlite/turso own four things: the client's `withTransaction`, the runtime, * the injected `isRetryable` predicate, and `wrapTransaction` — the fiber flag * that upgrades turso's `BEGIN` to `BEGIN CONCURRENT`. Retry, commit-defect * promotion, attribution threading and exit settling are shared; the * defect-promotion in particular lived HERE and only here for a release, which * is why it is no longer a per-store decision. See `transactionOutcome.ts`. */ private txnSpec; onChange(listener: (event: ChangeEvent) => void): () => void; /** Cross-instance reactivity seam — emit an externally-sourced event to * local subscribers without re-persisting. sqlite is single-process so * this is unused in practice, but the seam keeps the DataStore contract * uniform. See `DataStore.injectExternalChange`. */ injectExternalChange(event: ChangeEvent): void; run(effect: Effect.Effect): Promise; close(gracePeriodMs?: number): Promise; /** Liveness probe — `SELECT 1`. Rejects if the pool can't answer. */ ping(): Promise; } export declare interface SqliteStoreOptions { readonly sqlLayer: Layer.Layer; readonly tracerLayer?: Layer.Layer; /** Accepted for cross-dialect symmetry; ignored (always in-process). */ readonly changeStrategy?: ChangeStrategy; /** * Predicate deciding whether a failed transaction is a transient * contention error worth retrying. Defaults to sqlite's busy/locked * codes. The Turso dialect injects its own (matches the * `"Write-write conflict"` message its MVCC `BEGIN CONCURRENT` raises). */ readonly isRetryable?: (e: unknown) => boolean; /** `db.system` span attribute on `store.transactional`. Default `'sqlite'`. */ readonly systemName?: string; /** * Wraps each `transactional()` program before it runs. The Turso dialect uses * it to set a fiber flag that upgrades `BEGIN` → `BEGIN CONCURRENT` for the * store's DML (DDL paths keep plain BEGIN). Default: identity (sqlite). */ readonly wrapTransaction?: (effect: Effect.Effect) => Effect.Effect; } declare type TxnContext = Context.Tag.Service; export { }