adonisjs-server-stats 1.9.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 (227) 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 +596 -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/split-pane.d.ts +18 -0
  16. package/dist/core/trace-utils.d.ts +5 -0
  17. package/dist/core/transmit-helpers.d.ts +7 -0
  18. package/dist/core/types-dashboard.d.ts +178 -0
  19. package/dist/core/types-diagnostics.d.ts +85 -0
  20. package/dist/core/types.d.ts +11 -443
  21. package/dist/react/{CacheSection-xH75hwXu.js → CacheSection-baMZotSn.js} +2 -2
  22. package/dist/react/CacheTab-2cw_rMzj.js +117 -0
  23. package/dist/react/{ConfigSection-D8BO1Ry9.js → ConfigSection-DGgqjAal.js} +1 -1
  24. package/dist/react/{ConfigTab-CcN-tfjv.js → ConfigTab-H3OnYqmK.js} +1 -1
  25. package/dist/react/CustomPaneTab-B6r7ha0u.js +98 -0
  26. package/dist/react/{EmailsSection-BzlsTdPs.js → EmailsSection-C-UZISG-.js} +2 -2
  27. package/dist/react/EmailsTab-DbK4Eobn.js +139 -0
  28. package/dist/react/{EventsSection-CGQWiIdV.js → EventsSection-C7RQW_LY.js} +2 -2
  29. package/dist/react/EventsTab-CfVr7AiM.js +57 -0
  30. package/dist/react/{FilterBar-DQRXpWrb.js → FilterBar-CQ7bD669.js} +15 -15
  31. package/dist/react/{JobsSection-D7AHQmZi.js → JobsSection-CQHNK_Ls.js} +2 -2
  32. package/dist/react/{JobsTab-B3Lfdqed.js → JobsTab-znzf6jzk.js} +54 -42
  33. package/dist/react/{LogsSection-Cly1dpvS.js → LogsSection-Dmm3rE2B.js} +9 -3
  34. package/dist/react/LogsTab-D8unMV5P.js +108 -0
  35. package/dist/react/{OverviewSection-CkBGFEWq.js → OverviewSection-ABP9ueBo.js} +1 -1
  36. package/dist/react/{QueriesSection-CfCpnNUD.js → QueriesSection-CnmSkznA.js} +2 -2
  37. package/dist/react/{QueriesTab-DbBmAqzO.js → QueriesTab-BQzcxEiW.js} +37 -40
  38. package/dist/react/RelatedLogs-3A8RuGKH.js +52 -0
  39. package/dist/react/RequestsSection-kW79_M7k.js +341 -0
  40. package/dist/react/{RoutesSection-CRqF-cNM.js → RoutesSection-BRhxrtjZ.js} +2 -2
  41. package/dist/react/RoutesTab-CpYH5lUw.js +68 -0
  42. package/dist/react/TimelineTab-DjLR35Ce.js +214 -0
  43. package/dist/react/index-CsImORX6.js +1121 -0
  44. package/dist/react/index.js +1 -1
  45. package/dist/react/react/components/{Dashboard/shared → shared}/FilterBar.d.ts +4 -3
  46. package/dist/react/react/components/shared/RelatedLogs.d.ts +7 -0
  47. package/dist/react/react/hooks/useDashboardData.d.ts +4 -8
  48. package/dist/react/style.css +1 -1
  49. package/dist/src/collectors/app_collector.d.ts +0 -8
  50. package/dist/src/collectors/app_collector.js +45 -52
  51. package/dist/src/collectors/auto_detect.d.ts +0 -23
  52. package/dist/src/collectors/auto_detect.js +33 -55
  53. package/dist/src/collectors/db_pool_collector.d.ts +14 -16
  54. package/dist/src/collectors/db_pool_collector.js +72 -57
  55. package/dist/src/collectors/log_collector.d.ts +0 -47
  56. package/dist/src/collectors/log_collector.js +36 -65
  57. package/dist/src/collectors/queue_collector.d.ts +0 -20
  58. package/dist/src/collectors/queue_collector.js +60 -76
  59. package/dist/src/collectors/redis_collector.d.ts +10 -10
  60. package/dist/src/collectors/redis_collector.js +69 -66
  61. package/dist/src/config/deprecation_migration.d.ts +7 -0
  62. package/dist/src/config/deprecation_migration.js +201 -0
  63. package/dist/src/controller/debug_controller.d.ts +1 -1
  64. package/dist/src/controller/debug_controller.js +87 -81
  65. package/dist/src/dashboard/cache_handlers.d.ts +14 -0
  66. package/dist/src/dashboard/cache_handlers.js +52 -0
  67. package/dist/src/dashboard/chart_aggregator.d.ts +0 -7
  68. package/dist/src/dashboard/chart_aggregator.js +68 -50
  69. package/dist/src/dashboard/coalesce_cache.d.ts +25 -0
  70. package/dist/src/dashboard/coalesce_cache.js +47 -0
  71. package/dist/src/dashboard/dashboard_controller.d.ts +11 -37
  72. package/dist/src/dashboard/dashboard_controller.js +52 -532
  73. package/dist/src/dashboard/dashboard_page_assets.d.ts +17 -0
  74. package/dist/src/dashboard/dashboard_page_assets.js +51 -0
  75. package/dist/src/dashboard/dashboard_store.d.ts +19 -217
  76. package/dist/src/dashboard/dashboard_store.js +115 -1069
  77. package/dist/src/dashboard/dashboard_types.d.ts +83 -0
  78. package/dist/src/dashboard/dashboard_types.js +4 -0
  79. package/dist/src/dashboard/detail_queries.d.ts +19 -0
  80. package/dist/src/dashboard/detail_queries.js +98 -0
  81. package/dist/src/dashboard/email_event_builder.d.ts +8 -0
  82. package/dist/src/dashboard/email_event_builder.js +65 -0
  83. package/dist/src/dashboard/explain_query.d.ts +8 -0
  84. package/dist/src/dashboard/explain_query.js +22 -0
  85. package/dist/src/dashboard/filter_handlers.d.ts +23 -0
  86. package/dist/src/dashboard/filter_handlers.js +56 -0
  87. package/dist/src/dashboard/filtered_queries.d.ts +15 -0
  88. package/dist/src/dashboard/filtered_queries.js +155 -0
  89. package/dist/src/dashboard/flush_manager.d.ts +25 -0
  90. package/dist/src/dashboard/flush_manager.js +107 -0
  91. package/dist/src/dashboard/format_helpers.d.ts +126 -0
  92. package/dist/src/dashboard/format_helpers.js +140 -0
  93. package/dist/src/dashboard/inspector_manager.d.ts +36 -0
  94. package/dist/src/dashboard/inspector_manager.js +102 -0
  95. package/dist/src/dashboard/integrations/config_inspector.js +11 -13
  96. package/dist/src/dashboard/integrations/queue_inspector.d.ts +3 -3
  97. package/dist/src/dashboard/integrations/queue_inspector.js +13 -10
  98. package/dist/src/dashboard/jobs_handlers.d.ts +14 -0
  99. package/dist/src/dashboard/jobs_handlers.js +61 -0
  100. package/dist/src/dashboard/knex_factory.d.ts +18 -0
  101. package/dist/src/dashboard/knex_factory.js +91 -0
  102. package/dist/src/dashboard/migrator.js +30 -153
  103. package/dist/src/dashboard/migrator_tables.d.ts +19 -0
  104. package/dist/src/dashboard/migrator_tables.js +153 -0
  105. package/dist/src/dashboard/overview_queries.d.ts +66 -0
  106. package/dist/src/dashboard/overview_queries.js +155 -0
  107. package/dist/src/dashboard/overview_query_runners.d.ts +25 -0
  108. package/dist/src/dashboard/overview_query_runners.js +84 -0
  109. package/dist/src/dashboard/overview_store_queries.d.ts +40 -0
  110. package/dist/src/dashboard/overview_store_queries.js +69 -0
  111. package/dist/src/dashboard/paginate_helper.d.ts +12 -0
  112. package/dist/src/dashboard/paginate_helper.js +33 -0
  113. package/dist/src/dashboard/query_explain_handler.d.ts +10 -0
  114. package/dist/src/dashboard/query_explain_handler.js +80 -0
  115. package/dist/src/dashboard/read_queries.d.ts +32 -0
  116. package/dist/src/dashboard/read_queries.js +107 -0
  117. package/dist/src/dashboard/saved_filter_queries.d.ts +10 -0
  118. package/dist/src/dashboard/saved_filter_queries.js +24 -0
  119. package/dist/src/dashboard/storage_stats.d.ts +41 -0
  120. package/dist/src/dashboard/storage_stats.js +81 -0
  121. package/dist/src/dashboard/write_queue.d.ts +106 -0
  122. package/dist/src/dashboard/write_queue.js +225 -0
  123. package/dist/src/data/data_access.d.ts +6 -36
  124. package/dist/src/data/data_access.js +43 -188
  125. package/dist/src/data/data_access_helpers.d.ts +130 -0
  126. package/dist/src/data/data_access_helpers.js +212 -0
  127. package/dist/src/debug/debug_store.js +37 -32
  128. package/dist/src/debug/email_collector.d.ts +1 -10
  129. package/dist/src/debug/email_collector.js +78 -81
  130. package/dist/src/debug/event_collector.d.ts +0 -9
  131. package/dist/src/debug/event_collector.js +79 -62
  132. package/dist/src/debug/query_collector.js +23 -19
  133. package/dist/src/debug/route_inspector.d.ts +1 -5
  134. package/dist/src/debug/route_inspector.js +50 -51
  135. package/dist/src/debug/trace_collector.d.ts +10 -2
  136. package/dist/src/debug/trace_collector.js +23 -16
  137. package/dist/src/debug/types.d.ts +5 -1
  138. package/dist/src/define_config.d.ts +0 -65
  139. package/dist/src/define_config.js +93 -333
  140. package/dist/src/edge/client/dashboard.js +2 -2
  141. package/dist/src/edge/client/debug-panel-deferred.js +1 -1
  142. package/dist/src/edge/client/stats-bar.js +1 -1
  143. package/dist/src/edge/client-vue/dashboard.js +5 -5
  144. package/dist/src/edge/client-vue/debug-panel-deferred.js +3 -3
  145. package/dist/src/edge/client-vue/stats-bar.js +3 -3
  146. package/dist/src/edge/plugin.d.ts +0 -16
  147. package/dist/src/edge/plugin.js +57 -64
  148. package/dist/src/engine/request_metrics.d.ts +1 -0
  149. package/dist/src/engine/request_metrics.js +32 -42
  150. package/dist/src/middleware/request_tracking_middleware.d.ts +3 -8
  151. package/dist/src/middleware/request_tracking_middleware.js +65 -91
  152. package/dist/src/provider/auth_middleware_detector.d.ts +16 -0
  153. package/dist/src/provider/auth_middleware_detector.js +97 -0
  154. package/dist/src/provider/boot_helpers.d.ts +20 -0
  155. package/dist/src/provider/boot_helpers.js +91 -0
  156. package/dist/src/provider/boot_initializer.d.ts +28 -0
  157. package/dist/src/provider/boot_initializer.js +35 -0
  158. package/dist/src/provider/dashboard_init.d.ts +30 -0
  159. package/dist/src/provider/dashboard_init.js +138 -0
  160. package/dist/src/provider/dashboard_setup.d.ts +25 -0
  161. package/dist/src/provider/dashboard_setup.js +78 -0
  162. package/dist/src/provider/diagnostics.d.ts +134 -0
  163. package/dist/src/provider/diagnostics.js +127 -0
  164. package/dist/src/provider/email_bridge.d.ts +43 -0
  165. package/dist/src/provider/email_bridge.js +80 -0
  166. package/dist/src/provider/email_helpers.d.ts +13 -0
  167. package/dist/src/provider/email_helpers.js +68 -0
  168. package/dist/src/provider/pino_hook.d.ts +17 -0
  169. package/dist/src/provider/pino_hook.js +35 -0
  170. package/dist/src/provider/provider_helpers_extra.d.ts +47 -0
  171. package/dist/src/provider/provider_helpers_extra.js +177 -0
  172. package/dist/src/provider/server_stats_provider.d.ts +39 -85
  173. package/dist/src/provider/server_stats_provider.js +131 -936
  174. package/dist/src/provider/shutdown_helpers.d.ts +43 -0
  175. package/dist/src/provider/shutdown_helpers.js +70 -0
  176. package/dist/src/provider/toolbar_setup.d.ts +57 -0
  177. package/dist/src/provider/toolbar_setup.js +141 -0
  178. package/dist/src/routes/dashboard_routes.d.ts +14 -0
  179. package/dist/src/routes/dashboard_routes.js +197 -0
  180. package/dist/src/routes/debug_routes.d.ts +14 -0
  181. package/dist/src/routes/debug_routes.js +101 -0
  182. package/dist/src/routes/register_routes.d.ts +0 -78
  183. package/dist/src/routes/register_routes.js +22 -347
  184. package/dist/src/routes/stats_routes.d.ts +5 -0
  185. package/dist/src/routes/stats_routes.js +14 -0
  186. package/dist/src/styles/components.css +177 -0
  187. package/dist/src/styles/dashboard.css +8 -90
  188. package/dist/src/styles/debug-panel.css +10 -31
  189. package/dist/src/types.d.ts +306 -15
  190. package/dist/vue/{CacheSection-Cx-hj09X.js → CacheSection-ITqvpfH5.js} +1 -1
  191. package/dist/vue/{ConfigSection-CMXyryf6.js → ConfigSection-DTn3GslE.js} +1 -1
  192. package/dist/vue/{EmailsSection-DgKl9xGT.js → EmailsSection-DtLJ4XoS.js} +1 -1
  193. package/dist/vue/{EventsSection-BNMCAim1.js → EventsSection-BOYYz0Ty.js} +1 -1
  194. package/dist/vue/{JobsSection-CCMgMlxd.js → JobsSection-BazTxcJL.js} +1 -1
  195. package/dist/vue/{LogsSection-CvOnTxUu.js → LogsSection-D55PjTKX.js} +9 -3
  196. package/dist/vue/{LogsTab-Bg3o0Mm6.js → LogsTab-47zEK7jL.js} +4 -1
  197. package/dist/vue/{OverviewSection-CHgaKtUR.js → OverviewSection-1uBKo-Tu.js} +1 -1
  198. package/dist/vue/{QueriesSection-BnHRD98z.js → QueriesSection-rpoZ4ogd.js} +1 -1
  199. package/dist/vue/RelatedLogs.vue_vue_type_script_setup_true_lang-CB2_TzYW.js +84 -0
  200. package/dist/vue/RequestsSection-x7LvT0MC.js +401 -0
  201. package/dist/vue/{RoutesSection-BrceOcKQ.js → RoutesSection-CCD0zZqQ.js} +1 -1
  202. package/dist/vue/TimelineTab-zj5Z5OdT.js +338 -0
  203. package/dist/vue/components/Dashboard/sections/RequestsSection.vue.d.ts +4 -0
  204. package/dist/vue/components/DebugPanel/tabs/TimelineTab.vue.d.ts +4 -0
  205. package/dist/vue/components/{Dashboard/sections/TimelineSection.vue.d.ts → shared/RelatedLogs.vue.d.ts} +5 -6
  206. package/dist/vue/composables/useDashboardData.d.ts +12 -23
  207. package/dist/vue/index-C8MxnS7Q.js +1232 -0
  208. package/dist/vue/index.js +1 -1
  209. package/dist/vue/style.css +1 -1
  210. package/package.json +1 -1
  211. package/dist/react/CacheTab-DYmsZJJ1.js +0 -123
  212. package/dist/react/CustomPaneTab-D7_o3Ec6.js +0 -104
  213. package/dist/react/EmailsTab-Uh2CQY3o.js +0 -153
  214. package/dist/react/EventsTab-CC6DQzEm.js +0 -63
  215. package/dist/react/LogsTab-BbYK-iyh.js +0 -103
  216. package/dist/react/RequestsSection-Cb5a6MlT.js +0 -209
  217. package/dist/react/RoutesTab-Bwreij3e.js +0 -74
  218. package/dist/react/TimelineSection-B2y06kRE.js +0 -158
  219. package/dist/react/TimelineTab-6hthfdBB.js +0 -193
  220. package/dist/react/WaterfallChart-Cj73WdfM.js +0 -100
  221. package/dist/react/index-CecA4IdQ.js +0 -1075
  222. package/dist/react/react/components/Dashboard/sections/TimelineSection.d.ts +0 -8
  223. package/dist/vue/RequestsSection-B-uSlM0f.js +0 -243
  224. package/dist/vue/TimelineSection-CfvnA2Oo.js +0 -186
  225. package/dist/vue/TimelineTab-Db6lKKsD.js +0 -250
  226. package/dist/vue/WaterfallChart.vue_vue_type_script_setup_true_lang-tZ13cNj1.js +0 -118
  227. package/dist/vue/index-oLxS08vN.js +0 -1235
