@adonis-agora/telescope 0.4.0 → 0.5.1

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 (198) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/configure.d.ts.map +1 -1
  3. package/dist/configure.js +6 -0
  4. package/dist/configure.js.map +1 -1
  5. package/dist/providers/telescope_ai_provider.d.ts +9 -1
  6. package/dist/providers/telescope_ai_provider.d.ts.map +1 -1
  7. package/dist/providers/telescope_ai_provider.js +22 -2
  8. package/dist/providers/telescope_ai_provider.js.map +1 -1
  9. package/dist/providers/telescope_alerts_provider.d.ts +1 -0
  10. package/dist/providers/telescope_alerts_provider.d.ts.map +1 -1
  11. package/dist/providers/telescope_alerts_provider.js +37 -1
  12. package/dist/providers/telescope_alerts_provider.js.map +1 -1
  13. package/dist/providers/telescope_mcp_provider.d.ts +26 -0
  14. package/dist/providers/telescope_mcp_provider.d.ts.map +1 -0
  15. package/dist/providers/telescope_mcp_provider.js +114 -0
  16. package/dist/providers/telescope_mcp_provider.js.map +1 -0
  17. package/dist/providers/telescope_provider.d.ts +8 -0
  18. package/dist/providers/telescope_provider.d.ts.map +1 -1
  19. package/dist/providers/telescope_provider.js +56 -1
  20. package/dist/providers/telescope_provider.js.map +1 -1
  21. package/dist/providers/telescope_ui_provider.d.ts +2 -0
  22. package/dist/providers/telescope_ui_provider.d.ts.map +1 -1
  23. package/dist/providers/telescope_ui_provider.js +106 -22
  24. package/dist/providers/telescope_ui_provider.js.map +1 -1
  25. package/dist/providers/telescope_watchers_provider.d.ts +13 -2
  26. package/dist/providers/telescope_watchers_provider.d.ts.map +1 -1
  27. package/dist/providers/telescope_watchers_provider.js +57 -7
  28. package/dist/providers/telescope_watchers_provider.js.map +1 -1
  29. package/dist/src/ai/define_config.d.ts +39 -7
  30. package/dist/src/ai/define_config.d.ts.map +1 -1
  31. package/dist/src/ai/define_config.js +14 -4
  32. package/dist/src/ai/define_config.js.map +1 -1
  33. package/dist/src/ai/diagnosis_coordinator.d.ts +106 -0
  34. package/dist/src/ai/diagnosis_coordinator.d.ts.map +1 -0
  35. package/dist/src/ai/diagnosis_coordinator.js +143 -0
  36. package/dist/src/ai/diagnosis_coordinator.js.map +1 -0
  37. package/dist/src/ai/factory.d.ts +8 -5
  38. package/dist/src/ai/factory.d.ts.map +1 -1
  39. package/dist/src/ai/factory.js +20 -8
  40. package/dist/src/ai/factory.js.map +1 -1
  41. package/dist/src/ai/index.d.ts +3 -1
  42. package/dist/src/ai/index.d.ts.map +1 -1
  43. package/dist/src/ai/index.js +3 -1
  44. package/dist/src/ai/index.js.map +1 -1
  45. package/dist/src/alerts/alert_rule.d.ts +79 -11
  46. package/dist/src/alerts/alert_rule.d.ts.map +1 -1
  47. package/dist/src/alerts/alerter.d.ts +11 -1
  48. package/dist/src/alerts/alerter.d.ts.map +1 -1
  49. package/dist/src/alerts/alerter.js +21 -1
  50. package/dist/src/alerts/alerter.js.map +1 -1
  51. package/dist/src/alerts/alerter_service.d.ts +103 -0
  52. package/dist/src/alerts/alerter_service.d.ts.map +1 -0
  53. package/dist/src/alerts/alerter_service.js +215 -0
  54. package/dist/src/alerts/alerter_service.js.map +1 -0
  55. package/dist/src/alerts/index.d.ts +4 -2
  56. package/dist/src/alerts/index.d.ts.map +1 -1
  57. package/dist/src/alerts/index.js +4 -2
  58. package/dist/src/alerts/index.js.map +1 -1
  59. package/dist/src/alerts/slack_format.d.ts.map +1 -1
  60. package/dist/src/alerts/slack_format.js +35 -2
  61. package/dist/src/alerts/slack_format.js.map +1 -1
  62. package/dist/src/client_errors/config.d.ts +66 -0
  63. package/dist/src/client_errors/config.d.ts.map +1 -0
  64. package/dist/src/client_errors/config.js +17 -0
  65. package/dist/src/client_errors/config.js.map +1 -0
  66. package/dist/src/client_errors/index.d.ts +5 -0
  67. package/dist/src/client_errors/index.d.ts.map +1 -0
  68. package/dist/src/client_errors/index.js +5 -0
  69. package/dist/src/client_errors/index.js.map +1 -0
  70. package/dist/src/client_errors/ingestor.d.ts +87 -0
  71. package/dist/src/client_errors/ingestor.d.ts.map +1 -0
  72. package/dist/src/client_errors/ingestor.js +135 -0
  73. package/dist/src/client_errors/ingestor.js.map +1 -0
  74. package/dist/src/client_errors/rate_limiter.d.ts +53 -0
  75. package/dist/src/client_errors/rate_limiter.d.ts.map +1 -0
  76. package/dist/src/client_errors/rate_limiter.js +83 -0
  77. package/dist/src/client_errors/rate_limiter.js.map +1 -0
  78. package/dist/src/client_errors/validation.d.ts +62 -0
  79. package/dist/src/client_errors/validation.d.ts.map +1 -0
  80. package/dist/src/client_errors/validation.js +109 -0
  81. package/dist/src/client_errors/validation.js.map +1 -0
  82. package/dist/src/define_config.d.ts +120 -0
  83. package/dist/src/define_config.d.ts.map +1 -1
  84. package/dist/src/define_config.js +41 -0
  85. package/dist/src/define_config.js.map +1 -1
  86. package/dist/src/entry.d.ts +20 -0
  87. package/dist/src/entry.d.ts.map +1 -1
  88. package/dist/src/entry.js +20 -0
  89. package/dist/src/entry.js.map +1 -1
  90. package/dist/src/index.d.ts +16 -3
  91. package/dist/src/index.d.ts.map +1 -1
  92. package/dist/src/index.js +15 -2
  93. package/dist/src/index.js.map +1 -1
  94. package/dist/src/mcp/define_config.d.ts +57 -0
  95. package/dist/src/mcp/define_config.d.ts.map +1 -0
  96. package/dist/src/mcp/define_config.js +35 -0
  97. package/dist/src/mcp/define_config.js.map +1 -0
  98. package/dist/src/mcp/index.d.ts +7 -0
  99. package/dist/src/mcp/index.d.ts.map +1 -0
  100. package/dist/src/mcp/index.js +7 -0
  101. package/dist/src/mcp/index.js.map +1 -0
  102. package/dist/src/mcp/server.d.ts +89 -0
  103. package/dist/src/mcp/server.d.ts.map +1 -0
  104. package/dist/src/mcp/server.js +279 -0
  105. package/dist/src/mcp/server.js.map +1 -0
  106. package/dist/src/metrics/pulse.d.ts +158 -0
  107. package/dist/src/metrics/pulse.d.ts.map +1 -0
  108. package/dist/src/metrics/pulse.js +414 -0
  109. package/dist/src/metrics/pulse.js.map +1 -0
  110. package/dist/src/overload_guard.d.ts +77 -0
  111. package/dist/src/overload_guard.d.ts.map +1 -0
  112. package/dist/src/overload_guard.js +134 -0
  113. package/dist/src/overload_guard.js.map +1 -0
  114. package/dist/src/pruner.d.ts +93 -0
  115. package/dist/src/pruner.d.ts.map +1 -0
  116. package/dist/src/pruner.js +120 -0
  117. package/dist/src/pruner.js.map +1 -0
  118. package/dist/src/registry.d.ts +26 -0
  119. package/dist/src/registry.d.ts.map +1 -1
  120. package/dist/src/registry.js +19 -0
  121. package/dist/src/registry.js.map +1 -1
  122. package/dist/src/service.d.ts +16 -1
  123. package/dist/src/service.d.ts.map +1 -1
  124. package/dist/src/service.js +19 -1
  125. package/dist/src/service.js.map +1 -1
  126. package/dist/src/stores/factory.d.ts.map +1 -1
  127. package/dist/src/stores/factory.js +8 -2
  128. package/dist/src/stores/factory.js.map +1 -1
  129. package/dist/src/telescope_middleware.d.ts.map +1 -1
  130. package/dist/src/telescope_middleware.js +3 -1
  131. package/dist/src/telescope_middleware.js.map +1 -1
  132. package/dist/src/ui/api.d.ts +9 -1
  133. package/dist/src/ui/api.d.ts.map +1 -1
  134. package/dist/src/ui/api.js +23 -1
  135. package/dist/src/ui/api.js.map +1 -1
  136. package/dist/src/ui/auth.d.ts +112 -0
  137. package/dist/src/ui/auth.d.ts.map +1 -0
  138. package/dist/src/ui/auth.js +130 -0
  139. package/dist/src/ui/auth.js.map +1 -0
  140. package/dist/src/ui/dashboard_auth.d.ts +34 -0
  141. package/dist/src/ui/dashboard_auth.d.ts.map +1 -0
  142. package/dist/src/ui/dashboard_auth.js +57 -0
  143. package/dist/src/ui/dashboard_auth.js.map +1 -0
  144. package/dist/src/ui/define_config.d.ts +15 -0
  145. package/dist/src/ui/define_config.d.ts.map +1 -1
  146. package/dist/src/ui/define_config.js +4 -0
  147. package/dist/src/ui/define_config.js.map +1 -1
  148. package/dist/src/ui/index.d.ts +7 -1
  149. package/dist/src/ui/index.d.ts.map +1 -1
  150. package/dist/src/ui/index.js +6 -1
  151. package/dist/src/ui/index.js.map +1 -1
  152. package/dist/src/ui/login_page.d.ts +23 -0
  153. package/dist/src/ui/login_page.d.ts.map +1 -0
  154. package/dist/src/ui/login_page.js +170 -0
  155. package/dist/src/ui/login_page.js.map +1 -0
  156. package/dist/src/ui/session_cookie.d.ts +42 -0
  157. package/dist/src/ui/session_cookie.d.ts.map +1 -0
  158. package/dist/src/ui/session_cookie.js +97 -0
  159. package/dist/src/ui/session_cookie.js.map +1 -0
  160. package/dist/src/watchers/define_config.d.ts +115 -12
  161. package/dist/src/watchers/define_config.d.ts.map +1 -1
  162. package/dist/src/watchers/define_config.js +48 -11
  163. package/dist/src/watchers/define_config.js.map +1 -1
  164. package/dist/src/watchers/http_client_watcher.d.ts +91 -31
  165. package/dist/src/watchers/http_client_watcher.d.ts.map +1 -1
  166. package/dist/src/watchers/http_client_watcher.js +161 -53
  167. package/dist/src/watchers/http_client_watcher.js.map +1 -1
  168. package/dist/src/watchers/index.d.ts +9 -5
  169. package/dist/src/watchers/index.d.ts.map +1 -1
  170. package/dist/src/watchers/index.js +6 -2
  171. package/dist/src/watchers/index.js.map +1 -1
  172. package/dist/src/watchers/lucid_query_watcher.d.ts +20 -2
  173. package/dist/src/watchers/lucid_query_watcher.d.ts.map +1 -1
  174. package/dist/src/watchers/lucid_query_watcher.js +44 -10
  175. package/dist/src/watchers/lucid_query_watcher.js.map +1 -1
  176. package/dist/src/watchers/profiling_watcher.d.ts +144 -0
  177. package/dist/src/watchers/profiling_watcher.d.ts.map +1 -0
  178. package/dist/src/watchers/profiling_watcher.js +231 -0
  179. package/dist/src/watchers/profiling_watcher.js.map +1 -0
  180. package/dist/src/watchers/query_family_hash.d.ts +7 -2
  181. package/dist/src/watchers/query_family_hash.d.ts.map +1 -1
  182. package/dist/src/watchers/query_family_hash.js +8 -3
  183. package/dist/src/watchers/query_family_hash.js.map +1 -1
  184. package/dist/src/watchers/record.d.ts.map +1 -1
  185. package/dist/src/watchers/record.js +4 -0
  186. package/dist/src/watchers/record.js.map +1 -1
  187. package/dist/src/watchers/schedule_watcher.d.ts +143 -0
  188. package/dist/src/watchers/schedule_watcher.d.ts.map +1 -0
  189. package/dist/src/watchers/schedule_watcher.js +195 -0
  190. package/dist/src/watchers/schedule_watcher.js.map +1 -0
  191. package/dist/stubs/config/telescope.stub +0 -65
  192. package/dist/stubs/config/telescope_ai.stub +0 -36
  193. package/dist/stubs/config/telescope_alerts.stub +0 -47
  194. package/dist/stubs/config/telescope_mcp.stub +0 -0
  195. package/dist/stubs/config/telescope_ui.stub +0 -53
  196. package/dist/stubs/config/telescope_watchers.stub +0 -41
  197. package/dist/stubs/database/migrations/create_telescope_entries_table.stub +2 -2
  198. package/package.json +12 -2
