Files
Claude e27541c46a
CI / Node 20 (push) Successful in 1m49s
CI / Node 22 (push) Successful in 1m52s
Add Prettier formatting and Gitea Actions CI (format, typecheck, test, build)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
2026-10-23 07:29:38 +00:00

84 lines
2.8 KiB
TypeScript

import { BaseKVDriver, type BatchOp, type KVDriver, type ScanOptions } from '../src/kv/driver.js'
import { InMemoryKVDriver } from '../src/kv/memory.js'
/** A minimal driver implementing only the four required methods, over a plain Map. */
export class MinimalDriver extends BaseKVDriver {
readonly map = new Map<string, string>()
async getMany(keys: string[]) {
return keys.map((k) => (this.map.has(k) ? JSON.parse(this.map.get(k)!) : undefined))
}
async setMany(entries: [string, unknown][]) {
for (const [k, v] of entries) this.map.set(k, JSON.stringify(v))
}
async deleteMany(keys: string[]) {
for (const k of keys) this.map.delete(k)
}
async *scan({ gte, lt, limit = Infinity, reverse, values }: ScanOptions): AsyncGenerator<[string, unknown]> {
let keys = [...this.map.keys()].filter((k) => (gte === undefined || k >= gte) && (lt === undefined || k < lt)).sort()
if (reverse) keys.reverse()
keys = keys.slice(0, limit)
for (const k of keys) {
if (!this.map.has(k)) continue
yield [k, values ? JSON.parse(this.map.get(k)!) : undefined]
}
}
}
export type Capability = 'setIfAbsent' | 'batch' | 'deleteRange'
/**
* Wraps a driver, counting calls per method and optionally hiding optional capabilities.
* `scan` counts each scan started (i.e. each seek).
*/
export class CountingDriver implements KVDriver {
calls: Record<string, number> = {}
setIfAbsent?: KVDriver['setIfAbsent']
batch?: KVDriver['batch']
deleteRange?: KVDriver['deleteRange']
constructor(
readonly inner: KVDriver = new InMemoryKVDriver(),
hide: Capability[] = [],
) {
if (inner.setIfAbsent && !hide.includes('setIfAbsent')) this.setIfAbsent = (e: [string, unknown][]) => (this.#count('setIfAbsent'), inner.setIfAbsent!(e))
if (inner.batch && !hide.includes('batch')) this.batch = (ops: BatchOp[]) => (this.#count('batch'), inner.batch!(ops))
if (inner.deleteRange && !hide.includes('deleteRange'))
this.deleteRange = (gte: string, lt: string) => (this.#count('deleteRange'), inner.deleteRange!(gte, lt))
}
#count(m: string): void {
this.calls[m] = (this.calls[m] ?? 0) + 1
}
reset(): void {
this.calls = {}
}
get total(): number {
return Object.values(this.calls).reduce((a, b) => a + b, 0)
}
getMany(keys: string[]) {
this.#count('getMany')
return this.inner.getMany(keys)
}
setMany(entries: [string, unknown][]) {
this.#count('setMany')
return this.inner.setMany(entries)
}
deleteMany(keys: string[]) {
this.#count('deleteMany')
return this.inner.deleteMany(keys)
}
scan(opts: ScanOptions) {
this.#count('scan')
return this.inner.scan(opts)
}
}
export async function collect<T>(it: AsyncIterable<T>): Promise<T[]> {
const out: T[] = []
for await (const x of it) out.push(x)
return out
}