/** * URL parameter schema engine — the ONE grammar for every URL/API integer, * the ONE omission rule, the ONE serializer. * * Hoisted out of `hooks/state/` so SERVER code (route handlers, DALs) and the * CLIENT hook parse the same URL with the same function: a param contract that * disagrees across the wire is the bug class this removes. JSX-free leaf with * its own `exports` subpath; `hooks/state/*` re-exports every name it used to own. */ /** JavaScript types a URL parameter can carry. `int` is a whole number with a floor. */ export type JSType = 'string' | 'number' | 'int' | 'boolean' | 'array' | 'object'; export interface PositiveIntOptions { /** Smallest accepted value (default 1). Anything below falls back. */ min?: number; /** Largest accepted value. Applied to the PARSED value AND to the fallback. */ max?: number; } /** * THE integer grammar for URL params, env vars, and page sizes. * * `null`/`undefined`/`''` → fallback. Otherwise `Math.trunc(Number(raw))`, which * accepts `'12'`, `12`, `'12.9'` (→ 12) and `'0x10'` (→ 16), and rejects `'12abc'` * and `'abc'` (`NaN`) — the historical `parseInt` accepted `'12abc'` as 12 and * produced `NaN` for `'abc'`. A value below `min` falls back. The RESULT (parsed * value or fallback alike) is clamped to `max`, so "never exceeds max" has one owner. */ export declare function positiveInt(raw: string | number | null | undefined, fallback: TFallback, { min, max }?: PositiveIntOptions): number | TFallback; /** Ceilings every paged surface shares. */ export declare const PAGE_PARAM_LIMITS: { readonly maxPage: 10000; readonly maxPageSize: 100; }; /** Total pages for a row count. Floors at 1, so an empty result is still "page 1 of 1". */ export declare function pageCount(total: number, pageSize: number): number; /** Clamp a requested page into `1..totalPages`. */ export declare function clampPageToTotal(page: number, totalPages: number): number; type OutputTypeMap = { string: string; number: number; int: number; boolean: boolean; array: string[]; object: Record; }; export type OutputTypeForJSType = OutputTypeMap[T]; export interface BaseParamConfig { type: T; default?: OutputTypeMap[T]; required?: boolean; } /** An integer parameter with an optional floor and ceiling. */ export interface IntParamConfig extends BaseParamConfig<'int'> { min?: number; max?: number; } export type ParamConfig = T extends 'int' ? IntParamConfig : BaseParamConfig; export type ParamSchema = Record; /** Identity helper that preserves literal types through a schema object. */ export declare function defineParamSchema(schema: T): T; /** * Build a `Record` from a key tuple. Keeps `keyof` EXACT when spreading a * shared config across derived keys (an inline `Object.fromEntries` widens to * `Record` and loses the key union). */ export declare function fromKeys(keys: readonly K[], value: V): Record; /** Anything a URL can be read from: `URLSearchParams`, Next's `searchParams`, or a plain record. */ export type ParamInput = URLSearchParams | Record | null | undefined; /** THE first-value rule for a duplicated key (`?a=1&a=2` reads as `1`). */ export declare function firstParamValue(input: ParamInput, key: string): string | undefined; export interface ParseSchemaOptions { /** * What an ABSENT scalar with no declared `default` resolves to. * `'undefined'` (default) keeps today's behaviour; `'null'` is the explicit * "this filter is unset" spelling that survives JSON and `URLSearchParams`. */ absent?: 'undefined' | 'null'; } export type AbsentValue = Opts extends { absent: 'null'; } ? null : undefined; /** * The parsed shape of a schema: keys with a `default` and every array key are * non-nullable; an undefaulted scalar carries the caller's absent value. */ export type InferParsedParams = { [K in keyof TSchema]: TSchema[K] extends { type: 'array'; } ? string[] : TSchema[K] extends { default: infer D; } ? D : TSchema[K]['type'] extends infer T ? T extends JSType ? OutputTypeForJSType | Absent : never : never; }; /** * Parse a URL into a schema's shape. THE reader — the server route and the * client hook both call this, so a URL means exactly one thing. * * PRECEDENCE: an absent or empty SCALAR takes the declared `default` when there * is one, else the `absent` value. ARRAY keys never take `absent`: they are `[]`. */ export declare function parseSchemaParams>(schema: TSchema, input: ParamInput, options?: Opts): InferParsedParams>; /** * A schema entry OR a legacy flattened param (which spells its default * `defaultValue`). * * A UNION, not a bag of two optional fields: with only `default?` and * `defaultValue?` this is a WEAK TYPE, and TypeScript rejects any argument * sharing neither — including `{ type: 'string' }`, the single most common * schema entry there is. Naming the real config shapes fixes that without an * index signature, which `FlattenedParam` (an interface) could never satisfy. */ type OmissionConfig = ParamConfig | { default?: unknown; defaultValue?: unknown; }; /** * THE omission rule: `null`, `undefined`, `''`, `[]`, and a value equal to the * declared default are left out, so a URL only ever carries what differs. */ export declare function shouldIncludeInUrl(value: unknown, config: OmissionConfig | undefined): boolean; export interface CreateSearchParamsOptions { /** Join arrays with this separator instead of repeating the key (`;` for the hub's list APIs). */ arrayJoin?: string; } /** Build `URLSearchParams` from a value record (arrays repeat the key by default). */ export declare function createSearchParams(params: Record, { arrayJoin }?: CreateSearchParamsOptions): URLSearchParams; /** Serialize params to a query string, omitting everything `shouldIncludeInUrl` rejects. */ export declare function serializeSchemaParams(schema: TSchema, params: Record, options?: CreateSearchParamsOptions): string; /** Append a query string to a path, or return the path unchanged when it is empty. */ export declare function withQuery(path: string, queryString: string | null | undefined): string; export {}; //# sourceMappingURL=search-params.d.ts.map