@@ -0,0 +1,414 @@
1
+ import { EntryType } from '../entry.js';
2
+ import { detectNPlusOne } from '../query/n_plus_one.js';
3
+ import { percentile, summarizeStats, } from './stats.js';
4
+ import { bucketTimeseries } from './timeseries.js';
5
+ /**
6
+ * The Pulse feature — an aggregated "at a glance" health rollup over recorded
7
+ * telescope entries. Ported from `nestjs-telescope`'s `pulse.service` +
8
+ * `pulse-summary`, adapted to the Adonis entry model (a single-pass scan over
9
+ * entries that already carry their `content`, N+1 grouped by `traceId` rather
10
+ * than the NestJS `batchId`) and to REUSE the existing metrics primitives:
11
+ * {@link summarizeStats} (request status + cache hit ratio + exception groups),
12
+ * {@link bucketTimeseries} (throughput), {@link percentile} (slow-hotspot p99),
13
+ * and {@link detectNPlusOne} (N+1 loops). Pure aggregation lives in
14
+ * {@link summarizePulse}; {@link PulseService} adds the windowed store scan.
15
+ */
16
+ const DEFAULT_WINDOW_MS = 3_600_000;
17
+ const DEFAULT_TOP_N = 5;
18
+ const DEFAULT_BUCKETS = 60;
19
+ const MAX_BUCKETS = 500;
20
+ /** Threshold (ms) at/above which a latency sample counts as "slow". */
21
+ const DEFAULT_SLOW_MS = 100;
22
+ /**
23
+ * Min p99 (ms) a route/outgoing family must reach to surface as a slow hotspot,
24
+ * so a quiet, healthy host's fastest-of-the-slow route (e.g. `/health` at 18ms)
25
+ * is not a false alarm. Matches the NestJS pulse default.
26
+ */
27
+ const DEFAULT_SLOW_ROUTE_MS = 1000;
28
+ /** Min repetitions of one query template within a trace to flag an N+1 loop. */
29
+ const DEFAULT_NPLUSONE_THRESHOLD = 5;
30
+ /** Min samples a family needs before it can rank as a hotspot. */
31
+ const DEFAULT_HOTSPOT_MIN_COUNT = 1;
32
+ /** Safety cap on entries scanned per pulse; surfaced via `truncated`. */
33
+ const DEFAULT_SCAN_CAP = 50_000;
34
+ const MAX_LABEL_LENGTH = 500;
35
+ /** The togglable pulse cards. `counts` and the window meta are always present. */
36
+ export const PULSE_CARDS = [
37
+ 'throughput',
38
+ 'requests',
39
+ 'cache',
40
+ 'slowest',
41
+ 'slowRoutes',
42
+ 'slowOutgoing',
43
+ 'slowJobs',
44
+ 'exceptions',
45
+ 'nPlusOne',
46
+ 'loadByUser',
47
+ ];
48
+ function truncate(value) {
49
+ return value.length > MAX_LABEL_LENGTH ? `${value.slice(0, MAX_LABEL_LENGTH)}…` : value;
50
+ }
51
+ function asRecord(content) {
52
+ return typeof content === 'object' && content !== null
53
+ ? content
54
+ : null;
55
+ }
56
+ /** A human label for a slow entry, derived from its content by type. */
57
+ function labelFrom(entry) {
58
+ const record = asRecord(entry.content);
59
+ if (record === null)
60
+ return entry.familyHash ?? entry.type;
61
+ if (typeof record.url === 'string') {
62
+ return typeof record.method === 'string' ? `${record.method} ${record.url}` : record.url;
63
+ }
64
+ if (typeof record.sql === 'string')
65
+ return record.sql;
66
+ if (typeof record.queue === 'string' && typeof record.name === 'string') {
67
+ return `${record.queue}:${record.name}`;
68
+ }
69
+ return entry.familyHash ?? entry.type;
70
+ }
71
+ /** The route family for a request entry: its `familyHash`, else `METHOD url`. */
72
+ function routeKey(entry) {
73
+ if (entry.familyHash !== null)
74
+ return entry.familyHash;
75
+ const record = asRecord(entry.content);
76
+ if (record === null)
77
+ return null;
78
+ if (typeof record.url !== 'string')
79
+ return null;
80
+ return typeof record.method === 'string' ? `${record.method} ${record.url}` : record.url;
81
+ }
82
+ /** Extract the user id from a `user:<id>` tag, or `null` when none is present. */
83
+ function userFromTags(tags) {
84
+ for (const tag of tags) {
85
+ if (tag.startsWith('user:'))
86
+ return tag.slice('user:'.length);
87
+ }
88
+ return null;
89
+ }
90
+ /** Rank per-family durations into hotspots by p99, gated by min-count (+ optional slow-ms). */
91
+ function toHotspots(durationsByFamily, options, slowRouteMs) {
92
+ return [...durationsByFamily.entries()]
93
+ .map(([route, durations]) => {
94
+ const sorted = [...durations].sort((a, b) => a - b);
95
+ return {
96
+ route,
97
+ count: sorted.length,
98
+ p99: percentile(sorted, 0.99),
99
+ p50: percentile(sorted, 0.5),
100
+ };
101
+ })
102
+ .filter((h) => h.count >= options.hotspotMinCount && h.p99 >= slowRouteMs)
103
+ .sort((a, b) => b.p99 - a.p99 || b.count - a.count || a.route.localeCompare(b.route))
104
+ .slice(0, options.topN);
105
+ }
106
+ function pushDuration(map, key, durationMs) {
107
+ const existing = map.get(key);
108
+ if (existing)
109
+ existing.push(durationMs);
110
+ else
111
+ map.set(key, [durationMs]);
112
+ }
113
+ /** Empty request-health card (used when the `requests` card is disabled). */
114
+ function emptyRequestHealth() {
115
+ return {
116
+ total: 0,
117
+ errorRate: 0,
118
+ status: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, other: 0 },
119
+ };
120
+ }
121
+ /**
122
+ * Aggregate a window of entries — each carrying its `content` — into a
123
+ * {@link PulseSummary}. Pure: callers fetch the windowed entries and supply the
124
+ * window bounds. Reuses {@link summarizeStats}, {@link bucketTimeseries},
125
+ * {@link percentile}, and {@link detectNPlusOne} for the heavy lifting.
126
+ */
127
+ export function summarizePulse(entries, windowStart, windowEnd, options, truncated = false) {
128
+ const enabled = (card) => options.cards.has(card);
129
+ const windowMs = Math.max(0, windowEnd.getTime() - windowStart.getTime());
130
+ const counts = {};
131
+ const slowCandidates = [];
132
+ const routeDurations = new Map();
133
+ const outgoingDurations = new Map();
134
+ const jobDurations = new Map();
135
+ const byTrace = new Map();
136
+ const userLoad = new Map();
137
+ const requestEntries = [];
138
+ const cacheEntries = [];
139
+ const exceptionEntries = [];
140
+ for (const entry of entries) {
141
+ counts[entry.type] = (counts[entry.type] ?? 0) + 1;
142
+ if (typeof entry.durationMs === 'number')
143
+ slowCandidates.push(entry);
144
+ if (entry.type === EntryType.Request) {
145
+ requestEntries.push(entry);
146
+ const route = routeKey(entry);
147
+ if (route !== null && typeof entry.durationMs === 'number') {
148
+ pushDuration(routeDurations, route, entry.durationMs);
149
+ }
150
+ const user = userFromTags(entry.tags);
151
+ if (user !== null) {
152
+ const duration = typeof entry.durationMs === 'number' ? entry.durationMs : 0;
153
+ const existing = userLoad.get(user);
154
+ if (existing) {
155
+ existing.count += 1;
156
+ existing.totalDurationMs += duration;
157
+ }
158
+ else {
159
+ userLoad.set(user, { count: 1, totalDurationMs: duration });
160
+ }
161
+ }
162
+ }
163
+ else if (entry.type === EntryType.HttpClient) {
164
+ if (entry.familyHash !== null && typeof entry.durationMs === 'number') {
165
+ pushDuration(outgoingDurations, entry.familyHash, entry.durationMs);
166
+ }
167
+ }
168
+ else if (entry.type === EntryType.Job) {
169
+ if (entry.familyHash !== null && typeof entry.durationMs === 'number') {
170
+ pushDuration(jobDurations, entry.familyHash, entry.durationMs);
171
+ }
172
+ }
173
+ else if (entry.type === EntryType.Cache) {
174
+ cacheEntries.push(entry);
175
+ }
176
+ else if (entry.type === EntryType.Exception) {
177
+ exceptionEntries.push(entry);
178
+ }
179
+ if (entry.traceId !== null && entry.type === EntryType.Query) {
180
+ const bucket = byTrace.get(entry.traceId);
181
+ if (bucket)
182
+ bucket.push(entry);
183
+ else
184
+ byTrace.set(entry.traceId, [entry]);
185
+ }
186
+ }
187
+ // Throughput — reuse bucketTimeseries.
188
+ const overTime = bucketTimeseries(entries, windowStart, windowEnd, options.buckets);
189
+ const minutes = windowMs > 0 ? windowMs / 60_000 : 0;
190
+ const throughput = enabled('throughput')
191
+ ? {
192
+ total: entries.length,
193
+ perMinute: minutes > 0 ? entries.length / minutes : 0,
194
+ overTime,
195
+ }
196
+ : { total: 0, perMinute: 0, overTime };
197
+ // Request health — reuse summarizeStats for status + latency.
198
+ let requests = emptyRequestHealth();
199
+ if (enabled('requests')) {
200
+ const stats = summarizeStats({
201
+ entries: requestEntries,
202
+ type: EntryType.Request,
203
+ windowStart,
204
+ windowEnd,
205
+ windowMs,
206
+ buckets: options.buckets,
207
+ slowMs: options.slowMs,
208
+ truncated,
209
+ });
210
+ const status = stats.status ?? emptyRequestHealth().status;
211
+ const failed = status['4xx'] + status['5xx'];
212
+ requests = {
213
+ total: requestEntries.length,
214
+ errorRate: requestEntries.length > 0 ? failed / requestEntries.length : 0,
215
+ status,
216
+ ...(stats.latency !== undefined ? { latency: stats.latency } : {}),
217
+ };
218
+ }
219
+ // Cache hit ratio — reuse summarizeStats' cache aggregate.
220
+ let cache;
221
+ if (enabled('cache') && cacheEntries.length > 0) {
222
+ const stats = summarizeStats({
223
+ entries: cacheEntries,
224
+ type: EntryType.Cache,
225
+ windowStart,
226
+ windowEnd,
227
+ windowMs,
228
+ buckets: options.buckets,
229
+ slowMs: options.slowMs,
230
+ truncated,
231
+ });
232
+ cache = stats.cache;
233
+ }
234
+ // Top exceptions — reuse summarizeStats' exception grouping.
235
+ let topExceptions = [];
236
+ if (enabled('exceptions') && exceptionEntries.length > 0) {
237
+ const stats = summarizeStats({
238
+ entries: exceptionEntries,
239
+ type: EntryType.Exception,
240
+ windowStart,
241
+ windowEnd,
242
+ windowMs,
243
+ buckets: options.buckets,
244
+ slowMs: options.slowMs,
245
+ truncated,
246
+ topExceptions: options.topN,
247
+ });
248
+ topExceptions = (stats.exceptions ?? []).map((group) => ({
249
+ key: group.key,
250
+ class: group.class,
251
+ message: group.message,
252
+ count: group.count,
253
+ lastSeen: group.lastAt.toISOString(),
254
+ }));
255
+ }
256
+ // Slowest entries across all types.
257
+ const slowest = enabled('slowest')
258
+ ? slowCandidates
259
+ .slice()
260
+ .sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0) || a.id.localeCompare(b.id))
261
+ .slice(0, options.topN)
262
+ .map((entry) => ({
263
+ id: entry.id,
264
+ type: entry.type,
265
+ durationMs: entry.durationMs,
266
+ label: truncate(labelFrom(entry)),
267
+ traceId: entry.traceId,
268
+ }))
269
+ : [];
270
+ // Slow hotspots — reuse percentile via toHotspots.
271
+ const slowRoutes = enabled('slowRoutes')
272
+ ? toHotspots(routeDurations, options, options.slowRouteMs)
273
+ : [];
274
+ const slowOutgoing = enabled('slowOutgoing')
275
+ ? toHotspots(outgoingDurations, options, options.slowRouteMs)
276
+ : [];
277
+ // Jobs are not gated by the slow-ms threshold — the slowest jobs always matter.
278
+ const slowJobs = enabled('slowJobs') ? toHotspots(jobDurations, options, 0) : [];
279
+ // N+1 hotspots — reuse detectNPlusOne per trace, aggregated across traces.
280
+ let nPlusOne = [];
281
+ if (enabled('nPlusOne')) {
282
+ const hotspots = new Map();
283
+ const familyDurationInTrace = new Map();
284
+ for (const [traceId, traceEntries] of byTrace) {
285
+ familyDurationInTrace.clear();
286
+ for (const entry of traceEntries) {
287
+ if (entry.familyHash !== null && typeof entry.durationMs === 'number') {
288
+ familyDurationInTrace.set(entry.familyHash, (familyDurationInTrace.get(entry.familyHash) ?? 0) + entry.durationMs);
289
+ }
290
+ }
291
+ for (const insight of detectNPlusOne(traceEntries, options.nPlusOneThreshold)) {
292
+ const loopDuration = familyDurationInTrace.get(insight.familyHash) ?? 0;
293
+ const existing = hotspots.get(insight.familyHash);
294
+ if (existing) {
295
+ existing.traces += 1;
296
+ existing.total += insight.count;
297
+ existing.totalDurationMs += loopDuration;
298
+ if (insight.count > existing.perRequest) {
299
+ existing.perRequest = insight.count;
300
+ existing.sampleTraceId = traceId;
301
+ }
302
+ }
303
+ else {
304
+ hotspots.set(insight.familyHash, {
305
+ familyHash: insight.familyHash,
306
+ sql: insight.sql,
307
+ perRequest: insight.count,
308
+ traces: 1,
309
+ total: insight.count,
310
+ totalDurationMs: loopDuration,
311
+ sampleTraceId: traceId,
312
+ });
313
+ }
314
+ }
315
+ }
316
+ nPlusOne = [...hotspots.values()]
317
+ .sort((a, b) => b.totalDurationMs - a.totalDurationMs ||
318
+ b.total - a.total ||
319
+ b.traces - a.traces ||
320
+ a.familyHash.localeCompare(b.familyHash))
321
+ .slice(0, options.topN)
322
+ .map((h) => ({
323
+ familyHash: h.familyHash,
324
+ sql: truncate(h.sql),
325
+ perRequest: h.perRequest,
326
+ traces: h.traces,
327
+ total: h.total,
328
+ totalDurationMs: h.totalDurationMs,
329
+ sampleTraceId: h.sampleTraceId,
330
+ }));
331
+ }
332
+ // Load by user — top users by total request time.
333
+ const loadByUser = enabled('loadByUser')
334
+ ? [...userLoad.entries()]
335
+ .map(([user, load]) => ({ user, count: load.count, totalDurationMs: load.totalDurationMs }))
336
+ .sort((a, b) => b.totalDurationMs - a.totalDurationMs ||
337
+ b.count - a.count ||
338
+ a.user.localeCompare(b.user))
339
+ .slice(0, options.topN)
340
+ : [];
341
+ return {
342
+ windowStart: windowStart.toISOString(),
343
+ windowEnd: windowEnd.toISOString(),
344
+ windowMs,
345
+ counts,
346
+ throughput,
347
+ requests,
348
+ ...(cache !== undefined ? { cache } : {}),
349
+ slowest,
350
+ slowRoutes,
351
+ slowOutgoing,
352
+ slowJobs,
353
+ topExceptions,
354
+ nPlusOne,
355
+ loadByUser,
356
+ scanned: entries.length,
357
+ truncated,
358
+ };
359
+ }
360
+ /**
361
+ * Read-only Pulse rollup over a {@link TelescopeStore}. Storage-agnostic — it
362
+ * reads the store (the source of truth) on demand through the newest-first
363
+ * {@link TelescopeStore.list} contract, never a specific provider, and never a
364
+ * live counter. Analysis reads ALREADY-redacted stored entries, so redaction is
365
+ * never bypassed. Idiomatic Adonis: a plain class, no DI decorators (constructed
366
+ * by the provider / {@link TelescopeService}, exactly like {@link import('./metrics_service.js').MetricsService}).
367
+ */
368
+ export class PulseService {
369
+ store;
370
+ windowMs;
371
+ scanCap;
372
+ base;
373
+ constructor(store, options = {}) {
374
+ this.store = store;
375
+ this.windowMs = positiveOr(options.windowMs, DEFAULT_WINDOW_MS);
376
+ this.scanCap = positiveOr(options.scanCap, DEFAULT_SCAN_CAP);
377
+ this.base = {
378
+ topN: positiveOr(options.topN, DEFAULT_TOP_N),
379
+ buckets: clampBuckets(options.buckets),
380
+ slowMs: options.slowMs ?? DEFAULT_SLOW_MS,
381
+ slowRouteMs: options.slowRouteMs ?? DEFAULT_SLOW_ROUTE_MS,
382
+ hotspotMinCount: options.hotspotMinCount ?? DEFAULT_HOTSPOT_MIN_COUNT,
383
+ nPlusOneThreshold: positiveOr(options.nPlusOneThreshold, DEFAULT_NPLUSONE_THRESHOLD),
384
+ cards: options.cards ? new Set(options.cards) : new Set(PULSE_CARDS),
385
+ };
386
+ }
387
+ /**
388
+ * Build the health snapshot over the trailing window. `query.windowMs` /
389
+ * `query.topN` override the configured defaults for a single call.
390
+ */
391
+ async getHealth(query = {}) {
392
+ const windowMs = query.windowMs ?? this.windowMs;
393
+ if (!Number.isFinite(windowMs) || windowMs <= 0) {
394
+ throw new RangeError(`windowMs must be a positive, finite number (got ${windowMs}).`);
395
+ }
396
+ const windowEnd = new Date();
397
+ const windowStart = new Date(windowEnd.getTime() - windowMs);
398
+ const listQuery = { after: windowStart, limit: this.scanCap };
399
+ const entries = await this.store.list(listQuery);
400
+ const truncated = entries.length >= this.scanCap;
401
+ const options = {
402
+ ...this.base,
403
+ topN: query.topN ?? this.base.topN,
404
+ };
405
+ return summarizePulse(entries, windowStart, windowEnd, options, truncated);
406
+ }
407
+ }
408
+ function positiveOr(value, fallback) {
409
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
410
+ }
411
+ function clampBuckets(buckets) {
412
+ return Math.min(MAX_BUCKETS, Math.max(1, Math.floor(buckets ?? DEFAULT_BUCKETS)));
413
+ }
414
+ //# sourceMappingURL=pulse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pulse.js","sourceRoot":"","sources":["../../../src/metrics/pulse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import type { ResolvedTelescopeConfig } from './define_config.js';
2
+ /** The resolved overload-guard slice of the telescope config. */
3
+ export type ResolvedOverloadConfig = ResolvedTelescopeConfig['overload'];
4
+ /**
5
+ * The pause surface the guard toggles: the ingestion path is expected to become a
6
+ * dropping no-op while `isPaused` is `true`. The provider backs this with the
7
+ * global runtime `paused` flag the request middleware and watcher `safeRecord`
8
+ * honour, so a pause sheds ingestion across every recording entry point at once.
9
+ */
10
+ export interface PauseController {
11
+ pause(): void;
12
+ resume(): void;
13
+ readonly isPaused: boolean;
14
+ }
15
+ /** The minimal `perf_hooks` monitor surface the guard reads — injectable for tests. */
16
+ export interface EventLoopDelayMonitor {
17
+ enable(): void;
18
+ disable(): void;
19
+ reset(): void;
20
+ /** Delay at the given percentile, in nanoseconds. */
21
+ percentile(percentile: number): number;
22
+ }
23
+ /** The sink for the guard's operational messages. Defaults to `console`. */
24
+ export interface OverloadLogger {
25
+ warn(message: string): void;
26
+ log?(message: string): void;
27
+ }
28
+ /** Constructor seams for {@link OverloadGuard} (monitor factory + logger). */
29
+ export interface OverloadGuardDeps {
30
+ /** Build the event-loop-delay monitor. Defaults to `perf_hooks`; `null` ⇒ no-op. */
31
+ monitorFactory?: () => EventLoopDelayMonitor | null;
32
+ logger?: OverloadLogger;
33
+ }
34
+ /**
35
+ * Overload protection. Samples the process event-loop delay histogram
36
+ * (`perf_hooks.monitorEventLoopDelay`) on an interval and PAUSES ingestion via the
37
+ * {@link PauseController} when the p99 lag crosses the configured threshold,
38
+ * resuming once it recovers — so a telescope under load can never amplify an
39
+ * incident.
40
+ *
41
+ * A startup grace (default ~5s) discards the first measurement windows so the
42
+ * synchronous boot stall (provider wiring, migrations) never trips the guard on a
43
+ * transient. Degrades to a no-op when `perf_hooks.monitorEventLoopDelay` is
44
+ * unavailable or `overload.enabled` is `false`. The sampling interval is unref'd so
45
+ * it never keeps the host's event loop alive.
46
+ *
47
+ * Ported from `nestjs-telescope`'s `TelescopeOverloadGuard`; the only adaptation is
48
+ * that it pauses a {@link PauseController} (the runtime flag) rather than a NestJS
49
+ * Recorder, matching this package's DI-free ingestion path.
50
+ */
51
+ export declare class OverloadGuard {
52
+ private readonly controller;
53
+ private readonly maxLagMs;
54
+ /**
55
+ * Number of leading sample windows still to discard for the startup grace.
56
+ * Counts down on every sample taken while warming up, after which the guard
57
+ * judges normally.
58
+ */
59
+ private warmupSamplesRemaining;
60
+ private monitor;
61
+ private timer;
62
+ private readonly monitorFactory;
63
+ private readonly logger;
64
+ constructor(config: ResolvedOverloadConfig, controller: PauseController, deps?: OverloadGuardDeps);
65
+ /**
66
+ * Arm the guard: start the monitor + sampling timer. A no-op when protection is
67
+ * off, already started, or `perf_hooks` lacks the monitor.
68
+ */
69
+ start(): void;
70
+ /** Stop sampling and disable the monitor (called by the provider at shutdown). */
71
+ stop(): void;
72
+ /** Run exactly one sampling tick. Exposed so tests can drive the guard deterministically. */
73
+ sampleOnce(): void;
74
+ /** Read the rolling p99 lag and pause/resume ingestion around the threshold. */
75
+ private sample;
76
+ }
77
+ //# sourceMappingURL=overload_guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overload_guard.d.ts","sourceRoot":"","sources":["../../src/overload_guard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAMlE,iEAAiE;AACjE,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IACf,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,uFAAuF;AACvF,MAAM,WAAW,qBAAqB;IACpC,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;IAChB,KAAK,IAAI,IAAI,CAAC;IACd,qDAAqD;IACrD,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;CACxC;AAED,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,oFAAoF;IACpF,cAAc,CAAC,EAAE,MAAM,qBAAqB,GAAG,IAAI,CAAC;IACpD,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,aAAa;IAetB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAd7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC;;;;OAIG;IACH,OAAO,CAAC,sBAAsB,CAAS;IACvC,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqC;IACpE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;gBAGtC,MAAM,EAAE,sBAAsB,EACb,UAAU,EAAE,eAAe,EAC5C,IAAI,GAAE,iBAAsB;IAU9B;;;OAGG;IACH,KAAK,IAAI,IAAI;IAQb,kFAAkF;IAClF,IAAI,IAAI,IAAI;IASZ,6FAA6F;IAC7F,UAAU,IAAI,IAAI;IAIlB,gFAAgF;IAChF,OAAO,CAAC,MAAM;CAmCf"}
@@ -0,0 +1,134 @@
1
+ import * as perfHooks from 'node:perf_hooks';
2
+ /** How often the guard samples the lag histogram. */
3
+ const SAMPLE_INTERVAL_MS = 1_000;
4
+ const NS_PER_MS = 1e6;
5
+ /**
6
+ * Overload protection. Samples the process event-loop delay histogram
7
+ * (`perf_hooks.monitorEventLoopDelay`) on an interval and PAUSES ingestion via the
8
+ * {@link PauseController} when the p99 lag crosses the configured threshold,
9
+ * resuming once it recovers — so a telescope under load can never amplify an
10
+ * incident.
11
+ *
12
+ * A startup grace (default ~5s) discards the first measurement windows so the
13
+ * synchronous boot stall (provider wiring, migrations) never trips the guard on a
14
+ * transient. Degrades to a no-op when `perf_hooks.monitorEventLoopDelay` is
15
+ * unavailable or `overload.enabled` is `false`. The sampling interval is unref'd so
16
+ * it never keeps the host's event loop alive.
17
+ *
18
+ * Ported from `nestjs-telescope`'s `TelescopeOverloadGuard`; the only adaptation is
19
+ * that it pauses a {@link PauseController} (the runtime flag) rather than a NestJS
20
+ * Recorder, matching this package's DI-free ingestion path.
21
+ */
22
+ export class OverloadGuard {
23
+ controller;
24
+ maxLagMs;
25
+ /**
26
+ * Number of leading sample windows still to discard for the startup grace.
27
+ * Counts down on every sample taken while warming up, after which the guard
28
+ * judges normally.
29
+ */
30
+ warmupSamplesRemaining;
31
+ monitor = null;
32
+ timer = null;
33
+ monitorFactory;
34
+ logger;
35
+ constructor(config, controller, deps = {}) {
36
+ this.controller = controller;
37
+ this.maxLagMs = config.enabled ? config.maxEventLoopLagMs : null;
38
+ // Round UP so a sub-interval grace still discards at least one window (the one
39
+ // that holds the bootstrap stall). 0ms ⇒ 0 windows ⇒ no grace.
40
+ this.warmupSamplesRemaining = Math.ceil(config.startupGraceMs / SAMPLE_INTERVAL_MS);
41
+ this.monitorFactory = deps.monitorFactory ?? startMonitor;
42
+ this.logger = deps.logger ?? console;
43
+ }
44
+ /**
45
+ * Arm the guard: start the monitor + sampling timer. A no-op when protection is
46
+ * off, already started, or `perf_hooks` lacks the monitor.
47
+ */
48
+ start() {
49
+ if (this.maxLagMs === null || this.timer)
50
+ return;
51
+ this.monitor = this.monitorFactory();
52
+ if (this.monitor === null)
53
+ return;
54
+ this.timer = setInterval(() => this.sample(), SAMPLE_INTERVAL_MS);
55
+ this.timer.unref?.();
56
+ }
57
+ /** Stop sampling and disable the monitor (called by the provider at shutdown). */
58
+ stop() {
59
+ if (this.timer) {
60
+ clearInterval(this.timer);
61
+ this.timer = null;
62
+ }
63
+ this.monitor?.disable();
64
+ this.monitor = null;
65
+ }
66
+ /** Run exactly one sampling tick. Exposed so tests can drive the guard deterministically. */
67
+ sampleOnce() {
68
+ this.sample();
69
+ }
70
+ /** Read the rolling p99 lag and pause/resume ingestion around the threshold. */
71
+ sample() {
72
+ const monitor = this.monitor;
73
+ const maxLagMs = this.maxLagMs;
74
+ if (monitor === null || maxLagMs === null)
75
+ return;
76
+ let p99Ms;
77
+ try {
78
+ p99Ms = monitor.percentile(99) / NS_PER_MS;
79
+ }
80
+ catch {
81
+ return; // A misbehaving monitor must never crash the host.
82
+ }
83
+ // Reset the histogram each cycle so the decision reflects the RECENT window (a
84
+ // per-process accumulation would never recover once lag spiked).
85
+ monitor.reset();
86
+ // Startup grace: discard the leading window(s) — they carry the boot stall, not
87
+ // live load. Reset above still ran, so the NEXT judged window starts clean.
88
+ if (this.warmupSamplesRemaining > 0) {
89
+ this.warmupSamplesRemaining -= 1;
90
+ return;
91
+ }
92
+ if (p99Ms >= maxLagMs) {
93
+ if (!this.controller.isPaused) {
94
+ this.logger.warn(`Event-loop p99 lag ${p99Ms.toFixed(0)}ms >= ${maxLagMs}ms — pausing Telescope capture.`);
95
+ this.controller.pause();
96
+ }
97
+ }
98
+ else if (this.controller.isPaused) {
99
+ const info = this.logger.log ?? this.logger.warn;
100
+ info.call(this.logger, `Event-loop p99 lag ${p99Ms.toFixed(0)}ms recovered — resuming Telescope capture.`);
101
+ this.controller.resume();
102
+ }
103
+ }
104
+ }
105
+ /** Start an enabled event-loop-delay monitor, or `null` if perf_hooks lacks it. */
106
+ function startMonitor() {
107
+ const factory = perfHooks.monitorEventLoopDelay;
108
+ if (typeof factory !== 'function')
109
+ return null;
110
+ try {
111
+ const monitor = factory();
112
+ if (!isEventLoopDelayMonitor(monitor))
113
+ return null;
114
+ monitor.enable();
115
+ return monitor;
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ /** Structural guard: confirms a value exposes the monitor surface we read. */
122
+ function isEventLoopDelayMonitor(value) {
123
+ if (typeof value !== 'object' || value === null)
124
+ return false;
125
+ if (!('enable' in value) || !('disable' in value))
126
+ return false;
127
+ if (!('reset' in value) || !('percentile' in value))
128
+ return false;
129
+ return (typeof value.enable === 'function' &&
130
+ typeof value.disable === 'function' &&
131
+ typeof value.reset === 'function' &&
132
+ typeof value.percentile === 'function');
133
+ }
134
+ //# sourceMappingURL=overload_guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overload_guard.js","sourceRoot":"","sources":["../../src/overload_guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAG7C,qDAAqD;AACrD,MAAM,kBAAkB,GAAG,KAAK,CAAC;AACjC,MAAM,SAAS,GAAG,GAAG,CAAC;AAuCtB;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,aAAa;IAeL;IAdF,QAAQ,CAAgB;IACzC;;;;OAIG;IACK,sBAAsB,CAAS;IAC/B,OAAO,GAAiC,IAAI,CAAC;IAC7C,KAAK,GAA0C,IAAI,CAAC;IAC3C,cAAc,CAAqC;IACnD,MAAM,CAAiB;IAExC,YACE,MAA8B,EACb,UAA2B,EAC5C,OAA0B,EAAE;QADX,eAAU,GAAV,UAAU,CAAiB;QAG5C,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC;QACjE,+EAA+E;QAC/E,+DAA+D;QAC/D,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,GAAG,kBAAkB,CAAC,CAAC;QACpF,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,YAAY,CAAC;QAC1D,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC;IACvC,CAAC;IAED;;;OAGG;IACH,KAAK;QACH,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACjD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;YAAE,OAAO;QAClC,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC;QAClE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IACvB,CAAC;IAED,kFAAkF;IAClF,IAAI;QACF,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,6FAA6F;IAC7F,UAAU;QACR,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,gFAAgF;IACxE,MAAM;QACZ,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,OAAO,KAAK,IAAI,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO;QAClD,IAAI,KAAa,CAAC;QAClB,IAAI,CAAC;YACH,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,mDAAmD;QAC7D,CAAC;QACD,+EAA+E;QAC/E,iEAAiE;QACjE,OAAO,CAAC,KAAK,EAAE,CAAC;QAChB,gFAAgF;QAChF,4EAA4E;QAC5E,IAAI,IAAI,CAAC,sBAAsB,GAAG,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC,sBAAsB,IAAI,CAAC,CAAC;YACjC,OAAO;QACT,CAAC;QACD,IAAI,KAAK,IAAI,QAAQ,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;gBAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,sBAAsB,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,QAAQ,iCAAiC,CACzF,CAAC;gBACF,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;YACpC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YACjD,IAAI,CAAC,IAAI,CACP,IAAI,CAAC,MAAM,EACX,sBAAsB,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,4CAA4C,CACnF,CAAC;YACF,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC;CACF;AAED,mFAAmF;AACnF,SAAS,YAAY;IACnB,MAAM,OAAO,GAAY,SAAS,CAAC,qBAAqB,CAAC;IACzD,IAAI,OAAO,OAAO,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,CAAC;QACH,MAAM,OAAO,GAAY,OAAO,EAAE,CAAC;QACnC,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QACnD,OAAO,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,SAAS,uBAAuB,CAAC,KAAc;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAChE,IAAI,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,YAAY,IAAI,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAClE,OAAO,CACL,OAAO,KAAK,CAAC,MAAM,KAAK,UAAU;QAClC,OAAO,KAAK,CAAC,OAAO,KAAK,UAAU;QACnC,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU;QACjC,OAAO,KAAK,CAAC,UAAU,KAAK,UAAU,CACvC,CAAC;AACJ,CAAC"}