@beignet/nuqs 0.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # @beignet/nuqs
2
+
3
+ ## 0.0.1
4
+
5
+ - Initial Beignet release under the `@beignet` npm scope.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @beignet/nuqs
2
+
3
+ > nuqs integration for Beignet
4
+
5
+ This package gives you a thin bridge between contract query params and [`nuqs`](https://nuqs.dev) URL state.
6
+
7
+ It does not replace `@beignet/react-query`. Instead, it helps you keep URL-backed filters aligned with a contract's `query` shape and then pass that state into `rq(contract).queryOptions(...)`.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @beignet/nuqs @beignet/react-query nuqs react @tanstack/react-query
13
+ ```
14
+
15
+ In Next.js App Router, mount the `NuqsAdapter` once near the root:
16
+
17
+ ```tsx
18
+ import { NuqsAdapter } from "@beignet/nuqs/next/app";
19
+
20
+ export function Providers({ children }: { children: React.ReactNode }) {
21
+ return <NuqsAdapter>{children}</NuqsAdapter>;
22
+ }
23
+ ```
24
+
25
+ ## Setup
26
+
27
+ ```ts
28
+ import { createClient } from "@beignet/core/client";
29
+ import { createReactQuery } from "@beignet/react-query";
30
+ import { createNuqs } from "@beignet/nuqs";
31
+
32
+ const client = createClient({ baseUrl: "/api" });
33
+
34
+ export const rq = createReactQuery(client);
35
+ export const nq = createNuqs();
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```tsx
41
+ import { parseAsInteger, parseAsString } from "nuqs";
42
+ import { useQuery } from "@tanstack/react-query";
43
+ import { nq } from "@/client/nq";
44
+ import { rq } from "@/client/rq";
45
+ import { listContacts } from "@/features/contacts/contracts";
46
+
47
+ const contactsSearch = nq(listContacts).query({
48
+ parsers: {
49
+ search: parseAsString.withDefault(""),
50
+ offset: parseAsInteger.withDefault(0),
51
+ },
52
+ });
53
+
54
+ function ContactsPage() {
55
+ const [filters, setFilters] = contactsSearch.useState();
56
+
57
+ const query = useQuery(
58
+ contactsSearch.toQueryOptions(rq(listContacts), filters)
59
+ );
60
+
61
+ return null;
62
+ }
63
+ ```
64
+
65
+ ## API
66
+
67
+ ### `createNuqs()`
68
+
69
+ Creates the Beignet `nuqs` adapter factory.
70
+
71
+ ### `nq(contract).query({ parsers, ...options })`
72
+
73
+ Creates a contract-aware URL query helper.
74
+
75
+ - `parsers`: required `nuqs` parser map keyed by the contract's query params
76
+ - `history`, `shallow`, `scroll`, `urlKeys`, etc.: forwarded to `useQueryStates`
77
+
78
+ ### `.useState(options?)`
79
+
80
+ Wraps `nuqs` `useQueryStates(parsers, options)` with the configured parser map.
81
+
82
+ ### `.toQuery(state, { omitNullish })`
83
+
84
+ Converts `nuqs` state into a Beignet query object.
85
+
86
+ - `omitNullish` defaults to `true`
87
+ - `null` and `undefined` are removed
88
+ - valid falsy values like `0`, `false`, and `""` are preserved
89
+
90
+ ### `.toQueryOptions(rq(contract), state, options?)`
91
+
92
+ Convenience helper that calls `rq(contract).queryOptions({ query, ...options })`.
93
+
94
+ ## When to use this
95
+
96
+ `@beignet/nuqs` is a good fit for pages where the URL should reflect search or filter state:
97
+
98
+ - admin tables
99
+ - search screens
100
+ - dashboards
101
+ - reporting pages
102
+ - index/list views with tabs, pagination, or filters
103
+
104
+ If you just need typed fetching, use `@beignet/react-query` directly.
@@ -0,0 +1,46 @@
1
+ import type { EndpointCallArgs, InferQuery } from "@beignet/core/client";
2
+ import { type ContractLike, type HttpContractConfig, type ResolveContract } from "@beignet/core/contracts";
3
+ import type { ContractUseQueryOptions, ReactQueryContractHelper } from "@beignet/react-query";
4
+ import { type inferParserType, type UrlKeys, type UseQueryStatesKeysMap, type UseQueryStatesOptions, type UseQueryStatesReturn } from "./runtime";
5
+ type QueryShape<TContract extends HttpContractConfig> = NonNullable<InferQuery<TContract>>;
6
+ type QueryRecord<TContract extends HttpContractConfig> = QueryShape<TContract> extends Record<string, unknown> ? QueryShape<TContract> : never;
7
+ type QueryKeys<TContract extends HttpContractConfig> = keyof QueryRecord<TContract> & string;
8
+ type QueryParsers<TContract extends HttpContractConfig> = Partial<{
9
+ [K in QueryKeys<TContract>]: UseQueryStatesKeysMap<{
10
+ [P in K]: Exclude<QueryRecord<TContract>[P], undefined>;
11
+ }>[K];
12
+ }>;
13
+ export type NuqsParserMap<TContract extends HttpContractConfig> = QueryParsers<TContract>;
14
+ export type NuqsState<TParsers extends UseQueryStatesKeysMap> = inferParserType<TParsers>;
15
+ type ContractQueryHookOptions<TParsers extends UseQueryStatesKeysMap> = Partial<UseQueryStatesOptions<TParsers>>;
16
+ type ContractQueryConfig<TContract extends HttpContractConfig, TParsers extends NuqsParserMap<TContract> & UseQueryStatesKeysMap> = ContractQueryHookOptions<TParsers> & {
17
+ parsers: TParsers;
18
+ };
19
+ type HasRequiredKeys<T> = {
20
+ [K in keyof T]-?: Record<string, never> extends Pick<T, K> ? never : K;
21
+ }[keyof T];
22
+ type NuqsQueryOptionsRequiredInput<TContract extends HttpContractConfig> = Omit<EndpointCallArgs<TContract>, "query" | "rawBody" | "signal">;
23
+ type NuqsQueryOptions<TContract extends HttpContractConfig> = Omit<ContractUseQueryOptions<TContract>, "query"> & {
24
+ query?: EndpointCallArgs<TContract>["query"];
25
+ omitNullish?: boolean;
26
+ };
27
+ type NuqsQueryOptionsCallArgs<TContract extends HttpContractConfig> = HasRequiredKeys<NuqsQueryOptionsRequiredInput<TContract>> extends never ? [opts?: NuqsQueryOptions<TContract>] : [opts: NuqsQueryOptions<TContract>];
28
+ export declare class NuqsQueryHelper<TContract extends HttpContractConfig, TParsers extends NuqsParserMap<TContract> & UseQueryStatesKeysMap> {
29
+ private contract;
30
+ private config;
31
+ constructor(contract: TContract, config: ContractQueryConfig<TContract, TParsers>);
32
+ useState(options?: ContractQueryHookOptions<TParsers>): UseQueryStatesReturn<TParsers>;
33
+ toQuery(state: NuqsState<TParsers>, opts?: {
34
+ omitNullish?: boolean;
35
+ }): EndpointCallArgs<TContract>["query"];
36
+ toQueryOptions(rq: ReactQueryContractHelper<TContract>, state: NuqsState<TParsers>, ...callArgs: NuqsQueryOptionsCallArgs<TContract>): ReturnType<ReactQueryContractHelper<TContract>["queryOptions"]>;
37
+ get contractName(): string;
38
+ }
39
+ export declare class NuqsContractHelper<TContract extends HttpContractConfig> {
40
+ private contract;
41
+ constructor(contract: TContract);
42
+ query<TParsers extends NuqsParserMap<TContract> & UseQueryStatesKeysMap>(config: ContractQueryConfig<TContract, TParsers>): NuqsQueryHelper<TContract, TParsers>;
43
+ }
44
+ export declare function createNuqs(): <TContractLike extends ContractLike>(contract: TContractLike) => NuqsContractHelper<ResolveContract<TContractLike>>;
45
+ export type { UrlKeys };
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,64 @@
1
+ import { resolveContract, } from "@beignet/core/contracts";
2
+ import { useQueryStates, } from "./runtime";
3
+ export class NuqsQueryHelper {
4
+ contract;
5
+ config;
6
+ constructor(contract, config) {
7
+ this.contract = contract;
8
+ this.config = config;
9
+ }
10
+ useState(options = {}) {
11
+ const { parsers, ...baseOptions } = this.config;
12
+ return useQueryStates(parsers, {
13
+ ...baseOptions,
14
+ ...options,
15
+ });
16
+ }
17
+ toQuery(state, opts = {}) {
18
+ const { omitNullish = true } = opts;
19
+ const query = {};
20
+ const entries = Object.entries(state);
21
+ for (const [key, value] of entries) {
22
+ if (omitNullish && (value === null || value === undefined))
23
+ continue;
24
+ query[key] = value;
25
+ }
26
+ return query;
27
+ }
28
+ toQueryOptions(rq, state, ...callArgs) {
29
+ const opts = (callArgs[0] ?? {});
30
+ const { omitNullish, query: baseQuery, ...queryOptions } = opts;
31
+ const stateQuery = this.toQuery(state, { omitNullish });
32
+ const queryOptionsFn = rq.queryOptions.bind(rq);
33
+ return queryOptionsFn({
34
+ ...queryOptions,
35
+ query: {
36
+ ...baseQuery,
37
+ ...stateQuery,
38
+ },
39
+ });
40
+ }
41
+ get contractName() {
42
+ return this.contract.name;
43
+ }
44
+ }
45
+ export class NuqsContractHelper {
46
+ contract;
47
+ constructor(contract) {
48
+ this.contract = contract;
49
+ }
50
+ query(config) {
51
+ if (!this.contract.query) {
52
+ throw new Error(`nq(${this.contract.name}): Contract has no query schema. ` +
53
+ "@beignet/nuqs requires a query schema to bind URL state.");
54
+ }
55
+ return new NuqsQueryHelper(this.contract, config);
56
+ }
57
+ }
58
+ export function createNuqs() {
59
+ return function nq(contract) {
60
+ const resolvedContract = resolveContract(contract);
61
+ return new NuqsContractHelper(resolvedContract);
62
+ };
63
+ }
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,eAAe,GAChB,MAAM,yBAAyB,CAAC;AAKjC,OAAO,EAML,cAAc,GACf,MAAM,WAAW,CAAC;AA2DnB,MAAM,OAAO,eAAe;IAKhB;IACA;IAFV,YACU,QAAmB,EACnB,MAAgD;QADhD,aAAQ,GAAR,QAAQ,CAAW;QACnB,WAAM,GAAN,MAAM,CAA0C;IACvD,CAAC;IAEJ,QAAQ,CACN,UAA8C,EAAE;QAEhD,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QAChD,OAAO,cAAc,CAAC,OAAO,EAAE;YAC7B,GAAG,WAAW;YACd,GAAG,OAAO;SACX,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CACL,KAA0B,EAC1B,OAEI,EAAE;QAEN,MAAM,EAAE,WAAW,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;QACpC,MAAM,KAAK,GAA4B,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,CAAC;QAEjE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;YACnC,IAAI,WAAW,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC;gBAAE,SAAS;YACrE,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACrB,CAAC;QAED,OAAO,KAA6C,CAAC;IACvD,CAAC;IAED,cAAc,CACZ,EAAuC,EACvC,KAA0B,EAC1B,GAAG,QAA6C;QAEhD,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAgC,CAAC;QAChE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,YAAY,EAAE,GAAG,IAAI,CAAC;QAChE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;QACxD,MAAM,cAAc,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAEsB,CAAC;QAErE,OAAO,cAAc,CAAC;YACpB,GAAG,YAAY;YACf,KAAK,EAAE;gBACL,GAAI,SAAiD;gBACrD,GAAI,UAAkD;aACf;SACJ,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,OAAO,kBAAkB;IACT;IAApB,YAAoB,QAAmB;QAAnB,aAAQ,GAAR,QAAQ,CAAW;IAAG,CAAC;IAE3C,KAAK,CACH,MAAgD;QAEhD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,mCAAmC;gBACzD,0DAA0D,CAC7D,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACpD,CAAC;CACF;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,SAAS,EAAE,CAChB,QAAuB;QAEvB,MAAM,gBAAgB,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;QACnD,OAAO,IAAI,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;IAClD,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { NuqsAdapter } from "nuqs/adapters/next/app";
2
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/next/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { NuqsAdapter } from "nuqs/adapters/next/app";
2
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/next/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { type inferParserType, type UrlKeys, type UseQueryStatesKeysMap, type UseQueryStatesOptions, type UseQueryStatesReturn, useQueryStates, } from "nuqs";
2
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,cAAc,GACf,MAAM,MAAM,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { useQueryStates, } from "nuqs";
2
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,cAAc,GACf,MAAM,MAAM,CAAC"}
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@beignet/nuqs",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "nuqs integration for Beignet",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./next/app": {
14
+ "types": "./dist/next/app.d.ts",
15
+ "default": "./dist/next/app.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "!src/**/*.test.ts",
22
+ "!src/**/*.test.tsx",
23
+ "!src/**/*.test-d.ts",
24
+ "README.md",
25
+ "CHANGELOG.md"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "dev": "tsc --watch",
30
+ "clean": "rm -rf dist coverage .turbo",
31
+ "test": "bun test",
32
+ "test:coverage": "bun test --coverage",
33
+ "lint": "biome check ."
34
+ },
35
+ "keywords": [
36
+ "contract",
37
+ "api",
38
+ "typescript",
39
+ "nuqs",
40
+ "search-params"
41
+ ],
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/taylorbryant/beignet.git",
46
+ "directory": "packages/nuqs"
47
+ },
48
+ "author": "Taylor Bryant",
49
+ "homepage": "https://github.com/taylorbryant/beignet#readme",
50
+ "bugs": "https://github.com/taylorbryant/beignet/issues",
51
+ "sideEffects": false,
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "engines": {
56
+ "node": ">=18.0.0"
57
+ },
58
+ "peerDependencies": {
59
+ "nuqs": "^2.8.9",
60
+ "react": "^18.0.0 || ^19.0.0"
61
+ },
62
+ "dependencies": {
63
+ "@beignet/core": "*",
64
+ "@beignet/react-query": "*"
65
+ },
66
+ "devDependencies": {
67
+ "@types/bun": "^1.3.13",
68
+ "@types/node": "^20.10.0",
69
+ "@types/react": "^19.0.0",
70
+ "nuqs": "^2.8.9",
71
+ "react": "^19.0.0",
72
+ "typescript": "^5.3.0",
73
+ "zod": "^4.0.0"
74
+ }
75
+ }
package/src/index.ts ADDED
@@ -0,0 +1,167 @@
1
+ import type { EndpointCallArgs, InferQuery } from "@beignet/core/client";
2
+ import {
3
+ type ContractLike,
4
+ type HttpContractConfig,
5
+ type ResolveContract,
6
+ resolveContract,
7
+ } from "@beignet/core/contracts";
8
+ import type {
9
+ ContractUseQueryOptions,
10
+ ReactQueryContractHelper,
11
+ } from "@beignet/react-query";
12
+ import {
13
+ type inferParserType,
14
+ type UrlKeys,
15
+ type UseQueryStatesKeysMap,
16
+ type UseQueryStatesOptions,
17
+ type UseQueryStatesReturn,
18
+ useQueryStates,
19
+ } from "./runtime";
20
+
21
+ type QueryShape<TContract extends HttpContractConfig> = NonNullable<
22
+ InferQuery<TContract>
23
+ >;
24
+
25
+ type QueryRecord<TContract extends HttpContractConfig> =
26
+ QueryShape<TContract> extends Record<string, unknown>
27
+ ? QueryShape<TContract>
28
+ : never;
29
+
30
+ type QueryKeys<TContract extends HttpContractConfig> =
31
+ keyof QueryRecord<TContract> & string;
32
+
33
+ type QueryParsers<TContract extends HttpContractConfig> = Partial<{
34
+ [K in QueryKeys<TContract>]: UseQueryStatesKeysMap<{
35
+ [P in K]: Exclude<QueryRecord<TContract>[P], undefined>;
36
+ }>[K];
37
+ }>;
38
+
39
+ export type NuqsParserMap<TContract extends HttpContractConfig> =
40
+ QueryParsers<TContract>;
41
+
42
+ export type NuqsState<TParsers extends UseQueryStatesKeysMap> =
43
+ inferParserType<TParsers>;
44
+
45
+ type ContractQueryHookOptions<TParsers extends UseQueryStatesKeysMap> = Partial<
46
+ UseQueryStatesOptions<TParsers>
47
+ >;
48
+
49
+ type ContractQueryConfig<
50
+ TContract extends HttpContractConfig,
51
+ TParsers extends NuqsParserMap<TContract> & UseQueryStatesKeysMap,
52
+ > = ContractQueryHookOptions<TParsers> & {
53
+ parsers: TParsers;
54
+ };
55
+
56
+ type HasRequiredKeys<T> = {
57
+ [K in keyof T]-?: Record<string, never> extends Pick<T, K> ? never : K;
58
+ }[keyof T];
59
+
60
+ type NuqsQueryOptionsRequiredInput<TContract extends HttpContractConfig> = Omit<
61
+ EndpointCallArgs<TContract>,
62
+ "query" | "rawBody" | "signal"
63
+ >;
64
+
65
+ type NuqsQueryOptions<TContract extends HttpContractConfig> = Omit<
66
+ ContractUseQueryOptions<TContract>,
67
+ "query"
68
+ > & {
69
+ query?: EndpointCallArgs<TContract>["query"];
70
+ omitNullish?: boolean;
71
+ };
72
+
73
+ type NuqsQueryOptionsCallArgs<TContract extends HttpContractConfig> =
74
+ HasRequiredKeys<NuqsQueryOptionsRequiredInput<TContract>> extends never
75
+ ? [opts?: NuqsQueryOptions<TContract>]
76
+ : [opts: NuqsQueryOptions<TContract>];
77
+
78
+ export class NuqsQueryHelper<
79
+ TContract extends HttpContractConfig,
80
+ TParsers extends NuqsParserMap<TContract> & UseQueryStatesKeysMap,
81
+ > {
82
+ constructor(
83
+ private contract: TContract,
84
+ private config: ContractQueryConfig<TContract, TParsers>,
85
+ ) {}
86
+
87
+ useState(
88
+ options: ContractQueryHookOptions<TParsers> = {},
89
+ ): UseQueryStatesReturn<TParsers> {
90
+ const { parsers, ...baseOptions } = this.config;
91
+ return useQueryStates(parsers, {
92
+ ...baseOptions,
93
+ ...options,
94
+ });
95
+ }
96
+
97
+ toQuery(
98
+ state: NuqsState<TParsers>,
99
+ opts: {
100
+ omitNullish?: boolean;
101
+ } = {},
102
+ ): EndpointCallArgs<TContract>["query"] {
103
+ const { omitNullish = true } = opts;
104
+ const query: Record<string, unknown> = {};
105
+ const entries = Object.entries(state as Record<string, unknown>);
106
+
107
+ for (const [key, value] of entries) {
108
+ if (omitNullish && (value === null || value === undefined)) continue;
109
+ query[key] = value;
110
+ }
111
+
112
+ return query as EndpointCallArgs<TContract>["query"];
113
+ }
114
+
115
+ toQueryOptions(
116
+ rq: ReactQueryContractHelper<TContract>,
117
+ state: NuqsState<TParsers>,
118
+ ...callArgs: NuqsQueryOptionsCallArgs<TContract>
119
+ ): ReturnType<ReactQueryContractHelper<TContract>["queryOptions"]> {
120
+ const opts = (callArgs[0] ?? {}) as NuqsQueryOptions<TContract>;
121
+ const { omitNullish, query: baseQuery, ...queryOptions } = opts;
122
+ const stateQuery = this.toQuery(state, { omitNullish });
123
+ const queryOptionsFn = rq.queryOptions.bind(rq) as (
124
+ args: ContractUseQueryOptions<TContract>,
125
+ ) => ReturnType<ReactQueryContractHelper<TContract>["queryOptions"]>;
126
+
127
+ return queryOptionsFn({
128
+ ...queryOptions,
129
+ query: {
130
+ ...(baseQuery as Record<string, unknown> | undefined),
131
+ ...(stateQuery as Record<string, unknown> | undefined),
132
+ } as EndpointCallArgs<TContract>["query"],
133
+ } as ContractUseQueryOptions<TContract>);
134
+ }
135
+
136
+ get contractName(): string {
137
+ return this.contract.name;
138
+ }
139
+ }
140
+
141
+ export class NuqsContractHelper<TContract extends HttpContractConfig> {
142
+ constructor(private contract: TContract) {}
143
+
144
+ query<TParsers extends NuqsParserMap<TContract> & UseQueryStatesKeysMap>(
145
+ config: ContractQueryConfig<TContract, TParsers>,
146
+ ): NuqsQueryHelper<TContract, TParsers> {
147
+ if (!this.contract.query) {
148
+ throw new Error(
149
+ `nq(${this.contract.name}): Contract has no query schema. ` +
150
+ "@beignet/nuqs requires a query schema to bind URL state.",
151
+ );
152
+ }
153
+
154
+ return new NuqsQueryHelper(this.contract, config);
155
+ }
156
+ }
157
+
158
+ export function createNuqs() {
159
+ return function nq<TContractLike extends ContractLike>(
160
+ contract: TContractLike,
161
+ ): NuqsContractHelper<ResolveContract<TContractLike>> {
162
+ const resolvedContract = resolveContract(contract);
163
+ return new NuqsContractHelper(resolvedContract);
164
+ };
165
+ }
166
+
167
+ export type { UrlKeys };
@@ -0,0 +1 @@
1
+ export { NuqsAdapter } from "nuqs/adapters/next/app";
package/src/runtime.ts ADDED
@@ -0,0 +1,8 @@
1
+ export {
2
+ type inferParserType,
3
+ type UrlKeys,
4
+ type UseQueryStatesKeysMap,
5
+ type UseQueryStatesOptions,
6
+ type UseQueryStatesReturn,
7
+ useQueryStates,
8
+ } from "nuqs";