Skip to content

Repository files navigation

@smooai/file — Trust the bytes, not the extension

npm PyPI crates.io NuGet

Smoo AI license CI

downloads TypeScript Python Rust Go .NET

Features  ·  Install  ·  Language status  ·  Usage  ·  Platform


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
Loading

📦 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
// }

(back to usage)

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');

(back to usage)

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

(back to usage)

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

(back to usage)

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,
});

(back to usage)

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.
});

(back to usage)

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.

(back to usage)

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',
        },
    ],
});

(back to usage)

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,
});

(back to usage)

🔧 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

(back to top)


Built by Smoo AI — AI built into every product.

About

Multi-language file handling library (TypeScript, Python, Rust, Go) — unified interface for local filesystem, S3, URLs, and FormData with stream-first design, lazy byte loading, and intelligent type detection.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages