npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

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 adaptersr2() and s3() factories, or bring your own via StorageAdapter
  • Full S3-compatible API — put, get, head, delete, copy, move, list, presign
  • Lifecycle hooksbeforeDelete, afterDelete, afterMove, afterCopy for DB sync
  • HTTP router — drop-in API route handler (action-based, Zod-validated)
  • DB-agnostic listinggetObjects callback for DB-backed pagination
  • React hooksuseListFiles, useDeleteFile, useDownloadUrl, useMoveFile
  • Edge-compatible — uses aws4fetch for signing (no Node.js crypto)

Installation

npm install @digibuffer/file-manager-core

Setup

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.