@zodmon/core 0.11.0 → 0.12.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, Document, DeleteResult, FindCursor, Collection, UpdateResult, MongoClientOptions } from 'mongodb';
1
+ import { ObjectId, Document, DeleteResult, Collection, FindCursor, ClientSession, UpdateResult, MongoClientOptions } from 'mongodb';
2
2
  import { ZodPipe, ZodCustom, ZodTransform, z, ZodDefault } from 'zod';
3
3
 
4
4
  /**
@@ -260,6 +260,8 @@ type InferInsert<TDef extends {
260
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
261
  readonly name: TName;
262
262
  readonly schema: z.ZodObject<ResolvedShape<TShape>>;
263
+ /** Strict validation variant that throws on unknown fields. */
264
+ readonly strictSchema: z.ZodObject<ResolvedShape<TShape>>;
263
265
  readonly shape: TShape;
264
266
  readonly fieldIndexes: FieldIndexDefinition[];
265
267
  readonly compoundIndexes: TIndexes;
@@ -431,6 +433,48 @@ declare module 'zod' {
431
433
  * ```
432
434
  */
433
435
  declare function getRefMetadata(schema: unknown): RefMetadata | undefined;
436
+ /**
437
+ * Peel Zod wrappers (ZodOptional, ZodNullable, ZodDefault, ZodArray)
438
+ * to reach the inner schema type. Used to detect RefMarker on wrapped
439
+ * fields like `objectId().ref(X).optional()` or `z.array(objectId().ref(X))`.
440
+ *
441
+ * Bounded by wrapper depth (1-3 layers) — no circular risk.
442
+ *
443
+ * @example
444
+ * ```ts
445
+ * // ZodOptional<ZodObjectId & RefMarker<typeof Users>> → ZodObjectId & RefMarker<typeof Users>
446
+ * type Inner = UnwrapRef<typeof schema>
447
+ * ```
448
+ */
449
+ type UnwrapRef<T> = T extends z.ZodOptional<infer U> ? UnwrapRef<U> : T extends z.ZodNullable<infer U> ? UnwrapRef<U> : T extends z.ZodDefault<infer U> ? UnwrapRef<U> : T extends z.ZodArray<infer E> ? UnwrapRef<E> : T;
450
+ /**
451
+ * Extract field names that carry `.ref()` metadata from a collection definition.
452
+ *
453
+ * Unwraps ZodArray, ZodOptional, ZodNullable, ZodDefault to detect refs on
454
+ * inner schemas. Works for bare refs, array refs, and optional refs.
455
+ *
456
+ * @example
457
+ * ```ts
458
+ * type PostRefs = RefFields<typeof Posts>
459
+ * // ^? 'authorId' | 'categoryIds' | 'reviewerId'
460
+ * ```
461
+ */
462
+ type RefFields<TDef extends AnyCollection> = {
463
+ [K in keyof TDef['shape'] & string]: UnwrapRef<TDef['shape'][K]> extends RefMarker ? K : never;
464
+ }[keyof TDef['shape'] & string];
465
+ /**
466
+ * Given a collection definition and a ref-bearing field key, extract
467
+ * the target collection type from the `RefMarker<TCollection>` phantom.
468
+ *
469
+ * Unwraps Zod wrappers to reach the inner RefMarker.
470
+ *
471
+ * @example
472
+ * ```ts
473
+ * type Target = ExtractRefCollection<typeof Posts, 'authorId'>
474
+ * // ^? typeof Users
475
+ * ```
476
+ */
477
+ type ExtractRefCollection<TDef extends AnyCollection, K extends RefFields<TDef>> = UnwrapRef<TDef['shape'][K]> extends RefMarker<infer TCol> ? TCol : never;
434
478
 
435
479
  /**
436
480
  * Branded wrapper for accumulator expressions used inside `groupBy`.
@@ -572,12 +616,26 @@ type FieldRefType<T, F extends string> = F extends `$${infer K}` ? DotPathValue<
572
616
  type GroupByResult<T, K extends DotPath<T>, TAccum extends Record<string, Accumulator>> = Prettify<{
573
617
  _id: DotPathValue<T, K & string>;
574
618
  } & InferAccumulators<TAccum>>;
619
+ /**
620
+ * Replace dots with underscores in a string type.
621
+ *
622
+ * Used to sanitize dot-path keys for compound `groupBy` `_id` sub-documents,
623
+ * since MongoDB forbids dots in field names.
624
+ *
625
+ * @example
626
+ * ```ts
627
+ * type R = ReplaceDots<'address.city'>
628
+ * // ^? 'address_city'
629
+ * ```
630
+ */
631
+ type ReplaceDots<S extends string> = S extends `${infer A}.${infer B}` ? `${A}_${ReplaceDots<B>}` : S;
575
632
  /**
576
633
  * Output shape of a compound (multi-field) `groupBy` stage.
577
634
  *
578
635
  * The `_id` field is an object with one property per grouped field,
579
636
  * and the rest of the shape comes from the inferred accumulator types.
580
- * Supports dot-path keys for grouping by nested fields.
637
+ * Dot-path keys are sanitized (dots replaced with underscores) because
638
+ * MongoDB forbids dots in field names.
581
639
  *
582
640
  * @example
583
641
  * ```ts
@@ -585,14 +643,30 @@ type GroupByResult<T, K extends DotPath<T>, TAccum extends Record<string, Accumu
585
643
  * // ^? { _id: { status: string; region: string }; count: number }
586
644
  *
587
645
  * type Nested = GroupByCompoundResult<Order, 'name' | 'address.city', { count: Accumulator<number> }>
588
- * // ^? { _id: { name: string; 'address.city': string }; count: number }
646
+ * // ^? { _id: { name: string; address_city: string }; count: number }
589
647
  * ```
590
648
  */
591
649
  type GroupByCompoundResult<T, K extends DotPath<T>, TAccum extends Record<string, Accumulator>> = Prettify<{
592
650
  _id: Prettify<{
593
- [P in K & string]: DotPathValue<T, P>;
651
+ [P in K & string as ReplaceDots<P>]: DotPathValue<T, P>;
594
652
  }>;
595
653
  } & InferAccumulators<TAccum>>;
654
+ /**
655
+ * Output shape of a null-key `groupBy` stage (global aggregation).
656
+ *
657
+ * When `groupBy(null, ...)` is used, MongoDB collapses all documents
658
+ * into a single group with `_id: null`. The rest of the shape comes
659
+ * from the inferred accumulator types.
660
+ *
661
+ * @example
662
+ * ```ts
663
+ * type Result = GroupByNullResult<{ total: Accumulator<number> }>
664
+ * // ^? { _id: null; total: number }
665
+ * ```
666
+ */
667
+ type GroupByNullResult<TAccum extends Record<string, Accumulator>> = Prettify<{
668
+ _id: null;
669
+ } & InferAccumulators<TAccum>>;
596
670
  /**
597
671
  * Output shape after unwinding an array field.
598
672
  *
@@ -644,29 +718,6 @@ type NarrowFromFilter<T, F> = {
644
718
  $nin: ReadonlyArray<infer V extends T[K]>;
645
719
  } ? Exclude<T[K], V> : T[K] : T[K];
646
720
  };
647
- /**
648
- * Extract field keys from a collection definition whose schema fields
649
- * carry `RefMarker` (i.e. fields that have `.ref()` metadata).
650
- *
651
- * @example
652
- * ```ts
653
- * type EmpRefs = RefFields<typeof Employees> // → 'departmentId'
654
- * ```
655
- */
656
- type RefFields<TDef extends AnyCollection> = {
657
- [K in keyof TDef['shape'] & string]: TDef['shape'][K] extends RefMarker ? K : never;
658
- }[keyof TDef['shape'] & string];
659
- /**
660
- * Given a collection definition and a ref-bearing field key, extract
661
- * the target collection type from the `RefMarker<TCollection>` phantom.
662
- *
663
- * @example
664
- * ```ts
665
- * type Target = ExtractRefCollection<typeof Employees, 'departmentId'>
666
- * // → typeof Departments
667
- * ```
668
- */
669
- type ExtractRefCollection<TDef extends AnyCollection, K extends RefFields<TDef>> = TDef['shape'][K] extends RefMarker<infer TCol> ? TCol : never;
670
721
  /**
671
722
  * Extract the collection name string literal from a collection definition.
672
723
  *
@@ -723,12 +774,44 @@ type InferExpression<T> = T extends Expression<infer R> ? R : unknown;
723
774
  type InferAddedFields<T extends Record<string, unknown>> = {
724
775
  [K in keyof T]: InferExpression<T[K]>;
725
776
  };
777
+ /**
778
+ * Accepts either a dot-path field reference or a pre-built `Expression<R>`.
779
+ *
780
+ * Used throughout `ExpressionBuilder` to allow method inputs to be either a
781
+ * field name string (auto-prefixed with `$` at runtime) or the result of a
782
+ * previous expression call (embedded directly into the pipeline stage).
783
+ *
784
+ * @typeParam T - The current pipeline document type.
785
+ * @typeParam R - The runtime value type produced by this input.
786
+ *
787
+ * @example
788
+ * ```ts
789
+ * // Field path (common case)
790
+ * expr.round('salary', 2)
791
+ *
792
+ * // Composed expression
793
+ * expr.round(expr.multiply('salary', 0.1), 2)
794
+ * ```
795
+ */
796
+ type FieldOrExpr<T, R = unknown> = DotPath<T> | Expression<R>;
797
+ /**
798
+ * Extracts the element type from a readonly or mutable array type.
799
+ *
800
+ * @example
801
+ * ```ts
802
+ * type Item = ArrayElement<string[]> // → string
803
+ * type Num = ArrayElement<number[]> // → number
804
+ * type N = ArrayElement<string> // → never
805
+ * ```
806
+ */
807
+ type ArrayElement<T> = T extends ReadonlyArray<infer E> ? E : never;
726
808
  /**
727
809
  * Typed expression factory passed to the `addFields` callback.
728
810
  *
729
- * Each method constrains field names to `keyof T & string`, providing
730
- * IDE autocomplete and compile-time validation. Return types resolve
731
- * to `Expression<R>` where `R` is the MongoDB operator's output type.
811
+ * Each method accepts either a `DotPath<T>` field name string (auto-prefixed
812
+ * with `$` at runtime) or the result of a previous expression call
813
+ * (`Expression<R>`, embedded directly). This enables arbitrarily deep
814
+ * expression composition without `.raw()`.
732
815
  *
733
816
  * @typeParam T - The current pipeline output document type.
734
817
  *
@@ -736,65 +819,181 @@ type InferAddedFields<T extends Record<string, unknown>> = {
736
819
  * ```ts
737
820
  * employees.aggregate()
738
821
  * .addFields(expr => ({
739
- * hireYear: expr.year('hiredAt'), // Expression<number>
740
- * fullName: expr.concat('name', ' '), // Expression<string>
741
- * isHighPay: expr.gte('salary', 100_000), // Expression<boolean>
822
+ * hireYear: expr.year('hiredAt'),
823
+ * bonus: expr.round(expr.multiply('salary', 0.1), 2),
824
+ * profitMargin: expr.cond(
825
+ * expr.eq('revenue', 0),
826
+ * 0,
827
+ * expr.round(expr.divide(expr.subtract('revenue', 'cost'), 'revenue'), 4),
828
+ * ),
742
829
  * }))
743
830
  * ```
744
831
  */
745
832
  type ExpressionBuilder<T> = {
746
- /** Add a numeric field and a value. Returns `Expression<number>`. */
747
- add<K extends keyof T & string>(field: K, value: number | (keyof T & string)): Expression<number>;
748
- /** Subtract a value from a numeric field. Returns `Expression<number>`. */
749
- subtract<K extends keyof T & string>(field: K, value: number | (keyof T & string)): Expression<number>;
750
- /** Multiply a numeric field by a value. Returns `Expression<number>`. */
751
- multiply<K extends keyof T & string>(field: K, value: number | (keyof T & string)): Expression<number>;
752
- /** Divide a numeric field by a value. Returns `Expression<number>`. */
753
- divide<K extends keyof T & string>(field: K, value: number | (keyof T & string)): Expression<number>;
754
- /** Modulo of a numeric field. Returns `Expression<number>`. */
755
- mod<K extends keyof T & string>(field: K, value: number | (keyof T & string)): Expression<number>;
756
- /** Absolute value of a numeric field. Returns `Expression<number>`. */
757
- abs<K extends keyof T & string>(field: K): Expression<number>;
758
- /** Ceiling of a numeric field. Returns `Expression<number>`. */
759
- ceil<K extends keyof T & string>(field: K): Expression<number>;
760
- /** Floor of a numeric field. Returns `Expression<number>`. */
761
- floor<K extends keyof T & string>(field: K): Expression<number>;
762
- /** Round a numeric field to N decimal places. Returns `Expression<number>`. */
763
- round<K extends keyof T & string>(field: K, place?: number): Expression<number>;
764
- /** Concatenate field values and literal strings. Returns `Expression<string>`. */
765
- concat(...parts: Array<(keyof T & string) | string>): Expression<string>;
766
- /** Convert a string field to lowercase. Returns `Expression<string>`. */
767
- toLower<K extends keyof T & string>(field: K): Expression<string>;
768
- /** Convert a string field to uppercase. Returns `Expression<string>`. */
769
- toUpper<K extends keyof T & string>(field: K): Expression<string>;
770
- /** Trim whitespace from a string field. Returns `Expression<string>`. */
771
- trim<K extends keyof T & string>(field: K): Expression<string>;
772
- /** Extract a substring from a string field. Returns `Expression<string>`. */
773
- substr<K extends keyof T & string>(field: K, start: number, length: number): Expression<string>;
774
- /** Test equality of a field against a value. Returns `Expression<boolean>`. */
775
- eq<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
776
- /** Test if a field is greater than a value. Returns `Expression<boolean>`. */
777
- gt<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
778
- /** Test if a field is greater than or equal to a value. Returns `Expression<boolean>`. */
779
- gte<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
780
- /** Test if a field is less than a value. Returns `Expression<boolean>`. */
781
- lt<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
782
- /** Test if a field is less than or equal to a value. Returns `Expression<boolean>`. */
783
- lte<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
784
- /** Test if a field is not equal to a value. Returns `Expression<boolean>`. */
785
- ne<K extends keyof T & string>(field: K, value: T[K]): Expression<boolean>;
786
- /** Extract year from a date field. Returns `Expression<number>`. */
787
- year<K extends keyof T & string>(field: K): Expression<number>;
788
- /** Extract month (1-12) from a date field. Returns `Expression<number>`. */
789
- month<K extends keyof T & string>(field: K): Expression<number>;
790
- /** Extract day of month (1-31) from a date field. Returns `Expression<number>`. */
791
- dayOfMonth<K extends keyof T & string>(field: K): Expression<number>;
792
- /** Count elements in an array field. Returns `Expression<number>`. */
793
- size<K extends keyof T & string>(field: K): Expression<number>;
794
- /** Conditional expression. Returns `Expression<TThen | TElse>`. */
795
- cond<TThen, TElse>(condition: Expression<boolean>, thenValue: TThen, elseValue: TElse): Expression<TThen | TElse>;
796
- /** Replace null/missing field with a default. Returns `Expression<NonNullable<T[K]> | TDefault>`. */
797
- ifNull<K extends keyof T & string, TDefault>(field: K, fallback: TDefault): Expression<NonNullable<T[K]> | TDefault>;
833
+ /** Add two numeric values. Returns `Expression<number>`. */
834
+ add(a: FieldOrExpr<T, number>, b: FieldOrExpr<T, number> | number): Expression<number>;
835
+ /** Subtract `b` from `a`. Returns `Expression<number>`. */
836
+ subtract(a: FieldOrExpr<T, number>, b: FieldOrExpr<T, number> | number): Expression<number>;
837
+ /** Multiply two numeric values. Returns `Expression<number>`. */
838
+ multiply(a: FieldOrExpr<T, number>, b: FieldOrExpr<T, number> | number): Expression<number>;
839
+ /** Divide `a` by `b`. Returns `Expression<number>`. */
840
+ divide(a: FieldOrExpr<T, number>, b: FieldOrExpr<T, number> | number): Expression<number>;
841
+ /** Modulo of `a` divided by `b`. Returns `Expression<number>`. */
842
+ mod(a: FieldOrExpr<T, number>, b: FieldOrExpr<T, number> | number): Expression<number>;
843
+ /** Absolute value of a numeric field or expression. Returns `Expression<number>`. */
844
+ abs(field: FieldOrExpr<T, number>): Expression<number>;
845
+ /** Ceiling (round up) of a numeric field or expression. Returns `Expression<number>`. */
846
+ ceil(field: FieldOrExpr<T, number>): Expression<number>;
847
+ /** Floor (round down) of a numeric field or expression. Returns `Expression<number>`. */
848
+ floor(field: FieldOrExpr<T, number>): Expression<number>;
849
+ /** Round to `place` decimal places (default 0). Returns `Expression<number>`. */
850
+ round(field: FieldOrExpr<T, number>, place?: number): Expression<number>;
851
+ /** Concatenate field values, literal strings, and string expressions. Returns `Expression<string>`. */
852
+ concat(...parts: Array<DotPath<T> | string | Expression<string>>): Expression<string>;
853
+ /** Convert a string field or expression to lowercase. Returns `Expression<string>`. */
854
+ toLower(field: FieldOrExpr<T, string>): Expression<string>;
855
+ /** Convert a string field or expression to uppercase. Returns `Expression<string>`. */
856
+ toUpper(field: FieldOrExpr<T, string>): Expression<string>;
857
+ /** Trim whitespace from a string field or expression. Returns `Expression<string>`. */
858
+ trim(field: FieldOrExpr<T, string>): Expression<string>;
859
+ /** Extract a byte-based substring. Returns `Expression<string>`. */
860
+ substr(field: FieldOrExpr<T, string>, start: number, length: number): Expression<string>;
861
+ /** Convert any value (number, date, ObjectId, etc.) to its string representation. Returns `Expression<string>`. */
862
+ toString(field: FieldOrExpr<T, unknown>): Expression<string>;
863
+ /** Test equality of a field against a value (type-safe value). Returns `Expression<boolean>`. */
864
+ eq<F extends DotPath<T>>(field: F, value: DotPathValue<T, F>): Expression<boolean>;
865
+ /** Test equality of a field against a field reference expression (field-vs-field). Returns `Expression<boolean>`. */
866
+ eq(field: DotPath<T>, value: Expression<unknown>): Expression<boolean>;
867
+ /** Test equality with a composed expression as the left-hand side. Returns `Expression<boolean>`. */
868
+ eq(field: Expression<unknown>, value: unknown): Expression<boolean>;
869
+ /** Test if a field is greater than a value (type-safe). Returns `Expression<boolean>`. */
870
+ gt<F extends DotPath<T>>(field: F, value: DotPathValue<T, F>): Expression<boolean>;
871
+ /** Test if a field is greater than a field reference expression (field-vs-field). Returns `Expression<boolean>`. */
872
+ gt(field: DotPath<T>, value: Expression<unknown>): Expression<boolean>;
873
+ /** Test if a composed expression is greater than a value. Returns `Expression<boolean>`. */
874
+ gt(field: Expression<unknown>, value: unknown): Expression<boolean>;
875
+ /** Test if a field is greater than or equal to a value (type-safe). Returns `Expression<boolean>`. */
876
+ gte<F extends DotPath<T>>(field: F, value: DotPathValue<T, F>): Expression<boolean>;
877
+ /** Test if a field is greater than or equal to a field reference expression (field-vs-field). Returns `Expression<boolean>`. */
878
+ gte(field: DotPath<T>, value: Expression<unknown>): Expression<boolean>;
879
+ /** Test if a composed expression is greater than or equal to a value. Returns `Expression<boolean>`. */
880
+ gte(field: Expression<unknown>, value: unknown): Expression<boolean>;
881
+ /** Test if a field is less than a value (type-safe). Returns `Expression<boolean>`. */
882
+ lt<F extends DotPath<T>>(field: F, value: DotPathValue<T, F>): Expression<boolean>;
883
+ /** Test if a field is less than a field reference expression (field-vs-field). Returns `Expression<boolean>`. */
884
+ lt(field: DotPath<T>, value: Expression<unknown>): Expression<boolean>;
885
+ /** Test if a composed expression is less than a value. Returns `Expression<boolean>`. */
886
+ lt(field: Expression<unknown>, value: unknown): Expression<boolean>;
887
+ /** Test if a field is less than or equal to a value (type-safe). Returns `Expression<boolean>`. */
888
+ lte<F extends DotPath<T>>(field: F, value: DotPathValue<T, F>): Expression<boolean>;
889
+ /** Test if a field is less than or equal to a field reference expression (field-vs-field). Returns `Expression<boolean>`. */
890
+ lte(field: DotPath<T>, value: Expression<unknown>): Expression<boolean>;
891
+ /** Test if a composed expression is less than or equal to a value. Returns `Expression<boolean>`. */
892
+ lte(field: Expression<unknown>, value: unknown): Expression<boolean>;
893
+ /** Test if a field is not equal to a value (type-safe). Returns `Expression<boolean>`. */
894
+ ne<F extends DotPath<T>>(field: F, value: DotPathValue<T, F>): Expression<boolean>;
895
+ /** Test if a field is not equal to a field reference expression (field-vs-field). Returns `Expression<boolean>`. */
896
+ ne(field: DotPath<T>, value: Expression<unknown>): Expression<boolean>;
897
+ /** Test if a composed expression is not equal to a value. Returns `Expression<boolean>`. */
898
+ ne(field: Expression<unknown>, value: unknown): Expression<boolean>;
899
+ /** Extract year from a date field or expression. Returns `Expression<number>`. */
900
+ year(field: FieldOrExpr<T, Date>): Expression<number>;
901
+ /** Extract month (1-12) from a date field or expression. Returns `Expression<number>`. */
902
+ month(field: FieldOrExpr<T, Date>): Expression<number>;
903
+ /** Extract day of month (1-31) from a date field or expression. Returns `Expression<number>`. */
904
+ dayOfMonth(field: FieldOrExpr<T, Date>): Expression<number>;
905
+ /** Extract day of week (1=Sunday … 7=Saturday) from a date field or expression. Returns `Expression<number>`. */
906
+ dayOfWeek(field: FieldOrExpr<T, Date>): Expression<number>;
907
+ /**
908
+ * Format a date field or expression as a string.
909
+ *
910
+ * Uses MongoDB format specifiers: `%Y` year, `%m` month (01-12), `%d` day (01-31), etc.
911
+ *
912
+ * @example
913
+ * ```ts
914
+ * expr.dateToString('createdAt', '%Y-%m') // Expression<string>
915
+ * ```
916
+ */
917
+ dateToString(field: FieldOrExpr<T, Date>, format: string): Expression<string>;
918
+ /** Current timestamp at query start (`$$NOW`). Returns `Expression<Date>`. */
919
+ now(): Expression<Date>;
920
+ /** Count elements in an array field or expression. Returns `Expression<number>`. */
921
+ size(field: FieldOrExpr<T, unknown[]>): Expression<number>;
922
+ /**
923
+ * Check if a value is present in an array.
924
+ *
925
+ * Named `inArray` to distinguish from the filter-level `$in` operator.
926
+ * The second argument may be a field/expression producing an array, or a literal array.
927
+ *
928
+ * @example
929
+ * ```ts
930
+ * expr.inArray(expr.dayOfWeek('createdAt'), [1, 7]) // Expression<boolean>
931
+ * ```
932
+ */
933
+ inArray(value: FieldOrExpr<T, unknown>, array: FieldOrExpr<T, unknown[]> | ReadonlyArray<unknown>): Expression<boolean>;
934
+ /**
935
+ * Return the element at `index` from an array field (zero-based).
936
+ *
937
+ * When called with a typed field path, the element type is inferred from the schema.
938
+ * When called with a composed expression, the element type is `unknown`.
939
+ *
940
+ * @example
941
+ * ```ts
942
+ * // items: Item[] in schema → Expression<Item>
943
+ * expr.arrayElemAt('items', 0)
944
+ * ```
945
+ */
946
+ arrayElemAt<F extends DotPath<T>>(field: F, index: number): Expression<ArrayElement<DotPathValue<T, F>>>;
947
+ /** Return the element at `index` from an array expression. Returns `Expression<unknown>`. */
948
+ arrayElemAt(field: Expression<unknown[]>, index: number): Expression<unknown>;
949
+ /**
950
+ * Conditional expression: if `condition` is true return `thenValue`, else return `elseValue`.
951
+ *
952
+ * Both `thenValue` and `elseValue` accept literal values or composed `Expression<R>` results.
953
+ * Returns `Expression<TThen | TElse>`.
954
+ *
955
+ * @example
956
+ * ```ts
957
+ * expr.cond(expr.eq('revenue', 0), 0, expr.round(expr.divide('profit', 'revenue'), 4))
958
+ * ```
959
+ */
960
+ cond<TThen, TElse>(condition: Expression<boolean>, thenValue: TThen | Expression<TThen>, elseValue: TElse | Expression<TElse>): Expression<TThen | TElse>;
961
+ /** Replace null/missing field with a default. Returns `Expression<NonNullable<DotPathValue<T, F>> | TDefault>`. */
962
+ ifNull<F extends DotPath<T>, TDefault>(field: F, fallback: TDefault): Expression<NonNullable<DotPathValue<T, F>> | TDefault>;
963
+ /** Replace null/missing expression result with a default. Returns `Expression<NonNullable<R> | TDefault>`. */
964
+ ifNull<R, TDefault>(field: Expression<R>, fallback: TDefault): Expression<NonNullable<R> | TDefault>;
965
+ /**
966
+ * Multi-branch conditional. Evaluates `branches` in order and returns the first matching `then` value,
967
+ * or `fallback` if no branch matches.
968
+ *
969
+ * TypeScript infers `TThen` as the union of all `then` value types and the fallback type.
970
+ *
971
+ * @example
972
+ * ```ts
973
+ * expr.switch([
974
+ * { case: expr.gte('spend', 10_000), then: 'VIP' },
975
+ * { case: expr.gte('spend', 5_000), then: 'Gold' },
976
+ * ], 'Bronze')
977
+ * // → Expression<'VIP' | 'Gold' | 'Bronze'>
978
+ * ```
979
+ */
980
+ switch<TThen>(branches: ReadonlyArray<{
981
+ case: Expression<boolean>;
982
+ then: TThen | Expression<TThen>;
983
+ }>, fallback: TThen | Expression<TThen>): Expression<TThen>;
984
+ /**
985
+ * Create a typed field reference for use as a comparison operand.
986
+ *
987
+ * Use when the right-hand side of a comparison should be a document
988
+ * field rather than a literal value (field-vs-field comparison).
989
+ *
990
+ * @example
991
+ * ```ts
992
+ * // Compare two fields in the same document
993
+ * .match({}, (expr) => expr.gt('totalAmount', expr.field('refundedAmount')))
994
+ * ```
995
+ */
996
+ field<F extends DotPath<T>>(name: F): Expression<DotPathValue<T, F>>;
798
997
  };
799
998
 
800
999
  /**
@@ -1030,19 +1229,20 @@ declare function createAccumulatorBuilder<T>(): AccumulatorBuilder<T>;
1030
1229
  /**
1031
1230
  * Create a typed expression builder for use inside `addFields` callbacks.
1032
1231
  *
1033
- * The builder adds the `$` prefix to field names automatically and returns
1034
- * properly typed `Expression<T>` values. Primarily used internally by
1035
- * `AggregatePipeline.addFields` — most users interact with the builder via
1036
- * the callback parameter rather than calling this directly.
1232
+ * The builder accepts either a field name string (auto-prefixed with `$`) or a
1233
+ * pre-built `Expression<R>` from another builder method, enabling composition.
1234
+ * Primarily used internally by `AggregatePipeline.addFields` — most users
1235
+ * interact with the builder via the callback parameter rather than calling
1236
+ * this directly.
1037
1237
  *
1038
1238
  * @typeParam T - The current pipeline output document type.
1039
1239
  * @returns An `ExpressionBuilder<T>` with methods for each MongoDB expression operator.
1040
1240
  *
1041
1241
  * @example
1042
1242
  * ```ts
1043
- * const expr = createExpressionBuilder<{ salary: number; name: string; hiredAt: Date }>()
1044
- * expr.year('hiredAt') // { __expr: true, value: { $year: '$hiredAt' } }
1045
- * expr.multiply('salary', 0.9) // { __expr: true, value: { $multiply: ['$salary', 0.9] } }
1243
+ * const expr = createExpressionBuilder<{ salary: number; hiredAt: Date }>()
1244
+ * expr.year('hiredAt') // { __expr: true, value: { $year: '$hiredAt' } }
1245
+ * expr.round(expr.multiply('salary', 0.1), 2) // { __expr: true, value: { $round: [{ $multiply: ['$salary', 0.1] }, 2] } }
1046
1246
  * ```
1047
1247
  */
1048
1248
  declare function createExpressionBuilder<T>(): ExpressionBuilder<T>;
@@ -1088,40 +1288,21 @@ type ComparisonOperators<V> = {
1088
1288
  } & (V extends string ? {
1089
1289
  $regex?: RegExp | string;
1090
1290
  } : unknown);
1091
- /** Depth counter for limiting dot-notation recursion. Index = current depth, value = next depth. */
1092
- type Prev = [never, 0, 1, 2];
1093
1291
  /**
1094
- * Generates a union of all valid dot-separated paths for nested object fields in `T`.
1292
+ * Dot-separated paths for nested fields, excluding top-level keys.
1095
1293
  *
1096
- * Recursion is limited to 3 levels deep to prevent TypeScript compilation performance issues.
1097
- * Only plain object fields are traversed arrays, `Date`, `RegExp`, and `ObjectId` are
1098
- * treated as leaf nodes and do not produce sub-paths.
1294
+ * Filters out `keyof T & string` because top-level fields are already
1295
+ * handled by slot (A) of {@link TypedFilter}. Keeping them separate
1296
+ * preserves the intersection structure for lazy TypeScript evaluation.
1099
1297
  *
1100
1298
  * @example
1101
1299
  * ```ts
1102
- * type User = { address: { city: string; geo: { lat: number; lng: number } } }
1103
- *
1104
- * // _DotPaths<User> = 'address.city' | 'address.geo' | 'address.geo.lat' | 'address.geo.lng'
1300
+ * type User = { name: string; address: { city: string } }
1301
+ * type Paths = DotSubPaths<User>
1302
+ * // ^? 'address.city'
1105
1303
  * ```
1106
1304
  */
1107
- type _DotPaths<T, Depth extends number = 3> = Depth extends 0 ? never : {
1108
- [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;
1109
- }[keyof T & string];
1110
- /**
1111
- * Resolves the value type at a dot-separated path `P` within type `T`.
1112
- *
1113
- * Splits `P` on the first `.` and recursively descends into `T`'s nested types.
1114
- * Returns `never` if the path is invalid.
1115
- *
1116
- * @example
1117
- * ```ts
1118
- * type User = { address: { city: string; geo: { lat: number } } }
1119
- *
1120
- * // _DotPathType<User, 'address.city'> = string
1121
- * // _DotPathType<User, 'address.geo.lat'> = number
1122
- * ```
1123
- */
1124
- 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;
1305
+ type DotSubPaths<T> = Exclude<DotPath<T>, keyof T & string>;
1125
1306
  /**
1126
1307
  * Strict type-safe MongoDB filter query type.
1127
1308
  *
@@ -1132,7 +1313,7 @@ type _DotPathType<T, P extends string> = P extends `${infer K}.${infer Rest}` ?
1132
1313
  * Supports three forms of filter expressions:
1133
1314
  * - **Direct field values** (implicit `$eq`): `{ name: 'Alice' }`
1134
1315
  * - **Comparison operators**: `{ age: { $gt: 25 } }` or `{ age: $gt(25) }`
1135
- * - **Dot notation** for nested fields up to 3 levels: `{ 'address.city': 'NYC' }`
1316
+ * - **Dot notation** including nested and array-traversed fields: `{ 'address.city': 'NYC' }`
1136
1317
  *
1137
1318
  * Logical operators `$and`, `$or`, and `$nor` accept arrays of `TypedFilter<T>`
1138
1319
  * for composing complex queries.
@@ -1161,7 +1342,7 @@ type _DotPathType<T, P extends string> = P extends `${infer K}.${infer Rest}` ?
1161
1342
  type TypedFilter<T> = {
1162
1343
  [K in keyof T]?: T[K] | ComparisonOperators<T[K]>;
1163
1344
  } & {
1164
- [P in _DotPaths<T>]?: _DotPathType<T, P> | ComparisonOperators<_DotPathType<T, P>>;
1345
+ [P in DotSubPaths<T>]?: DotPathValue<T, P> | ComparisonOperators<DotPathValue<T, P>>;
1165
1346
  } & {
1166
1347
  /** Joins clauses with a logical AND. Matches documents that satisfy all filters. */
1167
1348
  $and?: TypedFilter<T>[];
@@ -1451,6 +1632,400 @@ declare function isInclusionProjection(projection: Record<string, 0 | 1 | boolea
1451
1632
  */
1452
1633
  declare function deriveProjectedSchema(schema: z.ZodObject<z.core.$ZodShape>, projection: Record<string, 0 | 1 | boolean>): z.ZodObject<z.core.$ZodShape>;
1453
1634
 
1635
+ /**
1636
+ * Typed return value from {@link PopulateRefBuilder.project}.
1637
+ *
1638
+ * Carries the projection type `P` for compile-time narrowing in populate calls.
1639
+ * Callers never construct this directly — it is returned by the builder and
1640
+ * consumed by the `.populate()` overload to narrow the result type.
1641
+ *
1642
+ * @example
1643
+ * ```ts
1644
+ * // P is inferred as { name: 1; email: 1 } — post.authorId is narrowed accordingly
1645
+ * const post = await posts.findOne({}).populate('authorId', (b) => b.project({ name: 1, email: 1 }))
1646
+ * ```
1647
+ */
1648
+ type PopulateProjectionConfig<P> = {
1649
+ readonly projection: P;
1650
+ };
1651
+ /**
1652
+ * Fluent builder passed to populate projection callbacks.
1653
+ *
1654
+ * Call `.project()` to specify which fields to fetch from the referenced collection.
1655
+ * The projection type `P` flows back to the `.populate()` overload to narrow the
1656
+ * result type at compile time.
1657
+ *
1658
+ * @typeParam TDoc - The referenced document type (e.g. `InferDocument<typeof Users>`).
1659
+ *
1660
+ * @example
1661
+ * ```ts
1662
+ * posts.findOne({}).populate('authorId', (b) => b.project({ name: 1, email: 1 }))
1663
+ * // post.authorId is narrowed to { _id: ObjectId; name: string; email: string }
1664
+ * ```
1665
+ */
1666
+ declare class PopulateRefBuilder<TDoc> {
1667
+ /**
1668
+ * Declare a projection to apply when fetching the referenced documents.
1669
+ *
1670
+ * Supported: inclusion (`{ name: 1 }`), exclusion (`{ email: 0 }`), or
1671
+ * `_id` suppression (`{ name: 1, _id: 0 }`).
1672
+ *
1673
+ * @param projection - MongoDB-style inclusion or exclusion projection.
1674
+ * @returns A config object carrying the projection type for compile-time narrowing.
1675
+ *
1676
+ * @example
1677
+ * ```ts
1678
+ * (b) => b.project({ name: 1, email: 1 })
1679
+ * (b) => b.project({ password: 0 })
1680
+ * ```
1681
+ */
1682
+ project<P extends TypedProjection<TDoc>>(projection: P): PopulateProjectionConfig<P>;
1683
+ }
1684
+
1685
+ /**
1686
+ * Resolve the populated type for a ref field, preserving wrappers.
1687
+ *
1688
+ * Transforms the field type from ID to document based on the Zod wrapper:
1689
+ * - `ObjectId` → `InferDocument<Target>`
1690
+ * - `ObjectId[]` → `InferDocument<Target>[]`
1691
+ * - `ObjectId | undefined` → `InferDocument<Target> | undefined`
1692
+ * - `ObjectId | null` → `InferDocument<Target> | null`
1693
+ *
1694
+ * @example
1695
+ * ```ts
1696
+ * type Author = PopulateField<typeof Posts, 'authorId'>
1697
+ * // ^? { _id: ObjectId; name: string; email: string; companyId: ObjectId }
1698
+ *
1699
+ * type Cats = PopulateField<typeof Posts, 'categoryIds'>
1700
+ * // ^? { _id: ObjectId; title: string }[]
1701
+ * ```
1702
+ */
1703
+ type PopulateField<TDef extends AnyCollection, K extends RefFields<TDef>> = TDef['shape'][K] extends z.ZodArray<infer _E> ? InferDocument<ExtractRefCollection<TDef, K>>[] : TDef['shape'][K] extends z.ZodOptional<infer _U> ? InferDocument<ExtractRefCollection<TDef, K>> | undefined : TDef['shape'][K] extends z.ZodNullable<infer _U> ? InferDocument<ExtractRefCollection<TDef, K>> | null : InferDocument<ExtractRefCollection<TDef, K>>;
1704
+ /**
1705
+ * Document type with specified ref fields replaced by their populated types.
1706
+ *
1707
+ * @example
1708
+ * ```ts
1709
+ * type PostWithAuthor = Populated<typeof Posts, 'authorId'>
1710
+ * // ^? { _id: ObjectId; title: string; authorId: User; categoryIds: ObjectId[]; ... }
1711
+ *
1712
+ * type PostFull = Populated<typeof Posts, 'authorId' | 'categoryIds'>
1713
+ * // ^? { ...; authorId: User; categoryIds: Category[]; ... }
1714
+ * ```
1715
+ */
1716
+ type Populated<TDef extends AnyCollection, K extends RefFields<TDef>> = Prettify<Omit<InferDocument<TDef>, K> & {
1717
+ [P in K]: PopulateField<TDef, P>;
1718
+ }>;
1719
+ /**
1720
+ * Apply a single populate step to the output type.
1721
+ *
1722
+ * Without rename: replaces the field type in-place.
1723
+ * With rename: removes the old key and adds the new key with the populated type.
1724
+ *
1725
+ * @example
1726
+ * ```ts
1727
+ * // No rename: authorId stays, type changes to User
1728
+ * type A = ApplyPopulate<PostDoc, typeof Posts, 'authorId'>
1729
+ *
1730
+ * // Rename: authorId removed, author added with User type
1731
+ * type B = ApplyPopulate<PostDoc, typeof Posts, 'authorId', 'author'>
1732
+ * ```
1733
+ */
1734
+ type ApplyPopulate<T, TDef extends AnyCollection, K extends RefFields<TDef>, TAs extends string = K> = Prettify<Omit<T, K> & {
1735
+ [P in TAs]: PopulateField<TDef, K>;
1736
+ }>;
1737
+ /**
1738
+ * Resolve the projected populated type for a ref field, preserving wrappers.
1739
+ *
1740
+ * Like {@link PopulateField} but applies `ProjectionResult<T, P>` to narrow
1741
+ * the document type to only the projected fields.
1742
+ *
1743
+ * - `ObjectId` → `ProjectionResult<InferDocument<Target>, P>`
1744
+ * - `ObjectId[]` → `ProjectionResult<InferDocument<Target>, P>[]`
1745
+ * - `ObjectId | undefined` → `ProjectionResult<InferDocument<Target>, P> | undefined`
1746
+ * - `ObjectId | null` → `ProjectionResult<InferDocument<Target>, P> | null`
1747
+ *
1748
+ * @example
1749
+ * ```ts
1750
+ * type Author = PopulateFieldProjected<typeof Posts, 'authorId', { name: 1; email: 1 }>
1751
+ * // ^? { _id: ObjectId; name: string; email: string }
1752
+ * ```
1753
+ */
1754
+ type PopulateFieldProjected<TDef extends AnyCollection, K extends RefFields<TDef>, P> = TDef['shape'][K] extends z.ZodArray<infer _E> ? ProjectionResult<InferDocument<ExtractRefCollection<TDef, K>>, P>[] : TDef['shape'][K] extends z.ZodOptional<infer _U> ? ProjectionResult<InferDocument<ExtractRefCollection<TDef, K>>, P> | undefined : TDef['shape'][K] extends z.ZodNullable<infer _U> ? ProjectionResult<InferDocument<ExtractRefCollection<TDef, K>>, P> | null : ProjectionResult<InferDocument<ExtractRefCollection<TDef, K>>, P>;
1755
+ /**
1756
+ * Apply a projected populate step to the output type.
1757
+ *
1758
+ * Like {@link ApplyPopulate} but uses {@link PopulateFieldProjected} to narrow
1759
+ * the result to only the projected fields. Use `TAs` to rename the output field.
1760
+ *
1761
+ * @example
1762
+ * ```ts
1763
+ * type Result = ApplyPopulateProjected<PostDoc, typeof Posts, 'authorId', { name: 1; email: 1 }>
1764
+ * // ^? { ...; authorId: { _id: ObjectId; name: string; email: string } }
1765
+ * ```
1766
+ */
1767
+ type ApplyPopulateProjected<T, TDef extends AnyCollection, K extends RefFields<TDef>, P, TAs extends string = K> = Prettify<Omit<T, K> & {
1768
+ [F in TAs]: PopulateFieldProjected<TDef, K, P>;
1769
+ }>;
1770
+ /**
1771
+ * Apply a populate at an arbitrary nesting depth.
1772
+ *
1773
+ * Splits the dot-path and recursively navigates into the output type.
1774
+ * If a parent field is an array, the element type is transformed.
1775
+ *
1776
+ * @example
1777
+ * ```ts
1778
+ * // Nested: author.companyId → author.company
1779
+ * type Result = DeepPopulate<PostWithAuthor, 'author.companyId', 'companyId', 'company', Company>
1780
+ * ```
1781
+ */
1782
+ type DeepPopulate<T, Path extends string, LeafField extends string, TAs extends string, PopType> = Path extends `${infer Parent}.${infer Rest}` ? Parent extends keyof T ? T[Parent] extends (infer E)[] ? Prettify<Omit<T, Parent> & {
1783
+ [P in Parent]: DeepPopulate<E, Rest, LeafField, TAs, PopType>[];
1784
+ }> : Prettify<Omit<T, Parent> & {
1785
+ [P in Parent]: DeepPopulate<T[Parent], Rest, LeafField, TAs, PopType>;
1786
+ }> : never : Prettify<Omit<T, LeafField> & {
1787
+ [P in TAs]: PopType;
1788
+ }>;
1789
+ /**
1790
+ * Valid nested populate paths given the current TPopMap.
1791
+ *
1792
+ * Produces a union of `parentPath.refField` strings where `parentPath`
1793
+ * is a key in TPopMap and `refField` is a ref-bearing field in that
1794
+ * parent's collection definition.
1795
+ *
1796
+ * @example
1797
+ * ```ts
1798
+ * // If TPopMap = { author: typeof Users }, produces:
1799
+ * // 'author.companyId' | 'author.<other ref fields>'
1800
+ * type Paths = NestedRefPath<{ author: typeof Users }>
1801
+ * ```
1802
+ */
1803
+ type NestedRefPath<TPopMap extends Record<string, AnyCollection>> = {
1804
+ [Path in keyof TPopMap & string]: `${Path}.${RefFields<TPopMap[Path]>}`;
1805
+ }[keyof TPopMap & string];
1806
+ /**
1807
+ * Runtime data for a single populate step.
1808
+ *
1809
+ * Built by each `.populate()` call and consumed by `executePopulate()`.
1810
+ */
1811
+ type PopulateStep = {
1812
+ /** The original field path from the root collection. */
1813
+ readonly originalPath: string;
1814
+ /** The leaf field name in the target shape. */
1815
+ readonly leafField: string;
1816
+ /** The output field name (renamed or original). */
1817
+ readonly as: string;
1818
+ /** Parent path in the output document for nesting, or undefined for top-level. */
1819
+ readonly parentOutputPath: string | undefined;
1820
+ /** The target collection definition resolved from RefMarker metadata. */
1821
+ readonly targetCollection: AnyCollection;
1822
+ /** Whether the field is an array ref. */
1823
+ readonly isArray: boolean;
1824
+ /** MongoDB projection spec to pass to the `$in` query. Omit to fetch the full document. */
1825
+ readonly projection?: Record<string, 0 | 1 | boolean>;
1826
+ };
1827
+
1828
+ /**
1829
+ * Extract the parent segment from a dot-separated path.
1830
+ *
1831
+ * @example
1832
+ * ```ts
1833
+ * type P = ExtractParent<'author.companyId'> // 'author'
1834
+ * ```
1835
+ */
1836
+ type ExtractParent$1<T extends string> = T extends `${infer P}.${string}` ? P : never;
1837
+ /**
1838
+ * Extract the leaf segment from a dot-separated path.
1839
+ *
1840
+ * @example
1841
+ * ```ts
1842
+ * type L = ExtractLeaf<'author.companyId'> // 'companyId'
1843
+ * ```
1844
+ */
1845
+ type ExtractLeaf$1<T extends string> = T extends `${string}.${infer L}` ? L : never;
1846
+ /**
1847
+ * Cursor with chained populate steps, wrapping a {@link TypedFindCursor}.
1848
+ *
1849
+ * Created by calling `.populate()` on a `TypedFindCursor`. Exposes only
1850
+ * `.populate()` (for additional fields) and terminal methods (`toArray`,
1851
+ * async iteration). Cursor modifiers (`sort`, `skip`, `limit`) are not
1852
+ * available -- they must be called before `.populate()`.
1853
+ *
1854
+ * @typeParam TDef - The root collection definition type.
1855
+ * @typeParam TOutput - The current output document type after populate transforms.
1856
+ * @typeParam TPopMap - Map of populated alias names to their collection definitions,
1857
+ * used to resolve nested populate paths.
1858
+ *
1859
+ * @example
1860
+ * ```ts
1861
+ * const posts = await db.use(Posts)
1862
+ * .find({ published: true })
1863
+ * .sort({ createdAt: -1 })
1864
+ * .limit(10)
1865
+ * .populate('authorId', 'author')
1866
+ * .populate('categoryIds')
1867
+ * .toArray()
1868
+ * ```
1869
+ */
1870
+ declare class PopulateCursor<TDef extends AnyCollection, TOutput, TPopMap extends Record<string, AnyCollection> = Record<string, never>> {
1871
+ private readonly cursor;
1872
+ private readonly definition;
1873
+ private readonly steps;
1874
+ private readonly nativeCollection;
1875
+ /** @internal */
1876
+ constructor(cursor: TypedFindCursor<TDef>, definition: TDef, steps: readonly PopulateStep[], nativeCollection: Collection<InferDocument<TDef>>);
1877
+ /**
1878
+ * Populate a top-level ref field, keeping the original field name.
1879
+ *
1880
+ * @param field - A ref-bearing field name on the root collection.
1881
+ * @returns A new cursor with the field type replaced by the referenced document.
1882
+ *
1883
+ * @example
1884
+ * ```ts
1885
+ * const posts = await db.use(Posts)
1886
+ * .find({})
1887
+ * .populate('authorId')
1888
+ * .toArray()
1889
+ * // posts[0].authorId is now a User document instead of ObjectId
1890
+ * ```
1891
+ */
1892
+ populate<K extends RefFields<TDef>>(field: K): PopulateCursor<TDef, ApplyPopulate<TOutput, TDef, K>, TPopMap & {
1893
+ [P in K]: ExtractRefCollection<TDef, K>;
1894
+ }>;
1895
+ /**
1896
+ * Populate a top-level ref field with a projection, keeping the original field name.
1897
+ *
1898
+ * @param field - A ref-bearing field name on the root collection.
1899
+ * @param configure - Callback receiving a builder; call `.project()` to set the projection.
1900
+ * @returns A new cursor with the field type narrowed to only the projected fields.
1901
+ *
1902
+ * @example
1903
+ * ```ts
1904
+ * const posts = await db.use(Posts)
1905
+ * .find({})
1906
+ * .populate('authorId', (b) => b.project({ name: 1, email: 1 }))
1907
+ * .toArray()
1908
+ * // posts[0].authorId is { _id: ObjectId; name: string; email: string }
1909
+ * ```
1910
+ */
1911
+ populate<K extends RefFields<TDef>, P>(field: K, configure: (b: PopulateRefBuilder<InferDocument<ExtractRefCollection<TDef, K>>>) => PopulateProjectionConfig<P>): PopulateCursor<TDef, ApplyPopulateProjected<TOutput, TDef, K, P>, TPopMap & {
1912
+ [F in K]: ExtractRefCollection<TDef, K>;
1913
+ }>;
1914
+ /**
1915
+ * Populate a top-level ref field and rename it in the output.
1916
+ *
1917
+ * @param field - A ref-bearing field name on the root collection.
1918
+ * @param as - The new field name in the output document.
1919
+ * @returns A new cursor with the old field removed and the new field added.
1920
+ *
1921
+ * @example
1922
+ * ```ts
1923
+ * const posts = await db.use(Posts)
1924
+ * .find({})
1925
+ * .populate('authorId', 'author')
1926
+ * .toArray()
1927
+ * // posts[0].author is a User document; posts[0].authorId no longer exists
1928
+ * ```
1929
+ */
1930
+ populate<K extends RefFields<TDef>, TAs extends string>(field: K, as: TAs): PopulateCursor<TDef, ApplyPopulate<TOutput, TDef, K, TAs>, TPopMap & {
1931
+ [P in TAs]: ExtractRefCollection<TDef, K>;
1932
+ }>;
1933
+ /**
1934
+ * Populate a nested ref field, keeping the leaf field name.
1935
+ *
1936
+ * The parent path must have been populated in a previous `.populate()` call.
1937
+ *
1938
+ * @param path - A dot-separated path like `'author.companyId'`.
1939
+ * @returns A new cursor with the nested field type replaced.
1940
+ *
1941
+ * @example
1942
+ * ```ts
1943
+ * const posts = await db.use(Posts)
1944
+ * .find({})
1945
+ * .populate('authorId', 'author')
1946
+ * .populate('author.companyId')
1947
+ * .toArray()
1948
+ * // posts[0].author.companyId is now a Company document
1949
+ * ```
1950
+ */
1951
+ populate<TPath extends NestedRefPath<TPopMap>>(path: TPath): PopulateCursor<TDef, DeepPopulate<TOutput, `${ExtractParent$1<TPath>}.${ExtractLeaf$1<TPath>}`, ExtractLeaf$1<TPath>, ExtractLeaf$1<TPath>, PopulateField<TPopMap[ExtractParent$1<TPath>], ExtractLeaf$1<TPath> & RefFields<TPopMap[ExtractParent$1<TPath>]>>>, TPopMap & {
1952
+ [P in `${ExtractParent$1<TPath>}.${ExtractLeaf$1<TPath>}`]: ExtractRefCollection<TPopMap[ExtractParent$1<TPath>], ExtractLeaf$1<TPath> & RefFields<TPopMap[ExtractParent$1<TPath>]>>;
1953
+ }>;
1954
+ /**
1955
+ * Populate a nested ref field and rename the leaf in the output.
1956
+ *
1957
+ * The parent path must have been populated in a previous `.populate()` call.
1958
+ *
1959
+ * @param path - A dot-separated path like `'author.companyId'`.
1960
+ * @param as - The new name for the leaf field in the output.
1961
+ * @returns A new cursor with the nested field renamed and typed.
1962
+ *
1963
+ * @example
1964
+ * ```ts
1965
+ * const posts = await db.use(Posts)
1966
+ * .find({})
1967
+ * .populate('authorId', 'author')
1968
+ * .populate('author.companyId', 'company')
1969
+ * .toArray()
1970
+ * // posts[0].author.company is a Company document
1971
+ * ```
1972
+ */
1973
+ populate<TPath extends NestedRefPath<TPopMap>, TAs extends string>(path: TPath, as: TAs): PopulateCursor<TDef, DeepPopulate<TOutput, `${ExtractParent$1<TPath>}.${TAs}`, ExtractLeaf$1<TPath>, TAs, PopulateField<TPopMap[ExtractParent$1<TPath>], ExtractLeaf$1<TPath> & RefFields<TPopMap[ExtractParent$1<TPath>]>>>, TPopMap & {
1974
+ [P in `${ExtractParent$1<TPath>}.${TAs}`]: ExtractRefCollection<TPopMap[ExtractParent$1<TPath>], ExtractLeaf$1<TPath> & RefFields<TPopMap[ExtractParent$1<TPath>]>>;
1975
+ }>;
1976
+ /**
1977
+ * Execute the query and return all matching documents as a populated array.
1978
+ *
1979
+ * Fetches all documents from the underlying cursor, then applies populate
1980
+ * steps in order using batch `$in` queries (no N+1 problem).
1981
+ *
1982
+ * @returns Array of populated documents.
1983
+ *
1984
+ * @example
1985
+ * ```ts
1986
+ * const posts = await db.use(Posts)
1987
+ * .find({})
1988
+ * .populate('authorId', 'author')
1989
+ * .toArray()
1990
+ * ```
1991
+ */
1992
+ toArray(): Promise<TOutput[]>;
1993
+ /**
1994
+ * Async iterator for streaming populated documents.
1995
+ *
1996
+ * Fetches all documents first (populate requires the full batch for
1997
+ * efficient `$in` queries), then yields results one at a time.
1998
+ *
1999
+ * @yields Populated documents one at a time.
2000
+ *
2001
+ * @example
2002
+ * ```ts
2003
+ * for await (const post of db.use(Posts).find({}).populate('authorId', 'author')) {
2004
+ * console.log(post.author.name)
2005
+ * }
2006
+ * ```
2007
+ */
2008
+ [Symbol.asyncIterator](): AsyncGenerator<TOutput>;
2009
+ }
2010
+ /**
2011
+ * Create a new {@link PopulateCursor} from a {@link TypedFindCursor}.
2012
+ *
2013
+ * This factory function exists to break the circular import between
2014
+ * `query/cursor.ts` and `populate/cursor.ts`. The `TypedFindCursor.populate()`
2015
+ * method uses a lazy `require()` to call this function at runtime.
2016
+ *
2017
+ * @param cursor - The typed find cursor to wrap.
2018
+ * @param definition - The collection definition.
2019
+ * @param steps - Initial populate steps (typically empty).
2020
+ * @returns A new PopulateCursor instance.
2021
+ *
2022
+ * @example
2023
+ * ```ts
2024
+ * const popCursor = createPopulateCursor(typedCursor, Posts, [])
2025
+ * ```
2026
+ */
2027
+ declare function createPopulateCursor<TDef extends AnyCollection>(cursor: TypedFindCursor<TDef>, definition: TDef, steps: readonly PopulateStep[]): PopulateCursor<TDef, InferDocument<TDef>>;
2028
+
1454
2029
  /**
1455
2030
  * Type-safe sort specification for a document type.
1456
2031
  *
@@ -1489,21 +2064,25 @@ declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends st
1489
2064
  /** @internal */
1490
2065
  private cursor;
1491
2066
  /** @internal */
2067
+ readonly definition: TDef;
2068
+ /** @internal */
1492
2069
  private schema;
1493
2070
  /** @internal */
1494
2071
  private collectionName;
1495
2072
  /** @internal */
1496
2073
  private mode;
1497
2074
  /** @internal */
1498
- private readonly nativeCollection;
2075
+ readonly nativeCollection: Collection<InferDocument<TDef>>;
1499
2076
  /** @internal */
1500
2077
  private readonly filter;
1501
2078
  /** @internal */
2079
+ private readonly session;
2080
+ /** @internal */
1502
2081
  private sortSpec;
1503
2082
  /** @internal */
1504
2083
  private projectedSchema;
1505
2084
  /** @internal */
1506
- constructor(cursor: FindCursor<InferDocument<TDef>>, definition: TDef, mode: ValidationMode | false, nativeCollection: Collection<InferDocument<TDef>>, filter: any);
2085
+ constructor(cursor: FindCursor<InferDocument<TDef>>, definition: TDef, mode: ValidationMode | false, nativeCollection: Collection<InferDocument<TDef>>, filter: any, session?: ClientSession);
1507
2086
  /**
1508
2087
  * Set the sort order for the query.
1509
2088
  *
@@ -1590,6 +2169,73 @@ declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends st
1590
2169
  * ```
1591
2170
  */
1592
2171
  project<P extends TypedProjection<InferDocument<TDef>>>(spec: P): TypedFindCursor<TDef, TIndexNames, Prettify<ProjectionResult<InferDocument<TDef>, P>>>;
2172
+ /**
2173
+ * Populate a top-level ref field, keeping the original field name.
2174
+ *
2175
+ * Transitions to a {@link PopulateCursor} that only exposes `.populate()`
2176
+ * and terminal methods. Cursor modifiers (`sort`, `skip`, `limit`) must
2177
+ * be called before `.populate()`.
2178
+ *
2179
+ * @param field - A ref-bearing field name on the root collection.
2180
+ * @returns A populate cursor with the field type replaced by the referenced document.
2181
+ *
2182
+ * @example
2183
+ * ```ts
2184
+ * const posts = await db.use(Posts)
2185
+ * .find({})
2186
+ * .populate('authorId')
2187
+ * .toArray()
2188
+ * ```
2189
+ */
2190
+ populate<K extends RefFields<TDef>>(field: K): PopulateCursor<TDef, ApplyPopulate<TOutput, TDef, K>, {
2191
+ [P in K]: ExtractRefCollection<TDef, K>;
2192
+ }>;
2193
+ /**
2194
+ * Populate a top-level ref field with a projection, keeping the original field name.
2195
+ *
2196
+ * Transitions to a {@link PopulateCursor}. Cursor modifiers (`sort`, `skip`, `limit`)
2197
+ * must be called before `.populate()`.
2198
+ *
2199
+ * @param field - A ref-bearing field name on the root collection.
2200
+ * @param configure - Callback receiving a builder; call `.project()` to set the projection.
2201
+ * @returns A populate cursor with the field type narrowed to only the projected fields.
2202
+ *
2203
+ * @example
2204
+ * ```ts
2205
+ * const posts = await db.use(Posts)
2206
+ * .find({})
2207
+ * .populate('authorId', (b) => b.project({ name: 1, email: 1 }))
2208
+ * .toArray()
2209
+ * // posts[0].authorId is { _id: ObjectId; name: string; email: string }
2210
+ * ```
2211
+ */
2212
+ populate<K extends RefFields<TDef>, P>(field: K, configure: (b: PopulateRefBuilder<InferDocument<ExtractRefCollection<TDef, K>>>) => PopulateProjectionConfig<P>): PopulateCursor<TDef, ApplyPopulateProjected<TOutput, TDef, K, P>, {
2213
+ [F in K]: ExtractRefCollection<TDef, K>;
2214
+ }>;
2215
+ /**
2216
+ * Populate a top-level ref field and rename it in the output.
2217
+ *
2218
+ * Transitions to a {@link PopulateCursor} that only exposes `.populate()`
2219
+ * and terminal methods. Cursor modifiers (`sort`, `skip`, `limit`) must
2220
+ * be called before `.populate()`.
2221
+ *
2222
+ * @param field - A ref-bearing field name on the root collection.
2223
+ * @param as - The new field name in the output document.
2224
+ * @returns A populate cursor with the old field removed and the new field added.
2225
+ *
2226
+ * @example
2227
+ * ```ts
2228
+ * const posts = await db.use(Posts)
2229
+ * .find({})
2230
+ * .sort({ createdAt: -1 })
2231
+ * .limit(10)
2232
+ * .populate('authorId', 'author')
2233
+ * .toArray()
2234
+ * ```
2235
+ */
2236
+ populate<K extends RefFields<TDef>, TAs extends string>(field: K, as: TAs): PopulateCursor<TDef, ApplyPopulate<TOutput, TDef, K, TAs>, {
2237
+ [P in TAs]: ExtractRefCollection<TDef, K>;
2238
+ }>;
1593
2239
  /**
1594
2240
  * Execute the query with offset-based pagination, returning a page of documents
1595
2241
  * with total count and navigation metadata.
@@ -1858,16 +2504,6 @@ declare function find<TDef extends AnyCollection>(handle: CollectionHandle<TDef>
1858
2504
  */
1859
2505
  declare function find<TDef extends AnyCollection, P extends TypedProjection<InferDocument<TDef>>>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options: FindProjectionOptions<InferDocument<TDef>, P>): TypedFindCursor<TDef, IndexNames<TDef>, Prettify<ProjectionResult<InferDocument<TDef>, P>>>;
