appflare 0.2.15 → 0.2.16

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/Documentation.md CHANGED
@@ -45,6 +45,33 @@ Schema is defined with `schema`, `table`, and `v` from Appflare.
45
45
 
46
46
  Example source: `packages/backend/schema.ts`.
47
47
 
48
+ ### 2.3 Relation helpers (`v.one`, `v.many`, `v.manyToMany`)
49
+
50
+ - `v.one("target")` creates a single-reference relation and infers a local FK field.
51
+ - `v.many("target")` is inverse one-to-many and infers an FK on the target table.
52
+ - `v.manyToMany("target")` creates a many-to-many relation by synthesizing a junction table.
53
+
54
+ Many-to-many example:
55
+
56
+ ```ts
57
+ export const schemas = schema({
58
+ pets: table({
59
+ id: v.uuid(),
60
+ trips: v.manyToMany("trips"),
61
+ }),
62
+ trips: table({
63
+ id: v.uuid(),
64
+ pets: v.manyToMany("pets"),
65
+ }),
66
+ });
67
+ ```
68
+
69
+ Default behavior for `v.manyToMany`:
70
+
71
+ - Generates one deterministic junction table per pair.
72
+ - Junction rows are pure links (two FK columns, no payload columns).
73
+ - Reciprocal declarations must agree on options (`junctionTable`, field names, FK actions), otherwise generation throws a conflict error.
74
+
48
75
  ---
49
76
 
50
77
  ## 3) How to create handlers
