import { describe, expect, it } from 'vitest' import fc from 'fast-check' import { KVStorageInMemory } from '../../src/kv-storage/kv-storage-in-memory.js' import type { BaseKVStorage, KVChange } from '../../src/kv-storage/base-kv-storage.js' import { collect, KVStorageCounting } from '../helpers.js' import { runKVStorageConformance } from './kv-storage.conformance.js' runKVStorageConformance('KVStorageInMemory', () => new KVStorageInMemory()) runKVStorageConformance('wrapper (KVStorageCounting)', () => new KVStorageCounting()) describe('costs', () => { it('single-key operations are one primitive call', async () => { const db = new KVStorageCounting() const users = db.child('users') await users.set('1', 'x') await users.get('1') await users.child('1').get() await users.delete('1') expect(db.calls).toEqual({ writeMany: 2, readMany: 2 }) }) it('keys() seeks once per child with descendants, not once per key', async () => { const db = new KVStorageCounting() const writes: KVChange[] = [] for (let c = 0; c < 50; c++) for (let d = 0; d < 20; d++) writes.push({ key: `n/c${c}/d${d}`, value: d }) for (let c = 0; c < 50; c++) writes.push({ key: `n/leaf${c}`, value: c }) await db.writeMany(writes) db.reset() expect(await collect(db.child('n').keys())).toHaveLength(100) expect(db.calls).toEqual({ scan: 51 }) }) }) describe('model-based: random writes vs a reference map', () => { const segment = fc.constantFrom('a', 'a-1', 'a.b', 'b', 'x/y', '%', '~e', '1', '10', '9', 'ab', 'a!', 'é') const path = fc.array(segment, { minLength: 1, maxLength: 3 }) const op = fc.oneof( fc.record({ t: fc.constant('set' as const), path, value: fc.integer() }), fc.record({ t: fc.constant('delete' as const), path }), fc.record({ t: fc.constant('deleteTree' as const), path }), ) const at = (db: BaseKVStorage, p: string[]) => p.reduce((n, s) => n.child(s), db) it('get, keys and entries agree with the model', async () => { await fc.assert( fc.asyncProperty(fc.array(op, { maxLength: 30 }), fc.array(segment, { maxLength: 2 }), async (ops, parent) => { const db = new KVStorageInMemory() const model = new Map() const isUnder = (p: string[], q: string[]) => q.length >= p.length && p.every((s, i) => q[i] === s) for (const o of ops) { if (o.t === 'set') (await at(db, o.path).set(o.value), model.set(JSON.stringify(o.path), o.value)) else if (o.t === 'delete') (await at(db, o.path).delete(), model.delete(JSON.stringify(o.path))) else { await at(db, o.path).deleteTree() for (const k of [...model.keys()]) if (isUnder(o.path, JSON.parse(k))) model.delete(k) } } for (const [k, v] of model) expect(await at(db, JSON.parse(k)).get()).toBe(v) const children = new Map() for (const [k, v] of model) { const p = JSON.parse(k) as string[] if (p.length <= parent.length || !isUnder(parent, p)) continue const own = p.length === parent.length + 1 if (own || !children.has(p[parent.length]!)) children.set(p[parent.length]!, own ? v : children.get(p[parent.length]!)) } const node = at(db, parent) const listed = await collect(node.keys()) expect([...listed].sort()).toEqual([...children.keys()].sort()) expect(await collect(node.keys({ reverse: true }))).toEqual([...listed].reverse()) const withValues = listed.filter((k) => children.get(k) !== undefined) expect(await collect(node.entries())).toEqual(withValues.map((k) => [k, children.get(k)])) expect(await collect(node.entries({ reverse: true }))).toEqual(withValues.reverse().map((k) => [k, children.get(k)])) for (let i = 0; i < listed.length; i++) expect(await collect(node.keys({ after: listed[i]! }))).toEqual(listed.slice(i + 1)) }), { numRuns: 300 }, ) }) }) describe('child nodes', () => { it('are frozen, so implementations cannot accidentally shadow shared state', () => { const child = new KVStorageInMemory().child('a').child('b') expect(Object.isFrozen(child)).toBe(true) expect(() => ((child as unknown as { state: number }).state = 1)).toThrow(TypeError) expect(child.key).toBe('a/b') }) })