Break KVStorageComposite into focused classes
- KVReplicaSet: seed, apply, local scan, diff against the source, sync meta - KVOutbox: persisted offline queue with in-order replay (load() resumes it) - utils: KeyRange (also used for server authorization), KeyCounter, SerialQueue (also used by KVStorageHttp and the event-sourced store), RetryTimer (separate timers for sync and replay; previously one shared timer could cancel the other's retry) The composite now only orchestrates. Behaviour unchanged; new unit tests for the building blocks and the outbox. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { KVOutbox, type OutboxEntry } from '../../src/kv-storage/kv-outbox.js'
|
||||
import { KVStorageInMemory } from '../../src/kv-storage/kv-storage-in-memory.js'
|
||||
import { RetryTimer } from '../../src/utils/retry-timer.js'
|
||||
import { StorageUnavailableError } from '../../src/utils/errors.js'
|
||||
|
||||
describe('KVOutbox', () => {
|
||||
it('replays entries in order once the source is reachable, and survives a restart', async () => {
|
||||
const store = new KVStorageInMemory()
|
||||
let online = false
|
||||
const sent: OutboxEntry[] = []
|
||||
const send = async (entry: OutboxEntry) => {
|
||||
if (!online) throw new StorageUnavailableError('offline')
|
||||
sent.push(entry)
|
||||
}
|
||||
const firstRetry = new RetryTimer({ baseDelay: 5, maxDelay: 5 })
|
||||
const first = new KVOutbox(store, send, firstRetry)
|
||||
await first.push({ type: 'write', entries: [['a', 1]] })
|
||||
await first.push({ type: 'remove', keys: ['a'] })
|
||||
expect(first.size).toBe(2)
|
||||
expect(first.pending.has('a')).toBe(true)
|
||||
|
||||
firstRetry.stop() // the first session ends...
|
||||
await first.replay() // ...once its in-flight replay attempt has failed
|
||||
// A new session over the same store picks up the queue.
|
||||
const second = new KVOutbox(store, send, new RetryTimer({ baseDelay: 5, maxDelay: 5 }))
|
||||
online = true
|
||||
await second.load()
|
||||
await vi.waitFor(() => expect(second.size).toBe(0))
|
||||
expect(sent).toEqual([
|
||||
{ type: 'write', entries: [['a', 1]] },
|
||||
{ type: 'remove', keys: ['a'] },
|
||||
])
|
||||
expect(await store.readMany(['~q/o/000000000001', '~q/o/000000000002'])).toEqual([undefined, undefined])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user