Skip to main content

Import & Export Commands

Import

CommandTS wrapperNotes
validate_importvalidateImport(userId, kind, json, audioMeta?)Dry-run validation, returns ImportPreview, no DB writes
import_reading_testimportReadingTest(userId, json)Validates then commits
import_writing_testimportWritingTest(userId, json)Validates then commits
import_listening_testimportListeningTest(userId, json, audioFiles)Validates, commits, and writes audio files to storage
export type ImportKind = "reading" | "writing" | "listening";

export interface ImportPreview {
title: string;
status: string;
kind: string;
passage_or_section_or_task_count: number;
group_count: number;
question_count: number;
duplicate_of: string | null;
}

export interface ImportAudioMeta {
section_number: number;
file_name: string;
size: number;
}

export interface ImportAudioFile {
sectionNumber: number;
file: File;
}

All three commit commands return the newly created test's id as a string. See Importing Test Content (JSON + Media) for the full JSON schema and audio payload conversion details.

Export

CommandTS wrapperNotes
export_test_to_zipexportTestToZip(userId, kind, id)Opens a native Save-As dialog; returns null if cancelled
export interface ExportResult {
filePath: string;
fileName: string;
warnings: string[];
}

export async function exportTestToZip(
userId: string,
kind: ImportKind,
id: string
): Promise<ExportResult | null> {
return invoke<ExportResult | null>("export_test_to_zip", { userId, kind, id });
}

ExportResult's Rust struct (src/core/src/services/export_service.rs) is one of the exceptions to the general snake_case-on-the-wire rule — it's explicitly annotated #[serde(rename_all = "camelCase")], matching the camelCase field names above.

See Exporting Tests as ZIP Archives for the full zip layout, edge cases, and authorization rules.

upload_writing_asset

export async function uploadWritingAsset(taskId: string, file: File): Promise<string> {
const bytes = Array.from(new Uint8Array(await file.arrayBuffer()));
const path = await invoke<string>("upload_writing_asset", {
taskId,
file_name: file.name,
file_data: bytes,
});
return convertFileSrc(path);
}

Used for Writing Task 1 image prompts (writing_tasks.image_url). Unlike uploadListeningAudio, the file is stored in a per-task subfolder named after the writing_task id with a fixed filename rather than a timestamp/uuid pair: $HOME/.imh/writing-assets/<task_id>/figure.<ext>. Callers must generate the writing_task id client-side (e.g. via crypto.randomUUID()) before uploading, then pass that same id to create_writing_tasks/import_writing_test so the DB row and the stored file match. Re-uploading for the same task id overwrites the existing file. The returned path is converted to a webview-ready asset:// URL via convertFileSrc (see Listening Test Commands for the analogous uploadListeningAudio pattern).