Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
53 changes: 49 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"@tableland/sdk": "^4.5.1",
"@tableland/sdk": "^4.5.2",
"@tableland/studio-mail": "^0.0.0",
"@tableland/studio-store": "^1.0.0",
"@trpc/server": "^10.38.4",
Expand Down
18 changes: 14 additions & 4 deletions packages/api/src/routers/tables.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { Validator, helpers } from "@tableland/sdk";
import { Store } from "@tableland/studio-store";
import { Schema, Store } from "@tableland/studio-store";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { projectProcedure, publicProcedure, router } from "../trpc";

const schemaSchema: z.ZodType<Schema> = z.object({
columns: z.array(
z.object({
name: z.string().nonempty(),
type: z.string().nonempty(),
constraints: z.array(z.string().nonempty()).optional(),
}),
),
table_constraints: z.array(z.string().nonempty()).optional(),
});

Comment on lines +7 to +17
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 the best way I could find to create an api input definition that conforms to the Schema type. At least if the Schema type changes, this will cause a TS error.

export function tablesRouter(store: Store) {
return router({
projectTables: publicProcedure
Expand All @@ -15,8 +26,8 @@ export function tablesRouter(store: Store) {
.input(
z.object({
name: z.string(),
schema: z.string(),
description: z.string().nonempty(),
schema: schemaSchema,
}),
)
.mutation(async ({ input }) => {
Expand Down Expand Up @@ -47,12 +58,11 @@ export function tablesRouter(store: Store) {
tableId: input.tableId,
});

// TODO: Figure out a standard way of encoding schema for both Tables created in Studio and imported tables.
const table = await store.tables.createTable(
input.projectId,
input.name,
input.description,
JSON.stringify(tablelandTable.schema),
tablelandTable.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.

Pretty cool, just send the Schema type into the db layer.

);
const createdAttr = tablelandTable.attributes?.find(
(attr) => attr.traitType === "created",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
},
"license": "MIT AND Apache-2.0",
"dependencies": {
"@tableland/sdk": "^4.5.1",
"@tableland/sdk": "^4.5.2",
"@tableland/sqlparser": "^1.3.0",
"@tableland/studio-client": "^0.0.0",
"@toruslabs/broadcast-channel": "^8.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"@tableland/sdk": "^4.5.1",
"@tableland/sdk": "^4.5.2",
"drizzle-orm": "^0.28.5",
"iron-session": "^6.3.1"
}
Expand Down
3 changes: 2 additions & 1 deletion packages/store/src/api/tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Database } from "@tableland/sdk";
import { randomUUID } from "crypto";
import { eq } from "drizzle-orm";
import { DrizzleD1Database } from "drizzle-orm/d1";
import { Schema } from "../custom-types";
import * as schema from "../schema";
import { Table, projectTables, tables, teamProjects, teams } from "../schema";
import { slugify } from "./utils";
Expand All @@ -15,7 +16,7 @@ export function initTables(
projectId: string,
name: string,
description: string,
schema: string,
schema: Schema,
) {
const tableId = randomUUID();
const slug = slugify(name);
Expand Down
18 changes: 18 additions & 0 deletions packages/store/src/custom-types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Schema as SDKSchema } from "@tableland/sdk";
import { customType } from "drizzle-orm/sqlite-core";

export type Schema = SDKSchema;

export const schema = customType<{
data: Schema;
}>({
dataType() {
return "text";
},
fromDriver(value: unknown): Schema {
return value as Schema;
},
toDriver(value: Schema): string {
return JSON.stringify(value);
},
});
Comment on lines +4 to +18
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Our custom drizzle column type. I had to use unknown in the fromDriver implementation because we have an asymmetric mapping where we go from Schema -> string on the way into the db and Schema -> Schema on the way out of the db. This generic customType function for drizzle works better when it's a symmetric mapping like Schema <-> string.

3 changes: 2 additions & 1 deletion packages/store/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { init } from "./api";
import { Schema } from "./custom-types";
import * as schema from "./schema";

type Store = ReturnType<typeof init>;

export { Store, init, schema };
export { Schema, Store, init, schema };
3 changes: 2 additions & 1 deletion packages/store/src/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
text,
uniqueIndex,
} from "drizzle-orm/sqlite-core";
import { schema } from "../custom-types";

export const users = sqliteTable(
"users",
Expand Down Expand Up @@ -80,7 +81,7 @@ export const tables = sqliteTable("tables", {
slug: text("slug").notNull(),
name: text("name").notNull(),
description: text("description").notNull(),
schema: text("schema").notNull(),
schema: schema("schema").notNull(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using the custom type in the table definition. I verified this doesn't effect the generated migration (create table statement) because it still just creates a text column to back this (according to our custom type definition).

});

export const projectTables = sqliteTable(
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 { Schema, schema } from "@tableland/studio-store";
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>({ 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 @@ -2,7 +2,7 @@

import { api } from "@/trpc/server-invoker";
import { Auth } from "@tableland/studio-api";
import { schema } from "@tableland/studio-store";
import { schema, Schema } from "@tableland/studio-store";

export async function authenticated() {
return await api.auth.authenticated.query();
Expand Down Expand Up @@ -80,8 +80,8 @@ export async function newEnvironment(
export async function newTable(
project: schema.Project,
name: string,
schema: string,
description: string,
schema: Schema,
) {
const table = api.tables.newTable.mutate({
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