@@ -1,58 +1,4 @@
1
- import { readFile, stat } from 'node:fs/promises';
2
- import { parseAndEnrich } from '../log_stream/log_stream_service.js';
3
- // ---------------------------------------------------------------------------
4
- // Helpers
5
- // ---------------------------------------------------------------------------
6
- /**
7
- * Wrap a plain array in the standard {@link PaginatedResult} envelope.
8
- *
9
- * Applies optional client-side search filtering and pagination so that
10
- * ring-buffer results match the same shape returned by the dashboard store.
11
- */
12
- function wrapArray(items, opts, searchFn) {
13
- let filtered = items;
14
- // Client-side search
15
- if (opts.search && searchFn) {
16
- const term = opts.search.toLowerCase();
17
- filtered = filtered.filter((item) => searchFn(item, term));
18
- }
19
- const total = filtered.length;
20
- const page = opts.page ?? 1;
21
- // When perPage is not specified, return all items (backward compat for debug panel)
22
- const perPage = opts.perPage ?? (total || 1);
23
- const lastPage = Math.max(1, Math.ceil(total / perPage));
24
- const start = (page - 1) * perPage;
25
- const data = filtered.slice(start, start + perPage);
26
- return {
27
- data,
28
- meta: { total, page, perPage, lastPage },
29
- };
30
- }
31
- /**
32
- * Convert a flat {@link DashboardStore.PaginatedResult} to the nested
33
- * `{ data, meta }` shape used by the unified API.
34
- */
35
- function fromDashboardResult(result) {
36
- return {
37
- data: result.data,
38
- meta: {
39
- total: result.total,
40
- page: result.page,
41
- perPage: result.perPage,
42
- lastPage: result.lastPage,
43
- },
44
- };
45
- }
46
- function mapTraceListRow(row) {
47
- return {
48
- ...row,
49
- requestId: row.request_id ?? row.requestId,
50
- statusCode: row.status_code ?? row.statusCode,
51
- totalDuration: row.total_duration ?? row.totalDuration,
52
- spanCount: row.span_count ?? row.spanCount,
53
- createdAt: row.created_at ?? row.createdAt,
54
- };
55
- }
1
+ import { wrapArray, fromDashboardResult, mapTraceListRow, normalizeEmailRow, buildPaginationArgs, buildQueryFilters, buildEventFilters, buildEmailFilters, buildTraceFilters, buildLogFilters, stripEmailForList, stripTraceForList, filterRoutes, readLogFile, } from './data_access_helpers.js';
56
2
  // ---------------------------------------------------------------------------
