@glasstrace/sdk 0.4.2 → 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
@@ -457,6 +485,43 @@ declare function computeBuildHash(maps?: SourceMapEntry[]): Promise<string>;
457
485
  * and file entries. Validates the response against SourceMapUploadResponseSchema.
458
486
  */
459
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>;
460
525
 
461
526
  /**
462
527
  * Manual error capture API.
@@ -519,4 +584,4 @@ declare function extractImports(fileContent: string): string[];
519
584
  */
520
585
  declare function buildImportGraph(projectRoot: string): Promise<ImportGraphPayload>;
521
586
 
522
- 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
@@ -457,6 +485,43 @@ declare function computeBuildHash(maps?: SourceMapEntry[]): Promise<string>;
457
485
  * and file entries. Validates the response against SourceMapUploadResponseSchema.
458
486
  */
459
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>;
460
525
 
461
526
  /**
462
527
  * Manual error capture API.
@@ -519,4 +584,4 @@ declare function extractImports(fileContent: string): string[];
519
584
  */
520
585
  declare function buildImportGraph(projectRoot: string): Promise<ImportGraphPayload>;
521
586
 
522
- 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-67H2JEI4.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-TVOYTJ7I.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) {
@@ -3452,6 +3470,14 @@ async function installConsoleCapture() {
3452
3470
  }
3453
3471
  };
3454
3472
  }
3473
+ function sdkLog(level, message) {
3474
+ isGlasstraceLog = true;
3475
+ try {
3476
+ console[level](message);
3477
+ } finally {
3478
+ isGlasstraceLog = false;
3479
+ }
3480
+ }
3455
3481
 
3456
3482
  // src/register.ts
3457
3483
  var consoleCaptureInstalled = false;
@@ -3541,7 +3567,11 @@ function registerGlasstrace(options) {
3541
3567
  if (config.verbose) {
3542
3568
  console.info("[glasstrace] Background init firing.");
3543
3569
  }
3544
- await performInit(config, anonKey, "0.4.2");
3570
+ const initResult = await performInit(config, anonKey, "0.7.0");
3571
+ if (initResult?.claimResult) {
3572
+ setResolvedApiKey(initResult.claimResult.newApiKey);
3573
+ notifyApiKeyResolved();
3574
+ }
3545
3575
  maybeInstallConsoleCapture();
3546
3576
  } catch (err) {
3547
3577
  console.warn(
@@ -3561,7 +3591,11 @@ function registerGlasstrace(options) {
3561
3591
  if (config.verbose) {
3562
3592
  console.info("[glasstrace] Background init firing.");
3563
3593
  }
3564
- await performInit(config, anonKey, "0.4.2");
3594
+ const initResult = await performInit(config, anonKey, "0.7.0");
3595
+ if (initResult?.claimResult) {
3596
+ setResolvedApiKey(initResult.claimResult.newApiKey);
3597
+ notifyApiKeyResolved();
3598
+ }
3565
3599
  maybeInstallConsoleCapture();
3566
3600
  } catch (err) {
3567
3601
  console.warn(
@@ -3583,7 +3617,7 @@ function registerGlasstrace(options) {
3583
3617
  if (config.verbose) {
3584
3618
  console.info("[glasstrace] Background init firing.");
3585
3619
  }
3586
- await performInit(config, anonKeyForInit, "0.4.2");
3620
+ await performInit(config, anonKeyForInit, "0.7.0");
3587
3621
  maybeInstallConsoleCapture();
3588
3622
  } catch (err) {
3589
3623
  console.warn(
@@ -3682,10 +3716,7 @@ async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
3682
3716
  sourceMap: m.content
3683
3717
  }))
3684
3718
  };
3685
- let baseUrl = endpoint;
3686
- while (baseUrl.endsWith("/")) {
3687
- baseUrl = baseUrl.slice(0, -1);
3688
- }
3719
+ const baseUrl = stripTrailingSlashes(endpoint);
3689
3720
  const response = await fetch(`${baseUrl}/v1/source-maps`, {
3690
3721
  method: "POST",
3691
3722
  headers: {
@@ -3706,6 +3737,155 @@ async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
3706
3737
  const json = await response.json();
3707
3738
  return SourceMapUploadResponseSchema.parse(json);
3708
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
+ }
3709
3889
 
3710
3890
  // src/config-wrapper.ts
3711
3891
  function withGlasstraceConfig(nextConfig) {
@@ -3823,6 +4003,7 @@ function captureError(error) {
3823
4003
  export {
3824
4004
  GlasstraceExporter,
3825
4005
  GlasstraceSpanProcessor,
4006
+ PRESIGNED_THRESHOLD_BYTES,
3826
4007
  SdkError,
3827
4008
  SessionManager,
3828
4009
  buildImportGraph,
@@ -3850,6 +4031,8 @@ export {
3850
4031
  saveCachedConfig,
3851
4032
  sendInitRequest,
3852
4033
  uploadSourceMaps,
4034
+ uploadSourceMapsAuto,
4035
+ uploadSourceMapsPresigned,
3853
4036
  withGlasstraceConfig
3854
4037
  };
3855
4038
  //# sourceMappingURL=index.js.map