@querykitjs/mongoose 2.0.0 → 3.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.en.md +168 -22
- package/README.md +169 -22
- package/dist/index.cjs +1 -610
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -55
- package/dist/index.d.ts +120 -55
- package/dist/index.js +1 -603
- package/dist/index.js.map +1 -1
- package/package.json +18 -9
- package/skills/querykit-mongoose/SKILL.md +189 -0
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Types, Model, Connection, ClientSession } from 'mongoose';
|
|
2
|
-
import * as
|
|
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> =
|
|
42
|
-
type FilterOperator =
|
|
43
|
-
type FilterScalar =
|
|
44
|
-
type FilterValue =
|
|
45
|
-
type FieldCondition<TDoc = Record<string, unknown>> =
|
|
46
|
-
type AndGroup<TDoc = Record<string, unknown>> =
|
|
47
|
-
type OrGroup<TDoc = Record<string, unknown>> =
|
|
48
|
-
type NotGroup<TDoc = Record<string, unknown>> =
|
|
49
|
-
type FilterNode<TDoc = Record<string, unknown>> =
|
|
50
|
-
type Filter<TDoc = Record<string, unknown>> =
|
|
51
|
-
type 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>> =
|
|
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>> =
|
|
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>> =
|
|
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>> =
|
|
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` (task 013). Default `true`. */
|
|
88
|
+
withTotal?: boolean;
|
|
86
89
|
}
|
|
87
|
-
type 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 =
|
|
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 =
|
|
112
|
+
type CursorMeta = Core.CursorMeta;
|
|
110
113
|
interface CursorResult<T> {
|
|
111
114
|
data: T[];
|
|
112
115
|
meta: CursorMeta;
|
|
113
116
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Relation field → its rule (task 014, 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
|
-
/**
|
|
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
|
-
/**
|
|
194
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Update every document matching the filter in one `updateMany` → `{ count }` (matched, task 013).
|
|
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
|
|
198
|
-
deleteWhere(filter: Filter<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:
|
|
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, task 012). 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 (task 014; 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 (task 012). */
|
|
364
|
+
cursorSecret?: string;
|
|
365
|
+
/** createdAt / deletedAt field names (task 014); 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
|
|
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 {
|
|
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
|
|
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> =
|
|
42
|
-
type FilterOperator =
|
|
43
|
-
type FilterScalar =
|
|
44
|
-
type FilterValue =
|
|
45
|
-
type FieldCondition<TDoc = Record<string, unknown>> =
|
|
46
|
-
type AndGroup<TDoc = Record<string, unknown>> =
|
|
47
|
-
type OrGroup<TDoc = Record<string, unknown>> =
|
|
48
|
-
type NotGroup<TDoc = Record<string, unknown>> =
|
|
49
|
-
type FilterNode<TDoc = Record<string, unknown>> =
|
|
50
|
-
type Filter<TDoc = Record<string, unknown>> =
|
|
51
|
-
type 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>> =
|
|
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>> =
|
|
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>> =
|
|
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>> =
|
|
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` (task 013). Default `true`. */
|
|
88
|
+
withTotal?: boolean;
|
|
86
89
|
}
|
|
87
|
-
type 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 =
|
|
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 =
|
|
112
|
+
type CursorMeta = Core.CursorMeta;
|
|
110
113
|
interface CursorResult<T> {
|
|
111
114
|
data: T[];
|
|
112
115
|
meta: CursorMeta;
|
|
113
116
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Relation field → its rule (task 014, 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
|
-
/**
|
|
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
|
-
/**
|
|
194
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Update every document matching the filter in one `updateMany` → `{ count }` (matched, task 013).
|
|
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
|
|
198
|
-
deleteWhere(filter: Filter<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:
|
|
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, task 012). 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 (task 014; 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 (task 012). */
|
|
364
|
+
cursorSecret?: string;
|
|
365
|
+
/** createdAt / deletedAt field names (task 014); 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
|
|
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 {
|
|
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 };
|