Files
PotentialStorage/test/kv-storage/sync-helpers.ts
T
Claude 631c75428b
CI / Node 22 (pull_request) Successful in 12s
CI / Node 26 (pull_request) Successful in 13s
CI / Node 24 (pull_request) Successful in 14s
Plain operations: last write wins, hash-keyed events, simpler composite
- KV: one write primitive, write(changes) with { key, value? }; writeIfAbsent removed.
  The server executes exactly what clients send and never reads values, so they can be
  encrypted end to end. Adds setFields/getFields on the base class.
- HTTP: no server history or event ids; SSE relays every change (own writes included) and
  the client emits resync when a dropped stream reconnects.
- Composite: writes apply locally, then go to the source (undone locally if refused);
  pushed changes are applied and announced only where they differ from local values;
  offline writes go to a queue store (any KV storage); sync() = replay, diff, replay.
  Removes KVReplicaSet, KVOutbox, KeyCounter, SerialQueue and RetryTimer.
- Events: id = <time>_<sha256(time + canonical data)>, so concurrent appends never
  collide and retries can't duplicate; no versions or ConcurrencyError.
- Event-sourced documents: read, resolve, append, apply; catchUp folds in foreign events;
  getAt takes an event id.

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

70 lines
2.4 KiB
TypeScript

import { KVStorageHttp } from '../../src/kv-storage/kv-storage-http.js'
import { KVStorageHttpServer, type KVStorageHttpServerOptions } from '../../src/kv-storage/kv-storage-http-server.js'
import { KVStorageInMemory } from '../../src/kv-storage/kv-storage-in-memory.js'
import type { RequestSigner } from '../../src/utils/request-auth.js'
import { KVStorageCounting } from '../helpers.js'
/**
* An in-process server with a network switch. While `online` is false, new requests fail like a
* dropped connection (open SSE streams stay up, so pushes can still be simulated).
* `dropStreams()` ends every open SSE stream, as a proxy or server restart would.
*/
export class TestNetwork {
online = true
readonly store = new KVStorageCounting(new KVStorageInMemory())
server: KVStorageHttpServer
private readonly clients: KVStorageHttp[] = []
private readonly streams = new Set<() => void>()
constructor(options: Partial<KVStorageHttpServerOptions> = {}) {
this.server = new KVStorageHttpServer(this.store, { auth: 'none', ...options })
}
client(signer?: RequestSigner): KVStorageHttp {
const client = new KVStorageHttp({
url: 'http://server',
signer,
fetch: async (url, init) => {
if (!this.online) throw new TypeError('fetch failed')
const response = await this.server.handle(new Request(url, init))
return url.endsWith('/events') && response.body ? this.droppable(response) : response
},
})
this.clients.push(client)
return client
}
dropStreams(): void {
for (const drop of this.streams) drop()
this.streams.clear()
}
private droppable(response: Response): Response {
const reader = response.body!.getReader()
let drop!: () => void
const body = new ReadableStream<Uint8Array>({
start: (controller) => {
drop = () => {
controller.close()
void reader.cancel()
}
this.streams.add(drop)
},
pull: async (controller) => {
const { done, value } = await reader.read()
if (done) controller.close()
else controller.enqueue(value)
},
cancel: async () => {
this.streams.delete(drop)
await reader.cancel()
},
})
return new Response(body, { status: response.status, headers: response.headers })
}
async close(): Promise<void> {
await Promise.all(this.clients.map((c) => c.close()))
}
}