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
@@ -6,75 +6,70 @@ import { log } from '../utils/logger.js';
6
6
  import { loadTransmitClient } from '../utils/transmit_client.js';
7
7
  const DIR = dirname(fileURLToPath(import.meta.url));
8
8
  const read = (rel) => readFileSync(join(DIR, rel), 'utf-8');
9
+ /** Build concatenated CSS from component, utility, and stats-bar stylesheets. */
10
+ function buildCss() {
11
+ const componentsCss = read('../styles/components.css');
12
+ const utilitiesCss = read('../styles/utilities.css');
13
+ return componentsCss + '\n' + utilitiesCss + '\n' + read('../styles/stats-bar.css');
14
+ }
15
+ /** Resolve the client asset directory based on renderer config. */
16
+ function resolveClientDir(config) {
17
+ const renderer = config.devToolbar?.renderer || 'preact';
18
+ return renderer === 'vue' ? 'client-vue' : 'client';
19
+ }
20
+ /** Build the bar configuration object for the stats bar client. */
21
+ function buildBarConfig(config) {
22
+ const endpoint = typeof config.endpoint === 'string' ? config.endpoint : '/admin/api/server-stats';
23
+ const showDebug = !!config.devToolbar?.enabled;
24
+ const result = {
25
+ endpoint,
26
+ pollInterval: config.intervalMs || 3000,
27
+ channelName: config.channelName || 'admin/server-stats',
28
+ showDebug,
29
+ };
30
+ if (showDebug) {
31
+ addDebugBarConfig(config, result);
32
+ }
33
+ return result;
34
+ }
35
+ /** Add debug-specific fields to bar config. */
36
+ function addDebugBarConfig(config, result) {
37
+ result.debugEndpoint = config.devToolbar?.debugEndpoint || '/admin/api/debug';
38
+ result.dashboardPath = config.devToolbar?.dashboard
39
+ ? config.devToolbar.dashboardPath || '/__stats'
40
+ : null;
41
+ }
42
+ /** Build template state for the Edge stats-bar partial. */
43
+ function buildTemplateState(config, clientDir) {
44
+ const showDebug = !!config.devToolbar?.enabled;
45
+ const transmitClient = loadTransmitClient(join(process.cwd(), 'package.json'));
46
+ if (!transmitClient) {
47
+ log.info('@adonisjs/transmit-client not found — will use polling');
48
+ }
49
+ const state = {
50
+ css: buildCss(),
51
+ js: read(clientDir + '/stats-bar.js'),
52
+ barConfig: buildBarConfig(config),
53
+ showDebug,
54
+ transmitClient,
55
+ };
56
+ if (showDebug) {
57
+ state.debugCss = read('../styles/debug-panel.css');
58
+ state.debugDeferredJs = read(clientDir + '/debug-panel-deferred.js');
59
+ }
60
+ return state;
61
+ }
9
62
  /**
10
63
  * Edge plugin that registers the `@serverStats()` tag.
11
- *
12
- * - Mounts `views/` as the `ss` Edge disk for partials
13
- * - Reads CSS/JS client assets from `client/`
14
- * - Pre-renders the stats-bar template once at boot (via `Template` directly
15
- * to avoid the `#executePlugins` recursion from `edge.renderSync`)
16
- * - Registers `@serverStats()` tag that outputs the pre-rendered HTML
17
- *
18
- * Usage in the provider's `boot()` method:
19
- * ```ts
20
- * edge.use(edgePluginServerStats(config))
21
- * ```
22
- *
23
- * Usage in Edge templates:
24
- * ```edge
25
- * @serverStats()
26
- * ```
27
64
  */
