@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
@@ -0,0 +1,136 @@
1
+ import { FlatSpan } from "../types/spans.js";
2
+
3
+ //#region src/repositories/span_repository.d.ts
4
+ /**
5
+ * Data-access layer for distributed trace spans stored in DuckDB.
6
+ * Provides queries for traces, jobs, commands, database queries,
7
+ * and exceptions.
8
+ */
9
+ declare class SpanRepository {
10
+ #private;
11
+ /**
12
+ * Upserts a batch of flat spans into the spans table.
13
+ * Uses ON CONFLICT to update existing spans (same trace_id + span_id).
14
+ */
15
+ insertSpans(spans: FlatSpan[]): Promise<void>;
16
+ /**
17
+ * Returns all distinct service names that have emitted spans.
18
+ */
19
+ listServices(): Promise<string[]>;
20
+ /**
21
+ * Lists HTTP request root spans with their span counts,
22
+ * optionally filtered by trace ID or service name.
23
+ */
24
+ listTraces(options?: {
25
+ limit?: number;
26
+ offset?: number;
27
+ traceId?: string;
28
+ service?: string;
29
+ }): Promise<{
30
+ span_count: number;
31
+ trace_id: string;
32
+ span_name: string;
33
+ service_name: string | null;
34
+ status_code: string;
35
+ duration_ns: bigint;
36
+ start_time_unix: bigint;
37
+ span_attributes: string | null;
38
+ }[]>;
39
+ /**
40
+ * Lists background job root spans (messaging/queue consumers).
41
+ */
42
+ listJobs(options?: {
43
+ limit?: number;
44
+ offset?: number;
45
+ traceId?: string;
46
+ }): Promise<{
47
+ span_count: number;
48
+ trace_id: string;
49
+ span_name: string;
50
+ service_name: string | null;
51
+ status_code: string;
52
+ duration_ns: bigint;
53
+ start_time_unix: bigint;
54
+ span_attributes: string | null;
55
+ }[]>;
56
+ /**
57
+ * Lists CLI command root spans.
58
+ */
59
+ listCommands(options?: {
60
+ limit?: number;
61
+ offset?: number;
62
+ traceId?: string;
63
+ }): Promise<{
64
+ span_count: number;
65
+ trace_id: string;
66
+ span_name: string;
67
+ service_name: string | null;
68
+ status_code: string;
69
+ duration_ns: bigint;
70
+ start_time_unix: bigint;
71
+ span_attributes: string | null;
72
+ }[]>;
73
+ /**
74
+ * Lists database query spans, optionally filtered by DB system
75
+ * (postgresql, mysql, sqlite, etc.).
76
+ */
77
+ listQueries(options?: {
78
+ limit?: number;
79
+ offset?: number;
80
+ traceId?: string;
81
+ dbSystem?: string;
82
+ }): Promise<{
83
+ trace_id: string;
84
+ span_id: string;
85
+ span_name: string;
86
+ service_name: string | null;
87
+ status_code: string;
88
+ duration_ns: bigint;
89
+ start_time_unix: bigint;
90
+ span_attributes: string | null;
91
+ }[]>;
92
+ /**
93
+ * Lists exceptions extracted from span events by unnesting
94
+ * the JSON events array and filtering for "exception" events.
95
+ */
96
+ listExceptions(options?: {
97
+ limit?: number;
98
+ offset?: number;
99
+ traceId?: string;
100
+ }): Promise<{
101
+ trace_id: string;
102
+ span_id: string;
103
+ span_name: string;
104
+ service_name: string | null;
105
+ start_time_unix: bigint;
106
+ event_json: string;
107
+ }[]>;
108
+ /**
109
+ * Deletes all rows from the spans table.
110
+ */
111
+ clearAll(): Promise<void>;
112
+ /**
113
+ * Returns all spans belonging to a trace, ordered by start time,
114
+ * with parsed attributes and events.
115
+ */
116
+ getTraceSpans(traceId: string): Promise<{
117
+ trace_id: string;
118
+ span_id: string;
119
+ span_name: string;
120
+ service_name: string | null;
121
+ status_code: string;
122
+ duration_ns: bigint;
123
+ start_time_unix: bigint;
124
+ parent_span_id: string | null;
125
+ span_kind: string | null;
126
+ status_message: string | null;
127
+ end_time_unix: bigint;
128
+ span_attributes: string | null;
129
+ events: string | null;
130
+ resource_attributes: string | null;
131
+ scope_name: string | null;
132
+ scope_version: string | null;
133
+ }[]>;
134
+ }
135
+ //#endregion
136
+ export { SpanRepository };
@@ -0,0 +1,254 @@
1
+ import { getDb } from "../db/connection.js";
2
+ import { Semconv } from "../semconv.js";
3
+ import { sql } from "kysely";
4
+ //#region src/repositories/span_repository.ts
5
+ const S = Semconv.Stable;
6
+ const L = Semconv.Legacy;
7
+ /**
8
+ * Data-access layer for distributed trace spans stored in DuckDB.
9
+ * Provides queries for traces, jobs, commands, database queries,
10
+ * and exceptions.
11
+ */
12
+ var SpanRepository = class {
13
+ /**
14
+ * Serializes a value to JSON, returning null for falsy values.
15
+ */
16
+ #jsonOrNull(value) {
17
+ if (!value) return null;
18
+ return JSON.stringify(value);
19
+ }
20
+ /**
21
+ * SQL expression: json_extract checking both stable and legacy keys via COALESCE.
22
+ */
23
+ #sqlAttr(stable, legacy) {
24
+ return sql`COALESCE(
25
+ json_extract_string(span_attributes, ${`$."${stable}"`}),
26
+ json_extract_string(span_attributes, ${`$."${legacy}"`})
27
+ )`;
28
+ }
29
+ /**
30
+ * SQL expression: check if at least one of the two keys exists (IS NOT NULL).
31
+ */
32
+ #sqlAttrExists(stable, legacy) {
33
+ return sql`(
34
+ json_extract_string(span_attributes, ${`$."${stable}"`}) IS NOT NULL
35
+ OR json_extract_string(span_attributes, ${`$."${legacy}"`}) IS NOT NULL
36
+ )`;
37
+ }
38
+ /**
39
+ * SQL condition: root span (no parent).
40
+ */
41
+ #whereRootSpan() {
42
+ return sql`(parent_span_id IS NULL OR parent_span_id = '')`;
43
+ }
44
+ /**
45
+ * SQL condition: entry is an HTTP request (has http.request.method attribute).
46
+ */
47
+ #whereHttpRequest() {
48
+ return this.#sqlAttrExists(S.HTTP_METHOD, L.HTTP_METHOD);
49
+ }
50
+ /**
51
+ * SQL condition: entry is a job (has entry_point.type = 'job' or messaging.system attribute).
52
+ */
53
+ #whereJob() {
54
+ return sql`(
55
+ json_extract_string(span_attributes, '$."entry_point.type"') = 'job'
56
+ OR json_extract_string(span_attributes, '$."messaging.system"') IS NOT NULL
57
+ )`;
58
+ }
59
+ /**
60
+ * SQL condition: entry is a CLI command (has entry_point.type = 'cli').
61
+ */
62
+ #whereCommand() {
63
+ return sql`json_extract_string(span_attributes, '$."entry_point.type"') = 'cli'`;
64
+ }
65
+ /**
66
+ * Upserts a batch of flat spans into the spans table.
67
+ * Uses ON CONFLICT to update existing spans (same trace_id + span_id).
68
+ */
69
+ async insertSpans(spans) {
70
+ const db = getDb();
71
+ for (const span of spans) await db.insertInto("spans").values({
72
+ trace_id: span.traceId,
73
+ span_id: span.spanId,
74
+ parent_span_id: span.parentSpanId,
75
+ span_name: span.spanName,
76
+ span_kind: span.spanKind,
77
+ service_name: span.serviceName,
78
+ status_code: span.statusCode,
79
+ status_message: span.statusMessage,
80
+ start_time_unix: span.startTimeUnix,
81
+ end_time_unix: span.endTimeUnix,
82
+ duration_ns: span.durationNs,
83
+ resource_attributes: this.#jsonOrNull(span.resourceAttributes),
84
+ span_attributes: this.#jsonOrNull(span.spanAttributes),
85
+ scope_name: span.scopeName,
86
+ scope_version: span.scopeVersion,
87
+ events: this.#jsonOrNull(span.events)
88
+ }).onConflict((oc) => oc.columns(["trace_id", "span_id"]).doUpdateSet({
89
+ parent_span_id: span.parentSpanId,
90
+ span_name: span.spanName,
91
+ span_kind: span.spanKind,
92
+ service_name: span.serviceName,
93
+ status_code: span.statusCode,
94
+ status_message: span.statusMessage,
95
+ start_time_unix: span.startTimeUnix,
96
+ end_time_unix: span.endTimeUnix,
97
+ duration_ns: span.durationNs,
98
+ resource_attributes: this.#jsonOrNull(span.resourceAttributes),
99
+ span_attributes: this.#jsonOrNull(span.spanAttributes),
100
+ scope_name: span.scopeName,
101
+ scope_version: span.scopeVersion,
102
+ events: this.#jsonOrNull(span.events)
103
+ })).execute();
104
+ }
105
+ /**
106
+ * Returns all distinct service names that have emitted spans.
107
+ */
108
+ async listServices() {
109
+ return (await getDb().selectFrom("spans").select("service_name").distinct().where("service_name", "is not", null).orderBy("service_name").execute()).map((r) => r.service_name).filter(Boolean);
110
+ }
111
+ /**
112
+ * Lists HTTP request root spans with their span counts,
113
+ * optionally filtered by trace ID or service name.
114
+ */
115
+ async listTraces(options = {}) {
116
+ const db = getDb();
117
+ const limit = options.limit ?? 50;
118
+ const offset = options.offset ?? 0;
119
+ let rootSpansQuery = db.selectFrom("spans").select([
120
+ "trace_id",
121
+ "span_name",
122
+ "service_name",
123
+ "status_code",
124
+ "start_time_unix",
125
+ "duration_ns",
126
+ "span_attributes"
127
+ ]).where(this.#whereRootSpan()).where(this.#whereHttpRequest()).orderBy("start_time_unix", "desc").limit(limit).offset(offset);
128
+ if (options.traceId) rootSpansQuery = rootSpansQuery.where("trace_id", "=", options.traceId);
129
+ if (options.service) rootSpansQuery = rootSpansQuery.where("service_name", "=", options.service);
130
+ return await db.with("root_spans", () => rootSpansQuery).with("span_counts", (qb) => qb.selectFrom("spans").select(["trace_id", sql`COUNT(*)`.as("span_count")]).where("trace_id", "in", (eb) => eb.selectFrom("root_spans").select("trace_id")).groupBy("trace_id")).selectFrom("root_spans as r").leftJoin("span_counts as c", "r.trace_id", "c.trace_id").select([
131
+ "r.trace_id",
132
+ "r.span_name",
133
+ "r.service_name",
134
+ "r.status_code",
135
+ "r.start_time_unix",
136
+ "r.duration_ns",
137
+ "r.span_attributes",
138
+ sql`COALESCE(c.span_count, 1)`.as("span_count")
139
+ ]).orderBy("r.start_time_unix", "desc").execute();
140
+ }
141
+ /**
142
+ * Lists background job root spans (messaging/queue consumers).
143
+ */
144
+ async listJobs(options = {}) {
145
+ const db = getDb();
146
+ const limit = options.limit ?? 50;
147
+ const offset = options.offset ?? 0;
148
+ let rootSpansQuery = db.selectFrom("spans").select([
149
+ "trace_id",
150
+ "span_name",
151
+ "service_name",
152
+ "status_code",
153
+ "start_time_unix",
154
+ "duration_ns",
155
+ "span_attributes"
156
+ ]).where(this.#whereRootSpan()).where(this.#whereJob()).orderBy("start_time_unix", "desc").limit(limit).offset(offset);
157
+ if (options.traceId) rootSpansQuery = rootSpansQuery.where("trace_id", "=", options.traceId);
158
+ return await db.with("root_spans", () => rootSpansQuery).with("span_counts", (qb) => qb.selectFrom("spans").select(["trace_id", sql`COUNT(*)`.as("span_count")]).where("trace_id", "in", (eb) => eb.selectFrom("root_spans").select("trace_id")).groupBy("trace_id")).selectFrom("root_spans as r").leftJoin("span_counts as c", "r.trace_id", "c.trace_id").select([
159
+ "r.trace_id",
160
+ "r.span_name",
161
+ "r.service_name",
162
+ "r.status_code",
163
+ "r.start_time_unix",
164
+ "r.duration_ns",
165
+ "r.span_attributes",
166
+ sql`COALESCE(c.span_count, 1)`.as("span_count")
167
+ ]).orderBy("r.start_time_unix", "desc").execute();
168
+ }
169
+ /**
170
+ * Lists CLI command root spans.
171
+ */
172
+ async listCommands(options = {}) {
173
+ const db = getDb();
174
+ const limit = options.limit ?? 50;
175
+ const offset = options.offset ?? 0;
176
+ let rootSpansQuery = db.selectFrom("spans").select([
177
+ "trace_id",
178
+ "span_name",
179
+ "service_name",
180
+ "status_code",
181
+ "start_time_unix",
182
+ "duration_ns",
183
+ "span_attributes"
184
+ ]).where(this.#whereRootSpan()).where(this.#whereCommand()).orderBy("start_time_unix", "desc").limit(limit).offset(offset);
185
+ if (options.traceId) rootSpansQuery = rootSpansQuery.where("trace_id", "=", options.traceId);
186
+ return await db.with("root_spans", () => rootSpansQuery).with("span_counts", (qb) => qb.selectFrom("spans").select(["trace_id", sql`COUNT(*)`.as("span_count")]).where("trace_id", "in", (eb) => eb.selectFrom("root_spans").select("trace_id")).groupBy("trace_id")).selectFrom("root_spans as r").leftJoin("span_counts as c", "r.trace_id", "c.trace_id").select([
187
+ "r.trace_id",
188
+ "r.span_name",
189
+ "r.service_name",
190
+ "r.status_code",
191
+ "r.start_time_unix",
192
+ "r.duration_ns",
193
+ "r.span_attributes",
194
+ sql`COALESCE(c.span_count, 1)`.as("span_count")
195
+ ]).orderBy("r.start_time_unix", "desc").execute();
196
+ }
197
+ /**
198
+ * Lists database query spans, optionally filtered by DB system
199
+ * (postgresql, mysql, sqlite, etc.).
200
+ */
201
+ async listQueries(options = {}) {
202
+ const db = getDb();
203
+ const limit = options.limit ?? 100;
204
+ const offset = options.offset ?? 0;
205
+ let query = db.selectFrom("spans").select([
206
+ "trace_id",
207
+ "span_id",
208
+ "span_name",
209
+ "service_name",
210
+ "status_code",
211
+ "start_time_unix",
212
+ "duration_ns",
213
+ "span_attributes"
214
+ ]).where(this.#sqlAttrExists(S.DB_SYSTEM, L.DB_SYSTEM)).orderBy("start_time_unix", "desc").limit(limit).offset(offset);
215
+ if (options.traceId) query = query.where("trace_id", "=", options.traceId);
216
+ if (options.dbSystem) query = query.where(this.#sqlAttr(S.DB_SYSTEM, L.DB_SYSTEM), "=", options.dbSystem);
217
+ return await query.execute();
218
+ }
219
+ /**
220
+ * Lists exceptions extracted from span events by unnesting
221
+ * the JSON events array and filtering for "exception" events.
222
+ */
223
+ async listExceptions(options = {}) {
224
+ const db = getDb();
225
+ const limit = options.limit ?? 100;
226
+ const offset = options.offset ?? 0;
227
+ let query = db.selectFrom(sql`spans s, unnest(from_json(s.events, '["JSON"]')) AS e(json)`).select([
228
+ sql`s.trace_id`.as("trace_id"),
229
+ sql`s.span_id`.as("span_id"),
230
+ sql`s.span_name`.as("span_name"),
231
+ sql`s.service_name`.as("service_name"),
232
+ sql`s.start_time_unix`.as("start_time_unix"),
233
+ sql`e.json`.as("event_json")
234
+ ]).where(sql`s.events`, "is not", null).where(sql`json_extract_string(e.json, '$.name')`, "=", "exception").orderBy(sql`s.start_time_unix`, "desc").limit(limit).offset(offset);
235
+ if (options.traceId) query = query.where(sql`s.trace_id`, "=", options.traceId);
236
+ return await query.execute();
237
+ }
238
+ /**
239
+ * Deletes all rows from the spans table.
240
+ */
241
+ async clearAll() {
242
+ const db = getDb();
243
+ await sql`DELETE FROM spans`.execute(db);
244
+ }
245
+ /**
246
+ * Returns all spans belonging to a trace, ordered by start time,
247
+ * with parsed attributes and events.
248
+ */
249
+ async getTraceSpans(traceId) {
250
+ return getDb().selectFrom("spans").selectAll().where("trace_id", "=", traceId).orderBy("start_time_unix", "asc").execute();
251
+ }
252
+ };
253
+ //#endregion
254
+ export { SpanRepository };
@@ -0,0 +1,38 @@
1
+ //#region src/semconv.d.ts
2
+ /**
3
+ * OpenTelemetry Semantic Convention attribute constants.
4
+ *
5
+ * Re-exports from `@opentelemetry/semantic-conventions` organized by
6
+ * stability level. The official package splits stable vs incubating,
7
+ * but conflates deprecated and experimental under "incubating".
8
+ * We explicitly separate **Stable** (current spec) from **Legacy**
9
+ * (deprecated, kept for old ClickHouse data reference).
10
+ *
11
+ * @see https://opentelemetry.io/docs/specs/semconv/http/http-spans/
12
+ * @see https://opentelemetry.io/docs/specs/semconv/database/database-spans/
13
+ */
14
+ declare const Semconv: {
15
+ readonly Stable: {
16
+ readonly HTTP_METHOD: "http.request.method";
17
+ readonly HTTP_STATUS_CODE: "http.response.status_code";
18
+ readonly HTTP_ROUTE: "http.route";
19
+ readonly HTTP_TARGET: "url.path";
20
+ readonly DB_SYSTEM: "db.system.name";
21
+ readonly DB_OPERATION: "db.operation.name";
22
+ readonly DB_STATEMENT: "db.query.text";
23
+ readonly DB_TABLE: "db.collection.name";
24
+ readonly DB_NAMESPACE: "db.namespace";
25
+ };
26
+ readonly Legacy: {
27
+ readonly HTTP_METHOD: "http.method";
28
+ readonly HTTP_STATUS_CODE: "http.status_code";
29
+ readonly HTTP_TARGET: "http.target";
30
+ readonly DB_SYSTEM: "db.system";
31
+ readonly DB_OPERATION: "db.operation";
32
+ readonly DB_STATEMENT: "db.statement";
33
+ readonly DB_TABLE: "db.sql.table";
34
+ readonly DB_NAME: "db.name";
35
+ };
36
+ };
37
+ //#endregion
38
+ export { Semconv };
@@ -0,0 +1,40 @@
1
+ import { ATTR_DB_COLLECTION_NAME, ATTR_DB_NAMESPACE, ATTR_DB_OPERATION_NAME, ATTR_DB_QUERY_TEXT, ATTR_DB_SYSTEM_NAME, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, ATTR_URL_PATH } from "@opentelemetry/semantic-conventions";
2
+ import { ATTR_DB_NAME, ATTR_DB_OPERATION, ATTR_DB_SQL_TABLE, ATTR_DB_STATEMENT, ATTR_DB_SYSTEM, ATTR_HTTP_METHOD, ATTR_HTTP_STATUS_CODE, ATTR_HTTP_TARGET } from "@opentelemetry/semantic-conventions/incubating";
3
+ //#region src/semconv.ts
4
+ /**
5
+ * OpenTelemetry Semantic Convention attribute constants.
6
+ *
7
+ * Re-exports from `@opentelemetry/semantic-conventions` organized by
8
+ * stability level. The official package splits stable vs incubating,
9
+ * but conflates deprecated and experimental under "incubating".
10
+ * We explicitly separate **Stable** (current spec) from **Legacy**
11
+ * (deprecated, kept for old ClickHouse data reference).
12
+ *
13
+ * @see https://opentelemetry.io/docs/specs/semconv/http/http-spans/
14
+ * @see https://opentelemetry.io/docs/specs/semconv/database/database-spans/
15
+ */
16
+ const Semconv = {
17
+ Stable: {
18
+ HTTP_METHOD: ATTR_HTTP_REQUEST_METHOD,
19
+ HTTP_STATUS_CODE: ATTR_HTTP_RESPONSE_STATUS_CODE,
20
+ HTTP_ROUTE: ATTR_HTTP_ROUTE,
21
+ HTTP_TARGET: ATTR_URL_PATH,
22
+ DB_SYSTEM: ATTR_DB_SYSTEM_NAME,
23
+ DB_OPERATION: ATTR_DB_OPERATION_NAME,
24
+ DB_STATEMENT: ATTR_DB_QUERY_TEXT,
25
+ DB_TABLE: ATTR_DB_COLLECTION_NAME,
26
+ DB_NAMESPACE: ATTR_DB_NAMESPACE
27
+ },
28
+ Legacy: {
29
+ HTTP_METHOD: ATTR_HTTP_METHOD,
30
+ HTTP_STATUS_CODE: ATTR_HTTP_STATUS_CODE,
31
+ HTTP_TARGET: ATTR_HTTP_TARGET,
32
+ DB_SYSTEM: ATTR_DB_SYSTEM,
33
+ DB_OPERATION: ATTR_DB_OPERATION,
34
+ DB_STATEMENT: ATTR_DB_STATEMENT,
35
+ DB_TABLE: ATTR_DB_SQL_TABLE,
36
+ DB_NAME: ATTR_DB_NAME
37
+ }
38
+ };
39
+ //#endregion
40
+ export { Semconv };
@@ -0,0 +1,16 @@
1
+ //#region src/server.d.ts
2
+ /**
3
+ * Configuration options for the local DevTools server.
4
+ */
5
+ interface DevToolsOptions {
6
+ port?: number;
7
+ host?: string;
8
+ dbPath?: string;
9
+ }
10
+ /**
11
+ * Boots the AdonisJS-based DevTools HTTP server with DuckDB
12
+ * storage, OTLP ingestion, and the Studio UI.
13
+ */
14
+ declare function startServer(options?: DevToolsOptions): Promise<void>;
15
+ //#endregion
16
+ export { DevToolsOptions, startServer };
package/dist/server.js ADDED
@@ -0,0 +1,68 @@
1
+ import { closeConnection, initConnection } from "./db/connection.js";
2
+ import { initializeSchema } from "./db/schema.js";
3
+ import { Ignitor, prettyPrintError } from "@adonisjs/core";
4
+ import { join } from "node:path";
5
+ import "reflect-metadata";
6
+ import { homedir } from "node:os";
7
+ //#region src/server.ts
8
+ function printStartupBanner(options) {
9
+ const dim = (s) => `\x1B[2m${s}\x1B[22m`;
10
+ const bold = (s) => `\x1B[1m${s}\x1B[22m`;
11
+ const cyan = (s) => `\x1B[36m${s}\x1B[39m`;
12
+ const green = (s) => `\x1B[32m${s}\x1B[39m`;
13
+ const local = `http://localhost:${options.port}`;
14
+ console.log();
15
+ console.log(` ${cyan(bold("Monocle Studio"))} ${dim("v0.1.0")}`);
16
+ console.log(` ${dim("Local observability for your app. Traces, logs, queries & more.")}`);
17
+ console.log();
18
+ console.log(` ${green("➜")} ${bold("Dashboard:")} ${cyan(local)}`);
19
+ console.log(` ${green("➜")} ${bold("OTLP:")} ${dim(`http://localhost:${options.port}/v1/traces`)}`);
20
+ console.log(` ${green("➜")} ${bold("Docs:")} ${dim("https://docs.monocle.sh/studio/overview")}`);
21
+ console.log();
22
+ }
23
+ /**
24
+ * Boots the AdonisJS-based DevTools HTTP server with DuckDB
25
+ * storage, OTLP ingestion, and the Studio UI.
26
+ */
27
+ async function startServer(options = {}) {
28
+ const port = options.port ?? 4200;
29
+ const host = options.host ?? "0.0.0.0";
30
+ const dbPath = options.dbPath ?? join(homedir(), ".config", "monocle", "studio.db");
31
+ const APP_ROOT = new URL("./", import.meta.url);
32
+ const IMPORTER = (filePath) => {
33
+ if (filePath.startsWith("./") || filePath.startsWith("../")) return import(new URL(filePath, APP_ROOT).href);
34
+ return import(filePath);
35
+ };
36
+ process.env.PORT = String(port);
37
+ process.env.HOST = host;
38
+ process.env.NODE_ENV = "development";
39
+ process.env.APP_KEY = process.env.APP_KEY || "devtools-local-key-not-used-for-security";
40
+ /**
41
+ * Force exit on CTRL+C. Close DuckDB first.
42
+ */
43
+ process.on("SIGINT", async () => {
44
+ console.log(`\n \x1B[2mShutting down...\x1B[22m`);
45
+ await closeConnection();
46
+ process.exit(0);
47
+ });
48
+ process.on("SIGTERM", async () => {
49
+ await closeConnection();
50
+ process.exit(0);
51
+ });
52
+ new Ignitor(APP_ROOT, { importer: IMPORTER }).tap((app) => {
53
+ app.booting(async () => {
54
+ await initializeSchema(await initConnection(dbPath));
55
+ });
56
+ app.ready(async () => {
57
+ printStartupBanner({
58
+ port,
59
+ host
60
+ });
61
+ });
62
+ }).httpServer().start().catch((error) => {
63
+ process.exitCode = 1;
64
+ prettyPrintError(error);
65
+ });
66
+ }
67
+ //#endregion
68
+ export { startServer };
@@ -0,0 +1,24 @@
1
+ import { EventEmitter } from "node:events";
2
+
3
+ //#region src/services/event_bus.d.ts
4
+ interface DevToolsEvent {
5
+ type: 'traces' | 'logs';
6
+ count: number;
7
+ }
8
+ /**
9
+ * In-process event bus for broadcasting telemetry ingestion
10
+ * events to SSE subscribers in real time.
11
+ */
12
+ declare class DevToolsEventBus extends EventEmitter {
13
+ /**
14
+ * Broadcasts a new ingestion event to all listeners.
15
+ */
16
+ emitIngested(event: DevToolsEvent): void;
17
+ /**
18
+ * Subscribes to ingestion events. Returns an unsubscribe function.
19
+ */
20
+ onIngested(callback: (event: DevToolsEvent) => void): () => this;
21
+ }
22
+ declare const eventBus: DevToolsEventBus;
23
+ //#endregion
24
+ export { DevToolsEvent, eventBus };
@@ -0,0 +1,24 @@
1
+ import { EventEmitter } from "node:events";
2
+ //#region src/services/event_bus.ts
3
+ /**
4
+ * In-process event bus for broadcasting telemetry ingestion
5
+ * events to SSE subscribers in real time.
6
+ */
7
+ var DevToolsEventBus = class extends EventEmitter {
8
+ /**
9
+ * Broadcasts a new ingestion event to all listeners.
10
+ */
11
+ emitIngested(event) {
12
+ this.emit("ingested", event);
13
+ }
14
+ /**
15
+ * Subscribes to ingestion events. Returns an unsubscribe function.
16
+ */
17
+ onIngested(callback) {
18
+ this.on("ingested", callback);
19
+ return () => this.off("ingested", callback);
20
+ }
21
+ };
22
+ const eventBus = new DevToolsEventBus();
23
+ //#endregion
24
+ export { eventBus };
@@ -0,0 +1,21 @@
1
+ import { FlatLog } from "../types/logs.js";
2
+ import { FlatSpan } from "../types/spans.js";
3
+ import { OtlpAttribute, OtlpLogRequest, OtlpTraceRequest } from "../types/otlp.js";
4
+
5
+ //#region src/services/otlp_parser.d.ts
6
+ /**
7
+ * Flattens an array of OTLP key-value attributes into a plain object.
8
+ */
9
+ declare function flattenAttributes(attrs?: OtlpAttribute[]): Record<string, unknown>;
10
+ /**
11
+ * Converts an OTLP trace export request into flat span models
12
+ * ready for DuckDB insertion.
13
+ */
14
+ declare function parseTraceRequest(body: OtlpTraceRequest): FlatSpan[];
15
+ /**
16
+ * Converts an OTLP log export request into flat log models
17
+ * ready for DuckDB insertion.
18
+ */
19
+ declare function parseLogRequest(body: OtlpLogRequest): FlatLog[];
20
+ //#endregion
21
+ export { flattenAttributes, parseLogRequest, parseTraceRequest };