create-absolutejs 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +9 -2
  3. package/changelog.json +10 -0
  4. package/dist/generators/configurations/generateEslintConfig.js +1 -1
  5. package/dist/generators/configurations/generatePackageJson.d.ts +2 -1
  6. package/dist/generators/configurations/generatePackageJson.js +10 -1
  7. package/dist/generators/db/scaffoldDatabase.js +14 -4
  8. package/dist/generators/prisma/generatePrismaCode.d.ts +6 -0
  9. package/dist/generators/prisma/generatePrismaCode.js +148 -0
  10. package/dist/generators/prisma/generatePrismaConfig.d.ts +8 -0
  11. package/dist/generators/prisma/generatePrismaConfig.js +68 -0
  12. package/dist/generators/prisma/generatePrismaSchema.d.ts +9 -0
  13. package/dist/generators/prisma/generatePrismaSchema.js +80 -0
  14. package/dist/generators/prisma/prismaPackageJson.d.ts +8 -0
  15. package/dist/generators/prisma/prismaPackageJson.js +46 -0
  16. package/dist/generators/prisma/prismaTargets.d.ts +22 -0
  17. package/dist/generators/prisma/prismaTargets.js +91 -0
  18. package/dist/generators/prisma/scaffoldPrismaDatabase.d.ts +16 -0
  19. package/dist/generators/prisma/scaffoldPrismaDatabase.js +136 -0
  20. package/dist/generators/project/generateDBBlock.js +5 -0
  21. package/dist/generators/project/generateImportsBlock.js +6 -1
  22. package/dist/questions/orm.js +5 -2
  23. package/dist/scaffold.js +4 -4
  24. package/dist/templates/configurations/.prettierignore +2 -1
  25. package/dist/templates/configurations/eslint.config.example.mjs +2 -1
  26. package/dist/templates/db/migrations/prisma-cockroachdb-count-history/20260926000000_init/migration.sql +12 -0
  27. package/dist/templates/db/migrations/prisma-cockroachdb-count-history/migration_lock.toml +3 -0
  28. package/dist/templates/db/migrations/prisma-cockroachdb-users/20260926000000_init/migration.sql +12 -0
  29. package/dist/templates/db/migrations/prisma-cockroachdb-users/migration_lock.toml +3 -0
  30. package/dist/templates/db/migrations/prisma-mysql-count-history/20260926000000_init/migration.sql +9 -0
  31. package/dist/templates/db/migrations/prisma-mysql-count-history/migration_lock.toml +3 -0
  32. package/dist/templates/db/migrations/prisma-mysql-users/20260926000000_init/migration.sql +9 -0
  33. package/dist/templates/db/migrations/prisma-mysql-users/migration_lock.toml +3 -0
  34. package/dist/templates/db/migrations/prisma-postgresql-count-history/20260926000000_init/migration.sql +12 -0
  35. package/dist/templates/db/migrations/prisma-postgresql-count-history/migration_lock.toml +3 -0
  36. package/dist/templates/db/migrations/prisma-postgresql-users/20260926000000_init/migration.sql +12 -0
  37. package/dist/templates/db/migrations/prisma-postgresql-users/migration_lock.toml +3 -0
  38. package/dist/templates/db/migrations/prisma-sqlite-count-history/20260926000000_init/migration.sql +7 -0
  39. package/dist/templates/db/migrations/prisma-sqlite-count-history/migration_lock.toml +3 -0
  40. package/dist/templates/db/migrations/prisma-sqlite-users/20260926000000_init/migration.sql +7 -0
  41. package/dist/templates/db/migrations/prisma-sqlite-users/migration_lock.toml +3 -0
  42. package/dist/templates/db/migrations/prisma-sqlserver-count-history/20260926000000_init/migration.sql +28 -0
  43. package/dist/templates/db/migrations/prisma-sqlserver-count-history/migration_lock.toml +3 -0
  44. package/dist/templates/db/migrations/prisma-sqlserver-users/20260926000000_init/migration.sql +28 -0
  45. package/dist/templates/db/migrations/prisma-sqlserver-users/migration_lock.toml +3 -0
  46. package/dist/templates/git/gitignore +5 -0
  47. package/dist/typeGuards.d.ts +2 -2
  48. package/dist/utils/parseCommandLineOptions.js +2 -8
  49. package/dist/utils/validateDatabaseSelection.js +5 -2
  50. package/dist/versions.d.ts +9 -0
  51. package/dist/versions.js +13 -0
  52. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ This file is generated by `absolute-changelog` from the entries in
