@monocle.sh/studio 0.1.0

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 (102) hide show
  1. package/LICENSE +15 -0
  2. package/dist/adonisrc.d.ts +6 -0
  3. package/dist/adonisrc.js +25 -0
  4. package/dist/config/app.d.ts +6 -0
  5. package/dist/config/app.js +5 -0
  6. package/dist/config/bodyparser.d.ts +4 -0
  7. package/dist/config/bodyparser.js +5 -0
  8. package/dist/config/encryption.d.ts +12 -0
  9. package/dist/config/encryption.js +11 -0
  10. package/dist/config/logger.d.ts +4 -0
  11. package/dist/config/logger.js +13 -0
  12. package/dist/config/static.d.ts +6 -0
  13. package/dist/config/static.js +11 -0
  14. package/dist/controllers/api_controller.d.ts +174 -0
  15. package/dist/controllers/api_controller.js +118 -0
  16. package/dist/controllers/mcp_controller.d.ts +20 -0
  17. package/dist/controllers/mcp_controller.js +29 -0
  18. package/dist/controllers/otlp_controller.d.ts +52 -0
  19. package/dist/controllers/otlp_controller.js +92 -0
  20. package/dist/controllers/sse_controller.d.ts +18 -0
  21. package/dist/controllers/sse_controller.js +36 -0
  22. package/dist/db/connection.d.ts +20 -0
  23. package/dist/db/connection.js +38 -0
  24. package/dist/db/schema.d.ts +10 -0
  25. package/dist/db/schema.js +45 -0
  26. package/dist/db/types.d.ts +40 -0
  27. package/dist/db/types.js +1 -0
  28. package/dist/mcp/define_tool.d.ts +10 -0
  29. package/dist/mcp/define_tool.js +9 -0
  30. package/dist/mcp/tools/clear_data.d.ts +9 -0
  31. package/dist/mcp/tools/clear_data.js +26 -0
  32. package/dist/mcp/tools/get_trace.d.ts +12 -0
  33. package/dist/mcp/tools/get_trace.js +25 -0
  34. package/dist/mcp/tools/index.d.ts +10 -0
  35. package/dist/mcp/tools/index.js +37 -0
  36. package/dist/mcp/tools/list_errors.d.ts +14 -0
  37. package/dist/mcp/tools/list_errors.js +25 -0
  38. package/dist/mcp/tools/list_logs.d.ts +16 -0
  39. package/dist/mcp/tools/list_logs.js +30 -0
  40. package/dist/mcp/tools/list_queries.d.ts +15 -0
  41. package/dist/mcp/tools/list_queries.js +27 -0
  42. package/dist/mcp/tools/list_traces.d.ts +14 -0
  43. package/dist/mcp/tools/list_traces.js +25 -0
  44. package/dist/mcp/types.d.ts +21 -0
  45. package/dist/mcp/types.js +1 -0
  46. package/dist/mcp.d.ts +2 -0
  47. package/dist/mcp.js +2 -0
  48. package/dist/providers/api_provider.d.ts +14 -0
  49. package/dist/providers/api_provider.js +13 -0
  50. package/dist/repositories/log_repository.d.ts +43 -0
  51. package/dist/repositories/log_repository.js +58 -0
  52. package/dist/repositories/span_repository.d.ts +136 -0
  53. package/dist/repositories/span_repository.js +254 -0
  54. package/dist/semconv.d.ts +38 -0
  55. package/dist/semconv.js +40 -0
  56. package/dist/server.d.ts +16 -0
  57. package/dist/server.js +68 -0
  58. package/dist/services/event_bus.d.ts +24 -0
  59. package/dist/services/event_bus.js +24 -0
  60. package/dist/services/otlp_parser.d.ts +21 -0
  61. package/dist/services/otlp_parser.js +121 -0
  62. package/dist/start/kernel.d.ts +1 -0
  63. package/dist/start/kernel.js +5 -0
  64. package/dist/start/routes.d.ts +1 -0
  65. package/dist/start/routes.js +35 -0
  66. package/dist/transformers/command_transformer.d.ts +18 -0
  67. package/dist/transformers/command_transformer.js +20 -0
  68. package/dist/transformers/exception_transformer.d.ts +17 -0
  69. package/dist/transformers/exception_transformer.js +19 -0
  70. package/dist/transformers/job_transformer.d.ts +18 -0
  71. package/dist/transformers/job_transformer.js +20 -0
  72. package/dist/transformers/log_transformer.d.ts +18 -0
  73. package/dist/transformers/log_transformer.js +19 -0
  74. package/dist/transformers/query_transformer.d.ts +20 -0
  75. package/dist/transformers/query_transformer.js +27 -0
  76. package/dist/transformers/span_transformer.d.ts +22 -0
  77. package/dist/transformers/span_transformer.js +23 -0
  78. package/dist/transformers/trace_transformer.d.ts +22 -0
  79. package/dist/transformers/trace_transformer.js +33 -0
  80. package/dist/types/logs.d.ts +19 -0
  81. package/dist/types/logs.js +1 -0
  82. package/dist/types/otlp.d.ts +105 -0
  83. package/dist/types/otlp.js +1 -0
  84. package/dist/types/spans.d.ts +29 -0
  85. package/dist/types/spans.js +1 -0
  86. package/dist/validators.d.ts +103 -0
  87. package/dist/validators.js +41 -0
  88. package/package.json +60 -0
  89. package/ui/dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  90. package/ui/dist/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  91. package/ui/dist/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  92. package/ui/dist/assets/index-Bfk6GRvP.css +1 -0
  93. package/ui/dist/assets/index-XOaGlb1r.js +115 -0
  94. package/ui/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  95. package/ui/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  96. package/ui/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  97. package/ui/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  98. package/ui/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  99. package/ui/dist/assets/silkscreen-latin-400-normal-CtPo2yA5.woff2 +0 -0
  100. package/ui/dist/assets/silkscreen-latin-400-normal-D0DfPJut.woff +0 -0
  101. package/ui/dist/favicon.svg +4 -0
  102. package/ui/dist/index.html +14 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2024-present, Julien Ripouteau
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,6 @@
1
+ import * as _adonisjs_core_types_app0 from "@adonisjs/core/types/app";
2
+
3
+ //#region src/adonisrc.d.ts
4
+ declare const _default: _adonisjs_core_types_app0.RcFileInput;
5
+ //#endregion
6
+ export { _default as default };
@@ -0,0 +1,25 @@
1
+ import { indexEntities } from "@adonisjs/core";
2
+ import { defineConfig } from "@adonisjs/core/app";
3
+ import { generateRegistry } from "@tuyau/core/hooks";
4
+ //#region src/adonisrc.ts
5
+ var adonisrc_default = defineConfig({
6
+ directories: { public: "../ui/dist" },
7
+ providers: [
8
+ () => import("@adonisjs/core/providers/app_provider"),
9
+ () => import("@adonisjs/core/providers/vinejs_provider"),
10
+ () => import("@adonisjs/static/static_provider")
11
+ ],
12
+ preloads: [
13
+ () => import("./providers/api_provider.js"),
14
+ () => import("./start/kernel.js"),
15
+ () => import("./start/routes.js")
16
+ ],
17
+ hooks: { init: [indexEntities({ transformers: {
18
+ enabled: true,
19
+ source: "./src",
20
+ glob: ["transformers/*_transformer.ts"],
21
+ importAlias: "#app"
22
+ } }), generateRegistry()] }
23
+ });
24
+ //#endregion
25
+ export { adonisrc_default as default };
@@ -0,0 +1,6 @@
1
+ import * as _adonisjs_core_types_http0 from "@adonisjs/core/types/http";
2
+
3
+ //#region src/config/app.d.ts
4
+ declare const http: _adonisjs_core_types_http0.ServerConfig;
5
+ //#endregion
6
+ export { http };
@@ -0,0 +1,5 @@
1
+ import { defineConfig } from "@adonisjs/core/http";
2
+ //#region src/config/app.ts
3
+ const http = defineConfig({});
4
+ //#endregion
5
+ export { http };
@@ -0,0 +1,4 @@
1
+ //#region src/config/bodyparser.d.ts
2
+ declare const config: any;
3
+ //#endregion
4
+ export { config as default };
@@ -0,0 +1,5 @@
1
+ import { defineConfig } from "@adonisjs/core/bodyparser";
2
+ //#region src/config/bodyparser.ts
3
+ const config = defineConfig({ json: { limit: "10mb" } });
4
+ //#endregion
5
+ export { config as default };
@@ -0,0 +1,12 @@
1
+ import * as _adonisjs_core_types_encryption0 from "@adonisjs/core/types/encryption";
2
+ import * as _adonisjs_core_types0 from "@adonisjs/core/types";
3
+
4
+ //#region src/config/encryption.d.ts
5
+ declare const _default: _adonisjs_core_types0.ConfigProvider<{
6
+ default?: "app" | undefined;
7
+ list: {
8
+ app: _adonisjs_core_types_encryption0.EncryptionConfig;
9
+ };
10
+ }>;
11
+ //#endregion
12
+ export { _default as default };
@@ -0,0 +1,11 @@
1
+ import { defineConfig, drivers } from "@adonisjs/core/encryption";
2
+ //#region src/config/encryption.ts
3
+ var encryption_default = defineConfig({
4
+ default: "app",
5
+ list: { app: drivers.aes256gcm({
6
+ id: "app",
7
+ keys: [process.env.APP_KEY]
8
+ }) }
9
+ });
10
+ //#endregion
11
+ export { encryption_default as default };
@@ -0,0 +1,4 @@
1
+ //#region src/config/logger.d.ts
2
+ declare const config: any;
3
+ //#endregion
4
+ export { config as default };
@@ -0,0 +1,13 @@
1
+ import { defineConfig, targets } from "@adonisjs/core/logger";
2
+ //#region src/config/logger.ts
3
+ const config = defineConfig({
4
+ default: "app",
5
+ loggers: { app: {
6
+ enabled: true,
7
+ name: "monocle-devtools",
8
+ level: "info",
9
+ transport: { targets: targets().push(targets.pretty()).toArray() }
10
+ } }
11
+ });
12
+ //#endregion
13
+ export { config as default };
@@ -0,0 +1,6 @@
1
+ import * as _adonisjs_static_types0 from "@adonisjs/static/types";
2
+
3
+ //#region src/config/static.d.ts
4
+ declare const _default: _adonisjs_static_types0.AssetsConfig;
5
+ //#endregion
6
+ export { _default as default };
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "@adonisjs/static";
2
+ //#region src/config/static.ts
3
+ var static_default = defineConfig({
4
+ enabled: true,
5
+ immutable: true,
6
+ maxAge: "1y",
7
+ cacheControl: true,
8
+ etag: true
9
+ });
10
+ //#endregion
11
+ export { static_default as default };
@@ -0,0 +1,174 @@
1
+ import { HttpContext } from "@adonisjs/core/http";
2
+ //#region src/controllers/api_controller.d.ts
3
+ /**
4
+ * HTTP API controller exposing telemetry data collected by the
5
+ * OTLP ingester. Serves paginated lists of traces, logs,
6
+ * exceptions, queries, jobs, and commands.
7
+ */
8
+ declare class ApiController {
9
+ /**
10
+ * Returns all distinct service names that have emitted spans.
11
+ */
12
+ listServices({
13
+ response
14
+ }: HttpContext): Promise<{
15
+ __response: {
16
+ data: string[];
17
+ };
18
+ __status: 200;
19
+ }>;
20
+ /**
21
+ * Lists HTTP request traces with optional service and trace ID filters.
22
+ */
23
+ listTraces({
24
+ request,
25
+ serialize
26
+ }: HttpContext): Promise<{
27
+ data: {
28
+ traceId: string;
29
+ rootSpanName: string;
30
+ serviceName: string;
31
+ method: string | null;
32
+ path: string | null;
33
+ route: string;
34
+ host: string | null;
35
+ statusCode: string;
36
+ httpStatus: number | null;
37
+ durationMs: number;
38
+ startTime: string;
39
+ spanCount: number;
40
+ };
41
+ }>;
42
+ /**
43
+ * Returns the full span waterfall for a single trace.
44
+ */
45
+ getTrace({
46
+ params,
47
+ response,
48
+ serialize
49
+ }: HttpContext): Promise<{
50
+ __response: {
51
+ error: string;
52
+ };
53
+ __status: 404;
54
+ } | {
55
+ data: {
56
+ traceId: string;
57
+ serviceName: string;
58
+ statusCode: string;
59
+ durationMs: number;
60
+ spanId: string;
61
+ parentSpanId: string | null;
62
+ name: string;
63
+ kind: string;
64
+ statusMessage: string;
65
+ startTimeUnix: string;
66
+ endTimeUnix: string;
67
+ events?: any;
68
+ attributes?: any;
69
+ };
70
+ }>;
71
+ /**
72
+ * Lists exceptions extracted from span events.
73
+ */
74
+ listExceptions({
75
+ request,
76
+ serialize
77
+ }: HttpContext): Promise<{
78
+ data: {
79
+ traceId: string;
80
+ serviceName: string;
81
+ spanId: string;
82
+ spanName: string;
83
+ exceptionType: string;
84
+ exceptionMessage: string;
85
+ exceptionStacktrace: string;
86
+ timestamp: string;
87
+ };
88
+ }>;
89
+ /**
90
+ * Lists database query spans with optional DB system filter.
91
+ */
92
+ listQueries({
93
+ request,
94
+ serialize
95
+ }: HttpContext): Promise<{
96
+ data: {
97
+ traceId: string;
98
+ dbSystem: string | null;
99
+ serviceName: string;
100
+ statusCode: string;
101
+ durationMs: number;
102
+ startTime: string;
103
+ spanId: string;
104
+ spanName: string;
105
+ dbStatement: string | null;
106
+ dbOperation: string | null;
107
+ };
108
+ }>;
109
+ /**
110
+ * Lists background job traces.
111
+ */
112
+ listJobs({
113
+ request,
114
+ serialize
115
+ }: HttpContext): Promise<{
116
+ data: {
117
+ traceId: string;
118
+ rootSpanName: string;
119
+ serviceName: string;
120
+ statusCode: string;
121
+ durationMs: number;
122
+ startTime: string;
123
+ spanCount: number;
124
+ jobName: string;
125
+ queueName: string | null;
126
+ };
127
+ }>;
128
+ /**
129
+ * Lists CLI command traces.
130
+ */
131
+ listCommands({
132
+ request,
133
+ serialize
134
+ }: HttpContext): Promise<{
135
+ data: {
136
+ traceId: string;
137
+ rootSpanName: string;
138
+ serviceName: string;
139
+ statusCode: string;
140
+ durationMs: number;
141
+ startTime: string;
142
+ spanCount: number;
143
+ commandName: string;
144
+ commandDescription: string;
145
+ };
146
+ }>;
147
+ /**
148
+ * Clears all stored telemetry data.
149
+ */
150
+ clearData({}: HttpContext): Promise<{
151
+ success: boolean;
152
+ }>;
153
+ /**
154
+ * Lists application logs with severity, search, and trace filters.
155
+ */
156
+ listLogs({
157
+ request,
158
+ serialize
159
+ }: HttpContext): Promise<{
160
+ data: {
161
+ traceId: string | null;
162
+ body: string;
163
+ serviceName: string;
164
+ spanId: string | null;
165
+ timestamp: string;
166
+ timestampUnix: string;
167
+ severityText: string;
168
+ severityNumber: number | null;
169
+ attributes?: any;
170
+ };
171
+ }>;
172
+ }
173
+ //#endregion
174
+ export { ApiController as default };
@@ -0,0 +1,118 @@
1
+ import { SpanRepository } from "../repositories/span_repository.js";
2
+ import SpanTransformer from "../transformers/span_transformer.js";
3
+ import { LogRepository } from "../repositories/log_repository.js";
4
+ import LogTransformer from "../transformers/log_transformer.js";
5
+ import { listLogsValidator, listQueriesValidator, listTracesValidator, paginationValidator } from "../validators.js";
6
+ import TraceTransformer from "../transformers/trace_transformer.js";
7
+ import QueryTransformer from "../transformers/query_transformer.js";
8
+ import ExceptionTransformer from "../transformers/exception_transformer.js";
9
+ import JobTransformer from "../transformers/job_transformer.js";
10
+ import CommandTransformer from "../transformers/command_transformer.js";
11
+ import "@adonisjs/core/providers/vinejs_provider";
12
+ //#region src/controllers/api_controller.ts
13
+ const spanRepo = new SpanRepository();
14
+ const logRepo = new LogRepository();
15
+ /**
16
+ * HTTP API controller exposing telemetry data collected by the
17
+ * OTLP ingester. Serves paginated lists of traces, logs,
18
+ * exceptions, queries, jobs, and commands.
19
+ */
20
+ var ApiController = class {
21
+ /**
22
+ * Returns all distinct service names that have emitted spans.
23
+ */
24
+ async listServices({ response }) {
25
+ const services = await spanRepo.listServices();
26
+ return response.ok({ data: services });
27
+ }
28
+ /**
29
+ * Lists HTTP request traces with optional service and trace ID filters.
30
+ */
31
+ async listTraces({ request, serialize }) {
32
+ const { limit = 50, offset = 0, ...filters } = await request.validateUsing(listTracesValidator);
33
+ const rows = await spanRepo.listTraces({
34
+ limit,
35
+ offset,
36
+ ...filters
37
+ });
38
+ return { data: await serialize(TraceTransformer.transform(rows)) };
39
+ }
40
+ /**
41
+ * Returns the full span waterfall for a single trace.
42
+ */
43
+ async getTrace({ params, response, serialize }) {
44
+ const rows = await spanRepo.getTraceSpans(params.traceId);
45
+ if (!rows.length) return response.notFound({ error: "Trace not found" });
46
+ return { data: await serialize(SpanTransformer.transform(rows)) };
47
+ }
48
+ /**
49
+ * Lists exceptions extracted from span events.
50
+ */
51
+ async listExceptions({ request, serialize }) {
52
+ const { limit = 100, offset = 0, ...filters } = await request.validateUsing(paginationValidator);
53
+ const rows = await spanRepo.listExceptions({
54
+ limit,
55
+ offset,
56
+ ...filters
57
+ });
58
+ return { data: await serialize(ExceptionTransformer.transform(rows)) };
59
+ }
60
+ /**
61
+ * Lists database query spans with optional DB system filter.
62
+ */
63
+ async listQueries({ request, serialize }) {
64
+ const { limit = 100, offset = 0, ...filters } = await request.validateUsing(listQueriesValidator);
65
+ const rows = await spanRepo.listQueries({
66
+ limit,
67
+ offset,
68
+ ...filters
69
+ });
70
+ return { data: await serialize(QueryTransformer.transform(rows)) };
71
+ }
72
+ /**
73
+ * Lists background job traces.
74
+ */
75
+ async listJobs({ request, serialize }) {
76
+ const { limit = 50, offset = 0, ...filters } = await request.validateUsing(paginationValidator);
77
+ const rows = await spanRepo.listJobs({
78
+ limit,
79
+ offset,
80
+ ...filters
81
+ });
82
+ return { data: await serialize(JobTransformer.transform(rows)) };
83
+ }
84
+ /**
85
+ * Lists CLI command traces.
86
+ */
87
+ async listCommands({ request, serialize }) {
88
+ const { limit = 50, offset = 0, ...filters } = await request.validateUsing(paginationValidator);
89
+ const rows = await spanRepo.listCommands({
90
+ limit,
91
+ offset,
92
+ ...filters
93
+ });
94
+ return { data: await serialize(CommandTransformer.transform(rows)) };
95
+ }
96
+ /**
97
+ * Clears all stored telemetry data.
98
+ */
99
+ async clearData({}) {
100
+ await spanRepo.clearAll();
101
+ await logRepo.clearAll();
102
+ return { success: true };
103
+ }
104
+ /**
105
+ * Lists application logs with severity, search, and trace filters.
106
+ */
107
+ async listLogs({ request, serialize }) {
108
+ const { limit = 100, offset = 0, ...filters } = await request.validateUsing(listLogsValidator);
109
+ const rows = await logRepo.listLogs({
110
+ limit,
111
+ offset,
112
+ ...filters
113
+ });
114
+ return { data: await serialize(LogTransformer.transform(rows)) };
115
+ }
116
+ };
117
+ //#endregion
118
+ export { ApiController as default };
@@ -0,0 +1,20 @@
1
+ import { HttpContext } from "@adonisjs/core/http";
2
+
3
+ //#region src/controllers/mcp_controller.d.ts
4
+ /**
5
+ * Model Context Protocol controller. Creates a stateless MCP server
6
+ * per request with all registered tools, then handles the HTTP
7
+ * streaming transport handshake.
8
+ */
9
+ declare class McpController {
10
+ /**
11
+ * Handles an incoming MCP HTTP request by spinning up a
12
+ * short-lived MCP server with all devtools tools registered.
13
+ */
14
+ handle({
15
+ request,
16
+ response
17
+ }: HttpContext): Promise<void>;
18
+ }
19
+ //#endregion
20
+ export { McpController as default };
@@ -0,0 +1,29 @@
1
+ import { registerAllTools } from "../mcp/tools/index.js";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ //#region src/controllers/mcp_controller.ts
5
+ /**
6
+ * Model Context Protocol controller. Creates a stateless MCP server
7
+ * per request with all registered tools, then handles the HTTP
8
+ * streaming transport handshake.
9
+ */
10
+ var McpController = class {
11
+ /**
12
+ * Handles an incoming MCP HTTP request by spinning up a
13
+ * short-lived MCP server with all devtools tools registered.
14
+ */
15
+ async handle({ request, response }) {
16
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
17
+ const server = new McpServer({
18
+ name: "monocle-devtools",
19
+ version: "0.1.0"
20
+ });
21
+ registerAllTools(server);
22
+ await server.connect(transport);
23
+ await transport.handleRequest(request.request, response.response);
24
+ await server.close();
25
+ if (!response.response.writableEnded) response.response.end();
26
+ }
27
+ };
28
+ //#endregion
29
+ export { McpController as default };
@@ -0,0 +1,52 @@
1
+ import { HttpContext } from "@adonisjs/core/http";
2
+
3
+ //#region src/controllers/otlp_controller.d.ts
4
+ /**
5
+ * OTLP receiver controller. Accepts OpenTelemetry trace and log
6
+ * exports (JSON + optional gzip), parses them into flat models,
7
+ * persists to DuckDB, and emits real-time events via the EventBus.
8
+ */
9
+ declare class OtlpController {
10
+ /**
11
+ * Ingests an OTLP trace export request.
12
+ */
13
+ traces({
14
+ request,
15
+ response
16
+ }: HttpContext): Promise<{
17
+ __response: {
18
+ partialSuccess: {
19
+ rejectedSpans: number;
20
+ errorMessage: string;
21
+ };
22
+ };
23
+ __status: 200;
24
+ } | {
25
+ __response: {
26
+ error: string;
27
+ };
28
+ __status: 400;
29
+ }>;
30
+ /**
31
+ * Ingests an OTLP log export request.
32
+ */
33
+ logs({
34
+ request,
35
+ response
36
+ }: HttpContext): Promise<{
37
+ __response: {
38
+ partialSuccess: {
39
+ rejectedLogRecords: number;
40
+ errorMessage: string;
41
+ };
42
+ };
43
+ __status: 200;
44
+ } | {
45
+ __response: {
46
+ error: string;
47
+ };
48
+ __status: 400;
49
+ }>;
50
+ }
51
+ //#endregion
52
+ export { OtlpController as default };
@@ -0,0 +1,92 @@
1
+ import { SpanRepository } from "../repositories/span_repository.js";
2
+ import { LogRepository } from "../repositories/log_repository.js";
3
+ import { eventBus } from "../services/event_bus.js";
4
+ import { parseLogRequest, parseTraceRequest } from "../services/otlp_parser.js";
5
+ import { gunzipSync } from "node:zlib";
6
+ //#region src/controllers/otlp_controller.ts
7
+ const spanRepo = new SpanRepository();
8
+ const logRepo = new LogRepository();
9
+ /**
10
+ * Reads the full request body into a Buffer with a timeout
11
+ * guard to prevent hung connections.
12
+ */
13
+ function readRawBody(req, timeoutMs = 5e3) {
14
+ return new Promise((resolve, reject) => {
15
+ const chunks = [];
16
+ const timer = setTimeout(() => {
17
+ req.destroy();
18
+ reject(/* @__PURE__ */ new Error("Body read timeout"));
19
+ }, timeoutMs);
20
+ req.on("data", (chunk) => chunks.push(chunk));
21
+ req.on("end", () => {
22
+ clearTimeout(timer);
23
+ resolve(Buffer.concat(chunks));
24
+ });
25
+ req.on("error", (err) => {
26
+ clearTimeout(timer);
27
+ reject(err);
28
+ });
29
+ });
30
+ }
31
+ /**
32
+ * Reads and decompresses (gzip) an OTLP JSON request body.
33
+ */
34
+ async function parseOtlpBody(ctx) {
35
+ const raw = await readRawBody(ctx.request.request);
36
+ if (!raw.length) return {};
37
+ const body = ctx.request.header("content-encoding") === "gzip" ? gunzipSync(raw) : raw;
38
+ return JSON.parse(body.toString("utf-8"));
39
+ }
40
+ /**
41
+ * OTLP receiver controller. Accepts OpenTelemetry trace and log
42
+ * exports (JSON + optional gzip), parses them into flat models,
43
+ * persists to DuckDB, and emits real-time events via the EventBus.
44
+ */
45
+ var OtlpController = class {
46
+ /**
47
+ * Ingests an OTLP trace export request.
48
+ */
49
+ async traces({ request, response }) {
50
+ try {
51
+ const spans = parseTraceRequest(await parseOtlpBody({ request }));
52
+ if (spans.length) {
53
+ await spanRepo.insertSpans(spans);
54
+ eventBus.emitIngested({
55
+ type: "traces",
56
+ count: spans.length
57
+ });
58
+ }
59
+ return response.ok({ partialSuccess: {
60
+ rejectedSpans: 0,
61
+ errorMessage: ""
62
+ } });
63
+ } catch (error) {
64
+ console.error("[DevTools] Failed to ingest traces:", error);
65
+ return response.badRequest({ error: "Failed to parse OTLP traces" });
66
+ }
67
+ }
68
+ /**
69
+ * Ingests an OTLP log export request.
70
+ */
71
+ async logs({ request, response }) {
72
+ try {
73
+ const logs = parseLogRequest(await parseOtlpBody({ request }));
74
+ if (logs.length) {
75
+ await logRepo.insertLogs(logs);
76
+ eventBus.emitIngested({
77
+ type: "logs",
78
+ count: logs.length
79
+ });
80
+ }
81
+ return response.ok({ partialSuccess: {
82
+ rejectedLogRecords: 0,
83
+ errorMessage: ""
84
+ } });
85
+ } catch (error) {
86
+ console.error("[DevTools] Failed to ingest logs:", error);
87
+ return response.badRequest({ error: "Failed to parse OTLP logs" });
88
+ }
89
+ }
90
+ };
91
+ //#endregion
92
+ export { OtlpController as default };
@@ -0,0 +1,18 @@
1
+ import { HttpContext } from "@adonisjs/core/http";
2
+
3
+ //#region src/controllers/sse_controller.d.ts
4
+ /**
5
+ * Server-Sent Events controller that streams real-time
6
+ * telemetry ingestion events to connected browser clients.
7
+ */
8
+ declare class SseController {
9
+ /**
10
+ * Establishes a persistent SSE connection and forwards
11
+ * ingested trace/log events until the client disconnects.
12
+ */
13
+ stream({
14
+ response
15
+ }: HttpContext): Promise<void>;
16
+ }
17
+ //#endregion
18
+ export { SseController as default };
@@ -0,0 +1,36 @@
1
+ import { eventBus } from "../services/event_bus.js";
2
+ //#region src/controllers/sse_controller.ts
3
+ /**
4
+ * Server-Sent Events controller that streams real-time
5
+ * telemetry ingestion events to connected browser clients.
6
+ */
7
+ var SseController = class {
8
+ /**
9
+ * Establishes a persistent SSE connection and forwards
10
+ * ingested trace/log events until the client disconnects.
11
+ */
12
+ async stream({ response }) {
13
+ const nodeRes = response.response;
14
+ nodeRes.writeHead(200, {
15
+ "Content-Type": "text/event-stream",
16
+ "Cache-Control": "no-cache",
17
+ Connection: "keep-alive",
18
+ "Access-Control-Allow-Origin": "*"
19
+ });
20
+ nodeRes.write(": connected\n\n");
21
+ const onEvent = (event) => {
22
+ nodeRes.write(`event: ingested\ndata: ${JSON.stringify(event)}\n\n`);
23
+ };
24
+ const unsubscribe = eventBus.onIngested(onEvent);
25
+ /**
26
+ * Keep-alive every 30s to prevent connection timeout.
27
+ */
28
+ const keepAlive = setInterval(() => nodeRes.write(": ping\n\n"), 3e4);
29
+ nodeRes.on("close", () => {
30
+ unsubscribe();
31
+ clearInterval(keepAlive);
32
+ });
33
+ }
34
+ };
35
+ //#endregion
36
+ export { SseController as default };