@push.rocks/smartbucket
Version:
A TypeScript library providing a cloud-agnostic interface for managing object storage with functionalities like bucket management, file and directory operations, and advanced features such as metadata handling and file locking.
564 lines • 43.6 kB
JavaScript
// classes.bucket.ts
import * as plugins from './plugins.js';
import * as helpers from './helpers.js';
import * as interfaces from './interfaces.js';
import { SmartBucket } from './classes.smartbucket.js';
import { Directory } from './classes.directory.js';
import { File } from './classes.file.js';
import { Trash } from './classes.trash.js';
import { ListCursor } from './classes.listcursor.js';
import { BucketWatcher } from './classes.watcher.js';
/**
* The bucket class exposes the basic functionality of a bucket.
* The functions of the bucket alone are enough to
* operate on blobs of data in an S3-compatible object store.
*/
export class Bucket {
static async getBucketByName(smartbucketRef, bucketNameArg) {
const command = new plugins.s3.ListBucketsCommand({});
const buckets = await smartbucketRef.storageClient.send(command);
const foundBucket = buckets.Buckets.find((bucket) => bucket.Name === bucketNameArg);
if (foundBucket) {
console.log(`bucket with name ${bucketNameArg} exists.`);
console.log(`Taking this as base for new Bucket instance`);
return new this(smartbucketRef, bucketNameArg);
}
else {
throw new Error(`Bucket '${bucketNameArg}' not found.`);
}
}
static async createBucketByName(smartbucketRef, bucketName) {
const command = new plugins.s3.CreateBucketCommand({ Bucket: bucketName });
await smartbucketRef.storageClient.send(command);
return new Bucket(smartbucketRef, bucketName);
}
static async removeBucketByName(smartbucketRef, bucketName) {
const command = new plugins.s3.DeleteBucketCommand({ Bucket: bucketName });
await smartbucketRef.storageClient.send(command);
}
constructor(smartbucketRef, bucketName) {
this.smartbucketRef = smartbucketRef;
this.name = bucketName;
}
/**
* Returns the underlying AWS SDK v3 S3Client for this bucket.
*
* Use this when you need to perform operations smartbucket doesn't
* wrap directly (e.g. lifecycle policies, bucket tagging, multipart
* upload control, object-lock, inventory config, etc.).
*
* The returned client is shared with the parent SmartBucket — do not
* call `.destroy()` on it.
*/
getStorageClient() {
return this.smartbucketRef.storageClient;
}
/**
* gets the base directory of the bucket
*/
async getBaseDirectory() {
return new Directory(this, null, '');
}
/**
* gets the trash directory
*/
async getTrash() {
const trash = new Trash(this);
return trash;
}
async getDirectoryFromPath(pathDescriptorArg) {
if (!pathDescriptorArg.path && !pathDescriptorArg.directory) {
return this.getBaseDirectory();
}
const checkPath = await helpers.reducePathDescriptorToPath(pathDescriptorArg);
const baseDirectory = await this.getBaseDirectory();
return await baseDirectory.getSubDirectoryByName(checkPath, {
getEmptyDirectory: true,
});
}
// ===============
// Fast Operations
// ===============
/**
* store file
*/
async fastPut(optionsArg) {
try {
const reducedPath = await helpers.reducePathDescriptorToPath(optionsArg);
const exists = await this.fastExists({ path: reducedPath });
if (exists && !optionsArg.overwrite) {
throw new Error(`Object already exists at path '${reducedPath}' in bucket '${this.name}'. ` +
`Set overwrite:true to replace it.`);
}
else if (exists && optionsArg.overwrite) {
console.log(`Overwriting existing object at path '${reducedPath}' in bucket '${this.name}'.`);
}
else {
console.log(`Creating new object at path '${reducedPath}' in bucket '${this.name}'.`);
}
const command = new plugins.s3.PutObjectCommand({
Bucket: this.name,
Key: reducedPath,
Body: optionsArg.contents,
});
await this.smartbucketRef.storageClient.send(command);
console.log(`Object '${reducedPath}' has been successfully stored in bucket '${this.name}'.`);
const parsedPath = plugins.path.parse(reducedPath);
return new File({
directoryRefArg: await this.getDirectoryFromPath({
path: parsedPath.dir,
}),
fileName: parsedPath.base,
});
}
catch (error) {
console.error(`Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`, error);
throw error;
}
}
/**
* get file
*/
async fastGet(optionsArg) {
const done = plugins.smartpromise.defer();
let completeFile;
const replaySubject = await this.fastGetReplaySubject(optionsArg);
const subscription = replaySubject.subscribe({
next: (chunk) => {
if (completeFile) {
completeFile = Buffer.concat([completeFile, chunk]);
}
else {
completeFile = chunk;
}
},
complete: () => {
done.resolve();
subscription.unsubscribe();
},
error: (err) => {
console.log(err);
},
});
await done.promise;
return completeFile;
}
/**
* good when time to first byte is important
* and multiple subscribers are expected
* @param optionsArg
* @returns
*/
async fastGetReplaySubject(optionsArg) {
const command = new plugins.s3.GetObjectCommand({
Bucket: this.name,
Key: optionsArg.path,
});
const response = await this.smartbucketRef.storageClient.send(command);
const replaySubject = new plugins.smartrx.rxjs.ReplaySubject();
// Convert the stream to a format that supports piping
const stream = response.Body; // SdkStreamMixin includes readable stream
if (typeof stream.pipe === 'function') {
const duplexStream = new plugins.smartstream.SmartDuplex({
writeFunction: async (chunk) => {
replaySubject.next(chunk);
return;
},
finalFunction: async (cb) => {
replaySubject.complete();
return;
},
});
stream.pipe(duplexStream);
}
return replaySubject;
}
async fastGetStream(optionsArg, typeArg = 'nodestream') {
const command = new plugins.s3.GetObjectCommand({
Bucket: this.name,
Key: optionsArg.path,
});
const response = await this.smartbucketRef.storageClient.send(command);
const stream = response.Body; // SdkStreamMixin includes readable stream
const duplexStream = new plugins.smartstream.SmartDuplex({
writeFunction: async (chunk) => {
return chunk;
},
finalFunction: async (cb) => {
return null;
},
});
if (typeof stream.pipe === 'function') {
stream.pipe(duplexStream);
}
if (typeArg === 'nodestream') {
return duplexStream;
}
if (typeArg === 'webstream') {
return (await duplexStream.getWebStreams()).readable;
}
throw new Error('unknown typeArg');
}
/**
* store file as stream
*/
async fastPutStream(optionsArg) {
try {
const exists = await this.fastExists({ path: optionsArg.path });
if (exists && !optionsArg.overwrite) {
throw new Error(`Object already exists at path '${optionsArg.path}' in bucket '${this.name}'. ` +
`Set overwrite:true to replace it.`);
}
else if (exists && optionsArg.overwrite) {
console.log(`Overwriting existing object at path '${optionsArg.path}' in bucket '${this.name}'.`);
}
else {
console.log(`Creating new object at path '${optionsArg.path}' in bucket '${this.name}'.`);
}
const command = new plugins.s3.PutObjectCommand({
Bucket: this.name,
Key: optionsArg.path,
Body: optionsArg.readableStream,
Metadata: optionsArg.nativeMetadata,
});
await this.smartbucketRef.storageClient.send(command);
console.log(`Object '${optionsArg.path}' has been successfully stored in bucket '${this.name}'.`);
}
catch (error) {
console.error(`Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`, error);
throw error;
}
}
async fastCopy(optionsArg) {
try {
const targetBucketName = optionsArg.targetBucket ? optionsArg.targetBucket.name : this.name;
// Retrieve current object information to use in copy conditions
const currentObjInfo = await this.smartbucketRef.storageClient.send(new plugins.s3.HeadObjectCommand({
Bucket: this.name,
Key: optionsArg.sourcePath,
}));
// Prepare new metadata
const newNativeMetadata = {
...(optionsArg.deleteExistingNativeMetadata ? {} : currentObjInfo.Metadata),
...optionsArg.nativeMetadata,
};
// Define the copy operation
const copySource = `${this.name}/${optionsArg.sourcePath}`;
const command = new plugins.s3.CopyObjectCommand({
Bucket: targetBucketName,
CopySource: copySource,
Key: optionsArg.destinationPath || optionsArg.sourcePath,
Metadata: newNativeMetadata,
MetadataDirective: optionsArg.deleteExistingNativeMetadata ? 'REPLACE' : 'COPY',
});
await this.smartbucketRef.storageClient.send(command);
}
catch (err) {
console.error('Error updating metadata:', err);
throw err; // rethrow to allow caller to handle
}
}
/**
* Move object from one path to another within the same bucket or to another bucket
*/
async fastMove(optionsArg) {
try {
const destinationBucket = optionsArg.targetBucket || this;
const exists = await destinationBucket.fastExists({
path: optionsArg.destinationPath,
});
if (exists && !optionsArg.overwrite) {
console.error(`Object already exists at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`);
return;
}
else if (exists && optionsArg.overwrite) {
console.log(`Overwriting existing object at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`);
}
else {
console.log(`Moving object to path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`);
}
await this.fastCopy(optionsArg);
await this.fastRemove({ path: optionsArg.sourcePath });
console.log(`Object '${optionsArg.sourcePath}' has been successfully moved to '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`);
}
catch (error) {
console.error(`Error moving object from '${optionsArg.sourcePath}' to '${optionsArg.destinationPath}':`, error);
throw error;
}
}
/**
* removeObject
*/
async fastRemove(optionsArg) {
const command = new plugins.s3.DeleteObjectCommand({
Bucket: this.name,
Key: optionsArg.path,
});
await this.smartbucketRef.storageClient.send(command);
}
/**
* check whether file exists
* @param optionsArg
* @returns
*/
async fastExists(optionsArg) {
try {
const command = new plugins.s3.HeadObjectCommand({
Bucket: this.name,
Key: optionsArg.path,
});
await this.smartbucketRef.storageClient.send(command);
console.log(`Object '${optionsArg.path}' exists in bucket '${this.name}'.`);
return true;
}
catch (error) {
if (error?.name === 'NotFound') {
console.log(`Object '${optionsArg.path}' does not exist in bucket '${this.name}'.`);
return false;
}
else {
console.error('Error checking object existence:', error);
throw error; // Rethrow if it's not a NotFound error to handle unexpected issues
}
}
}
/**
* deletes this bucket
*/
async delete() {
await this.smartbucketRef.storageClient.send(new plugins.s3.DeleteBucketCommand({ Bucket: this.name }));
}
async fastStat(pathDescriptor) {
const checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor);
const command = new plugins.s3.HeadObjectCommand({
Bucket: this.name,
Key: checkPath,
});
return this.smartbucketRef.storageClient.send(command);
}
async isDirectory(pathDescriptor) {
const checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor);
const command = new plugins.s3.ListObjectsV2Command({
Bucket: this.name,
Prefix: checkPath,
Delimiter: '/',
});
const { CommonPrefixes } = await this.smartbucketRef.storageClient.send(command);
return !!CommonPrefixes && CommonPrefixes.length > 0;
}
async isFile(pathDescriptor) {
const checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor);
const command = new plugins.s3.ListObjectsV2Command({
Bucket: this.name,
Prefix: checkPath,
Delimiter: '/',
});
const { Contents } = await this.smartbucketRef.storageClient.send(command);
return !!Contents && Contents.length > 0;
}
async getMagicBytes(optionsArg) {
try {
const command = new plugins.s3.GetObjectCommand({
Bucket: this.name,
Key: optionsArg.path,
Range: `bytes=0-${optionsArg.length - 1}`,
});
const response = await this.smartbucketRef.storageClient.send(command);
const chunks = [];
const stream = response.Body; // SdkStreamMixin includes readable stream
for await (const chunk of stream) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
catch (error) {
console.error(`Error retrieving magic bytes from object at path '${optionsArg.path}' in bucket '${this.name}':`, error);
throw error;
}
}
// ==========================================
// Memory-Efficient Listing Methods (Phase 1)
// ==========================================
/**
* List all objects with a given prefix using async generator (memory-efficient streaming)
* @param prefix - Optional prefix to filter objects (default: '' for all objects)
* @yields Object keys one at a time
* @example
* ```ts
* for await (const key of bucket.listAllObjects('npm/')) {
* console.log(key);
* if (shouldStop) break; // Early exit supported
* }
* ```
*/
async *listAllObjects(prefix = '') {
let continuationToken;
do {
const command = new plugins.s3.ListObjectsV2Command({
Bucket: this.name,
Prefix: prefix,
ContinuationToken: continuationToken,
});
const response = await this.smartbucketRef.storageClient.send(command);
for (const obj of response.Contents || []) {
if (obj.Key)
yield obj.Key;
}
continuationToken = response.NextContinuationToken;
} while (continuationToken);
}
/**
* List all objects as an RxJS Observable (for complex reactive pipelines)
* @param prefix - Optional prefix to filter objects (default: '' for all objects)
* @returns Observable that emits object keys
* @example
* ```ts
* bucket.listAllObjectsObservable('npm/')
* .pipe(
* filter(key => key.endsWith('.json')),
* take(100)
* )
* .subscribe(key => console.log(key));
* ```
*/
listAllObjectsObservable(prefix = '') {
return new plugins.smartrx.rxjs.Observable((subscriber) => {
const fetchPage = async (token) => {
try {
const command = new plugins.s3.ListObjectsV2Command({
Bucket: this.name,
Prefix: prefix,
ContinuationToken: token,
});
const response = await this.smartbucketRef.storageClient.send(command);
for (const obj of response.Contents || []) {
if (obj.Key)
subscriber.next(obj.Key);
}
if (response.NextContinuationToken) {
await fetchPage(response.NextContinuationToken);
}
else {
subscriber.complete();
}
}
catch (error) {
subscriber.error(error);
}
};
fetchPage();
});
}
/**
* Create a cursor for manual pagination control
* @param prefix - Optional prefix to filter objects (default: '' for all objects)
* @param options - Cursor options (pageSize, etc.)
* @returns ListCursor instance
* @example
* ```ts
* const cursor = bucket.createCursor('npm/', { pageSize: 500 });
* while (cursor.hasMore()) {
* const { keys, done } = await cursor.next();
* console.log(`Processing ${keys.length} keys...`);
* }
* ```
*/
createCursor(prefix = '', options) {
return new ListCursor(this, prefix, options);
}
/**
* Create a watcher for monitoring bucket changes (add/modify/delete)
* @param options - Watcher options (prefix, pollIntervalMs, etc.)
* @returns BucketWatcher instance
* @example
* ```ts
* const watcher = bucket.createWatcher({ prefix: 'uploads/', pollIntervalMs: 3000 });
* watcher.changeSubject.subscribe((change) => console.log('Change:', change));
* await watcher.start();
* // ... later
* await watcher.stop();
* ```
*/
createWatcher(options) {
return new BucketWatcher(this, options);
}
// ==========================================
// High-Level Listing Helpers (Phase 2)
// ==========================================
/**
* Find objects matching a glob pattern (memory-efficient)
* @param pattern - Glob pattern (e.g., "**\/*.json", "npm/packages/*\/index.json")
* @yields Matching object keys
* @example
* ```ts
* for await (const key of bucket.findByGlob('npm/packages/*\/index.json')) {
* console.log('Found package index:', key);
* }
* ```
*/
async *findByGlob(pattern) {
const matcher = new plugins.Minimatch(pattern);
for await (const key of this.listAllObjects('')) {
if (matcher.match(key))
yield key;
}
}
/**
* List all objects and collect into an array (convenience method)
* WARNING: Loads entire result set into memory. Use listAllObjects() generator for large buckets.
* @param prefix - Optional prefix to filter objects (default: '' for all objects)
* @returns Array of all object keys
* @example
* ```ts
* const allKeys = await bucket.listAllObjectsArray('npm/');
* console.log(`Found ${allKeys.length} objects`);
* ```
*/
async listAllObjectsArray(prefix = '') {
const keys = [];
for await (const key of this.listAllObjects(prefix)) {
keys.push(key);
}
return keys;
}
async cleanAllContents() {
try {
// Define the command type explicitly
const listCommandInput = {
Bucket: this.name,
};
let isTruncated = true;
let continuationToken = undefined;
while (isTruncated) {
// Add the continuation token to the input if present
const listCommand = new plugins.s3.ListObjectsV2Command({
...listCommandInput,
ContinuationToken: continuationToken,
});
// Explicitly type the response
const response = await this.smartbucketRef.storageClient.send(listCommand);
console.log(`Cleaning contents of bucket '${this.name}': Now deleting ${response.Contents?.length} items...`);
if (response.Contents && response.Contents.length > 0) {
// Delete objects in batches, mapping each item to { Key: string }
const deleteCommand = new plugins.s3.DeleteObjectsCommand({
Bucket: this.name,
Delete: {
Objects: response.Contents.map((item) => ({ Key: item.Key })),
Quiet: true,
},
});
await this.smartbucketRef.storageClient.send(deleteCommand);
}
// Update continuation token and truncation status
isTruncated = response.IsTruncated || false;
continuationToken = response.NextContinuationToken;
}
console.log(`All contents in bucket '${this.name}' have been deleted.`);
}
catch (error) {
console.error(`Error cleaning contents of bucket '${this.name}':`, error);
throw error;
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5idWNrZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLmJ1Y2tldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxvQkFBb0I7QUFFcEIsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxLQUFLLFVBQVUsTUFBTSxpQkFBaUIsQ0FBQztBQUM5QyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDdkQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ25ELE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUN6QyxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDM0MsT0FBTyxFQUFFLFVBQVUsRUFBMkIsTUFBTSx5QkFBeUIsQ0FBQztBQUM5RSxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFFckQ7Ozs7R0FJRztBQUNILE1BQU0sT0FBTyxNQUFNO0lBQ1YsTUFBTSxDQUFDLEtBQUssQ0FBQyxlQUFlLENBQUMsY0FBMkIsRUFBRSxhQUFxQjtRQUNwRixNQUFNLE9BQU8sR0FBRyxJQUFJLE9BQU8sQ0FBQyxFQUFFLENBQUMsa0JBQWtCLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDdEQsTUFBTSxPQUFPLEdBQUcsTUFBTSxjQUFjLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNqRSxNQUFNLFdBQVcsR0FBRyxPQUFPLENBQUMsT0FBUSxDQUFDLElBQUksQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLElBQUksS0FBSyxhQUFhLENBQUMsQ0FBQztRQUVyRixJQUFJLFdBQVcsRUFBRSxDQUFDO1lBQ2hCLE9BQU8sQ0FBQyxHQUFHLENBQUMsb0JBQW9CLGFB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