2026-03-14 22:42:29 +00:00
# @push.rocks/smartbucket
2024-05-21 18:46:59 +02:00
2026-03-14 22:42:29 +00:00
A powerful, cloud-agnostic TypeScript library for object storage that makes S3-compatible storage feel like a modern filesystem. Built for developers who demand simplicity, type-safety, and advanced features like real-time bucket watching, metadata management, file locking, intelligent trash handling, and memory-efficient streaming.
2026-01-25 18:09:38 +00:00
## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/ ](https://community.foss.global/ ). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/ ](https://code.foss.global/ ) account to submit Pull Requests directly.
2019-07-07 10:48:24 +02:00
2026-03-14 22:42:29 +00:00
## Why SmartBucket?
2024-04-14 17:22:27 +02:00
2026-03-14 22:42:29 +00:00
- **Cloud Agnostic** - Write once, run on AWS S3, MinIO, DigitalOcean Spaces, Backblaze B2, Wasabi, Cloudflare R2, or any S3-compatible storage
- **Modern TypeScript** - First-class TypeScript support with complete type definitions and async/await patterns
- **Real-Time Watching** - Monitor bucket changes with polling-based watcher supporting RxJS and EventEmitter patterns
- **Memory Efficient** - Handle millions of files with async generators, RxJS observables, and cursor pagination
- **Smart Trash System** - Recover accidentally deleted files with built-in trash and restore functionality
- **File Locking** - Prevent concurrent modifications with built-in locking mechanisms
- **Rich Metadata** - Attach custom metadata to any file for powerful organization and search
- **Streaming Support** - Efficient handling of large files with Node.js and Web streams
- **Directory-like API** - Intuitive filesystem-like operations on object storage
- **Fail-Fast** - Strict-by-default API catches errors immediately with precise stack traces
2024-04-14 17:22:27 +02:00
2026-03-14 22:42:29 +00:00
## Quick Start
2025-11-20 15:14:11 +00:00
```typescript
import { SmartBucket } from '@push .rocks/smartbucket';
// Connect to your storage
const storage = new SmartBucket({
accessKey: 'your-access-key',
accessSecret: 'your-secret-key',
endpoint: 's3.amazonaws.com',
port: 443,
useSsl: true
});
// Get or create a bucket
const bucket = await storage.getBucketByName('my-app-data');
// Upload a file
await bucket.fastPut({
path: 'users/profile.json',
contents: JSON.stringify({ name: 'Alice', role: 'admin' })
});
// Download it back
const data = await bucket.fastGet({ path: 'users/profile.json' });
2026-03-14 22:42:29 +00:00
console.log(JSON.parse(data.toString()));
2025-11-20 15:14:11 +00:00
// List files efficiently (even with millions of objects!)
for await (const key of bucket.listAllObjects('users/')) {
2026-03-14 22:42:29 +00:00
console.log('Found:', key);
2025-11-20 15:14:11 +00:00
}
2026-01-25 18:09:38 +00:00
// Watch for changes in real-time
const watcher = bucket.createWatcher({ prefix: 'uploads/', pollIntervalMs: 3000 });
watcher.changeSubject.subscribe((change) => {
2026-03-14 22:42:29 +00:00
console.log('Change detected:', change.type, change.key);
2026-01-25 18:09:38 +00:00
});
await watcher.start();
2024-04-14 17:22:27 +02:00
```
2026-03-14 22:42:29 +00:00
## Install
2019-07-07 10:48:24 +02:00
2025-08-15 18:31:42 +00:00
```bash
2025-11-20 15:14:11 +00:00
# Using pnpm (recommended)
2025-08-15 18:31:42 +00:00
pnpm add @push .rocks/smartbucket
2025-11-20 15:14:11 +00:00
# Using npm
npm install @push .rocks/smartbucket --save
2025-08-15 18:31:42 +00:00
```
2026-03-14 22:42:29 +00:00
## Usage
2019-07-07 10:48:24 +02:00
2024-05-17 19:24:52 +02:00
### Table of Contents
2024-11-24 20:02:40 +01:00
2026-03-14 22:42:29 +00:00
1. [Getting Started ](#getting-started )
2. [Working with Buckets ](#working-with-buckets )
3. [File Operations ](#file-operations )
4. [Memory-Efficient Listing ](#memory-efficient-listing )
5. [Bucket Watching ](#bucket-watching )
6. [Directory Management ](#directory-management )
7. [Streaming Operations ](#streaming-operations )
8. [File Locking ](#file-locking )
9. [Metadata Management ](#metadata-management )
10. [Trash & Recovery ](#trash--recovery )
11. [Advanced Features ](#advanced-features )
12. [Cloud Provider Support ](#cloud-provider-support )
### Getting Started
2025-08-15 18:31:42 +00:00
First, set up your storage connection:
2024-04-14 17:22:27 +02:00
```typescript
2025-08-15 18:31:42 +00:00
import { SmartBucket } from '@push .rocks/smartbucket';
// Initialize with your cloud storage credentials
const smartBucket = new SmartBucket({
accessKey: 'your-access-key',
accessSecret: 'your-secret-key',
endpoint: 's3.amazonaws.com', // Or your provider's endpoint
2024-11-24 20:02:40 +01:00
port: 443,
2025-08-15 18:31:42 +00:00
useSsl: true,
region: 'us-east-1' // Optional, defaults to 'us-east-1'
2024-04-14 17:22:27 +02:00
});
```
2026-03-14 22:42:29 +00:00
**For MinIO or self-hosted storage:**
2025-11-20 15:14:11 +00:00
```typescript
const smartBucket = new SmartBucket({
accessKey: 'minioadmin',
accessSecret: 'minioadmin',
endpoint: 'localhost',
port: 9000,
useSsl: false // MinIO often runs without SSL locally
});
```
2026-03-14 22:42:29 +00:00
### Working with Buckets
2024-11-24 20:02:40 +01:00
2025-08-15 18:31:42 +00:00
#### Creating Buckets
2024-04-14 17:22:27 +02:00
2025-08-15 18:31:42 +00:00
```typescript
// Create a new bucket
const myBucket = await smartBucket.createBucket('my-awesome-bucket');
```
2024-04-14 17:22:27 +02:00
2025-08-15 18:31:42 +00:00
#### Getting Existing Buckets
2024-04-14 17:22:27 +02:00
```typescript
2025-11-20 15:14:11 +00:00
// Get a bucket reference (throws if not found - strict by default!)
2025-08-15 18:31:42 +00:00
const existingBucket = await smartBucket.getBucketByName('existing-bucket');
2024-04-14 17:22:27 +02:00
2025-11-20 15:14:11 +00:00
// Check first, then get (non-throwing approach)
if (await smartBucket.bucketExists('maybe-exists')) {
const bucket = await smartBucket.getBucketByName('maybe-exists');
}
2024-04-14 17:22:27 +02:00
```
2025-08-15 18:31:42 +00:00
#### Removing Buckets
2024-04-14 17:22:27 +02:00
2025-08-15 18:31:42 +00:00
```typescript
// Delete a bucket (must be empty)
await smartBucket.removeBucket('old-bucket');
```
2024-04-14 17:22:27 +02:00
2026-03-14 22:42:29 +00:00
### File Operations
2024-04-14 17:22:27 +02:00
2025-08-15 18:31:42 +00:00
#### Upload Files
2024-04-14 17:22:27 +02:00
```typescript
2025-08-15 18:31:42 +00:00
const bucket = await smartBucket.getBucketByName('my-bucket');
2024-04-14 17:22:27 +02:00
2025-11-20 13:20:19 +00:00
// Simple file upload (returns File object)
const file = await bucket.fastPut({
2025-08-15 18:31:42 +00:00
path: 'documents/report.pdf',
contents: Buffer.from('Your file content here')
});
// Upload with string content
await bucket.fastPut({
path: 'notes/todo.txt',
contents: 'Buy milk\nCall mom\nRule the world'
});
2025-11-20 13:20:19 +00:00
// Upload with overwrite control
const uploadedFile = await bucket.fastPut({
2025-08-15 18:31:42 +00:00
path: 'images/logo.png',
contents: imageBuffer,
2025-11-20 13:20:19 +00:00
overwrite: true // Set to true to replace existing files
2025-08-15 18:31:42 +00:00
});
2025-11-20 13:20:19 +00:00
// Error handling: fastPut throws if file exists and overwrite is false
try {
await bucket.fastPut({
path: 'existing-file.txt',
contents: 'new content'
});
} catch (error) {
2026-03-14 22:42:29 +00:00
console.error('Upload failed:', error.message);
2025-11-20 13:20:19 +00:00
}
2024-04-14 17:22:27 +02:00
```
2025-08-15 18:31:42 +00:00
#### Download Files
2024-11-24 20:02:40 +01:00
2025-08-15 18:31:42 +00:00
```typescript
// Get file as Buffer
const fileContent = await bucket.fastGet({
path: 'documents/report.pdf'
});
2024-11-24 20:12:20 +01:00
2025-08-15 18:31:42 +00:00
// Get file as string
const textContent = fileContent.toString('utf-8');
2025-11-20 15:14:11 +00:00
// Parse JSON files directly
const jsonData = JSON.parse(fileContent.toString());
2025-08-15 18:31:42 +00:00
```
2024-04-14 17:22:27 +02:00
2025-08-15 18:31:42 +00:00
#### Check File Existence
2024-04-14 17:22:27 +02:00
```typescript
2025-08-15 18:31:42 +00:00
const exists = await bucket.fastExists({
path: 'documents/report.pdf'
});
2024-04-14 17:22:27 +02:00
```
2025-08-15 18:31:42 +00:00
#### Delete Files
2024-04-14 17:22:27 +02:00
2025-08-15 18:31:42 +00:00
```typescript
// Permanent deletion
await bucket.fastRemove({
path: 'old-file.txt'
});
```
#### Copy & Move Files
2024-04-14 17:22:27 +02:00
```typescript
2025-08-15 18:31:42 +00:00
// Copy file within bucket
await bucket.fastCopy({
sourcePath: 'original/file.txt',
destinationPath: 'backup/file-copy.txt'
});
2024-04-14 17:22:27 +02:00
2025-08-15 18:31:42 +00:00
// Move file (copy + delete original)
await bucket.fastMove({
sourcePath: 'temp/draft.txt',
destinationPath: 'final/document.txt'
});
2026-01-25 18:09:38 +00:00
// Copy to different bucket
const targetBucket = await smartBucket.getBucketByName('backup-bucket');
await bucket.fastCopy({
sourcePath: 'important/data.json',
destinationPath: 'archived/data.json',
targetBucket: targetBucket
});
2025-11-20 15:14:11 +00:00
```
2026-03-14 22:42:29 +00:00
### Memory-Efficient Listing
2025-11-20 15:14:11 +00:00
SmartBucket provides three powerful patterns for listing objects, optimized for handling **millions of files ** efficiently:
2026-03-14 22:42:29 +00:00
#### Async Generators (Recommended)
2025-11-20 15:14:11 +00:00
Memory-efficient streaming using native JavaScript async iteration:
```typescript
// List all objects with prefix - streams one at a time!
for await (const key of bucket.listAllObjects('documents/')) {
2026-03-14 22:42:29 +00:00
console.log('Found:', key);
2025-11-20 15:14:11 +00:00
// Process each file individually (memory efficient!)
const content = await bucket.fastGet({ path: key });
processFile(content);
// Early termination support
if (shouldStop()) break;
}
// Find objects matching glob patterns
for await (const key of bucket.findByGlob('**/*.json')) {
2026-03-14 22:42:29 +00:00
console.log('JSON file:', key);
2025-11-20 15:14:11 +00:00
}
// Complex glob patterns
for await (const key of bucket.findByGlob('npm/packages/*/index.json')) {
2026-03-14 22:42:29 +00:00
console.log('Package index:', key);
2025-11-20 15:14:11 +00:00
}
for await (const key of bucket.findByGlob('images/*.{jpg,png,gif}')) {
2026-03-14 22:42:29 +00:00
console.log('Image:', key);
2025-11-20 15:14:11 +00:00
}
```
**Why use async generators?**
2026-03-14 22:42:29 +00:00
- Processes one item at a time (constant memory usage)
- Supports early termination with `break`
- Native JavaScript - no dependencies
- Perfect for large buckets with millions of objects
2025-11-20 15:14:11 +00:00
#### RxJS Observables
Perfect for reactive pipelines and complex data transformations:
```typescript
import { filter, take, map } from 'rxjs/operators';
// Stream keys as Observable with powerful operators
bucket.listAllObjectsObservable('logs/')
.pipe(
filter(key => key.endsWith('.log')),
take(100),
map(key => ({ key, timestamp: Date.now() }))
)
.subscribe({
2026-03-14 22:42:29 +00:00
next: (item) => console.log('Log file:', item.key),
error: (err) => console.error('Error:', err),
complete: () => console.log('Listing complete')
2025-11-20 15:14:11 +00:00
});
// Combine with other observables
import { merge } from 'rxjs';
const logs$ = bucket.listAllObjectsObservable('logs/');
const backups$ = bucket.listAllObjectsObservable('backups/');
merge(logs$, backups$)
.pipe(filter(key => key.includes('2024')))
2026-03-14 22:42:29 +00:00
.subscribe(key => console.log('2024 file:', key));
2024-04-14 17:22:27 +02:00
```
2025-11-20 15:14:11 +00:00
#### Cursor Pattern
Explicit pagination control for UI and resumable operations:
```typescript
// Create cursor with custom page size
const cursor = bucket.createCursor('uploads/', { pageSize: 100 });
// Fetch pages manually
while (cursor.hasMore()) {
const page = await cursor.next();
2026-03-14 22:42:29 +00:00
console.log(`Page has ${page.keys.length} items` );
2025-11-20 15:14:11 +00:00
for (const key of page.keys) {
console.log(` - ${key}` );
}
2026-03-14 22:42:29 +00:00
if (page.done) break;
2025-11-20 15:14:11 +00:00
}
// Save and restore cursor state (perfect for resumable operations!)
const token = cursor.getToken();
// Store token in database or session...
// ... later, in a different request ...
const newCursor = bucket.createCursor('uploads/', { pageSize: 100 });
newCursor.setToken(token); // Resume from saved position!
2026-01-25 18:09:38 +00:00
const nextPage = await newCursor.next();
2025-11-20 15:14:11 +00:00
// Reset cursor to start over
cursor.reset();
```
#### Convenience Methods
```typescript
2026-03-14 22:42:29 +00:00
// Collect all keys into array (WARNING: loads everything into memory!)
2025-11-20 15:14:11 +00:00
const allKeys = await bucket.listAllObjectsArray('images/');
2026-03-14 22:42:29 +00:00
console.log(`Found ${allKeys.length} images` );
2025-11-20 15:14:11 +00:00
```
**Performance Comparison:**
| Method | Memory Usage | Best For | Supports Early Exit |
|--------|-------------|----------|-------------------|
2026-03-14 22:42:29 +00:00
| **Async Generator ** | O(1) - constant | Most use cases, large datasets | Yes |
| **Observable ** | O(1) - constant | Reactive pipelines, RxJS apps | Yes |
| **Cursor ** | O(pageSize) | UI pagination, resumable ops | Yes |
| **Array ** | O(n) - grows with results | Small datasets (<10k items) | No |
2025-11-20 15:14:11 +00:00
2026-03-14 22:42:29 +00:00
### Bucket Watching
2026-01-25 18:09:38 +00:00
2026-03-14 22:42:29 +00:00
Monitor your storage bucket for changes in real-time with the powerful `BucketWatcher` :
2026-01-25 18:09:38 +00:00
```typescript
// Create a watcher for a specific prefix
const watcher = bucket.createWatcher({
prefix: 'uploads/', // Watch files with this prefix
pollIntervalMs: 3000, // Check every 3 seconds
includeInitial: false, // Don't emit existing files on start
});
// RxJS Observable pattern (recommended for reactive apps)
watcher.changeSubject.subscribe((change) => {
if (change.type === 'add') {
2026-03-14 22:42:29 +00:00
console.log('New file:', change.key);
2026-01-25 18:09:38 +00:00
} else if (change.type === 'modify') {
2026-03-14 22:42:29 +00:00
console.log('Modified:', change.key);
2026-01-25 18:09:38 +00:00
} else if (change.type === 'delete') {
2026-03-14 22:42:29 +00:00
console.log('Deleted:', change.key);
2026-01-25 18:09:38 +00:00
}
});
// EventEmitter pattern (classic Node.js style)
watcher.on('change', (change) => {
2026-03-14 22:42:29 +00:00
console.log(`${change.type}: ${change.key}` );
2026-01-25 18:09:38 +00:00
});
watcher.on('error', (err) => {
2026-03-14 22:42:29 +00:00
console.error('Watcher error:', err);
2026-01-25 18:09:38 +00:00
});
// Start watching
await watcher.start();
// Wait until watcher is ready (initial state built)
await watcher.readyDeferred.promise;
2026-03-14 22:42:29 +00:00
console.log('Watcher is now monitoring the bucket');
2026-01-25 18:09:38 +00:00
// ... your application runs ...
// Stop watching when done
await watcher.stop();
// Or use the alias:
await watcher.close();
```
#### Watcher Options
```typescript
interface IBucketWatcherOptions {
prefix?: string; // Filter objects by prefix (default: '' = all)
pollIntervalMs?: number; // Polling interval in ms (default: 5000)
bufferTimeMs?: number; // Buffer events before emitting (for batching)
includeInitial?: boolean; // Emit existing files as 'add' on start (default: false)
pageSize?: number; // Objects per page when listing (default: 1000)
}
```
#### Buffered Events
For high-frequency change environments, buffer events to reduce processing overhead:
```typescript
const watcher = bucket.createWatcher({
prefix: 'high-traffic/',
pollIntervalMs: 1000,
bufferTimeMs: 2000, // Collect events for 2 seconds before emitting
});
// Receive batched events as arrays
watcher.changeSubject.subscribe((changes) => {
if (Array.isArray(changes)) {
2026-03-14 22:42:29 +00:00
console.log(`Batch of ${changes.length} changes:` );
2026-01-25 18:09:38 +00:00
changes.forEach(c => console.log(` - ${c.type}: ${c.key}` ));
}
});
await watcher.start();
```
#### Change Event Structure
```typescript
2026-03-14 22:42:29 +00:00
interface IStorageChangeEvent {
2026-01-25 18:09:38 +00:00
type: 'add' | 'modify' | 'delete';
key: string; // Object key (path)
bucket: string; // Bucket name
size?: number; // File size (not present for deletes)
etag?: string; // ETag hash (not present for deletes)
lastModified?: Date; // Last modified date (not present for deletes)
}
```
#### Watch Use Cases
2026-03-14 22:42:29 +00:00
- **Sync systems** - Detect changes to trigger synchronization
- **Analytics** - Track file uploads/modifications in real-time
- **Notifications** - Alert users when their files are ready
- **Processing pipelines** - Trigger workflows on new file uploads
- **Backup systems** - Detect changes for incremental backups
- **Audit logs** - Track all bucket activity
2026-01-25 18:09:38 +00:00
2026-03-14 22:42:29 +00:00
### Directory Management
2024-05-17 19:24:52 +02:00
2025-08-15 18:31:42 +00:00
SmartBucket provides powerful directory-like operations for organizing your files:
2024-05-17 19:24:52 +02:00
```typescript
2025-08-15 18:31:42 +00:00
// Get base directory
const baseDir = await bucket.getBaseDirectory();
2024-05-17 19:24:52 +02:00
2025-08-15 18:31:42 +00:00
// List directories and files
const directories = await baseDir.listDirectories();
const files = await baseDir.listFiles();
2024-05-17 19:24:52 +02:00
2026-03-14 22:42:29 +00:00
console.log(`Found ${directories.length} directories` );
console.log(`Found ${files.length} files` );
2024-11-24 20:02:40 +01:00
2025-08-15 18:31:42 +00:00
// Navigate subdirectories
const subDir = await baseDir.getSubDirectoryByName('projects/2024');
2024-05-17 19:24:52 +02:00
2025-08-15 18:31:42 +00:00
// Create nested file
await subDir.fastPut({
path: 'report.pdf',
contents: reportBuffer
});
// Get directory tree structure
const tree = await subDir.getTreeArray();
2024-05-17 19:24:52 +02:00
2025-11-20 15:14:11 +00:00
// Get directory path
2026-03-14 22:42:29 +00:00
console.log('Base path:', subDir.getBasePath()); // "projects/2024/"
2025-11-20 15:14:11 +00:00
2025-08-15 18:31:42 +00:00
// Create empty file as placeholder
await subDir.createEmptyFile('placeholder.txt');
2026-01-25 18:09:38 +00:00
// Check existence
const fileExists = await subDir.fileExists({ path: 'report.pdf' });
const dirExists = await baseDir.directoryExists('projects');
2024-05-17 19:24:52 +02:00
```
2026-03-14 22:42:29 +00:00
### Streaming Operations
2024-04-14 17:22:27 +02:00
2025-11-20 15:14:11 +00:00
Handle large files efficiently with streaming support:
2024-11-24 20:12:20 +01:00
2025-08-15 18:31:42 +00:00
#### Download Streams
2024-04-14 17:22:27 +02:00
2025-08-15 18:31:42 +00:00
```typescript
2025-11-20 15:14:11 +00:00
// Node.js stream (for file I/O, HTTP responses, etc.)
2025-08-15 18:31:42 +00:00
const nodeStream = await bucket.fastGetStream(
{ path: 'large-video.mp4' },
'nodestream'
);
2025-11-20 15:14:11 +00:00
// Pipe to file
import * as fs from 'node:fs';
2025-08-15 18:31:42 +00:00
nodeStream.pipe(fs.createWriteStream('local-video.mp4'));
2025-11-20 15:14:11 +00:00
// Pipe to HTTP response
app.get('/download', async (req, res) => {
const stream = await bucket.fastGetStream(
{ path: 'file.pdf' },
'nodestream'
);
res.setHeader('Content-Type', 'application/pdf');
stream.pipe(res);
});
// Web stream (for modern browser/Deno environments)
2025-08-15 18:31:42 +00:00
const webStream = await bucket.fastGetStream(
{ path: 'large-file.zip' },
'webstream'
);
```
#### Upload Streams
2024-05-21 18:46:59 +02:00
2024-05-17 19:24:52 +02:00
```typescript
2025-11-20 15:14:11 +00:00
import * as fs from 'node:fs';
2025-08-15 18:31:42 +00:00
// Stream upload from file
const readStream = fs.createReadStream('big-data.csv');
await bucket.fastPutStream({
path: 'uploads/big-data.csv',
2026-01-25 18:09:38 +00:00
readableStream: readStream,
nativeMetadata: {
'content-type': 'text/csv',
'x-custom-header': 'my-value'
2025-08-15 18:31:42 +00:00
}
});
```
#### Reactive Streams with RxJS
2024-11-24 20:02:40 +01:00
2025-08-15 18:31:42 +00:00
```typescript
// Get file as ReplaySubject for reactive programming
const replaySubject = await bucket.fastGetReplaySubject({
2026-01-25 18:09:38 +00:00
path: 'data/sensor-readings.json'
2025-08-15 18:31:42 +00:00
});
2024-11-24 20:02:40 +01:00
2025-11-20 15:14:11 +00:00
// Multiple subscribers can consume the same data
2025-08-15 18:31:42 +00:00
replaySubject.subscribe({
next: (chunk) => processChunk(chunk),
2026-03-14 22:42:29 +00:00
complete: () => console.log('Stream complete')
2025-08-15 18:31:42 +00:00
});
2025-11-20 15:14:11 +00:00
replaySubject.subscribe({
next: (chunk) => logChunk(chunk)
});
2025-08-15 18:31:42 +00:00
```
2026-03-14 22:42:29 +00:00
### File Locking
2025-08-15 18:31:42 +00:00
2025-11-20 15:14:11 +00:00
Prevent concurrent modifications with built-in file locking:
2025-08-15 18:31:42 +00:00
```typescript
2026-01-25 18:09:38 +00:00
const baseDir = await bucket.getBaseDirectory();
const file = await baseDir.getFile({ path: 'important-config.json' });
2025-08-15 18:31:42 +00:00
// Lock file for 10 minutes
await file.lock({ timeoutMillis: 600000 });
2024-05-17 19:24:52 +02:00
2026-01-25 18:09:38 +00:00
// Check lock status via metadata
const metadata = await file.getMetaData();
const isLocked = await metadata.checkLocked();
2025-11-20 15:14:11 +00:00
2026-01-25 18:09:38 +00:00
// Get lock info
const lockInfo = await metadata.getLockInfo();
console.log(`Lock expires: ${new Date(lockInfo.expires)}` );
2025-08-15 18:31:42 +00:00
// Unlock when done
await file.unlock();
2026-01-25 18:09:38 +00:00
// Force unlock (even if locked by another process)
await file.unlock({ force: true });
2024-05-17 19:24:52 +02:00
```
2025-11-20 15:14:11 +00:00
**Lock use cases:**
2026-03-14 22:42:29 +00:00
- Prevent concurrent writes during critical updates
- Protect configuration files during deployment
- Coordinate distributed workers
- Ensure data consistency
2025-11-20 15:14:11 +00:00
2026-03-14 22:42:29 +00:00
### Metadata Management
2024-11-24 20:02:40 +01:00
2025-11-20 15:14:11 +00:00
Attach and manage rich metadata for your files:
2024-05-17 19:24:52 +02:00
```typescript
2026-01-25 18:09:38 +00:00
const baseDir = await bucket.getBaseDirectory();
const file = await baseDir.getFile({ path: 'document.pdf' });
2024-11-24 20:02:40 +01:00
2025-08-15 18:31:42 +00:00
// Get metadata handler
const metadata = await file.getMetaData();
2024-11-24 20:02:40 +01:00
2026-01-25 18:09:38 +00:00
// Store custom metadata (can be any JSON-serializable value)
await metadata.storeCustomMetaData({
2025-08-15 18:31:42 +00:00
key: 'author',
value: 'John Doe'
});
2026-01-25 18:09:38 +00:00
await metadata.storeCustomMetaData({
key: 'tags',
value: ['important', 'quarterly-report', '2024']
2025-08-15 18:31:42 +00:00
});
2024-05-17 19:24:52 +02:00
2026-01-25 18:09:38 +00:00
await metadata.storeCustomMetaData({
key: 'workflow',
value: { status: 'approved', approvedBy: 'jane@company .com' }
2025-11-20 15:14:11 +00:00
});
2025-08-15 18:31:42 +00:00
// Retrieve metadata
const author = await metadata.getCustomMetaData({ key: 'author' });
2026-01-25 18:09:38 +00:00
// Delete metadata
await metadata.deleteCustomMetaData({ key: 'workflow' });
2025-11-20 15:14:11 +00:00
2026-01-25 18:09:38 +00:00
// Check if file has any metadata
const hasMetadata = await file.hasMetaData();
// Get file type detection
const fileType = await metadata.getFileType({ useFileExtension: true });
// Get file type from magic bytes (more accurate)
const detectedType = await metadata.getFileType({ useMagicBytes: true });
// Get file size
const size = await metadata.getSizeInBytes();
2024-05-17 19:24:52 +02:00
```
2025-11-20 15:14:11 +00:00
**Metadata use cases:**
2026-03-14 22:42:29 +00:00
- Track file ownership and authorship
- Add tags and categories for search
- Store processing status or workflow state
- Enable rich querying and filtering
- Maintain audit trails
2025-11-20 15:14:11 +00:00
2026-03-14 22:42:29 +00:00
### Trash & Recovery
2025-08-15 18:31:42 +00:00
2025-11-20 15:14:11 +00:00
SmartBucket includes an intelligent trash system for safe file deletion and recovery:
2024-05-17 19:24:52 +02:00
2025-08-15 18:31:42 +00:00
```typescript
2026-01-25 18:09:38 +00:00
const baseDir = await bucket.getBaseDirectory();
const file = await baseDir.getFile({ path: 'important-data.xlsx' });
2025-08-15 18:31:42 +00:00
// Move to trash instead of permanent deletion
await file.delete({ mode: 'trash' });
2025-11-20 15:14:11 +00:00
// Permanent deletion (use with caution!)
await file.delete({ mode: 'permanent' });
2025-08-15 18:31:42 +00:00
// Access trash
const trash = await bucket.getTrash();
const trashDir = await trash.getTrashDir();
const trashedFiles = await trashDir.listFiles();
// Restore from trash
2026-01-25 18:09:38 +00:00
const trashedFile = await baseDir.getFile({
path: 'important-data.xlsx',
getFromTrash: true
});
2025-08-15 18:31:42 +00:00
await trashedFile.restore({ useOriginalPath: true });
2025-11-20 15:14:11 +00:00
// Or restore to a different location
await trashedFile.restore({
2026-01-25 18:09:38 +00:00
toPath: 'recovered/important-data.xlsx'
2025-11-20 15:14:11 +00:00
});
2025-08-15 18:31:42 +00:00
```
2024-11-24 20:12:20 +01:00
2025-11-20 15:14:11 +00:00
**Trash features:**
2026-03-14 22:42:29 +00:00
- Recover accidentally deleted files
- Preserves original path in metadata
- Tracks deletion timestamp
- List and inspect trashed files
2025-11-20 15:14:11 +00:00
2026-03-14 22:42:29 +00:00
### Advanced Features
2024-05-17 19:24:52 +02:00
2025-08-15 18:31:42 +00:00
#### File Statistics
2024-05-17 19:24:52 +02:00
2025-08-15 18:31:42 +00:00
```typescript
// Get detailed file statistics
const stats = await bucket.fastStat({ path: 'document.pdf' });
2026-03-14 22:42:29 +00:00
console.log(`Size: ${stats.ContentLength} bytes` );
console.log(`Last modified: ${stats.LastModified}` );
console.log(`ETag: ${stats.ETag}` );
console.log(`Content type: ${stats.ContentType}` );
2025-08-15 18:31:42 +00:00
```
2024-05-17 19:24:52 +02:00
2025-08-15 18:31:42 +00:00
#### Magic Bytes Detection
2024-05-17 19:24:52 +02:00
2025-11-20 15:14:11 +00:00
Detect file types by examining the first bytes (useful for validation):
2024-05-17 19:24:52 +02:00
```typescript
2025-08-15 18:31:42 +00:00
// Read first bytes for file type detection
const magicBytes = await bucket.getMagicBytes({
path: 'mystery-file',
length: 16
});
2024-05-17 19:24:52 +02:00
2025-08-15 18:31:42 +00:00
// Or from a File object
2026-01-25 18:09:38 +00:00
const baseDir = await bucket.getBaseDirectory();
const file = await baseDir.getFile({ path: 'image.jpg' });
2025-08-15 18:31:42 +00:00
const magic = await file.getMagicBytes({ length: 4 });
2025-11-20 15:14:11 +00:00
// Check file signatures
if (magic[0] === 0xFF && magic[1] === 0xD8) {
2026-03-14 22:42:29 +00:00
console.log('This is a JPEG image');
2025-11-20 15:14:11 +00:00
} else if (magic[0] === 0x89 && magic[1] === 0x50) {
2026-03-14 22:42:29 +00:00
console.log('This is a PNG image');
2025-11-20 15:14:11 +00:00
}
2024-05-17 19:24:52 +02:00
```
2025-08-15 18:31:42 +00:00
#### JSON Data Operations
```typescript
2026-01-25 18:09:38 +00:00
const baseDir = await bucket.getBaseDirectory();
const file = await baseDir.getFile({ path: 'config.json' });
2025-08-15 18:31:42 +00:00
// Read JSON data
const config = await file.getJsonData();
// Update JSON data
config.version = '2.0';
config.updated = new Date().toISOString();
2025-11-20 15:14:11 +00:00
config.features.push('newFeature');
2025-08-15 18:31:42 +00:00
await file.writeJsonData(config);
```
2024-11-24 20:02:40 +01:00
2025-08-15 18:31:42 +00:00
#### Directory & File Type Detection
2024-05-17 19:24:52 +02:00
```typescript
2025-08-15 18:31:42 +00:00
// Check if path is a directory
const isDir = await bucket.isDirectory({ path: 'uploads/' });
// Check if path is a file
const isFile = await bucket.isFile({ path: 'uploads/document.pdf' });
2024-05-17 19:24:52 +02:00
```
2025-08-15 18:31:42 +00:00
#### Clean Bucket Contents
```typescript
2026-03-14 22:42:29 +00:00
// Remove all files and directories (use with caution!)
2025-08-15 18:31:42 +00:00
await bucket.cleanAllContents();
```
2026-03-14 22:42:29 +00:00
### Cloud Provider Support
2025-08-15 18:31:42 +00:00
2025-11-20 15:14:11 +00:00
SmartBucket works seamlessly with all major S3-compatible providers:
2024-04-14 17:22:27 +02:00
2025-11-20 15:14:11 +00:00
| Provider | Status | Notes |
|----------|--------|-------|
2026-03-14 22:42:29 +00:00
| **AWS S3 ** | Supported | Native S3 API |
| **MinIO ** | Supported | Self-hosted, perfect for development |
| **DigitalOcean Spaces ** | Supported | Cost-effective S3-compatible |
| **Backblaze B2 ** | Supported | Very affordable storage |
| **Wasabi ** | Supported | High-performance hot storage |
| **Google Cloud Storage ** | Supported | Via S3-compatible API |
| **Cloudflare R2 ** | Supported | Zero egress fees |
| **Any S3-compatible ** | Supported | Works with any S3-compatible provider |
2025-08-15 18:31:42 +00:00
2025-11-20 15:14:11 +00:00
**Configuration examples:**
2024-04-14 17:22:27 +02:00
2024-11-24 20:12:20 +01:00
```typescript
2025-11-20 15:14:11 +00:00
// AWS S3
const awsStorage = new SmartBucket({
accessKey: process.env.AWS_ACCESS_KEY_ID,
accessSecret: process.env.AWS_SECRET_ACCESS_KEY,
endpoint: 's3.amazonaws.com',
region: 'us-east-1',
useSsl: true
});
// MinIO (local development)
const minioStorage = new SmartBucket({
accessKey: 'minioadmin',
accessSecret: 'minioadmin',
endpoint: 'localhost',
port: 9000,
useSsl: false
});
// DigitalOcean Spaces
const doStorage = new SmartBucket({
accessKey: process.env.DO_SPACES_KEY,
accessSecret: process.env.DO_SPACES_SECRET,
endpoint: 'nyc3.digitaloceanspaces.com',
region: 'nyc3',
useSsl: true
});
// Backblaze B2
const b2Storage = new SmartBucket({
accessKey: process.env.B2_KEY_ID,
accessSecret: process.env.B2_APPLICATION_KEY,
endpoint: 's3.us-west-002.backblazeb2.com',
region: 'us-west-002',
useSsl: true
2025-08-15 18:31:42 +00:00
});
2026-01-25 18:09:38 +00:00
// Cloudflare R2
const r2Storage = new SmartBucket({
accessKey: process.env.R2_ACCESS_KEY_ID,
accessSecret: process.env.R2_SECRET_ACCESS_KEY,
endpoint: `${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com` ,
region: 'auto',
useSsl: true
});
2025-11-20 15:14:11 +00:00
```
2024-11-24 20:12:20 +01:00
2026-03-14 22:42:29 +00:00
### Advanced Configuration
2025-11-20 15:14:11 +00:00
```typescript
// Environment-based configuration with @push .rocks/qenv
2025-08-15 18:31:42 +00:00
import { Qenv } from '@push .rocks/qenv';
2025-11-20 15:14:11 +00:00
2025-08-15 18:31:42 +00:00
const qenv = new Qenv('./', './.nogit/');
const smartBucket = new SmartBucket({
2026-01-25 18:09:38 +00:00
accessKey: await qenv.getEnvVarOnDemand('S3_ACCESS_KEY'),
accessSecret: await qenv.getEnvVarOnDemand('S3_SECRET'),
endpoint: await qenv.getEnvVarOnDemand('S3_ENDPOINT'),
port: parseInt(await qenv.getEnvVarOnDemand('S3_PORT')),
useSsl: await qenv.getEnvVarOnDemand('S3_USE_SSL') === 'true',
region: await qenv.getEnvVarOnDemand('S3_REGION')
2025-08-15 18:31:42 +00:00
});
2024-11-24 20:02:40 +01:00
```
2024-04-14 17:22:27 +02:00
2026-03-14 22:42:29 +00:00
### Testing
2024-11-24 20:12:20 +01:00
2025-08-15 18:31:42 +00:00
```bash
2025-11-20 15:14:11 +00:00
# Run all tests
2025-08-15 18:31:42 +00:00
pnpm test
2025-11-20 15:14:11 +00:00
# Run specific test file
2026-01-25 18:09:38 +00:00
pnpm tstest test/test.watcher.node.ts --verbose
2025-11-20 15:14:11 +00:00
# Run tests with log file
pnpm test --logfile
```
2026-03-14 22:42:29 +00:00
### Error Handling Best Practices
2025-11-20 15:14:11 +00:00
SmartBucket uses a **strict-by-default ** approach - methods throw errors instead of returning null:
```typescript
2026-03-14 22:42:29 +00:00
// Check existence first
2025-11-20 15:14:11 +00:00
if (await bucket.fastExists({ path: 'file.txt' })) {
const content = await bucket.fastGet({ path: 'file.txt' });
process(content);
}
2026-03-14 22:42:29 +00:00
// Try/catch for expected failures
2025-11-20 15:14:11 +00:00
try {
const file = await bucket.fastGet({ path: 'might-not-exist.txt' });
process(file);
} catch (error) {
console.log('File not found, using default');
useDefault();
}
2026-03-14 22:42:29 +00:00
// Explicit overwrite control
2025-11-20 15:14:11 +00:00
try {
await bucket.fastPut({
path: 'existing-file.txt',
contents: 'new data',
overwrite: false // Explicitly fail if exists
});
} catch (error) {
console.log('File already exists');
}
2024-11-24 20:12:20 +01:00
```
2024-04-14 17:22:27 +02:00
2026-03-14 22:42:29 +00:00
### Best Practices
2025-08-15 18:31:42 +00:00
1. **Always use strict mode ** for critical operations to catch errors early
2025-11-20 15:14:11 +00:00
2. **Check existence first ** with `fastExists()` , `bucketExists()` , etc. before operations
3. **Implement proper error handling ** for network and permission issues
4. **Use streaming ** for large files (>100MB) to optimize memory usage
5. **Leverage metadata ** for organizing and searching files
6. **Enable trash mode ** for important data to prevent accidental loss
7. **Lock files ** during critical operations to prevent race conditions
8. **Use async generators ** for listing large buckets to avoid memory issues
9. **Set explicit overwrite flags ** to prevent accidental file overwrites
2026-01-25 18:09:38 +00:00
10. **Use the watcher ** for real-time synchronization and event-driven architectures
2025-11-20 15:14:11 +00:00
2026-03-14 22:42:29 +00:00
### Performance Tips
2025-11-20 15:14:11 +00:00
- **Listing**: Use async generators or cursors for buckets with >10,000 objects
- **Uploads**: Use streams for files >100MB
- **Downloads**: Use streams for files you'll process incrementally
- **Metadata**: Cache metadata when reading frequently
- **Locking**: Keep lock durations as short as possible
- **Glob patterns**: Be specific to reduce objects scanned
2026-01-25 18:09:38 +00:00
- **Watching**: Use appropriate `pollIntervalMs` based on your change frequency
2025-08-15 18:31:42 +00:00
2024-04-14 17:22:27 +02:00
## License and Legal Information
2026-01-25 18:09:38 +00:00
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE ](./LICENSE ) file.
2024-04-14 17:22:27 +02:00
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
2020-05-17 15:57:12 +00:00
2026-01-25 18:09:38 +00:00
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
2020-05-17 15:57:12 +00:00
2024-04-14 17:22:27 +02:00
### Company Information
2020-05-17 15:57:12 +00:00
2025-11-20 15:14:11 +00:00
Task Venture Capital GmbH
2026-01-25 18:09:38 +00:00
Registered at District Court Bremen HRB 35230 HB, Germany
2019-07-07 10:48:24 +02:00
2026-01-25 18:09:38 +00:00
For any legal inquiries or further information, please contact us via email at hello@task .vc.
2019-07-07 10:48:24 +02:00
2025-11-20 15:14:11 +00:00
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.