@glasstrace/sdk 0.0.1 → 0.2.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.
@@ -0,0 +1,389 @@
1
+ import { SdkDiagnosticCode, GlasstraceEnvVars, GlasstraceOptions, SessionId, AnonApiKey, CaptureConfig, SdkInitResponse, ImportGraphPayload, SdkHealthReport, SourceMapUploadResponse } from '@glasstrace/protocol';
2
+ import { Context } from '@opentelemetry/api';
3
+ import { SpanProcessor, Span, ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';
4
+ import { ExportResult } from '@opentelemetry/core';
5
+
6
+ /**
7
+ * Internal SDK error class with a typed diagnostic code.
8
+ * Caught at the boundary and converted to a log message + diagnostic entry.
9
+ * Never thrown to the developer.
10
+ */
11
+ declare class SdkError extends Error {
12
+ readonly code: SdkDiagnosticCode;
13
+ constructor(code: SdkDiagnosticCode, message: string, cause?: Error);
14
+ }
15
+
16
+ /**
17
+ * Resolved configuration after merging explicit options with environment variables.
18
+ */
19
+ interface ResolvedConfig {
20
+ apiKey: string | undefined;
21
+ endpoint: string;
22
+ forceEnable: boolean;
23
+ verbose: boolean;
24
+ environment: string | undefined;
25
+ coverageMapEnabled: boolean;
26
+ nodeEnv: string | undefined;
27
+ vercelEnv: string | undefined;
28
+ }
29
+ /**
30
+ * Reads all recognized Glasstrace environment variables from process.env.
31
+ * Returns undefined for any variable not set. Never throws.
32
+ */
33
+ declare function readEnvVars(): GlasstraceEnvVars;
34
+ /**
35
+ * Merges explicit GlasstraceOptions with environment variables.
36
+ * Explicit options take precedence over environment variables.
37
+ */
38
+ declare function resolveConfig(options?: GlasstraceOptions): ResolvedConfig;
39
+ /**
40
+ * Returns true when the SDK should be inactive (production detected without force-enable).
41
+ * Logic order:
42
+ * 1. forceEnable === true → return false (override)
43
+ * 2. NODE_ENV === 'production' → return true
44
+ * 3. VERCEL_ENV === 'production' → return true
45
+ * 4. Otherwise → return false
46
+ */
47
+ declare function isProductionDisabled(config: ResolvedConfig): boolean;
48
+ /**
49
+ * Returns true when no API key is configured (anonymous mode).
50
+ * Treats undefined, empty string, whitespace-only, and gt_anon_* keys as anonymous.
51
+ */
52
+ declare function isAnonymousMode(config: ResolvedConfig): boolean;
53
+
54
+ /**
55
+ * Derives a deterministic session ID from the given inputs using SHA-256.
56
+ * The hash is truncated to 16 hex characters and parsed through SessionIdSchema.
57
+ *
58
+ * @param apiKey - The project's API key (or anonymous placeholder).
59
+ * @param origin - The origin string identifying the deployment environment.
60
+ * @param date - UTC date as YYYY-MM-DD.
61
+ * @param windowIndex - Zero-based index of the 4-hour activity window within the day.
62
+ * @returns A 16-character hex SessionId.
63
+ */
64
+ declare function deriveSessionId(apiKey: string, origin: string, date: string, windowIndex: number): SessionId;
65
+ /**
66
+ * Returns the origin string for the current process.
67
+ * If GLASSTRACE_ENV is set, returns that value.
68
+ * Otherwise returns `localhost:{PORT}` (PORT defaults to 3000).
69
+ *
70
+ * @returns The origin string used as a session derivation input.
71
+ */
72
+ declare function getOrigin(): string;
73
+ /**
74
+ * Returns the current UTC date as a YYYY-MM-DD string.
75
+ *
76
+ * @returns The UTC date formatted as "YYYY-MM-DD".
77
+ */
78
+ declare function getDateString(): string;
79
+ /**
80
+ * Tracks the current session state with 4-hour window tracking.
81
+ * Instantiated once by the orchestrator.
82
+ */
83
+ declare class SessionManager {
84
+ private windowIndex;
85
+ private lastActivityTimestamp;
86
+ private lastDate;
87
+ private lastApiKey;
88
+ private currentSessionId;
89
+ /**
90
+ * Returns the current session ID, deriving a new one if:
91
+ * - More than 4 hours have elapsed since last activity
92
+ * - The UTC date has changed (resets window index to 0)
93
+ * - The API key has changed (e.g., deferred anonymous key swap)
94
+ * - This is the first call
95
+ *
96
+ * @param apiKey - The project's API key used in session derivation.
97
+ * @returns The current or newly derived SessionId.
98
+ */
99
+ getSessionId(apiKey: string): SessionId;
100
+ }
101
+
102
+ /**
103
+ * The set of recognized fetch target categories.
104
+ */
105
+ type FetchTarget = "supabase" | "stripe" | "internal" | "unknown";
106
+ /**
107
+ * Classifies an outbound fetch target URL into a known category.
108
+ * Classification is case-insensitive and based on the URL hostname.
109
+ * Uses dot-boundary matching to avoid false positives (e.g. evilstripe.com).
110
+ *
111
+ * Returns one of: 'supabase', 'stripe', 'internal', or 'unknown'.
112
+ */
113
+ declare function classifyFetchTarget(url: string): FetchTarget;
114
+
115
+ /**
116
+ * Reads an existing anonymous key from the filesystem.
117
+ * Returns the key if valid, or null if:
118
+ * - The file does not exist
119
+ * - The file content is invalid
120
+ * - An I/O error occurs
121
+ */
122
+ declare function readAnonKey(projectRoot?: string): Promise<AnonApiKey | null>;
123
+ /**
124
+ * Gets an existing anonymous key from the filesystem, or creates a new one.
125
+ *
126
+ * - If file exists and contains a valid key, returns it
127
+ * - If file does not exist or content is invalid, generates a new key via createAnonApiKey()
128
+ * - Writes the new key to `.glasstrace/anon_key`, creating the directory if needed
129
+ * - On file write failure: logs a warning, caches an ephemeral in-memory key so
130
+ * repeated calls in the same process return the same key
131
+ */
132
+ declare function getOrCreateAnonKey(projectRoot?: string): Promise<AnonApiKey>;
133
+
134
+ /**
135
+ * Reads and validates a cached config file from `.glasstrace/config`.
136
+ * Returns the parsed `SdkInitResponse` or `null` on any failure.
137
+ */
138
+ declare function loadCachedConfig(projectRoot?: string): SdkInitResponse | null;
139
+ /**
140
+ * Persists the init response to `.glasstrace/config`.
141
+ * On failure, logs a warning and continues.
142
+ */
143
+ declare function saveCachedConfig(response: SdkInitResponse, projectRoot?: string): Promise<void>;
144
+ /**
145
+ * Sends a POST request to `/v1/sdk/init`.
146
+ * Validates the response against `SdkInitResponseSchema`.
147
+ */
148
+ declare function sendInitRequest(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string, importGraph?: ImportGraphPayload, healthReport?: SdkHealthReport, diagnostics?: Array<{
149
+ code: SdkDiagnosticCode;
150
+ message: string;
151
+ timestamp: number;
152
+ }>, signal?: AbortSignal): Promise<SdkInitResponse>;
153
+ /**
154
+ * Orchestrates the full init flow: send request, update config, cache result.
155
+ * This function MUST NOT throw.
156
+ */
157
+ declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<void>;
158
+ /**
159
+ * Returns the current capture config from the three-tier fallback chain:
160
+ * 1. In-memory config from latest init response
161
+ * 2. File cache
162
+ * 3. DEFAULT_CAPTURE_CONFIG
163
+ */
164
+ declare function getActiveConfig(): CaptureConfig;
165
+
166
+ /**
167
+ * Lightweight SpanProcessor that delegates to a wrapped processor.
168
+ *
169
+ * All glasstrace.* attribute enrichment has been moved to {@link GlasstraceExporter}
170
+ * (see enriching-exporter.ts), which enriches spans at export time. This resolves:
171
+ * - Cold-start spans are buffered in the exporter, not dropped
172
+ * - Vercel's CompositeSpanProcessor skips onEnding(); the exporter doesn't need it
173
+ * - Session ID is computed at export time with the resolved API key
174
+ *
175
+ * This class is retained for backward compatibility. New code should use
176
+ * GlasstraceExporter directly.
177
+ *
178
+ * @deprecated Use GlasstraceExporter for span enrichment. This processor is now a pass-through.
179
+ */
180
+ declare class GlasstraceSpanProcessor implements SpanProcessor {
181
+ private readonly wrappedProcessor;
182
+ constructor(wrappedProcessor: SpanProcessor, _sessionManager?: SessionManager, _apiKey?: string | (() => string), _getConfig?: () => CaptureConfig, _environment?: string);
183
+ onStart(span: Span, parentContext: Context): void;
184
+ onEnd(readableSpan: ReadableSpan): void;
185
+ shutdown(): Promise<void>;
186
+ forceFlush(): Promise<void>;
187
+ }
188
+
189
+ /**
190
+ * Options for constructing a {@link GlasstraceExporter}.
191
+ */
192
+ interface GlasstraceExporterOptions {
193
+ getApiKey: () => string;
194
+ sessionManager: SessionManager;
195
+ getConfig: () => CaptureConfig;
196
+ environment: string | undefined;
197
+ endpointUrl: string;
198
+ createDelegate: ((url: string, headers: Record<string, string>) => SpanExporter) | null;
199
+ }
200
+ /**
201
+ * A SpanExporter that enriches spans with glasstrace.* attributes at export
202
+ * time, then delegates to a real OTLP exporter.
203
+ *
204
+ * This design resolves three issues:
205
+ * - Spans emitted before the API key resolves are buffered (not dropped)
206
+ * and flushed once the key is available.
207
+ * - Enrichment happens in the exporter (not onEnding), so it works
208
+ * on Vercel where CompositeSpanProcessor does not forward onEnding().
209
+ * - Session ID is computed at export time using the resolved API key,
210
+ * not the "pending" placeholder.
211
+ */
212
+ declare class GlasstraceExporter implements SpanExporter {
213
+ private readonly getApiKey;
214
+ private readonly sessionManager;
215
+ private readonly getConfig;
216
+ private readonly environment;
217
+ private readonly endpointUrl;
218
+ private readonly createDelegateFn;
219
+ private delegate;
220
+ private delegateKey;
221
+ private pendingBatches;
222
+ private pendingSpanCount;
223
+ private overflowLogged;
224
+ constructor(options: GlasstraceExporterOptions);
225
+ export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
226
+ /**
227
+ * Called when the API key transitions from "pending" to a resolved value.
228
+ * Creates the delegate exporter and flushes all buffered spans.
229
+ */
230
+ notifyKeyResolved(): void;
231
+ shutdown(): Promise<void>;
232
+ forceFlush(): Promise<void>;
233
+ /**
234
+ * Enriches a ReadableSpan with all glasstrace.* attributes.
235
+ * Returns a new ReadableSpan wrapper; the original span is not mutated.
236
+ *
237
+ * External function calls (getSessionId, deriveErrorCategory,
238
+ * deriveOrmProvider, classifyFetchTarget) are individually guarded so a
239
+ * failure in one does not prevent the remaining attributes from being set.
240
+ * On total failure, returns the original span unchanged.
241
+ */
242
+ private enrichSpan;
243
+ /**
244
+ * Lazily creates the delegate OTLP exporter once the API key is resolved.
245
+ * Recreates the delegate if the key has changed (e.g., after key rotation)
246
+ * so the Authorization header stays current.
247
+ */
248
+ private ensureDelegate;
249
+ /**
250
+ * Buffers raw (unenriched) spans while the API key is pending.
251
+ * Evicts oldest batches if the buffer exceeds MAX_PENDING_SPANS.
252
+ * Re-checks the key after buffering to close the race window where
253
+ * the key resolves between the caller's check and this buffer call.
254
+ */
255
+ private bufferSpans;
256
+ /**
257
+ * Flushes all buffered spans through the delegate exporter.
258
+ * Enriches spans at flush time (not buffer time) so that session IDs
259
+ * are computed with the resolved API key instead of the "pending" sentinel.
260
+ */
261
+ private flushPending;
262
+ }
263
+
264
+ /**
265
+ * Creates a request handler for the `/__glasstrace/config` discovery endpoint.
266
+ *
267
+ * The returned handler checks if the request URL path is `/__glasstrace/config`.
268
+ * If not, returns `null` (pass-through). If it matches, returns a `DiscoveryResponse`
269
+ * with the anonymous key and current session ID.
270
+ *
271
+ * The triple guard (anonymous + dev + active) is enforced by the caller,
272
+ * not by this module. If the handler is registered, it serves.
273
+ */
274
+ declare function createDiscoveryHandler(getAnonKey: () => Promise<AnonApiKey | null>, getSessionId: () => SessionId): (request: Request) => Promise<Response | null>;
275
+
276
+ /**
277
+ * The primary SDK entry point called by developers in their `instrumentation.ts`.
278
+ * Orchestrates OTel setup, span processor, init client, anon key, and discovery endpoint.
279
+ *
280
+ * This function is synchronous and MUST NOT throw. The developer's server is never blocked.
281
+ * Background work (key resolution, init call) happens via fire-and-forget promises.
282
+ *
283
+ * @param options - Optional SDK configuration. Environment variables are used as fallbacks.
284
+ *
285
+ * @example
286
+ * ```ts
287
+ * // instrumentation.ts
288
+ * import { registerGlasstrace } from "@glasstrace/sdk";
289
+ * registerGlasstrace(); // uses env vars
290
+ * ```
291
+ */
292
+ declare function registerGlasstrace(options?: GlasstraceOptions): void;
293
+ /**
294
+ * Returns the registered discovery handler, or null if not registered.
295
+ */
296
+ declare function getDiscoveryHandler(): ((request: Request) => Promise<Response | null>) | null;
297
+
298
+ type NextConfig = Record<string, unknown>;
299
+ /**
300
+ * Wraps the developer's Next.js config to enable source map generation
301
+ * and upload .map files to the ingestion API at build time.
302
+ *
303
+ * The build NEVER fails because of Glasstrace — all errors are caught
304
+ * and logged as warnings.
305
+ *
306
+ * @param nextConfig - The developer's existing Next.js configuration object.
307
+ * @returns A new config object with source map generation and upload enabled.
308
+ */
309
+ declare function withGlasstraceConfig(nextConfig: NextConfig): NextConfig;
310
+
311
+ interface SourceMapEntry {
312
+ filePath: string;
313
+ content: string;
314
+ }
315
+ /**
316
+ * Recursively finds all .map files in the given build directory.
317
+ * Returns relative paths and file contents.
318
+ */
319
+ declare function collectSourceMaps(buildDir: string): Promise<SourceMapEntry[]>;
320
+ /**
321
+ * Computes a build hash for source map uploads.
322
+ *
323
+ * First tries `git rev-parse HEAD` to get the git commit SHA.
324
+ * On failure, falls back to a deterministic content hash:
325
+ * sort source map file paths alphabetically, concatenate each as
326
+ * `{relativePath}\n{fileLength}\n{fileContent}`, then SHA-256 the result.
327
+ */
328
+ declare function computeBuildHash(maps?: SourceMapEntry[]): Promise<string>;
329
+ /**
330
+ * Uploads source maps to the ingestion API.
331
+ *
332
+ * POSTs to `{endpoint}/v1/source-maps` with the API key, build hash,
333
+ * and file entries. Validates the response against SourceMapUploadResponseSchema.
334
+ */
335
+ declare function uploadSourceMaps(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[]): Promise<SourceMapUploadResponse>;
336
+
337
+ /**
338
+ * Records an error as a span event on the currently active OTel span.
339
+ *
340
+ * Works regardless of the `consoleErrors` configuration — this is an
341
+ * explicit, opt-in API for manual error reporting. If no span is active
342
+ * or OTel is not available, the call is silently ignored.
343
+ *
344
+ * @param error - The error to capture. Accepts `Error` objects, strings, or any value.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * import { captureError } from "@glasstrace/sdk";
349
+ *
350
+ * try {
351
+ * await riskyOperation();
352
+ * } catch (err) {
353
+ * captureError(err);
354
+ * // handle error normally...
355
+ * }
356
+ * ```
357
+ */
358
+ declare function captureError(error: unknown): void;
359
+
360
+ /**
361
+ * Discovers test files by scanning the project directory for conventional
362
+ * test file patterns. Also reads vitest/jest config files for custom include
363
+ * patterns and merges them with the defaults. Excludes node_modules/ and .next/.
364
+ *
365
+ * @param projectRoot - Absolute path to the project root directory.
366
+ * @returns Relative POSIX paths from projectRoot, capped at {@link MAX_TEST_FILES}.
367
+ */
368
+ declare function discoverTestFiles(projectRoot: string): Promise<string[]>;
369
+ /**
370
+ * Extracts import paths from file content using regex.
371
+ * Handles ES module imports, CommonJS requires, and dynamic imports.
372
+ *
373
+ * @param fileContent - The full text content of a TypeScript/JavaScript file.
374
+ * @returns An array of import path strings as written in the source (e.g. "./foo", "react").
375
+ */
376
+ declare function extractImports(fileContent: string): string[];
377
+ /**
378
+ * Builds an import graph mapping test file paths to their imported module paths.
379
+ *
380
+ * Discovers test files, reads each, extracts imports, and builds a graph.
381
+ * Computes a deterministic buildHash from the serialized graph content.
382
+ * Individual file read failures are silently skipped.
383
+ *
384
+ * @param projectRoot - Absolute path to the project root directory.
385
+ * @returns An {@link ImportGraphPayload} containing the graph and a deterministic buildHash.
386
+ */
387
+ declare function buildImportGraph(projectRoot: string): Promise<ImportGraphPayload>;
388
+
389
+ export { type FetchTarget, GlasstraceExporter, type GlasstraceExporterOptions, GlasstraceSpanProcessor, type ResolvedConfig, SdkError, SessionManager, type SourceMapEntry, buildImportGraph, captureError, classifyFetchTarget, collectSourceMaps, computeBuildHash, createDiscoveryHandler, deriveSessionId, discoverTestFiles, extractImports, getActiveConfig, getDateString, getDiscoveryHandler, getOrCreateAnonKey, getOrigin, isAnonymousMode, isProductionDisabled, loadCachedConfig, performInit, readAnonKey, readEnvVars, registerGlasstrace, resolveConfig, saveCachedConfig, sendInitRequest, uploadSourceMaps, withGlasstraceConfig };