v0.7.8: MySQL/SQLite backup-sync, Excel export, query cancel, settings import/export, Windows title-bar fix, SQLite table editor (#16)

* [P1-T1] feat(backup): MySQL/SQLite backup models + db_type on SyncOptions (Task 1.1)

* [P1-T2] feat: enable tools(mysql,sqlite) + tableManagement(sqlite) + add fflate (Task 1.2)

* [P1-T3] feat(cancel): CancelHandle enum + CancelRegistry (Task 1.3)

* [P2-T1] feat(export): hand-rolled XLSX writer with inline-string cells (Task 2.1)

* [P2-T2] feat(backup): SQLite .dump/restore/sync core, fail-closed virtual tables (Task 2.2)

* [P2-T3] feat(backup): MySQL dump/restore/sync arg builders + tool resolution (Task 2.3)

* [P2-T4] feat(settings): pure import validator + SettingsExport envelope + Store.apply_settings (Task 2.4)

* [P2-T5] feat(table-editor): SQLite create/diff/rebuild SQL generation, fail-closed AUTOINCREMENT (Task 2.5)

* [P3-T1] feat(commands): MySQL/SQLite backup + settings export/import commands + wrappers (Task 3.1)

* [P3-T2] feat(cancel): capture cancel primitives at connect; cancel_query command; SQLite interrupt test (Task 3.2)

* [P3-T3] feat(table-editor): SQLite object-change dispatch + execute_change Ddl/RebuildTable (Task 3.3)

* [P4-T1] feat(tools): DB-aware backup/restore/sync pages (Task 4.1)

* [P4-T2] feat(export): xlsx export in grid toolbar + overflow menu (Task 4.2)

* [P4-T3] feat(query): cancel button wired to cancelQuery (Task 4.3)

* [P4-T4] feat(settings): export/import buttons + validation gate (Task 4.4)

* [P4-T5] fix(ui): gate macOS overlay drag strip to macOS only (Task 4.5)

* [P4-T6] feat(table-editor): SQLite Create/Edit Table mode (Task 4.6)

* [P5-T1] chore: bump 0.7.7 -> 0.7.8 + README/AGENTS/ROADMAP status (Task 5.1)

* [P5-T2] build(release): bundle mariadb-dump + mariadb client (system-first fallback) (Task 5.2)

* fix(cancel): propagate cancellations past wrapped->raw fallback (SQLite/PG/MySQL) + MySQL CONNECTION_ID cast

* fix(export): Excel export from overflow menu did nothing + add export success/error toasts

* fix(export): tree kebab export fetches table data when rows not loaded

* docs(readme): surface v0.7.8 features (MySQL/SQLite backup-sync, Excel export, query cancel, SQLite table editor, settings import/export)
This commit is contained in:
2026-08-08 00:09:36 +08:00
committed by GitHub
parent 9fb3222956
commit 32a7b852ec
59 changed files with 4068 additions and 572 deletions
+41 -10
View File
@@ -30,6 +30,7 @@ import type {
ColumnInfo,
ConstraintInfo,
TablespaceInfo,
DbType,
} from "../../../lib/types";
import { getCapabilities } from "../../../lib/dbCapabilities";
import { FkPanel, type FkDefinition } from "./FkPanel";
@@ -75,6 +76,8 @@ const PG_TYPES = [
"money",
];
const SQLITE_TYPES = ["integer", "real", "text", "blob", "numeric"];
interface TableFormColumn {
rowId: string;
name: string;
@@ -96,6 +99,7 @@ interface SqlColumn {
default: string | null;
is_pk: boolean;
unique?: boolean;
auto_increment?: boolean;
}
interface TableFormAction {
@@ -112,10 +116,14 @@ interface TableFormParams {
action: TableFormAction;
}
function toSqlColumn(c: TableFormColumn, mode: "create" | "edit"): SqlColumn {
function toSqlColumn(
c: TableFormColumn,
mode: "create" | "edit",
dbType: DbType | undefined,
): SqlColumn {
let type = c.type;
const base = c.type.trim().toLowerCase();
if (mode === "create" && c.auto_increment) {
if (mode === "create" && c.auto_increment && dbType !== "sqlite") {
if (base === "integer" || base === "int" || base === "int4")
type = "serial";
else if (base === "bigint" || base === "int8") type = "bigserial";
@@ -124,7 +132,7 @@ function toSqlColumn(c: TableFormColumn, mode: "create" | "edit"): SqlColumn {
if (c.params && c.params.trim()) {
type = `${type}(${c.params.trim()})`;
}
return {
const out: SqlColumn = {
name: c.name,
type,
nullable: c.nullable,
@@ -132,6 +140,10 @@ function toSqlColumn(c: TableFormColumn, mode: "create" | "edit"): SqlColumn {
is_pk: c.is_pk,
unique: c.unique ?? undefined,
};
if (dbType === "sqlite" && c.auto_increment) {
out.auto_increment = true;
}
return out;
}
function sameColumns(a: TableFormColumn[], b: TableFormColumn[]): boolean {
@@ -175,6 +187,10 @@ const restrictToVerticalAxis: Modifier = ({ transform }) => ({
x: 0,
});
function pickTypeList(dbType: DbType | undefined): string[] {
return dbType === "sqlite" ? SQLITE_TYPES : PG_TYPES;
}
// serial types only exist for the integer family (short + long forms).
function supportsAutoIncrement(type: string): boolean {
const t = type.trim().toLowerCase();
@@ -203,9 +219,12 @@ function buildTablePayload(
params: TableFormParams,
op: "create" | "edit" | "rebuild",
foreignKeys: FkDefinition[] = [],
dbType: DbType | undefined,
): Record<string, unknown> {
const action = params.action;
const sqlColumns = action.columns.map((c) => toSqlColumn(c, action.op));
const sqlColumns = action.columns.map((c) =>
toSqlColumn(c, action.op, dbType),
);
return {
...params,
action: {
@@ -283,6 +302,13 @@ export function TableForm({
);
};
// SQLite has a single schema.
useEffect(() => {
if (dbType === "sqlite" && params.schema !== "main") {
setParams({ ...params, schema: "main" });
}
}, [dbType, params.schema]);
// Rebuild readiness check
useEffect(() => {
if (!isRebuild) {
@@ -343,7 +369,7 @@ export function TableForm({
: cmd.buildObjectDdl(
connectionId,
"table",
buildTablePayload(params, op, createFks),
buildTablePayload(params, op, createFks, dbType),
);
promise
.then((sqls: string[] | string) => {
@@ -469,7 +495,7 @@ export function TableForm({
const sqls = await cmd.buildObjectDdl(
connectionId,
"table",
buildTablePayload(params, op, createFks),
buildTablePayload(params, op, createFks, dbType),
);
sqls.forEach((sql, i) =>
useDbViewerStore.getState().addChange({
@@ -581,7 +607,7 @@ export function TableForm({
{view === "visual" ? (
<>
{mode === "create" && (
{mode === "create" && dbType !== "sqlite" && (
<FormRow label="Schema">
{schemas && schemas.length > 0 ? (
<select
@@ -697,6 +723,7 @@ export function TableForm({
column: c.name,
})
}
dbType={dbType}
/>
))}
</SortableContext>
@@ -806,6 +833,7 @@ interface ColumnRowProps {
onRemove: () => void;
onFk: () => void;
hasFk: boolean;
dbType: DbType | undefined;
}
function ColumnRow({
@@ -816,6 +844,7 @@ function ColumnRow({
onRemove,
onFk,
hasFk,
dbType,
}: ColumnRowProps) {
const {
attributes,
@@ -872,7 +901,9 @@ function ColumnRow({
Primary key
</label>
{mode === "create" &&
supportsAutoIncrement(c.type) && (
(dbType === "sqlite"
? supportsAutoIncrement(c.type) && c.is_pk
: supportsAutoIncrement(c.type)) && (
<label className="flex items-center gap-2 px-2 py-1 text-xs text-text whitespace-nowrap cursor-pointer">
<input
type="checkbox"
@@ -982,10 +1013,10 @@ function ColumnRow({
onChange={(e) => setCell(index, "type", e.target.value)}
className="min-w-0 flex-1 bg-transparent font-mono text-xs text-text outline-none cursor-pointer"
>
{c.type !== "" && !PG_TYPES.includes(c.type) && (
{c.type !== "" && !pickTypeList(dbType).includes(c.type) && (
<option value={c.type}>{c.type}</option>
)}
{PG_TYPES.map((t) => (
{pickTypeList(dbType).map((t) => (
<option key={t} value={t}>
{t}
</option>