@beam-network/sdk 0.5.20 → 0.6.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/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # BEAM SDK for TypeScript
2
2
 
3
+ This release requires Core's `transfer-client-control/v7` contract. Multipart uploads use consecutive parts;
4
+ Core may request retained staging for recovery through the existing SDK signing connection. Workers receive
5
+ ordinary upload URLs. Keep the client connected until the transfer reaches a terminal state so Core can renew
6
+ grants, promote recovered data, and clean up staging. Drain active transfers before upgrading Core and SDK consumers.
7
+
3
8
  Install:
4
9
 
5
10
  ```bash
@@ -149,7 +154,7 @@ For low-level raw transfer configs, use `createRawTransfer`; lifecycle transport
149
154
 
150
155
  ## Route Streaming And Payload Size
151
156
 
152
- Provider transfers use `transfer-client-control/v6`. Every reply carries Runtime and transport epochs; prepare and every route-stream message carry a UUID route generation. S3, R2, and S3-compatible destinations retain direct multipart UploadPart/ListParts/HEAD handling. Hippius and Hugging Face destinations take a plain PUT per chunk instead, so they carry no multipart group manifest.
157
+ Provider transfers use `transfer-client-control/v7`. Every reply carries Runtime and transport epochs; prepare and every route-stream message carry a UUID route generation. S3, R2, and S3-compatible destinations retain direct multipart UploadPart/ListParts/HEAD handling. Hippius and Hugging Face destinations take a plain PUT per chunk instead, so they carry no multipart group manifest.
153
158
 
154
159
  The client signs up to 64 routes concurrently by default and emits 2,048-route logical batches. Encoded MessagePack requests target 4 MiB physical batches under `maxPayloadBytes` (8 MiB by default; explicit positive overrides remain supported). The splitter reserves control-envelope headroom for the live auth token and stable request identity so the final encoded request remains below the configured guard. Lifecycle control refreshes cached SDK auth before the final 30 seconds of token lifetime and retries `auth_token_expired` replies with the same request identity and payload but a fresh token, preserving Core request-conflict protection during large route streams. A single route above the 4 MiB target but within the configured guard publishes alone; a single route larger than the configured limit fails before publication with both sizes in the error. Multipart create and abort requests use a separate `multipartControlConcurrency` limit of 2 by default, with explicit positive overrides supported, and S3-compatible control requests make up to five attempts for transient provider failures. This keeps URL signing throughput independent from provider control-plane pressure.
155
160
 
@@ -161,7 +166,7 @@ Create and resume accept an ownership `signal`. Aborting it stops initial routes
161
166
 
162
167
  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. A non-recoverable provider setup or route failure raises `BeamProviderTransferError`, whose `errors`, `transferCancelled`, and `multipartCleanupComplete` fields retain the original provider, cancellation, and cleanup outcomes. `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.
163
168
 
164
- During the pre-completion `integrity_check` phase, Runtime may include an `integrity_audit_challenge` in status. The TypeScript client signs exact read-only source and final-destination GET ranges and submits `transfer.integrity_audit_grants`. S3-compatible source and destination grants use the prepared source ETag and Runtime-verified final-object ETag as `If-Match` conditions. Submission failures retry on the next status poll; the transfer completes with a warning if the check cannot finish by its deadline.
169
+ During the pre-completion `integrity_check` phase, Runtime may include an `integrity_audit_challenge` in status. The TypeScript client signs exact read-only source and final-destination GET ranges and submits `transfer.integrity_audit_grants`. S3-compatible source and destination grants use the prepared source ETag and Runtime-verified final-object ETag as `If-Match` conditions. Submission failures retry on the next status poll and appear as a URL-safe `integrity_audit_submission_error` on that status response; the transfer completes with a warning if the check cannot finish by its deadline.
165
170
 
166
171
  Hippius uses canonical non-multipart `signed_url` routes, so its manifest is empty and group-level final HEAD verification is not available from that provider flow.
