@glasstrace/sdk 0.4.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -67,6 +67,7 @@ type GlasstraceEnvVars = z.infer<typeof GlasstraceEnvVarsSchema>;
67
67
  * - SDK health diagnostics (embedded in init requests)
68
68
  * - Discovery endpoint (GET /__glasstrace/config)
69
69
  * - Source map upload (POST /v1/source-maps)
70
+ * - Presigned source map upload (POST /v1/source-maps/presign, POST /v1/source-maps/manifest)
70
71
  */
71
72
 
72
73
  /** Test file import relationships, embedded in SDK init request. */
@@ -112,6 +113,11 @@ declare const SdkInitResponseSchema: z.ZodObject<{
112
113
  maxTraceSizeBytes: z.ZodNumber;
113
114
  maxConcurrentSessions: z.ZodNumber;
114
115
  }, z.core.$strip>;
116
+ claimResult: z.ZodOptional<z.ZodObject<{
117
+ newApiKey: z.core.$ZodBranded<z.ZodString, "DevApiKey", "out">;
118
+ accountId: z.ZodString;
119
+ graceExpiresAt: z.ZodNumber;
120
+ }, z.core.$strip>>;
115
121
  }, z.core.$strip>;
116
122
  type SdkInitResponse = z.infer<typeof SdkInitResponseSchema>;
117
123
  /** Response from POST /v1/source-maps. */
@@ -122,6 +128,15 @@ declare const SourceMapUploadResponseSchema: z.ZodObject<{
122
128
  totalSizeBytes: z.ZodNumber;
123
129
  }, z.core.$strip>;
124
130
  type SourceMapUploadResponse = z.infer<typeof SourceMapUploadResponseSchema>;
131
+ /** Response confirming source map manifest activation. */
132
+ declare const SourceMapManifestResponseSchema: z.ZodObject<{
133
+ success: z.ZodLiteral<true>;
134
+ buildHash: z.core.$ZodBranded<z.ZodString, "BuildHash", "out">;
135
+ fileCount: z.ZodNumber;
136
+ totalSizeBytes: z.ZodNumber;
137
+ activatedAt: z.ZodNumber;
138
+ }, z.core.$strip>;
139
+ type SourceMapManifestResponse = z.infer<typeof SourceMapManifestResponseSchema>;
125
140
 
126
141
  /**
127
142
  * Internal SDK error class with a typed diagnostic code.
@@ -270,11 +285,24 @@ declare function sendInitRequest(config: ResolvedConfig, anonKey: AnonApiKey | n
270
285
  message: string;
271
286
  timestamp: number;
272
287
  }>, signal?: AbortSignal): Promise<SdkInitResponse>;
288
+ /**
289
+ * Result returned by {@link performInit} when the backend reports an
290
+ * account claim transition. `null` means no claim was present.
291
+ */
292
+ interface InitClaimResult {
293
+ claimResult: NonNullable<SdkInitResponse["claimResult"]>;
294
+ }
273
295
  /**
274
296
  * Orchestrates the full init flow: send request, update config, cache result.
275
297
  * This function MUST NOT throw.
298
+ *
299
+ * Returns the claim result when the backend reports an account claim
300
+ * transition, or `null` when no claim result is available (including
301
+ * when init is skipped due to rate-limit backoff, missing API key,
302
+ * or request failure). Callers that do not need claim information
303
+ * can safely ignore the return value.
276
304
  */
277
- declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<void>;
305
+ declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<InitClaimResult | null>;
278
306
  /**
279
307
  * Returns the current capture config from the three-tier fallback chain:
280
308
  * 1. In-memory config from latest init response
@@ -354,9 +382,13 @@ declare class GlasstraceExporter implements SpanExporter {
354
382
  * Enriches a ReadableSpan with all glasstrace.* attributes.
355
383
  * Returns a new ReadableSpan wrapper; the original span is not mutated.
356
384
  *
357
- * External function calls (getSessionId, deriveErrorCategory,
358
- * deriveOrmProvider, classifyFetchTarget) are individually guarded so a
359
- * failure in one does not prevent the remaining attributes from being set.
385
+ * Only {@link SessionManager.getSessionId} is individually guarded because
386
+ * it calls into crypto and schema validation — a session ID failure should
387
+ * not prevent the rest of enrichment. The other helper calls
388
+ * ({@link deriveErrorCategory}, {@link deriveOrmProvider},
389
+ * {@link classifyFetchTarget}) are pure functions on typed string inputs
390
+ * and rely on the outer catch for any unexpected failure.
391
+ *
360
392
  * On total failure, returns the original span unchanged.
361
393
  */
