@prisma-next/cli 0.5.0-dev.3 → 0.5.0-dev.5
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/dist/agent-skill-mongo.md +63 -31
- package/dist/agent-skill-postgres.md +1 -1
- package/dist/cli.mjs +119 -13
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-TG7rbCWT.mjs → client-CrsnY58k.mjs} +4 -4
- package/dist/{client-TG7rbCWT.mjs.map → client-CrsnY58k.mjs.map} +1 -1
- package/dist/commands/contract-emit.mjs +2 -2
- package/dist/commands/contract-infer.mjs +2 -2
- package/dist/commands/db-init.mjs +7 -7
- package/dist/commands/db-schema.mjs +5 -5
- package/dist/commands/db-sign.mjs +7 -7
- package/dist/commands/db-update.mjs +7 -7
- package/dist/commands/db-verify.mjs +7 -7
- package/dist/commands/migration-apply.mjs +8 -8
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.mjs +5 -5
- package/dist/commands/migration-plan.mjs +6 -6
- package/dist/commands/migration-ref.d.mts +6 -4
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +29 -34
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +1 -1
- package/dist/commands/migration-show.mjs +6 -6
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -2
- package/dist/{config-loader-_W4T21X1.mjs → config-loader-C25b63rJ.mjs} +1 -1
- package/dist/{config-loader-_W4T21X1.mjs.map → config-loader-C25b63rJ.mjs.map} +1 -1
- package/dist/config-loader.mjs +1 -1
- package/dist/contract-emit--feXyNd7.mjs +4 -0
- package/dist/{contract-emit-CNYyzJwF.mjs → contract-emit-NJ01hiiv.mjs} +8 -8
- package/dist/{contract-emit-CNYyzJwF.mjs.map → contract-emit-NJ01hiiv.mjs.map} +1 -1
- package/dist/{contract-emit-CQfj7xJn.mjs → contract-emit-V5SSitUT.mjs} +6 -6
- package/dist/{contract-emit-CQfj7xJn.mjs.map → contract-emit-V5SSitUT.mjs.map} +1 -1
- package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-CAOELa-H.mjs} +1 -1
- package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-CAOELa-H.mjs.map} +1 -1
- package/dist/{contract-infer-BP3DrGgz.mjs → contract-infer-D9cC3rJm.mjs} +4 -4
- package/dist/{contract-infer-BP3DrGgz.mjs.map → contract-infer-D9cC3rJm.mjs.map} +1 -1
- package/dist/exports/control-api.mjs +4 -4
- package/dist/exports/index.mjs +2 -2
- package/dist/exports/init-output.d.mts +39 -0
- package/dist/exports/init-output.d.mts.map +1 -0
- package/dist/exports/init-output.mjs +3 -0
- package/dist/{extract-operation-statements-DZUJNmL3.mjs → extract-operation-statements-DsFfxXVZ.mjs} +2 -2
- package/dist/{extract-operation-statements-DZUJNmL3.mjs.map → extract-operation-statements-DsFfxXVZ.mjs.map} +1 -1
- package/dist/{extract-sql-ddl-DDMX-9mz.mjs → extract-sql-ddl-D9UbZDyz.mjs} +1 -1
- package/dist/{extract-sql-ddl-DDMX-9mz.mjs.map → extract-sql-ddl-D9UbZDyz.mjs.map} +1 -1
- package/dist/{framework-components-DfZKQBQ2.mjs → framework-components-Cr--XBKy.mjs} +2 -2
- package/dist/{framework-components-DfZKQBQ2.mjs.map → framework-components-Cr--XBKy.mjs.map} +1 -1
- package/dist/init-C5220SY9.mjs +2062 -0
- package/dist/init-C5220SY9.mjs.map +1 -0
- package/dist/{inspect-live-schema-DWzf4Q_m.mjs → inspect-live-schema-yrHAvG71.mjs} +6 -6
- package/dist/{inspect-live-schema-DWzf4Q_m.mjs.map → inspect-live-schema-yrHAvG71.mjs.map} +1 -1
- package/dist/migration-cli.mjs +1 -1
- package/dist/{migration-command-scaffold-CLMD302g.mjs → migration-command-scaffold-B3B09et6.mjs} +6 -6
- package/dist/{migration-command-scaffold-CLMD302g.mjs.map → migration-command-scaffold-B3B09et6.mjs.map} +1 -1
- package/dist/{migration-status-B0HLF7So.mjs → migration-status-DUMiH8_G.mjs} +12 -14
- package/dist/{migration-status-B0HLF7So.mjs.map → migration-status-DUMiH8_G.mjs.map} +1 -1
- package/dist/{migrations-B0dOQlk0.mjs → migrations-Bo5WtTla.mjs} +2 -2
- package/dist/{migrations-B0dOQlk0.mjs.map → migrations-Bo5WtTla.mjs.map} +1 -1
- package/dist/output-BpcQrnnq.mjs +103 -0
- package/dist/output-BpcQrnnq.mjs.map +1 -0
- package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-DvQWB1nK.mjs} +1 -1
- package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-DvQWB1nK.mjs.map} +1 -1
- package/dist/quick-reference-mongo.md +34 -13
- package/dist/quick-reference-postgres.md +11 -9
- package/dist/{result-handler-CIyu0Pdt.mjs → result-handler-Ba3zWQsI.mjs} +5 -78
- package/dist/result-handler-Ba3zWQsI.mjs.map +1 -0
- package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-C3ZLwQxK.mjs} +76 -2
- package/dist/terminal-ui-C3ZLwQxK.mjs.map +1 -0
- package/dist/{validate-contract-deps-esa-VQ0h.mjs → validate-contract-deps-B_Cs29TL.mjs} +1 -1
- package/dist/{validate-contract-deps-esa-VQ0h.mjs.map → validate-contract-deps-B_Cs29TL.mjs.map} +1 -1
- package/dist/{verify-BxiVp50b.mjs → verify-Bkycc-Tf.mjs} +2 -2
- package/dist/{verify-BxiVp50b.mjs.map → verify-Bkycc-Tf.mjs.map} +1 -1
- package/package.json +21 -16
- package/src/commands/init/detect-pnpm-catalog.ts +141 -0
- package/src/commands/init/errors.ts +254 -0
- package/src/commands/init/exit-codes.ts +62 -0
- package/src/commands/init/hygiene-gitattributes.ts +97 -0
- package/src/commands/init/hygiene-gitignore.ts +48 -0
- package/src/commands/init/hygiene-package-scripts.ts +91 -0
- package/src/commands/init/index.ts +112 -7
- package/src/commands/init/init.ts +766 -144
- package/src/commands/init/inputs.ts +421 -0
- package/src/commands/init/output.ts +147 -0
- package/src/commands/init/probe-db.ts +308 -0
- package/src/commands/init/reinit-cleanup.ts +83 -0
- package/src/commands/init/templates/agent-skill-mongo.md +63 -31
- package/src/commands/init/templates/agent-skill-postgres.md +1 -1
- package/src/commands/init/templates/agent-skill.ts +25 -3
- package/src/commands/init/templates/code-templates.ts +125 -32
- package/src/commands/init/templates/env.ts +80 -0
- package/src/commands/init/templates/quick-reference-mongo.md +34 -13
- package/src/commands/init/templates/quick-reference-postgres.md +11 -9
- package/src/commands/init/templates/quick-reference.ts +42 -3
- package/src/commands/init/templates/tsconfig.ts +167 -5
- package/src/commands/migration-apply.ts +3 -3
- package/src/commands/migration-ref.ts +32 -47
- package/src/commands/migration-status.ts +16 -21
- package/src/exports/init-output.ts +10 -0
- package/src/utils/command-helpers.ts +3 -3
- package/dist/contract-emit-fhNwwhkQ.mjs +0 -4
- package/dist/init-CQfo_4Ro.mjs +0 -430
- package/dist/init-CQfo_4Ro.mjs.map +0 -1
- package/dist/result-handler-CIyu0Pdt.mjs.map +0 -1
- package/dist/terminal-ui-C5k88MmW.mjs.map +0 -1
- /package/dist/{cli-errors-C0JhVj0c.d.mts → cli-errors-BFYgBH3L.d.mts} +0 -0
- /package/dist/{cli-errors-DHq6GQGu.mjs → cli-errors-Cd79vmTH.mjs} +0 -0
|
@@ -23,6 +23,90 @@ export function starterSchema(target: TargetId, authoring: AuthoringId): string
|
|
|
23
23
|
return target === 'mongo' ? starterSchemaPslMongo() : starterSchemaPslPostgres();
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Renders a short authoring-appropriate schema sample (FR5.1) for embedding
|
|
28
|
+
* in `prisma-next.md`. Returns a complete fenced markdown code block.
|
|
29
|
+
*
|
|
30
|
+
* The sample intentionally shows just one model: it's illustrative, not
|
|
31
|
+
* a substitute for the full scaffolded contract file. The TS samples use
|
|
32
|
+
* the same outer shape as `starterSchemaTs*` (FR5.3) so a user reading
|
|
33
|
+
* the doc and the file side-by-side sees the same structure.
|
|
34
|
+
*/
|
|
35
|
+
export function schemaSample(target: TargetId, authoring: AuthoringId): string {
|
|
36
|
+
if (authoring === 'typescript') {
|
|
37
|
+
return target === 'mongo' ? schemaSampleTsMongo() : schemaSampleTsPostgres();
|
|
38
|
+
}
|
|
39
|
+
return target === 'mongo' ? schemaSamplePslMongo() : schemaSamplePslPostgres();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function schemaSamplePslPostgres(): string {
|
|
43
|
+
return `\`\`\`prisma
|
|
44
|
+
model User {
|
|
45
|
+
id Int @id @default(autoincrement())
|
|
46
|
+
email String @unique
|
|
47
|
+
name String?
|
|
48
|
+
}
|
|
49
|
+
\`\`\``;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function schemaSamplePslMongo(): string {
|
|
53
|
+
return `\`\`\`prisma
|
|
54
|
+
model User {
|
|
55
|
+
id ObjectId @id @map("_id")
|
|
56
|
+
email String @unique
|
|
57
|
+
name String?
|
|
58
|
+
@@map("users")
|
|
59
|
+
}
|
|
60
|
+
\`\`\``;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function schemaSampleTsPostgres(): string {
|
|
64
|
+
return `\`\`\`typescript
|
|
65
|
+
import sqlFamily from '@prisma-next/family-sql/pack';
|
|
66
|
+
import { defineContract } from '@prisma-next/sql-contract-ts/contract-builder';
|
|
67
|
+
import postgresPack from '@prisma-next/target-postgres/pack';
|
|
68
|
+
|
|
69
|
+
export const contract = defineContract(
|
|
70
|
+
{ family: sqlFamily, target: postgresPack },
|
|
71
|
+
({ field, model }) => ({
|
|
72
|
+
models: {
|
|
73
|
+
User: model('User', {
|
|
74
|
+
fields: {
|
|
75
|
+
id: field.id.uuidv7(),
|
|
76
|
+
email: field.text().unique(),
|
|
77
|
+
name: field.text().optional(),
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
\`\`\``;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function schemaSampleTsMongo(): string {
|
|
87
|
+
return `\`\`\`typescript
|
|
88
|
+
import mongoFamily from '@prisma-next/family-mongo/pack';
|
|
89
|
+
import { defineContract } from '@prisma-next/mongo-contract-ts/contract-builder';
|
|
90
|
+
import mongoTarget from '@prisma-next/target-mongo/pack';
|
|
91
|
+
|
|
92
|
+
export const contract = defineContract(
|
|
93
|
+
{ family: mongoFamily, target: mongoTarget },
|
|
94
|
+
({ field, model }) => ({
|
|
95
|
+
models: {
|
|
96
|
+
User: model('User', {
|
|
97
|
+
collection: 'users',
|
|
98
|
+
fields: {
|
|
99
|
+
_id: field.objectId(),
|
|
100
|
+
email: field.string(),
|
|
101
|
+
name: field.string().optional(),
|
|
102
|
+
},
|
|
103
|
+
}),
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
\`\`\``;
|
|
108
|
+
}
|
|
109
|
+
|
|
26
110
|
function starterSchemaPslPostgres(): string {
|
|
27
111
|
return `model User {
|
|
28
112
|
id Int @id @default(autoincrement())
|
|
@@ -79,8 +163,9 @@ export const contract = defineContract(
|
|
|
79
163
|
name: field.text().optional(),
|
|
80
164
|
createdAt: field.createdAt(),
|
|
81
165
|
},
|
|
82
|
-
|
|
83
|
-
|
|
166
|
+
relations: {
|
|
167
|
+
posts: rel.hasMany('Post', { by: 'authorId' }),
|
|
168
|
+
},
|
|
84
169
|
}),
|
|
85
170
|
|
|
86
171
|
Post: model('Post', {
|
|
@@ -91,8 +176,9 @@ export const contract = defineContract(
|
|
|
91
176
|
authorId: field.uuid(),
|
|
92
177
|
createdAt: field.createdAt(),
|
|
93
178
|
},
|
|
94
|
-
|
|
95
|
-
|
|
179
|
+
relations: {
|
|
180
|
+
author: rel.belongsTo('User', { from: 'authorId', to: 'id' }),
|
|
181
|
+
},
|
|
96
182
|
}),
|
|
97
183
|
},
|
|
98
184
|
}),
|
|
@@ -102,36 +188,40 @@ export const contract = defineContract(
|
|
|
102
188
|
|
|
103
189
|
function starterSchemaTsMongo(): string {
|
|
104
190
|
return `import mongoFamily from '@prisma-next/family-mongo/pack';
|
|
105
|
-
import { defineContract
|
|
191
|
+
import { defineContract } from '@prisma-next/mongo-contract-ts/contract-builder';
|
|
106
192
|
import mongoTarget from '@prisma-next/target-mongo/pack';
|
|
107
193
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
authorId: field.objectId(),
|
|
124
|
-
},
|
|
125
|
-
relations: {
|
|
126
|
-
author: rel.belongsTo(User, { from: 'authorId', to: User.ref('_id') }),
|
|
127
|
-
},
|
|
128
|
-
});
|
|
194
|
+
export const contract = defineContract(
|
|
195
|
+
{ family: mongoFamily, target: mongoTarget },
|
|
196
|
+
({ field, model, rel }) => ({
|
|
197
|
+
models: {
|
|
198
|
+
User: model('User', {
|
|
199
|
+
collection: 'users',
|
|
200
|
+
fields: {
|
|
201
|
+
_id: field.objectId(),
|
|
202
|
+
email: field.string(),
|
|
203
|
+
name: field.string().optional(),
|
|
204
|
+
},
|
|
205
|
+
relations: {
|
|
206
|
+
posts: rel.hasMany('Post', { from: '_id', to: 'authorId' }),
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
129
209
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
210
|
+
Post: model('Post', {
|
|
211
|
+
collection: 'posts',
|
|
212
|
+
fields: {
|
|
213
|
+
_id: field.objectId(),
|
|
214
|
+
title: field.string(),
|
|
215
|
+
content: field.string().optional(),
|
|
216
|
+
authorId: field.objectId(),
|
|
217
|
+
},
|
|
218
|
+
relations: {
|
|
219
|
+
author: rel.belongsTo('User', { from: 'authorId', to: '_id' }),
|
|
220
|
+
},
|
|
221
|
+
}),
|
|
222
|
+
},
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
135
225
|
`;
|
|
136
226
|
}
|
|
137
227
|
|
|
@@ -163,6 +253,9 @@ export const db = postgres<Contract>({ contractJson });
|
|
|
163
253
|
import type { Contract } from './contract.d';
|
|
164
254
|
import contractJson from './contract.json' with { type: 'json' };
|
|
165
255
|
|
|
166
|
-
export const db = mongo<Contract>({
|
|
256
|
+
export const db = mongo<Contract>({
|
|
257
|
+
contractJson,
|
|
258
|
+
url: process.env['DATABASE_URL']!,
|
|
259
|
+
});
|
|
167
260
|
`;
|
|
168
261
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { TargetId } from './code-templates';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The minimum supported server version for each target (FR8.1). The
|
|
5
|
+
* authoritative source of truth is each target package's
|
|
6
|
+
* `package.json#prismaNext.minServerVersion` field — this module
|
|
7
|
+
* mirrors those values and a workspace-level test asserts the two
|
|
8
|
+
* never drift (`templates/tsconfig-env.test.ts`).
|
|
9
|
+
*
|
|
10
|
+
* Bumping a value here in isolation is **not** safe: edit the
|
|
11
|
+
* corresponding target package's `package.json` first, then mirror
|
|
12
|
+
* here. The scaffold's `.env.example` (FR3.1, FR8.2) and the
|
|
13
|
+
* "Requirements" section of `prisma-next.md` both read from this
|
|
14
|
+
* constant, so a stale value lies to every freshly initialised user.
|
|
15
|
+
*/
|
|
16
|
+
export const MIN_SERVER_VERSION: Record<TargetId, string> = {
|
|
17
|
+
postgres: '14',
|
|
18
|
+
mongo: '6.0',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const TARGET_LABEL: Record<TargetId, string> = {
|
|
22
|
+
postgres: 'PostgreSQL',
|
|
23
|
+
mongo: 'MongoDB',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Renders the placeholder body shared by `.env` and `.env.example`:
|
|
28
|
+
* the target-specific connection-string requirement comments and the
|
|
29
|
+
* commented-shape `DATABASE_URL` line. The output is identical for both
|
|
30
|
+
* authoring styles — the env file is orthogonal to PSL vs TS schema
|
|
31
|
+
* authoring.
|
|
32
|
+
*/
|
|
33
|
+
function envPlaceholderBody(target: TargetId): string {
|
|
34
|
+
const label = TARGET_LABEL[target];
|
|
35
|
+
const minVersion = MIN_SERVER_VERSION[target];
|
|
36
|
+
const lines: string[] = [];
|
|
37
|
+
lines.push(`# Connection string for ${label}.`);
|
|
38
|
+
lines.push(`# Requires ${label} >= ${minVersion}.`);
|
|
39
|
+
lines.push('');
|
|
40
|
+
if (target === 'postgres') {
|
|
41
|
+
lines.push('DATABASE_URL="postgresql://user:password@localhost:5432/mydb"');
|
|
42
|
+
} else {
|
|
43
|
+
lines.push('DATABASE_URL="mongodb://localhost:27017/mydb"');
|
|
44
|
+
}
|
|
45
|
+
lines.push('');
|
|
46
|
+
return lines.join('\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Renders the `.env.example` content for a given target (FR3.1):
|
|
51
|
+
*
|
|
52
|
+
* - Carries a "Copy this file to `.env`…" intro that only makes sense
|
|
53
|
+
* for the example file (the real `.env` is the destination of that
|
|
54
|
+
* copy and so does not get the same intro).
|
|
55
|
+
* - Documents the `DATABASE_URL` placeholder in the target's native URL
|
|
56
|
+
* shape (Postgres: standard `postgresql://`, Mongo: `mongodb://` plus
|
|
57
|
+
* a `mydb` database segment so the lazy facade has a `dbName`).
|
|
58
|
+
* - Carries a `# Requires <db> >= <version>` comment so a fresh user
|
|
59
|
+
* knows the minimum supported server before they first try to
|
|
60
|
+
* connect (FR8.2).
|
|
61
|
+
*/
|
|
62
|
+
export function envExampleContent(target: TargetId): string {
|
|
63
|
+
const lines: string[] = [];
|
|
64
|
+
lines.push(
|
|
65
|
+
'# Copy this file to `.env` and replace the placeholder with your real connection string.',
|
|
66
|
+
);
|
|
67
|
+
lines.push(envPlaceholderBody(target));
|
|
68
|
+
return lines.join('\n');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Renders the initial `.env` content for `--write-env` / interactive
|
|
73
|
+
* opt-in (FR3.2). Same placeholder body as `.env.example`, **without**
|
|
74
|
+
* the example file's "Copy this file to `.env`…" intro: the real `.env`
|
|
75
|
+
* is the destination of that copy, so the line would lie. Writing this
|
|
76
|
+
* file is gitignored (FR3.3 ensures `.env` lands in `.gitignore`).
|
|
77
|
+
*/
|
|
78
|
+
export function envFileContent(target: TargetId): string {
|
|
79
|
+
return envPlaceholderBody(target);
|
|
80
|
+
}
|
|
@@ -4,35 +4,29 @@ Prisma Next lets you query your database in simple, easy-to-read TypeScript. Def
|
|
|
4
4
|
|
|
5
5
|
This project is set up for MongoDB. Prisma Next also supports other databases.
|
|
6
6
|
|
|
7
|
+
{{requirements}}
|
|
8
|
+
|
|
7
9
|
## Your data contract
|
|
8
10
|
|
|
9
11
|
Your data contract is the heart of your application. It lives at [`{{schemaPath}}`]({{schemaPath}}) and describes your models:
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
model User {
|
|
13
|
-
id ObjectId @id @map("_id")
|
|
14
|
-
email String @unique
|
|
15
|
-
name String?
|
|
16
|
-
posts Post[]
|
|
17
|
-
@@map("users")
|
|
18
|
-
}
|
|
19
|
-
```
|
|
13
|
+
{{schemaSample}}
|
|
20
14
|
|
|
21
15
|
Every model you define in your contract can be queried from your app. Your editor will autocomplete the query methods and show you what type each field is:
|
|
22
16
|
|
|
23
17
|
```typescript
|
|
24
18
|
import { db } from '{{dbImportPath}}';
|
|
25
19
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const user = await client.orm.User
|
|
20
|
+
const user = await db.orm.users
|
|
29
21
|
.where({ email: 'alice@example.com' })
|
|
30
22
|
.first();
|
|
31
23
|
|
|
32
24
|
// Your editor will show the type of user as
|
|
33
|
-
// {
|
|
25
|
+
// { _id: ObjectId; email: string; name: string | null; posts: Post[] } | null
|
|
34
26
|
```
|
|
35
27
|
|
|
28
|
+
`db` connects to MongoDB lazily on the first query, so there is no manual `connect(...)` step in your application code. Call `await db.close()` if you need to release the underlying connection (typically only in tests or short-lived scripts). After `close()` the client is single-shot — any further query, `connect()`, or `runtime()` call rejects with `"Mongo client is closed"`. Construct a new `mongo({...})` if you need to use it again.
|
|
29
|
+
|
|
36
30
|
Your contract has two companion files in the same directory:
|
|
37
31
|
|
|
38
32
|
- **`contract.json`** — this tells your application what models exist, just like `package-lock.json` tells your package manager what dependencies your project has
|
|
@@ -91,3 +85,30 @@ You can customize how your environment variables are loaded by changing or remov
|
|
|
91
85
|
1. Edit [`{{schemaPath}}`]({{schemaPath}}) to add or change models.
|
|
92
86
|
2. Run `{{pkgRun}} contract emit` to regenerate the contract.
|
|
93
87
|
3. Query your models — your IDE will autocomplete everything.
|
|
88
|
+
|
|
89
|
+
## Transactions and change streams (Mongo)
|
|
90
|
+
|
|
91
|
+
Multi-document transactions and change streams require MongoDB to run as a **replica set** — even single-node setups for development. A standalone `mongod` will reject `withTransaction()` at runtime. For local development you have a few options:
|
|
92
|
+
|
|
93
|
+
- **Docker Compose:** start `mongo` with `--replSet rs0` and run `rs.initiate()` once.
|
|
94
|
+
- **`mongodb-memory-server`:** use `MongoMemoryReplSet` instead of `MongoMemoryServer` in tests.
|
|
95
|
+
- **MongoDB Atlas:** every Atlas cluster is already a replica set.
|
|
96
|
+
|
|
97
|
+
The transaction API (`db.transaction(...)`) is on the roadmap and tracked under [TML-2313](https://linear.app/prisma-company/issue/TML-2313/mongo-dev-replica-set-story-is-missing-transactions-change-streams). Prisma Next's Mongo facade does not expose it yet — until that ticket lands, drive transactions yourself with a raw `MongoClient` using the escape hatch in the next section.
|
|
98
|
+
|
|
99
|
+
## Escape hatches
|
|
100
|
+
|
|
101
|
+
The ORM covers the common cases. For the rest, two escape hatches are designed in:
|
|
102
|
+
|
|
103
|
+
- **Typed raw aggregations — `db.query`.** The facade exposes `db.query`, a typed builder for aggregation pipelines that runs through the same runtime + middleware + codec stack as `db.orm`. Reach for it when the ORM can't express a `$lookup`/`$facet`/`$graphLookup`/window-function pipeline.
|
|
104
|
+
- **Direct `mongodb` driver control — `mongoClient` binding.** Construct your own `MongoClient` and pass it to `mongo({ mongoClient, dbName, contractJson })`. Your code keeps the `MongoClient` reference and uses it directly (transactions, change streams, sessions, anything Prisma Next doesn't surface yet); the same `db` object continues to give you the typed ORM.
|
|
105
|
+
|
|
106
|
+
`db.runtime()` is **not** the escape hatch — it returns the internal executor (`MongoRuntime`), not a `mongodb` `MongoClient` or `Db`. Use `db.query` for raw aggregations and the `mongoClient` binding for direct driver control.
|
|
107
|
+
|
|
108
|
+
## Monorepo notes (pnpm workspaces)
|
|
109
|
+
|
|
110
|
+
If this project lives inside a pnpm workspace, a few things are worth knowing:
|
|
111
|
+
|
|
112
|
+
- **Catalogs.** When the workspace's `pnpm-workspace.yaml` defines a `catalogs` entry for `prisma-next` or `@prisma-next/mongo`, pnpm uses the catalog version everywhere — `init` does too. If you wanted the published `latest` instead, update or remove the catalog entry, then re-run `pnpm install`.
|
|
113
|
+
- **`pnpm dlx`.** `pnpm dlx prisma-next@latest init …` works in any directory. Inside a workspace, pnpm still resolves dependencies through the workspace's catalog/overrides rather than the registry; expect the installed Prisma Next packages to reflect the workspace's catalog rather than `latest`.
|
|
114
|
+
- **`pnpm` → `npm` fallback.** If `pnpm` ever fails to install Prisma Next with a `workspace:*` or `catalog:` resolution error (a leak in a published artefact), `init` falls back to `npm install` and surfaces a warning. Once the offending package republishes a clean version you can switch back with `pnpm install`.
|
|
@@ -4,19 +4,13 @@ Prisma Next lets you query your database in simple, easy-to-read TypeScript. Def
|
|
|
4
4
|
|
|
5
5
|
This project is set up for PostgreSQL. Prisma Next also supports other databases.
|
|
6
6
|
|
|
7
|
+
{{requirements}}
|
|
8
|
+
|
|
7
9
|
## Your data contract
|
|
8
10
|
|
|
9
11
|
Your data contract is the heart of your application. It lives at [`{{schemaPath}}`]({{schemaPath}}) and describes your models:
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
model User {
|
|
13
|
-
id Int @id @default(autoincrement())
|
|
14
|
-
email String @unique
|
|
15
|
-
name String?
|
|
16
|
-
posts Post[]
|
|
17
|
-
createdAt DateTime @default(now())
|
|
18
|
-
}
|
|
19
|
-
```
|
|
13
|
+
{{schemaSample}}
|
|
20
14
|
|
|
21
15
|
Every model you define in your contract can be queried from your app. Your editor will autocomplete the query methods and show you what type each model field is:
|
|
22
16
|
|
|
@@ -89,3 +83,11 @@ You can customize how your environment variables are loaded by changing or remov
|
|
|
89
83
|
1. Edit [`{{schemaPath}}`]({{schemaPath}}) to add or change models.
|
|
90
84
|
2. Run `{{pkgRun}} contract emit` to regenerate the contract.
|
|
91
85
|
3. Query your models — your IDE will autocomplete everything.
|
|
86
|
+
|
|
87
|
+
## Monorepo notes (pnpm workspaces)
|
|
88
|
+
|
|
89
|
+
If this project lives inside a pnpm workspace, a few things are worth knowing:
|
|
90
|
+
|
|
91
|
+
- **Catalogs.** When the workspace's `pnpm-workspace.yaml` defines a `catalogs` entry for `prisma-next` or `@prisma-next/postgres`, pnpm uses the catalog version everywhere — `init` does too. If you wanted the published `latest` instead, update or remove the catalog entry, then re-run `pnpm install`.
|
|
92
|
+
- **`pnpm dlx`.** `pnpm dlx prisma-next@latest init …` works in any directory. Inside a workspace, pnpm still resolves dependencies through the workspace's catalog/overrides rather than the registry; expect the installed Prisma Next packages to reflect the workspace's catalog rather than `latest`.
|
|
93
|
+
- **`pnpm` → `npm` fallback.** If `pnpm` ever fails to install Prisma Next with a `workspace:*` or `catalog:` resolution error (a leak in a published artefact), `init` falls back to `npm install` and surfaces a warning. Once the offending package republishes a clean version you can switch back with `pnpm install`.
|
|
@@ -1,19 +1,58 @@
|
|
|
1
1
|
import { dirname } from 'pathe';
|
|
2
|
-
import type
|
|
2
|
+
import { type AuthoringId, schemaSample, type TargetId } from './code-templates';
|
|
3
|
+
import { MIN_SERVER_VERSION, TARGET_LABEL } from './env';
|
|
3
4
|
import { renderTemplate } from './render';
|
|
4
5
|
|
|
5
|
-
export const variables = [
|
|
6
|
+
export const variables = [
|
|
7
|
+
'schemaPath',
|
|
8
|
+
'schemaDir',
|
|
9
|
+
'dbImportPath',
|
|
10
|
+
'pkgRun',
|
|
11
|
+
'schemaSample',
|
|
12
|
+
'requirements',
|
|
13
|
+
] as const;
|
|
6
14
|
|
|
7
15
|
type TemplateVars = Record<(typeof variables)[number], string>;
|
|
8
16
|
|
|
9
|
-
export function quickReferenceMd(
|
|
17
|
+
export function quickReferenceMd(
|
|
18
|
+
target: TargetId,
|
|
19
|
+
authoring: AuthoringId,
|
|
20
|
+
schemaPath: string,
|
|
21
|
+
pkgRun: string,
|
|
22
|
+
): string {
|
|
10
23
|
const schemaDir = dirname(schemaPath);
|
|
11
24
|
const vars: TemplateVars = {
|
|
12
25
|
schemaPath,
|
|
13
26
|
schemaDir,
|
|
14
27
|
dbImportPath: `./${schemaDir}/db`,
|
|
15
28
|
pkgRun,
|
|
29
|
+
schemaSample: schemaSample(target, authoring),
|
|
30
|
+
requirements: requirementsBlock(target),
|
|
16
31
|
};
|
|
17
32
|
const templateFile = `quick-reference-${target}.md`;
|
|
18
33
|
return renderTemplate(templateFile, variables, vars);
|
|
19
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Renders the FR8.2 "Requirements" block injected into `prisma-next.md`
|
|
38
|
+
* (the user-facing quick reference). Sources the minimum server
|
|
39
|
+
* version from `MIN_SERVER_VERSION` — itself mirrored from each
|
|
40
|
+
* target package's `package.json#prismaNext.minServerVersion`
|
|
41
|
+
* (FR8.1).
|
|
42
|
+
*
|
|
43
|
+
* The verification command is target-specific — Postgres scaffolds
|
|
44
|
+
* shouldn't ship Mongo's `db.runCommand` (and vice versa) just because
|
|
45
|
+
* we couldn't be bothered to branch.
|
|
46
|
+
*/
|
|
47
|
+
function requirementsBlock(target: TargetId): string {
|
|
48
|
+
const label = TARGET_LABEL[target];
|
|
49
|
+
const minVersion = MIN_SERVER_VERSION[target];
|
|
50
|
+
const verifyCommand =
|
|
51
|
+
target === 'postgres' ? '`SELECT version()`' : '`db.runCommand({ buildInfo: 1 })`';
|
|
52
|
+
return [
|
|
53
|
+
'## Requirements',
|
|
54
|
+
'',
|
|
55
|
+
`- **${label} ${minVersion} or newer.** Older servers are not supported. Run ${verifyCommand} against your server to verify.`,
|
|
56
|
+
'- The CLI never connects to your database without explicit consent. Pass `--probe-db` to `prisma-next init` if you want `init` to verify the server version itself.',
|
|
57
|
+
].join('\n');
|
|
58
|
+
}
|
|
@@ -1,15 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyEdits,
|
|
3
|
+
modify,
|
|
4
|
+
type ParseError,
|
|
5
|
+
parse as parseJsonc,
|
|
6
|
+
printParseErrorCode,
|
|
7
|
+
} from 'jsonc-parser';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Compiler options the scaffolded `prisma-next.config.ts` and `db.ts` need
|
|
11
|
+
* to typecheck:
|
|
12
|
+
*
|
|
13
|
+
* - `module: 'preserve'` + `moduleResolution: 'bundler'` align with how
|
|
14
|
+
* modern bundlers (and `tsdown`) consume our facade packages.
|
|
15
|
+
* - `resolveJsonModule` lets `db.ts` import `contract.json with { type:
|
|
16
|
+
* 'json' }` — the runtime path the facades document (FR4).
|
|
17
|
+
*
|
|
18
|
+
* `types: ['node']` is FR2.2 territory and lives in
|
|
19
|
+
* `REQUIRED_COMPILER_OPTIONS_TYPES` because TS only honours an _array_
|
|
20
|
+
* here, and a string-keyed merge would clobber any user-specified entries.
|
|
21
|
+
* Merge handling preserves any extra `types` the user added.
|
|
22
|
+
*/
|
|
1
23
|
export const REQUIRED_COMPILER_OPTIONS: Record<string, string | boolean> = {
|
|
2
24
|
module: 'preserve',
|
|
3
25
|
moduleResolution: 'bundler',
|
|
4
26
|
resolveJsonModule: true,
|
|
5
27
|
};
|
|
6
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Types that must be present in `compilerOptions.types` for the scaffold
|
|
31
|
+
* to typecheck. With `moduleResolution: 'bundler'`, TypeScript does not
|
|
32
|
+
* implicitly include all `@types/*` packages — `process.env` only resolves
|
|
33
|
+
* when `node` is in this array (or `types` is omitted, but then any other
|
|
34
|
+
* type listed here would force the same behaviour). Listing `node`
|
|
35
|
+
* explicitly is the documented escape hatch (FR2.2).
|
|
36
|
+
*/
|
|
37
|
+
export const REQUIRED_COMPILER_OPTIONS_TYPES: readonly string[] = ['node'];
|
|
38
|
+
|
|
7
39
|
export function defaultTsConfig(): string {
|
|
8
40
|
return JSON.stringify(
|
|
9
41
|
{
|
|
10
42
|
compilerOptions: {
|
|
11
43
|
target: 'ES2022',
|
|
12
44
|
...REQUIRED_COMPILER_OPTIONS,
|
|
45
|
+
types: [...REQUIRED_COMPILER_OPTIONS_TYPES],
|
|
13
46
|
strict: true,
|
|
14
47
|
skipLibCheck: true,
|
|
15
48
|
esModuleInterop: true,
|
|
@@ -22,14 +55,143 @@ export function defaultTsConfig(): string {
|
|
|
22
55
|
);
|
|
23
56
|
}
|
|
24
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Thrown by `mergeTsConfig` when the user's existing `tsconfig.json` is
|
|
60
|
+
* not parseable as JSONC (TypeScript's actual configured dialect — see
|
|
61
|
+
* FR6.1). Carries the raw parse errors so the caller can render an
|
|
62
|
+
* actionable, location-aware message.
|
|
63
|
+
*
|
|
64
|
+
* `runInit` catches this exception during the precondition phase and
|
|
65
|
+
* maps it to a `CliStructuredError(5011)` so the user's working tree
|
|
66
|
+
* stays byte-identical when init bails (FR6.2 / NFR3).
|
|
67
|
+
*/
|
|
68
|
+
export class TsConfigParseError extends Error {
|
|
69
|
+
readonly errors: readonly ParseError[];
|
|
70
|
+
|
|
71
|
+
constructor(errors: readonly ParseError[]) {
|
|
72
|
+
super(formatTsConfigParseErrors(errors));
|
|
73
|
+
this.errors = errors;
|
|
74
|
+
this.name = 'TsConfigParseError';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function formatTsConfigParseErrors(errors: readonly ParseError[]): string {
|
|
79
|
+
if (errors.length === 0) {
|
|
80
|
+
return 'tsconfig.json is empty or not an object';
|
|
81
|
+
}
|
|
82
|
+
return errors.map((e) => `${printParseErrorCode(e.error)} at offset ${e.offset}`).join('; ');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Merges the required compiler options into an existing `tsconfig.json`.
|
|
87
|
+
*
|
|
88
|
+
* Parsing is delegated to `jsonc-parser` so JSONC inputs (comments,
|
|
89
|
+
* trailing commas) — TypeScript's real configuration dialect — survive
|
|
90
|
+
* unchanged: edits are applied as text patches via `modify` /
|
|
91
|
+
* `applyEdits`, preserving the user's formatting, key ordering, and
|
|
92
|
+
* comments wherever the touched paths permit (FR6.1, AC "Hostile
|
|
93
|
+
* inputs").
|
|
94
|
+
*
|
|
95
|
+
* Throws `TsConfigParseError` when the input is not parseable as JSONC.
|
|
96
|
+
* The caller must catch this and surface a structured error before
|
|
97
|
+
* writing any scaffold files (FR6.2 atomicity).
|
|
98
|
+
*/
|
|
25
99
|
export function mergeTsConfig(existing: string): string {
|
|
26
|
-
const config =
|
|
27
|
-
const compilerOptions = (config['compilerOptions'] ?? {}) as Record<string, unknown>;
|
|
100
|
+
const { config } = parseTsConfigText(existing);
|
|
28
101
|
|
|
102
|
+
// Match the indentation / line-ending style of the existing file so
|
|
103
|
+
// the merged output diffs cleanly against it. `jsonc-parser` uses
|
|
104
|
+
// these only when it has to insert a brand-new object node;
|
|
105
|
+
// existing-key edits replace the value in place.
|
|
106
|
+
const formattingOptions = {
|
|
107
|
+
tabSize: detectIndent(existing),
|
|
108
|
+
insertSpaces: true,
|
|
109
|
+
eol: existing.includes('\r\n') ? '\r\n' : '\n',
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
let result = existing;
|
|
29
113
|
for (const [key, value] of Object.entries(REQUIRED_COMPILER_OPTIONS)) {
|
|
30
|
-
compilerOptions
|
|
114
|
+
const edits = modify(result, ['compilerOptions', key], value, { formattingOptions });
|
|
115
|
+
result = applyEdits(result, edits);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const existingTypes = (config['compilerOptions'] as Record<string, unknown> | undefined)?.[
|
|
119
|
+
'types'
|
|
120
|
+
];
|
|
121
|
+
const mergedTypes = mergeTypesArray(existingTypes);
|
|
122
|
+
const typesEdits = modify(result, ['compilerOptions', 'types'], mergedTypes, {
|
|
123
|
+
formattingOptions,
|
|
124
|
+
});
|
|
125
|
+
result = applyEdits(result, typesEdits);
|
|
126
|
+
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Parses an existing `tsconfig.json` (JSONC) and returns the structured
|
|
132
|
+
* config alongside any non-fatal parse warnings. Throws
|
|
133
|
+
* `TsConfigParseError` if the input cannot be parsed at all or does
|
|
134
|
+
* not resolve to a JSON object — both cases mean we cannot safely
|
|
135
|
+
* apply edits.
|
|
136
|
+
*
|
|
137
|
+
* Exposed independently so callers (notably `runInit`'s precondition
|
|
138
|
+
* gate) can validate the file *before* any scaffold file is written.
|
|
139
|
+
*/
|
|
140
|
+
export function parseTsConfigText(text: string): {
|
|
141
|
+
readonly config: Record<string, unknown>;
|
|
142
|
+
} {
|
|
143
|
+
const errors: ParseError[] = [];
|
|
144
|
+
const value = parseJsonc(text, errors, {
|
|
145
|
+
allowTrailingComma: true,
|
|
146
|
+
disallowComments: false,
|
|
147
|
+
allowEmptyContent: false,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (value === undefined || value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
151
|
+
throw new TsConfigParseError(errors);
|
|
152
|
+
}
|
|
153
|
+
if (errors.length > 0) {
|
|
154
|
+
throw new TsConfigParseError(errors);
|
|
155
|
+
}
|
|
156
|
+
return { config: value as Record<string, unknown> };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function detectIndent(text: string): number {
|
|
160
|
+
// Look at the first indented line. A 2-space indent (the TS default)
|
|
161
|
+
// is by far the most common; we fall back to 2 when nothing useful
|
|
162
|
+
// is detectable (e.g. a single-line tsconfig).
|
|
163
|
+
const match = text.match(/^([ \t]+)\S/m);
|
|
164
|
+
if (match === null) {
|
|
165
|
+
return 2;
|
|
166
|
+
}
|
|
167
|
+
const indent = match[1] ?? '';
|
|
168
|
+
if (indent.startsWith('\t')) {
|
|
169
|
+
return 1;
|
|
31
170
|
}
|
|
171
|
+
return indent.length || 2;
|
|
172
|
+
}
|
|
32
173
|
|
|
33
|
-
|
|
34
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Merges `REQUIRED_COMPILER_OPTIONS_TYPES` into the user's existing
|
|
176
|
+
* `compilerOptions.types` array. Preserves order and dedupes. If the
|
|
177
|
+
* user has no `types` array (or has set it to a non-array), we replace
|
|
178
|
+
* with the required minimum — overwriting a non-array `types` is the
|
|
179
|
+
* correct fix because anything other than a string array is invalid TS
|
|
180
|
+
* config.
|
|
181
|
+
*/
|
|
182
|
+
function mergeTypesArray(existing: unknown): readonly string[] {
|
|
183
|
+
const result: string[] = [];
|
|
184
|
+
if (Array.isArray(existing)) {
|
|
185
|
+
for (const item of existing) {
|
|
186
|
+
if (typeof item === 'string' && !result.includes(item)) {
|
|
187
|
+
result.push(item);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
for (const required of REQUIRED_COMPILER_OPTIONS_TYPES) {
|
|
192
|
+
if (!result.includes(required)) {
|
|
193
|
+
result.push(required);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
35
197
|
}
|
|
@@ -112,7 +112,7 @@ async function executeMigrationApplyCommand(
|
|
|
112
112
|
startTime: number,
|
|
113
113
|
): Promise<Result<MigrationApplyResult, CliStructuredErrorType>> {
|
|
114
114
|
const config = await loadConfig(options.config);
|
|
115
|
-
const { configPath, migrationsDir, migrationsRelative,
|
|
115
|
+
const { configPath, migrationsDir, migrationsRelative, refsDir } = resolveMigrationPaths(
|
|
116
116
|
options.config,
|
|
117
117
|
config,
|
|
118
118
|
);
|
|
@@ -149,8 +149,8 @@ async function executeMigrationApplyCommand(
|
|
|
149
149
|
if (options.ref) {
|
|
150
150
|
refName = options.ref;
|
|
151
151
|
try {
|
|
152
|
-
const refs = await readRefs(
|
|
153
|
-
destinationHash = resolveRef(refs, refName);
|
|
152
|
+
const refs = await readRefs(refsDir);
|
|
153
|
+
destinationHash = resolveRef(refs, refName).hash;
|
|
154
154
|
} catch (error) {
|
|
155
155
|
if (MigrationToolsError.is(error)) {
|
|
156
156
|
return notOk(mapMigrationToolsError(error));
|