@synap-core/import-core
v1.0.0
Published
Shared frontend import orchestration primitives for Relay and Browser
Readme
@synap-core/import-core
Shared frontend import primitives for Browser, Relay, and channels surfaces.
This package centralizes small, reusable import building blocks that are safe to share across app surfaces:
- File helpers (
readFileAsBase64,inferMimeTypeFromFileName) - Lightweight preview parsers (
parseCsvPreview,parseBookmarksHtmlPreview,parseJsonPreview) - Lifecycle vocabulary and UI labels (
ImportLifecycleStatus,getStatusLabel,isTerminalStatus,toProgressPercent)
It is intentionally narrow: it does not own backend orchestration, queue workers, mutation calls, or UI flow state machines.
Why This Package Exists
Multiple import entry points exist in the product today:
- Browser settings import flow
- Browser workspace
ImportCell - Channels
ImportDialog - Relay archive/contact import hooks
Without a shared package, each surface tends to re-implement:
- Base64 file conversion
- MIME inference from file name
- Preview row extraction for CSV/JSON/bookmarks
- Lifecycle status label mapping
import-core prevents this drift while keeping the API surface minimal and framework-agnostic.
Current Package API
Exports are defined in src/index.ts and currently include:
src/file.tstype ImportBatchIteminferMimeTypeFromFileName(fileName: string): stringreadFileAsBase64(file: File): Promise<string>
src/preview.tstype PreviewRowparseCsvPreview(text: string): PreviewRow[]parseBookmarksHtmlPreview(html: string): PreviewRow[]parseJsonPreview(text: string): PreviewRow[]
src/lifecycle.tstype ImportLifecycleStatustype ImportLifecycleProgresstoProgressPercent(progress): numberisTerminalStatus(status): booleangetStatusLabel(status): string
src/identifiers.tsnormalizeIdentifierSlug(value): stringbuildTelegramExternalId(name, phone?): stringbuildLinkedInExternalId({ email, firstName, lastName, fallbackIndex? }): string
Import Path Architecture
This section describes how import paths are structured today and where import-core fits.
1) Template-based path (format-driven batch import)
Current behavior:
- UIs choose a format template (for example CSV, JSON, bookmarks HTML).
- UIs may parse preview rows client-side for a user confirmation step.
- UIs submit raw file payloads to backend
import.submitBatch. - Backend
ImportOrchestrator.submitBatch()routes by MIME/extension and transforms where supported.
Where it is used now:
- Browser settings
ImportSection(format registry + preview + submit) - Channels
ImportDialog(direct file batch submit) - Browser
ImportCell(direct file batch submit)
import-core role:
- Preview parser helpers
- File reading helper
2) Guided path (step-by-step UI flow)
Current behavior:
- Surface-specific components own staged steps (select format, select file, preview, import, done/error).
- Step orchestration/state is local UI logic.
Where it is used now:
- Browser settings
ImportSectionstep machine - Channels
ImportDialogstaged import states - Relay hooks (
useTelegramImport,useLinkedInImport) with pick/parse/confirm phases
import-core role:
- Shared lifecycle labels/types where a flow wants common status semantics
- Shared file helpers/parsers so guided UIs do not duplicate low-level logic
3) AI-assisted path (modeling preview)
Current behavior:
- Backend exposes
import.previewModelingand orchestratorpreviewModeling(...). - This returns profile/view suggestions from sample rows.
- No shared
import-corewrapper exists for this endpoint today.
import-core role today:
- None directly (endpoint integration is currently done outside this package).
Lifecycle Statuses
ImportLifecycleStatus in this package is:
queuedparsingnormalizingmergingwritingcompletedfailed
Helper behavior:
getStatusLabel("merging")returns"Resolving schema"(UI-friendly wording)isTerminalStatusreturns true only forcompletedandfailedtoProgressPercentclamps to 0..100 and handles zero totals
Important alignment note:
- Relay hook local unions currently also include
idle/picking/parsingand sometimes set non-lifecycle values likedoneorerrorin demo/error branches. - Treat
ImportLifecycleStatusas canonical for server/job lifecycle states; keep local UI-only states separate when needed.
Capability Boundaries
import-core should own:
- Small pure helpers usable across Browser/Relay/channels
- Shared frontend lifecycle labels/types
- Lightweight preview extraction for UX (not authoritative ingestion parsing)
import-core should not own:
- tRPC calls (
import.submitBatch,import.telegramContacts,import.linkedInContacts,import.previewModeling) - Backend parsing/transform orchestration logic
- Queue/job processing logic
- Workspace access checks, auth, or persistence
- Product-specific UI state machines/components
Backend authority remains in:
synap-backend/packages/api/src/routers/import.tssynap-backend/packages/api/src/services/import-orchestrator.tssynap-backend/packages/api/src/utils/import-parsers.ts
Integration Points (Current State)
- Relay hooks
useTelegramImportusesImportLifecycleStatusandgetStatusLabeluseLinkedInImportusesImportLifecycleStatusandgetStatusLabel
- Browser
ImportSectionuses preview parsersImportCellusesreadFileAsBase64
- Channels
ImportDialogusesreadFileAsBase64
- Backend
importrouter routes toImportOrchestrator- Orchestrator handles submit batch, queue-based Telegram/LinkedIn imports, and modeling preview
Practical Examples
Read files for submitBatch payload
import { readFileAsBase64 } from "@synap-core/import-core";
const contentBase64 = await readFileAsBase64(file);
const item = {
path: file.name,
contentBase64,
mimeType: file.type || "application/octet-stream",
};Infer MIME type from a filename
import { inferMimeTypeFromFileName } from "@synap-core/import-core";
const mimeType = inferMimeTypeFromFileName("contacts.csv"); // "text/csv"Build stable external IDs for archive imports
import {
buildLinkedInExternalId,
buildTelegramExternalId,
} from "@synap-core/import-core";
const tg = buildTelegramExternalId("Ada Lovelace", "+15550101");
const li = buildLinkedInExternalId({
email: null,
firstName: "Ada",
lastName: "Lovelace",
fallbackIndex: 0,
});Generate preview rows from source text
import {
parseCsvPreview,
parseBookmarksHtmlPreview,
parseJsonPreview,
} from "@synap-core/import-core";
const csvRows = parseCsvPreview(csvText);
const bookmarkRows = parseBookmarksHtmlPreview(bookmarksHtml);
const jsonRows = parseJsonPreview(jsonText);Show consistent lifecycle labels/progress
import {
getStatusLabel,
isTerminalStatus,
toProgressPercent,
type ImportLifecycleProgress,
} from "@synap-core/import-core";
const progress: ImportLifecycleProgress = {
status: "writing",
totalItems: 100,
processedItems: 64,
createdItems: 50,
updatedItems: 10,
skippedItems: 2,
failedItems: 2,
};
const label = getStatusLabel(progress.status); // "Writing"
const percent = toProgressPercent(progress); // 64
const done = isTerminalStatus(progress.status); // falseDo / Do Not
Do:
- Use
readFileAsBase64instead of re-implementingFileReaderconversion in each surface. - Use
getStatusLabelfor consistent lifecycle wording in UI. - Use preview parsers for fast user feedback before submit.
- Keep backend parsing/orchestration as source of truth for actual import behavior.
- Add new shared helpers here only when used by more than one import surface.
Do not:
- Do not duplicate parser logic in every app package when an equivalent helper already exists here.
- Do not move backend ingestion logic into this package.
- Do not treat preview parser output as authoritative ingestion output.
- Do not add tRPC client wrappers unless there is a clear cross-surface need and ownership agreement.
- Do not add UI components/state machines to
import-core; keep this package utility-focused.
Long-term Usage Guidance
When adding a new import surface:
- Start with
import-coreutilities for file handling and preview. - Keep surface-specific UX flow local to that app package.
- Send canonical payloads to backend import router/orchestrator.
- Reuse lifecycle status vocabulary for user-visible job states.
When adding a new shared primitive:
- Verify at least two surfaces need it.
- Keep API browser-safe and dependency-light.
- Document behavior and constraints in this README.
- Avoid overlap with backend parser/orchestrator contracts.
