@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.
@@ -0,0 +1,626 @@
1
+ import type {
2
+ Model,
3
+ Schema,
4
+ ScalarField,
5
+ EnumField,
6
+ RelationField,
7
+ PrismaScalarType,
8
+ } from "@vibeorm/parser";
9
+ import { fileHeader } from "../utils.ts";
10
+
11
+ /**
12
+ * Generates WhereInput, OrderByInput, CreateInput, UpdateInput types for each model.
13
+ */
14
+ export function generateInputs(params: { schema: Schema }): string {
15
+ const { schema } = params;
16
+ const parts: string[] = [fileHeader()];
17
+
18
+ // Import enums
19
+ if (schema.enums.length > 0) {
20
+ const enumImports = schema.enums.map((e) => e.name).join(", ");
21
+ parts.push(`import type { ${enumImports} } from "./enums.ts";`);
22
+ }
23
+
24
+ // Import model types for relation filter references
25
+ parts.push(`import type { JsonValue } from "./models.ts";\n`);
26
+
27
+ // Sort direction
28
+ parts.push(`export type SortOrder = "asc" | "desc";
29
+
30
+ export type SortOrderWithNulls = {
31
+ sort: SortOrder;
32
+ nulls?: "first" | "last";
33
+ };
34
+ `);
35
+
36
+ // Generate for each model
37
+ for (const model of schema.models) {
38
+ parts.push(generateWhereInput({ model, schema }));
39
+ parts.push(generateWhereUniqueInput({ model }));
40
+ parts.push(generateOrderByInput({ model }));
41
+ parts.push(generateCreateInput({ model, schema }));
42
+ parts.push(generateUpdateInput({ model, schema }));
43
+ }
44
+
45
+ return parts.join("\n");
46
+ }
47
+
48
+ // ─── Where Input ──────────────────────────────────────────────────
49
+
50
+ function generateWhereInput(params: { model: Model; schema: Schema }): string {
51
+ const { model } = params;
52
+
53
+ const entries: string[] = [];
54
+
55
+ // Logical combinators
56
+ entries.push(` AND?: ${model.name}WhereInput | ${model.name}WhereInput[];`);
57
+ entries.push(` OR?: ${model.name}WhereInput[];`);
58
+ entries.push(` NOT?: ${model.name}WhereInput | ${model.name}WhereInput[];`);
59
+
60
+ for (const field of model.fields) {
61
+ if (field.kind === "scalar") {
62
+ if (field.isList) {
63
+ // Array field — use list filter type
64
+ const listFilterType = scalarListFilterType({ field });
65
+ entries.push(` ${field.name}?: ${listFilterType};`);
66
+ } else {
67
+ entries.push(` ${field.name}?: ${scalarFilterType({ field })} | ${scalarTsType({ field })};`);
68
+ }
69
+ } else if (field.kind === "enum") {
70
+ entries.push(` ${field.name}?: EnumFilter<${field.enumName}> | ${field.enumName};`);
71
+ } else if (field.kind === "relation") {
72
+ if (field.isList) {
73
+ entries.push(` ${field.name}?: ${field.relatedModel}ListRelationFilter;`);
74
+ } else {
75
+ entries.push(` ${field.name}?: ${field.relatedModel}WhereInput | ${field.isRequired ? "" : "null | "}${field.relatedModel}RelationFilter;`);
76
+ }
77
+ }
78
+ }
79
+
80
+ return `export type ${model.name}WhereInput = {
81
+ ${entries.join("\n")}
82
+ };
83
+ `;
84
+ }
85
+
86
+ function generateWhereUniqueInput(params: { model: Model }): string {
87
+ const { model } = params;
88
+
89
+ // Collect unique-able fields: @id, @unique, @@id, @@unique
90
+ const uniqueFields: string[] = [];
91
+
92
+ for (const field of model.fields) {
93
+ if (field.kind === "scalar" || field.kind === "enum") {
94
+ if (field.isId || field.isUnique) {
95
+ uniqueFields.push(field.name);
96
+ }
97
+ }
98
+ }
99
+
100
+ if (uniqueFields.length === 0) {
101
+ // Fallback: use all primary key fields
102
+ for (const pkField of model.primaryKey.fields) {
103
+ uniqueFields.push(pkField);
104
+ }
105
+ }
106
+
107
+ const entries = uniqueFields.map((fieldName) => {
108
+ const field = model.fields.find((f) => f.name === fieldName);
109
+ if (!field || field.kind === "relation") return "";
110
+ const tsType = field.kind === "scalar" ? field.tsType : field.enumName;
111
+ return ` ${fieldName}?: ${tsType};`;
112
+ });
113
+
114
+ // Also support composite unique if @@id is composite
115
+ if (model.primaryKey.isComposite) {
116
+ const compositeType = model.primaryKey.fields
117
+ .map((fn) => {
118
+ const field = model.fields.find((f) => f.name === fn);
119
+ if (!field || field.kind === "relation") return `${fn}: unknown`;
120
+ const tsType = field.kind === "scalar" ? field.tsType : field.enumName;
121
+ return `${fn}: ${tsType}`;
122
+ })
123
+ .join("; ");
124
+ entries.push(` ${model.primaryKey.fields.join("_")}?: { ${compositeType} };`);
125
+ }
126
+
127
+ return `export type ${model.name}WhereUniqueInput = {
128
+ ${entries.join("\n")}
129
+ };
130
+ `;
131
+ }
132
+
133
+ // ─── OrderBy Input ────────────────────────────────────────────────
134
+
135
+ function generateOrderByInput(params: { model: Model }): string {
136
+ const { model } = params;
137
+
138
+ const entries: string[] = [];
139
+
140
+ for (const field of model.fields) {
141
+ if (field.kind === "scalar" || field.kind === "enum") {
142
+ entries.push(` ${field.name}?: SortOrder | SortOrderWithNulls;`);
143
+ } else if (field.kind === "relation") {
144
+ if (field.isList) {
145
+ // Order by relation count: { posts: { _count: "desc" } }
146
+ entries.push(` ${field.name}?: { _count?: SortOrder };`);
147
+ } else {
148
+ // Order by relation field: { author: { name: "asc" } }
149
+ entries.push(` ${field.name}?: ${field.relatedModel}OrderByInput;`);
150
+ }
151
+ }
152
+ }
153
+
154
+ return `export type ${model.name}OrderByInput = {
155
+ ${entries.join("\n")}
156
+ };
157
+ `;
158
+ }
159
+
160
+ // ─── Create Input ─────────────────────────────────────────────────
161
+
162
+ function generateCreateInput(params: {
163
+ model: Model;
164
+ schema: Schema;
165
+ }): string {
166
+ const { model } = params;
167
+
168
+ const entries: string[] = [];
169
+
170
+ for (const field of model.fields) {
171
+ if (field.kind === "scalar") {
172
+ // Skip auto-generated fields (autoincrement, now, uuid, cuid, @updatedAt)
173
+ const isAutoGenerated = field.default &&
174
+ (field.default.kind === "autoincrement" ||
175
+ field.default.kind === "now" ||
176
+ field.default.kind === "uuid" ||
177
+ field.default.kind === "cuid" ||
178
+ field.default.kind === "nanoid" ||
179
+ field.default.kind === "ulid");
180
+ const isOptional = !field.isRequired || field.default !== undefined || isAutoGenerated || field.isUpdatedAt;
181
+ const tsType = scalarTsType({ field });
182
+ entries.push(` ${field.name}${isOptional ? "?" : ""}: ${tsType};`);
183
+ } else if (field.kind === "enum") {
184
+ const isOptional = !field.isRequired || field.default !== undefined;
185
+ entries.push(` ${field.name}${isOptional ? "?" : ""}: ${field.enumName};`);
186
+ } else if (field.kind === "relation") {
187
+ // Relation fields in create — nested create/connect
188
+ if (field.isList) {
189
+ entries.push(` ${field.name}?: {
190
+ create?: ${field.relatedModel}CreateInput | ${field.relatedModel}CreateInput[];
191
+ connect?: ${field.relatedModel}WhereUniqueInput | ${field.relatedModel}WhereUniqueInput[];
192
+ connectOrCreate?: { where: ${field.relatedModel}WhereUniqueInput; create: ${field.relatedModel}CreateInput } | { where: ${field.relatedModel}WhereUniqueInput; create: ${field.relatedModel}CreateInput }[];
193
+ };`);
194
+ } else {
195
+ entries.push(` ${field.name}?: {
196
+ create?: ${field.relatedModel}CreateInput;
197
+ connect?: ${field.relatedModel}WhereUniqueInput;
198
+ connectOrCreate?: { where: ${field.relatedModel}WhereUniqueInput; create: ${field.relatedModel}CreateInput };
199
+ };`);
200
+ }
201
+ }
202
+ }
203
+
204
+ return `export type ${model.name}CreateInput = {
205
+ ${entries.join("\n")}
206
+ };
207
+ `;
208
+ }
209
+
210
+ // ─── Update Input ─────────────────────────────────────────────────
211
+
212
+ function generateUpdateInput(params: {
213
+ model: Model;
214
+ schema: Schema;
215
+ }): string {
216
+ const { model } = params;
217
+
218
+ const entries: string[] = [];
219
+
220
+ for (const field of model.fields) {
221
+ if (field.kind === "scalar") {
222
+ if (field.isList) {
223
+ // Array field — use list update operations
224
+ const listUpdateType = scalarListUpdateType({ field });
225
+ entries.push(` ${field.name}?: ${listUpdateType};`);
226
+ } else {
227
+ const tsType = scalarTsType({ field });
228
+ const atomicType = numericFieldUpdateType({ field });
229
+ if (atomicType) {
230
+ entries.push(` ${field.name}?: ${tsType} | ${atomicType};`);
231
+ } else {
232
+ entries.push(` ${field.name}?: ${tsType};`);
233
+ }
234
+ }
235
+ } else if (field.kind === "enum") {
236
+ entries.push(` ${field.name}?: ${field.enumName};`);
237
+ } else if (field.kind === "relation") {
238
+ if (field.isList) {
239
+ entries.push(` ${field.name}?: {
240
+ create?: ${field.relatedModel}CreateInput | ${field.relatedModel}CreateInput[];
241
+ connect?: ${field.relatedModel}WhereUniqueInput | ${field.relatedModel}WhereUniqueInput[];
242
+ connectOrCreate?: { where: ${field.relatedModel}WhereUniqueInput; create: ${field.relatedModel}CreateInput } | { where: ${field.relatedModel}WhereUniqueInput; create: ${field.relatedModel}CreateInput }[];
243
+ disconnect?: ${field.relatedModel}WhereUniqueInput | ${field.relatedModel}WhereUniqueInput[];
244
+ delete?: ${field.relatedModel}WhereUniqueInput | ${field.relatedModel}WhereUniqueInput[];
245
+ set?: ${field.relatedModel}WhereUniqueInput[];
246
+ update?: { where: ${field.relatedModel}WhereUniqueInput; data: ${field.relatedModel}UpdateInput } | { where: ${field.relatedModel}WhereUniqueInput; data: ${field.relatedModel}UpdateInput }[];
247
+ upsert?: { where: ${field.relatedModel}WhereUniqueInput; create: ${field.relatedModel}CreateInput; update: ${field.relatedModel}UpdateInput } | { where: ${field.relatedModel}WhereUniqueInput; create: ${field.relatedModel}CreateInput; update: ${field.relatedModel}UpdateInput }[];
248
+ updateMany?: { where: ${field.relatedModel}WhereInput; data: ${field.relatedModel}UpdateInput } | { where: ${field.relatedModel}WhereInput; data: ${field.relatedModel}UpdateInput }[];
249
+ deleteMany?: ${field.relatedModel}WhereInput | ${field.relatedModel}WhereInput[];
250
+ };`);
251
+ } else {
252
+ entries.push(` ${field.name}?: {
253
+ create?: ${field.relatedModel}CreateInput;
254
+ connect?: ${field.relatedModel}WhereUniqueInput;
255
+ connectOrCreate?: { where: ${field.relatedModel}WhereUniqueInput; create: ${field.relatedModel}CreateInput };
256
+ disconnect?: boolean;
257
+ delete?: boolean;
258
+ update?: ${field.relatedModel}UpdateInput;
259
+ upsert?: { create: ${field.relatedModel}CreateInput; update: ${field.relatedModel}UpdateInput };
260
+ };`);
261
+ }
262
+ }
263
+ }
264
+
265
+ return `export type ${model.name}UpdateInput = {
266
+ ${entries.join("\n")}
267
+ };
268
+ `;
269
+ }
270
+
271
+ // ─── Scalar Filter Types ──────────────────────────────────────────
272
+
273
+ function scalarTsType(params: { field: ScalarField }): string {
274
+ const { field } = params;
275
+ const base = field.tsType;
276
+ if (field.isList) return `${base}[]`;
277
+ if (!field.isRequired) return `${base} | null`;
278
+ return base;
279
+ }
280
+
281
+ function scalarFilterType(params: { field: ScalarField }): string {
282
+ const { field } = params;
283
+ const nullable = !field.isRequired && !field.isList;
284
+
285
+ switch (field.prismaType) {
286
+ case "String":
287
+ return nullable ? "NullableStringFilter" : "StringFilter";
288
+ case "Int":
289
+ case "Float":
290
+ case "Decimal":
291
+ return nullable ? "NullableNumberFilter" : "NumberFilter";
292
+ case "BigInt":
293
+ return nullable ? "NullableBigIntFilter" : "BigIntFilter";
294
+ case "Boolean":
295
+ return nullable ? "NullableBooleanFilter" : "BooleanFilter";
296
+ case "DateTime":
297
+ return nullable ? "NullableDateTimeFilter" : "DateTimeFilter";
298
+ case "Json":
299
+ return "JsonFilter";
300
+ case "Bytes":
301
+ return nullable ? "NullableBytesFilter" : "BytesFilter";
302
+ default:
303
+ return nullable ? "NullableStringFilter" : "StringFilter";
304
+ }
305
+ }
306
+
307
+ function numericFieldUpdateType(params: { field: ScalarField }): string | null {
308
+ switch (params.field.prismaType) {
309
+ case "Int":
310
+ return "IntFieldUpdateOperationsInput";
311
+ case "Float":
312
+ case "Decimal":
313
+ return "FloatFieldUpdateOperationsInput";
314
+ case "BigInt":
315
+ return "BigIntFieldUpdateOperationsInput";
316
+ default:
317
+ return null;
318
+ }
319
+ }
320
+
321
+ function scalarListFilterType(params: { field: ScalarField }): string {
322
+ switch (params.field.prismaType) {
323
+ case "String":
324
+ return "StringListFilter";
325
+ case "Int":
326
+ case "Float":
327
+ case "Decimal":
328
+ return "NumberListFilter";
329
+ case "BigInt":
330
+ return "BigIntListFilter";
331
+ case "Boolean":
332
+ return "BooleanListFilter";
333
+ case "DateTime":
334
+ return "DateTimeListFilter";
335
+ default:
336
+ return "StringListFilter";
337
+ }
338
+ }
339
+
340
+ function scalarListUpdateType(params: { field: ScalarField }): string {
341
+ switch (params.field.prismaType) {
342
+ case "String":
343
+ return "StringListUpdateInput";
344
+ case "Int":
345
+ case "Float":
346
+ case "Decimal":
347
+ return "NumberListUpdateInput";
348
+ case "BigInt":
349
+ return "BigIntListUpdateInput";
350
+ case "Boolean":
351
+ return "BooleanListUpdateInput";
352
+ case "DateTime":
353
+ return "DateTimeListUpdateInput";
354
+ default:
355
+ return "StringListUpdateInput";
356
+ }
357
+ }
358
+
359
+ // The filter types will be generated as shared types at the bottom of the file
360
+ export function generateFilterTypes(): string {
361
+ return `
362
+ // ─── Shared Filter Types ──────────────────────────────────────────
363
+
364
+ export type StringFilter = {
365
+ equals?: string;
366
+ not?: string | StringFilter;
367
+ in?: string[];
368
+ notIn?: string[];
369
+ lt?: string;
370
+ lte?: string;
371
+ gt?: string;
372
+ gte?: string;
373
+ contains?: string;
374
+ startsWith?: string;
375
+ endsWith?: string;
376
+ mode?: "default" | "insensitive";
377
+ };
378
+
379
+ export type NumberFilter = {
380
+ equals?: number;
381
+ not?: number | NumberFilter;
382
+ in?: number[];
383
+ notIn?: number[];
384
+ lt?: number;
385
+ lte?: number;
386
+ gt?: number;
387
+ gte?: number;
388
+ };
389
+
390
+ export type BigIntFilter = {
391
+ equals?: bigint;
392
+ not?: bigint | BigIntFilter;
393
+ in?: bigint[];
394
+ notIn?: bigint[];
395
+ lt?: bigint;
396
+ lte?: bigint;
397
+ gt?: bigint;
398
+ gte?: bigint;
399
+ };
400
+
401
+ export type BooleanFilter = {
402
+ equals?: boolean;
403
+ not?: boolean | BooleanFilter;
404
+ };
405
+
406
+ export type DateTimeFilter = {
407
+ equals?: Date | string;
408
+ not?: Date | string | DateTimeFilter;
409
+ in?: (Date | string)[];
410
+ notIn?: (Date | string)[];
411
+ lt?: Date | string;
412
+ lte?: Date | string;
413
+ gt?: Date | string;
414
+ gte?: Date | string;
415
+ };
416
+
417
+ export type JsonFilter = {
418
+ equals?: JsonValue;
419
+ not?: JsonValue;
420
+ path?: string[];
421
+ string_contains?: string;
422
+ string_starts_with?: string;
423
+ string_ends_with?: string;
424
+ array_contains?: JsonValue;
425
+ array_starts_with?: JsonValue;
426
+ array_ends_with?: JsonValue;
427
+ };
428
+
429
+ export type BytesFilter = {
430
+ equals?: Buffer;
431
+ not?: Buffer | BytesFilter;
432
+ };
433
+
434
+ export type EnumFilter<T extends string> = {
435
+ equals?: T;
436
+ not?: T | EnumFilter<T>;
437
+ in?: T[];
438
+ notIn?: T[];
439
+ };
440
+
441
+ // ─── Nullable Filter Types ───────────────────────────────────────
442
+ // These allow null in equals/not for nullable fields (e.g. { not: null })
443
+
444
+ export type NullableStringFilter = {
445
+ equals?: string | null;
446
+ not?: string | null | NullableStringFilter;
447
+ in?: string[];
448
+ notIn?: string[];
449
+ lt?: string;
450
+ lte?: string;
451
+ gt?: string;
452
+ gte?: string;
453
+ contains?: string;
454
+ startsWith?: string;
455
+ endsWith?: string;
456
+ mode?: "default" | "insensitive";
457
+ };
458
+
459
+ export type NullableNumberFilter = {
460
+ equals?: number | null;
461
+ not?: number | null | NullableNumberFilter;
462
+ in?: number[];
463
+ notIn?: number[];
464
+ lt?: number;
465
+ lte?: number;
466
+ gt?: number;
467
+ gte?: number;
468
+ };
469
+
470
+ export type NullableBigIntFilter = {
471
+ equals?: bigint | null;
472
+ not?: bigint | null | NullableBigIntFilter;
473
+ in?: bigint[];
474
+ notIn?: bigint[];
475
+ lt?: bigint;
476
+ lte?: bigint;
477
+ gt?: bigint;
478
+ gte?: bigint;
479
+ };
480
+
481
+ export type NullableBooleanFilter = {
482
+ equals?: boolean | null;
483
+ not?: boolean | null | NullableBooleanFilter;
484
+ };
485
+
486
+ export type NullableDateTimeFilter = {
487
+ equals?: Date | string | null;
488
+ not?: Date | string | null | NullableDateTimeFilter;
489
+ in?: (Date | string)[];
490
+ notIn?: (Date | string)[];
491
+ lt?: Date | string;
492
+ lte?: Date | string;
493
+ gt?: Date | string;
494
+ gte?: Date | string;
495
+ };
496
+
497
+ export type NullableBytesFilter = {
498
+ equals?: Buffer | null;
499
+ not?: Buffer | null | NullableBytesFilter;
500
+ };
501
+
502
+ // ─── Atomic Number Operations ────────────────────────────────────
503
+ // Used in UpdateInput for numeric fields: { viewCount: { increment: 1 } }
504
+
505
+ export type IntFieldUpdateOperationsInput = {
506
+ set?: number;
507
+ increment?: number;
508
+ decrement?: number;
509
+ multiply?: number;
510
+ divide?: number;
511
+ };
512
+
513
+ export type FloatFieldUpdateOperationsInput = {
514
+ set?: number;
515
+ increment?: number;
516
+ decrement?: number;
517
+ multiply?: number;
518
+ divide?: number;
519
+ };
520
+
521
+ export type BigIntFieldUpdateOperationsInput = {
522
+ set?: bigint;
523
+ increment?: bigint;
524
+ decrement?: bigint;
525
+ multiply?: bigint;
526
+ divide?: bigint;
527
+ };
528
+
529
+ // ─── Scalar List Filter Types ────────────────────────────────────
530
+ // Used for String[], Int[], etc. array fields
531
+
532
+ export type StringListFilter = {
533
+ has?: string;
534
+ hasEvery?: string[];
535
+ hasSome?: string[];
536
+ isEmpty?: boolean;
537
+ equals?: string[];
538
+ };
539
+
540
+ export type NumberListFilter = {
541
+ has?: number;
542
+ hasEvery?: number[];
543
+ hasSome?: number[];
544
+ isEmpty?: boolean;
545
+ equals?: number[];
546
+ };
547
+
548
+ export type BigIntListFilter = {
549
+ has?: bigint;
550
+ hasEvery?: bigint[];
551
+ hasSome?: bigint[];
552
+ isEmpty?: boolean;
553
+ equals?: bigint[];
554
+ };
555
+
556
+ export type BooleanListFilter = {
557
+ has?: boolean;
558
+ hasEvery?: boolean[];
559
+ hasSome?: boolean[];
560
+ isEmpty?: boolean;
561
+ equals?: boolean[];
562
+ };
563
+
564
+ export type DateTimeListFilter = {
565
+ has?: Date | string;
566
+ hasEvery?: (Date | string)[];
567
+ hasSome?: (Date | string)[];
568
+ isEmpty?: boolean;
569
+ equals?: (Date | string)[];
570
+ };
571
+
572
+ // ─── Scalar List Update Operations ───────────────────────────────
573
+
574
+ export type StringListUpdateInput = {
575
+ set?: string[];
576
+ push?: string | string[];
577
+ };
578
+
579
+ export type NumberListUpdateInput = {
580
+ set?: number[];
581
+ push?: number | number[];
582
+ };
583
+
584
+ export type BigIntListUpdateInput = {
585
+ set?: bigint[];
586
+ push?: bigint | bigint[];
587
+ };
588
+
589
+ export type BooleanListUpdateInput = {
590
+ set?: boolean[];
591
+ push?: boolean | boolean[];
592
+ };
593
+
594
+ export type DateTimeListUpdateInput = {
595
+ set?: (Date | string)[];
596
+ push?: Date | string | (Date | string)[];
597
+ };
598
+ `;
599
+ }
600
+
601
+ // Relation filter helper types
602
+ export function generateRelationFilterTypes(params: {
603
+ schema: Schema;
604
+ }): string {
605
+ const { schema } = params;
606
+ const parts: string[] = [];
607
+
608
+ for (const model of schema.models) {
609
+ // List relation filter (for oneToMany/manyToMany)
610
+ parts.push(`export type ${model.name}ListRelationFilter = {
611
+ every?: ${model.name}WhereInput;
612
+ some?: ${model.name}WhereInput;
613
+ none?: ${model.name}WhereInput;
614
+ };
615
+ `);
616
+
617
+ // Single relation filter (for oneToOne/manyToOne)
618
+ parts.push(`export type ${model.name}RelationFilter = {
619
+ is?: ${model.name}WhereInput | null;
620
+ isNot?: ${model.name}WhereInput | null;
621
+ };
622
+ `);
623
+ }
624
+
625
+ return parts.join("\n");
626
+ }