@digibuffer/file-manager-core
v3.0.1
Published
File management library for S3/R2 storage — presigned URLs, delete, move, copy, and React hooks. DB-agnostic via lifecycle callbacks.
Maintainers
Readme
@digibuffer/file-manager-core
Storage adapter layer for S3-compatible object storage — R2 and S3 out of the box, fully pluggable. Wraps raw storage operations (putObject, getObject, deleteObject, etc.) with optional lifecycle hooks for DB sync.
Features
- Pluggable adapters —
r2()ands3()factories, or bring your own viaStorageAdapter - Full S3-compatible API — put, get, head, delete, copy, move, list, presign
- Lifecycle hooks —
beforeDelete,afterDelete,afterMove,afterCopyfor DB sync - HTTP router — drop-in API route handler (action-based, Zod-validated)
- DB-agnostic listing —
getObjectscallback for DB-backed pagination - React hooks —
useListFiles,useDeleteFile,useDownloadUrl,useMoveFile - Edge-compatible — uses
aws4fetchfor signing (no Node.js crypto)
Installation
npm install @digibuffer/file-manager-coreSetup
R2 (Cloudflare)
import { FileManager, r2 } from '@digibuffer/file-manager-core';
const fileManager = new FileManager({
adapter: r2({
bucket: process.env.R2_BUCKET!,
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
}),
});S3 (AWS)
import { FileManager, s3 } from '@digibuffer/file-manager-core';
const fileManager = new FileManager({
adapter: s3({
bucket: process.env.S3_BUCKET!,
region: process.env.AWS_REGION!,
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
}),
});FileManager methods
// Upload
await fileManager.putObject('users/123/photo.jpg', fileBody, { contentType: 'image/jpeg' });
// Download
const result = await fileManager.getObject('users/123/photo.jpg');
// → { body: ReadableStream, contentType, contentLength, lastModified }
// Metadata
const meta = await fileManager.headObject('users/123/photo.jpg');
// → { key, size, lastModified, contentType, etag } or null
// Check existence
const exists = await fileManager.exists('users/123/photo.jpg');
// List
const { objects, hasMore, nextCursor } = await fileManager.listObjects({
prefix: 'users/123/',
maxKeys: 100,
cursor: nextCursor, // for pagination
});
// Delete
await fileManager.deleteObject('users/123/photo.jpg');
await fileManager.deleteObjects(['key1', 'key2', 'key3']);
// Copy / Move / Rename
await fileManager.copyObject('source/file.jpg', 'dest/file.jpg');
await fileManager.moveObject('old/file.jpg', 'new/file.jpg');
await fileManager.rename('old/file.jpg', 'new/file.jpg'); // alias for moveObject
// Presigned URLs
const getUrl = await fileManager.presignGetObject('users/123/photo.jpg', { expiresIn: 3600 });
const putUrl = await fileManager.presignPutObject('uploads/new.jpg', { contentType: 'image/jpeg' });
// Presign for download (attachment)
const dlUrl = await fileManager.presignGetObject('report.pdf', {
disposition: 'attachment',
filename: 'Q1-Report.pdf',
expiresIn: 300,
});Lifecycle hooks
Hooks let you sync your DB without coupling the library to any ORM:
const fileManager = new FileManager(
{ adapter: r2({ ... }) },
{
beforeDelete: async (keys) => {
// Check permissions before deletion
},
afterDelete: async (keys) => {
await db.files.deleteMany({ where: { key: { in: keys } } });
},
afterMove: async (sourceKey, destinationKey) => {
await db.files.update({ where: { key: sourceKey }, data: { key: destinationKey } });
},
afterCopy: async (sourceKey, destinationKey) => {
const original = await db.files.findUnique({ where: { key: sourceKey } });
if (original) await db.files.create({ data: { ...original, key: destinationKey } });
},
}
);Hooks wrapped with lifecycle: deleteObject, deleteObjects, copyObject, moveObject.
Direct pass-throughs (no hooks): putObject, getObject, headObject, listObjects, presignGetObject, presignPutObject.
HTTP router (Next.js / any runtime)
// app/api/files/route.ts
import { FileManager, createFileManagerRouter, r2 } from '@digibuffer/file-manager-core';
import { toRouteHandler } from '@digibuffer/file-manager-core/adapters/next';
const fileManager = new FileManager({ adapter: r2({ ... }) });
const router = createFileManagerRouter({
fileManager,
// Optional: extract auth context from request
getAuthContext: async (req) => {
const session = await getSession(req);
return { userId: session?.user?.id };
},
// Optional: authorize actions
authorize: async (context, action) => {
if (action === 'delete' && context.userId !== 'admin') return false;
return true;
},
// Optional: DB-backed listing instead of raw storage listing
getObjects: async (options, context) => {
const result = await db.files.findMany({
where: { userId: context.userId, folderId: options.folderId },
take: options.limit ?? 20,
});
return { data: result, hasMore: false, total: result.length };
},
});
export const { POST } = toRouteHandler(router);Supported actions
All actions are POST with a JSON body { action, ...params }:
| Action | Body | Description |
|---|---|---|
| list | { options? } | List objects (or call getObjects if configured) |
| delete | { key } | Delete a single object |
| deleteBatch | { keys } | Delete multiple objects |
| move | { sourceKey, destinationKey } | Move an object |
| moveBatch | { moves: [{ sourceKey, destinationKey }] } | Move multiple objects |
| copy | { sourceKey, destinationKey } | Copy an object |
| rename | { key, newKey } | Rename (alias for move) |
| presignGet | { key, options? } | Generate a presigned GET URL |
| presignPut | { key, options? } | Generate a presigned PUT URL |
| exists | { key } | Check if object exists |
| headObject | { key } | Get object metadata |
React client hooks
Wrap your app (or a section of it) with FileManagerProvider:
import { FileManagerProvider } from '@digibuffer/file-manager-core/client';
<FileManagerProvider config={{ endpoint: '/api/files' }}>
<YourApp />
</FileManagerProvider>useListFiles
import { useListFiles } from '@digibuffer/file-manager-core/client';
function FileList() {
const { data, isLoading, hasMore, list, loadMore } = useListFiles<MyFile>();
useEffect(() => {
list({ prefix: 'users/123/', limit: 20 });
}, []);
return (
<>
{data?.data.map(file => <div key={file.key}>{file.key}</div>)}
{hasMore && <button onClick={() => loadMore()}>Load more</button>}
</>
);
}useDeleteFile
const { deleteFile, deleteBatch, isLoading } = useDeleteFile();
await deleteFile('users/123/photo.jpg');
await deleteBatch(['key1', 'key2']);useDownloadUrl
const { getUrl, download } = useDownloadUrl();
// Get a presigned URL (inline view)
const url = await getUrl('photo.jpg', { disposition: 'inline', expiresIn: 300 });
// Trigger browser download
await download('report.pdf', { filename: 'Q1-Report.pdf' });useMoveFile
const { moveFile, moveBatch, isLoading } = useMoveFile();
await moveFile({ sourceKey: 'uploads/photo.jpg', destinationKey: 'albums/photo.jpg' });
await moveBatch([
{ sourceKey: 'uploads/a.jpg', destinationKey: 'archive/a.jpg' },
{ sourceKey: 'uploads/b.jpg', destinationKey: 'archive/b.jpg' },
]);Custom adapter
Implement StorageAdapter to support any S3-compatible provider (MinIO, Wasabi, Backblaze B2, etc.):
import type { StorageAdapter } from '@digibuffer/file-manager-core';
class MyAdapter implements StorageAdapter {
readonly bucket = 'my-bucket';
async putObject(key, body, options?) { ... }
async getObject(key) { ... }
async headObject(key) { ... }
async deleteObject(key) { ... }
async deleteObjects(keys) { ... }
async copyObject(sourceKey, destinationKey) { ... }
async moveObject(sourceKey, destinationKey) { ... }
async listObjects(options?) { ... }
async presignGetObject(key, options?) { ... }
async presignPutObject(key, options?) { ... }
}
const fileManager = new FileManager({ adapter: new MyAdapter() });API Reference
r2(config) → StorageAdapter
| Field | Type | Description |
|---|---|---|
| bucket | string | R2 bucket name |
| accountId | string | Cloudflare account ID |
| accessKeyId | string | R2 access key ID |
| secretAccessKey | string | R2 secret access key |
s3(config) → StorageAdapter
| Field | Type | Description |
|---|---|---|
| bucket | string | S3 bucket name |
| region | string | AWS region (e.g. us-east-1) |
| accessKeyId | string | AWS access key ID |
| secretAccessKey | string | AWS secret access key |
| endpoint | string? | Custom endpoint (overrides default AWS endpoint) |
FileManager
| Method | Description |
|---|---|
| putObject(key, body, options?) | Upload an object |
| getObject(key) | Download an object |
| headObject(key) | Get metadata (or null) |
| exists(key) | Check existence |
| listObjects(options?) | List with pagination |
| deleteObject(key) | Delete one object |
| deleteObjects(keys) | Delete multiple objects |
| copyObject(src, dst, options?) | Copy |
| moveObject(src, dst) | Move (copy + delete) |
| rename(oldKey, newKey) | Alias for moveObject |
| presignGetObject(key, options?) | Presigned GET URL |
| presignPutObject(key, options?) | Presigned PUT URL |
| getDownloadUrl(key, options?) | Alias for presignGetObject |
License
Proprietary — All rights reserved.
