@beam-network/sdk 0.3.0 → 0.4.1

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/client.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare class BeamClient {
12
12
  private readonly fetchImpl;
13
13
  private readonly control;
14
14
  private readonly routeSigningConcurrency;
15
+ private readonly routeSigningConcurrencyOverridden;
15
16
  constructor(options?: BeamClientOptions);
16
17
  close(): Promise<void>;
17
18
  openTransferTerminalWaiter(transferId: string): Promise<TransferTerminalSignalWaiter>;
package/dist/client.js CHANGED
@@ -18,6 +18,7 @@ export class BeamClient {
18
18
  fetchImpl;
19
19
  control;
20
20
  routeSigningConcurrency;
21
+ routeSigningConcurrencyOverridden;
21
22
  constructor(options = {}) {
22
23
  if (!options.apiKey?.trim()) {
23
24
  throw new Error("apiKey is required.");
@@ -25,6 +26,7 @@ export class BeamClient {
25
26
  this.apiKey = options.apiKey;
26
27
  this.natsUrl = options.natsUrl ?? options.natsWsUrl ?? BEAM_DEFAULT_NATS_URL;
27
28
  this.routeSigningConcurrency = positiveInteger(options.routeSigningConcurrency ?? 64, "routeSigningConcurrency");
29
+ this.routeSigningConcurrencyOverridden = options.routeSigningConcurrency !== undefined;
28
30
  this.control = new BeamTransferControl({
29
31
  apiKey: this.apiKey,
30
32
  natsUrl: options.natsUrl,
@@ -148,12 +150,14 @@ export class BeamClient {
148
150
  totalRoutes: orderedRoutes.length,
149
151
  totalChunks: countDistinctRouteChunks(orderedRoutes),
150
152
  urlsExpiresAt: input.urlsExpiresAt,
151
- multipartGroupManifest: input.multipartGroupManifest,
152
153
  autoDistribute: input.autoDistribute ?? true
153
154
  });
154
155
  await stream.begin();
156
+ if (input.multipartGroupManifest.length) {
157
+ await stream.addManifestGroups(input.multipartGroupManifest);
158
+ }
155
159
  for (const route of orderedRoutes) {
156
- stream.addRoute(route);
160
+ await stream.addRoute(route);
157
161
  }
158
162
  return stream.complete();
159
163
  }
@@ -196,17 +200,14 @@ export class BeamClient {
196
200
  });
197
201
  const multipartUploads = new Map();
198
202
  const pendingRoutes = new Set();
203
+ let signingConcurrency = this.routeSigningConcurrency;
204
+ let signedInWindow = 0;
205
+ let signingWindowStartedAt = performance.now();
199
206
  let routeStream = null;
200
207
  let routeStreamBeginAttempted = false;
208
+ let multipartManifestTask = null;
209
+ let multipartGroupWaiters = new Map();
201
210
  try {
202
- const multipartGroupManifest = await createMultipartGroupManifest({
203
- prepared,
204
- destinationsById,
205
- multipartUploads,
206
- expiresIn,
207
- concurrency: this.routeSigningConcurrency
208
- });
209
- validateMultipartGroupManifest(multipartGroupManifest, prepared.transfer_id);
210
211
  const streamId = await stableRouteStreamId({
211
212
  transferId: prepared.transfer_id,
212
213
  planIdentity: prepared.plan_descriptor.plan_nonce,
@@ -218,15 +219,37 @@ export class BeamClient {
218
219
  transferId: prepared.transfer_id,
219
220
  totalRoutes: prepared.plan_descriptor.delivery_route_count,
220
221
  totalChunks: prepared.plan_descriptor.logical_chunk_count,
221
- multipartGroupManifest,
222
222
  autoDistribute: input.distribute !== false
223
223
  });
224
224
  routeStreamBeginAttempted = true;
225
225
  await routeStream.begin();
226
+ multipartGroupWaiters = createMultipartGroupWaiters(prepared, destinationsById);
227
+ multipartManifestTask = createMultipartGroupManifest({
228
+ prepared,
229
+ destinationsById,
230
+ multipartUploads,
231
+ expiresIn,
232
+ concurrency: this.routeSigningConcurrency,
233
+ onGroupReady: async (state) => {
234
+ validateMultipartGroupManifest([state.manifest], prepared.transfer_id);
235
+ await routeStream.addManifestGroups([state.manifest]);
236
+ multipartGroupWaiters.get(state.manifest.multipart_group_id)?.resolve(state);
237
+ },
238
+ onGroupFailed: (groupId, error) => multipartGroupWaiters.get(groupId)?.reject(error)
239
+ });
226
240
  const streamNextCompletedRoute = async () => {
227
241
  const settled = await Promise.race([...pendingRoutes].map((pending) => pending.then((route) => ({ pending, route }))));
228
242
  pendingRoutes.delete(settled.pending);
229
- routeStream.addRoute(settled.route);
243
+ await routeStream.addRoute(settled.route);
244
+ signedInWindow += 1;
245
+ if (signedInWindow === ROUTE_STREAM_BATCH_ROUTES) {
246
+ const elapsedMs = performance.now() - signingWindowStartedAt;
247
+ if (!this.routeSigningConcurrencyOverridden && elapsedMs > 4_000 && signingConcurrency < 256) {
248
+ signingConcurrency = Math.min(256, signingConcurrency * 2);
249
+ }
250
+ signedInWindow = 0;
251
+ signingWindowStartedAt = performance.now();
252
+ }
230
253
  };
231
254
  for (const chunk of materializePlanChunks(prepared.plan_descriptor, prepared.transfer_id)) {
232
255
  await input.throwIfCancelled?.(prepared.transfer_id);
@@ -243,10 +266,12 @@ export class BeamClient {
243
266
  const finalObjectKey = typeof metadata.final_object_key === "string" ? metadata.final_object_key : target.object_key;
244
267
  if (!finalObjectKey)
245
268
  throw new Error("destination signing target is missing object_key");
246
- const upload = multipartUploads.get(multipartGroupStateKey(prepared.transfer_id, target.destination_id, chunk.source_id, finalObjectKey));
247
- if (!isHippiusDestination(destination) && !upload) {
269
+ const multipartGroupId = multipartGroupStateKey(prepared.transfer_id, target.destination_id, chunk.source_id, finalObjectKey);
270
+ const upload = isHippiusDestination(destination)
271
+ ? undefined
272
+ : await multipartGroupWaiters.get(multipartGroupId)?.promise;
273
+ if (!isHippiusDestination(destination) && !upload)
248
274
  throw new Error(`multipart group manifest is missing for ${chunk.source_id}:${target.destination_id}`);
249
- }
250
275
  return this.signProviderRoute({
251
276
  chunk,
252
277
  target,
@@ -262,12 +287,14 @@ export class BeamClient {
262
287
  });
263
288
  })();
264
289
  pendingRoutes.add(pendingRoute);
265
- if (pendingRoutes.size >= this.routeSigningConcurrency)
290
+ if (pendingRoutes.size >= signingConcurrency)
266
291
  await streamNextCompletedRoute();
267
292
  }
268
293
  }
269
294
  while (pendingRoutes.size)
270
295
  await streamNextCompletedRoute();
296
+ const multipartGroupManifest = await multipartManifestTask;
297
+ validateMultipartGroupManifest(multipartGroupManifest, prepared.transfer_id);
271
298
  const attached = await routeStream.complete();
272
299
  if (!attached.success) {
273
300
  throw new Error(attached.error ?? attached.message ?? "route stream failed");
@@ -277,6 +304,8 @@ export class BeamClient {
277
304
  if (routeStream)
278
305
  await routeStream.abort();
279
306
  await Promise.allSettled(pendingRoutes);
307
+ if (multipartManifestTask)
308
+ await Promise.allSettled([multipartManifestTask]);
280
309
  await cancelAndAbortProviderFailure({
281
310
  client: this,
282
311
  transferId: prepared.transfer_id,
@@ -423,16 +452,14 @@ export class BeamClient {
423
452
  }
424
453
  }
425
454
  }
426
- const ROUTE_STREAM_BOOTSTRAP_BATCH_ROUTES = 32;
427
- const ROUTE_STREAM_STEADY_BATCH_ROUTES = 256;
455
+ const ROUTE_STREAM_BATCH_ROUTES = 2_048;
428
456
  class RouteStreamSender {
429
457
  control;
430
458
  options;
431
459
  streamId;
432
460
  checksum = new RouteKeysChecksum();
433
461
  batch = [];
434
- pendingRoutes = new Map();
435
- nextDeliveryIndex = 0;
462
+ seenDeliveryIndices = new Set();
436
463
  batchIndex = 0;
437
464
  routeCount = 0;
438
465
  sendTail = Promise.resolve();
@@ -449,7 +476,6 @@ class RouteStreamSender {
449
476
  total_routes: this.options.totalRoutes,
450
477
  total_chunks: this.options.totalChunks,
451
478
  route_contract_version: "signed_url_v2",
452
- multipart_group_manifest: this.options.multipartGroupManifest,
453
479
  urls_expires_at: this.options.urlsExpiresAt,
454
480
  signed_url_flow: "signed_url_v2",
455
481
  auto_distribute: this.options.autoDistribute
@@ -458,7 +484,23 @@ class RouteStreamSender {
458
484
  idempotencyKey: `transfer:${this.options.transferId}:route-stream:${this.streamId}:begin`
459
485
  });
460
486
  }
461
- addRoute(route) {
487
+ async addManifestGroups(groups) {
488
+ if (!groups.length)
489
+ return;
490
+ await Promise.all(groups.map(async (group) => {
491
+ const identity = await stableManifestBatchIdentity([group]);
492
+ await this.control.request("transfer.route_stream.manifest", {
493
+ transfer_id: this.options.transferId,
494
+ stream_id: this.streamId,
495
+ manifest_batch_id: identity,
496
+ groups: [group]
497
+ }, {
498
+ transferId: this.options.transferId,
499
+ idempotencyKey: `transfer:${this.options.transferId}:route-stream:${this.streamId}:manifest:${identity}`
500
+ });
501
+ }));
502
+ }
503
+ async addRoute(route) {
462
504
  const deliveryIndex = signedRouteDeliveryIndex(route);
463
505
  if (deliveryIndex === undefined) {
464
506
  throw new Error("route delivery_index is required");
@@ -466,31 +508,22 @@ class RouteStreamSender {
466
508
  if (!Number.isSafeInteger(deliveryIndex) || deliveryIndex < 0 || deliveryIndex >= this.options.totalRoutes) {
467
509
  throw new Error(`route delivery_index ${deliveryIndex} is outside the declared route stream`);
468
510
  }
469
- if (deliveryIndex < this.nextDeliveryIndex || this.pendingRoutes.has(deliveryIndex)) {
511
+ if (this.seenDeliveryIndices.has(deliveryIndex)) {
470
512
  throw new Error(`duplicate route delivery_index ${deliveryIndex}`);
471
513
  }
472
- this.pendingRoutes.set(deliveryIndex, route);
514
+ this.seenDeliveryIndices.add(deliveryIndex);
473
515
  this.routeCount += 1;
474
- while (this.pendingRoutes.has(this.nextDeliveryIndex)) {
475
- const next = this.pendingRoutes.get(this.nextDeliveryIndex);
476
- this.pendingRoutes.delete(this.nextDeliveryIndex);
477
- this.batch.push({ ...next, delivery_index: this.nextDeliveryIndex });
478
- this.nextDeliveryIndex += 1;
479
- const target = this.batchIndex === 0
480
- ? ROUTE_STREAM_BOOTSTRAP_BATCH_ROUTES
481
- : ROUTE_STREAM_STEADY_BATCH_ROUTES;
482
- if (this.batch.length >= target) {
483
- this.enqueueFlush();
484
- }
516
+ this.batch.push({ ...route, delivery_index: deliveryIndex });
517
+ if (this.batch.length >= ROUTE_STREAM_BATCH_ROUTES) {
518
+ await this.enqueueFlush();
485
519
  }
486
520
  }
487
521
  async complete() {
488
522
  if (this.routeCount !== this.options.totalRoutes ||
489
- this.pendingRoutes.size !== 0 ||
490
- this.nextDeliveryIndex !== this.options.totalRoutes) {
491
- throw new Error(`route stream has non-contiguous delivery indices: received ${this.routeCount} of ${this.options.totalRoutes} routes`);
523
+ this.seenDeliveryIndices.size !== this.options.totalRoutes) {
524
+ throw new Error(`route stream has incomplete or duplicate delivery indices: received ${this.routeCount} of ${this.options.totalRoutes} routes`);
492
525
  }
493
- this.enqueueFlush();
526
+ await this.enqueueFlush();
494
527
  await this.sendTail;
495
528
  if (this.sendError)
496
529
  throw this.sendError;
@@ -505,9 +538,12 @@ class RouteStreamSender {
505
538
  idempotencyKey: `transfer:${this.options.transferId}:route-stream:${this.streamId}:complete`
506
539
  });
507
540
  }
508
- enqueueFlush() {
541
+ async enqueueFlush() {
509
542
  if (!this.batch.length)
510
543
  return;
544
+ await this.sendTail;
545
+ if (this.sendError)
546
+ throw this.sendError;
511
547
  const routes = this.batch.splice(0, this.batch.length);
512
548
  const chunks = this.control.splitRoutesForPayload("transfer.route_stream.batch", {
513
549
  transfer_id: this.options.transferId,
@@ -604,6 +640,12 @@ async function stableRouteStreamId(input) {
604
640
  const value = hex(bytes);
605
641
  return `${value.slice(0, 8)}-${value.slice(8, 12)}-${value.slice(12, 16)}-${value.slice(16, 20)}-${value.slice(20)}`;
606
642
  }
643
+ async function stableManifestBatchIdentity(groups) {
644
+ const identities = groups
645
+ .map((group) => `${group.multipart_group_id}:${group.source_id}:${group.destination_id}`)
646
+ .sort();
647
+ return hex((await sha256Bytes(identities.join("\n"))).slice(0, 16));
648
+ }
607
649
  function countDistinctRouteChunks(routes) {
608
650
  return new Set(routes.map((route) => `${route.source_id}:${route.chunk_index}`)).size;
609
651
  }
@@ -704,7 +746,7 @@ async function cancelTransferAfterProviderFailure(client, transferId, cause) {
704
746
  }
705
747
  }
706
748
  catch (cancelError) {
707
- throw new Error(`provider transfer failed (${errorMessage(cause)}) and transfer cancellation failed (${errorMessage(cancelError)})`);
749
+ throw new Error(`provider transfer failed (${safeErrorCode(cause)}) and transfer cancellation failed (${safeErrorCode(cancelError)})`);
708
750
  }
709
751
  }
710
752
  async function cancelAndAbortProviderFailure(input) {
@@ -746,12 +788,46 @@ async function cancelAndAbortProviderFailure(input) {
746
788
  throw new AggregateError([input.cause, cleanupError], `provider transfer failed and multipart cleanup failed for ${input.transferId}`);
747
789
  }
748
790
  }
749
- function errorMessage(error) {
750
- return error instanceof Error ? error.message : String(error);
791
+ function safeErrorCode(error) {
792
+ if (!(error instanceof Error))
793
+ return "UnknownError";
794
+ const record = error;
795
+ const code = typeof record.code === "string" && /^[A-Za-z0-9_.-]{1,64}$/.test(record.code)
796
+ ? record.code
797
+ : error.name;
798
+ const status = [record.status, record.statusCode]
799
+ .find((value) => Number.isInteger(value) && Number(value) >= 100 && Number(value) <= 599);
800
+ return status === undefined ? code : `${code}:status=${status}`;
801
+ }
802
+ function createDeferred() {
803
+ let resolve;
804
+ let reject;
805
+ const promise = new Promise((resolvePromise, rejectPromise) => {
806
+ resolve = resolvePromise;
807
+ reject = rejectPromise;
808
+ });
809
+ return { promise, resolve, reject };
810
+ }
811
+ function createMultipartGroupWaiters(prepared, destinationsById) {
812
+ const waiters = new Map();
813
+ for (const source of prepared.plan_descriptor.sources) {
814
+ for (const destination of prepared.plan_descriptor.destinations) {
815
+ const destinationConfig = destinationsById.get(destination.destination_id);
816
+ if (!destinationConfig || isHippiusDestination(destinationConfig))
817
+ continue;
818
+ const finalObjectKey = destination.final_object_keys[source.source_id];
819
+ if (!finalObjectKey)
820
+ throw new Error(`plan final object key not found: ${source.source_id}:${destination.destination_id}`);
821
+ const groupId = multipartGroupStateKey(prepared.transfer_id, destination.destination_id, source.source_id, finalObjectKey);
822
+ waiters.set(groupId, createDeferred());
823
+ }
824
+ }
825
+ return waiters;
751
826
  }
752
827
  async function createMultipartGroupManifest(input) {
753
828
  const groups = input.prepared.plan_descriptor.sources.flatMap((source) => input.prepared.plan_descriptor.destinations.map((destination) => ({ source, destination })));
754
829
  const results = await mapOrderedWithConcurrency(groups, input.concurrency, async ({ source, destination }) => {
830
+ let multipartGroupId = null;
755
831
  try {
756
832
  const destinationConfig = input.destinationsById.get(destination.destination_id);
757
833
  if (!destinationConfig) {
@@ -764,7 +840,7 @@ async function createMultipartGroupManifest(input) {
764
840
  if (!finalObjectKey) {
765
841
  throw new Error(`plan final object key not found: ${source.source_id}:${destination.destination_id}`);
766
842
  }
767
- const multipartGroupId = multipartGroupStateKey(input.prepared.transfer_id, destination.destination_id, source.source_id, finalObjectKey);
843
+ multipartGroupId = multipartGroupStateKey(input.prepared.transfer_id, destination.destination_id, source.source_id, finalObjectKey);
768
844
  const finalObjectMetadata = {
769
845
  "beam-transfer-id": input.prepared.transfer_id,
770
846
  "beam-multipart-group-id": multipartGroupId
@@ -825,6 +901,7 @@ async function createMultipartGroupManifest(input) {
825
901
  manifest
826
902
  };
827
903
  input.multipartUploads.set(multipartGroupId, state);
904
+ await input.onGroupReady(state);
828
905
  return { ok: true, state };
829
906
  }
830
907
  catch (error) {
@@ -838,6 +915,8 @@ async function createMultipartGroupManifest(input) {
838
915
  }
839
916
  }
840
917
  catch (error) {
918
+ if (multipartGroupId)
919
+ input.onGroupFailed(multipartGroupId, error);
841
920
  return { ok: false, error };
842
921
  }
843
922
  });
package/dist/models.d.ts CHANGED
@@ -122,22 +122,30 @@ export interface TransferStatusInfo {
122
122
  started_at: string | null;
123
123
  completed_at: string | null;
124
124
  runtime?: boolean;
125
- total_bytes?: number;
126
- total_size?: number;
127
- total_chunks?: number;
128
- chunks_completed?: number;
129
- total_destinations?: number;
130
- destinations_completed?: number;
125
+ source_bytes_total: number;
126
+ delivery_bytes_total: number;
127
+ delivery_bytes_completed: number;
128
+ delivery_tasks_total: number;
129
+ delivery_tasks_completed: number;
130
+ destinations_total: number;
131
+ destinations_completed: number;
132
+ destination_progress: Array<{
133
+ destination_id: string;
134
+ delivery_bytes_total: number;
135
+ delivery_bytes_completed: number;
136
+ completion_verified: boolean;
137
+ }>;
131
138
  destination_groups?: {
132
139
  totalGroups: number;
133
140
  completedGroups: number;
134
141
  pendingGroups: number;
135
142
  totalDestinations: number;
136
143
  completedDestinations: number;
144
+ completedDestinationIds: string[];
137
145
  };
138
146
  }
139
147
  export interface TransferTerminalEvent {
140
- schema_version: "transfer-client-control/v3";
148
+ schema_version: "transfer-client-control/v4";
141
149
  producer: "transfer-runtime";
142
150
  transfer_id: string;
143
151
  status: "completed" | "failed" | "cancelled";
@@ -1,9 +1,9 @@
1
1
  import type { MultipartGroupManifest, SignedChunkRoute, TransferTerminalSignalWaiter } from "./models.js";
2
- export declare const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v3";
2
+ export declare const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v4";
3
3
  export declare const BEAM_DEFAULT_NATS_URL = "tls://nats.b1m.ai:4222";
4
4
  export declare const BEAM_DEFAULT_NATS_WS_URL = "wss://nats.b1m.ai:443";
5
5
  export declare const BEAM_DEFAULT_MAX_PAYLOAD_BYTES: number;
6
- export type TransferClientMessageType = "transfer.plan" | "transfer.create" | "transfer.prepare" | "transfer.route_stream.begin" | "transfer.route_stream.batch" | "transfer.route_stream.complete" | "transfer.distribute" | "transfer.status" | "transfer.cancel";
6
+ export type TransferClientMessageType = "transfer.plan" | "transfer.create" | "transfer.prepare" | "transfer.route_stream.begin" | "transfer.route_stream.manifest" | "transfer.route_stream.batch" | "transfer.route_stream.complete" | "transfer.distribute" | "transfer.status" | "transfer.cancel";
7
7
  export interface CompactSignedRouteBatch {
8
8
  source_chunks: Array<{
9
9
  source_ref: number;
@@ -32,11 +32,16 @@ export interface RouteStreamBeginPayload {
32
32
  total_routes: number;
33
33
  total_chunks: number;
34
34
  route_contract_version: "signed_url_v2";
35
- multipart_group_manifest: MultipartGroupManifest[];
36
35
  urls_expires_at?: string;
37
36
  signed_url_flow: "signed_url_v2";
38
37
  auto_distribute: boolean;
39
38
  }
39
+ export interface RouteStreamManifestPayload {
40
+ transfer_id: string;
41
+ stream_id: string;
42
+ manifest_batch_id: string;
43
+ groups: MultipartGroupManifest[];
44
+ }
40
45
  export interface TransferControlOptions {
41
46
  apiKey: string;
42
47
  natsUrl?: string;
@@ -1,6 +1,6 @@
1
1
  import { encode, decode } from "@msgpack/msgpack";
2
2
  import { connect } from "nats";
3
- export const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v3";
3
+ export const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v4";
4
4
  export const BEAM_DEFAULT_NATS_URL = "tls://nats.b1m.ai:4222";
5
5
  export const BEAM_DEFAULT_NATS_WS_URL = "wss://nats.b1m.ai:443";
6
6
  // NATS enforces max_payload per message. This guard splits signed-route control
@@ -214,7 +214,7 @@ export class BeamTransferControl {
214
214
  }
215
215
  }
216
216
  splitRoutesForPayload(messageType, basePayload, routes) {
217
- const split = (candidate) => {
217
+ const encodedBytes = (candidate) => {
218
218
  const encoded = encode({
219
219
  schema_version: TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION,
220
220
  environment: this.environment,
@@ -227,15 +227,31 @@ export class BeamTransferControl {
227
227
  producer: "sdk",
228
228
  payload: { ...basePayload, route_batch: compactSignedRoutes(candidate) }
229
229
  });
230
- if (encoded.byteLength <= this.maxPayloadBytes)
231
- return [candidate];
232
- if (candidate.length === 1) {
233
- throw new Error(`single signed route is ${encoded.byteLength} bytes, above maxPayloadBytes=${this.maxPayloadBytes}`);
234
- }
235
- const midpoint = Math.ceil(candidate.length / 2);
236
- return [...split(candidate.slice(0, midpoint)), ...split(candidate.slice(midpoint))];
230
+ return encoded.byteLength;
237
231
  };
238
- return routes.length ? split(routes) : [];
232
+ const chunks = [];
233
+ let offset = 0;
234
+ while (offset < routes.length) {
235
+ let low = 1;
236
+ let high = routes.length - offset;
237
+ let accepted = 0;
238
+ while (low <= high) {
239
+ const count = Math.floor((low + high) / 2);
240
+ if (encodedBytes(routes.slice(offset, offset + count)) <= this.maxPayloadBytes) {
241
+ accepted = count;
242
+ low = count + 1;
243
+ }
244
+ else
245
+ high = count - 1;
246
+ }
247
+ if (accepted === 0) {
248
+ const bytes = encodedBytes(routes.slice(offset, offset + 1));
249
+ throw new Error(`single signed route is ${bytes} bytes, above maxPayloadBytes=${this.maxPayloadBytes}`);
250
+ }
251
+ chunks.push(routes.slice(offset, offset + accepted));
252
+ offset += accepted;
253
+ }
254
+ return chunks;
239
255
  }
240
256
  async connection() {
241
257
  if (this.closed)
@@ -384,7 +384,8 @@ function s3CompatibleMetadata(source, endpoint = s3CompatibleEndpoint(source)) {
384
384
  bucket: source.bucket,
385
385
  key: source.key,
386
386
  region: s3CompatibleRegion(source),
387
- endpoint_url: endpoint
387
+ endpoint_url: endpoint,
388
+ account_id: "account_id" in source ? source.account_id : undefined
388
389
  });
389
390
  }
390
391
  function isHippiusProvider(config) {
@@ -397,7 +398,7 @@ async function hippiusPresign(fetchImpl, baseUrl, token, bucket, key, action, ex
397
398
  url.searchParams.set("expires_in", String(expiresIn));
398
399
  const response = await fetchImpl(url, { headers: { Authorization: `Token ${token}` } });
399
400
  if (!response.ok) {
400
- throw new Error(`Hippius presigned URL failed with ${response.status}: ${await response.text()}`);
401
+ throw new Error(`Hippius presigned URL failed status=${response.status}`);
401
402
  }
402
403
  return String((await response.json()).url);
403
404
  }
@@ -407,12 +408,12 @@ async function hippiusObjectSize(fetchImpl, baseUrl, token, bucket, key) {
407
408
  url.searchParams.set("max_keys", "1");
408
409
  const response = await fetchImpl(url, { headers: { Authorization: `Token ${token}` } });
409
410
  if (!response.ok) {
410
- throw new Error(`Hippius object lookup failed with ${response.status}: ${await response.text()}`);
411
+ throw new Error(`Hippius object lookup failed status=${response.status}`);
411
412
  }
412
413
  const payload = await response.json();
413
414
  const contents = payload.Contents ?? payload.contents ?? [];
414
415
  if (!contents.length) {
415
- throw new Error(`Object not found: hippius://${bucket}/${key}`);
416
+ throw new Error("Hippius object not found");
416
417
  }
417
418
  return Number(contents[0].Size ?? contents[0].size);
418
419
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beam-network/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "TypeScript SDK for BEAM transfer creation and management.",
5
5
  "type": "module",
6
6
  "license": "MIT",