Skip to content

Commit

Permalink
Add array search params (#585)
Browse files Browse the repository at this point in the history
* allow array search params

* make array search params more type safe

* join array values by default

* lint

* export appendSp

* extract string or undefined helper

* default to split mode

* expand array test routes

* changeset

* add array params to tests
  • Loading branch information
fehnomenal authored Dec 31, 2023
1 parent d303739 commit b9fc188
Show file tree
Hide file tree
Showing 25 changed files with 957 additions and 374 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-hats-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'vite-plugin-kit-routes': minor
---

support array search parameters
65 changes: 49 additions & 16 deletions packages/vite-plugin-kit-routes/src/lib/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ const PAGES = {
params.lang = params.lang ?? 'fr'
params.id = params.id ?? 'Vienna'
return `${params?.lang ? `/${params?.lang}` : ''}/site/${params.id}${appendSp({
limit: params?.limit,
demo: params?.demo,
limit: params.limit,
demo: params.demo,
})}`
},
'/site_contract/[siteId]-[contractId]': (params: {
Expand All @@ -73,7 +73,7 @@ const PAGES = {
}) => {
return `${params?.lang ? `/${params?.lang}` : ''}/site_contract/${params.siteId}-${
params.contractId
}${appendSp({ limit: params?.limit })}`
}${appendSp({ limit: params.limit })}`
},
'/a/[...rest]/z': (params: { rest: (string | number)[] }) => {
return `/a/${params.rest?.join('/')}/z`
Expand All @@ -84,6 +84,12 @@ const PAGES = {
'/sp': (sp?: Record<string, string | number>) => {
return `/sp${appendSp(sp)}`
},
'/spArray': (params: { ids: number[] }) => {
return `/spArray${appendSp({ ids: params.ids })}`
},
'/spArrayComma': (params: { ids: number[] }) => {
return `/spArrayComma${appendSp({ ids: String(params.ids) })}`
},
}

/**
Expand Down Expand Up @@ -118,7 +124,7 @@ const ACTIONS = {
limit?: number
}) => {
return `${params?.lang ? `/${params?.lang}` : ''}/contract/${params.id}${appendSp({
limit: params?.limit,
limit: params.limit,
})}`
},
'create /site': (params?: {
Expand Down Expand Up @@ -154,7 +160,7 @@ const ACTIONS = {
params.extra = params.extra ?? 'A'
return `${params?.lang ? `/${params?.lang}` : ''}/site_contract/${params.siteId}-${
params.contractId
}?/send${appendSp({ extra: params?.extra }, '&')}`
}?/send${appendSp({ extra: params.extra }, '&')}`
},
}

Expand All @@ -169,25 +175,41 @@ const LINKS = {
gravatar: (params: { str: string; s?: number; d?: 'retro' | 'identicon' }) => {
params.s = params.s ?? 75
params.d = params.d ?? 'identicon'
return `https://www.gravatar.com/avatar/${params.str}${appendSp({
s: params?.s,
d: params?.d,
})}`
return `https://www.gravatar.com/avatar/${params.str}${appendSp({ s: params.s, d: params.d })}`
},
}

type ParamValue = string | number | undefined

