Overview
Purpose
The Upload Engine provides reliable, background-safe multipart file uploads for ONECMS.
It treats uploads as infrastructure, not UI interactions.
Uploads must continue correctly regardless of UI lifecycle, routing, or transient backend conditions.
If a consumer needs to understand how uploads work in order to use them correctly,
then the abstraction has failed.
What This Module Is
- A system capability, not a feature
- Event-driven and stateful via an explicit 9-state machine
- UI-agnostic and lifecycle-safe
- Designed to be boring, predictable, and resilient
Uploads are modeled as long-lived jobs with explicit state, not as callbacks tied to components.
What This Module Is Not
The Upload Engine is not:
- A UI component or hook
- A file management system
- A domain workflow engine
- A backend cleanup mechanism
It does not create CMS entities, decide business actions, expose upload internals to UI, or guarantee immediate storage cleanup.
Public Surface
The public API is defined by index.ts (the barrel export):
export { UploadEngine } from './UploadEngine';
export type {
UploadContext,
UploadMeta,
StartUploadOptions,
UploadSessionSnapshot,
UploadEvent,
UploadCompletionHandler,
UploadSessionId,
} from './UploadTypes';
Everything else is private implementation detail.
See API Reference for full method signatures and types.
Internal Architecture
The Upload Engine is composed of 12 source files organized into three layers:

Module Inventory
| Module | File | Role |
|---|---|---|
UploadEngineImpl |
UploadEngine.ts |
Singleton orchestrator — the only public entry point |
UploadSession |
UploadSession.ts |
Per-upload lifecycle owner — holds state, enforces transitions |
UploadWorker |
UploadWorker.ts |
Per-session executor — runs the chunk upload loop |
UploadQueue |
UploadQueue.ts |
FIFO concurrency scheduler — limits parallel sessions |
UploadPlanner |
UploadPlanner.ts |
Pure chunk math — byte boundaries, windowing |
UploadStateMachine |
UploadStateMachine.ts |
State transition table — STATE_TRANSITIONS map |
UploadService |
UploadService.ts |
Backend HTTP adapter — init, presigned URLs, complete |
UploadPersistence |
UploadPersistence.ts |
sessionStorage adapter — tenant-scoped persistence |
RetryPolicy |
RetryPolicy.ts |
Retry rules — category-based, linear backoff |
UploadEvents |
UploadEvents.ts |
Event bridge — emitUploadEvent() publishes to Signal bus |
UploadTypes |
UploadTypes.ts |
All public and internal type definitions |
index.ts |
index.ts |
Barrel export — defines public surface |
Key Constants
| Constant | Value | Defined In |
|---|---|---|
MAX_PARALLEL_UPLOADS |
5 | UploadEngine.ts |
DEFAULT_CHUNK_SIZE |
5 * 1024 * 1024 (5 MB) |
UploadEngine.ts |
DEFAULT_WINDOW_SIZE |
5 (chunks) | UploadEngine.ts |
PARALLEL_CHUNK_LIMIT |
5 | UploadWorker.ts |
External Integration Points
The Upload Engine integrates with the broader application through a 4-layer boundary:

| Layer | Location | Responsibility |
|---|---|---|
UploadEngine |
src/lib/internal/UploadEngine/ |
Orchestrates uploads, owns lifecycle and state machine, emits immutable fact-based events |
UploadContext |
Outside this library (React context) | Subscribes to engine events, stores upload state, drives global upload UI |
UploadListener |
Outside this library (React effect) | Observes upload facts, reacts based on upload intent, triggers domain side-effects |
| UI / Feature Modules | Callers | Start uploads with intent, observe progress, may unmount at any time |
Core Principles
- Completion is State, Not a Callback — upload completion is observable durable state; UI callbacks (
onComplete) are best-effort only - UI Independence — uploads survive modal closure, route changes, and component unmounts
- Event-Driven Boundaries — the engine emits facts, context stores them, listeners react
- Intent-Carrying Uploads — each upload carries immutable
UploadContext<T>metadata explaining why it exists - Separation of Concerns — file transfer (
UploadWorker) and domain actions (external listeners) are completely separate
Typical Flow

When to Read Which Doc
| Doc | What it covers |
|---|---|
| Lifecycle | 9-state machine, transition rules, rehydration |
| API | Public methods, full TypeScript signatures |
| Data Model | Public types, internal types, adapter layer |
| Events | 7-event union, delivery guarantees, Signal integration |
| Responsibilities | What the engine owns vs explicitly does not own |
| Boundaries | Hard dependency and information-flow constraints |
| Invariants | Non-negotiable system rules enforced in code |
| Failure Modes | Expected failures, retry policy, and acceptable degradation |
| Testing | 42-test inventory across 6 test files |
| Migrations | How to evolve the system safely |
| Future | Deferred capabilities and explicit non-goals |