@vibeorm/generator 1.0.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/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @vibeorm/generator
2
+
3
+ TypeScript client generator for VibeORM. Takes a parsed schema IR and produces fully typed client files including model types, input types, delegates, and Zod v4 validation schemas.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @vibeorm/generator
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { parsePrismaSchema } from "@vibeorm/parser";
15
+ import { generate } from "@vibeorm/generator";
16
+ import { writeFileSync, mkdirSync } from "fs";
17
+ import { join } from "path";
18
+
19
+ const schema = parsePrismaSchema({ source: prismaSchemaString });
20
+ const files = generate({ schema });
21
+
22
+ const outputDir = "./generated/vibeorm";
23
+ mkdirSync(outputDir, { recursive: true });
24
+
25
+ for (const file of files) {
26
+ writeFileSync(join(outputDir, file.filename), file.content);
27
+ }
28
+ ```
29
+
30
+ ## API
31
+
32
+ ### `generate({ schema })`
33
+
34
+ Accepts a `Schema` IR (from `@vibeorm/parser`) and returns an array of `GeneratedFile` objects:
35
+
36
+ ```ts
37
+ type GeneratedFile = {
38
+ filename: string;
39
+ content: string;
40
+ };
41
+ ```
42
+
43
+ ### Generated Files
44
+
45
+ | File | Contents |
46
+ |------|----------|
47
+ | `enums.ts` | TypeScript enums |
48
+ | `models.ts` | Model types (record shapes) |
49
+ | `inputs.ts` | Create, update, where, and filter input types |
50
+ | `args.ts` | Operation argument types (`FindManyArgs`, `CreateArgs`, etc.) |
51
+ | `result.ts` | Result types narrowed by `select`/`include`/`omit` |
52
+ | `delegates.ts` | Per-model delegate types (the `.findMany()`, `.create()` API surface) |
53
+ | `schemas.ts` | Zod v4 runtime validation schemas |
54
+ | `index.ts` | `VibeClient()` factory that wires model metadata into the runtime |
55
+
56
+ ## License
57
+
58
+ [MIT](../../LICENSE)
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@vibeorm/generator",
3
+ "version": "1.0.0",
4
+ "description": "TypeScript client generator for VibeORM — produces typed delegates, inputs, and Zod schemas from a Prisma schema",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "orm",
8
+ "codegen",
9
+ "prisma",
10
+ "bun",
11
+ "typescript",
12
+ "postgresql"
13
+ ],
14
+ "type": "module",
15
+ "exports": {
16
+ ".": {
17
+ "default": "./src/index.ts",
18
+ "types": "./src/index.ts"
19
+ }
20
+ },
21
+ "files": [
22
+ "src"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/vibeorm/vibeorm.git",
27
+ "directory": "packages/generator"
28
+ },
29
+ "homepage": "https://github.com/vibeorm/vibeorm/tree/master/packages/generator",
30
+ "bugs": {
31
+ "url": "https://github.com/vibeorm/vibeorm/issues"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "engines": {
37
+ "bun": ">=1.1.0"
38
+ },
39
+ "dependencies": {
40
+ "@vibeorm/parser": "workspace:*"
41
+ }
42
+ }
@@ -0,0 +1,65 @@
1
+ import type { Schema } from "@vibeorm/parser";
2
+ import { generateEnums } from "./generators/generate-enums.ts";
3
+ import { generateModels } from "./generators/generate-models.ts";
4
+ import {
5
+ generateInputs,
6
+ generateFilterTypes,
7
+ generateRelationFilterTypes,
8
+ } from "./generators/generate-inputs.ts";
9
+ import { generateArgs } from "./generators/generate-args.ts";
10
+ import { generateResult } from "./generators/generate-result.ts";
11
+ import { generateDelegates } from "./generators/generate-delegates.ts";
12
+ import { generateClient } from "./generators/generate-client.ts";
13
+ import { generateSchemas } from "./generators/generate-schemas.ts";
14
+
15
+ export type GeneratedFile = {
16
+ filename: string;
17
+ content: string;
18
+ };
19
+
20
+ /**
21
+ * Takes a parsed schema IR and produces all the generated TypeScript files.
22
+ */
23
+ export function generate(params: { schema: Schema }): GeneratedFile[] {
24
+ const { schema } = params;
25
+
26
+ const files: GeneratedFile[] = [
27
+ {
28
+ filename: "enums.ts",
29
+ content: generateEnums({ enums: schema.enums }),
30
+ },
31
+ {
32
+ filename: "models.ts",
33
+ content: generateModels({ schema }),
34
+ },
35
+ {
36
+ filename: "inputs.ts",
37
+ content:
38
+ generateInputs({ schema }) +
39
+ generateFilterTypes() +
40
+ generateRelationFilterTypes({ schema }),
41
+ },
42
+ {
43
+ filename: "args.ts",
44
+ content: generateArgs({ schema }),
45
+ },
46
+ {
47
+ filename: "result.ts",
48
+ content: generateResult({ schema }),
49
+ },
50
+ {
51
+ filename: "delegates.ts",
52
+ content: generateDelegates({ schema }),
53
+ },
54
+ {
55
+ filename: "index.ts",
56
+ content: generateClient({ schema }),
57
+ },
58
+ {
59
+ filename: "schemas.ts",
60
+ content: generateSchemas({ schema }),
61
+ },
62
+ ];
63
+
64
+ return files;
65
+ }
@@ -0,0 +1,322 @@
1
+ import type { Model, Schema, RelationField, ScalarField } from "@vibeorm/parser";
2
+ import { fileHeader } from "../utils.ts";
3
+
4
+ /**
5
+ * Generates Select, Include, and Args types for each model,
6
+ * plus the FindManyArgs, FindFirstArgs, FindUniqueArgs, etc.
7
+ */
8
+ export function generateArgs(params: { schema: Schema }): string {
9
+ const { schema } = params;
10
+ const parts: string[] = [fileHeader()];
11
+
12
+ // Imports
13
+ const modelImports = schema.models.map((m) => `${m.name}WhereInput, ${m.name}WhereUniqueInput, ${m.name}OrderByInput, ${m.name}CreateInput, ${m.name}UpdateInput`).join(", ");
14
+ parts.push(`import type { ${modelImports}, SortOrder, SortOrderWithNulls } from "./inputs.ts";`);
15
+
16
+ const payloadImports = schema.models.map((m) => `$${m.name}Payload`).join(", ");
17
+ parts.push(`import type { ${payloadImports}, OperationPayload } from "./models.ts";\n`);
18
+
19
+ // Shared types
20
+ parts.push(`export type RelationStrategy = "query" | "join";
21
+
22
+ export type NumberWithAggregatesFilter = {
23
+ equals?: number;
24
+ not?: number;
25
+ lt?: number;
26
+ lte?: number;
27
+ gt?: number;
28
+ gte?: number;
29
+ };
30
+ `);
31
+
32
+ // Generate Select + Include + Args for each model
33
+ for (const model of schema.models) {
34
+ parts.push(generateSelectType({ model, schema }));
35
+ parts.push(generateIncludeType({ model, schema }));
36
+ parts.push(generateOperationArgs({ model }));
37
+ parts.push(generateAggregateInputTypes({ model }));
38
+ }
39
+
40
+ return parts.join("\n");
41
+ }
42
+
43
+ // ─── Select Type ──────────────────────────────────────────────────
44
+
45
+ function generateSelectType(params: {
46
+ model: Model;
47
+ schema: Schema;
48
+ }): string {
49
+ const { model, schema } = params;
50
+
51
+ const entries: string[] = [];
52
+
53
+ for (const field of model.fields) {
54
+ if (field.kind === "scalar" || field.kind === "enum") {
55
+ entries.push(` ${field.name}?: boolean;`);
56
+ } else if (field.kind === "relation") {
57
+ if (field.isList) {
58
+ entries.push(` ${field.name}?: boolean | ${field.relatedModel}FindManyArgs;`);
59
+ } else {
60
+ entries.push(` ${field.name}?: boolean | ${field.relatedModel}FindFirstArgs;`);
61
+ }
62
+ }
63
+ }
64
+
65
+ // Add _count support for relation counting
66
+ const countableRelations = model.fields.filter(
67
+ (f): f is RelationField => f.kind === "relation" && f.isList
68
+ );
69
+ if (countableRelations.length > 0) {
70
+ const countFields = countableRelations.map((f) => ` ${f.name}?: boolean;`);
71
+ entries.push(` _count?: boolean | {\n select?: {\n${countFields.join("\n")}\n };\n };`);
72
+ }
73
+
74
+ return `export type ${model.name}Select = {
75
+ ${entries.join("\n")}
76
+ };
77
+ `;
78
+ }
79
+
80
+ // ─── Include Type ─────────────────────────────────────────────────
81
+
82
+ function generateIncludeType(params: {
83
+ model: Model;
84
+ schema: Schema;
85
+ }): string {
86
+ const { model } = params;
87
+
88
+ const relationFields = model.fields.filter(
89
+ (f): f is RelationField => f.kind === "relation"
90
+ );
91
+
92
+ if (relationFields.length === 0) {
93
+ return `export type ${model.name}Include = Record<string, never>;\n`;
94
+ }
95
+
96
+ const entries = relationFields.map((f) => {
97
+ if (f.isList) {
98
+ return ` ${f.name}?: boolean | ${f.relatedModel}FindManyArgs;`;
99
+ }
100
+ return ` ${f.name}?: boolean | ${f.relatedModel}FindFirstArgs;`;
101
+ });
102
+
103
+ // Add _count support for relation counting
104
+ const countableRelations = relationFields.filter((f) => f.isList);
105
+ if (countableRelations.length > 0) {
106
+ const countFields = countableRelations.map((f) => ` ${f.name}?: boolean;`);
107
+ entries.push(` _count?: boolean | {\n select?: {\n${countFields.join("\n")}\n };\n };`);
108
+ }
109
+
110
+ return `export type ${model.name}Include = {
111
+ ${entries.join("\n")}
112
+ };
113
+ `;
114
+ }
115
+
116
+ // ─── Operation Args ───────────────────────────────────────────────
117
+
118
+ function generateOperationArgs(params: { model: Model }): string {
119
+ const { model } = params;
120
+
121
+ // Omit type — inverse of select (exclude specific fields)
122
+ const scalarFieldNames = model.fields
123
+ .filter((f) => f.kind === "scalar" || f.kind === "enum")
124
+ .map((f) => ` ${f.name}?: boolean;`);
125
+
126
+ const omitTypeDef = `export type ${model.name}Omit = {
127
+ ${scalarFieldNames.join("\n")}
128
+ };
129
+ `;
130
+
131
+ return `${omitTypeDef}
132
+ export type ${model.name}FindManyArgs = {
133
+ select?: ${model.name}Select | null;
134
+ include?: ${model.name}Include | null;
135
+ omit?: ${model.name}Omit;
136
+ where?: ${model.name}WhereInput;
137
+ orderBy?: ${model.name}OrderByInput | ${model.name}OrderByInput[];
138
+ take?: number;
139
+ skip?: number;
140
+ cursor?: ${model.name}WhereUniqueInput;
141
+ distinct?: (keyof ${model.name}Select)[];
142
+ relationStrategy?: RelationStrategy;
143
+ };
144
+
145
+ export type ${model.name}FindFirstArgs = {
146
+ select?: ${model.name}Select | null;
147
+ include?: ${model.name}Include | null;
148
+ omit?: ${model.name}Omit;
149
+ where?: ${model.name}WhereInput;
150
+ orderBy?: ${model.name}OrderByInput | ${model.name}OrderByInput[];
151
+ take?: number;
152
+ skip?: number;
153
+ cursor?: ${model.name}WhereUniqueInput;
154
+ relationStrategy?: RelationStrategy;
155
+ };
156
+
157
+ export type ${model.name}FindUniqueArgs = {
158
+ select?: ${model.name}Select | null;
159
+ include?: ${model.name}Include | null;
160
+ omit?: ${model.name}Omit;
161
+ where: ${model.name}WhereUniqueInput;
162
+ relationStrategy?: RelationStrategy;
163
+ };
164
+
165
+ export type ${model.name}CreateArgs = {
166
+ select?: ${model.name}Select | null;
167
+ include?: ${model.name}Include | null;
168
+ omit?: ${model.name}Omit;
169
+ data: ${model.name}CreateInput;
170
+ relationStrategy?: RelationStrategy;
171
+ };
172
+
173
+ export type ${model.name}UpdateArgs = {
174
+ select?: ${model.name}Select | null;
175
+ include?: ${model.name}Include | null;
176
+ omit?: ${model.name}Omit;
177
+ where: ${model.name}WhereUniqueInput;
178
+ data: ${model.name}UpdateInput;
179
+ relationStrategy?: RelationStrategy;
180
+ };
181
+
182
+ export type ${model.name}UpsertArgs = {
183
+ select?: ${model.name}Select | null;
184
+ include?: ${model.name}Include | null;
185
+ where: ${model.name}WhereUniqueInput;
186
+ create: ${model.name}CreateInput;
187
+ update: ${model.name}UpdateInput;
188
+ };
189
+
190
+ export type ${model.name}DeleteArgs = {
191
+ select?: ${model.name}Select | null;
192
+ include?: ${model.name}Include | null;
193
+ where: ${model.name}WhereUniqueInput;
194
+ };
195
+
196
+ export type ${model.name}DeleteManyArgs = {
197
+ where?: ${model.name}WhereInput;
198
+ };
199
+
200
+ export type ${model.name}CountArgs = {
201
+ where?: ${model.name}WhereInput;
202
+ orderBy?: ${model.name}OrderByInput | ${model.name}OrderByInput[];
203
+ take?: number;
204
+ skip?: number;
205
+ cursor?: ${model.name}WhereUniqueInput;
206
+ };
207
+
208
+ export type ${model.name}CreateManyArgs = {
209
+ data: ${model.name}CreateInput | ${model.name}CreateInput[];
210
+ skipDuplicates?: boolean;
211
+ };
212
+
213
+ export type ${model.name}CreateManyAndReturnArgs = {
214
+ select?: ${model.name}Select | null;
215
+ include?: ${model.name}Include | null;
216
+ data: ${model.name}CreateInput | ${model.name}CreateInput[];
217
+ skipDuplicates?: boolean;
218
+ };
219
+
220
+ export type ${model.name}UpdateManyArgs = {
221
+ where?: ${model.name}WhereInput;
222
+ data: ${model.name}UpdateInput;
223
+ };
224
+
225
+ export type ${model.name}AggregateArgs = {
226
+ where?: ${model.name}WhereInput;
227
+ orderBy?: ${model.name}OrderByInput | ${model.name}OrderByInput[];
228
+ take?: number;
229
+ skip?: number;
230
+ cursor?: ${model.name}WhereUniqueInput;
231
+ _count?: true | ${model.name}CountAggregateInputType;
232
+ _avg?: ${model.name}AvgAggregateInputType;
233
+ _sum?: ${model.name}SumAggregateInputType;
234
+ _min?: ${model.name}MinAggregateInputType;
235
+ _max?: ${model.name}MaxAggregateInputType;
236
+ };
237
+
238
+ export type ${model.name}GroupByArgs = {
239
+ by: (keyof ${model.name}Select)[];
240
+ where?: ${model.name}WhereInput;
241
+ orderBy?: ${model.name}OrderByInput | ${model.name}OrderByInput[];
242
+ having?: ${model.name}ScalarWhereWithAggregatesInput;
243
+ take?: number;
244
+ skip?: number;
245
+ _count?: true | ${model.name}CountAggregateInputType;
246
+ _avg?: ${model.name}AvgAggregateInputType;
247
+ _sum?: ${model.name}SumAggregateInputType;
248
+ _min?: ${model.name}MinAggregateInputType;
249
+ _max?: ${model.name}MaxAggregateInputType;
250
+ };
251
+ `;
252
+ }
253
+
254
+ // ─── Aggregate Input Types ────────────────────────────────────────
255
+
256
+ const NUMERIC_PRISMA_TYPES = new Set(["Int", "Float", "Decimal", "BigInt"]);
257
+
258
+ function generateAggregateInputTypes(params: { model: Model }): string {
259
+ const { model } = params;
260
+
261
+ const scalarFields = model.fields.filter(
262
+ (f): f is ScalarField => f.kind === "scalar"
263
+ );
264
+
265
+ const numericFields = scalarFields.filter(
266
+ (f) => NUMERIC_PRISMA_TYPES.has(f.prismaType)
267
+ );
268
+
269
+ // CountAggregateInputType — all scalar fields + _all
270
+ const countEntries = [
271
+ ` _all?: boolean;`,
272
+ ...scalarFields.map((f) => ` ${f.name}?: boolean;`),
273
+ ];
274
+
275
+ // AvgAggregateInputType — only numeric fields
276
+ const avgEntries = numericFields.map((f) => ` ${f.name}?: boolean;`);
277
+
278
+ // SumAggregateInputType — only numeric fields
279
+ const sumEntries = numericFields.map((f) => ` ${f.name}?: boolean;`);
280
+
281
+ // MinAggregateInputType — all scalar fields
282
+ const minEntries = scalarFields.map((f) => ` ${f.name}?: boolean;`);
283
+
284
+ // MaxAggregateInputType — all scalar fields
285
+ const maxEntries = scalarFields.map((f) => ` ${f.name}?: boolean;`);
286
+
287
+ // ScalarWhereWithAggregatesInput — for HAVING clause in groupBy
288
+ const havingEntries = scalarFields.map((f) => {
289
+ return ` ${f.name}?: {
290
+ _count?: NumberWithAggregatesFilter;
291
+ _avg?: NumberWithAggregatesFilter;
292
+ _sum?: NumberWithAggregatesFilter;
293
+ _min?: NumberWithAggregatesFilter;
294
+ _max?: NumberWithAggregatesFilter;
295
+ };`;
296
+ });
297
+
298
+ return `export type ${model.name}CountAggregateInputType = {
299
+ ${countEntries.join("\n")}
300
+ };
301
+
302
+ export type ${model.name}AvgAggregateInputType = {
303
+ ${avgEntries.length > 0 ? avgEntries.join("\n") : " [key: string]: never;"}
304
+ };
305
+
306
+ export type ${model.name}SumAggregateInputType = {
307
+ ${sumEntries.length > 0 ? sumEntries.join("\n") : " [key: string]: never;"}
308
+ };
309
+
310
+ export type ${model.name}MinAggregateInputType = {
311
+ ${minEntries.join("\n")}
312
+ };
313
+
314
+ export type ${model.name}MaxAggregateInputType = {
315
+ ${maxEntries.join("\n")}
316
+ };
317
+
318
+ export type ${model.name}ScalarWhereWithAggregatesInput = {
319
+ ${havingEntries.join("\n")}
320
+ };
321
+ `;
322
+ }
@@ -0,0 +1,186 @@
1
+ import type { Schema } from "@vibeorm/parser";
2
+ import { fileHeader, toCamelCase } from "../utils.ts";
3
+
4
+ /**
5
+ * Generates the main index.ts file that:
6
+ * - Re-exports all types
7
+ * - Creates the VibeClient class that wires delegates to the runtime
8
+ */
9
+ export function generateClient(params: { schema: Schema }): string {
10
+ const { schema } = params;
11
+ const parts: string[] = [fileHeader()];
12
+
13
+ // Re-exports
14
+ parts.push(`export * from "./enums.ts";`);
15
+ parts.push(`export * from "./models.ts";`);
16
+ parts.push(`export * from "./inputs.ts";`);
17
+ parts.push(`export * from "./args.ts";`);
18
+ parts.push(`export * from "./result.ts";`);
19
+ parts.push(`export * from "./delegates.ts";`);
20
+ parts.push(`export * from "./schemas.ts";`);
21
+ parts.push(``);
22
+
23
+ // Import delegate types
24
+ const delegateImports = schema.models
25
+ .map((m) => `${m.name}Delegate`)
26
+ .join(", ");
27
+ parts.push(`import type { ${delegateImports} } from "./delegates.ts";`);
28
+
29
+ // Import runtime
30
+ parts.push(`import { createClient } from "@vibeorm/runtime";`);
31
+ parts.push(`import type { VibeClientOptions } from "@vibeorm/runtime";`);
32
+ parts.push(``);
33
+
34
+ // Import schemas for validation wiring
35
+ const schemaImports = schema.models.flatMap((m) => [
36
+ `${m.name}Schema`,
37
+ `${m.name}CreateInputSchema`,
38
+ `${m.name}UpdateInputSchema`,
39
+ `${m.name}WhereInputSchema`,
40
+ ]);
41
+ parts.push(`import { ${schemaImports.join(", ")} } from "./schemas.ts";`);
42
+ parts.push(``);
43
+
44
+ // Generate the model metadata object that the runtime needs
45
+ parts.push(generateModelMeta({ schema }));
46
+
47
+ // Generate the schemas map for validation wiring
48
+ parts.push(generateSchemasMap({ schema }));
49
+
50
+ // Generate the VibeClient type (intersection of all delegates)
51
+ const clientProperties = schema.models
52
+ .map((m) => ` ${toCamelCase({ str: m.name })}: ${m.name}Delegate;`)
53
+ .join("\n");
54
+
55
+ parts.push(`export type VibeClientInstance = {
56
+ ${clientProperties}
57
+ $transaction<T>(fn: (tx: VibeClientInstance) => Promise<T>): Promise<T>;
58
+ $transaction(promises: Promise<unknown>[]): Promise<unknown[]>;
59
+ $queryRaw<T = unknown>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T[]>;
60
+ $executeRaw(strings: TemplateStringsArray, ...values: unknown[]): Promise<number>;
61
+ $queryRawUnsafe<T = unknown>(query: string, ...values: unknown[]): Promise<T[]>;
62
+ $executeRawUnsafe(query: string, ...values: unknown[]): Promise<number>;
63
+ $connect(): Promise<void>;
64
+ $disconnect(): Promise<void>;
65
+ };
66
+ `);
67
+
68
+ // Generate the VibeClient constructor function
69
+ parts.push(`export function VibeClient(options: VibeClientOptions): VibeClientInstance {
70
+ return createClient({
71
+ options,
72
+ modelMeta: MODEL_META,
73
+ schemas: options.validate ? SCHEMAS : undefined,
74
+ }) as unknown as VibeClientInstance;
75
+ }
76
+ `);
77
+
78
+ return parts.join("\n");
79
+ }
80
+
81
+ function generateModelMeta(params: { schema: Schema }): string {
82
+ const { schema } = params;
83
+
84
+ const modelEntries = schema.models.map((model) => {
85
+ const scalarFields = model.fields
86
+ .filter((f) => f.kind === "scalar" || f.kind === "enum")
87
+ .map((f) => {
88
+ const dbName = f.kind === "scalar" ? f.dbName : f.dbName;
89
+ const isUpdatedAt = f.kind === "scalar" && f.isUpdatedAt;
90
+ const extras: string[] = [];
91
+ if (isUpdatedAt) extras.push(`isUpdatedAt: true`);
92
+ if (f.kind === "scalar") extras.push(`type: "${f.prismaType}"`);
93
+
94
+ // Emit default kind for application-level generation (uuid, cuid, nanoid, ulid)
95
+ if (f.default) {
96
+ const appLevelDefaults = new Set(["uuid", "cuid", "nanoid", "ulid"]);
97
+ if (appLevelDefaults.has(f.default.kind)) {
98
+ extras.push(`hasDefault: "${f.default.kind}"`);
99
+ } else if (f.default.kind !== "dbgenerated") {
100
+ // autoincrement, now, literal — DB handles these, but runtime should know
101
+ extras.push(`hasDefault: true`);
102
+ }
103
+ }
104
+
105
+ // Extract @vibeorm.idPrefix from documentation comment
106
+ const doc = (f.kind === "scalar" || f.kind === "enum") ? f.documentation : undefined;
107
+ if (doc) {
108
+ const prefixMatch = doc.match(/@vibeorm\.idPrefix\(["']([^"']+)["']\)/);
109
+ if (prefixMatch) {
110
+ extras.push(`idPrefix: "${prefixMatch[1]}"`);
111
+ }
112
+ }
113
+
114
+ const extraStr = extras.length > 0 ? `, ${extras.join(", ")}` : "";
115
+ return ` { name: "${f.name}", dbName: "${dbName}", kind: "${f.kind}" as const${extraStr} }`;
116
+ });
117
+
118
+ const relationFields = model.fields
119
+ .filter((f) => f.kind === "relation")
120
+ .map((f) => {
121
+ if (f.kind !== "relation") return "";
122
+ const isM2M = f.relation.type === "manyToMany";
123
+ let joinTableLine = "";
124
+ if (isM2M) {
125
+ const sorted = [model.name, f.relatedModel].sort();
126
+ const jtName = `_${sorted[0]}To${sorted[1]}`;
127
+ joinTableLine = `\n joinTable: "${jtName}",`;
128
+ }
129
+ const relationNameLine = f.relation.name
130
+ ? `\n relationName: "${f.relation.name}",`
131
+ : "";
132
+ return ` {
133
+ name: "${f.name}",
134
+ kind: "relation" as const,
135
+ relatedModel: "${f.relatedModel}",
136
+ type: "${f.relation.type}" as const,
137
+ isList: ${f.isList},
138
+ isForeignKey: ${f.relation.isForeignKey},
139
+ fields: ${JSON.stringify(f.relation.fields)},
140
+ references: ${JSON.stringify(f.relation.references)},${relationNameLine}${joinTableLine}
141
+ }`;
142
+ });
143
+
144
+ const pkFields = JSON.stringify(model.primaryKey.fields);
145
+ const uniqueFields = model.fields
146
+ .filter((f) => (f.kind === "scalar" || f.kind === "enum") && f.isUnique)
147
+ .map((f) => `"${f.name}"`);
148
+
149
+ return ` ${toCamelCase({ str: model.name })}: {
150
+ name: "${model.name}",
151
+ dbName: "${model.dbName}",
152
+ primaryKey: ${pkFields},
153
+ uniqueFields: [${uniqueFields.join(", ")}],
154
+ scalarFields: [
155
+ ${scalarFields.join(",\n")}
156
+ ],
157
+ relationFields: [
158
+ ${relationFields.join(",\n")}
159
+ ],
160
+ }`;
161
+ });
162
+
163
+ return `const MODEL_META = {
164
+ ${modelEntries.join(",\n")}
165
+ } as const;
166
+ `;
167
+ }
168
+
169
+ function generateSchemasMap(params: { schema: Schema }): string {
170
+ const { schema } = params;
171
+
172
+ const entries = schema.models.map((model) => {
173
+ const key = toCamelCase({ str: model.name });
174
+ return ` ${key}: {
175
+ model: ${model.name}Schema,
176
+ createInput: ${model.name}CreateInputSchema,
177
+ updateInput: ${model.name}UpdateInputSchema,
178
+ whereInput: ${model.name}WhereInputSchema,
179
+ }`;
180
+ });
181
+
182
+ return `const SCHEMAS = {
183
+ ${entries.join(",\n")}
184
+ };
185
+ `;
186
+ }