create-absolutejs 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +9 -2
  3. package/changelog.json +24 -0
  4. package/dist/commands/migrateDatabase.d.ts +6 -0
  5. package/dist/commands/migrateDatabase.js +53 -0
  6. package/dist/data.d.ts +1 -1
  7. package/dist/data.js +1 -0
  8. package/dist/generators/configurations/generateDrizzleConfig.d.ts +5 -3
  9. package/dist/generators/configurations/generateDrizzleConfig.js +40 -14
  10. package/dist/generators/configurations/generateEnv.js +6 -0
  11. package/dist/generators/configurations/generateEslintConfig.js +1 -1
  12. package/dist/generators/configurations/generatePackageJson.d.ts +2 -1
  13. package/dist/generators/configurations/generatePackageJson.js +40 -4
  14. package/dist/generators/db/drizzleTargets.d.ts +5 -0
  15. package/dist/generators/db/drizzleTargets.js +27 -0
  16. package/dist/generators/db/generateDatabaseTypes.js +10 -6
  17. package/dist/generators/db/generateDockerContainer.d.ts +5 -0
  18. package/dist/generators/db/generateDockerContainer.js +22 -4
  19. package/dist/generators/db/generateDrizzleSchema.js +65 -81
  20. package/dist/generators/db/handlerTemplates.d.ts +4 -4
  21. package/dist/generators/db/handlerTemplates.js +7 -10
  22. package/dist/generators/db/scaffoldDatabase.js +31 -8
  23. package/dist/generators/db/scaffoldDocker.d.ts +2 -1
  24. package/dist/generators/db/scaffoldDocker.js +7 -3
  25. package/dist/generators/prisma/generatePrismaCode.d.ts +6 -0
  26. package/dist/generators/prisma/generatePrismaCode.js +148 -0
  27. package/dist/generators/prisma/generatePrismaConfig.d.ts +8 -0
  28. package/dist/generators/prisma/generatePrismaConfig.js +68 -0
  29. package/dist/generators/prisma/generatePrismaSchema.d.ts +9 -0
  30. package/dist/generators/prisma/generatePrismaSchema.js +80 -0
  31. package/dist/generators/prisma/prismaPackageJson.d.ts +8 -0
  32. package/dist/generators/prisma/prismaPackageJson.js +46 -0
  33. package/dist/generators/prisma/prismaTargets.d.ts +22 -0
  34. package/dist/generators/prisma/prismaTargets.js +91 -0
  35. package/dist/generators/prisma/scaffoldPrismaDatabase.d.ts +16 -0
  36. package/dist/generators/prisma/scaffoldPrismaDatabase.js +136 -0
  37. package/dist/generators/project/generateDBBlock.js +16 -2
  38. package/dist/generators/project/generateImportsBlock.js +17 -7
  39. package/dist/generators/project/generateServer.js +2 -1
  40. package/dist/index.js +2 -1
  41. package/dist/messages.d.ts +2 -1
  42. package/dist/messages.js +2 -1
  43. package/dist/questions/authOption.js +4 -1
  44. package/dist/questions/databaseEngine.d.ts +1 -1
  45. package/dist/questions/orm.js +5 -2
  46. package/dist/scaffold.d.ts +1 -0
  47. package/dist/scaffold.js +23 -5
  48. package/dist/templates/configurations/.prettierignore +2 -1
  49. package/dist/templates/configurations/eslint.config.example.mjs +81 -41
  50. package/dist/templates/db/migrations/cockroach-count-history/20260926210314_init/migration.sql +5 -0
  51. package/dist/templates/db/migrations/cockroach-count-history/20260926210314_init/snapshot.json +69 -0
  52. package/dist/templates/db/migrations/cockroach-users/20260926210315_init/migration.sql +5 -0
  53. package/dist/templates/db/migrations/cockroach-users/20260926210315_init/snapshot.json +62 -0
  54. package/dist/templates/db/migrations/mssql-count-history/20260926210316_init/migration.sql +6 -0
  55. package/dist/templates/db/migrations/mssql-count-history/20260926210316_init/snapshot.json +64 -0
  56. package/dist/templates/db/migrations/mssql-users/20260926210317_init/migration.sql +6 -0
  57. package/dist/templates/db/migrations/mssql-users/20260926210317_init/snapshot.json +70 -0
  58. package/dist/templates/db/migrations/mysql-count-history/20260926210318_init/migration.sql +5 -0
  59. package/dist/templates/db/migrations/mysql-count-history/20260926210318_init/snapshot.json +61 -0
  60. package/dist/templates/db/migrations/mysql-users/20260926210319_init/migration.sql +5 -0
  61. package/dist/templates/db/migrations/mysql-users/20260926210319_init/snapshot.json +61 -0
  62. package/dist/templates/db/migrations/postgresql-count-history/20260926210320_init/migration.sql +5 -0
  63. package/dist/templates/db/migrations/postgresql-count-history/20260926210320_init/snapshot.json +71 -0
  64. package/dist/templates/db/migrations/postgresql-users/20260926210320_init/migration.sql +5 -0
  65. package/dist/templates/db/migrations/postgresql-users/20260926210320_init/snapshot.json +62 -0
  66. package/dist/templates/db/migrations/prisma-cockroachdb-count-history/20260926000000_init/migration.sql +12 -0
  67. package/dist/templates/db/migrations/prisma-cockroachdb-count-history/migration_lock.toml +3 -0
  68. package/dist/templates/db/migrations/prisma-cockroachdb-users/20260926000000_init/migration.sql +12 -0
  69. package/dist/templates/db/migrations/prisma-cockroachdb-users/migration_lock.toml +3 -0
  70. package/dist/templates/db/migrations/prisma-mysql-count-history/20260926000000_init/migration.sql +9 -0
  71. package/dist/templates/db/migrations/prisma-mysql-count-history/migration_lock.toml +3 -0
  72. package/dist/templates/db/migrations/prisma-mysql-users/20260926000000_init/migration.sql +9 -0
  73. package/dist/templates/db/migrations/prisma-mysql-users/migration_lock.toml +3 -0
  74. package/dist/templates/db/migrations/prisma-postgresql-count-history/20260926000000_init/migration.sql +12 -0
  75. package/dist/templates/db/migrations/prisma-postgresql-count-history/migration_lock.toml +3 -0
  76. package/dist/templates/db/migrations/prisma-postgresql-users/20260926000000_init/migration.sql +12 -0
  77. package/dist/templates/db/migrations/prisma-postgresql-users/migration_lock.toml +3 -0
  78. package/dist/templates/db/migrations/prisma-sqlite-count-history/20260926000000_init/migration.sql +7 -0
  79. package/dist/templates/db/migrations/prisma-sqlite-count-history/migration_lock.toml +3 -0
  80. package/dist/templates/db/migrations/prisma-sqlite-users/20260926000000_init/migration.sql +7 -0
  81. package/dist/templates/db/migrations/prisma-sqlite-users/migration_lock.toml +3 -0
  82. package/dist/templates/db/migrations/prisma-sqlserver-count-history/20260926000000_init/migration.sql +28 -0
  83. package/dist/templates/db/migrations/prisma-sqlserver-count-history/migration_lock.toml +3 -0
  84. package/dist/templates/db/migrations/prisma-sqlserver-users/20260926000000_init/migration.sql +28 -0
  85. package/dist/templates/db/migrations/prisma-sqlserver-users/migration_lock.toml +3 -0
  86. package/dist/templates/db/migrations/singlestore-count-history/20260926210321_init/migration.sql +5 -0
  87. package/dist/templates/db/migrations/singlestore-count-history/20260926210321_init/snapshot.json +61 -0
  88. package/dist/templates/db/migrations/singlestore-users/20260926210322_init/migration.sql +5 -0
  89. package/dist/templates/db/migrations/singlestore-users/20260926210322_init/snapshot.json +61 -0
  90. package/dist/templates/db/migrations/sqlite-count-history/20260926210323_init/migration.sql +5 -0
  91. package/dist/templates/db/migrations/sqlite-count-history/20260926210323_init/snapshot.json +50 -0
  92. package/dist/templates/db/migrations/sqlite-users/20260926210324_init/migration.sql +5 -0
  93. package/dist/templates/db/migrations/sqlite-users/20260926210324_init/snapshot.json +50 -0
  94. package/dist/templates/db/migrations/turso-count-history/20260926210324_init/migration.sql +5 -0
  95. package/dist/templates/db/migrations/turso-count-history/20260926210324_init/snapshot.json +50 -0
  96. package/dist/templates/db/migrations/turso-users/20260926210325_init/migration.sql +5 -0
  97. package/dist/templates/db/migrations/turso-users/20260926210325_init/snapshot.json +50 -0
  98. package/dist/templates/git/gitignore +5 -3
  99. package/dist/typeGuards.d.ts +2 -2
  100. package/dist/typeGuards.js +2 -2
  101. package/dist/utils/parseCommandLineOptions.js +16 -31
  102. package/dist/utils/validateDatabaseSelection.d.ts +11 -0
  103. package/dist/utils/validateDatabaseSelection.js +34 -0
  104. package/dist/versions.d.ts +9 -0
  105. package/dist/versions.js +13 -0
  106. package/package.json +4 -1
  107. package/dist/templates/configurations/drizzle.config.ts +0 -13
