UNPKG

@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
// 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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