@vibeorm/generator 1.1.5 → 1.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeorm/generator",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "TypeScript client generator for VibeORM — produces typed delegates, inputs, and Zod schemas from a Prisma schema",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -37,6 +37,6 @@
37
37
  "bun": ">=1.1.0"
38
38
  },
39
39
  "dependencies": {
40
- "@vibeorm/parser": "1.1.4"
40
+ "@vibeorm/parser": "1.1.5"
41
41
  }
42
42
  }
@@ -73,6 +73,9 @@ export type ViewResultWrapper<T> = T & {
73
73
  toJSON(): Partial<T>;
74
74
  $unsafe(): T;
75
75
  };
76
+
77
+ /** Flatten intersection types for cleaner hover tooltips and type resolution */
78
+ type Compute<T> = T extends Function ? T : { [K in keyof T]: T[K] } & unknown;
76
79
  `);
77
80
 
78
81
  // ─── ViewDefinitionInput ──────────────────────────────────────────
@@ -109,6 +112,18 @@ export type ViewResultWrapper<T> = T & {
109
112
  }
110
113
  parts.push(``);
111
114
 
115
+ // ─── Per-model ViewIncludeResult ──────────────────────────────────
116
+
117
+ for (const model of schema.models) {
118
+ parts.push(generateViewIncludeResult({ model, schema }));
119
+ }
120
+
121
+ // ─── Per-model GetViewResult ────────────────────────────────────
122
+
123
+ for (const model of schema.models) {
124
+ parts.push(generateGetViewResult({ model, schema }));
125
+ }
126
+
112
127
  // ─── Per-model ViewFindArgs ───────────────────────────────────────
113
128
 
114
129
  for (const model of schema.models) {
@@ -128,6 +143,114 @@ export type ViewResultWrapper<T> = T & {
128
143
  return parts.join("\n");
129
144
  }
130
145
 
146
+ // ─── ViewIncludeResult (per model) ────────────────────────────────
147
+
148
+ /**
149
+ * Generates a mapped type that computes the result types for included
150
+ * relations on a view query. For each relation field:
151
+ *
152
+ * - If the related model is in VD: result is ViewResultWrapper<Pick<Model, viewFields>>
153
+ * - If the related model is NOT in VD: result is the full model type
154
+ * - Array relations get [], nullable singles get | null
155
+ */
156
+ function generateViewIncludeResult(params: { model: Model; schema: Schema }): string {
157
+ const { model } = params;
158
+ const n = model.name;
159
+
160
+ const relationFields = model.fields.filter(
161
+ (f): f is RelationField => f.kind === "relation"
162
+ );
163
+
164
+ if (relationFields.length === 0) {
165
+ // No relations — include result is always empty
166
+ return `
167
+ // ─── ${n} View Include Result ────────────────────────────────
168
+
169
+ type ${n}ViewIncludeResult<
170
+ I extends Record<string, unknown>,
171
+ VD extends ViewDefinitionInput,
172
+ > = {};
173
+ `;
174
+ }
175
+
176
+ // Build nested conditional branches: K extends "relName" ? ... : K extends ... : never
177
+ const branches = relationFields.map((f) => {
178
+ const relModelVar = toCamelCase({ str: f.relatedModel });
179
+ const relModel = f.relatedModel;
180
+
181
+ // Type when the related model IS in the view definition
182
+ const viewRestricted = `ViewResultWrapper<Pick<${relModel}, keyof RF & keyof ${relModel}>>`;
183
+ // Type when the related model is NOT in the view definition
184
+ const fullType = relModel;
185
+
186
+ let inViewType: string;
187
+ let notInViewType: string;
188
+
189
+ if (f.isList) {
190
+ inViewType = `${viewRestricted}[]`;
191
+ notInViewType = `${fullType}[]`;
192
+ } else if (!f.isRequired) {
193
+ inViewType = `${viewRestricted} | null`;
194
+ notInViewType = `${fullType} | null`;
195
+ } else {
196
+ inViewType = viewRestricted;
197
+ notInViewType = fullType;
198
+ }
199
+
200
+ return ` K extends "${f.name}"
201
+ ? (VD extends { ${relModelVar}: infer RF extends Record<string, true> }
202
+ ? ${inViewType}
203
+ : ${notInViewType})`;
204
+ });
205
+
206
+ return `
207
+ // ─── ${n} View Include Result ────────────────────────────────
208
+
209
+ type ${n}ViewIncludeResult<
210
+ I extends Record<string, unknown>,
211
+ VD extends ViewDefinitionInput,
212
+ > = {
213
+ [K in keyof I as I[K] extends false | undefined | null ? never : K]:
214
+ ${branches.join("\n : ")}
215
+ : never;
216
+ };
217
+ `;
218
+ }
219
+
220
+ // ─── GetViewResult (per model) ────────────────────────────────────
221
+
222
+ /**
223
+ * Generates a top-level conditional type that resolves the complete result
224
+ * type for a view query, including both scalar fields and included relations.
225
+ *
226
+ * The conditional is at the outermost position (matching how GetFindResult
227
+ * works in the regular client) so TypeScript evaluates it eagerly at call
228
+ * sites instead of deferring it inside ViewResultWrapper's type parameter.
229
+ */
230
+ function generateGetViewResult(params: { model: Model; schema: Schema }): string {
231
+ const { model } = params;
232
+ const n = model.name;
233
+ const hasRelations = model.fields.some((f) => f.kind === "relation");
234
+
235
+ if (!hasRelations) return "";
236
+
237
+ return `
238
+ // ─── ${n} Get View Result ───────────────────────────────────
239
+
240
+ type ${n}GetViewResult<
241
+ VF extends Record<string, true>,
242
+ T,
243
+ VD extends ViewDefinitionInput,
244
+ > =
245
+ T extends { include: infer I extends Record<string, unknown> }
246
+ ? ViewResultWrapper<Compute<
247
+ ViewSelectResult<${n}, VF, T>
248
+ & ${n}ViewIncludeResult<I, VD>
249
+ >>
250
+ : ViewResultWrapper<ViewSelectResult<${n}, VF, T>>;
251
+ `;
252
+ }
253
+
131
254
  // ─── ViewFindArgs (per model) ─────────────────────────────────────
132
255
 
133
256
  function generateViewFindArgs(params: { model: Model; schema: Schema }): string {
@@ -195,6 +318,20 @@ function generateViewDelegate(params: { model: Model; schema: Schema }): string
195
318
  const { model } = params;
196
319
  const n = model.name;
197
320
 
321
+ const hasRelations = model.fields.some((f) => f.kind === "relation");
322
+
323
+ // Helper: builds the full result type including the include intersection.
324
+ // Models with relations use a dedicated GetViewResult type that places the
325
+ // conditional at the outermost position (matching GetFindResult in the regular
326
+ // client) so TypeScript evaluates it eagerly instead of deferring it inside
327
+ // ViewResultWrapper's type parameter.
328
+ const resultType = (argsType: string) => {
329
+ if (!hasRelations) {
330
+ return `ViewResultWrapper<ViewSelectResult<${n}, VF, ${argsType}>>`;
331
+ }
332
+ return `${n}GetViewResult<VF, ${argsType}, VD>`;
333
+ };
334
+
198
335
  return `
