API index and search · Build metadata
@rindle/react
0.0.0 · Public export map; development manifest version (0.0.0).
assembleDurableText
FunctionDeclaration · Source: packages/client/src/stream.ts:87 · Supporting declarations
The durable half of the splice for the mapped-table layout: the compacted body followed by
whatever chunk rows have not been folded into it yet.
Chunks are ALWAYS the suffix after body — the closing checkpoint rewrites body and drops the
chunks it absorbed in ONE transaction — so a reader never observes a torn state where a chunk both
is and is not in the body.
export declare function assembleDurableText(message: {
body?: string | null;
} | null | undefined, chunks?: ReadonlyArray<{
seq: number;
text: string;
}>): string;DEFAULT_STREAM_ENDPOINT
VariableDeclaration · Source: packages/react/src/stream.ts:37 · Supporting declarations
Where {@link useStreamedText} subscribes by default — DEFAULT_RINDLE_API_ROUTES.stream, mirrored
here rather than imported so the browser never pulls @rindle/api-server.
export declare const DEFAULT_STREAM_ENDPOINT = "/api/rindle/stream";eventSourceTransport
FunctionDeclaration · Source: packages/react/src/stream.ts:81 · Supporting declarations
The default SSE transport. Absent EventSource (SSR, an older runtime, a test without jsdom) it
attaches nothing and the reader stays on the durable plane — correct, just chunkier.
export declare function eventSourceTransport(onError?: (err: unknown) => void): StreamTransport;Frag
FunctionDeclaration · Source: packages/react/src/index.ts:605 · Supporting declarations
Render-prop sugar over {@link useFragment}: does the null check once. from is a fragment ref
(or null/undefined — an absent to-one relationship, an emptied .one(), or a row deleted out from
under a live read); when the row is present children(data) renders, otherwise fallback (default
nothing). Keeps the per-row subscription isolation — a child-only edit re-renders just this read.
export declare function Frag<F extends AnyFragment>({ of, from, fallback, releaseDelayMs, children }: {
of: F;
from: FragmentRef<F> | null | undefined;
fallback?: ReactNode;
/** Per-call-site grace window — see {@link QueryReleaseOptions}. */
releaseDelayMs?: number;
children: (data: FragmentData<F>) => ReactNode;
}): ReactNode;Fragment
InterfaceDeclaration · Source: packages/client/src/query.ts:533 · Supporting declarations
A reusable, typed selection over a table — Relay's "fragment", promoted to a first-class value. Two verbs compose a fragment, on one axis — does its data belong to THIS row or to a related one:
- SAME node —
q.include(Frag): merge its selection into this node. This is also how you root a fragment onto a base query —queries.t.where.id(x).include(Frag). - CHILD node —
q.sub(alias, child, corr, Frag): nest it under a relationship (sub's 4th arg).
A Fragment is therefore also a build transform ((q: Query<C>) => Query<C, Rels>); that
call signature is the mechanism by which sub accepts a fragment as its build. Prefer
include to root a fragment (canonical + merged) over calling it directly. It additionally
carries its table (for {@link FragmentRef}/useFragment typing and masking). Composing
fragments assembles ONE {@link Ast} → one materialization → one /query — the whole point
(no request waterfall; design §1).
export interface Fragment<C extends AnyCols, Rels = {}, Sel extends string = never, LocalRels = Rels> {
(q: Query<C>): Query<C, Rels, false, Sel, LocalRels>;
readonly table: TableLike<C>;
readonly [FRAGMENT_BRAND]: true;
}FragmentData
TypeAliasDeclaration · Source: packages/client/src/query.ts:544 · Supporting declarations
The data returned by useFragment(fragment, ref): the fragment's own selected columns plus
immediate relationship values. Relationships whose builder is another fragment surface as
opaque {@link FragmentRef}s, so child-owned payload is read only by the child fragment reader.
export type FragmentData<F> = F extends Fragment<infer C, unknown, infer Sel, infer LocalRels> ? Projected<C, Sel> & LocalRels : never;fragmentKey
FunctionDeclaration · Source: packages/client/src/query.ts:605 · Supporting declarations
export declare function fragmentKey(ref: FragmentRef<any>): string;FragmentRef
TypeAliasDeclaration · Source: packages/client/src/query.ts:563 · Supporting declarations
The opaque token a parent passes to a component that reads {@link FragmentData} for F.
export type FragmentRef<F> = F extends Fragment<any, any, any, any> ? LocalFragmentRef<F> : never;QueryCache
ClassDeclaration · Source: packages/react/src/index.ts:1044 · Supporting declarations
export declare class QueryCache {
private readonly entries;
private nextLeaseId;
private readonly store;
private readonly defaultReleaseDelayMs;
constructor(store: Store<ColsMap>, opts?: QueryCacheOptions);
/** `releaseDelayMs` overrides the cache default for THIS lease only (see
* {@link QueryReleaseOptions}). Ignored for a local-only store, whose views are always torn down
* on release. */
retain<Q extends AnyQuery>(viewKey: string, query: Q, releaseDelayMs?: number): QueryLease;
release(lease: QueryLease): void;
subscribe(viewKey: string, listener: () => void): () => void;
snapshot(viewKey: string, one: boolean): unknown;
/** A query's current {@link ResultType} (from its view), or `unknown` before it is retained. */
resultType(viewKey: string): ResultType;
/** The SSR/hydration snapshot for `viewKey` — the store's preloaded/dehydrated seed, read
* WITHOUT retaining (the server never opens a subscription). Falls back to empty so a
* non-preloaded query renders like an unhydrated one. */
serverSnapshot(viewKey: string, one: boolean): unknown;
/** The SSR {@link ResultType}: `complete` when a seed exists (server-authoritative first paint),
* else `unknown`. */
serverResultType(viewKey: string): ResultType;
size(): number;
private createSplitEntry;
private createSplitLease;
private createMaterializedEntry;
private createMaterializedLease;
private chooseCanonical;
private setCanonical;
private resolveDelay;
/** Hand back every deferred remote lease and cancel the pending teardown, WITHOUT touching
* `entry.releaseDeadline`. Called when a retain revives the entry: the new lease covers the query,
* so the deferred ones are redundant, and the timer armed for an idle entry is stale. The deadline
* is not — it is an outstanding claim, and dropping it here would let a remount silently refresh a
* window that must only ever decay. */
private flushPendingReleases;
/** Arm (or re-arm) the teardown for `entry.releaseDeadline`. Because the deadline is an ABSOLUTE
* instant, re-arming is idempotent — a later release recomputes the same wake-up time instead of
* restarting the window. */
private scheduleSplitRelease;
private finalizeSplitRelease;
}queryCacheKey
FunctionDeclaration · Source: packages/react/src/index.ts:1281 · Supporting declarations
export declare function queryCacheKey(query: AnyQuery): string;QueryData
TypeAliasDeclaration · Source: packages/react/src/index.ts:49 · Supporting declarations
export type QueryData<Q extends AnyQuery> = ReturnType<Q["materialize"]>["data"];QueryReleaseOptions
InterfaceDeclaration · Source: packages/react/src/index.ts:189 · Supporting declarations
Per-call-site override for how long a query is kept warm after its LAST subscriber unmounts.
The default (2s, or whatever <Rindle releaseDelayMs> sets) exists so a changed filter/limit can
re-materialize from the still-warm local base while the replacement server lease streams its first
answer — it's what keeps navigation from flashing empty. That grace window is wrong for queries you
KNOW you will never come back to, the canonical case being typeahead search: every keystroke is a
distinct query, so a 2s window leaves one dead view + server subscription open per character typed.
Pass 0 there to tear down on unmount:
const results = useQuery(searchIssues(term), { releaseDelayMs: 0 });
Treat the value as a constant per call site — changing it re-leases the query (drops the old lease and takes a fresh one), which is wasted work if it changes every render.
The rule for a query several components share with DIFFERENT delays is a DEADLINE, not a duration:
every release stamps now + that lease's delay, and the query stays warm until the latest deadline
any of its leases asked for (max-wins over what REMAINS, matching the SSR preload TTL rule in
@rindle/client's ssr.ts). Two consequences worth internalizing:
- The clock starts when a subscriber LEAVES, never when it arrives — a mounted reader is never timed out, however long it stays.
- A deadline expires on its own, so a later lease inherits at most the RESIDUE of an older window,
never a fresh copy of it. Unmount a 2s reader, remount a
releaseDelayMs: 0one 1.9s later and drop it: teardown lands at the original 2s mark, not 1.9s past it.
Only meaningful against a backend that can retain remote queries. A local-only store (the SSR seed
over OneShotBackend, or a store with no remote leg) always tears its views down on release, so
there is no window to shorten.
export interface QueryReleaseOptions {
/** ms to keep this query warm after the last subscriber unmounts. `0` = release immediately.
* Defaults to the provider's `releaseDelayMs` (2s). */
releaseDelayMs?: number;
}ResultType
TypeAliasDeclaration · Source: packages/client/src/types.ts:155 · Supporting declarations
A query's SERVER-CHANNEL state, surfaced on its {@link ArrayView } (FOLDED-MUTATIONS-DESIGN §7 — formerly conflated with pending-ness, OPTIMISTIC-WRITES-DESIGN.md §6):
unknown— not hydrated: the server has not produced a first result for this query yet;complete— the server has answered. STAYScompletewhile a local mutation is pending (the prediction is the client's best current answer); reversion on rejection is an event (onRejected), not a downgrade of completeness;error— RESERVED for a future server-side, query-level error signal (seedesigns/101-QUERY-ERRORS-DESIGN.md); no longer produced by a pending mutation. "Is a prediction pending here?" is now a separate reactive axis (the backend'spending(qid)/onPending), not folded into this type. A backend with no server lifecycle (the in-process engine) leaves every viewcomplete.
export type ResultType = "unknown" | "complete" | "error";Rindle
FunctionDeclaration · Source: packages/react/src/index.ts:221 · Supporting declarations
export declare function Rindle<S extends ColsMap>({ store, releaseDelayMs, children }: RindleProps<S>): import("react").FunctionComponentElement<import("react").ProviderProps<RindleContextValue | null>>;RindleProps
InterfaceDeclaration · Source: packages/react/src/index.ts:77 · Supporting declarations
export interface RindleProps<S extends ColsMap = ColsMap> {
store: Store<S>;
/** Default grace window (ms) for every query in this tree — how long a view + its server lease are
* kept warm after the last subscriber unmounts. Defaults to 2s; see {@link QueryReleaseOptions}
* for why, and for the per-call-site override. Treat as a constant: changing it rebuilds the
* caches and tears down every live view. */
releaseDelayMs?: number;
children?: ReactNode;
}RindleProvider
VariableDeclaration · Source: packages/react/src/index.ts:232 · Supporting declarations
export declare const RindleProvider: typeof Rindle;RindleSSR
FunctionDeclaration · Source: packages/react/src/index.ts:268 · Supporting declarations
The SSR→SPA store handoff (SSR-DESIGN.md §6.1). Renders <Rindle> with a store that swaps from a
transport-less SSR seed to the live engine WITHOUT changing a single useQuery caller — bind the
app's schema + bootClient and drop it in above the tree:
- Server render + browser HYDRATION: a seed {@link Store} over a {@link OneShotBackend}, hydrated
from
ssrState.useQueryreads its seed viagetServerSnapshot, so the server and the client's first render produce identical markup with NO engine on either side — first paint is the server-rendered data, never a "Starting…" splash that would break hydration. - After hydration:
boot()starts the wasm IVM engine (browser only), its views are seeded from the SAME snapshot (so the swap shows the SSR rows with no flash), and the livesubscribereconciles — the page is now a live SPA.
Framework-agnostic of the app: everything but schema/boot/ssrState is owned here (previously
hand-rolled per app as src/RindleApp.tsx).
export declare function RindleSSR<S extends ColsMap>({ schema, ssrState, boot, children }: RindleSSRProps<S>): import("react").FunctionComponentElement<RindleProps<S>>;RindleSSRProps
InterfaceDeclaration · Source: packages/react/src/index.ts:238 · Supporting declarations
export interface RindleSSRProps<S extends ColsMap = ColsMap> {
/** The app schema — used to build the transport-less seed {@link Store} that backs the server
* render and the matching client hydration pass. */
schema: Schema<S>;
/** The dehydrated first-paint cache from the route loader (`ServerStore.dehydrate()`), embedded in
* the HTML. Read on BOTH the server render and the client's first (hydration) render. */
ssrState: DehydratedState;
/** Boots the live (wasm-backed) client in the BROWSER — the app's `bootClient`. Called once, after
* hydration, and must resolve to the live optimistic store. Never invoked during the server
* render (SSR seeds are a first-paint concern only). */
boot: () => Promise<{
store: Store<S>;
}>;
children?: ReactNode;
}RootData
TypeAliasDeclaration · Source: packages/react/src/index.ts:50 · Supporting declarations
export type RootData<Q extends AnyQuery> = QueryLocalData<Q>;RootDetails
InterfaceDeclaration · Source: packages/react/src/index.ts:53 · Supporting declarations
export interface RootDetails {
readonly status: ResultType;
}RootRefData
TypeAliasDeclaration · Source: packages/react/src/index.ts:51 · Supporting declarations
export type RootRefData<Q extends AnyQuery, F extends Fragment<any, any, any, any>> = QueryData<Q> extends readonly unknown[] ? readonly FragmentRef<F>[] : FragmentRef<F> | null;RootRefResult
TypeAliasDeclaration · Source: packages/react/src/index.ts:57 · Supporting declarations
export type RootRefResult<Q extends AnyQuery, F extends Fragment<any, any, any, any>> = readonly [
data: RootRefData<Q, F>,
details: RootDetails
];RootResult
TypeAliasDeclaration · Source: packages/react/src/index.ts:56 · Supporting declarations
export type RootResult<Q extends AnyQuery> = readonly [
data: RootData<Q>,
details: RootDetails
];spliceStreamText
FunctionDeclaration · Source: packages/client/src/stream.ts:75 · Supporting declarations
Merge the durable plane with the live tail.
durable is what the IVM view shows; produced is what a subscription has accumulated (the prefix
it joined at, plus every chunk). Both are prefixes of the same response, so the merge is "take
the longer" — no diffing, no overlap handling, no ranges.
The length comparison is the whole algorithm, which is why a caller MUST seed its accumulator with
the text it joined at: a tail carrying only the chunks it received would read as shorter than the
durable text and be discarded. useStreamedText does that for you.
export declare function spliceStreamText(durable: string, produced: string): string;StreamFrame
TypeAliasDeclaration · Source: packages/client/src/stream.ts:32 · Supporting declarations
One frame of a subscription. A subscription always begins with open and always ends with exactly
one terminal frame — end, stale, or absent — after which the iterator completes.
stale and absent are the two "you are on the durable plane now" answers, and both are SAFE:
the store holds everything below floorSeq and everything through durableSeq, so the reader's
IVM view converges without the stream. Neither is an error.
export type StreamFrame =
/** Join accepted. `from` is the (clamped) offset the replay starts at. */
{
type: "open";
streamId: string;
from: number;
seq: number;
durableSeq: number;
ended: boolean;
}
/** PRODUCED text — not a durability claim. `text.length === seq - from`, always. */
| {
type: "chunk";
from: number;
seq: number;
text: string;
}
/** The store now holds the prefix through `seq`. */
| {
type: "durable";
seq: number;
}
/** Sealed. No further frames. */
| {
type: "end";
seq: number;
status: StreamStatus;
error?: string;
}
/** `from` is below the producer's retained buffer floor (or the subscriber fell too far behind):
* read the store. (A raw `EventSource` rejoins automatically on its reconnect; `useStreamedText`
* deliberately stays on the durable plane instead — correct, at checkpoint granularity.) */
| {
type: "stale";
floorSeq: number;
durableSeq: number;
}
/** The process serving this subscribe is not hosting the stream (wrong instance, already evicted,
* or it never existed): the store is the whole truth. */
| {
type: "absent";
};StreamStatus
TypeAliasDeclaration · Source: packages/client/src/stream.ts:19 · Supporting declarations
How a stream ended.
complete— the model finished.cancelled— the reader asked it to stop and the producer honoured it.error— the generation threw.interrupted— the host went away mid-generation. The one status that implies the store may be short of what was produced.
export type StreamStatus = "complete" | "cancelled" | "error" | "interrupted";streamSubscribeUrl
FunctionDeclaration · Source: packages/react/src/stream.ts:74 · Supporting declarations
<endpoint>?streamId=…&from=…. from is the join offset; a reconnecting EventSource overrides
it with its own Last-Event-ID header, which the server prefers.
export declare function streamSubscribeUrl(endpoint: string, streamId: string, from: number): string;StreamTransport
InterfaceDeclaration · Source: packages/react/src/stream.ts:44 · Supporting declarations
How the live plane is reached. The default ({@link eventSourceTransport}) is SSE, which is what the
api-server's streamFramesToSse serves and what gets Last-Event-ID resume for free. Supply your
own for a WebSocket, a fetch-stream, or a test.
export interface StreamTransport {
/** Attach at `url` and call `onFrame` per decoded frame. MUST return a detach function; it may be
* called more than once and must tolerate that. `onFrame` may be called synchronously. */
subscribe(url: string, onFrame: (frame: StreamFrame) => void): () => void;
}SyncQueryCache
ClassDeclaration · Source: packages/react/src/index.ts:923 · Supporting declarations
export declare class SyncQueryCache {
private readonly entries;
private nextLeaseId;
private readonly store;
private readonly defaultReleaseDelayMs;
constructor(store: Store<ColsMap>, opts?: QueryCacheOptions);
/** `releaseDelayMs` overrides the cache default for THIS lease only (see
* {@link QueryReleaseOptions}) — `0` asks for no warm window of its own, though an unexpired
* deadline from an earlier lease on this coverage still applies. */
retain(coverageKey: string, query: AnyQuery, releaseDelayMs?: number): SyncLease;
release(lease: SyncLease): void;
subscribe(coverageKey: string, listener: () => void): () => void;
resultType(coverageKey: string): ResultType;
size(): number;
private createHandle;
private resolveDelay;
/** Arm (or re-arm) the teardown for `entry.releaseDeadline`. Because the deadline is an ABSOLUTE
* instant, re-arming is idempotent — a later release recomputes the same wake-up time instead of
* restarting the window. */
private scheduleRelease;
private finalizeRelease;
}useFragment
FunctionDeclaration · Source: packages/react/src/index.ts:591 · Supporting declarations
Read a {@link Fragment}'s local data from an opaque ref.
The query boundary calls {@link useRoot} with a fragment argument to retain the full named
coverage query and receive root refs. Descendants call useFragment with those refs (or child
refs returned by a parent fragment read) to open narrow local-only reads for the fields their
fragment owns.
ref is an opaque token created by {@link useRoot} or returned from another local fragment read.
The hook opens a narrow local-only query for this exact fragment and keeps the root coverage
lease retained while mounted. Passing a legacy projected data object is unsupported.
export declare function useFragment<F extends Fragment<any, any, any, any>>(fragment: F, ref: FragmentRef<F> | null | undefined, opts?: QueryReleaseOptions): FragmentData<F> | null;useQuery
FunctionDeclaration · Source: packages/react/src/index.ts:302 · Supporting declarations
export declare function useQuery<Q extends AnyQuery>(query: Q, opts?: QueryReleaseOptions): QueryData<Q>;useQueryStatus
FunctionDeclaration · Source: packages/react/src/index.ts:343 · Supporting declarations
The SERVER-CHANNEL state of a query's view (@rindle/client {@link ResultType}): unknown while
it loads (not yet server-authoritative), complete once the server has answered. A pending
optimistic mutation no longer moves this — that is a separate axis now (FOLDED-MUTATIONS-DESIGN
§7); the error variant is reserved and currently unproduced. Shares the same cached/leased view
as {@link useQuery} (so reading both for one query is one subscription), and re-renders only when
the status changes.
export declare function useQueryStatus(query: AnyQuery, opts?: QueryReleaseOptions): ResultType;useRindleStore
FunctionDeclaration · Source: packages/react/src/index.ts:234 · Supporting declarations
export declare function useRindleStore<S extends ColsMap = ColsMap>(): Store<S>;useRoot
FunctionDeclaration · Source: packages/react/src/index.ts:411 · Supporting declarations
Run a named root query and expose its local React-facing data. Fragment child relationships are refs, so child components can keep owning their own local reads. Passing a root fragment as the final argument switches the result to opaque root refs for that fragment.
export declare function useRoot<Q extends AnyQuery>(query: Q): RootResult<Q>;
export declare function useRoot<Q extends AnyQuery, F extends AnyFragment>(query: Q, fragment: F): RootRefResult<Q, F>;
export declare function useRoot<Q extends AnyQuery>(query: NamedQuery<void, [
], Q>): RootResult<Q>;
export declare function useRoot<Q extends AnyQuery, F extends AnyFragment>(query: NamedQuery<void, [
], Q>, fragment: F): RootRefResult<Q, F>;
export declare function useRoot<Args, Ctx extends readonly unknown[], Q extends AnyQuery>(query: NamedQuery<Args, Ctx, Q>, args: Args, ...ctx: Ctx): RootResult<Q>;
export declare function useRoot<Args, Ctx extends readonly unknown[], Q extends AnyQuery, F extends AnyFragment>(query: NamedQuery<Args, Ctx, Q>, args: Args, ...ctxAndFragment: [
...ctx: Ctx,
fragment: F
]): RootRefResult<Q, F>;useStreamedText
FunctionDeclaration · Source: packages/react/src/stream.ts:131 · Supporting declarations
The response text as it should be rendered right now: the durable prefix spliced with the live tail.
const data = useFragment(MessageFragment, message);
const text = useStreamedText({
streamId: data.id,
durable: assembleDurableText(data, data.chunks),
live: data.status === "streaming" || data.status === "pending",
});
The value is monotone in practice — it only grows while a stream runs — and when the closing checkpoint compacts the chunks into the body it returns the identical string, so there is no flicker at the handoff.
export declare function useStreamedText({ streamId, durable, live }: UseStreamedTextInput, options?: UseStreamedTextOptions): string;UseStreamedTextInput
InterfaceDeclaration · Source: packages/react/src/stream.ts:50 · Supporting declarations
export interface UseStreamedTextInput {
/** The stream's id — the message row's key. Changing it drops the old tail and rejoins. */
streamId: string;
/** What the IVM view shows: `assembleDurableText(message, message.chunks)`. Read from a ref
* internally (TRAP 1), so it may change every checkpoint without disturbing the connection. */
durable: string;
/** Whether a producer is still running — the app's own read of its status column (typically
* `status === "streaming" || status === "pending"`). The live leg attaches only while true. */
live: boolean;
}UseStreamedTextOptions
InterfaceDeclaration · Source: packages/react/src/stream.ts:61 · Supporting declarations
export interface UseStreamedTextOptions {
/** Default {@link DEFAULT_STREAM_ENDPOINT}. `streamId` and `from` are appended as query params. */
endpoint?: string;
/** Default {@link eventSourceTransport}. Read at subscribe time, NOT a dependency — an inline
* literal would otherwise reconnect on every render. */
transport?: StreamTransport;
/** A frame that could not be decoded, or a transport-level error. The durable plane is unaffected,
* so this is a diagnostic, not a failure. */
onError?: (err: unknown) => void;
}useSyncQuery
FunctionDeclaration · Source: packages/react/src/index.ts:376 · Supporting declarations
Retain a named server query for normalized/local-first sync coverage without subscribing React
to that query's broad result tree. The returned value is lifecycle state only; it is unknown
until the backend reports that the retained coverage has hydrated.
export declare function useSyncQuery(query: AnyQuery, opts?: QueryReleaseOptions): ResultType;