Responsibilities

Prev Next

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:

Image

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