tempest-react-sdk 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,6 +28,7 @@ import { ForwardRefExoticComponent } from 'react';
28
28
  import { HashRouter } from 'react-router-dom';
29
29
  import { HTMLAttributes } from 'react';
30
30
  import { ImgHTMLAttributes } from 'react';
31
+ import { InfiniteData } from '@tanstack/react-query';
31
32
  import { InputEventHandler } from 'react';
32
33
  import { InputHTMLAttributes } from 'react';
33
34
  import { JSX } from 'react/jsx-runtime';
@@ -47,6 +48,7 @@ import { Path } from 'react-hook-form';
47
48
  import { PersistOptions } from 'zustand/middleware';
48
49
  import { PointerEventHandler } from 'react';
49
50
  import { QueryClient } from '@tanstack/react-query';
51
+ import { QueryKey } from '@tanstack/react-query';
50
52
  import { ReactElement } from 'react';
51
53
  import { ReactEventHandler } from 'react';
52
54
  import { ReactNode } from 'react';
@@ -77,10 +79,12 @@ import { UseFormProps } from 'react-hook-form';
77
79
  import { UseFormRegister } from 'react-hook-form';
78
80
  import { UseFormReturn } from 'react-hook-form';
79
81
  import { useFormState } from 'react-hook-form';
82
+ import { UseInfiniteQueryOptions } from '@tanstack/react-query';
80
83
  import { useLocation } from 'react-router-dom';
81
84
  import { useMatch } from 'react-router-dom';
82
85
  import { useNavigate } from 'react-router-dom';
83
86
  import { useParams } from 'react-router-dom';
87
+ import { UseQueryOptions } from '@tanstack/react-query';
84
88
  import { useRouteError } from 'react-router-dom';
85
89
  import { useSearchParams } from 'react-router-dom';
86
90
  import { useWatch } from 'react-hook-form';
@@ -157,6 +161,12 @@ export declare interface ApiClientConfig {
157
161
  baseURL: string;
158
162
  /** Returns the current bearer token (or null/undefined). Called per request. */
159
163
  getToken?: () => string | null | undefined;
164
+ /**
165
+ * Per-request correlation id sent as the `X-Request-ID` header, matching the
166
+ * Tempest FastAPI SDK `RequestIDMiddleware`. Defaults to a generated id.
167
+ * Return an empty string to disable the header.
168
+ */
169
+ requestId?: () => string;
160
170
  /** Called on 401 responses. Use it to logout the user or trigger a refresh. */
161
171
  onUnauthorized?: (response: Response) => void | Promise<void>;
162
172
  /**
@@ -173,8 +183,26 @@ export declare interface ApiClientConfig {
173
183
  }
174
184
 
175
185
  export declare interface ApiError {
186
+ /** HTTP status code (0 for network failures). */
176
187
  status: number;
188
+ /** Human-readable message — the backend envelope's `detail` (or `message`). */
177
189
  detail: string;
190
+ /**
191
+ * Programmatic error code from the Tempest FastAPI SDK envelope (`code`),
192
+ * e.g. `"EMAIL_TAKEN"`. Lets callers branch without parsing `detail`.
193
+ */
194
+ code?: string;
195
+ /**
196
+ * Correlation id echoed from the backend envelope's `details.request_id`
197
+ * (or the `X-Request-ID` response header). Pair it with `createLogger`.
198
+ */
199
+ requestId?: string;
200
+ /**
201
+ * Seconds to wait before retrying, parsed from the `Retry-After` response
202
+ * header (commonly on `429`/`503`). Honored by {@link retry}.
203
+ */
204
+ retryAfter?: number;
205
+ /** The raw parsed error body, when available. */
178
206
  body?: unknown;
179
207
  }
180
208
 
@@ -558,6 +586,23 @@ export declare const BREAKPOINTS: Record<Breakpoint, number>;
558
586
 
559
587
  export { BrowserRouter }
560
588
 
