Liking cljdoc? Tell your friends :D

darkstar.patch

Translates diff ops (see remuda.diff) into datastar patch instructions.

This is the seam between the data model and the DOM model, and it is the part 2 predicted would be wrong. The diff engine says "path [:items 2] changed"; datastar needs a CSS selector and a patch mode.

Deliberately dependency-free: this namespace emits descriptions of patches as plain maps, not calls into the datastar SDK. Three reasons.

  1. It stays testable with no server, no SSE connection, and no SDK on the classpath — the same property that made the diff engine cheap to verify.
  2. It keeps the transport seam narrow . The SDK's own seam is a four-method SSEGenerator protocol, so whatever applies these descriptions is the only part that needs to know about a server.
  3. Op order is part of the diff contract (move ops are not commutative), and a vector of descriptions preserves it where a side-effecting translation might not.

Mode selection, mapping the op vocabulary onto datastar's eight modes:

:replace / :assoc -> outer (re-render the owning element) :dissoc / :delete -> remove :insert / :move -> before | after | append

Note that :replace and :assoc do not patch a value; they identify which element owns the changed path and re-render it. Datastar patches elements, so the unit of update is an element, never a bare value.

Boundaries: not every path owns an element

The load-bearing correction found while building this . The first version of this namespace assumed every view path maps to a DOM element, and derived a selector directly from the path. That is wrong: paths are finer grained than elements. Given

[:li {:id "c1-items-2"} (:text item)]

the path [:items 2 :text] addresses a text node, not an element, so #c1-items-2-text matches nothing and the patch silently fails.

So a component must declare which paths own elements — its boundaries — and a changed path resolves to its nearest boundary ancestor. [:items 2 :text] resolves to [:items 2], and that <li> is re-rendered whole.

Boundaries are declared as a set of paths, where :* is a wildcard matching any key of a keyed collection:

#{[] [:title] [:items] [:items :*]}

This is also what keeps re-render granularity honest: the smallest thing that can be patched is the smallest thing declared as a boundary, and a component author controls that trade directly.

Translates diff ops (see `remuda.diff`) into datastar patch
instructions.

This is the seam between the data model and the DOM model, and it is the part
2 predicted would be wrong. The diff engine says "path
`[:items 2]` changed"; datastar needs a CSS selector and a patch mode.

Deliberately dependency-free: this namespace emits *descriptions* of patches
as plain maps, not calls into the datastar SDK. Three reasons.

1. It stays testable with no server, no SSE connection, and no SDK on the
   classpath — the same property that made the diff engine cheap to verify.
2. It keeps the transport seam narrow . The SDK's own seam is
   a four-method `SSEGenerator` protocol, so whatever applies these
   descriptions is the only part that needs to know about a server.
3. Op order is part of the diff contract (move ops are not commutative), and a
   vector of descriptions preserves it where a side-effecting translation
   might not.

Mode selection, mapping the op vocabulary onto datastar's eight modes:

  :replace / :assoc  -> outer   (re-render the owning element)
  :dissoc / :delete  -> remove
  :insert / :move    -> before | after | append

Note that `:replace` and `:assoc` do not patch a *value*; they identify which
element owns the changed path and re-render it. Datastar patches elements, so
the unit of update is an element, never a bare value.

## Boundaries: not every path owns an element

The load-bearing correction found while building this . The
first version of this namespace assumed every view path maps to a DOM element,
and derived a selector directly from the path. That is wrong: paths are finer
grained than elements. Given

    [:li {:id "c1-items-2"} (:text item)]

the path `[:items 2 :text]` addresses a *text node*, not an element, so
`#c1-items-2-text` matches nothing and the patch silently fails.

So a component must declare which paths own elements — its **boundaries** — and
a changed path resolves to its nearest boundary ancestor. `[:items 2 :text]`
resolves to `[:items 2]`, and that `<li>` is re-rendered whole.