362
394
  private enrichSpan;
@@ -453,6 +485,43 @@ declare function computeBuildHash(maps?: SourceMapEntry[]): Promise<string>;
453
485
  * and file entries. Validates the response against SourceMapUploadResponseSchema.
454
486
  */
455
487
  declare function uploadSourceMaps(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[]): Promise<SourceMapUploadResponse>;
488
+ /** Builds at or above this byte size route to the presigned upload flow. */
489
+ declare const PRESIGNED_THRESHOLD_BYTES = 4500000;
490
+ /** Signature for the blob upload function, injectable for testing. */
491
+ type BlobUploader = (clientToken: string, pathname: string, content: string) => Promise<{
492
+ url: string;
493
+ size: number;
494
+ }>;
495
+ /**
496
+ * Orchestrates the 3-phase presigned upload flow.
497
+ *
498
+ * 1. Requests presigned tokens for all source map files
499
+ * 2. Uploads each file to blob storage with a concurrency limit of 5
500
+ * 3. Submits the manifest to finalize the upload
501
+ *
502
+ * Accepts an optional `blobUploader` for test injection; defaults to
503
+ * {@link uploadToBlob}.
504
+ */
505
+ declare function uploadSourceMapsPresigned(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], blobUploader?: BlobUploader): Promise<SourceMapManifestResponse>;
506
+ /**
507
+ * Options for {@link uploadSourceMapsAuto}, primarily used for test injection.
508
+ */
509
+ interface AutoUploadOptions {
510
+ /** Override blob availability check (for testing). */
511
+ checkBlobAvailable?: () => Promise<boolean>;
512
+ /** Override blob uploader (for testing). */
513
+ blobUploader?: BlobUploader;
514
+ }
515
+ /**
516
+ * Automatically routes source map uploads based on total build size.
517
+ *
518
+ * - Below {@link PRESIGNED_THRESHOLD_BYTES}: uses the legacy single-request
519
+ * {@link uploadSourceMaps} endpoint.
520
+ * - At or above the threshold: checks if `@vercel/blob` is available and
521
+ * uses the presigned 3-phase flow. Falls back to legacy with a warning
522
+ * if the package is not installed.
523
+ */
524
+ declare function uploadSourceMapsAuto(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], options?: AutoUploadOptions): Promise<SourceMapUploadResponse | SourceMapManifestResponse>;
456
525
 
457
526
  /**
458
527
  * Manual error capture API.
@@ -515,4 +584,4 @@ declare function extractImports(fileContent: string): string[];
515
584
  */
516
585
  declare function buildImportGraph(projectRoot: string): Promise<ImportGraphPayload>;
517
586
 
518
- 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 };
587
+ export { type AutoUploadOptions, type BlobUploader, type FetchTarget, GlasstraceExporter, type GlasstraceExporterOptions, GlasstraceSpanProcessor, type InitClaimResult, PRESIGNED_THRESHOLD_BYTES, 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, uploadSourceMapsAuto, uploadSourceMapsPresigned, withGlasstraceConfig };
package/dist/index.d.ts CHANGED
@@ -67,6 +67,7 @@ type GlasstraceEnvVars = z.infer<typeof GlasstraceEnvVarsSchema>;
67
67
  * - SDK health diagnostics (embedded in init requests)
68
68
  * - Discovery endpoint (GET /__glasstrace/config)
69
69
  * - Source map upload (POST /v1/source-maps)
70
+ * - Presigned source map upload (POST /v1/source-maps/presign, POST /v1/source-maps/manifest)
70
71
  */
71
72
 
72
73
  /** Test file import relationships, embedded in SDK init request. */
@@ -112,6 +113,11 @@ declare const SdkInitResponseSchema: z.ZodObject<{
112
113
  maxTraceSizeBytes: z.ZodNumber;
113
114
  maxConcurrentSessions: z.ZodNumber;
114
115
  }, z.core.$strip>;
