From fe636282cbe6b4d6d038fd9ec26850d99a5aebb6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Oct 2026 08:34:21 +0000 Subject: [PATCH] :books: Document promesa API shapes in memory Two API families with inconsistent argument order, verified against the 12.0.1 jar: fmap/mcat are fn-first (they pair with ->>), then/catch are promise-first (they pair with ->). The bind-style ops require the fn to return a promise; a plain value crashes with "expected thenable". The repo convention prefers ->> composition, and legacy -> chains stay as they are. Routes from critical-info and exporter/core. AI-assisted-by: glm-5.3-flash --- .serena/memories/clojure/promesa.md | 73 +++++++++++++++++++++++++++++ .serena/memories/critical-info.md | 1 + .serena/memories/exporter/core.md | 1 + 3 files changed, 75 insertions(+) create mode 100644 .serena/memories/clojure/promesa.md diff --git a/.serena/memories/clojure/promesa.md b/.serena/memories/clojure/promesa.md new file mode 100644 index 0000000000..ae66b214aa --- /dev/null +++ b/.serena/memories/clojure/promesa.md @@ -0,0 +1,73 @@ +# Promesa (funcool, 12.0.1) — verified API shapes + +The promise lib of the monorepo: JVM (backend, `promesa.exec` thread +pools) and CLJS (frontend, exporter). Declared in `common/deps.edn`; +heavy users: `frontend/src/app/main/**` (Rx interop), `exporter/src/app/**` +(management client, auth), `backend/src/app/storage/s3.clj` and +`worker/cron.clj` (JVM await paths). Doc: `doc/promises.md` upstream. + +## Two API families (verified against the 12.0.1 jar) + +Argument order is inconsistent BY DESIGN; each op belongs to one of +two families: + +**promise-first `([p f ...])` — pairs with `->` threading** (the + promise arrives as the threaded first argument): +- `then`, `then'`, `bind`, `handle`, `finally`, `catch` (`([p f])`, + 2-arity with predicate: `([p pred-or-type f])`), `chain` + `([p f & fs])`. + +**fn-first `([f p ...])` — pairs with `->>` threading** (the promise + arrives as the threaded LAST argument): +- `map`/`fmap`, `mapcat`/`mcat`, `hmap`/`hcat`, `fnly`, `merr`. + +Every 3-arity in a family inserts its executor FIRST for the fn-first +family (`([executor f p])`) and LAST for the promise-first family +(`([p f executor])`). + +**What `f` must return:** +- `then`/`catch`/`fmap`/`hmap`: plain value OK — auto unwrapping. +- the bind-style ops `mcat` and `merr`: **`f` MUST return a promise + instance** (`merr` may also throw). A plain value does NOT get + wrapped: the impl's `fbind` pushes a BIND task that demands a + thenable and crashes with `TypeError: expected thenable` + (first-hand crash in the exporter worker, 2026-10-07). + +`finally`/`fnly`: run `f`, mirror the original promise value. + +## promesa's own -> / ->> macros (do NOT confuse with clojure.core) + +`p/->` and `p/->>` exist in `promesa.core` (12.x). Their purpose is NOT +arg positioning: they expand to a `chain` over `then` and REALIZE the +promise at each step, so every form receives the realized value +(e.g. `(p/-> (js/fetch ...) .-body .-status)`). + +## Repo convention + +**Prefer `->>` composition** over the map family (fn-first ops); it is +how new promise code should read: + + (->> (fetch ...) + (p/fmap (fn [v] ...)) + (p/mcat (fn [v] (p/...)))) + +A `p/do` terminator inside `->>` receives the threaded promise as its +LAST expression — it runs AFTER the earlier exprs, so it can never act +as the chain's continuation; terminate by wrapping the chain in an +explicit `(p/do chain ...)` (`p/let` avoids the same trap). + +Legacy code threads promises with `->` over the promise-first +family (`(-> p (p/then ...))`, `(p/then p f)` calls). Read the args in +that order when touching it; do not rewrite it unless the task touches +that code anyway. + +For JVM blocking code, `promesa.exec/await!` exists but is deprecated +since 12.0.0 → `p/await`. + +## Async test gotcha (CLJS) + +An async cljs.test var only proves its chain when the chain is really +awaited: a broken link outside the `p/let`/`p/do` body ends silently as +"Ran N tests containing 0 assertions" with 0 failures. If a test passes +with 0 assertions, the chain broke upstream; add a print in the last +link (`CHAIN RAN`) to prove plumbing before trusting green. diff --git a/.serena/memories/critical-info.md b/.serena/memories/critical-info.md index 64141eed15..3eeaf450aa 100644 --- a/.serena/memories/critical-info.md +++ b/.serena/memories/critical-info.md @@ -8,6 +8,7 @@ You are working on the GitHub project `penpot/penpot`, a monorepo. - Cross-cutting testing principles, TDD workflow, and anti-patterns: `mem:testing`. - Verified Clojure language behaviors that contradict common assumptions (e.g. `int?` covers `Long`; `integer?` is the general predicate): `mem:clojure/idioms` — read before assuming stdlib predicate semantics. - When designing a solution or an API, read `mem:clojure/design-rules` (function shape, boundary checks, naming). +- Promise code (JVM + CLJS): promesa's two API families and their arg orders (fmap/mcat fn-first, then/catch promise-first, `mcat` f must return a promise), the repo `->>` preference, and async-test silent-green traps: `mem:clojure/promesa` — read before writing or reading promise chains. # Development workflow diff --git a/.serena/memories/exporter/core.md b/.serena/memories/exporter/core.md index 9b7078045b..84dd45e9ea 100644 --- a/.serena/memories/exporter/core.md +++ b/.serena/memories/exporter/core.md @@ -9,6 +9,7 @@ - Because exporter consumes `common/`, shared file/shape/model changes may need exporter verification even when the immediate change is not under `exporter/`. - Cross-cutting testing principles and anti-patterns: `mem:testing`. - Exporter test conventions and CI: `mem:exporter/testing`. +- Promise chains in the exporter (management client, auth) follow promesa's two API families; arg orders, what each fn must return, the `->>` preference and the async-test silent-green trap: `mem:clojure/promesa` — read before touching promise code. ## HTTP and browser pool