Files
PotentialStorage/test/document-storage/document-storage.suite.ts
T
Claude 08fb78721f
CI / Node 20 (pull_request) Successful in 10s
CI / Node 22 (pull_request) Successful in 11s
Simplify: one abstract base class per layer
- BaseKVStorage / BaseEventStorage / BaseDocumentStorage define the API once;
  implementations provide only raw primitives (client and server share an API)
- Drop KV indexes, query planner, unique indexes; document stores take index fields
- Escape every char <= '/' so child keys stay contiguous; skip-scan is now simple
- Event-sourced documents = event storage + any document store for projections
- New folder layout (kv-storage, event-storage, document-storage, utils); tests only in test/

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
2026-10-23 08:05:03 +00:00

87 lines
4.3 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { BaseDocumentStorage } from '../../src/document-storage/base-document-storage.js'
import { collect } from '../helpers.js'
type User = { name?: string; email?: string; age?: number; tags?: string[]; logins?: number; address?: { city?: string } }
/** Behaviour every document storage must have. `make` returns an empty storage. */
export function runDocumentStorageSuite(name: string, make: () => BaseDocumentStorage): void {
describe(`document storage: ${name}`, () => {
const users = () => make().child('users')
it('inserts with time-sortable ids, gets and lists', async () => {
const u = users()
const a = await u.insert({ name: 'Ada' })
const b = await u.insert({ name: 'Bob' })
expect(a < b).toBe(true)
expect(await u.get(a)).toEqual({ name: 'Ada' })
expect(await collect(u.keys())).toEqual([a, b])
})
it('set / get / delete in self and child forms', async () => {
const u = users()
await u.set('1', { name: 'Ada' })
expect(await u.child('1').get()).toEqual({ name: 'Ada' })
await u.child('1').set({ name: 'Ada L.' })
expect(await u.getMany(['1', '2'])).toEqual([{ name: 'Ada L.' }, undefined])
expect(await u.delete('1')).toBe(true)
expect(await u.child('1').delete()).toBe(false)
expect(await u.get('1')).toBeUndefined()
await u.set('1', { name: 'Again' })
expect(await u.get('1')).toEqual({ name: 'Again' })
})
it('updates with operators, returns the new state, and supports upsert', async () => {
const u = users()
await u.set('1', { name: 'Ada', tags: ['a'], logins: 1 })
expect(await u.update('1', { $inc: { logins: 2 }, $push: { tags: 'b' }, $set: { 'address.city': 'London' } })).toEqual({
name: 'Ada',
tags: ['a', 'b'],
logins: 3,
address: { city: 'London' },
})
expect(await u.child('1').update({ $unset: { address: 1 } })).toEqual({ name: 'Ada', tags: ['a', 'b'], logins: 3 })
expect(await u.update('missing', { $set: { a: 1 } })).toBeUndefined()
expect(await u.update('missing', { $inc: { n: 1 } }, { upsert: true })).toEqual({ n: 1 })
})
it('rejects bad keys and documents', async () => {
const u = users()
await expect(u.get(undefined as never)).rejects.toThrow(TypeError)
await expect(u.update(undefined as never, { $set: {} })).rejects.toThrow(TypeError)
await expect(make().get()).rejects.toThrow(/root/)
await expect(u.set('x', [] as never)).rejects.toThrow(/plain object/)
})
it('nests documents under documents', async () => {
const u = users()
await u.set('1', { name: 'Ada' })
await u.child('1').child('posts').set('p1', { title: 'Hi' })
expect(await u.child('1').child('posts').get('p1')).toEqual({ title: 'Hi' })
expect((await collect(u.find())).map((r) => r.key)).toEqual(['1'])
})
it('finds with equality, comparisons, $in, dot paths, sort, skip and limit', async () => {
const u = users()
const rows: [string, User][] = [
['a', { name: 'Ada', age: 36, email: 'ada@x', tags: ['x', 'y'], address: { city: 'London' } }],
['b', { name: 'Bob', age: 20, email: 'bob@x', tags: ['y'] }],
['c', { name: 'Cy', age: 51, address: { city: 'London' } }],
['d', { name: 'Di', age: 20 }],
]
for (const [k, v] of rows) await u.set(k, v)
const keys = async (filter: Record<string, unknown>, options = {}) => (await collect(u.find(filter, options))).map((r) => r.key)
expect(await keys({ age: 20 })).toEqual(['b', 'd'])
expect(await keys({ age: { $gte: 20, $lt: 40 } })).toEqual(['a', 'b', 'd'])
expect(await keys({ age: { $in: [36, 51] } })).toEqual(['a', 'c'])
expect(await keys({ 'address.city': 'London', age: { $gt: 40 } })).toEqual(['c'])
expect(await keys({ tags: 'y' })).toEqual(['a', 'b'])
expect(await keys({ email: null })).toEqual(['c', 'd'])
expect(await keys({}, { sort: { age: -1 } })).toEqual(['c', 'a', 'b', 'd'])
expect(await keys({}, { sort: { age: 1 }, skip: 1, limit: 2 })).toEqual(['d', 'a'])
expect(await u.findOne({ email: 'bob@x' })).toEqual({ key: 'b', data: rows[1]![1] })
expect(await u.findOne({ email: 'nobody' })).toBeUndefined()
})
})
}