datanotes 1.3.0 → 1.4.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/SECURITY.md CHANGED
@@ -49,8 +49,8 @@ Nothing leaves the machine unless you turn it on:
49
49
  LM Studio) keeps it on the machine.
50
50
  - **Telemetry** sends logs to the OTLP endpoint you configure (`telemetryEnabled`, off by default):
51
51
  operations, routes, note paths, actors, tool names and error messages (which may quote a value),
52
- not note bodies. `telemetryHeaders` (e.g. an
53
- ingestion key) is stored in the settings file; `OTEL_EXPORTER_OTLP_HEADERS` keeps it out.
52
+ not note bodies. `telemetryHeaders` (an API key) is
53
+ stored in the settings file; `OTEL_EXPORTER_OTLP_HEADERS` keeps it out.
54
54
  - **Webhooks** (created by the owner) post change events to the URLs they name.
55
55
  - **Apps' MCP endpoints** (registered by the owner): the engine sends each app's `mcp_token`, and
56
56
  the arguments and caller of every call of its tools, to the app's `mcp_url`, which may be remote.
@@ -3075,9 +3075,72 @@ function attr(key, v) {
3075
3075
  }
3076
3076
  var attrs = (a) => Object.entries(a).map(([k, v]) => attr(k, v)).filter((x) => x !== null);
3077
3077
  var fetchSend = async (url, body, headers) => {
3078
- const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", ...headers }, body });
3078
+ const res = await fetch(url, { method: "POST", headers, body });
3079
3079
  if (!res.ok) throw new Error(`OTLP HTTP ${res.status}`);
3080
3080
  };
