@lossless.org/client 2.1.0 → 2.2.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/objectstorage/interfaces.d.ts +16 -2
- package/dist_ts/objectstorage/internal.exactupload.operations.js +148 -77
- package/dist_ts/objectstorage/internal.multipart.planner.d.ts +8 -2
- package/dist_ts/objectstorage/internal.multipart.planner.js +30 -1
- package/dist_ts/objectstorage/internal.multipart.stream.js +68 -19
- package/dist_ts/objectstorage/internal.multipart.types.d.ts +32 -3
- package/package.json +1 -1
- package/readme.md +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/objectstorage/interfaces.ts +16 -2
- package/ts/objectstorage/internal.exactupload.operations.ts +190 -97
- package/ts/objectstorage/internal.multipart.planner.ts +46 -2
- package/ts/objectstorage/internal.multipart.stream.ts +79 -21
- package/ts/objectstorage/internal.multipart.types.ts +35 -2
|
@@ -15,7 +15,10 @@ import {
|
|
|
15
15
|
mintExactUploadCapability,
|
|
16
16
|
type IExactUploadOperation,
|
|
17
17
|
} from './internal.exactupload.capability.js';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
planBoundedMultipartUpload,
|
|
20
|
+
planMultipartUpload,
|
|
21
|
+
} from './internal.multipart.planner.js';
|
|
19
22
|
import {
|
|
20
23
|
consumeExactMultipartStream,
|
|
21
24
|
MultipartStreamConsumptionError,
|
|
@@ -1306,6 +1309,69 @@ const abortMultipartUpload = async (
|
|
|
1306
1309
|
};
|
|
1307
1310
|
};
|
|
1308
1311
|
|
|
1312
|
+
/**
|
|
1313
|
+
* Plans the upload from its declared length, or from the maximum of a source
|
|
1314
|
+
* of undeclared length, and names that length for failure states.
|
|
1315
|
+
*/
|
|
1316
|
+
const planExactUpload = (optionsArg: IFastPutStreamExactOptions) => {
|
|
1317
|
+
const declaresLength = optionsArg.contentLength !== undefined;
|
|
1318
|
+
if (declaresLength === (optionsArg.maxContentLength !== undefined)) {
|
|
1319
|
+
throw new TypeError(
|
|
1320
|
+
'Exactly one of contentLength and maxContentLength must be given.',
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
if (declaresLength) {
|
|
1324
|
+
return {
|
|
1325
|
+
plan: planMultipartUpload({
|
|
1326
|
+
objectSize: optionsArg.contentLength!,
|
|
1327
|
+
requestedPartSize: optionsArg.requestedPartSize,
|
|
1328
|
+
requestedConcurrency: optionsArg.requestedConcurrency,
|
|
1329
|
+
}),
|
|
1330
|
+
lengthState: {
|
|
1331
|
+
expectedContentLength: optionsArg.contentLength,
|
|
1332
|
+
},
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
return {
|
|
1336
|
+
plan: planBoundedMultipartUpload({
|
|
1337
|
+
maxObjectSize: optionsArg.maxContentLength!,
|
|
1338
|
+
requestedPartSize: optionsArg.requestedPartSize,
|
|
1339
|
+
requestedConcurrency: optionsArg.requestedConcurrency,
|
|
1340
|
+
}),
|
|
1341
|
+
lengthState: {
|
|
1342
|
+
maxContentLength: optionsArg.maxContentLength,
|
|
1343
|
+
},
|
|
1344
|
+
};
|
|
1345
|
+
};
|
|
1346
|
+
|
|
1347
|
+
const sendConditionalZeroPut = (
|
|
1348
|
+
operationArg: IExactUploadOperation,
|
|
1349
|
+
optionsArg: IFastPutStreamExactOptions,
|
|
1350
|
+
contentTypeArg: string | undefined,
|
|
1351
|
+
onAttemptArg: () => void,
|
|
1352
|
+
) => {
|
|
1353
|
+
return sendOperationCommand(
|
|
1354
|
+
operationArg,
|
|
1355
|
+
(signalArg) => {
|
|
1356
|
+
onAttemptArg();
|
|
1357
|
+
return operationArg.client.send(
|
|
1358
|
+
new plugins.s3.PutObjectCommand({
|
|
1359
|
+
Bucket: operationArg.bucketName,
|
|
1360
|
+
Key: optionsArg.path,
|
|
1361
|
+
Body: new Uint8Array(0),
|
|
1362
|
+
ContentLength: 0,
|
|
1363
|
+
Metadata: optionsArg.nativeMetadata,
|
|
1364
|
+
ContentType: contentTypeArg,
|
|
1365
|
+
IfNoneMatch: '*',
|
|
1366
|
+
}),
|
|
1367
|
+
{
|
|
1368
|
+
abortSignal: signalArg,
|
|
1369
|
+
},
|
|
1370
|
+
);
|
|
1371
|
+
},
|
|
1372
|
+
);
|
|
1373
|
+
};
|
|
1374
|
+
|
|
1309
1375
|
export const fastPutStreamExact = async (
|
|
1310
1376
|
bucketRefArg: IExactUploadBucketRef,
|
|
1311
1377
|
optionsArg: IFastPutStreamExactOptions,
|
|
@@ -1316,11 +1382,7 @@ export const fastPutStreamExact = async (
|
|
|
1316
1382
|
const nativeObjectProperties = snapshotNativeObjectProperties(
|
|
1317
1383
|
optionsArg.nativeObjectProperties,
|
|
1318
1384
|
);
|
|
1319
|
-
const plan =
|
|
1320
|
-
objectSize: optionsArg.contentLength,
|
|
1321
|
-
requestedPartSize: optionsArg.requestedPartSize,
|
|
1322
|
-
requestedConcurrency: optionsArg.requestedConcurrency,
|
|
1323
|
-
});
|
|
1385
|
+
const { plan, lengthState } = planExactUpload(optionsArg);
|
|
1324
1386
|
|
|
1325
1387
|
let operation: IExactUploadOperation;
|
|
1326
1388
|
try {
|
|
@@ -1334,7 +1396,7 @@ export const fastPutStreamExact = async (
|
|
|
1334
1396
|
{
|
|
1335
1397
|
phase: 'capability',
|
|
1336
1398
|
destinationPath: optionsArg.path,
|
|
1337
|
-
|
|
1399
|
+
...lengthState,
|
|
1338
1400
|
destinationPublication: 'not-published',
|
|
1339
1401
|
multipartCleanup: {
|
|
1340
1402
|
status: 'not-required',
|
|
@@ -1353,7 +1415,7 @@ export const fastPutStreamExact = async (
|
|
|
1353
1415
|
{
|
|
1354
1416
|
phase: 'source',
|
|
1355
1417
|
destinationPath: optionsArg.path,
|
|
1356
|
-
|
|
1418
|
+
...lengthState,
|
|
1357
1419
|
destinationPublication: 'not-published',
|
|
1358
1420
|
multipartCleanup: {
|
|
1359
1421
|
status: 'not-required',
|
|
@@ -1393,7 +1455,7 @@ export const fastPutStreamExact = async (
|
|
|
1393
1455
|
{
|
|
1394
1456
|
phase: 'source',
|
|
1395
1457
|
destinationPath: optionsArg.path,
|
|
1396
|
-
|
|
1458
|
+
...lengthState,
|
|
1397
1459
|
destinationPublication: 'not-published',
|
|
1398
1460
|
multipartCleanup: {
|
|
1399
1461
|
status: 'not-required',
|
|
@@ -1404,24 +1466,12 @@ export const fastPutStreamExact = async (
|
|
|
1404
1466
|
}
|
|
1405
1467
|
|
|
1406
1468
|
try {
|
|
1407
|
-
const putOutput = await
|
|
1469
|
+
const putOutput = await sendConditionalZeroPut(
|
|
1408
1470
|
operation,
|
|
1409
|
-
|
|
1471
|
+
optionsArg,
|
|
1472
|
+
nativeObjectProperties?.contentType,
|
|
1473
|
+
() => {
|
|
1410
1474
|
putRequestAttempted = true;
|
|
1411
|
-
return operation.client.send(
|
|
1412
|
-
new plugins.s3.PutObjectCommand({
|
|
1413
|
-
Bucket: operation.bucketName,
|
|
1414
|
-
Key: optionsArg.path,
|
|
1415
|
-
Body: new Uint8Array(0),
|
|
1416
|
-
ContentLength: 0,
|
|
1417
|
-
Metadata: optionsArg.nativeMetadata,
|
|
1418
|
-
ContentType: nativeObjectProperties?.contentType,
|
|
1419
|
-
IfNoneMatch: '*',
|
|
1420
|
-
}),
|
|
1421
|
-
{
|
|
1422
|
-
abortSignal: signalArg,
|
|
1423
|
-
},
|
|
1424
|
-
);
|
|
1425
1475
|
},
|
|
1426
1476
|
);
|
|
1427
1477
|
return {
|
|
@@ -1441,7 +1491,7 @@ export const fastPutStreamExact = async (
|
|
|
1441
1491
|
{
|
|
1442
1492
|
phase: 'put-zero',
|
|
1443
1493
|
destinationPath: optionsArg.path,
|
|
1444
|
-
|
|
1494
|
+
...lengthState,
|
|
1445
1495
|
destinationPublication:
|
|
1446
1496
|
putRequestAttempted &&
|
|
1447
1497
|
!isDefinitiveConditionalWriteRejection(error)
|
|
@@ -1465,36 +1515,64 @@ export const fastPutStreamExact = async (
|
|
|
1465
1515
|
}
|
|
1466
1516
|
}
|
|
1467
1517
|
|
|
1468
|
-
|
|
1518
|
+
const bounded = plan.classification === 'bounded';
|
|
1519
|
+
let phase: TExactUploadFailurePhase = bounded ? 'source' : 'create-multipart';
|
|
1469
1520
|
let uploadId: string | undefined;
|
|
1470
1521
|
let createRequestAttempted = false;
|
|
1522
|
+
let createFailed = false;
|
|
1523
|
+
let createSettled = false;
|
|
1524
|
+
let zeroPutAttempted = false;
|
|
1471
1525
|
let completeRequestAttempted = false;
|
|
1472
1526
|
let operationFinishSettlement: Promise<void> | undefined;
|
|
1473
1527
|
let detachedStreamError: MultipartStreamConsumptionError | undefined;
|
|
1474
|
-
|
|
1475
|
-
|
|
1528
|
+
let multipartUploadCreation: Promise<string> | undefined;
|
|
1529
|
+
// A source of undeclared length creates the multipart upload with its first
|
|
1530
|
+
// part, so an empty source never leaves one behind.
|
|
1531
|
+
const ensureMultipartUpload = (): Promise<string> => {
|
|
1532
|
+
multipartUploadCreation ??= sendOperationCommand(
|
|
1476
1533
|
operation,
|
|
1477
1534
|
(signalArg) => {
|
|
1478
1535
|
createRequestAttempted = true;
|
|
1479
|
-
return operation.client
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1536
|
+
return operation.client
|
|
1537
|
+
.send(
|
|
1538
|
+
new plugins.s3.CreateMultipartUploadCommand({
|
|
1539
|
+
Bucket: operation.bucketName,
|
|
1540
|
+
Key: optionsArg.path,
|
|
1541
|
+
Metadata: optionsArg.nativeMetadata,
|
|
1542
|
+
ContentType: nativeObjectProperties?.contentType,
|
|
1543
|
+
}),
|
|
1544
|
+
{
|
|
1545
|
+
abortSignal: signalArg,
|
|
1546
|
+
},
|
|
1547
|
+
)
|
|
1548
|
+
.then((createOutputArg) => {
|
|
1549
|
+
// A create that ignored cancellation still owns its upload, so
|
|
1550
|
+
// deferred cleanup aborts it by this id.
|
|
1551
|
+
uploadId = getOptionalNonEmptyString(createOutputArg.UploadId);
|
|
1552
|
+
return createOutputArg;
|
|
1553
|
+
});
|
|
1554
|
+
},
|
|
1555
|
+
).then(
|
|
1556
|
+
(createOutputArg) => {
|
|
1557
|
+
createSettled = true;
|
|
1558
|
+
return requireNonEmptyString(
|
|
1559
|
+
createOutputArg.UploadId,
|
|
1560
|
+
'CreateMultipartUpload UploadId',
|
|
1489
1561
|
);
|
|
1490
1562
|
},
|
|
1563
|
+
(error: unknown) => {
|
|
1564
|
+
createSettled = true;
|
|
1565
|
+
createFailed = true;
|
|
1566
|
+
throw error;
|
|
1567
|
+
},
|
|
1491
1568
|
);
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
)
|
|
1496
|
-
|
|
1497
|
-
|
|
1569
|
+
return multipartUploadCreation;
|
|
1570
|
+
};
|
|
1571
|
+
try {
|
|
1572
|
+
if (!bounded) {
|
|
1573
|
+
await ensureMultipartUpload();
|
|
1574
|
+
phase = 'source';
|
|
1575
|
+
}
|
|
1498
1576
|
const completedParts: plugins.s3.CompletedPart[] = [];
|
|
1499
1577
|
let consumptionResult;
|
|
1500
1578
|
try {
|
|
@@ -1505,12 +1583,14 @@ export const fastPutStreamExact = async (
|
|
|
1505
1583
|
failureSettlementTimeoutMs:
|
|
1506
1584
|
optionsArg.failureSettlementTimeoutMs,
|
|
1507
1585
|
async onPart(partArg) {
|
|
1586
|
+
operation.assertCurrent();
|
|
1587
|
+
const partUploadId = await ensureMultipartUpload();
|
|
1508
1588
|
operation.assertCurrent();
|
|
1509
1589
|
const uploadPartOutput = await operation.client.send(
|
|
1510
1590
|
new plugins.s3.UploadPartCommand({
|
|
1511
1591
|
Bucket: operation.bucketName,
|
|
1512
1592
|
Key: optionsArg.path,
|
|
1513
|
-
UploadId:
|
|
1593
|
+
UploadId: partUploadId,
|
|
1514
1594
|
PartNumber: partArg.partNumber,
|
|
1515
1595
|
Body: partArg.data,
|
|
1516
1596
|
ContentLength: partArg.data.byteLength,
|
|
@@ -1532,9 +1612,15 @@ export const fastPutStreamExact = async (
|
|
|
1532
1612
|
} catch (error) {
|
|
1533
1613
|
if (
|
|
1534
1614
|
error instanceof MultipartStreamConsumptionError &&
|
|
1535
|
-
error.state.kind === 'part-callback'
|
|
1615
|
+
(error.state.kind === 'part-callback' || error.state.kind === 'aborted')
|
|
1536
1616
|
) {
|
|
1537
|
-
|
|
1617
|
+
// A source of undeclared length creates its upload with the first
|
|
1618
|
+
// part, so a failed or still outstanding create owns the failure.
|
|
1619
|
+
if (createFailed || (multipartUploadCreation && !createSettled)) {
|
|
1620
|
+
phase = 'create-multipart';
|
|
1621
|
+
} else if (error.state.kind === 'part-callback') {
|
|
1622
|
+
phase = 'upload-part';
|
|
1623
|
+
}
|
|
1538
1624
|
}
|
|
1539
1625
|
if (
|
|
1540
1626
|
error instanceof MultipartStreamConsumptionError &&
|
|
@@ -1548,6 +1634,28 @@ export const fastPutStreamExact = async (
|
|
|
1548
1634
|
throw error;
|
|
1549
1635
|
}
|
|
1550
1636
|
|
|
1637
|
+
if (!multipartUploadCreation) {
|
|
1638
|
+
// Only an empty source of undeclared length reaches this point.
|
|
1639
|
+
phase = 'put-zero';
|
|
1640
|
+
const putOutput = await sendConditionalZeroPut(
|
|
1641
|
+
operation,
|
|
1642
|
+
optionsArg,
|
|
1643
|
+
nativeObjectProperties?.contentType,
|
|
1644
|
+
() => {
|
|
1645
|
+
zeroPutAttempted = true;
|
|
1646
|
+
},
|
|
1647
|
+
);
|
|
1648
|
+
return {
|
|
1649
|
+
destinationPath: optionsArg.path,
|
|
1650
|
+
contentLength: 0,
|
|
1651
|
+
etag: requireNonEmptyString(putOutput.ETag, 'PutObject ETag'),
|
|
1652
|
+
versionId: getOptionalNonEmptyString(putOutput.VersionId),
|
|
1653
|
+
partCount: 0,
|
|
1654
|
+
peakBufferedBytes: consumptionResult.peakBufferedBytes,
|
|
1655
|
+
maximumBufferedBytes: consumptionResult.maximumBufferedBytes,
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
const completeUploadId = await multipartUploadCreation;
|
|
1551
1659
|
phase = 'complete-multipart';
|
|
1552
1660
|
const completeOutput = await sendOperationCommand(
|
|
1553
1661
|
operation,
|
|
@@ -1557,12 +1665,12 @@ export const fastPutStreamExact = async (
|
|
|
1557
1665
|
new plugins.s3.CompleteMultipartUploadCommand({
|
|
1558
1666
|
Bucket: operation.bucketName,
|
|
1559
1667
|
Key: optionsArg.path,
|
|
1560
|
-
UploadId:
|
|
1668
|
+
UploadId: completeUploadId,
|
|
1561
1669
|
MultipartUpload: {
|
|
1562
1670
|
Parts: completedParts,
|
|
1563
1671
|
},
|
|
1564
1672
|
IfNoneMatch: '*',
|
|
1565
|
-
MpuObjectSize:
|
|
1673
|
+
MpuObjectSize: consumptionResult.bytesConsumed,
|
|
1566
1674
|
}),
|
|
1567
1675
|
{
|
|
1568
1676
|
abortSignal: signalArg,
|
|
@@ -1572,7 +1680,7 @@ export const fastPutStreamExact = async (
|
|
|
1572
1680
|
);
|
|
1573
1681
|
return {
|
|
1574
1682
|
destinationPath: optionsArg.path,
|
|
1575
|
-
contentLength:
|
|
1683
|
+
contentLength: consumptionResult.bytesConsumed,
|
|
1576
1684
|
etag: requireNonEmptyString(
|
|
1577
1685
|
completeOutput.ETag,
|
|
1578
1686
|
'CompleteMultipartUpload ETag',
|
|
@@ -1586,21 +1694,21 @@ export const fastPutStreamExact = async (
|
|
|
1586
1694
|
let multipartCleanup: TExactUploadMultipartCleanup;
|
|
1587
1695
|
const primaryProviderSettlement =
|
|
1588
1696
|
getDetachedOperationSendSettlement(operation.signal);
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
multipartCleanup = {
|
|
1594
|
-
status: 'deferred',
|
|
1595
|
-
};
|
|
1596
|
-
const callbackGatedCleanup = detachedStreamError.callbackSettlement
|
|
1697
|
+
// Aborts the multipart upload once the given work settles. A create that
|
|
1698
|
+
// was still in flight may only report its UploadId by then.
|
|
1699
|
+
const abortAfterSettlement = (settlementArg: Promise<unknown>) =>
|
|
1700
|
+
settlementArg
|
|
1597
1701
|
.then(async () => {
|
|
1702
|
+
await getDetachedOperationSendSettlement(operation.signal);
|
|
1703
|
+
if (!uploadId) {
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1598
1706
|
try {
|
|
1599
1707
|
operation.assertLifecycleCurrent();
|
|
1600
1708
|
const abortResult = await abortMultipartUpload(
|
|
1601
1709
|
operation,
|
|
1602
1710
|
optionsArg.path,
|
|
1603
|
-
uploadId
|
|
1711
|
+
uploadId,
|
|
1604
1712
|
);
|
|
1605
1713
|
await abortResult.settlement;
|
|
1606
1714
|
} catch {
|
|
@@ -1611,35 +1719,27 @@ export const fastPutStreamExact = async (
|
|
|
1611
1719
|
() => undefined,
|
|
1612
1720
|
() => undefined,
|
|
1613
1721
|
);
|
|
1722
|
+
if (
|
|
1723
|
+
(uploadId || createRequestAttempted) &&
|
|
1724
|
+
detachedStreamError?.state.cleanup.callbacks === 'detached'
|
|
1725
|
+
) {
|
|
1726
|
+
multipartCleanup = {
|
|
1727
|
+
status: 'deferred',
|
|
1728
|
+
};
|
|
1614
1729
|
operationFinishSettlement = Promise.allSettled([
|
|
1615
|
-
|
|
1730
|
+
abortAfterSettlement(detachedStreamError.callbackSettlement),
|
|
1616
1731
|
detachedStreamError.ownedSettlement,
|
|
1617
1732
|
]).then(() => undefined);
|
|
1618
|
-
} else if (
|
|
1733
|
+
} else if (
|
|
1734
|
+
(uploadId || createRequestAttempted) &&
|
|
1735
|
+
primaryProviderSettlement
|
|
1736
|
+
) {
|
|
1619
1737
|
multipartCleanup = {
|
|
1620
1738
|
status: 'deferred',
|
|
1621
1739
|
};
|
|
1622
|
-
const providerGatedCleanup = primaryProviderSettlement
|
|
1623
|
-
.then(async () => {
|
|
1624
|
-
try {
|
|
1625
|
-
operation.assertLifecycleCurrent();
|
|
1626
|
-
const abortResult = await abortMultipartUpload(
|
|
1627
|
-
operation,
|
|
1628
|
-
optionsArg.path,
|
|
1629
|
-
uploadId!,
|
|
1630
|
-
);
|
|
1631
|
-
await abortResult.settlement;
|
|
1632
|
-
} catch {
|
|
1633
|
-
// Lifecycle close makes deferred cleanup indeterminate.
|
|
1634
|
-
}
|
|
1635
|
-
})
|
|
1636
|
-
.then(
|
|
1637
|
-
() => undefined,
|
|
1638
|
-
() => undefined,
|
|
1639
|
-
);
|
|
1640
1740
|
operationFinishSettlement = Promise.allSettled([
|
|
1641
1741
|
primaryProviderSettlement,
|
|
1642
|
-
|
|
1742
|
+
abortAfterSettlement(primaryProviderSettlement),
|
|
1643
1743
|
]).then(() => undefined);
|
|
1644
1744
|
} else if (uploadId) {
|
|
1645
1745
|
const abortResult = await abortMultipartUpload(
|
|
@@ -1653,20 +1753,11 @@ export const fastPutStreamExact = async (
|
|
|
1653
1753
|
}
|
|
1654
1754
|
} else if (createRequestAttempted) {
|
|
1655
1755
|
multipartCleanup = {
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
: {
|
|
1661
|
-
status: 'abort-failed' as const,
|
|
1662
|
-
cause: new Error(
|
|
1663
|
-
'The multipart upload could not be aborted because no UploadId was returned.',
|
|
1664
|
-
),
|
|
1665
|
-
}),
|
|
1756
|
+
status: 'abort-failed',
|
|
1757
|
+
cause: new Error(
|
|
1758
|
+
'The multipart upload could not be aborted because no UploadId was returned.',
|
|
1759
|
+
),
|
|
1666
1760
|
};
|
|
1667
|
-
if (primaryProviderSettlement) {
|
|
1668
|
-
operationFinishSettlement = primaryProviderSettlement;
|
|
1669
|
-
}
|
|
1670
1761
|
} else {
|
|
1671
1762
|
multipartCleanup = {
|
|
1672
1763
|
status: 'not-required',
|
|
@@ -1691,10 +1782,12 @@ export const fastPutStreamExact = async (
|
|
|
1691
1782
|
{
|
|
1692
1783
|
phase,
|
|
1693
1784
|
destinationPath: optionsArg.path,
|
|
1694
|
-
|
|
1785
|
+
...lengthState,
|
|
1695
1786
|
destinationPublication:
|
|
1696
|
-
|
|
1697
|
-
|
|
1787
|
+
(
|
|
1788
|
+
(phase === 'complete-multipart' && completeRequestAttempted) ||
|
|
1789
|
+
(phase === 'put-zero' && zeroPutAttempted)
|
|
1790
|
+
) &&
|
|
1698
1791
|
!isDefinitiveConditionalWriteRejection(error)
|
|
1699
1792
|
? 'ambiguous'
|
|
1700
1793
|
: 'not-published',
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
IMultipartBoundedPlanningOptions,
|
|
3
|
+
IMultipartBoundedUploadPlan,
|
|
2
4
|
IMultipartPlanningOptions,
|
|
3
|
-
|
|
5
|
+
TMultipartDeclaredUploadPlan,
|
|
4
6
|
} from './internal.multipart.types.js';
|
|
5
7
|
|
|
6
8
|
export const MULTIPART_MIN_PART_SIZE = 5 * 1024 * 1024;
|
|
@@ -43,7 +45,7 @@ const normalizeRequestedConcurrency = (
|
|
|
43
45
|
|
|
44
46
|
export const planMultipartUpload = (
|
|
45
47
|
optionsArg: IMultipartPlanningOptions,
|
|
46
|
-
):
|
|
48
|
+
): TMultipartDeclaredUploadPlan => {
|
|
47
49
|
requireSafeNonNegativeInteger(optionsArg.objectSize, 'objectSize');
|
|
48
50
|
if (optionsArg.objectSize > MULTIPART_MAX_OBJECT_SIZE) {
|
|
49
51
|
throw new RangeError(
|
|
@@ -88,3 +90,45 @@ export const planMultipartUpload = (
|
|
|
88
90
|
) + Math.min(optionsArg.objectSize, partSize),
|
|
89
91
|
};
|
|
90
92
|
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Plans an upload of undeclared length that must not exceed
|
|
96
|
+
* `maxObjectSize`. The part size grows with the maximum so that the largest
|
|
97
|
+
* allowed source still fits into the multipart part limit.
|
|
98
|
+
*/
|
|
99
|
+
export const planBoundedMultipartUpload = (
|
|
100
|
+
optionsArg: IMultipartBoundedPlanningOptions,
|
|
101
|
+
): IMultipartBoundedUploadPlan => {
|
|
102
|
+
if (!Number.isSafeInteger(optionsArg.maxObjectSize) || optionsArg.maxObjectSize < 1) {
|
|
103
|
+
throw new TypeError('maxObjectSize must be a positive safe integer.');
|
|
104
|
+
}
|
|
105
|
+
if (optionsArg.maxObjectSize > MULTIPART_MAX_OBJECT_SIZE) {
|
|
106
|
+
throw new RangeError(
|
|
107
|
+
`maxObjectSize exceeds the ${MULTIPART_MAX_OBJECT_SIZE} byte multipart limit.`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const requestedConcurrency = normalizeRequestedConcurrency(
|
|
111
|
+
optionsArg.requestedConcurrency,
|
|
112
|
+
);
|
|
113
|
+
const preferredPartSize = normalizeRequestedPartSize(optionsArg.requestedPartSize);
|
|
114
|
+
const partSize = Math.max(
|
|
115
|
+
preferredPartSize,
|
|
116
|
+
Math.ceil(optionsArg.maxObjectSize / MULTIPART_MAX_PART_COUNT),
|
|
117
|
+
);
|
|
118
|
+
if (partSize > MULTIPART_MAX_PART_SIZE) {
|
|
119
|
+
throw new RangeError('The maximum cannot be represented by legal multipart parts.');
|
|
120
|
+
}
|
|
121
|
+
const maxPartCount = Math.ceil(optionsArg.maxObjectSize / partSize);
|
|
122
|
+
const concurrency = Math.min(requestedConcurrency, maxPartCount);
|
|
123
|
+
return {
|
|
124
|
+
classification: 'bounded',
|
|
125
|
+
maxObjectSize: optionsArg.maxObjectSize,
|
|
126
|
+
partSize,
|
|
127
|
+
maxPartCount,
|
|
128
|
+
concurrency,
|
|
129
|
+
maximumBufferedBytes: Math.min(
|
|
130
|
+
optionsArg.maxObjectSize,
|
|
131
|
+
partSize * concurrency,
|
|
132
|
+
) + Math.min(optionsArg.maxObjectSize, partSize),
|
|
133
|
+
};
|
|
134
|
+
};
|