589
+ /**
590
+ * Parse an error body + response into the Tempest {@link ApiError} envelope.
591
+ *
592
+ * Reads `detail`/`message`, the programmatic `code`, and the correlation id
593
+ * from `details.request_id` (falling back to the `X-Request-ID` header, then
594
+ * the id the client sent).
595
+ *
596
+ * @param status - HTTP status code.
597
+ * @param body - The parsed error body (object, string, or null).
598
+ * @param headers - The response headers (for the `X-Request-ID` fallback).
599
+ * @param sentRequestId - The id the client sent on the request, if any.
600
+ * @returns A fully-populated `ApiError`.
601
+ */
602
+ export declare function buildApiError(status: number, body: unknown, headers?: Headers | {
603
+ get(name: string): string | null;
604
+ }, sentRequestId?: string): ApiError;
605
+
561
606
  /**
562
607
  * Primary action button with variants, sizes and a loading state that
563
608
  * preserves layout via an absolutely-positioned spinner.
@@ -1259,6 +1304,17 @@ export declare interface CreateLoggerOptions {
1259
1304
  */
1260
1305
  export declare function createOfflineStore<TItem, TKey extends string | number = string>(config: OfflineStoreConfig<TItem>): OfflineStore<TItem, TKey>;
1261
1306
 
1307
+ /**
1308
+ * Build a `206 Partial Content` response from a full one for an HTTP `Range`
1309
+ * request. Supports `bytes=start-end`, open-ended `bytes=start-` and suffix
1310
+ * `bytes=-suffixLength`. Returns the original response when there is no usable
1311
+ * `Range` header, or a `416` when the range is unsatisfiable.
1312
+ *
1313
+ * @param request The incoming request (its `Range` header drives the slice).
1314
+ * @param response The full (200) response to slice.
1315
+ */
1316
+ export declare function createPartialResponse(request: Request, response: Response): Promise<Response>;
1317
+
1262
1318
  /**
1263
1319
  * Build a [[TelemetryAdapter]] backed by [`posthog-js`](https://posthog.com/docs/libraries/js).
1264
1320
  * The PostHog client is supplied by the caller (not bundled).
@@ -1433,6 +1489,65 @@ export declare interface CreateStorePersistOptions<T> {
1433
1489
  migrate?: PersistOptions<T, Partial<T>>["migrate"];
1434
1490
  }
1435
1491
 
1492
+ /**
1493
+ * Turn-key auth preset wiring `createAuthStore` + `createRefreshQueue` +
1494
+ * `createApiClient` to the Tempest FastAPI SDK auth contract: login returns
1495
+ * `{ access_token, token_type }`, requests carry `Authorization: Bearer`, and a
1496
+ * `401` triggers a single deduplicated refresh + retry. Logout (or a failed
1497
+ * refresh) clears the session.
1498
+ *
1499
+ * @example
1500
+ * const auth = createTempestAuth<User, { email: string; password: string }>({
1501
+ * baseURL: import.meta.env.VITE_API_URL,
1502
+ * mePath: "/api/auth/me",
1503
+ * });
1504
+ *
1505
+ * await auth.login({ email, password }); // stores session, returns the user
1506
+ * const orders = await auth.api.get("/api/orders"); // sends the bearer token
1507
+ * auth.logout();
1508
+ *
1509
+ * @param options - The auth configuration.
1510
+ * @returns The store hook, a wired API client, and login/logout/refresh helpers.
1511
+ */
1512
+ export declare function createTempestAuth<TUser, TCredentials = {
1513
+ email: string;
1514
+ password: string;
1515
+ }>(options: CreateTempestAuthOptions<TUser>): TempestAuth<TUser, TCredentials>;
1516
+
1517
+ export declare interface CreateTempestAuthOptions<TUser> {
1518
+ /** Base URL of the API. Required. */
1519
+ baseURL: string;
1520
+ /** Login route (`POST`). Default: `"/api/auth/login"`. */
1521
+ loginPath?: string;
1522
+ /** Refresh route (`POST`). Default: `"/api/auth/refresh"`. */
1523
+ refreshPath?: string;
1524
+ /** Optional current-user route (`GET`) called after login/refresh. */
1525
+ mePath?: string;
1526
+ /** Persist key for the store. Default: `"tempest-auth"`. */
1527
+ storeName?: string;
1528
+ /** Storage backend. Default: `"local"`. */
1529
+ storage?: "local" | "session";
1530
+ /** Send cookies (needed when the refresh token lives in an httpOnly cookie). */
1531
+ withCredentials?: boolean;
1532
+ /** Custom fetch implementation (testing / SSR). Defaults to `globalThis.fetch`. */
1533
+ fetcher?: typeof fetch;
1534
+ /**
1535
+ * Extract tokens from a login/refresh response. Default reads
1536
+ * `access_token` + `refresh_token`.
1537
+ */
1538
+ parseTokens?: (data: unknown) => {
1539
+ token: string;
1540
+ refreshToken?: string;
1541
+ };
1542
+ /** Pull the user out of the login response, when the API embeds it. */
1543
+ parseUser?: (data: unknown) => TUser | null;
1544
+ /**
1545
+ * Build the refresh request body. Default sends `{ refresh_token }` when a
1546
+ * refresh token is stored, else `undefined` (cookie-based refresh).
1547
+ */
1548
+ refreshBody?: (refreshToken: string | null) => unknown;
1549
+ }
1550
+
1436
1551
  /**
1437
1552
  * Open a WebSocket with automatic exponential-backoff reconnect, optional
1438
1553
  * heartbeat pings, and typed JSON parsing.
@@ -1468,6 +1583,26 @@ export declare interface CreateWebSocketOptions<T> {
1468
1583
  onStatusChange?: (status: WebSocketStatus) => void;
1469
1584
  }
1470
1585
 
1586
+ /** Cursor-paginated response envelope (`CursorPaginationSchema[T]`). */
1587
+ export declare interface CursorPage<T> {
1588
+ /** The rows for this batch. */
1589
+ items: T[];
1590
+ /** Opaque cursor for the next batch, or null when exhausted. Pass it back verbatim. */
1591
+ next_cursor: string | null;
1592
+ /** Whether another batch exists. */
1593
+ has_more: boolean;
1594
+ /** Batch size used for this query. */
1595
+ limit: number;
1596
+ }
1597
+
1598
+ /** Cursor filter query params (`CursorPaginationFilterSchema`). */
1599
+ export declare interface CursorParams {
1600
+ cursor?: string | null;
1601
+ limit?: number;
1602
+ order_by?: string;
1603
+ ascending?: boolean;
1604
+ }
1605
+
1471
1606
  /**
1472
1607
  * Generic, typed list that renders a `<ul>` with one `<li>` per item.
1473
1608
  *
@@ -1767,6 +1902,14 @@ export declare interface ElementSize {
1767
1902
  height: number;
1768
1903
  }
1769
1904
 
1905
+ /**
1906
+ * Build an empty {@link OffsetPage} — handy as `placeholderData`/`initialData`.
1907
+ *
1908
+ * @param pageSize - The page size to report (default 20).
1909
+ * @returns An empty offset page on page 1.
1910
+ */
1911
+ export declare function emptyOffsetPage<T>(pageSize?: number): OffsetPage<T>;
1912
+
1770
1913
  /** Centered "nothing here yet" placeholder with optional icon and CTA. */
