Repository navigation
v0.9.0 — real-time and long-running work
This release also carries everything planned for 0.8, which was never published
on its own. Upgrading from 0.7.x: read Breaking below; it's types plus one
change to chained .input().
New
- Background jobs —
createJobs({ store, pubsub, context }),
jobs.define(name, { input, run, key, retries, backoff, concurrency }), and
route.….background(job, map?), which validates, runs the guards, enqueues and
returns{ jobId }. Retries with backoff, one queued-or-running job per key, a
per-name concurrency cap, leases so a crashed worker's job is retried, and
job.watch(jobId, { signal })to stream status: each state once, in order,
with aversionthat goes up on every change. Stores:
createInMemoryJobStore()andcreatePgJobStore(pool). - Postgres pub/sub —
createPgPubSub(pool)overLISTEN/NOTIFY: one
listening connection per process, reconnect with backoff, one process's
messages arrive in order, SQL triggers can publish withpg_notify. Messages
must be under 8000 bytes. - Pluggable pub/sub —
createPubSub(transport)holds the shared subscription
logic; in-memory, Redis and Postgres are transports over it, and another broker
is onePubSubTransportaway. attachWsServer— WebSocket server with safe defaults: required Origin
allowlist,createContext(Request)once per connection before the socket
opens, 1 MB messages, 1 MB unsent backlog, 100 subscriptions per connection,
30 s heartbeat,revalidate,closeWhere, and a cleanclose()for shutdown.createWsConnection— a browser connection that reconnects (0.5 s doubling
to 10 s, with jitter), retries at once when the browser comes back online,
reports its status, stops on 4401 (signed out) untilreconnect(), and
resubscribes open subscriptions.- Typed, validated input in middleware —
.input()before.use()types and
validates the guard'sinput. - Cancellation — every call takes
{ signal }; stream handlers get asignal
that aborts when the client disconnects, cancels or unsubscribes. - Batching that doesn't stall — batch results stream back as each call
settles; the URL names the calls (__batch?p=a,b);{ batch: false }sends a
call alone. Old clients and servers still work with new ones. dendra/next—orPage(call, { signIn })turnsNOT_FOUNDinto
notFound()andUNAUTHORIZEDinto a sign-in redirect in Server Components.createWsSession(router, send, ctx, { maxSubscriptions }).
Fixed
- Client calls are real Promises (
.catch,.finally,instanceof Promise),
still lazy and still iterable for streams. toNodeHandleraborted every request as soon as its body was read.- A batch the server rejects as a whole now rejects every call in it; they used
to resolve toundefined. - Cancelling a pub/sub subscription that was never read leaked its listener (and
its Redis subscription).
Breaking
- Types only:
createClientandcreateWsClientresults are typed as what
survives JSON (Jsonify<Out>): aDateis typedstring,undefinedand
function properties are dropped.createCallerkeeps the handler's real types,
now asCaller<R>;Client<R>is the over-the-wire type. - Chained
.input(): a later.input()now extends the earlier one instead of
replacing it (objects only; the earlier schema wins a shared key), and both
validate the call.
Upgrading: parse date strings from client results instead of casting them to
Date; type createCaller results as Caller<R>; check any procedure that calls
.input() twice. If you hand-rolled a WebSocket server or a reconnecting browser
socket, attachWsServer and createWsConnection replace them.
Install: npm i dendra@0.9.0 · Full notes: https://products.techfoundry.io/dendra/releases