@superblocksteam/sdk 2.0.139 → 2.0.140-next.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 (80) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/application-build.d.mts.map +1 -1
  3. package/dist/application-build.mjs +3 -0
  4. package/dist/application-build.mjs.map +1 -1
  5. package/dist/dev-utils/dev-server-resolve-config.test.d.mts +2 -0
  6. package/dist/dev-utils/dev-server-resolve-config.test.d.mts.map +1 -0
  7. package/dist/dev-utils/dev-server-resolve-config.test.mjs +20 -0
  8. package/dist/dev-utils/dev-server-resolve-config.test.mjs.map +1 -0
  9. package/dist/dev-utils/dev-server-tracing.test.d.mts +2 -0
  10. package/dist/dev-utils/dev-server-tracing.test.d.mts.map +1 -0
  11. package/dist/dev-utils/dev-server-tracing.test.mjs +24 -0
  12. package/dist/dev-utils/dev-server-tracing.test.mjs.map +1 -0
  13. package/dist/dev-utils/dev-server.d.mts +10 -0
  14. package/dist/dev-utils/dev-server.d.mts.map +1 -1
  15. package/dist/dev-utils/dev-server.mjs +20 -1
  16. package/dist/dev-utils/dev-server.mjs.map +1 -1
  17. package/dist/react-compiler-runtime-shim.d.mts +23 -0
  18. package/dist/react-compiler-runtime-shim.d.mts.map +1 -0
  19. package/dist/react-compiler-runtime-shim.mjs +44 -0
  20. package/dist/react-compiler-runtime-shim.mjs.map +1 -0
  21. package/dist/react-compiler-runtime-shim.test.d.mts +2 -0
  22. package/dist/react-compiler-runtime-shim.test.d.mts.map +1 -0
  23. package/dist/react-compiler-runtime-shim.test.mjs +23 -0
  24. package/dist/react-compiler-runtime-shim.test.mjs.map +1 -0
  25. package/dist/telemetry/attributes.d.ts +0 -1
  26. package/dist/telemetry/attributes.d.ts.map +1 -1
  27. package/dist/telemetry/attributes.js +0 -1
  28. package/dist/telemetry/attributes.js.map +1 -1
  29. package/dist/telemetry/identity.d.ts +29 -0
  30. package/dist/telemetry/identity.d.ts.map +1 -0
  31. package/dist/telemetry/identity.js +9 -0
  32. package/dist/telemetry/identity.js.map +1 -0
  33. package/dist/telemetry/identity.test.d.ts +2 -0
  34. package/dist/telemetry/identity.test.d.ts.map +1 -0
  35. package/dist/telemetry/identity.test.js +25 -0
  36. package/dist/telemetry/identity.test.js.map +1 -0
  37. package/dist/telemetry/index.d.ts.map +1 -1
  38. package/dist/telemetry/index.js +55 -45
  39. package/dist/telemetry/index.js.map +1 -1
  40. package/dist/telemetry/index.test.js +29 -1
  41. package/dist/telemetry/index.test.js.map +1 -1
  42. package/dist/telemetry/local-obs.d.ts +8 -2
  43. package/dist/telemetry/local-obs.d.ts.map +1 -1
  44. package/dist/telemetry/local-obs.js +8 -3
  45. package/dist/telemetry/local-obs.js.map +1 -1
  46. package/dist/telemetry/otlp-config.d.ts +74 -0
  47. package/dist/telemetry/otlp-config.d.ts.map +1 -0
  48. package/dist/telemetry/otlp-config.js +204 -0
  49. package/dist/telemetry/otlp-config.js.map +1 -0
  50. package/dist/telemetry/otlp-config.test.d.ts +2 -0
  51. package/dist/telemetry/otlp-config.test.d.ts.map +1 -0
  52. package/dist/telemetry/otlp-config.test.js +156 -0
  53. package/dist/telemetry/otlp-config.test.js.map +1 -0
  54. package/dist/telemetry/preload.d.ts +23 -0
  55. package/dist/telemetry/preload.d.ts.map +1 -0
  56. package/dist/telemetry/preload.js +92 -0
  57. package/dist/telemetry/preload.js.map +1 -0
  58. package/dist/telemetry/util.d.ts +3 -3
  59. package/dist/telemetry/util.d.ts.map +1 -1
  60. package/dist/telemetry/util.js +6 -32
  61. package/dist/telemetry/util.js.map +1 -1
  62. package/package.json +18 -9
  63. package/src/application-build.mts +4 -0
  64. package/src/dev-utils/dev-server-resolve-config.test.mts +29 -0
  65. package/src/dev-utils/dev-server-tracing.test.mts +32 -0
  66. package/src/dev-utils/dev-server.mts +22 -1
  67. package/src/react-compiler-runtime-shim.mts +50 -0
  68. package/src/react-compiler-runtime-shim.test.mts +26 -0
  69. package/src/telemetry/attributes.ts +0 -1
  70. package/src/telemetry/identity.test.ts +30 -0
  71. package/src/telemetry/identity.ts +40 -0
  72. package/src/telemetry/index.test.ts +36 -1
  73. package/src/telemetry/index.ts +58 -47
  74. package/src/telemetry/local-obs.ts +8 -3
  75. package/src/telemetry/otlp-config.test.ts +213 -0
  76. package/src/telemetry/otlp-config.ts +250 -0
  77. package/src/telemetry/preload.ts +113 -0
  78. package/src/telemetry/util.ts +6 -32
  79. package/tsconfig.tsbuildinfo +1 -1
  80. package/turbo.json +0 -1
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Shared OTLP configuration for the dev-server CLI.
3
+ *
4
+ * This is the single source of truth for where the dev-server sends telemetry
5
+ * and how it tags it. It is imported by three places that must agree:
6
+ * - the early telemetry preload (`preload.mts`, full OTel SDK),
7
+ * - the SDK telemetry init (`util.ts` / `index.ts`),
8
+ * - the lightweight dev-server parent (`parent-otlp-metric.mts`), which hand-
9
+ * rolls OTLP because it must stay small and cannot load the OTel SDK.
10
+ *
11
+ * Because the parent imports this, the module MUST depend only on `node:`
12
+ * builtins and other import-free modules in this package (`attributes.ts`). Do
13
+ * NOT import OpenTelemetry, dd-trace, or the `@superblocksteam/sdk` barrel
14
+ * here — that would drag the SDK into the lightweight parent.
15
+ *
16
+ * Endpoint policy: the dev-server always exports through the Superblocks server's
17
+ * OTLP proxy at `<baseUrl>/api/v1/{traces,metrics,logs}`. The server forwards to
18
+ * the LGTM collector locally and to the in-cluster collector when deployed, so
19
+ * the client behaves identically in every environment — there is no
20
+ * direct-to-collector path.
21
+ */
22
+
23
+ import * as http from "node:http";
24
+ import * as https from "node:https";
25
+
26
+ import { getEnvironmentFromHostname } from "./attributes.js";
27
+
28
+ /** Resource `service.name` for every dev-server telemetry signal. */
29
+ export const SERVICE_NAME = "sdk-dev-server";
30
+
31
+ /** OTel instrument scope for parent-emitted metrics (distinct from the child). */
32
+ const SCOPE_NAME = "superblocks.cli.dev-parent";
33
+
34
+ /** Default fire-and-forget POST budget. Telemetry must never delay the parent. */
35
+ const DEFAULT_TIMEOUT_MS = 1000;
36
+
37
+ /**
38
+ * Base URL the dev-server exports OTLP to: `<baseUrl origin>/api`. Callers append
39
+ * `/v1/traces`, `/v1/metrics`, or `/v1/logs`. Returns undefined when the base URL
40
+ * is missing or unparseable so callers can skip emission rather than crash.
41
+ */
42
+ export function otlpBaseUrl(
43
+ baseUrl: string | URL | undefined,
44
+ ): string | undefined {
45
+ if (!baseUrl) {
46
+ return undefined;
47
+ }
48
+ try {
49
+ return `${new URL(baseUrl).origin}/api`;
50
+ } catch {
51
+ return undefined;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Full OTLP metrics endpoint derived from `SUPERBLOCKS_BASE_URL`. Used by the
57
+ * lightweight parent's hand-rolled emitter.
58
+ */
59
+ export function otlpMetricsUrl(
60
+ env: NodeJS.ProcessEnv = process.env,
61
+ ): string | null {
62
+ const base = otlpBaseUrl(env.SUPERBLOCKS_BASE_URL);
63
+ return base ? `${base}/v1/metrics` : null;
64
+ }
65
+
66
+ /**
67
+ * Derives the `deployment.environment.name` resource attribute from
68
+ * `SUPERBLOCKS_BASE_URL` so every dev-server signal carries the same environment
69
+ * tag. Defaults to `"other"` when the host is unknown or unavailable.
70
+ */
71
+ export function deploymentEnvironment(
72
+ env: NodeJS.ProcessEnv = process.env,
73
+ ): string {
74
+ const baseUrl = env.SUPERBLOCKS_BASE_URL;
75
+ if (!baseUrl) {
76
+ return "other";
77
+ }
78
+ let hostname: string;
79
+ try {
80
+ hostname = new URL(baseUrl).hostname;
81
+ } catch {
82
+ return "other";
83
+ }
84
+ return getEnvironmentFromHostname(hostname);
85
+ }
86
+
87
+ // The direct-to-collector export path was removed: the dev-server always goes
88
+ // through the server proxy. Anyone still setting the old env var gets one
89
+ // visible notice instead of silently-dropped configuration.
90
+ let warnedCollectorUrlIgnored = false;
91
+ export function warnIfCollectorUrlIgnored(
92
+ env: NodeJS.ProcessEnv = process.env,
93
+ ): void {
94
+ if (warnedCollectorUrlIgnored || !env.SUPERBLOCKS_OTEL_COLLECTOR_URL) {
95
+ return;
96
+ }
97
+ warnedCollectorUrlIgnored = true;
98
+ console.warn(
99
+ "[Telemetry] SUPERBLOCKS_OTEL_COLLECTOR_URL is set but ignored: the dev-server always exports through the Superblocks server proxy at <SUPERBLOCKS_BASE_URL>/api.",
100
+ );
101
+ }
102
+ warnIfCollectorUrlIgnored();
103
+
104
+ function toAttributeArray(
105
+ attributes: Record<string, string>,
106
+ ): Array<{ key: string; value: { stringValue: string } }> {
107
+ return Object.entries(attributes).map(([key, value]) => ({
108
+ key,
109
+ value: { stringValue: value },
110
+ }));
111
+ }
112
+
113
+ /**
114
+ * Builds a minimal OTLP/HTTP `ExportMetricsServiceRequest` body for a single
115
+ * monotonic DELTA counter increment (value 1). Timestamps are nanosecond
116
+ * decimal strings built with BigInt so JS number precision can't corrupt them;
117
+ * `startTimeUnixNano <= timeUnixNano` is required by the protocol.
118
+ */
119
+ export function buildDeltaCounterPayload(opts: {
120
+ name: string;
121
+ description: string;
122
+ attributes: Record<string, string>;
123
+ environment: string;
124
+ serviceName?: string;
125
+ scopeName?: string;
126
+ nowMs?: number;
127
+ }): unknown {
128
+ const nowNano = BigInt(opts.nowMs ?? Date.now()) * 1_000_000n;
129
+ // A 1ms window keeps start strictly <= time without implying real duration.
130
+ const startNano = nowNano - 1_000_000n;
131
+
132
+ return {
133
+ resourceMetrics: [
134
+ {
135
+ resource: {
136
+ attributes: toAttributeArray({
137
+ "service.name": opts.serviceName ?? SERVICE_NAME,
138
+ "deployment.environment.name": opts.environment,
139
+ }),
140
+ },
141
+ scopeMetrics: [
142
+ {
143
+ scope: { name: opts.scopeName ?? SCOPE_NAME },
144
+ metrics: [
145
+ {
146
+ name: opts.name,
147
+ description: opts.description,
148
+ sum: {
149
+ aggregationTemporality: 1, // AGGREGATION_TEMPORALITY_DELTA
150
+ isMonotonic: true,
151
+ dataPoints: [
152
+ {
153
+ attributes: toAttributeArray(opts.attributes),
154
+ startTimeUnixNano: startNano.toString(),
155
+ timeUnixNano: nowNano.toString(),
156
+ asInt: "1",
157
+ },
158
+ ],
159
+ },
160
+ },
161
+ ],
162
+ },
163
+ ],
164
+ },
165
+ ],
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Fire-and-forget POST of a single counter increment as OTLP/HTTP JSON.
171
+ *
172
+ * Never throws and never rejects: any failure (no endpoint, DNS, timeout,
173
+ * non-2xx) resolves quietly so telemetry cannot break or delay the parent's
174
+ * supervision loop or shutdown. In production the request socket and timeout are
175
+ * `unref`'d so a slow collector can't keep the parent alive; tests pass
176
+ * `unref: false` and `await` to observe the request deterministically.
177
+ */
178
+ export function emitOtlpCounter(opts: {
179
+ name: string;
180
+ description: string;
181
+ attributes: Record<string, string>;
182
+ env?: NodeJS.ProcessEnv;
183
+ timeoutMs?: number;
184
+ unref?: boolean;
185
+ }): Promise<void> {
186
+ const env = opts.env ?? process.env;
187
+ const url = otlpMetricsUrl(env);
188
+ if (!url) {
189
+ return Promise.resolve();
190
+ }
191
+
192
+ let parsed: URL;
193
+ try {
194
+ parsed = new URL(url);
195
+ } catch {
196
+ return Promise.resolve();
197
+ }
198
+
199
+ const body = JSON.stringify(
200
+ buildDeltaCounterPayload({
201
+ name: opts.name,
202
+ description: opts.description,
203
+ attributes: opts.attributes,
204
+ environment: deploymentEnvironment(env),
205
+ }),
206
+ );
207
+
208
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
209
+ const unref = opts.unref ?? true;
210
+ const transport = parsed.protocol === "https:" ? https : http;
211
+
212
+ return new Promise<void>((resolve) => {
213
+ let settled = false;
214
+ const done = (): void => {
215
+ if (settled) return;
216
+ settled = true;
217
+ resolve();
218
+ };
219
+
220
+ const req = transport.request(
221
+ {
222
+ protocol: parsed.protocol,
223
+ hostname: parsed.hostname,
224
+ port: parsed.port,
225
+ path: parsed.pathname + parsed.search,
226
+ method: "POST",
227
+ headers: {
228
+ "content-type": "application/json",
229
+ "content-length": Buffer.byteLength(body),
230
+ },
231
+ },
232
+ (res) => {
233
+ // Drain so the socket can close; the status is irrelevant to the parent.
234
+ res.on("data", () => {});
235
+ res.on("end", done);
236
+ res.on("error", done);
237
+ },
238
+ );
239
+
240
+ req.on("error", done);
241
+ req.setTimeout(timeoutMs, () => {
242
+ req.destroy();
243
+ done();
244
+ });
245
+ if (unref) {
246
+ req.on("socket", (socket) => socket.unref());
247
+ }
248
+ req.end(body);
249
+ });
250
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Early telemetry preload for the dev-server child.
3
+ *
4
+ * This module has ONE job: start the OpenTelemetry SDK before any instrumented
5
+ * module (node:http, express, the ai-service) is loaded. OTel auto-instrumentation
6
+ * patches modules at require time, so if the SDK starts late — as it did when the
7
+ * only init was `configureTelemetry` at connect time — those modules load
8
+ * unpatched and the dev-server exports nothing. The child entry (`dev.mts`)
9
+ * imports this as its very first import so it runs before the SDK barrel.
10
+ *
11
+ * It intentionally does NOT init dd-trace/LLMObs. Those spans are created during
12
+ * generation (after connect) and need the application id that is only known then,
13
+ * so `configureTelemetry` still owns LLMObs. `configureTelemetry`'s own
14
+ * `initNodeTelemetry` call becomes a no-op here: `initNodeTelemetry` returns the
15
+ * already-created instance, so we init once, early.
16
+ *
17
+ * Config comes from the environment (available at process start), not the auth
18
+ * file, so nothing here has to await a network/disk read. The endpoint and
19
+ * environment tag are resolved by the shared `otlp-config` module, the same one
20
+ * the lightweight parent uses, so parent and child agree on where telemetry goes.
21
+ */
22
+
23
+ import os from "node:os";
24
+
25
+ import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
26
+ import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
27
+
28
+ import { DeploymentTypeEnum } from "@superblocksteam/shared";
29
+ import {
30
+ getDefaultPolicy,
31
+ initNodeTelemetry,
32
+ parseDeploymentType,
33
+ } from "@superblocksteam/telemetry";
34
+
35
+ import packageJson from "../../package.json" with { type: "json" };
36
+ import {
37
+ ATTR_SUPERBLOCKS_BASE_URL,
38
+ ATTR_SUPERBLOCKS_CSB_ID,
39
+ ATTR_SUPERBLOCKS_IS_CSB,
40
+ getEnvironmentFromHostname,
41
+ } from "./attributes.js";
42
+ import { getTelemetryIdentity } from "./identity.js";
43
+ import { applyLocalObsEnvVars } from "./local-obs.js";
44
+ import { otlpBaseUrl, SERVICE_NAME } from "./otlp-config.js";
45
+
46
+ // Apply local-obs env defaults first so deployment type / export policy are set
47
+ // before we read them below (mirrors the module-scope call in index.ts).
48
+ applyLocalObsEnvVars();
49
+
50
+ const baseUrl = process.env.SUPERBLOCKS_BASE_URL;
51
+ const otlpUrl = otlpBaseUrl(baseUrl);
52
+
53
+ // Without a base URL we have no proxy to export to; skip early init and let the
54
+ // later configureTelemetry path (which resolves config from the auth file) run.
55
+ // Warn so the fallback is visible: by then http/express are already loaded
56
+ // unpatched, so auto-instrumented traces are missing for this run.
57
+ if (!otlpUrl) {
58
+ console.warn(
59
+ "[Telemetry] SUPERBLOCKS_BASE_URL is not set; skipping early OpenTelemetry init. Auto-instrumented HTTP/Express traces will be missing for this run.",
60
+ );
61
+ } else {
62
+ const hostname = (() => {
63
+ try {
64
+ return baseUrl ? new URL(baseUrl).hostname : "";
65
+ } catch {
66
+ return "";
67
+ }
68
+ })();
69
+ const isCsb = !!process.env.SUPERBLOCKS_IS_CSB;
70
+
71
+ // A typo'd SUPERBLOCKS_DEPLOYMENT_TYPE must not abort early init; default to
72
+ // CLOUD like the connect-time path in index.ts does.
73
+ const deploymentType = (() => {
74
+ try {
75
+ return parseDeploymentType(process.env.SUPERBLOCKS_DEPLOYMENT_TYPE);
76
+ } catch (error) {
77
+ console.warn(
78
+ "[Telemetry] Invalid SUPERBLOCKS_DEPLOYMENT_TYPE, defaulting to CLOUD:",
79
+ error,
80
+ );
81
+ return DeploymentTypeEnum.CLOUD;
82
+ }
83
+ })();
84
+
85
+ try {
86
+ initNodeTelemetry(
87
+ {
88
+ serviceName: SERVICE_NAME,
89
+ serviceVersion: packageJson.version,
90
+ environment: getEnvironmentFromHostname(hostname),
91
+ otlpUrl,
92
+ resourceAttributes: {
93
+ [ATTR_SUPERBLOCKS_BASE_URL]: hostname,
94
+ [ATTR_SUPERBLOCKS_IS_CSB]: String(isCsb),
95
+ ...(isCsb && { [ATTR_SUPERBLOCKS_CSB_ID]: os.hostname() }),
96
+ },
97
+ instrumentations: [
98
+ new HttpInstrumentation(),
99
+ new ExpressInstrumentation(),
100
+ ],
101
+ logsEnabled: true,
102
+ // Identity (application/user/org) is only known at connect;
103
+ // configureTelemetry fills it in and every span and exported log
104
+ // record picks it up here.
105
+ dynamicAttributes: getTelemetryIdentity,
106
+ },
107
+ getDefaultPolicy(deploymentType),
108
+ );
109
+ } catch (error) {
110
+ // Never let telemetry init crash the dev-server boot.
111
+ console.error("[Telemetry] Early preload failed to initialize:", error);
112
+ }
113
+ }
@@ -1,48 +1,22 @@
1
1
  import { getLocalTokenWithUrl } from "@superblocksteam/util";
2
2
 
3
- export const SERVICE_NAME = "sdk-dev-server";
3
+ import { otlpBaseUrl, SERVICE_NAME } from "./otlp-config.js";
4
4
 
5
- /**
6
- * Get the OTLP base URL for telemetry export.
7
- *
8
- * Priority:
9
- * 1. SUPERBLOCKS_OTEL_COLLECTOR_URL env var (for local obs mode)
10
- * 2. Superblocks API endpoint derived from base URL
11
- *
12
- * When SUPERBLOCKS_OTEL_COLLECTOR_URL is set (e.g., http://localhost:4318),
13
- * telemetry is sent directly to the local OTEL collector.
14
- *
15
- * Note: This returns the BASE URL. The telemetry library appends /v1/traces,
16
- * /v1/metrics, /v1/logs to this URL.
17
- */
18
- function getOtlpBaseUrl(superblocksBaseUrl: URL): string {
19
- const localOtelUrl = process.env.SUPERBLOCKS_OTEL_COLLECTOR_URL;
20
- if (localOtelUrl) {
21
- // Remove trailing /v1/traces etc. if present (just use base URL)
22
- return localOtelUrl.replace(/\/v1\/(traces|metrics|logs)$/, "");
23
- }
24
- // Default: Superblocks API proxies OTLP requests
25
- // The server expects /api/v1/traces but the telemetry lib adds /v1/traces,
26
- // so we return /api and let telemetry lib append /v1/traces
27
- return superblocksBaseUrl.origin + "/api";
28
- }
5
+ // Re-exported so existing importers of `./util.js` keep resolving SERVICE_NAME
6
+ // from the single source of truth in otlp-config.
7
+ export { SERVICE_NAME };
29
8
 
30
9
  export async function getConfiguration() {
31
10
  try {
32
11
  const tokenWithUrl = await getLocalTokenWithUrl();
33
12
  const superblocksBaseUrl = new URL(tokenWithUrl.superblocksBaseUrl);
34
- const otlpBaseUrl = getOtlpBaseUrl(superblocksBaseUrl);
13
+ const otlpBase = otlpBaseUrl(superblocksBaseUrl);
35
14
  const superblocksHostname = superblocksBaseUrl.hostname;
36
- let token = "";
37
- if ("token" in tokenWithUrl) {
38
- token = tokenWithUrl.token.substring(0, 8);
39
- }
40
15
  return {
41
16
  superblocksBaseUrl,
42
- otlpBaseUrl,
17
+ otlpBaseUrl: otlpBase,
43
18
  superblocksHostname,
44
19
  serviceName: SERVICE_NAME,
45
- token,
46
20
  };
47
21
  } catch (e) {
48
22
  console.error(