@ignex/ninox 0.1.1

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.
@@ -0,0 +1,2570 @@
1
+ import * as mongodb from 'mongodb';
2
+ import { Db, MongoClient, ClientSession, Document, Filter, Hint, CollationOptions, ReadPreferenceLike, ReadConcern, WriteConcern, WithId, Condition, RootFilterOperators, Collection, FindOptions, Sort, FindCursor, ObjectId, Decimal128, ReadConcernLike, AggregateOptions, AggregationCursor, CreateCollectionOptions, TransactionOptions, InsertOneOptions, InsertOneResult, InsertManyResult, UpdateOptions, UpdateResult, FindOneAndUpdateOptions, DeleteResult, ChangeStreamOptions, ChangeStream } from 'mongodb';
3
+ import { L as LoggerLike } from './timeout-BgrqtMnd.js';
4
+ export { C as CacheOptions, a as LRU, b as LRUOptions, c as LogFn, d as LogLevel, e as createCachedAsyncFactory, f as createCachedFactory, g as createConsoleLogger, h as createNoopLogger, i as hashString, s as sleep, j as sleepJittered, k as stableHash, l as stableStringify, w as withTimeout } from './timeout-BgrqtMnd.js';
5
+ import * as bson from 'bson';
6
+
7
+ /**
8
+ * Types + constants for the HotCache layer. All type-only (plus the stream-key
9
+ * separator constant), so the coordinating class (`HotCache` in `./index.ts`)
10
+ * and its strategy modules (`./ticker.ts`, `./watcher.ts`) share one vocabulary
11
+ * without importing each other's runtime logic.
12
+ */
13
+
14
+ /** A collection to watch for invalidation. `collection` is the PHYSICAL name. */
15
+ interface HotCollectionRef {
16
+ /** Raw `Db` handle (e.g. `service.db.primaryClient.client`). */
17
+ db: Db;
18
+ /** Physical collection name whose writes invalidate bound queries. */
19
+ collection: string;
20
+ }
21
+ /** Opt-in query registration. `TArgs` = the parameter tuple, `TResult` = loader result. */
22
+ interface HotQueryConfig<TArgs extends readonly unknown[], TResult> {
23
+ /** Read-through loader executed on cache miss / background refresh. */
24
+ loader: (...args: TArgs) => Promise<TResult>;
25
+ /**
26
+ * Optional cache-key extractor. Lets callers decide which parameters form the
27
+ * key (e.g. hash only some of them). Default: `stableHash` of all arguments.
28
+ */
29
+ keyOf?: (...args: TArgs) => string;
30
+ /** Per-query TTL in ms (`0` = no expiry). Defaults to `defaultTtlMs`. */
31
+ ttlMs?: number;
32
+ /** Per-query LRU cap. Defaults to `defaultMaxSize`. */
33
+ maxSize?: number;
34
+ /**
35
+ * Background refresh interval in ms (standalone ticker only). Default `0` =
36
+ * no background refresh — entries then update only via `ttlMs` expiry or
37
+ * manual `invalidate*` calls. In standalone mode with `autoRefresh` on, a
38
+ * query with neither `refreshIntervalMs` nor `ttlMs` set serves values until
39
+ * it is manually invalidated (unbounded staleness); `start()` logs a warning
40
+ * naming those queries so you can opt in deliberately.
41
+ */
42
+ refreshIntervalMs?: number;
43
+ /**
44
+ * Collections whose data changes invalidate this query. Used by replica mode
45
+ * to open change-stream watchers. In standalone mode these have no effect
46
+ * (the ticker refreshes instead) — use `invalidateCollection` manually if
47
+ * needed.
48
+ */
49
+ watch?: HotCollectionRef[];
50
+ /**
51
+ * Clone results on every read so callers can't mutate the shared cached entry
52
+ * (default false — cloning has a per-read cost; treat cached results as
53
+ * read-only when disabled).
54
+ */
55
+ clone?: boolean;
56
+ /**
57
+ * Upper bound (bytes) for a cached value, estimated via a cheap BSON-aware
58
+ * size probe. Larger values are still returned to the caller but are NOT
59
+ * cached (a first oversized result logs a warning). Protects the LRU from
60
+ * holding unbounded single entries. Default: no limit.
61
+ */
62
+ maxValueBytes?: number;
63
+ }
64
+ interface HotCacheOptions {
65
+ /**
66
+ * Replica-support probe. Default: `probeMongoCapabilities` over the first
67
+ * watched collection's `db`. Return `true` → change-stream mode,
68
+ * `false` → standalone ticker mode. A probe that returns no result (timeout)
69
+ * falls back to standalone with a warning — pass `mode` to pin the behavior.
70
+ */
71
+ probe?: () => Promise<boolean>;
72
+ /**
73
+ * Pin the cache mode directly, skipping the probe entirely. `'replica'` opens
74
+ * change-stream watchers, `'standalone'` starts the ticker. Useful when the
75
+ * deployment is known (e.g. from connection config) and you want to avoid a
76
+ * `hello` round-trip or a mis-detection.
77
+ */
78
+ mode?: 'replica' | 'standalone';
79
+ /** Default per-query TTL in ms (`0` = no expiry). Default `0`. */
80
+ defaultTtlMs?: number;
81
+ /** Default per-query LRU cap. Default `500`. */
82
+ defaultMaxSize?: number;
83
+ /** Global ticker interval in ms (standalone mode). Default `1000`. */
84
+ tickIntervalMs?: number;
85
+ /**
86
+ * Master switch for the standalone background-refresh ticker. `true` (default)
87
+ * runs the global ticker that re-fetches entries at `refreshIntervalMs`. Set
88
+ * `false` to turn off auto-refetch entirely — data then updates only via
89
+ * manual `invalidate`/`invalidateParams`/`invalidateCollection` or `ttlMs`
90
+ * expiry. Replica-mode change streams are event-driven and unaffected.
91
+ */
92
+ autoRefresh?: boolean;
93
+ logger?: LoggerLike;
94
+ }
95
+ type HotCacheMode = 'unknown' | 'replica' | 'standalone';
96
+ /** Typed handle returned by `register` — type-safe alternative to dynamic `get`. */
97
+ interface HotQueryAccessor<TArgs extends readonly unknown[], TResult> {
98
+ /** Read-through: serve from LRU, else run the loader. */
99
+ get: (...args: TArgs) => Promise<TResult>;
100
+ /**
101
+ * Manual invalidation (fully typed, with intellisense on the parameters).
102
+ * Called with no arguments it drops every cached entry for this query; called
103
+ * with the query's parameters it drops only that parameter's entry.
104
+ *
105
+ * topProducts.invalidate(); // drop all
106
+ * topProducts.invalidate(10); // drop only the { limit: 10 } entry
107
+ */
108
+ invalidate: {
109
+ (): void;
110
+ (...args: TArgs): void;
111
+ };
112
+ }
113
+ /** Per-query runtime counters (see `HotCache.stats()`). */
114
+ interface HotQueryStats {
115
+ /** Current number of cached entries for this query. */
116
+ size: number;
117
+ /** Reads served from the LRU without running the loader. */
118
+ hits: number;
119
+ /** Reads that ran the loader (LRU miss or expired entry). */
120
+ misses: number;
121
+ /** Background refreshes kicked by the standalone ticker. */
122
+ refreshes: number;
123
+ /** Loader rejections (failed reads/refreshes). */
124
+ loadErrors: number;
125
+ /** Values too large to cache (`maxValueBytes`). */
126
+ sizeSkips: number;
127
+ /** Capacity evictions (LRU). */
128
+ evictions: number;
129
+ }
130
+ /** Snapshot of the whole cache (see `HotCache.stats()`). */
131
+ interface HotCacheStats {
132
+ mode: HotCacheMode;
133
+ /** Number of registered queries. */
134
+ queries: number;
135
+ /** Total cached entries across all queries. */
136
+ entries: number;
137
+ perQuery: Record<string, HotQueryStats>;
138
+ }
139
+
140
+ declare class HotCache {
141
+ private options;
142
+ private logger;
143
+ private refreshEnabled;
144
+ private queries;
145
+ private inflight;
146
+ private watcher;
147
+ private ticker;
148
+ private _mode;
149
+ private started;
150
+ private startAttempted;
151
+ private stopped;
152
+ private disposed;
153
+ private startPromise?;
154
+ constructor(options?: HotCacheOptions);
155
+ /** Number of cached entries across all registered queries. */
156
+ get size(): number;
157
+ /** Resolved mode: `unknown` until `start()` (or the first `get`) probes. */
158
+ get mode(): HotCacheMode;
159
+ /** Whether the standalone background-refresh ticker is enabled (`autoRefresh`). */
160
+ get autoRefresh(): boolean;
161
+ /** Whether a query with this name has been registered. */
162
+ has(name: string): boolean;
163
+ /**
164
+ * Opt a query into the hot cache. Throws `BadRequest` on a duplicate name.
165
+ * Returns a fully typed accessor — `.get(...args)` (intellisense on the loader
166
+ * parameters) and `.invalidate()` / `.invalidate(...args)` for manual
167
+ * invalidation (all entries or a single parameter set).
168
+ */
169
+ register<TArgs extends readonly unknown[], TResult>(name: string, config: HotQueryConfig<TArgs, TResult>): HotQueryAccessor<TArgs, TResult>;
170
+ /**
171
+ * Read-through read by dynamic name lookup. Call parameters are hashed into
172
+ * the cache key (`keyOf` override wins). Throws `BadRequest` for an unknown
173
+ * query name. For fully typed parameters/results prefer the accessor returned
174
+ * by `register()` — this dynamic form returns `Promise<unknown>`.
175
+ */
176
+ get(name: string, ...args: unknown[]): Promise<unknown>;
177
+ /** Drop every cached entry for a registered query. */
178
+ invalidate(name: string): void;
179
+ /**
180
+ * Drop only the cached entry for a specific parameter set. The key is computed
181
+ * exactly like `get` (`keyOf` override ?? `stableHash` of args), so the same
182
+ * parameters invalidate the same entry.
183
+ */
184
+ invalidateParams(name: string, ...args: unknown[]): void;
185
+ /**
186
+ * Invalidate every query bound to a physical collection (via `watch`). Can be
187
+ * called manually to wire ORM write hooks in standalone mode.
188
+ */
189
+ invalidateCollection(collection: string): void;
190
+ /** Drop every cached entry for every query. */
191
+ clear(): void;
192
+ /**
193
+ * Runtime observability snapshot: total queries/entries plus per-query
194
+ * counters (`hits`, `misses`, background `refreshes`, `loadErrors`, and
195
+ * `sizeSkips` for entries dropped by `maxValueBytes`). Useful for dashboards
196
+ * and cache-effectiveness checks.
197
+ */
198
+ stats(): HotCacheStats;
199
+ /**
200
+ * Probe replica support once, then start change-stream watchers (replica) or
201
+ * the global refresh ticker (standalone). Idempotent — resolves the mode.
202
+ */
203
+ start(): Promise<HotCacheMode>;
204
+ /**
205
+ * Stop the ticker, close every change stream, and **dispose** the instance.
206
+ * Terminal: a later `get()` serves cached values read-through but never
207
+ * re-probes or re-opens background resources.
208
+ */
209
+ stop(): Promise<void>;
210
+ private runStart;
211
+ private ensureStarted;
212
+ private defaultProbe;
213
+ private fetch;
214
+ private setEntry;
215
+ }
216
+ /** Create a new global hot-cache instance. */
217
+ declare const createHotCache: (options?: HotCacheOptions) => HotCache;
218
+
219
+ /**
220
+ * In-flight request coalescing: identical concurrent operations share a single
221
+ * Promise so the underlying (expensive) work runs once. This is the "query
222
+ * dedup" optimization used by the CRUD read path.
223
+ */
224
+ declare class InFlight {
225
+ private pending;
226
+ get size(): number;
227
+ run<T>(key: string, fn: () => Promise<T>): Promise<T>;
228
+ clear(): void;
229
+ }
230
+
231
+ interface QueryCacheOptions {
232
+ /** Max cached entries (default 500). */
233
+ maxSize?: number;
234
+ /** Per-entry TTL in ms (default 0 = no expiry). */
235
+ ttlMs?: number;
236
+ /**
237
+ * Clone results on every read so callers can't mutate the shared cache entry
238
+ * (default false — cloning has a per-read cost; treat cached results as
239
+ * read-only when disabled).
240
+ */
241
+ clone?: boolean;
242
+ }
243
+ /** Snapshot of cache health (see `QueryCache.stats()`). */
244
+ interface QueryCacheStats {
245
+ size: number;
246
+ maxSize: number;
247
+ /** Reads served from the cache without touching the DB. */
248
+ hits: number;
249
+ /** Reads that missed (absent or expired). */
250
+ misses: number;
251
+ /** `set()` calls (writes to the cache). */
252
+ sets: number;
253
+ /** `delete()` calls. */
254
+ deletes: number;
255
+ /** `invalidateByCollection` calls that actually dropped entries. */
256
+ invalidateEvents: number;
257
+ /** `clear()` calls. */
258
+ clearEvents: number;
259
+ /** Capacity evictions (LRU). */
260
+ evictions: number;
261
+ }
262
+ /**
263
+ * Compose the "collection" part of a cache key, namespaced by the DATABASE name.
264
+ * One service shares a single QueryCache + InFlight across every DB client; without
265
+ * the namespace, same-named physical collections in different databases would
266
+ * collide — a read on DB A could be served DB B's cached rows, and identical
267
+ * concurrent reads across DBs could coalesce into one driver call. Reads,
268
+ * write-through invalidations, and change-stream invalidations MUST all use the
269
+ * same namespace so they agree.
270
+ */
271
+ declare const cacheCollectionKey: (dbName: string, physical: string) => string;
272
+ /**
273
+ * Read-through query cache with per-collection invalidation.
274
+ *
275
+ * Keys are `collection<sep>hash` so a single write can invalidate every cached
276
+ * read for that collection. Invalidation is WRITE-THROUGH only: the ORM drops a
277
+ * collection's entries on every write it performs. Reads are NOT invalidated by
278
+ * external writers (other processes, the raw `client` escape hatch, or direct
279
+ * DB writes) — with `ttlMs: 0` (default) those reads stay stale indefinitely,
280
+ * so multi-writer deployments should set `ttlMs` or use the change-stream aware
281
+ * `createHotCache()`. Perf wins: cache hits perform zero driver round-trips.
282
+ */
283
+ declare class QueryCache {
284
+ private lru;
285
+ private index;
286
+ private ttlMs;
287
+ private clone;
288
+ private hits;
289
+ private misses;
290
+ private sets;
291
+ private deletes;
292
+ private invalidateEvents;
293
+ private clearEvents;
294
+ constructor(options?: QueryCacheOptions);
295
+ get size(): number;
296
+ /** Cache-health snapshot: sizes plus lifetime hit/miss/eviction counters. */
297
+ stats(): QueryCacheStats;
298
+ /** Compose a cache key for a collection + filter/options hash. */
299
+ key(collection: string, filterHash: string): string;
300
+ get(key: string): unknown | undefined;
301
+ set(key: string, value: unknown, ttlMs?: number): void;
302
+ delete(key: string): void;
303
+ /** Drop every cached entry belonging to a physical collection. */
304
+ invalidateByCollection(collection: string): void;
305
+ clear(): void;
306
+ }
307
+
308
+ /**
309
+ * Transaction capability detection. MongoDB transactions require a replica set
310
+ * (or mongos). The ORM probes once and caches the result so the transaction
311
+ * wrapper can fall back to plain (non-transactional) execution on standalone
312
+ * servers.
313
+ */
314
+ interface MongoCapabilities {
315
+ probed: boolean;
316
+ transactionsSupported: boolean;
317
+ }
318
+ interface MongoCapabilitiesStore {
319
+ get(): MongoCapabilities;
320
+ set(capabilities: MongoCapabilities): void;
321
+ reset(): void;
322
+ }
323
+ declare const createMongoCapabilitiesStore: () => MongoCapabilitiesStore;
324
+ /** Probe a connected client/db via `hello`. */
325
+ declare const probeMongoCapabilities: (client: MongoClient | Db | null | undefined) => Promise<MongoCapabilities>;
326
+
327
+ /**
328
+ * The ORM's error classes (`AppError` → `DomainError` / `InfraError` /
329
+ * `BadRequest`), their type guards, and the `ERROR_HTTP_STATUS` table that sets
330
+ * each error's `statusCode` at construction.
331
+ *
332
+ * A leaf module: nothing else in `src/errors/` imports this (so `driver-map.ts`
333
+ * and `http-status.ts` can depend on it without cycles).
334
+ */
335
+ interface ErrorContext {
336
+ db?: string;
337
+ collection?: string;
338
+ op?: string;
339
+ }
340
+ /**
341
+ * Canonical HTTP status for each error code. `AppError.statusCode` is set from
342
+ * this table at construction (falling back to the class default: 400 for
343
+ * `DomainError`, 500 for `InfraError`), so a framework can respond with
344
+ * `err.statusCode` directly and still get correct REST semantics:
345
+ * NOT_FOUND → 404, DUPLICATE_KEY/VERSION_CONFLICT/COLLECTION_EXISTS → 409,
346
+ * VALIDATION_FAILED → 422, MONGO_TIMEOUT → 504.
347
+ */
348
+ declare const ERROR_HTTP_STATUS: Record<string, number>;
349
+ declare class AppError extends Error {
350
+ readonly code: string;
351
+ readonly statusCode: number;
352
+ readonly extra?: Record<string, unknown>;
353
+ constructor(code: string, message: string, statusCode?: number, extra?: Record<string, unknown>);
354
+ /**
355
+ * Stable, client-safe serialization for framework error middleware: a
356
+ * framework can forward `JSON.stringify(error)` (or `error.toJSON()`) straight
357
+ * to the client — no parsing of server internals. `stack` is dropped; the
358
+ * shape is always `{ name, code, message, statusCode, ...(extra) }`.
359
+ */
360
+ toJSON(): Record<string, unknown>;
361
+ }
362
+ declare class DomainError extends AppError {
363
+ constructor(code: string, message: string, extra?: Record<string, unknown>);
364
+ }
365
+ declare class InfraError extends AppError {
366
+ constructor(code: string, message: string, extra?: Record<string, unknown>);
367
+ }
368
+ declare class BadRequest extends DomainError {
369
+ constructor(message: string, extra?: Record<string, unknown>);
370
+ }
371
+ declare const isAppError: (err: unknown) => err is AppError;
372
+ declare const isDomainError: (err: unknown) => err is DomainError;
373
+ declare const isInfraError: (err: unknown) => err is InfraError;
374
+
375
+ /**
376
+ * Raw driver-error mapping: turn Mongo driver errors (codes 11000, 112, 50,
377
+ * 121, bulk-write, transient) into the ORM's typed `DomainError` / `InfraError`,
378
+ * plus `extractValidationPaths` for surfacing which `$jsonSchema` fields failed.
379
+ */
380
+
381
+ /**
382
+ * Map a raw driver error to a typed DomainError/InfraError. Mirrors the sdk-db
383
+ * mapping table (codes 11000 → DUPLICATE_KEY, 112 → VERSION_CONFLICT,
384
+ * 50 → MONGO_TIMEOUT, 121 → VALIDATION_FAILED, everything else → MONGO_QUERY_ERROR).
385
+ * When the error is already an AppError it is returned unchanged.
386
+ */
387
+ declare const mapMongoDriverError: (error: unknown, ctx?: ErrorContext) => unknown;
388
+ /** True when the error is already a mapped AppError (used by callers that opted into wrapping). */
389
+ declare const isMappedMongoError: (error: unknown) => error is AppError;
390
+
391
+ /**
392
+ * Framework-facing error helpers: `httpStatusForError` (effective HTTP status
393
+ * for any thrown value) and `serializeError` (reduce any thrown value to a
394
+ * stable, client-safe payload).
395
+ */
396
+
397
+ /** Effective HTTP status for any thrown value: AppError-aware, raw → 500 (503 transient). */
398
+ declare const httpStatusForError: (err: unknown) => number;
399
+ /**
400
+ * Reduce any thrown value to a stable, client-safe error payload:
401
+ * `{ name, code, message, statusCode, ...(extra) }`.
402
+ *
403
+ * Raw driver errors are mapped through `mapMongoDriverError` first, so a
404
+ * framework can forward the result directly without parsing Mongo server
405
+ * internals (`errInfo`, `writeErrors`, `keyPattern`, ...):
406
+ *
407
+ * app.use((err, _req, res, _next) => {
408
+ * const payload = serializeError(err, { db, collection, op });
409
+ * res.status(payload.statusCode).json(payload);
410
+ * });
411
+ */
412
+ declare const serializeError: (err: unknown, ctx?: ErrorContext) => Record<string, unknown>;
413
+
414
+ /**
415
+ * Transient-error detection: which MongoDB errors are safe to retry. This is a
416
+ * leaf module (no folder imports) so both `driver-map.ts` and `http-status.ts`
417
+ * can depend on it without cycles.
418
+ */
419
+ /**
420
+ * Server error codes MongoDB raises that are safe to retry (network hiccups,
421
+ * not-primary, sharding moves, interrupted queries). Mirrors the reference set.
422
+ *
423
+ * Frozen + typed `ReadonlySet` so a consumer can't mutate the shared instance
424
+ * (it is a module-level singleton, re-exported through the barrel).
425
+ */
426
+ declare const TRANSIENT_MONGO_ERROR_CODES: ReadonlySet<number>;
427
+ /** True when the error is a transient Mongo failure that retry can recover from. */
428
+ declare const isMongoTransientError: (error: unknown) => boolean;
429
+ /** True when the error is a duplicate-key violation (code 11000 / E11000 message). */
430
+ declare const isMongoDuplicateKeyError: (error: unknown) => boolean;
431
+
432
+ /** Minimal transaction runner surface (satisfied by the service `transaction`). */
433
+ interface MongoTransactionRunner {
434
+ transaction: <T>(fn: (session: ClientSession | null) => Promise<T>) => Promise<T>;
435
+ }
436
+ interface GracefulTransactionOptions {
437
+ capabilities?: ReturnType<typeof createMongoCapabilitiesStore>;
438
+ urlHint?: string;
439
+ logger?: LoggerLike;
440
+ /** Map driver errors to typed DomainError/InfraError before rethrowing (default true). */
441
+ wrapMongoErrors?: boolean;
442
+ }
443
+ /**
444
+ * Run `fn` inside a transaction when the deployment supports it; otherwise run
445
+ * with a `null` session (no-op fallback). Also handles the "retryable writes
446
+ * unsupported" error by re-running with a null session.
447
+ */
448
+ declare const withGracefulMongoTransaction: <T>(runner: MongoTransactionRunner, fn: (session: ClientSession | null) => Promise<T>, options?: GracefulTransactionOptions) => Promise<T>;
449
+
450
+ /**
451
+ * Lifecycle hooks — the ORM's middleware system. Hooks are declared per
452
+ * collection and run around the corresponding CRUD operation.
453
+ */
454
+ type HookName = 'beforeCreate' | 'afterCreate' | 'beforeUpdate' | 'afterUpdate' | 'beforeDelete' | 'afterDelete' | 'afterRead';
455
+ interface HookContext<TSchema = Document> {
456
+ collection: string;
457
+ doc?: TSchema;
458
+ docs?: TSchema[];
459
+ filter?: Filter<Document>;
460
+ options?: Record<string, unknown>;
461
+ }
462
+ type Hook<TSchema = Document> = (ctx: HookContext<TSchema>) => void | Promise<void>;
463
+ type HookMap<TSchema = Document> = Partial<Record<HookName, Hook<TSchema>>>;
464
+ /** Registry keyed by logical collection name. */
465
+ type HooksRegistry = Record<string, HookMap>;
466
+ declare const HOOK_NAMES: readonly HookName[];
467
+ /** Run a single hook (no-op when the collection has none registered). */
468
+ declare const runHooks: <TSchema = Document>(registry: HooksRegistry | undefined, collection: string, name: HookName, ctx: HookContext<TSchema>) => Promise<void>;
469
+
470
+ /**
471
+ * A self-contained DataLoader — batch + cache loading that eliminates the
472
+ * N+1 query problem (the classic GraphQL utility, implemented from scratch so
473
+ * the ORM owns its batching behavior).
474
+ *
475
+ * Semantics:
476
+ * - concurrent `load(key)` calls made in the same tick are coalesced into ONE
477
+ * `batch(keys)` call (flushed on a microtask, or after `maxBatchDelayMs`)
478
+ * - the batch result is a Map (key → value); a missing key resolves `undefined`
479
+ * - results are cached per key (canonicalized via `keyOf`) for the loader's
480
+ * lifetime; `clear`/`clearAll` reset the cache
481
+ * - a FAILED batch is never cached: its keys are evicted so a later `load`
482
+ * runs a fresh batch instead of re-surfacing the same rejection
483
+ * - `maxBatchSize` splits oversized batches
484
+ */
485
+ interface DataLoaderOptions<Key, Value> {
486
+ /** Resolve a batch of keys → one Map. Keys that have no row should be absent. */
487
+ batch: (keys: Key[]) => Promise<Map<Key, Value>>;
488
+ /** Max keys per batch call (default 1000). */
489
+ maxBatchSize?: number;
490
+ /** Flush delay in ms (default 0 → microtask flush, i.e. one tick batching). */
491
+ maxBatchDelayMs?: number;
492
+ /** Cache loaded values (default true). */
493
+ cache?: boolean;
494
+ /** Canonical key function for value-based keys (ObjectId, Date, ...). */
495
+ keyOf?: (key: Key) => string;
496
+ cacheMap?: Map<string, Promise<Value | undefined>>;
497
+ }
498
+ declare class DataLoader<Key, Value> {
499
+ private readonly batchFn;
500
+ private readonly maxBatchSize;
501
+ private readonly maxBatchDelayMs;
502
+ private readonly cacheEnabled;
503
+ private readonly keyOf;
504
+ private readonly cache;
505
+ private queue;
506
+ private scheduled;
507
+ private flushing;
508
+ constructor(options: DataLoaderOptions<Key, Value>);
509
+ /** Load one key. Concurrent loads are batched; cached keys resolve instantly. */
510
+ load(key: Key): Promise<Value | undefined>;
511
+ /** Load many keys (each resolves independently). */
512
+ loadMany(keys: readonly Key[]): Promise<Array<Value | undefined>>;
513
+ clear(key: Key): this;
514
+ clearAll(): this;
515
+ /** Pre-populate the cache without hitting the batch function. */
516
+ prime(key: Key, value: Value): this;
517
+ get pendingCount(): number;
518
+ private scheduleFlush;
519
+ private flush;
520
+ }
521
+ /** Canonical key for value-comparison keys (ObjectId → hex, primitives → typed string). */
522
+ declare const canonicalKey: (value: unknown) => string;
523
+
524
+ /**
525
+ * Migration types + constants shared by `./files.ts` (discovery/scaffolding),
526
+ * `./journal.ts` (the `_migrations` journal) and `./index.ts` (the runner).
527
+ */
528
+
529
+ interface MigrationContext {
530
+ service: unknown;
531
+ logger: LoggerLike;
532
+ name: string;
533
+ }
534
+ interface MigrationModule {
535
+ up: (ctx: MigrationContext) => void | Promise<void>;
536
+ down: (ctx: MigrationContext) => void | Promise<void>;
537
+ }
538
+ interface MongoMigrationRunner {
539
+ /** Apply every not-yet-applied migration in numeric order. */
540
+ up(): Promise<void>;
541
+ /** Roll back applied migrations in reverse order (down to `targetName` when given). */
542
+ down(targetName?: string): Promise<void>;
543
+ status(): Promise<{
544
+ applied: string[];
545
+ pending: string[];
546
+ }>;
547
+ /** Scaffold a new `NNN_name.ts` migration file; returns its path. */
548
+ create(name: string): Promise<string>;
549
+ }
550
+ interface MongoMigrationRunnerOptions {
551
+ migrationDir?: string;
552
+ logger?: LoggerLike;
553
+ }
554
+
555
+ /**
556
+ * File-based migration runner — `createMongoMigrationRunner`. Orchestrates the
557
+ * two sub-systems:
558
+ *
559
+ * - `./files.ts` — discover / load / scaffold `NNN_name.ts` files
560
+ * - `./journal.ts` — claim-based `_migrations` journal (atomic, crash-safe)
561
+ *
562
+ * `up()` applies pending migrations in numeric order with an atomic claim;
563
+ * `down()` rolls back applied migrations in reverse order; `status()` reports
564
+ * applied/pending; `create()` scaffolds a new file.
565
+ */
566
+
567
+ /** The `service` shape the runner needs (one connected manager + config/logger). */
568
+ interface MigrationService {
569
+ db: Record<string, unknown>;
570
+ logger: LoggerLike;
571
+ config?: {
572
+ migrationDir?: string;
573
+ };
574
+ }
575
+ declare const createMongoMigrationRunner: (service: MigrationService, options?: MongoMigrationRunnerOptions) => MongoMigrationRunner;
576
+
577
+ interface RetryOptions {
578
+ maxAttempts?: number;
579
+ delayMs?: number;
580
+ logger?: LoggerLike;
581
+ }
582
+ /**
583
+ * Retry `fn` while it throws a *transient* Mongo error (network / not-primary /
584
+ * sharding), with exponential backoff `delayMs * 2^attempt`. Domain errors and
585
+ * genuine data errors are never retried.
586
+ */
587
+ declare const withRetry: <T>(fn: () => Promise<T>, options?: RetryOptions, _ctx?: unknown) => Promise<T>;
588
+ /**
589
+ * Manual transaction helper: start → run → commit, aborting + ending the
590
+ * session on any failure. Prefer the service-level `transaction()` which also
591
+ * handles capability detection and unsupported-server fallback.
592
+ */
593
+ declare const withTransaction: <T>(client: MongoClient, fn: (session: ClientSession) => Promise<T>) => Promise<T>;
594
+
595
+ /**
596
+ * Types for the read-side document-drift validator (`validateDoc`). Pure type
597
+ * declarations — the detection logic lives in `./validate.ts`,
598
+ * `./field-validators.ts` (per-kind rules), and `./helpers.ts`.
599
+ */
600
+ /**
601
+ * Read-path drift policy: how the ORM reacts to a document that doesn't match
602
+ * its declared schema when it is fetched from the DB (cache-miss).
603
+ *
604
+ * - `'off'` — do not check (pre-drift-detection behavior).
605
+ * - `'report'` — log a warning with field-level detail, return as-is.
606
+ * - `'throw'` — throw `DomainError SCHEMA_DRIFT` with the offending fields.
607
+ */
608
+ type DriftMode = 'off' | 'report' | 'throw';
609
+ type DriftIssueCode = 'missing' | 'type' | 'unknown_key' | 'constraint' | 'enum';
610
+ /** A single schema/drift violation on a stored document. */
611
+ interface DriftIssue {
612
+ /** Dot-path to the offending field (array indexes as `[n]`), e.g. `profile.address.city`. */
613
+ path: string;
614
+ code: DriftIssueCode;
615
+ /** Human-readable expectation, e.g. `string(minLength 3)`, `ObjectId`, `enum[...]`. */
616
+ expected: string;
617
+ message: string;
618
+ }
619
+
620
+ /**
621
+ * The `$jsonSchema` fragment type emitted by the ORM's schema DSL.
622
+ *
623
+ * Kept in its own module (no imports) so both the DSL (`types.ts`) and the
624
+ * converter (`to-mongo-schema.ts`) can reference it without a circular import
625
+ * — the DSL's `s.jsonSchema(fragment)` escape hatch needs the type, and the
626
+ * converter produces it.
627
+ *
628
+ * Mirrors the MongoDB `$jsonSchema` keywords this ORM supports (plus a few it
629
+ * accepts verbatim via the raw escape hatch). Unknown/unlisted keywords are not
630
+ * enforced at the type level, so a raw fragment can still be passed through
631
+ * `as MongoJsonSchema` when needed.
632
+ */
633
+ interface MongoJsonSchema {
634
+ /** A single BSON type, or an array of allowed types (e.g. ['date', 'null']). */
635
+ bsonType?: string | string[];
636
+ required?: string[];
637
+ properties?: Record<string, MongoJsonSchema>;
638
+ additionalProperties?: boolean | MongoJsonSchema;
639
+ minLength?: number;
640
+ maxLength?: number;
641
+ pattern?: string;
642
+ format?: string;
643
+ minimum?: number;
644
+ maximum?: number;
645
+ /** Draft-4 boolean form, or numeric form on newer servers. */
646
+ exclusiveMinimum?: boolean | number;
647
+ exclusiveMaximum?: boolean | number;
648
+ multipleOf?: number;
649
+ enum?: unknown[];
650
+ items?: MongoJsonSchema | MongoJsonSchema[];
651
+ minItems?: number;
652
+ maxItems?: number;
653
+ uniqueItems?: boolean;
654
+ minProperties?: number;
655
+ maxProperties?: number;
656
+ description?: string;
657
+ }
658
+
659
+ /**
660
+ * Typed schema DSL for `ninox`.
661
+ *
662
+ * Schemas are plain, serializable objects (kind + constraints + flags). They
663
+ * serve three purposes at once:
664
+ * 1. Derive the TypeScript document type (`InferDoc<T>`)
665
+ * 2. Drive the fluent query builder's field typing
666
+ * 3. Convert to a MongoDB `$jsonSchema` validator (`toMongoValidator`)
667
+ *
668
+ * Example:
669
+ * const userSchema = s.object({
670
+ * _id: s.objectId(),
671
+ * email: s.string().optional(),
672
+ * age: s.integer({ minimum: 0 }),
673
+ * role: s.enum(['admin', 'user'] as const),
674
+ * createdAt: s.date(),
675
+ * });
676
+ * type User = InferDoc<typeof userSchema>;
677
+ */
678
+
679
+ interface FieldFlags {
680
+ optional: boolean;
681
+ hasDefault: boolean;
682
+ defaultValue?: unknown;
683
+ }
684
+ interface FieldMeta {
685
+ kind: string;
686
+ flags: FieldFlags;
687
+ description?: string;
688
+ }
689
+ interface StringField extends FieldMeta {
690
+ kind: 'string';
691
+ format?: string;
692
+ minLength?: number;
693
+ maxLength?: number;
694
+ pattern?: string;
695
+ }
696
+ interface IntegerField extends FieldMeta {
697
+ kind: 'number';
698
+ integer: true;
699
+ minimum?: number;
700
+ maximum?: number;
701
+ multipleOf?: number;
702
+ /** Draft-4 boolean form: `minimum` is excluded from the allowed range. */
703
+ exclusiveMinimum?: boolean;
704
+ exclusiveMaximum?: boolean;
705
+ }
706
+ interface NumberField extends FieldMeta {
707
+ kind: 'number';
708
+ integer?: false;
709
+ minimum?: number;
710
+ maximum?: number;
711
+ multipleOf?: number;
712
+ exclusiveMinimum?: boolean;
713
+ exclusiveMaximum?: boolean;
714
+ }
715
+ /** Strict BSON `double` (IEEE 754 float64) field. */
716
+ interface DoubleField extends FieldMeta {
717
+ kind: 'double';
718
+ minimum?: number;
719
+ maximum?: number;
720
+ multipleOf?: number;
721
+ exclusiveMinimum?: boolean;
722
+ exclusiveMaximum?: boolean;
723
+ }
724
+ /** Strict BSON `long` (int64) field — inferred as `number` (precision caveat). */
725
+ interface LongField extends FieldMeta {
726
+ kind: 'long';
727
+ minimum?: number;
728
+ maximum?: number;
729
+ multipleOf?: number;
730
+ exclusiveMinimum?: boolean;
731
+ exclusiveMaximum?: boolean;
732
+ }
733
+ /** Strict BSON `decimal` (Decimal128) field — inferred as `Decimal128`. */
734
+ interface DecimalField extends FieldMeta {
735
+ kind: 'decimal';
736
+ minimum?: number;
737
+ maximum?: number;
738
+ multipleOf?: number;
739
+ exclusiveMinimum?: boolean;
740
+ exclusiveMaximum?: boolean;
741
+ }
742
+ interface BooleanField extends FieldMeta {
743
+ kind: 'boolean';
744
+ }
745
+ interface DateField extends FieldMeta {
746
+ kind: 'date';
747
+ }
748
+ interface ObjectIdField extends FieldMeta {
749
+ kind: 'objectId';
750
+ }
751
+ interface ArrayField<I extends SchemaType = SchemaType> extends FieldMeta {
752
+ kind: 'array';
753
+ items: I;
754
+ minItems?: number;
755
+ maxItems?: number;
756
+ uniqueItems?: boolean;
757
+ }
758
+ interface ObjectField<P extends Record<string, SchemaType> = Record<string, SchemaType>, N extends string = string> extends FieldMeta {
759
+ kind: 'object';
760
+ properties: P;
761
+ additionalProperties?: boolean;
762
+ minProperties?: number;
763
+ maxProperties?: number;
764
+ /**
765
+ * Optional collection name carried by the schema. When present, the schema is
766
+ * the single source of truth for the collection name — `defineCollections`
767
+ * keys the derived `collections` map by it.
768
+ */
769
+ name?: N;
770
+ }
771
+ interface EnumField<V extends readonly (string | number)[] = readonly (string | number)[]> extends FieldMeta {
772
+ kind: 'enum';
773
+ values: V;
774
+ }
775
+ interface AnyField extends FieldMeta {
776
+ kind: 'any';
777
+ }
778
+ interface NullField extends FieldMeta {
779
+ kind: 'null';
780
+ }
781
+ interface GeoPointField extends FieldMeta {
782
+ kind: 'geoPoint';
783
+ }
784
+ interface RawField extends FieldMeta {
785
+ kind: 'raw';
786
+ /** Arbitrary `$jsonSchema` fragment passed through verbatim (escape hatch). */
787
+ fragment: MongoJsonSchema;
788
+ }
789
+ type SchemaType = StringField | IntegerField | NumberField | DoubleField | LongField | DecimalField | BooleanField | DateField | ObjectIdField | ArrayField | ObjectField | EnumField | AnyField | NullField | GeoPointField | RawField;
790
+ /** A schema with chainable `.optional()` / `.default()` modifiers attached. */
791
+ type Chainable<T extends SchemaType> = T & {
792
+ optional(): Chainable<T & {
793
+ flags: {
794
+ optional: true;
795
+ };
796
+ }>;
797
+ default(value: unknown): Chainable<T & {
798
+ flags: {
799
+ hasDefault: true;
800
+ defaultValue: unknown;
801
+ };
802
+ }>;
803
+ };
804
+ /**
805
+ * Object schema factory (exposed as `s.object`). When `name` is provided the
806
+ * returned schema carries it as a required literal, so `defineCollections` can
807
+ * key the derived map by it and forget a name becomes a type error.
808
+ */
809
+ declare function objectField<P extends Record<string, SchemaType>, N extends string>(properties: P, opts: {
810
+ additionalProperties?: boolean;
811
+ minProperties?: number;
812
+ maxProperties?: number;
813
+ name: N;
814
+ }): Chainable<ObjectField<P, N> & {
815
+ name: N;
816
+ }>;
817
+ declare function objectField<P extends Record<string, SchemaType>>(properties: P, opts?: {
818
+ additionalProperties?: boolean;
819
+ minProperties?: number;
820
+ maxProperties?: number;
821
+ }): Chainable<ObjectField<P, string>>;
822
+ /** Numeric constraints shared by every numeric field kind. */
823
+ type NumberBounds = {
824
+ minimum?: number;
825
+ maximum?: number;
826
+ multipleOf?: number;
827
+ exclusiveMinimum?: boolean;
828
+ exclusiveMaximum?: boolean;
829
+ };
830
+ declare const s: {
831
+ /** UTF-8 string field (optional `format` / length / regex constraints). */
832
+ string(opts?: {
833
+ format?: string;
834
+ minLength?: number;
835
+ maxLength?: number;
836
+ pattern?: string;
837
+ }): Chainable<StringField>;
838
+ /** 32-bit integer field with optional bounds (min/max, exclusive, multipleOf). */
839
+ integer(opts?: NumberBounds): Chainable<IntegerField>;
840
+ /** Double/float field with optional bounds (min/max, exclusive, multipleOf). */
841
+ number(opts?: NumberBounds): Chainable<NumberField>;
842
+ /** Strict BSON `double` (IEEE 754 float64) field. */
843
+ double(opts?: NumberBounds): Chainable<DoubleField>;
844
+ /** Strict BSON `long` (int64) field — inferred as `number`. */
845
+ long(opts?: NumberBounds): Chainable<LongField>;
846
+ /** Strict BSON `decimal` (Decimal128) field — inferred as `Decimal128`. */
847
+ decimal(opts?: NumberBounds): Chainable<DecimalField>;
848
+ /** Boolean field. */
849
+ boolean(): Chainable<BooleanField>;
850
+ /** UTC datetime field (BSON date). */
851
+ date(): Chainable<DateField>;
852
+ /** MongoDB ObjectId field (`_id` and foreign keys). */
853
+ objectId(): Chainable<ObjectIdField>;
854
+ /** Array of `items` (nested schema types allowed), with optional length/uniqueness. */
855
+ array<I extends SchemaType>(items: I, opts?: {
856
+ minItems?: number;
857
+ maxItems?: number;
858
+ uniqueItems?: boolean;
859
+ }): Chainable<ArrayField<I>>;
860
+ object: typeof objectField;
861
+ /** Enum union of literal string/number values. */
862
+ enum<V extends readonly (string | number)[]>(values: V): Chainable<EnumField<V>>;
863
+ /** Unconstrained field (no validator bounds). */
864
+ any(): Chainable<AnyField>;
865
+ /** Explicitly-nullable field. */
866
+ null(): Chainable<NullField>;
867
+ /** GeoJSON Point field (type `Point` + `[lng, lat]` coordinates) for `2dsphere` queries. */
868
+ geoPoint(): Chainable<GeoPointField>;
869
+ /**
870
+ * Raw `$jsonSchema` escape hatch: pass an arbitrary fragment through verbatim
871
+ * to the validator. Use for Mongo keywords the DSL doesn't model (e.g.
872
+ * `patternProperties`, `allOf`, `anyOf`, `dependencies`).
873
+ */
874
+ jsonSchema(fragment: MongoJsonSchema): Chainable<RawField>;
875
+ };
876
+ /** Standalone modifier: mark a field as optional (`s.string().optional()` equivalent). */
877
+ declare const optional: <T extends SchemaType>(field: T) => Chainable<T & {
878
+ flags: {
879
+ optional: true;
880
+ };
881
+ }>;
882
+ /** Standalone modifier: attach a default value. */
883
+ declare const withDefault: <T extends SchemaType>(field: T, value: unknown) => Chainable<T & {
884
+ flags: {
885
+ hasDefault: true;
886
+ defaultValue: unknown;
887
+ };
888
+ }>;
889
+
890
+ /**
891
+ * Drift-validation orchestrator: `validateValue` (dispatch to the per-kind
892
+ * registry), `validateObjectProps` (declared properties + strict unknown-key
893
+ * handling), and the public `validateDoc` entry point.
894
+ *
895
+ * Recursion between `validateValue` and `validateObjectProps` flows through the
896
+ * `deps` object so neither this module nor `./field-validators.ts` imports the
897
+ * other (no cycle).
898
+ */
899
+
900
+ /** Check a stored document against the declared schema; returns any drift issues. */
901
+ declare const validateDoc: (schema: ObjectField, doc: Document) => DriftIssue[];
902
+
903
+ /**
904
+ * SDK-level query options. These are separated from driver options so the
905
+ * public API surface stays stable against driver changes. `resolveQueryOptions`
906
+ * strips them out and forwards the remainder untouched to the driver.
907
+ */
908
+ interface QueryOptions {
909
+ session?: ClientSession;
910
+ maxTimeMS?: number;
911
+ hint?: Hint;
912
+ /** Retry attempts for transient errors (default 3). */
913
+ maxAttempts?: number;
914
+ /** Base retry backoff in ms (default 150, exponential). */
915
+ retryDelayMs?: number;
916
+ batchSize?: number;
917
+ /**
918
+ * Per-op in-flight dedup override. `true` forces dedup on, `false` forces it
919
+ * off (even when the service default is on); unset inherits the service
920
+ * `dedupeReads` default.
921
+ */
922
+ dedupe?: boolean;
923
+ /** Per-op read-cache bypass. `false` skips the cache for this read. */
924
+ cache?: boolean;
925
+ /**
926
+ * Per-op write-retry override. Reads always retry transient errors; writes
927
+ * retry ONLY when `retryWrites: true` (retrying a write is at-least-once —
928
+ * a committed write whose ack was lost would be re-executed, so enable only
929
+ * for idempotent writes). Default: off for write ops.
930
+ */
931
+ retryWrites?: boolean /**
932
+ * Per-op schema-drift policy override for reads (see `MongoServiceConfig.drift`).
933
+ * `true` → `'throw'`, `false` → `'off'`, or an explicit `DriftMode` string.
934
+ * Unset inherits the service-level default (`'report'`). Only checked when a
935
+ * document is fetched from the DB (cache-miss); projected/partial reads are
936
+ * skipped.
937
+ */;
938
+ drift?: DriftMode | boolean /** Driver-level options forwarded untouched to the underlying call. */;
939
+ collation?: CollationOptions;
940
+ readPreference?: ReadPreferenceLike;
941
+ readConcern?: ReadConcern;
942
+ writeConcern?: WriteConcern;
943
+ comment?: string | Document;
944
+ let?: Document;
945
+ timeoutMS?: number;
946
+ }
947
+ interface ResolvedQueryOptions<TDriverOpts = Record<string, unknown>> {
948
+ maxAttempts: number;
949
+ retryDelayMs: number;
950
+ /** Tri-state: `undefined` inherits the service-level default. */
951
+ dedupe?: boolean;
952
+ /** Tri-state: `undefined` inherits the service-level default. */
953
+ cache?: boolean;
954
+ /** Write ops retry transient errors only when this is `true`. */
955
+ retryWrites?: boolean;
956
+ sdk: {
957
+ session?: ClientSession;
958
+ maxTimeMS?: number;
959
+ hint?: Hint;
960
+ batchSize?: number;
961
+ drift?: DriftMode | boolean;
962
+ };
963
+ driverOpts: TDriverOpts;
964
+ }
965
+
966
+ /**
967
+ * Strict, schema-aware query filter.
968
+ *
969
+ * The driver's own `Filter<T>` is deliberately loose: its `RootFilterOperators`
970
+ * extends `Document` (`{ [key: string]: any }`), so `Filter<PreciseDoc>` still
971
+ * accepts ANY top-level key. This type re-declares the field-level portion of
972
+ * the filter plus the root operators WITHOUT inheriting that index signature,
973
+ * so a typo'd or undeclared field is a compile error on object literals.
974
+ *
975
+ * It is a strict subset of the driver's `Filter<T>`, so the ORM bridges it with
976
+ * a cast only at the driver boundary.
977
+ */
978
+
979
+ /** Field-level portion of a filter: exact match, `$`-operators, or nested. */
980
+ type FieldFilters<T> = {
981
+ [P in keyof WithId<T>]?: Condition<WithId<T>[P]>;
982
+ };
983
+ /** Root operators, mirroring the driver's `RootFilterOperators` without `Document`'s index signature. */
984
+ type RootOperators<T> = {
985
+ $and?: FilterInput<T>[];
986
+ $nor?: FilterInput<T>[];
987
+ $or?: FilterInput<T>[];
988
+ $text?: RootFilterOperators<T>['$text'];
989
+ $where?: RootFilterOperators<T>['$where'];
990
+ $comment?: RootFilterOperators<T>['$comment'];
991
+ };
992
+ type FilterInput<T> = FieldFilters<T> & RootOperators<T>;
993
+
994
+ /**
995
+ * Execution context provided by the CRUD layer. `run` participates in the
996
+ * uniform trace + retry + cache + in-flight-dedup pipeline and returns the
997
+ * merged driver options to the execute closure.
998
+ */
999
+ interface QueryBuilderContext<TDoc extends Document> {
1000
+ physicalName: string;
1001
+ collection: Collection<TDoc>;
1002
+ run: <T>(opName: string, filter: FilterInput<TDoc>, execute: (driverOpts: FindOptions) => Promise<T>, options?: QueryOptions) => Promise<T>;
1003
+ }
1004
+ /**
1005
+ * Lazy, fluent, schema-typed query builder. Nothing hits the database until an
1006
+ * executor (`one`/`many`/`cursor`/`count`/`exists`) is called. Projections via
1007
+ * `select` are pushed to the driver so only requested fields are transferred.
1008
+ *
1009
+ * await users.query('users')
1010
+ * .where({ role: 'admin' })
1011
+ * .sort({ createdAt: -1 })
1012
+ * .select(['_id', 'email'])
1013
+ * .limit(20)
1014
+ * .many();
1015
+ */
1016
+ declare class QueryBuilder<TDoc extends Document, TSelection = TDoc> {
1017
+ private readonly ctx;
1018
+ private _filter;
1019
+ private _sort?;
1020
+ private _skip?;
1021
+ private _limit?;
1022
+ private _projection?;
1023
+ private _options;
1024
+ constructor(ctx: QueryBuilderContext<TDoc>);
1025
+ get filter(): FilterInput<TDoc>;
1026
+ /** Shallow-merge a filter into the current one (later keys win). */
1027
+ where(filter: FilterInput<TDoc>): this;
1028
+ /** AND-combine the current filter with a new one. */
1029
+ and(filter: FilterInput<TDoc>): this;
1030
+ /** OR-combine: current filter AND (filter1 OR filter2 ...). */
1031
+ or(...filters: Array<FilterInput<TDoc>>): this;
1032
+ /** Set the sort order (`{ field: 1 | -1 }`). */
1033
+ sort(sort: Sort): this;
1034
+ /** Skip the first `n` matching documents. */
1035
+ skip(n: number): this;
1036
+ /** Limit the result set to `n` documents. */
1037
+ limit(n: number): this;
1038
+ /** Project only the given fields — the projection is pushed to the driver. */
1039
+ select<const K extends keyof TSelection>(fields: readonly K[]): QueryBuilder<TDoc, Pick<TSelection, K>>;
1040
+ /** Raw projection document (`{ field: 1 }` / `{ field: 0 }`). */
1041
+ project(projection: Document): this;
1042
+ /** Set the index hint. */
1043
+ hint(hint: Hint): this;
1044
+ /** Set the driver cursor batch size. */
1045
+ batchSize(n: number): this;
1046
+ /** Run the query inside a transaction session. */
1047
+ session(session: NonNullable<QueryOptions['session']>): this;
1048
+ /** Cap server-side execution time. */
1049
+ maxTimeMS(ms: number): this;
1050
+ /** Bypass the read cache for this query when `false`. */
1051
+ cache(on: boolean): this;
1052
+ /** Override in-flight dedup for this query when set. */
1053
+ dedupe(on: boolean): this;
1054
+ /** Compile the builder into driver options (does not execute). */
1055
+ private compile;
1056
+ private countOptions;
1057
+ /** Fetch one document (limit forced to 1). */
1058
+ one(): Promise<WithId<TSelection> | null>;
1059
+ /** Fetch many documents (default limit 100 when unset). */
1060
+ many(): Promise<WithId<TSelection>[]>;
1061
+ /** Streaming cursor (raw driver cursor; no retry, applies batchSize). */
1062
+ cursor(): FindCursor<WithId<TDoc>>;
1063
+ /** Count matching documents. */
1064
+ count(): Promise<number>;
1065
+ /** True when at least one document matches. */
1066
+ exists(): Promise<boolean>;
1067
+ }
1068
+
1069
+ /**
1070
+ * Shared type-level utilities (no runtime code).
1071
+ */
1072
+ /**
1073
+ * Strip index-signature keys (e.g. `Document`'s `[key: string]`) from a type,
1074
+ * leaving only its explicitly declared fields.
1075
+ */
1076
+ type RemoveIndexSignature<T> = {
1077
+ [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K];
1078
+ };
1079
+ /** GeoJSON Point as stored in MongoDB (used by `s.geoPoint()` and `$geoNear`). */
1080
+ type GeoPoint = {
1081
+ type: 'Point';
1082
+ coordinates: [number, number];
1083
+ };
1084
+
1085
+ /**
1086
+ * Type-level inference from the schema DSL → the document's TypeScript type.
1087
+ *
1088
+ * type User = InferDoc<typeof userSchema>;
1089
+ *
1090
+ * `optional`/`default` fields become `T | undefined`; everything else is required.
1091
+ */
1092
+ type InferField<T extends SchemaType> = T extends {
1093
+ kind: 'string';
1094
+ } ? string : T extends {
1095
+ kind: 'number';
1096
+ } ? number : T extends {
1097
+ kind: 'boolean';
1098
+ } ? boolean : T extends {
1099
+ kind: 'date';
1100
+ } ? Date : T extends {
1101
+ kind: 'objectId';
1102
+ } ? ObjectId : T extends {
1103
+ kind: 'double';
1104
+ } ? number : T extends {
1105
+ kind: 'long';
1106
+ } ? number : T extends {
1107
+ kind: 'decimal';
1108
+ } ? Decimal128 : T extends {
1109
+ kind: 'geoPoint';
1110
+ } ? GeoPoint : T extends {
1111
+ kind: 'array';
1112
+ } ? InferField<T['items']>[] : T extends {
1113
+ kind: 'object';
1114
+ } ? InferObject<Extract<T, ObjectField>> : T extends {
1115
+ kind: 'enum';
1116
+ } ? T['values'][number] : T extends {
1117
+ kind: 'null';
1118
+ } ? null : T extends {
1119
+ kind: 'any';
1120
+ } ? unknown : T extends {
1121
+ kind: 'raw';
1122
+ } ? unknown : never;
1123
+ /** True when a field is optional (`.optional()`) or defaulted (`.default(v)`). */
1124
+ type IsOptionalProp<T extends SchemaType> = T['flags']['optional'] extends true ? true : T['flags']['hasDefault'] extends true ? true : false;
1125
+ /**
1126
+ * Optional/default fields become *optional keys* (`qty?: number`) rather than
1127
+ * required-but-`| undefined`, so documents can be built without them.
1128
+ */
1129
+ type InferObject<T extends ObjectField> = {
1130
+ [K in keyof T['properties'] as IsOptionalProp<T['properties'][K]> extends true ? never : K]: InferField<T['properties'][K]>;
1131
+ } & {
1132
+ [K in keyof T['properties'] as IsOptionalProp<T['properties'][K]> extends true ? K : never]?: InferField<T['properties'][K]>;
1133
+ };
1134
+ type InferDoc<T extends ObjectField> = InferObject<T>;
1135
+
1136
+ /** A Mongo index definition usable by `createIndexes`. */
1137
+ interface IndexSpec {
1138
+ key: Record<string, 1 | -1 | 'text' | 'hashed' | '2dsphere'>;
1139
+ options?: {
1140
+ name?: string;
1141
+ unique?: boolean;
1142
+ sparse?: boolean;
1143
+ expireAfterSeconds?: number;
1144
+ };
1145
+ }
1146
+ /** Auto-timestamp field names maintained by the ORM when `timestamps` is set. */
1147
+ interface CollectionTimestamps {
1148
+ /** Field stamped with the creation time on insert (default `createdAt`). */
1149
+ createdAt?: string;
1150
+ /** Field stamped with the modification time on every update (default `updatedAt`). */
1151
+ updatedAt?: string;
1152
+ }
1153
+ /**
1154
+ * Collection definition. A value of `collections` may be either a raw object
1155
+ * schema (`s.object({...})`) or a `CollectionDefinition` carrying optional
1156
+ * indexes + hooks.
1157
+ */
1158
+ interface CollectionDefinition<TSchema extends ObjectField = ObjectField> {
1159
+ schema: TSchema;
1160
+ indexes?: IndexSpec[];
1161
+ hooks?: HookMap<InferDoc<TSchema>>;
1162
+ /**
1163
+ * Auto-maintain timestamps on create/update. `true` uses the defaults
1164
+ * (`createdAt`/`updatedAt`); an object names custom fields. Fields must exist
1165
+ * in the schema for the values to be stamped.
1166
+ */
1167
+ timestamps?: CollectionTimestamps | boolean;
1168
+ }
1169
+ type CollectionLike = CollectionDefinition<any> | ObjectField;
1170
+ /**
1171
+ * One logical Mongo database. Keys of `collections` are the *logical* collection
1172
+ * names used in every ORM call; physical names are derived via
1173
+ * `collectionPrefix` / `collectionPhysicalNames`.
1174
+ */
1175
+ interface DBClientDefinition<TCollections extends Record<string, CollectionLike> = Record<string, CollectionLike>> {
1176
+ /** Actual MongoDB database name. */
1177
+ name: string;
1178
+ /** Connection string. Falls back to env `DB_<CONSTANT_CASE_KEY>`, then config.defaultDb. */
1179
+ dbUrl?: string;
1180
+ collections: TCollections;
1181
+ /** Prefix applied to every logical collection name to produce the physical name. */
1182
+ collectionPrefix?: string;
1183
+ collectionPrefixSeparator?: string;
1184
+ /** Per-logical-collection physical-name overrides. */
1185
+ collectionPhysicalNames?: Partial<Record<string, string>>;
1186
+ connectTimeoutMs?: number;
1187
+ readPreference?: ReadPreferenceLike;
1188
+ readConcern?: ReadConcernLike;
1189
+ writeConcern?: WriteConcern;
1190
+ }
1191
+ type DbClientsDefinition = Record<string, DBClientDefinition<Record<string, CollectionLike>>>;
1192
+ type CollectionDoc<T> = T extends CollectionDefinition<infer S> ? InferDoc<S> : T extends ObjectField ? InferDoc<T> : never;
1193
+ type ExtractDbNames<TClients extends DbClientsDefinition> = keyof TClients & string;
1194
+ type ExtractCollectionNames<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>> = keyof TClients[TDb]['collections'] & string;
1195
+ type ExtractCollectionType<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, TCollection extends ExtractCollectionNames<TClients, TDb>> = CollectionDoc<TClients[TDb]['collections'][TCollection]>;
1196
+
1197
+ /**
1198
+ * Relation definitions used by `populate`. Relations declare how documents in
1199
+ * one collection reference documents in another; `populate` then resolves them
1200
+ * with DataLoader-batched `$in` queries (1 + N queries total, not 1 + N×M).
1201
+ */
1202
+
1203
+ interface RelationDefBase {
1204
+ /** Foreign logical collection. */
1205
+ collection: string;
1206
+ /** Field on the source document holding the reference key(s). */
1207
+ localField: string;
1208
+ /** Field on the foreign document to match (default `_id`). */
1209
+ foreignField?: string;
1210
+ /** Field name attached to the source document after population. */
1211
+ as: string;
1212
+ /** Optional: max keys per batched `$in` query. */
1213
+ maxBatchSize?: number;
1214
+ /** Optional: cache resolved relations on the loader (default true). */
1215
+ cache?: boolean;
1216
+ /** When true, a missing belongsTo target attaches `undefined` instead of `null`. */
1217
+ optional?: boolean;
1218
+ }
1219
+ interface BelongsToRelation extends RelationDefBase {
1220
+ type: 'belongsTo';
1221
+ }
1222
+ interface HasManyRelation extends RelationDefBase {
1223
+ type: 'hasMany';
1224
+ }
1225
+ interface ManyToManyRelation extends RelationDefBase {
1226
+ type: 'manyToMany';
1227
+ through: {
1228
+ /** Join collection (logical). */
1229
+ collection: string;
1230
+ /** Field on the join doc pointing at the source doc. */
1231
+ localField: string;
1232
+ /** Field on the join doc pointing at the target doc. */
1233
+ foreignField: string;
1234
+ };
1235
+ }
1236
+ type RelationDef = BelongsToRelation | HasManyRelation | ManyToManyRelation;
1237
+ type RelColNames<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>> = ExtractCollectionNames<TClients, TDb>;
1238
+ type RelDocOf<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, C extends RelColNames<TClients, TDb>> = ExtractCollectionType<TClients, TDb, C>;
1239
+ /** Field-name union of a doc, excluding Mongo `Document`'s index-signature keys. */
1240
+ type RelField<T> = Extract<keyof RemoveIndexSignature<T>, string>;
1241
+ /** Validates one relation object `R` against the source doc and the registry. */
1242
+ type ValidRelation<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, TSource extends Document, R> = R extends {
1243
+ type: 'belongsTo';
1244
+ collection: infer C;
1245
+ } ? C extends RelColNames<TClients, TDb> ? Omit<R, 'localField' | 'foreignField'> & {
1246
+ localField: RelField<TSource>;
1247
+ foreignField?: RelField<RelDocOf<TClients, TDb, C>>;
1248
+ } : never : R extends {
1249
+ type: 'hasMany';
1250
+ collection: infer C;
1251
+ } ? C extends RelColNames<TClients, TDb> ? Omit<R, 'localField' | 'foreignField'> & {
1252
+ localField: RelField<TSource>;
1253
+ foreignField?: RelField<RelDocOf<TClients, TDb, C>>;
1254
+ } : never : R extends {
1255
+ type: 'manyToMany';
1256
+ collection: infer C;
1257
+ through: {
1258
+ collection: infer TC;
1259
+ };
1260
+ } ? C extends RelColNames<TClients, TDb> ? TC extends RelColNames<TClients, TDb> ? Omit<R, 'localField' | 'foreignField' | 'through'> & {
1261
+ localField: RelField<TSource>;
1262
+ foreignField?: RelField<RelDocOf<TClients, TDb, C>>;
1263
+ through: {
1264
+ collection: TC;
1265
+ localField: RelField<RelDocOf<TClients, TDb, TC>>;
1266
+ foreignField: RelField<RelDocOf<TClients, TDb, TC>>;
1267
+ };
1268
+ } : never : never : never;
1269
+ /**
1270
+ * Maps a tuple of relations to the joined `as` fields attached to result docs.
1271
+ * `belongsTo` resolves to `Doc | null`; `hasMany` / `manyToMany` to `Doc[]`.
1272
+ */
1273
+ type JoinedFields<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, R extends readonly unknown[]> = {
1274
+ [K in keyof R as K extends `${number}` ? R[K] extends {
1275
+ as: infer A extends string;
1276
+ } ? A : never : never]: R[K] extends {
1277
+ type: 'belongsTo';
1278
+ collection: infer C;
1279
+ } ? C extends RelColNames<TClients, TDb> ? RelDocOf<TClients, TDb, C> | null : never : R[K] extends {
1280
+ type: 'hasMany' | 'manyToMany';
1281
+ collection: infer C;
1282
+ } ? C extends RelColNames<TClients, TDb> ? RelDocOf<TClients, TDb, C>[] : never : never;
1283
+ };
1284
+ type BelongsToConfig<C extends string, A extends string, L extends string, F extends string> = Omit<BelongsToRelation, 'type' | 'collection' | 'as' | 'localField' | 'foreignField'> & {
1285
+ collection: C;
1286
+ as: A;
1287
+ localField: L;
1288
+ foreignField?: F;
1289
+ };
1290
+ type HasManyConfig<C extends string, A extends string, L extends string, F extends string> = Omit<HasManyRelation, 'type' | 'collection' | 'as' | 'localField' | 'foreignField'> & {
1291
+ collection: C;
1292
+ as: A;
1293
+ localField: L;
1294
+ foreignField?: F;
1295
+ };
1296
+ type ManyToManyConfig<C extends string, A extends string, L extends string, TC extends string, F extends string, TLoc extends string, TFor extends string> = Omit<ManyToManyRelation, 'type' | 'collection' | 'as' | 'localField' | 'foreignField' | 'through'> & {
1297
+ collection: C;
1298
+ as: A;
1299
+ localField: L;
1300
+ foreignField?: F;
1301
+ through: {
1302
+ collection: TC;
1303
+ localField: TLoc;
1304
+ foreignField: TFor;
1305
+ };
1306
+ };
1307
+ /** One source doc → at most one foreign doc (defaults `foreignField: '_id'`). */
1308
+ declare const belongsTo: <const C extends string, const A extends string, const L extends string, const F extends string = "_id">(config: BelongsToConfig<C, A, L, F>) => BelongsToRelation & {
1309
+ collection: C;
1310
+ as: A;
1311
+ localField: L;
1312
+ foreignField: F;
1313
+ };
1314
+ /** One source doc → many foreign docs (defaults `foreignField: '_id'`). */
1315
+ declare const hasMany: <const C extends string, const A extends string, const L extends string, const F extends string = "_id">(config: HasManyConfig<C, A, L, F>) => HasManyRelation & {
1316
+ collection: C;
1317
+ as: A;
1318
+ localField: L;
1319
+ foreignField: F;
1320
+ };
1321
+ /** Source doc → join docs → target docs through a pivot collection. */
1322
+ declare const manyToMany: <const C extends string, const A extends string, const L extends string, const TC extends string, const F extends string = "_id", const TLoc extends string = "_id", const TFor extends string = "_id">(config: ManyToManyConfig<C, A, L, TC, F, TLoc, TFor>) => ManyToManyRelation & {
1323
+ collection: C;
1324
+ as: A;
1325
+ localField: L;
1326
+ foreignField: F;
1327
+ through: {
1328
+ collection: TC;
1329
+ localField: TLoc;
1330
+ foreignField: TFor;
1331
+ };
1332
+ };
1333
+
1334
+ interface PopulateOptions {
1335
+ maxBatchSize?: number;
1336
+ cache?: boolean;
1337
+ /**
1338
+ * Cap on docs fetched per foreign-key batch (default 10_000). A `hasMany` /
1339
+ * `manyToMany` relation with more children than this is silently truncated —
1340
+ * raise the cap when you expect larger joins.
1341
+ */
1342
+ maxDocs?: number;
1343
+ }
1344
+
1345
+ /** Structured metadata attached to every logged DB operation. */
1346
+ interface DbOpMeta {
1347
+ collection: string;
1348
+ physicalCollection?: string;
1349
+ db: string;
1350
+ op: string;
1351
+ }
1352
+
1353
+ interface CrudOpDeps<TCollection> {
1354
+ trace: <T>(meta: DbOpMeta, fn: () => T | Promise<T>) => Promise<T>;
1355
+ meta: (collection: TCollection, op: string) => DbOpMeta;
1356
+ }
1357
+
1358
+ /**
1359
+ * Self-declared pipeline stage types (no ODM dependency). A stage is any object
1360
+ * with a `$`-operator key; `Document` is the escape hatch for custom stages.
1361
+ */
1362
+ type PipelineStage = Document;
1363
+ type FacetPipelineStage = Document;
1364
+ /** Real top-level field names of a document (index-signature keys excluded). */
1365
+ type FieldName<T> = Extract<keyof RemoveIndexSignature<T>, string>;
1366
+ /** `$`-prefixed reference to a real field, e.g. `$status`. */
1367
+ type FieldRef<T> = `$${FieldName<T>}`;
1368
+ /** Value allowed in a `$project` / `$addFields` / `$set` document. */
1369
+ type ProjectionValue = boolean | number | string | null | Document;
1370
+ /**
1371
+ * A `$project` / `$addFields` / `$set` spec. Known fields autocomplete;
1372
+ * arbitrary computed / new field names are allowed.
1373
+ */
1374
+ type Projection<TDoc> = {
1375
+ [K in FieldName<TDoc>]?: ProjectionValue;
1376
+ } & Record<string, ProjectionValue>;
1377
+ /** Result type of a single projection / expression value. */
1378
+ type ExprResult<TDoc, V> = V extends `$${infer F}` ? F extends FieldName<TDoc> ? TDoc[F & keyof TDoc] : unknown : unknown;
1379
+ type IncludeKeys<P> = {
1380
+ [K in keyof P]: P[K] extends 1 ? K : P[K] extends true ? K : never;
1381
+ }[keyof P];
1382
+ type ExcludeKeys<P> = {
1383
+ [K in keyof P]: P[K] extends 0 ? K : P[K] extends false ? K : never;
1384
+ }[keyof P];
1385
+ type HasInclusions<P> = IncludeKeys<P> extends never ? false : true;
1386
+ type ComputedFields<TDoc, P> = {
1387
+ [K in Exclude<keyof P, FieldName<TDoc>>]: ExprResult<TDoc, P[K]>;
1388
+ };
1389
+ /** Pick the included fields, keeping `_id` unless the projection drops it. */
1390
+ type PickedWithId<TDoc, P> = Pick<TDoc, IncludeKeys<P> & keyof TDoc> & ('_id' extends keyof TDoc ? '_id' extends IncludeKeys<P> | ExcludeKeys<P> ? unknown : Pick<TDoc, '_id'> : unknown);
1391
+ /**
1392
+ * Output document of a `$project`. With inclusions (`1`) the output keeps only
1393
+ * those fields (+ `_id`); otherwise it is the input minus exclusions (`0`),
1394
+ * plus any computed new fields (typed when they are `$field` references).
1395
+ */
1396
+ type Projected<TDoc extends Document, P> = HasInclusions<P> extends true ? PickedWithId<TDoc, P> & ComputedFields<TDoc, P> : Omit<TDoc, ExcludeKeys<P> & keyof TDoc> & ComputedFields<TDoc, P>;
1397
+ /** Output of `$addFields` / `$set`: the input plus added / overridden fields. */
1398
+ type Added<TDoc, P> = TDoc & {
1399
+ [K in keyof P]: ExprResult<TDoc, P[K]>;
1400
+ };
1401
+ /** Output of `$unset` (fields removed). */
1402
+ type Unsetted<TDoc, F> = Omit<TDoc, UnsetKeys<F> & keyof TDoc>;
1403
+ type UnsetKeys<F> = F extends readonly (infer E)[] ? E extends string ? E : never : F extends string ? F : never;
1404
+ /** `$sort` spec: real fields autocomplete; arbitrary paths allowed. */
1405
+ type SortKeys<TDoc> = {
1406
+ [K in FieldName<TDoc>]?: 1 | -1;
1407
+ } & Record<string, 1 | -1>;
1408
+ /**
1409
+ * `$geoNear` spec — MongoDB requires `$geoNear` to be the FIRST pipeline stage
1410
+ * and a `2dsphere` (or `2d`) index on the location field.
1411
+ */
1412
+ interface GeoNearSpec {
1413
+ /** GeoJSON Point or [longitude, latitude] near which to search. */
1414
+ near: GeoPoint | [number, number];
1415
+ /** Field name written with the computed distance (meters when `spherical`). */
1416
+ distanceField: string;
1417
+ maxDistance?: number;
1418
+ minDistance?: number;
1419
+ spherical?: boolean;
1420
+ distanceMultiplier?: number;
1421
+ includeLocs?: string;
1422
+ key?: string;
1423
+ query?: Document;
1424
+ }
1425
+ /** Output of `$geoNear`: the input doc plus the computed distance field. */
1426
+ type GeoNearOutput<TDoc extends Document, S extends GeoNearSpec> = TDoc & {
1427
+ [K in S['distanceField']]: number;
1428
+ };
1429
+ /** Value accepted by `$group` accumulators. */
1430
+ type AccumulatorSpec = {
1431
+ $sum: number | string | Document | null;
1432
+ } | {
1433
+ $avg: number | string | Document | null;
1434
+ } | {
1435
+ $first: number | string | Document | null;
1436
+ } | {
1437
+ $last: number | string | Document | null;
1438
+ } | {
1439
+ $max: number | string | Document | null;
1440
+ } | {
1441
+ $min: number | string | Document | null;
1442
+ } | {
1443
+ $push: number | string | Document | null;
1444
+ } | {
1445
+ $addToSet: number | string | Document | null;
1446
+ } | {
1447
+ $count: Record<string, never>;
1448
+ };
1449
+ /**
1450
+ * `$group` spec: `_id` autocompletes real fields (or a bare name / `null` /
1451
+ * an arbitrary expression document); other keys are accumulators.
1452
+ */
1453
+ type GroupSpec<TDoc> = {
1454
+ _id: FieldRef<TDoc> | FieldName<TDoc> | null | Document;
1455
+ } & Record<string, AccumulatorSpec | FieldRef<TDoc> | FieldName<TDoc> | null | Document>;
1456
+ type DollarField<TDoc, V> = V extends `$${infer F}` ? F extends FieldName<TDoc> ? TDoc[F & keyof TDoc] : unknown : V extends Document ? unknown : V;
1457
+ /** Resolved type of a `$group` accumulator expression. */
1458
+ type AccumulatorResult<TDoc, A> = A extends {
1459
+ $sum: unknown;
1460
+ } ? number : A extends {
1461
+ $avg: unknown;
1462
+ } ? number : A extends {
1463
+ $count: unknown;
1464
+ } ? number : A extends {
1465
+ $first: infer V;
1466
+ } ? DollarField<TDoc, V> : A extends {
1467
+ $last: infer V;
1468
+ } ? DollarField<TDoc, V> : A extends {
1469
+ $max: infer V;
1470
+ } ? DollarField<TDoc, V> : A extends {
1471
+ $min: infer V;
1472
+ } ? DollarField<TDoc, V> : A extends {
1473
+ $push: infer V;
1474
+ } ? DollarField<TDoc, V>[] : A extends {
1475
+ $addToSet: infer V;
1476
+ } ? DollarField<TDoc, V>[] : unknown;
1477
+ /** Output document of a `$group`: `_id` + one field per accumulator. */
1478
+ type Grouped<TDoc, G extends GroupSpec<TDoc>> = {
1479
+ _id: G['_id'] extends FieldRef<TDoc> ? DollarField<TDoc, G['_id']> : G['_id'] extends FieldName<TDoc> ? G['_id'] extends keyof TDoc ? TDoc[G['_id']] : unknown : G['_id'] extends null ? null : unknown;
1480
+ } & {
1481
+ [K in Exclude<keyof G, '_id'>]: AccumulatorResult<TDoc, G[K]>;
1482
+ };
1483
+ /** `$unwind` argument (string path or object form). */
1484
+ type UnwindSpec = string | {
1485
+ path: string;
1486
+ includeArrayIndex?: string;
1487
+ preserveNullAndEmptyArrays?: boolean;
1488
+ };
1489
+ type UnwindScalar<TDoc, F extends string> = F extends keyof TDoc ? TDoc[F] extends readonly (infer E)[] ? E : TDoc[F] : unknown;
1490
+ /** Output of `$unwind`: the unwound array field becomes its element type. */
1491
+ type Unwound<TDoc, P extends UnwindSpec> = P extends string ? P extends `$${infer F}` ? TDoc & {
1492
+ [K in F]: UnwindScalar<TDoc, F>;
1493
+ } : TDoc : P extends {
1494
+ path: `$${infer F}`;
1495
+ preserveNullAndEmptyArrays: true;
1496
+ } ? TDoc & {
1497
+ [K in F]: UnwindScalar<TDoc, F> | null;
1498
+ } : P extends {
1499
+ path: `$${infer F}`;
1500
+ } ? TDoc & {
1501
+ [K in F]: UnwindScalar<TDoc, F>;
1502
+ } : TDoc;
1503
+ /** Foreign collection's document type for a `$lookup.from`. */
1504
+ type ForeignDocOf<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, C> = C extends ExtractCollectionNames<TClients, TDb> ? ExtractCollectionType<TClients, TDb, C> : never;
1505
+ /**
1506
+ * `$lookup` spec keyed by a concrete foreign collection `F`. `from` is
1507
+ * validated as a known collection, `localField` a field on the source doc,
1508
+ * `foreignField` a field on the foreign doc.
1509
+ *
1510
+ * `F` is inferred from `from` as its own type parameter so the sub-pipeline
1511
+ * callback can be contextually typed against the foreign doc without depending
1512
+ * on the (not-yet-inferred) whole spec — this avoids the implicit-`any` pitfall
1513
+ * of an `S & Valid<..., S>` intersection.
1514
+ */
1515
+ type LookupSpecFor<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, TDoc extends Document, F extends ExtractCollectionNames<TClients, TDb>> = {
1516
+ from: F;
1517
+ localField: FieldName<TDoc>;
1518
+ foreignField?: FieldName<ForeignDocOf<TClients, TDb, F>>;
1519
+ as: string;
1520
+ let?: Record<string, unknown>;
1521
+ };
1522
+ /** Source doc joined with the `$lookup` output under its `as` field. */
1523
+ type LookupJoinedFor<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, TDoc extends Document, F extends ExtractCollectionNames<TClients, TDb>, A extends string, O extends Document = ForeignDocOf<TClients, TDb, F>> = TDoc & {
1524
+ [K in A]: O[];
1525
+ };
1526
+ /**
1527
+ * `$facet` spec: each named branch is a raw pipeline or a function receiving a
1528
+ * builder scoped to the current doc.
1529
+ */
1530
+ type FacetSpec<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, TDoc extends Document> = Record<string, Document[] | ((sub: PipelineBuilder<TClients, TDb, TDoc>) => PipelineBuilder<TClients, TDb, any> | Document[])>;
1531
+ /** Output of `$facet`: one array per branch, typed when built with a builder. */
1532
+ type FacetOutput<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, TDoc extends Document, F extends FacetSpec<TClients, TDb, TDoc>> = {
1533
+ [K in keyof F]: F[K] extends (s: any) => infer R ? R extends PipelineBuilder<any, any, infer O> ? O[] : unknown[] : unknown[];
1534
+ };
1535
+ /** Backward-compatible runtime shape for the callback `aggregate().lookup(...)`. */
1536
+ interface LookupParams<TDoc extends Document = Document> {
1537
+ from: string;
1538
+ localField: FieldName<TDoc> | string;
1539
+ foreignField: string;
1540
+ as: string;
1541
+ pipeline?: Document[];
1542
+ let?: Record<string, unknown>;
1543
+ }
1544
+
1545
+ /**
1546
+ * Fully type-safe, chainable aggregation pipeline builder.
1547
+ *
1548
+ * Every stage is typed against the current document shape, so intellisense
1549
+ * follows the real schema fields and the result type of `.toArray()` /
1550
+ * `.first()` is inferred through the whole chain. `$lookup` sub-pipelines are
1551
+ * scoped to the related collection's schema and `$facet` branches get their own
1552
+ * typed sub-builder — see `src/service/pipeline-types.ts` for the type model.
1553
+ *
1554
+ * const top = await db.pipeline('orders')
1555
+ * .match({ status: 'paid' })
1556
+ * .group({ _id: '$userId', total: { $sum: '$total' } })
1557
+ * .sort({ total: -1 })
1558
+ * .limit(3)
1559
+ * .toArray();
1560
+ */
1561
+
1562
+ /** Execution dependencies wired up by `makeAggregationOps`. */
1563
+ interface PipelineBuilderDeps {
1564
+ logical: string;
1565
+ coll: () => Collection<Document>;
1566
+ crudDeps: CrudOpDeps<string>;
1567
+ mergeDriver: (driverOpts: Record<string, unknown>, sdk: {
1568
+ session?: ClientSession;
1569
+ maxTimeMS?: number;
1570
+ hint?: Hint;
1571
+ }) => AggregateOptions;
1572
+ options?: AggregateOptions & QueryOptions;
1573
+ }
1574
+ declare class PipelineBuilder<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, TDoc extends Document> {
1575
+ private readonly deps;
1576
+ private readonly stages;
1577
+ constructor(deps: PipelineBuilderDeps, stages?: Document[]);
1578
+ /** Raw accumulated stages — embeddable into `$lookup` / `$facet` branches. */
1579
+ raw(): Document[];
1580
+ private assertNonNegativeFinite;
1581
+ match(filter: FilterInput<TDoc>): this;
1582
+ sort(sort: SortKeys<TDoc>): this;
1583
+ limit(n: number): this;
1584
+ skip(n: number): this;
1585
+ sample(n: number): this;
1586
+ /**
1587
+ * `$geoNear` — requires a `2dsphere`/`2d` index and MUST be the first stage.
1588
+ * Adds the computed distance to `spec.distanceField` on every output doc.
1589
+ */
1590
+ geoNear<const S extends GeoNearSpec>(spec: S): PipelineBuilder<TClients, TDb, GeoNearOutput<TDoc, S>>;
1591
+ project<const P extends Projection<TDoc>>(projection: P): PipelineBuilder<TClients, TDb, Projected<TDoc, P>>;
1592
+ addFields<const P extends Projection<TDoc>>(fields: P): PipelineBuilder<TClients, TDb, Added<TDoc, P>>;
1593
+ set<const P extends Projection<TDoc>>(fields: P): PipelineBuilder<TClients, TDb, Added<TDoc, P>>;
1594
+ unset<const F extends string | readonly string[]>(fields: F): PipelineBuilder<TClients, TDb, Unsetted<TDoc, F>>;
1595
+ count<const F extends string>(field: F): PipelineBuilder<TClients, TDb, {
1596
+ [K in F]: number;
1597
+ }>;
1598
+ group<const G extends GroupSpec<TDoc>>(group: G): PipelineBuilder<TClients, TDb, Grouped<TDoc, G>>;
1599
+ unwind<const P extends UnwindSpec>(path: P): PipelineBuilder<TClients, TDb, Unwound<TDoc, P>>;
1600
+ lookup<const F extends ExtractCollectionNames<TClients, TDb>, const A extends string, const O extends Document = ForeignDocOf<TClients, TDb, F>>(spec: LookupSpecFor<TClients, TDb, TDoc, F> & {
1601
+ as: A;
1602
+ pipeline?: Document[] | ((sub: PipelineBuilder<TClients, TDb, ForeignDocOf<TClients, TDb, F>>) => PipelineBuilder<TClients, TDb, O> | Document[]);
1603
+ }): PipelineBuilder<TClients, TDb, LookupJoinedFor<TClients, TDb, TDoc, F, A, O>>;
1604
+ facet<const F extends FacetSpec<TClients, TDb, TDoc>>(facets: F): PipelineBuilder<TClients, TDb, FacetOutput<TClients, TDb, TDoc, F>>;
1605
+ /** Run the pipeline and return every output document. */
1606
+ toArray(): Promise<TDoc[]>;
1607
+ /** Run the pipeline and return the first output document (or null). */
1608
+ first(): Promise<TDoc | null>;
1609
+ /** Streaming cursor (raw driver cursor; no retry / trace). */
1610
+ cursor(): AggregationCursor<TDoc>;
1611
+ private derive;
1612
+ private evalLookup;
1613
+ private evalFacets;
1614
+ }
1615
+
1616
+ /**
1617
+ * Typed aggregation-stage builder for the `db.aggregate(collection, (stages) =>
1618
+ * [...])` callback API. Each stage is validated against the source document
1619
+ * (`match` fields autocomplete and typo'd fields are compile errors), `$lookup`
1620
+ * sub-pipelines are scoped to the related collection, and `$facet` branches get
1621
+ * their own typed sub-builder. For inferred result types use the chained
1622
+ * `db.pipeline()` builder instead (`src/service/pipeline-builder.ts`).
1623
+ */
1624
+ interface AggregationStages<TDoc extends Document = Document, TClients extends DbClientsDefinition = DbClientsDefinition, TDb extends ExtractDbNames<TClients> = ExtractDbNames<TClients>> {
1625
+ match(filter: FilterInput<TDoc>): {
1626
+ $match: FilterInput<TDoc>;
1627
+ };
1628
+ project(fields: Projection<TDoc>): {
1629
+ $project: Projection<TDoc>;
1630
+ };
1631
+ addFields(fields: Projection<TDoc>): {
1632
+ $addFields: Projection<TDoc>;
1633
+ };
1634
+ set(fields: Projection<TDoc>): {
1635
+ $set: Projection<TDoc>;
1636
+ };
1637
+ unset(fields: string | string[]): {
1638
+ $unset: string | string[];
1639
+ };
1640
+ sort(sort: SortKeys<TDoc>): {
1641
+ $sort: SortKeys<TDoc>;
1642
+ };
1643
+ limit(n: number): {
1644
+ $limit: number;
1645
+ };
1646
+ skip(n: number): {
1647
+ $skip: number;
1648
+ };
1649
+ count(field: string): {
1650
+ $count: string;
1651
+ };
1652
+ group(group: GroupSpec<TDoc>): {
1653
+ $group: GroupSpec<TDoc>;
1654
+ };
1655
+ /**
1656
+ * `$geoNear` — requires a `2dsphere`/`2d` index and MUST be the first stage
1657
+ * (throws otherwise).
1658
+ */
1659
+ geoNear(spec: GeoNearSpec): {
1660
+ $geoNear: GeoNearSpec;
1661
+ };
1662
+ lookup<const F extends ExtractCollectionNames<TClients, TDb>, const A extends string, const O extends Document = ForeignDocOf<TClients, TDb, F>>(params: LookupSpecFor<TClients, TDb, TDoc, F> & {
1663
+ as: A;
1664
+ pipeline?: Document[] | ((sub: PipelineBuilder<TClients, TDb, ForeignDocOf<TClients, TDb, F>>) => PipelineBuilder<TClients, TDb, O> | Document[]);
1665
+ }): {
1666
+ $lookup: Document;
1667
+ };
1668
+ facet(facets: Record<string, Document[] | ((stages: AggregationStages<TDoc, TClients, TDb>) => Document[])>): {
1669
+ $facet: Record<string, Document[]>;
1670
+ };
1671
+ unwind(path: UnwindSpec): {
1672
+ $unwind: UnwindSpec;
1673
+ };
1674
+ fill(params: Document): {
1675
+ $fill: Document;
1676
+ };
1677
+ bucket(params: Document): {
1678
+ $bucket: Document;
1679
+ };
1680
+ bucketAuto(params: Document): {
1681
+ $bucketAuto: Document;
1682
+ };
1683
+ densify(params: Document): {
1684
+ $densify: Document;
1685
+ };
1686
+ }
1687
+
1688
+ /**
1689
+ * Strict, schema-aware update payloads.
1690
+ *
1691
+ * The MongoDB driver's own `UpdateFilter<T>` is intentionally loose — every
1692
+ * operator is intersected with `Record<string, any>`, so unknown keys and
1693
+ * wrong value types all type-check. These types re-derive each `$` operator
1694
+ * against the document type instead: keys are restricted to schema fields,
1695
+ * `$inc`/`$mul`/`$bit` to numeric fields, `$push`/`$addToSet`/`$pull`/`$pullAll`/
1696
+ * `$pop` to array fields, and `$currentDate` to date fields. The driver accepts
1697
+ * a superset of these payloads, so the tight type is passed through with a
1698
+ * plain cast at the driver boundary.
1699
+ */
1700
+ /** Fields of `T` whose value is numeric (usable with `$inc`, `$mul`, `$bit`). */
1701
+ type NumericFields<T> = {
1702
+ [K in keyof T as T[K] extends number ? K : never]?: number;
1703
+ };
1704
+ /** Fields of `T` whose value is a `Date` (usable with `$currentDate`). */
1705
+ type DateFields<T> = {
1706
+ [K in keyof T as T[K] extends Date ? K : never]?: true | {
1707
+ $type: 'date' | 'timestamp';
1708
+ };
1709
+ };
1710
+ /** Value accepted by `$push` for an array of `I`. */
1711
+ type PushValue<I> = I | {
1712
+ $each: readonly I[];
1713
+ $position?: number;
1714
+ $slice?: number;
1715
+ $sort?: 1 | -1 | Record<string, 1 | -1>;
1716
+ };
1717
+ /** Value accepted by `$addToSet` for an array of `I`. */
1718
+ type AddToSetValue<I> = I | {
1719
+ $each: readonly I[];
1720
+ };
1721
+ /** Value accepted by `$pull` for an array of `I`. */
1722
+ type PullValue<I> = I | {
1723
+ $in: readonly I[];
1724
+ };
1725
+ /** Array fields of `T` mapped to a per-op value type derived from the item type. */
1726
+ type PushFields<T> = {
1727
+ [K in keyof T as T[K] extends readonly unknown[] ? K : never]?: T[K] extends readonly (infer I)[] ? PushValue<I> : never;
1728
+ };
1729
+ type AddToSetFields<T> = {
1730
+ [K in keyof T as T[K] extends readonly unknown[] ? K : never]?: T[K] extends readonly (infer I)[] ? AddToSetValue<I> : never;
1731
+ };
1732
+ type PullFields<T> = {
1733
+ [K in keyof T as T[K] extends readonly unknown[] ? K : never]?: T[K] extends readonly (infer I)[] ? PullValue<I> : never;
1734
+ };
1735
+ type PullAllFields<T> = {
1736
+ [K in keyof T as T[K] extends readonly unknown[] ? K : never]?: T[K] extends readonly (infer I)[] ? readonly I[] : never;
1737
+ };
1738
+ type PopFields<T> = {
1739
+ [K in keyof T as T[K] extends readonly unknown[] ? K : never]?: 1 | -1;
1740
+ };
1741
+ /** The `$`-operator form of an update, keyed strictly against the document type. */
1742
+ type UpdateOperators<T> = {
1743
+ $set?: Partial<T>;
1744
+ $setOnInsert?: Partial<T>;
1745
+ $unset?: Partial<Record<keyof T, '' | true | 1>>;
1746
+ $inc?: NumericFields<T>;
1747
+ $mul?: NumericFields<T>;
1748
+ $min?: Partial<T>;
1749
+ $max?: Partial<T>;
1750
+ $bit?: NumericFields<T>;
1751
+ $currentDate?: DateFields<T>;
1752
+ $push?: PushFields<T>;
1753
+ $addToSet?: AddToSetFields<T>;
1754
+ $pull?: PullFields<T>;
1755
+ $pullAll?: PullAllFields<T>;
1756
+ $pop?: PopFields<T>;
1757
+ $rename?: Partial<Record<keyof T, keyof T>>;
1758
+ };
1759
+ /**
1760
+ * Update payload accepted by the ORM's update methods: either a plain field
1761
+ * patch (automatically wrapped in `$set` by `formatUpdateFilter`) or
1762
+ * `$`-operator syntax. Keys and values are checked against the document schema.
1763
+ *
1764
+ * The doc type is precise (schema-derived), so unknown keys are rejected in
1765
+ * fresh object literals via excess-property checking AND in non-literal patches
1766
+ * (the inferred type has no index signature to absorb them). The only way an
1767
+ * unknown key passes is a variable explicitly typed with an index signature
1768
+ * (e.g. `Record<string, any>`) — an intentional "unknown shape" escape hatch;
1769
+ * prefer inline literals for full checking.
1770
+ */
1771
+ type UpdateInput<T> = Partial<T> | UpdateOperators<T>;
1772
+
1773
+ /**
1774
+ * Shared CRUD context: the cross-cutting helpers every CRUD op group depends
1775
+ * on (collection resolution, tracing, retry plumbing, cache/dedup/drift read
1776
+ * pipeline, auto-timestamps, `select` normalization).
1777
+ *
1778
+ * `makeCrudOps` (see `./index.ts`) builds ONE context via `createCrudContext`
1779
+ * and hands it to the read / write / watch op-group factories, so the helpers
1780
+ * live in exactly one place instead of being duplicated per op. This module is
1781
+ * the dependency-injection seam of the CRUD layer.
1782
+ */
1783
+
1784
+ type ColNames<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>> = ExtractCollectionNames<TClients, TDb>;
1785
+ /** Input accepted by insert ops: the doc without `_id`, or with an explicit `_id`. */
1786
+ type InsertInput<T> = Omit<T, '_id'> & {
1787
+ _id?: T extends {
1788
+ _id: infer I;
1789
+ } ? I : never;
1790
+ };
1791
+ type VersionedUpdateResult<T> = {
1792
+ ok: true;
1793
+ doc: T;
1794
+ } | {
1795
+ ok: false;
1796
+ reason: 'not_found' | 'version_conflict';
1797
+ };
1798
+ /**
1799
+ * Find-query options. Extends driver `FindOptions` + SDK `QueryOptions` with a
1800
+ * first-class `select` (field list) that is translated to a driver `projection`
1801
+ * so only the requested fields are transferred from the server.
1802
+ */
1803
+ type FindQueryOptions<T> = FindOptions & QueryOptions & {
1804
+ /** Project only the listed fields — pushed to the driver as `projection`. */
1805
+ select?: readonly (keyof T)[];
1806
+ };
1807
+ /** Per-collection auto-timestamp config (keyed by logical collection name). */
1808
+ type TimestampsByCollection = Record<string, CollectionTimestamps | undefined>;
1809
+ interface CrudOpsOptions {
1810
+ resolveCollectionName: (logical: string) => string;
1811
+ wrapMongoErrors?: boolean;
1812
+ cache?: QueryCache;
1813
+ dedupeReads?: boolean;
1814
+ inFlight?: InFlight;
1815
+ hooks?: HooksRegistry;
1816
+ /** Auto-timestamp field names per collection (from the collection definition). */
1817
+ timestamps?: TimestampsByCollection;
1818
+ /** Service-level schema-drift policy for reads (default `'report'`). */
1819
+ drift?: DriftMode;
1820
+ /** Resolve the declared schema for a logical collection (drift detection). */
1821
+ getSchema?: (logical: string) => ObjectField | undefined;
1822
+ }
1823
+
1824
+ /**
1825
+ * Build the CRUD operation set for one database. Every method is collection-name
1826
+ * first and schema-typed (no `<TDoc>` at call sites). Reads go through the
1827
+ * cache + in-flight dedup path; writes invalidate the cache for that collection
1828
+ * and fire lifecycle hooks.
1829
+ */
1830
+ declare const makeCrudOps: <TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>>(client: Db, dbLabel: string, logger: LoggerLike, opts: CrudOpsOptions) => {
1831
+ watchCollection: <X extends ColNames<TClients, TDb>>(collection: X, pipeline?: bson.Document[], options?: mongodb.ChangeStreamOptions) => mongodb.ChangeStream<CollectionDoc<TClients[TDb]["collections"][X]>, mongodb.ChangeStreamDocument<CollectionDoc<TClients[TDb]["collections"][X]>>>;
1832
+ query: <X extends ColNames<TClients, TDb>>(collection: X) => QueryBuilder<CollectionDoc<TClients[TDb]["collections"][X]>, CollectionDoc<TClients[TDb]["collections"][X]>>;
1833
+ insertOne: <X extends ColNames<TClients, TDb>>(collection: X, doc: InsertInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.InsertOneOptions & QueryOptions) => Promise<mongodb.InsertOneResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
1834
+ insertMany: <X extends ColNames<TClients, TDb>>(collection: X, docs: readonly InsertInput<CollectionDoc<TClients[TDb]["collections"][X]>>[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<mongodb.InsertManyResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
1835
+ updateOne: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, update: UpdateInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.UpdateOptions & QueryOptions) => Promise<mongodb.UpdateResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
1836
+ updateMany: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, update: UpdateInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.UpdateOptions & QueryOptions) => Promise<mongodb.UpdateResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
1837
+ findOneAndUpdate: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, update: UpdateInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.FindOneAndUpdateOptions & QueryOptions) => Promise<CollectionDoc<TClients[TDb]["collections"][X]> | null>;
1838
+ findOneAndReplace: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, replacement: CollectionDoc<TClients[TDb]["collections"][X]>, options?: mongodb.FindOneAndReplaceOptions & QueryOptions) => Promise<CollectionDoc<TClients[TDb]["collections"][X]> | null>;
1839
+ deleteOne: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.DeleteOptions & QueryOptions) => Promise<mongodb.DeleteResult>;
1840
+ deleteMany: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.DeleteOptions & QueryOptions) => Promise<mongodb.DeleteResult>;
1841
+ softDeleteOne: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.UpdateOptions & QueryOptions) => Promise<mongodb.UpdateResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
1842
+ upsert: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, update: UpdateInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.UpdateOptions & QueryOptions) => Promise<mongodb.UpdateResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
1843
+ bulkUpsert: <X extends ColNames<TClients, TDb>>(collection: X, operations: {
1844
+ filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>;
1845
+ update: UpdateInput<CollectionDoc<TClients[TDb]["collections"][X]>>;
1846
+ upsert?: boolean;
1847
+ }[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<bson.Document>;
1848
+ bulkWrite: <X extends ColNames<TClients, TDb>>(collection: X, operations: mongodb.AnyBulkWriteOperation<CollectionDoc<TClients[TDb]["collections"][X]>>[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<bson.Document>;
1849
+ updateWithVersion: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, update: UpdateInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.FindOneAndUpdateOptions & QueryOptions) => Promise<VersionedUpdateResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
1850
+ findOneAndDelete: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.FindOneAndDeleteOptions & QueryOptions) => Promise<CollectionDoc<TClients[TDb]["collections"][X]> | null>;
1851
+ replaceOne: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, replacement: InsertInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.ReplaceOptions & QueryOptions) => Promise<mongodb.UpdateResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
1852
+ getOne: <X extends ColNames<TClients, TDb>>(collection: X, filter?: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined, options?: FindQueryOptions<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined) => Promise<CollectionDoc<TClients[TDb]["collections"][X]> | null>;
1853
+ getOneOrFail: <X extends ColNames<TClients, TDb>>(collection: X, filter?: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined, options?: FindQueryOptions<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined, errorFactory?: ((name: string) => Error) | undefined) => Promise<CollectionDoc<TClients[TDb]["collections"][X]>>;
1854
+ findMany: <X extends ColNames<TClients, TDb>>(collection: X, filter?: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined, options?: FindQueryOptions<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined) => Promise<CollectionDoc<TClients[TDb]["collections"][X]>[]>;
1855
+ findManyCursor: <X extends ColNames<TClients, TDb>>(collection: X, filter?: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined, options?: FindQueryOptions<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined) => mongodb.FindCursor<CollectionDoc<TClients[TDb]["collections"][X]>>;
1856
+ findActive: <X extends ColNames<TClients, TDb>>(collection: X, filter?: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined, options?: FindQueryOptions<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined) => Promise<CollectionDoc<TClients[TDb]["collections"][X]>[]>;
1857
+ findActiveOne: <X extends ColNames<TClients, TDb>>(collection: X, filter?: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined, options?: FindQueryOptions<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined) => Promise<CollectionDoc<TClients[TDb]["collections"][X]> | null>;
1858
+ countDocuments: <X extends ColNames<TClients, TDb>>(collection: X, filter?: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined, options?: mongodb.CountDocumentsOptions & QueryOptions) => Promise<number>;
1859
+ distinct: <X extends ColNames<TClients, TDb>, K extends keyof CollectionDoc<TClients[TDb]["collections"][X]>>(collection: X, field: K, filter?: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined, options?: FindQueryOptions<CollectionDoc<TClients[TDb]["collections"][X]>> | undefined) => Promise<CollectionDoc<TClients[TDb]["collections"][X]>[K][]>;
1860
+ estimatedDocumentCount: <X extends ColNames<TClients, TDb>>(collection: X, options?: mongodb.EstimatedDocumentCountOptions & QueryOptions) => Promise<number>;
1861
+ };
1862
+ type CrudOps<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>> = ReturnType<typeof makeCrudOps<TClients, TDb>>;
1863
+
1864
+ /**
1865
+ * Service configuration: the `MongoServiceConfig` options type, cache policy
1866
+ * resolution (`resolveCache`), and runtime-config derivation
1867
+ * (`resolveRuntimeConfig`). Pure — no connection or manager wiring here, so the
1868
+ * config concerns of the service are isolated from the composition root.
1869
+ */
1870
+
1871
+ interface MongoServiceConfig {
1872
+ defaultDb?: string;
1873
+ migrationDir?: string;
1874
+ appName?: string;
1875
+ logger?: LoggerLike;
1876
+ /**
1877
+ * Map driver errors to typed DomainError/InfraError before rethrowing. ON by
1878
+ * default — set `false` to surface raw driver errors instead.
1879
+ */
1880
+ wrapMongoErrors?: boolean;
1881
+ /**
1882
+ * Read-through query cache shared by every CRUD read. ON by default:
1883
+ * `undefined` creates a default `QueryCache()`, `QueryCacheOptions` builds
1884
+ * one, a `QueryCache` is reused, and `null` disables caching.
1885
+ *
1886
+ * INVALIDATION: entries are dropped write-through — every ORM write
1887
+ * invalidates that collection's cached reads. External writers (other
1888
+ * processes, the raw `client` escape hatch, direct DB writes) do NOT
1889
+ * invalidate; with the default `ttlMs: 0` those reads stay stale
1890
+ * indefinitely, so multi-writer deployments should set `QueryCacheOptions.ttlMs`,
1891
+ * disable the cache, route latency-critical reads through the
1892
+ * change-stream aware `createHotCache()`, or set `cacheWatch: true`.
1893
+ */
1894
+ cache?: QueryCache | QueryCacheOptions | null;
1895
+ /**
1896
+ * Opt into change-stream invalidation of the shared query cache. When `true`
1897
+ * (and a cache is enabled), after `makeConnections()` one `$changeStream`
1898
+ * watcher is opened per registered collection; any write — including external
1899
+ * ones — invalidates that collection's cached reads. Requires a replica set /
1900
+ * mongos. On standalone servers the streams are rejected, a warning is logged
1901
+ * once, and invalidation silently disables (the cache stays write-through
1902
+ * only). Watchers self-heal across transient errors and are closed by
1903
+ * `closeConnections()`. Default: false.
1904
+ */
1905
+ cacheWatch?: boolean;
1906
+ /** Coalesce identical concurrent reads into one driver call. ON by default. */
1907
+ dedupeReads?: boolean;
1908
+ /**
1909
+ * Master performance switch. `perf: false` disables the query cache and
1910
+ * in-flight dedup even when the granular options are set. Defaults to true.
1911
+ */
1912
+ perf?: boolean;
1913
+ /**
1914
+ * Schema-drift policy for reads. When a document fetched from the DB
1915
+ * (cache-miss) doesn't match its declared schema:
1916
+ * - `'report'` (default) — log a warning with the offending fields, return as-is
1917
+ * - `'throw'` — throw `DomainError SCHEMA_DRIFT` (the document is never
1918
+ * cached or returned)
1919
+ * - `'off'` — no drift checking (previous behavior)
1920
+ * Detection-only: documents are never mutated. Projected/partial reads are
1921
+ * skipped. Overridable per op via `QueryOptions.drift`.
1922
+ */
1923
+ drift?: DriftMode;
1924
+ }
1925
+
1926
+ interface DbHealthResult {
1927
+ /** Logical db client key (e.g. `primary`). */
1928
+ name: string;
1929
+ ok: boolean;
1930
+ /** Ping round-trip time in ms (present when `ok`). */
1931
+ latencyMs?: number;
1932
+ }
1933
+ interface HealthReport {
1934
+ ok: boolean;
1935
+ /** Total elapsed time across all pings in ms. */
1936
+ latencyMs: number;
1937
+ dbs: DbHealthResult[];
1938
+ }
1939
+
1940
+ interface SearchConfig {
1941
+ /** Fields to search across. */
1942
+ searchFields: string[];
1943
+ /** Search term. */
1944
+ searchTerm: string;
1945
+ /** Loose per-character matching (`$regex` mode only). */
1946
+ fuzzy?: boolean;
1947
+ /** Case sensitivity (`$text` mode; `$regex` mode uses this too). */
1948
+ caseSensitive?: boolean;
1949
+ /** true → `$regex` search; false (default) → `$text` search. */
1950
+ useRegex?: boolean;
1951
+ /** `$text` search language (defaults to the server's default). */
1952
+ language?: string;
1953
+ /** `$text` diacritic sensitivity (default false → insensitive). */
1954
+ diacriticSensitive?: boolean;
1955
+ /**
1956
+ * When true, `textSearch` sorts by `searchScore` (textScore meta) instead of
1957
+ * the caller's sort. `$text`-mode only.
1958
+ */
1959
+ sortByScore?: boolean;
1960
+ }
1961
+
1962
+ /**
1963
+ * Aggregation types + the shared context handed to the aggregation op-group
1964
+ * factories (`./aggregate.ts`, `./group.ts`, `./text-search.ts`,
1965
+ * `./lookup-join.ts`). Type-only plus the `AggregationCtx` interface.
1966
+ */
1967
+
1968
+ interface GroupByConfig {
1969
+ /** Group key: a field name or a document of `$`-expressions. */
1970
+ groupBy: string | Record<string, unknown>;
1971
+ /** Accumulator expressions, e.g. `{ revenue: { $sum: '$total' } }`. */
1972
+ accumulate?: Record<string, unknown>;
1973
+ /** Sort the grouped output. */
1974
+ sort?: Record<string, 1 | -1>;
1975
+ /** Cap the number of groups returned. */
1976
+ limit?: number;
1977
+ }
1978
+ interface DateRangeConfig {
1979
+ /** Date field to bucket on (must exist on every doc). */
1980
+ dateField: string;
1981
+ /** Inclusive range start. */
1982
+ startDate: Date;
1983
+ /** Inclusive range end. */
1984
+ endDate: Date;
1985
+ /** Bucket granularity (default `day`). */
1986
+ granularity?: 'hour' | 'day' | 'week' | 'month' | 'year';
1987
+ }
1988
+ interface LookupConfig {
1989
+ /** Related (logical) collection to join. */
1990
+ fromCollection: string;
1991
+ /** Field on the source docs holding the foreign key. */
1992
+ localField: string;
1993
+ /** Field on the foreign docs to match. */
1994
+ foreignField: string;
1995
+ /** Output field name for the joined array. */
1996
+ as: string;
1997
+ /** Optional `$pipeline` limiting/filtering the joined docs. */
1998
+ pipeline?: Document[];
1999
+ /** When true, `$unwind` the joined array (preserving nulls). */
2000
+ unwindSingle?: boolean;
2001
+ }
2002
+ interface PipelineCustomization {
2003
+ /** Stages prepended before the generated aggregation. */
2004
+ prePipeline?: Document[];
2005
+ /** Stages appended after the generated aggregation. */
2006
+ postPipeline?: Document[];
2007
+ }
2008
+ interface AggregationOpsOptions {
2009
+ resolveCollectionName: (logical: string) => string;
2010
+ wrapMongoErrors?: boolean;
2011
+ }
2012
+ /** Pagination subset used by `textSearch`. */
2013
+ interface TextSearchPagination {
2014
+ page?: number;
2015
+ limit?: number;
2016
+ sort?: Record<string, 1 | -1>;
2017
+ maxLimit?: number;
2018
+ }
2019
+
2020
+ /**
2021
+ * Aggregation ops for one database: `aggregate`, `pipeline`, `groupBy`,
2022
+ * `textSearch`, `dateRangeAnalysis`, `lookupJoin`. All collection-name first
2023
+ * and schema-typed.
2024
+ */
2025
+ declare const makeAggregationOps: <TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>>(client: Db, dbLabel: string, logger: LoggerLike, opts: AggregationOpsOptions) => {
2026
+ lookupJoin: <X extends ExtractCollectionNames<TClients, TDb>, TResult extends Document = CollectionDoc<TClients[TDb]["collections"][X]>>(collection: X, baseFilter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, lookups: LookupConfig[], customization?: PipelineCustomization, options?: mongodb.AggregateOptions & QueryOptions) => Promise<TResult[]>;
2027
+ textSearch: <X extends ExtractCollectionNames<TClients, TDb>>(collection: X, baseFilter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, searchConfig: SearchConfig, paginationConfig?: TextSearchPagination, customization?: PipelineCustomization, options?: mongodb.AggregateOptions & QueryOptions) => Promise<PaginationResult<CollectionDoc<TClients[TDb]["collections"][X]> & {
2028
+ searchScore?: number;
2029
+ }>>;
2030
+ groupBy: <X extends ExtractCollectionNames<TClients, TDb>, TResult_1 extends Document = Document>(collection: X, baseFilter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, config: GroupByConfig, customization?: PipelineCustomization, options?: mongodb.AggregateOptions & QueryOptions) => Promise<TResult_1[]>;
2031
+ dateRangeAnalysis: <X extends ExtractCollectionNames<TClients, TDb>, TResult_2 extends Document = Document>(collection: X, baseFilter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, dateConfig: DateRangeConfig, analysisConfig?: {
2032
+ accumulate?: Record<string, unknown>;
2033
+ sort?: Record<string, 1 | -1>;
2034
+ limit?: number;
2035
+ }, customization?: PipelineCustomization, options?: mongodb.AggregateOptions & QueryOptions) => Promise<TResult_2[]>;
2036
+ aggregate: <X extends ExtractCollectionNames<TClients, TDb>, TResult_3 extends Document = Document>(collection: X, pipelineBuilder: (stages: AggregationStages<CollectionDoc<TClients[TDb]["collections"][X]>, TClients, TDb>) => Document[], options?: mongodb.AggregateOptions & QueryOptions) => Promise<mongodb.AggregationCursor<TResult_3>>;
2037
+ pipeline: <const X extends ExtractCollectionNames<TClients, TDb>>(collection: X, options?: mongodb.AggregateOptions & QueryOptions) => PipelineBuilder<TClients, TDb, CollectionDoc<TClients[TDb]["collections"][X]>>;
2038
+ };
2039
+
2040
+ /**
2041
+ * Optional change-stream watchers that invalidate the shared query cache on
2042
+ * EXTERNAL writes — other processes, the raw `client` escape hatch, or direct
2043
+ * DB writes — closing the write-through-only gap of the ORM `QueryCache`.
2044
+ *
2045
+ * Requires a replica set / mongos. On standalone servers `$changeStream` is
2046
+ * rejected, so the invalidator logs a warning once and disables itself (the
2047
+ * cache then stays write-through only — see `MongoServiceConfig.cacheWatch`).
2048
+ *
2049
+ * One watcher is opened per unique (database, physical collection); any change
2050
+ * invalidates every cached read for that collection via
2051
+ * `QueryCache.invalidateByCollection`. Watchers self-heal: transient errors
2052
+ * retry with backoff and the collection is re-invalidated on reopen, so
2053
+ * changes missed during an outage don't leave stale entries behind.
2054
+ */
2055
+
2056
+ /** A collection whose external writes should invalidate cached reads. */
2057
+ interface CacheInvalidationRef {
2058
+ /** Raw `Db` handle (e.g. `service.db.primaryClient.client`). */
2059
+ db: Db;
2060
+ /** PHYSICAL collection name — cache keys use it, so invalidation must too. */
2061
+ collection: string;
2062
+ }
2063
+ declare class CacheInvalidator {
2064
+ private cache;
2065
+ private logger;
2066
+ private streams;
2067
+ private disabled;
2068
+ private stopped;
2069
+ private warnedUnsupported;
2070
+ constructor(options: {
2071
+ cache: QueryCache;
2072
+ logger: LoggerLike;
2073
+ });
2074
+ /** Number of currently open change streams. */
2075
+ get size(): number;
2076
+ /**
2077
+ * Open one watcher per unique (database, physical collection). Idempotent:
2078
+ * already-open streams are skipped; after `stop()` a later `start()` reopens.
2079
+ * Never throws — failures are logged (on unsupported servers it disables once).
2080
+ */
2081
+ start(refs: CacheInvalidationRef[]): Promise<void>;
2082
+ /** Close every watcher. Safe to call repeatedly, and before any `start`. */
2083
+ stop(): Promise<void>;
2084
+ private watchLoop;
2085
+ }
2086
+
2087
+ /**
2088
+ * Pagination types + the shared context handed to the two pagination
2089
+ * strategies (`./offset.ts`, `./cursor.ts`). Type-only plus the `PaginationCtx`
2090
+ * interface, so the strategy modules and the composing `makePaginationOps`
2091
+ * (in `./index.ts`) share one vocabulary without importing each other's logic.
2092
+ */
2093
+
2094
+ interface PaginationConfig {
2095
+ page?: number;
2096
+ limit?: number;
2097
+ sort?: Record<string, 1 | -1>;
2098
+ /** Cap on the requested limit (default DEFAULT_MAX_LIMIT=1000). */
2099
+ maxLimit?: number;
2100
+ /** Stages prepended to the data facet (e.g. extra $match). */
2101
+ prePipeline?: Document[];
2102
+ /** Stages appended inside the data facet. */
2103
+ postPipeline?: Document[];
2104
+ queryOptions?: QueryOptions;
2105
+ }
2106
+ interface CursorPaginationConfig<T = Document> {
2107
+ limit?: number;
2108
+ /**
2109
+ * Sort keys (1 ascending / -1 descending). MUST include a unique tiebreaker —
2110
+ * typically `_id` — so cursor boundaries are unambiguous. Sort fields should
2111
+ * exist on every document for deterministic ordering.
2112
+ */
2113
+ sort: Record<string, 1 | -1>;
2114
+ /** Opaque cursor from a previous `nextCursor` — fetch the page after it. */
2115
+ after?: string;
2116
+ /** Opaque cursor — fetch the page before it (backward navigation). */
2117
+ before?: string;
2118
+ /** Cap on the requested limit (default DEFAULT_MAX_LIMIT). */
2119
+ maxLimit?: number;
2120
+ queryOptions?: QueryOptions;
2121
+ }
2122
+ interface CursorPage<T> {
2123
+ data: T[];
2124
+ /** Opaque cursor for the next page in the queried direction, or null when exhausted. */
2125
+ nextCursor: string | null;
2126
+ /** True when another page exists in the queried direction. */
2127
+ hasMore: boolean;
2128
+ }
2129
+ interface PaginationOpsOptions {
2130
+ resolveCollectionName: (logical: string) => string;
2131
+ wrapMongoErrors?: boolean;
2132
+ /** Service-level schema-drift policy for reads (default `'report'`). */
2133
+ drift?: DriftMode;
2134
+ /** Resolve the declared schema for a logical collection (drift detection). */
2135
+ getSchema?: (logical: string) => ObjectField | undefined;
2136
+ }
2137
+
2138
+ /**
2139
+ * Pagination ops for one database: `paginateFlexible` (offset, `$facet`) and
2140
+ * `paginateCursor` (keyset). Both are collection-name first and schema-typed.
2141
+ */
2142
+ declare const makePaginationOps: <TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>>(client: Db, dbLabel: string, logger: LoggerLike, opts: PaginationOpsOptions) => {
2143
+ paginateCursor: <X extends ExtractCollectionNames<TClients, TDb>>(collection: X, baseFilter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, config: CursorPaginationConfig<CollectionDoc<TClients[TDb]["collections"][X]>>) => Promise<CursorPage<CollectionDoc<TClients[TDb]["collections"][X]>>>;
2144
+ paginateFlexible: <X extends ExtractCollectionNames<TClients, TDb>>(collection: X, baseFilter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, config?: PaginationConfig) => Promise<PaginationResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
2145
+ };
2146
+
2147
+ /**
2148
+ * Schema-driven collection registry.
2149
+ *
2150
+ * Collection names are carried by the schema itself (`s.object({...}, { name })`
2151
+ * or `defineCollection(name, schema, extras)`), so the ORM's `collections` map
2152
+ * is *derived* from the schemas instead of hand-typed. The keys of the derived
2153
+ * map ARE the schema names — a typo'd collection name becomes a type error
2154
+ * everywhere (CRUD, query builder, relations, populate).
2155
+ *
2156
+ * const users = s.object(
2157
+ * { _id: s.objectId(), email: s.string() },
2158
+ * { name: 'users' },
2159
+ * );
2160
+ * const orders = defineCollection('orders', s.object({ ... }), {
2161
+ * indexes: [{ key: { userId: 1 } }],
2162
+ * });
2163
+ *
2164
+ * const dbClients = {
2165
+ * primary: {
2166
+ * name: 'app',
2167
+ * collections: defineCollections(users, orders),
2168
+ * },
2169
+ * };
2170
+ */
2171
+
2172
+ /** An object schema that carries its collection name. */
2173
+ type NamedObjectField<N extends string = string> = ObjectField<Record<string, SchemaType>, N> & {
2174
+ name: N;
2175
+ };
2176
+ /** A collection definition (schema + optional indexes/hooks) that carries its name. */
2177
+ type NamedCollectionDefinition<N extends string = string, S extends ObjectField = ObjectField> = CollectionDefinition<S & {
2178
+ name: N;
2179
+ }> & {
2180
+ name: N;
2181
+ };
2182
+ /** Either form accepted by `defineCollections`. */
2183
+ type NamedCollection = (ObjectField & {
2184
+ name: string;
2185
+ }) | ({
2186
+ schema: ObjectField;
2187
+ indexes?: IndexSpec[];
2188
+ hooks?: HookMap<any>;
2189
+ timestamps?: CollectionTimestamps | boolean;
2190
+ } & {
2191
+ name: string;
2192
+ });
2193
+ /** Extract the carried collection name from a named collection item. */
2194
+ type StringName<T> = T extends {
2195
+ name: infer N;
2196
+ } ? Extract<N, string> : never;
2197
+ type CollectionName<T> = T extends {
2198
+ schema: infer S;
2199
+ } ? StringName<S> : StringName<T>;
2200
+ /** The value placed in the derived `collections` map for a given item. */
2201
+ type CollectionValue<T> = T extends {
2202
+ schema: infer S;
2203
+ indexes?: infer _Ix;
2204
+ hooks?: infer _Hk;
2205
+ } ? CollectionDefinition<Extract<S, ObjectField>> : T;
2206
+ /** The derived `collections` map type: keyed by each item's carried name. */
2207
+ type CollectionMap<T extends readonly NamedCollection[]> = {
2208
+ [K in T[number] as CollectionName<K>]: CollectionValue<K>;
2209
+ };
2210
+ /**
2211
+ * Attach a collection name to a schema (plus optional indexes/hooks) and return
2212
+ * a `CollectionDefinition`-shaped item. When no extras are needed this is
2213
+ * equivalent to `s.object({...}, { name })`.
2214
+ */
2215
+ declare const defineCollection: <N extends string, S extends ObjectField>(name: N, schema: S, extras?: {
2216
+ indexes?: IndexSpec[];
2217
+ hooks?: HookMap<InferDoc<S>>;
2218
+ timestamps?: CollectionTimestamps | boolean;
2219
+ }) => NamedCollectionDefinition<N, S>;
2220
+ /**
2221
+ * Derive a `collections` map from schemas that carry their own names. Throws on
2222
+ * duplicate names. The returned map's keys are the schema names and its values
2223
+ * are valid `CollectionLike` entries, so it plugs directly into
2224
+ * `DBClientDefinition.collections` with full type inference.
2225
+ */
2226
+ declare const defineCollections: <const T extends readonly NamedCollection[]>(...items: T) => CollectionMap<T>;
2227
+
2228
+ /** Convert an object schema into a Mongo JSON-schema fragment (no `$jsonSchema` wrapper). */
2229
+ declare const toMongoSchema: (schema: ObjectField) => MongoJsonSchema;
2230
+ /** Full validator object ready for `createCollection({ validator })` / `collMod`. */
2231
+ declare const toMongoValidator: (schema: ObjectField) => {
2232
+ $jsonSchema: MongoJsonSchema;
2233
+ };
2234
+
2235
+ interface CollectionRegistryEntry {
2236
+ schema?: ObjectField;
2237
+ indexes?: IndexSpec[];
2238
+ }
2239
+ /** What `syncIndexes` changed on a collection. */
2240
+ interface SyncIndexesResult {
2241
+ /** Names of indexes that were created (missing declared indexes). */
2242
+ created: string[];
2243
+ /** Names of indexes that were dropped (undeclared, excluding `_id_`). */
2244
+ dropped: string[];
2245
+ }
2246
+ interface SchemaOpsOptions {
2247
+ resolveCollectionName: (logical: string) => string;
2248
+ getDefinition: (logical: string) => CollectionRegistryEntry | undefined;
2249
+ }
2250
+ /**
2251
+ * Schema-first collection management. `createSchema` provisions a collection
2252
+ * with a `$jsonSchema` validator derived from the ORM schema (and creates any
2253
+ * declared indexes); `updateSchema` hot-swaps the validator via `collMod`.
2254
+ */
2255
+ declare const makeSchemaOps: (client: Db, _logger: LoggerLike, opts: SchemaOpsOptions) => {
2256
+ createSchema: (collection: string, createCollectionOptions?: CreateCollectionOptions) => Promise<void>;
2257
+ updateSchema: (collection: string, schema?: ObjectField) => Promise<void>;
2258
+ syncIndexes: (collection: string) => Promise<SyncIndexesResult>;
2259
+ };
2260
+
2261
+ interface TransactionOpsOptions {
2262
+ migrationCollectionPhysical: string;
2263
+ /** Map driver errors to typed DomainError/InfraError before rethrowing. */
2264
+ wrapMongoErrors?: boolean;
2265
+ }
2266
+ /**
2267
+ * Transaction + idempotent-migration ops for one database handle.
2268
+ */
2269
+ declare const makeTransactionOps: (client: MongoClient, handle: Db, logger: LoggerLike, _config: Record<string, unknown>, opts: TransactionOpsOptions) => {
2270
+ transaction: <T>(fn: (session: ClientSession | null) => Promise<T>, options?: TransactionOptions) => Promise<T>;
2271
+ migrate: (name: string, fn: (session: ClientSession | null) => Promise<void>, filePath?: string) => Promise<void>;
2272
+ };
2273
+
2274
+ /**
2275
+ * Per-database manager assembly. `buildManager` is the composition point of the
2276
+ * op-factory pattern: it builds the collection registry (schema/hooks/
2277
+ * timestamps), resolves logical→physical names, and **spreads** every
2278
+ * `make*Ops` factory into one `CollectionManager` object per database.
2279
+ *
2280
+ * The `CollectionManager` type mirrors that spread as an intersection so both
2281
+ * the runtime value and its type stay in sync from a single source.
2282
+ */
2283
+
2284
+ /** One collection manager per connected DB — the ORM's primary surface. */
2285
+ type CollectionManager<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>> = CrudOps<TClients, TDb> & ReturnType<typeof makePaginationOps<TClients, TDb>> & ReturnType<typeof makeAggregationOps<TClients, TDb>> & ReturnType<typeof makeSchemaOps> & ReturnType<typeof makeTransactionOps> & {
2286
+ /**
2287
+ * DataLoader-backed relation population (kills N+1). Each relation is
2288
+ * validated against the schema registry (`collection` / `localField` /
2289
+ * `foreignField` / `through`), and the joined `as` fields are typed.
2290
+ */
2291
+ populate: <const T extends Document, const R extends readonly RelationDef[]>(docs: T[], relations: R & {
2292
+ [K in keyof R]: ValidRelation<TClients, TDb, T, R[K]>;
2293
+ }, options?: PopulateOptions) => Promise<Array<T & JoinedFields<TClients, TDb, R>>>;
2294
+ /** Raw `db()` handle escape hatch. */
2295
+ client: Db;
2296
+ /** Reference to sibling managers (cross-DB). */
2297
+ db: Record<string, unknown>;
2298
+ };
2299
+
2300
+ /**
2301
+ * Service facade — the composition root. `createMongoService` wires the whole
2302
+ * ORM: it resolves config (`./config.ts`), opens pooled connections
2303
+ * (`./connection.ts`), assembles one `CollectionManager` per database
2304
+ * (`./manager.ts`), and exposes health (`./health.ts`).
2305
+ *
2306
+ * Kept deliberately thin: each concern lives in a sibling module and is only
2307
+ * composed here.
2308
+ */
2309
+
2310
+ type MongoService<TClients extends DbClientsDefinition = DbClientsDefinition> = {
2311
+ db: {
2312
+ [K in ExtractDbNames<TClients> as `${K}Client`]: CollectionManager<TClients, K>;
2313
+ };
2314
+ /** Open the pooled MongoClients and build all managers. */
2315
+ makeConnections: (names?: (keyof TClients & string)[]) => Promise<void>;
2316
+ closeConnections: () => Promise<void>;
2317
+ /** Run `fn` over every connected manager (in parallel). */
2318
+ eachDb: <T>(fn: (manager: unknown, dbKey: string) => Promise<T>) => Promise<T[]>;
2319
+ /** Ping every connected database and report per-DB health. */
2320
+ health: () => Promise<HealthReport>;
2321
+ getDbUrl: (name: string) => string;
2322
+ config: {
2323
+ defaultDb: string;
2324
+ migrationDir: string;
2325
+ appName: string;
2326
+ };
2327
+ logger: LoggerLike;
2328
+ ObjectId: typeof ObjectId;
2329
+ };
2330
+ /**
2331
+ * Assemble the ORM service from a client definition:
2332
+ *
2333
+ * const service = createMongoService({
2334
+ * primary: {
2335
+ * name: 'app',
2336
+ * dbUrl: process.env.MONGO_URL,
2337
+ * collectionPrefix: process.env.DB_PREFIX,
2338
+ * collections: { users: userSchema, orders: { schema: orderSchema, indexes: [...] } },
2339
+ * },
2340
+ * });
2341
+ * await service.makeConnections();
2342
+ * const users = service.db.primaryClient;
2343
+ * await users.insertOne('users', { email });
2344
+ * const page = await users.paginateFlexible('users', {}, { page: 1, limit: 20 });
2345
+ */
2346
+ declare const createMongoService: <TClients extends DbClientsDefinition = DbClientsDefinition>(dbClients: TClients, configOverrides?: MongoServiceConfig) => MongoService<TClients>;
2347
+
2348
+ interface AggregationMeta {
2349
+ executionTimeMs?: number;
2350
+ indexesUsed?: string[];
2351
+ }
2352
+ /** Uniform pagination result shape (shared by all list/paginate/search ops). */
2353
+ interface PaginationResult<T> {
2354
+ data: T[];
2355
+ totalCount: number;
2356
+ hasNextPage: boolean;
2357
+ hasPrevPage: boolean;
2358
+ currentPage: number;
2359
+ totalPages: number;
2360
+ aggregationMeta?: AggregationMeta;
2361
+ }
2362
+ declare const buildPaginationResult: <T>(data: T[], totalCount: number, page: number, limit: number) => PaginationResult<T>;
2363
+
2364
+ /**
2365
+ * Optional repository layer over a `CollectionManager` — a thin, domain-typed
2366
+ * convenience wrapper. The op-based core API is unchanged; a repository just
2367
+ * binds a manager + collection name so call sites don't repeat the collection
2368
+ * literal, and adds small ergonomic helpers (`getById`, `getByIds`,
2369
+ * `updateVersioned`, `exists`, …).
2370
+ *
2371
+ * There is deliberately NO change tracking / unit-of-work: `getById` → mutate →
2372
+ * `update`/`updateVersioned` is explicit, matching the core op semantics. All
2373
+ * perf features (cache, dedup, batching, populate) and collection hooks /
2374
+ * timestamps apply transparently because every method delegates to the manager.
2375
+ */
2376
+
2377
+ interface RepositoryOptions {
2378
+ /** Default `findMany` limit (defaults to the ORM's `DEFAULT_FIND_LIMIT`). */
2379
+ limit?: number;
2380
+ }
2381
+ /** Repository for one collection — type is derived from the manager + name. */
2382
+ type Repository<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, C extends ExtractCollectionNames<TClients, TDb>> = RepositoryFor<TClients, TDb, C, ExtractCollectionType<TClients, TDb, C>>;
2383
+ type RepositoryFor<TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, C extends ExtractCollectionNames<TClients, TDb>, Doc extends Document> = {
2384
+ readonly collection: C;
2385
+ /** The underlying manager — escape hatch to any op. */
2386
+ readonly manager: CollectionManager<TClients, TDb>;
2387
+ /** Fetch one doc by primary key, or `null`. */
2388
+ getById(id: Doc['_id'], options?: FindQueryOptions<Doc>): Promise<Doc | null>;
2389
+ /** Fetch many docs by primary key (`$in`). */
2390
+ getByIds(ids: readonly Doc['_id'][], options?: FindQueryOptions<Doc>): Promise<Doc[]>;
2391
+ /** Fetch one doc by filter, or `null`. */
2392
+ findOne(filter?: FilterInput<Doc>, options?: FindQueryOptions<Doc>): Promise<Doc | null>;
2393
+ /** Fetch one doc by filter or throw `NOT_FOUND`. */
2394
+ findOneOrFail(filter?: FilterInput<Doc>, options?: FindQueryOptions<Doc>): Promise<Doc>;
2395
+ /** Fetch many docs (default limit from `RepositoryOptions.limit`). */
2396
+ findMany(filter?: FilterInput<Doc>, options?: FindQueryOptions<Doc>): Promise<Doc[]>;
2397
+ /** Fetch many non-soft-deleted docs. */
2398
+ findActive(filter?: FilterInput<Doc>, options?: FindQueryOptions<Doc>): Promise<Doc[]>;
2399
+ /** Count docs matching a filter. */
2400
+ count(filter?: FilterInput<Doc>, options?: QueryOptions): Promise<number>;
2401
+ /** Whether at least one doc matches. */
2402
+ exists(filter?: FilterInput<Doc>, options?: FindQueryOptions<Doc>): Promise<boolean>;
2403
+ /** Distinct values of `field`. */
2404
+ distinct<K extends keyof Doc>(field: K, filter?: FilterInput<Doc>, options?: FindQueryOptions<Doc>): Promise<Array<Doc[K]>>;
2405
+ /** Insert one doc. */
2406
+ create(doc: InsertInput<Doc>, options?: InsertOneOptions & QueryOptions): Promise<InsertOneResult<Doc>>;
2407
+ /** Insert many docs. */
2408
+ createMany(docs: readonly InsertInput<Doc>[], options?: QueryOptions): Promise<InsertManyResult<Doc>>;
2409
+ /** Update one doc by primary key. */
2410
+ update(id: Doc['_id'], patch: UpdateInput<Doc>, options?: UpdateOptions & QueryOptions): Promise<UpdateResult<Doc>>;
2411
+ /** Optimistic-lock update by primary key (`__v` CAS). */
2412
+ updateVersioned(id: Doc['_id'], patch: UpdateInput<Doc>, options?: FindOneAndUpdateOptions & QueryOptions): Promise<VersionedUpdateResult<Doc>>;
2413
+ /** Delete one doc by primary key. */
2414
+ deleteById(id: Doc['_id'], options?: QueryOptions): Promise<DeleteResult>;
2415
+ /** Soft-delete one doc by primary key (sets `deletedAt`). */
2416
+ softDelete(id: Doc['_id'], options?: QueryOptions): Promise<UpdateResult<Doc>>;
2417
+ /** `$facet` pagination with totals (one round trip). */
2418
+ page(filter: FilterInput<Doc>, config?: PaginationConfig): Promise<PaginationResult<Doc>>;
2419
+ /** Keyset/cursor pagination (O(log n), no totals). */
2420
+ pageCursor(filter: FilterInput<Doc>, config: CursorPaginationConfig<Doc>): Promise<CursorPage<Doc>>;
2421
+ /** Type-safe chained aggregation builder. */
2422
+ pipeline(options?: QueryOptions): PipelineBuilder<TClients, TDb, Doc>;
2423
+ /** Callback aggregation over a typed stage builder. */
2424
+ aggregate(stages: (s: AggregationStages<Doc, TClients, TDb>) => Document[], options?: QueryOptions): Promise<AggregationCursor<Document>>;
2425
+ /** DataLoader-batched relation population (no N+1). */
2426
+ populate<const T extends Document, const R extends readonly RelationDef[]>(docs: T[], relations: R & {
2427
+ [K in keyof R]: ValidRelation<TClients, TDb, T, R[K]>;
2428
+ }, options?: PopulateOptions): Promise<Array<T & JoinedFields<TClients, TDb, R>>>;
2429
+ /** Change stream (caller-owned — attach `error` listener + close). */
2430
+ watch(pipeline?: Document[], options?: ChangeStreamOptions): ChangeStream<Doc>;
2431
+ };
2432
+ /**
2433
+ * Bind a repository to a manager + collection. `Doc` is inferred from the
2434
+ * collection name — no `<TDoc>` at call sites.
2435
+ */
2436
+ declare const createRepository: <TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>, C extends ExtractCollectionNames<TClients, TDb>>(manager: CollectionManager<TClients, TDb>, collection: C, options?: RepositoryOptions) => Repository<TClients, TDb, C>;
2437
+
2438
+ interface ResolveCollectionConfig {
2439
+ /** Prepended to every physical name: `prefix + separator + logical`. */
2440
+ collectionPrefix?: string;
2441
+ /** Joins `collectionPrefix` and the logical name (default `-`). */
2442
+ collectionPrefixSeparator?: string;
2443
+ /** Per-logical-name physical overrides; wins over the prefix rule. */
2444
+ collectionPhysicalNames?: Partial<Record<string, string>>;
2445
+ }
2446
+ /**
2447
+ * Resolve a logical collection name → physical collection name.
2448
+ * - explicit `collectionPhysicalNames[logical]` wins
2449
+ * - otherwise `prefix + separator + logical`
2450
+ * - no prefix → identity
2451
+ */
2452
+ declare const createResolveCollectionName: (config: ResolveCollectionConfig) => (logical: string) => string;
2453
+
2454
+ /**
2455
+ * Normalize a Mongo connection string so performance/reliability defaults are
2456
+ * explicit (mirrors sdk-db `normalizeMongoUrl`):
2457
+ * - retryWrites=false unless already set (keeps semantics predictable)
2458
+ * - w=1 unless another write concern is present
2459
+ * - directConnection=true for single-host non-replicaSet URIs
2460
+ */
2461
+ declare const normalizeMongoUrl: (rawUrl: string) => string;
2462
+
2463
+ /** Default cap for list/pagination limits (mirrors sdk-db). */
2464
+ declare const DEFAULT_MAX_LIMIT = 1000;
2465
+ /** Default limit applied to `findMany` when none is provided. */
2466
+ declare const DEFAULT_FIND_LIMIT = 100;
2467
+ /**
2468
+ * Hard cap for a single `insertMany` / `bulkWrite` / `bulkUpsert` call (the
2469
+ * driver splits at 100k; anything above is almost certainly an unbounded caller
2470
+ * array). Above this the ORM throws `BadRequest` instead of spiking memory.
2471
+ */
2472
+ declare const MAX_BATCH_OPS = 100000;
2473
+
2474
+ /**
2475
+ * Keyset (cursor) pagination helpers — pure, DB-agnostic.
2476
+ *
2477
+ * Unlike offset pagination, keyset pagination filters on the *last seen* row's
2478
+ * sort values, so it stays O(log n) regardless of page depth (no deep `$skip`)
2479
+ * and is stable under concurrent inserts. It cannot report a total count —
2480
+ * use `paginateFlexible` ($facet) when you need totals.
2481
+ *
2482
+ * Requirement: every sort key (and the `_id` tiebreaker) should exist on every
2483
+ * document for deterministic ordering, since the boundary comparison relies on
2484
+ * them.
2485
+ */
2486
+
2487
+ interface KeysetCursor {
2488
+ /** The sort spec this cursor belongs to (used to reject mismatched cursors). */
2489
+ sort: Record<string, 1 | -1>;
2490
+ /** Boundary field values, in sort-key order. */
2491
+ values: unknown[];
2492
+ }
2493
+ /** Base64url-encode a cursor. The payload is opaque to callers. */
2494
+ declare const encodeCursor: (cursor: KeysetCursor) => string;
2495
+ /** Decode + validate an opaque cursor. Throws on malformed input. */
2496
+ declare const decodeCursor: (cursor: string) => KeysetCursor;
2497
+ /**
2498
+ * Build the keyset filter for the next page.
2499
+ *
2500
+ * Sort keys `[k1(d1), k2(d2), ...]` with boundary values `[v1, v2, ...]` produce
2501
+ * the standard disjunction of prefix-equalities plus a strict comparison on the
2502
+ * next key:
2503
+ *
2504
+ * k1 ≻ v1 OR (k1 = v1 AND k2 ≻ v2) OR ... OR (k1 = v1 AND ... AND kn ≻ vn)
2505
+ *
2506
+ * where `≻` is `>` for ascending keys and `<` for descending keys when
2507
+ * `direction` is `'after'` (inverted for `'before'`). `_id` is the natural
2508
+ * unique tiebreaker.
2509
+ */
2510
+ declare const buildKeysetFilter: (sort: Record<string, 1 | -1>, values: unknown[], direction: "after" | "before") => Document;
2511
+
2512
+ /**
2513
+ * Merge multiple Mongo filters into one. Empty/undefined parts are skipped and
2514
+ * later keys win on shallow conflicts (mirrors sdk-db `mergeMongoFilters`).
2515
+ */
2516
+ declare const mergeMongoFilters: <T extends Document>(...parts: Array<Filter<T> | undefined>) => Filter<T>;
2517
+
2518
+ interface CrudListQuery {
2519
+ page?: number;
2520
+ limit?: number;
2521
+ }
2522
+ interface NormalizedPagination {
2523
+ page: number;
2524
+ limit: number;
2525
+ offset: number;
2526
+ }
2527
+ /**
2528
+ * Normalize + validate page/limit. Pure — no DB knowledge.
2529
+ * - `page` is floored to >= 1
2530
+ * - `limit` must be within [1, maxLimit] or a `BadRequest` is thrown
2531
+ */
2532
+ declare const normalizePageLimit: (query: CrudListQuery, maxLimit: number, defaults?: {
2533
+ page?: number;
2534
+ limit?: number;
2535
+ }) => NormalizedPagination;
2536
+
2537
+ /**
2538
+ * Soft-delete support. A soft-deleted row is one where `deletedAt` exists and is
2539
+ * non-null. Mirrors sdk-db's `crud-shared/soft-delete.ts`.
2540
+ */
2541
+ declare const buildMongoActiveFilter: () => Filter<Document>;
2542
+ /**
2543
+ * Merge a caller filter with the soft-delete "active" filter.
2544
+ * - softDelete=false → the caller filter unchanged
2545
+ * - empty/absent filter → active filter alone
2546
+ * - otherwise → `{ $and: [filter, active] }`
2547
+ */
2548
+ declare const mergeMongoActiveFilter: <T extends Document>(softDelete: boolean, filter?: FilterInput<T> | Filter<T>) => Filter<T>;
2549
+
2550
+ /** Remove the `_id` field from a document (returns the same object when absent). */
2551
+ declare const stripDocumentId: <T extends Record<string, unknown>>(doc: T) => Omit<T, "_id">;
2552
+ /** Remove an arbitrary primary key field from an object. */
2553
+ declare const stripPrimaryKey: <T extends Record<string, unknown>, K extends keyof T & string>(patch: T, key: K) => Omit<T, K>;
2554
+
2555
+ interface MongoToolkitOptions extends MongoServiceConfig {
2556
+ }
2557
+ /**
2558
+ * Convenience bundle: service + migration runner sharing one config.
2559
+ *
2560
+ * const toolkit = createMongoToolkit({ primary: { name, collections } });
2561
+ * await toolkit.service.makeConnections();
2562
+ * await toolkit.migrations.up();
2563
+ */
2564
+ declare const createMongoToolkit: <TClients extends DbClientsDefinition = DbClientsDefinition>(dbClients: TClients, options?: MongoToolkitOptions) => {
2565
+ service: MongoService<TClients>;
2566
+ migrations: MongoMigrationRunner;
2567
+ };
2568
+ type MongoToolkit<TClients extends DbClientsDefinition> = ReturnType<typeof createMongoToolkit<TClients>>;
2569
+
2570
+ export { type AccumulatorResult, type AccumulatorSpec, type Added, type AggregationMeta, type AggregationStages, type AnyField, AppError, type ArrayField, BadRequest, type BelongsToRelation, type BooleanField, type CacheInvalidationRef, CacheInvalidator, type Chainable, type CollectionDefinition, type CollectionDoc, type CollectionLike, type CollectionManager, type CollectionMap, type CollectionName, type CollectionTimestamps, type CollectionValue, type CrudListQuery, type CrudOps, type CursorPage, type CursorPaginationConfig, type DBClientDefinition, DEFAULT_FIND_LIMIT, DEFAULT_MAX_LIMIT, DataLoader, type DataLoaderOptions, type DateField, type DateRangeConfig, type DbClientsDefinition, type DbHealthResult, type DecimalField, DomainError, type DoubleField, type DriftIssue, type DriftIssueCode, type DriftMode, ERROR_HTTP_STATUS, type EnumField, type ErrorContext, type ExprResult, type ExtractCollectionNames, type ExtractCollectionType, type ExtractDbNames, type FacetOutput, type FacetPipelineStage, type FacetSpec, type FieldFlags, type FieldMeta, type FieldName, type FieldRef, type FilterInput, type FindQueryOptions, type ForeignDocOf, type GeoNearOutput, type GeoNearSpec, type GeoPoint, type GeoPointField, type GracefulTransactionOptions, type GroupByConfig, type GroupSpec, type Grouped, HOOK_NAMES, type HasManyRelation, type HealthReport, type Hook, type HookContext, type HookMap, type HookName, type HooksRegistry, HotCache, type HotCacheMode, type HotCacheOptions, type HotCacheStats, type HotCollectionRef, type HotQueryAccessor, type HotQueryConfig, type HotQueryStats, InFlight, type IndexSpec, type InferDoc, type InferField, InfraError, type InsertInput, type IntegerField, type JoinedFields, type KeysetCursor, LoggerLike, type LongField, type LookupConfig, type LookupJoinedFor, type LookupParams, type LookupSpecFor, MAX_BATCH_OPS, type ManyToManyRelation, type MigrationContext, type MigrationModule, type MongoCapabilities, type MongoCapabilitiesStore, type MongoJsonSchema, type MongoMigrationRunner, type MongoMigrationRunnerOptions, type MongoService, type MongoServiceConfig, type MongoToolkit, type MongoToolkitOptions, type MongoTransactionRunner, type NamedCollection, type NamedCollectionDefinition, type NamedObjectField, type NormalizedPagination, type NullField, type NumberBounds, type NumberField, type ObjectField, type ObjectIdField, type PaginationConfig, type PaginationResult, PipelineBuilder, type PipelineCustomization, type PipelineStage, type PopulateOptions, type Projected, type Projection, QueryBuilder, type QueryBuilderContext, QueryCache, type QueryCacheOptions, type QueryCacheStats, type QueryOptions, type RawField, type RelationDef, type RelationDefBase, type RemoveIndexSignature, type Repository, type RepositoryOptions, type ResolvedQueryOptions, type RetryOptions, type SchemaType, type SearchConfig, type SortKeys, type StringField, type SyncIndexesResult, TRANSIENT_MONGO_ERROR_CODES, type TimestampsByCollection, type Unsetted, type UnwindSpec, type Unwound, type UpdateInput, type UpdateOperators, type ValidRelation, type VersionedUpdateResult, belongsTo, buildKeysetFilter, buildMongoActiveFilter, buildPaginationResult, cacheCollectionKey, canonicalKey, createHotCache, createMongoCapabilitiesStore, createMongoMigrationRunner, createMongoService, createMongoToolkit, createRepository, createResolveCollectionName, decodeCursor, defineCollection, defineCollections, encodeCursor, hasMany, httpStatusForError, isAppError, isDomainError, isInfraError, isMappedMongoError, isMongoDuplicateKeyError, isMongoTransientError, manyToMany, mapMongoDriverError, mergeMongoActiveFilter, mergeMongoFilters, normalizeMongoUrl, normalizePageLimit, optional, probeMongoCapabilities, runHooks, s, serializeError, stripDocumentId, stripPrimaryKey, toMongoSchema, toMongoValidator, validateDoc, withDefault, withGracefulMongoTransaction, withRetry, withTransaction };