Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
78 lines
2.7 KiB
TypeScript
78 lines
2.7 KiB
TypeScript
export interface ScanOptions {
|
|
gte?: string
|
|
lt?: string
|
|
limit?: number
|
|
reverse?: boolean
|
|
/** When false/omitted, yielded values are `undefined`. */
|
|
values?: boolean
|
|
}
|
|
|
|
export type BatchOp = { type: 'set'; key: string; value: unknown } | { type: 'delete'; key: string }
|
|
|
|
/**
|
|
* A flat, string-keyed backend. Values are JSON-serialisable; byte-oriented backends serialise
|
|
* internally with a {@link Codec}. All path, index and typing logic lives above this interface.
|
|
*/
|
|
export interface KVDriver {
|
|
/** Same order as `keys`; `undefined` for missing keys. */
|
|
getMany(keys: string[]): Promise<(unknown | undefined)[]>
|
|
setMany(entries: [key: string, value: unknown][]): Promise<void>
|
|
deleteMany(keys: string[]): Promise<void>
|
|
/** MUST yield in key order (descending when `reverse`). `gte` is inclusive, `lt` exclusive. */
|
|
scan(opts: ScanOptions): AsyncIterable<[key: string, value: unknown]>
|
|
|
|
// Optional capabilities. Stores feature-detect them and fall back where possible.
|
|
/** Writes each entry only if its key is absent. `true` = written. */
|
|
setIfAbsent?(entries: [string, unknown][]): Promise<boolean[]>
|
|
/** Applies all ops atomically. */
|
|
batch?(ops: BatchOp[]): Promise<void>
|
|
deleteRange?(gte: string, lt: string): Promise<void>
|
|
}
|
|
|
|
/** Serialises values for byte/string-oriented backends. */
|
|
export interface Codec {
|
|
encode(value: unknown): string
|
|
decode(encoded: string): unknown
|
|
}
|
|
|
|
export const JsonCodec: Codec = {
|
|
encode: (value) => JSON.stringify(value),
|
|
decode: (encoded) => JSON.parse(encoded),
|
|
}
|
|
|
|
/**
|
|
* Base class for drivers: implement the four required methods and get `deleteRange`
|
|
* (scan + deleteMany in chunks) and single-key helpers for free.
|
|
*/
|
|
export abstract class BaseKVDriver implements KVDriver {
|
|
abstract getMany(keys: string[]): Promise<(unknown | undefined)[]>
|
|
abstract setMany(entries: [string, unknown][]): Promise<void>
|
|
abstract deleteMany(keys: string[]): Promise<void>
|
|
abstract scan(opts: ScanOptions): AsyncIterable<[string, unknown]>
|
|
|
|
/** Chunk size used by the default `deleteRange`. */
|
|
protected deleteRangeChunk = 1000
|
|
|
|
async deleteRange(gte: string, lt: string): Promise<void> {
|
|
for (;;) {
|
|
const keys: string[] = []
|
|
for await (const [key] of this.scan({ gte, lt, limit: this.deleteRangeChunk })) keys.push(key)
|
|
if (keys.length === 0) return
|
|
await this.deleteMany(keys)
|
|
if (keys.length < this.deleteRangeChunk) return
|
|
}
|
|
}
|
|
|
|
async get(key: string): Promise<unknown | undefined> {
|
|
return (await this.getMany([key]))[0]
|
|
}
|
|
|
|
set(key: string, value: unknown): Promise<void> {
|
|
return this.setMany([[key, value]])
|
|
}
|
|
|
|
delete(key: string): Promise<void> {
|
|
return this.deleteMany([key])
|
|
}
|
|
}
|