1771
1914
  export declare function EmptyState({ icon, title, description, action, className }: EmptyStateProps): JSX.Element;
1772
1915
 
@@ -2455,6 +2598,42 @@ export declare interface InputProps extends Omit<InputHTMLAttributes<HTMLInputEl
2455
2598
 
2456
2599
  export declare type InputSize = "sm" | "md" | "lg";
2457
2600
 
2601
+ /**
2602
+ * Install the background-sync queue: on a failed mutating request, the request
2603
+ * is serialized to IndexedDB and a sync is registered; the original fetch still
2604
+ * rejects (so your app can show an offline state), and the request is replayed
2605
+ * later when the network returns.
2606
+ */
2607
+ export declare function installBackgroundSync(options?: InstallBackgroundSyncOptions): void;
2608
+
2609
+ /**
2610
+ * Background-sync helper: queue failed mutating requests (POST/PUT/PATCH/DELETE)
2611
+ * while offline and replay them when connectivity returns. A dependency-free
2612
+ * take on Workbox's `BackgroundSyncPlugin`, backed by a tiny IndexedDB queue.
2613
+ *
2614
+ * Import inside your `sw.ts`. Uses the Background Sync API (`registration.sync`)
2615
+ * when available, and also replays opportunistically on the next request as a
2616
+ * fallback for browsers without it (e.g. Safari).
2617
+ *
2618
+ * @example
2619
+ * import { installBackgroundSync } from "tempest-react-sdk/sw";
2620
+ *
2621
+ * installBackgroundSync({ match: (url) => url.pathname.startsWith("/api/") });
2622
+ */
2623
+ /** Options for {@link installBackgroundSync}. */
2624
+ export declare interface InstallBackgroundSyncOptions {
2625
+ /**
2626
+ * Which requests to queue on failure. A `RegExp` against the URL or a
2627
+ * predicate. Only non-`GET` requests are ever considered. Default: all
2628
+ * non-`GET` requests.
2629
+ */
2630
+ match?: RegExp | ((url: URL, request: Request) => boolean);
2631
+ /** IndexedDB database name, also used as the sync tag. Default `tempest-bg-sync`. */
2632
+ queueName?: string;
2633
+ /** Drop queued requests older than this (minutes) on replay. Default `1440` (24h). */
2634
+ maxRetentionMinutes?: number;
2635
+ }
2636
+
2458
2637
  /**
2459
2638
  * Install a `notificationclick` handler that focuses an existing client when
2460
2639
  * possible and falls back to opening a new window.
@@ -2466,6 +2645,32 @@ export declare interface InstallNotificationClickHandlerOptions {
2466
2645
  resolveUrl?: (data: unknown) => string;
2467
2646
  }
2468
2647
 
2648
+ /**
2649
+ * Precache the app shell at `install` and serve it offline:
2650
+ * - reads `precache-manifest.json` (emitted by `tempestPwaManifest()`),
2651
+ * - caches every listed URL under a versioned cache,
2652
+ * - on `activate`, deletes stale precache versions and claims open clients,
2653
+ * - on `fetch`, serves precached assets cache-first and falls back to the
2654
+ * `navigateFallback` document for offline navigations (SPA routing).
2655
+ *
2656
+ * Same-origin only. Register this LAST, after any {@link installRuntimeCache}.
2657
+ */
2658
+ export declare function installPrecache(options?: InstallPrecacheOptions): void;
2659
+
2660
+ /** Options for {@link installPrecache}. */
2661
+ export declare interface InstallPrecacheOptions {
2662
+ /** URL of the manifest emitted by `tempestPwaManifest()`. Default `/precache-manifest.json`. */
2663
+ manifestUrl?: string;
2664
+ /** Cache name prefix; the manifest `version` is appended. Default `tempest-precache`. */
2665
+ cacheName?: string;
2666
+ /** App-shell document served for navigation requests offline. Default `/index.html`. */
2667
+ navigateFallback?: string;
2668
+ /** Navigation paths that should NOT use the fallback (e.g. `[/^\/api\//]`). */
2669
+ navigateFallbackDenylist?: RegExp[];
2670
+ /** Activate the new worker immediately after precaching. Default `true`. */
2671
+ skipWaiting?: boolean;
2672
+ }
2673
+
2469
2674
  /**
2470
2675
  * Install a `push` event listener that parses the payload as JSON (with a
2471
2676
  * plain-text fallback) and shows a notification.
@@ -2486,6 +2691,18 @@ export declare interface InstallPushHandlerOptions {
2486
2691
  transform?: (payload: PushPayload) => PushPayload | null;
2487
2692
  }
2488
2693
 
2694
+ /**
2695
+ * Install a `fetch` handler that resolves matching `GET` requests with the
2696
+ * given runtime strategies. Non-matching requests are left untouched (no
2697
+ * `respondWith`), so a later {@link installPrecache} can handle them.
2698
+ *
2699
+ * Register this BEFORE `installPrecache` so specific routes win over the
2700
+ * precache catch-all.
2701
+ *
2702
+ * @param routes Ordered rules; the first whose `match` passes handles the request.
2703
+ */
2704
+ export declare function installRuntimeCache(routes: RuntimeRoute[]): void;
2705
+
2489
2706
  /**
2490
2707
  * Install a `message` listener that activates a waiting worker when the host
2491
2708
  * app sends `{ type: "SKIP_WAITING" }`.
@@ -2494,6 +2711,23 @@ export declare function installSkipWaitingListener(): void;
2494
2711
 
2495
2712
  export declare type InterpolationValues = Record<string, string | number>;
2496
2713
 
2714
+ /**
2715
+ * Type guard for the {@link ApiError} shape. Matches both {@link TempestApiError}
2716
+ * instances and plain objects carrying `status` + `detail`.
2717
+ *
2718
+ * @param error - The unknown value (typically a caught error).
2719
+ * @returns Whether `error` conforms to the `ApiError` contract.
2720
+ */
2721
+ export declare function isApiError(error: unknown): error is ApiError;
2722
+
2723
+ /**
2724
+ * Type guard for a {@link CursorPage} envelope.
2725
+ *
2726
+ * @param value - The unknown value to test.
2727
+ * @returns Whether it carries `items` + `has_more` + a `next_cursor` key.
2728
+ */
2729
+ export declare function isCursorPage<T = unknown>(value: unknown): value is CursorPage<T>;
2730
+
2497
2731
  /**
2498
2732
  * Type guard asserting a value is neither `null` nor `undefined`.
2499
2733
  *
@@ -2543,6 +2777,14 @@ export declare function isJWTExpired(token: string, leewaySeconds?: number): boo
2543
2777
  */
