@dudousxd/nestjs-telescope-ui 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 (179) hide show
  1. package/CHANGELOG.md +349 -0
  2. package/LICENSE +21 -0
  3. package/README.md +51 -0
  4. package/dist/client/index.d.ts +4 -0
  5. package/dist/client/index.d.ts.map +1 -0
  6. package/dist/client/index.js +2 -0
  7. package/dist/client/index.js.map +1 -0
  8. package/dist/client/telescope-client.d.ts +63 -0
  9. package/dist/client/telescope-client.d.ts.map +1 -0
  10. package/dist/client/telescope-client.js +87 -0
  11. package/dist/client/telescope-client.js.map +1 -0
  12. package/dist/client/types.d.ts +267 -0
  13. package/dist/client/types.d.ts.map +1 -0
  14. package/dist/client/types.js +2 -0
  15. package/dist/client/types.js.map +1 -0
  16. package/dist/react/components/batch-timeline.d.ts +7 -0
  17. package/dist/react/components/batch-timeline.d.ts.map +1 -0
  18. package/dist/react/components/batch-timeline.js +10 -0
  19. package/dist/react/components/batch-timeline.js.map +1 -0
  20. package/dist/react/components/cache-badge.d.ts +20 -0
  21. package/dist/react/components/cache-badge.d.ts.map +1 -0
  22. package/dist/react/components/cache-badge.js +42 -0
  23. package/dist/react/components/cache-badge.js.map +1 -0
  24. package/dist/react/components/charts/area-chart-card.d.ts +16 -0
  25. package/dist/react/components/charts/area-chart-card.d.ts.map +1 -0
  26. package/dist/react/components/charts/area-chart-card.js +12 -0
  27. package/dist/react/components/charts/area-chart-card.js.map +1 -0
  28. package/dist/react/components/charts/bar-chart-card.d.ts +26 -0
  29. package/dist/react/components/charts/bar-chart-card.d.ts.map +1 -0
  30. package/dist/react/components/charts/bar-chart-card.js +27 -0
  31. package/dist/react/components/charts/bar-chart-card.js.map +1 -0
  32. package/dist/react/components/charts/chart-card.d.ts +11 -0
  33. package/dist/react/components/charts/chart-card.d.ts.map +1 -0
  34. package/dist/react/components/charts/chart-card.js +9 -0
  35. package/dist/react/components/charts/chart-card.js.map +1 -0
  36. package/dist/react/components/charts/chart-theme.d.ts +41 -0
  37. package/dist/react/components/charts/chart-theme.d.ts.map +1 -0
  38. package/dist/react/components/charts/chart-theme.js +53 -0
  39. package/dist/react/components/charts/chart-theme.js.map +1 -0
  40. package/dist/react/components/charts/index.d.ts +6 -0
  41. package/dist/react/components/charts/index.d.ts.map +1 -0
  42. package/dist/react/components/charts/index.js +6 -0
  43. package/dist/react/components/charts/index.js.map +1 -0
  44. package/dist/react/components/charts/stacked-area-chart-card.d.ts +14 -0
  45. package/dist/react/components/charts/stacked-area-chart-card.d.ts.map +1 -0
  46. package/dist/react/components/charts/stacked-area-chart-card.js +13 -0
  47. package/dist/react/components/charts/stacked-area-chart-card.js.map +1 -0
  48. package/dist/react/components/entries-table.d.ts +11 -0
  49. package/dist/react/components/entries-table.d.ts.map +1 -0
  50. package/dist/react/components/entries-table.js +68 -0
  51. package/dist/react/components/entries-table.js.map +1 -0
  52. package/dist/react/components/entry-detail.d.ts +6 -0
  53. package/dist/react/components/entry-detail.d.ts.map +1 -0
  54. package/dist/react/components/entry-detail.js +110 -0
  55. package/dist/react/components/entry-detail.js.map +1 -0
  56. package/dist/react/components/entry-insights.d.ts +5 -0
  57. package/dist/react/components/entry-insights.d.ts.map +1 -0
  58. package/dist/react/components/entry-insights.js +107 -0
  59. package/dist/react/components/entry-insights.js.map +1 -0
  60. package/dist/react/components/entry-types.d.ts +40 -0
  61. package/dist/react/components/entry-types.d.ts.map +1 -0
  62. package/dist/react/components/entry-types.js +44 -0
  63. package/dist/react/components/entry-types.js.map +1 -0
  64. package/dist/react/components/export-json-toolbar.d.ts +9 -0
  65. package/dist/react/components/export-json-toolbar.d.ts.map +1 -0
  66. package/dist/react/components/export-json-toolbar.js +21 -0
  67. package/dist/react/components/export-json-toolbar.js.map +1 -0
  68. package/dist/react/components/export-json.d.ts +14 -0
  69. package/dist/react/components/export-json.d.ts.map +1 -0
  70. package/dist/react/components/export-json.js +44 -0
  71. package/dist/react/components/export-json.js.map +1 -0
  72. package/dist/react/components/pulse-panel.d.ts +9 -0
  73. package/dist/react/components/pulse-panel.d.ts.map +1 -0
  74. package/dist/react/components/pulse-panel.js +20 -0
  75. package/dist/react/components/pulse-panel.js.map +1 -0
  76. package/dist/react/components/queue-sparkline.d.ts +5 -0
  77. package/dist/react/components/queue-sparkline.d.ts.map +1 -0
  78. package/dist/react/components/queue-sparkline.js +8 -0
  79. package/dist/react/components/queue-sparkline.js.map +1 -0
  80. package/dist/react/components/queues/EnqueueJobForm.d.ts +14 -0
  81. package/dist/react/components/queues/EnqueueJobForm.d.ts.map +1 -0
  82. package/dist/react/components/queues/EnqueueJobForm.js +61 -0
  83. package/dist/react/components/queues/EnqueueJobForm.js.map +1 -0
  84. package/dist/react/components/queues/JobActions.d.ts +38 -0
  85. package/dist/react/components/queues/JobActions.d.ts.map +1 -0
  86. package/dist/react/components/queues/JobActions.js +106 -0
  87. package/dist/react/components/queues/JobActions.js.map +1 -0
  88. package/dist/react/components/queues/JobDetailDrawer.d.ts +11 -0
  89. package/dist/react/components/queues/JobDetailDrawer.d.ts.map +1 -0
  90. package/dist/react/components/queues/JobDetailDrawer.js +45 -0
  91. package/dist/react/components/queues/JobDetailDrawer.js.map +1 -0
  92. package/dist/react/components/queues/JobTable.d.ts +13 -0
  93. package/dist/react/components/queues/JobTable.d.ts.map +1 -0
  94. package/dist/react/components/queues/JobTable.js +0 -0
  95. package/dist/react/components/queues/JobTable.js.map +1 -0
  96. package/dist/react/components/queues/QueueList.d.ts +8 -0
  97. package/dist/react/components/queues/QueueList.d.ts.map +1 -0
  98. package/dist/react/components/queues/QueueList.js +27 -0
  99. package/dist/react/components/queues/QueueList.js.map +1 -0
  100. package/dist/react/components/queues/QueueStateTabs.d.ts +7 -0
  101. package/dist/react/components/queues/QueueStateTabs.d.ts.map +1 -0
  102. package/dist/react/components/queues/QueueStateTabs.js +12 -0
  103. package/dist/react/components/queues/QueueStateTabs.js.map +1 -0
  104. package/dist/react/components/queues/index.d.ts +8 -0
  105. package/dist/react/components/queues/index.d.ts.map +1 -0
  106. package/dist/react/components/queues/index.js +8 -0
  107. package/dist/react/components/queues/index.js.map +1 -0
  108. package/dist/react/components/queues/queue-format.d.ts +8 -0
  109. package/dist/react/components/queues/queue-format.d.ts.map +1 -0
  110. package/dist/react/components/queues/queue-format.js +51 -0
  111. package/dist/react/components/queues/queue-format.js.map +1 -0
  112. package/dist/react/components/queues-panel.d.ts +6 -0
  113. package/dist/react/components/queues-panel.d.ts.map +1 -0
  114. package/dist/react/components/queues-panel.js +8 -0
  115. package/dist/react/components/queues-panel.js.map +1 -0
  116. package/dist/react/components/request-timeline.d.ts +14 -0
  117. package/dist/react/components/request-timeline.d.ts.map +1 -0
  118. package/dist/react/components/request-timeline.js +30 -0
  119. package/dist/react/components/request-timeline.js.map +1 -0
  120. package/dist/react/components/retention-indicator.d.ts +10 -0
  121. package/dist/react/components/retention-indicator.d.ts.map +1 -0
  122. package/dist/react/components/retention-indicator.js +38 -0
  123. package/dist/react/components/retention-indicator.js.map +1 -0
  124. package/dist/react/components/sparkline.d.ts +7 -0
  125. package/dist/react/components/sparkline.d.ts.map +1 -0
  126. package/dist/react/components/sparkline.js +20 -0
  127. package/dist/react/components/sparkline.js.map +1 -0
  128. package/dist/react/components/stacked-bars.d.ts +21 -0
  129. package/dist/react/components/stacked-bars.d.ts.map +1 -0
  130. package/dist/react/components/stacked-bars.js +51 -0
  131. package/dist/react/components/stacked-bars.js.map +1 -0
  132. package/dist/react/components/tag-autocomplete.d.ts +12 -0
  133. package/dist/react/components/tag-autocomplete.d.ts.map +1 -0
  134. package/dist/react/components/tag-autocomplete.js +90 -0
  135. package/dist/react/components/tag-autocomplete.js.map +1 -0
  136. package/dist/react/components/trace-link.d.ts +6 -0
  137. package/dist/react/components/trace-link.d.ts.map +1 -0
  138. package/dist/react/components/trace-link.js +10 -0
  139. package/dist/react/components/trace-link.js.map +1 -0
  140. package/dist/react/components/type-tabs.d.ts +5 -0
  141. package/dist/react/components/type-tabs.d.ts.map +1 -0
  142. package/dist/react/components/type-tabs.js +8 -0
  143. package/dist/react/components/type-tabs.js.map +1 -0
  144. package/dist/react/components/window-select.d.ts +5 -0
  145. package/dist/react/components/window-select.d.ts.map +1 -0
  146. package/dist/react/components/window-select.js +6 -0
  147. package/dist/react/components/window-select.js.map +1 -0
  148. package/dist/react/index.d.ts +23 -0
  149. package/dist/react/index.d.ts.map +1 -0
  150. package/dist/react/index.js +23 -0
  151. package/dist/react/index.js.map +1 -0
  152. package/dist/react/telescope-context.d.ts +14 -0
  153. package/dist/react/telescope-context.d.ts.map +1 -0
  154. package/dist/react/telescope-context.js +27 -0
  155. package/dist/react/telescope-context.js.map +1 -0
  156. package/dist/react/use-telescope-queries.d.ts +244 -0
  157. package/dist/react/use-telescope-queries.d.ts.map +1 -0
  158. package/dist/react/use-telescope-queries.js +216 -0
  159. package/dist/react/use-telescope-queries.js.map +1 -0
  160. package/dist/server/index.d.ts +3 -0
  161. package/dist/server/index.d.ts.map +1 -0
  162. package/dist/server/index.js +2 -0
  163. package/dist/server/index.js.map +1 -0
  164. package/dist/server/telescope-ui.controller.d.ts +10 -0
  165. package/dist/server/telescope-ui.controller.d.ts.map +1 -0
  166. package/dist/server/telescope-ui.controller.js +105 -0
  167. package/dist/server/telescope-ui.controller.js.map +1 -0
  168. package/dist/server/telescope-ui.module.d.ts +6 -0
  169. package/dist/server/telescope-ui.module.d.ts.map +1 -0
  170. package/dist/server/telescope-ui.module.js +26 -0
  171. package/dist/server/telescope-ui.module.js.map +1 -0
  172. package/dist/server/telescope-ui.options.d.ts +12 -0
  173. package/dist/server/telescope-ui.options.d.ts.map +1 -0
  174. package/dist/server/telescope-ui.options.js +2 -0
  175. package/dist/server/telescope-ui.options.js.map +1 -0
  176. package/dist/spa/assets/index-BHS361H_.js +135 -0
  177. package/dist/spa/assets/index-DsBEgxHC.css +1 -0
  178. package/dist/spa/index.html +13 -0
  179. package/package.json +97 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,349 @@