116
+ claimResult: z.ZodOptional<z.ZodObject<{
117
+ newApiKey: z.core.$ZodBranded<z.ZodString, "DevApiKey", "out">;
118
+ accountId: z.ZodString;
119
+ graceExpiresAt: z.ZodNumber;
120
+ }, z.core.$strip>>;
115
121
  }, z.core.$strip>;
116
122
  type SdkInitResponse = z.infer<typeof SdkInitResponseSchema>;
117
123
  /** Response from POST /v1/source-maps. */
@@ -122,6 +128,15 @@ declare const SourceMapUploadResponseSchema: z.ZodObject<{
122
128
  totalSizeBytes: z.ZodNumber;
123
129
  }, z.core.$strip>;
124
130
  type SourceMapUploadResponse = z.infer<typeof SourceMapUploadResponseSchema>;
131
+ /** Response confirming source map manifest activation. */
132
+ declare const SourceMapManifestResponseSchema: z.ZodObject<{
133
+ success: z.ZodLiteral<true>;
134
+ buildHash: z.core.$ZodBranded<z.ZodString, "BuildHash", "out">;
135
+ fileCount: z.ZodNumber;
136
+ totalSizeBytes: z.ZodNumber;
137
+ activatedAt: z.ZodNumber;
138
+ }, z.core.$strip>;
139
+ type SourceMapManifestResponse = z.infer<typeof SourceMapManifestResponseSchema>;
125
140
 
126
141
  /**
127
142
  * Internal SDK error class with a typed diagnostic code.
@@ -270,11 +285,24 @@ declare function sendInitRequest(config: ResolvedConfig, anonKey: AnonApiKey | n
270
285
  message: string;
271
286
  timestamp: number;
272
287
  }>, signal?: AbortSignal): Promise<SdkInitResponse>;
288
+ /**
289
+ * Result returned by {@link performInit} when the backend reports an
290
+ * account claim transition. `null` means no claim was present.
291
+ */
292
+ interface InitClaimResult {
293
+ claimResult: NonNullable<SdkInitResponse["claimResult"]>;
294
+ }
273
295
  /**
274
296
  * Orchestrates the full init flow: send request, update config, cache result.
275
297
  * This function MUST NOT throw.
298
+ *
299
+ * Returns the claim result when the backend reports an account claim
300
+ * transition, or `null` when no claim result is available (including
301
+ * when init is skipped due to rate-limit backoff, missing API key,
302
+ * or request failure). Callers that do not need claim information
303
+ * can safely ignore the return value.
276
304
  */
277
- declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<void>;
305
+ declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<InitClaimResult | null>;
278
306
  /**
279
307
  * Returns the current capture config from the three-tier fallback chain:
280
308
  * 1. In-memory config from latest init response
@@ -354,9 +382,13 @@ declare class GlasstraceExporter implements SpanExporter {
354
382
  * Enriches a ReadableSpan with all glasstrace.* attributes.
355
383
  * Returns a new ReadableSpan wrapper; the original span is not mutated.
356
384
  *
357
- * External function calls (getSessionId, deriveErrorCategory,
358
- * deriveOrmProvider, classifyFetchTarget) are individually guarded so a
359
- * failure in one does not prevent the remaining attributes from being set.
385
+ * Only {@link SessionManager.getSessionId} is individually guarded because
386
+ * it calls into crypto and schema validation — a session ID failure should
387
+ * not prevent the rest of enrichment. The other helper calls
388
+ * ({@link deriveErrorCategory}, {@link deriveOrmProvider},
389
+ * {@link classifyFetchTarget}) are pure functions on typed string inputs
390
+ * and rely on the outer catch for any unexpected failure.
391
+ *
360
392
  * On total failure, returns the original span unchanged.
361
393
  */
362
394
  private enrichSpan;
@@ -453,6 +485,43 @@ declare function computeBuildHash(maps?: SourceMapEntry[]): Promise<string>;
453
485
  * and file entries. Validates the response against SourceMapUploadResponseSchema.
454
486
  */
455
487
  declare function uploadSourceMaps(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[]): Promise<SourceMapUploadResponse>;
