Bibliothèques · Référence
Query Helpers
Reusable Effect query helper schemas and types for paginated, filtered, and sorted API request and response envelopes.
Dernière mise à jour le 16 octobre 2026
Overview
Use Query for table state, router validation, and Effect HttpApi query parameters. Sorting stays structured in application code and JSON; Effect HttpApi automatically derives its compact HTTP representation. PaginatedResponse(Item) wraps results in { data, meta } and preserves the item schema's codecs and service requirements.
Install
bunx shadcn@latest add @krak-stack/query-helpersQuery Input
page: zero-based index, default0.pageSize: between1and100, default20.globalFilter: optional search text.sort: optional ordered array of{ id, direction: "asc" | "desc" }.
const query = {
page: 0,
pageSize: 20,
sort: [{ id: "publicName", direction: "asc" }],
} satisfies QueryType;HTTP Encoding
Pass Query directly to HttpApiEndpoint. Default pagination values are omitted during encoding: page=0 and pageSize=20 are not sent. Each non-default value is retained independently, and decoding restores omitted defaults. This also applies to base and JSON encoding. Its StringTree codec encodes ascending fields as field, descending fields as -field, and multiple fields as field,-otherField. An empty array encodes as an empty string and decodes back to []; omitted sorting stays omitted. Invalid compact strings fail schema validation.
import { Schema } from "effect";
import { Query } from "@/lib/query";
const httpQuery = Schema.toCodecStringTree(Query);
const encoded = Schema.encodeSync(httpQuery)({
page: 0, pageSize: 20,
sort: [{ id: "name", direction: "desc" }],
});
// { sort: "-name" }
const decoded = Schema.decodeUnknownSync(httpQuery)(encoded);
// decoded.page: 0; decoded.pageSize: 20
// decoded.sort: [{ id: "name", direction: "desc" }]Schema.encodeSync(Query) and Schema.toCodecJson(Query) preserve arrays. Use QueryStandard as TanStack Router's validateSearch for structured search state. QueryEncoded describes the base schema representation, not the HTTP StringTree representation.
Router Defaults and Clean URLs
QueryStandard supplies page: 0 and pageSize: 20 during validation, so Route.useSearch() provides complete pagination state without another decode in the component. Validation restores defaults for application code; search middleware omits redundant defaults from URLs. Do not make validated pagination optional just to shorten URLs.
Configure TanStack Router's stripSearchParams once in src/routes/__root.tsx:
import { createRootRoute, stripSearchParams } from "@tanstack/react-router";
export const Route = createRootRoute({
search: {
middlewares: [
stripSearchParams<{ page?: number; pageSize?: number }>({
page: 0,
pageSize: 20,
}),
],
},
// Preserve the application's existing shell, head, and route options.
});Each child route still opts into pagination validation:
import { createFileRoute } from "@tanstack/react-router";
import { QueryStandard } from "@krak-stack/registry/query";
export const Route = createFileRoute("/admin/items")({
validateSearch: QueryStandard,
// Keep the application's component and other route options.
});For registry-file installations, import QueryStandard from @/lib/query instead. Components can pass Route.useSearch() directly to controlled table state.
| URL search | Validated pagination |
|---|---|
| Empty | { page: 0, pageSize: 20 } |
?page=2 | { page: 2, pageSize: 20 } |
?pageSize=50 | { page: 0, pageSize: 50 } |
Root middleware applies to descendant routes, but does not add pagination to every route's validated schema. Keep existing middleware such as retainSearchParams in the same array. If the root already validates search, include typeof RootSearchSchema.Type in the middleware generic using an intersection with the optional pagination fields.
Use root-wide stripping only when those keys share the same defaults throughout the subtree. If a route has a different default page size, configure stripping on a narrower common layout: removing an explicit non-default value would change that route's meaning. Stripping happens when the router builds URLs; do not rely on it alone to immediately rewrite every manually entered URL. It handles top-level keys, not fields inside nested query objects.
HTTP/base encoding with Schema.encodeSync(Query) also omits defaults, but that is separate from Router search middleware.
Explicit Sort Codecs
For standalone compact strings, use the exported Effect codecs:
import { Schema } from "effect";
import { SortParamFromString, SortParamsFromString } from "@/lib/query";
Schema.encodeSync(SortParamFromString)({ id: "name", direction: "desc" });
// "-name"
Schema.decodeUnknownSync(SortParamsFromString)("name,-createdAt");
// [{ id: "name", direction: "asc" }, { id: "createdAt", direction: "desc" }]The older encodeSortParam / decodeSortParam functions and field:direction format are no longer supported.
API Usage
import { HttpApiEndpoint } from "effect/http-api";
import { Query, PaginatedResponse } from "@/lib/query";
import { Record } from "@/services/record/schema";
HttpApiEndpoint.get("listRecords", "/records", {
query: Query,
success: PaginatedResponse(Record),
});Generated clients accept structured sorting; handlers receive the same structured sorting after HTTP decoding. No per-service sort override is needed. Return { data: rows, meta: { page, pageSize, total, pageCount: Math.ceil(total / pageSize) } }.
Included Exports
Query, QueryStandard, QueryType, QueryEncoded, SortDirection, SortState, SortParam, SortParams, SortParamFromString, SortParamsFromString, PaginationMeta, PaginationMetaType, and PaginatedResponse.
Français
Les valeurs de pagination par défaut (page=0 et pageSize=20) sont omises lors de l'encodage HTTP, JSON et de base. Chaque valeur non standard est conservée indépendamment; le décodage rétablit les valeurs par défaut absentes. Query conserve le tri sous forme de tableau dans l'état du tableau, le routeur et les données JSON. Effect HttpApi utilise automatiquement le codec StringTree pour transmettre le tri sous la forme champ,-autreChamp, puis le décoder en tableau. Un tableau vide devient une chaîne vide; un tri absent reste absent. Utilisez directement Query dans les endpoints et QueryStandard pour valider les paramètres du routeur. Pour convertir une chaîne séparément, utilisez Schema.encodeSync ou Schema.decodeUnknownSync avec SortParamFromString ou SortParamsFromString. Les anciennes fonctions encodeSortParam / decodeSortParam et le format champ:direction ne sont plus pris en charge.
Valeurs par défaut du routeur
QueryStandard rétablit les valeurs absentes (page: 0, pageSize: 20) pour fournir un état complet aux composants. Configurez stripSearchParams dans __root.tsx pour retirer ces valeurs redondantes des URL générées, puis utilisez QueryStandard dans chaque route concernée. Conservez les middlewares existants. Si les valeurs par défaut diffèrent selon les routes, limitez le middleware à une branche partageant les mêmes valeurs. Il traite les clés de premier niveau et ne garantit pas une réécriture immédiate de toutes les URL saisies manuellement.