Liking cljdoc? Tell your friends :D

wagoe.platform.core.circuit-breaker

When to stop calling a service that keeps failing.

Retries bound the damage of one call; this bounds the damage of many. A service that is down still receives every request until each one times out, and every caller pays that timeout. Declining to call is the only thing that stops both.

FC/IS: pure. The state lives elsewhere — see …shell.rpc.breaker, which keeps it in the cache so replicas share one breaker rather than each discovering the outage separately.

When to stop calling a service that keeps failing.

Retries bound the damage of one call; this bounds the damage of many. A
service that is down still receives every request until each one times out,
and every caller pays that timeout. Declining to call is the only thing that
stops both.

FC/IS: pure. The state lives elsewhere — see `…shell.rpc.breaker`, which
keeps it in the cache so replicas share one breaker rather than each
discovering the outage separately.
raw docstring

wagoe.platform.core.csrf

Pure CSRF token functions — synchronizer token bound to a session.

The token is a signed double-submit value:

token = base64url(nonce) "." base64url(HMAC-SHA256(secret, nonce || binding))

The binding is the value the token is tied to: the user's session token for authenticated requests, or a per-request pre-session cookie value for the login form (which has no session yet). A token is only valid when presented together with the same binding it was signed against, which is what defeats CSRF: an attacker can forge a cross-site request but cannot read the victim's binding to produce a matching token.

Functional Core: these functions are pure and deterministic. The CSPRNG nonce and the secret are produced in the shell and passed in as arguments — nothing here performs I/O or reads ambient state.

Safety parity with ring-anti-forgery: validation uses buddy's mac/verify, which performs a constant-time comparison of the recomputed HMAC, so token checks do not leak timing information.

Enforcement is opt-in at the interceptor level (default off); see wagoe.platform.shell.http.interceptors/http-csrf-protection. Emit the token with hidden-field (server forms) or hx-headers (HTMX elements), or via the

<meta name="csrf-token"> tag + the ui-style init.js htmx:configRequest listener.

Pure CSRF token functions — synchronizer token bound to a session.

The token is a signed double-submit value:

    token = base64url(nonce) "." base64url(HMAC-SHA256(secret, nonce || binding))

The `binding` is the value the token is tied to: the user's session token for
authenticated requests, or a per-request pre-session cookie value for the login
form (which has no session yet). A token is only valid when presented together
with the same binding it was signed against, which is what defeats CSRF: an
attacker can forge a cross-site request but cannot read the victim's binding to
produce a matching token.

Functional Core: these functions are pure and deterministic. The CSPRNG nonce
and the secret are produced in the shell and passed in as arguments — nothing
here performs I/O or reads ambient state.

Safety parity with ring-anti-forgery: validation uses buddy's `mac/verify`,
which performs a constant-time comparison of the recomputed HMAC, so token
checks do not leak timing information.

Enforcement is opt-in at the interceptor level (default off); see
`wagoe.platform.shell.http.interceptors/http-csrf-protection`. Emit the token
with `hidden-field` (server forms) or `hx-headers` (HTMX elements), or via the
<meta name="csrf-token"> tag + the ui-style init.js htmx:configRequest listener.
raw docstring

wagoe.platform.core.database.constraint

Which integrity constraint a database refused a write on, read from what the driver reports: SQLState, vendor code and message. H2, PostgreSQL, SQLite and MySQL.

Which integrity constraint a database refused a write on, read from what the
driver reports: SQLState, vendor code and message. H2, PostgreSQL, SQLite
and MySQL.
raw docstring

wagoe.platform.core.database.migration-sql

Migration SQL written for PostgreSQL, H2 and SQLite, rewritten for MySQL.

The scaffolder writes one migration for every database (BOU-569). MySQL has no UUID or zoned TIMESTAMP type, takes no IF NOT EXISTS on an index, and ignores a REFERENCES written on a column. Only that is rewritten, so SQL written for MySQL passes through as it is.

Migration SQL written for PostgreSQL, H2 and SQLite, rewritten for MySQL.

The scaffolder writes one migration for every database (BOU-569). MySQL has
no UUID or zoned TIMESTAMP type, takes no IF NOT EXISTS on an index, and
ignores a REFERENCES written on a column. Only that is rewritten, so SQL
written for MySQL passes through as it is.
raw docstring

wagoe.platform.core.database.query

Pure functions for SQL query building and transformation.