1
+ # @dudousxd/nestjs-telescope-ui
2
+
3
+ ## 1.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`1f00e62`](https://github.com/DavideCarvalho/nestjs-telescope/commit/1f00e62c8e60482b64251813680a5f866ef1619a) - Make the telescope mount path configurable. `TelescopeModule.forRoot({ path:
8
+ 'observability' })` and `TelescopeUiModule.forRoot({ path: 'observability' })`
9
+ now serve the dashboard at `/observability` and the API at `/observability/api`.
10
+ The `path` option (normalized — leading/trailing slashes stripped) defaults to
11
+ `'telescope'`, so behavior is byte-identical when unset.
12
+
13
+ Core binds the API controller route at module-build time via a per-`forRoot`
14
+ subclass of `TelescopeController` (`dynamicController(...)`), preserving all
15
+ route/DI metadata. `forRootAsync` accepts a static `path` field (the route must
16
+ be known synchronously). The request-capture middleware now skips the configured
17
+ path instead of a hardcoded `/telescope`, and the resolved core config exposes
18
+ `path`.
19
+
20
+ The UI is **not** rebuilt per-path: the SPA keeps the `/telescope/` Vite base
21
+ placeholder and the UI controller rewrites the asset base to `/<path>/` at serve
22
+ time, injecting `window.__TELESCOPE_BASE__` so the client derives its API base
23
+ (`createTelescopeClient` falls back to `/telescope/api` when the global is
24
+ absent; an explicit `baseUrl` still wins). Asset traversal safety is unchanged.
25
+
26
+ - [`090cd1f`](https://github.com/DavideCarvalho/nestjs-telescope/commit/090cd1ff871dbe46c1c877a26f90496550b5304c) - Dashboard insights, charts, and trace grouping. Core gains a `StatsService` +
27
+ `GET /stats` endpoint with per-type analytics (latency p50/p95/p99, query-family
28
+ breakdown, cache hit/miss ratio, request status breakdown) and an `EntryQuery.traceId`
29
+ filter across all storages. The schedule watcher now tags scheduled runs with a stable
30
+ `schedule` tag. The dashboard adds a per-type insights header with Recharts cards, cache
31
+ hit/miss badges, a working Schedule view (job entries tagged `schedule`), and a
32
+ `#/traces/:traceId` page that groups every entry sharing an OpenTelemetry trace.
33
+
34
+ - [`b7326b3`](https://github.com/DavideCarvalho/nestjs-telescope/commit/b7326b33d8d55b5f1ac5de4256f5e1980278699e) - Add a **Dumps** dev tool. Call `telescopeDump(value, 'label')` anywhere in your
35
+ code (no dependency injection at the call site) and the value shows up in a new
36
+ "Dumps" tab in the dashboard — a request-correlated alternative to
37
+ `console.log`. `telescopeDump` forwards to a module-level sink that
38
+ `TelescopeService` wires on construction and detaches on shutdown (it is a
39
+ no-op until wired, so importing it in shared code never crashes outside a
40
+ Telescope-enabled app). `TelescopeService.dump(value, label?)` records a `dump`
41
+ entry whose `value` is redacted by the Recorder like any other content and
42
+ correlated to the active batch. Core exports `telescopeDump`, `setTelescopeDump`,
43
+ `DumpContent`, and `EntryType.Dump`. The UI gains a `dump` entry type, a detail
44
+ view that renders the label and pretty-printed JSON value (guarding
45
+ non-serializable values), and a table summary showing the label or a value
46
+ preview.
47
+
48
+ - [`bfc0e26`](https://github.com/DavideCarvalho/nestjs-telescope/commit/bfc0e268388b5563d05c24e4de6ff99c74d1201a) - Add free-text search over entries. `EntryQuery` gains a `search?: string` field —
49
+ a case-insensitive substring matched against the entry's serialized `content`, so
50
+ you can find a request by uri, a query by sql, a cache op by key, or an exception
51
+ by message. It is applied as an extra AND predicate, so it composes with every
52
+ other filter and with keyset pagination, and it is independent of `omitContent`
53
+ (the match runs over the stored content before any projection).
54
+
55
+ Every storage provider implements it: sqlite (`content LIKE '%term%'`), mikro-orm
56
+ (`raw('content')` `$like` so the JSON column is matched as text, not JSON-encoded),
57
+ in-memory and redis (case-insensitive substring over `JSON.stringify(content)` in
58
+ the predicate / in-scan post-filter). The `GET /entries` endpoint reads a non-empty
59
+ `search` query param, and the dashboard entries filter bar gains a "Search content"
60
+ input (commits on Enter/blur, never on keystroke) shown as a clearable
61
+ `search:"term"` chip that "Clear filters" drops.
62
+
63
+ - [`6817fe6`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6817fe62775b1ff847fdb1038d3298e7709569e0) - Add exception grouping to the per-type stats insight. `summarizeStats` now
64
+ returns `exceptions?: ExceptionGroupStats[]` for the `exception` type: groups
65
+ the window's exception entries by family key (the entry's `familyHash`, or a
66
+ `${class}: ${message}` fallback when null) with `count`, `lastAt`, `class`,
67
+ `message`, and a per-bucket `overTime` series aligned to the report's buckets,
68
+ sorted by count desc then last-seen desc (top 8). The Exceptions view renders an
69
+ "Exception groups" list — class (mono), truncated message, count, relative
70
+ last-seen, and an inline over-time sparkline — each row drilling through to that
71
+ group's occurrences via `?familyHash=`.
72
+
73
+ - [`20ceb87`](https://github.com/DavideCarvalho/nestjs-telescope/commit/20ceb878cd4495dfbc7a3c71d882ae216a633757) - Surface JSON export and retention/sampling in the dashboard.
74
+
75
+ Core: `TelescopeMeta` (`GET /meta`) now reports the resolved `retention`
76
+ (`{ afterMs, keepLast }` from `prune`, or `null` when unbounded) and the
77
+ resolved per-type `sampling` rates, sourced from the resolved config.
78
+
79
+ UI: the entry-detail page gains a "Copy JSON" / "Download JSON" toolbar that
80
+ exports the full `EntryWithBatch` (`telescope-entry-<id>.json`); the trace page
81
+ gains a "Download JSON" button exporting all trace entries as a JSON array
82
+ (`telescope-trace-<traceId>.json`). The dashboard header shows a subtle
83
+ retention indicator (e.g. "retention: 5m", or "none" when unbounded) with a
84
+ sampling tooltip when any type is down-sampled below 1. New `toPrettyJson`,
85
+ `copyJson`, and `downloadJson` export utilities back the buttons; clipboard
86
+ absence degrades gracefully.
87
+
88
+ - [`d3ccf7d`](https://github.com/DavideCarvalho/nestjs-telescope/commit/d3ccf7dfdbe515b7efe134ca0d7d0cfb901bc8a2) - Add a "Telescope health" card to the Overview page rendering `GET /health`:
89
+ per-capture cost (µs), buffer pressure + high-water, flush durations, and dropped
90
+ count (green when keeping up, red with an overflow/store breakdown when shedding).
91
+ Makes Telescope's own overhead visible at a glance.
92
+
93
+ - [`9d8eb65`](https://github.com/DavideCarvalho/nestjs-telescope/commit/9d8eb6562a7584801d5aa8b74491091f0fade5f9) - Let operators **enqueue (send) a new job/message** onto a queue from the dashboard.
94
+
95
+ Core adds an optional `enqueue?(queue, payload, opts, ctx)` method to the
96
+ `QueueManager` SPI and a new `POST /telescope/api/queues/live/:driver/:queue/enqueue`
97
+ route. Unlike the other mutations it carries a JSON body (`{ name?, payload }`),
98
+ so it lives on its own path rather than under `:action` — but it flows through the
99
+ same default-deny `TelescopeActionGuard` as `retry` / `remove` / `redrive`: the
100
+ guard recovers the `enqueue` action from the request path, so without an
101
+ `authorizeAction` callback it returns `403`. The route returns `400` when the
102
+ payload is absent and `404` when the driver is unknown or doesn't implement
103
+ `enqueue`. `enqueue` is added to `QUEUE_ACTIONS` and advertised in the
104
+ `/queues/live` `actionsByDriver` capabilities when a manager implements it.
105
+
106
+ `BullMqQueueManager` implements `enqueue` via the real `Queue.add(name, data)`
107
+ (defaulting the name to `manual`), returning the new job id.
108
+
109
+ The UI gains an "Send message" form on the queue console — shown only when the
110
+ selected driver advertises `enqueue` and mutations are enabled. It parses the
111
+ payload textarea as JSON (invalid JSON surfaces inline without calling the API),
112
+ posts via a new `queueEnqueue` client method, and on success confirms and
113
+ refreshes the queue counts/jobs. A `403` surfaces inline as "Not authorized".
114
+
115
+ - [`9b6f12f`](https://github.com/DavideCarvalho/nestjs-telescope/commit/9b6f12f8b00f847f52c1adaa043c1e32d0b64598) - Add the live queue management console. The Queues area now leads with a
116
+ master-detail manager (`#/queues`): a live queue list with per-state count
117
+ badges and paused indicators, state tabs, a dense job table with cursor-based
118
+ "Load more", and a slide-over job detail drawer (pretty-printed payload/opts,
119
+ timestamps, attempts, and stacktrace for failures). Retry / remove / promote
120
+ and "Retry all failed" actions render only when the driver advertises the
121
+ action and `mutationsEnabled` is true (server-side default-deny still enforced;
122
+ 403s surface a friendly "Not authorized" hint). The Phase-3 throughput/runtime
123
+ metrics now live under a `Metrics` sub-tab (`#/queues/metrics`) so there is a
124
+ single, coherent "Queues" nav entry.
125
+
126
+ - [`ba7eeae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/ba7eeaeb23da9d054f5ef40b769f7738607305e6) - Add a gated "Redrive DLQ" action to the queue management UI. On the failed tab /
127
+ queue header, `RedriveDlqButton` is shown only when the driver advertises
128
+ `'redrive'` (`capabilities.actionsByDriver[driver]`) and `mutationsEnabled` is
129
+ true — so it surfaces for SQS-style DLQ drivers and stays hidden for BullMQ. It
130
+ confirms before firing (it moves messages back to the source queue) via the
131
+ existing `useQueueAction()` with `action: 'redrive'`, and a 403 surfaces inline as
132
+ "Not authorized".
133
+
134
+ - [`3778c8d`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3778c8d9fbbfeb96e0e32b5ebed63751a9debcd7) - Add Laravel-Telescope-style per-type navigation and entry filtering to the
135
+ dashboard UI. The chrome is now a left sidebar with the existing top-level nav
136
+ (Overview, Entries, Pulse, Queues) plus a grouped "Watchers" section linking to
137
+ each entry type (Requests, Queries, Exceptions, Jobs, Mail, Cache, Schedule,
138
+ HTTP Client), each with a per-type color dot and routing to the entries list
139
+ pre-filtered to that type via `#/entries/<type>`. The entries list gains a
140
+ compact filter bar: an active-type chip with a clear affordance, a tag text
141
+ filter wired into the backend-supported `tag` query param, a "Clear filters"
142
+ control, and type/tag-aware empty states. Entry detail moves to
143
+ `#/entries/view/:id` to disambiguate from the per-type list route.
144
+
145
+ - [`25fcff4`](https://github.com/DavideCarvalho/nestjs-telescope/commit/25fcff41ea9783d8a8085f5a2b7358464d3d74be) - Add a Horizon-style Overview landing page (now the index route `#/`) with a stat
146
+ cards row (entries, requests, exceptions, jobs, jobs/min, failed jobs), a
147
+ throughput area chart, a by-type stacked-area chart, and recent-failures /
148
+ slowest lists linking to entry detail. Introduce dark-themed Recharts chart
149
+ primitives (`AreaChartCard`, `StackedAreaChartCard`, `BarChartCard` + shared
150
+ `chart-theme`), exported from `/react`, with all Recharts imports isolated to
151
+ `src/react/components/charts/*`. Re-skin the Pulse throughput-by-type and the
152
+ Queues failure-rate visualizations onto the new charts.
153
+
154
+ - [`c0804b9`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c0804b96523deeb92762a03aa6b1ccee16598cef) - Add a global live-tail pause/resume toggle to the dashboard. A `Live`/`Paused`
155
+ control in the header freezes all polling so you can inspect a moment in time.
156
+ `TelescopeProvider` now exposes a live-tail flag via `useLiveTail()`/`usePaused()`,
157
+ and every polling query hook (entries, meta, pulse, stats, queues, timeseries,
158
+ traces, live queues, queue jobs) sets `refetchInterval: false` while paused and
159
+ resumes at the normal interval when live. Default is live (unchanged behavior).
160
+ Manual navigation/clicks still fetch — only the interval is gated.
161
+
162
+ - [`6fa0946`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6fa0946f5543868704864af2e32793eb448ac827) - Add the `model` and `redis` entry types (`EntryType.Model` / `EntryType.Redis`)
163
+ and their content shapes `ModelContent { action, entity, id, changes }` and
164
+ `RedisContent { command, args, durationMs }`, consumed by the new
165
+ `@dudousxd/nestjs-telescope-mikro-orm-watcher` and
166
+ `@dudousxd/nestjs-telescope-redis-watcher`. The UI gains Models/Redis nav types,
167
+ `ModelBody` / `RedisBody` detail renderers, and table summaries
168
+ (`<action> <entity>#<id>` for model, `<COMMAND> <args-preview>` for redis).
169
+
170
+ - [`d507547`](https://github.com/DavideCarvalho/nestjs-telescope/commit/d507547df3c13e76e90b8a97c4e3e1d8aef25bd1) - Add OpenTelemetry trace linking. The new `@dudousxd/nestjs-telescope-otel`
171
+ package implements core's `TraceContextProvider` SPI by reading the active
172
+ OpenTelemetry span via `@opentelemetry/api` (`OtelTraceContextProvider`,
173
+ optional peer, read-only — never creates or exports spans, degrades to `null`
174
+ when no span/API is present). Core gains the `TraceContextProvider` SPI,
175
+ `traceId`/`spanId` on each `Entry`, and a `meta.traceLink` template so the UI
176
+ can deep-link entries to a trace backend; the MikroORM storage provider adds the
177
+ corresponding trace columns so the ids survive persistence. The Redis storage
178
+ provider now reconstructs `traceId`/`spanId` when reading entries back, and the
179
+ dashboard shows a Trace row in the entry detail with a clickable deep-link when
180
+ `traceLink` is configured.
181
+
182
+ - [`cad6dae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/cad6dae0dba4f22e476d78c23ce2f74f7f6848e4) - Two pulse/dashboard UX improvements.
183
+
184
+ N+1 query hotspots are now aggregated by query family instead of listed per
185
+ request. `summarizePulse` returns `NPlusOneHotspot[]` — one row per family with
186
+ `perRequest` (worst single-request repetition), `requests` (how many requests
187
+ tripped the threshold), `total` (sum of repetitions), and `sampleBatchId` (the
188
+ worst batch). Identical queries that previously appeared as several identical
189
+ rows now collapse into a single hotspot, sorted by total repetitions. The Pulse
190
+ panel renders `×{perRequest} per request · {requests} requests · {total} total`
191
+ and links each hotspot to its query family (`#/entries/query?familyHash=…`).
192
+
193
+ The entries table now surfaces a subtle `trace:<id>` chip on any row that
194
+ belongs to a trace, linking straight to `#/traces/:traceId` so traces are
195
+ discoverable from the list without opening an entry's detail. Rows without a
196
+ trace show nothing extra.
197
+
198
+ - [`c8596c8`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c8596c85712880cb235e8cce059a1d93d339e9bd) - Add slow-request hotspots to the pulse panel — like the N+1 hotspot, but for
199
+ endpoints: which routes are consistently slow, aggregated by route with `p99`,
200
+ `p50`, and request `count`.
201
+
202
+ Request entries now carry a readable, normalized **route family** as their
203
+ `familyHash` (e.g. `"GET /api/base/:id/mel"`), via a new pure
204
+ `normalizeRoute(method, url)` helper that strips the query string and replaces
205
+ id-like path segments (UUID / all-digits / long hex) with `:id`. The route
206
+ family doubles as the human label and indexes on the existing `family_hash`
207
+ column, so the new `slowRoutes` aggregation runs entirely over content-less
208
+ columns in the existing two-pass pulse scan — no `content` reads, no hydration.
209
+
210
+ The pulse panel renders a "Slow request hotspots" section; each row links
211
+ through to that route's request entries (`familyHash` filter).
212
+
213
+ - [`abde392`](https://github.com/DavideCarvalho/nestjs-telescope/commit/abde39264effb31b0524cc4fa89a335276c8dccb) - Capture requests under a global prefix, and make query-shape insights actionable.
214
+
215
+ Core: the request-capture middleware no longer relies on NestJS module routing,
216
+ which silently scoped the catch-all to `/` whenever the host called
217
+ `setGlobalPrefix(...)`. New `telescopeRequestCapture(service)` factory lets hosts
218
+ register the capture globally (`app.use(...)` in bootstrap), and a
219
+ `registerRequestMiddleware: false` option disables the built-in registration.
220
+ Telescope's own dashboard routes are skipped inside the middleware instead of via
221
+ `.exclude()` (which breaks with catch-all routes). The query-family label now
222
+ tolerates content shapes that omit `slow`/`connection` (e.g. the MikroORM logger).
223
+
224
+ UI: the "Slowest query shapes" chart uses a horizontal, truncated layout (full
225
+ SQL on hover) so long labels no longer overlap, and each bar is clickable —
226
+ drilling into the entries list filtered to that query family.
227
+
228
+ - [`8ff32a2`](https://github.com/DavideCarvalho/nestjs-telescope/commit/8ff32a2cc95775224eea3377460d91674dfda47f) - Capture richer request detail. The request middleware now records the parsed
229
+ request body as `payload` and the authenticated user as the new
230
+ `RequestContent.user` field — read in the `res.on('finish')` callback (after the
231
+ host body-parser and guards have run). The user defaults to the raw request's
232
+ `user` (the Passport/guard convention) and is customizable via the new
233
+ `TelescopeModuleOptions.resolveUser(request)` hook. Both flow through the
234
+ Recorder's redaction (passwords/tokens masked). The request detail UI gains
235
+ collapsible Headers (with count), a pretty-printed Payload section, and a User
236
+ section ("anonymous" when none).
237
+
238
+ - [`b14a201`](https://github.com/DavideCarvalho/nestjs-telescope/commit/b14a20175eae3d3017e8cbc068d367a03f634175) - Add a live Schedule console for `@nestjs/schedule` scheduled tasks, mirroring the
239
+ Queues console.
240
+
241
+ Core gains a `ScheduleManager` SPI (`ScheduledTask` = name, kind, schedule,
242
+ nextRunAt, lastRunAt, lastDurationMs, lastStatus), a `ScheduleManagerRegistry`
243
+ that boots the configured managers, a `scheduleManagers?` option, and a
244
+ read-guarded `GET /telescope/api/schedules/live` returning `{ tasks }`.
245
+
246
+ The `@nestjs/schedule` watcher now also implements `ScheduleManager`: its
247
+ `listTasks()` reads `SchedulerRegistry` (via `moduleRef`) for the registered
248
+ cron/interval/timeout names, cron expression and next fire time, and merges its
249
+ own in-memory last-run map (additive to the existing entry-recording path). It
250
+ degrades gracefully — a missing/partial registry yields a null-filled or empty
251
+ list and never throws.
252
+
253
+ The UI adds a read-only `#/schedules` page (a table of name, kind, schedule,
254
+ next run, last run, duration, status) that polls like the queues console and
255
+ honors the live-tail pause, a `schedulesLive()` client method + `useSchedulesLive()`
256
+ hook, and a "Schedules" sidebar console link (distinct from the per-type
257
+ Schedule entries tab).
258
+
259
+ - [`a90ef56`](https://github.com/DavideCarvalho/nestjs-telescope/commit/a90ef569ba12484bece07d8de2045e13ff2ff528) - Two dashboard additions:
260
+
261
+ - **Slow outgoing HTTP hotspots.** The `http_client` watcher now records a
262
+ grouping `familyHash` of `${method} ${host}${normalizedPath}` (via a new
263
+ `normalizeHttpTarget` helper that keeps the host and `:id`-normalizes the
264
+ path), so outbound calls to the same external endpoint aggregate regardless of
265
+ ids. Pulse gains `slowOutgoing: SlowRouteHotspot[]` — top-N external targets by
266
+ p99 (count + p99/p50 over `durationMs`), computed entirely from content-less
267
+ columns (no hydration). The UI renders a "Slow outgoing HTTP" section in the
268
+ Pulse panel, each row deep-linking to `#/entries/http_client?familyHash=…`.
269
+
270
+ - **Server stats card.** A read-guarded `GET /telescope/api/server-stats`
271
+ endpoint returns a point-in-time Node process-health snapshot
272
+ (`uptimeSec`, `memory` RSS/heap MB, `cpu` user/system ms, `eventLoopDelayMs`,
273
+ `instanceId`). Event-loop delay is read from a `perf_hooks.monitorEventLoopDelay`
274
+ histogram started once at service init and degrades to `null` when unavailable.
275
+ The UI Overview page shows a polled "Server" card (honors pause) via the new
276
+ `serverStats()` client method and `useServerStats()` hook.
277
+
278
+ - [`f144584`](https://github.com/DavideCarvalho/nestjs-telescope/commit/f144584f7be7bda2a1189ddfd0b9fd461d2e1e86) - Turn the entries tag filter into an accessible autocomplete. Typing now queries
279
+ `GET /telescope/api/tags?prefix=` and surfaces matching tags with their entry
280
+ counts (top 10, sorted by count desc); picking one applies it as the active tag
281
+ filter and shows a clearable chip. Adds a `tags(prefix?)` client method and a
282
+ `useTags(prefix)` hook (non-polling reference data) plus the `TagCount` type.
283
+
284
+ - [`7b6636b`](https://github.com/DavideCarvalho/nestjs-telescope/commit/7b6636b54438427cd53ea0cbedd186b77d807169) - Add `@dudousxd/nestjs-telescope-ui`: a bundled React dashboard served by
285
+ `TelescopeUiModule` (no host frontend deps), with an Entries view (type tabs,
286
+ polling) and correlated entry detail. The same building blocks are exported at
287
+ `/react` (components + hooks) and `/client` (typed API client) so consumers can
288
+ compose their own dashboard. Core's request middleware now also excludes the
289
+ `/telescope` UI routes from capture.
290
+
291
+ - [`e64f35a`](https://github.com/DavideCarvalho/nestjs-telescope/commit/e64f35a1bb7cae15b2ef24404888463d04f81eef) - Add throughput time-series. `GET /telescope/api/timeseries?window=1h&buckets=60`
292
+ returns bucketed entry counts (total + per type) computed from stored entries
293
+ (`TimeseriesService` + `bucketTimeseries`), with optional `type`/`tag` filters.
294
+ The dashboard renders it as a zero-dependency SVG `Sparkline` on the Pulse page
295
+ (also exported at `/react`).
296
+
297
+ - [`4892ef6`](https://github.com/DavideCarvalho/nestjs-telescope/commit/4892ef61e45e5486d34c7ec82764e6767fe8233d) - Add a **Traces list** — browse recent distinct OTel traces instead of only
298
+ drilling in from a single entry. Core gains a pure `summarizeTraces` that groups
299
+ windowed entries by `traceId` (skipping null trace ids) into per-trace summaries
300
+ (`entryCount`, distinct sorted `types`, `firstAt`/`lastAt`, summed
301
+ `totalDurationMs`, optional `rootLabel`), a `TracesService` that scans the store
302
+ content-less over a window, and a `GET /telescope/api/traces?window=&limit=`
303
+ endpoint (read-guarded, mirrors `/timeseries`). The dashboard adds a **Traces**
304
+ nav item and a `#/traces` page listing recent traces — each row shows the root
305
+ label (or traceId), the type dots present, entry count, total duration, and
306
+ relative time, and links to the existing `#/traces/:traceId` grouping page.
307
+
308
+ - [`205eabe`](https://github.com/DavideCarvalho/nestjs-telescope/commit/205eabe2c3a34496f2dea608e89887407f4997df) - Add a Cmd+K / Ctrl+K command palette and a light/dark theme toggle to the
309
+ dashboard. The palette is a centered modal (backdrop/Escape close, focus-trapped
310
+ input, ArrowUp/Down wrapping highlight, Enter to navigate via the hash router)
311
+ whose action list is built from `ENTRY_TYPES` plus the static page targets so it
312
+ stays in sync. A header "⌘K" badge also opens it. The theme toggle (dark default)
313
+ persists to `localStorage`, restores on load, and applies a `light`/`dark` root
314
+ class; a scoped `.light` stylesheet flips the most common dark surfaces, text, and
315
+ borders for a usable light mode (full per-component theming is a follow-up).
316
+
317
+ - [`7797a2a`](https://github.com/DavideCarvalho/nestjs-telescope/commit/7797a2a1554aff49bb59f5ca1b204974a7e04a41) - Render the new `event` and `log` entry types: nav entries (Events/Logs), an event
318
+ detail (name + pretty payload + listener count) and a log detail (level badge +
319
+ message + context), plus table summaries (`event` name / `[level] message`).
320
+
321
+ - [`8dcded2`](https://github.com/DavideCarvalho/nestjs-telescope/commit/8dcded2a46582799e8176a83136a56cd1ac1f7fb) - Richer dashboard charts: per-type stacked-bar throughput (with a legend) on the
322
+ Pulse page, and a per-queue throughput sparkline column on the Queues page (via
323
+ a `tag`-filtered timeseries). New `StackedBars` / `QueueSparkline` components +
324
+ `stackedBarLayout`/`deriveTypes`/`colorForType` helpers exported at `/react`;
325
+ `QueuesPanel` gains an optional `sparkline` render-prop.
326
+
327
+ - [`d4c3986`](https://github.com/DavideCarvalho/nestjs-telescope/commit/d4c398631c2a77867b26a4b9bc86db073d9dd8c3) - Complete the dashboard with Pulse (per-type counts, slowest entries, top
328
+ exceptions, N+1 hotspots) and Queues (throughput, failure rate, runtime/wait
329
+ percentiles) views, each with a window selector. Typed the `pulse()`/`queues()`
330
+ client responses and exported `PulsePanel`/`QueuesPanel`/`WindowSelect` at
331
+ `/react`. Added an e2e that serves the real built bundle.
332
+
333
+ - [`e995e26`](https://github.com/DavideCarvalho/nestjs-telescope/commit/e995e266c87673f865164e76c7c930eb5170f6ac) - Show a request's batch as a compact timeline/waterfall in the entry detail. When
334
+ viewing a `request` entry that captured children (queries, cache, jobs, …), a new
335
+ `RequestTimeline` renders one row per batch entry ordered by `sequence`: a type
336
+ dot, a short label, a horizontal duration bar scaled to the slowest entry in the
337
+ batch, and the `durationMs`. Each row links to that child's detail. This makes
338
+ "where did the time go in this request" obvious without leaving for a trace
339
+ viewer. Null-duration children render with a minimal bar, and large batches cap
340
+ at 50 rows with a "+N more" note.
341
+
342
+ ### Patch Changes
343
+
344
+ - [`da0aa24`](https://github.com/DavideCarvalho/nestjs-telescope/commit/da0aa245b4efac6b95037339b0b81b50305bf9d9) - Serve the dashboard `index.html` with `Cache-Control: no-store` and the hashed
345
+ asset bundles with a long immutable cache. Previously index.html had no cache
346
+ headers, so browsers kept loading a stale bundle across upgrades — the classic
347
+ "a widget is stuck loading / shows old labels after deploying a new version".
348
+ - 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)]:
349
+ - @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,51 @@
1
+ # @dudousxd/nestjs-telescope-ui
2
+
3
+ A visual dashboard for [`@dudousxd/nestjs-telescope`](../../README.md) — a bundled
4
+ React SPA served by a NestJS module. **No frontend dependency is imposed on your
5
+ app:** React + Tailwind are bundled into static assets at publish time; your app
6
+ just serves them. Works on Express and Fastify.
7
+
8
+ ## Turnkey dashboard
9
+
10
+ ```ts
11
+ import { Module } from '@nestjs/common';
12
+ import { TelescopeModule } from '@dudousxd/nestjs-telescope';
13
+ import { TelescopeUiModule } from '@dudousxd/nestjs-telescope-ui';
14
+
15
+ @Module({
16
+ imports: [TelescopeModule.forRoot(), TelescopeUiModule.forRoot()],
17
+ })
18
+ export class AppModule {}
19
+ ```
20
+
21
+ Open `/telescope`. The dashboard polls `/telescope/api/*` and shows captured
22
+ entries (type tabs + live polling) and, on an entry, the correlated batch — the
23
+ request and everything it caused.
24
+
25
+ ## Build your own (composable)
26
+
27
+ The same pieces are exported so you can drop them into your own admin:
28
+
29
+ ```tsx
30
+ import { TelescopeProvider, useEntries, EntriesTable } from '@dudousxd/nestjs-telescope-ui/react';
31
+
32
+ function MyEntries() {
33
+ const { data } = useEntries({ type: 'query' });
34
+ return <EntriesTable entries={data?.data ?? []} />;
35
+ }
36
+ // wrap your tree once in <TelescopeProvider> (defaults to the /telescope/api base)
37
+ ```
38
+
39
+ Or just the typed client (no React):
40
+
41
+ ```ts
42
+ import { createTelescopeClient } from '@dudousxd/nestjs-telescope-ui/client';
43
+ const telescope = createTelescopeClient({ baseUrl: '/telescope/api' });
44
+ const page = await telescope.entries({ type: 'job' });
45
+ ```
46
+
47
+ ## Peer dependencies
48
+
49
+ `.` (the module) needs `@nestjs/common` / `@nestjs/core` (>= 11). `/react` needs
50
+ `react`, `react-dom`, `@tanstack/react-query` (only if you import it — they're
51
+ optional peers). The turnkey dashboard bundles its own copies.
@@ -0,0 +1,4 @@
1
+ export { createTelescopeClient } from './telescope-client.js';
2
+ export type { BulkActionName, JobActionName, TelescopeClient, TelescopeClientOptions, } from './telescope-client.js';
3
+ export type * from './types.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,YAAY,EACV,cAAc,EACd,aAAa,EACb,eAAe,EACf,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,mBAAmB,YAAY,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { createTelescopeClient } from './telescope-client.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,63 @@
1
+ import type { EntriesQuery, Entry, EntryWithBatch, JobPage, Page, PulseReport, QueueCapabilities, QueueCounts, QueueJobDetail, QueueMetricsReport, QueueState, QueueSummary, ScheduledTask, ServerStats, StatsResult, TagCount, TelescopeHealth, TelescopeMeta, TimeseriesQuery, TimeseriesReport, TracesResult } from './types.js';
2
+ declare global {
3
+ interface Window {
4
+ /**
5
+ * Mount base (e.g. `/observability`) injected by the UI controller's
6
+ * index.html when the dashboard is served under a custom path. Absent on the
7
+ * default mount and in SSR/tests, where the API base falls back to
8
+ * `/telescope/api`.
9
+ */
10
+ __TELESCOPE_BASE__?: string;
11
+ }
12
+ }
13
+ export interface TelescopeClientOptions {
14
+ /** Base URL of the telescope API. Default '/telescope/api' (or `${window.__TELESCOPE_BASE__}/api`). */
15
+ baseUrl?: string;
16
+ /** Fetch implementation (injectable for tests). Default global fetch. */
17
+ fetch?: typeof globalThis.fetch;
18
+ }
19
+ export type JobActionName = 'retry' | 'remove' | 'promote';
20
+ export type BulkActionName = 'retry-all' | 'redrive';
21
+ export interface TelescopeClient {
22
+ entries(query?: EntriesQuery): Promise<Page<Entry>>;
23
+ entry(id: string): Promise<EntryWithBatch>;
24
+ pulse(window?: string): Promise<PulseReport>;
25
+ queues(window?: string): Promise<QueueMetricsReport>;
26
+ timeseries(query?: TimeseriesQuery): Promise<TimeseriesReport>;
27
+ traces(window?: string, limit?: number): Promise<TracesResult>;
28
+ stats(type: string, window: string): Promise<StatsResult>;
29
+ tags(prefix?: string): Promise<TagCount[]>;
30
+ meta(): Promise<TelescopeMeta>;
31
+ serverStats(): Promise<ServerStats>;
32
+ health(): Promise<TelescopeHealth>;
33
+ liveQueues(): Promise<{
34
+ queues: QueueSummary[];
35
+ capabilities: QueueCapabilities;
36
+ }>;
37
+ schedulesLive(): Promise<{
38
+ tasks: ScheduledTask[];
39
+ }>;
40
+ queueCounts(driver: string, queue: string): Promise<QueueCounts>;
41
+ queueJobs(driver: string, queue: string, state: QueueState, page?: {
42
+ cursor?: string;
43
+ limit?: number;
44
+ }): Promise<JobPage>;
45
+ queueJob(driver: string, queue: string, id: string): Promise<QueueJobDetail | null>;
46
+ queueJobAction(driver: string, queue: string, id: string, action: JobActionName): Promise<{
47
+ ok: true;
48
+ }>;
49
+ queueAction(driver: string, queue: string, action: BulkActionName, opts?: {
50
+ state?: QueueState;
51
+ }): Promise<{
52
+ ok: true;
53
+ count?: number;
54
+ }>;
55
+ queueEnqueue(driver: string, queue: string, body: {
56
+ name?: string;
57
+ payload: unknown;
58
+ }): Promise<{
59
+ id: string | null;
60
+ }>;
61
+ }
62
+ export declare function createTelescopeClient(options?: TelescopeClientOptions): TelescopeClient;
63
+ //# sourceMappingURL=telescope-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telescope-client.d.ts","sourceRoot":"","sources":["../../src/client/telescope-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /** Derives the API base from the server-injected mount base, falling back to the default. */
2
+ function defaultBaseUrl() {
3
+ const base = (typeof window !== 'undefined' && window.__TELESCOPE_BASE__) || '/telescope';
4
+ return `${base}/api`;
5
+ }
6
+ export function createTelescopeClient(options = {}) {
7
+ const baseUrl = (options.baseUrl ?? defaultBaseUrl()).replace(/\/$/, '');
8
+ const doFetch = options.fetch ?? globalThis.fetch;
9
+ async function get(path, params) {
10
+ let url = `${baseUrl}${path}`;
11
+ if (params) {
12
+ const search = new URLSearchParams();
13
+ for (const [key, value] of Object.entries(params)) {
14
+ if (value !== undefined)
15
+ search.set(key, String(value));
16
+ }
17
+ const qs = search.toString();
18
+ if (qs)
19
+ url += `?${qs}`;
20
+ }
21
+ const response = await doFetch(url);
22
+ if (!response.ok)
23
+ throw new Error(`Telescope API ${path} failed: ${response.status}`);
24
+ return (await response.json());
25
+ }
26
+ async function post(path, params, body) {
27
+ let url = `${baseUrl}${path}`;
28
+ if (params) {
29
+ const search = new URLSearchParams();
30
+ for (const [key, value] of Object.entries(params)) {
31
+ if (value !== undefined)
32
+ search.set(key, String(value));
33
+ }
34
+ const qs = search.toString();
35
+ if (qs)
36
+ url += `?${qs}`;
37
+ }
38
+ const response = await doFetch(url, {
39
+ method: 'POST',
40
+ headers: { 'Content-Type': 'application/json' },
41
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
42
+ });
43
+ if (!response.ok)
44
+ throw new Error(`Telescope API ${path} failed: ${response.status}`);
45
+ return (await response.json());
46
+ }
47
+ return {
48
+ entries: (query = {}) => get('/entries', {
49
+ type: query.type,
50
+ tag: query.tag,
51
+ traceId: query.traceId,
52
+ batchId: query.batchId,
53
+ familyHash: query.familyHash,
54
+ search: query.search,
55
+ cursor: query.cursor,
56
+ limit: query.limit,
57
+ }),
58
+ entry: (id) => get(`/entries/${encodeURIComponent(id)}`),
59
+ pulse: (window) => get('/pulse', { window }),
60
+ queues: (window) => get('/queues', { window }),
61
+ timeseries: (query = {}) => get('/timeseries', {
62
+ window: query.window,
63
+ buckets: query.buckets,
64
+ type: query.type,
65
+ tag: query.tag,
66
+ }),
67
+ traces: (window, limit) => get('/traces', { window, limit }),
68
+ stats: (type, window) => get('/stats', { type, window }),
69
+ tags: (prefix) => get('/tags', { prefix }),
70
+ meta: () => get('/meta'),
71
+ serverStats: () => get('/server-stats'),
72
+ health: () => get('/health'),
73
+ liveQueues: () => get('/queues/live'),
74
+ schedulesLive: () => get('/schedules/live'),
75
+ queueCounts: (driver, queue) => get(`/queues/live/${encodeURIComponent(driver)}/${encodeURIComponent(queue)}/counts`),
76
+ queueJobs: (driver, queue, state, page = {}) => get(`/queues/live/${encodeURIComponent(driver)}/${encodeURIComponent(queue)}/jobs`, {
77
+ state,
78
+ cursor: page.cursor,
79
+ limit: page.limit,
80
+ }),
81
+ queueJob: (driver, queue, id) => get(`/queues/live/${encodeURIComponent(driver)}/${encodeURIComponent(queue)}/jobs/${encodeURIComponent(id)}`),
82
+ queueJobAction: (driver, queue, id, action) => post(`/queues/live/${encodeURIComponent(driver)}/${encodeURIComponent(queue)}/jobs/${encodeURIComponent(id)}/${encodeURIComponent(action)}`),
83
+ queueAction: (driver, queue, action, opts = {}) => post(`/queues/live/${encodeURIComponent(driver)}/${encodeURIComponent(queue)}/actions/${encodeURIComponent(action)}`, { state: opts.state }),
84
+ queueEnqueue: (driver, queue, body) => post(`/queues/live/${encodeURIComponent(driver)}/${encodeURIComponent(queue)}/enqueue`, undefined, body),
85
+ };
86
+ }
87
+ //# sourceMappingURL=telescope-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telescope-client.js","sourceRoot":"","sources":["../../src/client/telescope-client.ts"],"names":[],"mappings":"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"}