@zodmon/core 0.7.0 → 0.9.0

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