@beam-network/sdk 0.5.17-dev.1 → 0.5.17-dev.2
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 +4 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +84 -76
- package/dist/models.d.ts +5 -12
- package/dist/provider-signing.d.ts +1 -0
- package/dist/provider-signing.js +7 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,6 +155,10 @@ The client signs up to 64 routes concurrently by default and emits 2,048-route l
|
|
|
155
155
|
|
|
156
156
|
Callers that need durable recovery accounting can provide `onMultipartGroupReady`. The SDK invokes it after creating a multipart upload and before streaming that group’s signed routes. Its payload contains transfer, source, destination, object-key, upload, size, part-count, and expiry identities only; it never includes credentials, request headers, or signed URLs. A rejected callback aborts the newly created upload and fails preparation closed.
|
|
157
157
|
|
|
158
|
+
`resumeProviderTransfer` requires the complete saved callback payloads in `multipartGroups`. It validates every transfer/source/destination/object/size/part coordinate, then uses the same route-stream implementation as creation with those upload IDs. Missing, duplicate, or changed identities fail before new provider uploads or route publication. If a process died before recording an upload identity, treat it as uncertain cleanup instead of inventing a replacement. The `onPrepared` callback also runs during resume, before routes are streamed.
|
|
159
|
+
|
|
160
|
+
Create and resume accept an ownership `signal`. Aborting it stops initial routes, multipart creation, and recovery signing/replay; it does not cancel the transfer or abort a replacement owner's uploads. User cancellation still uses `cancelTransfer` and verified provider cleanup. `prepareProviderSource` accepts the same signal for metadata requests.
|
|
161
|
+
|
|
158
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.
|
|
159
163
|
|
|
160
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.
|
package/dist/client.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ export declare class BeamClient {
|
|
|
84
84
|
autoDistribute?: boolean;
|
|
85
85
|
}): Promise<AttachSignedUrlsResponse>;
|
|
86
86
|
prepareProviderTransfer(input: ProviderTransferCreateInput): Promise<TransferPrepareResponse>;
|
|
87
|
+
private executeProviderTransfer;
|
|
87
88
|
/**
|
|
88
89
|
* Negotiate every Hugging Face destination before the plan exists.
|
|
89
90
|
*
|
package/dist/client.js
CHANGED
|
@@ -115,62 +115,7 @@ export class BeamClient {
|
|
|
115
115
|
}
|
|
116
116
|
async resumeProviderTransfer(input) {
|
|
117
117
|
validateId(input.transferId, "transferId");
|
|
118
|
-
|
|
119
|
-
const requestedSignedUrlFlow = input.signedUrlFlow ?? "signed_url";
|
|
120
|
-
const retainedRecoveryInput = {
|
|
121
|
-
...input,
|
|
122
|
-
sources: input.sources.map((source) => ({ ...source })),
|
|
123
|
-
destinations: input.destinations.map((destination) => ({ ...destination }))
|
|
124
|
-
};
|
|
125
|
-
const preparedDestinations = retainedRecoveryInput.destinations.map((destination, index) => prepareProviderDestination(destination, { index }));
|
|
126
|
-
const destinationsById = new Map();
|
|
127
|
-
preparedDestinations.forEach((preparedDestination, index) => {
|
|
128
|
-
const destination = retainedRecoveryInput.destinations[index];
|
|
129
|
-
if (!destination) {
|
|
130
|
-
throw new Error(`missing provider destination config for prepared destination ${preparedDestination.destination_id}`);
|
|
131
|
-
}
|
|
132
|
-
destinationsById.set(preparedDestination.destination_id, destination);
|
|
133
|
-
});
|
|
134
|
-
const preparedSources = await Promise.all(retainedRecoveryInput.sources.map((source, index) => prepareProviderSource(source, { index, expiresIn, fetchImpl: this.fetchImpl })));
|
|
135
|
-
const resumePrepareRequestKey = `transfer:${input.transferId}:prepare:resume:${randomUuid()}`;
|
|
136
|
-
const prepared = await this.prepareTransferWithRequestKey({
|
|
137
|
-
transferId: input.transferId,
|
|
138
|
-
sources: preparedSources,
|
|
139
|
-
destinations: preparedDestinations,
|
|
140
|
-
name: input.name,
|
|
141
|
-
testMode: input.testMode,
|
|
142
|
-
signedUrlFlow: requestedSignedUrlFlow
|
|
143
|
-
}, resumePrepareRequestKey);
|
|
144
|
-
if (!prepared.success)
|
|
145
|
-
return prepared;
|
|
146
|
-
if (prepared.transfer_id !== input.transferId) {
|
|
147
|
-
throw new Error("resumed provider transfer id mismatch");
|
|
148
|
-
}
|
|
149
|
-
const sourcesById = new Map();
|
|
150
|
-
preparedSources.forEach((preparedSource, index) => {
|
|
151
|
-
const source = retainedRecoveryInput.sources[index];
|
|
152
|
-
if (!source) {
|
|
153
|
-
throw new Error(`missing provider source config for prepared source ${preparedSource.source_id}`);
|
|
154
|
-
}
|
|
155
|
-
sourcesById.set(preparedSource.source_id, source);
|
|
156
|
-
});
|
|
157
|
-
await this.startProviderRouteRecoverySigner({
|
|
158
|
-
prepared,
|
|
159
|
-
sourcesById,
|
|
160
|
-
destinationsById,
|
|
161
|
-
multipartUploads: new Map(),
|
|
162
|
-
expiresIn
|
|
163
|
-
});
|
|
164
|
-
this.control.registerRecoveryLease({
|
|
165
|
-
transferId: prepared.transfer_id,
|
|
166
|
-
planFingerprint: prepared.plan_fingerprint,
|
|
167
|
-
coordinateChecksum: prepared.coordinate_checksum,
|
|
168
|
-
replayRoutes: async () => {
|
|
169
|
-
throw new Error("route replay is unavailable after provider action resume; route signing remains available");
|
|
170
|
-
},
|
|
171
|
-
disposeSecrets: () => clearProviderResumeSecrets(retainedRecoveryInput)
|
|
172
|
-
});
|
|
173
|
-
return prepared;
|
|
118
|
+
return this.executeProviderTransfer(input, input);
|
|
174
119
|
}
|
|
175
120
|
async transferStatus(transferId) {
|
|
176
121
|
validateId(transferId, "transferId");
|
|
@@ -314,6 +259,10 @@ export class BeamClient {
|
|
|
314
259
|
}
|
|
315
260
|
}
|
|
316
261
|
async prepareProviderTransfer(input) {
|
|
262
|
+
return this.executeProviderTransfer(input);
|
|
263
|
+
}
|
|
264
|
+
async executeProviderTransfer(input, resume) {
|
|
265
|
+
input.signal?.throwIfAborted();
|
|
317
266
|
const expiresIn = input.expiresIn ?? 3600;
|
|
318
267
|
const requestedSignedUrlFlow = input.signedUrlFlow ?? "signed_url";
|
|
319
268
|
const retainedRecoveryInput = {
|
|
@@ -335,7 +284,7 @@ export class BeamClient {
|
|
|
335
284
|
destinationsById.set(preparedDestination.destination_id, destination);
|
|
336
285
|
});
|
|
337
286
|
await input.throwIfCancelled?.();
|
|
338
|
-
const preparedSources = await Promise.all(retainedRecoveryInput.sources.map((source, index) => prepareProviderSource(source, { index, expiresIn, fetchImpl: this.fetchImpl })));
|
|
287
|
+
const preparedSources = await Promise.all(retainedRecoveryInput.sources.map((source, index) => prepareProviderSource(source, { index, expiresIn, fetchImpl: this.fetchImpl, signal: input.signal })));
|
|
339
288
|
await input.throwIfCancelled?.();
|
|
340
289
|
const huggingFace = await this.planHuggingFaceUploads({
|
|
341
290
|
sources: retainedRecoveryInput.sources,
|
|
@@ -345,16 +294,22 @@ export class BeamClient {
|
|
|
345
294
|
});
|
|
346
295
|
await input.throwIfCancelled?.();
|
|
347
296
|
await input.onBeforeTransferPrepare?.();
|
|
348
|
-
|
|
297
|
+
input.signal?.throwIfAborted();
|
|
298
|
+
const prepareInput = {
|
|
349
299
|
sources: preparedSources,
|
|
350
300
|
destinations: preparedDestinations,
|
|
351
301
|
name: input.name,
|
|
352
302
|
testMode: input.testMode,
|
|
353
303
|
chunkSize: huggingFace.chunkSize ?? input.chunkSize,
|
|
354
304
|
signedUrlFlow: requestedSignedUrlFlow,
|
|
355
|
-
idempotencyKey: input.idempotencyKey,
|
|
356
|
-
|
|
357
|
-
|
|
305
|
+
...(resume ? { transferId: resume.transferId } : { idempotencyKey: input.idempotencyKey, routeGenerationId: input.routeGenerationId })
|
|
306
|
+
};
|
|
307
|
+
const prepared = resume
|
|
308
|
+
? await this.prepareTransferWithRequestKey(prepareInput, `transfer:${resume.transferId}:prepare:resume:${randomUuid()}`)
|
|
309
|
+
: await this.prepareTransfer(prepareInput);
|
|
310
|
+
if (resume && prepared.transfer_id !== resume.transferId)
|
|
311
|
+
throw new Error("resumed provider transfer id mismatch");
|
|
312
|
+
input.signal?.throwIfAborted();
|
|
358
313
|
if (!prepared.success) {
|
|
359
314
|
return prepared;
|
|
360
315
|
}
|
|
@@ -372,9 +327,12 @@ export class BeamClient {
|
|
|
372
327
|
sourcesById.set(preparedSource.source_id, source);
|
|
373
328
|
});
|
|
374
329
|
const multipartUploads = new Map();
|
|
330
|
+
if (resume)
|
|
331
|
+
restoreProviderMultipartIdentities(prepared, destinationsById, resume.multipartGroups, multipartUploads);
|
|
375
332
|
const recoveryMultipartUploads = new Map();
|
|
376
333
|
const autoDistribute = input.distribute !== false;
|
|
377
334
|
let initialThrowIfCancelled = input.throwIfCancelled;
|
|
335
|
+
const assertOwnership = () => input.signal?.throwIfAborted();
|
|
378
336
|
const routeStreamLock = new AsyncMutex();
|
|
379
337
|
const releaseInitialStream = await routeStreamLock.acquire();
|
|
380
338
|
const streamPreparedRoutes = async (routeGenerationId, recoveryReplay) => {
|
|
@@ -387,9 +345,12 @@ export class BeamClient {
|
|
|
387
345
|
let multipartManifestTask = null;
|
|
388
346
|
let multipartGroupWaiters = new Map();
|
|
389
347
|
let foregroundCancelled = false;
|
|
390
|
-
|
|
348
|
+
assertOwnership();
|
|
349
|
+
const throwIfCancelled = async (transferId) => {
|
|
391
350
|
try {
|
|
392
|
-
|
|
351
|
+
assertOwnership();
|
|
352
|
+
if (!recoveryReplay)
|
|
353
|
+
await initialThrowIfCancelled?.(transferId);
|
|
393
354
|
}
|
|
394
355
|
catch (error) {
|
|
395
356
|
foregroundCancelled = true;
|
|
@@ -424,7 +385,9 @@ export class BeamClient {
|
|
|
424
385
|
expiresIn,
|
|
425
386
|
signedUrlFlow: requestedSignedUrlFlow,
|
|
426
387
|
concurrency: this.multipartControlConcurrency,
|
|
388
|
+
signal: input.signal,
|
|
427
389
|
onGroupReady: async (state) => {
|
|
390
|
+
assertOwnership();
|
|
428
391
|
recoveryMultipartUploads.set(state.manifest.multipart_group_id, state);
|
|
429
392
|
validateMultipartGroupManifest([state.manifest], prepared.transfer_id);
|
|
430
393
|
await input.onMultipartGroupReady?.(multipartGroupIdentity(prepared.transfer_id, state.manifest));
|
|
@@ -436,6 +399,7 @@ export class BeamClient {
|
|
|
436
399
|
const streamNextCompletedRoute = async () => {
|
|
437
400
|
const settled = await Promise.race([...pendingRoutes].map((pending) => pending.then((route) => ({ pending, route }))));
|
|
438
401
|
pendingRoutes.delete(settled.pending);
|
|
402
|
+
assertOwnership();
|
|
439
403
|
await routeStream.addRoute(settled.route);
|
|
440
404
|
signedInWindow += 1;
|
|
441
405
|
if (signedInWindow === ROUTE_STREAM_BATCH_ROUTES) {
|
|
@@ -493,12 +457,21 @@ export class BeamClient {
|
|
|
493
457
|
await streamNextCompletedRoute();
|
|
494
458
|
const multipartGroupManifest = await multipartManifestTask;
|
|
495
459
|
validateMultipartGroupManifest(multipartGroupManifest, prepared.transfer_id);
|
|
460
|
+
assertOwnership();
|
|
496
461
|
const attached = await routeStream.complete();
|
|
497
462
|
if (!attached.success) {
|
|
498
463
|
throw new Error(attached.error ?? attached.message ?? "route stream failed");
|
|
499
464
|
}
|
|
500
465
|
}
|
|
501
466
|
catch (error) {
|
|
467
|
+
if (input.signal?.aborted) {
|
|
468
|
+
this.stopRecoverySigner(prepared.transfer_id);
|
|
469
|
+
this.control.releaseRecoveryLease(prepared.transfer_id);
|
|
470
|
+
await Promise.allSettled(pendingRoutes);
|
|
471
|
+
if (multipartManifestTask)
|
|
472
|
+
await Promise.allSettled([multipartManifestTask]);
|
|
473
|
+
throw input.signal.reason;
|
|
474
|
+
}
|
|
502
475
|
if (routeStream)
|
|
503
476
|
await routeStream.abort().catch(() => undefined);
|
|
504
477
|
await Promise.allSettled(pendingRoutes);
|
|
@@ -532,8 +505,14 @@ export class BeamClient {
|
|
|
532
505
|
sourcesById,
|
|
533
506
|
destinationsById,
|
|
534
507
|
multipartUploads: recoveryMultipartUploads,
|
|
508
|
+
signal: input.signal,
|
|
535
509
|
expiresIn
|
|
536
510
|
});
|
|
511
|
+
const stopOwnedRecovery = () => {
|
|
512
|
+
this.stopRecoverySigner(prepared.transfer_id);
|
|
513
|
+
this.control.releaseRecoveryLease(prepared.transfer_id);
|
|
514
|
+
};
|
|
515
|
+
input.signal?.addEventListener("abort", stopOwnedRecovery, { once: true });
|
|
537
516
|
this.control.registerRecoveryLease({
|
|
538
517
|
transferId: prepared.transfer_id,
|
|
539
518
|
planFingerprint: prepared.plan_fingerprint,
|
|
@@ -541,10 +520,14 @@ export class BeamClient {
|
|
|
541
520
|
replayRoutes: async (routeGenerationId) => {
|
|
542
521
|
await routeStreamLock.run(() => streamPreparedRoutes(routeGenerationId, true));
|
|
543
522
|
},
|
|
544
|
-
disposeSecrets: () =>
|
|
523
|
+
disposeSecrets: () => {
|
|
524
|
+
input.signal?.removeEventListener("abort", stopOwnedRecovery);
|
|
525
|
+
clearRecoverySecrets(retainedRecoveryInput);
|
|
526
|
+
}
|
|
545
527
|
});
|
|
546
528
|
try {
|
|
547
529
|
try {
|
|
530
|
+
assertOwnership();
|
|
548
531
|
await input.onPrepared?.(prepared);
|
|
549
532
|
}
|
|
550
533
|
catch (error) {
|
|
@@ -788,9 +771,11 @@ export class BeamClient {
|
|
|
788
771
|
async startProviderRouteRecoverySigner(input) {
|
|
789
772
|
this.stopRecoverySigner(input.prepared.transfer_id);
|
|
790
773
|
const stop = await this.control.serveRouteRecoverySigner(input.prepared.transfer_id, async (payload) => {
|
|
774
|
+
input.signal?.throwIfAborted();
|
|
791
775
|
if (payload.route_generation_id.length === 0)
|
|
792
776
|
throw new Error("route recovery generation is required");
|
|
793
777
|
const routes = await mapOrderedWithConcurrency(payload.chunks, this.routeSigningConcurrency, async (requested) => {
|
|
778
|
+
input.signal?.throwIfAborted();
|
|
794
779
|
if (requested.route_generation_id !== payload.route_generation_id) {
|
|
795
780
|
throw new Error("route recovery chunk generation mismatch");
|
|
796
781
|
}
|
|
@@ -1505,6 +1490,37 @@ function createMultipartGroupWaiters(prepared, destinationsById) {
|
|
|
1505
1490
|
}
|
|
1506
1491
|
return waiters;
|
|
1507
1492
|
}
|
|
1493
|
+
function restoreProviderMultipartIdentities(prepared, destinations, identities, uploads) {
|
|
1494
|
+
if (!Array.isArray(identities))
|
|
1495
|
+
throw new Error("provider_multipart_recovery_identity_required");
|
|
1496
|
+
const expected = new Map();
|
|
1497
|
+
for (const source of prepared.plan_descriptor.sources) {
|
|
1498
|
+
for (const target of prepared.plan_descriptor.destinations) {
|
|
1499
|
+
const destination = destinations.get(target.destination_id);
|
|
1500
|
+
if (!destination)
|
|
1501
|
+
throw new Error("provider_multipart_recovery_destination_invalid");
|
|
1502
|
+
if (isDirectPutDestination(destination))
|
|
1503
|
+
continue;
|
|
1504
|
+
const objectKey = target.final_object_keys[source.source_id];
|
|
1505
|
+
if (!objectKey)
|
|
1506
|
+
throw new Error("provider_multipart_recovery_object_invalid");
|
|
1507
|
+
const key = multipartGroupStateKey(prepared.transfer_id, target.destination_id, source.source_id, objectKey);
|
|
1508
|
+
expected.set(key, { destination, objectKey, source, destinationId: target.destination_id });
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (expected.size !== identities.length)
|
|
1512
|
+
throw new Error("provider_multipart_recovery_incomplete");
|
|
1513
|
+
for (const identity of identities) {
|
|
1514
|
+
const group = expected.get(identity.multipartGroupId);
|
|
1515
|
+
if (!group || uploads.has(identity.multipartGroupId) || identity.transferId !== prepared.transfer_id ||
|
|
1516
|
+
identity.sourceId !== group.source.source_id || identity.destinationId !== group.destinationId ||
|
|
1517
|
+
identity.objectKey !== group.objectKey || identity.expectedObjectSize !== group.source.size ||
|
|
1518
|
+
identity.expectedPartCount !== group.source.chunk_count || !identity.uploadId?.trim()) {
|
|
1519
|
+
throw new Error("provider_multipart_recovery_identity_invalid");
|
|
1520
|
+
}
|
|
1521
|
+
uploads.set(identity.multipartGroupId, { destination: group.destination, objectKey: group.objectKey, uploadId: identity.uploadId });
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1508
1524
|
async function createMultipartGroupManifest(input) {
|
|
1509
1525
|
const groups = input.prepared.plan_descriptor.sources.flatMap((source) => input.prepared.plan_descriptor.destinations.map((destination) => ({ source, destination })));
|
|
1510
1526
|
const results = await mapOrderedWithConcurrency(groups, input.concurrency, async ({ source, destination }) => {
|
|
@@ -1523,11 +1539,13 @@ async function createMultipartGroupManifest(input) {
|
|
|
1523
1539
|
}
|
|
1524
1540
|
multipartGroupId = multipartGroupStateKey(input.prepared.transfer_id, destination.destination_id, source.source_id, finalObjectKey);
|
|
1525
1541
|
const finalObjectMetadata = { "beam-transfer-id": input.prepared.transfer_id };
|
|
1542
|
+
input.signal?.throwIfAborted();
|
|
1526
1543
|
const retainedUpload = input.multipartUploads.get(multipartGroupId);
|
|
1527
1544
|
const uploadId = retainedUpload?.uploadId ?? await createMultipartUpload({
|
|
1528
1545
|
destination: destinationConfig,
|
|
1529
1546
|
objectKey: finalObjectKey,
|
|
1530
|
-
metadata: finalObjectMetadata
|
|
1547
|
+
metadata: finalObjectMetadata,
|
|
1548
|
+
signal: input.signal
|
|
1531
1549
|
});
|
|
1532
1550
|
const createdUpload = !retainedUpload;
|
|
1533
1551
|
if (createdUpload) {
|
|
@@ -1574,7 +1592,7 @@ async function createMultipartGroupManifest(input) {
|
|
|
1574
1592
|
return { ok: true, state };
|
|
1575
1593
|
}
|
|
1576
1594
|
catch (error) {
|
|
1577
|
-
if (createdUpload) {
|
|
1595
|
+
if (createdUpload && !input.signal?.aborted) {
|
|
1578
1596
|
try {
|
|
1579
1597
|
await abortMultipartUpload(destinationConfig, finalObjectKey, uploadId);
|
|
1580
1598
|
input.multipartUploads.delete(multipartGroupId);
|
|
@@ -1858,13 +1876,3 @@ function clearRecoverySecrets(input) {
|
|
|
1858
1876
|
}
|
|
1859
1877
|
}
|
|
1860
1878
|
}
|
|
1861
|
-
function clearProviderResumeSecrets(input) {
|
|
1862
|
-
const secretKeys = new Set(["access_key_id", "secret_access_key", "session_token", "api_token", "token", "password"]);
|
|
1863
|
-
for (const provider of [...input.sources, ...input.destinations]) {
|
|
1864
|
-
const record = provider;
|
|
1865
|
-
for (const key of secretKeys) {
|
|
1866
|
-
if (key in record)
|
|
1867
|
-
record[key] = "";
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
}
|
package/dist/models.d.ts
CHANGED
|
@@ -171,19 +171,10 @@ export interface TransferStatusInfo {
|
|
|
171
171
|
};
|
|
172
172
|
integrity_audit_challenge?: IntegrityAuditChallenge;
|
|
173
173
|
}
|
|
174
|
-
export interface ProviderTransferResumeInput {
|
|
174
|
+
export interface ProviderTransferResumeInput extends Omit<ProviderTransferCreateInput, "idempotencyKey" | "routeGenerationId"> {
|
|
175
175
|
transferId: string;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
name?: string;
|
|
179
|
-
/**
|
|
180
|
-
* Maps to BeamCore test_mode. Must match the original prepared transfer.
|
|
181
|
-
*/
|
|
182
|
-
testMode?: boolean;
|
|
183
|
-
expiresIn?: number;
|
|
184
|
-
signedUrlFlow?: SignedUrlFlow;
|
|
185
|
-
/** Requested plan chunk size. BeamCore may raise it; the response carries the effective value. */
|
|
186
|
-
chunkSize?: number;
|
|
176
|
+
/** Complete durable multipart identities from onMultipartGroupReady; resume never creates replacement uploads. */
|
|
177
|
+
multipartGroups: ProviderMultipartGroupIdentity[];
|
|
187
178
|
}
|
|
188
179
|
export interface TransferTerminalEvent {
|
|
189
180
|
schema_version: "transfer-client-control/v6";
|
|
@@ -290,6 +281,8 @@ export declare const HuggingFaceProviderConfig: Readonly<{
|
|
|
290
281
|
create(input: HuggingFaceProviderConfigInput): HuggingFaceProviderConfig;
|
|
291
282
|
}>;
|
|
292
283
|
export interface ProviderTransferCreateInput {
|
|
284
|
+
/** Ownership fence. Aborting stops signing/replay without cancelling a replacement owner. */
|
|
285
|
+
signal?: AbortSignal;
|
|
293
286
|
sources: ProviderSourceConfig[];
|
|
294
287
|
destinations: ProviderDestinationConfig[];
|
|
295
288
|
name?: string;
|
|
@@ -6,6 +6,7 @@ export declare function prepareProviderSource(source: ProviderSourceConfig, opti
|
|
|
6
6
|
index?: number;
|
|
7
7
|
expiresIn?: number;
|
|
8
8
|
fetchImpl?: typeof fetch;
|
|
9
|
+
signal?: AbortSignal;
|
|
9
10
|
}): Promise<PreparedHttpSource>;
|
|
10
11
|
export declare function prepareProviderSourceForPlan(source: ProviderSourceConfig, options?: {
|
|
11
12
|
index?: number;
|
package/dist/provider-signing.js
CHANGED
|
@@ -10,10 +10,15 @@ export function expiresAtIso(expiresIn) {
|
|
|
10
10
|
export async function prepareProviderSource(source, options = {}) {
|
|
11
11
|
const index = options.index ?? 0;
|
|
12
12
|
const expiresIn = options.expiresIn ?? 3600;
|
|
13
|
+
options.signal?.throwIfAborted();
|
|
14
|
+
const baseFetch = options.fetchImpl ?? globalThis.fetch;
|
|
15
|
+
const fetchImpl = options.signal
|
|
16
|
+
? (input, init) => baseFetch(input, { ...init, signal: AbortSignal.any([options.signal, ...(init?.signal ? [init.signal] : [])]) })
|
|
17
|
+
: baseFetch;
|
|
13
18
|
if (isS3CompatibleProvider(source)) {
|
|
14
19
|
const endpoint = s3CompatibleEndpoint(source);
|
|
15
20
|
const client = createS3CompatibleClient(source, endpoint);
|
|
16
|
-
const head = await client.send(new HeadObjectCommand({ Bucket: source.bucket, Key: source.key }));
|
|
21
|
+
const head = await client.send(new HeadObjectCommand({ Bucket: source.bucket, Key: source.key }), { abortSignal: options.signal });
|
|
17
22
|
const getUrl = await getSignedUrl(client, new GetObjectCommand({ Bucket: source.bucket, Key: source.key }), { expiresIn });
|
|
18
23
|
return {
|
|
19
24
|
source_id: source.id ?? `src_${index}`,
|
|
@@ -33,7 +38,6 @@ export async function prepareProviderSource(source, options = {}) {
|
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
40
|
if (isHippiusProvider(source)) {
|
|
36
|
-
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
37
41
|
const baseUrl = source.base_url ?? "https://api.hippius.com";
|
|
38
42
|
const size = await hippiusObjectSize(fetchImpl, baseUrl, source.api_token, source.bucket, source.key);
|
|
39
43
|
const url = await hippiusPresign(fetchImpl, baseUrl, source.api_token, source.bucket, source.key, "get", expiresIn);
|
|
@@ -49,7 +53,7 @@ export async function prepareProviderSource(source, options = {}) {
|
|
|
49
53
|
};
|
|
50
54
|
}
|
|
51
55
|
if (isHuggingFaceProvider(source)) {
|
|
52
|
-
const metadata = await huggingFaceFileMetadata(
|
|
56
|
+
const metadata = await huggingFaceFileMetadata(fetchImpl, source);
|
|
53
57
|
return {
|
|
54
58
|
source_id: source.id ?? `src_${index}`,
|
|
55
59
|
type: "http",
|