57
3
  // DataAccess
58
4
  // ---------------------------------------------------------------------------
@@ -87,13 +33,8 @@ export class DataAccess {
87
33
  // =========================================================================
88
34
  async getQueries(opts = {}) {
89
35
  if (this.hasPersistence && opts.source !== 'memory') {
90
- const page = opts.page ?? 1;
91
- const perPage = opts.perPage ?? 50;
92
- const filters = {
93
- search: opts.search,
94
- ...opts.filters,
95
- };
96
- const result = await this.dashboardStore.getQueries(page, perPage, filters);
36
+ const { page, perPage } = buildPaginationArgs(opts);
37
+ const result = await this.dashboardStore.getQueries(page, perPage, buildQueryFilters(opts));
97
38
  return fromDashboardResult(result);
98
39
  }
99
40
  const queries = this.debugStore.queries.getQueries();
@@ -111,13 +52,8 @@ export class DataAccess {
111
52
  // =========================================================================
112
53
  async getEvents(opts = {}) {
113
54
  if (this.hasPersistence && opts.source !== 'memory') {
114
- const page = opts.page ?? 1;
115
- const perPage = opts.perPage ?? 50;
116
- const filters = {
117
- search: opts.search,
118
- ...opts.filters,
119
- };
120
- const result = await this.dashboardStore.getEvents(page, perPage, filters);
55
+ const { page, perPage } = buildPaginationArgs(opts);
56
+ const result = await this.dashboardStore.getEvents(page, perPage, buildEventFilters(opts));
121
57
  return fromDashboardResult(result);
122
58
  }
123
59
  const events = this.debugStore.events.getEvents();
@@ -136,42 +72,14 @@ export class DataAccess {
136
72
  */
137
73
  async getEmails(opts = {}) {
138
74
  if (this.hasPersistence && opts.source !== 'memory') {
139
- const page = opts.page ?? 1;
140
- const perPage = opts.perPage ?? 50;
141
- const filters = {
142
- search: opts.search,
143
- ...opts.filters,
144
- };
145
- const result = await this.dashboardStore.getEmails(page, perPage, filters, true);
75
+ const { page, perPage } = buildPaginationArgs(opts);
76
+ const result = await this.dashboardStore.getEmails(page, perPage, buildEmailFilters(opts), true);
146
77
  const normalized = fromDashboardResult(result);
147
- // Normalize SQLite column names to match the EmailRecord shape
148
- // so both memory and SQLite paths return consistent field names
149
- normalized.data = normalized.data.map((row) => ({
150
- ...row,
151
- from: row.from_addr ?? row.from ?? '',
152
- to: row.to_addr ?? row.to ?? '',
153
- messageId: row.message_id ?? row.messageId ?? null,
154
- attachmentCount: row.attachment_count ?? row.attachmentCount ?? 0,
155
- timestamp: row.created_at ?? row.timestamp ?? null,
156
- }));
78
+ normalized.data = normalized.data.map(normalizeEmailRow);
157
79
  return normalized;
158
80
  }
159
81
  const emails = this.debugStore.emails.getEmails();
160
- // Strip html/text from list response — build lightweight objects
161
- // without object-spread to avoid copying large HTML bodies
162
- const stripped = emails.map((e) => ({
163
- id: e.id,
164
- from: e.from,
165
- to: e.to,
166
- cc: e.cc,
167
- bcc: e.bcc,
168
- subject: e.subject,
169
- mailer: e.mailer,
170
- status: e.status,
171
- messageId: e.messageId,
172
- attachmentCount: e.attachmentCount,
173
- timestamp: e.timestamp,
174
- }));
82
+ const stripped = emails.map(stripEmailForList);
175
83
  return wrapArray(stripped, opts, (e, term) => {
176
84
  return (e.from.toLowerCase().includes(term) ||
177
85
  e.to.toLowerCase().includes(term) ||
@@ -201,13 +109,8 @@ export class DataAccess {
201
109
  */
202
110
  async getTraces(opts = {}) {
203
111
  if (this.hasPersistence && opts.source !== 'memory') {
204
- const page = opts.page ?? 1;
205
- const perPage = opts.perPage ?? 50;
206
- const filters = {
207
- search: opts.search,
208
- ...opts.filters,
209
- };
210
- const result = await this.dashboardStore.getTraces(page, perPage, filters);
112
+ const { page, perPage } = buildPaginationArgs(opts);
113
+ const result = await this.dashboardStore.getTraces(page, perPage, buildTraceFilters(opts));
211
114
  return {
212
115
  ...fromDashboardResult(result),
213
116
  data: result.data.map(mapTraceListRow),
@@ -217,18 +120,7 @@ export class DataAccess {
217
120
  return { data: [], meta: { total: 0, page: 1, perPage: opts.perPage ?? 50, lastPage: 1 } };
218
121
  }
219
122
  const traces = this.debugStore.traces.getTraces();
220
- // Strip spans from list view, add warningCount — build lightweight
221
- // objects without spread to avoid copying large span arrays
222
- const list = traces.map((t) => ({
223
- id: t.id,
224
- method: t.method,
225
- url: t.url,
226
- statusCode: t.statusCode,
227
- totalDuration: t.totalDuration,
228
- spanCount: t.spanCount,
229
- warningCount: t.warnings.length,
230
- timestamp: t.timestamp,
231
- }));
123
+ const list = traces.map(stripTraceForList);
232
124
  return wrapArray(list, opts, (t, term) => {
233
125
  return t.method.toLowerCase().includes(term) || t.url.toLowerCase().includes(term);
234
126
  });
@@ -244,7 +136,33 @@ export class DataAccess {
244
136
  }
245
137
  if (!this.debugStore.traces)
246
138
  return null;
247
- return this.debugStore.traces.getTrace(id) ?? null;
139
+ const trace = this.debugStore.traces.getTrace(id) ?? null;
140
+ if (!trace)
141
+ return null;
142
+ // Enrich with related logs by httpRequestId
143
+ if (trace.httpRequestId) {
144
+ const logs = await this.getRelatedLogsByRequestId(trace.httpRequestId);
145
+ if (logs.length > 0) {
146
+ return { ...trace, logs };
147
+ }
148
+ }
149
+ return trace;
150
+ }
151
+ /**
152
+ * Find log entries matching a specific request ID.
153
+ */
154
+ async getRelatedLogsByRequestId(requestId) {
155
+ if (this.hasPersistence) {
156
+ try {
157
+ const result = await this.dashboardStore.getLogs(1, 50, { requestId });
158
+ return result.data;
159
+ }
160
+ catch {
161
+ // Fall through to log file
162
+ }
163
+ }
164
+ const entries = this.logPath ? await readLogFile(this.logPath) : [];
165
+ return entries.filter((e) => e.request_id === requestId || e.requestId === requestId);
248
166
  }
249
167
  // =========================================================================
250
168
  // Routes
@@ -258,17 +176,7 @@ export class DataAccess {
258
176
  getRoutes(search) {
259
177
  let routes = this.debugStore.routes.getRoutes();
260
178
  if (search) {
261
- const term = search.toLowerCase();
262
- routes = routes.filter((r) => {
263
- const pattern = (r.pattern || '').toLowerCase();
264
- const handler = (r.handler || '').toLowerCase();
265
- const name = (r.name || '').toLowerCase();
266
- const method = (r.method || '').toLowerCase();
267
- return (pattern.includes(term) ||
268
- handler.includes(term) ||
269
- name.includes(term) ||
270
- method.includes(term));
271
- });
179
+ routes = filterRoutes(routes, search);
272
180
  }
273
181
  const total = routes.length;
274
182
  return {
@@ -288,68 +196,15 @@ export class DataAccess {
288
196
  */
289
197
  async getLogs(opts = {}) {
290
198
  if (this.hasPersistence && opts.source !== 'memory') {
291
- const page = opts.page ?? 1;
292
- const perPage = opts.perPage ?? 50;
293
- const filters = {
294
- search: opts.search,
295
- ...opts.filters,
296
- };
297
- const result = await this.dashboardStore.getLogs(page, perPage, filters);
199
+ const { page, perPage } = buildPaginationArgs(opts);
200
+ const result = await this.dashboardStore.getLogs(page, perPage, buildLogFilters(opts));
298
201
  return fromDashboardResult(result);
299
202
  }
300
- // Fallback: read from log file on disk (same approach as DebugController)
301
- const entries = await this.readLogFile();
203
+ const entries = this.logPath ? await readLogFile(this.logPath) : [];
302
204
  return wrapArray(entries, opts, (e, term) => {
303
205
  const msg = String(e.msg ?? e.message ?? '').toLowerCase();
304
206
  const levelName = String(e.levelName ?? '').toLowerCase();
305
207
  return msg.includes(term) || levelName.includes(term);
306
208
  });
307
209
  }
308
- // =========================================================================
309
- // Private helpers
310
- // =========================================================================
311
- /**
312
- * Read and parse the last 256 KB of the application log file.
313
- *
314
- * Returns an array of enriched log entry objects. If the log file
315
- * does not exist or cannot be read, returns an empty array.
316
- */
317
- async readLogFile() {
318
- if (!this.logPath)
319
- return [];
320
- try {
321
- const stats = await stat(this.logPath);
322
- const maxBytes = 256 * 1024;
323
- let content;
324
- if (stats.size > maxBytes) {
325
- const { createReadStream } = await import('node:fs');
326
- const stream = createReadStream(this.logPath, {
327
- start: stats.size - maxBytes,
328
- encoding: 'utf-8',
329
- });
330
- const chunks = [];
331
- for await (const chunk of stream) {
332
- chunks.push(chunk);
333
- }
334
- content = chunks.join('');
335
- // Skip first potentially incomplete line
336
- const firstNewline = content.indexOf('\n');
337
- if (firstNewline !== -1)
338
- content = content.slice(firstNewline + 1);
339
- }
340
- else {
341
- content = await readFile(this.logPath, 'utf-8');
342
- }
343
- return content
344
- .trim()
345
- .split('\n')
346
- .filter(Boolean)
347
- .map((line) => parseAndEnrich(line))
348
- .filter((entry) => entry !== null)
349
- .reverse();
350
- }
351
- catch {
352
- return [];
353
- }
354
- }
355
210
  }
@@ -0,0 +1,130 @@
1
+ import type { DashboardStore, QueryFilters, EventFilters, EmailFilters, TraceFilters, LogFilters } from '../dashboard/dashboard_store.js';
2
+ import type { DebugStore } from '../debug/debug_store.js';
3
+ import type { QueryRecord, EventRecord, TraceRecord, RouteRecord } from '../debug/types.js';
4
+ export interface ListOptions {
5
+ page?: number;
6
+ perPage?: number;
7
+ search?: string;
8
+ sort?: string;
9
+ sortDir?: 'asc' | 'desc';
10
+ filters?: Record<string, unknown>;
11
+ /**
12
+ * Force the data source for this read.
13
+ *
14
+ * - `'memory'` — always read from ring buffers ({@link DebugStore}).
15
+ * Use this for the debug panel, which expects camelCase field names
16
+ * matching the {@link QueryRecord}/{@link EventRecord}/etc. interfaces.
17
+ * - `'auto'` (default) — use SQLite when available, fall back to memory.
18
+ */
19
+ source?: 'memory' | 'auto';
20
+ }
21
+ export interface PaginatedResult<T = Record<string, unknown>> {
22
+ data: T[];
23
+ meta: {
24
+ total: number;
25
+ page: number;
26
+ perPage: number;
27
+ lastPage: number;
28
+ };
29
+ }
30
+ /**
31
+ * Wrap a plain array in the standard {@link PaginatedResult} envelope.
32
+ *
33
+ * Applies optional client-side search filtering and pagination so that
34
+ * ring-buffer results match the same shape returned by the dashboard store.
35
+ */
36
+ export declare function wrapArray<T>(items: T[], opts: ListOptions, searchFn?: (item: T, term: string) => boolean): PaginatedResult<T>;
37
+ /**
38
+ * Convert a flat {@link DashboardStore.PaginatedResult} to the nested
39
+ * `{ data, meta }` shape used by the unified API.
40
+ */
41
+ export declare function fromDashboardResult<T>(result: {
42
+ data: T[];
43
+ total: number;
44
+ page: number;
45
+ perPage: number;
46
+ lastPage: number;
47
+ }): PaginatedResult<T>;
48
+ export declare function mapTraceListRow<T extends Record<string, unknown>>(row: T): T & {
49
+ requestId: unknown;
50
+ statusCode: unknown;
51
+ totalDuration: unknown;
52
+ spanCount: unknown;
53
+ createdAt: unknown;
54
+ };
55
+ /** Normalize SQLite email column names to match the EmailRecord shape. */
56
+ export declare function normalizeEmailRow(row: Record<string, unknown>): {
57
+ from: unknown;
58
+ to: unknown;
59
+ messageId: unknown;
60
+ attachmentCount: unknown;
61
+ timestamp: unknown;
62
+ };
63
+ /** Build dashboard store query/event/email/trace pagination args. */
64
+ export declare function buildPaginationArgs(opts: ListOptions): {
65
+ page: number;
66
+ perPage: number;
67
+ };
68
+ export declare function buildQueryFilters(opts: ListOptions): QueryFilters;
69
+ export declare function buildEventFilters(opts: ListOptions): EventFilters;
70
+ export declare function buildEmailFilters(opts: ListOptions): EmailFilters;
71
+ export declare function buildTraceFilters(opts: ListOptions): TraceFilters;
72
+ export declare function buildLogFilters(opts: ListOptions): LogFilters;
73
+ /** Strip heavy html/text bodies from email records for list view. */
74
+ export declare function stripEmailForList(e: {
75
+ id: number;
76
+ from: string;
77
+ to: string;
78
+ cc?: string | null;
79
+ bcc?: string | null;
80
+ subject: string;
81
+ mailer: string;
82
+ status: string;
83
+ messageId?: string | null;
84
+ attachmentCount?: number;
85
+ timestamp?: string | number | null;
86
+ }): {
87
+ id: number;
88
+ from: string;
89
+ to: string;
90
+ cc: string | null | undefined;
91
+ bcc: string | null | undefined;
92
+ subject: string;
93
+ mailer: string;
94
+ status: string;
95
+ messageId: string | null | undefined;
96
+ attachmentCount: number | undefined;
97
+ timestamp: string | number | null | undefined;
98
+ };
99
+ /** Strip spans from trace records for list view. */
100
+ export declare function stripTraceForList(t: {
101
+ id: number;
102
+ method: string;
103
+ url: string;
104
+ statusCode: number;
105
+ totalDuration: number;
106
+ spanCount: number;
107
+ warnings: readonly unknown[];
108
+ timestamp?: string | number | null;
109
+ }): {
110
+ id: number;
111
+ method: string;
112
+ url: string;
113
+ statusCode: number;
114
+ totalDuration: number;
115
+ spanCount: number;
116
+ warningCount: number;
117
+ timestamp: string | number | null | undefined;
118
+ };
119
+ /** Filter routes by search term across pattern, handler, name, and method. */
120
+ export declare function filterRoutes(routes: RouteRecord[], search: string): RouteRecord[];
121
+ /**
122
+ * Read and parse the last 256 KB of a log file.
123
+ *
124
+ * Returns an array of enriched log entry objects. If the log file
125
+ * does not exist or cannot be read, returns an empty array.
126
+ */
127
+ export declare function readLogFile(logPath: string): Promise<Record<string, unknown>[]>;
128
+ export type { DashboardStore, QueryFilters, EventFilters, EmailFilters, TraceFilters, LogFilters };
129
+ export type { DebugStore };
130
+ export type { QueryRecord, EventRecord, TraceRecord, RouteRecord };
@@ -0,0 +1,212 @@
1
+ import { readFile, stat } from 'node:fs/promises';
2
+ import { parseAndEnrich } from '../log_stream/log_stream_service.js';
3
+ // ---------------------------------------------------------------------------
4
+ // Pagination helpers
5
+ // ---------------------------------------------------------------------------
6
+ /**
7
+ * Wrap a plain array in the standard {@link PaginatedResult} envelope.
8
+ *
9
+ * Applies optional client-side search filtering and pagination so that
10
+ * ring-buffer results match the same shape returned by the dashboard store.
11
+ */
12
+ export function wrapArray(items, opts, searchFn) {
13
+ let filtered = items;
14
+ // Client-side search
15
+ if (opts.search && searchFn) {
16
+ const term = opts.search.toLowerCase();
17
+ filtered = filtered.filter((item) => searchFn(item, term));
18
+ }
19
+ const total = filtered.length;
20
+ const page = opts.page ?? 1;
21
+ // When perPage is not specified, return all items (backward compat for debug panel)
22
+ const perPage = opts.perPage ?? (total || 1);
23
+ const lastPage = Math.max(1, Math.ceil(total / perPage));
24
+ const start = (page - 1) * perPage;
25
+ const data = filtered.slice(start, start + perPage);
26
+ return {
27
+ data,
28
+ meta: { total, page, perPage, lastPage },
29
+ };
30
+ }
31
+ /**
32
+ * Convert a flat {@link DashboardStore.PaginatedResult} to the nested
33
+ * `{ data, meta }` shape used by the unified API.
34
+ */
35
+ export function fromDashboardResult(result) {
36
+ return {
37
+ data: result.data,
38
+ meta: {
39
+ total: result.total,
40
+ page: result.page,
41
+ perPage: result.perPage,
42
+ lastPage: result.lastPage,
43
+ },
44
+ };
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // Row mapping helpers
48
+ // ---------------------------------------------------------------------------
49
+ export function mapTraceListRow(row) {
50
+ return {
51
+ ...row,
52
+ requestId: row.request_id ?? row.requestId,
53
+ statusCode: row.status_code ?? row.statusCode,
54
+ totalDuration: row.total_duration ?? row.totalDuration,
55
+ spanCount: row.span_count ?? row.spanCount,
56
+ createdAt: row.created_at ?? row.createdAt,
57
+ };
58
+ }
59
+ /** Pick the first defined value from a row, trying snake_case then camelCase. */
60
+ function pick(row, snake, camel, fallback = null) {
61
+ return row[snake] ?? row[camel] ?? fallback;
62
+ }
63
+ /** Normalize SQLite email column names to match the EmailRecord shape. */
64
+ export function normalizeEmailRow(row) {
65
+ return {
66
+ ...row,
67
+ from: pick(row, 'from_addr', 'from', ''),
68
+ to: pick(row, 'to_addr', 'to', ''),
69
+ messageId: pick(row, 'message_id', 'messageId'),
70
+ attachmentCount: pick(row, 'attachment_count', 'attachmentCount', 0),
71
+ timestamp: pick(row, 'created_at', 'timestamp'),
72
+ };
73
+ }
74
+ // ---------------------------------------------------------------------------
75
+ // Store delegate helpers
76
+ // ---------------------------------------------------------------------------
77
+ /** Build dashboard store query/event/email/trace pagination args. */
78
+ export function buildPaginationArgs(opts) {
79
+ return {
80
+ page: opts.page ?? 1,
81
+ perPage: opts.perPage ?? 50,
82
+ };
83
+ }
84
+ export function buildQueryFilters(opts) {
85
+ return {
86
+ search: opts.search,
87
+ ...opts.filters,
88
+ };
89
+ }
90
+ export function buildEventFilters(opts) {
91
+ return {
92
+ search: opts.search,
93
+ ...opts.filters,
94
+ };
95
+ }
96
+ export function buildEmailFilters(opts) {
97
+ return {
98
+ search: opts.search,
99
+ ...opts.filters,
100
+ };
101
+ }
102
+ export function buildTraceFilters(opts) {
103
+ return {
104
+ search: opts.search,
105
+ ...opts.filters,
106
+ };
107
+ }
108
+ export function buildLogFilters(opts) {
109
+ return {
110
+ search: opts.search,
111
+ ...opts.filters,
112
+ };
113
+ }
114
+ // ---------------------------------------------------------------------------
115
+ // Email stripping
116
+ // ---------------------------------------------------------------------------
117
+ /** Strip heavy html/text bodies from email records for list view. */
118
+ export function stripEmailForList(e) {
119
+ return {
120
+ id: e.id,
121
+ from: e.from,
122
+ to: e.to,
123
+ cc: e.cc,
124
+ bcc: e.bcc,
125
+ subject: e.subject,
126
+ mailer: e.mailer,
127
+ status: e.status,
128
+ messageId: e.messageId,
129
+ attachmentCount: e.attachmentCount,
130
+ timestamp: e.timestamp,
131
+ };
132
+ }
133
+ // ---------------------------------------------------------------------------
134
+ // Trace list stripping
135
+ // ---------------------------------------------------------------------------
136
+ /** Strip spans from trace records for list view. */
137
+ export function stripTraceForList(t) {
138
+ return {
139
+ id: t.id,
140
+ method: t.method,
141
+ url: t.url,
142
+ statusCode: t.statusCode,
143
+ totalDuration: t.totalDuration,
144
+ spanCount: t.spanCount,
145
+ warningCount: t.warnings.length,
146
+ timestamp: t.timestamp,
147
+ };
148
+ }
149
+ // ---------------------------------------------------------------------------
150
+ // Route search
151
+ // ---------------------------------------------------------------------------
152
+ /** Filter routes by search term across pattern, handler, name, and method. */
153
+ export function filterRoutes(routes, search) {
154
+ const term = search.toLowerCase();
155
+ return routes.filter((r) => {
156
+ const pattern = (r.pattern || '').toLowerCase();
157
+ const handler = (r.handler || '').toLowerCase();
158
+ const name = (r.name || '').toLowerCase();
159
+ const method = (r.method || '').toLowerCase();
160
+ return (pattern.includes(term) ||
161
+ handler.includes(term) ||
162
+ name.includes(term) ||
163
+ method.includes(term));
164
+ });
165
+ }
166
+ // ---------------------------------------------------------------------------
167
+ // Log file reader
168
+ // ---------------------------------------------------------------------------
169
+ /**
170
+ * Read and parse the last 256 KB of a log file.
171
+ *
172
+ * Returns an array of enriched log entry objects. If the log file
173
+ * does not exist or cannot be read, returns an empty array.
174
+ */
175
+ export async function readLogFile(logPath) {
176
+ try {
177
+ const stats = await stat(logPath);
178
+ const content = await readLogContent(logPath, stats.size);
179
+ return content
180
+ .trim()
181
+ .split('\n')
182
+ .filter(Boolean)
183
+ .map((line) => parseAndEnrich(line))
184
+ .filter((entry) => entry !== null)
185
+ .reverse();
186
+ }
187
+ catch {
188
+ return [];
189
+ }
190
+ }
191
+ /** Read the last maxBytes of a file, skipping partial first line if truncated. */
192
+ async function readLogContent(logPath, fileSize) {
193
+ const maxBytes = 256 * 1024;
194
+ if (fileSize <= maxBytes) {
195
+ return readFile(logPath, 'utf-8');
196
+ }
197
+ const { createReadStream } = await import('node:fs');
198
+ const stream = createReadStream(logPath, {
199
+ start: fileSize - maxBytes,
200
+ encoding: 'utf-8',
201
+ });
202
+ const chunks = [];
203
+ for await (const chunk of stream) {
204
+ chunks.push(chunk);
205
+ }
206
+ let content = chunks.join('');
207
+ // Skip first potentially incomplete line
208
+ const firstNewline = content.indexOf('\n');
209
+ if (firstNewline !== -1)
210
+ content = content.slice(firstNewline + 1);
211
+ return content;
212
+ }