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,18 @@
1
+ /**
2
+ * Knex/SQLite connection factory and PRAGMA configuration.
3
+ *
4
+ * Extracted from DashboardStore to reduce initKnex line count
5
+ * and keep the main store file focused on business logic.
6
+ */
7
+ import type { Knex } from 'knex';
8
+ export declare const SQLITE_PRAGMAS: string[];
9
+ /**
10
+ * Apply all SQLite PRAGMAs via db.raw() calls.
11
+ * This is the fallback if afterCreate didn't work.
12
+ */
13
+ export declare function applyPragmas(db: Knex): Promise<void>;
14
+ /**
15
+ * Import knex and better-sqlite3 from the host app, then create
16
+ * a standalone Knex connection to SQLite.
17
+ */
18
+ export declare function createKnexConnection(dbFilePath: string): Promise<Knex>;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Knex/SQLite connection factory and PRAGMA configuration.
3
+ *
4
+ * Extracted from DashboardStore to reduce initKnex line count
5
+ * and keep the main store file focused on business logic.
6
+ */
7
+ import { log } from '../utils/logger.js';
8
+ // ---------------------------------------------------------------------------
9
+ // PRAGMA constants
10
+ // ---------------------------------------------------------------------------
11
+ export const SQLITE_PRAGMAS = [
12
+ 'journal_mode=WAL',
13
+ 'foreign_keys=ON',
14
+ 'synchronous=NORMAL',
15
+ 'cache_size=-64000',
16
+ 'mmap_size=268435456',
17
+ 'temp_store=MEMORY',
18
+ ];
19
+ const PRAGMA_STATEMENTS_FOR_POOL = [
20
+ 'journal_mode = WAL',
21
+ 'foreign_keys = ON',
22
+ 'synchronous = NORMAL',
23
+ 'cache_size = -64000',
24
+ 'mmap_size = 268435456',
25
+ 'temp_store = MEMORY',
26
+ ];
27
+ // ---------------------------------------------------------------------------
28
+ // PRAGMA application via db.raw()
29
+ // ---------------------------------------------------------------------------
30
+ /**
31
+ * Apply all SQLite PRAGMAs via db.raw() calls.
32
+ * This is the fallback if afterCreate didn't work.
33
+ */
34
+ export async function applyPragmas(db) {
35
+ log.info('dashboard: setting PRAGMA...');
36
+ for (const pragma of SQLITE_PRAGMAS) {
37
+ await db.raw(`PRAGMA ${pragma}`);
38
+ }
39
+ log.info('dashboard: PRAGMA set');
40
+ }
41
+ // ---------------------------------------------------------------------------
42
+ // Knex connection creation
43
+ // ---------------------------------------------------------------------------
44
+ /**
45
+ * Import knex and better-sqlite3 from the host app, then create
46
+ * a standalone Knex connection to SQLite.
47
+ */
48
+ export async function createKnexConnection(dbFilePath) {
49
+ log.info('dashboard: loading knex...');
50
+ const { appImportWithPath } = await import('../utils/app_import.js');
51
+ const { module: knexModule, resolvedPath: knexPath } = await importWithError(appImportWithPath, 'knex', 'Install it with: npm install knex better-sqlite3');
52
+ const { resolvedPath: sqlite3Path } = await importWithError(appImportWithPath, 'better-sqlite3', 'Install it with: npm install better-sqlite3');
53
+ log.info(`dashboard: knex resolved from ${knexPath}`);
54
+ log.info(`dashboard: better-sqlite3 resolved from ${sqlite3Path}`);
55
+ const knexFactory = knexModule.default ?? knexModule;
56
+ log.info(`dashboard: opening SQLite database at ${dbFilePath}`);
57
+ return knexFactory({
58
+ client: 'better-sqlite3',
59
+ connection: { filename: dbFilePath },
60
+ useNullAsDefault: true,
61
+ pool: {
62
+ min: 1,
63
+ max: 1,
64
+ acquireTimeoutMillis: 10_000,
65
+ afterCreate: afterCreateHandler,
66
+ },
67
+ });
68
+ }
69
+ // ---------------------------------------------------------------------------
70
+ // Internal helpers
71
+ // ---------------------------------------------------------------------------
72
+ async function importWithError(appImportWithPath, moduleName, installHint) {
73
+ try {
74
+ return await appImportWithPath(moduleName);
75
+ }
76
+ catch (err) {
77
+ throw new Error(`Could not load ${moduleName}: ${err?.message}. ${installHint}`);
78
+ }
79
+ }
80
+ function afterCreateHandler(conn, done) {
81
+ const raw = conn;
82
+ try {
83
+ for (const pragma of PRAGMA_STATEMENTS_FOR_POOL) {
84
+ raw.pragma(pragma);
85
+ }
86
+ }
87
+ catch {
88
+ // Fallback: PRAGMAs will be set via db.raw() below
89
+ }
90
+ done(null, conn);
91
+ }
@@ -1,13 +1,4 @@
1
- /**
2
- * Yield control back to the event loop so Node.js can process pending
3
- * I/O (incoming HTTP requests, timers, etc.).
4
- *
5
- * `better-sqlite3` is fully synchronous — when Knex wraps it, each
6
- * `await db.raw(...)` resolves via the microtask queue, never actually
7
- * yielding to the I/O phase. Without explicit yields, 25+ sequential
8
- * migration statements block the event loop for their entire duration.
9
- */
10
- const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
1
+ import { yieldToEventLoop, migrateRequests, migrateQueries, migrateEvents, migrateEmails, migrateLogs, migrateTraces, migrateMetrics, migrateSavedFilters, } from './migrator_tables.js';
11
2
  /**
12
3
  * Auto-migrate all dashboard SQLite tables.
13
4
  *
@@ -19,145 +10,21 @@ const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
19
10
  * continue processing HTTP requests during migration.
20
11
  */
