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

@smooai/file

v2.2.20

Published

File operations that don't lie — magic-byte MIME detection catches spoofed extensions, size + content validation is built in, and presigned S3 uploads are one call away. Stream-first so large uploads don't blow your memory.

Readme


A file abstraction that trusts the bytes, not the extension. Built for backends that take uploads from the open internet: magic-byte MIME detection, size and content validation, and presigned S3 uploads — one File API over local files, URLs, streams, S3 objects, and browser uploads, ported natively to five languages: TypeScript, Python, Rust, Go, and .NET.

✨ Features

| | Capability | What you get | | --- | ---------------------------------------------------------- | -------------------------------------------------------------------- | | 🔒 | Trust the bytes | Magic-byte MIME detection catches spoofed uploads, in all five ports | | ☁️ | S3 in one call | Upload, download, signed URLs, presigned uploads with size caps | | 🌐 | One API, many sources | Local · URL · bytes · stream · S3 · multipart, one File type | | 🌊 | Lazy streaming | Streams ingest without full buffering — all five ports | | 📝 | Rich metadata | Detected MIME, size, timestamps, hash — on one object |

🔒 Trust the bytes, not the extension

Magic-byte MIME detection catches spoofed uploads. A .php renamed to avatar.png fails validation because the bytes disagree with the claim.

  • Magic-byte detection across 100+ file types — in every port
  • Validation fails with a typed error when the client-claimed MIME disagrees with the bytes, when the file is oversize, or when the type isn't allowed
  • One validate() call; the errors map cleanly to HTTP 400

The error shape is idiomatic per language: TypeScript and Python throw FileContentMismatchError / FileSizeError / FileMimeError (all extending FileValidationError); .NET throws the same three as exceptions; Rust has a FileValidationError enum; Go returns one FileValidationError with a Kind field. Because catch-by-type doesn't survive that, all five also carry the same kind string (size · mime · content_mismatch) — the discriminant portable code branches on.

☁️ S3 in one call

  • Stream a file into S3 and pull S3 objects back through the same validation pipeline — all five ports
  • Presigned upload URLs with maxSize baked into the signature, so oversized uploads are rejected by S3 before they hit you — all five ports
  • Signed download URLs — all five ports
  • In .NET, S3 support is the separate SmooAI.File.S3 package, so .NET consumers who only need detection/validation skip the AWS SDK dependency (the TypeScript package currently ships its AWS SDK dependencies unconditionally)

🌐 One API, many sources

Local filesystem, URL download, S3 object, raw bytes, streams, multipart form uploads, or a browser File/Blob — all resolve to the same File instance with the same validation and metadata surface.

🌊 Lazy streaming

All five ports ingest streams lazily — only a 64 KiB head is read for MIME sniffing, and the rest of the bytes flow through without buffering the whole file (createFromStreamLazy · from_stream(lazy=True) · from_stream_lazy · NewFromStreamLazy · CreateFromStreamLazyAsync). The semantics are identical because all five test suites load one shared fixture, spec/lazy-stream-contract.json — head size, what stays lazy, what triggers a full read, and what a read after a full iteration does. The honest per-port breakdown is in the capability matrix.

📝 Rich metadata

File name, real (detected) MIME type, size, created/modified timestamps, hash/checksum, source type — all on one object.

How it fits together

