@dudousxd/nestjs-telescope-mikro-orm 1.0.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/LICENSE +21 -0
  3. package/README.md +164 -0
  4. package/dist/index.d.ts +7 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +7 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/mikro-orm-query.watcher.d.ts +43 -0
  9. package/dist/mikro-orm-query.watcher.d.ts.map +1 -0
  10. package/dist/mikro-orm-query.watcher.js +62 -0
  11. package/dist/mikro-orm-query.watcher.js.map +1 -0
  12. package/dist/mikro-orm-storage.provider.d.ts +51 -0
  13. package/dist/mikro-orm-storage.provider.d.ts.map +1 -0
  14. package/dist/mikro-orm-storage.provider.js +348 -0
  15. package/dist/mikro-orm-storage.provider.js.map +1 -0
  16. package/dist/n-plus-one.d.ts +9 -0
  17. package/dist/n-plus-one.d.ts.map +1 -0
  18. package/dist/n-plus-one.js +28 -0
  19. package/dist/n-plus-one.js.map +1 -0
  20. package/dist/query-family-hash.d.ts +3 -0
  21. package/dist/query-family-hash.d.ts.map +1 -0
  22. package/dist/query-family-hash.js +26 -0
  23. package/dist/query-family-hash.js.map +1 -0
  24. package/dist/telescope-entry.entity.d.ts +19 -0
  25. package/dist/telescope-entry.entity.d.ts.map +1 -0
  26. package/dist/telescope-entry.entity.js +37 -0
  27. package/dist/telescope-entry.entity.js.map +1 -0
  28. package/dist/telescope-mikro-orm.logger.d.ts +28 -0
  29. package/dist/telescope-mikro-orm.logger.d.ts.map +1 -0
  30. package/dist/telescope-mikro-orm.logger.js +49 -0
  31. package/dist/telescope-mikro-orm.logger.js.map +1 -0
  32. package/dist/telescope-rollup.entity.d.ts +12 -0
  33. package/dist/telescope-rollup.entity.d.ts.map +1 -0
  34. package/dist/telescope-rollup.entity.js +37 -0
  35. package/dist/telescope-rollup.entity.js.map +1 -0
  36. package/package.json +70 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,110 @@