167
172
 
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { abortMultipartUpload, createMultipartUpload, expiresAtIso, prepareProviderDestination, prepareProviderSource, signAbortMultipartUpload, signCompleteMultipartUpload, signDestinationReadRange, signDestinationRoute, signFinalObjectHead, signListMultipartUpload, signSourceReadRange, isHuggingFaceProvider } from "./provider-signing.js";
1
+ import { signMultipartRecovery, abortMultipartUpload, createMultipartUpload, expiresAtIso, prepareProviderDestination, prepareProviderSource, signAbortMultipartUpload, signCompleteMultipartUpload, signDestinationReadRange, signDestinationRoute, signFinalObjectHead, signListMultipartUpload, signSourceReadRange, isHuggingFaceProvider } from "./provider-signing.js";
2
2
  import { describe as describeHuggingFace, hashSourceStream, huggingFaceCommit, huggingFaceCompleteLfsUpload, huggingFaceLfsBatch, huggingFacePreupload, huggingFaceVerifyLfsUpload, readSourceSample } from "./huggingface.js";
3
3
  import { BeamTransferControl, BEAM_DEFAULT_NATS_URL, compactSignedRoutes, isRecoverableRouteStreamError } from "./nats-control.js";
4
4
  export class BeamRouteRecoveryPendingError extends Error {
@@ -121,7 +121,12 @@ export class BeamClient {
121
121
  validateId(transferId, "transferId");
122
122
  const status = await this.control.request("transfer.status", { transfer_id: transferId }, { transferId });
123
123
  if (status.integrity_audit_challenge) {
124
- await this.submitIntegrityAuditGrantsIfPresent(status).catch(() => undefined);
124
+ try {
125
+ await this.submitIntegrityAuditGrantsIfPresent(status);
126
+ }
127
+ catch (error) {
128
+ status.integrity_audit_submission_error = integrityAuditErrorSummary(error);
129
+ }
125
130
  }
126
131
  if (["completed", "failed", "cancelled"].includes(status.status)) {
127
132
  this.control.releaseRecoveryLease(transferId);
@@ -816,7 +821,7 @@ export class BeamClient {
816
821
  multipartUploads: input.multipartUploads,
817
822
  expiresIn: input.expiresIn
818
823
  });
819
- return this.signProviderRoute({
824
+ const route = await this.signProviderRoute({
820
825
  chunk,
821
826
  target: {
822
827
  ...target,
@@ -847,6 +852,7 @@ export class BeamClient {
847
852
  signedUrlFlow: "signed_url",
848
853
  partNumber: requested.part_number
849
854
  });
855
+ return signMultipartRecovery({ destination, transferId: input.prepared.transfer_id, requested, route, expiresIn: input.expiresIn });
850
856
  });
851
857
  return {
852
858
  transfer_id: payload.transfer_id,
@@ -929,7 +935,7 @@ export class BeamClient {
929
935
  return;
930
936
  const signer = this.integrityAuditSigners.get(challenge.transfer_id);
931
937
  if (!signer)
932
- return;
938
+ throw new Error("integrity audit signer unavailable");
933
939
  const existing = this.integrityAuditSubmissions.get(challenge.audit_id);
934
940
  if (existing) {
935
941
  await existing;
@@ -1357,6 +1363,15 @@ async function mapOrderedWithConcurrency(values, concurrency, map) {
1357
1363
  await Promise.all(workers);
1358
1364
  return results;
1359
1365
  }
1366
+ function integrityAuditErrorSummary(error) {
1367
+ if (!(error instanceof Error))
1368
+ return "unknown_error";
1369
+ const message = error.message.trim();
1370
+ if (!message || /https?:\/\/|x-amz|password|secret|token|authorization|credential/i.test(message)) {
1371
+ return error.name || "unknown_error";
1372
+ }
1373
+ return message.slice(0, 200);
1374
+ }
1360
1375
  function positiveInteger(value, label) {
1361
1376
  if (!Number.isInteger(value) || value < 1)
1362
1377
  throw new Error(`${label} must be a positive integer.`);
@@ -1647,7 +1662,7 @@ function multipartListPageMarkers(maxPartNumber) {
1647
1662
  return markers;
1648
1663
  }
1649
1664
  function multipartMaxPartNumber(chunkCount) {
1650
- return multipartPartNumber(positiveInteger(chunkCount, "source chunk_count") - 1, 2);
1665
+ return multipartPartNumber(positiveInteger(chunkCount, "source chunk_count") - 1);
1651
1666
  }
1652
1667
  function validateMultipartPartNumber(partNumber, manifest) {
1653
1668
  if (!Number.isInteger(partNumber) || partNumber < 1 || partNumber > manifest.max_part_number) {
@@ -1701,7 +1716,7 @@ const COMPACT_TRANSFER_PLAN_KEYS = new Set([
1701
1716
  const COMPACT_TRANSFER_PLAN_FORMULAS = {
1702
1717
  source_offset: "source_chunk_index * chunk_size",
1703
1718
  delivery_index: "chunk_index * destination_count + destination_index",
1704
- part_number: "source_chunk_index * 3 + attempt_slot + 1",
1719
+ part_number: "source_chunk_index + 1",
1705
1720
  route_generation_id: "initial-{chunk_index}-{destination_id}"
1706
1721
  };
1707
1722
  function validateCompactTransferPlan(descriptor, signedUrlFlow) {
@@ -1715,7 +1730,7 @@ function validateCompactTransferPlan(descriptor, signedUrlFlow) {
1715
1730
  if (descriptor.version !== "compact-transfer-plan/v1") {
1716
1731
  throw new Error(`BeamCore returned unsupported plan version: ${descriptor.version}`);
1717
1732
  }
1718
- if (descriptor.multipart_attempt_slots !== 3) {
1733
+ if (descriptor.multipart_attempt_slots !== 1) {
1719
1734
  throw new Error("BeamCore returned unsupported multipart attempt slot count");
1720
1735
  }
1721
1736
  const formulaKeys = Object.keys(descriptor.formulas);
@@ -1768,7 +1783,7 @@ function validateMultipartGroupManifest(manifest, transferId) {
1768
1783
  if (!Number.isInteger(group.expected_part_count) || group.expected_part_count < 1 || group.expected_part_count > 10_000) {
1769
1784
  throw new Error(`multipart group ${group.multipart_group_id} has invalid expected_part_count`);
1770
1785
  }
1771
- if (!Number.isInteger(group.max_part_number) || group.max_part_number < group.expected_part_count || group.max_part_number > 10_000) {
1786
+ if (!Number.isInteger(group.max_part_number) || group.max_part_number !== group.expected_part_count || group.max_part_number > 10_000) {
1772
1787
  throw new Error(`multipart group ${group.multipart_group_id} has invalid max_part_number`);
1773
1788
  }
1774
1789
  if (!Number.isSafeInteger(group.expected_object_size) || group.expected_object_size <= 0) {
@@ -1796,7 +1811,8 @@ const PART_ROUTE_METADATA_KEYS = new Set([
1796
1811
  "logical_attempt_index",
1797
1812
  "attempt_slot",
1798
1813
  "route_generation_id",
1799
- "delivery_index"
1814
+ "delivery_index",
1815
+ "etag_required"
1800
1816
  ]);
1801
1817
  function partRouteMetadata(metadata) {
1802
1818
  return Object.fromEntries(Object.entries(metadata ?? {}).filter(([key]) => PART_ROUTE_METADATA_KEYS.has(key)));
package/dist/models.d.ts CHANGED
@@ -171,6 +171,7 @@ export interface TransferStatusInfo {
171
171
  completedDestinationIds: string[];
172
172
  };
173
173
  integrity_audit_challenge?: IntegrityAuditChallenge;
174
+ integrity_audit_submission_error?: string;
174
175
  }
175
176
  export interface ProviderTransferResumeInput extends Omit<ProviderTransferCreateInput, "idempotencyKey" | "routeGenerationId"> {
176
177
  transferId: string;
@@ -178,7 +179,7 @@ export interface ProviderTransferResumeInput extends Omit<ProviderTransferCreate
178
179
  multipartGroups: ProviderMultipartGroupIdentity[];
179
180
  }
180
181
  export interface TransferTerminalEvent {
181
- schema_version: "transfer-client-control/v6";
182
+ schema_version: "transfer-client-control/v7";
182
183
  producer: "transfer-runtime";
183
184
  transfer_id: string;
184
185
  status: "completed" | "failed" | "cancelled";
@@ -376,11 +377,11 @@ export interface CompactTransferPlanDescriptor {
376
377
  destinations: CompactTransferPlanDestination[];
377
378
  logical_chunk_count: number;
378
379
  delivery_route_count: number;
379
- multipart_attempt_slots: 3;
380
+ multipart_attempt_slots: 1;
380
381
  formulas: {
381
382
  source_offset: "source_chunk_index * chunk_size";
382
383
  delivery_index: "chunk_index * destination_count + destination_index";
383
- part_number: "source_chunk_index * 3 + attempt_slot + 1";
384
+ part_number: "source_chunk_index + 1";
384
385
  route_generation_id: "initial-{chunk_index}-{destination_id}";
385
386
  };
386
387
  }
@@ -1,4 +1,4 @@
1
1
  export declare const MULTIPART_MAX_PART_NUMBER = 10000;
2
- export declare const MULTIPART_ATTEMPT_SLOT_COUNT = 3;
2
+ export declare const MULTIPART_ATTEMPT_SLOT_COUNT = 1;
3
3
  export declare const MULTIPART_MAX_SOURCE_CHUNKS: number;
4
4
  export declare function multipartPartNumber(chunkIndex: number, attemptSlot?: number): number;
@@ -1,5 +1,5 @@
1
1
  export const MULTIPART_MAX_PART_NUMBER = 10_000;
2
- export const MULTIPART_ATTEMPT_SLOT_COUNT = 3;
2
+ export const MULTIPART_ATTEMPT_SLOT_COUNT = 1;
3
3
  export const MULTIPART_MAX_SOURCE_CHUNKS = Math.floor(MULTIPART_MAX_PART_NUMBER / MULTIPART_ATTEMPT_SLOT_COUNT);
4
4
  export function multipartPartNumber(chunkIndex, attemptSlot = 0) {
5
5
  if (!Number.isInteger(chunkIndex) || chunkIndex < 0) {
@@ -9,8 +9,8 @@ export function multipartPartNumber(chunkIndex, attemptSlot = 0) {
9
9
  throw new Error(`source_chunk_index must be less than ${MULTIPART_MAX_SOURCE_CHUNKS}`);
10
10
  }
11
11
  if (!Number.isInteger(attemptSlot) || attemptSlot < 0 || attemptSlot >= MULTIPART_ATTEMPT_SLOT_COUNT) {
12
- throw new Error("attempt_slot must be 0, 1, or 2");
12
+ throw new Error("attempt_slot must be 0 for consecutive multipart uploads");
13
13
  }
14
- const partNumber = chunkIndex * MULTIPART_ATTEMPT_SLOT_COUNT + attemptSlot + 1;
14
+ const partNumber = chunkIndex + 1;
15
15
  return partNumber;
16
16
  }
@@ -1,12 +1,20 @@
1
1
  import { type ConnectionOptions } from "nats";
2
2
  import type { MultipartGroupManifest, SignedChunkRoute, TransferTerminalSignalWaiter } from "./models.js";
3
- export declare const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v6";
3
+ export declare const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v7";
4
4
  export declare const BEAM_DEFAULT_NATS_URL = "tls://orch-gateway.b1m.ai:4222";
5
5
  export declare const BEAM_DEFAULT_NATS_WS_URL = "wss://orch-gateway.b1m.ai:443";
6
6
  export declare const BEAM_DEFAULT_MAX_PAYLOAD_BYTES: number;
7
7
  export declare const BEAM_ROUTE_TARGET_PAYLOAD_BYTES: number;
8
8
  export declare const ROUTE_RECOVERY_SIGN_MESSAGE_TYPE: "transfer.route_recovery.sign";
9
9
  export interface RouteRecoverySignChunk {
10
+ recovery?: {
11
+ operation: "upload" | "controls" | "list" | "delete" | "renew";
12
+ mode: "staged" | "direct";
13
+ attempt_id: string;
14
+ object_key?: string;
15
+ etag?: string;
16
+ continuation_token?: string;
17
+ };
10
18
  source_id: string;
11
19
  destination_id: string;
12
20
  chunk_index: number;
@@ -14,7 +22,7 @@ export interface RouteRecoverySignChunk {
14
22
  source_offset: number;
15
23
  chunk_size: number;
16
24
  logical_attempt_index: number;
17
- attempt_slot: 0 | 1 | 2;
25
+ attempt_slot: 0;
18
26
  part_number: number;
19
27
  route_generation_id: string;
20
28
  multipart_group_id: 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/v6";
3
+ export const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v7";
4
4
  export const BEAM_DEFAULT_NATS_URL = "tls://orch-gateway.b1m.ai:4222";
5
5
  export const BEAM_DEFAULT_NATS_WS_URL = "wss://orch-gateway.b1m.ai:443";
6
6
  // NATS enforces max_payload per message. This guard splits signed-route control
@@ -604,6 +604,7 @@ const ROUTE_ATTEMPT_METADATA_KEYS = new Set([
604
604
  "part_number",
605
605
  "logical_attempt_index",
606
606
  "attempt_slot",
607
+ "etag_required",
607
608
  "route_generation_id"
608
609
  ]);
609
610
  const ROUTE_IDENTITY_METADATA_KEYS = [
@@ -1,5 +1,14 @@
1
+ import type { RouteRecoverySignChunk } from "./nats-control.js";
1
2
  import type { ChunkDestinationSigningTarget, ChunkSigningPlanItem, HuggingFaceProviderConfig, PlanningHttpSource, PreparedDestination, PreparedHttpSource, ProviderDestinationConfig, ProviderSourceConfig, R2ProviderConfig, S3CompatibleProviderConfig, S3ProviderConfig, SignedChunkRoute } from "./models.js";
2
3
  export type AnyS3CompatibleProviderConfig = S3ProviderConfig | R2ProviderConfig | S3CompatibleProviderConfig;
4
+ /** Core-only grants: the worker receives just the ordinary upload URL. */
5
+ export declare function signMultipartRecovery(input: {
6
+ destination: ProviderDestinationConfig;
7
+ transferId: string;
8
+ requested: RouteRecoverySignChunk;
9
+ route: SignedChunkRoute;
10
+ expiresIn: number;
11
+ }): Promise<SignedChunkRoute>;
3
12
  export declare function nowIso(): string;
4
13
  export declare function expiresAtIso(expiresIn: number): string;
5
14
  export declare function prepareProviderSource(source: ProviderSourceConfig, options?: {
@@ -1,6 +1,70 @@
1
1
  import { AbortMultipartUploadCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, GetObjectCommand, HeadObjectCommand, ListPartsCommand, PutObjectCommand, S3Client, UploadPartCommand } from "@aws-sdk/client-s3";
2
+ import { UploadPartCopyCommand, DeleteObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
2
3
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
3
4
  import { huggingFaceEndpoint, huggingFaceFileMetadata, huggingFaceRepoType, huggingFaceRevision } from "./huggingface.js";
5
+ /** Core-only grants: the worker receives just the ordinary upload URL. */
6
+ export async function signMultipartRecovery(input) {
7
+ const { requested: r, destination, expiresIn } = input;
8
+ const request = r.recovery;
9
+ if (request?.operation === "renew") {
10
+ const count = Number(input.route.metadata?.expected_part_count);
11
+ if (!Number.isInteger(count) || count < 1 || count > 10_000)
12
+ throw new Error("invalid multipart count");
13
+ const [complete, abort, head, pages] = await Promise.all([
14
+ signCompleteMultipartUpload(destination, r.final_object_key, r.upload_id, expiresIn),
15
+ signAbortMultipartUpload(destination, r.final_object_key, r.upload_id, expiresIn),
16
+ signFinalObjectHead(destination, r.final_object_key, expiresIn),
17
+ Promise.all(Array.from({ length: Math.ceil(count / 1000) }, (_, i) => signListMultipartUpload(destination, r.final_object_key, r.upload_id, expiresIn, { maxParts: 1000, partNumberMarker: i * 1000 })))
18
+ ]);
19
+ return { ...input.route, metadata: { ...input.route.metadata, complete_url: complete, abort_url: abort, final_head_url: head,
20
+ list_page_urls: pages, list_page_url: pages[Math.floor((r.part_number - 1) / 1000)], control_urls_expires_at: expiresAtIso(expiresIn) } };
21
+ }
22
+ if (!request || request.mode === "direct") {
23
+ const metadata = { ...input.route.metadata };
24
+ delete metadata.recovery_staging;
25
+ return { ...input.route, metadata };
26
+ }
27
+ if (!isS3CompatibleProvider(destination))
28
+ throw new Error("multipart recovery requires an S3-compatible destination");
29
+ const prefix = `${r.final_object_key}.beam-recovery/${input.transferId}/${encodeURIComponent(r.multipart_group_id).replace(/[!'()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}/`;
30
+ const objectKey = `${prefix}${r.part_number}/${request.attempt_id}`;
31
+ if (request.object_key && request.object_key !== objectKey)
32
+ throw new Error("recovery staging identity mismatch");
33
+ const client = createS3CompatibleClient(destination);
34
+ const sign = (command) => {
35
+ if (command instanceof PutObjectCommand)
36
+ return getSignedUrl(client, command, { expiresIn });
37
+ if (command instanceof HeadObjectCommand)
38
+ return getSignedUrl(client, command, { expiresIn });
39
+ if (command instanceof UploadPartCopyCommand)
40
+ return getSignedUrl(client, command, { expiresIn, unhoistableHeaders: new Set(["x-amz-copy-source", "x-amz-copy-source-if-match"]) });
41
+ if (command instanceof DeleteObjectCommand)
42
+ return getSignedUrl(client, command, { expiresIn });
43
+ return getSignedUrl(client, command, { expiresIn });
44
+ };
45
+ const metadata = { ...input.route.metadata };
46
+ delete metadata.recovery_staging;
47
+ if (request.operation === "list") {
48
+ metadata.recovery_listing = { prefix, url: await sign(new ListObjectsV2Command({ Bucket: destination.bucket, Prefix: prefix, ContinuationToken: request.continuation_token, MaxKeys: 1000 })) };
49
+ return { ...input.route, metadata };
50
+ }
51
+ const params = { Bucket: destination.bucket, Key: objectKey };
52
+ if (request.operation === "delete") {
53
+ metadata.recovery_delete = { object_key: objectKey, url: await sign(new DeleteObjectCommand(params)) };
54
+ return { ...input.route, metadata };
55
+ }
56
+ const copySource = `${encodeURIComponent(destination.bucket)}/${objectKey.split("/").map(encodeURIComponent).join("/")}`;
57
+ // R2 does not promise to enforce copy source conditions. Each attempt has its own object.
58
+ const condition = destination.provider === "s3" && request.etag ? `"${request.etag.replace(/^"|"$/g, "")}"` : undefined;
59
+ const [head, copy, remove] = await Promise.all([
60
+ sign(new HeadObjectCommand(params)),
61
+ sign(new UploadPartCopyCommand({ Bucket: destination.bucket, Key: r.final_object_key, UploadId: r.upload_id, PartNumber: r.part_number, CopySource: copySource, CopySourceIfMatch: condition })),
62
+ sign(new DeleteObjectCommand(params))
63
+ ]);
64
+ metadata.recovery_staging = { object_key: objectKey, attempt_id: request.attempt_id, head_url: head, copy_url: copy, delete_url: remove,
65
+ copy_headers: { "x-amz-copy-source": copySource, ...(condition ? { "x-amz-copy-source-if-match": condition } : {}) }, expires_at: expiresAtIso(expiresIn) };
66
+ return { ...input.route, metadata, ...(request.operation === "upload" ? { dest_url: await sign(new PutObjectCommand(params)) } : {}) };
67
+ }
4
68
  export function nowIso() {
5
69
  return new Date().toISOString();
6
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beam-network/sdk",
3
- "version": "0.5.20",
3
+ "version": "0.6.0",
4
4
  "description": "TypeScript SDK for BEAM transfer creation and management.",
5
5
  "type": "module",
6
6
  "license": "MIT",