1860
2506
 
1861
- /**
1862
- * Extracts the element type from an array type.
1863
- *
1864
- * @example
1865
- * ```ts
1866
- * type E = ArrayElement<string[]> // string
1867
- * type N = ArrayElement<number[]> // number
1868
- * ```
1869
- */
1870
- type ArrayElement<T> = T extends ReadonlyArray<infer E> ? E : never;
1871
2507
  /**
1872
2508
  * Fields valid for `$set`, `$setOnInsert`, `$min`, and `$max` operators.
1873
2509
  *
@@ -1882,7 +2518,7 @@ type ArrayElement<T> = T extends ReadonlyArray<infer E> ? E : never;
1882
2518
  type SetFields<T> = {
1883
2519
  [K in keyof T]?: T[K];
1884
2520
  } & {
1885
- [P in _DotPaths<T>]?: _DotPathType<T, P>;
2521
+ [P in DotSubPaths<T>]?: DotPathValue<T, P>;
1886
2522
  };
1887
2523
  /**
1888
2524
  * Fields valid for the `$inc` operator — only number-typed fields.
@@ -1897,7 +2533,7 @@ type SetFields<T> = {
1897
2533
  type IncFields<T> = {
1898
2534
  [K in keyof T as NonNullable<T[K]> extends number ? K : never]?: number;
1899
2535
  } & {
1900
- [P in _DotPaths<T> as _DotPathType<T, P> extends number ? P : never]?: number;
2536
+ [P in DotSubPaths<T> as DotPathValue<T, P> extends number ? P : never]?: number;
1901
2537
  };
1902
2538
  /**
1903
2539
  * Modifiers for the `$push` operator.
@@ -2242,6 +2878,257 @@ type SyncIndexesResult = {
2242
2878
  stale: StaleIndex[];
2243
2879
  };
2244
2880
 
2881
+ /**
2882
+ * Extract the parent segment from a dot-separated path.
2883
+ *
2884
+ * @example
2885
+ * ```ts
2886
+ * type P = ExtractParent<'author.companyId'> // 'author'
2887
+ * ```
2888
+ */
2889
+ type ExtractParent<T extends string> = T extends `${infer P}.${string}` ? P : never;
2890
+ /**
2891
+ * Extract the leaf segment from a dot-separated path.
2892
+ *
2893
+ * @example
2894
+ * ```ts
2895
+ * type L = ExtractLeaf<'author.companyId'> // 'companyId'
2896
+ * ```
2897
+ */
2898
+ type ExtractLeaf<T extends string> = T extends `${string}.${infer L}` ? L : never;
2899
+ /**
2900
+ * Fluent populate builder for findOne queries.
2901
+ *
2902
+ * Implements `PromiseLike` so it can be awaited directly without `.populate()`.
2903
+ * Each `.populate()` call returns a new builder with updated generics.
2904
+ *
2905
+ * @example
2906
+ * ```ts
2907
+ * // Without populate (backward compatible)
2908
+ * const post = await posts.findOne({ title: 'Hello' })
2909
+ *
2910
+ * // With populate
2911
+ * const post = await posts.findOne({ title: 'Hello' })
2912
+ * .populate('authorId', 'author')
2913
+ * .populate('author.companyId', 'company')
2914
+ * ```
2915
+ */
2916
+ declare class PopulateOneQuery<TDef extends AnyCollection, TOutput, TPopMap extends Record<string, AnyCollection> = Record<string, never>> implements PromiseLike<TOutput | null> {
2917
+ private readonly handle;
2918
+ private readonly filter;
2919
+ private readonly options;
2920
+ private readonly steps;
2921
+ constructor(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options: FindOneOptions | undefined, steps?: readonly PopulateStep[]);
2922
+ /**
2923
+ * Populate a top-level ref field, keeping the original field name.
2924
+ *
2925
+ * @param field - A ref-bearing field name on the root collection.
2926
+ * @returns A new builder with the field type replaced by the referenced document.
2927
+ *
2928
+ * @example
2929
+ * ```ts
2930
+ * const post = await posts.findOne({ title: 'Hello' })
2931
+ * .populate('authorId')
2932
+ * // post.authorId is now a User document instead of ObjectId
2933
+ * ```
2934
+ */
2935
+ populate<K extends RefFields<TDef>>(field: K): PopulateOneQuery<TDef, ApplyPopulate<TOutput, TDef, K>, TPopMap & {
2936
+ [P in K]: ExtractRefCollection<TDef, K>;
2937
+ }>;
2938
+ /**
2939
+ * Populate a top-level ref field with a projection, keeping the original field name.
2940
+ *
2941
+ * @param field - A ref-bearing field name on the root collection.
2942
+ * @param configure - Callback receiving a builder; call `.project()` to set the projection.
2943
+ * @returns A new builder with the field type narrowed to only the projected fields.
2944
+ *
2945
+ * @example
2946
+ * ```ts
2947
+ * const post = await posts.findOne({ title: 'Hello' })
2948
+ * .populate('authorId', (b) => b.project({ name: 1, email: 1 }))
2949
+ * // post.authorId is { _id: ObjectId; name: string; email: string }
2950
+ * // post.authorId.age → TS error
2951
+ * ```
2952
+ */
2953
+ populate<K extends RefFields<TDef>, P>(field: K, configure: (b: PopulateRefBuilder<InferDocument<ExtractRefCollection<TDef, K>>>) => PopulateProjectionConfig<P>): PopulateOneQuery<TDef, ApplyPopulateProjected<TOutput, TDef, K, P>, TPopMap & {
2954
+ [F in K]: ExtractRefCollection<TDef, K>;
2955
+ }>;
2956
+ /**
2957
+ * Populate a top-level ref field and rename it in the output.
2958
+ *
2959
+ * @param field - A ref-bearing field name on the root collection.
2960
+ * @param as - The new field name in the output document.
2961
+ * @returns A new builder with the old field removed and the new field added.
2962
+ *
2963
+ * @example
2964
+ * ```ts
2965
+ * const post = await posts.findOne({ title: 'Hello' })
2966
+ * .populate('authorId', 'author')
2967
+ * // post.author is a User document; post.authorId no longer exists
2968
+ * ```
2969
+ */
2970
+ populate<K extends RefFields<TDef>, TAs extends string>(field: K, as: TAs): PopulateOneQuery<TDef, ApplyPopulate<TOutput, TDef, K, TAs>, TPopMap & {
2971
+ [P in TAs]: ExtractRefCollection<TDef, K>;
2972
+ }>;
2973
+ /**
2974
+ * Populate a nested ref field, keeping the leaf field name.
2975
+ *
2976
+ * The parent path must have been populated in a previous `.populate()` call.
2977
+ *
2978
+ * @param path - A dot-separated path like `'author.companyId'`.
2979
+ * @returns A new builder with the nested field type replaced.
2980
+ *
2981
+ * @example
2982
+ * ```ts
2983
+ * const post = await posts.findOne({ title: 'Hello' })
2984
+ * .populate('authorId', 'author')
2985
+ * .populate('author.companyId')
2986
+ * // post.author.companyId is now a Company document
2987
+ * ```
2988
+ */
2989
+ populate<TPath extends NestedRefPath<TPopMap>>(path: TPath): PopulateOneQuery<TDef, DeepPopulate<TOutput, `${ExtractParent<TPath>}.${ExtractLeaf<TPath>}`, ExtractLeaf<TPath>, ExtractLeaf<TPath>, PopulateField<TPopMap[ExtractParent<TPath>], ExtractLeaf<TPath> & RefFields<TPopMap[ExtractParent<TPath>]>>>, TPopMap & {
2990
+ [P in `${ExtractParent<TPath>}.${ExtractLeaf<TPath>}`]: ExtractRefCollection<TPopMap[ExtractParent<TPath>], ExtractLeaf<TPath> & RefFields<TPopMap[ExtractParent<TPath>]>>;
2991
+ }>;
2992
+ /**
2993
+ * Populate a nested ref field and rename the leaf in the output.
2994
+ *
2995
+ * The parent path must have been populated in a previous `.populate()` call.
2996
+ *
2997
+ * @param path - A dot-separated path like `'author.companyId'`.
2998
+ * @param as - The new name for the leaf field in the output.
2999
+ * @returns A new builder with the nested field renamed and typed.
3000
+ *
3001
+ * @example
3002
+ * ```ts
3003
+ * const post = await posts.findOne({ title: 'Hello' })
3004
+ * .populate('authorId', 'author')
3005
+ * .populate('author.companyId', 'company')
3006
+ * // post.author.company is a Company document
3007
+ * ```
3008
+ */
3009
+ populate<TPath extends NestedRefPath<TPopMap>, TAs extends string>(path: TPath, as: TAs): PopulateOneQuery<TDef, DeepPopulate<TOutput, `${ExtractParent<TPath>}.${TAs}`, ExtractLeaf<TPath>, TAs, PopulateField<TPopMap[ExtractParent<TPath>], ExtractLeaf<TPath> & RefFields<TPopMap[ExtractParent<TPath>]>>>, TPopMap & {
3010
+ [P in `${ExtractParent<TPath>}.${TAs}`]: ExtractRefCollection<TPopMap[ExtractParent<TPath>], ExtractLeaf<TPath> & RefFields<TPopMap[ExtractParent<TPath>]>>;
3011
+ }>;
3012
+ /**
3013
+ * Attach fulfillment and rejection handlers to the query promise.
3014
+ *
3015
+ * Executes the base findOne query and applies populate steps if any.
3016
+ * Returns `null` when no document matches the filter.
3017
+ */
3018
+ then<TResult1 = TOutput | null, TResult2 = never>(onfulfilled?: ((value: TOutput | null) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
3019
+ private execute;
3020
+ }
3021
+ /**
3022
+ * Fluent populate builder for findOneOrThrow queries.
3023
+ *
3024
+ * Identical to {@link PopulateOneQuery} but resolves to `TOutput` (never null).
3025
+ * Throws {@link ZodmonNotFoundError} when no document matches the filter.
3026
+ *
3027
+ * @example
3028
+ * ```ts
3029
+ * const post = await posts.findOneOrThrow({ title: 'Hello' })
3030
+ * .populate('authorId', 'author')
3031
+ * // Guaranteed non-null; throws if not found
3032
+ * ```
3033
+ */
3034
+ declare class PopulateOneOrThrowQuery<TDef extends AnyCollection, TOutput, TPopMap extends Record<string, AnyCollection> = Record<string, never>> implements PromiseLike<TOutput> {
3035
+ private readonly handle;
3036
+ private readonly filter;
3037
+ private readonly options;
3038
+ private readonly steps;
3039
+ constructor(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options: FindOneOptions | undefined, steps?: readonly PopulateStep[]);
3040
+ /**
3041
+ * Populate a top-level ref field, keeping the original field name.
3042
+ *
3043
+ * @param field - A ref-bearing field name on the root collection.
3044
+ * @returns A new builder with the field type replaced by the referenced document.
3045
+ *
3046
+ * @example
3047
+ * ```ts
3048
+ * const post = await posts.findOneOrThrow({ title: 'Hello' })
3049
+ * .populate('authorId')
3050
+ * ```
3051
+ */
3052
+ populate<K extends RefFields<TDef>>(field: K): PopulateOneOrThrowQuery<TDef, ApplyPopulate<TOutput, TDef, K>, TPopMap & {
3053
+ [P in K]: ExtractRefCollection<TDef, K>;
3054
+ }>;
3055
+ /**
3056
+ * Populate a top-level ref field with a projection, keeping the original field name.
3057
+ *
3058
+ * @param field - A ref-bearing field name on the root collection.
3059
+ * @param configure - Callback receiving a builder; call `.project()` to set the projection.
3060
+ * @returns A new builder with the field type narrowed to only the projected fields.
3061
+ *
3062
+ * @example
3063
+ * ```ts
3064
+ * const post = await posts.findOneOrThrow({ title: 'Hello' })
3065
+ * .populate('authorId', (b) => b.project({ name: 1, email: 1 }))
3066
+ * // post.authorId is { _id: ObjectId; name: string; email: string }
3067
+ * // post.authorId.age → TS error
3068
+ * ```
3069
+ */
3070
+ populate<K extends RefFields<TDef>, P>(field: K, configure: (b: PopulateRefBuilder<InferDocument<ExtractRefCollection<TDef, K>>>) => PopulateProjectionConfig<P>): PopulateOneOrThrowQuery<TDef, ApplyPopulateProjected<TOutput, TDef, K, P>, TPopMap & {
3071
+ [F in K]: ExtractRefCollection<TDef, K>;
3072
+ }>;
3073
+ /**
3074
+ * Populate a top-level ref field and rename it in the output.
3075
+ *
3076
+ * @param field - A ref-bearing field name on the root collection.
3077
+ * @param as - The new field name in the output document.
3078
+ * @returns A new builder with the old field removed and the new field added.
3079
+ *
3080
+ * @example
3081
+ * ```ts
3082
+ * const post = await posts.findOneOrThrow({ title: 'Hello' })
3083
+ * .populate('authorId', 'author')
3084
+ * ```
3085
+ */
3086
+ populate<K extends RefFields<TDef>, TAs extends string>(field: K, as: TAs): PopulateOneOrThrowQuery<TDef, ApplyPopulate<TOutput, TDef, K, TAs>, TPopMap & {
3087
+ [P in TAs]: ExtractRefCollection<TDef, K>;
3088
+ }>;
3089
+ /**
3090
+ * Populate a nested ref field, keeping the leaf field name.
3091
+ *
3092
+ * @param path - A dot-separated path like `'author.companyId'`.
3093
+ * @returns A new builder with the nested field type replaced.
3094
+ *
3095
+ * @example
3096
+ * ```ts
3097
+ * const post = await posts.findOneOrThrow({ title: 'Hello' })
3098
+ * .populate('authorId', 'author')
3099
+ * .populate('author.companyId')
3100
+ * ```
3101
+ */
3102
+ populate<TPath extends NestedRefPath<TPopMap>>(path: TPath): PopulateOneOrThrowQuery<TDef, DeepPopulate<TOutput, `${ExtractParent<TPath>}.${ExtractLeaf<TPath>}`, ExtractLeaf<TPath>, ExtractLeaf<TPath>, PopulateField<TPopMap[ExtractParent<TPath>], ExtractLeaf<TPath> & RefFields<TPopMap[ExtractParent<TPath>]>>>, TPopMap & {
3103
+ [P in `${ExtractParent<TPath>}.${ExtractLeaf<TPath>}`]: ExtractRefCollection<TPopMap[ExtractParent<TPath>], ExtractLeaf<TPath> & RefFields<TPopMap[ExtractParent<TPath>]>>;
3104
+ }>;
3105
+ /**
3106
+ * Populate a nested ref field and rename the leaf in the output.
3107
+ *
3108
+ * @param path - A dot-separated path like `'author.companyId'`.
3109
+ * @param as - The new name for the leaf field in the output.
3110
+ * @returns A new builder with the nested field renamed and typed.
3111
+ *
3112
+ * @example
3113
+ * ```ts
3114
+ * const post = await posts.findOneOrThrow({ title: 'Hello' })
3115
+ * .populate('authorId', 'author')
3116
+ * .populate('author.companyId', 'company')
3117
+ * ```
3118
+ */
3119
+ populate<TPath extends NestedRefPath<TPopMap>, TAs extends string>(path: TPath, as: TAs): PopulateOneOrThrowQuery<TDef, DeepPopulate<TOutput, `${ExtractParent<TPath>}.${TAs}`, ExtractLeaf<TPath>, TAs, PopulateField<TPopMap[ExtractParent<TPath>], ExtractLeaf<TPath> & RefFields<TPopMap[ExtractParent<TPath>]>>>, TPopMap & {
3120
+ [P in `${ExtractParent<TPath>}.${TAs}`]: ExtractRefCollection<TPopMap[ExtractParent<TPath>], ExtractLeaf<TPath> & RefFields<TPopMap[ExtractParent<TPath>]>>;
3121
+ }>;
3122
+ /**
3123
+ * Attach fulfillment and rejection handlers to the query promise.
3124
+ *
3125
+ * Executes the base findOneOrThrow query and applies populate steps if any.
3126
+ * Throws {@link ZodmonNotFoundError} when no document matches.
3127
+ */
3128
+ then<TResult1 = TOutput, TResult2 = never>(onfulfilled?: ((value: TOutput) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
3129
+ private execute;
3130
+ }
3131
+
2245
3132
  /**
2246
3133
  * Typed wrapper around a MongoDB driver `Collection`.
2247
3134
  *
@@ -2257,7 +3144,35 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
2257
3144
  readonly definition: TDef;
2258
3145
  /** The underlying MongoDB driver collection, typed to the inferred document type. */
2259
3146
  readonly native: Collection<InferDocument<TDef>>;
2260
- constructor(definition: TDef, native: Collection<InferDocument<TDef>>);
3147
+ /**
3148
+ * The MongoDB client session bound to this handle, if any.
3149
+ *
3150
+ * When set, all CRUD and aggregation operations performed through this
3151
+ * handle will include the session in their options, enabling transactional
3152
+ * reads and writes. Undefined when no session is bound.
3153
+ */
3154
+ readonly session: ClientSession | undefined;
3155
+ constructor(definition: TDef, native: Collection<InferDocument<TDef>>, session?: ClientSession);
3156
+ /**
3157
+ * Create a new handle bound to the given MongoDB client session.
3158
+ *
3159
+ * Returns a new {@link CollectionHandle} that shares the same collection
3160
+ * definition and native driver collection, but passes `session` to every
3161
+ * CRUD and aggregation operation. The original handle is not modified.
3162
+ *
3163
+ * @param session - The MongoDB `ClientSession` to bind.
3164
+ * @returns A new handle with the session attached.
3165
+ *
3166
+ * @example
3167
+ * ```ts
3168
+ * const users = db.use(Users)
3169
+ * await db.client.withSession(async (session) => {
3170
+ * const bound = users.withSession(session)
3171
+ * await bound.insertOne({ name: 'Ada' }) // uses session
3172
+ * })
3173
+ * ```
3174
+ */
3175
+ withSession(session: ClientSession): CollectionHandle<TDef>;
2261
3176
  /**
2262
3177
  * Insert a single document into the collection.
2263
3178
  *
@@ -2301,13 +3216,12 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
2301
3216
  /**
2302
3217
  * Find a single document matching the filter.
2303
3218
  *
2304
- * Queries MongoDB, then validates the fetched document against the collection's
2305
- * Zod schema. Validation mode is resolved from the per-query option, falling
2306
- * back to the collection-level default (which defaults to `'strict'`).
3219
+ * Returns a {@link PopulateOneQuery} that can be awaited directly or chained
3220
+ * with `.populate()` calls to resolve foreign key references.
2307
3221
  *
2308
3222
  * @param filter - Type-safe filter to match documents.
2309
3223
  * @param options - Optional validation overrides.
2310
- * @returns The matched document, or `null` if no document matches.
3224
+ * @returns A populate builder that resolves to the matched document, or `null`.
2311
3225
  * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
2312
3226
  *
2313
3227
  * @example
@@ -2316,13 +3230,20 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
2316
3230
  * const user = await users.findOne({ name: 'Ada' })
2317
3231
  * if (user) console.log(user.role)
2318
3232
  * ```
3233
+ *
3234
+ * @example
3235
+ * ```ts
3236
+ * const post = await posts.findOne({ title: 'Hello' })
3237
+ * .populate('authorId', 'author')
3238
+ * ```
2319
3239
  */
2320
- findOne(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef> | null>;
3240
+ findOne(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): PopulateOneQuery<TDef, InferDocument<TDef>>;
2321
3241
  /**
2322
3242
  * Find a single document matching the filter with a type-safe projection.
2323
3243
  *
2324
3244
  * Returns only the fields specified by the projection. The return type is
2325
3245
  * narrowed to reflect which fields are included or excluded.
3246
+ * Projected queries do not support `.populate()`.
2326
3247
  *
2327
3248
  * @param filter - Type-safe filter to match documents.
2328
3249
  * @param options - Projection and optional validation overrides.
@@ -2340,12 +3261,13 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
2340
3261
  /**
2341
3262
  * Find a single document matching the filter, or throw if none exists.
2342
3263
  *
2343
- * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
2344
- * instead of returning `null` when no document matches the filter.
3264
+ * Returns a {@link PopulateOneOrThrowQuery} that can be awaited directly or
3265
+ * chained with `.populate()` calls. Throws {@link ZodmonNotFoundError}
3266
+ * instead of returning `null` when no document matches.
2345
3267
  *
2346
3268
  * @param filter - Type-safe filter to match documents.
2347
3269
  * @param options - Optional validation overrides.
2348
- * @returns The matched document (never null).
3270
+ * @returns A populate builder that resolves to the matched document (never null).
2349
3271
  * @throws {ZodmonNotFoundError} When no document matches the filter.
2350
3272
  * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
2351
3273
  *
@@ -2355,14 +3277,21 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
2355
3277
  * const user = await users.findOneOrThrow({ name: 'Ada' })
2356
3278
  * console.log(user.role) // guaranteed non-null
2357
3279
  * ```
3280
+ *
3281
+ * @example
3282
+ * ```ts
3283
+ * const post = await posts.findOneOrThrow({ title: 'Hello' })
3284
+ * .populate('authorId', 'author')
3285
+ * ```
2358
3286
  */
2359
- findOneOrThrow(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef>>;
3287
+ findOneOrThrow(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): PopulateOneOrThrowQuery<TDef, InferDocument<TDef>>;
2360
3288
  /**
2361
3289
  * Find a single document matching the filter with a type-safe projection, or throw if none exists.
2362
3290
  *
2363
3291
  * Returns only the fields specified by the projection. The return type is
2364
3292
  * narrowed to reflect which fields are included or excluded. Throws
2365
3293
  * {@link ZodmonNotFoundError} instead of returning `null`.
3294
+ * Projected queries do not support `.populate()`.
2366
3295
  *
2367
3296
  * @param filter - Type-safe filter to match documents.
2368
3297
  * @param options - Projection and optional validation overrides.
@@ -2612,7 +3541,8 @@ declare class AggregatePipeline<TDef extends AnyCollection, TOutput> {
2612
3541
  protected readonly definition: TDef;
2613
3542
  private readonly nativeCollection;
2614
3543
  private readonly stages;
2615
- constructor(definition: TDef, nativeCollection: Collection<InferDocument<TDef>>, stages: Document[]);
3544
+ private readonly session;
3545
+ constructor(definition: TDef, nativeCollection: Collection<InferDocument<TDef>>, stages: Document[], session?: ClientSession);
2616
3546
  /**
2617
3547
  * Append an arbitrary aggregation stage to the pipeline (escape hatch).
2618
3548
  *
@@ -2728,10 +3658,21 @@ declare class AggregatePipeline<TDef extends AnyCollection, TOutput> {
2728
3658
  * .toArray()
2729
3659
  * // subset[0].role → 'engineer' | 'designer'
2730
3660
  * ```
3661
+ *
3662
+ * @example
3663
+ * ```ts
3664
+ * // Field-vs-field comparison via $expr callback
3665
+ * const overRefunded = await orders.aggregate()
3666
+ * .match(
3667
+ * { status: 'completed' },
3668
+ * (expr) => expr.gt('totalAmount', expr.field('refundedAmount')),
3669
+ * )
3670
+ * .toArray()
3671
+ * ```
2731
3672
  */
2732
3673
  match<TNarrow extends {
2733
3674
  [K in keyof TNarrow]: K extends keyof TOutput ? TOutput[K] : never;
2734
- } = {}, F extends TypedFilter<TOutput> = TypedFilter<TOutput>>(filter: F): AggregatePipeline<TDef, Prettify<Omit<NarrowFromFilter<TOutput, F>, keyof TNarrow> & TNarrow>>;
3675
+ } = {}, F extends TypedFilter<TOutput> = TypedFilter<TOutput>>(filter: F, exprCb?: (expr: ExpressionBuilder<TOutput>) => Expression<boolean>): AggregatePipeline<TDef, Prettify<Omit<NarrowFromFilter<TOutput, F>, keyof TNarrow> & TNarrow>>;
2735
3676
  /**
2736
3677
  * Sort documents by one or more fields.
2737
3678
  *
@@ -2848,7 +3789,7 @@ declare class AggregatePipeline<TDef extends AnyCollection, TOutput> {
2848
3789
  * and compile-time field validation. Builder methods resolve return types
2849
3790
  * to the actual field type (`T[K]`), not `unknown`.
2850
3791
  *
2851
- * @param field - A field name or array of field names to group by.
3792
+ * @param field - A field name, array of field names, or `null` to aggregate all documents into a single group.
2852
3793
  * @param accumulators - A callback `(acc) => ({ ... })` or plain accumulator object.
2853
3794
  * @returns A new pipeline with the `$group` stage appended.
2854
3795
  *
@@ -2879,7 +3820,20 @@ declare class AggregatePipeline<TDef extends AnyCollection, TOutput> {
2879
3820
  * .groupBy(['role', 'dept'], acc => ({ count: acc.count() }))
2880
3821
  * .toArray()
2881
3822
  * ```
3823
+ *
3824
+ * @example
3825
+ * ```ts
3826
+ * // Null groupBy — aggregate all documents into a single summary
3827
+ * const totals = await aggregate(orders)
3828
+ * .groupBy(null, acc => ({
3829
+ * grandTotal: acc.sum('amount'),
3830
+ * orderCount: acc.count(),
3831
+ * }))
3832
+ * .toArray()
3833
+ * // → [{ _id: null, grandTotal: 2740, orderCount: 7 }]
3834
+ * ```
2882
3835
  */
3836
+ groupBy<TAccum extends Record<string, Accumulator>>(field: null, accumulators: ((acc: AccumulatorBuilder<TOutput>) => TAccum) | TAccum): AggregatePipeline<TDef, GroupByNullResult<TAccum>>;
2883
3837
  groupBy<K extends DotPath<TOutput>, TAccum extends Record<string, Accumulator>>(field: K, accumulators: ((acc: AccumulatorBuilder<TOutput>) => TAccum) | TAccum): AggregatePipeline<TDef, GroupByResult<TOutput, K, TAccum>>;
2884
3838
  groupBy<K extends DotPath<TOutput>, TAccum extends Record<string, Accumulator>>(field: K[], accumulators: ((acc: AccumulatorBuilder<TOutput>) => TAccum) | TAccum): AggregatePipeline<TDef, GroupByCompoundResult<TOutput, K, TAccum>>;
2885
3839
  /**
@@ -3016,6 +3970,37 @@ declare class AggregatePipeline<TDef extends AnyCollection, TOutput> {
3016
3970
  }): AggregatePipeline<TDef, Prettify<TOutput & {
3017
3971
  [P in TAs]: InferDocument<TForeignDef>[];
3018
3972
  }>>;
3973
+ /**
3974
+ * Run multiple sub-pipelines on the same input documents in parallel.
3975
+ *
3976
+ * Each key in `spec` maps to a callback that receives a fresh `SubPipeline`
3977
+ * starting from `TOutput`. The callback chains stages and returns the terminal
3978
+ * pipeline. Zodmon extracts the accumulated stages at runtime to build the
3979
+ * `$facet` document. The output type is fully inferred — no annotation needed.
3980
+ *
3981
+ * Sub-pipelines support all stage methods including `.raw()` for operators not
3982
+ * yet first-class. Execution methods (`toArray`, `explain`) are not available
3983
+ * inside branches.
3984
+ *
3985
+ * @param spec - An object mapping branch names to sub-pipeline builder callbacks.
3986
+ * @returns A new pipeline whose output is one document with each branch name mapped to an array of results.
3987
+ *
3988
+ * @example
3989
+ * ```ts
3990
+ * const [report] = await aggregate(orders)
3991
+ * .facet({
3992
+ * byCategory: (sub) => sub
3993
+ * .groupBy('category', acc => ({ count: acc.count() }))
3994
+ * .sort({ count: -1 }),
3995
+ * totals: (sub) => sub
3996
+ * .groupBy(null, acc => ({ grandTotal: acc.sum('amount') })),
3997
+ * })
3998
+ * .toArray()
3999
+ * // report.byCategory → { _id: 'electronics' | 'books' | 'clothing'; count: number }[]
4000
+ * // report.totals → { _id: null; grandTotal: number }[]
4001
+ * ```
4002
+ */
4003
+ facet<TSpec extends Record<string, (sub: SubPipeline<TDef, TOutput>) => AggregatePipeline<TDef, any>>>(spec: TSpec): AggregatePipeline<TDef, Prettify<InferFacetOutput<TSpec>>>;
3019
4004
  /**
3020
4005
  * Count documents per group, sorted by count descending.
3021
4006
  *
@@ -3112,6 +4097,8 @@ declare class AggregatePipeline<TDef extends AnyCollection, TOutput> {
3112
4097
  bottom(n: number, options: {
3113
4098
  by: keyof TOutput & string;
3114
4099
  }): AggregatePipeline<TDef, TOutput>;
4100
+ /** @internal Used by facet() to extract branch stages. Not part of the public API. */
4101
+ getStages(): Document[];
3115
4102
  }
3116
4103
  /**
3117
4104
  * Create a new aggregation pipeline for a collection.
@@ -3131,6 +4118,99 @@ declare class AggregatePipeline<TDef extends AnyCollection, TOutput> {
3131
4118
  * ```
3132
4119
  */
3133
4120
  declare function aggregate<TDef extends AnyCollection>(handle: CollectionHandle<TDef>): AggregatePipeline<TDef, InferDocument<TDef>>;
4121
+ /**
4122
+ * A sub-pipeline passed to each `.facet()` branch callback.
4123
+ *
4124
+ * All stage-building methods are available. Execution methods (`toArray`,
4125
+ * `explain`, `[Symbol.asyncIterator]`) are omitted — sub-pipelines are
4126
+ * not executed directly; they only accumulate stages for the parent `$facet` stage.
4127
+ *
4128
+ * @typeParam TDef - The collection definition type.
4129
+ * @typeParam TOutput - The current output document type.
4130
+ *
4131
+ * @example
4132
+ * ```ts
4133
+ * .facet({
4134
+ * summary: (sub: SubPipeline<typeof Orders, Order>) => sub.groupBy(null, acc => ({ total: acc.sum('amount') }))
4135
+ * })
4136
+ * ```
4137
+ */
4138
+ type SubPipeline<TDef extends AnyCollection, TOutput> = Omit<AggregatePipeline<TDef, TOutput>, 'toArray' | 'explain' | typeof Symbol.asyncIterator>;
4139
+ /**
4140
+ * Maps a `.facet()` spec object to its inferred output shape.
4141
+ *
4142
+ * Each key maps to the array of results produced by that branch's terminal pipeline.
4143
+ * Uses `any` for the sub-pipeline parameter to avoid TypeScript contravariance
4144
+ * complications — inference of `TOut` from the return type is what matters.
4145
+ *
4146
+ * @example
4147
+ * ```ts
4148
+ * type Spec = {
4149
+ * byCategory: (sub: SubPipeline<any, any>) => AggregatePipeline<any, { _id: string; count: number }>
4150
+ * }
4151
+ * type Result = InferFacetOutput<Spec>
4152
+ * // ^? { byCategory: { _id: string; count: number }[] }
4153
+ * ```
4154
+ */
4155
+ type InferFacetOutput<TSpec> = {
4156
+ [K in keyof TSpec]: TSpec[K] extends (sub: any) => AggregatePipeline<any, infer TOut> ? TOut[] : never;
4157
+ };
4158
+
4159
+ /**
4160
+ * The callback signature for {@link Database.transaction}.
4161
+ *
4162
+ * @typeParam T - The return type of the transaction callback.
4163
+ *
4164
+ * @example
4165
+ * ```ts
4166
+ * const fn: TransactionFn<void> = async (tx) => {
4167
+ * const txUsers = tx.use(users)
4168
+ * await txUsers.insertOne({ name: 'Ada' })
4169
+ * }
4170
+ * ```
4171
+ */
4172
+ type TransactionFn<T> = (tx: TransactionContext) => Promise<T>;
4173
+ /**
4174
+ * Transaction context passed to the {@link Database.transaction} callback.
4175
+ *
4176
+ * Use {@link use} to bind existing collection handles to this transaction's
4177
+ * session. All operations on the returned handle participate in the
4178
+ * transaction -- auto-committed on success, auto-rolled-back on error.
4179
+ *
4180
+ * @example
4181
+ * ```ts
4182
+ * await db.transaction(async (tx) => {
4183
+ * const txUsers = tx.use(users)
4184
+ * const txPosts = tx.use(posts)
4185
+ * const user = await txUsers.insertOne({ name: 'Ada' })
4186
+ * await txPosts.insertOne({ authorId: user._id, title: 'Hello' })
4187
+ * })
4188
+ * ```
4189
+ */
4190
+ declare class TransactionContext {
4191
+ /** @internal */
4192
+ private readonly session;
4193
+ /** @internal */
4194
+ constructor(session: ClientSession);
4195
+ /**
4196
+ * Bind a collection handle to this transaction's session.
4197
+ *
4198
+ * Returns a cloned handle whose CRUD operations automatically include
4199
+ * the transaction session. The original handle is not modified.
4200
+ *
4201
+ * @param handle - An existing collection handle from `db.use()`.
4202
+ * @returns A new handle bound to the transaction session.
4203
+ *
4204
+ * @example
4205
+ * ```ts
4206
+ * await db.transaction(async (tx) => {
4207
+ * const txUsers = tx.use(users)
4208
+ * await txUsers.insertOne({ name: 'Ada' })
4209
+ * })
4210
+ * ```
4211
+ */
4212
+ use<TDef extends AnyCollection>(handle: CollectionHandle<TDef>): CollectionHandle<TDef>;
4213
+ }
3134
4214
 
3135
4215
  /**
3136
4216
  * Wraps a MongoDB `MongoClient` and `Db`, providing typed collection access
@@ -3187,11 +4267,40 @@ declare class Database {
3187
4267
  */
3188
4268
  syncIndexes(options?: SyncIndexesOptions): Promise<Record<string, SyncIndexesResult>>;
3189
4269
  /**
3190
- * Execute a function within a MongoDB transaction with auto-commit/rollback.
4270
+ * Execute a function within a MongoDB transaction.
4271
+ *
4272
+ * Starts a client session and runs the callback inside
4273
+ * `session.withTransaction()`. The driver handles commit on success,
4274
+ * abort on error, and automatic retries for transient transaction errors.
4275
+ *
4276
+ * The return value of `fn` is forwarded as the return value of this method.
4277
+ *
4278
+ * @param fn - Async callback receiving a {@link TransactionContext}.
4279
+ * @returns The value returned by `fn`.
4280
+ *
4281
+ * @example
4282
+ * ```ts
4283
+ * const user = await db.transaction(async (tx) => {
4284
+ * const txUsers = tx.use(users)
4285
+ * return await txUsers.insertOne({ name: 'Ada' })
4286
+ * })
4287
+ * ```
3191
4288
  *
3192
- * Stub — full implementation in TASK-106.
4289
+ * @example
4290
+ * ```ts
4291
+ * // Rollback on error
4292
+ * try {
4293
+ * await db.transaction(async (tx) => {
4294
+ * const txUsers = tx.use(users)
4295
+ * await txUsers.insertOne({ name: 'Ada' })
4296
+ * throw new Error('abort!')
4297
+ * })
4298
+ * } catch (err) {
4299
+ * // insert was rolled back, err is the original error
4300
+ * }
4301
+ * ```
3193
4302
  */
3194
- transaction<T>(_fn: () => Promise<T>): Promise<T>;
4303
+ transaction<T>(fn: TransactionFn<T>): Promise<T>;
3195
4304
  /**
3196
4305
  * Close the underlying `MongoClient` connection. Safe to call even if
3197
4306
  * no connection was established (the driver handles this gracefully).
@@ -4009,6 +5118,68 @@ type WarnableDefinition = {
4009
5118
  */
4010
5119
  declare function checkUnindexedFields(definition: WarnableDefinition, filter: Record<string, unknown>): void;
4011
5120
 
5121
+ /**
5122
+ * Walk through Zod wrappers to find the inner schema with ref metadata.
5123
+ *
5124
+ * Mirrors the type-level `UnwrapRef<T>` at runtime. Peels ZodOptional,
5125
+ * ZodNullable, ZodDefault (via `_zod.def.innerType`), and ZodArray
5126
+ * (via `_zod.def.element`).
5127
+ *
5128
+ * @param schema - The Zod schema to unwrap.
5129
+ * @returns The innermost schema after stripping wrappers.
5130
+ *
5131
+ * @example
5132
+ * ```ts
5133
+ * const inner = unwrapRefSchema(Posts.shape.categoryIds)
5134
+ * const ref = getRefMetadata(inner) // { collection: Categories }
5135
+ * ```
5136
+ */
5137
+ declare function unwrapRefSchema(schema: z.ZodType): z.ZodType;
5138
+ /**
5139
+ * Resolve a `.populate()` call into a `PopulateStep`.
5140
+ *
5141
+ * For top-level fields, inspects the collection's shape directly.
5142
+ * For nested dot-paths, walks previous steps to find the parent
5143
+ * collection definition, then resolves the leaf field in that shape.
5144
+ *
5145
+ * @param definition - The root collection definition.
5146
+ * @param previousSteps - Steps from earlier `.populate()` calls.
5147
+ * @param path - The field path (e.g. `'authorId'` or `'author.companyId'`).
5148
+ * @param as - The output field name (renamed or original).
5149
+ * @param projection - Optional projection to pass to the `$in` query.
5150
+ * @returns A resolved `PopulateStep`.
5151
+ * @throws When the field has no `.ref()` metadata or the parent path is not populated.
5152
+ *
5153
+ * @example
5154
+ * ```ts
5155
+ * const step = resolvePopulateStep(Posts, [], 'authorId', 'author')
5156
+ * // { leafField: 'authorId', as: 'author', targetCollection: Users, ... }
5157
+ * ```
5158
+ */
5159
+ declare function resolvePopulateStep(definition: AnyCollection, previousSteps: readonly PopulateStep[], path: string, as: string, projection?: Record<string, 0 | 1 | boolean>): PopulateStep;
5160
+ /**
5161
+ * Execute populate steps against a set of documents.
5162
+ *
5163
+ * Processes steps in order. For each step:
5164
+ * 1. Navigate to the parent level for nested paths
5165
+ * 2. Collect unique IDs from the leaf field
5166
+ * 3. Batch query the target collection with `$in`
5167
+ * 4. Merge results back, applying rename if needed
5168
+ *
5169
+ * Mutates the documents in place and returns them.
5170
+ *
5171
+ * @param documents - The documents to populate (mutated in place).
5172
+ * @param steps - The populate steps to execute in order.
5173
+ * @param getCollection - Function to get a native MongoDB Collection by name.
5174
+ * @returns The populated documents.
5175
+ *
5176
+ * @example
5177
+ * ```ts
5178
+ * const docs = await executePopulate(rawDocs, steps, name => db.collection(name))
5179
+ * ```
5180
+ */
5181
+ declare function executePopulate(documents: Document[], steps: readonly PopulateStep[], getCollection: (name: string) => Collection): Promise<Document[]>;
5182
+
4012
5183
  /**
4013
5184
  * Convenience namespace that groups all query operators under a single import.
4014
5185
  *
@@ -4317,4 +5488,4 @@ type UpdateFilterOf<TDef extends AnyCollection> = TypedUpdateFilter<InferDocumen
4317
5488
  */
4318
5489
  type SortOf<TDef extends AnyCollection> = TypedSort<InferDocument<TDef>>;
4319
5490
 
4320
- 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 DotPath, type DotPathValue, type ExclusionProjection, type Expression, type ExpressionBuilder, type ExtractRefCollection, type FieldIndexDefinition, type FieldRef, type FieldRefType, type FilterOf, type FindOneAndDeleteOptions, type FindOneAndUpdateOptions, type FindOneOptions, type FindOneProjectionOptions, type FindOptions, type FindProjectionOptions, type GroupByCompoundResult, type GroupByResult, type HandleOf, type IncFields, type InclusionProjection, 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 ProjectionResult, 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 TypedProjection, 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, type _DotPathType, type _DotPaths, aggregate, checkUnindexedFields, collection, createAccumulatorBuilder, createClient, createExpressionBuilder, deleteMany, deleteOne, deriveProjectedSchema, extractComparableOptions, extractDbName, extractFieldIndexes, find, findOne, findOneAndDelete, findOneAndUpdate, findOneOrThrow, generateIndexName, getIndexMetadata, getRefMetadata, index, insertMany, insertOne, isInclusionProjection, isOid, objectId, oid, raw, serializeIndexKey, syncIndexes, toCompoundIndexSpec, toFieldIndexSpec, updateMany, updateOne, wrapMongoError };
5491
+ 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 ApplyPopulate, type ApplyPopulateProjected, type ArrayElement, type CollectionDefinition, CollectionHandle, type CollectionName, type CollectionOptions, type ComparisonOperators, type CompoundIndexDefinition, type CurrentDateFields, type CursorPage, type CursorPaginateOptions, Database, type DeepPopulate, type DotPath, type DotPathValue, type DotSubPaths, type ExclusionProjection, type Expression, type ExpressionBuilder, type ExtractRefCollection, type FieldIndexDefinition, type FieldOrExpr, type FieldRef, type FieldRefType, type FilterOf, type FindOneAndDeleteOptions, type FindOneAndUpdateOptions, type FindOneOptions, type FindOneProjectionOptions, type FindOptions, type FindProjectionOptions, type GroupByCompoundResult, type GroupByNullResult, type GroupByResult, type HandleOf, type IncFields, type InclusionProjection, IndexBuilder, type IndexMetadata, type IndexNames, type IndexOptions, type IndexSpec, type InferAccumulator, type InferAccumulators, type InferAddedFields, type InferDocument, type InferExpression, type InferInsert, type NarrowFromFilter, type NestedRefPath, type OffsetPage, type OffsetPaginateOptions, type PopFields, PopulateCursor, type PopulateField, type PopulateFieldProjected, PopulateOneOrThrowQuery, PopulateOneQuery, type PopulateProjectionConfig, PopulateRefBuilder, type PopulateStep, type Populated, type Prettify, type ProjectionResult, type PullFields, type PushFields, type PushModifiers, type RefFields, type RefMarker, type RefMetadata, type RenameFields, type ReplaceDots, type ResolvedShape, type SetFields, type SortOf, type StaleIndex, type SyncIndexesOptions, type SyncIndexesResult, TransactionContext, type TransactionFn, type TypedFilter, TypedFindCursor, type TypedProjection, type TypedSort, type TypedUpdateFilter, type UnsetFields, type UnwindResult, type UnwrapRef, 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, createPopulateCursor, deleteMany, deleteOne, deriveProjectedSchema, executePopulate, extractComparableOptions, extractDbName, extractFieldIndexes, find, findOne, findOneAndDelete, findOneAndUpdate, findOneOrThrow, generateIndexName, getIndexMetadata, getRefMetadata, index, insertMany, insertOne, isInclusionProjection, isOid, objectId, oid, raw, resolvePopulateStep, serializeIndexKey, syncIndexes, toCompoundIndexSpec, toFieldIndexSpec, unwrapRefSchema, updateMany, updateOne, wrapMongoError };