@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 +5 -0
- package/README.md +104 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/next/app.d.ts +2 -0
- package/dist/next/app.d.ts.map +1 -0
- package/dist/next/app.js +2 -0
- package/dist/next/app.js.map +1 -0
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +2 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +75 -0
- package/src/index.ts +167 -0
- package/src/next/app.ts +1 -0
- package/src/runtime.ts +8 -0
package/CHANGELOG.md
ADDED
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/next/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/next/app.js
ADDED
|
@@ -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 @@
|
|
|
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"}
|
package/dist/runtime.js
ADDED
|
@@ -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 };
|
package/src/next/app.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NuqsAdapter } from "nuqs/adapters/next/app";
|