Liking cljdoc? Tell your friends :D

remuda.pubsub

Subscriptions, invalidation hints, and coalescing.

2 and This is what makes a component pushable — able to update without the user doing anything.

Hints, never data

A hint says "topic X changed, re-derive". It carries no payload, and there is deliberately no event handler: a hint triggers the same rebuild a reconnect uses. That is not a simplification, it is the correctness argument. A data-carrying event is wrong under every bus failure mode — dropped leaves the view permanently wrong, reordered applies stale data last, duplicated breaks any non-idempotent handler — and is correct only on an ordered exactly-once bus. Redis pub/sub is fire-and-forget; NATS core is at-most-once. Since "bring your own bus" is a goal, the engine has to be correct on the weakest plausible one.

With no handler, a drifting handler cannot be written, so the convergence invariant holds structurally rather than by discipline.

Coalescing is lossless, and that is the point

Because hints carry nothing, N hints for a topic are indistinguishable from one. So they collapse into a single rebuild with no information lost — a property data-carrying events could never have. flush-dirty! drains a dirty-topic set; a burst of 100 hints on a hot topic produces one rebuild per affected context rather than 100.

Measured on 1000 subscribed contexts: 100 hints without coalescing meant 100,000 :mount calls; with coalescing, 1,000. for the remaining amplification (one query per viewer); remuda.cache explains why sharing that query has to be opt-in.

The bus is yours

PubSub is a two-method protocol. The engine never implements it — Redis, NATS, core.async, or a bare atom for a single process are all the caller's choice. The engine only needs "deliver this topic to this process"; ordering, delivery guarantees and clustering are the bus's business, and the design above means none of them affect correctness.

Subscriptions, invalidation hints, and coalescing.

2 and  This is what makes a component *pushable* — able to
update without the user doing anything.

## Hints, never data

A hint says "topic X changed, re-derive". It carries no payload, and there is
deliberately **no event handler**: a hint triggers the same rebuild a reconnect
uses. That is not a simplification, it is the correctness argument. A
data-carrying event is wrong under every bus failure mode — dropped leaves the
view permanently wrong, reordered applies stale data last, duplicated breaks any
non-idempotent handler — and is correct only on an ordered exactly-once bus.
Redis pub/sub is fire-and-forget; NATS core is at-most-once. Since "bring your
own bus" is a goal, the engine has to be correct on the weakest plausible one.

With no handler, a drifting handler cannot be written, so the convergence
invariant holds structurally rather than by discipline.

## Coalescing is lossless, and that is the point

Because hints carry nothing, N hints for a topic are indistinguishable from one.
So they collapse into a single rebuild with no information lost — a property
data-carrying events could never have. `flush-dirty!` drains a dirty-topic set;
a burst of 100 hints on a hot topic produces one rebuild per affected context
rather than 100.

Measured on 1000 subscribed contexts: 100 hints without coalescing meant 100,000
`:mount` calls; with coalescing, 1,000.  for the remaining amplification
(one query per viewer); `remuda.cache` explains why sharing that query has to
be opt-in.

## The bus is yours

`PubSub` is a two-method protocol. The engine never implements it — Redis, NATS,
core.async, or a bare atom for a single process are all the caller's choice. The
engine only needs "deliver this topic to this process"; ordering, delivery
guarantees and clustering are the bus's business, and the design above means none
of them affect correctness.
raw docstring

contexts-forclj

(contexts-for reg topics)

Live contexts subscribed to any of topics.

Live contexts subscribed to any of `topics`.
sourceraw docstring

dirty-topicsclj

(dirty-topics reg)
source

flush-dirty!clj

(flush-dirty! reg)

Atomically drains the dirty set and returns the contexts that must rebuild.

Draining and reading must be one operation: a hint arriving mid-flush has to land in the next window rather than being dropped, or a lost hint means a permanently stale view. swap-vals! gives that atomically.

Returns {:topics #{...} :contexts #{...}}. Callers rebuild each context once, however many topics dirtied it — which is the coalescing win.

Atomically drains the dirty set and returns the contexts that must rebuild.

Draining and reading must be one operation: a hint arriving mid-flush has to
land in the *next* window rather than being dropped, or a lost hint means a
permanently stale view. `swap-vals!` gives that atomically.

Returns `{:topics #{...} :contexts #{...}}`. Callers rebuild each context once,
however many topics dirtied it — which is the coalescing win.
sourceraw docstring

local-pubsubclj

(local-pubsub)

An in-process PubSub, backed by an atom.

Sufficient for a single server, for tests, and for development. A multi-server deployment supplies a real bus instead — the engine cannot tell the difference, which is the point of the seam.

An in-process `PubSub`, backed by an atom.

Sufficient for a single server, for tests, and for development. A multi-server
deployment supplies a real bus instead — the engine cannot tell the difference,
which is the point of the seam.
sourceraw docstring

publish-and-flush!clj

(publish-and-flush! reg bus topic)

Convenience for tests and single-process use: publish then immediately flush.

Real deployments flush on a timer, so hints arriving close together coalesce. Flushing per publish defeats that, so this is deliberately not what the engine does in production.

Convenience for tests and single-process use: publish then immediately flush.

Real deployments flush on a timer, so hints arriving close together coalesce.
Flushing per publish defeats that, so this is deliberately not what the engine
does in production.
sourceraw docstring

PubSubcljprotocol

A hint channel. Deliberately minimal: the engine needs no ordering, no delivery guarantee, and no replay, because a hint carries no information beyond its topic.

A hint channel. Deliberately minimal: the engine needs no ordering, no
delivery guarantee, and no replay, because a hint carries no information beyond
its topic.

publish!clj

(publish! this topic)

Announce that topic changed. Fire-and-forget; may be lost.

Announce that `topic` changed. Fire-and-forget; may be lost.

subscribe!clj

(subscribe! this topic handler)

Register handler, a 1-arg fn of topic, to be called on hints for topic. Returns a 0-arg unsubscribe fn.

Register `handler`, a 1-arg fn of topic, to be called on hints for `topic`.
Returns a 0-arg unsubscribe fn.
sourceraw docstring

registryclj

(registry)
source

subscribe-context!clj

(subscribe-context! reg bus context-id topics)

Subscribes context-id to topics, replacing any previous subscription set.

Diffs against the current set rather than resubscribing wholesale, because :subscribe can depend on params: when params change, some topics persist and should not be torn down and re-established. This is the same add/remove-by-identity problem as keyed children and hot-reload preservation — the third place it appears.

Returns the topics actually added and removed, which is what makes the diffing testable rather than assumed.

Subscribes `context-id` to `topics`, replacing any previous subscription set.

 Diffs against the current set rather than resubscribing wholesale, because
 `:subscribe` can depend on params: when params change, some topics persist and
 should not be torn down and re-established. This is the same
 add/remove-by-identity problem as keyed children and hot-reload preservation
— the third place it appears.

 Returns the topics actually added and removed, which is what makes the diffing
 testable rather than assumed.
sourceraw docstring

topics-ofclj

(topics-of reg context-id)
source

unsubscribe-context!clj

(unsubscribe-context! reg context-id)

Removes every subscription for context-id. Called on disconnect.

Removes every subscription for `context-id`. Called on disconnect.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close