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

gw-file

v0.1.6

Published

Typed browser and server file-upload primitives for S3-compatible object storage.

Readme

gw-file

Typed browser and server primitives for uploading files to S3-compatible object storage. The package keeps application-specific persistence behind a FileRepository interface and returns gw-result values instead of throwing for expected infrastructure failures.

Features

  • Two-step browser uploads using presigned S3 PUT URLs
  • Direct server uploads and object lookup/deletion helpers
  • Application-owned file metadata through FileRepository
  • Browser image/video metadata extraction
  • CDN URL and responsive srcSet helpers
  • Separate root, browser, and server entry points
  • ESM, CommonJS, and TypeScript declaration output

Requirements

  • Node.js 20 or newer for server APIs and package builds
  • An S3 bucket and AWS credentials with the operations your application uses
  • React 19 when using gw-file/client responsive image components
  • A Fetch API-compatible runtime for the supplied HTTP handlers

Install

npm install gw-file

Install the React peers when using responsive image components:

npm install react react-dom

Entry points

| Import | Purpose | Runtime | | --- | --- | --- | | gw-file | CDN URL helpers | Universal | | gw-file/client | Browser uploader, media metadata, responsive images | Browser | | gw-file/server | S3 adapter, file service, repository contract, handlers | Node.js |

Do not import gw-file/server from browser bundles. It depends on the AWS SDK and server credentials.

Upload flow

Browser                     Application API                    S3
   | POST file metadata  ->       |                             |
   |                             create DB reference             |
   |                             create presigned URL            |
   | <- file + signedUrl          |                             |
   | PUT Blob -------------------------------------------------> |
   | <- success ------------------------------------------------ |

The application endpoint creates the file reference before the browser uploads the bytes. If the S3 PUT fails, the reference can remain in the repository; applications should clean up abandoned records when needed.

Server setup

1. Configure object storage

Pass the region explicitly for predictable deployments:

import { ObjectStorage } from "gw-file/server";

export const objectStorage = new ObjectStorage({
  bucketName: process.env.AWS_S3_BUCKET_NAME!,
  region: process.env.AWS_REGION!,
});

ObjectStorage passes all remaining options directly to the AWS S3Client. If region is omitted, the AWS SDK resolves it through its standard provider chain, such as AWS_REGION or the shared AWS config file. gw-file does not choose a default region.

AWS credentials use the same provider chain. In deployed AWS environments, prefer an IAM role over long-lived access keys.

For another S3-compatible service, pass its standard client options:

const objectStorage = new ObjectStorage({
  bucketName: "uploads",
  region: "auto",
  endpoint: "https://object-storage.example.com",
  forcePathStyle: true,
  credentials: {
    accessKeyId: process.env.OBJECT_STORAGE_ACCESS_KEY_ID!,
    secretAccessKey: process.env.OBJECT_STORAGE_SECRET_ACCESS_KEY!,
  },
});

2. Implement the repository

The package does not prescribe a database or ORM:

import type { FileRepository } from "gw-file/server";

type AppFile = {
  id: string;
  userId?: string;
  key: string;
  name: string;
  type?: string;
  size?: number;
  metadata?: Record<string, unknown>;
};

export const fileRepository: FileRepository<AppFile> = {
  async findFileById(fileId) {
    return database.files.find(fileId);
  },
  isForbidden(file, userId) {
    return Boolean(file.userId && file.userId !== userId);
  },
  async createFile(file) {
    return database.files.create(file);
  },
  async deleteFile(fileId) {
    await database.files.delete(fileId);
  },
};

3. Create the service

import { FileService } from "gw-file/server";

export const fileService = new FileService({
  prefix: "user",
  fileRepository,
  objectStorage,
});

Generated keys use this shape:

{prefix}/{uuid}/{filename}

4. Expose application routes

The supplied handlers use standard Request and Response objects:

import { deleteFileHandler, uploadFileHandler } from "gw-file/server";

export async function POST(request: Request) {
  const userId = await getOptionalUserId(request);
  return uploadFileHandler({ fileService })(request, { userId });
}

export async function DELETE(
  _request: Request,
  context: { params: Promise<{ fileId: string }> },
) {
  const { fileId } = await context.params;
  const userId = await getOptionalUserId(_request);

  return deleteFileHandler({ fileRepository })({ userId, fileId })();
}

deleteFileHandler deletes only the repository record. If your product must also delete S3 data, look up the file key and call objectStorage.delete(key) as part of your application-owned deletion workflow.

Browser uploads

import { FileUploader } from "gw-file/client";

type AppFile = {
  id: string;
  key: string;
  name: string;
  type?: string;
  size?: number;
};

const uploader = new FileUploader<AppFile>("/api/files");
const result = await uploader.uploadFile(file, {
  metadata: { purpose: "attachment" },
  convertToWebp: true,
});