3081
+ var Pb = class {
3082
+ constructor() {
3083
+ this.parts = [];
3084
+ }
3085
+ varint(v) {
3086
+ let x = BigInt.asUintN(64, v);
3087
+ const b128 = BigInt(128);
3088
+ const low7 = BigInt(127);
3089
+ const seven = BigInt(7);
3090
+ while (x >= b128) {
3091
+ this.parts.push(Number(x & low7) | 128);
3092
+ x >>= seven;
3093
+ }
3094
+ this.parts.push(Number(x));
3095
+ }
3096
+ tag(field, wire) {
3097
+ this.varint(BigInt(field << 3 | wire));
3098
+ }
3099
+ uint(field, v) {
3100
+ this.tag(field, 0);
3101
+ this.varint(BigInt(v));
3102
+ return this;
3103
+ }
3104
+ fixed64(field, v) {
3105
+ this.tag(field, 1);
3106
+ for (let i = 0; i < 8; i++) this.parts.push(Number(v >> BigInt(8 * i) & BigInt(255)));
3107
+ return this;
3108
+ }
3109
+ double(field, v) {
3110
+ this.tag(field, 1);
3111
+ const b = new DataView(new ArrayBuffer(8));
3112
+ b.setFloat64(0, v, true);
3113
+ for (let i = 0; i < 8; i++) this.parts.push(b.getUint8(i));
3114
+ return this;
3115
+ }
3116
+ bytes(field, data) {
3117
+ this.tag(field, 2);
3118
+ this.varint(BigInt(data.length));
3119
+ for (const x of data) this.parts.push(x);
3120
+ return this;
3121
+ }
3122
+ string(field, s) {
3123
+ return this.bytes(field, new TextEncoder().encode(s));
3124
+ }
3125
+ message(field, m) {
3126
+ return this.bytes(field, m.finish());
3127
+ }
3128
+ finish() {
3129
+ return Uint8Array.from(this.parts);
3130
+ }
3131
+ };
3132
+ function pbValue(v) {
3133
+ if (typeof v === "string") return new Pb().string(1, v);
3134
+ if (typeof v === "boolean") return new Pb().uint(2, v ? 1 : 0);
3135
+ return Number.isInteger(v) ? new Pb().uint(3, BigInt(v)) : new Pb().double(4, v);
3136
+ }
3137
+ function pbAttrs(m, field, a) {
3138
+ for (const [k, v] of Object.entries(a)) {
3139
+ if (v === void 0 || v === null) continue;
3140
+ m.message(field, new Pb().string(1, k).message(2, pbValue(v)));
3141
+ }
3142
+ return m;
3143
+ }
3081
3144
  var OtlpLogExporter = class {
3082
3145
  constructor(o) {
3083
3146
  this.o = o;
@@ -3103,10 +3166,12 @@ var OtlpLogExporter = class {
3103
3166
  if (this.buffer.length === 0) return this.inflight;
3104
3167
  const batch = this.buffer;
3105
3168
  this.buffer = [];
3106
- const body = JSON.stringify({
3169
+ const resource = { "service.name": this.o.serviceName, "service.version": this.o.serviceVersion, ...this.o.resource };
3170
+ const protobuf = (this.o.protocol ?? "http/protobuf") === "http/protobuf";
3171
+ const body = protobuf ? this.protobuf(resource, batch) : JSON.stringify({
3107
3172
  resourceLogs: [
3108
3173
  {
3109
- resource: { attributes: attrs({ "service.name": this.o.serviceName, "service.version": this.o.serviceVersion, ...this.o.resource }) },
3174
+ resource: { attributes: attrs(resource) },
3110
3175
  scopeLogs: [
3111
3176
  {
3112
3177
  scope: { name: this.o.serviceName },
@@ -3126,13 +3191,24 @@ var OtlpLogExporter = class {
3126
3191
  const url = `${this.o.endpoint.replace(/\/+$/, "")}/v1/logs`;
3127
3192
  const send = this.o.send ?? fetchSend;
3128
3193
  this.inflight = this.inflight.then(
3129
- () => send(url, body, this.o.headers ?? {}).then(
3194
+ () => send(url, body, { "Content-Type": protobuf ? "application/x-protobuf" : "application/json", ...this.o.headers ?? {} }).then(
3130
3195
  () => void (this.sent += batch.length),
3131
3196
  () => void (this.dropped += batch.length)
3132
3197
  )
3133
3198
  );
3134
3199
  return this.inflight;
3135
3200
  }
3201
+ protobuf(resource, batch) {
3202
+ const scope = new Pb().message(1, new Pb().string(1, this.o.serviceName));
3203
+ for (const r of batch) {
3204
+ const t = BigInt(r.timeUnixNano);
3205
+ const rec = new Pb().fixed64(1, t).uint(2, SEVERITY_NUMBER[r.severity]).string(3, r.severity).message(5, new Pb().string(1, r.body));
3206
+ pbAttrs(rec, 6, r.attributes).fixed64(11, t);
3207
+ scope.message(2, rec);
3208
+ }
3209
+ const resourceLogs = new Pb().message(1, pbAttrs(new Pb(), 1, resource)).message(2, scope);
3210
+ return new Pb().message(1, resourceLogs).finish();
3211
+ }
3136
3212
  /** Stop the timer and send what is left. */
3137
3213
  async close() {
3138
3214
  if (this.timer) clearInterval(this.timer);
@@ -3156,18 +3232,29 @@ function parseOtlpHeaders(text) {
3156
3232
  }
3157
3233
  return out;
3158
3234
  }
3235
+ var parseResourceAttributes = parseOtlpHeaders;
3236
+ var truthy = (v) => (v ?? "").trim().toLowerCase() === "true";
3159
3237
  function otlpFromEnv(env, defaults) {
3238
+ if (truthy(env.OTEL_SDK_DISABLED) || (env.OTEL_LOGS_EXPORTER ?? "").trim().toLowerCase() === "none") return null;
3160
3239
  const logs = env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT?.trim();
3161
3240
  const base = env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim();
3162
3241
  const endpoint = logs ? logs.replace(/\/v1\/logs\/?$/, "") : base;
3163
3242
  if (!endpoint) return null;
3243
+ const protocol = otlpProtocol(env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? env.OTEL_EXPORTER_OTLP_PROTOCOL);
3244
+ const resource = parseResourceAttributes(env.OTEL_RESOURCE_ATTRIBUTES);
3164
3245
  return {
3165
3246
  endpoint,
3166
- serviceName: env.OTEL_SERVICE_NAME?.trim() || defaults.serviceName,
3247
+ serviceName: env.OTEL_SERVICE_NAME?.trim() || resource["service.name"] || defaults.serviceName,
3167
3248
  ...defaults.serviceVersion ? { serviceVersion: defaults.serviceVersion } : {},
3249
+ ...protocol ? { protocol } : {},
3250
+ ...Object.keys(resource).length ? { resource } : {},
3168
3251
  headers: { ...parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS), ...parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_LOGS_HEADERS) }
3169
3252
  };
3170
3253
  }
3254
+ function otlpProtocol(v) {
3255
+ const p = (v ?? "").trim().toLowerCase();
3256
+ return p === "http/protobuf" || p === "http/json" ? p : void 0;
3257
+ }
3171
3258
 
3172
3259
  // src/api/client.ts
3173
3260
  var ACTOR_HEADER = "X-Datanotes-Actor";
package/dist/api/index.js CHANGED
@@ -3033,9 +3033,72 @@ function attr(key, v) {
3033
3033
  }
3034
3034
  var attrs = (a) => Object.entries(a).map(([k, v]) => attr(k, v)).filter((x) => x !== null);
3035
3035
  var fetchSend = async (url, body, headers) => {
3036
- const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", ...headers }, body });
3036
+ const res = await fetch(url, { method: "POST", headers, body });
3037
3037
  if (!res.ok) throw new Error(`OTLP HTTP ${res.status}`);
3038
3038
  };
3039
+ var Pb = class {
3040
+ constructor() {
3041
+ this.parts = [];
3042
+ }
3043
+ varint(v) {
3044
+ let x = BigInt.asUintN(64, v);
3045
+ const b128 = BigInt(128);
3046
+ const low7 = BigInt(127);
3047
+ const seven = BigInt(7);
3048
+ while (x >= b128) {
3049
+ this.parts.push(Number(x & low7) | 128);
3050
+ x >>= seven;
3051
+ }
3052
+ this.parts.push(Number(x));
3053
+ }
3054
+ tag(field, wire) {
3055
+ this.varint(BigInt(field << 3 | wire));
3056
+ }
3057
+ uint(field, v) {
3058
+ this.tag(field, 0);
3059
+ this.varint(BigInt(v));
3060
+ return this;
3061
+ }
3062
+ fixed64(field, v) {
3063
+ this.tag(field, 1);
3064
+ for (let i = 0; i < 8; i++) this.parts.push(Number(v >> BigInt(8 * i) & BigInt(255)));
3065
+ return this;
3066
+ }
3067
+ double(field, v) {
3068
+ this.tag(field, 1);
3069
+ const b = new DataView(new ArrayBuffer(8));
3070
+ b.setFloat64(0, v, true);
3071
+ for (let i = 0; i < 8; i++) this.parts.push(b.getUint8(i));
3072
+ return this;
3073
+ }
3074
+ bytes(field, data) {
3075
+ this.tag(field, 2);
3076
+ this.varint(BigInt(data.length));
3077
+ for (const x of data) this.parts.push(x);
3078
+ return this;
3079
+ }
3080
+ string(field, s) {
3081
+ return this.bytes(field, new TextEncoder().encode(s));
3082
+ }
3083
+ message(field, m) {
3084
+ return this.bytes(field, m.finish());
3085
+ }
3086
+ finish() {
3087
+ return Uint8Array.from(this.parts);
3088
+ }
3089
+ };
3090
+ function pbValue(v) {
3091
+ if (typeof v === "string") return new Pb().string(1, v);
3092
+ if (typeof v === "boolean") return new Pb().uint(2, v ? 1 : 0);
3093
+ return Number.isInteger(v) ? new Pb().uint(3, BigInt(v)) : new Pb().double(4, v);
3094
+ }
3095
+ function pbAttrs(m, field, a) {
3096
+ for (const [k, v] of Object.entries(a)) {
3097
+ if (v === void 0 || v === null) continue;
3098
+ m.message(field, new Pb().string(1, k).message(2, pbValue(v)));
3099
+ }
3100
+ return m;
3101
+ }
3039
3102
  var OtlpLogExporter = class {
3040
3103
  constructor(o) {
3041
3104
  this.o = o;
@@ -3061,10 +3124,12 @@ var OtlpLogExporter = class {
3061
3124
  if (this.buffer.length === 0) return this.inflight;
3062
3125
  const batch = this.buffer;
3063
3126
  this.buffer = [];
3064
- const body = JSON.stringify({
3127
+ const resource = { "service.name": this.o.serviceName, "service.version": this.o.serviceVersion, ...this.o.resource };
3128
+ const protobuf = (this.o.protocol ?? "http/protobuf") === "http/protobuf";
3129
+ const body = protobuf ? this.protobuf(resource, batch) : JSON.stringify({
3065
3130
  resourceLogs: [
3066
3131
  {
3067
- resource: { attributes: attrs({ "service.name": this.o.serviceName, "service.version": this.o.serviceVersion, ...this.o.resource }) },
3132
+ resource: { attributes: attrs(resource) },
3068
3133
  scopeLogs: [
3069
3134
  {
3070
3135
  scope: { name: this.o.serviceName },
@@ -3084,13 +3149,24 @@ var OtlpLogExporter = class {
3084
3149
  const url = `${this.o.endpoint.replace(/\/+$/, "")}/v1/logs`;
3085
3150
  const send = this.o.send ?? fetchSend;
3086
3151
  this.inflight = this.inflight.then(
3087
- () => send(url, body, this.o.headers ?? {}).then(
3152
+ () => send(url, body, { "Content-Type": protobuf ? "application/x-protobuf" : "application/json", ...this.o.headers ?? {} }).then(
3088
3153
  () => void (this.sent += batch.length),
3089
3154
  () => void (this.dropped += batch.length)
3090
3155
  )
3091
3156
  );
3092
3157
  return this.inflight;
3093
3158
  }
3159
+ protobuf(resource, batch) {
3160
+ const scope = new Pb().message(1, new Pb().string(1, this.o.serviceName));
3161
+ for (const r of batch) {
3162
+ const t = BigInt(r.timeUnixNano);
3163
+ const rec = new Pb().fixed64(1, t).uint(2, SEVERITY_NUMBER[r.severity]).string(3, r.severity).message(5, new Pb().string(1, r.body));
3164
+ pbAttrs(rec, 6, r.attributes).fixed64(11, t);
3165
+ scope.message(2, rec);
3166
+ }
3167
+ const resourceLogs = new Pb().message(1, pbAttrs(new Pb(), 1, resource)).message(2, scope);
3168
+ return new Pb().message(1, resourceLogs).finish();
3169
+ }
3094
3170
  /** Stop the timer and send what is left. */
3095
3171
  async close() {
3096
3172
  if (this.timer) clearInterval(this.timer);
@@ -3114,18 +3190,29 @@ function parseOtlpHeaders(text) {
3114
3190
  }
3115
3191
  return out;
3116
3192
  }
3193
+ var parseResourceAttributes = parseOtlpHeaders;
3194
+ var truthy = (v) => (v ?? "").trim().toLowerCase() === "true";
3117
3195
  function otlpFromEnv(env, defaults) {
3196
+ if (truthy(env.OTEL_SDK_DISABLED) || (env.OTEL_LOGS_EXPORTER ?? "").trim().toLowerCase() === "none") return null;
3118
3197
  const logs = env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT?.trim();
3119
3198
  const base = env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim();
3120
3199
  const endpoint = logs ? logs.replace(/\/v1\/logs\/?$/, "") : base;
3121
3200
  if (!endpoint) return null;
3201
+ const protocol = otlpProtocol(env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? env.OTEL_EXPORTER_OTLP_PROTOCOL);
3202
+ const resource = parseResourceAttributes(env.OTEL_RESOURCE_ATTRIBUTES);
3122
3203
  return {
3123
3204
  endpoint,
3124
- serviceName: env.OTEL_SERVICE_NAME?.trim() || defaults.serviceName,
3205
+ serviceName: env.OTEL_SERVICE_NAME?.trim() || resource["service.name"] || defaults.serviceName,
3125
3206
  ...defaults.serviceVersion ? { serviceVersion: defaults.serviceVersion } : {},
3207
+ ...protocol ? { protocol } : {},
3208
+ ...Object.keys(resource).length ? { resource } : {},
3126
3209
  headers: { ...parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS), ...parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_LOGS_HEADERS) }
3127
3210
  };
3128
3211
  }
3212
+ function otlpProtocol(v) {
3213
+ const p = (v ?? "").trim().toLowerCase();
3214
+ return p === "http/protobuf" || p === "http/json" ? p : void 0;
3215
+ }
3129
3216
 
3130
3217
  // src/api/client.ts
3131
3218
  var ACTOR_HEADER = "X-Datanotes-Actor";
@@ -1,20 +1,24 @@
1
1
  /**
2
- * OTLP/HTTP log export for programs around the engine (MCP servers, apps): records are buffered
3
- * and POSTed as OTLP JSON to `<endpoint>/v1/logs`, the format SigNoz and any OpenTelemetry
4
- * collector read. No dependency, no SDK. Export failures are counted, never thrown: logging must
5
- * not break the program.
2
+ * OpenTelemetry log export over OTLP/HTTP, vendor-neutral: records are buffered and POSTed to
3
+ * `<endpoint>/v1/logs` as `http/protobuf` (the OTLP default) or `http/json`, which every
4
+ * OpenTelemetry collector and OTLP backend reads. No dependency, no SDK. Export failures are
5
+ * counted, never thrown: logging must not break the program. (OTLP over gRPC is not supported:
6
+ * use the collector's HTTP port, 4318 by default.)
6
7
  *
7
- * Configure it in code, or from the standard environment variables with `otlpFromEnv`.
8
+ * Configure it in code, or from the standard OpenTelemetry variables with `otlpFromEnv`.
8
9
  */
9
10
  export type Severity = "DEBUG" | "INFO" | "WARN" | "ERROR";
10
11
  export type AttrValue = string | number | boolean | null | undefined;
12
+ export type OtlpProtocol = "http/protobuf" | "http/json";
11
13
  export interface OtlpOptions {
12
14
  /** Collector base URL, e.g. `http://127.0.0.1:4318` (`/v1/logs` is added). */
13
15
  endpoint: string;
14
16
  /** `service.name` of the records. */
15
17
  serviceName: string;
16
18
  serviceVersion?: string;
17
- /** Extra headers, e.g. `{"signoz-ingestion-key": "…"}`. */
19
+ /** `http/protobuf` (default) or `http/json`. */
20
+ protocol?: OtlpProtocol;
21
+ /** Extra headers, e.g. an API key or `Authorization`, as the backend asks. */
18
22
  headers?: Record<string, string>;
19
23
  /** More resource attributes (e.g. `host.name`). */
20
24
  resource?: Record<string, AttrValue>;
@@ -22,8 +26,8 @@ export interface OtlpOptions {
22
26
  maxBatch?: number;
23
27
  /** Send the buffer every this many ms (default 5000; 0: only on `flush`). */
24
28
  flushMs?: number;
25
- /** POST a body; resolves on 2xx (default: fetch). */
26
- send?: (url: string, body: string, headers: Record<string, string>) => Promise<void>;
29
+ /** POST a body; resolves on 2xx (default: fetch). `headers` include the Content-Type. */
30
+ send?: (url: string, body: string | Uint8Array, headers: Record<string, string>) => Promise<void>;
27
31
  now?: () => number;
28
32
  }
29
33
  export declare class OtlpLogExporter {
@@ -38,17 +42,24 @@ export declare class OtlpLogExporter {
38
42
  log(severity: Severity, body: string, attributes?: Record<string, AttrValue>): void;
39
43
  /** Send what is buffered. Never rejects. */
40
44
  flush(): Promise<void>;
45
+ private protobuf;
41
46
  /** Stop the timer and send what is left. */
42
47
  close(): Promise<void>;
43
48
  }
44
49
  /** `k1=v1,k2=v2` (values URL-encoded), the format of `OTEL_EXPORTER_OTLP_HEADERS`. */
45
50
  export declare function parseOtlpHeaders(text: string | undefined): Record<string, string>;
51
+ /** `k1=v1,k2=v2` resource attributes, the format of `OTEL_RESOURCE_ATTRIBUTES`. */
52
+ export declare const parseResourceAttributes: typeof parseOtlpHeaders;
46
53
  /**
47
- * Exporter options from the standard OpenTelemetry variables, or null when no endpoint is set:
48
- * `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` (full URL of /v1/logs) or `OTEL_EXPORTER_OTLP_ENDPOINT`,
49
- * `OTEL_EXPORTER_OTLP_HEADERS` / `OTEL_EXPORTER_OTLP_LOGS_HEADERS`, `OTEL_SERVICE_NAME`.
54
+ * Exporter options from the standard OpenTelemetry variables, or null when export is off (no
55
+ * endpoint, `OTEL_SDK_DISABLED=true` or `OTEL_LOGS_EXPORTER=none`):
56
+ * `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` (full URL of /v1/logs) or `OTEL_EXPORTER_OTLP_ENDPOINT`;
57
+ * `OTEL_EXPORTER_OTLP_[LOGS_]HEADERS`; `OTEL_EXPORTER_OTLP_[LOGS_]PROTOCOL` (`http/protobuf`,
58
+ * `http/json`); `OTEL_SERVICE_NAME`; `OTEL_RESOURCE_ATTRIBUTES`.
50
59
  */
51
60
  export declare function otlpFromEnv(env: Record<string, string | undefined>, defaults: {
52
61
  serviceName: string;
53
62
  serviceVersion?: string;
54
63
  }): OtlpOptions | null;
64
+ /** `http/protobuf` or `http/json` from a setting or variable; undefined for anything else. */
65
+ export declare function otlpProtocol(v: string | undefined): OtlpProtocol | undefined;
@@ -7426,6 +7426,7 @@ var DEFAULT_SETTINGS = {
7426
7426
  telemetryEndpoint: "http://127.0.0.1:4318",
7427
7427
  telemetryServiceName: "datanotes",
7428
7428
  telemetryHeaders: {},
7429
+ telemetryProtocol: "http/protobuf",
7429
7430
  autoRecompute: true,
7430
7431
  viewNotes: true,
7431
7432
  semanticEnabled: false,
@@ -7452,6 +7453,7 @@ var KEYS = [
7452
7453
  "telemetryEndpoint",
7453
7454
  "telemetryServiceName",
7454
7455
  "telemetryHeaders",
7456
+ "telemetryProtocol",
7455
7457
  "autoRecompute",
7456
7458
  "viewNotes",
7457
7459
  "semanticEnabled",
@@ -7502,6 +7504,10 @@ function mergeWithDefaults(loaded) {
7502
7504
  }
7503
7505
  break;
7504
7506
  }
7507
+ case "telemetryProtocol": {
7508
+ if (raw === "http/protobuf" || raw === "http/json") out.telemetryProtocol = raw;
7509
+ break;
7510
+ }
7505
7511
  case "telemetryHeaders": {
7506
7512
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
7507
7513
  out.telemetryHeaders = Object.fromEntries(
@@ -7644,89 +7650,12 @@ async function storeRename(app, from, to) {
7644
7650
  }
7645
7651
 
7646
7652
  // src/core/telemetry.ts
7647
- var SEVERITY_NUMBER = { DEBUG: 5, INFO: 9, WARN: 13, ERROR: 17 };
7648
7653
  var NOOP_TELEMETRY = {
7649
7654
  log: () => {
7650
7655
  },
7651
7656
  flush: async () => {
7652
7657
  }
7653
7658
  };
7654
- function otlpAttr(key, v) {
7655
- if (v === void 0 || v === null) return null;
7656
- if (typeof v === "boolean") return { key, value: { boolValue: v } };
7657
- if (typeof v === "number") {
7658
- return Number.isInteger(v) ? { key, value: { intValue: String(v) } } : { key, value: { doubleValue: v } };
7659
- }
7660
- return { key, value: { stringValue: v } };
7661
- }
7662
- function attrs(a) {
7663
- return Object.entries(a).map(([k, v]) => otlpAttr(k, v)).filter((x) => x != null);
7664
- }
7665
- function otlpLogsPayload(serviceName, records, resource = {}) {
7666
- return JSON.stringify({
7667
- resourceLogs: [
7668
- {
7669
- resource: { attributes: attrs({ "service.name": serviceName, ...resource }) },
7670
- scopeLogs: [
7671
- {
7672
- scope: { name: "datanotes" },
7673
- logRecords: records.map((r) => ({
7674
- timeUnixNano: r.timeUnixNano,
7675
- observedTimeUnixNano: r.timeUnixNano,
7676
- severityNumber: SEVERITY_NUMBER[r.severity],
7677
- severityText: r.severity,
7678
- body: { stringValue: r.body },
7679
- attributes: attrs(r.attributes)
7680
- }))
7681
- }
7682
- ]
7683
- }
7684
- ]
7685
- });
7686
- }
7687
- var OtlpLogger = class {
7688
- constructor(opts) {
7689
- this.opts = opts;
7690
- this.buffer = [];
7691
- this.inflight = Promise.resolve();
7692
- this.dropped = 0;
7693
- this.sent = 0;
7694
- }
7695
- log(severity, body, attributes = {}) {
7696
- if (!this.opts.getSettings().telemetryEnabled) return;
7697
- const ms = (this.opts.now ?? Date.now)();
7698
- this.buffer.push({
7699
- timeUnixNano: `${Math.round(ms)}000000`,
7700
- severity,
7701
- body,
7702
- attributes
7703
- });
7704
- if (this.buffer.length >= (this.opts.maxBatch ?? 50)) void this.flush();
7705
- }
7706
- /** Send buffered records. Never rejects. */
7707
- flush() {
7708
- const settings = this.opts.getSettings();
7709
- if (!settings.telemetryEnabled || this.buffer.length === 0) {
7710
- this.buffer = [];
7711
- return this.inflight;
7712
- }
7713
- const batch = this.buffer;
7714
- this.buffer = [];
7715
- const url = `${settings.telemetryEndpoint.replace(/\/+$/, "")}/v1/logs`;
7716
- const body = otlpLogsPayload(settings.telemetryServiceName, batch, this.opts.resource);
7717
- this.inflight = this.inflight.then(
7718
- () => this.opts.transport(url, body, { ...settings.telemetryHeaders ?? {} }).then(
7719
- () => {
7720
- this.sent += batch.length;
7721
- },
7722
- () => {
7723
- this.dropped += batch.length;
7724
- }
7725
- )
7726
- );
7727
- return this.inflight;
7728
- }
7729
- };
7730
7659
  var current = NOOP_TELEMETRY;
7731
7660
  function setTelemetry(t) {
7732
7661
  current = t;
@@ -7850,24 +7779,6 @@ var LockHolder = class {
7850
7779
  }
7851
7780
  };
7852
7781
 
7853
- // src/api/otlp.ts
7854
- function parseOtlpHeaders(text2) {
7855
- const out = {};
7856
- for (const part of (text2 ?? "").split(",")) {
7857
- const i = part.indexOf("=");
7858
- if (i <= 0) continue;
7859
- const key = part.slice(0, i).trim();
7860
- const value = part.slice(i + 1).trim();
7861
- if (!key) continue;
7862
- try {
7863
- out[key] = decodeURIComponent(value);
7864
- } catch {
7865
- out[key] = value;
7866
- }
7867
- }
7868
- return out;
7869
- }
7870
-
7871
7782
  // src/core/filename-values.ts
7872
7783
  var PLACEHOLDER = /\{([A-Za-z][A-Za-z0-9_-]*)(?::([^}]+))?\}/g;
7873
7784
  var FORMAT_TOKEN = /\[([^\]]*)\]|YYYY|MM|DD|HH|mm|ss/g;
@@ -15278,6 +15189,246 @@ var SemanticIndex = class {
15278
15189
  }
15279
15190
  };
15280
15191
 
15192
+ // src/api/otlp.ts
15193
+ var SEVERITY_NUMBER = { DEBUG: 5, INFO: 9, WARN: 13, ERROR: 17 };
15194
+ function attr(key, v) {
15195
+ if (v === void 0 || v === null) return null;
15196
+ if (typeof v === "boolean") return { key, value: { boolValue: v } };
15197
+ if (typeof v === "number") return Number.isInteger(v) ? { key, value: { intValue: String(v) } } : { key, value: { doubleValue: v } };
15198
+ return { key, value: { stringValue: v } };
15199
+ }
15200
+ var attrs = (a) => Object.entries(a).map(([k, v]) => attr(k, v)).filter((x) => x !== null);
15201
+ var fetchSend = async (url, body, headers) => {
15202
+ const res = await fetch(url, { method: "POST", headers, body });
15203
+ if (!res.ok) throw new Error(`OTLP HTTP ${res.status}`);
15204
+ };
15205
+ var Pb = class {
15206
+ constructor() {
15207
+ this.parts = [];
15208
+ }
15209
+ varint(v) {
15210
+ let x = BigInt.asUintN(64, v);
15211
+ const b128 = BigInt(128);
15212
+ const low7 = BigInt(127);
15213
+ const seven = BigInt(7);
15214
+ while (x >= b128) {
15215
+ this.parts.push(Number(x & low7) | 128);
15216
+ x >>= seven;
15217
+ }
15218
+ this.parts.push(Number(x));
15219
+ }
15220
+ tag(field, wire) {
15221
+ this.varint(BigInt(field << 3 | wire));
15222
+ }
15223
+ uint(field, v) {
15224
+ this.tag(field, 0);
15225
+ this.varint(BigInt(v));
15226
+ return this;
15227
+ }
15228
+ fixed64(field, v) {
15229
+ this.tag(field, 1);
15230
+ for (let i = 0; i < 8; i++) this.parts.push(Number(v >> BigInt(8 * i) & BigInt(255)));
15231
+ return this;
15232
+ }
15233
+ double(field, v) {
15234
+ this.tag(field, 1);
15235
+ const b = new DataView(new ArrayBuffer(8));
15236
+ b.setFloat64(0, v, true);
15237
+ for (let i = 0; i < 8; i++) this.parts.push(b.getUint8(i));
15238
+ return this;
15239
+ }
15240
+ bytes(field, data) {
15241
+ this.tag(field, 2);
15242
+ this.varint(BigInt(data.length));
15243
+ for (const x of data) this.parts.push(x);
15244
+ return this;
15245
+ }
15246
+ string(field, s) {
15247
+ return this.bytes(field, new TextEncoder().encode(s));
15248
+ }
15249
+ message(field, m) {
15250
+ return this.bytes(field, m.finish());
15251
+ }
15252
+ finish() {
15253
+ return Uint8Array.from(this.parts);
15254
+ }
15255
+ };
15256
+ function pbValue(v) {
15257
+ if (typeof v === "string") return new Pb().string(1, v);
15258
+ if (typeof v === "boolean") return new Pb().uint(2, v ? 1 : 0);
15259
+ return Number.isInteger(v) ? new Pb().uint(3, BigInt(v)) : new Pb().double(4, v);
15260
+ }
15261
+ function pbAttrs(m, field, a) {
15262
+ for (const [k, v] of Object.entries(a)) {
15263
+ if (v === void 0 || v === null) continue;
15264
+ m.message(field, new Pb().string(1, k).message(2, pbValue(v)));
15265
+ }
15266
+ return m;
15267
+ }
15268
+ var OtlpLogExporter = class {
15269
+ constructor(o) {
15270
+ this.o = o;
15271
+ this.buffer = [];
15272
+ this.inflight = Promise.resolve();
15273
+ this.timer = null;
15274
+ /** Records sent, and records lost because the collector did not take them. */
15275
+ this.sent = 0;
15276
+ this.dropped = 0;
15277
+ const every = o.flushMs ?? 5e3;
15278
+ if (every > 0) {
15279
+ this.timer = setInterval(() => void this.flush(), every);
15280
+ this.timer.unref?.();
15281
+ }
15282
+ }
15283
+ log(severity, body, attributes = {}) {
15284
+ const ms = (this.o.now ?? Date.now)();
15285
+ this.buffer.push({ timeUnixNano: `${Math.round(ms)}000000`, severity, body, attributes });
15286
+ if (this.buffer.length >= (this.o.maxBatch ?? 50)) void this.flush();
15287
+ }
15288
+ /** Send what is buffered. Never rejects. */
15289
+ flush() {
15290
+ if (this.buffer.length === 0) return this.inflight;
15291
+ const batch = this.buffer;
15292
+ this.buffer = [];
15293
+ const resource = { "service.name": this.o.serviceName, "service.version": this.o.serviceVersion, ...this.o.resource };
15294
+ const protobuf = (this.o.protocol ?? "http/protobuf") === "http/protobuf";
15295
+ const body = protobuf ? this.protobuf(resource, batch) : JSON.stringify({
15296
+ resourceLogs: [
15297
+ {
15298
+ resource: { attributes: attrs(resource) },
15299
+ scopeLogs: [
15300
+ {
15301
+ scope: { name: this.o.serviceName },
15302
+ logRecords: batch.map((r) => ({
15303
+ timeUnixNano: r.timeUnixNano,
15304
+ observedTimeUnixNano: r.timeUnixNano,
15305
+ severityNumber: SEVERITY_NUMBER[r.severity],
15306
+ severityText: r.severity,
15307
+ body: { stringValue: r.body },
15308
+ attributes: attrs(r.attributes)
15309
+ }))
15310
+ }
15311
+ ]
15312
+ }
15313
+ ]
15314
+ });
15315
+ const url = `${this.o.endpoint.replace(/\/+$/, "")}/v1/logs`;
15316
+ const send2 = this.o.send ?? fetchSend;
15317
+ this.inflight = this.inflight.then(
15318
+ () => send2(url, body, { "Content-Type": protobuf ? "application/x-protobuf" : "application/json", ...this.o.headers ?? {} }).then(
15319
+ () => void (this.sent += batch.length),
15320
+ () => void (this.dropped += batch.length)
15321
+ )
15322
+ );
15323
+ return this.inflight;
15324
+ }
15325
+ protobuf(resource, batch) {
15326
+ const scope = new Pb().message(1, new Pb().string(1, this.o.serviceName));
15327
+ for (const r of batch) {
15328
+ const t = BigInt(r.timeUnixNano);
15329
+ const rec = new Pb().fixed64(1, t).uint(2, SEVERITY_NUMBER[r.severity]).string(3, r.severity).message(5, new Pb().string(1, r.body));
15330
+ pbAttrs(rec, 6, r.attributes).fixed64(11, t);
15331
+ scope.message(2, rec);
15332
+ }
15333
+ const resourceLogs = new Pb().message(1, pbAttrs(new Pb(), 1, resource)).message(2, scope);
15334
+ return new Pb().message(1, resourceLogs).finish();
15335
+ }
15336
+ /** Stop the timer and send what is left. */
15337
+ async close() {
15338
+ if (this.timer) clearInterval(this.timer);
15339
+ this.timer = null;
15340
+ await this.flush();
15341
+ }
15342
+ };
15343
+ function parseOtlpHeaders(text2) {
15344
+ const out = {};
15345
+ for (const part of (text2 ?? "").split(",")) {
15346
+ const i = part.indexOf("=");
15347
+ if (i <= 0) continue;
15348
+ const key = part.slice(0, i).trim();
15349
+ const value = part.slice(i + 1).trim();
15350
+ if (!key) continue;
15351
+ try {
15352
+ out[key] = decodeURIComponent(value);
15353
+ } catch {
15354
+ out[key] = value;
15355
+ }
15356
+ }
15357
+ return out;
15358
+ }
15359
+ var parseResourceAttributes = parseOtlpHeaders;
15360
+ var truthy = (v) => (v ?? "").trim().toLowerCase() === "true";
15361
+ function otlpFromEnv(env, defaults) {
15362
+ if (truthy(env.OTEL_SDK_DISABLED) || (env.OTEL_LOGS_EXPORTER ?? "").trim().toLowerCase() === "none") return null;
15363
+ const logs = env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT?.trim();
15364
+ const base = env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim();
15365
+ const endpoint = logs ? logs.replace(/\/v1\/logs\/?$/, "") : base;
15366
+ if (!endpoint) return null;
15367
+ const protocol = otlpProtocol(env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? env.OTEL_EXPORTER_OTLP_PROTOCOL);
15368
+ const resource = parseResourceAttributes(env.OTEL_RESOURCE_ATTRIBUTES);
15369
+ return {
15370
+ endpoint,
15371
+ serviceName: env.OTEL_SERVICE_NAME?.trim() || resource["service.name"] || defaults.serviceName,
15372
+ ...defaults.serviceVersion ? { serviceVersion: defaults.serviceVersion } : {},
15373
+ ...protocol ? { protocol } : {},
15374
+ ...Object.keys(resource).length ? { resource } : {},
15375
+ headers: { ...parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS), ...parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_LOGS_HEADERS) }
15376
+ };
15377
+ }
15378
+ function otlpProtocol(v) {
15379
+ const p = (v ?? "").trim().toLowerCase();
15380
+ return p === "http/protobuf" || p === "http/json" ? p : void 0;
15381
+ }
15382
+
15383
+ // src/server/otlp-sink.ts
15384
+ function resolveOtlp(s, env, base) {
15385
+ const fromEnv = otlpFromEnv(env, { serviceName: s.telemetryServiceName, serviceVersion: base.serviceVersion });
15386
+ const disabled = (env.OTEL_SDK_DISABLED ?? "").trim().toLowerCase() === "true" || (env.OTEL_LOGS_EXPORTER ?? "").trim().toLowerCase() === "none";
15387
+ if (disabled || !fromEnv && !s.telemetryEnabled) return null;
15388
+ const resource = { ...base.resource, ...parseResourceAttributes(env.OTEL_RESOURCE_ATTRIBUTES) };
15389
+ const envProtocol = otlpProtocol(env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? env.OTEL_EXPORTER_OTLP_PROTOCOL);
15390
+ return {
15391
+ endpoint: fromEnv?.endpoint ?? s.telemetryEndpoint,
15392
+ serviceName: env.OTEL_SERVICE_NAME?.trim() || s.telemetryServiceName,
15393
+ serviceVersion: base.serviceVersion,
15394
+ protocol: envProtocol ?? s.telemetryProtocol ?? "http/protobuf",
15395
+ headers: { ...s.telemetryHeaders ?? {}, ...fromEnv?.headers ?? {} },
15396
+ resource
15397
+ };
15398
+ }
15399
+ var OtlpSink = class {
15400
+ constructor(options, send2) {
15401
+ this.options = options;
15402
+ this.send = send2;
15403
+ this.exporter = null;
15404
+ this.key = "";
15405
+ }
15406
+ /** The exporter for the current options (a new one when they changed), or null. */
15407
+ current() {
15408
+ const o = this.options();
15409
+ const key = o ? JSON.stringify(o) : "";
15410
+ if (key !== this.key) {
15411
+ const old = this.exporter;
15412
+ void old?.close();
15413
+ this.exporter = o ? new OtlpLogExporter({ ...o, flushMs: 0, ...this.send ? { send: this.send } : {} }) : null;
15414
+ this.key = key;
15415
+ }
15416
+ return this.exporter;
15417
+ }
15418
+ log(severity, body, attributes) {
15419
+ this.current()?.log(severity, body, attributes);
15420
+ }
15421
+ async flush() {
15422
+ await this.current()?.flush();
15423
+ }
15424
+ get sent() {
15425
+ return this.exporter?.sent ?? 0;
15426
+ }
15427
+ get dropped() {
15428
+ return this.exporter?.dropped ?? 0;
15429
+ }
15430
+ };
15431
+
15281
15432
  // src/core/view-block.ts
15282
15433
  var import_yaml3 = __toESM(require_dist(), 1);
15283
15434
  var BLOCK_KEYS = /* @__PURE__ */ new Set(["view", "table", "columns", "where", "order_by", "limit", "join", "group_by", "aggregates", "expand", "labels", "row_link", "chart", "with_table"]);
@@ -20785,14 +20936,10 @@ var EngineRuntime = class {
20785
20936
  session: call.session ?? ""
20786
20937
  });
20787
20938
  }
20788
- /** Logs to the OTLP collector (when enabled) and to `.datanotes/logs/`. */
20939
+ /** Logs to an OpenTelemetry collector (when configured) and to `.datanotes/logs/`. */
20789
20940
  startTelemetry() {
20790
20941
  const s = this.o.getSettings;
20791
- const logger = new OtlpLogger({
20792
- getSettings: s,
20793
- transport: async (url, body, headers) => this.o.postLogs(url, body, headers),
20794
- resource: { "service.version": this.o.version, "host.name": this.o.hostName }
20795
- });
20942
+ const logger = new OtlpSink(() => resolveOtlp(s(), this.o.env ?? {}, { serviceVersion: this.o.version, resource: { "service.instance.id": this.o.hostName } }), this.o.otlpSend);
20796
20943
  const source = this.o.hostName;
20797
20944
  const fileLog = new FileLogger({ enabled: () => s().fileLogEnabled, write: (day, lines) => appendLog(this.o.app, s(), day, lines, source) });
20798
20945
  const tee = new TeeTelemetry([logger, fileLog]);
@@ -22097,7 +22244,7 @@ var NodeWatcher = class {
22097
22244
  };
22098
22245
 
22099
22246
  // src/node/service.ts
22100
- var VERSION = true ? "1.3.0" : "dev";
22247
+ var VERSION = true ? "1.4.0" : "dev";
22101
22248
  function parseArgs(argv, env = process.env) {
22102
22249
  const get = (name) => {
22103
22250
  const i = argv.indexOf(`--${name}`);
@@ -22165,11 +22312,7 @@ async function startService(o, env = process.env) {
22165
22312
  const res = await fetch(req.url, { method: req.method, headers: req.headers, body: req.body });
22166
22313
  return { status: res.status, text: await res.text() };
22167
22314
  },
22168
- postLogs: async (url, body, headers) => {
22169
- const all = { ...headers, ...parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS) };
22170
- const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", ...all }, body });
22171
- if (!res.ok) throw new Error(`OTLP HTTP ${res.status}`);
22172
- },
22315
+ env,
22173
22316
  embeddingKey: () => env.DATANOTES_EMBEDDING_KEY || keyFromFile(),
22174
22317
  version: VERSION,
22175
22318
  hostName: "service",
@@ -22276,7 +22419,7 @@ Docs: https://datanotes.dev`;
22276
22419
  }
22277
22420
 
22278
22421
  // src/node/cli.ts
22279
- var VERSION2 = true ? "1.3.0" : "dev";
22422
+ var VERSION2 = true ? "1.4.0" : "dev";
22280
22423
  async function main2(argv = process.argv.slice(2)) {
22281
22424
  const [command, ...rest] = argv;
22282
22425
  if (command === "--version" || command === "-v" || command === "version") return void process.stdout.write(`${VERSION2}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datanotes",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "The engine of your second brain: Markdown notes as database tables, for you and your agents.",
5
5
  "keywords": [
6
6
  "markdown",