@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,1114 @@
1
+ // classes.bucket.ts
2
+
3
+ import * as plugins from './plugins.js';
4
+ import * as helpers from './helpers.js';
5
+ import * as interfaces from './interfaces.js';
6
+ import { SmartBucket } from './classes.smartbucket.js';
7
+ import { Directory } from './classes.directory.js';
8
+ import { File } from './classes.file.js';
9
+ import { Trash } from './classes.trash.js';
10
+ import { ListCursor, type IListCursorOptions } from './classes.listcursor.js';
11
+ import { BucketWatcher } from './classes.watcher.js';
12
+ import {
13
+ fastPutStreamExact as fastPutStreamExactOperation,
14
+ probeExactUploadCapability as probeExactUploadCapabilityOperation,
15
+ } from './internal.exactupload.operations.js';
16
+ import {
17
+ probeExactPathPurgeCapability as probeExactPathPurgeCapabilityOperation,
18
+ purgeExactPath as purgeExactPathOperation,
19
+ } from './internal.exactpathpurge.operations.js';
20
+ import { beginBasicBucketOperation } from './internal.exactupload.capability.js';
21
+
22
+ export interface IListObjectKeysPageOptions {
23
+ prefix?: string;
24
+ limit: number;
25
+ startAfter?: string;
26
+ }
27
+
28
+ export interface IListObjectKeysPageResult {
29
+ keys: string[];
30
+ nextStartAfter?: string;
31
+ }
32
+
33
+ const compareUtf8ObjectKeys = (leftArg: string, rightArg: string): number =>
34
+ Buffer.compare(Buffer.from(leftArg, 'utf8'), Buffer.from(rightArg, 'utf8'));
35
+
36
+ const destroyReadableAndWaitForSettlement = async (
37
+ readableArg: plugins.stream.Readable,
38
+ ): Promise<void> => {
39
+ let removeFinishedListeners = (): void => {};
40
+ const settlement = new Promise<boolean>((resolveArg) => {
41
+ removeFinishedListeners = plugins.stream.finished(
42
+ readableArg,
43
+ { readable: true, writable: false },
44
+ () => resolveArg(true),
45
+ );
46
+ });
47
+ readableArg.destroy();
48
+ let settlementDeadline: ReturnType<typeof setTimeout> | undefined;
49
+ let settled = false;
50
+ try {
51
+ settled = await Promise.race([
52
+ settlement,
53
+ new Promise<boolean>((resolveArg) => {
54
+ settlementDeadline = setTimeout(() => resolveArg(false), 5_000);
55
+ }),
56
+ ]);
57
+ } finally {
58
+ if (settlementDeadline) {
59
+ clearTimeout(settlementDeadline);
60
+ }
61
+ removeFinishedListeners();
62
+ }
63
+ if (!settled && !readableArg.closed) {
64
+ const onLateError = (): void => {
65
+ readableArg.removeListener('close', onLateClose);
66
+ };
67
+ const onLateClose = (): void => {
68
+ readableArg.removeListener('error', onLateError);
69
+ };
70
+ readableArg.once('error', onLateError);
71
+ readableArg.once('close', onLateClose);
72
+ }
73
+ };
74
+
75
+ /**
76
+ * The bucket class exposes the basic functionality of a bucket.
77
+ * The functions of the bucket alone are enough to
78
+ * operate on blobs of data in an S3-compatible object store.
79
+ */
80
+ export class Bucket {
81
+ public static async getBucketByName(smartbucketRef: SmartBucket, bucketNameArg: string): Promise<Bucket> {
82
+ const command = new plugins.s3.ListBucketsCommand({});
83
+ const buckets = await smartbucketRef.storageClient.send(command);
84
+ const foundBucket = buckets.Buckets!.find((bucket) => bucket.Name === bucketNameArg);
85
+
86
+ if (foundBucket) {
87
+ console.log(`bucket with name ${bucketNameArg} exists.`);
88
+ console.log(`Taking this as base for new Bucket instance`);
89
+ return new this(smartbucketRef, bucketNameArg);
90
+ } else {
91
+ throw new Error(`Bucket '${bucketNameArg}' not found.`);
92
+ }
93
+ }
94
+
95
+ public static async createBucketByName(smartbucketRef: SmartBucket, bucketName: string) {
96
+ const command = new plugins.s3.CreateBucketCommand({ Bucket: bucketName });
97
+ await smartbucketRef.storageClient.send(command);
98
+ return new Bucket(smartbucketRef, bucketName);
99
+ }
100
+
101
+ public static async removeBucketByName(smartbucketRef: SmartBucket, bucketName: string) {
102
+ const command = new plugins.s3.DeleteBucketCommand({ Bucket: bucketName });
103
+ await smartbucketRef.storageClient.send(command);
104
+ }
105
+
106
+ public smartbucketRef: SmartBucket;
107
+ public name: string;
108
+
109
+ constructor(smartbucketRef: SmartBucket, bucketName: string) {
110
+ this.smartbucketRef = smartbucketRef;
111
+ this.name = bucketName;
112
+ }
113
+
114
+ /**
115
+ * Returns the underlying AWS SDK v3 S3Client for this bucket.
116
+ *
117
+ * Use this when you need to perform operations smartbucket doesn't
118
+ * wrap directly (e.g. lifecycle policies, bucket tagging, multipart
119
+ * upload control, object-lock, inventory config, etc.).
120
+ *
121
+ * The returned client is shared with the parent SmartBucket — do not
122
+ * call `.destroy()` on it.
123
+ */
124
+ public getStorageClient(): plugins.s3.S3Client {
125
+ return this.smartbucketRef.storageClient;
126
+ }
127
+
128
+ /**
129
+ * Performs a live, side-effectful provider probe for the conditional S3
130
+ * semantics required by fastPutStreamExact().
131
+ *
132
+ * Probe objects and multipart uploads use unique private keys and are removed
133
+ * before a capability is returned. A capability becomes stale when the
134
+ * Bucket object, bucket name, parent SmartBucket, S3 client, or SmartBucket
135
+ * lifecycle generation changes.
136
+ */
137
+ public probeExactUploadCapability(
138
+ optionsArg: interfaces.IExactUploadCapabilityProbeOptions = {},
139
+ ): Promise<interfaces.TExactUploadCapabilityProbeResult> {
140
+ return probeExactUploadCapabilityOperation(this, optionsArg);
141
+ }
142
+
143
+ /**
144
+ * Performs a live provider probe for exact conditional deletion,
145
+ * version-specific deletion, delete-marker removal, and verified cleanup.
146
+ */
147
+ public probeExactPathPurgeCapability(
148
+ optionsArg: interfaces.IExactPathPurgeCapabilityProbeOptions = {},
149
+ ): Promise<interfaces.TExactPathPurgeCapabilityProbeResult> {
150
+ return probeExactPathPurgeCapabilityOperation(this, optionsArg);
151
+ }
152
+
153
+ /**
154
+ * gets the base directory of the bucket
155
+ */
156
+ public async getBaseDirectory(): Promise<Directory> {
157
+ return new Directory(this, null!, '');
158
+ }
159
+
160
+ /**
161
+ * gets the trash directory
162
+ */
163
+ public async getTrash(): Promise<Trash> {
164
+ const trash = new Trash(this);
165
+ return trash;
166
+ }
167
+
168
+ public async getDirectoryFromPath(
169
+ pathDescriptorArg: interfaces.IPathDecriptor
170
+ ): Promise<Directory> {
171
+ if (!pathDescriptorArg.path && !pathDescriptorArg.directory) {
172
+ return this.getBaseDirectory();
173
+ }
174
+ const checkPath = await helpers.reducePathDescriptorToPath(pathDescriptorArg);
175
+ const baseDirectory = await this.getBaseDirectory();
176
+ return await baseDirectory.getSubDirectoryByName(checkPath, {
177
+ getEmptyDirectory: true,
178
+ });
179
+ }
180
+
181
+ // ===============
182
+ // Fast Operations
183
+ // ===============
184
+
185
+ /**
186
+ * store file
187
+ */
188
+ public async fastPut(
189
+ optionsArg: interfaces.IPathDecriptor & {
190
+ contents: string | Buffer;
191
+ overwrite?: boolean;
192
+ }
193
+ ): Promise<File> {
194
+ try {
195
+ const reducedPath = await helpers.reducePathDescriptorToPath(optionsArg);
196
+ const exists = await this.fastExists({ path: reducedPath });
197
+
198
+ if (exists && !optionsArg.overwrite) {
199
+ throw new Error(
200
+ `Object already exists at path '${reducedPath}' in bucket '${this.name}'. ` +
201
+ `Set overwrite:true to replace it.`
202
+ );
203
+ } else if (exists && optionsArg.overwrite) {
204
+ console.log(
205
+ `Overwriting existing object at path '${reducedPath}' in bucket '${this.name}'.`
206
+ );
207
+ } else {
208
+ console.log(`Creating new object at path '${reducedPath}' in bucket '${this.name}'.`);
209
+ }
210
+
211
+ const command = new plugins.s3.PutObjectCommand({
212
+ Bucket: this.name,
213
+ Key: reducedPath,
214
+ Body: optionsArg.contents,
215
+ });
216
+ await this.smartbucketRef.storageClient.send(command);
217
+
218
+ console.log(`Object '${reducedPath}' has been successfully stored in bucket '${this.name}'.`);
219
+ const parsedPath = plugins.path.parse(reducedPath);
220
+ return new File({
221
+ directoryRefArg: await this.getDirectoryFromPath({
222
+ path: parsedPath.dir,
223
+ }),
224
+ fileName: parsedPath.base,
225
+ });
226
+ } catch (error) {
227
+ console.error(
228
+ `Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`,
229
+ error
230
+ );
231
+ throw error;
232
+ }
233
+ }
234
+
235
+
236
+ /**
237
+ * get file
238
+ */
239
+ public async fastGet(optionsArg: { path: string }): Promise<Buffer> {
240
+ const source = await this.fastGetStream(optionsArg);
241
+ const chunks: Buffer[] = [];
242
+ try {
243
+ for await (const chunk of source) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
244
+ return Buffer.concat(chunks);
245
+ } finally { source.destroy(); }
246
+ }
247
+
248
+ /**
249
+ * good when time to first byte is important
250
+ * and multiple subscribers are expected
251
+ * @param optionsArg
252
+ * @returns
253
+ */
254
+ public async fastGetReplaySubject(optionsArg: {
255
+ path: string;
256
+ }): Promise<plugins.smartrx.rxjs.ReplaySubject<Buffer>> {
257
+ const command = new plugins.s3.GetObjectCommand({
258
+ Bucket: this.name,
259
+ Key: optionsArg.path,
260
+ });
261
+ const response = await this.smartbucketRef.storageClient.send(command);
262
+ const replaySubject = new plugins.smartrx.rxjs.ReplaySubject<Buffer>();
263
+
264
+ // Convert the stream to a format that supports piping
265
+ const stream = response.Body as any; // SdkStreamMixin includes readable stream
266
+ if (typeof stream.pipe === 'function') {
267
+ const duplexStream = new plugins.smartstream.SmartDuplex<Buffer, void>({
268
+ writeFunction: async (chunk) => {
269
+ replaySubject.next(chunk);
270
+ return;
271
+ },
272
+ finalFunction: async (cb) => {
273
+ replaySubject.complete();
274
+ return;
275
+ },
276
+ });
277
+
278
+ stream.pipe(duplexStream);
279
+ }
280
+
281
+ return replaySubject;
282
+ }
283
+
284
+ public fastGetStream(
285
+ optionsArg: interfaces.IFastGetStreamOptions,
286
+ typeArg: 'webstream'
287
+ ): Promise<ReadableStream>;
288
+ public async fastGetStream(
289
+ optionsArg: interfaces.IFastGetStreamOptions,
290
+ typeArg?: 'nodestream'
291
+ ): Promise<plugins.stream.Readable>;
292
+
293
+ public async fastGetStream(
294
+ optionsArg: interfaces.IFastGetStreamOptions,
295
+ typeArg: 'webstream' | 'nodestream' = 'nodestream'
296
+ ): Promise<ReadableStream | plugins.stream.Readable> {
297
+ const operation = beginBasicBucketOperation(this, optionsArg.signal);
298
+ const disposeBody = async (responseBodyArg: unknown): Promise<void> => {
299
+ try {
300
+ if (responseBodyArg instanceof plugins.stream.Readable) {
301
+ await destroyReadableAndWaitForSettlement(responseBodyArg);
302
+ } else if (
303
+ typeof ReadableStream !== 'undefined'
304
+ && responseBodyArg instanceof ReadableStream
305
+ ) {
306
+ await responseBodyArg.cancel();
307
+ } else if (
308
+ typeof responseBodyArg === 'object'
309
+ && responseBodyArg !== null
310
+ && typeof (responseBodyArg as { destroy?: unknown }).destroy === 'function'
311
+ ) {
312
+ (responseBodyArg as { destroy: () => void }).destroy();
313
+ } else if (
314
+ typeof responseBodyArg === 'object'
315
+ && responseBodyArg !== null
316
+ && typeof (responseBodyArg as { cancel?: unknown }).cancel === 'function'
317
+ ) {
318
+ await (responseBodyArg as { cancel: () => Promise<void> }).cancel();
319
+ }
320
+ } catch {
321
+ // Preserve the primary response-body or conversion failure.
322
+ }
323
+ };
324
+ let response: plugins.s3.GetObjectCommandOutput;
325
+ try {
326
+ response = await operation.runProvider(
327
+ () => operation.client.send(
328
+ new plugins.s3.GetObjectCommand({
329
+ Bucket: operation.bucketName,
330
+ Key: optionsArg.path,
331
+ }),
332
+ { abortSignal: operation.signal },
333
+ ),
334
+ {
335
+ disposeLateResult: async (lateResponseArg) => {
336
+ await disposeBody(lateResponseArg.Body);
337
+ },
338
+ },
339
+ );
340
+ } catch (error) {
341
+ operation.finish();
342
+ throw error;
343
+ }
344
+ const responseBody = response.Body as unknown;
345
+ const disposeResponseBody = async (): Promise<void> => {
346
+ await disposeBody(responseBody);
347
+ };
348
+ let sourceStream: plugins.stream.Readable | null = null;
349
+ try {
350
+ sourceStream = responseBody instanceof plugins.stream.Readable
351
+ ? responseBody
352
+ : (
353
+ typeof ReadableStream !== 'undefined'
354
+ && responseBody instanceof ReadableStream
355
+ )
356
+ ? plugins.stream.Readable.fromWeb(responseBody as never)
357
+ : null;
358
+ } catch (error) {
359
+ await disposeResponseBody();
360
+ operation.finish();
361
+ throw error;
362
+ }
363
+ if (!sourceStream) {
364
+ await disposeResponseBody();
365
+ operation.finish();
366
+ throw new Error('S3 GetObject returned no supported response stream.');
367
+ }
368
+
369
+ const outputStream = new plugins.stream.PassThrough();
370
+ const abortError = (): Error => {
371
+ if (operation.signal.reason instanceof Error) {
372
+ return operation.signal.reason;
373
+ }
374
+ const error = new Error('S3 object download was aborted.');
375
+ error.name = 'AbortError';
376
+ return error;
377
+ };
378
+ let sourceEnded = false;
379
+ let forwardingCleanedUp = false;
380
+ let sourceErrorGuardReleased = false;
381
+ const preventUnhandledSourceError = (): void => {};
382
+ const preventUnhandledOutputError = (): void => {};
383
+ const cleanupForwarding = (): void => {
384
+ if (forwardingCleanedUp) {
385
+ return;
386
+ }
387
+ forwardingCleanedUp = true;
388
+ operation.signal.removeEventListener('abort', onAbort);
389
+ sourceStream.removeListener('error', onSourceError);
390
+ sourceStream.removeListener('end', onSourceEnd);
391
+ sourceStream.removeListener('close', onSourceClose);
392
+ outputStream.removeListener('error', preventUnhandledOutputError);
393
+ outputStream.removeListener('close', onOutputClose);
394
+ };
395
+ const releaseSourceErrorGuard = (): void => {
396
+ if (sourceErrorGuardReleased) {
397
+ return;
398
+ }
399
+ sourceErrorGuardReleased = true;
400
+ sourceStream.removeListener('error', preventUnhandledSourceError);
401
+ };
402
+ let sourceSettlement: Promise<void> | undefined;
403
+ const settleSource = (): Promise<void> => {
404
+ sourceSettlement ??= destroyReadableAndWaitForSettlement(sourceStream);
405
+ return sourceSettlement;
406
+ };
407
+ const finishAfterSourceSettlement = async (): Promise<void> => {
408
+ await settleSource();
409
+ releaseSourceErrorGuard();
410
+ operation.finish();
411
+ };
412
+ const onAbort = (): void => {
413
+ const error = abortError();
414
+ outputStream.destroy(error);
415
+ void settleSource();
416
+ };
417
+ const onSourceError = (errorArg: Error): void => {
418
+ outputStream.destroy(errorArg);
419
+ };
420
+ const onSourceEnd = (): void => {
421
+ sourceEnded = true;
422
+ };
423
+ const onSourceClose = (): void => {
424
+ if (!sourceEnded && !outputStream.destroyed) {
425
+ outputStream.destroy(
426
+ new Error('S3 object download stream closed before completion.'),
427
+ );
428
+ }
429
+ };
430
+ const onOutputClose = (): void => {
431
+ cleanupForwarding();
432
+ if (sourceStream.closed) {
433
+ releaseSourceErrorGuard();
434
+ operation.finish();
435
+ } else {
436
+ void finishAfterSourceSettlement();
437
+ }
438
+ };
439
+
440
+ sourceStream.on('error', preventUnhandledSourceError);
441
+ sourceStream.once('error', onSourceError);
442
+ sourceStream.once('end', onSourceEnd);
443
+ sourceStream.once('close', onSourceClose);
444
+ outputStream.on('error', preventUnhandledOutputError);
445
+ outputStream.once('close', onOutputClose);
446
+ operation.signal.addEventListener('abort', onAbort, { once: true });
447
+
448
+ if (sourceStream.closed) {
449
+ sourceEnded = sourceStream.readableEnded;
450
+ onSourceClose();
451
+ }
452
+
453
+ if (operation.signal.aborted) {
454
+ const error = abortError();
455
+ cleanupForwarding();
456
+ outputStream.destroy();
457
+ await settleSource();
458
+ releaseSourceErrorGuard();
459
+ operation.finish();
460
+ throw error;
461
+ }
462
+
463
+ let result: ReadableStream | plugins.stream.Readable;
464
+ try {
465
+ if (typeArg === 'nodestream') {
466
+ result = outputStream;
467
+ } else if (typeArg === 'webstream') {
468
+ result = plugins.stream.Readable.toWeb(outputStream) as ReadableStream;
469
+ } else {
470
+ throw new Error('unknown typeArg');
471
+ }
472
+ sourceStream.pipe(outputStream);
473
+ } catch (error) {
474
+ cleanupForwarding();
475
+ outputStream.destroy();
476
+ await settleSource();
477
+ releaseSourceErrorGuard();
478
+ operation.finish();
479
+ throw error;
480
+ }
481
+ return result;
482
+ }
483
+
484
+ /**
485
+ * store file as stream
486
+ */
487
+ public async fastPutStream(
488
+ optionsArg: interfaces.IFastPutStreamOptions,
489
+ ): Promise<void> {
490
+ if (
491
+ optionsArg.contentLength !== undefined
492
+ && (
493
+ !Number.isSafeInteger(optionsArg.contentLength)
494
+ || optionsArg.contentLength < 0
495
+ )
496
+ ) {
497
+ throw new Error('contentLength must be a non-negative safe integer');
498
+ }
499
+ const operation = beginBasicBucketOperation(this, optionsArg.signal);
500
+ let uploadStream: plugins.stream.Readable | undefined;
501
+ let sourceOwnershipStarted = false;
502
+ const disposeSource = async (): Promise<void> => {
503
+ if (!uploadStream) {
504
+ if (optionsArg.readableStream instanceof plugins.stream.Readable) {
505
+ uploadStream = optionsArg.readableStream;
506
+ } else {
507
+ const cancellation = optionsArg.readableStream.cancel().catch(() => undefined);
508
+ let cancellationDeadline: ReturnType<typeof setTimeout> | undefined;
509
+ try {
510
+ await Promise.race([
511
+ cancellation,
512
+ new Promise<void>((resolveCancellationArg) => {
513
+ cancellationDeadline = setTimeout(resolveCancellationArg, 5_000);
514
+ }),
515
+ ]);
516
+ } finally {
517
+ if (cancellationDeadline) {
518
+ clearTimeout(cancellationDeadline);
519
+ }
520
+ }
521
+ return;
522
+ }
523
+ }
524
+ await destroyReadableAndWaitForSettlement(uploadStream);
525
+ };
526
+ let putDispatched = false;
527
+ try {
528
+ let exists: boolean;
529
+ try {
530
+ await operation.runProvider(() => operation.client.send(
531
+ new plugins.s3.HeadObjectCommand({
532
+ Bucket: operation.bucketName,
533
+ Key: optionsArg.path,
534
+ }),
535
+ { abortSignal: operation.signal },
536
+ ));
537
+ exists = true;
538
+ } catch (error: any) {
539
+ try {
540
+ operation.assertCurrent();
541
+ } catch (abortError) {
542
+ throw abortError;
543
+ }
544
+ if (error?.name === 'NotFound') {
545
+ exists = false;
546
+ } else {
547
+ throw error;
548
+ }
549
+ }
550
+
551
+ if (exists && !optionsArg.overwrite) {
552
+ throw new Error(
553
+ `Object already exists at path '${optionsArg.path}' in bucket '${this.name}'. ` +
554
+ `Set overwrite:true to replace it.`
555
+ );
556
+ } else if (exists && optionsArg.overwrite) {
557
+ console.log(
558
+ `Overwriting existing object at path '${optionsArg.path}' in bucket '${this.name}'.`
559
+ );
560
+ } else {
561
+ console.log(`Creating new object at path '${optionsArg.path}' in bucket '${this.name}'.`);
562
+ }
563
+
564
+ sourceOwnershipStarted = true;
565
+ uploadStream = optionsArg.readableStream instanceof plugins.stream.Readable
566
+ ? optionsArg.readableStream
567
+ : plugins.stream.Readable.fromWeb(optionsArg.readableStream as never);
568
+ const command = new plugins.s3.PutObjectCommand({
569
+ Bucket: operation.bucketName,
570
+ Key: optionsArg.path,
571
+ Body: uploadStream,
572
+ ContentLength: optionsArg.contentLength,
573
+ Metadata: optionsArg.nativeMetadata,
574
+ });
575
+ putDispatched = true;
576
+ await operation.runProvider(
577
+ () => operation.client.send(command, { abortSignal: operation.signal }),
578
+ );
579
+
580
+ console.log(
581
+ `Object '${optionsArg.path}' has been successfully stored in bucket '${this.name}'.`
582
+ );
583
+ } catch (error) {
584
+ if (sourceOwnershipStarted || putDispatched || operation.signal.aborted) {
585
+ await disposeSource();
586
+ }
587
+ console.error(
588
+ `Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`,
589
+ error
590
+ );
591
+ throw error;
592
+ } finally {
593
+ operation.finish();
594
+ }
595
+ }
596
+
597
+ /**
598
+ * Creates an object from a stream while enforcing the declared byte length
599
+ * and create-only destination semantics.
600
+ *
601
+ * Non-empty inputs always use multipart upload and never fall back to a
602
+ * single streaming PutObject request. Memory is bounded by the returned
603
+ * multipart plan. Every source chunk must be no larger than the effective
604
+ * multipart part size (5 MiB by default, or requestedPartSize after protocol
605
+ * normalization).
606
+ */
607
+ public fastPutStreamExact(
608
+ optionsArg: interfaces.IFastPutStreamExactOptions,
609
+ ): Promise<interfaces.IExactUploadResult> {
610
+ return fastPutStreamExactOperation(this, optionsArg);
611
+ }
612
+
613
+ /**
614
+ * Permanently removes the exact key, including every object version and
615
+ * delete marker, without deleting keys that merely share its prefix.
616
+ */
617
+ public purgeExactPath(
618
+ optionsArg: interfaces.IExactPathPurgeOptions,
619
+ ): Promise<interfaces.IExactPathPurgeResult> {
620
+ return purgeExactPathOperation(this, optionsArg);
621
+ }
622
+
623
+ public async fastCopy(optionsArg: interfaces.IFastCopyOptions): Promise<void> {
624
+ const operation = beginBasicBucketOperation(this, optionsArg.signal);
625
+ try {
626
+ const targetBucketName = optionsArg.targetBucket ? optionsArg.targetBucket.name : this.name;
627
+
628
+ // Retrieve current object information to use in copy conditions
629
+ const currentObjInfo = await operation.runProvider(
630
+ () => operation.client.send(new plugins.s3.HeadObjectCommand({
631
+ Bucket: operation.bucketName,
632
+ Key: optionsArg.sourcePath,
633
+ }), { abortSignal: operation.signal }),
634
+ );
635
+
636
+ // Prepare new metadata
637
+ const newNativeMetadata = {
638
+ ...(optionsArg.deleteExistingNativeMetadata ? {} : currentObjInfo.Metadata),
639
+ ...optionsArg.nativeMetadata,
640
+ };
641
+
642
+ // Define the copy operation
643
+ const copySource = `${operation.bucketName}/${optionsArg.sourcePath}`;
644
+ const command = new plugins.s3.CopyObjectCommand({
645
+ Bucket: targetBucketName,
646
+ CopySource: copySource,
647
+ Key: optionsArg.destinationPath || optionsArg.sourcePath,
648
+ Metadata: newNativeMetadata,
649
+ MetadataDirective: optionsArg.deleteExistingNativeMetadata ? 'REPLACE' : 'COPY',
650
+ });
651
+ await operation.runProvider(
652
+ () => operation.client.send(command, { abortSignal: operation.signal }),
653
+ );
654
+ } catch (err) {
655
+ console.error('Error updating metadata:', err);
656
+ throw err; // rethrow to allow caller to handle
657
+ } finally {
658
+ operation.finish();
659
+ }
660
+ }
661
+
662
+ /**
663
+ * Move object from one path to another within the same bucket or to another bucket
664
+ */
665
+ public async fastMove(optionsArg: {
666
+ sourcePath: string;
667
+ destinationPath: string;
668
+ targetBucket?: Bucket;
669
+ overwrite?: boolean;
670
+ }): Promise<void> {
671
+ try {
672
+ const destinationBucket = optionsArg.targetBucket || this;
673
+ const exists = await destinationBucket.fastExists({
674
+ path: optionsArg.destinationPath,
675
+ });
676
+
677
+ if (exists && !optionsArg.overwrite) {
678
+ console.error(
679
+ `Object already exists at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
680
+ );
681
+ return;
682
+ } else if (exists && optionsArg.overwrite) {
683
+ console.log(
684
+ `Overwriting existing object at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
685
+ );
686
+ } else {
687
+ console.log(
688
+ `Moving object to path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
689
+ );
690
+ }
691
+
692
+ await this.fastCopy(optionsArg);
693
+ await this.fastRemove({ path: optionsArg.sourcePath });
694
+
695
+ console.log(
696
+ `Object '${optionsArg.sourcePath}' has been successfully moved to '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
697
+ );
698
+ } catch (error) {
699
+ console.error(
700
+ `Error moving object from '${optionsArg.sourcePath}' to '${optionsArg.destinationPath}':`,
701
+ error
702
+ );
703
+ throw error;
704
+ }
705
+ }
706
+
707
+ /**
708
+ * removeObject
709
+ */
710
+ public async fastRemove(optionsArg: interfaces.IFastRemoveOptions) {
711
+ const operation = beginBasicBucketOperation(this, optionsArg.signal);
712
+ const command = new plugins.s3.DeleteObjectCommand({
713
+ Bucket: operation.bucketName,
714
+ Key: optionsArg.path,
715
+ });
716
+ try {
717
+ await operation.runProvider(
718
+ () => operation.client.send(command, { abortSignal: operation.signal }),
719
+ );
720
+ } finally {
721
+ operation.finish();
722
+ }
723
+ }
724
+
725
+ /**
726
+ * check whether file exists
727
+ * @param optionsArg
728
+ * @returns
729
+ */
730
+ public async fastExists(optionsArg: interfaces.IFastExistsOptions): Promise<boolean> {
731
+ const operation = beginBasicBucketOperation(this, optionsArg.signal);
732
+ try {
733
+ const command = new plugins.s3.HeadObjectCommand({
734
+ Bucket: operation.bucketName,
735
+ Key: optionsArg.path,
736
+ });
737
+ await operation.runProvider(
738
+ () => operation.client.send(command, { abortSignal: operation.signal }),
739
+ );
740
+ console.log(`Object '${optionsArg.path}' exists in bucket '${this.name}'.`);
741
+ return true;
742
+ } catch (error: any) {
743
+ try {
744
+ operation.assertCurrent();
745
+ } catch (abortError) {
746
+ throw abortError;
747
+ }
748
+ if (error?.name === 'NotFound') {
749
+ console.log(`Object '${optionsArg.path}' does not exist in bucket '${this.name}'.`);
750
+ return false;
751
+ } else {
752
+ console.error('Error checking object existence:', error);
753
+ throw error; // Rethrow if it's not a NotFound error to handle unexpected issues
754
+ }
755
+ } finally {
756
+ operation.finish();
757
+ }
758
+ }
759
+
760
+ /**
761
+ * deletes this bucket
762
+ */
763
+ public async delete() {
764
+ await this.smartbucketRef.storageClient.send(
765
+ new plugins.s3.DeleteBucketCommand({ Bucket: this.name })
766
+ );
767
+ }
768
+
769
+ public async fastStat(
770
+ pathDescriptor: interfaces.IFastStatOptions,
771
+ ) {
772
+ const operation = beginBasicBucketOperation(this, pathDescriptor.signal);
773
+ try {
774
+ operation.assertCurrent();
775
+ const checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor);
776
+ operation.assertCurrent();
777
+ const command = new plugins.s3.HeadObjectCommand({
778
+ Bucket: operation.bucketName,
779
+ Key: checkPath,
780
+ });
781
+ return await operation.runProvider(
782
+ () => operation.client.send(command, { abortSignal: operation.signal }),
783
+ );
784
+ } finally {
785
+ operation.finish();
786
+ }
787
+ }
788
+
789
+ public async isDirectory(pathDescriptor: interfaces.IPathDecriptor): Promise<boolean> {
790
+ const checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor);
791
+ const command = new plugins.s3.ListObjectsV2Command({
792
+ Bucket: this.name,
793
+ Prefix: checkPath,
794
+ Delimiter: '/',
795
+ });
796
+ const { CommonPrefixes } = await this.smartbucketRef.storageClient.send(command);
797
+ return !!CommonPrefixes && CommonPrefixes.length > 0;
798
+ }
799
+
800
+ public async isFile(pathDescriptor: interfaces.IPathDecriptor): Promise<boolean> {
801
+ const checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor);
802
+ const command = new plugins.s3.ListObjectsV2Command({
803
+ Bucket: this.name,
804
+ Prefix: checkPath,
805
+ Delimiter: '/',
806
+ });
807
+ const { Contents } = await this.smartbucketRef.storageClient.send(command);
808
+ return !!Contents && Contents.length > 0;
809
+ }
810
+
811
+ public async getMagicBytes(optionsArg: { path: string; length: number }): Promise<Buffer> {
812
+ try {
813
+ const command = new plugins.s3.GetObjectCommand({
814
+ Bucket: this.name,
815
+ Key: optionsArg.path,
816
+ Range: `bytes=0-${optionsArg.length - 1}`,
817
+ });
818
+ const response = await this.smartbucketRef.storageClient.send(command);
819
+ const chunks: Buffer[] = [];
820
+ const stream = response.Body as any; // SdkStreamMixin includes readable stream
821
+
822
+ for await (const chunk of stream) {
823
+ chunks.push(chunk);
824
+ }
825
+ return Buffer.concat(chunks);
826
+ } catch (error) {
827
+ console.error(
828
+ `Error retrieving magic bytes from object at path '${optionsArg.path}' in bucket '${this.name}':`,
829
+ error
830
+ );
831
+ throw error;
832
+ }
833
+ }
834
+
835
+ // ==========================================
836
+ // Memory-Efficient Listing Methods (Phase 1)
837
+ // ==========================================
838
+
839
+ /**
840
+ * List all objects with a given prefix using async generator (memory-efficient streaming)
841
+ * @param prefix - Optional prefix to filter objects (default: '' for all objects)
842
+ * @yields Object keys one at a time
843
+ * @example
844
+ * ```ts
845
+ * for await (const key of bucket.listAllObjects('npm/')) {
846
+ * console.log(key);
847
+ * if (shouldStop) break; // Early exit supported
848
+ * }
849
+ * ```
850
+ */
851
+ public async *listAllObjects(prefix: string = ''): AsyncIterableIterator<string> {
852
+ let continuationToken: string | undefined;
853
+
854
+ do {
855
+ const command = new plugins.s3.ListObjectsV2Command({
856
+ Bucket: this.name,
857
+ Prefix: prefix,
858
+ ContinuationToken: continuationToken,
859
+ });
860
+
861
+ const response = await this.smartbucketRef.storageClient.send(command);
862
+
863
+ for (const obj of response.Contents || []) {
864
+ if (obj.Key) yield obj.Key;
865
+ }
866
+
867
+ continuationToken = response.NextContinuationToken;
868
+ } while (continuationToken);
869
+ }
870
+
871
+ /**
872
+ * List all objects as an RxJS Observable (for complex reactive pipelines)
873
+ * @param prefix - Optional prefix to filter objects (default: '' for all objects)
874
+ * @returns Observable that emits object keys
875
+ * @example
876
+ * ```ts
877
+ * bucket.listAllObjectsObservable('npm/')
878
+ * .pipe(
879
+ * filter(key => key.endsWith('.json')),
880
+ * take(100)
881
+ * )
882
+ * .subscribe(key => console.log(key));
883
+ * ```
884
+ */
885
+ public listAllObjectsObservable(prefix: string = ''): plugins.smartrx.rxjs.Observable<string> {
886
+ return new plugins.smartrx.rxjs.Observable<string>((subscriber) => {
887
+ const fetchPage = async (token?: string) => {
888
+ try {
889
+ const command = new plugins.s3.ListObjectsV2Command({
890
+ Bucket: this.name,
891
+ Prefix: prefix,
892
+ ContinuationToken: token,
893
+ });
894
+
895
+ const response = await this.smartbucketRef.storageClient.send(command);
896
+
897
+ for (const obj of response.Contents || []) {
898
+ if (obj.Key) subscriber.next(obj.Key);
899
+ }
900
+
901
+ if (response.NextContinuationToken) {
902
+ await fetchPage(response.NextContinuationToken);
903
+ } else {
904
+ subscriber.complete();
905
+ }
906
+ } catch (error) {
907
+ subscriber.error(error);
908
+ }
909
+ };
910
+
911
+ fetchPage();
912
+ });
913
+ }
914
+
915
+ /**
916
+ * Create a cursor for manual pagination control
917
+ * @param prefix - Optional prefix to filter objects (default: '' for all objects)
918
+ * @param options - Cursor options (pageSize, etc.)
919
+ * @returns ListCursor instance
920
+ * @example
921
+ * ```ts
922
+ * const cursor = bucket.createCursor('npm/', { pageSize: 500 });
923
+ * while (cursor.hasMore()) {
924
+ * const { keys, done } = await cursor.next();
925
+ * console.log(`Processing ${keys.length} keys...`);
926
+ * }
927
+ * ```
928
+ */
929
+ public createCursor(prefix: string = '', options?: IListCursorOptions): ListCursor {
930
+ return new ListCursor(this, prefix, options);
931
+ }
932
+
933
+ /**
934
+ * List one deletion-stable keyset page using S3 ListObjectsV2 StartAfter.
935
+ * This requires a general-purpose S3 provider with UTF-8 byte-ordered keys.
936
+ */
937
+ public async listObjectKeysPage(
938
+ optionsArg: IListObjectKeysPageOptions,
939
+ ): Promise<IListObjectKeysPageResult> {
940
+ if (!optionsArg || typeof optionsArg !== 'object' || Array.isArray(optionsArg)) {
941
+ throw new TypeError('Object key page options must be a plain object');
942
+ }
943
+ const prefix = optionsArg.prefix ?? '';
944
+ if (typeof prefix !== 'string') {
945
+ throw new TypeError('Object key page prefix must be a string');
946
+ }
947
+ if (!Number.isInteger(optionsArg.limit) || optionsArg.limit < 1 || optionsArg.limit > 1_000) {
948
+ throw new Error('Object key page limit must be an integer from 1 to 1000');
949
+ }
950
+ if (
951
+ optionsArg.startAfter !== undefined
952
+ && (
953
+ typeof optionsArg.startAfter !== 'string'
954
+ || optionsArg.startAfter.length === 0
955
+ || !optionsArg.startAfter.startsWith(prefix)
956
+ )
957
+ ) {
958
+ throw new Error('Object key page startAfter must be a non-empty key under the prefix');
959
+ }
960
+
961
+ const response = await this.smartbucketRef.storageClient.send(
962
+ new plugins.s3.ListObjectsV2Command({
963
+ Bucket: this.name,
964
+ Prefix: prefix,
965
+ MaxKeys: optionsArg.limit,
966
+ ...(optionsArg.startAfter ? { StartAfter: optionsArg.startAfter } : {}),
967
+ }),
968
+ );
969
+ if (typeof response.IsTruncated !== 'boolean') {
970
+ throw new Error('Object storage returned an invalid key page truncation flag');
971
+ }
972
+ if (response.Contents !== undefined && !Array.isArray(response.Contents)) {
973
+ throw new Error('Object storage returned invalid key page contents');
974
+ }
975
+
976
+ const keys = (response.Contents ?? []).map((entry) => {
977
+ if (!entry || typeof entry.Key !== 'string') {
978
+ throw new Error('Object storage returned a key page entry without a string key');
979
+ }
980
+ return entry.Key;
981
+ });
982
+ if (keys.length > optionsArg.limit) {
983
+ throw new Error('Object storage returned more keys than the requested page limit');
984
+ }
985
+ if (new Set(keys).size !== keys.length) {
986
+ throw new Error('Object storage returned duplicate keys in one page');
987
+ }
988
+
989
+ let previousKey = optionsArg.startAfter;
990
+ for (const key of keys) {
991
+ if (!key.startsWith(prefix)) {
992
+ throw new Error('Object storage returned a key outside the requested prefix');
993
+ }
994
+ if (previousKey !== undefined && compareUtf8ObjectKeys(key, previousKey) <= 0) {
995
+ throw new Error('Object storage returned keys outside strict UTF-8 byte order');
996
+ }
997
+ previousKey = key;
998
+ }
999
+ if (response.IsTruncated && keys.length === 0) {
1000
+ throw new Error('Object storage returned an empty truncated key page');
1001
+ }
1002
+
1003
+ return {
1004
+ keys,
1005
+ ...(response.IsTruncated ? { nextStartAfter: keys[keys.length - 1] } : {}),
1006
+ };
1007
+ }
1008
+
1009
+ /**
1010
+ * Create a watcher for monitoring bucket changes (add/modify/delete)
1011
+ * @param options - Watcher options (prefix, pollIntervalMs, etc.)
1012
+ * @returns BucketWatcher instance
1013
+ * @example
1014
+ * ```ts
1015
+ * const watcher = bucket.createWatcher({ prefix: 'uploads/', pollIntervalMs: 3000 });
1016
+ * watcher.changeSubject.subscribe((change) => console.log('Change:', change));
1017
+ * await watcher.start();
1018
+ * // ... later
1019
+ * await watcher.stop();
1020
+ * ```
1021
+ */
1022
+ public createWatcher(options?: interfaces.IBucketWatcherOptions): BucketWatcher {
1023
+ return new BucketWatcher(this, options);
1024
+ }
1025
+
1026
+ // ==========================================
1027
+ // High-Level Listing Helpers (Phase 2)
1028
+ // ==========================================
1029
+
1030
+ /**
1031
+ * Find objects matching a glob pattern (memory-efficient)
1032
+ * @param pattern - Glob pattern (e.g., "**\/*.json", "npm/packages/*\/index.json")
1033
+ * @yields Matching object keys
1034
+ * @example
1035
+ * ```ts
1036
+ * for await (const key of bucket.findByGlob('npm/packages/*\/index.json')) {
1037
+ * console.log('Found package index:', key);
1038
+ * }
1039
+ * ```
1040
+ */
1041
+ public async *findByGlob(pattern: string): AsyncIterableIterator<string> {
1042
+ const matcher = new plugins.Minimatch(pattern);
1043
+ for await (const key of this.listAllObjects('')) {
1044
+ if (matcher.match(key)) yield key;
1045
+ }
1046
+ }
1047
+
1048
+ /**
1049
+ * List all objects and collect into an array (convenience method)
1050
+ * WARNING: Loads entire result set into memory. Use listAllObjects() generator for large buckets.
1051
+ * @param prefix - Optional prefix to filter objects (default: '' for all objects)
1052
+ * @returns Array of all object keys
1053
+ * @example
1054
+ * ```ts
1055
+ * const allKeys = await bucket.listAllObjectsArray('npm/');
1056
+ * console.log(`Found ${allKeys.length} objects`);
1057
+ * ```
1058
+ */
1059
+ public async listAllObjectsArray(prefix: string = ''): Promise<string[]> {
1060
+ const keys: string[] = [];
1061
+ for await (const key of this.listAllObjects(prefix)) {
1062
+ keys.push(key);
1063
+ }
1064
+ return keys;
1065
+ }
1066
+
1067
+ public async cleanAllContents(): Promise<void> {
1068
+ try {
1069
+ // Define the command type explicitly
1070
+ const listCommandInput: plugins.s3.ListObjectsV2CommandInput = {
1071
+ Bucket: this.name,
1072
+ };
1073
+
1074
+ let isTruncated = true;
1075
+ let continuationToken: string | undefined = undefined;
1076
+
1077
+ while (isTruncated) {
1078
+ // Add the continuation token to the input if present
1079
+ const listCommand = new plugins.s3.ListObjectsV2Command({
1080
+ ...listCommandInput,
1081
+ ContinuationToken: continuationToken,
1082
+ });
1083
+
1084
+ // Explicitly type the response
1085
+ const response: plugins.s3.ListObjectsV2Output =
1086
+ await this.smartbucketRef.storageClient.send(listCommand);
1087
+
1088
+ console.log(`Cleaning contents of bucket '${this.name}': Now deleting ${response.Contents?.length} items...`);
1089
+
1090
+ if (response.Contents && response.Contents.length > 0) {
1091
+ // Delete objects in batches, mapping each item to { Key: string }
1092
+ const deleteCommand = new plugins.s3.DeleteObjectsCommand({
1093
+ Bucket: this.name,
1094
+ Delete: {
1095
+ Objects: response.Contents.map((item) => ({ Key: item.Key! })),
1096
+ Quiet: true,
1097
+ },
1098
+ });
1099
+
1100
+ await this.smartbucketRef.storageClient.send(deleteCommand);
1101
+ }
1102
+
1103
+ // Update continuation token and truncation status
1104
+ isTruncated = response.IsTruncated || false;
1105
+ continuationToken = response.NextContinuationToken;
1106
+ }
1107
+
1108
+ console.log(`All contents in bucket '${this.name}' have been deleted.`);
1109
+ } catch (error) {
1110
+ console.error(`Error cleaning contents of bucket '${this.name}':`, error);
1111
+ throw error;
1112
+ }
1113
+ }
1114
+ }