re-frame2 — API¶
Type: Reference Reference for the CLJS implementation's API: signatures, status, cross-references. No rationale — per-Spec docs own the why. Pattern-level contracts live in 000-Vision §The pattern and the per-Spec docs.
:fx-overridesasymmetry: id-valued at the pattern level; CLJS reference also accepts fn values — see 002 §:fx-overrides.
Conventions¶
- Status — exactly one base value, optionally combined with one or more parenthesised qualifiers. The closed set below is the same vocabulary the generated
api-manifest.edncurates (its:statusfield) — the two MUST agree: - Base values:
v1(ships in v1).v1 (preserved)(exists in current re-frame; preserved unchanged).v1 (preserved + extended)(exists today; v1 adds new arity or behaviour).EP-NNNN(a surface introduced or reshaped by a named pre-alpha EP, shipping in v1 — e.g.reg-event(EP-0018),reg-interceptor(EP-0022); the canonical lineage is the named EP, cited in the row's Notes).post-v1 lib(design spec in v1 Specs but ships in a post-v1 library).post-v1 (planned, rf2-<id>)(specced normatively but not yet shipped; the impl is tracked by the named bead — per the Projection-maintenance rule below).
- Qualifiers (parenthesised, combinable):
dev-only(elided in production builds — the macro emit site or runtime body, depending on the API);changed, EP-NNNN(a preserved v1 surface a named EP changed — e.g.v1 (changed, EP-0017));optional capability(ships only when the owning optional artefact is on the classpath, optionally narrowedoptional capability, dev/test);internal lowering only(an EP surface retained as a framework-internal lowering seam, not a public authoring form — e.g.EP-0022 (internal lowering only)). - Examples:
v1,v1 (preserved),v1 (dev-only),v1 (preserved, dev-only),v1 (changed, EP-0017),EP-0018,EP-0022 (internal lowering only),v1 (optional capability),post-v1 lib,post-v1 (planned, rf2-<id>). - The
re-frame.alphanamespace is dissolved — no APIs in this reference live outsidere-frame.core(with the documented per-namespace exceptions:re-frame.test-supportandre-frame.test-helpers). - Macro/Fn: marked
M(macro) orFn. - Spec column — names exactly the canonical owning Spec (the per-Spec doc whose contract this API implements). Migration rules and other cross-references are NOT in the Spec column; they appear in the Notes column when relevant.
- Configure keys — runtime configuration is uniformly via
(rf/configure! {<key> <opts>, …}), a single nested map. Every<key>is enumerated in §Configure keys below; per-area tables call out which keys their APIs read but do not redefine the key's vocabulary. - Per-artefact public namespaces. The core surfaces live in
re-frame.core. Per-feature artefacts ship their own public namespace; consumers:requirethe namespace directly (with the documented exceptions of the epoch + SSR-query surfaces, which late-bind re-exports throughre-frame.core). Front-porch boundary.re-frame.coreis the small app-developer front porch — registration, dispatch, subscribe, the frame basics, interceptors, lifecycle, configure, and the core trace / egress / registrar-query / app-realm surfaces. The optional-feature registration MACROS stay on the façade (reg-route,reg-flow,reg-app-schema/reg-app-schemas,reg-machine/defmachine,reg-resource/reg-mutation/reg-resource-scope,reg-error-projector,reg-head,reg-http-interceptor) because they capture call-site source-coords and have no owned-namespace macro form — registration stays central. But the optional features' non-registration query / introspection / lifecycle helpers are NOT re-exported fromre-frame.core; reach them through their owning namespace:re-frame.schemas(app-schema-at/app-schema-meta-at/app-schemas/app-schemas-digest/set-schema-validator!/-explainer!/-printer!/set-schema-fns!),re-frame.machines(reg-machine*/make-machine-handler/machine-transition/machines/machine-meta/machine-by-system-id),re-frame.routing(match-url/route-url/current-url/clear-route),re-frame.flows(clear-flow). The epoch + SSR-query re-exports remain on the façade as documented late-bind exceptions (rows below):
| Namespace | Artefact | Surfaces |
|---|---|---|
re-frame.core |
core | the registration / dispatch / subscribe / interceptor / lifecycle / configure surfaces; late-binds re-exports for two artefacts: (a) the re-frame.epoch surface (epoch-history, restore-epoch!, replace-frame-state!, projected-record, projected-history) — the epoch listener stream is reached through the core-native (register-listener! :epoch …) verb, not a per-channel re-export; (b) the re-frame.ssr query surface (render-to-string, render-tree-hash, project-error, render-head, active-head, head-model->html, head-snapshot). The streaming-render-shell / streaming-render-continuation / streaming-build-final-payload triple is not re-exported — the streaming surface is host-adapter territory and the SSR-aware host (re-frame.ssr.ring / equivalents) requires [re-frame.ssr :as ssr] directly. Re-exports activate when the named artefact is on the classpath; absent artefacts surface :rf.error/<feature>-artefact-missing errors. |
re-frame.test-support |
core | assert-path-equals, poll-until, fixture machinery (per §Testing). Runtime-state axis — registrar, frames, app-db, drain. assert-path-equals mirrors the :rf.assert/path-equals Story event. View-tree assertions live in the sibling re-frame.test-helpers. |
re-frame.test-helpers |
core | View-assertion helpers — hiccup-walk (find-by-testid / find-by-attr family, text-content, extract-handler, invoke-handler), the testid authoring helper, and the expand-tree walker (per §Testing — View-assertion helpers). View-tree axis — hiccup data, testids, attached handlers. Runtime-state assertions live in the sibling re-frame.test-support. |
re-frame.ssr |
day8/re-frame2-ssr |
render-to-string, emit-ui-tree, render-tree-hash, streaming-render-*, render-head, active-head, head-model->html, head-snapshot, project-error (per §SSR). |
re-frame.ssr.ring |
day8/re-frame2-ssr-ring |
the Ring host-adapter (default-html-shell, streaming-prefix/suffix, trusted-shell hooks per Spec 011). |
re-frame.schemas |
day8/re-frame2-schemas |
app-schemas, app-schema-at, app-schema-meta-at, app-schemas-digest, set-schema-validator!/-explainer!/-printer!/set-schema-fns!, validate-at-boundary-interceptor (per §Schemas). |
re-frame.http |
day8/re-frame2-http |
the verb helpers get / post / put / delete / patch / head / options (per §HTTP requests). |
re-frame.machines |
day8/re-frame2-machines (post-v1 scaffolding) |
reg-machine, make-machine-handler, machine-transition, machines, machine-meta, the :rf.machine/spawn / :rf.machine/destroy fx (per §Machines). |
re-frame.epoch |
day8/re-frame2-epoch |
epoch-history, restore-epoch!, replace-frame-state!, (rf/configure! {:epoch-history ...}), and the epoch listener stream reached via (rf/register-listener! :epoch id f) / (rf/unregister-listener! :epoch id). Re-exported / late-bound through re-frame.core via late-bind hooks — (:require [re-frame.epoch]) at boot before consuming the surfaces through re-frame.core (per Tool-Pair §Time-travel — Artefact home). |
re-frame.adapter.uix |
day8/re-frame2-uix |
UIx-specific surfaces (per §UIx adapter). |
- Projection-maintenance rule. This doc is a non-canonical projection — the canonical contract lives in the per-Spec docs cited in each row's Spec column. The projection MUST stay in sync with shipped artefacts. Every row carries: owner (Spec column) — the canonical spec doc; artefact / namespace — where the public-var lives (table above); public-var status —
v1/v1 (preserved)/post-v1 lib/post-v1 (planned, rf2-<id>)for surfaces specced normatively but not yet shipped (the spec contract holds; the impl is tracked by the named bead); verification pointer — the conformance fixture, the per-artefact test, or the AI-Audit row that asserts the row holds. Rows that document a surface neither shipped nor on a tracking bead MUST be cut from this projection — the design's normative claim then lives only in the owner spec.
Tier taxonomy¶
Every public-surface row in this document is either supported — a documented API that downstream apps and tools may rely on, in the status the row's Status column gives — or, for the one implementation tier, public-for-technical-reasons only (exported but explicitly not a surface to depend on; see below). Supported is not the same as front-porch: a system that ships restore-epoch!, projected-history, adapter hooks, and replace-frame-state! has a large public surface, but only a small slice of it is what a new app developer should ever reach for. The Tier column on every table below records that slice, using a closed vocabulary of eight values:
| Tier | Meaning | Who reaches for it |
|---|---|---|
| front-porch | The tight set a new app developer needs to build a working app — register / dispatch / subscribe, the frame basics, the everyday reads. Loaded by default by the Guide and the skills. | Every app author, day one. |
| advanced | Power-user surfaces that solve real problems but are not first-reach — epoch-listener registration, trace projection, flows internals, the *-twin fn forms, the lower-level lifecycle (install-adapter!, init-platform). Opt-in. |
Experienced authors, library authors, niche cases. |
| tooling | Dev / inspection surfaces consumed by Story, Xray, the pair-MCP servers, and other Spec 009 / Tool-Pair tools — trace listeners, the registrar query API, off-box-egress projections, the epoch query surface, the Story run-result read accessors. Not for application logic. | Tool and instrumentation authors. |
| adapter | The substrate-adapter hooks — the per-substrate adapter Var, the hooks and set-*! seams a substrate adapter implements or installs. |
Adapter authors (Reagent / UIx / custom). |
| testing | Test-only helpers — fixture machinery, assertion helpers, view-tree walkers, the HTTP-stub surfaces. Elided or simply absent from production paths. | Test authors. |
| internal-public | A supported host/tool embedding point: stable, exported, and safe to call, but not an application surface — provided for a specific tool/host integration, not for app logic. The Xray mount-<panel>! / mount-shell! family is the canonical (and now sole) instance — a host that builds its own Xray chrome may call them; an app never should. Narrower than it looks: it is not the bucket for every exported helper (those are implementation); it is the small set a host may legitimately embed against. |
Specific host/tool integrations only. |
| implementation | Public-for-technical-reasons only — exported because a sibling namespace, the tool chrome, or a test must reach it across a namespace boundary (ClojureScript has no cheap cross-namespace-private seam), but it is NOT a supported surface: an app or tool MUST NOT depend on it, and it may change or vanish without notice. The Story run-variant / migration vocabulary, the per-feature artefact lifecycle/validation/cache helpers (re-frame.schemas, re-frame.ssr, re-frame.routing, re-frame.machines, re-frame.flows), the Ring host-adapter internals, and the Xray panel-leaf Panel reg-views all live here. |
Nobody downstream — internal plumbing that merely happens to be public. |
| deprecated | On the way out; a replacement exists. Retained only long enough for callers to migrate. (Pre-alpha currently carries none — removed surfaces live in §Removed / not shipped, not here.) | Nobody new — migrate off. |
The Guide and the skills load FRONT-PORCH only by default. A new app developer reading the Guide, or an AI agent operating through the skills, sees the front-porch tier and nothing else unless they opt in. Advanced, tooling, adapter, testing, and internal-public surfaces are opt-in — reached by explicitly pulling in the relevant chapter, the relevant artefact, or the relevant tool. The implementation tier is never reached for: it is not opt-in, it is off-limits — exported only for technical reasons (see the table) and excluded from the supported surface entirely. The acceptance bar for this taxonomy: a new app developer can read the one-page front-porch list (the union of all front-porch rows below) and never trip over trace projection, epoch-listener registration, adapter hooks, or Xray internals.
Tier vs Status. Tier and Status are orthogonal axes. Status records shipping lineage (v1 / v1 (preserved) / post-v1 lib / …). Tier records who-reaches-for-it. A surface can be v1 and tooling (register-listener!), or post-v1 lib and front-porch (the Story run verb is post-v1 lib/tooling; a hypothetical post-v1 ergonomic core helper would be post-v1 lib/front-porch). Read the two columns together.
The front-porch / back-room split is the first instance, generalised. The multi-frame surface (§View ergonomics, per 002 §The multi-frame surface) was already organised as a front-porch / back-room split — dispatch / subscribe / with-frame on the porch, the {:frame …} override in the back room (and capture-frame, the ONE public HOLD primitive, alongside it). The Tier column generalises that split across the whole API: front-porch is the porch; advanced / tooling / adapter / testing / internal-public are the back rooms (all still supported); implementation is below the floorboards — exported but off the supported surface entirely.
Closed vocabulary, restrictive-by-default. The eight values above are the complete set — no ninth tier is added without an explicit governance decision. When a surface is genuinely ambiguous between two tiers, pick the more restrictive one (advanced over front-porch; internal-public over advanced; implementation over internal-public when the var is not actually a host/tool integration point but merely an exported helper) and note the call in the row's Notes. The downstream manifest consumes Tier as a first-class field, so the value must come from this closed set. The internal-public / implementation boundary: ask "is this a surface a host or tool legitimately embeds against?" — if yes, internal-public (the Xray mount family); if it is exported only so the framework's own namespaces / tests can reach it across a boundary, implementation. The split keeps the supported surface honest.
What the Tier column covers. Tier is a property of a public var (a fn / macro / Var). The tables that enumerate keyword-addressed registrations — standard events (:rf.route/navigate, :rf/hydrate, …), standard subs (:rf/route, :rf/response, …), standard fx (:rf.nav/push-url, :rf.http/managed, …), standard cofx, reserved fx-ids, the :fx-entry catalogue, the spec-internal schemas, the configure keys, and the error/trace-event catalogues — are not vars and carry no Tier column. They are part of the contract of whichever artefact owns them; their availability follows that artefact's tier (e.g. the :rf.route/* events ship with the advanced routing artefact). The front-porch one-page list is the union of front-porch-tiered var rows.
Tiering of cross-tool surfaces (Story, Xray, pair-MCP)¶
Story, Xray, and the MCP support namespaces ship their own public-var rows in their own specs (007-Stories.md, tools/xray/spec, Tool-Pair.md); this projection rows only the slices that surface through re-frame.core or the per-feature artefacts. The Tier taxonomy is nonetheless repo-wide and authoritative over those surfaces — the per-tool specs classify against this closed vocabulary rather than inventing local terms. The standing classifications (resolved here, so the per-tool specs reference rather than re-litigate):
- Story facade — the public execution verbs
run/is/explainand the registration macros (reg-story/reg-variant/reg-workspace/reg-tag/reg-decorator/reg-story-panel) are tooling (a Storybook-shaped dev surface, not application logic). The run-result read accessors —run-result,result-status,result-passed?,run-result-schema,valid-run-result?,explain-run-result— are tooling too: the statement-of-record for reading what arunproduced (per story spec 017 §Run result). Therun-variant/is-variant/run-plan/is-plan/watch-variant/reset-variantimplementation/migration vocabulary isimplementation— public-for-technical-reasons only (the chrome and tests reach it across namespaces), explicitly NOT the supported execution surface. Theimplementationtier is its home — public as a var, off the supported surface. (Absorbs story F-8.) - Xray
mount-<panel>!family (mount-epoch-panel!,mount-app-db-diff!,mount-trace!,mount-machine-inspector!,mount-routing!,mount-segment-inspector!, the mastermount-shell!, …) — internal-public, the canonical (and now sole) instance of that tier. They are exported and stable, but they are a host-embed surface, not an application or even general-tool surface: a host that builds its own Xray chrome may call amount-<panel>!; an app never should. This resolves the prior "public vs internal-but-stable" question against the closed vocabulary — the answer isinternal-public. (Resolves xray M2.) - Xray panel-leaf
Panelreg-views (day8.re-frame2-xray.panels.<area>/Panel, the Static-modepanel) —implementation. They are exported only so the shell can compose them across namespaces; they are NOT a host-facing single-panel embed surface — a host embeds the full shell viamount-shell!(per 008-Embedding-Contract), never a barePanel. Demoted offinternal-public(now reserved for the supported mount-embed surface) so the leaves do not read as a supported embed API. - Xray panel-helper functions (the per-panel render/projection helpers beneath the mount-fns) — tooling where they are a documented panel-author surface, otherwise unrowed-internal. (Absorbs xray H6.)
- pair-MCP support namespaces — the trace/egress surfaces they consume (
elide-wire-value,sensitive?, the registrar query API, the epoch query surface) are tooling, tiered at theirre-frame.core/ artefact home rows below.
Not-rowed internal carve-outs¶
The only internal (unrowed, MUST-NOT-depend-on) carve-outs in re-frame.core are two JVM-only macro-helpers re-exposed purely so pre-split tests can reach them:
re-frame.core/expand-reg-view—^:no-doc; the canonical home isre-frame.core-reg-view-macro/expand-reg-view.re-frame.core/parse-reg-view-args—^:no-doc; the canonical home isre-frame.core-reg-view-macro/parse-reg-view-args.
Neither is rowed in this projection. Applications and tools MUST NOT depend on these re-exports; reach the canonical homes directly. These are not a tier — they are below the public surface entirely.
Registration¶
Return value. Every
reg-*row below returns its primary id — the keyword (or path, forreg-app-schema) the caller registered with (its first positional argument). Per Conventions §reg-*return-value convention.
| API | M/Fn | Signature | Status | Tier | Spec | Notes |
|---|---|---|---|---|---|---|
reg-event |
M | (reg-event id ?metadata handler) |
EP-0018 | front-porch | 002 | The ONE public event form — a two-arg (fn [coeffects event-vec] effect-map) handler, coeffects in, a closed #{:db :rf.db/runtime :fx} effects map out (or nil no-op). The db write is an explicit {:db …} effect; there is no db-only return shape. Coeffects declared uniformly via :rf.cofx/requires. Full-context work is expressed with a registered interceptor (reg-interceptor, referenced by id). Metadata-map superset middle slot carries the reserved :interceptors key (a vector of interceptor refs). There is no reg-event-db/reg-event-fx, and reg-event-ctx is a framework-internal primitive — calling any of the retired names is a hard error (see §The retired event-registration names + the Removed §). |
reg-sub |
M | (reg-sub id ?metadata input-fn? computation-fn) |
v1 (preserved + extended) | front-porch | 002 | The only sub-registration form in v2. Three input-production modes (app-db reader / static :<- / parametric input-fn) — see §reg-sub input-production modes. The optional first fn is a v2 input-fn (query-v → vector-of-query-vectors), NOT a v1 reaction-returning signal fn. :<- sugar preserved. |
reg-fx |
M | (reg-fx id ?metadata handler) |
v1 (preserved + extended) | front-porch | 002 | The handler is binary, context-first: (fn [ctx args]) per 002 §The binary fx-handler signature. ctx is a small map carrying :frame (the active frame id), :event (the originating event vector), and a runtime-internal :envelope; args is the value the event handler placed beside the fx-id in its :fx vector. A unary (fn [args]) handler survives on CLJS only via JS argument-dropping — it is not the blessed contract. :platforms metadata (a set of :server / :client) gates execution by active platform (default universal). |
reg-cofx |
M | (reg-cofx id ?metadata supplier) |
v1 (changed, EP-0017) | front-porch | 001, 002 | Register a coeffect id with a value-returning supplier ((fn [] v) / (fn [arg] v)) and a registration grade — ambient (default) or recordable (:recordable? true, optionally :provided? true). A handler takes delivery by declaring :rf.cofx/requires (the value arrives FLAT under the id; 001 §:rf.cofx/requires). :rf/time-ms is the framework's one provided recordable registration. The ctx→ctx handler shape and inject-cofx are retired (no alias). See §Coeffects. |
reg-interceptor |
M | (reg-interceptor id ?metadata descriptor) |
EP-0022 | front-porch | 001, 002 | The public application-authoring form for an interceptor — a first-class registered program member (registrar kind :interceptor). descriptor is one of {:before f} / {:after f} / {:before f :after f} (static) or {:factory f} (a parameterized family; the factory takes ONE arg and is the mechanism the standard [:rf.interceptor/path …] rides). Event/frame :interceptors chains reference registered interceptors by id (bare keyword) or [id arg], never inline values. Captures source coords; surfaces via handler-meta :interceptor. A migration value carrying an :id is accepted at this boundary only (the id must match). Replaces ->interceptor as the public authoring surface. See 001 §Interceptors + 002 §Registered interceptors and the chain grammar. |
make-frame |
Fn | (make-frame opts) / (make-frame opts descriptors) → frame value |
EP-0024 | advanced | 002 | The ONE frame constructor: builds a live frame and returns the frame value (the lifecycle token — the routing ops dispatch / subscribe / frame-provider accept the value directly OR its id, normalizing a value to its id; destroy-frame! accepts either too but is the one lifecycle exception — the value is an exact-incarnation token that tears down only the incarnation it names (a stale value no-ops against a same-id successor), while the id is address-directed, per §Destroy. API-shrink #1, rf2-csbbwu removed the frame-value->id accessor; there is no need to unwrap the value). opts is a map (required — a non-map opts, including nil, fails loud with :rf.error/make-frame-bad-opts; the all-defaults frame is (make-frame {})). Accepts BOTH image-selection opts AND record-config opts in one call: :images (a non-empty vector; present ⇒ the selected generation; [] is an error — :rf.error/make-frame-bad-images; absent ⇒ the default image generation over the whole source store + framework standards, failing loud on a cross-namespace same-[kind id] collision — :rf.error/image-duplicate-id; app isolation is named via :select-ns, not absence) / :id (registers in the one frame registry; a duplicate id is idempotent replacement — config + generation refresh, durable state preserved) / :adapter, plus any record-config key (:initial-events — seed app-db via a leading [:rf/set-db {…}] step — :fx-overrides, :platform, :ssr, :doc, :preset, :tags, …) honoured in the same call. (There is no :capabilities image-selection key — image-declared host capabilities are removed; :capabilities flows through as ordinary record-config.) Re-calling make-frame against the SAME :id with a NEW :images vector IS image hot-reload — the generation swaps while durable state is preserved (rf2-lxwpob folded the dedicated reload-images! verb into this; read the diff via generation-diff). make-frame is the ONE programmatic constructor (tools / tests / SSR / dynamic / image-loaded frames — rf2-h1vqa4 deleted the reg-frame spelling, no alias); the day-1 mount recipe is frame-root (ENSURE). (re-frame.frame/make-anon-frame-record! is the internal no-:id record helper.) |
image |
M | (image spec) → normalized, INERT image value — spec carries :id (optional), :select-ns ({:include [globs] :exclude [globs]} provenance selection), :registrations (inline registrar-keyed sections). Pure data — no registrar, no side effect; supplied to make-frame via :images (later image wins; shadows reported on :rf.gen/shadows) |
EP-0023 | advanced | 002 | The one public image constructor. |
generation-diff |
Fn | (generation-diff before after) → {:added #{[kind id] …} :changed #{…} :removed #{…} :retained #{…}} — a PURE diff between two sealed image generations (read via frame-generation before/after a re-make-frame reload). |
EP-0023 | tooling | 002 | Replaces the removed reload-images! verb's report — a read over two generation values, not a bespoke verb (rf2-lxwpob). |
reg-view |
M | (reg-view sym [args] body+) / (reg-view sym docstring [args] body+) / (reg-view ^{:rf/id :explicit/id} sym [args] body+) |
v1 | front-porch | 001, 002 | Defn-shape; auto-defs the symbol; auto-derives id from (keyword *ns* sym); auto-injects dispatch / subscribe as lexical bindings; rejects non-defn-shape bodies at macroexpand. |
reg-view* |
Fn | (reg-view* id render-fn) / (reg-view* id metadata render-fn) |
v1 | advanced | 001, 002 | Plain-fn surface beneath reg-view. No auto-def, no auto-inject, no compile check. Use for computed ids, library-generated views, Reagent Form-3 (create-class), or registration without a Var. The * follows Clojure's let/let*, fn/fn* idiom (per Conventions). |
reg-machine |
M | (reg-machine machine-id machine-spec) / (reg-machine machine-id opts machine-spec) |
v1 | advanced | 005 | Optional re-frame.machines artefact. Walks the literal spec form at expansion time; co-locates per-element source on each :guards / :actions entry + a reference-site :source-coords on each :states-tree map node. Top-level call-site coords land on handler-meta. The optional opts metadata map is the canonical Spec 001 MIDDLE slot; it carries an event-vector :schema (the :where :event boundary on the dispatched outer vector) — the machine + event-vector-schema shape. |
defmachine |
M | (defmachine name spec) / (defmachine name docstring spec) |
v1 | advanced | 005 | def-shape for the def-then-register pattern. Walks the literal spec at the definition site, stamping per-element source onto the def'd value so it travels into a later (reg-machine id name). Does not register. |
reg-app-schema |
M | (reg-app-schema path schema) / (reg-app-schema path metadata schema) — the schema is the positional value slot; the optional middle metadata map carries the :frame target (e.g. (reg-app-schema [:user] {:frame :session} UserSchema)) (rf2-qm7k83 Part A) |
v1 | advanced | 010 | Optional re-frame.schemas artefact. Path is the registration id. App-db schemas are path-keyed and live in the schemas artefact's per-frame side-table (app-db schemas are NOT a registrar kind). Every other reg-* is keyword-id-keyed; here the first arg is the path vector (e.g. [:user]) and (app-schema-at [:user]) / (app-schema-meta-at [:user]) look up by the same vector. The path-as-id asymmetry is principled (paths are first-class in get-in / assoc-in grain — schemas-at-paths matches the dataflow grain), not accidental; otherwise reg-app-schema is an ordinary family member — the schema is the positional value slot uniform with the rest of the reg-* family. A non-map middle metadata arg (the 3-slot form) is a loud :rf.error/app-schema-bad-metadata. Per Conventions §reg-* return-value rule. |
reg-app-schemas |
M | (reg-app-schemas {path-1 schema-1, path-2 schema-2, ...}) / (reg-app-schemas {…} opts) — bulk plural form for feature-modular apps that register 5–20 paths against the same prefix (per Conventions §Feature-modularity prefix convention). The bulk {path -> schema} shape is retained (the map value IS the schema — no positional ambiguity in a bulk map); the plural form is KEPT for its all-or-nothing atomic-batch contract (every path validated before any mutation — a naive doseq does not preserve it). Each entry routes through the singular reg-app-schema positionally and is stamped with this call's source-coords. Returns the vector of paths registered |
v1 | advanced | 010 | |
reg-flow |
Fn | (reg-flow flow-id metadata derive-fn) |
v1 | advanced | 013 | Optional flows artefact. Per the canonical Spec 001 3-slot grammar (rf2-bqstzr): the pure :derive fn is the third VALUE slot; metadata carries :inputs / :output-path (both REQUIRED) plus optional :doc / :schema / the EP-0025 classification keys and the :frame mounting key. A :derive left inside the metadata map is rejected loudly (:rf.error/invalid-flow-metadata). Returns flow-id (per Conventions §reg-* return-value convention). Flows are frame-scoped and single-store: the :flow registrar kind is RESERVED-but-empty; introspection is via the frame-scoped re-frame.flows/flow-meta-at / flows-snapshot (not handler-meta :flow). Per 013-Flows.md §The registration shape / §Frame-scoping. |
reg-route |
M | (reg-route id metadata path) — canonical 3-slot grammar: the URL :path pattern is the third VALUE slot, metadata the pure reflection map |
v1 | advanced | 012 | Optional routing artefact. A :path left inside the metadata map is a loud :rf.error/route-bad-metadata. |
reg-head |
M | (reg-head id ?metadata head-fn) |
v1 | advanced | 011 | Optional SSR artefact. New registry kind :head; routes name a registered head via :head route metadata. Captures source-coords; under the optional-artefact wrapper convention the surface routes through the :ssr/reg-head late-bind hook. |
reg-error-projector |
M | (reg-error-projector id ?metadata projector-fn) |
v1 | advanced | 011 | Optional SSR artefact. New registry kind :error-projector; named per-frame via the frame's :ssr {:public-error-id ...} config (per make-frame / frame-root). |
reg-sub input-production modes¶
reg-sub supports three input-production modes. Every subscription has an input query-vector producer: layer-1 has no producer; :<- is the literal producer; input-fn is the query-parametric producer.
| Mode | Form | Meaning |
|---|---|---|
| App-db reader | (reg-sub id computation-fn) |
No upstream subscriptions. The computation fn receives app-db and the outer query-v. |
| Static inputs | (reg-sub id :<- q1 :<- q2 computation-fn) |
Inputs are literal query vectors known at registration. |
| Parametric inputs | (reg-sub id input-fn computation-fn) |
Inputs are computed from the outer query-v when a concrete cache entry is materialized. |
The two-function form's first fn is a v2 input-fn — a pure function from the outer query-v to a vector of input query vectors. It is not a v1 signal function: it must not call subscribe, deref app-db, dispatch, mutate, or perform IO; it receives only the outer query-v; and it must not return live reactions. The computation-fn receives the vector of resolved input values (in the same order) and the outer query-v.
(rf/reg-sub
:article/page
(fn input-fn [[_ article-id]]
[[:article/by-id article-id]
[:comments/for-article article-id]
[:viewer/current]])
(fn computation-fn [[article comments viewer] [_ article-id]]
{:id article-id :article article :comments comments
:can-edit? (:edit? viewer)}))
Input grammar. An input-fn MUST return a vector, and every element of that vector MUST be a query vector (a vector whose first element is a keyword):
;; Accepted
[[:article/by-id id] [:viewer/current]] ;; multiple inputs
[[:item/by-id id]] ;; single input — still a vector OF query vectors
[] ;; no inputs (unusual but valid)
;; Rejected — see :rf.error/sub-input-fn-bad-return
:viewer/current ;; bare keyword
[:article/by-id id] ;; scalar query vector (ambiguous: arg vs two inputs)
[[:article/by-id id] :viewer] ;; mixed vector + bare keyword
{:article [:article/by-id id]} ;; map return
The scalar query-vector rejection is deliberate: [:x :y] is ambiguous at this boundary (one query with argument :y, vs two inputs). The only accepted single-query spelling is [[:x :y]]. No bare keyword shorthand, no map return, no reaction/derefable. Use :<- for static inputs; reach for input-fn only when the upstream query vectors need values from the outer query-v. The static :<- form is exactly a constant input-fn ((fn [_] [[:items] [:filter]])). Per 006 §Subscription input producers, 008 §compute-sub algorithm, and Conventions §reg-sub input grammar. Registration-shape and input-return errors signal loudly via :rf.error/reg-sub-bad-args, :rf.error/sub-input-fn-exception, and :rf.error/sub-input-fn-bad-return (catalogued in 009 §Error event catalogue).
Clearing registrations¶
| API | M/Fn | Signature | Status | Tier |
|---|---|---|---|---|
clear-event |
Fn | (clear-event) / (clear-event id) |
v1 (preserved) | advanced |
clear-sub |
Fn | (clear-sub) / (clear-sub id) |
v1 (preserved) | advanced |
clear-fx |
Fn | (clear-fx) / (clear-fx id) |
v1 (preserved) | advanced |
destroy-frame! |
Fn | (destroy-frame! frame-id) — the normative teardown boundary. A frame value target carries exact-incarnation authority (tears down only the incarnation it names — a stale value no-ops against a same-id successor), while a frame-id keyword is address-directed (tears down whatever incarnation is currently live), per 002 §Destroy (rf2-moftbs). Per-feature artefacts (flows, machines, schemas, SSR, epoch) hang their frame-scoped cleanup off this call; flows release per 013 §Frame-destroy teardown. |
v1 | front-porch |
clear-sub-cache! |
Fn | (clear-sub-cache! frame-id?) |
v1 (preserved) | advanced |
Dispatch and subscribe¶
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
dispatch |
M/Fn (CLJS) | (dispatch event) / (dispatch event opts) |
v1 (preserved + extended); macro captures call-site for :rf.trace/call-site; on CLJS the same name is ALSO a plain-fn value-alias (Convention A, rf2-m90brg) for HoF / programmatic dispatch with no call-site capture |
front-porch | 002 |
dispatch-sync |
M/Fn (CLJS) | (dispatch-sync event) / (dispatch-sync event opts) |
v1 (preserved + extended); macro captures call-site for :rf.trace/call-site; on CLJS the same name is ALSO a plain-fn value-alias (Convention A, rf2-m90brg) for HoF / programmatic sync dispatch with no call-site capture |
front-porch | 002 |
subscribe |
M/Fn (CLJS) | (subscribe query-v) / (subscribe query-v opts) |
v1 (preserved + extended); opts may carry :frame (mirrors dispatch); macro captures call-site for :rf.trace/call-site; on CLJS the same name is ALSO a plain-fn value-alias (Convention A, rf2-m90brg) for HoF / programmatic subscribe with no call-site capture |
front-porch | 002 |
subscribe-once |
Fn | (subscribe-once query-v) / (subscribe-once query-v opts) → value (subscribe + deref + immediate unsubscribe; one-shot, non-reactive read for handler bodies, REPL — never inside a machine callback: an in-callback ambient read is unrecorded and breaks 005's replay contract; a machine reads facts via payload or a declared recordable cofx) |
v1 (preserved + extended); opts may carry :frame (mirrors subscribe, rf2-bfadc6) — closes the misbinding footgun for an author carrying the opts form over from subscribe |
advanced | 006 |
unsubscribe |
Fn | (unsubscribe query-v) / (unsubscribe frame-id query-v) → nil (decrement the cache ref-count; on the 1 → 0 transition the cache slot is disposed synchronously in-tick — no grace-period timer, per 006 §Reference counting and disposal). Carved out from the Conventions §Tear-down verb axis — clear-sub is already taken by the symmetric inverse of reg-sub (the registrar decrement), so un- is reserved as the singular form for the sub-cache ref-count decrement. |
v1 | advanced | 006 |
To read a machine's snapshot, subscribe to the canonical [:rf/machine machine-id] vector (see §Standard registered subs (machines)).
opts map keys: :frame, :fx-overrides, :interceptor-overrides, :trace-id, :source. Envelope shape and semantics: see 002 §Routing: the dispatch envelope.
Canonical event / query-v shape (best practice). [<id>] (trivial), [<id> <single-scalar>] (single-arg), [<id> {<k> <v>}] (multi-arg → single map payload). Variadic [<id> a b c] is tolerated by the runtime for v1-migration and caller convenience; the linter nudges new code toward the map form. Full rationale and cross-refs: Conventions §Canonical event-vector shape.
dispatch-* family taxonomy¶
Per audit-of-audits state-machines #10, the dispatch-* family has two sub-shapes that look alike on first read but answer different questions. Both are dispatch operations — the family-prefix is honest — but they sit in different sub-families.
Stamping-pair sub-family (dispatch / dispatch-sync / subscribe).
The pair-shape question is "do you want call-site stamping or not?" In call position the macro form captures :rf.trace/call-site from the surrounding source position so tooling can navigate from a trace event back to the originating expression. On CLJS the same name is ALSO a plain-fn value-alias (Convention A, rf2-m90brg — mirrors reg-event / reg-sub / etc.) for HoF composition ((map dispatch events)) where a macro can't sit inside the higher-order call — no call-site capture on that path. There is no *-suffixed twin (rf2-m90brg retired dispatch* / dispatch-sync* / subscribe* from the facade); a JVM programmatic caller reaches the owning ns fn directly (re-frame.router/dispatch! / -dispatch-sync!, re-frame.subs/subscribe). Both the macro path and the value-alias route through the same underlying dispatcher; only the trace stamping differs.
Named-target sub-family (the reserved [:rf.machine/dispatch-to-system [system-id event]] fx tuple, per 005 §Cross-machine messaging by name).
This question is "do you have a :system-id instead of a target machine-id?" Named addressing resolves the target through the per-frame [:rf.runtime/machines :system-ids] reverse index first, then dispatches just like the stamping pair. The only surface is the action-side fx tuple; the redundant dispatch-to-system direct-call FN twin was deleted (pre-alpha, zero in-repo consumers — a machine IS an event handler, so the everyday send is plain dispatch to the id you hold). The fx is a parked named-addressing escape (advanced/parity tier), retained for XState v6 actor-system parity (systemId addressing); the facade audit at API-freeze rules on its deletion. The naming reads as "dispatch with extra routing logic on top," not "different kind of dispatch."
View ergonomics¶
The multi-frame surface is organised by intent, not mechanism (a front-porch / back-room split — per 002 §The multi-frame surface):
- Single-frame (no frames in play):
dispatch,dispatch-sync,subscribe. - Scope:
with-frame,with-new-frame,frame-provider {:frame …}(SCOPE-only — scope an existing frame into a React subtree; fails loud if absent). (ENSURE is its sibling componentframe-root {:id …}— see below.) - Hold (carry a frame's ops as a value, across async):
capture-frame— the ONE public carry primitive (API-shrink #1, rf2-csbbwu removedframe-bound-fn/frame-bound-fn*from the facade entirely —capture-frameor an explicit{:frame …}opt expresses the real use cases). - Override: the
{:frame …}opt — first-class explicit routing for tools / tests / SSR / fx handlers. - Reads / lifecycle:
app-db-value,frame-state-value,current-frame-id,destroy-frame!,make-frame,frame-ids,frame-meta(see §Public registrar query API).
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
frame-provider |
Component (Reagent) | SCOPE-only (rf2-nyea0r split — roots ensure; providers scope). [rf/frame-provider {:frame :todo} & children]: provides an ALREADY-CREATED frame id through React context; :frame accepts a frame-id keyword OR a live frame value (API-shrink #1, rf2-csbbwu); creates / refreshes / destroys nothing; FAILS LOUD when the frame is absent (:rf.error/frame-provider-frame-absent; a value/keyword that is neither raises :rf.error/bad-frame-provider-arg; a nil :frame → :rf.error/no-frame-context). Given an :id (the ENSURE key), FAILS LOUD naming frame-root (:rf.error/frame-provider-given-id). For create-if-absent, use frame-root. |
v1 | front-porch | 002 |
frame-root |
Component (Reagent) | ENSURE — a COMMIT-OWNED TWO-PASS boundary (rf2-nyea0r split). [rf/frame-root {:id :todo :images […] :initial-events [[:rf/set-db {}]]} & children]: creates the frame if absent (via make-frame) in a client useLayoutEffect (NOT during render — first render emits no descendant subtree; a Suspense-aborted render creates + seeds nothing, no ghost frame), REUSES it WITHOUT re-seeding if present (idempotent re-mount / keyed remount preserves durable state and does NOT replay :initial-events; StrictMode-safe), provides its id to descendants; NO destroy-on-unmount. :id required (a missing/non-keyword :id → :rf.error/frame-root-missing-id); a mounted :id/opts change → :rf.error/frame-root-reconfigured; a stray :frame → :rf.error/frame-root-given-frame. True ownership stays make-frame + destroy-frame! in a create-class. |
v1 | front-porch | 002 |
with-frame |
M | (with-frame :keyword body) — pin to an existing frame-id. Vector arg is a compile-time error (use with-new-frame) |
v1 | front-porch | 002 |
with-new-frame |
M | (with-new-frame [sym expr] body) — eval expr, bind sym, run body, destroy frame on exit. Keyword arg is a compile-time error (use with-frame) |
v1 | front-porch | 002 |
capture-frame |
Fn | (capture-frame) or (capture-frame frame-id) → {:frame :dispatch :dispatch-sync :subscribe} — the keystone OPERATION BUNDLE. Captures the frame at CREATION; its ops always target the captured frame and survive async. Read app-db via (app-db-value (:frame h)), not the handle |
v1 | front-porch | 002 |
view |
Fn | (view view-id) → render-fn (runtime-lookup handle; returns the registered render-fn, not hiccup). Use in hiccup as [(rf/view :id) args...] — the lookup form for late-binding a registered view by id. |
v1 | advanced | 001 |
with-frame (pin) and with-new-frame (eval-bind-run-destroy) are documented in 002 §with-frame and with-new-frame. The macros are non-overlapping: each rejects the other's argument shape at compile time, with :recovery pointing the caller at the right sibling.
capture-frame is the keystone affordance — it replaces the removed dispatcher / subscriber nouns and is the single answer to "carry a frame's dispatch/subscribe ops across an async boundary." The handle is locked: a per-call :frame opt MUST NOT override the frame captured at handle creation — the captured frame always wins (per 002 §capture-frame). frame-bound-fn / frame-bound-fn* are REMOVED from the facade (API-shrink #1, rf2-csbbwu) — capture-frame (or an explicit {:frame …} opt) expresses the real use cases and is the ONE public carry primitive; the frame-rebinding closure semantics survive internally as re-frame.frame/bind-fn for the framework's own reach.
Reagent adapter (Spec 006)¶
Reagent-specific surfaces live in re-frame.adapter.reagent (artefact day8/re-frame2-reagent, the browser default). Reagent is the default substrate; its frame-provider Component is rowed in §View ergonomics and the reg-view macro in §Registration, not here. Apps targeting Reagent :require [re-frame.adapter.reagent :as reagent-adapter] and pass reagent-adapter/adapter to (rf/init! …).
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
reagent-adapter/adapter |
Var (map) | the 11-key adapter spec map {:kind :make-state-container :read-container :replace-container! :subscribe-container :make-derived-value :render :render-to-string :register-context-provider :flush-render! :dispose-adapter!} (ratom-backed; per Spec 006 §Adapter contract) |
v1 | adapter | 006 |
reagent-adapter/flush-views! |
Fn | (flush-views!) / (flush-views! f) — flush pending Reagent renders synchronously (wraps React's act() for tests); the canonical cross-substrate test-flush hook |
v1 | adapter | 006, 008 |
reagent-adapter/set-hiccup-emitter! |
Fn | (set-hiccup-emitter! f) — install the render-tree → HTML fn for render-to-string (the SSR late-bind seam, published through the :reagent/set-hiccup-emitter! hook) |
v1 | adapter | 006, 011 |
UIx adapter (Spec 006)¶
UIx-specific surfaces live in re-frame.adapter.uix (artefact day8/re-frame2-uix) — they are NOT re-exported from re-frame.core because core has no static dependency on the adapter (the dependency direction is adapter → core per Conventions §Adapter shipping convention). Apps targeting UIx :require [re-frame.adapter.uix :as uix-adapter] and call the surfaces directly.
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
uix-adapter/adapter |
Var (map) | the 11-key adapter spec map {:kind :make-state-container :read-container :replace-container! :subscribe-container :make-derived-value :render :render-to-string :register-context-provider :flush-render! :dispose-adapter!} (6 required + 3 optional + :dispose-adapter! + the :kind discriminator, per Spec 006 §Adapter contract) |
v1 | adapter | 006 |
uix-adapter/use-subscribe |
Fn (UIx hook) | (use-subscribe query-v) / (use-subscribe frame-kw query-v) → current sub value |
v1 | adapter | 006 |
uix-adapter/use-frame |
Fn (UIx hook) | (use-frame) → the frame api for the ambient provider frame — EXACTLY what (rf/capture-frame) returns ({:frame :dispatch :dispatch-sync :subscribe}), capture-frame in hook position; resolves through the same carried-invariant chain as the ambient use-subscribe (no scope → :rf.error/no-frame-context); reference-stable across re-renders for the same resolved frame incarnation — a same-id destroy-and-recreate retargets it, because the bundle is pinned to the incarnation capture-frame ran against and not to the address. No opts, no variants — explicit frames use (rf/capture-frame frame-id). |
v1 | adapter | 002, 006 |
uix-adapter/use-current-frame |
Fn (UIx hook) | (use-current-frame) → frame-kw, or the :rf.frame/no-provider sentinel when neither frame-provider nor frame-root sits above (the narrow raw useContext read of the one shared context both boundaries install — explicitly not :rf/default; use (rf/current-frame-id) for the full dynamic-var → context → nil resolution chain) |
v1 | adapter | 006 |
uix-adapter/frame-provider |
Fn (UIx component) | SCOPE-only (rf2-nyea0r split — roots ensure; providers scope) — ($ uix-adapter/frame-provider {:frame :session} child-1 child-2) provides an existing frame id; creates nothing; fails loud if absent (:rf.error/frame-provider-frame-absent); given an :id (the ENSURE key) fails loud naming frame-root (:rf.error/frame-provider-given-id); idiomatic $ trailing children |
v1 | adapter | 002, 006 |
uix-adapter/frame-root |
Fn (UIx component) | ENSURE — a commit-owned two-pass boundary (rf2-nyea0r split) — ($ uix-adapter/frame-root {:id :session :images […]} child-1 child-2) creates the frame if absent in a client useLayoutEffect (not during render — a discarded render creates nothing), reuses it without re-seeding if present, provides its id to descendants; no destroy-on-unmount; takes make-frame opts; a missing/non-keyword :id → :rf.error/frame-root-missing-id, a stray :frame → :rf.error/frame-root-given-frame; idiomatic $ trailing children |
v1 | adapter | 002, 006 |
uix-adapter/wrap-view |
Fn | (wrap-view id metadata user-fn) → wrapped fn (source-coord injection per Spec 006 §Source-coord annotation) |
v1 | adapter | 006 |
uix-adapter/flush-views! |
Fn | (flush-views!) / (flush-views! f) — wraps React's act() for tests |
v1 | adapter | 006, 008 |
uix-adapter/set-hiccup-emitter! |
Fn | (set-hiccup-emitter! f) — install render-tree → HTML fn (parity with the Reagent adapter's late-bind seam) |
v1 | adapter | 006, 011 |
Per Decision 1 the hook is named use-subscribe (matching the React/UIx idiom). Per Decision 3 there is no auto-injection — UIx components read via the hook and hold frame ops via use-frame (capture-frame in hook position: (let [{:keys [dispatch]} (use-frame)] …)); capture-frame is THE hold primitive, and reg-view injection / use-frame are its two ergonomic spellings. Per Decision 4 reg-view (the Reagent macro) does NOT cover UIx; UIx users register with rf/reg-view* if they need registry-keyed view addressing.
The shared React Context that backs frame-provider / frame-root lives in re-frame.adapter.context (CLJS-only file in core, factored out per Decision 2) — the Reagent and UIx adapters both consume the same createContext object so a mixed-substrate app's frame-provider chain composes across substrates.
Routing (Spec 012)¶
reg-route is rowed canonically in §Registration.
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
match-url |
Fn | (match-url url) → {:route-id :params :query :fragment :validation-failed? ?:validation-error} or nil (:validation-error present only on a validation failure) |
v1 | advanced | 012 |
route-url |
Fn | (route-url {:to route-id :params path-params :query query-params :fragment fragment}) → URL string (single address-map arity; strictly address-only — :url / :query-merge / policy / unknown keys reject loud) |
v1 | advanced | 012 |
route-link |
Fn (registered view at :route/link) |
[rf/route-link {:to :route-id :params {...} :query {...} :fragment "..." :prefetch :intent & html-attrs} & children] |
v1 | advanced | 012 |
history-url-strategy |
Var (map) | The DEFAULT :url-strategy — HTML5 History, path-form. re-frame.routing/history-url-strategy, NOT on the re-frame.core façade (routing bundle isolation) |
v1 | advanced | 012 |
hash-url-strategy |
Var (map) | #-prefixed :url-strategy for no-server-rewrite static hosting / secretary-era v1 migrations. re-frame.routing/hash-url-strategy |
v1 | advanced | 012 |
with-base-path |
Fn | (with-base-path strategy base) — STRATEGY COMBINATOR (rf2-g8pbwg): wraps strategy (either shipped strategy, or a custom one) so a deployment sub-path (e.g. an app served from /realworld/) is stripped off every inbound URL and re-added to every outbound one, at all four egress/ingress consult points. A blank/nil base returns strategy unchanged. re-frame.routing/with-base-path |
v1 | advanced | 012 |
A :url-bound? true frame's browser URL-change listener is installed / removed by the FRAME LIFECYCLE, automatically — creation installs (strategy-aware: popstate for history, hashchange for hash, per :url-strategy), destroy removes (rf2-g8pbwg). There is no imperative install/remove pair to call; the retired install-url-listener! / remove-url-listener! / install-history-listener! / remove-history-listener! exports are GONE (pre-alpha, no back-compat shim).
reg-route's routing-owned metadata reserved keys: :doc, :params, :query, :query-defaults, :tags, :parent, :on-match, :can-leave, :can-enter, :scroll, :sensitive, :large. Two cross-feature bare keys are accepted beside them: :head, owned by SSR (011) and always accepted; and :resources, owned by the Resources artefact (016) and accepted only when that artefact publishes its :routing/extra-route-keys hook. The URL :path pattern is the third VALUE slot, not a metadata key. Canonical detail in 012-Routing.md §Reserved route-metadata keys; shape in Spec-Schemas §:rf/route-metadata.
route-link click rules: a plain primary-button click (no modifier keys, no defaultPrevented) calls .preventDefault and dispatches [:rf.route/url-requested {:url <synthesised> :to <route-id> :params {...} :query {...} :fragment "..."}]. Modifier-key clicks (cmd / ctrl / shift / alt) and auxiliary-button clicks (middle-click) defer to the browser so the native href opens in a new tab. A caller-supplied :on-click runs first; if it calls .preventDefault (or otherwise leaves defaultPrevented true) the framework's interception is skipped. Keys other than :to / :params / :query / :fragment / :prefetch / :on-click pass through to the underlying <a> element. Detailed semantics in 012-Routing.md §Linking from views.
Standard route-related events:
| Event | Notes | Spec |
|---|---|---|
:rf.route/navigate |
Navigate to a registered route. | 012 |
:rf.route/handle-url-change |
URL-change handler for popstate / initial load / SSR (default scroll :restore). Co-equal sibling of :rf.route/transitioned, not a delegate. |
012 |
:rf.route/transitioned |
URL-change handler for forward navigation (link click / programmatic push; default scroll :top). |
012 |
:rf.route/url-requested |
The user clicked a framework-owned link. | 012 |
:rf.route/navigation-blocked |
A :can-leave guard rejected a navigation. |
012 |
:rf.route/entry-denied |
A :can-enter guard rejected navigation into a route. TERMINAL — nothing commits and no pending value is created; dispatched exactly once per attempt, carrying {:destination :target :cause :requested-url :guard}. A framework no-op default handler ships, so denial is safe with no application handler. |
012 |
:rf.route/continue |
User-dispatched event proceeding a blocked navigation. | 012 |
:rf.route/cancel |
User-dispatched event abandoning a blocked navigation. | 012 |
Standard route-related subs:
| Sub | Returns | Spec |
|---|---|---|
:rf/route |
The full :rf/route slice {:route-id :params :query :fragment :transition :error :nav-token} |
012 |
:rf.route/id |
Current route id | 012 |
:rf.route/params |
Current path params | 012 |
:rf.route/query |
Current query params | 012 |
:rf.route/transition |
:idle / :loading / :error |
012 |
:rf.route/error |
Current error map (when :transition = :error) |
012 |
:rf.route/fragment |
Current URL fragment (string or nil) | 012 |
:rf.route/chain |
Vector of route ids from parent-most to current (per :parent links) |
012 |
:rf/pending-navigation |
The pending-nav slot (per :rf/pending-navigation schema) when a navigation is blocked; nil otherwise |
012 |
The route and the pending-nav slot are read as ordinary subscription vectors — @(rf/subscribe [:rf/route]) and @(rf/subscribe [:rf/pending-navigation]); the :rf.route/* granular subs above chain off [:rf/route]. There is no named-read-sugar fn: a runtime-db framework read is a subscription vector, one grammar (per Conventions §Reserved sub-ids).
Standard route-related fx (canonical detail in 012-Routing.md):
| Fx | Args | Platforms |
|---|---|---|
:rf.nav/push-url |
URL string | :client |
:rf.nav/replace-url |
URL string | :client |
:rf.nav/scroll |
scroll-spec map | :client |
:rf.nav/capture-scroll |
{:url <leaving-route-url>} |
:client |
:rf.route/with-nav-token |
{:rf/reply-to <reply-target> :value <v> :nav-token <token> :route-id <route-id>} (per 012 §Threading the nav-token) |
universal |
Standard route-related cofx (canonical detail in 012-Routing.md):
| Cofx | Delivers | Spec |
|---|---|---|
:rf.route/nav-token |
The active navigation epoch token (read from [:rf.runtime/routing :current :nav-token]), delivered flat under the coeffect key :rf.route/nav-token — declare via {:rf.cofx/requires [:rf.route/nav-token]} in an :on-match-reached handler to capture the epoch live at scheduling time for stale-result suppression. Per 012 §Navigation tokens. |
012 |
SSR (Spec 011)¶
Namespace: the surfaces below live in
re-frame.ssr(artefactday8/re-frame2-ssr); consumers(:require [re-frame.ssr :as ssr]). The Ring host-adapter lives inre-frame.ssr.ring(artefactday8/re-frame2-ssr-ring). The SSR query surface (render-to-string,render-tree-hash,project-error,render-head,active-head,head-model->html,head-snapshot) is also late-bind re-exported throughre-frame.core— one of the two documented façade exceptions (the other is epoch), per the §Conventions per-artefact namespace table. The streaming surface (streaming-render-*) and the Ring host-adapter (re-frame.ssr.ring) are NOT re-exported — they are host-adapter territory; an SSR-aware host requires the namespace directly. Apps targeting SSR add the artefacts to their deps regardless.
reg-head and reg-error-projector are rowed canonically in §Registration. The head-fn signature is (fn [db route] head-model); the projector-fn signature is (fn [trace-event] :rf/public-error).
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
render-to-string |
Fn | (render-to-string view-or-hiccup opts) → HTML string |
v1 | advanced | 011 |
emit-ui-tree |
Fn | (emit-ui-tree tree) / (emit-ui-tree tree {:doctype? bool}) → HTML string. Folds an already-rendered version-1 structural tree to HTML — pure, deterministic to the byte, JVM-runnable; it calls no view and resolves no subscription. Validates the root :rf.ui/tree-version first, before any emission: a missing / non-integer / unsupported version throws :rf.error/ssr-ui-tree-version-unsupported ({:got … :supported #{1}}); a malformed node past the gate throws the shared :rf.error/ui-tree-malformed. :doctype? is the only current option — it prefixes <!DOCTYPE html>. Per 004B §The SSR consumption boundary. |
v1 | advanced | 004B |
render-tree-hash |
Fn | (render-tree-hash render-tree) → 32-bit FNV-1a structural hash (lowercase hex). Identical output on JVM and CLJS for the same canonical-EDN representation. Per 011 §Hydration-mismatch detection. |
v1 | advanced | 011 |
project-error |
Fn | (project-error frame-id trace-event) → :rf/public-error. Applies the active error-projector (selected by the frame's :ssr {:public-error-id ...} metadata) for the named frame. Per 011 §Server error projection. |
v1 | advanced | 011 |
render-head |
Fn | (render-head head-id opts) → :rf/head-model |
v1 | advanced | 011 |
active-head |
Fn | (active-head frame-id) → :rf/head-model. Head rendering is a frame-scoped read, so the frame is carried, not ambient: the no-arg form was removed (EP-0002) and a nil frame-id raises :rf.error/no-frame-context rather than resolving against a synthesised default frame. |
v1 | advanced | 011 |
head-model->html |
Fn | (head-model->html head-model) / (head-model->html head-model {:wrap? bool}) → inner-head HTML string |
v1 | advanced | 011 |
head-snapshot |
Fn | (head-snapshot frame-id) → {head-id → :rf/head-model}. Read the per-frame snapshot of last-produced head-models. Returns {} for a frame that has never seen a render-head call (or whose snapshot has been cleared via per-request frame teardown). Useful for tests, introspection, and tools. Re-exported as rf/head-snapshot. Per 011 §Head/meta contract. |
v1 | advanced | 011 |
streaming-render-shell |
Fn | (streaming-render-shell root-hiccup) → {:shell-html "…" :continuations [{:id <id> :subtree <hiccup>} …]}. Walks the tree once; at each :rf/suspense-boundary emits a <template …suspense-fallback> placeholder + records a continuation. Per 011 §Streaming SSR — (a). |
v1 | advanced | 011 |
streaming-render-continuation |
Fn | (streaming-render-continuation frame-id entry) → {:id … :html "…" :delta {…} :failed? bool :continuations [{:id <id> :subtree <hiccup>} …]} (:continuations are nested boundaries discovered while draining — [] in the common case). Drains one continuation against frame-id's app-db; snapshots before-db / after-db and computes the per-subtree delta. Catches throws and surfaces the original fallback HTML inline (per 011 §Failure semantics — inline fallback). |
v1 | advanced | 011 |
streaming-build-final-payload |
Fn | (streaming-build-final-payload frame-id render-hash opts) → canonical :rf/hydration-payload. Called after all continuations drain to populate the __rf_payload final chunk. |
v1 | advanced | 011 |
Standard SSR-related events:
| Event | What it does | Spec |
|---|---|---|
:rf/server-init |
Per-request server-side initialisation. Reads request cofx; dispatches setup events. :platforms #{:server}. |
011 |
:rf/hydrate |
Seed the client-side app-db from the server-supplied payload. Runs once on client bootstrap. |
011 |
Standard SSR-related fx (server-only; :platforms #{:server}):
| Fx | Args | Spec |
|---|---|---|
:rf.server/set-status |
:int (per :rf.fx.server/set-status-args) |
011 |
:rf.server/set-header |
{:name :value} (per :rf.fx.server/set-header-args) |
011 |
:rf.server/append-header |
{:name :value} (per :rf.fx.server/append-header-args) |
011 |
:rf.server/set-cookie |
:rf.server/cookie map |
011 |
:rf.server/delete-cookie |
{:name ?:path ?:domain} |
011 |
:rf.server/redirect |
{:location ?:status} (default :status 302); truncates HTML. Caller-trusted :location |
011 |
:rf.server/safe-redirect |
{:location ?:relative-only? ?:allow ?:status} — the caller-untrusted variant; parses :location, rejects javascript: / data: / vbscript: schemes, and enforces :relative-only? / :allow allowlist before setting :redirect. Open-redirect mitigation for attacker-controlled ?next= strings |
011 |
Standard SSR-related fx (client-side hydration compatibility checks; :platforms #{:client}):
| Fx | What it does | Spec |
|---|---|---|
:rf.ssr/check-version |
Hydration-side framework version-compatibility check (emits :rf.ssr/version-mismatch on drift). |
011 |
:rf.ssr/check-schema-digest |
Hydration-side schema-digest compatibility check (emits :rf.ssr/schema-digest-mismatch on drift). |
011 |
Implementation-tier SSR internals (not rowed). The client hydration / streaming lifecycle —
hydrate!,verify-hydration!,read-server-payload,streaming-install!,drain-blocking-resources!, and the per-request response / request accessors (get-response/peek-response/flush-response!/get-request/set-request!/clear-request!) — live inre-frame.ssrbut are:implementation-tier host-adapter plumbing (the supported client path is the:rf/hydrateevent plus there-frame.ssr.ringhost adapter). Per the projection's implementation-tier policy (§Tier taxonomy) they are carried by the manifest but not rowed here — the per-request response accumulator (:rf/response) is read through these accessors, not a subscription.
Standard SSR-related subs:
| Sub | Returns | Spec |
|---|---|---|
:rf/head |
The head model for the active route (resolved via (active-head frame-id)) |
011 |
:rf/public-error |
The sanitised public-error projection when an error page is being rendered; nil otherwise |
011 |
Standard cofx (server-only):
| Cofx | Returns | Spec |
|---|---|---|
:rf.server/request |
The active HTTP request map | 011 |
reg-fx's :platforms metadata key (a set containing :server and/or :client) gates fx execution by active platform; default #{:server :client} (universal) when the key is absent. Skipped fx emit a :rf.fx/skipped-on-platform trace event. Detail in 011 §:platforms metadata on reg-fx.
SSR error-projection policy is per-frame metadata (see Conventions §Configuration surfaces bucket 3): a frame opts in via the :ssr {:public-error-id ... :dev-error-detail? ...} map on its make-frame / frame-root config. See 011 §Server error projection for the keys.
HTTP requests (Spec 014)¶
:rf.http/managed is the canonical, optional HTTP-request fx — v1 (optional capability). CLJS reference ships it on Fetch (browser) and java.net.http.HttpClient (JVM). Args, behaviours, decode pipeline, retry semantics, abort surface, failure taxonomy, and reply addressing are normatively defined in 014-HTTPRequests.md; the surface below is the API-level summary.
| API | Kind | Signature / shape | Status | Tier | Spec |
|---|---|---|---|---|---|
:rf.http/managed |
fx | [:rf.http/managed args-map] — args per 014 §The args map and :rf.fx/managed-args |
v1 (optional capability) | — (fx-id; follows the advanced HTTP artefact) |
014 |
:rf.http/managed-abort |
fx | [:rf.http/managed-abort request-id] — abort the in-flight request with the given :request-id |
v1 (optional capability) | — (fx-id) | 014 |
:rf.http/managed-canned-success |
fx | [:rf.http/managed-canned-success {:value v}] — synthesises the canonical success reply (per 014 §Testing). Registered at load of re-frame.http.test-support (NOT re-frame.http.managed); the stubbing macros (rows below) ship in the same namespace per (audit-of-audits #15). |
v1 (optional capability, dev/test) | — (fx-id; test) | 014 |
:rf.http/managed-canned-failure |
fx | [:rf.http/managed-canned-failure {:kind <:rf.http/*> :tags {...}}] — synthesises the canonical failure reply. Same registration gate (re-frame.http.test-support) and same co-location with the stubbing macros. |
v1 (optional capability, dev/test) | — (fx-id; test) | 014 |
with-managed-request-stubs |
M | (with-managed-request-stubs route-map body+) — route-map {[<method> <url>] {:reply ...}} per 014 §Testing. Lives in re-frame.http.test-support (the single home for HTTP test surfaces — audit-of-audits #15 closed the prior split that placed the macros in re-frame.http.managed); see Spec 008 §HTTP test surfaces. |
v1 (optional capability, dev/test) | testing | 014 |
with-managed-request-stubs* |
Fn | (with-managed-request-stubs* route-map body-fn) — plain-fn surface beneath with-managed-request-stubs; the * follows the Clojure let/let*, fn/fn* idiom (per Conventions). Ships in re-frame.http.test-support. Use for computed route-maps or non-literal bodies. Per 014 §Testing. |
v1 (optional capability, dev/test) | testing | 014 |
reg-http-interceptor |
M | (reg-http-interceptor id interceptor-map) — register an HTTP interceptor on a frame's :rf.http/managed middleware chain (per 014 §Middleware). A façade macro (re-frame.core, capturing call-site source-coords; the fn form is re-frame.http.middleware/reg-http-interceptor). id is a keyword; interceptor-map carries at least one of :before (fn [ctx] ctx') and :after (fn [ctx response] response'), plus optional :frame (the EP-0002 override; absent, the carried scope it registers under resolves it — registering under no scope raises :rf.error/no-frame-context, never :rf/default) and any :rf/registration-metadata (:doc / :tags / :schema / :sensitive?). The surface mirrors the event-interceptor {:id :before :after} shape — symmetric request/response sides; :before chain in registration order, :after chain in reverse. |
v1 (optional capability) | advanced | 014 |
clear-http-interceptor |
Fn | (clear-http-interceptor id) / (clear-http-interceptor id {:frame target}) — unregister an interceptor by id (per 014 §Middleware). The single-arity resolves the frame from the carried scope; under no scope it raises :rf.error/no-frame-context — no :rf/default fallback (EP-0002). The two-arity names the frame via an id-first {:frame target} opts map (the framework-wide (id {:frame …}) shape) and is exact and fail-closed: anything but exactly {:frame target} with a present, non-nil target — {}, {:frame nil}, a typo'd or extra key, a non-map second arg — raises :rf.error/http-bad-interceptor BEFORE any ambient frame is resolved or touched (rf2-s32bf). A valid explicit target need not name a currently live frame: clearing an absent frame's chain is an idempotent no-op, not an error. The frame-first (frame id) spelling is the artefact-internal clear-http-interceptor* seam (used by the :rf.fx/clear-http-interceptor fx), not a public arity. |
v1 (optional capability) | advanced | 014 |
:rf.fx/reg-http-interceptor |
fx | [:rf.fx/reg-http-interceptor {:id <id> :before … :after … ?:frame …}] — register an HTTP interceptor at runtime via :fx (the data-shaped counterpart of the reg-http-interceptor macro; the dispatching frame threads through when :frame is omitted). Registered by re-frame.http.managed; :platforms #{:client :server}. |
v1 (optional capability) | — (fx-id) | 014 |
:rf.fx/clear-http-interceptor |
fx | [:rf.fx/clear-http-interceptor {:id <id> ?:frame …}] — unregister an HTTP interceptor at runtime via :fx (args :frame wins, else the fx-context frame). |
v1 (optional capability) | — (fx-id) | 014 |
re-frame.http/get |
Fn | (rf.http/get url) / (rf.http/get url args) — build a [:rf.http/managed {:request {:method :get :url url} ...}] fx vector (per 014 §Call-site helpers). |
v1 (optional capability) | advanced | 014 |
re-frame.http/post |
Fn | (rf.http/post url) / (rf.http/post url args) — POST helper; same shape as get. |
v1 (optional capability) | advanced | 014 |
re-frame.http/put |
Fn | (rf.http/put url) / (rf.http/put url args) — PUT helper. |
v1 (optional capability) | advanced | 014 |
re-frame.http/delete |
Fn | (rf.http/delete url) / (rf.http/delete url args) — DELETE helper. |
v1 (optional capability) | advanced | 014 |
re-frame.http/patch |
Fn | (rf.http/patch url) / (rf.http/patch url args) — PATCH helper. |
v1 (optional capability) | advanced | 014 |
re-frame.http/head |
Fn | (rf.http/head url) / (rf.http/head url args) — HEAD helper. |
v1 (optional capability) | advanced | 014 |
re-frame.http/options |
Fn | (rf.http/options url) / (rf.http/options url args) — OPTIONS helper. |
v1 (optional capability) | advanced | 014 |
Public API surface in re-frame.core for ports that ship Spec 014. Ports that omit it MUST NOT register :rf.http/* for any other purpose (per Conventions §Reserved namespaces).
The raw install-managed-request-stubs! / uninstall-managed-request-stubs! pair is NOT a re-frame.core façade export — it is test-support infrastructure reached through its home namespace re-frame.http.test-support ((:require [re-frame.http.test-support :as http-test-support])). The ergonomic with-managed-request-stubs macro is the façade surface; use the raw pair only when stubs must span multiple deftests.
The verb helpers (get / post / put / delete / patch / head / options) live in re-frame.http — users (:require [re-frame.http :as rf.http]) alongside re-frame.core. They're pure synthesis fns that produce the canonical [:rf.http/managed args-map] fx vector; the namespace ships in day8/re-frame2-http (same artefact as the fx they reference) so loading the helpers and the fx are a single dep decision.
Reply-payload shape¶
Every reply lands as the canonical uniform reply envelope (rf2-ibksxg — one dialect, no {:kind :success/:failure} reshape): {:status :ok :value v …} on success, {:status :error :error {:kind <:rf.http/*> …} …} on failure, {:status :cancelled :error {:kind :rf.http/aborted …} …} on abort. Reply addressing is explicit: :reply-to is the unified app-facing spelling — one event vector for both the success and the failure reply, the envelope appended as the last event-vector arg (the app branches on (:status reply)); the same :reply-to key resources / mutations use. :on-success / :on-failure are the split routing sugar over it — a named target per branch, both receiving the identical envelope. All lower to the one internal / normalized :rf/reply-to descriptor (a conformance surface, no longer an everyday spelling). Omitting all reply targets raises :rf.error/http-no-reply-target at dispatch — the co-located default (reply merged under :rf/reply back to the originating event) was retired pre-alpha (rf2-et4c1s). Detailed in 014 §Reply addressing and §Reply payload shape.
Failure categories (closed set)¶
The eight :kind values inside a failure reply, all reserved under :rf.http/* (per Conventions §Reserved namespaces). See 014 §Failure categories for tags-by-kind:
:kind |
Meaning |
|---|---|
:rf.http/transport |
Network / DNS / connection error pre-HTTP |
:rf.http/cors |
CORS preflight rejected (CLJS-only) |
:rf.http/timeout |
Per-attempt timeout fired |
:rf.http/http-4xx |
Non-2xx 4xx response |
:rf.http/http-5xx |
Non-2xx 5xx response |
:rf.http/decode-failure |
2xx response but decode rejected the body |
:rf.http/accept-failure |
:accept returned {:failure user-map} |
:rf.http/aborted |
Request aborted via :request-id or :abort-signal |
Trace events emitted by :rf.http/managed¶
:operation |
:op-type |
When |
|---|---|---|
:rf.http/retry-attempt |
:info |
Per intermediate attempt that matched :retry :on, plus a terminal retry-sequence stop marker once the sequence ends (budget spent, or a later attempt failed outside :retry :on); carries :request-id, :url, :attempt, :max-attempts, :failure, :next-backoff-ms (nil on the terminal stop marker) |
:rf.http.interceptor/registered |
:info |
A reg-http-interceptor succeeded; carries :frame, :id (per 014 §Middleware) |
:rf.http.interceptor/cleared |
:info |
A clear-http-interceptor removed an existing slot; carries :frame, :id |
:rf.error/http-interceptor-failed |
:error |
An interceptor :before or :after threw; carries :frame, :interceptor-id, :url, :cause (plus :phase :after on the response side). Request side: the request is NOT dispatched; response side: the reply is suppressed (per 014 §Middleware §Failure mode) |
Schema-reflection metadata¶
Handlers may declare :rf.http/decode-schemas [<schema> ...] in their reg-event metadata-map; pair tools and generators read it via (rf/handler-meta :event id). Optional, never enforced — see 014 §Schema reflection.
Resources (Spec 016)¶
The Resources artefact (day8/re-frame2-resources, post-v1 optional) ships declarative cached server-state. Its full registration / event / sub / accessor surface is normatively defined in 016-Resources.md; this projection rows the facade exports classified by the standing diff-time rule (every new re-frame.core facade export is classified + justified when it lands). The resource/mutation registrars and the :rf.resource/* / :rf.mutation/* keyword-addressed events, subs, and trace ops follow the artefact's tier (optional capability) and carry no Tier column (they are not vars).
| API | M/Fn | Signature | Status | Tier | Spec | Notes |
|---|---|---|---|---|---|---|
reg-resource |
Fn | (reg-resource resource-id metadata request-fn) — canonical 3-slot grammar: the :request fetch fn is the third VALUE slot, metadata the reflection + config map (:scope, :params-schema, :data-schema, …) |
post-v1 lib (optional capability) | advanced | 016 | The :resource registrar kind. Late-bound by the Resources artefact. A :request left inside the metadata map is a loud :rf.error/resource-bad-spec. Front-room of the resources surface but advanced (not front-porch): an optional post-v1 capability a new app does not reach for on day one. |
clear-resource |
Fn | (clear-resource resource-id) |
post-v1 lib (optional capability) | advanced | 016 | Registration-lifecycle removal (the clear- registrar decrement per Conventions §Tear-down verb axis); disposes resource-runtime state per 016 §Registration. |
reg-mutation |
Fn | (reg-mutation mutation-id metadata request-fn) — canonical 3-slot grammar: the :request write fn is the third VALUE slot, metadata the reflection + config map (:params-schema, :invalidates, …) |
post-v1 lib (optional capability) | advanced | 016 | The :mutation registrar kind — a named causal write. A :request left inside the metadata map is a loud :rf.error/mutation-bad-spec. :rf.mutation/execute carries the call-site :reply-to continuation target. |
clear-mutation |
Fn | (clear-mutation mutation-id) |
post-v1 lib (optional capability) | advanced | 016 | Registration-lifecycle removal of a mutation. |
reg-resource-scope |
Fn | (reg-resource-scope scope-id metadata resolve-fn) — register a pure named db-derived scope resolver in the canonical 3-slot grammar (rf2-bqstzr): the :resolve fn is the value slot, and metadata carries the declared :inputs {name [:db <rf-path>]} (plus optional :doc). Omitting :inputs (the 2-arg (reg-resource-scope scope-id resolve-fn) sugar) selects the whole-db form — the resolver reads the db, a tooling-marked explicit-cost dependency. A :resolve inside the metadata map is rejected loudly. The :resource-scope registrar kind. |
post-v1 lib (optional capability) | advanced | 016 | Facade classification (EP-0016 D3): a re-frame.core export of the optional Resources artefact, justified as the single scope-resolution currency reused by resource registration, route resources, event ensure, subscriptions, invalidation descriptors, exact targets, and clear-scope. advanced — an optional-artefact authoring surface, not front-porch. Per 016 §Named resource-scope resolvers. |
clear-resource-scope |
Fn | (clear-resource-scope scope-id) |
post-v1 lib (optional capability) | advanced | 016 | The clear- registrar decrement counterpart of reg-resource-scope (per Conventions §Tear-down verb axis). |
resolve-resource-scope |
Fn | (resolve-resource-scope db scope-id) — resolve a named scope resolver against a supplied db value; returns the canonical scope or nil. A pure resolver helper, not an effect (no app-state / dispatch side effects) and no observability side effect — it routes through the trace-free pure evaluator, so unlike the causal resolution sites it does not emit :rf.resource/scope-resolved. |
post-v1 lib (optional capability) | advanced | 016 | Facade classification (EP-0016 D3 / issue 7): a re-frame.core export justified as the ergonomic helper for the logout/account-switch idiom — resolve the concrete old scope from the handler's coeffect db (no :snapshot-db payload, which would be an egress-bearing record under EP-0015). A plain function over the resolver registry; no new effect-API surface, no resolution-timing ambiguity. It routes through the trace-free pure evaluator, so — unlike the causal {:from-db …} / route-entry / mutation-settle resolution sites — it does not emit :rf.resource/scope-resolved (rf2-ru73k6 F3: a passive read advertised as pure has no observability side effect). Per 016 §clear-scope resolves the concrete scope from the coeffect db. |
resources |
Fn | (resources {:frame f}) — frame-targeted introspection: the registered resources plus the live per-frame resource-instance table |
v1 (optional capability) | advanced | 016 | Façade re-export of the Resources artefact (late-bound). |
mutations |
Fn | (mutations {:frame f}) — frame-targeted introspection: the registered mutation ids plus the live per-frame mutation-instance table (keyed by instance id) |
v1 (optional capability) | advanced | 016 | |
resource-meta |
Fn | (resource-meta resource-id) — the registered resource's spec map (:params-schema, :data-schema, :request, :scope, …), or nil |
v1 (optional capability) | advanced | 016 | |
mutation-meta |
Fn | (mutation-meta mutation-id) — the registered mutation's spec map (:request, :params-schema, :invalidates, …), or nil |
v1 (optional capability) | advanced | 016 | |
resource-state |
Fn | (resource-state {:resource … :scope … :params … :frame …}) — a resource instance's live runtime state (explicit frame target) |
v1 (optional capability) | advanced | 016 | |
mutation-state |
Fn | (mutation-state {:instance … :frame …}) — a mutation instance's durable runtime row ({:status :result :error …}), or nil (explicit frame target) |
v1 (optional capability) | advanced | 016 | |
install-revalidation-listeners! |
Fn | (install-revalidation-listeners! frame-id) — window focus / network-reconnect listeners driving active-stale revalidation for the frame; idempotent; cancelled on frame destroy; CLJS-only (JVM no-op) |
v1 (optional capability) | advanced | 016 | |
remove-revalidation-listeners! |
Fn | (remove-revalidation-listeners! frame-id) — tear down the revalidation listeners; no-op when none installed (and on the JVM) |
v1 (optional capability) | advanced | 016 |
Request decoration (EP-0016 Rider 3) reuses the existing HTTP facade. Auth/tracing/base-URL/retry decoration for resources and mutations is not a new resources surface — it is the existing
reg-http-interceptor/clear-http-interceptor(§HTTP requests), registered once per frame and applied to every:rf.http/managedrequest (resource reads, mutations, plain managed calls). No new facade export is introduced for decoration; the doctrine is ownership (transport decoration lives in the managed-HTTP seam), per 016 §Request decoration belongs to the managed-HTTP seam.
Effect-map shape¶
Closed: #{:db :rf.db/runtime :fx}. Ordinary app handlers return only :db + :fx; :rf.db/runtime is reserved by convention for framework / runtime-extension authority (it writes the runtime-db partition — non-framework handlers emitting it are surfaced by dev diagnostics, not silently dropped). See Spec-Schemas §:rf/effect-map. Top-level :dispatch / :dispatch-later / :dispatch-n from v1 migrate via MIGRATION.md §M-8.
| Key | Notes |
|---|---|
:db |
New app-db partition (replaces). The app-facing state key. |
:rf.db/runtime |
New runtime-db partition (replaces). Reserved by convention for framework / runtime-extension authority — ordinary app handlers do not emit it. |
:fx |
Vector of [fx-id args] pairs. |
Standard :fx entries:
[fx-id args] |
Args | Status | Spec | Notes |
|---|---|---|---|---|
[:dispatch [event-id ...]] |
event vector | v1 | 002 | |
[:dispatch-later {:ms ms :event event-vec}] |
options map | v1 | 002 | |
[:http args] |
impl-specific | — | — | user-registered via reg-fx. |
[:rf.http/managed args-map] |
args per 014 §The args map | v1 (optional capability) | 014 | Framework-provided when the implementation ships Spec 014. CLJS reference: ships on Fetch + JVM HttpClient. See also :rf.http/managed-abort, :rf.http/managed-canned-success, :rf.http/managed-canned-failure. |
[:rf.nav/push-url url-string] |
URL string | v1 | 012 | |
[:raise event-vec] |
event vector | v1 | 005 | machine-only: reserved fx-id recognised by the machine handler; routes the event back into the same machine, atomic and pre-commit. Outside a machine action's :fx, this fx-id is unbound. |
[:rf.machine/spawn spawn-spec] |
spawn-spec map (per :rf.fx/spawn-args: :machine-id/:definition, :id-prefix, :data, :on-spawn, :start) |
v1 | 005 | Canonical actor-lifecycle fx (registered globally by re-frame.machines); installs a new dynamic actor (whose snapshot lives at [:rf.runtime/machines :snapshots <gensym'd-id>]). On the declarative :spawn / :spawn-all path the reducer binds the assigned id into the spawning machine's own :data under :rf/spawned (XState-context parity, per 005 §Recording the spawned id user-side); :on-spawn is advisory-only and cannot record it (its return is dropped). Emitted from any event handler's :fx (including machine actions and the :spawn desugar). |
[:rf.machine/destroy actor-id] |
actor id (keyword) | v1 | 005 | Canonical actor-destroy fx (registered globally by re-frame.machines); runs the actor's :exit action, dissociates [:rf.runtime/machines :snapshots <actor-id>], and clears the actor's event-handler registration. Symmetric counterpart to :rf.machine/spawn. |
Public registrar query API¶
For tooling, agents, story tools, 10x.
Two read shapes, no realm coordinate. The registrar query grammar has exactly two shapes per member (
registrations/handler-meta): the positional-keyword arity reads the default source store ((registrations :event)etc.), and a map is ALWAYS a frame-targeted read (next note). There is no:realmmap arity —(registrations {:realm r :kind k})and friends do not exist; there is no realm coordinate in the public read grammar. A registrar-query map without:frameis an error (:rf.error/registrar-query-needs-frame) — a map is never a "default realm" read. There is nore-frame.realmnamespace and no realm-scoped reader to read; frame resolution routes directly through the process registrar (re-frame.registrar/re-frame.frame/re-frame.image). Tooling that needs the host app's registrations without rebinding to its own image generation (e.g. Xray's host-registry generation-bypass) reads the process source store directly viare-frame.registrar, not a realm coordinate.Frame-targeted query. The map-shaped form —
(registrations {:frame f :kind k}),(handler-meta {:frame f :kind k :id id})— resolves the(kind, id)set through live framef's own sealed image generation ("target frame → resolved image generation → registration resolution"), surfacing the:rf.provenance/ns+ inline/image facts the resolved descriptors carry (cross-image overrides are reported on the generation's:rf.gen/shadowsshadow report).:frameis a registered frame id (keyword) OR a frame value (rf/make-frame's return token) — the same target shaperf/frame-generationaccepts; the public routing address is the frame id. This is the READ of the image→frame model: the public tooling surface a tool reaches instead of the internalre-frame.live-frame/re-frame.image-assemblynamespaces. Fail-loud: a:framethat does not resolve to a live frame carrying a generation throws:rf.error/frame-no-generation(no fallback to the default registrar — the read needs a live frame). The dedicated raw readframe-generation(below) returns the whole sealed generation. The positional-keyword arity (default source store) is byte-identical — only a caller passing a{:frame …}map reaches the generation path.
| API | M/Fn | Signature | Status | Tier | JVM-runnable? | Spec |
|---|---|---|---|---|---|---|
registrations |
Fn | (registrations kind) / (registrations kind pred-fn) → {id metadata-map}. Use when you want metadata — registry walks that read source-coords, :rf/sensitive, :rf/machine?, :platforms, etc. The positional-keyword arity reads the default source store. Frame-targeted: (registrations {:frame f :kind k}) → the {id metadata} for :kind resolved through live frame f's own sealed image generation (only the ids that frame's image carries, with :rf.provenance/ns provenance facts); :frame is a frame id or a direct frame object; an optional :pred filters. A map argument is ALWAYS a frame-targeted read — a map without :frame is an error (:rf.error/registrar-query-needs-frame); there is no :realm map arity. Fail-loud on an unresolvable :frame (see note above). |
v1 | tooling | ✓ | 002 |
handler-meta |
Fn | (handler-meta kind id) → registration-metadata map. View registrations include source-coord keys (:ns / :line / :column / :file) per :rf/source-coord-meta (Spec-Schemas); pair tools resolve data-rf2-source-coord DOM annotations to :file via this lookup. Frame-targeted: (handler-meta {:frame f :kind k :id id}) → the metadata for [k id] resolved through live frame f's sealed image generation (with :rf.provenance/ns provenance facts), or nil when that frame's image carries no such [k id]. A map argument is ALWAYS a frame-targeted read — a map without :frame is an error (:rf.error/registrar-query-needs-frame); there is no :realm map arity. Fail-loud on an unresolvable :frame (see note above). |
v1 | tooling | ✓ | 002 |
frame-generation |
Fn | (frame-generation f) → the sealed, resolved image generation live frame f is running — the inert image-assembly value with the four documented stable public keys :rf.gen/resolver ({[kind id] descriptor}), :rf.gen/images (the normalized images in :images order — later wins), :rf.gen/kinds, and :rf.gen/shadows (the cross-image shadow report — [{:registration [kind id] :image <defined-in> :shadowed-by <winner>} …]; [] when nothing was overridden — read the report directly off this key). f is a registered frame id or a direct frame object. The dedicated raw read over the frame→generation model — for describe-image-style views that want the whole generation (selected registrations, shadow report, provenance) without per-kind round-trips; the :frame arities of the pair above give per-(kind, id) resolution with provenance. Fail-loud :rf.error/frame-no-generation when f does not resolve to a live frame carrying a generation (no nil-as-default, no realm fallback). (There is no :rf.gen/requires capability slot — image-declared host capabilities are removed.) The public tooling surface tools (Pair MCP, Xray) reach instead of re-frame.live-frame / re-frame.image-assembly internals. |
EP-0023 | tooling | ✓ | 002 |
frame-ids |
Fn | (frame-ids) / (frame-ids ns-prefix) |
v1 | tooling | ✓ | 002 |
frame-meta |
Fn | (frame-meta frame-id) |
v1 | tooling | ✓ | 002 |
app-db-value |
Fn | (app-db-value frame-id) → the app-db partition value (plain map) — the out-of-band value read. The front-porch read is subscribe; app-db-value is the non-reactive snapshot read for tools, tests, REPL, and fx/handler bodies. |
v1 | advanced | ✓ | 002 |
frame-state-value |
Fn | (frame-state-value frame-id) → the coherent frame-state projection {:rf.db/app <app-db> :rf.db/runtime <runtime-db>}. The full-frame read for SSR / epoch / time-travel / Xray (EP-0001). The runtime-db-only read (retired runtime-db-value, rf2-t3lftq — API-shrink #3) is (:rf.db/runtime (frame-state-value frame-id)). |
v1 | tooling | 002 |
The static subscription-topology query and the runtime sub-cache snapshot are subscription-tooling surfaces, not re-frame.core facade reads. Reach them through their owning namespace re-frame.subs.tooling — (sub-topology) (static dependency graph over the registrar) and (sub-cache-snapshot frame-id) (live cache state) — with subs/sub-topology / subs/sub-cache-snapshot as the JVM legacy aliases.
Schema-introspection accessors — app-schemas, app-schema-at, app-schemas-digest — are rowed canonically in §Schemas.
compute-sub is rowed canonically in §Testing (pure sub computation against an app-db value).
App values and composition (EP-0013)¶
Removed from the public facade. This section is retained as a stable anchor for inbound cross-references; the surface it once documented is no longer public.
The retired composition vocabulary is not on the public facade. The app/realm/module composition surface —
rf/app/rf/module(+ therf/app-registrations/rf/app-requires/rf/app-ownsinspectors),rf/install!/rf/reinstall!,rf/realm/rf/dispose-realm!, and the realm readsrf/realm-ids/rf/frame-realm/rf/installed-app— is not part ofre-frame.core. The public model isimage → frame → event stream: a feature namespace registers ordinaryreg-*forms; anrf/imageselects them by:select-nsprovenance (or defines them inline via:registrations) and is supplied tomake-frame(see §Registration) via:images(the later image wins; the generation's:rf.gen/shadowsreport — read viaframe-generation— names cross-image overrides); re-callingmake-frameagainst the SAME:idwith a new:imagesvector hot-reloads a frame's image generation in place (preserving frame memory; the reload diff is a read —generation-diffover twoframe-generationvalues, not a bespoke verb); and a frame is addressed by its process-local frame id (or a frame value — the routing ops normalize a value to its id, so it is accepted directly; API-shrink #1 rf2-csbbwu), with no realm coordinate. There is nore-frame.realmnamespace, no installed-app value, and no realm coordinate on any wire record (see Spec-Schemas §:rf/realm): frame resolution routes directly through the process registrar (re-frame.registrar/re-frame.frame/re-frame.image), not a realm. The app/realm/module construction model — and the EP-0013→EP-0023 migration mapping — is documented in EP-0023 §Backwards Compatibility and the EP history; it is not a live data surface (there are norf/migration-map/rf/migration-explainfacade reads). For tooling that reads a frame's resolved registrations, use the frame-targeted{:frame f …}registrar queries +frame-generation(§Public registrar query API).
Schemas¶
Namespace: the introspection surfaces below live in
re-frame.schemas(artefactday8/re-frame2-schemas); consumers(:require [re-frame.schemas :as schemas]). They are not re-exported fromre-frame.core— apps targeting schemas add the artefact and require the namespace directly. The registration macros (reg-app-schema/reg-app-schemas) live inre-frame.coreand route through the schemas artefact at registration time. Per the §Conventions per-artefact namespace table.
reg-app-schema is rowed canonically in §Registration.
The
:frameopt is a frame TARGET. Every schema opts surface that names a frame — the:framekey ofreg-app-schema's metadata map, theoptsofreg-app-schemas, and the{:frame …}/ bare-frame-id arity ofapp-schemas/app-schema-at/app-schema-meta-at/app-schemas-digest— accepts either a frame-id keyword OR a frame value (rf/make-frame's return token), the same target shapes the registrar query API's:frameaccepts. A frame value is normalized to its frame id (the routing address) before it keys the per-frame schema store, so a schema registered against a frame value is found by a later read-by-id (and vice versa). A bare frame value passed as the opts argument routes to its own frame, not the ambient frame. An explicit:framethat resolves to a non-keyword target (a string, a non-frame map, a vector) fails loud with:rf.error/app-schemas-bad-argrather than silently becoming an unreachable registry key.One preferred validator-extension path. To swap the validator / explainer / printer at boot (e.g. drop Malli for a clojure.spec or Zod-style port), use
set-schema-fns!— the single bundle setter that installs all three from one map so they never drift mid-boot. The per-fn singletonsset-schema-validator!/set-schema-explainer!/set-schema-printer!are retained as lower-level convenience setters for adjusting one fn in isolation; they remainadvancedbut are not the preferred boot path.
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
app-schemas |
Fn | (app-schemas) / (app-schemas {:frame frame-id}) |
v1 | tooling | 010 |
app-schema-at |
Fn | (app-schema-at path) / (app-schema-at path {:frame frame-id}) |
v1 | tooling | 010 |
app-schema-meta-at |
Fn | (app-schema-meta-at path) / (app-schema-meta-at path opts-or-frame-id) — return the full registration-metadata map (:path, :schema, :frame, plus source-coords :ns / :line / :file and the rest of :rf/registration-metadata) for a registered app-db schema, or nil. Pair-tool and 10x consumers reach for this when they need the registration anchor (e.g. click-back-to-code); the lighter app-schema-at is the right call when only the schema value is needed. Per 010 §Schemas as a tooling/agent surface and Spec-Schemas §:rf/app-schema-meta. |
v1 | tooling | 010 |
app-schemas-digest |
Fn | (app-schemas-digest) / (app-schemas-digest {:frame frame-id}) → string |
v1 | tooling | 010 |
set-schema-fns! |
Fn | (set-schema-fns! {:validate validate-fn :explain explain-fn :print print-fn}) — the preferred schema-fns configuration path. Atomically install any subset of the validator / explainer / printer bundle from one map. Each key is optional; an absent key leaves the existing registration in place. The honest one-call substitute-Malli boot pattern (a Zod / clojure.spec port installs all three together so they never drift mid-boot — the reason this is the blessed entry point over the per-fn singletons below). :print nil coerces to the default EDN canonicaliser (parity with set-schema-printer!); :validate nil / :explain nil disable that fn. Last-write-wins per key; returns the installed bundle as a map {:validate … :explain … :print …} reflecting the live state of all three fns after the call (a bundle setter returns its bundle, not just the validator). Per 010 §Default validator and the validator-fn extension point. |
v1 | advanced | 010 |
set-schema-validator! |
Fn | Lower-level single-fn setter — prefer set-schema-fns! for boot. (set-schema-validator! validate-fn) installs only the validator ((fn [schema value] truthy?), same shape as malli.core/validate) every dev-time schema-validation site routes through; the explainer/printer are left untouched. nil disables validation entirely. Default ships Malli's validate; this seam lets apps swap in their own validator to drop the Malli dep. Last-write-wins; returns the installed validator. Reach for it only when adjusting one fn in isolation (e.g. a runtime toggle); installing a port's full bundle at boot goes through set-schema-fns! so the three never drift mid-boot. Per 010 §Default validator and the validator-fn extension point. |
v1 | advanced | 010 |
set-schema-explainer! |
Fn | Lower-level single-fn setter — prefer set-schema-fns! for boot. (set-schema-explainer! explain-fn) installs only the explainer used to enrich :rf.error/schema-validation-failure traces' :explain key. Companion to set-schema-validator!; subsumed by the :explain key of set-schema-fns!. Per 010 §Default validator and the validator-fn extension point. |
v1 | advanced | 010 |
set-schema-printer! |
Fn | Lower-level single-fn setter — prefer set-schema-fns! for boot. (set-schema-printer! print-fn) installs only the schema-print companion the digest pipeline (per 010 §Schema digest) hashes. print-fn is (fn [schema-value] canonical-string) and MUST be pure + deterministic across runtimes. nil falls back to the default EDN canonicaliser so the digest is never undefined. Parallel to set-schema-validator! / set-schema-explainer!; subsumed by the :print key of set-schema-fns!. Non-Malli ports register their own serialiser so cross-runtime digest comparison reflects their port's contract. Per 010 §Default validator and the validator-fn extension point. |
v1 | advanced | 010 |
validate-at-boundary-interceptor |
Var (interceptor value) | validate-at-boundary-interceptor — a pre-built interceptor value, not a fn (interceptor :id is :rf.schema/at-boundary); do not call it as a fn (invoking (rf/validate-at-boundary-interceptor ...) raises ArityException). Under EP-0022 a public :interceptors chain carries refs, not inline values: reference the registered :rf.schema/at-boundary interceptor by id (the value Var is the registration-boundary input, not a chain entry). |
v1 | advanced | 010 |
See 010 §Schemas for :schema metadata, validation timing, and dev/prod elision. (The framework does not accept a :spec metadata key on reg-* metadata; the key is :schema. Per MIGRATION §M-54.)
Event-emit (always-on, production-survivable)¶
Per 009 §What IS available in production (#2). A minimal always-on listener surface that survives :advanced + goog.DEBUG=false and delivers one tight record per processed event. Parallel to (not a fallback for) the dev-only trace surface; per-event only — no per-sub, per-fx, or per-:rf.event/db-changed records. Record shape {:event :event-id :frame :time :outcome :elapsed-ms} (the always-on event-emit substrate's own record fields, distinct from trace :tags keys); the :event slot is passed through rf/elide-wire-value once before fan-out, so schema-marked :sensitive? paths land as :rf/redacted and :large? paths land as :rf.size/large-elided. :outcome is one of :ok (clean settle), :error (the interceptor chain threw), :rolled-back (a candidate :db / machine-data transition was refused before install), :flow-error (a flow's :output threw), or :rejected (the :rf.schema/at-boundary interceptor refused the event's payload against the handler's :schema, so the handler never ran) — an aborted dispatch is never reported as a clean :ok. Two of those five behave differently under a release build, and the pair is worth reading together: :rolled-back has no producer in production (candidate validation is dev-only), while :rejected does, because boundary validation is ungated per 010 §Production builds. It is not the only schema check a release build keeps — C-000.35 settles that by what the check is for, not by who declared the schema it reads, and the framework's other load-bearing checks are ungated alongside it — but it is the only one this :outcome vocabulary names in its own right; the others throw on their own paths, and a throw reports :error. See 009 §What IS available in production for the full per-member contract.
Advanced corpus-wide hook — not the off-box default (EP-0015 §9). This registry fans an UNPROJECTED record across EVERY frame; it is not routed under any frame's egress policy. The NORMAL production observation surface for hosted back-ends (Datadog / Honeycomb / Sentry / …) is the frame-owned
:observability :handled-eventssink (Spec 015 §Frame-owned observability sink policy), declared on the frame'smake-frame/frame-rootconfig and wired withregister-observability-sink!, which hands the sink an already-PROJECTED record. Reach for the corpus-wide listener only for an intentionally cross-frame hook or a record the sink routing does not carry.Sensitive data marking is path-based per the upcoming data-classification mechanism (separate spec doc; in progress). The handler-meta
:sensitive?annotation is removed.
Registration uses the stream-parameterized listener verb with the :events stream — (register-listener! :events id listener-fn) / (unregister-listener! :events id) (see §Observation listeners). The always-on event-emit registry stays addressable via re-frame.event-emit for internal consumers (which also own between-scenario clears).
Error-emit (always-on, production-survivable)¶
Per 009 §What IS available in production. Sibling of the event-emit surface above; runs through the always-on error-emit substrate. Survives :advanced + goog.DEBUG=false.
Advanced corpus-wide hook — not the off-box default (EP-0015 §9). This registry fans the record across EVERY frame UNPROJECTED — the
:eventvector is wire-elided, but the:exceptionrides RAW and no frame egress policy is applied, so raw owner-local data can leave a frame here. It is the documented exception to the always-on axis's 'structured data only' rule, for post-mortem shippers that need the host throwable + stack. The NORMAL off-box error-observation surface is the frame-owned:observability :errorssink (Spec 015 §Frame-owned observability sink policy), declared on the frame'smake-frame/frame-rootconfig and wired withregister-observability-sink!, which PROJECTS the record under the owning frame's classification + the sink's egress profile (sensitive paths redacted,:exceptiondropped under:rf.egress/public-error) BEFORE the sink sees it. Off-box shippers (Sentry / Honeybadger / Rollbar) should prefer the frame sink; reach for the corpus-wide listener only for an intentionally cross-frame hook or a frameless record the sink routing does not carry.
The listener payload is a union of three record shapes: (a) the per-event error record — {:error :event :event-id :frame :time :exception :elapsed-ms} — fanned out by dispatch-on-error! once per catalogued production-reachable per-event runtime :rf.error/* event; (b) the frame-teardown report — {:error :rf.error/frame-teardown-failed :frame :hook-failures :reason :recovery :time} — one bounded record per frame destroy whose best-effort cleanup hooks threw (EP-0008 promotion criterion; see 009 §Observability channels and the promotion criterion); and (c) the six EP-0008-promoted SSR non-event categories (:rf.error/ssr-render-failed, :rf.error/ssr-streaming-writer-failed, :rf.error/malformed-hydration-payload — incl. a pre-frame frameless :frame nil sub-path — :rf.error/ssr-head-resolution-failed, :rf.error/sanitised-on-projection, :rf.error/ssr-ring-error-view-failed), flat union records {:error :frame :time …category keys…}. The two non-event arms (b) and (c) ride the general dispatch-error-record! helper and carry no :event / :event-id; the teardown report carries a :hook-failures vector instead of the per-event :exception / :elapsed-ms slots. Listener bodies MUST branch on (:error record) (or otherwise tolerate a record with no top-level :event / :exception) rather than assuming the per-event shape. The per-event record's :event slot is passed through rf/elide-wire-value once before fan-out, so schema-marked :sensitive? paths land as :rf/redacted and :large? paths land as :rf.size/large-elided. This is the single error-observability surface; recovery is the framework's typed per-category default, not app-steerable (the per-frame :on-error recovery policy was removed). Per-listener exceptions are isolated — a buggy listener cannot block siblings or the run.
Sensitive data marking on the error-emit substrate is path-based per the upcoming data-classification mechanism (separate spec doc; in progress). The handler-meta
:sensitive?annotation is removed — the per-path elision wire-walker is the sole redaction surface on this path.
Registration uses the stream-parameterized listener verb with the :errors stream — (register-listener! :errors id listener-fn) / (unregister-listener! :errors id) (see §Observation listeners). The always-on error-emit registry itself stays addressable via re-frame.error-emit + the :error-emit/register-error-listener! late-bind hooks for internal consumers (router fan-out, the SSR error projector), which also own between-scenario clears.
Observation listeners¶
One stream-parameterized listener verb registers an observation callback across the four pure listener streams — the differentiator is data (which stream), so it rides in a leading required stream keyword (in place of per-channel register-(trace|event|error|epoch)-listener! pairs). The closed stream vocabulary is :trace / :events / :errors / :epoch; an unknown stream throws :rf.error/unknown-listener-stream (no bare trace default, no compatibility aliases). The frame-owned observability sink is a distinct verb, NOT a :sink stream.
| Stream | Axis | Record |
|---|---|---|
:trace |
dev-only (DCE'd in production) | one trace event per call |
:events |
always-on (survives :advanced + goog.DEBUG=false) |
one tight event-record per processed event, fanned across EVERY frame, :event wire-elided but otherwise unprojected — the ADVANCED corpus-wide hook, NOT the off-box default (the frame-owned :observability sink is) |
:errors |
always-on | one error-record per :rf.error/* event, fanned across EVERY frame, :event wire-elided, :exception RAW — the ADVANCED corpus-wide hook, NOT the off-box default |
:epoch |
optional artefact (dev-only) | one :rf/epoch-record per dequeued event; no-op returning nil when day8/re-frame2-epoch is absent |
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
register-listener! |
Fn | (register-listener! stream id listener-fn) — register listener-fn under id on stream (:trace / :events / :errors / :epoch). Re-registering the same id on the same stream replaces. Returns id (or nil on :epoch when the epoch artefact is absent). Unknown stream throws :rf.error/unknown-listener-stream. |
v1 | tooling | 009 |
unregister-listener! |
Fn | (unregister-listener! stream id) → nil. No-op on :epoch when the epoch artefact is absent. |
v1 | tooling | 009 |
There is deliberately no facade clear-listeners! verb: dropping every listener on a stream is a test-isolation concern owned by the fixture layer, not the public facade. re-frame.test-support's reset clears the registries through the lower-level sinks directly (re-frame.trace.tooling/clear-listeners!, re-frame.event-emit/clear-event-listeners!, re-frame.error-emit/clear-error-listeners!, and the :epoch/clear-epoch-listeners! reset hook) — the former per-stream clear-listeners! façade verb was retired in API-shrink #4.
Tracing¶
All tracing is dev-only (elided in production). See 009 §Tracing for emit semantics and synchronous listener delivery. Trace-listener registration uses the stream-parameterized listener verb with the :trace stream.
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
emit-trace-event! |
Fn | (emit-trace-event! op-type operation tags) → nil |
v1 (dev-only) | tooling | 009 |
re-frame.interop/debug-enabled? |
Var | ^boolean. CLJS: alias of goog.DEBUG — constant-folded by Closure under :advanced, so :advanced + goog.DEBUG=false builds DCE every (when interop/debug-enabled? ...) branch. JVM: a def read ONCE at ns-load from the Java system property -Dre-frame.debug (winning on conflict) or the environment variable RE_FRAME_DEBUG; defaults true (dev parity). Accepts the conventional false-y vocabulary case-insensitively (false, 0, no, off, empty string) with whitespace trimmed; anything else leaves the flag at true. Set BEFORE re-frame.interop loads. SSR / webhook receivers / long-running JVMs facing untrusted input MUST set the gate false explicitly — per 009 §JVM builds and Security §Production gates. |
v1 | tooling | 009 |
re-frame.performance/enabled? |
Var | ^boolean goog-defined (CLJS) / ^:const false (JVM). Set via :closure-defines {re-frame.performance/enabled? true} to bracket event dispatch / sub recompute / fx walk / view render, emitting a single options-bag performance.measure(name, {start, end}) per bracket (User-Timing measure entries rf:event:*, rf:sub:*, rf:fx:*, rf:render:*; no performance.mark entries are allocated). The measure is cleared by name after emit unless the companion re-frame.performance/retain-entries? goog-define (default false) is set — when set, entries persist in the host User-Timing buffer for one-shot getEntriesByType("measure") readers. Compile-time only — not a (rf/configure! ...) knob; runtime mutation has no effect. Default false; under :advanced + default the bracket DCEs and shipped binaries carry zero User-Timing instrumentation. CLJS-only — JVM is a no-op. See 009 §Performance instrumentation and Tool-Pair §Performance API consumption |
v1 | tooling | 009 |
trace-buffer |
Fn | (trace-buffer frame-id) / (trace-buffer frame-id opts) → the named frame's event-keyed ring, oldest-first (event bundles by default; {:flat true} for raw trace events). [] for a destroyed / never-registered frame and in production |
v1 (dev-only) | tooling | 009 |
clear-trace-buffer! |
Fn | (clear-trace-buffer! frame-id) → nil — empty the named frame's ring. No-op for an unknown frame and in production |
v1 (dev-only) | tooling | 009 |
(rf/configure! {:trace-buffer {:events-retained N}}) |
— | See §Configure keys. | v1 (dev-only) | — (configure key) | 009 |
group-by-event |
Fn | (group-by-event events) → vector of event bundles {:dispatch-id :event :handler :fx :effects :subs :renders :other}, sorted by emission order. Pure data; JVM-runnable. Re-exported from re-frame.trace.projection (see 009 §Event-bundle projection). |
v1 (dev-only) | tooling | 009 |
domino-bucket |
Fn | (domino-bucket trace-event) → #{:event :handler :fx :effect :sub :render :other}. Classifies a raw trace event into the six-domino slot used by group-by-event. Pure data. |
v1 (dev-only) | tooling | 009 |
group-by-event-with-events |
Fn | (group-by-event-with-events events) — like group-by-event, but each event bundle additionally carries :trace-events (the raw events that composed it), keyed by the same frame-scoped [frame dispatch-id] grouping. Pure data; JVM-runnable |
v1 (dev-only) | tooling | 009 |
Trace-emission opt-out (per-handler metadata)¶
Event-handler registration accepts a :rf.trace/no-emit? true metadata flag. When set, the runtime suppresses every trace emission and event-emit record within the handler's scope — the handler runs invisibly to the trace surface, the event-emit substrate, and (transitively) the epoch buffer. Used by framework-internal bookkeeping handlers (Xray, Story, re-frame2-pair-mcp, story-mcp) that would otherwise saturate the trace stream. Per Conventions §Reserved namespaces the :rf.trace/* namespace is framework-owned.
| Metadata key | Where | Value | Default | Effect |
|---|---|---|---|---|
:rf.trace/no-emit? |
reg-event metadata map |
boolean | false |
When true, suppresses all trace + event-emit emissions inside the handler's scope. Per 009 §Trace-emission opt-out. |
:rf.trace/frame-no-emit? |
frame config map (make-frame / frame-root) |
boolean | false |
When true, marks the frame a tool / inspector frame: the runtime suppresses every trace emission tagged with that frame, so the inspector's own reactivity does not flood the shared ring it inspects. The frame-scoped sibling of :rf.trace/no-emit?. Per 009 §Frame-level trace-emission opt-out. |
Epoch history (per Tool-Pair)¶
Per-frame epoch snapshots, recorded once per dequeued event (at each event's run-to-completion boundary — not once per drain: a drain that processes a parent event and the :fx [[:dispatch …]] child it queued commits two records) in dev builds. Used by pair-shaped tools for time-travel and post-mortem analysis. Production builds elide entirely.
All rows below are the Tool-Pair time-travel surface — pair-shaped dev tools (Xray, the pair-MCP servers), so they tier tooling (the classification guidance also calls epoch-listener registration "advanced power-user"; either way the surface is back-room and opt-in — never front-porch). The state-injection member is the ONE partial-map mutator replace-frame-state! (rf2-t3lftq — API-shrink #3 consolidated the former replace-app-db! / reset-app-db! / replace-runtime-db! / replace-frame-state! four-mutator family into this): a present partition key replaces that partition, an absent key is preserved — a db-shaped key never silently replaces the other partition.
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
epoch-history |
Fn | (epoch-history frame-id) → vector of epoch records. Returns [] for an unknown / destroyed frame (per Tool-Pair §Surface behaviour against destroyed frames). |
v1 (dev-only) | tooling | Tool-Pair |
restore-epoch! |
Fn | (restore-epoch! frame-id epoch-id) → boolean (true on success). Rewinds to the record's canonical :frame-state-after (BOTH partitions — reviving machines / routes / elision / SSR), not just the app-db projection (EP-0001). Returns false and emits a structured trace on any of its seven failure modes — an unknown / destroyed frame (:rf.error/no-such-handler, kind :frame), plus the six :rf.epoch/restore-* preconditions (drain-in-progress, unknown epoch, non-ok record, schema-digest mismatch, missing handler, machine version mismatch) enumerated in the epoch trace-events table below (per Tool-Pair §Surface behaviour against destroyed frames). |
v1 (dev-only) | tooling | Tool-Pair |
replace-frame-state! |
Fn | (replace-frame-state! frame-id frame-state) → boolean — atomically install a PARTIAL frame-state map (any subset of {:rf.db/app … :rf.db/runtime …}); a present key replaces that partition, an absent key is preserved unchanged. The ONE frame-state write surface: an app-only map ({:rf.db/app v}) is the former replace-app-db!; {:rf.db/app {}} is the former reset-app-db!; a runtime-only map ({:rf.db/runtime v}) is the former replace-runtime-db!; a both-key map is the full-frame atomic install for tool-driven replay / fixture install. Records a synthetic epoch. Rejects a map with no recognized partition key, or an unrecognized key, as :rf.error/replace-frame-state-bad-keys (checked before frame resolution). Emits :rf.error/no-such-handler (kind :frame) / returns false for an unknown / destroyed frame. |
v1 (dev-only) | tooling | Tool-Pair |
(rf/configure! {:epoch-history {:depth N}}) |
— | See §Configure keys. | v1 (dev-only) | — (configure key) | Tool-Pair |
epoch-silence-current? |
Fn | (epoch-silence-current? tags) → true when a received :rf.epoch.cb/silenced-on-frame-destroy signal still names a CURRENT fact: the carried :observed-gen is still the generation registered under :cb-id, AND that registration is not observing :frame right now. false otherwise — including an absent/nil :observed-gen, and when the artefact is absent. THE supported receiver decision: pass the signal's :tags map straight back. ONE atomic operation, not two composable reads (rf2-uhouu) — registration identity (a same-id replacement or unregister-drop makes a different generation current) and observation continuum (a same-id successor frame re-arms by DELIVERY, which mints no generation, so :observed-gen still matches while the callback is live again) are weighed under a SINGLE ledger snapshot. Composing those two facts from separate reads is not linearizable: a replacement landing between them accepts a silence for an already-superseded registration, an answer no single point in time held — which is why the low-level halves are not published (per Tool-Pair §Surface behaviour against destroyed frames + 009 §The delayed-silence emission linearization law). |
v1 (dev-only) | tooling | Tool-Pair |
app-db-value / frame-state-value (cross-ref to §Public registrar query API) |
Fn | Partition readers — app-db, and the coherent frame-state projection (the runtime-db-only read is (:rf.db/runtime (frame-state-value frame-id))). Each returns nil for an unknown / destroyed frame (per Tool-Pair §Surface behaviour against destroyed frames). |
v1 | advanced/tooling | 002 |
Epoch-settled listeners are the :epoch stream of the stream-parameterized listener verb — (rf/register-listener! :epoch id callback-fn) / (rf/unregister-listener! :epoch id) — not a separate facade fn (the per-channel register-epoch-listener! / unregister-epoch-listener! pair was retired in API-shrink #4, rf2-9flalp; the epoch stream registers through the one verb exactly like :trace / :events / :errors). The callback is a record-publication notification, not a once-per-event clock: an ordinary handled event publishes one record when it settles, and the SAME record re-publishes — carrying the same :epoch-id — when a post-settle render / sub-run / unmount back-fills into that already-settled epoch; synthetic records publish too (:rf.epoch/db-replaced per replace-frame-state! write and :halted-depth at the depth ceiling, both ring-retained when depth permits, plus the terminal :halted-destroy — an already-started event interrupted by frame destruction, delivered to listeners only and never retained). Because the listener is process-global while :epoch-id is unique only within one frame, reconcile on the pair [(:frame record) (:epoch-id record)] — cache under that key and replace on re-publication rather than counting callbacks; :outcome is record state (:ok / :halted-depth / :halted-destroy), not identity. A dequeued event rejected before it runs (no handler) publishes nothing. id may be any comparable value and re-registering the same id replaces. Process-global; a callback whose previously-observed frame is destroyed receives a one-shot :rf.epoch.cb/silenced-on-frame-destroy trace (per Tool-Pair §Surface behaviour against destroyed frames) carrying :observed-gen — the generation the silence is attributed to. A consumer decides whether a received signal is current with epoch-silence-current? (row above), the one supported receiver operation: it discards a signal owed to a replaced/dropped registration AND one superseded by a fresh delivery on the same registration, from a single ledger snapshot and without reading private registry state. Returns id, or nil when day8/re-frame2-epoch is absent.
Trace events emitted by epoch-history machinery:
:operation |
Tags |
|---|---|
:rf.epoch/snapshotted |
:frame, :rf.epoch/id, :rf.trace/event-id, :outcome (the detailed cause enum :ok / :halted-depth / :halted-destroy; :halted-handler-exception is schema-reserved, not currently emitted) |
:rf.epoch/outcome |
:frame, :rf.epoch/id, :rf.trace/event-id, :outcome (the consumer-facing :ok / :blocked / :error projection; fires paired with :rf.epoch/snapshotted per dequeued event) |
:rf.epoch/restored |
:frame, :rf.epoch/id |
:rf.epoch/db-replaced |
:frame, :rf.epoch/id |
:rf.epoch/restore-unknown-epoch |
:frame, :rf.epoch/id, :history-size |
:rf.epoch/restore-schema-mismatch |
:frame, :rf.epoch/id, :schema-digest-recorded, :schema-digest-current, :failing-paths |
:rf.epoch/restore-missing-handler |
:frame, :rf.epoch/id, :missing |
:rf.epoch/restore-version-mismatch |
:frame, :rf.epoch/id, :machine-id, :version-recorded, :version-current |
:rf.epoch/restore-during-drain |
:frame, :rf.epoch/id |
:rf.epoch/restore-non-ok-record |
:frame, :rf.epoch/id, :rf.epoch/outcome, :halt-reason |
:rf.epoch/replace-during-drain |
:frame |
:rf.epoch/replace-schema-mismatch |
:frame, :failing-paths |
:rf.epoch/replace-history-disabled |
:frame (a replace-frame-state! precondition failure when the history ring is disabled) |
:rf.error/replace-frame-state-bad-keys |
:frame, :reason (:no-recognized-keys / :unknown-keys), :keys (a replace-frame-state! precondition failure — no recognized partition key, or an unrecognized key; checked before frame resolution) |
:rf.epoch.cb/silenced-on-frame-destroy |
:frame, :cb-id, :observed-gen (the reserved callback generation the silence is attributed to; a consumer calls epoch-silence-current? on the tags to discard a superseded signal, per 009 §The delayed-silence emission linearization law) |
:rf.epoch.cb/listener-exception |
:frame, :cb-id, :rf.epoch/id, :message (an :epoch-stream listener callback threw) |
:rf.warning/epoch-redact-fn-exception |
:frame, :rf.epoch/id, :ex-msg (an installed :redact-fn threw at projection egress) |
:rf.warning/restore-quiesce-hook-exception |
:category, :hook, :frame, :exception (a restore-time async-quiesce hook threw) |
Size-elision wire-boundary walker¶
Cross-reference: see Security.md §Privacy / secret handling —
elide-wire-valueis named there as the single normative emission site for the:rf/redactedsentinel. Every off-box egress (trace forwarders, MCP servers, error monitors) routes through this walker; the trust-boundary surfaces catalogued in Security.md compose against this primitive.
The framework primitive that walks tree-shaped values at the wire boundary and substitutes elision markers for sensitive or large slots. Consumed by every tool that emits wire data (the off-box error-monitor forwarders, the Xray-MCP / re-frame2-pair-mcp / story-mcp servers per Tool-Pair.md, the on-box dev panels). The walker is the single normative emission site for the :rf/redacted sensitive sentinel and the :rf.size/large-elided marker; per-tool reimplementation is prohibited.
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
elide-wire-value |
Fn | (elide-wire-value v opts) → v or an elision-marker substitution. opts is a map: {:rf.size/include-large? <bool> :rf.size/include-sensitive? <bool> :rf.size/include-digests? <bool> :rf.size/threshold-bytes <int> :path [...] :frame <frame-id>}. Defaults: both include-* flags false (maximum elision); :rf.size/threshold-bytes falls back to (rf/configure! {:elision ...}) then 16384. Walks v consulting [:rf.runtime/elision :declarations] and [:rf.runtime/elision :sensitive-declarations] of the named frame's runtime-db; substitutes :rf/redacted for sensitive slots and :rf.size/large-elided markers for large slots. Composition rule (normative): when both predicates match the sensitive drop wins — the size marker is suppressed because it would leak :path / :bytes / :digest. Per 009 §Size elision in traces and Spec-Schemas §:rf/elision-marker. |
v1 | tooling | 009 |
Commit-plane declaration path (EP-0025). The [:rf.runtime/elision] registry has exactly two slots: :declarations (:large? paths) and :sensitive-declarations (:sensitive? paths). Durable app-db classification rides the four commit-plane classification effects — a reg-event returns :large / :sensitive (or :clear-large / :clear-sensitive) alongside :db, installed by re-frame.elision/apply-classification-effects under :source :effect. A reg-app-schema {:large? true} / {:sensitive? true} slot prop is not a route into this registry: schemas describe shape and validation, not durable app-db egress policy (the schema's :sensitive? still drives schema-validation-failure-trace redaction, and machine / resource :data-schema per-slot props still classify owner-local data). The registry's declaration readers (re-frame.elision/declarations / sensitive-declarations) and the derived-tree value-match arms are reachable through the re-frame.elision home namespace; the user-facing façade exposes only elide-wire-value and project-egress. Per 015 §Durable app-db — the four commit-plane effects and implementation/core/src/re_frame/elision.cljc L1-16.
Record-level egress projection (EP-0015 / Spec 015)¶
Cross-reference: 015 §Projection is the normative home;
project-egressis the public, record-level boundary primitive layered overelide-wire-value. The six-member:rf.egress/*profile enum is the named-boundary vocabulary; the boolean:rf.size/*flags remain the advanced override layer beneath it (EP-0015 §10/§11).
rf/project-egress is the required projection step before any off-box sink. It dispatches on a record's :kind (the :rf.observe/* record kinds) to a private per-kind projector — only project-egress is public (EP-0015 issue 2: the name names the boundary, not a record kind) — and delegates every tree-shaped slot to rf/elide-wire-value. A profile resolves to a :rf.size/* opt-set; an explicit :rf.size/* boolean composes on top (the override wins).
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
project-egress |
Fn | (project-egress record-or-value) / (project-egress record-or-value opts) → a value safe to ship under the resolved profile. opts conforms to :rf/project-egress-opts: {:rf.egress/profile <closed six-member enum> :frame <frame-id> :path [...] :rf.size/include-sensitive? <bool> :rf.size/include-large? <bool> :rf.size/include-digests? <bool> :rf.size/threshold-bytes <int> :as-of-epoch <epoch>}. Dispatches on a record's :kind (:rf.observe/handled-event / :rf.observe/error) to a private per-kind projector, falling back to walking a kindless input as a tree-shaped value (the direct-read path); delegates tree-shaped slots to elide-wire-value. An :rf.observe/* record is frame-bearing: when opts omit :frame, the record's own top-level :frame (its owning frame) is seeded as the governing frame; an explicit :frame opt wins (override). Profile resolves to a :rf.size/* opt-set; explicit :rf.size/* booleans compose on top (override wins). Unknown profile raises :rf.error/unknown-egress-profile (closed enum). Fail-closed when no frame is known from the opt, the record, or the carried scope (no :rf/default synthesis). Per 015 §project-egress. |
v1 | tooling | 015 |
Frame-owned observability sinks (EP-0015 §9 / Spec 015)¶
Cross-reference: 015 §Frame-owned observability sink policy is the normative home. The normal production observability story (Datadog / Sentry / Honeycomb): an app declares a sink under a frame's
:observabilityconfig ({:handled-events [{:sink <id> :rf.egress/profile … :opts {…}}] :errors [...]}) and registers the concrete sink fn against that<id>withrf/register-observability-sink!. The runtime routes one:rf.observe/handled-eventrecord per processed event and one:rf.observe/errorrecord per:rf.error/*site throughrf/project-egress— under the owning frame's classification and the entry's egress profile (default:rf.egress/off-box-observability) — to the declared sink. Sinks consume already-projected records only (no sink-local redaction). Always-on (survives:advanced+goog.DEBUG=false). The lower-level corpus-wide event-emit / error-emit registries (the:events/:errorsstreams ofregister-listener!) remain for advanced integration. Routing is fail-closed: an unresolved frame or absent:observabilitypolicy routes nothing (no:rf/defaultsynthesis), and a throwing sink is isolated from its siblings.
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
register-observability-sink! |
Fn | (register-observability-sink! sink-id f) — register the concrete observability sink fn f under the keyword sink-id the frame's :observability {:sink <sink-id> …} entry names. f receives one already-projected :rf.observe/handled-event / :rf.observe/error record (projected under the owning frame's classification + the entry's egress profile); its return value is ignored. Re-registering the same id replaces. Returns sink-id. The framework ships no Datadog / Sentry client (EP-0015 Non-Goals); the sink fn is an app / integration-library concern. Always-on. Per 015 §Frame-owned observability sink policy. |
v1 | tooling | 015 |
unregister-observability-sink! |
Fn | (unregister-observability-sink! sink-id) → nil |
v1 | tooling | 015 |
DOM source-coord annotations (mandatory)¶
Per Spec 006 §Source-coord annotation and Tool-Pair §Source-mapping, every adapter whose host has a DOM-attribute concept MUST inject data-rf2-source-coord="<ns>:<sym>:<line>:<col>" on the rendered root DOM element of each registered view. Format and exemptions (Fragments, non-DOM roots) are documented in Spec 006 §Source-coord annotation. Annotation is gated on interop/debug-enabled? (the CLJS mirror of goog.DEBUG); production :advanced builds elide the attribute via dead-code elimination — there is no DOM-bytes cost in shipped bundles. On the JVM the same annotation is stamped at the reg-view* registration boundary — a debug-gated wrapper on the stored :handler-fn (re-frame.views.jvm-source-coord-annotation), the server-side twin of the CLJS substrate wrappers — not by the SSR emitter, which carries no annotation logic and merely serialises the hiccup the registration boundary already annotated; see Spec 011 §Source-coord annotation under SSR.
Error contract¶
Errors are emitted as structured trace events with :op-type :error (or :warning / :info / :fx / :flow / :frame) and a per-category :operation keyword. The complete normative catalogue — every :rf.error/*, :rf.warning/*, :rf.fx/*, :rf.cofx/*, :rf.ssr/*, :rf.epoch/*, :rf.flow/*, :rf.http/*, :rf.http.interceptor/*, :rf.frame/*, and :rf.route.nav-token/* event the runtime emits — lives at 009 §Error event catalogue (single source of truth for category names, :op-type discriminator, trigger conditions, default :recovery, and :tags payload keys). Per-category Malli :tags schemas are canonicalised at Spec-Schemas §Per-category :tags schemas — one schema per catalogue row.
Recent additions consumers should be aware of: :rf.ssr/version-mismatch, :rf.ssr/schema-digest-mismatch, :rf.ssr/compatibility-check-skipped (the SSR hydration compatibility-check trio,), and :rf.cofx/skipped-on-platform (the platform-gating mirror of :rf.fx/skipped-on-platform). The catalogue at 009 is the single source of truth — do not duplicate the table here.
Per-Spec emit-sites: 002-Frames, 005-StateMachines, 006-ReactiveSubstrate, 010-Schemas, 011-SSR, 012-Routing, 013-Flows, 014-HTTPRequests, Tool-Pair. Each catalogue row's "Per [N]" cross-link names the owning Spec section.
Privacy (Spec 009 §Privacy / sensitive data in traces)¶
Cross-reference: see Security.md §Privacy / secret handling for the framework-wide pattern-level posture; the trust-boundary catalogue lives in Security.md. The cross-artefact inventory + composition order (every privacy surface in
re-frame.core,re-frame.http,re-frame.schemas,re-frame.epoch,tools/mcp-base, with the data-flow from handler exit to off-box wire) lives in Privacy.md. The public classification model is the four commit-plane classification effects (:sensitive/:large/:clear-sensitive/:clear-large, returned by areg-eventalongside:db) for durable app-db (see 015 §Durable app-db — the four commit-plane effects) and registration-owned:sensitivepayload classification onreg-event/reg-sub/reg-flow, projected at trust boundaries byproject-egressand the:rf.egress/*profiles. The imperativeadd-marks/set-markspath-marks API, there-frame.marksnamespace, the frame:sensitive {:app-db …}annotation, and the positionalredact-interceptorare all removed; the marks projection substrate lives in the marks-freere-frame.classification/re-frame.elisionengine.
Per Spec 009 §Privacy the runtime stamps :sensitive? true at the top level of every trace event emitted inside the scope of a handler whose declared path overlap classifies sensitivity. (The legacy handler-meta :sensitive? annotation has been removed; sensitive-data marking is path-based per the data-classification mechanism in Spec 015.) Framework-published trace consumers (Sentry/Honeybadger forwarders, re-frame2-pair server, Xray, Story, story-mcp, re-frame2-pair-mcp) MUST default-drop the stamped events at their egress boundary.
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
sensitive? |
Fn | (sensitive? trace-event) → boolean. True iff trace-event is a map carrying :sensitive? true at the top level (not under :tags). The framework-published predicate every consumer composes against — replaces per-consumer reimplementations of the same five-token check. |
v1 | tooling | 009 |
Spec-internal schemas¶
Per Spec-Schemas.md, the spec's own runtime shapes are described as Malli schemas registered at runtime. These are the conformance contract an implementation validates against.
| Schema | Describes | Spec |
|---|---|---|
:rf/dispatch-envelope |
Internal envelope wrapping every dispatch | 002 |
:rf/dispatch-opts |
The user-facing opts map for dispatch / dispatch-sync / subscribe |
002 |
:rf/registration-metadata |
Common metadata-map shape across reg-* |
001 / 010 |
:rf/effect-map |
Return value of reg-event handlers — closed: #{:db :rf.db/runtime :fx} (:rf.db/runtime reserved by convention for framework authority; app handlers use only :db / :fx) |
002 |
:rf/trace-event |
Universal trace event shape | 009 |
:rf/error-event |
Refinement of :rf/trace-event for :op-type :error / :warning (unified error/warning envelope) |
009 |
:rf/handler-body-dsl |
Conformance corpus handler-body DSL (host-agnostic event/sub bodies; small-DSL grammar) | 008 / Spec-Schemas |
:rf/transition-table |
State-machine transition table grammar | 005 |
:rf/machine-snapshot |
Runtime snapshot of a machine instance | 005 |
:rf/hydration-payload |
Wire format for SSR hydration | 011 |
:rf/response |
HTTP-response accumulator owned by the request frame during SSR | 011 |
:rf.server/cookie |
Structured-cookie shape for :rf.server/set-cookie / :rf.server/delete-cookie |
011 |
:rf/head-model |
SSR head/meta data model (title, meta, link, json-ld, html/body attrs) | 011 |
:rf/public-error |
Sanitised, client-safe projection of an internal error trace event | 011 |
:rf.fx.server/set-status-args / :rf.fx.server/set-header-args / :rf.fx.server/append-header-args / :rf.fx.server/set-cookie-args / :rf.fx.server/delete-cookie-args / :rf.fx.server/redirect-args / :rf.fx.server/safe-redirect-args |
Args of standard :rf.server/* SSR fx |
011 |
:rf/frame-meta |
Returned by (frame-meta frame-id) |
002 |
:rf/variant |
Story-variant artefact contract (post-v1 lib) — variants are data, no fn-valued slots | 007 |
:rf/epoch-record |
Per-frame epoch snapshot record (Tool-Pair) | Tool-Pair |
:rf.fx/dispatch-args |
Args of standard :dispatch fx (and :raise, same shape) |
002 / 005 |
:rf.fx/dispatch-later-args |
Args of standard :dispatch-later fx |
002 |
:rf.fx/http-args |
Args of :http fx (user-owned recommendation) |
Pattern-RemoteData |
:rf.fx.nav/push-url-args |
Args of :rf.nav/push-url fx |
012 |
:rf.fx.nav/replace-url-args |
Args of :rf.nav/replace-url fx |
012 |
:rf.fx.nav/scroll-args |
Args of :rf.nav/scroll fx |
012 |
:rf.fx.nav/capture-scroll-args |
Args of :rf.nav/capture-scroll fx |
012 |
:rf.fx/with-nav-token-args |
Args of :rf.route/with-nav-token fx wrapper |
012 |
:rf.fx/spawn-args |
Args of :rf.machine/spawn fx (the canonical actor-lifecycle fx-id; emitted from any event handler's :fx) |
005 |
:rf.fx/managed-args |
Args of :rf.http/managed fx (request envelope, decode, accept, retry, timeout-ms, on-success/on-failure, request-id, abort-signal) |
014 |
:rf.fx/managed-abort-args |
Args of :rf.http/managed-abort fx (request-id) |
014 |
:rf.http/reply |
Canonical reply envelope {:status :ok :value v …} / {:status :error :error {:kind <:rf.http/*> …} …} / {:status :cancelled :error {:kind :rf.http/aborted …} …} appended to the reply target (:reply-to unified, or :on-success/:on-failure split sugar) |
014 |
:rf/route-rank |
Structural-rank tuple for route-precedence sorting | 012 |
:rf/pending-navigation |
Pending-navigation slot when :can-leave guard rejects |
012 |
:rf/elision-registry |
Per-frame size-elision declaration registry in the reserved runtime-db child [:rf.runtime/elision] |
009 |
:rf/elision-marker |
Wire shape rf/elide-wire-value substitutes for an elided large value (:rf.size/large-elided) |
009 |
:rf/project-egress-opts |
The opts map rf/project-egress accepts (:rf.egress/profile + advanced :rf.size/* overrides) |
015 |
Schemas are open by default (consumers tolerate unknown keys; producers grow shapes additively); :closed true is opt-in at boundary-validation sites and on the effect-map.
Testing¶
The testing surface lives across three namespaces. re-frame.core carries the production primitives that double as testing entry points (make-frame, with-frame, with-new-frame, dispatch-sync, with-fx-overrides, app-db-value, compute-sub, machine-transition, sub-topology). re-frame.test-support ships the test-only fixture machinery and test-flavoured helpers. re-frame.test-helpers ships the view-assertion helpers (hiccup-walk + testid authoring). re-frame.test-support does not re-export from re-frame.core — a test file requires both [re-frame.core :as rf] and [re-frame.test-support :as ts], and additionally [re-frame.test-helpers :as th] for view-assertion tests. See 008-Testing.md for fixtures, framework adapters, and re-frame-test compatibility.
| API | M/Fn | Signature | Status | Tier | Spec | Notes |
|---|---|---|---|---|---|---|
assert-path-equals |
Fn | (assert-path-equals path expected-val) / (assert-path-equals path expected-val opts) |
v1 | testing | 008 | Path-form sync assertion. Mismatch fires clojure.test/is-style failure via do-report. Lives in re-frame.test-support. Mirrors the :rf.assert/path-equals event used inside a Story :script block — same name root so the fn-side and event-side are navigable without a translation table. The wider sibling event family (:rf.assert/sub-equals, :rf.assert/state-is, :rf.assert/dispatched?, :rf.assert/no-warnings, :rf.assert/effect-emitted, :rf.assert/path-matches) lives in 007 §Play functions; runner and reporting channel differ. Choose by test surface: assert-path-equals from a deftest body, :rf.assert/path-equals from a story variant's :script vector. |
poll-until |
Fn | (poll-until pred) / (poll-until pred opts) |
v1 | testing | 008 | Bounded-deadline poll. JVM: synchronous — returns the truthy value, throws ex-info carrying :rf.error/id :rf.error/poll-until-timeout (the canonical discriminator, per Spec 009) on timeout. CLJS: returns a js/Promise resolving with the truthy value or rejecting on timeout. Opts: :timeout-ms (default 2000), :interval-ms (default 5), :label. Lives in re-frame.test-support. |
with-fx-overrides |
M | (with-fx-overrides {fx-id -> override, …} body+) |
v1 | testing | 002, 008 | Lexical-scope :fx-overrides binding. Every dispatch / dispatch-sync inside the body merges the supplied map into its envelope's :fx-overrides. Precedence: per-call opt > lexical with-fx-overrides > per-frame :fx-overrides. Composes with with-frame. Lives in re-frame.core. |
compute-sub |
Fn | (compute-sub query-v db) |
v1 | testing | 008 | Pure sub computation against an app-db value. Lives in re-frame.core. |
snapshot-registrar / restore-registrar! / make-reset-runtime-fixture |
Fn | per docstring | v1 | testing | 008 | Fixture machinery. make-reset-runtime-fixture builds the :each fixture (registrar snapshot/restore + per-process reset); the raw snapshot-registrar / restore-registrar! pair composes hand-rolled fixtures. Lives in re-frame.test-support. |
with-trace-recorder! |
M | (with-trace-recorder! [recs-sym opts?] body+) |
v1 | testing | 008 | Bracket body with a fresh trace-tooling listener that accumulates matching trace events into an atom bound to recs-sym; unregisters in a finally. Opts: :pred, :shape (:flat / :by-op), :key. Lives in re-frame.test-support. |
Testing — view-assertion helpers¶
re-frame.test-helpers ships the hiccup-walk view-assertion surface — call the view-fn directly, walk the returned hiccup, assert on content or invoke a handler. JVM-runnable; no JSDOM, no React, no act(). Pairs with render-to-string (the HTML-string view-test path per Spec 011): hiccup-walk for structure / handler assertions, render-to-string for HTML-markup assertions. Per 008-Testing §View-assertion helpers.
| API | M/Fn | Signature | Status | Tier | Spec | Notes |
|---|---|---|---|---|---|---|
expand-tree |
Fn | (expand-tree tree) → tree |
v1 | testing | 008 | Recursively expand fn-components and Form-3 class components inside a hiccup tree. After expansion every vector's first element is a keyword tag or a non-component value. Lives in re-frame.test-helpers. |
attrs |
Fn | (attrs node) → map? |
v1 | testing | 008 | Return the attrs map of a hiccup node, or nil. Lives in re-frame.test-helpers. |
children |
Fn | (children node) → vector |
v1 | testing | 008 | Return the child elements — everything after the tag (and optional attrs map). Lives in re-frame.test-helpers. |
text-content |
Fn | (text-content node) → string |
v1 | testing | 008 | Recursively collect string leaves under node and join. Numbers coerce to strings; nils are skipped. Lives in re-frame.test-helpers. |
extract-handler |
Fn | (extract-handler node event-key) → fn? |
v1 | testing | 008 | Return the value of event-key from node's attrs map, or nil. Lives in re-frame.test-helpers. |
find-by-attr |
Fn | (find-by-attr tree attr val) → node? |
v1 | testing | 008 | First hiccup node whose attrs map carries attr == val, or nil. Generic over the attribute keyword (:data-testid, :data-test, :id, custom). Lives in re-frame.test-helpers. |
find-all-by-attr |
Fn | (find-all-by-attr tree attr val) → vector |
v1 | testing | 008 | Every matching node, in depth-first order. Lives in re-frame.test-helpers. |
find-by-attr-prefix |
Fn | (find-by-attr-prefix tree attr prefix) → vector |
v1 | testing | 008 | Every node whose attr value (a string) STARTS with prefix. Non-string attr values do not match. Lives in re-frame.test-helpers. |
find-by-testid |
Fn | (find-by-testid tree test-id) → node? |
v1 | testing | 008 | Convenience over find-by-attr keyed on :data-testid. Lives in re-frame.test-helpers. |
find-all-by-testid |
Fn | (find-all-by-testid tree test-id) → vector |
v1 | testing | 008 | Convenience over find-all-by-attr keyed on :data-testid. Lives in re-frame.test-helpers. |
find-by-testid-prefix |
Fn | (find-by-testid-prefix tree prefix) → vector |
v1 | testing | 008 | Convenience over find-by-attr-prefix keyed on :data-testid. Lives in re-frame.test-helpers. |
invoke-handler |
Fn | (invoke-handler node event-key & args) → any |
v1 | testing | 008 | Find the handler under event-key on node and call it with args. Returns the handler's return value. THROWS when node is not a hiccup vector, the node has no attrs map, or no handler is registered — the throwing failure mode is deliberate (a missing handler is almost always a test bug). Lives in re-frame.test-helpers. |
testid |
Fn | (testid id) / (testid id extra) → map |
v1 | testing | 008 | Build an attrs map carrying :data-testid id. The 2-arity merges extra into the map; :data-testid always wins on collision. Authoring helper at the view call site. Lives in re-frame.test-helpers. |
The single-frame view test is a composition, not a bespoke fixture: re-frame.test-support/make-reset-runtime-fixture (an :adapter + optional :init-fn) seats the ambient :rf/default frame and runs the install thunk; the walkers above call the root view fn directly and assert on the returned hiccup; re-frame.test-support/poll-until covers the async case. Per 008-Testing §Single-frame view test.
Standard interceptors¶
Under EP-0022 the public interceptor-authoring surface is reg-interceptor (§Registration), and event/frame :interceptors chains carry interceptor references (a bare keyword id or [id arg]), never inline values. The v2 framework-standard interceptor surface is exactly one interceptor — :rf.interceptor/path — referenced as [:rf.interceptor/path <path-vector>]. There is no public rf/path value constructor and no standard unwrap (EP-0022 §No standard unwrap — handler destructuring, or a project-registered interceptor for intentional chain-wide event reshaping, replaces it). The earlier line (keep specific-work helpers, drop trivial (->interceptor :before f) ones) narrows to path-only because path is coupled to app-db commit no-op semantics and justifies the :factory mechanism. Five v1 interceptors removed: debug, trim-v, on-changes, enrich, after (per MIGRATION §M-21).
inject-cofx / inject-cofx* — the v1 coeffect-injection interceptors — are removed (no alias). They are not on the public re-frame.core facade and carry no API-manifest row (a removed surface is not part of the canonical public API): there is no public inject-cofx var to call. Coeffect delivery is no longer a chain member: a handler declares :rf.cofx/requires on its registration metadata and the value-returning supplier's result arrives flat in the coeffects map (§Registration — reg-cofx, 001 §:rf.cofx/requires). The removal alarm survives internally: a stale call still raises the always-on hard error :rf.error/inject-cofx-removed naming :rf.cofx/requires.
| Surface | Shape | Tier | Purpose |
|---|---|---|---|
[:rf.interceptor/path <path-vector>] |
interceptor reference (the one standard interceptor; the canonical :factory consumer) |
front-porch | Focus a handler on an app-db sub-slice: :before stages the focused slice as :db, :after widens the returned slice back into full app-db. Preserves the frame-commit identical? no-op — an unchanged focused slice widens back to the original app-db object, not an assoc-in allocation (002 §Standard :rf.interceptor/path). A non-vector/malformed path arg is :rf.error/path-interceptor-bad-path. |
reg-interceptor |
M (registrar) | front-porch | The public application-authoring form for any non-standard interceptor — analytics, logging, validation, ad-hoc context manipulation. The resulting interceptor is named, addressable, queryable, and referenced by id from chains. Rowed in §Registration. |
->interceptor* |
Fn (internal lowering constructor) | advanced | The framework-internal constructor that lowers a descriptor into an executable chain entry ((->interceptor* & {:keys [id before after source-coord]})). NOT a public application-authoring API and MUST NOT appear in a public event/frame chain (EP-0022). |
The retired v1 public interceptor-authoring helpers and their replacements:
| Removed / retired API | Replaced by |
|---|---|
path (public value constructor) |
the standard reference [:rf.interceptor/path <path-vector>] (EP-0022) |
unwrap |
handler destructuring (the M-19 canonical map-payload form), or a project-registered interceptor for intentional chain-wide event reshaping (EP-0022 — no standard unwrap) |
->interceptor (public authoring macro) |
reg-interceptor (->interceptor survives only as the internal lowering constructor) |
debug |
Trace surface (009) + 10x / re-frame-pair |
trim-v |
Canonical map-payload call shape (M-19) |
on-changes |
Flows (Spec 013) |
enrich |
Flows (derived state) / :schema (validation) / a project-registered interceptor (escape hatch) |
after |
Registered fx (:fx [[:my-fx ...]]) for side-effects; a project-registered interceptor for context-shaped work |
reg-flow / clear-flow (Spec 013)¶
reg-flow is rowed canonically in §Registration. Per the canonical 3-slot grammar (rf2-bqstzr) it is (reg-flow flow-id metadata derive-fn): flow-id first, the pure :derive fn last, and metadata carrying :inputs / :output-path (both REQUIRED) plus optional :doc / :schema / EP-0025 classification keys and the :frame mounting key (which selects the owning frame — the metadata middle slot, like every other 3-slot reg-* surface). clear-flow — whose primary arg IS the flow-id — keeps its trailing opts map for the frame override: (clear-flow id) / (clear-flow id opts). clear-flow is rowed canonically in §Clearing registrations; it deregisters the flow from the named frame and dissoc-ins its :output-path from that frame's app-db only (per Spec 013 §Frame-scoping).
Frame-destroy teardown. destroy-frame! releases every per-frame piece of flow state (the destroyed frame's slot in the per-frame flow registry, its last-inputs dirty-check rows, and its pending abandoned-output-paths) per Spec 013 §Frame-destroy teardown. Under single-store there is no registrar-slot prune (the old double-store "last owner" unregister / realign is gone — the :flow registrar kind is RESERVED-but-empty). Sibling frames' state is preserved.
Flow-eval failures in production. A throw inside a flow's :derive fn surfaces as :rf.error/flow-eval-exception on the always-on error-emit substrate — registered error-emit callbacks (the :errors stream of register-listener!) fire under CLJS :advanced + goog.DEBUG=false. The error is NOT trace-only. Per Spec 013 §Failure semantics rule 4 and 009 §Production builds.
Reserved fx-ids for runtime flow management via :fx:
| Name | Kind | Signature | Status |
|---|---|---|---|
:rf.fx/reg-flow |
Reserved fx-id | [:rf.fx/reg-flow [flow-id metadata derive-fn]] — register a flow at runtime via :fx (the same 3-slot triple reg-flow takes; the dispatching frame threads through as the :frame metadata key) |
v1 (optional capability) |
:rf.fx/clear-flow |
Reserved fx-id | [:rf.fx/clear-flow id] — clear a registered flow via :fx |
v1 (optional capability) |
Interceptor / context plumbing¶
The interceptor context accessors get-coeffect / assoc-coeffect / get-effect / assoc-effect are removed from the public re-frame.core façade and carry no API-manifest row (a removed surface is not part of the canonical public API). Post-EP-0017/EP-0022 they lost their audience — the setters had zero callers, the getters one. The intended interceptor model is to author with reg-interceptor and let the :before / :after fns receive and return the context map directly: read coeffects with (get-in ctx [:coeffects k]) and write effects with (assoc-in ctx [:effects k] v). The underlying re-frame.interceptor/{get,assoc}-{coeffect,effect} fns remain in their owning namespace as framework-internal context helpers; they are not a public surface. See §Removed / not shipped.
Lifecycle / utility¶
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
init! |
Fn | (init! adapter-map) — idempotent boot. Required arg: the adapter spec map. Each adapter ns exports an adapter Var; consumers require the ns and pass the Var, e.g. (rf/init! reagent/adapter). Calling (init!) with no args raises a language-level ArityException at compile/load time ( — the no-arg arity was cut so the missing-adapter mistake surfaces before runtime). Calling (init! nil) or (init! :reagent) raises :rf.error/no-adapter-specified at runtime. Per 006 §Adapter selection at boot. Installs adapters and runtime capabilities only — it creates no frame (EP-0002: no auto :rf/default); the app mounts its frame at the root with frame-root (ENSURE) — or constructs one programmatically with make-frame |
v1 | front-porch | 006 |
init-platform |
Fn | (init-platform p) — set the host-wide active-platform marker (:server or :client). The runtime tracks the active platform so reg-fx/reg-cofx :platforms metadata can gate execution (per 011 §Effect handling on the server). CLJS hosts default to :client, JVM hosts to :server; call at boot to override (CLJS-on-Node SSR runtime: (rf/init-platform :server); JVM-runnable test simulating browser: (rf/init-platform :client)). Per-frame :config :platform (set by the :ssr-server preset) still wins over this host-wide marker. p must be :server or :client; anything else raises :rf.error/invalid-platform. Idempotent / re-callable. Sibling boot call to init! — independent (adapter install vs platform marker). |
v1 | advanced | 011 |
install-adapter! |
Fn | (install-adapter! adapter-map) — must be called before any frame is created. Lower-level than init!; most consumers call init! instead |
v1 | advanced | 006 |
destroy-adapter! |
Fn | (destroy-adapter!) — tear down the exact installed adapter generation. This is a one-way terminal boundary: calls the adapter spec's :dispose-adapter! fn (if present), attempts all owned cleanup, finally clears only the generation it claimed, and leaves adapter-disposed? true even when the primary cleanup failure is rethrown. Later cleanup failures remain secondary diagnostic evidence. A fresh adapter may install afterward; stale finalization never clears a replacement. Per Conventions §Tear-down verb axis — destroy- cluster (lifecycle boundary; symmetric with install-adapter! and with destroy-frame!). The adapter-spec map key :dispose-adapter! (an internal contract slot adapters implement) is unchanged. |
v1 | advanced | 006 |
current-adapter |
Fn | (current-adapter) → discriminator keyword (:rf.adapter/reagent / :rf.adapter/reagent-slim / :rf.adapter/uix / :rf.adapter/hicasso / :rf.adapter/plain-atom / :rf.adapter/ssr / :custom, plus the retired-but-reserved :rf.adapter/freehand / :rf.adapter/ui that nothing produces since 2026-08-16) or nil when no adapter is installed. Answers "what substrate am I on?" — predicate / branch code. For the spec map (fn handles, identity checks), use current-adapter-spec. |
v1 | advanced | 006 |
current-adapter-spec |
Fn | (current-adapter-spec) → the installed adapter spec map (the value passed to (rf/init! ...)) or nil when no adapter is installed. Answers "give me the adapter fns to call" — tools, routing, identity checks across the install/dispose lifecycle. For the discriminator keyword, use current-adapter. |
v1 | advanced | 006 |
adapter-disposed? |
Fn | (adapter-disposed?) → true iff terminal teardown of the most recent installed generation has been claimed and no subsequent install-adapter! has fired. Cleanup success is not implied: it remains true when destruction rethrows a cleanup failure. false for never-installed (fresh process) and after a fresh install. Read-only — the breadcrumb is owned by the install / destroy pair. Use to distinguish :rf.error/no-adapter-installed (fresh process) from :rf.error/adapter-disposed (torn down). Per 006 §Disposed-vs-never-installed. |
v1 | advanced | 006 |
configure! |
Fn | (configure! {key opts, …}) — runtime config from a single nested map; non-map arg fails loud, missing top-level key leaves that subsystem untouched, unknown top-level key silently no-ops. Key vocabulary in §Configure keys. One of three orthogonal configuration surfaces per Conventions §Configuration surfaces (configure! for process-level data knobs; set-! / install-! for adapter-pluggable hooks; per-frame metadata for frame-scoped overrides). |
v1 | front-porch | — |
Feature inspection¶
re-frame2's optional capabilities ship as separate Maven artefacts (day8/re-frame2-<feature>) whose implementation namespaces core reaches through the late-bind hook registry at call time (per Conventions §Facade re-export, artefact require). The upside is bundle-isolation — an app that omits a feature does not carry its code. The downside this front-porch closes: the late-binding is otherwise invisible, so a developer who forgets to :require the impl artefact calls a re-exported fn that exists and is met with an opaque artefact-missing error. These three surfaces make the optional-feature inventory self-explaining.
The known optional features are :schemas, :machines, :routing, :flows, :http, :ssr, :epoch (the closed per-feature split set per Conventions §Artefact tiers).
These three fns ship to production. They are runtime queries, not dev-time instrumentation, so — unlike the trace / epoch surfaces — they are NOT gated on interop/debug-enabled? and do NOT elide under :advanced + goog.DEBUG=false. A production caller may legitimately probe (rf/feature-loaded? :routing) before taking a routing-dependent path. The feature→coordinate mapping is static data in the always-loaded re-frame.core facade (a plain table of {:feature {:maven … :require … :spec …}} strings), never a live :require reaching into the optional impl namespaces — a live reach-in would create a hard facade→optionals reference that pulls every optional artefact into every production bundle, breaking bundle-isolation. feature-loaded? detects presence by a pure keyword lookup in the always-loaded late-bind hooks atom against a representative key the impl publishes at ns-load — no reach into the optional namespace.
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
features |
Fn | (features) → map of every optional feature keyword to its inspection entry: the static coordinate data (:maven / :require / :spec) merged with the live :loaded? boolean. E.g. {:epoch {:maven "day8/re-frame2-epoch" :require "re-frame.epoch" :spec "Tool-Pair (Time-travel / epoch)" :loaded? true} …}. Ships to production (NOT elided). |
v1 | advanced | — |
feature-loaded? |
Fn | (feature-loaded? feature) → true when the feature's impl artefact is on the classpath, false otherwise (including for an unknown feature keyword). Pure keyword lookup against the always-loaded late-bind hooks atom — no require into the optional namespace. Ships to production (NOT elided). |
v1 | advanced | — |
require-feature! |
Fn | (require-feature! feature) → true when the feature is loaded; otherwise throws :rf.error/feature-not-loaded carrying the EXACT copy-pasteable Maven coordinate (:maven) + require namespace (:require-ns) and a :reason string naming both (an unknown feature keyword throws :rf.error/unknown-feature with the :known set). Use as a self-explaining early guard at the top of feature-dependent code. Ships to production (NOT elided). |
v1 | advanced | — |
Artefact-missing errors carry the require. This front-porch is paired with a hard rule: every artefact-missing error in the framework — including the existing late-bind facade throws (
:rf.error/<feature>-artefact-missing, raised viare-frame.late-bind/require-fn!from there-frame.core-<feature>wrappers) — carries the exact copy-pasteable Maven coordinate and the namespace to require at app boot in its:reasonslot. The named pattern is documented once at Conventions §Facade re-export, artefact require.
Configure keys¶
Runtime configuration is uniformly via (rf/configure! {<key> <opts>, …}) — a single nested map keyed by the top-level keys enumerated here. The argument MUST be a map (a non-map argument fails loudly); a missing top-level key leaves that subsystem untouched; a present key delegates to that subsystem's configurator in table order (:epoch-history, :trace-buffer, :elision) preserving its slot-merge semantics; an unknown top-level key is a silent no-op (closed-and-additive). Keys are plural-noun-shaped; opts are an open map of per-key settings.
(rf/configure! {:epoch-history {:depth 100}
:trace-buffer {:events-retained 25}
:elision {:rf.size/threshold-bytes 8192}})
| Key | Opts shape | Default | Status | Spec |
|---|---|---|---|---|
:epoch-history |
{:depth N :trace-events-keep N :redact-fn (fn [record] …)} — :depth non-negative integer (0 disables the ring); :trace-events-keep non-negative integer caps raw :trace-events retention (per Security §Epoch privacy posture); :redact-fn is fn? or nil — the advanced PROJECTION-SIDE override invoked once per record at the off-box egress boundary (inside projected-record, after the frame/profile projection), NOT at build/storage time: the in-process ring buffer and every :epoch-stream listener deliver the RAW record (post-EP-0010 causal replay material — storage-side mutation was removed so restore-epoch! fidelity is never affected), with the :rf.epoch/sensitive? rollup computed at build-time from raw signals, throws caught and surfaced as :rf.warning/epoch-redact-fn-exception with fallback to the projected record. Per Tool-Pair §Time-travel. |
{:depth 50, :redact-fn nil} |
v1 (dev-only) | Tool-Pair |
:trace-buffer |
{:events-retained N} — non-negative integer event-slot count (one slot per event / pipeline run); 0 disables retention (the surface stays live) |
{:events-retained 50} |
v1 (dev-only) | 009 |
:elision |
{:rf.size/threshold-bytes N} — non-negative integer; 0 disables runtime auto-detect (only declared / schema entries elide) |
{:rf.size/threshold-bytes 16384} |
v1 | 009 |
Retired key. The earlier
:sub-cache {:grace-period-ms N}knob is gone. Sub-cache disposal is now synchronous on derefer-count → 0 (per 006 §Reference counting and disposal); there is no deferred-grace timer to configure.
SSR error-projection policy (:public-error-id, :dev-error-detail?) is not a configure key — it is per-frame metadata on the frame's :ssr map (see Conventions §Configuration surfaces bucket 3 and 011 §Server error projection). Different frames in the same process can carry different projector / dev-detail settings, so the natural lifetime is per-frame, not process-global.
Opts-key naming rule¶
The opts map for any configure key mixes two shapes — it encodes which contract owns the sub-key:
- Framework-owned semantic sub-keys use a namespaced keyword under a reserved
:rf.<area>/*sub-namespace (per Conventions §Reserved namespaces). The namespace identifies the cross-spec policy area the sub-key participates in — the same key shape appears verbatim wherever that policy is consumed, not only insideconfigure. Example::elisioncarries{:rf.size/threshold-bytes N}because:rf.size/threshold-bytesis the same per-call policy key consumed byrf/elide-wire-valueand the wire-elision walker (per Conventions §Reserved namespaces —:rf.size/*). The namespaced form makes the cross-surface identity grep-visible and prevents collision with adjacent per-knob settings. - Ergonomic per-knob sub-keys are unqualified bare keywords (
:depth,:trace-events-keep,:redact-fn). These sub-keys are local to a singleconfigurekey's opts map — they do not appear elsewhere in the framework's vocabulary, so a framework-owned namespace would add noise without adding identity. The bare form is the default at this leaf position; reach for it whenever the knob is unique to oneconfigurekey.
The discriminator is whether the sub-key names a cross-surface policy slot or a one-off knob. A sub-key earns a :rf.<area>/* namespace when it names a contract that lives in more than one place (:rf.size/threshold-bytes is read by :elision, by elide-wire-value, and by the MCP wire walker). A sub-key stays bare when it is local to its parent configure key (:events-retained under :trace-buffer and :depth under :epoch-history each tune one ring's slot count and live nowhere else in the vocabulary — separate knobs, no shared contract, so each stays bare).
New configure keys MUST apply the same rule: if a sub-key participates in a cross-spec policy area, qualify it under the area's reserved namespace; otherwise leave it bare. The rule is closed — there is no third shape (no :configure/depth, no :rf.configure/* prefix). A sub-key that would want a third shape is evidence the proposed knob is doing two things and should be split.
Fixed-and-additive¶
The configure-keys vocabulary is fixed-and-additive (Spec-ulation): existing keys cannot be renamed or removed; new keys are added by extending the table. Because configure! takes a single map and silently ignores unknown top-level keys, user code that wraps configure can pass a composed config value straight through — unknown keys no-op rather than throw.
Machines¶
Split between the v1 machine-as-event-handler foundation and the post-v1 re-frame.machines scaffolding library — see 005-StateMachines.md §Disposition. The machine is the event handler: a machine is registered as one reg-event whose body comes from make-machine-handler.
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
reg-machine |
M | (reg-machine machine-id machine-spec) / (reg-machine machine-id opts machine-spec) — registers a machine as an event handler. Walks the literal spec form at expansion time and co-locates per-element source on each :guards / :actions entry + a reference-site :source-coords on each :states-tree map node. The optional opts metadata map is the canonical Spec 001 MIDDLE slot; it carries an event-vector :schema — the machine + event-vector-schema shape. |
v1 | advanced | 005 |
reg-machine* |
Fn | (reg-machine* machine-id machine-spec) / (reg-machine* machine-id opts machine-spec) — plain-fn surface beneath the macro. No source-coord walking. The optional opts metadata map is the MIDDLE slot (uniform with the macro); it carries the event-vector :schema. |
v1 | advanced | 005 |
defmachine |
M | (defmachine name [docstring] spec) — def-shape that walks the literal spec at the definition site and co-locates per-element source onto the def'd value, for the def-then-register shape (defmachine m {…}) / (reg-machine :id m). Does not register. |
v1 | advanced | 005 |
make-machine-handler |
Fn | (make-machine-handler spec) → event-handler fn |
v1 | advanced | 005 |
machine-transition |
Fn | (machine-transition definition snapshot event) → re-frame.machines.result value (result/ok?; result/snap / result/fx read the snapshot + fx; result/fail carries result/info) |
v1 | advanced | 005 |
machines |
Fn | (machines) → vector of registered machine-ids |
v1 | tooling | 005 |
machine-meta |
Fn | (machine-meta machine-id) → registration metadata; :guards / :actions entries carry co-located :source-coords / :source-code and each :states-tree map node carries its own reference-site :source-coords when registered via the macro |
v1 | tooling | 005 |
machine-by-system-id |
Fn | (machine-by-system-id system-id) / (machine-by-system-id system-id {:frame target}) → spawned-machine id bound to system-id in the frame's [:rf.runtime/machines :system-ids] reverse index (or nil). The 2-arity takes an {:frame target} opts map (the frame-last positional spelling is retired to internal / test / tooling plumbing). Per 005 §Named addressing via :system-id. |
v1 | advanced | 005 |
:rf.machine/dispatch-to-system (fx) |
— | Named-addressing escape (registered globally by re-frame.machines). From a machine action's :fx, [:rf.machine/dispatch-to-system [system-id event]] resolves system-id through the frame's [:rf.runtime/machines :system-ids] reverse index and dispatches event to the bound actor (no-op when unbound); actions can't read app-db, so the fx is how an action messages a NAMED child. Zero in-repo consumers as of 2026-07-10; retained for XState v6 actor-system parity (systemId addressing). The redundant dispatch-to-system call-site FN twin was deleted. Per 005 §Cross-machine messaging by name. |
v1 | — (fx-id) | 005 |
:rf.machine/spawn (fx) |
— | Canonical actor-lifecycle fx (registered globally by re-frame.machines). Args per :rf.fx/spawn-args. |
v1 | — (fx-id) | 005 |
:rf.machine/destroy (fx) |
— | Canonical actor-destroy fx (registered globally by re-frame.machines). Args: an actor id. |
v1 | — (fx-id) | 005 |
:raise (fx) |
— | Reserved fx-id inside a machine action's :fx (machine-internal, routed pre-commit). Args: an event vector. |
v1 | — (fx-id) | 005 |
:final? / :output-key (state-node keys) |
— | :final? marks a leaf state as terminal — entering it auto-destroys the machine. :output-key (requires :final?) designates the child's :data slot reported back via the parent's :on-done. Capability axis :fsm/final-states. Per ; see 005 §Final states. |
v1 | — (spec key) | 005 |
:on-done (:spawn spec key) |
— | (fn [{:keys [data result]}] new-data) on the parent's :spawn map. Fires synchronously when the spawned child enters a :final? state; result is the child's :data slot named by the final state's :output-key (or nil). Returns the parent's new :data map. Per and. |
v1 | — (spec key) | 005 |
:child-machine (transition-table key) |
— | Declarative state-scoped child-machine binding. | post-v1 lib | — (spec key) | 005 |
machine->xstate-json |
Fn | (machine->xstate-json definition) → JSON |
post-v1 lib | tooling | 005 |
machine->mermaid |
Fn | (machine->mermaid definition) → string |
post-v1 lib | tooling | 005 |
Canonical descriptions (factory purity, spec keys, snapshot location, registration-time validation, etc.) in 005-StateMachines.md and Spec-Schemas.
v1 transition-table grammar subset is enumerated in 005 §Capability matrix; shape in Spec-Schemas §:rf/transition-table.
Standard registered subs (machines)¶
| Standard sub | Returns | Spec |
|---|---|---|
[:rf/machine <machine-id>] |
The machine's snapshot {:state :data :tags} (or nil if not yet initialised) |
005 |
[:rf.machine/has-tag? <machine-id> <tag>] |
true iff the machine's current snapshot's :tags set contains tag (false for an unknown / not-yet-initialised machine) |
005 |
The canonical machine read is the registered [:rf/machine machine-id] subscription vector — see 005 §Subscribing to machines. It is read like any other subscription — @(rf/subscribe [:rf/machine machine-id]), @(rf/subscribe [:rf.machine/has-tag? machine-id tag]) — and named projections chain off it with :<-. There is no named-read-sugar fn: a runtime-db framework read is a subscription vector, one grammar.
Story / variant / workspace library (post-v1)¶
See 007-Stories.md.
All Story surfaces are tooling (a Storybook-shaped dev surface — registration, execution, introspection — not application logic). Absorbs story F-8; see §Tiering of cross-tool surfaces.
| API | M/Fn | Signature | Status | Tier | Spec |
|---|---|---|---|---|---|
reg-story |
M | (reg-story id metadata) |
post-v1 lib | tooling | 007 |
reg-variant |
M | (reg-variant id metadata) |
post-v1 lib | tooling | 007 |
reg-workspace |
M | (reg-workspace id metadata) |
post-v1 lib | tooling | 007 |
reg-tag |
M | (reg-tag id metadata) |
post-v1 lib | tooling | 007 |
reg-decorator |
M | (reg-decorator id metadata) |
post-v1 lib | tooling | 007 |
reg-story-panel |
M | (reg-story-panel id metadata) |
post-v1 lib | tooling | 007 |
run |
Fn | (run target) / (run target opts) → promise/future of the unified run-result. target is a keyword (registered variant) OR a map (inline plan). The single execution verb. |
post-v1 lib | tooling | 007 |
is |
Fn | (is target) / (is target opts) → runs target and reports each assertion to clojure.test / cljs.test. JVM blocks (bounded by :timeout-ms, default 30000) and returns the run-result; CLJS returns the run promise. |
post-v1 lib | tooling | 007 |
explain |
Fn | (explain target) / (explain target opts) → the plan's :explain map (args-validation, sub-overrides, decorators, …) without running. |
post-v1 lib | tooling | 007 |
variants-with-tags |
Fn | (variants-with-tags tag-set) → seq of variant ids |
post-v1 lib | tooling | 007 |
snapshot-identity |
Fn | (snapshot-identity variant-id) → {:variant-id ... :content-hash "..."} |
post-v1 lib | tooling | 007 |
story-view |
Fn | (story-view variant-id) → hiccup |
post-v1 lib | tooling | 007 |
The public execution surface is exactly the three verbs run / is / explain (each accepts a registered-variant keyword OR an inline-plan map). run-variant / is-variant / run-plan / is-plan / watch-variant / reset-variant are implementation / migration vocabulary, NOT the P1 public surface — they are not rowed here. The unified run-result is the single execution-record boundary; read its verdict via result-status / result-passed? (:pass / :fail / :cannot-run / :error — there is no :passing? boolean). Canonical execution model: story spec 017 §Public execution API (the re-frame2-story library spec, the normative home for the verbs + run-result shape).
Removed / not shipped¶
These surfaces are removed or renamed — not part of the public projection and not a tier (the deprecated tier is reserved for surfaces still shipping while on the way out; pre-alpha carries none). This is a migration table: each row names what to use instead.
| API | What to do | Reference |
|---|---|---|
dispatch-with (master) |
Use (dispatch event {:fx-overrides {...}}) |
MIGRATION M-4 |
dispatch-sync-with (master) |
Use (dispatch-sync event {:fx-overrides {...}}) |
MIGRATION M-4 |
dispatch-to (proposed earlier) |
Use (dispatch event {:frame :todo}) |
002 |
subscribe-to (proposed earlier) |
Use (subscribe query-v {:frame :todo}) |
002 |
frame-dispatcher / bound-dispatcher / bound-subscriber (proposed earlier) |
Use (rf/capture-frame) (the keystone OPERATION BUNDLE — captures the frame at creation; safe during render and from async callbacks) |
002 |
bound-fn (CLJS macro) |
Use (rf/capture-frame) — the keystone OPERATION BUNDLE captures the frame and carries dispatch / subscribe across the boundary. |
002 |
frame-bound-fn (macro) / frame-bound-fn* (fn) |
DELETED from the facade (API-shrink #1, rf2-csbbwu) — capture-frame is the ONE public carry primitive. The frame-rebinding closure semantics (re-establish *current-frame* around an arbitrary already-held fn) survive internally as re-frame.frame/bind-fn for framework / test / tooling reach; not app-facing. |
002 |
frame-value->id (fn) |
DELETED from the facade (API-shrink #1, rf2-csbbwu) — every public surface accepts a frame VALUE or its id: the routing ops (dispatch / subscribe / app-db-value / frame-provider / …) normalize a value to its id interchangeably, and destroy-frame! accepts either but reads the value's exact-incarnation lifecycle authority (rf2-moftbs — a stale value no-ops against a same-id successor; a keyword is address-directed). Either way there is no need to unwrap a value to its id. |
002 |
dispatcher |
Use (:dispatch (rf/capture-frame)) or the dispatch injected in a reg-view body |
002 |
subscriber |
Use (:subscribe (rf/capture-frame)) or the subscribe injected in a reg-view body |
002 |
current-frame |
Renamed to current-frame-id (returns a frame-id keyword) |
002 |
get-frame-db |
Renamed to app-db-value (returns the app-db VALUE, a plain map) |
002 |
(dispatch frame event) / (dispatch-sync frame event) / (subscribe frame-id query-v) / (subscribe-once frame-id query-v) frame-first positional forms |
DELETED (API-shrink #1, rf2-csbbwu) — every sig is [payload] / [payload opts]; target an explicit frame via the trailing {:frame …} opt (a frame-id keyword or a live frame value). |
002 |
enable-performance-api-tracing! (proposed earlier) |
Performance-API instrumentation is gated on the compile-time re-frame.performance/enabled? goog-define, not a runtime toggle (see 009 §Performance instrumentation) |
009 |
add-trace-listener / remove-trace-listener (proposed earlier) |
Use register-listener! / unregister-listener! |
009 |
register-trace-listener / unregister-trace-listener (no-bang, proposed earlier) |
Renamed to register-listener! / unregister-listener! (bang form matches the side-effecting nature of listener registration) |
009 |
Bare [:my-view "args"] keyword-tagged hiccup |
Use the Var form [my-view "args"] (canonical) or [(rf/view :my-view) "args"] for late-binding by id |
Conventions |
h macro (proposed earlier) |
Removed. Use the Var form [my-view "args"] or [(rf/view :my-view) "args"] |
Conventions |
reg-global-interceptor |
Use the frame-level :interceptors config key (frame-level is the canonical "global within this frame"). For cross-frame observation use register-listener!. |
MIGRATION M-17 |
clear-global-interceptor |
No replacement needed — re-declare the frame (make-frame with an updated :interceptors vector; absent-key semantics clear it). |
MIGRATION M-17 |
reg-sub-raw |
Use reg-sub (app-db reads), Pattern-AsyncEffect (non-app-db sources), state machines (lifecycle), or the 006 adapter contract (bridging external reactivity). |
MIGRATION M-18 |
reg-event-db |
Use reg-event (no alias) — destructure :db from the coeffects map and wrap the return in {:db …}: (reg-event id (fn [{:keys [db]} ev] {:db BODY})). A stale call raises the always-on hard error :rf.error/reg-event-db-removed naming reg-event. The ^:no-doc facade throwing stub carries no API-manifest row. |
001 §The retired event-registration names |
reg-event-fx |
Use reg-event (no alias) — reg-event IS the identical shape under the bare name (coeffects in, effects out); just rename the call. A stale call raises :rf.error/reg-event-fx-removed naming reg-event. |
001 §The retired event-registration names |
reg-event-ctx |
A framework-internal primitive. Express application full-context work as a registered interceptor (reg-interceptor with :before/:after, referenced by id from a reg-event chain). A stale public call raises :rf.error/reg-event-ctx-removed. |
001 §The retired event-registration names |
reset-frame! (rf2-lxwpob) |
Use (destroy-frame! id) then (make-frame config), re-supplying the SAME config (which carries :id, and :images for an image-loaded frame) you already hold. A stale call raises :rf.error/reset-frame-removed naming the composition. The ^:no-doc facade throwing stub carries no API-manifest row. |
002 §Resetting a frame — destroy + make-frame |
reload-images! (rf2-lxwpob) |
Image hot-reload is folded into re-construction — re-call make-frame against the SAME :id with a new :images vector; it swaps the generation while preserving frame memory. To read the reload diff, call frame-generation before/after and diff with generation-diff. A stale call raises :rf.error/reload-images-removed. The ^:no-doc facade throwing stub carries no API-manifest row. |
002 §Image resolution and composition |
get-coeffect / get-effect |
Removed from the façade (no audience; carry no manifest row). Inside a reg-interceptor :before/:after fn read the context map directly: (get-in ctx [:coeffects k]) / (get-in ctx [:effects k]). The owning-namespace re-frame.interceptor/get-coeffect / get-effect fns remain framework-internal. |
001, 002 |
assoc-coeffect / assoc-effect |
Removed from the façade (zero callers; carry no manifest row). Inside a reg-interceptor :before/:after fn write the context map directly: (assoc-in ctx [:coeffects k] v) / (assoc-in ctx [:effects k] v). The owning-namespace re-frame.interceptor/assoc-coeffect / assoc-effect fns remain framework-internal. |
001, 002 |
re-frame.alpha/reg |
The shipped per-kind registrars: reg-event / reg-sub / reg-fx / reg-cofx / reg-flow. (The v1 event trio reg-event-db / reg-event-fx / reg-event-ctx is not a v2 target — those are removed/withdrawn throwing stubs and migration inputs only, see the rows above and EP-0018; reg-event is the single event-registration form.) |
MIGRATION M-23 |
re-frame.alpha/sub |
Vector-form (rf/subscribe [::id arg]). |
MIGRATION M-23 |
re-frame.alpha/reg-sub-lifecycle and built-in lifecycle policies (:safe, :no-cache, :reactive, :forever) |
Sub-cache uses a single algorithm — synchronous ref-counting (dispose on derefer-count → 0), per Spec 006 §Reference counting and disposal. For specific edge cases file a follow-up bead. | MIGRATION M-23 |
Cross-references¶
- 000-Vision.md — principles and design decisions
- 002-Frames.md — frames, dispatch envelope, drain semantics, overrides, machine foundations
- 005-StateMachines.md — machine library design (post-v1)
- 007-Stories.md — story/variant/workspace library design (post-v1)
- 008-Testing.md — testing API and patterns
- 009-Instrumentation.md — trace event stream, listeners, error contract
- 010-Schemas.md — Malli schemas
- 014-HTTPRequests.md —
:rf.http/managedrequest fx (optional capability) - MIGRATION.md — AI-driven migration spec