@dudousxd/nestjs-telescope-mikro-orm 2.0.0 → 2.0.2

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/CHANGELOG.md CHANGED
@@ -1,7 +1,347 @@
1
1
  # @dudousxd/nestjs-telescope-mikro-orm
2
2
 
3
+ ## 2.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#183](https://github.com/DavideCarvalho/nestjs-telescope/pull/183) [`1a1d056`](https://github.com/DavideCarvalho/nestjs-telescope/commit/1a1d0560b680349d4e1ce142a61910ddebcb9ae2) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Works with `@dudousxd/nestjs-telescope` 3: the peer range is now `^2.0.1 || ^3.0.0`. Nothing else changes.
8
+
9
+ ## 2.0.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [#179](https://github.com/DavideCarvalho/nestjs-telescope/pull/179) [`3f6437a`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3f6437ae527264cc5acbbd4be7c0b9ab060cfdc9) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Republish the 2.0 release as 2.0.1. The 2.0.0 versions on npm are an old accidental build from 2026-06-05 (deprecated at the time), so `changeset publish` skipped them as "already published" and `^2.0.0` installs that June build. 2.0.1 is the real 2.0: the first-party NestJS integration and route protection release, with no further changes.
14
+
3
15
  ## 2.0.0
4
16
 
17
+ ### Major Changes
18
+
19
+ - [#178](https://github.com/DavideCarvalho/nestjs-telescope/pull/178) [`3222eae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3222eae574429a9807784ba413e8f0e43c3d98d3) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Requires `@dudousxd/nestjs-telescope@2`: the `@dudousxd/nestjs-telescope` peer range moves from `^1` to `^2`. No API of this package changes. Core 2.0 protects the console's routes only with `decorators` / `routes: false` (`@nestjs/authentication` + `@nestjs/authorization`) and refuses to boot in production without them; queue mutations (bullmq, sqs) are switched on with `mutations: true` instead of `authorizeAction`. See core's changelog for the migration.
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [[`3222eae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3222eae574429a9807784ba413e8f0e43c3d98d3), [`3222eae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3222eae574429a9807784ba413e8f0e43c3d98d3), [`3222eae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3222eae574429a9807784ba413e8f0e43c3d98d3)]:
24
+ - @dudousxd/nestjs-telescope@2.0.0
25
+
26
+ ## 1.15.3
27
+
28
+ ### Patch Changes
29
+
30
+ - [#174](https://github.com/DavideCarvalho/nestjs-telescope/pull/174) [`8a61f3d`](https://github.com/DavideCarvalho/nestjs-telescope/commit/8a61f3d41366b79a28caa1b211989614b890bcba) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Cheaper dashboard reads and flushes, and rollups that no longer lose counts across pods.
31
+
32
+ - Per-type stats scan without entry content (cache stats excepted) and read only the content they show: one row per query or exception family, and request status codes from the `status:` tag.
33
+ - Exception alerts are evaluated in their own queue behind the flush, so a slow channel, geo lookup or AI diagnosis no longer delays the next flush; each family is checked once per flush, and the per-family cooldown map is bounded.
34
+ - `/health` reuses its capture-cost measurement for a minute.
35
+ - The in-memory store keeps entries sorted and reads a page from the newest end instead of copying and sorting everything per page.
36
+ - Redis: rollup reads are two pipelined round trips; content-less reads use a content-free copy of each entry (`m:<id>`, falling back to the full entry for data written before it); `{ type, familyHash }` queries page the family index; tags with no entries left are removed from the tag counts.
37
+ - MikroORM: rollups are written with one native upsert per chunk (MySQL/MariaDB, PostgreSQL, SQLite) that merges counts, max and histogram in the database, so concurrent pods never lose or reject each other's increments.
38
+ - Parquet: listing an hour with many files is linear in the file count.
39
+ - Query and log watchers compute the family hash / serialize the log message only when the recorder keeps the entry.
40
+
41
+ - [#171](https://github.com/DavideCarvalho/nestjs-telescope/pull/171) [`eeb1798`](https://github.com/DavideCarvalho/nestjs-telescope/commit/eeb1798ad13e6b40e6534b1b5141e0b437d462ea) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - The pruner's batches no longer read the whole retained backlog to find the rows they delete.
42
+
43
+ The global cutoff (every type but the ones with their own retention) ran as one
44
+ `created_at < ? AND type NOT IN (…) ORDER BY created_at` query. The planner walked the `created_at`
45
+ index from the oldest row and stepped over every row of the excluded types, which are excluded
46
+ because they are kept longer: next to 2.8M `durable` entries kept for 24 h, a batch of 1,000 rows
47
+ took 10 s, so a cycle's batches ran back to back on the database. The batch now prunes one type at a
48
+ time (the types found by index seeks), each as a range of a new `(type, created_at, id)` index, which
49
+ the additive schema update adds on the next boot on every dialect. A type with nothing expired costs
50
+ one index seek. Same rows deleted, oldest first within each type, one shared `limit` per batch.
51
+
52
+ - [#172](https://github.com/DavideCarvalho/nestjs-telescope/pull/172) [`833fab2`](https://github.com/DavideCarvalho/nestjs-telescope/commit/833fab2f54efaf41e5a8a4e6e5cf3e044bee17d6) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Flushes are written as plain multi-row INSERTs instead of going through the unit of work.
53
+
54
+ `store()` created a managed entity per entry and ran `em.flush()`, which computes a change set per
55
+ entity. Entries are write-once, so that work was not needed, and on a host recording a wide durable
56
+ fan (80k entries in a 40k-step run) it cost more CPU than the INSERTs themselves. The batch is now
57
+ inserted directly, in chunks of the ORM's `batchSize` so a large flush stays under every driver's
58
+ bound-parameter limit. A flush that spans several chunks still runs in one transaction, so it is
59
+ stored whole or not at all and the recorder's retry never collides with rows from a failed attempt.
60
+ The same rows are written, for about half the CPU per flush.
61
+
62
+ ## 1.15.2
63
+
64
+ ### Patch Changes
65
+
66
+ - [#165](https://github.com/DavideCarvalho/nestjs-telescope/pull/165) [`6a1ecea`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6a1ecea013ea550167c362b69fcc74f27ec1c749) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `MikroOrmStorageProvider.tags()` honours the `TagQuery`: counts come back most used first with ties by tag, `search` matches anywhere in the tag, and `limit`/`offset` page over that order. It returned counts in first-seen order and ignored `search`, which the dashboard's tag picker does not re-apply. Found by the new storage conformance suite.
67
+
68
+ ## 1.15.1
69
+
70
+ ### Patch Changes
71
+
72
+ - [`80392f0`](https://github.com/DavideCarvalho/nestjs-telescope/commit/80392f0e4d038b50b5c8703275cfd91e3328064b) - Add NestJS 12 to the supported peer range.
73
+
74
+ NestJS 12.0.1 shipped the framework as pure ESM and raised its floor to Node >= 20.19. Telescope's
75
+ packages are already `"type": "module"`, so the ESM shift is a non-event here — nothing needed
76
+ porting, and every watcher, storage provider and dashboard route behaves identically on 11 and 12.
77
+
78
+ The declared peer ranges (`@nestjs/common`/`@nestjs/core` at `>=10.0.0`, `>=11.0.0` for the UI
79
+ package, and the satellite peers `@nestjs/bullmq`, `@nestjs/cache-manager`, `@nestjs/event-emitter`,
80
+ `@nestjs/schedule`) already admitted a 12.x resolution, so no range changed — what changed is that
81
+ the claim is now tested. Every package's dev and test matrix moved to the 12.x line, along with the
82
+ example app and the memory-soak harness, and the whole suite builds, typechecks and passes there.
83
+
84
+ Moving the example app and the soak harness matters for more than tidiness: leaving them on
85
+ `@nestjs/platform-express@11` left a second `@nestjs/platform-express` major in the workspace, and
86
+ pnpm's peer resolution then produced two distinct `@nestjs/core@12` instances. `tsc` correctly
87
+ rejected the resulting `ModuleRef` from one instance where the other was expected. That is a
88
+ workspace resolution artifact rather than a NestJS 12 incompatibility, but it is exactly the shape
89
+ of breakage a consumer sees when they upgrade half a dependency graph.
90
+
91
+ No behaviour change — peer ranges only affect what a package manager warns about on install.
92
+
93
+ ## 1.15.0
94
+
95
+ ### Minor Changes
96
+
97
+ - [`8c68625`](https://github.com/DavideCarvalho/nestjs-telescope/commit/8c686250872dd4d5ba1691a5fa0ead90af3495c0) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Prune in bounded batches, and let a fleet prune once instead of once per pod.
98
+
99
+ **Why `minor` and not `patch`:** this adds public API — `pruneScopedBatch`,
100
+ `tryAcquireLease`/`releaseLease` on the storage contract, the `TelescopePruneLock`
101
+ seam, and five `prune.*` options. **Why not `major`:** nothing existing breaks.
102
+ Every new SPI method is optional, so a third-party provider compiles and runs
103
+ unchanged (it just keeps the old unbounded delete and logs a one-time warning),
104
+ and every new option is defaulted. The MikroORM adapter does add a
105
+ `telescope_leases` table, created by the same additive, never-dropping
106
+ `schema.update` that already manages `telescope_entries` — no migration to write,
107
+ nothing dropped, and it is in `telescopeManagedTables()` so a host's own migration
108
+ differ still skips it.
109
+
110
+ **Bounded batched deletes.** The retention delete was one unbounded
111
+ `DELETE ... WHERE created_at < ? AND type NOT IN (...)`. On a large table that
112
+ predicate matches nearly every row, so the planner correctly picks a full scan —
113
+ and that single statement holds row locks for the length of the scan. Measured on
114
+ a shared MySQL store: 755,614 rows / 1.3 GB, **21 concurrent prune deletes, the
115
+ oldest 63 minutes in**, with everything else writing to that database queued
116
+ behind them, and the pruner still 25h44m behind its own 24h window. The pruner now
117
+ issues a loop of short, individually-committed deletes, oldest first, tuned by
118
+ `prune.batchSize` (1000), `prune.maxBatchesPerCycle` (50) and `prune.batchPauseMs`
119
+ (50). Same rows deleted; locks released between batches. The ceiling matters as
120
+ much as the batching: without it a badly-behind table turns one tick into an
121
+ hour-long loop, which is the original failure spelled differently.
122
+
123
+ This is expressed portably. `DELETE ... ORDER BY ... LIMIT` is MySQL/MariaDB-only —
124
+ PostgreSQL has no `ORDER BY`/`LIMIT` on `DELETE`, SQLite needs a non-default
125
+ compile flag, SQL Server spells it `DELETE TOP (n)` — so the bound is "select the
126
+ oldest `limit` ids, then delete by primary key", which every driver renders, and
127
+ which is the better plan anyway (the `created_at` index walk stops after `limit`
128
+ matches and takes no row locks). `keepLast` scopes keep the unbounded path:
129
+ "keep the newest N of the doomed rows" is a whole-set property a per-batch bound
130
+ cannot express, so `keepLast` is absent from `BoundedPruneScope` by construction.
131
+
132
+ **A prune lock, so N replicas do not do the same work N times.** The in-flight
133
+ guard bounds one process; it cannot see the other seven pods. `prune.lock` adds a
134
+ small advisory seam — acquire a named lease with a TTL, release it, or be told
135
+ somebody else holds it — defaulting to a lease row in the database Telescope
136
+ already writes to, so it needs no new dependency and works on every in-repo
137
+ provider. A host that already runs a better primitive (a job engine's singleton
138
+ mutex, a Redis lock) supplies its own `TelescopePruneLock`; the acquire result is a
139
+ discriminated union, so an implementation cannot reach the lease without handling
140
+ the refusal. The lock is advisory by design — two concurrent prunes delete the same
141
+ rows and one wins, costing waste and never correctness — so the pruner **fails
142
+ open**: a broken lock warns once and prunes anyway rather than letting retention
143
+ silently stop while the table grows.
144
+
145
+ ## 1.14.3
146
+
147
+ ### Patch Changes
148
+
149
+ - [#59](https://github.com/DavideCarvalho/nestjs-telescope/pull/59) [`93f2b6d`](https://github.com/DavideCarvalho/nestjs-telescope/commit/93f2b6dd471ef26efb7024c724f174cee4f3543f) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Widen `MikroOrmStorageProviderOptions` to `Partial<Options>` (was the full `Options`). The provider's own `buildOrmOptions()` already treated the connection config as partial — it forwards `source` to `MikroORM.init()` (whose own signature is `Partial<Options>`) and always overrides `entities` — so passing the minimal `{ driver, dbName }` shown in the docs and every test always worked at runtime but was rejected by the type checker. No behavior change.
150
+
151
+ ## 1.14.2
152
+
153
+ ### Patch Changes
154
+
155
+ - [`3243d8c`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3243d8c8f3196fc1fe8de0a79076b67a74b5b090) - Widen `telescope_entries.trace_id` to VARCHAR(64): an explicit `RecordInput.traceId` can be a 36-char UUID (e.g. a durable runId), which the previous length-32 column silently truncated on MySQL — making the `#/traces/:id` waterfall lookup match nothing (404). Existing tables are realigned by the additive `schema.update` on the next boot.
156
+
157
+ ## 1.14.1
158
+
159
+ ### Patch Changes
160
+
161
+ - [#39](https://github.com/DavideCarvalho/nestjs-telescope/pull/39) [`58f5d72`](https://github.com/DavideCarvalho/nestjs-telescope/commit/58f5d727244441c21f4db4ce2cc55609ac6dd11c) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `telescopeMikroOrmLogger` accepts options as the first argument for the zero-config case — `telescopeMikroOrmLogger({ slowMs: 100 })` instead of `telescopeMikroOrmLogger(undefined, { slowMs: 100 })`.
162
+
163
+ ## 1.14.0
164
+
165
+ ### Minor Changes
166
+
167
+ - [#37](https://github.com/DavideCarvalho/nestjs-telescope/pull/37) [`3969c33`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3969c338dd6c360fb08bb68e6b279f06cdf7ab77) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add `telescopeManagedTables()`, returning the three tables
168
+ (`telescope_entries`, `telescope_rollups`, `telescope_schema_meta`) the
169
+ `MikroOrmStorageProvider` boot-manages. Consumers running their own MikroORM
170
+ CLI migrations against a shared database previously had to hand-maintain a
171
+ `/^telescope_/` regex (or an explicit list) in `skipTables` so a migration
172
+ diff never tried to drop telescope's tables — including the boot fingerprint
173
+ marker, which is easy to forget. `telescopeManagedTables()` derives the list
174
+ straight from the entities' own `tableName` metadata (never a hardcoded
175
+ parallel literal), so a future rename flows through automatically:
176
+
177
+ ```ts
178
+ import { telescopeManagedTables } from "@dudousxd/nestjs-telescope-mikro-orm";
179
+
180
+ await MikroORM.init({
181
+ // ...your entities/driver config
182
+ schemaGenerator: { skipTables: telescopeManagedTables() },
183
+ });
184
+ ```
185
+
186
+ - [#37](https://github.com/DavideCarvalho/nestjs-telescope/pull/37) [`3969c33`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3969c338dd6c360fb08bb68e6b279f06cdf7ab77) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add a first-class deferred record sink, `telescopeRecord`/`setTelescopeRecordSink`
187
+ (exported from core), mirroring `telescopeDump`'s pattern. It exists for
188
+ boot-time integrations — most notably a MikroORM query logger — that need to
189
+ emit Telescope entries before Nest DI has constructed `TelescopeService`:
190
+ MikroORM builds (and can start calling) its logger at `MikroORM.init()`, well
191
+ before `TelescopeModule` wires anything. `TelescopeService`'s constructor binds
192
+ the global sink automatically and `onApplicationShutdown` clears it; calls made
193
+ before binding (or after shutdown) are dropped, not buffered — there is no
194
+ unbounded queue waiting to replay boot noise.
195
+
196
+ This removes the need for host apps to hand-roll their own module-global
197
+ bridge (`let telescopeRecord; export function setTelescopeRecord()` filled in
198
+ from `onModuleInit`) just to wire the MikroORM logger before DI is ready.
199
+
200
+ `telescopeMikroOrmLogger`'s `record` parameter is now optional. When omitted,
201
+ the logger lazily resolves `telescopeRecord` from core at each `logQuery` call
202
+ (not at construction), so binding order no longer matters:
203
+
204
+ ```ts
205
+ MikroORM.init({
206
+ debug: ["query"],
207
+ loggerFactory: telescopeMikroOrmLogger(),
208
+ });
209
+ ```
210
+
211
+ An explicit `record` function, when passed, still takes precedence over the
212
+ global sink.
213
+
214
+ ## 1.13.0
215
+
216
+ ### Minor Changes
217
+
218
+ - [`df101cb`](https://github.com/DavideCarvalho/nestjs-telescope/commit/df101cba5aa5402cb87425c567412b3a9503e3af) - Gate the boot-time schema reconciliation behind a metadata fingerprint so
219
+ steady-state boots stop paying for a full-database introspection. The provider's
220
+ `init()` previously ran `schema.update({ safe: true })` on every boot, which
221
+ diffs the WHOLE database's `information_schema` against telescope's entities and
222
+ emits zero DDL once the schema is settled — ~1.5–3s of round-trips on a remote
223
+ DB, every time.
224
+
225
+ `init()` now: ensures the database exists, maintains an idempotent
226
+ `telescope_schema_meta` marker table (one row), and compares the stored
227
+ fingerprint against one computed purely in memory from entity metadata (sorted
228
+ tables/columns/indexes, plus the platform name, configured collation, and a
229
+ hand-bump `SCHEMA_REVISION`). When they match it SKIPS `ensureDatabase`'s heavy
230
+ sibling `schema.update` entirely. Only a fresh DB, an entity change, or a
231
+ `SCHEMA_REVISION` bump invalidates the fingerprint and runs the additive
232
+ `schema.update`, after which the marker is re-cached. The heal runs under a
233
+ best-effort advisory lock (MySQL `GET_LOCK` / PostgreSQL `pg_advisory_lock`,
234
+ no-op on SQLite) with a re-check so concurrent pods don't all introspect at
235
+ once. Behavior is unchanged from the caller's view: fresh databases and CI still
236
+ auto-create their schema with zero configuration.
237
+
238
+ ## 1.12.0
239
+
240
+ ### Minor Changes
241
+
242
+ - [#25](https://github.com/DavideCarvalho/nestjs-telescope/pull/25) [`b8b6dcd`](https://github.com/DavideCarvalho/nestjs-telescope/commit/b8b6dcd3bb2fc29d6c287b00d4d75c0a149a4b56) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add a `silent` option to `telescopeMikroOrmLogger` so queries can be captured without spamming the console.
243
+
244
+ MikroORM only calls the logger while `debug` is enabled, but `debug` also echoes every query to stdout — which makes local debugging painful once Telescope is wired in. Passing `{ silent: true }` swaps MikroORM's writer for a no-op so queries still flow into Telescope (correlated to the active ALS batch, slow-tagged, hashed for N+1) while nothing is printed:
245
+
246
+ ```ts
247
+ loggerFactory: telescopeMikroOrmLogger(
248
+ (input) => telescope.record(input),
249
+ { slowMs: 100, silent: true },
250
+ ),
251
+ ```
252
+
253
+ `silent` defaults to `false`, preserving the previous tee-to-console behavior.
254
+
255
+ ## 1.11.1
256
+
257
+ ### Patch Changes
258
+
259
+ - [`ba64ffb`](https://github.com/DavideCarvalho/nestjs-telescope/commit/ba64ffb13437661f6cf6e2432608a6651210dce1) - Fix (redis): make rollup merges atomic via a per-bucket Lua script (`HINCRBY` count/sum/histogram cells + max CAS + `ZADD`), removing the read-modify-write race that could drop concurrent rollup writes; `readHistogram` still merges any legacy JSON blob.
260
+
261
+ Fix (mikro-orm): honor empty-string entry-query filters (switched truthy checks to `!== undefined` for type/familyHash/batchId/tag), matching the in-memory/sqlite/redis providers.
262
+
263
+ Internal refactors (behavior-preserving): drive `jobAction` off an `ACTION_METHOD` dispatch map, extract a module-level `parseWindowMs` (deduping 5 identical parsers), and move the request-replay subsystem out of the controller into `nest/request-replay.ts`.
264
+
265
+ ## 1.11.0
266
+
267
+ ### Minor Changes
268
+
269
+ - [#17](https://github.com/DavideCarvalho/nestjs-telescope/pull/17) [`76228a3`](https://github.com/DavideCarvalho/nestjs-telescope/commit/76228a3a33e4e6d649cd8343646472feff97ed16) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Ecosystem improvements across the telescope packages.
270
+
271
+ - **Nested trace / batch waterfall view.** Traces and batches now render as a
272
+ nested waterfall, showing child spans nested under their parents with relative
273
+ offsets and durations so the critical path of a request is visible at a glance.
274
+ - **Pulse-style outlier & rollup cards.** New overview cards surface the slowest
275
+ endpoints, slowest queries, slowest jobs, and slowest outbound calls, plus
276
+ load-by-user and CPU/memory history, in the spirit of Laravel Pulse rollups.
277
+ - **Metric-threshold alert rules.** Alerts can now fire on metric thresholds
278
+ (p95 / p99 latency, cache-hit ratio) in addition to existing rules, with
279
+ shared-storage exception deduplication so the same exception is not alerted
280
+ multiple times across replicas.
281
+ - **Improved N+1 detection.** Detection now recognizes the 1+N loop pattern and
282
+ is duration-weighted, reducing false positives and prioritizing the queries
283
+ that actually cost time.
284
+ - **On-demand CPU flamegraph profiling.** A strictly-opt-in `profiling` option
285
+ (OFF by default) captures V8 CPU profiles via Node's `inspector`, aggregates
286
+ them into a self/total frame tree, and persists them as `cpu_profile` entries
287
+ correlated to their request batch. The dashboard gains a Profiles tab with a
288
+ dependency-light flamegraph renderer, a hot-functions table, and an
289
+ "arm next N requests" trigger. When profiling is disabled there is zero
290
+ inspector usage and no request-path cost beyond a single boolean check.
291
+ - **`TelescopeUiModule.forRootAsync`.** The UI module now mirrors
292
+ `TelescopeModule.forRootAsync`, resolving dashboard options via DI.
293
+ - **Realtime dashboards / SSE live-tail.** Dashboards update in realtime over an
294
+ `@Sse` invalidation stream (falling back to polling) with a LIVE indicator.
295
+ - **Packaging hygiene.** `sideEffects` is now declared on each publishable
296
+ package to enable safe tree-shaking for downstream bundlers.
297
+ - **Independent versioning.** The changeset `fixed` group was removed so packages
298
+ version independently going forward.
299
+
300
+ ### Patch Changes
301
+
302
+ - [#17](https://github.com/DavideCarvalho/nestjs-telescope/pull/17) [`76228a3`](https://github.com/DavideCarvalho/nestjs-telescope/commit/76228a3a33e4e6d649cd8343646472feff97ed16) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Packaging hygiene + `TelescopeUiModule.forRootAsync`.
303
+
304
+ - `TelescopeUiModule` now exposes `forRootAsync({ imports, inject, useFactory, path })`, mirroring `TelescopeModule.forRootAsync`: dashboard options (`assetsDir`) are resolved via DI. As with the core module, the mount `path` is bound at module-build time and is passed statically on the config object (the static value also drives the serve-time asset-base rewrite, so it always matches the route).
305
+ - Added `"sideEffects"` to each publishable package to enable safe tree-shaking for downstream bundlers:
306
+ - `false` on the pure watcher / helper / provider packages (ai, bullmq, cache, events, logs, mail, mikro-orm, mikro-orm-watcher, otel, prisma, redis, redis-watcher, sqs, typeorm, testing) — their entrypoints only re-export classes/types with no module-load side effects.
307
+ - `["./dist/schedule.watcher.js"]` on schedule, which carries a load-bearing bare `import 'reflect-metadata'`.
308
+ - `["./dist/server/*.js", "**/*.css"]` on ui, whose server module/controller emit decorator metadata at load and whose bundled SPA ships CSS.
309
+ - core is left unmarked (treated as side-effecting) because its decorator metadata emit spans many directories.
310
+
311
+ ## 1.10.0
312
+
313
+ ## 1.9.1
314
+
315
+ ## 1.9.0
316
+
317
+ ## 1.8.0
318
+
319
+ ## 1.7.1
320
+
321
+ ## 1.7.0
322
+
323
+ ## 1.6.0
324
+
325
+ ## 1.5.0
326
+
327
+ ## 1.4.0
328
+
329
+ ### Minor Changes
330
+
331
+ - [`3f72bc5`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3f72bc55cf3a96fe6ea85b37a809b59531caa39a) - Per-type prune retention and an archive sink that exports entries before they are pruned.
332
+
333
+ - `prune.perType` overrides the global `after` cutoff for specific entry types (e.g. `{ after: '5m', perType: { exception: '7d' } }`). Types without an override keep using the global cutoff, so omitting `perType` is exactly backward compatible. Per-type durations are validated at module init like `after`.
334
+ - `archive` hands a type's doomed entries to a host-owned `sink` (S3, a data lake, cold storage) **before** the pruner deletes them, in bounded batches. A failing sink keeps the entries (retried next cycle) and never crashes the host or stops the pruner.
335
+ - New optional `StorageProvider.pruneScoped(input)` method backs per-type deletes; it is implemented for every in-repo adapter (SQLite, MikroORM, Redis, in-memory). Third-party providers without it fall back to the global cutoff with a one-time warning.
336
+
337
+ ## 1.3.0
338
+
339
+ ## 1.2.1
340
+
341
+ ## 1.2.0
342
+
343
+ ## 1.1.0
344
+
5
345
  ### Patch Changes
6
346
 
7
347
  - Updated dependencies [[`c2423f3`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c2423f330be3f9b92c0dbf2348220bf8740dab86), [`d8173d4`](https://github.com/DavideCarvalho/nestjs-telescope/commit/d8173d4c5d362814aa0fcdb0bfb35fd353b3d1a8), [`d200d15`](https://github.com/DavideCarvalho/nestjs-telescope/commit/d200d158f927e6a67396e594b32bfd0a0b3424e4), [`953ae12`](https://github.com/DavideCarvalho/nestjs-telescope/commit/953ae12fd35e42df3b806d6bbec6b49e3e3c71fb), [`4ceb884`](https://github.com/DavideCarvalho/nestjs-telescope/commit/4ceb8846b7307cf522d841c909d7eaf7fcb1aa4e), [`15e3e90`](https://github.com/DavideCarvalho/nestjs-telescope/commit/15e3e903d82616966342feeb966fbd44ad6a2631)]:
package/README.md CHANGED
@@ -42,7 +42,6 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
42
42
  @Module({
43
43
  imports: [
44
44
  TelescopeModule.forRoot({
45
- authorizer: () => true, // restrict in production
46
45
  watchers: [new MikroOrmQueryWatcher()],
47
46
  }),
48
47
 
@@ -68,6 +67,23 @@ Queries recorded via `telescopeMikroOrmLogger` automatically inherit the active
68
67
  ALS batch opened by the request middleware, so each query entry is correlated to
69
68
  the request that triggered it.
70
69
 
70
+ ### Capturing without console noise
71
+
72
+ Capture only works while MikroORM's `debug` is enabled — that's what makes it
73
+ call the logger. But `debug` also echoes every query to stdout, which drowns your
74
+ logs. To feed Telescope **without** the console spam, pass `silent: true`:
75
+
76
+ ```ts
77
+ loggerFactory: telescopeMikroOrmLogger(
78
+ (input) => telescope.record(input),
79
+ { slowMs: 100, silent: true }, // record into Telescope, but no stdout echo
80
+ ),
81
+ ```
82
+
83
+ Keep `debug: ['query']` enabled — `silent` swaps MikroORM's writer for a no-op,
84
+ so queries still flow into Telescope while nothing is printed. Leave `silent`
85
+ off (the default) if you want queries in both Telescope and the console.
86
+
71
87
  ## N+1 detection
72
88
 
73
89
  ```ts
@@ -143,6 +159,16 @@ How it works:
143
159
  - **Keyset pagination** is newest-first (`createdAt DESC, id DESC`); the cursor
144
160
  encodes a `(createdAt, id)` position via `$or`, so paging resumes correctly
145
161
  even after older entries are pruned.
162
+ - **Flushes are plain multi-row `INSERT`s** (`em.insertMany`), outside the
163
+ unit of work, at most the ORM's `batchSize` rows (default 300) per statement.
164
+ A flush that needs several statements runs them in one transaction, so it is
165
+ stored whole or not at all.
166
+ - **Rollups merge in the database.** `telescope_rollups` is written with one
167
+ native upsert per 100 buckets (`ON DUPLICATE KEY UPDATE` on MySQL/MariaDB,
168
+ `ON CONFLICT … DO UPDATE` on PostgreSQL and SQLite/libSQL) that adds count,
169
+ sum and histogram cells and keeps the larger max against the stored row, so
170
+ concurrent pods never lose or reject each other's increments. Other drivers
171
+ use a transactional read-modify-write, safe only with a single writer.
146
172
  - **Tag filtering** runs against a space-padded `tagsText` column with
147
173
  `LIKE '% <tag> %'` (JSON-array predicates are not portable across MySQL and
148
174
  SQLite). The `tags` JSON column remains the source of truth for retrieval.
@@ -152,7 +178,7 @@ How it works:
152
178
  | Export | Description |
153
179
  |--------|-------------|
154
180
  | `MikroOrmQueryWatcher` | Watcher marker — add to `TelescopeModule.forRoot({ watchers })` |
155
- | `telescopeMikroOrmLogger(record, opts?)` | `loggerFactory`-compatible factory; `opts.slowMs` defaults to `100` |
181
+ | `telescopeMikroOrmLogger(record, opts?)` | `loggerFactory`-compatible factory; `opts.slowMs` defaults to `100`, `opts.silent` (default `false`) suppresses MikroORM's console output while still recording |
156
182
  | `TelescopeMikroOrmLogger` | `DefaultLogger` subclass (advanced: extend or instantiate directly) |
157
183
  | `MikroOrmStorageProvider` | `StorageProvider` persisting entries to MySQL/SQLite; owns a dedicated scoped connection and self-heals its schema at boot |
158
184
  | `TelescopeEntry` | `EntitySchema` for table `telescope_entries` (owned internally by the provider; exported for advanced use — you don't register it) |
package/dist/index.d.ts CHANGED
@@ -3,5 +3,7 @@ export { detectNPlusOne, type NPlusOneInsight } from '@dudousxd/nestjs-telescope
3
3
  export * from './telescope-mikro-orm.logger.js';
4
4
  export * from './mikro-orm-query.watcher.js';
5
5
  export * from './telescope-entry.entity.js';
6
+ export * from './telescope-lease.entity.js';
6
7
  export * from './mikro-orm-storage.provider.js';
8
+ export * from './schema.js';
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -3,5 +3,7 @@ export { detectNPlusOne } from '@dudousxd/nestjs-telescope';
3
3
  export * from './telescope-mikro-orm.logger.js';
4
4
  export * from './mikro-orm-query.watcher.js';
5
5
  export * from './telescope-entry.entity.js';
6
+ export * from './telescope-lease.entity.js';
6
7
  export * from './mikro-orm-storage.provider.js';
8
+ export * from './schema.js';
7
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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"}
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,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,aAAa,CAAC"}
@@ -1,16 +1,24 @@
1
- import type { Entry, EntryQuery, EntryWithBatch, Page, RollupBucket, RollupDelta, RollupStore, StorageProvider, TagCount } from '@dudousxd/nestjs-telescope';
1
+ import type { BoundedPruneCapable, BoundedPruneResult, BoundedPruneScope, Entry, EntryQuery, EntryWithBatch, LeaseCapableStorage, Page, PruneScope, RollupBucket, RollupDelta, RollupStore, TagCount, TagQuery } from '@dudousxd/nestjs-telescope';
2
2
  import type { Options } from '@mikro-orm/core';
3
3
  import { MikroORM } from '@mikro-orm/core';
4
4
  /**
5
5
  * Options for the dedicated, single-entity ORM the provider owns. These are
6
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]`.
7
+ * `ensureSchema` flag. Partial, mirroring `MikroORM.init()`'s own `Partial<Options>`
8
+ * signature — callers only need to supply connection fields (e.g. `driver`,
9
+ * `dbName`); `buildOrmOptions()` below treats `source` as partial and fills in the
10
+ * rest. Any `entities` provided here is overridden — the provider always forces
11
+ * `entities: [TelescopeEntry, TelescopeRollup]`.
9
12
  */
10
- export type MikroOrmStorageProviderOptions = Options & {
13
+ export type MikroOrmStorageProviderOptions = Partial<Options> & {
11
14
  ensureSchema?: boolean;
12
15
  };
13
- export declare class MikroOrmStorageProvider implements StorageProvider, RollupStore {
16
+ /**
17
+ * Declared against {@link BoundedPruneCapable} and {@link LeaseCapableStorage}
18
+ * (both extend `StorageProvider`) so dropping either capability is a compile
19
+ * error rather than a silent fall back to unbounded, unlocked pruning.
20
+ */
21
+ export declare class MikroOrmStorageProvider implements BoundedPruneCapable, LeaseCapableStorage, RollupStore {
14
22
  private readonly source;
15
23
  private readonly ensureSchema;
16
24
  private orm;
@@ -19,33 +27,56 @@ export declare class MikroOrmStorageProvider implements StorageProvider, RollupS
19
27
  });
20
28
  private buildOrmOptions;
21
29
  init(): Promise<void>;
30
+ /**
31
+ * Fingerprint-gated schema reconciliation. Replaces a bare
32
+ * `ensureDatabase(); schema.update()` so steady-state boots skip the
33
+ * whole-database introspection (see the gate notes above the helpers).
34
+ *
35
+ * FRESH-DB ORDERING: `ensureDatabase()` runs FIRST, before the marker table is
36
+ * touched. The provider owns a DEDICATED connection, so on a brand-new DB the
37
+ * database itself may not exist yet — a `create table telescope_schema_meta`
38
+ * issued before `ensureDatabase()` would hit "unknown database" on MySQL/PG.
39
+ * `ensureDatabase()` creates it when missing and is a single cheap existence
40
+ * check when it already exists (the common case: the host app is already
41
+ * connected to that database), so paying for it every boot is negligible next
42
+ * to the multi-second `schema.update` introspection the gate eliminates.
43
+ */
44
+ private reconcileSchema;
22
45
  close(): Promise<void>;
23
46
  private forkEm;
47
+ /**
48
+ * Plain multi-row INSERTs, not `em.create()` + `flush()`: entries are write-once, so managed
49
+ * entities and change sets bought nothing and cost several times the INSERT itself on a busy
50
+ * host. Chunked by the ORM's `batchSize` so a large flush stays under every driver's
51
+ * bound-parameter limit; a flush spanning several chunks runs in one transaction so it lands
52
+ * whole or not at all, and the recorder's retry never re-inserts rows a failed attempt wrote.
53
+ */
24
54
  store(entries: Entry[]): Promise<void>;
25
55
  update(id: string, patch: Partial<Entry>): Promise<void>;
26
56
  find(id: string): Promise<EntryWithBatch | null>;
27
57
  get(query: EntryQuery): Promise<Page<Entry>>;
28
58
  batch(batchId: string): Promise<Entry[]>;
29
- tags(prefix?: string): Promise<TagCount[]>;
59
+ tags(prefix?: string, query?: TagQuery): Promise<TagCount[]>;
30
60
  prune(olderThan: Date, keepLast?: number): Promise<number>;
61
+ pruneScopedBatch(input: BoundedPruneScope): Promise<BoundedPruneResult>;
62
+ tryAcquireLease(key: string, owner: string, ttlMs: number, nowMs: number): Promise<boolean>;
63
+ releaseLease(key: string, owner: string): Promise<void>;
64
+ pruneScoped(input: PruneScope): Promise<number>;
31
65
  clear(): Promise<void>;
32
66
  /**
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
67
+ * Additively merge deltas into `telescope_rollups`: count/sum accumulate, `max`
68
+ * keeps the running maximum and the histogram adds element-wise, per
36
69
  * (metric, bucketStart).
37
70
  *
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.
71
+ * On MySQL/MariaDB, PostgreSQL and SQLite this is one native upsert per chunk
72
+ * of buckets, with every merge done by the database against the row as it is
73
+ * at write time, so concurrent writers (other pods) never lose each other's
74
+ * increments. Buckets are written in key order, so two writers touching the
75
+ * same buckets lock them in the same order. Other drivers fall back to a
76
+ * transactional read-modify-write.
47
77
  */
48
78
  recordRollups(deltas: RollupDelta[]): Promise<void>;
79
+ private recordRollupsReadModifyWrite;
49
80
  queryRollups(metrics: string[], fromBucket: number, toBucket: number): Promise<RollupBucket[]>;
50
81
  }
51
82
  //# sourceMappingURL=mikro-orm-storage.provider.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"mikro-orm-storage.provider.d.ts","sourceRoot":"","sources":["../src/mikro-orm-storage.provider.ts"],"names":[],"mappings":"AAoDA,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,EACL,UAAU,EACV,cAAc,EACd,mBAAmB,EACnB,IAAI,EACJ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,QAAQ,EACR,QAAQ,EACT,MAAM,4BAA4B,CAAC;AASpC,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC3F,OAAO,EAAE,QAAQ,EAAO,MAAM,iBAAiB,CAAC;AAoEhD;;;;;;;;GAQG;AACH,MAAM,MAAM,8BAA8B,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AA6W3F;;;;GAIG;AACH,qBAAa,uBACX,YAAW,mBAAmB,EAAE,mBAAmB,EAAE,WAAW;IAEhE,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;IAQ3B;;;;;;;;;;;;;OAaG;YACW,eAAe;IAmCvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,OAAO,CAAC,MAAM;IAOd;;;;;;OAMG;IACG,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBtC,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;IAkE5C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAMxC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IA0B5D,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgB1D,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA0BvE,eAAe,CACnB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC;IA8Bb,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvD,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAgB/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YA8B3C,4BAA4B;IAiCpC,YAAY,CAChB,OAAO,EAAE,MAAM,EAAE,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,EAAE,CAAC;CAgB3B"}