@zodmon/core 0.8.0 → 0.9.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ObjectId, DeleteResult, FindCursor, Collection, UpdateResult, MongoClientOptions } from 'mongodb';
1
+ import { ObjectId, Document, DeleteResult, FindCursor, Collection, UpdateResult, MongoClientOptions } from 'mongodb';
2
2
  import { ZodPipe, ZodCustom, ZodTransform, z, ZodDefault } from 'zod';
3
3
 
4
4
  /**
@@ -253,11 +253,12 @@ type InferInsert<TDef extends {
253
253
  * The immutable definition object returned by collection().
254
254
  * Holds everything needed to later create a live collection handle.
255
255
  *
256
+ * @typeParam TName - The collection name string literal.
256
257
  * @typeParam TShape - The Zod shape defining document fields.
257
258
  * @typeParam TIndexes - The compound indexes array type, preserving literal name types.
258
259
  */
259
- type CollectionDefinition<TShape extends z.core.$ZodShape = z.core.$ZodShape, TIndexes extends readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[] = readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[]> = {
260
- readonly name: string;
260
+ type CollectionDefinition<TName extends string = string, TShape extends z.core.$ZodShape = z.core.$ZodShape, TIndexes extends readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[] = readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[]> = {
261
+ readonly name: TName;
261
262
  readonly schema: z.ZodObject<ResolvedShape<TShape>>;
262
263
  readonly shape: TShape;
263
264
  readonly fieldIndexes: FieldIndexDefinition[];
@@ -265,7 +266,7 @@ type CollectionDefinition<TShape extends z.core.$ZodShape = z.core.$ZodShape, TI
265
266
  readonly options: Required<Pick<CollectionOptions, 'validation'>> & Omit<CollectionOptions, 'indexes' | 'validation'>;
266
267
  };
267
268
  /** Erased collection type for use in generic contexts. */
268
- type AnyCollection = CollectionDefinition<z.core.$ZodShape>;
269
+ type AnyCollection = CollectionDefinition<string, z.core.$ZodShape>;
269
270
  /**
270
271
  * Extract declared index names from a collection definition.
271
272
  *
@@ -304,1209 +305,2433 @@ type ExtractNames<T extends readonly {
304
305
  }[number];
305
306
 
306
307
  /**
307
- * Options controlling how {@link syncIndexes} behaves.
308
+ * Forces TypeScript to eagerly expand mapped/intersection types in tooltips.
309
+ *
310
+ * Instead of showing `Omit<{ _id: ObjectId; name: string; salary: number }, "salary">`,
311
+ * the IDE will display the resolved shape `{ _id: ObjectId; name: string }`.
308
312
  *
309
313
  * @example
310
314
  * ```ts
311
- * await users.syncIndexes({ dryRun: true })
312
- * await users.syncIndexes({ dropOrphaned: true })
315
+ * type Expanded = Prettify<Pick<User, 'name' | 'role'>>
316
+ * // ^? { name: string; role: string }
313
317
  * ```
314
318
  */
315
- type SyncIndexesOptions = {
316
- /**
317
- * When `true`, compute the diff without actually creating, dropping, or
318
- * modifying any indexes. The returned {@link SyncIndexesResult} shows what
319
- * *would* happen.
320
- */
321
- dryRun?: boolean;
322
- /**
323
- * When `true`, drop indexes that exist in MongoDB but are not declared in
324
- * the schema. Also drops and recreates stale indexes (same key, different
325
- * options). When `false` (the default), orphaned and stale indexes are
326
- * reported but left untouched.
327
- */
328
- dropOrphaned?: boolean;
319
+ type Prettify<T> = {
320
+ [K in keyof T]: T[K];
321
+ } & {};
322
+
323
+ /**
324
+ * Type-level marker that carries the target collection type through the
325
+ * type system. Intersected with the schema return type by `.ref()` so
326
+ * that `RefFields<T>` (future) can extract ref relationships.
327
+ *
328
+ * This is a phantom brand no runtime value has this property.
329
+ */
330
+ type RefMarker<TCollection extends AnyCollection = AnyCollection> = {
331
+ readonly _ref: TCollection;
329
332
  };
330
333
  /**
331
- * Describes an index whose key matches a desired index but whose options differ.
334
+ * Metadata stored in the WeakMap sidecar for schemas marked with `.ref()`.
335
+ * Holds a reference to the target collection definition object.
336
+ */
337
+ type RefMetadata = {
338
+ readonly collection: AnyCollection;
339
+ };
340
+ /**
341
+ * Module augmentation: adds `.ref()` to all `ZodType` schemas.
332
342
  *
333
- * Returned in {@link SyncIndexesResult.stale} so the caller can decide whether
334
- * to manually reconcile or re-run with `dropOrphaned: true`.
343
+ * The intersection constraint `this['_zod']['output'] extends InferDocument<TCollection>['_id']`
344
+ * ensures compile-time type safety: the field's output type must match the
345
+ * target collection's `_id` type. Mismatches produce a type error.
346
+ *
347
+ * Supports both default ObjectId `_id` and custom `_id` types (string, nanoid, etc.):
348
+ * - `objectId().ref(Users)` compiles when Users has ObjectId `_id`
349
+ * - `z.string().ref(Orgs)` compiles when Orgs has string `_id`
350
+ * - `z.string().ref(Users)` is a type error (string ≠ ObjectId)
351
+ * - `objectId().ref(Orgs)` is a type error (ObjectId ≠ string)
352
+ */
353
+ declare module 'zod' {
354
+ interface ZodType {
355
+ /**
356
+ * Declare a typed foreign key reference to another collection.
357
+ *
358
+ * Stores the target collection definition in metadata for runtime
359
+ * populate resolution, and brands the return type with
360
+ * `RefMarker<TCollection>` so `RefFields<T>` can extract refs
361
+ * at the type level.
362
+ *
363
+ * The field's output type must match the target collection's `_id` type.
364
+ * Mismatched types produce a compile error.
365
+ *
366
+ * Apply `.ref()` before wrapper methods like `.optional()` or `.nullable()`:
367
+ * `objectId().ref(Users).optional()` — not `objectId().optional().ref(Users)`.
368
+ *
369
+ * @param collection - The target collection definition object.
370
+ * @returns The same schema instance, branded with the ref marker.
371
+ *
372
+ * @example
373
+ * ```ts
374
+ * const Posts = collection('posts', {
375
+ * authorId: objectId().ref(Users),
376
+ * title: z.string(),
377
+ * })
378
+ * ```
379
+ */
380
+ ref<TCollection extends AnyCollection>(collection: TCollection & (this['_zod']['output'] extends InferDocument<TCollection>['_id'] ? unknown : never)): this & RefMarker<TCollection>;
381
+ }
382
+ }
383
+ /**
384
+ * Retrieve the ref metadata attached to a Zod schema, if any.
385
+ *
386
+ * Returns `undefined` when the schema was never marked with `.ref()`.
387
+ *
388
+ * @param schema - The Zod schema to inspect. Accepts `unknown` for
389
+ * convenience; non-object values safely return `undefined`.
390
+ * @returns The {@link RefMetadata} for the schema, or `undefined`.
335
391
  *
336
392
  * @example
337
393
  * ```ts
338
- * const result = await users.syncIndexes()
339
- * for (const s of result.stale) {
340
- * console.log(`${s.name}: key=${JSON.stringify(s.key)}`)
341
- * console.log(` existing=${JSON.stringify(s.existing)}`)
342
- * console.log(` desired=${JSON.stringify(s.desired)}`)
343
- * }
394
+ * const authorId = objectId().ref(Users)
395
+ * const meta = getRefMetadata(authorId)
396
+ * // => { collection: Users }
344
397
  * ```
345
398
  */
346
- type StaleIndex = {
347
- /** The MongoDB index name (e.g. `'email_1'`). */
348
- name: string;
349
- /** The index key spec (e.g. `{ email: 1 }`). */
350
- key: Record<string, 1 | -1 | 'text'>;
351
- /** The relevant options currently set on the existing index. */
352
- existing: Record<string, unknown>;
353
- /** The options the schema declares for this index. */
354
- desired: Record<string, unknown>;
355
- };
399
+ declare function getRefMetadata(schema: unknown): RefMetadata | undefined;
400
+
356
401
  /**
357
- * The result of a {@link syncIndexes} call.
402
+ * Branded wrapper for accumulator expressions used inside `groupBy`.
358
403
  *
359
- * Every array contains index names. `stale` contains full details so the
360
- * caller can inspect the mismatch.
404
+ * Carries the MongoDB accumulator expression at runtime and the inferred
405
+ * result type `T` at the type level.
361
406
  *
362
407
  * @example
363
408
  * ```ts
364
- * const result = await users.syncIndexes()
365
- * console.log('created:', result.created)
366
- * console.log('dropped:', result.dropped)
367
- * console.log('skipped:', result.skipped)
368
- * console.log('stale:', result.stale.map(s => s.name))
409
+ * const acc: Accumulator<number> = {
410
+ * __accum: true,
411
+ * expr: { $sum: '$price' },
412
+ * }
369
413
  * ```
370
414
  */
371
- type SyncIndexesResult = {
372
- /** Names of indexes that were created (or would be created in dryRun mode). */
373
- created: string[];
374
- /** Names of indexes that were dropped (or would be dropped in dryRun mode). */
375
- dropped: string[];
376
- /** Names of indexes that already existed with matching options — no action taken. */
377
- skipped: string[];
378
- /** Indexes whose key matches a desired spec but whose options differ. */
379
- stale: StaleIndex[];
415
+ type Accumulator<T = unknown> = {
416
+ readonly __accum: true;
417
+ readonly expr: Document;
418
+ /** @internal Phantom field carries the result type at the type level. */
419
+ readonly _type?: T;
380
420
  };
381
-
382
421
  /**
383
- * Comparison operators for a field value of type `V`.
384
- *
385
- * Maps each MongoDB comparison operator to its expected value type.
386
- * `$regex` is only available when `V` extends `string`.
387
- *
388
- * Used as the operator object that can be assigned to a field in {@link TypedFilter}.
422
+ * Extracts the result type from an `Accumulator<T>`.
389
423
  *
390
424
  * @example
391
425
  * ```ts
392
- * // As a raw object (without builder functions)
393
- * const filter: TypedFilter<User> = { age: { $gt: 25, $lte: 65 } }
394
- *
395
- * // $regex only available on string fields
396
- * const filter: TypedFilter<User> = { name: { $regex: /^A/i } }
426
+ * type Count = InferAccumulator<Accumulator<number>>
427
+ * // ^? number
397
428
  * ```
398
429
  */
399
- type ComparisonOperators<V> = {
400
- /** Matches values equal to the specified value. */
401
- $eq?: V;
402
- /** Matches values not equal to the specified value. */
403
- $ne?: V;
404
- /** Matches values greater than the specified value. */
405
- $gt?: V;
406
- /** Matches values greater than or equal to the specified value. */
407
- $gte?: V;
408
- /** Matches values less than the specified value. */
409
- $lt?: V;
410
- /** Matches values less than or equal to the specified value. */
411
- $lte?: V;
412
- /** Matches any value in the specified array. */
413
- $in?: V[];
414
- /** Matches none of the values in the specified array. */
415
- $nin?: V[];
416
- /** Matches documents where the field exists (`true`) or does not exist (`false`). */
417
- $exists?: boolean;
418
- /** Negates a comparison operator. */
419
- $not?: ComparisonOperators<V>;
420
- } & (V extends string ? {
421
- $regex?: RegExp | string;
422
- } : unknown);
423
- /** Depth counter for limiting dot-notation recursion. Index = current depth, value = next depth. */
424
- type Prev = [never, 0, 1, 2];
430
+ type InferAccumulator<T> = T extends Accumulator<infer R> ? R : never;
425
431
  /**
426
- * Generates a union of all valid dot-separated paths for nested object fields in `T`.
432
+ * Maps an accumulator spec object to its inferred output shape.
427
433
  *
428
- * Recursion is limited to 3 levels deep to prevent TypeScript compilation performance issues.
429
- * Only plain object fields are traversed — arrays, `Date`, `RegExp`, and `ObjectId` are
430
- * treated as leaf nodes and do not produce sub-paths.
434
+ * Given `{ count: Accumulator<number>, names: Accumulator<string[]> }`,
435
+ * produces `{ count: number, names: string[] }`.
431
436
  *
432
437
  * @example
433
438
  * ```ts
434
- * type User = { address: { city: string; geo: { lat: number; lng: number } } }
435
- *
436
- * // DotPaths<User> = 'address.city' | 'address.geo' | 'address.geo.lat' | 'address.geo.lng'
439
+ * type Spec = {
440
+ * count: Accumulator<number>
441
+ * total: Accumulator<number>
442
+ * }
443
+ * type Result = InferAccumulators<Spec>
444
+ * // ^? { count: number; total: number }
437
445
  * ```
438
446
  */
439
- type DotPaths<T, Depth extends number = 3> = Depth extends 0 ? never : {
440
- [K in keyof T & string]: NonNullable<T[K]> extends ReadonlyArray<unknown> | Date | RegExp | ObjectId ? never : NonNullable<T[K]> extends Record<string, unknown> ? `${K}.${keyof NonNullable<T[K]> & string}` | `${K}.${DotPaths<NonNullable<T[K]>, Prev[Depth]>}` : never;
441
- }[keyof T & string];
447
+ type InferAccumulators<T extends Record<string, Accumulator>> = {
448
+ [K in keyof T]: InferAccumulator<T[K]>;
449
+ };
442
450
  /**
443
- * Resolves the value type at a dot-separated path `P` within type `T`.
444
- *
445
- * Splits `P` on the first `.` and recursively descends into `T`'s nested types.
446
- * Returns `never` if the path is invalid.
451
+ * Field reference string prefixed with `$`, constrained to keys of `T`.
447
452
  *
448
453
  * @example
449
454
  * ```ts
450
- * type User = { address: { city: string; geo: { lat: number } } }
451
- *
452
- * // DotPathType<User, 'address.city'> = string
453
- * // DotPathType<User, 'address.geo.lat'> = number
455
+ * type OrderRef = FieldRef<{ price: number; qty: number }>
456
+ * // ^? '$price' | '$qty'
454
457
  * ```
455
458
  */
456
- type DotPathType<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? Rest extends keyof NonNullable<T[K]> ? NonNullable<T[K]>[Rest] : DotPathType<NonNullable<T[K]>, Rest> : never : P extends keyof T ? T[P] : never;
459
+ type FieldRef<T> = `$${keyof T & string}`;
457
460
  /**
458
- * Strict type-safe MongoDB filter query type.
459
- *
460
- * Validates filter objects at compile time — rejects nonexistent fields, type mismatches,
461
- * and invalid operator usage. Unlike the MongoDB driver's `Filter<T>`, does NOT allow
462
- * arbitrary keys via `& Document`.
461
+ * Typed accumulator factory passed to the `groupBy` callback.
463
462
  *
464
- * Supports three forms of filter expressions:
465
- * - **Direct field values** (implicit `$eq`): `{ name: 'Alice' }`
466
- * - **Comparison operators**: `{ age: { $gt: 25 } }` or `{ age: $gt(25) }`
467
- * - **Dot notation** for nested fields up to 3 levels: `{ 'address.city': 'NYC' }`
463
+ * Each method constrains field names to `keyof T & string`, providing
464
+ * IDE autocomplete and compile-time validation. Return types resolve
465
+ * to the actual field type (`T[K]`), not `unknown`.
468
466
  *
469
- * Logical operators `$and`, `$or`, and `$nor` accept arrays of `TypedFilter<T>`
470
- * for composing complex queries.
467
+ * @typeParam T - The current pipeline output document type.
471
468
  *
472
469
  * @example
473
470
  * ```ts
474
- * // Simple equality
475
- * const filter: TypedFilter<User> = { name: 'Alice' }
476
- *
477
- * // Builder functions mixed with object literals
478
- * const filter: TypedFilter<User> = { age: $gte(18), role: $in(['admin', 'mod']) }
479
- *
480
- * // Logical composition — T inferred from find() context
481
- * posts.find($and(
482
- * $or({ published: true }, { views: $gte(100) }),
483
- * { title: $regex(/guide/i) },
484
- * ))
485
- *
486
- * // Dynamic conditional building
487
- * const conditions: TypedFilter<User>[] = []
488
- * if (name) conditions.push({ name })
489
- * if (minAge) conditions.push({ age: $gte(minAge) })
490
- * const filter = conditions.length ? $and<User>(...conditions) : {}
471
+ * users.aggregate()
472
+ * .groupBy('role', acc => ({
473
+ * minSalary: acc.min('salary'), // Accumulator<number>
474
+ * firstName: acc.first('name'), // Accumulator<string>
475
+ * allNames: acc.push('name'), // Accumulator<string[]>
476
+ * count: acc.count(), // Accumulator<number>
477
+ * }))
491
478
  * ```
492
479
  */
493
- type TypedFilter<T> = {
494
- [K in keyof T]?: T[K] | ComparisonOperators<T[K]>;
495
- } & {
496
- [P in DotPaths<T>]?: DotPathType<T, P> | ComparisonOperators<DotPathType<T, P>>;
497
- } & {
498
- /** Joins clauses with a logical AND. Matches documents that satisfy all filters. */
499
- $and?: TypedFilter<T>[];
500
- /** Joins clauses with a logical OR. Matches documents that satisfy at least one filter. */
501
- $or?: TypedFilter<T>[];
502
- /** Joins clauses with a logical NOR. Matches documents that fail all filters. */
503
- $nor?: TypedFilter<T>[];
480
+ type AccumulatorBuilder<T> = {
481
+ /** Count documents in each group. Always returns `Accumulator<number>`. */
482
+ count(): Accumulator<number>;
483
+ /** Sum a numeric field. Always returns `Accumulator<number>`. */
484
+ sum<K extends keyof T & string>(field: K): Accumulator<number>;
485
+ /** Sum a literal number per document. Always returns `Accumulator<number>`. */
486
+ sum(value: number): Accumulator<number>;
487
+ /** Average a numeric field. Always returns `Accumulator<number>`. */
488
+ avg<K extends keyof T & string>(field: K): Accumulator<number>;
489
+ /** Minimum value of a field. Returns `Accumulator<T[K]>`. */
490
+ min<K extends keyof T & string>(field: K): Accumulator<T[K]>;
491
+ /** Maximum value of a field. Returns `Accumulator<T[K]>`. */
492
+ max<K extends keyof T & string>(field: K): Accumulator<T[K]>;
493
+ /** First value in each group (by document order). Returns `Accumulator<T[K]>`. */
494
+ first<K extends keyof T & string>(field: K): Accumulator<T[K]>;
495
+ /** Last value in each group (by document order). Returns `Accumulator<T[K]>`. */
496
+ last<K extends keyof T & string>(field: K): Accumulator<T[K]>;
497
+ /** Collect values into an array (with duplicates). Returns `Accumulator<T[K][]>`. */
498
+ push<K extends keyof T & string>(field: K): Accumulator<T[K][]>;
499
+ /** Collect unique values into an array. Returns `Accumulator<T[K][]>`. */
500
+ addToSet<K extends keyof T & string>(field: K): Accumulator<T[K][]>;
504
501
  };
505
-
506
502
  /**
507
- * Options for {@link findOneAndDelete}.
503
+ * Resolves the document field type from a `$`-prefixed field reference.
504
+ *
505
+ * @example
506
+ * ```ts
507
+ * type Order = { price: number; name: string }
508
+ * type PriceType = FieldRefType<Order, '$price'>
509
+ * // ^? number
510
+ * ```
508
511
  */
509
- type FindOneAndDeleteOptions = {
510
- /** Override the collection-level validation mode, or `false` to skip validation entirely. */
511
- validate?: ValidationMode | false;
512
- };
512
+ type FieldRefType<T, F extends string> = F extends `$${infer K}` ? K extends keyof T ? T[K] : never : never;
513
513
  /**
514
- * Delete a single document matching the filter.
514
+ * Output shape of a single-field `groupBy` stage.
515
515
  *
516
- * Removes the first document that matches the filter from the collection.
517
- * No validation is performed — the document is deleted directly through
518
- * the MongoDB driver.
519
- *
520
- * @param handle - The collection handle to delete from.
521
- * @param filter - Type-safe filter to match documents.
522
- * @returns The MongoDB `DeleteResult` with the deleted count.
516
+ * The `_id` field holds the grouped-by field's value, and the rest of the
517
+ * shape comes from the inferred accumulator types.
523
518
  *
524
519
  * @example
525
520
  * ```ts
526
- * const result = await deleteOne(users, { name: 'Ada' })
527
- * console.log(result.deletedCount) // 1
521
+ * type Result = GroupByResult<Order, 'status', { count: Accumulator<number> }>
522
+ * // ^? { _id: string; count: number }
528
523
  * ```
529
524
  */
530
- declare function deleteOne<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>): Promise<DeleteResult>;
525
+ type GroupByResult<T, K extends keyof T, TAccum extends Record<string, Accumulator>> = Prettify<{
526
+ _id: T[K];
527
+ } & InferAccumulators<TAccum>>;
531
528
  /**
532
- * Delete all documents matching the filter.
533
- *
534
- * Removes every document that matches the filter from the collection.
535
- * No validation is performed — documents are deleted directly through
536
- * the MongoDB driver.
529
+ * Output shape of a compound (multi-field) `groupBy` stage.
537
530
  *
538
- * @param handle - The collection handle to delete from.
539
- * @param filter - Type-safe filter to match documents.
540
- * @returns The MongoDB `DeleteResult` with the deleted count.
531
+ * The `_id` field is an object with one property per grouped field,
532
+ * and the rest of the shape comes from the inferred accumulator types.
541
533
  *
542
534
  * @example
543
535
  * ```ts
544
- * const result = await deleteMany(users, { role: 'guest' })
545
- * console.log(result.deletedCount) // number of guests removed
536
+ * type Result = GroupByCompoundResult<Order, 'status' | 'region', { count: Accumulator<number> }>
537
+ * // ^? { _id: Pick<Order, 'status' | 'region'>; count: number }
546
538
  * ```
547
539
  */
548
- declare function deleteMany<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>): Promise<DeleteResult>;
540
+ type GroupByCompoundResult<T, K extends keyof T, TAccum extends Record<string, Accumulator>> = Prettify<{
541
+ _id: Prettify<Pick<T, K>>;
542
+ } & InferAccumulators<TAccum>>;
549
543
  /**
550
- * Find a single document matching the filter, delete it, and return the document.
544
+ * Output shape after unwinding an array field.
551
545
  *
552
- * Returns the deleted document, or `null` if no document matches the filter.
553
- * The returned document is validated against the collection's Zod schema
554
- * using the same resolution logic as {@link findOne}.
546
+ * The unwound field's type changes from `Array<E>` to `E`; all other
547
+ * fields are preserved as-is.
555
548
  *
556
- * @param handle - The collection handle to delete from.
557
- * @param filter - Type-safe filter to match documents.
558
- * @param options - Optional settings: `validate`.
559
- * @returns The deleted document, or `null` if no document matches.
560
- * @throws {ZodmonValidationError} When the returned document fails schema validation in strict mode.
549
+ * @example
550
+ * ```ts
551
+ * type Order = { items: string[]; total: number }
552
+ * type Unwound = UnwindResult<Order, 'items'>
553
+ * // ^? { items: string; total: number }
554
+ * ```
555
+ */
556
+ type UnwindResult<T, K extends keyof T> = {
557
+ [P in keyof T]: P extends K ? (T[P] extends ReadonlyArray<infer E> ? E : T[P]) : T[P];
558
+ };
559
+ /**
560
+ * Narrows document field types based on a filter expression.
561
+ *
562
+ * Inspects each field in the filter for value-constraining operators
563
+ * (`$eq`, `$in`, `$ne`, `$nin`, or direct equality) and narrows the
564
+ * corresponding field type in the output. Fields not mentioned in the
565
+ * filter, or filtered with unsupported operators, keep their original type.
566
+ *
567
+ * Used internally by {@link AggregatePipeline.match} for automatic
568
+ * type narrowing (Tier 2). Requires `as const` on array literals
569
+ * for `$in` / `$nin` inference.
561
570
  *
562
571
  * @example
563
572
  * ```ts
564
- * const user = await findOneAndDelete(users, { name: 'Ada' })
565
- * if (user) console.log(user.name) // 'Ada' (the deleted document)
573
+ * type Narrowed = NarrowFromFilter<Employee, { role: 'engineer' }>
574
+ * // ^? { ..., role: 'engineer', ... }
575
+ *
576
+ * type Subset = NarrowFromFilter<Employee, { role: { $in: readonly ['engineer', 'designer'] } }>
577
+ * // ^? { ..., role: 'engineer' | 'designer', ... }
578
+ *
579
+ * type Excluded = NarrowFromFilter<Employee, { role: { $ne: 'intern' } }>
580
+ * // ^? { ..., role: 'engineer' | 'manager' | 'designer', ... }
566
581
  * ```
582
+ */
583
+ type NarrowFromFilter<T, F> = {
584
+ [K in keyof T]: K extends keyof F ? F[K] extends T[K] ? F[K] : F[K] extends {
585
+ $eq: infer V extends T[K];
586
+ } ? V : F[K] extends {
587
+ $in: ReadonlyArray<infer V extends T[K]>;
588
+ } ? V : F[K] extends {
589
+ $ne: infer V extends T[K];
590
+ } ? Exclude<T[K], V> : F[K] extends {
591
+ $nin: ReadonlyArray<infer V extends T[K]>;
592
+ } ? Exclude<T[K], V> : T[K] : T[K];
593
+ };
594
+ /**
595
+ * Extract field keys from a collection definition whose schema fields
596
+ * carry `RefMarker` (i.e. fields that have `.ref()` metadata).
567
597
  *
568
598
  * @example
569
599
  * ```ts
570
- * const user = await findOneAndDelete(
571
- * users,
572
- * { role: 'guest' },
573
- * { validate: false },
574
- * )
600
+ * type EmpRefs = RefFields<typeof Employees> // → 'departmentId'
575
601
  * ```
576
602
  */
577
- declare function findOneAndDelete<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOneAndDeleteOptions): Promise<InferDocument<TDef> | null>;
578
-
603
+ type RefFields<TDef extends AnyCollection> = {
604
+ [K in keyof TDef['shape'] & string]: TDef['shape'][K] extends RefMarker ? K : never;
605
+ }[keyof TDef['shape'] & string];
579
606
  /**
580
- * Options for offset-based pagination.
607
+ * Given a collection definition and a ref-bearing field key, extract
608
+ * the target collection type from the `RefMarker<TCollection>` phantom.
581
609
  *
582
610
  * @example
583
611
  * ```ts
584
- * await users.find({}).sort({ name: 1 }).paginate({ page: 2, perPage: 10 })
612
+ * type Target = ExtractRefCollection<typeof Employees, 'departmentId'>
613
+ * // → typeof Departments
585
614
  * ```
586
615
  */
587
- type OffsetPaginateOptions = {
588
- /** The page number to retrieve (1-indexed). */
589
- page: number;
590
- /** The number of documents per page. */
591
- perPage: number;
592
- };
616
+ type ExtractRefCollection<TDef extends AnyCollection, K extends RefFields<TDef>> = TDef['shape'][K] extends RefMarker<infer TCol> ? TCol : never;
593
617
  /**
594
- * Options for cursor-based pagination.
618
+ * Extract the collection name string literal from a collection definition.
595
619
  *
596
620
  * @example
597
621
  * ```ts
598
- * const first = await users.find({}).sort({ name: 1 }).paginate({ limit: 10 })
599
- * const next = await users.find({}).sort({ name: 1 }).paginate({ cursor: first.endCursor, limit: 10 })
622
+ * CollectionName<typeof Departments> // 'departments'
600
623
  * ```
601
624
  */
602
- type CursorPaginateOptions = {
603
- /** Maximum number of documents to return. */
604
- limit: number;
605
- /** Opaque cursor string from a previous `startCursor` or `endCursor`. */
606
- cursor?: string | null;
607
- };
625
+ type CollectionName<TDef extends AnyCollection> = TDef['name'];
608
626
  /**
609
- * Result of offset-based pagination.
627
+ * Branded wrapper for computed expressions used inside `addFields`.
628
+ *
629
+ * Carries the MongoDB expression at runtime and the inferred
630
+ * result type `T` at the type level.
610
631
  *
611
632
  * @example
612
633
  * ```ts
613
- * const page = await users.find({}).paginate({ page: 1, perPage: 10 })
614
- * console.log(page.total, page.totalPages, page.hasNext)
634
+ * const yearExpr: Expression<number> = {
635
+ * __expr: true,
636
+ * value: { $year: '$hiredAt' },
637
+ * }
615
638
  * ```
616
639
  */
617
- type OffsetPage<TDoc> = {
618
- /** The documents for this page. */
619
- docs: TDoc[];
620
- /** Total number of matching documents. */
621
- total: number;
622
- /** Current page number (1-indexed). */
623
- page: number;
624
- /** Number of documents per page. */
625
- perPage: number;
626
- /** Total number of pages (`Math.ceil(total / perPage)`). */
627
- totalPages: number;
628
- /** Whether there is a next page. */
629
- hasNext: boolean;
630
- /** Whether there is a previous page. */
631
- hasPrev: boolean;
640
+ type Expression<T = unknown> = {
641
+ readonly __expr: true;
642
+ readonly value: Document;
643
+ /** @internal Phantom field carries the result type at the type level. */
644
+ readonly _type?: T;
632
645
  };
633
646
  /**
634
- * Result of cursor-based pagination.
647
+ * Unwrap an `Expression<T>` to its result type `T`.
648
+ * Non-Expression values resolve to `unknown`.
635
649
  *
636
650
  * @example
637
651
  * ```ts
638
- * const page = await users.find({}).paginate({ limit: 10 })
639
- * if (page.hasNext) {
640
- * const next = await users.find({}).paginate({ cursor: page.endCursor, limit: 10 })
641
- * }
652
+ * type N = InferExpression<Expression<number>> // number
653
+ * type U = InferExpression<{ $year: string }> // → unknown
642
654
  * ```
643
655
  */
644
- type CursorPage<TDoc> = {
645
- /** The documents for this page. */
646
- docs: TDoc[];
647
- /** Whether there are more documents after this page. */
648
- hasNext: boolean;
649
- /** Whether there are documents before this page. */
650
- hasPrev: boolean;
651
- /** Cursor for the first document (pass to `cursor` to go backward). `null` when empty. */
652
- startCursor: string | null;
653
- /** Cursor for the last document (pass to `cursor` to go forward). `null` when empty. */
654
- endCursor: string | null;
656
+ type InferExpression<T> = T extends Expression<infer R> ? R : unknown;
657
+ /**
658
+ * Map an added-fields spec to its resolved output shape.
659
+ *
660
+ * `Expression<V>` values unwrap to `V`. Raw expression objects resolve
661
+ * to `unknown`, signaling that type information is lost.
662
+ *
663
+ * @example
664
+ * ```ts
665
+ * type Spec = { year: Expression<number>; raw: { $year: string } }
666
+ * type Result = InferAddedFields<Spec>
667
+ * // ^? { year: number; raw: unknown }
668
+ * ```
669
+ */
670
+ type InferAddedFields<T extends Record<string, unknown>> = {
671
+ [K in keyof T]: InferExpression<T[K]>;
672
+ };
673
+ /**
674
+ * Typed expression factory passed to the `addFields` callback.
675
+ *
676
+ * Each method constrains field names to `keyof T & string`, providing
677
+ * IDE autocomplete and compile-time validation. Return types resolve
678
+ * to `Expression<R>` where `R` is the MongoDB operator's output type.
679
+ *
680
+ * @typeParam T - The current pipeline output document type.
681
+ *
682
+ * @example
683
+ * ```ts
684
+ * employees.aggregate()
685
+ * .addFields(expr => ({
686
+ * hireYear: expr.year('hiredAt'), // Expression<number>
687
+ * fullName: expr.concat('name', ' '), // Expression<string>
688
+ * isHighPay: expr.gte('salary', 100_000), // Expression<boolean>
689
+ * }))
690
+ * ```
691
+ */
692
+ type ExpressionBuilder<T> = {
693
+ /** Add a numeric field and a value. Returns `Expression<number>`. */
694
+ add<K extends keyof T & string>(field: K, value: number | (keyof T & string)): Expression<number>;
695
+ /** Subtract a value from a numeric field. Returns `Expression<number>`. */
696
+ subtract<K extends keyof T & string>(field: K, value: number | (keyof T & string)): Expression<number>;
697
+ /** Multiply a numeric field by a value. Returns `Expression<number>`. */
698
+ multiply<K extends keyof T & string>(field: K, value: number | (keyof T & string)): Expression<number>;
699
+ /** Divide a numeric field by a value. Returns `Expression<number>`. */
700
+ divide<K extends keyof T & string>(field: K, value: number | (keyof T & string)): Expression<number>;
701
+ /** Modulo of a numeric field. Returns `Expression<number>`. */
702
+ mod<K extends keyof T & string>(field: K, value: number | (keyof T & string)): Expression<number>;
703
+ /** Absolute value of a numeric field. Returns `Expression<number>`. */
704
+ abs<K extends keyof T & string>(field: K): Expression<number>;
705
+ /** Ceiling of a numeric field. Returns `Expression<number>`. */
706
+ ceil<K extends keyof T & string>(field: K): Expression<number>;
707
+ /** Floor of a numeric field. Returns `Expression<number>`. */
708
+ floor<K extends keyof T & string>(field: K): Expression<number>;
709
+ /** Round a numeric field to N decimal places. Returns `Expression<number>`. */
710
+ round<K extends keyof T & string>(field: K, place?: number): Expression<number>;
711
+ /** Concatenate field values and literal strings. Returns `Expression<string>`. */
712
+ concat(...parts: Array<(keyof T & string) | string>): Expression<string>;
713
+ /** Convert a string field to lowercase. Returns `Expression<string>`. */
714
+ toLower<K extends keyof T & string>(field: K): Expression<string>;
715
+ /** Convert a string field to uppercase. Returns `Expression<string>`. */
716
+ toUpper<K extends keyof T & string>(field: K): Expression<string>;
717
+ /** Trim whitespace from a string field. Returns `Expression<string>`. */
718
+ trim<K extends keyof T & string>(field: K): Expression<string>;
719
+ /** Extract a substring from a string field. Returns `Expression<string>`. */
720
+ substr<K extends keyof T & string>(field: K, start: number, length: number): Expression<string>;
721
+ /** Test equality of a field against a value. Returns `Expression<boolean>`. */
722
+ eq<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
723
+ /** Test if a field is greater than a value. Returns `Expression<boolean>`. */
724
+ gt<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
725
+ /** Test if a field is greater than or equal to a value. Returns `Expression<boolean>`. */
726
+ gte<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
727
+ /** Test if a field is less than a value. Returns `Expression<boolean>`. */
728
+ lt<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
729
+ /** Test if a field is less than or equal to a value. Returns `Expression<boolean>`. */
730
+ lte<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
731
+ /** Test if a field is not equal to a value. Returns `Expression<boolean>`. */
732
+ ne<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
733
+ /** Extract year from a date field. Returns `Expression<number>`. */
734
+ year<K extends keyof T & string>(field: K): Expression<number>;
735
+ /** Extract month (1-12) from a date field. Returns `Expression<number>`. */
736
+ month<K extends keyof T & string>(field: K): Expression<number>;
737
+ /** Extract day of month (1-31) from a date field. Returns `Expression<number>`. */
738
+ dayOfMonth<K extends keyof T & string>(field: K): Expression<number>;
739
+ /** Count elements in an array field. Returns `Expression<number>`. */
740
+ size<K extends keyof T & string>(field: K): Expression<number>;
741
+ /** Conditional expression. Returns `Expression<TThen | TElse>`. */
742
+ cond<TThen, TElse>(condition: Expression<boolean>, thenValue: TThen, elseValue: TElse): Expression<TThen | TElse>;
743
+ /** Replace null/missing field with a default. Returns `Expression<NonNullable<T[K]> | TDefault>`. */
744
+ ifNull<K extends keyof T & string, TDefault>(field: K, fallback: TDefault): Expression<NonNullable<T[K]> | TDefault>;
655
745
  };
656
746
 
657
747
  /**
658
- * Type-safe sort specification for a document type.
748
+ * Counts the number of documents in each group.
659
749
  *
660
- * Constrains sort keys to top-level fields of `T` with direction `1` (ascending)
661
- * or `-1` (descending). Dot-path sorts deferred to v1.0.
750
+ * Equivalent to `{ $sum: 1 }` in a MongoDB `$group` stage.
751
+ *
752
+ * @returns An `Accumulator<number>` that counts documents.
662
753
  *
663
754
  * @example
664
755
  * ```ts
665
- * const sort: TypedSort<User> = { name: 1, createdAt: -1 }
756
+ * const pipeline = orders.aggregate()
757
+ * .groupBy('status', { count: $count() })
666
758
  * ```
667
759
  */
668
- type TypedSort<T> = Partial<Record<keyof T & string, 1 | -1>>;
760
+ declare const $count: () => Accumulator<number>;
669
761
  /**
670
- * Type-safe cursor wrapping MongoDB's `FindCursor`.
762
+ * Sums numeric values across documents in each group.
671
763
  *
672
- * Provides chainable query modifiers (`sort`, `skip`, `limit`, `hint`) that return
673
- * `this` for fluent chaining, and terminal methods (`toArray`,
674
- * `[Symbol.asyncIterator]`) that validate each document against the
675
- * collection's Zod schema before returning.
764
+ * Accepts either a `$`-prefixed field reference or a literal number.
676
765
  *
677
- * Created by {@link find} do not construct directly.
766
+ * @param field - A `$field` reference to a numeric field, or a literal number.
767
+ * @returns An `Accumulator<number>`.
678
768
  *
679
- * @typeParam TDef - The collection definition type, used to infer the document type.
680
- * @typeParam TIndexNames - Union of declared index names accepted by `.hint()`.
769
+ * @example
770
+ * ```ts
771
+ * const pipeline = orders.aggregate()
772
+ * .groupBy('status', {
773
+ * total: $sum('$amount'),
774
+ * fixed: $sum(1),
775
+ * })
776
+ * ```
777
+ */
778
+ declare const $sum: (field: `$${string}` | number) => Accumulator<number>;
779
+ /**
780
+ * Computes the average of numeric values across documents in each group.
781
+ *
782
+ * @param field - A `$field` reference to a numeric field.
783
+ * @returns An `Accumulator<number>`.
681
784
  *
682
785
  * @example
683
786
  * ```ts
684
- * const docs = await find(users, { role: 'admin' })
685
- * .sort({ name: 1 })
686
- * .limit(10)
687
- * .toArray()
787
+ * const pipeline = orders.aggregate()
788
+ * .groupBy('category', { avgPrice: $avg('$price') })
688
789
  * ```
689
790
  */
690
- declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends string = string> {
691
- /** @internal */
692
- private cursor;
693
- /** @internal */
694
- private schema;
695
- /** @internal */
696
- private collectionName;
697
- /** @internal */
698
- private mode;
699
- /** @internal */
700
- private readonly nativeCollection;
701
- /** @internal */
702
- private readonly filter;
703
- /** @internal */
704
- private sortSpec;
705
- /** @internal */
706
- constructor(cursor: FindCursor<InferDocument<TDef>>, definition: TDef, mode: ValidationMode | false, nativeCollection: Collection<InferDocument<TDef>>, filter: any);
707
- /**
708
- * Set the sort order for the query.
709
- *
710
- * Only top-level document fields are accepted as sort keys.
711
- * Values must be `1` (ascending) or `-1` (descending).
712
- *
713
- * @param spec - Sort specification mapping field names to sort direction.
714
- * @returns `this` for chaining.
715
- *
716
- * @example
717
- * ```ts
718
- * find(users, {}).sort({ name: 1, age: -1 }).toArray()
719
- * ```
720
- */
721
- sort(spec: TypedSort<InferDocument<TDef>>): this;
722
- /**
723
- * Skip the first `n` documents in the result set.
724
- *
725
- * @param n - Number of documents to skip.
726
- * @returns `this` for chaining.
727
- *
728
- * @example
729
- * ```ts
730
- * find(users, {}).skip(10).limit(10).toArray() // page 2
731
- * ```
732
- */
733
- skip(n: number): this;
734
- /**
735
- * Limit the number of documents returned.
736
- *
737
- * @param n - Maximum number of documents to return.
738
- * @returns `this` for chaining.
739
- *
740
- * @example
741
- * ```ts
742
- * find(users, {}).limit(10).toArray() // at most 10 docs
743
- * ```
744
- */
745
- limit(n: number): this;
746
- /**
747
- * Force the query optimizer to use the specified index.
748
- *
749
- * Only accepts index names that were declared via `.name()` in the
750
- * collection definition. If no named indexes exist, any string is accepted.
751
- *
752
- * @param indexName - The name of a declared compound index.
753
- * @returns `this` for chaining.
754
- *
755
- * @example
756
- * ```ts
757
- * const Users = collection('users', { email: z.string(), role: z.string() }, {
758
- * indexes: [index({ email: 1, role: -1 }).name('email_role_idx')],
759
- * })
760
- * const admins = await users.find({ role: 'admin' })
761
- * .hint('email_role_idx')
762
- * .toArray()
763
- * ```
764
- */
765
- hint(indexName: TIndexNames): this;
766
- /**
767
- * Execute the query with offset-based pagination, returning a page of documents
768
- * with total count and navigation metadata.
769
- *
770
- * Runs `countDocuments` and `find` in parallel for performance. Ignores any
771
- * `.skip()` or `.limit()` already set on the cursor — issues a fresh query.
772
- *
773
- * @param opts - Offset pagination options: `page` (1-indexed) and `perPage`.
774
- * @returns A page with `docs`, `total`, `totalPages`, `hasNext`, `hasPrev`.
775
- * @throws {ZodmonValidationError} When a document fails schema validation.
776
- *
777
- * @example
778
- * ```ts
779
- * const page = await users.find({ role: 'admin' })
780
- * .sort({ createdAt: -1 })
781
- * .paginate({ page: 2, perPage: 10 })
782
- * console.log(page.total, page.totalPages, page.hasNext)
783
- * ```
784
- */
785
- paginate(opts: OffsetPaginateOptions): Promise<OffsetPage<InferDocument<TDef>>>;
786
- /**
787
- * Execute the query with cursor-based pagination, returning a page of documents
788
- * with opaque cursors for forward/backward navigation.
789
- *
790
- * Uses the `limit + 1` trick to determine `hasNext`/`hasPrev` without extra queries.
791
- * Direction is encoded in the cursor — pass `endCursor` to go forward, `startCursor`
792
- * to go backward.
793
- *
794
- * @param opts - Cursor pagination options: `limit` and optional `cursor`.
795
- * @returns A page with `docs`, `hasNext`, `hasPrev`, `startCursor`, `endCursor`.
796
- * @throws {ZodmonValidationError} When a document fails schema validation.
797
- * @throws {Error} When the cursor string is malformed.
798
- *
799
- * @example
800
- * ```ts
801
- * const first = await users.find({}).sort({ name: 1 }).paginate({ limit: 10 })
802
- * const next = await users.find({}).sort({ name: 1 })
803
- * .paginate({ cursor: first.endCursor, limit: 10 })
804
- * ```
805
- */
806
- paginate(opts: CursorPaginateOptions): Promise<CursorPage<InferDocument<TDef>>>;
807
- /** @internal Offset pagination implementation. */
808
- private offsetPaginate;
809
- /** @internal Cursor pagination implementation. */
810
- private cursorPaginate;
811
- /**
812
- * Execute the query and return all matching documents as an array.
813
- *
814
- * Each document is validated against the collection's Zod schema
815
- * according to the resolved validation mode.
816
- *
817
- * @returns Array of validated documents.
818
- * @throws {ZodmonValidationError} When a document fails schema validation in strict/strip mode.
819
- *
820
- * @example
821
- * ```ts
822
- * const admins = await find(users, { role: 'admin' }).toArray()
823
- * ```
824
- */
825
- toArray(): Promise<InferDocument<TDef>[]>;
826
- /**
827
- * Async iterator for streaming documents one at a time.
828
- *
829
- * Each yielded document is validated against the collection's Zod schema.
830
- * Memory-efficient for large result sets.
831
- *
832
- * @yields Validated documents one at a time.
833
- * @throws {ZodmonValidationError} When a document fails schema validation.
834
- *
835
- * @example
836
- * ```ts
837
- * for await (const user of find(users, {})) {
838
- * console.log(user.name)
839
- * }
840
- * ```
841
- */
842
- [Symbol.asyncIterator](): AsyncGenerator<InferDocument<TDef>>;
843
- /** @internal Validate a single raw document against the schema. */
844
- private validateDoc;
845
- }
846
-
847
- /**
848
- * Options for {@link findOne} and {@link findOneOrThrow}.
849
- */
850
- type FindOneOptions = {
851
- /** MongoDB projection — include (`1`) or exclude (`0`) fields. Typed projections deferred to v1.0. */
852
- project?: Record<string, 0 | 1>;
853
- /** Override the collection-level validation mode, or `false` to skip validation entirely. */
854
- validate?: ValidationMode | false;
855
- };
791
+ declare const $avg: (field: `$${string}`) => Accumulator<number>;
856
792
  /**
857
- * Find a single document matching the filter.
793
+ * Returns the minimum value across documents in each group.
858
794
  *
859
- * Queries MongoDB, then validates the fetched document against the collection's
860
- * Zod schema. Validation mode is resolved from the per-query option, falling
861
- * back to the collection-level default (which defaults to `'strict'`).
795
+ * For full type safety, prefer the callback builder (`acc.min('price')`).
796
+ * The standalone form accepts an explicit result type `R` for manual typing.
862
797
  *
863
- * @param handle - The collection handle to query.
864
- * @param filter - Type-safe filter to match documents.
865
- * @param options - Optional projection and validation overrides.
866
- * @returns The matched document, or `null` if no document matches.
867
- * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
798
+ * @typeParam R - The expected result type (defaults to `unknown`).
799
+ * @param field - A `$field` reference.
800
+ * @returns An `Accumulator<R>`.
868
801
  *
869
802
  * @example
870
803
  * ```ts
871
- * const user = await findOne(users, { name: 'Ada' })
872
- * if (user) console.log(user.role) // typed as 'admin' | 'user'
804
+ * // Tier 2 explicit result type
805
+ * const pipeline = orders.aggregate()
806
+ * .groupBy('category', { cheapest: $min<number>('$price') })
807
+ *
808
+ * // Tier 1 — callback builder (recommended)
809
+ * orders.aggregate()
810
+ * .groupBy('category', acc => ({ cheapest: acc.min('price') }))
873
811
  * ```
874
812
  */
875
- declare function findOne<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef> | null>;
813
+ declare const $min: <R = unknown>(field: `$${string}`) => Accumulator<R>;
876
814
  /**
877
- * Find a single document matching the filter, or throw if none exists.
815
+ * Returns the maximum value across documents in each group.
878
816
  *
879
- * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
880
- * instead of returning `null` when no document matches the filter.
817
+ * For full type safety, prefer the callback builder (`acc.max('price')`).
818
+ * The standalone form accepts an explicit result type `R` for manual typing.
881
819
  *
882
- * @param handle - The collection handle to query.
883
- * @param filter - Type-safe filter to match documents.
884
- * @param options - Optional projection and validation overrides.
885
- * @returns The matched document (never null).
886
- * @throws {ZodmonNotFoundError} When no document matches the filter.
887
- * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
820
+ * @typeParam R - The expected result type (defaults to `unknown`).
821
+ * @param field - A `$field` reference.
822
+ * @returns An `Accumulator<R>`.
888
823
  *
889
824
  * @example
890
825
  * ```ts
891
- * const user = await findOneOrThrow(users, { name: 'Ada' })
892
- * console.log(user.role) // typed as 'admin' | 'user', guaranteed non-null
826
+ * // Tier 2 explicit result type
827
+ * const pipeline = orders.aggregate()
828
+ * .groupBy('category', { priciest: $max<number>('$price') })
829
+ *
830
+ * // Tier 1 — callback builder (recommended)
831
+ * orders.aggregate()
832
+ * .groupBy('category', acc => ({ priciest: acc.max('price') }))
893
833
  * ```
894
834
  */
895
- declare function findOneOrThrow<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef>>;
896
- /**
897
- * Options for {@link find}.
898
- */
899
- type FindOptions = {
900
- /** Override the collection-level validation mode, or `false` to skip validation entirely. */
901
- validate?: ValidationMode | false;
902
- };
835
+ declare const $max: <R = unknown>(field: `$${string}`) => Accumulator<R>;
903
836
  /**
904
- * Find all documents matching the filter, returning a chainable typed cursor.
905
- *
906
- * The cursor is lazy — no query is executed until a terminal method
907
- * (`toArray`, `for await`) is called. Use `sort`, `skip`, and `limit`
908
- * to shape the query before executing.
837
+ * Returns the first value in each group according to the document order.
909
838
  *
910
- * Each document is validated against the collection's Zod schema when
911
- * a terminal method consumes it.
839
+ * For full type safety, prefer the callback builder (`acc.first('name')`).
840
+ * The standalone form accepts an explicit result type `R` for manual typing.
912
841
  *
913
- * @param handle - The collection handle to query.
914
- * @param filter - Type-safe filter to match documents.
915
- * @param options - Optional validation overrides.
916
- * @returns A typed cursor for chaining query modifiers.
842
+ * @typeParam R - The expected result type (defaults to `unknown`).
843
+ * @param field - A `$field` reference.
844
+ * @returns An `Accumulator<R>`.
917
845
  *
918
846
  * @example
919
847
  * ```ts
920
- * const admins = await find(users, { role: 'admin' })
921
- * .sort({ name: 1 })
922
- * .limit(10)
923
- * .toArray()
924
- * ```
848
+ * // Tier 2 explicit result type
849
+ * const pipeline = orders.aggregate()
850
+ * .sort({ createdAt: 1 })
851
+ * .groupBy('status', { earliest: $first<Date>('$createdAt') })
925
852
  *
926
- * @example
927
- * ```ts
928
- * for await (const user of find(users, {})) {
929
- * console.log(user.name)
930
- * }
853
+ * // Tier 1 — callback builder (recommended)
854
+ * orders.aggregate()
855
+ * .sort({ createdAt: 1 })
856
+ * .groupBy('status', acc => ({ earliest: acc.first('createdAt') }))
931
857
  * ```
932
858
  */
933
- declare function find<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOptions): TypedFindCursor<TDef, IndexNames<TDef>>;
934
-
859
+ declare const $first: <R = unknown>(field: `$${string}`) => Accumulator<R>;
935
860
  /**
936
- * Extracts the element type from an array type.
861
+ * Returns the last value in each group according to the document order.
937
862
  *
938
- * @example
939
- * ```ts
940
- * type E = ArrayElement<string[]> // string
941
- * type N = ArrayElement<number[]> // number
942
- * ```
943
- */
944
- type ArrayElement<T> = T extends ReadonlyArray<infer E> ? E : never;
945
- /**
946
- * Fields valid for `$set`, `$setOnInsert`, `$min`, and `$max` operators.
863
+ * For full type safety, prefer the callback builder (`acc.last('name')`).
864
+ * The standalone form accepts an explicit result type `R` for manual typing.
947
865
  *
948
- * Allows top-level fields with matching value types plus dot-notation paths
949
- * for nested field updates.
866
+ * @typeParam R - The expected result type (defaults to `unknown`).
867
+ * @param field - A `$field` reference.
868
+ * @returns An `Accumulator<R>`.
950
869
  *
951
870
  * @example
952
871
  * ```ts
953
- * const set: SetFields<User> = { name: 'Alice', 'address.city': 'NYC' }
872
+ * // Tier 2 explicit result type
873
+ * const pipeline = orders.aggregate()
874
+ * .sort({ createdAt: -1 })
875
+ * .groupBy('status', { latest: $last<Date>('$createdAt') })
876
+ *
877
+ * // Tier 1 — callback builder (recommended)
878
+ * orders.aggregate()
879
+ * .sort({ createdAt: -1 })
880
+ * .groupBy('status', acc => ({ latest: acc.last('createdAt') }))
954
881
  * ```
955
882
  */
956
- type SetFields<T> = {
957
- [K in keyof T]?: T[K];
958
- } & {
959
- [P in DotPaths<T>]?: DotPathType<T, P>;
960
- };
883
+ declare const $last: <R = unknown>(field: `$${string}`) => Accumulator<R>;
961
884
  /**
962
- * Fields valid for the `$inc` operator only number-typed fields.
885
+ * Pushes values into an array for each group.
963
886
  *
964
- * Includes dot-notation paths that resolve to number types.
887
+ * May contain duplicates. Use `$addToSet` for unique values.
888
+ * For full type safety, prefer the callback builder (`acc.push('name')`).
889
+ *
890
+ * @typeParam R - The element type (defaults to `unknown`).
891
+ * @param field - A `$field` reference.
892
+ * @returns An `Accumulator<R[]>`.
965
893
  *
966
894
  * @example
967
895
  * ```ts
968
- * const inc: IncFields<User> = { age: 1, 'stats.views': 5 }
896
+ * // Tier 2 explicit element type
897
+ * const pipeline = orders.aggregate()
898
+ * .groupBy('status', { items: $push<string>('$name') })
899
+ *
900
+ * // Tier 1 — callback builder (recommended)
901
+ * orders.aggregate()
902
+ * .groupBy('status', acc => ({ items: acc.push('name') }))
969
903
  * ```
970
904
  */
971
- type IncFields<T> = {
972
- [K in keyof T as NonNullable<T[K]> extends number ? K : never]?: number;
973
- } & {
974
- [P in DotPaths<T> as DotPathType<T, P> extends number ? P : never]?: number;
975
- };
905
+ declare const $push: <R = unknown>(field: `$${string}`) => Accumulator<R[]>;
976
906
  /**
977
- * Modifiers for the `$push` operator.
907
+ * Collects unique values into an array for each group (set semantics).
978
908
  *
979
- * Use with `$push` to insert multiple elements and control array position/size.
909
+ * Like `$push` but deduplicates.
910
+ * For full type safety, prefer the callback builder (`acc.addToSet('tag')`).
911
+ *
912
+ * @typeParam R - The element type (defaults to `unknown`).
913
+ * @param field - A `$field` reference.
914
+ * @returns An `Accumulator<R[]>`.
980
915
  *
981
916
  * @example
982
917
  * ```ts
983
- * const push = { tags: { $each: ['a', 'b'], $position: 0, $slice: 10 } }
918
+ * // Tier 2 explicit element type
919
+ * const pipeline = orders.aggregate()
920
+ * .groupBy('category', { uniqueTags: $addToSet<string>('$tag') })
921
+ *
922
+ * // Tier 1 — callback builder (recommended)
923
+ * orders.aggregate()
924
+ * .groupBy('category', acc => ({ uniqueTags: acc.addToSet('tag') }))
984
925
  * ```
985
926
  */
986
- type PushModifiers<E> = {
987
- /** Array of elements to push. */
988
- $each: E[];
989
- /** Position at which to insert elements. */
990
- $position?: number;
991
- /** Maximum array length after push. */
992
- $slice?: number;
993
- /** Sort order applied after push. */
994
- $sort?: 1 | -1 | Record<string, 1 | -1>;
995
- };
927
+ declare const $addToSet: <R = unknown>(field: `$${string}`) => Accumulator<R[]>;
996
928
  /**
997
- * Fields valid for the `$push` operator only array-typed fields.
929
+ * Create a typed accumulator builder for use inside `groupBy` callbacks.
998
930
  *
999
- * Accepts a single element value or a modifier object with `$each`.
931
+ * The builder adds the `$` prefix to field names automatically and returns
932
+ * properly typed `Accumulator<T[K]>` values. Primarily used internally by
933
+ * `AggregatePipeline.groupBy` — most users interact with the builder via
934
+ * the callback parameter rather than calling this directly.
935
+ *
936
+ * @typeParam T - The current pipeline output document type.
937
+ * @returns An `AccumulatorBuilder<T>` with methods for each MongoDB accumulator.
1000
938
  *
1001
939
  * @example
1002
940
  * ```ts
1003
- * const push: PushFields<User> = { tags: 'dev' }
1004
- * const pushMany: PushFields<User> = { tags: { $each: ['a', 'b'] } }
941
+ * const acc = createAccumulatorBuilder<{ salary: number; name: string }>()
942
+ * acc.min('salary') // { __accum: true, expr: { $min: '$salary' } }
943
+ * acc.push('name') // { __accum: true, expr: { $push: '$name' } }
1005
944
  * ```
1006
945
  */
1007
- type PushFields<T> = {
1008
- [K in keyof T as NonNullable<T[K]> extends ReadonlyArray<unknown> ? K : never]?: ArrayElement<NonNullable<T[K]>> | PushModifiers<ArrayElement<NonNullable<T[K]>>>;
1009
- };
946
+ declare function createAccumulatorBuilder<T>(): AccumulatorBuilder<T>;
1010
947
  /**
1011
- * Fields valid for the `$pull` operator only array-typed fields.
948
+ * Create a typed expression builder for use inside `addFields` callbacks.
1012
949
  *
1013
- * For primitive arrays: accepts a value or comparison operators.
1014
- * For object arrays: also accepts a `TypedFilter` on the element type.
950
+ * The builder adds the `$` prefix to field names automatically and returns
951
+ * properly typed `Expression<T>` values. Primarily used internally by
952
+ * `AggregatePipeline.addFields` — most users interact with the builder via
953
+ * the callback parameter rather than calling this directly.
1015
954
  *
1016
- * @example
1017
- * ```ts
1018
- * const pull: PullFields<User> = { tags: 'old' }
1019
- * const pullQuery: PullFields<User> = { scores: { $lt: 50 } }
1020
- * const pullObj: PullFields<User> = { comments: { author: 'spam' } }
1021
- * ```
1022
- */
1023
- type PullFields<T> = {
1024
- [K in keyof T as NonNullable<T[K]> extends ReadonlyArray<unknown> ? K : never]?: ArrayElement<NonNullable<T[K]>> | ComparisonOperators<ArrayElement<NonNullable<T[K]>>> | (ArrayElement<NonNullable<T[K]>> extends Record<string, unknown> ? TypedFilter<ArrayElement<NonNullable<T[K]>>> : unknown);
1025
- };
1026
- /**
1027
- * Modifier for the `$addToSet` operator's `$each` syntax.
955
+ * @typeParam T - The current pipeline output document type.
956
+ * @returns An `ExpressionBuilder<T>` with methods for each MongoDB expression operator.
1028
957
  *
1029
958
  * @example
1030
959
  * ```ts
1031
- * const addToSet = { tags: { $each: ['a', 'b'] } }
960
+ * const expr = createExpressionBuilder<{ salary: number; name: string; hiredAt: Date }>()
961
+ * expr.year('hiredAt') // { __expr: true, value: { $year: '$hiredAt' } }
962
+ * expr.multiply('salary', 0.9) // { __expr: true, value: { $multiply: ['$salary', 0.9] } }
1032
963
  * ```
1033
964
  */
1034
- type AddToSetEach<E> = {
1035
- $each: E[];
1036
- };
965
+ declare function createExpressionBuilder<T>(): ExpressionBuilder<T>;
966
+
1037
967
  /**
1038
- * Fields valid for the `$addToSet` operator only array-typed fields.
968
+ * Comparison operators for a field value of type `V`.
1039
969
  *
1040
- * Accepts a single element value or `{ $each: [...] }` for multiple elements.
970
+ * Maps each MongoDB comparison operator to its expected value type.
971
+ * `$regex` is only available when `V` extends `string`.
972
+ *
973
+ * Used as the operator object that can be assigned to a field in {@link TypedFilter}.
1041
974
  *
1042
975
  * @example
1043
976
  * ```ts
1044
- * const add: AddToSetFields<User> = { tags: 'dev' }
1045
- * const addMany: AddToSetFields<User> = { tags: { $each: ['a', 'b'] } }
977
+ * // As a raw object (without builder functions)
978
+ * const filter: TypedFilter<User> = { age: { $gt: 25, $lte: 65 } }
979
+ *
980
+ * // $regex only available on string fields
981
+ * const filter: TypedFilter<User> = { name: { $regex: /^A/i } }
1046
982
  * ```
1047
983
  */
1048
- type AddToSetFields<T> = {
1049
- [K in keyof T as NonNullable<T[K]> extends ReadonlyArray<unknown> ? K : never]?: ArrayElement<NonNullable<T[K]>> | AddToSetEach<ArrayElement<NonNullable<T[K]>>>;
1050
- };
984
+ type ComparisonOperators<V> = {
985
+ /** Matches values equal to the specified value. */
986
+ $eq?: V;
987
+ /** Matches values not equal to the specified value. */
988
+ $ne?: V;
989
+ /** Matches values greater than the specified value. */
990
+ $gt?: V;
991
+ /** Matches values greater than or equal to the specified value. */
992
+ $gte?: V;
993
+ /** Matches values less than the specified value. */
994
+ $lt?: V;
995
+ /** Matches values less than or equal to the specified value. */
996
+ $lte?: V;
997
+ /** Matches any value in the specified array. Accepts `as const` arrays for type narrowing. */
998
+ $in?: readonly V[];
999
+ /** Matches none of the values in the specified array. Accepts `as const` arrays for type narrowing. */
1000
+ $nin?: readonly V[];
1001
+ /** Matches documents where the field exists (`true`) or does not exist (`false`). */
1002
+ $exists?: boolean;
1003
+ /** Negates a comparison operator. */
1004
+ $not?: ComparisonOperators<V>;
1005
+ } & (V extends string ? {
1006
+ $regex?: RegExp | string;
1007
+ } : unknown);
1008
+ /** Depth counter for limiting dot-notation recursion. Index = current depth, value = next depth. */
1009
+ type Prev = [never, 0, 1, 2];
1051
1010
  /**
1052
- * Fields valid for the `$pop` operator only array-typed fields.
1011
+ * Generates a union of all valid dot-separated paths for nested object fields in `T`.
1053
1012
  *
1054
- * Value `1` removes the last element, `-1` removes the first.
1013
+ * Recursion is limited to 3 levels deep to prevent TypeScript compilation performance issues.
1014
+ * Only plain object fields are traversed — arrays, `Date`, `RegExp`, and `ObjectId` are
1015
+ * treated as leaf nodes and do not produce sub-paths.
1055
1016
  *
1056
1017
  * @example
1057
1018
  * ```ts
1058
- * const pop: PopFields<User> = { tags: -1 } // remove first
1019
+ * type User = { address: { city: string; geo: { lat: number; lng: number } } }
1020
+ *
1021
+ * // DotPaths<User> = 'address.city' | 'address.geo' | 'address.geo.lat' | 'address.geo.lng'
1059
1022
  * ```
1060
1023
  */
1061
- type PopFields<T> = {
1062
- [K in keyof T as NonNullable<T[K]> extends ReadonlyArray<unknown> ? K : never]?: 1 | -1;
1063
- };
1024
+ type DotPaths<T, Depth extends number = 3> = Depth extends 0 ? never : {
1025
+ [K in keyof T & string]: NonNullable<T[K]> extends ReadonlyArray<unknown> | Date | RegExp | ObjectId ? never : NonNullable<T[K]> extends Record<string, unknown> ? `${K}.${keyof NonNullable<T[K]> & string}` | `${K}.${DotPaths<NonNullable<T[K]>, Prev[Depth]>}` : never;
1026
+ }[keyof T & string];
1064
1027
  /**
1065
- * Fields valid for the `$unset` operator any existing field.
1028
+ * Resolves the value type at a dot-separated path `P` within type `T`.
1066
1029
  *
1067
- * Value is `''`, `true`, or `1` (all mean "remove this field").
1030
+ * Splits `P` on the first `.` and recursively descends into `T`'s nested types.
1031
+ * Returns `never` if the path is invalid.
1068
1032
  *
1069
1033
  * @example
1070
1034
  * ```ts
1071
- * const unset: UnsetFields<User> = { middleName: '' }
1035
+ * type User = { address: { city: string; geo: { lat: number } } }
1036
+ *
1037
+ * // DotPathType<User, 'address.city'> = string
1038
+ * // DotPathType<User, 'address.geo.lat'> = number
1072
1039
  * ```
1073
1040
  */
1074
- type UnsetFields<T> = {
1075
- [K in keyof T]?: '' | true | 1;
1076
- };
1041
+ type DotPathType<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? Rest extends keyof NonNullable<T[K]> ? NonNullable<T[K]>[Rest] : DotPathType<NonNullable<T[K]>, Rest> : never : P extends keyof T ? T[P] : never;
1077
1042
  /**
1078
- * Fields valid for the `$currentDate` operator — only Date-typed fields.
1043
+ * Strict type-safe MongoDB filter query type.
1079
1044
  *
1080
- * Sets the field to the current date. Value is `true` or `{ $type: 'date' }`.
1045
+ * Validates filter objects at compile time rejects nonexistent fields, type mismatches,
1046
+ * and invalid operator usage. Unlike the MongoDB driver's `Filter<T>`, does NOT allow
1047
+ * arbitrary keys via `& Document`.
1081
1048
  *
1082
- * @example
1083
- * ```ts
1084
- * const cd: CurrentDateFields<User> = { createdAt: true }
1085
- * ```
1086
- */
1087
- type CurrentDateFields<T> = {
1088
- [K in keyof T as NonNullable<T[K]> extends Date ? K : never]?: true | {
1089
- $type: 'date';
1090
- };
1091
- };
1092
- /**
1093
- * Fields valid for the `$rename` operator.
1049
+ * Supports three forms of filter expressions:
1050
+ * - **Direct field values** (implicit `$eq`): `{ name: 'Alice' }`
1051
+ * - **Comparison operators**: `{ age: { $gt: 25 } }` or `{ age: $gt(25) }`
1052
+ * - **Dot notation** for nested fields up to 3 levels: `{ 'address.city': 'NYC' }`
1094
1053
  *
1095
- * Renames an existing field to a new name. Key is the current field name,
1096
- * value is the new name as a string.
1054
+ * Logical operators `$and`, `$or`, and `$nor` accept arrays of `TypedFilter<T>`
1055
+ * for composing complex queries.
1097
1056
  *
1098
1057
  * @example
1099
1058
  * ```ts
1100
- * const rename: RenameFields<User> = { name: 'fullName' }
1101
- * ```
1102
- */
1103
- type RenameFields<T> = {
1104
- [K in keyof T & string]?: string;
1105
- };
1106
- /**
1107
- * Strict type-safe MongoDB update filter.
1108
- *
1109
- * Validates update operators against field types at compile time. Each operator
1110
- * constrains which fields it accepts (e.g. `$inc` only on numbers, `$push` only
1111
- * on arrays). Supports dot-notation paths for nested field access.
1059
+ * // Simple equality
1060
+ * const filter: TypedFilter<User> = { name: 'Alice' }
1112
1061
  *
1113
- * @example
1114
- * ```ts
1115
- * const update: TypedUpdateFilter<User> = {
1116
- * $set: { name: 'Alice' },
1117
- * $inc: { age: 1 },
1118
- * }
1062
+ * // Builder functions mixed with object literals
1063
+ * const filter: TypedFilter<User> = { age: $gte(18), role: $in(['admin', 'mod']) }
1064
+ *
1065
+ * // Logical composition — T inferred from find() context
1066
+ * posts.find($and(
1067
+ * $or({ published: true }, { views: $gte(100) }),
1068
+ * { title: $regex(/guide/i) },
1069
+ * ))
1070
+ *
1071
+ * // Dynamic conditional building
1072
+ * const conditions: TypedFilter<User>[] = []
1073
+ * if (name) conditions.push({ name })
1074
+ * if (minAge) conditions.push({ age: $gte(minAge) })
1075
+ * const filter = conditions.length ? $and<User>(...conditions) : {}
1119
1076
  * ```
1120
1077
  */
1121
- type TypedUpdateFilter<T> = {
1122
- /** Sets the value of one or more fields. */
1123
- $set?: SetFields<T>;
1124
- /** Sets fields only when inserting (upsert). Same typing as `$set`. */
1125
- $setOnInsert?: SetFields<T>;
1126
- /** Increments numeric fields by the given amount. Only accepts number-typed fields. */
1127
- $inc?: IncFields<T>;
1128
- /** Updates the field if the given value is less than the current value. */
1129
- $min?: SetFields<T>;
1130
- /** Updates the field if the given value is greater than the current value. */
1131
- $max?: SetFields<T>;
1132
- /** Appends a value to an array field. Supports `$each`, `$position`, `$slice`, `$sort`. */
1133
- $push?: PushFields<T>;
1134
- /** Removes matching values from an array field. */
1135
- $pull?: PullFields<T>;
1136
- /** Adds a value to an array only if it doesn't already exist. */
1137
- $addToSet?: AddToSetFields<T>;
1138
- /** Removes the first (`-1`) or last (`1`) element of an array. */
1139
- $pop?: PopFields<T>;
1140
- /** Removes the specified fields from the document. */
1141
- $unset?: UnsetFields<T>;
1142
- /** Sets the field to the current date. Only accepts Date-typed fields. */
1143
- $currentDate?: CurrentDateFields<T>;
1144
- /** Renames a field. */
1145
- $rename?: RenameFields<T>;
1078
+ type TypedFilter<T> = {
1079
+ [K in keyof T]?: T[K] | ComparisonOperators<T[K]>;
1080
+ } & {
1081
+ [P in DotPaths<T>]?: DotPathType<T, P> | ComparisonOperators<DotPathType<T, P>>;
1082
+ } & {
1083
+ /** Joins clauses with a logical AND. Matches documents that satisfy all filters. */
1084
+ $and?: TypedFilter<T>[];
1085
+ /** Joins clauses with a logical OR. Matches documents that satisfy at least one filter. */
1086
+ $or?: TypedFilter<T>[];
1087
+ /** Joins clauses with a logical NOR. Matches documents that fail all filters. */
1088
+ $nor?: TypedFilter<T>[];
1146
1089
  };
1147
1090
 
1148
1091
  /**
1149
- * Options for {@link updateOne} and {@link updateMany}.
1150
- */
1151
- type UpdateOptions = {
1152
- /** When `true`, inserts a new document if no document matches the filter. */
1153
- upsert?: boolean;
1154
- };
1155
- /**
1156
- * Options for {@link findOneAndUpdate}.
1092
+ * Options for {@link findOneAndDelete}.
1157
1093
  */
1158
- type FindOneAndUpdateOptions = {
1159
- /** Whether to return the document before or after the update. Defaults to `'after'`. */
1160
- returnDocument?: 'before' | 'after';
1161
- /** When `true`, inserts a new document if no document matches the filter. */
1162
- upsert?: boolean;
1094
+ type FindOneAndDeleteOptions = {
1163
1095
  /** Override the collection-level validation mode, or `false` to skip validation entirely. */
1164
1096
  validate?: ValidationMode | false;
1165
1097
  };
1166
1098
  /**
1167
- * Update a single document matching the filter.
1099
+ * Delete a single document matching the filter.
1168
1100
  *
1169
- * Applies the update operators to the first document that matches the filter.
1170
- * Does not validate the update against the Zod schema validation happens
1171
- * at the field-operator level through {@link TypedUpdateFilter}.
1101
+ * Removes the first document that matches the filter from the collection.
1102
+ * No validation is performed the document is deleted directly through
1103
+ * the MongoDB driver.
1172
1104
  *
1173
- * @param handle - The collection handle to update in.
1105
+ * @param handle - The collection handle to delete from.
1174
1106
  * @param filter - Type-safe filter to match documents.
1175
- * @param update - Type-safe update operators to apply.
1176
- * @param options - Optional settings such as `upsert`.
1177
- * @returns The MongoDB `UpdateResult` with match/modify counts.
1107
+ * @returns The MongoDB `DeleteResult` with the deleted count.
1178
1108
  *
1179
1109
  * @example
1180
1110
  * ```ts
1181
- * const result = await updateOne(users, { name: 'Ada' }, { $set: { role: 'admin' } })
1182
- * console.log(result.modifiedCount) // 1
1111
+ * const result = await deleteOne(users, { name: 'Ada' })
1112
+ * console.log(result.deletedCount) // 1
1183
1113
  * ```
1184
1114
  */
1185
- declare function updateOne<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: UpdateOptions): Promise<UpdateResult>;
1115
+ declare function deleteOne<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>): Promise<DeleteResult>;
1186
1116
  /**
1187
- * Update all documents matching the filter.
1117
+ * Delete all documents matching the filter.
1188
1118
  *
1189
- * Applies the update operators to every document that matches the filter.
1190
- * Does not validate the update against the Zod schema — validation happens
1191
- * at the field-operator level through {@link TypedUpdateFilter}.
1119
+ * Removes every document that matches the filter from the collection.
1120
+ * No validation is performed documents are deleted directly through
1121
+ * the MongoDB driver.
1192
1122
  *
1193
- * @param handle - The collection handle to update in.
1123
+ * @param handle - The collection handle to delete from.
1194
1124
  * @param filter - Type-safe filter to match documents.
1195
- * @param update - Type-safe update operators to apply.
1196
- * @param options - Optional settings such as `upsert`.
1197
- * @returns The MongoDB `UpdateResult` with match/modify counts.
1125
+ * @returns The MongoDB `DeleteResult` with the deleted count.
1198
1126
  *
1199
1127
  * @example
1200
1128
  * ```ts
1201
- * const result = await updateMany(users, { role: 'guest' }, { $set: { role: 'user' } })
1202
- * console.log(result.modifiedCount) // number of guests promoted
1129
+ * const result = await deleteMany(users, { role: 'guest' })
1130
+ * console.log(result.deletedCount) // number of guests removed
1203
1131
  * ```
1204
1132
  */
1205
- declare function updateMany<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: UpdateOptions): Promise<UpdateResult>;
1133
+ declare function deleteMany<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>): Promise<DeleteResult>;
1206
1134
  /**
1207
- * Find a single document matching the filter, apply an update, and return the document.
1135
+ * Find a single document matching the filter, delete it, and return the document.
1208
1136
  *
1209
- * By default, returns the document **after** the update is applied. Set
1210
- * `returnDocument: 'before'` to get the pre-update snapshot. The returned
1211
- * document is validated against the collection's Zod schema using the same
1212
- * resolution logic as {@link findOne}.
1137
+ * Returns the deleted document, or `null` if no document matches the filter.
1138
+ * The returned document is validated against the collection's Zod schema
1139
+ * using the same resolution logic as {@link findOne}.
1213
1140
  *
1214
- * @param handle - The collection handle to update in.
1141
+ * @param handle - The collection handle to delete from.
1215
1142
  * @param filter - Type-safe filter to match documents.
1216
- * @param update - Type-safe update operators to apply.
1217
- * @param options - Optional settings: `returnDocument`, `upsert`, `validate`.
1218
- * @returns The matched document (before or after update), or `null` if no document matches.
1143
+ * @param options - Optional settings: `validate`.
1144
+ * @returns The deleted document, or `null` if no document matches.
1219
1145
  * @throws {ZodmonValidationError} When the returned document fails schema validation in strict mode.
1220
1146
  *
1221
1147
  * @example
1222
1148
  * ```ts
1223
- * const user = await findOneAndUpdate(
1224
- * users,
1225
- * { name: 'Ada' },
1226
- * { $set: { role: 'admin' } },
1227
- * )
1228
- * if (user) console.log(user.role) // 'admin' (returned after update)
1149
+ * const user = await findOneAndDelete(users, { name: 'Ada' })
1150
+ * if (user) console.log(user.name) // 'Ada' (the deleted document)
1229
1151
  * ```
1230
1152
  *
1231
1153
  * @example
1232
1154
  * ```ts
1233
- * const before = await findOneAndUpdate(
1155
+ * const user = await findOneAndDelete(
1234
1156
  * users,
1235
- * { name: 'Ada' },
1236
- * { $inc: { loginCount: 1 } },
1237
- * { returnDocument: 'before' },
1157
+ * { role: 'guest' },
1158
+ * { validate: false },
1238
1159
  * )
1239
1160
  * ```
1240
1161
  */
