Synthigy client for the /data endpoint — JVM / babashka.
Connect once, then call; no client threading:
(connect! {:endpoint "http://localhost:7887" :client-id "my-service" :client-secret "secret"})
(search :user {:-where {:active {:-eq true}}} {:name nil :roles [:name :active]})
(sync :user {:name "alice" :active true})
THE INVARIANT: one process, one client, one backend. A process (BFF,
service, script) connects to Synthigy as exactly ONE OAuth client —
identity is multiplexed per-call with :acting-as, never with a second
client. binding synthigy.client.core/*client* exists for TESTS, nothing
else. Internals that pass the client VALUE do so for lifecycle correctness
(a watch must tear down against the client it registered in, even after a
REPL re-connect!) — not for multi-endpoint support, which is a non-goal.
Keys are kebab-case in both directions by default (:key-format nil
for raw snake_case wire keys).
Pure data helpers — op builders for batch, results->data/ok?,
compose-tree/compose-forest — live in synthigy.client.core.
Synthigy client for the /data endpoint — JVM / babashka.
Connect once, then call; no client threading:
(connect! {:endpoint "http://localhost:7887"
:client-id "my-service"
:client-secret "secret"})
(search :user
{:-where {:active {:-eq true}}}
{:name nil :roles [:name :active]})
(sync :user {:name "alice" :active true})
THE INVARIANT: one process, one client, one backend. A process (BFF,
service, script) connects to Synthigy as exactly ONE OAuth client —
identity is multiplexed per-call with `:acting-as`, never with a second
client. `binding synthigy.client.core/*client*` exists for TESTS, nothing
else. Internals that pass the client VALUE do so for lifecycle correctness
(a watch must tear down against the client it registered in, even after a
REPL re-`connect!`) — not for multi-endpoint support, which is a non-goal.
Keys are kebab-case in both directions by default (`:key-format nil`
for raw snake_case wire keys).
Pure data helpers — op builders for `batch`, `results->data`/`ok?`,
`compose-tree`/`compose-forest` — live in `synthigy.client.core`.Synthigy client for the /data endpoint — ClojureScript (promise-returning).
Same surface as the CLJ client; every network fn returns a Promise.
Connect once via connect!; pure data helpers live in
synthigy.client.core.
THE INVARIANT: one process, one client, one backend — identity is
multiplexed per-call with :acting-as, never with a second client.
Synthigy client for the /data endpoint — ClojureScript (promise-returning). Same surface as the CLJ client; every network fn returns a Promise. Connect once via `connect!`; pure data helpers live in `synthigy.client.core`. THE INVARIANT: one process, one client, one backend — identity is multiplexed per-call with `:acting-as`, never with a second client.
Token providers for the Synthigy client.
A provider is a map {:token-fn f :invalidate-fn g}:
:token-fn — 0-arg returning a default bearer token.
Optional 1-arg variant (token-fn audience) returns
a token for another service that trusts Synthigy as IdP.
:invalidate-fn — 0-arg clearing the provider's cache so the next
token-fn call refetches. 1-arg variant clears
a specific audience. Optional.
The SDK calls token-fn before every request; on HTTP 401 it calls
invalidate-fn (if present) and retries once. How tokens are fetched,
cached, or refreshed is entirely the provider's concern — this ns ships
two sensible defaults:
(static token) — one fixed token, no refresh (oauth {:client-id ...}) — client-credentials flow, caches per audience, refreshes before expiry
Token providers for the Synthigy client.
A provider is a map `{:token-fn f :invalidate-fn g}`:
:token-fn — 0-arg returning a default bearer token.
Optional 1-arg variant `(token-fn audience)` returns
a token for another service that trusts Synthigy as IdP.
:invalidate-fn — 0-arg clearing the provider's cache so the next
`token-fn` call refetches. 1-arg variant clears
a specific audience. Optional.
The SDK calls `token-fn` before every request; on HTTP 401 it calls
`invalidate-fn` (if present) and retries once. How tokens are fetched,
cached, or refreshed is entirely the provider's concern — this ns ships
two sensible defaults:
(static token) — one fixed token, no refresh
(oauth {:client-id ...}) — client-credentials flow, caches per
audience, refreshes before expiryToken providers for the Synthigy CLJS client.
A provider is a map {:token-fn f :invalidate-fn g}:
:token-fn — 0-arg returning a bearer token (string or Promise<string>).
Optional 1-arg variant (token-fn audience) for IdP
federation.
:invalidate-fn — 0/1-arg clearing the provider's cache so the next
token-fn call refetches.
The SDK calls token-fn before every request; on HTTP 401 it calls
invalidate-fn (if present) and retries once. The two defaults shipped
here cover 90% of SDK use — teams wanting OIDC / PKCE / silent renew
should plug their own :token-fn / :invalidate-fn in.
Token providers for the Synthigy CLJS client.
A provider is a map `{:token-fn f :invalidate-fn g}`:
:token-fn — 0-arg returning a bearer token (string or Promise<string>).
Optional 1-arg variant `(token-fn audience)` for IdP
federation.
:invalidate-fn — 0/1-arg clearing the provider's cache so the next
`token-fn` call refetches.
The SDK calls `token-fn` before every request; on HTTP 401 it calls
`invalidate-fn` (if present) and retries once. The two defaults shipped
here cover 90% of SDK use — teams wanting OIDC / PKCE / silent renew
should plug their own `:token-fn` / `:invalidate-fn` in.Pure core of the Synthigy client — everything that is genuinely
platform-free: the client map (construction + the connected *client*),
wire operation builders, result unpacking, tree composition, and the
subscription wire shapes. No transport here; synthigy.client (.clj /
.cljs) owns the verbs.
Pure core of the Synthigy client — everything that is genuinely platform-free: the client map (construction + the connected `*client*`), wire operation builders, result unpacking, tree composition, and the subscription wire shapes. No transport here; `synthigy.client` (.clj / .cljs) owns the verbs.
Filter-condition helpers — mirror the Go (Eq/Neq/…) and JS (eq/neq/…)
SDKs for parity. In Clojure these are thin sugar over plain maps; you
can always write the literal ({:_eq v}) directly. The combinators
and/or/not are where they earn their keep.
Alias as f and place conditions in a where map keyed by field:
(require '[synthigy.client.filter :as f]) (client/search c :user {:-where {:active (f/eq true) :age (f/gt 18)}} [:name]) (client/search c :user {:-where (f/or {:role (f/eq "admin")} {:role (f/eq "owner")})} [:name])
Filter-condition helpers — mirror the Go (Eq/Neq/…) and JS (eq/neq/…)
SDKs for parity. In Clojure these are thin sugar over plain maps; you
can always write the literal (`{:_eq v}`) directly. The combinators
and/or/not are where they earn their keep.
Alias as `f` and place conditions in a where map keyed by field:
(require '[synthigy.client.filter :as f])
(client/search c :user {:-where {:active (f/eq true)
:age (f/gt 18)}} [:name])
(client/search c :user {:-where (f/or {:role (f/eq "admin")}
{:role (f/eq "owner")})} [:name])HTTP transport for Synthigy client.
ONE PROCESS, ONE CLIENT: every fn here reads the connected client from
synthigy.client.core/*client* — nothing below the public API passes a
client around (the Clojure server-restart idiom: connect! destroys the
old client and installs the new one, so the root var is always the only
live client). Each request resolves the client ONCE and uses that
snapshot for token + retry + hooks.
Tokens come from the client's :token-fn (0-arg for default token,
1-arg for audience-scoped). On 401 the client's :invalidate-fn (if
any) is called to force a token refresh, then the request is retried
once. If that still fails with 401, UNAUTHORIZED is thrown.
HTTP transport for Synthigy client. ONE PROCESS, ONE CLIENT: every fn here reads the connected client from `synthigy.client.core/*client*` — nothing below the public API passes a client around (the Clojure server-restart idiom: `connect!` destroys the old client and installs the new one, so the root var is always the only live client). Each request resolves the client ONCE and uses that snapshot for token + retry + hooks. Tokens come from the client's `:token-fn` (0-arg for default token, 1-arg for audience-scoped). On 401 the client's `:invalidate-fn` (if any) is called to force a token refresh, then the request is retried once. If that still fails with 401, UNAUTHORIZED is thrown.
HTTP transport for the Synthigy CLJS client.
Uses js/fetch — works in every modern browser and Node 18+. Promise-
returning throughout; callers use promesa (p/let) or .then.
Tokens come from the client's :token-fn (0-arg for default token,
1-arg for audience-scoped). On 401 the client's :invalidate-fn (if
any) is called to force a token refresh, then the request is retried
once. If that still fails with 401, UNAUTHORIZED is thrown.
HTTP transport for the Synthigy CLJS client. Uses js/fetch — works in every modern browser and Node 18+. Promise- returning throughout; callers use promesa (`p/let`) or `.then`. Tokens come from the client's `:token-fn` (0-arg for default token, 1-arg for audience-scoped). On 401 the client's `:invalidate-fn` (if any) is called to force a token refresh, then the request is retried once. If that still fails with 401, UNAUTHORIZED is thrown.
Key normalization for the Synthigy client.
Converts kebab-case and camelCase keys to snake_case for the /data endpoint wire format.
Key normalization for the Synthigy client. Converts kebab-case and camelCase keys to snake_case for the /data endpoint wire format.
Opt-in retry helper for transient failures.
Scope: wrap a thunk that calls the SDK, retry on transport errors and
5xx responses, leave 4xx alone. NOT composable with write ops at the
caller's discretion — retrying a sync after a timeout could double-
apply if the server processed the first attempt. Use on idempotent
calls (search / get / count / schema / sql-template).
Retryable: :code TRANSPORT_ERROR — network blip, DNS, timeout :code HTTP_ERROR + :status 502/503/504 — gateway / upstream hiccup
NOT retryable (propagates on first failure): :code UNAUTHORIZED / FORBIDDEN — auth problem; retry won't help :code HTTP_ERROR with 4xx — caller bug; retry won't help :code UNKNOWN_ENTITY / operation errors — app-level failures
Opt-in retry helper for transient failures. Scope: wrap a thunk that calls the SDK, retry on transport errors and 5xx responses, leave 4xx alone. NOT composable with write ops at the caller's discretion — retrying a `sync` after a timeout could double- apply if the server processed the first attempt. Use on idempotent calls (search / get / count / schema / sql-template). Retryable: :code TRANSPORT_ERROR — network blip, DNS, timeout :code HTTP_ERROR + :status 502/503/504 — gateway / upstream hiccup NOT retryable (propagates on first failure): :code UNAUTHORIZED / FORBIDDEN — auth problem; retry won't help :code HTTP_ERROR with 4xx — caller bug; retry won't help :code UNKNOWN_ENTITY / operation errors — app-level failures
Opt-in retry helper for transient failures in the browser SDK.
Scope matches the CLJ sibling: wrap a promise-returning thunk, retry transport errors and 5xx responses, leave 4xx alone. Use only on idempotent calls.
Retryable: :code TRANSPORT_ERROR — network blip, DNS, timeout :code HTTP_ERROR + :status 502/503/504 — gateway / upstream hiccup
NOT retryable (propagates on first failure): :code UNAUTHORIZED / FORBIDDEN — auth problem :code HTTP_ERROR with 4xx — caller bug app-level failures
Opt-in retry helper for transient failures in the browser SDK. Scope matches the CLJ sibling: wrap a promise-returning thunk, retry transport errors and 5xx responses, leave 4xx alone. Use only on idempotent calls. Retryable: :code TRANSPORT_ERROR — network blip, DNS, timeout :code HTTP_ERROR + :status 502/503/504 — gateway / upstream hiccup NOT retryable (propagates on first failure): :code UNAUTHORIZED / FORBIDDEN — auth problem :code HTTP_ERROR with 4xx — caller bug app-level failures
Selection normalization for the Synthigy client.
Converts user-friendly selection shorthand into the wire format expected by the /data endpoint.
Selection normalization for the Synthigy client. Converts user-friendly selection shorthand into the wire format expected by the /data endpoint.
SSE listener for the Synthigy CLJS client.
Uses fetch streaming (ReadableStream + TextDecoder) — supports
Authorization headers, matches sdk/js. Callback-driven: caller
provides on-event; listen returns a 0-arg stop function that
aborts the connection.
Auto-reconnects with Last-Event-ID + exponential backoff (1s → 30s).
SSE listener for the Synthigy CLJS client. Uses fetch streaming (ReadableStream + TextDecoder) — supports Authorization headers, matches sdk/js. Callback-driven: caller provides `on-event`; `listen` returns a 0-arg `stop` function that aborts the connection. Auto-reconnects with Last-Event-ID + exponential backoff (1s → 30s).
RAW server-side subscription-set management — ADVANCED / low-level.
Prefer synthigy.client/watch and synthigy.client/watch-query: the watch
multiplexer manages this same server-side set automatically (union of all
watch interests, re-asserted on every SSE reconnect).
WARNING: the server keeps ONE subscription set per client identity and
/data/subscription/set is FULL-REPLACE. Calling these fns while any
watch/watch-query is active on the same client CLOBBERS the multiplexer's
set (and vice versa) — live updates silently stop. Use this namespace only
when you consume /data/events yourself via synthigy.client/listen and
no watches are open.
RAW server-side subscription-set management — ADVANCED / low-level. Prefer `synthigy.client/watch` and `synthigy.client/watch-query`: the watch multiplexer manages this same server-side set automatically (union of all watch interests, re-asserted on every SSE reconnect). WARNING: the server keeps ONE subscription set per client identity and `/data/subscription/set` is FULL-REPLACE. Calling these fns while any watch/watch-query is active on the same client CLOBBERS the multiplexer's set (and vice versa) — live updates silently stop. Use this namespace only when you consume `/data/events` yourself via `synthigy.client/listen` and no watches are open.
RAW server-side subscription-set management — ADVANCED / low-level (ClojureScript, promise-returning).
Prefer the watch layer: the server keeps ONE subscription set per client
identity and /data/subscription/set is FULL-REPLACE — mixing this with
any watch machinery on the same client clobbers its set. Use only when
consuming /data/events yourself via synthigy.client/listen.
RAW server-side subscription-set management — ADVANCED / low-level (ClojureScript, promise-returning). Prefer the watch layer: the server keeps ONE subscription set per client identity and `/data/subscription/set` is FULL-REPLACE — mixing this with any watch machinery on the same client clobbers its set. Use only when consuming `/data/events` yourself via `synthigy.client/listen`.
Code generator: a folder of .xsql → committed Clojure source.
Connects to a running Synthigy backend ONCE (op:describe = the XSQL compiler) and emits one namespace per XSQL @namespace, with a named, documented function per operation. The generated code carries NO parser and needs NO backend at runtime — it embeds each op's compiled XSQL source string and sends it via synthigy.client. Connection is a BUILD-time dependency, like Prisma/sqlc/genqlient; commit the output and it runs offline forever after.
clj -X:gen :dir '"synthigy"' :out '"src"'
:ns-prefix myapp.ops :endpoint '"http://localhost:7887"'
Auth for the pull: THE APP'S OWN client credentials (:client-id/:client-secret
or SYNTHIGY_CLIENT_ID/SYNTHIGY_CLIENT_SECRET) — /schema + describe are
IAM-filtered per principal, so generating as the app makes the generated
contract exactly what the app can do at runtime (a personal/dev identity
would generate a surface the app can't honor). Grant the client schema:read
(or the broader dataset:load). :token / SYNTHIGY_TOKEN and authless remain
for bare dev servers. Regenerate any time you edit an .xsql — the diff shows
exactly what changed.
Code generator: a folder of .xsql → committed Clojure source.
Connects to a running Synthigy backend ONCE (op:describe = the XSQL
compiler) and emits one namespace per XSQL @namespace, with a named,
documented function per operation. The generated code carries NO parser and
needs NO backend at runtime — it embeds each op's compiled XSQL source string
and sends it via synthigy.client. Connection is a BUILD-time dependency, like
Prisma/sqlc/genqlient; commit the output and it runs offline forever after.
clj -X:gen :dir '"synthigy"' :out '"src"' \
:ns-prefix myapp.ops :endpoint '"http://localhost:7887"'
Auth for the pull: THE APP'S OWN client credentials (:client-id/:client-secret
or SYNTHIGY_CLIENT_ID/SYNTHIGY_CLIENT_SECRET) — /schema + describe are
IAM-filtered per principal, so generating as the app makes the generated
contract exactly what the app can do at runtime (a personal/dev identity
would generate a surface the app can't honor). Grant the client `schema:read`
(or the broader `dataset:load`). :token / SYNTHIGY_TOKEN and authless remain
for bare dev servers. Regenerate any time you edit an .xsql — the diff shows
exactly what changed.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |