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
FileAPI over local files, URLs, streams, S3 objects, and browser uploads, ported natively to five languages: TypeScript, Python, Rust, Go, and .NET.
| 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 |
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.
- Stream a file into S3 and pull S3 objects back through the same validation pipeline — all five ports
- Presigned upload URLs with
maxSizebaked 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.S3package, so .NET consumers who only need detection/validation skip the AWS SDK dependency (the TypeScript package currently ships its AWS SDK dependencies unconditionally)
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.
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.
File name, real (detected) MIME type, size, created/modified timestamps, hash/checksum, source type — all on one object.
%%{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
| 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.
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'sdownload_from_s3is a plain alias forfrom_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.
TypeScript below; the same shapes exist per the matrix above in Python, Rust, Go, and .NET.
Jump to a pattern:
- Basic usage
- Reading and saving
- S3 integration
- File type detection
- FormData support
- Web File / Blob (Hono, Next.js, Browser)
- Validation (size, mime, content-vs-claim)
- Base64 encoding
- Presigned upload URL
importFilefrom'@smooai/file';// Create a file from a local pathconstfile=awaitFile.createFromFile('path/to/file.txt');// Read file contentsconstcontent=awaitfile.readFileString();console.log(content);// Get file metadataconsole.log(file.metadata);// {// name: 'file.txt',// mimeType: 'text/plain',// size: 1234,// extension: 'txt',// path: 'path/to/file.txt',// lastModified: Date,// createdAt: Date// }importFilefrom'@smooai/file';// Create a file from a URLconstfile=awaitFile.createFromUrl('https://example.com/file.zip');// Pipe to a destination streamawaitfile.pipeTo(someWritableStream);// Read as bytes (note: buffers the content — the TS port has no lazy streaming yet)constbytes=awaitfile.readFileBytes();// Save to filesystemconst{ original, newFile }=awaitfile.saveToFile('downloads/file.zip');importFilefrom'@smooai/file';// Create from S3constfile=awaitFile.createFromS3('my-bucket','path/to/file.jpg');// Upload to S3awaitfile.uploadToS3('my-bucket','remote/file.jpg');// Save to S3 (creates new file instance)const{ original, newFile }=awaitfile.saveToS3('my-bucket','remote/file.jpg');// Move to S3 (deletes local file if source was local)consts3File=awaitfile.moveToS3('my-bucket','remote/file.jpg');// Generate signed URLconstsignedUrl=awaits3File.getSignedUrl(3600);// URL expires in 1 hourimportFilefrom'@smooai/file';constfile=awaitFile.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 detectorsimportFilefrom'@smooai/file';constfile=awaitFile.createFromFile('document.pdf');// Convert to FormData for uploadsconstformData=awaitfile.toFormData('document');// Use with fetch or other HTTP clientsawaitfetch('https://api.example.com/upload',{method: 'POST',body: formData,});importFilefrom'@smooai/file';// Hono multipart routeapp.post('/upload',async(c)=>{constform=awaitc.req.formData();constwebFile=form.get('file')asglobalThis.File;// Preserves the web File's name and type hints.constfile=awaitFile.createFromWebFile(webFile);// …validate, upload, etc.});importFile,{FileValidationError}from'@smooai/file';constfile=awaitFile.createFromWebFile(webFile);try{awaitfile.validate({maxSize: 5*1024*1024,// 5MBallowedMimes: ['image/png','image/jpeg','image/webp'],expectedMimeType: webFile.type,// compares magic-byte detection vs claimed Content-Type});}catch(err){if(errinstanceofFileValidationError){// FileSizeError | FileMimeError | FileContentMismatchError — map to HTTP 400thrownewHTTPException(400,{message: err.message});}throwerr;}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.
importFilefrom'@smooai/file';constfile=awaitFile.createFromUrl('https://s3.example.com/invoice.pdf');awaitsendEmail({attachments: [{filename: 'invoice.pdf',content: awaitfile.toBase64(),encoding: 'base64',},],});importFilefrom'@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.consturl=awaitFile.createPresignedUploadUrl({bucket: Resource.Bucket.name,key: `avatars/${userId}.png`,contentType: 'image/png',expiresIn: 600,maxSize: 2*1024*1024,});- TypeScript · Node.js File System API · AWS SDK v3
- file-type for magic number-based MIME type detection (TypeScript port)
- Mime-Detective for MIME sniffing (.NET port)
- @smooai/fetch for URL downloads
- @smooai/logger for structured logging
@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.
- 🧰 More open source from Smoo AI — smoo.ai/open-source
- 🧩 Sibling packages — @smooai/fetch, @smooai/logger, @smooai/config, smooth-operator, smooth
Contributions are welcome. This project uses changesets to manage versions and releases.
Fork the repository
Create your branch (
git checkout -b amazing-feature)Make your changes (the five ports live in
src/,python/,rust/,go/,dotnet/)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.
Commit your changes (
git commit -m 'Add some amazing feature')Push to the branch (
git push origin feature/amazing-feature)Open a pull request — reference any related issues in the description
The maintainers will review your PR and may request changes before merging.
MIT — see LICENSE.
Brent Rager
Smoo GitHub: https://github.com/SmooAI
Built by Smoo AI — AI built into every product.
