@querykitjs/core 1.2.0 → 2.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.
@@ -0,0 +1,249 @@
1
+ import { SkippedCondition, SortDirection, Sort, CursorMeta, InfiniteMeta, OffsetMeta, Relations, QueryLimits, Filterable } from './index.cjs';
2
+ export { Z as isRaw } from './raw-Bx8c-TrC.cjs';
3
+
4
+ /**
5
+ * Opaque cursor tokens for keyset pagination, shared by every adapter so a token
6
+ * is interchangeable between them. A token is the base64url encoding of
7
+ * `{ v: <cursor value> }`; dates travel as ISO strings.
8
+ */
9
+ declare function encodeCursor(value: unknown): string;
10
+ /** The cursor value, or `undefined` for a missing or undecodable token. */
11
+ declare function decodeCursor(cursor?: string | null): unknown | undefined;
12
+ interface CursorToken {
13
+ k: string | number | boolean;
14
+ id: string | number | boolean;
15
+ }
16
+ /** A v2 token for a row's cursor value and primary key; signed when `secret` is given. */
17
+ declare function encodeCursorToken(payload: {
18
+ k: unknown;
19
+ id: unknown;
20
+ }, secret?: string): Promise<string>;
21
+ /**
22
+ * The `{ k, id }` of a v2 token, `undefined` for no token. A v1 token, a
23
+ * non-scalar `k` / `id`, a missing or wrong signature (with `secret`) or
24
+ * anything undecodable throws `QueryKitError` — invalid-value, site cursorKey,
25
+ * `key` the cursor key in use (the same key as an adapter's cast error).
26
+ */
27
+ declare function decodeCursorToken(token: string | null | undefined, options: {
28
+ source: string;
29
+ secret?: string;
30
+ key?: string;
31
+ }): Promise<CursorToken | undefined>;
32
+
33
+ interface Diagnostics {
34
+ /** Table (drizzle), model (prisma, mongoose) — reported as `source`. */
35
+ source: string;
36
+ strict: boolean;
37
+ onSkipped?: (info: SkippedCondition) => void;
38
+ }
39
+ declare function reportSkip(diag: Diagnostics | undefined, info: Omit<SkippedCondition, "source">): void;
40
+
41
+ interface ContextStore<TContext extends object> {
42
+ get(): TContext | undefined;
43
+ /** Run `fn` with `patch` merged over the current context. */
44
+ run<T>(patch: TContext, fn: () => T): T;
45
+ }
46
+ declare function createContextStore<TContext extends object>(): ContextStore<TContext>;
47
+
48
+ interface NormalizedSortItem {
49
+ key: string;
50
+ direction: SortDirection;
51
+ }
52
+ /**
53
+ * The wire `sort` array with a direction on every item. An item without a `key`
54
+ * (`null`, `{}`) is dropped — sort comes from the client, and a malformed item
55
+ * must not crash the query — and, with `diag`, reported as `invalid-value` on
56
+ * `site: "sort"` (a `QueryKitError` under strict; task 021 B14).
57
+ */
58
+ declare function normalizeSort(sort?: Sort, diag?: Diagnostics): NormalizedSortItem[];
59
+
60
+ /**
61
+ * Page-size clamping and the three pagination metas, computed once for every
62
+ * adapter so `total_pages`, `has_more`, `next_cursor` … mean the same thing on
63
+ * all of them. Adapters only fetch rows; these functions turn rows into a page.
64
+ */
65
+
66
+ /** A requested page size, defaulted, truncated, at least 1 and at most `max`. */
67
+ declare function clampPageSize(requested: number | undefined, fallback: number, max: number): number;
68
+ /** A requested page number: an integer, at least 1. */
69
+ declare function clampPage(requested: number | undefined): number;
70
+ /** A requested offset: an integer, at least 0. */
71
+ declare function clampOffset(requested: number | undefined): number;
72
+ declare function offsetMeta(page: number, perPage: number, totalItems: number): OffsetMeta;
73
+ /**
74
+ * `withTotal: false` (task 013): rows fetched with `perPage + 1` and no count →
75
+ * the page and its meta; the extra row only signals `has_next`, totals are `null`.
76
+ */
77
+ declare function offsetPageWithoutTotal<T>(rows: readonly T[], page: number, perPage: number): {
78
+ data: T[];
79
+ meta: OffsetMeta;
80
+ };
81
+ /** Rows fetched with `limit + 1` → the page and its meta (the extra row only signals `has_more`). */
82
+ declare function infinitePage<T>(rows: readonly T[], limit: number, offset: number): {
83
+ data: T[];
84
+ meta: InfiniteMeta;
85
+ };
86
+ interface CursorPageOptions {
87
+ limit: number;
88
+ direction: "forward" | "backward";
89
+ /** A cursor was supplied (the request continues from a previous page). */
90
+ hasCursor: boolean;
91
+ /** The row field the tokens are read from. */
92
+ cursorField: string;
93
+ /** The primary-key field — the tie-break value in every token (task 012). */
94
+ idField: string;
95
+ /** Signs the tokens (registry `cursorSecret`). */
96
+ secret?: string;
97
+ }
98
+ /**
99
+ * Rows fetched with `limit + 1` in query order → the page in display order and
100
+ * its meta. A backward page is fetched reversed and flipped back here.
101
+ */
102
+ declare function cursorPage<T extends Record<string, unknown>>(rows: readonly T[], options: CursorPageOptions): Promise<{
103
+ data: T[];
104
+ meta: CursorMeta;
105
+ }>;
106
+
107
+ /** `{ all: true }` — and nothing else — asks for every row (the scope still applies). */
108
+ declare function isAllRows(filter: unknown): boolean;
109
+ /** `undefined`, `null`, `[]` or `{}`: no condition at all. */
110
+ declare function isEmptyFilter(filter: unknown): boolean;
111
+ /** A mutation without a condition — also one whose every condition was skipped. */
112
+ declare function refuseEmptyFilter(source: string, method: "updateWhere" | "deleteWhere"): never;
113
+
114
+ /**
115
+ * A repository `scope` (the server's RBAC / tenancy filter) split once for every
116
+ * adapter (task 014): equality values and `{ in: [...] }` lists.
117
+ */
118
+ /** One scope condition, in the shape every adapter's `buildWhere` reads. */
119
+ interface ScopeCondition {
120
+ key: string;
121
+ operation: "=" | "in";
122
+ value: unknown;
123
+ }
124
+ /** The scope as conditions: `{ k: v }` → `k = v`, `{ k: { in: [...] } }` → `k in [...]`. */
125
+ declare function scopeConditions(scope: Record<string, unknown> | undefined): ScopeCondition[];
126
+ /** The values `create` / `upsert` fill in: the equality scopes only (a list has no single value). */
127
+ declare function scopeDefaults(scope: Record<string, unknown> | undefined): Record<string, unknown>;
128
+
129
+ /** How one adapter spells a relation's options. */
130
+ interface WithDialect {
131
+ /** Column selection: drizzle `columns`, prisma / mongoose `select`. */
132
+ columns: string;
133
+ /** Nested relations: drizzle `with`, prisma `include`, mongoose `populate`. */
134
+ nested: string;
135
+ /** Row filters: prisma `where`, mongoose `match`; drizzle none (its `where` is code, not JSON). */
136
+ filters: readonly string[];
137
+ /** Logical keys inside a filter, walked into: prisma `AND` / `OR` / `NOT`, mongoose `$and` / `$or` / `$nor`. */
138
+ logical: readonly string[];
139
+ /** Options that only page: drizzle `limit` / `offset`, prisma `take` / `skip`, mongoose `options.limit` / `options.skip`. */
140
+ paging: readonly string[];
141
+ /** A sort whose keys must be allowed columns: prisma `orderBy`. */
142
+ orderBy?: string;
143
+ /** An options bag whose keys must be `paging` keys: mongoose `options`. */
144
+ options?: string;
145
+ }
146
+ /**
147
+ * The checked `with`: every relation in `relations`, every selected column in its
148
+ * `columns`, every filter field in its `filterable`, every nested relation in its
149
+ * `nested`. A relation asked for with `true` (or without a selection) gets its
150
+ * listed columns selected, so a column list is never widened to the full row.
151
+ */
152
+ declare function checkWith(withValue: unknown, relations: Relations | undefined, dialect: WithDialect, source: string, path?: string): Record<string, unknown> | undefined;
153
+
154
+ /**
155
+ * The filter-limit walk shared by the validators (task 008). It runs on the raw,
156
+ * unvalidated payload **before** any recursive schema sees it, so it is
157
+ * iterative — no recursion at all, whatever the payload's depth — and a
158
+ * 10 000-level `not` chain is rejected after `maxDepth + 1` steps.
159
+ *
160
+ * Only size is judged here. Shape errors (a missing `key`, a bad operator) are
161
+ * the validator's job — a malformed node is skipped, not reported.
162
+ */
163
+
164
+ /** Where in the filter a limit was crossed — object keys and array indexes, from the root. */
165
+ type LimitPath = (string | number)[];
166
+ interface LimitViolation {
167
+ limit: keyof QueryLimits;
168
+ max: number;
169
+ path: LimitPath;
170
+ }
171
+ /** The limits the walk checks. */
172
+ type FilterLimits = Pick<QueryLimits, "maxDepth" | "maxConditions" | "maxInItems" | "maxStringLength">;
173
+ /** `maxInItems (200)` — the form every package's error text uses. */
174
+ declare const formatLimit: (limit: keyof QueryLimits, max: number) => string;
175
+ /**
176
+ * The first limit the filter crosses, depth-first in document order, or
177
+ * `undefined` when it is within all four. A flat array (implicit AND) puts its
178
+ * conditions at level 1. Key order mirrors the validators: `key` first, then
179
+ * `and`, `or`, `not`.
180
+ */
181
+ declare function findFilterLimitViolation(filter: unknown, limits: FilterLimits): LimitViolation | undefined;
182
+ /** Whether an operator takes a SQL LIKE pattern. */
183
+ declare const isLikeOperator: (operation: string) => boolean;
184
+ /**
185
+ * Unescaped wildcards in a LIKE pattern, a run of `%` counted once (`%%a%` ⇒ 2).
186
+ * A backslash escapes the next character, as in the adapters' LIKE translation.
187
+ */
188
+ declare function likeWildcards(pattern: string): number;
189
+ /** Rows an offset-paginated request skips: `(page - 1) × perPage`, with the core default page size. */
190
+ declare const offsetOf: (page: number | undefined, perPage: number | undefined) => number;
191
+
192
+ /**
193
+ * The server-side request policy every adapter applies (task 009): which fields
194
+ * a client may filter by and with which operators (`filterable`), which it may
195
+ * sort and page by (`sortable`), and the request limits (`limits`, task 008).
196
+ *
197
+ * A plain object that is not a condition, a group, a branded `raw()` node or a
198
+ * driver-native object (`isNative`) is refused as well (R19): it would otherwise
199
+ * reach the driver as its own `where`, past `filterable`.
200
+ *
201
+ * A refused request never reaches the driver and is never dropped silently:
202
+ * every check throws `QueryKitError`, whatever `strict` / `onSkippedCondition`
203
+ * say. A key the model does not have is **not** the policy's business — it goes
204
+ * on to the adapter's own `unknown-key` handling, unchanged.
205
+ */
206
+
207
+ interface PolicyOptions {
208
+ /** Table / model name for the error. */
209
+ source: string;
210
+ /** Wire key → the model's canonical field name, or `undefined` when the model has no such field. */
211
+ fieldOf: (key: string) => string | undefined;
212
+ /** Canonical primary-key field — the last-resort safe default. */
213
+ primaryKey: string;
214
+ /** Canonical `createdAt` field when the model has one (default `sortable`). */
215
+ createdAt?: string;
216
+ filterable?: Filterable;
217
+ sortable?: readonly string[];
218
+ allowedColumns?: readonly string[];
219
+ forcedColumns?: Readonly<Record<string, unknown>>;
220
+ limits?: Partial<QueryLimits>;
221
+ /**
222
+ * A driver object that cannot come from JSON and stands for itself (drizzle `SQL`).
223
+ * Anything else that is not a condition, a group or a {@link isRaw} node is refused (R19).
224
+ */
225
+ isNative?: (node: object) => boolean;
226
+ }
227
+ interface Policy {
228
+ readonly limits: QueryLimits;
229
+ /** Limits, then every condition's field and operator. */
230
+ checkFilter(filter: unknown): void;
231
+ /** Field count, then every sort field. */
232
+ checkSort(sort: readonly {
233
+ key?: unknown;
234
+ }[] | undefined): void;
235
+ checkCursorKey(key: string): void;
236
+ /** Offset pagination: the clamped `page` × `perPage`. Infinite: the clamped `offset`. */
237
+ checkPage(page: {
238
+ page: number;
239
+ perPage: number;
240
+ } | {
241
+ offset: number;
242
+ }): void;
243
+ /** A `groupBy` / `sum` / `avg` / `min` / `max` field must be filterable. */
244
+ checkAggregateKey(key: string): void;
245
+ }
246
+ declare function resolvePolicy(options: PolicyOptions): Policy;
247
+
248
+ export { checkWith, clampOffset, clampPage, clampPageSize, createContextStore, cursorPage, decodeCursor, decodeCursorToken, encodeCursor, encodeCursorToken, findFilterLimitViolation, formatLimit, infinitePage, isAllRows, isEmptyFilter, isLikeOperator, likeWildcards, normalizeSort, offsetMeta, offsetOf, offsetPageWithoutTotal, refuseEmptyFilter, reportSkip, resolvePolicy, scopeConditions, scopeDefaults };
249
+ export type { ContextStore, CursorPageOptions, CursorToken, Diagnostics, FilterLimits, LimitPath, LimitViolation, NormalizedSortItem, Policy, PolicyOptions, ScopeCondition, WithDialect };
@@ -0,0 +1,249 @@
1
+ import { SkippedCondition, SortDirection, Sort, CursorMeta, InfiniteMeta, OffsetMeta, Relations, QueryLimits, Filterable } from './index.js';
2
+ export { Z as isRaw } from './raw-Bx8c-TrC.js';
3
+
4
+ /**
5
+ * Opaque cursor tokens for keyset pagination, shared by every adapter so a token
6
+ * is interchangeable between them. A token is the base64url encoding of
7
+ * `{ v: <cursor value> }`; dates travel as ISO strings.
8
+ */
9
+ declare function encodeCursor(value: unknown): string;
10
+ /** The cursor value, or `undefined` for a missing or undecodable token. */
11
+ declare function decodeCursor(cursor?: string | null): unknown | undefined;
12
+ interface CursorToken {
13
+ k: string | number | boolean;
14
+ id: string | number | boolean;
15
+ }
16
+ /** A v2 token for a row's cursor value and primary key; signed when `secret` is given. */
17
+ declare function encodeCursorToken(payload: {
18
+ k: unknown;
19
+ id: unknown;
20
+ }, secret?: string): Promise<string>;
21
+ /**
22
+ * The `{ k, id }` of a v2 token, `undefined` for no token. A v1 token, a
23
+ * non-scalar `k` / `id`, a missing or wrong signature (with `secret`) or
24
+ * anything undecodable throws `QueryKitError` — invalid-value, site cursorKey,
25
+ * `key` the cursor key in use (the same key as an adapter's cast error).
26
+ */
27
+ declare function decodeCursorToken(token: string | null | undefined, options: {
28
+ source: string;
29
+ secret?: string;
30
+ key?: string;
31
+ }): Promise<CursorToken | undefined>;
32
+
33
+ interface Diagnostics {
34
+ /** Table (drizzle), model (prisma, mongoose) — reported as `source`. */
35
+ source: string;
36
+ strict: boolean;
37
+ onSkipped?: (info: SkippedCondition) => void;
38
+ }
39
+ declare function reportSkip(diag: Diagnostics | undefined, info: Omit<SkippedCondition, "source">): void;
40
+
41
+ interface ContextStore<TContext extends object> {
42
+ get(): TContext | undefined;
43
+ /** Run `fn` with `patch` merged over the current context. */
44
+ run<T>(patch: TContext, fn: () => T): T;
45
+ }
46
+ declare function createContextStore<TContext extends object>(): ContextStore<TContext>;
47
+
48
+ interface NormalizedSortItem {
49
+ key: string;
50
+ direction: SortDirection;
51
+ }
52
+ /**
53
+ * The wire `sort` array with a direction on every item. An item without a `key`
54
+ * (`null`, `{}`) is dropped — sort comes from the client, and a malformed item
55
+ * must not crash the query — and, with `diag`, reported as `invalid-value` on
56
+ * `site: "sort"` (a `QueryKitError` under strict; task 021 B14).
57
+ */
58
+ declare function normalizeSort(sort?: Sort, diag?: Diagnostics): NormalizedSortItem[];
59
+
60
+ /**
61
+ * Page-size clamping and the three pagination metas, computed once for every
62
+ * adapter so `total_pages`, `has_more`, `next_cursor` … mean the same thing on
63
+ * all of them. Adapters only fetch rows; these functions turn rows into a page.
64
+ */
65
+
66
+ /** A requested page size, defaulted, truncated, at least 1 and at most `max`. */
67
+ declare function clampPageSize(requested: number | undefined, fallback: number, max: number): number;
68
+ /** A requested page number: an integer, at least 1. */
69
+ declare function clampPage(requested: number | undefined): number;
70
+ /** A requested offset: an integer, at least 0. */
71
+ declare function clampOffset(requested: number | undefined): number;
72
+ declare function offsetMeta(page: number, perPage: number, totalItems: number): OffsetMeta;
73
+ /**
74
+ * `withTotal: false` (task 013): rows fetched with `perPage + 1` and no count →
75
+ * the page and its meta; the extra row only signals `has_next`, totals are `null`.
76
+ */
77
+ declare function offsetPageWithoutTotal<T>(rows: readonly T[], page: number, perPage: number): {
78
+ data: T[];
79
+ meta: OffsetMeta;
80
+ };
81
+ /** Rows fetched with `limit + 1` → the page and its meta (the extra row only signals `has_more`). */
82
+ declare function infinitePage<T>(rows: readonly T[], limit: number, offset: number): {
83
+ data: T[];
84
+ meta: InfiniteMeta;
85
+ };
86
+ interface CursorPageOptions {
87
+ limit: number;
88
+ direction: "forward" | "backward";
89
+ /** A cursor was supplied (the request continues from a previous page). */
90
+ hasCursor: boolean;
91
+ /** The row field the tokens are read from. */
92
+ cursorField: string;
93
+ /** The primary-key field — the tie-break value in every token (task 012). */
94
+ idField: string;
95
+ /** Signs the tokens (registry `cursorSecret`). */
96
+ secret?: string;
97
+ }
98
+ /**
99
+ * Rows fetched with `limit + 1` in query order → the page in display order and
100
+ * its meta. A backward page is fetched reversed and flipped back here.
101
+ */
102
+ declare function cursorPage<T extends Record<string, unknown>>(rows: readonly T[], options: CursorPageOptions): Promise<{
103
+ data: T[];
104
+ meta: CursorMeta;
105
+ }>;
106
+
107
+ /** `{ all: true }` — and nothing else — asks for every row (the scope still applies). */
108
+ declare function isAllRows(filter: unknown): boolean;
109
+ /** `undefined`, `null`, `[]` or `{}`: no condition at all. */
110
+ declare function isEmptyFilter(filter: unknown): boolean;
111
+ /** A mutation without a condition — also one whose every condition was skipped. */
112
+ declare function refuseEmptyFilter(source: string, method: "updateWhere" | "deleteWhere"): never;
113
+
114
+ /**
115
+ * A repository `scope` (the server's RBAC / tenancy filter) split once for every
116
+ * adapter (task 014): equality values and `{ in: [...] }` lists.
117
+ */
118
+ /** One scope condition, in the shape every adapter's `buildWhere` reads. */
119
+ interface ScopeCondition {
120
+ key: string;
121
+ operation: "=" | "in";
122
+ value: unknown;
123
+ }
124
+ /** The scope as conditions: `{ k: v }` → `k = v`, `{ k: { in: [...] } }` → `k in [...]`. */
125
+ declare function scopeConditions(scope: Record<string, unknown> | undefined): ScopeCondition[];
126
+ /** The values `create` / `upsert` fill in: the equality scopes only (a list has no single value). */
127
+ declare function scopeDefaults(scope: Record<string, unknown> | undefined): Record<string, unknown>;
128
+
129
+ /** How one adapter spells a relation's options. */
130
+ interface WithDialect {
131
+ /** Column selection: drizzle `columns`, prisma / mongoose `select`. */
132
+ columns: string;
133
+ /** Nested relations: drizzle `with`, prisma `include`, mongoose `populate`. */
134
+ nested: string;
135
+ /** Row filters: prisma `where`, mongoose `match`; drizzle none (its `where` is code, not JSON). */
136
+ filters: readonly string[];
137
+ /** Logical keys inside a filter, walked into: prisma `AND` / `OR` / `NOT`, mongoose `$and` / `$or` / `$nor`. */
138
+ logical: readonly string[];
139
+ /** Options that only page: drizzle `limit` / `offset`, prisma `take` / `skip`, mongoose `options.limit` / `options.skip`. */
140
+ paging: readonly string[];
141
+ /** A sort whose keys must be allowed columns: prisma `orderBy`. */
142
+ orderBy?: string;
143
+ /** An options bag whose keys must be `paging` keys: mongoose `options`. */
144
+ options?: string;
145
+ }
146
+ /**
147
+ * The checked `with`: every relation in `relations`, every selected column in its
148
+ * `columns`, every filter field in its `filterable`, every nested relation in its
149
+ * `nested`. A relation asked for with `true` (or without a selection) gets its
150
+ * listed columns selected, so a column list is never widened to the full row.
151
+ */
152
+ declare function checkWith(withValue: unknown, relations: Relations | undefined, dialect: WithDialect, source: string, path?: string): Record<string, unknown> | undefined;
153
+
154
+ /**
155
+ * The filter-limit walk shared by the validators (task 008). It runs on the raw,
156
+ * unvalidated payload **before** any recursive schema sees it, so it is
157
+ * iterative — no recursion at all, whatever the payload's depth — and a
158
+ * 10 000-level `not` chain is rejected after `maxDepth + 1` steps.
159
+ *
160
+ * Only size is judged here. Shape errors (a missing `key`, a bad operator) are
161
+ * the validator's job — a malformed node is skipped, not reported.
162
+ */
163
+
164
+ /** Where in the filter a limit was crossed — object keys and array indexes, from the root. */
165
+ type LimitPath = (string | number)[];
166
+ interface LimitViolation {
167
+ limit: keyof QueryLimits;
168
+ max: number;
169
+ path: LimitPath;
170
+ }
171
+ /** The limits the walk checks. */
172
+ type FilterLimits = Pick<QueryLimits, "maxDepth" | "maxConditions" | "maxInItems" | "maxStringLength">;
173
+ /** `maxInItems (200)` — the form every package's error text uses. */
174
+ declare const formatLimit: (limit: keyof QueryLimits, max: number) => string;
175
+ /**
176
+ * The first limit the filter crosses, depth-first in document order, or
177
+ * `undefined` when it is within all four. A flat array (implicit AND) puts its
178
+ * conditions at level 1. Key order mirrors the validators: `key` first, then
179
+ * `and`, `or`, `not`.
180
+ */
181
+ declare function findFilterLimitViolation(filter: unknown, limits: FilterLimits): LimitViolation | undefined;
182
+ /** Whether an operator takes a SQL LIKE pattern. */
183
+ declare const isLikeOperator: (operation: string) => boolean;
184
+ /**
185
+ * Unescaped wildcards in a LIKE pattern, a run of `%` counted once (`%%a%` ⇒ 2).
186
+ * A backslash escapes the next character, as in the adapters' LIKE translation.
187
+ */
188
+ declare function likeWildcards(pattern: string): number;
189
+ /** Rows an offset-paginated request skips: `(page - 1) × perPage`, with the core default page size. */
190
+ declare const offsetOf: (page: number | undefined, perPage: number | undefined) => number;
191
+
192
+ /**
193
+ * The server-side request policy every adapter applies (task 009): which fields
194
+ * a client may filter by and with which operators (`filterable`), which it may
195
+ * sort and page by (`sortable`), and the request limits (`limits`, task 008).
196
+ *
197
+ * A plain object that is not a condition, a group, a branded `raw()` node or a
198
+ * driver-native object (`isNative`) is refused as well (R19): it would otherwise
199
+ * reach the driver as its own `where`, past `filterable`.
200
+ *
201
+ * A refused request never reaches the driver and is never dropped silently:
202
+ * every check throws `QueryKitError`, whatever `strict` / `onSkippedCondition`
203
+ * say. A key the model does not have is **not** the policy's business — it goes
204
+ * on to the adapter's own `unknown-key` handling, unchanged.
205
+ */
206
+
207
+ interface PolicyOptions {
208
+ /** Table / model name for the error. */
209
+ source: string;
210
+ /** Wire key → the model's canonical field name, or `undefined` when the model has no such field. */
211
+ fieldOf: (key: string) => string | undefined;
212
+ /** Canonical primary-key field — the last-resort safe default. */
213
+ primaryKey: string;
214
+ /** Canonical `createdAt` field when the model has one (default `sortable`). */
215
+ createdAt?: string;
216
+ filterable?: Filterable;
217
+ sortable?: readonly string[];
218
+ allowedColumns?: readonly string[];
219
+ forcedColumns?: Readonly<Record<string, unknown>>;
220
+ limits?: Partial<QueryLimits>;
221
+ /**
222
+ * A driver object that cannot come from JSON and stands for itself (drizzle `SQL`).
223
+ * Anything else that is not a condition, a group or a {@link isRaw} node is refused (R19).
224
+ */
225
+ isNative?: (node: object) => boolean;
226
+ }
227
+ interface Policy {
228
+ readonly limits: QueryLimits;
229
+ /** Limits, then every condition's field and operator. */
230
+ checkFilter(filter: unknown): void;
231
+ /** Field count, then every sort field. */
232
+ checkSort(sort: readonly {
233
+ key?: unknown;
234
+ }[] | undefined): void;
235
+ checkCursorKey(key: string): void;
236
+ /** Offset pagination: the clamped `page` × `perPage`. Infinite: the clamped `offset`. */
237
+ checkPage(page: {
238
+ page: number;
239
+ perPage: number;
240
+ } | {
241
+ offset: number;
242
+ }): void;
243
+ /** A `groupBy` / `sum` / `avg` / `min` / `max` field must be filterable. */
244
+ checkAggregateKey(key: string): void;
245
+ }
246
+ declare function resolvePolicy(options: PolicyOptions): Policy;
247
+
248
+ export { checkWith, clampOffset, clampPage, clampPageSize, createContextStore, cursorPage, decodeCursor, decodeCursorToken, encodeCursor, encodeCursorToken, findFilterLimitViolation, formatLimit, infinitePage, isAllRows, isEmptyFilter, isLikeOperator, likeWildcards, normalizeSort, offsetMeta, offsetOf, offsetPageWithoutTotal, refuseEmptyFilter, reportSkip, resolvePolicy, scopeConditions, scopeDefaults };
249
+ export type { ContextStore, CursorPageOptions, CursorToken, Diagnostics, FilterLimits, LimitPath, LimitViolation, NormalizedSortItem, Policy, PolicyOptions, ScopeCondition, WithDialect };