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.
Files changed (100) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +37 -4
  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 -1
  8. package/dist/generators/angular/generateAngularPage.d.ts +5 -6
  9. package/dist/generators/angular/generateAngularPage.js +16 -33
  10. package/dist/generators/angular/scaffoldAngular.d.ts +1 -1
  11. package/dist/generators/angular/scaffoldAngular.js +8 -9
  12. package/dist/generators/configurations/generateDrizzleConfig.d.ts +5 -3
  13. package/dist/generators/configurations/generateDrizzleConfig.js +40 -14
  14. package/dist/generators/configurations/generateEnv.js +6 -0
  15. package/dist/generators/configurations/generateEslintConfig.js +4 -2
  16. package/dist/generators/configurations/generatePackageJson.js +33 -6
  17. package/dist/generators/db/drizzleTargets.d.ts +5 -0
  18. package/dist/generators/db/drizzleTargets.js +27 -0
  19. package/dist/generators/db/generateDatabaseTypes.js +21 -24
  20. package/dist/generators/db/generateDockerContainer.d.ts +5 -0
  21. package/dist/generators/db/generateDockerContainer.js +22 -4
  22. package/dist/generators/db/generateDrizzleSchema.js +65 -77
  23. package/dist/generators/db/generateHandlers.js +3 -2
  24. package/dist/generators/db/generateRelationalSchema.d.ts +1 -1
  25. package/dist/generators/db/generateRelationalSchema.js +1 -1
  26. package/dist/generators/db/handlerTemplates.d.ts +11 -5
  27. package/dist/generators/db/handlerTemplates.js +14 -12
  28. package/dist/generators/db/scaffoldDatabase.d.ts +2 -1
  29. package/dist/generators/db/scaffoldDatabase.js +25 -10
  30. package/dist/generators/db/scaffoldDocker.d.ts +3 -1
  31. package/dist/generators/db/scaffoldDocker.js +11 -5
  32. package/dist/generators/htmx/scaffoldHTMX.js +1 -0
  33. package/dist/generators/project/generateAbsoluteAuthConfig.d.ts +1 -1
  34. package/dist/generators/project/generateAbsoluteAuthConfig.js +11 -10
  35. package/dist/generators/project/generateDBBlock.js +16 -7
  36. package/dist/generators/project/generateIdentity.d.ts +1 -0
  37. package/dist/generators/project/generateIdentity.js +15 -0
  38. package/dist/generators/project/generateImportsBlock.js +15 -15
  39. package/dist/generators/project/generateRoutesBlock.d.ts +2 -1
  40. package/dist/generators/project/generateRoutesBlock.js +8 -13
  41. package/dist/generators/project/generateServer.js +26 -16
  42. package/dist/generators/project/scaffoldAgentic.d.ts +1 -1
  43. package/dist/generators/project/scaffoldAgentic.js +45 -45
  44. package/dist/generators/project/scaffoldBackend.js +4 -9
  45. package/dist/generators/react/scaffoldReact.js +2 -2
  46. package/dist/index.js +2 -1
  47. package/dist/messages.d.ts +2 -1
  48. package/dist/messages.js +2 -1
  49. package/dist/prompt.js +3 -2
  50. package/dist/questions/authOption.js +4 -1
  51. package/dist/questions/databaseEngine.d.ts +1 -1
  52. package/dist/questions/orm.js +2 -5
  53. package/dist/scaffold.d.ts +3 -1
  54. package/dist/scaffold.js +33 -4
  55. package/dist/templates/configurations/eslint.config.example.mjs +81 -41
  56. package/dist/templates/db/migrations/cockroach-count-history/20260926210314_init/migration.sql +5 -0
  57. package/dist/templates/db/migrations/cockroach-count-history/20260926210314_init/snapshot.json +69 -0
  58. package/dist/templates/db/migrations/cockroach-users/20260926210315_init/migration.sql +5 -0
  59. package/dist/templates/db/migrations/cockroach-users/20260926210315_init/snapshot.json +62 -0
  60. package/dist/templates/db/migrations/mssql-count-history/20260926210316_init/migration.sql +6 -0
  61. package/dist/templates/db/migrations/mssql-count-history/20260926210316_init/snapshot.json +64 -0
  62. package/dist/templates/db/migrations/mssql-users/20260926210317_init/migration.sql +6 -0
  63. package/dist/templates/db/migrations/mssql-users/20260926210317_init/snapshot.json +70 -0
  64. package/dist/templates/db/migrations/mysql-count-history/20260926210318_init/migration.sql +5 -0
  65. package/dist/templates/db/migrations/mysql-count-history/20260926210318_init/snapshot.json +61 -0
  66. package/dist/templates/db/migrations/mysql-users/20260926210319_init/migration.sql +5 -0
  67. package/dist/templates/db/migrations/mysql-users/20260926210319_init/snapshot.json +61 -0
  68. package/dist/templates/db/migrations/postgresql-count-history/20260926210320_init/migration.sql +5 -0
  69. package/dist/templates/db/migrations/postgresql-count-history/20260926210320_init/snapshot.json +71 -0
  70. package/dist/templates/db/migrations/postgresql-users/20260926210320_init/migration.sql +5 -0
  71. package/dist/templates/db/migrations/postgresql-users/20260926210320_init/snapshot.json +62 -0
  72. package/dist/templates/db/migrations/singlestore-count-history/20260926210321_init/migration.sql +5 -0
  73. package/dist/templates/db/migrations/singlestore-count-history/20260926210321_init/snapshot.json +61 -0
  74. package/dist/templates/db/migrations/singlestore-users/20260926210322_init/migration.sql +5 -0
  75. package/dist/templates/db/migrations/singlestore-users/20260926210322_init/snapshot.json +61 -0
  76. package/dist/templates/db/migrations/sqlite-count-history/20260926210323_init/migration.sql +5 -0
  77. package/dist/templates/db/migrations/sqlite-count-history/20260926210323_init/snapshot.json +50 -0
  78. package/dist/templates/db/migrations/sqlite-users/20260926210324_init/migration.sql +5 -0
  79. package/dist/templates/db/migrations/sqlite-users/20260926210324_init/snapshot.json +50 -0
  80. package/dist/templates/db/migrations/turso-count-history/20260926210324_init/migration.sql +5 -0
  81. package/dist/templates/db/migrations/turso-count-history/20260926210324_init/snapshot.json +50 -0
  82. package/dist/templates/db/migrations/turso-users/20260926210325_init/migration.sql +5 -0
  83. package/dist/templates/db/migrations/turso-users/20260926210325_init/snapshot.json +50 -0
  84. package/dist/templates/git/gitignore +0 -3
  85. package/dist/templates/htmx/LICENSE +13 -0
  86. package/dist/templates/htmx/htmx.2.0.11.min.js +1 -0
  87. package/dist/typeGuards.js +2 -2
  88. package/dist/utils/abort.d.ts +1 -1
  89. package/dist/utils/abort.js +2 -4
  90. package/dist/utils/getPackageVersion.js +7 -2
  91. package/dist/utils/parseCommandLineOptions.js +17 -34
  92. package/dist/utils/registryChannel.d.ts +2 -0
  93. package/dist/utils/registryChannel.js +20 -0
  94. package/dist/utils/validateDatabaseSelection.d.ts +11 -0
  95. package/dist/utils/validateDatabaseSelection.js +31 -0
  96. package/dist/versions.d.ts +66 -65
  97. package/dist/versions.js +66 -65
  98. package/package.json +27 -22
  99. package/dist/templates/configurations/drizzle.config.ts +0 -13
  100. 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 { NeonHttpDatabase } from 'drizzle-orm/neon-http';`;
112
- dbTypeLine = 'export type DatabaseType = NeonHttpDatabase<SchemaType>;';
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 { PlanetScaleDatabase } from 'drizzle-orm/planetscale-serverless';`;
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 { NodePgDatabase } from 'drizzle-orm/node-postgres';`;
122
- dbTypeLine = 'export type DatabaseType = NodePgDatabase<SchemaType>;';
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 { LibSQLDatabase } from 'drizzle-orm/libsql';`;
126
- dbTypeLine = 'export type DatabaseType = LibSQLDatabase<SchemaType>;';
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 'gel':
132
- dbImport = `import { GelJsDatabase } from 'drizzle-orm/gel';`;
130
+ case 'cockroachdb':
131
+ dbImport = `import type { drizzle } from 'drizzle-orm/cockroach';`;
133
132
  dbTypeLine =