if (result.isErr) {
  console.error(result.error);
} else {
  console.log(result.value);
}

convertToWebp uses browser image and canvas APIs. It is not available in a server-only runtime.

FileUploader returns the gw-result value produced by each request. With gw-result 0.3, failed HTTP responses—including S3 XML errors—are returned as HttpException values instead of being replaced by a generic upload error.

Media metadata

generateMetadata(blob) extracts { width, height } from images and { width, height, poster } from videos. Other file types resolve to an empty object.

Pass uploadBlob when generated video posters should be uploaded instead of embedded as a data URL:

import { generateMetadata } from "gw-file/client";

const metadata = await generateMetadata(file, {
  uploadBlob: async (posterBlob) => {
    const result = await uploader.uploadBlob(posterBlob, "poster.jpg");

    if (result.isErr) {
      throw result.error;
    }

    return { src: cdn(result.value.key) };
  },
});

Direct server uploads

const result = await fileService.put(buffer, {
  name: "report.pdf",
  type: "application/pdf",
  size: buffer.byteLength,
  userId,
});

For a Blob, use fileService.putBlob(blob, params).

CDN URLs

import { createCDN } from "gw-file";

const cdn = createCDN("https://cdn.example.com");

cdn("user/id/photo.jpg");
// https://cdn.example.com/user/id/photo.jpg

cdn("user/id/photo.jpg", { width: 640 });
// https://cdn.example.com/user/id/photo.jpg?w=640

Responsive images

import { createResponsiveImage } from "gw-file/client";

const ResponsiveImage = createResponsiveImage({
  cdnOrigin: "https://cdn.example.com",
  defaultProps: { loading: "lazy" },
  sizes: [320, 640, 960, 1280],
});

export function Avatar({ file }: { file: { key: string } }) {
  return <ResponsiveImage file={file} alt="Profile" width={320} height={320} />;
}

Omit sizes to use the package defaults. GIF images are returned without a generated srcSet so their animation is preserved.

AWS permissions

Grant only the operations used by the application. A typical policy for the default user prefix is:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::YOUR_BUCKET/user/*"
    }
  ]
}
  • Presigned browser uploads require s3:PutObject.
  • ObjectStorage.find requires s3:GetObject.
  • ObjectStorage.head normally requires s3:GetObject.
  • ObjectStorage.delete requires s3:DeleteObject.

Bucket policy, KMS encryption, VPC endpoint policy, and organization service-control policies can further restrict these operations.

S3 CORS

Browser-to-S3 uploads need a bucket CORS rule. Restrict origins in production:

[
  {
    "AllowedOrigins": ["https://app.example.com"],
    "AllowedMethods": ["PUT"],
    "AllowedHeaders": ["content-type", "x-amz-*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3600
  }
]

CORS errors happen before or while the browser accesses the response. An S3 XML response with HTTP 403 generally points to credentials, IAM or bucket policy, an expired URL, a region mismatch, or a signature mismatch.

Environment variables

These names are examples; gw-file does not read application-specific bucket variables itself:

AWS_REGION=ap-northeast-2
AWS_S3_BUCKET_NAME=your-bucket
AWS_ACCESS_KEY_ID=use-a-secret-store-in-production
AWS_SECRET_ACCESS_KEY=use-a-secret-store-in-production

The AWS SDK reads its standard credential and region variables. Keep .env files out of source control and rotate exposed access keys immediately.

Public API summary

gw-file

  • createCDN(origin)
  • CDN

gw-file/client

  • FileUploader
  • generateMetadata
  • createResponsiveImage
  • ResponsiveImage
  • generateSrcSet

gw-file/server

  • ObjectStorage
  • FileService
  • FileRepository
  • uploadFileHandler
  • deleteFileHandler

Generated declaration files include JSDoc for detailed parameters and behavior.

Deployment checklist

  1. Set AWS_REGION explicitly or pass region to ObjectStorage.
  2. Provide credentials through an IAM role or secure deployment secret.
  3. Grant the minimum S3 actions for the configured key prefix.
  4. Configure S3 CORS for every browser origin that performs direct uploads.
  5. Configure a CDN or object URL strategy for reading uploaded files.
  6. Decide how abandoned repository references and orphaned S3 objects are cleaned up.
  7. Run npm run check and inspect npm pack --dry-run before publishing.

Version 0.1.6

  • Removed the package-owned ap-northeast-2 default; AWS region resolution is now explicit or delegated to the AWS SDK provider chain.
  • Added complete setup, IAM, CORS, lifecycle, and deployment documentation.
  • Added JSDoc to the public API.
  • Corrected package repository and module metadata.
  • Upgraded gw-result to 0.3.x and uuid to 14.x.

License

MIT