Rewrites the library around one abstract base class per layer, following review feedback. src/ goes from ~3,800 to ~1,500 lines; performance is similar or better (see DESIGN.md §9).
What changed
One abstract class per layer.
BaseKVStorage implementations provide five raw methods (readMany, writeMany, removeMany, writeIfAbsent, scan), and the user-facing API (child, get, set, delete, keys, entries, deleteTree, batch variants) is defined once in the base.
BaseEventStorage and BaseDocumentStorage follow the same pattern.
Client and server share an API.KVStorageHttp is a normal KV storage; KVStorageHttpServer wraps any storage. Wrappers (e.g. a future encryption layer) are just subclasses delegating the primitives. test/helpers.ts has a counting wrapper as an example.
No KV indexes. The ordered key space is the KV index.
Document indexes are just field names ({ indexes: { users: ['email', 'age'] } }), handled by each store. The in-memory store uses equality maps; the KV-backed store scans.
Key escaping: every character at or below / (plus ~) is escaped as ~XX, so each child's keys stay contiguous and child listing is a simple skip-scan.
Event-sourced documents are an event storage plus any document storage holding projections (about 125 lines). A _version field on projections detects conflicts, and the store rebuilds from the stream after a crashed write.
Layout and style:kv-storage/, event-storage/, document-storage/, utils/; classes; TS private; no overloads or overrides; tests only under test/.
Removed / changed (may want back)
Unique indexes, the query planner, $ne/$nin/$exists/$and/$or/$not, field projections, and KV tree().
The separator is fixed to /.
writeIfAbsent is required for every KV storage. In exchange, events are multi-writer safe everywhere.
_version is reserved in event-sourced documents.
DocumentStorageKV is last-writer-wins under concurrent writes to the same document.
Testing
npm test: typecheck plus 113 tests.
A shared KV conformance suite runs against in-memory, the wrapper, HTTP in-process and HTTP over a real socket. A model-based property test checks keys/entries against a reference map.
A shared document suite runs against in-memory (with and without indexes), KV-backed, and event-sourced stores with both projection backends.
Event tests cover concurrency between two instances and a property test of read windows.
expectTypeOf tests cover typed schemas across all three layers.
DESIGN.md is rewritten to describe the new design (section 7 lists the changes from the original brief).
Rewrites the library around one abstract base class per layer, following review feedback. `src/` goes from ~3,800 to ~1,500 lines; performance is similar or better (see DESIGN.md §9).
## What changed
- **One abstract class per layer.**
- `BaseKVStorage` implementations provide five raw methods (`readMany`, `writeMany`, `removeMany`, `writeIfAbsent`, `scan`), and the user-facing API (`child`, `get`, `set`, `delete`, `keys`, `entries`, `deleteTree`, batch variants) is defined once in the base.
- `BaseEventStorage` and `BaseDocumentStorage` follow the same pattern.
- **Client and server share an API.** `KVStorageHttp` is a normal KV storage; `KVStorageHttpServer` wraps any storage. Wrappers (e.g. a future encryption layer) are just subclasses delegating the primitives. `test/helpers.ts` has a counting wrapper as an example.
- **No KV indexes.** The ordered key space is the KV index.
- **Document indexes are just field names** (`{ indexes: { users: ['email', 'age'] } }`), handled by each store. The in-memory store uses equality maps; the KV-backed store scans.
- **Queries:** equality, `$gt`/`$gte`/`$lt`/`$lte`/`$in`, dot paths, sort/limit/skip.
- **Key escaping:** every character at or below `/` (plus `~`) is escaped as `~XX`, so each child's keys stay contiguous and child listing is a simple skip-scan.
- **Event-sourced documents** are an event storage plus any document storage holding projections (about 125 lines). A `_version` field on projections detects conflicts, and the store rebuilds from the stream after a crashed write.
- **Layout and style:** `kv-storage/`, `event-storage/`, `document-storage/`, `utils/`; classes; TS `private`; no overloads or overrides; tests only under `test/`.
## Removed / changed (may want back)
- Unique indexes, the query planner, `$ne`/`$nin`/`$exists`/`$and`/`$or`/`$not`, field projections, and KV `tree()`.
- The separator is fixed to `/`.
- `writeIfAbsent` is required for every KV storage. In exchange, events are multi-writer safe everywhere.
- `_version` is reserved in event-sourced documents.
- `DocumentStorageKV` is last-writer-wins under concurrent writes to the same document.
## Testing
- `npm test`: typecheck plus 113 tests.
- A shared KV conformance suite runs against in-memory, the wrapper, HTTP in-process and HTTP over a real socket. A model-based property test checks keys/entries against a reference map.
- A shared document suite runs against in-memory (with and without indexes), KV-backed, and event-sourced stores with both projection backends.
- Event tests cover concurrency between two instances and a property test of read windows.
- `expectTypeOf` tests cover typed schemas across all three layers.
DESIGN.md is rewritten to describe the new design (section 7 lists the changes from the original brief).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
- 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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Rewrites the library around one abstract base class per layer, following review feedback.
src/goes from ~3,800 to ~1,500 lines; performance is similar or better (see DESIGN.md §9).What changed
BaseKVStorageimplementations provide five raw methods (readMany,writeMany,removeMany,writeIfAbsent,scan), and the user-facing API (child,get,set,delete,keys,entries,deleteTree, batch variants) is defined once in the base.BaseEventStorageandBaseDocumentStoragefollow the same pattern.KVStorageHttpis a normal KV storage;KVStorageHttpServerwraps any storage. Wrappers (e.g. a future encryption layer) are just subclasses delegating the primitives.test/helpers.tshas a counting wrapper as an example.{ indexes: { users: ['email', 'age'] } }), handled by each store. The in-memory store uses equality maps; the KV-backed store scans.$gt/$gte/$lt/$lte/$in, dot paths, sort/limit/skip./(plus~) is escaped as~XX, so each child's keys stay contiguous and child listing is a simple skip-scan._versionfield on projections detects conflicts, and the store rebuilds from the stream after a crashed write.kv-storage/,event-storage/,document-storage/,utils/; classes; TSprivate; no overloads or overrides; tests only undertest/.Removed / changed (may want back)
$ne/$nin/$exists/$and/$or/$not, field projections, and KVtree()./.writeIfAbsentis required for every KV storage. In exchange, events are multi-writer safe everywhere._versionis reserved in event-sourced documents.DocumentStorageKVis last-writer-wins under concurrent writes to the same document.Testing
npm test: typecheck plus 113 tests.expectTypeOftests cover typed schemas across all three layers.DESIGN.md is rewritten to describe the new design (section 7 lists the changes from the original brief).
🤖 Generated with Claude Code
https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
Harvmaster referenced this pull request2026-10-23 19:42:07 +11:00
Closing: this work is included in #2 (
sync), which now targetsmaindirectly.Pull request closed