fix(export): support uuid fallback on http
This commit is contained in:
parent
b76efa56d6
commit
94f680ac7a
|
|
@ -10,6 +10,7 @@ import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
|
|||
import * as api from "@/lib/api";
|
||||
import type { ExportProgress } from "@/lib/api";
|
||||
import { isSchemaAware } from "@/lib/databaseCapabilities";
|
||||
import { generateDatabaseExportId } from "@/lib/databaseExport";
|
||||
import { buildSelectedTablesPayload } from "@/lib/databaseExportSelection";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
|
|
@ -161,7 +162,7 @@ async function startExport() {
|
|||
exportCancelled.value = false;
|
||||
exportProgress.value = null;
|
||||
|
||||
exportId.value = crypto.randomUUID();
|
||||
exportId.value = generateDatabaseExportId();
|
||||
|
||||
let filePath = "";
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ import {
|
|||
normalizeWhereInput,
|
||||
quoteTableIdentifier,
|
||||
} from "@/lib/tableSelectSql";
|
||||
import { uuid } from "@/lib/utils";
|
||||
import {
|
||||
canEditExistingTableRows,
|
||||
hiveTablePropertiesIndicateTransactional,
|
||||
|
|
@ -580,8 +581,7 @@ function selectCustomFormatter(value: string) {
|
|||
}
|
||||
|
||||
function createCustomFormatterId(): string {
|
||||
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return `fmt_${crypto.randomUUID()}`;
|
||||
return `fmt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
||||
return `fmt_${uuid()}`;
|
||||
}
|
||||
|
||||
function formatterPreviewRows(columnIndex: number) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { DatabaseType, QueryResult } from "../types/database.ts";
|
||||
import { buildTableSelectSql } from "./tableSelectSql.ts";
|
||||
import { uuid } from "./utils.ts";
|
||||
|
||||
type SqlValue = QueryResult["rows"][number][number];
|
||||
|
||||
|
|
@ -72,6 +73,10 @@ export function buildExportPageSql(options: BuildExportPageSqlOptions): string {
|
|||
});
|
||||
}
|
||||
|
||||
export function generateDatabaseExportId(): string {
|
||||
return uuid();
|
||||
}
|
||||
|
||||
export function buildDatabaseSqlExport(options: BuildDatabaseSqlExportOptions): string {
|
||||
const exportedAt = options.exportedAt ?? new Date();
|
||||
const rowLimit = options.rowLimitPerTable ?? DATABASE_EXPORT_ROW_LIMIT;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
buildDatabaseSqlExport,
|
||||
buildInsertStatements,
|
||||
formatSqlLiteral,
|
||||
generateDatabaseExportId,
|
||||
} from "../../apps/desktop/src/lib/databaseExport.ts";
|
||||
|
||||
test("formats SQL literals for exported INSERT statements", () => {
|
||||
|
|
@ -16,6 +17,24 @@ test("formats SQL literals for exported INSERT statements", () => {
|
|||
assert.equal(formatSqlLiteral("O'Hara"), "'O''Hara'");
|
||||
});
|
||||
|
||||
test("generates export ids when crypto.randomUUID is unavailable", () => {
|
||||
const originalCrypto = globalThis.crypto;
|
||||
|
||||
try {
|
||||
Object.defineProperty(globalThis, "crypto", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
|
||||
assert.match(generateDatabaseExportId(), /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
|
||||
} finally {
|
||||
Object.defineProperty(globalThis, "crypto", {
|
||||
configurable: true,
|
||||
value: originalCrypto,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("builds batched INSERT statements for one exported table", () => {
|
||||
const statements = buildInsertStatements({
|
||||
qualifiedTableName: "`users`",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { strict as assert } from "node:assert";
|
||||
import test from "node:test";
|
||||
|
||||
|
|
@ -6,6 +6,14 @@ const appSource = readFileSync("apps/desktop/src/App.vue", "utf8");
|
|||
const connectionDialogSource = readFileSync("apps/desktop/src/components/connection/ConnectionDialog.vue", "utf8");
|
||||
const driverStoreSource = readFileSync("apps/desktop/src/components/config/DriverStoreDialog.vue", "utf8");
|
||||
|
||||
function appSourceFiles(dir: string): string[] {
|
||||
return readdirSync(dir).flatMap((entry) => {
|
||||
const path = `${dir}/${entry}`;
|
||||
if (statSync(path).isDirectory()) return appSourceFiles(path);
|
||||
return /\.(ts|vue)$/.test(entry) ? [path] : [];
|
||||
});
|
||||
}
|
||||
|
||||
test("web runtime handles driver store open events", () => {
|
||||
assert.match(appSource, /showDriverStore\.value = true;/);
|
||||
assert.doesNotMatch(appSource, /if \(!isDesktop\) return;\s+showDriverStore\.value = true;/);
|
||||
|
|
@ -22,3 +30,11 @@ test("driver store uses the shared API instead of direct Tauri calls", () => {
|
|||
assert.match(driverStoreSource, /api\.listInstalledAgents/);
|
||||
assert.match(driverStoreSource, /api\.listenAgentInstallProgress/);
|
||||
});
|
||||
|
||||
test("web runtime uses the shared uuid helper instead of direct randomUUID calls", () => {
|
||||
const directRandomUuidCalls = appSourceFiles("apps/desktop/src")
|
||||
.filter((path) => path !== "apps/desktop/src/lib/utils.ts")
|
||||
.filter((path) => readFileSync(path, "utf8").includes("crypto.randomUUID("));
|
||||
|
||||
assert.deepEqual(directRandomUuidCalls, []);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue