diff --git a/package-lock.json b/package-lock.json index 87ebd44e..fe566132 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7214,6 +7214,7 @@ "version": "4.5.1", "resolved": "https://registry.npmjs.org/@tableland/sdk/-/sdk-4.5.1.tgz", "integrity": "sha512-ffvZYA9v6eNFsPYWtYJFfPT48rgtPIY4VTdcJbV2so7r4XGPw7JY4qdkWNO+2J8l4vOR9dusiSOHKVhHsNWeOA==", + "dev": true, "dependencies": { "@async-generators/from-emitter": "^0.3.0", "@tableland/evm": "^4.3.0", @@ -40430,7 +40431,7 @@ "version": "1.0.0", "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", @@ -40588,6 +40589,17 @@ "tslib": "^2.4.0" } }, + "packages/api/node_modules/@tableland/sdk": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@tableland/sdk/-/sdk-4.5.2.tgz", + "integrity": "sha512-fshBi0tyRGhLQY3C23XlGpbdrD2bPGiCFnOgGf7W83S6vB1aELM3yTMaVuQslmkwOcTW4bzSkzlq4TMPd0LR4A==", + "dependencies": { + "@async-generators/from-emitter": "^0.3.0", + "@tableland/evm": "^4.3.0", + "@tableland/sqlparser": "^1.3.0", + "ethers": "^5.7.2" + } + }, "packages/api/node_modules/nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -40684,7 +40696,7 @@ "version": "0.0.0", "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", @@ -40704,6 +40716,17 @@ "studio": "dist/cli.js" } }, + "packages/cli/node_modules/@tableland/sdk": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@tableland/sdk/-/sdk-4.5.2.tgz", + "integrity": "sha512-fshBi0tyRGhLQY3C23XlGpbdrD2bPGiCFnOgGf7W83S6vB1aELM3yTMaVuQslmkwOcTW4bzSkzlq4TMPd0LR4A==", + "dependencies": { + "@async-generators/from-emitter": "^0.3.0", + "@tableland/evm": "^4.3.0", + "@tableland/sqlparser": "^1.3.0", + "ethers": "^5.7.2" + } + }, "packages/cli/node_modules/@toruslabs/openlogin-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@toruslabs/openlogin-utils/-/openlogin-utils-4.7.0.tgz", @@ -41333,11 +41356,22 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@tableland/sdk": "^4.5.1", + "@tableland/sdk": "^4.5.2", "drizzle-orm": "^0.28.5", "iron-session": "^6.3.1" } }, + "packages/store/node_modules/@tableland/sdk": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@tableland/sdk/-/sdk-4.5.2.tgz", + "integrity": "sha512-fshBi0tyRGhLQY3C23XlGpbdrD2bPGiCFnOgGf7W83S6vB1aELM3yTMaVuQslmkwOcTW4bzSkzlq4TMPd0LR4A==", + "dependencies": { + "@async-generators/from-emitter": "^0.3.0", + "@tableland/evm": "^4.3.0", + "@tableland/sqlparser": "^1.3.0", + "ethers": "^5.7.2" + } + }, "packages/web": { "name": "@tableland/studio", "version": "0.0.0", @@ -41360,7 +41394,7 @@ "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-toast": "^1.1.4", "@radix-ui/react-tooltip": "^1.0.6", - "@tableland/sdk": "^4.5.1", + "@tableland/sdk": "^4.5.2", "@tableland/studio-api": "^1.0.0", "@tableland/studio-client": "^0.0.0", "@tableland/studio-store": "^1.0.0", @@ -41537,6 +41571,17 @@ "tslib": "^2.4.0" } }, + "packages/web/node_modules/@tableland/sdk": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@tableland/sdk/-/sdk-4.5.2.tgz", + "integrity": "sha512-fshBi0tyRGhLQY3C23XlGpbdrD2bPGiCFnOgGf7W83S6vB1aELM3yTMaVuQslmkwOcTW4bzSkzlq4TMPd0LR4A==", + "dependencies": { + "@async-generators/from-emitter": "^0.3.0", + "@tableland/evm": "^4.3.0", + "@tableland/sqlparser": "^1.3.0", + "ethers": "^5.7.2" + } + }, "packages/web/node_modules/@trpc/client": { "version": "10.38.4", "resolved": "https://registry.npmjs.org/@trpc/client/-/client-10.38.4.tgz", diff --git a/packages/api/package.json b/packages/api/package.json index 39ea2256..87f7e9a2 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -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", diff --git a/packages/api/src/routers/tables.ts b/packages/api/src/routers/tables.ts index 0eaa21c9..afe1a2d9 100644 --- a/packages/api/src/routers/tables.ts +++ b/packages/api/src/routers/tables.ts @@ -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 = 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(), +}); + export function tablesRouter(store: Store) { return router({ projectTables: publicProcedure @@ -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 }) => { @@ -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, ); const createdAttr = tablelandTable.attributes?.find( (attr) => attr.traitType === "created", diff --git a/packages/cli/package.json b/packages/cli/package.json index 3fb664e6..25780a02 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -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", diff --git a/packages/store/package.json b/packages/store/package.json index e856bb9f..42a0b8da 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -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" } diff --git a/packages/store/src/api/tables.ts b/packages/store/src/api/tables.ts index 4d391a0c..17c97924 100644 --- a/packages/store/src/api/tables.ts +++ b/packages/store/src/api/tables.ts @@ -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"; @@ -15,7 +16,7 @@ export function initTables( projectId: string, name: string, description: string, - schema: string, + schema: Schema, ) { const tableId = randomUUID(); const slug = slugify(name); diff --git a/packages/store/src/custom-types/index.ts b/packages/store/src/custom-types/index.ts new file mode 100644 index 00000000..718e1610 --- /dev/null +++ b/packages/store/src/custom-types/index.ts @@ -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); + }, +}); diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts index 3490971e..d39632fa 100644 --- a/packages/store/src/index.ts +++ b/packages/store/src/index.ts @@ -1,6 +1,7 @@ import { init } from "./api"; +import { Schema } from "./custom-types"; import * as schema from "./schema"; type Store = ReturnType; -export { Store, init, schema }; +export { Schema, Store, init, schema }; diff --git a/packages/store/src/schema/index.ts b/packages/store/src/schema/index.ts index 34bfc1ca..ebfe2ea6 100644 --- a/packages/store/src/schema/index.ts +++ b/packages/store/src/schema/index.ts @@ -6,6 +6,7 @@ import { text, uniqueIndex, } from "drizzle-orm/sqlite-core"; +import { schema } from "../custom-types"; export const users = sqliteTable( "users", @@ -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(), }); export const projectTables = sqliteTable( diff --git a/packages/web/app/[team]/[project]/(project)/deployments/[[...slug]]/_components/exec-deployment.tsx b/packages/web/app/[team]/[project]/(project)/deployments/[[...slug]]/_components/exec-deployment.tsx index bb2ae597..a121ed78 100644 --- a/packages/web/app/[team]/[project]/(project)/deployments/[[...slug]]/_components/exec-deployment.tsx +++ b/packages/web/app/[team]/[project]/(project)/deployments/[[...slug]]/_components/exec-deployment.tsx @@ -18,6 +18,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { generateCreateTableStatement } from "@/lib/schema"; import { cn } from "@/lib/utils"; import { Database, @@ -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); if (res.error) { throw new Error(res.error); } diff --git a/packages/web/app/[team]/[project]/new-table/_components/new-table-form.tsx b/packages/web/app/[team]/[project]/new-table/_components/new-table-form.tsx index f9022b6a..50834294 100644 --- a/packages/web/app/[team]/[project]/new-table/_components/new-table-form.tsx +++ b/packages/web/app/[team]/[project]/new-table/_components/new-table-form.tsx @@ -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, @@ -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"; @@ -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({ columns: [] }); const form = useForm>({ resolver: zodResolver(formSchema), @@ -73,17 +70,13 @@ export default function NewTable({ project, team, envs }: Props) { function onSubmit(values: z.infer) { startTransition(async () => { - const statement = createTableStatementFromObject( - createTable, + await newTable( + project, values.name, + values.description, + cleanSchema(schema), ); - if (!statement) { - console.error("No statement"); - return; - } - await newTable(project, values.name, statement, values.description); router.replace(`/${team.slug}/${project.slug}`); - setCreateTable({ columns: [] }); }); } @@ -128,8 +121,8 @@ export default function NewTable({ project, team, envs }: Props) { />
Columns - -
{createTableStatementFromObject(createTable, name)}
+ +
{generateCreateTableStatement(name, schema)}
{/*
Deployments diff --git a/packages/web/app/actions.ts b/packages/web/app/actions.ts index 7d51f8c1..c2afb721 100644 --- a/packages/web/app/actions.ts +++ b/packages/web/app/actions.ts @@ -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(); @@ -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, diff --git a/packages/web/components/crumb.tsx b/packages/web/components/crumb.tsx index a23d6911..138f4234 100644 --- a/packages/web/components/crumb.tsx +++ b/packages/web/components/crumb.tsx @@ -15,16 +15,15 @@ export default function Crumb({ return (
{items.map((item, index) => ( - <> +
{item.label} / - +
))}

{title}

diff --git a/packages/web/components/schema-builder.tsx b/packages/web/components/schema-builder.tsx index 7755b958..398e6090 100644 --- a/packages/web/components/schema-builder.tsx +++ b/packages/web/components/schema-builder.tsx @@ -6,9 +6,11 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import { CreateTable, createTableAtom } from "@/store/create-table"; -import { useAtom } from "jotai"; +import { Constraint, hasConstraint, setConstraint } from "@/lib/schema"; +import { CheckedState } from "@radix-ui/react-checkbox"; +import { Schema } from "@tableland/studio-store"; import { Plus, X } from "lucide-react"; +import { Dispatch, SetStateAction } from "react"; import { Button } from "./ui/button"; import { Checkbox } from "./ui/checkbox"; import { Input } from "./ui/input"; @@ -25,59 +27,17 @@ function isValidColumnName(variable: string) { return columnNameRegex.test(variable); } -export function createTableStatementFromObject( - tableObj: CreateTable, - name: string, -) { - if (!name) return false; - let statement = "CREATE TABLE " + name + " ("; - let invalid = false; - - let columns = tableObj.columns - .filter((column) => column.name !== "") - .map((column) => { - if (!isValidColumnName(column.name)) { - invalid = true; - return false; - } - let columnStatement = "\n " + column.name + " " + column.type; - - if (column.notNull) { - columnStatement += " NOT NULL"; - } - if (column.unique) { - columnStatement += " UNIQUE"; - } - if (column.primaryKey) { - columnStatement += " PRIMARY KEY"; - } - if (column.default) { - let defaultValue = column.default; - if (!column.type.toUpperCase().startsWith("INTEGER")) { - defaultValue = "'" + defaultValue + "'"; - } - columnStatement += " DEFAULT " + defaultValue; - } - - return columnStatement; - }); - - statement += columns.join(", "); - statement += "\n);"; - - if (invalid) { - return false; - } - - return statement; -} - -export default function SchemaBuilder() { - const [tbl, setCreateTable] = useAtom(createTableAtom); +export default function SchemaBuilder({ + schema, + setSchema, +}: { + schema: Schema; + setSchema: Dispatch>; +}) { return (
- {tbl.columns.length > 0 && ( + {schema.columns.length > 0 && ( Name @@ -85,14 +45,20 @@ export default function SchemaBuilder() { Not Null Primary Key Unique - Default Value )} - {tbl.columns.map((column, index) => { - return ; + {schema.columns.map((column, index) => { + return ( + + ); })}
@@ -102,18 +68,10 @@ export default function SchemaBuilder() { variant="outline" size="sm" onClick={() => { - setCreateTable((prev) => { - const newColumn = { - name: "", - type: "text", - notNull: false, - primaryKey: false, - unique: false, - default: null, - }; + setSchema((prev) => { return { ...prev, - columns: [...prev.columns, newColumn], + columns: [...prev.columns, { name: "", type: "text" }], }; }); }} @@ -125,15 +83,20 @@ export default function SchemaBuilder() { ); } -function RemoveColumn({ columnIndex }: { columnIndex: number }) { - const [tbl, setAtom] = useAtom(createTableAtom); +function RemoveColumn({ + columnIndex, + setSchema, +}: { + columnIndex: number; + setSchema: Dispatch>; +}) { return (