@weft-js/create 0.1.1 → 0.1.3

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 (127) hide show
  1. package/README.md +393 -19
  2. package/bin/create-weft.mjs +62 -14
  3. package/package.json +26 -16
  4. package/scripts/tarballs.mjs +56 -6
  5. package/src/data.mjs +90 -18
  6. package/src/index.mjs +210 -51
  7. package/src/stack/pages.mjs +151 -0
  8. package/src/stack/schema.mjs +195 -0
  9. package/src/stack.mjs +102 -313
  10. package/src/templates.mjs +174 -0
  11. package/src/wizard.mjs +65 -16
  12. package/templates/admin/files/drizzle/.schema-hash +1 -0
  13. package/templates/admin/files/drizzle/0000_admin.sql +136 -0
  14. package/templates/admin/files/drizzle/meta/0000_snapshot.json +771 -0
  15. package/templates/admin/files/drizzle/meta/_journal.json +13 -0
  16. package/templates/admin/files/public/favicon.svg +1 -0
  17. package/templates/admin/files/src/auth.server.ts +24 -0
  18. package/templates/admin/files/src/components/AuthPage.tsx +33 -0
  19. package/templates/admin/files/src/components/Failed.tsx +19 -0
  20. package/templates/admin/files/src/components/Logo.tsx +16 -0
  21. package/templates/admin/files/src/components/OrderDetail.tsx +93 -0
  22. package/templates/admin/files/src/components/OrdersFilters.tsx +44 -0
  23. package/templates/admin/files/src/components/OrdersTable.tsx +144 -0
  24. package/templates/admin/files/src/components/Overview.tsx +113 -0
  25. package/templates/admin/files/src/components/StatusBadge.tsx +12 -0
  26. package/templates/admin/files/src/db/schema.ts +68 -0
  27. package/templates/admin/files/src/lib/orders.ts +60 -0
  28. package/templates/admin/files/src/routes/404.tsx +20 -0
  29. package/templates/admin/files/src/routes/_error.tsx +20 -0
  30. package/templates/admin/files/src/routes/_layout.tsx +6 -0
  31. package/templates/admin/files/src/routes/admin/_layout.tsx +89 -0
  32. package/templates/admin/files/src/routes/admin/index.tsx +45 -0
  33. package/templates/admin/files/src/routes/admin/nested/_layout.tsx +35 -0
  34. package/templates/admin/files/src/routes/admin/nested/index.tsx +77 -0
  35. package/templates/admin/files/src/routes/admin/orders/[id].tsx +24 -0
  36. package/templates/admin/files/src/routes/admin/orders/index.tsx +28 -0
  37. package/templates/admin/files/src/routes/admin/per-row.tsx +81 -0
  38. package/templates/admin/files/src/routes/admin/slow.tsx +74 -0
  39. package/templates/admin/files/src/routes/index.tsx +13 -0
  40. package/templates/admin/files/src/routes/login.tsx +53 -0
  41. package/templates/admin/files/src/routes/signup.tsx +60 -0
  42. package/templates/admin/files/src/server/orders.ts +193 -0
  43. package/templates/admin/files/src/server/reports.ts +92 -0
  44. package/templates/admin/files/src/server/session.ts +42 -0
  45. package/templates/admin/files/src/theme.css +43 -0
  46. package/templates/admin/files/weft.config.ts +31 -0
  47. package/templates/admin/template.json +22 -0
  48. package/templates/catalog/files/drizzle/.schema-hash +1 -0
  49. package/templates/catalog/files/drizzle/0000_catalog.sql +47 -0
  50. package/templates/catalog/files/drizzle/meta/0000_snapshot.json +263 -0
  51. package/templates/catalog/files/drizzle/meta/_journal.json +13 -0
  52. package/templates/catalog/files/public/favicon.svg +1 -0
  53. package/templates/catalog/files/seed.sql +13 -0
  54. package/templates/catalog/files/src/components/AddToCart.tsx +22 -0
  55. package/templates/catalog/files/src/components/Aisles.tsx +31 -0
  56. package/templates/catalog/files/src/components/CartLines.tsx +96 -0
  57. package/templates/catalog/files/src/components/CartLink.tsx +29 -0
  58. package/templates/catalog/files/src/components/Failed.tsx +19 -0
  59. package/templates/catalog/files/src/components/Featured.tsx +19 -0
  60. package/templates/catalog/files/src/components/Filters.tsx +40 -0
  61. package/templates/catalog/files/src/components/Logo.tsx +16 -0
  62. package/templates/catalog/files/src/components/ProductArt.tsx +42 -0
  63. package/templates/catalog/files/src/components/ProductCard.tsx +42 -0
  64. package/templates/catalog/files/src/components/ProductList.tsx +65 -0
  65. package/templates/catalog/files/src/components/Related.tsx +19 -0
  66. package/templates/catalog/files/src/components/SiteFooter.tsx +9 -0
  67. package/templates/catalog/files/src/components/SiteHeader.tsx +16 -0
  68. package/templates/catalog/files/src/db/schema.ts +53 -0
  69. package/templates/catalog/files/src/lib/catalog.ts +35 -0
  70. package/templates/catalog/files/src/routes/404.tsx +18 -0
  71. package/templates/catalog/files/src/routes/_error.tsx +18 -0
  72. package/templates/catalog/files/src/routes/_layout.tsx +14 -0
  73. package/templates/catalog/files/src/routes/c/[slug].tsx +53 -0
  74. package/templates/catalog/files/src/routes/cart.tsx +29 -0
  75. package/templates/catalog/files/src/routes/index.tsx +84 -0
  76. package/templates/catalog/files/src/routes/p/[slug].tsx +67 -0
  77. package/templates/catalog/files/src/server/cart.ts +125 -0
  78. package/templates/catalog/files/src/server/catalog.ts +107 -0
  79. package/templates/catalog/files/src/theme.css +159 -0
  80. package/templates/catalog/files/weft.config.ts +30 -0
  81. package/templates/catalog/template.json +20 -0
  82. package/templates/landing/files/.weft/plugins.lock.json +42 -0
  83. package/templates/landing/files/assets/fonts/LICENSE-figtree.txt +93 -0
  84. package/templates/landing/files/assets/fonts/figtree.woff2 +0 -0
  85. package/templates/landing/files/assets/hero.webp +0 -0
  86. package/templates/landing/files/drizzle/.schema-hash +1 -0
  87. package/templates/landing/files/drizzle/0000_landing.sql +6 -0
  88. package/templates/landing/files/drizzle/meta/0000_snapshot.json +60 -0
  89. package/templates/landing/files/drizzle/meta/_journal.json +13 -0
  90. package/templates/landing/files/public/favicon.svg +1 -0
  91. package/templates/landing/files/src/components/SiteFooter.tsx +24 -0
  92. package/templates/landing/files/src/components/SiteHeader.tsx +23 -0
  93. package/templates/landing/files/src/components/WaitlistForm.tsx +36 -0
  94. package/templates/landing/files/src/db/schema.ts +7 -0
  95. package/templates/landing/files/src/routes/404.tsx +24 -0
  96. package/templates/landing/files/src/routes/_error.tsx +24 -0
  97. package/templates/landing/files/src/routes/_layout.tsx +14 -0
  98. package/templates/landing/files/src/routes/index.tsx +130 -0
  99. package/templates/landing/files/src/routes/joined.tsx +30 -0
  100. package/templates/landing/files/src/routes/pricing.tsx +131 -0
  101. package/templates/landing/files/src/routes/privacy.tsx +56 -0
  102. package/templates/landing/files/src/server/waitlist.ts +13 -0
  103. package/templates/landing/files/src/theme.css +108 -0
  104. package/templates/landing/files/weft.config.ts +44 -0
  105. package/templates/landing/template.json +17 -0
  106. package/templates/todo/files/drizzle/.schema-hash +1 -0
  107. package/templates/todo/files/drizzle/0000_todo.sql +82 -0
  108. package/templates/todo/files/drizzle/meta/0000_snapshot.json +462 -0
  109. package/templates/todo/files/drizzle/meta/_journal.json +13 -0
  110. package/templates/todo/files/public/favicon.svg +1 -0
  111. package/templates/todo/files/src/components/AuthPage.tsx +33 -0
  112. package/templates/todo/files/src/components/Logo.tsx +14 -0
  113. package/templates/todo/files/src/components/NewTodo.tsx +27 -0
  114. package/templates/todo/files/src/components/TodoList.tsx +101 -0
  115. package/templates/todo/files/src/db/schema.ts +18 -0
  116. package/templates/todo/files/src/lib/views.ts +13 -0
  117. package/templates/todo/files/src/routes/404.tsx +20 -0
  118. package/templates/todo/files/src/routes/_error.tsx +20 -0
  119. package/templates/todo/files/src/routes/_layout.tsx +6 -0
  120. package/templates/todo/files/src/routes/index.tsx +13 -0
  121. package/templates/todo/files/src/routes/login.tsx +53 -0
  122. package/templates/todo/files/src/routes/signup.tsx +57 -0
  123. package/templates/todo/files/src/routes/todos.tsx +87 -0
  124. package/templates/todo/files/src/server/todos.ts +98 -0
  125. package/templates/todo/files/src/theme.css +71 -0
  126. package/templates/todo/files/weft.config.ts +30 -0
  127. package/templates/todo/template.json +18 -0
