@zodmon/core 0.7.0 → 0.8.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
@@ -252,17 +252,132 @@ 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 TShape - The Zod shape defining document fields.
257
+ * @typeParam TIndexes - The compound indexes array type, preserving literal name types.
255
258
  */
256
- type CollectionDefinition<TShape extends z.core.$ZodShape = z.core.$ZodShape> = {
259
+ type CollectionDefinition<TShape extends z.core.$ZodShape = z.core.$ZodShape, TIndexes extends readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[] = readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[]> = {
257
260
  readonly name: string;
258
261
  readonly schema: z.ZodObject<ResolvedShape<TShape>>;
259
262
  readonly shape: TShape;
260
263
  readonly fieldIndexes: FieldIndexDefinition[];
261
- readonly compoundIndexes: CompoundIndexDefinition<Extract<keyof TShape, string>>[];
264
+ readonly compoundIndexes: TIndexes;
262
265
  readonly options: Required<Pick<CollectionOptions, 'validation'>> & Omit<CollectionOptions, 'indexes' | 'validation'>;
263
266
  };
264
267
  /** Erased collection type for use in generic contexts. */
265
268
  type AnyCollection = CollectionDefinition<z.core.$ZodShape>;
269
+ /**
270
+ * Extract declared index names from a collection definition.
271
+ *
272
+ * Walks the `compoundIndexes` tuple and extracts the literal `name` string
273
+ * from each index that declared one via `.name()`. Falls back to `string`
274
+ * when no compound indexes have names, allowing any string as a hint.
275
+ *
276
+ * @example
277
+ * ```ts
278
+ * const Users = collection('users', { email: z.string() }, {
279
+ * indexes: [
280
+ * index({ email: 1 }).name('email_idx'),
281
+ * ],
282
+ * })
283
+ * type Names = IndexNames<typeof Users> // 'email_idx'
284
+ * ```
285
+ */
286
+ type IndexNames<TDef extends {
287
+ readonly compoundIndexes: readonly {
288
+ options?: {
289
+ name?: string;
290
+ };
291
+ }[];
292
+ }> = ExtractNames<TDef['compoundIndexes']> extends never ? string : ExtractNames<TDef['compoundIndexes']>;
293
+ /** @internal Helper that extracts the `name` literal from each index in a tuple. */
294
+ type ExtractNames<T extends readonly {
295
+ options?: {
296
+ name?: string;
297
+ };
298
+ }[]> = {
299
+ [K in keyof T]: T[K] extends {
300
+ readonly options: {
301
+ readonly name: infer N;
302
+ };
303
+ } ? N extends string ? N : never : never;
304
+ }[number];
305
+
306
+ /**
307
+ * Options controlling how {@link syncIndexes} behaves.
308
+ *
309
+ * @example
310
+ * ```ts
311
+ * await users.syncIndexes({ dryRun: true })
312
+ * await users.syncIndexes({ dropOrphaned: true })
313
+ * ```
314
+ */
315
+ type SyncIndexesOptions = {
316
+ /**
317
+ * When `true`, compute the diff without actually creating, dropping, or
318
+ * modifying any indexes. The returned {@link SyncIndexesResult} shows what
319
+ * *would* happen.
320
+ */
321
+ dryRun?: boolean;
322
+ /**
323
+ * When `true`, drop indexes that exist in MongoDB but are not declared in
324
+ * the schema. Also drops and recreates stale indexes (same key, different
325
+ * options). When `false` (the default), orphaned and stale indexes are
326
+ * reported but left untouched.
327
+ */
328
+ dropOrphaned?: boolean;
329
+ };
330
+ /**
331
+ * Describes an index whose key matches a desired index but whose options differ.
332
+ *
333
+ * Returned in {@link SyncIndexesResult.stale} so the caller can decide whether
334
+ * to manually reconcile or re-run with `dropOrphaned: true`.
335
+ *
336
+ * @example
337
+ * ```ts
338
+ * const result = await users.syncIndexes()
339
+ * for (const s of result.stale) {
340
+ * console.log(`${s.name}: key=${JSON.stringify(s.key)}`)
341
+ * console.log(` existing=${JSON.stringify(s.existing)}`)
342
+ * console.log(` desired=${JSON.stringify(s.desired)}`)
343
+ * }
344
+ * ```
345
+ */
346
+ type StaleIndex = {
347
+ /** The MongoDB index name (e.g. `'email_1'`). */
348
+ name: string;
349
+ /** The index key spec (e.g. `{ email: 1 }`). */
350
+ key: Record<string, 1 | -1 | 'text'>;
351
+ /** The relevant options currently set on the existing index. */
352
+ existing: Record<string, unknown>;
353
+ /** The options the schema declares for this index. */
354
+ desired: Record<string, unknown>;
355
+ };
356
+ /**
357
+ * The result of a {@link syncIndexes} call.
358
+ *
359
+ * Every array contains index names. `stale` contains full details so the
360
+ * caller can inspect the mismatch.
361
+ *
362
+ * @example
363
+ * ```ts
364
+ * const result = await users.syncIndexes()
365
+ * console.log('created:', result.created)
366
+ * console.log('dropped:', result.dropped)
367
+ * console.log('skipped:', result.skipped)
368
+ * console.log('stale:', result.stale.map(s => s.name))
369
+ * ```
370
+ */
371
+ type SyncIndexesResult = {
372
+ /** Names of indexes that were created (or would be created in dryRun mode). */
373
+ created: string[];
374
+ /** Names of indexes that were dropped (or would be dropped in dryRun mode). */
375
+ dropped: string[];
376
+ /** Names of indexes that already existed with matching options — no action taken. */
377
+ skipped: string[];
378
+ /** Indexes whose key matches a desired spec but whose options differ. */
379
+ stale: StaleIndex[];
380
+ };
266
381
 
267
382
  /**
268
383
  * Comparison operators for a field value of type `V`.
@@ -554,7 +669,7 @@ type TypedSort<T> = Partial<Record<keyof T & string, 1 | -1>>;
554
669
  /**
555
670
  * Type-safe cursor wrapping MongoDB's `FindCursor`.
556
671
  *
557
- * Provides chainable query modifiers (`sort`, `skip`, `limit`) that return
672
+ * Provides chainable query modifiers (`sort`, `skip`, `limit`, `hint`) that return
558
673
  * `this` for fluent chaining, and terminal methods (`toArray`,
559
674
  * `[Symbol.asyncIterator]`) that validate each document against the
560
675
  * collection's Zod schema before returning.
@@ -562,6 +677,7 @@ type TypedSort<T> = Partial<Record<keyof T & string, 1 | -1>>;
562
677
  * Created by {@link find} — do not construct directly.
563
678
  *
564
679
  * @typeParam TDef - The collection definition type, used to infer the document type.
680
+ * @typeParam TIndexNames - Union of declared index names accepted by `.hint()`.
565
681
  *
566
682
  * @example
567
683
  * ```ts
@@ -571,7 +687,7 @@ type TypedSort<T> = Partial<Record<keyof T & string, 1 | -1>>;
571
687
  * .toArray()
572
688
  * ```
573
689
  */
574
- declare class TypedFindCursor<TDef extends AnyCollection> {
690
+ declare class TypedFindCursor<TDef extends AnyCollection, TIndexNames extends string = string> {
575
691
  /** @internal */
576
692
  private cursor;
577
693
  /** @internal */
@@ -627,6 +743,26 @@ declare class TypedFindCursor<TDef extends AnyCollection> {
627
743
  * ```
628
744
  */
629
745
  limit(n: number): this;
746
+ /**
747
+ * Force the query optimizer to use the specified index.
748
+ *
749
+ * Only accepts index names that were declared via `.name()` in the
750
+ * collection definition. If no named indexes exist, any string is accepted.
751
+ *
752
+ * @param indexName - The name of a declared compound index.
753
+ * @returns `this` for chaining.
754
+ *
755
+ * @example
756
+ * ```ts
757
+ * const Users = collection('users', { email: z.string(), role: z.string() }, {
758
+ * indexes: [index({ email: 1, role: -1 }).name('email_role_idx')],
759
+ * })
760
+ * const admins = await users.find({ role: 'admin' })
761
+ * .hint('email_role_idx')
762
+ * .toArray()
763
+ * ```
764
+ */
765
+ hint(indexName: TIndexNames): this;
630
766
  /**
631
767
  * Execute the query with offset-based pagination, returning a page of documents
632
768
  * with total count and navigation metadata.
@@ -794,7 +930,7 @@ type FindOptions = {
794
930
  * }
795
931
  * ```
796
932
  */
797
- declare function find<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOptions): TypedFindCursor<TDef>;
933
+ declare function find<TDef extends AnyCollection>(handle: CollectionHandle<TDef>, filter: TypedFilter<InferDocument<TDef>>, options?: FindOptions): TypedFindCursor<TDef, IndexNames<TDef>>;
798
934
 
799
935
  /**
800
936
  * Extracts the element type from an array type.
@@ -1220,7 +1356,7 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
1220
1356
  * .toArray()
1221
1357
  * ```
1222
1358
  */
1223
- find(filter: TypedFilter<InferDocument<TDef>>, options?: FindOptions): TypedFindCursor<TDef>;
1359
+ find(filter: TypedFilter<InferDocument<TDef>>, options?: FindOptions): TypedFindCursor<TDef, IndexNames<TDef>>;
1224
1360
  /**
1225
1361
  * Update a single document matching the filter.
1226
1362
  *
@@ -1342,6 +1478,34 @@ declare class CollectionHandle<TDef extends AnyCollection = AnyCollection> {
1342
1478
  * ```
1343
1479
  */
1344
1480
  findOneAndDelete(filter: TypedFilter<InferDocument<TDef>>, options?: FindOneAndDeleteOptions): Promise<InferDocument<TDef> | null>;
1481
+ /**
1482
+ * Synchronize the indexes declared in this collection's schema with MongoDB.
1483
+ *
1484
+ * Compares the desired indexes (from field-level `.index()` / `.unique()` /
1485
+ * `.text()` / `.expireAfter()` and compound `indexes` in collection options)
1486
+ * with the indexes that currently exist in MongoDB, then creates, drops, or
1487
+ * reports differences depending on the options.
1488
+ *
1489
+ * @param options - Optional sync behavior (dryRun, dropOrphaned).
1490
+ * @returns A summary of created, dropped, skipped, and stale indexes.
1491
+ *
1492
+ * @example
1493
+ * ```ts
1494
+ * const users = db.use(Users)
1495
+ * const result = await users.syncIndexes()
1496
+ * console.log('Created:', result.created)
1497
+ * console.log('Stale:', result.stale.map(s => s.name))
1498
+ * ```
1499
+ *
1500
+ * @example
1501
+ * ```ts
1502
+ * // Dry run to preview changes without modifying the database
1503
+ * const diff = await users.syncIndexes({ dryRun: true })
1504
+ * console.log('Would create:', diff.created)
1505
+ * console.log('Would drop:', diff.dropped)
1506
+ * ```
1507
+ */
1508
+ syncIndexes(options?: SyncIndexesOptions): Promise<SyncIndexesResult>;
1345
1509
  }
1346
1510
 
1347
1511
  /**
@@ -1376,13 +1540,28 @@ declare class Database {
1376
1540
  * @param def - A collection definition created by `collection()`.
1377
1541
  * @returns A typed collection handle for CRUD operations.
1378
1542
  */
1379
- use<TShape extends z.core.$ZodShape>(def: CollectionDefinition<TShape>): CollectionHandle<CollectionDefinition<TShape>>;
1543
+ use<TShape extends z.core.$ZodShape, TIndexes extends readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[] = readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[]>(def: CollectionDefinition<TShape, TIndexes>): CollectionHandle<CollectionDefinition<TShape, TIndexes>>;
1380
1544
  /**
1381
- * Synchronize indexes defined in registered collections with MongoDB.
1545
+ * Synchronize indexes for all registered collections with MongoDB.
1546
+ *
1547
+ * Iterates every collection registered via {@link use} and calls
1548
+ * {@link syncIndexes} on each one. Returns a record keyed by collection
1549
+ * name with the sync result for each.
1550
+ *
1551
+ * @param options - Optional sync behavior (dryRun, dropOrphaned).
1552
+ * @returns A record mapping collection names to their sync results.
1382
1553
  *
1383
- * Stub — full implementation in TASK-92.
1554
+ * @example
1555
+ * ```ts
1556
+ * const db = createClient('mongodb://localhost:27017', 'myapp')
1557
+ * db.use(Users)
1558
+ * db.use(Posts)
1559
+ * const results = await db.syncIndexes()
1560
+ * console.log(results['users'].created) // ['email_1']
1561
+ * console.log(results['posts'].created) // ['title_1']
1562
+ * ```
1384
1563
  */
1385
- syncIndexes(): Promise<void>;
1564
+ syncIndexes(options?: SyncIndexesOptions): Promise<Record<string, SyncIndexesResult>>;
1386
1565
  /**
1387
1566
  * Execute a function within a MongoDB transaction with auto-commit/rollback.
1388
1567
  *
@@ -1457,7 +1636,9 @@ declare function extractFieldIndexes(shape: z.core.$ZodShape): FieldIndexDefinit
1457
1636
  * })
1458
1637
  * ```
1459
1638
  */
1460
- declare function collection<TShape extends z.core.$ZodShape>(name: string, shape: TShape, options?: CollectionOptions<Extract<keyof TShape, string>>): CollectionDefinition<TShape>;
1639
+ declare function collection<TShape extends z.core.$ZodShape, const TIndexes extends readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[] = readonly CompoundIndexDefinition<Extract<keyof TShape, string>>[]>(name: string, shape: TShape, options?: Omit<CollectionOptions<Extract<keyof TShape, string>>, 'indexes'> & {
1640
+ indexes?: TIndexes;
1641
+ }): CollectionDefinition<TShape, [...TIndexes]>;
1461
1642
 
1462
1643
  type IndexDirection = 1 | -1;
1463
1644
  type CompoundIndexOptions = NonNullable<CompoundIndexDefinition['options']>;
@@ -1485,9 +1666,27 @@ declare class IndexBuilder<TKeys extends string> {
1485
1666
  readonly options: CompoundIndexOptions;
1486
1667
  constructor(fields: Record<TKeys, IndexDirection>);
1487
1668
  private _clone;
1488
- unique(): IndexBuilder<TKeys>;
1489
- sparse(): IndexBuilder<TKeys>;
1490
- name(name: string): IndexBuilder<TKeys>;
1669
+ unique(): this;
1670
+ sparse(): this;
1671
+ /**
1672
+ * Set a custom name for this index, preserving the literal type.
1673
+ *
1674
+ * The returned builder carries the literal name type via an intersection,
1675
+ * enabling type-safe `.hint()` on cursors that only accepts declared names.
1676
+ *
1677
+ * @param name - The index name.
1678
+ * @returns A new IndexBuilder with the name recorded at the type level.
1679
+ *
1680
+ * @example
1681
+ * ```ts
1682
+ * index({ email: 1, role: -1 }).name('email_role_idx')
1683
+ * ```
1684
+ */
1685
+ name<TName extends string>(name: TName): IndexBuilder<TKeys> & {
1686
+ readonly options: {
1687
+ readonly name: TName;
1688
+ };
1689
+ };
1491
1690
  }
1492
1691
  /**
1493
1692
  * Create a compound index definition with a fluent builder API.
@@ -1652,6 +1851,231 @@ declare function oid(value: ObjectId): ObjectId;
1652
1851
  */
1653
1852
  declare function isOid(value: unknown): value is ObjectId;
1654
1853
 
1854
+ /**
1855
+ * A normalized index specification ready for comparison and creation.
1856
+ *
1857
+ * `key` maps field paths to direction (`1`, `-1`, or `'text'`).
1858
+ * `options` holds MongoDB index options (`unique`, `sparse`, etc.).
1859
+ *
1860
+ * @example
1861
+ * ```ts
1862
+ * const spec: IndexSpec = {
1863
+ * key: { email: 1 },
1864
+ * options: { unique: true },
1865
+ * }
1866
+ * ```
1867
+ */
1868
+ type IndexSpec = {
1869
+ key: Record<string, 1 | -1 | 'text'>;
1870
+ options: Record<string, unknown>;
1871
+ };
1872
+ /**
1873
+ * Convert a field-level index definition to a normalized {@link IndexSpec}.
1874
+ *
1875
+ * Maps schema metadata (`text`, `descending`, `unique`, `sparse`, `expireAfter`,
1876
+ * `partial`) to their MongoDB driver equivalents.
1877
+ *
1878
+ * @param def - A field index definition extracted from schema metadata.
1879
+ * @returns A normalized index spec with key and options.
1880
+ *
1881
+ * @example
1882
+ * ```ts
1883
+ * const spec = toFieldIndexSpec({ field: 'email', indexed: true, unique: true })
1884
+ * // => { key: { email: 1 }, options: { unique: true } }
1885
+ *
1886
+ * const ttl = toFieldIndexSpec({ field: 'expiresAt', indexed: true, expireAfter: 3600 })
1887
+ * // => { key: { expiresAt: 1 }, options: { expireAfterSeconds: 3600 } }
1888
+ * ```
1889
+ */
1890
+ declare function toFieldIndexSpec(def: FieldIndexDefinition): IndexSpec;
1891
+ /**
1892
+ * Convert a compound index definition to a normalized {@link IndexSpec}.
1893
+ *
1894
+ * Copies the `fields` map directly to `key` and maps builder options
1895
+ * (`unique`, `sparse`, `name`, `partial`) to MongoDB driver equivalents.
1896
+ *
1897
+ * @param def - A compound index definition from the collection options.
1898
+ * @returns A normalized index spec with key and options.
1899
+ *
1900
+ * @example
1901
+ * ```ts
1902
+ * const spec = toCompoundIndexSpec({
1903
+ * fields: { email: 1, role: -1 },
1904
+ * options: { unique: true, name: 'email_role_idx' },
1905
+ * })
1906
+ * // => { key: { email: 1, role: -1 }, options: { unique: true, name: 'email_role_idx' } }
1907
+ * ```
1908
+ */
1909
+ declare function toCompoundIndexSpec(def: CompoundIndexDefinition): IndexSpec;
1910
+ /**
1911
+ * Produce a stable, deterministic string from an index key for comparison.
1912
+ *
1913
+ * Entries are sorted alphabetically by field name and formatted as
1914
+ * `field:direction` pairs joined by commas. Two index keys that should be
1915
+ * considered the same will always produce the same string.
1916
+ *
1917
+ * @param key - An index key mapping field names to direction.
1918
+ * @returns A string like `'email:1,role:-1'`.
1919
+ *
1920
+ * @example
1921
+ * ```ts
1922
+ * serializeIndexKey({ email: 1, role: -1 })
1923
+ * // => 'email:1,role:-1'
1924
+ *
1925
+ * serializeIndexKey({ name: 'text' })
1926
+ * // => 'name:text'
1927
+ * ```
1928
+ */
1929
+ declare function serializeIndexKey(key: Record<string, 1 | -1 | 'text'>): string;
1930
+
1931
+ /**
1932
+ * Structural constraint for the handle argument of {@link syncIndexes}.
1933
+ *
1934
+ * Uses a structural type rather than `CollectionHandle<AnyCollection>` to avoid
1935
+ * TypeScript variance issues with `exactOptionalPropertyTypes`. Any collection
1936
+ * handle returned by `db.use()` satisfies this constraint.
1937
+ */
1938
+ type SyncableHandle = {
1939
+ readonly definition: {
1940
+ readonly fieldIndexes: FieldIndexDefinition[];
1941
+ readonly compoundIndexes: readonly CompoundIndexDefinition[];
1942
+ };
1943
+ readonly native: Collection<any>;
1944
+ };
1945
+ /**
1946
+ * Extract the comparable options from a MongoDB index info object.
1947
+ *
1948
+ * Pulls only the keys listed in {@link COMPARABLE_OPTION_KEYS} from the raw
1949
+ * index info returned by `listIndexes()`. Keys whose value is `undefined`
1950
+ * are omitted so that JSON comparison works correctly.
1951
+ *
1952
+ * @param info - A raw MongoDB index info object.
1953
+ * @returns A plain object with only the relevant option keys.
1954
+ *
1955
+ * @example
1956
+ * ```ts
1957
+ * const opts = extractComparableOptions({ v: 2, unique: true, key: { email: 1 } })
1958
+ * // => { unique: true }
1959
+ * ```
1960
+ */
1961
+ declare function extractComparableOptions(info: Record<string, unknown>): Record<string, unknown>;
1962
+ /**
1963
+ * Generate the default MongoDB index name from a key spec.
1964
+ *
1965
+ * MongoDB names indexes by joining `field_direction` pairs with underscores.
1966
+ * For example, `{ email: 1, role: -1 }` becomes `'email_1_role_-1'`.
1967
+ *
1968
+ * @param key - An index key mapping field names to direction.
1969
+ * @returns The generated index name string.
1970
+ *
1971
+ * @example
1972
+ * ```ts
1973
+ * generateIndexName({ email: 1 })
1974
+ * // => 'email_1'
1975
+ *
1976
+ * generateIndexName({ email: 1, role: -1 })
1977
+ * // => 'email_1_role_-1'
1978
+ * ```
1979
+ */
1980
+ declare function generateIndexName(key: Record<string, 1 | -1 | 'text'>): string;
1981
+ /**
1982
+ * Synchronize the indexes declared in a collection's schema with MongoDB.
1983
+ *
1984
+ * Compares the desired indexes (from field-level and compound index definitions)
1985
+ * with the indexes that currently exist in MongoDB, then creates, drops, or
1986
+ * reports differences depending on the options.
1987
+ *
1988
+ * **Algorithm:**
1989
+ * 1. Build desired specs from field indexes and compound indexes.
1990
+ * 2. Fetch existing indexes from MongoDB via `listIndexes()`.
1991
+ * 3. For each desired spec, compare against existing:
1992
+ * - Missing → create (unless `dryRun`).
1993
+ * - Present with same options → skip.
1994
+ * - Present with different options → stale (or drop+recreate if `dropOrphaned`).
1995
+ * 4. For each existing index not in desired set (excluding `_id_`):
1996
+ * - If `dropOrphaned` → drop (unless `dryRun`).
1997
+ * 5. Return a summary of all actions taken.
1998
+ *
1999
+ * @param handle - A collection handle created by `db.use()`.
2000
+ * @param options - Optional sync behavior (dryRun, dropOrphaned).
2001
+ * @returns A summary of created, dropped, skipped, and stale indexes.
2002
+ *
2003
+ * @example
2004
+ * ```ts
2005
+ * import { syncIndexes } from '@zodmon/core'
2006
+ *
2007
+ * const users = db.use(Users)
2008
+ * const result = await syncIndexes(users)
2009
+ * console.log('Created:', result.created)
2010
+ * console.log('Stale:', result.stale.map(s => s.name))
2011
+ * ```
2012
+ *
2013
+ * @example
2014
+ * ```ts
2015
+ * // Dry run — no changes made
2016
+ * const diff = await syncIndexes(users, { dryRun: true })
2017
+ * console.log('Would create:', diff.created)
2018
+ * console.log('Would drop:', diff.dropped)
2019
+ * ```
2020
+ */
2021
+ declare function syncIndexes(handle: SyncableHandle, options?: SyncIndexesOptions): Promise<SyncIndexesResult>;
2022
+
2023
+ /**
2024
+ * Structural constraint for the definition argument of {@link checkUnindexedFields}.
2025
+ *
2026
+ * Uses a structural type rather than `CollectionDefinition<z.core.$ZodShape>` to avoid
2027
+ * TypeScript variance issues with `exactOptionalPropertyTypes`. Any collection
2028
+ * definition returned by `collection()` satisfies this constraint.
2029
+ */
2030
+ type WarnableDefinition = {
2031
+ readonly name: string;
2032
+ readonly fieldIndexes: FieldIndexDefinition[];
2033
+ readonly compoundIndexes: readonly CompoundIndexDefinition[];
2034
+ readonly options: {
2035
+ warnUnindexedQueries?: boolean;
2036
+ };
2037
+ };
2038
+ /**
2039
+ * Warn about unindexed fields used in a query filter.
2040
+ *
2041
+ * When `warnUnindexedQueries` is enabled on a collection definition, this
2042
+ * function checks each top-level field in the filter against the collection's
2043
+ * declared indexes. Fields that are not covered by any index produce a
2044
+ * `console.warn` message to help identify queries that may cause full
2045
+ * collection scans in development.
2046
+ *
2047
+ * **Covered fields:**
2048
+ * - `_id` (always indexed by MongoDB)
2049
+ * - Fields with `.index()`, `.unique()`, `.text()`, or `.expireAfter()` (field-level indexes)
2050
+ * - The **first** field of each compound index (prefix matching)
2051
+ *
2052
+ * **Skipped keys:**
2053
+ * - MongoDB operators: `$or`, `$and`, `$nor`, `$text`, `$where`, `$expr`, `$comment`
2054
+ *
2055
+ * This function is a no-op (zero overhead) when `warnUnindexedQueries` is not
2056
+ * explicitly set to `true`.
2057
+ *
2058
+ * @param definition - The collection definition containing index metadata.
2059
+ * @param filter - The query filter to check for unindexed fields.
2060
+ *
2061
+ * @example
2062
+ * ```ts
2063
+ * import { collection, checkUnindexedFields } from '@zodmon/core'
2064
+ *
2065
+ * const Users = collection('users', {
2066
+ * email: z.string().unique(),
2067
+ * name: z.string(),
2068
+ * }, { warnUnindexedQueries: true })
2069
+ *
2070
+ * // No warning — email is indexed
2071
+ * checkUnindexedFields(Users, { email: 'ada@example.com' })
2072
+ *
2073
+ * // Warns: "[zodmon] warn: query on 'users' uses unindexed field 'name'"
2074
+ * checkUnindexedFields(Users, { name: 'Ada' })
2075
+ * ```
2076
+ */
2077
+ declare function checkUnindexedFields(definition: WarnableDefinition, filter: Record<string, unknown>): void;
2078
+
1655
2079
  /**
1656
2080
  * Convenience namespace that groups all query operators under a single import.
1657
2081
  *
@@ -2038,4 +2462,4 @@ type UpdateFilterOf<TDef extends AnyCollection> = TypedUpdateFilter<InferDocumen
2038
2462
  */
2039
2463
  type SortOf<TDef extends AnyCollection> = TypedSort<InferDocument<TDef>>;
2040
2464
 
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 };
2465
+ export { $, $and, $eq, $exists, $gt, $gte, $in, $lt, $lte, $ne, $nin, $nor, $not, $or, $regex, type AddToSetEach, type AddToSetFields, type AnyCollection, type ArrayElement, type CollectionDefinition, CollectionHandle, type CollectionOptions, type ComparisonOperators, type CompoundIndexDefinition, type CurrentDateFields, type CursorPage, type CursorPaginateOptions, Database, type DotPathType, type DotPaths, type FieldIndexDefinition, type FilterOf, type FindOneAndDeleteOptions, type FindOneAndUpdateOptions, type FindOneOptions, type FindOptions, type HandleOf, type IncFields, IndexBuilder, type IndexMetadata, type IndexNames, type IndexOptions, type IndexSpec, type InferDocument, type InferInsert, type OffsetPage, type OffsetPaginateOptions, type PopFields, type PullFields, type PushFields, type PushModifiers, type RefMarker, type RefMetadata, type RenameFields, type ResolvedShape, type SetFields, type SortOf, type StaleIndex, type SyncIndexesOptions, type SyncIndexesResult, type TypedFilter, TypedFindCursor, type TypedSort, type TypedUpdateFilter, type UnsetFields, type UpdateFilterOf, type UpdateOptions, type ValidationMode, type ZodObjectId, ZodmonNotFoundError, ZodmonValidationError, checkUnindexedFields, collection, createClient, deleteMany, deleteOne, extractComparableOptions, extractDbName, extractFieldIndexes, find, findOne, findOneAndDelete, findOneAndUpdate, findOneOrThrow, generateIndexName, getIndexMetadata, getRefMetadata, index, insertMany, insertOne, isOid, objectId, oid, raw, serializeIndexKey, syncIndexes, toCompoundIndexSpec, toFieldIndexSpec, updateMany, updateOne };