/** * `@nifrajs/web` search-param engine - the agnostic core behind typed, validated search params. It parses * a URL query into a structured object, validates it against a route's Standard Schema (failing CLOSED to * defaults on hostile input), serializes it back, and structural-shares successive values so selector * reads stay referentially stable. Pure: no DOM, no framework, no fs - it runs identically at server match * time and on client navigation, so the two sides cannot drift. See SEARCH-PARAMS-PLAN.md. * * Not yet re-exported from the package entry: this is the engine the route/server/client wiring (later * phases) build on. The public surface (`searchSchema` on a route, `useSearch`, typed `navigate`) lands * with that wiring. */ import type { InferOutput, StandardSchemaV1 } from "@nifrajs/core/server"; /** * Bounds applied while parsing a query string. A search string is attacker-controlled, so it is capped * BEFORE an object graph is built from it - defense-in-depth alongside the route schema's own validation. */ export interface SearchLimits { /** Longest raw query (with or without a leading `?`) that will be parsed; longer fails closed to `{}`. */ readonly maxLength: number; /** Most top-level keys that will be kept; keys beyond the cap are dropped. */ readonly maxKeys: number; /** Deepest nesting a decoded JSON value may have; deeper values are kept as their raw string instead. */ readonly maxDepth: number; } /** Conservative defaults: a 4 KB query, 64 keys, 6 levels of nesting. */ export declare const DEFAULT_SEARCH_LIMITS: SearchLimits; /** * A pluggable query <-> object codec. The default is JSON-first (numbers/booleans/arrays/nested objects * survive the round-trip); an app can supply its own (compact, base64, comma-arrays) in a later phase. */ export interface SearchCodec { /** Parse a raw query (`"?a=1&b=x"` or `"a=1&b=x"`) into a structured object, honoring `limits`. */ parse(raw: string, limits: SearchLimits): Record; /** Serialize a structured object to a query string, including a leading `?` (or `""` when empty). */ serialize(value: Record): string; } /** * Parse a raw URL query into a structured object. Fails closed to `{}` when the raw string exceeds * `limits.maxLength` (before any parsing work is done). */ export declare function parseSearch(raw: string, codec?: SearchCodec, limits?: SearchLimits): Record; /** Serialize a structured search object back to a query string (leading `?`, or `""` when empty). */ export declare function serializeSearch(value: Record, codec?: SearchCodec): string; /** * The search a route sees for a raw URL query: parsed, then validated against a single `searchSchema` * when the route declares one (failing closed to its defaults), or the raw parsed query otherwise. A * one-link {@link searchOfChain}; use that directly for a layout+page chain. Both the server (`renderPage`, * loader ctx) and the client (the adapter mount) derive search this way, from the same URL + schema, so * they produce the identical value - SSR-correct by construction, no serialization. */ export declare function searchOf(searchSchema: StandardSchemaV1 | undefined, rawSearch: string): Record; /** * The search for a route whose effective schema is a CHAIN - a `_layout` may declare `searchSchema` for * shared keys (`?org`, `?theme`) and each page declares its own. The raw query is validated against every * schema in the chain (outermost layout first, page last) and their outputs are merged, page-wins on a key * conflict (nearest-wins, like `mergeHeads`): validate-each-then-combine, since Standard Schema has no * `.merge()`. `undefined` links (a layout with no `searchSchema`) contribute nothing; when the whole chain * is empty, the raw parsed query is returned. Fails closed per schema, same as {@link searchOf} (which is * this with a one-link chain). The server and the client build the identical chain, so the value matches. */ export declare function searchOfChain(schemas: readonly (StandardSchemaV1 | undefined)[], rawSearch: string): Record; /** * The search OUTPUT type for a route MODULE - its `searchSchema`'s validated output, or the raw parsed * query (`Record`) when it declares none. The building block for typed cross-route * navigation: generated route types (`nifra sync-routes`) map each path to * `SearchOf")>`, populating the augmentable `RouteSearch` interface. */ export type SearchOf = Module extends { searchSchema: infer S; } ? S extends StandardSchemaV1 ? InferOutput extends Record ? InferOutput : never : Record : Record; /** * Validate a parsed search object against a route's Standard Schema, returning the typed output. Fails * CLOSED: on validation issues it retries against an empty object so the schema's per-field defaults apply, * and degrades to `{}` only if even that fails - it never throws on hostile input. An async validator is a * configuration error (search must validate synchronously, in the render/nav path) and throws loudly. */ export declare function validateSearch(schema: Schema, parsed: Record): InferOutput; /** * Structural sharing (shallow-first): return `next`, but reuse `prev`'s reference for every top-level key * whose value is deep-equal. When nothing changed, `prev` itself is returned (stable identity), so a * selector like `useSearch(s => s.filters)` does not see a change - and a filters-only component does not * re-render when an unrelated key (`page`) changes. */ export declare function shareSearch(prev: T, next: T): T; /** * True when two raw queries differ ONLY in keys a route declared client-only (`searchClientKeys`) - the * signal a client navigation can update search WITHOUT re-running the loader (re-render, not revalidate: * `?tab`, a client-side `?sort` over already-loaded rows, `?modal`). Returns false when a key OUTSIDE the * allow-list changed (that key drives data, so the nav must revalidate) and when nothing changed at all (a * same-URL nav still revalidates). Fail-safe by construction: an empty allow-list can never return true, * so a route that declares no client keys always revalidates - the correct, never-stale default. */ export declare function isClientOnlySearchChange(prevRaw: string, nextRaw: string, clientKeys: readonly string[]): boolean; //# sourceMappingURL=search.d.ts.map