Boundaries are declared as a set of paths, where `:*` is a wildcard matching
any key of a keyed collection:

    #{[] [:title] [:items] [:items :*]}

This is also what keeps re-render granularity honest: the smallest thing that
can be patched is the smallest thing declared as a boundary, and a component
author controls that trade directly.
raw docstring

op->patchclj

(op->patch {:keys [component-id boundaries boundary-ids]} op)

Translates one diff op into a patch description.

ctx is {:component-id, :boundaries}.

Returns a map with :mode and :selector (the element to patch). Patches that need HTML carry :render {:path ...} — a marker naming what to render rather than the HTML itself, since rendering belongs to :render. Moves carry :move (the selector of the element to relocate) and never :render, because the element already exists.

Changed paths are resolved to their nearest declared boundary, so the selector always names an element that exists. See the namespace docstring.

Translates one diff op into a patch description.

`ctx` is `{:component-id, :boundaries}`.

Returns a map with `:mode` and `:selector` (the element to patch). Patches
that need HTML carry `:render {:path ...}` — a marker naming *what* to render
rather than the HTML itself, since rendering belongs to `:render`. Moves carry
`:move` (the selector of the element to relocate) and never `:render`, because
the element already exists.

Changed paths are resolved to their nearest declared boundary, so the selector
always names an element that exists. See the namespace docstring.
sourceraw docstring

ops->patchesclj

(ops->patches ctx ops)

Translates diff ops into patch descriptions, preserving order.

Order preservation is not incidental: move ops are not commutative and their anchors assume earlier ops have been applied (see remuda.diff).

Consecutive duplicate patches are collapsed — two fields of one item both changing resolve to the same boundary and would otherwise re-render it twice.

Translates diff ops into patch descriptions, preserving order.

Order preservation is not incidental: move ops are not commutative and their
anchors assume earlier ops have been applied (see `remuda.diff`).

Consecutive duplicate patches are collapsed — two fields of one item both
changing resolve to the same boundary and would otherwise re-render it twice.
sourceraw docstring

path->idclj

(path->id component-id path)

DOM id for the element owning path, within component component-id.

Ids are joined with -; path segments are stringified. Keyword segments lose their leading colon and namespaces are flattened with _, since : and / are not usable in a CSS id selector without escaping.

DOM id for the element owning `path`, within component `component-id`.

Ids are joined with `-`; path segments are stringified. Keyword segments lose
their leading colon and namespaces are flattened with `_`, since `:` and `/`
are not usable in a CSS id selector without escaping.
sourceraw docstring

path->selectorclj

(path->selector component-id path)

CSS id selector for the element owning path.

CSS id selector for the element owning `path`.
sourceraw docstring

render-targetsclj

(render-targets patches)

Distinct view paths that must be re-rendered to satisfy patches.

A caller renders each of these once, then supplies the HTML. Deduplicated because several ops can touch one element — e.g. two fields of the same item both change, and the owning element only needs rendering once.

Distinct view paths that must be re-rendered to satisfy `patches`.

A caller renders each of these once, then supplies the HTML. Deduplicated
because several ops can touch one element — e.g. two fields of the same item
both change, and the owning element only needs rendering once.
sourceraw docstring

resolve-boundaryclj

(resolve-boundary boundaries path)

Nearest ancestor of path that owns a DOM element, per boundaries.

Returns [] (the component root) when nothing more specific matches, which degrades to re-rendering the whole component — correct, just coarse.

This exists because view paths are finer grained than DOM elements: a path may address a text node or an attribute value, neither of which is patchable. See the namespace docstring.

Nearest ancestor of `path` that owns a DOM element, per `boundaries`.

Returns `[]` (the component root) when nothing more specific matches, which
degrades to re-rendering the whole component — correct, just coarse.

This exists because view paths are finer grained than DOM elements: a path may
address a text node or an attribute value, neither of which is patchable. See
the namespace docstring.
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