import { Context, Effect, Layer, Option } from "effect";
import { OperationError } from "../errors/index.js";
import { Sqlite } from "./sqlite.js";
/**
* Per-row metadata stored alongside the CBOR payload. All fields are optional;
* omitting a field falls back to the column default (no entity, version 0,
* never expires, updated_at = now).
*/
export interface DocMeta {
/** Entity name this row belongs to, or null for raw docstore rows. */
entity?: string | null;
/** Schema version of the payload. */
version?: number;
/** Absolute expiry (epoch ms), or null to never expire. */
expiresAt?: number | null;
/** Last-write timestamp (epoch ms); defaults to now. */
updatedAt?: number;
}
export interface RawRow {
pk: string;
entity: string | null;
version: number;
expires_at: number | null;
updated_at: number;
data: Buffer;
}
declare const CorruptRowError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
readonly _tag: "CorruptRowError";
} & Readonly;
/**
* A point read hit a row whose CBOR payload cannot be decoded. The caller asked
* for this exact key, so the failure is surfaced (rather than read as absent)
* to let a repair tool detect and delete the bad row.
*/
export declare class CorruptRowError extends CorruptRowError_base<{
readonly pk: string;
readonly cause: unknown;
}> {
get message(): string;
}
export declare function encodeDoc(data: unknown): Buffer;
/** Decodes one stored payload; throws on corrupt CBOR. */
export declare function decodeDoc(data: Buffer): T;
/**
* Synchronous statements over the shared connection. Every function may throw
* (better-sqlite3 / cbor); the `Docstore` service wraps them once as
* `OperationError`. Exposed so `Entity` can compose several steps inside a
* single transaction.
*/
export interface DocstoreSync {
readonly getRawRow: (pk: string, now: number) => RawRow | undefined;
readonly upsertDoc: (pk: string, data: unknown, meta: DocMeta, now: number) => number;
readonly deleteDoc: (pk: string) => boolean;
readonly getRawRowsByPrefix: (prefix: string) => RawRow[];
}
export interface DocstoreShape {
/**
* Retrieves the document for a primary key. Expired rows read as absent. A
* point read fails with `CorruptRowError` on an unreadable row; collection
* reads skip such rows instead so one bad blob can't sink the whole batch.
*/
getDoc(pk: string): Effect.Effect, OperationError | CorruptRowError>;
/** Raw-key escape hatch; `Entity` uses `getDocsByEntity`. Unreadable rows are skipped (warned). */
getDocsByPrefix(prefix: string): Effect.Effect;
/** All live documents of an entity. Unreadable rows are skipped (warned). */
getDocsByEntity(entity: string): Effect.Effect;
/** Upserts a document. Metadata defaults: no entity, version 0, no expiry, updated_at = now. */
upsertDoc(pk: string, data: T, meta?: DocMeta): Effect.Effect;
/** Updates the expiry (and updated_at) of a live row. Resolves true if a row was touched. */
touchDoc(pk: string, expiresAt: number | null): Effect.Effect;
/** Resolves true if a document was deleted. */
deleteDoc(pk: string): Effect.Effect;
/** Resolves the number of documents deleted. */
deleteDocsByPrefix(prefix: string): Effect.Effect;
/** Resolves the number of documents deleted. */
deleteDocsByEntity(entity: string): Effect.Effect;
/** Existence check without deserializing. Expired rows read as absent. */
hasDoc(pk: string): Effect.Effect;
countByPrefix(prefix: string): Effect.Effect;
countByEntity(entity: string): Effect.Effect;
/** All live primary keys matching a prefix (raw storage keys). */
getKeysByPrefix(prefix: string): Effect.Effect;
/**
* Physically deletes up to `limit` expired rows. Storage maintenance, not
* expiry correctness (reads already ignore expired rows).
*/
cleanupExpired(limit?: number): Effect.Effect;
/** The raw row (payload + metadata) for a live pk. */
getRawRow(pk: string): Effect.Effect, OperationError>;
/** Raw rows (including expired ones) matching a prefix. Used by `Entity.migrate`. */
getRawRowsByPrefix(prefix: string): Effect.Effect;
/**
* Runs synchronous statements inside one write transaction. `fn` must not
* return an Effect: it would never run.
*/
transaction(operation: string, fn: (tx: DocstoreSync) => A): Effect.Effect;
/** Deletes every row. */
readonly clear: Effect.Effect;
}
declare const Docstore_base: Context.ServiceClass;
/**
* CBOR-encoded documents in a single `blobs` table of the shared `Sqlite`
* connection, keyed by primary key with entity/version/expiry metadata columns.
*/
export declare class Docstore extends Docstore_base {
/** Ensures the `blobs` schema on the shared connection and exposes the store. */
static readonly layer: Layer.Layer;
/** Docstore over an in-memory database, for tests. Exposes `Sqlite` too. */
static readonly layerMemory: Layer.Layer;
}
export declare const getDoc: (pk: string) => Effect.Effect, CorruptRowError | OperationError, Docstore>;
export declare const getDocsByPrefix: (prefix: string) => Effect.Effect;
export declare const getDocsByEntity: (entity: string) => Effect.Effect;
export declare const upsertDoc: (pk: string, data: T, meta?: DocMeta) => Effect.Effect;
export declare const touchDoc: (pk: string, expiresAt: number | null) => Effect.Effect;
export declare const deleteDoc: (pk: string) => Effect.Effect;
export declare const deleteDocsByPrefix: (prefix: string) => Effect.Effect;
export declare const deleteDocsByEntity: (entity: string) => Effect.Effect;
export declare const hasDoc: (pk: string) => Effect.Effect;
export declare const countByPrefix: (prefix: string) => Effect.Effect;
export declare const countByEntity: (entity: string) => Effect.Effect;
export declare const getKeysByPrefix: (prefix: string) => Effect.Effect;
export declare const cleanupExpired: (limit?: number) => Effect.Effect;
export declare const getRawRow: (pk: string) => Effect.Effect, OperationError, Docstore>;
export declare const getRawRowsByPrefix: (prefix: string) => Effect.Effect;
export declare const clearDocstore: Effect.Effect;
export {};