Repository files navigation

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

npmPyPIcrates.ioNuGet

Smoo AIlicenseCI

downloadsTypeScriptPythonRustGo.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

CapabilityWhat you get
🔒Trust the bytesMagic-byte MIME detection catches spoofed uploads, in all five ports
☁️S3 in one callUpload, download, signed URLs, presigned uploads with size caps
🌐One API, many sourcesLocal · URL · bytes · stream · S3 · multipart, one File type
🌊Lazy streamingStreams ingest without full buffering — all five ports
📝Rich metadataDetected 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

LanguagePackageInstall
TypeScript@smooai/filepnpm add @smooai/file
Pythonsmooai-filepip install smooai-file
Rustsmooai-filecargo add smooai-file
Gogithub.com/SmooAI/file/go/file/v2go get github.com/SmooAI/file/go/file/v2
.NET (core)SmooAI.Filedotnet add package SmooAI.File
.NET (S3)SmooAI.File.S3dotnet 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:

CapabilityTypeScriptPythonRustGo.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

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

(back to usage)

Reading and saving

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

(back to usage)

S3 integration

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 hour

(back to usage)

File type detection

importFilefrom'@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 detectors

(back to usage)

FormData support

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

(back to usage)

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

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

(back to usage)

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

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.

(back to usage)

Base64 encoding (email attachments, data URLs)

importFilefrom'@smooai/file';constfile=awaitFile.createFromUrl('https://s3.example.com/invoice.pdf');awaitsendEmail({attachments: [{filename: 'invoice.pdf',content: awaitfile.toBase64(),encoding: 'base64',},],});

(back to usage)

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

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

