feat(d1): add Cloudflare D1 database support

This commit is contained in:
Anubis authored and GitHub committed 2026-07-12 22:03:57 +08:00
1 parent 977e7f78ca
commit 2a92746dc0
61 files changed
+1732 -69

No files matched your search

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import PasswordInput from "@/components/ui/PasswordInput.vue";
const accountId = defineModel<string>("accountId", { required: true });
const databaseId = defineModel<string | undefined>("databaseId");
const apiToken = defineModel<string>("apiToken", { required: true });
const { t } = useI18n();
</script>
<template>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="justify-self-start text-left">{{ t("connection.d1AccountId") }}</Label>
<Input v-model="accountId" class="col-span-3" placeholder="023e105f4ecef8ad9ca31a8372d0c353" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="justify-self-start text-left">{{ t("connection.d1DatabaseId") }}</Label>
<Input v-model="databaseId" class="col-span-3" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="justify-self-start text-left">{{ t("connection.d1ApiToken") }}</Label>
<PasswordInput v-model="apiToken" class="col-span-3" placeholder="Cloudflare API Token" />
</div>
<div class="grid grid-cols-4 items-start gap-4">
<span />
<p class="col-span-3 text-xs text-muted-foreground">{{ t("connection.d1TokenHint") }}</p>
</div>
</template>
@@ -50,9 +50,11 @@ import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatab
import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForVisiblePicker, isSystemDatabaseName, normalizeVisibleDatabaseSelection, buildDraftVisibleSchemasConnectionId, normalizeVisibleSchemaSelection } from "@/lib/database/visibleDatabases";
import { isSchemaAware, isSingleDatabase } from "@/lib/database/databaseFeatureSupport";
import VisibleSchemasDialog from "@/components/sidebar/VisibleSchemasDialog.vue";
import CloudflareD1ConnectionFields from "@/components/connection/CloudflareD1ConnectionFields.vue";
import { oceanbaseModeConnectionPatch, oceanbaseSubModeFromConfig } from "@/lib/database/oceanbaseConnectionMode";
import { translateBackendError } from "@/i18n/backend-errors";
import { applyHiveKerberosSubmitConfig, hiveKerberosFormConfig, type HiveKerberosAuthMode } from "@/lib/database/hiveKerberosOptions";
import { hasCloudflareD1Credentials, isCloudflareD1Connection, normalizeCloudflareD1Connection } from "@/lib/connection/cloudflareD1";
type DbOption = { value: string; label: string };
type DbCategory = { key: string; title: string; options: DbOption[] };
@@ -547,6 +549,7 @@ const driverProfiles: Record<
sqlite: { type: "sqlite", port: 0, user: "", label: "SQLite", icon: "sqlite" },
rqlite: { type: "rqlite", port: 4001, user: "", label: "RQLite", icon: "rqlite" },
turso: { type: "turso", port: 443, user: "", label: "Turso", icon: "turso" },
"cloudflare-d1": { type: "cloudflare-d1", port: 443, user: "", label: "Cloudflare D1", icon: "cloudflare-d1" },
duckdb: { type: "duckdb", port: 0, user: "", label: "DuckDB", icon: "duckdb" },
access: { type: "access", port: 0, user: "", label: "Microsoft Access", icon: "access" },
mongodb: { type: "mongodb", port: 27017, user: "", label: "MongoDB", icon: "mongodb" },
@@ -1635,6 +1638,7 @@ const iconTypeMap: Record<string, string> = {
sqlite: "sqlite",
rqlite: "rqlite",
turso: "turso",
"cloudflare-d1": "cloudflare-d1",
access: "access",
redis: "redis",
mongodb: "mongodb",
@@ -1724,6 +1728,7 @@ const dbOptions: DbOption[] = [
{ value: "dm", label: "DM (Dameng)" },
{ value: "opengauss", label: "openGauss" },
{ value: "turso", label: "Turso" },
{ value: "cloudflare-d1", label: "Cloudflare D1" },
{ value: "duckdb", label: "DuckDB" },
{ value: "rqlite", label: "RQLite" },
{ value: "access", label: "Microsoft Access" },
@@ -1915,7 +1920,7 @@ const zookeeperConnectString = computed({
form.value.connection_string = normalizeZooKeeperConnectString(value);
},
});
const canUseTransportLayers = computed(() => form.value.db_type !== "sqlite" && form.value.db_type !== "access" && !isH2FileMode.value);
const canUseTransportLayers = computed(() => form.value.db_type !== "sqlite" && form.value.db_type !== "access" && !isCloudflareD1Connection(form.value) && !isH2FileMode.value);
const shouldShowAgentDriverInstallHint = computed(() => showAgentDriverInstallHint(form.value.db_type, agentDrivers.value, form.value.driver_profile));
const h2DriverMissing = computed(() => form.value.db_type === "h2" && isH2FileMode.value && agentDrivers.value.find((d) => d.db_type === "h2")?.installed !== true);
const canChooseVisibleDatabases = computed(() => connectionCanChooseVisibleDatabases(form.value));
@@ -2040,6 +2045,7 @@ const hasRequiredConnectionTarget = computed(() => {
}
if (form.value.db_type === "zookeeper") return !!(form.value.host || form.value.connection_string || connectionUrlInput.value.trim());
if (form.value.db_type === "nacos") return !!nacosServerAddr.value.trim();
if (isCloudflareD1Connection(form.value)) return hasCloudflareD1Credentials(form.value);
if (isH2FileMode.value) return !!(form.value.host.trim() || h2FilePathFromJdbcUrl(form.value.connection_string));
return !!(form.value.host || (mongoUseUrl.value && form.value.connection_string) || (form.value.db_type === "jdbc" && form.value.connection_string) || connectionUrlInput.value.trim());
});
@@ -2290,6 +2296,12 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
throw new Error(t("connection.kingbaseDatabaseRequired"));
}
}
if (isCloudflareD1Connection(config)) {
normalizeCloudflareD1Connection(config);
if (!hasCloudflareD1Credentials(config)) {
throw new Error(t("connection.d1FieldsRequired"));
}
}
config.transport_layers = (config.transport_layers || []).map(normalizeTransportLayer);
config.transport_layers = config.transport_layers.map((layer) => {
if (layer.type !== "ssh") return layer;
@@ -4744,6 +4756,10 @@ function openExternalUrl(url: string) {
</div>
</template>
<template v-else-if="form.db_type === 'cloudflare-d1'">
<CloudflareD1ConnectionFields v-model:account-id="form.host" v-model:database-id="form.database" v-model:api-token="form.password" />
</template>
<!-- MySQL / PostgreSQL: host, port, user, password, database -->
<template v-else>
<div class="grid grid-cols-4 items-center gap-4">
@@ -14,6 +14,7 @@ const assetIcons: Record<string, string> = {
sqlite: "sqlite",
rqlite: "rqlite.png",
turso: "turso.png",
cloudflare_d1: "cloudflare-d1",
redis: "redis",
mongodb: "mongodb",
mongodb_legacy: "mongodb",
@@ -474,6 +474,8 @@ function connectionTooltipScheme(config: Pick<ConnectionConfig, "db_type" | "ssl
case "turso":
case "mq":
return config.ssl ? "https" : "http";
case "cloudflare-d1":
return "https";
case "dameng":
return "dm";
default:
@@ -4825,7 +4827,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
const sqlHistoryMenu = savedSqlHistorySubmenu();
if (sqlHistoryMenu) items.push(sqlHistoryMenu);
if (node.type === "database") {
if (node.type === "database" && currentDatabaseType() !== "cloudflare-d1") {
if (!isNodeDefaultDatabase.value) {
items.push({ label: t("contextMenu.setDefaultDatabase"), action: setNodeAsDefaultDatabase, icon: Database });
} else {