Add KVStorageComposite, IndexedDB storage and SSE change streams
- Every KV storage emits change events (base class owns writeMany/removeMany/ writeIfAbsent; implementations provide protected apply* methods) - KVStorageHttpServer streams changes over SSE (GET /events) with a resumable history; KVStorageHttp.watch() subscribes and resumes via Last-Event-ID - KVStorageIndexedDB for local persistence - KVStorageComposite keeps replicas of a source in sync: local reads, resume instead of full sync, offline outbox replayed in order, echo tracking - Values may contain bigint/Uint8Array (Extended JSON over the network) - Vendor sse-session, event-emitter and extended-json (no runtime deps) - Freeze child nodes so shadowed state on children fails loudly - CI on Node 22, 24 and 26 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
This commit is contained in:
@@ -27,7 +27,7 @@ export type ChildDoc<S> = ValueOf<ChildOf<S, KeyOf<S>>>
|
||||
*/
|
||||
export abstract class BaseDocumentStorage<S extends Spec = AnySpec> {
|
||||
/** Unescaped path of this node (`[]` for the root, which has no own document). */
|
||||
path: readonly string[] = []
|
||||
readonly path: readonly string[] = []
|
||||
protected readonly ids = new Ulid()
|
||||
|
||||
abstract readMany(paths: (readonly string[])[]): Promise<(Doc | undefined)[]>
|
||||
@@ -40,8 +40,11 @@ export abstract class BaseDocumentStorage<S extends Spec = AnySpec> {
|
||||
abstract query(parent: readonly string[], query: DocumentQuery): AsyncIterable<FindResult>
|
||||
|
||||
child<K extends KeyOf<S>>(key: K): BaseDocumentStorage<ChildOf<S, K>> {
|
||||
const node = Object.create(this) as BaseDocumentStorage<ChildOf<S, K>>
|
||||
node.path = [...this.path, Keys.segment(key)]
|
||||
const node = Object.create(this, { path: { value: [...this.path, Keys.segment(key)], enumerable: true } }) as BaseDocumentStorage<ChildOf<S, K>>
|
||||
// Frozen: child nodes share the root's state through the prototype, so an implementation that
|
||||
// reassigns a field (`this.x = ...`) while running on a child would silently shadow it.
|
||||
// Keep mutable state in objects (Maps, a `state` object) instead; this makes mistakes throw.
|
||||
Object.freeze(node)
|
||||
return node
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user