@nxgt/mongo 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,12 +2,13 @@
2
2
 
3
3
  A typed MongoDB collection, from one Zod schema: the schema types every read
4
4
  and write, and the same schema becomes the collection's `$jsonSchema`
5
- validator, applied idempotently. On top of it, a repository with pagination,
5
+ validator, applied idempotently. On top of it, a collection with pagination,
6
6
  transactions, optimistic locking, soft delete, audit stamps, and MongoDB's
7
7
  errors turned into ones you can catch.
8
8
 
9
- It wraps the official `mongodb` driver, which stays a peer dependency: the
10
- driver's collection is one property away at any time.
9
+ It wraps the official `mongodb` driver, which stays a peer dependency — and it
10
+ does not hide it: the driver's own methods are on the very same object, so
11
+ `aggregate`, `watch` and `bulkWrite` are always at hand.
11
12
 
12
13
  ## Install
13
14
 
@@ -31,6 +32,7 @@ export const users = defineCollection({
31
32
  _id: id(),
32
33
  email: z.email(),
33
34
  name: z.string().nullable().default(null),
35
+ loginCount: z.int().default(0),
34
36
  ...timestamps(),
35
37
  ...softDelete(),
36
38
  ...optimisticLock(),
@@ -46,8 +48,23 @@ const db = client.db('app');
46
48
  ## Definition
47
49
 
48
50
  `defineCollection` takes the collection's name, the Zod schema of its
49
- documents, its indexes as the driver describes them, and how its validator is
50
- applied.
51
+ documents, its indexes, and how its validator is applied.
52
+
53
+ An index is keyed on the schema's own fields, so an editor completes them and a
54
+ typo does not compile. A path into a field is allowed too, since that is how
55
+ MongoDB indexes a nested key. Everything else is the driver's own
56
+ `IndexDescription` — `unique`, `name`, `collation`, `expireAfterSeconds`,
57
+ `partialFilterExpression`:
58
+
59
+ ```ts
60
+ indexes: [
61
+ { key: { email: 1 }, unique: true, name: 'users_email_unique' },
62
+ { key: { createdAt: -1 } },
63
+ { key: { 'address.city': 1 } },
64
+ // @ts-expect-error there is no such field
65
+ { key: { emial: 1 } },
66
+ ]
67
+ ```
51
68
 
52
69
  The schema is the one source of truth. `z.output` is what a read gives back,
53
70
  `z.input` what a write takes: a field with a default — `_id`, `createdAt`,
@@ -94,34 +111,87 @@ already full.
94
111
  ## Documents
95
112
 
96
113
  ```ts
97
- import { createRepository } from '@nxgt/mongo';
114
+ import { getCollection } from '@nxgt/mongo';
98
115
 
99
- const repo = createRepository(db, users);
116
+ const collection = getCollection(db, users); // a Db, or a MongoClient
100
117
 
101
- const ada = await repo.create({ email: 'ada@example.com' });
102
- // → { _id: ObjectId, email, name: null, createdAt: Date, version: 0, … }
118
+ const ada = await collection.create({ email: 'ada@example.com' });
119
+ // → { _id: ObjectId, id: '507f…', email, name: null, createdAt: Date, version: 0, … }
103
120
 
104
- await repo.findById(ada._id); // the document, or undefined
105
- await repo.getById(ada._id); // or NotFoundError
106
- await repo.findFirst({ email: 'ada@example.com' });
107
- await repo.findMany({ filter: { name: null }, sort: { createdAt: -1 }, limit: 10 });
108
- await repo.count({ name: null });
109
- await repo.exists({ email: 'ada@example.com' });
121
+ await collection.findById(ada._id); // the document, or undefined
122
+ await collection.getById(ada._id); // or NotFoundError
123
+ await collection.findFirst({ email: 'ada@example.com' });
124
+ await collection.findMany({ filter: { name: null }, sort: { createdAt: -1 }, limit: 10 });
125
+ await collection.count({ name: null });
126
+ await collection.exists({ email: 'ada@example.com' });
110
127
 
111
- await repo.update(ada._id, { name: 'Ada' }); // checked field by field
112
- await repo.update(ada._id, { $inc: { logins: 1 } }); // MongoDB's operators too
113
- await repo.updateMany({ name: null }, { name: 'unknown' });
128
+ await collection.update(ada._id, { name: 'Ada' }); // checked field by field
129
+ await collection.update(ada._id, { $inc: { loginCount: 1 } }); // MongoDB's operators too
130
+ await collection.updateMany({ name: null }, { name: 'unknown' });
114
131
 
115
- await repo.delete(ada._id); // soft, on a schema with deletedAt
116
- await repo.restore(ada._id);
117
- await repo.hardDelete(ada._id); // really gone
132
+ await collection.delete(ada._id); // soft, on a schema with deletedAt
133
+ await collection.restore(ada._id);
134
+ await collection.hardDelete(ada._id); // really gone
135
+ ```
136
+
137
+ **The driver's collection is the same object.** Everything this package does
138
+ not wrap is on it directly — no `.collection` to go through:
139
+
140
+ ```ts
141
+ await collection.aggregate([{ $group: { _id: '$teamId', n: { $sum: 1 } } }]).toArray();
142
+ collection.watch();
143
+ await collection.distinct('email');
144
+ await collection.bulkWrite([…]);
145
+ collection.collectionName; // 'users'
118
146
  ```
119
147
 
148
+ Three names are defined by both, and this package's win, because a filter that
149
+ came out empty must not rewrite a collection: `count`, `updateMany` and
150
+ `deleteMany` return a number and require a filter. The driver's own are on
151
+ `raw`, which is its `Collection`, untouched:
152
+
153
+ ```ts
154
+ await collection.updateMany({ name: null }, { name: 'x' }); // → number
155
+ await collection.raw.updateMany({}, { $set: { name: 'x' } }); // → UpdateResult
156
+ ```
157
+
158
+ `raw` is also the way out for an update operator this package does not name.
159
+
120
160
  `create` checks the document against the schema before sending it, which is
121
161
  also what fills its defaults. `update` checks each field of a patch — the
122
162
  driver's own `UpdateFilter` is intersected with `Document` and accepts any key
123
163
  whatsoever, including a typo.
124
164
 
165
+ ## Ids
166
+
167
+ Every document a repository gives back carries `id`: its `_id` as a string. It
168
+ is computed, never stored — the collection holds `_id` alone — and it is an
169
+ ordinary enumerable property, so `JSON.stringify` and a spread carry it and a
170
+ handler can return the document as it is.
171
+
172
+ Because it is not a stored field, nothing can be filtered or patched on it: the
173
+ server would match nothing, and TypeScript refuses it. To go the other way,
174
+ from a string that arrived over HTTP:
175
+
176
+ ```ts
177
+ import { toObjectId, tryObjectId, isValidObjectId, objectIdParam } from '@nxgt/mongo';
178
+
179
+ await repo.getById(toObjectId(params.id)); // an ObjectId, or InvalidIdError
180
+ tryObjectId(params.id); // an ObjectId, or undefined
181
+ isValidObjectId(params.id); // a boolean
182
+ toObjectIds(query.ids); // for a `$in` filter
183
+
184
+ // Or as part of a schema, where the parameters are parsed:
185
+ const route = z.object({ id: objectIdParam() });
186
+ const { id } = route.parse(params); // ObjectId
187
+ ```
188
+
189
+ **Do not call `new ObjectId(value)` on input you did not produce.** Given
190
+ `null` or `undefined` the driver does not throw: it invents a fresh id, so a
191
+ parameter that never arrived becomes a perfectly valid id that matches nothing.
192
+ `toObjectId` throws `InvalidIdError`, which a handler can turn into a 400 or a
193
+ 404.
194
+
125
195
  ## Pagination
126
196
 
127
197
  ```ts
@@ -147,15 +217,20 @@ document is repeated or skipped while the collection is written to, where
147
217
  import { withTransaction } from '@nxgt/mongo';
148
218
 
149
219
  await withTransaction(client, async (session) => {
150
- const team = await teams.with(session).create({ name: 'Core' });
151
- await users.with(session).update(userId, { teamId: team._id });
220
+ const team = await teams.withSession(session).create({ name: 'Core' });
221
+ await users.withSession(session).update(userId, { teamId: team._id });
152
222
  });
153
223
  ```
154
224
 
155
225
  **Every operation has to be given the session.** MongoDB has no ambient
156
226
  session: a write that was not given one runs outside the transaction and is not
157
- rolled back with it. `repository.with(session)` is how a repository takes it,
158
- and it returns a new repository rather than changing the one you have.
227
+ rolled back with it. `collection.withSession(session)` is how a collection
228
+ takes it, and it returns a new collection rather than changing the one you
229
+ have.
230
+
231
+ `withSession` binds **this package's** methods. A driver method on the same
232
+ object — `aggregate`, `bulkWrite`, `countDocuments` — takes its session the
233
+ driver's way, in its options: `collection.aggregate(pipeline, { session })`.
159
234
 
160
235
  Given a session that is already in a transaction, `withTransaction` joins it.
161
236
  MongoDB has no savepoints, so an inner failure takes the whole transaction
@@ -190,6 +265,7 @@ application never reads a numeric code:
190
265
  | `ValidationError` | `VALIDATION` | the collection's validator refused it (121) |
191
266
  | `OptimisticLockError` | `OPTIMISTIC_LOCK` | `expectedVersion` no longer matches |
192
267
  | `InvalidCursorError` | `INVALID_CURSOR` | a cursor this package did not write |
268
+ | `InvalidIdError` | `INVALID_ID` | a value that is no `ObjectId`, nor the string of one |
193
269
  | `DataError` | `DATABASE` | any other server error, with its `serverCode` |
194
270
 
195
271
  `ConflictError` carries `index`, `keys` and, when the server gives them,
@@ -211,7 +287,9 @@ server error reaches you untouched.
211
287
  | --- | --- |
212
288
  | `defineCollection(config)` | a collection: name, schema, indexes, validation |
213
289
  | `id`, `objectId`, `timestamps`, `softDelete`, `optimisticLock`, `actors` | the field helpers |
214
- | `createRepository(db, definition, options?)` | the typed repository |
290
+ | `toObjectId`, `toObjectIds`, `tryObjectId`, `objectIdParam` | a string from outside as an `ObjectId` |
291
+ | `isValidObjectId`, `isObjectIdString`, `isObjectId` | the checks behind them |
292
+ | `getCollection(dbOrClient, definition, options?)` | the typed collection, driver methods included |
215
293
  | `syncCollection`, `syncCollections` | create and bring in line, with `dryRun` |
216
294
  | `withTransaction(clientOrSession, fn, options?)` | a transaction, joined when nested |
217
295
  | `toMongoJsonSchema(schema)` | a Zod schema as a MongoDB `$jsonSchema` |
@@ -219,14 +297,47 @@ server error reaches you untouched.
219
297
  | `DataError` and its subclasses, `toDataError` | the errors |
220
298
  | `diffIndexes`, `normalizeIndex`, `validationMatches` | what `sync` compares with |
221
299
 
222
- `RepositoryOptions` turns the behaviours off one by one: `softDelete`,
223
- `touchUpdatedAt`, `optimisticLock`, `validate: 'off'`, `maxPageSize`.
300
+ `CollectionOptions` turns the behaviours off one by one: `softDelete`,
301
+ `touchUpdatedAt`, `optimisticLock`, `validate: 'off'`, `maxPageSize`, and it
302
+ names the database with `db` when you pass a client.
303
+
304
+ ## What does not compile
305
+
306
+ The schema types more than the documents. These are compile errors, each one
307
+ kept as a test in `test/types/strictness.ts`:
308
+
309
+ ```ts
310
+ await collection.findMany({ sort: { nope: 1 } }); // no such field
311
+ await collection.findMany({ sort: { email: 'up' } }); // not a direction
312
+ await collection.findMany({ projection: { nope: 1 } }); // no such field
313
+ await collection.findMany({ projection: { email: 2 } }); // 0, 1 or an operator
314
+ await collection.update(id, { $set: { nope: 1 } }); // no such field
315
+ await collection.update(id, { $set: { email: 1 } }); // email is a string
316
+ await collection.update(id, { $inc: { email: 1 } }); // not a numeric field
317
+ await collection.update(id, { $push: { title: 'x' } }); // not an array field
318
+ await collection.update(id, { id: 'abc' }); // id is computed
319
+ collection.as('not-an-object-id'); // the schema types the actor
320
+ posts.as(someone); // posts stamp no actor
321
+ ```
322
+
323
+ What is **not** checked: the tail of a dotted path, and a `filter`, which stays
324
+ the driver's `Filter` — rebuilding it would mean reimplementing every query
325
+ operator, and getting it subtly wrong is worse than being honest about it.
224
326
 
225
327
  ## Traps
226
328
 
227
329
  - **There is no ambient session.** An operation inside `withTransaction` that
228
330
  was not given the session is not part of the transaction. Use
229
- `repository.with(session)` for every one of them.
331
+ `collection.withSession(session)` for every one of them.
332
+ - **A driver method does not take the collection's session.** `withSession`
333
+ binds this package's methods; `collection.aggregate(…)` is the driver's own,
334
+ so it wants `{ session }` in its options like anywhere else. The session is
335
+ on the collection as `collection.session` when you need to pass it along.
336
+ - **`estimatedDocumentCount` counts writes that have not committed.** It reads
337
+ the storage engine's metadata rather than the documents, so it is not
338
+ transactional and it is not exact — a document inserted by an open
339
+ transaction is already in its answer. `count` queries, and is the one to
340
+ assert on.
230
341
  - **`$jsonSchema` is not JSON Schema.** MongoDB rejects `$ref`, `$schema`,
231
342
  `default`, `format` and `id`, has no `integer` type, and treats a keyword it
232
343
  does not know as an error rather than ignoring it. `toMongoJsonSchema`
@@ -255,6 +366,15 @@ server error reaches you untouched.
255
366
  - **Rebuilding an index drops it first.** MongoDB cannot alter an index in
256
367
  place, so `sync` drops and recreates one whose options changed: there is a
257
368
  window with no index, and on a large collection the rebuild is not free.
369
+ - **`new ObjectId(undefined)` is a fresh id, not an error.** So is
370
+ `new ObjectId(null)`. A missing route parameter turns into a valid id that
371
+ matches nothing, and the bug surfaces as an empty result rather than as a
372
+ failure. Use `toObjectId`, which throws, or `tryObjectId`, which answers
373
+ `undefined`.
374
+ - **`id` is computed, not stored.** It is on every document a repository
375
+ returns, and on none in the collection: a filter or a patch keyed on it would
376
+ match nothing, so both are compile errors. Query on `_id`. A schema that
377
+ declares an `id` field of its own keeps it, untouched.
258
378
  - **`validate: 'off'` also turns the defaults off.** Nothing fills `_id`,
259
379
  `createdAt` or `version` any more, because filling them is what parsing does.
260
380
  - **The driver retries a transaction's callback** on a transient error, for up
@@ -0,0 +1,26 @@
1
+ import type { Db, MongoClient } from 'mongodb';
2
+ import type { z } from 'zod';
3
+ import { type CollectionDefinition } from '../definition/define-collection';
4
+ import type { CollectionOptions, TypedCollection } from './types';
5
+ /** What a collection can be reached through: a database, or a client. */
6
+ export type CollectionSource = Db | MongoClient;
7
+ /**
8
+ * A collection: this package's methods and the driver's own, on one object.
9
+ *
10
+ * ```ts
11
+ * const users = getCollection(db, usersDefinition);
12
+ *
13
+ * const ada = await users.create({ email: 'ada@example.com' });
14
+ * await users.update(ada._id, { name: 'Ada' }, { expectedVersion: ada.version });
15
+ * await users.aggregate([{ $group: { _id: '$teamId', n: { $sum: 1 } } }]);
16
+ * ```
17
+ *
18
+ * It takes a `Db`, or a `MongoClient` — then the database is the URI's, or the
19
+ * one named in `{ db }`.
20
+ *
21
+ * Every operation runs in the collection's session, which `withSession` sets:
22
+ * MongoDB has no ambient session, so a write inside a transaction that was not
23
+ * given one is not part of it and is not rolled back.
24
+ */
25
+ export declare function getCollection<Schema extends z.ZodObject>(source: CollectionSource, definition: CollectionDefinition<Schema>, options?: CollectionOptions<CollectionDefinition<Schema>>): TypedCollection<CollectionDefinition<Schema>>;
26
+ //# sourceMappingURL=get-collection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-collection.d.ts","sourceRoot":"","sources":["../../src/collection/get-collection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,EAAE,EAAY,WAAW,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAEN,KAAK,oBAAoB,EAEzB,MAAM,iCAAiC,CAAC;AAiBzC,OAAO,KAAK,EACX,iBAAiB,EAEjB,eAAe,EACf,MAAM,SAAS,CAAC;AAsBjB,yEAAyE;AACzE,MAAM,MAAM,gBAAgB,GAAG,EAAE,GAAG,WAAW,CAAC;AAmBhD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,MAAM,SAAS,CAAC,CAAC,SAAS,EACvD,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,oBAAoB,CAAC,MAAM,CAAC,EACxC,OAAO,GAAE,iBAAiB,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAM,GAC3D,eAAe,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAG/C"}
@@ -0,0 +1,270 @@
1
+ import type { ClientSession, Db, Collection as DriverCollection, Filter } from 'mongodb';
2
+ import type { DocumentOf, FieldOf, IdOf, NewDocumentOf, ReadDocumentOf } from '../definition/define-collection';
3
+ import type { CursorPage, Page, PageOptions } from '../pagination/page';
4
+ import type { SyncOptions, SyncReport } from '../sync/sync-collection';
5
+ export type OrderDirection = 'asc' | 'desc';
6
+ /**
7
+ * A field of the documents, or a path into one. The tail of a path cannot be
8
+ * checked — a schema says nothing about what is under an arbitrary key — but
9
+ * its head must be a field, which is what catches a misspelt name.
10
+ */
11
+ export type FieldPath<Def> = FieldOf<Def> | `${FieldOf<Def>}.${string}`;
12
+ /** The fields whose type is a number, for `$inc` and `$mul`. */
13
+ type NumericFieldsOf<Doc> = {
14
+ [K in keyof Doc]: NonNullable<Doc[K]> extends number ? K : never;
15
+ }[keyof Doc] & string;
16
+ /** The fields whose type is an array, for `$push` and `$addToSet`. */
17
+ type ArrayFieldsOf<Doc> = {
18
+ [K in keyof Doc]: NonNullable<Doc[K]> extends readonly unknown[] ? K : never;
19
+ }[keyof Doc] & string;
20
+ type ElementOf<T> = NonNullable<T> extends readonly (infer Element)[] ? Element : never;
21
+ /** A sort, keyed on the schema's fields. */
22
+ export type SortOf<Def> = {
23
+ [Field in FieldPath<Def>]?: 1 | -1 | 'asc' | 'desc';
24
+ };
25
+ /** What a projection may hold beside `0` and `1`. */
26
+ export type ProjectionOperator = {
27
+ $slice: number | [number, number];
28
+ } | {
29
+ $elemMatch: Record<string, unknown>;
30
+ } | {
31
+ $meta: string;
32
+ };
33
+ /**
34
+ * A projection, keyed on the schema's fields. It says what is sent over the
35
+ * wire; it does not narrow the type of the documents, because the driver
36
+ * cannot either — a projected read is typed as the whole document.
37
+ */
38
+ export type ProjectionOf<Def> = {
39
+ [Field in FieldPath<Def>]?: 0 | 1 | boolean | ProjectionOperator;
40
+ };
41
+ /**
42
+ * Who is writing: the type the schema gives `createdBy`, or `updatedBy`, or
43
+ * `deletedBy`. A collection with none of them has no actor to stamp, so `as`
44
+ * cannot be called on it at all.
45
+ */
46
+ export type ActorOf<Def> = 'createdBy' extends keyof DocumentOf<Def> ? NonNullable<DocumentOf<Def>['createdBy']> : 'updatedBy' extends keyof DocumentOf<Def> ? NonNullable<DocumentOf<Def>['updatedBy']> : 'deletedBy' extends keyof DocumentOf<Def> ? NonNullable<DocumentOf<Def>['deletedBy']> : never;
47
+ /** What `$set` takes: a field's own type, or anything under a path. */
48
+ export type SetOf<Def> = {
49
+ [Field in FieldPath<Def>]?: Field extends keyof DocumentOf<Def> ? DocumentOf<Def>[Field] : unknown;
50
+ };
51
+ /**
52
+ * MongoDB's update operators, keyed on the schema.
53
+ *
54
+ * The driver's own `UpdateFilter` is intersected with `Document`, whose index
55
+ * signature accepts every key — `$set: { nope: 1 }` included — and `Omit`
56
+ * cannot take that back, since omitting a literal key from an index signature
57
+ * leaves the index signature. So the operators are declared here instead. One
58
+ * this does not name is a reason to reach for `raw`, which is the driver's own
59
+ * collection, untouched.
60
+ */
61
+ export interface UpdateOperators<Def> {
62
+ $set?: SetOf<Def>;
63
+ $setOnInsert?: SetOf<Def>;
64
+ $unset?: {
65
+ [Field in FieldPath<Def>]?: '' | 1 | true;
66
+ };
67
+ $inc?: {
68
+ [Field in NumericFieldsOf<DocumentOf<Def>> | `${FieldOf<Def>}.${string}`]?: number;
69
+ };
70
+ $mul?: {
71
+ [Field in NumericFieldsOf<DocumentOf<Def>> | `${FieldOf<Def>}.${string}`]?: number;
72
+ };
73
+ $min?: SetOf<Def>;
74
+ $max?: SetOf<Def>;
75
+ $rename?: {
76
+ [Field in FieldPath<Def>]?: string;
77
+ };
78
+ $currentDate?: {
79
+ [Field in FieldPath<Def>]?: true | {
80
+ $type: 'date' | 'timestamp';
81
+ };
82
+ };
83
+ $push?: PushOf<Def>;
84
+ $addToSet?: PushOf<Def>;
85
+ $pull?: {
86
+ [Field in FieldPath<Def>]?: unknown;
87
+ };
88
+ $pullAll?: {
89
+ [Field in FieldPath<Def>]?: readonly unknown[];
90
+ };
91
+ $pop?: {
92
+ [Field in FieldPath<Def>]?: 1 | -1;
93
+ };
94
+ }
95
+ /** What `$push` and `$addToSet` take: an element of the array, or `$each`. */
96
+ export type PushOf<Def> = {
97
+ [Field in ArrayFieldsOf<DocumentOf<Def>>]?: ElementOf<DocumentOf<Def>[Field]> | {
98
+ $each: readonly ElementOf<DocumentOf<Def>[Field]>[];
99
+ $position?: number;
100
+ $slice?: number;
101
+ $sort?: 1 | -1 | Record<string, 1 | -1>;
102
+ };
103
+ } & {
104
+ [Path in `${string}.${string}`]?: unknown;
105
+ };
106
+ /**
107
+ * What an update writes: the document's own fields, checked against the
108
+ * schema, or MongoDB's operators for what they cannot say.
109
+ */
110
+ export type Patch<Def> = Partial<DocumentOf<Def>> | (UpdateOperators<Def> & {
111
+ [K in keyof DocumentOf<Def>]?: never;
112
+ } & {
113
+ id?: never;
114
+ });
115
+ export interface ReadOptions {
116
+ /** Include soft-deleted documents. Ignored without a `deletedAt` field. */
117
+ withDeleted?: boolean;
118
+ }
119
+ export interface FindFirstOptions<Def> extends ReadOptions {
120
+ sort?: SortOf<Def>;
121
+ projection?: ProjectionOf<Def>;
122
+ }
123
+ export interface FindManyOptions<Def> extends FindFirstOptions<Def> {
124
+ filter?: Filter<DocumentOf<Def>>;
125
+ limit?: number;
126
+ skip?: number;
127
+ }
128
+ export interface PaginateOptions<Def> extends PageOptions, ReadOptions {
129
+ filter?: Filter<DocumentOf<Def>>;
130
+ /** Default `{ _id: 1 }`, so that pages are stable. */
131
+ sort?: SortOf<Def>;
132
+ }
133
+ export interface CursorPaginateOptions<Def> extends ReadOptions {
134
+ /** The `nextCursor` of the previous page. Omit it for the first page. */
135
+ after?: string | null | undefined;
136
+ /** Documents per page. Default `20`, at most `maxPageSize`. */
137
+ limit?: number;
138
+ filter?: Filter<DocumentOf<Def>>;
139
+ /**
140
+ * The field to page along. Default `_id`. Any other is followed by `_id`,
141
+ * which breaks its ties, and must be set on every document.
142
+ */
143
+ orderBy?: FieldOf<Def>;
144
+ /** Default `'asc'`. */
145
+ direction?: OrderDirection;
146
+ }
147
+ export interface UpdateOptions {
148
+ /**
149
+ * Only update the document while its `version` is still this one. When it
150
+ * is not, nothing is written and `OptimisticLockError` is thrown with the
151
+ * version the document has now.
152
+ */
153
+ expectedVersion?: number;
154
+ }
155
+ export interface CollectionOptions<Def> {
156
+ /**
157
+ * Soft delete through the `deletedAt` field. Default: on when the schema
158
+ * has one. `false` makes `delete` a real delete.
159
+ */
160
+ softDelete?: boolean;
161
+ /**
162
+ * Set `updatedAt` on every update that does not set it. Default: on when
163
+ * the schema has the field.
164
+ */
165
+ touchUpdatedAt?: boolean;
166
+ /**
167
+ * Raise `version` by one on every update. Default: on when the schema has
168
+ * the field. `expectedVersion` needs it.
169
+ */
170
+ optimisticLock?: boolean;
171
+ /**
172
+ * Check documents against the schema before writing them, which is also
173
+ * what fills their defaults. Default `'parse'`. `'off'` sends them as they
174
+ * are — and then nothing fills `_id`, `createdAt` or `version`.
175
+ */
176
+ validate?: 'parse' | 'off';
177
+ /** The largest `pageSize` or `limit` a page may ask for. Default `100`. */
178
+ maxPageSize?: number;
179
+ /** The session every operation runs in. `withSession` is how it is set. */
180
+ session?: ClientSession;
181
+ /** Who is writing, stamped into `createdBy`, `updatedBy` and `deletedBy`. */
182
+ actor?: ActorOf<Def>;
183
+ /** Which database, when `getCollection` is given a client rather than a `Db`. */
184
+ db?: string;
185
+ }
186
+ /**
187
+ * What this package adds to a collection. The collection you are handed is
188
+ * this **and** the driver's own `Collection`, so `aggregate`, `watch`,
189
+ * `bulkWrite` and the rest are on it directly.
190
+ */
191
+ export interface CollectionApi<Def> {
192
+ readonly definition: Def;
193
+ readonly db: Db;
194
+ /**
195
+ * The driver's collection, untouched. It is where the three methods this
196
+ * one redefines still live — `raw.updateMany`, `raw.deleteMany` and
197
+ * `raw.count` — and where an update operator this package does not name
198
+ * can still be sent.
199
+ */
200
+ readonly raw: DriverCollection<DocumentOf<Def>>;
201
+ /** The session every operation of this collection runs in, if any. */
202
+ readonly session: ClientSession | undefined;
203
+ /**
204
+ * The same collection, bound to a session. MongoDB has no ambient session:
205
+ * without this, an operation inside a transaction runs outside it.
206
+ *
207
+ * It binds **this package's** methods. A driver method reached through the
208
+ * collection takes its session the driver's way, in its options.
209
+ */
210
+ withSession(session: ClientSession | undefined): TypedCollection<Def>;
211
+ /** The same collection, stamping this actor into the `*By` fields. */
212
+ as(actor: ActorOf<Def>): TypedCollection<Def>;
213
+ /** Creates the collection, its validator and its indexes. See `syncCollection`. */
214
+ sync(options?: SyncOptions): Promise<SyncReport>;
215
+ /** The document with this `_id`, or `undefined`. */
216
+ findById(id: IdOf<Def>, options?: ReadOptions): Promise<ReadDocumentOf<Def> | undefined>;
217
+ /** The document with this `_id`. Throws `NotFoundError`. */
218
+ getById(id: IdOf<Def>, options?: ReadOptions): Promise<ReadDocumentOf<Def>>;
219
+ /** The first document that matches, or `undefined`. */
220
+ findFirst(filter?: Filter<DocumentOf<Def>>, options?: FindFirstOptions<Def>): Promise<ReadDocumentOf<Def> | undefined>;
221
+ /** Every document that matches. */
222
+ findMany(options?: FindManyOptions<Def>): Promise<ReadDocumentOf<Def>[]>;
223
+ /** Checks the document against the schema, fills its defaults, inserts it. */
224
+ create(values: NewDocumentOf<Def>): Promise<ReadDocumentOf<Def>>;
225
+ /** The same, in one insert. `[]` sends nothing. */
226
+ createMany(values: readonly NewDocumentOf<Def>[]): Promise<ReadDocumentOf<Def>[]>;
227
+ /** Updates the document with this `_id` and returns it. Throws `NotFoundError`. */
228
+ update(id: IdOf<Def>, patch: Patch<Def>, options?: UpdateOptions): Promise<ReadDocumentOf<Def>>;
229
+ /**
230
+ * Updates every document that matches, and returns how many changed. The
231
+ * driver's own `updateMany`, which returns an `UpdateResult` and takes no
232
+ * filter for granted, is `raw.updateMany`.
233
+ */
234
+ updateMany(filter: Filter<DocumentOf<Def>>, patch: Patch<Def>): Promise<number>;
235
+ /**
236
+ * Deletes the document with this `_id` and returns it: a soft delete on a
237
+ * collection with `deletedAt`. Throws `NotFoundError`.
238
+ */
239
+ delete(id: IdOf<Def>): Promise<ReadDocumentOf<Def>>;
240
+ /** Deletes every document that matches, and returns how many. `raw.deleteMany` is the driver's. */
241
+ deleteMany(filter: Filter<DocumentOf<Def>>): Promise<number>;
242
+ /** A real delete, of a live or a soft-deleted document. */
243
+ hardDelete(id: IdOf<Def>): Promise<ReadDocumentOf<Def>>;
244
+ /** A real delete of every document that matches, soft-deleted ones included. */
245
+ hardDeleteMany(filter: Filter<DocumentOf<Def>>): Promise<number>;
246
+ /** Clears `deletedAt` and returns the document. Throws `NotFoundError`. */
247
+ restore(id: IdOf<Def>): Promise<ReadDocumentOf<Def>>;
248
+ /**
249
+ * How many documents match, soft-deleted ones left out. The driver's
250
+ * `count` is `raw.count`, and `estimatedDocumentCount` is on this
251
+ * collection directly.
252
+ */
253
+ count(filter?: Filter<DocumentOf<Def>>, options?: ReadOptions): Promise<number>;
254
+ /** Whether any document matches. */
255
+ exists(filter: Filter<DocumentOf<Def>>, options?: ReadOptions): Promise<boolean>;
256
+ /** One page of the documents that match, and how many there are. */
257
+ paginate(options?: PaginateOptions<Def>): Promise<Page<ReadDocumentOf<Def>>>;
258
+ /** One page of the documents that match, after a cursor. */
259
+ paginateByCursor(options?: CursorPaginateOptions<Def>): Promise<CursorPage<ReadDocumentOf<Def>>>;
260
+ }
261
+ /**
262
+ * A collection: this package's methods, plus every method of the driver's own
263
+ * `Collection` that they do not redefine.
264
+ *
265
+ * Three names are defined by both, and this package's win: `count`,
266
+ * `updateMany` and `deleteMany`. The driver's are on `raw`.
267
+ */
268
+ export type TypedCollection<Def> = CollectionApi<Def> & Omit<DriverCollection<DocumentOf<Def>>, keyof CollectionApi<Def>>;
269
+ export {};
270
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/collection/types.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { IndexDescription, ObjectId } from 'mongodb';
1
+ import type { IndexDescription, IndexDirection, ObjectId } from 'mongodb';
2
2
  import type { z } from 'zod';
3
3
  /** What MongoDB does with a document that fails the validator. */
4
4
  export type ValidationAction = 'error' | 'warn';
@@ -13,6 +13,23 @@ export interface ValidationConfig {
13
13
  /** Default `'error'`. `'warn'` logs and lets the write through. */
14
14
  action?: ValidationAction;
15
15
  }
16
+ /**
17
+ * What an index may be keyed on: a field of the documents, which an editor
18
+ * completes, or a path into one — `{ 'address.city': 1 }` is how MongoDB
19
+ * indexes a nested field, and there is no way to check the tail of a path
20
+ * against a schema without rejecting the paths Mongo allows.
21
+ */
22
+ export type IndexKey<Doc> = {
23
+ [Field in (keyof Doc & string) | `${keyof Doc & string}.${string}`]?: IndexDirection;
24
+ } | Map<string, IndexDirection>;
25
+ /**
26
+ * An index, keyed on the schema's own fields. Everything else — `unique`,
27
+ * `name`, `collation`, `partialFilterExpression`, the TTL — is the driver's
28
+ * `IndexDescription`, unchanged.
29
+ */
30
+ export interface CollectionIndex<Doc> extends Omit<IndexDescription, 'key'> {
31
+ key: IndexKey<Doc>;
32
+ }
16
33
  /** What `defineCollection` takes. */
17
34
  export interface CollectionConfig<Schema extends z.ZodObject> {
18
35
  /** The collection's name on the server. */
@@ -22,13 +39,16 @@ export interface CollectionConfig<Schema extends z.ZodObject> {
22
39
  * `z.input` what a write takes. It must have an `_id`.
23
40
  */
24
41
  schema: Schema;
25
- /** The indexes `sync` creates, as the driver describes them. */
26
- indexes?: readonly IndexDescription[];
42
+ /** The indexes `sync` creates, keyed on the schema's fields. */
43
+ indexes?: readonly CollectionIndex<z.output<Schema>>[];
27
44
  /** The `$jsonSchema` validator `sync` writes from the schema. */
28
45
  validation?: ValidationConfig;
29
46
  }
30
47
  /** A collection, as `defineCollection` returns it: frozen, with its defaults. */
31
- export interface CollectionDefinition<Schema extends z.ZodObject = z.ZodObject> extends Readonly<CollectionConfig<Schema>> {
48
+ export interface CollectionDefinition<Schema extends z.ZodObject = z.ZodObject> {
49
+ readonly name: string;
50
+ readonly schema: Schema;
51
+ /** As the driver takes them: `sync` hands these straight to MongoDB. */
32
52
  readonly indexes: readonly IndexDescription[];
33
53
  readonly validation: Required<ValidationConfig>;
34
54
  }
@@ -38,6 +58,18 @@ export type AnyCollectionDefinition = CollectionDefinition<any>;
38
58
  export type DocumentOf<Def> = Def extends {
39
59
  schema: infer Schema;
40
60
  } ? Schema extends z.ZodType ? z.output<Schema> : never : never;
61
+ /**
62
+ * A document as a collection gives it back: the stored document, plus `id`.
63
+ *
64
+ * `id` is `_id` as a string, computed rather than stored — the collection
65
+ * holds `_id` alone. It is enumerable, so `JSON.stringify` and a spread carry
66
+ * it, which is what makes a document ready to return from an API; it is not
67
+ * part of `DocumentOf`, so a filter or a patch cannot be keyed on it, because
68
+ * the server would match nothing.
69
+ */
70
+ export type ReadDocumentOf<Def> = DocumentOf<Def> & {
71
+ readonly id: string;
72
+ };
41
73
  /** What a write takes: the documents before their defaults are filled. */
42
74
  export type NewDocumentOf<Def> = Def extends {
43
75
  schema: infer Schema;
@@ -69,7 +101,7 @@ export type FieldOf<Def> = keyof DocumentOf<Def> & string;
69
101
  * ```
70
102
  */
71
103
  export declare function defineCollection<Schema extends z.ZodObject>(config: CollectionConfig<Schema>): CollectionDefinition<Schema>;
72
- /** Which of the fields the repository knows about a definition's schema has. */
104
+ /** Which of the fields the collection knows about a definition's schema has. */
73
105
  export declare function stampsOf(definition: AnyCollectionDefinition): {
74
106
  createdAt: boolean;
75
107
  updatedAt: boolean;