feat(mongodb): add Compass-style collection import/export
This commit is contained in:
1 parent
bf4874906c
commit
8ff56b4837
47 files changed
+5626
-52
No files matched your search
@@ -458,6 +458,12 @@ export const previewTableImportFile = forward("previewTableImportFile");
|
||||
export const importTableFile = forward("importTableFile");
|
||||
export const cancelTableImport = forward("cancelTableImport");
|
||||
export const releaseTableImportSource = forward("releaseTableImportSource");
|
||||
export const previewMongodbImportFile = forward("previewMongodbImportFile");
|
||||
export const importMongodbFile = forward("importMongodbFile");
|
||||
export const cancelMongodbImport = forward("cancelMongodbImport");
|
||||
export const releaseMongodbImportSource = forward("releaseMongodbImportSource");
|
||||
export const exportMongodbQuery = forward("exportMongodbQuery");
|
||||
export const cancelMongodbExport = forward("cancelMongodbExport");
|
||||
|
||||
// Database Export
|
||||
export const beginDatabaseBackupSnapshot = forward("beginDatabaseBackupSnapshot");
|
||||
@@ -995,6 +1001,19 @@ export type {
|
||||
TableImportRequest,
|
||||
TableImportSummary,
|
||||
TableImportProgress,
|
||||
MongoImportFormat,
|
||||
MongoImportTypeMode,
|
||||
MongoImportIssue,
|
||||
MongoImportParseOptions,
|
||||
MongoImportPreviewRequest,
|
||||
MongoImportPreview,
|
||||
MongoImportRequest,
|
||||
MongoImportProgress,
|
||||
MongoImportSummary,
|
||||
MongoExportFormat,
|
||||
MongoExportRequest,
|
||||
MongoExportProgress,
|
||||
MongoExportSummary,
|
||||
DatabaseExportRequest,
|
||||
ExportProgress,
|
||||
TableExportProgress,
|
||||
|
||||
@@ -132,6 +132,14 @@ import type {
|
||||
TableImportRequest,
|
||||
TableImportSummary,
|
||||
TableImportProgress,
|
||||
MongoImportPreviewRequest,
|
||||
MongoImportPreview,
|
||||
MongoImportRequest,
|
||||
MongoImportProgress,
|
||||
MongoImportSummary,
|
||||
MongoExportRequest,
|
||||
MongoExportProgress,
|
||||
MongoExportSummary,
|
||||
DatabaseBackupSnapshot,
|
||||
DatabaseExportRequest,
|
||||
ExportProgress,
|
||||
@@ -2469,6 +2477,122 @@ export async function releaseTableImportSource(sourceRef: string): Promise<boole
|
||||
return result.released;
|
||||
}
|
||||
|
||||
export async function previewMongodbImportFile(fileOrPath: string | File | MongoImportPreviewRequest, options: Partial<MongoImportPreviewRequest> = {}): Promise<MongoImportPreview> {
|
||||
if (typeof fileOrPath === "object" && !(fileOrPath instanceof File)) {
|
||||
throw new Error("previewMongodbImportFile in web mode requires a File object for upload previews");
|
||||
}
|
||||
if (typeof fileOrPath === "string") {
|
||||
if (!options.sourceRef) {
|
||||
throw new Error("previewMongodbImportFile in web mode requires a File object for new uploads");
|
||||
}
|
||||
const res = await fetch(apiUrl("/api/mongo/import/preview-source"), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
sourceRef: options.sourceRef,
|
||||
format: options.format,
|
||||
parseOptions: options.parseOptions,
|
||||
previewLimit: options.previewLimit,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw await backendResponseError(res);
|
||||
return res.json();
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append("file", fileOrPath);
|
||||
if (options.format) formData.append("format", options.format);
|
||||
if (options.parseOptions) formData.append("parseOptions", JSON.stringify(options.parseOptions));
|
||||
if (options.previewLimit != null) formData.append("previewLimit", String(options.previewLimit));
|
||||
const res = await fetch(apiUrl("/api/mongo/import/preview"), {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
if (!res.ok) throw await backendResponseError(res);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function importMongodbFile(request: MongoImportRequest, onProgress: (progress: MongoImportProgress) => void): Promise<MongoImportSummary> {
|
||||
const res = await fetch(apiUrl("/api/mongo/import/execute"), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ request }),
|
||||
});
|
||||
if (!res.ok) throw await backendResponseError(res);
|
||||
return new Promise((resolve, reject) => {
|
||||
const es = new EventSource(apiUrl(`/api/mongo/import/progress/${request.importId}`));
|
||||
es.onmessage = (e) => {
|
||||
const progress: MongoImportProgress = JSON.parse(e.data);
|
||||
onProgress(progress);
|
||||
if (progress.status === "done") {
|
||||
es.close();
|
||||
resolve({
|
||||
importId: progress.importId,
|
||||
rowsInserted: progress.rowsInserted,
|
||||
rowsFailed: progress.rowsFailed,
|
||||
batchesCommitted: progress.batchesCommitted,
|
||||
elapsedMs: progress.elapsedMs,
|
||||
});
|
||||
} else if (progress.status === "error" || progress.status === "cancelled") {
|
||||
es.close();
|
||||
reject(new Error(progress.errorMessage || "MongoDB import failed"));
|
||||
}
|
||||
};
|
||||
es.onerror = () => {
|
||||
es.close();
|
||||
reject(new Error("MongoDB import SSE connection failed"));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function cancelMongodbImport(importId: string): Promise<boolean> {
|
||||
return post("/api/mongo/import/cancel", { importId });
|
||||
}
|
||||
|
||||
export async function releaseMongodbImportSource(sourceRef: string): Promise<boolean> {
|
||||
const result = await post<{ released: boolean }>("/api/mongo/import/source/release", { sourceRef });
|
||||
return result.released;
|
||||
}
|
||||
|
||||
export async function exportMongodbQuery(request: MongoExportRequest, onProgress: (progress: MongoExportProgress) => void): Promise<MongoExportSummary> {
|
||||
const res = await fetch(apiUrl("/api/mongo/export"), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ request }),
|
||||
});
|
||||
if (!res.ok) throw await backendResponseError(res);
|
||||
return new Promise((resolve, reject) => {
|
||||
const es = new EventSource(apiUrl(`/api/mongo/export/progress/${request.exportId}`));
|
||||
es.onmessage = (e) => {
|
||||
const progress: MongoExportProgress = JSON.parse(e.data);
|
||||
onProgress(progress);
|
||||
if (progress.status === "done" || progress.status === "error" || progress.status === "cancelled") {
|
||||
es.close();
|
||||
if (progress.status === "done") {
|
||||
const a = document.createElement("a");
|
||||
a.href = apiUrl(`/api/mongo/export/download/${request.exportId}`);
|
||||
a.click();
|
||||
resolve({
|
||||
exportId: progress.exportId,
|
||||
documentsExported: progress.documentsRead,
|
||||
filePath: request.filePath,
|
||||
elapsedMs: progress.elapsedMs,
|
||||
});
|
||||
} else {
|
||||
reject(new Error(progress.errorMessage || "MongoDB export failed"));
|
||||
}
|
||||
}
|
||||
};
|
||||
es.onerror = () => {
|
||||
es.close();
|
||||
reject(new Error("MongoDB export SSE connection failed"));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function cancelMongodbExport(exportId: string): Promise<boolean> {
|
||||
return post("/api/mongo/export/cancel", { exportId });
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Database Export
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -4900,6 +4900,190 @@ export async function releaseTableImportSource(_sourceRef: string): Promise<bool
|
||||
return false;
|
||||
}
|
||||
|
||||
export type MongoImportFormat = "csv" | "json" | "ndjson";
|
||||
export type MongoImportTypeMode = "string" | "auto" | "extendedJson";
|
||||
export type MongoImportInferredType = "boolean" | "integer" | "decimal" | "date" | "object" | "array" | "string";
|
||||
export type MongoImportStatus = "running" | "done" | "error" | "cancelled";
|
||||
export type MongoImportPhase = "preparing" | "parsing" | "writing" | "done";
|
||||
export type MongoExportFormat = "csv" | "ndjson";
|
||||
export type MongoExportStatus = "running" | "done" | "error" | "cancelled";
|
||||
|
||||
export interface MongoImportIssue {
|
||||
code: string;
|
||||
message: string;
|
||||
row?: number | null;
|
||||
column?: string | null;
|
||||
value?: string | null;
|
||||
batch?: number | null;
|
||||
retryable?: boolean;
|
||||
}
|
||||
|
||||
export interface MongoImportParseOptions {
|
||||
encoding?: TableImportTextEncoding | null;
|
||||
delimiter?: string | null;
|
||||
hasHeader?: boolean | null;
|
||||
trim?: boolean | null;
|
||||
emptyAsNull?: boolean | null;
|
||||
typeMode?: MongoImportTypeMode | null;
|
||||
recognizeObjectIdHex?: boolean | null;
|
||||
skipErrorRows?: boolean | null;
|
||||
}
|
||||
|
||||
export interface MongoImportPreviewRequest {
|
||||
filePath: string;
|
||||
sourceRef?: string | null;
|
||||
format: MongoImportFormat;
|
||||
parseOptions?: MongoImportParseOptions;
|
||||
previewLimit?: number | null;
|
||||
}
|
||||
|
||||
export interface MongoImportColumn {
|
||||
name: string;
|
||||
inferredType: MongoImportInferredType;
|
||||
sampleValues?: unknown[];
|
||||
}
|
||||
|
||||
export interface MongoImportPreview {
|
||||
sourceRef?: string | null;
|
||||
format: MongoImportFormat;
|
||||
detectedEncoding?: TableImportTextEncoding | null;
|
||||
fileName: string;
|
||||
filePath: string;
|
||||
sizeBytes: number;
|
||||
columns: MongoImportColumn[];
|
||||
rows: Record<string, unknown>[];
|
||||
rowNumbers?: number[];
|
||||
warnings: MongoImportIssue[];
|
||||
errors: MongoImportIssue[];
|
||||
estimatedRows?: number | null;
|
||||
estimatedRowsExact: boolean;
|
||||
}
|
||||
|
||||
export interface MongoImportRequest {
|
||||
importId: string;
|
||||
connectionId: string;
|
||||
database: string;
|
||||
collection: string;
|
||||
filePath: string;
|
||||
sourceRef?: string | null;
|
||||
format: MongoImportFormat;
|
||||
parseOptions?: MongoImportParseOptions;
|
||||
batchSize: number;
|
||||
executionId?: string | null;
|
||||
}
|
||||
|
||||
export interface MongoImportProgress {
|
||||
importId: string;
|
||||
phase: MongoImportPhase;
|
||||
status: MongoImportStatus;
|
||||
rowsRead: number;
|
||||
rowsInserted: number;
|
||||
rowsFailed: number;
|
||||
batchesCommitted: number;
|
||||
totalRows?: number | null;
|
||||
errorRows?: MongoImportIssue[];
|
||||
errorMessage?: string | null;
|
||||
elapsedMs: number;
|
||||
}
|
||||
|
||||
export interface MongoImportSummary {
|
||||
importId: string;
|
||||
rowsInserted: number;
|
||||
rowsFailed: number;
|
||||
batchesCommitted: number;
|
||||
elapsedMs: number;
|
||||
}
|
||||
|
||||
export interface MongoExportRequest {
|
||||
exportId: string;
|
||||
connectionId: string;
|
||||
database: string;
|
||||
collection: string;
|
||||
filter?: string | null;
|
||||
sort?: string | null;
|
||||
projection?: string | null;
|
||||
collation?: string | null;
|
||||
format: MongoExportFormat;
|
||||
includeHeader?: boolean;
|
||||
filePath: string;
|
||||
executionId?: string | null;
|
||||
}
|
||||
|
||||
export interface MongoExportProgress {
|
||||
exportId: string;
|
||||
status: MongoExportStatus;
|
||||
documentsRead: number;
|
||||
bytesWritten: number;
|
||||
totalDocuments?: number | null;
|
||||
errorMessage?: string | null;
|
||||
elapsedMs: number;
|
||||
}
|
||||
|
||||
export interface MongoExportSummary {
|
||||
exportId: string;
|
||||
documentsExported: number;
|
||||
filePath: string;
|
||||
elapsedMs: number;
|
||||
}
|
||||
|
||||
export async function previewMongodbImportFile(filePathOrRequest: string | File | MongoImportPreviewRequest, options: Partial<MongoImportPreviewRequest> = {}): Promise<MongoImportPreview> {
|
||||
if (typeof filePathOrRequest !== "string" && !("filePath" in filePathOrRequest)) {
|
||||
throw new Error("previewMongodbImportFile in desktop mode requires a file path, not a File object");
|
||||
}
|
||||
const request: MongoImportPreviewRequest = typeof filePathOrRequest === "string" ? { format: options.format ?? "csv", ...options, filePath: filePathOrRequest } : filePathOrRequest;
|
||||
return invoke("preview_mongodb_import_file", { request });
|
||||
}
|
||||
|
||||
export async function importMongodbFile(request: MongoImportRequest, onProgress: (progress: MongoImportProgress) => void): Promise<MongoImportSummary> {
|
||||
const unlisten: UnlistenFn = await listen<MongoImportProgress>("mongo-import-progress", (event) => {
|
||||
if (event.payload.importId === request.importId) {
|
||||
onProgress(event.payload);
|
||||
if (event.payload.status === "done" || event.payload.status === "error" || event.payload.status === "cancelled") {
|
||||
unlisten();
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
const summary = await invoke<MongoImportSummary>("import_mongodb_file", { request });
|
||||
unlisten();
|
||||
return summary;
|
||||
} catch (e) {
|
||||
unlisten();
|
||||
throw e instanceof BackendErrorException ? e : new BackendErrorException(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelMongodbImport(importId: string): Promise<boolean> {
|
||||
return invoke("cancel_mongodb_import", { importId });
|
||||
}
|
||||
|
||||
export async function releaseMongodbImportSource(_sourceRef: string): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function exportMongodbQuery(request: MongoExportRequest, onProgress: (progress: MongoExportProgress) => void): Promise<MongoExportSummary> {
|
||||
const unlisten: UnlistenFn = await listen<MongoExportProgress>("mongo-export-progress", (event) => {
|
||||
if (event.payload.exportId === request.exportId) {
|
||||
onProgress(event.payload);
|
||||
if (event.payload.status === "done" || event.payload.status === "error" || event.payload.status === "cancelled") {
|
||||
unlisten();
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
const summary = await invoke<MongoExportSummary>("export_mongodb_query", { request });
|
||||
unlisten();
|
||||
return summary;
|
||||
} catch (e) {
|
||||
unlisten();
|
||||
throw e instanceof BackendErrorException ? e : new BackendErrorException(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelMongodbExport(exportId: string): Promise<boolean> {
|
||||
return invoke("cancel_mongodb_export", { exportId });
|
||||
}
|
||||
|
||||
// --- Database Export ---
|
||||
export interface DatabaseExportRequest {
|
||||
exportId: string;
|
||||
|
||||
Reference in new issue
Block a user