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

@putnami/storage

v0.3.0

Published

Object storage integration with pluggable backends (S3, local filesystem, in-memory), bucket declarations, and presigned URLs

Downloads

546

Readme

@putnami/storage

Object storage: declared buckets with per-bucket constraints, signed URLs for browser-direct transfer, and backends that answer one shared contract.

import { application } from '@putnami/application';
import { Bucket, storage, storagePlugin } from '@putnami/storage';

export const AvatarsBucket = Bucket('avatars', {
  maxFileSize: '5mb',
  allowedMimeTypes: ['image/png', 'image/jpeg'],
  public: false,
});

export const app = () => application().use(storagePlugin());

const avatars = await storage(AvatarsBucket.bucketName);
await avatars.put('user-1.png', file);
const { url, expiresAt } = await avatars.signedDownloadUrl('user-1.png');

Mental model

  • A bucket is a declaration: name, access level, size limit, MIME allowlist, optional retention. Declaring it registers it and emits it to the project's infra requirements.
  • A client is what application code uses. It resolves a backend for its bucket and enforces the bucket's constraints.
  • A backend is the substitutable part: memory for tests, filesystem for local development, S3, or the remote storage service.

One contract, four backends

Every backend answers the same questions the same way:

| Operation | Contract | |-----------|----------| | get of a missing object or bucket | returns null | | delete of a missing key | no-op | | copy from a missing source | throws StorageError with code NOT_FOUND | | put over an existing key | replaces it | | zero-byte object | round-trips with size === 0 | | list → lastModified | a Date, on every backend |

That table is not documentation of intent — it is the shared runStorageContract suite, and it runs against the memory backend, the file backend, and the remote backend over the HTTP shape the remote backend documents.

The S3 backend does not run the shared suite yet. It has its own unit tests plus a live integration suite gated behind PUTNAMI_S3_TEST_ENDPOINT, which covers a subset of the table above by hand. Wiring it in needs the shared suite to stop assuming a fixed bucket name and a destructive reset, so the gap is recorded rather than papered over.

Keys and signed URLs

Keys are validated before any I/O, on every key-taking method: empty keys, null bytes, absolute paths, and any .. segment are rejected, and each key segment is percent-encoded when it becomes a URL. A signed URL is bound to its method, bucket, and key, defaults to a 15-minute expiry, and is capped at 7 days — an upload token does not authorize a download, and a forged or expired token is refused.

Backends are interfaces, not SDKs

No cloud SDK is a dependency. The S3 backend is built on the runtime's own S3 client; the remote backend is plain fetch with a bearer token, and the service mints signed URLs server-side.

Maintained contract

This package is stable in the workspace support catalog. It owns one public promise:

  • Object storage, with the one-bucket-contract ADR. Code written against a bucket behaves the same on memory, filesystem, S3, and the remote service; declared types are the runtime types on every backend; and a user-supplied key cannot reach an object the bucket does not own.

Before v1.0.0, follow the workspace compatibility policy: a breaking change is allowed in a minor 0.x release when the migration is documented. Do not infer strict compatibility between every 0.x minor.

Documentation

See also

License

FSL-1.1-MIT