@querykitjs/mongoose 2.0.0 → 3.0.1

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/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Types, Model, Connection, ClientSession } from 'mongoose';
2
- import * as _querykitjs_core from '@querykitjs/core';
3
- import { SkippedCondition } from '@querykitjs/core';
2
+ import * as Core from '@querykitjs/core';
3
+ import { AllRows, MutationCount, MutationRows, RelationRule, SkippedCondition, QueryLimits, createFilters as createFilters$1 } from '@querykitjs/core';
4
4
  export { f } from '@querykitjs/core';
5
5
 
6
6
  /**
@@ -38,19 +38,19 @@ type FieldKey<TDoc> = keyof TDoc & string;
38
38
  * validated payload can go straight into a repository call with no `as` cast.
39
39
  * Autocomplete still suggests the document's real fields.
40
40
  */
41
- type LooseFieldKey<TDoc> = _querykitjs_core.LooseKey<FieldKey<TDoc>>;
42
- type FilterOperator = _querykitjs_core.FilterOperator;
43
- type FilterScalar = _querykitjs_core.FilterScalar;
44
- type FilterValue = _querykitjs_core.FilterValue;
45
- type FieldCondition<TDoc = Record<string, unknown>> = _querykitjs_core.FieldCondition<LooseFieldKey<TDoc>>;
46
- type AndGroup<TDoc = Record<string, unknown>> = _querykitjs_core.AndGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
47
- type OrGroup<TDoc = Record<string, unknown>> = _querykitjs_core.OrGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
48
- type NotGroup<TDoc = Record<string, unknown>> = _querykitjs_core.NotGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
49
- type FilterNode<TDoc = Record<string, unknown>> = _querykitjs_core.FilterNode<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
50
- type Filter<TDoc = Record<string, unknown>> = _querykitjs_core.Filter<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
51
- type SortDirection = _querykitjs_core.SortDirection;
41
+ type LooseFieldKey<TDoc> = Core.LooseKey<FieldKey<TDoc>>;
42
+ type FilterOperator = Core.FilterOperator;
43
+ type FilterScalar = Core.FilterScalar;
44
+ type FilterValue = Core.FilterValue;
45
+ type FieldCondition<TDoc = Record<string, unknown>> = Core.FieldCondition<LooseFieldKey<TDoc>>;
46
+ type AndGroup<TDoc = Record<string, unknown>> = Core.AndGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
47
+ type OrGroup<TDoc = Record<string, unknown>> = Core.OrGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
48
+ type NotGroup<TDoc = Record<string, unknown>> = Core.NotGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
49
+ type FilterNode<TDoc = Record<string, unknown>> = Core.FilterNode<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
50
+ type Filter<TDoc = Record<string, unknown>> = Core.Filter<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
51
+ type SortDirection = Core.SortDirection;
52
52
  /** One sort field by typed field key (`{ key, direction }`) — autocomplete. */
53
- type SortItem<TDoc = Record<string, unknown>> = _querykitjs_core.SortItem<LooseFieldKey<TDoc>>;
53
+ type SortItem<TDoc = Record<string, unknown>> = Core.SortItem<LooseFieldKey<TDoc>>;
54
54
  /**
55
55
  * Sort — **always an array** of `{ key, direction }` (multi-field). This is the
56
56
  * exact shape the querykit frontend sends (`@querykitjs/web` decodes its
@@ -69,22 +69,25 @@ interface QueryParams<TDoc = Record<string, unknown>> {
69
69
  withDeleted?: boolean;
70
70
  }
71
71
  /** Equality scope applied to every operation of a scoped repository. */
72
- type Scope<TDoc = Record<string, unknown>> = _querykitjs_core.Scope<FieldKey<TDoc>>;
72
+ type Scope<TDoc = Record<string, unknown>> = Core.Scope<FieldKey<TDoc>>;
73
73
  interface ByIdParams<TDoc = Record<string, unknown>> extends QueryParams<TDoc> {
74
74
  /** Field to match against the id. Defaults to `"id"` (→ `_id`). */
75
75
  idKey?: LooseFieldKey<TDoc>;
76
76
  }
77
77
  /** Options for {@link Repository.upsert} / {@link Repository.upsertMany}. */
78
- type UpsertOptions<TDoc = Record<string, unknown>> = _querykitjs_core.UpsertOptions<FieldKey<TDoc>, Insert<TDoc>>;
78
+ type UpsertOptions<TDoc = Record<string, unknown>> = Core.UpsertOptions<FieldKey<TDoc>, Insert<TDoc>>;
79
79
  /** Aggregate query specification (`count`/`sum`/`avg`/`min`/`max` + `groupBy`). */
80
- type AggregateSpec<TDoc = Record<string, unknown>> = _querykitjs_core.AggregateSpec<FieldKey<TDoc>, Filter<TDoc>>;
80
+ type AggregateSpec<TDoc = Record<string, unknown>> = Core.AggregateSpec<FieldKey<TDoc>, Filter<TDoc>>;
81
81
  /** One aggregated result row (group fields + aggregate values). */
82
82
  type AggregateRow = Record<string, unknown>;
