@zodmon/core 0.8.0 → 0.10.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.cts 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,80 +305,664 @@ 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)}`)
394
+ * const authorId = objectId().ref(Users)
395
+ * const meta = getRefMetadata(authorId)
396
+ * // => { collection: Users }
397
+ * ```
398
+ */
399
+ declare function getRefMetadata(schema: unknown): RefMetadata | undefined;
400
+
401
+ /**
402
+ * Branded wrapper for accumulator expressions used inside `groupBy`.
403
+ *
404
+ * Carries the MongoDB accumulator expression at runtime and the inferred
405
+ * result type `T` at the type level.
406
+ *
407
+ * @example
408
+ * ```ts
409
+ * const acc: Accumulator<number> = {
410
+ * __accum: true,
411
+ * expr: { $sum: '$price' },
343
412
  * }
344
413
  * ```
345
414
  */
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>;
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;
355
420
  };
356
421
  /**
357
- * The result of a {@link syncIndexes} call.
422
+ * Extracts the result type from an `Accumulator<T>`.
358
423
  *
359
- * Every array contains index names. `stale` contains full details so the
360
- * caller can inspect the mismatch.
424
+ * @example
425
+ * ```ts
426
+ * type Count = InferAccumulator<Accumulator<number>>
427
+ * // ^? number
428
+ * ```
429
+ */
430
+ type InferAccumulator<T> = T extends Accumulator<infer R> ? R : never;
431
+ /**
432
+ * Maps an accumulator spec object to its inferred output shape.
433
+ *
434
+ * Given `{ count: Accumulator<number>, names: Accumulator<string[]> }`,
435
+ * produces `{ count: number, names: string[] }`.
361
436
  *
362
437
  * @example
363
438
  * ```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))
439
+ * type Spec = {
440
+ * count: Accumulator<number>
441
+ * total: Accumulator<number>
442
+ * }
443
+ * type Result = InferAccumulators<Spec>
444
+ * // ^? { count: number; total: number }
369
445
  * ```
370
446
  */
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[];
447
+ type InferAccumulators<T extends Record<string, Accumulator>> = {
448
+ [K in keyof T]: InferAccumulator<T[K]>;
380
449
  };
