Portable public facade for NATS Services — hosting a request-reply micro-service
under one .cljc API (ADR 0024). The same consumer code compiles and runs on the
JVM, the browser, and Node.
Services is pure convenience over core request-reply: a queue-subscribed handler
per endpoint plus the auto-responders on $SRV.PING|INFO|STATS.*. So, unlike
nats-cljc.kv / nats-cljc.jetstream, there is NO service context and NOTHING
is verified at entry — create hangs directly off the Connection, because there
is no server feature to round-trip against (ADR 0024). A client invokes an
endpoint with plain nats-cljc.core/request; there is no new verb for the
caller.
Requiring this namespace loads the per-leg service impl — and, on CLJS, pulls the
@nats-io/services bundle bytes — which a consumer who never requires it does not
pay for (ADR 0016/0026). The impl require is for that load side-effect only (it
extends the Service protocol onto the platform Connection record); this facade
calls the record through the protocol.
Portable public facade for NATS Services — hosting a request-reply micro-service under one `.cljc` API (ADR 0024). The same consumer code compiles and runs on the JVM, the browser, and Node. Services is pure convenience over core request-reply: a queue-subscribed handler per endpoint plus the auto-responders on `$SRV.PING|INFO|STATS.*`. So, unlike `nats-cljc.kv` / `nats-cljc.jetstream`, there is NO service context and NOTHING is verified at entry — `create` hangs directly off the Connection, because there is no server feature to round-trip against (ADR 0024). A client invokes an endpoint with plain `nats-cljc.core/request`; there is no new verb for the caller. Requiring this namespace loads the per-leg service impl — and, on CLJS, pulls the `@nats-io/services` bundle bytes — which a consumer who never requires it does not pay for (ADR 0016/0026). The impl require is for that load side-effect only (it `extend`s the Service protocol onto the platform Connection record); this facade calls the record through the protocol.
(create conn {:keys [endpoints] :as config})Create and start a Service on conn from the portable config, returning a
platform-native promise (CompletableFuture on the JVM, js/Promise on CLJS) that
resolves to a running Service — the value stop tears down. There is no context
and no server feature to verify at entry: the promise resolves as soon as the
Service's endpoints are subscribed (ADR 0024). The config IS validated pre-flight,
before any native or wire call (ADR 0015): an omitted :name or :version
rejects the promise with :type :missing-required-key carrying the offending
:key, a malformed service or endpoint :name with :invalid-name, a non-semver
:version with :invalid-version carrying the offending :version, and two
endpoints sharing a :name with :duplicate-endpoint carrying the offending
:name. Endpoint :subject syntax stays native/server-enforced; empty or absent
:endpoints is legal.
Config keys: :name (required), :version (required), :description,
:metadata, and :endpoints, a vector of endpoint maps. Each endpoint is
{:name :subject :handler :queue-group :metadata}: :name and :handler are
required, :subject is the subject the endpoint listens on and DEFAULTS to
:name when omitted, and :queue-group joins the endpoint's subscription to a
queue group (the server load-balances each request to one member). :handler
is an ordinary ADR-0007 push Handler, invoked per request with the decoded
message {:subject :data :reply} and answering it with respond; a returned
promise applies per-endpoint backpressure. There is no Group noun — a consumer
composes a grouped subject directly (ADR 0024).
:metadata — at the service level and per endpoint — is a flat map serialized
onto the wire as a JSON object of STRING keys and STRING values, identically on
both legs: a keyword key or value contributes its name (no leading colon),
anything else its string form. Discovery (ping/info/stats) lifts it back
as that same string-keyed map of strings.
The Service binds ONE codec at create — conn's default codec, unless :codec
in config (a registry keyword or ICodec instance) overrides it — used to
decode every request and encode every reply across all the Service's endpoints
(ADR 0011). A single respond / respond-error may still override the codec
per call. An unresolvable :codec rejects the create promise pre-flight.
The resolved Service handle carries a :stopped key holding a platform-native
promise that resolves to nil once the Service stops for any reason — the
react-to-shutdown signal the lifecycle parallel of the Watch handle's
:initialized (ADR 0024), so a consumer awaits it instead of polling.
Create and start a Service on `conn` from the portable `config`, returning a
platform-native promise (CompletableFuture on the JVM, js/Promise on CLJS) that
resolves to a running Service — the value `stop` tears down. There is no context
and no server feature to verify at entry: the promise resolves as soon as the
Service's endpoints are subscribed (ADR 0024). The config IS validated pre-flight,
before any native or wire call (ADR 0015): an omitted `:name` or `:version`
rejects the promise with `:type :missing-required-key` carrying the offending
`:key`, a malformed service or endpoint `:name` with `:invalid-name`, a non-semver
`:version` with `:invalid-version` carrying the offending `:version`, and two
endpoints sharing a `:name` with `:duplicate-endpoint` carrying the offending
`:name`. Endpoint `:subject` syntax stays native/server-enforced; empty or absent
`:endpoints` is legal.
Config keys: `:name` (required), `:version` (required), `:description`,
`:metadata`, and `:endpoints`, a vector of endpoint maps. Each endpoint is
`{:name :subject :handler :queue-group :metadata}`: `:name` and `:handler` are
required, `:subject` is the subject the endpoint listens on and DEFAULTS to
`:name` when omitted, and `:queue-group` joins the endpoint's subscription to a
queue group (the server load-balances each request to one member). `:handler`
is an ordinary ADR-0007 push Handler, invoked per request with the decoded
message `{:subject :data :reply}` and answering it with `respond`; a returned
promise applies per-endpoint backpressure. There is no Group noun — a consumer
composes a grouped subject directly (ADR 0024).
`:metadata` — at the service level and per endpoint — is a flat map serialized
onto the wire as a JSON object of STRING keys and STRING values, identically on
both legs: a keyword key or value contributes its name (no leading colon),
anything else its string form. Discovery (`ping`/`info`/`stats`) lifts it back
as that same string-keyed map of strings.
The Service binds ONE codec at create — `conn`'s default codec, unless `:codec`
in `config` (a registry keyword or `ICodec` instance) overrides it — used to
decode every request and encode every reply across all the Service's endpoints
(ADR 0011). A single `respond` / `respond-error` may still override the codec
per call. An unresolvable `:codec` rejects the create promise pre-flight.
The resolved Service handle carries a `:stopped` key holding a platform-native
promise that resolves to nil once the Service stops for any reason — the
react-to-shutdown signal the lifecycle parallel of the Watch handle's
`:initialized` (ADR 0024), so a consumer awaits it instead of polling.(error msg)Read the service error a reply Message msg carries (ADR 0025): nil when the
reply is a normal success, or {:code <int> :description <string>} when the
Service answered with an error (respond-error, or the auto-500 on a thrown /
rejected handler). The opt-in reader of the Nats-Service-Error /
Nats-Service-Error-Code headers core/request leaves intact on the reply — a
service error is data the caller branches on, NOT a thrown transport failure, so
core/request resolves normally and never sniffs these headers (ADR 0025). The
:code is returned as an integer, parsed from the header's wire string form.
Read the service error a reply Message `msg` carries (ADR 0025): `nil` when the
reply is a normal success, or `{:code <int> :description <string>}` when the
Service answered with an error (`respond-error`, or the auto-500 on a thrown /
rejected handler). The opt-in reader of the `Nats-Service-Error` /
`Nats-Service-Error-Code` headers `core/request` leaves intact on the reply — a
service error is data the caller branches on, NOT a thrown transport failure, so
`core/request` resolves normally and never sniffs these headers (ADR 0025). The
`:code` is returned as an integer, parsed from the header's wire string form.(info conn)(info conn opts)Discover what the running Services conn can reach OFFER, resolving a native
promise of a VECTOR of info maps — each ping identity plus :description and
:endpoints, a vector of {:name :subject} (and :queue-group/:metadata when
set). Same opts, narrowing, bounding, and normalization as ping (ADR 0024).
Discover what the running Services `conn` can reach OFFER, resolving a native
promise of a VECTOR of info maps — each `ping` identity plus `:description` and
`:endpoints`, a vector of `{:name :subject}` (and `:queue-group`/`:metadata` when
set). Same `opts`, narrowing, bounding, and normalization as `ping` (ADR 0024).(ping conn)(ping conn opts)Discover the running Services conn can reach, resolving a platform-native
promise of a VECTOR of identity maps {:name :id :version} (and :metadata —
a string-keyed map of string values, the one portable metadata shape — when a
Service declared some) — the client side of the surface, hanging directly off
the Connection (ADR 0024). There is no Discovery handle and no local introspection
of a Service this same connection hosts: self-inspection is a wire request like
any other, narrowed by opts.
opts (all optional): :name narrows to Services of that name, :id to a single
instance, alone or together with :name; :max-results and :timeout-ms BOUND
the $SRV.PING fan-out so the gather terminates predictably even when the
Service count is unknown — :max-results stops after that many replies,
:timeout-ms after that long. A zero-endpoint Service still answers, so it is
discoverable here.
The result is normalized byte-identically across legs: kebab-case EDN with the
wire type discriminator dropped.
Discover the running Services `conn` can reach, resolving a platform-native
promise of a VECTOR of identity maps `{:name :id :version}` (and `:metadata` —
a string-keyed map of string values, the one portable metadata shape — when a
Service declared some) — the client side of the surface, hanging directly off
the Connection (ADR 0024). There is no Discovery handle and no local introspection
of a Service this same connection hosts: self-inspection is a wire request like
any other, narrowed by `opts`.
`opts` (all optional): `:name` narrows to Services of that name, `:id` to a single
instance, alone or together with `:name`; `:max-results` and `:timeout-ms` BOUND
the `$SRV.PING` fan-out so the gather terminates predictably even when the
Service count is unknown — `:max-results` stops after that many replies,
`:timeout-ms` after that long. A zero-endpoint Service still answers, so it is
discoverable here.
The result is normalized byte-identically across legs: kebab-case EDN with the
wire `type` discriminator dropped.(respond conn msg data)(respond conn msg data opts)Reply to a request message msg (the one an endpoint handler received) with
data, encoding it with the Service's bound codec and answering through the
request's native service message so the owning endpoint's native stats stay
correct (ADR 0024). Sugar over the native respond, the service analog of
core/reply; returns nil. opts may set :codec to override the Service's
codec for this single reply, so a polyglot reply can match the request's codec
(ADR 0011).
Reply to a request message `msg` (the one an endpoint handler received) with `data`, encoding it with the Service's bound codec and answering through the request's native service message so the owning endpoint's native stats stay correct (ADR 0024). Sugar over the native respond, the service analog of `core/reply`; returns nil. `opts` may set `:codec` to override the Service's codec for this single reply, so a polyglot reply can match the request's codec (ADR 0011).
(respond-error conn msg code description)(respond-error conn msg code description data)(respond-error conn msg code description data opts)Reply to a request message msg with a first-class service error (ADR 0025):
an integer code and a string description, optionally carrying data as the
reply body (encoded with the Service's bound codec, as respond), routed through the
request's native service message so the owning endpoint's native error stats
stay correct. Sets the Nats-Service-Error / Nats-Service-Error-Code headers
the caller reads back with error; conn is threaded as in respond. Returns nil.
This is a SUCCESSFUL reply carrying an error payload, not a transport failure —
the caller's plain core/request resolves normally and branches on
(service/error reply), which is {:code … :description …} here (ADR 0025). A
handler that throws or returns a rejected promise auto-replies the same shape
with code 500; this is the explicit form. opts may set :codec to override the
Service's codec for this single reply's data encode (ADR 0011).
NOT terminal: like respond (and core/reply) the handler keeps running after
it, and exactly one reply reaches the wire. It does NOT move the endpoint's
num_errors — both natives tally an endpoint error only on an UNCAUGHT handler
failure (the auto-500 path), never on an error REPLY, and jnats offers no other
lever on its counter, so the portable stats relay that native truth (ADR 0025).
Reply to a request message `msg` with a first-class service error (ADR 0025):
an integer `code` and a string `description`, optionally carrying `data` as the
reply body (encoded with the Service's bound codec, as `respond`), routed through the
request's native service message so the owning endpoint's native error stats
stay correct. Sets the `Nats-Service-Error` / `Nats-Service-Error-Code` headers
the caller reads back with `error`; conn is threaded as in `respond`. Returns nil.
This is a SUCCESSFUL reply carrying an error payload, not a transport failure —
the caller's plain `core/request` resolves normally and branches on
`(service/error reply)`, which is `{:code … :description …}` here (ADR 0025). A
handler that throws or returns a rejected promise auto-replies the same shape
with code 500; this is the explicit form. `opts` may set `:codec` to override the
Service's codec for this single reply's `data` encode (ADR 0011).
NOT terminal: like `respond` (and `core/reply`) the handler keeps running after
it, and exactly one reply reaches the wire. It does NOT move the endpoint's
`num_errors` — both natives tally an endpoint error only on an UNCAUGHT handler
failure (the auto-500 path), never on an error REPLY, and jnats offers no other
lever on its counter, so the portable stats relay that native truth (ADR 0025).(stats conn)(stats conn opts)Discover the running Services' INSTRUMENTATION, resolving a native promise of a
VECTOR of stats maps — each ping identity plus :started (the canonical
timestamp string, same form as KV :created) and :endpoints, a vector of
per-endpoint counter maps {:name :subject :num-requests :num-errors :processing-time-ns :average-processing-time-ns} (and :queue-group/:last-error
when set). A handled request moves :num-requests; an UNCAUGHT handler failure —
a throw or a rejected promise, the auto-500 path — moves :num-errors; an
explicit respond-error does not (ADR 0025). Durations are
integer NANOSECONDS; the per-endpoint custom :data blob, when a Service supplies
one, passes through as parsed JSON→EDN — NOT via the connection codec. Same opts,
narrowing, bounding, and normalization as ping (ADR 0024).
Discover the running Services' INSTRUMENTATION, resolving a native promise of a
VECTOR of stats maps — each `ping` identity plus `:started` (the canonical
timestamp string, same form as KV `:created`) and `:endpoints`, a vector of
per-endpoint counter maps `{:name :subject :num-requests :num-errors
:processing-time-ns :average-processing-time-ns}` (and `:queue-group`/`:last-error`
when set). A handled request moves `:num-requests`; an UNCAUGHT handler failure —
a throw or a rejected promise, the auto-500 path — moves `:num-errors`; an
explicit `respond-error` does not (ADR 0025). Durations are
integer NANOSECONDS; the per-endpoint custom `:data` blob, when a Service supplies
one, passes through as parsed JSON→EDN — NOT via the connection codec. Same `opts`,
narrowing, bounding, and normalization as `ping` (ADR 0024).(stop svc)Stop the Service svc, returning a platform-native promise that resolves to nil
once it has stopped — tearing the Service's endpoints down (ADR 0024). The stop
DRAINS in-flight requests: a request being handled when stop is called runs to
completion and still receives its reply, never dropped mid-request. After it
settles the endpoints are gone, so a fresh request to one rejects with the
canonical :no-responders (ADR 0006) — services hosts no responder once stopped.
Idempotent: a second stop is a safe no-op. There is no reset in v1.
Stop the Service `svc`, returning a platform-native promise that resolves to nil once it has stopped — tearing the Service's endpoints down (ADR 0024). The stop DRAINS in-flight requests: a request being handled when `stop` is called runs to completion and still receives its reply, never dropped mid-request. After it settles the endpoints are gone, so a fresh request to one rejects with the canonical `:no-responders` (ADR 0006) — services hosts no responder once stopped. Idempotent: a second `stop` is a safe no-op. There is no `reset` in v1.
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 |