From 4b6a25649f941567730d5400d49bf93c4e4dea8b Mon Sep 17 00:00:00 2001 From: Aaron Sutula Date: Tue, 26 Sep 2023 11:27:37 -0600 Subject: [PATCH 1/7] wip on new schema format Signed-off-by: Aaron Sutula --- packages/web/components/schema-builder.tsx | 312 +++++++++++---------- 1 file changed, 167 insertions(+), 145 deletions(-) diff --git a/packages/web/components/schema-builder.tsx b/packages/web/components/schema-builder.tsx index 7755b958..d052fc05 100644 --- a/packages/web/components/schema-builder.tsx +++ b/packages/web/components/schema-builder.tsx @@ -7,8 +7,10 @@ import { TableRow, } from "@/components/ui/table"; import { CreateTable, createTableAtom } from "@/store/create-table"; +import { Table as TblTable } from "@tableland/sdk"; import { useAtom } from "jotai"; import { Plus, X } from "lucide-react"; +import { useState } from "react"; import { Button } from "./ui/button"; import { Checkbox } from "./ui/checkbox"; import { Input } from "./ui/input"; @@ -20,6 +22,19 @@ import { SelectValue, } from "./ui/select"; +type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; + +type Schema = DeepWriteable; + +const foo: Schema = { + columns: [ + { + name: "", + type: "", + }, + ], +}; + function isValidColumnName(variable: string) { var columnNameRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/; return columnNameRegex.test(variable); @@ -73,11 +88,12 @@ export function createTableStatementFromObject( } export default function SchemaBuilder() { - const [tbl, setCreateTable] = useAtom(createTableAtom); + const [schema, setSchema] = useState({ columns: [] }); + return (
- {tbl.columns.length > 0 && ( + {schema.columns.length > 0 && ( Name @@ -91,7 +107,7 @@ export default function SchemaBuilder() { )} - {tbl.columns.map((column, index) => { + {schema.columns.map((column, index) => { return ; })} @@ -102,18 +118,13 @@ 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", constraints: [] }, + ], }; }); }} @@ -123,140 +134,151 @@ export default function SchemaBuilder() { ); -} -function RemoveColumn({ columnIndex }: { columnIndex: number }) { - const [tbl, setAtom] = useAtom(createTableAtom); - return ( - - ); + function RemoveColumn({ columnIndex }: { columnIndex: number }) { + const [tbl, setAtom] = useAtom(createTableAtom); + return ( + + ); + } + + function CreateColumn({ columnIndex }: { columnIndex: number }) { + const column = schema.columns[columnIndex]; + + return ( + + + { + setSchema((prev) => { + prev.columns[columnIndex].name = e.target.value; + return { + ...prev, + }; + }); + }} + /> + + + + + + { + setSchema((prev) => { + setNotNull(prev.columns[columnIndex], !!state); + return { + ...prev, + }; + }); + }} + /> + + + { + setCreateTable((prev) => { + prev.columns[columnIndex].primaryKey = !!state; + return { + ...prev, + }; + }); + }} + /> + + + { + setCreateTable((prev) => { + prev.columns[columnIndex].unique = !!state; + return { + ...prev, + }; + }); + }} + /> + + + { + setCreateTable((prev) => { + prev.columns[columnIndex].default = e.target.value; + return { + ...prev, + }; + }); + }} + /> + + + + + + ); + } } -function CreateColumn({ columnIndex }: { columnIndex: number }) { - const [tbl, setCreateTable] = useAtom(createTableAtom); - const column = tbl && tbl.columns[columnIndex]; +function hasNotNull(column: Schema["columns"][number]) { + return column.constraints?.includes("NOT NULL"); +} - return ( - - - { - setCreateTable((prev) => { - prev.columns[columnIndex].name = e.target.value; - return { - ...prev, - }; - }); - }} - /> - - - - - - { - setCreateTable((prev) => { - prev.columns[columnIndex].notNull = !!state; - return { - ...prev, - }; - }); - }} - /> - - - { - setCreateTable((prev) => { - prev.columns[columnIndex].primaryKey = !!state; - return { - ...prev, - }; - }); - }} - /> - - - { - setCreateTable((prev) => { - prev.columns[columnIndex].unique = !!state; - return { - ...prev, - }; - }); - }} - /> - - - { - setCreateTable((prev) => { - prev.columns[columnIndex].default = e.target.value; - return { - ...prev, - }; - }); - }} - /> - - - - - - ); +function setNotNull(column: Schema["columns"][number], value: boolean) { + if (value) { + column.constraints?.push("NOT NULL"); + } else { + column.constraints?.splice(column.constraints.indexOf("NOT NULL"), 1); + } } From f94f48be4ebacc5d6a79cdc98b85d115d36a8452 Mon Sep 17 00:00:00 2001 From: Aaron Sutula Date: Tue, 26 Sep 2023 12:06:44 -0600 Subject: [PATCH 2/7] more wip Signed-off-by: Aaron Sutula --- packages/web/components/schema-builder.tsx | 48 +++++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/web/components/schema-builder.tsx b/packages/web/components/schema-builder.tsx index d052fc05..4093d3aa 100644 --- a/packages/web/components/schema-builder.tsx +++ b/packages/web/components/schema-builder.tsx @@ -221,10 +221,10 @@ export default function SchemaBuilder() { { - setCreateTable((prev) => { - prev.columns[columnIndex].primaryKey = !!state; + setSchema((prev) => { + setPrimaryKey(prev.columns[columnIndex], !!state); return { ...prev, }; @@ -235,10 +235,10 @@ export default function SchemaBuilder() { { - setCreateTable((prev) => { - prev.columns[columnIndex].unique = !!state; + setSchema((prev) => { + setUnique(prev.columns[columnIndex], !!state); return { ...prev, }; @@ -282,3 +282,39 @@ function setNotNull(column: Schema["columns"][number], value: boolean) { column.constraints?.splice(column.constraints.indexOf("NOT NULL"), 1); } } + +function hasPrimaryKey(column: Schema["columns"][number]) { + return column.constraints?.includes("PRIMARY KEY"); +} + +function setPrimaryKey(column: Schema["columns"][number], value: boolean) { + if (value) { + column.constraints?.push("PRIMARY KEY"); + } else { + column.constraints?.splice(column.constraints.indexOf("PRIMARY KEY"), 1); + } +} + +function hasUnique(column: Schema["columns"][number]) { + return column.constraints?.includes("UNIQUE"); +} + +function setUnique(column: Schema["columns"][number], value: boolean) { + if (value) { + column.constraints?.push("UNIQUE"); + } else { + column.constraints?.splice(column.constraints.indexOf("UNIQUE"), 1); + } +} + +function hasDefault(column: Schema["columns"][number]) { + return column.constraints?.includes("UNIQUE"); +} + +function setDefault(column: Schema["columns"][number], value: any) { + if (value) { + column.constraints?.push("UNIQUE"); + } else { + column.constraints?.splice(column.constraints.indexOf("UNIQUE"), 1); + } +} From c54883771f5b81ad180da2641fff63fa666ae98d Mon Sep 17 00:00:00 2001 From: Aaron Sutula Date: Wed, 27 Sep 2023 12:16:14 -0600 Subject: [PATCH 3/7] converted schema builder to use standard schema type and store that json Signed-off-by: Aaron Sutula --- packages/store/src/api/tables.ts | 12 +- packages/store/src/schema/index.ts | 7 +- .../_components/exec-deployment.tsx | 6 +- .../new-table/_components/new-table-form.tsx | 27 +- packages/web/app/actions.ts | 4 +- packages/web/components/schema-builder.tsx | 396 +++++++----------- packages/web/lib/schema.ts | 60 +++ packages/web/store/create-table.ts | 27 -- 8 files changed, 240 insertions(+), 299 deletions(-) create mode 100644 packages/web/lib/schema.ts delete mode 100644 packages/web/store/create-table.ts diff --git a/packages/store/src/api/tables.ts b/packages/store/src/api/tables.ts index 4d391a0c..a30b46db 100644 --- a/packages/store/src/api/tables.ts +++ b/packages/store/src/api/tables.ts @@ -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( @@ -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; }, @@ -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[]; }, tableTeam: async function (tableId: string) { diff --git a/packages/store/src/schema/index.ts b/packages/store/src/schema/index.ts index 34bfc1ca..7b4185f1 100644 --- a/packages/store/src/schema/index.ts +++ b/packages/store/src/schema/index.ts @@ -1,3 +1,4 @@ +import { Table as TablelandTable } from "@tableland/sdk"; import { InferInsertModel, InferSelectModel } from "drizzle-orm"; import { integer, @@ -150,7 +151,11 @@ export const teamInvites = sqliteTable("team_invites", { claimedAt: text("claimed_at"), }); -export type Table = InferSelectModel; +export type Schema = TablelandTable["schema"]; + +export type Table = Omit, "schema"> & { + schema: Schema; +}; export type NewTable = InferInsertModel; export type UserSealed = InferSelectModel; 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..f72ab2c6 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 { 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..31cbbe0f 100644 --- a/packages/web/app/actions.ts +++ b/packages/web/app/actions.ts @@ -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 }); diff --git a/packages/web/components/schema-builder.tsx b/packages/web/components/schema-builder.tsx index 4093d3aa..d79b9818 100644 --- a/packages/web/components/schema-builder.tsx +++ b/packages/web/components/schema-builder.tsx @@ -6,11 +6,11 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import { CreateTable, createTableAtom } from "@/store/create-table"; -import { Table as TblTable } from "@tableland/sdk"; -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 { useState } from "react"; +import { Dispatch, SetStateAction } from "react"; import { Button } from "./ui/button"; import { Checkbox } from "./ui/checkbox"; import { Input } from "./ui/input"; @@ -22,74 +22,18 @@ import { SelectValue, } from "./ui/select"; -type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; - -type Schema = DeepWriteable; - -const foo: Schema = { - columns: [ - { - name: "", - type: "", - }, - ], -}; - function isValidColumnName(variable: string) { var columnNameRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/; 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 [schema, setSchema] = useState({ columns: [] }); - +export default function SchemaBuilder({ + schema, + setSchema, +}: { + schema: schema.Schema; + setSchema: Dispatch>; +}) { return (
@@ -101,14 +45,20 @@ export default function SchemaBuilder() { Not NullPrimary KeyUnique - Default Value )} {schema.columns.map((column, index) => { - return ; + return ( + + ); })}
@@ -134,187 +84,139 @@ export default function SchemaBuilder() {
); - - function RemoveColumn({ columnIndex }: { columnIndex: number }) { - const [tbl, setAtom] = useAtom(createTableAtom); - return ( - - ); - } - - function CreateColumn({ columnIndex }: { columnIndex: number }) { - const column = schema.columns[columnIndex]; - - return ( - - - { - setSchema((prev) => { - prev.columns[columnIndex].name = e.target.value; - return { - ...prev, - }; - }); - }} - /> - - - - - - { - setSchema((prev) => { - setNotNull(prev.columns[columnIndex], !!state); - return { - ...prev, - }; - }); - }} - /> - - - { - setSchema((prev) => { - setPrimaryKey(prev.columns[columnIndex], !!state); - return { - ...prev, - }; - }); - }} - /> - - - { - setSchema((prev) => { - setUnique(prev.columns[columnIndex], !!state); - return { - ...prev, - }; - }); - }} - /> - - - { - setCreateTable((prev) => { - prev.columns[columnIndex].default = e.target.value; - return { - ...prev, - }; - }); - }} - /> - - - - - - ); - } -} - -function hasNotNull(column: Schema["columns"][number]) { - return column.constraints?.includes("NOT NULL"); -} - -function setNotNull(column: Schema["columns"][number], value: boolean) { - if (value) { - column.constraints?.push("NOT NULL"); - } else { - column.constraints?.splice(column.constraints.indexOf("NOT NULL"), 1); - } -} - -function hasPrimaryKey(column: Schema["columns"][number]) { - return column.constraints?.includes("PRIMARY KEY"); -} - -function setPrimaryKey(column: Schema["columns"][number], value: boolean) { - if (value) { - column.constraints?.push("PRIMARY KEY"); - } else { - column.constraints?.splice(column.constraints.indexOf("PRIMARY KEY"), 1); - } -} - -function hasUnique(column: Schema["columns"][number]) { - return column.constraints?.includes("UNIQUE"); } -function setUnique(column: Schema["columns"][number], value: boolean) { - if (value) { - column.constraints?.push("UNIQUE"); - } else { - column.constraints?.splice(column.constraints.indexOf("UNIQUE"), 1); - } +function RemoveColumn({ + columnIndex, + setSchema, +}: { + columnIndex: number; + setSchema: Dispatch>; +}) { + return ( + + ); } -function hasDefault(column: Schema["columns"][number]) { - return column.constraints?.includes("UNIQUE"); -} +function CreateColumn({ + columnIndex, + schema, + setSchema, +}: { + columnIndex: number; + schema: schema.Schema; + setSchema: Dispatch>; +}) { + const column = schema.columns[columnIndex]; + + const handleConstraintChanged = ( + index: number, + constraint: Constraint, + state: CheckedState, + ) => { + if (state === "indeterminate") return; + const newSchema = { ...schema }; + const { columns, ...rest } = newSchema; + const col = { ...columns[index] }; + columns[index] = setConstraint(col, constraint, state); + setSchema({ columns, ...rest }); + }; + + const handleNameUpdated = (index: number, name: string) => { + const newSchema = { ...schema }; + const { columns, ...rest } = newSchema; + const col = { ...columns[index] }; + columns[index] = { ...col, name }; + setSchema({ columns, ...rest }); + }; + + const handleTypeUpdated = (index: number, type: string) => { + const newSchema = { ...schema }; + const { columns, ...rest } = newSchema; + const col = { ...columns[index] }; + columns[index] = { ...col, type }; + setSchema({ columns, ...rest }); + }; -function setDefault(column: Schema["columns"][number], value: any) { - if (value) { - column.constraints?.push("UNIQUE"); - } else { - column.constraints?.splice(column.constraints.indexOf("UNIQUE"), 1); - } + return ( + + + { + handleNameUpdated(columnIndex, e.target.value); + }} + /> + + + + + + { + handleConstraintChanged(columnIndex, "not null", state); + }} + /> + + + { + handleConstraintChanged(columnIndex, "primary key", state); + }} + /> + + + { + handleConstraintChanged(columnIndex, "unique", state); + }} + /> + + + + + + ); } diff --git a/packages/web/lib/schema.ts b/packages/web/lib/schema.ts new file mode 100644 index 00000000..401a748e --- /dev/null +++ b/packages/web/lib/schema.ts @@ -0,0 +1,60 @@ +import { schema } from "@tableland/studio-store"; + +export type Constraint = "not null" | "primary key" | "unique"; + +export function hasConstraint( + column: schema.Schema["columns"][number], + constraint: Constraint, +) { + return column.constraints ? column.constraints.includes(constraint) : false; +} + +export function setConstraint( + column: schema.Schema["columns"][number], + constraint: Constraint, + value: boolean, +) { + const { constraints, ...rest } = column; + if (value) { + return { ...rest, constraints: [...(constraints || []), constraint] }; + } else { + const res = [...(constraints || [])].filter((c) => c !== constraint); + return { ...rest, constraints: res.length ? res : undefined }; + } +} + +export function generateCreateTableStatement( + tableName: string, + schema: schema.Schema, +) { + if (!tableName.length) { + return ""; + } + const cleaned = cleanSchema(schema); + const columnDefinitions = cleaned.columns + .map((column) => { + const definition = `${column.name} ${column.type}`; + const columnConstraints = !!column.constraints?.length + ? " " + column.constraints.join(" ") + : ""; + return `${definition}${columnConstraints.toLowerCase()}`; + }) + .join(", "); + + const tableConstraints = cleaned.table_constraints + ? cleaned.table_constraints.join(",") + : ""; + + return `create table ${tableName}(${columnDefinitions}${ + tableConstraints ? `, ${tableConstraints}` : "" + });`; +} + +export function cleanSchema(schema: schema.Schema) { + return { + ...schema, + columns: schema.columns.filter( + (column) => column.name.length && column.type.length, + ), + }; +} diff --git a/packages/web/store/create-table.ts b/packages/web/store/create-table.ts deleted file mode 100644 index 7c32fb8f..00000000 --- a/packages/web/store/create-table.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { atom } from "jotai"; - -export interface CreateTable { - columns: CreateColumn[]; -} - -export interface CreateColumn { - name: string; - type: string; - primaryKey?: boolean; - notNull?: boolean; - unique?: boolean; - default?: any; -} - -export const createTableAtom = atom({ - columns: [ - { - name: "id", - type: "integer", - primaryKey: true, - notNull: true, - unique: true, - default: "", - }, - ], -}); From e198e053ae1fb6f92bc9fa205a1230ac8af9a71e Mon Sep 17 00:00:00 2001 From: Aaron Sutula Date: Wed, 27 Sep 2023 12:19:13 -0600 Subject: [PATCH 4/7] crumb key fix Signed-off-by: Aaron Sutula --- packages/web/components/crumb.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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}