450
+ /**
451
+ * Field reference string prefixed with `$`, constrained to keys of `T`.
452
+ *
453
+ * @example
454
+ * ```ts
455
+ * type OrderRef = FieldRef<{ price: number; qty: number }>
456
+ * // ^? '$price' | '$qty'
457
+ * ```
458
+ */
459
+ type FieldRef<T> = `$${keyof T & string}`;
460
+ /**
461
+ * Typed accumulator factory passed to the `groupBy` callback.
462
+ *
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`.
466
+ *
467
+ * @typeParam T - The current pipeline output document type.
468
+ *
469
+ * @example
470
+ * ```ts
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
+ * }))
478
+ * ```
479
+ */
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][]>;
501
+ };
502
+ /**
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
+ * ```
511
+ */
512
+ type FieldRefType<T, F extends string> = F extends `$${infer K}` ? K extends keyof T ? T[K] : never : never;
513
+ /**
514
+ * Output shape of a single-field `groupBy` stage.
515
+ *
516
+ * The `_id` field holds the grouped-by field's value, and the rest of the
517
+ * shape comes from the inferred accumulator types.
518
+ *
519
+ * @example
520
+ * ```ts
521
+ * type Result = GroupByResult<Order, 'status', { count: Accumulator<number> }>
522
+ * // ^? { _id: string; count: number }
523
+ * ```
524
+ */
525
+ type GroupByResult<T, K extends keyof T, TAccum extends Record<string, Accumulator>> = Prettify<{
526
+ _id: T[K];
527
+ } & InferAccumulators<TAccum>>;
528
+ /**
529
+ * Output shape of a compound (multi-field) `groupBy` stage.
530
+ *
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.
533
+ *
534
+ * @example
535
+ * ```ts
536
+ * type Result = GroupByCompoundResult<Order, 'status' | 'region', { count: Accumulator<number> }>
537
+ * // ^? { _id: Pick<Order, 'status' | 'region'>; count: number }
538
+ * ```
539
+ */
540
+ type GroupByCompoundResult<T, K extends keyof T, TAccum extends Record<string, Accumulator>> = Prettify<{
541
+ _id: Prettify<Pick<T, K>>;
542
+ } & InferAccumulators<TAccum>>;
543
+ /**
544
+ * Output shape after unwinding an array field.
545
+ *
546
+ * The unwound field's type changes from `Array<E>` to `E`; all other
547
+ * fields are preserved as-is.
548
+ *
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.
570
+ *
571
+ * @example
572
+ * ```ts
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', ... }
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).
597
+ *
598
+ * @example
599
+ * ```ts
600
+ * type EmpRefs = RefFields<typeof Employees> // → 'departmentId'
601
+ * ```
602
+ */
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];
606
+ /**
607
+ * Given a collection definition and a ref-bearing field key, extract
608
+ * the target collection type from the `RefMarker<TCollection>` phantom.
609
+ *
610
+ * @example
611
+ * ```ts
612
+ * type Target = ExtractRefCollection<typeof Employees, 'departmentId'>
613
+ * // → typeof Departments
614
+ * ```
615
+ */
616
+ type ExtractRefCollection<TDef extends AnyCollection, K extends RefFields<TDef>> = TDef['shape'][K] extends RefMarker<infer TCol> ? TCol : never;
617
+ /**
618
+ * Extract the collection name string literal from a collection definition.
619
+ *
620
+ * @example
621
+ * ```ts
622
+ * CollectionName<typeof Departments> // → 'departments'
623
+ * ```
624
+ */
625
+ type CollectionName<TDef extends AnyCollection> = TDef['name'];
626
+ /**
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.
631
+ *
632
+ * @example
633
+ * ```ts
634
+ * const yearExpr: Expression<number> = {
635
+ * __expr: true,
636
+ * value: { $year: '$hiredAt' },
637
+ * }
638
+ * ```
639
+ */
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;
645
+ };
646
+ /**
647
+ * Unwrap an `Expression<T>` to its result type `T`.
648
+ * Non-Expression values resolve to `unknown`.
649
+ *
650
+ * @example
651
+ * ```ts
652
+ * type N = InferExpression<Expression<number>> // → number
653
+ * type U = InferExpression<{ $year: string }> // → unknown
654
+ * ```
655
+ */
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>;
745
+ };
746
+
747
+ /**
748
+ * Counts the number of documents in each group.
749
+ *
750
+ * Equivalent to `{ $sum: 1 }` in a MongoDB `$group` stage.
751
+ *
752
+ * @returns An `Accumulator<number>` that counts documents.
753
+ *
754
+ * @example
755
+ * ```ts
756
+ * const pipeline = orders.aggregate()
757
+ * .groupBy('status', { count: $count() })
758
+ * ```
759
+ */
760
+ declare const $count: () => Accumulator<number>;
761
+ /**
762
+ * Sums numeric values across documents in each group.
763
+ *
764
+ * Accepts either a `$`-prefixed field reference or a literal number.
765
+ *
766
+ * @param field - A `$field` reference to a numeric field, or a literal number.
767
+ * @returns An `Accumulator<number>`.
768
+ *
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>`.
784
+ *
785
+ * @example
786
+ * ```ts
787
+ * const pipeline = orders.aggregate()
788
+ * .groupBy('category', { avgPrice: $avg('$price') })
789
+ * ```
790
+ */
791
+ declare const $avg: (field: `$${string}`) => Accumulator<number>;
792
+ /**
793
+ * Returns the minimum value across documents in each group.
794
+ *
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.
797
+ *
798
+ * @typeParam R - The expected result type (defaults to `unknown`).
799
+ * @param field - A `$field` reference.
800
+ * @returns An `Accumulator<R>`.
801
+ *
802
+ * @example
803
+ * ```ts
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') }))
811
+ * ```
812
+ */
813
+ declare const $min: <R = unknown>(field: `$${string}`) => Accumulator<R>;
814
+ /**
815
+ * Returns the maximum value across documents in each group.
816
+ *
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.
819
+ *
820
+ * @typeParam R - The expected result type (defaults to `unknown`).
821
+ * @param field - A `$field` reference.
822
+ * @returns An `Accumulator<R>`.
823
+ *
824
+ * @example
825
+ * ```ts
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') }))
833
+ * ```
834
+ */
835
+ declare const $max: <R = unknown>(field: `$${string}`) => Accumulator<R>;
836
+ /**
837
+ * Returns the first value in each group according to the document order.
838
+ *
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.
841
+ *
842
+ * @typeParam R - The expected result type (defaults to `unknown`).
843
+ * @param field - A `$field` reference.
844
+ * @returns An `Accumulator<R>`.
845
+ *
846
+ * @example
847
+ * ```ts
848
+ * // Tier 2 — explicit result type
849
+ * const pipeline = orders.aggregate()
850
+ * .sort({ createdAt: 1 })
851
+ * .groupBy('status', { earliest: $first<Date>('$createdAt') })
852
+ *
853
+ * // Tier 1 — callback builder (recommended)
854
+ * orders.aggregate()
855
+ * .sort({ createdAt: 1 })
856
+ * .groupBy('status', acc => ({ earliest: acc.first('createdAt') }))
857
+ * ```
858
+ */
859
+ declare const $first: <R = unknown>(field: `$${string}`) => Accumulator<R>;
860
+ /**
861
+ * Returns the last value in each group according to the document order.
862
+ *
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.
865
+ *
866
+ * @typeParam R - The expected result type (defaults to `unknown`).
867
+ * @param field - A `$field` reference.
868
+ * @returns An `Accumulator<R>`.
869
+ *
870
+ * @example
871
+ * ```ts
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') }))
881
+ * ```
882
+ */
883
+ declare const $last: <R = unknown>(field: `$${string}`) => Accumulator<R>;
884
+ /**
885
+ * Pushes values into an array for each group.
886
+ *
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[]>`.
893
+ *
894
+ * @example
895
+ * ```ts
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') }))
903
+ * ```
904
+ */
905
+ declare const $push: <R = unknown>(field: `$${string}`) => Accumulator<R[]>;
906
+ /**
907
+ * Collects unique values into an array for each group (set semantics).
908
+ *
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[]>`.
915
+ *
916
+ * @example
917
+ * ```ts
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') }))
925
+ * ```
926
+ */
927
+ declare const $addToSet: <R = unknown>(field: `$${string}`) => Accumulator<R[]>;
928
+ /**
929
+ * Create a typed accumulator builder for use inside `groupBy` callbacks.
930
+ *
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.
938
+ *
939
+ * @example
940
+ * ```ts
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' } }
944
+ * ```
945
+ */
946
+ declare function createAccumulatorBuilder<T>(): AccumulatorBuilder<T>;
947
+ /**
948
+ * Create a typed expression builder for use inside `addFields` callbacks.
949
+ *
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.
954
+ *
955
+ * @typeParam T - The current pipeline output document type.
956
+ * @returns An `ExpressionBuilder<T>` with methods for each MongoDB expression operator.
957
+ *
958
+ * @example
959
+ * ```ts
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] } }
963
+ * ```
964
+ */
965
+ declare function createExpressionBuilder<T>(): ExpressionBuilder<T>;
381
966
 
