API index and search · Build metadata
Supporting declarations
packages/react/src/index.ts. These declarations explain referenced types. Only package-page symbols are package exports.
AnyFragment
type AnyFragment = Fragment<any, any, any, any>;QueryData
export type QueryData<Q extends AnyQuery> = ReturnType<Q["materialize"]>["data"];RootData
export type RootData<Q extends AnyQuery> = QueryLocalData<Q>;RootRefData
export type RootRefData<Q extends AnyQuery, F extends Fragment<any, any, any, any>> = QueryData<Q> extends readonly unknown[] ? readonly FragmentRef<F>[] : FragmentRef<F> | null;RootDetails
export interface RootDetails {
readonly status: ResultType;
}RootResult
export type RootResult<Q extends AnyQuery> = readonly [
data: RootData<Q>,
details: RootDetails
];RootRefResult
export type RootRefResult<Q extends AnyQuery, F extends Fragment<any, any, any, any>> = readonly [
data: RootRefData<Q, F>,
details: RootDetails
];RindleProps
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;
}QueryLease
interface QueryLease {
id: number;
viewKey: string;
}SyncLease
interface SyncLease {
id: number;
coverageKey: string;
/** Resolved grace window for THIS lease (see {@link QueryReleaseOptions}). */
releaseDelayMs: number;
}QueryCacheOptions
interface QueryCacheOptions {
releaseDelayMs?: number;
}QueryReleaseOptions
/**
* 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:
*
* ```tsx
* 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: 0` one 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;
}RindleContextValue
declare class RindleContextValue {
readonly store: Store<ColsMap>;
readonly cache: QueryCache;
readonly syncCache: SyncQueryCache;
constructor(store: Store<ColsMap>, releaseDelayMs: number);
}Rindle
export declare function Rindle<S extends ColsMap>({ store, releaseDelayMs, children }: RindleProps<S>): import("react").FunctionComponentElement<import("react").ProviderProps<RindleContextValue | null>>;RindleProvider
export declare const RindleProvider: typeof Rindle;useRindleStore
export declare function useRindleStore<S extends ColsMap = ColsMap>(): Store<S>;RindleSSRProps
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;
}RindleSSR
/**
* 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`. `useQuery` reads its seed via `getServerSnapshot`, 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 live `subscribe`
* reconciles — 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>>;useQuery
export declare function useQuery<Q extends AnyQuery>(query: Q, opts?: QueryReleaseOptions): QueryData<Q>;useQueryStatus
/** 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;useSyncQuery
/** 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;useRoot
/** 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>;useFragment
/**
* 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;Frag
/**
* 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;SyncQueryCache
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;
}QueryCache
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
export declare function queryCacheKey(query: AnyQuery): string;