2544
2778
  export declare function isNumber(value: unknown): value is number;
2545
2779
 
2780
+ /**
2781
+ * Type guard for an {@link OffsetPage} envelope.
2782
+ *
2783
+ * @param value - The unknown value to test.
2784
+ * @returns Whether it carries `items` + `total` + `page` + `pages`.
2785
+ */
2786
+ export declare function isOffsetPage<T = unknown>(value: unknown): value is OffsetPage<T>;
2787
+
2546
2788
  /**
2547
2789
  * Type guard asserting a value is a plain object literal.
2548
2790
  *
@@ -2989,6 +3231,33 @@ export declare interface OfflineStoreConfig<TItem> {
2989
3231
  ownerField?: keyof TItem & string;
2990
3232
  }
2991
3233
 
3234
+ /** Offset-paginated response envelope (fastapi-pagination `Page[T]`). */
3235
+ export declare interface OffsetPage<T> {
3236
+ /** The rows for the current page. */
3237
+ items: T[];
3238
+ /** Total number of matching rows across all pages. */
3239
+ total: number;
3240
+ /** Current 1-based page number. */
3241
+ page: number;
3242
+ /** Total number of pages. */
3243
+ pages: number;
3244
+ /** Page size — fastapi-pagination emits `size`. */
3245
+ size?: number;
3246
+ /** Page size — some backends name it `page_size` instead. */
3247
+ page_size?: number;
3248
+ }
3249
+
3250
+ /** Offset filter query params (fastapi-pagination / `BasePaginationFilterSchema`). */
3251
+ export declare interface OffsetParams {
3252
+ page?: number;
3253
+ /** Page size — fastapi-pagination convention. */
3254
+ size?: number;
3255
+ /** Page size — `page_size` convention. */
3256
+ page_size?: number;
3257
+ order_by?: string;
3258
+ ascending?: boolean;
3259
+ }
3260
+
2992
3261
  /**
2993
3262
  * Create a new object with the given `keys` removed from `obj`.
2994
3263
  *
@@ -3587,6 +3856,13 @@ export declare interface RetryOptions {
3587
3856
  initialDelay?: number;
3588
3857
  /** Maximum delay between attempts. Default: 10_000. */
