Simplify: one abstract base class per layer #1

Closed
Harvmaster wants to merge 1 commits from simplify into main
Owner

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.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE

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
Harvmaster added 1 commit 2026-10-23 19:10:19 +11:00
Simplify: one abstract base class per layer
CI / Node 20 (pull_request) Successful in 10s
CI / Node 22 (pull_request) Successful in 11s
08fb78721f
- 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
Author
Owner

Closing: this work is included in #2 (sync), which now targets main directly.

Closing: this work is included in #2 (`sync`), which now targets `main` directly.
Harvmaster closed this pull request 2026-10-24 00:33:38 +11:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Harvmaster/PotentialStorage#1