Responsibilities
Core Responsibility
The Upload Engine is responsible for reliably transferring files from the client to storage under real-world conditions.
It guarantees that:
- uploads can start, pause, resume, abort, and complete
- upload progress and state are explicit and observable
- completion is durable and not tied to UI lifetime
- failures resolve into known, terminal states
The engine does not care why a file is uploaded.
It cares only that the file is uploaded correctly.
Responsibility Map by Module
Each module has exactly one job. The following table maps actual class responsibilities to source:

UploadEngineImpl — Singleton Orchestrator
File: UploadEngine.ts
| Responsibility | Methods | What it does |
|---|---|---|
| Session creation | start<T>(options) |
Creates UploadSession, creates UploadWorker, enqueues in UploadQueue |
| Session control | pause(), resume(), abort() |
Delegates to sessions and workers; manages queue slot release |
| Event routing | handleEvent(event) |
Receives events from sessions, calls emitUploadEvent(), persists state |
| Persistence coordination | rehydrateFromPersistence() |
Loads PersistedUploadState[] on construction, recreates sessions |
| Snapshot mapping | toPublicSnapshot(), toPersistedState(), mapInternalState() |
Uses adapter layer to convert internal → public types |
| Worker cleanup | cleanupWorker(uploadId) |
Removes worker reference, releases queue slot |
Does NOT own: UI state, domain logic, HTTP calls, chunk math, retry decisions.
UploadSession — Per-Upload State Owner
File: UploadSession.ts
| Responsibility | Methods | What it does |
|---|---|---|
| State transitions | startUploading(), pause(), resume(), complete(), fail(), abort() |
Calls transition() from state machine; throws on illegal transition |
| Progress tracking | updateProgress(progress) |
Updates InternalUploadSnapshot.progress and completedParts |
| Snapshot exposure | getSnapshot(), get state |
Returns immutable InternalUploadSnapshot |
| Event delegation | emit(event) |
Calls the EventEmitter callback passed from UploadEngineImpl |
| Callback invocation | onComplete?.(snapshot), onError?.(error) |
Best-effort UI callback delivery |
Does NOT own: Upload execution, HTTP calls, retry logic, persistence.
UploadWorker — Per-Session Executor
File: UploadWorker.ts
| Responsibility | Methods | What it does |
|---|---|---|
| Upload execution | run() |
End-to-end upload lifecycle: init → upload windows → complete |
| Initialization | initialize() |
Calls UploadService.initMultipartUpload(), creates UploadPlanner |
| Chunk loop | uploadAllWindows(), uploadWindow(), runChunkWindow() |
Iterates chunk windows, uploads 5 chunks in parallel |
| Single chunk upload | uploadChunk() |
PUT to presigned URL with retry, creates CompletedPart |
| Completion | completeMultipart() |
Calls UploadService.completeMultipartUpload() with retry |
| Completion handler | runCompletionHandler(fileUrl) |
Calls session.complete(), invokes onComplete, persists state |
| Pause/abort support | Checks pauseRequested and abortController.signal |
Throws PauseError or AbortError at chunk boundaries |
| Persistence | Calls toPersistedStateAdapter() |
Saves state after progress and completion |
Does NOT own: State transitions (delegates to session), queue scheduling, event publishing.
UploadQueue — FIFO Concurrency Scheduler
File: UploadQueue.ts
| Responsibility | Methods | What it does |
|---|---|---|
| Enqueueing | enqueue(uploadId) |
Adds to pending queue; calls maybeStartNext() |
| Concurrency control | maybeStartNext() |
Starts next upload if active.size < concurrencyLimit (default 5) |
| Slot release | Called by onFinish callback |
Removes from active set; calls maybeStartNext() |
| Pause slot management | remove(), prioritize() |
Removes paused uploads; moves resumed uploads to front |
Does NOT own: Upload execution, state transitions, persistence.
UploadPlanner — Pure Chunk Math
File: UploadPlanner.ts
| Responsibility | Methods | What it does |
|---|---|---|
| Total chunks | Constructor | Computes totalChunks = ceil(fileSize / chunkSize) |
| Chunk boundaries | getChunk(partNumber) |
Returns { partNumber, startByte, endByte, size } |
| Window planning | getWindow(startPart) |
Returns contiguous window of chunks for batch upload |
Does NOT own: Upload execution, HTTP calls, state management. Pure computation only.
UploadStateMachine — State Transition Table
File: UploadStateMachine.ts
| Responsibility | Exports | What it does |
|---|---|---|
| Transition validation | transition(from, to) |
Returns new state or throws if transition is illegal |
| Terminal detection | isTerminalState(state) |
Returns true for completed, aborted, failed, completion_failed |
| Attention detection | requiresAttention(state) |
Returns true for needs_attention |
Does NOT own: State storage, event emission, upload logic. Pure state table.
UploadService — Backend HTTP Adapter
File: UploadService.ts
| Responsibility | Static Methods | What it does |
|---|---|---|
| Init multipart | initMultipartUpload(args) |
POST to Endpoints.upload.init; returns { uploadId, chunkSize, totalChunks } |
| Presigned URLs | getBatchedPresignedUrls(args) |
POST to Endpoints.upload.batchedPresignedUrls; returns URL map |
| Complete multipart | completeMultipartUpload(args) |
POST to Endpoints.upload.complete; sends completed parts |
Does NOT own: Retry logic (caller retries), state management, chunk computation.
UploadPersistence — sessionStorage Adapter
File: UploadPersistence.ts
| Responsibility | Static Methods | What it does |
|---|---|---|
| Save | save(state) |
Writes PersistedUploadState to sessionStorage keyed by upload ID |
| Remove | remove(key) |
Deletes single upload from storage |
| Clear | clearAll() |
Removes all persisted uploads |
| Load all | loadAll() |
Returns all persisted uploads |
| Load by tenant | loadForTenant(site, siteId) |
Filters persisted uploads by tenant |
Does NOT own: State mapping (done by adapter), rehydration logic (done by engine).
RetryPolicy — Retry Rules
File: RetryPolicy.ts
| Responsibility | Exports | What it does |
|---|---|---|
| Retry eligibility | canRetry(category, attempt) |
Checks attempt count against per-category max |
| Error classification | isRetryableError(error) |
Returns true for network errors and 5xx HTTP status |
| Delay calculation | getRetryDelayMs(attempt) |
Linear backoff: 1000 * attempt ms |
Does NOT own: Retry execution (done by worker), error handling, state transitions.
UploadEvents — Signal Bus Bridge
File: UploadEvents.ts
| Responsibility | Exports | What it does |
|---|---|---|
| Event publishing | emitUploadEvent(event) |
Calls Signal.emit('upload', event) — the only outbound channel |
Does NOT own: Event creation (done by engine), event subscription (done by consumers).
What the Engine Explicitly Does NOT Own
| Responsibility | Why it's excluded | Who owns it |
|---|---|---|
| UI rendering (modals, progress bars) | Engine is UI-agnostic | Feature modules, UploadContext |
| Domain/business actions (CMS entities) | Engine transfers files, not domain objects | UploadListener |
| Storage hygiene (cleanup, dedup) | Not correctness-critical | Backend lifecycle rules |
Interpreting UploadContext<T> metadata |
Engine passes it through opaquely | UploadListener |
UI onComplete callback reliability |
Best-effort only; completion state is durable | Consumer responsibility |
| Authentication | Engine reads token via CookieManager |
Auth infrastructure |
| Endpoint configuration | Engine reads from Config and Endpoints |
Config infrastructure |
Responsibility Split: Engine vs External Layers
| Layer | Owns | Does NOT Own |
|---|---|---|
UploadEngine |
File transfer orchestration, state machine, event emission | UI state, domain logic, storage cleanup |
UploadContext (React context) |
Subscribing to events, storing snapshots, driving upload UI | Engine decisions, domain calls, upload control |
UploadListener (React effect) |
Observing facts, routing by intent, triggering domain side-effects | Upload state mutation, event emission, UI rendering |
persistence.adapter.ts |
Mapping internal ↔ public ↔ persisted types | State transitions, business logic |