134
- 'export type DatabaseType = GelJsDatabase<SchemaType>;';
133
+ 'export type DatabaseType = ReturnType<typeof drizzle>;';
135
134
  break;
136
135
  case 'mariadb':
137
136
  case 'mysql':
138
- dbImport = `import { Mysql2Database } from 'drizzle-orm/mysql2';`;
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 { NodeMssqlDatabase } from 'drizzle-orm/node-mssql';`;
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 { BunSQLDatabase } from 'drizzle-orm/bun-sql';`;
145
+ dbImport = `import type { drizzle } from 'drizzle-orm/bun-sql';`;
149
146
  dbTypeLine =
150
- 'export type DatabaseType = BunSQLDatabase<SchemaType>;';
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<SchemaType>;';
152
+ 'export type DatabaseType = SingleStoreDriverDatabase;';
156
153
  break;
157
154
  case 'sqlite':
158
- dbImport = `import { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';`;
155
+ dbImport = `import type { drizzle } from 'drizzle-orm/bun-sqlite';`;
159
156
  dbTypeLine =
160
- 'export type DatabaseType = BunSQLiteDatabase<SchemaType>;';
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
- test: 'cockroach sql --insecure -e "select 1" >/dev/null 2>&1'
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
- test: 'singlestore -u root -ppassword -e "SELECT 1" >/dev/null 2>&1'
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
- 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' })"
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 = `import { ${uniqueBuilders.join(', ')} } from 'drizzle-orm/${cfg.pkg}';`;
76
- const sqliteImports = databaseEngine === 'sqlite'
77
- ? `import { sql } from 'drizzle-orm';\n`
78
- : '';
79
- let uidColumn;
80
- if (databaseEngine === 'mariadb' ||
81
- databaseEngine === 'mssql' ||
82
- databaseEngine === 'mysql' ||
83
- databaseEngine === 'singlestore') {
84
- uidColumn = `${intBuilder}('uid').primaryKey().autoincrement()`;
85
- }
86
- else if (databaseEngine === 'sqlite') {
87
- uidColumn = `integer('uid').primaryKey({ autoIncrement: true })`;
88
- }
89
- else {
90
- uidColumn = `integer('uid').primaryKey().generatedAlwaysAsIdentity()`;
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<Record<string, unknown>>().default({})
91
+ metadata: ${cfg.json}.$type<UserIdentity>()${jsonDefault}
104
92
  });`
105
93
  : `export const countHistory = ${cfg.table}('count_history', {
106
- uid: ${uidColumn},
107
- count: ${intBuilder}('count').notNull(),
94
+ uid: ${cfg.uid},
95
+ count: ${cfg.int}('count').notNull(),
108
96
  created_at: ${timestampColumn}
109
97
  });`;
110
- const schemaKey = authOption === 'abs' ? 'users' : 'countHistory';
98
+ const schemaKey = usesAuth ? 'users' : 'countHistory';
111
99
  return `
112
- ${sqliteImports}${builderImport}
100
+ ${imports}
113
101
 
114
- ${constsBlock}${tableBlock}
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
- // @ts-expect-error - TODO: Finish the other templates
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: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': {
@@ -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: QueryOperations;
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: `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
@@ -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: drizzleQueryOperations
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
- if (databaseEngine === 'sqlite') {
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 { dockerFreshInstall } = await scaffoldDocker({
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
- return { dockerFreshInstall };
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({ databaseDirectory, databaseEngine, projectName });
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: false };
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: false };
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;