@zodmon/core 0.10.0 → 0.11.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
@@ -319,6 +319,40 @@ type ExtractNames<T extends readonly {
319
319
  type Prettify<T> = {
320
320
  [K in keyof T]: T[K];
321
321
  } & {};
322
+ /**
323
+ * Generates all valid dot-separated field paths for a document type.
324
+ *
325
+ * Recursively traverses nested objects and unwraps arrays to match
326
+ * MongoDB's implicit array traversal semantics. Both top-level keys
327
+ * and nested paths are included in the resulting union.
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * type Doc = { address: { city: string }; tags: Array<{ label: string }> }
332
+ * type Paths = DotPath<Doc>
333
+ * // ^? 'address' | 'address.city' | 'tags' | 'tags.label'
334
+ * ```
335
+ */
336
+ type DotPath<T> = T extends ReadonlyArray<infer E> ? DotPath<E> : T extends Record<string, unknown> ? {
337
+ [K in keyof T & string]: T[K] extends ReadonlyArray<infer E> ? E extends Record<string, unknown> ? K | `${K}.${DotPath<E>}` : K : T[K] extends Record<string, unknown> ? K | `${K}.${DotPath<T[K]>}` : K;
338
+ }[keyof T & string] : never;
339
+ /**
340
+ * Resolves the value type at a dot-separated path within a document type.
341
+ *
342
+ * Splits the path on `.` and recursively descends into nested types.
343
+ * Unwraps arrays at intermediate levels to match MongoDB's implicit
344
+ * array traversal. Returns `never` for invalid paths.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * type Doc = { address: { city: string }; tags: Array<{ label: string }> }
349
+ * type City = DotPathValue<Doc, 'address.city'>
350
+ * // ^? string
351
+ * type Label = DotPathValue<Doc, 'tags.label'>
352
+ * // ^? string
353
+ * ```
354
+ */
355
+ type DotPathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? T[K] extends ReadonlyArray<infer E> ? DotPathValue<E, Rest> : DotPathValue<T[K], Rest> : never : P extends keyof T ? T[P] : never;
322
356
 
323
357
  /**
324
358
  * Type-level marker that carries the target collection type through the
@@ -448,21 +482,25 @@ type InferAccumulators<T extends Record<string, Accumulator>> = {
448
482
  [K in keyof T]: InferAccumulator<T[K]>;
449
483
  };
450
484
  /**
451
- * Field reference string prefixed with `$`, constrained to keys of `T`.
485
+ * Field reference string prefixed with `$`, constrained to dot-paths of `T`.
486
+ *
487
+ * Supports both top-level fields (`$price`) and nested dot-paths
488
+ * (`$address.city`, `$address.geo.lat`).
452
489
  *
453
490
  * @example
454
491
  * ```ts
455
- * type OrderRef = FieldRef<{ price: number; qty: number }>
456
- * // ^? '$price' | '$qty'
492
+ * type OrderRef = FieldRef<{ price: number; address: { city: string } }>
493
+ * // ^? '$price' | '$address' | '$address.city'
457
494
  * ```
458
495
  */
459
- type FieldRef<T> = `$${keyof T & string}`;
496
+ type FieldRef<T> = `$${DotPath<T>}`;
460
497
  /**
461
498
  * Typed accumulator factory passed to the `groupBy` callback.
462
499
  *
463
- * Each method constrains field names to `keyof T & string`, providing
464
- * IDE autocomplete and compile-time validation. Return types resolve
465
- * to the actual field type (`T[K]`), not `unknown`.
500
+ * Each method constrains field names to `DotPath<T>`, providing
501
+ * IDE autocomplete and compile-time validation for both top-level
502
+ * and nested dot-path fields. Return types resolve to the actual
503
+ * field type via `DotPathValue<T, F>`, not `unknown`.
466
504
  *
467
505
  * @typeParam T - The current pipeline output document type.
468
506
  *
@@ -470,10 +508,10 @@ type FieldRef<T> = `$${keyof T & string}`;
470
508
  * ```ts
471
509
  * users.aggregate()
472
510
  * .groupBy('role', acc => ({
473
- * minSalary: acc.min('salary'), // Accumulator<number>
474
- * firstName: acc.first('name'), // Accumulator<string>
475
- * allNames: acc.push('name'), // Accumulator<string[]>
476
- * count: acc.count(), // Accumulator<number>
511
+ * minSalary: acc.min('salary'), // Accumulator<number>
512
+ * firstName: acc.first('name'), // Accumulator<string>
513
+ * allCities: acc.push('address.city'), // Accumulator<string[]>
514
+ * count: acc.count(), // Accumulator<number>
477
515
  * }))
478
516
  * ```
479
517
  */
@@ -481,64 +519,79 @@ type AccumulatorBuilder<T> = {
481
519
  /** Count documents in each group. Always returns `Accumulator<number>`. */
482
520
  count(): Accumulator<number>;
483
521
  /** Sum a numeric field. Always returns `Accumulator<number>`. */
484
- sum<K extends keyof T & string>(field: K): Accumulator<number>;
522
+ sum(field: DotPath<T>): Accumulator<number>;
485
523
  /** Sum a literal number per document. Always returns `Accumulator<number>`. */
486
524
  sum(value: number): Accumulator<number>;
487
525
  /** Average a numeric field. Always returns `Accumulator<number>`. */
488
- avg<K extends keyof T & string>(field: K): Accumulator<number>;
489
- /** Minimum value of a field. Returns `Accumulator<T[K]>`. */
490
- min<K extends keyof T & string>(field: K): Accumulator<T[K]>;
491
- /** Maximum value of a field. Returns `Accumulator<T[K]>`. */
492
- max<K extends keyof T & string>(field: K): Accumulator<T[K]>;
493
- /** First value in each group (by document order). Returns `Accumulator<T[K]>`. */
494
- first<K extends keyof T & string>(field: K): Accumulator<T[K]>;
495
- /** Last value in each group (by document order). Returns `Accumulator<T[K]>`. */
496
- last<K extends keyof T & string>(field: K): Accumulator<T[K]>;
497
- /** Collect values into an array (with duplicates). Returns `Accumulator<T[K][]>`. */
498
- push<K extends keyof T & string>(field: K): Accumulator<T[K][]>;
499
- /** Collect unique values into an array. Returns `Accumulator<T[K][]>`. */
500
- addToSet<K extends keyof T & string>(field: K): Accumulator<T[K][]>;
526
+ avg(field: DotPath<T>): Accumulator<number>;
527
+ /** Minimum value of a field. Returns `Accumulator<DotPathValue<T, F>>`. */
528
+ min<F extends DotPath<T>>(field: F): Accumulator<DotPathValue<T, F>>;
529
+ /** Maximum value of a field. Returns `Accumulator<DotPathValue<T, F>>`. */
530
+ max<F extends DotPath<T>>(field: F): Accumulator<DotPathValue<T, F>>;
531
+ /** First value in each group (by document order). Returns `Accumulator<DotPathValue<T, F>>`. */
532
+ first<F extends DotPath<T>>(field: F): Accumulator<DotPathValue<T, F>>;
533
+ /** Last value in each group (by document order). Returns `Accumulator<DotPathValue<T, F>>`. */
534
+ last<F extends DotPath<T>>(field: F): Accumulator<DotPathValue<T, F>>;
535
+ /** Collect values into an array (with duplicates). Returns `Accumulator<DotPathValue<T, F>[]>`. */
536
+ push<F extends DotPath<T>>(field: F): Accumulator<DotPathValue<T, F>[]>;
537
+ /** Collect unique values into an array. Returns `Accumulator<DotPathValue<T, F>[]>`. */
538
+ addToSet<F extends DotPath<T>>(field: F): Accumulator<DotPathValue<T, F>[]>;
501
539
  };
502
540
  /**
503
541
  * Resolves the document field type from a `$`-prefixed field reference.
504
542
  *
543
+ * Supports both top-level refs (`$price`) and dot-path refs
544
+ * (`$address.city`), resolving through nested objects and arrays.
545
+ *
505
546
  * @example
506
547
  * ```ts
507
- * type Order = { price: number; name: string }
548
+ * type Order = { price: number; address: { city: string } }
508
549
  * type PriceType = FieldRefType<Order, '$price'>
509
550
  * // ^? number
551
+ * type CityType = FieldRefType<Order, '$address.city'>
552
+ * // ^? string
510
553
  * ```
511
554
  */
512
- type FieldRefType<T, F extends string> = F extends `$${infer K}` ? K extends keyof T ? T[K] : never : never;
555
+ type FieldRefType<T, F extends string> = F extends `$${infer K}` ? DotPathValue<T, K> extends never ? never : DotPathValue<T, K> : never;
513
556
  /**
514
557
  * Output shape of a single-field `groupBy` stage.
515
558
  *
516
559
  * The `_id` field holds the grouped-by field's value, and the rest of the
517
- * shape comes from the inferred accumulator types.
560
+ * shape comes from the inferred accumulator types. Supports dot-path keys
561
+ * for grouping by nested fields.
518
562
  *
519
563
  * @example
520
564
  * ```ts
521
565
  * type Result = GroupByResult<Order, 'status', { count: Accumulator<number> }>
522
566
  * // ^? { _id: string; count: number }
567
+ *
568
+ * type Nested = GroupByResult<Order, 'address.city', { count: Accumulator<number> }>
569
+ * // ^? { _id: string; count: number }
523
570
  * ```
524
571
  */
525
- type GroupByResult<T, K extends keyof T, TAccum extends Record<string, Accumulator>> = Prettify<{
526
- _id: T[K];
572
+ type GroupByResult<T, K extends DotPath<T>, TAccum extends Record<string, Accumulator>> = Prettify<{
573
+ _id: DotPathValue<T, K & string>;
527
574
  } & InferAccumulators<TAccum>>;
528
575
  /**
529
576
  * Output shape of a compound (multi-field) `groupBy` stage.
530
577
  *
531
578
  * The `_id` field is an object with one property per grouped field,
532
579
  * and the rest of the shape comes from the inferred accumulator types.
580
+ * Supports dot-path keys for grouping by nested fields.
533
581
  *
534
582
  * @example
535
583
  * ```ts
536
584
  * type Result = GroupByCompoundResult<Order, 'status' | 'region', { count: Accumulator<number> }>
537
- * // ^? { _id: Pick<Order, 'status' | 'region'>; count: number }
585
+ * // ^? { _id: { status: string; region: string }; count: number }
586
+ *
587
+ * type Nested = GroupByCompoundResult<Order, 'name' | 'address.city', { count: Accumulator<number> }>
588
+ * // ^? { _id: { name: string; 'address.city': string }; count: number }
538
589
  * ```
539
590
  */
540
- type GroupByCompoundResult<T, K extends keyof T, TAccum extends Record<string, Accumulator>> = Prettify<{
541
- _id: Prettify<Pick<T, K>>;
591
+ type GroupByCompoundResult<T, K extends DotPath<T>, TAccum extends Record<string, Accumulator>> = Prettify<{
592
+ _id: Prettify<{
593
+ [P in K & string]: DotPathValue<T, P>;
594
+ }>;
542
595
  } & InferAccumulators<TAccum>>;
543
596
  /**
544
597
  * Output shape after unwinding an array field.
@@ -792,139 +845,169 @@ declare const $avg: (field: `$${string}`) => Accumulator<number>;
792
845
  /**
793
846
  * Returns the minimum value across documents in each group.
794
847
  *
795
- * For full type safety, prefer the callback builder (`acc.min('price')`).
796
- * The standalone form accepts an explicit result type `R` for manual typing.
848
+ * Three tiers of type safety:
849
+ * - **Tier 1 (document type):** `$min<Doc>('$address.city')` resolves via `FieldRefType`.
850
+ * - **Tier 2 (explicit scalar):** `$min<number>('$price')` — manual result type.
851
+ * - **Tier 3 (untyped):** `$min('$price')` — defaults to `unknown`.
852
+ *
853
+ * For best type safety, prefer the callback builder (`acc.min('price')`).
797
854
  *
798
- * @typeParam R - The expected result type (defaults to `unknown`).
799
- * @param field - A `$field` reference.
800
- * @returns An `Accumulator<R>`.
855
+ * @param field - A `$field` reference (supports dot-paths with document type param).
856
+ * @returns An `Accumulator` with the resolved result type.
801
857
  *
802
858
  * @example
803
859
  * ```ts
860
+ * // Tier 1 — document type with dot-path
861
+ * $min<Doc>('$address.city') // Accumulator<string>
862
+ *
804
863
  * // Tier 2 — explicit result type
805
- * const pipeline = orders.aggregate()
806
- * .groupBy('category', { cheapest: $min<number>('$price') })
864
+ * $min<number>('$price') // Accumulator<number>
807
865
  *
808
- * // Tier 1callback builder (recommended)
809
- * orders.aggregate()
810
- * .groupBy('category', acc => ({ cheapest: acc.min('price') }))
866
+ * // Tier 3untyped
867
+ * $min('$price') // Accumulator<unknown>
811
868
  * ```
812
869
  */
813
- declare const $min: <R = unknown>(field: `$${string}`) => Accumulator<R>;
870
+ declare function $min<T extends Record<string, unknown>>(field: FieldRef<T>): Accumulator<FieldRefType<T, FieldRef<T>>>;
871
+ declare function $min<R = unknown>(field: `$${string}`): Accumulator<R>;
814
872
  /**
815
873
  * Returns the maximum value across documents in each group.
816
874
  *
817
- * For full type safety, prefer the callback builder (`acc.max('price')`).
818
- * The standalone form accepts an explicit result type `R` for manual typing.
875
+ * Three tiers of type safety:
876
+ * - **Tier 1 (document type):** `$max<Doc>('$address.city')` resolves via `FieldRefType`.
877
+ * - **Tier 2 (explicit scalar):** `$max<number>('$price')` — manual result type.
878
+ * - **Tier 3 (untyped):** `$max('$price')` — defaults to `unknown`.
879
+ *
880
+ * For best type safety, prefer the callback builder (`acc.max('price')`).
819
881
  *
820
- * @typeParam R - The expected result type (defaults to `unknown`).
821
- * @param field - A `$field` reference.
822
- * @returns An `Accumulator<R>`.
882
+ * @param field - A `$field` reference (supports dot-paths with document type param).
883
+ * @returns An `Accumulator` with the resolved result type.
823
884
  *
824
885
  * @example
825
886
  * ```ts
887
+ * // Tier 1 — document type with dot-path
888
+ * $max<Doc>('$tags.label') // Accumulator<string>
889
+ *
826
890
  * // Tier 2 — explicit result type
827
- * const pipeline = orders.aggregate()
828
- * .groupBy('category', { priciest: $max<number>('$price') })
891
+ * $max<number>('$price') // Accumulator<number>
829
892
  *
830
- * // Tier 1callback builder (recommended)
831
- * orders.aggregate()
832
- * .groupBy('category', acc => ({ priciest: acc.max('price') }))
893
+ * // Tier 3untyped
894
+ * $max('$price') // Accumulator<unknown>
833
895
  * ```
834
896
  */
835
- declare const $max: <R = unknown>(field: `$${string}`) => Accumulator<R>;
897
+ declare function $max<T extends Record<string, unknown>>(field: FieldRef<T>): Accumulator<FieldRefType<T, FieldRef<T>>>;
898
+ declare function $max<R = unknown>(field: `$${string}`): Accumulator<R>;
836
899
  /**
837
900
  * Returns the first value in each group according to the document order.
838
901
  *
839
- * For full type safety, prefer the callback builder (`acc.first('name')`).
840
- * The standalone form accepts an explicit result type `R` for manual typing.
902
+ * Three tiers of type safety:
903
+ * - **Tier 1 (document type):** `$first<Doc>('$address.city')` resolves via `FieldRefType`.
904
+ * - **Tier 2 (explicit scalar):** `$first<Date>('$createdAt')` — manual result type.
905
+ * - **Tier 3 (untyped):** `$first('$name')` — defaults to `unknown`.
841
906
  *
842
- * @typeParam R - The expected result type (defaults to `unknown`).
843
- * @param field - A `$field` reference.
844
- * @returns An `Accumulator<R>`.
907
+ * For best type safety, prefer the callback builder (`acc.first('name')`).
908
+ *
909
+ * @param field - A `$field` reference (supports dot-paths with document type param).
910
+ * @returns An `Accumulator` with the resolved result type.
845
911
  *
846
912
  * @example
847
913
  * ```ts
914
+ * // Tier 1 — document type with dot-path
915
+ * $first<Doc>('$address.city') // Accumulator<string>
916
+ *
848
917
  * // Tier 2 — explicit result type
849
- * const pipeline = orders.aggregate()
850
- * .sort({ createdAt: 1 })
851
- * .groupBy('status', { earliest: $first<Date>('$createdAt') })
918
+ * $first<Date>('$createdAt') // Accumulator<Date>
852
919
  *
853
- * // Tier 1callback builder (recommended)
854
- * orders.aggregate()
855
- * .sort({ createdAt: 1 })
856
- * .groupBy('status', acc => ({ earliest: acc.first('createdAt') }))
920
+ * // Tier 3untyped
921
+ * $first('$name') // Accumulator<unknown>
857
922
  * ```
858
923
  */
859
- declare const $first: <R = unknown>(field: `$${string}`) => Accumulator<R>;
924
+ declare function $first<T extends Record<string, unknown>>(field: FieldRef<T>): Accumulator<FieldRefType<T, FieldRef<T>>>;
925
+ declare function $first<R = unknown>(field: `$${string}`): Accumulator<R>;
860
926
  /**
861
927
  * Returns the last value in each group according to the document order.
862
928
  *
863
- * For full type safety, prefer the callback builder (`acc.last('name')`).
864
- * The standalone form accepts an explicit result type `R` for manual typing.
929
+ * Three tiers of type safety:
930
+ * - **Tier 1 (document type):** `$last<Doc>('$address.city')` resolves via `FieldRefType`.
931
+ * - **Tier 2 (explicit scalar):** `$last<Date>('$createdAt')` — manual result type.
932
+ * - **Tier 3 (untyped):** `$last('$name')` — defaults to `unknown`.
933
+ *
934
+ * For best type safety, prefer the callback builder (`acc.last('name')`).
865
935
  *
866
- * @typeParam R - The expected result type (defaults to `unknown`).
867
- * @param field - A `$field` reference.
868
- * @returns An `Accumulator<R>`.
936
+ * @param field - A `$field` reference (supports dot-paths with document type param).
937
+ * @returns An `Accumulator` with the resolved result type.
869
938
  *
870
939
  * @example
871
940
  * ```ts
941
+ * // Tier 1 — document type with dot-path
942
+ * $last<Doc>('$address.city') // Accumulator<string>
943
+ *
872
944
  * // Tier 2 — explicit result type
873
- * const pipeline = orders.aggregate()
874
- * .sort({ createdAt: -1 })
875
- * .groupBy('status', { latest: $last<Date>('$createdAt') })
945
+ * $last<Date>('$createdAt') // Accumulator<Date>
876
946
  *
877
- * // Tier 1callback builder (recommended)
878
- * orders.aggregate()
879
- * .sort({ createdAt: -1 })
880
- * .groupBy('status', acc => ({ latest: acc.last('createdAt') }))
947
+ * // Tier 3untyped
948
+ * $last('$name') // Accumulator<unknown>
881
949
  * ```
882
950
  */
883
- declare const $last: <R = unknown>(field: `$${string}`) => Accumulator<R>;
951
+ declare function $last<T extends Record<string, unknown>>(field: FieldRef<T>): Accumulator<FieldRefType<T, FieldRef<T>>>;
952
+ declare function $last<R = unknown>(field: `$${string}`): Accumulator<R>;
884
953
  /**
885
954
  * Pushes values into an array for each group.
886
955
  *
887
956
  * May contain duplicates. Use `$addToSet` for unique values.
888
- * For full type safety, prefer the callback builder (`acc.push('name')`).
889
957
  *
890
- * @typeParam R - The element type (defaults to `unknown`).
891
- * @param field - A `$field` reference.
892
- * @returns An `Accumulator<R[]>`.
958
+ * Three tiers of type safety:
959
+ * - **Tier 1 (document type):** `$push<Doc>('$address.city')` — resolves via `FieldRefType`.
960
+ * - **Tier 2 (explicit scalar):** `$push<string>('$name')` — manual element type.
961
+ * - **Tier 3 (untyped):** `$push('$name')` — defaults to `unknown[]`.
962
+ *
963
+ * For best type safety, prefer the callback builder (`acc.push('name')`).
964
+ *
965
+ * @param field - A `$field` reference (supports dot-paths with document type param).
966
+ * @returns An `Accumulator` with the resolved array result type.
893
967
  *
894
968
  * @example
895
969
  * ```ts
970
+ * // Tier 1 — document type with dot-path
971
+ * $push<Doc>('$address.city') // Accumulator<string[]>
972
+ *
896
973
  * // Tier 2 — explicit element type
897
- * const pipeline = orders.aggregate()
898
- * .groupBy('status', { items: $push<string>('$name') })
974
+ * $push<string>('$name') // Accumulator<string[]>
899
975
  *
900
- * // Tier 1callback builder (recommended)
901
- * orders.aggregate()
902
- * .groupBy('status', acc => ({ items: acc.push('name') }))
976
+ * // Tier 3untyped
977
+ * $push('$name') // Accumulator<unknown[]>
903
978
  * ```
904
979
  */
905
- declare const $push: <R = unknown>(field: `$${string}`) => Accumulator<R[]>;
980
+ declare function $push<T extends Record<string, unknown>>(field: FieldRef<T>): Accumulator<FieldRefType<T, FieldRef<T>>[]>;
981
+ declare function $push<R = unknown>(field: `$${string}`): Accumulator<R[]>;
906
982
  /**
907
983
  * Collects unique values into an array for each group (set semantics).
908
984
  *
909
985
  * Like `$push` but deduplicates.
910
- * For full type safety, prefer the callback builder (`acc.addToSet('tag')`).
911
986
  *
912
- * @typeParam R - The element type (defaults to `unknown`).
913
- * @param field - A `$field` reference.
914
- * @returns An `Accumulator<R[]>`.
987
+ * Three tiers of type safety:
988
+ * - **Tier 1 (document type):** `$addToSet<Doc>('$address.zip')` — resolves via `FieldRefType`.
989
+ * - **Tier 2 (explicit scalar):** `$addToSet<string>('$tag')` — manual element type.
990
+ * - **Tier 3 (untyped):** `$addToSet('$tag')` — defaults to `unknown[]`.
991
+ *
992
+ * For best type safety, prefer the callback builder (`acc.addToSet('tag')`).
993
+ *
994
+ * @param field - A `$field` reference (supports dot-paths with document type param).
995
+ * @returns An `Accumulator` with the resolved array result type.
915
996
  *
916
997
  * @example
917
998
  * ```ts
999
+ * // Tier 1 — document type with dot-path
1000
+ * $addToSet<Doc>('$address.zip') // Accumulator<string[]>
1001
+ *
918
1002
  * // Tier 2 — explicit element type
919
- * const pipeline = orders.aggregate()
920
- * .groupBy('category', { uniqueTags: $addToSet<string>('$tag') })
1003
+ * $addToSet<string>('$tag') // Accumulator<string[]>
921
1004
  *
922
- * // Tier 1callback builder (recommended)
923
- * orders.aggregate()
924
- * .groupBy('category', acc => ({ uniqueTags: acc.addToSet('tag') }))
1005
+ * // Tier 3untyped
1006
+ * $addToSet('$tag') // Accumulator<unknown[]>
925
1007
  * ```
926
1008
  */
927
- declare const $addToSet: <R = unknown>(field: `$${string}`) => Accumulator<R[]>;
1009
+ declare function $addToSet<T extends Record<string, unknown>>(field: FieldRef<T>): Accumulator<FieldRefType<T, FieldRef<T>>[]>;
1010
+ declare function $addToSet<R = unknown>(field: `$${string}`): Accumulator<R[]>;
928
1011
  /**
929
1012
  * Create a typed accumulator builder for use inside `groupBy` callbacks.
930
1013
  *
@@ -1018,11 +1101,11 @@ type Prev = [never, 0, 1, 2];
1018
1101
  * ```ts
1019
1102
  * type User = { address: { city: string; geo: { lat: number; lng: number } } }
1020
1103
  *
1021
- * // DotPaths<User> = 'address.city' | 'address.geo' | 'address.geo.lat' | 'address.geo.lng'
1104
+ * // _DotPaths<User> = 'address.city' | 'address.geo' | 'address.geo.lat' | 'address.geo.lng'
1022
1105
  * ```
1023
1106
  */
1024
- type DotPaths<T, Depth extends number = 3> = Depth extends 0 ? never : {
1025
- [K in keyof T & string]: NonNullable<T[K]> extends ReadonlyArray<unknown> | Date | RegExp | ObjectId ? never : NonNullable<T[K]> extends Record<string, unknown> ? `${K}.${keyof NonNullable<T[K]> & string}` | `${K}.${DotPaths<NonNullable<T[K]>, Prev[Depth]>}` : never;
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;
1026
1109
  }[keyof T & string];
1027
1110
  /**
1028
1111
  * Resolves the value type at a dot-separated path `P` within type `T`.
@@ -1034,11 +1117,11 @@ type DotPaths<T, Depth extends number = 3> = Depth extends 0 ? never : {
1034
1117
  * ```ts
1035
1118
  * type User = { address: { city: string; geo: { lat: number } } }
1036
1119
  *
1037
- * // DotPathType<User, 'address.city'> = string
1038
- * // DotPathType<User, 'address.geo.lat'> = number
1120
+ * // _DotPathType<User, 'address.city'> = string
1121
+ * // _DotPathType<User, 'address.geo.lat'> = number
1039
1122
  * ```
1040
1123
  */
1041
- type DotPathType<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? Rest extends keyof NonNullable<T[K]> ? NonNullable<T[K]>[Rest] : DotPathType<NonNullable<T[K]>, Rest> : never : P extends keyof T ? T[P] : never;
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;
1042
1125
  /**
1043
1126
  * Strict type-safe MongoDB filter query type.
1044
1127
  *
@@ -1078,7 +1161,7 @@ type DotPathType<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K
1078
1161
  type TypedFilter<T> = {
1079
1162
  [K in keyof T]?: T[K] | ComparisonOperators<T[K]>;
1080
1163
  } & {
1081
- [P in DotPaths<T>]?: DotPathType<T, P> | ComparisonOperators<DotPathType<T, P>>;
1164
+ [P in _DotPaths<T>]?: _DotPathType<T, P> | ComparisonOperators<_DotPathType<T, P>>;
1082
1165
  } & {
1083
1166
  /** Joins clauses with a logical AND. Matches documents that satisfy all filters. */
1084
1167
  $and?: TypedFilter<T>[];
@@ -1239,6 +1322,135 @@ type CursorPage<TDoc> = {
1239
1322
  endCursor: string | null;
1240
1323
  };
1241
1324
 
1325
+ type IncludeValue = 1 | true;
1326
+ type ExcludeValue = 0 | false;
1327
+ /**
1328
+ * Inclusion projection — maps document fields to `1 | true`.
1329
+ *
1330
+ * Allows `_id: 0 | false` to suppress the `_id` field from the result.
1331
+ * All other fields only accept inclusion values (`1` or `true`).
1332
+ *
1333
+ * @example
1334
+ * ```ts
1335
+ * type UserInclusion = InclusionProjection<User>
1336
+ * const proj: UserInclusion = { name: 1, email: 1 }
1337
+ * const withoutId: UserInclusion = { name: 1, _id: 0 }
1338
+ * ```
1339
+ */
1340
+ type InclusionProjection<T> = {
1341
+ [K in keyof T & string]?: K extends '_id' ? IncludeValue | ExcludeValue : IncludeValue;
1342
+ };
1343
+ /**
1344
+ * Exclusion projection — maps document fields to `0 | false`.
1345
+ *
1346
+ * Removes the specified fields from the result, keeping all others.
1347
+ *
1348
+ * @example
1349
+ * ```ts
1350
+ * type UserExclusion = ExclusionProjection<User>
1351
+ * const proj: UserExclusion = { email: 0, age: 0 }
1352
+ * ```
1353
+ */
1354
+ type ExclusionProjection<T> = {
1355
+ [K in keyof T & string]?: ExcludeValue;
1356
+ };
1357
+ /**
1358
+ * Union of inclusion or exclusion projection for a document type `T`.
1359
+ *
1360
+ * MongoDB projections must be either all-inclusion or all-exclusion (with the
1361
+ * exception of `_id`, which can always be excluded). This type enforces that
1362
+ * constraint at the type level.
1363
+ *
1364
+ * @example
1365
+ * ```ts
1366
+ * type UserProjection = TypedProjection<User>
1367
+ * const include: UserProjection = { name: 1, email: 1 }
1368
+ * const exclude: UserProjection = { email: 0, age: 0 }
1369
+ * ```
1370
+ */
1371
+ type TypedProjection<T> = InclusionProjection<T> | ExclusionProjection<T>;
1372
+ /** True if any key in `P` (excluding `_id`) has value `1 | true`. */
1373
+ type IsInclusion<P> = true extends {
1374
+ [K in keyof P]: K extends '_id' ? never : P[K] extends IncludeValue ? true : never;
1375
+ }[keyof P] ? true : false;
1376
+ /** Keys of `P` whose value is `1 | true` (excluding `_id`). */
1377
+ type IncludedKeys<P> = {
1378
+ [K in keyof P]: K extends '_id' ? never : P[K] extends IncludeValue ? K : never;
1379
+ }[keyof P];
1380
+ /** Keys of `P` whose value is `0 | false`. */
1381
+ type ExcludedKeys<P> = {
1382
+ [K in keyof P]: P[K] extends ExcludeValue ? K : never;
1383
+ }[keyof P];
1384
+ /** True if `P` has `_id` set to `0 | false`. */
1385
+ type IsIdSuppressed<P> = '_id' extends keyof P ? P['_id'] extends ExcludeValue ? true : false : false;
1386
+ /**
1387
+ * Computes the result type after applying a projection `P` to document type `T`.
1388
+ *
1389
+ * For inclusion projections (`{ name: 1 }`), returns only the included fields
1390
+ * plus `_id` (unless `_id: 0` is specified). For exclusion projections
1391
+ * (`{ email: 0 }`), returns all fields except the excluded ones.
1392
+ *
1393
+ * @example
1394
+ * ```ts
1395
+ * type User = { _id: ObjectId; name: string; email: string; age: number }
1396
+ *
1397
+ * // Inclusion: picks name + _id
1398
+ * type A = ProjectionResult<User, { name: 1 }>
1399
+ * // ^? { _id: ObjectId; name: string }
1400
+ *
1401
+ * // Inclusion with _id suppressed
1402
+ * type B = ProjectionResult<User, { name: 1; _id: 0 }>
1403
+ * // ^? { name: string }
1404
+ *
1405
+ * // Exclusion: drops email
1406
+ * type C = ProjectionResult<User, { email: 0 }>
1407
+ * // ^? { _id: ObjectId; name: string; age: number }
1408
+ * ```
1409
+ */
1410
+ type ProjectionResult<T, P> = IsInclusion<P> extends true ? IsIdSuppressed<P> extends true ? Prettify<Pick<T, IncludedKeys<P> & keyof T>> : Prettify<Pick<T, (IncludedKeys<P> | '_id') & keyof T>> : IsIdSuppressed<P> extends true ? Prettify<Omit<T, ExcludedKeys<P>>> : Prettify<Omit<T, Exclude<ExcludedKeys<P>, '_id'>>>;
1411
+ /**
1412
+ * Returns `true` if the projection is an inclusion projection.
1413
+ *
1414
+ * A projection is considered inclusion when any key other than `_id` has
1415
+ * a value of `1` or `true`. This mirrors the MongoDB rule: you cannot mix
1416
+ * inclusion and exclusion (except for `_id`, which may always be suppressed).
1417
+ *
1418
+ * @example
1419
+ * ```ts
1420
+ * isInclusionProjection({ name: 1 }) // true
1421
+ * isInclusionProjection({ name: 1, _id: 0 }) // true
1422
+ * isInclusionProjection({ email: 0 }) // false
1423
+ * ```
1424
+ */
1425
+ declare function isInclusionProjection(projection: Record<string, 0 | 1 | boolean>): boolean;
1426
+ /**
1427
+ * Derives a scoped Zod schema by applying a MongoDB-style projection.
1428
+ *
1429
+ * For inclusion projections (`{ name: 1, email: 1 }`), returns a schema with
1430
+ * only the specified fields plus `_id` (unless `_id: 0`). For exclusion
1431
+ * projections (`{ email: 0 }`), returns a schema with the specified fields
1432
+ * removed.
1433
+ *
1434
+ * Keys not present in the original schema are silently ignored.
1435
+ *
1436
+ * @example
1437
+ * ```ts
1438
+ * const userSchema = z.object({
1439
+ * _id: z.string(),
1440
+ * name: z.string(),
1441
+ * email: z.string(),
1442
+ * age: z.number(),
1443
+ * })
1444
+ *
1445
+ * // Inclusion: picks name + email + _id
1446
+ * const projected = deriveProjectedSchema(userSchema, { name: 1, email: 1 })
1447
+ *
1448
+ * // Exclusion: drops email
1449
+ * const excluded = deriveProjectedSchema(userSchema, { email: 0 })
1450
+ * ```
1451
+ */
1452
+ declare function deriveProjectedSchema(schema: z.ZodObject<z.core.$ZodShape>, projection: Record<string, 0 | 1 | boolean>): z.ZodObject<z.core.$ZodShape>;
1453
+
1242
1454
  /**
1243
1455
  * Type-safe sort specification for a document type.
1244
1456
  *
@@ -1263,6 +1475,7 @@ type TypedSort<T> = Partial<Record<keyof T & string, 1 | -1>>;
1263
1475
  *
1264
1476
  * @typeParam TDef - The collection definition type, used to infer the document type.
1265
1477
  * @typeParam TIndexNames - Union of declared index names accepted by `.hint()`.
1478
+ * @typeParam TOutput - The output document type, narrowed by `.project()`.
1266
1479
  *
1267
1480
  * @example
1268
1481
  * ```ts
@@ -1272,7 +1485,7 @@ type TypedSort<T> = Partial<Record<keyof T & string, 1 | -1>>;
1272
1485
  * .toArray()
1273
1486
  * ```
1274
1487
  */
1275
- declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends string = string> {
1488
+ declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends string = string, TOutput = InferDocument<TDef>> {
1276
1489
  /** @internal */
1277
1490
  private cursor;
1278
1491
  /** @internal */
@@ -1288,6 +1501,8 @@ declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends st
1288
1501
  /** @internal */
1289
1502
  private sortSpec;
1290
1503
  /** @internal */
1504
+ private projectedSchema;
1505
+ /** @internal */
1291
1506
  constructor(cursor: FindCursor<InferDocument<TDef>>, definition: TDef, mode: ValidationMode | false, nativeCollection: Collection<InferDocument<TDef>>, filter: any);
1292
1507
  /**
1293
1508
  * Set the sort order for the query.
@@ -1348,6 +1563,33 @@ declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends st
1348
1563
  * ```
1349
1564
  */
1350
1565
  hint(indexName: TIndexNames): this;
1566
+ /**
1567
+ * Apply a projection to narrow the returned fields.
1568
+ *
1569
+ * Inclusion projections (`{ name: 1 }`) return only the specified fields
1570
+ * plus `_id` (unless `_id: 0`). Exclusion projections (`{ email: 0 }`)
1571
+ * return all fields except those excluded.
1572
+ *
1573
+ * The cursor's output type is narrowed at compile time. A derived Zod
1574
+ * schema is built for runtime validation of the projected fields.
1575
+ *
1576
+ * Projects from the original document type, not from a previous projection.
1577
+ * Calling `.project()` twice overrides the previous projection.
1578
+ *
1579
+ * @param spec - Type-safe projection document.
1580
+ * @returns A new cursor with the narrowed output type.
1581
+ *
1582
+ * @example
1583
+ * ```ts
1584
+ * const names = await find(users, {})
1585
+ * .project({ name: 1 })
1586
+ * .sort({ name: 1 })
1587
+ * .toArray()
1588
+ * // names[0].name ✓
1589
+ * // names[0].email TS error
1590
+ * ```
1591
+ */
1592
+ project<P extends TypedProjection<InferDocument<TDef>>>(spec: P): TypedFindCursor<TDef, TIndexNames, Prettify<ProjectionResult<InferDocument<TDef>, P>>>;
1351
1593
  /**
1352
1594
  * Execute the query with offset-based pagination, returning a page of documents
1353
1595
  * with total count and navigation metadata.
@@ -1367,7 +1609,7 @@ declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends st
1367
1609
  * console.log(page.total, page.totalPages, page.hasNext)
1368
1610
  * ```
1369
1611
  */
1370
- paginate(opts: OffsetPaginateOptions): Promise<OffsetPage<InferDocument<TDef>>>;
1612
+ paginate(opts: OffsetPaginateOptions): Promise<OffsetPage<TOutput>>;
1371
1613
  /**
1372
1614
  * Execute the query with cursor-based pagination, returning a page of documents
1373
1615
  * with opaque cursors for forward/backward navigation.
@@ -1388,7 +1630,7 @@ declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends st
1388
1630
  * .paginate({ cursor: first.endCursor, limit: 10 })
1389
1631
  * ```
1390
1632
  */
1391
- paginate(opts: CursorPaginateOptions): Promise<CursorPage<InferDocument<TDef>>>;
1633
+ paginate(opts: CursorPaginateOptions): Promise<CursorPage<TOutput>>;
1392
1634
  /** @internal Offset pagination implementation. */
1393
1635
  private offsetPaginate;
1394
1636
  /** @internal Cursor pagination implementation. */
@@ -1407,7 +1649,7 @@ declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends st
1407
1649
  * const admins = await find(users, { role: 'admin' }).toArray()
1408
1650
  * ```
1409
1651
  */
1410
- toArray(): Promise<InferDocument<TDef>[]>;
1652
+ toArray(): Promise<TOutput[]>;
1411
1653
  /**
1412
1654
  * Async iterator for streaming documents one at a time.
1413
1655
  *
@@ -1424,17 +1666,33 @@ declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends st
1424
1666
  * }
1425
1667
  * ```
1426
1668
  */
1427
- [Symbol.asyncIterator](): AsyncGenerator<InferDocument<TDef>>;
1669
+ [Symbol.asyncIterator](): AsyncGenerator<TOutput>;
1428
1670
  /** @internal Validate a single raw document against the schema. */
1429
1671
  private validateDoc;
1430
1672
  }
1431
1673
 
1432
1674
  /**
1433
- * Options for {@link findOne} and {@link findOneOrThrow}.
1675
+ * Options for {@link findOne} and {@link findOneOrThrow} without projection.
1434
1676
  */
1435
1677
  type FindOneOptions = {
1436
- /** MongoDB projection include (`1`) or exclude (`0`) fields. Typed projections deferred to v1.0. */
1437
- project?: Record<string, 0 | 1>;
1678
+ /** Override the collection-level validation mode, or `false` to skip validation entirely. */
1679
+ validate?: ValidationMode | false;
1680
+ };
1681
+ /**
1682
+ * Options for projected {@link findOne} and {@link findOneOrThrow} queries.
1683
+ *
1684
+ * @typeParam T - The document type.
1685
+ * @typeParam P - The projection document type.
1686
+ *
1687
+ * @example
1688
+ * ```ts
1689
+ * const user = await findOne(users, { name: 'Ada' }, { project: { name: 1 } })
1690
+ * // ^? { _id: ObjectId; name: string } | null
1691
+ * ```
1692
+ */
1693
+ type FindOneProjectionOptions<T, P extends TypedProjection<T>> = {
1694
+ /** Type-safe MongoDB projection — include (`1 | true`) or exclude (`0 | false`) fields. */
1695
+ project: P;
1438
1696
  /** Override the collection-level validation mode, or `false` to skip validation entirely. */
1439
1697
  validate?: ValidationMode | false;
1440
1698
  };
@@ -1447,7 +1705,7 @@ type FindOneOptions = {
1447
1705
  *
1448
1706
  * @param handle - The collection handle to query.
1449
1707
  * @param filter - Type-safe filter to match documents.
1450
- * @param options - Optional projection and validation overrides.
1708
+ * @param options - Optional validation overrides.
1451
1709
  * @returns The matched document, or `null` if no document matches.
1452
1710
  * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1453
1711
  *
@@ -1458,6 +1716,26 @@ type FindOneOptions = {
1458
1716
  * ```
1459
1717
  */
1460
1718
  declare function findOne<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef> | null>;
1719
+ /**
1720
+ * Find a single document matching the filter with a type-safe projection.
1721
+ *
1722
+ * Returns only the fields specified by the projection. The return type is
1723
+ * narrowed to reflect which fields are included or excluded.
1724
+ *
1725
+ * @param handle - The collection handle to query.
1726
+ * @param filter - Type-safe filter to match documents.
1727
+ * @param options - Projection and optional validation overrides.
1728
+ * @returns The projected document, or `null` if no document matches.
1729
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1730
+ *
1731
+ * @example
1732
+ * ```ts
1733
+ * const user = await findOne(users, { name: 'Ada' }, { project: { name: 1 } })
1734
+ * if (user) console.log(user.name) // typed as string
1735
+ * // user.role would be a type error — not in the projection
1736
+ * ```
1737
+ */
1738
+ declare function findOne<TDef extends AnyCollection, P extends TypedProjection<InferDocument<TDef>>>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options: FindOneProjectionOptions<InferDocument<TDef>, P>): Promise<Prettify<ProjectionResult<InferDocument<TDef>, P>> | null>;
1461
1739
  /**
1462
1740
  * Find a single document matching the filter, or throw if none exists.
1463
1741
  *
@@ -1466,7 +1744,7 @@ declare function findOne<TDef extends AnyCollection>(handle: CollectionHandle<TD
1466
1744
  *
1467
1745
  * @param handle - The collection handle to query.
1468
1746
  * @param filter - Type-safe filter to match documents.
1469
- * @param options - Optional projection and validation overrides.
1747
+ * @param options - Optional validation overrides.
1470
1748
  * @returns The matched document (never null).
1471
1749
  * @throws {ZodmonNotFoundError} When no document matches the filter.
1472
1750
  * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
@@ -1478,6 +1756,28 @@ declare function findOne<TDef extends AnyCollection>(handle: CollectionHandle<TD
1478
1756
  * ```
1479
1757
  */
1480
1758
  declare function findOneOrThrow<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef>>;
1759
+ /**
1760
+ * Find a single document matching the filter with a type-safe projection, or throw if none exists.
1761
+ *
1762
+ * Returns only the fields specified by the projection. The return type is
1763
+ * narrowed to reflect which fields are included or excluded. Throws
1764
+ * {@link ZodmonNotFoundError} instead of returning `null`.
1765
+ *
1766
+ * @param handle - The collection handle to query.
1767
+ * @param filter - Type-safe filter to match documents.
1768
+ * @param options - Projection and optional validation overrides.
1769
+ * @returns The projected document (never null).
1770
+ * @throws {ZodmonNotFoundError} When no document matches the filter.
1771
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1772
+ *
1773
+ * @example
1774
+ * ```ts
1775
+ * const user = await findOneOrThrow(users, { name: 'Ada' }, { project: { name: 1 } })
1776
+ * console.log(user.name) // typed as string
1777
+ * // user.role would be a type error — not in the projection
1778
+ * ```
1779
+ */
1780
+ declare function findOneOrThrow<TDef extends AnyCollection, P extends TypedProjection<InferDocument<TDef>>>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options: FindOneProjectionOptions<InferDocument<TDef>, P>): Promise<Prettify<ProjectionResult<InferDocument<TDef>, P>>>;
1481
1781
  /**
1482
1782
  * Options for {@link find}.
1483
1783
  */
@@ -1485,6 +1785,25 @@ type FindOptions = {
1485
1785
  /** Override the collection-level validation mode, or `false` to skip validation entirely. */
1486
1786
  validate?: ValidationMode | false;
1487
1787
  };
1788
+ /**
1789
+ * Options for projected {@link find} queries.
1790
+ *
1791
+ * @typeParam T - The document type.
1792
+ * @typeParam P - The projection document type.
1793
+ *
1794
+ * @example
1795
+ * ```ts
1796
+ * const admins = await find(users, { role: 'admin' }, { project: { name: 1 } })
1797
+ * .toArray()
1798
+ * // ^? Array<{ _id: ObjectId; name: string }>
1799
+ * ```
1800
+ */
1801
+ type FindProjectionOptions<T, P extends TypedProjection<T>> = {
1802
+ /** Type-safe MongoDB projection — include (`1 | true`) or exclude (`0 | false`) fields. */
1803
+ project: P;
1804
+ /** Override the collection-level validation mode, or `false` to skip validation entirely. */
1805
+ validate?: ValidationMode | false;
1806
+ };
1488
1807
  /**
1489
1808
  * Find all documents matching the filter, returning a chainable typed cursor.
1490
1809
  *
@@ -1516,6 +1835,28 @@ type FindOptions = {
1516
1835
  * ```
1517
1836
  */
1518
1837
  declare function find<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOptions): TypedFindCursor<TDef, IndexNames<TDef>>;
1838
+ /**
1839
+ * Find all documents matching the filter with a type-safe projection, returning a chainable typed cursor.
1840
+ *
1841
+ * The cursor is lazy — no query is executed until a terminal method
1842
+ * (`toArray`, `for await`) is called. The return type is narrowed to
1843
+ * reflect which fields are included or excluded by the projection.
1844
+ *
1845
+ * @param handle - The collection handle to query.
1846
+ * @param filter - Type-safe filter to match documents.
1847
+ * @param options - Projection and optional validation overrides.
1848
+ * @returns A typed cursor whose output type matches the projection.
1849
+ *
1850
+ * @example
1851
+ * ```ts
1852
+ * const names = await find(users, { role: 'admin' }, { project: { name: 1 } })
1853
+ * .sort({ name: 1 })
1854
+ * .toArray()
1855
+ * // names[0].name — string
1856
+ * // names[0].role — type error, not in projection
1857
+ * ```
1858
+ */
1859
+ 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>>>;
1519
1860
 
1520
1861
  /**
1521
1862
  * Extracts the element type from an array type.
@@ -1541,7 +1882,7 @@ type ArrayElement<T> = T extends ReadonlyArray<infer E> ? E : never;
1541
1882
  type SetFields<T> = {
1542
1883
  [K in keyof T]?: T[K];
1543
1884
  } & {
1544
- [P in DotPaths<T>]?: DotPathType<T, P>;
1885
+ [P in _DotPaths<T>]?: _DotPathType<T, P>;
1545
1886
  };
1546
1887
  /**
1547
1888
  * Fields valid for the `$inc` operator — only number-typed fields.
@@ -1556,7 +1897,7 @@ type SetFields<T> = {
1556
1897
  type IncFields<T> = {
1557
1898
  [K in keyof T as NonNullable<T[K]> extends number ? K : never]?: number;
1558
1899
  } & {
1559
- [P in DotPaths<T> as DotPathType<T, P> extends number ? P : never]?: number;
1900
+ [P in _DotPaths<T> as _DotPathType<T, P> extends number ? P : never]?: number;
1560
1901
  };
1561
1902
  /**
1562
1903
  * Modifiers for the `$push` operator.
@@ -1965,7 +2306,7 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
1965
2306
  * back to the collection-level default (which defaults to `'strict'`).
1966
2307
  *
1967
2308
  * @param filter - Type-safe filter to match documents.
1968
- * @param options - Optional projection and validation overrides.
2309
+ * @param options - Optional validation overrides.
1969
2310
  * @returns The matched document, or `null` if no document matches.
1970
2311
  * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1971
2312
  *
@@ -1977,6 +2318,25 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
1977
2318
  * ```
1978
2319
  */
1979
2320
  findOne(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef> | null>;
2321
+ /**
2322
+ * Find a single document matching the filter with a type-safe projection.
2323
+ *
2324
+ * Returns only the fields specified by the projection. The return type is
2325
+ * narrowed to reflect which fields are included or excluded.
2326
+ *
2327
+ * @param filter - Type-safe filter to match documents.
2328
+ * @param options - Projection and optional validation overrides.
2329
+ * @returns The projected document, or `null` if no document matches.
2330
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
2331
+ *
2332
+ * @example
2333
+ * ```ts
2334
+ * const users = db.use(Users)
2335
+ * const user = await users.findOne({ name: 'Ada' }, { project: { name: 1 } })
2336
+ * if (user) console.log(user.name) // typed as string
2337
+ * ```
2338
+ */
2339
+ findOne<P extends TypedProjection<InferDocument<TDef>>>(filter: TypedFilter<InferDocument<TDef>>, options: FindOneProjectionOptions<InferDocument<TDef>, P>): Promise<Prettify<ProjectionResult<InferDocument<TDef>, P>> | null>;
1980
2340
  /**
1981
2341
  * Find a single document matching the filter, or throw if none exists.
1982
2342
  *
@@ -1984,7 +2344,7 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
1984
2344
  * instead of returning `null` when no document matches the filter.
1985
2345
  *
1986
2346
  * @param filter - Type-safe filter to match documents.
1987
- * @param options - Optional projection and validation overrides.
2347
+ * @param options - Optional validation overrides.
1988
2348
  * @returns The matched document (never null).
1989
2349
  * @throws {ZodmonNotFoundError} When no document matches the filter.
1990
2350
  * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
@@ -1997,6 +2357,27 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
1997
2357
  * ```
1998
2358
  */
1999
2359
  findOneOrThrow(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneOptions): Promise<InferDocument<TDef>>;
2360
+ /**
2361
+ * Find a single document matching the filter with a type-safe projection, or throw if none exists.
2362
+ *
2363
+ * Returns only the fields specified by the projection. The return type is
2364
+ * narrowed to reflect which fields are included or excluded. Throws
2365
+ * {@link ZodmonNotFoundError} instead of returning `null`.
2366
+ *
2367
+ * @param filter - Type-safe filter to match documents.
2368
+ * @param options - Projection and optional validation overrides.
2369
+ * @returns The projected document (never null).
2370
+ * @throws {ZodmonNotFoundError} When no document matches the filter.
2371
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
2372
+ *
2373
+ * @example
2374
+ * ```ts
2375
+ * const users = db.use(Users)
2376
+ * const user = await users.findOneOrThrow({ name: 'Ada' }, { project: { name: 1 } })
2377
+ * console.log(user.name) // typed as string
2378
+ * ```
2379
+ */
2380
+ findOneOrThrow<P extends TypedProjection<InferDocument<TDef>>>(filter: TypedFilter<InferDocument<TDef>>, options: FindOneProjectionOptions<InferDocument<TDef>, P>): Promise<Prettify<ProjectionResult<InferDocument<TDef>, P>>>;
2000
2381
  /**
2001
2382
  * Find all documents matching the filter, returning a chainable typed cursor.
2002
2383
  *
@@ -2018,6 +2399,24 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
2018
2399
  * ```
2019
2400
  */
2020
2401
  find(filter: TypedFilter<InferDocument<TDef>>, options?: FindOptions): TypedFindCursor<TDef, IndexNames<TDef>>;
2402
+ /**
2403
+ * Find all documents matching the filter with a type-safe projection, returning a chainable typed cursor.
2404
+ *
2405
+ * The return type is narrowed to reflect which fields are included or excluded.
2406
+ *
2407
+ * @param filter - Type-safe filter to match documents.
2408
+ * @param options - Projection and optional validation overrides.
2409
+ * @returns A typed cursor whose output type matches the projection.
2410
+ *
2411
+ * @example
2412
+ * ```ts
2413
+ * const users = db.use(Users)
2414
+ * const names = await users.find({ role: 'admin' }, { project: { name: 1 } })
2415
+ * .toArray()
2416
+ * // names[0].name — string
2417
+ * ```
2418
+ */
2419
+ find<P extends TypedProjection<InferDocument<TDef>>>(filter: TypedFilter<InferDocument<TDef>>, options: FindProjectionOptions<InferDocument<TDef>, P>): TypedFindCursor<TDef, IndexNames<TDef>, Prettify<ProjectionResult<InferDocument<TDef>, P>>>;
2021
2420
  /**
2022
2421
  * Update a single document matching the filter.
2023
2422
  *
@@ -2481,8 +2880,8 @@ declare class AggregatePipeline<TDef extends AnyCollection, TOutput> {
2481
2880
  * .toArray()
2482
2881
  * ```
2483
2882
  */
2484
- groupBy<K extends keyof TOutput & string, TAccum extends Record<string, Accumulator>>(field: K, accumulators: ((acc: AccumulatorBuilder<TOutput>) => TAccum) | TAccum): AggregatePipeline<TDef, GroupByResult<TOutput, K, TAccum>>;
2485
- groupBy<K extends keyof TOutput & string, TAccum extends Record<string, Accumulator>>(field: K[], accumulators: ((acc: AccumulatorBuilder<TOutput>) => TAccum) | TAccum): AggregatePipeline<TDef, GroupByCompoundResult<TOutput, K, TAccum>>;
2883
+ 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
+ groupBy<K extends DotPath<TOutput>, TAccum extends Record<string, Accumulator>>(field: K[], accumulators: ((acc: AccumulatorBuilder<TOutput>) => TAccum) | TAccum): AggregatePipeline<TDef, GroupByCompoundResult<TOutput, K, TAccum>>;
2486
2885
  /**
2487
2886
  * Add new fields or overwrite existing ones in the output documents.
2488
2887
  *
@@ -3918,4 +4317,4 @@ type UpdateFilterOf<TDef extends AnyCollection> = TypedUpdateFilter<InferDocumen
3918
4317
  */
3919
4318
  type SortOf<TDef extends AnyCollection> = TypedSort<InferDocument<TDef>>;
3920
4319
 
3921
- export { $, $addToSet, $and, $avg, $count, $eq, $exists, $first, $gt, $gte, $in, $last, $lt, $lte, $max, $min, $ne, $nin, $nor, $not, $or, $push, $regex, $sum, type Accumulator, type AccumulatorBuilder, type AddToSetEach, type AddToSetFields, AggregatePipeline, type AnyCollection, type ArrayElement, type CollectionDefinition, CollectionHandle, type CollectionName, type CollectionOptions, type ComparisonOperators, type CompoundIndexDefinition, type CurrentDateFields, type CursorPage, type CursorPaginateOptions, Database, type DotPathType, type DotPaths, type Expression, type ExpressionBuilder, type ExtractRefCollection, type FieldIndexDefinition, type FieldRef, type FieldRefType, type FilterOf, type FindOneAndDeleteOptions, type FindOneAndUpdateOptions, type FindOneOptions, type FindOptions, type GroupByCompoundResult, type GroupByResult, type HandleOf, type IncFields, IndexBuilder, type IndexMetadata, type IndexNames, type IndexOptions, type IndexSpec, type InferAccumulator, type InferAccumulators, type InferAddedFields, type InferDocument, type InferExpression, type InferInsert, type NarrowFromFilter, type OffsetPage, type OffsetPaginateOptions, type PopFields, type Prettify, type PullFields, type PushFields, type PushModifiers, type RefFields, type RefMarker, type RefMetadata, type RenameFields, type ResolvedShape, type SetFields, type SortOf, type StaleIndex, type SyncIndexesOptions, type SyncIndexesResult, type TypedFilter, TypedFindCursor, type TypedSort, type TypedUpdateFilter, type UnsetFields, type UnwindResult, type UpdateFilterOf, type UpdateOptions, type ValidationMode, type ZodObjectId, ZodmonAuthError, ZodmonBulkWriteError, ZodmonDocValidationError, ZodmonDuplicateKeyError, ZodmonError, ZodmonIndexError, ZodmonNetworkError, ZodmonNotFoundError, ZodmonQueryError, ZodmonTimeoutError, ZodmonValidationError, ZodmonWriteConflictError, aggregate, checkUnindexedFields, collection, createAccumulatorBuilder, createClient, createExpressionBuilder, deleteMany, deleteOne, extractComparableOptions, extractDbName, extractFieldIndexes, find, findOne, findOneAndDelete, findOneAndUpdate, findOneOrThrow, generateIndexName, getIndexMetadata, getRefMetadata, index, insertMany, insertOne, isOid, objectId, oid, raw, serializeIndexKey, syncIndexes, toCompoundIndexSpec, toFieldIndexSpec, updateMany, updateOne, wrapMongoError };
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 };