zod-paginate 1.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.
package/README.md ADDED
@@ -0,0 +1,427 @@
1
+ # zod-paginate
2
+
3
+ A small utility to **parse and validate pagination + select + sort + filters** from querystring-like objects using **Zod v4**, and to generate a **response validator** that automatically projects your `dataSchema` based on the requested `select`.
4
+
5
+ It is designed for Node.js HTTP stacks where query parameters arrive as strings (or string arrays). It outputs a **typed, normalized** structure you can map to your ORM/query builder.
6
+
7
+ - Supports **LIMIT/OFFSET pagination** (`limit` + `page`).
8
+ - Supports **CURSOR pagination** with cursor coercion based on `cursorProperty` (number / string / ISO date string).
9
+ - Supports **field projection** using `select` (CSV), including wildcard expansion (`*`) when enabled.
10
+ - Supports **sorting** with an allowlist of sortable fields.
11
+ - Supports a **filter DSL** with `$` operators and **nested AND/OR grouping**.
12
+ - Provides a **response validator** (`validatorSchema`) to validate API responses against the projected schema.
13
+
14
+ > This library does **not** bind DB queries automatically.
15
+ > It gives you a safe parsed structure; you decide how to map it to your data layer.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm i zod-paginate
21
+ # or
22
+ pnpm add zod-paginate
23
+ # or
24
+ yarn add zod-paginate
25
+ ```
26
+
27
+ ## Quick start
28
+
29
+ ```ts
30
+ import { z } from "zod";
31
+ import { paginate } from "zod-paginate";
32
+
33
+ const ModelSchema = z.object({
34
+ id: z.number(),
35
+ status: z.string(),
36
+ createdAt: z.date(),
37
+ meta: z.object({
38
+ score: z.number(),
39
+ }),
40
+ });
41
+
42
+ const { queryParamsSchema, validatorSchema } = paginate({
43
+ paginationType: "LIMIT_OFFSET",
44
+ dataSchema: ModelSchema,
45
+
46
+ selectable: ["id", "status", "createdAt", "meta.score"],
47
+ sortable: ["createdAt", "id"],
48
+ filterable: {
49
+ status: { type: "string", ops: ["$eq", "$ilike"] },
50
+ createdAt: { type: "date", ops: ["$btw", "$null", "$eq", "$gt", "$lte"] },
51
+ id: { type: "number", ops: ["$gt", "$in", "$eq"] },
52
+ "meta.score": { type: "number", ops: ["$gte", "$lte"] },
53
+ },
54
+
55
+ defaultSortBy: [{ property: "createdAt", direction: "DESC" }],
56
+ defaultLimit: 20,
57
+ maxLimit: 100,
58
+ defaultSelect: ["*"],
59
+ });
60
+
61
+ // Example querystring-like input
62
+ const parsed = queryParamsSchema.parse({
63
+ limit: "10",
64
+ page: "2",
65
+ sortBy: "createdAt:DESC",
66
+ select: "id,status",
67
+ "filter.status": "$ilike:act",
68
+ });
69
+
70
+ console.log(parsed.pagination);
71
+
72
+ // Build the response validator from the request context
73
+ const responseSchema = validatorSchema(parsed);
74
+ ```
75
+
76
+ ## API
77
+
78
+ ### `paginate(config)`
79
+
80
+ Returns:
81
+
82
+ - `queryParamsSchema`: Zod schema to parse query objects (strings / string arrays).
83
+ - `validatorSchema(parsed?)`: function returning a Zod schema to validate the response payload.
84
+
85
+ ```ts
86
+ export function paginate<TSchema extends DataSchema>(
87
+ config: QueryConfigFromSchema<TSchema>,
88
+ ): {
89
+ queryParamsSchema: z.ZodType<PaginationQueryParams<TSchema>>;
90
+ validatorSchema: (parsed?: PaginationQueryParams<TSchema>) => z.ZodType;
91
+ }
92
+ ```
93
+
94
+ ## Configuration (`paginate({...})`)
95
+
96
+ | Option | Type | Description |
97
+ |---|---:|---|
98
+ | `paginationType` | `"LIMIT_OFFSET"` \| `"CURSOR"` | Select pagination mode. |
99
+ | `dataSchema` | `z.ZodObject` | Zod schema representing one **data item** returned by your API (used for projection + cursor inference). |
100
+ | `selectable?` | `string[]` (typed paths) | Allowlist of selectable fields (dot paths supported). Enables `select`. |
101
+ | `sortable?` | `string[]` (typed paths) | Allowlist of sortable fields. Enables `sortBy`. |
102
+ | `filterable?` | object | Allowlist of filterable fields and allowed operators + field type. |
103
+ | `defaultSortBy?` | `{ property, direction }[]` | Default sort if `sortBy` missing/empty. |
104
+ | `defaultLimit?` | `number` | Default limit if `limit` missing. |
105
+ | `maxLimit?` | `number` | Rejects `limit` values above this. |
106
+ | `defaultSelect?` | `("*" \| field)[]` | Default select if `select` missing. `["*"]` expands to `selectable`. |
107
+ | `cursorProperty` | (CURSOR only) typed path | The field used for cursor paging. Cursor type is inferred from `dataSchema` at that path and the query input cursor is coerced accordingly. |
108
+
109
+ ## Query input shape
110
+
111
+ `queryParamsSchema` accepts any record-like input:
112
+
113
+ ```ts
114
+ Record<string, unknown>
115
+ ```
116
+
117
+ Typical querystring parsers produce values like:
118
+
119
+ - `"10"` (string)
120
+ - `["a", "b"]` (repeated query params)
121
+ - everything else is ignored / treated as undefined
122
+
123
+ ## Query parameters
124
+
125
+ ### `limit`
126
+
127
+ - Input: string numeric (e.g. `"10"`)
128
+ - Output: number
129
+ - Rules
130
+ - Must be a numeric string
131
+ - Must be `<= maxLimit` if configured
132
+ - Falls back to `defaultLimit` when missing
133
+
134
+ ### `page` (LIMIT_OFFSET only)
135
+
136
+ - Input: string numeric (e.g. `"2"`)
137
+ - Output: number
138
+ - Rules
139
+ - Only valid when `paginationType: "LIMIT_OFFSET"`
140
+ - Forbidden in CURSOR mode
141
+
142
+ ### `cursor` (CURSOR only)
143
+
144
+ - Input: string (querystring input is always string)
145
+ - Output: `number | string` (coerced)
146
+ - Rules
147
+ - Only valid when `paginationType: "CURSOR"`
148
+ - Forbidden in LIMIT_OFFSET mode
149
+ - If provided, it is coerced based on the Zod type of `cursorProperty` in `dataSchema`:
150
+ - `z.number()` field → `"123"` becomes `123` (integer-only)
151
+ - `z.string()` field → `"abc"` stays `"abc"`
152
+ - `z.date()` field → must be ISO date or ISO datetime, stays a string (`"2022-01-01"` or `"2022-01-01T12:00:00Z"`)
153
+
154
+ ### `sortBy`
155
+
156
+ - Input: string or string[]
157
+ - Output: `[{ property, direction }]`
158
+ - Rules
159
+ - Requires `sortable` in config
160
+ - Format: `field:ASC` or `field:DESC`
161
+ - Empty items are ignored
162
+ - If missing (or becomes empty after cleanup), falls back to `defaultSortBy` if configured
163
+ - Properties are matched against the allowlist (unknown fields are dropped)
164
+
165
+ ### `select`
166
+
167
+ - Input: CSV string
168
+ - Output: string[] (typed paths)
169
+ - Rules
170
+ - Requires `selectable` in config
171
+ - CSV is split by `,`, trimmed, empty items removed
172
+ - `*` expands to the configured `selectable` allowlist
173
+ - If missing, falls back to `defaultSelect` if configured
174
+ - `select=` (empty) is rejected
175
+ - Unknown fields are rejected at parse-time (strict allowlist)
176
+
177
+ ## Filters
178
+
179
+ Filters are passed as query keys with this pattern:
180
+
181
+ ```txt
182
+ filter.<field>=<dsl>
183
+ ```
184
+
185
+ Where `<field>` is a dot-path field (example: `meta.score`).
186
+
187
+ You configure which fields are filterable and which operators are allowed via `filterable`.
188
+
189
+ ### Operators
190
+
191
+ | Operator | Meaning | Value format |
192
+ |---|---|---|
193
+ | `$eq` | equals | number / string / ISO date depending on field type |
194
+ | `$null` | is null | no value |
195
+ | `$in` | in list | `a,b,c` (comma-separated) |
196
+ | `$contains` | contains values | `a,b,c` (comma-separated) |
197
+ | `$gt` `$gte` `$lt` `$lte` | comparisons | number or ISO date |
198
+ | `$btw` | between | `a,b` where both are numbers OR both are ISO dates |
199
+ | `$ilike` | case-insensitive contains (string) | string |
200
+ | `$sw` | starts with (string) | string |
201
+
202
+ Runtime validation enforces:
203
+
204
+ 1) field allowlist (`filterable`)
205
+ 2) operator allowlist per field (`ops`)
206
+ 3) value type compatibility (number vs date vs string)
207
+
208
+ ### Default operator: `$eq`
209
+
210
+ If the filter does **not** start with `$`, it is interpreted as `$eq:<value>`.
211
+
212
+ ### Negation: `$not`
213
+
214
+ Prefix any operator with `$not:` to negate the condition.
215
+
216
+ Examples:
217
+
218
+ ```txt
219
+ filter.createdAt=$not:$null
220
+ filter.status=$not:$eq:active
221
+ ```
222
+
223
+ ### Multiple conditions for the same field
224
+
225
+ Use repeated query params:
226
+
227
+ ```txt
228
+ filter.id=$gt:10&filter.id=$lt:100
229
+ ```
230
+
231
+ Or in object form:
232
+
233
+ ```ts
234
+ {
235
+ "filter.id": ["$gt:10", "$lt:100"]
236
+ }
237
+ ```
238
+
239
+ ## Groups
240
+
241
+ Groups let you build nested AND/OR boolean logic.
242
+
243
+ There are two layers:
244
+
245
+ 1) Combine multiple conditions inside the same group
246
+ 2) Build a group tree (attach groups as children of other groups)
247
+
248
+ ### Put a condition into a group: `$g:<id>`
249
+
250
+ Prefix any filter DSL with:
251
+
252
+ ```txt
253
+ $g:<groupId>:
254
+ ```
255
+
256
+ ### Combine conditions inside a group: `$and` / `$or`
257
+
258
+ Within a group, the **first** condition cannot have `$and`/`$or`. All following conditions may be prefixed with `$and` or `$or`.
259
+
260
+ ### Group tree definitions: `group.<id>.*`
261
+
262
+ To nest groups, define these query keys:
263
+
264
+ - `group.<id>.parent` — parent group id (integer string)
265
+ - `group.<id>.join` — how this group is joined to its parent (`$and` or `$or`)
266
+ - `group.<id>.op` — default join used when combining this group's children (optional)
267
+
268
+ Rules:
269
+
270
+ - Root group id is always `"0"`.
271
+ - `group.0.parent` and `group.0.join` are forbidden.
272
+ - Cycles are rejected.
273
+ - Child groups are resolved in numeric order (deterministic).
274
+
275
+ ## Validating responses with `validatorSchema()`
276
+
277
+ `validatorSchema(parsed)` returns a Zod schema you can use to validate your API response.
278
+
279
+ What it does:
280
+
281
+ - Uses the effective `select` (explicit `select`, else `defaultSelect`, else full schema) to project the item schema.
282
+ - Validates cursor type (CURSOR mode) based on `cursorProperty`.
283
+ - Enforces mode-specific pagination metadata shape.
284
+
285
+ ### What `validatorSchema(parsed)` expects
286
+
287
+ **LIMIT/OFFSET mode**:
288
+
289
+ ```ts
290
+ {
291
+ data: Array<ProjectedItem>,
292
+ pagination: {
293
+ itemsPerPage: number,
294
+ totalItems: number,
295
+ currentPage: number,
296
+ totalPages: number,
297
+ sortBy?: Array<{ property: string, direction: "ASC" | "DESC" }>,
298
+ filter?: WhereNode
299
+ }
300
+ }
301
+ ```
302
+
303
+ **CURSOR mode**:
304
+
305
+ ```ts
306
+ {
307
+ data: Array<ProjectedItem>,
308
+ pagination: {
309
+ itemsPerPage: number,
310
+ cursor: number | string | Date,
311
+ sortBy?: Array<{ property: string, direction: "ASC" | "DESC" }>,
312
+ filter?: WhereNode
313
+ }
314
+ }
315
+ ```
316
+
317
+ Notes:
318
+
319
+ - `ProjectedItem` is computed from `dataSchema` + the effective `select`.
320
+ - If `cursorProperty` points to a `z.number()` field, `pagination.cursor` must be a number.
321
+ - If `cursorProperty` points to a `z.string()` field, `pagination.cursor` must be a string.
322
+ - If `cursorProperty` points to a `z.date()` field, this library accepts an ISO string or a `Date` (depending on implementation).
323
+
324
+ You can call `validatorSchema()` without arguments to build a validator based on defaults (`defaultSelect`, `cursorProperty`, etc.).
325
+
326
+ ## End-to-end examples
327
+
328
+ ### Example 1 — LIMIT/OFFSET
329
+
330
+ HTTP query:
331
+
332
+ ```txt
333
+ ?limit=20&page=1&select=id,status,createdAt&sortBy=createdAt:DESC&filter.status=$ilike:act&filter.id=$gt:10
334
+ ```
335
+
336
+ Parsing:
337
+
338
+ ```ts
339
+ const parsed = queryParamsSchema.parse({
340
+ limit: "20",
341
+ page: "1",
342
+ select: "id,status,createdAt",
343
+ sortBy: "createdAt:DESC",
344
+ "filter.status": "$ilike:act",
345
+ "filter.id": "$gt:10",
346
+ });
347
+
348
+ // parsed.pagination
349
+ // {
350
+ // type: "LIMIT_OFFSET",
351
+ // limit: 20,
352
+ // page: 1,
353
+ // select: ["id", "status", "createdAt"],
354
+ // sortBy: [{ property: "createdAt", direction: "DESC" }],
355
+ // filters: { type: "and", items: [...] } // WhereNode AST
356
+ // }
357
+ ```
358
+
359
+ ### Example 2 — CURSOR + coercion
360
+
361
+ Config:
362
+
363
+ ```ts
364
+ const { queryParamsSchema } = paginate({
365
+ paginationType: "CURSOR",
366
+ dataSchema: ModelSchema,
367
+ cursorProperty: "id", // id is z.number()
368
+ selectable: ["id", "status", "createdAt"],
369
+ defaultSelect: ["id", "createdAt"],
370
+ });
371
+ ```
372
+
373
+ Parsing:
374
+
375
+ ```ts
376
+ const parsed = queryParamsSchema.parse({ cursor: "123", limit: "10" });
377
+
378
+ // parsed.pagination
379
+ // {
380
+ // type: "CURSOR",
381
+ // limit: 10,
382
+ // cursor: 123, // <- coerced from "123" because cursorProperty is a number
383
+ // cursorProperty: "id",
384
+ // select: ["id", "createdAt"],
385
+ // filters: { type: "and", items: [] }
386
+ // }
387
+ ```
388
+
389
+ ### Example 3 — groups
390
+
391
+ Goal: `(status == active OR status == postponed) AND (id > 10)`
392
+
393
+ ```ts
394
+ const parsed = queryParamsSchema.parse({
395
+ "filter.status": ["$g:1:$eq:active", "$g:1:$or:$eq:postponed"],
396
+ "filter.id": "$g:2:$gt:10",
397
+
398
+ "group.1.parent": "0",
399
+ "group.2.parent": "0",
400
+ "group.2.join": "$and",
401
+ });
402
+
403
+ // parsed.pagination.filters
404
+ // {
405
+ // type: "and",
406
+ // items: [
407
+ // { type: "or", items: [ ...status filters... ] },
408
+ // { type: "filter", field: "id", condition: { op: "$gt", value: 10, ... } }
409
+ // ]
410
+ // }
411
+ ```
412
+
413
+ ### Example 4 — validating your response
414
+
415
+ ```ts
416
+ const parsed = queryParamsSchema.parse({ select: "id,status", limit: "10", page: "1" });
417
+ const responseSchema = validatorSchema(parsed);
418
+
419
+ // responseSchema expects:
420
+ // - data items shaped like { id: number, status: string }
421
+ // - pagination metadata for LIMIT/OFFSET
422
+
423
+ responseSchema.parse({
424
+ data: [{ id: 1, status: "active" }],
425
+ pagination: { itemsPerPage: 10, totalItems: 1, currentPage: 1, totalPages: 1 },
426
+ });
427
+ ```
package/dist/main.d.ts ADDED
@@ -0,0 +1,211 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Primitive types that we consider as leaves in the Path type. Arrays are also considered leaves, since we don't want to generate paths like "arrayField.0.someProp".
4
+ */
5
+ type Primitive = string | number | boolean | bigint | symbol | null | undefined | Date;
6
+ /**
7
+ * Join two path segments K and P with a dot, if both are strings. Otherwise, return never.
8
+ */
9
+ type Join<K, P> = K extends string ? (P extends string ? `${K}.${P}` : never) : never;
10
+ /**
11
+ * Generate dot notation paths for a given type T, up to a certain depth D (default 5).
12
+ * For example, for { a: { b: string }, c: number }, we would generate "a", "a.b", and "c". We stop recursion at depth 0 to prevent infinite types.
13
+ */
14
+ type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
15
+ /**
16
+ * Generate dot notation paths for a given type T. For example, for { a: { b: string }, c: number }, we would generate "a", "a.b", and "c".
17
+ */
18
+ type Path<T, D extends number = 5> = D extends 0 ? never : T extends Primitive ? never : T extends readonly unknown[] ? never : {
19
+ [K in Extract<keyof T, string>]: T[K] extends Primitive | readonly unknown[] ? K : K | Join<K, Path<T[K], Prev[D]>>;
20
+ }[Extract<keyof T, string>];
21
+ /**
22
+ * Given a type T and a dot notation path P, resolve the type at that path.
23
+ * For example, for T = { a: { b: string }, c: number } and P = "a.b", we would get string.
24
+ */
25
+ type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : never : P extends keyof T ? T[P] : never;
26
+ interface LimitOffsetPaginationConfig {
27
+ paginationType: 'LIMIT_OFFSET';
28
+ }
29
+ interface CursorPaginationConfig<T> {
30
+ paginationType: 'CURSOR';
31
+ cursorProperty: Path<T>;
32
+ }
33
+ declare const SortDirectionSchema: z.ZodEnum<{
34
+ ASC: "ASC";
35
+ DESC: "DESC";
36
+ }>;
37
+ type SortDirection = z.infer<typeof SortDirectionSchema>;
38
+ /**
39
+ * Supported operators.
40
+ * $eq: equality (for strings, numbers, dates)
41
+ * $null: checks for null (ignores the value)
42
+ * $in: checks if the field value is in the provided array (for strings, numbers, dates)
43
+ * $gt, $gte, $lt, $lte: comparison operators (for numbers and dates)
44
+ * $btw: checks if the field value is between two values (for numbers and dates)
45
+ * $ilike: case-insensitive substring match (for strings)
46
+ * $sw: case-insensitive starts-with match (for strings)
47
+ * $contains: checks if the field value contains the provided value (for strings)
48
+ */
49
+ declare const OperatorSchema: z.ZodEnum<{
50
+ $eq: "$eq";
51
+ $null: "$null";
52
+ $in: "$in";
53
+ $gt: "$gt";
54
+ $gte: "$gte";
55
+ $lt: "$lt";
56
+ $lte: "$lte";
57
+ $btw: "$btw";
58
+ $ilike: "$ilike";
59
+ $sw: "$sw";
60
+ $contains: "$contains";
61
+ }>;
62
+ type Operator = z.infer<typeof OperatorSchema>;
63
+ type FieldType = 'string' | 'number' | 'date' | 'any';
64
+ type FieldTypeFromValue<V> = V extends Date ? 'date' : V extends number ? 'number' : V extends string ? 'string' : 'any';
65
+ type CommonOps = '$eq' | '$null' | '$in' | '$contains';
66
+ type StringOnlyOps = '$ilike' | '$sw';
67
+ type ComparableOps = '$gt' | '$gte' | '$lt' | '$lte' | '$btw';
68
+ type OpsForFieldType<TKind extends FieldType> = TKind extends 'string' ? CommonOps | StringOnlyOps : TKind extends 'number' ? CommonOps | ComparableOps : TKind extends 'date' ? CommonOps | ComparableOps : Operator;
69
+ declare const ConditionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
70
+ group: z.ZodString;
71
+ combinator: z.ZodOptional<z.ZodEnum<{
72
+ $and: "$and";
73
+ $or: "$or";
74
+ }>>;
75
+ op: z.ZodLiteral<"$null">;
76
+ not: z.ZodOptional<z.ZodLiteral<true>>;
77
+ }, z.core.$strip>, z.ZodObject<{
78
+ group: z.ZodString;
79
+ combinator: z.ZodOptional<z.ZodEnum<{
80
+ $and: "$and";
81
+ $or: "$or";
82
+ }>>;
83
+ op: z.ZodLiteral<"$eq">;
84
+ not: z.ZodOptional<z.ZodLiteral<true>>;
85
+ value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
86
+ }, z.core.$strip>, z.ZodObject<{
87
+ group: z.ZodString;
88
+ combinator: z.ZodOptional<z.ZodEnum<{
89
+ $and: "$and";
90
+ $or: "$or";
91
+ }>>;
92
+ op: z.ZodEnum<{
93
+ $ilike: "$ilike";
94
+ $sw: "$sw";
95
+ }>;
96
+ not: z.ZodOptional<z.ZodLiteral<true>>;
97
+ value: z.ZodString;
98
+ }, z.core.$strip>, z.ZodObject<{
99
+ group: z.ZodString;
100
+ combinator: z.ZodOptional<z.ZodEnum<{
101
+ $and: "$and";
102
+ $or: "$or";
103
+ }>>;
104
+ op: z.ZodEnum<{
105
+ $in: "$in";
106
+ $contains: "$contains";
107
+ }>;
108
+ not: z.ZodOptional<z.ZodLiteral<true>>;
109
+ value: z.ZodArray<z.ZodString>;
110
+ }, z.core.$strip>, z.ZodObject<{
111
+ group: z.ZodString;
112
+ combinator: z.ZodOptional<z.ZodEnum<{
113
+ $and: "$and";
114
+ $or: "$or";
115
+ }>>;
116
+ op: z.ZodEnum<{
117
+ $gt: "$gt";
118
+ $gte: "$gte";
119
+ $lt: "$lt";
120
+ $lte: "$lte";
121
+ }>;
122
+ not: z.ZodOptional<z.ZodLiteral<true>>;
123
+ value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
124
+ }, z.core.$strip>, z.ZodObject<{
125
+ group: z.ZodString;
126
+ combinator: z.ZodOptional<z.ZodEnum<{
127
+ $and: "$and";
128
+ $or: "$or";
129
+ }>>;
130
+ op: z.ZodLiteral<"$btw">;
131
+ not: z.ZodOptional<z.ZodLiteral<true>>;
132
+ value: z.ZodTuple<[z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>], null>;
133
+ }, z.core.$strip>], "op">;
134
+ type Condition = z.infer<typeof ConditionSchema>;
135
+ interface WhereFilter {
136
+ type: 'filter';
137
+ field: string;
138
+ condition: Condition;
139
+ }
140
+ interface WhereAnd {
141
+ type: 'and';
142
+ items: WhereNode[];
143
+ }
144
+ interface WhereOr {
145
+ type: 'or';
146
+ items: WhereNode[];
147
+ }
148
+ type WhereNode = WhereFilter | WhereAnd | WhereOr;
149
+ export type DataSchema = z.ZodObject<z.ZodRawShape>;
150
+ export type InferData<TSchema extends DataSchema> = z.infer<TSchema>;
151
+ export type AllowedPath<TSchema extends DataSchema> = Path<InferData<TSchema>>;
152
+ interface FilterableFieldConfig<TKind extends FieldType> {
153
+ type: TKind;
154
+ ops: readonly OpsForFieldType<TKind>[];
155
+ }
156
+ export interface CommonQueryConfigFromSchema<TSchema extends DataSchema> {
157
+ dataSchema: TSchema;
158
+ selectable?: readonly AllowedPath<TSchema>[];
159
+ sortable?: readonly AllowedPath<TSchema>[];
160
+ filterable?: Partial<{
161
+ [P in AllowedPath<TSchema>]: FilterableFieldConfig<FieldTypeFromValue<PathValue<InferData<TSchema>, P>>>;
162
+ }>;
163
+ defaultSortBy?: readonly {
164
+ property: AllowedPath<TSchema>;
165
+ direction: SortDirection;
166
+ }[];
167
+ defaultLimit?: number;
168
+ defaultSelect?: readonly (AllowedPath<TSchema> | '*')[];
169
+ maxLimit?: number;
170
+ }
171
+ export type QueryConfigFromSchema<TSchema extends DataSchema> = CommonQueryConfigFromSchema<TSchema> & (LimitOffsetPaginationConfig | CursorPaginationConfig<InferData<TSchema>>);
172
+ export interface SortItemTyped<TSchema extends DataSchema> {
173
+ property: AllowedPath<TSchema>;
174
+ direction: SortDirection;
175
+ }
176
+ export interface LimitOffsetPaginationPayload<TSchema extends DataSchema> {
177
+ type: 'LIMIT_OFFSET';
178
+ limit?: number;
179
+ page?: number;
180
+ sortBy?: SortItemTyped<TSchema>[];
181
+ select?: AllowedPath<TSchema>[];
182
+ filters?: WhereNode;
183
+ }
184
+ /**
185
+ * Cursor is always a string in the query input, BUT we coerce it at parse-time
186
+ * to match the type of cursorProperty (number / string / ISO date string).
187
+ */
188
+ export interface CursorPaginationPayload<TSchema extends DataSchema> {
189
+ type: 'CURSOR';
190
+ limit?: number;
191
+ cursor?: number | string;
192
+ cursorProperty: AllowedPath<TSchema>;
193
+ sortBy?: SortItemTyped<TSchema>[];
194
+ select?: AllowedPath<TSchema>[];
195
+ filters?: WhereNode;
196
+ }
197
+ export interface PaginationQueryParams<TSchema extends DataSchema> {
198
+ pagination: LimitOffsetPaginationPayload<TSchema> | CursorPaginationPayload<TSchema>;
199
+ }
200
+ /**
201
+ * Generate Zod schemas and runtime validators for pagination query parameters, based on a config object.
202
+ * @param config The configuration object defining the pagination behavior and allowed fields.
203
+ * @returns An object containing:
204
+ * - `queryParamsSchema`: A Zod schema for validating and parsing the raw query parameters.
205
+ * - `validatorSchema`: A function that takes the already-parsed query parameters and returns a Zod schema for further validation (e.g. filters).
206
+ */
207
+ export declare function paginate<TSchema extends DataSchema>(config: QueryConfigFromSchema<TSchema>): {
208
+ queryParamsSchema: z.ZodType<PaginationQueryParams<TSchema>>;
209
+ validatorSchema: (parsed?: PaginationQueryParams<TSchema>) => z.ZodType;
210
+ };
211
+ export {};