28
65
  export function edgePluginServerStats(config) {
29
66
  return (edge) => {
30
- // Mount Edge views under the `ss` disk (needed for @include resolution)
31
67
  edge.mount('ss', join(DIR, 'views'));
32
- // Read client assets once at boot
33
- const componentsCss = read('../styles/components.css');
34
- const utilitiesCss = read('../styles/utilities.css');
35
- const css = componentsCss + '\n' + utilitiesCss + '\n' + read('../styles/stats-bar.css');
36
- const renderer = config.devToolbar?.renderer || 'preact';
37
- const clientDir = renderer === 'vue' ? 'client-vue' : 'client';
38
- const js = read(clientDir + '/stats-bar.js');
39
- const endpoint = typeof config.endpoint === 'string' ? config.endpoint : '/admin/api/server-stats';
40
- const intervalMs = config.intervalMs || 3000;
41
- const showDebug = !!config.devToolbar?.enabled;
42
- const channelName = config.channelName || 'admin/server-stats';
43
- const barConfig = {
44
- endpoint,
45
- pollInterval: intervalMs,
46
- channelName,
47
- showDebug,
48
- ...(showDebug && {
49
- debugEndpoint: config.devToolbar?.debugEndpoint || '/admin/api/debug',
50
- dashboardPath: config.devToolbar?.dashboard
51
- ? config.devToolbar.dashboardPath || '/__stats'
52
- : null,
53
- }),
54
- };
55
- // Always try to load the Transmit client — both the stats bar and the
56
- // debug panel use it for live (SSE) updates.
57
- const transmitClient = loadTransmitClient(join(process.cwd(), 'package.json'));
58
- if (!transmitClient) {
59
- log.info('@adonisjs/transmit-client not found — will use polling');
60
- }
61
- const state = {
62
- css,
63
- js,
64
- barConfig,
65
- showDebug,
66
- transmitClient,
67
- };
68
- if (showDebug) {
69
- state.debugCss = read('../styles/debug-panel.css');
70
- state.debugDeferredJs = read(clientDir + '/debug-panel-deferred.js');
71
- }
72
- // Pre-render via Template directly — bypasses edge.createRenderer() which
73
- // would re-run #executePlugins and cause infinite recursion.
68
+ const clientDir = resolveClientDir(config);
69
+ const state = buildTemplateState(config, clientDir);
74
70
  const template = new Template(edge.compiler, edge.globals, {}, edge.processor);
75
71
  const html = template.render('ss::stats-bar', state);
76
72
  const escaped = JSON.stringify(html);
77
- // Track whether shouldShow is configured (controls render-time guard)
78
73
  const hasShouldShow = !!config.shouldShow;
79
74
  edge.registerTag({
80
75
  tagName: 'serverStats',
@@ -82,13 +77,11 @@ export function edgePluginServerStats(config) {
82
77
  seekable: true,
83
78
  compile(_parser, buffer, token) {
84
79
  if (hasShouldShow) {
85
- // Guard: call the lazy __ssShowFn at render time (after auth middleware has run)
86
- buffer.writeStatement(`if (typeof state.__ssShowFn === 'function' ? state.__ssShowFn() : false) {`, token.filename, token.loc.start.line);
80
+ buffer.writeStatement("if (typeof state.__ssShowFn === 'function' ? state.__ssShowFn() : false) {", token.filename, token.loc.start.line);
87
81
  buffer.outputExpression(escaped, token.filename, token.loc.start.line, false);
88
- buffer.writeStatement(`}`, token.filename, -1);
82
+ buffer.writeStatement('}', token.filename, -1);
89
83
  }
90
84
  else {
91
- // No shouldShow configured — always render
92
85
  buffer.outputExpression(escaped, token.filename, token.loc.start.line, false);
93
86
  }
94
87
  },
@@ -1,4 +1,5 @@
1
1
  export declare class RequestMetrics {
2
+ #private;
2
3
  private records;
3
4
  private writeIndex;
4
5
  private count;
@@ -1,4 +1,10 @@
1
1
  import { performance } from 'node:perf_hooks';
2
+ function accumulateRecord(acc, r) {
3
+ acc.validCount++;
4
+ acc.totalDuration += r.durationMs;
5
+ if (r.statusCode >= 500)
6
+ acc.errorCount++;
7
+ }
2
8
  export class RequestMetrics {
3
9
  records = [];
4
10
  writeIndex = 0;
@@ -34,55 +40,39 @@ export class RequestMetrics {
34
40
  getMetrics() {
35
41
  const now = performance.now();
36
42
  const cutoff = now - this.windowMs;
37
- let totalDuration = 0;
38
- let errorCount = 0;
39
- let validCount = 0;
40
- // When the buffer is full (ring buffer mode), scan from the oldest
41
- // entry (writeIndex) forward. Records are in chronological order
42
- // within each wrap, so once we find one >= cutoff, all subsequent
43
- // records in that direction are also valid — but since they wrap,
44
- // we still need to check all slots. The key optimization is that
45
- // we can skip already-overwritten (stale) slots efficiently.
43
+ const acc = { validCount: 0, totalDuration: 0, errorCount: 0 };
46
44
  if (this.count === this.maxRecords) {
47
- // Full ring buffer — scan from writeIndex (oldest) to writeIndex-1 (newest)
48
- for (let j = 0; j < this.maxRecords; j++) {
49
- const idx = (this.writeIndex + j) % this.maxRecords;
50
- const r = this.records[idx];
51
- if (r.timestamp >= cutoff) {
52
- validCount++;
53
- totalDuration += r.durationMs;
54
- if (r.statusCode >= 500) {
55
- errorCount++;
56
- }
57
- }
58
- }
45
+ this.#scanFullBuffer(cutoff, acc);
59
46
  }
60
47
  else {
61
- // Buffer not yet full — records are in order 0..count-1
62
- // Scan backwards from newest to find the cutoff point, then
63
- // aggregate only the valid tail.
64
- let startIdx = 0;
65
- for (let i = this.count - 1; i >= 0; i--) {
66
- if (this.records[i].timestamp < cutoff) {
67
- startIdx = i + 1;
68
- break;
69
- }
70
- }
71
- for (let i = startIdx; i < this.count; i++) {
72
- const r = this.records[i];
73
- validCount++;
74
- totalDuration += r.durationMs;
75
- if (r.statusCode >= 500) {
76
- errorCount++;
77
- }
78
- }
48
+ this.#scanPartialBuffer(cutoff, acc);
79
49
  }
80
50
  const windowSeconds = this.windowMs / 1000;
81
51
  return {
82
- requestsPerSecond: validCount / windowSeconds,
83
- averageResponseTimeMs: validCount > 0 ? totalDuration / validCount : 0,
84
- errorRate: validCount > 0 ? (errorCount / validCount) * 100 : 0,
52
+ requestsPerSecond: acc.validCount / windowSeconds,
53
+ averageResponseTimeMs: acc.validCount > 0 ? acc.totalDuration / acc.validCount : 0,
54
+ errorRate: acc.validCount > 0 ? (acc.errorCount / acc.validCount) * 100 : 0,
85
55
  activeConnections: this.activeConnections,
86
56
  };
87
57
  }
58
+ #scanFullBuffer(cutoff, acc) {
59
+ for (let j = 0; j < this.maxRecords; j++) {
60
+ const idx = (this.writeIndex + j) % this.maxRecords;
61
+ const r = this.records[idx];
62
+ if (r.timestamp >= cutoff)
63
+ accumulateRecord(acc, r);
64
+ }
65
+ }
66
+ #scanPartialBuffer(cutoff, acc) {
67
+ let startIdx = 0;
68
+ for (let i = this.count - 1; i >= 0; i--) {
69
+ if (this.records[i].timestamp < cutoff) {
70
+ startIdx = i + 1;
71
+ break;
72
+ }
73
+ }
74
+ for (let i = startIdx; i < this.count; i++) {
75
+ accumulateRecord(acc, this.records[i]);
76
+ }
77
+ }
88
78
  }
@@ -2,19 +2,13 @@ import type { TraceCollector } from '../debug/trace_collector.js';
2
2
  import type { TraceRecord } from '../debug/types.js';
3
3
  import type { HttpContext } from '@adonisjs/core/http';
4
4
  import type { NextFn } from '@adonisjs/core/types/http';
5
- /**
6
- * Returns true if the current async context is inside an excluded request
7
- * (e.g. a debug panel polling request). Used by collectors to skip
8
- * self-generated data.
9
- */
5
+ /** Returns true if the current async context is inside an excluded request. */
10
6
  export declare function isExcludedRequest(): boolean;
11
7
  export declare function setShouldShow(fn: ((ctx: HttpContext) => boolean) | null): void;
12
8
  export declare function setTraceCollector(collector: TraceCollector | null): void;
13
9
  export declare function setDashboardPath(path: string | null): void;
14
10
  export declare function setExcludedPrefixes(prefixes: string[]): void;
15
- /**
16
- * Data passed to the onRequestComplete callback.
17
- */
11
+ /** Data passed to the onRequestComplete callback. */
18
12
  export interface RequestCompleteData {
19
13
  method: string;
20
14
  url: string;
@@ -2,74 +2,100 @@ import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import { performance } from 'node:perf_hooks';
3
3
  import { getRequestMetrics } from '../collectors/http_collector.js';
4
4
  import { log } from '../utils/logger.js';
5
- /**
6
- * AsyncLocalStorage that marks the current request as "excluded" from
7
- * debug collection. Checked by QueryCollector and EventCollector to
8
- * skip queries/events triggered by the debug panel's own polling.
9
- */
10
5
  const excludedRequestAls = new AsyncLocalStorage();
11
- /**
12
- * Returns true if the current async context is inside an excluded request
13
- * (e.g. a debug panel polling request). Used by collectors to skip
14
- * self-generated data.
15
- */
6
+ /** Returns true if the current async context is inside an excluded request. */
16
7
  export function isExcludedRequest() {
17
8
  return excludedRequestAls.getStore() === true;
18
9
  }
19
- /**
20
- * Warn-once guard for shouldShow callback failures.
21
- */
22
10
  let warnedShouldShow = false;
23
- /**
24
- * Module-level `shouldShow` callback, set by the provider at boot.
25
- */
26
11
  let shouldShowFn = null;
27
12
  export function setShouldShow(fn) {
28
13
  shouldShowFn = fn;
29
14
  }
30
- /**
31
- * Module-level trace collector, set by the provider when tracing is enabled.
32
- */
33
15
  let traceCollector = null;
34
16
  export function setTraceCollector(collector) {
35
17
  traceCollector = collector;
36
18
  }
37
- /**
38
- * Module-level dashboard path, set by the provider when dashboard is enabled.
39
- * Requests to this path (and sub-paths) are excluded from metrics and tracing.
40
- */
41
19
  let dashboardPath = null;
42
20
  export function setDashboardPath(path) {
43
21
  dashboardPath = path;
44
22
  }
45
- /**
46
- * Module-level list of URL prefixes to exclude from tracing and onRequestComplete.
47
- * Used to filter out the debug panel's own polling requests (e.g. /admin/api/debug/,
48
- * /admin/api/server-stats) so they don't flood the timeline.
49
- */
50
23
  let excludedPrefixes = [];
51
24
  export function setExcludedPrefixes(prefixes) {
52
25
  excludedPrefixes = prefixes;
53
26
  }
54
- /**
55
- * Module-level callback fired after each request completes.
56
- * Used by the provider to pipe request data to the DashboardStore.
57
- */
58
27
  let onRequestCompleteFn = null;
59
28
  export function setOnRequestComplete(fn) {
60
29
  onRequestCompleteFn = fn;
61
30
  }
31
+ /** Share a lazy shouldShow evaluator with Edge for @serverStats() tag. */
32
+ function shareShouldShowWithEdge(ctx) {
33
+ const ctxView = ctx.view;
34
+ if (!shouldShowFn || typeof ctxView?.share !== 'function')
35
+ return;
36
+ ctxView.share({
37
+ __ssShowFn: () => {
38
+ try {
39
+ return shouldShowFn(ctx);
40
+ }
41
+ catch (err) {
42
+ if (!warnedShouldShow) {
43
+ warnedShouldShow = true;
44
+ log.warn('shouldShow callback threw — stats bar will be hidden: ' + err?.message);
45
+ }
46
+ return false;
47
+ }
48
+ },
49
+ });
50
+ }
51
+ /** Record request metrics, finish trace, and fire the completion callback. */
52
+ function recordRequestCompletion(opts) {
53
+ const duration = performance.now() - opts.start;
54
+ opts.metrics.decrementActiveConnections();
55
+ opts.metrics.recordRequest(duration, opts.ctx.response.getStatus());
56
+ if (opts.skipTracing)
57
+ return;
58
+ const reqId = typeof opts.ctx.request.id === 'function' ? String(opts.ctx.request.id()) : undefined;
59
+ const traceRecord = traceCollector?.finishTrace(opts.ctx.request.method(), opts.ctx.request.url(true), opts.ctx.response.getStatus(), reqId);
60
+ onRequestCompleteFn?.({
61
+ method: opts.ctx.request.method(),
62
+ url: opts.ctx.request.url(true),
63
+ statusCode: opts.ctx.response.getStatus(),
64
+ duration,
65
+ trace: traceRecord ?? undefined,
66
+ httpRequestId: reqId,
67
+ });
68
+ }
69
+ /** Build the inner request handler that calls next() and records completion. */
70
+ function buildRequestRunner(opts) {
71
+ return async () => {
72
+ try {
73
+ await opts.next();
74
+ }
75
+ finally {
76
+ recordRequestCompletion(opts);
77
+ }
78
+ };
79
+ }
80
+ /** Dispatch the request runner through tracing, ALS, or direct execution. */
81
+ async function dispatchRequest(runRequest, skipTracing) {
82
+ if (traceCollector && !skipTracing) {
83
+ await traceCollector.startTrace(runRequest);
84
+ }
85
+ else if (skipTracing) {
86
+ await excludedRequestAls.run(true, runRequest);
87
+ }
88
+ else {
89
+ await runRequest();
90
+ }
91
+ }
62
92
  export default class RequestTrackingMiddleware {
63
93
  async handle(ctx, next) {
64
- // Self-exclude: skip metrics and tracing for dashboard routes
65
94
  const requestUrl = ctx.request.url(true);
66
95
  if (dashboardPath && requestUrl.startsWith(dashboardPath)) {
67
96
  await next();
68
97
  return;
69
98
  }
70
- // Gracefully handle the startup window before collectors initialize.
71
- // The provider defers initialization via setImmediate, so early
72
- // requests may arrive before httpCollector() has been called.
73
99
  const metrics = getRequestMetrics();
74
100
  if (!metrics) {
75
101
  await next();
@@ -77,63 +103,9 @@ export default class RequestTrackingMiddleware {
77
103
  }
78
104
  const start = performance.now();
79
105
  metrics.incrementActiveConnections();
80
- // Share a lazy shouldShow evaluator with Edge for @serverStats() tag.
81
- // Must be lazy (a function, not a boolean) because this server middleware
82
- // runs BEFORE router middleware like initialize_auth_middleware and
83
- // silentAuth — so ctx.auth isn't populated yet. The function is called
84
- // at Edge render time (inside the controller), when auth is available.
85
- const ctxView = ctx.view;
86
- if (shouldShowFn && typeof ctxView?.share === 'function') {
87
- ctxView.share({
88
- __ssShowFn: () => {
89
- try {
90
- return shouldShowFn(ctx);
91
- }
92
- catch (err) {
93
- if (!warnedShouldShow) {
94
- warnedShouldShow = true;
95
- log.warn('shouldShow callback threw — stats bar will be hidden: ' + err?.message);
96
- }
97
- return false;
98
- }
99
- },
100
- });
101
- }
102
- // Skip tracing and dashboard persistence for the debug panel's own requests
103
- // (e.g. /admin/api/debug/*, /admin/api/server-stats) so they don't flood
104
- // the timeline. HTTP metrics (req/s, avg latency) are still recorded.
106
+ shareShouldShowWithEdge(ctx);
105
107
  const skipTracing = excludedPrefixes.some((prefix) => requestUrl.startsWith(prefix));
106
- const runRequest = async () => {
107
- try {
108
- await next();
109
- }
110
- finally {
111
- const duration = performance.now() - start;
112
- metrics.decrementActiveConnections();
113
- metrics.recordRequest(duration, ctx.response.getStatus());
114
- if (!skipTracing) {
115
- const reqId = typeof ctx.request.id === 'function' ? String(ctx.request.id()) : undefined;
116
- const traceRecord = traceCollector?.finishTrace(ctx.request.method(), ctx.request.url(true), ctx.response.getStatus(), reqId);
117
- onRequestCompleteFn?.({
118
- method: ctx.request.method(),
119
- url: ctx.request.url(true),
120
- statusCode: ctx.response.getStatus(),
121
- duration,
122
- trace: traceRecord ?? undefined,
123
- httpRequestId: reqId,
124
- });
125
- }
126
- }
127
- };
128
- if (traceCollector && !skipTracing) {
129
- await traceCollector.startTrace(runRequest);
130
- }
131
- else if (skipTracing) {
132
- // Run inside ALS so collectors can check isExcludedRequest()
133
- await excludedRequestAls.run(true, runRequest);
134
- }
135
- else {
136
- await runRequest();
137
- }
108
+ const runRequest = buildRequestRunner({ ctx, next, start, metrics, skipTracing });
109
+ await dispatchRequest(runRequest, skipTracing);
138
110
  }
139
111
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Parse source code and detect auth-related middleware
3
+ * in `server.use()` or `router.use()` blocks.
4
+ *
5
+ * This is a pure function that operates on source text.
6
+ */
7
+ export declare function detectAuthMiddlewareInSource(source: string): string[];
8
+ /**
9
+ * Read `start/kernel.{ts,js}` from the app root and detect global auth
10
+ * middleware. Returns an empty array if the file cannot be read.
11
+ */
12
+ export declare function detectGlobalAuthMiddleware(makePath: (dir: string, file: string) => string): string[];
13
+ /**
14
+ * Build the warning message lines for detected auth middleware.
15
+ */
16
+ export declare function buildAuthMiddlewareWarning(found: string[], dimFn: (s: string) => string, boldFn: (s: string) => string): string[];
@@ -0,0 +1,97 @@
1
+ import { readFileSync } from 'node:fs';
2
+ /**
3
+ * Check if an import path refers to an auth-related middleware
4
+ * (excluding `initialize_auth` which only sets up ctx.auth).
5
+ */
6
+ function isAuthMiddleware(importPath) {
7
+ if (importPath.includes('initialize_auth'))
8
+ return false;
9
+ return (importPath.includes('auth') ||
10
+ importPath.includes('silent_auth') ||
11
+ importPath.includes('silentAuth'));
12
+ }
13
+ /**
14
+ * Extract import paths from a `server.use([...])` or `router.use([...])`
15
+ * block that match auth-related middleware.
16
+ */
17
+ function extractAuthImportsFromBlock(block) {
18
+ const importRegex = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
19
+ const results = [];
20
+ let importMatch;
21
+ while ((importMatch = importRegex.exec(block)) !== null) {
22
+ const importPath = importMatch[1];
23
+ if (isAuthMiddleware(importPath)) {
24
+ results.push(importPath);
25
+ }
26
+ }
27
+ return results;
28
+ }
29
+ /**
30
+ * Parse source code and detect auth-related middleware
31
+ * in `server.use()` or `router.use()` blocks.
32
+ *
33
+ * This is a pure function that operates on source text.
34
+ */
35
+ export function detectAuthMiddlewareInSource(source) {
36
+ if (!source)
37
+ return [];
38
+ const found = [];
39
+ const useBlockRegex = /(?:server|router)\.use\(\s*\[([\s\S]*?)\]\s*\)/g;
40
+ let match;
41
+ while ((match = useBlockRegex.exec(source)) !== null) {
42
+ found.push(...extractAuthImportsFromBlock(match[1]));
43
+ }
44
+ return found;
45
+ }
46
+ /**
47
+ * Read the kernel source from disk, trying `.ts` then `.js` extensions.
48
+ */
49
+ function readKernelSource(makePath) {
50
+ for (const ext of ['ts', 'js']) {
51
+ try {
52
+ const source = readFileSync(makePath('start', `kernel.${ext}`), 'utf-8');
53
+ if (source)
54
+ return source;
55
+ }
56
+ catch {
57
+ // Try next extension
58
+ }
59
+ }
60
+ return '';
61
+ }
62
+ /**
63
+ * Read `start/kernel.{ts,js}` from the app root and detect global auth
64
+ * middleware. Returns an empty array if the file cannot be read.
65
+ */
66
+ export function detectGlobalAuthMiddleware(makePath) {
67
+ try {
68
+ return detectAuthMiddlewareInSource(readKernelSource(makePath));
69
+ }
70
+ catch {
71
+ return [];
72
+ }
73
+ }
74
+ /**
75
+ * Build the warning message lines for detected auth middleware.
76
+ */
77
+ export function buildAuthMiddlewareWarning(found, dimFn, boldFn) {
78
+ return [
79
+ ...found.map((m) => dimFn('→') + ' ' + m),
80
+ '',
81
+ dimFn('these routes get polled every ~3s, so auth middleware will'),
82
+ dimFn('trigger a DB query on each poll. here are two ways to fix it:'),
83
+ '',
84
+ boldFn('option 1:') + ' add a shouldShow callback to your config:',
85
+ '',
86
+ dimFn('// config/server_stats.ts'),
87
+ dimFn("shouldShow: (ctx) => ctx.auth?.user?.role === 'admin'"),
88
+ '',
89
+ boldFn('option 2:') + ' move auth middleware from router.use() to a route group:',
90
+ '',
91
+ dimFn('// start/kernel.ts — remove from router.use()'),
92
+ dimFn("// () => import('#middleware/silent_auth_middleware')"),
93
+ '',
94
+ dimFn('// start/routes.ts — add to your route groups instead'),
95
+ dimFn('router.group(() => { ... }).use(middleware.silentAuth())'),
96
+ ];
97
+ }
@@ -0,0 +1,20 @@
1
+ import type { ResolvedServerStatsConfig } from '../types.js';
2
+ export declare function deriveEndpointPaths(endpoint: string | false | undefined, devToolbar?: {
3
+ enabled?: boolean;
4
+ debugEndpoint?: string;
5
+ dashboard?: boolean;
6
+ dashboardPath?: string;
7
+ }): {
8
+ statsEndpoint: string | false;
9
+ debugEndpoint: string | undefined;
10
+ };
11
+ export declare function computeDashboardPath(devToolbar?: {
12
+ enabled?: boolean;
13
+ dashboard?: boolean;
14
+ dashboardPath?: string;
15
+ }, depsAvailable?: boolean): string | undefined;
16
+ export declare function collectRegisteredPaths(statsEndpoint: string | false, debugEndpoint?: string, dashboardPath?: string): string[];
17
+ export declare function checkDashboardDeps(appImport: (name: string) => Promise<unknown>): Promise<string[]>;
18
+ export declare function logMissingDeps(missing: string[]): void;
19
+ export declare function warnAboutAuthMiddleware(config: ResolvedServerStatsConfig, makePath: (dir: string, file: string) => string): void;
20
+ export declare function logDashboardError(category: 'missing-dep' | 'timeout' | 'unknown', err: unknown): void;