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,24 @@
1
+ /**
2
+ * Saved filter CRUD operations for the DashboardStore.
3
+ *
4
+ * Extracted from DashboardStore to reduce file length.
5
+ */
6
+ export function fetchSavedFilters(db, cache, section) {
7
+ return cache.coalesce('savedFilters:' + (section || ''), async () => {
8
+ const q = db('server_stats_saved_filters').orderBy('created_at', 'desc');
9
+ if (section)
10
+ q.where('section', section);
11
+ return q;
12
+ });
13
+ }
14
+ export async function insertSavedFilter(db, name, section, filterConfig) {
15
+ const [id] = await db('server_stats_saved_filters').insert({
16
+ name,
17
+ section,
18
+ filter_config: JSON.stringify(filterConfig),
19
+ });
20
+ return { id, name, section, filterConfig };
21
+ }
22
+ export async function removeSavedFilter(db, id) {
23
+ return (await db('server_stats_saved_filters').where('id', id).delete()) > 0;
24
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Storage statistics queries for the dashboard diagnostics endpoint.
3
+ */
4
+ import { CoalesceCache } from './coalesce_cache.js';
5
+ import type { Knex } from 'knex';
6
+ /**
7
+ * Get the file sizes of the SQLite database and its WAL file.
8
+ */
9
+ export declare function getFileSizes(dbFilePath: string): Promise<[number, number]>;
10
+ /**
11
+ * Count rows in all dashboard tables within a single transaction.
12
+ */
13
+ export declare function countAllTables(db: Knex): Promise<Array<{
14
+ name: string;
15
+ rowCount: number;
16
+ }>>;
17
+ export interface StorageStatsResult {
18
+ ready: boolean;
19
+ dbPath: string;
20
+ fileSizeMb: number;
21
+ walSizeMb: number;
22
+ retentionDays: number;
23
+ tables: Array<{
24
+ name: string;
25
+ rowCount: number;
26
+ }>;
27
+ lastCleanupAt: number | null;
28
+ }
29
+ export interface StorageStatsOpts {
30
+ db: Knex;
31
+ cache: CoalesceCache;
32
+ dbFilePath: string;
33
+ dbPath: string;
34
+ retentionDays: number;
35
+ lastCleanupAt: number | null;
36
+ onResult: (stats: StorageStatsResult) => void;
37
+ }
38
+ /**
39
+ * Build storage stats by querying file sizes and table row counts.
40
+ */
41
+ export declare function fetchStorageStats(opts: StorageStatsOpts): Promise<StorageStatsResult>;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Storage statistics queries for the dashboard diagnostics endpoint.
3
+ */
4
+ import { stat as fsStat } from 'node:fs/promises';
5
+ // ---------------------------------------------------------------------------
6
+ // File size helpers
7
+ // ---------------------------------------------------------------------------
8
+ /**
9
+ * Get the file sizes of the SQLite database and its WAL file.
10
+ */
11
+ export async function getFileSizes(dbFilePath) {
12
+ let fileSizeMb = 0;
13
+ let walSizeMb = 0;
14
+ try {
15
+ const s = await fsStat(dbFilePath);
16
+ fileSizeMb = Math.round((s.size / (1024 * 1024)) * 100) / 100;
17
+ }
18
+ catch {
19
+ // File may not exist yet
20
+ }
21
+ try {
22
+ const ws = await fsStat(dbFilePath + '-wal');
23
+ walSizeMb = Math.round((ws.size / (1024 * 1024)) * 100) / 100;
24
+ }
25
+ catch {
26
+ // WAL file may not exist
27
+ }
28
+ return [fileSizeMb, walSizeMb];
29
+ }
30
+ // ---------------------------------------------------------------------------
31
+ // Table row counts
32
+ // ---------------------------------------------------------------------------
33
+ const TABLE_NAMES = [
34
+ 'server_stats_requests',
35
+ 'server_stats_queries',
36
+ 'server_stats_events',
37
+ 'server_stats_emails',
38
+ 'server_stats_logs',
39
+ 'server_stats_traces',
40
+ 'server_stats_metrics',
41
+ 'server_stats_saved_filters',
42
+ ];
43
+ /**
44
+ * Count rows in all dashboard tables within a single transaction.
45
+ */
46
+ export async function countAllTables(db) {
47
+ return db.transaction(async (trx) => {
48
+ const result = [];
49
+ for (const name of TABLE_NAMES) {
50
+ try {
51
+ const [row] = await trx(name).count('* as count');
52
+ result.push({ name, rowCount: Number(row.count) });
53
+ }
54
+ catch {
55
+ result.push({ name, rowCount: 0 });
56
+ }
57
+ }
58
+ return result;
59
+ });
60
+ }
61
+ /**
62
+ * Build storage stats by querying file sizes and table row counts.
63
+ */
64
+ export function fetchStorageStats(opts) {
65
+ const { db, cache, dbFilePath, dbPath, retentionDays, lastCleanupAt, onResult } = opts;
66
+ return cache.coalesce('storageStats', async () => {
67
+ const [fileSizeMb, walSizeMb] = await getFileSizes(dbFilePath);
68
+ const tables = await countAllTables(db);
69
+ const stats = {
70
+ ready: true,
71
+ dbPath,
72
+ fileSizeMb,
73
+ walSizeMb,
74
+ retentionDays,
75
+ tables,
76
+ lastCleanupAt,
77
+ };
78
+ onResult(stats);
79
+ return stats;
80
+ });
81
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Data preparation helpers for the DashboardStore write queue.
3
+ *
4
+ * These functions transform in-memory records into SQLite-ready row
5
+ * objects. They are pure (no I/O, no Knex dependency) so they can
6
+ * be tested in isolation.
7
+ */
8
+ import type { EventRecord, EmailRecord } from '../debug/types.js';
9
+ import type { PersistRequestInput } from './dashboard_types.js';
10
+ import type { Knex } from 'knex';
11
+ export declare function hasWarned(path: string): boolean;
12
+ export declare function markWarned(path: string): void;
13
+ /**
14
+ * Normalize a SQL query by replacing literal values with `?` placeholders.
15
+ * Used for grouping identical query patterns.
16
+ */
17
+ export declare function normalizeSql(sql: string): string;
18
+ export interface PreparedQuery {
19
+ sql_text: string;
20
+ sql_normalized: string;
21
+ bindings: string | null;
22
+ duration: number;
23
+ method: string;
24
+ model: string | null;
25
+ connection: string;
26
+ in_transaction: number;
27
+ }
28
+ export interface PreparedTraceRow {
29
+ method: string;
30
+ url: string;
31
+ status_code: number;
32
+ total_duration: number;
33
+ span_count: number;
34
+ spans: string;
35
+ warnings: string | null;
36
+ }
37
+ export interface PreparedRequest {
38
+ input: PersistRequestInput;
39
+ filteredQueries: PreparedQuery[];
40
+ traceRow: PreparedTraceRow | null;
41
+ }
42
+ export interface PreparedLog {
43
+ [key: string]: unknown;
44
+ level: string;
45
+ message: string;
46
+ request_id: string | null;
47
+ data: string;
48
+ }
49
+ export interface EmailRow {
50
+ [key: string]: unknown;
51
+ from_addr: string;
52
+ to_addr: string;
53
+ cc: string | null;
54
+ bcc: string | null;
55
+ subject: string;
56
+ html: string | null;
57
+ text_body: string | null;
58
+ mailer: string;
59
+ status: string;
60
+ message_id: string | null;
61
+ attachment_count: number;
62
+ }
63
+ export interface EventRow {
64
+ [key: string]: unknown;
65
+ request_id: null;
66
+ event_name: string;
67
+ data: string | null;
68
+ }
69
+ /**
70
+ * Pre-stringify and transform request inputs into SQLite-ready row objects.
71
+ * This is done OUTSIDE the transaction so the synchronous better-sqlite3
72
+ * execution does not block the event loop on large spans.
73
+ */
74
+ export declare function prepareRequestRows(requests: PersistRequestInput[]): PreparedRequest[];
75
+ /**
76
+ * Transform raw log entries into SQLite-ready row objects.
77
+ */
78
+ export declare function prepareLogRows(logs: Record<string, unknown>[]): PreparedLog[];
79
+ /**
80
+ * Transform an EmailRecord into a SQLite-ready row object.
81
+ */
82
+ export declare function buildEmailRow(record: EmailRecord): EmailRow;
83
+ /**
84
+ * Transform EventRecords into SQLite-ready row objects.
85
+ */
86
+ export declare function buildEventRows(events: EventRecord[]): EventRow[];
87
+ /**
88
+ * Insert rows into a table in batches of 50.
89
+ */
90
+ export declare function batchInsert(trx: Knex.Transaction, table: string, rows: Record<string, unknown>[]): Promise<void>;
91
+ export declare function flushRequests(trx: Knex.Transaction, preparedRequests: PreparedRequest[]): Promise<void>;
92
+ /**
93
+ * Flush pending events into the database.
94
+ */
95
+ export declare function flushEvents(trx: Knex.Transaction, events: {
96
+ requestIndex: number;
97
+ events: EventRecord[];
98
+ }[]): Promise<void>;
99
+ /**
100
+ * Flush pending emails into the database.
101
+ */
102
+ export declare function flushEmails(trx: Knex.Transaction, emails: EmailRecord[]): Promise<void>;
103
+ /**
104
+ * Flush prepared logs into the database.
105
+ */
106
+ export declare function flushLogs(trx: Knex.Transaction, preparedLogs: PreparedLog[]): Promise<void>;
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Data preparation helpers for the DashboardStore write queue.
3
+ *
4
+ * These functions transform in-memory records into SQLite-ready row
5
+ * objects. They are pure (no I/O, no Knex dependency) so they can
6
+ * be tested in isolation.
7
+ */
8
+ import { round } from '../utils/math_helpers.js';
9
+ // ---------------------------------------------------------------------------
10
+ // Warn-once tracking for write-path catch blocks
11
+ // ---------------------------------------------------------------------------
12
+ const warnedWritePaths = new Set();
13
+ export function hasWarned(path) {
14
+ return warnedWritePaths.has(path);
15
+ }
16
+ export function markWarned(path) {
17
+ warnedWritePaths.add(path);
18
+ }
19
+ // ---------------------------------------------------------------------------
20
+ // SQL normalization
21
+ // ---------------------------------------------------------------------------
22
+ /**
23
+ * Normalize a SQL query by replacing literal values with `?` placeholders.
24
+ * Used for grouping identical query patterns.
25
+ */
26
+ export function normalizeSql(sql) {
27
+ return sql
28
+ .replace(/'[^']*'/g, '?')
29
+ .replace(/\b\d+(\.\d+)?\b/g, '?')
30
+ .replace(/\s+/g, ' ')
31
+ .trim();
32
+ }
33
+ // ---------------------------------------------------------------------------
34
+ // Pure data-prep functions
35
+ // ---------------------------------------------------------------------------
36
+ /**
37
+ * Pre-stringify and transform request inputs into SQLite-ready row objects.
38
+ * This is done OUTSIDE the transaction so the synchronous better-sqlite3
39
+ * execution does not block the event loop on large spans.
40
+ */
41
+ export function prepareRequestRows(requests) {
42
+ return requests.map((input) => ({
43
+ input,
44
+ filteredQueries: input.queries
45
+ .filter((q) => q.connection !== 'server_stats')
46
+ .map((q) => ({
47
+ sql_text: q.sql,
48
+ sql_normalized: normalizeSql(q.sql),
49
+ bindings: q.bindings ? JSON.stringify(q.bindings) : null,
50
+ duration: round(q.duration),
51
+ method: q.method,
52
+ model: q.model,
53
+ connection: q.connection,
54
+ in_transaction: q.inTransaction ? 1 : 0,
55
+ })),
56
+ traceRow: input.trace
57
+ ? {
58
+ method: input.trace.method,
59
+ url: input.trace.url,
60
+ status_code: input.trace.statusCode,
61
+ total_duration: round(input.trace.totalDuration),
62
+ span_count: input.trace.spanCount,
63
+ spans: JSON.stringify(input.trace.spans),
64
+ warnings: input.trace.warnings.length > 0 ? JSON.stringify(input.trace.warnings) : null,
65
+ }
66
+ : null,
67
+ }));
68
+ }
69
+ /**
70
+ * Transform raw log entries into SQLite-ready row objects.
71
+ */
72
+ export function prepareLogRows(logs) {
73
+ return logs.map((entry) => {
74
+ const levelName = typeof entry.levelName === 'string' ? entry.levelName : String(entry.level || 'unknown');
75
+ return {
76
+ level: levelName,
77
+ message: String(entry.msg || entry.message || ''),
78
+ request_id: entry.request_id || entry.requestId || entry['x-request-id']
79
+ ? String(entry.request_id || entry.requestId || entry['x-request-id'])
80
+ : null,
81
+ data: JSON.stringify(entry),
82
+ };
83
+ });
84
+ }
85
+ /**
86
+ * Transform an EmailRecord into a SQLite-ready row object.
87
+ */
88
+ export function buildEmailRow(record) {
89
+ return {
90
+ from_addr: record.from,
91
+ to_addr: record.to,
92
+ cc: record.cc,
93
+ bcc: record.bcc,
94
+ subject: record.subject,
95
+ html: record.html,
96
+ text_body: record.text,
97
+ mailer: record.mailer,
98
+ status: record.status,
99
+ message_id: record.messageId,
100
+ attachment_count: record.attachmentCount,
101
+ };
102
+ }
103
+ /**
104
+ * Transform EventRecords into SQLite-ready row objects.
105
+ */
106
+ export function buildEventRows(events) {
107
+ return events.map((e) => ({
108
+ request_id: null,
109
+ event_name: e.event,
110
+ data: e.data,
111
+ }));
112
+ }
113
+ // ---------------------------------------------------------------------------
114
+ // Batch insert helper
115
+ // ---------------------------------------------------------------------------
116
+ /**
117
+ * Insert rows into a table in batches of 50.
118
+ */
119
+ export async function batchInsert(trx, table, rows) {
120
+ for (let i = 0; i < rows.length; i += 50) {
121
+ await trx(table).insert(rows.slice(i, i + 50));
122
+ }
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // Transaction sub-routines for flushWriteQueue
126
+ // ---------------------------------------------------------------------------
127
+ /**
128
+ * Flush prepared requests (with queries + traces) into the database.
129
+ */
130
+ /** Build the request row object for insertion. */
131
+ function buildRequestRow(input) {
132
+ const row = {
133
+ method: input.method,
134
+ url: input.url,
135
+ status_code: input.statusCode,
136
+ duration: round(input.duration),
137
+ span_count: input.trace?.spanCount ?? 0,
138
+ warning_count: input.trace?.warnings?.length ?? 0,
139
+ };
140
+ if (input.httpRequestId) {
141
+ row.http_request_id = String(input.httpRequestId);
142
+ }
143
+ return row;
144
+ }
145
+ /** Insert a single prepared request with its queries and trace. */
146
+ async function insertOneRequest(trx, prepared) {
147
+ const { input, filteredQueries, traceRow } = prepared;
148
+ const row = buildRequestRow(input);
149
+ const [requestId] = await trx('server_stats_requests').insert(row);
150
+ const hasId = requestId !== null && requestId !== undefined;
151
+ if (hasId && filteredQueries.length > 0) {
152
+ const rows = filteredQueries.map((q) => ({ ...q, request_id: requestId }));
153
+ await batchInsert(trx, 'server_stats_queries', rows);
154
+ }
155
+ if (hasId && traceRow) {
156
+ await trx('server_stats_traces').insert({ ...traceRow, request_id: requestId });
157
+ }
158
+ }
159
+ export async function flushRequests(trx, preparedRequests) {
160
+ for (const prepared of preparedRequests) {
161
+ try {
162
+ await insertOneRequest(trx, prepared);
163
+ }
164
+ catch (err) {
165
+ if (!hasWarned('persistRequest')) {
166
+ markWarned('persistRequest');
167
+ const { log } = await import('../utils/logger.js');
168
+ log.warn(`dashboard: persistRequest failed — ${err?.message}`);
169
+ }
170
+ }
171
+ }
172
+ }
173
+ /**
174
+ * Flush pending events into the database.
175
+ */
176
+ export async function flushEvents(trx, events) {
177
+ for (const { events: evts } of events) {
178
+ try {
179
+ const rows = buildEventRows(evts);
180
+ await batchInsert(trx, 'server_stats_events', rows);
181
+ }
182
+ catch (err) {
183
+ if (!hasWarned('recordEvents')) {
184
+ markWarned('recordEvents');
185
+ const { log } = await import('../utils/logger.js');
186
+ log.warn(`dashboard: recordEvents failed — ${err?.message}`);
187
+ }
188
+ }
189
+ }
190
+ }
191
+ /**
192
+ * Flush pending emails into the database.
193
+ */
194
+ export async function flushEmails(trx, emails) {
195
+ if (emails.length === 0)
196
+ return;
197
+ try {
198
+ const rows = emails.map((record) => buildEmailRow(record));
199
+ await batchInsert(trx, 'server_stats_emails', rows);
200
+ }
201
+ catch (err) {
202
+ if (!hasWarned('recordEmail')) {
203
+ markWarned('recordEmail');
204
+ const { log } = await import('../utils/logger.js');
205
+ log.warn(`dashboard: recordEmail failed — ${err?.message}`);
206
+ }
207
+ }
208
+ }
209
+ /**
210
+ * Flush prepared logs into the database.
211
+ */
212
+ export async function flushLogs(trx, preparedLogs) {
213
+ if (preparedLogs.length === 0)
214
+ return;
215
+ try {
216
+ await batchInsert(trx, 'server_stats_logs', preparedLogs);
217
+ }
218
+ catch (err) {
219
+ if (!hasWarned('recordLog')) {
220
+ markWarned('recordLog');
221
+ const { log } = await import('../utils/logger.js');
222
+ log.warn(`dashboard: recordLog failed — ${err?.message}`);
223
+ }
224
+ }
225
+ }
@@ -1,32 +1,5 @@
1
- import type { DashboardStore } 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
- }
1
+ import type { ListOptions, PaginatedResult, DashboardStore, DebugStore, QueryRecord, EventRecord, TraceRecord, RouteRecord } from './data_access_helpers.js';
2
+ export type { ListOptions, PaginatedResult };
30
3
  /**
31
4
  * Thin abstraction layer that delegates reads to either the
32
5
  * {@link DashboardStore} (SQLite persistence) or the {@link DebugStore}
@@ -81,9 +54,6 @@ export declare class DataAccess {
81
54
  getTraceDetail(id: number, source?: 'memory' | 'auto'): Promise<TraceRecord | null>;
82
55
  /**
83
56
  * Find log entries matching a specific request ID.
84
- *
85
- * Checks SQLite first (if available), then falls back to scanning
86
- * the log file for entries with a matching `request_id` field.
87
57
  */
88
58
  private getRelatedLogsByRequestId;
89
59
  /**
@@ -101,11 +71,4 @@ export declare class DataAccess {
101
71
  * 256 KB of the application log file from the filesystem.
102
72
  */
103
73
  getLogs(opts?: ListOptions): Promise<PaginatedResult>;
104
- /**
105
- * Read and parse the last 256 KB of the application log file.
106
- *
107
- * Returns an array of enriched log entry objects. If the log file
108
- * does not exist or cannot be read, returns an empty array.
109
- */
110
- private readLogFile;
111
74
  }