@vibeorm/generator 1.3.0 → 2.0.0-alpha.1

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.
@@ -1,590 +0,0 @@
1
- import type { Model, Schema, ScalarField } from "@vibeorm/parser";
2
- import { fileHeader, toCamelCase } from "../utils.ts";
3
-
4
- const NUMERIC_PRISMA_TYPES = new Set(["Int", "Float", "Decimal", "BigInt"]);
5
-
6
- /**
7
- * Generates delegate interfaces for each model.
8
- * These are the user-facing APIs: db.user.findMany(), db.post.create(), etc.
9
- *
10
- * Each method uses a generic parameter to capture the exact literal type
11
- * of the args object, enabling TypeScript to narrow the return type
12
- * through the GetFindResult conditional type.
13
- */
14
- export function generateDelegates(params: { schema: Schema }): string {
15
- const { schema } = params;
16
- const parts: string[] = [fileHeader()];
17
-
18
- // Imports
19
- const argsImports = schema.models
20
- .flatMap((m) => [
21
- `${m.name}FindManyArgs`,
22
- `${m.name}FindFirstArgs`,
23
- `${m.name}FindUniqueArgs`,
24
- `${m.name}CreateArgs`,
25
- `${m.name}CreateManyArgs`,
26
- `${m.name}CreateManyAndReturnArgs`,
27
- `${m.name}UpdateArgs`,
28
- `${m.name}UpsertArgs`,
29
- `${m.name}DeleteArgs`,
30
- `${m.name}DeleteManyArgs`,
31
- `${m.name}UpdateManyArgs`,
32
- `${m.name}CountArgs`,
33
- `${m.name}AggregateArgs`,
34
- `${m.name}GroupByArgs`,
35
- ])
36
- .join(", ");
37
- parts.push(`import type { ${argsImports} } from "./args.ts";`);
38
-
39
- const payloadImports = schema.models
40
- .map((m) => `$${m.name}Payload`)
41
- .join(", ");
42
-
43
- // Include JsonValue if any model has Json scalar fields (used in aggregate/groupBy types)
44
- const hasJsonField = schema.models.some((m) =>
45
- m.fields.some((f) => f.kind === "scalar" && f.prismaType === "Json")
46
- );
47
- const extraImports = hasJsonField ? ", JsonValue" : "";
48
- parts.push(`import type { ${payloadImports}${extraImports} } from "./models.ts";`);
49
-
50
- parts.push(`import type { GetResult, Exact } from "./result.ts";`);
51
-
52
- // Import enums if any models have enum fields
53
- if (schema.enums.length > 0) {
54
- const enumImports = schema.enums.map((e) => e.name).join(", ");
55
- parts.push(`import type { ${enumImports} } from "./enums.ts";`);
56
- }
57
- parts.push("");
58
-
59
- // Generate delegate for each model
60
- for (const model of schema.models) {
61
- parts.push(generateModelDelegate({ model }));
62
- parts.push(generateAggregateResultType({ model }));
63
- parts.push(generateGroupByResultType({ model }));
64
- }
65
-
66
- return parts.join("\n");
67
- }
68
-
69
- function generateModelDelegate(params: { model: Model }): string {
70
- const { model } = params;
71
- const p = `$${model.name}Payload`;
72
- const modelVar = toCamelCase({ str: model.name });
73
-
74
- const findManyWhereBlock = buildFindManyWhereBlock({ model });
75
- const uniqueWhereBlock = buildUniqueWhereBlock({ model });
76
- const createDataBlock = buildObjectBlock({
77
- key: "data",
78
- lines: getExampleDataLines({ model }),
79
- });
80
- const updateDataBlock = buildObjectBlock({
81
- key: "data",
82
- lines: getExampleUpdateLines({ model }),
83
- });
84
- const createManyDataBlock = buildArrayOfObjectsBlock({
85
- key: "data",
86
- lines: getExampleDataLines({ model }),
87
- });
88
- const upsertCreateBlock = buildObjectBlock({
89
- key: "create",
90
- lines: getExampleDataLines({ model }),
91
- });
92
- const upsertUpdateBlock = buildObjectBlock({
93
- key: "update",
94
- lines: getExampleUpdateLines({ model }),
95
- });
96
- const includeBlock = buildIncludeBlock({ model });
97
- const orderByField = getOrderByFieldName({ model });
98
-
99
- return `/**
100
- * Delegate API for the ${model.name} model.
101
- * Access this via \`db.${modelVar}\`.
102
- */
103
- export type ${model.name}Delegate = {
104
- /**
105
- * Find zero or more ${model.name} records.
106
- *
107
- * @example
108
- * const rows = await db.${modelVar}.findMany();
109
- *
110
- * @example
111
- * const filteredRows = await db.${modelVar}.findMany({
112
- ${findManyWhereBlock}
113
- * });
114
- *
115
- * @example
116
- * const page = await db.${modelVar}.findMany({
117
- ${findManyWhereBlock}${includeBlock}
118
- * orderBy: {
119
- * ${orderByField}: "desc",
120
- * },
121
- * take: 20,
122
- * skip: 0,
123
- * });
124
- */
125
- findMany<T extends ${model.name}FindManyArgs>(
126
- args?: Exact<T, ${model.name}FindManyArgs>,
127
- ): Promise<GetResult<${p}, T, "findMany">>;
128
-
129
- /**
130
- * Find the first ${model.name} record matching a filter.
131
- *
132
- * @example
133
- * const row = await db.${modelVar}.findFirst({
134
- ${findManyWhereBlock}
135
- * orderBy: {
136
- * ${orderByField}: "desc",
137
- * },
138
- * });
139
- */
140
- findFirst<T extends ${model.name}FindFirstArgs>(
141
- args?: Exact<T, ${model.name}FindFirstArgs>,
142
- ): Promise<GetResult<${p}, T, "findFirst">>;
143
-
144
- /**
145
- * Find one ${model.name} record by a unique field.
146
- *
147
- * @example
148
- * const row = await db.${modelVar}.findUnique({
149
- ${uniqueWhereBlock}
150
- * });
151
- */
152
- findUnique<T extends ${model.name}FindUniqueArgs>(
153
- args: Exact<T, ${model.name}FindUniqueArgs>,
154
- ): Promise<GetResult<${p}, T, "findUnique">>;
155
-
156
- /**
157
- * Find one ${model.name} record by a unique field, or throw if not found.
158
- *
159
- * @example
160
- * const row = await db.${modelVar}.findUniqueOrThrow({
161
- ${uniqueWhereBlock}
162
- * });
163
- */
164
- findUniqueOrThrow<T extends ${model.name}FindUniqueArgs>(
165
- args: Exact<T, ${model.name}FindUniqueArgs>,
166
- ): Promise<GetResult<${p}, T, "findUniqueOrThrow">>;
167
-
168
- /**
169
- * Find the first ${model.name} record matching a filter, or throw if not found.
170
- */
171
- findFirstOrThrow<T extends ${model.name}FindFirstArgs>(
172
- args?: Exact<T, ${model.name}FindFirstArgs>,
173
- ): Promise<GetResult<${p}, T, "findFirstOrThrow">>;
174
-
175
- /**
176
- * Create one ${model.name} record.
177
- *
178
- * @example
179
- * const row = await db.${modelVar}.create({
180
- ${createDataBlock}
181
- * });
182
- */
183
- create<T extends ${model.name}CreateArgs>(
184
- args: Exact<T, ${model.name}CreateArgs>,
185
- ): Promise<GetResult<${p}, T, "create">>;
186
-
187
- /**
188
- * Create multiple ${model.name} records in one query.
189
- *
190
- * @example
191
- * const result = await db.${modelVar}.createMany({
192
- ${createManyDataBlock}
193
- * skipDuplicates: true,
194
- * });
195
- */
196
- createMany(
197
- args: ${model.name}CreateManyArgs,
198
- ): Promise<{ count: number }>;
199
-
200
- /**
201
- * Create multiple ${model.name} records and return selected fields.
202
- *
203
- * @example
204
- * const rows = await db.${modelVar}.createManyAndReturn({
205
- ${createManyDataBlock}
206
- * select: {
207
- * // Choose fields to return
208
- * },
209
- * });
210
- */
211
- createManyAndReturn<T extends ${model.name}CreateManyAndReturnArgs>(
212
- args: Exact<T, ${model.name}CreateManyAndReturnArgs>,
213
- ): Promise<GetResult<${p}, T, "createManyAndReturn">>;
214
-
215
- /**
216
- * Update one ${model.name} record.
217
- *
218
- * @example
219
- * const row = await db.${modelVar}.update({
220
- ${uniqueWhereBlock}
221
- ${updateDataBlock}
222
- * });
223
- */
224
- update<T extends ${model.name}UpdateArgs>(
225
- args: Exact<T, ${model.name}UpdateArgs>,
226
- ): Promise<GetResult<${p}, T, "update">>;
227
-
228
- /**
229
- * Update one ${model.name} record if it exists, or create it otherwise.
230
- *
231
- * @example
232
- * const row = await db.${modelVar}.upsert({
233
- ${uniqueWhereBlock}
234
- ${upsertCreateBlock}
235
- ${upsertUpdateBlock}
236
- * });
237
- */
238
- upsert<T extends ${model.name}UpsertArgs>(
239
- args: Exact<T, ${model.name}UpsertArgs>,
240
- ): Promise<GetResult<${p}, T, "upsert">>;
241
-
242
- /**
243
- * Delete one ${model.name} record by a unique field.
244
- *
245
- * @example
246
- * const row = await db.${modelVar}.delete({
247
- ${uniqueWhereBlock}
248
- * });
249
- */
250
- delete<T extends ${model.name}DeleteArgs>(
251
- args: Exact<T, ${model.name}DeleteArgs>,
252
- ): Promise<GetResult<${p}, T, "delete">>;
253
-
254
- /**
255
- * Delete multiple ${model.name} records.
256
- *
257
- * @example
258
- * const result = await db.${modelVar}.deleteMany({
259
- ${findManyWhereBlock}
260
- * });
261
- */
262
- deleteMany(
263
- args?: ${model.name}DeleteManyArgs,
264
- ): Promise<{ count: number }>;
265
-
266
- /**
267
- * Update multiple ${model.name} records.
268
- *
269
- * @example
270
- * const result = await db.${modelVar}.updateMany({
271
- ${findManyWhereBlock}
272
- ${updateDataBlock}
273
- * });
274
- */
275
- updateMany(
276
- args: ${model.name}UpdateManyArgs,
277
- ): Promise<{ count: number }>;
278
-
279
- /**
280
- * Count ${model.name} records.
281
- *
282
- * @example
283
- * const total = await db.${modelVar}.count({
284
- ${findManyWhereBlock}
285
- * });
286
- */
287
- count(
288
- args?: ${model.name}CountArgs,
289
- ): Promise<number>;
290
-
291
- /**
292
- * Run aggregate calculations on ${model.name} records.
293
- *
294
- * @example
295
- * const stats = await db.${modelVar}.aggregate({
296
- ${findManyWhereBlock}
297
- * _count: true,
298
- * _avg: {
299
- * // Choose numeric fields
300
- * },
301
- * });
302
- */
303
- aggregate(
304
- args: ${model.name}AggregateArgs,
305
- ): Promise<Aggregate${model.name}Result>;
306
-
307
- /**
308
- * Group ${model.name} records by scalar fields.
309
- *
310
- * @example
311
- * const grouped = await db.${modelVar}.groupBy({
312
- * by: ["${orderByField}"],
313
- ${findManyWhereBlock}
314
- * _count: true,
315
- * });
316
- */
317
- groupBy(
318
- args: ${model.name}GroupByArgs,
319
- ): Promise<${model.name}GroupByResult[]>;
320
- };
321
- `;
322
- }
323
-
324
- function buildObjectBlock(params: { key: string; lines: string[] }): string {
325
- const { key, lines } = params;
326
- const safeLines = lines.length > 0 ? lines : ["// Add model fields here"];
327
- return ` * ${key}: {
328
- ${safeLines.map((line) => ` * ${line}`).join("\n")}
329
- * },`;
330
- }
331
-
332
- function buildArrayOfObjectsBlock(params: { key: string; lines: string[] }): string {
333
- const { key, lines } = params;
334
- const safeLines = lines.length > 0 ? lines : ["// Add model fields here"];
335
- return ` * ${key}: [
336
- * {
337
- ${safeLines.map((line) => ` * ${line}`).join("\n")}
338
- * },
339
- * ],`;
340
- }
341
-
342
- function buildFindManyWhereBlock(params: { model: Model }): string {
343
- return buildObjectBlock({
344
- key: "where",
345
- lines: getFindManyWhereLines({ model: params.model }),
346
- });
347
- }
348
-
349
- function buildUniqueWhereBlock(params: { model: Model }): string {
350
- return buildObjectBlock({
351
- key: "where",
352
- lines: getUniqueWhereLines({ model: params.model }),
353
- });
354
- }
355
-
356
- function buildIncludeBlock(params: { model: Model }): string {
357
- const relationField = params.model.fields.find((f) => f.kind === "relation");
358
- if (!relationField) return "";
359
- return `
360
- * include: {
361
- * ${relationField.name}: true,
362
- * },`;
363
- }
364
-
365
- function getFindManyWhereLines(params: { model: Model }): string[] {
366
- const field = getPreferredWhereField({ model: params.model });
367
- if (!field) {
368
- return ["// Add model-specific filters here"];
369
- }
370
-
371
- const fieldNameLower = field.name.toLowerCase();
372
- if (field.prismaType === "String") {
373
- if (fieldNameLower.includes("email")) {
374
- return [`${field.name}: { endsWith: "@example.com" },`];
375
- }
376
- return [`${field.name}: { contains: "sample" },`];
377
- }
378
-
379
- if (field.prismaType === "DateTime") {
380
- return [`${field.name}: { gte: new Date("2024-01-01T00:00:00.000Z") },`];
381
- }
382
-
383
- if (field.prismaType === "Boolean") {
384
- return [`${field.name}: true,`];
385
- }
386
-
387
- if (field.prismaType === "BigInt") {
388
- return [`${field.name}: { gte: 1n },`];
389
- }
390
-
391
- if (field.prismaType === "Decimal") {
392
- return [`${field.name}: { gte: "1.00" },`];
393
- }
394
-
395
- if (field.prismaType === "Int" || field.prismaType === "Float") {
396
- return [`${field.name}: { gte: 1 },`];
397
- }
398
-
399
- if (field.prismaType === "Json") {
400
- return [`${field.name}: { equals: { key: "value" } },`];
401
- }
402
-
403
- if (field.prismaType === "Bytes") {
404
- return [`${field.name}: { equals: new Uint8Array([1, 2, 3]) },`];
405
- }
406
-
407
- return [`${field.name}: { equals: ${getSampleValueForScalarField({ field })} },`];
408
- }
409
-
410
- function getUniqueWhereLines(params: { model: Model }): string[] {
411
- const uniqueField = params.model.fields.find(
412
- (f): f is ScalarField =>
413
- f.kind === "scalar" && !f.isList && (f.isId || f.isUnique)
414
- );
415
-
416
- if (!uniqueField) {
417
- return ["// Provide a unique field (for example id or email)"];
418
- }
419
-
420
- return [`${uniqueField.name}: ${getSampleValueForScalarField({ field: uniqueField })},`];
421
- }
422
-
423
- function getExampleDataLines(params: { model: Model }): string[] {
424
- const candidates = params.model.fields.filter(
425
- (f): f is ScalarField =>
426
- f.kind === "scalar" &&
427
- !f.isList &&
428
- !f.isId &&
429
- !f.isUpdatedAt &&
430
- !f.default
431
- );
432
-
433
- const selected = candidates.length > 0
434
- ? candidates
435
- : params.model.fields.filter(
436
- (f): f is ScalarField =>
437
- f.kind === "scalar" && !f.isList && !f.isId && !f.isUpdatedAt
438
- );
439
-
440
- if (selected.length === 0) {
441
- return ["// Add model fields here"];
442
- }
443
-
444
- return selected.slice(0, 2).map((field) => {
445
- return `${field.name}: ${getSampleValueForScalarField({ field })},`;
446
- });
447
- }
448
-
449
- function getExampleUpdateLines(params: { model: Model }): string[] {
450
- const selected = params.model.fields.filter(
451
- (f): f is ScalarField =>
452
- f.kind === "scalar" && !f.isList && !f.isId && !f.isUpdatedAt
453
- );
454
-
455
- if (selected.length === 0) {
456
- return ["// Add fields to update"];
457
- }
458
-
459
- return selected.slice(0, 2).map((field) => {
460
- return `${field.name}: ${getSampleValueForScalarField({ field })},`;
461
- });
462
- }
463
-
464
- function getPreferredWhereField(params: { model: Model }): ScalarField | undefined {
465
- const fields = params.model.fields.filter(
466
- (f): f is ScalarField => f.kind === "scalar" && !f.isList
467
- );
468
-
469
- return fields.find((f) => !f.isId && f.prismaType === "String")
470
- ?? fields.find((f) => !f.isId && f.prismaType === "DateTime")
471
- ?? fields.find((f) => !f.isId && NUMERIC_PRISMA_TYPES.has(f.prismaType))
472
- ?? fields.find((f) => !f.isId && f.prismaType === "Boolean")
473
- ?? fields.find((f) => !f.isId)
474
- ?? fields[0];
475
- }
476
-
477
- function getOrderByFieldName(params: { model: Model }): string {
478
- const scalarFields = params.model.fields.filter(
479
- (f): f is ScalarField => f.kind === "scalar" && !f.isList
480
- );
481
-
482
- const preferred = scalarFields.find((f) => f.name === "createdAt")
483
- ?? scalarFields.find((f) => f.name === "updatedAt")
484
- ?? scalarFields.find((f) => f.isId)
485
- ?? scalarFields[0];
486
-
487
- return preferred?.name ?? "id";
488
- }
489
-
490
- function getSampleValueForScalarField(params: { field: ScalarField }): string {
491
- const { field } = params;
492
- const fieldNameLower = field.name.toLowerCase();
493
-
494
- if (field.prismaType === "String") {
495
- if (fieldNameLower.includes("email")) return '"user@example.com"';
496
- if (fieldNameLower.includes("name")) return '"Example"';
497
- if (fieldNameLower.includes("title")) return '"Example title"';
498
- return '"sample"';
499
- }
500
-
501
- if (field.prismaType === "Boolean") return "true";
502
- if (field.prismaType === "Int" || field.prismaType === "Float") return "1";
503
- if (field.prismaType === "Decimal") return '"1.00"';
504
- if (field.prismaType === "BigInt") return "1n";
505
- if (field.prismaType === "DateTime") return 'new Date("2024-01-01T00:00:00.000Z")';
506
- if (field.prismaType === "Json") return '{ key: "value" }';
507
- if (field.prismaType === "Bytes") return "new Uint8Array([1, 2, 3])";
508
-
509
- return "undefined";
510
- }
511
-
512
- function generateAggregateResultType(params: { model: Model }): string {
513
- const { model } = params;
514
-
515
- const scalarFields = model.fields.filter(
516
- (f): f is ScalarField => f.kind === "scalar"
517
- );
518
- const numericFields = scalarFields.filter(
519
- (f) => NUMERIC_PRISMA_TYPES.has(f.prismaType)
520
- );
521
-
522
- const countFields = [
523
- ` _all: number;`,
524
- ...scalarFields.map((f) => ` ${f.name}: number;`),
525
- ];
526
-
527
- const avgFields = numericFields.map((f) => ` ${f.name}: number | null;`);
528
- const sumFields = numericFields.map((f) => ` ${f.name}: number | null;`);
529
- const minFields = scalarFields.map((f) => {
530
- const tsType = f.tsType;
531
- return ` ${f.name}: ${tsType} | null;`;
532
- });
533
- const maxFields = scalarFields.map((f) => {
534
- const tsType = f.tsType;
535
- return ` ${f.name}: ${tsType} | null;`;
536
- });
537
-
538
- return `export type Aggregate${model.name}Result = {
539
- _count: number | {
540
- ${countFields.join("\n")}
541
- };
542
- _avg: {
543
- ${avgFields.length > 0 ? avgFields.join("\n") : " [key: string]: never;"}
544
- } | null;
545
- _sum: {
546
- ${sumFields.length > 0 ? sumFields.join("\n") : " [key: string]: never;"}
547
- } | null;
548
- _min: {
549
- ${minFields.join("\n")}
550
- } | null;
551
- _max: {
552
- ${maxFields.join("\n")}
553
- } | null;
554
- };
555
- `;
556
- }
557
-
558
- function generateGroupByResultType(params: { model: Model }): string {
559
- const { model } = params;
560
-
561
- const scalarFields = model.fields.filter(
562
- (f): f is ScalarField => f.kind === "scalar"
563
- );
564
-
565
- // GroupBy result is a partial of scalars + aggregate fields
566
- const scalarEntries = scalarFields.map((f) => {
567
- const tsType = f.tsType;
568
- const nullable = f.isRequired ? "" : " | null";
569
- return ` ${f.name}?: ${tsType}${nullable};`;
570
- });
571
-
572
- // Also include enum fields
573
- const enumEntries = model.fields
574
- .filter((f) => f.kind === "enum")
575
- .map((f) => {
576
- if (f.kind !== "enum") return "";
577
- const nullable = f.isRequired ? "" : " | null";
578
- return ` ${f.name}?: ${f.enumName}${nullable};`;
579
- });
580
-
581
- return `export type ${model.name}GroupByResult = {
582
- ${[...scalarEntries, ...enumEntries].join("\n")}
583
- _count?: number | Record<string, number>;
584
- _avg?: Record<string, number | null>;
585
- _sum?: Record<string, number | null>;
586
- _min?: Record<string, unknown>;
587
- _max?: Record<string, unknown>;
588
- };
589
- `;
590
- }
@@ -1,28 +0,0 @@
1
- import type { Enum } from "@vibeorm/parser";
2
- import { fileHeader } from "../utils.ts";
3
-
4
- export function generateEnums(params: { enums: Enum[] }): string {
5
- const { enums } = params;
6
-
7
- if (enums.length === 0) {
8
- return fileHeader() + "\nexport {};\n";
9
- }
10
-
11
- const parts: string[] = [fileHeader()];
12
-
13
- for (const enumDef of enums) {
14
- // Generate a const object (not a TS enum — objects are more flexible)
15
- const values = enumDef.values
16
- .map((v) => ` ${v.name}: "${v.name}" as const`)
17
- .join(",\n");
18
-
19
- parts.push(`export const ${enumDef.name} = {
20
- ${values},
21
- } as const;
22
-
23
- export type ${enumDef.name} = (typeof ${enumDef.name})[keyof typeof ${enumDef.name}];
24
- `);
25
- }
26
-
27
- return parts.join("\n");
28
- }