Skip to content

Commit 91dd4b0

Browse files
fix(database-studio): restore foreign key UI and stabilize table layout (#77)
* fix(database-studio): restore foreign key UI and stabilize table layout * indexes * import openapi types now we have them al;
1 parent ca6cc9b commit 91dd4b0

10 files changed

Lines changed: 62 additions & 33 deletions

File tree

.changeset/dull-carrots-win.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@bunny.net/database-openapi": minor
3+
---
4+
5+
add indexes

.changeset/great-ghosts-boil.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@bunny.net/database-openapi": minor
3+
"@bunny.net/database-studio": minor
4+
---
5+
6+
Restore foreign key support in Database Studio. The OpenAPI generator now emits an `x-foreign-key` extension on column schemas, and the studio reads it to show `FK` badges in column headers and let you click a foreign key value to open the referenced row in a side sheet. Also fixes the sticky table header during vertical scroll and a small vertical shift in the Data/Schema toolbar when switching tabs.

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/database-openapi/src/generate.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ export interface SchemaObject {
8181
oneOf?: SchemaObject[];
8282
nullable?: boolean;
8383
example?: unknown;
84+
"x-foreign-key"?: { table: string; column: string };
85+
"x-indexes"?: Array<{ name: string; columns: string[]; unique: boolean }>;
8486
}
8587

8688
const NAME_EXAMPLES: [RegExp, unknown][] = [
@@ -199,8 +201,20 @@ const tableToSchema = (table: TableDefinition): SchemaObject => {
199201
const properties: Record<string, SchemaObject> = {};
200202
const required: string[] = [];
201203

204+
const fkByColumn = new Map(
205+
table.foreignKeys.map((fk) => [fk.column, fk] as const),
206+
);
207+
202208
for (const column of table.columns) {
203-
properties[column.name] = columnTypeToSchema(column);
209+
const schema = columnTypeToSchema(column);
210+
const fk = fkByColumn.get(column.name);
211+
if (fk) {
212+
schema["x-foreign-key"] = {
213+
table: fk.referencesTable,
214+
column: fk.referencesColumn,
215+
};
216+
}
217+
properties[column.name] = schema;
204218
if (
205219
!column.nullable &&
206220
column.defaultValue === undefined &&
@@ -214,6 +228,7 @@ const tableToSchema = (table: TableDefinition): SchemaObject => {
214228
type: "object",
215229
properties,
216230
...(required.length > 0 ? { required } : {}),
231+
...(table.indexes.length > 0 ? { "x-indexes": table.indexes } : {}),
217232
};
218233
};
219234

packages/database-studio/client/src/api.ts

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,4 @@
1-
// OpenAPI spec types (subset we use)
2-
interface OpenAPISpec {
3-
paths: Record<string, unknown>;
4-
components: {
5-
schemas: Record<
6-
string,
7-
{
8-
type?: string;
9-
properties?: Record<
10-
string,
11-
{
12-
type?: string;
13-
format?: string;
14-
nullable?: boolean;
15-
example?: unknown;
16-
}
17-
>;
18-
required?: string[];
19-
}
20-
>;
21-
};
22-
}
1+
import type { OpenAPISpec } from "@bunny.net/database-openapi";
232

243
export interface TableSummary {
254
name: string;
@@ -108,12 +87,24 @@ export const fetchTableSchema = async (name: string): Promise<TableSchema> => {
10887
},
10988
);
11089

111-
// Foreign keys and indexes aren't in the OpenAPI spec - return empty for now
112-
// The schema tab will show what's available from the spec
90+
const foreignKeys: TableSchema["foreignKeys"] = [];
91+
for (const [colName, colSchema] of Object.entries(tableSchema.properties)) {
92+
const fk = colSchema["x-foreign-key"];
93+
if (fk) {
94+
foreignKeys.push({ from: colName, table: fk.table, to: fk.column });
95+
}
96+
}
97+
98+
const indexes: TableSchema["indexes"] =
99+
tableSchema["x-indexes"]?.map((idx) => ({
100+
name: idx.name,
101+
unique: idx.unique,
102+
})) ?? [];
103+
113104
return {
114105
columns,
115-
foreignKeys: [],
116-
indexes: [],
106+
foreignKeys,
107+
indexes,
117108
};
118109
};
119110

packages/database-studio/client/src/components/DataTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export function DataTab({
190190

191191
return (
192192
<>
193-
<div className="flex-1 overflow-auto">
193+
<div className="flex flex-1 min-h-0 flex-col">
194194
<DataTable
195195
columns={columns}
196196
data={data.rows as RowRecord[]}

packages/database-studio/client/src/components/TableView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,8 @@ export function TableView({ tableName, onSelectTable }: TableViewProps) {
230230

231231
return (
232232
<div className="flex h-full flex-col">
233-
<div className="flex h-10 shrink-0 items-center gap-1 border-b px-4">
234-
<div className="flex gap-1">
233+
<div className="flex h-10 min-h-10 max-h-10 shrink-0 items-center gap-1 border-b px-4">
234+
<div className="flex items-center gap-1">
235235
<Button
236236
variant={tab === "data" ? "secondary" : "ghost"}
237237
size="sm"

packages/database-studio/client/src/components/ui/data-table.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ export function DataTable<TData, TValue>({
130130
const colCount = columns.length + (onInspectRow ? 1 : 0);
131131

132132
return (
133-
<Table style={{ minWidth: `${colCount * 150}px` }}>
133+
<Table
134+
containerClassName="h-full"
135+
style={{ minWidth: `${colCount * 150}px` }}
136+
>
134137
<TableHeader className="sticky top-0 z-10 bg-card">
135138
{table.getHeaderGroups().map((headerGroup) => (
136139
<TableRow key={headerGroup.id}>

packages/database-studio/client/src/components/ui/table.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import type * as React from "react";
22
import { cn } from "@/lib/utils";
33

4-
function Table({ className, ...props }: React.ComponentProps<"table">) {
4+
function Table({
5+
className,
6+
containerClassName,
7+
...props
8+
}: React.ComponentProps<"table"> & { containerClassName?: string }) {
59
return (
6-
<div data-slot="table-container" className="relative w-full overflow-auto">
10+
<div
11+
data-slot="table-container"
12+
className={cn("relative w-full overflow-auto", containerClassName)}
13+
>
714
<table
815
data-slot="table"
916
className={cn("w-full caption-bottom text-sm", className)}

packages/database-studio/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
},
1515
"dependencies": {
1616
"@bunny.net/database-adapter-libsql": "workspace:*",
17+
"@bunny.net/database-openapi": "workspace:*",
1718
"@bunny.net/database-rest": "workspace:*",
1819
"@libsql/client": "^0.17.0"
1920
},

0 commit comments

Comments
 (0)