/**
* Append search params to a string
*/
const appendSp = (sp?: Record<string, string | number | undefined>, prefix: '?' | '&' = '?') => {
export const appendSp = (
sp?: Record<string, ParamValue | ParamValue[]>,
prefix: '?' | '&' = '?',
) => {
if (sp === undefined) return ''
const mapping = Object.entries(sp)
.filter(c => c[1] !== undefined)
.map(c => [c[0], String(c[1])])

const formated = new URLSearchParams(mapping).toString()
if (formated) {
return `${prefix}${formated}`
const params = new URLSearchParams()
const append = (n: string, v: ParamValue) => {
if (v !== undefined) {
params.append(n, String(v))
}
}

for (const [name, val] of Object.entries(sp)) {
if (Array.isArray(val)) {
for (const v of val) {
append(name, v)
}
} else {
append(name, val)
}
}

const formatted = params.toString()
if (formatted) {
return `${prefix}${formatted}`
}
return ''
}
Expand All @@ -209,6 +231,14 @@ export const currentSp = () => {
return record
}

function StringOrUndefined(val: any) {
if (val === undefined) {
return undefined
}

return String(val)
}

// route function helpers
type NonFunctionKeys<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]
type FunctionKeys<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T]
Expand Down Expand Up @@ -275,6 +305,8 @@ export type KIT_ROUTES = {
'/lay/root-layout': never
'/lay/skip': never
'/sp': never
'/spArray': never
'/spArrayComma': never
}
SERVERS: {
'GET /server_func_get': never
Expand Down Expand Up @@ -304,6 +336,7 @@ export type KIT_ROUTES = {
siteId: never
contractId: never
rest: never
ids: never
locale: never
redirectTo: never
extra: never
Expand Down
40 changes: 32 additions & 8 deletions packages/vite-plugin-kit-routes/src/lib/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,34 @@ export const format = (margin: { left?: number; top?: number; bottom?: number },
)
}

export const appendSp = `/**
export const appendSp = `type ParamValue = string | number | undefined
/**
* Append search params to a string
*/
const appendSp = (sp?: Record<string, string | number | undefined>, prefix: '?' | '&' = '?') => {
export const appendSp = (sp?: Record<string, ParamValue | ParamValue[]>, prefix: '?' | '&' = '?') => {
if (sp === undefined) return ''
const mapping = Object.entries(sp)
.filter(c => c[1] !== undefined)
.map(c => [c[0], String(c[1])])
const formated = new URLSearchParams(mapping).toString()
if (formated) {
return \`\${prefix}\${formated}\`
const params = new URLSearchParams()
const append = (n: string, v: ParamValue) => {
if (v !== undefined) {
params.append(n, String(v))
}
}
for (const [name, val] of Object.entries(sp)) {
if (Array.isArray(val)) {
for (const v of val) {
append(name, v)
}
} else {
append(name, val)
}
}
const formatted = params.toString()
if (formatted) {
return \`\${prefix}\${formatted}\`
}
return ''
}
Expand All @@ -50,6 +66,14 @@ export const currentSp = () => {
record[key] = value
}
return record
}
function StringOrUndefined(val: any) {
if (val === undefined) {
return undefined
}
return String(val)
}`

export const routeFn = `// route function helpers
Expand Down
89 changes: 68 additions & 21 deletions packages/vite-plugin-kit-routes/src/lib/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,14 @@ export type ExtendParam = {

export type ExplicitSearchParam = ExtendParam & {
required?: boolean
/**
* Controls how arrays are converted into parameters.
* `join` will join elements with `,` into a single parameter.
* With `split` the parameter will be repeated for each element.
*
* @default 'split'
*/
arrayMode?: 'join' | 'split'
}

export const log = new Log('Kit Routes')
Expand Down Expand Up @@ -521,26 +529,50 @@ export function buildMetadata(

const params = []

let isAllOptional = paramsFromPath.filter(c => !c.optional).length === 0
const paramsReq = paramsFromPath.filter(c => !c.optional)

// custom search Param?
let explicit_search_params_to_function: string[] = []
const explicit_search_params_to_function: [param: string, val: string][] = []
if (customConf.explicit_search_params) {
let someParamsHaveDefault = paramsFromPath.filter(c => c.default !== undefined).length > 0

Object.entries(customConf.explicit_search_params).forEach(sp => {
paramsFromPath.push({
const param = {
name: sp[0],
optional: !sp[1].required,
type: sp[1].type,
default: sp[1].default,
isArray: false,
})
explicit_search_params_to_function.push(
`${sp[0]}: params${sp[1].required ? '' : '?'}.${sp[0]}`,
)
}

paramsFromPath.push(param)

if (sp[1].required) {
isAllOptional = false
paramsReq.push(param)
}
if (sp[1].default !== undefined) {
someParamsHaveDefault = true
}
})

let paramsIsOptional = isAllOptional
if (options.format_short && paramsReq.length === 1) {
paramsIsOptional = true
}
if (someParamsHaveDefault) {
paramsIsOptional = false
}

Object.entries(customConf.explicit_search_params).forEach(sp => {
const val = `params${paramsIsOptional ? '?' : ''}.${sp[0]}`

explicit_search_params_to_function.push([sp[0], getSpValue(val, sp[1])])
})
}

let isAllOptional = paramsFromPath.filter(c => !c.optional).length === 0
if (paramsFromPath.length > 0) {
const paramsReq = paramsFromPath.filter(c => !c.optional)
if (options.format_short && paramsReq.length === 1) {
// If only ONE required param, and we have only one, then let's put params optional
isAllOptional = true
Expand All @@ -550,10 +582,11 @@ export function buildMetadata(
// If it's in the explicite and it's THIS one, let's change the array...
if (
explicit_search_params_to_function.length === 1 &&
explicit_search_params_to_function[0] ===
`${paramsReq[0].name}: params${!paramsReq[0].optional ? '' : '?'}.${paramsReq[0].name}`
explicit_search_params_to_function[0][0] === paramsReq[0].name
) {
explicit_search_params_to_function = [paramsReq[0].name]
const sp = customConf.explicit_search_params![paramsReq[0].name]

explicit_search_params_to_function[0][1] = getSpValue(paramsReq[0].name, sp)
} else {
// in params
toRet = toRet.replaceAll(`params.${paramsReq[0].name}`, paramsReq[0].name)
Expand All @@ -562,6 +595,10 @@ export function buildMetadata(
params.push(`params${isAllOptional ? '?' : ''}: { ${formatArgs(paramsFromPath, options)} }`)
}

const explicit_search_params = explicit_search_params_to_function
.map(([param, val]) => (param === val ? param : `${param}: ${val}`))
.join(', ')

let fullSP = ''
const wExtraSP =
(customConf.extra_search_params === 'default' && useWithAppendSp) ||
Expand All @@ -574,11 +611,9 @@ export function buildMetadata(
} else if (wExtraSP && customConf.explicit_search_params) {
params.push(`sp?: Record<string, string | number>`)
// We want explicite to be stronger and override sp
fullSP =
`\${appendSp({ ...sp, ${explicit_search_params_to_function.join(', ')}` +
` }${appendSpPrefix})}`
fullSP = `\${appendSp({ ...sp, ${explicit_search_params} }${appendSpPrefix})}`
} else if (!wExtraSP && customConf.explicit_search_params) {
fullSP = `\${appendSp({ ${explicit_search_params_to_function.join(', ')} }${appendSpPrefix})}`
fullSP = `\${appendSp({ ${explicit_search_params} }${appendSpPrefix})}`
}

let paramsDefaults = paramsFromPath
Expand Down Expand Up @@ -609,6 +644,18 @@ export function buildMetadata(
return baseToReturn
}

function getSpValue(rawValue: string, param: ExplicitSearchParam) {
if (param.arrayMode === 'join') {
if (param.required || param.default !== undefined) {
return `String(${rawValue})`
}

return `StringOrUndefined(${rawValue})`
}

return rawValue
}

export function extractParamsFromPath(path: string, o: Options): Param[] {
const options = getDefaultOption(o)
const paramPattern = /\[+([^\]]+)]+/g
Expand Down Expand Up @@ -786,9 +833,9 @@ export const run = (atStart: boolean, o?: Options) => {
if (allOk) {
const result = write(options.generated_file_path, [
`/* eslint-disable */
/**
/**
* This file was generated by 'vite-plugin-kit-routes'
*
*
* >> DO NOT EDIT THIS FILE MANUALLY <<
*/${options?.path_base ? `\nimport { base } from '$app/paths'` : ''}
`,
Expand All @@ -804,7 +851,7 @@ ${c.files
return (
`export const ${c.type.slice(0, -1)}_${key.keyToUse} = (${key.strParams}) => {` +
`${format({ bottom: 0, top: 1, left: 2 }, key.strDefault)}
return ${key.strReturn}
return ${key.strReturn}
}`
)
} else {
Expand Down Expand Up @@ -848,20 +895,20 @@ ${options?.format?.includes('object') ? `export ` : ``}` +
// types
`/**
* Add this type as a generic of the vite plugin \`kitRoutes<KIT_ROUTES>\`.
*
*
* Full example:
* \`\`\`ts
* import type { KIT_ROUTES } from '$lib/ROUTES'
* import { kitRoutes } from 'vite-plugin-kit-routes'
*
*
* kitRoutes<KIT_ROUTES>({
* PAGES: {
* // here, key of object will be typed!
* }
* })
* \`\`\`
*/
export type KIT_ROUTES = {
export type KIT_ROUTES = {
${objTypes
.map(c => {
return ` ${c.type}${arrayToRecord(
Expand Down
Loading

0 comments on commit b9fc188

Please sign in to comment.