mongoosleuth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mongoosleuth Authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,335 @@
1
+ <div align="center">
2
+
3
+ <img src="assets/banner.svg" width="100%" alt="Mongoosleuth Banner"/>
4
+
5
+ <a href="https://git.io/typing-svg">
6
+ <img src="https://readme-typing-svg.demolab.com/?font=Fira+Code&size=18&pause=1500&color=0F766E&center=true&vCenter=true&width=600&lines=Zero%20runtime%20dependencies.%20Just%20a%20peerDependency.;Catches%20the%20N%2B1%20queries%20Bullet%20catches%20in%20Rails.;Built%20for%20Mongoose.%20Built%20for%20TypeScript." alt="Typing SVG"/>
7
+ </a>
8
+
9
+ <br/>
10
+
11
+ <img src="https://img.shields.io/npm/v/mongoosleuth?style=for-the-badge&color=0F766E&label=npm" alt="npm version"/>
12
+ <img src="https://img.shields.io/badge/dependencies-zero-0F766E?style=for-the-badge" alt="zero dependencies"/>
13
+ <img src="https://img.shields.io/badge/TypeScript-Ready-3178C6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript ready"/>
14
+ <img src="https://img.shields.io/badge/module-ESM%20%2B%20CJS-134E4A?style=for-the-badge" alt="ESM and CJS"/>
15
+ <img src="https://img.shields.io/badge/Node.js-%3E%3D18-339933?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js >=18"/>
16
+ <img src="https://img.shields.io/npm/l/mongoosleuth?style=for-the-badge&color=134E4A" alt="license"/>
17
+ <br/>
18
+ <img src="https://img.shields.io/github/stars/Fizm00/Mongoosleuth?style=for-the-badge&color=0F766E" alt="GitHub stars"/>
19
+ <img src="https://img.shields.io/github/issues/Fizm00/Mongoosleuth?style=for-the-badge&color=134E4A" alt="GitHub issues"/>
20
+ <img src="https://img.shields.io/github/last-commit/Fizm00/Mongoosleuth?style=for-the-badge&color=134E4A" alt="GitHub last commit"/>
21
+
22
+ <p><strong>A zero-runtime-dependency Node.js + TypeScript library that catches N+1 query
23
+ patterns in Mongoose applications before they reach production.</strong></p>
24
+ <p>Inspired by Ruby on Rails' <code>Bullet</code> gem — built for an ecosystem that never had one.</p>
25
+
26
+ </div>
27
+
28
+ <br/>
29
+
30
+ ## Table of contents
31
+
32
+ - [The problem](#the-problem)
33
+ - [What Mongoosleuth does about it](#what-mongoosleuth-does-about-it)
34
+ - [Installation](#installation)
35
+ - [Compatibility & Requirements](#compatibility--requirements)
36
+ - [Usage](#usage)
37
+ - [Express / Fastify / Koa middleware](#express--fastify--koa-middleware)
38
+ - [Manual scope (background jobs, scripts, anything without HTTP)](#manual-scope-background-jobs-scripts-anything-without-http)
39
+ - [Ignore options](#ignore-options)
40
+ - [Pluggable Custom Reporters](#pluggable-custom-reporters)
41
+ - [Configuration Reference](#configuration-reference)
42
+ - [How it works](#how-it-works)
43
+ - [Technical details: the fingerprinting system](#technical-details-the-fingerprinting-system)
44
+ - [FAQ](#faq)
45
+ - [Development scripts](#development-scripts)
46
+ - [Non-negotiable principles](#non-negotiable-principles)
47
+ - [Contributing & Issues](#contributing--issues)
48
+ - [Star History](#star-history)
49
+ - [Roadmap](#roadmap)
50
+ - [License](#license)
51
+
52
+ <br/>
53
+
54
+ ## The problem
55
+
56
+ ```ts
57
+ // Looks completely fine. Isn't.
58
+ const posts = await Post.find();
59
+
60
+ for (const post of posts) {
61
+ // One extra query. Per post. Every single time this route is hit.
62
+ post.author = await User.findById(post.authorId);
63
+ }
64
+ ```
65
+
66
+ Fifty posts. Fifty-one queries. The response still comes back. Nobody notices —
67
+ until the collection grows, the route gets popular, and the database starts
68
+ spending more time on this one endpoint than on everything else combined.
69
+
70
+ This is the N+1 query problem, and in the Ruby on Rails world it has had a
71
+ well-known solution for over a decade: the `Bullet` gem watches your queries
72
+ in development and tells you exactly where you went wrong. Mongoose has never
73
+ had an equivalent. Mongoosleuth is that equivalent.
74
+
75
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
76
+
77
+ ## What Mongoosleuth does about it
78
+
79
+ It watches the same code, says nothing while everything is fine, and the
80
+ moment that loop fires the same query shape more times than your threshold
81
+ allows in a single request, it tells you exactly where to look:
82
+
83
+ ```
84
+ [mongoosleuth] N+1 detected
85
+ model: User
86
+ query: findById(<value>)
87
+ called 50 times in routes/posts.ts:14
88
+ fix: use .populate('author') or User.find({ _id: { $in: [...] } })
89
+ ```
90
+
91
+ No raw values are ever logged — only field names and value _types_ — so this
92
+ is safe to leave running against a database full of real, sensitive, user data.
93
+
94
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
95
+
96
+ ## Installation
97
+
98
+ ```bash
99
+ npm install mongoosleuth
100
+ ```
101
+
102
+ `mongoose` is a peer dependency, not a regular dependency — make sure it is
103
+ already installed in your project.
104
+
105
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
106
+
107
+ ## Compatibility & Requirements
108
+
109
+ - **Node.js**: `>= 18.0.0`
110
+ - **Mongoose**: `^7.0.0 || ^8.0.0`
111
+ - **Zero runtime dependencies**: Will not clutter your `node_modules` or increase bundle sizes.
112
+
113
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
114
+
115
+ ## Usage
116
+
117
+ ### Express / Fastify / Koa middleware
118
+
119
+ ```typescript
120
+ import mongoose from 'mongoose';
121
+ import { Mongoosleuth } from 'mongoosleuth';
122
+
123
+ const sleuth = new Mongoosleuth({
124
+ enabled: process.env.NODE_ENV !== 'production',
125
+ threshold: 3,
126
+ });
127
+
128
+ // Patch Mongoose's query execution once, at startup.
129
+ sleuth.attach(mongoose);
130
+
131
+ // Open a tracking scope for every incoming request.
132
+ app.use(sleuth.middleware());
133
+ ```
134
+
135
+ ### Manual scope (background jobs, scripts, anything without HTTP)
136
+
137
+ ```typescript
138
+ await sleuth.run(async () => {
139
+ const users = await User.find({ status: 'active' });
140
+
141
+ for (const user of users) {
142
+ // Tracked and analyzed exactly like a request — fires the same warning
143
+ // if this turns out to be a loop instead of a batched query.
144
+ const profile = await Profile.findOne({ userId: user.id });
145
+ }
146
+ });
147
+ ```
148
+
149
+ ### Ignore options
150
+
151
+ You can exclude specific collections or operations from N+1 analysis to avoid tracking logs on expected bulk operations:
152
+
153
+ ```typescript
154
+ const sleuth = new Mongoosleuth({
155
+ ignore: [
156
+ { model: 'AuditLog' }, // Ignore all queries on AuditLog collection
157
+ { operation: 'updateOne' }, // Ignore all updates on any collection
158
+ { model: 'Session', operation: 'findOne' } // Ignore specific collection-operation pair
159
+ ]
160
+ });
161
+ ```
162
+
163
+ ### Pluggable Custom Reporters
164
+
165
+ Mongoosleuth comes built-in with `ConsoleReporter` (default) and `JsonReporter` (for NDJSON logging pipelines). You can also easily implement the `Reporter` interface to ship warnings to Slack, Sentry, or S3:
166
+
167
+ ```typescript
168
+ import { Mongoosleuth, JsonReporter, Reporter, Finding } from 'mongoosleuth';
169
+
170
+ class SlackReporter implements Reporter {
171
+ report(findings: Finding[]): void {
172
+ for (const finding of findings) {
173
+ // Post warning to Slack hook
174
+ fetch('https://hooks.slack.com/services/...', {
175
+ method: 'POST',
176
+ headers: { 'Content-Type': 'application/json' },
177
+ body: JSON.stringify({
178
+ text: `🚨 N+1 Query detected on *${finding.model}* (${finding.count} calls) at ${finding.callSite}`
179
+ })
180
+ });
181
+ }
182
+ }
183
+ }
184
+
185
+ const sleuth = new Mongoosleuth({
186
+ reporters: [
187
+ new SlackReporter(),
188
+ new JsonReporter(line => process.stderr.write(line + '\n'))
189
+ ]
190
+ });
191
+ ```
192
+
193
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
194
+
195
+ ## Configuration Reference
196
+
197
+ <details>
198
+ <summary><strong>View Configuration Options (MongoosleuthOptions)</strong></summary>
199
+
200
+ <br/>
201
+
202
+ | Option | Type | Default | Description |
203
+ | :--- | :--- | :--- | :--- |
204
+ | `enabled` | `boolean` | `process.env.NODE_ENV !== 'production'` | Toggle Mongoosleuth N+1 pattern detection. When set to `false`, it acts as a zero-overhead pass-through. |
205
+ | `threshold` | `number` | `3` | Number of identical query shapes executed from the same call site within a single scope before triggering a warning. Must be `>= 1`. |
206
+ | `captureStackTrace` | `boolean` | `true` | When `true`, automatically identifies the exact file and line number (call site) of queries. Set to `false` for absolute zero-overhead performance (logs will group call sites as `'unknown'`). |
207
+ | `ignore` | `Array<{ model?: string; operation?: string }>` | `[]` | Excludes queries matching specific model names or operations from being tracked. |
208
+ | `reporters` | `Reporter[]` | `[new ConsoleReporter()]` | Pluggable output handlers that receive identified N+1 query findings. |
209
+
210
+ </details>
211
+
212
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
213
+
214
+ ## How it works
215
+
216
+ ```mermaid
217
+ flowchart TD
218
+ A[Incoming request] --> B[Tracking scope opens<br/>AsyncLocalStorage]
219
+ B --> C[Query interceptor<br/>logs every Mongoose query]
220
+ C --> D[Pattern analyzer<br/>flags repeated query patterns]
221
+ D --> E[Reporter<br/>warns in the console]
222
+ ```
223
+
224
+ Each request gets its own isolated tracking scope. Every query fired inside
225
+ that scope is fingerprinted and logged. When the request finishes, the
226
+ analyzer checks whether any fingerprint repeated, from the same line of code,
227
+ often enough to be a loop rather than a coincidence — and if so, the reporter
228
+ prints it.
229
+
230
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
231
+
232
+ ## Technical details: the fingerprinting system
233
+
234
+ At the core of the detector is a deterministic fingerprinting function
235
+ (`src/fingerprint.ts`) that identifies _the shape_ of a query without ever
236
+ touching the actual data inside it.
237
+
238
+ **1. Normalization.** Every primitive value inside a query filter is replaced
239
+ with a tag representing its type, never its value:
240
+
241
+ | Value type | Normalized to |
242
+ | ----------- | ------------- |
243
+ | `string` | `<string>` |
244
+ | `number` | `<number>` |
245
+ | `boolean` | `<boolean>` |
246
+ | `Date` | `<Date>` |
247
+ | `ObjectId` | `<ObjectId>` |
248
+ | `RegExp` | `<RegExp>` |
249
+ | `null` | `<null>` |
250
+ | `undefined` | `<undefined>` |
251
+
252
+ MongoDB query operators such as `$gt`, `$in`, or `$regex` are preserved as
253
+ part of the shape — they change what the query means, so they are never
254
+ collapsed away. Arrays are normalized using the shape of their first element
255
+ only; an empty array is tagged `<empty array>`.
256
+
257
+ **2. Deterministic key sorting.** Object keys are sorted alphabetically at
258
+ every nesting level before the fingerprint is built, so field order in your
259
+ code never affects detection:
260
+
261
+ ```
262
+ { name: "John", age: 30 } → { age: <number>, name: <string> }
263
+ { age: 30, name: "John" } → { age: <number>, name: <string> }
264
+ ```
265
+
266
+ Same fingerprint either way — which is exactly the point.
267
+
268
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
269
+
270
+ ## FAQ
271
+
272
+ #### Is it safe to run in production?
273
+ Yes, absolutely. When `enabled` is set to `false` (which is the default outside development/staging environments), all of Mongoosleuth's internal hooks are bypassed and act as zero-overhead no-op pass-throughs. Additionally, Mongoosleuth never captures or logs raw query values (PII data is fully protected by substituting types), ensuring complete data privacy.
274
+
275
+ #### Will this affect my application's performance?
276
+ In development (`enabled: true`), capturing stack traces to locate the exact query line (`callSite`) has a minor CPU cost. If you wish to run high-throughput load tests or minimize trace collection overhead, you can set `captureStackTrace: false` to disable call-site tracing and group queries under a single `'unknown'` location.
277
+
278
+ #### Why not just use built-in database query logging?
279
+ Database profilers and query loggers record individual raw queries independently without context of where they originated. Mongoosleuth isolates query shapes per request scope and groups them by their exact code line location. This allows it to distinguish between normal independent query executions and repeated, identical queries fired from a single loop context (N+1 query pattern).
280
+
281
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
282
+
283
+ ## Development scripts
284
+
285
+ | Command | What it does |
286
+ | ---------------- | --------------------------------------------------------------------------- |
287
+ | `npm run build` | Builds dual ESM (`.mjs`) and CommonJS (`.js`) output, plus `.d.ts`/`.d.mts` |
288
+ | `npm test` | Runs the unit and integration suite (Vitest + `mongodb-memory-server`) |
289
+ | `npm run lint` | Checks code style and TypeScript linter compliance |
290
+ | `npm run format` | Formats the codebase with Prettier |
291
+
292
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
293
+
294
+ ## Non-negotiable principles
295
+
296
+ > Zero runtime dependencies. `mongoose` is a peer dependency, never a
297
+ > dependency.
298
+ >
299
+ > Privacy first. Raw query values are never logged — only field names and
300
+ > value type tags.
301
+ >
302
+ > No shared state. Every request is isolated through `AsyncLocalStorage`; two
303
+ > concurrent requests never see each other's query logs.
304
+ >
305
+ > Dual module output. Full support for ESM and CommonJS consumers, with
306
+ > accurate `.d.ts` definitions for both.
307
+
308
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
309
+
310
+ ## Contributing & Issues
311
+
312
+ We welcome contributions in the form of bug reports, feature suggestions, or pull requests. Please report any issues you encounter via the [GitHub Issues](https://github.com/Fizm00/Mongoosleuth/issues) page.
313
+
314
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
315
+
316
+ ## Star History
317
+
318
+ [![Star History Chart](https://api.star-history.com/svg?repos=Fizm00/Mongoosleuth&type=Date)](https://star-history.com/#Fizm00/Mongoosleuth&Date)
319
+
320
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
321
+
322
+ ## Roadmap
323
+
324
+ - **Unused eager loading detection** — flagging a `.populate()` call whose
325
+ result was never actually read, the mirror image of the N+1 problem.
326
+
327
+ <p align="right"><a href="#table-of-contents">▲ Back to top</a></p>
328
+
329
+ ## License
330
+
331
+ MIT
332
+
333
+ <div align="center">
334
+ <img src="assets/footer.svg" width="100%" alt="Mongoosleuth Footer"/>
335
+ </div>
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Options for configuring Mongoosleuth.
3
+ */
4
+ interface MongoosleuthOptions {
5
+ /**
6
+ * Whether Mongoosleuth is enabled.
7
+ * @default process.env.NODE_ENV !== 'production'
8
+ */
9
+ enabled?: boolean;
10
+ /**
11
+ * The count threshold for identical queries at a single call-site to flag a finding.
12
+ * @default 3
13
+ */
14
+ threshold?: number;
15
+ /**
16
+ * Whether to capture the stack trace for precise location mapping.
17
+ * @default true
18
+ */
19
+ captureStackTrace?: boolean;
20
+ /**
21
+ * Configurations to ignore specific models or operations.
22
+ */
23
+ ignore?: Array<{
24
+ model?: string;
25
+ operation?: string;
26
+ }>;
27
+ /**
28
+ * Reporters to write results to.
29
+ * @default [new ConsoleReporter()]
30
+ */
31
+ reporters?: Reporter[];
32
+ }
33
+ /**
34
+ * Represents a single N+1 query pattern finding.
35
+ */
36
+ interface Finding {
37
+ /**
38
+ * The name of the Mongoose model being queried.
39
+ */
40
+ model: string;
41
+ /**
42
+ * The Mongoose query operation (e.g., 'find', 'findOne').
43
+ */
44
+ operation: string;
45
+ /**
46
+ * The fingerprint representing the query shape, normalized filter, and operation.
47
+ */
48
+ fingerprint: string;
49
+ /**
50
+ * The number of times this exact pattern was executed.
51
+ */
52
+ count: number;
53
+ /**
54
+ * The call site (file, line number, column) where the query originated.
55
+ */
56
+ callSite: string;
57
+ }
58
+ /**
59
+ * Pluggable interface for reporting N+1 query findings.
60
+ */
61
+ interface Reporter {
62
+ /**
63
+ * Reports the findings to the target output.
64
+ * @param findings The list of findings to report.
65
+ */
66
+ report(findings: Finding[]): void;
67
+ }
68
+
69
+ /**
70
+ * The main Mongoosleuth library class.
71
+ * Coordinates intercepting queries, request-scoped tracking, and analyzing/reporting.
72
+ *
73
+ * See AGENTS.md: Public API surface.
74
+ */
75
+ declare class Mongoosleuth {
76
+ readonly options: Required<MongoosleuthOptions>;
77
+ /**
78
+ * Initializes a new instance of Mongoosleuth.
79
+ * @param options Configuration options.
80
+ */
81
+ constructor(options?: MongoosleuthOptions);
82
+ /**
83
+ * Patches the given Mongoose instance to intercept all query executions.
84
+ *
85
+ * @param mongooseInstance Mongoose library instance to attach to.
86
+ */
87
+ attach(mongooseInstance: any): void;
88
+ /**
89
+ * Internal helper to run query pattern analysis and trigger reporters.
90
+ */
91
+ private analyzeAndReport;
92
+ /**
93
+ * Express/Koa/Fastify middleware that opens a RequestScope for each incoming request
94
+ * and runs pattern analysis upon request completion.
95
+ *
96
+ * @returns Request middleware function.
97
+ */
98
+ middleware(): (req: any, res: any, next: (err?: any) => void) => void;
99
+ /**
100
+ * Manually runs a function within a new query tracking scope.
101
+ * Useful for background jobs, scripts, or manual request cycles outside standard middleware.
102
+ *
103
+ * @param fn The asynchronous function to execute within the scope.
104
+ */
105
+ run<T>(fn: () => Promise<T>): Promise<T>;
106
+ }
107
+
108
+ /**
109
+ * Default reporter that prints query N+1 findings to console.warn.
110
+ * See AGENTS.md: Reporter.
111
+ */
112
+ declare class ConsoleReporter implements Reporter {
113
+ /**
114
+ * Reports the identified N+1 query findings.
115
+ * @param findings The list of findings to report.
116
+ */
117
+ report(findings: Finding[]): void;
118
+ }
119
+
120
+ /**
121
+ * Pluggable reporter that outputs query N+1 findings as a single-line JSON string (NDJSON format).
122
+ * See AGENTS.md: Reporter.
123
+ */
124
+ declare class JsonReporter implements Reporter {
125
+ private write;
126
+ /**
127
+ * Initializes a new instance of JsonReporter.
128
+ * @param write Optional callback function to write the JSON line. Defaults to console.log.
129
+ */
130
+ constructor(write?: (line: string) => void);
131
+ /**
132
+ * Serializes the findings to JSON lines and writes them using the write handler.
133
+ * @param findings The list of findings to serialize and report.
134
+ */
135
+ report(findings: Finding[]): void;
136
+ }
137
+
138
+ export { ConsoleReporter, type Finding, JsonReporter, Mongoosleuth, type MongoosleuthOptions, type Reporter };
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Options for configuring Mongoosleuth.
3
+ */
4
+ interface MongoosleuthOptions {
5
+ /**
6
+ * Whether Mongoosleuth is enabled.
7
+ * @default process.env.NODE_ENV !== 'production'
8
+ */
9
+ enabled?: boolean;
10
+ /**
11
+ * The count threshold for identical queries at a single call-site to flag a finding.
12
+ * @default 3
13
+ */
14
+ threshold?: number;
15
+ /**
16
+ * Whether to capture the stack trace for precise location mapping.
17
+ * @default true
18
+ */
19
+ captureStackTrace?: boolean;
20
+ /**
21
+ * Configurations to ignore specific models or operations.
22
+ */
23
+ ignore?: Array<{
24
+ model?: string;
25
+ operation?: string;
26
+ }>;
27
+ /**
28
+ * Reporters to write results to.
29
+ * @default [new ConsoleReporter()]
30
+ */
31
+ reporters?: Reporter[];
32
+ }
33
+ /**
34
+ * Represents a single N+1 query pattern finding.
35
+ */
36
+ interface Finding {
37
+ /**
38
+ * The name of the Mongoose model being queried.
39
+ */
40
+ model: string;
41
+ /**
42
+ * The Mongoose query operation (e.g., 'find', 'findOne').
43
+ */
44
+ operation: string;
45
+ /**
46
+ * The fingerprint representing the query shape, normalized filter, and operation.
47
+ */
48
+ fingerprint: string;
49
+ /**
50
+ * The number of times this exact pattern was executed.
51
+ */
52
+ count: number;
53
+ /**
54
+ * The call site (file, line number, column) where the query originated.
55
+ */
56
+ callSite: string;
57
+ }
58
+ /**
59
+ * Pluggable interface for reporting N+1 query findings.
60
+ */
61
+ interface Reporter {
62
+ /**
63
+ * Reports the findings to the target output.
64
+ * @param findings The list of findings to report.
65
+ */
66
+ report(findings: Finding[]): void;
67
+ }
68
+
69
+ /**
70
+ * The main Mongoosleuth library class.
71
+ * Coordinates intercepting queries, request-scoped tracking, and analyzing/reporting.
72
+ *
73
+ * See AGENTS.md: Public API surface.
74
+ */
75
+ declare class Mongoosleuth {
76
+ readonly options: Required<MongoosleuthOptions>;
77
+ /**
78
+ * Initializes a new instance of Mongoosleuth.
79
+ * @param options Configuration options.
80
+ */
81
+ constructor(options?: MongoosleuthOptions);
82
+ /**
83
+ * Patches the given Mongoose instance to intercept all query executions.
84
+ *
85
+ * @param mongooseInstance Mongoose library instance to attach to.
86
+ */
87
+ attach(mongooseInstance: any): void;
88
+ /**
89
+ * Internal helper to run query pattern analysis and trigger reporters.
90
+ */
91
+ private analyzeAndReport;
92
+ /**
93
+ * Express/Koa/Fastify middleware that opens a RequestScope for each incoming request
94
+ * and runs pattern analysis upon request completion.
95
+ *
96
+ * @returns Request middleware function.
97
+ */
98
+ middleware(): (req: any, res: any, next: (err?: any) => void) => void;
99
+ /**
100
+ * Manually runs a function within a new query tracking scope.
101
+ * Useful for background jobs, scripts, or manual request cycles outside standard middleware.
102
+ *
103
+ * @param fn The asynchronous function to execute within the scope.
104
+ */
105
+ run<T>(fn: () => Promise<T>): Promise<T>;
106
+ }
107
+
108
+ /**
109
+ * Default reporter that prints query N+1 findings to console.warn.
110
+ * See AGENTS.md: Reporter.
111
+ */
112
+ declare class ConsoleReporter implements Reporter {
113
+ /**
114
+ * Reports the identified N+1 query findings.
115
+ * @param findings The list of findings to report.
116
+ */
117
+ report(findings: Finding[]): void;
118
+ }
119
+
120
+ /**
121
+ * Pluggable reporter that outputs query N+1 findings as a single-line JSON string (NDJSON format).
122
+ * See AGENTS.md: Reporter.
123
+ */
124
+ declare class JsonReporter implements Reporter {
125
+ private write;
126
+ /**
127
+ * Initializes a new instance of JsonReporter.
128
+ * @param write Optional callback function to write the JSON line. Defaults to console.log.
129
+ */
130
+ constructor(write?: (line: string) => void);
131
+ /**
132
+ * Serializes the findings to JSON lines and writes them using the write handler.
133
+ * @param findings The list of findings to serialize and report.
134
+ */
135
+ report(findings: Finding[]): void;
136
+ }
137
+
138
+ export { ConsoleReporter, type Finding, JsonReporter, Mongoosleuth, type MongoosleuthOptions, type Reporter };