@lossless.org/client 0.1.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/.smartconfig.json +37 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.losslessorgclient.d.ts +32 -0
- package/dist_ts/classes.losslessorgclient.js +103 -0
- package/dist_ts/core/classes.error.d.ts +16 -0
- package/dist_ts/core/classes.error.js +29 -0
- package/dist_ts/core/classes.operation.d.ts +17 -0
- package/dist_ts/core/classes.operation.js +70 -0
- package/dist_ts/core/interfaces.d.ts +18 -0
- package/dist_ts/core/interfaces.js +2 -0
- package/dist_ts/index.d.ts +4 -0
- package/dist_ts/index.js +3 -0
- package/dist_ts/nosqldb/classes.atomicdelete.d.ts +26 -0
- package/dist_ts/nosqldb/classes.atomicdelete.js +44 -0
- package/dist_ts/nosqldb/classes.atomicfindoneandupdate.d.ts +27 -0
- package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +86 -0
- package/dist_ts/nosqldb/classes.atomicupdate.d.ts +26 -0
- package/dist_ts/nosqldb/classes.atomicupdate.js +55 -0
- package/dist_ts/nosqldb/classes.bootstraprecord.d.ts +14 -0
- package/dist_ts/nosqldb/classes.bootstraprecord.js +80 -0
- package/dist_ts/nosqldb/classes.bsonassertion.d.ts +2 -0
- package/dist_ts/nosqldb/classes.bsonassertion.js +29 -0
- package/dist_ts/nosqldb/classes.bsonobjectid.d.ts +2 -0
- package/dist_ts/nosqldb/classes.bsonobjectid.js +50 -0
- package/dist_ts/nosqldb/classes.bsonsnapshot.d.ts +3 -0
- package/dist_ts/nosqldb/classes.bsonsnapshot.js +126 -0
- package/dist_ts/nosqldb/classes.cached-document.d.ts +27 -0
- package/dist_ts/nosqldb/classes.cached-document.js +112 -0
- package/dist_ts/nosqldb/classes.collection.d.ts +302 -0
- package/dist_ts/nosqldb/classes.collection.js +1568 -0
- package/dist_ts/nosqldb/classes.collectionfactory.d.ts +30 -0
- package/dist_ts/nosqldb/classes.collectionfactory.js +65 -0
- package/dist_ts/nosqldb/classes.collectionlifecycle.d.ts +4 -0
- package/dist_ts/nosqldb/classes.collectionlifecycle.js +15 -0
- package/dist_ts/nosqldb/classes.collectionpreparation.d.ts +34 -0
- package/dist_ts/nosqldb/classes.collectionpreparation.js +134 -0
- package/dist_ts/nosqldb/classes.collectiontopology.d.ts +47 -0
- package/dist_ts/nosqldb/classes.collectiontopology.js +388 -0
- package/dist_ts/nosqldb/classes.connection.d.ts +18 -0
- package/dist_ts/nosqldb/classes.connection.js +23 -0
- package/dist_ts/nosqldb/classes.convenience.d.ts +1 -0
- package/dist_ts/nosqldb/classes.convenience.js +5 -0
- package/dist_ts/nosqldb/classes.cursor.d.ts +15 -0
- package/dist_ts/nosqldb/classes.cursor.js +82 -0
- package/dist_ts/nosqldb/classes.cursoroperation.d.ts +7 -0
- package/dist_ts/nosqldb/classes.cursoroperation.js +36 -0
- package/dist_ts/nosqldb/classes.db.d.ts +98 -0
- package/dist_ts/nosqldb/classes.db.js +434 -0
- package/dist_ts/nosqldb/classes.distributedcoordinator.d.ts +52 -0
- package/dist_ts/nosqldb/classes.distributedcoordinator.js +314 -0
- package/dist_ts/nosqldb/classes.doc.d.ts +722 -0
- package/dist_ts/nosqldb/classes.doc.js +2976 -0
- package/dist_ts/nosqldb/classes.easystore.d.ts +42 -0
- package/dist_ts/nosqldb/classes.easystore.js +199 -0
- package/dist_ts/nosqldb/classes.exactpersistence.d.ts +141 -0
- package/dist_ts/nosqldb/classes.exactpersistence.js +1110 -0
- package/dist_ts/nosqldb/classes.lucene.adapter.d.ts +173 -0
- package/dist_ts/nosqldb/classes.lucene.adapter.js +623 -0
- package/dist_ts/nosqldb/classes.mongoadministration.d.ts +73 -0
- package/dist_ts/nosqldb/classes.mongoadministration.js +497 -0
- package/dist_ts/nosqldb/classes.namespaceinspection.d.ts +42 -0
- package/dist_ts/nosqldb/classes.namespaceinspection.js +138 -0
- package/dist_ts/nosqldb/classes.operationbudget.d.ts +22 -0
- package/dist_ts/nosqldb/classes.operationbudget.js +65 -0
- package/dist_ts/nosqldb/classes.ordinarypersistence.d.ts +18 -0
- package/dist_ts/nosqldb/classes.ordinarypersistence.js +71 -0
- package/dist_ts/nosqldb/classes.persistence.d.ts +14 -0
- package/dist_ts/nosqldb/classes.persistence.js +34 -0
- package/dist_ts/nosqldb/classes.session.d.ts +28 -0
- package/dist_ts/nosqldb/classes.session.js +246 -0
- package/dist_ts/nosqldb/classes.storedinspection.d.ts +44 -0
- package/dist_ts/nosqldb/classes.storedinspection.js +100 -0
- package/dist_ts/nosqldb/classes.testdatabase.d.ts +56 -0
- package/dist_ts/nosqldb/classes.testdatabase.js +250 -0
- package/dist_ts/nosqldb/classes.watcher.d.ts +37 -0
- package/dist_ts/nosqldb/classes.watcher.js +98 -0
- package/dist_ts/nosqldb/index.d.ts +27 -0
- package/dist_ts/nosqldb/index.js +18 -0
- package/dist_ts/nosqldb/logging.d.ts +2 -0
- package/dist_ts/nosqldb/logging.js +3 -0
- package/dist_ts/nosqldb/plugins.d.ts +17 -0
- package/dist_ts/nosqldb/plugins.js +20 -0
- package/dist_ts/nosqldb/shim.d.ts +1 -0
- package/dist_ts/nosqldb/shim.js +8 -0
- package/dist_ts/nosqldb/testsupport.d.ts +3 -0
- package/dist_ts/nosqldb/testsupport.js +3 -0
- package/dist_ts/objectstorage/classes.bucket.d.ts +227 -0
- package/dist_ts/objectstorage/classes.bucket.js +939 -0
- package/dist_ts/objectstorage/classes.connection.d.ts +21 -0
- package/dist_ts/objectstorage/classes.connection.js +27 -0
- package/dist_ts/objectstorage/classes.directory.d.ts +124 -0
- package/dist_ts/objectstorage/classes.directory.js +335 -0
- package/dist_ts/objectstorage/classes.exactpathpurgeerror.d.ts +5 -0
- package/dist_ts/objectstorage/classes.exactpathpurgeerror.js +18 -0
- package/dist_ts/objectstorage/classes.exactuploaderror.d.ts +5 -0
- package/dist_ts/objectstorage/classes.exactuploaderror.js +19 -0
- package/dist_ts/objectstorage/classes.file.d.ts +91 -0
- package/dist_ts/objectstorage/classes.file.js +255 -0
- package/dist_ts/objectstorage/classes.listcursor.d.ts +41 -0
- package/dist_ts/objectstorage/classes.listcursor.js +67 -0
- package/dist_ts/objectstorage/classes.metadata.d.ts +57 -0
- package/dist_ts/objectstorage/classes.metadata.js +113 -0
- package/dist_ts/objectstorage/classes.smartbucket.d.ts +57 -0
- package/dist_ts/objectstorage/classes.smartbucket.js +357 -0
- package/dist_ts/objectstorage/classes.trash.d.ts +11 -0
- package/dist_ts/objectstorage/classes.trash.js +22 -0
- package/dist_ts/objectstorage/classes.watcher.d.ts +79 -0
- package/dist_ts/objectstorage/classes.watcher.js +249 -0
- package/dist_ts/objectstorage/helpers.d.ts +25 -0
- package/dist_ts/objectstorage/helpers.js +215 -0
- package/dist_ts/objectstorage/index.d.ts +13 -0
- package/dist_ts/objectstorage/index.js +13 -0
- package/dist_ts/objectstorage/interfaces.d.ts +276 -0
- package/dist_ts/objectstorage/interfaces.js +2 -0
- package/dist_ts/objectstorage/internal.exactpathpurge.operations.d.ts +11 -0
- package/dist_ts/objectstorage/internal.exactpathpurge.operations.js +1069 -0
- package/dist_ts/objectstorage/internal.exactupload.capability.d.ts +40 -0
- package/dist_ts/objectstorage/internal.exactupload.capability.js +382 -0
- package/dist_ts/objectstorage/internal.exactupload.operations.d.ts +11 -0
- package/dist_ts/objectstorage/internal.exactupload.operations.js +1104 -0
- package/dist_ts/objectstorage/internal.multipart.planner.d.ts +8 -0
- package/dist_ts/objectstorage/internal.multipart.planner.js +63 -0
- package/dist_ts/objectstorage/internal.multipart.stream.d.ts +17 -0
- package/dist_ts/objectstorage/internal.multipart.stream.js +440 -0
- package/dist_ts/objectstorage/internal.multipart.types.d.ts +82 -0
- package/dist_ts/objectstorage/internal.multipart.types.js +2 -0
- package/dist_ts/objectstorage/plugins.d.ts +17 -0
- package/dist_ts/objectstorage/plugins.js +23 -0
- package/dist_ts/plugins.d.ts +4 -0
- package/dist_ts/plugins.js +5 -0
- package/dist_ts/sqldb/classes.clickhouseconnection.d.ts +39 -0
- package/dist_ts/sqldb/classes.clickhouseconnection.js +225 -0
- package/dist_ts/sqldb/classes.sqlconnection.d.ts +50 -0
- package/dist_ts/sqldb/classes.sqlconnection.js +376 -0
- package/dist_ts/sqldb/classes.sqltable.d.ts +29 -0
- package/dist_ts/sqldb/classes.sqltable.js +70 -0
- package/dist_ts/sqldb/clickhouse/classes.metricwriter.d.ts +27 -0
- package/dist_ts/sqldb/clickhouse/classes.metricwriter.js +89 -0
- package/dist_ts/sqldb/clickhouse/index.d.ts +9 -0
- package/dist_ts/sqldb/clickhouse/index.js +13 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.d.ts +88 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.js +304 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.d.ts +28 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.js +46 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.d.ts +43 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.js +155 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.resultset.d.ts +13 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.resultset.js +36 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.d.ts +49 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.js +106 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.d.ts +51 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.js +96 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.plugins.d.ts +5 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.plugins.js +6 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.types.d.ts +53 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.types.js +41 -0
- package/dist_ts/sqldb/index.d.ts +6 -0
- package/dist_ts/sqldb/index.js +6 -0
- package/dist_ts/sqldb/interfaces.d.ts +53 -0
- package/dist_ts/sqldb/interfaces.js +19 -0
- package/dist_ts/sqldb/internal.sqlpool.d.ts +51 -0
- package/dist_ts/sqldb/internal.sqlpool.js +214 -0
- package/dist_ts/sqldb/plugins.clickhouse.d.ts +4 -0
- package/dist_ts/sqldb/plugins.clickhouse.js +5 -0
- package/dist_ts/sqldb/plugins.mariadb.d.ts +4 -0
- package/dist_ts/sqldb/plugins.mariadb.js +5 -0
- package/docs/source/smartbucket/license.md +19 -0
- package/docs/source/smartclickhouse/license.md +19 -0
- package/license.md +21 -0
- package/package.json +101 -0
- package/readme.md +157 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.losslessorgclient.ts +104 -0
- package/ts/core/classes.error.ts +31 -0
- package/ts/core/classes.operation.ts +60 -0
- package/ts/core/interfaces.ts +24 -0
- package/ts/index.ts +4 -0
- package/ts/nosqldb/classes.atomicdelete.ts +81 -0
- package/ts/nosqldb/classes.atomicfindoneandupdate.ts +152 -0
- package/ts/nosqldb/classes.atomicupdate.ts +99 -0
- package/ts/nosqldb/classes.bootstraprecord.ts +107 -0
- package/ts/nosqldb/classes.bsonassertion.ts +25 -0
- package/ts/nosqldb/classes.bsonobjectid.ts +45 -0
- package/ts/nosqldb/classes.bsonsnapshot.ts +105 -0
- package/ts/nosqldb/classes.cached-document.ts +67 -0
- package/ts/nosqldb/classes.collection.ts +2399 -0
- package/ts/nosqldb/classes.collectionfactory.ts +87 -0
- package/ts/nosqldb/classes.collectionlifecycle.ts +24 -0
- package/ts/nosqldb/classes.collectionpreparation.ts +164 -0
- package/ts/nosqldb/classes.collectiontopology.ts +645 -0
- package/ts/nosqldb/classes.connection.ts +31 -0
- package/ts/nosqldb/classes.convenience.ts +5 -0
- package/ts/nosqldb/classes.cursor.ts +88 -0
- package/ts/nosqldb/classes.cursoroperation.ts +38 -0
- package/ts/nosqldb/classes.db.ts +637 -0
- package/ts/nosqldb/classes.distributedcoordinator.ts +305 -0
- package/ts/nosqldb/classes.doc.ts +4928 -0
- package/ts/nosqldb/classes.easystore.ts +154 -0
- package/ts/nosqldb/classes.exactpersistence.ts +1974 -0
- package/ts/nosqldb/classes.lucene.adapter.ts +780 -0
- package/ts/nosqldb/classes.mongoadministration.ts +867 -0
- package/ts/nosqldb/classes.namespaceinspection.ts +199 -0
- package/ts/nosqldb/classes.operationbudget.ts +75 -0
- package/ts/nosqldb/classes.ordinarypersistence.ts +84 -0
- package/ts/nosqldb/classes.persistence.ts +62 -0
- package/ts/nosqldb/classes.session.ts +389 -0
- package/ts/nosqldb/classes.storedinspection.ts +151 -0
- package/ts/nosqldb/classes.testdatabase.ts +272 -0
- package/ts/nosqldb/classes.watcher.ts +96 -0
- package/ts/nosqldb/index.ts +51 -0
- package/ts/nosqldb/logging.ts +3 -0
- package/ts/nosqldb/plugins.ts +37 -0
- package/ts/nosqldb/shim.ts +6 -0
- package/ts/nosqldb/testsupport.ts +13 -0
- package/ts/objectstorage/classes.bucket.ts +1114 -0
- package/ts/objectstorage/classes.connection.ts +36 -0
- package/ts/objectstorage/classes.directory.ts +425 -0
- package/ts/objectstorage/classes.exactpathpurgeerror.ts +24 -0
- package/ts/objectstorage/classes.exactuploaderror.ts +25 -0
- package/ts/objectstorage/classes.file.ts +303 -0
- package/ts/objectstorage/classes.listcursor.ts +89 -0
- package/ts/objectstorage/classes.metadata.ts +134 -0
- package/ts/objectstorage/classes.smartbucket.ts +445 -0
- package/ts/objectstorage/classes.trash.ts +30 -0
- package/ts/objectstorage/classes.watcher.ts +289 -0
- package/ts/objectstorage/helpers.ts +254 -0
- package/ts/objectstorage/index.ts +13 -0
- package/ts/objectstorage/interfaces.ts +389 -0
- package/ts/objectstorage/internal.exactpathpurge.operations.ts +1936 -0
- package/ts/objectstorage/internal.exactupload.capability.ts +580 -0
- package/ts/objectstorage/internal.exactupload.operations.ts +1715 -0
- package/ts/objectstorage/internal.multipart.planner.ts +90 -0
- package/ts/objectstorage/internal.multipart.stream.ts +606 -0
- package/ts/objectstorage/internal.multipart.types.ts +106 -0
- package/ts/objectstorage/plugins.ts +35 -0
- package/ts/plugins.ts +4 -0
- package/ts/sqldb/classes.clickhouseconnection.ts +189 -0
- package/ts/sqldb/classes.sqlconnection.ts +350 -0
- package/ts/sqldb/classes.sqltable.ts +70 -0
- package/ts/sqldb/clickhouse/classes.metricwriter.ts +72 -0
- package/ts/sqldb/clickhouse/index.ts +16 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.ts +372 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.ts +52 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.ts +120 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.resultset.ts +44 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.ts +123 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.ts +114 -0
- package/ts/sqldb/clickhouse/smartclickhouse.plugins.ts +5 -0
- package/ts/sqldb/clickhouse/smartclickhouse.types.ts +134 -0
- package/ts/sqldb/index.ts +6 -0
- package/ts/sqldb/interfaces.ts +61 -0
- package/ts/sqldb/internal.sqlpool.ts +179 -0
- package/ts/sqldb/plugins.clickhouse.ts +4 -0
- package/ts/sqldb/plugins.mariadb.ts +4 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IMultipartPlanningOptions,
|
|
3
|
+
TMultipartUploadPlan,
|
|
4
|
+
} from './internal.multipart.types.js';
|
|
5
|
+
|
|
6
|
+
export const MULTIPART_MIN_PART_SIZE = 5 * 1024 * 1024;
|
|
7
|
+
export const MULTIPART_MAX_PART_SIZE = 5 * 1024 * 1024 * 1024;
|
|
8
|
+
export const MULTIPART_MAX_PART_COUNT = 10_000;
|
|
9
|
+
export const MULTIPART_MAX_OBJECT_SIZE = 5 * 1024 * 1024 * 1024 * 1024;
|
|
10
|
+
export const MULTIPART_DEFAULT_CONCURRENCY = 1;
|
|
11
|
+
export const MULTIPART_MAX_CONCURRENCY = 32;
|
|
12
|
+
|
|
13
|
+
const requireSafeNonNegativeInteger = (valueArg: number, nameArg: string): void => {
|
|
14
|
+
if (!Number.isSafeInteger(valueArg) || valueArg < 0) {
|
|
15
|
+
throw new TypeError(`${nameArg} must be a non-negative safe integer.`);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const normalizeRequestedPartSize = (requestedPartSizeArg: number | undefined): number => {
|
|
20
|
+
if (requestedPartSizeArg === undefined) {
|
|
21
|
+
return MULTIPART_MIN_PART_SIZE;
|
|
22
|
+
}
|
|
23
|
+
if (!Number.isSafeInteger(requestedPartSizeArg) || requestedPartSizeArg <= 0) {
|
|
24
|
+
throw new TypeError('requestedPartSize must be a positive safe integer.');
|
|
25
|
+
}
|
|
26
|
+
return Math.min(
|
|
27
|
+
MULTIPART_MAX_PART_SIZE,
|
|
28
|
+
Math.max(MULTIPART_MIN_PART_SIZE, requestedPartSizeArg),
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const normalizeRequestedConcurrency = (
|
|
33
|
+
requestedConcurrencyArg: number | undefined,
|
|
34
|
+
): number => {
|
|
35
|
+
if (requestedConcurrencyArg === undefined) {
|
|
36
|
+
return MULTIPART_DEFAULT_CONCURRENCY;
|
|
37
|
+
}
|
|
38
|
+
if (!Number.isSafeInteger(requestedConcurrencyArg) || requestedConcurrencyArg <= 0) {
|
|
39
|
+
throw new TypeError('requestedConcurrency must be a positive safe integer.');
|
|
40
|
+
}
|
|
41
|
+
return Math.min(MULTIPART_MAX_CONCURRENCY, requestedConcurrencyArg);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const planMultipartUpload = (
|
|
45
|
+
optionsArg: IMultipartPlanningOptions,
|
|
46
|
+
): TMultipartUploadPlan => {
|
|
47
|
+
requireSafeNonNegativeInteger(optionsArg.objectSize, 'objectSize');
|
|
48
|
+
if (optionsArg.objectSize > MULTIPART_MAX_OBJECT_SIZE) {
|
|
49
|
+
throw new RangeError(
|
|
50
|
+
`objectSize exceeds the ${MULTIPART_MAX_OBJECT_SIZE} byte multipart limit.`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const requestedConcurrency = normalizeRequestedConcurrency(
|
|
55
|
+
optionsArg.requestedConcurrency,
|
|
56
|
+
);
|
|
57
|
+
if (optionsArg.objectSize === 0) {
|
|
58
|
+
return {
|
|
59
|
+
classification: 'zero',
|
|
60
|
+
objectSize: 0,
|
|
61
|
+
partSize: 0,
|
|
62
|
+
partCount: 0,
|
|
63
|
+
concurrency: requestedConcurrency,
|
|
64
|
+
maximumBufferedBytes: 0,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const preferredPartSize = normalizeRequestedPartSize(optionsArg.requestedPartSize);
|
|
69
|
+
const minimumPartSizeForCount = Math.ceil(
|
|
70
|
+
optionsArg.objectSize / MULTIPART_MAX_PART_COUNT,
|
|
71
|
+
);
|
|
72
|
+
const partSize = Math.max(preferredPartSize, minimumPartSizeForCount);
|
|
73
|
+
if (partSize > MULTIPART_MAX_PART_SIZE) {
|
|
74
|
+
throw new RangeError('The object cannot be represented by legal multipart parts.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const partCount = Math.ceil(optionsArg.objectSize / partSize);
|
|
78
|
+
const concurrency = Math.min(requestedConcurrency, partCount);
|
|
79
|
+
return {
|
|
80
|
+
classification: 'multipart',
|
|
81
|
+
objectSize: optionsArg.objectSize,
|
|
82
|
+
partSize,
|
|
83
|
+
partCount,
|
|
84
|
+
concurrency,
|
|
85
|
+
maximumBufferedBytes: Math.min(
|
|
86
|
+
optionsArg.objectSize,
|
|
87
|
+
partSize * concurrency,
|
|
88
|
+
) + Math.min(optionsArg.objectSize, partSize),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MULTIPART_MAX_CONCURRENCY,
|
|
3
|
+
MULTIPART_MAX_OBJECT_SIZE,
|
|
4
|
+
MULTIPART_MAX_PART_COUNT,
|
|
5
|
+
MULTIPART_MAX_PART_SIZE,
|
|
6
|
+
MULTIPART_MIN_PART_SIZE,
|
|
7
|
+
} from './internal.multipart.planner.js';
|
|
8
|
+
import type {
|
|
9
|
+
IConsumeExactMultipartStreamOptions,
|
|
10
|
+
IMultipartStreamConsumptionResult,
|
|
11
|
+
IMultipartStreamFailureState,
|
|
12
|
+
TMultipartOwnedReaderCleanup,
|
|
13
|
+
TMultipartStreamFailureKind,
|
|
14
|
+
TMultipartUploadPlan,
|
|
15
|
+
} from './internal.multipart.types.js';
|
|
16
|
+
|
|
17
|
+
interface IPrimaryFailure {
|
|
18
|
+
kind: TMultipartStreamFailureKind;
|
|
19
|
+
cause?: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type TPartOutcome =
|
|
23
|
+
| {
|
|
24
|
+
token: symbol;
|
|
25
|
+
partNumber: number;
|
|
26
|
+
succeeded: true;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
token: symbol;
|
|
30
|
+
partNumber: number;
|
|
31
|
+
succeeded: false;
|
|
32
|
+
cause: unknown;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
interface IInFlightPart {
|
|
36
|
+
byteLength: number;
|
|
37
|
+
outcome: Promise<TPartOutcome>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type TReadOutcome =
|
|
41
|
+
| {
|
|
42
|
+
type: 'read';
|
|
43
|
+
result: ReadableStreamReadResult<Uint8Array>;
|
|
44
|
+
}
|
|
45
|
+
| {
|
|
46
|
+
type: 'read-error';
|
|
47
|
+
cause: unknown;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type TAbortOutcome = {
|
|
51
|
+
type: 'abort';
|
|
52
|
+
cause: unknown;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const failureMessages: Record<TMultipartStreamFailureKind, string> = {
|
|
56
|
+
aborted: 'Multipart stream consumption was aborted.',
|
|
57
|
+
'source-read': 'The multipart source stream failed while being read.',
|
|
58
|
+
'source-buffer-limit': 'The multipart source emitted a chunk larger than the buffer limit.',
|
|
59
|
+
'source-empty-chunk-limit':
|
|
60
|
+
'The multipart source emitted too many consecutive empty chunks.',
|
|
61
|
+
'source-short': 'The multipart source ended before the declared content length.',
|
|
62
|
+
'source-extra': 'The multipart source exceeded the declared content length.',
|
|
63
|
+
'part-callback': 'A multipart part callback failed.',
|
|
64
|
+
};
|
|
65
|
+
const failureSettlementMaximumTimeoutMs = 30_000;
|
|
66
|
+
const maximumConsecutiveEmptyChunks = 1_024;
|
|
67
|
+
|
|
68
|
+
export class MultipartStreamConsumptionError extends Error {
|
|
69
|
+
public readonly state: IMultipartStreamFailureState;
|
|
70
|
+
/**
|
|
71
|
+
* Non-rejecting settlement of every started part callback.
|
|
72
|
+
*/
|
|
73
|
+
public readonly callbackSettlement: Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Non-rejecting settlement of callbacks and owned reader cancellation.
|
|
76
|
+
*/
|
|
77
|
+
public readonly ownedSettlement: Promise<void>;
|
|
78
|
+
|
|
79
|
+
constructor(
|
|
80
|
+
stateArg: IMultipartStreamFailureState,
|
|
81
|
+
settlementsArg?: {
|
|
82
|
+
callbackSettlement: Promise<void>;
|
|
83
|
+
ownedSettlement: Promise<void>;
|
|
84
|
+
},
|
|
85
|
+
) {
|
|
86
|
+
super(failureMessages[stateArg.kind], {
|
|
87
|
+
cause: stateArg.cause,
|
|
88
|
+
});
|
|
89
|
+
this.name = 'MultipartStreamConsumptionError';
|
|
90
|
+
this.state = stateArg;
|
|
91
|
+
this.callbackSettlement =
|
|
92
|
+
settlementsArg?.callbackSettlement ?? Promise.resolve();
|
|
93
|
+
this.ownedSettlement =
|
|
94
|
+
settlementsArg?.ownedSettlement ?? Promise.resolve();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const validatePlan = (planArg: TMultipartUploadPlan): void => {
|
|
99
|
+
if (
|
|
100
|
+
!Number.isSafeInteger(planArg.objectSize) ||
|
|
101
|
+
planArg.objectSize < 0 ||
|
|
102
|
+
planArg.objectSize > MULTIPART_MAX_OBJECT_SIZE
|
|
103
|
+
) {
|
|
104
|
+
throw new TypeError('plan.objectSize is invalid.');
|
|
105
|
+
}
|
|
106
|
+
if (
|
|
107
|
+
!Number.isSafeInteger(planArg.concurrency) ||
|
|
108
|
+
planArg.concurrency < 1 ||
|
|
109
|
+
planArg.concurrency > MULTIPART_MAX_CONCURRENCY
|
|
110
|
+
) {
|
|
111
|
+
throw new TypeError('plan.concurrency is invalid.');
|
|
112
|
+
}
|
|
113
|
+
if (planArg.classification === 'zero') {
|
|
114
|
+
if (
|
|
115
|
+
planArg.objectSize !== 0 ||
|
|
116
|
+
planArg.partSize !== 0 ||
|
|
117
|
+
planArg.partCount !== 0 ||
|
|
118
|
+
planArg.maximumBufferedBytes !== 0
|
|
119
|
+
) {
|
|
120
|
+
throw new TypeError('The zero-byte multipart plan is inconsistent.');
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (
|
|
126
|
+
planArg.objectSize === 0 ||
|
|
127
|
+
!Number.isSafeInteger(planArg.partSize) ||
|
|
128
|
+
planArg.partSize < MULTIPART_MIN_PART_SIZE ||
|
|
129
|
+
planArg.partSize > MULTIPART_MAX_PART_SIZE ||
|
|
130
|
+
planArg.partCount !== Math.ceil(planArg.objectSize / planArg.partSize) ||
|
|
131
|
+
planArg.partCount < 1 ||
|
|
132
|
+
planArg.partCount > MULTIPART_MAX_PART_COUNT ||
|
|
133
|
+
planArg.concurrency > planArg.partCount ||
|
|
134
|
+
planArg.maximumBufferedBytes !==
|
|
135
|
+
Math.min(planArg.objectSize, planArg.partSize * planArg.concurrency) +
|
|
136
|
+
Math.min(planArg.objectSize, planArg.partSize)
|
|
137
|
+
) {
|
|
138
|
+
throw new TypeError('The multipart plan is inconsistent.');
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const consumeExactMultipartStream = async (
|
|
143
|
+
optionsArg: IConsumeExactMultipartStreamOptions,
|
|
144
|
+
): Promise<IMultipartStreamConsumptionResult> => {
|
|
145
|
+
validatePlan(optionsArg.plan);
|
|
146
|
+
if (
|
|
147
|
+
!optionsArg.readableStream ||
|
|
148
|
+
typeof optionsArg.readableStream.getReader !== 'function'
|
|
149
|
+
) {
|
|
150
|
+
throw new TypeError('readableStream must be a web ReadableStream.');
|
|
151
|
+
}
|
|
152
|
+
if (typeof optionsArg.onPart !== 'function') {
|
|
153
|
+
throw new TypeError('onPart must be a function.');
|
|
154
|
+
}
|
|
155
|
+
const failureSettlementTimeoutMs =
|
|
156
|
+
optionsArg.failureSettlementTimeoutMs ??
|
|
157
|
+
failureSettlementMaximumTimeoutMs;
|
|
158
|
+
if (
|
|
159
|
+
!Number.isSafeInteger(failureSettlementTimeoutMs) ||
|
|
160
|
+
failureSettlementTimeoutMs <= 0 ||
|
|
161
|
+
failureSettlementTimeoutMs > failureSettlementMaximumTimeoutMs
|
|
162
|
+
) {
|
|
163
|
+
throw new TypeError(
|
|
164
|
+
`failureSettlementTimeoutMs must be a positive safe integer no greater than ${failureSettlementMaximumTimeoutMs}.`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const reader = optionsArg.readableStream.getReader();
|
|
169
|
+
const callbackAbortController = new AbortController();
|
|
170
|
+
const inFlightParts = new Map<symbol, IInFlightPart>();
|
|
171
|
+
const completedPartNumbers = new Set<number>();
|
|
172
|
+
let inFlightBytes = 0;
|
|
173
|
+
let currentPartBytes = 0;
|
|
174
|
+
let pendingChunkBytes = 0;
|
|
175
|
+
let peakBufferedBytes = 0;
|
|
176
|
+
let observedBytes = 0;
|
|
177
|
+
let partCallbacksStarted = 0;
|
|
178
|
+
let sourceClosed = false;
|
|
179
|
+
let consecutiveEmptyChunks = 0;
|
|
180
|
+
|
|
181
|
+
let resolveAbort!: (outcomeArg: TAbortOutcome) => void;
|
|
182
|
+
const abortOutcome = new Promise<TAbortOutcome>((resolveArg) => {
|
|
183
|
+
resolveAbort = resolveArg;
|
|
184
|
+
});
|
|
185
|
+
const handleAbort = (): void => {
|
|
186
|
+
const cause = optionsArg.signal?.reason ?? new Error('Multipart stream aborted.');
|
|
187
|
+
callbackAbortController.abort(cause);
|
|
188
|
+
resolveAbort({
|
|
189
|
+
type: 'abort',
|
|
190
|
+
cause,
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
optionsArg.signal?.addEventListener('abort', handleAbort, { once: true });
|
|
194
|
+
if (optionsArg.signal?.aborted) {
|
|
195
|
+
handleAbort();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const processPartOutcome = (outcomeArg: TPartOutcome): void => {
|
|
199
|
+
const inFlightPart = inFlightParts.get(outcomeArg.token);
|
|
200
|
+
if (!inFlightPart) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
inFlightParts.delete(outcomeArg.token);
|
|
204
|
+
inFlightBytes -= inFlightPart.byteLength;
|
|
205
|
+
if (outcomeArg.succeeded) {
|
|
206
|
+
completedPartNumbers.add(outcomeArg.partNumber);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
throw {
|
|
210
|
+
kind: 'part-callback',
|
|
211
|
+
cause: outcomeArg.cause,
|
|
212
|
+
} satisfies IPrimaryFailure;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const observeBufferedBytes = (): void => {
|
|
216
|
+
const bufferedBytes = inFlightBytes + currentPartBytes + pendingChunkBytes;
|
|
217
|
+
if (bufferedBytes > optionsArg.plan.maximumBufferedBytes) {
|
|
218
|
+
throw {
|
|
219
|
+
kind: 'source-buffer-limit',
|
|
220
|
+
cause: new RangeError('Multipart buffering exceeded its planned bound.'),
|
|
221
|
+
} satisfies IPrimaryFailure;
|
|
222
|
+
}
|
|
223
|
+
peakBufferedBytes = Math.max(peakBufferedBytes, bufferedBytes);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const waitForOnePart = async (): Promise<void> => {
|
|
227
|
+
const outcome = await Promise.race(
|
|
228
|
+
[
|
|
229
|
+
abortOutcome,
|
|
230
|
+
...[...inFlightParts.values()].map(
|
|
231
|
+
(inFlightPartArg) => inFlightPartArg.outcome,
|
|
232
|
+
),
|
|
233
|
+
],
|
|
234
|
+
);
|
|
235
|
+
if (!('token' in outcome)) {
|
|
236
|
+
throw {
|
|
237
|
+
kind: 'aborted',
|
|
238
|
+
cause: outcome.cause,
|
|
239
|
+
} satisfies IPrimaryFailure;
|
|
240
|
+
}
|
|
241
|
+
processPartOutcome(outcome);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const waitForCapacity = async (): Promise<void> => {
|
|
245
|
+
while (inFlightParts.size >= optionsArg.plan.concurrency) {
|
|
246
|
+
await waitForOnePart();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const readWhileObservingCallbacks = async (): Promise<
|
|
251
|
+
ReadableStreamReadResult<Uint8Array>
|
|
252
|
+
> => {
|
|
253
|
+
const readOutcome: Promise<TReadOutcome> = reader.read().then(
|
|
254
|
+
(resultArg) => ({
|
|
255
|
+
type: 'read',
|
|
256
|
+
result: resultArg,
|
|
257
|
+
}),
|
|
258
|
+
(causeArg: unknown) => ({
|
|
259
|
+
type: 'read-error',
|
|
260
|
+
cause: causeArg,
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
while (true) {
|
|
265
|
+
const outcome = await Promise.race([
|
|
266
|
+
readOutcome,
|
|
267
|
+
abortOutcome,
|
|
268
|
+
...[...inFlightParts.values()].map(
|
|
269
|
+
(inFlightPartArg) => inFlightPartArg.outcome,
|
|
270
|
+
),
|
|
271
|
+
]);
|
|
272
|
+
if ('token' in outcome) {
|
|
273
|
+
processPartOutcome(outcome);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (outcome.type === 'abort') {
|
|
277
|
+
throw {
|
|
278
|
+
kind: 'aborted',
|
|
279
|
+
cause: outcome.cause,
|
|
280
|
+
} satisfies IPrimaryFailure;
|
|
281
|
+
}
|
|
282
|
+
if (outcome.type === 'read-error') {
|
|
283
|
+
throw {
|
|
284
|
+
kind: 'source-read',
|
|
285
|
+
cause: outcome.cause,
|
|
286
|
+
} satisfies IPrimaryFailure;
|
|
287
|
+
}
|
|
288
|
+
return outcome.result;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const dispatchPart = (dataArg: Uint8Array, partNumberArg: number): void => {
|
|
293
|
+
const token = Symbol(`multipart-part-${partNumberArg}`);
|
|
294
|
+
partCallbacksStarted++;
|
|
295
|
+
currentPartBytes = 0;
|
|
296
|
+
inFlightBytes += dataArg.byteLength;
|
|
297
|
+
observeBufferedBytes();
|
|
298
|
+
const outcome = Promise.resolve()
|
|
299
|
+
.then(() =>
|
|
300
|
+
optionsArg.onPart({
|
|
301
|
+
partNumber: partNumberArg,
|
|
302
|
+
byteOffset: (partNumberArg - 1) * optionsArg.plan.partSize,
|
|
303
|
+
isLastPart: partNumberArg === optionsArg.plan.partCount,
|
|
304
|
+
data: dataArg,
|
|
305
|
+
signal: callbackAbortController.signal,
|
|
306
|
+
}),
|
|
307
|
+
)
|
|
308
|
+
.then<TPartOutcome, TPartOutcome>(
|
|
309
|
+
() => ({
|
|
310
|
+
token,
|
|
311
|
+
partNumber: partNumberArg,
|
|
312
|
+
succeeded: true,
|
|
313
|
+
}),
|
|
314
|
+
(causeArg: unknown) => ({
|
|
315
|
+
token,
|
|
316
|
+
partNumber: partNumberArg,
|
|
317
|
+
succeeded: false,
|
|
318
|
+
cause: causeArg,
|
|
319
|
+
}),
|
|
320
|
+
);
|
|
321
|
+
inFlightParts.set(token, {
|
|
322
|
+
byteLength: dataArg.byteLength,
|
|
323
|
+
outcome,
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const acceptSourceChunk = (chunkArg: Uint8Array): boolean => {
|
|
328
|
+
if (chunkArg.byteLength > 0) {
|
|
329
|
+
consecutiveEmptyChunks = 0;
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
consecutiveEmptyChunks++;
|
|
333
|
+
if (consecutiveEmptyChunks > maximumConsecutiveEmptyChunks) {
|
|
334
|
+
throw {
|
|
335
|
+
kind: 'source-empty-chunk-limit',
|
|
336
|
+
cause: new RangeError(
|
|
337
|
+
`The multipart source emitted more than ${maximumConsecutiveEmptyChunks} consecutive empty chunks.`,
|
|
338
|
+
),
|
|
339
|
+
} satisfies IPrimaryFailure;
|
|
340
|
+
}
|
|
341
|
+
return false;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
let pendingChunk: Uint8Array | undefined;
|
|
346
|
+
let pendingChunkOffset = 0;
|
|
347
|
+
|
|
348
|
+
for (
|
|
349
|
+
let partNumber = 1;
|
|
350
|
+
partNumber <= optionsArg.plan.partCount;
|
|
351
|
+
partNumber++
|
|
352
|
+
) {
|
|
353
|
+
await waitForCapacity();
|
|
354
|
+
const partByteLength = Math.min(
|
|
355
|
+
optionsArg.plan.partSize,
|
|
356
|
+
optionsArg.plan.objectSize -
|
|
357
|
+
(partNumber - 1) * optionsArg.plan.partSize,
|
|
358
|
+
);
|
|
359
|
+
if (
|
|
360
|
+
inFlightBytes + partByteLength >
|
|
361
|
+
optionsArg.plan.maximumBufferedBytes
|
|
362
|
+
) {
|
|
363
|
+
throw new Error('The multipart plan cannot honor its buffering bound.');
|
|
364
|
+
}
|
|
365
|
+
const partData = new Uint8Array(partByteLength);
|
|
366
|
+
currentPartBytes = partByteLength;
|
|
367
|
+
observeBufferedBytes();
|
|
368
|
+
let partOffset = 0;
|
|
369
|
+
|
|
370
|
+
while (partOffset < partByteLength) {
|
|
371
|
+
if (
|
|
372
|
+
pendingChunk &&
|
|
373
|
+
pendingChunkOffset < pendingChunk.byteLength
|
|
374
|
+
) {
|
|
375
|
+
const copyByteLength = Math.min(
|
|
376
|
+
partByteLength - partOffset,
|
|
377
|
+
pendingChunk.byteLength - pendingChunkOffset,
|
|
378
|
+
);
|
|
379
|
+
partData.set(
|
|
380
|
+
pendingChunk.subarray(
|
|
381
|
+
pendingChunkOffset,
|
|
382
|
+
pendingChunkOffset + copyByteLength,
|
|
383
|
+
),
|
|
384
|
+
partOffset,
|
|
385
|
+
);
|
|
386
|
+
pendingChunkOffset += copyByteLength;
|
|
387
|
+
partOffset += copyByteLength;
|
|
388
|
+
if (pendingChunkOffset === pendingChunk.byteLength) {
|
|
389
|
+
pendingChunk = undefined;
|
|
390
|
+
pendingChunkOffset = 0;
|
|
391
|
+
pendingChunkBytes = 0;
|
|
392
|
+
}
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const readResult = await readWhileObservingCallbacks();
|
|
397
|
+
if (readResult.done) {
|
|
398
|
+
sourceClosed = true;
|
|
399
|
+
throw {
|
|
400
|
+
kind: 'source-short',
|
|
401
|
+
} satisfies IPrimaryFailure;
|
|
402
|
+
}
|
|
403
|
+
if (!(readResult.value instanceof Uint8Array)) {
|
|
404
|
+
throw {
|
|
405
|
+
kind: 'source-read',
|
|
406
|
+
cause: new TypeError('The multipart source emitted a non-Uint8Array chunk.'),
|
|
407
|
+
} satisfies IPrimaryFailure;
|
|
408
|
+
}
|
|
409
|
+
if (!acceptSourceChunk(readResult.value)) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
observedBytes += readResult.value.byteLength;
|
|
413
|
+
if (observedBytes > optionsArg.plan.objectSize) {
|
|
414
|
+
throw {
|
|
415
|
+
kind: 'source-extra',
|
|
416
|
+
} satisfies IPrimaryFailure;
|
|
417
|
+
}
|
|
418
|
+
if (readResult.value.byteLength > optionsArg.plan.partSize) {
|
|
419
|
+
throw {
|
|
420
|
+
kind: 'source-buffer-limit',
|
|
421
|
+
cause: new RangeError(
|
|
422
|
+
`Source chunks must not exceed ${optionsArg.plan.partSize} bytes.`,
|
|
423
|
+
),
|
|
424
|
+
} satisfies IPrimaryFailure;
|
|
425
|
+
}
|
|
426
|
+
pendingChunk = readResult.value;
|
|
427
|
+
pendingChunkBytes = readResult.value.byteLength;
|
|
428
|
+
observeBufferedBytes();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
dispatchPart(partData, partNumber);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
while (!sourceClosed) {
|
|
435
|
+
const readResult = await readWhileObservingCallbacks();
|
|
436
|
+
if (readResult.done) {
|
|
437
|
+
sourceClosed = true;
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
if (!(readResult.value instanceof Uint8Array)) {
|
|
441
|
+
throw {
|
|
442
|
+
kind: 'source-read',
|
|
443
|
+
cause: new TypeError('The multipart source emitted a non-Uint8Array chunk.'),
|
|
444
|
+
} satisfies IPrimaryFailure;
|
|
445
|
+
}
|
|
446
|
+
if (!acceptSourceChunk(readResult.value)) {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
observedBytes += readResult.value.byteLength;
|
|
450
|
+
throw {
|
|
451
|
+
kind: 'source-extra',
|
|
452
|
+
} satisfies IPrimaryFailure;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
while (inFlightParts.size > 0) {
|
|
456
|
+
await waitForOnePart();
|
|
457
|
+
}
|
|
458
|
+
if (optionsArg.signal?.aborted) {
|
|
459
|
+
throw {
|
|
460
|
+
kind: 'aborted',
|
|
461
|
+
cause: optionsArg.signal.reason,
|
|
462
|
+
} satisfies IPrimaryFailure;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
completionAuthorized: true,
|
|
467
|
+
bytesConsumed: observedBytes,
|
|
468
|
+
partsConsumed: completedPartNumbers.size,
|
|
469
|
+
peakBufferedBytes,
|
|
470
|
+
maximumBufferedBytes: optionsArg.plan.maximumBufferedBytes,
|
|
471
|
+
};
|
|
472
|
+
} catch (error) {
|
|
473
|
+
const failure =
|
|
474
|
+
typeof error === 'object' &&
|
|
475
|
+
error !== null &&
|
|
476
|
+
'kind' in error &&
|
|
477
|
+
typeof error.kind === 'string'
|
|
478
|
+
? (error as IPrimaryFailure)
|
|
479
|
+
: ({
|
|
480
|
+
kind: 'source-read',
|
|
481
|
+
cause: error,
|
|
482
|
+
} satisfies IPrimaryFailure);
|
|
483
|
+
callbackAbortController.abort(failure.cause ?? new Error(failureMessages[failure.kind]));
|
|
484
|
+
|
|
485
|
+
const readerCleanupPromise: Promise<TMultipartOwnedReaderCleanup> = sourceClosed
|
|
486
|
+
? Promise.resolve({
|
|
487
|
+
status: 'closed',
|
|
488
|
+
})
|
|
489
|
+
: Promise.resolve()
|
|
490
|
+
.then(() => reader.cancel(failure.cause))
|
|
491
|
+
.then<TMultipartOwnedReaderCleanup, TMultipartOwnedReaderCleanup>(
|
|
492
|
+
() => ({
|
|
493
|
+
status: 'cancelled',
|
|
494
|
+
}),
|
|
495
|
+
(causeArg: unknown) => ({
|
|
496
|
+
status: 'cancel-failed',
|
|
497
|
+
cause: causeArg,
|
|
498
|
+
}),
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
let callbacksSettled = false;
|
|
502
|
+
const callbackSettlement = Promise.all(
|
|
503
|
+
[...inFlightParts.values()].map(
|
|
504
|
+
(inFlightPartArg) => inFlightPartArg.outcome,
|
|
505
|
+
),
|
|
506
|
+
)
|
|
507
|
+
.then((remainingOutcomesArg) => {
|
|
508
|
+
for (const outcome of remainingOutcomesArg) {
|
|
509
|
+
if (outcome.succeeded) {
|
|
510
|
+
completedPartNumbers.add(outcome.partNumber);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
inFlightParts.clear();
|
|
514
|
+
inFlightBytes = 0;
|
|
515
|
+
})
|
|
516
|
+
.then(
|
|
517
|
+
() => undefined,
|
|
518
|
+
() => undefined,
|
|
519
|
+
);
|
|
520
|
+
const observedCallbackSettlement = callbackSettlement.then(() => {
|
|
521
|
+
callbacksSettled = true;
|
|
522
|
+
});
|
|
523
|
+
let readerCleanup: TMultipartOwnedReaderCleanup | undefined;
|
|
524
|
+
let readerCleanupSettled = false;
|
|
525
|
+
const observedReaderCleanup = readerCleanupPromise.then(
|
|
526
|
+
(readerCleanupArg) => {
|
|
527
|
+
readerCleanup = readerCleanupArg;
|
|
528
|
+
readerCleanupSettled = true;
|
|
529
|
+
},
|
|
530
|
+
() => {
|
|
531
|
+
readerCleanup = {
|
|
532
|
+
status: 'cancel-failed',
|
|
533
|
+
cause: new Error('Reader cancellation rejected unexpectedly.'),
|
|
534
|
+
};
|
|
535
|
+
readerCleanupSettled = true;
|
|
536
|
+
},
|
|
537
|
+
);
|
|
538
|
+
const ownedSettlement = Promise.allSettled([
|
|
539
|
+
observedCallbackSettlement,
|
|
540
|
+
observedReaderCleanup,
|
|
541
|
+
]).then(() => undefined);
|
|
542
|
+
const settlementDeadlineToken = Symbol(
|
|
543
|
+
'multipart-failure-settlement-deadline',
|
|
544
|
+
);
|
|
545
|
+
let resolveSettlementDeadline!: (
|
|
546
|
+
valueArg: typeof settlementDeadlineToken,
|
|
547
|
+
) => void;
|
|
548
|
+
const settlementDeadlineOutcome = new Promise<
|
|
549
|
+
typeof settlementDeadlineToken
|
|
550
|
+
>((resolveArg) => {
|
|
551
|
+
resolveSettlementDeadline = resolveArg;
|
|
552
|
+
});
|
|
553
|
+
const settlementDeadline = setTimeout(() => {
|
|
554
|
+
resolveSettlementDeadline(settlementDeadlineToken);
|
|
555
|
+
}, failureSettlementTimeoutMs);
|
|
556
|
+
await Promise.race([ownedSettlement, settlementDeadlineOutcome]);
|
|
557
|
+
clearTimeout(settlementDeadline);
|
|
558
|
+
const retry =
|
|
559
|
+
partCallbacksStarted === 0
|
|
560
|
+
? ({
|
|
561
|
+
disposition: 'safe-to-retry',
|
|
562
|
+
requiresMultipartAbort: false,
|
|
563
|
+
} as const)
|
|
564
|
+
: ({
|
|
565
|
+
disposition: 'abort-multipart-first',
|
|
566
|
+
requiresMultipartAbort: true,
|
|
567
|
+
} as const);
|
|
568
|
+
|
|
569
|
+
throw new MultipartStreamConsumptionError(
|
|
570
|
+
{
|
|
571
|
+
completionAuthorized: false,
|
|
572
|
+
kind: failure.kind,
|
|
573
|
+
cause: failure.cause,
|
|
574
|
+
expectedBytes: optionsArg.plan.objectSize,
|
|
575
|
+
observedBytes,
|
|
576
|
+
partCallbacksStarted,
|
|
577
|
+
completedPartNumbers: [...completedPartNumbers].sort(
|
|
578
|
+
(leftArg, rightArg) => {
|
|
579
|
+
return leftArg - rightArg;
|
|
580
|
+
},
|
|
581
|
+
),
|
|
582
|
+
cleanup: {
|
|
583
|
+
callbacks: callbacksSettled ? 'settled' : 'detached',
|
|
584
|
+
reader:
|
|
585
|
+
readerCleanupSettled && readerCleanup
|
|
586
|
+
? readerCleanup
|
|
587
|
+
: {
|
|
588
|
+
status: 'cancel-detached',
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
retry,
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
callbackSettlement,
|
|
595
|
+
ownedSettlement,
|
|
596
|
+
},
|
|
597
|
+
);
|
|
598
|
+
} finally {
|
|
599
|
+
optionsArg.signal?.removeEventListener('abort', handleAbort);
|
|
600
|
+
try {
|
|
601
|
+
reader.releaseLock();
|
|
602
|
+
} catch {
|
|
603
|
+
// Detached reader cancellation retains ownership until it settles.
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
};
|