Boundaries

Prev Next

Boundaries

This document defines the hard boundaries of the Upload Engine — what it depends on, what it must never depend on, and how information flows.

Boundaries are stricter than responsibilities.
Responsibilities describe what the module owns.
Boundaries describe what the module is not allowed to see, depend on, or influence.

Boundary Principle

The Upload Engine is an infrastructure subsystem.

It must be possible to:

  • remove every UI component without breaking uploads
  • change backend implementations without touching UI code
  • add new upload consumers without modifying the engine

If a change makes any of the above false, it has crossed a boundary.

Dependency Boundaries (from Source)

Actual Import Graph

Every external dependency of the Upload Engine, traced from source:

Image

External Dependencies by File

File External Imports Why
UploadEngine.ts CookieManager, Config, persistence.adapter Reads auth token via cookie; reads site config; maps state for persistence
UploadService.ts AsyncHandler, Fetch, Endpoints, AppError, getAuthHeadersOnClient HTTP layer for multipart API calls
UploadEvents.ts Signal Publishes events to the global signal bus
UploadPersistence.ts Keys (constants) Storage key prefix for sessionStorage
UploadPlanner.ts AppError Error class for chunk-math validation failures
UploadWorker.ts persistence.adapter Converts internal state to persisted format
UploadTypes.ts ContentTypeEnum Union member in UploadContext
RetryPolicy.ts (none) Zero external dependencies — pure logic
UploadStateMachine.ts (none) Zero external dependencies — pure state table
UploadQueue.ts (none) Zero external dependencies — pure FIFO scheduler
UploadSession.ts (none) Zero external dependencies — pure state container

What the Engine Must Not Depend On

The following are hard import boundaries — no file in the Upload Engine directory may ever import:

Forbidden Dependency Reason
React / any UI framework Engine is UI-agnostic; lifecycle-safe
Hooks or component lifecycle No useEffect, useState, etc.
Next.js routing / navigation Uploads must survive route changes
Feature modules (src/modules/*) Infrastructure cannot depend on features
Domain services Engine emits facts, never calls domain logic
Analytics business logic Telemetry is external — observe events, don't instrument internals

:::tip Verification
Run grep -r "from 'react'" src/lib/internal/UploadEngine/ — must return zero results at all times.
:::

Information Flow Boundaries

Allowed Direction

Information flows in one direction only:
Image

flowchart LR
    UI["UI / Feature Modules"] -->|"start(file, context, onComplete)"| Engine[UploadEngine]
    Engine -->|"emitUploadEvent(event)"| Context["UploadContext
    (React context)"]
    Context -->|"snapshot observation"| Listener["UploadListener
    (React effect)"]
    Listener -->|"domain side-effects"| Domain["Domain Services"]

    Domain -.->|"❌ mutate upload state"| Engine
    Domain -.->|"❌ control orchestration"| Engine
    Context -.->|"❌ call domain APIs"| Domain
    Listener -.->|"❌ emit upload events"| Engine
Direction Allowed? Mechanism
UI → Engine UploadEngine.start(), .pause(), .resume(), .abort()
Engine → Signal Bus emitUploadEvent() via Signal.emit()
Signal Bus → UploadContext Signal.subscribe() in React context
UploadContext → UploadListener React state observation
UploadListener → Domain Services Calls domain APIs based on upload intent
Domain Services → Engine Domain must never mutate upload state
UI → Engine internals Only public API methods; no internal state mutation

Forbidden Flows

These flows are design errors — if observed, they must be refactored:

  • Domain services mutating upload state — domain reacts to facts, never controls the engine
  • UploadContext calling domain APIs — context is a state container only
  • UploadListener emitting upload events — listeners observe, never produce
  • UI bypassing UploadEngine to access UploadSession or UploadWorker — these are private modules
  • Backend responses altering engine behavior beyond what UploadService translates

Boundary Between Upload and Domain

The uploader boundary ends at:

  • a successfully uploaded file (state: completed)
  • a durable UPLOAD_COMPLETED event with fileUrl
  • an immutable UploadSessionSnapshot

Everything after that point is outside the Upload Engine.

The engine must never:

Action Why it's forbidden
Create or mutate CMS entities Domain concern — handled by UploadListener
Interpret UploadContext<T> metadata Generic metadata; engine passes it through opaquely
Retry domain actions Engine retries HTTP uploads only (RetryPolicy.ts)
Infer business success from upload success Upload success ≠ domain success

Boundary Between Engine and Adapter

The persistence.adapter.ts file sits at the boundary between the engine's internal state model and the persistence format:

Image

The engine never exposes InternalUploadState directly.
All consumers receive UploadSessionSnapshot via the adapter layer.

Boundary Enforcement

Enforcement Mechanism
Import isolation No React, no hooks, no feature modules in engine imports
Barrel export index.ts exports only public types and UploadEngine singleton
Adapter layer persistence.adapter.ts maps between internal and public types
Event contracts emitUploadEvent() is the only outbound channel
Immutable metadata UploadContext<T> is set at start() and never mutated
Code review Boundary violations must be caught in review