@datalayer/core 1.0.15 → 1.0.18

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.
@@ -12,11 +12,8 @@ export interface HealthzPingResponse {
12
12
  success: boolean;
13
13
  /** Response message from the server */
14
14
  message: string;
15
- /** Service status information */
16
- status?: {
17
- /** Status indicator (e.g., "OK") */
18
- status: string;
19
- };
15
+ /** Service status indicator (e.g., "OK") */
16
+ status?: string;
20
17
  /** API version */
21
18
  version?: string;
22
19
  }
@@ -74,17 +74,17 @@ export interface UserMeResponse {
74
74
  /** Unique identifier (UID) for the user */
75
75
  uid: string;
76
76
  /** User handle (username) */
77
- handle: string;
77
+ handle_s: string;
78
78
  /** Email address of the user (may be empty string) */
79
- email: string;
79
+ email_s: string;
80
80
  /** First name of the user (may be empty string) */
81
- firstName: string;
81
+ first_name_t: string;
82
82
  /** Last name of the user (may be empty string) */
83
- lastName: string;
83
+ last_name_t: string;
84
84
  /** Avatar URL for the user (may be empty string) */
85
- avatarUrl: string;
85
+ avatar_url_s: string;
86
86
  /** Array of roles assigned to the user */
87
- roles: string[];
87
+ roles_ss: string[];
88
88
  /** Allow additional fields that may come from the server */
89
89
  [key: string]: any;
90
90
  };
@@ -0,0 +1,11 @@
1
+ export interface ResolvedOtelAuth {
2
+ token: string;
3
+ userUid: string;
4
+ }
5
+ export declare function resolveOtelUserUid(token?: string, userUid?: string): string | null;
6
+ export declare function resolveOtelAuth(token?: string, userUid?: string): ResolvedOtelAuth;
7
+ export declare function buildOtelWebSocketUrl(options: {
8
+ baseUrl: string;
9
+ token: string;
10
+ userUid?: string;
11
+ }): string;
@@ -0,0 +1,69 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /*
6
+ * Copyright (c) 2023-2026 Datalayer, Inc.
7
+ * Distributed under the terms of the Modified BSD License.
8
+ */
9
+ import { getDatalayerJwtUser, parseJwtPayload } from '../utils/Jwt.js';
10
+ function normalizeBaseUrl(baseUrl) {
11
+ const normalized = baseUrl.startsWith('ws://') || baseUrl.startsWith('wss://')
12
+ ? baseUrl.replace(/^ws/, 'http')
13
+ : baseUrl;
14
+ const isAbsoluteUrl = /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(normalized);
15
+ if (isAbsoluteUrl) {
16
+ return new URL(normalized);
17
+ }
18
+ if (typeof window !== 'undefined' &&
19
+ typeof window.location !== 'undefined' &&
20
+ typeof window.location.origin === 'string') {
21
+ return new URL(normalized || '/', window.location.origin);
22
+ }
23
+ throw new Error('OTEL baseUrl must be an absolute URL when window.location is unavailable.');
24
+ }
25
+ export function resolveOtelUserUid(token, userUid) {
26
+ if (typeof userUid === 'string' && userUid.trim().length > 0) {
27
+ return userUid.trim();
28
+ }
29
+ if (!token) {
30
+ return null;
31
+ }
32
+ const user = getDatalayerJwtUser(token);
33
+ if (user?.uid) {
34
+ return user.uid;
35
+ }
36
+ const payload = parseJwtPayload(token);
37
+ const sub = payload?.sub;
38
+ if (typeof sub === 'string' && sub.trim().length > 0) {
39
+ return sub.trim();
40
+ }
41
+ return null;
42
+ }
43
+ export function resolveOtelAuth(token, userUid) {
44
+ if (!token || token.trim().length === 0) {
45
+ throw new Error('OTEL requires an authenticated token.');
46
+ }
47
+ const trimmedToken = token.trim();
48
+ const resolvedUserUid = resolveOtelUserUid(trimmedToken, userUid);
49
+ if (!resolvedUserUid) {
50
+ throw new Error('OTEL requires datalayer.user_uid. Provide a JWT with user.uid/sub or pass userUid explicitly.');
51
+ }
52
+ return {
53
+ token: trimmedToken,
54
+ userUid: resolvedUserUid,
55
+ };
56
+ }
57
+ export function buildOtelWebSocketUrl(options) {
58
+ const auth = resolveOtelAuth(options.token, options.userUid);
59
+ const url = normalizeBaseUrl(options.baseUrl);
60
+ const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
61
+ const pathname = url.pathname.replace(/\/$/, '');
62
+ const hasApiPrefix = pathname.endsWith('/api/otel/v1') || pathname.includes('/api/otel/v1/');
63
+ url.protocol = wsProtocol;
64
+ url.pathname = hasApiPrefix ? `${pathname}/ws` : '/api/otel/v1/ws';
65
+ url.search = '';
66
+ url.searchParams.set('token', auth.token);
67
+ url.searchParams.set('user_uid', auth.userUid);
68
+ return url.toString();
69
+ }
@@ -8,7 +8,9 @@ export interface OtelClientOptions {
8
8
  */
9
9
  baseUrl?: string;
10
10
  /** JWT bearer token or API key for authentication. */
11
- token?: string;
11
+ token: string;
12
+ /** Explicit user UID; optional when token contains user.uid/sub. */
13
+ userUid?: string;
12
14
  }
