@trypattern/sdk 0.0.0-stage → 0.1.0

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 (247) hide show
  1. package/README.md +127 -2
  2. package/dist/access.d.ts +31 -0
  3. package/dist/access.js +72 -0
  4. package/dist/actions.d.ts +57 -0
  5. package/dist/actions.js +198 -0
  6. package/dist/api.d.ts +20 -0
  7. package/dist/api.js +34 -0
  8. package/dist/app.d.ts +106 -0
  9. package/dist/app.js +224 -0
  10. package/dist/appearance.d.ts +7 -0
  11. package/dist/appearance.js +2 -0
  12. package/dist/auth/sessionApi.d.ts +41 -0
  13. package/dist/auth/sessionApi.js +158 -0
  14. package/dist/auth/sessionStore.d.ts +20 -0
  15. package/dist/auth/sessionStore.js +40 -0
  16. package/dist/auth/user.d.ts +11 -0
  17. package/dist/auth/user.js +9 -0
  18. package/dist/builtins/Account.d.ts +25 -0
  19. package/dist/builtins/Account.js +15 -0
  20. package/dist/builtins/Palette.d.ts +29 -0
  21. package/dist/builtins/Palette.js +63 -0
  22. package/dist/builtins/SignIn.d.ts +28 -0
  23. package/dist/builtins/SignIn.js +60 -0
  24. package/dist/builtins/Toasts.d.ts +32 -0
  25. package/dist/builtins/Toasts.js +24 -0
  26. package/dist/builtins/actions.d.ts +2 -0
  27. package/dist/builtins/actions.js +45 -0
  28. package/dist/cache.d.ts +24 -0
  29. package/dist/cache.js +112 -0
  30. package/dist/cli/app.d.ts +2 -0
  31. package/dist/cli/app.js +1329 -0
  32. package/dist/cli/screenshot.d.ts +1 -0
  33. package/dist/cli/screenshot.js +96 -0
  34. package/dist/cli/tools.d.ts +12 -0
  35. package/dist/cli/tools.js +35 -0
  36. package/dist/components/AppFrame.d.ts +14 -0
  37. package/dist/components/AppFrame.js +64 -0
  38. package/dist/components/AppearancePicker.d.ts +1 -0
  39. package/dist/components/AppearancePicker.js +12 -0
  40. package/dist/components/Avatar.d.ts +9 -0
  41. package/dist/components/Avatar.js +63 -0
  42. package/dist/components/Badge.d.ts +11 -0
  43. package/dist/components/Badge.js +87 -0
  44. package/dist/components/Button.d.ts +23 -0
  45. package/dist/components/Button.js +249 -0
  46. package/dist/components/Card.d.ts +12 -0
  47. package/dist/components/Card.js +38 -0
  48. package/dist/components/CommandMenu.d.ts +23 -0
  49. package/dist/components/CommandMenu.js +214 -0
  50. package/dist/components/CommandPalette.d.ts +1 -0
  51. package/dist/components/CommandPalette.js +7 -0
  52. package/dist/components/Content.d.ts +7 -0
  53. package/dist/components/Content.js +24 -0
  54. package/dist/components/ContextMenu.d.ts +8 -0
  55. package/dist/components/ContextMenu.js +23 -0
  56. package/dist/components/Dialog.d.ts +11 -0
  57. package/dist/components/Dialog.js +81 -0
  58. package/dist/components/Divider.d.ts +6 -0
  59. package/dist/components/Divider.js +15 -0
  60. package/dist/components/EmptyState.d.ts +8 -0
  61. package/dist/components/EmptyState.js +24 -0
  62. package/dist/components/Form.d.ts +9 -0
  63. package/dist/components/Form.js +23 -0
  64. package/dist/components/Icon.d.ts +10 -0
  65. package/dist/components/Icon.js +22 -0
  66. package/dist/components/IconButton.d.ts +14 -0
  67. package/dist/components/IconButton.js +53 -0
  68. package/dist/components/Kbd.d.ts +6 -0
  69. package/dist/components/Kbd.js +67 -0
  70. package/dist/components/Link.d.ts +9 -0
  71. package/dist/components/Link.js +35 -0
  72. package/dist/components/ListRow.d.ts +12 -0
  73. package/dist/components/ListRow.js +62 -0
  74. package/dist/components/LoadingRows.d.ts +5 -0
  75. package/dist/components/LoadingRows.js +6 -0
  76. package/dist/components/Menu.d.ts +12 -0
  77. package/dist/components/Menu.js +33 -0
  78. package/dist/components/MenuItems.d.ts +21 -0
  79. package/dist/components/MenuItems.js +32 -0
  80. package/dist/components/Placement.d.ts +21 -0
  81. package/dist/components/Placement.js +1 -0
  82. package/dist/components/Popup.d.ts +8 -0
  83. package/dist/components/Popup.js +43 -0
  84. package/dist/components/PopupItem.d.ts +8 -0
  85. package/dist/components/PopupItem.js +51 -0
  86. package/dist/components/PropertyRow.d.ts +7 -0
  87. package/dist/components/PropertyRow.js +23 -0
  88. package/dist/components/Row.d.ts +16 -0
  89. package/dist/components/Row.js +27 -0
  90. package/dist/components/Screen.d.ts +13 -0
  91. package/dist/components/Screen.js +56 -0
  92. package/dist/components/Scrim.d.ts +8 -0
  93. package/dist/components/Scrim.js +40 -0
  94. package/dist/components/ScrollArea.d.ts +15 -0
  95. package/dist/components/ScrollArea.js +13 -0
  96. package/dist/components/SegmentedControl.d.ts +17 -0
  97. package/dist/components/SegmentedControl.js +68 -0
  98. package/dist/components/Select.d.ts +17 -0
  99. package/dist/components/Select.js +107 -0
  100. package/dist/components/SettingsRow.d.ts +8 -0
  101. package/dist/components/SettingsRow.js +32 -0
  102. package/dist/components/Sidebar.d.ts +20 -0
  103. package/dist/components/Sidebar.js +138 -0
  104. package/dist/components/SignInScreen.d.ts +1 -0
  105. package/dist/components/SignInScreen.js +40 -0
  106. package/dist/components/Size.d.ts +1 -0
  107. package/dist/components/Size.js +1 -0
  108. package/dist/components/Skeleton.d.ts +8 -0
  109. package/dist/components/Skeleton.js +48 -0
  110. package/dist/components/Spinner.d.ts +6 -0
  111. package/dist/components/Spinner.js +54 -0
  112. package/dist/components/Stack.d.ts +14 -0
  113. package/dist/components/Stack.js +19 -0
  114. package/dist/components/Switch.d.ts +10 -0
  115. package/dist/components/Switch.js +71 -0
  116. package/dist/components/Text.d.ts +21 -0
  117. package/dist/components/Text.js +77 -0
  118. package/dist/components/TextField.d.ts +25 -0
  119. package/dist/components/TextField.js +68 -0
  120. package/dist/components/Toaster.d.ts +1 -0
  121. package/dist/components/Toaster.js +83 -0
  122. package/dist/components/Tooltip.d.ts +8 -0
  123. package/dist/components/Tooltip.js +43 -0
  124. package/dist/components/controlLook.d.ts +60 -0
  125. package/dist/components/controlLook.js +67 -0
  126. package/dist/components/frame.d.ts +4 -0
  127. package/dist/components/frame.js +2 -0
  128. package/dist/components/icons.d.ts +58 -0
  129. package/dist/components/icons.js +105 -0
  130. package/dist/components/interaction.d.ts +12 -0
  131. package/dist/components/interaction.js +22 -0
  132. package/dist/components/spacing.d.ts +118 -0
  133. package/dist/components/spacing.js +46 -0
  134. package/dist/components/tones.d.ts +28 -0
  135. package/dist/components/tones.js +12 -0
  136. package/dist/entityStore.d.ts +46 -0
  137. package/dist/entityStore.js +160 -0
  138. package/dist/flows/accessFlows.d.ts +9 -0
  139. package/dist/flows/accessFlows.js +151 -0
  140. package/dist/flows/browser.d.ts +32 -0
  141. package/dist/flows/browser.js +164 -0
  142. package/dist/flows/contactSheet.d.ts +1 -0
  143. package/dist/flows/contactSheet.js +63 -0
  144. package/dist/flows/flowBrowser.d.ts +23 -0
  145. package/dist/flows/flowBrowser.js +21 -0
  146. package/dist/flows/flowSetup.d.ts +3 -0
  147. package/dist/flows/flowSetup.js +35 -0
  148. package/dist/flows/flows.d.ts +41 -0
  149. package/dist/flows/flows.js +148 -0
  150. package/dist/flows/screenshotName.d.ts +1 -0
  151. package/dist/flows/screenshotName.js +6 -0
  152. package/dist/gate.d.ts +8 -0
  153. package/dist/gate.js +52 -0
  154. package/dist/hooks/appContext.d.ts +14 -0
  155. package/dist/hooks/appContext.js +5 -0
  156. package/dist/hooks/appHooks.d.ts +21 -0
  157. package/dist/hooks/appHooks.js +24 -0
  158. package/dist/hooks/useAnimatesOnChange.d.ts +1 -0
  159. package/dist/hooks/useAnimatesOnChange.js +14 -0
  160. package/dist/hooks/useStoreState.d.ts +6 -0
  161. package/dist/hooks/useStoreState.js +5 -0
  162. package/dist/index.d.ts +40 -0
  163. package/dist/index.js +19 -0
  164. package/dist/memoryApi.d.ts +19 -0
  165. package/dist/memoryApi.js +42 -0
  166. package/dist/model.d.ts +94 -0
  167. package/dist/model.js +243 -0
  168. package/dist/page.d.ts +11 -0
  169. package/dist/page.js +9 -0
  170. package/dist/platform.d.ts +1 -0
  171. package/dist/platform.js +5 -0
  172. package/dist/react.d.ts +46 -0
  173. package/dist/react.js +38 -0
  174. package/dist/registered.d.ts +54 -0
  175. package/dist/registered.js +17 -0
  176. package/dist/routes.d.ts +38 -0
  177. package/dist/routes.js +21 -0
  178. package/dist/rules/checkAllFiles.d.ts +1 -0
  179. package/dist/rules/checkAllFiles.js +741 -0
  180. package/dist/rules/checkApiRouting.d.ts +1 -0
  181. package/dist/rules/checkApiRouting.js +112 -0
  182. package/dist/rules/lintPlugin.d.ts +252 -0
  183. package/dist/rules/lintPlugin.js +1157 -0
  184. package/dist/rules/messages.d.ts +48 -0
  185. package/dist/rules/messages.js +48 -0
  186. package/dist/rules/themeTokens.d.ts +6 -0
  187. package/dist/rules/themeTokens.js +64 -0
  188. package/dist/schema.d.ts +2 -0
  189. package/dist/schema.js +1 -0
  190. package/dist/server/auth.d.ts +115 -0
  191. package/dist/server/auth.js +131 -0
  192. package/dist/server/authService.d.ts +9 -0
  193. package/dist/server/authService.js +50 -0
  194. package/dist/server/createServer.d.ts +357 -0
  195. package/dist/server/createServer.js +62 -0
  196. package/dist/server/env.d.ts +15 -0
  197. package/dist/server/env.js +1 -0
  198. package/dist/server/index.d.ts +8 -0
  199. package/dist/server/index.js +5 -0
  200. package/dist/server/session.d.ts +28 -0
  201. package/dist/server/session.js +54 -0
  202. package/dist/server/sessions.d.ts +92 -0
  203. package/dist/server/sessions.js +47 -0
  204. package/dist/server/signInWire.d.ts +52 -0
  205. package/dist/server/signInWire.js +22 -0
  206. package/dist/server/tables.d.ts +242 -0
  207. package/dist/server/tables.js +246 -0
  208. package/dist/server/users.d.ts +189 -0
  209. package/dist/server/users.js +49 -0
  210. package/dist/shortcuts.d.ts +3 -0
  211. package/dist/shortcuts.js +40 -0
  212. package/dist/start.d.ts +20 -0
  213. package/dist/start.js +121 -0
  214. package/dist/state.d.ts +14 -0
  215. package/dist/state.js +17 -0
  216. package/dist/store.d.ts +43 -0
  217. package/dist/store.js +198 -0
  218. package/dist/subscribers.d.ts +5 -0
  219. package/dist/subscribers.js +13 -0
  220. package/dist/table.d.ts +143 -0
  221. package/dist/table.js +91 -0
  222. package/dist/tables.d.ts +77 -0
  223. package/dist/tables.js +216 -0
  224. package/dist/theme/reset.css +84 -0
  225. package/dist/theme/scales.d.ts +76 -0
  226. package/dist/theme/scales.js +82 -0
  227. package/dist/theme/tokens.stylex.d.ts +218 -0
  228. package/dist/theme/tokens.stylex.js +233 -0
  229. package/dist/vite/commandBridge.d.ts +2 -0
  230. package/dist/vite/commandBridge.js +46 -0
  231. package/dist/vite/designManifest.d.ts +25 -0
  232. package/dist/vite/designManifest.js +191 -0
  233. package/dist/vite/localWorker.d.ts +4 -0
  234. package/dist/vite/localWorker.js +59 -0
  235. package/dist/vite/rootAlias.d.ts +2 -0
  236. package/dist/vite/rootAlias.js +15 -0
  237. package/dist/vite/vite.d.ts +7 -0
  238. package/dist/vite/vite.js +88 -0
  239. package/dist/wire.d.ts +7 -0
  240. package/dist/wire.js +1 -0
  241. package/package.json +99 -3
  242. package/presets/lintPlugin.js +7 -0
  243. package/presets/oxlint.json +167 -0
  244. package/presets/register.d.ts +19 -0
  245. package/presets/server.d.ts +2 -0
  246. package/presets/tsconfig.app.json +29 -0
  247. package/presets/tsconfig.server.json +27 -0
