import { LinkProps } from 'next/link' ; import { ParsedUrlQuery } from 'querystring' ; import { PropsWithChildren } from 'react' ; import { parseCsv , quoteVal } from './csv' ; import { isInTuple } from './type-guards' ; import { OptionalToUndefined , UndefinableToOptional } from './utility-types' ; export type QueryParam = ParsedUrlQuery [ string ] ; export type QueryTo < Props > = ( query : ParsedUrlQuery ) => OptionalToUndefined < Props > ; export type LinkFrom < Props > = PropsWithChildren < Props > & Omit < LinkProps , 'as' | 'href' > ; type Codec < T > = { decode : ( p : QueryParam ) => T ; encode : ( v : T ) => QueryParam ; } ; type CodecMap = Record < string , Codec < unknown > > ; export type FromCodecMap < Map extends CodecMap > = UndefinableToOptional < { [ K in keyof Map ] : ReturnType < Map [ K ] [ 'decode' ] > ; } > ; function paramParser < T > ( param , fs ) { return param === '' ? fs . empty ( param ) : typeof param === 'string' ? fs . string ( param ) : Array . isArray ( param ) ? fs . array ( param ) : fs . nodef ( ) ; } export function toCsv ( param ) { return paramParser ( param , { empty : ( ) => [ ] , string : p => p . split ( ',' ) , array : p => p , nodef : ( ) => [ ] , } ) ; } export function toCsvFromQuotedCsv ( param ) { return paramParser ( param , { empty : ( ) => [ ] , string : p => parseCsv ( p ) , array : p => p , nodef : ( ) => [ ] , } ) ; } export function toNumber ( param , fallback ) { return paramParser ( param , { empty : ( ) => fallback , string : p => parseInt ( p , 10 ) || fallback , array : p => parseInt ( p [ 0 ] , 10 ) || fallback , nodef : ( ) => fallback , } ) ; } export function toMaybeNumber ( param ) { return paramParser ( param , { empty : ( ) => undefined , string : p => parseInt ( p , 10 ) || undefined , array : p => parseInt ( p [ 0 ] , 10 ) || undefined , nodef : ( ) => undefined , } ) ; } export function toString ( param , fallback ) { return paramParser ( param , { empty : ( ) => '' , string : p => p , array : p => p . join ( ',' ) , nodef : ( ) => fallback , } ) ; } export function toMaybeString ( param ) { return paramParser ( param , { empty : ( ) => undefined , string : p => p , array : p => p . join ( ',' ) , nodef : ( ) => undefined , } ) ; } export function toBoolean ( param ) { return paramParser ( param , { empty : ( ) => false , string : p => p === 'true' , array : ( ) => false , nodef : ( ) => false , } ) ; } export function toSource < T extends string > ( param , sources ) { const val = toMaybeString ( param ) ; if ( val && isInTuple ( val , sources ) ) { return val ; } } function fromString ( v ) { return v === '' ? undefined : v ; } function fromNumber ( v ) { return v === 1 ? undefined : v . toString ( ) ; } function fromMaybeNumber ( v ) { return v ? ( v === 1 ? undefined : v . toString ( ) ) : undefined ; } function fromCsv ( v ) { return v . length === 0 ? undefined : v . join ( ',' ) ; } function fromQuotedCsv ( v ) { return v . length === 0 ? undefined : v . map ( val => quoteVal ( val ) ) . join ( ',' ) ; } function fromBoolean ( v ) { return v === true ? 'true' : undefined ; } export const stringCodec = { decode : p => toString ( p , '' ) , encode : fromString , } ; export const maybeStringCodec = { decode : toMaybeString , encode : fromString , } ; export const numberCodec = { decode : p => toNumber ( p , 1 ) , encode : fromNumber , } ; export const maybeNumberCodec = { decode : toMaybeNumber , encode : fromMaybeNumber , } ; export const csvCodec = { decode : toCsv , encode : fromCsv , } ; export const quotedCsvCodec = { decode : toCsvFromQuotedCsv , encode : fromQuotedCsv , } ; export const booleanCodec = { decode : toBoolean , encode : fromBoolean , } ; export function decodeQuery < R > ( query , codecMap ) { const map = new Map ( ) ; for ( const prop in codecMap ) { map . set ( prop , codecMap [ prop ] . decode ( query [ prop ] ) ) ; } return Object . fromEntries ( map ) ; } export function encodeQuery < T > ( props , codecMap ) { const map = new Map ( ) ; for ( const prop in codecMap ) { map . set ( prop , codecMap [ prop ] . encode ( props [ prop ] ) ) ; } return Object . fromEntries ( map ) ; } export function propsToQuery ( props ) { return Object . keys ( props ) . reduce ( ( acc , key ) => { const val = props [ key ] ; if ( Array . isArray ( val ) ) { if ( val . length === 0 ) { return { ... acc , } ; } else { return { ... acc , [ key ] : val . join ( ',' ) , } ; } } if ( val === null || val === undefined || val === '' ) { return { ... acc } ; } const pageKeys = [ 'page' , 'canvas' , 'manifest' ] ; if ( pageKeys . includes ( key ) && ( val === '1' || val === 1 ) ) { return { ... acc } ; } return { ... acc , [ key ] : val . toString ( ) , } ; } , { } ) ; }