prolly-map 0.3.0

Content-addressed versioned map storage primitives.
Documentation
# Language Ports

The crate is Rust-only today. Future Python, TypeScript, or other language
ports should treat Rust as the reference implementation until a shared
conformance suite exists.

## Porting Goals

A port can target different levels:

1. Inspector: read roots, nodes, manifests, and stats from a store.
2. Logical client: expose get, range, and diff against existing snapshots.
3. Writer: create nodes and roots compatible with Rust.
4. Full engine: support batch, merge, GC, sync, async stores, and blobs.

Do not start by promising full byte compatibility unless node encoding and
fixture tests are in place.

## Non-Negotiable Semantics

Every port must match:

- byte lexicographic key ordering;
- delete vs empty value semantics;
- immutable snapshot behavior;
- content-addressed node identity;
- deterministic boundary decisions;
- manifest compare-and-swap behavior;
- standard conflict shape with `None` for absence;
- resolver outcomes of value, delete, or unresolved;
- CRDT custom outcomes of value or delete.

Language APIs can be idiomatic, but storage semantics cannot drift.

## Python Port Strategy

Suggested phases:

Phase 1: read-only tooling

- parse manifests;
- load nodes by CID;
- traverse tree snapshots;
- run point lookups and range scans;
- print stats and debug views.

Phase 2: logical conformance

- compare Python lookup and range results against Rust fixtures;
- decode value envelopes;
- verify diff output;
- verify delete/empty-value behavior.

Phase 3: write support

- implement node building;
- implement content-defined boundaries;
- serialize nodes byte-for-byte with fixtures;
- compute matching CIDs;
- publish named roots through a manifest store.

Phase 4: merge and sync

- implement standard merge;
- implement delete-aware resolvers;
- implement CRDT strategies;
- implement missing-node planning;
- add object-store and local filesystem backends.

Python should probably expose bytes-first APIs initially:

```python
tree = await prolly.load_named_root("main")
value = await tree.get(b"user:001")
rows = [row async for row in tree.range(b"user:", b"user;")]
```

Higher-level string and serde helpers can come later.

## TypeScript and Browser Strategy

The browser story should be async-native:

- `AsyncStore` over IndexedDB, OPFS, Cache Storage, or remote HTTP;
- range pages for UI rendering;
- background sync of missing CIDs;
- local named roots for offline branches;
- optional WASM bridge to the Rust implementation.

TypeScript may start as a client-side inspector or a WASM wrapper before a
native implementation.

## Shared Fixtures

A compatibility fixture should include:

- config;
- sorted input key/value pairs;
- expected root CID;
- serialized node bytes by CID;
- point lookup cases;
- range cases;
- diff cases;
- merge cases;
- manifest cases;
- blob reference cases.

Example fixture shape:

```json
{
  "version": 1,
  "config": {
    "min_chunk_size": 4,
    "max_chunk_size": 1024,
    "chunking_factor": 128,
    "hash_seed": 42,
    "encoding": "raw"
  },
  "entries": [
    ["757365723a303031", "416461"]
  ],
  "root": "hex-encoded-cid"
}
```

The fixture format should use hex for byte strings so every language can load
it without Unicode ambiguity.

## Value Encoding Across Languages

The tree only sees bytes. Cross-language applications should agree on value
encoding separately.

Recommended:

- versioned JSON for debuggable app state;
- versioned CBOR for compact binary state;
- explicit schema version fields;
- checksums for large external payloads;
- no reliance on language-specific serialization formats.

Avoid:

- Rust-only bincode for data meant for Python;
- locale-dependent string normalization in keys;
- timestamps without timezone and precision rules;
- maps with nondeterministic key ordering inside values when those values are
  merged structurally by the application.

## Error Mapping

Ports should map core errors into idiomatic language exceptions or result
types, but preserve categories:

- store error;
- missing node;
- invalid encoding;
- conflict;
- invalid input;
- unsupported feature.

Merge conflict errors should carry the full conflict shape.

## Testing a Port

Minimum test suite:

- sorted ordering over tricky byte keys;
- insert, replace, delete, empty value;
- range boundaries and prefix ranges;
- root stability for fixture datasets;
- diff against fixtures;
- merge with no conflict;
- merge with value/value conflict;
- merge with delete/update conflict;
- custom resolver value, delete, unresolved;
- named root CAS success and failure;
- store missing-node behavior.

Only after these pass should the port add higher-level conveniences.