@beignet/cli 0.0.9 → 0.0.11

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 (76) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +70 -21
  3. package/dist/choices.d.ts +32 -3
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +54 -3
  6. package/dist/choices.js.map +1 -1
  7. package/dist/create-prompts.d.ts +5 -4
  8. package/dist/create-prompts.d.ts.map +1 -1
  9. package/dist/create-prompts.js +22 -4
  10. package/dist/create-prompts.js.map +1 -1
  11. package/dist/create.d.ts +7 -1
  12. package/dist/create.d.ts.map +1 -1
  13. package/dist/create.js +13 -3
  14. package/dist/create.js.map +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +47 -6
  17. package/dist/index.js.map +1 -1
  18. package/dist/make.d.ts +21 -1
  19. package/dist/make.d.ts.map +1 -1
  20. package/dist/make.js +210 -37
  21. package/dist/make.js.map +1 -1
  22. package/dist/mcp.d.ts +19 -0
  23. package/dist/mcp.d.ts.map +1 -0
  24. package/dist/mcp.js +250 -0
  25. package/dist/mcp.js.map +1 -0
  26. package/dist/templates/agents.d.ts +14 -0
  27. package/dist/templates/agents.d.ts.map +1 -0
  28. package/dist/templates/agents.js +98 -0
  29. package/dist/templates/agents.js.map +1 -0
  30. package/dist/templates/base.d.ts.map +1 -1
  31. package/dist/templates/base.js +79 -9
  32. package/dist/templates/base.js.map +1 -1
  33. package/dist/templates/db/index.d.ts +21 -0
  34. package/dist/templates/db/index.d.ts.map +1 -0
  35. package/dist/templates/db/index.js +14 -0
  36. package/dist/templates/db/index.js.map +1 -0
  37. package/dist/templates/db/mysql.d.ts +4 -0
  38. package/dist/templates/db/mysql.d.ts.map +1 -0
  39. package/dist/templates/db/mysql.js +972 -0
  40. package/dist/templates/db/mysql.js.map +1 -0
  41. package/dist/templates/db/postgres.d.ts +4 -0
  42. package/dist/templates/db/postgres.d.ts.map +1 -0
  43. package/dist/templates/db/postgres.js +863 -0
  44. package/dist/templates/db/postgres.js.map +1 -0
  45. package/dist/templates/db/sqlite.d.ts +3 -0
  46. package/dist/templates/db/sqlite.d.ts.map +1 -0
  47. package/dist/templates/db/sqlite.js +878 -0
  48. package/dist/templates/db/sqlite.js.map +1 -0
  49. package/dist/templates/index.d.ts +5 -5
  50. package/dist/templates/index.d.ts.map +1 -1
  51. package/dist/templates/index.js +24 -20
  52. package/dist/templates/index.js.map +1 -1
  53. package/dist/templates/server.d.ts +3 -3
  54. package/dist/templates/server.d.ts.map +1 -1
  55. package/dist/templates/server.js +149 -97
  56. package/dist/templates/server.js.map +1 -1
  57. package/dist/templates/shared.d.ts +34 -1
  58. package/dist/templates/shared.d.ts.map +1 -1
  59. package/dist/templates/shared.js +45 -0
  60. package/dist/templates/shared.js.map +1 -1
  61. package/package.json +5 -3
  62. package/src/choices.ts +70 -3
  63. package/src/create-prompts.ts +25 -3
  64. package/src/create.ts +24 -2
  65. package/src/index.ts +58 -5
  66. package/src/make.ts +265 -34
  67. package/src/mcp.ts +367 -0
  68. package/src/templates/agents.ts +103 -0
  69. package/src/templates/base.ts +95 -9
  70. package/src/templates/db/index.ts +34 -0
  71. package/src/templates/db/mysql.ts +976 -0
  72. package/src/templates/db/postgres.ts +867 -0
  73. package/src/templates/{db.ts → db/sqlite.ts} +31 -152
  74. package/src/templates/index.ts +27 -19
  75. package/src/templates/server.ts +161 -97
  76. package/src/templates/shared.ts +90 -1
