@prisma-next/cli 0.3.0-dev.163 → 0.3.0-dev.164

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 (93) hide show
  1. package/dist/agent-skill-mongo.md +106 -0
  2. package/dist/agent-skill-postgres.md +106 -0
  3. package/dist/cli.mjs +27 -4
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/{client-yYtotiSX.mjs → client-DiUkJAeN.mjs} +4 -80
  6. package/dist/client-DiUkJAeN.mjs.map +1 -0
  7. package/dist/commands/contract-emit.d.mts.map +1 -1
  8. package/dist/commands/contract-emit.mjs +6 -1
  9. package/dist/commands/contract-infer.mjs +7 -1
  10. package/dist/commands/db-init.mjs +6 -4
  11. package/dist/commands/db-init.mjs.map +1 -1
  12. package/dist/commands/db-schema.mjs +7 -3
  13. package/dist/commands/db-schema.mjs.map +1 -1
  14. package/dist/commands/db-sign.mjs +6 -4
  15. package/dist/commands/db-sign.mjs.map +1 -1
  16. package/dist/commands/db-update.mjs +6 -4
  17. package/dist/commands/db-update.mjs.map +1 -1
  18. package/dist/commands/db-verify.mjs +6 -4
  19. package/dist/commands/db-verify.mjs.map +1 -1
  20. package/dist/commands/migration-apply.mjs +5 -3
  21. package/dist/commands/migration-apply.mjs.map +1 -1
  22. package/dist/commands/migration-new.mjs +2 -1
  23. package/dist/commands/migration-new.mjs.map +1 -1
  24. package/dist/commands/migration-plan.mjs +3 -2
  25. package/dist/commands/migration-plan.mjs.map +1 -1
  26. package/dist/commands/migration-ref.d.mts +1 -1
  27. package/dist/commands/migration-ref.mjs +2 -1
  28. package/dist/commands/migration-ref.mjs.map +1 -1
  29. package/dist/commands/migration-show.d.mts +1 -1
  30. package/dist/commands/migration-show.mjs +4 -3
  31. package/dist/commands/migration-show.mjs.map +1 -1
  32. package/dist/commands/migration-status.mjs +6 -1
  33. package/dist/commands/migration-verify.mjs +3 -2
  34. package/dist/commands/migration-verify.mjs.map +1 -1
  35. package/dist/{contract-emit-Bk_eEDKu.mjs → contract-emit-D2wDXfyo.mjs} +8 -4
  36. package/dist/{contract-emit-Bk_eEDKu.mjs.map → contract-emit-D2wDXfyo.mjs.map} +1 -1
  37. package/dist/contract-emit-Zm_sd1wQ.mjs +112 -0
  38. package/dist/contract-emit-Zm_sd1wQ.mjs.map +1 -0
  39. package/dist/contract-emit-kN-IkKTE.mjs +6 -0
  40. package/dist/contract-enrichment-CGW6mm-E.mjs +79 -0
  41. package/dist/contract-enrichment-CGW6mm-E.mjs.map +1 -0
  42. package/dist/{contract-infer-suMDmFSG.mjs → contract-infer-DozZT511.mjs} +4 -3
  43. package/dist/{contract-infer-suMDmFSG.mjs.map → contract-infer-DozZT511.mjs.map} +1 -1
  44. package/dist/exports/control-api.mjs +7 -108
  45. package/dist/exports/index.mjs +6 -1
  46. package/dist/exports/index.mjs.map +1 -1
  47. package/dist/{extract-operation-statements-BVlb3jxp.mjs → extract-operation-statements-DZUJNmL3.mjs} +2 -2
  48. package/dist/{extract-operation-statements-BVlb3jxp.mjs.map → extract-operation-statements-DZUJNmL3.mjs.map} +1 -1
  49. package/dist/{extract-sql-ddl-6EVSOThm.mjs → extract-sql-ddl-DDMX-9mz.mjs} +1 -1
  50. package/dist/{extract-sql-ddl-6EVSOThm.mjs.map → extract-sql-ddl-DDMX-9mz.mjs.map} +1 -1
  51. package/dist/init-6Pvm_esG.mjs +430 -0
  52. package/dist/init-6Pvm_esG.mjs.map +1 -0
  53. package/dist/{inspect-live-schema-HMutsJYh.mjs → inspect-live-schema-BYnhztxZ.mjs} +4 -4
  54. package/dist/{inspect-live-schema-HMutsJYh.mjs.map → inspect-live-schema-BYnhztxZ.mjs.map} +1 -1
  55. package/dist/{migration-command-scaffold-Dg7CKKCg.mjs → migration-command-scaffold-CntCcntR.mjs} +4 -4
  56. package/dist/{migration-command-scaffold-Dg7CKKCg.mjs.map → migration-command-scaffold-CntCcntR.mjs.map} +1 -1
  57. package/dist/{migration-status-BqfVmC0w.mjs → migration-status-CJANY4yr.mjs} +4 -3
  58. package/dist/{migration-status-BqfVmC0w.mjs.map → migration-status-CJANY4yr.mjs.map} +1 -1
  59. package/dist/{migrations-Bv8oeiY_.mjs → migrations-DTZBYXm1.mjs} +2 -2
  60. package/dist/{migrations-Bv8oeiY_.mjs.map → migrations-DTZBYXm1.mjs.map} +1 -1
  61. package/dist/{progress-adapter-D4x8SbJa.mjs → progress-adapter-B-YvmcDu.mjs} +1 -1
  62. package/dist/{progress-adapter-D4x8SbJa.mjs.map → progress-adapter-B-YvmcDu.mjs.map} +1 -1
  63. package/dist/quick-reference-mongo.md +93 -0
  64. package/dist/quick-reference-postgres.md +91 -0
  65. package/dist/{terminal-ui-N5tR-ob5.mjs → result-handler-oK_vA-Fn.mjs} +3 -273
  66. package/dist/result-handler-oK_vA-Fn.mjs.map +1 -0
  67. package/dist/terminal-ui-C5k88MmW.mjs +274 -0
  68. package/dist/terminal-ui-C5k88MmW.mjs.map +1 -0
  69. package/dist/validate-contract-deps-esa-VQ0h.mjs +37 -0
  70. package/dist/validate-contract-deps-esa-VQ0h.mjs.map +1 -0
  71. package/dist/{verify-WARh5TjK.mjs → verify-DlFQ2FOw.mjs} +2 -2
  72. package/dist/{verify-WARh5TjK.mjs.map → verify-DlFQ2FOw.mjs.map} +1 -1
  73. package/package.json +17 -16
  74. package/src/cli.ts +5 -0
  75. package/src/commands/contract-emit.ts +7 -0
  76. package/src/commands/init/detect-package-manager.ts +47 -0
  77. package/src/commands/init/index.ts +21 -0
  78. package/src/commands/init/init.ts +203 -0
  79. package/src/commands/init/templates/agent-skill-mongo.md +106 -0
  80. package/src/commands/init/templates/agent-skill-postgres.md +106 -0
  81. package/src/commands/init/templates/agent-skill.ts +19 -0
  82. package/src/commands/init/templates/code-templates.ts +168 -0
  83. package/src/commands/init/templates/quick-reference-mongo.md +93 -0
  84. package/src/commands/init/templates/quick-reference-postgres.md +91 -0
  85. package/src/commands/init/templates/quick-reference.ts +19 -0
  86. package/src/commands/init/templates/render.ts +20 -0
  87. package/src/commands/init/templates/tsconfig.ts +35 -0
  88. package/src/control-api/operations/contract-emit.ts +7 -0
  89. package/src/utils/validate-contract-deps.ts +49 -0
  90. package/dist/client-yYtotiSX.mjs.map +0 -1
  91. package/dist/exports/control-api.mjs.map +0 -1
  92. package/dist/terminal-ui-N5tR-ob5.mjs.map +0 -1
  93. /package/dist/{cli-errors-Dzs7Oxz7.d.mts → cli-errors-DStABy9d.d.mts} +0 -0