21
12
  export async function autoMigrate(db) {
22
- // -- server_stats_requests --------------------------------------------------
23
- await db.raw(`
24
- CREATE TABLE IF NOT EXISTS server_stats_requests (
25
- id INTEGER PRIMARY KEY AUTOINCREMENT,
26
- method TEXT NOT NULL,
27
- url TEXT NOT NULL,
28
- status_code INTEGER NOT NULL,
29
- duration REAL NOT NULL,
30
- span_count INTEGER DEFAULT 0,
31
- warning_count INTEGER DEFAULT 0,
32
- http_request_id TEXT,
33
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
34
- )
35
- `);
36
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_created ON server_stats_requests(created_at)`);
37
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_url ON server_stats_requests(url)`);
38
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_duration ON server_stats_requests(duration)`);
39
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_status ON server_stats_requests(status_code)`);
40
- try {
41
- await db.raw('ALTER TABLE server_stats_requests ADD COLUMN http_request_id TEXT');
42
- }
43
- catch { }
44
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_http_req ON server_stats_requests(http_request_id)`);
13
+ await migrateRequests(db);
45
14
  await yieldToEventLoop();
46
- // -- server_stats_queries ---------------------------------------------------
47
- await db.raw(`
48
- CREATE TABLE IF NOT EXISTS server_stats_queries (
49
- id INTEGER PRIMARY KEY AUTOINCREMENT,
50
- request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
51
- sql_text TEXT NOT NULL,
52
- sql_normalized TEXT NOT NULL,
53
- bindings TEXT,
54
- duration REAL NOT NULL,
55
- method TEXT,
56
- model TEXT,
57
- connection TEXT,
58
- in_transaction INTEGER DEFAULT 0,
59
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
60
- )
61
- `);
62
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_created ON server_stats_queries(created_at)`);
63
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_normalized ON server_stats_queries(sql_normalized)`);
64
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_request ON server_stats_queries(request_id)`);
65
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_duration ON server_stats_queries(duration)`);
15
+ await migrateQueries(db);
66
16
  await yieldToEventLoop();
67
- // -- server_stats_events ----------------------------------------------------
68
- await db.raw(`
69
- CREATE TABLE IF NOT EXISTS server_stats_events (
70
- id INTEGER PRIMARY KEY AUTOINCREMENT,
71
- request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
72
- event_name TEXT NOT NULL,
73
- data TEXT,
74
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
75
- )
76
- `);
77
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_events_created ON server_stats_events(created_at)`);
78
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_events_name ON server_stats_events(event_name)`);
17
+ await migrateEvents(db);
79
18
  await yieldToEventLoop();
80
- // -- server_stats_emails ----------------------------------------------------
81
- await db.raw(`
82
- CREATE TABLE IF NOT EXISTS server_stats_emails (
83
- id INTEGER PRIMARY KEY AUTOINCREMENT,
84
- from_addr TEXT NOT NULL,
85
- to_addr TEXT NOT NULL,
86
- cc TEXT,
87
- bcc TEXT,
88
- subject TEXT NOT NULL,
89
- html TEXT,
90
- text_body TEXT,
91
- mailer TEXT NOT NULL,
92
- status TEXT NOT NULL,
93
- message_id TEXT,
94
- attachment_count INTEGER DEFAULT 0,
95
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
96
- )
97
- `);
98
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_emails_created ON server_stats_emails(created_at)`);
19
+ await migrateEmails(db);
99
20
  await yieldToEventLoop();
