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.
- package/CHANGELOG.md +16 -0
- package/README.md +9 -2
- package/changelog.json +24 -0
- package/dist/commands/migrateDatabase.d.ts +6 -0
- package/dist/commands/migrateDatabase.js +53 -0
- package/dist/data.d.ts +1 -1
- package/dist/data.js +1 -0
- package/dist/generators/configurations/generateDrizzleConfig.d.ts +5 -3
- package/dist/generators/configurations/generateDrizzleConfig.js +40 -14
- package/dist/generators/configurations/generateEnv.js +6 -0
- package/dist/generators/configurations/generateEslintConfig.js +1 -1
- package/dist/generators/configurations/generatePackageJson.d.ts +2 -1
- package/dist/generators/configurations/generatePackageJson.js +40 -4
- package/dist/generators/db/drizzleTargets.d.ts +5 -0
- package/dist/generators/db/drizzleTargets.js +27 -0
- package/dist/generators/db/generateDatabaseTypes.js +10 -6
- package/dist/generators/db/generateDockerContainer.d.ts +5 -0
- package/dist/generators/db/generateDockerContainer.js +22 -4
- package/dist/generators/db/generateDrizzleSchema.js +65 -81
- package/dist/generators/db/handlerTemplates.d.ts +4 -4
- package/dist/generators/db/handlerTemplates.js +7 -10
- package/dist/generators/db/scaffoldDatabase.js +31 -8
- package/dist/generators/db/scaffoldDocker.d.ts +2 -1
- package/dist/generators/db/scaffoldDocker.js +7 -3
- package/dist/generators/prisma/generatePrismaCode.d.ts +6 -0
- package/dist/generators/prisma/generatePrismaCode.js +148 -0
- package/dist/generators/prisma/generatePrismaConfig.d.ts +8 -0
- package/dist/generators/prisma/generatePrismaConfig.js +68 -0
- package/dist/generators/prisma/generatePrismaSchema.d.ts +9 -0
- package/dist/generators/prisma/generatePrismaSchema.js +80 -0
- package/dist/generators/prisma/prismaPackageJson.d.ts +8 -0
- package/dist/generators/prisma/prismaPackageJson.js +46 -0
- package/dist/generators/prisma/prismaTargets.d.ts +22 -0
- package/dist/generators/prisma/prismaTargets.js +91 -0
- package/dist/generators/prisma/scaffoldPrismaDatabase.d.ts +16 -0
- package/dist/generators/prisma/scaffoldPrismaDatabase.js +136 -0
- package/dist/generators/project/generateDBBlock.js +16 -2
- package/dist/generators/project/generateImportsBlock.js +17 -7
- package/dist/generators/project/generateServer.js +2 -1
- package/dist/index.js +2 -1
- package/dist/messages.d.ts +2 -1
- package/dist/messages.js +2 -1
- package/dist/questions/authOption.js +4 -1
- package/dist/questions/databaseEngine.d.ts +1 -1
- package/dist/questions/orm.js +5 -2
- package/dist/scaffold.d.ts +1 -0
- package/dist/scaffold.js +23 -5
- package/dist/templates/configurations/.prettierignore +2 -1
- package/dist/templates/configurations/eslint.config.example.mjs +81 -41
- package/dist/templates/db/migrations/cockroach-count-history/20260926210314_init/migration.sql +5 -0
- package/dist/templates/db/migrations/cockroach-count-history/20260926210314_init/snapshot.json +69 -0
- package/dist/templates/db/migrations/cockroach-users/20260926210315_init/migration.sql +5 -0
- package/dist/templates/db/migrations/cockroach-users/20260926210315_init/snapshot.json +62 -0
- package/dist/templates/db/migrations/mssql-count-history/20260926210316_init/migration.sql +6 -0
- package/dist/templates/db/migrations/mssql-count-history/20260926210316_init/snapshot.json +64 -0
- package/dist/templates/db/migrations/mssql-users/20260926210317_init/migration.sql +6 -0
- package/dist/templates/db/migrations/mssql-users/20260926210317_init/snapshot.json +70 -0
- package/dist/templates/db/migrations/mysql-count-history/20260926210318_init/migration.sql +5 -0
- package/dist/templates/db/migrations/mysql-count-history/20260926210318_init/snapshot.json +61 -0
- package/dist/templates/db/migrations/mysql-users/20260926210319_init/migration.sql +5 -0
- package/dist/templates/db/migrations/mysql-users/20260926210319_init/snapshot.json +61 -0
- package/dist/templates/db/migrations/postgresql-count-history/20260926210320_init/migration.sql +5 -0
- package/dist/templates/db/migrations/postgresql-count-history/20260926210320_init/snapshot.json +71 -0
- package/dist/templates/db/migrations/postgresql-users/20260926210320_init/migration.sql +5 -0
- package/dist/templates/db/migrations/postgresql-users/20260926210320_init/snapshot.json +62 -0
- package/dist/templates/db/migrations/prisma-cockroachdb-count-history/20260926000000_init/migration.sql +12 -0
- package/dist/templates/db/migrations/prisma-cockroachdb-count-history/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-cockroachdb-users/20260926000000_init/migration.sql +12 -0
- package/dist/templates/db/migrations/prisma-cockroachdb-users/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-mysql-count-history/20260926000000_init/migration.sql +9 -0
- package/dist/templates/db/migrations/prisma-mysql-count-history/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-mysql-users/20260926000000_init/migration.sql +9 -0
- package/dist/templates/db/migrations/prisma-mysql-users/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-postgresql-count-history/20260926000000_init/migration.sql +12 -0
- package/dist/templates/db/migrations/prisma-postgresql-count-history/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-postgresql-users/20260926000000_init/migration.sql +12 -0
- package/dist/templates/db/migrations/prisma-postgresql-users/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-sqlite-count-history/20260926000000_init/migration.sql +7 -0
- package/dist/templates/db/migrations/prisma-sqlite-count-history/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-sqlite-users/20260926000000_init/migration.sql +7 -0
- package/dist/templates/db/migrations/prisma-sqlite-users/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-sqlserver-count-history/20260926000000_init/migration.sql +28 -0
- package/dist/templates/db/migrations/prisma-sqlserver-count-history/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-sqlserver-users/20260926000000_init/migration.sql +28 -0
- package/dist/templates/db/migrations/prisma-sqlserver-users/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/singlestore-count-history/20260926210321_init/migration.sql +5 -0
- package/dist/templates/db/migrations/singlestore-count-history/20260926210321_init/snapshot.json +61 -0
- package/dist/templates/db/migrations/singlestore-users/20260926210322_init/migration.sql +5 -0
- package/dist/templates/db/migrations/singlestore-users/20260926210322_init/snapshot.json +61 -0
- package/dist/templates/db/migrations/sqlite-count-history/20260926210323_init/migration.sql +5 -0
- package/dist/templates/db/migrations/sqlite-count-history/20260926210323_init/snapshot.json +50 -0
- package/dist/templates/db/migrations/sqlite-users/20260926210324_init/migration.sql +5 -0
- package/dist/templates/db/migrations/sqlite-users/20260926210324_init/snapshot.json +50 -0
- package/dist/templates/db/migrations/turso-count-history/20260926210324_init/migration.sql +5 -0
- package/dist/templates/db/migrations/turso-count-history/20260926210324_init/snapshot.json +50 -0
- package/dist/templates/db/migrations/turso-users/20260926210325_init/migration.sql +5 -0
- package/dist/templates/db/migrations/turso-users/20260926210325_init/snapshot.json +50 -0
- package/dist/templates/git/gitignore +5 -3
- package/dist/typeGuards.d.ts +2 -2
- package/dist/typeGuards.js +2 -2
- package/dist/utils/parseCommandLineOptions.js +16 -31
- package/dist/utils/validateDatabaseSelection.d.ts +11 -0
- package/dist/utils/validateDatabaseSelection.js +34 -0
- package/dist/versions.d.ts +9 -0
- package/dist/versions.js +13 -0
- package/package.json +4 -1
- 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
|
-
|
|
3
|
-
|
|
4
|
-
json: '
|
|
5
|
-
pkg: '
|
|
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: '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
table: 'singlestoreTable',
|
|
48
|
-
time: 'timestamp()'
|
|
48
|
+
table: 'singlestoreTable'
|
|
49
49
|
},
|
|
50
50
|
sqlite: {
|
|
51
|
-
|
|
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
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
: ''
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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>()
|
|
91
|
+
metadata: ${cfg.json}.$type<UserIdentity>()${jsonDefault}
|
|
108
92
|
});`
|
|
109
93
|
: `export const countHistory = ${cfg.table}('count_history', {
|
|
110
|
-
uid: ${
|
|
111
|
-
count: ${
|
|
94
|
+
uid: ${cfg.uid},
|
|
95
|
+
count: ${cfg.int}('count').notNull(),
|
|
112
96
|
created_at: ${timestampColumn}
|
|
113
97
|
});`;
|
|
114
|
-
const schemaKey =
|
|
98
|
+
const schemaKey = usesAuth ? 'users' : 'countHistory';
|
|
115
99
|
return `
|
|
116
|
-
${
|
|
100
|
+
${imports}
|
|
117
101
|
|
|
118
|
-
${
|
|
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:
|
|
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 '
|
|
13
|
-
readonly importLines: "
|
|
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: `
|
|
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,
|
|
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 {
|
|
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
|
-
|
|
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({
|
|
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 {};
|