Design Decisions
WIPArchitecture decisions and design rationale behind @gentleduck/upload.
Architecture decisions and rationale behind the upload engine.
Typed Results End-to-End
The engine carries a typed completion result all the way through:
UploadApi.completereturns a typed result.- Internal events include the result.
upload.completedemits it.- Items in the
completedphase keep it.
unknown never appears on the completion path, so UIs use results without casts.
Dedupe Without Fake Intents
When findByChecksum matches, the engine completes the item via a dedicated internal
event (dedupe.ok) — no synthetic intent. State stays honest and no strategy
assumptions sneak in.
Defaults At Construction
createUploadStore resolves config defaults and supplies a default transport
(createXHRTransport) when none is given. Pass only the fields you care about — the
runtime is always fully specified.
Core Does Not Depend On React
The reducer and runtime are React-free. React adapters live in src/react and only
read the store interface. No import cycles, and the core works in Node.js, Vue, or
vanilla JS.
Immutable State Updates
All state changes go through the reducer. Direct map mutations were removed to fix
useSyncExternalStore snapshot identity issues. Internal events drive every change:
- Files are added via
files.added. - Fingerprints update via
fingerprint.updated. - Cleanup returns a new state instead of mutating.
Single Event Emission Layer
State-derived public events emit from one place in the store runtime, not inside individual handlers. No duplicates, consistent semantics across the lifecycle.
Strategy Decoupling
Core defines strategy interfaces in core/contracts/strategy.types.ts. The strategies
package implements the registry helper and the protocol logic. Core never imports
strategy implementations directly.