/** * Cloudflare Durable Object SQLite storage adapter. * * Wraps a Durable Object's `ctx.storage.sql` binding (structurally typed as * {@link Sql} — see `cloudflare-durable-object-sql.ts`) in the same `Storage` * interface every other adapter implements, over a single key/value table. * `@cloudflare/workers-types` is never imported: the structural `Sql`/ * `SqlStorageCursor` shape is enough to both type this adapter and drive it * in tests with a `bun:sqlite`-backed double. * * This module has zero runtime-specific imports of its own — the `sql` * binding is injected by the caller — so it is bundleable for Bun, Node, and * the Cloudflare Workers (`workerd`) runtime alike. Import from * `@lostgradient/weft/storage/cloudflare`. * * @module storage/cloudflare */ import type { Sql } from './cloudflare-durable-object-sql.ts'; import { type CloudflareValueEncoding } from './cloudflare-value-codec.ts'; import { type DeleteRangeOptions } from './delete-range.ts'; import { type BatchOperation, type ConditionalBatchCondition, type ScanOptions, type Storage, type StorageCapabilities } from './interface.ts'; export type { Sql, SqlStorageCursor, SqlStorageValue } from './cloudflare-durable-object-sql.ts'; export type { CloudflareValueEncoding } from './cloudflare-value-codec.ts'; /** * Configuration for {@link CloudflareDurableObjectSQLiteStorage}. * * @example * ```ts * import type { CloudflareDurableObjectSQLiteStorageOptions } from '@lostgradient/weft/storage/cloudflare'; * import type { Sql } from '@lostgradient/weft/storage/cloudflare'; * * declare const sql: Sql; * const options: CloudflareDurableObjectSQLiteStorageOptions = { sql, table: 'weft_kv' }; * void options; * ``` */ export type CloudflareDurableObjectSQLiteStorageOptions = { /** The Durable Object's `ctx.storage.sql` binding, or a structurally compatible double. */ sql: Sql; /** * Table name for the single key/value table this adapter reads and writes. * Validated as a strict SQL identifier at construction; defaults to `kv`. */ table?: string; /** * How values are stored in the `value` column. Defaults to `'base64'` * (base64-encoded `TEXT`), which keeps this adapter's SQL binding contract * to the TEXT/number/null value types the Durable Object SQL binding * guarantees. Opt into `'blob'` to bind and store raw `ArrayBuffer`/`BLOB` * values instead, avoiding base64's ~4/3 size expansion for large values — * this requires the wider binding contract the real Durable Object SQL * binding also supports. * * A table's `value` column holds whichever encoding wrote each row: pick * one `valueEncoding` for a table's lifetime and do not change it. Reading * a row written under the other encoding fails fast with a descriptive * error rather than silently misinterpreting the bytes — see the * `documentation/guides/storage.md` Cloudflare section for the full * cross-mode contract. */ valueEncoding?: CloudflareValueEncoding; }; /** * Storage adapter over a Cloudflare Durable Object's `ctx.storage.sql` * binding. * * A **non-owning** view: the `sql` binding is injected, the Durable Object * owns its storage connection, and `[Symbol.dispose]` is a no-op — there is * nothing here to close. * * Schema is one `kv(key TEXT PRIMARY KEY, value ...)` table (name * configurable via `table`). By default (`valueEncoding: 'base64'`) values * are stored as base64-encoded text, keeping this adapter's SQL binding * contract to the TEXT/number/null value types the Durable Object SQL * binding guarantees. Set `valueEncoding: 'blob'` to bind and store raw * `ArrayBuffer`/`BLOB` values instead — see * {@link CloudflareDurableObjectSQLiteStorageOptions.valueEncoding}. * * `ctx.storage.sql.exec()` is synchronous — Durable Object storage is * transactional only up to the next `await`/yield point in the calling code. * Every method here that needs that guarantee (`batch`, `conditionalBatch`, * `scan`) runs its `exec()` calls with no `await` in between, so a single * `Storage` call from this adapter is one atomic unit of Durable Object * storage work. * * `deletePrefix()` and `deleteRange()` are native single-statement `DELETE`s * (not a scan-then-batch fallback), so `capabilities().boundedRangeDelete` * is honestly `true`. * * @example * ```ts * import { CloudflareDurableObjectSQLiteStorage, type Sql } from '@lostgradient/weft/storage/cloudflare'; * import { Engine, workflow, type WorkflowContext } from '@lostgradient/weft'; * * // Injected by the Durable Object runtime: `ctx.storage.sql` inside a * // `DurableObject` subclass. * declare const sql: Sql; * * const storage = new CloudflareDurableObjectSQLiteStorage({ sql }); * * // Durable Objects drive their own event loop; there is no host process to * // own background intervals, so the engine runs in manual maintenance mode * // and the Durable Object alarm (or a Worker Cron Trigger) drives * // `engine.runMaintenance()` explicitly. * const engine = await Engine.create({ * storage, * backgroundTasks: 'manual', * startScheduler: false, * }); * * engine.register( * workflow({ name: 'echo' }).execute(async function* (ctx: WorkflowContext, input: unknown) { * return input; * }), * ); * ``` */ export declare class CloudflareDurableObjectSQLiteStorage implements Storage { #private; constructor(options: CloudflareDurableObjectSQLiteStorageOptions); capabilities(): StorageCapabilities; get(key: string): Promise; put(key: string, value: Uint8Array): Promise; delete(key: string): Promise; has(key: string): Promise; count(prefix: string): Promise; deletePrefix(prefix: string): Promise; deleteRange(prefix: string, options: DeleteRangeOptions): Promise; scan(prefix: string, options?: ScanOptions): AsyncIterable<[string, Uint8Array]>; keys(prefix: string, options?: ScanOptions): AsyncIterable; batch(operations: BatchOperation[]): Promise; conditionalBatch(conditions: ConditionalBatchCondition[], operations: BatchOperation[]): Promise; /** * No-op. This adapter is a non-owning view over an injected `sql` binding * — the Durable Object owns its storage connection, so there is nothing * here to close. Disposing this adapter never touches `sql` beyond the * ordinary reads/writes issued by the methods above. */ [Symbol.dispose](): void; }