@beam-network/sdk 0.6.0 → 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/models.d.ts CHANGED
@@ -1,5 +1,27 @@
1
1
  export type SignedUrlFlow = "signed_url";
2
+ export interface SdkPerformanceSummary {
3
+ gauges?: Record<string, number>;
4
+ milestones?: Record<string, number>;
5
+ unmeasured?: string[];
6
+ detail_dropped?: number;
7
+ schema_version: "sdk-performance/v1" | "sdk-performance/v2";
8
+ measurements: Array<{
9
+ name: string;
10
+ count: number;
11
+ work_ms: number;
12
+ max_ms: number;
13
+ histogram?: number[];
14
+ }>;
15
+ counters: {
16
+ source_signatures: number;
17
+ source_reuses: number;
18
+ route_batches: number;
19
+ source_renewals?: number;
20
+ };
21
+ }
2
22
  export interface BeamClientOptions {
23
+ /** Best effort, at most one bounded summary per route stream. Never awaited. */
24
+ onDiagnostics?: (summary: SdkPerformanceSummary) => void | Promise<void>;
3
25
  apiKey?: string;
4
26
  natsUrl?: string;
5
27
  natsWsUrl?: string;
@@ -141,7 +163,50 @@ export interface IntegrityAuditChallenge {
141
163
  range_bytes: number;
142
164
  chunks: IntegrityAuditChallengeChunk[];
143
165
  }
166
+ export interface TransferPerformance {
167
+ recovery_bandwidth?: {
168
+ excluded_tasks: number;
169
+ accepted_bytes: number;
170
+ };
171
+ deadline?: {
172
+ version: "assignment-deadline/v1";
173
+ decisions: number;
174
+ selected_min_ms: number;
175
+ selected_max_ms: number;
176
+ estimated_max_ms: number;
177
+ sample_count_max: number;
178
+ data_age_max_ms: number | null;
179
+ reasons: Record<string, number>;
180
+ };
181
+ sdk_detail?: SdkPerformanceSummary;
182
+ schema_version: "transfer-performance/v1";
183
+ runtime_epoch: string;
184
+ coverage: "complete" | "restarted";
185
+ elapsed_ms: number;
186
+ /** Work can overlap; these durations must not be summed into elapsed time. */
187
+ measurements: Record<string, {
188
+ count: number;
189
+ work_ms: number;
190
+ max_ms: number;
191
+ }>;
192
+ events: Record<string, {
193
+ count: number;
194
+ first_ms: number;
195
+ last_ms: number;
196
+ }>;
197
+ sdk_counters: {
198
+ source_signatures: number;
199
+ source_reuses: number;
200
+ route_batches: number;
201
+ source_renewals?: number;
202
+ };
203
+ detail_dropped: number;
204
+ unmeasured?: string[];
205
+ measurement_clocks?: Record<string, "runtime" | "sdk" | "fraud-service">;
206
+ sdk_report_received?: boolean;
207
+ }
144
208
  export interface TransferStatusInfo {
209
+ performance?: TransferPerformance | null;
145
210
  transfer_id: string;
146
211
  name?: string;
147
212
  status: string;
@@ -190,6 +255,8 @@ export interface TransferTerminalSignalWaiter {
190
255
  close(): Promise<void>;
191
256
  }
192
257
  export interface S3ProviderConfig {
258
+ /** Physical storage location; independent of the signing region. */
259
+ storage_location?: string;
193
260
  provider: "s3";
194
261
  id?: string;
195
262
  bucket: string;
@@ -201,6 +268,8 @@ export interface S3ProviderConfig {
201
268
  endpoint_url?: string;
202
269
  }
203
270
  export interface R2ProviderConfig {
271
+ /** Physical storage location; independent of the signing region. */
272
+ storage_location?: string;
204
273
  provider: "r2";
205
274
  id?: string;
206
275
  bucket: string;
@@ -211,6 +280,8 @@ export interface R2ProviderConfig {
211
280
  endpoint_url?: string;
212
281
  }
213
282
  export interface S3CompatibleProviderConfig {
283
+ /** Physical storage location; independent of the signing region. */
284
+ storage_location?: string;
214
285
  provider: string;
215
286
  driver?: "s3-compatible";
216
287
  id?: string;
@@ -225,6 +296,8 @@ export interface S3CompatibleProviderConfig {
225
296
  account_id?: string;
226
297
  }
227
298
  export interface HippiusProviderConfig {
299
+ /** Physical storage location; independent of the signing region. */
300
+ storage_location?: string;
228
301
  provider: "hippius";
229
302
  id?: string;
230
303
  bucket: string;
@@ -234,6 +307,8 @@ export interface HippiusProviderConfig {
234
307
  }
235
308
  export type HuggingFaceRepoType = "model" | "dataset" | "space" | "kernel" | "bucket";
236
309
  export interface HuggingFaceProviderConfig {
310
+ /** Physical storage location; independent of the signing region. */
311
+ storage_location?: string;
237
312
  provider: "huggingface";
238
313
  id?: string;
239
314
  /** Namespace and repo or bucket name separated by a slash, for example `org/dataset`. */
@@ -1,3 +1,4 @@
1
+ import type { IntegrityAuditChallenge } from "./models.js";
1
2
  import { type ConnectionOptions } from "nats";
2
3
  import type { MultipartGroupManifest, SignedChunkRoute, TransferTerminalSignalWaiter } from "./models.js";
3
4
  export declare const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v7";
@@ -134,18 +135,28 @@ export declare class BeamTransferControl {
134
135
  private readonly recoveryOperations;
135
136
  private readonly recoveryRequestedEpoch;
136
137
  private readonly runtimeEpochs;
138
+ private readonly runtimeCapabilities;
137
139
  private helloTimer;
138
140
  constructor(options: TransferControlOptions);
139
141
  close(): Promise<void>;
142
+ serveIntegritySigner(transferId: string, handler: (challenge: IntegrityAuditChallenge) => Promise<Record<string, unknown>>): Promise<() => void>;
140
143
  serveRouteRecoverySigner(transferId: string, handler: (payload: RouteRecoverySignRequestPayload) => Promise<RouteRecoverySignReplyPayload>): Promise<() => void>;
141
144
  registerRecoveryLease(lease: TransferRecoveryLease): () => void;
142
- releaseRecoveryLease(transferId: string): void;
143
- continueRecoveryLease(transferId: string): void;
145
+ /**
146
+ * Release a transfer's recovery lease, disposing its retained secrets.
147
+ *
148
+ * With `owner`, release only when that exact lease is still registered, so a
149
+ * fenced-off owner can never release a replacement owner's lease.
150
+ */
151
+ releaseRecoveryLease(transferId: string, owner?: TransferRecoveryLease): void;
152
+ /** Request recovery for the registered lease; with `owner`, only if that lease is still registered. */
153
+ continueRecoveryLease(transferId: string, owner?: TransferRecoveryLease): void;
144
154
  request<T>(messageType: TransferClientMessageType, payload: Record<string, unknown>, options?: LifecycleRequestOptions): Promise<T>;
145
155
  openTerminalSignalWaiter(transferId: string): Promise<TransferTerminalSignalWaiter>;
146
156
  splitRoutesForPayload(messageType: TransferClientMessageType, basePayload: Record<string, unknown>, routes: SignedChunkRoute[]): SignedChunkRoute[][];
147
157
  private connection;
148
158
  private ensureHelloMonitor;
159
+ supportsPerformanceV2(transferId: string): boolean;
149
160
  private pollRuntimeHello;
150
161
  private observeRuntimeEpoch;
151
162
  private recoverLease;
@@ -1,3 +1,4 @@
1
+ import { currentSdkPerformance } from "./performance.js";
1
2
  import { encode, decode } from "@msgpack/msgpack";
2
3
  import { connect } from "nats";
3
4
  export const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v7";
@@ -60,9 +61,12 @@ export class BeamTransferControl {
60
61
  terminalWaitCancellations = new Set();
61
62
  routeRecoverySignerCancellations = new Set();
62
63
  recoveryLeases = new Map();
64
+ // Keyed by lease identity: a replacement owner's recovery must never be
65
+ // coalesced into (and silently dropped behind) a fenced-off owner's loop.
63
66
  recoveryOperations = new Map();
64
67
  recoveryRequestedEpoch = new Map();
65
68
  runtimeEpochs = new Map();
69
+ runtimeCapabilities = new Map();
66
70
  helloTimer = null;
67
71
  constructor(options) {
68
72
  this.apiKey = options.apiKey;
@@ -106,6 +110,58 @@ export class BeamTransferControl {
106
110
  if (nc && !nc.isClosed())
107
111
  await nc.drain();
108
112
  }
113
+ async serveIntegritySigner(transferId, handler) {
114
+ const nc = await this.connection();
115
+ const subject = this.routeRecoverySignSubject(transferId).replace(/route_recovery_sign$/, "integrity_sign");
116
+ let active = true;
117
+ let pending = null;
118
+ let auditId = "";
119
+ let challengeFingerprint = "";
120
+ let awaiting = 0;
121
+ const subscription = nc.subscribe(subject, { callback: (error, message) => {
122
+ if (!active || error || !message.reply)
123
+ return;
124
+ if (awaiting >= 32) {
125
+ message.respond(new TextEncoder().encode('{"retry":true}'));
126
+ return;
127
+ }
128
+ awaiting++;
129
+ void (async () => {
130
+ try {
131
+ const request = JSON.parse(new TextDecoder().decode(message.data));
132
+ if (request.schema_version !== TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION || request.capability !== "integrity-signing/v1"
133
+ || request.producer !== "transfer-runtime" || request.environment !== this.environment || request.key_prefix !== this.keyPrefix
134
+ || request.transfer_id !== transferId || request.challenge?.transfer_id !== transferId || typeof request.request_id !== "string")
135
+ throw new Error("integrity_sign_scope_mismatch");
136
+ const fingerprint = JSON.stringify(request.challenge);
137
+ if (pending && (auditId !== request.challenge.audit_id || challengeFingerprint !== fingerprint))
138
+ throw new Error("integrity_sign_conflicting_audit");
139
+ if (!pending) {
140
+ auditId = request.challenge.audit_id;
141
+ challengeFingerprint = fingerprint;
142
+ pending = handler(request.challenge).finally(() => { pending = null; });
143
+ }
144
+ const payload = await pending;
145
+ if (active)
146
+ message.respond(new TextEncoder().encode(JSON.stringify({ schema_version: TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION,
147
+ capability: "integrity-signing/v1", environment: this.environment, key_prefix: this.keyPrefix,
148
+ transfer_id: transferId, request_id: request.request_id, payload })));
149
+ }
150
+ catch {
151
+ message.respond(new TextEncoder().encode('{"retry":true}'));
152
+ }
153
+ finally {
154
+ awaiting--;
155
+ }
156
+ })();
157
+ } });
158
+ await nc.flush();
159
+ const stop = () => { if (!active)
160
+ return; active = false; subscription.unsubscribe(); this.routeRecoverySignerCancellations.delete(stop); };
161
+ this.routeRecoverySignerCancellations.add(stop);
162
+ void this.pollRuntimeHello(transferShardId(transferId, this.shardCount));
163
+ return stop;
164
+ }
109
165
  async serveRouteRecoverySigner(transferId, handler) {
110
166
  const nc = await this.connection();
111
167
  const subject = this.routeRecoverySignSubject(transferId);
@@ -165,23 +221,20 @@ export class BeamTransferControl {
165
221
  this.ensureHelloMonitor();
166
222
  const shardId = transferShardId(lease.transferId, this.shardCount);
167
223
  void this.pollRuntimeHello(shardId);
168
- return () => {
169
- const current = this.recoveryLeases.get(lease.transferId);
170
- if (current !== lease)
171
- return;
172
- this.recoveryLeases.delete(lease.transferId);
173
- this.recoveryRequestedEpoch.delete(lease.transferId);
174
- lease.disposeSecrets?.();
175
- if (!this.recoveryLeases.size && this.helloTimer) {
176
- clearInterval(this.helloTimer);
177
- this.helloTimer = null;
178
- }
179
- };
224
+ return () => this.releaseRecoveryLease(lease.transferId, lease);
180
225
  }
181
- releaseRecoveryLease(transferId) {
226
+ /**
227
+ * Release a transfer's recovery lease, disposing its retained secrets.
228
+ *
229
+ * With `owner`, release only when that exact lease is still registered, so a
230
+ * fenced-off owner can never release a replacement owner's lease.
231
+ */
232
+ releaseRecoveryLease(transferId, owner) {
182
233
  const lease = this.recoveryLeases.get(transferId);
183
234
  if (!lease)
184
235
  return;
236
+ if (owner && lease !== owner)
237
+ return;
185
238
  this.recoveryLeases.delete(transferId);
186
239
  this.recoveryRequestedEpoch.delete(transferId);
187
240
  lease.disposeSecrets?.();
@@ -190,10 +243,13 @@ export class BeamTransferControl {
190
243
  this.helloTimer = null;
191
244
  }
192
245
  }
193
- continueRecoveryLease(transferId) {
246
+ /** Request recovery for the registered lease; with `owner`, only if that lease is still registered. */
247
+ continueRecoveryLease(transferId, owner) {
194
248
  const lease = this.recoveryLeases.get(transferId);
195
249
  if (!lease || this.closed)
196
250
  return;
251
+ if (owner && lease !== owner)
252
+ return;
197
253
  const shardId = transferShardId(transferId, this.shardCount);
198
254
  const epoch = this.runtimeEpochs.get(shardId);
199
255
  this.recoveryRequestedEpoch.set(transferId, epoch ? `${epoch.runtimeEpoch}:${epoch.transportEpoch}` : `foreground:${randomId()}`);
@@ -223,7 +279,12 @@ export class BeamTransferControl {
223
279
  producer: "sdk",
224
280
  payload
225
281
  };
282
+ const encodeStarted = performance.now();
226
283
  const bytes = encode(envelope);
284
+ if (messageType === "transfer.route_stream.batch") {
285
+ currentSdkPerformance()?.observe("sdk.batch_encode", performance.now() - encodeStarted);
286
+ currentSdkPerformance()?.gauge("batch_bytes_max", bytes.byteLength);
287
+ }
227
288
  if (bytes.byteLength > this.maxPayloadBytes) {
228
289
  throw new Error(`NATS lifecycle request is ${bytes.byteLength} bytes, above maxPayloadBytes=${this.maxPayloadBytes}`);
229
290
  }
@@ -434,9 +495,19 @@ export class BeamTransferControl {
434
495
  }, 5_000);
435
496
  this.helloTimer.unref?.();
436
497
  }
498
+ supportsPerformanceV2(transferId) {
499
+ const value = this.runtimeCapabilities.get(transferShardId(transferId, this.shardCount));
500
+ return !!value && value.until > Date.now() && value.capabilities.includes("sdk-performance/v2");
501
+ }
437
502
  async pollRuntimeHello(shardId) {
438
503
  try {
439
- await this.request("runtime.hello", {}, { idempotencyKey: `runtime:hello:${shardId}`, shardId });
504
+ try {
505
+ const hello = await this.request("runtime.hello", { capabilities: ["integrity-signing/v1", "sdk-performance/v2"] }, { idempotencyKey: `runtime:hello:${shardId}:integrity-signing`, shardId });
506
+ this.runtimeCapabilities.set(shardId, { until: Date.now() + 15_000, capabilities: hello.capabilities ?? [] });
507
+ }
508
+ catch {
509
+ await this.request("runtime.hello", {}, { idempotencyKey: `runtime:hello:${shardId}`, shardId });
510
+ }
440
511
  }
441
512
  catch {
442
513
  // Infinite NATS reconnect continues in the background; the next hello reconciles epochs.
@@ -451,6 +522,7 @@ export class BeamTransferControl {
451
522
  return;
452
523
  if (previous.runtimeEpoch === runtimeEpoch && previous.transportEpoch === transportEpoch)
453
524
  return;
525
+ this.runtimeCapabilities.delete(shardId);
454
526
  if (previous.runtimeEpoch !== runtimeEpoch) {
455
527
  this.authToken = null;
456
528
  this.authTokenExpiresAt = 0;
@@ -463,13 +535,14 @@ export class BeamTransferControl {
463
535
  }
464
536
  }
465
537
  recoverLease(lease) {
466
- const existing = this.recoveryOperations.get(lease.transferId);
538
+ const existing = this.recoveryOperations.get(lease);
467
539
  if (existing)
468
540
  return existing;
469
- let operation;
470
- operation = (async () => {
541
+ const owns = () => !this.closed && this.recoveryLeases.get(lease.transferId) === lease;
542
+ let replaced = false;
543
+ const operation = (async () => {
471
544
  let attempt = 0;
472
- while (!this.closed && this.recoveryLeases.get(lease.transferId) === lease) {
545
+ while (owns()) {
473
546
  const requestedEpoch = this.recoveryRequestedEpoch.get(lease.transferId);
474
547
  const routeGenerationId = randomId();
475
548
  try {
@@ -479,39 +552,53 @@ export class BeamTransferControl {
479
552
  coordinate_checksum: lease.coordinateChecksum,
480
553
  route_generation_id: routeGenerationId
481
554
  }, { transferId: lease.transferId, idempotencyKey: `transfer:${lease.transferId}:resume:${routeGenerationId}` });
555
+ if (!owns()) {
556
+ // A fenced-off owner must neither act on nor release a replacement's lease.
557
+ replaced = true;
558
+ return;
559
+ }
482
560
  if (response.recovery === "terminal") {
483
- this.releaseRecoveryLease(lease.transferId);
484
- if (this.recoveryOperations.get(lease.transferId) === operation) {
485
- this.recoveryOperations.delete(lease.transferId);
486
- }
561
+ this.releaseRecoveryLease(lease.transferId, lease);
487
562
  return;
488
563
  }
489
564
  if (response.route_replay_required || response.recovery === "route_replay_required") {
490
565
  await lease.replayRoutes(routeGenerationId);
566
+ if (!owns()) {
567
+ replaced = true;
568
+ return;
569
+ }
491
570
  }
492
571
  if (this.recoveryRequestedEpoch.get(lease.transferId) !== requestedEpoch) {
493
572
  attempt = 0;
494
573
  continue;
495
574
  }
496
- if (this.recoveryOperations.get(lease.transferId) === operation) {
497
- this.recoveryOperations.delete(lease.transferId);
498
- }
499
575
  return;
500
576
  }
501
577
  catch (error) {
578
+ if (!owns()) {
579
+ replaced = true;
580
+ return;
581
+ }
502
582
  if (!isRetryableLifecycleError(error)) {
503
- this.releaseRecoveryLease(lease.transferId);
583
+ this.releaseRecoveryLease(lease.transferId, lease);
504
584
  return;
505
585
  }
506
586
  attempt += 1;
507
587
  await sleepWithJitter(Math.min(30_000, 500 * (2 ** Math.min(attempt, 6))));
508
588
  }
509
589
  }
590
+ replaced = !this.closed;
510
591
  })().finally(() => {
511
- if (this.recoveryOperations.get(lease.transferId) === operation)
512
- this.recoveryOperations.delete(lease.transferId);
592
+ if (this.recoveryOperations.get(lease) === operation)
593
+ this.recoveryOperations.delete(lease);
594
+ if (!replaced || this.closed)
595
+ return;
596
+ // The recovery this loop was driving still matters: hand it to the replacement owner.
597
+ const current = this.recoveryLeases.get(lease.transferId);
598
+ if (current && current !== lease)
599
+ void this.recoverLease(current);
513
600
  });
514
- this.recoveryOperations.set(lease.transferId, operation);
601
+ this.recoveryOperations.set(lease, operation);
515
602
  return operation;
516
603
  }
517
604
  async resolveAuthToken() {
@@ -0,0 +1,39 @@
1
+ import type { SdkPerformanceSummary } from './models.js';
2
+ export declare const currentSdkPerformance: () => SdkPerformanceCollector | undefined;
3
+ export declare const measureSdkPhase: <T>(name: string, work: () => Promise<T>) => Promise<T>;
4
+ /** Bounded identities only: no URLs, credentials or grant bodies. */
5
+ export declare class SourceSignatureHistory {
6
+ complete: boolean;
7
+ private readonly bits;
8
+ constructor(complete?: boolean);
9
+ created(index: number): boolean;
10
+ }
11
+ export declare class SdkPerformanceCollector {
12
+ private readonly sourceHistory?;
13
+ constructor(sourceHistory?: SourceSignatureHistory | undefined);
14
+ private sourceRenewals;
15
+ sourceCreated(index: number): void;
16
+ private readonly started;
17
+ private readonly details;
18
+ private readonly cpu;
19
+ private finished;
20
+ private delayTimer?;
21
+ private readonly measurements;
22
+ private readonly gauges;
23
+ private readonly milestones;
24
+ private dropped;
25
+ readonly counters: {
26
+ source_signatures: number;
27
+ source_reuses: number;
28
+ route_batches: number;
29
+ };
30
+ observe(name: string, milliseconds: number): void;
31
+ gauge(name: string, value: number): void;
32
+ increment(name: string): void;
33
+ seedDiscovery(other: SdkPerformanceCollector): void;
34
+ mark(name: string): void;
35
+ measure<T>(name: string, work: () => Promise<T>): Promise<T>;
36
+ startDelaySampling(): void;
37
+ finish(): void;
38
+ snapshot(v2?: boolean): SdkPerformanceSummary;
39
+ }
@@ -0,0 +1,124 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ const NAMES = ["sdk.discovery", "sdk.multipart_create", "sdk.signing", "sdk.batch_ack", "sdk.buffer_wait", "sdk.preparation", "sdk.source_signing", "sdk.destination_signing", "sdk.source_grant_wait", "sdk.multipart_ready_wait", "sdk.multipart_provider", "sdk.multipart_queue", "sdk.multipart_callback", "sdk.manifest_ack", "sdk.provider_client_setup", "sdk.metadata_request", "sdk.route_assembly", "sdk.batch_encode", "sdk.batch_checksum", "sdk.batch_queue", "sdk.signing_queue", "sdk.process_cpu", "sdk.event_loop_delay", "sdk.producer_wait"];
3
+ const BOUNDS = [0.1, .5, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000, 120000];
4
+ const GAUGES = new Set(["signing_configured_limit", "multipart_configured_limit", "signing_limit", "multipart_limit", "signing_active_peak", "signing_pending_peak", "multipart_active_peak", "buffered_batches_peak", "batch_routes_max", "batch_bytes_max", "concurrency_changes", "source_waiters", "provider_clients_created", "provider_clients_reused"]);
5
+ const MILESTONES = new Set(["first_manifest_ms", "first_route_ms", "first_batch_ms", "final_flush_ms", "prepared_ms"]);
6
+ const context = new AsyncLocalStorage();
7
+ let activeDelaySamplers = 0;
8
+ export const currentSdkPerformance = () => context.getStore();
9
+ export const measureSdkPhase = (name, work) => context.getStore()?.measure(name, work) ?? work();
10
+ /** Bounded identities only: no URLs, credentials or grant bodies. */
11
+ export class SourceSignatureHistory {
12
+ complete;
13
+ bits = new Uint8Array(16_384);
14
+ constructor(complete = true) {
15
+ this.complete = complete;
16
+ }
17
+ created(index) {
18
+ if (!Number.isSafeInteger(index) || index < 0 || index >= this.bits.length * 8) {
19
+ this.complete = false;
20
+ return false;
21
+ }
22
+ const byte = index >>> 3, mask = 1 << (index & 7);
23
+ const renewed = (this.bits[byte] & mask) !== 0;
24
+ this.bits[byte] |= mask;
25
+ return renewed;
26
+ }
27
+ }
28
+ export class SdkPerformanceCollector {
29
+ sourceHistory;
30
+ constructor(sourceHistory) {
31
+ this.sourceHistory = sourceHistory;
32
+ }
33
+ sourceRenewals = 0;
34
+ sourceCreated(index) {
35
+ this.counters.source_signatures++;
36
+ if (this.sourceHistory?.created(index))
37
+ this.sourceRenewals++;
38
+ }
39
+ started = performance.now();
40
+ details = process.env.BEAM_SDK_PERFORMANCE_DETAILS !== 'false';
41
+ cpu = process.cpuUsage();
42
+ finished = false;
43
+ delayTimer;
44
+ measurements = new Map();
45
+ gauges = {};
46
+ milestones = {};
47
+ dropped = 0;
48
+ counters = { source_signatures: 0, source_reuses: 0, route_batches: 0 };
49
+ observe(name, milliseconds) {
50
+ const index = NAMES.indexOf(name);
51
+ if (index < 0 || (!this.details && index >= 6) || !Number.isFinite(milliseconds) || milliseconds < 0 || milliseconds > 86_400_000)
52
+ return;
53
+ const value = this.measurements.get(name) ?? { count: 0, work_ms: 0, max_ms: 0, histogram: Array(18).fill(0) };
54
+ if (value.count >= 1_000_000 || value.work_ms + milliseconds > 86_400_000) {
55
+ this.dropped++;
56
+ return;
57
+ }
58
+ value.count++;
59
+ value.work_ms += milliseconds;
60
+ value.max_ms = Math.max(value.max_ms, milliseconds);
61
+ const bin = BOUNDS.findIndex(bound => milliseconds <= bound);
62
+ value.histogram[bin < 0 ? 17 : bin]++;
63
+ this.measurements.set(name, value);
64
+ }
65
+ gauge(name, value) {
66
+ if (this.details && GAUGES.has(name) && Number.isFinite(value) && value >= 0)
67
+ this.gauges[name] = Math.min(1_000_000_000, Math.max(this.gauges[name] ?? 0, value));
68
+ }
69
+ increment(name) { this.gauge(name, (this.gauges[name] ?? 0) + 1); }
70
+ seedDiscovery(other) {
71
+ for (const name of ["sdk.discovery", "sdk.metadata_request", "sdk.provider_client_setup"]) {
72
+ const value = other.measurements.get(name);
73
+ if (value)
74
+ this.measurements.set(name, { ...value, histogram: [...value.histogram] });
75
+ }
76
+ for (const name of ["provider_clients_created", "provider_clients_reused"])
77
+ if (other.gauges[name] !== undefined)
78
+ this.gauges[name] = other.gauges[name];
79
+ }
80
+ mark(name) {
81
+ if (this.details && MILESTONES.has(name) && this.milestones[name] === undefined)
82
+ this.milestones[name] = performance.now() - this.started;
83
+ }
84
+ async measure(name, work) {
85
+ const start = performance.now();
86
+ try {
87
+ return await context.run(this, work);
88
+ }
89
+ finally {
90
+ this.observe(name, performance.now() - start);
91
+ }
92
+ }
93
+ startDelaySampling() {
94
+ if (!this.details || this.delayTimer || this.finished || activeDelaySamplers >= 16)
95
+ return;
96
+ activeDelaySamplers++;
97
+ let last = performance.now();
98
+ this.delayTimer = setInterval(() => {
99
+ const now = performance.now();
100
+ this.observe('sdk.event_loop_delay', Math.max(0, now - last - 100));
101
+ last = now;
102
+ }, 100);
103
+ this.delayTimer.unref();
104
+ }
105
+ finish() {
106
+ if (this.finished)
107
+ return;
108
+ this.finished = true;
109
+ if (this.delayTimer) {
110
+ clearInterval(this.delayTimer);
111
+ this.delayTimer = undefined;
112
+ activeDelaySamplers--;
113
+ }
114
+ const used = process.cpuUsage(this.cpu);
115
+ this.observe('sdk.process_cpu', (used.user + used.system) / 1000);
116
+ this.mark('prepared_ms');
117
+ }
118
+ snapshot(v2 = true) {
119
+ const measurements = [...this.measurements].filter(([name]) => v2 || NAMES.slice(0, 6).includes(name))
120
+ .map(([name, value]) => ({ name, count: value.count, work_ms: value.work_ms, max_ms: value.max_ms, ...(v2 ? { histogram: [...value.histogram] } : {}) }));
121
+ return { schema_version: v2 ? 'sdk-performance/v2' : 'sdk-performance/v1', measurements, counters: { ...this.counters, ...(v2 && this.sourceHistory?.complete ? { source_renewals: this.sourceRenewals } : {}) },
122
+ ...(v2 ? { gauges: { ...this.gauges }, milestones: { ...this.milestones }, unmeasured: NAMES.slice(6).filter(name => !this.measurements.has(name)), detail_dropped: this.dropped } : {}) };
123
+ }
124
+ }
@@ -63,7 +63,15 @@ export declare function inspectDestinationObject(destination: ProviderDestinatio
63
63
  versionId: string | undefined;
64
64
  metadata: Record<string, string>;
65
65
  }>;
66
+ export declare function boundedGrantExpiry(url: string, upperBound: string): string;
67
+ export type SourceRouteGrant = {
68
+ url: string;
69
+ headers: Record<string, string>;
70
+ expiresAt: string;
71
+ };
72
+ export declare function signSourceChunk(input: Parameters<typeof signSourceRoute>[0]): Promise<SourceRouteGrant>;
66
73
  export declare function signDestinationRoute(input: {
74
+ sourceGrant?: Promise<SourceRouteGrant>;
67
75
  chunk: ChunkSigningPlanItem;
68
76
  target: ChunkDestinationSigningTarget;
69
77
  source?: ProviderSourceConfig;
@@ -109,6 +117,16 @@ export declare function signDestinationReadRange(input: {
109
117
  url: string;
110
118
  headers: Record<string, string>;
111
119
  }>;
120
+ declare function signSourceRoute(input: {
121
+ source?: ProviderSourceConfig;
122
+ fallbackUrl: string;
123
+ chunk: ChunkSigningPlanItem;
124
+ expiresIn: number;
125
+ fetchImpl?: typeof fetch;
126
+ }): Promise<{
127
+ url: string;
128
+ headers: Record<string, string>;
129
+ }>;
112
130
  export declare function signDestinationUrl(input: {
113
131
  contentMd5?: string;
114
132
  destination: ProviderDestinationConfig;
@@ -118,8 +136,10 @@ export declare function signDestinationUrl(input: {
118
136
  expiresIn: number;
119
137
  fetchImpl?: typeof fetch;
120
138
  }): Promise<string>;
139
+ export declare function releaseProviderClients(configs: Array<ProviderSourceConfig | ProviderDestinationConfig>): void;
121
140
  export declare function isS3CompatibleProvider(config: ProviderSourceConfig | ProviderDestinationConfig): config is AnyS3CompatibleProviderConfig;
122
141
  export declare function s3CompatibleEndpoint(source: AnyS3CompatibleProviderConfig): string | undefined;
123
142
  export declare function s3CompatibleRegion(source: AnyS3CompatibleProviderConfig): string;
124
143
  export declare function s3CompatibleForcePathStyle(source: AnyS3CompatibleProviderConfig, endpoint?: string | undefined): boolean | undefined;
125
144
  export declare function isHuggingFaceProvider(config: ProviderSourceConfig | ProviderDestinationConfig): config is HuggingFaceProviderConfig;
145
+ export {};