13
15
  export interface FetchTracesOptions {
14
16
  serviceName?: string;
@@ -37,8 +39,9 @@ export interface FetchMetricTotalOptions {
37
39
  */
38
40
  export declare class OtelClient {
39
41
  private readonly baseUrl;
40
- private readonly token?;
41
- constructor(options?: OtelClientOptions);
42
+ private readonly token;
43
+ private readonly userUid;
44
+ constructor(options: OtelClientOptions);
42
45
  /** List recent spans / traces. */
43
46
  fetchTraces(options?: FetchTracesOptions): Promise<{
44
47
  data: OtelSpan[];
@@ -89,5 +92,5 @@ export declare class OtelClient {
89
92
  * const services = await client.fetchServices();
90
93
  * ```
91
94
  */
92
- export declare function createOtelClient(options?: OtelClientOptions): OtelClient;
95
+ export declare function createOtelClient(options: OtelClientOptions): OtelClient;
93
96
  export type { OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable, };
@@ -17,15 +17,16 @@
17
17
  * ```
18
18
  */
19
19
  import { coreStore } from '../../state/substates/CoreState.js';
20
+ import { resolveOtelAuth } from '../auth.js';
20
21
  // ── Fetch helper ────────────────────────────────────────────────────────────
21
- async function otelFetch(url, token, options) {
22
+ async function otelFetch(url, token, userUid, options) {
23
+ const auth = resolveOtelAuth(token, userUid);
22
24
  const headers = {
23
25
  Accept: 'application/json',
24
26
  'Cache-Control': 'no-store, no-cache, must-revalidate',
25
27
  };
26
- if (token) {
27
- headers['Authorization'] = `Bearer ${token}`;
28
- }
28
+ headers['Authorization'] = `Bearer ${auth.token}`;
29
+ headers['X-Datalayer-User-Uid'] = auth.userUid;
29
30
  if (options?.body) {
30
31
  headers['Content-Type'] = 'application/json';
31
32
  }
@@ -76,6 +77,18 @@ function parseMetricTimestamp(raw) {
76
77
  return new Date(numeric).toISOString();
77
78
  }
78
79
  function normalizeMetric(raw) {
80
+ // Preserve the raw nanosecond timestamp for time-series charts.
81
+ let timestampUnixNano;
82
+ const rawNano = raw.timestamp_unix_nano;
83
+ if (typeof rawNano === 'number' && Number.isFinite(rawNano) && rawNano > 0) {
84
+ timestampUnixNano = String(Math.trunc(rawNano));
85
+ }
86
+ else if (typeof rawNano === 'string') {
87
+ const trimmed = rawNano.trim();
88
+ if (/^\d+$/.test(trimmed) && trimmed !== '0') {
89
+ timestampUnixNano = trimmed;
90
+ }
91
+ }
79
92
  return {
80
93
  metric_name: String(raw.metric_name ?? raw.MetricName ?? raw.name ?? raw.Name ?? ''),
81
94
  service_name: String(raw.service_name ?? raw.ServiceName ?? ''),
@@ -90,6 +103,7 @@ function normalizeMetric(raw) {
90
103
  ? String(raw.Unit)
91
104
  : undefined,
92
105
  timestamp: parseMetricTimestamp(raw),
106
+ timestamp_unix_nano: timestampUnixNano,
93
107
  metric_type: raw.metric_type != null
94
108
  ? String(raw.metric_type)
95
109
  : raw.MetricType != null
@@ -109,10 +123,13 @@ function normalizeMetric(raw) {
109
123
  export class OtelClient {
110
124
  baseUrl;
111
125
  token;
112
- constructor(options = {}) {
126
+ userUid;
127
+ constructor(options) {
113
128
  this.baseUrl =
114
129
  options.baseUrl ?? coreStore.getState().configuration.otelRunUrl;
115
- this.token = options.token;
130
+ const auth = resolveOtelAuth(options.token, options.userUid);
131
+ this.token = auth.token;
132
+ this.userUid = auth.userUid;
116
133
  }
117
134
  // ── Traces ────────────────────────────────────────────────────────────────
118
135
  /** List recent spans / traces. */
@@ -120,13 +137,13 @@ export class OtelClient {
120
137
  const params = new URLSearchParams({ limit: String(options.limit ?? 50) });
121
138
  if (options.serviceName)
122
139
  params.set('service_name', options.serviceName);
123
- const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/?${params}`, this.token);
140
+ const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/?${params}`, this.token, this.userUid);
124
141
  const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
125
142
  return { data: rows, count: rows.length };
126
143
  }
127
144
  /** Fetch all spans for a single trace by `traceId`. */
128
145
  async fetchTrace(traceId) {
129
- const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/${traceId}`, this.token);
146
+ const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/${traceId}`, this.token, this.userUid);
130
147
  const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
131
148
  return { data: rows };
132
149
  }
@@ -140,7 +157,7 @@ export class OtelClient {
140
157
  params.set('severity', options.severity);
141
158
  if (options.traceId)
142
159
  params.set('trace_id', options.traceId);
143
- const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/logs/?${params}`, this.token);
160
+ const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/logs/?${params}`, this.token, this.userUid);
144
161
  const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
145
162
  return { data: rows, count: rows.length };
146
163
  }
@@ -152,7 +169,7 @@ export class OtelClient {
152
169
  params.set('service_name', options.serviceName);
153
170
  if (options.metricName)
154
171
  params.set('metric_name', options.metricName);
155
- const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/metrics/?${params}`, this.token);
172
+ const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/metrics/?${params}`, this.token, this.userUid);
156
173
  const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
157
174
  const normalized = Array.isArray(rows)
158
175
  ? rows.map(row => normalizeMetric(row))
@@ -177,7 +194,7 @@ export class OtelClient {
177
194
  // ── Services ──────────────────────────────────────────────────────────────
178
195
  /** List known service names observed in traces. */
179
196
  async fetchServices() {
180
- const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/services/list`, this.token);
197
+ const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/services/list`, this.token, this.userUid);
181
198
  if (Array.isArray(resp))
182
199
  return resp;
183
200
  const byServices = resp.services;
@@ -192,7 +209,7 @@ export class OtelClient {
192
209
  // ── Stats ─────────────────────────────────────────────────────────────────
193
210
  /** Fetch storage / ingestion statistics. */
194
211
  async fetchStats() {
195
- return otelFetch(`${this.baseUrl}/api/otel/v1/stats/`, this.token);
212
+ return otelFetch(`${this.baseUrl}/api/otel/v1/stats/`, this.token, this.userUid);
196
213
  }
197
214
  // ── SQL query ─────────────────────────────────────────────────────────────
198
215
  /**
@@ -201,7 +218,7 @@ export class OtelClient {
201
218
  * Available tables: `spans`, `logs`, `metrics`.
202
219
  */
203
220
  async executeQuery(sql) {
204
- return otelFetch(`${this.baseUrl}/api/otel/v1/query/`, this.token, { method: 'POST', body: { sql } });
221
+ return otelFetch(`${this.baseUrl}/api/otel/v1/query/`, this.token, this.userUid, { method: 'POST', body: { sql } });
205
222
  }
206
223
  // ── System ────────────────────────────────────────────────────────────────
207
224
  /**
@@ -210,7 +227,7 @@ export class OtelClient {
210
227
  * Returns process memory/CPU, disk usage, and per-table row counts.
211
228
  */
212
229
  async fetchSystem() {
213
- const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/system/`, this.token);
230
+ const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/system/`, this.token, this.userUid);
214
231
  return (resp.data ?? resp);
215
232
  }
216
233
  }
@@ -227,6 +244,6 @@ export class OtelClient {
227
244
  * const services = await client.fetchServices();
228
245
  * ```
229
246
  */
230
- export function createOtelClient(options = {}) {
247
+ export function createOtelClient(options) {
231
248
  return new OtelClient(options);
232
249
  }
@@ -173,6 +173,8 @@ export declare function useOtelWebSocket(options: {
173
173
  baseUrl?: string;
174
174
  /** JWT token or API key for authentication. */
175
175
  token?: string;
176
+ /** Explicit user UID; optional when token contains user.uid/sub. */
177
+ userUid?: string;
176
178
  /** Whether to automatically reconnect on disconnect. Default true. */
177
179
  autoReconnect?: boolean;
178
180
  /** Reconnect delay in ms. Default 3000. */
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { useCallback, useEffect, useRef, useState } from 'react';
11
11
  import { coreStore } from '../../state/substates/CoreState.js';
12
+ import { buildOtelWebSocketUrl, resolveOtelAuth } from '../auth.js';
12
13
  // ── Global 401 handler ──────────────────────────────────────────────
13
14
  let _onUnauthorized = null;
14
15
  /**
@@ -35,14 +36,14 @@ export function setOtelOnUnauthorized(cb) {
35
36
  * handler has been registered via {@link setOtelOnUnauthorized}, the
36
37
  * handler is called before the error is thrown.
37
38
  */
38
- async function otelFetch(url, token, options) {
39
+ async function otelFetch(url, token, userUid, options) {
40
+ const auth = resolveOtelAuth(token, userUid);
39
41
  const headers = {
40
42
  Accept: 'application/json',
41
43
  'Cache-Control': 'no-store, no-cache, must-revalidate',
42
44
  };
43
- if (token) {
44
- headers['Authorization'] = `Bearer ${token}`;
45
- }
45
+ headers['Authorization'] = `Bearer ${auth.token}`;
46
+ headers['X-Datalayer-User-Uid'] = auth.userUid;
46
47
  const isPost = options?.method === 'POST';
47
48
  if (isPost) {
48
49
  headers['Content-Type'] = 'application/json';
@@ -407,7 +408,7 @@ export function useOtelQuery(options) {
407
408
  setLoading(true);
408
409
  setError(null);
409
410
  try {
410
- const data = await otelFetch(`${baseUrl}/api/otel/v1/query/`, token, {
411
+ const data = await otelFetch(`${baseUrl}/api/otel/v1/query/`, token, undefined, {
411
412
  method: 'POST',
412
413
  body: { sql },
413
414
  });
@@ -436,7 +437,7 @@ export function useOtelQuery(options) {
436
437
  * @returns `{ connected, error, close }` – reactive connection state.
437
438
  */
438
439
  export function useOtelWebSocket(options) {
439
- const { baseUrl = coreStore.getState().configuration.otelRunUrl, token, autoReconnect = true, reconnectDelayMs = 3000, callbacks, } = options;
440
+ const { baseUrl = coreStore.getState().configuration.otelRunUrl, token, userUid, autoReconnect = true, reconnectDelayMs = 3000, callbacks, } = options;
440
441
  const [connected, setConnected] = useState(false);
441
442
  const [error, setError] = useState(null);
442
443
  const wsRef = useRef(null);
@@ -459,23 +460,19 @@ export function useOtelWebSocket(options) {
459
460
  setConnected(false);
460
461
  return;
461
462
  }
462
- // Derive ws(s):// URL from http(s):// baseUrl.
463
463
  let wsUrl;
464
- if (baseUrl.startsWith('http://')) {
465
- wsUrl = `ws://${baseUrl.slice(7)}`;
466
- }
467
- else if (baseUrl.startsWith('https://')) {
468
- wsUrl = `wss://${baseUrl.slice(8)}`;
469
- }
470
- else if (baseUrl.startsWith('ws://') || baseUrl.startsWith('wss://')) {
471
- wsUrl = baseUrl;
464
+ try {
465
+ wsUrl = buildOtelWebSocketUrl({
466
+ baseUrl,
467
+ token,
468
+ userUid,
469
+ });
472
470
  }
473
- else {
474
- // Relative URL – use current page's protocol.
475
- const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
476
- wsUrl = `${proto}//${window.location.host}${baseUrl}`;
471
+ catch (err) {
472
+ setError(err?.message ?? 'WebSocket auth error');
473
+ setConnected(false);
474
+ return;
477
475
  }
478
- wsUrl = `${wsUrl.replace(/\/$/, '')}/api/otel/v1/ws?token=${encodeURIComponent(token)}`;
479
476
  function connect() {
480
477
  const ws = new WebSocket(wsUrl);
481
478
  wsRef.current = ws;
@@ -527,6 +524,6 @@ export function useOtelWebSocket(options) {
527
524
  wsRef.current = null;
528
525
  }
529
526
  };
530
- }, [baseUrl, token, autoReconnect, reconnectDelayMs]);
527
+ }, [baseUrl, token, userUid, autoReconnect, reconnectDelayMs]);
531
528
  return { connected, error, close };
532
529
  }
@@ -13,6 +13,8 @@
13
13
  */
14
14
  export type { OtelSpan, OtelLog, OtelMetric, OtelSpanEvent, OtelSpanLink, SignalType, OtelLiveProps, OtelTracesListProps, OtelSpanDetailProps, OtelTimelineProps, OtelLogsListProps, OtelMetricsListProps, OtelSpanTreeProps, OtelSearchBarProps, OtelTimelineRangeSliderProps, OtelQueryRow, OtelQueryResult, } from './types';
15
15
  export { toMs, formatDuration, formatTime, serviceColor, kindColor, severityColor, severityVariant, buildSpanTree, flattenSpanTree, } from './utils';
16
+ export { resolveOtelAuth, resolveOtelUserUid, buildOtelWebSocketUrl, } from './auth';
17
+ export type { ResolvedOtelAuth } from './auth';
16
18
  export { useOtelTraces, useOtelTrace, useOtelLogs, useOtelMetrics, useOtelServices, useOtelStats, useOtelQuery, useOtelSystem, useOtelWebSocket, setOtelOnUnauthorized, } from './hooks';
17
19
  export type { OtelWsMessage, OtelWsCallbacks, OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable, } from './hooks';
18
20
  export { OtelClient, createOtelClient } from './client';
package/lib/otel/index.js CHANGED
@@ -4,6 +4,8 @@
4
4
  */
5
5
  // ── Utils ─────────────────────────────────────────────────────────────────────
6
6
  export { toMs, formatDuration, formatTime, serviceColor, kindColor, severityColor, severityVariant, buildSpanTree, flattenSpanTree, } from './utils.js';
7
+ // ── Auth helpers ──────────────────────────────────────────────────────────────
8
+ export { resolveOtelAuth, resolveOtelUserUid, buildOtelWebSocketUrl, } from './auth.js';
7
9
  // ── Hooks ─────────────────────────────────────────────────────────────────────
8
10
  export { useOtelTraces, useOtelTrace, useOtelLogs, useOtelMetrics, useOtelServices, useOtelStats, useOtelQuery, useOtelSystem, useOtelWebSocket, setOtelOnUnauthorized, } from './hooks/index.js';
9
11
  // ── Client ────────────────────────────────────────────────────────────────────
@@ -55,6 +55,8 @@ export interface OtelMetric {
55
55
  value: number;
56
56
  unit?: string;
57
57
  timestamp: string;
58
+ /** Raw nanosecond timestamp preserved from the backend. */
59
+ timestamp_unix_nano?: string;
58
60
  attributes?: Record<string, unknown>;
59
61
  metric_type?: string;
60
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/core",
3
- "version": "1.0.15",
3
+ "version": "1.0.18",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",