Skip to content

Commit

Permalink
feat: Add base parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
franky47 committed Jan 27, 2024
1 parent 1668eed commit e188939
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 59 deletions.
2 changes: 1 addition & 1 deletion packages/nuqs/src/index.parsers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './cache'
export * from './parsers'
export { createSerializer } from './serialize'
export { createSerializer } from './serializer'
2 changes: 1 addition & 1 deletion packages/nuqs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
export type { HistoryOptions, Options } from './defs'
export * from './deprecated'
export * from './parsers'
export { createSerializer } from './serialize'
export { createSerializer } from './serializer'
export { subscribeToQueryUpdates } from './sync'
export type { QueryUpdateNotificationArgs, QueryUpdateSource } from './sync'
export * from './useQueryState'
Expand Down
32 changes: 0 additions & 32 deletions packages/nuqs/src/serialize.test.ts

This file was deleted.

25 changes: 0 additions & 25 deletions packages/nuqs/src/serialize.ts

This file was deleted.

65 changes: 65 additions & 0 deletions packages/nuqs/src/serializer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, expect, test } from 'vitest'
import { parseAsBoolean, parseAsInteger, parseAsString } from './parsers'
import { createSerializer } from './serializer'

const parsers = {
str: parseAsString,
int: parseAsInteger,
bool: parseAsBoolean
}

describe('serializer', () => {
test('empty', () => {
const serialize = createSerializer(parsers)
const result = serialize({})
expect(result).toBe('')
})
test('one item', () => {
const serialize = createSerializer(parsers)
const result = serialize({ str: 'foo' })
expect(result).toBe('?str=foo')
})
test('several items', () => {
const serialize = createSerializer(parsers)
const result = serialize({ str: 'foo', int: 1, bool: true })
expect(result).toBe('?str=foo&int=1&bool=true')
})
test("null items don't show up", () => {
const serialize = createSerializer(parsers)
const result = serialize({ str: null })
expect(result).toBe('')
})
test('with string base', () => {
const serialize = createSerializer(parsers)
const result = serialize('/foo', { str: 'foo' })
expect(result).toBe('/foo?str=foo')
})
test('with string base with search params', () => {
const serialize = createSerializer(parsers)
const result = serialize('/foo?bar=egg', { str: 'foo' })
expect(result).toBe('/foo?bar=egg&str=foo')
})
test('with URLSearchParams base', () => {
const serialize = createSerializer(parsers)
const search = new URLSearchParams('?bar=egg')
const result = serialize(search, { str: 'foo' })
expect(result).toBe('?bar=egg&str=foo')
})
test('with URL base', () => {
const serialize = createSerializer(parsers)
const url = new URL('https://example.com/path')
const result = serialize(url, { str: 'foo' })
expect(result).toBe('https://example.com/path?str=foo')
})
test('with URL base and search params', () => {
const serialize = createSerializer(parsers)
const url = new URL('https://example.com/path?bar=egg')
const result = serialize(url, { str: 'foo' })
expect(result).toBe('https://example.com/path?bar=egg&str=foo')
})
test('null deletes from base', () => {
const serialize = createSerializer(parsers)
const result = serialize('?str=bar&int=-1', { str: 'foo', int: null })
expect(result).toBe('?str=foo')
})
})
60 changes: 60 additions & 0 deletions packages/nuqs/src/serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { ParserBuilder } from './parsers'
import { renderQueryString } from './url-encoding'

type ExtractParserType<Parser> = Parser extends ParserBuilder<any>
? ReturnType<Parser['parseServerSide']>
: never

type Base = string | URLSearchParams | URL
type Values<Parsers extends Record<string, ParserBuilder<any>>> = Partial<{
[K in keyof Parsers]?: ExtractParserType<Parsers[K]>
}>

export function createSerializer<
Parsers extends Record<string, ParserBuilder<any>>
>(parsers: Parsers) {
function serialize(values: Values<Parsers>): string
function serialize(base: Base, values: Values<Parsers>): string
function serialize(
baseOrValues: Base | Values<Parsers>,
values?: Values<Parsers>
) {
const [base, search] = isBase(baseOrValues)
? splitBase(baseOrValues)
: ['', new URLSearchParams()]
const vals = isBase(baseOrValues) ? values! : baseOrValues
for (const key in parsers) {
const parser = parsers[key]
const value = vals[key]
if (!parser || value === undefined) {
continue
}
if (value === null) {
search.delete(key)
} else {
search.set(key, parser.serialize(value))
}
}
return base + renderQueryString(search)
}
return serialize
}

function isBase(base: any): base is Base {
return (
typeof base === 'string' ||
base instanceof URLSearchParams ||
base instanceof URL
)
}

function splitBase(base: Base) {
if (typeof base === 'string') {
const [path = '', search] = base.split('?')
return [path, new URLSearchParams(search)] as const
} else if (base instanceof URLSearchParams) {
return ['', base] as const
} else {
return [base.origin + base.pathname, base.searchParams] as const
}
}

0 comments on commit e188939

Please sign in to comment.