@adonis-agora/telescope 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/dist/configure.d.ts +16 -0
  4. package/dist/configure.d.ts.map +1 -0
  5. package/dist/configure.js +75 -0
  6. package/dist/configure.js.map +1 -0
  7. package/dist/providers/telescope_ai_provider.d.ts +20 -0
  8. package/dist/providers/telescope_ai_provider.d.ts.map +1 -0
  9. package/dist/providers/telescope_ai_provider.js +45 -0
  10. package/dist/providers/telescope_ai_provider.js.map +1 -0
  11. package/dist/providers/telescope_alerts_provider.d.ts +23 -0
  12. package/dist/providers/telescope_alerts_provider.d.ts.map +1 -0
  13. package/dist/providers/telescope_alerts_provider.js +72 -0
  14. package/dist/providers/telescope_alerts_provider.js.map +1 -0
  15. package/dist/providers/telescope_provider.d.ts +43 -0
  16. package/dist/providers/telescope_provider.d.ts.map +1 -0
  17. package/dist/providers/telescope_provider.js +103 -0
  18. package/dist/providers/telescope_provider.js.map +1 -0
  19. package/dist/providers/telescope_ui_provider.d.ts +21 -0
  20. package/dist/providers/telescope_ui_provider.d.ts.map +1 -0
  21. package/dist/providers/telescope_ui_provider.js +119 -0
  22. package/dist/providers/telescope_ui_provider.js.map +1 -0
  23. package/dist/providers/telescope_watchers_provider.d.ts +31 -0
  24. package/dist/providers/telescope_watchers_provider.d.ts.map +1 -0
  25. package/dist/providers/telescope_watchers_provider.js +116 -0
  26. package/dist/providers/telescope_watchers_provider.js.map +1 -0
  27. package/dist/src/ai/define_config.d.ts +56 -0
  28. package/dist/src/ai/define_config.d.ts.map +1 -0
  29. package/dist/src/ai/define_config.js +39 -0
  30. package/dist/src/ai/define_config.js.map +1 -0
  31. package/dist/src/ai/diagnoser.d.ts +34 -0
  32. package/dist/src/ai/diagnoser.d.ts.map +1 -0
  33. package/dist/src/ai/diagnoser.js +74 -0
  34. package/dist/src/ai/diagnoser.js.map +1 -0
  35. package/dist/src/ai/diagnosis_cache.d.ts +43 -0
  36. package/dist/src/ai/diagnosis_cache.d.ts.map +1 -0
  37. package/dist/src/ai/diagnosis_cache.js +56 -0
  38. package/dist/src/ai/diagnosis_cache.js.map +1 -0
  39. package/dist/src/ai/factory.d.ts +15 -0
  40. package/dist/src/ai/factory.d.ts.map +1 -0
  41. package/dist/src/ai/factory.js +24 -0
  42. package/dist/src/ai/factory.js.map +1 -0
  43. package/dist/src/ai/index.d.ts +14 -0
  44. package/dist/src/ai/index.d.ts.map +1 -0
  45. package/dist/src/ai/index.js +15 -0
  46. package/dist/src/ai/index.js.map +1 -0
  47. package/dist/src/ai/prompt.d.ts +31 -0
  48. package/dist/src/ai/prompt.d.ts.map +1 -0
  49. package/dist/src/ai/prompt.js +66 -0
  50. package/dist/src/ai/prompt.js.map +1 -0
  51. package/dist/src/ai/telescope_ai_diagnoser.d.ts +79 -0
  52. package/dist/src/ai/telescope_ai_diagnoser.d.ts.map +1 -0
  53. package/dist/src/ai/telescope_ai_diagnoser.js +111 -0
  54. package/dist/src/ai/telescope_ai_diagnoser.js.map +1 -0
  55. package/dist/src/alerts/alert_channel.d.ts +69 -0
  56. package/dist/src/alerts/alert_channel.d.ts.map +1 -0
  57. package/dist/src/alerts/alert_channel.js +114 -0
  58. package/dist/src/alerts/alert_channel.js.map +1 -0
  59. package/dist/src/alerts/alert_rule.d.ts +86 -0
  60. package/dist/src/alerts/alert_rule.d.ts.map +1 -0
  61. package/dist/src/alerts/alert_rule.js +2 -0
  62. package/dist/src/alerts/alert_rule.js.map +1 -0
  63. package/dist/src/alerts/alerter.d.ts +72 -0
  64. package/dist/src/alerts/alerter.d.ts.map +1 -0
  65. package/dist/src/alerts/alerter.js +248 -0
  66. package/dist/src/alerts/alerter.js.map +1 -0
  67. package/dist/src/alerts/define_config.d.ts +68 -0
  68. package/dist/src/alerts/define_config.d.ts.map +1 -0
  69. package/dist/src/alerts/define_config.js +57 -0
  70. package/dist/src/alerts/define_config.js.map +1 -0
  71. package/dist/src/alerts/exception_source.d.ts +44 -0
  72. package/dist/src/alerts/exception_source.d.ts.map +1 -0
  73. package/dist/src/alerts/exception_source.js +79 -0
  74. package/dist/src/alerts/exception_source.js.map +1 -0
  75. package/dist/src/alerts/index.d.ts +16 -0
  76. package/dist/src/alerts/index.d.ts.map +1 -0
  77. package/dist/src/alerts/index.js +17 -0
  78. package/dist/src/alerts/index.js.map +1 -0
  79. package/dist/src/alerts/new_exception_tracker.d.ts +50 -0
  80. package/dist/src/alerts/new_exception_tracker.d.ts.map +1 -0
  81. package/dist/src/alerts/new_exception_tracker.js +74 -0
  82. package/dist/src/alerts/new_exception_tracker.js.map +1 -0
  83. package/dist/src/alerts/parse_duration.d.ts +10 -0
  84. package/dist/src/alerts/parse_duration.d.ts.map +1 -0
  85. package/dist/src/alerts/parse_duration.js +27 -0
  86. package/dist/src/alerts/parse_duration.js.map +1 -0
  87. package/dist/src/alerts/slack_format.d.ts +60 -0
  88. package/dist/src/alerts/slack_format.d.ts.map +1 -0
  89. package/dist/src/alerts/slack_format.js +122 -0
  90. package/dist/src/alerts/slack_format.js.map +1 -0
  91. package/dist/src/context_accessor.d.ts +30 -0
  92. package/dist/src/context_accessor.d.ts.map +1 -0
  93. package/dist/src/context_accessor.js +20 -0
  94. package/dist/src/context_accessor.js.map +1 -0
  95. package/dist/src/define_config.d.ts +109 -0
  96. package/dist/src/define_config.d.ts.map +1 -0
  97. package/dist/src/define_config.js +38 -0
  98. package/dist/src/define_config.js.map +1 -0
  99. package/dist/src/diagnostics_registry.d.ts +46 -0
  100. package/dist/src/diagnostics_registry.d.ts.map +1 -0
  101. package/dist/src/diagnostics_registry.js +34 -0
  102. package/dist/src/diagnostics_registry.js.map +1 -0
  103. package/dist/src/diagnostics_watcher.d.ts +72 -0
  104. package/dist/src/diagnostics_watcher.d.ts.map +1 -0
  105. package/dist/src/diagnostics_watcher.js +119 -0
  106. package/dist/src/diagnostics_watcher.js.map +1 -0
  107. package/dist/src/entry.d.ts +81 -0
  108. package/dist/src/entry.d.ts.map +1 -0
  109. package/dist/src/entry.js +34 -0
  110. package/dist/src/entry.js.map +1 -0
  111. package/dist/src/exception_family_hash.d.ts +29 -0
  112. package/dist/src/exception_family_hash.d.ts.map +1 -0
  113. package/dist/src/exception_family_hash.js +30 -0
  114. package/dist/src/exception_family_hash.js.map +1 -0
  115. package/dist/src/exception_watcher.d.ts +66 -0
  116. package/dist/src/exception_watcher.d.ts.map +1 -0
  117. package/dist/src/exception_watcher.js +94 -0
  118. package/dist/src/exception_watcher.js.map +1 -0
  119. package/dist/src/extension/registry.d.ts +17 -0
  120. package/dist/src/extension/registry.d.ts.map +1 -0
  121. package/dist/src/extension/registry.js +56 -0
  122. package/dist/src/extension/registry.js.map +1 -0
  123. package/dist/src/extension/types.d.ts +158 -0
  124. package/dist/src/extension/types.d.ts.map +1 -0
  125. package/dist/src/extension/types.js +5 -0
  126. package/dist/src/extension/types.js.map +1 -0
  127. package/dist/src/index.d.ts +36 -0
  128. package/dist/src/index.d.ts.map +1 -0
  129. package/dist/src/index.js +28 -0
  130. package/dist/src/index.js.map +1 -0
  131. package/dist/src/redaction/redact.d.ts +93 -0
  132. package/dist/src/redaction/redact.d.ts.map +1 -0
  133. package/dist/src/redaction/redact.js +184 -0
  134. package/dist/src/redaction/redact.js.map +1 -0
  135. package/dist/src/redaction/redacting_store.d.ts +28 -0
  136. package/dist/src/redaction/redacting_store.d.ts.map +1 -0
  137. package/dist/src/redaction/redacting_store.js +49 -0
  138. package/dist/src/redaction/redacting_store.js.map +1 -0
  139. package/dist/src/registry.d.ts +26 -0
  140. package/dist/src/registry.d.ts.map +1 -0
  141. package/dist/src/registry.js +28 -0
  142. package/dist/src/registry.js.map +1 -0
  143. package/dist/src/request_watcher.d.ts +44 -0
  144. package/dist/src/request_watcher.d.ts.map +1 -0
  145. package/dist/src/request_watcher.js +37 -0
  146. package/dist/src/request_watcher.js.map +1 -0
  147. package/dist/src/service.d.ts +36 -0
  148. package/dist/src/service.d.ts.map +1 -0
  149. package/dist/src/service.js +65 -0
  150. package/dist/src/service.js.map +1 -0
  151. package/dist/src/store.d.ts +56 -0
  152. package/dist/src/store.d.ts.map +1 -0
  153. package/dist/src/store.js +2 -0
  154. package/dist/src/store.js.map +1 -0
  155. package/dist/src/stores/factory.d.ts +61 -0
  156. package/dist/src/stores/factory.d.ts.map +1 -0
  157. package/dist/src/stores/factory.js +42 -0
  158. package/dist/src/stores/factory.js.map +1 -0
  159. package/dist/src/stores/lucid.d.ts +138 -0
  160. package/dist/src/stores/lucid.d.ts.map +1 -0
  161. package/dist/src/stores/lucid.js +257 -0
  162. package/dist/src/stores/lucid.js.map +1 -0
  163. package/dist/src/stores/memory.d.ts +31 -0
  164. package/dist/src/stores/memory.d.ts.map +1 -0
  165. package/dist/src/stores/memory.js +117 -0
  166. package/dist/src/stores/memory.js.map +1 -0
  167. package/dist/src/telescope_middleware.d.ts +19 -0
  168. package/dist/src/telescope_middleware.d.ts.map +1 -0
  169. package/dist/src/telescope_middleware.js +56 -0
  170. package/dist/src/telescope_middleware.js.map +1 -0
  171. package/dist/src/ui/api.d.ts +49 -0
  172. package/dist/src/ui/api.d.ts.map +1 -0
  173. package/dist/src/ui/api.js +155 -0
  174. package/dist/src/ui/api.js.map +1 -0
  175. package/dist/src/ui/dashboard.d.ts +8 -0
  176. package/dist/src/ui/dashboard.d.ts.map +1 -0
  177. package/dist/src/ui/dashboard.html +626 -0
  178. package/dist/src/ui/dashboard.js +29 -0
  179. package/dist/src/ui/dashboard.js.map +1 -0
  180. package/dist/src/ui/define_config.d.ts +87 -0
  181. package/dist/src/ui/define_config.d.ts.map +1 -0
  182. package/dist/src/ui/define_config.js +104 -0
  183. package/dist/src/ui/define_config.js.map +1 -0
  184. package/dist/src/ui/extension_api.d.ts +23 -0
  185. package/dist/src/ui/extension_api.d.ts.map +1 -0
  186. package/dist/src/ui/extension_api.js +50 -0
  187. package/dist/src/ui/extension_api.js.map +1 -0
  188. package/dist/src/ui/guard.d.ts +33 -0
  189. package/dist/src/ui/guard.d.ts.map +1 -0
  190. package/dist/src/ui/guard.js +47 -0
  191. package/dist/src/ui/guard.js.map +1 -0
  192. package/dist/src/ui/http.d.ts +47 -0
  193. package/dist/src/ui/http.d.ts.map +1 -0
  194. package/dist/src/ui/http.js +43 -0
  195. package/dist/src/ui/http.js.map +1 -0
  196. package/dist/src/ui/index.d.ts +12 -0
  197. package/dist/src/ui/index.d.ts.map +1 -0
  198. package/dist/src/ui/index.js +13 -0
  199. package/dist/src/ui/index.js.map +1 -0
  200. package/dist/src/watchers/cache_watcher.d.ts +60 -0
  201. package/dist/src/watchers/cache_watcher.d.ts.map +1 -0
  202. package/dist/src/watchers/cache_watcher.js +72 -0
  203. package/dist/src/watchers/cache_watcher.js.map +1 -0
  204. package/dist/src/watchers/define_config.d.ts +38 -0
  205. package/dist/src/watchers/define_config.d.ts.map +1 -0
  206. package/dist/src/watchers/define_config.js +17 -0
  207. package/dist/src/watchers/define_config.js.map +1 -0
  208. package/dist/src/watchers/emitter.d.ts +32 -0
  209. package/dist/src/watchers/emitter.d.ts.map +1 -0
  210. package/dist/src/watchers/emitter.js +2 -0
  211. package/dist/src/watchers/emitter.js.map +1 -0
  212. package/dist/src/watchers/http_client_watcher.d.ts +74 -0
  213. package/dist/src/watchers/http_client_watcher.d.ts.map +1 -0
  214. package/dist/src/watchers/http_client_watcher.js +168 -0
  215. package/dist/src/watchers/http_client_watcher.js.map +1 -0
  216. package/dist/src/watchers/index.d.ts +19 -0
  217. package/dist/src/watchers/index.d.ts.map +1 -0
  218. package/dist/src/watchers/index.js +19 -0
  219. package/dist/src/watchers/index.js.map +1 -0
  220. package/dist/src/watchers/logs_watcher.d.ts +82 -0
  221. package/dist/src/watchers/logs_watcher.d.ts.map +1 -0
  222. package/dist/src/watchers/logs_watcher.js +145 -0
  223. package/dist/src/watchers/logs_watcher.js.map +1 -0
  224. package/dist/src/watchers/lucid_query_watcher.d.ts +64 -0
  225. package/dist/src/watchers/lucid_query_watcher.d.ts.map +1 -0
  226. package/dist/src/watchers/lucid_query_watcher.js +84 -0
  227. package/dist/src/watchers/lucid_query_watcher.js.map +1 -0
  228. package/dist/src/watchers/mail_watcher.d.ts +51 -0
  229. package/dist/src/watchers/mail_watcher.d.ts.map +1 -0
  230. package/dist/src/watchers/mail_watcher.js +93 -0
  231. package/dist/src/watchers/mail_watcher.js.map +1 -0
  232. package/dist/src/watchers/normalize_http_target.d.ts +17 -0
  233. package/dist/src/watchers/normalize_http_target.d.ts.map +1 -0
  234. package/dist/src/watchers/normalize_http_target.js +41 -0
  235. package/dist/src/watchers/normalize_http_target.js.map +1 -0
  236. package/dist/src/watchers/query_family_hash.d.ts +8 -0
  237. package/dist/src/watchers/query_family_hash.d.ts.map +1 -0
  238. package/dist/src/watchers/query_family_hash.js +31 -0
  239. package/dist/src/watchers/query_family_hash.js.map +1 -0
  240. package/dist/src/watchers/record.d.ts +22 -0
  241. package/dist/src/watchers/record.d.ts.map +1 -0
  242. package/dist/src/watchers/record.js +48 -0
  243. package/dist/src/watchers/record.js.map +1 -0
  244. package/dist/stubs/config/telescope.stub +56 -0
  245. package/dist/stubs/config/telescope_ai.stub +36 -0
  246. package/dist/stubs/config/telescope_alerts.stub +47 -0
  247. package/dist/stubs/config/telescope_ui.stub +40 -0
  248. package/dist/stubs/config/telescope_watchers.stub +30 -0
  249. package/dist/stubs/database/migrations/create_telescope_entries_table.stub +39 -0
  250. package/dist/stubs/main.d.ts +6 -0
  251. package/dist/stubs/main.d.ts.map +1 -0
  252. package/dist/stubs/main.js +7 -0
  253. package/dist/stubs/main.js.map +1 -0
  254. package/package.json +140 -0