83
+
83
84
  interface OffsetParams<TDoc = Record<string, unknown>> extends QueryParams<TDoc> {
84
85
  page?: number;
85
86
  perPage?: number;
87
+ /** `false` — no count query: `perPage + 1` rows are read, `total_items` / `total_pages` are `null`. Default `true`. */
88
+ withTotal?: boolean;
86
89
  }
87
- type OffsetMeta = _querykitjs_core.OffsetMeta;
90
+ type OffsetMeta = Core.OffsetMeta;
88
91
  interface OffsetResult<T> {
89
92
  data: T[];
90
93
  meta: OffsetMeta;
@@ -93,7 +96,7 @@ interface InfiniteParams<TDoc = Record<string, unknown>> extends QueryParams<TDo
93
96
  limit?: number;
94
97
  offset?: number;
95
98
  }
96
- type InfiniteMeta = _querykitjs_core.InfiniteMeta;
99
+ type InfiniteMeta = Core.InfiniteMeta;
97
100
  interface InfiniteResult<T> {
98
101
  data: T[];
99
102
  meta: InfiniteMeta;
@@ -106,14 +109,23 @@ interface CursorParams<TDoc = Record<string, unknown>> extends QueryParams<TDoc>
106
109
  order?: SortDirection;
107
110
  direction?: "forward" | "backward";
108
111
  }
109
- type CursorMeta = _querykitjs_core.CursorMeta;
112
+ type CursorMeta = Core.CursorMeta;
110
113
  interface CursorResult<T> {
111
114
  data: T[];
112
115
  meta: CursorMeta;
113
116
  }
114
- /** Map of relation field name → the Mongoose model it refers to. */
115
- type RelationMap = Record<string, Model<any>>;
116
- type DocOf<M> = M extends Model<infer T> ? T : never;
117
+
118
+ /**
119
+ * Relation field → its rule (R21): `{ model?, columns, filterable?, nested? }` opens it for a
120
+ * client `with` (`columns` required — a list or `"*"`). A bare model (`{ author: User }`) still types
121
+ * `with`, but opens nothing: `with: { author: … }` is `forbidden-key`, `site: "with"`.
122
+ */
123
+ type RelationMap = Record<string, Model<any> | (RelationRule & {
124
+ model?: Model<any>;
125
+ })>;
126
+ type DocOf<M> = M extends Model<infer T> ? T : M extends {
127
+ model: Model<infer T>;
128
+ } ? T : Record<string, unknown>;
117
129
  /** Resolve a {@link RelationMap} to `{ field: <its document type> }`. */
