@kopai/sqlite-datasource 0.3.0 → 0.5.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.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DatabaseSync } from "node:sqlite";
2
- import { DummyDriver, Kysely, SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler, sql } from "kysely";
3
2
  import { otlp } from "@kopai/core";
3
+ import { DummyDriver, Kysely, SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler, sql } from "kysely";
4
4
 
5
5
  //#region src/sqlite-datasource-error.ts
6
6
  var SqliteDatasourceError = class extends Error {
@@ -17,14 +17,14 @@ var SqliteDatasourceQueryError = class extends SqliteDatasourceError {
17
17
  };
18
18
 
19
19
  //#endregion
20
- //#region src/datasource.ts
20
+ //#region src/db-datasource.ts
21
21
  const queryBuilder = new Kysely({ dialect: {
22
22
  createAdapter: () => new SqliteAdapter(),
23
23
  createDriver: () => new DummyDriver(),
24
24
  createIntrospector: (db) => new SqliteIntrospector(db),
25
25
  createQueryCompiler: () => new SqliteQueryCompiler()
26
26
  } });
27
- var NodeSqliteTelemetryDatasource = class {
27
+ var DbDatasource = class {
28
28
  constructor(sqliteConnection) {
29
29
  this.sqliteConnection = sqliteConnection;
30
30
  }
@@ -46,6 +46,9 @@ var NodeSqliteTelemetryDatasource = class {
46
46
  if (metric.summary) for (const dataPoint of metric.summary.dataPoints ?? []) summaryRows.push(toSummaryRow(resource, resourceSchemaUrl, scope, scopeSchemaUrl, metric, dataPoint));
47
47
  }
48
48
  }
49
+ }
50
+ this.sqliteConnection.exec("BEGIN");
51
+ try {
49
52
  for (const { table, rows } of [
50
53
  {
51
54
  table: "otel_metrics_gauge",
@@ -71,6 +74,10 @@ var NodeSqliteTelemetryDatasource = class {
71
74
  const { sql, parameters } = queryBuilder.insertInto(table).values(row).compile();
72
75
  this.sqliteConnection.prepare(sql).run(...parameters);
73
76
  }
77
+ this.sqliteConnection.exec("COMMIT");
78
+ } catch (error) {
79
+ this.sqliteConnection.exec("ROLLBACK");
80
+ throw error;
74
81
  }
75
82
  return { rejectedDataPoints: "" };
76
83
  }
@@ -99,20 +106,27 @@ var NodeSqliteTelemetryDatasource = class {
99
106
  }
100
107
  }
101
108
  }
102
- for (const row of spanRows) {
103
- const { sql, parameters } = queryBuilder.insertInto("otel_traces").values(row).compile();
104
- this.sqliteConnection.prepare(sql).run(...parameters);
105
- }
106
- for (const [traceId, { min, max }] of traceTimestamps) {
107
- const { sql, parameters } = queryBuilder.insertInto("otel_traces_trace_id_ts").values({
108
- TraceId: traceId,
109
- Start: min,
110
- End: max
111
- }).onConflict((oc) => oc.column("TraceId").doUpdateSet({
112
- Start: (eb) => eb.fn("min", [eb.ref("otel_traces_trace_id_ts.Start"), eb.val(min)]),
113
- End: (eb) => eb.fn("max", [eb.ref("otel_traces_trace_id_ts.End"), eb.val(max)])
114
- })).compile();
115
- this.sqliteConnection.prepare(sql).run(...parameters);
109
+ this.sqliteConnection.exec("BEGIN");
110
+ try {
111
+ for (const row of spanRows) {
112
+ const { sql, parameters } = queryBuilder.insertInto("otel_traces").values(row).compile();
113
+ this.sqliteConnection.prepare(sql).run(...parameters);
114
+ }
115
+ for (const [traceId, { min, max }] of traceTimestamps) {
116
+ const { sql, parameters } = queryBuilder.insertInto("otel_traces_trace_id_ts").values({
117
+ TraceId: traceId,
118
+ Start: min,
119
+ End: max
120
+ }).onConflict((oc) => oc.column("TraceId").doUpdateSet({
121
+ Start: (eb) => eb.fn("min", [eb.ref("otel_traces_trace_id_ts.Start"), eb.val(min)]),
122
+ End: (eb) => eb.fn("max", [eb.ref("otel_traces_trace_id_ts.End"), eb.val(max)])
123
+ })).compile();
124
+ this.sqliteConnection.prepare(sql).run(...parameters);
125
+ }
126
+ this.sqliteConnection.exec("COMMIT");
127
+ } catch (error) {
128
+ this.sqliteConnection.exec("ROLLBACK");
129
+ throw error;
116
130
  }
117
131
  return { rejectedSpans: "" };
118
132
  }
@@ -125,9 +139,16 @@ var NodeSqliteTelemetryDatasource = class {
125
139
  for (const logRecord of scopeLog.logRecords ?? []) logRows.push(toLogRow(resource, resourceSchemaUrl, scope, scopeSchemaUrl, logRecord));
126
140
  }
127
141
  }
