Skip to content

Commit 07234e3

Browse files
committed
feat: Add UrlKeys type helper
1 parent 8951598 commit 07234e3

File tree

7 files changed

+130
-16
lines changed

7 files changed

+130
-16
lines changed

packages/nuqs/src/cache.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
// @ts-ignore
22
import { cache } from 'react'
3-
import type { SearchParams } from './defs'
3+
import type { SearchParams, UrlKeys } from './defs'
44
import { error } from './errors'
5-
import type { ParserBuilder, inferParserType } from './parsers'
5+
import type { inferParserType, ParserMap } from './parsers'
66

77
const $input: unique symbol = Symbol('Input')
88

9-
export function createSearchParamsCache<
10-
Parsers extends Record<string, ParserBuilder<any>>
11-
>(
9+
export function createSearchParamsCache<Parsers extends ParserMap>(
1210
parsers: Parsers,
13-
{ urlKeys = {} }: { urlKeys?: Partial<Record<keyof Parsers, string>> } = {}
11+
{ urlKeys = {} }: { urlKeys?: UrlKeys<Parsers> } = {}
1412
) {
1513
type Keys = keyof Parsers
1614
type ParsedSearchParams = {

packages/nuqs/src/defs.ts

+32
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,35 @@ export type Options = {
6666
export type Nullable<T> = {
6767
[K in keyof T]: T[K] | null
6868
}
69+
70+
/**
71+
* Helper type to define and reuse urlKey options to rename search params keys
72+
*
73+
* Usage:
74+
* ```ts
75+
* import { type UrlKeys } from 'nuqs' // or 'nuqs/server'
76+
*
77+
* export const coordinatesSearchParams = {
78+
* latitude: parseAsFloat.withDefault(0),
79+
* longitude: parseAsFloat.withDefault(0),
80+
* }
81+
* export const coordinatesUrlKeys: UrlKeys<typeof coordinatesSearchParams> = {
82+
* latitude: 'lat',
83+
* longitude: 'lng',
84+
* }
85+
*
86+
* // Later in the code:
87+
* useQueryStates(coordinatesSearchParams, {
88+
* urlKeys: coordinatesUrlKeys
89+
* })
90+
* createSerializer(coordinatesSearchParams, {
91+
* urlKeys: coordinatesUrlKeys
92+
* })
93+
* createSearchParamsCache(coordinatesSearchParams, {
94+
* urlKeys: coordinatesUrlKeys
95+
* })
96+
* ```
97+
*/
98+
export type UrlKeys<Parsers extends Record<string, any>> = Partial<
99+
Record<keyof Parsers, string>
100+
>

packages/nuqs/src/parsers.ts

+5
Original file line numberDiff line numberDiff line change
@@ -464,3 +464,8 @@ export type inferParserType<Input> =
464464
: Input extends Record<string, ParserBuilder<any>>
465465
? inferParserRecordType<Input>
466466
: never
467+
468+
export type ParserWithOptionalDefault<T> = ParserBuilder<T> & {
469+
defaultValue?: T
470+
}
471+
export type ParserMap = Record<string, ParserWithOptionalDefault<any>>

packages/nuqs/src/serializer.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
import type { Nullable, Options } from './defs'
2-
import type { inferParserType, ParserBuilder } from './parsers'
1+
import type { Nullable, Options, UrlKeys } from './defs'
2+
import type { inferParserType, ParserMap } from './parsers'
33
import { renderQueryString } from './url-encoding'
44

55
type Base = string | URLSearchParams | URL
6-
type ParserWithOptionalDefault<T> = ParserBuilder<T> & { defaultValue?: T }
76

8-
export function createSerializer<
9-
Parsers extends Record<string, ParserWithOptionalDefault<any>>
10-
>(
7+
export function createSerializer<Parsers extends ParserMap>(
118
parsers: Parsers,
129
{
1310
clearOnDefault = true,
1411
urlKeys = {}
1512
}: Pick<Options, 'clearOnDefault'> & {
16-
urlKeys?: Partial<Record<keyof Parsers, string>>
13+
urlKeys?: UrlKeys<Parsers>
1714
} = {}
1815
) {
1916
type Values = Partial<Nullable<inferParserType<Parsers>>>

packages/nuqs/src/tests/cache.test-d.ts

+41
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,44 @@ import {
3434
expectType<Promise<All>>(cache.parse(Promise.resolve({})))
3535
expectType<All>(cache.all())
3636
}
37+
38+
// It supports urlKeys
39+
{
40+
createSearchParamsCache(
41+
{
42+
foo: parseAsString,
43+
bar: parseAsInteger
44+
},
45+
{
46+
urlKeys: {
47+
foo: 'f'
48+
// It accepts partial inputs
49+
}
50+
}
51+
)
52+
createSearchParamsCache(
53+
{
54+
foo: parseAsString,
55+
bar: parseAsInteger
56+
},
57+
{
58+
urlKeys: {
59+
foo: 'f',
60+
bar: 'b'
61+
}
62+
}
63+
)
64+
expectError(() => {
65+
createSearchParamsCache(
66+
{
67+
foo: parseAsString,
68+
bar: parseAsInteger
69+
},
70+
{
71+
urlKeys: {
72+
nope: 'n' // Doesn't accept extra properties
73+
}
74+
}
75+
)
76+
})
77+
}

packages/nuqs/src/tests/serializer.test-d.ts

+41
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,44 @@ import { createSerializer, parseAsInteger, parseAsString } from '../../dist'
6161
expectType<string>(serialize({ bar: null }))
6262
expectType<string>(serialize({ bar: undefined }))
6363
}
64+
65+
// It supports urlKeys
66+
{
67+
createSerializer(
68+
{
69+
foo: parseAsString,
70+
bar: parseAsInteger
71+
},
72+
{
73+
urlKeys: {
74+
foo: 'f'
75+
// It accepts partial inputs
76+
}
77+
}
78+
)
79+
createSerializer(
80+
{
81+
foo: parseAsString,
82+
bar: parseAsInteger
83+
},
84+
{
85+
urlKeys: {
86+
foo: 'f',
87+
bar: 'b'
88+
}
89+
}
90+
)
91+
expectError(() => {
92+
createSerializer(
93+
{
94+
foo: parseAsString,
95+
bar: parseAsInteger
96+
},
97+
{
98+
urlKeys: {
99+
nope: 'n' // Doesn't accept extra properties
100+
}
101+
}
102+
)
103+
})
104+
}

packages/nuqs/src/useQueryStates.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import {
88
} from 'react'
99
import { useAdapter } from './adapters/lib/context'
1010
import { debug } from './debug'
11-
import type { Nullable, Options } from './defs'
11+
import type { Nullable, Options, UrlKeys } from './defs'
1212
import type { Parser } from './parsers'
1313
import { emitter, type CrossHookSyncPayload } from './sync'
1414
import {
15-
FLUSH_RATE_LIMIT_MS,
1615
enqueueQueryStringUpdate,
16+
FLUSH_RATE_LIMIT_MS,
1717
getQueuedValue,
1818
scheduleFlushToURL
1919
} from './update-queue'
@@ -30,7 +30,7 @@ export type UseQueryStatesKeysMap<Map = any> = {
3030

3131
export type UseQueryStatesOptions<KeyMap extends UseQueryStatesKeysMap> =
3232
Options & {
33-
urlKeys: Partial<Record<keyof KeyMap, string>>
33+
urlKeys: UrlKeys<KeyMap>
3434
}
3535

3636
export type Values<T extends UseQueryStatesKeysMap> = {

0 commit comments

Comments
 (0)