@beam-network/sdk 0.5.17-dev.2 → 0.5.17-dev.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 +2 -2
- package/dist/client.d.ts +1 -0
- package/dist/client.js +40 -16
- package/dist/models.d.ts +1 -0
- package/dist/provider-signing.d.ts +1 -0
- package/dist/provider-signing.js +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Example:
|
|
|
12
12
|
import { BeamClient, R2ProviderConfig, S3ProviderConfig } from "@beam-network/sdk";
|
|
13
13
|
|
|
14
14
|
const beam = new BeamClient({
|
|
15
|
-
apiKey: "
|
|
15
|
+
apiKey: "b1m_your_key"
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
const transfer = await beam.createTransfer({
|
|
@@ -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
|
-
|
|
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.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ export declare class BeamClient {
|
|
|
56
56
|
resumeProviderTransfer(input: ProviderTransferResumeInput): Promise<TransferPrepareResponse>;
|
|
57
57
|
transferStatus(transferId: string): Promise<TransferStatusInfo>;
|
|
58
58
|
distributeTransfer(transferId: string): Promise<DistributeResponse>;
|
|
59
|
+
private requestTransferCancellation;
|
|
59
60
|
cancelTransfer(transferId: string): Promise<TransferCancelResponse>;
|
|
60
61
|
planTransfer(input: {
|
|
61
62
|
sources: PlanningHttpSource[];
|
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 (
|
|
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
|
}
|
|
@@ -134,12 +136,15 @@ export class BeamClient {
|
|
|
134
136
|
idempotencyKey: `transfer:${transferId}:distribute`
|
|
135
137
|
});
|
|
136
138
|
}
|
|
137
|
-
|
|
139
|
+
requestTransferCancellation(transferId) {
|
|
138
140
|
validateId(transferId, "transferId");
|
|
139
|
-
|
|
141
|
+
return this.control.request("transfer.cancel", { transfer_id: transferId }, {
|
|
140
142
|
transferId,
|
|
141
143
|
idempotencyKey: `transfer:${transferId}:cancel`
|
|
142
144
|
});
|
|
145
|
+
}
|
|
146
|
+
async cancelTransfer(transferId) {
|
|
147
|
+
const result = await this.requestTransferCancellation(transferId);
|
|
143
148
|
this.control.releaseRecoveryLease(transferId);
|
|
144
149
|
this.stopRecoverySigner(transferId);
|
|
145
150
|
return result;
|
|
@@ -486,15 +491,22 @@ export class BeamClient {
|
|
|
486
491
|
throw new BeamRouteRecoveryPendingError(prepared.transfer_id, error);
|
|
487
492
|
}
|
|
488
493
|
else {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
494
|
+
try {
|
|
495
|
+
await cancelAndAbortProviderFailure({
|
|
496
|
+
cancelTransfer: () => this.requestTransferCancellation(prepared.transfer_id),
|
|
497
|
+
transferId: prepared.transfer_id,
|
|
498
|
+
cause: error,
|
|
499
|
+
uploads: multipartUploads,
|
|
500
|
+
abortBeforeCancel: !routeStreamBeginAttempted,
|
|
501
|
+
cleanupConcurrency: this.multipartControlConcurrency
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
finally {
|
|
505
|
+
// The retained recovery input owns the same destination objects used by
|
|
506
|
+
// multipart cleanup. Releasing it earlier scrubs their scoped credentials
|
|
507
|
+
// before abortMultipartUpload can clean up already-created uploads.
|
|
508
|
+
this.control.releaseRecoveryLease(prepared.transfer_id);
|
|
509
|
+
}
|
|
498
510
|
}
|
|
499
511
|
}
|
|
500
512
|
throw error;
|
|
@@ -925,7 +937,13 @@ export class BeamClient {
|
|
|
925
937
|
}
|
|
926
938
|
const submission = signer(challenge);
|
|
927
939
|
this.integrityAuditSubmissions.set(challenge.audit_id, submission);
|
|
928
|
-
|
|
940
|
+
try {
|
|
941
|
+
await submission;
|
|
942
|
+
}
|
|
943
|
+
catch (error) {
|
|
944
|
+
this.integrityAuditSubmissions.delete(challenge.audit_id);
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
929
947
|
}
|
|
930
948
|
async submitProviderIntegrityAuditGrants(input) {
|
|
931
949
|
if (input.challenge.transfer_id !== input.prepared.transfer_id) {
|
|
@@ -948,12 +966,17 @@ export class BeamClient {
|
|
|
948
966
|
}
|
|
949
967
|
const source = input.sourcesById.get(chunk.source_id);
|
|
950
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);
|
|
951
970
|
if (!source || !destination) {
|
|
952
971
|
throw new Error("integrity audit provider configuration is unavailable");
|
|
953
972
|
}
|
|
973
|
+
const sourceEtag = plannedSource?.metadata?.etag;
|
|
974
|
+
const sourceVersionId = plannedSource?.metadata?.version_id;
|
|
954
975
|
const [sourceGrant, destinationGrant] = await Promise.all([
|
|
955
976
|
signSourceReadRange({
|
|
956
977
|
source,
|
|
978
|
+
...(typeof sourceEtag === "string" ? { ifMatch: sourceEtag } : {}),
|
|
979
|
+
...(typeof sourceVersionId === "string" ? { versionId: sourceVersionId } : {}),
|
|
957
980
|
offset: chunk.source_offset,
|
|
958
981
|
length: chunk.range_length,
|
|
959
982
|
expiresIn: input.expiresIn,
|
|
@@ -962,6 +985,7 @@ export class BeamClient {
|
|
|
962
985
|
signDestinationReadRange({
|
|
963
986
|
destination,
|
|
964
987
|
objectKey: chunk.final_object_key,
|
|
988
|
+
...(chunk.final_object_etag ? { ifMatch: chunk.final_object_etag } : {}),
|
|
965
989
|
offset: chunk.destination_offset,
|
|
966
990
|
length: chunk.range_length,
|
|
967
991
|
expiresIn: input.expiresIn,
|
|
@@ -1393,9 +1417,9 @@ async function sha256Bytes(value) {
|
|
|
1393
1417
|
function hex(bytes) {
|
|
1394
1418
|
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1395
1419
|
}
|
|
1396
|
-
async function cancelTransferAfterProviderFailure(
|
|
1420
|
+
async function cancelTransferAfterProviderFailure(cancelTransfer, transferId, cause) {
|
|
1397
1421
|
try {
|
|
1398
|
-
const result = await
|
|
1422
|
+
const result = await cancelTransfer();
|
|
1399
1423
|
if (!result.success) {
|
|
1400
1424
|
throw new Error(result.message ?? `Beam rejected cancellation for ${transferId}`);
|
|
1401
1425
|
}
|
|
@@ -1418,7 +1442,7 @@ async function cancelAndAbortProviderFailure(input) {
|
|
|
1418
1442
|
}
|
|
1419
1443
|
}
|
|
1420
1444
|
try {
|
|
1421
|
-
await cancelTransferAfterProviderFailure(input.
|
|
1445
|
+
await cancelTransferAfterProviderFailure(input.cancelTransfer, input.transferId, input.cause);
|
|
1422
1446
|
}
|
|
1423
1447
|
catch (error) {
|
|
1424
1448
|
cancelError = error;
|
package/dist/models.d.ts
CHANGED
package/dist/provider-signing.js
CHANGED
|
@@ -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),
|