100
- // -- server_stats_logs ------------------------------------------------------
101
- await db.raw(`
102
- CREATE TABLE IF NOT EXISTS server_stats_logs (
103
- id INTEGER PRIMARY KEY AUTOINCREMENT,
104
- level TEXT NOT NULL,
105
- message TEXT NOT NULL,
106
- request_id TEXT,
107
- data TEXT,
108
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
109
- )
110
- `);
111
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_created ON server_stats_logs(created_at)`);
112
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_level ON server_stats_logs(level)`);
113
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_request ON server_stats_logs(request_id)`);
21
+ await migrateLogs(db);
114
22
  await yieldToEventLoop();
115
- // -- server_stats_traces ----------------------------------------------------
116
- await db.raw(`
117
- CREATE TABLE IF NOT EXISTS server_stats_traces (
118
- id INTEGER PRIMARY KEY AUTOINCREMENT,
119
- request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
120
- method TEXT NOT NULL,
121
- url TEXT NOT NULL,
122
- status_code INTEGER NOT NULL,
123
- total_duration REAL NOT NULL,
124
- span_count INTEGER DEFAULT 0,
125
- spans TEXT NOT NULL,
126
- warnings TEXT,
127
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
128
- )
129
- `);
130
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_traces_created ON server_stats_traces(created_at)`);
131
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_traces_request ON server_stats_traces(request_id)`);
23
+ await migrateTraces(db);
132
24
  await yieldToEventLoop();
133
- // -- server_stats_metrics ---------------------------------------------------
134
- await db.raw(`
135
- CREATE TABLE IF NOT EXISTS server_stats_metrics (
136
- id INTEGER PRIMARY KEY AUTOINCREMENT,
137
- bucket TEXT NOT NULL,
138
- request_count INTEGER DEFAULT 0,
139
- avg_duration REAL DEFAULT 0,
140
- p95_duration REAL DEFAULT 0,
141
- error_count INTEGER DEFAULT 0,
142
- query_count INTEGER DEFAULT 0,
143
- avg_query_duration REAL DEFAULT 0,
144
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
145
- )
146
- `);
147
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_metrics_bucket ON server_stats_metrics(bucket)`);
148
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_metrics_created ON server_stats_metrics(created_at)`);
25
+ await migrateMetrics(db);
149
26
  await yieldToEventLoop();
150
- // -- server_stats_saved_filters ---------------------------------------------
151
- await db.raw(`
152
- CREATE TABLE IF NOT EXISTS server_stats_saved_filters (
153
- id INTEGER PRIMARY KEY AUTOINCREMENT,
154
- name TEXT NOT NULL,
155
- section TEXT NOT NULL,
156
- filter_config TEXT NOT NULL,
157
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
158
- )
159
- `);
160
- await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_filters_section ON server_stats_saved_filters(section)`);
27
+ await migrateSavedFilters(db);
161
28
  }