%%{init: {'theme':'base','themeVariables':{
  'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
  'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
  'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
  SRC["local file · URL · bytes<br/>stream · S3 · multipart · Blob"] --> F
  subgraph F["File"]
    D["magic-byte MIME detection"] --> V["validate()<br/>size · allowed types · claim vs bytes"]
    V --> M["metadata<br/>name · MIME · size · hash"]
  end
  F --> OUT["save · S3 upload · FormData<br/>base64 · signed URLs"]

  classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
  classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
  class V warm
  class SRC,OUT teal

📦 Install

| Language | Package | Install | | ----------- | ----------------------------------------------------------------- | ------------------------------------------ | | TypeScript | @smooai/file | pnpm add @smooai/file | | Python | smooai-file | pip install smooai-file | | Rust | smooai-file | cargo add smooai-file | | Go | github.com/SmooAI/file/go/file/v2 | go get github.com/SmooAI/file/go/file/v2 | | .NET (core) | SmooAI.File | dotnet add package SmooAI.File | | .NET (S3) | SmooAI.File.S3 | dotnet add package SmooAI.File.S3 |

Language-specific source lives in src/ (TypeScript), python/, rust/, go/, and dotnet/. The .NET port uses Mime-Detective for magic-byte MIME sniffing.

Five languages, honestly

Every port carries the core promise: magic-byte MIME detection, typed size/mime/content-mismatch validation, rich metadata, S3 upload + presigned/signed URLs, and creation from local files, URLs, bytes, streams, S3, and multipart uploads. Beyond that the surfaces are uneven — here is the verified breakdown, so you know before you pick one:

| Capability | TypeScript | Python | Rust | Go | .NET | | --------------------------------------------------- | :--------: | :----: | :--: | :-: | :----------------------------------------------------------: | | Magic-byte detection + typed validate() | ✅ | ✅ | ✅ | ✅ | ✅ | | Lazy streaming ingest | ✅ | ✅ | ✅ | ✅ | ✅ | | Chunked reads (iter_bytes) | ✅ | ✅ | ✅ | ✅ | ✅ (OpenReadStream) | | pipeTo a writable stream | ✅ | ❌ | ❌ | ❌ | ❌ | | append / prepend / truncate | ✅ | ✅ | ❌ | ✅ | ❌ | | exists / isReadable / isWritable / getStats | ✅ | ✅ | ❌ | ❌ | ❌ | | Upload to S3 + presigned upload URL | ✅ | ✅ | ✅ | ✅ | ✅ (S3 pkg) | | Signed download URL | ✅ | ✅ | ✅ | ✅ | ✅ | | saveToS3 / moveToS3 (returns new File) | ✅ | ✅ | ❌ | ❌ | ❌ | | downloadFromS3 (S3 → local path) | ❌ | ✅ | ❌ | ❌ | ❌ | | setMetadata | ✅ | ✅ | ✅ | ✅ | ✅ |

On downloadFromS3: only Python writes an S3 object to a local path. Rust's download_from_s3 is a plain alias for from_s3, and Go's replaces the receiver in place — neither touches the filesystem, so neither belongs in this row. TypeScript composes the same thing today with (await File.createFromS3(b, k)).saveToFile(p).

Same semantics where a capability exists in two ports; each port is written idiomatically for its ecosystem and carries its own test suite.

Errors are portable by kind, not by type. TypeScript, Python and .NET raise three distinct classes; Rust collapses them into one enum; Go returns one struct with a Kind field — so catch (e) { if (e instanceof FileSizeError) } has no equivalent in Rust or Go. Every port now also carries the same kind discriminant ("size" · "mime" · "content_mismatch"), and that is what portable code branches on. The values and the fields each one carries are pinned by spec/error-taxonomy.json, which all five test suites load.

🚀 Usage

TypeScript below; the same shapes exist per the matrix above in Python, Rust, Go, and .NET.

Jump to a pattern:

Basic usage

import File from '@smooai/file';

// Create a file from a local path
const file = await File.createFromFile('path/to/file.txt');

// Read file contents
const content = await file.readFileString();
console.log(content);

// Get file metadata
console.log(file.metadata);
// {
//   name: 'file.txt',
//   mimeType: 'text/plain',
//   size: 1234,
//   extension: 'txt',
//   path: 'path/to/file.txt',
//   lastModified: Date,
//   createdAt: Date
// }

Reading and saving

import File from '@smooai/file';

// Create a file from a URL
const file = await File.createFromUrl('https://example.com/file.zip');

// Pipe to a destination stream
await file.pipeTo(someWritableStream);

// Read as bytes (note: buffers the content — the TS port has no lazy streaming yet)
const bytes = await file.readFileBytes();

// Save to filesystem
const { original, newFile } = await file.saveToFile('downloads/file.zip');

S3 integration

import File from '@smooai/file';

// Create from S3
const file = await File.createFromS3('my-bucket', 'path/to/file.jpg');

// Upload to S3
await file.uploadToS3('my-bucket', 'remote/file.jpg');

// Save to S3 (creates new file instance)
const { original, newFile } = await file.saveToS3('my-bucket', 'remote/file.jpg');

// Move to S3 (deletes local file if source was local)
const s3File = await file.moveToS3('my-bucket', 'remote/file.jpg');

// Generate signed URL
const signedUrl = await s3File.getSignedUrl(3600); // URL expires in 1 hour

File type detection

import File from '@smooai/file';

const file = await File.createFromFile('document.xml');

// Get file type information (detected via magic numbers)
console.log(file.mimeType); // 'application/xml'
console.log(file.extension); // 'xml'

// File type is automatically detected from:
// - Magic numbers (via file-type)
// - MIME type headers
// - File extension
// - Custom detectors

FormData support

import File from '@smooai/file';

const file = await File.createFromFile('document.pdf');

// Convert to FormData for uploads
const formData = await file.toFormData('document');

// Use with fetch or other HTTP clients
await fetch('https://api.example.com/upload', {
    method: 'POST',
    body: formData,
});

Web File / Blob (Hono, Next.js, Browser)

import File from '@smooai/file';

// Hono multipart route
app.post('/upload', async (c) => {
    const form = await c.req.formData();
    const webFile = form.get('file') as globalThis.File;

    // Preserves the web File's name and type hints.
    const file = await File.createFromWebFile(webFile);
    // …validate, upload, etc.
});

Validation (size, mime, content-vs-claim)

import File, { FileValidationError } from '@smooai/file';

const file = await File.createFromWebFile(webFile);

try {
    await file.validate({
        maxSize: 5 * 1024 * 1024, // 5MB
        allowedMimes: ['image/png', 'image/jpeg', 'image/webp'],
        expectedMimeType: webFile.type, // compares magic-byte detection vs claimed Content-Type
    });
} catch (err) {
    if (err instanceof FileValidationError) {
        // FileSizeError | FileMimeError | FileContentMismatchError — map to HTTP 400
        throw new HTTPException(400, { message: err.message });
    }
    throw err;
}

expectedMimeType is the primary defense against mime-spoofing: a .php file uploaded with Content-Type: image/png will fail because magic-byte detection doesn't match the claim.

Base64 encoding (email attachments, data URLs)

import File from '@smooai/file';

const file = await File.createFromUrl('https://s3.example.com/invoice.pdf');

await sendEmail({
    attachments: [
        {
            filename: 'invoice.pdf',
            content: await file.toBase64(),
            encoding: 'base64',
        },
    ],
});

Presigned upload URL (server signs, client uploads direct to S3)

import File from '@smooai/file';

// Server issues a time-limited signed URL the client uploads bytes to directly.
// `maxSize` is baked into the signature so oversized uploads are rejected by S3.
const url = await File.createPresignedUploadUrl({
    bucket: Resource.Bucket.name,
    key: `avatars/${userId}.png`,
    contentType: 'image/png',
    expiresIn: 600,
    maxSize: 2 * 1024 * 1024,
});

🔧 Built with

🧩 Part of Smoo AI

@smooai/file is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

🤝 Contributing

Contributions are welcome. This project uses changesets to manage versions and releases.

Development workflow

  1. Fork the repository

  2. Create your branch (git checkout -b amazing-feature)

  3. Make your changes (the five ports live in src/, python/, rust/, go/, dotnet/)

  4. Add a changeset to document them:

    pnpm changeset

    You'll be prompted to choose a version bump (patch, minor, or major) and describe the change.

  5. Commit your changes (git commit -m 'Add some amazing feature')

  6. Push to the branch (git push origin feature/amazing-feature)

  7. Open a pull request — reference any related issues in the description

The maintainers will review your PR and may request changes before merging.

📄 License

MIT — see LICENSE.

📬 Contact

Brent Rager

Smoo GitHub: https://github.com/SmooAI