@@ -0,0 +1,106 @@
1
+ # Prisma Next — project skill
2
+
3
+ This project uses **Prisma Next** with **PostgreSQL** via `@prisma-next/postgres`. Prisma Next lets the user define data models in a contract file and query them with a fully typed ORM.
4
+
5
+ ## Files
6
+
7
+ - **Contract**: `{{schemaPath}}` — the user's data models. Edit this to add or change models.
8
+ - **Config**: `prisma-next.config.ts` — tells the CLI where the contract is and how to connect to the database. Loads `.env` via `dotenv/config`.
9
+ - **Database client**: `{{schemaDir}}/db.ts` — `import { db } from '{{dbImportPath}}'`. This is the entry point for all queries.
10
+ - **Generated files** (do not edit by hand):
11
+ - `{{schemaDir}}/contract.json` — compiled contract, used at runtime
12
+ - `{{schemaDir}}/contract.d.ts` — TypeScript types for the contract, used for autocomplete and type checking
13
+
14
+ ## Commands
15
+
16
+ - `{{pkgRun}} contract emit` — regenerate `contract.json` and `contract.d.ts` after changing the contract
17
+ - `{{pkgRun}} db init` — bootstrap a database to match the contract (creates tables, indexes, constraints). Additive only — won't drop existing structures.
18
+ - `{{pkgRun}} db update` — update the database to match the current contract. Prompts for confirmation on destructive changes. Use `--dry-run` to preview.
19
+ - `{{pkgRun}} migration plan` — create a new migration from contract changes (offline, no database needed). Use `--name <slug>` to name it.
20
+ - `{{pkgRun}} migration apply` — apply pending migrations to the database
21
+ - `{{pkgRun}} migration status` — show which migrations are applied and which are pending
22
+ - `{{pkgRun}} migration show <name>` — show details of a specific migration
23
+
24
+ ## How to write queries
25
+
26
+ Always use the ORM (`db.orm`). Only fall back to `db.sql` if the user explicitly asks for raw SQL or the ORM doesn't support the operation.
27
+
28
+ ```typescript
29
+ import { db } from '{{dbImportPath}}';
30
+
31
+ // Find one record
32
+ const user = await db.orm.User
33
+ .where(user => user.email.eq('alice@example.com'))
34
+ .first();
35
+ // Returns { id: number; email: string; ... } | null
36
+
37
+ // Find multiple records
38
+ const users = await db.orm.User
39
+ .select('id', 'email')
40
+ .take(10)
41
+ .all();
42
+ // Returns Array<{ id: number; email: string }>
43
+
44
+ // Filter, order, limit
45
+ const recentPosts = await db.orm.Post
46
+ .where(post => post.authorId.eq(userId))
47
+ .orderBy(post => post.createdAt.desc())
48
+ .select('id', 'title', 'createdAt')
49
+ .take(50)
50
+ .all();
51
+
52
+ // Include relations
53
+ const usersWithPosts = await db.orm.User
54
+ .select('id', 'email')
55
+ .include('posts', post =>
56
+ post.select('id', 'title').orderBy(p => p.createdAt.desc()).take(5)
57
+ )
58
+ .take(10)
59
+ .all();
60
+ ```
61
+
62
+ ### Key ORM methods
63
+
64
+ - `.where(predicate)` — filter records. Predicate receives a model accessor with `.eq()`, `.neq()`, `.ilike()`, `.lt()`, `.gt()`, etc.
65
+ - `.select('field1', 'field2', ...)` — pick which fields to return
66
+ - `.orderBy(accessor => accessor.field.asc()` or `.desc())` — sort results
67
+ - `.take(n)` — limit number of results
68
+ - `.all()` — execute and return all matching records as an array
69
+ - `.first()` — execute and return the first matching record, or `null`
70
+ - `.first({ id: value })` — find a single record by primary key, or `null`
71
+ - `.include('relation', builder => ...)` — eager-load a relation
72
+
73
+ ## Rules
74
+
75
+ - **Never hand-edit** `contract.json` or `contract.d.ts`. Always regenerate them with `contract emit`.
76
+ - **Always emit after contract changes.** When you modify `{{schemaPath}}`, run `{{pkgRun}} contract emit` before writing any code that depends on the new or changed models.
77
+ - **Don't restructure `db.ts`.** It's scaffolded by init and works as-is.
78
+ - **Use `db.orm` for queries**, not `db.sql`. The ORM is the primary query surface.
79
+ - **Connection string** is `DATABASE_URL` in `.env`. If the user reports connection errors, check this value and the `.env` file.
80
+
81
+ ## Workflow for common tasks
82
+
83
+ **User wants to add a new model or field:**
84
+ 1. Edit `{{schemaPath}}`
85
+ 2. Run `{{pkgRun}} contract emit`
86
+ 3. Write query code using `db.orm.ModelName`
87
+
88
+ **User wants to query data:**
89
+ 1. Import `db` from `{{dbImportPath}}`
90
+ 2. Use `db.orm.ModelName` with `.where()`, `.select()`, `.all()`, `.first()`, etc.
91
+
92
+ **User wants to set up or change the database connection:**
93
+ 1. Edit `DATABASE_URL` in `.env`
94
+ 2. The config file (`prisma-next.config.ts`) reads it automatically via `dotenv/config`
95
+
96
+ **User wants to set up the database for the first time:**
97
+ 1. Run `{{pkgRun}} db init`
98
+
99
+ **User wants to update the database after changing the contract:**
100
+ 1. Quick path: `{{pkgRun}} db update` — compares the database to the contract and applies changes directly
101
+ 2. Migration path (for production workflows):
102
+ - `{{pkgRun}} migration plan --name describe-the-change` — creates a migration
103
+ - `{{pkgRun}} migration apply` — applies pending migrations
104
+
105
+ **User wants to check what migrations need to be applied:**
106
+ 1. Run `{{pkgRun}} migration status`
@@ -0,0 +1,19 @@
1
+ import { dirname } from 'pathe';
2
+ import type { TargetId } from './code-templates';
3
+ import { renderTemplate } from './render';
4
+
5
+ export const variables = ['schemaPath', 'schemaDir', 'dbImportPath', 'pkgRun'] as const;
6
+
7
+ type TemplateVars = Record<(typeof variables)[number], string>;
8
+
9
+ export function agentSkillMd(target: TargetId, schemaPath: string, pkgRun: string): string {
10
+ const schemaDir = dirname(schemaPath);
11
+ const vars: TemplateVars = {
12
+ schemaPath,
13
+ schemaDir,
14
+ dbImportPath: `./${schemaDir}/db`,
15
+ pkgRun,
16
+ };
17
+ const templateFile = `agent-skill-${target}.md`;
18
+ return renderTemplate(templateFile, variables, vars);
19
+ }
@@ -0,0 +1,168 @@
1
+ export type TargetId = 'postgres' | 'mongo';
2
+ export type AuthoringId = 'psl' | 'typescript';
3
+
4
+ export function targetPackageName(target: TargetId): string {
5
+ return target === 'postgres' ? '@prisma-next/postgres' : '@prisma-next/mongo';
6
+ }
7
+
8
+ export function targetLabel(target: TargetId): string {
9
+ return target === 'postgres' ? 'PostgreSQL' : 'MongoDB';
10
+ }
11
+
12
+ export function defaultSchemaPath(authoring: AuthoringId): string {
13
+ if (authoring === 'typescript') {
14
+ return 'prisma/contract.ts';
15
+ }
16
+ return 'prisma/contract.prisma';
17
+ }
18
+
19
+ export function starterSchema(target: TargetId, authoring: AuthoringId): string {
20
+ if (authoring === 'typescript') {
21
+ return target === 'mongo' ? starterSchemaTsMongo() : starterSchemaTsPostgres();
22
+ }
23
+ return target === 'mongo' ? starterSchemaPslMongo() : starterSchemaPslPostgres();
24
+ }
25
+
26
+ function starterSchemaPslPostgres(): string {
27
+ return `model User {
28
+ id Int @id @default(autoincrement())
29
+ email String @unique
30
+ name String?
31
+ posts Post[]
32
+ createdAt DateTime @default(now())
33
+ }
34
+
35
+ model Post {
36
+ id Int @id @default(autoincrement())
37
+ title String
38
+ content String?
39
+ author User @relation(fields: [authorId], references: [id])
40
+ authorId Int
41
+ createdAt DateTime @default(now())
42
+ }
43
+ `;
44
+ }
45
+
46
+ function starterSchemaPslMongo(): string {
47
+ return `model User {
48
+ id ObjectId @id @map("_id")
49
+ email String @unique
50
+ name String?
51
+ posts Post[]
52
+ @@map("users")
53
+ }
54
+
55
+ model Post {
56
+ id ObjectId @id @map("_id")
57
+ title String
58
+ content String?
59
+ author User @relation(fields: [authorId], references: [id])
60
+ authorId ObjectId
61
+ @@map("posts")
62
+ }
63
+ `;
64
+ }
65
+
66
+ function starterSchemaTsPostgres(): string {
67
+ return `import sqlFamily from '@prisma-next/family-sql/pack';
68
+ import { defineContract } from '@prisma-next/sql-contract-ts/contract-builder';
69
+ import postgresPack from '@prisma-next/target-postgres/pack';
70
+
71
+ export const contract = defineContract(
72
+ { family: sqlFamily, target: postgresPack },
73
+ ({ field, model, rel }) => ({
74
+ models: {
75
+ User: model('User', {
76
+ fields: {
77
+ id: field.id.uuidv7(),
78
+ email: field.text().unique(),
79
+ name: field.text().optional(),
80
+ createdAt: field.createdAt(),
81
+ },
82
+ }).relations({
83
+ posts: rel.hasMany('Post', { by: 'authorId' }),
84
+ }),
85
+
86
+ Post: model('Post', {
87
+ fields: {
88
+ id: field.id.uuidv7(),
89
+ title: field.text(),
90
+ content: field.text().optional(),
91
+ authorId: field.uuid(),
92
+ createdAt: field.createdAt(),
93
+ },
94
+ }).relations({
95
+ author: rel.belongsTo('User', { from: 'authorId', to: 'id' }),
96
+ }),
97
+ },
98
+ }),
99
+ );
100
+ `;
101
+ }
102
+
103
+ function starterSchemaTsMongo(): string {
104
+ return `import mongoFamily from '@prisma-next/family-mongo/pack';
105
+ import { defineContract, field, model, rel } from '@prisma-next/mongo-contract-ts/contract-builder';
106
+ import mongoTarget from '@prisma-next/target-mongo/pack';
107
+
108
+ const User = model('User', {
109
+ collection: 'users',
110
+ fields: {
111
+ _id: field.objectId(),
112
+ email: field.string(),
113
+ name: field.string().optional(),
114
+ },
115
+ });
116
+
117
+ const Post = model('Post', {
118
+ collection: 'posts',
119
+ fields: {
120
+ _id: field.objectId(),
121
+ title: field.string(),
122
+ content: field.string().optional(),
123
+ authorId: field.objectId(),
124
+ },
125
+ relations: {
126
+ author: rel.belongsTo(User, { from: 'authorId', to: User.ref('_id') }),
127
+ },
128
+ });
129
+
130
+ export const contract = defineContract({
131
+ family: mongoFamily,
132
+ target: mongoTarget,
133
+ models: { User, Post },
134
+ });
135
+ `;
136
+ }
137
+
138
+ export function configFile(target: TargetId, contractPath: string): string {
139
+ const pkg = targetPackageName(target);
140
+ return `import 'dotenv/config';
141
+ import { defineConfig } from '${pkg}/config';
142
+
143
+ export default defineConfig({
144
+ contract: ${JSON.stringify(contractPath)},
145
+ db: {
146
+ connection: process.env['DATABASE_URL']!,
147
+ },
148
+ });
149
+ `;
150
+ }
151
+
152
+ export function dbFile(target: TargetId): string {
153
+ if (target === 'postgres') {
154
+ return `import postgres from '@prisma-next/postgres/runtime';
155
+ import type { Contract } from './contract.d';
156
+ import contractJson from './contract.json' with { type: 'json' };
157
+
158
+ export const db = postgres<Contract>({ contractJson });
159
+ `;
160
+ }
161
+
162
+ return `import mongo from '@prisma-next/mongo/runtime';
163
+ import type { Contract } from './contract.d';
164
+ import contractJson from './contract.json' with { type: 'json' };
165
+
166
+ export const db = mongo<Contract>({ contractJson });
167
+ `;
168
+ }
@@ -0,0 +1,93 @@
1
+ # Welcome to Prisma Next!
2
+
3
+ Prisma Next lets you query your database in simple, easy-to-read TypeScript. Define what your data looks like, and Prisma Next gives you a fully typed client — with autocomplete for every collection, field, and relation.
4
+
5
+ This project is set up for MongoDB. Prisma Next also supports other databases.
6
+
7
+ ## Your data contract
8
+
9
+ Your data contract is the heart of your application. It lives at [`{{schemaPath}}`]({{schemaPath}}) and describes your models:
10
+
11
+ ```prisma
12
+ model User {
13
+ id ObjectId @id @map("_id")
14
+ email String @unique
15
+ name String?
16
+ posts Post[]
17
+ @@map("users")
18
+ }
19
+ ```
20
+
21
+ 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
+
23
+ ```typescript
24
+ import { db } from '{{dbImportPath}}';
25
+
26
+ const client = await db.connect(process.env['DATABASE_URL']!, 'mydb');
27
+
28
+ const user = await client.orm.User
29
+ .where({ email: 'alice@example.com' })
30
+ .first();
31
+
32
+ // Your editor will show the type of user as
33
+ // { id: ObjectId; email: string; name: string | null; posts: Post[] } | null
34
+ ```
35
+
36
+ Your contract has two companion files in the same directory:
37
+
38
+ - **`contract.json`** — this tells your application what models exist, just like `package-lock.json` tells your package manager what dependencies your project has
39
+ - **`contract.d.ts`** — this powers autocomplete and type checking in your editor
40
+
41
+ Commit both files to git. When you change your contract, run `{{pkgRun}} contract emit` to update them.
42
+
43
+ If you use a framework like Next.js or Vite, the Prisma Next plugin will do this for you automatically.
44
+
45
+ ## Configuration
46
+
47
+ [`prisma-next.config.ts`](prisma-next.config.ts) tells the CLI where your contract lives and how to connect to your database. It loads environment variables from `.env` automatically:
48
+
49
+ ```typescript
50
+ import 'dotenv/config';
51
+ import { defineConfig } from '@prisma-next/mongo/config';
52
+
53
+ export default defineConfig({
54
+ contract: './{{schemaPath}}',
55
+ db: {
56
+ connection: process.env['DATABASE_URL']!,
57
+ },
58
+ });
59
+ ```
60
+
61
+ Notice the `DATABASE_URL` above? It's defined in your [`.env`](./.env) file:
62
+
63
+ ```env
64
+ DATABASE_URL="mongodb://localhost:27017/mydb"
65
+ ```
66
+
67
+ You can customize how your environment variables are loaded by changing or removing the `import 'dotenv/config'` line.
68
+
69
+ ## Quick reference
70
+
71
+ ### Commands
72
+
73
+ ```bash
74
+ {{pkgRun}} contract emit # Update contract.json and contract.d.ts
75
+ {{pkgRun}} db init # Create collections in the database
76
+ {{pkgRun}} migration status # Show migration status
77
+ ```
78
+
79
+ ### Files
80
+
81
+ | File | Purpose |
82
+ |---|---|
83
+ | [`{{schemaPath}}`]({{schemaPath}}) | Your data contract — define your models here |
84
+ | [`prisma-next.config.ts`](prisma-next.config.ts) | CLI configuration |
85
+ | [`{{schemaDir}}/db.ts`]({{schemaDir}}/db.ts) | Database client — `import { db } from '{{dbImportPath}}'` |
86
+ | `{{schemaDir}}/contract.json` | Compiled contract (generated) |
87
+ | `{{schemaDir}}/contract.d.ts` | Contract types (generated) |
88
+
89
+ ### Workflow
90
+
91
+ 1. Edit [`{{schemaPath}}`]({{schemaPath}}) to add or change models.
92
+ 2. Run `{{pkgRun}} contract emit` to regenerate the contract.
93
+ 3. Query your models — your IDE will autocomplete everything.
@@ -0,0 +1,91 @@
1
+ # Welcome to Prisma Next!
2
+
3
+ Prisma Next lets you query your database in simple, easy-to-read TypeScript. Define what your data looks like, and Prisma Next gives you a fully typed client — with autocomplete for every table, column, and relation.
4
+
5
+ This project is set up for PostgreSQL. Prisma Next also supports other databases.
6
+
7
+ ## Your data contract
8
+
9
+ Your data contract is the heart of your application. It lives at [`{{schemaPath}}`]({{schemaPath}}) and describes your models:
10
+
11
+ ```prisma
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
+ ```
20
+
21
+ 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
+
23
+ ```typescript
24
+ import { db } from '{{dbImportPath}}';
25
+
26
+ const user = await db.orm.User
27
+ .where({ email: 'alice@example.com' })
28
+ .first();
29
+
30
+ // Your editor will show the type of user as
31
+ // { id: number; email: string; createdAt: Date, name: string, posts: Post[] } | null
32
+ ```
33
+
34
+ Your contract has two companion files in the same directory:
35
+
36
+ - **`contract.json`** — this tells your application what models exist, just like `package-lock.json` tells your package manager what dependencies your project has
37
+ - **`contract.d.ts`** — this powers autocomplete and type checking in your editor
38
+
39
+ Commit both files to git. When you change your contract, run `{{pkgRun}} contract emit` to update them.
40
+
41
+ If you use a framework like Next.js or Vite, the Prisma Next plugin will do this for you automatically.
42
+
43
+ ## Configuration
44
+
45
+ [`prisma-next.config.ts`](prisma-next.config.ts) tells the CLI where your contract lives and how to connect to your database. It loads environment variables from `.env` automatically:
46
+
47
+ ```typescript
48
+ import 'dotenv/config';
49
+ import { defineConfig } from '@prisma-next/postgres/config';
50
+
51
+ export default defineConfig({
52
+ contract: './{{schemaPath}}',
53
+ db: {
54
+ connection: process.env['DATABASE_URL']!,
55
+ },
56
+ });
57
+ ```
58
+
59
+ Notice the `DATABASE_URL` above? It's defined in your [`.env`](./.env) file:
60
+
61
+ ```env
62
+ DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
63
+ ```
64
+
65
+ You can customize how your environment variables are loaded by changing or removing the `import 'dotenv/config'` line.
66
+
67
+ ## Quick reference
68
+
69
+ ### Commands
70
+
71
+ ```bash
72
+ {{pkgRun}} contract emit # Update contract.json and contract.d.ts
73
+ {{pkgRun}} db init # Create tables in the database
74
+ {{pkgRun}} migration status # Show migration status
75
+ ```
76
+
77
+ ### Files
78
+
79
+ | File | Purpose |
80
+ |---|---|
81
+ | [`{{schemaPath}}`]({{schemaPath}}) | Your data contract — define your models here |
82
+ | [`prisma-next.config.ts`](prisma-next.config.ts) | CLI configuration |
83
+ | [`{{schemaDir}}/db.ts`]({{schemaDir}}/db.ts) | Database client — `import { db } from '{{dbImportPath}}'` |
84
+ | `{{schemaDir}}/contract.json` | Compiled contract (generated) |
85
+ | `{{schemaDir}}/contract.d.ts` | Contract types (generated) |
86
+
87
+ ### Workflow
88
+
89
+ 1. Edit [`{{schemaPath}}`]({{schemaPath}}) to add or change models.
90
+ 2. Run `{{pkgRun}} contract emit` to regenerate the contract.
91
+ 3. Query your models — your IDE will autocomplete everything.
@@ -0,0 +1,19 @@
1
+ import { dirname } from 'pathe';
2
+ import type { TargetId } from './code-templates';
3
+ import { renderTemplate } from './render';
4
+
5
+ export const variables = ['schemaPath', 'schemaDir', 'dbImportPath', 'pkgRun'] as const;
6
+
7
+ type TemplateVars = Record<(typeof variables)[number], string>;
8
+
9
+ export function quickReferenceMd(target: TargetId, schemaPath: string, pkgRun: string): string {
10
+ const schemaDir = dirname(schemaPath);
11
+ const vars: TemplateVars = {
12
+ schemaPath,
13
+ schemaDir,
14
+ dbImportPath: `./${schemaDir}/db`,
15
+ pkgRun,
16
+ };
17
+ const templateFile = `quick-reference-${target}.md`;
18
+ return renderTemplate(templateFile, variables, vars);
19
+ }
@@ -0,0 +1,20 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'pathe';
3
+
4
+ export function renderTemplate(
5
+ templateFile: string,
6
+ variableNames: readonly string[],
7
+ vars: Record<string, string>,
8
+ ): string {
9
+ const templatePath = join(import.meta.dirname, templateFile);
10
+ const raw = readFileSync(templatePath, 'utf-8');
11
+ let result = raw;
12
+ for (const key of variableNames) {
13
+ const value = vars[key];
14
+ if (value === undefined) {
15
+ throw new Error(`Template variable '${key}' is not defined`);
16
+ }
17
+ result = result.replaceAll(`{{${key}}}`, value);
18
+ }
19
+ return result;
20
+ }
@@ -0,0 +1,35 @@
1
+ export const REQUIRED_COMPILER_OPTIONS: Record<string, string | boolean> = {
2
+ module: 'preserve',
3
+ moduleResolution: 'bundler',
4
+ resolveJsonModule: true,
5
+ };
6
+
7
+ export function defaultTsConfig(): string {
8
+ return JSON.stringify(
9
+ {
10
+ compilerOptions: {
11
+ target: 'ES2022',
12
+ ...REQUIRED_COMPILER_OPTIONS,
13
+ strict: true,
14
+ skipLibCheck: true,
15
+ esModuleInterop: true,
16
+ outDir: 'dist',
17
+ },
18
+ include: ['**/*.ts'],
19
+ },
20
+ null,
21
+ 2,
22
+ );
23
+ }
24
+
25
+ export function mergeTsConfig(existing: string): string {
26
+ const config = JSON.parse(existing) as Record<string, unknown>;
27
+ const compilerOptions = (config['compilerOptions'] ?? {}) as Record<string, unknown>;
28
+
29
+ for (const [key, value] of Object.entries(REQUIRED_COMPILER_OPTIONS)) {
30
+ compilerOptions[key] = value;
31
+ }
32
+
33
+ config['compilerOptions'] = compilerOptions;
34
+ return JSON.stringify(config, null, 2);
35
+ }
@@ -162,6 +162,13 @@ export async function executeContractEmit(
162
162
  await unlessAborted(writeFile(outputJsonPath, emitResult.contractJson, 'utf-8'));
163
163
  await unlessAborted(writeFile(outputDtsPath, emitResult.contractDts, 'utf-8'));
164
164
 
165
+ // Validate that contract.d.ts type imports are resolvable
166
+ const { validateContractDeps } = await import('../../utils/validate-contract-deps');
167
+ const depsValidation = validateContractDeps(emitResult.contractDts, dirname(outputDtsPath));
168
+ if (depsValidation.warning) {
169
+ process.stderr.write(`\n⚠ ${depsValidation.warning}\n`);
170
+ }
171
+
165
172
  return {
166
173
  storageHash: emitResult.storageHash,
167
174
  ...ifDefined('executionHash', emitResult.executionHash),
@@ -0,0 +1,49 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ const IMPORT_PATTERN = /import\s+type\s+.*?\s+from\s+['"](@[^/]+\/[^/'"]+)/g;
4
+
5
+ export function extractPackageSpecifiers(dtsContent: string): string[] {
6
+ const packages = new Set<string>();
7
+ for (const match of dtsContent.matchAll(IMPORT_PATTERN)) {
8
+ const pkg = match[1];
9
+ if (pkg) packages.add(pkg);
10
+ }
11
+ return [...packages];
12
+ }
13
+
14
+ export interface ContractDepsValidation {
15
+ readonly missing: readonly string[];
16
+ readonly warning?: string;
17
+ }
18
+
19
+ export function validateContractDeps(
20
+ dtsContent: string,
21
+ projectRoot: string,
22
+ ): ContractDepsValidation {
23
+ const packages = extractPackageSpecifiers(dtsContent);
24
+ const resolve = createRequire(`${projectRoot}/package.json`);
25
+
26
+ const missing: string[] = [];
27
+ for (const pkg of packages) {
28
+ try {
29
+ resolve.resolve(`${pkg}/package.json`);
30
+ } catch {
31
+ missing.push(pkg);
32
+ }
33
+ }
34
+
35
+ if (missing.length === 0) {
36
+ return { missing };
37
+ }
38
+
39
+ const list = missing.map((p) => ` - ${p}`).join('\n');
40
+ const warning = [
41
+ 'contract.d.ts imports types from packages that are not installed:',
42
+ list,
43
+ '',
44
+ 'Install them with your package manager:',
45
+ ...missing.map((p) => ` ${p}`),
46
+ ].join('\n');
47
+
48
+ return { missing, warning };
49
+ }