Skip to content

feat(atom): add dedupeWith combinator for custom write equivalence#415

Open
front-depiction wants to merge 1 commit intotim-smart:mainfrom
front-depiction:feat/dedupe-with
Open

feat(atom): add dedupeWith combinator for custom write equivalence#415
front-depiction wants to merge 1 commit intotim-smart:mainfrom
front-depiction:feat/dedupe-with

Conversation

@front-depiction
Copy link
Copy Markdown

@front-depiction front-depiction commented Apr 18, 2026

Summary

Every atom already deduplicates writes using Equal.equals (Node.setValue in internal/registry.ts), but Equal.equals falls back to reference equality for values that don't implement Equal — plain objects, arrays, API payloads, derived collections. A structurally-identical new reference therefore re-notifies every subscriber and cascades invalidation downstream. This combinator sits in between no equality, and implementing custom Equal.Equal traits.

dedupeWith attaches a custom Equivalence<A> to an atom so structurally-equal writes become no-ops.

const user = Atom.make({ id: "u1", name: "Alice" }).pipe(
  Atom.dedupeWith((a, b) => a.id === b.id && a.name === b.name)
)

Design

  • Atom<A> gains readonly eq: Equivalence<A>, defaulted via AtomProto to Equal.equals. No branching in the hot path — this.atom.eq(prev, next) is always a function call.
  • dedupeWith is dual (data-first / data-last), returns Self → Self, and clones the prototype the same way setIdleTTL does (Object.assign(Object.create(proto), { ...self, eq })), preserving Writable / AtomRuntime proto methods.
  • Node.setValue changes one line: Equal.equals(this._value, value)this.atom.eq(this._value, value).
  • Default behavior is preserved bit-for-bit: existing atoms with no dedupeWith call continue to dedupe via Equal.equals.

Test plan

New describe("dedupeWith") block in packages/atom/test/Atom.test.ts covers:

  • plain-object atom without dedupeWith re-notifies on structurally-equal new references (status-quo witness)
  • dedupeWith with a custom equivalence suppresses notify on structurally-equal writes and fires on genuinely new values
  • dedupeWith does not suppress the initial value
  • downstream derived atoms are not invalidated when the parent dedupes
  • composes with setIdleTTL in either order (both eq and idleTTL preserved)
  • default behavior preserved for primitives

Full suite: 104/104 pass. pnpm lint, pnpm circular, pnpm docgen clean. Changeset added (@effect-atom/atom: minor).

Every atom already deduplicates writes via Equal.equals, but that falls back
to reference equality for plain objects/arrays — causing re-notifies on
structurally-equal new references. dedupeWith lets callers attach a custom
Equivalence so structurally-equal writes become no-ops.

The default is preserved exactly: AtomProto carries eq = Equal.equals, so
existing atoms see no behavior change.
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.

1 participant