@@ -1,121 +1,105 @@
1
+ const mysqlFamily = {
2
+ int: 'int',
3
+ json: 'json()',
4
+ string: 'varchar({ length: 255 })',
5
+ time: 'timestamp()',
6
+ timestampDefault: '.defaultNow()',
7
+ uid: "int('uid').primaryKey().autoincrement()"
8
+ };
1
9
  const DIALECTS = {
2
- gel: {
3
- builders: ['text', 'gelTable', 'timestamp', 'integer', 'json'],
4
- json: 'json()',
5
- pkg: 'gel-core',
6
- string: 'text()',
7
- table: 'gelTable',
8
- time: 'timestamp()'
9
- },
10
- mariadb: {
11
- builders: ['json', 'mysqlTable', 'timestamp', 'varchar', 'int'],
12
- json: 'json()',
13
- pkg: 'mysql-core',
10
+ cockroachdb: {
11
+ int: 'int4',
12
+ json: 'jsonb()',
13
+ pkg: 'cockroach-core',
14
14
  string: 'varchar({ length: 255 })',
15
- table: 'mysqlTable',
16
- time: 'timestamp()'
15
+ table: 'cockroachTable',
16
+ time: 'timestamp()',
17
+ timestampDefault: '.defaultNow()',
18
+ uid: "int4('uid').primaryKey().generatedAlwaysAsIdentity()"
17
19
  },
20
+ mariadb: { ...mysqlFamily, pkg: 'mysql-core', table: 'mysqlTable' },
18
21
  mssql: {
19
- builders: ['datetime2', 'int', 'mssqlTable', 'nvarchar', 'json'],
22
+ int: 'int',
20
23
  json: "nvarchar({ length: 'max', mode: 'json' })",
21
24
  pkg: 'mssql-core',
22
25
  string: 'nvarchar({ length: 255 })',
23
26
  table: 'mssqlTable',
24
- time: 'datetime2()'
25
- },
26
- mysql: {
27
- builders: ['json', 'mysqlTable', 'timestamp', 'varchar', 'int'],
28
- json: 'json()',
29
- pkg: 'mysql-core',
30
- string: 'varchar({ length: 255 })',
31
- table: 'mysqlTable',
32
- time: 'timestamp()'
27
+ time: 'datetime2()',
28
+ timestampDefault: '.default(sql`sysdatetime()`)',
29
+ uid: "int('uid').identity().primaryKey()"
33
30
  },
31
+ mysql: { ...mysqlFamily, pkg: 'mysql-core', table: 'mysqlTable' },
34
32
  postgresql: {
35
- builders: ['jsonb', 'pgTable', 'timestamp', 'varchar', 'integer'],
33
+ int: 'integer',
36
34
  json: 'jsonb()',
37
35
  pkg: 'pg-core',
38
36
  string: 'varchar({ length: 255 })',
39
37
  table: 'pgTable',
40
- time: 'timestamp()'
38
+ time: 'timestamp()',
39
+ timestampDefault: '.defaultNow()',
40
+ uid: "integer('uid').primaryKey().generatedAlwaysAsIdentity()"
41
41
  },
42
42
  singlestore: {
43
- builders: ['json', 'singlestoreTable', 'timestamp', 'varchar', 'int'],
44
- json: 'json()',
43
+ ...mysqlFamily,
44
+ /* SingleStore rejects MySQL's parenthesised expression default
45
+ (DEFAULT ('{}')), which is what drizzle-kit emits for .default({}). */
46
+ jsonDefault: ".default(sql`'{}'`)",
45
47
  pkg: 'singlestore-core',
46
- string: 'varchar({ length: 255 })',
47
- table: 'singlestoreTable',
48
- time: 'timestamp()'
48
+ table: 'singlestoreTable'
49
49
  },
50
50
  sqlite: {
51
- builders: ['text', 'sqliteTable', 'integer'],
51
+ int: 'integer',
52
52
  json: "text('', { mode: 'json' })",
53
53
  pkg: 'sqlite-core',
54
54
  string: 'text()',
55
55
  table: 'sqliteTable',
56
- time: "integer({ mode: 'timestamp_ms' })"
56
+ time: "integer({ mode: 'timestamp_ms' })",
57
+ /* Milliseconds since the Unix epoch: julianday() counts days from
58
+ 2440587.5 (the epoch's Julian day). drizzle-kit only accepts a
59
+ parameter-free SQL default, so the constants are inline. */
60
+ timestampDefault: ".default(sql`((julianday('now') - 2440587.5) * 86400000)`)",
61
+ uid: "integer('uid').primaryKey({ autoIncrement: true })"
57
62
  }
58
63
  };
59
- const builder = (expr) => expr.split('(')[0];
64
+ const builder = (expr) => expr.split('(')[0] ?? expr;
60
65
  export const generateDrizzleSchema = ({ databaseEngine, authOption }) => {
61
66
  const cfg = DIALECTS[databaseEngine];
62
- const intBuilder = databaseEngine === 'mariadb' ||
63
- databaseEngine === 'mssql' ||
64
- databaseEngine === 'mysql' ||
65
- databaseEngine === 'singlestore'
66
- ? 'int'
67
- : 'integer';
68
- const timeBuilder = builder(cfg.time);
69
- const jsonBuilder = builder(cfg.json);
70
- const stringBuilder = builder(cfg.string);
71
- const importBuilders = authOption === 'abs'
72
- ? [cfg.table, stringBuilder, timeBuilder, jsonBuilder]
73
- : [cfg.table, intBuilder, timeBuilder];
67
+ const usesAuth = authOption === 'abs';
68
+ const importBuilders = usesAuth
69
+ ? [cfg.table, builder(cfg.string), builder(cfg.time), builder(cfg.json)]
70
+ : [cfg.table, cfg.int, builder(cfg.time)];
74
71
  const uniqueBuilders = Array.from(new Set(importBuilders));
75
- const builderImport = `${authOption === 'abs' ? "import type { UserIdentity } from '../src/types/userIdentity';\n" : ''}import { ${uniqueBuilders.join(', ')} } from 'drizzle-orm/${cfg.pkg}';`;
76
- const sqliteImports = databaseEngine === 'sqlite' || databaseEngine === 'mssql'
77
- ? `import { sql } from 'drizzle-orm';\n`
78
- : '';
79
- let uidColumn;
80
- if (databaseEngine === 'mssql') {
81
- uidColumn = `int('uid').identity().primaryKey()`;
82
- }
83
- else if (databaseEngine === 'mariadb' ||
84
- databaseEngine === 'mysql' ||
85
- databaseEngine === 'singlestore') {
86
- uidColumn = `${intBuilder}('uid').primaryKey().autoincrement()`;
87
- }
88
- else if (databaseEngine === 'sqlite') {
89
- uidColumn = `integer('uid').primaryKey({ autoIncrement: true })`;
90
- }
91
- else {
92
- uidColumn = `integer('uid').primaryKey().generatedAlwaysAsIdentity()`;
93
- }
94
- const constsBlock = databaseEngine === 'sqlite'
95
- ? `const JULIAN_DAY_UNIX_EPOCH_OFFSET = 2440587.5;
96
- const MILLIS_PER_DAY = 86400000;\n\n`
97
- : '';
98
- let timestampColumn = `${cfg.time}.notNull().defaultNow()`;
99
- if (databaseEngine === 'sqlite')
100
- timestampColumn = `${cfg.time}.notNull().default(sql\`(julianday('now') - \${JULIAN_DAY_UNIX_EPOCH_OFFSET}) * \${MILLIS_PER_DAY}\`)`;
101
- if (databaseEngine === 'mssql')
102
- timestampColumn = `${cfg.time}.notNull().default(sql\`sysdatetime()\`)`;
103
- const tableBlock = authOption === 'abs'
72
+ const jsonDefault = cfg.jsonDefault ?? '.default({})';
73
+ const usesSqlDefault = [
74
+ cfg.timestampDefault,
75
+ usesAuth ? jsonDefault : ''
76
+ ].some((expression) => expression.includes('sql`'));
77
+ const imports = [
78
+ usesSqlDefault ? "import { sql } from 'drizzle-orm';" : '',
79
+ usesAuth
80
+ ? "import type { UserIdentity } from '../src/types/userIdentity';"
81
+ : '',
82
+ `import { ${uniqueBuilders.join(', ')} } from 'drizzle-orm/${cfg.pkg}';`
83
+ ]
84
+ .filter(Boolean)
85
+ .join('\n');
86
+ const timestampColumn = `${cfg.time}.notNull()${cfg.timestampDefault}`;
87
+ const tableBlock = usesAuth
104
88
  ? `export const users = ${cfg.table}('users', {
105
89
  auth_sub: ${cfg.string}.primaryKey(),
106
90
  created_at: ${timestampColumn},
107
- metadata: ${cfg.json}.$type<UserIdentity>().default({})
91
+ metadata: ${cfg.json}.$type<UserIdentity>()${jsonDefault}
108
92
  });`
109
93
  : `export const countHistory = ${cfg.table}('count_history', {
110
- uid: ${uidColumn},
111
- count: ${intBuilder}('count').notNull(),
94
+ uid: ${cfg.uid},
95
+ count: ${cfg.int}('count').notNull(),
112
96
  created_at: ${timestampColumn}
113
97
  });`;
114
- const schemaKey = authOption === 'abs' ? 'users' : 'countHistory';
98
+ const schemaKey = usesAuth ? 'users' : 'countHistory';
115
99
  return `
116
- ${sqliteImports}${builderImport}
100
+ ${imports}
117
101
 
118
- ${constsBlock}${tableBlock}
102
+ ${tableBlock}
119
103
 
120
104
  export const schema = {
121
105
  ${schemaKey}
@@ -5,12 +5,12 @@ type QueryOperations = {
5
5
  insertHistory: string;
6
6
  };
7
7
  declare const driverConfigurations: {
8
- readonly 'cockroachdb:sql:local': {
9
- readonly importLines: "";
8
+ readonly 'cockroachdb:drizzle:local': {
9
+ readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
10
10
  readonly queries: QueryOperations;
11
11
  };
12
- readonly 'gel:drizzle:local': {
13
- readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'\n";
12
+ readonly 'cockroachdb:sql:local': {
13
+ readonly importLines: "";
14
14
  readonly queries: QueryOperations;
15
15
  };
16
16
  readonly 'gel:sql:local': {
@@ -269,14 +269,12 @@ const mysqlDrizzleQueryOperations = {
269
269
  .where(eq(schema.countHistory.uid, uid));
270
270
 
271
271
  return newHistory;`,
272
- insertUser: `const [row] = await db
273
- .insert(schema.users)
274
- .values({ auth_sub: authSub, metadata: userIdentity });
272
+ insertUser: `await db.insert(schema.users).values(newUserData);
275
273
 
276
274
  const [newUser] = await db
277
275
  .select()
278
276
  .from(schema.users)
279
- .where(eq(schema.users.auth_sub, authSub));
277
+ .where(eq(schema.users.auth_sub, newUserData.auth_sub));
280
278
 
281
279
  if (!newUser) throw new Error('Failed to create user');
282
280
  return newUser;`,
@@ -337,16 +335,15 @@ const mysqlPlanetScaleQueryOperations = {
337
335
  `
338
336
  };
339
337
  const driverConfigurations = {
338
+ 'cockroachdb:drizzle:local': {
339
+ importLines: `import { eq } from 'drizzle-orm'
340
+ import { schema } from '../../../db/schema'`,
341
+ queries: drizzleQueryOperations
342
+ },
340
343
  'cockroachdb:sql:local': {
341
344
  importLines: ``,
342
345
  queries: postgresSqlQueryOperations
343
346
  },
344
- 'gel:drizzle:local': {
345
- importLines: `import { eq } from 'drizzle-orm'
346
- import { schema } from '../../../db/schema'
347
- `,
348
- queries: drizzleQueryOperations
349
- },
350
347
  'gel:sql:local': {
351
348
  importLines: ``,
352
349
  queries: gelClientQueryOperations
@@ -1,9 +1,11 @@
1
- import { mkdirSync, writeFileSync } from 'fs';
2
- import { join } from 'path';
3
- import { dim, yellow } from 'picocolors';
1
+ import { cpSync, mkdirSync, writeFileSync } from 'fs';
2
+ import { dirname, join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
4
  import { isDrizzleDialect } from '../../typeGuards';
5
5
  import { checkSqliteInstalled } from '../../utils/checkSqliteInstalled';
6
6
  import { createDrizzleConfig } from '../configurations/generateDrizzleConfig';
7
+ import { scaffoldPrismaDatabase } from '../prisma/scaffoldPrismaDatabase';
8
+ import { getDrizzleKitDialect, getMigrationTemplateName } from './drizzleTargets';
7
9
  import { generateDatabaseTypes } from './generateDatabaseTypes';
8
10
  import { generateDrizzleSchema } from './generateDrizzleSchema';
9
11
  import { generateDBHandlers } from './generateHandlers';
@@ -15,6 +17,19 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
15
17
  const handlerDirectory = join(backendDirectory, 'handlers');
16
18
  mkdirSync(projectDatabaseDirectory, { recursive: true });
17
19
  mkdirSync(handlerDirectory, { recursive: true });
20
+ if (orm === 'prisma') {
21
+ const dockerFreshInstall = await scaffoldPrismaDatabase({
22
+ authOption,
23
+ backendDirectory,
24
+ databaseDirectory,
25
+ databaseEngine,
26
+ databaseHost,
27
+ projectName,
28
+ typesDirectory,
29
+ verifyLocalDatabase
30
+ });
31
+ return { dockerFreshInstall };
32
+ }
18
33
  const usesAuth = authOption !== undefined && authOption !== 'none';
19
34
  const handlerFileName = usesAuth
20
35
  ? 'userHandlers.ts'
@@ -50,7 +65,10 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
50
65
  supportsRelationalSchema(databaseEngine)) {
51
66
  writeFileSync(join(projectDatabaseDirectory, 'schema.sql'), generateRelationalSchema(databaseEngine, authOption));
52
67
  }
53
- if (databaseEngine === 'sqlite') {
68
+ /* Drizzle projects get their tables from the committed initial migration
69
+ (`db:migrate`), never from hand-written DDL, so the migration history and
70
+ the database always agree. */
71
+ if (databaseEngine === 'sqlite' && orm !== 'drizzle') {
54
72
  // The sqlite3 CLI is a developer convenience (a db shell), never a
55
73
  // scaffold requirement — seeding goes through bun:sqlite below, which
56
74
  // ships with the runtime. The check only nudges interactive users;
@@ -72,6 +90,7 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
72
90
  const result = await scaffoldDocker({
73
91
  authOption,
74
92
  databaseEngine,
93
+ initializeSchema: orm !== 'drizzle',
75
94
  projectDatabaseDirectory,
76
95
  projectName,
77
96
  verifyLocalDatabase
@@ -87,7 +106,14 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
87
106
  databaseEngine
88
107
  });
89
108
  writeFileSync(join(projectDatabaseDirectory, 'schema.ts'), drizzleSchema);
90
- createDrizzleConfig({ databaseDirectory, databaseEngine, projectName });
109
+ createDrizzleConfig({
110
+ databaseDirectory,
111
+ databaseEngine,
112
+ databaseHost,
113
+ projectName
114
+ });
115
+ const templatesDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'templates');
116
+ cpSync(join(templatesDirectory, 'db', 'migrations', getMigrationTemplateName(getDrizzleKitDialect(databaseEngine, databaseHost), usesAuth)), join(projectDatabaseDirectory, 'migrations'), { recursive: true });
91
117
  const drizzleTypes = generateDatabaseTypes({
92
118
  authOption,
93
119
  databaseEngine,
@@ -97,8 +123,5 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
97
123
  writeFileSync(join(typesDirectory, 'databaseTypes.ts'), drizzleTypes);
98
124
  return { dockerFreshInstall };
99
125
  }
100
- if (orm === 'prisma') {
101
- console.warn(`${dim('│')}\n${yellow('▲')} Prisma support is not implemented yet`);
102
- }
103
126
  return { dockerFreshInstall };
104
127
  };
@@ -1,12 +1,13 @@
1
1
  import { AuthOption, DatabaseEngine } from '../../types';
2
2
  type ScaffoldDockerProps = {
3
+ initializeSchema?: boolean;
3
4
  verifyLocalDatabase?: boolean;
4
5
  databaseEngine: DatabaseEngine;
5
6
  projectDatabaseDirectory: string;
6
7
  authOption: AuthOption;
7
8
  projectName: string;
8
9
  };
9
- export declare const scaffoldDocker: ({ databaseEngine, projectDatabaseDirectory, projectName, authOption, verifyLocalDatabase }: ScaffoldDockerProps) => Promise<{
10
+ export declare const scaffoldDocker: ({ databaseEngine, projectDatabaseDirectory, projectName, authOption, initializeSchema, verifyLocalDatabase }: ScaffoldDockerProps) => Promise<{
10
11
  dockerFreshInstall: boolean;
11
12
  }>;
12
13
  export {};
@@ -5,7 +5,7 @@ import { $ } from 'bun';
5
5
  import { green, red } from 'picocolors';
6
6
  import { checkDockerInstalled, ensureDockerDaemonRunning, resolveDockerExe, shutdownDockerDaemon } from '../../utils/checkDockerInstalled';
7
7
  import { countHistoryTables, initTemplates, userTables } from './dockerInitTemplates';
8
- import { generateDockerContainer } from './generateDockerContainer';
8
+ import { generateDockerContainer, getDockerInitScript } from './generateDockerContainer';
9
9
  const initDockerSchema = async ({ authOption, databaseEngine, docker, projectName, spin }) => {
10
10
  const dbKey = databaseEngine;
11
11
  const { wait, cli } = initTemplates[dbKey];
@@ -33,7 +33,7 @@ const verifyDockerContainer = async ({ databaseEngine, docker, projectName, spin
33
33
  .cwd(projectName)
34
34
  .quiet();
35
35
  };
36
- export const scaffoldDocker = async ({ databaseEngine, projectDatabaseDirectory, projectName, authOption, verifyLocalDatabase = true }) => {
36
+ export const scaffoldDocker = async ({ databaseEngine, projectDatabaseDirectory, projectName, authOption, initializeSchema = true, verifyLocalDatabase = true }) => {
37
37
  if (databaseEngine === undefined ||
38
38
  databaseEngine === 'none' ||
39
39
  databaseEngine === 'sqlite') {
@@ -41,6 +41,10 @@ export const scaffoldDocker = async ({ databaseEngine, projectDatabaseDirectory,
41
41
  }
42
42
  const dbContainer = generateDockerContainer(databaseEngine);
43
43
  writeFileSync(join(projectDatabaseDirectory, 'docker-compose.db.yml'), dbContainer, 'utf-8');
44
+ const initScript = getDockerInitScript(databaseEngine);
45
+ if (initScript) {
46
+ writeFileSync(join(projectDatabaseDirectory, initScript.fileName), initScript.sql, 'utf-8');
47
+ }
44
48
  if (!verifyLocalDatabase)
45
49
  return { dockerFreshInstall: false };
46
50
  const { freshInstall } = await checkDockerInstalled(databaseEngine);
@@ -48,7 +52,7 @@ export const scaffoldDocker = async ({ databaseEngine, projectDatabaseDirectory,
48
52
  const docker = resolveDockerExe();
49
53
  const spin = spinner();
50
54
  spin.start(`Starting ${databaseEngine} container`);
51
- const dockerAction = databaseEngine in userTables
55
+ const dockerAction = initializeSchema && databaseEngine in userTables
52
56
  ? () => initDockerSchema({
53
57
  authOption,
54
58
  databaseEngine,
@@ -0,0 +1,6 @@
1
+ import type { AuthOption } from '../../types';
2
+ import type { PrismaTarget } from './prismaTargets';
3
+ export declare const generatePrismaDatabaseTypes: (authOption: AuthOption, { provider }: PrismaTarget) => string;
4
+ export declare const generatePrismaDBBlock: ({ adapter }: PrismaTarget) => string;
5
+ export declare const getPrismaServerImports: ({ adapter }: PrismaTarget) => string[];
6
+ export declare const generatePrismaHandlers: (authOption: AuthOption, target: PrismaTarget) => string;
@@ -0,0 +1,148 @@
1
+ /* The generated client's entry, as imported from src/backend/server.ts,
2
+ src/backend/handlers/* and src/types/* (see prismaClientDirectory). */
3
+ const clientFromBackend = '../generated/prisma/client';
4
+ const clientFromTypes = '../generated/prisma/client';
5
+ /* src/types/databaseTypes.ts. Rows are the client's own model types; the
6
+ user's metadata is narrowed to the identity shape the auth flow stores. */
7
+ export const generatePrismaDatabaseTypes = (authOption, { provider }) => {
8
+ if (authOption === 'abs')
9
+ return `import type { PrismaClient } from '${clientFromTypes}';
10
+ import type { UserIdentity } from './userIdentity';
11
+
12
+ export type DatabaseType = PrismaClient;
13
+
14
+ export type User = {
15
+ auth_sub: string;
16
+ created_at: Date;
17
+ metadata: UserIdentity;
18
+ };
19
+
20
+ export type NewUser = {
21
+ auth_sub: string;
22
+ metadata?: UserIdentity;
23
+ };
24
+ `;
25
+ const countHistory = provider === 'mongodb'
26
+ ? `/* The document's ObjectId stays internal; the API is keyed by uid. */
27
+ export type CountHistory = Omit<CountHistoryModel, 'id'>;`
28
+ : 'export type CountHistory = CountHistoryModel;';
29
+ return `import type {
30
+ CountHistory as CountHistoryModel,
31
+ PrismaClient
32
+ } from '${clientFromTypes}';
33
+
34
+ export type DatabaseType = PrismaClient;
35
+
36
+ ${countHistory}
37
+
38
+ export type NewCountHistory = {
39
+ count: number;
40
+ };
41
+ `;
42
+ };
43
+ /* Prisma 7 queries through the driver adapter; Prisma 6 (MongoDB) connects
44
+ with its own engine, given the URL explicitly so a missing one fails fast. */
45
+ export const generatePrismaDBBlock = ({ adapter }) => adapter
46
+ ? `const db = new PrismaClient({ adapter: ${adapter.expression} })`
47
+ : `const db = new PrismaClient({ datasourceUrl: getEnv('DATABASE_URL') })`;
48
+ /* server.ts imports for the Prisma client and its driver adapter. */
49
+ export const getPrismaServerImports = ({ adapter }) => [
50
+ `import { PrismaClient } from '${clientFromBackend}'`,
51
+ `import { getEnv } from '@absolutejs/absolute'`,
52
+ ...(adapter
53
+ ? [`import { ${adapter.className} } from '${adapter.packageName}'`]
54
+ : []),
55
+ ...(adapter?.expression.includes('env.')
56
+ ? [`import { env } from 'bun'`]
57
+ : [])
58
+ ];
59
+ /* Prisma queries return a lazy PrismaPromise that only runs when awaited;
60
+ Elysia serializes a returned non-Promise object as-is. Every handler is
61
+ async and awaits, so routes always receive a real Promise of the row. */
62
+ const countHistoryHandlers = ({ provider }) => provider === 'mongodb'
63
+ ? `import type { CountHistory, DatabaseType } from '../../types/databaseTypes';
64
+
65
+ export const getCountHistory = async (
66
+ db: DatabaseType,
67
+ uid: number
68
+ ): Promise<CountHistory | null> =>
69
+ await db.countHistory.findUnique({ omit: { id: true }, where: { uid } });
70
+
71
+ /* MongoDB has no autoincrement: the next uid comes from an atomic increment
72
+ on a counter document. */
73
+ export const createCountHistory = async (
74
+ db: DatabaseType,
75
+ count: number
76
+ ): Promise<CountHistory> => {
77
+ const { value: uid } = await db.counter.upsert({
78
+ create: { id: 'count_history', value: 1 },
79
+ update: { value: { increment: 1 } },
80
+ where: { id: 'count_history' }
81
+ });
82
+
83
+ return await db.countHistory.create({
84
+ data: { count, uid },
85
+ omit: { id: true }
86
+ });
87
+ };
88
+ `
89
+ : `import type { CountHistory, DatabaseType } from '../../types/databaseTypes';
90
+
91
+ export const getCountHistory = async (
92
+ db: DatabaseType,
93
+ uid: number
94
+ ): Promise<CountHistory | null> =>
95
+ await db.countHistory.findUnique({ where: { uid } });
96
+
97
+ export const createCountHistory = async (
98
+ db: DatabaseType,
99
+ count: number
100
+ ): Promise<CountHistory> => await db.countHistory.create({ data: { count } });
101
+ `;
102
+ /* Json columns hand back Prisma's JsonValue; SQL Server stores the metadata as
103
+ JSON text. Either way it is validated back into a UserIdentity. */
104
+ const userHandlers = ({ supportsJson }) => {
105
+ const readMetadata = supportsJson ? 'metadata' : 'JSON.parse(metadata)';
106
+ const writeMetadata = supportsJson
107
+ ? 'metadata'
108
+ : 'metadata: JSON.stringify(metadata)';
109
+ return `import type { DatabaseType, NewUser, User } from '../../types/databaseTypes';
110
+ import { parseUserIdentity } from '../../types/userIdentity';
111
+
112
+ type UserRow = {
113
+ auth_sub: string;
114
+ created_at: Date;
115
+ metadata: ${supportsJson ? 'unknown' : 'string'};
116
+ };
117
+
118
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
119
+ typeof value === 'object' && value !== null && !Array.isArray(value);
120
+
121
+ const toUser = ({ auth_sub, created_at, metadata }: UserRow): User => {
122
+ const identity: unknown = ${readMetadata};
123
+
124
+ return {
125
+ auth_sub,
126
+ created_at,
127
+ metadata: isRecord(identity) ? parseUserIdentity(identity) : {}
128
+ };
129
+ };
130
+
131
+ export const getUser = async (
132
+ db: DatabaseType,
133
+ authSub: string
134
+ ): Promise<User | null> => {
135
+ const user = await db.user.findUnique({ where: { auth_sub: authSub } });
136
+
137
+ return user ? toUser(user) : null;
138
+ };
139
+
140
+ export const createUser = async (
141
+ db: DatabaseType,
142
+ { auth_sub, metadata = {} }: NewUser
143
+ ): Promise<User> =>
144
+ toUser(await db.user.create({ data: { auth_sub, ${writeMetadata} } }));
145
+ `;
146
+ };
147
+ /* src/backend/handlers/{countHistory,user}Handlers.ts */
148
+ export const generatePrismaHandlers = (authOption, target) => authOption === 'abs' ? userHandlers(target) : countHistoryHandlers(target);
@@ -0,0 +1,8 @@
1
+ import type { PrismaTarget } from './prismaTargets';
2
+ type GeneratePrismaConfigProps = {
3
+ databaseDirectory: string;
4
+ target: PrismaTarget;
5
+ };
6
+ export declare const generateLibsqlMigrateScript: () => string;
7
+ export declare const generatePrismaConfig: ({ databaseDirectory, target }: GeneratePrismaConfigProps) => string;
8
+ export {};
@@ -0,0 +1,68 @@
1
+ /* Prisma reads no .env file on its own once a prisma.config.ts exists, and its
2
+ CLI may run under Node, so the config loads the project's .env itself (never
3
+ overriding variables already set in the environment). */
4
+ const loadEnv = `/* Prisma does not load .env by itself; variables already set win. */
5
+ if (existsSync('.env')) process.loadEnvFile('.env');
6
+ `;
7
+ /* Applies the committed Prisma migrations to a libSQL/Turso database, in
8
+ order, once each, recording them in their own table. */
9
+ export const generateLibsqlMigrateScript = () => `import { readdirSync, readFileSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { createClient } from '@libsql/client';
12
+ import { env } from 'bun';
13
+
14
+ const url = env.DATABASE_URL;
15
+ if (!url) throw new Error('DATABASE_URL must be set in the environment variables');
16
+
17
+ const client = createClient({ authToken: env.DATABASE_AUTH_TOKEN, url });
18
+ const migrationsDirectory = join(import.meta.dir, 'migrations');
19
+
20
+ await client.execute(
21
+ 'CREATE TABLE IF NOT EXISTS "_libsql_migrations" ("name" TEXT PRIMARY KEY, "applied_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP)'
22
+ );
23
+ const { rows } = await client.execute('SELECT "name" FROM "_libsql_migrations"');
24
+ const applied = new Set(rows.map((row) => String(row.name)));
25
+ const pending = readdirSync(migrationsDirectory, { withFileTypes: true })
26
+ .filter((entry) => entry.isDirectory() && !applied.has(entry.name))
27
+ .map((entry) => entry.name)
28
+ .sort();
29
+
30
+ for (const name of pending) {
31
+ const sql = readFileSync(join(migrationsDirectory, name, 'migration.sql'), 'utf8');
32
+ await client.executeMultiple(sql);
33
+ await client.execute({ args: [name], sql: 'INSERT INTO "_libsql_migrations" ("name") VALUES (?)' });
34
+ console.log(\`Applied \${name}\`);
35
+ }
36
+
37
+ console.log(pending.length ? \`\${pending.length} migration(s) applied\` : 'No pending migrations');
38
+ client.close();
39
+ `;
40
+ export const generatePrismaConfig = ({ databaseDirectory, target }) => {
41
+ const schema = `schema: '${databaseDirectory}/schema.prisma'`;
42
+ if (target.line === 'prisma6') {
43
+ return `import { existsSync } from 'node:fs';
44
+ import { defineConfig } from 'prisma/config';
45
+
46
+ ${loadEnv}
47
+ export default defineConfig({
48
+ ${schema}
49
+ });
50
+ `;
51
+ }
52
+ /* Prisma Migrate cannot reach libSQL/Turso URLs. \`db:migrate:dev\` authors
53
+ new migrations against a scratch SQLite file instead, and \`db:migrate\`
54
+ applies them to DATABASE_URL through ${databaseDirectory}/migrate.ts. */
55
+ const url = target.migration === 'libsql'
56
+ ? `'file:./${databaseDirectory}/prisma-migrate-dev.db'`
57
+ : "process.env.DATABASE_URL ?? ''";
58
+ return `import { existsSync } from 'node:fs';
59
+ import { defineConfig } from 'prisma/config';
60
+
61
+ ${loadEnv}
62
+ export default defineConfig({
63
+ datasource: { url: ${url} },
64
+ migrations: { path: '${databaseDirectory}/migrations' },
65
+ ${schema}
66
+ });
67
+ `;
68
+ };
@@ -0,0 +1,9 @@
1
+ import type { AuthOption } from '../../types';
2
+ import { type PrismaTarget } from './prismaTargets';
3
+ type GeneratePrismaSchemaProps = {
4
+ authOption: AuthOption;
5
+ databaseDirectory: string;
6
+ target: PrismaTarget;
7
+ };
8
+ export declare const generatePrismaSchema: ({ authOption, databaseDirectory, target }: GeneratePrismaSchemaProps) => string;
9
+ export {};