ta_data_mas 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/README.md +42 -0
  2. package/apps/api/.prettierrc +4 -0
  3. package/apps/api/README.md +98 -0
  4. package/apps/api/drizzle/0000_right_drax.sql +87 -0
  5. package/apps/api/drizzle/meta/0000_snapshot.json +588 -0
  6. package/apps/api/drizzle/meta/_journal.json +13 -0
  7. package/apps/api/drizzle.config.ts +12 -0
  8. package/apps/api/eslint.config.mjs +35 -0
  9. package/apps/api/nest-cli.json +8 -0
  10. package/apps/api/package.json +99 -0
  11. package/apps/api/src/app.controller.spec.ts +25 -0
  12. package/apps/api/src/app.controller.ts +9 -0
  13. package/apps/api/src/app.module.ts +37 -0
  14. package/apps/api/src/app.service.ts +8 -0
  15. package/apps/api/src/auth/auth.controller.ts +113 -0
  16. package/apps/api/src/auth/auth.module.ts +15 -0
  17. package/apps/api/src/auth/auth.service.ts +309 -0
  18. package/apps/api/src/auth/decorators/current-user-decorator.ts +10 -0
  19. package/apps/api/src/auth/decorators/require-org-role-decorator.ts +7 -0
  20. package/apps/api/src/auth/dto/login.dto.ts +10 -0
  21. package/apps/api/src/auth/dto/register.dto.ts +14 -0
  22. package/apps/api/src/auth/guards/jwt-auth-guard.ts +40 -0
  23. package/apps/api/src/auth/guards/org-role.guards.ts +60 -0
  24. package/apps/api/src/db/db.module.ts +9 -0
  25. package/apps/api/src/db/drizzle.service.ts +16 -0
  26. package/apps/api/src/db/schema/index.ts +6 -0
  27. package/apps/api/src/db/schema/organizations.ts +30 -0
  28. package/apps/api/src/db/schema/projects.ts +26 -0
  29. package/apps/api/src/db/schema/query-history.ts +16 -0
  30. package/apps/api/src/db/schema/storage-buckets.ts +17 -0
  31. package/apps/api/src/db/schema/storage-objects.ts +18 -0
  32. package/apps/api/src/db/schema/users.ts +14 -0
  33. package/apps/api/src/main.ts +51 -0
  34. package/apps/api/src/members/dto/invite-member-dto.ts +6 -0
  35. package/apps/api/src/members/dto/update-role.dto.ts +8 -0
  36. package/apps/api/src/members/invite.service.ts +147 -0
  37. package/apps/api/src/members/members.controller.ts +56 -0
  38. package/apps/api/src/members/members.module.ts +13 -0
  39. package/apps/api/src/members/members.service.ts +85 -0
  40. package/apps/api/src/orgs/dto/create-org.dto.ts +8 -0
  41. package/apps/api/src/orgs/orgs.controller.ts +27 -0
  42. package/apps/api/src/orgs/orgs.module.ts +12 -0
  43. package/apps/api/src/orgs/orgs.service.ts +86 -0
  44. package/apps/api/src/project-api/project-api.controller.ts +95 -0
  45. package/apps/api/src/project-api/project-api.module.ts +12 -0
  46. package/apps/api/src/project-api/project-api.service.ts +196 -0
  47. package/apps/api/src/project-api/project-key.guard.ts +45 -0
  48. package/apps/api/src/project-api/query-parser.ts +129 -0
  49. package/apps/api/src/project-auth/dto/magic-link.dto.ts +7 -0
  50. package/apps/api/src/project-auth/dto/signin.dto.ts +10 -0
  51. package/apps/api/src/project-auth/dto/signup.dto.ts +11 -0
  52. package/apps/api/src/project-auth/project-auth-dashboard.controller.ts +35 -0
  53. package/apps/api/src/project-auth/project-auth.controller.ts +111 -0
  54. package/apps/api/src/project-auth/project-auth.module.ts +13 -0
  55. package/apps/api/src/project-auth/project-auth.service.ts +440 -0
  56. package/apps/api/src/projects/dto/create-project.dto.ts +9 -0
  57. package/apps/api/src/projects/projects.controller.ts +34 -0
  58. package/apps/api/src/projects/projects.module.ts +12 -0
  59. package/apps/api/src/projects/projects.service.ts +136 -0
  60. package/apps/api/src/realtime/realtime-socket.type.ts +14 -0
  61. package/apps/api/src/realtime/realtime.controller.ts +63 -0
  62. package/apps/api/src/realtime/realtime.gateway.ts +150 -0
  63. package/apps/api/src/realtime/realtime.module.ts +14 -0
  64. package/apps/api/src/realtime/realtime.service.ts +101 -0
  65. package/apps/api/src/realtime/trigger.service.ts +95 -0
  66. package/apps/api/src/sql-editor/dto/execute-query.dto.ts +7 -0
  67. package/apps/api/src/sql-editor/sql-editor.controller.ts +28 -0
  68. package/apps/api/src/sql-editor/sql-editor.module.ts +12 -0
  69. package/apps/api/src/sql-editor/sql-editor.service.ts +154 -0
  70. package/apps/api/src/storage/project-storage.controller.ts +140 -0
  71. package/apps/api/src/storage/storage.controller.ts +118 -0
  72. package/apps/api/src/storage/storage.module.ts +15 -0
  73. package/apps/api/src/storage/storage.service.ts +191 -0
  74. package/apps/api/src/storage/uploadthing.ts +23 -0
  75. package/apps/api/src/table-editor/dto/alter-table.dto.ts +15 -0
  76. package/apps/api/src/table-editor/dto/create-table.dto.ts +54 -0
  77. package/apps/api/src/table-editor/table-editor.controller.ts +117 -0
  78. package/apps/api/src/table-editor/table-editor.module.ts +12 -0
  79. package/apps/api/src/table-editor/table-editor.service.ts +366 -0
  80. package/apps/api/test/app.e2e-spec.ts +29 -0
  81. package/apps/api/test/jest-e2e.json +9 -0
  82. package/apps/api/tsconfig.build.json +4 -0
  83. package/apps/api/tsconfig.json +25 -0
  84. package/apps/web/.env.example +12 -0
  85. package/apps/web/AGENTS.md +9 -0
  86. package/apps/web/CLAUDE.md +1 -0
  87. package/apps/web/README.md +36 -0
  88. package/apps/web/components.json +25 -0
  89. package/apps/web/eslint.config.mjs +18 -0
  90. package/apps/web/next.config.ts +11 -0
  91. package/apps/web/package.json +48 -0
  92. package/apps/web/postcss.config.mjs +7 -0
  93. package/apps/web/public/file.svg +1 -0
  94. package/apps/web/public/globe.svg +1 -0
  95. package/apps/web/public/logo.svg +4 -0
  96. package/apps/web/public/next.svg +1 -0
  97. package/apps/web/public/vercel.svg +1 -0
  98. package/apps/web/public/window.svg +1 -0
  99. package/apps/web/src/app/(dashboard)/layout.tsx +33 -0
  100. package/apps/web/src/app/(dashboard)/organizations/(list)/layout.tsx +7 -0
  101. package/apps/web/src/app/(dashboard)/organizations/(list)/new/page.tsx +26 -0
  102. package/apps/web/src/app/(dashboard)/organizations/(list)/page.tsx +44 -0
  103. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/api/page.tsx +13 -0
  104. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/layout.tsx +19 -0
  105. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/page.tsx +10 -0
  106. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/providers/page.tsx +19 -0
  107. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/url-configuration/page.tsx +24 -0
  108. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/users/page.tsx +13 -0
  109. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/database/page.tsx +29 -0
  110. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/realtime/page.tsx +28 -0
  111. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/sql/page.tsx +21 -0
  112. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/storage/page.tsx +28 -0
  113. package/apps/web/src/app/(dashboard)/organizations/[slug]/layout.tsx +20 -0
  114. package/apps/web/src/app/(dashboard)/organizations/[slug]/projects/page.tsx +62 -0
  115. package/apps/web/src/app/(dashboard)/organizations/[slug]/settings/members/page.tsx +28 -0
  116. package/apps/web/src/app/(dashboard)/organizations/[slug]/settings/page.tsx +10 -0
  117. package/apps/web/src/app/(dashboard)/settings/page.tsx +12 -0
  118. package/apps/web/src/app/dashboard/page.tsx +14 -0
  119. package/apps/web/src/app/favicon.ico +0 -0
  120. package/apps/web/src/app/globals.css +115 -0
  121. package/apps/web/src/app/layout.tsx +36 -0
  122. package/apps/web/src/app/login/page.tsx +199 -0
  123. package/apps/web/src/app/page.tsx +100 -0
  124. package/apps/web/src/app/register/page.tsx +218 -0
  125. package/apps/web/src/components/app-sidebar.tsx +301 -0
  126. package/apps/web/src/components/navbar.tsx +63 -0
  127. package/apps/web/src/components/topNav.tsx +68 -0
  128. package/apps/web/src/components/ui/avatar.tsx +112 -0
  129. package/apps/web/src/components/ui/badge.tsx +48 -0
  130. package/apps/web/src/components/ui/button.tsx +66 -0
  131. package/apps/web/src/components/ui/card.tsx +102 -0
  132. package/apps/web/src/components/ui/checkbox.tsx +32 -0
  133. package/apps/web/src/components/ui/dialog.tsx +168 -0
  134. package/apps/web/src/components/ui/dropdown-menu.tsx +269 -0
  135. package/apps/web/src/components/ui/field.tsx +238 -0
  136. package/apps/web/src/components/ui/input.tsx +19 -0
  137. package/apps/web/src/components/ui/label.tsx +24 -0
  138. package/apps/web/src/components/ui/resizable.tsx +50 -0
  139. package/apps/web/src/components/ui/scroll-area.tsx +55 -0
  140. package/apps/web/src/components/ui/select.tsx +192 -0
  141. package/apps/web/src/components/ui/separator.tsx +28 -0
  142. package/apps/web/src/components/ui/sheet.tsx +147 -0
  143. package/apps/web/src/components/ui/sidebar.tsx +703 -0
  144. package/apps/web/src/components/ui/skeleton.tsx +13 -0
  145. package/apps/web/src/components/ui/switch.tsx +33 -0
  146. package/apps/web/src/components/ui/table.tsx +116 -0
  147. package/apps/web/src/components/ui/tooltip.tsx +57 -0
  148. package/apps/web/src/features/api-docs/api-docs-client.tsx +227 -0
  149. package/apps/web/src/features/api-docs/api-docs-helpers.server.ts +86 -0
  150. package/apps/web/src/features/auth/action.ts +67 -0
  151. package/apps/web/src/features/auth/client.schema.ts +13 -0
  152. package/apps/web/src/features/auth/constants.ts +6 -0
  153. package/apps/web/src/features/auth/cookies.ts +86 -0
  154. package/apps/web/src/features/auth/server.schema.ts +11 -0
  155. package/apps/web/src/features/auth/sign-out.ts +17 -0
  156. package/apps/web/src/features/members/action.ts +66 -0
  157. package/apps/web/src/features/members/client.schema.ts +11 -0
  158. package/apps/web/src/features/members/constants.ts +8 -0
  159. package/apps/web/src/features/members/members-client.tsx +198 -0
  160. package/apps/web/src/features/members/members-helpers.server.ts +21 -0
  161. package/apps/web/src/features/members/server.schema.ts +19 -0
  162. package/apps/web/src/features/organization/action.ts +45 -0
  163. package/apps/web/src/features/organization/client.schema.ts +5 -0
  164. package/apps/web/src/features/organization/constants.tsx +3 -0
  165. package/apps/web/src/features/organization/create-org-form.tsx +66 -0
  166. package/apps/web/src/features/organization/organization-helpers.server.ts +35 -0
  167. package/apps/web/src/features/organization/server.schema.ts +10 -0
  168. package/apps/web/src/features/project-auth/project-auth-action.ts +78 -0
  169. package/apps/web/src/features/project-auth/project-auth-client.schema.ts +18 -0
  170. package/apps/web/src/features/project-auth/project-auth-constants.ts +8 -0
  171. package/apps/web/src/features/project-auth/project-auth-helpers.server.ts +39 -0
  172. package/apps/web/src/features/project-auth/project-auth-providers.tsx +222 -0
  173. package/apps/web/src/features/project-auth/project-auth-server.schema.ts +22 -0
  174. package/apps/web/src/features/project-auth/project-auth-shell.tsx +106 -0
  175. package/apps/web/src/features/project-auth/project-auth-url-config.tsx +151 -0
  176. package/apps/web/src/features/project-auth/project-auth-users.tsx +138 -0
  177. package/apps/web/src/features/projects/action.ts +46 -0
  178. package/apps/web/src/features/projects/client.schema.ts +9 -0
  179. package/apps/web/src/features/projects/constants.ts +6 -0
  180. package/apps/web/src/features/projects/create-project-modal.tsx +117 -0
  181. package/apps/web/src/features/projects/project-helpers.server.ts +38 -0
  182. package/apps/web/src/features/projects/server.schema.ts +10 -0
  183. package/apps/web/src/features/realtime/realtime-client.tsx +294 -0
  184. package/apps/web/src/features/realtime/realtime-helpers.server.ts +40 -0
  185. package/apps/web/src/features/sql-editor/sql-editor-client.tsx +324 -0
  186. package/apps/web/src/features/sql-editor/sql-editor-helpers.server.ts +22 -0
  187. package/apps/web/src/features/storage/storage-client.tsx +500 -0
  188. package/apps/web/src/features/storage/storage-helpers.server.ts +36 -0
  189. package/apps/web/src/features/table-editor/action.ts +97 -0
  190. package/apps/web/src/features/table-editor/add-column-dialog.tsx +184 -0
  191. package/apps/web/src/features/table-editor/client.schema.ts +36 -0
  192. package/apps/web/src/features/table-editor/create-table-drawer.tsx +542 -0
  193. package/apps/web/src/features/table-editor/server.schema.ts +41 -0
  194. package/apps/web/src/features/table-editor/table-editor-client.tsx +737 -0
  195. package/apps/web/src/features/table-editor/table-editor-helpers.server.ts +39 -0
  196. package/apps/web/src/hooks/use-mobile.ts +19 -0
  197. package/apps/web/src/lib/axios.ts +8 -0
  198. package/apps/web/src/lib/utils.ts +6 -0
  199. package/apps/web/src/proxy.ts +72 -0
  200. package/apps/web/src/server-utils/utils.ts +10 -0
  201. package/apps/web/tsconfig.json +34 -0
  202. package/package.json +22 -0
  203. package/packages/apiDatabase-js/package.json +39 -0
  204. package/packages/apiDatabase-js/src/auth/index.ts +121 -0
  205. package/packages/apiDatabase-js/src/client.ts +25 -0
  206. package/packages/apiDatabase-js/src/db/index.ts +15 -0
  207. package/packages/apiDatabase-js/src/db/query-builder.ts +194 -0
  208. package/packages/apiDatabase-js/src/index.ts +16 -0
  209. package/packages/apiDatabase-js/src/realtime/index.ts +84 -0
  210. package/packages/apiDatabase-js/src/storage/index.ts +130 -0
  211. package/packages/apiDatabase-js/tsconfig.json +13 -0
  212. package/packages/apiDatabase-js/tsdown.config.ts +20 -0
  213. package/packages/constants/package.json +6 -0
  214. package/packages/constants/src/index.ts +76 -0
  215. package/packages/tsconfig.json +13 -0
  216. package/packages/tsdown.config.ts +20 -0
  217. package/packages/types/package.json +6 -0
  218. package/packages/types/src/index.ts +254 -0
  219. package/pnpm-workspace.yaml +10 -0