1
+ # @dudousxd/nestjs-telescope-mikro-orm
2
+
3
+ ## 1.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`1dd4db0`](https://github.com/DavideCarvalho/nestjs-telescope/commit/1dd4db0f3cd46d04b35ac112343cfb424c2d3190) - Estimate latency percentiles (p50/p95/p99 in `/stats`) from a pre-aggregated
8
+ latency histogram instead of scanning every raw entry in the window. Rollups now
9
+ carry a fixed-size `histogram` (counts per `LATENCY_BOUNDARIES_MS` cell, plus an
10
+ overflow cell) aggregated at flush time from `durationMs`. When the store is a
11
+ `RollupStore`, `StatsService` queries the histogram rollups for the type+window,
12
+ merges them element-wise, and calls `estimatePercentileFromHistogram`
13
+ (bucket-resolution nearest-rank, O(buckets)); count/max/slow and the
14
+ family/cache/status/exception breakdowns still derive from the raw scan, and
15
+ stores without rollup support keep the exact raw-scan percentile path as a
16
+ fallback.
17
+
18
+ The histogram is persisted and merged additively by every `RollupStore`
19
+ implementation (in-memory, core SQLite, MikroORM, Redis). Legacy rollup
20
+ rows/hashes without a histogram are self-healed (new column/field) and read back
21
+ as an all-zeros array of the canonical length — never NaN, never a crash.
22
+
23
+ - [`00e0539`](https://github.com/DavideCarvalho/nestjs-telescope/commit/00e05393b00047258622be093d0006af1999d300) - `MikroOrmStorageProvider` now implements the `RollupStore` SPI, so production
24
+ MySQL deployments get rollup-backed timeseries automatically (the Recorder's
25
+ ingest hook and the timeseries service both detect the provider via
26
+ `isRollupStore`). Adds a `TelescopeRollup` entity mapping the `telescope_rollups`
27
+ table (composite PK `(metric, bucket_start)` with additive `count`/`sum` and a
28
+ running `max`), registered in the provider's owned ORM so `schema.update({ safe:
29
+ true })` self-heals it additively at boot — no migration. `recordRollups`
30
+ accumulates per `(metric, bucketStart)` inside a transaction (read-modify-write,
31
+ portable across MySQL/SQLite); `queryRollups` filters by metric set and an
32
+ inclusive bucket range; `clear()` now also empties the rollups table.
33
+
34
+ - [`e14ac60`](https://github.com/DavideCarvalho/nestjs-telescope/commit/e14ac603551372fc3767c63a349c509582b5e6ab) - Add `MikroOrmStorageProvider`: persist Telescope entries to MySQL/SQLite through
35
+ MikroORM with zero migration and zero manual table setup.
36
+
37
+ The provider owns a **dedicated MikroORM scoped to a single `TelescopeEntry`** and
38
+ **self-heals its schema at boot** (`schema.ensureDatabase()` +
39
+ `schema.update({ safe: true })` — additive only: creates the table and adds
40
+ missing columns, never drops). The scoped connection means the schema diff is
41
+ structurally incapable of touching the host's other tables, and it avoids a
42
+ self-capture loop with the query watcher's `loggerFactory`. Pass your existing
43
+ `MikroORM` (its connection config is borrowed) or explicit connection options to
44
+ run Telescope on a separate database. `ensureSchema: false` opts out.
45
+
46
+ Core gains an optional `StorageProvider.init()` boot hook (awaited by
47
+ `TelescopeModule` at startup) and now always calls `close()` after the final
48
+ flush at shutdown — providers that borrow a host-owned resource must no-op
49
+ `close()`. Features newest-first keyset pagination via an `$or` `(createdAt, id)`
50
+ position and cross-driver tag filtering against a space-padded `tagsText` column.
51
+
52
+ - [`d507547`](https://github.com/DavideCarvalho/nestjs-telescope/commit/d507547df3c13e76e90b8a97c4e3e1d8aef25bd1) - Add OpenTelemetry trace linking. The new `@dudousxd/nestjs-telescope-otel`
53
+ package implements core's `TraceContextProvider` SPI by reading the active
54
+ OpenTelemetry span via `@opentelemetry/api` (`OtelTraceContextProvider`,
55
+ optional peer, read-only — never creates or exports spans, degrades to `null`
56
+ when no span/API is present). Core gains the `TraceContextProvider` SPI,
57
+ `traceId`/`spanId` on each `Entry`, and a `meta.traceLink` template so the UI
58
+ can deep-link entries to a trace backend; the MikroORM storage provider adds the
59
+ corresponding trace columns so the ids survive persistence. The Redis storage
60
+ provider now reconstructs `traceId`/`spanId` when reading entries back, and the
61
+ dashboard shows a Trace row in the entry detail with a clickable deep-link when
62
+ `traceLink` is configured.
63
+
64
+ ### Patch Changes
65
+
66
+ - [`090cd1f`](https://github.com/DavideCarvalho/nestjs-telescope/commit/090cd1ff871dbe46c1c877a26f90496550b5304c) - Dashboard insights, charts, and trace grouping. Core gains a `StatsService` +
67
+ `GET /stats` endpoint with per-type analytics (latency p50/p95/p99, query-family
68
+ breakdown, cache hit/miss ratio, request status breakdown) and an `EntryQuery.traceId`
69
+ filter across all storages. The schedule watcher now tags scheduled runs with a stable
70
+ `schedule` tag. The dashboard adds a per-type insights header with Recharts cards, cache
71
+ hit/miss badges, a working Schedule view (job entries tagged `schedule`), and a
72
+ `#/traces/:traceId` page that groups every entry sharing an OpenTelemetry trace.
73
+
74
+ - [`6a4d8d5`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6a4d8d56321d3840fe64e646130ccfdafcfb1bdd) - Batch pulse two-pass hydration into ONE query. `EntryQuery` gains an optional
75
+ `ids?: string[]` filter: when set, only entries whose `id` is in the set are
76
+ returned (ANDed with every other filter; an empty array returns no rows). Each
77
+ storage applies it natively — SQLite via `id IN (...)`, MikroORM via
78
+ `id: { $in }`, in-memory via a membership `Set`, and Redis via a single `MGET`
79
+ of the entry keys (the big win — it skips the index scan entirely). The pulse
80
+ service now hydrates the displayed rows (top-N slowest + exception reps + N+1
81
+ reps) with one batched `get({ ids })` instead of N per-id `find()` round-trips,
82
+ cutting pulse latency against a remote store. Pulse output shape is unchanged.
83
+
84
+ - [`c4222b1`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c4222b16ef4c0fc9c61694eb67033f03369ff24e) - Speed up the `/pulse` and `/timeseries` analytics endpoints by aggregating over
85
+ a content-less projection instead of reading every entry's heavy `content` JSON
86
+ blob.
87
+
88
+ `EntryQuery` gains an `omitContent?: boolean` flag. When set, `get()` returns
89
+ entries with `content: null` and the provider skips reading/parsing the content
90
+ column where its driver allows:
91
+
92
+ - **mikro-orm**: projects every persisted column EXCEPT `content` via MikroORM's
93
+ `fields` option, so the blob never leaves the database (the main win on MySQL).
94
+ - **sqlite**: SELECTs the content-less column list and skips JSON parsing.
95
+ - **in-memory**: returns shallow copies with `content` nulled.
96
+ - **redis**: stores the whole entry as one blob, so it must read it anyway —
97
+ `omitContent` nulls `content` after parse (correct, but no projection benefit).
98
+
99
+ `timeseries` now scans content-less (bucketing only needs `createdAt`/`type`).
100
+ `pulse` runs two passes: pass 1 aggregates the whole window content-less
101
+ (counts, slowest candidates, exception families, N+1 hotspots); pass 2 hydrates
102
+ `content` for ONLY the few displayed rows (top-N slowest, one representative per
103
+ reported exception family, one per reported N+1 family) via targeted `find`s.
104
+ The pulse output shape is unchanged.
105
+
106
+ At ~10k entries with large content blobs this drops pulse and timeseries from
107
+ several seconds to a fraction of a second.
108
+
109
+ - Updated dependencies [[`73b50ad`](https://github.com/DavideCarvalho/nestjs-telescope/commit/73b50ad00193127271fdec36ad080d2858045922), [`9126bb0`](https://github.com/DavideCarvalho/nestjs-telescope/commit/9126bb04777cdaec6af3b0a1c5fe6f91d055ce82), [`1f00e62`](https://github.com/DavideCarvalho/nestjs-telescope/commit/1f00e62c8e60482b64251813680a5f866ef1619a), [`090cd1f`](https://github.com/DavideCarvalho/nestjs-telescope/commit/090cd1ff871dbe46c1c877a26f90496550b5304c), [`b7326b3`](https://github.com/DavideCarvalho/nestjs-telescope/commit/b7326b33d8d55b5f1ac5de4256f5e1980278699e), [`bfc0e26`](https://github.com/DavideCarvalho/nestjs-telescope/commit/bfc0e268388b5563d05c24e4de6ff99c74d1201a), [`7797a2a`](https://github.com/DavideCarvalho/nestjs-telescope/commit/7797a2a1554aff49bb59f5ca1b204974a7e04a41), [`6817fe6`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6817fe62775b1ff847fdb1038d3298e7709569e0), [`20ceb87`](https://github.com/DavideCarvalho/nestjs-telescope/commit/20ceb878cd4495dfbc7a3c71d882ae216a633757), [`1dd4db0`](https://github.com/DavideCarvalho/nestjs-telescope/commit/1dd4db0f3cd46d04b35ac112343cfb424c2d3190), [`a9f517c`](https://github.com/DavideCarvalho/nestjs-telescope/commit/a9f517c076461aef55cfb90d072ec38427ace91b), [`9d8eb65`](https://github.com/DavideCarvalho/nestjs-telescope/commit/9d8eb6562a7584801d5aa8b74491091f0fade5f9), [`418f1f0`](https://github.com/DavideCarvalho/nestjs-telescope/commit/418f1f0421948b40b25f845441a716fa4c6655c2), [`e76980d`](https://github.com/DavideCarvalho/nestjs-telescope/commit/e76980ddd7ee740f1b337a422a98ca98d97a007e), [`80c8f97`](https://github.com/DavideCarvalho/nestjs-telescope/commit/80c8f9769c8ab9ee724635086740910bc4d44ea3), [`e14ac60`](https://github.com/DavideCarvalho/nestjs-telescope/commit/e14ac603551372fc3767c63a349c509582b5e6ab), [`6fa0946`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6fa0946f5543868704864af2e32793eb448ac827), [`d507547`](https://github.com/DavideCarvalho/nestjs-telescope/commit/d507547df3c13e76e90b8a97c4e3e1d8aef25bd1), [`affd07e`](https://github.com/DavideCarvalho/nestjs-telescope/commit/affd07e4cb9ee85cfabaabed424833e8c638d04a), [`6a4d8d5`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6a4d8d56321d3840fe64e646130ccfdafcfb1bdd), [`c1f1ec9`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c1f1ec903d470d6b884924e2713de305b61b7481), [`cad6dae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/cad6dae0dba4f22e476d78c23ce2f74f7f6848e4), [`c8596c8`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c8596c85712880cb235e8cce059a1d93d339e9bd), [`c4222b1`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c4222b16ef4c0fc9c61694eb67033f03369ff24e), [`de29d2f`](https://github.com/DavideCarvalho/nestjs-telescope/commit/de29d2f519b1f25bc702c9dcc737d99b4751c8c9), [`10a3bc2`](https://github.com/DavideCarvalho/nestjs-telescope/commit/10a3bc224f6e0b1a237e1e7631acad70493b4c12), [`abde392`](https://github.com/DavideCarvalho/nestjs-telescope/commit/abde39264effb31b0524cc4fa89a335276c8dccb), [`8ff32a2`](https://github.com/DavideCarvalho/nestjs-telescope/commit/8ff32a2cc95775224eea3377460d91674dfda47f), [`5f2eddd`](https://github.com/DavideCarvalho/nestjs-telescope/commit/5f2eddd0ed5d72bf1de323b45870d7ddcaf64349), [`b14a201`](https://github.com/DavideCarvalho/nestjs-telescope/commit/b14a20175eae3d3017e8cbc068d367a03f634175), [`a90ef56`](https://github.com/DavideCarvalho/nestjs-telescope/commit/a90ef569ba12484bece07d8de2045e13ff2ff528), [`593bcc8`](https://github.com/DavideCarvalho/nestjs-telescope/commit/593bcc85ad6558040c62ba66bd1e5e0cbe5a6ac7), [`7b6636b`](https://github.com/DavideCarvalho/nestjs-telescope/commit/7b6636b54438427cd53ea0cbedd186b77d807169), [`e64f35a`](https://github.com/DavideCarvalho/nestjs-telescope/commit/e64f35a1bb7cae15b2ef24404888463d04f81eef), [`4892ef6`](https://github.com/DavideCarvalho/nestjs-telescope/commit/4892ef61e45e5486d34c7ec82764e6767fe8233d)]:
110
+ - @dudousxd/nestjs-telescope@1.0.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Davi Carvalho
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,164 @@
1
+ # @dudousxd/nestjs-telescope-mikro-orm
2
+
3
+ > MikroORM query watcher for
4
+ > [@dudousxd/nestjs-telescope](https://github.com/DavideCarvalho/nestjs-telescope).
5
+ > Captures every executed SQL query, correlates it to the request that triggered
6
+ > it, tags slow queries, and exposes a pure N+1 detector. Also ships a
7
+ > `MikroOrmStorageProvider` so you can persist Telescope entries in your own
8
+ > MySQL/SQLite instead of Redis.
9
+
10
+ **Status:** early development (`0.0.0`). Requires MikroORM v7+.
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ npm install @dudousxd/nestjs-telescope @dudousxd/nestjs-telescope-mikro-orm
16
+ ```
17
+
18
+ ## How query capture works
19
+
20
+ MikroORM v7 stores its logger in a private field set once at `Configuration`
21
+ construction time. Runtime logger replacement is not possible after the ORM is
22
+ built. Because of this, **the host must wire `loggerFactory` before constructing
23
+ the ORM** — that is the real, working integration path.
24
+
25
+ `MikroOrmQueryWatcher` is still registered in `watchers` (it marks the query
26
+ entry type and logs a clear warning if `loggerFactory` is missing), but it does
27
+ not capture queries on its own. The `loggerFactory` is the capture mechanism.
28
+
29
+ ## Setup
30
+
31
+ Use `MikroOrmModule.forRootAsync` so `TelescopeService` can be injected before
32
+ MikroORM is constructed:
33
+
34
+ ```ts
35
+ import { TelescopeModule, TelescopeService } from '@dudousxd/nestjs-telescope';
36
+ import {
37
+ MikroOrmQueryWatcher,
38
+ telescopeMikroOrmLogger,
39
+ } from '@dudousxd/nestjs-telescope-mikro-orm';
40
+ import { MikroOrmModule } from '@mikro-orm/nestjs';
41
+
42
+ @Module({
43
+ imports: [
44
+ TelescopeModule.forRoot({
45
+ authorizer: () => true, // restrict in production
46
+ watchers: [new MikroOrmQueryWatcher()],
47
+ }),
48
+
49
+ // forRootAsync lets us inject TelescopeService, which isn't available
50
+ // at module-definition time.
51
+ MikroOrmModule.forRootAsync({
52
+ inject: [TelescopeService],
53
+ useFactory: (telescope: TelescopeService) => ({
54
+ // ... your driver, dbName, entities, etc.
55
+ debug: ['query'],
56
+ loggerFactory: telescopeMikroOrmLogger(
57
+ (input) => telescope.record(input),
58
+ { slowMs: 100 }, // queries >= 100 ms get a 'slow' tag
59
+ ),
60
+ }),
61
+ }),
62
+ ],
63
+ })
64
+ export class AppModule {}
65
+ ```
66
+
67
+ Queries recorded via `telescopeMikroOrmLogger` automatically inherit the active
68
+ ALS batch opened by the request middleware, so each query entry is correlated to
69
+ the request that triggered it.
70
+
71
+ ## N+1 detection
72
+
73
+ ```ts
74
+ import { detectNPlusOne } from '@dudousxd/nestjs-telescope-mikro-orm';
75
+
76
+ // Pass the query entries for a single request batch and a repeat threshold.
77
+ const insights = detectNPlusOne(batchEntries, 5);
78
+ // insights: Array<{ familyHash: string; count: number; sql: string }>
79
+ ```
80
+
81
+ `detectNPlusOne` is a pure function — no I/O, no side effects. Feed it the
82
+ entries for one batch (filter by `batchId`) and it returns every query template
83
+ that ran at least `threshold` times.
84
+
85
+ ## MySQL / SQLite storage
86
+
87
+ `MikroOrmStorageProvider` persists Telescope entries to your application's own
88
+ database through MikroORM, so you can use your existing MySQL (or SQLite) for
89
+ Telescope instead of standing up Redis. **No migration and no manual table
90
+ setup** — the provider owns a dedicated MikroORM scoped to a single
91
+ `TelescopeEntry` and self-heals its schema at boot. Just hand it your existing
92
+ `MikroORM`:
93
+
94
+ ```ts
95
+ import { TelescopeModule } from '@dudousxd/nestjs-telescope';
96
+ import { MikroOrmStorageProvider } from '@dudousxd/nestjs-telescope-mikro-orm';
97
+ import { MikroORM } from '@mikro-orm/core';
98
+
99
+ @Module({
100
+ imports: [
101
+ // your normal MikroOrmModule.forRoot({ ... }) — nothing to add here
102
+ TelescopeModule.forRootAsync({
103
+ inject: [MikroORM],
104
+ useFactory: (orm: MikroORM) => ({
105
+ // borrows the host's connection config; opens its own scoped connection
106
+ storage: new MikroOrmStorageProvider(orm),
107
+ }),
108
+ }),
109
+ ],
110
+ })
111
+ export class AppModule {}
112
+ ```
113
+
114
+ Want Telescope on a **separate database** (recommended for high write volume)?
115
+ Pass explicit MikroORM connection options instead of the host `MikroORM`:
116
+
117
+ ```ts
118
+ import { MySqlDriver } from '@mikro-orm/mysql';
119
+
120
+ new MikroOrmStorageProvider({
121
+ driver: MySqlDriver,
122
+ clientUrl: process.env.TELESCOPE_DATABASE_URL,
123
+ // ensureSchema: false, // opt out of boot-time schema sync (e.g. you manage it via migration)
124
+ });
125
+ ```
126
+
127
+ How it works:
128
+
129
+ - **Dedicated, scoped connection.** The provider opens its OWN MikroORM that
130
+ knows *only* `TelescopeEntry`. Two reasons this matters: (1) the schema diff
131
+ can only ever touch `telescope_entries` — it is structurally incapable of
132
+ altering your other tables; (2) it avoids a self-capture loop — if storage
133
+ writes ran on the host connection (which has the query watcher's
134
+ `loggerFactory` wired), every `INSERT INTO telescope_entries` would be captured
135
+ as a query entry, recorded, flushed, and re-captured.
136
+ - **Self-healing schema at boot.** On `init()` the provider runs
137
+ `schema.ensureDatabase()` + `schema.update({ safe: true })` — additive only:
138
+ it creates `telescope_entries` if missing and adds any missing columns, and
139
+ **never drops** a table or column. Set `ensureSchema: false` to disable (e.g.
140
+ when you provision the table via your own migration).
141
+ - **Lifecycle.** `init()` is awaited by `TelescopeModule` at startup; `close()`
142
+ closes the owned connection at shutdown. You don't call either yourself.
143
+ - **Keyset pagination** is newest-first (`createdAt DESC, id DESC`); the cursor
144
+ encodes a `(createdAt, id)` position via `$or`, so paging resumes correctly
145
+ even after older entries are pruned.
146
+ - **Tag filtering** runs against a space-padded `tagsText` column with
147
+ `LIKE '% <tag> %'` (JSON-array predicates are not portable across MySQL and
148
+ SQLite). The `tags` JSON column remains the source of truth for retrieval.
149
+
150
+ ## Exports
151
+
152
+ | Export | Description |
153
+ |--------|-------------|
154
+ | `MikroOrmQueryWatcher` | Watcher marker — add to `TelescopeModule.forRoot({ watchers })` |
155
+ | `telescopeMikroOrmLogger(record, opts?)` | `loggerFactory`-compatible factory; `opts.slowMs` defaults to `100` |
156
+ | `TelescopeMikroOrmLogger` | `DefaultLogger` subclass (advanced: extend or instantiate directly) |
157
+ | `MikroOrmStorageProvider` | `StorageProvider` persisting entries to MySQL/SQLite; owns a dedicated scoped connection and self-heals its schema at boot |
158
+ | `TelescopeEntry` | `EntitySchema` for table `telescope_entries` (owned internally by the provider; exported for advanced use — you don't register it) |
159
+ | `detectNPlusOne(entries, threshold)` | Pure N+1 detector |
160
+ | `queryFamilyHash(sql)` | SQL template normalizer + FNV-1a hash (used internally) |
161
+
162
+ ## License
163
+
164
+ MIT © Davi Carvalho
@@ -0,0 +1,7 @@
1
+ export { queryFamilyHash } from '@dudousxd/nestjs-telescope';
2
+ export { detectNPlusOne, type NPlusOneInsight } from '@dudousxd/nestjs-telescope';
3
+ export * from './telescope-mikro-orm.logger.js';
4
+ export * from './mikro-orm-query.watcher.js';
5
+ export * from './telescope-entry.entity.js';
6
+ export * from './mikro-orm-storage.provider.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClF,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { queryFamilyHash } from '@dudousxd/nestjs-telescope';
2
+ export { detectNPlusOne } from '@dudousxd/nestjs-telescope';
3
+ export * from './telescope-mikro-orm.logger.js';
4
+ export * from './mikro-orm-query.watcher.js';
5
+ export * from './telescope-entry.entity.js';
6
+ export * from './mikro-orm-storage.provider.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAwB,MAAM,4BAA4B,CAAC;AAClF,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { Watcher, WatcherContext } from '@dudousxd/nestjs-telescope';
2
+ export interface MikroOrmQueryWatcherOptions {
3
+ slowMs?: number;
4
+ }
5
+ /**
6
+ * Captures MikroORM queries and correlates them to the active request batch.
7
+ *
8
+ * ## Integration path: host-wired loggerFactory (v7.x)
9
+ *
10
+ * MikroORM v7 caches the logger instance at `Configuration` constructor time
11
+ * (stored in a private `#logger` field). Runtime `config.set('loggerFactory')`
12
+ * does NOT update the cached logger, so zero-config runtime-wrap is not
13
+ * possible on v7.
14
+ *
15
+ * Instead, `register()` logs a clear warning and exposes `record` via
16
+ * `ctx.record`. The host must wire the logger manually:
17
+ *
18
+ * ```ts
19
+ * import { telescopeMikroOrmLogger } from '@dudousxd/nestjs-telescope-mikro-orm';
20
+ *
21
+ * // In your MikroORM config, before the ORM is constructed:
22
+ * MikroOrmModule.forRootAsync({
23
+ * inject: [TelescopeService],
24
+ * useFactory: (telescope: TelescopeService) => ({
25
+ * debug: ['query'],
26
+ * loggerFactory: telescopeMikroOrmLogger((input) => telescope.record(input)),
27
+ * // ...rest of config
28
+ * }),
29
+ * });
30
+ * ```
31
+ *
32
+ * Queries recorded via `telescopeMikroOrmLogger` automatically inherit the
33
+ * active ALS batch (set by the request middleware's `enterWith`), so they
34
+ * correlate to their request.
35
+ */
36
+ export declare class MikroOrmQueryWatcher implements Watcher {
37
+ private readonly options;
38
+ readonly type: "query";
39
+ private readonly logger;
40
+ constructor(options?: MikroOrmQueryWatcherOptions);
41
+ register(_ctx: WatcherContext): void;
42
+ }
43
+ //# sourceMappingURL=mikro-orm-query.watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mikro-orm-query.watcher.d.ts","sourceRoot":"","sources":["../src/mikro-orm-query.watcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAK1E,MAAM,WAAW,2BAA2B;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,oBAAqB,YAAW,OAAO;IAItC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,QAAQ,CAAC,IAAI,UAAmB;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;gBAEnC,OAAO,GAAE,2BAAgC;IAEtE,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI;CAqBrC"}
@@ -0,0 +1,62 @@
1
+ import { EntryType } from '@dudousxd/nestjs-telescope';
2
+ // packages/mikro-orm/src/mikro-orm-query.watcher.ts
3
+ import { Logger } from '@nestjs/common';
4
+ /**
5
+ * Captures MikroORM queries and correlates them to the active request batch.
6
+ *
7
+ * ## Integration path: host-wired loggerFactory (v7.x)
8
+ *
9
+ * MikroORM v7 caches the logger instance at `Configuration` constructor time
10
+ * (stored in a private `#logger` field). Runtime `config.set('loggerFactory')`
11
+ * does NOT update the cached logger, so zero-config runtime-wrap is not
12
+ * possible on v7.
13
+ *
14
+ * Instead, `register()` logs a clear warning and exposes `record` via
15
+ * `ctx.record`. The host must wire the logger manually:
16
+ *
17
+ * ```ts
18
+ * import { telescopeMikroOrmLogger } from '@dudousxd/nestjs-telescope-mikro-orm';
19
+ *
20
+ * // In your MikroORM config, before the ORM is constructed:
21
+ * MikroOrmModule.forRootAsync({
22
+ * inject: [TelescopeService],
23
+ * useFactory: (telescope: TelescopeService) => ({
24
+ * debug: ['query'],
25
+ * loggerFactory: telescopeMikroOrmLogger((input) => telescope.record(input)),
26
+ * // ...rest of config
27
+ * }),
28
+ * });
29
+ * ```
30
+ *
31
+ * Queries recorded via `telescopeMikroOrmLogger` automatically inherit the
32
+ * active ALS batch (set by the request middleware's `enterWith`), so they
33
+ * correlate to their request.
34
+ */
35
+ export class MikroOrmQueryWatcher {
36
+ options;
37
+ type = EntryType.Query;
38
+ logger = new Logger(MikroOrmQueryWatcher.name);
39
+ constructor(options = {}) {
40
+ this.options = options;
41
+ }
42
+ register(_ctx) {
43
+ this.logger.warn([
44
+ 'MikroOrmQueryWatcher: MikroORM v7 caches its logger at construction time,',
45
+ 'so runtime logger replacement is not possible.',
46
+ 'To capture queries, add loggerFactory + debug to your MikroORM config:',
47
+ '',
48
+ " import { telescopeMikroOrmLogger } from '@dudousxd/nestjs-telescope-mikro-orm';",
49
+ '',
50
+ ' MikroOrmModule.forRootAsync({',
51
+ ' inject: [TelescopeService],',
52
+ ' useFactory: (telescope: TelescopeService) => ({',
53
+ " debug: ['query'],",
54
+ ' loggerFactory: telescopeMikroOrmLogger((input) => telescope.record(input)),',
55
+ ' }),',
56
+ ' });',
57
+ ].join('\n'));
58
+ // slowMs option is available for consumers who use telescopeMikroOrmLogger directly.
59
+ void this.options.slowMs;
60
+ }
61
+ }
62
+ //# sourceMappingURL=mikro-orm-query.watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mikro-orm-query.watcher.js","sourceRoot":"","sources":["../src/mikro-orm-query.watcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,oDAAoD;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAMxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,OAAO,oBAAoB;IAIF;IAHpB,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC;IACf,MAAM,GAAG,IAAI,MAAM,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAEhE,YAA6B,UAAuC,EAAE;QAAzC,YAAO,GAAP,OAAO,CAAkC;IAAG,CAAC;IAE1E,QAAQ,CAAC,IAAoB;QAC3B,IAAI,CAAC,MAAM,CAAC,IAAI,CACd;YACE,2EAA2E;YAC3E,gDAAgD;YAChD,wEAAwE;YACxE,EAAE;YACF,mFAAmF;YACnF,EAAE;YACF,iCAAiC;YACjC,iCAAiC;YACjC,qDAAqD;YACrD,yBAAyB;YACzB,mFAAmF;YACnF,SAAS;YACT,OAAO;SACR,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;QACF,qFAAqF;QACrF,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC3B,CAAC;CACF"}
@@ -0,0 +1,51 @@
1
+ import type { Entry, EntryQuery, EntryWithBatch, Page, RollupBucket, RollupDelta, RollupStore, StorageProvider, TagCount } from '@dudousxd/nestjs-telescope';
2
+ import type { Options } from '@mikro-orm/core';
3
+ import { MikroORM } from '@mikro-orm/core';
4
+ /**
5
+ * Options for the dedicated, single-entity ORM the provider owns. These are
6
+ * standard MikroORM `Options` (connection config) plus an optional
7
+ * `ensureSchema` flag. Any `entities` provided here is overridden — the provider
8
+ * always forces `entities: [TelescopeEntry, TelescopeRollup]`.
9
+ */
10
+ export type MikroOrmStorageProviderOptions = Options & {
11
+ ensureSchema?: boolean;
12
+ };
13
+ export declare class MikroOrmStorageProvider implements StorageProvider, RollupStore {
14
+ private readonly source;
15
+ private readonly ensureSchema;
16
+ private orm;
17
+ constructor(source: MikroORM | MikroOrmStorageProviderOptions, opts?: {
18
+ ensureSchema?: boolean;
19
+ });
20
+ private buildOrmOptions;
21
+ init(): Promise<void>;
22
+ close(): Promise<void>;
23
+ private forkEm;
24
+ store(entries: Entry[]): Promise<void>;
25
+ update(id: string, patch: Partial<Entry>): Promise<void>;
26
+ find(id: string): Promise<EntryWithBatch | null>;
27
+ get(query: EntryQuery): Promise<Page<Entry>>;
28
+ batch(batchId: string): Promise<Entry[]>;
29
+ tags(prefix?: string): Promise<TagCount[]>;
30
+ prune(olderThan: Date, keepLast?: number): Promise<number>;
31
+ clear(): Promise<void>;
32
+ /**
33
+ * Additively merge deltas into `telescope_rollups`, matching the sqlite
34
+ * provider's `on conflict do update set count = count + excluded.count, ...`
35
+ * semantics: accumulate count/sum and keep a running max per
36
+ * (metric, bucketStart).
37
+ *
38
+ * Implemented as a read-modify-write inside ONE transaction rather than a
39
+ * native upsert. MikroORM's `em.upsert` / `qb.onConflict().merge()` REPLACE
40
+ * conflicting columns; neither expresses a portable `count = count +
41
+ * excluded.count` self-referential increment across MySQL and SQLite without
42
+ * driver-specific raw SQL. The transactional read-modify-write is portable,
43
+ * cast-free, and — because the whole batch commits atomically with row locks
44
+ * on the touched PKs — safe to call repeatedly and concurrently: concurrent
45
+ * writers serialize on the same (metric, bucketStart) rows, so no increment is
46
+ * lost. New buckets are inserted; existing ones accumulate.
47
+ */
48
+ recordRollups(deltas: RollupDelta[]): Promise<void>;
49
+ queryRollups(metrics: string[], fromBucket: number, toBucket: number): Promise<RollupBucket[]>;
50
+ }
51
+ //# sourceMappingURL=mikro-orm-storage.provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mikro-orm-storage.provider.d.ts","sourceRoot":"","sources":["../src/mikro-orm-storage.provider.ts"],"names":[],"mappings":"AAmDA,OAAO,KAAK,EACV,KAAK,EACL,UAAU,EACV,cAAc,EACd,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,eAAe,EACf,QAAQ,EACT,MAAM,4BAA4B,CAAC;AAQpC,OAAO,KAAK,EAA8B,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,QAAQ,EAAO,MAAM,iBAAiB,CAAC;AAuChD;;;;;GAKG;AACH,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AA8DlF,qBAAa,uBAAwB,YAAW,eAAe,EAAE,WAAW;IAC1E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4C;IACnE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,GAAG,CAAyB;gBAGlC,MAAM,EAAE,QAAQ,GAAG,8BAA8B,EACjD,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE;IAYnC,OAAO,CAAC,eAAe;IAoBjB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAYrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,OAAO,CAAC,MAAM;IAOR,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAStC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxD,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAQhD,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IA+D5C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAMxC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAkB1C,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgB1D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;;;;;;;;;;;;;;OAeG;IACG,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAgCnD,YAAY,CAChB,OAAO,EAAE,MAAM,EAAE,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,EAAE,CAAC;CAgB3B"}