6
6
  `changelog/`. Edit an entry, not this file — and add new ones under
7
7
  `changelog/unreleased/`.
8
8
 
9
+ ## 0.20.0 — 2026-09-27
10
+
11
+ ### Added
12
+
13
+ - **Scaffold Prisma for every Prisma database (PostgreSQL, Neon, PlanetScale, CockroachDB, MySQL, MariaDB, SQLite, Turso, SQL Server on Prisma 7.10 driver adapters; MongoDB on Prisma 6.19.3) with a committed initial migration, typed handlers and prisma generate / db:migrate scripts**
14
+
9
15
  ## 0.19.0 — 2026-09-26
10
16
 
11
17
  ### Added
package/README.md CHANGED
@@ -112,8 +112,9 @@ Usage: create-absolute [project-name] [options]
112
112
  - `--lts`
113
113
  Use LTS versions of required packages.
114
114
 
115
- - `--orm <drizzle|none>`
116
- ORM to configure: `drizzle` | `none`. Prisma scaffolding is not implemented and is rejected before files are written. Drizzle 1 no longer includes the Gel dialect; choose no ORM for Gel.
115
+ - `--orm <drizzle|prisma|none>`
116
+ ORM to configure: `drizzle` | `prisma` | `none`. Drizzle 1 no longer includes the Gel dialect; choose no ORM for Gel.
117
+ Prisma supports PostgreSQL (local, Neon, PlanetScale), CockroachDB, MySQL (local, PlanetScale), MariaDB, SQLite (local, Turso), SQL Server and MongoDB; Gel and SingleStore are rejected before files are written. A Prisma project ships `prisma.config.ts`, `db/schema.prisma` and a committed initial migration; `postinstall` / `db:generate` run `prisma generate` into `src/generated/prisma` (git-ignored), and `db:migrate` applies the migrations (`prisma migrate deploy`; Turso through `db/migrate.ts`, since Prisma Migrate cannot reach libSQL URLs; MongoDB through `prisma db push`). `db:migrate:dev` authors new migrations.
117
118
 
118
119
  - `--plugin <plugin>`
119
120
  Elysia plugin(s) to include (repeatable); `none` skips plugin setup. Select
@@ -206,6 +207,12 @@ switch a generated server back to Elysia 1.
206
207
  - TypeScript 5.9.3 and Angular's current 21 LTS patches match the framework's
207
208
  compiler peer dependencies. Newer incompatible compiler majors are not selected.
208
209
  - Drizzle ORM and Kit use the coordinated 1.0 release-candidate channel.
