refactor(http): round-2 polish cluster — 12 follow-on beads (rf2-jklja)#1010
Merged
Conversation
added 12 commits
May 14, 2026 13:54
The outer caller in decode-response-body already gates on the same not-supplied condition. Inlining the body removes one indirection and the duplicated decode-supplied? check inside the helper. Per audit finding 1.3 (rf2-jklja round-2).
The earlier impl used (* j (- 0.5 (rand))) where j = 0.25 × capped, yielding offset in [-0.125 × capped, +0.125 × capped] — a ±12.5% spread, not the ±25% Spec 014 §Retry and backoff line 250 states. Now uses (- 1.0 (* 2.0 (rand))) (uniform in [-1, +1]) scaled by 0.25 × capped for a true ±25% range. Per audit finding 4.3 (rf2-jklja round-2).
The per-line bead-ref comments duplicated information that lives in each sub-namespace's docstring. Replaced with a single block-header comment pointing at the docstrings, and sorted the publications alphabetically for scan-ability. The drift gate matches on keyword only — ordering is free. Per audit finding 1.7 (rf2-jklja round-2).
…, rf2-exycf) malli-decode previously ran 3-4 requiring-resolve calls per decode (decode + transformer + validate, with reader-conditional asymmetry between JVM and CLJS). Cheshire's generate-string/parse-string had the same per-call resolve cost on JVM. Now: defonce'd delays cache the resolved fn once per runtime; the CLJ/CLJS deref asymmetry is hidden behind a single resolve-fn helper that normalises the return to a callable value. The decode call site is platform-uniform (no #? around the invocations themselves) and the malli pipeline cost reduces from 3 resolves + 3 derefs per call to a single shared atom deref. Closes both rf2-tja2y (memoise) and rf2-exycf (deref asymmetry — the new resolve-fn wraps once). Per audit findings 1.4 + 1.5 (rf2-jklja round-2).
prepare-emit-tags previously parsed each :url query string twice: once via query-denylist-hit? to decide whether to stamp :sensitive?, then a second time via redact-url inside redact-request-tags / redact-failure. Now: redact-request-tags-with-flag and redact-failure-with-flag return [tags url-redacted?] tuples from the single redact-url-query- string call, and prepare-emit-tags / prepare-emit-failure consume the flag directly. The query-denylist-hit? helper is removed (the shape-fused walker subsumes it). Halves the per-trace-emit URL parse cost on every retry-attempt, abort-on-actor-destroy, and decode-defaulted warning that carries a :url with a query string. Per audit finding 3.4 (rf2-jklja round-2).
…f2-hzn1a) Every :rf.http/managed request previously walked the [:rf/spawned <parent> <invoke-id>] registry looking for the originating event-id, on every invocation. Apps that don't use state machines (the common case) paid the deref + structural walk for every HTTP request even though the registry is always empty. Now: deref the db once, pluck :rf/spawned, and short-circuit on seq-empty before the O(parents × invokes) walk. The deref itself stays (it's how we read frame state without statically requiring core/spawn machinery), but the walk skips on the empty registry. The deeper refactor (reverse-index maintained by spawn/destroy) is left for a later bead — this early-out covers the no-machines hot path without touching the spawn lifecycle. Per audit finding 3.1 (rf2-jklja round-2).
…utlm) http-transport/dispatch-reply!, canned-success-handler, and canned- failure-handler each manually composed build-reply-event with a late-bind :router/dispatch! lookup. Three near-verbatim copies of the same shape — and three places to keep in sync. Now: encoding/dispatch-reply-via-late-bind! owns the composition. Transport's dispatch-reply! is a thin wrapper that chooses the explicit-on slot; the canned stubs delegate directly. http-machine- wrapper no longer needs the late-bind import for the dispatch path (it keeps it for :machines/reg-machine). Per audit finding 1.6 (rf2-jklja round-2).
…-plngk) Aborts previously cleared the in-flight registry 2-3 times per cascade: managed-abort-handler pre-cleared by request-id, then the abort-fn fired finalise-failure! which cleared again; abort-on-actor- destroy walked the request-id index per-handle (N eager swaps) and finalise-failure! re-walked. Now the contract is pinned: the natural-completion sites (finalise-success!, finalise-failure!, maybe-retry!'s retry-clear) own per-request cleanup. The abort entry points fire the abort-fn and rely on its finalise-failure! cascade. The actor-side eager dissoc inside abort-on-actor-destroy remains: it pins idempotency against re-entry (a re-entered call sees an empty slot), and it's a single swap! regardless of handle count. Saves N request-side swap!s on actor-destroy with N in-flight, and one swap on user-initiated aborts. Per audit finding 3.2 / Top-#3 (rf2-jklja round-2).
…not a decode pipeline (rf2-1z21y) The previous wording claimed ':decode <schema> runs through 010's decode pipeline' but Spec 010 has no decode pipeline. The decode contract lives in Spec 014 (request body → parse by content-type → apply schema-language decode-or-validate primitive); Spec 010 standardises the schema language itself (the :spec / reg-app-schema surface and the pluggable validator seam). The CLJS reference implementation already uses malli.core/decode + malli.transform/json-transformer directly — it cannot route through re-frame.schemas because the http artefact has no production dep on schemas (per rf2-p7va schemas is optional; pulling it in would violate the artefact split). The two surfaces share a schema language but not a code path. Per audit finding 4.2 (rf2-jklja round-2).
http-managed.cljc claimed to be a thin public façade but still owned two non-trivial private handlers (normalise-args + managed-handler + managed-abort-handler, ~80 LoC) — the only call sites for middleware/run-interceptor-chain! and transport/check-cljs-only-keys!. Now: re-frame.http-handlers owns the handler bodies. The façade is true re-exports + the four fx/reg-fx calls + the late-bind publications. Every concern has a per-named sibling (encoding/registry/middleware/transport/privacy/machine-wrapper/handlers) — the symmetry the audit named the conspicuous absence is closed. Per audit finding 2.1 (rf2-jklja round-2).
http-encoding.cljc (355 LoC) held five distinct concerns under one roof — URL encoding, body encoding, decode pipeline, :accept normalisation, failure-map / reply addressing / backoff. Well over the 250-LoC leaf-size ceiling (rf2-zkca8). This commit extracts the response-side decode pipeline into a sibling http-decode namespace: - content-type-of (case-insensitive header lookup) - sniff-decoder (Content-Type → :json | :text | :blob) - malli-decode + the memoised resolves (rf2-tja2y delays) - decode-response-body Bonus: opportunistically inlines default-accept-fn into run-accept (audit finding 3.3 — saves one closure alloc per response). Post-split sizes: - http-encoding.cljc: 217 LoC (under ceiling) - http-decode.cljc: 171 LoC (under ceiling) URL helpers (url-encode, params->query, merge-params) stay in http-encoding for now. rf2-8vofn (in flight on another branch) pre-sizes a http-url.cljc; once that lands, the URL slot moves there in a follow-on. Per audit finding 2.3 + 3.3 (rf2-jklja round-2).
…rf2-5ijhk) The rf2-5ijhk split introduced a resolve-fn helper that factored the JVM requiring-resolve / CLJS resolve asymmetry behind a single symbol-taking fn. CLJS resolve is a compile-time macro that demands a literal quoted symbol — passing the symbol as a runtime fn arg fails analysis with 'Argument to resolve must be a quoted symbol'. Inline each malli resolve into its own defonce delay so the symbol sits as a literal in the macroexpansion point. The cache and the delayed-once-per-runtime semantics are unchanged; the abstraction just folds into three sibling delays instead of one helper + three callers.
9321a56 to
8f455b6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Eighth batched-by-surface refactor — 12-bead P2/P3 polish cluster on
implementation/http/, dispatched from the rf2-jklja round-2 audit. All beads addressed in commit-per-bead form.Commits
maybe-emit-decode-defaulted!gaterequiring-resolvefor malli + cheshire (fuses with CLJ/CLJS deref asymmetry)compute-actor-idshort-circuits when no spawned actorsdispatch-replyacross transport + canned stubsclear-in-flight!ownership to finalise siteshttp-handlerssiblinghttp-decodeout ofhttp-encoding(was 355 → 217 LoC; new decode 171 LoC)(Plus a fix-up commit for the CLJS resolve-fn compilation issue introduced by rf2-tja2y / rf2-5ijhk — CLJS
resolveis a compile-time macro that demands a literal quoted symbol.)Net diff
Against the merge base — 577 insertions, 448 deletions, 11 files. New siblings:
http_handlers.cljc(125 LoC),http_decode.cljc(177 LoC). The pre-existinghttp_encoding.cljcdrops from 355 to 217 LoC;http_managed.cljc(the public façade) drops to 200 LoC.Perf wins
compute-actor-idearly-out: apps without state machines pay a single deref + map lookup + seq-check per request instead of a full structural walk.prepare-emit-tagsURL walk: one parse instead of two per retry-attempt, abort-on-actor-destroy, and decode-defaulted emit.clear-in-flight!ownership: saves N request-sideswap!s on actor-destroy with N in-flight; saves one on user-initiated aborts.requiring-resolvecalls collapse to a singledefoncedelay per fn.run-acceptdefault: inlined; no closure allocation per response.Test plan
cd implementation/http && clojure -M:test— 136 tests / 385 assertionscd implementation && npm run test:cljs— 1817 tests / 5105 assertionsnpm run test:perf-bundle— counter clean, counter-perf carries perf markersnpm run test:bundle-isolation— http symbols stay inside http; allow-list within budget