(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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

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

npmPyPIcrates.ioNuGet

Smoo AIlicenseCI

downloadsTypeScriptPythonRustGo.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

CapabilityWhat you get
🔒Trust the bytesMagic-byte MIME detection catches spoofed uploads, in all five ports
☁️S3 in one callUpload, download, signed URLs, presigned uploads with size caps
🌐One API, many sourcesLocal · URL · bytes · stream · S3 · multipart, one File type
🌊Lazy streamingStreams ingest without full buffering — all five ports
📝Rich metadataDetected 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

LanguagePackageInstall
TypeScript@smooai/filepnpm add @smooai/file
Pythonsmooai-filepip install smooai-file
Rustsmooai-filecargo add smooai-file
Gogithub.com/SmooAI/file/go/file/v2go get github.com/SmooAI/file/go/file/v2
.NET (core)SmooAI.Filedotnet add package SmooAI.File
.NET (S3)SmooAI.File.S3dotnet 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:

CapabilityTypeScriptPythonRustGo.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

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

(back to usage)

Reading and saving

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

(back to usage)

S3 integration

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 hour

(back to usage)

File type detection

importFilefrom'@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 detectors

(back to usage)

FormData support

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

(back to usage)

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

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

(back to usage)

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

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.

(back to usage)

Base64 encoding (email attachments, data URLs)

importFilefrom'@smooai/file';constfile=awaitFile.createFromUrl('https://s3.example.com/invoice.pdf');awaitsendEmail({attachments: [{filename: 'invoice.pdf',content: awaitfile.toBase64(),encoding: 'base64',},],});

(back to usage)

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

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

(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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

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

npmPyPIcrates.ioNuGet

Smoo AIlicenseCI

downloadsTypeScriptPythonRustGo.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

CapabilityWhat you get
🔒Trust the bytesMagic-byte MIME detection catches spoofed uploads, in all five ports
☁️S3 in one callUpload, download, signed URLs, presigned uploads with size caps
🌐One API, many sourcesLocal · URL · bytes · stream · S3 · multipart, one File type
🌊Lazy streamingStreams ingest without full buffering — all five ports
📝Rich metadataDetected 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

LanguagePackageInstall
TypeScript@smooai/filepnpm add @smooai/file
Pythonsmooai-filepip install smooai-file
Rustsmooai-filecargo add smooai-file
Gogithub.com/SmooAI/file/go/file/v2go get github.com/SmooAI/file/go/file/v2
.NET (core)SmooAI.Filedotnet add package SmooAI.File
.NET (S3)SmooAI.File.S3dotnet 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:

CapabilityTypeScriptPythonRustGo.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

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

(back to usage)

Reading and saving

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

(back to usage)

S3 integration

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 hour

(back to usage)

File type detection

importFilefrom'@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 detectors

(back to usage)

FormData support

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

(back to usage)

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

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

(back to usage)

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

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.

(back to usage)

Base64 encoding (email attachments, data URLs)

importFilefrom'@smooai/file';constfile=awaitFile.createFromUrl('https://s3.example.com/invoice.pdf');awaitsendEmail({attachments: [{filename: 'invoice.pdf',content: awaitfile.toBase64(),encoding: 'base64',},],});

(back to usage)

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

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

(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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

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

npmPyPIcrates.ioNuGet

Smoo AIlicenseCI

downloadsTypeScriptPythonRustGo.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

CapabilityWhat you get
🔒Trust the bytesMagic-byte MIME detection catches spoofed uploads, in all five ports
☁️S3 in one callUpload, download, signed URLs, presigned uploads with size caps
🌐One API, many sourcesLocal · URL · bytes · stream · S3 · multipart, one File type
🌊Lazy streamingStreams ingest without full buffering — all five ports
📝Rich metadataDetected 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

LanguagePackageInstall
TypeScript@smooai/filepnpm add @smooai/file
Pythonsmooai-filepip install smooai-file
Rustsmooai-filecargo add smooai-file
Gogithub.com/SmooAI/file/go/file/v2go get github.com/SmooAI/file/go/file/v2
.NET (core)SmooAI.Filedotnet add package SmooAI.File
.NET (S3)SmooAI.File.S3dotnet 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:

CapabilityTypeScriptPythonRustGo.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

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

(back to usage)

Reading and saving

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

(back to usage)

S3 integration

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 hour

(back to usage)

File type detection

importFilefrom'@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 detectors

(back to usage)

FormData support

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

(back to usage)

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

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

(back to usage)

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

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.

(back to usage)

Base64 encoding (email attachments, data URLs)

importFilefrom'@smooai/file';constfile=awaitFile.createFromUrl('https://s3.example.com/invoice.pdf');awaitsendEmail({attachments: [{filename: 'invoice.pdf',content: awaitfile.toBase64(),encoding: 'base64',},],});

(back to usage)

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

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

(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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

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

npmPyPIcrates.ioNuGet

Smoo AIlicenseCI

downloadsTypeScriptPythonRustGo.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

CapabilityWhat you get
🔒Trust the bytesMagic-byte MIME detection catches spoofed uploads, in all five ports
☁️S3 in one callUpload, download, signed URLs, presigned uploads with size caps
🌐One API, many sourcesLocal · URL · bytes · stream · S3 · multipart, one File type
🌊Lazy streamingStreams ingest without full buffering — all five ports
📝Rich metadataDetected 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

LanguagePackageInstall
TypeScript@smooai/filepnpm add @smooai/file
Pythonsmooai-filepip install smooai-file
Rustsmooai-filecargo add smooai-file
Gogithub.com/SmooAI/file/go/file/v2go get github.com/SmooAI/file/go/file/v2
.NET (core)SmooAI.Filedotnet add package SmooAI.File
.NET (S3)SmooAI.File.S3dotnet 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:

CapabilityTypeScriptPythonRustGo.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

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

(back to usage)

Reading and saving

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

(back to usage)

S3 integration

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 hour

(back to usage)

File type detection

importFilefrom'@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 detectors

(back to usage)

FormData support

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

(back to usage)

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

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

(back to usage)

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

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.

(back to usage)

Base64 encoding (email attachments, data URLs)

importFilefrom'@smooai/file';constfile=awaitFile.createFromUrl('https://s3.example.com/invoice.pdf');awaitsendEmail({attachments: [{filename: 'invoice.pdf',content: awaitfile.toBase64(),encoding: 'base64',},],});

(back to usage)

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

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

(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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

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

npmPyPIcrates.ioNuGet

Smoo AIlicenseCI

downloadsTypeScriptPythonRustGo.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

CapabilityWhat you get
🔒Trust the bytesMagic-byte MIME detection catches spoofed uploads, in all five ports
☁️S3 in one callUpload, download, signed URLs, presigned uploads with size caps
🌐One API, many sourcesLocal · URL · bytes · stream · S3 · multipart, one File type
🌊Lazy streamingStreams ingest without full buffering — all five ports
📝Rich metadataDetected 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

LanguagePackageInstall
TypeScript@smooai/filepnpm add @smooai/file
Pythonsmooai-filepip install smooai-file
Rustsmooai-filecargo add smooai-file
Gogithub.com/SmooAI/file/go/file/v2go get github.com/SmooAI/file/go/file/v2
.NET (core)SmooAI.Filedotnet add package SmooAI.File
.NET (S3)SmooAI.File.S3dotnet 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:

CapabilityTypeScriptPythonRustGo.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

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

(back to usage)

Reading and saving

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

(back to usage)

S3 integration

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 hour

(back to usage)

File type detection

importFilefrom'@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 detectors

(back to usage)

FormData support

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

(back to usage)

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

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

(back to usage)

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

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.

(back to usage)

Base64 encoding (email attachments, data URLs)

importFilefrom'@smooai/file';constfile=awaitFile.createFromUrl('https://s3.example.com/invoice.pdf');awaitsendEmail({attachments: [{filename: 'invoice.pdf',content: awaitfile.toBase64(),encoding: 'base64',},],});

(back to usage)

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

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

(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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

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

npmPyPIcrates.ioNuGet

Smoo AIlicenseCI

downloadsTypeScriptPythonRustGo.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

CapabilityWhat you get
🔒Trust the bytesMagic-byte MIME detection catches spoofed uploads, in all five ports
☁️S3 in one callUpload, download, signed URLs, presigned uploads with size caps
🌐One API, many sourcesLocal · URL · bytes · stream · S3 · multipart, one File type
🌊Lazy streamingStreams ingest without full buffering — all five ports
📝Rich metadataDetected 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

LanguagePackageInstall
TypeScript@smooai/filepnpm add @smooai/file
Pythonsmooai-filepip install smooai-file
Rustsmooai-filecargo add smooai-file
Gogithub.com/SmooAI/file/go/file/v2go get github.com/SmooAI/file/go/file/v2
.NET (core)SmooAI.Filedotnet add package SmooAI.File
.NET (S3)SmooAI.File.S3dotnet 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:

CapabilityTypeScriptPythonRustGo.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

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

(back to usage)

Reading and saving

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

(back to usage)

S3 integration

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 hour

(back to usage)

File type detection

importFilefrom'@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 detectors

(back to usage)

FormData support

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

(back to usage)

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

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

(back to usage)

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

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.

(back to usage)

Base64 encoding (email attachments, data URLs)

importFilefrom'@smooai/file';constfile=awaitFile.createFromUrl('https://s3.example.com/invoice.pdf');awaitsendEmail({attachments: [{filename: 'invoice.pdf',content: awaitfile.toBase64(),encoding: 'base64',},],});

(back to usage)

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

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

(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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

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

npmPyPIcrates.ioNuGet

Smoo AIlicenseCI

downloadsTypeScriptPythonRustGo.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

CapabilityWhat you get
🔒Trust the bytesMagic-byte MIME detection catches spoofed uploads, in all five ports
☁️S3 in one callUpload, download, signed URLs, presigned uploads with size caps
🌐One API, many sourcesLocal · URL · bytes · stream · S3 · multipart, one File type
🌊Lazy streamingStreams ingest without full buffering — all five ports
📝Rich metadataDetected 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

LanguagePackageInstall
TypeScript@smooai/filepnpm add @smooai/file
Pythonsmooai-filepip install smooai-file
Rustsmooai-filecargo add smooai-file
Gogithub.com/SmooAI/file/go/file/v2go get github.com/SmooAI/file/go/file/v2
.NET (core)SmooAI.Filedotnet add package SmooAI.File
.NET (S3)SmooAI.File.S3dotnet 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:

CapabilityTypeScriptPythonRustGo.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

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

(back to usage)

Reading and saving

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

(back to usage)

S3 integration

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 hour

(back to usage)

File type detection

importFilefrom'@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 detectors

(back to usage)

FormData support

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

(back to usage)

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

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

(back to usage)

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

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.

(back to usage)

Base64 encoding (email attachments, data URLs)

importFilefrom'@smooai/file';constfile=awaitFile.createFromUrl('https://s3.example.com/invoice.pdf');awaitsendEmail({attachments: [{filename: 'invoice.pdf',content: awaitfile.toBase64(),encoding: 'base64',},],});

(back to usage)

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

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

(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