All functions in this namespace are pure - they take data and return data without side effects. No I/O, logging, or state mutation.

Pure functions for SQL query building and transformation.

All functions in this namespace are pure - they take data and return data
without side effects. No I/O, logging, or state mutation.
raw docstring

wagoe.platform.core.database.seed

Pure logic for database seeding.

A seed file is EDN, in either of two shapes.

A map of table -> rows, for the simple case:

{:tasks [{:title "Try the admin UI" :done false} {:title "Read AGENTS.md" :done true}]}

Or a vector of [table rows] pairs, which is ordered:

[[:users [{:email "admin@example.com"}]] [:tasks [{:title "Owned by that user" :user-id 1}]]]

Insert order matters as soon as one table references another, and EDN maps only preserve their written order up to 8 entries — a 9th turns the literal into a PersistentHashMap and the order becomes hash order. Rather than let a seed file quietly start inserting children before parents once it grows, a map larger than that is rejected with a pointer to the vector form.

Table and column names are written in kebab-case, like the rest of the codebase; the conversion to snake_case happens here, at the point where the data becomes a persistence concern.

id, created-at and updated-at may be left out; see resolve-seed. A child names its parent by a symbolic id, a qualified keyword:

[[:invoices [{:id :invoice/acme :number "INV-1"}]] [:invoice-line-items [{:invoice-id :invoice/acme :description "Work"}]]]

Everything in this namespace is pure. Validation returns typed error values rather than throwing — the shell decides how to present them.

Pure logic for database seeding.

A seed file is EDN, in either of two shapes.

A map of table -> rows, for the simple case:

  {:tasks [{:title "Try the admin UI" :done false}
           {:title "Read AGENTS.md"   :done true}]}

Or a vector of [table rows] pairs, which is ordered:

  [[:users [{:email "admin@example.com"}]]
   [:tasks [{:title "Owned by that user" :user-id 1}]]]

Insert order matters as soon as one table references another, and EDN maps
only preserve their written order up to 8 entries — a 9th turns the literal
into a PersistentHashMap and the order becomes hash order. Rather than let a
seed file quietly start inserting children before parents once it grows, a
map larger than that is rejected with a pointer to the vector form.

Table and column names are written in kebab-case, like the rest of the
codebase; the conversion to snake_case happens here, at the point where the
data becomes a persistence concern.

`id`, `created-at` and `updated-at` may be left out; see `resolve-seed`. A
child names its parent by a symbolic id, a qualified keyword:

  [[:invoices [{:id :invoice/acme :number "INV-1"}]]
   [:invoice-line-items [{:invoice-id :invoice/acme :description "Work"}]]]

Everything in this namespace is pure. Validation returns typed error values
rather than throwing — the shell decides how to present them.
raw docstring

wagoe.platform.core.http.access

Who may reach a route, and what a refused caller is told.

Every route requires a signed-in user unless its route data carries :public true (BOU-568). A refusal has the shape the user module's require-authenticated interceptor answers with, so a client sees one 401 whichever layer refused it: the one error body of wagoe.platform.core.http.errors (BOU-586). No Content-Type: muuntaja encodes a map body only when none is set (ZZP-120).

Who may reach a route, and what a refused caller is told.

Every route requires a signed-in user unless its route data carries
`:public true` (BOU-568). A refusal has the shape the user module's
`require-authenticated` interceptor answers with, so a client sees one 401
whichever layer refused it: the one error body of
`wagoe.platform.core.http.errors` (BOU-586). No Content-Type: muuntaja encodes a map body
only when none is set (ZZP-120).
raw docstring

wagoe.platform.core.http.errors

The one JSON error body (BOU-586):

{"error": {"type": "not-found", "message": "User not found", "details": {...}, ; optional "correlation-id": "...", ; optional "dev": {...}}} ; optional, dev only (BND code)

Everything about the failure sits inside error, so a client reads one key. type is a string: the :type keyword of the ex-info, without its colon.

The one JSON error body (BOU-586):

  {"error": {"type":           "not-found",
             "message":        "User not found",
             "details":        {...},    ; optional
             "correlation-id": "...",  ; optional
             "dev":            {...}}}   ; optional, dev only (BND code)

Everything about the failure sits inside `error`, so a client reads one
key. `type` is a string: the `:type` keyword of the ex-info, without its
colon.
raw docstring