488
+ /** Builds at or above this byte size route to the presigned upload flow. */
489
+ declare const PRESIGNED_THRESHOLD_BYTES = 4500000;
490
+ /** Signature for the blob upload function, injectable for testing. */
491
+ type BlobUploader = (clientToken: string, pathname: string, content: string) => Promise<{
492
+ url: string;
493
+ size: number;
494
+ }>;
495
+ /**
496
+ * Orchestrates the 3-phase presigned upload flow.
497
+ *
498
+ * 1. Requests presigned tokens for all source map files
499
+ * 2. Uploads each file to blob storage with a concurrency limit of 5
500
+ * 3. Submits the manifest to finalize the upload
501
+ *
502
+ * Accepts an optional `blobUploader` for test injection; defaults to
503
+ * {@link uploadToBlob}.
504
+ */
505
+ declare function uploadSourceMapsPresigned(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], blobUploader?: BlobUploader): Promise<SourceMapManifestResponse>;
506
+ /**
507
+ * Options for {@link uploadSourceMapsAuto}, primarily used for test injection.
508
+ */
509
+ interface AutoUploadOptions {
510
+ /** Override blob availability check (for testing). */
511
+ checkBlobAvailable?: () => Promise<boolean>;
512
+ /** Override blob uploader (for testing). */
513
+ blobUploader?: BlobUploader;
514
+ }
515
+ /**
516
+ * Automatically routes source map uploads based on total build size.
517
+ *
518
+ * - Below {@link PRESIGNED_THRESHOLD_BYTES}: uses the legacy single-request
519
+ * {@link uploadSourceMaps} endpoint.
520
+ * - At or above the threshold: checks if `@vercel/blob` is available and
521
+ * uses the presigned 3-phase flow. Falls back to legacy with a warning
522
+ * if the package is not installed.
523
+ */
524
+ declare function uploadSourceMapsAuto(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], options?: AutoUploadOptions): Promise<SourceMapUploadResponse | SourceMapManifestResponse>;
456
525
 
457
526
  /**
458
527
  * Manual error capture API.
@@ -515,4 +584,4 @@ declare function extractImports(fileContent: string): string[];
515
584
  */
516
585
  declare function buildImportGraph(projectRoot: string): Promise<ImportGraphPayload>;
517
586
 
