← All @molecule/* packages · App templates

@molecule/api-uploads-filesystem

Provider bond · storage · API (Node) · v1.0.1 · Apache-2.0

File system upload provider for molecule.dev.

npm install @molecule/api-uploads-filesystem

npm · Source on GitHub · Implements @molecule/api-uploads

How it works

@molecule/api-uploads-filesystem is a provider bond on the API (Node) side: it implements the storage core interface (@molecule/api-uploads) with a concrete vendor or library behind it.

Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.

Works with: @molecule/api-bond, @molecule/api-i18n, @molecule/api-uploads

Reference

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

File system upload provider for molecule.dev.

Handles file uploads to the local file system.

Note: For your files to remain on disk indefinitely, your server needs a permanent file system. Many "serverless" deployments have transient file systems, meaning that files written to them will not remain.

Type

provider

Installation

npm install @molecule/api-uploads-filesystem @molecule/api-bond @molecule/api-i18n @molecule/api-uploads uuid

API

Interfaces

File

Filesystem-uploaded file extending the core UploadedFile with the active write stream.

interface File extends UploadedFile {
  /**
   * The stream being written to disk.
   */
  upload?: NodeJS.WritableStream

  /**
   * Aborts the in-progress write: destroys the write stream (so `'finish'` never
   * fires and `uploadPromise` cannot resolve as success), removes the partially
   * written file from disk, and rejects `uploadPromise` with an `UploadAbortedError`.
   * Set internally by `upload()`; not intended for external use — call the exported
   * `abortUpload()` instead.
   */
  abort?: () => void
}

FileInfo

Information about a file being uploaded.

This interface is provider-agnostic. Implementations using busboy or other multipart parsers should adapt to this interface.

interface FileInfo {
  /**
   * The original filename - e.g., `some-image.jpg`.
   */
  filename: string
  /**
   * The file's encoding - e.g., `7bit`, `binary`.
   */
  encoding: string
  /**
   * The file's MIME type - e.g., `image/jpeg`.
   */
  mimeType: string
}

UploadedFile

Properties describing an uploading/uploaded file.

interface UploadedFile {
  /**
   * The unique file identifier.
   */
  id: string
  /**
   * The file's fieldname from the form.
   * Used as the key for the file within `req.files` - e.g., `req.files[fieldname] = file`.
   */
  fieldname: string
  /**
   * The original filename - e.g., `some-image.jpg`.
   */
  filename: string
  /**
   * The file's encoding - e.g., `binary`.
   */
  encoding: string
  /**
   * The file's mimetype - e.g., `image/jpeg`.
   */
  mimetype: string
  /**
   * The file's size in bytes.
   */
  size: number
  /**
   * The source stream (available during upload).
   */
  stream?: NodeJS.ReadableStream
  /**
   * A promise that resolves when the upload completes.
   */
  uploadPromise?: Promise<void>
  /**
   * Whether the upload has completed.
   */
  uploaded: boolean
  /**
   * The URL/location of the uploaded file (if available).
   */
  location?: string
}

Functions

abortUpload(file)

Aborts an in-progress file upload. Removes stream listeners, destroys the write stream, deletes the partially-written file from disk, and rejects the file's uploadPromise with an UploadAbortedError.

function abortUpload(file: File): void
  • file — The File object returned by upload().

deleteFile(id)

Deletes a previously uploaded file from the local file system by its UUID.

function deleteFile(id: string): Promise<void>
  • id — The UUID file identifier (also the filename on disk).

Returns: A promise that resolves when the file is deleted.

getFileStream(id)

Opens a read stream for a previously uploaded file.

function getFileStream(id: string): fs.ReadStream
  • id — The UUID file identifier.

Returns: A ReadStream for the file at uploadPath/id.

upload(fieldname, stream, info, onError)

Streams a file upload to the local file system. Creates a UUID-named file in uploadPath and pipes the readable stream into it. Tracks upload progress via file.size.

function upload(
  fieldname: string,
  stream: NodeJS.ReadableStream,
  info: FileInfo,
  onError: (error: Error) => void,
): File
  • fieldname — The form field name this file was submitted under.
  • stream — The readable stream of the uploaded file data.
  • info — File metadata (filename, encoding, mimeType) from the multipart parser.
  • onError — Callback invoked if the write stream errors or the stream exceeds its size limit.

Returns: A File object with the upload's ID, metadata, and a uploadPromise that resolves on completion.

Constants

provider

File system upload provider implementing UploadProvider. Stores files in the local directory specified by FILE_UPLOAD_PATH.

const provider: UploadProvider

uploadPath

The absolute path where uploaded files are stored. Reads from FILE_UPLOAD_PATH env var, defaults to 'uploads' in the CWD.

const uploadPath: string

Core Interface

Implements @molecule/api-uploads interface.

Bond Wiring

Setup function to register this provider with the core interface:

import { setProvider } from '@molecule/api-uploads'
import { provider } from '@molecule/api-uploads-filesystem'

export function setupUploadsFilesystem(): void {
  setProvider(provider)
}

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-bond ^1.0.1
  • @molecule/api-i18n ^1.0.1
  • @molecule/api-uploads ^1.0.1

Runtime Dependencies

  • @molecule/api-bond
  • @molecule/api-i18n
  • @molecule/api-uploads
  • uuid

abortUpload() rejects the file's uploadPromise with UploadAbortedError (from @molecule/api-uploads) — it never resolves as success and never calls the upload() call's onError. This is identical to the @molecule/api-uploads-s3 bond's abort behavior; see that core package's AbortHandler remarks for the full cross-provider contract.

  • Blocked MIME types: uploads declaring text/html, application/xhtml+xml, JavaScript types, image/svg+xml, or XML are REJECTED at upload() as a stored-XSS defense (same list as the S3 bond). The rejection is reported through the onError callback and the returned file has uploaded: false and NO uploadPromise — handle onError; don't await uploadPromise alone. Rasterize SVGs client-side if the app needs vector-source uploads.
  • Import-time setup: FILE_UPLOAD_PATH is read ONCE at module import and the directory is created immediately — importing this package throws an actionable error if the path is not writable, and changing the env var later in the same process has no effect (restart required).

E2E Tests

Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:

  • Uploading a valid file through the UI shows progress/confirmation and the file appears in the user's file list.
  • The uploaded content is retrievable: opening/downloading it returns the same content (an uploaded image actually renders).
  • A disallowed file type is rejected with a visible error and does NOT appear in the list.
  • An over-the-cap file is rejected cleanly (visible error, no partial phantom entry).
  • Ownership is enforced: a second signed-in user cannot retrieve the first user's file by its id/URL (404 — not the file).
  • Deleting a file removes it from the list, and it stays gone (and unretrievable) after a full reload.