wagoe.platform.core.http.problem-details

Exceptions to error responses, and the :type -> status table.

The name is historical: these were RFC 7807 bodies until BOU-586 made every JSON error the one shape of wagoe.platform.core.http.errors.

Exceptions to error responses, and the `:type` -> status table.

The name is historical: these were RFC 7807 bodies until BOU-586 made every
JSON error the one shape of `wagoe.platform.core.http.errors`.
raw docstring

wagoe.platform.core.pagination.pagination

Pure functions for pagination logic.

This namespace provides pure functional implementations for pagination calculations, following the Functional Core pattern. All functions are deterministic and side-effect free.

Supports:

  • Offset-based pagination (simple, familiar)
  • Cursor-based pagination (high performance, stable results)
  • Parameter validation
  • Metadata calculation

Pure: All functions return data, no side effects.

Pure functions for pagination logic.

This namespace provides pure functional implementations for pagination calculations,
following the Functional Core pattern. All functions are deterministic and side-effect free.

Supports:
- Offset-based pagination (simple, familiar)
- Cursor-based pagination (high performance, stable results)
- Parameter validation
- Metadata calculation

Pure: All functions return data, no side effects.
raw docstring

wagoe.platform.core.pagination.versioning

Pure functions for API versioning logic.

This namespace provides pure functional implementations for API version management, following the Functional Core pattern. All functions are deterministic and side-effect free.

Supports:

  • Version parsing and comparison
  • Version lifecycle management (experimental → stable → deprecated → sunset)
  • Version validation
  • Migration path tracking

Pure: All functions return data, no side effects.

Pure functions for API versioning logic.

This namespace provides pure functional implementations for API version
management, following the Functional Core pattern. All functions are
deterministic and side-effect free.

Supports:
- Version parsing and comparison
- Version lifecycle management (experimental → stable → deprecated → sunset)
- Version validation
- Migration path tracking

Pure: All functions return data, no side effects.
raw docstring

wagoe.platform.core.rpc

Pure RPC envelope handling for the remote-port adapter.

A cross-module call goes through a protocol (ports.clj). Slicing a module into its own process means implementing that same protocol with something that makes a network call instead — the seam is already there, only the adapter is missing (BOU-90, scaling.adoc → Functional decomposition).

This namespace is the wire contract and nothing else: building an envelope, reading one, and turning a remote failure into the error shape callers already handle. No I/O — see wagoe.platform.shell.rpc.client and …rpc.server for that.

FC/IS: pure. Everything here is data in, data out.

Pure RPC envelope handling for the remote-port adapter.

A cross-module call goes through a protocol (`ports.clj`). Slicing a module
into its own process means implementing that same protocol with something
that makes a network call instead — the seam is already there, only the
adapter is missing (BOU-90, scaling.adoc → Functional decomposition).

This namespace is the wire contract and nothing else: building an envelope,
reading one, and turning a remote failure into the error shape callers
already handle. No I/O — see `wagoe.platform.shell.rpc.client` and
`…rpc.server` for that.

FC/IS: pure. Everything here is data in, data out.
raw docstring

wagoe.platform.core.system-selection

Cut an Integrant config down to the modules one service runs.

Modules are already gated by :enabled?, and the router mounts only the routes it is handed, so a process can run a subset — there was just no way to say which subset (BOU-91). This is that, as a pure transformation: config in, smaller config out.

The whole difficulty is refs. Dropping :wagoe/tenant-service leaves :wagoe/http-handler pointing at a key that no longer exists, and Integrant refuses to build a config with a dangling ref — so the refs have to go with the keys, and they have to go first, or the closure that works out what is still needed follows them straight back to everything.

FC/IS: pure. Nothing here starts, stops or reads anything.

Cut an Integrant config down to the modules one service runs.

Modules are already gated by `:enabled?`, and the router mounts only the
routes it is handed, so a process *can* run a subset — there was just no way
to say which subset (BOU-91). This is that, as a pure transformation:
config in, smaller config out.

The whole difficulty is refs. Dropping `:wagoe/tenant-service` leaves
`:wagoe/http-handler` pointing at a key that no longer exists, and Integrant
refuses to build a config with a dangling ref — so the refs have to go with
the keys, and they have to go *first*, or the closure that works out what is
still needed follows them straight back to everything.

FC/IS: pure. Nothing here starts, stops or reads anything.
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

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