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,43 @@
1
+ /**
2
+ * Email bridge helpers for cross-process email capture via Redis pub/sub.
3
+ */
4
+ /** Minimal emitter interface. */
5
+ interface BridgeEmitter {
6
+ on(event: string, handler: (...args: unknown[]) => void): void;
7
+ }
8
+ /** Minimal Redis publisher interface. */
9
+ interface RedisPublisher {
10
+ publish(channel: string, message: string): Promise<unknown>;
11
+ }
12
+ /** Minimal Redis subscriber interface. */
13
+ interface RedisSubscriber extends RedisPublisher {
14
+ subscribe(channel: string, handler: (message: string) => void): unknown;
15
+ }
16
+ /** Targets for ingesting remote emails. */
17
+ export interface EmailBridgeTargets {
18
+ debugEmails: {
19
+ ingest(record: Record<string, unknown>): void;
20
+ } | null;
21
+ dashboardStore: {
22
+ recordEmail(record: Record<string, unknown>): void;
23
+ } | null;
24
+ }
25
+ /**
26
+ * Ingest a remote email message from Redis pub/sub.
27
+ * Skips messages from the same process.
28
+ */
29
+ export declare function ingestRemoteEmail(message: string, processTag: string, targets: EmailBridgeTargets): void;
30
+ /**
31
+ * Register mail event listeners that publish to Redis.
32
+ */
33
+ export declare function registerMailEventPublisher(emitter: BridgeEmitter, redis: RedisPublisher, processTag: string, channel: string): void;
34
+ /**
35
+ * Full email bridge setup: publish local events + subscribe to remote ones.
36
+ * Returns the Redis instance (for cleanup) or null.
37
+ */
38
+ export declare function setupFullEmailBridge(emitter: BridgeEmitter, redis: RedisSubscriber, channel: string, targets: EmailBridgeTargets): Promise<unknown>;
39
+ /**
40
+ * Lightweight publisher-only email bridge (for non-web environments).
41
+ */
42
+ export declare function setupPublisherOnlyBridge(emitter: BridgeEmitter, redis: RedisPublisher, channel: string): void;
43
+ export {};
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Email bridge helpers for cross-process email capture via Redis pub/sub.
3
+ */
4
+ import { log } from '../utils/logger.js';
5
+ import { buildEmailPayload, MAIL_STATUS_MAP } from './email_helpers.js';
6
+ /**
7
+ * Ingest a remote email message from Redis pub/sub.
8
+ * Skips messages from the same process.
9
+ */
10
+ export function ingestRemoteEmail(message, processTag, targets) {
11
+ try {
12
+ const parsed = JSON.parse(message);
13
+ if (parsed._t === processTag)
14
+ return;
15
+ const { _t: _, ...fields } = parsed;
16
+ const record = {
17
+ ...fields,
18
+ html: fields.html || null,
19
+ text: fields.text || null,
20
+ };
21
+ targets.debugEmails?.ingest(record);
22
+ targets.dashboardStore?.recordEmail({ id: 0, ...record });
23
+ }
24
+ catch {
25
+ // Ignore malformed messages
26
+ }
27
+ }
28
+ /**
29
+ * Register mail event listeners that publish to Redis.
30
+ */
31
+ export function registerMailEventPublisher(emitter, redis, processTag, channel) {
32
+ for (const [event, status] of MAIL_STATUS_MAP) {
33
+ emitter.on(event, (data) => {
34
+ try {
35
+ const payload = JSON.stringify(buildEmailPayload(data, status, processTag));
36
+ redis.publish(channel, payload).catch(() => { });
37
+ }
38
+ catch {
39
+ // Silently ignore serialization errors
40
+ }
41
+ });
42
+ }
43
+ }
44
+ /**
45
+ * Subscribe to Redis channel for cross-process email capture.
46
+ */
47
+ async function subscribeToEmailBridge(opts) {
48
+ try {
49
+ await opts.redis.subscribe(opts.channel, (message) => {
50
+ ingestRemoteEmail(message, opts.processTag, opts.targets);
51
+ });
52
+ log.info('email bridge active (cross-process capture via Redis)');
53
+ return opts.redis;
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ /**
60
+ * Full email bridge setup: publish local events + subscribe to remote ones.
61
+ * Returns the Redis instance (for cleanup) or null.
62
+ */
63
+ export async function setupFullEmailBridge(emitter, redis, channel, targets) {
64
+ const processTag = `${process.pid}-${Date.now()}`;
65
+ registerMailEventPublisher(emitter, redis, processTag, channel);
66
+ return subscribeToEmailBridge({
67
+ redis,
68
+ channel,
69
+ processTag,
70
+ targets,
71
+ });
72
+ }
73
+ /**
74
+ * Lightweight publisher-only email bridge (for non-web environments).
75
+ */
76
+ export function setupPublisherOnlyBridge(emitter, redis, channel) {
77
+ const tag = `${process.pid}-${Date.now()}`;
78
+ registerMailEventPublisher(emitter, redis, tag, channel);
79
+ log.info('email bridge publisher active (queue worker → Redis)');
80
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Cap an HTML/text body to a maximum size.
3
+ * Returns `null` for falsy or non-string inputs.
4
+ */
5
+ export declare function capHtmlSize(v: unknown, maxSize?: number): string | null;
6
+ /**
7
+ * Mapping of AdonisJS mail events to their corresponding status strings.
8
+ */
9
+ export declare const MAIL_STATUS_MAP: Array<[string, string]>;
10
+ /**
11
+ * Build a serializable email payload from a mail event.
12
+ */
13
+ export declare function buildEmailPayload(data: unknown, status: string, processTag: string): Record<string, unknown>;
@@ -0,0 +1,68 @@
1
+ import { extractAddresses } from '../utils/mail_helpers.js';
2
+ const MAX_HTML = 50_000;
3
+ /**
4
+ * Cap an HTML/text body to a maximum size.
5
+ * Returns `null` for falsy or non-string inputs.
6
+ */
7
+ export function capHtmlSize(v, maxSize = MAX_HTML) {
8
+ if (!v || typeof v !== 'string')
9
+ return null;
10
+ if (v.length <= maxSize)
11
+ return v;
12
+ return v.slice(0, maxSize) + '\n<!-- truncated -->';
13
+ }
14
+ /**
15
+ * Mapping of AdonisJS mail events to their corresponding status strings.
16
+ */
17
+ export const MAIL_STATUS_MAP = [
18
+ ['mail:sending', 'sending'],
19
+ ['mail:sent', 'sent'],
20
+ ['mail:queueing', 'queueing'],
21
+ ['mail:queued', 'queued'],
22
+ ['queued:mail:error', 'failed'],
23
+ ];
24
+ function extractMessage(data) {
25
+ return ((data?.message || data) ?? {});
26
+ }
27
+ function extractMailer(data) {
28
+ return data?.mailerName || data?.mailer || 'unknown';
29
+ }
30
+ function extractMessageId(data) {
31
+ const response = data?.response;
32
+ return response?.messageId || data?.messageId || null;
33
+ }
34
+ function addressOrDefault(value, fallback) {
35
+ return extractAddresses(value) || fallback;
36
+ }
37
+ function extractAddressFields(msg) {
38
+ return {
39
+ from: addressOrDefault(msg?.from, 'unknown'),
40
+ to: addressOrDefault(msg?.to, 'unknown'),
41
+ cc: extractAddresses(msg?.cc) || null,
42
+ bcc: extractAddresses(msg?.bcc) || null,
43
+ };
44
+ }
45
+ function extractContentFields(msg) {
46
+ return {
47
+ subject: msg?.subject || '(no subject)',
48
+ html: capHtmlSize(msg?.html),
49
+ text: capHtmlSize(msg?.text),
50
+ attachmentCount: Array.isArray(msg?.attachments) ? msg.attachments.length : 0,
51
+ };
52
+ }
53
+ /**
54
+ * Build a serializable email payload from a mail event.
55
+ */
56
+ export function buildEmailPayload(data, status, processTag) {
57
+ const d = (data ?? {});
58
+ const msg = extractMessage(d);
59
+ return {
60
+ _t: processTag,
61
+ ...extractAddressFields(msg),
62
+ ...extractContentFields(msg),
63
+ mailer: extractMailer(d),
64
+ status,
65
+ messageId: extractMessageId(d),
66
+ timestamp: Date.now(),
67
+ };
68
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Pure helpers for hooking into pino's internal stream.
3
+ */
4
+ /**
5
+ * Find the `pino.stream` symbol on an object by description.
6
+ * Pino uses a local Symbol('pino.stream'), not a global registry symbol.
7
+ */
8
+ export declare function findPinoStreamSymbol(obj: object): symbol | undefined;
9
+ /**
10
+ * Wrap a stream's `write` method to intercept JSON log entries.
11
+ * Calls `ingest` for each valid JSON entry that has a numeric `level`.
12
+ * The original write is always called to preserve normal logging.
13
+ */
14
+ export declare function wrapWriteMethod(stream: {
15
+ write: Function;
16
+ [key: string]: unknown;
17
+ }, ingest: (entry: Record<string, unknown>) => void): void;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Pure helpers for hooking into pino's internal stream.
3
+ */
4
+ /**
5
+ * Find the `pino.stream` symbol on an object by description.
6
+ * Pino uses a local Symbol('pino.stream'), not a global registry symbol.
7
+ */
8
+ export function findPinoStreamSymbol(obj) {
9
+ return Object.getOwnPropertySymbols(obj).find((s) => s.description === 'pino.stream');
10
+ }
11
+ /**
12
+ * Wrap a stream's `write` method to intercept JSON log entries.
13
+ * Calls `ingest` for each valid JSON entry that has a numeric `level`.
14
+ * The original write is always called to preserve normal logging.
15
+ */
16
+ export function wrapWriteMethod(stream, ingest) {
17
+ const originalWrite = stream.write.bind(stream);
18
+ stream.write = function wrappedWrite(chunk, ...args) {
19
+ try {
20
+ const str = typeof chunk === 'string'
21
+ ? chunk
22
+ : chunk instanceof Uint8Array
23
+ ? new TextDecoder().decode(chunk)
24
+ : String(chunk);
25
+ const entry = JSON.parse(str);
26
+ if (entry && typeof entry.level === 'number') {
27
+ ingest(entry);
28
+ }
29
+ }
30
+ catch {
31
+ // Not valid JSON — ignore (e.g. pino-pretty output)
32
+ }
33
+ return originalWrite(chunk, ...args);
34
+ };
35
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Provider helper functions extracted from ServerStatsProvider.
3
+ * Handles pino hooking, log stream broadcast, stats interval,
4
+ * dependency checking, edge plugin, and non-web bridge.
5
+ *
6
+ * Dashboard/toolbar setup is in ./toolbar_setup.ts and re-exported here.
7
+ */
8
+ import { LogStreamService } from '../log_stream/log_stream_service.js';
9
+ import type { DevToolbarConfig } from '../debug/types.js';
10
+ import type { StatsEngine } from '../engine/stats_engine.js';
11
+ import type { ResolvedServerStatsConfig } from '../types.js';
12
+ import type { ToolbarCoreResult, ProviderFields } from './toolbar_setup.js';
13
+ import type { ApplicationService } from '@adonisjs/core/types';
14
+ export { initDashboardStore } from './dashboard_init.js';
15
+ interface SetupDevToolbarArgs {
16
+ tc: DevToolbarConfig;
17
+ config: ResolvedServerStatsConfig;
18
+ app: ApplicationService;
19
+ resolve: (binding: string) => Promise<unknown>;
20
+ getDiagnostics: () => unknown;
21
+ }
22
+ /** Wrapper matching the provider's call signature. */
23
+ export declare function setupDevToolbarCore(args: SetupDevToolbarArgs): Promise<ToolbarCoreResult | null>;
24
+ /** Wrapper matching the provider's 3-arg call signature. */
25
+ export declare function applyToolbarResult(result: ToolbarCoreResult, tc: DevToolbarConfig, provider: ProviderFields): void;
26
+ /** Hook the AdonisJS Pino logger into the log collector. Returns true if hooked. */
27
+ export declare function hookPinoToLogStream(logger: unknown): boolean;
28
+ /** Set up log stream broadcasting via Transmit. Returns LogStreamService if file-based fallback. */
29
+ export declare function setupLogStreamBroadcast(transmit: {
30
+ broadcast(ch: string, d: unknown): void;
31
+ }, channelName: string, pinoHookActive: boolean, makePath: (...parts: string[]) => string): LogStreamService | null;
32
+ /** Check if dashboard dependencies are available. Returns true if available, false if missing. */
33
+ export declare function checkDashboardDepsHelper(config: ResolvedServerStatsConfig, _app: ApplicationService): Promise<boolean>;
34
+ /** Register the Edge.js plugin if Edge is available. Returns true if registered. */
35
+ export declare function registerEdgePluginHelper(app: ApplicationService, config: ResolvedServerStatsConfig): Promise<boolean>;
36
+ /** Set up the publisher-only email bridge for non-web environments. */
37
+ export declare function setupNonWebBridgeHelper(emitter: unknown, channel: string): Promise<void>;
38
+ interface StatsIntervalResult {
39
+ intervalId: ReturnType<typeof setInterval>;
40
+ transmitAvailable: boolean;
41
+ prometheusActive: boolean;
42
+ channelName: string | null;
43
+ }
44
+ /** Set up the stats collection interval with transmit and prometheus integration. */
45
+ export declare function setupStatsIntervalHelper(engine: StatsEngine, config: ResolvedServerStatsConfig, container: {
46
+ make(b: string): Promise<unknown>;
47
+ }): Promise<StatsIntervalResult>;
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Provider helper functions extracted from ServerStatsProvider.
3
+ * Handles pino hooking, log stream broadcast, stats interval,
4
+ * dependency checking, edge plugin, and non-web bridge.
5
+ *
6
+ * Dashboard/toolbar setup is in ./toolbar_setup.ts and re-exported here.
7
+ */
8
+ import { getLogStreamService } from '../collectors/log_collector.js';
9
+ import { LogStreamService } from '../log_stream/log_stream_service.js';
10
+ import { log, dim, bold } from '../utils/logger.js';
11
+ import { setupPublisherOnlyBridge } from './email_bridge.js';
12
+ import { findPinoStreamSymbol, wrapWriteMethod } from './pino_hook.js';
13
+ // Re-export toolbar/dashboard functions with provider-compatible signatures
14
+ import { setupDevToolbarCore as coreSetup, applyToolbarResult as coreApply, } from './toolbar_setup.js';
15
+ export { initDashboardStore } from './dashboard_init.js';
16
+ /** Wrapper matching the provider's call signature. */
17
+ export async function setupDevToolbarCore(args) {
18
+ return coreSetup(args);
19
+ }
20
+ /** Wrapper matching the provider's 3-arg call signature. */
21
+ export function applyToolbarResult(result, tc, provider) {
22
+ coreApply(result, tc, provider);
23
+ }
24
+ // ── hookPinoToLogStream ─────────────────────────────────────────
25
+ /** Hook the AdonisJS Pino logger into the log collector. Returns true if hooked. */
26
+ export function hookPinoToLogStream(logger) {
27
+ const logStream = getLogStreamService();
28
+ if (!logStream)
29
+ return false;
30
+ const pino = logger?.pino;
31
+ if (!pino)
32
+ return false;
33
+ const streamSym = findPinoStreamSymbol(pino);
34
+ if (!streamSym)
35
+ return false;
36
+ const rawStream = pino[streamSym];
37
+ if (!rawStream)
38
+ return false;
39
+ if (typeof rawStream.write !== 'function')
40
+ return false;
41
+ wrapWriteMethod(rawStream, (entry) => logStream.ingest(entry));
42
+ log.info('log collector hooked into AdonisJS logger (zero-config)');
43
+ return true;
44
+ }
45
+ // ── setupLogStreamBroadcast ─────────────────────────────────────
46
+ /** Set up log stream broadcasting via Transmit. Returns LogStreamService if file-based fallback. */
47
+ export function setupLogStreamBroadcast(transmit, channelName, pinoHookActive, makePath) {
48
+ const broadcast = (entry) => {
49
+ try {
50
+ transmit.broadcast(channelName, entry);
51
+ }
52
+ catch { }
53
+ };
54
+ const existing = getLogStreamService();
55
+ if (pinoHookActive && existing) {
56
+ const internal = existing;
57
+ const orig = internal.onEntry;
58
+ internal.onEntry = (entry) => {
59
+ orig?.(entry);
60
+ broadcast(entry);
61
+ };
62
+ return null;
63
+ }
64
+ const service = new LogStreamService(makePath('logs', 'adonisjs.log'), broadcast);
65
+ service.start().catch(() => { });
66
+ return service;
67
+ }
68
+ // ── checkDashboardDepsHelper ────────────────────────────────────
69
+ /** Check if dashboard dependencies are available. Returns true if available, false if missing. */
70
+ export async function checkDashboardDepsHelper(config, _app) {
71
+ if (!config.devToolbar?.enabled || !config.devToolbar.dashboard)
72
+ return true;
73
+ const { appImport } = await import('../utils/app_import.js');
74
+ const missing = [];
75
+ try {
76
+ await appImport('knex');
77
+ }
78
+ catch {
79
+ missing.push('knex');
80
+ }
81
+ try {
82
+ await appImport('better-sqlite3');
83
+ }
84
+ catch {
85
+ missing.push('better-sqlite3');
86
+ }
87
+ if (missing.length === 0)
88
+ return true;
89
+ log.block(`Dashboard requires ${missing.join(' and ')}. Install with:`, [
90
+ '',
91
+ bold(`npm install ${missing.join(' ')}`),
92
+ '',
93
+ dim('Dashboard routes have been skipped for now.'),
94
+ dim('Everything else (stats bar, debug panel) works without it.'),
95
+ ]);
96
+ return false;
97
+ }
98
+ // ── registerEdgePluginHelper ────────────────────────────────────
99
+ /** Register the Edge.js plugin if Edge is available. Returns true if registered. */
100
+ export async function registerEdgePluginHelper(app, config) {
101
+ if (!app.usingEdgeJS)
102
+ return false;
103
+ try {
104
+ const { appImport } = await import('../utils/app_import.js');
105
+ const edge = await appImport('edge.js');
106
+ const { edgePluginServerStats } = await import('../edge/plugin.js');
107
+ edge.default.use(edgePluginServerStats(config));
108
+ return true;
109
+ }
110
+ catch (err) {
111
+ log.warn('could not register Edge plugin: ' + err?.message);
112
+ return false;
113
+ }
114
+ }
115
+ // ── setupNonWebBridgeHelper ─────────────────────────────────────
116
+ /** Set up the publisher-only email bridge for non-web environments. */
117
+ export async function setupNonWebBridgeHelper(emitter, channel) {
118
+ if (!emitter)
119
+ return;
120
+ try {
121
+ const { appImport } = await import('../utils/app_import.js');
122
+ const mod = await appImport('@adonisjs/redis/services/main');
123
+ const redis = mod.default;
124
+ setupPublisherOnlyBridge(emitter, redis, channel);
125
+ }
126
+ catch {
127
+ // Redis not available — skip bridge
128
+ }
129
+ }
130
+ /** Set up the stats collection interval with transmit and prometheus integration. */
131
+ export async function setupStatsIntervalHelper(engine, config, container) {
132
+ const { transmit, transmitAvailable } = await resolveTransmit(config, container);
133
+ const { prom, prometheusActive } = await resolvePrometheus();
134
+ const intervalId = setInterval(async () => {
135
+ try {
136
+ const s = await engine.collect();
137
+ if (transmit && config.channelName)
138
+ transmit.broadcast(config.channelName, s);
139
+ if (prom)
140
+ prom.update(s);
141
+ config.onStats?.(s);
142
+ }
143
+ catch { }
144
+ }, config.intervalMs);
145
+ return {
146
+ intervalId,
147
+ transmitAvailable,
148
+ prometheusActive,
149
+ channelName: transmitAvailable ? config.channelName : null,
150
+ };
151
+ }
152
+ async function resolveTransmit(config, container) {
153
+ if (config.transport !== 'transmit')
154
+ return { transmit: null, transmitAvailable: false };
155
+ try {
156
+ const t = await container.make('transmit');
157
+ return t
158
+ ? { transmit: t, transmitAvailable: true }
159
+ : { transmit: null, transmitAvailable: false };
160
+ }
161
+ catch {
162
+ log.info('transmit not installed — falling back to polling');
163
+ return { transmit: null, transmitAvailable: false };
164
+ }
165
+ }
166
+ async function resolvePrometheus() {
167
+ try {
168
+ const m = await import('../prometheus/prometheus_collector.js');
169
+ const p = m.ServerStatsCollector.instance;
170
+ if (p) {
171
+ log.info('Prometheus integration active');
172
+ return { prom: p, prometheusActive: true };
173
+ }
174
+ }
175
+ catch { }
176
+ return { prom: null, prometheusActive: false };
177
+ }
@@ -1,92 +1,46 @@
1
+ import { StatsEngine } from '../engine/stats_engine.js';
2
+ import type DebugController from '../controller/debug_controller.js';
3
+ import type DashboardController from '../dashboard/dashboard_controller.js';
4
+ import type { DashboardStore } from '../dashboard/dashboard_store.js';
5
+ import type { DebugStore } from '../debug/debug_store.js';
6
+ import type { LogStreamService } from '../log_stream/log_stream_service.js';
1
7
  import type { ApplicationService } from '@adonisjs/core/types';
2
8
  export default class ServerStatsProvider {
3
9
  protected app: ApplicationService;
4
10
  private intervalId;
5
- private engine;
6
- private debugStore;
7
- private dashboardStore;
8
- private dashboardController;
9
- private dashboardLogStream;
10
- private dashboardBroadcastTimer;
11
- private debugBroadcastTimer;
12
- private persistPath;
13
- private flushTimer;
11
+ engine: StatsEngine | null;
12
+ debugStore: DebugStore | null;
13
+ dashboardStore: DashboardStore | null;
14
+ dashboardController: DashboardController | null;
15
+ dashboardLogStream: LogStreamService | null;
16
+ dashboardBroadcastTimer: ReturnType<typeof setInterval> | null;
17
+ debugBroadcastTimer: ReturnType<typeof setTimeout> | null;
18
+ persistPath: string | null;
19
+ flushTimer: ReturnType<typeof setInterval> | null;
14
20
  private statsController;
15
- private debugController;
21
+ debugController: DebugController | null;
16
22
  private apiController;
17
- private dashboardDepsAvailable;
18
- private emailBridgeRedis;
23
+ dashboardDepsAvailable: boolean;
24
+ emailBridgeRedis: unknown;
19
25
  private emailBridgeChannel;
20
26
  private logStreamService;
21
- private pinoHookActive;
22
- private edgePluginActive;
27
+ pinoHookActive: boolean;
28
+ edgePluginActive: boolean;
23
29
  private prometheusActive;
24
- private transmitAvailable;
25
- private transmitChannels;
30
+ transmitAvailable: boolean;
31
+ transmitChannels: string[];
26
32
  private resolvedConfig;
27
33
  private resolvedCollectors;
28
34
  constructor(app: ApplicationService);
29
35
  boot(): Promise<void>;
30
- private initializeBoot;
31
- /**
32
- * Read start/kernel.ts and detect auth-related middleware in server.use()
33
- * or router.use() blocks. Returns import paths of problematic middleware.
34
- *
35
- * Ignores initialize_auth_middleware (no DB query — just sets up ctx.auth).
36
- */
37
- private detectGlobalAuthMiddleware;
38
- /**
39
- * Hook into the AdonisJS logger's Pino stream to feed log entries
40
- * directly into the LogStreamService — no file path needed.
41
- *
42
- * Uses pino's exported `symbols.streamSym` to access the underlying
43
- * destination stream, then wraps its `write` method to tee entries
44
- * into the log collector.
45
- */
46
- private hookPinoLogger;
36
+ private initBoot;
37
+ private registerRoutes;
47
38
  ready(): Promise<void>;
48
- private initializeServerStats;
49
- /**
50
- * Set up the stats collection interval, transmit broadcasting,
51
- * and Prometheus integration. Extracted from initializeServerStats
52
- * so the ready log fires promptly.
53
- */
54
- private setupStatsInterval;
39
+ private initStats;
40
+ private resolveCollectors;
55
41
  private setupDevToolbar;
56
- /**
57
- * Initialize the full-page dashboard: SQLite store, controller,
58
- * log piping, and per-request data persistence.
59
- *
60
- * Routes are already registered in boot() with a lazy controller getter.
61
- * This method creates the controller so those routes become functional.
62
- */
63
- private setupDashboard;
64
- /**
65
- * Lightweight email bridge publisher for non-web environments
66
- * (queue workers, scheduler). Listens to local AdonisJS mail events
67
- * and publishes them to Redis so the web server can ingest them.
68
- */
69
- private setupEmailBridgePublisher;
70
- /**
71
- * Set up live log streaming via Transmit (web environment only).
72
- * Merged from the former standalone LogStreamProvider.
73
- */
74
- private setupLogStream;
75
- /**
76
- * Set up a Redis pub/sub bridge for cross-process email capture.
77
- *
78
- * Mail events (`mail:sending`, `mail:sent`, etc.) are process-local.
79
- * When emails are sent from a Bull queue worker, the web server's
80
- * {@link EmailCollector} never sees them. This bridge solves that:
81
- *
82
- * 1. **Every process** publishes mail events to a Redis channel.
83
- * 2. **Every process** subscribes and ingests events from *other*
84
- * processes (identified by a unique process tag).
85
- *
86
- * Requires `@adonisjs/redis`. Silently skipped if not installed.
87
- */
88
- private setupEmailBridge;
89
- /** Return diagnostics state for the Internals endpoint. */
42
+ private setupLogBroadcast;
43
+ private resolve;
90
44
  getDiagnostics(): {
91
45
  timers: {
92
46
  collectionInterval: {
@@ -137,7 +91,7 @@ export default class ServerStatsProvider {
137
91
  };
138
92
  config: {
139
93
  intervalMs: number;
140
- transport: "transmit" | "none";
94
+ transport: string;
141
95
  channelName: string;
142
96
  endpoint: string | false;
143
97
  skipInTest: boolean;
@@ -145,22 +99,22 @@ export default class ServerStatsProvider {
145
99
  hasShouldShowCallback: boolean;
146
100
  };
147
101
  devToolbar: {
148
- enabled: boolean;
149
- maxQueries: number;
150
- maxEvents: number;
151
- maxEmails: number;
152
- maxTraces: number;
153
- slowQueryThresholdMs: number;
154
102
  tracing: boolean;
155
103
  dashboard: boolean;
156
- dashboardPath: string;
157
- debugEndpoint: string;
158
104
  retentionDays: number;
159
- dbPath: string;
160
105
  persistDebugData: string | boolean;
161
- renderer: "preact" | "vue";
106
+ renderer: string;
162
107
  excludeFromTracing: string[];
163
108
  customPaneCount: number;
109
+ dashboardPath: string;
110
+ debugEndpoint: string;
111
+ dbPath: string;
112
+ maxQueries: number;
113
+ maxEvents: number;
114
+ maxEmails: number;
115
+ maxTraces: number;
116
+ slowQueryThresholdMs: number;
117
+ enabled: boolean;
164
118
  };
165
119
  };
166
120
  shutdown(): Promise<void>;