@querykitjs/web 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,145 +1,2 @@
1
- import { useSearchParams } from 'react-router-dom';
2
- import { useMemo, useCallback } from 'react';
3
-
4
- // src/react/use-list-params.ts
5
-
6
- // src/url.ts
7
- var URL_DEFAULTS = {
8
- pageParam: "page",
9
- sizeParam: "size",
10
- sortParam: "sortType"
11
- };
12
- function decodeSort(value) {
13
- if (!value) return void 0;
14
- const items = value.split(",").map((s) => s.trim()).filter(Boolean).map((tok) => {
15
- const desc = tok.startsWith("-");
16
- return { key: desc ? tok.slice(1) : tok, direction: desc ? "desc" : "asc" };
17
- }).filter((i) => i.key);
18
- return items.length ? items : void 0;
19
- }
20
- function encodeSort(sort) {
21
- return sort.map((i) => typeof i === "string" ? i.trim() : i && i.key ? (i.direction === "desc" ? "-" : "") + i.key : "").filter(Boolean).join(",");
22
- }
23
- function schemaToFilter(schema, searchParams) {
24
- const conditions = [];
25
- const groups = [];
26
- for (const [param, d] of Object.entries(schema)) {
27
- const key = d.key ?? param;
28
- if (d.range) {
29
- const from = searchParams.get(d.range[0]);
30
- const to = searchParams.get(d.range[1]);
31
- if (from) conditions.push(cond(key, ">=", from));
32
- if (to) conditions.push(cond(key, "<=", to));
33
- continue;
34
- }
35
- if (d.between) {
36
- const from = searchParams.get(d.between[0]);
37
- const to = searchParams.get(d.between[1]);
38
- if (from && to) conditions.push(cond(key, "between", [from, to]));
39
- continue;
40
- }
41
- const raw = searchParams.get(param);
42
- let value = raw !== null ? raw : d.default;
43
- if (typeof value === "string" && d.trim) value = value.trim();
44
- if (value === void 0 || value === null || value === "") continue;
45
- if (Array.isArray(value) && value.length === 0) continue;
46
- if (d.search && typeof value === "string") {
47
- groups.push({ or: d.search.map((field) => cond(field, "%_%", value)) });
48
- continue;
49
- }
50
- if (d.split && typeof value === "string") {
51
- const delimiter = d.split === true ? "," : d.split;
52
- const parts = value.split(delimiter).map((s) => s.trim()).filter(Boolean);
53
- if (parts.length === 0) continue;
54
- value = parts;
55
- }
56
- conditions.push(cond(key, d.operation ?? "=", value));
57
- }
58
- return groups.length === 0 ? conditions : { and: [...groups, ...conditions] };
59
- }
60
- function cond(key, operation, value) {
61
- return { key, operation, value };
62
- }
63
- function readListParams(schema, searchParams, url = {}) {
64
- const cfg = { ...URL_DEFAULTS, ...url };
65
- const size = searchParams.get(cfg.sizeParam);
66
- return {
67
- filter: schemaToFilter(schema, searchParams),
68
- sort: decodeSort(searchParams.get(cfg.sortParam)),
69
- page: Number(searchParams.get(cfg.pageParam)) || 1,
70
- perPage: size ? Number(size) : void 0
71
- };
72
- }
73
- function clone(searchParams) {
74
- return new URLSearchParams(searchParams);
75
- }
76
- function setParam(searchParams, name, value, url = {}) {
77
- const cfg = { ...URL_DEFAULTS, ...url };
78
- const next = clone(searchParams);
79
- next.delete(cfg.pageParam);
80
- if (value === null || value === void 0 || value === "") next.delete(name);
81
- else next.set(name, value);
82
- return next;
83
- }
84
- function setPage(searchParams, page, url = {}) {
85
- const cfg = { ...URL_DEFAULTS, ...url };
86
- const next = clone(searchParams);
87
- next.set(cfg.pageParam, String(page));
88
- return next;
89
- }
90
- function setSize(searchParams, size, url = {}) {
91
- const cfg = { ...URL_DEFAULTS, ...url };
92
- const next = clone(searchParams);
93
- next.delete(cfg.pageParam);
94
- next.set(cfg.sizeParam, String(size));
95
- return next;
96
- }
97
- function setSort(searchParams, sort, url = {}) {
98
- const cfg = { ...URL_DEFAULTS, ...url };
99
- const next = clone(searchParams);
100
- next.delete(cfg.pageParam);
101
- const encoded = encodeSort(sort);
102
- if (encoded) next.set(cfg.sortParam, encoded);
103
- else next.delete(cfg.sortParam);
104
- return next;
105
- }
106
- function resetParams(searchParams, keep = []) {
107
- const next = new URLSearchParams();
108
- for (const key of keep) {
109
- const value = searchParams.get(key);
110
- if (value !== null) next.set(key, value);
111
- }
112
- return next;
113
- }
114
-
115
- // src/react/use-list-params-base.ts
116
- function useListParamsBase(resource, options) {
117
- const { schema, searchParams, setSearchParams, url, with: withRel } = options;
118
- const params = useMemo(
119
- () => resource.list({ ...readListParams(schema, searchParams, url), with: withRel }),
120
- [resource, schema, searchParams, url, withRel]
121
- );
122
- return {
123
- params,
124
- searchParams,
125
- setParam: useCallback((name, value) => setSearchParams(setParam(searchParams, name, value, url)), [searchParams, setSearchParams, url]),
126
- setPage: useCallback((page) => setSearchParams(setPage(searchParams, page, url)), [searchParams, setSearchParams, url]),
127
- setSize: useCallback((size) => setSearchParams(setSize(searchParams, size, url)), [searchParams, setSearchParams, url]),
128
- setSort: useCallback((sort) => setSearchParams(setSort(searchParams, sort, url)), [searchParams, setSearchParams, url]),
129
- reset: useCallback((keep) => setSearchParams(resetParams(searchParams, keep)), [searchParams, setSearchParams])
130
- };
131
- }
132
-
133
- // src/react/use-list-params.ts
134
- function useListParams(resource, options) {
135
- const [searchParams, setSearchParams] = useSearchParams();
136
- return useListParamsBase(resource, {
137
- ...options,
138
- searchParams,
139
- setSearchParams: (next) => setSearchParams(next)
140
- });
141
- }
142
-
143
- export { useListParams, useListParamsBase };
144
- //# sourceMappingURL=index.js.map
1
+ import {useSearchParams}from'react-router';import {useMemo,useCallback}from'react';import'@querykitjs/core';var m={pageParam:"page",sizeParam:"size",sortParam:"sortType"};function S(a){return a.map(r=>typeof r=="string"?r.trim():r&&r.key?(r.direction==="desc"?"-":"")+r.key:"").filter(Boolean).join(",")}function c(a){return new URLSearchParams(a)}function f(a,r,s,e={}){let t={...m,...e},n=c(a);return n.delete(t.pageParam),s==null||s===""?n.delete(r):n.set(r,s),n}function d(a,r,s={}){let e={...m,...s},t=c(a);return t.set(e.pageParam,String(r)),t}function P(a,r,s={}){let e={...m,...s},t=c(a);return t.delete(e.pageParam),t.set(e.sizeParam,String(r)),t}function g(a,r,s={}){let e={...m,...s},t=c(a);t.delete(e.pageParam);let n=S(r);return n?t.set(e.sortParam,n):t.delete(e.sortParam),t}function L(a,r=[]){let s=new URLSearchParams;for(let e of r){let t=a.get(e);t!==null&&s.set(e,t);}return s}function p(a,r){let{schema:s,searchParams:e,setSearchParams:t,url:n,with:l}=r,u=useMemo(()=>a.fromSearchParams(e,{schema:s,url:n,with:l}),[a,s,e,n,l]);return {params:u,clamped:u.clamped===true,searchParams:e,setParam:useCallback((o,h)=>t(f(e,o,h,n)),[e,t,n]),setPage:useCallback(o=>t(d(e,o,n)),[e,t,n]),setSize:useCallback(o=>t(P(e,o,n)),[e,t,n]),setSort:useCallback(o=>t(g(e,o,n)),[e,t,n]),reset:useCallback(o=>t(L(e,o)),[e,t])}}function x(a,r){let[s,e]=useSearchParams();return p(a,{...r,searchParams:s,setSearchParams:t=>e(t)})}export{x as useListParams,p as useListParamsBase};//# sourceMappingURL=index.js.map
145
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/url.ts","../../src/react/use-list-params-base.ts","../../src/react/use-list-params.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { buildListParams } from \"./query\";\nimport type { FieldDescriptor, ListSchema } from \"./schema\";\nimport type { FieldCondition, Filter, FilterNode, FilterValue, ListParams, ListPayload, Sort, SortDirection, SortInput } from \"./types\";\n\n/** URL param nomlari (sahifa/o'lcham/sort) — sozlanadigan. */\nexport interface UrlConfig {\n pageParam?: string;\n sizeParam?: string;\n sortParam?: string;\n}\n\nconst URL_DEFAULTS: Required<UrlConfig> = {\n pageParam: \"page\",\n sizeParam: \"size\",\n sortParam: \"sortType\",\n};\n\n/**\n * `\"-createdAt,id,-appNumber\"` → `[{ key, direction }, …]` — comma-separated,\n * order preserved, `-` prefix = `desc` (else `asc`). Empty → `undefined`.\n */\nexport function decodeSort(value: string | null | undefined): Sort | undefined {\n if (!value) return undefined;\n const items = value\n .split(\",\")\n .map(s => s.trim())\n .filter(Boolean)\n .map(tok => {\n const desc = tok.startsWith(\"-\");\n return { key: desc ? tok.slice(1) : tok, direction: (desc ? \"desc\" : \"asc\") as SortDirection };\n })\n .filter(i => i.key); // drop empty keys (e.g. a lone \"-\")\n return items.length ? items : undefined;\n}\n\n/** `[{ key, direction }, …]` (or `\"-field\"` shorthand strings) → `\"-createdAt,id,-appNumber\"` (empty → `\"\"`). */\nexport function encodeSort(sort: SortInput): string {\n return sort\n .map(i => (typeof i === \"string\" ? i.trim() : i && i.key ? (i.direction === \"desc\" ? \"-\" : \"\") + i.key : \"\"))\n .filter(Boolean)\n .join(\",\");\n}\n\n/**\n * Schema + searchParams → filter. Applies `range`/`between`/`search`/`split`/\n * `default`/`trim`; empty/absent params are dropped. Returns a flat\n * `FieldCondition[]` (implicit AND), or a tree when a `search` OR-group is\n * present (`{ and: [orGroup, …conditions] }`).\n */\nexport function schemaToFilter(schema: ListSchema, searchParams: URLSearchParams): Filter {\n const conditions: FieldCondition[] = [];\n const groups: FilterNode[] = [];\n\n for (const [param, d] of Object.entries(schema) as [string, FieldDescriptor][]) {\n const key = d.key ?? param;\n\n // range → two inclusive conditions (>= , <=)\n if (d.range) {\n const from = searchParams.get(d.range[0]);\n const to = searchParams.get(d.range[1]);\n if (from) conditions.push(cond(key, \">=\", from));\n if (to) conditions.push(cond(key, \"<=\", to));\n continue;\n }\n\n // between → one condition with a 2-tuple value\n if (d.between) {\n const from = searchParams.get(d.between[0]);\n const to = searchParams.get(d.between[1]);\n if (from && to) conditions.push(cond(key, \"between\", [from, to]));\n continue;\n }\n\n // read value, falling back to `default` only when the param is ABSENT\n // (an empty `?x=` means the user cleared the filter → prune, don't default).\n const raw = searchParams.get(param);\n let value: FilterValue | undefined = raw !== null ? raw : d.default;\n if (typeof value === \"string\" && d.trim) value = value.trim();\n if (value === undefined || value === null || value === \"\") continue;\n if (Array.isArray(value) && value.length === 0) continue;\n\n // search → OR of `contains` across the listed fields (needs a string)\n if (d.search && typeof value === \"string\") {\n groups.push({ or: d.search.map(field => cond(field, \"%_%\", value as string)) });\n continue;\n }\n\n // split → comma-string → array (for `in`/`notIn`); guarded against empties\n if (d.split && typeof value === \"string\") {\n const delimiter = d.split === true ? \",\" : d.split;\n const parts = value\n .split(delimiter)\n .map(s => s.trim())\n .filter(Boolean);\n if (parts.length === 0) continue;\n value = parts;\n }\n\n conditions.push(cond(key, d.operation ?? \"=\", value));\n }\n\n return groups.length === 0 ? conditions : { and: [...groups, ...conditions] };\n}\n\nfunction cond(key: string, operation: FieldCondition[\"operation\"], value: FilterValue): FieldCondition {\n return { key, operation, value };\n}\n\n/** searchParams → normalizatsiyalanmagan {@link ListParams} (filter/sort/page/perPage). */\nexport function readListParams(schema: ListSchema, searchParams: URLSearchParams, url: UrlConfig = {}): ListParams {\n const cfg = { ...URL_DEFAULTS, ...url };\n const size = searchParams.get(cfg.sizeParam);\n return {\n filter: schemaToFilter(schema, searchParams),\n sort: decodeSort(searchParams.get(cfg.sortParam)),\n page: Number(searchParams.get(cfg.pageParam)) || 1,\n perPage: size ? Number(size) : undefined,\n };\n}\n\n/** searchParams → to'liq, yuborishga tayyor {@link ListPayload}. */\nexport function searchParamsToPayload(schema: ListSchema, searchParams: URLSearchParams, url?: UrlConfig): ListPayload {\n return buildListParams(readListParams(schema, searchParams, url));\n}\n\n/* ------------------------- URL yozish (immutable) ------------------------- */\n/* Har biri YANGI URLSearchParams qaytaradi; filter/sort o'zgarsa `page` reset. */\n\nfunction clone(searchParams: URLSearchParams): URLSearchParams {\n return new URLSearchParams(searchParams);\n}\n\n/** Filter param'ini o'rnatadi/o'chiradi va `page`ni reset qiladi. */\nexport function setParam(searchParams: URLSearchParams, name: string, value: string | null | undefined, url: UrlConfig = {}): URLSearchParams {\n const cfg = { ...URL_DEFAULTS, ...url };\n const next = clone(searchParams);\n next.delete(cfg.pageParam);\n if (value === null || value === undefined || value === \"\") next.delete(name);\n else next.set(name, value);\n return next;\n}\n\n/** Sahifani o'rnatadi (`page` reset qilinmaydi). */\nexport function setPage(searchParams: URLSearchParams, page: number, url: UrlConfig = {}): URLSearchParams {\n const cfg = { ...URL_DEFAULTS, ...url };\n const next = clone(searchParams);\n next.set(cfg.pageParam, String(page));\n return next;\n}\n\n/** Sahifa o'lchamini o'rnatadi va `page`ni reset qiladi. */\nexport function setSize(searchParams: URLSearchParams, size: number, url: UrlConfig = {}): URLSearchParams {\n const cfg = { ...URL_DEFAULTS, ...url };\n const next = clone(searchParams);\n next.delete(cfg.pageParam);\n next.set(cfg.sizeParam, String(size));\n return next;\n}\n\n/** Sortni o'rnatadi (`[{ key, direction }, …]`) va `page`ni reset qiladi. */\nexport function setSort(searchParams: URLSearchParams, sort: SortInput, url: UrlConfig = {}): URLSearchParams {\n const cfg = { ...URL_DEFAULTS, ...url };\n const next = clone(searchParams);\n next.delete(cfg.pageParam);\n const encoded = encodeSort(sort);\n if (encoded) next.set(cfg.sortParam, encoded);\n else next.delete(cfg.sortParam);\n return next;\n}\n\n/** Barcha list param'larini tozalaydi (ixtiyoriy saqlanadigan kalitlar bilan). */\nexport function resetParams(searchParams: URLSearchParams, keep: string[] = []): URLSearchParams {\n const next = new URLSearchParams();\n for (const key of keep) {\n const value = searchParams.get(key);\n if (value !== null) next.set(key, value);\n }\n return next;\n}\n","import { useCallback, useMemo } from \"react\";\n\nimport type { AdapterName, WithFor } from \"../adapter\";\nimport type { Resource, ResourceListParams } from \"../registry\";\nimport type { ListSchema } from \"../schema\";\nimport type { ListPayload, SortInput } from \"../types\";\nimport type { UrlConfig } from \"../url\";\nimport { readListParams, resetParams, setPage, setParam, setSize, setSort } from \"../url\";\n\nexport interface UseListParamsBaseOptions<A extends AdapterName> {\n /** URL↔filter schema ({@link defineListSchema} / `resource.schema`). Memoize it. */\n schema: ListSchema;\n /** Current URL search params (from your router). */\n searchParams: URLSearchParams;\n /** Apply new search params (`(next) => void`). */\n setSearchParams: (next: URLSearchParams) => void;\n /** URL param names (page/size/sortType) — optional. Memoize it. */\n url?: UrlConfig;\n /** Fixed relations to load on every request (not URL-driven). */\n with?: WithFor<A>;\n}\n\nexport interface UseListParamsResult {\n /** Ready-to-send list payload (built from the URL via the resource's defaults). */\n params: ListPayload;\n searchParams: URLSearchParams;\n /** Set/clear a filter param (resets `page`). */\n setParam: (name: string, value: string | null | undefined) => void;\n setPage: (page: number) => void;\n setSize: (size: number) => void;\n setSort: (sort: SortInput) => void;\n reset: (keep?: string[]) => void;\n}\n\n/**\n * Router-agnostic list URL-state hook. Reads the URL via the schema, builds a\n * ready-to-send payload through the `resource` (registry defaults + adapter), and\n * returns URL setters (filter/sort/size changes reset `page`). Bring your own\n * `searchParams`/`setSearchParams` (Next.js, TanStack Router, …).\n */\nexport function useListParamsBase<T, A extends AdapterName>(resource: Resource<T, A>, options: UseListParamsBaseOptions<A>): UseListParamsResult {\n const { schema, searchParams, setSearchParams, url, with: withRel } = options;\n\n const params = useMemo(\n () => resource.list({ ...readListParams(schema, searchParams, url), with: withRel } as ResourceListParams<T, A>),\n [resource, schema, searchParams, url, withRel],\n );\n\n return {\n params,\n searchParams,\n setParam: useCallback((name, value) => setSearchParams(setParam(searchParams, name, value, url)), [searchParams, setSearchParams, url]),\n setPage: useCallback((page: number) => setSearchParams(setPage(searchParams, page, url)), [searchParams, setSearchParams, url]),\n setSize: useCallback((size: number) => setSearchParams(setSize(searchParams, size, url)), [searchParams, setSearchParams, url]),\n setSort: useCallback((sort: SortInput) => setSearchParams(setSort(searchParams, sort, url)), [searchParams, setSearchParams, url]),\n reset: useCallback((keep?: string[]) => setSearchParams(resetParams(searchParams, keep)), [searchParams, setSearchParams]),\n };\n}\n","import { useSearchParams } from \"react-router-dom\";\n\nimport type { AdapterName, WithFor } from \"../adapter\";\nimport type { Resource } from \"../registry\";\nimport type { ListSchema } from \"../schema\";\nimport type { UrlConfig } from \"../url\";\nimport { useListParamsBase, type UseListParamsResult } from \"./use-list-params-base\";\n\nexport interface UseListParamsOptions<A extends AdapterName> {\n /** URL↔filter schema ({@link defineListSchema} / `resource.schema`). Memoize it. */\n schema: ListSchema;\n /** URL param names (page/size/sortType) — optional. Memoize it. */\n url?: UrlConfig;\n /** Fixed relations to load on every request (not URL-driven). */\n with?: WithFor<A>;\n}\n\n/**\n * List URL-state hook — turnkey with **react-router-dom** (uses `useSearchParams`\n * internally). Reads filters/sort/page from the URL via the schema, builds a\n * ready-to-send payload through the `resource`, and returns URL setters (which\n * reset `page` on filter/sort/size change). Does **not** fetch — pass `params`\n * to your own axios / TanStack Query.\n *\n * For other routers (Next.js, TanStack Router), use {@link useListParamsBase} and\n * supply `searchParams`/`setSearchParams` yourself.\n *\n * @example\n * ```tsx\n * const users = qk.resource<IUser>(\"users\");\n * const { params, setPage, setSort } = useListParams(users, { schema: usersSchema });\n * const { data } = useQuery({ queryKey: users.keys.list(params), queryFn: () => post(\"/users/list\", params) });\n * ```\n */\nexport function useListParams<T, A extends AdapterName>(resource: Resource<T, A>, options: UseListParamsOptions<A>): UseListParamsResult {\n const [searchParams, setSearchParams] = useSearchParams();\n return useListParamsBase(resource, {\n ...options,\n searchParams,\n setSearchParams: next => setSearchParams(next),\n });\n}\n"]}
1
+ {"version":3,"sources":["../../src/url.ts","../../src/react/use-list-params-base.ts","../../src/react/use-list-params.ts"],"names":["URL_DEFAULTS","encodeSort","sort","i","clone","searchParams","setParam","name","value","url","cfg","next","setPage","page","setSize","size","setSort","encoded","resetParams","keep","key","useListParamsBase","resource","options","schema","setSearchParams","withRel","params","useMemo","useCallback","useListParams","useSearchParams"],"mappings":"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","file":"index.js","sourcesContent":["import { DEFAULT_MAX_OFFSET, DEFAULT_MAX_PER_PAGE, DEFAULT_MAX_SORT_FIELDS, DEFAULT_PER_PAGE } from \"@querykitjs/core\";\n\nimport { buildListParams, clampInt } from \"./query\";\nimport type { FieldDescriptor, ListSchema } from \"./schema\";\nimport type { FieldCondition, Filter, FilterNode, FilterValue, ListParams, ListPayload, Sort, SortDirection, SortInput } from \"./types\";\n\n/** URL param nomlari (sahifa/o'lcham/sort) — sozlanadigan. */\nexport interface UrlConfig {\n pageParam?: string;\n sizeParam?: string;\n sortParam?: string;\n}\n\nconst URL_DEFAULTS: Required<UrlConfig> = {\n pageParam: \"page\",\n sizeParam: \"size\",\n sortParam: \"sortType\",\n};\n\n/**\n * `\"-createdAt,id,-appNumber\"` → `[{ key, direction }, …]` — comma-separated,\n * order preserved, `-` prefix = `desc` (else `asc`). Empty → `undefined`.\n */\nexport function decodeSort(value: string | null | undefined): Sort | undefined {\n if (!value) return undefined;\n const items = value\n .split(\",\")\n .map(s => s.trim())\n .filter(Boolean)\n .map(tok => {\n const desc = tok.startsWith(\"-\");\n return { key: desc ? tok.slice(1) : tok, direction: (desc ? \"desc\" : \"asc\") as SortDirection };\n })\n .filter(i => i.key); // drop empty keys (e.g. a lone \"-\")\n return items.length ? items : undefined;\n}\n\n/** `[{ key, direction }, …]` (or `\"-field\"` shorthand strings) → `\"-createdAt,id,-appNumber\"` (empty → `\"\"`). */\nexport function encodeSort(sort: SortInput): string {\n return sort\n .map(i => (typeof i === \"string\" ? i.trim() : i && i.key ? (i.direction === \"desc\" ? \"-\" : \"\") + i.key : \"\"))\n .filter(Boolean)\n .join(\",\");\n}\n\n/**\n * Schema + searchParams → filter. Applies `range`/`between`/`search`/`split`/\n * `default`/`trim`; empty/absent params are dropped. Returns a flat\n * `FieldCondition[]` (implicit AND), or a tree when a `search` OR-group is\n * present (`{ and: [orGroup, …conditions] }`).\n */\nexport function schemaToFilter(schema: ListSchema, searchParams: URLSearchParams): Filter {\n const conditions: FieldCondition[] = [];\n const groups: FilterNode[] = [];\n\n for (const [param, d] of Object.entries(schema) as [string, FieldDescriptor][]) {\n const key = d.key ?? param;\n\n // range → two inclusive conditions (>= , <=)\n if (d.range) {\n const from = searchParams.get(d.range[0]);\n const to = searchParams.get(d.range[1]);\n if (from) conditions.push(cond(key, \">=\", from));\n if (to) conditions.push(cond(key, \"<=\", to));\n continue;\n }\n\n // between → one condition with a 2-tuple value\n if (d.between) {\n const from = searchParams.get(d.between[0]);\n const to = searchParams.get(d.between[1]);\n if (from && to) conditions.push(cond(key, \"between\", [from, to]));\n continue;\n }\n\n // read value, falling back to `default` only when the param is ABSENT\n // (an empty `?x=` means the user cleared the filter → prune, don't default).\n const raw = searchParams.get(param);\n let value: FilterValue | undefined = raw !== null ? raw : d.default;\n if (typeof value === \"string\" && d.trim) value = value.trim();\n if (value === undefined || value === null || value === \"\") continue;\n if (Array.isArray(value) && value.length === 0) continue;\n\n // search → OR of `contains` across the listed fields (needs a string)\n if (d.search && typeof value === \"string\") {\n groups.push({ or: d.search.map(field => cond(field, \"%_%\", value as string)) });\n continue;\n }\n\n // split → comma-string → array (for `in`/`notIn`); guarded against empties\n if (d.split && typeof value === \"string\") {\n const delimiter = d.split === true ? \",\" : d.split;\n const parts = value\n .split(delimiter)\n .map(s => s.trim())\n .filter(Boolean);\n if (parts.length === 0) continue;\n value = parts;\n }\n\n conditions.push(cond(key, d.operation ?? \"=\", value));\n }\n\n return groups.length === 0 ? conditions : { and: [...groups, ...conditions] };\n}\n\nfunction cond(key: string, operation: FieldCondition[\"operation\"], value: FilterValue): FieldCondition {\n return { key, operation, value };\n}\n\n/**\n * The bounds the URL readers clamp to — the builder's own (`createRegistry`\n * `defaults`); an absent one is the core default.\n */\nexport interface UrlLimits {\n perPage?: number;\n maxPerPage?: number;\n maxOffset?: number;\n maxSortFields?: number;\n}\n\n/** {@link ListParams} read from a URL. `clamped` — the URL's page or sort was cut to the limits. */\nexport type UrlListParams = ListParams & { clamped: boolean };\n\n/**\n * A list payload built from a URL. `clamped` is non-enumerable: readable in code,\n * never serialized, so the wire payload is unchanged.\n */\nexport type UrlListPayload = ListPayload & { readonly clamped?: boolean };\n\n/** Marks a payload with a non-enumerable `clamped` flag. */\nexport function withClamped(payload: ListPayload, clamped: boolean): UrlListPayload {\n return Object.defineProperty(payload, \"clamped\", { value: clamped, enumerable: false });\n}\n\n/** The last page the builder accepts: `(page - 1) × perPage ≤ maxOffset`. */\nfunction lastPage(perPage: number | undefined, limits: UrlLimits): number {\n const resolved = clampInt(perPage, limits.perPage ?? DEFAULT_PER_PAGE, limits.maxPerPage ?? DEFAULT_MAX_PER_PAGE);\n return Math.floor((limits.maxOffset ?? DEFAULT_MAX_OFFSET) / resolved) + 1;\n}\n\n/**\n * searchParams → normalizatsiyalanmagan {@link ListParams} (filter/sort/page/perPage).\n * The URL is user input: a page past `maxOffset` becomes the last allowed page and a\n * sort past `maxSortFields` keeps its first fields, with `clamped: true` — so a\n * hand-edited `?page=9999` never throws during render. The builders still throw\n * for a direct call (task 008).\n */\nexport function readListParams(schema: ListSchema, searchParams: URLSearchParams, url: UrlConfig = {}, limits: UrlLimits = {}): UrlListParams {\n const cfg = { ...URL_DEFAULTS, ...url };\n const size = searchParams.get(cfg.sizeParam);\n const perPage = size ? Number(size) : undefined;\n const page = Math.trunc(Number(searchParams.get(cfg.pageParam))) || 1;\n const sort = decodeSort(searchParams.get(cfg.sortParam));\n const maxPage = lastPage(perPage, limits);\n const maxSortFields = limits.maxSortFields ?? DEFAULT_MAX_SORT_FIELDS;\n const isPageClamped = page > maxPage;\n const isSortClamped = sort !== undefined && sort.length > maxSortFields;\n return {\n filter: schemaToFilter(schema, searchParams),\n sort: isSortClamped ? sort.slice(0, maxSortFields) : sort,\n page: isPageClamped ? maxPage : page,\n perPage,\n clamped: isPageClamped || isSortClamped,\n };\n}\n\n/** searchParams → to'liq, yuborishga tayyor {@link ListPayload} (core default limits; clamped like {@link readListParams}). */\nexport function searchParamsToPayload(schema: ListSchema, searchParams: URLSearchParams, url?: UrlConfig): UrlListPayload {\n const params = readListParams(schema, searchParams, url);\n return withClamped(buildListParams(params), params.clamped);\n}\n\n/* ------------------------- URL yozish (immutable) ------------------------- */\n/* Har biri YANGI URLSearchParams qaytaradi; filter/sort o'zgarsa `page` reset. */\n\nfunction clone(searchParams: URLSearchParams): URLSearchParams {\n return new URLSearchParams(searchParams);\n}\n\n/** Filter param'ini o'rnatadi/o'chiradi va `page`ni reset qiladi. */\nexport function setParam(searchParams: URLSearchParams, name: string, value: string | null | undefined, url: UrlConfig = {}): URLSearchParams {\n const cfg = { ...URL_DEFAULTS, ...url };\n const next = clone(searchParams);\n next.delete(cfg.pageParam);\n if (value === null || value === undefined || value === \"\") next.delete(name);\n else next.set(name, value);\n return next;\n}\n\n/** Sahifani o'rnatadi (`page` reset qilinmaydi). */\nexport function setPage(searchParams: URLSearchParams, page: number, url: UrlConfig = {}): URLSearchParams {\n const cfg = { ...URL_DEFAULTS, ...url };\n const next = clone(searchParams);\n next.set(cfg.pageParam, String(page));\n return next;\n}\n\n/** Sahifa o'lchamini o'rnatadi va `page`ni reset qiladi. */\nexport function setSize(searchParams: URLSearchParams, size: number, url: UrlConfig = {}): URLSearchParams {\n const cfg = { ...URL_DEFAULTS, ...url };\n const next = clone(searchParams);\n next.delete(cfg.pageParam);\n next.set(cfg.sizeParam, String(size));\n return next;\n}\n\n/** Sortni o'rnatadi (`[{ key, direction }, …]`) va `page`ni reset qiladi. */\nexport function setSort(searchParams: URLSearchParams, sort: SortInput, url: UrlConfig = {}): URLSearchParams {\n const cfg = { ...URL_DEFAULTS, ...url };\n const next = clone(searchParams);\n next.delete(cfg.pageParam);\n const encoded = encodeSort(sort);\n if (encoded) next.set(cfg.sortParam, encoded);\n else next.delete(cfg.sortParam);\n return next;\n}\n\n/** Barcha list param'larini tozalaydi (ixtiyoriy saqlanadigan kalitlar bilan). */\nexport function resetParams(searchParams: URLSearchParams, keep: string[] = []): URLSearchParams {\n const next = new URLSearchParams();\n for (const key of keep) {\n const value = searchParams.get(key);\n if (value !== null) next.set(key, value);\n }\n return next;\n}\n","import { useCallback, useMemo } from \"react\";\n\nimport type { AdapterName, WithFor } from \"../adapter\";\nimport type { Resource } from \"../registry\";\nimport type { ListSchema } from \"../schema\";\nimport type { ListPayload, SortInput } from \"../types\";\nimport type { UrlConfig } from \"../url\";\nimport { resetParams, setPage, setParam, setSize, setSort } from \"../url\";\n\nexport interface UseListParamsBaseOptions<A extends AdapterName> {\n /** URL↔filter schema ({@link defineListSchema} / `resource.schema`). Memoize it. */\n schema: ListSchema;\n /** Current URL search params (from your router). */\n searchParams: URLSearchParams;\n /** Apply new search params (`(next) => void`). */\n setSearchParams: (next: URLSearchParams) => void;\n /** URL param names (page/size/sortType) — optional. Memoize it. */\n url?: UrlConfig;\n /** Fixed relations to load on every request (not URL-driven). */\n with?: WithFor<A>;\n}\n\nexport interface UseListParamsResult {\n /** Ready-to-send list payload (built from the URL via the resource's defaults). */\n params: ListPayload;\n /**\n * The URL's page or sort was past the limits and was clamped (`?page=9999` → the last\n * allowed page). The app may rewrite the URL (`setPage(params.page)`).\n */\n clamped: boolean;\n searchParams: URLSearchParams;\n /** Set/clear a filter param (resets `page`). */\n setParam: (name: string, value: string | null | undefined) => void;\n setPage: (page: number) => void;\n setSize: (size: number) => void;\n setSort: (sort: SortInput) => void;\n reset: (keep?: string[]) => void;\n}\n\n/**\n * Router-agnostic list URL-state hook. Reads the URL via the schema (a page or sort\n * past the limits is clamped — `clamped`, never a render error), builds a\n * ready-to-send payload through the `resource` (registry defaults + adapter), and\n * returns URL setters (filter/sort/size changes reset `page`). Bring your own\n * `searchParams`/`setSearchParams` (Next.js, TanStack Router, …).\n */\nexport function useListParamsBase<T, A extends AdapterName>(resource: Resource<T, A>, options: UseListParamsBaseOptions<A>): UseListParamsResult {\n const { schema, searchParams, setSearchParams, url, with: withRel } = options;\n\n const params = useMemo(() => resource.fromSearchParams(searchParams, { schema, url, with: withRel }), [resource, schema, searchParams, url, withRel]);\n\n return {\n params,\n clamped: params.clamped === true,\n searchParams,\n setParam: useCallback((name, value) => setSearchParams(setParam(searchParams, name, value, url)), [searchParams, setSearchParams, url]),\n setPage: useCallback((page: number) => setSearchParams(setPage(searchParams, page, url)), [searchParams, setSearchParams, url]),\n setSize: useCallback((size: number) => setSearchParams(setSize(searchParams, size, url)), [searchParams, setSearchParams, url]),\n setSort: useCallback((sort: SortInput) => setSearchParams(setSort(searchParams, sort, url)), [searchParams, setSearchParams, url]),\n reset: useCallback((keep?: string[]) => setSearchParams(resetParams(searchParams, keep)), [searchParams, setSearchParams]),\n };\n}\n","import { useSearchParams } from \"react-router\";\n\nimport type { AdapterName, WithFor } from \"../adapter\";\nimport type { Resource } from \"../registry\";\nimport type { ListSchema } from \"../schema\";\nimport type { UrlConfig } from \"../url\";\nimport { useListParamsBase, type UseListParamsResult } from \"./use-list-params-base\";\n\nexport interface UseListParamsOptions<A extends AdapterName> {\n /** URL↔filter schema ({@link defineListSchema} / `resource.schema`). Memoize it. */\n schema: ListSchema;\n /** URL param names (page/size/sortType) — optional. Memoize it. */\n url?: UrlConfig;\n /** Fixed relations to load on every request (not URL-driven). */\n with?: WithFor<A>;\n}\n\n/**\n * List URL-state hook — turnkey with **react-router** v7 / v8 (uses `useSearchParams`\n * internally). Reads filters/sort/page from the URL via the schema, builds a\n * ready-to-send payload through the `resource`, and returns URL setters (which\n * reset `page` on filter/sort/size change). Does **not** fetch — pass `params`\n * to your own axios / TanStack Query.\n *\n * For other routers (Next.js, TanStack Router), use {@link useListParamsBase} and\n * supply `searchParams`/`setSearchParams` yourself.\n *\n * @example\n * ```tsx\n * const users = qk.resource<IUser>(\"users\");\n * const { params, setPage, setSort } = useListParams(users, { schema: usersSchema });\n * const { data } = useQuery({ queryKey: users.keys.list(params), queryFn: () => post(\"/users/list\", params) });\n * ```\n */\nexport function useListParams<T, A extends AdapterName>(resource: Resource<T, A>, options: UseListParamsOptions<A>): UseListParamsResult {\n const [searchParams, setSearchParams] = useSearchParams();\n return useListParamsBase(resource, {\n ...options,\n searchParams,\n setSearchParams: next => setSearchParams(next),\n });\n}\n"]}
@@ -46,6 +46,8 @@ interface Params<T = Record<string, unknown>> {
46
46
  interface ListParams<T = Record<string, unknown>> extends Params<T> {
47
47
  page?: number;
48
48
  perPage?: number;
49
+ /** `false` — the server runs no count; the meta totals come back `null` (sent only when given). */
50
+ withTotal?: boolean;
49
51
  }
50
52
  /** Infinite-scroll (limit + offset) paginatsiya kirishi. */
51
53
  interface InfiniteParams<T = Record<string, unknown>> extends Params<T> {
@@ -66,10 +68,14 @@ interface QueryPayload {
66
68
  sort: Sort;
67
69
  columns: Record<string, boolean>;
68
70
  with: Record<string, unknown>;
71
+ /** Soft-delete qatorlarni ham qo'shish — faqat kirishда berilса payloadga qo'shiladi. */
72
+ withDeleted?: boolean;
69
73
  }
70
74
  interface ListPayload extends QueryPayload {
71
75
  page: number;
72
76
  perPage: number;
77
+ /** Only present when given in the input. */
78
+ withTotal?: boolean;
73
79
  }
74
80
  interface InfinitePayload extends QueryPayload {
75
81
  limit: number;
@@ -85,6 +91,7 @@ interface CursorPayload {
85
91
  order: SortDirection;
86
92
  direction: "forward" | "backward";
87
93
  cursorKey?: string;
94
+ withDeleted?: boolean;
88
95
  }
89
96
  /**
90
97
  * Xom (server, snake_case) meta tiplari — `@querykitjs/core`dan (adapter shu
@@ -96,8 +103,10 @@ type RawInfiniteMeta = Partial<Core.InfiniteMeta>;
96
103
  type RawCursorMeta = Partial<Core.CursorMeta>;
97
104
  /** Offset (list) — normalizatsiya qilingan meta (camelCase). */
98
105
  interface Meta {
99
- totalPages: number;
100
- totalCount: number;
106
+ /** `null` when the request sent `withTotal: false` (no count ran). */
107
+ totalPages: number | null;
108
+ /** `null` when the request sent `withTotal: false` (no count ran). */
109
+ totalCount: number | null;
101
110
  currentPage: number;
102
111
  perPage: number;
103
112
  hasNext: boolean;
@@ -259,10 +268,37 @@ declare function encodeSort(sort: SortInput): string;
259
268
  * present (`{ and: [orGroup, …conditions] }`).
260
269
  */
261
270
  declare function schemaToFilter(schema: ListSchema, searchParams: URLSearchParams): Filter;
262
- /** searchParams → normalizatsiyalanmagan {@link ListParams} (filter/sort/page/perPage). */
263
- declare function readListParams(schema: ListSchema, searchParams: URLSearchParams, url?: UrlConfig): ListParams;
264
- /** searchParams → to'liq, yuborishga tayyor {@link ListPayload}. */
265
- declare function searchParamsToPayload(schema: ListSchema, searchParams: URLSearchParams, url?: UrlConfig): ListPayload;
271
+ /**
272
+ * The bounds the URL readers clamp to — the builder's own (`createRegistry`
273
+ * `defaults`); an absent one is the core default.
274
+ */
275
+ interface UrlLimits {
276
+ perPage?: number;
277
+ maxPerPage?: number;
278
+ maxOffset?: number;
279
+ maxSortFields?: number;
280
+ }
281
+ /** {@link ListParams} read from a URL. `clamped` — the URL's page or sort was cut to the limits. */
282
+ type UrlListParams = ListParams & {
283
+ clamped: boolean;
284
+ };
285
+ /**
286
+ * A list payload built from a URL. `clamped` is non-enumerable: readable in code,
287
+ * never serialized, so the wire payload is unchanged.
288
+ */
289
+ type UrlListPayload = ListPayload & {
290
+ readonly clamped?: boolean;
291
+ };
292
+ /**
293
+ * searchParams → normalizatsiyalanmagan {@link ListParams} (filter/sort/page/perPage).
294
+ * The URL is user input: a page past `maxOffset` becomes the last allowed page and a
295
+ * sort past `maxSortFields` keeps its first fields, with `clamped: true` — so a
296
+ * hand-edited `?page=9999` never throws during render. The builders still throw
297
+ * for a direct call (task 008).
298
+ */
299
+ declare function readListParams(schema: ListSchema, searchParams: URLSearchParams, url?: UrlConfig, limits?: UrlLimits): UrlListParams;
300
+ /** searchParams → to'liq, yuborishga tayyor {@link ListPayload} (core default limits; clamped like {@link readListParams}). */
301
+ declare function searchParamsToPayload(schema: ListSchema, searchParams: URLSearchParams, url?: UrlConfig): UrlListPayload;
266
302
  /** Filter param'ini o'rnatadi/o'chiradi va `page`ni reset qiladi. */
267
303
  declare function setParam(searchParams: URLSearchParams, name: string, value: string | null | undefined, url?: UrlConfig): URLSearchParams;
268
304
  /** Sahifani o'rnatadi (`page` reset qilinmaydi). */
@@ -328,6 +364,11 @@ type ResourceCursorParams<T, A extends AdapterName> = Omit<CursorParams<T>, "wit
328
364
  type ResourceParams<T, A extends AdapterName> = Omit<Params<T>, "with"> & {
329
365
  with?: WithFor<A>;
330
366
  };
367
+ /** `resource.search` options. */
368
+ interface SearchOptions {
369
+ /** The string operator per field (default `"contains"`). */
370
+ operation?: "contains" | "startsWith" | "endsWith";
371
+ }
331
372
  interface RawResponse<T, M> {
332
373
  data?: T[];
333
374
  meta?: M | null;
@@ -362,16 +403,22 @@ interface Resource<T, A extends AdapterName> {
362
403
  params(input?: ResourceParams<T, A>): QueryPayload;
363
404
  /** Filter builder typed to `T`'s fields. */
364
405
  f: ReturnType<typeof createFilters<T>>;
365
- /** Search preset — OR of `contains` across `fields` (e.g. one input → many columns). */
366
- search(term: string, fields: FieldKey<T>[]): Filter<T>;
406
+ /**
407
+ * Search preset — OR of `operation` (default `contains`) across `fields` (one input → many columns).
408
+ * Each field must be `filterable` with that operator on the server.
409
+ */
410
+ search(term: string, fields: FieldKey<T>[], options?: SearchOptions): Filter<T>;
367
411
  /** Declare a URL↔filter schema (identity helper). */
368
412
  schema<S extends ListSchema>(s: S): S;
369
- /** Build a list payload from URL search params (SSR — the non-hook `useListParams`). */
413
+ /**
414
+ * Build a list payload from URL search params (SSR — the non-hook `useListParams`).
415
+ * A page or sort past the registry limits is clamped (`clamped`, non-enumerable — never sent).
416
+ */
370
417
  fromSearchParams(searchParams: URLSearchParams, options: {
371
418
  schema: ListSchema;
372
419
  url?: UrlConfig;
373
420
  with?: WithFor<A>;
374
- }): ListPayload;
421
+ }): UrlListPayload;
375
422
  /** Stable query keys for TanStack/SWR caches. */
376
423
  keys: ResourceKeys;
377
424
  /** Map a list response's meta to camelCase (`data` typed to `T`). */
@@ -384,6 +431,18 @@ interface RegistryDefaults {
384
431
  perPage?: number;
385
432
  /** `infinite` + `cursor` page size. */
386
433
  limit?: number;
434
+ /**
435
+ * `perPage` upper bound (core `DEFAULT_MAX_PER_PAGE` = 200) — the same constant
436
+ * the zod factories and both backend repositories use, so the frontend never
437
+ * sends a request the server would reject.
438
+ */
439
+ maxPerPage?: number;
440
+ /** `limit` upper bound (core `DEFAULT_MAX_LIMIT` = 200). */
441
+ maxLimit?: number;
442
+ /** Page depth bound (core `DEFAULT_MAX_OFFSET` = 10 000) — a deeper `list`/`infinite` request throws. */
443
+ maxOffset?: number;
444
+ /** Sort field count bound (core `DEFAULT_MAX_SORT_FIELDS` = 3) — more fields throw. */
445
+ maxSortFields?: number;
387
446
  /** `list`/`infinite` sort — `[{key,direction}]` or `["-createdAt", "id"]` shorthand. */
388
447
  sort?: SortInput;
389
448
  /** Cursor-only defaults. */
@@ -391,9 +450,16 @@ interface RegistryDefaults {
391
450
  order?: SortDirection;
392
451
  };
393
452
  }
453
+ /** The backend registry's `timestamps` field names, so the web default sort names the same field. */
454
+ interface RegistryTimestamps {
455
+ /** The createdAt field (default `"createdAt"`) — the default `list`/`infinite` sort is `[{ key: createdAt, direction: "desc" }]`. */
456
+ createdAt?: string;
457
+ }
394
458
  interface RegistryConfig<A extends AdapterName> {
395
459
  adapter: A;
396
460
  defaults?: RegistryDefaults;
461
+ /** Same as the backend registry's `timestamps`; `defaults.sort` wins over it. */
462
+ timestamps?: RegistryTimestamps;
397
463
  /** Drop empty filter values built from form inputs (default `true`). */
398
464
  pruneEmpty?: boolean;
399
465
  }
@@ -420,4 +486,5 @@ interface Registry<A extends AdapterName> {
420
486
  */
421
487
  declare function createRegistry<A extends AdapterName>(config: RegistryConfig<A>): Registry<A>;
422
488
 
423
- export { readListParams as $, type AdapterName as A, type ResourceCursorParams as B, type CursorParams as C, type DrizzleWith as D, type ResourceInfiniteParams as E, type Filter as F, type ResourceKeys as G, type ResourceListParams as H, type InfiniteParams as I, type ResourceParams as J, type SortDirection as K, type ListParams as L, type Meta as M, type NotGroup as N, type OrGroup as O, type Params as P, type QueryPayload as Q, type RawCursorMeta as R, type Sort as S, type SortItem as T, type UrlConfig as U, createFilters as V, type WithFor as W, createRegistry as X, decodeSort as Y, defineListSchema as Z, encodeSort as _, type CursorPayload as a, resetParams as a0, schemaToFilter as a1, searchParamsToPayload as a2, setPage as a3, setParam as a4, setSize as a5, setSort as a6, type InfinitePayload as b, type ListPayload as c, type FieldCondition as d, type FilterNode as e, type SortInput as f, type AndGroup as g, type CursorMeta as h, type CursorResult as i, type FieldDescriptor as j, type FieldKey as k, type FilterOperator as l, type FilterScalar as m, type FilterValue as n, type InfiniteMeta as o, type InfiniteResult as p, type ListResult as q, type ListSchema as r, type MongoosePopulate as s, type PrismaInclude as t, type RawInfiniteMeta as u, type RawMeta as v, type Registry as w, type RegistryConfig as x, type RegistryDefaults as y, type Resource as z };
489
+ export { createFilters as $, createRegistry as a0, decodeSort as a1, defineListSchema as a2, encodeSort as a3, readListParams as a4, resetParams as a5, schemaToFilter as a6, searchParamsToPayload as a7, setPage as a8, setParam as a9, setSize as aa, setSort as ab };
490
+ export type { AdapterName as A, Resource as B, CursorParams as C, DrizzleWith as D, ResourceCursorParams as E, Filter as F, ResourceInfiniteParams as G, ResourceKeys as H, InfiniteParams as I, ResourceListParams as J, ResourceParams as K, ListParams as L, Meta as M, NotGroup as N, OrGroup as O, Params as P, QueryPayload as Q, RawCursorMeta as R, Sort as S, SearchOptions as T, SortDirection as U, SortItem as V, UrlConfig as W, UrlLimits as X, UrlListParams as Y, UrlListPayload as Z, WithFor as _, CursorPayload as a, InfinitePayload as b, ListPayload as c, FieldCondition as d, FilterNode as e, SortInput as f, AndGroup as g, CursorMeta as h, CursorResult as i, FieldDescriptor as j, FieldKey as k, FilterOperator as l, FilterScalar as m, FilterValue as n, InfiniteMeta as o, InfiniteResult as p, ListResult as q, ListSchema as r, MongoosePopulate as s, PrismaInclude as t, RawInfiniteMeta as u, RawMeta as v, Registry as w, RegistryConfig as x, RegistryDefaults as y, RegistryTimestamps as z };
@@ -46,6 +46,8 @@ interface Params<T = Record<string, unknown>> {
46
46
  interface ListParams<T = Record<string, unknown>> extends Params<T> {
47
47
  page?: number;
48
48
  perPage?: number;
49
+ /** `false` — the server runs no count; the meta totals come back `null` (sent only when given). */
50
+ withTotal?: boolean;
49
51
  }
50
52
  /** Infinite-scroll (limit + offset) paginatsiya kirishi. */
51
53
  interface InfiniteParams<T = Record<string, unknown>> extends Params<T> {
@@ -66,10 +68,14 @@ interface QueryPayload {
66
68
  sort: Sort;
67
69
  columns: Record<string, boolean>;
68
70
  with: Record<string, unknown>;
71
+ /** Soft-delete qatorlarni ham qo'shish — faqat kirishда berilса payloadga qo'shiladi. */
72
+ withDeleted?: boolean;
69
73
  }
70
74
  interface ListPayload extends QueryPayload {
71
75
  page: number;
72
76
  perPage: number;
77
+ /** Only present when given in the input. */
78
+ withTotal?: boolean;
73
79
  }
74
80
  interface InfinitePayload extends QueryPayload {
75
81
  limit: number;
@@ -85,6 +91,7 @@ interface CursorPayload {
85
91
  order: SortDirection;
86
92
  direction: "forward" | "backward";
87
93
  cursorKey?: string;
94
+ withDeleted?: boolean;
88
95
  }
89
96
  /**
90
97
  * Xom (server, snake_case) meta tiplari — `@querykitjs/core`dan (adapter shu
@@ -96,8 +103,10 @@ type RawInfiniteMeta = Partial<Core.InfiniteMeta>;
96
103
  type RawCursorMeta = Partial<Core.CursorMeta>;
97
104
  /** Offset (list) — normalizatsiya qilingan meta (camelCase). */
98
105
  interface Meta {
99
- totalPages: number;
100
- totalCount: number;
106
+ /** `null` when the request sent `withTotal: false` (no count ran). */
107
+ totalPages: number | null;
108
+ /** `null` when the request sent `withTotal: false` (no count ran). */
109
+ totalCount: number | null;
101
110
  currentPage: number;
102
111
  perPage: number;
103
112
  hasNext: boolean;
@@ -259,10 +268,37 @@ declare function encodeSort(sort: SortInput): string;
259
268
  * present (`{ and: [orGroup, …conditions] }`).
260
269
  */
261
270
  declare function schemaToFilter(schema: ListSchema, searchParams: URLSearchParams): Filter;
262
- /** searchParams → normalizatsiyalanmagan {@link ListParams} (filter/sort/page/perPage). */
263
- declare function readListParams(schema: ListSchema, searchParams: URLSearchParams, url?: UrlConfig): ListParams;
264
- /** searchParams → to'liq, yuborishga tayyor {@link ListPayload}. */
265
- declare function searchParamsToPayload(schema: ListSchema, searchParams: URLSearchParams, url?: UrlConfig): ListPayload;
271
+ /**
272
+ * The bounds the URL readers clamp to — the builder's own (`createRegistry`
273
+ * `defaults`); an absent one is the core default.
274
+ */
275
+ interface UrlLimits {
276
+ perPage?: number;
277
+ maxPerPage?: number;
278
+ maxOffset?: number;
279
+ maxSortFields?: number;
280
+ }
281
+ /** {@link ListParams} read from a URL. `clamped` — the URL's page or sort was cut to the limits. */
282
+ type UrlListParams = ListParams & {
283
+ clamped: boolean;
284
+ };
285
+ /**
286
+ * A list payload built from a URL. `clamped` is non-enumerable: readable in code,
287
+ * never serialized, so the wire payload is unchanged.
288
+ */
289
+ type UrlListPayload = ListPayload & {
290
+ readonly clamped?: boolean;
291
+ };
292
+ /**
293
+ * searchParams → normalizatsiyalanmagan {@link ListParams} (filter/sort/page/perPage).
294
+ * The URL is user input: a page past `maxOffset` becomes the last allowed page and a
295
+ * sort past `maxSortFields` keeps its first fields, with `clamped: true` — so a
296
+ * hand-edited `?page=9999` never throws during render. The builders still throw
297
+ * for a direct call (task 008).
298
+ */
299
+ declare function readListParams(schema: ListSchema, searchParams: URLSearchParams, url?: UrlConfig, limits?: UrlLimits): UrlListParams;
300
+ /** searchParams → to'liq, yuborishga tayyor {@link ListPayload} (core default limits; clamped like {@link readListParams}). */
301
+ declare function searchParamsToPayload(schema: ListSchema, searchParams: URLSearchParams, url?: UrlConfig): UrlListPayload;
266
302
  /** Filter param'ini o'rnatadi/o'chiradi va `page`ni reset qiladi. */
267
303
  declare function setParam(searchParams: URLSearchParams, name: string, value: string | null | undefined, url?: UrlConfig): URLSearchParams;
268
304
  /** Sahifani o'rnatadi (`page` reset qilinmaydi). */
@@ -328,6 +364,11 @@ type ResourceCursorParams<T, A extends AdapterName> = Omit<CursorParams<T>, "wit
328
364
  type ResourceParams<T, A extends AdapterName> = Omit<Params<T>, "with"> & {
329
365
  with?: WithFor<A>;
330
366
  };
367
+ /** `resource.search` options. */
368
+ interface SearchOptions {
369
+ /** The string operator per field (default `"contains"`). */
370
+ operation?: "contains" | "startsWith" | "endsWith";
371
+ }
331
372
  interface RawResponse<T, M> {
332
373
  data?: T[];
333
374
  meta?: M | null;
@@ -362,16 +403,22 @@ interface Resource<T, A extends AdapterName> {
362
403
  params(input?: ResourceParams<T, A>): QueryPayload;
363
404
  /** Filter builder typed to `T`'s fields. */
364
405
  f: ReturnType<typeof createFilters<T>>;
365
- /** Search preset — OR of `contains` across `fields` (e.g. one input → many columns). */
366
- search(term: string, fields: FieldKey<T>[]): Filter<T>;
406
+ /**
407
+ * Search preset — OR of `operation` (default `contains`) across `fields` (one input → many columns).
408
+ * Each field must be `filterable` with that operator on the server.
409
+ */
410
+ search(term: string, fields: FieldKey<T>[], options?: SearchOptions): Filter<T>;
367
411
  /** Declare a URL↔filter schema (identity helper). */
368
412
  schema<S extends ListSchema>(s: S): S;
369
- /** Build a list payload from URL search params (SSR — the non-hook `useListParams`). */
413
+ /**
414
+ * Build a list payload from URL search params (SSR — the non-hook `useListParams`).
415
+ * A page or sort past the registry limits is clamped (`clamped`, non-enumerable — never sent).
416
+ */
370
417
  fromSearchParams(searchParams: URLSearchParams, options: {
371
418
  schema: ListSchema;
372
419
  url?: UrlConfig;
373
420
  with?: WithFor<A>;
374
- }): ListPayload;
421
+ }): UrlListPayload;
375
422
  /** Stable query keys for TanStack/SWR caches. */
376
423
  keys: ResourceKeys;
377
424
  /** Map a list response's meta to camelCase (`data` typed to `T`). */
@@ -384,6 +431,18 @@ interface RegistryDefaults {
384
431
  perPage?: number;
385
432
  /** `infinite` + `cursor` page size. */
386
433
  limit?: number;
434
+ /**
435
+ * `perPage` upper bound (core `DEFAULT_MAX_PER_PAGE` = 200) — the same constant
436
+ * the zod factories and both backend repositories use, so the frontend never
437
+ * sends a request the server would reject.
438
+ */
439
+ maxPerPage?: number;
440
+ /** `limit` upper bound (core `DEFAULT_MAX_LIMIT` = 200). */
441
+ maxLimit?: number;
442
+ /** Page depth bound (core `DEFAULT_MAX_OFFSET` = 10 000) — a deeper `list`/`infinite` request throws. */
443
+ maxOffset?: number;
444
+ /** Sort field count bound (core `DEFAULT_MAX_SORT_FIELDS` = 3) — more fields throw. */
445
+ maxSortFields?: number;
387
446
  /** `list`/`infinite` sort — `[{key,direction}]` or `["-createdAt", "id"]` shorthand. */
388
447
  sort?: SortInput;
389
448
  /** Cursor-only defaults. */
@@ -391,9 +450,16 @@ interface RegistryDefaults {
391
450
  order?: SortDirection;
392
451
  };
393
452
  }
453
+ /** The backend registry's `timestamps` field names, so the web default sort names the same field. */
454
+ interface RegistryTimestamps {
455
+ /** The createdAt field (default `"createdAt"`) — the default `list`/`infinite` sort is `[{ key: createdAt, direction: "desc" }]`. */
456
+ createdAt?: string;
457
+ }
394
458
  interface RegistryConfig<A extends AdapterName> {
395
459
  adapter: A;
396
460
  defaults?: RegistryDefaults;
461
+ /** Same as the backend registry's `timestamps`; `defaults.sort` wins over it. */
462
+ timestamps?: RegistryTimestamps;
397
463
  /** Drop empty filter values built from form inputs (default `true`). */
398
464
  pruneEmpty?: boolean;
399
465
  }
@@ -420,4 +486,5 @@ interface Registry<A extends AdapterName> {
420
486
  */
421
487
  declare function createRegistry<A extends AdapterName>(config: RegistryConfig<A>): Registry<A>;
422
488
 
423
- export { readListParams as $, type AdapterName as A, type ResourceCursorParams as B, type CursorParams as C, type DrizzleWith as D, type ResourceInfiniteParams as E, type Filter as F, type ResourceKeys as G, type ResourceListParams as H, type InfiniteParams as I, type ResourceParams as J, type SortDirection as K, type ListParams as L, type Meta as M, type NotGroup as N, type OrGroup as O, type Params as P, type QueryPayload as Q, type RawCursorMeta as R, type Sort as S, type SortItem as T, type UrlConfig as U, createFilters as V, type WithFor as W, createRegistry as X, decodeSort as Y, defineListSchema as Z, encodeSort as _, type CursorPayload as a, resetParams as a0, schemaToFilter as a1, searchParamsToPayload as a2, setPage as a3, setParam as a4, setSize as a5, setSort as a6, type InfinitePayload as b, type ListPayload as c, type FieldCondition as d, type FilterNode as e, type SortInput as f, type AndGroup as g, type CursorMeta as h, type CursorResult as i, type FieldDescriptor as j, type FieldKey as k, type FilterOperator as l, type FilterScalar as m, type FilterValue as n, type InfiniteMeta as o, type InfiniteResult as p, type ListResult as q, type ListSchema as r, type MongoosePopulate as s, type PrismaInclude as t, type RawInfiniteMeta as u, type RawMeta as v, type Registry as w, type RegistryConfig as x, type RegistryDefaults as y, type Resource as z };
489
+ export { createFilters as $, createRegistry as a0, decodeSort as a1, defineListSchema as a2, encodeSort as a3, readListParams as a4, resetParams as a5, schemaToFilter as a6, searchParamsToPayload as a7, setPage as a8, setParam as a9, setSize as aa, setSort as ab };
490
+ export type { AdapterName as A, Resource as B, CursorParams as C, DrizzleWith as D, ResourceCursorParams as E, Filter as F, ResourceInfiniteParams as G, ResourceKeys as H, InfiniteParams as I, ResourceListParams as J, ResourceParams as K, ListParams as L, Meta as M, NotGroup as N, OrGroup as O, Params as P, QueryPayload as Q, RawCursorMeta as R, Sort as S, SearchOptions as T, SortDirection as U, SortItem as V, UrlConfig as W, UrlLimits as X, UrlListParams as Y, UrlListPayload as Z, WithFor as _, CursorPayload as a, InfinitePayload as b, ListPayload as c, FieldCondition as d, FilterNode as e, SortInput as f, AndGroup as g, CursorMeta as h, CursorResult as i, FieldDescriptor as j, FieldKey as k, FilterOperator as l, FilterScalar as m, FilterValue as n, InfiniteMeta as o, InfiniteResult as p, ListResult as q, ListSchema as r, MongoosePopulate as s, PrismaInclude as t, RawInfiniteMeta as u, RawMeta as v, Registry as w, RegistryConfig as x, RegistryDefaults as y, RegistryTimestamps as z };