API
The Upload Engine exposes a deliberately small public API.
Everything listed here is a supported contract — any change to signatures or semantics is a breaking change.
Exported Surface
// src/lib/internal/UploadEngine/index.ts
export { UploadEngine } from './UploadEngine';
export type {
UploadContext,
UploadMeta,
StartUploadOptions,
UploadSessionSnapshot,
UploadEvent,
UploadCompletionHandler,
UploadSessionId,
} from './UploadTypes';
UploadEngine is a singleton — instantiated once at module level. All consumers share the same instance.
Methods
start<T>(options): UploadSessionId
Starts a new upload session and returns its stable identity.
start<T>(options: StartUploadOptions<T>): UploadSessionId
StartUploadOptions<T>
type StartUploadOptions<T = unknown> = {
file: File;
context: UploadContext;
onComplete?: UploadCompletionHandler<T>; // best-effort callback
onError?: (error: Error) => void; // advisory error notification
chunkSize?: number; // override (default: 5 MB)
enableParallelChunkProcessing?: boolean; // feature-flagged parallel chunks
};
Behavior:
- Generates a unique
uploadId(upl_<timestamp>_<random>) - Creates an
UploadSessionwith stateidle - Transitions to
initializing, emitsUPLOAD_STARTED - Sets persistence metadata (tenant-scoped:
<siteId>:<site>:<epoch>) - Applies chunk config (caller override → persisted → default 5 MB)
- Enqueues into
UploadQueue(max 5 concurrent sessions)
Example:
import { UploadEngine } from '@/lib/internal/UploadEngine';
const uploadId = UploadEngine.start({
file: selectedFile,
context: {
source: 'content-create',
contentType: 'video',
entityId: contentId,
meta: {
intent: 'CREATE_VIDEO',
payload: { title: 'Intro Video', seriesId: 'abc123' },
owner: { contentType: 'video', contentId },
},
},
onComplete: async ({ fileUrl, file, context }) => {
await saveVideoToBackend(fileUrl, context);
},
});
pause(uploadId): void
Pauses an active upload.
pause(uploadId: UploadSessionId): void
What happens internally:
- Calls
worker.requestPause()→ setspauseRequested = trueand aborts theAbortController - Calls
session.pause()→ transitions state topaused, emitsUPLOAD_PAUSED - Persists current state via
UploadPersistence.save()(tenant-scopedsessionStorage) - Calls
queue.finish(uploadId)→ frees a concurrency slot
UploadEngine.pause(uploadId);
resume(uploadId): void
Resumes a paused or needs-attention upload.
resume(uploadId: UploadSessionId): void
What happens internally:
- Restores chunk config from session (persisted
chunkSize→ default 5 MB) - Emits
UPLOAD_RESUMEDevent immediately for UI feedback - Re-enqueues the session into
UploadQueue - When the queue starts the worker,
initialize()detects existinguploadServiceDataand resumes from the last completed chunk
:::warning Design note
resume()does not callsession.resume(). Instead, the worker'sinitialize()→session.startUploading()handles thepaused→uploadingtransition. This is intentional.
:::
UploadEngine.resume(uploadId);
abort(uploadId): void
Aborts an upload. This is a terminal operation — aborted uploads cannot be resumed.
abort(uploadId: UploadSessionId): void
What happens internally:
- Calls
worker.abort()→ aborts theAbortController - Calls
session.abort()→ transitions toaborted(idempotent if already terminal), emitsUPLOAD_ABORTED - On
UPLOAD_ABORTEDevent,handleEvent()removes persistence data and deletes the session from the engine's map - Calls
queue.finish(uploadId)→ frees a concurrency slot
UploadEngine.abort(uploadId);
get(uploadId): UploadSessionSnapshot | null
Returns the current public snapshot for a given upload, or null if not found.
get(uploadId: UploadSessionId): UploadSessionSnapshot | null
The returned snapshot is a read-only projection of internal state via toPublicSnapshotAdapter().
const snapshot = UploadEngine.get(uploadId);
if (snapshot?.state === 'completed') {
console.log('File URL:', snapshot.fileUrl);
}
list(): UploadSessionSnapshot[]
Returns public snapshots for all sessions (active, paused, completed, needs-attention).
list(): UploadSessionSnapshot[]
Ordering is not guaranteed. Aborted sessions are not included (they are deleted on abort).
const uploads = UploadEngine.list();
const active = uploads.filter((u) => u.state === 'uploading');
Public Types
UploadSessionId
type UploadSessionId = string;
// Format: "upl_<timestamp>_<random6>"
// Example: "upl_1711100400000_k3f9x2"
Stable for the entire lifetime of the upload. Never reused.
UploadContext
Describes where and why an upload was initiated. Stored immutably for the entire lifecycle.
type UploadContext = {
source: 'account-avatar' | 'content-create' | 'content-details' | 'brand-settings' | string;
contentType:
| 'image'
| 'video'
| 'audio'
| 'document'
| 'closedCaptions'
| 'brand'
| 'brand-video'
| 'androidKeystore'
| 'firetvKeystore'
| 'androidLauncherXML';
entityId?: string; // contentId, brandId, userId, etc.
dropZoneKey?: string; // originating drop zone (e.g. "_16x9Images")
meta?: UploadMeta; // immutable intent metadata
};
UploadMeta
Immutable intent metadata carried through the entire upload lifecycle.
type UploadMeta = Readonly<{
intent: string; // e.g. "CREATE_VIDEO", "UPLOAD_BRAND_IMAGE"
payload: Record<string, unknown>; // arbitrary serializable data
owner?: UploadOwner; // optional content-level routing
}>;
type UploadOwner = {
contentType: ContentTypeEnum | 'video';
contentId: string;
};
The engine stores metadata but never interprets it. Intent resolution happens in UploadListener.
UploadSessionSnapshot
The only data shape UI layers are allowed to consume.
type UploadSessionSnapshot = {
uploadId: UploadSessionId;
fileName: string;
fileSize: number;
state: 'uploading' | 'paused' | 'completed' | 'needs-attention' | 'aborted';
progress: number; // 0–100, monotonic
context: UploadContext;
fileUrl?: string; // present only when state === 'completed'
error?: {
stage: 'upload' | 'completion';
message: string;
}; // present only when state === 'needs-attention'
};
:::info Internal → Public State Mapping
Internal statesidle,initializing,retrying,finalizing, andcommittingall map to'uploading'in the public snapshot. This hides orchestration complexity from consumers.
:::
UploadEvent
Discriminated union of all events emitted by the engine. Events represent facts, not commands.
type UploadEvent =
| { type: 'UPLOAD_STARTED'; uploadId: UploadSessionId }
| { type: 'UPLOAD_PROGRESS'; uploadId: UploadSessionId; progress: number }
| { type: 'UPLOAD_PAUSED'; uploadId: UploadSessionId }
| { type: 'UPLOAD_RESUMED'; uploadId: UploadSessionId }
| { type: 'UPLOAD_COMPLETED'; uploadId: UploadSessionId; fileUrl: string }
| { type: 'UPLOAD_NEEDS_ATTENTION'; uploadId: UploadSessionId; reason: string }
| { type: 'UPLOAD_ABORTED'; uploadId: UploadSessionId };
Events are published on the global Signal bus under topic 'upload':
import { Signal } from '@/lib/internal/Signal';
const unsub = Signal.subscribe<{ event: UploadEvent }>('upload', ({ event }) => {
if (event.type === 'UPLOAD_COMPLETED') {
console.log(event.fileUrl);
}
});
// later
unsub();
UploadCompletionHandler<T>
Optional callback invoked after successful multipart completion.
type UploadCompletionHandler<T = unknown> = (args: {
fileUrl: string;
file: File;
context: UploadContext;
}) => Promise<T>;
:::danger Completion handlers are best-effort
- Handler failure does not invalidate the upload
- Has 1 retry attempt (category:
completion-handler)- If handler fails after retry, the upload enters
needs-attentionwithstage: 'completion'- Domain correctness must be driven by Signal events, not this callback
:::
Constants
| Constant | Value | Description |
|---|---|---|
MAX_PARALLEL_UPLOADS |
5 | Max concurrent sessions in UploadQueue |
DEFAULT_CHUNK_SIZE |
5 MB (5 × 1024 × 1024) | Default chunk size if not overridden |
DEFAULT_WINDOW_SIZE |
5 | Chunks per presigned-URL batch window |
PARALLEL_CHUNK_LIMIT |
5 | Max concurrent chunk uploads (when parallel mode enabled) |
API Flow Diagram

Stability Guarantees
- Backward compatible — public method signatures will not change
- Additive only — new optional fields may be added to
StartUploadOptions,UploadContext,UploadSessionSnapshot - Explicit migrations — any breaking change ships with a documented migration path (see migrations)