118
130
  type RelationDocs<TRel extends RelationMap> = {
119
131
  [K in keyof TRel]: DocOf<TRel[K]>;
@@ -133,7 +145,7 @@ type RelationDocs<TRel extends RelationMap> = {
133
145
  * result to a client.
134
146
  */
135
147
  interface RepositoryOptions<TDoc = any, TRel extends RelationMap = RelationMap> {
136
- /** Declare populatable relations (field → model) so `with` types the result. */
148
+ /** Populatable relations: field → `{ model?, columns, filterable?, nested? }` (R21), or a bare model (typing only). */
137
149
  relations?: TRel;
138
150
  /** Constant equality filter added to every read/write (RBAC / multi-tenancy). */
139
151
  scope?: Scope<TDoc>;
@@ -149,6 +161,23 @@ interface RepositoryOptions<TDoc = any, TRel extends RelationMap = RelationMap>
149
161
  * An empty array throws (a projection selecting nothing means "everything").
150
162
  */
151
163
  allowedColumns?: readonly FieldKey<TDoc>[];
164
+ /**
165
+ * Paths a client may filter by, and the operators for each (`"*"` — all). A path
166
+ * not listed ⇒ `reason: "forbidden-key"`, an operator not listed ⇒
167
+ * `"forbidden-operator"` — a `QueryKitError` whatever `strict` says, before the
168
+ * query runs. **When not given** — `allowedColumns` (else the `forcedColumns` keys,
169
+ * else only `_id`) with `"="` and `"in"` only. `aggregate` paths come from this list too.
170
+ */
171
+ filterable?: Core.Filterable<FieldKey<TDoc> | "_id">;
172
+ /**
173
+ * Paths a client may sort (and use as `cursorKey`) by. When not given — `_id` and
174
+ * `createdAt` (when the schema has it). Anything else ⇒ `reason: "forbidden-key"`.
175
+ */
176
+ sortable?: readonly (FieldKey<TDoc> | "_id")[];
177
+ /** Request limits for this repository — over the registry's `limits`. */
178
+ limits?: Partial<Core.QueryLimits>;
179
+ /** Read time limit for this repository, in ms — over the registry's `maxTimeMS`. */
180
+ maxTimeMS?: number;
152
181
  }
153
182
  type TrueKeys<T> = {
154
183
  [K in keyof T]: T[K] extends true ? K : never;
@@ -190,12 +219,32 @@ interface Repository<TDoc = Record<string, unknown>, TRel extends Record<string,
190
219
  upsertMany(values: Insert<TDoc>[], options: UpsertOptions<TDoc>): Promise<Row<TDoc>[]>;
191
220
  /** Update one document by id; returns the updated doc (or undefined). */
192
221
  updateById(id: Id, patch: Partial<Insert<TDoc>>, idKey?: FieldKey<TDoc>): Promise<Row<TDoc> | undefined>;
193
- /** Update all documents matching the filter; returns the updated docs. */
194
- updateWhere(filter: Filter<TDoc>, patch: Partial<Insert<TDoc>>): Promise<Row<TDoc>[]>;
222
+ /**
223
+ * Update every document matching the filter in one `updateMany` → `{ count }` (matched).
224
+ * No filter only with `{ all: true }` (the scope still applies); an empty or fully skipped
225
+ * filter is `QueryKitError` `empty-filter`. The filter passes the request policy like a read.
226
+ */
227
+ updateWhere(filter: Filter<TDoc> | AllRows, patch: Partial<Insert<TDoc>>, options?: {
228
+ returning?: false;
229
+ }): Promise<MutationCount>;
230
+ /** With `returning: { limit }`: `{ count, rows }` — the rows are read right after the update with the same filter (not atomic). */
231
+ updateWhere(filter: Filter<TDoc> | AllRows, patch: Partial<Insert<TDoc>>, options: {
232
+ returning: {
233
+ limit: number;
234
+ };
235
+ }): Promise<MutationRows<Row<TDoc>>>;
195
236
  /** Hard-delete one document by id; returns the deleted doc (or undefined). */
196
237
  deleteById(id: Id, idKey?: FieldKey<TDoc>): Promise<Row<TDoc> | undefined>;
197
- /** Hard-delete all documents matching the filter; returns the deleted docs. */
198
- deleteWhere(filter: Filter<TDoc>): Promise<Row<TDoc>[]>;
238
+ /** Hard-delete every document matching the filter in one `deleteMany` → `{ count }` (same rules as `updateWhere`). */
239
+ deleteWhere(filter: Filter<TDoc> | AllRows, options?: {
240
+ returning?: false;
241
+ }): Promise<MutationCount>;
242
+ /** With `returning: { limit }`: `{ count, rows }` — the rows are read just before the delete (not atomic). */
243
+ deleteWhere(filter: Filter<TDoc> | AllRows, options: {
244
+ returning: {
245
+ limit: number;
246
+ };
247
+ }): Promise<MutationRows<Row<TDoc>>>;
199
248
  /** Soft-delete (set `deletedAt`); requires a `deletedAt` schema path. */
200
249
  softDelete(id: Id, idKey?: FieldKey<TDoc>): Promise<Row<TDoc> | undefined>;
201
250
  /** Restore a soft-deleted document (`deletedAt = null`). */
@@ -204,6 +253,7 @@ interface Repository<TDoc = Record<string, unknown>, TRel extends Record<string,
204
253
  aggregate(spec: AggregateSpec<TDoc>): Promise<AggregateRow[]>;
205
254
  scoped(scope: Scope<TDoc>): Repository<TDoc, TRel>;
206
255
  }
256
+
207
257
  /** {@link createRegistry} options — pagination defaults and bounds. */
208
258
  interface RegistryOptions {
209
259
  /** `findList` default page size (core `DEFAULT_PER_PAGE` = 20). */
@@ -232,7 +282,32 @@ interface RegistryOptions {
232
282
  * Called for every dropped condition (also when `strict` is off). Migration
233
283
  * path: watch with the hook first, flip `strict: true` once the log is clean.
234
284
  */
235
- onSkippedCondition?: (info: _querykitjs_core.SkippedCondition) => void;
285
+ onSkippedCondition?: (info: Core.SkippedCondition) => void;
286
+ /**
287
+ * Server-side request limits (core `DEFAULT_QUERY_LIMITS`) — enforced even when
288
+ * validation was bypassed. Past one ⇒ `QueryKitError` (`reason: "limit-exceeded"`).
289
+ * A repository's own `limits` win.
290
+ */
291
+ limits?: Partial<Core.QueryLimits>;
292
+ /**
293
+ * Time limit for every read (`find`, `findOne`, `countDocuments`, `aggregate`), in ms.
294
+ * Default `2000`. Past it MongoDB aborts the read and the repository throws
295
+ * `QueryKitTimeoutError` (`code: "QUERYKIT_TIMEOUT"`). A repository's own `maxTimeMS` wins.
296
+ */
297
+ maxTimeMS?: number;
298
+ /**
299
+ * Signs cursor tokens (HMAC-SHA256). With it a token that was not
300
+ * issued by this server — or was changed — is `invalid-value`.
301
+ */
302
+ cursorSecret?: string;
303
+ /**
304
+ * The createdAt / deletedAt field names (default `createdAt`, `deletedAt`): the default
305
+ * sort and the default `sortable` use `createdAt`, soft delete uses `deletedAt`.
306
+ */
307
+ timestamps?: {
308
+ createdAt?: string;
309
+ deletedAt?: string;
310
+ };
236
311
  }
237
312
  /** Extends a base repository with custom, model-specific methods (drizzle-pg style). */
238
313
  type RepositoryExtender<TDoc, TRel extends Record<string, unknown>, TExt extends Record<string, unknown>> = (base: Repository<TDoc, TRel>) => TExt;
@@ -281,10 +356,22 @@ interface RepoRuntime {
281
356
  strict: boolean;
282
357
  /** Called for every dropped condition (also when `strict` is off). */
283
358
  onSkippedCondition?: (info: SkippedCondition) => void;
359
+ /** Registry-level request limits; a repository's own `limits` win. */
360
+ limits?: Partial<QueryLimits>;
361
+ /** Read time limit in ms; a repository's own `maxTimeMS` wins. */
362
+ maxTimeMS: number;
363
+ /** Signs cursor tokens. */
364
+ cursorSecret?: string;
365
+ /** createdAt / deletedAt field names; default `createdAt`, `deletedAt`. */
366
+ timestamps?: {
367
+ createdAt?: string;
368
+ deletedAt?: string;
369
+ };
284
370
  }
371
+
285
372
  /**
286
373
  * Build a model-scoped repository. `runtime` (session getter + defaults) comes
287
- * from the registry, so this file never touches the connection directly.
374
+ * from the registry, so this module never touches the connection directly.
288
375
  */
289
376
  declare function buildRepository<TDoc>(runtime: RepoRuntime, model: Model<TDoc>, config?: RepositoryOptions<TDoc>): Repository<TDoc>;
290
377
 
@@ -299,29 +386,7 @@ declare function buildRepository<TDoc>(runtime: RepoRuntime, model: Model<TDoc>,
299
386
  * await usersRepo.findAll({ filter: uf.and(uf.eq("status", "active"), uf.gte("age", 18)) });
300
387
  * ```
301
388
  */
302
- declare function createFilters<TDoc>(): {
303
- eq: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
304
- ne: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
305
- gt: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
306
- gte: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
307
- lt: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
308
- lte: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
309
- contains: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
310
- startsWith: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
311
- endsWith: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
312
- like: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
313
- ilike: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
314
- notLike: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
315
- in: (key: FieldKey<TDoc>, value: Array<string | number>) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
316
- notIn: (key: FieldKey<TDoc>, value: Array<string | number>) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
317
- between: (key: FieldKey<TDoc>, min: _querykitjs_core.FilterScalar | Date, max: _querykitjs_core.FilterScalar | Date) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
318
- notBetween: (key: FieldKey<TDoc>, min: _querykitjs_core.FilterScalar | Date, max: _querykitjs_core.FilterScalar | Date) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
319
- range: (key: FieldKey<TDoc>, from: _querykitjs_core.FilterScalar | Date, to: _querykitjs_core.FilterScalar | Date) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>[];
320
- isNull: (key: FieldKey<TDoc>) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
321
- isNotNull: (key: FieldKey<TDoc>) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
322
- and: (...nodes: _querykitjs_core.FilterNode<FieldKey<TDoc>, RawFilter<_TDoc>>[]) => _querykitjs_core.AndGroup<FieldKey<TDoc>, RawFilter<_TDoc>>;
323
- or: (...nodes: _querykitjs_core.FilterNode<FieldKey<TDoc>, RawFilter<_TDoc>>[]) => _querykitjs_core.OrGroup<FieldKey<TDoc>, RawFilter<_TDoc>>;
324
- not: (node: _querykitjs_core.FilterNode<FieldKey<TDoc>, RawFilter<_TDoc>>) => _querykitjs_core.NotGroup<FieldKey<TDoc>, RawFilter<_TDoc>>;
325
- };
389
+ declare function createFilters<TDoc>(): ReturnType<typeof createFilters$1<FieldKey<TDoc>, RawFilter<TDoc>>>;
326
390
 
327
- export { type AggregateRow, type AggregateSpec, type AndGroup, type ByIdParams, type ColumnSelection, type CursorMeta, type CursorParams, type CursorResult, type FieldCondition, type FieldKey, type Filter, type FilterNode, type FilterOperator, type FilterScalar, type FilterValue, type Id, type InfiniteMeta, type InfiniteParams, type InfiniteResult, type Insert, type LooseFieldKey, type NotGroup, type OffsetMeta, type OffsetParams, type OffsetResult, type OrGroup, type QueryParams, type RawFilter, type Registry, type RegistryOptions, type RelationDocs, type RelationMap, type RepoRuntime, type Repository, type RepositoryExtender, type RepositoryOptions, type Result, type Row, type Scope, type Sort, type SortDirection, type SortItem, type UpsertOptions, type WithRelations, buildRepository, createFilters, createRegistry };
391
+ export { buildRepository, createFilters, createRegistry };
392
+ export type { AggregateRow, AggregateSpec, AndGroup, ByIdParams, ColumnSelection, CursorMeta, CursorParams, CursorResult, FieldCondition, FieldKey, Filter, FilterNode, FilterOperator, FilterScalar, FilterValue, Id, InfiniteMeta, InfiniteParams, InfiniteResult, Insert, LooseFieldKey, NotGroup, OffsetMeta, OffsetParams, OffsetResult, OrGroup, QueryParams, RawFilter, Registry, RegistryOptions, RelationDocs, RelationMap, RepoRuntime, Repository, RepositoryExtender, RepositoryOptions, Result, Row, Scope, Sort, SortDirection, SortItem, UpsertOptions, WithRelations };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Types, Model, Connection, ClientSession } from 'mongoose';
2
- import * as _querykitjs_core from '@querykitjs/core';
3
- import { SkippedCondition } from '@querykitjs/core';
2
+ import * as Core from '@querykitjs/core';
3
+ import { AllRows, MutationCount, MutationRows, RelationRule, SkippedCondition, QueryLimits, createFilters as createFilters$1 } from '@querykitjs/core';
4
4
  export { f } from '@querykitjs/core';
5
5
 
6
6
  /**
@@ -38,19 +38,19 @@ type FieldKey<TDoc> = keyof TDoc & string;
38
38
  * validated payload can go straight into a repository call with no `as` cast.
39
39
  * Autocomplete still suggests the document's real fields.
40
40
  */
41
- type LooseFieldKey<TDoc> = _querykitjs_core.LooseKey<FieldKey<TDoc>>;
42
- type FilterOperator = _querykitjs_core.FilterOperator;
43
- type FilterScalar = _querykitjs_core.FilterScalar;
44
- type FilterValue = _querykitjs_core.FilterValue;
45
- type FieldCondition<TDoc = Record<string, unknown>> = _querykitjs_core.FieldCondition<LooseFieldKey<TDoc>>;
46
- type AndGroup<TDoc = Record<string, unknown>> = _querykitjs_core.AndGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
47
- type OrGroup<TDoc = Record<string, unknown>> = _querykitjs_core.OrGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
48
- type NotGroup<TDoc = Record<string, unknown>> = _querykitjs_core.NotGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
49
- type FilterNode<TDoc = Record<string, unknown>> = _querykitjs_core.FilterNode<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
50
- type Filter<TDoc = Record<string, unknown>> = _querykitjs_core.Filter<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
51
- type SortDirection = _querykitjs_core.SortDirection;
41
+ type LooseFieldKey<TDoc> = Core.LooseKey<FieldKey<TDoc>>;
42
+ type FilterOperator = Core.FilterOperator;
43
+ type FilterScalar = Core.FilterScalar;
44
+ type FilterValue = Core.FilterValue;
45
+ type FieldCondition<TDoc = Record<string, unknown>> = Core.FieldCondition<LooseFieldKey<TDoc>>;
46
+ type AndGroup<TDoc = Record<string, unknown>> = Core.AndGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
47
+ type OrGroup<TDoc = Record<string, unknown>> = Core.OrGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
48
+ type NotGroup<TDoc = Record<string, unknown>> = Core.NotGroup<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
49
+ type FilterNode<TDoc = Record<string, unknown>> = Core.FilterNode<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
50
+ type Filter<TDoc = Record<string, unknown>> = Core.Filter<LooseFieldKey<TDoc>, RawFilter<TDoc>>;
51
+ type SortDirection = Core.SortDirection;
52
52
  /** One sort field by typed field key (`{ key, direction }`) — autocomplete. */
53
- type SortItem<TDoc = Record<string, unknown>> = _querykitjs_core.SortItem<LooseFieldKey<TDoc>>;
53
+ type SortItem<TDoc = Record<string, unknown>> = Core.SortItem<LooseFieldKey<TDoc>>;
54
54
  /**
55
55
  * Sort — **always an array** of `{ key, direction }` (multi-field). This is the
56
56
  * exact shape the querykit frontend sends (`@querykitjs/web` decodes its
@@ -69,22 +69,25 @@ interface QueryParams<TDoc = Record<string, unknown>> {
69
69
  withDeleted?: boolean;
70
70
  }
71
71
  /** Equality scope applied to every operation of a scoped repository. */
72
- type Scope<TDoc = Record<string, unknown>> = _querykitjs_core.Scope<FieldKey<TDoc>>;
72
+ type Scope<TDoc = Record<string, unknown>> = Core.Scope<FieldKey<TDoc>>;
73
73
  interface ByIdParams<TDoc = Record<string, unknown>> extends QueryParams<TDoc> {
74
74
  /** Field to match against the id. Defaults to `"id"` (→ `_id`). */
75
75
  idKey?: LooseFieldKey<TDoc>;
76
76
  }
77
77
  /** Options for {@link Repository.upsert} / {@link Repository.upsertMany}. */
78
- type UpsertOptions<TDoc = Record<string, unknown>> = _querykitjs_core.UpsertOptions<FieldKey<TDoc>, Insert<TDoc>>;
78
+ type UpsertOptions<TDoc = Record<string, unknown>> = Core.UpsertOptions<FieldKey<TDoc>, Insert<TDoc>>;
79
79
  /** Aggregate query specification (`count`/`sum`/`avg`/`min`/`max` + `groupBy`). */
80
- type AggregateSpec<TDoc = Record<string, unknown>> = _querykitjs_core.AggregateSpec<FieldKey<TDoc>, Filter<TDoc>>;
80
+ type AggregateSpec<TDoc = Record<string, unknown>> = Core.AggregateSpec<FieldKey<TDoc>, Filter<TDoc>>;
81
81
  /** One aggregated result row (group fields + aggregate values). */
82
82
  type AggregateRow = Record<string, unknown>;
83
+
83
84
  interface OffsetParams<TDoc = Record<string, unknown>> extends QueryParams<TDoc> {
84
85
  page?: number;
85
86
  perPage?: number;
87
+ /** `false` — no count query: `perPage + 1` rows are read, `total_items` / `total_pages` are `null`. Default `true`. */
88
+ withTotal?: boolean;
86
89
  }
87
- type OffsetMeta = _querykitjs_core.OffsetMeta;
90
+ type OffsetMeta = Core.OffsetMeta;
88
91
  interface OffsetResult<T> {
89
92
  data: T[];
90
93
  meta: OffsetMeta;
@@ -93,7 +96,7 @@ interface InfiniteParams<TDoc = Record<string, unknown>> extends QueryParams<TDo
93
96
  limit?: number;
94
97
  offset?: number;
95
98
  }
96
- type InfiniteMeta = _querykitjs_core.InfiniteMeta;
99
+ type InfiniteMeta = Core.InfiniteMeta;
97
100
  interface InfiniteResult<T> {
98
101
  data: T[];
99
102
  meta: InfiniteMeta;
@@ -106,14 +109,23 @@ interface CursorParams<TDoc = Record<string, unknown>> extends QueryParams<TDoc>
106
109
  order?: SortDirection;
107
110
  direction?: "forward" | "backward";
108
111
  }
109
- type CursorMeta = _querykitjs_core.CursorMeta;
112
+ type CursorMeta = Core.CursorMeta;
110
113
  interface CursorResult<T> {
111
114
  data: T[];
112
115
  meta: CursorMeta;
113
116
  }
114
- /** Map of relation field name → the Mongoose model it refers to. */
115
- type RelationMap = Record<string, Model<any>>;
116
- type DocOf<M> = M extends Model<infer T> ? T : never;
117
+
118
+ /**
119
+ * Relation field → its rule (R21): `{ model?, columns, filterable?, nested? }` opens it for a
120
+ * client `with` (`columns` required — a list or `"*"`). A bare model (`{ author: User }`) still types
121
+ * `with`, but opens nothing: `with: { author: … }` is `forbidden-key`, `site: "with"`.
122
+ */
123
+ type RelationMap = Record<string, Model<any> | (RelationRule & {
124
+ model?: Model<any>;
125
+ })>;
126
+ type DocOf<M> = M extends Model<infer T> ? T : M extends {
127
+ model: Model<infer T>;
128
+ } ? T : Record<string, unknown>;
117
129
  /** Resolve a {@link RelationMap} to `{ field: <its document type> }`. */
118
130
  type RelationDocs<TRel extends RelationMap> = {
119
131
  [K in keyof TRel]: DocOf<TRel[K]>;
@@ -133,7 +145,7 @@ type RelationDocs<TRel extends RelationMap> = {
133
145
  * result to a client.
134
146
  */
135
147
  interface RepositoryOptions<TDoc = any, TRel extends RelationMap = RelationMap> {
136
- /** Declare populatable relations (field → model) so `with` types the result. */
148
+ /** Populatable relations: field → `{ model?, columns, filterable?, nested? }` (R21), or a bare model (typing only). */
137
149
  relations?: TRel;
138
150
  /** Constant equality filter added to every read/write (RBAC / multi-tenancy). */
139
151
  scope?: Scope<TDoc>;
@@ -149,6 +161,23 @@ interface RepositoryOptions<TDoc = any, TRel extends RelationMap = RelationMap>
149
161
  * An empty array throws (a projection selecting nothing means "everything").
150
162
  */
151
163
  allowedColumns?: readonly FieldKey<TDoc>[];
164
+ /**
165
+ * Paths a client may filter by, and the operators for each (`"*"` — all). A path
166
+ * not listed ⇒ `reason: "forbidden-key"`, an operator not listed ⇒
167
+ * `"forbidden-operator"` — a `QueryKitError` whatever `strict` says, before the
168
+ * query runs. **When not given** — `allowedColumns` (else the `forcedColumns` keys,
169
+ * else only `_id`) with `"="` and `"in"` only. `aggregate` paths come from this list too.
170
+ */
171
+ filterable?: Core.Filterable<FieldKey<TDoc> | "_id">;
172
+ /**
173
+ * Paths a client may sort (and use as `cursorKey`) by. When not given — `_id` and
174
+ * `createdAt` (when the schema has it). Anything else ⇒ `reason: "forbidden-key"`.
175
+ */
176
+ sortable?: readonly (FieldKey<TDoc> | "_id")[];
177
+ /** Request limits for this repository — over the registry's `limits`. */
178
+ limits?: Partial<Core.QueryLimits>;
179
+ /** Read time limit for this repository, in ms — over the registry's `maxTimeMS`. */
180
+ maxTimeMS?: number;
152
181
  }
153
182
  type TrueKeys<T> = {
154
183
  [K in keyof T]: T[K] extends true ? K : never;
@@ -190,12 +219,32 @@ interface Repository<TDoc = Record<string, unknown>, TRel extends Record<string,
190
219
  upsertMany(values: Insert<TDoc>[], options: UpsertOptions<TDoc>): Promise<Row<TDoc>[]>;
191
220
  /** Update one document by id; returns the updated doc (or undefined). */
192
221
  updateById(id: Id, patch: Partial<Insert<TDoc>>, idKey?: FieldKey<TDoc>): Promise<Row<TDoc> | undefined>;
193
- /** Update all documents matching the filter; returns the updated docs. */
194
- updateWhere(filter: Filter<TDoc>, patch: Partial<Insert<TDoc>>): Promise<Row<TDoc>[]>;
222
+ /**
223
+ * Update every document matching the filter in one `updateMany` → `{ count }` (matched).
224
+ * No filter only with `{ all: true }` (the scope still applies); an empty or fully skipped
225
+ * filter is `QueryKitError` `empty-filter`. The filter passes the request policy like a read.
226
+ */
227
+ updateWhere(filter: Filter<TDoc> | AllRows, patch: Partial<Insert<TDoc>>, options?: {
228
+ returning?: false;
229
+ }): Promise<MutationCount>;
230
+ /** With `returning: { limit }`: `{ count, rows }` — the rows are read right after the update with the same filter (not atomic). */
231
+ updateWhere(filter: Filter<TDoc> | AllRows, patch: Partial<Insert<TDoc>>, options: {
232
+ returning: {
233
+ limit: number;
234
+ };
235
+ }): Promise<MutationRows<Row<TDoc>>>;
195
236
  /** Hard-delete one document by id; returns the deleted doc (or undefined). */
196
237
  deleteById(id: Id, idKey?: FieldKey<TDoc>): Promise<Row<TDoc> | undefined>;
197
- /** Hard-delete all documents matching the filter; returns the deleted docs. */
198
- deleteWhere(filter: Filter<TDoc>): Promise<Row<TDoc>[]>;
238
+ /** Hard-delete every document matching the filter in one `deleteMany` → `{ count }` (same rules as `updateWhere`). */
239
+ deleteWhere(filter: Filter<TDoc> | AllRows, options?: {
240
+ returning?: false;
241
+ }): Promise<MutationCount>;
242
+ /** With `returning: { limit }`: `{ count, rows }` — the rows are read just before the delete (not atomic). */
243
+ deleteWhere(filter: Filter<TDoc> | AllRows, options: {
244
+ returning: {
245
+ limit: number;
246
+ };
247
+ }): Promise<MutationRows<Row<TDoc>>>;
199
248
  /** Soft-delete (set `deletedAt`); requires a `deletedAt` schema path. */
200
249
  softDelete(id: Id, idKey?: FieldKey<TDoc>): Promise<Row<TDoc> | undefined>;
201
250
  /** Restore a soft-deleted document (`deletedAt = null`). */
@@ -204,6 +253,7 @@ interface Repository<TDoc = Record<string, unknown>, TRel extends Record<string,
204
253
  aggregate(spec: AggregateSpec<TDoc>): Promise<AggregateRow[]>;
205
254
  scoped(scope: Scope<TDoc>): Repository<TDoc, TRel>;
206
255
  }
256
+
207
257
  /** {@link createRegistry} options — pagination defaults and bounds. */
208
258
  interface RegistryOptions {
209
259
  /** `findList` default page size (core `DEFAULT_PER_PAGE` = 20). */
@@ -232,7 +282,32 @@ interface RegistryOptions {
232
282
  * Called for every dropped condition (also when `strict` is off). Migration
233
283
  * path: watch with the hook first, flip `strict: true` once the log is clean.
234
284
  */
235
- onSkippedCondition?: (info: _querykitjs_core.SkippedCondition) => void;
285
+ onSkippedCondition?: (info: Core.SkippedCondition) => void;
286
+ /**
287
+ * Server-side request limits (core `DEFAULT_QUERY_LIMITS`) — enforced even when
288
+ * validation was bypassed. Past one ⇒ `QueryKitError` (`reason: "limit-exceeded"`).
289
+ * A repository's own `limits` win.
290
+ */
291
+ limits?: Partial<Core.QueryLimits>;
292
+ /**
293
+ * Time limit for every read (`find`, `findOne`, `countDocuments`, `aggregate`), in ms.
294
+ * Default `2000`. Past it MongoDB aborts the read and the repository throws
295
+ * `QueryKitTimeoutError` (`code: "QUERYKIT_TIMEOUT"`). A repository's own `maxTimeMS` wins.
296
+ */
297
+ maxTimeMS?: number;
298
+ /**
299
+ * Signs cursor tokens (HMAC-SHA256). With it a token that was not
300
+ * issued by this server — or was changed — is `invalid-value`.
301
+ */
302
+ cursorSecret?: string;
303
+ /**
304
+ * The createdAt / deletedAt field names (default `createdAt`, `deletedAt`): the default
305
+ * sort and the default `sortable` use `createdAt`, soft delete uses `deletedAt`.
306
+ */
307
+ timestamps?: {
308
+ createdAt?: string;
309
+ deletedAt?: string;
310
+ };
236
311
  }
237
312
  /** Extends a base repository with custom, model-specific methods (drizzle-pg style). */
238
313
  type RepositoryExtender<TDoc, TRel extends Record<string, unknown>, TExt extends Record<string, unknown>> = (base: Repository<TDoc, TRel>) => TExt;
@@ -281,10 +356,22 @@ interface RepoRuntime {
281
356
  strict: boolean;
282
357
  /** Called for every dropped condition (also when `strict` is off). */
283
358
  onSkippedCondition?: (info: SkippedCondition) => void;
359
+ /** Registry-level request limits; a repository's own `limits` win. */
360
+ limits?: Partial<QueryLimits>;
361
+ /** Read time limit in ms; a repository's own `maxTimeMS` wins. */
362
+ maxTimeMS: number;
363
+ /** Signs cursor tokens. */
364
+ cursorSecret?: string;
365
+ /** createdAt / deletedAt field names; default `createdAt`, `deletedAt`. */
366
+ timestamps?: {
367
+ createdAt?: string;
368
+ deletedAt?: string;
369
+ };
284
370
  }
371
+
285
372
  /**
286
373
  * Build a model-scoped repository. `runtime` (session getter + defaults) comes
287
- * from the registry, so this file never touches the connection directly.
374
+ * from the registry, so this module never touches the connection directly.
288
375
  */
289
376
  declare function buildRepository<TDoc>(runtime: RepoRuntime, model: Model<TDoc>, config?: RepositoryOptions<TDoc>): Repository<TDoc>;
290
377
 
@@ -299,29 +386,7 @@ declare function buildRepository<TDoc>(runtime: RepoRuntime, model: Model<TDoc>,
299
386
  * await usersRepo.findAll({ filter: uf.and(uf.eq("status", "active"), uf.gte("age", 18)) });
300
387
  * ```
301
388
  */
302
- declare function createFilters<TDoc>(): {
303
- eq: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
304
- ne: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
305
- gt: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
306
- gte: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
307
- lt: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
308
- lte: (key: FieldKey<TDoc>, value: _querykitjs_core.FilterValue) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
309
- contains: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
310
- startsWith: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
311
- endsWith: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
312
- like: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
313
- ilike: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
314
- notLike: (key: FieldKey<TDoc>, value: string) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
315
- in: (key: FieldKey<TDoc>, value: Array<string | number>) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
316
- notIn: (key: FieldKey<TDoc>, value: Array<string | number>) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
317
- between: (key: FieldKey<TDoc>, min: _querykitjs_core.FilterScalar | Date, max: _querykitjs_core.FilterScalar | Date) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
318
- notBetween: (key: FieldKey<TDoc>, min: _querykitjs_core.FilterScalar | Date, max: _querykitjs_core.FilterScalar | Date) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
319
- range: (key: FieldKey<TDoc>, from: _querykitjs_core.FilterScalar | Date, to: _querykitjs_core.FilterScalar | Date) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>[];
320
- isNull: (key: FieldKey<TDoc>) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
321
- isNotNull: (key: FieldKey<TDoc>) => _querykitjs_core.FieldCondition<FieldKey<TDoc>>;
322
- and: (...nodes: _querykitjs_core.FilterNode<FieldKey<TDoc>, RawFilter<_TDoc>>[]) => _querykitjs_core.AndGroup<FieldKey<TDoc>, RawFilter<_TDoc>>;
323
- or: (...nodes: _querykitjs_core.FilterNode<FieldKey<TDoc>, RawFilter<_TDoc>>[]) => _querykitjs_core.OrGroup<FieldKey<TDoc>, RawFilter<_TDoc>>;
324
- not: (node: _querykitjs_core.FilterNode<FieldKey<TDoc>, RawFilter<_TDoc>>) => _querykitjs_core.NotGroup<FieldKey<TDoc>, RawFilter<_TDoc>>;
325
- };
389
+ declare function createFilters<TDoc>(): ReturnType<typeof createFilters$1<FieldKey<TDoc>, RawFilter<TDoc>>>;
326
390
 
327
- export { type AggregateRow, type AggregateSpec, type AndGroup, type ByIdParams, type ColumnSelection, type CursorMeta, type CursorParams, type CursorResult, type FieldCondition, type FieldKey, type Filter, type FilterNode, type FilterOperator, type FilterScalar, type FilterValue, type Id, type InfiniteMeta, type InfiniteParams, type InfiniteResult, type Insert, type LooseFieldKey, type NotGroup, type OffsetMeta, type OffsetParams, type OffsetResult, type OrGroup, type QueryParams, type RawFilter, type Registry, type RegistryOptions, type RelationDocs, type RelationMap, type RepoRuntime, type Repository, type RepositoryExtender, type RepositoryOptions, type Result, type Row, type Scope, type Sort, type SortDirection, type SortItem, type UpsertOptions, type WithRelations, buildRepository, createFilters, createRegistry };
391
+ export { buildRepository, createFilters, createRegistry };
392
+ export type { AggregateRow, AggregateSpec, AndGroup, ByIdParams, ColumnSelection, CursorMeta, CursorParams, CursorResult, FieldCondition, FieldKey, Filter, FilterNode, FilterOperator, FilterScalar, FilterValue, Id, InfiniteMeta, InfiniteParams, InfiniteResult, Insert, LooseFieldKey, NotGroup, OffsetMeta, OffsetParams, OffsetResult, OrGroup, QueryParams, RawFilter, Registry, RegistryOptions, RelationDocs, RelationMap, RepoRuntime, Repository, RepositoryExtender, RepositoryOptions, Result, Row, Scope, Sort, SortDirection, SortItem, UpsertOptions, WithRelations };