Yare models APIs as intents: named outcomes with a declared state machine. Start one and you get a typed stream of frames, one per state transition, until it reaches a terminal state.
REST and GraphQL model the API as data. tRPC and RPC model it as procedures. Yare models it as the outcome you're trying to reach.
The name is archaic English for nimble, responsive, ready to move, historically said of ships that handle well.
This is a research project, not a product. The bet is that the combination of intent, state machine, durable execution, and exhaustive client matching is worth its own library. Don't treat any of the API decisions as settled.
Four packages, exercised end-to-end by eight runnable examples:
@yare/core:intent,state,ref,Frame, schema-derived type helpers, runtime + type-level validation, asuperjson-backed wire codec.@yare/server:handler()+serve()over Bun WebSockets. Handler context:transition,sleep,waitFor,signal. Optionalbun:sqlitepersistence so the frame log, current state, and pending notifies all survive a process restart.@yare/client:createClient(), async-iterableFlow<S>per intent,resume(schema, intentId, sinceSeq?),notify, optional autoReconnect.@yare/react:<YareProvider>anduseIntent(schema)returning an exhaustivematchover the schema's states plusidle.
CLAUDE.md has the design rationale, what's deferred, and the scaffold's known sharp edges.
packages/
core/ @yare/core schema DSL, wire codec, types
server/ @yare/server handler runtime, transport, durability
client/ @yare/client framework-agnostic client, frames as async iterator
react/ @yare/react useIntent hook with exhaustive match
examples/
basic/ single terminal state
checkout/ multi-state with retries and sleep
resume/ reattach after disconnect
gate/ waitFor + out-of-band notify
cancel/ flow.cancel() aborts an in-flight sleep
reconnect/ autoReconnect keeps a flow alive across socket drops
durable/ SQLite-backed; server2 picks up where server1 crashed
durable-gate/ durability + waitFor; a notify survives a restart
bun install
bun run typecheck
bun run build
bun run example:basic
Every example is one Bun process running both server and client, so the round trip fits in a single file:
- basic: fires a one-state
user.pingintent; prints its single terminal frame. - checkout:
checkout.completeretries a flaky mock-Stripe charge twice before settling. Doubles as proof thatDateround-trips through the wire codec. - resume: client A drops after three frames; client B reattaches via
client.resume(schema, intentId, lastSeq)and consumes the rest of the log, terminal frame included. - gate: handler pauses on
await waitFor('decision'); an out-of-bandclient.notify(intentId, 'decision', { ... })resolves it. The Temporal-shaped pattern in about fifty lines. - cancel:
flow.cancel()aborts the handler's in-flightsleep(5000)viaAbortSignal. Sleep rejects in under 300ms instead of running the full five seconds. - reconnect: with
autoReconnect: true, a long-running flow survives its underlying socket dropping. The client opens a fresh connection, replays the gap, and the caller sees a continuous frame stream. - durable: server1 starts an intent with
db: './yare-durable.db', sees a few frames, then stops mid-execution. Server2 opens the same file, picks the intent up at its persisted state, and a new client resumes by intentId to watch it finish. - durable-gate: durability plus
waitFor. Anotify('decision', { approved: true, approver: ... })arrives while the handler is in a pre-waitForstate; server1 crashes before the handler ever reaches thewaitFor. Server2 opens the same database, hydrates the persisted notify, re-runs the handler, and the resumedwaitForconsumes the pre-restart payload to transition intoapproved. The approval crossed the restart boundary.
TBD.