3589
3858
  maxDelay?: number;
3859
+ /**
3860
+ * Honor a `Retry-After` hint on the thrown error (`error.retryAfter`, in
3861
+ * seconds — populated by {@link createApiClient} on `429`/`503`). When
3862
+ * present it overrides the exponential backoff for that attempt. The value
3863
+ * is capped at `maxDelay`. Default: true.
3864
+ */
3865
+ respectRetryAfter?: boolean;
3590
3866
  /**
3591
3867
  * Return false to stop retrying for a specific error.
3592
3868
  * Default: retry on any thrown error.
@@ -3635,6 +3911,51 @@ export declare type RouterKind = "browser" | "hash" | "memory";
3635
3911
 
3636
3912
  export { Routes }
3637
3913
 
3914
+ /** A single runtime-caching rule, matched against each `GET` request. */
3915
+ export declare interface RuntimeRoute {
3916
+ /** A `RegExp` tested against the full URL, or a predicate over the parsed URL. */
3917
+ match: RegExp | ((url: URL, request: Request) => boolean);
3918
+ /** How to resolve a match. */
3919
+ strategy: RuntimeStrategy;
3920
+ /** Cache bucket name for this route. */
3921
+ cacheName: string;
3922
+ /** Trim the cache to at most this many entries (FIFO) after each write. */
3923
+ maxEntries?: number;
3924
+ /** Treat a cached response older than this (seconds) as a miss. */
3925
+ maxAgeSeconds?: number;
3926
+ /** For `network-first`: fall back to cache after this timeout (seconds). */
3927
+ networkTimeoutSeconds?: number;
3928
+ /**
3929
+ * Serve HTTP `Range` requests (206 Partial Content) by slicing the cached
3930
+ * full response. Enable for audio/video so seeking works offline. The full
3931
+ * resource is cached once (the `Range` header is stripped before caching).
3932
+ */
3933
+ rangeRequests?: boolean;
3934
+ }
3935
+
3936
+ /**
3937
+ * Service-worker caching helpers — a small, dependency-free subset of what
3938
+ * Workbox provides: precaching of the build's app shell (so the app launches
3939
+ * offline) plus runtime caching strategies for fonts, APIs and images.
3940
+ *
3941
+ * Import these inside your own `sw.ts`. They run in the service-worker global
3942
+ * scope, not the main thread. Pair `installPrecache` with the
3943
+ * `tempestPwaManifest()` Vite plugin (from `tempest-react-sdk/vite`), which
3944
+ * emits the `precache-manifest.json` this reads at install time.
3945
+ *
3946
+ * @example
3947
+ * /// <reference lib="webworker" />
3948
+ * import { installRuntimeCache, installPrecache } from "tempest-react-sdk/sw";
3949
+ *
3950
+ * // Register specific routes FIRST so they win over the precache catch-all.
3951
+ * installRuntimeCache([
3952
+ * { match: /\/api\//, strategy: "network-first", cacheName: "api", maxAgeSeconds: 300 },
3953
+ * ]);
3954
+ * installPrecache();
3955
+ */
3956
+ /** Caching strategy for a runtime route. Mirrors the common Workbox trio. */
3957
+ export declare type RuntimeStrategy = "cache-first" | "network-first" | "stale-while-revalidate";
3958
+
3638
3959
  /**
3639
3960
  * Apply `env(safe-area-inset-*)` padding so content avoids iOS notch /
3640
3961
  * Android navbar / device chrome. Wrap the outermost container of pages
@@ -4192,6 +4513,46 @@ export declare interface TelemetryUser {
4192
4513
  traits?: Record<string, unknown>;
4193
4514
  }
4194
4515
 
4516
+ /**
4517
+ * Error thrown by {@link createApiClient} / {@link uploadWithProgress} on a
4518
+ * non-2xx response. Mirrors the Tempest FastAPI SDK error envelope
4519
+ * (`{ detail, code, details.request_id }`) so callers get a typed `code` and a
4520
+ * `requestId` for log correlation, while still being a real `Error` (stack
4521
+ * trace, `instanceof Error`).
4522
+ *
4523
+ * @example
4524
+ * try {
4525
+ * await api.post("/users", { body });
4526
+ * } catch (err) {
4527
+ * if (isApiError(err) && err.code === "EMAIL_TAKEN") {
4528
+ * showFieldError("email", err.detail);
4529
+ * }
4530
+ * }
4531
+ */
4532
+ export declare class TempestApiError extends Error implements ApiError {
4533
+ readonly status: number;
4534
+ readonly detail: string;
4535
+ readonly code?: string;
4536
+ readonly requestId?: string;
4537
+ readonly body?: unknown;
4538
+ constructor(init: ApiError);
4539
+ }
4540
+
4541
+ export declare interface TempestAuth<TUser, TCredentials> {
4542
+ /** The persisted Zustand auth store hook (compatible with `<AuthGuard>`). */
4543
+ useAuthStore: ReturnType<typeof createAuthStore<TUser>>;
4544
+ /** A `createApiClient` wired with bearer auth + 401 → refresh → retry. */
4545
+ api: ApiClient;
4546
+ /** Authenticate, store the session, and resolve the user (or null). */
4547
+ login: (credentials: TCredentials) => Promise<TUser | null>;
4548
+ /** Clear the session (and the stored refresh token). */
4549
+ logout: () => void;
4550
+ /** Refresh the access token (deduplicated across concurrent callers). */
4551
+ refresh: () => Promise<void>;
4552
+ /** The current access token, or null. */
4553
+ getToken: () => string | null;
4554
+ }
4555
+
4195
4556
  /**
4196
4557
  * A single declarative route node. Mirrors React Router's nested `<Route>`
4197
4558
  * model but adds first-class `lazy` (code-split with retry) and `guard`
@@ -4224,6 +4585,16 @@ export declare interface TempestRouteObject {
4224
4585
  caseSensitive?: boolean;
4225
4586
  }
4226
4587
 
4588
+ /** The token envelope returned by a Tempest FastAPI SDK login/refresh route. */
4589
+ export declare interface TempestTokenResponse {
4590
+ /** The bearer access token. */
4591
+ access_token: string;
4592
+ /** Token type — always `"bearer"` for the SDK. */
4593
+ token_type?: string;
4594
+ /** Optional refresh token (when the API returns it in the body, not a cookie). */
4595
+ refresh_token?: string;
4596
+ }
4597
+
4227
4598
  /**
4228
4599
  * Multi-line text input. Mirrors the {@link Input} API for label/helper/error.
4229
4600
  */
