@beam-network/sdk 0.4.1 → 0.4.3

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/README.md CHANGED
@@ -96,9 +96,9 @@ For low-level raw transfer configs, use `createRawTransfer`; lifecycle transport
96
96
 
97
97
  ## Route Streaming And Payload Size
98
98
 
99
- Provider transfers use `signed_url_v2` and `transfer-client-control/v3`. Before route streaming, S3, R2, and S3-compatible destinations create one metadata-bearing multipart upload per source/destination object. Its deduplicated manifest contains complete/abort and final HEAD controls, expected object size/count, required Beam object metadata, and exactly `ceil(max_part_number / 1000)` unique URLs in each of `list_page_urls` and `staging_list_urls`. Staging pages fix `MaxKeys=1000`; keys are ordered by unsigned UTF-8 bytes, page zero omits `StartAfter`, and later pages start after the preceding page's last key. Per-route batches carry only the stable group ID and route-attempt metadata. Part number is `source_chunk_index + 1`, and retries overwrite that same part.
99
+ Provider transfers default to `signed_url_v1` and `transfer-client-control/v3`. S3, R2, and S3-compatible destinations create one final multipart upload per source/destination object, then each route signs direct `UploadPart` to that upload. Core receives per-route completion, abort, ListParts, and final HEAD controls and completes the destination after all winning ETags are known. `signed_url_v2` remains explicitly selectable for the legacy staged-copy manifest path.
100
100
 
101
- The client signs up to 64 routes concurrently by default and emits a 32-route bootstrap batch followed by 256-route steady batches. Out-of-order signing completions are buffered until the next deterministic delivery index, keeping batch contents reproducible. The stream ID derives from the transfer and immutable plan identity, and each ordered batch ID includes its route-coordinate checksum. Manual multi-destination attachment requires `delivery_index` on every route. Encoded MessagePack requests still split under `maxPayloadBytes` (24 MiB by default), and lifecycle mutations retry transient failures up to three times with the same request identity. `waitForTransfer` subscribes to the API-key-owned, at-most-once terminal signal before its first status read and reconciles every signal through authoritative status; subscription failure degrades to the same jittered 15-to-30-second status fallback. Call `close()` when the client is no longer needed; it also closes outstanding terminal waiters.
101
+ The client signs up to 64 routes concurrently by default and emits 2,048-route logical batches, split only when encoded MessagePack requests exceed `maxPayloadBytes` (24 MiB by default). The stream ID derives from the transfer, selected flow, and immutable plan identity, and each ordered batch ID includes its route-coordinate checksum. Manual multi-destination attachment requires `delivery_index` on every route. Lifecycle mutations retry transient failures up to three times with the same request identity. `waitForTransfer` subscribes to the API-key-owned, at-most-once terminal signal before its first status read and reconciles every signal through authoritative status; subscription failure degrades to the same jittered 15-to-30-second status fallback. Call `close()` when the client is no longer needed; it also closes outstanding terminal waiters.
102
102
 
103
103
  Hippius uses genuine non-multipart v2 routes, so its manifest is empty and group-level final HEAD verification is not available from that provider flow.
104
104
 
package/dist/client.js CHANGED
@@ -63,7 +63,7 @@ export class BeamClient {
63
63
  callbacks: input.callbacks,
64
64
  test_mode: input.testMode || undefined,
65
65
  progressive_mode: input.progressiveMode || undefined,
66
- signed_url_flow: "signed_url_v2"
66
+ signed_url_flow: input.signedUrlFlow ?? "signed_url_v1"
67
67
  });
68
68
  return this.control.request("transfer.create", body, {
69
69
  transferId,
@@ -99,7 +99,7 @@ export class BeamClient {
99
99
  name: input.name,
100
100
  test_mode: input.testMode || undefined,
101
101
  urls_expires_at: input.urlsExpiresAt,
102
- signed_url_flow: "signed_url_v2"
102
+ signed_url_flow: input.signedUrlFlow ?? "signed_url_v1"
103
103
  }));
104
104
  if (result.success)
105
105
  validateCompactTransferPlan(result.plan_descriptor, result.signed_url_flow);