@@ -0,0 +1,976 @@
1
+ // The starter ships its initial Drizzle migration inside the template so the
2
+ // first run is `db migrate` with no generate step. `dbSchema` and the
3
+ // `starterMigration*` files below pair together — regenerate together.
4
+ //
5
+ // To regenerate: scaffold a scratch app with `dbSchema` as
6
+ // infra/db/schema/index.ts plus `drizzleConfig` as drizzle.config.ts, run
7
+ // `drizzle-kit generate --name starter` with the drizzle-kit version pinned in
8
+ // externalVersions (no MySQL server needed; generate diffs the schema against
9
+ // an empty snapshot), paste drizzle/0000_starter.sql,
10
+ // drizzle/meta/_journal.json, and drizzle/meta/0000_snapshot.json back here
11
+ // verbatim, then append the output of
12
+ // createDrizzleMysqlIdempotencySetupStatements() from
13
+ // @beignet/provider-db-drizzle/mysql to the migration SQL, separated by
14
+ // `--> statement-breakpoint`. The idempotency table is provider-owned and
15
+ // lives outside the app schema, so `db generate` never diffs against it.
16
+ //
17
+ // The Better Auth tables were generated with
18
+ // `bunx @better-auth/cli generate` against a scratch app configured with
19
+ // drizzleAdapter(db, { provider: "mysql" }), then normalized to the starter's
20
+ // conventions (tabs, no relations, no extra indexes, no column defaults).
21
+ // MySQL cannot put UNIQUE constraints on bare text columns, so identity
22
+ // columns are varchar: 36-char ids and foreign keys, 255-char unique email
23
+ // and token.
24
+
25
+ import { databaseLocalUrl, databaseStartCommand } from "../../choices.js";
26
+ import type { TemplateContext } from "../shared.js";
27
+ import type { DbTemplateFiles } from "./index.js";
28
+
29
+ export function mysqlDbFiles(ctx: TemplateContext): DbTemplateFiles {
30
+ return {
31
+ drizzleConfig: `export default {
32
+ schema: "./infra/db/schema/index.ts",
33
+ out: "./drizzle",
34
+ dialect: "mysql",
35
+ dbCredentials: {
36
+ url: process.env.MYSQL_DB_URL ?? "${databaseLocalUrl("mysql", ctx.name)}",
37
+ },
38
+ };
39
+ `,
40
+ dbSchema: `import {
41
+ boolean,
42
+ index,
43
+ mysqlTable,
44
+ text,
45
+ timestamp,
46
+ varchar,
47
+ } from "drizzle-orm/mysql-core";
48
+
49
+ export const user = mysqlTable("user", {
50
+ id: varchar("id", { length: 36 }).primaryKey(),
51
+ name: varchar("name", { length: 255 }).notNull(),
52
+ email: varchar("email", { length: 255 }).notNull().unique(),
53
+ emailVerified: boolean("email_verified").notNull().default(false),
54
+ image: text("image"),
55
+ createdAt: timestamp("created_at", { fsp: 3 }).notNull(),
56
+ updatedAt: timestamp("updated_at", { fsp: 3 }).notNull(),
57
+ });
58
+
59
+ export const session = mysqlTable("session", {
60
+ id: varchar("id", { length: 36 }).primaryKey(),
61
+ expiresAt: timestamp("expires_at", { fsp: 3 }).notNull(),
62
+ token: varchar("token", { length: 255 }).notNull().unique(),
63
+ createdAt: timestamp("created_at", { fsp: 3 }).notNull(),
64
+ updatedAt: timestamp("updated_at", { fsp: 3 }).notNull(),
65
+ ipAddress: text("ip_address"),
66
+ userAgent: text("user_agent"),
67
+ userId: varchar("user_id", { length: 36 })
68
+ .notNull()
69
+ .references(() => user.id, { onDelete: "cascade" }),
70
+ });
71
+
72
+ export const account = mysqlTable("account", {
73
+ id: varchar("id", { length: 36 }).primaryKey(),
74
+ accountId: text("account_id").notNull(),
75
+ providerId: text("provider_id").notNull(),
76
+ userId: varchar("user_id", { length: 36 })
77
+ .notNull()
78
+ .references(() => user.id, { onDelete: "cascade" }),
79
+ accessToken: text("access_token"),
80
+ refreshToken: text("refresh_token"),
81
+ idToken: text("id_token"),
82
+ accessTokenExpiresAt: timestamp("access_token_expires_at", { fsp: 3 }),
83
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { fsp: 3 }),
84
+ scope: text("scope"),
85
+ password: text("password"),
86
+ createdAt: timestamp("created_at", { fsp: 3 }).notNull(),
87
+ updatedAt: timestamp("updated_at", { fsp: 3 }).notNull(),
88
+ });
89
+
90
+ export const verification = mysqlTable("verification", {
91
+ id: varchar("id", { length: 36 }).primaryKey(),
92
+ identifier: varchar("identifier", { length: 255 }).notNull(),
93
+ value: text("value").notNull(),
94
+ expiresAt: timestamp("expires_at", { fsp: 3 }).notNull(),
95
+ createdAt: timestamp("created_at", { fsp: 3 }),
96
+ updatedAt: timestamp("updated_at", { fsp: 3 }),
97
+ });
98
+
99
+ export const todos = mysqlTable(
100
+ "todos",
101
+ {
102
+ id: varchar("id", { length: 36 }).primaryKey(),
103
+ userId: varchar("user_id", { length: 36 })
104
+ .notNull()
105
+ .references(() => user.id, { onDelete: "cascade" }),
106
+ title: text("title").notNull(),
107
+ completed: boolean("completed").notNull().default(false),
108
+ // Kept as an ISO-8601 varchar column on purpose: TodoSchema validates
109
+ // createdAt with z.string().datetime(), repositories pass rows through
110
+ // unmapped, and list ordering compares the strings lexicographically.
111
+ createdAt: varchar("created_at", { length: 32 }).notNull(),
112
+ },
113
+ (table) => ({
114
+ userIdx: index("todos_user_idx").on(table.userId, table.createdAt),
115
+ }),
116
+ );
117
+ `,
118
+ drizzleTodoRepository: `import type { beignetServerOnly } from "@beignet/core/server-only";
119
+ import { offsetPageResult } from "@beignet/core/pagination";
120
+ import type { DrizzleMysqlDatabase } from "@beignet/provider-db-drizzle/mysql";
121
+ import { count, desc, eq } from "drizzle-orm";
122
+ import type {
123
+ NewTodo,
124
+ TodoRepository,
125
+ UpdateTodoData,
126
+ } from "@/features/todos/ports";
127
+ import type { Todo } from "@/features/todos/schemas";
128
+ import * as schema from "@/infra/db/schema";
129
+
130
+ type TodoRow = typeof schema.todos.$inferSelect;
131
+
132
+ function toTodo(row: TodoRow): Todo {
133
+ return {
134
+ id: row.id,
135
+ userId: row.userId,
136
+ title: row.title,
137
+ completed: row.completed,
138
+ createdAt: row.createdAt,
139
+ };
140
+ }
141
+
142
+ export function createDrizzleTodoRepository(
143
+ db: DrizzleMysqlDatabase<typeof schema>,
144
+ ): TodoRepository {
145
+ return {
146
+ async list(userId, page) {
147
+ const rows = await db
148
+ .select()
149
+ .from(schema.todos)
150
+ .where(eq(schema.todos.userId, userId))
151
+ .orderBy(desc(schema.todos.createdAt))
152
+ .limit(page.limit)
153
+ .offset(page.offset);
154
+ const [{ total }] = await db
155
+ .select({ total: count() })
156
+ .from(schema.todos)
157
+ .where(eq(schema.todos.userId, userId));
158
+
159
+ return offsetPageResult(rows.map(toTodo), page, total);
160
+ },
161
+ async findById(id: string) {
162
+ const [row] = await db
163
+ .select()
164
+ .from(schema.todos)
165
+ .where(eq(schema.todos.id, id))
166
+ .limit(1);
167
+
168
+ return row ? toTodo(row) : null;
169
+ },
170
+ async create(input: NewTodo) {
171
+ // MySQL has no INSERT ... RETURNING; the full row is known up front.
172
+ const todo = {
173
+ id: crypto.randomUUID(),
174
+ userId: input.userId,
175
+ title: input.title,
176
+ completed: false,
177
+ createdAt: new Date().toISOString(),
178
+ };
179
+ await db.insert(schema.todos).values(todo);
180
+
181
+ return todo;
182
+ },
183
+ async update(id: string, input: UpdateTodoData) {
184
+ // MySQL has no UPDATE ... RETURNING; re-read the row after writing.
185
+ await db
186
+ .update(schema.todos)
187
+ .set({ completed: input.completed })
188
+ .where(eq(schema.todos.id, id));
189
+ const [row] = await db
190
+ .select()
191
+ .from(schema.todos)
192
+ .where(eq(schema.todos.id, id))
193
+ .limit(1);
194
+
195
+ if (!row) {
196
+ throw new Error(\`Failed to update todo \${id}\`);
197
+ }
198
+
199
+ return toTodo(row);
200
+ },
201
+ async delete(id: string) {
202
+ await db.delete(schema.todos).where(eq(schema.todos.id, id));
203
+ },
204
+ };
205
+ }
206
+ `,
207
+ dbRepositories: `import type { DrizzleMysqlDatabase } from "@beignet/provider-db-drizzle/mysql";
208
+ import { createDrizzleTodoRepository } from "@/infra/todos/drizzle-todo-repository";
209
+ import type { AppTransactionPorts } from "@/ports";
210
+ import * as schema from "./schema";
211
+
212
+ export function createRepositories(
213
+ db: DrizzleMysqlDatabase<typeof schema>,
214
+ ): Omit<AppTransactionPorts, "idempotency"> {
215
+ return {
216
+ todos: createDrizzleTodoRepository(db),
217
+ };
218
+ }
219
+ `,
220
+ databaseReady: `import { drizzle } from "drizzle-orm/mysql2";
221
+ import { migrate } from "drizzle-orm/mysql2/migrator";
222
+ import type { Pool, RowDataPacket } from "mysql2/promise";
223
+
224
+ let ready: Promise<void> | undefined;
225
+
226
+ /**
227
+ * Make sure the database is usable before the first query runs.
228
+ *
229
+ * In development, pending migrations from \`drizzle/\` are applied
230
+ * automatically so a fresh clone works without remembering
231
+ * \`beignet db migrate\`. In production the schema is never mutated;
232
+ * an unmigrated database fails loudly with the command to run.
233
+ *
234
+ * Memoized per process so Next.js page-data workers that reach this in
235
+ * parallel share one preparation pass.
236
+ */
237
+ export function ensureDatabaseReady(pool: Pool): Promise<void> {
238
+ ready ??= prepare(pool);
239
+ return ready;
240
+ }
241
+
242
+ async function prepare(pool: Pool): Promise<void> {
243
+ if (process.env.NODE_ENV === "production") {
244
+ const [tables] = await pool.query<RowDataPacket[]>(
245
+ "SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'user' LIMIT 1",
246
+ );
247
+ if (tables.length === 0) {
248
+ throw new Error(
249
+ "Database has no tables yet. Run \`beignet db migrate\` against this database before starting the app.",
250
+ );
251
+ }
252
+ return;
253
+ }
254
+
255
+ await migrate(drizzle(pool), { migrationsFolder: "drizzle" });
256
+ }
257
+ `,
258
+ dbProvider: `import type { beignetServerOnly } from "@beignet/core/server-only";
259
+ import { createProvider } from "@beignet/core/providers";
260
+ import {
261
+ createDrizzleMysqlIdempotencyPort,
262
+ createDrizzleMysqlUnitOfWork,
263
+ type DbPort,
264
+ } from "@beignet/provider-db-drizzle/mysql";
265
+ import type { AppPorts } from "@/ports";
266
+ import { ensureDatabaseReady } from "./database-ready";
267
+ import { createRepositories } from "./repositories";
268
+ import type * as schema from "./schema";
269
+
270
+ export const starterDatabaseProvider = createProvider<{
271
+ db: DbPort<typeof schema>;
272
+ }>()({
273
+ name: "starter-database",
274
+
275
+ async setup({ ports }) {
276
+ const dbPort = ports.db;
277
+ if (!dbPort) {
278
+ throw new Error(
279
+ "starterDatabaseProvider requires a db port. Register createDrizzleMysqlProvider({ schema }) before it.",
280
+ );
281
+ }
282
+
283
+ const repositories = createRepositories(dbPort.db);
284
+ const idempotency = createDrizzleMysqlIdempotencyPort(dbPort.db);
285
+
286
+ const providedPorts: Pick<AppPorts, "idempotency" | "todos" | "uow"> = {
287
+ ...repositories,
288
+ idempotency,
289
+ uow: createDrizzleMysqlUnitOfWork({
290
+ db: dbPort.db,
291
+ createTransactionPorts: (tx) => ({
292
+ ...createRepositories(tx),
293
+ idempotency: createDrizzleMysqlIdempotencyPort(tx),
294
+ }),
295
+ }),
296
+ };
297
+
298
+ return {
299
+ ports: providedPorts,
300
+ async start() {
301
+ await ensureDatabaseReady(dbPort.pool);
302
+ },
303
+ };
304
+ },
305
+ });
306
+ `,
307
+ dbReset: `import { drizzle } from "drizzle-orm/mysql2";
308
+ import { migrate } from "drizzle-orm/mysql2/migrator";
309
+ import mysql from "mysql2/promise";
310
+ import { env } from "@/lib/env";
311
+
312
+ const host = new URL(env.MYSQL_DB_URL).hostname;
313
+ if (
314
+ host !== "localhost" &&
315
+ host !== "127.0.0.1" &&
316
+ process.env.BEIGNET_ALLOW_DATABASE_RESET !== "true"
317
+ ) {
318
+ throw new Error(
319
+ "Refusing to reset a non-local database. Set BEIGNET_ALLOW_DATABASE_RESET=true if this is intentional.",
320
+ );
321
+ }
322
+
323
+ const tables = [
324
+ "verification",
325
+ "account",
326
+ "session",
327
+ "todos",
328
+ "user",
329
+ "idempotency_records",
330
+ "__drizzle_migrations",
331
+ ] as const;
332
+
333
+ const pool = mysql.createPool({ uri: env.MYSQL_DB_URL });
334
+
335
+ try {
336
+ await pool.query("SET FOREIGN_KEY_CHECKS = 0");
337
+ try {
338
+ for (const table of tables) {
339
+ await pool.query(\`DROP TABLE IF EXISTS \\\`\${table}\\\`\`);
340
+ }
341
+ } finally {
342
+ await pool.query("SET FOREIGN_KEY_CHECKS = 1");
343
+ }
344
+
345
+ await migrate(drizzle(pool), { migrationsFolder: "drizzle" });
346
+ console.log("Database reset.");
347
+ } finally {
348
+ await pool.end();
349
+ }
350
+ `,
351
+ dbTestDatabase: `import { drizzle, type MySql2Database } from "drizzle-orm/mysql2";
352
+ import { migrate } from "drizzle-orm/mysql2/migrator";
353
+ import mysql, { type Pool } from "mysql2/promise";
354
+ import { createRepositories } from "./repositories";
355
+ import * as schema from "./schema";
356
+
357
+ // Set MYSQL_TEST_URL to run database-backed tests against a real MySQL 8.0+
358
+ // server. The starter's shipped tests use in-memory ports and do not need it.
359
+ const MYSQL_TEST_URL = process.env.MYSQL_TEST_URL;
360
+
361
+ /**
362
+ * Whether database-backed tests can run. Pair with \`test.skipIf\`:
363
+ *
364
+ * \`\`\`ts
365
+ * test.skipIf(!isMysqlTestDatabaseAvailable())("...", async () => { ... });
366
+ * \`\`\`
367
+ */
368
+ export function isMysqlTestDatabaseAvailable(): boolean {
369
+ return Boolean(MYSQL_TEST_URL);
370
+ }
371
+
372
+ export type TestDatabase = {
373
+ pool: Pool;
374
+ db: MySql2Database<typeof schema>;
375
+ repositories: ReturnType<typeof createRepositories>;
376
+ databaseName: string;
377
+ close(): Promise<void>;
378
+ };
379
+
380
+ /**
381
+ * Create an isolated test database on the MYSQL_TEST_URL server.
382
+ *
383
+ * Each call creates a uniquely named database, applies the checked-in
384
+ * migrations into it, and drops it again on close, so parallel tests never
385
+ * share state.
386
+ */
387
+ export async function createTestDatabase(): Promise<TestDatabase> {
388
+ if (!MYSQL_TEST_URL) {
389
+ throw new Error(
390
+ "MYSQL_TEST_URL is not set. Start MySQL with: " +
391
+ "${databaseStartCommand("mysql", ctx.name)} " +
392
+ "then run tests with MYSQL_TEST_URL=${databaseLocalUrl("mysql", ctx.name)}.",
393
+ );
394
+ }
395
+
396
+ const databaseName = \`beignet_test_\${crypto
397
+ .randomUUID()
398
+ .replaceAll("-", "")
399
+ .slice(0, 12)}\`;
400
+
401
+ const bootstrap = await mysql.createConnection({ uri: MYSQL_TEST_URL });
402
+ try {
403
+ await bootstrap.query(\`CREATE DATABASE \\\`\${databaseName}\\\`\`);
404
+ } finally {
405
+ await bootstrap.end();
406
+ }
407
+
408
+ const url = new URL(MYSQL_TEST_URL);
409
+ url.pathname = \`/\${databaseName}\`;
410
+ const pool = mysql.createPool({ uri: url.toString() });
411
+ const db = drizzle(pool, { schema, mode: "default" });
412
+ await migrate(db, { migrationsFolder: "drizzle" });
413
+
414
+ return {
415
+ pool,
416
+ db,
417
+ repositories: createRepositories(db),
418
+ databaseName,
419
+ close: async () => {
420
+ await pool.end();
421
+ const cleanup = await mysql.createConnection({ uri: MYSQL_TEST_URL });
422
+ try {
423
+ await cleanup.query(\`DROP DATABASE IF EXISTS \\\`\${databaseName}\\\`\`);
424
+ } finally {
425
+ await cleanup.end();
426
+ }
427
+ },
428
+ };
429
+ }
430
+ `,
431
+ ...mysqlStarterMigration,
432
+ };
433
+ }
434
+
435
+ const mysqlStarterMigration = {
436
+ // Generated by drizzle-kit generate --name starter against dbSchema
437
+ // above, with the provider idempotency setup statements appended.
438
+ starterMigrationSql: `CREATE TABLE \`account\` (
439
+ \`id\` varchar(36) NOT NULL,
440
+ \`account_id\` text NOT NULL,
441
+ \`provider_id\` text NOT NULL,
442
+ \`user_id\` varchar(36) NOT NULL,
443
+ \`access_token\` text,
444
+ \`refresh_token\` text,
445
+ \`id_token\` text,
446
+ \`access_token_expires_at\` timestamp(3),
447
+ \`refresh_token_expires_at\` timestamp(3),
448
+ \`scope\` text,
449
+ \`password\` text,
450
+ \`created_at\` timestamp(3) NOT NULL,
451
+ \`updated_at\` timestamp(3) NOT NULL,
452
+ CONSTRAINT \`account_id\` PRIMARY KEY(\`id\`)
453
+ );
454
+ --> statement-breakpoint
455
+ CREATE TABLE \`session\` (
456
+ \`id\` varchar(36) NOT NULL,
457
+ \`expires_at\` timestamp(3) NOT NULL,
458
+ \`token\` varchar(255) NOT NULL,
459
+ \`created_at\` timestamp(3) NOT NULL,
460
+ \`updated_at\` timestamp(3) NOT NULL,
461
+ \`ip_address\` text,
462
+ \`user_agent\` text,
463
+ \`user_id\` varchar(36) NOT NULL,
464
+ CONSTRAINT \`session_id\` PRIMARY KEY(\`id\`),
465
+ CONSTRAINT \`session_token_unique\` UNIQUE(\`token\`)
466
+ );
467
+ --> statement-breakpoint
468
+ CREATE TABLE \`todos\` (
469
+ \`id\` varchar(36) NOT NULL,
470
+ \`user_id\` varchar(36) NOT NULL,
471
+ \`title\` text NOT NULL,
472
+ \`completed\` boolean NOT NULL DEFAULT false,
473
+ \`created_at\` varchar(32) NOT NULL,
474
+ CONSTRAINT \`todos_id\` PRIMARY KEY(\`id\`)
475
+ );
476
+ --> statement-breakpoint
477
+ CREATE TABLE \`user\` (
478
+ \`id\` varchar(36) NOT NULL,
479
+ \`name\` varchar(255) NOT NULL,
480
+ \`email\` varchar(255) NOT NULL,
481
+ \`email_verified\` boolean NOT NULL DEFAULT false,
482
+ \`image\` text,
483
+ \`created_at\` timestamp(3) NOT NULL,
484
+ \`updated_at\` timestamp(3) NOT NULL,
485
+ CONSTRAINT \`user_id\` PRIMARY KEY(\`id\`),
486
+ CONSTRAINT \`user_email_unique\` UNIQUE(\`email\`)
487
+ );
488
+ --> statement-breakpoint
489
+ CREATE TABLE \`verification\` (
490
+ \`id\` varchar(36) NOT NULL,
491
+ \`identifier\` varchar(255) NOT NULL,
492
+ \`value\` text NOT NULL,
493
+ \`expires_at\` timestamp(3) NOT NULL,
494
+ \`created_at\` timestamp(3),
495
+ \`updated_at\` timestamp(3),
496
+ CONSTRAINT \`verification_id\` PRIMARY KEY(\`id\`)
497
+ );
498
+ --> statement-breakpoint
499
+ ALTER TABLE \`account\` ADD CONSTRAINT \`account_user_id_user_id_fk\` FOREIGN KEY (\`user_id\`) REFERENCES \`user\`(\`id\`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
500
+ ALTER TABLE \`session\` ADD CONSTRAINT \`session_user_id_user_id_fk\` FOREIGN KEY (\`user_id\`) REFERENCES \`user\`(\`id\`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
501
+ ALTER TABLE \`todos\` ADD CONSTRAINT \`todos_user_id_user_id_fk\` FOREIGN KEY (\`user_id\`) REFERENCES \`user\`(\`id\`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
502
+ CREATE INDEX \`todos_user_idx\` ON \`todos\` (\`user_id\`,\`created_at\`);
503
+ --> statement-breakpoint
504
+ CREATE TABLE IF NOT EXISTS \`idempotency_records\` (
505
+ storage_key varchar(512) NOT NULL PRIMARY KEY,
506
+ namespace varchar(255) NOT NULL,
507
+ idempotency_key varchar(255) NOT NULL,
508
+ scope_key varchar(255) NOT NULL,
509
+ fingerprint varchar(255) NOT NULL,
510
+ status varchar(16) NOT NULL,
511
+ result_json longtext,
512
+ reserved_at varchar(32) NOT NULL,
513
+ completed_at varchar(32),
514
+ expires_at varchar(32),
515
+ updated_at varchar(32) NOT NULL,
516
+ INDEX \`idempotency_records_lookup_idx\` (namespace, scope_key, idempotency_key),
517
+ INDEX \`idempotency_records_expires_idx\` (expires_at)
518
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;
519
+ `,
520
+ starterMigrationJournal: `{
521
+ "version": "7",
522
+ "dialect": "mysql",
523
+ "entries": [
524
+ {
525
+ "idx": 0,
526
+ "version": "5",
527
+ "when": 1781285557496,
528
+ "tag": "0000_starter",
529
+ "breakpoints": true
530
+ }
531
+ ]
532
+ }`,
533
+ starterMigrationSnapshot: `{
534
+ "version": "5",
535
+ "dialect": "mysql",
536
+ "id": "597ed4cf-be53-41d9-9c60-d53032e7ca3f",
537
+ "prevId": "00000000-0000-0000-0000-000000000000",
538
+ "tables": {
539
+ "account": {
540
+ "name": "account",
541
+ "columns": {
542
+ "id": {
543
+ "name": "id",
544
+ "type": "varchar(36)",
545
+ "primaryKey": false,
546
+ "notNull": true,
547
+ "autoincrement": false
548
+ },
549
+ "account_id": {
550
+ "name": "account_id",
551
+ "type": "text",
552
+ "primaryKey": false,
553
+ "notNull": true,
554
+ "autoincrement": false
555
+ },
556
+ "provider_id": {
557
+ "name": "provider_id",
558
+ "type": "text",
559
+ "primaryKey": false,
560
+ "notNull": true,
561
+ "autoincrement": false
562
+ },
563
+ "user_id": {
564
+ "name": "user_id",
565
+ "type": "varchar(36)",
566
+ "primaryKey": false,
567
+ "notNull": true,
568
+ "autoincrement": false
569
+ },
570
+ "access_token": {
571
+ "name": "access_token",
572
+ "type": "text",
573
+ "primaryKey": false,
574
+ "notNull": false,
575
+ "autoincrement": false
576
+ },
577
+ "refresh_token": {
578
+ "name": "refresh_token",
579
+ "type": "text",
580
+ "primaryKey": false,
581
+ "notNull": false,
582
+ "autoincrement": false
583
+ },
584
+ "id_token": {
585
+ "name": "id_token",
586
+ "type": "text",
587
+ "primaryKey": false,
588
+ "notNull": false,
589
+ "autoincrement": false
590
+ },
591
+ "access_token_expires_at": {
592
+ "name": "access_token_expires_at",
593
+ "type": "timestamp(3)",
594
+ "primaryKey": false,
595
+ "notNull": false,
596
+ "autoincrement": false
597
+ },
598
+ "refresh_token_expires_at": {
599
+ "name": "refresh_token_expires_at",
600
+ "type": "timestamp(3)",
601
+ "primaryKey": false,
602
+ "notNull": false,
603
+ "autoincrement": false
604
+ },
605
+ "scope": {
606
+ "name": "scope",
607
+ "type": "text",
608
+ "primaryKey": false,
609
+ "notNull": false,
610
+ "autoincrement": false
611
+ },
612
+ "password": {
613
+ "name": "password",
614
+ "type": "text",
615
+ "primaryKey": false,
616
+ "notNull": false,
617
+ "autoincrement": false
618
+ },
619
+ "created_at": {
620
+ "name": "created_at",
621
+ "type": "timestamp(3)",
622
+ "primaryKey": false,
623
+ "notNull": true,
624
+ "autoincrement": false
625
+ },
626
+ "updated_at": {
627
+ "name": "updated_at",
628
+ "type": "timestamp(3)",
629
+ "primaryKey": false,
630
+ "notNull": true,
631
+ "autoincrement": false
632
+ }
633
+ },
634
+ "indexes": {},
635
+ "foreignKeys": {
636
+ "account_user_id_user_id_fk": {
637
+ "name": "account_user_id_user_id_fk",
638
+ "tableFrom": "account",
639
+ "tableTo": "user",
640
+ "columnsFrom": [
641
+ "user_id"
642
+ ],
643
+ "columnsTo": [
644
+ "id"
645
+ ],
646
+ "onDelete": "cascade",
647
+ "onUpdate": "no action"
648
+ }
649
+ },
650
+ "compositePrimaryKeys": {
651
+ "account_id": {
652
+ "name": "account_id",
653
+ "columns": [
654
+ "id"
655
+ ]
656
+ }
657
+ },
658
+ "uniqueConstraints": {},
659
+ "checkConstraint": {}
660
+ },
661
+ "session": {
662
+ "name": "session",
663
+ "columns": {
664
+ "id": {
665
+ "name": "id",
666
+ "type": "varchar(36)",
667
+ "primaryKey": false,
668
+ "notNull": true,
669
+ "autoincrement": false
670
+ },
671
+ "expires_at": {
672
+ "name": "expires_at",
673
+ "type": "timestamp(3)",
674
+ "primaryKey": false,
675
+ "notNull": true,
676
+ "autoincrement": false
677
+ },
678
+ "token": {
679
+ "name": "token",
680
+ "type": "varchar(255)",
681
+ "primaryKey": false,
682
+ "notNull": true,
683
+ "autoincrement": false
684
+ },
685
+ "created_at": {
686
+ "name": "created_at",
687
+ "type": "timestamp(3)",
688
+ "primaryKey": false,
689
+ "notNull": true,
690
+ "autoincrement": false
691
+ },
692
+ "updated_at": {
693
+ "name": "updated_at",
694
+ "type": "timestamp(3)",
695
+ "primaryKey": false,
696
+ "notNull": true,
697
+ "autoincrement": false
698
+ },
699
+ "ip_address": {
700
+ "name": "ip_address",
701
+ "type": "text",
702
+ "primaryKey": false,
703
+ "notNull": false,
704
+ "autoincrement": false
705
+ },
706
+ "user_agent": {
707
+ "name": "user_agent",
708
+ "type": "text",
709
+ "primaryKey": false,
710
+ "notNull": false,
711
+ "autoincrement": false
712
+ },
713
+ "user_id": {
714
+ "name": "user_id",
715
+ "type": "varchar(36)",
716
+ "primaryKey": false,
717
+ "notNull": true,
718
+ "autoincrement": false
719
+ }
720
+ },
721
+ "indexes": {},
722
+ "foreignKeys": {
723
+ "session_user_id_user_id_fk": {
724
+ "name": "session_user_id_user_id_fk",
725
+ "tableFrom": "session",
726
+ "tableTo": "user",
727
+ "columnsFrom": [
728
+ "user_id"
729
+ ],
730
+ "columnsTo": [
731
+ "id"
732
+ ],
733
+ "onDelete": "cascade",
734
+ "onUpdate": "no action"
735
+ }
736
+ },
737
+ "compositePrimaryKeys": {
738
+ "session_id": {
739
+ "name": "session_id",
740
+ "columns": [
741
+ "id"
742
+ ]
743
+ }
744
+ },
745
+ "uniqueConstraints": {
746
+ "session_token_unique": {
747
+ "name": "session_token_unique",
748
+ "columns": [
749
+ "token"
750
+ ]
751
+ }
752
+ },
753
+ "checkConstraint": {}
754
+ },
755
+ "todos": {
756
+ "name": "todos",
757
+ "columns": {
758
+ "id": {
759
+ "name": "id",
760
+ "type": "varchar(36)",
761
+ "primaryKey": false,
762
+ "notNull": true,
763
+ "autoincrement": false
764
+ },
765
+ "user_id": {
766
+ "name": "user_id",
767
+ "type": "varchar(36)",
768
+ "primaryKey": false,
769
+ "notNull": true,
770
+ "autoincrement": false
771
+ },
772
+ "title": {
773
+ "name": "title",
774
+ "type": "text",
775
+ "primaryKey": false,
776
+ "notNull": true,
777
+ "autoincrement": false
778
+ },
779
+ "completed": {
780
+ "name": "completed",
781
+ "type": "boolean",
782
+ "primaryKey": false,
783
+ "notNull": true,
784
+ "autoincrement": false,
785
+ "default": false
786
+ },
787
+ "created_at": {
788
+ "name": "created_at",
789
+ "type": "varchar(32)",
790
+ "primaryKey": false,
791
+ "notNull": true,
792
+ "autoincrement": false
793
+ }
794
+ },
795
+ "indexes": {
796
+ "todos_user_idx": {
797
+ "name": "todos_user_idx",
798
+ "columns": [
799
+ "user_id",
800
+ "created_at"
801
+ ],
802
+ "isUnique": false
803
+ }
804
+ },
805
+ "foreignKeys": {
806
+ "todos_user_id_user_id_fk": {
807
+ "name": "todos_user_id_user_id_fk",
808
+ "tableFrom": "todos",
809
+ "tableTo": "user",
810
+ "columnsFrom": [
811
+ "user_id"
812
+ ],
813
+ "columnsTo": [
814
+ "id"
815
+ ],
816
+ "onDelete": "cascade",
817
+ "onUpdate": "no action"
818
+ }
819
+ },
820
+ "compositePrimaryKeys": {
821
+ "todos_id": {
822
+ "name": "todos_id",
823
+ "columns": [
824
+ "id"
825
+ ]
826
+ }
827
+ },
828
+ "uniqueConstraints": {},
829
+ "checkConstraint": {}
830
+ },
831
+ "user": {
832
+ "name": "user",
833
+ "columns": {
834
+ "id": {
835
+ "name": "id",
836
+ "type": "varchar(36)",
837
+ "primaryKey": false,
838
+ "notNull": true,
839
+ "autoincrement": false
840
+ },
841
+ "name": {
842
+ "name": "name",
843
+ "type": "varchar(255)",
844
+ "primaryKey": false,
845
+ "notNull": true,
846
+ "autoincrement": false
847
+ },
848
+ "email": {
849
+ "name": "email",
850
+ "type": "varchar(255)",
851
+ "primaryKey": false,
852
+ "notNull": true,
853
+ "autoincrement": false
854
+ },
855
+ "email_verified": {
856
+ "name": "email_verified",
857
+ "type": "boolean",
858
+ "primaryKey": false,
859
+ "notNull": true,
860
+ "autoincrement": false,
861
+ "default": false
862
+ },
863
+ "image": {
864
+ "name": "image",
865
+ "type": "text",
866
+ "primaryKey": false,
867
+ "notNull": false,
868
+ "autoincrement": false
869
+ },
870
+ "created_at": {
871
+ "name": "created_at",
872
+ "type": "timestamp(3)",
873
+ "primaryKey": false,
874
+ "notNull": true,
875
+ "autoincrement": false
876
+ },
877
+ "updated_at": {
878
+ "name": "updated_at",
879
+ "type": "timestamp(3)",
880
+ "primaryKey": false,
881
+ "notNull": true,
882
+ "autoincrement": false
883
+ }
884
+ },
885
+ "indexes": {},
886
+ "foreignKeys": {},
887
+ "compositePrimaryKeys": {
888
+ "user_id": {
889
+ "name": "user_id",
890
+ "columns": [
891
+ "id"
892
+ ]
893
+ }
894
+ },
895
+ "uniqueConstraints": {
896
+ "user_email_unique": {
897
+ "name": "user_email_unique",
898
+ "columns": [
899
+ "email"
900
+ ]
901
+ }
902
+ },
903
+ "checkConstraint": {}
904
+ },
905
+ "verification": {
906
+ "name": "verification",
907
+ "columns": {
908
+ "id": {
909
+ "name": "id",
910
+ "type": "varchar(36)",
911
+ "primaryKey": false,
912
+ "notNull": true,
913
+ "autoincrement": false
914
+ },
915
+ "identifier": {
916
+ "name": "identifier",
917
+ "type": "varchar(255)",
918
+ "primaryKey": false,
919
+ "notNull": true,
920
+ "autoincrement": false
921
+ },
922
+ "value": {
923
+ "name": "value",
924
+ "type": "text",
925
+ "primaryKey": false,
926
+ "notNull": true,
927
+ "autoincrement": false
928
+ },
929
+ "expires_at": {
930
+ "name": "expires_at",
931
+ "type": "timestamp(3)",
932
+ "primaryKey": false,
933
+ "notNull": true,
934
+ "autoincrement": false
935
+ },
936
+ "created_at": {
937
+ "name": "created_at",
938
+ "type": "timestamp(3)",
939
+ "primaryKey": false,
940
+ "notNull": false,
941
+ "autoincrement": false
942
+ },
943
+ "updated_at": {
944
+ "name": "updated_at",
945
+ "type": "timestamp(3)",
946
+ "primaryKey": false,
947
+ "notNull": false,
948
+ "autoincrement": false
949
+ }
950
+ },
951
+ "indexes": {},
952
+ "foreignKeys": {},
953
+ "compositePrimaryKeys": {
954
+ "verification_id": {
955
+ "name": "verification_id",
956
+ "columns": [
957
+ "id"
958
+ ]
959
+ }
960
+ },
961
+ "uniqueConstraints": {},
962
+ "checkConstraint": {}
963
+ }
964
+ },
965
+ "views": {},
966
+ "_meta": {
967
+ "schemas": {},
968
+ "tables": {},
969
+ "columns": {}
970
+ },
971
+ "internal": {
972
+ "tables": {},
973
+ "indexes": {}
974
+ }
975
+ }`,
976
+ };