@@ -4545,7 +4916,7 @@ export declare interface UploadProgressEvent {
4545
4916
  *
4546
4917
  * `fetch` cannot report upload progress in browsers, so this helper falls
4547
4918
  * back to `XMLHttpRequest`. It mirrors the error contract used by
4548
- * {@link createApiClient}: non-2xx responses throw an {@link ApiError}.
4919
+ * {@link createApiClient}: non-2xx responses reject with a `TempestApiError`.
4549
4920
  *
4550
4921
  * @returns The parsed JSON response, or the raw text when the response is not JSON.
4551
4922
  */
@@ -4566,6 +4937,11 @@ export declare interface UploadWithProgressOptions {
4566
4937
  signal?: AbortSignal;
4567
4938
  /** Override the JSON parser. Defaults to `JSON.parse`. */
4568
4939
  parser?: (raw: string) => unknown;
4940
+ /**
4941
+ * Per-request correlation id sent as `X-Request-ID` (Tempest convention).
4942
+ * Defaults to a generated id. Return an empty string to disable.
4943
+ */
4944
+ requestId?: () => string;
4569
4945
  }
4570
4946
 
4571
4947
  /**
@@ -4680,6 +5056,52 @@ export declare interface UseClipboardResult {
4680
5056
  reset: () => void;
4681
5057
  }
4682
5058
 
5059
+ /**
5060
+ * Cursor-pagination hook over TanStack Query's `useInfiniteQuery` for the
5061
+ * Tempest `CursorPaginationSchema` envelope
5062
+ * (`{ items, next_cursor, has_more, limit }`).
5063
+ *
5064
+ * The opaque `next_cursor` is fed straight back as the next page param; the
5065
+ * loop stops when `has_more` is false (or `next_cursor` is null).
5066
+ *
5067
+ * @example
5068
+ * const feed = useCursorQuery<Post>({
5069
+ * queryKey: ["feed"],
5070
+ * limit: 30,
5071
+ * queryFn: (params) => postsService.listPosts(params),
5072
+ * });
5073
+ * // feed.items, feed.fetchNextPage(), feed.hasNextPage
5074
+ */
5075
+ export declare function useCursorQuery<T>(options: UseCursorQueryOptions<T>): UseCursorQueryResult<T>;
5076
+
5077
+ export declare interface UseCursorQueryOptions<T> extends Omit<UseInfiniteQueryOptions<CursorPage<T>, Error, InfiniteData<CursorPage<T>, string | null>, QueryKey, string | null>, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> {
5078
+ /** Base query key — `limit`/sort is appended for cache isolation. */
5079
+ queryKey: QueryKey;
5080
+ /** Fetcher receiving the cursor params; return the backend envelope. */
5081
+ queryFn: (params: CursorParams) => Promise<CursorPage<T>> | CursorPage<T>;
5082
+ /** Batch size sent as `limit`. Default: 20. */
5083
+ limit?: number;
5084
+ /** Initial `order_by`. */
5085
+ orderBy?: string;
5086
+ /** Initial `ascending`. Default: false. */
5087
+ ascending?: boolean;
5088
+ }
5089
+
5090
+ export declare interface UseCursorQueryResult<T> {
5091
+ /** All rows fetched so far, flattened across batches. */
5092
+ items: T[];
5093
+ /** The raw batches, in fetch order. */
5094
+ pages: CursorPage<T>[];
5095
+ hasNextPage: boolean;
5096
+ isLoading: boolean;
5097
+ isFetchingNextPage: boolean;
5098
+ error: Error | null;
5099
+ /** Fetch the next batch (no-op when exhausted). */
5100
+ fetchNextPage: () => void;
5101
+ /** Refetch from the first batch. */
5102
+ refetch: () => void;
5103
+ }
5104
+
4683
5105
  /**
4684
5106
  * Debounce a fast-changing value. Returns the latest value once `delay` ms
4685
5107
  * have elapsed without further changes.
@@ -4965,6 +5387,71 @@ export declare interface UseOAuthCallbackResult<T> {
4965
5387
  */
4966
5388
  export declare function useOnline(): boolean;
4967
5389
 
5390
+ /**
5391
+ * Offset-pagination hook over TanStack Query for the fastapi-pagination /
5392
+ * Tempest envelope (`{ items, total, page, size, pages }`).
5393
+ *
5394
+ * It owns the page state, sends `page` + the size param (`size` by default,
5395
+ * or `page_size` via `sizeParam`) plus optional `order_by`/`ascending` to your
5396
+ * fetcher, keeps the previous page visible while the next loads, and derives
5397
+ * `hasNext`/`hasPrev`/`pageCount`.
5398
+ *
5399
+ * @example
5400
+ * const users = usePaginatedQuery<User>({
5401
+ * queryKey: ["users"],
5402
+ * pageSize: 25,
5403
+ * queryFn: (params) => usersService.listUsers(params),
5404
+ * });
5405
+ * // users.items, users.next(), users.hasNext, users.pageCount
5406
+ */
5407
+ export declare function usePaginatedQuery<T>(options: UsePaginatedQueryOptions<T>): UsePaginatedQueryResult<T>;
5408
+
5409
+ export declare interface UsePaginatedQueryOptions<T> extends Omit<UseQueryOptions<OffsetPage<T>, Error, OffsetPage<T>, QueryKey>, "queryKey" | "queryFn"> {
5410
+ /** Base query key — the current page/sort is appended for cache isolation. */
5411
+ queryKey: QueryKey;
5412
+ /** Fetcher receiving the offset params; return the backend envelope. */
5413
+ queryFn: (params: OffsetParams) => Promise<OffsetPage<T>> | OffsetPage<T>;
5414
+ /** Initial 1-based page. Default: 1. */
5415
+ initialPage?: number;
5416
+ /** Page size. Default: 20. */
5417
+ pageSize?: number;
5418
+ /**
5419
+ * Query-param name for the page size. fastapi-pagination uses `"size"`
5420
+ * (default); SDK `BasePaginationFilterSchema` configs may use `"page_size"`.
5421
+ */
5422
+ sizeParam?: "size" | "page_size";
5423
+ /** Initial `order_by` (only sent when set). */
5424
+ orderBy?: string;
5425
+ /** Initial `ascending` (only sent when `orderBy` is set). Default: false. */
5426
+ ascending?: boolean;
5427
+ }
5428
+
5429
+ export declare interface UsePaginatedQueryResult<T> {
5430
+ /** The current page envelope, or undefined while the first load is pending. */
5431
+ page: OffsetPage<T> | undefined;
5432
+ /** The rows of the current page (empty array while pending). */
5433
+ items: T[];
5434
+ /** Current 1-based page number (controlled by the hook). */
5435
+ pageNumber: number;
5436
+ /** Total page count from the last successful response. */
5437
+ pageCount: number;
5438
+ /** Total row count from the last successful response. */
5439
+ total: number;
5440
+ hasNext: boolean;
5441
+ hasPrev: boolean;
5442
+ isLoading: boolean;
5443
+ isFetching: boolean;
5444
+ error: Error | null;
5445
+ /** Jump to a specific 1-based page (clamped to >= 1). */
5446
+ setPage: (page: number) => void;
5447
+ /** Go to the next page when available. */
5448
+ next: () => void;
5449
+ /** Go to the previous page when available. */
5450
+ prev: () => void;
5451
+ /** Refetch the current page. */
5452
+ refetch: () => void;
5453
+ }
5454
+
4968
5455
  /**
4969
5456
  * Manage page/size state for paginated lists. Reset goes back to page 1
4970
5457
  * without touching the page size.