import { Effect, Option } from "effect";
import { type OperationError } from "../errors/index.js";
import { CorruptRowError, Docstore } from "./docstore.js";
/** A primary-key component value. Anything else fails with `InvalidKeyError`. */
export type PKValue = string | number | boolean;
declare const InvalidKeyError_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: "InvalidKeyError";
} & Readonly;
/** A primary-key component was not a finite number, a string or a boolean. */
export declare class InvalidKeyError extends InvalidKeyError_base<{
readonly entity: string;
readonly property: string;
readonly value: unknown;
}> {
get message(): string;
}
declare const EntityValidationError_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: "EntityValidationError";
} & Readonly;
/** The entity's `validate` function rejected a payload on the way in. */
export declare class EntityValidationError extends EntityValidationError_base<{
readonly entity: string;
readonly cause: unknown;
}> {
get message(): string;
}
export interface UpsertOptions {
/** Time-to-live in ms from now. Overridden by `expiresAt`. */
ttlMs?: number;
/** Absolute expiry (epoch ms). Takes precedence over `ttlMs`. */
expiresAt?: number;
}
export interface EntityOptions {
name: string;
pk: PKProps;
/** Current payload schema version. Defaults to 0. */
version?: number;
/** Upgrades a stored payload to the current version. Run by migrateAll(). */
migrate?: (data: unknown, fromVersion: number) => Data;
/**
* Validates/parses data on the way in; throw to reject. Pair with
* `Schema.decodeUnknownSync(MySchema)`. A rejection fails the upsert with
* `EntityValidationError`.
*/
validate?: (data: unknown) => Data;
/** Default TTL (ms) applied to upserts that don't specify their own. */
defaultTtlMs?: number;
}
type PK = Pick;
/**
* A class representing an entity in the database
*
* Heavily inspired by ElectroDB except:
* - It's stupidly simple
* - It uses a local SQLite database
* - It does not support any kind of query besides getting by primary key
*
* Construct entities at module level: the constructor touches no database,
* it only records the entity in a process-wide registry that
* `Entity.migrateAll()` walks. Every operation is an `Effect` that requires
* the `Docstore` service.
*
* Migration model: this is not a lazy/dual-read store. Reads assume the current
* on-disk representation. After upgrading the library, run `Entity.migrateAll()`
* once at startup to rewrite existing rows into the current key encoding,
* payload version, and metadata columns. Reading data that predates that
* migration is undefined behavior.
*/
export declare class Entity {
private static readonly registry;
private readonly version;
private readonly migrateFn?;
private readonly validateFn?;
private readonly defaultTtlMs?;
readonly name: string;
readonly pkProps: PKProps;
constructor(name: string, pkProps: PKProps);
constructor(options: EntityOptions);
/** Builds the storage key for a primary key; throws `InvalidKeyError` on a bad component. */
getPk(arg: PK): string;
private readonly pk;
private readonly validate;
private meta;
private resolveExpiry;
private nextExpiry;
private readonly annotate;
get(arg: PK): Effect.Effect, OperationError | CorruptRowError | InvalidKeyError, Docstore>;
getAll(): Effect.Effect;
upsert(data: Data, options?: UpsertOptions): Effect.Effect;
delete(arg: PK): Effect.Effect;
deleteAll(): Effect.Effect;
has(arg: PK): Effect.Effect;
count(): Effect.Effect;
/**
* Extends (or clears) the expiry of an existing entity without rewriting its
* payload. Resolves true if a live row was touched.
*/
touch(arg: PK, options?: UpsertOptions): Effect.Effect;
/**
* Shallow read-modify-write in one transaction (`validate` runs on the
* result). Primary-key fields are re-asserted from `arg` afterwards, so an untyped caller cannot move the row
* by passing pk fields in `partial`. Existing expiry is preserved unless
* `options` overrides it. Resolves `None` (and writes nothing) if the row is
* absent.
*/
patch(arg: PK, partial: Partial>, options?: UpsertOptions): Effect.Effect, OperationError | InvalidKeyError | CorruptRowError | EntityValidationError, Docstore>;
/**
* Transactional read-modify-write. The callback receives the current value
* and returns the next one; the whole cycle runs in a single SQLite
* transaction, so keep it synchronous; `validate` runs on the result.
* Existing expiry is preserved unless
* `options` overrides it. Resolves `None` (and does nothing) if the row is
* absent.
*/
update(arg: PK, fn: (current: Data) => Data, options?: UpsertOptions): Effect.Effect, OperationError | InvalidKeyError | CorruptRowError | EntityValidationError, Docstore>;
/** The primary-key objects of all live entities. */
keys(): Effect.Effect[], OperationError, Docstore>;
/** Physically removes expired rows across the whole store (all entities). */
cleanupExpired(limit?: number): Effect.Effect;
/**
* Rewrites every stored row for this entity into the current representation:
* new key encoding, current payload version (via `migrate`), and the
* entity/version/updated_at columns. Idempotent and safe to re-run. Existing
* expiry is preserved. Resolves the number of rows rewritten.
*/
migrate(): Effect.Effect;
private migrateRows;
/**
* Runs `migrate()` for every Entity constructed in this process. Call once at
* startup. Resolves the total rows rewritten.
*/
static migrateAll(): Effect.Effect;
}
export {};