create-absolutejs 0.17.3 → 0.19.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 +37 -4
- 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 -1
- package/dist/generators/angular/generateAngularPage.d.ts +5 -6
- package/dist/generators/angular/generateAngularPage.js +16 -33
- package/dist/generators/angular/scaffoldAngular.d.ts +1 -1
- package/dist/generators/angular/scaffoldAngular.js +8 -9
- 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 +4 -2
- package/dist/generators/configurations/generatePackageJson.js +33 -6
- package/dist/generators/db/drizzleTargets.d.ts +5 -0
- package/dist/generators/db/drizzleTargets.js +27 -0
- package/dist/generators/db/generateDatabaseTypes.js +21 -24
- package/dist/generators/db/generateDockerContainer.d.ts +5 -0
- package/dist/generators/db/generateDockerContainer.js +22 -4
- package/dist/generators/db/generateDrizzleSchema.js +65 -77
- package/dist/generators/db/generateHandlers.js +3 -2
- package/dist/generators/db/generateRelationalSchema.d.ts +1 -1
- package/dist/generators/db/generateRelationalSchema.js +1 -1
- package/dist/generators/db/handlerTemplates.d.ts +11 -5
- package/dist/generators/db/handlerTemplates.js +14 -12
- package/dist/generators/db/scaffoldDatabase.d.ts +2 -1
- package/dist/generators/db/scaffoldDatabase.js +25 -10
- package/dist/generators/db/scaffoldDocker.d.ts +3 -1
- package/dist/generators/db/scaffoldDocker.js +11 -5
- package/dist/generators/htmx/scaffoldHTMX.js +1 -0
- package/dist/generators/project/generateAbsoluteAuthConfig.d.ts +1 -1
- package/dist/generators/project/generateAbsoluteAuthConfig.js +11 -10
- package/dist/generators/project/generateDBBlock.js +16 -7
- package/dist/generators/project/generateIdentity.d.ts +1 -0
- package/dist/generators/project/generateIdentity.js +15 -0
- package/dist/generators/project/generateImportsBlock.js +15 -15
- package/dist/generators/project/generateRoutesBlock.d.ts +2 -1
- package/dist/generators/project/generateRoutesBlock.js +8 -13
- package/dist/generators/project/generateServer.js +26 -16
- package/dist/generators/project/scaffoldAgentic.d.ts +1 -1
- package/dist/generators/project/scaffoldAgentic.js +45 -45
- package/dist/generators/project/scaffoldBackend.js +4 -9
- package/dist/generators/react/scaffoldReact.js +2 -2
- package/dist/index.js +2 -1
- package/dist/messages.d.ts +2 -1
- package/dist/messages.js +2 -1
- package/dist/prompt.js +3 -2
- package/dist/questions/authOption.js +4 -1
- package/dist/questions/databaseEngine.d.ts +1 -1
- package/dist/questions/orm.js +2 -5
- package/dist/scaffold.d.ts +3 -1
- package/dist/scaffold.js +33 -4
- 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/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 +0 -3
- package/dist/templates/htmx/LICENSE +13 -0
- package/dist/templates/htmx/htmx.2.0.11.min.js +1 -0
- package/dist/typeGuards.js +2 -2
- package/dist/utils/abort.d.ts +1 -1
- package/dist/utils/abort.js +2 -4
- package/dist/utils/getPackageVersion.js +7 -2
- package/dist/utils/parseCommandLineOptions.js +17 -34
- package/dist/utils/registryChannel.d.ts +2 -0
- package/dist/utils/registryChannel.js +20 -0
- package/dist/utils/validateDatabaseSelection.d.ts +11 -0
- package/dist/utils/validateDatabaseSelection.js +31 -0
- package/dist/versions.d.ts +66 -65
- package/dist/versions.js +66 -65
- package/package.json +27 -22
- package/dist/templates/configurations/drizzle.config.ts +0 -13
- package/dist/templates/htmx/htmx.2.0.6.min.js +0 -1
|
@@ -108,56 +108,53 @@ export const generateDatabaseTypes = ({ databaseEngine, databaseHost, authOption
|
|
|
108
108
|
let dbImport = '';
|
|
109
109
|
let dbTypeLine = '';
|
|
110
110
|
if (databaseHost === 'neon') {
|
|
111
|
-
dbImport = `import {
|
|
112
|
-
dbTypeLine = 'export type DatabaseType =
|
|
111
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/neon-http';`;
|
|
112
|
+
dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
113
113
|
}
|
|
114
114
|
else if (databaseHost === 'planetscale' && databaseEngine === 'mysql') {
|
|
115
|
-
dbImport = `import {
|
|
116
|
-
dbTypeLine =
|
|
117
|
-
'export type DatabaseType = PlanetScaleDatabase<SchemaType>;';
|
|
115
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/planetscale-serverless';`;
|
|
116
|
+
dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
118
117
|
}
|
|
119
118
|
else if (databaseHost === 'planetscale' &&
|
|
120
119
|
databaseEngine === 'postgresql') {
|
|
121
|
-
dbImport = `import {
|
|
122
|
-
dbTypeLine = 'export type DatabaseType =
|
|
120
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/node-postgres';`;
|
|
121
|
+
dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
123
122
|
}
|
|
124
123
|
else if (databaseHost === 'turso') {
|
|
125
|
-
dbImport = `import {
|
|
126
|
-
dbTypeLine = 'export type DatabaseType =
|
|
124
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/libsql';`;
|
|
125
|
+
dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
127
126
|
}
|
|
128
127
|
if ((!databaseHost || databaseHost === 'none') &&
|
|
129
128
|
isDrizzleDialect(databaseEngine)) {
|
|
130
129
|
switch (databaseEngine) {
|
|
131
|
-
case '
|
|
132
|
-
dbImport = `import {
|
|
130
|
+
case 'cockroachdb':
|
|
131
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/cockroach';`;
|
|
133
132
|
dbTypeLine =
|
|
134
|
-
'export type DatabaseType =
|
|
133
|
+
'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
135
134
|
break;
|
|
136
135
|
case 'mariadb':
|
|
137
136
|
case 'mysql':
|
|
138
|
-
dbImport = `import {
|
|
139
|
-
dbTypeLine =
|
|
140
|
-
'export type DatabaseType = Mysql2Database<SchemaType>;';
|
|
137
|
+
dbImport = `import type { MySql2Database } from 'drizzle-orm/mysql2';`;
|
|
138
|
+
dbTypeLine = 'export type DatabaseType = MySql2Database;';
|
|
141
139
|
break;
|
|
142
140
|
case 'mssql':
|
|
143
|
-
dbImport = `import {
|
|
144
|
-
dbTypeLine =
|
|
145
|
-
'export type DatabaseType = NodeMssqlDatabase<SchemaType>;';
|
|
141
|
+
dbImport = `import type { NodeMsSqlDatabase } from 'drizzle-orm/node-mssql';`;
|
|
142
|
+
dbTypeLine = 'export type DatabaseType = NodeMsSqlDatabase;';
|
|
146
143
|
break;
|
|
147
144
|
case 'postgresql':
|
|
148
|
-
dbImport = `import {
|
|
145
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/bun-sql';`;
|
|
149
146
|
dbTypeLine =
|
|
150
|
-
'export type DatabaseType =
|
|
147
|
+
'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
151
148
|
break;
|
|
152
149
|
case 'singlestore':
|
|
153
|
-
dbImport = `import { SingleStoreDriverDatabase } from 'drizzle-orm/singlestore';`;
|
|
150
|
+
dbImport = `import type { SingleStoreDriverDatabase } from 'drizzle-orm/singlestore';`;
|
|
154
151
|
dbTypeLine =
|
|
155
|
-
'export type DatabaseType = SingleStoreDriverDatabase
|
|
152
|
+
'export type DatabaseType = SingleStoreDriverDatabase;';
|
|
156
153
|
break;
|
|
157
154
|
case 'sqlite':
|
|
158
|
-
dbImport = `import {
|
|
155
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/bun-sqlite';`;
|
|
159
156
|
dbTypeLine =
|
|
160
|
-
'export type DatabaseType =
|
|
157
|
+
'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
161
158
|
break;
|
|
162
159
|
}
|
|
163
160
|
}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import { DatabaseEngine } from '../../types';
|
|
2
2
|
export declare const generateDockerContainer: (databaseEngine: DatabaseEngine) => string;
|
|
3
|
+
export declare const getDockerInitScript: (databaseEngine: DatabaseEngine) => {
|
|
4
|
+
fileName: string;
|
|
5
|
+
mountPath: string;
|
|
6
|
+
sql: string;
|
|
7
|
+
} | undefined;
|
|
@@ -6,7 +6,9 @@ const templates = {
|
|
|
6
6
|
},
|
|
7
7
|
healthcheck: {
|
|
8
8
|
startPeriod: '5s',
|
|
9
|
-
|
|
9
|
+
/* Healthy once COCKROACH_DATABASE exists, not merely once the node
|
|
10
|
+
answers, so `db:up --wait` is followed safely by `db:migrate`. */
|
|
11
|
+
test: 'cockroach sql --insecure -e "SHOW DATABASES" 2>/dev/null | grep -q "^database"'
|
|
10
12
|
},
|
|
11
13
|
image: 'cockroachdb/cockroach:latest-v25.3',
|
|
12
14
|
port: '26257:26257',
|
|
@@ -101,9 +103,17 @@ const templates = {
|
|
|
101
103
|
},
|
|
102
104
|
healthcheck: {
|
|
103
105
|
startPeriod: '30s',
|
|
104
|
-
|
|
106
|
+
/* Healthy once the init script has created the database. */
|
|
107
|
+
test: 'singlestore -u root -ppassword -D database -e "SELECT 1" >/dev/null 2>&1'
|
|
105
108
|
},
|
|
106
109
|
image: 'ghcr.io/singlestore-labs/singlestoredb-dev', // NOTE: No tag specified due to data persistence
|
|
110
|
+
/* The dev image has no "create this database" variable; without it the
|
|
111
|
+
DATABASE_URL's database would not exist until something created it. */
|
|
112
|
+
initScript: {
|
|
113
|
+
fileName: 'singlestore-init.sql',
|
|
114
|
+
mountPath: '/init.sql',
|
|
115
|
+
sql: 'CREATE DATABASE IF NOT EXISTS `database`;\n'
|
|
116
|
+
},
|
|
107
117
|
platform: 'linux/amd64', // Required for ARM64 (Apple Silicon); no-op on amd64
|
|
108
118
|
port: '3306:3306',
|
|
109
119
|
volumePath: '/data'
|
|
@@ -115,7 +125,10 @@ export const generateDockerContainer = (databaseEngine) => {
|
|
|
115
125
|
databaseEngine === 'sqlite') {
|
|
116
126
|
throw new Error('Internal type error: Expected a valid local database engine');
|
|
117
127
|
}
|
|
118
|
-
const { command, env, healthcheck, image, platform, port, volumePath } = templates[databaseEngine];
|
|
128
|
+
const { command, env, healthcheck, image, initScript, platform, port, volumePath } = templates[databaseEngine];
|
|
129
|
+
const initScriptLine = initScript
|
|
130
|
+
? `\n - ./${initScript.fileName}:${initScript.mountPath}:ro`
|
|
131
|
+
: '';
|
|
119
132
|
const commandLine = command ? `\n command: ${command}` : '';
|
|
120
133
|
const platformLine = platform ? `\n platform: ${platform}` : '';
|
|
121
134
|
const envLines = Object.entries(env)
|
|
@@ -136,9 +149,14 @@ ${envLines}
|
|
|
136
149
|
retries: 30
|
|
137
150
|
start_period: ${healthcheck.startPeriod}
|
|
138
151
|
volumes:
|
|
139
|
-
- db_data:${volumePath}
|
|
152
|
+
- db_data:${volumePath}${initScriptLine}
|
|
140
153
|
|
|
141
154
|
volumes:
|
|
142
155
|
db_data:
|
|
143
156
|
`;
|
|
144
157
|
};
|
|
158
|
+
export const getDockerInitScript = (databaseEngine) => databaseEngine === undefined ||
|
|
159
|
+
databaseEngine === 'none' ||
|
|
160
|
+
databaseEngine === 'sqlite'
|
|
161
|
+
? undefined
|
|
162
|
+
: templates[databaseEngine].initScript;
|
|
@@ -1,117 +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: '
|
|
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
|
-
}
|
|
92
|
-
const constsBlock = databaseEngine === 'sqlite'
|
|
93
|
-
? `const JULIAN_DAY_UNIX_EPOCH_OFFSET = 2440587.5;
|
|
94
|
-
const MILLIS_PER_DAY = 86400000;\n\n`
|
|
95
|
-
: '';
|
|
96
|
-
const timestampColumn = databaseEngine === 'sqlite'
|
|
97
|
-
? `${cfg.time}.notNull().default(sql\`(julianday('now') - \${JULIAN_DAY_UNIX_EPOCH_OFFSET}) * \${MILLIS_PER_DAY}\`)`
|
|
98
|
-
: `${cfg.time}.notNull().defaultNow()`;
|
|
99
|
-
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
|
|
100
88
|
? `export const users = ${cfg.table}('users', {
|
|
101
89
|
auth_sub: ${cfg.string}.primaryKey(),
|
|
102
90
|
created_at: ${timestampColumn},
|
|
103
|
-
metadata: ${cfg.json}.$type<
|
|
91
|
+
metadata: ${cfg.json}.$type<UserIdentity>()${jsonDefault}
|
|
104
92
|
});`
|
|
105
93
|
: `export const countHistory = ${cfg.table}('count_history', {
|
|
106
|
-
uid: ${
|
|
107
|
-
count: ${
|
|
94
|
+
uid: ${cfg.uid},
|
|
95
|
+
count: ${cfg.int}('count').notNull(),
|
|
108
96
|
created_at: ${timestampColumn}
|
|
109
97
|
});`;
|
|
110
|
-
const schemaKey =
|
|
98
|
+
const schemaKey = usesAuth ? 'users' : 'countHistory';
|
|
111
99
|
return `
|
|
112
|
-
${
|
|
100
|
+
${imports}
|
|
113
101
|
|
|
114
|
-
${
|
|
102
|
+
${tableBlock}
|
|
115
103
|
|
|
116
104
|
export const schema = {
|
|
117
105
|
${schemaKey}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAuthTemplate, getCountTemplate } from './handlerTemplates';
|
|
1
|
+
import { getAuthTemplate, getCountTemplate, isDriverConfigurationKey } from './handlerTemplates';
|
|
2
2
|
export const generateDBHandlers = ({ databaseEngine, databaseHost, orm, usesAuth }) => {
|
|
3
3
|
if (databaseEngine === undefined || databaseEngine === 'none') {
|
|
4
4
|
throw new Error('Internal Error: databaseEngine is undefined or "none".');
|
|
@@ -6,6 +6,7 @@ export const generateDBHandlers = ({ databaseEngine, databaseHost, orm, usesAuth
|
|
|
6
6
|
const host = databaseHost && databaseHost !== 'none' ? databaseHost : 'local';
|
|
7
7
|
const ormKey = orm === 'drizzle' ? 'drizzle' : 'sql';
|
|
8
8
|
const key = `${databaseEngine}:${ormKey}:${host}`;
|
|
9
|
-
|
|
9
|
+
if (!isDriverConfigurationKey(key))
|
|
10
|
+
throw new Error(`Unsupported database configuration: ${key}`);
|
|
10
11
|
return usesAuth ? getAuthTemplate(key) : getCountTemplate(key);
|
|
11
12
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { AuthOption, DatabaseEngine } from '../../types';
|
|
2
|
-
export declare const supportsRelationalSchema: (engine: DatabaseEngine) => boolean;
|
|
3
2
|
export declare const generateRelationalSchema: (databaseEngine: DatabaseEngine, authOption: AuthOption) => string;
|
|
3
|
+
export declare const supportsRelationalSchema: (engine: DatabaseEngine) => boolean;
|
|
@@ -30,7 +30,6 @@ const DIALECTS = {
|
|
|
30
30
|
timestamp: 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP'
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
-
export const supportsRelationalSchema = (engine) => engine !== undefined && engine in DIALECTS;
|
|
34
33
|
export const generateRelationalSchema = (databaseEngine, authOption) => {
|
|
35
34
|
if (databaseEngine === undefined || !(databaseEngine in DIALECTS)) {
|
|
36
35
|
throw new Error(`Internal error: no relational DDL for engine "${databaseEngine}"`);
|
|
@@ -50,3 +49,4 @@ export const generateRelationalSchema = (databaseEngine, authOption) => {
|
|
|
50
49
|
);
|
|
51
50
|
`;
|
|
52
51
|
};
|
|
52
|
+
export const supportsRelationalSchema = (engine) => engine !== undefined && engine in DIALECTS;
|
|
@@ -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': {
|
|
@@ -31,7 +31,12 @@ declare const driverConfigurations: {
|
|
|
31
31
|
};
|
|
32
32
|
readonly 'mssql:drizzle:local': {
|
|
33
33
|
readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
|
|
34
|
-
readonly queries:
|
|
34
|
+
readonly queries: {
|
|
35
|
+
readonly insertHistory: string;
|
|
36
|
+
readonly insertUser: string;
|
|
37
|
+
readonly selectUser: string;
|
|
38
|
+
readonly selectHistory: string;
|
|
39
|
+
};
|
|
35
40
|
};
|
|
36
41
|
readonly 'mssql:sql:local': {
|
|
37
42
|
readonly importLines: "";
|
|
@@ -105,4 +110,5 @@ declare const driverConfigurations: {
|
|
|
105
110
|
type DriverConfigurationKey = keyof typeof driverConfigurations;
|
|
106
111
|
export declare const getAuthTemplate: (key: DriverConfigurationKey) => string;
|
|
107
112
|
export declare const getCountTemplate: (key: DriverConfigurationKey) => string;
|
|
113
|
+
export declare const isDriverConfigurationKey: (key: string) => key is DriverConfigurationKey;
|
|
108
114
|
export {};
|
|
@@ -19,7 +19,7 @@ export const getUser = async (
|
|
|
19
19
|
((await selectUserRow(db, authSub)) as User | null) ?? null;`}
|
|
20
20
|
|
|
21
21
|
export const createUser = async (db: DatabaseType, newUserData: NewUser) => {
|
|
22
|
-
const { auth_sub: authSub, metadata: userIdentity } = newUserData;
|
|
22
|
+
${rowsAreTyped ? '' : 'const { auth_sub: authSub, metadata: userIdentity } = newUserData;'}
|
|
23
23
|
${queries.insertUser}
|
|
24
24
|
}`;
|
|
25
25
|
/* Takes `db` as the `DatabaseType` alias rather than naming the driver type
|
|
@@ -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
|
|
@@ -367,7 +364,11 @@ import { schema } from '../../../db/schema'`,
|
|
|
367
364
|
'mssql:drizzle:local': {
|
|
368
365
|
importLines: `import { eq } from 'drizzle-orm'
|
|
369
366
|
import { schema } from '../../../db/schema'`,
|
|
370
|
-
queries:
|
|
367
|
+
queries: {
|
|
368
|
+
...drizzleQueryOperations,
|
|
369
|
+
insertHistory: drizzleQueryOperations.insertHistory.replace('.values({ count }).returning()', '.output().values({ count })'),
|
|
370
|
+
insertUser: drizzleQueryOperations.insertUser.replace('.values(newUserData)\n\t\t.returning()', '.output()\n\t\t.values(newUserData)')
|
|
371
|
+
}
|
|
371
372
|
},
|
|
372
373
|
'mssql:sql:local': {
|
|
373
374
|
importLines: ``,
|
|
@@ -462,3 +463,4 @@ export const getCountTemplate = (key) => {
|
|
|
462
463
|
throw new Error(`Unsupported driver configuration: ${key}`);
|
|
463
464
|
return buildSqlCountTemplate(configuration);
|
|
464
465
|
};
|
|
466
|
+
export const isDriverConfigurationKey = (key) => Object.hasOwn(driverConfigurations, key);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { CreateConfiguration } from '../../types';
|
|
2
2
|
type ScaffoldDatabaseProps = Pick<CreateConfiguration, 'projectName' | 'databaseHost' | 'orm' | 'databaseDirectory' | 'authOption' | 'databaseEngine'> & {
|
|
3
3
|
databaseDirectory: string;
|
|
4
|
+
verifyLocalDatabase?: boolean;
|
|
4
5
|
backendDirectory: string;
|
|
5
6
|
typesDirectory: string;
|
|
6
7
|
};
|
|
7
|
-
export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory }: ScaffoldDatabaseProps) => Promise<{
|
|
8
|
+
export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory, verifyLocalDatabase }: ScaffoldDatabaseProps) => Promise<{
|
|
8
9
|
dockerFreshInstall: boolean;
|
|
9
10
|
}>;
|
|
10
11
|
export {};
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
1
|
+
import { cpSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
3
4
|
import { dim, yellow } from 'picocolors';
|
|
4
5
|
import { isDrizzleDialect } from '../../typeGuards';
|
|
5
6
|
import { checkSqliteInstalled } from '../../utils/checkSqliteInstalled';
|
|
6
7
|
import { createDrizzleConfig } from '../configurations/generateDrizzleConfig';
|
|
8
|
+
import { getDrizzleKitDialect, getMigrationTemplateName } from './drizzleTargets';
|
|
7
9
|
import { generateDatabaseTypes } from './generateDatabaseTypes';
|
|
8
10
|
import { generateDrizzleSchema } from './generateDrizzleSchema';
|
|
9
11
|
import { generateDBHandlers } from './generateHandlers';
|
|
10
12
|
import { generateRelationalSchema, supportsRelationalSchema } from './generateRelationalSchema';
|
|
11
13
|
import { generateSqliteSchema } from './generateSqliteSchema';
|
|
12
14
|
import { scaffoldDocker } from './scaffoldDocker';
|
|
13
|
-
export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory }) => {
|
|
15
|
+
export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory, verifyLocalDatabase = true }) => {
|
|
14
16
|
const projectDatabaseDirectory = join(projectName, databaseDirectory);
|
|
15
17
|
const handlerDirectory = join(backendDirectory, 'handlers');
|
|
16
18
|
mkdirSync(projectDatabaseDirectory, { recursive: true });
|
|
@@ -50,7 +52,10 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
|
|
|
50
52
|
supportsRelationalSchema(databaseEngine)) {
|
|
51
53
|
writeFileSync(join(projectDatabaseDirectory, 'schema.sql'), generateRelationalSchema(databaseEngine, authOption));
|
|
52
54
|
}
|
|
53
|
-
|
|
55
|
+
/* Drizzle projects get their tables from the committed initial migration
|
|
56
|
+
(`db:migrate`), never from hand-written DDL, so the migration history and
|
|
57
|
+
the database always agree. */
|
|
58
|
+
if (databaseEngine === 'sqlite' && orm !== 'drizzle') {
|
|
54
59
|
// The sqlite3 CLI is a developer convenience (a db shell), never a
|
|
55
60
|
// scaffold requirement — seeding goes through bun:sqlite below, which
|
|
56
61
|
// ships with the runtime. The check only nudges interactive users;
|
|
@@ -64,17 +69,20 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
|
|
|
64
69
|
seeded.run(sqliteSchema);
|
|
65
70
|
seeded.close();
|
|
66
71
|
}
|
|
72
|
+
let dockerFreshInstall = false;
|
|
67
73
|
if ((databaseHost === 'none' || databaseHost === undefined) &&
|
|
68
74
|
databaseEngine !== 'sqlite' &&
|
|
69
75
|
databaseEngine !== undefined &&
|
|
70
76
|
databaseEngine !== 'none') {
|
|
71
|
-
const
|
|
77
|
+
const result = await scaffoldDocker({
|
|
72
78
|
authOption,
|
|
73
79
|
databaseEngine,
|
|
80
|
+
initializeSchema: orm !== 'drizzle',
|
|
74
81
|
projectDatabaseDirectory,
|
|
75
|
-
projectName
|
|
82
|
+
projectName,
|
|
83
|
+
verifyLocalDatabase
|
|
76
84
|
});
|
|
77
|
-
|
|
85
|
+
({ dockerFreshInstall } = result);
|
|
78
86
|
}
|
|
79
87
|
if (orm === 'drizzle') {
|
|
80
88
|
if (!isDrizzleDialect(databaseEngine)) {
|
|
@@ -85,7 +93,14 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
|
|
|
85
93
|
databaseEngine
|
|
86
94
|
});
|
|
87
95
|
writeFileSync(join(projectDatabaseDirectory, 'schema.ts'), drizzleSchema);
|
|
88
|
-
createDrizzleConfig({
|
|
96
|
+
createDrizzleConfig({
|
|
97
|
+
databaseDirectory,
|
|
98
|
+
databaseEngine,
|
|
99
|
+
databaseHost,
|
|
100
|
+
projectName
|
|
101
|
+
});
|
|
102
|
+
const templatesDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'templates');
|
|
103
|
+
cpSync(join(templatesDirectory, 'db', 'migrations', getMigrationTemplateName(getDrizzleKitDialect(databaseEngine, databaseHost), usesAuth)), join(projectDatabaseDirectory, 'migrations'), { recursive: true });
|
|
89
104
|
const drizzleTypes = generateDatabaseTypes({
|
|
90
105
|
authOption,
|
|
91
106
|
databaseEngine,
|
|
@@ -93,10 +108,10 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
|
|
|
93
108
|
orm
|
|
94
109
|
});
|
|
95
110
|
writeFileSync(join(typesDirectory, 'databaseTypes.ts'), drizzleTypes);
|
|
96
|
-
return { dockerFreshInstall
|
|
111
|
+
return { dockerFreshInstall };
|
|
97
112
|
}
|
|
98
113
|
if (orm === 'prisma') {
|
|
99
114
|
console.warn(`${dim('│')}\n${yellow('▲')} Prisma support is not implemented yet`);
|
|
100
115
|
}
|
|
101
|
-
return { dockerFreshInstall
|
|
116
|
+
return { dockerFreshInstall };
|
|
102
117
|
};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { AuthOption, DatabaseEngine } from '../../types';
|
|
2
2
|
type ScaffoldDockerProps = {
|
|
3
|
+
initializeSchema?: boolean;
|
|
4
|
+
verifyLocalDatabase?: boolean;
|
|
3
5
|
databaseEngine: DatabaseEngine;
|
|
4
6
|
projectDatabaseDirectory: string;
|
|
5
7
|
authOption: AuthOption;
|
|
6
8
|
projectName: string;
|
|
7
9
|
};
|
|
8
|
-
export declare const scaffoldDocker: ({ databaseEngine, projectDatabaseDirectory, projectName, authOption }: ScaffoldDockerProps) => Promise<{
|
|
10
|
+
export declare const scaffoldDocker: ({ databaseEngine, projectDatabaseDirectory, projectName, authOption, initializeSchema, verifyLocalDatabase }: ScaffoldDockerProps) => Promise<{
|
|
9
11
|
dockerFreshInstall: boolean;
|
|
10
12
|
}>;
|
|
11
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,20 +33,26 @@ 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 }) => {
|
|
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') {
|
|
40
40
|
throw new Error('Internal type error: databaseEngine must be defined and not "none" or "sqlite"');
|
|
41
41
|
}
|
|
42
|
-
const { freshInstall } = await checkDockerInstalled(databaseEngine);
|
|
43
|
-
const { daemonWasStarted } = await ensureDockerDaemonRunning();
|
|
44
42
|
const dbContainer = generateDockerContainer(databaseEngine);
|
|
45
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
|
+
}
|
|
48
|
+
if (!verifyLocalDatabase)
|
|
49
|
+
return { dockerFreshInstall: false };
|
|
50
|
+
const { freshInstall } = await checkDockerInstalled(databaseEngine);
|
|
51
|
+
const { daemonWasStarted } = await ensureDockerDaemonRunning();
|
|
46
52
|
const docker = resolveDockerExe();
|
|
47
53
|
const spin = spinner();
|
|
48
54
|
spin.start(`Starting ${databaseEngine} container`);
|
|
49
|
-
const dockerAction = databaseEngine in userTables
|
|
55
|
+
const dockerAction = initializeSchema && databaseEngine in userTables
|
|
50
56
|
? () => initDockerSchema({
|
|
51
57
|
authOption,
|
|
52
58
|
databaseEngine,
|
|
@@ -6,6 +6,7 @@ import { generateHTMXPage } from './generateHTMXPage';
|
|
|
6
6
|
export const scaffoldHTMX = ({ editBasePath, includeExamples, targetDirectory, templatesDirectory, projectAssetsDirectory, frontends, isSingleFrontend, stylesIndexesDirectory }) => {
|
|
7
7
|
const pagesDirectory = join(targetDirectory, 'pages');
|
|
8
8
|
const cssOutputFile = join(stylesIndexesDirectory, 'htmx-example.css');
|
|
9
|
+
copyFileSync(join(templatesDirectory, 'htmx', 'LICENSE'), join(targetDirectory, 'htmx.LICENSE'));
|
|
9
10
|
// htmx.min.js is the framework runtime, copied in both modes.
|
|
10
11
|
const glob = new Glob('htmx*.min.js');
|
|
11
12
|
for (const relativePath of glob.scanSync({
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { ProviderOption } from '@absolutejs/auth';
|
|
2
|
-
export declare const generateSessionUserType: () => string;
|
|
3
2
|
export declare const generateAbsoluteAuthConfig: (absProviders: ProviderOption[] | undefined, hasDatabase: boolean) => string;
|
|
3
|
+
export declare const generateSessionUserType: () => string;
|