Skip to content

feat(store)!: make json feature additive — repository() returns NeedsCodec unconditionally (#211)#298

Merged
joeldsouzax merged 1 commit into
mainfrom
feat/211-additive-json
Jul 5, 2026
Merged

feat(store)!: make json feature additive — repository() returns NeedsCodec unconditionally (#211)#298
joeldsouzax merged 1 commit into
mainfrom
feat/211-additive-json

Conversation

@joeldsouzax

Copy link
Copy Markdown
Contributor

Summary

Closes #211. Makes the json (and snapshot-json) feature purely additive so that Cargo feature unification can never flip a public signature out from under a crate that didn't ask for it.

Two builder methods previously returned different public types depending on #[cfg]:

  • Store::repository()RepositoryBuilder<S, JsonCodec, A> with json, RepositoryBuilder<S, NeedsCodec, A> without.
  • RepositoryBuilder::snapshot_store() → byte-level store + auto-JsonCodec wrap with snapshot-json, pre-composed typed store without.

Under feature unification, a transitive dep enabling json would silently change these signatures for everyone — code written against NeedsCodec / a typed snapshot store would fail to compile on certain dependency graphs. Latent breakage, exactly the kind the 1.0 freeze audit (cross-cutting I4) flags.

Changes

  • repository() is now unconditional and always returns RepositoryBuilder<S, NeedsCodec, A>. NeedsCodec::new() drops its #[cfg(not(json))] gate.
  • .json() builder method (#[cfg(json)]) — sugar for .codec(JsonCodec::default()). Enabling json only adds this method.
  • snapshot_store() split: always-available typed-store form (under snapshot), and a new #[cfg(snapshot-json)] snapshot_store_json() for the byte-level auto-JSON-wrap convenience.
  • Migrated all call sites that relied on the hidden JSON default to the explicit surface: fjall-end-to-end example (7), serde_codec_tests (1), snapshot_integration_tests (16), snapshot_tests (3).

Before → after

// events
store.repository::<Order>().build()            // implicit JSON default
store.repository::<Order>().json().build()      // explicit

// snapshots (byte-level store)
store.repository::<A>().snapshot_store(bytes).build()        // implicit wrap
store.repository::<A>().json().snapshot_store_json(bytes).build()  // explicit

The two jsons in the snapshot form are two distinct codecs — one for the event stream, one for the aggregate-state snapshot; both were previously silent defaults.

Verification

  • cargo build -p nexus-store (default features, json off) — repository() compiles as NeedsCodec
  • cargo test -p nexus-store --no-run (json on via dev-dep) — .json() / snapshot_store_json() exercised
  • cargo build -p nexus-example-fjall-end-to-end
  • ✅ full nix flake check (clippy, fmt, doc, deny, audit, hakari, nextest) green

Both feature states are covered by the existing CI matrix (empty default features = json off; test dev-dep pulls snapshot-json = json on), so the additivity property is verified without a bespoke test.

Breaking change

Marked feat(store)!. Callers relying on the implicit JSON default must add .json(); snapshot-json users pass byte-level stores to snapshot_store_json() instead of snapshot_store(). Pre-1.0 experimental API.

🤖 Generated with Claude Code

…Codec unconditionally (#211)

Under Cargo feature unification, a transitive dep enabling `json` must not
change any public signature for crates that don't. Two builder methods
violated this by returning different types per `#[cfg]`:

- `Store::repository()` returned `RepositoryBuilder<S, JsonCodec, A>` with
  `json`, `RepositoryBuilder<S, NeedsCodec, A>` without.
- `RepositoryBuilder::snapshot_store()` took a byte-level store and
  auto-wrapped `JsonCodec` with `snapshot-json`, but a pre-composed typed
  store without it.

Make both additive:

- `repository()` is now unconditional and always returns `NeedsCodec`.
  Enabling `json` adds a `.json()` builder convenience (sugar for
  `.codec(JsonCodec::default())`) instead of flipping the return type.
  `NeedsCodec::new()` loses its `#[cfg(not(json))]` gate.
- `snapshot_store()` is now the always-available typed-store form; the
  byte-level auto-JSON-wrap moves to a new `snapshot-json`-gated
  `snapshot_store_json()`.

Migrate call sites that relied on the hidden JSON default (fjall example,
serde/snapshot integration + builder tests) to the explicit `.json()` /
`snapshot_store_json()` surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@joeldsouzax joeldsouzax enabled auto-merge (squash) July 5, 2026 22:25
@joeldsouzax joeldsouzax merged commit 1508716 into main Jul 5, 2026
4 checks passed
@joeldsouzax joeldsouzax deleted the feat/211-additive-json branch July 5, 2026 22:25
@github-actions github-actions Bot mentioned this pull request Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[freeze][T1] Make the json feature additive (repository() signature differs by cfg)

1 participant