@@ -0,0 +1,72 @@
1
+ import { type DiagnosticEvent } from './diagnostics_registry.js';
2
+ import { type RecordInput } from './entry.js';
3
+ import type { TelescopeStore } from './store.js';
4
+ /** Telescope entry `type` produced by this watcher. */
5
+ export declare const DIAGNOSTIC_ENTRY_TYPE: "diagnostic";
6
+ /**
7
+ * What a single recorded diagnostic entry looks like. Mirrors the
8
+ * {@link DiagnosticEvent} envelope, with the library-defined data preserved
9
+ * verbatim under `payload`.
10
+ */
11
+ export interface DiagnosticEntryContent {
12
+ /** Envelope schema version, or `null` on a legacy (pre-versioning) envelope. */
13
+ v: number | null;
14
+ /** The emitting library, e.g. `'billing'`. */
15
+ lib: string;
16
+ /** The event within that library, e.g. `'invoice-paid'`. */
17
+ event: string;
18
+ /** Epoch millis the producer stamped the event with. */
19
+ ts: number;
20
+ /** The trace id the producer resolved, or `null` when none. */
21
+ traceId: string | null;
22
+ /** The library-defined payload, recorded as-is. */
23
+ payload: unknown;
24
+ }
25
+ /**
26
+ * The ONE generic watcher behind `@adonis-agora/telescope`'s diagnostics integration. It
27
+ * records every event any `@adonis-agora/*` library emits through `@adonis-agora/diagnostics` —
28
+ * one `diagnostic` entry per `agora:<lib>:<event>` publish — without a bespoke
29
+ * watcher per library. This is the Agora equivalent of NestJS's
30
+ * `@dudousxd/nestjs-diagnostics-telescope` extension.
31
+ *
32
+ * ## Cross-repo decoupling
33
+ * Telescope CANNOT import `@adonis-agora/diagnostics`. Instead it reads the registry
34
+ * `@adonis-agora/diagnostics` publishes on `Symbol.for('@agora/diagnostics:registry')`
35
+ * (`{ channels, listeners }`) and subscribes to each channel via the Node builtin
36
+ * `node:diagnostics_channel` — no Agora import needed.
37
+ *
38
+ * ## How it auto-subscribes to current + future channels
39
+ * `node:diagnostics_channel` has no wildcard, so on {@link start} the watcher:
40
+ * 1. subscribes to every channel already in `registry.channels`, and
41
+ * 2. adds a listener to `registry.listeners` so any channel that appears later
42
+ * (a library's first emit) is subscribed too.
43
+ *
44
+ * Subscribing also flips each producer's `channel.hasSubscribers` to `true`, which
45
+ * is what makes the producer build + publish envelopes at all (zero-overhead when
46
+ * nobody listens).
47
+ */
48
+ export declare class DiagnosticsWatcher {
49
+ private readonly store;
50
+ readonly type: "diagnostic";
51
+ private started;
52
+ /** The listener we added to `registry.listeners`, for exact removal on stop. */
53
+ private registryListener;
54
+ /** name → the subscribe handler we attached, so cleanup can detach exactly. */
55
+ private readonly subscriptions;
56
+ constructor(store: TelescopeStore);
57
+ /**
58
+ * Begin recording. Subscribes to every currently-registered channel and arms a
59
+ * listener for future ones. A no-op when `@adonis-agora/diagnostics` is not loaded
60
+ * (the registry slot is absent) — telescope degrades gracefully.
61
+ */
62
+ start(): void;
63
+ /** Unsubscribe from every channel and stop watching for new ones. */
64
+ stop(): void;
65
+ /** Subscribe once to `name`, recording each publish as a `diagnostic` entry. */
66
+ private subscribe;
67
+ /** Validate + record, swallowing any failure so a producer can never break. */
68
+ private safeRecord;
69
+ }
70
+ /** Map a {@link DiagnosticEvent} envelope to a Telescope {@link RecordInput}. */
71
+ export declare function buildDiagnosticEntry(msg: DiagnosticEvent): RecordInput<DiagnosticEntryContent>;
72
+ //# sourceMappingURL=diagnostics_watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics_watcher.d.ts","sourceRoot":"","sources":["../../src/diagnostics_watcher.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,eAAe,EAGrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,uDAAuD;AACvD,eAAO,MAAM,qBAAqB,cAAuB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,mDAAmD;IACnD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,kBAAkB;IAQjB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAPlC,QAAQ,CAAC,IAAI,eAAyB;IACtC,OAAO,CAAC,OAAO,CAAS;IACxB,gFAAgF;IAChF,OAAO,CAAC,gBAAgB,CAAyC;IACjE,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6C;gBAE9C,KAAK,EAAE,cAAc;IAElD;;;;OAIG;IACH,KAAK,IAAI,IAAI;IAab,qEAAqE;IACrE,IAAI,IAAI,IAAI;IAYZ,gFAAgF;IAChF,OAAO,CAAC,SAAS;IAQjB,+EAA+E;IAC/E,OAAO,CAAC,UAAU;CAcnB;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,GAAG,WAAW,CAAC,sBAAsB,CAAC,CAoB9F"}
@@ -0,0 +1,119 @@
1
+ import diagnostics_channel from 'node:diagnostics_channel';
2
+ import { getDiagnosticsRegistry, isDiagnosticEvent, } from './diagnostics_registry.js';
3
+ import { EntryType } from './entry.js';
4
+ /** Telescope entry `type` produced by this watcher. */
5
+ export const DIAGNOSTIC_ENTRY_TYPE = EntryType.Diagnostic;
6
+ /**
7
+ * The ONE generic watcher behind `@adonis-agora/telescope`'s diagnostics integration. It
8
+ * records every event any `@adonis-agora/*` library emits through `@adonis-agora/diagnostics` —
9
+ * one `diagnostic` entry per `agora:<lib>:<event>` publish — without a bespoke
10
+ * watcher per library. This is the Agora equivalent of NestJS's
11
+ * `@dudousxd/nestjs-diagnostics-telescope` extension.
12
+ *
13
+ * ## Cross-repo decoupling
14
+ * Telescope CANNOT import `@adonis-agora/diagnostics`. Instead it reads the registry
15
+ * `@adonis-agora/diagnostics` publishes on `Symbol.for('@agora/diagnostics:registry')`
16
+ * (`{ channels, listeners }`) and subscribes to each channel via the Node builtin
17
+ * `node:diagnostics_channel` — no Agora import needed.
18
+ *
19
+ * ## How it auto-subscribes to current + future channels
20
+ * `node:diagnostics_channel` has no wildcard, so on {@link start} the watcher:
21
+ * 1. subscribes to every channel already in `registry.channels`, and
22
+ * 2. adds a listener to `registry.listeners` so any channel that appears later
23
+ * (a library's first emit) is subscribed too.
24
+ *
25
+ * Subscribing also flips each producer's `channel.hasSubscribers` to `true`, which
26
+ * is what makes the producer build + publish envelopes at all (zero-overhead when
27
+ * nobody listens).
28
+ */
29
+ export class DiagnosticsWatcher {
30
+ store;
31
+ type = DIAGNOSTIC_ENTRY_TYPE;
32
+ started = false;
33
+ /** The listener we added to `registry.listeners`, for exact removal on stop. */
34
+ registryListener = null;
35
+ /** name → the subscribe handler we attached, so cleanup can detach exactly. */
36
+ subscriptions = new Map();
37
+ constructor(store) {
38
+ this.store = store;
39
+ }
40
+ /**
41
+ * Begin recording. Subscribes to every currently-registered channel and arms a
42
+ * listener for future ones. A no-op when `@adonis-agora/diagnostics` is not loaded
43
+ * (the registry slot is absent) — telescope degrades gracefully.
44
+ */
45
+ start() {
46
+ if (this.started)
47
+ return;
48
+ this.started = true;
49
+ const registry = getDiagnosticsRegistry();
50
+ if (!registry)
51
+ return;
52
+ for (const name of registry.channels)
53
+ this.subscribe(name);
54
+ const listener = (name) => this.subscribe(name);
55
+ this.registryListener = listener;
56
+ registry.listeners.add(listener);
57
+ }
58
+ /** Unsubscribe from every channel and stop watching for new ones. */
59
+ stop() {
60
+ if (this.registryListener) {
61
+ getDiagnosticsRegistry()?.listeners.delete(this.registryListener);
62
+ this.registryListener = null;
63
+ }
64
+ for (const [name, handler] of this.subscriptions) {
65
+ diagnostics_channel.channel(name).unsubscribe(handler);
66
+ }
67
+ this.subscriptions.clear();
68
+ this.started = false;
69
+ }
70
+ /** Subscribe once to `name`, recording each publish as a `diagnostic` entry. */
71
+ subscribe(name) {
72
+ if (this.subscriptions.has(name))
73
+ return;
74
+ const handler = (msg) => this.safeRecord(msg);
75
+ this.subscriptions.set(name, handler);
76
+ const channel = diagnostics_channel.channel(name);
77
+ channel.subscribe(handler);
78
+ }
79
+ /** Validate + record, swallowing any failure so a producer can never break. */
80
+ safeRecord(msg) {
81
+ try {
82
+ if (!isDiagnosticEvent(msg))
83
+ return;
84
+ // This runs inside a synchronous `node:diagnostics_channel` subscriber, so we
85
+ // CANNOT await the now-async store. Fire-and-forget and swallow rejections —
86
+ // telescope must never break (or block) an emitting code path.
87
+ void this.store.record(buildDiagnosticEntry(msg)).catch((err) => {
88
+ console.error('DiagnosticsWatcher: failed to record diagnostic event:', err);
89
+ });
90
+ }
91
+ catch (err) {
92
+ // NOT rethrown — telescope must never break an emitting code path.
93
+ console.error('DiagnosticsWatcher: failed to record diagnostic event:', err);
94
+ }
95
+ }
96
+ }
97
+ /** Map a {@link DiagnosticEvent} envelope to a Telescope {@link RecordInput}. */
98
+ export function buildDiagnosticEntry(msg) {
99
+ const traceId = msg.traceId ?? null;
100
+ const content = {
101
+ // Tolerate envelopes from emitters that predate schema versioning.
102
+ v: msg.v ?? null,
103
+ lib: msg.lib,
104
+ event: msg.event,
105
+ ts: msg.ts,
106
+ traceId,
107
+ payload: msg.payload,
108
+ };
109
+ return {
110
+ type: DIAGNOSTIC_ENTRY_TYPE,
111
+ // Group by lib + event so a dashboard can roll up "billing:invoice-paid".
112
+ familyHash: `${msg.lib}:${msg.event}`,
113
+ tags: [`lib:${msg.lib}`, `event:${msg.event}`, ...(traceId ? [`trace:${traceId}`] : [])],
114
+ content,
115
+ // Carry the producer-resolved trace id (it knows the emitting context best).
116
+ traceId,
117
+ };
118
+ }
119
+ //# sourceMappingURL=diagnostics_watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics_watcher.js","sourceRoot":"","sources":["../../src/diagnostics_watcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The built-in Telescope entry types shipped by `@adonis-agora/telescope`. Adapted from
3
+ * the NestJS `nestjs-telescope` core, trimmed to the headless slice this package
4
+ * actually records (request + diagnostic). The remaining values are reserved so a
5
+ * future watcher (a Lucid query watcher, a mailer watcher, …) can record under a
6
+ * stable, already-documented type without a breaking change.
7
+ */
8
+ export declare const EntryType: {
9
+ /** An inbound HTTP request, recorded by {@link TelescopeMiddleware}. */
10
+ readonly Request: "request";
11
+ /** A single `agora:<lib>:<event>` diagnostics publish. */
12
+ readonly Diagnostic: "diagnostic";
13
+ readonly Query: "query";
14
+ readonly Job: "job";
15
+ readonly Exception: "exception";
16
+ readonly Mail: "mail";
17
+ readonly Cache: "cache";
18
+ readonly Redis: "redis";
19
+ readonly Event: "event";
20
+ readonly Log: "log";
21
+ /** An OUTBOUND HTTP call, recorded by the http-client watcher. */
22
+ readonly HttpClient: "http-client";
23
+ };
24
+ export type BuiltinEntryType = (typeof EntryType)[keyof typeof EntryType];
25
+ /**
26
+ * Where a batch of entries originated. An entry recorded inside an HTTP request
27
+ * is `http`; one recorded by a queue worker is `queue`; a diagnostic recorded
28
+ * with no active request defaults to `manual`.
29
+ */
30
+ declare const BATCH_ORIGINS: readonly ["http", "queue", "schedule", "cli", "manual"];
31
+ export type BatchOrigin = (typeof BATCH_ORIGINS)[number];
32
+ export declare function isBatchOrigin(value: unknown): value is BatchOrigin;
33
+ /**
34
+ * A captured, persisted observability record. `content` is type-specific (e.g.
35
+ * {@link RequestEntryContent} for a `request`, {@link DiagnosticEntryContent} for
36
+ * a `diagnostic`).
37
+ */
38
+ export interface Entry<TContent = unknown> {
39
+ /** Unique id of this entry. */
40
+ id: string;
41
+ /** The entry type, one of {@link EntryType} (or a custom string). */
42
+ type: string;
43
+ /**
44
+ * Stable grouping key — entries that share a `familyHash` are "the same kind of
45
+ * thing" (e.g. all `billing:invoice-paid` diagnostics). `null` when the entry
46
+ * is not groupable.
47
+ */
48
+ familyHash: string | null;
49
+ /** The type-specific payload. */
50
+ content: TContent;
51
+ /** Searchable labels, e.g. `lib:billing`, `event:invoice-paid`, `status:500`. */
52
+ tags: string[];
53
+ /** Monotonic record order within this process. */
54
+ sequence: number;
55
+ /** Wall-clock duration of the recorded operation, when known. */
56
+ durationMs: number | null;
57
+ /** Where the recording happened. */
58
+ origin: BatchOrigin;
59
+ /** The active trace id at record time, or `null` when no context. */
60
+ traceId: string | null;
61
+ /** When the entry was recorded. */
62
+ createdAt: Date;
63
+ }
64
+ /**
65
+ * What a watcher hands to {@link TelescopeStore.record}. The store fills in `id`,
66
+ * `sequence`, `createdAt`, and resolves `traceId`/`origin` from the ambient
67
+ * context when the caller omits them.
68
+ */
69
+ export interface RecordInput<TContent = unknown> {
70
+ type: string;
71
+ content: TContent;
72
+ familyHash?: string | null;
73
+ tags?: string[];
74
+ durationMs?: number | null;
75
+ /** Override the resolved trace id; defaults to the ambient `@adonis-agora/context`. */
76
+ traceId?: string | null;
77
+ /** Override the batch origin; defaults to `http` when a request is active. */
78
+ origin?: BatchOrigin;
79
+ }
80
+ export {};
81
+ //# sourceMappingURL=entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/entry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,SAAS;IACpB,wEAAwE;;IAExE,0DAA0D;;;;;;;;;;IAW1D,kEAAkE;;CAE1D,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAE1E;;;;GAIG;AACH,QAAA,MAAM,aAAa,yDAA0D,CAAC;AAC9E,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE;AAED;;;;GAIG;AACH,MAAM,WAAW,KAAK,CAAC,QAAQ,GAAG,OAAO;IACvC,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iCAAiC;IACjC,OAAO,EAAE,QAAQ,CAAC;IAClB,iFAAiF;IACjF,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oCAAoC;IACpC,MAAM,EAAE,WAAW,CAAC;IACpB,qEAAqE;IACrE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,mCAAmC;IACnC,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW,CAAC,QAAQ,GAAG,OAAO;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,QAAQ,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The built-in Telescope entry types shipped by `@adonis-agora/telescope`. Adapted from
3
+ * the NestJS `nestjs-telescope` core, trimmed to the headless slice this package
4
+ * actually records (request + diagnostic). The remaining values are reserved so a
5
+ * future watcher (a Lucid query watcher, a mailer watcher, …) can record under a
6
+ * stable, already-documented type without a breaking change.
7
+ */
8
+ export const EntryType = {
9
+ /** An inbound HTTP request, recorded by {@link TelescopeMiddleware}. */
10
+ Request: 'request',
11
+ /** A single `agora:<lib>:<event>` diagnostics publish. */
12
+ Diagnostic: 'diagnostic',
13
+ // — reserved for deferred per-tech watchers (see DESIGN.md) —
14
+ Query: 'query',
15
+ Job: 'job',
16
+ Exception: 'exception',
17
+ Mail: 'mail',
18
+ Cache: 'cache',
19
+ Redis: 'redis',
20
+ Event: 'event',
21
+ Log: 'log',
22
+ /** An OUTBOUND HTTP call, recorded by the http-client watcher. */
23
+ HttpClient: 'http-client',
24
+ };
25
+ /**
26
+ * Where a batch of entries originated. An entry recorded inside an HTTP request
27
+ * is `http`; one recorded by a queue worker is `queue`; a diagnostic recorded
28
+ * with no active request defaults to `manual`.
29
+ */
30
+ const BATCH_ORIGINS = ['http', 'queue', 'schedule', 'cli', 'manual'];
31
+ export function isBatchOrigin(value) {
32
+ return typeof value === 'string' && BATCH_ORIGINS.includes(value);
33
+ }
34
+ //# sourceMappingURL=entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/entry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,wEAAwE;IACxE,OAAO,EAAE,SAAS;IAClB,0DAA0D;IAC1D,UAAU,EAAE,YAAY;IACxB,8DAA8D;IAC9D,KAAK,EAAE,OAAO;IACd,GAAG,EAAE,KAAK;IACV,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,GAAG,EAAE,KAAK;IACV,kEAAkE;IAClE,UAAU,EAAE,aAAa;CACjB,CAAC;AAIX;;;;GAIG;AACH,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAU,CAAC;AAG9E,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,aAAmC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC3F,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The grouping key for "the same error". Two occurrences share a family when
3
+ * they have the same name, the same message, AND originate at the same top stack
4
+ * frame — so a `TypeError: x is undefined` thrown from two unrelated call sites
5
+ * stays two families, while the same bug recurring stays ONE. This is what the
6
+ * dashboard's exception groupings and the `new-exception` alert dedup on, so it
7
+ * must be deterministic across processes and across recording sources.
8
+ *
9
+ * Ported from the NestJS `nestjs-telescope` core. Why include the top frame:
10
+ * name+message alone over-groups (the same generic message from different code
11
+ * paths collapses into one family) and a host that embeds ids in messages would
12
+ * otherwise under-group; pinning to the first frame is the pragmatic middle
13
+ * ground used by most error trackers.
14
+ */
15
+ export interface ExceptionFamilyParts {
16
+ /** Error class/name (e.g. `TypeError`); empty string when unknown. */
17
+ name: string;
18
+ /** Error message; empty string when unknown. */
19
+ message: string;
20
+ /** Full stack string, or `null` — only its FIRST frame contributes. */
21
+ stack: string | null;
22
+ }
23
+ /**
24
+ * Build the stable family hash for an exception from its name, message and top
25
+ * stack frame. Pure string concatenation (no hashing) keeps it human-readable in
26
+ * the dashboard and trivially equal across every recording path.
27
+ */
28
+ export declare function exceptionFamilyHash(parts: ExceptionFamilyParts): string;
29
+ //# sourceMappingURL=exception_family_hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exception_family_hash.d.ts","sourceRoot":"","sources":["../../src/exception_family_hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAsBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAGvE"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Extract the first stack FRAME line (the deepest call site), skipping the
3
+ * leading `Name: message` header that V8 and most browsers prepend. Returns an
4
+ * empty string when no frame line is present, so the family key degrades to
5
+ * name+message rather than throwing.
6
+ */
7
+ function topStackFrame(stack) {
8
+ if (stack === null)
9
+ return '';
10
+ for (const rawLine of stack.split('\n')) {
11
+ const line = rawLine.trim();
12
+ // The first line is usually the `Error: message` header (no `at `). Frame
13
+ // lines start with `at ` in V8; browsers vary, so also accept an `@`-form
14
+ // (Firefox/Safari: `fn@url:line:col`). Take the first that looks like a frame.
15
+ if (line.startsWith('at ') || line.includes('@')) {
16
+ return line;
17
+ }
18
+ }
19
+ return '';
20
+ }
21
+ /**
22
+ * Build the stable family hash for an exception from its name, message and top
23
+ * stack frame. Pure string concatenation (no hashing) keeps it human-readable in
24
+ * the dashboard and trivially equal across every recording path.
25
+ */
26
+ export function exceptionFamilyHash(parts) {
27
+ const frame = topStackFrame(parts.stack);
28
+ return `${parts.name}:${parts.message}:${frame}`;
29
+ }
30
+ //# sourceMappingURL=exception_family_hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exception_family_hash.js","sourceRoot":"","sources":["../../src/exception_family_hash.ts"],"names":[],"mappings":"AAuBA;;;;;GAKG;AACH,SAAS,aAAa,CAAC,KAAoB;IACzC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC9B,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,0EAA0E;QAC1E,0EAA0E;QAC1E,+EAA+E;QAC/E,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACjD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA2B;IAC7D,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACzC,OAAO,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,EAAE,CAAC;AACnD,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { type Entry, type RecordInput } from './entry.js';
2
+ import type { TelescopeStore } from './store.js';
3
+ /** The recorded body of an `exception` entry. */
4
+ export interface ExceptionEntryContent {
5
+ /** Error class/name (e.g. `TypeError`, `Error`). */
6
+ name: string;
7
+ /** Error message. */
8
+ message: string;
9
+ /** Full stack string, or `null` when none was captured. */
10
+ stack: string | null;
11
+ /** HTTP method, when the exception was recorded inside a request. */
12
+ method: string | null;
13
+ /** Request url (no query string), when recorded inside a request. */
14
+ url: string | null;
15
+ /** The active trace id at record time, or `null`. */
16
+ traceId: string | null;
17
+ }
18
+ /** Optional request/correlation context for {@link recordException}. */
19
+ export interface RecordExceptionContext {
20
+ /** HTTP method (upper-cased by the watcher), when applicable. */
21
+ method?: string;
22
+ /** Request url; the query string is stripped. */
23
+ url?: string;
24
+ /** Override the trace id; defaults to the ambient `@adonis-agora/context`. */
25
+ traceId?: string | null;
26
+ /** Override the batch origin; defaults to the store's resolution. */
27
+ origin?: RecordInput['origin'];
28
+ }
29
+ /**
30
+ * Build the `exception` {@link RecordInput} for `error` + optional context. Pure
31
+ * and side-effect free so it is trivially unit-testable; both the middleware
32
+ * auto-capture and {@link recordException} build through it. The family hash
33
+ * groups same-signature errors (name + message + top stack frame).
34
+ */
35
+ export declare function buildExceptionInput(error: unknown, context?: RecordExceptionContext): RecordInput<ExceptionEntryContent>;
36
+ /**
37
+ * Record an `exception` entry into `store` from a thrown value. The pure,
38
+ * framework-agnostic core used by the request middleware. Resolves to the
39
+ * recorded {@link Entry}; backfills the content trace id from whatever the store
40
+ * resolved (the ambient `@adonis-agora/context`).
41
+ */
42
+ export declare function recordExceptionInStore(store: TelescopeStore, error: unknown, context?: RecordExceptionContext): Promise<Entry<ExceptionEntryContent>>;
43
+ /**
44
+ * Standalone exception capture for manual / non-HTTP code paths — queue workers,
45
+ * ace commands, and an app's `app/exceptions/handler.ts` `report()`. Reads the
46
+ * active store from the global telescope runtime slot (the same handle the
47
+ * watchers record through) so callers need no DI wiring, and is a no-op when
48
+ * telescope is disabled / not booted.
49
+ *
50
+ * Fire-and-forget-safe: never throws. A missing store or a failing store is
51
+ * swallowed (warn-logged) so capturing an exception can never mask or replace the
52
+ * original error in the caller's failure path.
53
+ *
54
+ * ```ts
55
+ * // app/exceptions/handler.ts
56
+ * import { recordException } from '@adonis-agora/telescope'
57
+ * async report(error: unknown, ctx: HttpContext) {
58
+ * recordException(error, { method: ctx.request.method(), url: ctx.request.url() })
59
+ * return super.report(error, ctx)
60
+ * }
61
+ * ```
62
+ *
63
+ * Resolves to the recorded {@link Entry}, or `null` when nothing was recorded.
64
+ */
65
+ export declare function recordException(error: unknown, context?: RecordExceptionContext): Promise<Entry<ExceptionEntryContent> | null>;
66
+ //# sourceMappingURL=exception_watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exception_watcher.d.ts","sourceRoot":"","sources":["../../src/exception_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAa,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAGrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,iDAAiD;AACjD,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qEAAqE;IACrE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,qEAAqE;IACrE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,qDAAqD;IACrD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,qEAAqE;IACrE,MAAM,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;CAChC;AAqBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,sBAA2B,GACnC,WAAW,CAAC,qBAAqB,CAAC,CAgBpC;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAIvC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,GAAG,IAAI,CAAC,CAoB9C"}
@@ -0,0 +1,94 @@
1
+ import { EntryType } from './entry.js';
2
+ import { exceptionFamilyHash } from './exception_family_hash.js';
3
+ import { getTelescopeRuntime } from './registry.js';
4
+ /** Coerce an unknown thrown value into name/message/stack. */
5
+ function normalizeError(error) {
6
+ if (error instanceof Error) {
7
+ return {
8
+ name: error.name || 'Error',
9
+ message: error.message,
10
+ stack: typeof error.stack === 'string' ? error.stack : null,
11
+ };
12
+ }
13
+ // Non-Error throws (strings, objects) still deserve an entry.
14
+ return { name: 'Error', message: String(error), stack: null };
15
+ }
16
+ /** Drop a `?query=string` suffix from a url. */
17
+ function stripQuery(url) {
18
+ const q = url.indexOf('?');
19
+ return q === -1 ? url : url.slice(0, q);
20
+ }
21
+ /**
22
+ * Build the `exception` {@link RecordInput} for `error` + optional context. Pure
23
+ * and side-effect free so it is trivially unit-testable; both the middleware
24
+ * auto-capture and {@link recordException} build through it. The family hash
25
+ * groups same-signature errors (name + message + top stack frame).
26
+ */
27
+ export function buildExceptionInput(error, context = {}) {
28
+ const { name, message, stack } = normalizeError(error);
29
+ const method = context.method !== undefined ? context.method.toUpperCase() : null;
30
+ const url = context.url !== undefined ? stripQuery(context.url) : null;
31
+ const familyHash = exceptionFamilyHash({ name, message, stack });
32
+ const tags = [`exception:${name}`, ...(method !== null ? [`method:${method}`] : [])];
33
+ return {
34
+ type: EntryType.Exception,
35
+ content: { name, message, stack, method, url, traceId: context.traceId ?? null },
36
+ familyHash,
37
+ tags,
38
+ ...(context.traceId !== undefined ? { traceId: context.traceId } : {}),
39
+ ...(context.origin !== undefined ? { origin: context.origin } : {}),
40
+ };
41
+ }
42
+ /**
43
+ * Record an `exception` entry into `store` from a thrown value. The pure,
44
+ * framework-agnostic core used by the request middleware. Resolves to the
45
+ * recorded {@link Entry}; backfills the content trace id from whatever the store
46
+ * resolved (the ambient `@adonis-agora/context`).
47
+ */
48
+ export async function recordExceptionInStore(store, error, context = {}) {
49
+ const recorded = await store.record(buildExceptionInput(error, context));
50
+ recorded.content.traceId = recorded.traceId;
51
+ return recorded;
52
+ }
53
+ /**
54
+ * Standalone exception capture for manual / non-HTTP code paths — queue workers,
55
+ * ace commands, and an app's `app/exceptions/handler.ts` `report()`. Reads the
56
+ * active store from the global telescope runtime slot (the same handle the
57
+ * watchers record through) so callers need no DI wiring, and is a no-op when
58
+ * telescope is disabled / not booted.
59
+ *
60
+ * Fire-and-forget-safe: never throws. A missing store or a failing store is
61
+ * swallowed (warn-logged) so capturing an exception can never mask or replace the
62
+ * original error in the caller's failure path.
63
+ *
64
+ * ```ts
65
+ * // app/exceptions/handler.ts
66
+ * import { recordException } from '@adonis-agora/telescope'
67
+ * async report(error: unknown, ctx: HttpContext) {
68
+ * recordException(error, { method: ctx.request.method(), url: ctx.request.url() })
69
+ * return super.report(error, ctx)
70
+ * }
71
+ * ```
72
+ *
73
+ * Resolves to the recorded {@link Entry}, or `null` when nothing was recorded.
74
+ */
75
+ export async function recordException(error, context = {}) {
76
+ let store;
77
+ try {
78
+ store = getTelescopeRuntime().store;
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ if (store === null)
84
+ return null;
85
+ try {
86
+ return await recordExceptionInStore(store, error, context);
87
+ }
88
+ catch (recordError) {
89
+ // Observability must never break the path it observes.
90
+ console.warn(`Telescope: failed to record exception: ${recordError instanceof Error ? recordError.message : String(recordError)}`);
91
+ return null;
92
+ }
93
+ }
94
+ //# sourceMappingURL=exception_watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exception_watcher.js","sourceRoot":"","sources":["../../src/exception_watcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { DashboardSpec, DataProvider, ExtensionContext, ExtensionEntryType, TelescopeExtension } from './types.js';
2
+ /**
3
+ * Eagerly validates and collects every contribution from the registered extensions at boot. Enforces
4
+ * uniqueness — two extensions cannot contribute the same entry-type id, dashboard id, or provider
5
+ * name; a collision throws at boot (fail-closed), naming both owners, so drift is a startup error
6
+ * rather than a confusing runtime one. Accessors return copies; `findProvider` also tracks the owning
7
+ * extension so the HTTP layer can validate the `/ext/:ext/data/:provider` namespace.
8
+ */
9
+ export declare class ExtensionRegistry {
10
+ #private;
11
+ constructor(extensions: readonly TelescopeExtension[], ctx: ExtensionContext);
12
+ entryTypes(): ExtensionEntryType[];
13
+ dashboards(): DashboardSpec[];
14
+ findProvider(name: string): DataProvider | undefined;
15
+ providerOwner(name: string): string | undefined;
16
+ }
17
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/extension/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,qBAAa,iBAAiB;;gBAMhB,UAAU,EAAE,SAAS,kBAAkB,EAAE,EAAE,GAAG,EAAE,gBAAgB;IAwC5E,UAAU,IAAI,kBAAkB,EAAE;IAGlC,UAAU,IAAI,aAAa,EAAE;IAG7B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAGpD,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAGhD"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Eagerly validates and collects every contribution from the registered extensions at boot. Enforces
3
+ * uniqueness — two extensions cannot contribute the same entry-type id, dashboard id, or provider
4
+ * name; a collision throws at boot (fail-closed), naming both owners, so drift is a startup error
5
+ * rather than a confusing runtime one. Accessors return copies; `findProvider` also tracks the owning
6
+ * extension so the HTTP layer can validate the `/ext/:ext/data/:provider` namespace.
7
+ */
8
+ export class ExtensionRegistry {
9
+ #entryTypes = [];
10
+ #dashboards = [];
11
+ #providers = new Map();
12
+ #providerOwners = new Map();
13
+ constructor(extensions, ctx) {
14
+ const entryOwners = new Map();
15
+ const dashOwners = new Map();
16
+ for (const ext of extensions) {
17
+ for (const et of ext.entryTypes?.(ctx) ?? []) {
18
+ const prev = entryOwners.get(et.id);
19
+ if (prev !== undefined) {
20
+ throw new Error(`Telescope entry type "${et.id}" is contributed by both "${prev}" and "${ext.name}". Entry-type ids must be unique.`);
21
+ }
22
+ entryOwners.set(et.id, ext.name);
23
+ this.#entryTypes.push(et);
24
+ }
25
+ for (const d of ext.dashboards?.(ctx) ?? []) {
26
+ const prev = dashOwners.get(d.id);
27
+ if (prev !== undefined) {
28
+ throw new Error(`Telescope dashboard "${d.id}" is contributed by both "${prev}" and "${ext.name}". Dashboard ids must be unique.`);
29
+ }
30
+ dashOwners.set(d.id, ext.name);
31
+ this.#dashboards.push(d);
32
+ }
33
+ for (const p of ext.dataProviders?.(ctx) ?? []) {
34
+ const prev = this.#providerOwners.get(p.name);
35
+ if (prev !== undefined) {
36
+ throw new Error(`Telescope data provider "${p.name}" is contributed by both "${prev}" and "${ext.name}". Provider names must be unique.`);
37
+ }
38
+ this.#providerOwners.set(p.name, ext.name);
39
+ this.#providers.set(p.name, p);
40
+ }
41
+ }
42
+ }
43
+ entryTypes() {
44
+ return [...this.#entryTypes];
45
+ }
46
+ dashboards() {
47
+ return [...this.#dashboards];
48
+ }
49
+ findProvider(name) {
50
+ return this.#providers.get(name);
51
+ }
52
+ providerOwner(name) {
53
+ return this.#providerOwners.get(name);
54
+ }
55
+ }
56
+ //# sourceMappingURL=registry.js.map