@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.
- package/CHANGELOG.md +15 -0
- package/README.md +70 -21
- package/dist/choices.d.ts +32 -3
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +54 -3
- package/dist/choices.js.map +1 -1
- package/dist/create-prompts.d.ts +5 -4
- package/dist/create-prompts.d.ts.map +1 -1
- package/dist/create-prompts.js +22 -4
- package/dist/create-prompts.js.map +1 -1
- package/dist/create.d.ts +7 -1
- package/dist/create.d.ts.map +1 -1
- package/dist/create.js +13 -3
- package/dist/create.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -6
- package/dist/index.js.map +1 -1
- package/dist/make.d.ts +21 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +210 -37
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts +19 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +250 -0
- package/dist/mcp.js.map +1 -0
- package/dist/templates/agents.d.ts +14 -0
- package/dist/templates/agents.d.ts.map +1 -0
- package/dist/templates/agents.js +98 -0
- package/dist/templates/agents.js.map +1 -0
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +79 -9
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/db/index.d.ts +21 -0
- package/dist/templates/db/index.d.ts.map +1 -0
- package/dist/templates/db/index.js +14 -0
- package/dist/templates/db/index.js.map +1 -0
- package/dist/templates/db/mysql.d.ts +4 -0
- package/dist/templates/db/mysql.d.ts.map +1 -0
- package/dist/templates/db/mysql.js +972 -0
- package/dist/templates/db/mysql.js.map +1 -0
- package/dist/templates/db/postgres.d.ts +4 -0
- package/dist/templates/db/postgres.d.ts.map +1 -0
- package/dist/templates/db/postgres.js +863 -0
- package/dist/templates/db/postgres.js.map +1 -0
- package/dist/templates/db/sqlite.d.ts +3 -0
- package/dist/templates/db/sqlite.d.ts.map +1 -0
- package/dist/templates/db/sqlite.js +878 -0
- package/dist/templates/db/sqlite.js.map +1 -0
- package/dist/templates/index.d.ts +5 -5
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +24 -20
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts +3 -3
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +149 -97
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +34 -1
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +45 -0
- package/dist/templates/shared.js.map +1 -1
- package/package.json +5 -3
- package/src/choices.ts +70 -3
- package/src/create-prompts.ts +25 -3
- package/src/create.ts +24 -2
- package/src/index.ts +58 -5
- package/src/make.ts +265 -34
- package/src/mcp.ts +367 -0
- package/src/templates/agents.ts +103 -0
- package/src/templates/base.ts +95 -9
- package/src/templates/db/index.ts +34 -0
- package/src/templates/db/mysql.ts +976 -0
- package/src/templates/db/postgres.ts +867 -0
- package/src/templates/{db.ts → db/sqlite.ts} +31 -152
- package/src/templates/index.ts +27 -19
- package/src/templates/server.ts +161 -97
- 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,
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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
|
|
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":
|
|
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": "
|
|
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
|
|
879
|
+
export function sqliteDbFiles(): DbTemplateFiles {
|
|
880
|
+
return files;
|
|
881
|
+
}
|
package/src/templates/index.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
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
|
|
|
10
|
+
import { agentsMd, mcpJson } from "./agents.js";
|
|
9
11
|
import {
|
|
10
12
|
baseFiles,
|
|
11
13
|
envExample,
|
|
@@ -13,10 +15,13 @@ import {
|
|
|
13
15
|
packageJson,
|
|
14
16
|
readme,
|
|
15
17
|
} from "./base.js";
|
|
16
|
-
import { dbFiles } from "./db.js";
|
|
18
|
+
import { dbFiles } from "./db/index.js";
|
|
17
19
|
import {
|
|
20
|
+
betterAuth,
|
|
21
|
+
env,
|
|
18
22
|
infrastructurePorts,
|
|
19
23
|
ports,
|
|
24
|
+
server,
|
|
20
25
|
serverFiles,
|
|
21
26
|
serverProviders,
|
|
22
27
|
} from "./server.js";
|
|
@@ -29,14 +34,17 @@ import { todosFiles } from "./todos.js";
|
|
|
29
34
|
* Render all template files for a new Beignet app.
|
|
30
35
|
*
|
|
31
36
|
* There is one starter: a full-stack app with a typed API, Better Auth,
|
|
32
|
-
* Drizzle
|
|
33
|
-
* `ctx.api` swaps the UI shell for
|
|
34
|
-
* typed client.
|
|
37
|
+
* Drizzle persistence (SQLite, Postgres, or MySQL via `ctx.database`), pino
|
|
38
|
+
* logging, devtools, and a shadcn UI shell. `ctx.api` swaps the UI shell for
|
|
39
|
+
* a minimal API landing page and a reduced typed client.
|
|
35
40
|
*/
|
|
36
41
|
export function getTemplateFiles(ctx: TemplateContext): TemplateFile[] {
|
|
42
|
+
const db = dbFiles(ctx);
|
|
37
43
|
const templateFiles: TemplateFile[] = [
|
|
38
44
|
{ path: "package.json", content: packageJson(ctx) },
|
|
39
45
|
{ path: "README.md", content: readme(ctx) },
|
|
46
|
+
{ path: "AGENTS.md", content: agentsMd(ctx) },
|
|
47
|
+
{ path: ".mcp.json", content: mcpJson(ctx) },
|
|
40
48
|
{ path: ".gitignore", content: baseFiles.gitignore },
|
|
41
49
|
{ path: ".env.example", content: envExample(ctx) },
|
|
42
50
|
{ path: "next-env.d.ts", content: baseFiles.nextEnv },
|
|
@@ -98,31 +106,31 @@ export function getTemplateFiles(ctx: TemplateContext): TemplateFile[] {
|
|
|
98
106
|
{ path: "ports/index.ts", content: ports(ctx) },
|
|
99
107
|
{ path: "ports/auth.ts", content: serverFiles.authPort },
|
|
100
108
|
{ path: "infra/app-ports.ts", content: infrastructurePorts(ctx) },
|
|
101
|
-
{ path: "infra/db/database-ready.ts", content:
|
|
102
|
-
{ path: "infra/db/provider.ts", content:
|
|
103
|
-
{ path: "infra/db/repositories.ts", content:
|
|
104
|
-
{ path: "infra/db/reset.ts", content:
|
|
105
|
-
{ path: "infra/db/test-database.ts", content:
|
|
106
|
-
{ path: "infra/db/schema/index.ts", content:
|
|
109
|
+
{ path: "infra/db/database-ready.ts", content: db.databaseReady },
|
|
110
|
+
{ path: "infra/db/provider.ts", content: db.dbProvider },
|
|
111
|
+
{ path: "infra/db/repositories.ts", content: db.dbRepositories },
|
|
112
|
+
{ path: "infra/db/reset.ts", content: db.dbReset },
|
|
113
|
+
{ path: "infra/db/test-database.ts", content: db.dbTestDatabase },
|
|
114
|
+
{ path: "infra/db/schema/index.ts", content: db.dbSchema },
|
|
107
115
|
{
|
|
108
116
|
path: "infra/todos/drizzle-todo-repository.ts",
|
|
109
|
-
content:
|
|
117
|
+
content: db.drizzleTodoRepository,
|
|
110
118
|
},
|
|
111
|
-
{ path: "drizzle.config.ts", content:
|
|
112
|
-
{ path: "drizzle/0000_starter.sql", content:
|
|
119
|
+
{ path: "drizzle.config.ts", content: db.drizzleConfig },
|
|
120
|
+
{ path: "drizzle/0000_starter.sql", content: db.starterMigrationSql },
|
|
113
121
|
{
|
|
114
122
|
path: "drizzle/meta/_journal.json",
|
|
115
|
-
content:
|
|
123
|
+
content: db.starterMigrationJournal,
|
|
116
124
|
},
|
|
117
125
|
{
|
|
118
126
|
path: "drizzle/meta/0000_snapshot.json",
|
|
119
|
-
content:
|
|
127
|
+
content: db.starterMigrationSnapshot,
|
|
120
128
|
},
|
|
121
|
-
{ path: "lib/env.ts", content:
|
|
129
|
+
{ path: "lib/env.ts", content: env(ctx) },
|
|
122
130
|
{ path: "lib/auth.ts", content: serverFiles.authHelpers },
|
|
123
|
-
{ path: "lib/better-auth.ts", content:
|
|
131
|
+
{ path: "lib/better-auth.ts", content: betterAuth(ctx) },
|
|
124
132
|
{ path: "lib/use-case.ts", content: serverFiles.useCaseBuilder },
|
|
125
|
-
{ path: "server/index.ts", content:
|
|
133
|
+
{ path: "server/index.ts", content: server(ctx) },
|
|
126
134
|
{ path: "server/context.ts", content: serverFiles.serverContext },
|
|
127
135
|
{ path: "server/routes.ts", content: serverFiles.serverRoutes },
|
|
128
136
|
{ path: "server/providers.ts", content: serverProviders(ctx) },
|
package/src/templates/server.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|