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.
- package/CHANGELOG.md +6 -0
- package/README.md +9 -2
- package/changelog.json +10 -0
- package/dist/generators/configurations/generateEslintConfig.js +1 -1
- package/dist/generators/configurations/generatePackageJson.d.ts +2 -1
- package/dist/generators/configurations/generatePackageJson.js +10 -1
- package/dist/generators/db/scaffoldDatabase.js +14 -4
- package/dist/generators/prisma/generatePrismaCode.d.ts +6 -0
- package/dist/generators/prisma/generatePrismaCode.js +148 -0
- package/dist/generators/prisma/generatePrismaConfig.d.ts +8 -0
- package/dist/generators/prisma/generatePrismaConfig.js +68 -0
- package/dist/generators/prisma/generatePrismaSchema.d.ts +9 -0
- package/dist/generators/prisma/generatePrismaSchema.js +80 -0
- package/dist/generators/prisma/prismaPackageJson.d.ts +8 -0
- package/dist/generators/prisma/prismaPackageJson.js +46 -0
- package/dist/generators/prisma/prismaTargets.d.ts +22 -0
- package/dist/generators/prisma/prismaTargets.js +91 -0
- package/dist/generators/prisma/scaffoldPrismaDatabase.d.ts +16 -0
- package/dist/generators/prisma/scaffoldPrismaDatabase.js +136 -0
- package/dist/generators/project/generateDBBlock.js +5 -0
- package/dist/generators/project/generateImportsBlock.js +6 -1
- package/dist/questions/orm.js +5 -2
- package/dist/scaffold.js +4 -4
- package/dist/templates/configurations/.prettierignore +2 -1
- package/dist/templates/configurations/eslint.config.example.mjs +2 -1
- package/dist/templates/db/migrations/prisma-cockroachdb-count-history/20260926000000_init/migration.sql +12 -0
- package/dist/templates/db/migrations/prisma-cockroachdb-count-history/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-cockroachdb-users/20260926000000_init/migration.sql +12 -0
- package/dist/templates/db/migrations/prisma-cockroachdb-users/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-mysql-count-history/20260926000000_init/migration.sql +9 -0
- package/dist/templates/db/migrations/prisma-mysql-count-history/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-mysql-users/20260926000000_init/migration.sql +9 -0
- package/dist/templates/db/migrations/prisma-mysql-users/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-postgresql-count-history/20260926000000_init/migration.sql +12 -0
- package/dist/templates/db/migrations/prisma-postgresql-count-history/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-postgresql-users/20260926000000_init/migration.sql +12 -0
- package/dist/templates/db/migrations/prisma-postgresql-users/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-sqlite-count-history/20260926000000_init/migration.sql +7 -0
- package/dist/templates/db/migrations/prisma-sqlite-count-history/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-sqlite-users/20260926000000_init/migration.sql +7 -0
- package/dist/templates/db/migrations/prisma-sqlite-users/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-sqlserver-count-history/20260926000000_init/migration.sql +28 -0
- package/dist/templates/db/migrations/prisma-sqlserver-count-history/migration_lock.toml +3 -0
- package/dist/templates/db/migrations/prisma-sqlserver-users/20260926000000_init/migration.sql +28 -0
- package/dist/templates/db/migrations/prisma-sqlserver-users/migration_lock.toml +3 -0
- package/dist/templates/git/gitignore +5 -0
- package/dist/typeGuards.d.ts +2 -2
- package/dist/utils/parseCommandLineOptions.js +2 -8
- package/dist/utils/validateDatabaseSelection.js +5 -2
- package/dist/versions.d.ts +9 -0
- package/dist/versions.js +13 -0
- 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`.
|
|
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);
|