yatta/binding
The shared reactive contract every framework binding is built on.
11 exported symbols and 8 members, read from src/types/binding.ts.
Construct
bind
functionBinds one call to a store.
`start` is separate because a binding is built during a render, and a render must not do work — that is what makes it safe to render many times, and what stops a Server Component from multiplying requests.
bind<TArgs, T>(store: CallStore, method: (args?: TArgs) => Promise<T>, args: TArgs | undefined, options?: BindOptions): BoundCall<T>- store
- Where results live. Per request on a server, shared in a browser.
- method
- The route's method, taken from the app.
- args
- Its arguments. Part of the key, so different arguments never share.
callKey
functionThe key one call is stored under: its route and its arguments.
callKey<TArgs>(method: unknown, args: TArgs | undefined): stringmakeBinding
functionShares one framework subscription across many subscribers.
Written this way because the alternative — one subscription per component — is a subscription per component on the same store, which is what a framework charges for.
makeBinding(track: (notify: () => void) => void | (() => void)): Binding- track
- Starts the framework subscription and returns its teardown.
methodName
functionThe route a method came from, used as the key's prefix.
The derived methods are named after their routes, so this is the route name. Two routes cannot collide under one key — which matters, because a key built from arguments alone would let one route serve another's cached value.
methodName(method: unknown): stringsameState
functionWhether two states are indistinguishable to a reader.
Compared field by field, because the store hands out a fresh object on every write: an identity check would report a change on every notification and defeat the whole point of the filter above.
sameState<T>(a: CallState<T>, b: CallState<T>): booleanserverBinding
functionA binding that never notifies: correct on a server, where nothing updates.
serverBinding(): BindingstableStringify
functionSerialises arguments so key order does not matter.
`{ id: "1", q: 2 }` and `{ q: 2, id: "1" }` are the same call. Keying them differently makes a component refetch whenever its parent reorders a literal. The non-plain cases are handled explicitly, because `Object.entries` sees them as empty and every one of them silently becomes `"{}"`: - A `Date` has no own enumerable properties, so two *different* dates produced the same key. Every date-range query then shared one cache entry and returned whichever date happened to be cached first. - A `RegExp` and a `Map` or `Set` have the same problem. - A circular structure recursed until the stack overflowed, which took down the render that built the arguments. `undefined` and `{}` are the same call, so an omitted argument does not fork the key.
stableStringify(value: unknown): stringwatchCall
functionSubscribes to one call's changes.
The one thing every framework binding does. Returns the current state immediately, so a subscriber never renders one frame against an empty cache. Filtered to the key, and gated on the state actually having changed. Without either, the store's subscribe is global: one mutation anywhere woke every mounted component, each allocated a fresh state object, and each re-rendered. On a page with fifty calls that is fifty renders to change one, and the reason a small change could be felt as a janky one.
watchCall<T>(store: CallStore, key: string, onChange: (state: CallState<T>) => void): () => voidTypes
Binding
interfaceHow a binding is told the store changed.
One operation: register a callback and get back an unsubscribe.
interface Binding1 member
subscribesubscribe(notify: () => void): () => void
BindOptions
interfaceinterface BindOptions3 members
enabledpropertyenabled?: booleanSkip the load without tearing anything down, for an argument that is not
keypropertykey?: stringOverrides the derived key. Used by the mutation paths.
signalpropertysignal?: AbortSignalCancels the request.
Forwarded to `fetch` on the HTTP path, and readable from `ctx.signal` in a direct call. Without it, a component whose arguments change mid-flight cannot abandon the answers nobody is waiting for — the work still runs and still occupies a connection.
BoundCall
interfaceA bound call.
Reads through to the store, so `state` is correct whether or not a notification has arrived yet.
interface BoundCall<T>4 members
statepropertystate: CallState<T>startstart(): () => voidStarts the load. Returns a teardown; does nothing if already started.
refetchrefetch(): voidReloads, ignoring any cached value.
setset(updater: T | ((previous: T | undefined) => T)): voidWrites a value without a request, for an optimistic update.
AuthConfig or JobPayload — declaring your schema once is enough for the rest to follow. See Typed keys.