adonisjs-server-stats 1.10.0 → 1.10.3

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 (210) hide show
  1. package/README.md +23 -14
  2. package/dist/core/config-utils.d.ts +8 -0
  3. package/dist/core/constants.d.ts +4 -0
  4. package/dist/core/dashboard-data-controller.d.ts +16 -0
  5. package/dist/core/dashboard-data-helpers.d.ts +12 -0
  6. package/dist/core/debug-data-controller.d.ts +4 -0
  7. package/dist/core/define-config-helpers.d.ts +25 -0
  8. package/dist/core/feature-detect-helpers.d.ts +36 -0
  9. package/dist/core/formatters-helpers.d.ts +23 -0
  10. package/dist/core/index.js +594 -509
  11. package/dist/core/log-utils-helpers.d.ts +13 -0
  12. package/dist/core/metrics.d.ts +3 -28
  13. package/dist/core/pagination.d.ts +0 -9
  14. package/dist/core/server-stats-controller.d.ts +6 -0
  15. package/dist/core/transmit-helpers.d.ts +7 -0
  16. package/dist/core/types-dashboard.d.ts +178 -0
  17. package/dist/core/types-diagnostics.d.ts +85 -0
  18. package/dist/core/types.d.ts +10 -442
  19. package/dist/react/{CacheSection-UCMptWyn.js → CacheSection-baMZotSn.js} +2 -2
  20. package/dist/react/CacheTab-2cw_rMzj.js +117 -0
  21. package/dist/react/{ConfigSection-DfFd-WRq.js → ConfigSection-DGgqjAal.js} +1 -1
  22. package/dist/react/{ConfigTab-Bdg8YMer.js → ConfigTab-H3OnYqmK.js} +1 -1
  23. package/dist/react/CustomPaneTab-B6r7ha0u.js +98 -0
  24. package/dist/react/{EmailsSection-CM7stSyh.js → EmailsSection-C-UZISG-.js} +2 -2
  25. package/dist/react/EmailsTab-DbK4Eobn.js +139 -0
  26. package/dist/react/{EventsSection-ByQ-9blq.js → EventsSection-C7RQW_LY.js} +2 -2
  27. package/dist/react/EventsTab-CfVr7AiM.js +57 -0
  28. package/dist/react/{FilterBar-DQRXpWrb.js → FilterBar-CQ7bD669.js} +15 -15
  29. package/dist/react/{JobsSection-DF3qEv9O.js → JobsSection-CQHNK_Ls.js} +2 -2
  30. package/dist/react/{JobsTab-BbrBWIOb.js → JobsTab-znzf6jzk.js} +54 -42
  31. package/dist/react/{LogsSection-DcFTZY7b.js → LogsSection-Dmm3rE2B.js} +9 -3
  32. package/dist/react/LogsTab-D8unMV5P.js +108 -0
  33. package/dist/react/{OverviewSection-C4T1ur51.js → OverviewSection-ABP9ueBo.js} +1 -1
  34. package/dist/react/{QueriesSection-PswteoF9.js → QueriesSection-CnmSkznA.js} +2 -2
  35. package/dist/react/{QueriesTab-osLUWd4L.js → QueriesTab-BQzcxEiW.js} +37 -40
  36. package/dist/react/{RelatedLogs-DFDOyUMr.js → RelatedLogs-3A8RuGKH.js} +15 -3
  37. package/dist/react/{RequestsSection-Nag30rEA.js → RequestsSection-kW79_M7k.js} +3 -3
  38. package/dist/react/{RoutesSection-BUSkM6PY.js → RoutesSection-BRhxrtjZ.js} +2 -2
  39. package/dist/react/RoutesTab-CpYH5lUw.js +68 -0
  40. package/dist/react/{TimelineTab-Covg5weo.js → TimelineTab-DjLR35Ce.js} +47 -53
  41. package/dist/react/index-CsImORX6.js +1121 -0
  42. package/dist/react/index.js +1 -1
  43. package/dist/react/react/components/{Dashboard/shared → shared}/FilterBar.d.ts +4 -3
  44. package/dist/react/react/hooks/useDashboardData.d.ts +4 -8
  45. package/dist/react/style.css +1 -1
  46. package/dist/src/collectors/app_collector.d.ts +0 -8
  47. package/dist/src/collectors/app_collector.js +45 -52
  48. package/dist/src/collectors/auto_detect.d.ts +0 -23
  49. package/dist/src/collectors/auto_detect.js +33 -55
  50. package/dist/src/collectors/db_pool_collector.d.ts +14 -16
  51. package/dist/src/collectors/db_pool_collector.js +72 -57
  52. package/dist/src/collectors/log_collector.d.ts +0 -47
  53. package/dist/src/collectors/log_collector.js +36 -65
  54. package/dist/src/collectors/queue_collector.d.ts +0 -20
  55. package/dist/src/collectors/queue_collector.js +60 -76
  56. package/dist/src/collectors/redis_collector.d.ts +10 -10
  57. package/dist/src/collectors/redis_collector.js +69 -66
  58. package/dist/src/config/deprecation_migration.d.ts +7 -0
  59. package/dist/src/config/deprecation_migration.js +201 -0
  60. package/dist/src/controller/debug_controller.d.ts +1 -1
  61. package/dist/src/controller/debug_controller.js +87 -81
  62. package/dist/src/dashboard/cache_handlers.d.ts +14 -0
  63. package/dist/src/dashboard/cache_handlers.js +52 -0
  64. package/dist/src/dashboard/chart_aggregator.d.ts +0 -7
  65. package/dist/src/dashboard/chart_aggregator.js +68 -50
  66. package/dist/src/dashboard/coalesce_cache.d.ts +25 -0
  67. package/dist/src/dashboard/coalesce_cache.js +47 -0
  68. package/dist/src/dashboard/dashboard_controller.d.ts +11 -37
  69. package/dist/src/dashboard/dashboard_controller.js +51 -544
  70. package/dist/src/dashboard/dashboard_page_assets.d.ts +17 -0
  71. package/dist/src/dashboard/dashboard_page_assets.js +51 -0
  72. package/dist/src/dashboard/dashboard_store.d.ts +19 -218
  73. package/dist/src/dashboard/dashboard_store.js +115 -1116
  74. package/dist/src/dashboard/dashboard_types.d.ts +83 -0
  75. package/dist/src/dashboard/dashboard_types.js +4 -0
  76. package/dist/src/dashboard/detail_queries.d.ts +19 -0
  77. package/dist/src/dashboard/detail_queries.js +98 -0
  78. package/dist/src/dashboard/email_event_builder.d.ts +8 -0
  79. package/dist/src/dashboard/email_event_builder.js +65 -0
  80. package/dist/src/dashboard/explain_query.d.ts +8 -0
  81. package/dist/src/dashboard/explain_query.js +22 -0
  82. package/dist/src/dashboard/filter_handlers.d.ts +23 -0
  83. package/dist/src/dashboard/filter_handlers.js +56 -0
  84. package/dist/src/dashboard/filtered_queries.d.ts +15 -0
  85. package/dist/src/dashboard/filtered_queries.js +155 -0
  86. package/dist/src/dashboard/flush_manager.d.ts +25 -0
  87. package/dist/src/dashboard/flush_manager.js +107 -0
  88. package/dist/src/dashboard/format_helpers.d.ts +126 -0
  89. package/dist/src/dashboard/format_helpers.js +140 -0
  90. package/dist/src/dashboard/inspector_manager.d.ts +36 -0
  91. package/dist/src/dashboard/inspector_manager.js +102 -0
  92. package/dist/src/dashboard/integrations/config_inspector.js +11 -13
  93. package/dist/src/dashboard/integrations/queue_inspector.d.ts +3 -3
  94. package/dist/src/dashboard/integrations/queue_inspector.js +13 -10
  95. package/dist/src/dashboard/jobs_handlers.d.ts +14 -0
  96. package/dist/src/dashboard/jobs_handlers.js +61 -0
  97. package/dist/src/dashboard/knex_factory.d.ts +18 -0
  98. package/dist/src/dashboard/knex_factory.js +91 -0
  99. package/dist/src/dashboard/migrator.js +30 -159
  100. package/dist/src/dashboard/migrator_tables.d.ts +19 -0
  101. package/dist/src/dashboard/migrator_tables.js +153 -0
  102. package/dist/src/dashboard/overview_queries.d.ts +66 -0
  103. package/dist/src/dashboard/overview_queries.js +155 -0
  104. package/dist/src/dashboard/overview_query_runners.d.ts +25 -0
  105. package/dist/src/dashboard/overview_query_runners.js +84 -0
  106. package/dist/src/dashboard/overview_store_queries.d.ts +40 -0
  107. package/dist/src/dashboard/overview_store_queries.js +69 -0
  108. package/dist/src/dashboard/paginate_helper.d.ts +12 -0
  109. package/dist/src/dashboard/paginate_helper.js +33 -0
  110. package/dist/src/dashboard/query_explain_handler.d.ts +10 -0
  111. package/dist/src/dashboard/query_explain_handler.js +80 -0
  112. package/dist/src/dashboard/read_queries.d.ts +32 -0
  113. package/dist/src/dashboard/read_queries.js +107 -0
  114. package/dist/src/dashboard/saved_filter_queries.d.ts +10 -0
  115. package/dist/src/dashboard/saved_filter_queries.js +24 -0
  116. package/dist/src/dashboard/storage_stats.d.ts +41 -0
  117. package/dist/src/dashboard/storage_stats.js +81 -0
  118. package/dist/src/dashboard/write_queue.d.ts +106 -0
  119. package/dist/src/dashboard/write_queue.js +225 -0
  120. package/dist/src/data/data_access.d.ts +2 -39
  121. package/dist/src/data/data_access.js +17 -193
  122. package/dist/src/data/data_access_helpers.d.ts +130 -0
  123. package/dist/src/data/data_access_helpers.js +212 -0
  124. package/dist/src/debug/debug_store.js +37 -32
  125. package/dist/src/debug/email_collector.d.ts +1 -10
  126. package/dist/src/debug/email_collector.js +78 -81
  127. package/dist/src/debug/event_collector.d.ts +0 -9
  128. package/dist/src/debug/event_collector.js +79 -62
  129. package/dist/src/debug/query_collector.js +23 -19
  130. package/dist/src/debug/route_inspector.d.ts +1 -5
  131. package/dist/src/debug/route_inspector.js +50 -51
  132. package/dist/src/debug/trace_collector.d.ts +9 -1
  133. package/dist/src/debug/trace_collector.js +21 -15
  134. package/dist/src/debug/types.d.ts +1 -1
  135. package/dist/src/define_config.d.ts +0 -65
  136. package/dist/src/define_config.js +93 -333
  137. package/dist/src/edge/client/dashboard.js +2 -2
  138. package/dist/src/edge/client/debug-panel-deferred.js +1 -1
  139. package/dist/src/edge/client/stats-bar.js +1 -1
  140. package/dist/src/edge/client-vue/dashboard.js +5 -5
  141. package/dist/src/edge/client-vue/debug-panel-deferred.js +3 -3
  142. package/dist/src/edge/client-vue/stats-bar.js +3 -3
  143. package/dist/src/edge/plugin.d.ts +0 -16
  144. package/dist/src/edge/plugin.js +57 -64
  145. package/dist/src/engine/request_metrics.d.ts +1 -0
  146. package/dist/src/engine/request_metrics.js +32 -42
  147. package/dist/src/middleware/request_tracking_middleware.d.ts +2 -8
  148. package/dist/src/middleware/request_tracking_middleware.js +65 -93
  149. package/dist/src/provider/auth_middleware_detector.d.ts +16 -0
  150. package/dist/src/provider/auth_middleware_detector.js +97 -0
  151. package/dist/src/provider/boot_helpers.d.ts +20 -0
  152. package/dist/src/provider/boot_helpers.js +91 -0
  153. package/dist/src/provider/boot_initializer.d.ts +28 -0
  154. package/dist/src/provider/boot_initializer.js +35 -0
  155. package/dist/src/provider/dashboard_init.d.ts +30 -0
  156. package/dist/src/provider/dashboard_init.js +138 -0
  157. package/dist/src/provider/dashboard_setup.d.ts +25 -0
  158. package/dist/src/provider/dashboard_setup.js +78 -0
  159. package/dist/src/provider/diagnostics.d.ts +134 -0
  160. package/dist/src/provider/diagnostics.js +127 -0
  161. package/dist/src/provider/email_bridge.d.ts +43 -0
  162. package/dist/src/provider/email_bridge.js +80 -0
  163. package/dist/src/provider/email_helpers.d.ts +13 -0
  164. package/dist/src/provider/email_helpers.js +68 -0
  165. package/dist/src/provider/pino_hook.d.ts +17 -0
  166. package/dist/src/provider/pino_hook.js +35 -0
  167. package/dist/src/provider/provider_helpers_extra.d.ts +47 -0
  168. package/dist/src/provider/provider_helpers_extra.js +177 -0
  169. package/dist/src/provider/server_stats_provider.d.ts +39 -85
  170. package/dist/src/provider/server_stats_provider.js +132 -951
  171. package/dist/src/provider/shutdown_helpers.d.ts +43 -0
  172. package/dist/src/provider/shutdown_helpers.js +70 -0
  173. package/dist/src/provider/toolbar_setup.d.ts +57 -0
  174. package/dist/src/provider/toolbar_setup.js +141 -0
  175. package/dist/src/routes/dashboard_routes.d.ts +14 -0
  176. package/dist/src/routes/dashboard_routes.js +197 -0
  177. package/dist/src/routes/debug_routes.d.ts +14 -0
  178. package/dist/src/routes/debug_routes.js +101 -0
  179. package/dist/src/routes/register_routes.d.ts +0 -78
  180. package/dist/src/routes/register_routes.js +22 -352
  181. package/dist/src/routes/stats_routes.d.ts +5 -0
  182. package/dist/src/routes/stats_routes.js +14 -0
  183. package/dist/src/styles/components.css +96 -0
  184. package/dist/src/styles/dashboard.css +8 -90
  185. package/dist/src/styles/debug-panel.css +1 -31
  186. package/dist/src/types.d.ts +305 -14
  187. package/dist/vue/{CacheSection-oFAJL3mo.js → CacheSection-ITqvpfH5.js} +1 -1
  188. package/dist/vue/{ConfigSection-BhfJ4KqL.js → ConfigSection-DTn3GslE.js} +1 -1
  189. package/dist/vue/{EmailsSection-BcNyhyHs.js → EmailsSection-DtLJ4XoS.js} +1 -1
  190. package/dist/vue/{EventsSection-r60Q5Lmu.js → EventsSection-BOYYz0Ty.js} +1 -1
  191. package/dist/vue/{JobsSection-BHL-hkQw.js → JobsSection-BazTxcJL.js} +1 -1
  192. package/dist/vue/{LogsSection-DRMGzJmg.js → LogsSection-D55PjTKX.js} +9 -3
  193. package/dist/vue/{LogsTab-Bg3o0Mm6.js → LogsTab-47zEK7jL.js} +4 -1
  194. package/dist/vue/{OverviewSection-CXh6Ja1B.js → OverviewSection-1uBKo-Tu.js} +1 -1
  195. package/dist/vue/{QueriesSection-IodIsCJ-.js → QueriesSection-rpoZ4ogd.js} +1 -1
  196. package/dist/vue/{RequestsSection-BPuMdmMc.js → RequestsSection-x7LvT0MC.js} +1 -1
  197. package/dist/vue/{RoutesSection-NKo3Rbq3.js → RoutesSection-CCD0zZqQ.js} +1 -1
  198. package/dist/vue/composables/useDashboardData.d.ts +12 -23
  199. package/dist/vue/index-C8MxnS7Q.js +1232 -0
  200. package/dist/vue/index.js +1 -1
  201. package/dist/vue/style.css +1 -1
  202. package/package.json +1 -1
  203. package/dist/react/CacheTab-CA8LB1J5.js +0 -123
  204. package/dist/react/CustomPaneTab-Bxtv_8Rw.js +0 -104
  205. package/dist/react/EmailsTab-BDhEiomM.js +0 -153
  206. package/dist/react/EventsTab-CMfY98Rl.js +0 -63
  207. package/dist/react/LogsTab-CicucmVk.js +0 -103
  208. package/dist/react/RoutesTab-DgVzd2PZ.js +0 -74
  209. package/dist/react/index-Cflz9Ebj.js +0 -1069
  210. package/dist/vue/index-Dtgysd26.js +0 -1229
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Shared types for the dashboard store and its extracted modules.
3
+ */
4
+ import type { Knex } from 'knex';
5
+ export interface EventEmitter {
6
+ on(event: string, handler: (...args: unknown[]) => void): void;
7
+ off(event: string, handler: (...args: unknown[]) => void): void;
8
+ }
9
+ export interface RequestInput {
10
+ method: string;
11
+ url: string;
12
+ statusCode: number;
13
+ duration: number;
14
+ spanCount?: number;
15
+ warningCount?: number;
16
+ }
17
+ export interface PersistRequestInput extends RequestInput {
18
+ queries: import('../debug/types.js').QueryRecord[];
19
+ trace: import('../debug/types.js').TraceRecord | null;
20
+ httpRequestId?: string | null;
21
+ }
22
+ export interface RequestFilters {
23
+ method?: string;
24
+ url?: string;
25
+ status?: number;
26
+ statusMin?: number;
27
+ statusMax?: number;
28
+ durationMin?: number;
29
+ durationMax?: number;
30
+ search?: string;
31
+ }
32
+ export interface QueryFilters {
33
+ method?: string;
34
+ model?: string;
35
+ connection?: string;
36
+ durationMin?: number;
37
+ durationMax?: number;
38
+ requestId?: number;
39
+ search?: string;
40
+ }
41
+ export interface EventFilters {
42
+ eventName?: string;
43
+ search?: string;
44
+ }
45
+ export interface EmailFilters {
46
+ search?: string;
47
+ from?: string;
48
+ to?: string;
49
+ subject?: string;
50
+ mailer?: string;
51
+ status?: string;
52
+ }
53
+ export interface LogFilters {
54
+ level?: string;
55
+ requestId?: string;
56
+ search?: string;
57
+ structured?: {
58
+ field: string;
59
+ operator: 'equals' | 'contains' | 'startsWith';
60
+ value: string;
61
+ }[];
62
+ }
63
+ export interface TraceFilters {
64
+ method?: string;
65
+ url?: string;
66
+ statusMin?: number;
67
+ statusMax?: number;
68
+ search?: string;
69
+ }
70
+ export interface PaginatedResult<T> {
71
+ data: T[];
72
+ total: number;
73
+ page: number;
74
+ perPage: number;
75
+ lastPage: number;
76
+ }
77
+ export interface PaginateOptions {
78
+ table: string;
79
+ page: number;
80
+ perPage: number;
81
+ applyFilters?: (query: Knex.QueryBuilder) => void;
82
+ filterKey?: string;
83
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Shared types for the dashboard store and its extracted modules.
3
+ */
4
+ export {};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Detail queries for individual request and trace records.
3
+ *
4
+ * Extracted from DashboardStore to reduce file length.
5
+ */
6
+ import type { Knex } from 'knex';
7
+ /**
8
+ * Query logs within a time window around a given timestamp.
9
+ * Used as a fallback when no precise request_id correlation exists.
10
+ */
11
+ export declare function queryLogsByTimeWindow(db: Knex, createdAt: string, duration: number): Promise<Record<string, unknown>[]>;
12
+ /**
13
+ * Fetch a single trace with full span data and correlated logs.
14
+ */
15
+ export declare function fetchTraceDetail(db: Knex, id: number): Promise<Record<string, unknown> | null>;
16
+ /**
17
+ * Fetch a single request with associated queries, events, trace, and logs.
18
+ */
19
+ export declare function fetchRequestDetail(db: Knex, id: number): Promise<Record<string, unknown> | null>;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Detail queries for individual request and trace records.
3
+ *
4
+ * Extracted from DashboardStore to reduce file length.
5
+ */
6
+ import { safeParseJson, safeParseJsonArray } from '../utils/json_helpers.js';
7
+ // ---------------------------------------------------------------------------
8
+ // Log time-window fallback
9
+ // ---------------------------------------------------------------------------
10
+ /**
11
+ * Query logs within a time window around a given timestamp.
12
+ * Used as a fallback when no precise request_id correlation exists.
13
+ */
14
+ export async function queryLogsByTimeWindow(db, createdAt, duration) {
15
+ const windowSec = Math.ceil(duration / 1000) + 2;
16
+ return db('server_stats_logs')
17
+ .where('created_at', '>=', db.raw(`datetime(?, '-${windowSec} seconds')`, [createdAt]))
18
+ .where('created_at', '<=', db.raw(`datetime(?, '+${windowSec} seconds')`, [createdAt]))
19
+ .orderBy('created_at', 'asc')
20
+ .limit(100);
21
+ }
22
+ // ---------------------------------------------------------------------------
23
+ // Trace detail
24
+ // ---------------------------------------------------------------------------
25
+ /**
26
+ * Fetch a single trace with full span data and correlated logs.
27
+ */
28
+ export async function fetchTraceDetail(db, id) {
29
+ const row = await db('server_stats_traces').where('id', id).first();
30
+ if (!row)
31
+ return null;
32
+ let logs = [];
33
+ let httpRequestId = null;
34
+ if (row.request_id) {
35
+ const linkedRequest = await db('server_stats_requests')
36
+ .where('id', row.request_id)
37
+ .select('http_request_id', 'created_at')
38
+ .first();
39
+ if (linkedRequest?.http_request_id) {
40
+ httpRequestId = linkedRequest.http_request_id;
41
+ logs = await db('server_stats_logs')
42
+ .where('request_id', linkedRequest.http_request_id)
43
+ .orderBy('created_at', 'asc');
44
+ }
45
+ }
46
+ if (logs.length === 0 && row.created_at) {
47
+ logs = await queryLogsByTimeWindow(db, row.created_at, row.total_duration || 0);
48
+ }
49
+ return {
50
+ ...row,
51
+ spans: safeParseJson(row.spans) ?? [],
52
+ warnings: safeParseJsonArray(row.warnings),
53
+ logs,
54
+ http_request_id: httpRequestId,
55
+ };
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Request detail
59
+ // ---------------------------------------------------------------------------
60
+ /**
61
+ * Fetch a single request with associated queries, events, trace, and logs.
62
+ */
63
+ export async function fetchRequestDetail(db, id) {
64
+ return db.transaction(async (trx) => {
65
+ const request = await trx('server_stats_requests').where('id', id).first();
66
+ if (!request)
67
+ return null;
68
+ const queries = await trx('server_stats_queries')
69
+ .where('request_id', id)
70
+ .orderBy('created_at', 'asc');
71
+ const events = await trx('server_stats_events')
72
+ .where('request_id', id)
73
+ .orderBy('created_at', 'asc');
74
+ const trace = await trx('server_stats_traces').where('request_id', id).first();
75
+ let logs = [];
76
+ if (request.http_request_id) {
77
+ logs = await trx('server_stats_logs')
78
+ .where('request_id', request.http_request_id)
79
+ .orderBy('created_at', 'asc');
80
+ }
81
+ if (logs.length === 0 && request.created_at) {
82
+ logs = await queryLogsByTimeWindow(trx, request.created_at, request.duration || 0);
83
+ }
84
+ return {
85
+ ...request,
86
+ queries,
87
+ events,
88
+ logs,
89
+ trace: trace
90
+ ? {
91
+ ...trace,
92
+ spans: safeParseJson(trace.spans) ?? [],
93
+ warnings: safeParseJsonArray(trace.warnings),
94
+ }
95
+ : null,
96
+ };
97
+ });
98
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Pure function to build an EmailRecord from an AdonisJS mail event.
3
+ *
4
+ * Extracted from DashboardStore.wireEventListeners to reduce
5
+ * cyclomatic complexity (the original had 28 due to many || chains).
6
+ */
7
+ import type { EmailRecord } from '../debug/types.js';
8
+ export declare function buildEmailRecordFromEvent(data: unknown, status: EmailRecord['status']): EmailRecord;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Pure function to build an EmailRecord from an AdonisJS mail event.
3
+ *
4
+ * Extracted from DashboardStore.wireEventListeners to reduce
5
+ * cyclomatic complexity (the original had 28 due to many || chains).
6
+ */
7
+ import { extractAddresses } from '../utils/mail_helpers.js';
8
+ /**
9
+ * Build an EmailRecord from raw event data emitted by AdonisJS mail events.
10
+ *
11
+ * Handles two shapes:
12
+ * 1. Standard: `{ message: { from, to, ... }, mailerName, response }`
13
+ * 2. Flat: `{ from, to, subject, ... }` (data IS the message)
14
+ */
15
+ function extractAddressFields(msg) {
16
+ return {
17
+ from: extractAddresses(msg?.from) || 'unknown',
18
+ to: extractAddresses(msg?.to) || 'unknown',
19
+ cc: extractAddresses(msg?.cc) || null,
20
+ bcc: extractAddresses(msg?.bcc) || null,
21
+ };
22
+ }
23
+ function extractContentFields(msg) {
24
+ return {
25
+ subject: extractSubject(msg),
26
+ html: extractString(msg?.html),
27
+ text: extractString(msg?.text),
28
+ attachmentCount: countAttachments(msg),
29
+ };
30
+ }
31
+ export function buildEmailRecordFromEvent(data, status) {
32
+ const d = (data ?? {});
33
+ const msg = extractMessage(d);
34
+ return {
35
+ ...extractAddressFields(msg),
36
+ ...extractContentFields(msg),
37
+ mailer: extractMailer(d),
38
+ status,
39
+ messageId: extractMessageId(d),
40
+ timestamp: Date.now(),
41
+ };
42
+ }
43
+ function extractMessage(d) {
44
+ return (d.message || d);
45
+ }
46
+ function extractSubject(msg) {
47
+ return msg?.subject || '(no subject)';
48
+ }
49
+ function extractString(value) {
50
+ return value || null;
51
+ }
52
+ function extractMailer(d) {
53
+ return d.mailerName || d.mailer || 'unknown';
54
+ }
55
+ function extractMessageId(d) {
56
+ const fromResponse = d.response?.messageId;
57
+ if (fromResponse)
58
+ return fromResponse;
59
+ return d.messageId || null;
60
+ }
61
+ function countAttachments(msg) {
62
+ if (!Array.isArray(msg?.attachments))
63
+ return 0;
64
+ return msg.attachments.length;
65
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * EXPLAIN query execution helper for the DashboardStore.
3
+ *
4
+ * Extracted from DashboardStore to reduce file length.
5
+ */
6
+ import { CoalesceCache } from './coalesce_cache.js';
7
+ import type { Knex } from 'knex';
8
+ export declare function executeExplain(db: Knex, cache: CoalesceCache, queryId: number, appDb: unknown): Promise<Record<string, unknown> | null>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * EXPLAIN query execution helper for the DashboardStore.
3
+ *
4
+ * Extracted from DashboardStore to reduce file length.
5
+ */
6
+ export function executeExplain(db, cache, queryId, appDb) {
7
+ return cache.coalesce('explain:' + queryId, async () => {
8
+ const row = await db('server_stats_queries').where('id', queryId).first();
9
+ if (!row)
10
+ return { error: 'Query not found' };
11
+ const sql = row.sql_text.trim();
12
+ if (!sql.toLowerCase().startsWith('select'))
13
+ return { error: 'EXPLAIN is only supported for SELECT queries' };
14
+ try {
15
+ const r = await appDb.rawQuery(`EXPLAIN ${sql}`);
16
+ return { plan: r.rows || r };
17
+ }
18
+ catch (err) {
19
+ return { error: err.message || 'EXPLAIN failed' };
20
+ }
21
+ });
22
+ }
@@ -0,0 +1,23 @@
1
+ import type { DashboardStore } from './dashboard_store.js';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
+ /**
4
+ * Handle GET /saved-filters — list all saved filters.
5
+ */
6
+ export declare function handleSavedFilters(dashboardStore: DashboardStore): Promise<{
7
+ filters: {
8
+ id: unknown;
9
+ name: unknown;
10
+ section: unknown;
11
+ filterConfig: unknown;
12
+ createdAt: unknown;
13
+ }[];
14
+ }>;
15
+ /**
16
+ * Handle POST /saved-filters — create a new saved filter.
17
+ * Returns the response payload or null if creation fails.
18
+ */
19
+ export declare function handleCreateSavedFilter(dashboardStore: DashboardStore, { request, response }: HttpContext): Promise<void>;
20
+ /**
21
+ * Handle DELETE /saved-filters/:id — delete a saved filter.
22
+ */
23
+ export declare function handleDeleteSavedFilter(dashboardStore: DashboardStore, { params, response }: HttpContext): Promise<void>;
@@ -0,0 +1,56 @@
1
+ import { safeParseJson } from '../utils/json_helpers.js';
2
+ /**
3
+ * Handle GET /saved-filters — list all saved filters.
4
+ */
5
+ export async function handleSavedFilters(dashboardStore) {
6
+ const filters = await dashboardStore.getSavedFilters();
7
+ return {
8
+ filters: filters.map((f) => ({
9
+ id: f.id,
10
+ name: f.name,
11
+ section: f.section,
12
+ filterConfig: safeParseJson(f.filter_config),
13
+ createdAt: f.created_at,
14
+ })),
15
+ };
16
+ }
17
+ /**
18
+ * Handle POST /saved-filters — create a new saved filter.
19
+ * Returns the response payload or null if creation fails.
20
+ */
21
+ export async function handleCreateSavedFilter(dashboardStore, { request, response }) {
22
+ if (!dashboardStore.isReady()) {
23
+ return response.serviceUnavailable({ error: 'Database not available' });
24
+ }
25
+ try {
26
+ const { name, section, filterConfig } = request.body();
27
+ if (!name || !section || !filterConfig) {
28
+ return response.badRequest({
29
+ error: 'Missing required fields: name, section, filterConfig',
30
+ });
31
+ }
32
+ const result = await dashboardStore.createSavedFilter(name, section, typeof filterConfig === 'string' ? safeParseJson(filterConfig) : filterConfig);
33
+ return result
34
+ ? response.json(result)
35
+ : response.serviceUnavailable({ error: 'Database not available' });
36
+ }
37
+ catch {
38
+ return response.internalServerError({ error: 'Failed to create filter' });
39
+ }
40
+ }
41
+ /**
42
+ * Handle DELETE /saved-filters/:id — delete a saved filter.
43
+ */
44
+ export async function handleDeleteSavedFilter(dashboardStore, { params, response }) {
45
+ if (!dashboardStore.isReady()) {
46
+ return response.serviceUnavailable({ error: 'Database not available' });
47
+ }
48
+ try {
49
+ return (await dashboardStore.deleteSavedFilter(Number(params.id)))
50
+ ? response.json({ success: true })
51
+ : response.notFound({ error: 'Filter not found' });
52
+ }
53
+ catch {
54
+ return response.internalServerError({ error: 'Failed to delete filter' });
55
+ }
56
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Filter application functions for paginated dashboard queries.
3
+ *
4
+ * Each `apply*Filters` function takes a Knex QueryBuilder and an
5
+ * optional filter object, adding WHERE clauses as needed.
6
+ * Extracting these from DashboardStore reduces per-function complexity.
7
+ */
8
+ import type { RequestFilters, QueryFilters, EventFilters, EmailFilters, LogFilters, TraceFilters } from './dashboard_types.js';
9
+ import type { Knex } from 'knex';
10
+ export declare function applyRequestFilters(query: Knex.QueryBuilder, filters: RequestFilters | undefined): void;
11
+ export declare function applyQueryFilters(query: Knex.QueryBuilder, filters: QueryFilters | undefined): void;
12
+ export declare function applyEventFilters(query: Knex.QueryBuilder, filters: EventFilters | undefined): void;
13
+ export declare function applyEmailFilters(query: Knex.QueryBuilder, filters: EmailFilters | undefined, excludeBody: boolean): void;
14
+ export declare function applyLogFilters(query: Knex.QueryBuilder, filters: LogFilters | undefined): void;
15
+ export declare function applyTraceFilters(query: Knex.QueryBuilder, filters: TraceFilters | undefined): void;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Filter application functions for paginated dashboard queries.
3
+ *
4
+ * Each `apply*Filters` function takes a Knex QueryBuilder and an
5
+ * optional filter object, adding WHERE clauses as needed.
6
+ * Extracting these from DashboardStore reduces per-function complexity.
7
+ */
8
+ // ---------------------------------------------------------------------------
9
+ // Request filters
10
+ // ---------------------------------------------------------------------------
11
+ export function applyRequestFilters(query, filters) {
12
+ if (!filters)
13
+ return;
14
+ if (filters.method)
15
+ query.where('method', filters.method);
16
+ if (filters.url)
17
+ query.where('url', 'like', `%${filters.url}%`);
18
+ if (filters.status)
19
+ query.where('status_code', filters.status);
20
+ if (filters.statusMin)
21
+ query.where('status_code', '>=', filters.statusMin);
22
+ if (filters.statusMax)
23
+ query.where('status_code', '<=', filters.statusMax);
24
+ if (filters.durationMin)
25
+ query.where('duration', '>=', filters.durationMin);
26
+ if (filters.durationMax)
27
+ query.where('duration', '<=', filters.durationMax);
28
+ if (filters.search) {
29
+ const term = `%${filters.search}%`;
30
+ query.where((qb) => {
31
+ qb.where('url', 'like', term).orWhere('method', 'like', term);
32
+ });
33
+ }
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // Query filters
37
+ // ---------------------------------------------------------------------------
38
+ export function applyQueryFilters(query, filters) {
39
+ if (!filters)
40
+ return;
41
+ if (filters.method)
42
+ query.where('method', filters.method);
43
+ if (filters.model)
44
+ query.where('model', filters.model);
45
+ if (filters.connection)
46
+ query.where('connection', filters.connection);
47
+ if (filters.durationMin)
48
+ query.where('duration', '>=', filters.durationMin);
49
+ if (filters.durationMax)
50
+ query.where('duration', '<=', filters.durationMax);
51
+ if (filters.requestId)
52
+ query.where('request_id', filters.requestId);
53
+ if (filters.search) {
54
+ const term = `%${filters.search}%`;
55
+ query.where((qb) => {
56
+ qb.where('sql_text', 'like', term)
57
+ .orWhere('model', 'like', term)
58
+ .orWhere('connection', 'like', term);
59
+ });
60
+ }
61
+ }
62
+ // ---------------------------------------------------------------------------
63
+ // Event filters
64
+ // ---------------------------------------------------------------------------
65
+ export function applyEventFilters(query, filters) {
66
+ if (!filters)
67
+ return;
68
+ if (filters.eventName)
69
+ query.where('event_name', 'like', `%${filters.eventName}%`);
70
+ if (filters.search)
71
+ query.where('event_name', 'like', `%${filters.search}%`);
72
+ }
73
+ // ---------------------------------------------------------------------------
74
+ // Email filters
75
+ // ---------------------------------------------------------------------------
76
+ export function applyEmailFilters(query, filters, excludeBody) {
77
+ if (filters) {
78
+ if (filters.search) {
79
+ const term = `%${filters.search}%`;
80
+ query.where((sub) => {
81
+ sub
82
+ .where('from_addr', 'like', term)
83
+ .orWhere('to_addr', 'like', term)
84
+ .orWhere('subject', 'like', term);
85
+ });
86
+ }
87
+ if (filters.from)
88
+ query.where('from_addr', 'like', `%${filters.from}%`);
89
+ if (filters.to)
90
+ query.where('to_addr', 'like', `%${filters.to}%`);
91
+ if (filters.subject)
92
+ query.where('subject', 'like', `%${filters.subject}%`);
93
+ if (filters.mailer)
94
+ query.where('mailer', filters.mailer);
95
+ if (filters.status)
96
+ query.where('status', filters.status);
97
+ }
98
+ if (excludeBody) {
99
+ query.select('id', 'from_addr', 'to_addr', 'cc', 'bcc', 'subject', 'mailer', 'status', 'message_id', 'attachment_count', 'created_at');
100
+ }
101
+ }
102
+ // ---------------------------------------------------------------------------
103
+ // Log filters
104
+ // ---------------------------------------------------------------------------
105
+ /** Operator-to-SQL pattern lookup for structured log filters. */
106
+ const STRUCTURED_OPERATORS = {
107
+ equals: (value) => value,
108
+ contains: (value) => `%${value}%`,
109
+ startsWith: (value) => `${value}%`,
110
+ };
111
+ /** Apply a single structured filter to a query. */
112
+ function applyStructuredFilter(query, sf) {
113
+ const patternFn = STRUCTURED_OPERATORS[sf.operator];
114
+ if (!patternFn)
115
+ return;
116
+ const jsonPath = `$.${sf.field}`;
117
+ const op = sf.operator === 'equals' ? '=' : 'LIKE';
118
+ query.whereRaw(`json_extract(data, ?) ${op} ?`, [jsonPath, patternFn(sf.value)]);
119
+ }
120
+ export function applyLogFilters(query, filters) {
121
+ if (!filters)
122
+ return;
123
+ if (filters.level)
124
+ query.where('level', filters.level);
125
+ if (filters.requestId)
126
+ query.where('request_id', filters.requestId);
127
+ if (filters.search)
128
+ query.where('message', 'like', `%${filters.search}%`);
129
+ if (filters.structured && filters.structured.length > 0) {
130
+ for (const sf of filters.structured) {
131
+ applyStructuredFilter(query, sf);
132
+ }
133
+ }
134
+ }
135
+ // ---------------------------------------------------------------------------
136
+ // Trace filters
137
+ // ---------------------------------------------------------------------------
138
+ export function applyTraceFilters(query, filters) {
139
+ if (!filters)
140
+ return;
141
+ if (filters.method)
142
+ query.where('method', filters.method);
143
+ if (filters.url)
144
+ query.where('url', 'like', `%${filters.url}%`);
145
+ if (filters.statusMin)
146
+ query.where('status_code', '>=', filters.statusMin);
147
+ if (filters.statusMax)
148
+ query.where('status_code', '<=', filters.statusMax);
149
+ if (filters.search) {
150
+ const term = `%${filters.search}%`;
151
+ query.where((qb) => {
152
+ qb.where('url', 'like', term).orWhere('method', 'like', term);
153
+ });
154
+ }
155
+ }
@@ -0,0 +1,25 @@
1
+ import type { EventRecord, EmailRecord } from '../debug/types.js';
2
+ import type { PersistRequestInput } from './dashboard_types.js';
3
+ import type { Knex } from 'knex';
4
+ export declare class FlushManager {
5
+ writeQueue: PersistRequestInput[];
6
+ pendingEvents: {
7
+ requestIndex: number;
8
+ events: EventRecord[];
9
+ }[];
10
+ pendingLogs: Record<string, unknown>[];
11
+ pendingEmails: EmailRecord[];
12
+ private flushTimer;
13
+ private flushing;
14
+ private db;
15
+ constructor(getDb: () => Knex | null);
16
+ persistRequest(input: PersistRequestInput, dashboardPath: string): void;
17
+ queueEvents(requestIndex: number, events: EventRecord[]): void;
18
+ recordLog(entry: Record<string, unknown>): void;
19
+ recordEmail(record: EmailRecord): void;
20
+ stop(): Promise<void>;
21
+ private backpressure;
22
+ private scheduleFlush;
23
+ flush(): Promise<void>;
24
+ private takeSnapshot;
25
+ }