@beignet/cli 0.0.9 → 0.0.10

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 (66) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +35 -19
  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 +25 -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/templates/base.d.ts.map +1 -1
  23. package/dist/templates/base.js +79 -9
  24. package/dist/templates/base.js.map +1 -1
  25. package/dist/templates/db/index.d.ts +21 -0
  26. package/dist/templates/db/index.d.ts.map +1 -0
  27. package/dist/templates/db/index.js +14 -0
  28. package/dist/templates/db/index.js.map +1 -0
  29. package/dist/templates/db/mysql.d.ts +4 -0
  30. package/dist/templates/db/mysql.d.ts.map +1 -0
  31. package/dist/templates/db/mysql.js +972 -0
  32. package/dist/templates/db/mysql.js.map +1 -0
  33. package/dist/templates/db/postgres.d.ts +4 -0
  34. package/dist/templates/db/postgres.d.ts.map +1 -0
  35. package/dist/templates/db/postgres.js +863 -0
  36. package/dist/templates/db/postgres.js.map +1 -0
  37. package/dist/templates/db/sqlite.d.ts +3 -0
  38. package/dist/templates/db/sqlite.d.ts.map +1 -0
  39. package/dist/templates/db/sqlite.js +878 -0
  40. package/dist/templates/db/sqlite.js.map +1 -0
  41. package/dist/templates/index.d.ts +5 -5
  42. package/dist/templates/index.d.ts.map +1 -1
  43. package/dist/templates/index.js +21 -20
  44. package/dist/templates/index.js.map +1 -1
  45. package/dist/templates/server.d.ts +3 -3
  46. package/dist/templates/server.d.ts.map +1 -1
  47. package/dist/templates/server.js +149 -97
  48. package/dist/templates/server.js.map +1 -1
  49. package/dist/templates/shared.d.ts +34 -1
  50. package/dist/templates/shared.d.ts.map +1 -1
  51. package/dist/templates/shared.js +45 -0
  52. package/dist/templates/shared.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/choices.ts +70 -3
  55. package/src/create-prompts.ts +25 -3
  56. package/src/create.ts +24 -2
  57. package/src/index.ts +29 -5
  58. package/src/make.ts +265 -34
  59. package/src/templates/base.ts +95 -9
  60. package/src/templates/db/index.ts +34 -0
  61. package/src/templates/db/mysql.ts +976 -0
  62. package/src/templates/db/postgres.ts +867 -0
  63. package/src/templates/{db.ts → db/sqlite.ts} +31 -152
  64. package/src/templates/index.ts +24 -19
  65. package/src/templates/server.ts +161 -97
  66. package/src/templates/shared.ts +90 -1
@@ -5,9 +5,14 @@
5
5
  // To regenerate: scaffold a scratch app with `dbSchema` as
6
6
  // infra/db/schema/index.ts plus `drizzleConfig` as drizzle.config.ts, run
7
7
  // `drizzle-kit generate --name starter` with the drizzle-kit version pinned in
8
- // externalVersions, then paste drizzle/0000_starter.sql,
9
- // drizzle/meta/_journal.json, and drizzle/meta/0000_snapshot.json back here
10
- // verbatim.
8
+ // externalVersions, paste drizzle/0000_starter.sql, drizzle/meta/_journal.json,
9
+ // and drizzle/meta/0000_snapshot.json back here verbatim, then append the
10
+ // output of createDrizzleSqliteIdempotencySetupStatements() from
11
+ // @beignet/provider-db-drizzle/sqlite to the migration SQL, separated by
12
+ // `--> statement-breakpoint`. The idempotency table is provider-owned and
13
+ // lives outside the app schema, so `db generate` never diffs against it.
14
+
15
+ import type { DbTemplateFiles } from "./index.js";
11
16
 
