import type { StateStore } from '../core/store.ts'; /** * The three operations a KV service has to offer. * * Deliberately narrower than the `StateStore` it backs: no sweep, no size, no * iteration. A networked store cannot scan keys cheaply, and it does not need to: * `ttlSeconds` lets the service reclaim space, and expiry remains a read-time * decision (D6). */ export type KvTransport = { /** Raw stored string, or `undefined` if the key is absent. */ get(key: string): Promise; /** Store a string. `ttlSeconds`, when given, is a space bound and never a correctness one. */ set(key: string, value: string, ttlSeconds?: number): Promise; del(key: string): Promise; }; export type KvFailureHandler = (error: unknown, operation: 'get' | 'set' | 'del', key: string) => void; type KvStoreShared = { readonly transport: KvTransport; readonly retentionHours?: number; /** * Prefix for every key, so SG's state cannot collide with the host's own data in * a shared namespace. Joined with `:`: and the entity reference itself is never * altered beyond that, because D1 makes it opaque. */ readonly keyPrefix?: string; }; /** * The store options, split by what a failure is allowed to mean. D62. * * Failing open stays the default, because D9 makes the guard advisory and a KV outage * must not take the host request path down with it. What is no longer free is the * silence: failing open requires naming the sink that hears about it. The cost is * stated rather than hidden, an attacker who can make your KV unreachable resets every * entity to unknown, which is the most permissive state the trust dimension has. What * survives that attack is the invariant layer: a proven violation reaches RESTRICT * with no history at all (D15), so fail-open degrades the statistical dimension and * not the provable one. * * Setting failOpen to false propagates instead, for a host that would rather fail the * request than trust an entity it has forgotten. That branch is where a sink stays * optional, because the failure is already loud. */ export type KvStoreOptions = KvStoreShared & ({ readonly failOpen?: true; readonly onError: KvFailureHandler; } | { readonly failOpen: false; readonly onError?: KvFailureHandler; }); export type KvStore = StateStore; /** * Build a `StateStore` on a KV transport. * * Prove it before trusting it. The conformance kit (D8) is the point of the store * contract existing, and a networked store is exactly where the subtle mistakes * live: * * import { checkStoreConformance, assertConformant } from '@fachryxyf/scorpio-guard'; * assertConformant(await checkStoreConformance(() => kvStore({ transport, onError: log }))); */ export declare function kvStore(options: KvStoreOptions): KvStore; export type UpstashTransportOptions = { /** REST endpoint, e.g. `https://eu1-xxx.upstash.io`. Vercel KV exposes the same shape. */ readonly url: string; readonly token: string; /** Abort a request that hangs. A store that never answers is worse than one that fails. */ readonly timeoutMs?: number; /** Injectable for tests. Defaults to the global `fetch`. */ readonly fetch?: typeof globalThis.fetch; }; /** * A `KvTransport` for Upstash Redis and Vercel KV. * * Commands go in a JSON body rather than in the URL path, which is the one detail * that matters for correctness here: `POST /get/` breaks on an entity * reference containing a slash or a space, and D1 permits both. The body form has * no such constraint, so the key stays opaque. */ export declare function upstashTransport(options: UpstashTransportOptions): KvTransport; /** * An in-process `KvTransport`, for running the conformance kit and for tests. * * Not a store: it is the *transport* the store sits on, so exercising `kvStore` * against it proves the encoding, key handling and freezing without needing a * network. What it cannot prove is anything about the network itself, which is * stated rather than implied. */ export declare function memoryTransport(): KvTransport & { size(): number; }; export {}; //# sourceMappingURL=kv.d.ts.map