Frontend SDK
Framework-neutral API client and mapping core, plus a React mapping editor
and importer screen. The mapping UI edits a portable MappingSpec
(source index → stable target ID); the backend stays authoritative for
validation and execution.
Installation
Section titled “Installation”See Install the frontend SDK for the npm/pnpm/yarn/bun commands and stylesheet import.
const client = createMapperClient({ baseUrl: "/mapper" });
<MapperImporter client={client} schemaId={SUBSCRIBER_SCHEMA_ID} />For local SDK development (inside the mapper-fe repo):
bun installbun run test # vitestbun run typecheck # tsc --noEmitPackages
Section titled “Packages”Dependency order, enforced by workspace dependencies:
@mapper-fe/client ─────────────────────────────┐ HTTP + protocol types. No workspace deps. │@mapper-fe/core │ depends on client Mapping ops, validation, workflow state. │ No workspace deps. │@mapper-fe/upload ─────────────────────────────┘ UploadAdapter + multipart default. Depends on @mapper-fe/client.@mapper-fe/react Editor, importer, React Flow adapter. Depends on client, core, upload. React ^19. Ships styles.css (CSS variables only).@mapper-fe/client
Section titled “@mapper-fe/client”Protocol types mirroring the backend wire format: Schema,
SchemaField, FieldType, FileMetadata, SourceRow, SheetAnalysis,
SourceAnalysis, FieldMapping, ImportRequest, RowError,
ImportResult, and the ErrorEnvelope ({ error: { code, message } }).
Client construction accepts either a base URL plus an optional fetch, or an
options object — pass a custom fetch to inject auth, retries, or tracing:
createMapperClient(baseUrl: string, fetcher?: MapperFetch): MapperClient;createMapperClient(options: MapperClientOptions): MapperClient;MapperClient methods and their routes:
getSchema(schemaId)→GET /schemas/{id}analyzeFile(file: Blob, filename?)→ multipartPOST /files/analyzeanalyzeFileId(fileId)→ JSONPOST /files/analyzewith{ file_id }import(request)→POST /imports/sync
Failures throw MapperError carrying code, status, and the raw body,
parsed from the error envelope via MapperError.fromResponse. No React, no
DOM.
@mapper-fe/core
Section titled “@mapper-fe/core”Pure mapping logic with no framework code.
Spec. MappingSpec (file_id, schema_id, sheet, mappings) is the
source of truth. connect(spec, source, target) replaces any existing
mapping on that target; disconnect(spec, source, target?) and
disconnectTarget(spec, target) remove mappings. Specs are treated
immutably — every op returns a new object.
Validation. validateMappings accepts either a MappingSchema or
(sourceColumns, targetFields) and reports duplicate targets, unknown target
IDs, out-of-range source indexes, and unmapped required targets:
invalid_mappinginvalid_source_indexunknown_targetduplicate_targetrequired_target_missingIt returns { valid, codes, issues, errors }; validate is an alias for the
schema form.
Workflow state. ImporterStatus models the lifecycle explicitly:
idle → uploading → analyzing → ready → importing → success ↘______________↗_____________↗________________↗ error (retry via upload/analyze)Drive it with createMapperState, transitionMapperState(state, event),
and canTransition(status, event). mapping_changed and reset events
apply from any status; state and events are generic over
MapperState<Schema, Analysis, Result> so hosts keep their own types.
@mapper-fe/upload
Section titled “@mapper-fe/upload”interface UploadAdapter { upload(file: Blob, filename?: string): Promise<SourceAnalysis>;}MultipartUploadAdapter (or createMultipartUploadAdapter) implements it
over client.analyzeFile. Resumable uploads live on the backend
(mapper-be/upload/tus, mounted via mapperhttp.WithUploadExtension);
there is no separate frontend upload package.
@mapper-fe/react
Section titled “@mapper-fe/react”ReactFlowAdapter(spec, sourceColumns, targetFields) derives a
React Flow graph from the spec: source-{index} / target-{index} nodes
plus one edge per mapping. Node IDs never leak into the spec, where identity
stays source index → stable target ID.
MappingEditor is a controlled component — value/onChange plus
sourceColumns, targetFields, optional disabled, validation, and
className. It renders one row per target field with a source <select>,
a derived connection list, and validation errors.
MapperImporter runs the full flow and owns its state internally:
interface MapperImporterProps { client: MapperClient; schemaId: number; uploadAdapter?: UploadAdapter; initialMapping?: Partial<MappingSpec>; onImported?: (result: ImportResult) => void; className?: string;}On mount it loads the schema; on file selection it uploads, analyzes, and
resets the mapping to the new file_id; MappingEditor edits the spec;
submit posts the validated mapping and reports { processed, succeeded, failed } with bounded row errors. Multi-sheet files get a sheet selector
that clears mappings on change. Styling is CSS variables only — import
@mapper-fe/react/styles.css, no Tailwind requirement.
The SDK stays thin: no CSV parsing, no mapping execution, no automatic matching. The backend revalidates everything and remains authoritative.