162
29
  /**
163
30
  * Delete records older than `retentionDays` from all tables.
@@ -176,28 +43,15 @@ export async function runRetentionCleanup(db, retentionDays) {
176
43
  const days = Math.max(1, Math.floor(retentionDays));
177
44
  const cutoff = `datetime('now', '-${days} days')`;
178
45
  try {
179
- // Batch deletes to avoid blocking the event loop for large tables.
180
- // Each batch deletes up to 1000 rows, yielding between batches.
181
- const batchDelete = async (table) => {
182
- let hasMore = true;
183
- while (hasMore) {
184
- await db.raw(`DELETE FROM ${table} WHERE rowid IN (SELECT rowid FROM ${table} WHERE created_at < ${cutoff} LIMIT 1000)`);
185
- const remaining = await db.raw(`SELECT COUNT(*) as cnt FROM ${table} WHERE created_at < ${cutoff} LIMIT 1`);
186
- const cnt = remaining?.[0]?.cnt ?? 0;
187
- hasMore = cnt > 0;
188
- if (hasMore)
189
- await yieldToEventLoop();
190
- }
191
- };
192
46
  // Cascade deletes queries, events, traces via FK ON DELETE CASCADE
193
- await batchDelete('server_stats_requests');
47
+ await batchDelete(db, 'server_stats_requests', cutoff);
194
48
  await yieldToEventLoop();
195
49
  // Standalone tables
196
- await batchDelete('server_stats_logs');
50
+ await batchDelete(db, 'server_stats_logs', cutoff);
197
51
  await yieldToEventLoop();
198
- await batchDelete('server_stats_emails');
52
+ await batchDelete(db, 'server_stats_emails', cutoff);
199
53
  await yieldToEventLoop();
200
- await batchDelete('server_stats_metrics');
54
+ await batchDelete(db, 'server_stats_metrics', cutoff);
201
55
  await yieldToEventLoop();
202
56
  // Reclaim space and update query planner statistics
203
57
  await db.raw('PRAGMA optimize');
@@ -208,3 +62,20 @@ export async function runRetentionCleanup(db, retentionDays) {
208
62
  log.warn(`dashboard: retention cleanup error — ${err?.message}`);
209
63
  }
210
64
  }
65
+ /**
66
+ * Batch-delete old rows from a table, yielding between batches.
67
+ *
68
+ * Each batch deletes up to 1000 rows to avoid blocking the event loop
69
+ * for large tables.
70
+ */
71
+ async function batchDelete(db, table, cutoff) {
72
+ let hasMore = true;
73
+ while (hasMore) {
74
+ await db.raw(`DELETE FROM ${table} WHERE rowid IN (SELECT rowid FROM ${table} WHERE created_at < ${cutoff} LIMIT 1000)`);
75
+ const remaining = await db.raw(`SELECT COUNT(*) as cnt FROM ${table} WHERE created_at < ${cutoff} LIMIT 1`);
76
+ const cnt = remaining?.[0]?.cnt ?? 0;
77
+ hasMore = cnt > 0;
78
+ if (hasMore)
79
+ await yieldToEventLoop();
80
+ }
81
+ }
@@ -0,0 +1,19 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Yield control back to the event loop so Node.js can process pending
4
+ * I/O (incoming HTTP requests, timers, etc.).
5
+ *
6
+ * `better-sqlite3` is fully synchronous — when Knex wraps it, each
7
+ * `await db.raw(...)` resolves via the microtask queue, never actually
8
+ * yielding to the I/O phase. Without explicit yields, 25+ sequential
9
+ * migration statements block the event loop for their entire duration.
10
+ */
11
+ export declare const yieldToEventLoop: () => Promise<void>;
12
+ export declare function migrateRequests(db: Knex): Promise<void>;
13
+ export declare function migrateQueries(db: Knex): Promise<void>;
14
+ export declare function migrateEvents(db: Knex): Promise<void>;
15
+ export declare function migrateEmails(db: Knex): Promise<void>;
16
+ export declare function migrateLogs(db: Knex): Promise<void>;
17
+ export declare function migrateTraces(db: Knex): Promise<void>;
18
+ export declare function migrateMetrics(db: Knex): Promise<void>;
19
+ export declare function migrateSavedFilters(db: Knex): Promise<void>;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Yield control back to the event loop so Node.js can process pending
3
+ * I/O (incoming HTTP requests, timers, etc.).
4
+ *
5
+ * `better-sqlite3` is fully synchronous — when Knex wraps it, each
6
+ * `await db.raw(...)` resolves via the microtask queue, never actually
7
+ * yielding to the I/O phase. Without explicit yields, 25+ sequential
8
+ * migration statements block the event loop for their entire duration.
9
+ */
10
+ export const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
11
+ // ---------------------------------------------------------------------------
12
+ // Per-table migration functions
13
+ // ---------------------------------------------------------------------------
14
+ export async function migrateRequests(db) {
15
+ await db.raw(`
16
+ CREATE TABLE IF NOT EXISTS server_stats_requests (
17
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
18
+ method TEXT NOT NULL,
19
+ url TEXT NOT NULL,
20
+ status_code INTEGER NOT NULL,
21
+ duration REAL NOT NULL,
22
+ span_count INTEGER DEFAULT 0,
23
+ warning_count INTEGER DEFAULT 0,
24
+ http_request_id TEXT,
25
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
26
+ )
27
+ `);
28
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_created ON server_stats_requests(created_at)`);
29
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_url ON server_stats_requests(url)`);
30
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_duration ON server_stats_requests(duration)`);
31
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_status ON server_stats_requests(status_code)`);
32
+ try {
33
+ await db.raw('ALTER TABLE server_stats_requests ADD COLUMN http_request_id TEXT');
34
+ }
35
+ catch { }
36
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_http_req ON server_stats_requests(http_request_id)`);
37
+ }
38
+ export async function migrateQueries(db) {
39
+ await db.raw(`
40
+ CREATE TABLE IF NOT EXISTS server_stats_queries (
41
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
42
+ request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
43
+ sql_text TEXT NOT NULL,
44
+ sql_normalized TEXT NOT NULL,
45
+ bindings TEXT,
46
+ duration REAL NOT NULL,
47
+ method TEXT,
48
+ model TEXT,
49
+ connection TEXT,
50
+ in_transaction INTEGER DEFAULT 0,
51
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
52
+ )
53
+ `);
54
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_created ON server_stats_queries(created_at)`);
55
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_normalized ON server_stats_queries(sql_normalized)`);
56
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_request ON server_stats_queries(request_id)`);
57
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_duration ON server_stats_queries(duration)`);
58
+ }
59
+ export async function migrateEvents(db) {
60
+ await db.raw(`
61
+ CREATE TABLE IF NOT EXISTS server_stats_events (
62
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
63
+ request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
64
+ event_name TEXT NOT NULL,
65
+ data TEXT,
66
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
67
+ )
68
+ `);
69
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_events_created ON server_stats_events(created_at)`);
70
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_events_name ON server_stats_events(event_name)`);
71
+ }
72
+ export async function migrateEmails(db) {
73
+ await db.raw(`
74
+ CREATE TABLE IF NOT EXISTS server_stats_emails (
75
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
76
+ from_addr TEXT NOT NULL,
77
+ to_addr TEXT NOT NULL,
78
+ cc TEXT,
79
+ bcc TEXT,
80
+ subject TEXT NOT NULL,
81
+ html TEXT,
82
+ text_body TEXT,
83
+ mailer TEXT NOT NULL,
84
+ status TEXT NOT NULL,
85
+ message_id TEXT,
86
+ attachment_count INTEGER DEFAULT 0,
87
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
88
+ )
89
+ `);
90
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_emails_created ON server_stats_emails(created_at)`);
91
+ }
92
+ export async function migrateLogs(db) {
93
+ await db.raw(`
94
+ CREATE TABLE IF NOT EXISTS server_stats_logs (
95
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
96
+ level TEXT NOT NULL,
97
+ message TEXT NOT NULL,
98
+ request_id TEXT,
99
+ data TEXT,
100
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
101
+ )
102
+ `);
103
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_created ON server_stats_logs(created_at)`);
104
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_level ON server_stats_logs(level)`);
105
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_request ON server_stats_logs(request_id)`);
106
+ }
107
+ export async function migrateTraces(db) {
108
+ await db.raw(`
109
+ CREATE TABLE IF NOT EXISTS server_stats_traces (
110
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
111
+ request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
112
+ method TEXT NOT NULL,
113
+ url TEXT NOT NULL,
114
+ status_code INTEGER NOT NULL,
115
+ total_duration REAL NOT NULL,
116
+ span_count INTEGER DEFAULT 0,
117
+ spans TEXT NOT NULL,
118
+ warnings TEXT,
119
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
120
+ )
121
+ `);
122
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_traces_created ON server_stats_traces(created_at)`);
123
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_traces_request ON server_stats_traces(request_id)`);
124
+ }
125
+ export async function migrateMetrics(db) {
126
+ await db.raw(`
127
+ CREATE TABLE IF NOT EXISTS server_stats_metrics (
128
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
129
+ bucket TEXT NOT NULL,
130
+ request_count INTEGER DEFAULT 0,
131
+ avg_duration REAL DEFAULT 0,
132
+ p95_duration REAL DEFAULT 0,
133
+ error_count INTEGER DEFAULT 0,
134
+ query_count INTEGER DEFAULT 0,
135
+ avg_query_duration REAL DEFAULT 0,
136
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
137
+ )
138
+ `);
139
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_metrics_bucket ON server_stats_metrics(bucket)`);
140
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_metrics_created ON server_stats_metrics(created_at)`);
141
+ }
142
+ export async function migrateSavedFilters(db) {
143
+ await db.raw(`
144
+ CREATE TABLE IF NOT EXISTS server_stats_saved_filters (
145
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
146
+ name TEXT NOT NULL,
147
+ section TEXT NOT NULL,
148
+ filter_config TEXT NOT NULL,
149
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
150
+ )
151
+ `);
152
+ await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_filters_section ON server_stats_saved_filters(section)`);
153
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Pure data-mapping helpers for dashboard overview and chart queries.
3
+ *
4
+ * These functions transform raw database rows into the shapes
5
+ * expected by the dashboard API. No I/O — just mapping and math.
6
+ */
7
+ /**
8
+ * Build the overview metrics response from raw aggregated data.
9
+ * Pure function — no database access.
10
+ */
11
+ interface OverviewInput {
12
+ total: number;
13
+ stats: Record<string, unknown> | null | undefined;
14
+ range: string;
15
+ slowestEndpoints: Record<string, unknown>[];
16
+ queryStats: Record<string, unknown> | null | undefined;
17
+ recentErrors: Record<string, unknown>[];
18
+ }
19
+ export declare function buildOverviewResult(input: OverviewInput): Record<string, unknown>;
20
+ /**
21
+ * Map raw top events rows to the widget shape.
22
+ */
23
+ export declare function mapTopEvents(raw: Record<string, unknown>[] | null | undefined): {
24
+ eventName: string;
25
+ count: number;
26
+ }[];
27
+ /**
28
+ * Aggregate email status rows into sent/queued/failed counts.
29
+ */
30
+ export declare function mapEmailActivity(raw: Record<string, unknown>[] | null | undefined): {
31
+ sent: number;
32
+ queued: number;
33
+ failed: number;
34
+ };
35
+ /**
36
+ * Map log level rows to the breakdown shape.
37
+ */
38
+ export declare function mapLogLevelBreakdown(raw: Record<string, unknown>[] | null | undefined): {
39
+ error: number;
40
+ warn: number;
41
+ info: number;
42
+ debug: number;
43
+ };
44
+ /**
45
+ * Map status distribution row to the widget shape.
46
+ */
47
+ export declare function mapStatusDistribution(row: Record<string, unknown> | null | undefined): {
48
+ '2xx': number;
49
+ '3xx': number;
50
+ '4xx': number;
51
+ '5xx': number;
52
+ };
53
+ /**
54
+ * Map slowest query rows to the widget shape.
55
+ */
56
+ export declare function mapSlowestQueries(raw: Record<string, unknown>[] | null | undefined): {
57
+ sqlNormalized: string;
58
+ avgDuration: number;
59
+ count: number;
60
+ }[];
61
+ /**
62
+ * Aggregate per-minute metrics into larger buckets for 24h/7d ranges.
63
+ * For 1h/6h, rows are returned as-is.
64
+ */
65
+ export declare function aggregateChartBuckets(rows: Record<string, unknown>[], range: string): Record<string, unknown>[];
66
+ export {};