import { type Task, type TaskStatus } from "../models.js"; import { type BackpressureOptions } from "../backpressure.js"; /** Encode a value for a protocol JSON column, raising SerializationError on * anything JSON cannot represent. Refuses what JSON.stringify would silently * mangle into `null`: NaN/Infinity anywhere, undefined/function/symbol inside an * array, and a top-level undefined that disappears entirely — either way the * twin SDK reads back something other than what the caller meant (the Python * SDK rejects the same values, via allow_nan=False). */ export declare function dumpJson(value: unknown): string; /** Refuse to run against a store whose protocol major this SDK does not speak. * The supported major is a protocol fact, not a dialect one — every backend * checks it here so the constant can't fork per store. */ export declare function checkProtocolVersion(version: number): void; declare const CONFLICTS: readonly ["reuse", "reject", "replace"]; export type Conflict = (typeof CONFLICTS)[number]; /** The queue a submit lands on when it names none. Owned here, where the * default is applied, so nothing above has to re-derive it. */ export declare const DEFAULT_QUEUE = "default"; export interface SubmitInput { name: string; payload: unknown; queue?: string; key?: string | null; conflict?: Conflict; maxAttempts?: number; priority?: number; metadata?: unknown; parentId?: string | null; rootId?: string | null; correlationId?: string | null; runAtDelayMs?: number; } export interface ListInput { status?: TaskStatus | null; queue?: string | null; name?: string | null; rootId?: string | null; correlationId?: string | null; limit?: number; offset?: number; } export interface PurgeInput { olderThanMs?: number; limit?: number; } export type Params = Record; /** Runs one named protocol statement and returns its rows. */ export type Fetch = (name: string, params: Params) => Promise; export declare const LEASE_EXPIRED_ERROR_JSON: string; /** Strips SQL line comments, so a `:name` in a header comment isn't a parameter. */ export declare const COMMENT: RegExp; /** A `:name` placeholder. The lookbehind spares Postgres `::type` casts. */ export declare const NAMED: RegExp; /** * The parameter names a statement binds, in first-appearance order. * * Callers pass a superset of parameters and each dialect takes what its own SQL * asks for — that is what lets one call site serve both dialects even though e.g. * SQLite binds `:lease_until_ms` where Postgres binds `:lease_ms`. This is the one * place that decides what counts as a parameter; both dialects' binding goes * through it. */ export declare function statementParams(sql: string): readonly string[]; /** * The storage seam. * * A backend supplies three things: how to run one protocol statement, how to run * several inside a transaction, and how its dialect binds parameters. Everything * above that — the submit conflict branches, the *_by_key lookups, the * recover-then-claim sequence, the ownership-checked writes — lives here once, * because those are protocol decisions rather than storage decisions. Keeping * them in one place is what stops SQLite and Postgres from drifting apart in * behavior; the shared SQL already stops them from drifting in wording. */ export declare abstract class TaskStore { /** Set by useBackpressure; null means submit is ungated. */ private gate; abstract connect(): Promise; abstract close(): Promise; abstract protocolVersion(): Promise; /** Run one protocol statement outside a transaction, connecting if needed. */ protected abstract fetch(name: string, params: Params): Promise; /** * Run several statements atomically; `fn` receives a Fetch bound to the txn. * * A backend may invoke `fn` more than once, retrying the transaction after a * transient failure (SQLite does, on write-lock contention). So `fn` must be * replayable: derive nothing inside it that the caller cannot derive twice — * build ids and payloads before opening the transaction, not within it. */ protected abstract tx(fn: (fetch: Fetch) => Promise): Promise; /** * Whether it is worth opening the claim transaction at all. SQLite gates its * single write lock behind a read-only probe; Postgres readers don't block * writers, so it just says yes. */ protected hasClaimableWork(_params: Params): Promise; /** Resolves when a task may have become claimable on one of `queues`. The * timer is unref'd: the worker races this against its own stop-aware, ref'd * sleep, so it must neither hold the process open nor need clearing. */ claimWake(_queues: string[], timeoutMs: number): Promise; /** Resolves when `taskId` may have gone terminal. Plain ref'd sleep — * pollWait awaits it directly, so it is what keeps the process alive. */ taskDoneWake(_taskId: string, timeoutMs: number): Promise; /** * An ownership-checked worker write (heartbeat/progress/succeed/complete/fail). * Each statement's WHERE pins worker_id + a live lease, so 0 rows back means * the lease was lost — every such write reports it the same way. */ private ownedWrite; private static one; /** * Bound how deep a queue may get before `submit` blocks. Off unless set. * * It hangs here rather than on `CairnQ` because the store is the one choke * point every submit passes through — a handler spawning children via * `TaskContext.submit` is the shape most likely to outrun its workers, and * gating only the client would leave exactly that path unbounded. */ useBackpressure(opts: BackpressureOptions): void; submit(input: SubmitInput): Promise; get(taskId: string): Promise; getByKey(key: string): Promise; list(input?: ListInput): Promise; cancel(taskId: string): Promise; retry(taskId: string, opts?: { resetAttempt?: boolean; }): Promise; cancelByKey(key: string): Promise; retryByKey(key: string, opts?: { resetAttempt?: boolean; }): Promise; /** * Resolve a key to the task it currently points at, then act on that task — * under the key's lock, so a concurrent `replace` can't repoint the key * between the lookup and the write (the transaction alone is not enough on * Postgres; see lock_key.sql). */ private byKey; /** * Delete terminal tasks that completed more than `olderThanMs` ago and return * their ids. Nothing else removes rows, so a long-lived database needs this * called periodically. Bounded by `limit` to keep each sweep a short write; * call it in a loop until it returns fewer than `limit`. */ purge(input?: PurgeInput): Promise; /** * Task counts per queue, keyed by status and zero-filled across all statuses — * `(await stats()).default.queued` is the backlog of a queue. A queue appears * only while it has rows; terminal tasks keep counting until `purge` removes * them. */ stats(): Promise>>; /** * How many more tasks fit on `queue` under `maxDepth` — 0 once it is full. * * The cheap half of backpressure: bounded at `maxDepth` index entries, unlike * `stats()`, which aggregates the whole table (terminal rows included) and so * costs more the longer a database has been running. Use it directly to shed * load or shape a producer; `QueueDepthGate` builds the blocking form on top. */ queueDepth(queue: string, maxDepth: number): Promise; /** * Take up to `limit` claimable tasks. `names` restricts the claim to task names * this caller can actually run — a worker passes its registered handlers. * Queues alone do not partition work, so without it a worker claims a task it * cannot run and fails it permanently. Undefined means no filter; an empty * array claims nothing. */ claim(input: { queues: string[]; workerId: string; leaseMs?: number; limit?: number; names?: string[]; }): Promise; heartbeat(input: { taskId: string; workerId: string; leaseMs?: number; }): Promise; progress(input: { taskId: string; workerId: string; progress: number | null; message: string | null; }): Promise; succeed(input: { taskId: string; workerId: string; result: unknown; }): Promise; complete(input: { taskId: string; workerId: string; result: unknown; }): Promise; fail(input: { taskId: string; workerId: string; error: unknown; retryable?: boolean; delayMs?: number; }): Promise; } export {};