Rindle

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.

Exact source

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;