1241
- declare function findOneAndUpdate<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: FindOneAndUpdateOptions): Promise<InferDocument<TDef> | null>;
1162
+ declare function findOneAndDelete<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOneAndDeleteOptions): Promise<InferDocument<TDef> | null>;
1242
1163
 
1243
1164
  /**
1244
- * Typed wrapper around a MongoDB driver `Collection`.
1165
+ * Options for offset-based pagination.
1245
1166
  *
1246
- * Created by {@link Database.use}. Holds the original `CollectionDefinition`
1247
- * (for runtime schema validation and index metadata) alongside the native
1248
- * driver collection parameterized with the inferred document type.
1167
+ * @example
1168
+ * ```ts
1169
+ * await users.find({}).sort({ name: 1 }).paginate({ page: 2, perPage: 10 })
1170
+ * ```
1171
+ */
1172
+ type OffsetPaginateOptions = {
1173
+ /** The page number to retrieve (1-indexed). */
1174
+ page: number;
1175
+ /** The number of documents per page. */
1176
+ perPage: number;
1177
+ };
1178
+ /**
1179
+ * Options for cursor-based pagination.
1249
1180
  *
1250
- * @typeParam TDef - The collection definition type. Used to derive both
1251
- * the document type (`InferDocument`) and the insert type (`InferInsert`).
1181
+ * @example
1182
+ * ```ts
1183
+ * const first = await users.find({}).sort({ name: 1 }).paginate({ limit: 10 })
1184
+ * const next = await users.find({}).sort({ name: 1 }).paginate({ cursor: first.endCursor, limit: 10 })
1185
+ * ```
1252
1186
  */