518
- 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 };
587
+ export { type AutoUploadOptions, type BlobUploader, type FetchTarget, GlasstraceExporter, type GlasstraceExporterOptions, GlasstraceSpanProcessor, type InitClaimResult, PRESIGNED_THRESHOLD_BYTES, 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, uploadSourceMapsAuto, uploadSourceMapsPresigned, withGlasstraceConfig };
package/dist/index.js CHANGED
@@ -2,17 +2,19 @@ import {
2
2
  buildImportGraph,
3
3
  discoverTestFiles,
4
4
  extractImports
5
- } from "./chunk-LAMTBURS.js";
5
+ } from "./chunk-SALPGSWK.js";
6
6
  import {
7
7
  DEFAULT_CAPTURE_CONFIG,
8
8
  GLASSTRACE_ATTRIBUTE_NAMES,
9
+ PresignedUploadResponseSchema,
9
10
  SdkCachedConfigSchema,
10
11
  SdkInitResponseSchema,
11
12
  SessionIdSchema,
13
+ SourceMapManifestResponseSchema,
12
14
  SourceMapUploadResponseSchema,
13
15
  getOrCreateAnonKey,
14
16
  readAnonKey
15
- } from "./chunk-EC5IINUT.js";
17
+ } from "./chunk-QW6W4CSA.js";
16
18
  import {
17
19
  INVALID_SPAN_CONTEXT,
18
20
  SamplingDecision,
@@ -281,13 +283,13 @@ async function sendInitRequest(config, anonKey, sdkVersion, importGraph, healthR
281
283
  async function performInit(config, anonKey, sdkVersion) {
282
284
  if (rateLimitBackoff) {
283
285
  rateLimitBackoff = false;
284
- return;
286
+ return null;
285
287
  }
286
288
  try {
287
289
  const effectiveKey = config.apiKey || anonKey;
288
290
  if (!effectiveKey) {
289
291
  console.warn("[glasstrace] No API key available for init request.");
290
- return;
292
+ return null;
291
293
  }
292
294
  const controller = new AbortController();
293
295
  const timeoutId = setTimeout(() => controller.abort(), INIT_TIMEOUT_MS);
@@ -304,45 +306,61 @@ async function performInit(config, anonKey, sdkVersion) {
304
306
  clearTimeout(timeoutId);
305
307
  currentConfig = result;
306
308
  await saveCachedConfig(result);
309
+ if (result.claimResult) {
310
+ try {
311
+ process.stderr.write(
312
+ `[glasstrace] Account claimed! Update GLASSTRACE_API_KEY=${result.claimResult.newApiKey} in your .env file.
313
+ `
314
+ );
315
+ } catch (logErr) {
316
+ console.warn(
317
+ `[glasstrace] Failed to write claim migration message: ${logErr instanceof Error ? logErr.message : String(logErr)}`
318
+ );
319
+ }
320
+ return { claimResult: result.claimResult };
321
+ }
322
+ return null;
307
323
  } catch (err) {
308
324
  clearTimeout(timeoutId);
309
325
  if (err instanceof DOMException && err.name === "AbortError") {
310
326
  console.warn("[glasstrace] ingestion_unreachable: Init request timed out.");
311
- return;
327
+ return null;
312
328
  }
313
329
  const status = err.status;
314
330
  if (status === 401) {
315
331
  console.warn(
316
332
  "[glasstrace] ingestion_auth_failed: Check your GLASSTRACE_API_KEY."
317
333
  );
318
- return;
334
+ return null;
319
335
  }
320
336
  if (status === 429) {
321
337
  console.warn("[glasstrace] ingestion_rate_limited: Backing off.");
322
338
  rateLimitBackoff = true;
323
- return;
339
+ return null;
324
340
  }
325
341
  if (typeof status === "number" && status >= 400) {
326
342
  console.warn(
327
343
  `[glasstrace] Init request failed with status ${status}. Using cached config.`
328
344
  );
329
- return;
345
+ return null;
330
346
  }
331
347
  if (err instanceof Error && err.name === "ZodError") {
332
348
  console.warn(
333
349
  "[glasstrace] Init response failed validation (schema version mismatch?). Using cached config."
334
350
  );
335
- return;
351
+ return null;
336
352
  }
337
353
  console.warn(
338
354
  `[glasstrace] ingestion_unreachable: ${err instanceof Error ? err.message : String(err)}`
339
355
  );
356
+ return null;
340
357
  }
341
358
  } catch (err) {
342
359
  console.warn(
343
360
  `[glasstrace] Unexpected init error: ${err instanceof Error ? err.message : String(err)}`
344
361
  );
345
362
  }
363
+ return null;
346
364
  }
347
365
  function getActiveConfig() {
348
366
  if (currentConfig) {
@@ -452,9 +470,13 @@ var GlasstraceExporter = class {
452
470
  * Enriches a ReadableSpan with all glasstrace.* attributes.
453
471
  * Returns a new ReadableSpan wrapper; the original span is not mutated.
454
472
  *
455
- * External function calls (getSessionId, deriveErrorCategory,
456
- * deriveOrmProvider, classifyFetchTarget) are individually guarded so a
457
- * failure in one does not prevent the remaining attributes from being set.
473
+ * Only {@link SessionManager.getSessionId} is individually guarded because
474
+ * it calls into crypto and schema validation — a session ID failure should
475
+ * not prevent the rest of enrichment. The other helper calls
476
+ * ({@link deriveErrorCategory}, {@link deriveOrmProvider},
477
+ * {@link classifyFetchTarget}) are pure functions on typed string inputs
478
+ * and rely on the outer catch for any unexpected failure.
479
+ *
458
480
  * On total failure, returns the original span unchanged.
459
481
  */
460
482
  enrichSpan(span) {
@@ -497,44 +519,39 @@ var GlasstraceExporter = class {
497
519
  }
498
520
  }
499
521
  const errorMessage = attrs["exception.message"];
500
- if (errorMessage) {
522
+ if (typeof errorMessage === "string") {
501
523
  extra[ATTR.ERROR_MESSAGE] = errorMessage;
502
524
  }
503
- try {
504
- const errorType = attrs["exception.type"];
505
- if (errorType) {
506
- extra[ATTR.ERROR_CODE] = errorType;
507
- extra[ATTR.ERROR_CATEGORY] = deriveErrorCategory(errorType);
508
- }
509
- } catch {
525
+ const errorType = attrs["exception.type"];
526
+ if (typeof errorType === "string") {
527
+ extra[ATTR.ERROR_CODE] = errorType;
528
+ extra[ATTR.ERROR_CATEGORY] = deriveErrorCategory(errorType);
510
529
  }
511
530
  const errorField = attrs["error.field"];
512
- if (errorField) {
531
+ if (typeof errorField === "string") {
513
532
  extra[ATTR.ERROR_FIELD] = errorField;
514
533
  }
515
- try {
516
- const spanAny = span;
517
- const instrumentationName = spanAny.instrumentationScope?.name ?? spanAny.instrumentationLibrary?.name ?? "";
518
- const ormProvider = deriveOrmProvider(instrumentationName);
519
- if (ormProvider) {
520
- extra[ATTR.ORM_PROVIDER] = ormProvider;
521
- const model = attrs["db.sql.table"] ?? attrs["db.prisma.model"];
522
- if (model) {
523
- extra[ATTR.ORM_MODEL] = model;
524
- }
525
- const operation = attrs["db.operation"];
526
- if (operation) {
527
- extra[ATTR.ORM_OPERATION] = operation;
528
- }
534
+ const spanAny = span;
535
+ const instrumentationName = spanAny.instrumentationScope?.name ?? spanAny.instrumentationLibrary?.name ?? "";
536
+ const ormProvider = deriveOrmProvider(instrumentationName);
537
+ if (ormProvider) {
538
+ extra[ATTR.ORM_PROVIDER] = ormProvider;
539
+ const table = attrs["db.sql.table"];
540
+ const prismaModel = attrs["db.prisma.model"];
541
+ const model = typeof table === "string" ? table : typeof prismaModel === "string" ? prismaModel : void 0;
542
+ if (model) {
543
+ extra[ATTR.ORM_MODEL] = model;
529
544
  }
530
- } catch {
531
- }
532
- try {
533
- const url = attrs["http.url"] ?? attrs["url.full"];
534
- if (url && span.kind === SpanKind.CLIENT) {
535
- extra[ATTR.FETCH_TARGET] = classifyFetchTarget(url);
545
+ const operation = attrs["db.operation"];
546
+ if (typeof operation === "string") {
547
+ extra[ATTR.ORM_OPERATION] = operation;
536
548
  }
537
- } catch {
549
+ }
550
+ const httpUrl = attrs["http.url"];
551
+ const fullUrl = attrs["url.full"];
552
+ const url = typeof httpUrl === "string" ? httpUrl : typeof fullUrl === "string" ? fullUrl : void 0;
553
+ if (url && span.kind === SpanKind.CLIENT) {
554
+ extra[ATTR.FETCH_TARGET] = classifyFetchTarget(url);
538
555
  }
539
556
  return createEnrichedSpan(span, extra);
540
557
  } catch {
@@ -3453,6 +3470,14 @@ async function installConsoleCapture() {
3453
3470
  }
3454
3471
  };
3455
3472
  }
3473
+ function sdkLog(level, message) {
3474
+ isGlasstraceLog = true;
3475
+ try {
3476
+ console[level](message);
3477
+ } finally {
3478
+ isGlasstraceLog = false;
3479
+ }
3480
+ }
3456
3481
 
3457
3482
  // src/register.ts
3458
3483
  var consoleCaptureInstalled = false;
@@ -3542,7 +3567,11 @@ function registerGlasstrace(options) {
3542
3567
  if (config.verbose) {
3543
3568
  console.info("[glasstrace] Background init firing.");
3544
3569
  }
3545
- await performInit(config, anonKey, "0.4.1");
3570
+ const initResult = await performInit(config, anonKey, "0.7.0");
3571
+ if (initResult?.claimResult) {
3572
+ setResolvedApiKey(initResult.claimResult.newApiKey);
3573
+ notifyApiKeyResolved();
3574
+ }
3546
3575
  maybeInstallConsoleCapture();
3547
3576
  } catch (err) {
3548
3577
  console.warn(
@@ -3562,7 +3591,11 @@ function registerGlasstrace(options) {
3562
3591
  if (config.verbose) {
3563
3592
  console.info("[glasstrace] Background init firing.");
3564
3593
  }
3565
- await performInit(config, anonKey, "0.4.1");
3594
+ const initResult = await performInit(config, anonKey, "0.7.0");
3595
+ if (initResult?.claimResult) {
3596
+ setResolvedApiKey(initResult.claimResult.newApiKey);
3597
+ notifyApiKeyResolved();
3598
+ }
3566
3599
  maybeInstallConsoleCapture();
3567
3600
  } catch (err) {
3568
3601
  console.warn(
@@ -3584,7 +3617,7 @@ function registerGlasstrace(options) {
3584
3617
  if (config.verbose) {
3585
3618
  console.info("[glasstrace] Background init firing.");
3586
3619
  }
3587
- await performInit(config, anonKeyForInit, "0.4.1");
3620
+ await performInit(config, anonKeyForInit, "0.7.0");
3588
3621
  maybeInstallConsoleCapture();
3589
3622
  } catch (err) {
3590
3623
  console.warn(
@@ -3683,10 +3716,7 @@ async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
3683
3716
  sourceMap: m.content
3684
3717
  }))
3685
3718
  };
3686
- let baseUrl = endpoint;
3687
- while (baseUrl.endsWith("/")) {
3688
- baseUrl = baseUrl.slice(0, -1);
3689
- }
3719
+ const baseUrl = stripTrailingSlashes(endpoint);
3690
3720
  const response = await fetch(`${baseUrl}/v1/source-maps`, {
3691
3721
  method: "POST",
3692
3722
  headers: {
@@ -3707,6 +3737,155 @@ async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
3707
3737
  const json = await response.json();
3708
3738
  return SourceMapUploadResponseSchema.parse(json);
3709
3739
  }
3740
+ var PRESIGNED_THRESHOLD_BYTES = 45e5;
3741
+ function stripTrailingSlashes(url) {
3742
+ let result = url;
3743
+ while (result.endsWith("/")) {
3744
+ result = result.slice(0, -1);
3745
+ }
3746
+ return result;
3747
+ }
3748
+ async function requestPresignedTokens(apiKey, endpoint, buildHash, files) {
3749
+ const baseUrl = stripTrailingSlashes(endpoint);
3750
+ const response = await fetch(`${baseUrl}/v1/source-maps/presign`, {
3751
+ method: "POST",
3752
+ headers: {
3753
+ "Content-Type": "application/json",
3754
+ Authorization: `Bearer ${apiKey}`
3755
+ },
3756
+ body: JSON.stringify({ buildHash, files })
3757
+ });
3758
+ if (!response.ok) {
3759
+ try {
3760
+ await response.text();
3761
+ } catch {
3762
+ }
3763
+ throw new Error(
3764
+ `Presigned token request failed: ${String(response.status)} ${response.statusText}`
3765
+ );
3766
+ }
3767
+ const json = await response.json();
3768
+ return PresignedUploadResponseSchema.parse(json);
3769
+ }
3770
+ async function uploadToBlob(clientToken, pathname, content) {
3771
+ let mod;
3772
+ try {
3773
+ mod = await import("@vercel/blob/client");
3774
+ } catch (err) {
3775
+ const code = err.code;
3776
+ if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
3777
+ throw new Error(
3778
+ "Presigned upload requires @vercel/blob. Install it: npm install @vercel/blob"
3779
+ );
3780
+ }
3781
+ throw err;
3782
+ }
3783
+ const result = await mod.put(pathname, new Blob([content]), {
3784
+ access: "public",
3785
+ token: clientToken
3786
+ });
3787
+ return { url: result.url, size: Buffer.byteLength(content, "utf-8") };
3788
+ }
3789
+ async function submitManifest(apiKey, endpoint, uploadId, buildHash, files) {
3790
+ const baseUrl = stripTrailingSlashes(endpoint);
3791
+ const response = await fetch(`${baseUrl}/v1/source-maps/manifest`, {
3792
+ method: "POST",
3793
+ headers: {
3794
+ "Content-Type": "application/json",
3795
+ Authorization: `Bearer ${apiKey}`
3796
+ },
3797
+ body: JSON.stringify({ uploadId, buildHash, files })
3798
+ });
3799
+ if (!response.ok) {
3800
+ try {
3801
+ await response.text();
3802
+ } catch {
3803
+ }
3804
+ throw new Error(
3805
+ `Source map manifest submission failed: ${String(response.status)} ${response.statusText}`
3806
+ );
3807
+ }
3808
+ const json = await response.json();
3809
+ return SourceMapManifestResponseSchema.parse(json);
3810
+ }
3811
+ async function uploadSourceMapsPresigned(apiKey, endpoint, buildHash, maps, blobUploader = uploadToBlob) {
3812
+ if (maps.length === 0) {
3813
+ throw new Error("No source maps to upload");
3814
+ }
3815
+ const presigned = await requestPresignedTokens(
3816
+ apiKey,
3817
+ endpoint,
3818
+ buildHash,
3819
+ maps.map((m) => ({
3820
+ filePath: m.filePath,
3821
+ sizeBytes: Buffer.byteLength(m.content, "utf-8")
3822
+ }))
3823
+ );
3824
+ const mapsByPath = new Map(maps.map((m) => [m.filePath, m]));
3825
+ if (mapsByPath.size !== maps.length) {
3826
+ throw new Error("Duplicate filePath entries in source maps");
3827
+ }
3828
+ for (const token of presigned.files) {
3829
+ if (!mapsByPath.has(token.filePath)) {
3830
+ throw new Error(
3831
+ `Presigned token for "${token.filePath}" has no matching source map entry`
3832
+ );
3833
+ }
3834
+ }
3835
+ const CONCURRENCY = 5;
3836
+ const uploadResults = [];
3837
+ for (let i = 0; i < presigned.files.length; i += CONCURRENCY) {
3838
+ const chunk = presigned.files.slice(i, i + CONCURRENCY);
3839
+ const chunkResults = await Promise.all(
3840
+ chunk.map(async (token) => {
3841
+ const entry = mapsByPath.get(token.filePath);
3842
+ const result = await blobUploader(token.clientToken, token.pathname, entry.content);
3843
+ return {
3844
+ filePath: token.filePath,
3845
+ sizeBytes: result.size,
3846
+ blobUrl: result.url
3847
+ };
3848
+ })
3849
+ );
3850
+ uploadResults.push(...chunkResults);
3851
+ }
3852
+ return submitManifest(apiKey, endpoint, presigned.uploadId, buildHash, uploadResults);
3853
+ }
3854
+ async function uploadSourceMapsAuto(apiKey, endpoint, buildHash, maps, options) {
3855
+ if (maps.length === 0) {
3856
+ throw new Error("No source maps to upload");
3857
+ }
3858
+ const totalBytes = maps.reduce(
3859
+ (sum, m) => sum + Buffer.byteLength(m.content, "utf-8"),
3860
+ 0
3861
+ );
3862
+ if (totalBytes < PRESIGNED_THRESHOLD_BYTES) {
3863
+ return uploadSourceMaps(apiKey, endpoint, buildHash, maps);
3864
+ }
3865
+ const checkAvailable = options?.checkBlobAvailable ?? (async () => {
3866
+ try {
3867
+ await import("@vercel/blob/client");
3868
+ return true;
3869
+ } catch {
3870
+ return false;
3871
+ }
3872
+ });
3873
+ const blobAvailable = await checkAvailable();
3874
+ if (blobAvailable) {
3875
+ return uploadSourceMapsPresigned(
3876
+ apiKey,
3877
+ endpoint,
3878
+ buildHash,
3879
+ maps,
3880
+ options?.blobUploader
3881
+ );
3882
+ }
3883
+ sdkLog(
3884
+ "warn",
3885
+ `[glasstrace] Build exceeds 4.5MB (${totalBytes} bytes). Install @vercel/blob for presigned uploads to avoid serverless body size limits. Falling back to legacy upload.`
3886
+ );
3887
+ return uploadSourceMaps(apiKey, endpoint, buildHash, maps);
3888
+ }
3710
3889
 
3711
3890
  // src/config-wrapper.ts
3712
3891
  function withGlasstraceConfig(nextConfig) {
@@ -3824,6 +4003,7 @@ function captureError(error) {
3824
4003
  export {
3825
4004
  GlasstraceExporter,
3826
4005
  GlasstraceSpanProcessor,
4006
+ PRESIGNED_THRESHOLD_BYTES,
3827
4007
  SdkError,
3828
4008
  SessionManager,
3829
4009
  buildImportGraph,
@@ -3851,6 +4031,8 @@ export {
3851
4031
  saveCachedConfig,
3852
4032
  sendInitRequest,
3853
4033
  uploadSourceMaps,
4034
+ uploadSourceMapsAuto,
4035
+ uploadSourceMapsPresigned,
3854
4036
  withGlasstraceConfig
3855
4037
  };
3856
4038
  //# sourceMappingURL=index.js.map