@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.
Files changed (255) hide show
  1. package/.smartconfig.json +37 -0
  2. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  3. package/dist_ts/00_commitinfo_data.js +9 -0
  4. package/dist_ts/classes.losslessorgclient.d.ts +32 -0
  5. package/dist_ts/classes.losslessorgclient.js +103 -0
  6. package/dist_ts/core/classes.error.d.ts +16 -0
  7. package/dist_ts/core/classes.error.js +29 -0
  8. package/dist_ts/core/classes.operation.d.ts +17 -0
  9. package/dist_ts/core/classes.operation.js +70 -0
  10. package/dist_ts/core/interfaces.d.ts +18 -0
  11. package/dist_ts/core/interfaces.js +2 -0
  12. package/dist_ts/index.d.ts +4 -0
  13. package/dist_ts/index.js +3 -0
  14. package/dist_ts/nosqldb/classes.atomicdelete.d.ts +26 -0
  15. package/dist_ts/nosqldb/classes.atomicdelete.js +44 -0
  16. package/dist_ts/nosqldb/classes.atomicfindoneandupdate.d.ts +27 -0
  17. package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +86 -0
  18. package/dist_ts/nosqldb/classes.atomicupdate.d.ts +26 -0
  19. package/dist_ts/nosqldb/classes.atomicupdate.js +55 -0
  20. package/dist_ts/nosqldb/classes.bootstraprecord.d.ts +14 -0
  21. package/dist_ts/nosqldb/classes.bootstraprecord.js +80 -0
  22. package/dist_ts/nosqldb/classes.bsonassertion.d.ts +2 -0
  23. package/dist_ts/nosqldb/classes.bsonassertion.js +29 -0
  24. package/dist_ts/nosqldb/classes.bsonobjectid.d.ts +2 -0
  25. package/dist_ts/nosqldb/classes.bsonobjectid.js +50 -0
  26. package/dist_ts/nosqldb/classes.bsonsnapshot.d.ts +3 -0
  27. package/dist_ts/nosqldb/classes.bsonsnapshot.js +126 -0
  28. package/dist_ts/nosqldb/classes.cached-document.d.ts +27 -0
  29. package/dist_ts/nosqldb/classes.cached-document.js +112 -0
  30. package/dist_ts/nosqldb/classes.collection.d.ts +302 -0
  31. package/dist_ts/nosqldb/classes.collection.js +1568 -0
  32. package/dist_ts/nosqldb/classes.collectionfactory.d.ts +30 -0
  33. package/dist_ts/nosqldb/classes.collectionfactory.js +65 -0
  34. package/dist_ts/nosqldb/classes.collectionlifecycle.d.ts +4 -0
  35. package/dist_ts/nosqldb/classes.collectionlifecycle.js +15 -0
  36. package/dist_ts/nosqldb/classes.collectionpreparation.d.ts +34 -0
  37. package/dist_ts/nosqldb/classes.collectionpreparation.js +134 -0
  38. package/dist_ts/nosqldb/classes.collectiontopology.d.ts +47 -0
  39. package/dist_ts/nosqldb/classes.collectiontopology.js +388 -0
  40. package/dist_ts/nosqldb/classes.connection.d.ts +18 -0
  41. package/dist_ts/nosqldb/classes.connection.js +23 -0
  42. package/dist_ts/nosqldb/classes.convenience.d.ts +1 -0
  43. package/dist_ts/nosqldb/classes.convenience.js +5 -0
  44. package/dist_ts/nosqldb/classes.cursor.d.ts +15 -0
  45. package/dist_ts/nosqldb/classes.cursor.js +82 -0
  46. package/dist_ts/nosqldb/classes.cursoroperation.d.ts +7 -0
  47. package/dist_ts/nosqldb/classes.cursoroperation.js +36 -0
  48. package/dist_ts/nosqldb/classes.db.d.ts +98 -0
  49. package/dist_ts/nosqldb/classes.db.js +434 -0
  50. package/dist_ts/nosqldb/classes.distributedcoordinator.d.ts +52 -0
  51. package/dist_ts/nosqldb/classes.distributedcoordinator.js +314 -0
  52. package/dist_ts/nosqldb/classes.doc.d.ts +722 -0
  53. package/dist_ts/nosqldb/classes.doc.js +2976 -0
  54. package/dist_ts/nosqldb/classes.easystore.d.ts +42 -0
  55. package/dist_ts/nosqldb/classes.easystore.js +199 -0
  56. package/dist_ts/nosqldb/classes.exactpersistence.d.ts +141 -0
  57. package/dist_ts/nosqldb/classes.exactpersistence.js +1110 -0
  58. package/dist_ts/nosqldb/classes.lucene.adapter.d.ts +173 -0
  59. package/dist_ts/nosqldb/classes.lucene.adapter.js +623 -0
  60. package/dist_ts/nosqldb/classes.mongoadministration.d.ts +73 -0
  61. package/dist_ts/nosqldb/classes.mongoadministration.js +497 -0
  62. package/dist_ts/nosqldb/classes.namespaceinspection.d.ts +42 -0
  63. package/dist_ts/nosqldb/classes.namespaceinspection.js +138 -0
  64. package/dist_ts/nosqldb/classes.operationbudget.d.ts +22 -0
  65. package/dist_ts/nosqldb/classes.operationbudget.js +65 -0
  66. package/dist_ts/nosqldb/classes.ordinarypersistence.d.ts +18 -0
  67. package/dist_ts/nosqldb/classes.ordinarypersistence.js +71 -0
  68. package/dist_ts/nosqldb/classes.persistence.d.ts +14 -0
  69. package/dist_ts/nosqldb/classes.persistence.js +34 -0
  70. package/dist_ts/nosqldb/classes.session.d.ts +28 -0
  71. package/dist_ts/nosqldb/classes.session.js +246 -0
  72. package/dist_ts/nosqldb/classes.storedinspection.d.ts +44 -0
  73. package/dist_ts/nosqldb/classes.storedinspection.js +100 -0
  74. package/dist_ts/nosqldb/classes.testdatabase.d.ts +56 -0
  75. package/dist_ts/nosqldb/classes.testdatabase.js +250 -0
  76. package/dist_ts/nosqldb/classes.watcher.d.ts +37 -0
  77. package/dist_ts/nosqldb/classes.watcher.js +98 -0
  78. package/dist_ts/nosqldb/index.d.ts +27 -0
  79. package/dist_ts/nosqldb/index.js +18 -0
  80. package/dist_ts/nosqldb/logging.d.ts +2 -0
  81. package/dist_ts/nosqldb/logging.js +3 -0
  82. package/dist_ts/nosqldb/plugins.d.ts +17 -0
  83. package/dist_ts/nosqldb/plugins.js +20 -0
  84. package/dist_ts/nosqldb/shim.d.ts +1 -0
  85. package/dist_ts/nosqldb/shim.js +8 -0
  86. package/dist_ts/nosqldb/testsupport.d.ts +3 -0
  87. package/dist_ts/nosqldb/testsupport.js +3 -0
  88. package/dist_ts/objectstorage/classes.bucket.d.ts +227 -0
  89. package/dist_ts/objectstorage/classes.bucket.js +939 -0
  90. package/dist_ts/objectstorage/classes.connection.d.ts +21 -0
  91. package/dist_ts/objectstorage/classes.connection.js +27 -0
  92. package/dist_ts/objectstorage/classes.directory.d.ts +124 -0
  93. package/dist_ts/objectstorage/classes.directory.js +335 -0
  94. package/dist_ts/objectstorage/classes.exactpathpurgeerror.d.ts +5 -0
  95. package/dist_ts/objectstorage/classes.exactpathpurgeerror.js +18 -0
  96. package/dist_ts/objectstorage/classes.exactuploaderror.d.ts +5 -0
  97. package/dist_ts/objectstorage/classes.exactuploaderror.js +19 -0
  98. package/dist_ts/objectstorage/classes.file.d.ts +91 -0
  99. package/dist_ts/objectstorage/classes.file.js +255 -0
  100. package/dist_ts/objectstorage/classes.listcursor.d.ts +41 -0
  101. package/dist_ts/objectstorage/classes.listcursor.js +67 -0
  102. package/dist_ts/objectstorage/classes.metadata.d.ts +57 -0
  103. package/dist_ts/objectstorage/classes.metadata.js +113 -0
  104. package/dist_ts/objectstorage/classes.smartbucket.d.ts +57 -0
  105. package/dist_ts/objectstorage/classes.smartbucket.js +357 -0
  106. package/dist_ts/objectstorage/classes.trash.d.ts +11 -0
  107. package/dist_ts/objectstorage/classes.trash.js +22 -0
  108. package/dist_ts/objectstorage/classes.watcher.d.ts +79 -0
  109. package/dist_ts/objectstorage/classes.watcher.js +249 -0
  110. package/dist_ts/objectstorage/helpers.d.ts +25 -0
  111. package/dist_ts/objectstorage/helpers.js +215 -0
  112. package/dist_ts/objectstorage/index.d.ts +13 -0
  113. package/dist_ts/objectstorage/index.js +13 -0
  114. package/dist_ts/objectstorage/interfaces.d.ts +276 -0
  115. package/dist_ts/objectstorage/interfaces.js +2 -0
  116. package/dist_ts/objectstorage/internal.exactpathpurge.operations.d.ts +11 -0
  117. package/dist_ts/objectstorage/internal.exactpathpurge.operations.js +1069 -0
  118. package/dist_ts/objectstorage/internal.exactupload.capability.d.ts +40 -0
  119. package/dist_ts/objectstorage/internal.exactupload.capability.js +382 -0
  120. package/dist_ts/objectstorage/internal.exactupload.operations.d.ts +11 -0
  121. package/dist_ts/objectstorage/internal.exactupload.operations.js +1104 -0
  122. package/dist_ts/objectstorage/internal.multipart.planner.d.ts +8 -0
  123. package/dist_ts/objectstorage/internal.multipart.planner.js +63 -0
  124. package/dist_ts/objectstorage/internal.multipart.stream.d.ts +17 -0
  125. package/dist_ts/objectstorage/internal.multipart.stream.js +440 -0
  126. package/dist_ts/objectstorage/internal.multipart.types.d.ts +82 -0
  127. package/dist_ts/objectstorage/internal.multipart.types.js +2 -0
  128. package/dist_ts/objectstorage/plugins.d.ts +17 -0
  129. package/dist_ts/objectstorage/plugins.js +23 -0
  130. package/dist_ts/plugins.d.ts +4 -0
  131. package/dist_ts/plugins.js +5 -0
  132. package/dist_ts/sqldb/classes.clickhouseconnection.d.ts +39 -0
  133. package/dist_ts/sqldb/classes.clickhouseconnection.js +225 -0
  134. package/dist_ts/sqldb/classes.sqlconnection.d.ts +50 -0
  135. package/dist_ts/sqldb/classes.sqlconnection.js +376 -0
  136. package/dist_ts/sqldb/classes.sqltable.d.ts +29 -0
  137. package/dist_ts/sqldb/classes.sqltable.js +70 -0
  138. package/dist_ts/sqldb/clickhouse/classes.metricwriter.d.ts +27 -0
  139. package/dist_ts/sqldb/clickhouse/classes.metricwriter.js +89 -0
  140. package/dist_ts/sqldb/clickhouse/index.d.ts +9 -0
  141. package/dist_ts/sqldb/clickhouse/index.js +13 -0
  142. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.d.ts +88 -0
  143. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.js +304 -0
  144. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.d.ts +28 -0
  145. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.js +46 -0
  146. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.d.ts +43 -0
  147. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.js +155 -0
  148. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.resultset.d.ts +13 -0
  149. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.resultset.js +36 -0
  150. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.d.ts +49 -0
  151. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.js +106 -0
  152. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.d.ts +51 -0
  153. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.js +96 -0
  154. package/dist_ts/sqldb/clickhouse/smartclickhouse.plugins.d.ts +5 -0
  155. package/dist_ts/sqldb/clickhouse/smartclickhouse.plugins.js +6 -0
  156. package/dist_ts/sqldb/clickhouse/smartclickhouse.types.d.ts +53 -0
  157. package/dist_ts/sqldb/clickhouse/smartclickhouse.types.js +41 -0
  158. package/dist_ts/sqldb/index.d.ts +6 -0
  159. package/dist_ts/sqldb/index.js +6 -0
  160. package/dist_ts/sqldb/interfaces.d.ts +53 -0
  161. package/dist_ts/sqldb/interfaces.js +19 -0
  162. package/dist_ts/sqldb/internal.sqlpool.d.ts +51 -0
  163. package/dist_ts/sqldb/internal.sqlpool.js +214 -0
  164. package/dist_ts/sqldb/plugins.clickhouse.d.ts +4 -0
  165. package/dist_ts/sqldb/plugins.clickhouse.js +5 -0
  166. package/dist_ts/sqldb/plugins.mariadb.d.ts +4 -0
  167. package/dist_ts/sqldb/plugins.mariadb.js +5 -0
  168. package/docs/source/smartbucket/license.md +19 -0
  169. package/docs/source/smartclickhouse/license.md +19 -0
  170. package/license.md +21 -0
  171. package/package.json +101 -0
  172. package/readme.md +157 -0
  173. package/ts/00_commitinfo_data.ts +8 -0
  174. package/ts/classes.losslessorgclient.ts +104 -0
  175. package/ts/core/classes.error.ts +31 -0
  176. package/ts/core/classes.operation.ts +60 -0
  177. package/ts/core/interfaces.ts +24 -0
  178. package/ts/index.ts +4 -0
  179. package/ts/nosqldb/classes.atomicdelete.ts +81 -0
  180. package/ts/nosqldb/classes.atomicfindoneandupdate.ts +152 -0
  181. package/ts/nosqldb/classes.atomicupdate.ts +99 -0
  182. package/ts/nosqldb/classes.bootstraprecord.ts +107 -0
  183. package/ts/nosqldb/classes.bsonassertion.ts +25 -0
  184. package/ts/nosqldb/classes.bsonobjectid.ts +45 -0
  185. package/ts/nosqldb/classes.bsonsnapshot.ts +105 -0
  186. package/ts/nosqldb/classes.cached-document.ts +67 -0
  187. package/ts/nosqldb/classes.collection.ts +2399 -0
  188. package/ts/nosqldb/classes.collectionfactory.ts +87 -0
  189. package/ts/nosqldb/classes.collectionlifecycle.ts +24 -0
  190. package/ts/nosqldb/classes.collectionpreparation.ts +164 -0
  191. package/ts/nosqldb/classes.collectiontopology.ts +645 -0
  192. package/ts/nosqldb/classes.connection.ts +31 -0
  193. package/ts/nosqldb/classes.convenience.ts +5 -0
  194. package/ts/nosqldb/classes.cursor.ts +88 -0
  195. package/ts/nosqldb/classes.cursoroperation.ts +38 -0
  196. package/ts/nosqldb/classes.db.ts +637 -0
  197. package/ts/nosqldb/classes.distributedcoordinator.ts +305 -0
  198. package/ts/nosqldb/classes.doc.ts +4928 -0
  199. package/ts/nosqldb/classes.easystore.ts +154 -0
  200. package/ts/nosqldb/classes.exactpersistence.ts +1974 -0
  201. package/ts/nosqldb/classes.lucene.adapter.ts +780 -0
  202. package/ts/nosqldb/classes.mongoadministration.ts +867 -0
  203. package/ts/nosqldb/classes.namespaceinspection.ts +199 -0
  204. package/ts/nosqldb/classes.operationbudget.ts +75 -0
  205. package/ts/nosqldb/classes.ordinarypersistence.ts +84 -0
  206. package/ts/nosqldb/classes.persistence.ts +62 -0
  207. package/ts/nosqldb/classes.session.ts +389 -0
  208. package/ts/nosqldb/classes.storedinspection.ts +151 -0
  209. package/ts/nosqldb/classes.testdatabase.ts +272 -0
  210. package/ts/nosqldb/classes.watcher.ts +96 -0
  211. package/ts/nosqldb/index.ts +51 -0
  212. package/ts/nosqldb/logging.ts +3 -0
  213. package/ts/nosqldb/plugins.ts +37 -0
  214. package/ts/nosqldb/shim.ts +6 -0
  215. package/ts/nosqldb/testsupport.ts +13 -0
  216. package/ts/objectstorage/classes.bucket.ts +1114 -0
  217. package/ts/objectstorage/classes.connection.ts +36 -0
  218. package/ts/objectstorage/classes.directory.ts +425 -0
  219. package/ts/objectstorage/classes.exactpathpurgeerror.ts +24 -0
  220. package/ts/objectstorage/classes.exactuploaderror.ts +25 -0
  221. package/ts/objectstorage/classes.file.ts +303 -0
  222. package/ts/objectstorage/classes.listcursor.ts +89 -0
  223. package/ts/objectstorage/classes.metadata.ts +134 -0
  224. package/ts/objectstorage/classes.smartbucket.ts +445 -0
  225. package/ts/objectstorage/classes.trash.ts +30 -0
  226. package/ts/objectstorage/classes.watcher.ts +289 -0
  227. package/ts/objectstorage/helpers.ts +254 -0
  228. package/ts/objectstorage/index.ts +13 -0
  229. package/ts/objectstorage/interfaces.ts +389 -0
  230. package/ts/objectstorage/internal.exactpathpurge.operations.ts +1936 -0
  231. package/ts/objectstorage/internal.exactupload.capability.ts +580 -0
  232. package/ts/objectstorage/internal.exactupload.operations.ts +1715 -0
  233. package/ts/objectstorage/internal.multipart.planner.ts +90 -0
  234. package/ts/objectstorage/internal.multipart.stream.ts +606 -0
  235. package/ts/objectstorage/internal.multipart.types.ts +106 -0
  236. package/ts/objectstorage/plugins.ts +35 -0
  237. package/ts/plugins.ts +4 -0
  238. package/ts/sqldb/classes.clickhouseconnection.ts +189 -0
  239. package/ts/sqldb/classes.sqlconnection.ts +350 -0
  240. package/ts/sqldb/classes.sqltable.ts +70 -0
  241. package/ts/sqldb/clickhouse/classes.metricwriter.ts +72 -0
  242. package/ts/sqldb/clickhouse/index.ts +16 -0
  243. package/ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.ts +372 -0
  244. package/ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.ts +52 -0
  245. package/ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.ts +120 -0
  246. package/ts/sqldb/clickhouse/smartclickhouse.classes.resultset.ts +44 -0
  247. package/ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.ts +123 -0
  248. package/ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.ts +114 -0
  249. package/ts/sqldb/clickhouse/smartclickhouse.plugins.ts +5 -0
  250. package/ts/sqldb/clickhouse/smartclickhouse.types.ts +134 -0
  251. package/ts/sqldb/index.ts +6 -0
  252. package/ts/sqldb/interfaces.ts +61 -0
  253. package/ts/sqldb/internal.sqlpool.ts +179 -0
  254. package/ts/sqldb/plugins.clickhouse.ts +4 -0
  255. 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
+ };