Skip to content

Commit

Permalink
Merge pull request #172 from outerbase/develop
Browse files Browse the repository at this point in the history
deployment
  • Loading branch information
invisal authored Oct 5, 2024
2 parents 9b0789f + b7f5591 commit b11456c
Show file tree
Hide file tree
Showing 17 changed files with 312 additions and 29 deletions.
7 changes: 7 additions & 0 deletions src/app/(theme)/client/[[...driver]]/page-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ValtownDriver from "@/drivers/valtown-driver";

import MyStudio from "@/components/my-studio";
import CloudflareD1Driver from "@/drivers/cloudflare-d1-driver";
import StarbaseDriver from "@/drivers/starbase-driver";

export default function ClientPageBody() {
const driver = useMemo(() => {
Expand All @@ -27,7 +28,13 @@ export default function ClientPageBody() {
"x-account-id": config.username ?? "",
"x-database-id": config.database ?? "",
});
} else if (config.driver === "starbase") {
return new StarbaseDriver("/proxy/starbase", {
Authorization: "Bearer " + (config.token ?? ""),
"x-starbase-url": config.url ?? "",
});
}

return new TursoDriver(config.url, config.token as string, true);
}, []);

Expand Down
6 changes: 6 additions & 0 deletions src/app/(theme)/client/s/[[...driver]]/page-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useMemo } from "react";
import MyStudio from "@/components/my-studio";
import IndexdbSavedDoc from "@/drivers/saved-doc/indexdb-saved-doc";
import CloudflareD1Driver from "@/drivers/cloudflare-d1-driver";
import StarbaseDriver from "@/drivers/starbase-driver";

export default function ClientPageBody() {
const params = useSearchParams();
Expand Down Expand Up @@ -35,6 +36,11 @@ export default function ClientPageBody() {
"x-account-id": conn.config.username ?? "",
"x-database-id": conn.config.database ?? "",
});
} else if (conn.driver === "starbase") {
return new StarbaseDriver("/proxy/starbase", {
Authorization: "Bearer " + (conn.config.token ?? ""),
"x-starbase-url": conn.config.url ?? "",
});
}