@@ -0,0 +1,52 @@
1
+ import { z } from 'zod';
2
+ export declare const verifiedIdentity: z.ZodObject<{
3
+ email: z.ZodEmail;
4
+ name: z.ZodNullable<z.ZodString>;
5
+ avatarUrl: z.ZodNullable<z.ZodString>;
6
+ via: z.ZodEnum<{
7
+ email: "email";
8
+ google: "google";
9
+ }>;
10
+ }, z.core.$strip>;
11
+ export type VerifiedIdentity = z.infer<typeof verifiedIdentity>;
12
+ export declare const exchangeRequest: z.ZodObject<{
13
+ code: z.ZodString;
14
+ }, z.core.$strip>;
15
+ export declare const exchangeReply: z.ZodObject<{
16
+ hostname: z.ZodString;
17
+ identity: z.ZodObject<{
18
+ email: z.ZodEmail;
19
+ name: z.ZodNullable<z.ZodString>;
20
+ avatarUrl: z.ZodNullable<z.ZodString>;
21
+ via: z.ZodEnum<{
22
+ email: "email";
23
+ google: "google";
24
+ }>;
25
+ }, z.core.$strip>;
26
+ }, z.core.$strip>;
27
+ export declare const sendCodeRequest: z.ZodObject<{
28
+ email: z.ZodEmail;
29
+ hostname: z.ZodString;
30
+ }, z.core.$strip>;
31
+ export declare const sendCodeReply: z.ZodObject<{
32
+ challenge: z.ZodString;
33
+ }, z.core.$strip>;
34
+ export declare const verifyCodeRequest: z.ZodObject<{
35
+ challenge: z.ZodString;
36
+ code: z.ZodString;
37
+ }, z.core.$strip>;
38
+ export declare const verifyCodeReply: z.ZodObject<{
39
+ identity: z.ZodObject<{
40
+ email: z.ZodEmail;
41
+ name: z.ZodNullable<z.ZodString>;
42
+ avatarUrl: z.ZodNullable<z.ZodString>;
43
+ via: z.ZodEnum<{
44
+ email: "email";
45
+ google: "google";
46
+ }>;
47
+ }, z.core.$strip>;
48
+ }, z.core.$strip>;
49
+ export type ExchangeReply = z.infer<typeof exchangeReply>;
50
+ export type SendCodeRequest = z.infer<typeof sendCodeRequest>;
51
+ export type SendCodeReply = z.infer<typeof sendCodeReply>;
52
+ export type VerifyCodeRequest = z.infer<typeof verifyCodeRequest>;
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ export const verifiedIdentity = z.object({
3
+ email: z.email(),
4
+ name: z.string().nullable(),
5
+ avatarUrl: z.string().nullable(),
6
+ via: z.enum(['google', 'email'])
7
+ });
8
+ export const exchangeRequest = z.object({ code: z.string() });
9
+ export const exchangeReply = z.object({
10
+ hostname: z.string(),
11
+ identity: verifiedIdentity
12
+ });
13
+ export const sendCodeRequest = z.object({
14
+ email: z.email(),
15
+ hostname: z.string()
16
+ });
17
+ export const sendCodeReply = z.object({ challenge: z.string() });
18
+ export const verifyCodeRequest = z.object({
19
+ challenge: z.string(),
20
+ code: z.string()
21
+ });
22
+ export const verifyCodeReply = z.object({ identity: verifiedIdentity });
@@ -0,0 +1,242 @@
1
+ import type { SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core';
2
+ import type { ServerEnv } from './env.ts';
3
+ import type { Seed, Table, Tables } from '../table.ts';
4
+ type SqlTable = SQLiteTableWithColumns<{
5
+ name: string;
6
+ schema: undefined;
7
+ dialect: 'sqlite';
8
+ columns: Record<string, never>;
9
+ }>;
10
+ export declare function sqlTable(table: Table): SqlTable;
11
+ export declare function sqlTables<Group extends Tables>(tables: Group): Record<keyof Group, SqlTable>;
12
+ export declare function seedSql<Group extends Tables>(tables: Group, seed: Seed<Group>): string;
13
+ export declare function serveTables<Group extends Tables>(tables: Group): import("hono/hono-base").HonoBase<ServerEnv, {
14
+ "/:table": {
15
+ $get: {
16
+ input: {
17
+ param: {
18
+ table: string;
19
+ };
20
+ };
21
+ output: {
22
+ error: "forbidden" | "Signed out" | "No such row";
23
+ };
24
+ outputFormat: "json";
25
+ status: 401 | 403 | 404;
26
+ } | {
27
+ input: {
28
+ param: {
29
+ table: string;
30
+ };
31
+ };
32
+ output: {}[];
33
+ outputFormat: "json";
34
+ status: import("hono/utils/http-status").ContentfulStatusCode;
35
+ } | {
36
+ input: {
37
+ param: {
38
+ table: string;
39
+ };
40
+ };
41
+ output: {
42
+ error: string;
43
+ };
44
+ outputFormat: "json";
45
+ status: 404;
46
+ };
47
+ };
48
+ } & {
49
+ "/:table/public": {
50
+ $get: {
51
+ input: {
52
+ param: {
53
+ table: string;
54
+ };
55
+ };
56
+ output: {}[];
57
+ outputFormat: "json";
58
+ status: import("hono/utils/http-status").ContentfulStatusCode;
59
+ } | {
60
+ input: {
61
+ param: {
62
+ table: string;
63
+ };
64
+ };
65
+ output: {
66
+ error: string;
67
+ };
68
+ outputFormat: "json";
69
+ status: 404;
70
+ };
71
+ };
72
+ } & {
73
+ "/:table": {
74
+ $post: {
75
+ input: {
76
+ param: {
77
+ table: string;
78
+ };
79
+ };
80
+ output: {
81
+ error: "forbidden" | "Signed out" | "No such row";
82
+ };
83
+ outputFormat: "json";
84
+ status: 401 | 403 | 404;
85
+ } | {
86
+ input: {
87
+ param: {
88
+ table: string;
89
+ };
90
+ };
91
+ output: {};
92
+ outputFormat: "json";
93
+ status: 201;
94
+ } | {
95
+ input: {
96
+ param: {
97
+ table: string;
98
+ };
99
+ };
100
+ output: {
101
+ error: string;
102
+ };
103
+ outputFormat: "json";
104
+ status: 404;
105
+ } | {
106
+ input: {
107
+ param: {
108
+ table: string;
109
+ };
110
+ };
111
+ output: {
112
+ error: string;
113
+ };
114
+ outputFormat: "json";
115
+ status: 400;
116
+ } | {
117
+ input: {
118
+ param: {
119
+ table: string;
120
+ };
121
+ };
122
+ output: {
123
+ error: string;
124
+ unique: readonly (readonly string[])[];
125
+ };
126
+ outputFormat: "json";
127
+ status: 409;
128
+ };
129
+ };
130
+ } & {
131
+ "/:table/:id": {
132
+ $put: {
133
+ input: {
134
+ param: {
135
+ table: string;
136
+ } & {
137
+ id: string;
138
+ };
139
+ };
140
+ output: {
141
+ error: "forbidden" | "Signed out" | "No such row";
142
+ };
143
+ outputFormat: "json";
144
+ status: 401 | 403 | 404;
145
+ } | {
146
+ input: {
147
+ param: {
148
+ table: string;
149
+ } & {
150
+ id: string;
151
+ };
152
+ };
153
+ output: {};
154
+ outputFormat: "json";
155
+ status: 200;
156
+ } | {
157
+ input: {
158
+ param: {
159
+ table: string;
160
+ } & {
161
+ id: string;
162
+ };
163
+ };
164
+ output: {
165
+ error: string;
166
+ unique: readonly (readonly string[])[];
167
+ };
168
+ outputFormat: "json";
169
+ status: 409;
170
+ } | {
171
+ input: {
172
+ param: {
173
+ table: string;
174
+ } & {
175
+ id: string;
176
+ };
177
+ };
178
+ output: {
179
+ error: string;
180
+ };
181
+ outputFormat: "json";
182
+ status: 404;
183
+ } | {
184
+ input: {
185
+ param: {
186
+ table: string;
187
+ } & {
188
+ id: string;
189
+ };
190
+ };
191
+ output: {
192
+ error: string;
193
+ };
194
+ outputFormat: "json";
195
+ status: 400;
196
+ };
197
+ };
198
+ } & {
199
+ "/:table/:id": {
200
+ $delete: {
201
+ input: {
202
+ param: {
203
+ table: string;
204
+ } & {
205
+ id: string;
206
+ };
207
+ };
208
+ output: {
209
+ error: "forbidden" | "Signed out" | "No such row";
210
+ };
211
+ outputFormat: "json";
212
+ status: 401 | 403 | 404;
213
+ } | {
214
+ input: {
215
+ param: {
216
+ table: string;
217
+ } & {
218
+ id: string;
219
+ };
220
+ };
221
+ output: {
222
+ error: string;
223
+ };
224
+ outputFormat: "json";
225
+ status: 404;
226
+ } | {
227
+ input: {
228
+ param: {
229
+ table: string;
230
+ } & {
231
+ id: string;
232
+ };
233
+ };
234
+ output: {
235
+ ok: true;
236
+ };
237
+ outputFormat: "json";
238
+ status: import("hono/utils/http-status").ContentfulStatusCode;
239
+ };
240
+ };
241
+ }, "/", "/:table/:id">;
242
+ export {};
@@ -0,0 +1,246 @@
1
+ import { asc, eq, sql } from 'drizzle-orm';
2
+ import { drizzle } from 'drizzle-orm/d1';
3
+ import { integer, real, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
4
+ import { Hono } from 'hono';
5
+ import { z } from 'zod';
6
+ import { changeRefusal, createRefusal, listRefusal, readScope } from "../access.js";
7
+ import { ownerIdOf, seededRows, snakeCase } from "../table.js";
8
+ function typesOf(schema) {
9
+ const { type } = z.toJSONSchema(schema);
10
+ if (typeof type === 'string')
11
+ return [type];
12
+ return Array.isArray(type) ? type : [];
13
+ }
14
+ function sqlColumn(key, schema) {
15
+ const types = typesOf(schema);
16
+ const name = snakeCase(key);
17
+ const column = types.includes('boolean')
18
+ ? integer(name, { mode: 'boolean' })
19
+ : types.includes('integer')
20
+ ? integer(name)
21
+ : types.includes('number')
22
+ ? real(name)
23
+ : text(name);
24
+ return types.includes('null') ? column : column.notNull();
25
+ }
26
+ function uniqueIndexName(table, columns) {
27
+ return [table.name, ...columns, 'unique'].map(snakeCase).join('_');
28
+ }
29
+ export function sqlTable(table) {
30
+ const columns = Object.fromEntries(Object.entries(table.columns).map(([key, schema]) => [
31
+ key,
32
+ sqlColumn(key, schema)
33
+ ]));
34
+ return sqliteTable(snakeCase(table.name), {
35
+ id: text('id').primaryKey(),
36
+ ...(table.owned ? { ownerId: text('owner_id').notNull() } : {}),
37
+ ...columns,
38
+ createdAt: text('created_at')
39
+ .notNull()
40
+ .default(sql `(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`)
41
+ }, (target) => table.unique.map((unique) => {
42
+ const [first, ...rest] = unique.map((key) => Reflect.get(target, key));
43
+ return uniqueIndex(uniqueIndexName(table, unique)).on(first, ...rest);
44
+ }));
45
+ }
46
+ export function sqlTables(tables) {
47
+ return Object.fromEntries(Object.entries(tables).map(([key, table]) => [key, sqlTable(table)]));
48
+ }
49
+ function column(target, key) {
50
+ const found = Reflect.get(target, key);
51
+ if (found === undefined)
52
+ throw new Error(`No column ${key}`);
53
+ return found;
54
+ }
55
+ function sqlLiteral(value) {
56
+ if (value === null)
57
+ return 'NULL';
58
+ if (typeof value === 'boolean')
59
+ return value ? '1' : '0';
60
+ if (typeof value === 'number')
61
+ return String(value);
62
+ return `'${value.replaceAll("'", "''")}'`;
63
+ }
64
+ function seedOwners(seed) {
65
+ const owners = Object.values(seed).flatMap((rows) => (rows ?? []).flatMap((row) => {
66
+ const owner = Reflect.get(row, 'owner');
67
+ return typeof owner === 'string' ? [owner.toLowerCase()] : [];
68
+ }));
69
+ return [...new Set(owners)];
70
+ }
71
+ function ownerSql(email) {
72
+ return `(SELECT id FROM users WHERE email = ${sqlLiteral(email)})`;
73
+ }
74
+ export function seedSql(tables, seed) {
75
+ const users = seedOwners(seed).map((email) => `INSERT OR IGNORE INTO users (id, email, name) VALUES (${sqlLiteral(ownerIdOf(email))}, ${sqlLiteral(email)}, ${sqlLiteral(email.split('@')[0] ?? email)});`);
76
+ const rowsSql = Object.values(tables).flatMap((table) => {
77
+ const rows = seededRows(seed, table.name);
78
+ if (rows.length === 0)
79
+ return [];
80
+ const keys = ['id', 'createdAt', ...Object.keys(table.columns)];
81
+ const source = Reflect.get(seed, table.name);
82
+ const owners = (Array.isArray(source) ? source : []).map((raw) => {
83
+ const owner = Reflect.get(Object(raw), 'owner');
84
+ return typeof owner === 'string' ? owner.toLowerCase() : '';
85
+ });
86
+ const values = rows
87
+ .map((row, index) => `(${[
88
+ ...(table.owned ? [ownerSql(owners[index] ?? '')] : []),
89
+ ...keys.map((key) => sqlLiteral((row[key] ?? null)))
90
+ ].join(', ')})`)
91
+ .join(',\n ');
92
+ const names = [...(table.owned ? ['ownerId'] : []), ...keys];
93
+ return [
94
+ `INSERT INTO ${snakeCase(table.name)} (${names.map(snakeCase).join(', ')})`,
95
+ `SELECT * FROM (VALUES\n ${values}\n)`,
96
+ `WHERE NOT EXISTS (SELECT 1 FROM ${snakeCase(table.name)});`
97
+ ];
98
+ });
99
+ return [...users, ...rowsSql].join('\n');
100
+ }
101
+ const REFUSAL_STATUSES = {
102
+ signedOut: { status: 401, error: 'Signed out' },
103
+ forbidden: { status: 403, error: 'forbidden' },
104
+ missing: { status: 404, error: 'No such row' }
105
+ };
106
+ function refuse(c, refusal) {
107
+ const { status, error } = REFUSAL_STATUSES[refusal];
108
+ return c.json({ error }, status);
109
+ }
110
+ function isUniqueViolation(failure) {
111
+ for (let error = failure; error instanceof Error; error = error.cause) {
112
+ if (error.message.includes('UNIQUE constraint failed'))
113
+ return true;
114
+ }
115
+ return false;
116
+ }
117
+ export function serveTables(tables) {
118
+ const known = new Map(Object.values(tables).map((table) => [
119
+ table.name,
120
+ { table, target: sqlTable(table) }
121
+ ]));
122
+ function selection(table, target) {
123
+ return Object.fromEntries([
124
+ 'id',
125
+ 'createdAt',
126
+ ...(table.owned ? ['ownerId'] : []),
127
+ ...Object.keys(table.columns)
128
+ ].map((key) => [key, column(target, key)]));
129
+ }
130
+ function publicSelection(table, target) {
131
+ return Object.fromEntries(['id', ...table.public].map((key) => [key, column(target, key)]));
132
+ }
133
+ async function findRow(c, table, target) {
134
+ const row = await drizzle(c.env.DB)
135
+ .select(selection(table, target))
136
+ .from(target)
137
+ .where(eq(column(target, 'id'), c.req.param('id') ?? ''))
138
+ .get();
139
+ return row;
140
+ }
141
+ async function saveOrConflict(c, table, save) {
142
+ try {
143
+ return { saved: await save() };
144
+ }
145
+ catch (failure) {
146
+ if (!isUniqueViolation(failure))
147
+ throw failure;
148
+ return {
149
+ conflict: c.json({ error: 'conflict', unique: table.unique }, 409)
150
+ };
151
+ }
152
+ }
153
+ return new Hono()
154
+ .get('/:table', async (c) => {
155
+ const entry = known.get(c.req.param('table'));
156
+ if (entry === undefined)
157
+ return c.json({ error: 'No such table' }, 404);
158
+ const { table, target } = entry;
159
+ const viewer = c.get('viewer');
160
+ const refusal = listRefusal(table, viewer);
161
+ if (refusal !== undefined)
162
+ return refuse(c, refusal);
163
+ const scope = readScope(table, viewer);
164
+ const query = drizzle(c.env.DB)
165
+ .select(selection(table, target))
166
+ .from(target);
167
+ const rows = await (typeof scope === 'object'
168
+ ? query.where(eq(column(target, 'ownerId'), scope.ownerId))
169
+ : query).orderBy(asc(column(target, 'createdAt')), asc(column(target, 'id')));
170
+ return c.json(rows);
171
+ })
172
+ .get('/:table/public', async (c) => {
173
+ const entry = known.get(c.req.param('table'));
174
+ if (entry === undefined || entry.table.public.length === 0) {
175
+ return c.json({ error: 'No such table' }, 404);
176
+ }
177
+ const { table, target } = entry;
178
+ const rows = await drizzle(c.env.DB)
179
+ .select(publicSelection(table, target))
180
+ .from(target)
181
+ .orderBy(asc(column(target, 'createdAt')), asc(column(target, 'id')));
182
+ return c.json(rows);
183
+ })
184
+ .post('/:table', async (c) => {
185
+ const entry = known.get(c.req.param('table'));
186
+ if (entry === undefined)
187
+ return c.json({ error: 'No such table' }, 404);
188
+ const { table, target } = entry;
189
+ const viewer = c.get('viewer');
190
+ const refusal = createRefusal(table, viewer);
191
+ if (refusal !== undefined)
192
+ return refuse(c, refusal);
193
+ const parsed = table.incoming.safeParse(await c.req.json());
194
+ if (!parsed.success) {
195
+ return c.json({ error: z.prettifyError(parsed.error) }, 400);
196
+ }
197
+ const values = table.owned && viewer !== null
198
+ ? { ...parsed.data, ownerId: viewer.userId }
199
+ : parsed.data;
200
+ const result = await saveOrConflict(c, table, () => drizzle(c.env.DB)
201
+ .insert(target)
202
+ .values(values)
203
+ .returning(selection(table, target))
204
+ .get());
205
+ return 'conflict' in result ? result.conflict : c.json(result.saved, 201);
206
+ })
207
+ .put('/:table/:id', async (c) => {
208
+ const entry = known.get(c.req.param('table'));
209
+ if (entry === undefined)
210
+ return c.json({ error: 'No such table' }, 404);
211
+ const { table, target } = entry;
212
+ const existing = await findRow(c, table, target);
213
+ const refusal = changeRefusal(table, 'update', c.get('viewer'), existing);
214
+ if (refusal !== undefined)
215
+ return refuse(c, refusal);
216
+ const parsed = table.incoming.safeParse(await c.req.json());
217
+ if (!parsed.success) {
218
+ return c.json({ error: z.prettifyError(parsed.error) }, 400);
219
+ }
220
+ const { id, ...changes } = parsed.data;
221
+ if (id !== c.req.param('id')) {
222
+ return c.json({ error: 'The row id does not match the URL' }, 400);
223
+ }
224
+ const result = await saveOrConflict(c, table, () => drizzle(c.env.DB)
225
+ .update(target)
226
+ .set(changes)
227
+ .where(eq(column(target, 'id'), id))
228
+ .returning(selection(table, target))
229
+ .get());
230
+ return 'conflict' in result ? result.conflict : c.json(result.saved, 200);
231
+ })
232
+ .delete('/:table/:id', async (c) => {
233
+ const entry = known.get(c.req.param('table'));
234
+ if (entry === undefined)
235
+ return c.json({ error: 'No such table' }, 404);
236
+ const { table, target } = entry;
237
+ const existing = await findRow(c, table, target);
238
+ const refusal = changeRefusal(table, 'delete', c.get('viewer'), existing);
239
+ if (refusal !== undefined)
240
+ return refuse(c, refusal);
241
+ await drizzle(c.env.DB)
242
+ .delete(target)
243
+ .where(eq(column(target, 'id'), c.req.param('id')));
244
+ return c.json({ ok: true });
245
+ });
246
+ }