1253
- declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
1254
- /** The collection definition containing schema, name, and index metadata. */
1255
- readonly definition: TDef;
1256
- /** The underlying MongoDB driver collection, typed to the inferred document type. */
1257
- readonly native: Collection<InferDocument<TDef>>;
1258
- constructor(definition: TDef, native: Collection<InferDocument<TDef>>);
1259
- /**
1260
- * Insert a single document into the collection.
1261
- *
1262
- * Validates the input against the collection's Zod schema before writing.
1263
- * Schema defaults (including auto-generated `_id`) are applied during
1264
- * validation. Returns the full document with all defaults filled in.
1265
- *
1266
- * @param doc - The document to insert. Fields with `.default()` are optional.
1267
- * @returns The inserted document with `_id` and all defaults applied.
1268
- * @throws {ZodmonValidationError} When the document fails schema validation.
1269
- *
1270
- * @example
1271
- * ```ts
1272
- * const users = db.use(Users)
1273
- * const user = await users.insertOne({ name: 'Ada' })
1274
- * console.log(user._id) // ObjectId (auto-generated)
1275
- * console.log(user.role) // 'user' (schema default)
1276
- * ```
1277
- */
1278
- insertOne(doc: InferInsert<TDef>): Promise<InferDocument<TDef>>;
1279
- /**
1280
- * Insert multiple documents into the collection.
1281
- *
1282
- * Validates every document against the collection's Zod schema before
1283
- * writing any to MongoDB. If any document fails validation, none are
1284
- * inserted (fail-fast before the driver call).
1285
- *
1286
- * @param docs - The documents to insert.
1287
- * @returns The inserted documents with `_id` and all defaults applied.
1288
- * @throws {ZodmonValidationError} When any document fails schema validation.
1289
- *
1290
- * @example
1291
- * ```ts
1292
- * const created = await users.insertMany([
1293
- * { name: 'Ada' },
1294
- * { name: 'Bob', role: 'admin' },
1295
- * ])
1296
- * ```
1297
- */
1298
- insertMany(docs: InferInsert<TDef>[]): Promise<InferDocument<TDef>[]>;
1299
- /**
1300
- * Find a single document matching the filter.
1301
- *
1302
- * Queries MongoDB, then validates the fetched document against the collection's
1303
- * Zod schema. Validation mode is resolved from the per-query option, falling
1304
- * back to the collection-level default (which defaults to `'strict'`).
1187
+ type CursorPaginateOptions = {
1188
+ /** Maximum number of documents to return. */
1189
+ limit: number;
1190
+ /** Opaque cursor string from a previous `startCursor` or `endCursor`. */
1191
+ cursor?: string | null;
1192
+ };
1193
+ /**
1194
+ * Result of offset-based pagination.
1195
+ *
1196
+ * @example
1197
+ * ```ts
1198
+ * const page = await users.find({}).paginate({ page: 1, perPage: 10 })
1199
+ * console.log(page.total, page.totalPages, page.hasNext)
1200
+ * ```
1201
+ */
1202
+ type OffsetPage<TDoc> = {
1203
+ /** The documents for this page. */
1204
+ docs: TDoc[];
1205
+ /** Total number of matching documents. */
1206
+ total: number;
1207
+ /** Current page number (1-indexed). */
1208
+ page: number;
1209
+ /** Number of documents per page. */
1210
+ perPage: number;
1211
+ /** Total number of pages (`Math.ceil(total / perPage)`). */
1212
+ totalPages: number;
1213
+ /** Whether there is a next page. */
1214
+ hasNext: boolean;
1215
+ /** Whether there is a previous page. */
1216
+ hasPrev: boolean;
1217
+ };
1218
+ /**
1219
+ * Result of cursor-based pagination.
1220
+ *
1221
+ * @example
1222
+ * ```ts
1223
+ * const page = await users.find({}).paginate({ limit: 10 })
1224
+ * if (page.hasNext) {
1225
+ * const next = await users.find({}).paginate({ cursor: page.endCursor, limit: 10 })
1226
+ * }
1227
+ * ```
1228
+ */
1229
+ type CursorPage<TDoc> = {
1230
+ /** The documents for this page. */
1231
+ docs: TDoc[];
1232
+ /** Whether there are more documents after this page. */
1233
+ hasNext: boolean;
1234
+ /** Whether there are documents before this page. */
1235
+ hasPrev: boolean;
1236
+ /** Cursor for the first document (pass to `cursor` to go backward). `null` when empty. */
1237
+ startCursor: string | null;
1238
+ /** Cursor for the last document (pass to `cursor` to go forward). `null` when empty. */
1239
+ endCursor: string | null;
1240
+ };
1241
+
1242
+ /**
1243
+ * Type-safe sort specification for a document type.
1244
+ *
1245
+ * Constrains sort keys to top-level fields of `T` with direction `1` (ascending)
1246
+ * or `-1` (descending). Dot-path sorts deferred to v1.0.
1247
+ *
1248
+ * @example
1249
+ * ```ts
1250
+ * const sort: TypedSort<User> = { name: 1, createdAt: -1 }
1251
+ * ```
1252
+ */
1253
+ type TypedSort<T> = Partial<Record<keyof T & string, 1 | -1>>;
1254
+ /**
1255
+ * Type-safe cursor wrapping MongoDB's `FindCursor`.
1256
+ *
1257
+ * Provides chainable query modifiers (`sort`, `skip`, `limit`, `hint`) that return
1258
+ * `this` for fluent chaining, and terminal methods (`toArray`,
1259
+ * `[Symbol.asyncIterator]`) that validate each document against the
1260
+ * collection's Zod schema before returning.
1261
+ *
1262
+ * Created by {@link find} — do not construct directly.
1263
+ *
1264
+ * @typeParam TDef - The collection definition type, used to infer the document type.
1265
+ * @typeParam TIndexNames - Union of declared index names accepted by `.hint()`.
1266
+ *
1267
+ * @example
1268
+ * ```ts
1269
+ * const docs = await find(users, { role: 'admin' })
1270
+ * .sort({ name: 1 })
1271
+ * .limit(10)
1272
+ * .toArray()
1273
+ * ```
1274
+ */
1275
+ declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends string = string> {
1276
+ /** @internal */
1277
+ private cursor;
1278
+ /** @internal */
1279
+ private schema;
1280
+ /** @internal */
1281
+ private collectionName;
1282
+ /** @internal */
1283
+ private mode;
1284
+ /** @internal */
1285
+ private readonly nativeCollection;
1286
+ /** @internal */
1287
+ private readonly filter;
1288
+ /** @internal */
1289
+ private sortSpec;
1290
+ /** @internal */
1291
+ constructor(cursor: FindCursor<InferDocument<TDef>>, definition: TDef, mode: ValidationMode | false, nativeCollection: Collection<InferDocument<TDef>>, filter: any);
1292
+ /**
1293
+ * Set the sort order for the query.
1294
+ *
1295
+ * Only top-level document fields are accepted as sort keys.
1296
+ * Values must be `1` (ascending) or `-1` (descending).
1297
+ *
1298
+ * @param spec - Sort specification mapping field names to sort direction.
1299
+ * @returns `this` for chaining.
1300
+ *
1301
+ * @example
1302
+ * ```ts
1303
+ * find(users, {}).sort({ name: 1, age: -1 }).toArray()
1304
+ * ```
1305
+ */
1306
+ sort(spec: TypedSort<InferDocument<TDef>>): this;
1307
+ /**
1308
+ * Skip the first `n` documents in the result set.
1309
+ *
1310
+ * @param n - Number of documents to skip.
1311
+ * @returns `this` for chaining.
1312
+ *
1313
+ * @example
1314
+ * ```ts
1315
+ * find(users, {}).skip(10).limit(10).toArray() // page 2
1316
+ * ```
1317
+ */
1318
+ skip(n: number): this;
1319
+ /**
1320
+ * Limit the number of documents returned.
1321
+ *
1322
+ * @param n - Maximum number of documents to return.
1323
+ * @returns `this` for chaining.
1324
+ *
1325
+ * @example
1326
+ * ```ts
1327
+ * find(users, {}).limit(10).toArray() // at most 10 docs
1328
+ * ```
1329
+ */
1330
+ limit(n: number): this;
1331
+ /**
1332
+ * Force the query optimizer to use the specified index.
1333
+ *
1334
+ * Only accepts index names that were declared via `.name()` in the
1335
+ * collection definition. If no named indexes exist, any string is accepted.
1336
+ *
1337
+ * @param indexName - The name of a declared compound index.
1338
+ * @returns `this` for chaining.
1339
+ *
1340
+ * @example
1341
+ * ```ts
1342
+ * const Users = collection('users', { email: z.string(), role: z.string() }, {
1343
+ * indexes: [index({ email: 1, role: -1 }).name('email_role_idx')],
1344
+ * })
1345
+ * const admins = await users.find({ role: 'admin' })
1346
+ * .hint('email_role_idx')
1347
+ * .toArray()
1348
+ * ```
1349
+ */
1350
+ hint(indexName: TIndexNames): this;
1351
+ /**
1352
+ * Execute the query with offset-based pagination, returning a page of documents
1353
+ * with total count and navigation metadata.
1354
+ *
1355
+ * Runs `countDocuments` and `find` in parallel for performance. Ignores any
1356
+ * `.skip()` or `.limit()` already set on the cursor — issues a fresh query.
1357
+ *
1358
+ * @param opts - Offset pagination options: `page` (1-indexed) and `perPage`.
1359
+ * @returns A page with `docs`, `total`, `totalPages`, `hasNext`, `hasPrev`.
1360
+ * @throws {ZodmonValidationError} When a document fails schema validation.
1361
+ *
1362
+ * @example
1363
+ * ```ts
1364
+ * const page = await users.find({ role: 'admin' })
1365
+ * .sort({ createdAt: -1 })
1366
+ * .paginate({ page: 2, perPage: 10 })
1367
+ * console.log(page.total, page.totalPages, page.hasNext)
1368
+ * ```
1369
+ */
1370
+ paginate(opts: OffsetPaginateOptions): Promise<OffsetPage<InferDocument<TDef>>>;
1371
+ /**
1372
+ * Execute the query with cursor-based pagination, returning a page of documents
1373
+ * with opaque cursors for forward/backward navigation.
1374
+ *
1375
+ * Uses the `limit + 1` trick to determine `hasNext`/`hasPrev` without extra queries.
1376
+ * Direction is encoded in the cursor — pass `endCursor` to go forward, `startCursor`
1377
+ * to go backward.
1378
+ *
1379
+ * @param opts - Cursor pagination options: `limit` and optional `cursor`.
1380
+ * @returns A page with `docs`, `hasNext`, `hasPrev`, `startCursor`, `endCursor`.
1381
+ * @throws {ZodmonValidationError} When a document fails schema validation.
1382
+ * @throws {Error} When the cursor string is malformed.
1383
+ *
1384
+ * @example
1385
+ * ```ts
1386
+ * const first = await users.find({}).sort({ name: 1 }).paginate({ limit: 10 })
1387
+ * const next = await users.find({}).sort({ name: 1 })
1388
+ * .paginate({ cursor: first.endCursor, limit: 10 })
1389
+ * ```
1390
+ */
1391
+ paginate(opts: CursorPaginateOptions): Promise<CursorPage<InferDocument<TDef>>>;
1392
+ /** @internal Offset pagination implementation. */
1393
+ private offsetPaginate;
1394
+ /** @internal Cursor pagination implementation. */
1395
+ private cursorPaginate;
1396
+ /**
1397
+ * Execute the query and return all matching documents as an array.
1398
+ *
1399
+ * Each document is validated against the collection's Zod schema
1400
+ * according to the resolved validation mode.
1401
+ *
1402
+ * @returns Array of validated documents.
1403
+ * @throws {ZodmonValidationError} When a document fails schema validation in strict/strip mode.
1404
+ *
1405
+ * @example
1406
+ * ```ts
1407
+ * const admins = await find(users, { role: 'admin' }).toArray()
1408
+ * ```
1409
+ */
1410
+ toArray(): Promise<InferDocument<TDef>[]>;
1411
+ /**
1412
+ * Async iterator for streaming documents one at a time.
1413
+ *
1414
+ * Each yielded document is validated against the collection's Zod schema.
1415
+ * Memory-efficient for large result sets.
1416
+ *
1417
+ * @yields Validated documents one at a time.
1418
+ * @throws {ZodmonValidationError} When a document fails schema validation.
1419
+ *
1420
+ * @example
1421
+ * ```ts
1422
+ * for await (const user of find(users, {})) {
1423
+ * console.log(user.name)
1424
+ * }
1425
+ * ```
1426
+ */
1427
+ [Symbol.asyncIterator](): AsyncGenerator<InferDocument<TDef>>;
1428
+ /** @internal Validate a single raw document against the schema. */
1429
+ private validateDoc;
1430
+ }
1431
+
1432
+ /**
1433
+ * Options for {@link findOne} and {@link findOneOrThrow}.
1434
+ */
1435
+ type FindOneOptions = {
1436
+ /** MongoDB projection — include (`1`) or exclude (`0`) fields. Typed projections deferred to v1.0. */
1437
+ project?: Record<string, 0 | 1>;
1438
+ /** Override the collection-level validation mode, or `false` to skip validation entirely. */
1439
+ validate?: ValidationMode | false;
1440
+ };
1441
+ /**
1442
+ * Find a single document matching the filter.
1443
+ *
1444
+ * Queries MongoDB, then validates the fetched document against the collection's
1445
+ * Zod schema. Validation mode is resolved from the per-query option, falling
1446
+ * back to the collection-level default (which defaults to `'strict'`).
1447
+ *
1448
+ * @param handle - The collection handle to query.
1449
+ * @param filter - Type-safe filter to match documents.
1450
+ * @param options - Optional projection and validation overrides.
1451
+ * @returns The matched document, or `null` if no document matches.
1452
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1453
+ *
1454
+ * @example
1455
+ * ```ts
1456
+ * const user = await findOne(users, { name: 'Ada' })
1457
+ * if (user) console.log(user.role) // typed as 'admin' | 'user'
1458
+ * ```
1459
+ */
1460
+ declare function findOne<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef> | null>;
1461
+ /**
1462
+ * Find a single document matching the filter, or throw if none exists.
1463
+ *
1464
+ * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
1465
+ * instead of returning `null` when no document matches the filter.
1466
+ *
1467
+ * @param handle - The collection handle to query.
1468
+ * @param filter - Type-safe filter to match documents.
1469
+ * @param options - Optional projection and validation overrides.
1470
+ * @returns The matched document (never null).
1471
+ * @throws {ZodmonNotFoundError} When no document matches the filter.
1472
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1473
+ *
1474
+ * @example
1475
+ * ```ts
1476
+ * const user = await findOneOrThrow(users, { name: 'Ada' })
1477
+ * console.log(user.role) // typed as 'admin' | 'user', guaranteed non-null
1478
+ * ```
1479
+ */
1480
+ declare function findOneOrThrow<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef>>;
1481
+ /**
1482
+ * Options for {@link find}.
1483
+ */
1484
+ type FindOptions = {
1485
+ /** Override the collection-level validation mode, or `false` to skip validation entirely. */
1486
+ validate?: ValidationMode | false;
1487
+ };
1488
+ /**
1489
+ * Find all documents matching the filter, returning a chainable typed cursor.
1490
+ *
1491
+ * The cursor is lazy — no query is executed until a terminal method
1492
+ * (`toArray`, `for await`) is called. Use `sort`, `skip`, and `limit`
1493
+ * to shape the query before executing.
1494
+ *
1495
+ * Each document is validated against the collection's Zod schema when
1496
+ * a terminal method consumes it.
1497
+ *
1498
+ * @param handle - The collection handle to query.
1499
+ * @param filter - Type-safe filter to match documents.
1500
+ * @param options - Optional validation overrides.
1501
+ * @returns A typed cursor for chaining query modifiers.
1502
+ *
1503
+ * @example
1504
+ * ```ts
1505
+ * const admins = await find(users, { role: 'admin' })
1506
+ * .sort({ name: 1 })
1507
+ * .limit(10)
1508
+ * .toArray()
1509
+ * ```
1510
+ *
1511
+ * @example
1512
+ * ```ts
1513
+ * for await (const user of find(users, {})) {
1514
+ * console.log(user.name)
1515
+ * }
1516
+ * ```
1517
+ */
1518
+ declare function find<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOptions): TypedFindCursor<TDef, IndexNames<TDef>>;
1519
+
1520
+ /**
1521
+ * Extracts the element type from an array type.
1522
+ *
1523
+ * @example
1524
+ * ```ts
1525
+ * type E = ArrayElement<string[]> // string
1526
+ * type N = ArrayElement<number[]> // number
1527
+ * ```
1528
+ */
1529
+ type ArrayElement<T> = T extends ReadonlyArray<infer E> ? E : never;
1530
+ /**
1531
+ * Fields valid for `$set`, `$setOnInsert`, `$min`, and `$max` operators.
1532
+ *
1533
+ * Allows top-level fields with matching value types plus dot-notation paths
1534
+ * for nested field updates.
1535
+ *
1536
+ * @example
1537
+ * ```ts
1538
+ * const set: SetFields<User> = { name: 'Alice', 'address.city': 'NYC' }
1539
+ * ```
1540
+ */
1541
+ type SetFields<T> = {
1542
+ [K in keyof T]?: T[K];
1543
+ } & {
1544
+ [P in DotPaths<T>]?: DotPathType<T, P>;
1545
+ };
1546
+ /**
1547
+ * Fields valid for the `$inc` operator — only number-typed fields.
1548
+ *
1549
+ * Includes dot-notation paths that resolve to number types.
1550
+ *
1551
+ * @example
1552
+ * ```ts
1553
+ * const inc: IncFields<User> = { age: 1, 'stats.views': 5 }
1554
+ * ```
1555
+ */
1556
+ type IncFields<T> = {
1557
+ [K in keyof T as NonNullable<T[K]> extends number ? K : never]?: number;
1558
+ } & {
1559
+ [P in DotPaths<T> as DotPathType<T, P> extends number ? P : never]?: number;
1560
+ };
1561
+ /**
1562
+ * Modifiers for the `$push` operator.
1563
+ *
1564
+ * Use with `$push` to insert multiple elements and control array position/size.
1565
+ *
1566
+ * @example
1567
+ * ```ts
1568
+ * const push = { tags: { $each: ['a', 'b'], $position: 0, $slice: 10 } }
1569
+ * ```
1570
+ */
1571
+ type PushModifiers<E> = {
1572
+ /** Array of elements to push. */
1573
+ $each: E[];
1574
+ /** Position at which to insert elements. */
1575
+ $position?: number;
1576
+ /** Maximum array length after push. */
1577
+ $slice?: number;
1578
+ /** Sort order applied after push. */
1579
+ $sort?: 1 | -1 | Record<string, 1 | -1>;
1580
+ };
1581
+ /**
1582
+ * Fields valid for the `$push` operator — only array-typed fields.
1583
+ *
1584
+ * Accepts a single element value or a modifier object with `$each`.
1585
+ *
1586
+ * @example
1587
+ * ```ts
1588
+ * const push: PushFields<User> = { tags: 'dev' }
1589
+ * const pushMany: PushFields<User> = { tags: { $each: ['a', 'b'] } }
1590
+ * ```
1591
+ */
1592
+ type PushFields<T> = {
1593
+ [K in keyof T as NonNullable<T[K]> extends ReadonlyArray<unknown> ? K : never]?: ArrayElement<NonNullable<T[K]>> | PushModifiers<ArrayElement<NonNullable<T[K]>>>;
1594
+ };
1595
+ /**
1596
+ * Fields valid for the `$pull` operator — only array-typed fields.
1597
+ *
1598
+ * For primitive arrays: accepts a value or comparison operators.
1599
+ * For object arrays: also accepts a `TypedFilter` on the element type.
1600
+ *
1601
+ * @example
1602
+ * ```ts
1603
+ * const pull: PullFields<User> = { tags: 'old' }
1604
+ * const pullQuery: PullFields<User> = { scores: { $lt: 50 } }
1605
+ * const pullObj: PullFields<User> = { comments: { author: 'spam' } }
1606
+ * ```
1607
+ */
1608
+ type PullFields<T> = {
1609
+ [K in keyof T as NonNullable<T[K]> extends ReadonlyArray<unknown> ? K : never]?: ArrayElement<NonNullable<T[K]>> | ComparisonOperators<ArrayElement<NonNullable<T[K]>>> | (ArrayElement<NonNullable<T[K]>> extends Record<string, unknown> ? TypedFilter<ArrayElement<NonNullable<T[K]>>> : unknown);
1610
+ };
1611
+ /**
1612
+ * Modifier for the `$addToSet` operator's `$each` syntax.
1613
+ *
1614
+ * @example
1615
+ * ```ts
1616
+ * const addToSet = { tags: { $each: ['a', 'b'] } }
1617
+ * ```
1618
+ */
1619
+ type AddToSetEach<E> = {
1620
+ $each: E[];
1621
+ };
1622
+ /**
1623
+ * Fields valid for the `$addToSet` operator — only array-typed fields.
1624
+ *
1625
+ * Accepts a single element value or `{ $each: [...] }` for multiple elements.
1626
+ *
1627
+ * @example
1628
+ * ```ts
1629
+ * const add: AddToSetFields<User> = { tags: 'dev' }
1630
+ * const addMany: AddToSetFields<User> = { tags: { $each: ['a', 'b'] } }
1631
+ * ```
1632
+ */
1633
+ type AddToSetFields<T> = {
1634
+ [K in keyof T as NonNullable<T[K]> extends ReadonlyArray<unknown> ? K : never]?: ArrayElement<NonNullable<T[K]>> | AddToSetEach<ArrayElement<NonNullable<T[K]>>>;
1635
+ };
1636
+ /**
1637
+ * Fields valid for the `$pop` operator — only array-typed fields.
1638
+ *
1639
+ * Value `1` removes the last element, `-1` removes the first.
1640
+ *
1641
+ * @example
1642
+ * ```ts
1643
+ * const pop: PopFields<User> = { tags: -1 } // remove first
1644
+ * ```
1645
+ */
1646
+ type PopFields<T> = {
1647
+ [K in keyof T as NonNullable<T[K]> extends ReadonlyArray<unknown> ? K : never]?: 1 | -1;
1648
+ };
1649
+ /**
1650
+ * Fields valid for the `$unset` operator — any existing field.
1651
+ *
1652
+ * Value is `''`, `true`, or `1` (all mean "remove this field").
1653
+ *
1654
+ * @example
1655
+ * ```ts
1656
+ * const unset: UnsetFields<User> = { middleName: '' }
1657
+ * ```
1658
+ */
1659
+ type UnsetFields<T> = {
1660
+ [K in keyof T]?: '' | true | 1;
1661
+ };
1662
+ /**
1663
+ * Fields valid for the `$currentDate` operator — only Date-typed fields.
1664
+ *
1665
+ * Sets the field to the current date. Value is `true` or `{ $type: 'date' }`.
1666
+ *
1667
+ * @example
1668
+ * ```ts
1669
+ * const cd: CurrentDateFields<User> = { createdAt: true }
1670
+ * ```
1671
+ */
1672
+ type CurrentDateFields<T> = {
1673
+ [K in keyof T as NonNullable<T[K]> extends Date ? K : never]?: true | {
1674
+ $type: 'date';
1675
+ };
1676
+ };
1677
+ /**
1678
+ * Fields valid for the `$rename` operator.
1679
+ *
1680
+ * Renames an existing field to a new name. Key is the current field name,
1681
+ * value is the new name as a string.
1682
+ *
1683
+ * @example
1684
+ * ```ts
1685
+ * const rename: RenameFields<User> = { name: 'fullName' }
1686
+ * ```
1687
+ */
1688
+ type RenameFields<T> = {
1689
+ [K in keyof T & string]?: string;
1690
+ };
1691
+ /**
1692
+ * Strict type-safe MongoDB update filter.
1693
+ *
1694
+ * Validates update operators against field types at compile time. Each operator
1695
+ * constrains which fields it accepts (e.g. `$inc` only on numbers, `$push` only
1696
+ * on arrays). Supports dot-notation paths for nested field access.
1697
+ *
1698
+ * @example
1699
+ * ```ts
1700
+ * const update: TypedUpdateFilter<User> = {
1701
+ * $set: { name: 'Alice' },
1702
+ * $inc: { age: 1 },
1703
+ * }
1704
+ * ```
1705
+ */
1706
+ type TypedUpdateFilter<T> = {
1707
+ /** Sets the value of one or more fields. */
1708
+ $set?: SetFields<T>;
1709
+ /** Sets fields only when inserting (upsert). Same typing as `$set`. */
1710
+ $setOnInsert?: SetFields<T>;
1711
+ /** Increments numeric fields by the given amount. Only accepts number-typed fields. */
1712
+ $inc?: IncFields<T>;
1713
+ /** Updates the field if the given value is less than the current value. */
1714
+ $min?: SetFields<T>;
1715
+ /** Updates the field if the given value is greater than the current value. */
1716
+ $max?: SetFields<T>;
1717
+ /** Appends a value to an array field. Supports `$each`, `$position`, `$slice`, `$sort`. */
1718
+ $push?: PushFields<T>;
1719
+ /** Removes matching values from an array field. */
1720
+ $pull?: PullFields<T>;
1721
+ /** Adds a value to an array only if it doesn't already exist. */
1722
+ $addToSet?: AddToSetFields<T>;
1723
+ /** Removes the first (`-1`) or last (`1`) element of an array. */
1724
+ $pop?: PopFields<T>;
1725
+ /** Removes the specified fields from the document. */
1726
+ $unset?: UnsetFields<T>;
1727
+ /** Sets the field to the current date. Only accepts Date-typed fields. */
1728
+ $currentDate?: CurrentDateFields<T>;
1729
+ /** Renames a field. */
1730
+ $rename?: RenameFields<T>;
1731
+ };
1732
+
1733
+ /**
1734
+ * Options for {@link updateOne} and {@link updateMany}.
1735
+ */
1736
+ type UpdateOptions = {
1737
+ /** When `true`, inserts a new document if no document matches the filter. */
1738
+ upsert?: boolean;
1739
+ };
1740
+ /**
1741
+ * Options for {@link findOneAndUpdate}.
1742
+ */
1743
+ type FindOneAndUpdateOptions = {
1744
+ /** Whether to return the document before or after the update. Defaults to `'after'`. */
1745
+ returnDocument?: 'before' | 'after';
1746
+ /** When `true`, inserts a new document if no document matches the filter. */
1747
+ upsert?: boolean;
1748
+ /** Override the collection-level validation mode, or `false` to skip validation entirely. */
1749
+ validate?: ValidationMode | false;
1750
+ };
1751
+ /**
1752
+ * Update a single document matching the filter.
1753
+ *
1754
+ * Applies the update operators to the first document that matches the filter.
1755
+ * Does not validate the update against the Zod schema — validation happens
1756
+ * at the field-operator level through {@link TypedUpdateFilter}.
1757
+ *
1758
+ * @param handle - The collection handle to update in.
1759
+ * @param filter - Type-safe filter to match documents.
1760
+ * @param update - Type-safe update operators to apply.
1761
+ * @param options - Optional settings such as `upsert`.
1762
+ * @returns The MongoDB `UpdateResult` with match/modify counts.
1763
+ *
1764
+ * @example
1765
+ * ```ts
1766
+ * const result = await updateOne(users, { name: 'Ada' }, { $set: { role: 'admin' } })
1767
+ * console.log(result.modifiedCount) // 1
1768
+ * ```
1769
+ */
1770
+ declare function updateOne<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: UpdateOptions): Promise<UpdateResult>;
1771
+ /**
1772
+ * Update all documents matching the filter.
1773
+ *
1774
+ * Applies the update operators to every document that matches the filter.
1775
+ * Does not validate the update against the Zod schema — validation happens
1776
+ * at the field-operator level through {@link TypedUpdateFilter}.
1777
+ *
1778
+ * @param handle - The collection handle to update in.
1779
+ * @param filter - Type-safe filter to match documents.
1780
+ * @param update - Type-safe update operators to apply.
1781
+ * @param options - Optional settings such as `upsert`.
1782
+ * @returns The MongoDB `UpdateResult` with match/modify counts.
1783
+ *
1784
+ * @example
1785
+ * ```ts
1786
+ * const result = await updateMany(users, { role: 'guest' }, { $set: { role: 'user' } })
1787
+ * console.log(result.modifiedCount) // number of guests promoted
1788
+ * ```
1789
+ */
1790
+ declare function updateMany<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: UpdateOptions): Promise<UpdateResult>;
1791
+ /**
1792
+ * Find a single document matching the filter, apply an update, and return the document.
1793
+ *
1794
+ * By default, returns the document **after** the update is applied. Set
1795
+ * `returnDocument: 'before'` to get the pre-update snapshot. The returned
1796
+ * document is validated against the collection's Zod schema using the same
1797
+ * resolution logic as {@link findOne}.
1798
+ *
1799
+ * @param handle - The collection handle to update in.
1800
+ * @param filter - Type-safe filter to match documents.
1801
+ * @param update - Type-safe update operators to apply.
1802
+ * @param options - Optional settings: `returnDocument`, `upsert`, `validate`.
1803
+ * @returns The matched document (before or after update), or `null` if no document matches.
1804
+ * @throws {ZodmonValidationError} When the returned document fails schema validation in strict mode.
1805
+ *
1806
+ * @example
1807
+ * ```ts
1808
+ * const user = await findOneAndUpdate(
1809
+ * users,
1810
+ * { name: 'Ada' },
1811
+ * { $set: { role: 'admin' } },
1812
+ * )
1813
+ * if (user) console.log(user.role) // 'admin' (returned after update)
1814
+ * ```
1815
+ *
1816
+ * @example
1817
+ * ```ts
1818
+ * const before = await findOneAndUpdate(
1819
+ * users,
1820
+ * { name: 'Ada' },
1821
+ * { $inc: { loginCount: 1 } },
1822
+ * { returnDocument: 'before' },
1823
+ * )
1824
+ * ```
1825
+ */
1826
+ declare function findOneAndUpdate<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: FindOneAndUpdateOptions): Promise<InferDocument<TDef> | null>;
1827
+
1828
+ /**
1829
+ * Options controlling how {@link syncIndexes} behaves.
1830
+ *
1831
+ * @example
1832
+ * ```ts
1833
+ * await users.syncIndexes({ dryRun: true })
1834
+ * await users.syncIndexes({ dropOrphaned: true })
1835
+ * ```
1836
+ */
1837
+ type SyncIndexesOptions = {
1838
+ /**
1839
+ * When `true`, compute the diff without actually creating, dropping, or
1840
+ * modifying any indexes. The returned {@link SyncIndexesResult} shows what
1841
+ * *would* happen.
1842
+ */
1843
+ dryRun?: boolean;
1844
+ /**
1845
+ * When `true`, drop indexes that exist in MongoDB but are not declared in
1846
+ * the schema. Also drops and recreates stale indexes (same key, different
1847
+ * options). When `false` (the default), orphaned and stale indexes are
1848
+ * reported but left untouched.
1849
+ */
1850
+ dropOrphaned?: boolean;
1851
+ };
1852
+ /**
1853
+ * Describes an index whose key matches a desired index but whose options differ.
1854
+ *
1855
+ * Returned in {@link SyncIndexesResult.stale} so the caller can decide whether
1856
+ * to manually reconcile or re-run with `dropOrphaned: true`.
1857
+ *
1858
+ * @example
1859
+ * ```ts
1860
+ * const result = await users.syncIndexes()
1861
+ * for (const s of result.stale) {
1862
+ * console.log(`${s.name}: key=${JSON.stringify(s.key)}`)
1863
+ * console.log(` existing=${JSON.stringify(s.existing)}`)
1864
+ * console.log(` desired=${JSON.stringify(s.desired)}`)
1865
+ * }
1866
+ * ```
1867
+ */
1868
+ type StaleIndex = {
1869
+ /** The MongoDB index name (e.g. `'email_1'`). */
1870
+ name: string;
1871
+ /** The index key spec (e.g. `{ email: 1 }`). */
1872
+ key: Record<string, 1 | -1 | 'text'>;
1873
+ /** The relevant options currently set on the existing index. */
1874
+ existing: Record<string, unknown>;
1875
+ /** The options the schema declares for this index. */
1876
+ desired: Record<string, unknown>;
1877
+ };
1878
+ /**
1879
+ * The result of a {@link syncIndexes} call.
1880
+ *
1881
+ * Every array contains index names. `stale` contains full details so the
1882
+ * caller can inspect the mismatch.
1883
+ *
1884
+ * @example
1885
+ * ```ts
1886
+ * const result = await users.syncIndexes()
1887
+ * console.log('created:', result.created)
1888
+ * console.log('dropped:', result.dropped)
1889
+ * console.log('skipped:', result.skipped)
1890
+ * console.log('stale:', result.stale.map(s => s.name))
1891
+ * ```
1892
+ */
1893
+ type SyncIndexesResult = {
1894
+ /** Names of indexes that were created (or would be created in dryRun mode). */
1895
+ created: string[];
1896
+ /** Names of indexes that were dropped (or would be dropped in dryRun mode). */
1897
+ dropped: string[];
1898
+ /** Names of indexes that already existed with matching options — no action taken. */
1899
+ skipped: string[];
1900
+ /** Indexes whose key matches a desired spec but whose options differ. */
1901
+ stale: StaleIndex[];
1902
+ };
1903
+
1904
+ /**
1905
+ * Typed wrapper around a MongoDB driver `Collection`.
1906
+ *
1907
+ * Created by {@link Database.use}. Holds the original `CollectionDefinition`
1908
+ * (for runtime schema validation and index metadata) alongside the native
1909
+ * driver collection parameterized with the inferred document type.
1910
+ *
1911
+ * @typeParam TDef - The collection definition type. Used to derive both
1912
+ * the document type (`InferDocument`) and the insert type (`InferInsert`).
1913
+ */
1914
+ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
1915
+ /** The collection definition containing schema, name, and index metadata. */
1916
+ readonly definition: TDef;
1917
+ /** The underlying MongoDB driver collection, typed to the inferred document type. */
1918
+ readonly native: Collection<InferDocument<TDef>>;
1919
+ constructor(definition: TDef, native: Collection<InferDocument<TDef>>);
1920
+ /**
1921
+ * Insert a single document into the collection.
1922
+ *
1923
+ * Validates the input against the collection's Zod schema before writing.
1924
+ * Schema defaults (including auto-generated `_id`) are applied during
1925
+ * validation. Returns the full document with all defaults filled in.
1926
+ *
1927
+ * @param doc - The document to insert. Fields with `.default()` are optional.
1928
+ * @returns The inserted document with `_id` and all defaults applied.
1929
+ * @throws {ZodmonValidationError} When the document fails schema validation.
1930
+ *
1931
+ * @example
1932
+ * ```ts
1933
+ * const users = db.use(Users)
1934
+ * const user = await users.insertOne({ name: 'Ada' })
1935
+ * console.log(user._id) // ObjectId (auto-generated)
1936
+ * console.log(user.role) // 'user' (schema default)
1937
+ * ```
1938
+ */
1939
+ insertOne(doc: InferInsert<TDef>): Promise<InferDocument<TDef>>;
1940
+ /**
1941
+ * Insert multiple documents into the collection.
1942
+ *
1943
+ * Validates every document against the collection's Zod schema before
1944
+ * writing any to MongoDB. If any document fails validation, none are
1945
+ * inserted (fail-fast before the driver call).
1946
+ *
1947
+ * @param docs - The documents to insert.
1948
+ * @returns The inserted documents with `_id` and all defaults applied.
1949
+ * @throws {ZodmonValidationError} When any document fails schema validation.
1950
+ *
1951
+ * @example
1952
+ * ```ts
1953
+ * const created = await users.insertMany([
1954
+ * { name: 'Ada' },
1955
+ * { name: 'Bob', role: 'admin' },
1956
+ * ])
1957
+ * ```
1958
+ */
1959
+ insertMany(docs: InferInsert<TDef>[]): Promise<InferDocument<TDef>[]>;
1960
+ /**
1961
+ * Find a single document matching the filter.
1962
+ *
1963
+ * Queries MongoDB, then validates the fetched document against the collection's
1964
+ * Zod schema. Validation mode is resolved from the per-query option, falling
1965
+ * back to the collection-level default (which defaults to `'strict'`).
1966
+ *
1967
+ * @param filter - Type-safe filter to match documents.
1968
+ * @param options - Optional projection and validation overrides.
1969
+ * @returns The matched document, or `null` if no document matches.
1970
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1971
+ *
1972
+ * @example
1973
+ * ```ts
1974
+ * const users = db.use(Users)
1975
+ * const user = await users.findOne({ name: 'Ada' })
1976
+ * if (user) console.log(user.role)
1977
+ * ```
1978
+ */
1979
+ findOne(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef> | null>;
1980
+ /**
1981
+ * Find a single document matching the filter, or throw if none exists.
1982
+ *
1983
+ * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
1984
+ * instead of returning `null` when no document matches the filter.
1985
+ *
1986
+ * @param filter - Type-safe filter to match documents.
1987
+ * @param options - Optional projection and validation overrides.
1988
+ * @returns The matched document (never null).
1989
+ * @throws {ZodmonNotFoundError} When no document matches the filter.
1990
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1991
+ *
1992
+ * @example
1993
+ * ```ts
1994
+ * const users = db.use(Users)
1995
+ * const user = await users.findOneOrThrow({ name: 'Ada' })
1996
+ * console.log(user.role) // guaranteed non-null
1997
+ * ```
1998
+ */
1999
+ findOneOrThrow(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef>>;
2000
+ /**
2001
+ * Find all documents matching the filter, returning a chainable typed cursor.
2002
+ *
2003
+ * The cursor is lazy — no query is executed until a terminal method
2004
+ * (`toArray`, `for await`) is called. Use `sort`, `skip`, and `limit`
2005
+ * to shape the query before executing.
2006
+ *
2007
+ * @param filter - Type-safe filter to match documents.
2008
+ * @param options - Optional validation overrides.
2009
+ * @returns A typed cursor for chaining query modifiers.
2010
+ *
2011
+ * @example
2012
+ * ```ts
2013
+ * const users = db.use(Users)
2014
+ * const admins = await users.find({ role: 'admin' })
2015
+ * .sort({ name: 1 })
2016
+ * .limit(10)
2017
+ * .toArray()
2018
+ * ```
2019
+ */
2020
+ find(filter: TypedFilter<InferDocument<TDef>>, options?: FindOptions): TypedFindCursor<TDef, IndexNames<TDef>>;
2021
+ /**
2022
+ * Update a single document matching the filter.
2023
+ *
2024
+ * Applies the update operators to the first document that matches the filter.
2025
+ * Does not validate the update against the Zod schema — validation happens
2026
+ * at the field-operator level through {@link TypedUpdateFilter}.
2027
+ *
2028
+ * @param filter - Type-safe filter to match documents.
2029
+ * @param update - Type-safe update operators to apply.
2030
+ * @param options - Optional settings such as `upsert`.
2031
+ * @returns The MongoDB `UpdateResult` with match/modify counts.
2032
+ *
2033
+ * @example
2034
+ * ```ts
2035
+ * const users = db.use(Users)
2036
+ * const result = await users.updateOne({ name: 'Ada' }, { $set: { role: 'admin' } })
2037
+ * console.log(result.modifiedCount) // 1
2038
+ * ```
2039
+ */
2040
+ updateOne(filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: UpdateOptions): Promise<UpdateResult>;
2041
+ /**
2042
+ * Update all documents matching the filter.
2043
+ *
2044
+ * Applies the update operators to every document that matches the filter.
2045
+ * Does not validate the update against the Zod schema — validation happens
2046
+ * at the field-operator level through {@link TypedUpdateFilter}.
2047
+ *
2048
+ * @param filter - Type-safe filter to match documents.
2049
+ * @param update - Type-safe update operators to apply.
2050
+ * @param options - Optional settings such as `upsert`.
2051
+ * @returns The MongoDB `UpdateResult` with match/modify counts.
2052
+ *
2053
+ * @example
2054
+ * ```ts
2055
+ * const users = db.use(Users)
2056
+ * const result = await users.updateMany({ role: 'guest' }, { $set: { role: 'user' } })
2057
+ * console.log(result.modifiedCount) // number of guests promoted
2058
+ * ```
2059
+ */
2060
+ updateMany(filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: UpdateOptions): Promise<UpdateResult>;
2061
+ /**
2062
+ * Find a single document matching the filter, apply an update, and return the document.
2063
+ *
2064
+ * By default, returns the document **after** the update is applied. Set
2065
+ * `returnDocument: 'before'` to get the pre-update snapshot. The returned
2066
+ * document is validated against the collection's Zod schema using the same
2067
+ * resolution logic as {@link findOne}.
2068
+ *
2069
+ * @param filter - Type-safe filter to match documents.
2070
+ * @param update - Type-safe update operators to apply.
2071
+ * @param options - Optional settings: `returnDocument`, `upsert`, `validate`.
2072
+ * @returns The matched document (before or after update), or `null` if no document matches.
2073
+ * @throws {ZodmonValidationError} When the returned document fails schema validation in strict mode.
2074
+ *
2075
+ * @example
2076
+ * ```ts
2077
+ * const users = db.use(Users)
2078
+ * const user = await users.findOneAndUpdate(
2079
+ * { name: 'Ada' },
2080
+ * { $set: { role: 'admin' } },
2081
+ * )
2082
+ * if (user) console.log(user.role) // 'admin' (returned after update)
2083
+ * ```
2084
+ */
2085
+ findOneAndUpdate(filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: FindOneAndUpdateOptions): Promise<InferDocument<TDef> | null>;
2086
+ /**
2087
+ * Delete a single document matching the filter.
2088
+ *
2089
+ * Removes the first document that matches the filter from the collection.
2090
+ * No validation is performed — the document is deleted directly through
2091
+ * the MongoDB driver.
2092
+ *
2093
+ * @param filter - Type-safe filter to match documents.
2094
+ * @returns The MongoDB `DeleteResult` with the deleted count.
2095
+ *
2096
+ * @example
2097
+ * ```ts
2098
+ * const users = db.use(Users)
2099
+ * const result = await users.deleteOne({ name: 'Ada' })
2100
+ * console.log(result.deletedCount) // 1
2101
+ * ```
2102
+ */
2103
+ deleteOne(filter: TypedFilter<InferDocument<TDef>>): Promise<DeleteResult>;
2104
+ /**
2105
+ * Delete all documents matching the filter.
2106
+ *
2107
+ * Removes every document that matches the filter from the collection.
2108
+ * No validation is performed — documents are deleted directly through
2109
+ * the MongoDB driver.
2110
+ *
2111
+ * @param filter - Type-safe filter to match documents.
2112
+ * @returns The MongoDB `DeleteResult` with the deleted count.
2113
+ *
2114
+ * @example
2115
+ * ```ts
2116
+ * const users = db.use(Users)
2117
+ * const result = await users.deleteMany({ role: 'guest' })
2118
+ * console.log(result.deletedCount) // number of guests removed
2119
+ * ```
2120
+ */
2121
+ deleteMany(filter: TypedFilter<InferDocument<TDef>>): Promise<DeleteResult>;
2122
+ /**
2123
+ * Find a single document matching the filter, delete it, and return the document.
2124
+ *
2125
+ * Returns the deleted document, or `null` if no document matches the filter.
2126
+ * The returned document is validated against the collection's Zod schema
2127
+ * using the same resolution logic as {@link findOne}.
2128
+ *
2129
+ * @param filter - Type-safe filter to match documents.
2130
+ * @param options - Optional settings: `validate`.
2131
+ * @returns The deleted document, or `null` if no document matches.
2132
+ * @throws {ZodmonValidationError} When the returned document fails schema validation in strict mode.
2133
+ *
2134
+ * @example
2135
+ * ```ts
2136
+ * const users = db.use(Users)
2137
+ * const user = await users.findOneAndDelete({ name: 'Ada' })
2138
+ * if (user) console.log(user.name) // 'Ada' (the deleted document)
2139
+ * ```
2140
+ */
2141
+ findOneAndDelete(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneAndDeleteOptions): Promise<InferDocument<TDef> | null>;
2142
+ /**
2143
+ * Synchronize the indexes declared in this collection's schema with MongoDB.
2144
+ *
2145
+ * Compares the desired indexes (from field-level `.index()` / `.unique()` /
2146
+ * `.text()` / `.expireAfter()` and compound `indexes` in collection options)
2147
+ * with the indexes that currently exist in MongoDB, then creates, drops, or
2148
+ * reports differences depending on the options.
2149
+ *
2150
+ * @param options - Optional sync behavior (dryRun, dropOrphaned).
2151
+ * @returns A summary of created, dropped, skipped, and stale indexes.
2152
+ *
2153
+ * @example
2154
+ * ```ts
2155
+ * const users = db.use(Users)
2156
+ * const result = await users.syncIndexes()
2157
+ * console.log('Created:', result.created)
2158
+ * console.log('Stale:', result.stale.map(s => s.name))
2159
+ * ```
2160
+ *
2161
+ * @example
2162
+ * ```ts
2163
+ * // Dry run to preview changes without modifying the database
2164
+ * const diff = await users.syncIndexes({ dryRun: true })
2165
+ * console.log('Would create:', diff.created)
2166
+ * console.log('Would drop:', diff.dropped)
2167
+ * ```
2168
+ */
2169
+ syncIndexes(options?: SyncIndexesOptions): Promise<SyncIndexesResult>;
2170
+ /**
2171
+ * Start a type-safe aggregation pipeline on this collection.
2172
+ *
2173
+ * Returns a fluent pipeline builder that tracks the output document
2174
+ * shape through each stage. The pipeline is lazy — no query executes
2175
+ * until a terminal method (`toArray`, `for await`, `explain`) is called.
2176
+ *
2177
+ * @returns A new pipeline builder starting with this collection's document type.
2178
+ *
2179
+ * @example
2180
+ * ```ts
2181
+ * const users = db.use(Users)
2182
+ * const result = await users.aggregate()
2183
+ * .match({ role: 'admin' })
2184
+ * .groupBy('role', { count: $count() })
2185
+ * .toArray()
2186
+ * ```
2187
+ */
2188
+ aggregate(): AggregatePipeline<TDef, InferDocument<TDef>>;
2189
+ }
2190
+
2191
+ /**
2192
+ * Immutable aggregation pipeline builder for type-safe MongoDB aggregations.
2193
+ *
2194
+ * Each stage method returns a **new** `AggregatePipeline` instance — the
2195
+ * original is never mutated. This makes it safe to branch from a shared base
2196
+ * pipeline without cross-contamination.
2197
+ *
2198
+ * Use {@link aggregate} to create a pipeline from a {@link CollectionHandle},
2199
+ * or call `raw()` to append arbitrary stages.
2200
+ *
2201
+ * @typeParam TDef - The collection definition type, used to derive document types.
2202
+ * @typeParam TOutput - The current output document type (changes as stages transform the shape).
2203
+ *
2204
+ * @example
2205
+ * ```ts
2206
+ * const results = await aggregate(users)
2207
+ * .raw({ $match: { role: 'admin' } })
2208
+ * .raw({ $sort: { name: 1 } })
2209
+ * .toArray()
2210
+ * ```
2211
+ */
2212
+ declare class AggregatePipeline<TDef extends AnyCollection, TOutput> {
2213
+ protected readonly definition: TDef;
2214
+ private readonly nativeCollection;
2215
+ private readonly stages;
2216
+ constructor(definition: TDef, nativeCollection: Collection<InferDocument<TDef>>, stages: Document[]);
2217
+ /**
2218
+ * Append an arbitrary aggregation stage to the pipeline (escape hatch).
2219
+ *
2220
+ * Returns a new pipeline instance with the stage appended — the
2221
+ * original pipeline is not modified.
2222
+ *
2223
+ * Optionally accepts a type parameter `TNew` to change the output
2224
+ * type when the stage transforms the document shape.
2225
+ *
2226
+ * @typeParam TNew - The output type after this stage. Defaults to the current output type.
2227
+ * @param stage - A raw MongoDB aggregation stage document (e.g. `{ $match: { ... } }`).
2228
+ * @returns A new pipeline with the stage appended.
2229
+ *
2230
+ * @example
2231
+ * ```ts
2232
+ * const admins = aggregate(users)
2233
+ * .raw({ $match: { role: 'admin' } })
2234
+ * .toArray()
2235
+ * ```
2236
+ *
2237
+ * @example
2238
+ * ```ts
2239
+ * // Change output type with a $project stage
2240
+ * const names = aggregate(users)
2241
+ * .raw<{ name: string }>({ $project: { name: 1, _id: 0 } })
2242
+ * .toArray()
2243
+ * ```
2244
+ */
2245
+ raw<TNew = TOutput>(stage: Document): AggregatePipeline<TDef, TNew>;
2246
+ /**
2247
+ * Execute the pipeline and return all results as an array.
2248
+ *
2249
+ * @returns A promise resolving to the array of output documents.
2250
+ *
2251
+ * @example
2252
+ * ```ts
2253
+ * const results = await aggregate(users)
2254
+ * .raw({ $match: { age: { $gte: 18 } } })
2255
+ * .toArray()
2256
+ * ```
2257
+ */
2258
+ toArray(): Promise<TOutput[]>;
2259
+ /**
2260
+ * Stream pipeline results one document at a time via `for await...of`.
2261
+ *
2262
+ * @returns An async generator yielding output documents.
2263
+ *
2264
+ * @example
2265
+ * ```ts
2266
+ * for await (const user of aggregate(users).raw({ $match: { role: 'admin' } })) {
2267
+ * console.log(user.name)
2268
+ * }
2269
+ * ```
2270
+ */
2271
+ [Symbol.asyncIterator](): AsyncGenerator<TOutput>;
2272
+ /**
2273
+ * Return the query execution plan without running the pipeline.
2274
+ *
2275
+ * Useful for debugging and understanding how MongoDB will process
2276
+ * the pipeline stages.
2277
+ *
2278
+ * @returns A promise resolving to the explain output document.
2279
+ *
2280
+ * @example
2281
+ * ```ts
2282
+ * const plan = await aggregate(users)
2283
+ * .raw({ $match: { role: 'admin' } })
2284
+ * .explain()
2285
+ * console.log(plan)
2286
+ * ```
2287
+ */
2288
+ explain(): Promise<Document>;
2289
+ /**
2290
+ * Filter documents using a type-safe match expression.
2291
+ *
2292
+ * Appends a `$match` stage to the pipeline. The filter is constrained
2293
+ * to the current output type, so only valid fields and operators are accepted.
2294
+ *
2295
+ * Supports two forms of type narrowing:
2296
+ *
2297
+ * **Tier 1 — Explicit type parameter:**
2298
+ * ```ts
2299
+ * .match<{ role: 'engineer' | 'designer' }>({ role: { $in: ['engineer', 'designer'] } })
2300
+ * // role narrows to 'engineer' | 'designer'
2301
+ * ```
2302
+ *
2303
+ * **Tier 2 — Automatic inference from filter literals:**
2304
+ * ```ts
2305
+ * .match({ role: 'engineer' }) // role narrows to 'engineer'
2306
+ * .match({ role: { $ne: 'intern' } }) // role narrows to Exclude<Role, 'intern'>
2307
+ * .match({ role: { $in: ['engineer', 'designer'] as const } }) // needs as const
2308
+ * ```
2309
+ *
2310
+ * When no type parameter is provided and the filter doesn't contain
2311
+ * inferrable literals, the output type is unchanged (backward compatible).
2312
+ *
2313
+ * @typeParam TNarrow - Optional object mapping field names to narrowed types. Must be a subtype of the corresponding fields in TOutput.
2314
+ * @typeParam F - Inferred from the filter argument. Do not provide explicitly.
2315
+ * @param filter - A type-safe filter for the current output type.
2316
+ * @returns A new pipeline with the `$match` stage appended and output type narrowed.
2317
+ *
2318
+ * @example
2319
+ * ```ts
2320
+ * // Explicit narrowing
2321
+ * const filtered = await users.aggregate()
2322
+ * .match<{ role: 'engineer' }>({ role: 'engineer' })
2323
+ * .toArray()
2324
+ * // filtered[0].role → 'engineer'
2325
+ *
2326
+ * // Automatic narrowing with $in (requires as const)
2327
+ * const subset = await users.aggregate()
2328
+ * .match({ role: { $in: ['engineer', 'designer'] as const } })
2329
+ * .toArray()
2330
+ * // subset[0].role → 'engineer' | 'designer'
2331
+ * ```
2332
+ */
2333
+ match<TNarrow extends {
2334
+ [K in keyof TNarrow]: K extends keyof TOutput ? TOutput[K] : never;
2335
+ } = {}, F extends TypedFilter<TOutput> = TypedFilter<TOutput>>(filter: F): AggregatePipeline<TDef, Prettify<Omit<NarrowFromFilter<TOutput, F>, keyof TNarrow> & TNarrow>>;
2336
+ /**
2337
+ * Sort documents by one or more fields.
2338
+ *
2339
+ * Appends a `$sort` stage. Keys are constrained to `keyof TOutput & string`
2340
+ * and values must be `1` (ascending) or `-1` (descending).
2341
+ *
2342
+ * @param spec - A sort specification mapping field names to sort direction.
2343
+ * @returns A new pipeline with the `$sort` stage appended.
2344
+ *
2345
+ * @example
2346
+ * ```ts
2347
+ * const sorted = await aggregate(users)
2348
+ * .sort({ age: -1, name: 1 })
2349
+ * .toArray()
2350
+ * ```
2351
+ */
2352
+ sort(spec: Partial<Record<keyof TOutput & string, 1 | -1>>): AggregatePipeline<TDef, TOutput>;
2353
+ /**
2354
+ * Skip a number of documents in the pipeline.
2355
+ *
2356
+ * Appends a `$skip` stage. Commonly used with {@link limit} for pagination.
2357
+ *
2358
+ * @param n - The number of documents to skip.
2359
+ * @returns A new pipeline with the `$skip` stage appended.
2360
+ *
2361
+ * @example
2362
+ * ```ts
2363
+ * // Page 2 (10 items per page)
2364
+ * const page2 = await aggregate(users)
2365
+ * .sort({ name: 1 })
2366
+ * .skip(10)
2367
+ * .limit(10)
2368
+ * .toArray()
2369
+ * ```
2370
+ */
2371
+ skip(n: number): AggregatePipeline<TDef, TOutput>;
2372
+ /**
2373
+ * Limit the number of documents passing through the pipeline.
2374
+ *
2375
+ * Appends a `$limit` stage. Commonly used with {@link skip} for pagination,
2376
+ * or after {@link sort} to get top/bottom N results.
2377
+ *
2378
+ * @param n - The maximum number of documents to pass through.
2379
+ * @returns A new pipeline with the `$limit` stage appended.
2380
+ *
2381
+ * @example
2382
+ * ```ts
2383
+ * const top5 = await aggregate(users)
2384
+ * .sort({ score: -1 })
2385
+ * .limit(5)
2386
+ * .toArray()
2387
+ * ```
2388
+ */
2389
+ limit(n: number): AggregatePipeline<TDef, TOutput>;
2390
+ /**
2391
+ * Include only specified fields in the output.
2392
+ *
2393
+ * Appends a `$project` stage with inclusion (`1`) for each key.
2394
+ * The `_id` field is always included. The output type narrows to
2395
+ * `Pick<TOutput, K | '_id'>`.
2396
+ *
2397
+ * @param spec - An object mapping field names to `1` for inclusion.
2398
+ * @returns A new pipeline with the `$project` stage appended.
2399
+ *
2400
+ * @example
2401
+ * ```ts
2402
+ * const namesOnly = await aggregate(users)
2403
+ * .project({ name: 1 })
2404
+ * .toArray()
2405
+ * // [{ _id: ..., name: 'Ada' }, ...]
2406
+ * ```
2407
+ */
2408
+ project<K extends keyof TOutput & string>(spec: Record<K, 1>): AggregatePipeline<TDef, Prettify<Pick<TOutput, K | ('_id' extends keyof TOutput ? '_id' : never)>>>;
2409
+ /**
2410
+ * Variadic shorthand for {@link project} — pick fields to include.
1305
2411
  *
1306
- * @param filter - Type-safe filter to match documents.
1307
- * @param options - Optional projection and validation overrides.
1308
- * @returns The matched document, or `null` if no document matches.
1309
- * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
2412
+ * Generates a `$project` stage that includes only the listed fields
2413
+ * (plus `_id`). Equivalent to `.project({ field1: 1, field2: 1 })`.
2414
+ *
2415
+ * @param fields - Field names to include in the output.
2416
+ * @returns A new pipeline with the `$project` stage appended.
1310
2417
  *
1311
2418
  * @example
1312
2419
  * ```ts
1313
- * const users = db.use(Users)
1314
- * const user = await users.findOne({ name: 'Ada' })
1315
- * if (user) console.log(user.role)
2420
+ * const namesAndRoles = await aggregate(users)
2421
+ * .pick('name', 'role')
2422
+ * .toArray()
1316
2423
  * ```
1317
2424
  */
1318
- findOne(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef> | null>;
2425
+ pick<K extends keyof TOutput & string>(...fields: K[]): AggregatePipeline<TDef, Prettify<Pick<TOutput, K | ('_id' extends keyof TOutput ? '_id' : never)>>>;
1319
2426
  /**
1320
- * Find a single document matching the filter, or throw if none exists.
2427
+ * Exclude specified fields from the output.
1321
2428
  *
1322
- * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
1323
- * instead of returning `null` when no document matches the filter.
2429
+ * Appends a `$project` stage with exclusion (`0`) for each key.
2430
+ * All other fields pass through. The output type becomes `Omit<TOutput, K>`.
1324
2431
  *
1325
- * @param filter - Type-safe filter to match documents.
1326
- * @param options - Optional projection and validation overrides.
1327
- * @returns The matched document (never null).
1328
- * @throws {ZodmonNotFoundError} When no document matches the filter.
1329
- * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
2432
+ * @param fields - Field names to exclude from the output.
2433
+ * @returns A new pipeline with the `$project` stage appended.
1330
2434
  *
1331
2435
  * @example
1332
2436
  * ```ts
1333
- * const users = db.use(Users)
1334
- * const user = await users.findOneOrThrow({ name: 'Ada' })
1335
- * console.log(user.role) // guaranteed non-null
2437
+ * const noAge = await aggregate(users)
2438
+ * .omit('age')
2439
+ * .toArray()
1336
2440
  * ```
1337
2441
  */
1338
- findOneOrThrow(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef>>;
2442
+ omit<K extends keyof TOutput & string>(...fields: K[]): AggregatePipeline<TDef, Prettify<Omit<TOutput, K>>>;
1339
2443
  /**
1340
- * Find all documents matching the filter, returning a chainable typed cursor.
2444
+ * Group documents by one or more fields with accumulator expressions.
1341
2445
  *
1342
- * The cursor is lazy no query is executed until a terminal method
1343
- * (`toArray`, `for await`) is called. Use `sort`, `skip`, and `limit`
1344
- * to shape the query before executing.
2446
+ * Accepts accumulators as either a **callback** (recommended) or a plain object.
1345
2447
  *
1346
- * @param filter - Type-safe filter to match documents.
1347
- * @param options - Optional validation overrides.
1348
- * @returns A typed cursor for chaining query modifiers.
2448
+ * The callback receives a typed `AccumulatorBuilder` with full autocomplete
2449
+ * and compile-time field validation. Builder methods resolve return types
2450
+ * to the actual field type (`T[K]`), not `unknown`.
2451
+ *
2452
+ * @param field - A field name or array of field names to group by.
2453
+ * @param accumulators - A callback `(acc) => ({ ... })` or plain accumulator object.
2454
+ * @returns A new pipeline with the `$group` stage appended.
1349
2455
  *
1350
2456
  * @example
1351
2457
  * ```ts
1352
- * const users = db.use(Users)
1353
- * const admins = await users.find({ role: 'admin' })
1354
- * .sort({ name: 1 })
1355
- * .limit(10)
2458
+ * // Callback style (recommended — full type safety)
2459
+ * const byRole = await aggregate(users)
2460
+ * .groupBy('role', acc => ({
2461
+ * count: acc.count(),
2462
+ * minSalary: acc.min('salary'), // → number
2463
+ * firstName: acc.first('name'), // → string
2464
+ * }))
2465
+ * .toArray()
2466
+ * ```
2467
+ *
2468
+ * @example
2469
+ * ```ts
2470
+ * // Object style (backward compatible)
2471
+ * const byRole = await aggregate(users)
2472
+ * .groupBy('role', { count: $count() })
2473
+ * .toArray()
2474
+ * ```
2475
+ *
2476
+ * @example
2477
+ * ```ts
2478
+ * // Compound groupBy
2479
+ * const byRoleAndDept = await aggregate(users)
2480
+ * .groupBy(['role', 'dept'], acc => ({ count: acc.count() }))
1356
2481
  * .toArray()
1357
2482
  * ```
1358
2483
  */
1359
- find(filter: TypedFilter<InferDocument<TDef>>, options?: FindOptions): TypedFindCursor<TDef, IndexNames<TDef>>;
2484
+ groupBy<K extends keyof TOutput & string, TAccum extends Record<string, Accumulator>>(field: K, accumulators: ((acc: AccumulatorBuilder<TOutput>) => TAccum) | TAccum): AggregatePipeline<TDef, GroupByResult<TOutput, K, TAccum>>;
2485
+ groupBy<K extends keyof TOutput & string, TAccum extends Record<string, Accumulator>>(field: K[], accumulators: ((acc: AccumulatorBuilder<TOutput>) => TAccum) | TAccum): AggregatePipeline<TDef, GroupByCompoundResult<TOutput, K, TAccum>>;
1360
2486
  /**
1361
- * Update a single document matching the filter.
2487
+ * Add new fields or overwrite existing ones in the output documents.
1362
2488
  *
1363
- * Applies the update operators to the first document that matches the filter.
1364
- * Does not validate the update against the Zod schema — validation happens
1365
- * at the field-operator level through {@link TypedUpdateFilter}.
2489
+ * **Callback style (recommended)** the `ExpressionBuilder` provides
2490
+ * autocomplete for field names and infers return types automatically:
1366
2491
  *
1367
- * @param filter - Type-safe filter to match documents.
1368
- * @param update - Type-safe update operators to apply.
1369
- * @param options - Optional settings such as `upsert`.
1370
- * @returns The MongoDB `UpdateResult` with match/modify counts.
2492
+ * ```ts
2493
+ * employees.aggregate()
2494
+ * .addFields(expr => ({
2495
+ * hireYear: expr.year('hiredAt'), // Expression<number>
2496
+ * isHighPay: expr.gte('salary', 100_000), // Expression<boolean>
2497
+ * }))
2498
+ * ```
2499
+ *
2500
+ * **Raw style** — pass MongoDB expression objects directly. Use an
2501
+ * explicit type parameter to preserve type safety:
2502
+ *
2503
+ * ```ts
2504
+ * // Tier 2: explicit type parameter
2505
+ * .addFields<{ hireYear: number }>({ hireYear: { $year: '$hiredAt' } })
2506
+ *
2507
+ * // Tier 3: no type parameter — fields resolve to unknown
2508
+ * .addFields({ hireYear: { $year: '$hiredAt' } })
2509
+ * ```
2510
+ *
2511
+ * @param fields - A callback `(expr) => ({ ... })` or plain fields object.
2512
+ * @returns A new pipeline with the `$addFields` stage appended.
1371
2513
  *
1372
2514
  * @example
1373
2515
  * ```ts
1374
- * const users = db.use(Users)
1375
- * const result = await users.updateOne({ name: 'Ada' }, { $set: { role: 'admin' } })
1376
- * console.log(result.modifiedCount) // 1
2516
+ * const enriched = await aggregate(employees)
2517
+ * .addFields(expr => ({
2518
+ * hireYear: expr.year('hiredAt'),
2519
+ * monthlySalary: expr.divide('salary', 12),
2520
+ * }))
2521
+ * .toArray()
1377
2522
  * ```
1378
2523
  */
1379
- updateOne(filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: UpdateOptions): Promise<UpdateResult>;
2524
+ addFields<TFields extends Record<string, Expression>>(fields: (expr: ExpressionBuilder<TOutput>) => TFields): AggregatePipeline<TDef, Prettify<TOutput & InferAddedFields<TFields>>>;
2525
+ addFields<TFields extends Record<string, unknown> = Record<string, unknown>>(fields: TFields): AggregatePipeline<TDef, Prettify<TOutput & TFields>>;
1380
2526
  /**
1381
- * Update all documents matching the filter.
2527
+ * Deconstruct an array field, outputting one document per array element.
1382
2528
  *
1383
- * Applies the update operators to every document that matches the filter.
1384
- * Does not validate the update against the Zod schema validation happens
1385
- * at the field-operator level through {@link TypedUpdateFilter}.
2529
+ * Appends an `$unwind` stage. The unwound field's type changes from
2530
+ * `T[]` to `T` in the output type. Documents with empty or missing
2531
+ * arrays are dropped unless `preserveEmpty` is `true`.
1386
2532
  *
1387
- * @param filter - Type-safe filter to match documents.
1388
- * @param update - Type-safe update operators to apply.
1389
- * @param options - Optional settings such as `upsert`.
1390
- * @returns The MongoDB `UpdateResult` with match/modify counts.
2533
+ * @param field - The name of the array field to unwind.
2534
+ * @param options - Optional settings for the unwind stage.
2535
+ * @param options.preserveEmpty - If `true`, documents with null, missing, or empty arrays are preserved.
2536
+ * @returns A new pipeline with the `$unwind` stage appended.
1391
2537
  *
1392
2538
  * @example
1393
2539
  * ```ts
1394
- * const users = db.use(Users)
1395
- * const result = await users.updateMany({ role: 'guest' }, { $set: { role: 'user' } })
1396
- * console.log(result.modifiedCount) // number of guests promoted
2540
+ * const flat = await aggregate(orders)
2541
+ * .unwind('items')
2542
+ * .toArray()
2543
+ * // Each result has a single `items` value instead of an array
1397
2544
  * ```
1398
2545
  */
1399
- updateMany(filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: UpdateOptions): Promise<UpdateResult>;
2546
+ unwind<K extends keyof TOutput & string>(field: K, options?: {
2547
+ preserveEmpty?: boolean;
2548
+ }): AggregatePipeline<TDef, UnwindResult<TOutput, K>>;
1400
2549
  /**
1401
- * Find a single document matching the filter, apply an update, and return the document.
2550
+ * Join documents from another collection via a `$lookup` stage.
1402
2551
  *
1403
- * By default, returns the document **after** the update is applied. Set
1404
- * `returnDocument: 'before'` to get the pre-update snapshot. The returned
1405
- * document is validated against the collection's Zod schema using the same
1406
- * resolution logic as {@link findOne}.
2552
+ * **Forward lookup** when the field has `.ref()` metadata pointing to
2553
+ * another collection, resolves the target collection and output type
2554
+ * automatically:
1407
2555
  *
1408
- * @param filter - Type-safe filter to match documents.
1409
- * @param update - Type-safe update operators to apply.
1410
- * @param options - Optional settings: `returnDocument`, `upsert`, `validate`.
1411
- * @returns The matched document (before or after update), or `null` if no document matches.
1412
- * @throws {ZodmonValidationError} When the returned document fails schema validation in strict mode.
2556
+ * ```ts
2557
+ * books.aggregate().lookup('authorId').toArray()
2558
+ * // Each book gains an `authors: Author[]` array
2559
+ * ```
2560
+ *
2561
+ * **Reverse lookup** — when the foreign key lives on the *other*
2562
+ * collection, pass the collection definition and the `on` field:
2563
+ *
2564
+ * ```ts
2565
+ * users.aggregate().lookup(Books, { on: 'authorId' }).toArray()
2566
+ * // Each user gains a `books: Book[]` array
2567
+ * ```
2568
+ *
2569
+ * Pass `unwind: true` to flatten the joined array into a single object
2570
+ * (adds a `$unwind` stage with `preserveNullAndEmptyArrays: true`).
2571
+ *
2572
+ * @param fieldOrCollection - A ref-bearing field name (forward) or collection definition (reverse).
2573
+ * @param options - `as` alias, `unwind` flag, and `on` (reverse only).
2574
+ * @returns A new pipeline with `$lookup` (and optionally `$unwind`) appended.
1413
2575
  *
1414
2576
  * @example
1415
2577
  * ```ts
1416
- * const users = db.use(Users)
1417
- * const user = await users.findOneAndUpdate(
1418
- * { name: 'Ada' },
1419
- * { $set: { role: 'admin' } },
1420
- * )
1421
- * if (user) console.log(user.role) // 'admin' (returned after update)
2578
+ * // Forward lookup with custom alias and unwind
2579
+ * const results = await aggregate(books)
2580
+ * .lookup('authorId', { as: 'author', unwind: true })
2581
+ * .toArray()
2582
+ * // Each book has a single `author: Author` object
2583
+ * ```
2584
+ *
2585
+ * @example
2586
+ * ```ts
2587
+ * // Reverse lookup: each author gains their books
2588
+ * const results = await aggregate(authors)
2589
+ * .lookup(Books, { on: 'authorId' })
2590
+ * .toArray()
2591
+ * // Each author has a `books: Book[]` array
1422
2592
  * ```
1423
2593
  */
1424
- findOneAndUpdate(filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: FindOneAndUpdateOptions): Promise<InferDocument<TDef> | null>;
2594
+ lookup<K extends RefFields<TDef>, TAs extends string = CollectionName<ExtractRefCollection<TDef, K>>>(field: K, options: {
2595
+ as?: TAs;
2596
+ unwind: true;
2597
+ }): AggregatePipeline<TDef, Prettify<TOutput & {
2598
+ [P in TAs]: InferDocument<ExtractRefCollection<TDef, K>>;
2599
+ }>>;
2600
+ lookup<K extends RefFields<TDef>, TAs extends string = CollectionName<ExtractRefCollection<TDef, K>>>(field: K, options?: {
2601
+ as?: TAs;
2602
+ unwind?: false;
2603
+ }): AggregatePipeline<TDef, Prettify<TOutput & {
2604
+ [P in TAs]: InferDocument<ExtractRefCollection<TDef, K>>[];
2605
+ }>>;
2606
+ lookup<TForeignDef extends AnyCollection, TAs extends string = CollectionName<TForeignDef>>(from: TForeignDef, options: {
2607
+ on: keyof InferDocument<TForeignDef> & string;
2608
+ as?: TAs;
2609
+ unwind: true;
2610
+ }): AggregatePipeline<TDef, Prettify<TOutput & {
2611
+ [P in TAs]: InferDocument<TForeignDef>;
2612
+ }>>;
2613
+ lookup<TForeignDef extends AnyCollection, TAs extends string = CollectionName<TForeignDef>>(from: TForeignDef, options: {
2614
+ on: keyof InferDocument<TForeignDef> & string;
2615
+ as?: TAs;
2616
+ unwind?: false;
2617
+ }): AggregatePipeline<TDef, Prettify<TOutput & {
2618
+ [P in TAs]: InferDocument<TForeignDef>[];
2619
+ }>>;
1425
2620
  /**
1426
- * Delete a single document matching the filter.
2621
+ * Count documents per group, sorted by count descending.
1427
2622
  *
1428
- * Removes the first document that matches the filter from the collection.
1429
- * No validation is performed — the document is deleted directly through
1430
- * the MongoDB driver.
2623
+ * Shorthand for `.groupBy(field, { count: $count() }).sort({ count: -1 })`.
1431
2624
  *
1432
- * @param filter - Type-safe filter to match documents.
1433
- * @returns The MongoDB `DeleteResult` with the deleted count.
2625
+ * @param field - The field to group and count by.
2626
+ * @returns A new pipeline producing `{ _id: TOutput[K], count: number }` results.
1434
2627
  *
1435
2628
  * @example
1436
2629
  * ```ts
1437
- * const users = db.use(Users)
1438
- * const result = await users.deleteOne({ name: 'Ada' })
1439
- * console.log(result.deletedCount) // 1
2630
+ * const roleCounts = await aggregate(users)
2631
+ * .countBy('role')
2632
+ * .toArray()
2633
+ * // [{ _id: 'user', count: 3 }, { _id: 'admin', count: 2 }]
1440
2634
  * ```
1441
2635
  */
1442
- deleteOne(filter: TypedFilter<InferDocument<TDef>>): Promise<DeleteResult>;
2636
+ countBy<K extends keyof TOutput & string>(field: K): AggregatePipeline<TDef, Prettify<{
2637
+ _id: TOutput[K];
2638
+ count: number;
2639
+ }>>;
1443
2640
  /**
1444
- * Delete all documents matching the filter.
2641
+ * Sum a numeric field per group, sorted by total descending.
1445
2642
  *
1446
- * Removes every document that matches the filter from the collection.
1447
- * No validation is performed — documents are deleted directly through
1448
- * the MongoDB driver.
2643
+ * Shorthand for `.groupBy(field, { total: $sum('$sumField') }).sort({ total: -1 })`.
1449
2644
  *
1450
- * @param filter - Type-safe filter to match documents.
1451
- * @returns The MongoDB `DeleteResult` with the deleted count.
2645
+ * @param field - The field to group by.
2646
+ * @param sumField - The numeric field to sum.
2647
+ * @returns A new pipeline producing `{ _id: TOutput[K], total: number }` results.
1452
2648
  *
1453
2649
  * @example
1454
2650
  * ```ts
1455
- * const users = db.use(Users)
1456
- * const result = await users.deleteMany({ role: 'guest' })
1457
- * console.log(result.deletedCount) // number of guests removed
2651
+ * const revenueByCategory = await aggregate(orders)
2652
+ * .sumBy('category', 'amount')
2653
+ * .toArray()
2654
+ * // [{ _id: 'electronics', total: 5000 }, ...]
1458
2655
  * ```
1459
2656
  */
1460
- deleteMany(filter: TypedFilter<InferDocument<TDef>>): Promise<DeleteResult>;
2657
+ sumBy<K extends keyof TOutput & string, S extends keyof TOutput & string>(field: K, sumField: S): AggregatePipeline<TDef, Prettify<{
2658
+ _id: TOutput[K];
2659
+ total: number;
2660
+ }>>;
1461
2661
  /**
1462
- * Find a single document matching the filter, delete it, and return the document.
2662
+ * Sort by a single field with a friendly direction name.
1463
2663
  *
1464
- * Returns the deleted document, or `null` if no document matches the filter.
1465
- * The returned document is validated against the collection's Zod schema
1466
- * using the same resolution logic as {@link findOne}.
2664
+ * Shorthand for `.sort({ [field]: direction === 'desc' ? -1 : 1 })`.
1467
2665
  *
1468
- * @param filter - Type-safe filter to match documents.
1469
- * @param options - Optional settings: `validate`.
1470
- * @returns The deleted document, or `null` if no document matches.
1471
- * @throws {ZodmonValidationError} When the returned document fails schema validation in strict mode.
2666
+ * @param field - The field to sort by.
2667
+ * @param direction - Sort direction: `'asc'` (default) or `'desc'`.
2668
+ * @returns A new pipeline with the `$sort` stage appended.
1472
2669
  *
1473
2670
  * @example
1474
2671
  * ```ts
1475
- * const users = db.use(Users)
1476
- * const user = await users.findOneAndDelete({ name: 'Ada' })
1477
- * if (user) console.log(user.name) // 'Ada' (the deleted document)
2672
+ * const youngest = await aggregate(users)
2673
+ * .sortBy('age')
2674
+ * .toArray()
1478
2675
  * ```
1479
2676
  */
1480
- findOneAndDelete(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneAndDeleteOptions): Promise<InferDocument<TDef> | null>;
2677
+ sortBy(field: keyof TOutput & string, direction?: 'asc' | 'desc'): AggregatePipeline<TDef, TOutput>;
1481
2678
  /**
1482
- * Synchronize the indexes declared in this collection's schema with MongoDB.
2679
+ * Return the top N documents sorted by a field descending.
1483
2680
  *
1484
- * Compares the desired indexes (from field-level `.index()` / `.unique()` /
1485
- * `.text()` / `.expireAfter()` and compound `indexes` in collection options)
1486
- * with the indexes that currently exist in MongoDB, then creates, drops, or
1487
- * reports differences depending on the options.
2681
+ * Shorthand for `.sort({ [by]: -1 }).limit(n)`.
1488
2682
  *
1489
- * @param options - Optional sync behavior (dryRun, dropOrphaned).
1490
- * @returns A summary of created, dropped, skipped, and stale indexes.
2683
+ * @param n - The number of documents to return.
2684
+ * @param options - An object with a `by` field specifying the sort key.
2685
+ * @returns A new pipeline with `$sort` and `$limit` stages appended.
1491
2686
  *
1492
2687
  * @example
1493
2688
  * ```ts
1494
- * const users = db.use(Users)
1495
- * const result = await users.syncIndexes()
1496
- * console.log('Created:', result.created)
1497
- * console.log('Stale:', result.stale.map(s => s.name))
2689
+ * const top3 = await aggregate(users)
2690
+ * .top(3, { by: 'score' })
2691
+ * .toArray()
1498
2692
  * ```
2693
+ */
2694
+ top(n: number, options: {
2695
+ by: keyof TOutput & string;
2696
+ }): AggregatePipeline<TDef, TOutput>;
2697
+ /**
2698
+ * Return the bottom N documents sorted by a field ascending.
2699
+ *
2700
+ * Shorthand for `.sort({ [by]: 1 }).limit(n)`.
2701
+ *
2702
+ * @param n - The number of documents to return.
2703
+ * @param options - An object with a `by` field specifying the sort key.
2704
+ * @returns A new pipeline with `$sort` and `$limit` stages appended.
1499
2705
  *
1500
2706
  * @example
1501
2707
  * ```ts
1502
- * // Dry run to preview changes without modifying the database
1503
- * const diff = await users.syncIndexes({ dryRun: true })
1504
- * console.log('Would create:', diff.created)
1505
- * console.log('Would drop:', diff.dropped)
2708
+ * const bottom3 = await aggregate(users)
2709
+ * .bottom(3, { by: 'score' })
2710
+ * .toArray()
1506
2711
  * ```
1507
2712
  */
1508
- syncIndexes(options?: SyncIndexesOptions): Promise<SyncIndexesResult>;
2713
+ bottom(n: number, options: {
2714
+ by: keyof TOutput & string;
2715
+ }): AggregatePipeline<TDef, TOutput>;
1509
2716
  }
2717
+ /**
2718
+ * Create a new aggregation pipeline for a collection.
2719
+ *
2720
+ * Returns an empty pipeline that can be extended with stage methods
2721
+ * like `raw()`, or future typed stages (`match`, `project`, etc.).
2722
+ *
2723
+ * @param handle - A typed collection handle obtained from `db.use()`.
2724
+ * @returns A new empty `AggregatePipeline` whose output type is the collection's document type.
2725
+ *
2726
+ * @example
2727
+ * ```ts
2728
+ * const users = db.use(Users)
2729
+ * const admins = await aggregate(users)
2730
+ * .raw({ $match: { role: 'admin' } })
2731
+ * .toArray()
2732
+ * ```
2733
+ */
2734
+ declare function aggregate<TDef extends AnyCollection>(handle: CollectionHandle<TDef>): AggregatePipeline<TDef, InferDocument<TDef>>;
1510
2735
 
1511
2736
  /**
1512
2737
  * Wraps a MongoDB `MongoClient` and `Db`, providing typed collection access
@@ -1540,7 +2765,7 @@ declare class Database {
1540
2765
  * @param def - A collection definition created by `collection()`.
1541
2766
  * @returns A typed collection handle for CRUD operations.
1542
2767
  */
1543
- use<TShape extends z.core.$ZodShape, TIndexes extends readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[] = readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[]>(def: CollectionDefinition<TShape, TIndexes>): CollectionHandle<CollectionDefinition<TShape, TIndexes>>;
2768
+ use<TName extends string, TShape extends z.core.$ZodShape, TIndexes extends readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[] = readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[]>(def: CollectionDefinition<TName, TShape, TIndexes>): CollectionHandle<CollectionDefinition<TName, TShape, TIndexes>>;
1544
2769
  /**
1545
2770
  * Synchronize indexes for all registered collections with MongoDB.
1546
2771
  *
@@ -1636,9 +2861,9 @@ declare function extractFieldIndexes(shape: z.core.$ZodShape): FieldIndexDefinit
1636
2861
  * })
1637
2862
  * ```
1638
2863
  */
1639
- declare function collection<TShape extends z.core.$ZodShape, const TIndexes extends readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[] = readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[]>(name: string, shape: TShape, options?: Omit<CollectionOptions<Extract<keyof TShape, string>>, 'indexes'> & {
2864
+ declare function collection<TName extends string, TShape extends z.core.$ZodShape, const TIndexes extends readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[] = readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[]>(name: TName, shape: TShape, options?: Omit<CollectionOptions<Extract<keyof TShape, string>>, 'indexes'> & {
1640
2865
  indexes?: TIndexes;
1641
- }): CollectionDefinition<TShape, [...TIndexes]>;
2866
+ }): CollectionDefinition<TName, TShape, [...TIndexes]>;
1642
2867
 
1643
2868
  type IndexDirection = 1 | -1;
1644
2869
  type CompoundIndexOptions = NonNullable<CompoundIndexDefinition['options']>;
@@ -2111,11 +3336,11 @@ declare const $: {
2111
3336
  readonly lte: <V>(value: V) => {
2112
3337
  $lte: V;
2113
3338
  };
2114
- readonly in: <V>(values: V[]) => {
2115
- $in: V[];
3339
+ readonly in: <V>(values: readonly V[]) => {
3340
+ $in: readonly V[];
2116
3341
  };
2117
- readonly nin: <V>(values: V[]) => {
2118
- $nin: V[];
3342
+ readonly nin: <V>(values: readonly V[]) => {
3343
+ $nin: readonly V[];
2119
3344
  };
2120
3345
  readonly exists: (flag?: boolean) => {
2121
3346
  $exists: boolean;
@@ -2207,8 +3432,8 @@ declare const $lte: <V>(value: V) => {
2207
3432
  * users.find({ role: $in(['admin', 'moderator']) })
2208
3433
  * ```
2209
3434
  */
2210
- declare const $in: <V>(values: V[]) => {
2211
- $in: V[];
3435
+ declare const $in: <V>(values: readonly V[]) => {
3436
+ $in: readonly V[];
2212
3437
  };
2213
3438
  /**
2214
3439
  * Matches none of the values in the specified array.
@@ -2218,8 +3443,8 @@ declare const $in: <V>(values: V[]) => {
2218
3443
  * users.find({ role: $nin(['banned', 'suspended']) })
2219
3444
  * ```
2220
3445
  */
2221
- declare const $nin: <V>(values: V[]) => {
2222
- $nin: V[];
3446
+ declare const $nin: <V>(values: readonly V[]) => {
3447
+ $nin: readonly V[];
2223
3448
  };
2224
3449
  /**
2225
3450
  * Matches documents where the field exists (or does not exist).
@@ -2319,84 +3544,6 @@ declare const $nor: <T>(...filters: NoInfer<TypedFilter<T>>[]) => TypedFilter<T>
2319
3544
  */
2320
3545
  declare const raw: <T = any>(filter: Record<string, unknown>) => TypedFilter<T>;
2321
3546
 
2322
- /**
2323
- * Type-level marker that carries the target collection type through the
2324
- * type system. Intersected with the schema return type by `.ref()` so
2325
- * that `RefFields<T>` (future) can extract ref relationships.
2326
- *
2327
- * This is a phantom brand — no runtime value has this property.
2328
- */
2329
- type RefMarker<TCollection extends AnyCollection = AnyCollection> = {
2330
- readonly _ref: TCollection;
2331
- };
2332
- /**
2333
- * Metadata stored in the WeakMap sidecar for schemas marked with `.ref()`.
2334
- * Holds a reference to the target collection definition object.
2335
- */
2336
- type RefMetadata = {
2337
- readonly collection: AnyCollection;
2338
- };
2339
- /**
2340
- * Module augmentation: adds `.ref()` to all `ZodType` schemas.
2341
- *
2342
- * The intersection constraint `this['_zod']['output'] extends InferDocument<TCollection>['_id']`
2343
- * ensures compile-time type safety: the field's output type must match the
2344
- * target collection's `_id` type. Mismatches produce a type error.
2345
- *
2346
- * Supports both default ObjectId `_id` and custom `_id` types (string, nanoid, etc.):
2347
- * - `objectId().ref(Users)` compiles when Users has ObjectId `_id`
2348
- * - `z.string().ref(Orgs)` compiles when Orgs has string `_id`
2349
- * - `z.string().ref(Users)` is a type error (string ≠ ObjectId)
2350
- * - `objectId().ref(Orgs)` is a type error (ObjectId ≠ string)
2351
- */
2352
- declare module 'zod' {
2353
- interface ZodType {
2354
- /**
2355
- * Declare a typed foreign key reference to another collection.
2356
- *
2357
- * Stores the target collection definition in metadata for runtime
2358
- * populate resolution, and brands the return type with
2359
- * `RefMarker<TCollection>` so `RefFields<T>` can extract refs
2360
- * at the type level.
2361
- *
2362
- * The field's output type must match the target collection's `_id` type.
2363
- * Mismatched types produce a compile error.
2364
- *
2365
- * Apply `.ref()` before wrapper methods like `.optional()` or `.nullable()`:
2366
- * `objectId().ref(Users).optional()` — not `objectId().optional().ref(Users)`.
2367
- *
2368
- * @param collection - The target collection definition object.
2369
- * @returns The same schema instance, branded with the ref marker.
2370
- *
2371
- * @example
2372
- * ```ts
2373
- * const Posts = collection('posts', {
2374
- * authorId: objectId().ref(Users),
2375
- * title: z.string(),
2376
- * })
2377
- * ```
2378
- */
2379
- ref<TCollection extends AnyCollection>(collection: TCollection & (this['_zod']['output'] extends InferDocument<TCollection>['_id'] ? unknown : never)): this & RefMarker<TCollection>;
2380
- }
2381
- }
2382
- /**
2383
- * Retrieve the ref metadata attached to a Zod schema, if any.
2384
- *
2385
- * Returns `undefined` when the schema was never marked with `.ref()`.
2386
- *
2387
- * @param schema - The Zod schema to inspect. Accepts `unknown` for
2388
- * convenience; non-object values safely return `undefined`.
2389
- * @returns The {@link RefMetadata} for the schema, or `undefined`.
2390
- *
2391
- * @example
2392
- * ```ts
2393
- * const authorId = objectId().ref(Users)
2394
- * const meta = getRefMetadata(authorId)
2395
- * // => { collection: Users }
2396
- * ```
2397
- */
2398
- declare function getRefMetadata(schema: unknown): RefMetadata | undefined;
2399
-
2400
3547
  /**
2401
3548
  * Shorthand for `CollectionHandle<TDef>`.
2402
3549
  *
@@ -2462,4 +3609,4 @@ type UpdateFilterOf<TDef extends AnyCollection> = TypedUpdateFilter<InferDocumen
2462
3609
  */
2463
3610
  type SortOf<TDef extends AnyCollection> = TypedSort<InferDocument<TDef>>;
2464
3611
 
2465
- export { $, $and, $eq, $exists, $gt, $gte, $in, $lt, $lte, $ne, $nin, $nor, $not, $or, $regex, type AddToSetEach, type AddToSetFields, type AnyCollection, type ArrayElement, type CollectionDefinition, CollectionHandle, type CollectionOptions, type ComparisonOperators, type CompoundIndexDefinition, type CurrentDateFields, type CursorPage, type CursorPaginateOptions, Database, type DotPathType, type DotPaths, type FieldIndexDefinition, type FilterOf, type FindOneAndDeleteOptions, type FindOneAndUpdateOptions, type FindOneOptions, type FindOptions, type HandleOf, type IncFields, IndexBuilder, type IndexMetadata, type IndexNames, type IndexOptions, type IndexSpec, type InferDocument, type InferInsert, type OffsetPage, type OffsetPaginateOptions, type PopFields, type PullFields, type PushFields, type PushModifiers, type RefMarker, type RefMetadata, type RenameFields, type ResolvedShape, type SetFields, type SortOf, type StaleIndex, type SyncIndexesOptions, type SyncIndexesResult, type TypedFilter, TypedFindCursor, type TypedSort, type TypedUpdateFilter, type UnsetFields, type UpdateFilterOf, type UpdateOptions, type ValidationMode, type ZodObjectId, ZodmonNotFoundError, ZodmonValidationError, checkUnindexedFields, collection, createClient, deleteMany, deleteOne, extractComparableOptions, extractDbName, extractFieldIndexes, find, findOne, findOneAndDelete, findOneAndUpdate, findOneOrThrow, generateIndexName, getIndexMetadata, getRefMetadata, index, insertMany, insertOne, isOid, objectId, oid, raw, serializeIndexKey, syncIndexes, toCompoundIndexSpec, toFieldIndexSpec, updateMany, updateOne };
3612
+ export { $, $addToSet, $and, $avg, $count, $eq, $exists, $first, $gt, $gte, $in, $last, $lt, $lte, $max, $min, $ne, $nin, $nor, $not, $or, $push, $regex, $sum, type Accumulator, type AccumulatorBuilder, type AddToSetEach, type AddToSetFields, AggregatePipeline, type AnyCollection, type ArrayElement, type CollectionDefinition, CollectionHandle, type CollectionName, type CollectionOptions, type ComparisonOperators, type CompoundIndexDefinition, type CurrentDateFields, type CursorPage, type CursorPaginateOptions, Database, type DotPathType, type DotPaths, type Expression, type ExpressionBuilder, type ExtractRefCollection, type FieldIndexDefinition, type FieldRef, type FieldRefType, type FilterOf, type FindOneAndDeleteOptions, type FindOneAndUpdateOptions, type FindOneOptions, type FindOptions, type GroupByCompoundResult, type GroupByResult, type HandleOf, type IncFields, IndexBuilder, type IndexMetadata, type IndexNames, type IndexOptions, type IndexSpec, type InferAccumulator, type InferAccumulators, type InferAddedFields, type InferDocument, type InferExpression, type InferInsert, type NarrowFromFilter, type OffsetPage, type OffsetPaginateOptions, type PopFields, type Prettify, type PullFields, type PushFields, type PushModifiers, type RefFields, type RefMarker, type RefMetadata, type RenameFields, type ResolvedShape, type SetFields, type SortOf, type StaleIndex, type SyncIndexesOptions, type SyncIndexesResult, type TypedFilter, TypedFindCursor, type TypedSort, type TypedUpdateFilter, type UnsetFields, type UnwindResult, type UpdateFilterOf, type UpdateOptions, type ValidationMode, type ZodObjectId, ZodmonNotFoundError, ZodmonValidationError, aggregate, checkUnindexedFields, collection, createAccumulatorBuilder, createClient, createExpressionBuilder, deleteMany, deleteOne, extractComparableOptions, extractDbName, extractFieldIndexes, find, findOne, findOneAndDelete, findOneAndUpdate, findOneOrThrow, generateIndexName, getIndexMetadata, getRefMetadata, index, insertMany, insertOne, isOid, objectId, oid, raw, serializeIndexKey, syncIndexes, toCompoundIndexSpec, toFieldIndexSpec, updateMany, updateOne };