128
- for (const row of logRows) {
129
- const { sql, parameters } = queryBuilder.insertInto("otel_logs").values(row).compile();
130
- this.sqliteConnection.prepare(sql).run(...parameters);
142
+ this.sqliteConnection.exec("BEGIN");
143
+ try {
144
+ for (const row of logRows) {
145
+ const { sql, parameters } = queryBuilder.insertInto("otel_logs").values(row).compile();
146
+ this.sqliteConnection.prepare(sql).run(...parameters);
147
+ }
148
+ this.sqliteConnection.exec("COMMIT");
149
+ } catch (error) {
150
+ this.sqliteConnection.exec("ROLLBACK");
151
+ throw error;
131
152
  }
132
153
  return { rejectedLogRecords: "" };
133
154
  }
@@ -362,58 +383,72 @@ var NodeSqliteTelemetryDatasource = class {
362
383
  }
363
384
  async discoverMetrics() {
364
385
  try {
365
- const distinctMetricsSql = METRIC_TABLES.map(({ table, type }) => `SELECT DISTINCT MetricName, MetricUnit, MetricDescription, '${type}' as MetricType FROM ${table}`).join(" UNION ");
366
- const distinctMetrics = this.sqliteConnection.prepare(distinctMetricsSql).all();
386
+ const discoveryState = /* @__PURE__ */ new Map();
387
+ const attrTuplesSql = METRIC_TABLES.map(({ table, type }) => `SELECT MetricName, MetricUnit, MetricDescription, '${type}' as MetricType, json_each.key as attr_key, json_each.value as attr_value
388
+ FROM ${table}, json_each(Attributes)`).join(" UNION ALL ");
389
+ const attrTuples = this.sqliteConnection.prepare(attrTuplesSql).all();
390
+ const resAttrTuplesSql = METRIC_TABLES.map(({ table, type }) => `SELECT MetricName, MetricUnit, MetricDescription, '${type}' as MetricType, json_each.key as attr_key, json_each.value as attr_value
391
+ FROM ${table}, json_each(ResourceAttributes)`).join(" UNION ALL ");
392
+ const resAttrTuples = this.sqliteConnection.prepare(resAttrTuplesSql).all();
393
+ for (const tuple of attrTuples) {
394
+ const metricKey = `${tuple.MetricName}:${tuple.MetricType}`;
395
+ let state = discoveryState.get(metricKey);
396
+ if (!state) {
397
+ state = {
398
+ name: tuple.MetricName,
399
+ type: tuple.MetricType,
400
+ unit: tuple.MetricUnit || void 0,
401
+ description: tuple.MetricDescription || void 0,
402
+ attributes: /* @__PURE__ */ new Map(),
403
+ resourceAttributes: /* @__PURE__ */ new Map()
404
+ };
405
+ discoveryState.set(metricKey, state);
406
+ }
407
+ if (!state.attributes.has(tuple.attr_key)) state.attributes.set(tuple.attr_key, /* @__PURE__ */ new Set());
408
+ state.attributes.get(tuple.attr_key).add(String(tuple.attr_value));
409
+ }
410
+ for (const tuple of resAttrTuples) {
411
+ const metricKey = `${tuple.MetricName}:${tuple.MetricType}`;
412
+ let state = discoveryState.get(metricKey);
413
+ if (!state) {
414
+ state = {
415
+ name: tuple.MetricName,
416
+ type: tuple.MetricType,
417
+ unit: tuple.MetricUnit || void 0,
418
+ description: tuple.MetricDescription || void 0,
419
+ attributes: /* @__PURE__ */ new Map(),
420
+ resourceAttributes: /* @__PURE__ */ new Map()
421
+ };
422
+ discoveryState.set(metricKey, state);
423
+ }
424
+ if (!state.resourceAttributes.has(tuple.attr_key)) state.resourceAttributes.set(tuple.attr_key, /* @__PURE__ */ new Set());
425
+ state.resourceAttributes.get(tuple.attr_key).add(String(tuple.attr_value));
426
+ }
367
427
  const metrics = [];
368
- for (const metric of distinctMetrics) {
369
- const table = METRIC_TABLES.find((t) => t.type === metric.MetricType).table;
370
- const attrKeysSql = `
371
- SELECT DISTINCT json_each.key as key
372
- FROM ${table}, json_each(Attributes)
373
- WHERE MetricName = ?
374
- `;
375
- const attrKeys = this.sqliteConnection.prepare(attrKeysSql).all(metric.MetricName);
428
+ for (const state of discoveryState.values()) {
376
429
  let attrsTruncated = false;
377
430
  const attributes = {};
378
- for (const { key } of attrKeys) {
379
- const valuesSql = `
380
- SELECT DISTINCT json_each.value as value
381
- FROM ${table}, json_each(Attributes)
382
- WHERE MetricName = ? AND json_each.key = ?
383
- LIMIT ${MAX_ATTR_VALUES + 1}
384
- `;
385
- const values = this.sqliteConnection.prepare(valuesSql).all(metric.MetricName, key);
386
- if (values.length > MAX_ATTR_VALUES) {
431
+ for (const [key, valueSet] of state.attributes) {
432
+ const values = Array.from(valueSet);
433
+ if (values.length > MAX_ATTR_VALUES$1) {
387
434
  attrsTruncated = true;
388
- attributes[key] = values.slice(0, MAX_ATTR_VALUES).map((v) => String(v.value));
389
- } else attributes[key] = values.map((v) => String(v.value));
435
+ attributes[key] = values.slice(0, MAX_ATTR_VALUES$1);
436
+ } else attributes[key] = values;
390
437
  }
391
- const resAttrKeysSql = `
392
- SELECT DISTINCT json_each.key as key
393
- FROM ${table}, json_each(ResourceAttributes)
394
- WHERE MetricName = ?
395
- `;
396
- const resAttrKeys = this.sqliteConnection.prepare(resAttrKeysSql).all(metric.MetricName);
397
438
  let resAttrsTruncated = false;
398
439
  const resourceAttributes = {};
399
- for (const { key } of resAttrKeys) {
400
- const valuesSql = `
401
- SELECT DISTINCT json_each.value as value
402
- FROM ${table}, json_each(ResourceAttributes)
403
- WHERE MetricName = ? AND json_each.key = ?
404
- LIMIT ${MAX_ATTR_VALUES + 1}
405
- `;
406
- const values = this.sqliteConnection.prepare(valuesSql).all(metric.MetricName, key);
407
- if (values.length > MAX_ATTR_VALUES) {
440
+ for (const [key, valueSet] of state.resourceAttributes) {
441
+ const values = Array.from(valueSet);
442
+ if (values.length > MAX_ATTR_VALUES$1) {
408
443
  resAttrsTruncated = true;
409
- resourceAttributes[key] = values.slice(0, MAX_ATTR_VALUES).map((v) => String(v.value));
410
- } else resourceAttributes[key] = values.map((v) => String(v.value));
444
+ resourceAttributes[key] = values.slice(0, MAX_ATTR_VALUES$1);
445
+ } else resourceAttributes[key] = values;
411
446
  }
412
447
  metrics.push({
413
- name: metric.MetricName,
414
- type: metric.MetricType,
415
- unit: metric.MetricUnit || void 0,
416
- description: metric.MetricDescription || void 0,
448
+ name: state.name,
449
+ type: state.type,
450
+ unit: state.unit,
451
+ description: state.description,
417
452
  attributes: {
418
453
  values: attributes,
419
454
  ...attrsTruncated && { _truncated: true }
@@ -511,7 +546,7 @@ function toGaugeRow(resource, resourceSchemaUrl, scope, scopeSchemaUrl, metric,
511
546
  "Exemplars.TimeUnix": exemplarsArrayToJson(exemplars, (e) => String(nanosToSqlite(e.timeUnixNano))),
512
547
  "Exemplars.Value": exemplarsArrayToJson(exemplars, (e) => e.asDouble ?? Number(e.asInt ?? 0)),
513
548
  "Exemplars.SpanId": exemplarsArrayToJson(exemplars, (e) => e.spanId ?? ""),
514
- "Exemplars.TraceId": exemplarsArrayToJson(exemplars, (e) => e.traceId ? bufferToHex(e.traceId) : "")
549
+ "Exemplars.TraceId": exemplarsArrayToJson(exemplars, (e) => e.traceId ?? "")
515
550
  };
516
551
  }
517
552
  function toSumRow(resource, resourceSchemaUrl, scope, scopeSchemaUrl, metric, dataPoint, aggregationTemporality, isMonotonic) {
@@ -537,7 +572,7 @@ function toSumRow(resource, resourceSchemaUrl, scope, scopeSchemaUrl, metric, da
537
572
  "Exemplars.TimeUnix": exemplarsArrayToJson(exemplars, (e) => String(nanosToSqlite(e.timeUnixNano))),
538
573
  "Exemplars.Value": exemplarsArrayToJson(exemplars, (e) => e.asDouble ?? Number(e.asInt ?? 0)),
539
574
  "Exemplars.SpanId": exemplarsArrayToJson(exemplars, (e) => e.spanId ?? ""),
540
- "Exemplars.TraceId": exemplarsArrayToJson(exemplars, (e) => e.traceId ? bufferToHex(e.traceId) : ""),
575
+ "Exemplars.TraceId": exemplarsArrayToJson(exemplars, (e) => e.traceId ?? ""),
541
576
  AggTemporality: aggTemporalityToString(aggregationTemporality),
542
577
  IsMonotonic: isMonotonic ? 1 : 0
543
578
  };
@@ -569,7 +604,7 @@ function toHistogramRow(resource, resourceSchemaUrl, scope, scopeSchemaUrl, metr
569
604
  "Exemplars.TimeUnix": exemplarsArrayToJson(exemplars, (e) => String(nanosToSqlite(e.timeUnixNano))),
570
605
  "Exemplars.Value": exemplarsArrayToJson(exemplars, (e) => e.asDouble ?? Number(e.asInt ?? 0)),
571
606
  "Exemplars.SpanId": exemplarsArrayToJson(exemplars, (e) => e.spanId ?? ""),
572
- "Exemplars.TraceId": exemplarsArrayToJson(exemplars, (e) => e.traceId ? bufferToHex(e.traceId) : ""),
607
+ "Exemplars.TraceId": exemplarsArrayToJson(exemplars, (e) => e.traceId ?? ""),
573
608
  AggTemporality: aggTemporalityToString(aggregationTemporality)
574
609
  };
575
610
  }
@@ -605,7 +640,7 @@ function toExpHistogramRow(resource, resourceSchemaUrl, scope, scopeSchemaUrl, m
605
640
  "Exemplars.TimeUnix": exemplarsArrayToJson(exemplars, (e) => String(nanosToSqlite(e.timeUnixNano))),
606
641
  "Exemplars.Value": exemplarsArrayToJson(exemplars, (e) => e.asDouble ?? Number(e.asInt ?? 0)),
607
642
  "Exemplars.SpanId": exemplarsArrayToJson(exemplars, (e) => e.spanId ?? ""),
608
- "Exemplars.TraceId": exemplarsArrayToJson(exemplars, (e) => e.traceId ? bufferToHex(e.traceId) : ""),
643
+ "Exemplars.TraceId": exemplarsArrayToJson(exemplars, (e) => e.traceId ?? ""),
609
644
  AggTemporality: aggTemporalityToString(aggregationTemporality)
610
645
  };
611
646
  }
@@ -678,9 +713,6 @@ function exemplarsArrayToJson(exemplars, extractor) {
678
713
  if (exemplars.length === 0) return "[]";
679
714
  return JSON.stringify(exemplars.map(extractor));
680
715
  }
681
- function bufferToHex(buf) {
682
- return Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
683
- }
684
716
  function mapRowToOtelTraces(row) {
685
717
  return {
686
718
  TraceId: row.TraceId,
@@ -785,7 +817,7 @@ const METRIC_TABLES = [
785
817
  type: "Summary"
786
818
  }
787
819
  ];
788
- const MAX_ATTR_VALUES = 100;
820
+ const MAX_ATTR_VALUES$1 = 100;
789
821
  function mapRowToOtelMetrics(row, metricType) {
790
822
  const base = {
791
823
  TimeUnix: String(row.TimeUnix),
@@ -858,6 +890,154 @@ function mapRowToOtelMetrics(row, metricType) {
858
890
  };
859
891
  }
860
892
 
893
+ //#endregion
894
+ //#region src/optimized-datasource.ts
895
+ const MAX_ATTR_VALUES = 100;
896
+ var OptimizedDatasource = class {
897
+ discoveryState = /* @__PURE__ */ new Map();
898
+ constructor(dbDatasource) {
899
+ this.dbDatasource = dbDatasource;
900
+ }
901
+ async writeMetrics(metricsData) {
902
+ const result = await this.dbDatasource.writeMetrics(metricsData);
903
+ for (const resourceMetric of metricsData.resourceMetrics ?? []) {
904
+ const { resource } = resourceMetric;
905
+ const resourceAttrs = this.extractResourceAttributes(resource);
906
+ for (const scopeMetric of resourceMetric.scopeMetrics ?? []) for (const metric of scopeMetric.metrics ?? []) {
907
+ const metricType = this.getMetricType(metric);
908
+ if (!metricType) continue;
909
+ const metricKey = `${metric.name}:${metricType}`;
910
+ let state = this.discoveryState.get(metricKey);
911
+ if (!state) {
912
+ state = {
913
+ name: metric.name ?? "",
914
+ type: metricType,
915
+ unit: metric.unit || void 0,
916
+ description: metric.description || void 0,
917
+ attributes: /* @__PURE__ */ new Map(),
918
+ resourceAttributes: /* @__PURE__ */ new Map()
919
+ };
920
+ this.discoveryState.set(metricKey, state);
921
+ }
922
+ for (const [key, value] of Object.entries(resourceAttrs)) {
923
+ if (!state.resourceAttributes.has(key)) state.resourceAttributes.set(key, /* @__PURE__ */ new Set());
924
+ state.resourceAttributes.get(key).add(String(value));
925
+ }
926
+ const dataPoints = this.getDataPoints(metric);
927
+ for (const dp of dataPoints) {
928
+ const attrs = this.extractAttributes(dp.attributes);
929
+ for (const [key, value] of Object.entries(attrs)) {
930
+ if (!state.attributes.has(key)) state.attributes.set(key, /* @__PURE__ */ new Set());
931
+ state.attributes.get(key).add(String(value));
932
+ }
933
+ }
934
+ }
935
+ }
936
+ return result;
937
+ }
938
+ async writeTraces(tracesData) {
939
+ return this.dbDatasource.writeTraces(tracesData);
940
+ }
941
+ async writeLogs(logsData) {
942
+ return this.dbDatasource.writeLogs(logsData);
943
+ }
944
+ async getTraces(filter) {
945
+ return this.dbDatasource.getTraces(filter);
946
+ }
947
+ async getMetrics(filter) {
948
+ return this.dbDatasource.getMetrics(filter);
949
+ }
950
+ async getLogs(filter) {
951
+ return this.dbDatasource.getLogs(filter);
952
+ }
953
+ async discoverMetrics() {
954
+ const metrics = [];
955
+ for (const state of this.discoveryState.values()) {
956
+ let attrsTruncated = false;
957
+ const attributes = {};
958
+ for (const [key, valueSet] of state.attributes) {
959
+ const values = Array.from(valueSet);
960
+ if (values.length > MAX_ATTR_VALUES) {
961
+ attrsTruncated = true;
962
+ attributes[key] = values.slice(0, MAX_ATTR_VALUES);
963
+ } else attributes[key] = values;
964
+ }
965
+ let resAttrsTruncated = false;
966
+ const resourceAttributes = {};
967
+ for (const [key, valueSet] of state.resourceAttributes) {
968
+ const values = Array.from(valueSet);
969
+ if (values.length > MAX_ATTR_VALUES) {
970
+ resAttrsTruncated = true;
971
+ resourceAttributes[key] = values.slice(0, MAX_ATTR_VALUES);
972
+ } else resourceAttributes[key] = values;
973
+ }
974
+ metrics.push({
975
+ name: state.name,
976
+ type: state.type,
977
+ unit: state.unit,
978
+ description: state.description,
979
+ attributes: {
980
+ values: attributes,
981
+ ...attrsTruncated && { _truncated: true }
982
+ },
983
+ resourceAttributes: {
984
+ values: resourceAttributes,
985
+ ...resAttrsTruncated && { _truncated: true }
986
+ }
987
+ });
988
+ }
989
+ return { metrics };
990
+ }
991
+ getMetricType(metric) {
992
+ if (metric.gauge) return "Gauge";
993
+ if (metric.sum) return "Sum";
994
+ if (metric.histogram) return "Histogram";
995
+ if (metric.exponentialHistogram) return "ExponentialHistogram";
996
+ if (metric.summary) return "Summary";
997
+ return null;
998
+ }
999
+ getDataPoints(metric) {
1000
+ if (metric.gauge) return metric.gauge.dataPoints ?? [];
1001
+ if (metric.sum) return metric.sum.dataPoints ?? [];
1002
+ if (metric.histogram) return metric.histogram.dataPoints ?? [];
1003
+ if (metric.exponentialHistogram) return metric.exponentialHistogram.dataPoints ?? [];
1004
+ if (metric.summary) return metric.summary.dataPoints ?? [];
1005
+ return [];
1006
+ }
1007
+ extractResourceAttributes(resource) {
1008
+ if (!resource?.attributes) return {};
1009
+ const result = {};
1010
+ for (const attr of resource.attributes) if (attr.key) result[attr.key] = this.extractAnyValue(attr.value);
1011
+ return result;
1012
+ }
1013
+ extractAttributes(attributes) {
1014
+ if (!attributes) return {};
1015
+ const result = {};
1016
+ for (const attr of attributes) if (attr.key) result[attr.key] = this.extractAnyValue(attr.value);
1017
+ return result;
1018
+ }
1019
+ extractAnyValue(value) {
1020
+ if (!value || typeof value !== "object") return value;
1021
+ const v = value;
1022
+ if (v.stringValue !== void 0) return v.stringValue;
1023
+ if (v.boolValue !== void 0) return v.boolValue;
1024
+ if (v.intValue !== void 0) return v.intValue;
1025
+ if (v.doubleValue !== void 0) return v.doubleValue;
1026
+ if (v.bytesValue !== void 0) return v.bytesValue;
1027
+ if (v.arrayValue && typeof v.arrayValue === "object") return (v.arrayValue.values ?? []).map((item) => this.extractAnyValue(item));
1028
+ if (v.kvlistValue && typeof v.kvlistValue === "object") {
1029
+ const kvlist = v.kvlistValue;
1030
+ const result = {};
1031
+ for (const kv of kvlist.values ?? []) if (kv.key) result[kv.key] = this.extractAnyValue(kv.value);
1032
+ return result;
1033
+ }
1034
+ return value;
1035
+ }
1036
+ };
1037
+ function createOptimizedDatasource(sqliteConnection) {
1038
+ return new OptimizedDatasource(new DbDatasource(sqliteConnection));
1039
+ }
1040
+
861
1041
  //#endregion
862
1042
  //#region src/sqlite-opentelemetry-ddl.ts
863
1043
  const ddl = `
@@ -1122,5 +1302,5 @@ function initializeDatabase(path, opts) {
1122
1302
  }
1123
1303
 
1124
1304
  //#endregion
1125
- export { NodeSqliteTelemetryDatasource, initializeDatabase };
1305
+ export { DbDatasource, OptimizedDatasource, createOptimizedDatasource, initializeDatabase };
1126
1306
  //# sourceMappingURL=index.mjs.map