@@ -0,0 +1,542 @@
1
+ "use client";
2
+
3
+ import { useActionState, useEffect } from "react";
4
+ import { Controller, useFieldArray, useForm } from "react-hook-form";
5
+ import { zodResolver } from "@hookform/resolvers/zod";
6
+ import { z } from "zod";
7
+ import { Plus, Trash2 } from "lucide-react";
8
+ import { Button } from "@/components/ui/button";
9
+ import {
10
+ Sheet,
11
+ SheetContent,
12
+ SheetTitle,
13
+ SheetDescription,
14
+ } from "@/components/ui/sheet";
15
+ import {
16
+ Field,
17
+ FieldError,
18
+ FieldGroup,
19
+ FieldLabel,
20
+ } from "@/components/ui/field";
21
+ import { Input } from "@/components/ui/input";
22
+ import { Checkbox } from "@/components/ui/checkbox";
23
+ import {
24
+ Select,
25
+ SelectContent,
26
+ SelectItem,
27
+ SelectTrigger,
28
+ SelectValue,
29
+ } from "@/components/ui/select";
30
+ import { COLUMN_TYPES, TABLE_EDITOR_INTENT } from "@apiDatabase/constants";
31
+ import type { ColumnType } from "@apiDatabase/types";
32
+ import { createTableSchema } from "@/features/table-editor/client.schema";
33
+ import { tableEditorAction } from "@/features/table-editor/action";
34
+ import { cn } from "@/lib/utils";
35
+
36
+ type CreateTableValues = z.infer<typeof createTableSchema>;
37
+
38
+ interface CreateTableDrawerProps {
39
+ open: boolean;
40
+ onOpenChange: (open: boolean) => void;
41
+ orgSlug: string;
42
+ projectSlug: string;
43
+ existingTables: string[];
44
+ onCreated: (tableName: string) => void;
45
+ }
46
+
47
+ const sheetClassName = cn(
48
+ "flex h-full w-full max-w-full flex-col gap-0! overflow-hidden p-0! shadow-2xl",
49
+ "data-[side=right]:w-full data-[side=right]:max-w-full",
50
+ "sm:data-[side=right]:w-[700px] sm:data-[side=right]:max-w-[700px]",
51
+ );
52
+
53
+ export function CreateTableDrawer({
54
+ open,
55
+ onOpenChange,
56
+ orgSlug,
57
+ projectSlug,
58
+ existingTables,
59
+ onCreated,
60
+ }: CreateTableDrawerProps) {
61
+ const boundAction = tableEditorAction.bind(null, { orgSlug, projectSlug });
62
+ const [state, formAction, isPending] = useActionState(boundAction, {});
63
+
64
+ const form = useForm<CreateTableValues>({
65
+ resolver: zodResolver(createTableSchema),
66
+ defaultValues: {
67
+ name: "",
68
+ columns: [
69
+ {
70
+ name: "id",
71
+ type: "bigint",
72
+ isNullable: false,
73
+ isPrimaryKey: true,
74
+ defaultValue: "",
75
+ },
76
+ {
77
+ name: "created_at",
78
+ type: "timestamp",
79
+ isNullable: false,
80
+ isPrimaryKey: false,
81
+ defaultValue: "now()",
82
+ },
83
+ ],
84
+ },
85
+ });
86
+
87
+ const { fields, append, remove } = useFieldArray({
88
+ control: form.control,
89
+ name: "columns",
90
+ });
91
+
92
+ useEffect(() => {
93
+ if (state.success && state.tableName && open) {
94
+ onCreated(state.tableName);
95
+ form.reset();
96
+ }
97
+ }, [state.success, state.tableName, open, onCreated, form]);
98
+
99
+ return (
100
+ <Sheet open={open} onOpenChange={onOpenChange}>
101
+ <SheetContent side="right" className={sheetClassName}>
102
+ <div className="shrink-0 border-b bg-muted/30 px-6 py-3 pr-12">
103
+ <SheetTitle>Create a new table</SheetTitle>
104
+ <SheetDescription className="mt-1">
105
+ Define your table name and columns. Defaults:{" "}
106
+ <span className="font-mono">id</span> +{" "}
107
+ <span className="font-mono">created_at</span>.
108
+ </SheetDescription>
109
+ </div>
110
+
111
+ {state.error && (
112
+ <p className="shrink-0 px-6 pt-3 text-sm text-destructive">
113
+ {state.error}
114
+ </p>
115
+ )}
116
+
117
+ <form
118
+ action={formAction}
119
+ noValidate
120
+ onSubmitCapture={async (e) => {
121
+ const formEl = e.currentTarget;
122
+
123
+ const ok = await form.trigger(undefined, { shouldFocus: true });
124
+ if (!ok) {
125
+ e.preventDefault();
126
+ return;
127
+ }
128
+
129
+ const values = form.getValues();
130
+ const nameInput = formEl.elements.namedItem(
131
+ "name",
132
+ ) as HTMLInputElement;
133
+ const columnsInput = formEl.elements.namedItem(
134
+ "columns",
135
+ ) as HTMLInputElement;
136
+
137
+ if (nameInput) nameInput.value = values.name;
138
+ if (columnsInput) {
139
+ columnsInput.value = JSON.stringify(values.columns);
140
+ }
141
+ }}
142
+ className="flex min-h-0 flex-1 flex-col overflow-hidden"
143
+ >
144
+ <input type="hidden" name="name" defaultValue="" />
145
+ <input type="hidden" name="columns" defaultValue="[]" />
146
+
147
+ <div className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto px-6 py-4">
148
+ <div className="space-y-6">
149
+ <FieldGroup>
150
+ <Controller
151
+ name="name"
152
+ control={form.control}
153
+ render={({ field, fieldState }) => (
154
+ <Field data-invalid={fieldState.invalid}>
155
+ <FieldLabel htmlFor="table-name">Name</FieldLabel>
156
+ <Input
157
+ {...field}
158
+ id="table-name"
159
+ placeholder="table_name"
160
+ aria-invalid={fieldState.invalid}
161
+ />
162
+ {fieldState.invalid && (
163
+ <FieldError errors={[fieldState.error]} />
164
+ )}
165
+ </Field>
166
+ )}
167
+ />
168
+ </FieldGroup>
169
+
170
+ <div className="min-w-0">
171
+ <p className="mb-3 text-sm font-medium">Columns</p>
172
+
173
+ {/* Desktop: column grid constrained to drawer width */}
174
+ <div className="hidden w-full min-w-0 md:block">
175
+ <div className="mb-2 grid grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)_72px_40px_40px_minmax(0,1fr)_minmax(0,1fr)_28px] gap-2">
176
+ <span className="text-xs text-muted-foreground">Name</span>
177
+ <span className="text-xs text-muted-foreground">Type</span>
178
+ <span className="text-xs text-muted-foreground">
179
+ Default
180
+ </span>
181
+ <span className="text-center text-xs text-muted-foreground">
182
+ PK
183
+ </span>
184
+ <span className="text-center text-xs text-muted-foreground">
185
+ Null
186
+ </span>
187
+ <span className="text-xs text-muted-foreground">
188
+ FK table
189
+ </span>
190
+ <span className="text-xs text-muted-foreground">
191
+ FK column
192
+ </span>
193
+ <span />
194
+ </div>
195
+
196
+ <div className="space-y-2">
197
+ {fields.map((field, index) => (
198
+ <ColumnRowDesktop
199
+ key={field.id}
200
+ index={index}
201
+ control={form.control}
202
+ existingTables={existingTables}
203
+ canRemove={fields.length > 1}
204
+ onRemove={() => remove(index)}
205
+ />
206
+ ))}
207
+ </div>
208
+ </div>
209
+
210
+ {/* Mobile: stacked cards */}
211
+ <div className="space-y-3 md:hidden">
212
+ {fields.map((field, index) => (
213
+ <ColumnRowMobile
214
+ key={field.id}
215
+ index={index}
216
+ control={form.control}
217
+ existingTables={existingTables}
218
+ canRemove={fields.length > 1}
219
+ onRemove={() => remove(index)}
220
+ />
221
+ ))}
222
+ </div>
223
+
224
+ <Button
225
+ type="button"
226
+ variant="outline"
227
+ size="sm"
228
+ className="mt-4 w-full max-w-full"
229
+ onClick={() =>
230
+ append({
231
+ name: "",
232
+ type: "text",
233
+ isNullable: true,
234
+ isPrimaryKey: false,
235
+ defaultValue: "",
236
+ foreignKeyTable: "",
237
+ foreignKeyColumn: "",
238
+ })
239
+ }
240
+ >
241
+ <Plus size={14} className="mr-1.5" />
242
+ Add column
243
+ </Button>
244
+ </div>
245
+ </div>
246
+ </div>
247
+
248
+ <div className="flex shrink-0 justify-end gap-2 border-t border-border bg-background px-6 py-3">
249
+ <Button
250
+ type="button"
251
+ variant="outline"
252
+ onClick={() => onOpenChange(false)}
253
+ >
254
+ Cancel
255
+ </Button>
256
+ <Button
257
+ type="submit"
258
+ name="intent"
259
+ value={TABLE_EDITOR_INTENT.CREATE_TABLE}
260
+ disabled={isPending}
261
+ >
262
+ {isPending ? "Creating..." : "Create table"}
263
+ </Button>
264
+ </div>
265
+ </form>
266
+ </SheetContent>
267
+ </Sheet>
268
+ );
269
+ }
270
+
271
+ function ColumnRowDesktop({
272
+ index,
273
+ control,
274
+ existingTables,
275
+ canRemove,
276
+ onRemove,
277
+ }: {
278
+ index: number;
279
+ control: ReturnType<typeof useForm<CreateTableValues>>["control"];
280
+ existingTables: string[];
281
+ canRemove: boolean;
282
+ onRemove: () => void;
283
+ }) {
284
+ return (
285
+ <div className="grid min-w-0 grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)_72px_40px_40px_minmax(0,1fr)_minmax(0,1fr)_28px] items-center gap-2">
286
+ <Controller
287
+ name={`columns.${index}.name`}
288
+ control={control}
289
+ render={({ field, fieldState }) => (
290
+ <Field data-invalid={fieldState.invalid} className="min-w-0">
291
+ <Input
292
+ {...field}
293
+ className="h-8 min-w-0 font-mono text-xs"
294
+ placeholder="column_name"
295
+ aria-invalid={fieldState.invalid}
296
+ />
297
+ </Field>
298
+ )}
299
+ />
300
+
301
+ <ColumnTypeSelect index={index} control={control} />
302
+ <ColumnDefaultInput index={index} control={control} />
303
+ <ColumnCheckbox index={index} control={control} name="isPrimaryKey" />
304
+ <ColumnCheckbox index={index} control={control} name="isNullable" />
305
+ <ColumnFkTableSelect
306
+ index={index}
307
+ control={control}
308
+ existingTables={existingTables}
309
+ />
310
+ <ColumnFkColumnInput index={index} control={control} />
311
+
312
+ <Button
313
+ type="button"
314
+ variant="ghost"
315
+ size="icon"
316
+ className="h-7 w-7 text-muted-foreground hover:text-destructive"
317
+ onClick={onRemove}
318
+ disabled={!canRemove}
319
+ >
320
+ <Trash2 size={12} />
321
+ </Button>
322
+ </div>
323
+ );
324
+ }
325
+
326
+ function ColumnRowMobile({
327
+ index,
328
+ control,
329
+ existingTables,
330
+ canRemove,
331
+ onRemove,
332
+ }: {
333
+ index: number;
334
+ control: ReturnType<typeof useForm<CreateTableValues>>["control"];
335
+ existingTables: string[];
336
+ canRemove: boolean;
337
+ onRemove: () => void;
338
+ }) {
339
+ return (
340
+ <div className="space-y-3 rounded-lg border bg-card p-3">
341
+ <div className="flex items-center justify-between">
342
+ <span className="text-xs font-medium text-muted-foreground">
343
+ Column {index + 1}
344
+ </span>
345
+ <Button
346
+ type="button"
347
+ variant="ghost"
348
+ size="icon"
349
+ className="h-7 w-7 text-muted-foreground hover:text-destructive"
350
+ onClick={onRemove}
351
+ disabled={!canRemove}
352
+ >
353
+ <Trash2 size={12} />
354
+ </Button>
355
+ </div>
356
+
357
+ <Controller
358
+ name={`columns.${index}.name`}
359
+ control={control}
360
+ render={({ field, fieldState }) => (
361
+ <Field data-invalid={fieldState.invalid}>
362
+ <FieldLabel className="text-xs">Name</FieldLabel>
363
+ <Input
364
+ {...field}
365
+ className="h-9 font-mono text-xs"
366
+ placeholder="column_name"
367
+ aria-invalid={fieldState.invalid}
368
+ />
369
+ </Field>
370
+ )}
371
+ />
372
+
373
+ <div className="grid grid-cols-2 gap-3">
374
+ <div>
375
+ <FieldLabel className="mb-1.5 text-xs">Type</FieldLabel>
376
+ <ColumnTypeSelect index={index} control={control} />
377
+ </div>
378
+ <div>
379
+ <FieldLabel className="mb-1.5 text-xs">Default</FieldLabel>
380
+ <ColumnDefaultInput index={index} control={control} />
381
+ </div>
382
+ </div>
383
+
384
+ <div className="flex gap-6">
385
+ <label className="flex items-center gap-2 text-xs">
386
+ <ColumnCheckbox index={index} control={control} name="isPrimaryKey" />
387
+ Primary key
388
+ </label>
389
+ <label className="flex items-center gap-2 text-xs">
390
+ <ColumnCheckbox index={index} control={control} name="isNullable" />
391
+ Nullable
392
+ </label>
393
+ </div>
394
+
395
+ <div className="grid grid-cols-2 gap-3">
396
+ <div>
397
+ <FieldLabel className="mb-1.5 text-xs">FK table</FieldLabel>
398
+ <ColumnFkTableSelect
399
+ index={index}
400
+ control={control}
401
+ existingTables={existingTables}
402
+ />
403
+ </div>
404
+ <div>
405
+ <FieldLabel className="mb-1.5 text-xs">FK column</FieldLabel>
406
+ <ColumnFkColumnInput index={index} control={control} />
407
+ </div>
408
+ </div>
409
+ </div>
410
+ );
411
+ }
412
+
413
+ function ColumnTypeSelect({
414
+ index,
415
+ control,
416
+ }: {
417
+ index: number;
418
+ control: ReturnType<typeof useForm<CreateTableValues>>["control"];
419
+ }) {
420
+ return (
421
+ <Controller
422
+ name={`columns.${index}.type`}
423
+ control={control}
424
+ render={({ field }) => (
425
+ <Select
426
+ value={field.value}
427
+ onValueChange={(v) => field.onChange(v as ColumnType)}
428
+ >
429
+ <SelectTrigger className="h-8 w-full min-w-0 text-xs">
430
+ <SelectValue />
431
+ </SelectTrigger>
432
+ <SelectContent>
433
+ {COLUMN_TYPES.map((t) => (
434
+ <SelectItem key={t} value={t} className="font-mono text-xs">
435
+ {t}
436
+ </SelectItem>
437
+ ))}
438
+ </SelectContent>
439
+ </Select>
440
+ )}
441
+ />
442
+ );
443
+ }
444
+
445
+ function ColumnDefaultInput({
446
+ index,
447
+ control,
448
+ }: {
449
+ index: number;
450
+ control: ReturnType<typeof useForm<CreateTableValues>>["control"];
451
+ }) {
452
+ return (
453
+ <Controller
454
+ name={`columns.${index}.defaultValue`}
455
+ control={control}
456
+ render={({ field }) => (
457
+ <Input
458
+ {...field}
459
+ className="h-8 w-full min-w-0 font-mono text-xs"
460
+ placeholder="NULL"
461
+ />
462
+ )}
463
+ />
464
+ );
465
+ }
466
+
467
+ function ColumnCheckbox({
468
+ index,
469
+ control,
470
+ name,
471
+ }: {
472
+ index: number;
473
+ control: ReturnType<typeof useForm<CreateTableValues>>["control"];
474
+ name: "isPrimaryKey" | "isNullable";
475
+ }) {
476
+ return (
477
+ <Controller
478
+ name={`columns.${index}.${name}`}
479
+ control={control}
480
+ render={({ field }) => (
481
+ <Checkbox checked={field.value} onCheckedChange={field.onChange} />
482
+ )}
483
+ />
484
+ );
485
+ }
486
+
487
+ function ColumnFkTableSelect({
488
+ index,
489
+ control,
490
+ existingTables,
491
+ }: {
492
+ index: number;
493
+ control: ReturnType<typeof useForm<CreateTableValues>>["control"];
494
+ existingTables: string[];
495
+ }) {
496
+ return (
497
+ <Controller
498
+ name={`columns.${index}.foreignKeyTable`}
499
+ control={control}
500
+ render={({ field }) => (
501
+ <Select
502
+ value={field.value ?? ""}
503
+ onValueChange={(v) => field.onChange(v === "__none__" ? "" : v)}
504
+ >
505
+ <SelectTrigger className="h-8 w-full min-w-0 text-xs">
506
+ <SelectValue placeholder="None" />
507
+ </SelectTrigger>
508
+ <SelectContent>
509
+ <SelectItem value="__none__">None</SelectItem>
510
+ {existingTables.map((t) => (
511
+ <SelectItem key={t} value={t} className="font-mono text-xs">
512
+ {t}
513
+ </SelectItem>
514
+ ))}
515
+ </SelectContent>
516
+ </Select>
517
+ )}
518
+ />
519
+ );
520
+ }
521
+
522
+ function ColumnFkColumnInput({
523
+ index,
524
+ control,
525
+ }: {
526
+ index: number;
527
+ control: ReturnType<typeof useForm<CreateTableValues>>["control"];
528
+ }) {
529
+ return (
530
+ <Controller
531
+ name={`columns.${index}.foreignKeyColumn`}
532
+ control={control}
533
+ render={({ field }) => (
534
+ <Input
535
+ {...field}
536
+ className="h-8 w-full min-w-0 font-mono text-xs"
537
+ placeholder="id"
538
+ />
539
+ )}
540
+ />
541
+ );
542
+ }
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ import {
3
+ addColumnSchema,
4
+ createTableSchema,
5
+ deleteTableSchema,
6
+ fetchTableSchema,
7
+ } from "./client.schema";
8
+ import { TABLE_EDITOR_INTENT } from "@apiDatabase/constants";
9
+
10
+ export const tableEditorServerSchema = z.discriminatedUnion("intent", [
11
+ z.object({
12
+ intent: z.literal(TABLE_EDITOR_INTENT.CREATE_TABLE),
13
+ name: createTableSchema.shape.name,
14
+ columns: z.string().transform((raw, ctx) => {
15
+ try {
16
+ const parsed: unknown = JSON.parse(raw);
17
+ const result = createTableSchema.shape.columns.safeParse(parsed);
18
+ if (!result.success) {
19
+ ctx.addIssue({ code: "custom", message: "Invalid columns" });
20
+ return z.NEVER;
21
+ }
22
+ return result.data;
23
+ } catch {
24
+ ctx.addIssue({ code: "custom", message: "Invalid columns JSON" });
25
+ return z.NEVER;
26
+ }
27
+ }),
28
+ }),
29
+ z.object({
30
+ intent: z.literal(TABLE_EDITOR_INTENT.DELETE_TABLE),
31
+ ...deleteTableSchema.shape,
32
+ }),
33
+ z.object({
34
+ intent: z.literal(TABLE_EDITOR_INTENT.FETCH_TABLE),
35
+ ...fetchTableSchema.shape,
36
+ }),
37
+ z.object({
38
+ intent: z.literal(TABLE_EDITOR_INTENT.ADD_COLUMN),
39
+ ...addColumnSchema.shape,
40
+ }),
41
+ ]);