@@ -0,0 +1,151 @@
1
+ // Tailwind: the same files `weft add tailwind` writes (packages/cli/src/tailwind.mjs, held equal by a test).
2
+ /** What `--ui tailwind` adds, pinned. */
3
+ export const tailwindPackages = { tailwindcss: '4.3.3', '@tailwindcss/vite': '4.3.3' }
4
+ /** `vite.config.ts` for `--ui tailwind`. */
5
+ export const TAILWIND_VITE = `import tailwindcss from '@tailwindcss/vite'
6
+
7
+ export default { plugins: [tailwindcss()] }
8
+ `
9
+ /** The stylesheet for `--ui tailwind`. */
10
+ export const TAILWIND_CSS = `@import "tailwindcss";
11
+ `
12
+ const tw = {
13
+ card: 'mx-auto max-w-2xl space-y-4 rounded-lg border border-current/15 p-6',
14
+ title: 'text-2xl font-semibold',
15
+ button: 'rounded-md border border-current/30 px-3 py-1.5 hover:bg-current/5',
16
+ input: 'rounded-md border border-current/30 px-3 py-1.5',
17
+ }
18
+ /** The class of a scaffold element: Tailwind's utilities, or the plain CSS in src/app.css. */
19
+ export const classes = (ui, part) => (ui === 'tailwind' ? tw[part] : part === 'card' ? 'card' : '')
20
+ const attr = (ui, part) => (ui === 'tailwind' ? ` class="${tw[part]}"` : '')
21
+
22
+ /** The page's controls in each UI kit: warp's components, or plain elements with the same roles and names. */
23
+ export const controls = (ui) =>
24
+ ui === 'warp'
25
+ ? {
26
+ imports: `import { Alert, Button, Card, Checkbox, Input } from '@weft-js/warp'\n`,
27
+ card: (title, body) => `<Card title=${JSON.stringify(title)}>${body}</Card>`,
28
+ input: (label, box, type = 'text') =>
29
+ `<Input label=${JSON.stringify(label)} type="${type}" value={${box}.value} onInput={e => (${box}.value = e.currentTarget.value)} />`,
30
+ submit: (label, pending) => `<Button type="submit" loading={${pending}}>${label}</Button>`,
31
+ button: (label, onClick, extra = '') =>
32
+ `<Button emphasis="ghost" ${extra}onClick={${onClick}}>${label}</Button>`,
33
+ error: (box) => `{${box}.value && <Alert tone="critical" live>{${box}.value}</Alert>}`,
34
+ row: `<Checkbox checked={t.done} onChange={() => toggleTodo({ id: t.id })}>{t.title}</Checkbox>
35
+ <Button emphasis="ghost" tone="neutral" size="sm" icon="x" aria-label={\`Delete \${t.title}\`} onClick={() => deleteTodo({ id: t.id })} />`,
36
+ }
37
+ : {
38
+ imports: '',
39
+ card: (title, body) =>
40
+ `<section class="${classes(ui, 'card')}"><h1${attr(ui, 'title')}>${title}</h1>${body}</section>`,
41
+ input: (label, box, type = 'text') =>
42
+ `<label>${label} <input${attr(ui, 'input')} type="${type}" value={${box}.value} onInput={e => (${box}.value = e.currentTarget.value)} /></label>`,
43
+ submit: (label, pending) =>
44
+ `<button${attr(ui, 'button')} type="submit" disabled={${pending}}>${label}</button>`,
45
+ button: (label, onClick, extra = '') =>
46
+ `<button${attr(ui, 'button')} type="button" ${extra}onClick={${onClick}}>${label}</button>`,
47
+ error: (box) => `{${box}.value && <p role="alert">{${box}.value}</p>}`,
48
+ row: `<label>
49
+ <input type="checkbox" checked={t.done} onChange={() => toggleTodo({ id: t.id })} /> {t.title}
50
+ </label>
51
+ <button type="button" aria-label={\`Delete \${t.title}\`} onClick={() => deleteTodo({ id: t.id })}>×</button>`,
52
+ }
53
+
54
+ /** `/todos`: the list on the server functions, written for this `--ui`. */
55
+ export function todosPage(ui) {
56
+ const c = controls(ui)
57
+ return `import type { Meta } from '@weft-js/start'
58
+ import { Boundary, state } from '@weft-js/core'
59
+ ${c.imports}import { addTodo, deleteTodo, listTodos, toggleTodo } from '#/server/todos'
60
+
61
+ export const meta: Meta = { title: 'Todos', description: 'Your todo list: add, tick off and delete.' }
62
+
63
+ // The query is made in here, inside the Boundary, so the Boundary sees it: it shows \`pending\` until the list arrives.
64
+ function TodoList() {
65
+ const todos = listTodos()
66
+ return (
67
+ <ul class="todos">
68
+ {(todos.value ?? []).map(t => (
69
+ <li key={t.id} class={t.done ? 'done' : 'open'}>
70
+ ${c.row}
71
+ </li>
72
+ ))}
73
+ </ul>
74
+ )
75
+ }
76
+
77
+ export default function Todos() {
78
+ const title = state('')
79
+ const problem = state('')
80
+ const add = async (e: SubmitEvent) => {
81
+ e.preventDefault()
82
+ const text = title.value.trim()
83
+ if (!text) return
84
+ title.value = ''
85
+ problem.value = ''
86
+ await addTodo({ title: text }).catch((err: Error) => (problem.value = err.message))
87
+ }
88
+ return (
89
+ ${c.card(
90
+ 'Todos',
91
+ `
92
+ <form onSubmit={add}>
93
+ ${c.input('New todo', 'title')}
94
+ ${c.submit('Add', 'addTodo.pending.value')}
95
+ </form>
96
+ ${c.error('problem')}
97
+ <Boundary pending={<p>Loading…</p>}>
98
+ <TodoList />
99
+ </Boundary>
100
+ `,
101
+ )}
102
+ )
103
+ }
104
+ `
105
+ }
106
+
107
+ /** `/login`: sign in and sign up, written for this `--ui`. */
108
+ export function loginPage(ui) {
109
+ const c = controls(ui)
110
+ return `import { redirect, type LoadArgs, type Meta } from '@weft-js/start'
111
+ import { state } from '@weft-js/core'
112
+ ${c.imports}import { signIn, signUp } from '#/server/session'
113
+
114
+ export const load = ({ ctx }: LoadArgs) => {
115
+ if (ctx.user) redirect('/todos')
116
+ }
117
+
118
+ export const meta: Meta = { title: 'Sign in', description: 'Sign in, or make an account.' }
119
+
120
+ export default function Login() {
121
+ const creating = state(false)
122
+ const name = state('')
123
+ const email = state('')
124
+ const password = state('')
125
+ const problem = state('')
126
+ const submit = async (e: SubmitEvent) => {
127
+ e.preventDefault()
128
+ problem.value = ''
129
+ const done = creating.value
130
+ ? signUp({ name: name.value, email: email.value, password: password.value })
131
+ : signIn({ email: email.value, password: password.value })
132
+ await done.catch((err: Error) => (problem.value = err.message))
133
+ }
134
+ return (
135
+ ${c.card(
136
+ 'Sign in',
137
+ `
138
+ <form onSubmit={submit}>
139
+ {creating.value && ${c.input('Name', 'name')}}
140
+ ${c.input('Email', 'email', 'email')}
141
+ ${c.input('Password', 'password', 'password')}
142
+ ${c.submit(`{creating.value ? 'Create account' : 'Sign in'}`, 'signIn.pending.value || signUp.pending.value')}
143
+ </form>
144
+ ${c.error('problem')}
145
+ ${c.button(`{creating.value ? 'I have an account' : 'Create an account'}`, '() => (creating.value = !creating.value)')}
146
+ `,
147
+ )}
148
+ )
149
+ }
150
+ `
151
+ }
@@ -0,0 +1,195 @@
1
+ export const authTablesFile = (db) =>
2
+ ({
3
+ sqlite: () =>
4
+ `import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'\n${authTablesSqlite()}`,
5
+ postgres: () =>
6
+ `import { boolean, pgTable, text, timestamp } from 'drizzle-orm/pg-core'\n${authTablesPg()}`,
7
+ mysql: () =>
8
+ `import { boolean, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core'\n${authTablesMysql()}`,
9
+ })[db]()
10
+
11
+ const camel = (column) => column.replace(/_(\w)/g, (_, c) => c.toUpperCase())
12
+
13
+ /** A template's `src/db/auth.ts`: Better Auth's own column names with time zones, as examples/shared/pg/schema.sql has them. */
14
+ export const sharedAuthTables = () =>
15
+ `import { boolean, pgTable, text, timestamp } from 'drizzle-orm/pg-core'\n${authTablesPg().replace(
16
+ /(text|boolean|timestamp)\('(\w+)'\)/g,
17
+ (_, type, column) =>
18
+ `${type}('${camel(column)}'${type === 'timestamp' ? ', { withTimezone: true }' : ''})`,
19
+ )}`
20
+
21
+ export function sqliteSchema(auth) {
22
+ const owner = auth
23
+ ? `\n userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),`
24
+ : ''
25
+ return `import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
26
+ ${auth ? authTablesSqlite() : ''}
27
+ export const todos = sqliteTable('todos', {
28
+ id: text('id').primaryKey(),${owner}
29
+ title: text('title').notNull(),
30
+ done: integer('done', { mode: 'boolean' }).notNull().default(false),
31
+ createdAt: integer('created_at').notNull(),
32
+ })
33
+ `
34
+ }
35
+
36
+ export function pgSchema(auth) {
37
+ const owner = auth
38
+ ? `\n userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),`
39
+ : ''
40
+ return `import { bigint, boolean, pgTable, text${auth ? ', timestamp' : ''} } from 'drizzle-orm/pg-core'
41
+ ${auth ? authTablesPg() : ''}
42
+ export const todos = pgTable('todos', {
43
+ id: text('id').primaryKey(),${owner}
44
+ title: text('title').notNull(),
45
+ done: boolean('done').notNull().default(false),
46
+ createdAt: bigint('created_at', { mode: 'number' }).notNull(),
47
+ })
48
+ `
49
+ }
50
+
51
+ // MySQL can't index a TEXT column, so ids, keys and anything unique are VARCHAR. Otherwise Postgres's tables hold.
52
+ const authTablesMysql = () =>
53
+ authTablesPg()
54
+ .replaceAll('pgTable', 'mysqlTable')
55
+ .replace(
56
+ /text\('(id|user_id|email|token|account_id|provider_id|identifier)'\)/g,
57
+ "varchar('$1', { length: 255 })",
58
+ )
59
+ .replace(/timestamp\('(\w+)'\)/g, "timestamp('$1', { fsp: 3 })")
60
+
61
+ export function mysqlSchema(auth) {
62
+ const owner = auth
63
+ ? `\n userId: varchar('user_id', { length: 255 }).notNull().references(() => user.id, { onDelete: 'cascade' }),`
64
+ : ''
65
+ return `import { bigint, boolean, mysqlTable, ${auth ? 'text, timestamp, ' : ''}varchar } from 'drizzle-orm/mysql-core'
66
+ ${auth ? authTablesMysql() : ''}
67
+ export const todos = mysqlTable('todos', {
68
+ id: varchar('id', { length: 36 }).primaryKey(),${owner}
69
+ title: varchar('title', { length: 200 }).notNull(),
70
+ done: boolean('done').notNull().default(false),
71
+ createdAt: bigint('created_at', { mode: 'number' }).notNull(),
72
+ })
73
+ `
74
+ }
75
+
76
+ const authTablesSqlite = () => {
77
+ const when = (col, opts = '.notNull()') => `integer('${col}', { mode: 'timestamp_ms' })${opts}`
78
+ return `
79
+ // Better Auth's tables: it reads and writes them through Drizzle, with these names.
80
+ export const user = sqliteTable('user', {
81
+ id: text('id').primaryKey(),
82
+ name: text('name').notNull(),
83
+ email: text('email').notNull().unique(),
84
+ emailVerified: integer('email_verified', { mode: 'boolean' }).notNull(),
85
+ image: text('image'),
86
+ createdAt: ${when('created_at')},
87
+ updatedAt: ${when('updated_at')},
88
+ })
89
+
90
+ export const session = sqliteTable('session', {
91
+ id: text('id').primaryKey(),
92
+ expiresAt: ${when('expires_at')},
93
+ token: text('token').notNull().unique(),
94
+ createdAt: ${when('created_at')},
95
+ updatedAt: ${when('updated_at')},
96
+ ipAddress: text('ip_address'),
97
+ userAgent: text('user_agent'),
98
+ userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
99
+ })
100
+
101
+ export const account = sqliteTable('account', {
102
+ id: text('id').primaryKey(),
103
+ accountId: text('account_id').notNull(),
104
+ providerId: text('provider_id').notNull(),
105
+ userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
106
+ accessToken: text('access_token'),
107
+ refreshToken: text('refresh_token'),
108
+ idToken: text('id_token'),
109
+ accessTokenExpiresAt: ${when('access_token_expires_at', '')},
110
+ refreshTokenExpiresAt: ${when('refresh_token_expires_at', '')},
111
+ scope: text('scope'),
112
+ password: text('password'),
113
+ createdAt: ${when('created_at')},
114
+ updatedAt: ${when('updated_at')},
115
+ })
116
+
117
+ export const verification = sqliteTable('verification', {
118
+ id: text('id').primaryKey(),
119
+ identifier: text('identifier').notNull(),
120
+ value: text('value').notNull(),
121
+ expiresAt: ${when('expires_at')},
122
+ createdAt: ${when('created_at', '')},
123
+ updatedAt: ${when('updated_at', '')},
124
+ })
125
+
126
+ export const jwks = sqliteTable('jwks', {
127
+ id: text('id').primaryKey(),
128
+ publicKey: text('public_key').notNull(),
129
+ privateKey: text('private_key').notNull(),
130
+ createdAt: ${when('created_at')},
131
+ expiresAt: ${when('expires_at', '')},
132
+ alg: text('alg'),
133
+ crv: text('crv'),
134
+ })
135
+ `
136
+ }
137
+
138
+ const authTablesPg = () => `
139
+ // Better Auth's tables: it reads and writes them through Drizzle, with these names.
140
+ export const user = pgTable('user', {
141
+ id: text('id').primaryKey(),
142
+ name: text('name').notNull(),
143
+ email: text('email').notNull().unique(),
144
+ emailVerified: boolean('email_verified').notNull(),
145
+ image: text('image'),
146
+ createdAt: timestamp('created_at').notNull(),
147
+ updatedAt: timestamp('updated_at').notNull(),
148
+ })
149
+
150
+ export const session = pgTable('session', {
151
+ id: text('id').primaryKey(),
152
+ expiresAt: timestamp('expires_at').notNull(),
153
+ token: text('token').notNull().unique(),
154
+ createdAt: timestamp('created_at').notNull(),
155
+ updatedAt: timestamp('updated_at').notNull(),
156
+ ipAddress: text('ip_address'),
157
+ userAgent: text('user_agent'),
158
+ userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
159
+ })
160
+
161
+ export const account = pgTable('account', {
162
+ id: text('id').primaryKey(),
163
+ accountId: text('account_id').notNull(),
164
+ providerId: text('provider_id').notNull(),
165
+ userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
166
+ accessToken: text('access_token'),
167
+ refreshToken: text('refresh_token'),
168
+ idToken: text('id_token'),
169
+ accessTokenExpiresAt: timestamp('access_token_expires_at'),
170
+ refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
171
+ scope: text('scope'),
172
+ password: text('password'),
173
+ createdAt: timestamp('created_at').notNull(),
174
+ updatedAt: timestamp('updated_at').notNull(),
175
+ })
176
+
177
+ export const verification = pgTable('verification', {
178
+ id: text('id').primaryKey(),
179
+ identifier: text('identifier').notNull(),
180
+ value: text('value').notNull(),
181
+ expiresAt: timestamp('expires_at').notNull(),
182
+ createdAt: timestamp('created_at'),
183
+ updatedAt: timestamp('updated_at'),
184
+ })
185
+
186
+ export const jwks = pgTable('jwks', {
187
+ id: text('id').primaryKey(),
188
+ publicKey: text('public_key').notNull(),
189
+ privateKey: text('private_key').notNull(),
190
+ createdAt: timestamp('created_at').notNull(),
191
+ expiresAt: timestamp('expires_at'),
192
+ alg: text('alg'),
193
+ crv: text('crv'),
194
+ })
195
+ `