Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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