return new TursoDriver(conn.config.url, conn.config.token, true);
Expand Down
11 changes: 11 additions & 0 deletions src/app/(theme)/connect/driver-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ export default function DriverDropdown({
</div>
</DropdownMenuItem>

<DropdownMenuItem
onClick={() => {
onSelect("starbase");
}}
>
<div className="flex gap-4 px-2 items-center h-8">
<SQLiteIcon className="w-6 h-6" />
<div className="font-semibold">StarbaseDB</div>
</div>
</DropdownMenuItem>

<DropdownMenuSeparator />

<DropdownMenuItem
Expand Down
37 changes: 37 additions & 0 deletions src/app/(theme)/connect/saved-connection-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,41 @@ export const DRIVER_DETAIL: Record<SupportedDriver, DriverDetail> =
},
],
},
starbase: {
name: "starbase",
displayName: "Starbase",
icon: SQLiteIcon,
disableRemote: true,
prefill: "",
fields: [
{
name: "url",
title: "Endpoint",
required: true,
type: "text",
secret: false,
invalidate: (url: string): null | string => {
const trimmedUrl = url.trim();
const valid =
trimmedUrl.startsWith("https://") ||
trimmedUrl.startsWith("http://");

if (!valid) {
return "Endpoint must start with https:// or http://";
}

return null;
},
},
{
name: "token",
title: "API Token",
required: true,
type: "text",
secret: true,
},
],
},
"cloudflare-d1": {
name: "cloudflare-d1",
displayName: "Cloudflare D1",
Expand Down Expand Up @@ -211,8 +246,10 @@ export type SupportedDriver =
| "turso"
| "rqlite"
| "valtown"
| "starbase"
| "cloudflare-d1"
| "sqlite-filehandler";

export type SavedConnectionStorage = "remote" | "local";
export type SavedConnectionLabel = "gray" | "red" | "yellow" | "green" | "blue";

Expand Down
62 changes: 62 additions & 0 deletions src/app/proxy/starbase/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { HttpStatus } from "@/constants/http-status";
import { headers } from "next/headers";
import { NextRequest, NextResponse } from "next/server";

export const runtime = "edge";

export async function POST(req: NextRequest) {
// Get the account id and database id from header
const endpoint = headers().get("x-starbase-url");

if (!endpoint) {
return NextResponse.json(
{
error: "Please provide account id or database id",
},
{ status: HttpStatus.BAD_REQUEST }
);
}

const authorizationHeader = headers().get("Authorization");
if (!authorizationHeader) {
return NextResponse.json(
{
error: "Please provide authorization header",
},
{ status: HttpStatus.BAD_REQUEST }
);
}

try {
const url = `${endpoint.replace(/\/$/, "")}/query/raw`;

const response: { errors: { message: string }[] } = await (
await fetch(url, {
method: "POST",
headers: {
Authorization: authorizationHeader,
"Content-Type": "application/json",
},
body: JSON.stringify(await req.json()),
})
).json();

if (response.errors && response.errors.length > 0) {
return NextResponse.json(
{
error: response.errors[0].message,
},
{ status: HttpStatus.INTERNAL_SERVER_ERROR }
);
}

return NextResponse.json(response);
} catch (e) {
return NextResponse.json(
{
error: (e as Error).message,
},
{ status: HttpStatus.BAD_REQUEST }
);
}
}
2 changes: 1 addition & 1 deletion src/components/gui/main-connection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function MainConnectionContainer() {
*/
useLayoutEffect(() => {
console.info("Injecting message into window object");
window.internalPubSub = new InternalPubSub();
if (!window.internalPubSub) window.internalPubSub = new InternalPubSub();
}, [driver]);

useEffect(() => {
Expand Down
5 changes: 4 additions & 1 deletion src/components/gui/query-result.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ export default function QueryResult({
return { _tag: "EXPLAIN", value: result.result } as const;
}

const state = OptimizeTableState.createFromResult(result.result);
const state = OptimizeTableState.createFromResult(
databaseDriver,
result.result
);
state.setReadOnlyMode(true);
state.mismatchDetection = databaseDriver.getFlags().mismatchDetection;
return { _tag: "QUERY", value: state } as const;
Expand Down
12 changes: 7 additions & 5 deletions src/components/gui/table-optimized/OptimizeTableState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { selectArrayFromIndexList } from "@/components/lib/export-helper";
import { OptimizeTableHeaderProps } from ".";
import { LucideKey, LucideKeySquare, LucideSigma } from "lucide-react";
import {
BaseDriver,
DatabaseResultSet,
DatabaseTableSchema,
TableColumnDataType,
Expand Down Expand Up @@ -37,14 +38,19 @@ export default class OptimizeTableState {
protected changeLogs: Record<number, OptimizeTableRowValue> = {};

static createFromResult(
driver: BaseDriver,
dataResult: DatabaseResultSet,
schemaResult?: DatabaseTableSchema
) {
return new OptimizeTableState(
dataResult.headers.map((header) => {
const headerData = schemaResult
? schemaResult.columns.find((c) => c.name === header.name)
: undefined;

let initialSize = 150;
const headerName = header.name;
const dataType = header.type;
const dataType = header.type ?? driver.inferTypeFromHeader(headerData);

if (
dataType === TableColumnDataType.INTEGER ||
Expand All @@ -67,10 +73,6 @@ export default class OptimizeTableState {
initialSize = Math.max(150, Math.min(500, maxSize * 8));
}

const headerData = schemaResult
? schemaResult.columns.find((c) => c.name === header.name)
: undefined;

// --------------------------------------
// Matching foreign key
// --------------------------------------
Expand Down
1 change: 1 addition & 0 deletions src/components/gui/tabs/table-data-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export default function TableDataWindow({
});

const tableState = OptimizeTableState.createFromResult(
databaseDriver,
dataResult,
schemaResult
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/gui/toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function ToolbarButton({
if (tooltip) {
return (
<Tooltip>
<TooltipTrigger>{buttonContent}</TooltipTrigger>
<TooltipTrigger asChild>{buttonContent}</TooltipTrigger>
<TooltipContent>{tooltip}</TooltipContent>
</Tooltip>
);
Expand Down
10 changes: 10 additions & 0 deletions src/drivers/base-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ export interface DriverFlags {
supportModifyColumn: boolean;
mismatchDetection: boolean;
dialect: SupportedDialect;

// If database supports this, we don't need
// to make a separate request to get updated
// data when update
supportInsertReturning: boolean;
supportUpdateReturning: boolean;
}

export interface DatabaseTableColumnChange {
Expand Down Expand Up @@ -252,6 +258,10 @@ export abstract class BaseDriver {
tableName: string
): Promise<DatabaseTableSchema>;

abstract inferTypeFromHeader(
header?: DatabaseTableColumn
): TableColumnDataType | undefined;

abstract trigger(
schemaName: string,
name: string
Expand Down
49 changes: 37 additions & 12 deletions src/drivers/common-sql-imp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,25 @@ export default abstract class CommonSQLImplement extends BaseDriver {

const sqls = ops.map((op) => {
if (op.operation === "INSERT")
return insertInto(this, schemaName, tableName, op.values);
return insertInto(
this,
schemaName,
tableName,
op.values,
this.getFlags().supportInsertReturning
);

if (op.operation === "DELETE")
return deleteFrom(this, schemaName, tableName, op.where);

return updateTable(this, schemaName, tableName, op.values, op.where);
return updateTable(
this,
schemaName,
tableName,
op.values,
op.where,
this.getFlags().supportInsertReturning
);
});

const result = await this.transaction(sqls);
Expand All @@ -57,18 +71,29 @@ export default abstract class CommonSQLImplement extends BaseDriver {
}

if (op.operation === "UPDATE") {
const selectResult = await this.findFirst(
schemaName,
tableName,
op.where
);
if (r.rows.length === 1)
// This is when database support RETURNING
tmp.push({
record: r.rows[0],
});
else {
const selectResult = await this.findFirst(
schemaName,
tableName,
op.where
);

tmp.push({
lastId: r.lastInsertRowid,
record: selectResult.rows[0],
});
tmp.push({
lastId: r.lastInsertRowid,
record: selectResult.rows[0],
});
}
} else if (op.operation === "INSERT") {
if (op.autoIncrementPkColumn) {
if (r.rows.length === 1) {
tmp.push({
record: r.rows[0],
});
} else if (op.autoIncrementPkColumn) {
const selectResult = await this.findFirst(schemaName, tableName, {
[op.autoIncrementPkColumn]: r.lastInsertRowid,
});
Expand Down
8 changes: 8 additions & 0 deletions src/drivers/mysql/mysql-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DriverFlags,
DatabaseSchemaItem,
DatabaseTableColumn,
TableColumnDataType,
} from "../base-driver";
import CommonSQLImplement from "../common-sql-imp";
import { escapeSqlValue } from "../sqlite/sql-helper";
Expand Down Expand Up @@ -53,6 +54,9 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement {
mismatchDetection: false,
supportCreateUpdateTable: false,
dialect: "mysql",

supportInsertReturning: false,
supportUpdateReturning: false,
};
}

Expand Down Expand Up @@ -153,4 +157,8 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement {
createUpdateTableSchema(): string[] {
throw new Error("Not implemented");
}

inferTypeFromHeader(): TableColumnDataType | undefined {
return undefined;
}
}
Loading

0 comments on commit b11456c

Please sign in to comment.