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
@@ -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
  });
@@ -258,12 +150,8 @@ export class DataAccess {
258
150
  }
259
151
  /**
260
152
  * Find log entries matching a specific request ID.
261
- *
262
- * Checks SQLite first (if available), then falls back to scanning
263
- * the log file for entries with a matching `request_id` field.
264
153
  */
265
154
  async getRelatedLogsByRequestId(requestId) {
266
- // Try SQLite first
267
155
  if (this.hasPersistence) {
268
156
  try {
269
157
  const result = await this.dashboardStore.getLogs(1, 50, { requestId });
@@ -273,8 +161,7 @@ export class DataAccess {
273
161
  // Fall through to log file
274
162
  }
275
163
  }
276
- // Fallback: scan log file
277
- const entries = await this.readLogFile();
164
+ const entries = this.logPath ? await readLogFile(this.logPath) : [];
278
165
  return entries.filter((e) => e.request_id === requestId || e.requestId === requestId);
279
166
  }
280
167
  // =========================================================================
@@ -289,17 +176,7 @@ export class DataAccess {
289
176
  getRoutes(search) {
290
177
  let routes = this.debugStore.routes.getRoutes();
291
178
  if (search) {
292
- const term = search.toLowerCase();
293
- routes = routes.filter((r) => {
294
- const pattern = (r.pattern || '').toLowerCase();
295
- const handler = (r.handler || '').toLowerCase();
296
- const name = (r.name || '').toLowerCase();
297
- const method = (r.method || '').toLowerCase();
298
- return (pattern.includes(term) ||
299
- handler.includes(term) ||
300
- name.includes(term) ||
301
- method.includes(term));
302
- });
179
+ routes = filterRoutes(routes, search);
303
180
  }
304
181
  const total = routes.length;
305
182
  return {
@@ -319,68 +196,15 @@ export class DataAccess {
319
196
  */
320
197
  async getLogs(opts = {}) {
321
198
  if (this.hasPersistence && opts.source !== 'memory') {
322
- const page = opts.page ?? 1;
323
- const perPage = opts.perPage ?? 50;
324
- const filters = {
325
- search: opts.search,
326
- ...opts.filters,
327
- };
328
- 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));
329
201
  return fromDashboardResult(result);
330
202
  }
331
- // Fallback: read from log file on disk (same approach as DebugController)
332
- const entries = await this.readLogFile();
203
+ const entries = this.logPath ? await readLogFile(this.logPath) : [];
333
204
  return wrapArray(entries, opts, (e, term) => {
334
205
  const msg = String(e.msg ?? e.message ?? '').toLowerCase();
335
206
  const levelName = String(e.levelName ?? '').toLowerCase();
336
207
  return msg.includes(term) || levelName.includes(term);
337
208
  });
338
209
  }
339
- // =========================================================================
340
- // Private helpers
341
- // =========================================================================
342
- /**
343
- * Read and parse the last 256 KB of the application log file.
344
- *
345
- * Returns an array of enriched log entry objects. If the log file
346
- * does not exist or cannot be read, returns an empty array.
347
- */
348
- async readLogFile() {
349
- if (!this.logPath)
350
- return [];
351
- try {
352
- const stats = await stat(this.logPath);
353
- const maxBytes = 256 * 1024;
354
- let content;
355
- if (stats.size > maxBytes) {
356
- const { createReadStream } = await import('node:fs');
357
- const stream = createReadStream(this.logPath, {
358
- start: stats.size - maxBytes,
359
- encoding: 'utf-8',
360
- });
361
- const chunks = [];
362
- for await (const chunk of stream) {
363
- chunks.push(chunk);
364
- }
365
- content = chunks.join('');
366
- // Skip first potentially incomplete line
367
- const firstNewline = content.indexOf('\n');
368
- if (firstNewline !== -1)
369
- content = content.slice(firstNewline + 1);
370
- }
371
- else {
372
- content = await readFile(this.logPath, 'utf-8');
373
- }
374
- return content
375
- .trim()
376
- .split('\n')
377
- .filter(Boolean)
378
- .map((line) => parseAndEnrich(line))
379
- .filter((entry) => entry !== null)
380
- .reverse();
381
- }
382
- catch {
383
- return [];
384
- }
385
- }
386
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
+ }