import type { WritableComputedRef } from 'vue' import type { LocationQuery, LocationQueryRaw } from 'vue-router' import { computed } from 'vue' import { useRoute, useRouter } from 'vue-router' function buildQuery( current: LocationQuery, key: string, value: string | undefined, ): LocationQueryRaw { const query: LocationQueryRaw = { ...current } if (value === undefined) delete query[key] else query[key] = value return query } type QueryDefaults = { [K in keyof T]?: T[K] } type QueryResult> = { [K in keyof T]: WritableComputedRef : T[K] | undefined> } /** * Reactive URL query param bindings. * Each key becomes a writable computed that reads/writes a single query param. * * @example * const q = useQuery<{ search: string; page: string }>({ page: '1' }) * q.search.value = 'hello' // → ?search=hello&page=1 */ export function useQuery< T extends Record = Record, D extends QueryDefaults = QueryDefaults, >(defaults?: D): QueryResult { const route = useRoute() const router = useRouter() const cache: Record> = {} return new Proxy({} as QueryResult, { get(_, key: string) { if (!(key in cache)) { const fallback = defaults?.[key as keyof T] as string | undefined cache[key] = computed({ get: () => (route.query[key] as string | undefined) ?? fallback, set: value => router.replace({ query: buildQuery(route.query, key, value) }), }) } return cache[key] as QueryResult[keyof T] }, }) }