@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,322 +0,0 @@
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
- }
@@ -1,252 +0,0 @@
1
- import type { Model, 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(`export * from "./view-types.ts";`);
22
- parts.push(``);
23
-
24
- // Import delegate types
25
- const delegateImports = schema.models
26
- .map((m) => `${m.name}Delegate`)
27
- .join(", ");
28
- parts.push(`import type { ${delegateImports} } from "./delegates.ts";`);
29
-
30
- // Import view types
31
- parts.push(`import type { ViewDefinitionInput, ViewClient } from "./view-types.ts";`);
32
-
33
- // Import runtime
34
- parts.push(`import { createClient } from "@vibeorm/runtime";`);
35
- parts.push(`import type { VibeClientOptions, TransactionOptions } from "@vibeorm/runtime";`);
36
- parts.push(``);
37
-
38
- // Import schemas for validation wiring
39
- const schemaImports = schema.models.flatMap((m) => [
40
- `${m.name}Schema`,
41
- `${m.name}CreateInputSchema`,
42
- `${m.name}UpdateInputSchema`,
43
- `${m.name}WhereInputSchema`,
44
- ]);
45
- parts.push(`import { ${schemaImports.join(", ")} } from "./schemas.ts";`);
46
- parts.push(``);
47
-
48
- // Generate the model metadata object that the runtime needs
49
- parts.push(generateModelMeta({ schema }));
50
-
51
- // Generate the schemas map for validation wiring
52
- parts.push(generateSchemasMap({ schema }));
53
-
54
- // Generate the VibeClient type (intersection of all delegates)
55
- const clientProperties = schema.models
56
- .map((m) => generateClientProperty({ model: m }))
57
- .join("\n");
58
-
59
- parts.push(`export type VibeClientInstance = {
60
- ${clientProperties}
61
- $transaction<T>(fn: (tx: VibeClientInstance) => Promise<T>, options?: TransactionOptions): Promise<T>;
62
- $transaction(promises: Promise<unknown>[], options?: TransactionOptions): Promise<unknown[]>;
63
- $queryRaw<T = unknown>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T[]>;
64
- $executeRaw(strings: TemplateStringsArray, ...values: unknown[]): Promise<number>;
65
- $queryRawUnsafe<T = unknown>(query: string, ...values: unknown[]): Promise<T[]>;
66
- $executeRawUnsafe(query: string, ...values: unknown[]): Promise<number>;
67
- $connect(): Promise<void>;
68
- $disconnect(): Promise<void>;
69
-
70
- /**
71
- * Create a read-only view that restricts which fields are returned per model.
72
- *
73
- * @example
74
- * const publicApi = db.defineView({
75
- * name: "public-api",
76
- * definition: {
77
- * user: { id: true, name: true, createdAt: true },
78
- * post: { id: true, title: true },
79
- * },
80
- * });
81
- *
82
- * // Only returns { id, name, createdAt }
83
- * const users = await publicApi.user.findMany();
84
- *
85
- * // Reveal extra fields (accessible but excluded from JSON.stringify)
86
- * const user = await publicApi.user.findUniqueOrThrow({
87
- * where: { id: 1 },
88
- * reveal: { email: true },
89
- * });
90
- * user.email; // accessible
91
- * JSON.stringify(user); // { id, name, createdAt } — email excluded
92
- * user.$unsafe(); // { id, name, createdAt, email } — escape hatch
93
- */
94
- defineView<D extends ViewDefinitionInput>(params: {
95
- name: string;
96
- definition: D;
97
- }): ViewClient<D>;
98
- };
99
- `);
100
-
101
- // Generate the VibeClient constructor function
102
- parts.push(`export function VibeClient(options: VibeClientOptions): VibeClientInstance {
103
- return createClient({
104
- options,
105
- modelMeta: MODEL_META,
106
- schemas: options.validate ? SCHEMAS : undefined,
107
- }) as unknown as VibeClientInstance;
108
- }
109
- `);
110
-
111
- return parts.join("\n");
112
- }
113
-
114
- function generateClientProperty(params: { model: Model }): string {
115
- const { model } = params;
116
- const modelVar = toCamelCase({ str: model.name });
117
-
118
- return ` /**
119
- * Operations for the ${model.name} model.
120
- *
121
- * @example
122
- * const rows = await db.${modelVar}.findMany();
123
- *
124
- * @example
125
- * const filteredRows = await db.${modelVar}.findMany({
126
- * where: {
127
- * // Add ${model.name}-specific filters here
128
- * },
129
- * });
130
- *
131
- * @example
132
- * const page = await db.${modelVar}.findMany({
133
- * where: {
134
- * // Add ${model.name}-specific filters here
135
- * },
136
- * orderBy: {
137
- * // e.g. createdAt: "desc"
138
- * },
139
- * take: 20,
140
- * skip: 0,
141
- * });
142
- */
143
- ${modelVar}: ${model.name}Delegate;`;
144
- }
145
-
146
- function generateModelMeta(params: { schema: Schema }): string {
147
- const { schema } = params;
148
-
149
- const modelEntries = schema.models.map((model) => {
150
- const scalarFields = model.fields
151
- .filter((f) => f.kind === "scalar" || f.kind === "enum")
152
- .map((f) => {
153
- const dbName = f.kind === "scalar" ? f.dbName : f.dbName;
154
- const isUpdatedAt = f.kind === "scalar" && f.isUpdatedAt;
155
- const extras: string[] = [];
156
- if (isUpdatedAt) extras.push(`isUpdatedAt: true`);
157
- if (f.kind === "scalar") extras.push(`type: "${f.prismaType}"`);
158
- if (f.isList) extras.push(`isList: true`);
159
-
160
- // Emit default kind for application-level generation (uuid, cuid, nanoid, ulid)
161
- if (f.default) {
162
- const appLevelDefaults = new Set(["uuid", "cuid", "nanoid", "ulid"]);
163
- if (appLevelDefaults.has(f.default.kind)) {
164
- extras.push(`hasDefault: "${f.default.kind}"`);
165
- } else if (f.default.kind !== "dbgenerated") {
166
- // autoincrement, now, literal — DB handles these, but runtime should know
167
- extras.push(`hasDefault: true`);
168
- }
169
- }
170
-
171
- // Extract @vibeorm.idPrefix from documentation comment
172
- const doc = (f.kind === "scalar" || f.kind === "enum") ? f.documentation : undefined;
173
- if (doc) {
174
- const prefixMatch = doc.match(/@vibeorm\.idPrefix\(["']([^"']+)["']\)/);
175
- if (prefixMatch) {
176
- extras.push(`idPrefix: "${prefixMatch[1]}"`);
177
- }
178
- }
179
-
180
- const extraStr = extras.length > 0 ? `, ${extras.join(", ")}` : "";
181
- return ` { name: "${f.name}", dbName: "${dbName}", kind: "${f.kind}" as const${extraStr} }`;
182
- });
183
-
184
- const relationFields = model.fields
185
- .filter((f) => f.kind === "relation")
186
- .map((f) => {
187
- if (f.kind !== "relation") return "";
188
- const isM2M = f.relation.type === "manyToMany";
189
- let joinTableLine = "";
190
- if (isM2M) {
191
- const sorted = [model.name, f.relatedModel].sort();
192
- const jtName = `_${sorted[0]}To${sorted[1]}`;
193
- joinTableLine = `\n joinTable: "${jtName}",`;
194
- }
195
- const relationNameLine = f.relation.name
196
- ? `\n relationName: "${f.relation.name}",`
197
- : "";
198
- return ` {
199
- name: "${f.name}",
200
- kind: "relation" as const,
201
- relatedModel: "${f.relatedModel}",
202
- type: "${f.relation.type}" as const,
203
- isList: ${f.isList},
204
- isForeignKey: ${f.relation.isForeignKey},
205
- fields: ${JSON.stringify(f.relation.fields)},
206
- references: ${JSON.stringify(f.relation.references)},${relationNameLine}${joinTableLine}
207
- }`;
208
- });
209
-
210
- const pkFields = JSON.stringify(model.primaryKey.fields);
211
- const uniqueFields = model.fields
212
- .filter((f) => (f.kind === "scalar" || f.kind === "enum") && f.isUnique)
213
- .map((f) => `"${f.name}"`);
214
-
215
- return ` ${toCamelCase({ str: model.name })}: {
216
- name: "${model.name}",
217
- dbName: "${model.dbName}",
218
- primaryKey: ${pkFields},
219
- uniqueFields: [${uniqueFields.join(", ")}],
220
- scalarFields: [
221
- ${scalarFields.join(",\n")}
222
- ],
223
- relationFields: [
224
- ${relationFields.join(",\n")}
225
- ],
226
- }`;
227
- });
228
-
229
- return `const MODEL_META = {
230
- ${modelEntries.join(",\n")}
231
- } as const;
232
- `;
233
- }
234
-
235
- function generateSchemasMap(params: { schema: Schema }): string {
236
- const { schema } = params;
237
-
238
- const entries = schema.models.map((model) => {
239
- const key = toCamelCase({ str: model.name });
240
- return ` ${key}: {
241
- model: ${model.name}Schema,
242
- createInput: ${model.name}CreateInputSchema,
243
- updateInput: ${model.name}UpdateInputSchema,
244
- whereInput: ${model.name}WhereInputSchema,
245
- }`;
246
- });
247
-
248
- return `const SCHEMAS = {
249
- ${entries.join(",\n")}
250
- };
251
- `;
252
- }