382
967
  /**
383
968
  * Comparison operators for a field value of type `V`.
@@ -409,10 +994,10 @@ type ComparisonOperators<V> = {
409
994
  $lt?: V;
410
995
  /** Matches values less than or equal to the specified value. */
411
996
  $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[];
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[];
416
1001
  /** Matches documents where the field exists (`true`) or does not exist (`false`). */
417
1002
  $exists?: boolean;
418
1003
  /** Negates a comparison operator. */
@@ -1240,6 +1825,82 @@ declare function updateMany<TDef extends AnyCollection>(handle: CollectionHandle
1240
1825
  */
1241
1826
  declare function findOneAndUpdate<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, update: TypedUpdateFilter<InferDocument<TDef>>, options?: FindOneAndUpdateOptions): Promise<InferDocument<TDef> | null>;
1242
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
+
1243
1904
  /**
1244
1905
  * Typed wrapper around a MongoDB driver `Collection`.
1245
1906
  *
@@ -1499,14 +2160,578 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
1499
2160
  *
1500
2161
  * @example
1501
2162
  * ```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)
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.
2411
+ *
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.
2417
+ *
2418
+ * @example
2419
+ * ```ts
2420
+ * const namesAndRoles = await aggregate(users)
2421
+ * .pick('name', 'role')
2422
+ * .toArray()
2423
+ * ```
2424
+ */
2425
+ pick<K extends keyof TOutput & string>(...fields: K[]): AggregatePipeline<TDef, Prettify<Pick<TOutput, K | ('_id' extends keyof TOutput ? '_id' : never)>>>;
2426
+ /**
2427
+ * Exclude specified fields from the output.
2428
+ *
2429
+ * Appends a `$project` stage with exclusion (`0`) for each key.
2430
+ * All other fields pass through. The output type becomes `Omit<TOutput, K>`.
2431
+ *
2432
+ * @param fields - Field names to exclude from the output.
2433
+ * @returns A new pipeline with the `$project` stage appended.
2434
+ *
2435
+ * @example
2436
+ * ```ts
2437
+ * const noAge = await aggregate(users)
2438
+ * .omit('age')
2439
+ * .toArray()
2440
+ * ```
2441
+ */
2442
+ omit<K extends keyof TOutput & string>(...fields: K[]): AggregatePipeline<TDef, Prettify<Omit<TOutput, K>>>;
2443
+ /**
2444
+ * Group documents by one or more fields with accumulator expressions.
2445
+ *
2446
+ * Accepts accumulators as either a **callback** (recommended) or a plain object.
2447
+ *
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.
2455
+ *
2456
+ * @example
2457
+ * ```ts
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() }))
2481
+ * .toArray()
2482
+ * ```
2483
+ */
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>>;
2486
+ /**
2487
+ * Add new fields or overwrite existing ones in the output documents.
2488
+ *
2489
+ * **Callback style (recommended)** — the `ExpressionBuilder` provides
2490
+ * autocomplete for field names and infers return types automatically:
2491
+ *
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.
2513
+ *
2514
+ * @example
2515
+ * ```ts
2516
+ * const enriched = await aggregate(employees)
2517
+ * .addFields(expr => ({
2518
+ * hireYear: expr.year('hiredAt'),
2519
+ * monthlySalary: expr.divide('salary', 12),
2520
+ * }))
2521
+ * .toArray()
2522
+ * ```
2523
+ */
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>>;
2526
+ /**
2527
+ * Deconstruct an array field, outputting one document per array element.
2528
+ *
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`.
2532
+ *
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.
2537
+ *
2538
+ * @example
2539
+ * ```ts
2540
+ * const flat = await aggregate(orders)
2541
+ * .unwind('items')
2542
+ * .toArray()
2543
+ * // Each result has a single `items` value instead of an array
2544
+ * ```
2545
+ */
2546
+ unwind<K extends keyof TOutput & string>(field: K, options?: {
2547
+ preserveEmpty?: boolean;
2548
+ }): AggregatePipeline<TDef, UnwindResult<TOutput, K>>;
2549
+ /**
2550
+ * Join documents from another collection via a `$lookup` stage.
2551
+ *
2552
+ * **Forward lookup** — when the field has `.ref()` metadata pointing to
2553
+ * another collection, resolves the target collection and output type
2554
+ * automatically:
2555
+ *
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.
2575
+ *
2576
+ * @example
2577
+ * ```ts
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
2592
+ * ```
2593
+ */
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
+ }>>;
2620
+ /**
2621
+ * Count documents per group, sorted by count descending.
2622
+ *
2623
+ * Shorthand for `.groupBy(field, { count: $count() }).sort({ count: -1 })`.
2624
+ *
2625
+ * @param field - The field to group and count by.
2626
+ * @returns A new pipeline producing `{ _id: TOutput[K], count: number }` results.
2627
+ *
2628
+ * @example
2629
+ * ```ts
2630
+ * const roleCounts = await aggregate(users)
2631
+ * .countBy('role')
2632
+ * .toArray()
2633
+ * // [{ _id: 'user', count: 3 }, { _id: 'admin', count: 2 }]
2634
+ * ```
2635
+ */
2636
+ countBy<K extends keyof TOutput & string>(field: K): AggregatePipeline<TDef, Prettify<{
2637
+ _id: TOutput[K];
2638
+ count: number;
2639
+ }>>;
2640
+ /**
2641
+ * Sum a numeric field per group, sorted by total descending.
2642
+ *
2643
+ * Shorthand for `.groupBy(field, { total: $sum('$sumField') }).sort({ total: -1 })`.
2644
+ *
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.
2648
+ *
2649
+ * @example
2650
+ * ```ts
2651
+ * const revenueByCategory = await aggregate(orders)
2652
+ * .sumBy('category', 'amount')
2653
+ * .toArray()
2654
+ * // [{ _id: 'electronics', total: 5000 }, ...]
2655
+ * ```
2656
+ */
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
+ }>>;
2661
+ /**
2662
+ * Sort by a single field with a friendly direction name.
2663
+ *
2664
+ * Shorthand for `.sort({ [field]: direction === 'desc' ? -1 : 1 })`.
2665
+ *
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.
2669
+ *
2670
+ * @example
2671
+ * ```ts
2672
+ * const youngest = await aggregate(users)
2673
+ * .sortBy('age')
2674
+ * .toArray()
2675
+ * ```
2676
+ */
2677
+ sortBy(field: keyof TOutput & string, direction?: 'asc' | 'desc'): AggregatePipeline<TDef, TOutput>;
2678
+ /**
2679
+ * Return the top N documents sorted by a field descending.
2680
+ *
2681
+ * Shorthand for `.sort({ [by]: -1 }).limit(n)`.
2682
+ *
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.
2686
+ *
2687
+ * @example
2688
+ * ```ts
2689
+ * const top3 = await aggregate(users)
2690
+ * .top(3, { by: 'score' })
2691
+ * .toArray()
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.
2705
+ *
2706
+ * @example
2707
+ * ```ts
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']>;
@@ -1752,11 +2977,222 @@ declare function insertOne<TDef extends AnyCollection>(handle: CollectionHandle<
1752
2977
  */