From dc408e463513ef8f82d0a567110a5df9b1453222 Mon Sep 17 00:00:00 2001 From: Aaron Sutula Date: Wed, 27 Sep 2023 15:11:32 -0600 Subject: [PATCH 5/7] custom type working Signed-off-by: Aaron Sutula --- packages/api/src/routers/tables.ts | 18 ++++++++++++++---- packages/store/src/api/tables.ts | 5 +++-- packages/store/src/custom-types/index.ts | 18 ++++++++++++++++++ packages/store/src/index.ts | 3 ++- packages/store/src/schema/index.ts | 10 +++------- .../new-table/_components/new-table-form.tsx | 4 ++-- packages/web/app/actions.ts | 6 +++--- 7 files changed, 45 insertions(+), 19 deletions(-) create mode 100644 packages/store/src/custom-types/index.ts 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/store/src/api/tables.ts b/packages/store/src/api/tables.ts index a30b46db..74d5f428 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 { NewTable, @@ -21,7 +22,7 @@ export function initTables( projectId: string, name: string, description: string, - schema: string, + schema: Schema, ) { const tableId = randomUUID(); const slug = slugify(name); @@ -50,7 +51,7 @@ export function initTables( .orderBy(tables.name) .all(); const mapped = res.map((r) => r.tables); - return mapped as unknown as schema.Table[]; + return mapped; }, tableTeam: async function (tableId: string) { 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 7b4185f1..ebfe2ea6 100644 --- a/packages/store/src/schema/index.ts +++ b/packages/store/src/schema/index.ts @@ -1,4 +1,3 @@ -import { Table as TablelandTable } from "@tableland/sdk"; import { InferInsertModel, InferSelectModel } from "drizzle-orm"; import { integer, @@ -7,6 +6,7 @@ import { text, uniqueIndex, } from "drizzle-orm/sqlite-core"; +import { schema } from "../custom-types"; export const users = sqliteTable( "users", @@ -81,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( @@ -151,11 +151,7 @@ export const teamInvites = sqliteTable("team_invites", { claimedAt: text("claimed_at"), }); -export type Schema = TablelandTable["schema"]; - -export type Table = Omit, "schema"> & { - schema: Schema; -}; +export type Table = InferSelectModel; export type NewTable = InferInsertModel; export type UserSealed = InferSelectModel; 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 f72ab2c6..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 @@ -17,7 +17,7 @@ import { Textarea } from "@/components/ui/textarea"; import { cleanSchema, generateCreateTableStatement } from "@/lib/schema"; import { zodResolver } from "@hookform/resolvers/zod"; import { helpers } from "@tableland/sdk"; -import { schema } from "@tableland/studio-store"; +import { Schema, schema } from "@tableland/studio-store"; import { Loader2 } from "lucide-react"; import { useRouter } from "next/navigation"; import { useState, useTransition } from "react"; @@ -48,7 +48,7 @@ export default function NewTable({ project, team, envs }: Props) { const [pending, startTransition] = useTransition(); const router = useRouter(); - const [schema, setSchema] = useState({ columns: [] }); + const [schema, setSchema] = useState({ columns: [] }); const form = useForm>({ resolver: zodResolver(formSchema), diff --git a/packages/web/app/actions.ts b/packages/web/app/actions.ts index 31cbbe0f..8cbd3a9d 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(); @@ -81,12 +81,12 @@ export async function newTable( project: schema.Project, name: string, description: string, - schema: schema.Schema, + schema: Schema, ) { const table = api.tables.newTable.mutate({ projectId: project.id, name, - schema: JSON.stringify(schema), + schema: schema, description, }); await api.tables.projectTables.revalidate({ projectId: project.id }); From c0b5382fdc4a6008fa63d9f4eac92363eb7074f7 Mon Sep 17 00:00:00 2001 From: Aaron Sutula Date: Wed, 27 Sep 2023 16:28:09 -0600 Subject: [PATCH 6/7] update to latest sdk, finish custom type Signed-off-by: Aaron Sutula --- package-lock.json | 53 ++++++++++++++++++++-- packages/api/package.json | 2 +- packages/cli/package.json | 2 +- packages/store/package.json | 2 +- packages/web/components/schema-builder.tsx | 17 +++---- packages/web/lib/schema.ts | 10 ++-- packages/web/package.json | 2 +- 7 files changed, 65 insertions(+), 23 deletions(-) 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/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/web/components/schema-builder.tsx b/packages/web/components/schema-builder.tsx index d79b9818..398e6090 100644 --- a/packages/web/components/schema-builder.tsx +++ b/packages/web/components/schema-builder.tsx @@ -8,7 +8,7 @@ import { } from "@/components/ui/table"; import { Constraint, hasConstraint, setConstraint } from "@/lib/schema"; import { CheckedState } from "@radix-ui/react-checkbox"; -import { schema } from "@tableland/studio-store"; +import { Schema } from "@tableland/studio-store"; import { Plus, X } from "lucide-react"; import { Dispatch, SetStateAction } from "react"; import { Button } from "./ui/button"; @@ -31,8 +31,8 @@ export default function SchemaBuilder({ schema, setSchema, }: { - schema: schema.Schema; - setSchema: Dispatch>; + schema: Schema; + setSchema: Dispatch>; }) { return (
@@ -71,10 +71,7 @@ export default function SchemaBuilder({ setSchema((prev) => { return { ...prev, - columns: [ - ...prev.columns, - { name: "", type: "text", constraints: [] }, - ], + columns: [...prev.columns, { name: "", type: "text" }], }; }); }} @@ -91,7 +88,7 @@ function RemoveColumn({ setSchema, }: { columnIndex: number; - setSchema: Dispatch>; + setSchema: Dispatch>; }) { return (