Overview

Prev Next

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:

Image

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:

Image

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

Image

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