1753
2978
  declare function insertMany<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, docs: InferInsert<TDef>[]): Promise<InferDocument<TDef>[]>;
1754
2979
 
2980
+ /**
2981
+ * Base error class for all Zodmon errors.
2982
+ *
2983
+ * Every error thrown by Zodmon extends this class, making it easy to catch all
2984
+ * Zodmon-related errors in a single `catch` block while still allowing more
2985
+ * specific handling via subclass checks.
2986
+ *
2987
+ * @example
2988
+ * ```ts
2989
+ * try {
2990
+ * await users.insertOne({ name: 123 })
2991
+ * } catch (err) {
2992
+ * if (err instanceof ZodmonError) {
2993
+ * console.log(err.name) // => 'ZodmonError' (or a subclass name)
2994
+ * console.log(err.collection) // => 'users'
2995
+ * console.log(err.cause) // => original Error, if provided
2996
+ * }
2997
+ * }
2998
+ * ```
2999
+ */
3000
+ declare class ZodmonError extends Error {
3001
+ readonly name: string;
3002
+ /** The MongoDB collection name associated with this error. */
3003
+ readonly collection: string;
3004
+ /** The underlying error that caused this error, if any. */
3005
+ readonly cause?: Error;
3006
+ constructor(message: string, collection: string, options?: {
3007
+ cause?: Error;
3008
+ });
3009
+ }
3010
+
3011
+ /**
3012
+ * Thrown when MongoDB rejects an operation due to authentication or authorization failure.
3013
+ *
3014
+ * Corresponds to MongoDB error codes 13 (Unauthorized) and 18 (AuthenticationFailed).
3015
+ * Inspect `.code` to distinguish between authorization and authentication failures.
3016
+ *
3017
+ * @example
3018
+ * ```ts
3019
+ * try {
3020
+ * await users.insertOne({ name: 'Alice' })
3021
+ * } catch (err) {
3022
+ * if (err instanceof ZodmonAuthError) {
3023
+ * console.log(err.message) // => 'Not authorized to perform this operation on "users"'
3024
+ * console.log(err.code) // => 13 or 18
3025
+ * console.log(err.collection) // => 'users'
3026
+ * }
3027
+ * }
3028
+ * ```
3029
+ */
3030
+ declare class ZodmonAuthError extends ZodmonError {
3031
+ readonly name = "ZodmonAuthError";
3032
+ /** The MongoDB error code (13 or 18). */
3033
+ readonly code: number;
3034
+ constructor(collection: string, code: number, cause: Error);
3035
+ }
3036
+
3037
+ /**
3038
+ * Thrown when a bulk write operation partially or fully fails.
3039
+ *
3040
+ * Exposes partial result counts (`.insertedCount`, `.matchedCount`, etc.) alongside
3041
+ * an array of `.writeErrors` describing each individual failure. Use this to determine
3042
+ * which operations succeeded and which failed.
3043
+ *
3044
+ * @example
3045
+ * ```ts
3046
+ * try {
3047
+ * await users.insertMany([{ name: 'Alice' }, { name: 'Alice' }]) // duplicate
3048
+ * } catch (err) {
3049
+ * if (err instanceof ZodmonBulkWriteError) {
3050
+ * console.log(err.insertedCount) // => 1
3051
+ * console.log(err.writeErrors) // => [{ index: 1, code: 11000, message: '...' }]
3052
+ * console.log(err.collection) // => 'users'
3053
+ * }
3054
+ * }
3055
+ * ```
3056
+ */
3057
+ declare class ZodmonBulkWriteError extends ZodmonError {
3058
+ readonly name = "ZodmonBulkWriteError";
3059
+ /** Number of documents successfully inserted. */
3060
+ readonly insertedCount: number;
3061
+ /** Number of documents matched by update filters. */
3062
+ readonly matchedCount: number;
3063
+ /** Number of documents actually modified. */
3064
+ readonly modifiedCount: number;
3065
+ /** Number of documents deleted. */
3066
+ readonly deletedCount: number;
3067
+ /** Individual write errors with their operation index, code, and message. */
3068
+ readonly writeErrors: Array<{
3069
+ index: number;
3070
+ code: number;
3071
+ message: string;
3072
+ }>;
3073
+ constructor(collection: string, cause: Error, totalOps?: number);
3074
+ }
3075
+
3076
+ /**
3077
+ * Thrown when a document fails server-side JSON Schema validation (MongoDB error code 121).
3078
+ *
3079
+ * This is distinct from {@link ZodmonValidationError}, which validates against Zod schemas
3080
+ * on the client side. This error surfaces when MongoDB's own document validation rejects
3081
+ * a write. Inspect `.errInfo` for the server's detailed validation failure information.
3082
+ *
3083
+ * @example
3084
+ * ```ts
3085
+ * try {
3086
+ * await users.insertOne({ name: 'Alice', age: -1 })
3087
+ * } catch (err) {
3088
+ * if (err instanceof ZodmonDocValidationError) {
3089
+ * console.log(err.message) // => 'Server-side document validation failed for "users": ...'
3090
+ * console.log(err.errInfo) // => { failingDocumentId: ..., details: ... }
3091
+ * console.log(err.collection) // => 'users'
3092
+ * }
3093
+ * }
3094
+ * ```
3095
+ */
3096
+ declare class ZodmonDocValidationError extends ZodmonError {
3097
+ readonly name = "ZodmonDocValidationError";
3098
+ /** Server-provided validation failure details. */
3099
+ readonly errInfo: unknown;
3100
+ constructor(collection: string, errInfo: unknown, cause: Error);
3101
+ }
3102
+
3103
+ /**
3104
+ * Thrown when a MongoDB insert or update violates a unique index constraint (E11000).
3105
+ *
3106
+ * Parses the duplicate key information from the MongoDB driver error to expose
3107
+ * structured fields: `.field` (first conflicting key), `.value`, `.index` (index name),
3108
+ * `.keyPattern`, and `.keyValue`. Falls back to regex parsing for older drivers.
3109
+ *
3110
+ * @example
3111
+ * ```ts
3112
+ * try {
3113
+ * await users.insertOne({ email: 'alice@example.com' })
3114
+ * } catch (err) {
3115
+ * if (err instanceof ZodmonDuplicateKeyError) {
3116
+ * console.log(err.field) // => 'email'
3117
+ * console.log(err.value) // => 'alice@example.com'
3118
+ * console.log(err.index) // => 'email_1'
3119
+ * console.log(err.keyPattern) // => { email: 1 }
3120
+ * console.log(err.keyValue) // => { email: 'alice@example.com' }
3121
+ * console.log(err.collection) // => 'users'
3122
+ * }
3123
+ * }
3124
+ * ```
3125
+ */
3126
+ declare class ZodmonDuplicateKeyError extends ZodmonError {
3127
+ readonly name = "ZodmonDuplicateKeyError";
3128
+ /** The first field that caused the duplicate key violation. */
3129
+ readonly field: string;
3130
+ /** The duplicate value, or `undefined` if it could not be extracted. */
3131
+ readonly value: unknown;
3132
+ /** The name of the index that was violated. */
3133
+ readonly index: string;
3134
+ /** The key pattern of the violated index (e.g. `{ email: 1 }`). */
3135
+ readonly keyPattern: Record<string, number>;
3136
+ /** The key values that caused the violation. */
3137
+ readonly keyValue: Record<string, unknown>;
3138
+ constructor(collection: string, cause: Error);
3139
+ }
3140
+
3141
+ /**
3142
+ * Thrown when a MongoDB index operation fails.
3143
+ *
3144
+ * Corresponds to MongoDB error codes 67 (CannotCreateIndex), 85 (IndexOptionsConflict),
3145
+ * and 86 (IndexKeySpecsConflict). Inspect `.code` to determine the specific failure reason.
3146
+ *
3147
+ * @example
3148
+ * ```ts
3149
+ * try {
3150
+ * await syncIndexes(db, [Users])
3151
+ * } catch (err) {
3152
+ * if (err instanceof ZodmonIndexError) {
3153
+ * console.log(err.message) // => 'Index options conflict on "users": ...'
3154
+ * console.log(err.code) // => 67, 85, or 86
3155
+ * console.log(err.collection) // => 'users'
3156
+ * }
3157
+ * }
3158
+ * ```
3159
+ */
3160
+ declare class ZodmonIndexError extends ZodmonError {
3161
+ readonly name = "ZodmonIndexError";
3162
+ /** The MongoDB error code (67, 85, or 86). */
3163
+ readonly code: number;
3164
+ constructor(collection: string, code: number, errmsg: string, cause: Error);
3165
+ }
3166
+
3167
+ /**
3168
+ * Thrown when a MongoDB operation fails due to a network error.
3169
+ *
3170
+ * Wraps `MongoNetworkError` from the MongoDB driver with the collection name
3171
+ * and the original error message for easier debugging.
3172
+ *
3173
+ * @example
3174
+ * ```ts
3175
+ * try {
3176
+ * await users.find({ status: 'active' })
3177
+ * } catch (err) {
3178
+ * if (err instanceof ZodmonNetworkError) {
3179
+ * console.log(err.message) // => 'Network error on "users": connection refused'
3180
+ * console.log(err.collection) // => 'users'
3181
+ * }
3182
+ * }
3183
+ * ```
3184
+ */
3185
+ declare class ZodmonNetworkError extends ZodmonError {
3186
+ readonly name = "ZodmonNetworkError";
3187
+ constructor(collection: string, cause: Error);
3188
+ }
3189
+
1755
3190
  /**
1756
3191
  * Thrown when a query expected to find a document returns no results.
1757
3192
  *
1758
3193
  * Used by {@link findOneOrThrow} when no document matches the provided filter.
1759
- * Callers can inspect `.collection` to identify which collection the query targeted.
3194
+ * Callers can inspect `.collection` to identify which collection the query targeted,
3195
+ * and `.filter` to see the query that produced no results.
1760
3196
  *
1761
3197
  * @example
1762
3198
  * ```ts
@@ -1766,15 +3202,68 @@ declare function insertMany<TDef extends AnyCollection>(handle: CollectionHandle
1766
3202
  * if (err instanceof ZodmonNotFoundError) {
1767
3203
  * console.log(err.message) // => 'Document not found in "users"'
1768
3204
  * console.log(err.collection) // => 'users'
3205
+ * console.log(err.filter) // => { name: 'nonexistent' }
1769
3206
  * }
1770
3207
  * }
1771
3208
  * ```
1772
3209
  */
