@beam-network/sdk 0.5.19 → 0.5.20

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
@@ -161,7 +161,7 @@ Create and resume accept an ownership `signal`. Aborting it stops initial routes
161
161
 
162
162
  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
163
 
164
- On completed provider transfers, Runtime may include an `integrity_audit_challenge` in status. Before releasing provider signing state, the TypeScript client signs exact read-only source and final-destination GET ranges and submits `transfer.integrity_audit_grants`; audit submission failures are best-effort and do not change transfer completion.
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.
165
165
 
166
166
  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
167
 
package/dist/client.js CHANGED
@@ -120,8 +120,10 @@ export class BeamClient {
120
120
  async transferStatus(transferId) {
121
121
  validateId(transferId, "transferId");
122
122
  const status = await this.control.request("transfer.status", { transfer_id: transferId }, { transferId });
123
- if (["completed", "failed", "cancelled"].includes(status.status)) {
123
+ if (status.integrity_audit_challenge) {
124
124
  await this.submitIntegrityAuditGrantsIfPresent(status).catch(() => undefined);
125
+ }
126
+ if (["completed", "failed", "cancelled"].includes(status.status)) {
125
127
  this.control.releaseRecoveryLease(transferId);
126
128
  this.stopRecoverySigner(transferId);
127
129
  }
@@ -935,7 +937,13 @@ export class BeamClient {
935
937
  }
936
938
  const submission = signer(challenge);
937
939
  this.integrityAuditSubmissions.set(challenge.audit_id, submission);
938
- await submission;
940
+ try {
941
+ await submission;
942
+ }
943
+ catch (error) {
944
+ this.integrityAuditSubmissions.delete(challenge.audit_id);
945
+ throw error;
946
+ }
939
947
  }
940
948
  async submitProviderIntegrityAuditGrants(input) {
941
949
  if (input.challenge.transfer_id !== input.prepared.transfer_id) {
@@ -958,12 +966,17 @@ export class BeamClient {
958
966
  }
959
967
  const source = input.sourcesById.get(chunk.source_id);
960
968
  const destination = input.destinationsById.get(chunk.destination_id);
969
+ const plannedSource = input.prepared.plan_descriptor.sources.find((candidate) => candidate.source_id === chunk.source_id);
961
970
  if (!source || !destination) {
962
971
  throw new Error("integrity audit provider configuration is unavailable");
963
972
  }
973
+ const sourceEtag = plannedSource?.metadata?.etag;
974
+ const sourceVersionId = plannedSource?.metadata?.version_id;
964
975
  const [sourceGrant, destinationGrant] = await Promise.all([
965
976
  signSourceReadRange({
966
977
  source,
978
+ ...(typeof sourceEtag === "string" ? { ifMatch: sourceEtag } : {}),
979
+ ...(typeof sourceVersionId === "string" ? { versionId: sourceVersionId } : {}),
967
980
  offset: chunk.source_offset,
968
981
  length: chunk.range_length,
969
982
  expiresIn: input.expiresIn,
@@ -972,6 +985,7 @@ export class BeamClient {
972
985
  signDestinationReadRange({
973
986
  destination,
974
987
  objectKey: chunk.final_object_key,
988
+ ...(chunk.final_object_etag ? { ifMatch: chunk.final_object_etag } : {}),
975
989
  offset: chunk.destination_offset,
976
990
  length: chunk.range_length,
977
991
  expiresIn: input.expiresIn,
package/dist/models.d.ts CHANGED
@@ -132,6 +132,7 @@ export interface IntegrityAuditChallengeChunk {
132
132
  destination_offset: number;
133
133
  range_length: number;
134
134
  final_object_key: string;
135
+ final_object_etag: string | null;
135
136
  }
136
137
  export interface IntegrityAuditChallenge {
137
138
  audit_id: string;
@@ -91,6 +91,7 @@ export declare function signSourceReadRange(input: {
91
91
  export declare function signDestinationReadRange(input: {
92
92
  destination: ProviderDestinationConfig;
93
93
  objectKey: string;
94
+ ifMatch?: string;
94
95
  offset: number;
95
96
  length: number;
96
97
  expiresIn: number;
@@ -337,11 +337,14 @@ export async function signDestinationReadRange(input) {
337
337
  url: await getSignedUrl(client, new GetObjectCommand({
338
338
  Bucket: input.destination.bucket,
339
339
  Key: input.objectKey,
340
- Range: range
340
+ Range: range,
341
+ IfMatch: input.ifMatch
341
342
  }), { expiresIn: input.expiresIn }),
342
- headers: { Range: range }
343
+ headers: { Range: range, ...(input.ifMatch ? { "If-Match": input.ifMatch } : {}) }
343
344
  };
344
345
  }
346
+ if (input.ifMatch)
347
+ throw new Error("conditional destination ranges require S3-compatible storage");
345
348
  if (isHippiusProvider(input.destination)) {
346
349
  return {
347
350
  url: await hippiusPresign(input.fetchImpl ?? globalThis.fetch, input.destination.base_url ?? "https://api.hippius.com", input.destination.api_token, input.destination.bucket, input.objectKey, "get", input.expiresIn),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beam-network/sdk",
3
- "version": "0.5.19",
3
+ "version": "0.5.20",
4
4
  "description": "TypeScript SDK for BEAM transfer creation and management.",
5
5
  "type": "module",
6
6
  "license": "MIT",