12
17
  const files = {
13
18
  drizzleConfig: `export default {
@@ -95,31 +100,6 @@ export const todos = sqliteTable(
95
100
  userIdx: index("todos_user_idx").on(table.userId, table.createdAt),
96
101
  }),
97
102
  );
98
-
99
- export const idempotencyRecords = sqliteTable(
100
- "idempotency_records",
101
- {
102
- storageKey: text("storage_key").primaryKey(),
103
- namespace: text("namespace").notNull(),
104
- idempotencyKey: text("idempotency_key").notNull(),
105
- scopeKey: text("scope_key").notNull(),
106
- fingerprint: text("fingerprint").notNull(),
107
- status: text("status", { enum: ["in-progress", "completed"] }).notNull(),
108
- resultJson: text("result_json"),
109
- reservedAt: text("reserved_at").notNull(),
110
- completedAt: text("completed_at"),
111
- expiresAt: text("expires_at"),
112
- updatedAt: text("updated_at").notNull(),
113
- },
114
- (table) => ({
115
- lookupIdx: index("idempotency_records_lookup_idx").on(
116
- table.namespace,
117
- table.scopeKey,
118
- table.idempotencyKey,
119
- ),
120
- expiresIdx: index("idempotency_records_expires_idx").on(table.expiresAt),
121
- }),
122
- );
123
103
  `,
124
104
  drizzleTodoRepository: `import type { beignetServerOnly } from "@beignet/core/server-only";
125
105
  import { offsetPageResult } from "@beignet/core/pagination";
@@ -393,7 +373,8 @@ export async function createTestDatabase(): Promise<TestDatabase> {
393
373
  };
394
374
  }
395
375
  `,
396
- // Generated by drizzle-kit generate --name starter against dbSchema above.
376
+ // Generated by drizzle-kit generate --name starter against dbSchema
377
+ // above, with the provider idempotency setup statements appended.
397
378
  starterMigrationSql: `CREATE TABLE \`account\` (
398
379
  \`id\` text PRIMARY KEY NOT NULL,
399
380
  \`account_id\` text NOT NULL,
@@ -411,22 +392,6 @@ export async function createTestDatabase(): Promise<TestDatabase> {
411
392
  FOREIGN KEY (\`user_id\`) REFERENCES \`user\`(\`id\`) ON UPDATE no action ON DELETE cascade
412
393
  );
413
394
  --> statement-breakpoint
414
- CREATE TABLE \`idempotency_records\` (
415
- \`storage_key\` text PRIMARY KEY NOT NULL,
416
- \`namespace\` text NOT NULL,
417
- \`idempotency_key\` text NOT NULL,
418
- \`scope_key\` text NOT NULL,
419
- \`fingerprint\` text NOT NULL,
420
- \`status\` text NOT NULL,
421
- \`result_json\` text,
422
- \`reserved_at\` text NOT NULL,
423
- \`completed_at\` text,
424
- \`expires_at\` text,
425
- \`updated_at\` text NOT NULL
426
- );
427
- --> statement-breakpoint
428
- CREATE INDEX \`idempotency_records_lookup_idx\` ON \`idempotency_records\` (\`namespace\`,\`scope_key\`,\`idempotency_key\`);--> statement-breakpoint
429
- CREATE INDEX \`idempotency_records_expires_idx\` ON \`idempotency_records\` (\`expires_at\`);--> statement-breakpoint
430
395
  CREATE TABLE \`session\` (
431
396
  \`id\` text PRIMARY KEY NOT NULL,
432
397
  \`expires_at\` integer NOT NULL,
@@ -469,6 +434,22 @@ CREATE TABLE \`verification\` (
469
434
  \`created_at\` integer,
470
435
  \`updated_at\` integer
471
436
  );
437
+ --> statement-breakpoint
438
+ CREATE TABLE IF NOT EXISTS "idempotency_records" (
439
+ storage_key text PRIMARY KEY NOT NULL,
440
+ namespace text NOT NULL,
441
+ idempotency_key text NOT NULL,
442
+ scope_key text NOT NULL,
443
+ fingerprint text NOT NULL,
444
+ status text NOT NULL,
445
+ result_json text,
446
+ reserved_at text NOT NULL,
447
+ completed_at text,
448
+ expires_at text,
449
+ updated_at text NOT NULL
450
+ );--> statement-breakpoint
451
+ CREATE INDEX IF NOT EXISTS "idempotency_records_lookup_idx" ON "idempotency_records" (namespace, scope_key, idempotency_key);--> statement-breakpoint
452
+ CREATE INDEX IF NOT EXISTS "idempotency_records_expires_idx" ON "idempotency_records" (expires_at);
472
453
  `,
473
454
  starterMigrationJournal: `{
474
455
  "version": "7",
@@ -477,7 +458,7 @@ CREATE TABLE \`verification\` (
477
458
  {
478
459
  "idx": 0,
479
460
  "version": "6",
480
- "when": 1781185557868,
461
+ "when": 1781285554238,
481
462
  "tag": "0000_starter",
482
463
  "breakpoints": true
483
464
  }
@@ -486,7 +467,7 @@ CREATE TABLE \`verification\` (
486
467
  starterMigrationSnapshot: `{
487
468
  "version": "6",
488
469
  "dialect": "sqlite",
489
- "id": "5131bbf1-cff0-42de-a5f4-298187fdafd1",
470
+ "id": "1fa6ba12-5a4d-4815-bda1-225734f18921",
490
471
  "prevId": "00000000-0000-0000-0000-000000000000",
491
472
  "tables": {
492
473
  "account": {
@@ -604,110 +585,6 @@ CREATE TABLE \`verification\` (
604
585
  "uniqueConstraints": {},
605
586
  "checkConstraints": {}
606
587
  },
607
- "idempotency_records": {
608
- "name": "idempotency_records",
609
- "columns": {
610
- "storage_key": {
611
- "name": "storage_key",
612
- "type": "text",
613
- "primaryKey": true,
614
- "notNull": true,
615
- "autoincrement": false
616
- },
617
- "namespace": {
618
- "name": "namespace",
619
- "type": "text",
620
- "primaryKey": false,
621
- "notNull": true,
622
- "autoincrement": false
623
- },
624
- "idempotency_key": {
625
- "name": "idempotency_key",
626
- "type": "text",
627
- "primaryKey": false,
628
- "notNull": true,
629
- "autoincrement": false
630
- },
631
- "scope_key": {
632
- "name": "scope_key",
633
- "type": "text",
634
- "primaryKey": false,
635
- "notNull": true,
636
- "autoincrement": false
637
- },
638
- "fingerprint": {
639
- "name": "fingerprint",
640
- "type": "text",
641
- "primaryKey": false,
642
- "notNull": true,
643
- "autoincrement": false
644
- },
645
- "status": {
646
- "name": "status",
647
- "type": "text",
648
- "primaryKey": false,
649
- "notNull": true,
650
- "autoincrement": false
651
- },
652
- "result_json": {
653
- "name": "result_json",
654
- "type": "text",
655
- "primaryKey": false,
656
- "notNull": false,
657
- "autoincrement": false
658
- },
659
- "reserved_at": {
660
- "name": "reserved_at",
661
- "type": "text",
662
- "primaryKey": false,
663
- "notNull": true,
664
- "autoincrement": false
665
- },
666
- "completed_at": {
667
- "name": "completed_at",
668
- "type": "text",
669
- "primaryKey": false,
670
- "notNull": false,
671
- "autoincrement": false
672
- },
673
- "expires_at": {
674
- "name": "expires_at",
675
- "type": "text",
676
- "primaryKey": false,
677
- "notNull": false,
678
- "autoincrement": false
679
- },
680
- "updated_at": {
681
- "name": "updated_at",
682
- "type": "text",
683
- "primaryKey": false,
684
- "notNull": true,
685
- "autoincrement": false
686
- }
687
- },
688
- "indexes": {
689
- "idempotency_records_lookup_idx": {
690
- "name": "idempotency_records_lookup_idx",
691
- "columns": [
692
- "namespace",
693
- "scope_key",
694
- "idempotency_key"
695
- ],
696
- "isUnique": false
697
- },
698
- "idempotency_records_expires_idx": {
699
- "name": "idempotency_records_expires_idx",
700
- "columns": [
701
- "expires_at"
702
- ],
703
- "isUnique": false
704
- }
705
- },
706
- "foreignKeys": {},
707
- "compositePrimaryKeys": {},
708
- "uniqueConstraints": {},
709
- "checkConstraints": {}
710
- },
711
588
  "session": {
712
589
  "name": "session",
713
590
  "columns": {
@@ -999,4 +876,6 @@ CREATE TABLE \`verification\` (
999
876
  }`,
1000
877
  };
1001
878
 
1002
- export { files as dbFiles };
879
+ export function sqliteDbFiles(): DbTemplateFiles {
880
+ return files;
881
+ }
@@ -1,9 +1,10 @@
1
1
  export type {
2
+ DatabaseName,
2
3
  IntegrationName,
3
4
  PackageManager,
4
5
  TemplateName,
5
6
  } from "../choices.js";
6
- export { integrationChoices } from "../choices.js";
7
+ export { databaseChoices, integrationChoices } from "../choices.js";
7
8
  export type { TemplateContext, TemplateFile } from "./shared.js";
8
9
 
9
10
  import {
@@ -13,10 +14,13 @@ import {
13
14
  packageJson,
14
15
  readme,
15
16
  } from "./base.js";
16
- import { dbFiles } from "./db.js";
17
+ import { dbFiles } from "./db/index.js";
17
18
  import {
19
+ betterAuth,
20
+ env,
18
21
  infrastructurePorts,
19
22
  ports,
23
+ server,
20
24
  serverFiles,
21
25
  serverProviders,
22
26
  } from "./server.js";
@@ -29,11 +33,12 @@ import { todosFiles } from "./todos.js";
29
33
  * Render all template files for a new Beignet app.
30
34
  *
31
35
  * There is one starter: a full-stack app with a typed API, Better Auth,
32
- * Drizzle/libSQL persistence, pino logging, devtools, and a shadcn UI shell.
33
- * `ctx.api` swaps the UI shell for a minimal API landing page and a reduced
34
- * typed client.
36
+ * Drizzle persistence (SQLite, Postgres, or MySQL via `ctx.database`), pino
37
+ * logging, devtools, and a shadcn UI shell. `ctx.api` swaps the UI shell for
38
+ * a minimal API landing page and a reduced typed client.
35
39
  */
36
40
  export function getTemplateFiles(ctx: TemplateContext): TemplateFile[] {
41
+ const db = dbFiles(ctx);
37
42
  const templateFiles: TemplateFile[] = [
38
43
  { path: "package.json", content: packageJson(ctx) },
39
44
  { path: "README.md", content: readme(ctx) },
@@ -98,31 +103,31 @@ export function getTemplateFiles(ctx: TemplateContext): TemplateFile[] {
98
103
  { path: "ports/index.ts", content: ports(ctx) },
99
104
  { path: "ports/auth.ts", content: serverFiles.authPort },
100
105
  { path: "infra/app-ports.ts", content: infrastructurePorts(ctx) },
101
- { path: "infra/db/database-ready.ts", content: dbFiles.databaseReady },
102
- { path: "infra/db/provider.ts", content: dbFiles.dbProvider },
103
- { path: "infra/db/repositories.ts", content: dbFiles.dbRepositories },
104
- { path: "infra/db/reset.ts", content: dbFiles.dbReset },
105
- { path: "infra/db/test-database.ts", content: dbFiles.dbTestDatabase },
106
- { path: "infra/db/schema/index.ts", content: dbFiles.dbSchema },
106
+ { path: "infra/db/database-ready.ts", content: db.databaseReady },
107
+ { path: "infra/db/provider.ts", content: db.dbProvider },
108
+ { path: "infra/db/repositories.ts", content: db.dbRepositories },
109
+ { path: "infra/db/reset.ts", content: db.dbReset },
110
+ { path: "infra/db/test-database.ts", content: db.dbTestDatabase },
111
+ { path: "infra/db/schema/index.ts", content: db.dbSchema },
107
112
  {
108
113
  path: "infra/todos/drizzle-todo-repository.ts",
109
- content: dbFiles.drizzleTodoRepository,
114
+ content: db.drizzleTodoRepository,
110
115
  },
111
- { path: "drizzle.config.ts", content: dbFiles.drizzleConfig },
112
- { path: "drizzle/0000_starter.sql", content: dbFiles.starterMigrationSql },
116
+ { path: "drizzle.config.ts", content: db.drizzleConfig },
117
+ { path: "drizzle/0000_starter.sql", content: db.starterMigrationSql },
113
118
  {
114
119
  path: "drizzle/meta/_journal.json",
115
- content: dbFiles.starterMigrationJournal,
120
+ content: db.starterMigrationJournal,
116
121
  },
117
122
  {
118
123
  path: "drizzle/meta/0000_snapshot.json",
119
- content: dbFiles.starterMigrationSnapshot,
124
+ content: db.starterMigrationSnapshot,
120
125
  },
121
- { path: "lib/env.ts", content: serverFiles.env },
126
+ { path: "lib/env.ts", content: env(ctx) },
122
127
  { path: "lib/auth.ts", content: serverFiles.authHelpers },
123
- { path: "lib/better-auth.ts", content: serverFiles.betterAuth },
128
+ { path: "lib/better-auth.ts", content: betterAuth(ctx) },
124
129
  { path: "lib/use-case.ts", content: serverFiles.useCaseBuilder },
125
- { path: "server/index.ts", content: serverFiles.server },
130
+ { path: "server/index.ts", content: server(ctx) },
126
131
  { path: "server/context.ts", content: serverFiles.serverContext },
127
132
  { path: "server/routes.ts", content: serverFiles.serverRoutes },
128
133
  { path: "server/providers.ts", content: serverProviders(ctx) },
@@ -1,11 +1,17 @@
1
- import { hasIntegration, type TemplateContext } from "./shared.js";
1
+ import { databaseLocalUrl } from "../choices.js";
2
+ import {
3
+ databaseDescriptor,
4
+ hasIntegration,
5
+ type TemplateContext,
6
+ } from "./shared.js";
2
7
 
3
8
  export function serverProviders(ctx: TemplateContext): string {
9
+ const db = databaseDescriptor(ctx);
4
10
  const imports = [
5
11
  'import type { beignetServerOnly } from "@beignet/core/server-only";',
6
12
  'import { createDevtoolsProvider } from "@beignet/devtools";',
7
13
  'import { createAuthBetterAuthProvider } from "@beignet/provider-auth-better-auth";',
8
- 'import { createDrizzleSqliteProvider } from "@beignet/provider-db-drizzle/sqlite";',
14
+ `import { ${db.providerFactory} } from "@beignet/provider-db-drizzle/${db.subpath}";`,
9
15
  hasIntegration(ctx, "inngest")
10
16
  ? 'import { inngestProvider } from "@beignet/provider-inngest";'
11
17
  : undefined,
@@ -26,7 +32,7 @@ export function serverProviders(ctx: TemplateContext): string {
26
32
  "\tcreateDevtoolsProvider(),",
27
33
  "\tcreateAuthBetterAuthProvider<AuthUser, AuthSessionMetadata>(auth),",
28
34
  "\tloggerPinoProvider,",
29
- "\tdrizzleSqliteProvider,",
35
+ `\t${db.providerInstance},`,
30
36
  "\tstarterDatabaseProvider,",
31
37
  hasIntegration(ctx, "inngest") ? "\tinngestProvider," : undefined,
32
38
  hasIntegration(ctx, "resend") ? "\tmailResendProvider," : undefined,
@@ -37,7 +43,7 @@ export function serverProviders(ctx: TemplateContext): string {
37
43
 
38
44
  return `${imports.join("\n")}
39
45
 
40
- const drizzleSqliteProvider = createDrizzleSqliteProvider({ schema });
46
+ const ${db.providerInstance} = ${db.providerFactory}({ schema });
41
47
 
42
48
  export const providers = [
43
49
  ${entries.join("\n")}
@@ -132,8 +138,16 @@ ${deferred.join("\n")}
132
138
  `;
133
139
  }
134
140
 
135
- const files = {
136
- env: `import { createEnv } from "@beignet/core/config";
141
+ export function env(ctx: TemplateContext): string {
142
+ const databaseEnvLines =
143
+ ctx.database === "sqlite"
144
+ ? `\t\tSQLITE_DB_URL: z.string().default("file:local.db"),
145
+ \t\tSQLITE_DB_AUTH_TOKEN: z.string().optional(),`
146
+ : `\t\t${databaseDescriptor(ctx).urlEnvVar}: z
147
+ \t\t\t.string()
148
+ \t\t\t.default("${databaseLocalUrl(ctx.database, ctx.name)}"),`;
149
+
150
+ return `import { createEnv } from "@beignet/core/config";
137
151
  import { z } from "zod";
138
152
 
139
153
  const BooleanEnv = z.enum(["true", "false"]).transform((value) => value === "true");
@@ -151,8 +165,7 @@ export const env = createEnv({
151
165
  .default("local-dev-better-auth-secret-change-me"),
152
166
  BETTER_AUTH_URL: z.string().url().default("http://localhost:3000"),
153
167
  BETTER_AUTH_TRUSTED_ORIGINS: z.string().optional(),
154
- SQLITE_DB_URL: z.string().default("file:local.db"),
155
- SQLITE_DB_AUTH_TOKEN: z.string().optional(),
168
+ ${databaseEnvLines}
156
169
  LOG_LEVEL: z
157
170
  .enum(["trace", "debug", "info", "warn", "error", "fatal"])
158
171
  .default("info"),
@@ -166,7 +179,146 @@ export const isProduction = env.NODE_ENV === "production";
166
179
 
167
180
  export const isDevtoolsEnabled =
168
181
  env.DEVTOOLS_ENABLED ?? process.env.NODE_ENV !== "production";
169
- `,
182
+ `;
183
+ }
184
+
185
+ export function betterAuth(ctx: TemplateContext): string {
186
+ const connectionByDatabase = {
187
+ sqlite: `import { createClient } from "@libsql/client";
188
+ import { betterAuth } from "better-auth";
189
+ import { drizzleAdapter } from "better-auth/adapters/drizzle";
190
+ import { drizzle } from "drizzle-orm/libsql";
191
+ import { ensureDatabaseReady } from "@/infra/db/database-ready";
192
+ import * as schema from "@/infra/db/schema";
193
+ import { env } from "@/lib/env";
194
+
195
+ const client = createClient({
196
+ url: env.SQLITE_DB_URL,
197
+ authToken: env.SQLITE_DB_AUTH_TOKEN,
198
+ });
199
+
200
+ // Auth routes can be the first thing to touch the database, before any
201
+ // Beignet provider boots, so readiness is enforced here too.
202
+ await ensureDatabaseReady(client);
203
+
204
+ const db = drizzle(client, { schema });`,
205
+ postgres: `import { betterAuth } from "better-auth";
206
+ import { drizzleAdapter } from "better-auth/adapters/drizzle";
207
+ import { drizzle } from "drizzle-orm/node-postgres";
208
+ import pg from "pg";
209
+ import { ensureDatabaseReady } from "@/infra/db/database-ready";
210
+ import * as schema from "@/infra/db/schema";
211
+ import { env } from "@/lib/env";
212
+
213
+ const pool = new pg.Pool({ connectionString: env.POSTGRES_DB_URL });
214
+
215
+ // Auth routes can be the first thing to touch the database, before any
216
+ // Beignet provider boots, so readiness is enforced here too.
217
+ await ensureDatabaseReady(pool);
218
+
219
+ const db = drizzle(pool, { schema });`,
220
+ mysql: `import { betterAuth } from "better-auth";
221
+ import { drizzleAdapter } from "better-auth/adapters/drizzle";
222
+ import { drizzle } from "drizzle-orm/mysql2";
223
+ import mysql from "mysql2/promise";
224
+ import { ensureDatabaseReady } from "@/infra/db/database-ready";
225
+ import * as schema from "@/infra/db/schema";
226
+ import { env } from "@/lib/env";
227
+
228
+ const pool = mysql.createPool({ uri: env.MYSQL_DB_URL });
229
+
230
+ // Auth routes can be the first thing to touch the database, before any
231
+ // Beignet provider boots, so readiness is enforced here too.
232
+ await ensureDatabaseReady(pool);
233
+
234
+ const db = drizzle(pool, { schema, mode: "default" });`,
235
+ } as const;
236
+
237
+ return `${connectionByDatabase[ctx.database]}
238
+
239
+ const trustedOrigins = [
240
+ env.APP_URL,
241
+ env.BETTER_AUTH_URL,
242
+ ...(env.BETTER_AUTH_TRUSTED_ORIGINS?.split(",")
243
+ .map((origin) => origin.trim())
244
+ .filter(Boolean) ?? []),
245
+ ];
246
+
247
+ export const auth = betterAuth({
248
+ baseURL: env.BETTER_AUTH_URL,
249
+ secret: env.BETTER_AUTH_SECRET,
250
+ trustedOrigins: [...new Set(trustedOrigins)],
251
+ database: drizzleAdapter(db, {
252
+ provider: "${databaseDescriptor(ctx).betterAuthProvider}",
253
+ schema,
254
+ }),
255
+ emailAndPassword: {
256
+ enabled: true,
257
+ },
258
+ user: {
259
+ changeEmail: {
260
+ enabled: true,
261
+ },
262
+ deleteUser: {
263
+ enabled: true,
264
+ },
265
+ },
266
+ });
267
+ `;
268
+ }
269
+
270
+ export function server(ctx: TemplateContext): string {
271
+ const db = databaseDescriptor(ctx);
272
+ const providerConfigEntries =
273
+ ctx.database === "sqlite"
274
+ ? `\t\t"drizzle-sqlite": {
275
+ \t\t\tDB_URL: env.SQLITE_DB_URL,
276
+ \t\t\tDB_AUTH_TOKEN: env.SQLITE_DB_AUTH_TOKEN,
277
+ \t\t},`
278
+ : `\t\t"${db.providerName}": {
279
+ \t\t\tDB_URL: env.${db.urlEnvVar},
280
+ \t\t},`;
281
+
282
+ return `import type { beignetServerOnly } from "@beignet/core/server-only";
283
+ import { createNextServer } from "@beignet/next";
284
+ import { createIdempotencyHooks } from "@beignet/core/server";
285
+ import type { AppContext } from "@/app-context";
286
+ import { appPorts } from "@/infra/app-ports";
287
+ import { env } from "@/lib/env";
288
+ import { appContext } from "./context";
289
+ import { providers } from "./providers";
290
+ import { routes } from "./routes";
291
+
292
+ export const server = await createNextServer({
293
+ ports: appPorts,
294
+ providers,
295
+ providerConfig: {
296
+ ${providerConfigEntries}
297
+ },
298
+ hooks: [createIdempotencyHooks<AppContext>()],
299
+ context: appContext,
300
+ routes,
301
+ mapUnhandledError: ({ err, ctx }) => {
302
+ ctx?.ports.logger.error("Unhandled API error", {
303
+ error: err,
304
+ requestId: ctx?.requestId,
305
+ environment: env.NODE_ENV,
306
+ });
307
+
308
+ return {
309
+ status: 500,
310
+ body: {
311
+ code: "INTERNAL_SERVER_ERROR",
312
+ message: "Internal server error",
313
+ requestId: ctx?.requestId,
314
+ },
315
+ };
316
+ },
317
+ });
318
+ `;
319
+ }
320
+
321
+ const files = {
170
322
  appContext: `import type { ActivityActor, ActivityTenant } from "@beignet/core/ports";
171
323
  import type { InferProviderPorts } from "@beignet/core/providers";
172
324
  import type { TraceContext } from "@beignet/core/tracing";
@@ -329,46 +481,6 @@ export const appContext = defineServerContext<AppContext, AppRuntimePorts>()({
329
481
  tenant: createTenant(input?.tenantId ?? "tenant_default"),
330
482
  }),
331
483
  });
332
- `,
333
- server: `import type { beignetServerOnly } from "@beignet/core/server-only";
334
- import { createNextServer } from "@beignet/next";
335
- import { createIdempotencyHooks } from "@beignet/core/server";
336
- import type { AppContext } from "@/app-context";
337
- import { appPorts } from "@/infra/app-ports";
338
- import { env } from "@/lib/env";
339
- import { appContext } from "./context";
340
- import { providers } from "./providers";
341
- import { routes } from "./routes";
342
-
343
- export const server = await createNextServer({
344
- ports: appPorts,
345
- providers,
346
- providerConfig: {
347
- "drizzle-sqlite": {
348
- DB_URL: env.SQLITE_DB_URL,
349
- DB_AUTH_TOKEN: env.SQLITE_DB_AUTH_TOKEN,
350
- },
351
- },
352
- hooks: [createIdempotencyHooks<AppContext>()],
353
- context: appContext,
354
- routes,
355
- mapUnhandledError: ({ err, ctx }) => {
356
- ctx?.ports.logger.error("Unhandled API error", {
357
- error: err,
358
- requestId: ctx?.requestId,
359
- environment: env.NODE_ENV,
360
- });
361
-
362
- return {
363
- status: 500,
364
- body: {
365
- code: "INTERNAL_SERVER_ERROR",
366
- message: "Internal server error",
367
- requestId: ctx?.requestId,
368
- },
369
- };
370
- },
371
- });
372
484
  `,
373
485
  apiCatchAllRoute: `import { server } from "@/server";
374
486
 
@@ -418,54 +530,6 @@ export const GET = createOpenAPIHandler(server.contracts, {
418
530
  import { auth } from "@/lib/better-auth";
419
531
 
420
532
  export const { GET, POST } = toNextJsHandler(auth);
421
- `,
422
- betterAuth: `import { createClient } from "@libsql/client";
423
- import { betterAuth } from "better-auth";
424
- import { drizzleAdapter } from "better-auth/adapters/drizzle";
425
- import { drizzle } from "drizzle-orm/libsql";
426
- import { ensureDatabaseReady } from "@/infra/db/database-ready";
427
- import * as schema from "@/infra/db/schema";
428
- import { env } from "@/lib/env";
429
-
430
- const client = createClient({
431
- url: env.SQLITE_DB_URL,
432
- authToken: env.SQLITE_DB_AUTH_TOKEN,
433
- });
434
-
435
- // Auth routes can be the first thing to touch the database, before any
436
- // Beignet provider boots, so readiness is enforced here too.
437
- await ensureDatabaseReady(client);
438
-
439
- const db = drizzle(client, { schema });
440
-
441
- const trustedOrigins = [
442
- env.APP_URL,
443
- env.BETTER_AUTH_URL,
444
- ...(env.BETTER_AUTH_TRUSTED_ORIGINS?.split(",")
445
- .map((origin) => origin.trim())
446
- .filter(Boolean) ?? []),
447
- ];
448
-
449
- export const auth = betterAuth({
450
- baseURL: env.BETTER_AUTH_URL,
451
- secret: env.BETTER_AUTH_SECRET,
452
- trustedOrigins: [...new Set(trustedOrigins)],
453
- database: drizzleAdapter(db, {
454
- provider: "sqlite",
455
- schema,
456
- }),
457
- emailAndPassword: {
458
- enabled: true,
459
- },
460
- user: {
461
- changeEmail: {
462
- enabled: true,
463
- },
464
- deleteUser: {
465
- enabled: true,
466
- },
467
- },
468
- });
469
533
  `,
470
534
  // API-only scaffold frontend: a minimal layout and landing page that point
471
535
  // at the contract-backed API surface, plus a typed client without React