199
336
  // ─── ${n} View Delegate ─────────────────────────────────────
200
337
 
@@ -207,45 +344,35 @@ export type ${n}ViewDelegate<
207
344
  */
208
345
  findMany<T extends ${n}ViewFindManyArgs<VF, VD>>(
209
346
  args?: Exact<T, ${n}ViewFindManyArgs<VF, VD>>
210
- ): Promise<ViewResultWrapper<
211
- ViewSelectResult<${n}, VF, T>
212
- >[]>;
347
+ ): Promise<${resultType("T")}[]>;
213
348
 
214
349
  /**
215
350
  * Find the first ${n} matching the filter, or null.
216
351
  */
217
352
  findFirst<T extends ${n}ViewFindFirstArgs<VF, VD>>(
218
353
  args?: Exact<T, ${n}ViewFindFirstArgs<VF, VD>>
219
- ): Promise<ViewResultWrapper<
220
- ViewSelectResult<${n}, VF, T>
221
- > | null>;
354
+ ): Promise<${resultType("T")} | null>;
222
355
 
223
356
  /**
224
357
  * Find a unique ${n} by its primary key or unique field, or null.
225
358
  */
226
359
  findUnique<T extends ${n}ViewFindUniqueArgs<VF, VD>>(
227
360
  args: Exact<T, ${n}ViewFindUniqueArgs<VF, VD>>
228
- ): Promise<ViewResultWrapper<
229
- ViewSelectResult<${n}, VF, T>
230
- > | null>;
361
+ ): Promise<${resultType("T")} | null>;
231
362
 
232
363
  /**
233
364
  * Find a unique ${n} or throw if not found.
234
365
  */
235
366
  findUniqueOrThrow<T extends ${n}ViewFindUniqueArgs<VF, VD>>(
236
367
  args: Exact<T, ${n}ViewFindUniqueArgs<VF, VD>>
237
- ): Promise<ViewResultWrapper<
238
- ViewSelectResult<${n}, VF, T>
239
- >>;
368
+ ): Promise<${resultType("T")}>;
240
369
 
241
370
  /**
242
371
  * Find the first ${n} matching the filter, or throw.
243
372
  */
244
373
  findFirstOrThrow<T extends ${n}ViewFindFirstArgs<VF, VD>>(
245
374
  args?: Exact<T, ${n}ViewFindFirstArgs<VF, VD>>
246
- ): Promise<ViewResultWrapper<
247
- ViewSelectResult<${n}, VF, T>
248
- >>;
375
+ ): Promise<${resultType("T")}>;
249
376
 
250
377
  /**
251
378
  * Count matching ${n} records.