@zeno-lib/query 0.0.2 → 0.1.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.
- package/dist/prefetched-queries.d.mts +4 -5
- package/dist/query-caller.d.mts +48 -0
- package/dist/query-caller.mjs +71 -0
- package/dist/query-defaults.d.mts +7 -8
- package/dist/query-hydration-boundary.d.mts +9 -4
- package/dist/query-hydration-boundary.mjs +3 -1
- package/dist/query-map-Dbbk7vtk.d.mts +9 -0
- package/dist/query-map-XxynJ3jl.mjs +26 -0
- package/dist/query-route.d.mts +23 -0
- package/dist/query-route.mjs +54 -0
- package/dist/query-suspense-loading.d.mts +3 -4
- package/dist/query-suspense.d.mts +5 -6
- package/package.json +35 -21
|
@@ -2,8 +2,8 @@ import { DehydratedState } from "@tanstack/react-query";
|
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
//#region src/prefetched-queries.d.ts
|
|
4
4
|
/** Whether the nearest `PrefetchedQueries` above was handed at least one dehydrated query. */
|
|
5
|
-
declare function useHasPrefetchedQueries(): boolean;
|
|
6
|
-
type PrefetchedQueriesProps = {
|
|
5
|
+
export declare function useHasPrefetchedQueries(): boolean;
|
|
6
|
+
export type PrefetchedQueriesProps = {
|
|
7
7
|
children: ReactNode;
|
|
8
8
|
state: DehydratedState;
|
|
9
9
|
};
|
|
@@ -12,6 +12,5 @@ type PrefetchedQueriesProps = {
|
|
|
12
12
|
* render** (on the server pass as well as in the browser), so a `useSuspenseQuery` below it
|
|
13
13
|
* resolves synchronously in both and the two passes produce the same markup.
|
|
14
14
|
*/
|
|
15
|
-
declare function PrefetchedQueries({ children, state }: PrefetchedQueriesProps): import("react
|
|
16
|
-
//#endregion
|
|
17
|
-
export { PrefetchedQueries, PrefetchedQueriesProps, useHasPrefetchedQueries };
|
|
15
|
+
export declare function PrefetchedQueries({ children, state }: PrefetchedQueriesProps): import("react").JSX.Element;
|
|
16
|
+
//#endregion
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { t as QueryMap } from "./query-map-Dbbk7vtk.mjs";
|
|
2
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
3
|
+
//#region src/query-caller.d.ts
|
|
4
|
+
/** The part of TanStack's `QueryFunctionContext` a call reads. */
|
|
5
|
+
export type QueryCallContext = {
|
|
6
|
+
/** Finds the queries a `QueryHydrationBoundary` bound for its server pass. */
|
|
7
|
+
client?: QueryClient;
|
|
8
|
+
/** Abandons the request when TanStack cancels the query. */
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
};
|
|
11
|
+
/** What `name` takes: its one argument, or nothing. */
|
|
12
|
+
export type QueryInput<TQueries extends QueryMap, TName extends keyof TQueries> = Parameters<TQueries[TName]>[0];
|
|
13
|
+
/** What `name` resolves to. */
|
|
14
|
+
export type QueryData<TQueries extends QueryMap, TName extends keyof TQueries> = Awaited<ReturnType<TQueries[TName]>>;
|
|
15
|
+
/** A query the route answered with an error status. */
|
|
16
|
+
export declare class QueryRequestError extends Error {
|
|
17
|
+
readonly query: string;
|
|
18
|
+
readonly status: number;
|
|
19
|
+
constructor(query: string, status: number);
|
|
20
|
+
}
|
|
21
|
+
export type QueryCaller<TQueries extends QueryMap> = <TName extends keyof TQueries & string>(name: TName, input: QueryInput<TQueries, TName>, context?: QueryCallContext) => Promise<QueryData<TQueries, TName>>;
|
|
22
|
+
/**
|
|
23
|
+
* Builds the browser side of `createQueryRoute`: `callQuery(name, input,
|
|
24
|
+
* context)` posts to `${path}/${name}` and resolves to the query's data. Type
|
|
25
|
+
* it with the map the route serves, imported as a type only, so no server code
|
|
26
|
+
* reaches the bundle:
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* import type { queries } from "./queries"
|
|
30
|
+
*
|
|
31
|
+
* export const callQuery = createQueryCaller<typeof queries>({
|
|
32
|
+
* path: "/api/queries",
|
|
33
|
+
* })
|
|
34
|
+
*
|
|
35
|
+
* queryFn: (context) => callQuery("selectPosts", { authorId }, context)
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* Unlike a Server Function, each call is its own request, so a screen's
|
|
39
|
+
* queries run in parallel, and TanStack's `signal` cancels them. During a
|
|
40
|
+
* `QueryHydrationBoundary` prefetch, the call runs the bound server function
|
|
41
|
+
* in-process instead.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createQueryCaller<TQueries extends QueryMap>({ path }: {
|
|
44
|
+
/** Where the route is mounted, without the trailing name segment. */
|
|
45
|
+
path: string;
|
|
46
|
+
}): QueryCaller<TQueries>;
|
|
47
|
+
//#endregion
|
|
48
|
+
export type { QueryMap };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { i as getBoundQueries, r as findQuery, t as QUERY_CONTENT_TYPE } from "./query-map-XxynJ3jl.mjs";
|
|
2
|
+
import { parse, stringify } from "devalue";
|
|
3
|
+
//#region src/query-caller.ts
|
|
4
|
+
/** A query the route answered with an error status. */
|
|
5
|
+
var QueryRequestError = class extends Error {
|
|
6
|
+
constructor(query, status) {
|
|
7
|
+
super(`Query ${query} failed with status ${status}`);
|
|
8
|
+
this.name = "QueryRequestError";
|
|
9
|
+
this.query = query;
|
|
10
|
+
this.status = status;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Builds the browser side of `createQueryRoute`: `callQuery(name, input,
|
|
15
|
+
* context)` posts to `${path}/${name}` and resolves to the query's data. Type
|
|
16
|
+
* it with the map the route serves, imported as a type only, so no server code
|
|
17
|
+
* reaches the bundle:
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* import type { queries } from "./queries"
|
|
21
|
+
*
|
|
22
|
+
* export const callQuery = createQueryCaller<typeof queries>({
|
|
23
|
+
* path: "/api/queries",
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* queryFn: (context) => callQuery("selectPosts", { authorId }, context)
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Unlike a Server Function, each call is its own request, so a screen's
|
|
30
|
+
* queries run in parallel, and TanStack's `signal` cancels them. During a
|
|
31
|
+
* `QueryHydrationBoundary` prefetch, the call runs the bound server function
|
|
32
|
+
* in-process instead.
|
|
33
|
+
*/
|
|
34
|
+
function createQueryCaller({ path }) {
|
|
35
|
+
return async (name, input, context = {}) => {
|
|
36
|
+
const bound = context.client && getBoundQueries(context.client);
|
|
37
|
+
if (bound) {
|
|
38
|
+
const query = findQuery(bound, name);
|
|
39
|
+
if (!query) throw new Error(`callQuery: "${name}" is not in the queries bound to this QueryHydrationBoundary`);
|
|
40
|
+
return await query(input);
|
|
41
|
+
}
|
|
42
|
+
if (typeof window === "undefined") throw new Error(`callQuery: "${name}" ran on the server outside a QueryHydrationBoundary that binds its queries`);
|
|
43
|
+
const response = await fetch(`${path}/${encodeURIComponent(name)}`, {
|
|
44
|
+
body: stringify(input),
|
|
45
|
+
headers: { "content-type": QUERY_CONTENT_TYPE },
|
|
46
|
+
method: "POST",
|
|
47
|
+
redirect: "manual",
|
|
48
|
+
signal: context.signal
|
|
49
|
+
});
|
|
50
|
+
if (response.type === "opaqueredirect") {
|
|
51
|
+
reloadOnce();
|
|
52
|
+
throw new QueryRequestError(name, 401);
|
|
53
|
+
}
|
|
54
|
+
if (!(response.ok && response.headers.get("content-type")?.startsWith("application/json"))) throw new QueryRequestError(name, response.status);
|
|
55
|
+
return parse(await response.text());
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const RELOADED_AT_KEY = "zeno-query:reloaded-at";
|
|
59
|
+
/**
|
|
60
|
+
* Reloads the page, unless a redirect already did within the last minute: a
|
|
61
|
+
* redirect the reload does not clear (a trailing-slash rule, a middleware that
|
|
62
|
+
* redirects only API paths) would otherwise reload forever.
|
|
63
|
+
*/
|
|
64
|
+
function reloadOnce() {
|
|
65
|
+
const reloadedAt = Number(sessionStorage.getItem(RELOADED_AT_KEY));
|
|
66
|
+
if (Date.now() - reloadedAt < 6e4) return;
|
|
67
|
+
sessionStorage.setItem(RELOADED_AT_KEY, String(Date.now()));
|
|
68
|
+
window.location.reload();
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { QueryRequestError, createQueryCaller };
|
|
@@ -10,32 +10,31 @@
|
|
|
10
10
|
* Every duration is in milliseconds.
|
|
11
11
|
*/
|
|
12
12
|
/** Fast-moving data that keeps updating even while the tab is hidden: 15 s, refetched in the background. */
|
|
13
|
-
declare const liveBackgroundQueryDefaults: {
|
|
13
|
+
export declare const liveBackgroundQueryDefaults: {
|
|
14
14
|
readonly refetchInterval: number;
|
|
15
15
|
readonly refetchIntervalInBackground: true;
|
|
16
16
|
readonly staleTime: number;
|
|
17
17
|
};
|
|
18
18
|
/** Fast-moving data refreshed while it is on screen: 30 s. */
|
|
19
|
-
declare const liveQueryDefaults: {
|
|
19
|
+
export declare const liveQueryDefaults: {
|
|
20
20
|
readonly refetchInterval: number;
|
|
21
21
|
readonly staleTime: number;
|
|
22
22
|
};
|
|
23
23
|
/** Data that can be slightly stale: 1 min. */
|
|
24
|
-
declare const shortLivedQueryDefaults: {
|
|
24
|
+
export declare const shortLivedQueryDefaults: {
|
|
25
25
|
readonly staleTime: number;
|
|
26
26
|
};
|
|
27
27
|
/** Slower-changing detail data: 2 min. */
|
|
28
|
-
declare const longLivedQueryDefaults: {
|
|
28
|
+
export declare const longLivedQueryDefaults: {
|
|
29
29
|
readonly staleTime: number;
|
|
30
30
|
};
|
|
31
31
|
/** Reference data worth keeping longer: 5 min stale, 30 min in the cache (the one preset that raises `gcTime`). */
|
|
32
|
-
declare const cachedQueryDefaults: {
|
|
32
|
+
export declare const cachedQueryDefaults: {
|
|
33
33
|
readonly gcTime: number;
|
|
34
34
|
readonly staleTime: number;
|
|
35
35
|
};
|
|
36
36
|
/** Effectively immutable data: never stale. */
|
|
37
|
-
declare const staticQueryDefaults: {
|
|
37
|
+
export declare const staticQueryDefaults: {
|
|
38
38
|
readonly staleTime: number;
|
|
39
39
|
};
|
|
40
|
-
//#endregion
|
|
41
|
-
export { cachedQueryDefaults, liveBackgroundQueryDefaults, liveQueryDefaults, longLivedQueryDefaults, shortLivedQueryDefaults, staticQueryDefaults };
|
|
40
|
+
//#endregion
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { t as QueryMap } from "./query-map-Dbbk7vtk.mjs";
|
|
1
2
|
import { DehydrateOptions, QueryClient } from "@tanstack/react-query";
|
|
2
3
|
import { ReactNode } from "react";
|
|
3
4
|
//#region src/query-hydration-boundary.d.ts
|
|
4
|
-
type QueryHydrationBoundaryProps = {
|
|
5
|
+
export type QueryHydrationBoundaryProps = {
|
|
5
6
|
children: ReactNode;
|
|
6
7
|
/**
|
|
7
8
|
* Fills the server-side cache. Call each query through the same `queryOptions` factory the
|
|
@@ -17,6 +18,11 @@ type QueryHydrationBoundaryProps = {
|
|
|
17
18
|
createQueryClient?: () => QueryClient;
|
|
18
19
|
/** Forwarded to TanStack's `dehydrate` (e.g. to also dehydrate pending queries). */
|
|
19
20
|
dehydrateOptions?: DehydrateOptions;
|
|
21
|
+
/**
|
|
22
|
+
* The map your query route serves. A `queryFn` built on `createQueryCaller` then calls these
|
|
23
|
+
* functions in-process during `prefetch`, since the server has no URL to fetch them from.
|
|
24
|
+
*/
|
|
25
|
+
queries?: QueryMap;
|
|
20
26
|
};
|
|
21
27
|
/**
|
|
22
28
|
* Async **server component**: runs `prefetch` against a fresh `QueryClient` and dehydrates the
|
|
@@ -32,6 +38,5 @@ type QueryHydrationBoundaryProps = {
|
|
|
32
38
|
* </QueryHydrationBoundary>
|
|
33
39
|
* ```
|
|
34
40
|
*/
|
|
35
|
-
declare function QueryHydrationBoundary({ children, createQueryClient, dehydrateOptions, prefetch }: QueryHydrationBoundaryProps): Promise<import("react
|
|
36
|
-
//#endregion
|
|
37
|
-
export { QueryHydrationBoundary, QueryHydrationBoundaryProps };
|
|
41
|
+
export declare function QueryHydrationBoundary({ children, createQueryClient, dehydrateOptions, prefetch, queries }: QueryHydrationBoundaryProps): Promise<import("react").JSX.Element>;
|
|
42
|
+
//#endregion
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PrefetchedQueries } from "./prefetched-queries.mjs";
|
|
2
|
+
import { n as bindQueries } from "./query-map-XxynJ3jl.mjs";
|
|
2
3
|
import { QueryClient, dehydrate } from "@tanstack/react-query";
|
|
3
4
|
import { jsx } from "react/jsx-runtime";
|
|
4
5
|
//#region src/query-hydration-boundary.tsx
|
|
@@ -16,8 +17,9 @@ import { jsx } from "react/jsx-runtime";
|
|
|
16
17
|
* </QueryHydrationBoundary>
|
|
17
18
|
* ```
|
|
18
19
|
*/
|
|
19
|
-
async function QueryHydrationBoundary({ children, createQueryClient = () => new QueryClient(), dehydrateOptions, prefetch }) {
|
|
20
|
+
async function QueryHydrationBoundary({ children, createQueryClient = () => new QueryClient(), dehydrateOptions, prefetch, queries }) {
|
|
20
21
|
const queryClient = createQueryClient();
|
|
22
|
+
if (queries) bindQueries(queryClient, queries);
|
|
21
23
|
await prefetch(queryClient);
|
|
22
24
|
return /* @__PURE__ */ jsx(PrefetchedQueries, {
|
|
23
25
|
state: dehydrate(queryClient, dehydrateOptions),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
2
|
+
//#region src/query-map.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The server functions a query route serves, keyed by the name the browser
|
|
5
|
+
* calls them by. Each takes one argument and resolves to the query's data.
|
|
6
|
+
*/
|
|
7
|
+
type QueryMap = Record<string, (input: any) => Promise<unknown>>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { QueryMap as t };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/query-map.ts
|
|
2
|
+
/**
|
|
3
|
+
* The function `queries` serves as `name`. `Object.hasOwn`, so `constructor` or
|
|
4
|
+
* `__proto__` is not a query, and the type check, so a module namespace's
|
|
5
|
+
* `__esModule` flag is not one either.
|
|
6
|
+
*/
|
|
7
|
+
function findQuery(queries, name) {
|
|
8
|
+
const query = Object.hasOwn(queries, name) ? queries[name] : void 0;
|
|
9
|
+
return typeof query === "function" ? query : void 0;
|
|
10
|
+
}
|
|
11
|
+
/** The body type both ways; the route refuses any other. */
|
|
12
|
+
const QUERY_CONTENT_TYPE = "application/json";
|
|
13
|
+
/**
|
|
14
|
+
* The queries bound to a server `QueryClient`. A query function that runs
|
|
15
|
+
* against that client calls the server function in-process instead of going
|
|
16
|
+
* through the route, which a server render has no URL for.
|
|
17
|
+
*/
|
|
18
|
+
const boundQueries = /* @__PURE__ */ new WeakMap();
|
|
19
|
+
function bindQueries(client, queries) {
|
|
20
|
+
boundQueries.set(client, queries);
|
|
21
|
+
}
|
|
22
|
+
function getBoundQueries(client) {
|
|
23
|
+
return boundQueries.get(client);
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { getBoundQueries as i, bindQueries as n, findQuery as r, QUERY_CONTENT_TYPE as t };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { t as QueryMap } from "./query-map-Dbbk7vtk.mjs";
|
|
2
|
+
//#region src/query-route.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Builds the `POST` handler of a route that serves `queries` to the browser,
|
|
5
|
+
* one request per call, so the reads of a screen run in parallel. Mount it on a
|
|
6
|
+
* dynamic segment whose last part is the query's name:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // app/api/queries/[query]/route.ts
|
|
10
|
+
* export const { POST } = createQueryRoute(queries)
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* The body and the result travel through `devalue`, so a `Date`, a `Map`, a
|
|
14
|
+
* `bigint` or an `undefined` arrives as it was sent. An error the query throws
|
|
15
|
+
* is left to the framework, which answers 500 and reports it. Every entry of
|
|
16
|
+
* `queries` is a public endpoint: authorize inside it, as you would in a
|
|
17
|
+
* server action.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createQueryRoute(queries: QueryMap): {
|
|
20
|
+
POST: (request: Request) => Promise<Response>;
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export type { QueryMap };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { r as findQuery, t as QUERY_CONTENT_TYPE } from "./query-map-XxynJ3jl.mjs";
|
|
2
|
+
import { parse, stringify } from "devalue";
|
|
3
|
+
//#region src/query-route.ts
|
|
4
|
+
/** The largest body the route parses, the default Server Functions accept. */
|
|
5
|
+
const MAX_BODY_BYTES = 1048576;
|
|
6
|
+
const TRAILING_SLASH = /\/$/;
|
|
7
|
+
/**
|
|
8
|
+
* Builds the `POST` handler of a route that serves `queries` to the browser,
|
|
9
|
+
* one request per call, so the reads of a screen run in parallel. Mount it on a
|
|
10
|
+
* dynamic segment whose last part is the query's name:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* // app/api/queries/[query]/route.ts
|
|
14
|
+
* export const { POST } = createQueryRoute(queries)
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* The body and the result travel through `devalue`, so a `Date`, a `Map`, a
|
|
18
|
+
* `bigint` or an `undefined` arrives as it was sent. An error the query throws
|
|
19
|
+
* is left to the framework, which answers 500 and reports it. Every entry of
|
|
20
|
+
* `queries` is a public endpoint: authorize inside it, as you would in a
|
|
21
|
+
* server action.
|
|
22
|
+
*/
|
|
23
|
+
function createQueryRoute(queries) {
|
|
24
|
+
return { POST: async (request) => {
|
|
25
|
+
const query = findQuery(queries, queryName(request));
|
|
26
|
+
if (!query) return new Response(null, { status: 404 });
|
|
27
|
+
if (!request.headers.get("content-type")?.startsWith("application/json")) return new Response(null, { status: 415 });
|
|
28
|
+
if (Number(request.headers.get("content-length")) > MAX_BODY_BYTES) return new Response(null, { status: 413 });
|
|
29
|
+
const body = await request.text();
|
|
30
|
+
if (body.length > MAX_BODY_BYTES) return new Response(null, { status: 413 });
|
|
31
|
+
let input;
|
|
32
|
+
try {
|
|
33
|
+
input = parse(body);
|
|
34
|
+
} catch {
|
|
35
|
+
return new Response(null, { status: 400 });
|
|
36
|
+
}
|
|
37
|
+
const data = await query(input);
|
|
38
|
+
return new Response(stringify(data), { headers: {
|
|
39
|
+
"cache-control": "private, no-store",
|
|
40
|
+
"content-type": QUERY_CONTENT_TYPE
|
|
41
|
+
} });
|
|
42
|
+
} };
|
|
43
|
+
}
|
|
44
|
+
/** The path's last segment, or `""` when it is not a valid escape. */
|
|
45
|
+
function queryName(request) {
|
|
46
|
+
const segment = new URL(request.url).pathname.replace(TRAILING_SLASH, "").split("/").at(-1);
|
|
47
|
+
try {
|
|
48
|
+
return decodeURIComponent(segment ?? "");
|
|
49
|
+
} catch {
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { createQueryRoute };
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { ComponentPropsWithoutRef } from "react";
|
|
2
2
|
//#region src/query-suspense-loading.d.ts
|
|
3
|
-
type QuerySuspenseLoadingProps = ComponentPropsWithoutRef<"div">;
|
|
3
|
+
export type QuerySuspenseLoadingProps = ComponentPropsWithoutRef<"div">;
|
|
4
4
|
/**
|
|
5
5
|
* Single layout root for Suspense loading UI: set `className` and children here so `aria-busy` /
|
|
6
6
|
* `aria-live` apply to the same element as your skeleton stack (no inner wrapper div). Accepts
|
|
7
7
|
* all `div` props; the `aria-busy` and `aria-live` defaults can be overridden.
|
|
8
8
|
*/
|
|
9
|
-
declare function QuerySuspenseLoading({ "aria-busy": ariaBusy, "aria-live": ariaLive, children, ...props }: QuerySuspenseLoadingProps): import("react
|
|
10
|
-
//#endregion
|
|
11
|
-
export { QuerySuspenseLoading, QuerySuspenseLoadingProps };
|
|
9
|
+
export declare function QuerySuspenseLoading({ "aria-busy": ariaBusy, "aria-live": ariaLive, children, ...props }: QuerySuspenseLoadingProps): import("react").JSX.Element;
|
|
10
|
+
//#endregion
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
2
|
//#region src/query-suspense.d.ts
|
|
3
|
-
type QuerySuspenseErrorFallbackProps = {
|
|
3
|
+
export type QuerySuspenseErrorFallbackProps = {
|
|
4
4
|
error: Error;
|
|
5
5
|
onRetry: () => void;
|
|
6
6
|
};
|
|
7
7
|
/** Dev/test only: force a boundary's loading or error UI (e.g. for screenshots). Ignored in production. */
|
|
8
|
-
type QuerySuspenseDebugState = "loading" | "error";
|
|
8
|
+
export type QuerySuspenseDebugState = "loading" | "error";
|
|
9
9
|
/** Exported so callers can type wrapper components that forward `fallback` / `errorFallback`. */
|
|
10
|
-
type QuerySuspenseProps = {
|
|
10
|
+
export type QuerySuspenseProps = {
|
|
11
11
|
children: ReactNode;
|
|
12
12
|
/** Suspense fallback: usually a skeleton of the same layout. */
|
|
13
13
|
fallback: ReactNode;
|
|
@@ -25,6 +25,5 @@ type QuerySuspenseProps = {
|
|
|
25
25
|
* hydration gate: `fallback` is all it renders on the server and through hydration, unless a
|
|
26
26
|
* `PrefetchedQueries` (e.g. from `QueryHydrationBoundary`) above it was handed dehydrated queries.
|
|
27
27
|
*/
|
|
28
|
-
declare function QuerySuspense({ children, errorFallback, fallback, debugState }: QuerySuspenseProps): import("react
|
|
29
|
-
//#endregion
|
|
30
|
-
export { QuerySuspense, QuerySuspenseDebugState, QuerySuspenseErrorFallbackProps, QuerySuspenseProps };
|
|
28
|
+
export declare function QuerySuspense({ children, errorFallback, fallback, debugState }: QuerySuspenseProps): import("react").JSX.Element;
|
|
29
|
+
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeno-lib/query",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"url": "https://github.com/zeno-lib/zeno.git",
|
|
6
|
+
"directory": "packages/query"
|
|
7
|
+
},
|
|
4
8
|
"type": "module",
|
|
5
9
|
"exports": {
|
|
6
10
|
"./prefetched-queries": {
|
|
@@ -13,11 +17,21 @@
|
|
|
13
17
|
"import": "./dist/query-defaults.mjs",
|
|
14
18
|
"default": "./dist/query-defaults.mjs"
|
|
15
19
|
},
|
|
20
|
+
"./query-caller": {
|
|
21
|
+
"types": "./dist/query-caller.d.mts",
|
|
22
|
+
"import": "./dist/query-caller.mjs",
|
|
23
|
+
"default": "./dist/query-caller.mjs"
|
|
24
|
+
},
|
|
16
25
|
"./query-hydration-boundary": {
|
|
17
26
|
"types": "./dist/query-hydration-boundary.d.mts",
|
|
18
27
|
"import": "./dist/query-hydration-boundary.mjs",
|
|
19
28
|
"default": "./dist/query-hydration-boundary.mjs"
|
|
20
29
|
},
|
|
30
|
+
"./query-route": {
|
|
31
|
+
"types": "./dist/query-route.d.mts",
|
|
32
|
+
"import": "./dist/query-route.mjs",
|
|
33
|
+
"default": "./dist/query-route.mjs"
|
|
34
|
+
},
|
|
21
35
|
"./query-suspense": {
|
|
22
36
|
"types": "./dist/query-suspense.d.mts",
|
|
23
37
|
"import": "./dist/query-suspense.mjs",
|
|
@@ -32,34 +46,34 @@
|
|
|
32
46
|
"files": [
|
|
33
47
|
"dist"
|
|
34
48
|
],
|
|
35
|
-
"
|
|
36
|
-
"
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"devalue": "^6.0.3"
|
|
37
51
|
},
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@tanstack/react-query": "^5.104.1",
|
|
54
|
+
"@types/node": "26.6.4",
|
|
55
|
+
"@types/react": "19.3.0",
|
|
56
|
+
"@types/react-dom": "19.3.0",
|
|
57
|
+
"@zeno-lib/test": "^0.1.1",
|
|
58
|
+
"@zeno-lib/typescript": "^1.2.0",
|
|
59
|
+
"@zeno-lib/ui": "^0.0.1",
|
|
60
|
+
"react": "^19.3.0",
|
|
61
|
+
"react-dom": "^19.3.0",
|
|
62
|
+
"tsdown": "^0.23.0",
|
|
63
|
+
"typescript": "7.0.2",
|
|
64
|
+
"vite": "8.3.3",
|
|
65
|
+
"vitest": "5.0.3"
|
|
41
66
|
},
|
|
42
67
|
"peerDependencies": {
|
|
43
|
-
"@tanstack/react-query": ">=5",
|
|
68
|
+
"@tanstack/react-query": ">=5.66.0",
|
|
44
69
|
"react": ">=19"
|
|
45
70
|
},
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"@types/node": "25.6.0",
|
|
49
|
-
"@types/react": "19.2.14",
|
|
50
|
-
"@types/react-dom": "19.2.3",
|
|
51
|
-
"react": "^19.2.5",
|
|
52
|
-
"react-dom": "^19.2.5",
|
|
53
|
-
"tsdown": "^0.22.14",
|
|
54
|
-
"typescript": "7.0.2",
|
|
55
|
-
"vite": "8.0.16",
|
|
56
|
-
"vitest": "4.1.11",
|
|
57
|
-
"@zeno-lib/test": "^0.1.0",
|
|
58
|
-
"@zeno-lib/typescript": "^1.1.0",
|
|
59
|
-
"@zeno-lib/ui": "^0.0.1"
|
|
71
|
+
"publishConfig": {
|
|
72
|
+
"access": "public"
|
|
60
73
|
},
|
|
61
74
|
"scripts": {
|
|
62
75
|
"build": "tsdown",
|
|
76
|
+
"dev": "tsdown --watch --no-clean",
|
|
63
77
|
"test": "vitest --run",
|
|
64
78
|
"test:watch": "vitest watch",
|
|
65
79
|
"types:check": "tsc --noEmit"
|