@@ -5,6 +5,7 @@ import type {
5
5
  ColumnDefinition,
6
6
  ColumnType,
7
7
  ManyRelationDefinition,
8
+ ManyToManyRelationDefinition,
8
9
  OneRelationDefinition,
9
10
  SchemaDefinition,
10
11
  TableDefinition,
@@ -196,6 +197,296 @@ function resolveNotNullFromNullableFlags(
196
197
  return defaultNotNull;
197
198
  }
198
199
 
200
+ type ManyToManyPair = {
201
+ leftTable: string;
202
+ leftReferenceField: string;
203
+ rightTable: string;
204
+ rightReferenceField: string;
205
+ junctionTable: string;
206
+ leftField: string;
207
+ rightField: string;
208
+ leftSqlName?: string;
209
+ rightSqlName?: string;
210
+ onDelete?: OneRelationDefinition["onDelete"];
211
+ onUpdate?: OneRelationDefinition["onUpdate"];
212
+ };
213
+
214
+ function endpointKey(tableName: string, referenceField: string): string {
215
+ return `${tableName}:${referenceField}`;
216
+ }
217
+
218
+ function normalizeManyToManyPairKey(
219
+ sourceTable: string,
220
+ sourceReferenceField: string,
221
+ targetTable: string,
222
+ targetReferenceField: string,
223
+ ): {
224
+ key: string;
225
+ leftTable: string;
226
+ leftReferenceField: string;
227
+ rightTable: string;
228
+ rightReferenceField: string;
229
+ sourceIsLeft: boolean;
230
+ } {
231
+ const source = endpointKey(sourceTable, sourceReferenceField);
232
+ const target = endpointKey(targetTable, targetReferenceField);
233
+ const sourceIsLeft = source <= target;
234
+
235
+ if (sourceIsLeft) {
236
+ return {
237
+ key: `${source}|${target}`,
238
+ leftTable: sourceTable,
239
+ leftReferenceField: sourceReferenceField,
240
+ rightTable: targetTable,
241
+ rightReferenceField: targetReferenceField,
242
+ sourceIsLeft: true,
243
+ };
244
+ }
245
+
246
+ return {
247
+ key: `${target}|${source}`,
248
+ leftTable: targetTable,
249
+ leftReferenceField: targetReferenceField,
250
+ rightTable: sourceTable,
251
+ rightReferenceField: sourceReferenceField,
252
+ sourceIsLeft: false,
253
+ };
254
+ }
255
+
256
+ function defaultManyToManyJunctionTable(
257
+ leftTable: string,
258
+ rightTable: string,
259
+ ): string {
260
+ return `${leftTable}${toPascalCase(rightTable)}Links`;
261
+ }
262
+
263
+ function defaultManyToManyFieldName(
264
+ ownerTable: string,
265
+ otherTable: string,
266
+ suffix: "source" | "target",
267
+ ): string {
268
+ const ownerSingular = singularize(ownerTable);
269
+ const otherSingular = singularize(otherTable);
270
+
271
+ if (ownerSingular === otherSingular) {
272
+ return `${suffix}${toPascalCase(ownerSingular)}Id`;
273
+ }
274
+
275
+ return `${ownerSingular}Id`;
276
+ }
277
+
278
+ function mergeManyToManyPairConfig(
279
+ existing: ManyToManyPair,
280
+ incoming: ManyToManyPair,
281
+ pairKey: string,
282
+ ): ManyToManyPair {
283
+ if (existing.junctionTable !== incoming.junctionTable) {
284
+ throw new Error(
285
+ `manyToMany pair '${pairKey}' has conflicting junctionTable values ('${existing.junctionTable}' vs '${incoming.junctionTable}').`,
286
+ );
287
+ }
288
+
289
+ if (existing.leftField !== incoming.leftField) {
290
+ throw new Error(
291
+ `manyToMany pair '${pairKey}' has conflicting left field values ('${existing.leftField}' vs '${incoming.leftField}').`,
292
+ );
293
+ }
294
+
295
+ if (existing.rightField !== incoming.rightField) {
296
+ throw new Error(
297
+ `manyToMany pair '${pairKey}' has conflicting right field values ('${existing.rightField}' vs '${incoming.rightField}').`,
298
+ );
299
+ }
300
+
301
+ if (existing.leftSqlName !== incoming.leftSqlName) {
302
+ throw new Error(
303
+ `manyToMany pair '${pairKey}' has conflicting left sql name values ('${existing.leftSqlName}' vs '${incoming.leftSqlName}').`,
304
+ );
305
+ }
306
+
307
+ if (existing.rightSqlName !== incoming.rightSqlName) {
308
+ throw new Error(
309
+ `manyToMany pair '${pairKey}' has conflicting right sql name values ('${existing.rightSqlName}' vs '${incoming.rightSqlName}').`,
310
+ );
311
+ }
312
+
313
+ if (existing.onDelete !== incoming.onDelete) {
314
+ throw new Error(
315
+ `manyToMany pair '${pairKey}' has conflicting onDelete values ('${existing.onDelete}' vs '${incoming.onDelete}').`,
316
+ );
317
+ }
318
+
319
+ if (existing.onUpdate !== incoming.onUpdate) {
320
+ throw new Error(
321
+ `manyToMany pair '${pairKey}' has conflicting onUpdate values ('${existing.onUpdate}' vs '${incoming.onUpdate}').`,
322
+ );
323
+ }
324
+
325
+ return existing;
326
+ }
327
+
328
+ function normalizeManyToManyRelations(definition: SchemaDefinition): void {
329
+ const pairMap = new Map<string, ManyToManyPair>();
330
+
331
+ for (const [sourceTableName, sourceTable] of Object.entries(
332
+ definition.tables,
333
+ )) {
334
+ for (const relation of Object.values(sourceTable.relations)) {
335
+ if (relation.relation !== "manyToMany") {
336
+ continue;
337
+ }
338
+
339
+ const sourceReferenceField = relation.referenceField ?? "id";
340
+ const targetReferenceField = relation.targetReferenceField ?? "id";
341
+
342
+ const normalizedPair = normalizeManyToManyPairKey(
343
+ sourceTableName,
344
+ sourceReferenceField,
345
+ relation.targetTable,
346
+ targetReferenceField,
347
+ );
348
+
349
+ const defaultLeftField = defaultManyToManyFieldName(
350
+ normalizedPair.leftTable,
351
+ normalizedPair.rightTable,
352
+ "source",
353
+ );
354
+ const defaultRightField = defaultManyToManyFieldName(
355
+ normalizedPair.rightTable,
356
+ normalizedPair.leftTable,
357
+ "target",
358
+ );
359
+
360
+ const pair: ManyToManyPair = {
361
+ leftTable: normalizedPair.leftTable,
362
+ leftReferenceField: normalizedPair.leftReferenceField,
363
+ rightTable: normalizedPair.rightTable,
364
+ rightReferenceField: normalizedPair.rightReferenceField,
365
+ junctionTable:
366
+ relation.junctionTable ??
367
+ defaultManyToManyJunctionTable(
368
+ normalizedPair.leftTable,
369
+ normalizedPair.rightTable,
370
+ ),
371
+ leftField: normalizedPair.sourceIsLeft
372
+ ? (relation.sourceField ?? defaultLeftField)
373
+ : (relation.targetField ?? defaultLeftField),
374
+ rightField: normalizedPair.sourceIsLeft
375
+ ? (relation.targetField ?? defaultRightField)
376
+ : (relation.sourceField ?? defaultRightField),
377
+ leftSqlName: normalizedPair.sourceIsLeft
378
+ ? relation.sourceSqlName
379
+ : relation.targetSqlName,
380
+ rightSqlName: normalizedPair.sourceIsLeft
381
+ ? relation.targetSqlName
382
+ : relation.sourceSqlName,
383
+ onDelete: relation.onDelete,
384
+ onUpdate: relation.onUpdate,
385
+ };
386
+
387
+ if (pair.leftField === pair.rightField) {
388
+ throw new Error(
389
+ `manyToMany pair '${normalizedPair.key}' resolves to duplicate junction fields '${pair.leftField}'. Set sourceField/targetField explicitly.`,
390
+ );
391
+ }
392
+
393
+ const existing = pairMap.get(normalizedPair.key);
394
+ if (existing) {
395
+ mergeManyToManyPairConfig(existing, pair, normalizedPair.key);
396
+ } else {
397
+ pairMap.set(normalizedPair.key, pair);
398
+ }
399
+
400
+ relation.referenceField = sourceReferenceField;
401
+ relation.targetReferenceField = targetReferenceField;
402
+ relation.junctionTable = pair.junctionTable;
403
+ relation.sourceField = normalizedPair.sourceIsLeft
404
+ ? pair.leftField
405
+ : pair.rightField;
406
+ relation.targetField = normalizedPair.sourceIsLeft
407
+ ? pair.rightField
408
+ : pair.leftField;
409
+ relation.sourceSqlName = normalizedPair.sourceIsLeft
410
+ ? pair.leftSqlName
411
+ : pair.rightSqlName;
412
+ relation.targetSqlName = normalizedPair.sourceIsLeft
413
+ ? pair.rightSqlName
414
+ : pair.leftSqlName;
415
+ }
416
+ }
417
+
418
+ for (const pair of pairMap.values()) {
419
+ if (pair.junctionTable in definition.tables) {
420
+ throw new Error(
421
+ `manyToMany auto junction table '${pair.junctionTable}' conflicts with an existing table. Set a different junctionTable name.`,
422
+ );
423
+ }
424
+
425
+ const leftType =
426
+ getLocalReferenceType(
427
+ definition,
428
+ pair.leftTable,
429
+ pair.leftReferenceField,
430
+ ) ?? "string";
431
+ const rightType =
432
+ getLocalReferenceType(
433
+ definition,
434
+ pair.rightTable,
435
+ pair.rightReferenceField,
436
+ ) ?? "string";
437
+
438
+ definition.tables[pair.junctionTable] = {
439
+ kind: "table",
440
+ columns: {
441
+ [pair.leftField]: {
442
+ kind: "column",
443
+ type: leftType,
444
+ sqlName: pair.leftSqlName,
445
+ notNull: true,
446
+ nullable: false,
447
+ references: {
448
+ table: pair.leftTable,
449
+ column: pair.leftReferenceField,
450
+ onDelete: pair.onDelete,
451
+ onUpdate: pair.onUpdate,
452
+ },
453
+ index: true,
454
+ },
455
+ [pair.rightField]: {
456
+ kind: "column",
457
+ type: rightType,
458
+ sqlName: pair.rightSqlName,
459
+ notNull: true,
460
+ nullable: false,
461
+ references: {
462
+ table: pair.rightTable,
463
+ column: pair.rightReferenceField,
464
+ onDelete: pair.onDelete,
465
+ onUpdate: pair.onUpdate,
466
+ },
467
+ index: true,
468
+ },
469
+ },
470
+ relations: {
471
+ [pair.leftTable]: {
472
+ kind: "relation",
473
+ relation: "one",
474
+ targetTable: pair.leftTable,
475
+ field: pair.leftField,
476
+ referenceField: pair.leftReferenceField,
477
+ },
478
+ [pair.rightTable]: {
479
+ kind: "relation",
480
+ relation: "one",
481
+ targetTable: pair.rightTable,
482
+ field: pair.rightField,
483
+ referenceField: pair.rightReferenceField,
484
+ },
485
+ },
486
+ };
487
+ }
488
+ }
489
+
199
490
  function validateNullableFlags(definition: SchemaDefinition): void {
200
491
  for (const [tableName, table] of Object.entries(definition.tables)) {
201
492
  for (const [columnName, column] of Object.entries(table.columns)) {
@@ -207,7 +498,11 @@ function validateNullableFlags(definition: SchemaDefinition): void {
207
498
  }
208
499
 
209
500
  for (const [relationName, relation] of Object.entries(table.relations)) {
210
- if (relation.notNull === true && relation.nullable === true) {
501
+ if (
502
+ relation.relation !== "manyToMany" &&
503
+ relation.notNull === true &&
504
+ relation.nullable === true
505
+ ) {
211
506
  throw new Error(
212
507
  `Invalid nullable configuration on '${tableName}.${relationName}': cannot set both notNull and nullable to true.`,
213
508
  );
@@ -279,6 +574,7 @@ function normalizeSchemaDefinition(
279
574
  const referenceField = relation.referenceField ?? "id";
280
575
  const inferredField =
281
576
  relation.field ?? `${singularize(sourceTableName)}Id`;
577
+ relation.field = inferredField;
282
578
  const inferredType =
283
579
  getLocalReferenceType(normalized, sourceTableName, referenceField) ??
284
580
  relation.fkType ??
@@ -306,6 +602,8 @@ function normalizeSchemaDefinition(
306
602
  }
307
603
  }
308
604
 
605
+ normalizeManyToManyRelations(normalized);
606
+
309
607
  return normalized;
310
608
  }
311
609
 
@@ -432,6 +730,117 @@ function resolveColumnReference(
432
730
  };
433
731
  }
434
732
 
733
+ function emitManyToManyRuntimeMetadata(definition: SchemaDefinition): string {
734
+ const tableEntries: string[] = [];
735
+
736
+ for (const [tableName, table] of Object.entries(definition.tables)) {
737
+ const relationEntries: string[] = [];
738
+
739
+ for (const [relationName, relation] of Object.entries(table.relations)) {
740
+ if (relation.relation !== "manyToMany") {
741
+ continue;
742
+ }
743
+
744
+ if (!relation.junctionTable) {
745
+ continue;
746
+ }
747
+
748
+ relationEntries.push(
749
+ `${quote(relationName)}: {
750
+ targetTable: ${quote(relation.targetTable)},
751
+ junctionTable: ${quote(relation.junctionTable)},
752
+ sourceField: ${quote(relation.sourceField ?? "")},
753
+ targetField: ${quote(relation.targetField ?? "")},
754
+ },`,
755
+ );
756
+ }
757
+
758
+ if (relationEntries.length === 0) {
759
+ continue;
760
+ }
761
+
762
+ tableEntries.push(
763
+ `${quote(tableName)}: {
764
+ ${relationEntries.map((entry) => `\t${entry}`).join("\n")}
765
+ },`,
766
+ );
767
+ }
768
+
769
+ if (tableEntries.length === 0) {
770
+ return "export const __appflareManyToMany = {} as const;\n";
771
+ }
772
+
773
+ return `export const __appflareManyToMany = {
774
+ ${tableEntries.map((entry) => `\t${entry}`).join("\n")}
775
+ } as const;
776
+ `;
777
+ }
778
+
779
+ function emitRuntimeRelationMetadata(definition: SchemaDefinition): string {
780
+ const tableEntries: string[] = [];
781
+
782
+ for (const [tableName, table] of Object.entries(definition.tables)) {
783
+ const relationEntries: string[] = [];
784
+
785
+ for (const [relationName, relation] of Object.entries(table.relations)) {
786
+ if (relation.relation === "one") {
787
+ relationEntries.push(
788
+ `${quote(relationName)}: {
789
+ kind: "one",
790
+ targetTable: ${quote(relation.targetTable)},
791
+ sourceField: ${quote(relation.field ?? "")},
792
+ referenceField: ${quote(relation.referenceField ?? "id")},
793
+ },`,
794
+ );
795
+ continue;
796
+ }
797
+
798
+ if (relation.relation === "many") {
799
+ relationEntries.push(
800
+ `${quote(relationName)}: {
801
+ kind: "many",
802
+ targetTable: ${quote(relation.targetTable)},
803
+ sourceField: ${quote(relation.field ?? "")},
804
+ referenceField: ${quote(relation.referenceField ?? "id")},
805
+ },`,
806
+ );
807
+ continue;
808
+ }
809
+
810
+ relationEntries.push(
811
+ `${quote(relationName)}: {
812
+ kind: "manyToMany",
813
+ targetTable: ${quote(relation.targetTable)},
814
+ junctionTable: ${quote(relation.junctionTable ?? "")},
815
+ sourceField: ${quote(relation.sourceField ?? "")},
816
+ targetField: ${quote(relation.targetField ?? "")},
817
+ referenceField: ${quote(relation.referenceField ?? "id")},
818
+ targetReferenceField: ${quote(relation.targetReferenceField ?? "id")},
819
+ },`,
820
+ );
821
+ }
822
+
823
+ if (relationEntries.length === 0) {
824
+ continue;
825
+ }
826
+
827
+ tableEntries.push(
828
+ `${quote(tableName)}: {
829
+ ${relationEntries.map((entry) => `\t${entry}`).join("\n")}
830
+ },`,
831
+ );
832
+ }
833
+
834
+ if (tableEntries.length === 0) {
835
+ return "export const __appflareRelations = {} as const;\n";
836
+ }
837
+
838
+ return `export const __appflareRelations = {
839
+ ${tableEntries.map((entry) => `\t${entry}`).join("\n")}
840
+ } as const;
841
+ `;
842
+ }
843
+
435
844
  function emitDrizzleSchema(
436
845
  definition: SchemaDefinition,
437
846
  strategy: "camelToSnake",
@@ -536,8 +945,17 @@ function emitDrizzleSchema(
536
945
  return relation.relation === "many";
537
946
  },
538
947
  ) as Array<[string, ManyRelationDefinition]>;
948
+ const manyToManyRelations = Object.entries(table.relations).filter(
949
+ ([, relation]) => {
950
+ return relation.relation === "manyToMany";
951
+ },
952
+ ) as Array<[string, ManyToManyRelationDefinition]>;
539
953
 
540
- if (oneRelations.length === 0 && manyRelations.length === 0) {
954
+ if (
955
+ oneRelations.length === 0 &&
956
+ manyRelations.length === 0 &&
957
+ manyToManyRelations.length === 0
958
+ ) {
541
959
  continue;
542
960
  }
543
961
 
@@ -551,6 +969,14 @@ function emitDrizzleSchema(
551
969
  for (const [relationName, relation] of manyRelations) {
552
970
  relationLines.push(`\t${relationName}: many(${relation.targetTable}),`);
553
971
  }
972
+ for (const [relationName, relation] of manyToManyRelations) {
973
+ if (!relation.junctionTable) {
974
+ throw new Error(
975
+ `manyToMany relation '${tableName}.${relationName}' is missing junctionTable after normalization.`,
976
+ );
977
+ }
978
+ relationLines.push(`\t${relationName}: many(${relation.junctionTable}),`);
979
+ }
554
980
 
555
981
  relationBlocks.push(
556
982
  `export const ${tableName}Relations = relations(${tableName}, ({ one, many }) => ({\n${relationLines.join("\n")}\n}));`,
@@ -564,6 +990,10 @@ ${buildExternalTableImportLines(externalTables)}
564
990
  ${tableBlocks.join("\n\n")}
565
991
 
566
992
  ${relationBlocks.join("\n\n")}
993
+
994
+ ${emitManyToManyRuntimeMetadata(definition)}
995
+
996
+ ${emitRuntimeRelationMetadata(definition)}
567
997
  `;
568
998
  }
569
999
 
@@ -114,7 +114,7 @@ type RelationAggregateSelectionInput<TModel extends Record<string, unknown>> = {
114
114
  _avg?: RelationAvgSelectionInput<TModel>;
115
115
  };
116
116
 
117
- type QueryWithRelationInput<TEntry> =
117
+ type QueryNativeWithRelationInput<TEntry> =
118
118
  | (Extract<TEntry, true> extends never ? never : true)
119
119
  | (Extract<TEntry, Record<string, unknown>> extends infer TRelation
120
120
  ? TRelation extends Record<string, unknown>
@@ -122,24 +122,57 @@ type QueryWithRelationInput<TEntry> =
122
122
  RelationAggregateSelectionInput<RelationModelFromConfig<TRelation>> & {
123
123
  where?: WhereInput<RelationModelFromConfig<TRelation>>;
124
124
  with?: TRelation extends { with?: infer TNestedWith }
125
- ? QueryWithInput<TNestedWith>
125
+ ? QueryWithInputForTable<TableName, TNestedWith>
126
126
  : never;
127
127
  }
128
128
  : never
129
129
  : never);
130
130
 
131
- type QueryWithInput<TWith> = TWith extends Record<string, unknown>
131
+ type QueryManyToManyWithRelationInput<TTargetTable extends TableName> =
132
+ | true
133
+ | (Omit<NonNullable<TableFindManyArgs<TTargetTable>>, "where" | "with"> &
134
+ RelationAggregateSelectionInput<TableModel<TTargetTable>> & {
135
+ where?: WhereInput<TableModel<TTargetTable>>;
136
+ with?: QueryWithInputForTable<
137
+ TTargetTable,
138
+ NativeFindManyWith<TTargetTable>
139
+ >;
140
+ });
141
+
142
+ type QueryWithRelationInputForTable<
143
+ TSourceTable extends TableName,
144
+ TRelationName extends string,
145
+ TEntry,
146
+ > = ManyToManyTargetTableName<TSourceTable, TRelationName> extends infer TTarget
147
+ ? TTarget extends TableName
148
+ ? QueryManyToManyWithRelationInput<TTarget>
149
+ : QueryNativeWithRelationInput<TEntry>
150
+ : QueryNativeWithRelationInput<TEntry>;
151
+
152
+ type QueryWithInputForTable<
153
+ TSourceTable extends TableName,
154
+ TWith,
155
+ > = TWith extends Record<string, unknown>
132
156
  ? {
133
- [K in keyof TWith]?: QueryWithRelationInput<TWith[K]>;
157
+ [K in keyof TWith]?: QueryWithRelationInputForTable<
158
+ TSourceTable,
159
+ Extract<K, string>,
160
+ TWith[K]
161
+ >;
134
162
  }
135
163
  : TWith;
136
164
 
165
+ type QueryWithInput<TName extends TableName, TWith> = QueryWithInputForTable<
166
+ TName,
167
+ TWith
168
+ >;
169
+
137
170
  export type QueryFindManyArgs<TName extends TableName> = Omit<
138
171
  NonNullable<TableFindManyArgs<TName>>,
139
172
  "where" | "with"
140
173
  > & {
141
174
  where?: WhereInput<TableModel<TName>>;
142
- with?: QueryWithInput<NativeFindManyWith<TName>>;
175
+ with?: QueryWithInput<TName, NativeFindManyWith<TName>>;
143
176
  };
144
177
 
145
178
  export type QueryFindFirstArgs<TName extends TableName> = Omit<
@@ -147,11 +180,59 @@ export type QueryFindFirstArgs<TName extends TableName> = Omit<
147
180
  "where" | "with"
148
181
  > & {
149
182
  where?: WhereInput<TableModel<TName>>;
150
- with?: QueryWithInput<NativeFindFirstWith<TName>>;
183
+ with?: QueryWithInput<TName, NativeFindFirstWith<TName>>;
184
+ };
185
+
186
+ type SingularizeRelationName<TName extends string> =
187
+ TName extends \`\${infer TRoot\}ies\`
188
+ ? \`\${TRoot\}y\`
189
+ : TName extends \`\${infer TRoot\}s\`
190
+ ? TRoot
191
+ : TName;
192
+
193
+ type RelationInsertItem<TTargetTable extends TableName> =
194
+ | TableModel<TTargetTable>
195
+ | TableInsertModel<TTargetTable>
196
+ | ("id" extends keyof TableModel<TTargetTable>
197
+ ? TableModel<TTargetTable>["id"]
198
+ : never);
199
+
200
+ type RelationInsertValue<
201
+ TSourceTable extends TableName,
202
+ TRelationName extends RuntimeRelationName<TSourceTable>,
203
+ > = RuntimeRelationTargetTable<TSourceTable, TRelationName> extends infer TTargetTable
204
+ ? TTargetTable extends TableName
205
+ ? RuntimeRelationKind<TSourceTable, TRelationName> extends "one"
206
+ ? RelationInsertItem<TTargetTable>
207
+ : RelationInsertItem<TTargetTable> | Array<RelationInsertItem<TTargetTable>>
208
+ : never
209
+ : never;
210
+
211
+ type RelationInsertFields<TName extends TableName> = {
212
+ [TRelationName in RuntimeRelationName<TName>]?: RelationInsertValue<
213
+ TName,
214
+ TRelationName
215
+ >;
216
+ };
217
+
218
+ type RelationInsertIdAliasFields<TName extends TableName> = {
219
+ [TRelationName in RuntimeRelationName<TName> as \`\${TRelationName\}Id\`]?: RelationInsertValue<
220
+ TName,
221
+ TRelationName
222
+ >;
223
+ } & {
224
+ [TRelationName in RuntimeRelationName<TName> as \`\${SingularizeRelationName<TRelationName>\}Id\`]?: RelationInsertValue<
225
+ TName,
226
+ TRelationName
227
+ >;
151
228
  };
152
229
 
230
+ type QueryInsertValue<TName extends TableName> = TableInsertScalarModel<TName> &
231
+ RelationInsertFields<TName> &
232
+ RelationInsertIdAliasFields<TName>;
233
+
153
234
  export type QueryInsertArgs<TName extends TableName> = {
154
- values: TableInsertModel<TName> | Array<TableInsertModel<TName>>;
235
+ values: QueryInsertValue<TName> | Array<QueryInsertValue<TName>>;
155
236
  };
156
237
 
157
238
  export type QueryUpdateArgs<TName extends TableName> = {
@@ -1,6 +1,6 @@
1
1
  export function generateQueryApiTypesSection(): string {
2
2
  return `type AggregateWithInput<TName extends TableName> =
3
- QueryWithInput<NativeFindManyWith<TName>>;
3
+ QueryWithInput<TName, NativeFindManyWith<TName>>;
4
4
 
5
5
  type NumericFieldKey<TName extends TableName> = NumericModelFieldKey<
6
6
  TableModel<TName>