@lossless.org/client 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/nosqldb/classes.atomicdelete.d.ts +1 -0
- package/dist_ts/nosqldb/classes.atomicdelete.js +10 -7
- package/dist_ts/nosqldb/classes.atomicfindoneandupdate.d.ts +2 -0
- package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +9 -5
- package/dist_ts/nosqldb/classes.atomicupdate.d.ts +2 -0
- package/dist_ts/nosqldb/classes.atomicupdate.js +13 -8
- package/dist_ts/nosqldb/classes.collection.d.ts +13 -4
- package/dist_ts/nosqldb/classes.collection.js +57 -64
- package/dist_ts/nosqldb/classes.db.d.ts +1 -1
- package/dist_ts/nosqldb/classes.doc.d.ts +60 -3
- package/dist_ts/nosqldb/classes.doc.js +68 -26
- package/dist_ts/nosqldb/classes.exactpersistence.d.ts +36 -7
- package/dist_ts/nosqldb/classes.exactpersistence.js +92 -81
- package/dist_ts/nosqldb/classes.operationdeadline.d.ts +61 -0
- package/dist_ts/nosqldb/classes.operationdeadline.js +149 -0
- package/dist_ts/nosqldb/classes.persistence.d.ts +16 -1
- package/dist_ts/nosqldb/classes.persistence.js +20 -1
- package/dist_ts/nosqldb/classes.session.d.ts +73 -2
- package/dist_ts/nosqldb/classes.session.js +164 -38
- package/dist_ts/objectstorage/classes.bucket.d.ts +8 -3
- package/dist_ts/objectstorage/classes.bucket.js +83 -131
- package/dist_ts/objectstorage/classes.directory.js +17 -28
- package/dist_ts/objectstorage/classes.smartbucket.d.ts +13 -6
- package/dist_ts/objectstorage/classes.smartbucket.js +18 -12
- package/dist_ts/objectstorage/classes.watcher.js +1 -2
- package/dist_ts/objectstorage/interfaces.d.ts +8 -0
- package/dist_ts/objectstorage/internal.bucketrequests.d.ts +14 -0
- package/dist_ts/objectstorage/internal.bucketrequests.js +53 -0
- package/dist_ts/objectstorage/internal.exactupload.capability.d.ts +12 -0
- package/dist_ts/objectstorage/internal.exactupload.capability.js +46 -17
- package/package.json +1 -1
- package/readme.md +6 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/nosqldb/classes.atomicdelete.ts +10 -6
- package/ts/nosqldb/classes.atomicfindoneandupdate.ts +10 -4
- package/ts/nosqldb/classes.atomicupdate.ts +14 -7
- package/ts/nosqldb/classes.collection.ts +82 -73
- package/ts/nosqldb/classes.db.ts +1 -1
- package/ts/nosqldb/classes.doc.ts +162 -33
- package/ts/nosqldb/classes.exactpersistence.ts +131 -93
- package/ts/nosqldb/classes.operationdeadline.ts +179 -0
- package/ts/nosqldb/classes.persistence.ts +36 -1
- package/ts/nosqldb/classes.session.ts +221 -39
- package/ts/objectstorage/classes.bucket.ts +111 -158
- package/ts/objectstorage/classes.directory.ts +18 -27
- package/ts/objectstorage/classes.smartbucket.ts +21 -11
- package/ts/objectstorage/classes.watcher.ts +0 -1
- package/ts/objectstorage/interfaces.ts +9 -0
- package/ts/objectstorage/internal.bucketrequests.ts +70 -0
- package/ts/objectstorage/internal.exactupload.capability.ts +105 -24
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
purgeExactPath as purgeExactPathOperation,
|
|
19
19
|
} from './internal.exactpathpurge.operations.js';
|
|
20
20
|
import { beginBasicBucketOperation } from './internal.exactupload.capability.js';
|
|
21
|
+
import { headBucketExists, runAccountRequest } from './internal.bucketrequests.js';
|
|
21
22
|
|
|
22
23
|
export interface IListObjectKeysPageOptions {
|
|
23
24
|
prefix?: string;
|
|
@@ -240,29 +241,45 @@ const destroyReadableAndWaitForSettlement = async (
|
|
|
240
241
|
* operate on blobs of data in an S3-compatible object store.
|
|
241
242
|
*/
|
|
242
243
|
export class Bucket {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Returns a reference to an existing bucket, checked with one HeadBucket
|
|
246
|
+
* request. Rejects when the bucket does not exist or the credentials may
|
|
247
|
+
* not access it.
|
|
248
|
+
*/
|
|
249
|
+
public static async getBucketByName(
|
|
250
|
+
smartbucketRef: SmartBucket,
|
|
251
|
+
bucketNameArg: string,
|
|
252
|
+
optionsArg: interfaces.IBucketRequestOptions = {},
|
|
253
|
+
): Promise<Bucket> {
|
|
254
|
+
if (!(await headBucketExists(smartbucketRef, bucketNameArg, optionsArg.signal))) {
|
|
253
255
|
throw new Error(`Bucket '${bucketNameArg}' not found.`);
|
|
254
256
|
}
|
|
257
|
+
return new this(smartbucketRef, bucketNameArg);
|
|
255
258
|
}
|
|
256
259
|
|
|
257
|
-
public static async createBucketByName(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
+
public static async createBucketByName(
|
|
261
|
+
smartbucketRef: SmartBucket,
|
|
262
|
+
bucketName: string,
|
|
263
|
+
optionsArg: interfaces.IBucketRequestOptions = {},
|
|
264
|
+
) {
|
|
265
|
+
await runAccountRequest(smartbucketRef, optionsArg.signal, (clientArg, abortSignalArg) =>
|
|
266
|
+
clientArg.send(new plugins.s3.CreateBucketCommand({ Bucket: bucketName }), {
|
|
267
|
+
abortSignal: abortSignalArg,
|
|
268
|
+
}),
|
|
269
|
+
);
|
|
260
270
|
return new Bucket(smartbucketRef, bucketName);
|
|
261
271
|
}
|
|
262
272
|
|
|
263
|
-
public static async removeBucketByName(
|
|
264
|
-
|
|
265
|
-
|
|
273
|
+
public static async removeBucketByName(
|
|
274
|
+
smartbucketRef: SmartBucket,
|
|
275
|
+
bucketName: string,
|
|
276
|
+
optionsArg: interfaces.IBucketRequestOptions = {},
|
|
277
|
+
) {
|
|
278
|
+
await runAccountRequest(smartbucketRef, optionsArg.signal, (clientArg, abortSignalArg) =>
|
|
279
|
+
clientArg.send(new plugins.s3.DeleteBucketCommand({ Bucket: bucketName }), {
|
|
280
|
+
abortSignal: abortSignalArg,
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
266
283
|
}
|
|
267
284
|
|
|
268
285
|
public smartbucketRef: SmartBucket;
|
|
@@ -353,43 +370,32 @@ export class Bucket {
|
|
|
353
370
|
overwrite?: boolean;
|
|
354
371
|
}
|
|
355
372
|
): Promise<File> {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
);
|
|
366
|
-
} else {
|
|
367
|
-
console.log(`Creating new object at path '${reducedPath}' in bucket '${this.name}'.`);
|
|
368
|
-
}
|
|
373
|
+
const reducedPath = await helpers.reducePathDescriptorToPath(optionsArg);
|
|
374
|
+
if (optionsArg.overwrite !== true) {
|
|
375
|
+
const exists = await this.fastExists({ path: reducedPath });
|
|
376
|
+
|
|
377
|
+
if (exists) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
`Object already exists at path '${reducedPath}' in bucket '${this.name}'. ` +
|
|
380
|
+
`Set overwrite:true to replace it.`
|
|
381
|
+
);
|
|
369
382
|
}
|
|
370
|
-
|
|
371
|
-
const command = new plugins.s3.PutObjectCommand({
|
|
372
|
-
Bucket: this.name,
|
|
373
|
-
Key: reducedPath,
|
|
374
|
-
Body: optionsArg.contents,
|
|
375
|
-
});
|
|
376
|
-
await this.smartbucketRef.storageClient.send(command);
|
|
377
|
-
|
|
378
|
-
console.log(`Object '${reducedPath}' has been successfully stored in bucket '${this.name}'.`);
|
|
379
|
-
const parsedPath = plugins.path.parse(reducedPath);
|
|
380
|
-
return new File({
|
|
381
|
-
directoryRefArg: await this.getDirectoryFromPath({
|
|
382
|
-
path: parsedPath.dir,
|
|
383
|
-
}),
|
|
384
|
-
fileName: parsedPath.base,
|
|
385
|
-
});
|
|
386
|
-
} catch (error) {
|
|
387
|
-
console.error(
|
|
388
|
-
`Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`,
|
|
389
|
-
error
|
|
390
|
-
);
|
|
391
|
-
throw error;
|
|
392
383
|
}
|
|
384
|
+
|
|
385
|
+
const command = new plugins.s3.PutObjectCommand({
|
|
386
|
+
Bucket: this.name,
|
|
387
|
+
Key: reducedPath,
|
|
388
|
+
Body: optionsArg.contents,
|
|
389
|
+
});
|
|
390
|
+
await this.smartbucketRef.storageClient.send(command);
|
|
391
|
+
|
|
392
|
+
const parsedPath = plugins.path.parse(reducedPath);
|
|
393
|
+
return new File({
|
|
394
|
+
directoryRefArg: await this.getDirectoryFromPath({
|
|
395
|
+
path: parsedPath.dir,
|
|
396
|
+
}),
|
|
397
|
+
fileName: parsedPath.base,
|
|
398
|
+
});
|
|
393
399
|
}
|
|
394
400
|
|
|
395
401
|
|
|
@@ -714,8 +720,6 @@ export class Bucket {
|
|
|
714
720
|
`Object already exists at path '${optionsArg.path}' in bucket '${this.name}'. ` +
|
|
715
721
|
`Set overwrite:true to replace it.`
|
|
716
722
|
);
|
|
717
|
-
} else {
|
|
718
|
-
console.log(`Creating new object at path '${optionsArg.path}' in bucket '${this.name}'.`);
|
|
719
723
|
}
|
|
720
724
|
}
|
|
721
725
|
|
|
@@ -734,18 +738,10 @@ export class Bucket {
|
|
|
734
738
|
await operation.runProvider(
|
|
735
739
|
() => operation.client.send(command, { abortSignal: operation.signal }),
|
|
736
740
|
);
|
|
737
|
-
|
|
738
|
-
console.log(
|
|
739
|
-
`Object '${optionsArg.path}' has been successfully stored in bucket '${this.name}'.`
|
|
740
|
-
);
|
|
741
741
|
} catch (error) {
|
|
742
742
|
if (sourceOwnershipStarted || putDispatched || operation.signal.aborted) {
|
|
743
743
|
await disposeSource();
|
|
744
744
|
}
|
|
745
|
-
console.error(
|
|
746
|
-
`Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`,
|
|
747
|
-
error
|
|
748
|
-
);
|
|
749
745
|
throw error;
|
|
750
746
|
} finally {
|
|
751
747
|
operation.finish();
|
|
@@ -809,9 +805,6 @@ export class Bucket {
|
|
|
809
805
|
await operation.runProvider(
|
|
810
806
|
() => operation.client.send(command, { abortSignal: operation.signal }),
|
|
811
807
|
);
|
|
812
|
-
} catch (err) {
|
|
813
|
-
console.error('Error updating metadata:', err);
|
|
814
|
-
throw err; // rethrow to allow caller to handle
|
|
815
808
|
} finally {
|
|
816
809
|
operation.finish();
|
|
817
810
|
}
|
|
@@ -826,40 +819,20 @@ export class Bucket {
|
|
|
826
819
|
targetBucket?: Bucket;
|
|
827
820
|
overwrite?: boolean;
|
|
828
821
|
}): Promise<void> {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
});
|
|
834
|
-
|
|
835
|
-
if (exists && !optionsArg.overwrite) {
|
|
836
|
-
console.error(
|
|
837
|
-
`Object already exists at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
|
|
838
|
-
);
|
|
839
|
-
return;
|
|
840
|
-
} else if (exists && optionsArg.overwrite) {
|
|
841
|
-
console.log(
|
|
842
|
-
`Overwriting existing object at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
|
|
843
|
-
);
|
|
844
|
-
} else {
|
|
845
|
-
console.log(
|
|
846
|
-
`Moving object to path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
|
|
847
|
-
);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
await this.fastCopy(optionsArg);
|
|
851
|
-
await this.fastRemove({ path: optionsArg.sourcePath });
|
|
822
|
+
const destinationBucket = optionsArg.targetBucket || this;
|
|
823
|
+
const exists = await destinationBucket.fastExists({
|
|
824
|
+
path: optionsArg.destinationPath,
|
|
825
|
+
});
|
|
852
826
|
|
|
853
|
-
|
|
854
|
-
`Object '${optionsArg.sourcePath}' has been successfully moved to '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
|
|
855
|
-
);
|
|
856
|
-
} catch (error) {
|
|
827
|
+
if (exists && !optionsArg.overwrite) {
|
|
857
828
|
console.error(
|
|
858
|
-
`
|
|
859
|
-
error
|
|
829
|
+
`Object already exists at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
|
|
860
830
|
);
|
|
861
|
-
|
|
831
|
+
return;
|
|
862
832
|
}
|
|
833
|
+
|
|
834
|
+
await this.fastCopy(optionsArg);
|
|
835
|
+
await this.fastRemove({ path: optionsArg.sourcePath });
|
|
863
836
|
}
|
|
864
837
|
|
|
865
838
|
/**
|
|
@@ -895,7 +868,6 @@ export class Bucket {
|
|
|
895
868
|
await operation.runProvider(
|
|
896
869
|
() => operation.client.send(command, { abortSignal: operation.signal }),
|
|
897
870
|
);
|
|
898
|
-
console.log(`Object '${optionsArg.path}' exists in bucket '${this.name}'.`);
|
|
899
871
|
return true;
|
|
900
872
|
} catch (error: any) {
|
|
901
873
|
try {
|
|
@@ -904,10 +876,8 @@ export class Bucket {
|
|
|
904
876
|
throw abortError;
|
|
905
877
|
}
|
|
906
878
|
if (error?.name === 'NotFound') {
|
|
907
|
-
console.log(`Object '${optionsArg.path}' does not exist in bucket '${this.name}'.`);
|
|
908
879
|
return false;
|
|
909
880
|
} else {
|
|
910
|
-
console.error('Error checking object existence:', error);
|
|
911
881
|
throw error; // Rethrow if it's not a NotFound error to handle unexpected issues
|
|
912
882
|
}
|
|
913
883
|
} finally {
|
|
@@ -967,27 +937,19 @@ export class Bucket {
|
|
|
967
937
|
}
|
|
968
938
|
|
|
969
939
|
public async getMagicBytes(optionsArg: { path: string; length: number }): Promise<Buffer> {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
const stream = response.Body as any; // SdkStreamMixin includes readable stream
|
|
940
|
+
const command = new plugins.s3.GetObjectCommand({
|
|
941
|
+
Bucket: this.name,
|
|
942
|
+
Key: optionsArg.path,
|
|
943
|
+
Range: `bytes=0-${optionsArg.length - 1}`,
|
|
944
|
+
});
|
|
945
|
+
const response = await this.smartbucketRef.storageClient.send(command);
|
|
946
|
+
const chunks: Buffer[] = [];
|
|
947
|
+
const stream = response.Body as any; // SdkStreamMixin includes readable stream
|
|
979
948
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
return Buffer.concat(chunks);
|
|
984
|
-
} catch (error) {
|
|
985
|
-
console.error(
|
|
986
|
-
`Error retrieving magic bytes from object at path '${optionsArg.path}' in bucket '${this.name}':`,
|
|
987
|
-
error
|
|
988
|
-
);
|
|
989
|
-
throw error;
|
|
949
|
+
for await (const chunk of stream) {
|
|
950
|
+
chunks.push(chunk);
|
|
990
951
|
}
|
|
952
|
+
return Buffer.concat(chunks);
|
|
991
953
|
}
|
|
992
954
|
|
|
993
955
|
// ==========================================
|
|
@@ -1240,50 +1202,41 @@ export class Bucket {
|
|
|
1240
1202
|
}
|
|
1241
1203
|
|
|
1242
1204
|
public async cleanAllContents(): Promise<void> {
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1205
|
+
// Define the command type explicitly
|
|
1206
|
+
const listCommandInput: plugins.s3.ListObjectsV2CommandInput = {
|
|
1207
|
+
Bucket: this.name,
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
let isTruncated = true;
|
|
1211
|
+
let continuationToken: string | undefined = undefined;
|
|
1212
|
+
|
|
1213
|
+
while (isTruncated) {
|
|
1214
|
+
// Add the continuation token to the input if present
|
|
1215
|
+
const listCommand = new plugins.s3.ListObjectsV2Command({
|
|
1216
|
+
...listCommandInput,
|
|
1217
|
+
ContinuationToken: continuationToken,
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
// Explicitly type the response
|
|
1221
|
+
const response: plugins.s3.ListObjectsV2Output =
|
|
1222
|
+
await this.smartbucketRef.storageClient.send(listCommand);
|
|
1223
|
+
|
|
1224
|
+
if (response.Contents && response.Contents.length > 0) {
|
|
1225
|
+
// Delete objects in batches, mapping each item to { Key: string }
|
|
1226
|
+
const deleteCommand = new plugins.s3.DeleteObjectsCommand({
|
|
1227
|
+
Bucket: this.name,
|
|
1228
|
+
Delete: {
|
|
1229
|
+
Objects: response.Contents.map((item) => ({ Key: item.Key! })),
|
|
1230
|
+
Quiet: true,
|
|
1231
|
+
},
|
|
1257
1232
|
});
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
const response: plugins.s3.ListObjectsV2Output =
|
|
1261
|
-
await this.smartbucketRef.storageClient.send(listCommand);
|
|
1262
|
-
|
|
1263
|
-
console.log(`Cleaning contents of bucket '${this.name}': Now deleting ${response.Contents?.length} items...`);
|
|
1264
|
-
|
|
1265
|
-
if (response.Contents && response.Contents.length > 0) {
|
|
1266
|
-
// Delete objects in batches, mapping each item to { Key: string }
|
|
1267
|
-
const deleteCommand = new plugins.s3.DeleteObjectsCommand({
|
|
1268
|
-
Bucket: this.name,
|
|
1269
|
-
Delete: {
|
|
1270
|
-
Objects: response.Contents.map((item) => ({ Key: item.Key! })),
|
|
1271
|
-
Quiet: true,
|
|
1272
|
-
},
|
|
1273
|
-
});
|
|
1274
|
-
|
|
1275
|
-
await this.smartbucketRef.storageClient.send(deleteCommand);
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
// Update continuation token and truncation status
|
|
1279
|
-
isTruncated = response.IsTruncated || false;
|
|
1280
|
-
continuationToken = response.NextContinuationToken;
|
|
1233
|
+
|
|
1234
|
+
await this.smartbucketRef.storageClient.send(deleteCommand);
|
|
1281
1235
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
throw error;
|
|
1236
|
+
|
|
1237
|
+
// Update continuation token and truncation status
|
|
1238
|
+
isTruncated = response.IsTruncated || false;
|
|
1239
|
+
continuationToken = response.NextContinuationToken;
|
|
1287
1240
|
}
|
|
1288
1241
|
}
|
|
1289
1242
|
}
|
|
@@ -179,30 +179,25 @@ export class Directory {
|
|
|
179
179
|
* lists all folders
|
|
180
180
|
*/
|
|
181
181
|
public async listDirectories(): Promise<Directory[]> {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
directoryArray.push(new Directory(this.bucketRef, this, dirName));
|
|
195
|
-
}
|
|
182
|
+
const { commonPrefixes } = await this.listObjectsV2AllPages(this.getBasePath(), '/');
|
|
183
|
+
const directoryArray: Directory[] = [];
|
|
184
|
+
|
|
185
|
+
if (commonPrefixes) {
|
|
186
|
+
commonPrefixes.forEach((item) => {
|
|
187
|
+
if (item.Prefix) {
|
|
188
|
+
const subtractedPath = item.Prefix.replace(this.getBasePath(), '');
|
|
189
|
+
if (subtractedPath.endsWith('/')) {
|
|
190
|
+
const dirName = subtractedPath.slice(0, -1);
|
|
191
|
+
// Ensure the directory name is not empty (which would indicate the base directory itself)
|
|
192
|
+
if (dirName) {
|
|
193
|
+
directoryArray.push(new Directory(this.bucketRef, this, dirName));
|
|
196
194
|
}
|
|
197
195
|
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return directoryArray;
|
|
202
|
-
} catch (error) {
|
|
203
|
-
console.error('Error listing directories:', error);
|
|
204
|
-
throw error;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
205
198
|
}
|
|
199
|
+
|
|
200
|
+
return directoryArray;
|
|
206
201
|
}
|
|
207
202
|
|
|
208
203
|
/**
|
|
@@ -409,12 +404,8 @@ export class Directory {
|
|
|
409
404
|
}) {
|
|
410
405
|
const deleteDirectory = async (directoryArg: Directory) => {
|
|
411
406
|
const childDirectories = await directoryArg.listDirectories();
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
} else {
|
|
415
|
-
for (const childDir of childDirectories) {
|
|
416
|
-
await deleteDirectory(childDir);
|
|
417
|
-
}
|
|
407
|
+
for (const childDir of childDirectories) {
|
|
408
|
+
await deleteDirectory(childDir);
|
|
418
409
|
}
|
|
419
410
|
const files = await directoryArg.listFiles();
|
|
420
411
|
for (const file of files) {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as plugins from './plugins.js';
|
|
4
4
|
import { Bucket } from './classes.bucket.js';
|
|
5
5
|
import { normalizeStorageDescriptor } from './helpers.js';
|
|
6
|
+
import { headBucketExists } from './internal.bucketrequests.js';
|
|
6
7
|
import {
|
|
7
8
|
beginOperationLifecycleClose,
|
|
8
9
|
commitOperationLifecycleClient,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
rollbackOperationLifecycleClientMutation,
|
|
13
14
|
} from './internal.exactupload.capability.js';
|
|
14
15
|
import type {
|
|
16
|
+
IBucketRequestOptions,
|
|
15
17
|
ISmartBucketReadinessOptions,
|
|
16
18
|
TSmartBucketReadinessFailureReason,
|
|
17
19
|
TSmartBucketReadinessResult,
|
|
@@ -107,26 +109,34 @@ export class SmartBucket {
|
|
|
107
109
|
registerOperationLifecycle(this, this.currentStorageClient);
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
public async createBucket(bucketNameArg: string) {
|
|
111
|
-
const bucket = await Bucket.createBucketByName(this, bucketNameArg);
|
|
112
|
+
public async createBucket(bucketNameArg: string, optionsArg: IBucketRequestOptions = {}) {
|
|
113
|
+
const bucket = await Bucket.createBucketByName(this, bucketNameArg, optionsArg);
|
|
112
114
|
return bucket;
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
public async removeBucket(bucketName: string) {
|
|
116
|
-
await Bucket.removeBucketByName(this, bucketName);
|
|
117
|
+
public async removeBucket(bucketName: string, optionsArg: IBucketRequestOptions = {}) {
|
|
118
|
+
await Bucket.removeBucketByName(this, bucketName, optionsArg);
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Returns a reference to an existing bucket, checked with one HeadBucket
|
|
123
|
+
* request. Rejects when the bucket does not exist or the credentials may
|
|
124
|
+
* not access it.
|
|
125
|
+
*/
|
|
126
|
+
public async getBucketByName(bucketNameArg: string, optionsArg: IBucketRequestOptions = {}) {
|
|
127
|
+
return Bucket.getBucketByName(this, bucketNameArg, optionsArg);
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
/**
|
|
124
|
-
*
|
|
131
|
+
* Checks with one HeadBucket request whether a bucket exists. Resolves
|
|
132
|
+
* `false` for a missing bucket and rejects when the credentials may not
|
|
133
|
+
* access it.
|
|
125
134
|
*/
|
|
126
|
-
public async bucketExists(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
public async bucketExists(
|
|
136
|
+
bucketNameArg: string,
|
|
137
|
+
optionsArg: IBucketRequestOptions = {},
|
|
138
|
+
): Promise<boolean> {
|
|
139
|
+
return headBucketExists(this, bucketNameArg, optionsArg.signal);
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
/**
|
|
@@ -52,6 +52,15 @@ export interface IDirectoryFastPutStreamOptions {
|
|
|
52
52
|
signal?: AbortSignal;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Options for a request about a bucket itself: `getBucketByName()`,
|
|
57
|
+
* `bucketExists()`, `createBucket()` and `removeBucket()`.
|
|
58
|
+
*/
|
|
59
|
+
export interface IBucketRequestOptions {
|
|
60
|
+
/** Optional caller-owned cancellation signal */
|
|
61
|
+
signal?: AbortSignal;
|
|
62
|
+
}
|
|
63
|
+
|
|
55
64
|
// ================================
|
|
56
65
|
// SmartBucket Readiness Interfaces
|
|
57
66
|
// ================================
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import type { SmartBucket } from './classes.smartbucket.js';
|
|
3
|
+
import { beginAccountOperation } from './internal.exactupload.capability.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sends one account-scoped request under the SmartBucket lifecycle. The
|
|
7
|
+
* caller's signal and `SmartBucket.close()` abort the in-flight SDK request.
|
|
8
|
+
*/
|
|
9
|
+
export const runAccountRequest = async <T>(
|
|
10
|
+
smartbucketRefArg: SmartBucket,
|
|
11
|
+
signalArg: AbortSignal | undefined,
|
|
12
|
+
sendArg: (clientArg: plugins.s3.S3Client, abortSignalArg: AbortSignal) => Promise<T>,
|
|
13
|
+
): Promise<T> => {
|
|
14
|
+
const operation = beginAccountOperation(smartbucketRefArg, signalArg);
|
|
15
|
+
try {
|
|
16
|
+
return await operation.runProvider(() => sendArg(operation.client, operation.signal));
|
|
17
|
+
} finally {
|
|
18
|
+
operation.finish();
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getProviderStatus = (errorArg: unknown): { name?: string; httpStatusCode?: number } => {
|
|
23
|
+
if (typeof errorArg !== 'object' || errorArg === null) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
const providerError = errorArg as {
|
|
27
|
+
name?: unknown;
|
|
28
|
+
$metadata?: { httpStatusCode?: unknown };
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
name: typeof providerError.name === 'string' ? providerError.name : undefined,
|
|
32
|
+
httpStatusCode: typeof providerError.$metadata?.httpStatusCode === 'number'
|
|
33
|
+
? providerError.$metadata.httpStatusCode
|
|
34
|
+
: undefined,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Answers whether the bucket exists with one HeadBucket request, which a
|
|
40
|
+
* bucket-scoped credential may send for its own bucket. A missing bucket
|
|
41
|
+
* resolves `false`; a bucket the credentials may not access rejects, because
|
|
42
|
+
* S3 does not say whether it exists.
|
|
43
|
+
*/
|
|
44
|
+
export const headBucketExists = async (
|
|
45
|
+
smartbucketRefArg: SmartBucket,
|
|
46
|
+
bucketNameArg: string,
|
|
47
|
+
signalArg: AbortSignal | undefined,
|
|
48
|
+
): Promise<boolean> => {
|
|
49
|
+
try {
|
|
50
|
+
await runAccountRequest(smartbucketRefArg, signalArg, (clientArg, abortSignalArg) =>
|
|
51
|
+
clientArg.send(new plugins.s3.HeadBucketCommand({ Bucket: bucketNameArg }), {
|
|
52
|
+
abortSignal: abortSignalArg,
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
return true;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
const { name, httpStatusCode } = getProviderStatus(error);
|
|
58
|
+
if (httpStatusCode === 404 || name === 'NotFound' || name === 'NoSuchBucket') {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (httpStatusCode === 403 || name === 'Forbidden' || name === 'AccessDenied') {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Access to bucket '${bucketNameArg}' was denied (HTTP 403): the credentials may not ` +
|
|
64
|
+
`read this bucket, or it belongs to another account.`,
|
|
65
|
+
{ cause: error },
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
};
|