210
+ - Prisma uses 7.10 (the `prisma-client` generator with driver adapters) for every
211
+ SQL database. MongoDB projects pin Prisma 6.19.3, because Prisma 7 does not
212
+ support MongoDB yet (Prisma's documentation directs MongoDB users to v6.19).
213
+ They need a replica set, which the generated Docker database provides. The npm
214
+ `latest` tag for `prisma` currently points at an 8.0 release candidate, so
215
+ Prisma is always pinned, including under `--lts`.
209
216
  - All other pins are checked against their latest published release channel.
210
217
 
211
218
  Automatic authentication setup currently supplies a complete Google configuration
package/changelog.json CHANGED
@@ -2,6 +2,16 @@
2
2
  "contract": 1,
3
3
  "name": "create-absolutejs",
4
4
  "releases": [
5
+ {
6
+ "changes": [
7
+ {
8
+ "kind": "added",
9
+ "summary": "Scaffold Prisma for every Prisma database (PostgreSQL, Neon, PlanetScale, CockroachDB, MySQL, MariaDB, SQLite, Turso, SQL Server on Prisma 7.10 driver adapters; MongoDB on Prisma 6.19.3) with a committed initial migration, typed handlers and prisma generate / db:migrate scripts"
10
+ }
11
+ ],
12
+ "date": "2026-09-27",
13
+ "version": "0.20.0"
14
+ },
5
15
  {
6
16
  "changes": [
7
17
  {
@@ -7,7 +7,7 @@ ${frontends.includes('react') ? "import reactHooks from 'eslint-plugin-react-hoo
7
7
 
8
8
  export default defineConfig([
9
9
  // The copied HTMX distribution is third-party minified runtime code.
10
- { ignores: ['src/frontend/**/htmx.min.js', 'node_modules/**', 'build/**', 'dist/**', '.absolutejs/**', '.data/**', '**/.absolutejs-hmr-*', 'drizzle/**'] },
10
+ { ignores: ['src/frontend/**/htmx.min.js', 'node_modules/**', 'build/**', 'dist/**', '.absolutejs/**', '.data/**', '**/.absolutejs-hmr-*', 'drizzle/**', 'src/generated/**'] },
11
11
  { linterOptions: { noInlineConfig: true, reportUnusedDisableDirectives: 'error' } },
12
12
  ...tseslint.configs.recommended,
13
13
  {
@@ -1,8 +1,9 @@
1
1
  import type { CreateConfiguration } from '../../types';
2
2
  type CreatePackageJsonProps = Pick<CreateConfiguration, 'authOption' | 'agentic' | 'useTailwind' | 'databaseEngine' | 'databaseHost' | 'plugins' | 'orm' | 'frontendDirectories' | 'codeQualityTool'> & {
3
3
  projectName: string;
4
+ databaseDirectory?: string;
4
5
  latest: boolean;
5
6
  repositoryUrl: string | undefined;
6
7
  };
7
- export declare const createPackageJson: ({ agentic, projectName, authOption, plugins, databaseEngine, orm, databaseHost, useTailwind, latest, frontendDirectories, codeQualityTool, repositoryUrl }: CreatePackageJsonProps) => Promise<void>;
8
+ export declare const createPackageJson: ({ agentic, projectName, authOption, plugins, databaseEngine, orm, databaseHost, useTailwind, latest, frontendDirectories, codeQualityTool, databaseDirectory, repositoryUrl }: CreatePackageJsonProps) => Promise<void>;
8
9
  export {};
@@ -3,8 +3,11 @@ import { join } from 'path';
3
3
  import { spinner } from '@clack/prompts';
4
4
  import { green } from 'picocolors';
5
5
  import { absoluteAuthPlugin, availablePlugins, defaultDependencies, defaultPlugins, eslintAndPrettierDependencies, eslintReactDependencies } from '../../data';
6
+ import { isPrismaDialect } from '../../typeGuards';
6
7
  import { getPackageVersions } from '../../utils/getPackageVersion';
7
8
  import { versions } from '../../versions';
9
+ import { getPrismaPackageJson } from '../prisma/prismaPackageJson';
10
+ import { getPrismaTarget } from '../prisma/prismaTargets';
8
11
  import { computeFlags } from '../project/computeFlags';
9
12
  const dbClientCommands = {
10
13
  cockroachdb: 'cockroach sql --insecure --database=database',
@@ -16,7 +19,7 @@ const dbClientCommands = {
16
19
  postgresql: 'psql -h localhost -U user -d database',
17
20
  singlestore: 'singlestore -u root -ppassword -D database'
18
21
  };
19
- export const createPackageJson = async ({ agentic, projectName, authOption, plugins, databaseEngine, orm, databaseHost, useTailwind, latest, frontendDirectories, codeQualityTool, repositoryUrl }) => {
22
+ export const createPackageJson = async ({ agentic, projectName, authOption, plugins, databaseEngine, orm, databaseHost, useTailwind, latest, frontendDirectories, codeQualityTool, databaseDirectory = 'db', repositoryUrl }) => {
20
23
  const flags = computeFlags(frontendDirectories);
21
24
  const isLocal = !databaseHost || databaseHost === 'none';
22
25
  /* ── Collect all package names that need versions ─────────── */
@@ -322,6 +325,12 @@ export const createPackageJson = async ({ agentic, projectName, authOption, plug
322
325
  /* `drizzle.config.ts` imports it and the scripts above shell out to it. */
323
326
  devDependencies['drizzle-kit'] = resolveVersion('drizzle-kit', versions['drizzle-kit']);
324
327
  }
328
+ if (orm === 'prisma' && isPrismaDialect(databaseEngine)) {
329
+ const prisma = getPrismaPackageJson(getPrismaTarget(databaseEngine, databaseHost), databaseDirectory);
330
+ Object.assign(dependencies, prisma.dependencies);
331
+ Object.assign(devDependencies, prisma.devDependencies);
332
+ Object.assign(scripts, prisma.scripts);
333
+ }
325
334
  const packageJson = {
326
335
  dependencies,
327
336
  devDependencies,
@@ -1,10 +1,10 @@
1
1
  import { cpSync, mkdirSync, writeFileSync } from 'fs';
2
2
  import { dirname, join } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import { dim, yellow } from 'picocolors';
5
4
  import { isDrizzleDialect } from '../../typeGuards';
6
5
  import { checkSqliteInstalled } from '../../utils/checkSqliteInstalled';
7
6
  import { createDrizzleConfig } from '../configurations/generateDrizzleConfig';
7
+ import { scaffoldPrismaDatabase } from '../prisma/scaffoldPrismaDatabase';
8
8
  import { getDrizzleKitDialect, getMigrationTemplateName } from './drizzleTargets';
9
9
  import { generateDatabaseTypes } from './generateDatabaseTypes';
10
10
  import { generateDrizzleSchema } from './generateDrizzleSchema';
@@ -17,6 +17,19 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
17
17
  const handlerDirectory = join(backendDirectory, 'handlers');
18
18
  mkdirSync(projectDatabaseDirectory, { recursive: true });
19
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
+ }
20
33
  const usesAuth = authOption !== undefined && authOption !== 'none';
21
34
  const handlerFileName = usesAuth
22
35
  ? 'userHandlers.ts'
@@ -110,8 +123,5 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
110
123
  writeFileSync(join(typesDirectory, 'databaseTypes.ts'), drizzleTypes);
111
124
  return { dockerFreshInstall };
112
125
  }
113
- if (orm === 'prisma') {
114
- console.warn(`${dim('│')}\n${yellow('▲')} Prisma support is not implemented yet`);
115
- }
116
126
  return { dockerFreshInstall };
117
127
  };
@@ -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 {};
@@ -0,0 +1,80 @@
1
+ import { posix } from 'path';
2
+ import { prismaClientDirectory } from './prismaTargets';
3
+ /* Mirrors the Drizzle example tables (count_history, or users with auth) so
4
+ both ORMs scaffold the same database. MongoDB documents are keyed by an
5
+ ObjectId, so the numeric `uid` the API exposes is a unique field there,
6
+ assigned from an atomic counter document. */
7
+ /* CockroachDB only autoincrements BigInt; an Int key takes a sequence, so
8
+ the uid stays a JavaScript number like every other engine's. Its maximum is
9
+ pinned to INT4's, which is what CockroachDB gives the identity column —
10
+ otherwise Prisma reports the migrated table as drifted from the schema. */
11
+ const countHistoryModel = ({ provider }) => provider === 'mongodb'
12
+ ? `model CountHistory {
13
+ id String @id @default(auto()) @map("_id") @db.ObjectId
14
+ uid Int @unique
15
+ count Int
16
+ created_at DateTime @default(now())
17
+
18
+ @@map("count_history")
19
+ }
20
+
21
+ /// Hands out sequential count_history uids (MongoDB has no autoincrement).
22
+ model Counter {
23
+ id String @id @map("_id")
24
+ value Int
25
+
26
+ @@map("counters")
27
+ }`
28
+ : `model CountHistory {
29
+ uid Int @id @default(${provider === 'cockroachdb' ? 'sequence(maxValue: 2147483647)' : 'autoincrement()'})
30
+ count Int
31
+ created_at DateTime @default(now())
32
+
33
+ @@map("count_history")
34
+ }`;
35
+ /* The auth subject's column type: bounded like the Drizzle schema's
36
+ varchar(255) wherever the provider has a native type for it. */
37
+ const authSubType = {
38
+ cockroachdb: ' @db.String(255)',
39
+ mongodb: '',
40
+ mysql: ' @db.VarChar(255)',
41
+ postgresql: ' @db.VarChar(255)',
42
+ sqlite: '',
43
+ sqlserver: ' @db.NVarChar(255)'
44
+ };
45
+ /* `metadata` has no database default: Prisma renders a Json default as
46
+ invalid SQLite DDL, so the handlers always write it instead. */
47
+ const userModel = ({ provider, supportsJson }) => {
48
+ const metadataColumn = supportsJson
49
+ ? 'metadata Json'
50
+ : 'metadata String @db.NVarChar(Max)';
51
+ const idColumns = provider === 'mongodb'
52
+ ? `id String @id @default(auto()) @map("_id") @db.ObjectId
53
+ auth_sub String @unique`
54
+ : `auth_sub String @id${authSubType[provider]}`;
55
+ return `model User {
56
+ ${idColumns}
57
+ created_at DateTime @default(now())
58
+ ${metadataColumn}
59
+
60
+ @@map("users")
61
+ }`;
62
+ };
63
+ export const generatePrismaSchema = ({ authOption, databaseDirectory, target }) => {
64
+ const output = posix.relative(posix.normalize(databaseDirectory), prismaClientDirectory);
65
+ /* Prisma 7 takes the connection URL from prisma.config.ts; Prisma 6 (the
66
+ MongoDB line) still reads it from the schema. */
67
+ const url = target.line === 'prisma6' ? '\n url = env("DATABASE_URL")' : '';
68
+ const model = authOption === 'abs' ? userModel(target) : countHistoryModel(target);
69
+ return `generator client {
70
+ provider = "prisma-client"
71
+ output = "${output}"
72
+ }
73
+
74
+ datasource db {
75
+ provider = "${target.provider}"${url}
76
+ }
77
+
78
+ ${model}
79
+ `;
80
+ };
@@ -0,0 +1,8 @@
1
+ import type { PrismaTarget } from './prismaTargets';
2
+ type PrismaPackageJson = {
3
+ dependencies: Record<string, string>;
4
+ devDependencies: Record<string, string>;
5
+ scripts: Record<string, string>;
6
+ };
7
+ export declare const getPrismaPackageJson: (target: PrismaTarget, databaseDirectory: string) => PrismaPackageJson;
8
+ export {};
@@ -0,0 +1,46 @@
1
+ import { prismaMongoDBVersion, versions } from '../../versions';
2
+ const migrationScripts = ({ migration }, databaseDirectory) => {
3
+ /* The Prisma MongoDB container is an unauthenticated single-node replica
4
+ set (see prismaMongoDBCompose), so its shell needs no credentials. */
5
+ if (migration === 'push')
6
+ return {
7
+ 'db:migrate': 'prisma db push',
8
+ 'db:mongodb': `docker compose -p mongodb -f ${databaseDirectory}/docker-compose.db.yml exec -it db mongosh database`
9
+ };
10
+ if (migration === 'libsql')
11
+ return {
12
+ 'db:migrate': `bun ${databaseDirectory}/migrate.ts`,
13
+ 'db:migrate:dev': 'prisma migrate dev'
14
+ };
15
+ return {
16
+ 'db:migrate': 'prisma migrate deploy',
17
+ 'db:migrate:dev': 'prisma migrate dev'
18
+ };
19
+ };
20
+ /* Dependencies and scripts a Prisma project adds. Versions are always the
21
+ pinned ones — never `--lts` resolved — because npm's `latest` dist-tag for
22
+ `prisma` currently points at a release candidate.
23
+
24
+ The client is generated by the project's own `postinstall` (a root package
25
+ script, which package managers always run) and by `db:generate`; it never
26
+ depends on a dependency's lifecycle script being trusted. */
27
+ export const getPrismaPackageJson = (target, databaseDirectory) => {
28
+ const version = target.line === 'prisma6' ? prismaMongoDBVersion : versions.prisma;
29
+ const dependencies = {
30
+ '@prisma/client': version
31
+ };
32
+ if (target.adapter)
33
+ dependencies[target.adapter.packageName] = version;
34
+ if (target.migration === 'libsql')
35
+ dependencies['@libsql/client'] = versions['@libsql/client'];
36
+ return {
37
+ dependencies,
38
+ devDependencies: { prisma: version },
39
+ scripts: {
40
+ 'db:generate': 'prisma generate',
41
+ 'db:studio': 'prisma studio',
42
+ postinstall: 'prisma generate',
43
+ ...migrationScripts(target, databaseDirectory)
44
+ }
45
+ };
46
+ };
@@ -0,0 +1,22 @@
1
+ import type { AvailablePrismaDialect, DatabaseHost } from '../../types';
2
+ export declare const prismaProviders: readonly ["cockroachdb", "mongodb", "mysql", "postgresql", "sqlite", "sqlserver"];
3
+ export type PrismaProvider = (typeof prismaProviders)[number];
4
+ export type PrismaMigrationStrategy = 'libsql' | 'migrate' | 'push';
5
+ type PrismaAdapter = {
6
+ className: string;
7
+ expression: string;
8
+ packageName: string;
9
+ };
10
+ export type PrismaTarget = {
11
+ adapter: PrismaAdapter | undefined;
12
+ line: 'prisma6' | 'prisma7';
13
+ migration: PrismaMigrationStrategy;
14
+ provider: PrismaProvider;
15
+ supportsJson: boolean;
16
+ };
17
+ export declare const initialPrismaMigrationName = "20260926000000_init";
18
+ export declare const prismaClientDirectory = "src/generated/prisma";
19
+ export declare const getMigrationLockProvider: (provider: PrismaProvider) => "cockroachdb" | "mssql" | "mysql" | "postgresql" | "sqlite" | "mongodb";
20
+ export declare const getPrismaMigrationTemplateName: (provider: PrismaProvider, usesAuth: boolean) => string;
21
+ export declare const getPrismaTarget: (databaseEngine: AvailablePrismaDialect, databaseHost: DatabaseHost) => PrismaTarget;
22
+ export {};
@@ -0,0 +1,91 @@
1
+ /* The `provider` values a Prisma schema's datasource accepts. MariaDB has no
2
+ provider of its own: Prisma drives it as MySQL. */
3
+ export const prismaProviders = [
4
+ 'cockroachdb',
5
+ 'mongodb',
6
+ 'mysql',
7
+ 'postgresql',
8
+ 'sqlite',
9
+ 'sqlserver'
10
+ ];
11
+ const databaseUrl = "getEnv('DATABASE_URL')";
12
+ const pgAdapter = {
13
+ className: 'PrismaPg',
14
+ expression: `new PrismaPg({ connectionString: ${databaseUrl} })`,
15
+ packageName: '@prisma/adapter-pg'
16
+ };
17
+ const mariadbAdapter = {
18
+ className: 'PrismaMariaDb',
19
+ expression: `new PrismaMariaDb(${databaseUrl})`,
20
+ packageName: '@prisma/adapter-mariadb'
21
+ };
22
+ /* A local libSQL `file:` URL needs no token; a Turso database does. */
23
+ const libsqlAdapter = {
24
+ className: 'PrismaLibSql',
25
+ expression: `new PrismaLibSql({ authToken: env.DATABASE_AUTH_TOKEN, url: ${databaseUrl} })`,
26
+ packageName: '@prisma/adapter-libsql'
27
+ };
28
+ const sqlTarget = (provider, adapter) => ({
29
+ adapter,
30
+ line: 'prisma7',
31
+ migration: 'migrate',
32
+ provider,
33
+ supportsJson: provider !== 'sqlserver'
34
+ });
35
+ const getPostgresqlTarget = (databaseHost) => {
36
+ if (databaseHost !== 'neon')
37
+ return sqlTarget('postgresql', pgAdapter);
38
+ return sqlTarget('postgresql', {
39
+ className: 'PrismaNeon',
40
+ expression: `new PrismaNeon({ connectionString: ${databaseUrl} })`,
41
+ packageName: '@prisma/adapter-neon'
42
+ });
43
+ };
44
+ const getMysqlTarget = (databaseHost) => {
45
+ if (databaseHost !== 'planetscale')
46
+ return sqlTarget('mysql', mariadbAdapter);
47
+ return sqlTarget('mysql', {
48
+ className: 'PrismaPlanetScale',
49
+ expression: `new PrismaPlanetScale({ url: ${databaseUrl} })`,
50
+ packageName: '@prisma/adapter-planetscale'
51
+ });
52
+ };
53
+ const engineTargets = {
54
+ mysql: getMysqlTarget,
55
+ postgresql: getPostgresqlTarget,
56
+ cockroachdb: () => sqlTarget('cockroachdb', pgAdapter),
57
+ mariadb: () => sqlTarget('mysql', mariadbAdapter),
58
+ mongodb: () => ({
59
+ adapter: undefined,
60
+ line: 'prisma6',
61
+ migration: 'push',
62
+ provider: 'mongodb',
63
+ supportsJson: true
64
+ }),
65
+ mssql: () => sqlTarget('sqlserver', {
66
+ className: 'PrismaMssql',
67
+ expression: `new PrismaMssql(${databaseUrl})`,
68
+ packageName: '@prisma/adapter-mssql'
69
+ }),
70
+ sqlite: (databaseHost) => ({
71
+ ...sqlTarget('sqlite', libsqlAdapter),
72
+ migration: databaseHost === 'turso' ? 'libsql' : 'migrate'
73
+ })
74
+ };
75
+ /* The initial migration's directory name, in Prisma Migrate's
76
+ `<timestamp>_<name>` format. */
77
+ export const initialPrismaMigrationName = '20260926000000_init';
78
+ /* Where `prisma generate` writes the client, relative to the project root.
79
+ Fixed (independent of --db-dir) so server, handlers and types import it by
80
+ a stable path. It is build output: ignored by git, ESLint and Prettier. */
81
+ export const prismaClientDirectory = 'src/generated/prisma';
82
+ /* The value Prisma Migrate records in migrations/migration_lock.toml. Prisma 7
83
+ names SQL Server `mssql` there, not by its schema provider name. */
84
+ export const getMigrationLockProvider = (provider) => provider === 'sqlserver' ? 'mssql' : provider;
85
+ /* The directory holding the committed initial migration for a provider and
86
+ example table (users when auth is on, count_history otherwise). */
87
+ export const getPrismaMigrationTemplateName = (provider, usesAuth) => `prisma-${provider}-${usesAuth ? 'users' : 'count-history'}`;
88
+ /* Resolves the provider, driver adapter and migration strategy for an
89
+ engine/host pair. Hosts are validated beforehand (validateDatabaseSelection),
90
+ so PlanetScale Postgres is plain Postgres over `pg` here. */
91
+ export const getPrismaTarget = (databaseEngine, databaseHost) => engineTargets[databaseEngine](databaseHost);