Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/store/src/api/tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { randomUUID } from "crypto";
import { eq } from "drizzle-orm";
import { DrizzleD1Database } from "drizzle-orm/d1";
import * as schema from "../schema";
import { Table, projectTables, tables, teamProjects, teams } from "../schema";
import {
NewTable,
projectTables,
tables,
teamProjects,
teams,
} from "../schema";
import { slugify } from "./utils";

export function initTables(
Expand Down Expand Up @@ -31,7 +37,7 @@ export function initTables(
tbl.prepare(projectTableSql).bind(projectTableParams),
tbl.prepare(tableSql).bind(tableParams),
]);
const table: Table = { id: tableId, name, description, schema, slug };
const table: NewTable = { id: tableId, name, description, schema, slug };
return table;
},

Expand All @@ -44,7 +50,7 @@ export function initTables(
.orderBy(tables.name)
.all();
const mapped = res.map((r) => r.tables);
return mapped;
return mapped as unknown as schema.Table[];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is weird, see my comment in packages/store/src/schema/index.ts for an explanation.

},

tableTeam: async function (tableId: string) {
Expand Down
7 changes: 6 additions & 1 deletion packages/store/src/schema/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Table as TablelandTable } from "@tableland/sdk";
import { InferInsertModel, InferSelectModel } from "drizzle-orm";
import {
integer,
Expand Down Expand Up @@ -150,7 +151,11 @@ export const teamInvites = sqliteTable("team_invites", {
claimedAt: text("claimed_at"),
});

export type Table = InferSelectModel<typeof tables>;
export type Schema = TablelandTable["schema"];

export type Table = Omit<InferSelectModel<typeof tables>, "schema"> & {
schema: Schema;
};
export type NewTable = InferInsertModel<typeof tables>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an interesting/quirky behavior when reading data from a table that has a text column, but JSON strings are stored in that column: The validator marshals the data as JSON within the containing JSON response payload, not as a string property of the response payload. This is pretty convenient if you're simply querying the validator and using the returned JSON data because you don't have to do any further parsing of your JSON strings to convert them to objects. It's not so convenient in the context of using an ORM like Drizzle where types are inferred from your table schema definitions. Drizzle says "you defined this column as text, so when you query it, it will come back as text". So to get around that, I hacked this inferred drizzle type which represents the data returned from the tables table. Not ideal, but not sure what else to do short of adding support of an option to the validator to leave text as text no matter what.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What really isn't nice is the asymmetry between the table getting written to with string but returning a Schema

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't ideal, but seems like a worthy compromise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually looking into Drizzle's support for custom column types. Have a feeling it will work.


export type UserSealed = InferSelectModel<typeof users>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { generateCreateTableStatement } from "@/lib/schema";
import { cn } from "@/lib/utils";
import {
Database,
Expand Down Expand Up @@ -108,8 +109,9 @@ export default function ExecDeployment({
baseUrl: helpers.getBaseUrl(chainId),
autoWait: false,
});
// TODO: Table.schema will be JSON, convert it to SQL create table statement.
const res = await tbl.exec(table.schema);

const stmt = generateCreateTableStatement(table.name, table.schema);
const res = await tbl.exec(stmt);
Comment on lines +112 to +114
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convert the Schema object to a create table statement and execute it.

if (res.error) {
throw new Error(res.error);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
"use client";

import { newTable } from "@/app/actions";
import SchemaBuilder, {
createTableStatementFromObject,
} from "@/components/schema-builder";
import SchemaBuilder from "@/components/schema-builder";
import { Button } from "@/components/ui/button";
import {
Form,
Expand All @@ -16,14 +14,13 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { createTableAtom } from "@/store/create-table";
import { cleanSchema, generateCreateTableStatement } from "@/lib/schema";
import { zodResolver } from "@hookform/resolvers/zod";
import { helpers } from "@tableland/sdk";
import { schema } from "@tableland/studio-store";
import { useAtom } from "jotai";
import { Loader2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { useTransition } from "react";
import { useState, useTransition } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import * as z from "zod";

Expand Down Expand Up @@ -51,7 +48,7 @@ export default function NewTable({ project, team, envs }: Props) {
const [pending, startTransition] = useTransition();
const router = useRouter();

const [createTable, setCreateTable] = useAtom(createTableAtom);
const [schema, setSchema] = useState<schema.Schema>({ columns: [] });

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
Expand All @@ -73,17 +70,13 @@ export default function NewTable({ project, team, envs }: Props) {

function onSubmit(values: z.infer<typeof formSchema>) {
startTransition(async () => {
const statement = createTableStatementFromObject(
createTable,
await newTable(
project,
values.name,
values.description,
cleanSchema(schema),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanSchema just removes any column definitions that don't have a name (this is possible in the UI to create an effectively empty column. We could change the ux to error out on those columns, but this seems easy enough)

);
if (!statement) {
console.error("No statement");
return;
}
await newTable(project, values.name, statement, values.description);
router.replace(`/${team.slug}/${project.slug}`);
setCreateTable({ columns: [] });
});
}

Expand Down Expand Up @@ -128,8 +121,8 @@ export default function NewTable({ project, team, envs }: Props) {
/>
<div className="space-y-2">
<FormLabel>Columns</FormLabel>
<SchemaBuilder />
<pre>{createTableStatementFromObject(createTable, name)}</pre>
<SchemaBuilder schema={schema} setSchema={setSchema} />
<pre>{generateCreateTableStatement(name, schema)}</pre>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Display the equivalent create statement in real time.

</div>
{/* <div className="space-y-2">
<FormLabel>Deployments</FormLabel>
Expand Down
4 changes: 2 additions & 2 deletions packages/web/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,13 @@ export async function newEnvironment(
export async function newTable(
project: schema.Project,
name: string,
schema: string,
description: string,
schema: schema.Schema,
) {
const table = api.tables.newTable.mutate({
projectId: project.id,
name,
schema,
schema: JSON.stringify(schema),
description,
});
await api.tables.projectTables.revalidate({ projectId: project.id });
Expand Down
5 changes: 2 additions & 3 deletions packages/web/components/crumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,15 @@ export default function Crumb({
return (
<div className={cn("flex", className)}>
{items.map((item, index) => (
<>
<div key={item.label}>
<Link
key={item.label}
href={item.href}
className="text-lg text-muted-foreground hover:text-primary"
>
{item.label}
</Link>
<span className="mx-2 text-lg text-muted-foreground">/</span>
</>
</div>
))}
<p className="text-lg">{title}</p>
</div>
Expand Down
Loading