@@ -114,7 +114,7 @@ export class BeamClient {
114
114
  name: input.name,
115
115
  test_mode: input.testMode || undefined,
116
116
  urls_expires_at: input.urlsExpiresAt,
117
- signed_url_flow: "signed_url_v2"
117
+ signed_url_flow: input.signedUrlFlow ?? "signed_url_v1"
118
118
  }), {
119
119
  transferId,
120
120
  idempotencyKey: `transfer:${transferId}:prepare`
@@ -142,7 +142,8 @@ export class BeamClient {
142
142
  transferId,
143
143
  planIdentity: await routeCoordinateChecksum(orderedRoutes),
144
144
  totalRoutes: orderedRoutes.length,
145
- totalChunks: countDistinctRouteChunks(orderedRoutes)
145
+ totalChunks: countDistinctRouteChunks(orderedRoutes),
146
+ signedUrlFlow: input.multipartGroupManifest.length ? "signed_url_v2" : "signed_url_v1"
146
147
  });
147
148
  const stream = new RouteStreamSender(this.control, {
148
149
  streamId,
@@ -150,6 +151,7 @@ export class BeamClient {
150
151
  totalRoutes: orderedRoutes.length,
151
152
  totalChunks: countDistinctRouteChunks(orderedRoutes),
152
153
  urlsExpiresAt: input.urlsExpiresAt,
154
+ signedUrlFlow: input.multipartGroupManifest.length ? "signed_url_v2" : "signed_url_v1",
153
155
  autoDistribute: input.autoDistribute ?? true
154
156
  });
155
157
  await stream.begin();
@@ -163,7 +165,7 @@ export class BeamClient {
163
165
  }
164
166
  async prepareProviderTransfer(input) {
165
167
  const expiresIn = input.expiresIn ?? 3600;
166
- const requestedSignedUrlFlow = "signed_url_v2";
168
+ const requestedSignedUrlFlow = input.signedUrlFlow ?? "signed_url_v1";
167
169
  const preparedDestinations = input.destinations.map((destination, index) => prepareProviderDestination(destination, { index }));
168
170
  const destinationsById = new Map();
169
171
  preparedDestinations.forEach((preparedDestination, index) => {
@@ -212,13 +214,15 @@ export class BeamClient {
212
214
  transferId: prepared.transfer_id,
213
215
  planIdentity: prepared.plan_descriptor.plan_nonce,
214
216
  totalRoutes: prepared.plan_descriptor.delivery_route_count,
215
- totalChunks: prepared.plan_descriptor.logical_chunk_count
217
+ totalChunks: prepared.plan_descriptor.logical_chunk_count,
218
+ signedUrlFlow: requestedSignedUrlFlow
216
219
  });
217
220
  routeStream = new RouteStreamSender(this.control, {
218
221
  streamId,
219
222
  transferId: prepared.transfer_id,
220
223
  totalRoutes: prepared.plan_descriptor.delivery_route_count,
221
224
  totalChunks: prepared.plan_descriptor.logical_chunk_count,
225
+ signedUrlFlow: requestedSignedUrlFlow,
222
226
  autoDistribute: input.distribute !== false
223
227
  });
224
228
  routeStreamBeginAttempted = true;
@@ -229,10 +233,13 @@ export class BeamClient {
229
233
  destinationsById,
230
234
  multipartUploads,
231
235
  expiresIn,
236
+ signedUrlFlow: requestedSignedUrlFlow,
232
237
  concurrency: this.routeSigningConcurrency,
233
238
  onGroupReady: async (state) => {
234
- validateMultipartGroupManifest([state.manifest], prepared.transfer_id);
235
- await routeStream.addManifestGroups([state.manifest]);
239
+ if (requestedSignedUrlFlow === "signed_url_v2") {
240
+ validateMultipartGroupManifest([state.manifest], prepared.transfer_id);
241
+ await routeStream.addManifestGroups([state.manifest]);
242
+ }
236
243
  multipartGroupWaiters.get(state.manifest.multipart_group_id)?.resolve(state);
237
244
  },
238
245
  onGroupFailed: (groupId, error) => multipartGroupWaiters.get(groupId)?.reject(error)
@@ -281,6 +288,7 @@ export class BeamClient {
281
288
  ...(upload ? { upload } : {}),
282
289
  transferId: prepared.transfer_id,
283
290
  finalObjectKey,
291
+ signedUrlFlow: requestedSignedUrlFlow,
284
292
  partNumber: typeof metadata.part_number === "number"
285
293
  ? metadata.part_number
286
294
  : multipartPartNumber(chunk.source_chunk_index),
@@ -294,7 +302,9 @@ export class BeamClient {
294
302
  while (pendingRoutes.size)
295
303
  await streamNextCompletedRoute();
296
304
  const multipartGroupManifest = await multipartManifestTask;
297
- validateMultipartGroupManifest(multipartGroupManifest, prepared.transfer_id);
305
+ if (requestedSignedUrlFlow === "signed_url_v2") {
306
+ validateMultipartGroupManifest(multipartGroupManifest, prepared.transfer_id);
307
+ }
298
308
  const attached = await routeStream.complete();
299
309
  if (!attached.success) {
300
310
  throw new Error(attached.error ?? attached.message ?? "route stream failed");
@@ -330,8 +340,33 @@ export class BeamClient {
330
340
  }
331
341
  const upload = input.upload;
332
342
  if (!upload)
333
- throw new Error("signed_url_v2 route is missing its multipart group");
343
+ throw new Error(`${input.signedUrlFlow} route is missing its multipart upload`);
334
344
  validateMultipartPartNumber(input.partNumber, upload.manifest);
345
+ if (input.signedUrlFlow === "signed_url_v1") {
346
+ return signDestinationRoute({
347
+ chunk: input.chunk,
348
+ target: {
349
+ ...input.target,
350
+ object_key: input.finalObjectKey,
351
+ metadata: partRouteMetadata(input.target.metadata)
352
+ },
353
+ source: input.source,
354
+ destination: input.destination,
355
+ expiresIn: input.expiresIn,
356
+ uploadId: upload.uploadId,
357
+ partNumber: input.partNumber,
358
+ finalObjectKey: input.finalObjectKey,
359
+ expectedObjectSize: upload.manifest.expected_object_size,
360
+ expectedPartCount: upload.manifest.expected_part_count,
361
+ maxPartNumber: upload.manifest.max_part_number,
362
+ completeUrl: upload.manifest.complete_url,
363
+ abortUrl: upload.manifest.abort_url,
364
+ listPageUrl: upload.manifest.list_page_urls[multipartListPageIndex(input.partNumber)],
365
+ finalHeadUrl: upload.manifest.final_head_url,
366
+ finalObjectMetadata: upload.manifest.final_object_metadata,
367
+ fetchImpl: this.fetchImpl
368
+ });
369
+ }
335
370
  const stagingObjectPrefix = buildStagingObjectPrefix({
336
371
  finalObjectKey: input.finalObjectKey,
337
372
  transferId: input.transferId,
@@ -475,9 +510,9 @@ class RouteStreamSender {
475
510
  stream_id: this.streamId,
476
511
  total_routes: this.options.totalRoutes,
477
512
  total_chunks: this.options.totalChunks,
478
- route_contract_version: "signed_url_v2",
513
+ route_contract_version: this.options.signedUrlFlow,
479
514
  urls_expires_at: this.options.urlsExpiresAt,
480
- signed_url_flow: "signed_url_v2",
515
+ signed_url_flow: this.options.signedUrlFlow,
481
516
  auto_distribute: this.options.autoDistribute
482
517
  }), {
483
518
  transferId: this.options.transferId,
@@ -633,7 +668,7 @@ async function routeCoordinateChecksum(routes) {
633
668
  return checksum.value();
634
669
  }
635
670
  async function stableRouteStreamId(input) {
636
- const digest = await sha256Bytes(`beam:route-stream:signed_url_v2:${input.transferId}:${input.planIdentity}:${input.totalRoutes}:${input.totalChunks}`);
671
+ const digest = await sha256Bytes(`beam:route-stream:${input.signedUrlFlow}:${input.transferId}:${input.planIdentity}:${input.totalRoutes}:${input.totalChunks}`);
637
672
  const bytes = digest.slice(0, 16);
638
673
  bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x50;
639
674
  bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;
@@ -843,7 +878,7 @@ async function createMultipartGroupManifest(input) {
843
878
  multipartGroupId = multipartGroupStateKey(input.prepared.transfer_id, destination.destination_id, source.source_id, finalObjectKey);
844
879
  const finalObjectMetadata = {
845
880
  "beam-transfer-id": input.prepared.transfer_id,
846
- "beam-multipart-group-id": multipartGroupId
881
+ ...(input.signedUrlFlow === "signed_url_v2" ? { "beam-multipart-group-id": multipartGroupId } : {})
847
882
  };
848
883
  const uploadId = await createMultipartUpload({
849
884
  destination: destinationConfig,
@@ -874,7 +909,9 @@ async function createMultipartGroupManifest(input) {
874
909
  signCompleteMultipartUpload(destinationConfig, finalObjectKey, uploadId, input.expiresIn),
875
910
  signAbortMultipartUpload(destinationConfig, finalObjectKey, uploadId, input.expiresIn),
876
911
  Promise.all(listPageMarkers.map((partNumberMarker) => signListMultipartUpload(destinationConfig, finalObjectKey, uploadId, input.expiresIn, { maxParts: 1_000, ...(partNumberMarker ? { partNumberMarker } : {}) }))),
877
- Promise.all(stagingListStartAfter.map((startAfter) => signListObjects(destinationConfig, stagingListPrefix, input.expiresIn, startAfter ? { startAfter } : {}))),
912
+ input.signedUrlFlow === "signed_url_v2"
913
+ ? Promise.all(stagingListStartAfter.map((startAfter) => signListObjects(destinationConfig, stagingListPrefix, input.expiresIn, startAfter ? { startAfter } : {})))
914
+ : Promise.resolve([]),
878
915
  signFinalObjectHead(destinationConfig, finalObjectKey, input.expiresIn)
879
916
  ]);
880
917
  const manifest = {
@@ -980,6 +1017,9 @@ function validateMultipartPartNumber(partNumber, manifest) {
980
1017
  throw new Error(`multipart part_number ${partNumber} is outside group ${manifest.multipart_group_id} range 1-${manifest.max_part_number}`);
981
1018
  }
982
1019
  }
1020
+ function multipartListPageIndex(partNumber) {
1021
+ return Math.floor((partNumber - 1) / 1_000);
1022
+ }
983
1023
  function validateSignedRouteManifestContract(routes, manifest) {
984
1024
  const groups = new Map(manifest.map((group) => [group.multipart_group_id, group]));
985
1025
  for (const route of routes) {
@@ -1019,7 +1059,7 @@ const COMPACT_TRANSFER_PLAN_FORMULAS = {
1019
1059
  staging_object_key: "{final_object_key}.beam-staging/{transfer_id}/{source_id}/{destination_id}/part-{part_number}-chunk-{chunk_index}"
1020
1060
  };
1021
1061
  function validateCompactTransferPlan(descriptor, signedUrlFlow) {
1022
- if (signedUrlFlow !== "signed_url_v2") {
1062
+ if (signedUrlFlow !== "signed_url_v1" && signedUrlFlow !== "signed_url_v2") {
1023
1063
  throw new Error(`BeamCore returned unsupported signed_url_flow: ${String(signedUrlFlow)}`);
1024
1064
  }
1025
1065
  const keys = Object.keys(descriptor);
@@ -1094,15 +1134,21 @@ function validateMultipartGroupManifest(manifest, transferId) {
1094
1134
  throw new Error(`multipart group ${group.multipart_group_id} requires ${expectedListPageCount} unique list_page_urls`);
1095
1135
  }
1096
1136
  if (!Array.isArray(group.staging_list_urls)
1097
- || group.staging_list_urls.length !== expectedListPageCount
1098
- || new Set(group.staging_list_urls).size !== expectedListPageCount
1099
1137
  || group.staging_list_urls.some((url) => typeof url !== "string" || url.length === 0)) {
1100
- throw new Error(`multipart group ${group.multipart_group_id} requires ${expectedListPageCount} unique staging_list_urls`);
1138
+ throw new Error(`multipart group ${group.multipart_group_id} has invalid staging_list_urls`);
1139
+ }
1140
+ if (group.staging_list_urls.length > 0
1141
+ && (group.staging_list_urls.length !== expectedListPageCount
1142
+ || new Set(group.staging_list_urls).size !== expectedListPageCount)) {
1143
+ throw new Error(`multipart group ${group.multipart_group_id} requires ${expectedListPageCount} unique staging_list_urls when staging is used`);
1101
1144
  }
1102
1145
  const metadataKeys = Object.keys(group.final_object_metadata);
1103
- if (metadataKeys.length !== 2
1146
+ const stagedCopyMetadata = group.staging_list_urls.length > 0;
1147
+ if (metadataKeys.length !== (stagedCopyMetadata ? 2 : 1)
1104
1148
  || group.final_object_metadata["beam-transfer-id"] !== transferId
1105
- || group.final_object_metadata["beam-multipart-group-id"] !== group.multipart_group_id) {
1149
+ || (stagedCopyMetadata
1150
+ ? group.final_object_metadata["beam-multipart-group-id"] !== group.multipart_group_id
1151
+ : group.final_object_metadata["beam-multipart-group-id"] !== undefined)) {
1106
1152
  throw new Error(`multipart group ${group.multipart_group_id} has invalid final_object_metadata`);
1107
1153
  }
1108
1154
  }
package/dist/models.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type SignedUrlFlow = "signed_url_v2";
1
+ export type SignedUrlFlow = "signed_url_v1" | "signed_url_v2";
2
2
  export interface BeamClientOptions {
3
3
  apiKey?: string;
4
4
  natsUrl?: string;
@@ -176,6 +176,15 @@ export interface R2ProviderConfig {
176
176
  account_id?: string;
177
177
  endpoint_url?: string;
178
178
  }
179
+ export interface S3CompatibleProviderCapabilities {
180
+ /**
181
+ * Whether the provider's ListParts / ListObjects response includes a per-part
182
+ * LastModified timestamp. Defaults to true (S3-spec compliant). Providers that
183
+ * omit it (e.g. Hippius) declare false so Beam finalization substitutes a
184
+ * Beam-side timestamp instead of looping into multipart recovery.
185
+ */
186
+ multipart_part_last_modified?: boolean;
187
+ }
179
188
  export interface S3CompatibleProviderConfig {
180
189
  provider: string;
181
190
  driver?: "s3-compatible";
@@ -189,6 +198,7 @@ export interface S3CompatibleProviderConfig {
189
198
  session_token?: string;
190
199
  force_path_style?: boolean;
191
200
  account_id?: string;
201
+ capabilities?: S3CompatibleProviderCapabilities;
192
202
  }
193
203
  export interface HippiusProviderConfig {
194
204
  provider: "hippius";
@@ -325,9 +335,9 @@ export interface MultipartGroupManifest {
325
335
  list_page_urls: string[];
326
336
  staging_list_urls: string[];
327
337
  final_head_url: string;
328
- final_object_metadata: {
338
+ final_object_metadata: Record<string, string> & {
329
339
  "beam-transfer-id": string;
330
- "beam-multipart-group-id": string;
340
+ "beam-multipart-group-id"?: string;
331
341
  };
332
342
  urls_expires_at: string;
333
343
  }
@@ -31,9 +31,9 @@ export interface RouteStreamBeginPayload {
31
31
  stream_id: string;
32
32
  total_routes: number;
33
33
  total_chunks: number;
34
- route_contract_version: "signed_url_v2";
34
+ route_contract_version: "signed_url_v1" | "signed_url_v2";
35
35
  urls_expires_at?: string;
36
- signed_url_flow: "signed_url_v2";
36
+ signed_url_flow: "signed_url_v1" | "signed_url_v2";
37
37
  auto_distribute: boolean;
38
38
  }
39
39
  export interface RouteStreamManifestPayload {
@@ -402,9 +402,11 @@ export function compactSignedRoutes(routes) {
402
402
  const multipartGroupId = typeof metadata.multipart_group_id === "string"
403
403
  ? metadata.multipart_group_id
404
404
  : undefined;
405
- for (const key of Object.keys(metadata)) {
406
- if (!ROUTE_ATTEMPT_METADATA_KEYS.has(key))
407
- delete metadata[key];
405
+ if (multipartGroupId) {
406
+ for (const key of Object.keys(metadata)) {
407
+ if (!ROUTE_ATTEMPT_METADATA_KEYS.has(key))
408
+ delete metadata[key];
409
+ }
408
410
  }
409
411
  compactRoutes.push(compact({
410
412
  source_ref: sourceRef,
@@ -42,6 +42,16 @@ export declare function signDestinationRoute(input: {
42
42
  destination: ProviderDestinationConfig;
43
43
  expiresIn: number;
44
44
  partNumber?: number;
45
+ uploadId?: string;
46
+ completeUrl?: string;
47
+ abortUrl?: string;
48
+ listPageUrl?: string;
49
+ finalHeadUrl?: string;
50
+ finalObjectKey?: string;
51
+ expectedObjectSize?: number;
52
+ expectedPartCount?: number;
53
+ maxPartNumber?: number;
54
+ finalObjectMetadata?: Record<string, string>;
45
55
  multipartGroupId?: string;
46
56
  commitMethod?: "upload_part_copy";
47
57
  stagingObjectKey?: string;
@@ -1,4 +1,4 @@
1
- import { AbortMultipartUploadCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, ListPartsCommand, PutObjectCommand, S3Client, UploadPartCopyCommand } from "@aws-sdk/client-s3";
1
+ import { AbortMultipartUploadCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, ListPartsCommand, PutObjectCommand, S3Client, UploadPartCommand, UploadPartCopyCommand } from "@aws-sdk/client-s3";
2
2
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
3
3
  export function nowIso() {
4
4
  return new Date().toISOString();
@@ -230,6 +230,8 @@ export async function signDestinationRoute(input) {
230
230
  signDestinationUrl({
231
231
  destination: input.destination,
232
232
  objectKey,
233
+ uploadId: input.uploadId,
234
+ partNumber: input.partNumber,
233
235
  expiresIn: input.expiresIn,
234
236
  fetchImpl: input.fetchImpl,
235
237
  }),
@@ -260,6 +262,16 @@ export async function signDestinationRoute(input) {
260
262
  ...(input.copySource ? { copy_source: input.copySource } : {}),
261
263
  ...(input.copySourceRange ? { copy_source_range: input.copySourceRange } : {}),
262
264
  ...(input.deleteUrl ? { delete_url: input.deleteUrl } : {}),
265
+ ...(input.uploadId ? { upload_id: input.uploadId } : {}),
266
+ ...(input.completeUrl ? { complete_url: input.completeUrl } : {}),
267
+ ...(input.abortUrl ? { abort_url: input.abortUrl } : {}),
268
+ ...(input.listPageUrl ? { list_page_url: input.listPageUrl } : {}),
269
+ ...(input.finalHeadUrl ? { final_head_url: input.finalHeadUrl } : {}),
270
+ ...(input.finalObjectKey ? { final_object_key: input.finalObjectKey } : {}),
271
+ ...(input.expectedObjectSize !== undefined ? { expected_object_size: input.expectedObjectSize } : {}),
272
+ ...(input.expectedPartCount !== undefined ? { expected_part_count: input.expectedPartCount } : {}),
273
+ ...(input.maxPartNumber !== undefined ? { max_part_number: input.maxPartNumber } : {}),
274
+ ...(input.finalObjectMetadata ? { final_object_metadata: input.finalObjectMetadata } : {}),
263
275
  ...(input.partNumber ? { part_number: input.partNumber } : {})
264
276
  }
265
277
  };
@@ -299,6 +311,14 @@ async function signSourceRoute(input) {
299
311
  async function signDestinationUrl(input) {
300
312
  if (isS3CompatibleProvider(input.destination)) {
301
313
  const client = createS3CompatibleClient(input.destination);
314
+ if (input.uploadId && input.partNumber) {
315
+ return getSignedUrl(client, new UploadPartCommand({
316
+ Bucket: input.destination.bucket,
317
+ Key: input.objectKey,
318
+ UploadId: input.uploadId,
319
+ PartNumber: input.partNumber
320
+ }), { expiresIn: input.expiresIn });
321
+ }
302
322
  return getSignedUrl(client, new PutObjectCommand({ Bucket: input.destination.bucket, Key: input.objectKey }), { expiresIn: input.expiresIn });
303
323
  }
304
324
  if (isHippiusProvider(input.destination)) {
@@ -385,7 +405,8 @@ function s3CompatibleMetadata(source, endpoint = s3CompatibleEndpoint(source)) {
385
405
  key: source.key,
386
406
  region: s3CompatibleRegion(source),
387
407
  endpoint_url: endpoint,
388
- account_id: "account_id" in source ? source.account_id : undefined
408
+ account_id: "account_id" in source ? source.account_id : undefined,
409
+ capabilities: "capabilities" in source ? source.capabilities : undefined
389
410
  });
390
411
  }
391
412
  function isHippiusProvider(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beam-network/sdk",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "TypeScript SDK for BEAM transfer creation and management.",
5
5
  "type": "module",
6
6
  "license": "MIT",