1773
- declare class ZodmonNotFoundError extends Error {
3210
+ declare class ZodmonNotFoundError extends ZodmonError {
1774
3211
  readonly name = "ZodmonNotFoundError";
1775
- /** The MongoDB collection name where the query found no results. */
1776
- readonly collection: string;
1777
- constructor(collection: string);
3212
+ /** The filter that produced no results. */
3213
+ readonly filter: unknown;
3214
+ constructor(collection: string, filter: unknown);
3215
+ }
3216
+
3217
+ /**
3218
+ * Thrown when a MongoDB query is malformed or exceeds resource limits.
3219
+ *
3220
+ * Corresponds to MongoDB error codes 2 (BadValue), 9 (FailedToParse), and
3221
+ * 292 (QueryExceededMemoryLimit). Inspect `.code` to determine the specific failure.
3222
+ *
3223
+ * @example
3224
+ * ```ts
3225
+ * try {
3226
+ * await users.find({ $invalid: true })
3227
+ * } catch (err) {
3228
+ * if (err instanceof ZodmonQueryError) {
3229
+ * console.log(err.message) // => 'Bad value in query on "users": ...'
3230
+ * console.log(err.code) // => 2, 9, or 292
3231
+ * console.log(err.collection) // => 'users'
3232
+ * }
3233
+ * }
3234
+ * ```
3235
+ */
3236
+ declare class ZodmonQueryError extends ZodmonError {
3237
+ readonly name = "ZodmonQueryError";
3238
+ /** The MongoDB error code (2, 9, or 292). */
3239
+ readonly code: number;
3240
+ constructor(collection: string, code: number, errmsg: string, cause: Error);
3241
+ }
3242
+
3243
+ /**
3244
+ * Thrown when a MongoDB operation exceeds its server time limit.
3245
+ *
3246
+ * Corresponds to MongoDB error codes 50 (MaxTimeMSExpired) and
3247
+ * 262 (ExceededTimeLimit). Inspect `.code` to distinguish between the two.
3248
+ *
3249
+ * @example
3250
+ * ```ts
3251
+ * try {
3252
+ * await users.find({ status: 'active' })
3253
+ * } catch (err) {
3254
+ * if (err instanceof ZodmonTimeoutError) {
3255
+ * console.log(err.message) // => 'Operation timed out on "users": ...'
3256
+ * console.log(err.code) // => 50 or 262
3257
+ * console.log(err.collection) // => 'users'
3258
+ * }
3259
+ * }
3260
+ * ```
3261
+ */
3262
+ declare class ZodmonTimeoutError extends ZodmonError {
3263
+ readonly name = "ZodmonTimeoutError";
3264
+ /** The MongoDB error code (50 or 262). */
3265
+ readonly code: number;
3266
+ constructor(collection: string, code: number, cause: Error);
1778
3267
  }
1779
3268
 
1780
3269
  /**
@@ -1794,17 +3283,62 @@ declare class ZodmonNotFoundError extends Error {
1794
3283
  * // => 'Validation failed for "users": name (Expected string, received number)'
1795
3284
  * console.log(err.collection) // => 'users'
1796
3285
  * console.log(err.zodError) // => ZodError with .issues array
3286
+ * console.log(err.document) // => the document that failed validation
1797
3287
  * }
1798
3288
  * }
1799
3289
  * ```
1800
3290
  */
1801
- declare class ZodmonValidationError extends Error {
3291
+ declare class ZodmonValidationError extends ZodmonError {
1802
3292
  readonly name = "ZodmonValidationError";
1803
- /** The MongoDB collection name where the validation failed. */
1804
- readonly collection: string;
1805
3293
  /** The original Zod validation error with detailed issue information. */
1806
3294
  readonly zodError: z.ZodError;
1807
- constructor(collection: string, zodError: z.ZodError);
3295
+ /** The document that failed validation. */
3296
+ readonly document: unknown;
3297
+ constructor(collection: string, zodError: z.ZodError, document: unknown);
3298
+ }
3299
+
3300
+ /**
3301
+ * Maps a caught MongoDB error to the appropriate Zodmon error subclass and throws it.
3302
+ *
3303
+ * This function always throws — it never returns. Use it in `catch` blocks to convert
3304
+ * raw MongoDB driver errors into structured, typed Zodmon errors. If the error is already
3305
+ * a `ZodmonError`, it is rethrown as-is to prevent double-wrapping.
3306
+ *
3307
+ * @example
3308
+ * ```ts
3309
+ * try {
3310
+ * await db.collection('users').insertOne(doc)
3311
+ * } catch (err) {
3312
+ * wrapMongoError(err, 'users')
3313
+ * }
3314
+ * ```
3315
+ */
3316
+ declare function wrapMongoError(err: unknown, collection: string): never;
3317
+
3318
+ /**
3319
+ * Thrown when a transaction encounters a write conflict (MongoDB error code 112).
3320
+ *
3321
+ * This occurs when another operation modifies the same document concurrently during
3322
+ * a transaction. The recommended recovery strategy is to retry the transaction.
3323
+ *
3324
+ * @example
3325
+ * ```ts
3326
+ * try {
3327
+ * await db.transaction(async (tx) => {
3328
+ * const users = tx.use(Users)
3329
+ * await users.updateOne({ _id: id }, { $inc: { balance: -100 } })
3330
+ * })
3331
+ * } catch (err) {
3332
+ * if (err instanceof ZodmonWriteConflictError) {
3333
+ * console.log(err.message) // => 'Write conflict in "users": ...'
3334
+ * console.log(err.collection) // => 'users'
3335
+ * }
3336
+ * }
3337
+ * ```
3338
+ */
3339
+ declare class ZodmonWriteConflictError extends ZodmonError {
3340
+ readonly name = "ZodmonWriteConflictError";
3341
+ constructor(collection: string, cause: Error);
1808
3342
  }
1809
3343
 
1810
3344
  /**
@@ -2111,11 +3645,11 @@ declare const $: {
2111
3645
  readonly lte: <V>(value: V) => {
2112
3646
  $lte: V;
2113
3647
  };
2114
- readonly in: <V>(values: V[]) => {
2115
- $in: V[];
3648
+ readonly in: <V>(values: readonly V[]) => {
3649
+ $in: readonly V[];
2116
3650
  };
2117
- readonly nin: <V>(values: V[]) => {
2118
- $nin: V[];
3651
+ readonly nin: <V>(values: readonly V[]) => {
3652
+ $nin: readonly V[];
2119
3653
  };
2120
3654
  readonly exists: (flag?: boolean) => {
2121
3655
  $exists: boolean;
@@ -2207,8 +3741,8 @@ declare const $lte: <V>(value: V) => {
2207
3741
  * users.find({ role: $in(['admin', 'moderator']) })
2208
3742
  * ```
2209
3743
  */
2210
- declare const $in: <V>(values: V[]) => {
2211
- $in: V[];
3744
+ declare const $in: <V>(values: readonly V[]) => {
3745
+ $in: readonly V[];
2212
3746
  };
2213
3747
  /**
2214
3748
  * Matches none of the values in the specified array.
@@ -2218,8 +3752,8 @@ declare const $in: <V>(values: V[]) => {
2218
3752
  * users.find({ role: $nin(['banned', 'suspended']) })
2219
3753
  * ```
2220
3754
  */
2221
- declare const $nin: <V>(values: V[]) => {
2222
- $nin: V[];
3755
+ declare const $nin: <V>(values: readonly V[]) => {
3756
+ $nin: readonly V[];
2223
3757
  };
2224
3758
  /**
2225
3759
  * Matches documents where the field exists (or does not exist).
@@ -2319,84 +3853,6 @@ declare const $nor: <T>(...filters: NoInfer<TypedFilter<T>>[]) => TypedFilter<T>
2319
3853
  */
2320
3854
  declare const raw: <T = any>(filter: Record<string, unknown>) => TypedFilter<T>;
2321
3855
 
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
3856
  /**
2401
3857
  * Shorthand for `CollectionHandle<TDef>`.
2402
3858
  *
@@ -2462,4 +3918,4 @@ type UpdateFilterOf<TDef extends AnyCollection> = TypedUpdateFilter<InferDocumen
2462
3918
  */
2463
3919
  type SortOf<TDef extends AnyCollection> = TypedSort<InferDocument<TDef>>;
2464
3920
 
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 };
3921
+ 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, ZodmonAuthError, ZodmonBulkWriteError, ZodmonDocValidationError, ZodmonDuplicateKeyError, ZodmonError, ZodmonIndexError, ZodmonNetworkError, ZodmonNotFoundError, ZodmonQueryError, ZodmonTimeoutError, ZodmonValidationError, ZodmonWriteConflictError, 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, wrapMongoError };