@@ -259,11 +259,22 @@ export default class DataModelValidator implements AstValidator<DataModel> {
259259 return ;
260260 }
261261
262+ if ( this . isSelfRelation ( field ) ) {
263+ if ( ! thisRelation . name ) {
264+ accept ( 'error' , 'Self-relation field must have a name in @relation attribute' , {
265+ node : field ,
266+ } ) ;
267+ return ;
268+ }
269+ }
270+
262271 const oppositeModel = field . type . reference ! . ref ! as DataModel ;
263272
264273 // Use name because the current document might be updated
265274 let oppositeFields = getModelFieldsWithBases ( oppositeModel , false ) . filter (
266- ( f ) => f . type . reference ?. ref ?. name === contextModel . name ,
275+ ( f ) =>
276+ f !== field && // exclude self in case of self relation
277+ f . type . reference ?. ref ?. name === contextModel . name ,
267278 ) ;
268279 oppositeFields = oppositeFields . filter ( ( f ) => {
269280 const fieldRel = this . parseRelation ( f ) ;
@@ -322,27 +333,41 @@ export default class DataModelValidator implements AstValidator<DataModel> {
322333
323334 let relationOwner : DataModelField ;
324335
325- if ( thisRelation ?. references ?. length && thisRelation . fields ?. length ) {
326- if ( oppositeRelation ?. references || oppositeRelation ?. fields ) {
327- accept ( 'error' , '"fields" and "references" must be provided only on one side of relation field' , {
328- node : oppositeField ,
329- } ) ;
330- return ;
331- } else {
332- relationOwner = oppositeField ;
333- }
334- } else if ( oppositeRelation ?. references ?. length && oppositeRelation . fields ?. length ) {
335- if ( thisRelation ?. references || thisRelation ?. fields ) {
336- accept ( 'error' , '"fields" and "references" must be provided only on one side of relation field' , {
337- node : field ,
338- } ) ;
339- return ;
340- } else {
341- relationOwner = field ;
336+ if ( field . type . array && oppositeField . type . array ) {
337+ // if both the field is array, then it's an implicit many-to-many relation,
338+ // neither side should have fields/references
339+ for ( const r of [ thisRelation , oppositeRelation ] ) {
340+ if ( r . fields ?. length || r . references ?. length ) {
341+ accept (
342+ 'error' ,
343+ 'Implicit many-to-many relation cannot have "fields" or "references" in @relation attribute' ,
344+ {
345+ node : r === thisRelation ? field : oppositeField ,
346+ } ,
347+ ) ;
348+ }
342349 }
343350 } else {
344- // if both the field is array, then it's an implicit many-to-many relation
345- if ( ! ( field . type . array && oppositeField . type . array ) ) {
351+ if ( thisRelation ?. references ?. length && thisRelation . fields ?. length ) {
352+ if ( oppositeRelation ?. references || oppositeRelation ?. fields ) {
353+ accept ( 'error' , '"fields" and "references" must be provided only on one side of relation field' , {
354+ node : oppositeField ,
355+ } ) ;
356+ return ;
357+ } else {
358+ relationOwner = oppositeField ;
359+ }
360+ } else if ( oppositeRelation ?. references ?. length && oppositeRelation . fields ?. length ) {
361+ if ( thisRelation ?. references || thisRelation ?. fields ) {
362+ accept ( 'error' , '"fields" and "references" must be provided only on one side of relation field' , {
363+ node : field ,
364+ } ) ;
365+ return ;
366+ } else {
367+ relationOwner = field ;
368+ }
369+ } else {
370+ // for non-M2M relations, one side must have fields/references
346371 [ field , oppositeField ] . forEach ( ( f ) => {
347372 if ( ! this . isSelfRelation ( f ) ) {
348373 accept (
@@ -352,56 +377,60 @@ export default class DataModelValidator implements AstValidator<DataModel> {
352377 ) ;
353378 }
354379 } ) ;
380+ return ;
355381 }
356- return ;
357- }
358-
359- if ( ! relationOwner . type . array && ! relationOwner . type . optional ) {
360- accept ( 'error' , 'Relation field needs to be list or optional' , {
361- node : relationOwner ,
362- } ) ;
363- return ;
364- }
365382
366- if ( relationOwner !== field && ! relationOwner . type . array ) {
367- // one-to-one relation requires defining side's reference field to be @unique
368- // e.g.:
369- // model User {
370- // id String @id @default (cuid())
371- // data UserData?
372- // }
373- // model UserData {
374- // id String @id @default (cuid())
375- // user User @relation (fields: [userId], references: [id])
376- // userId String
377- // }
378- //
379- // UserData.userId field needs to be @unique
380-
381- const containingModel = field . $container as DataModel ;
382- const uniqueFieldList = getUniqueFields ( containingModel ) ;
383-
384- // field is defined in the abstract base model
385- if ( containingModel !== contextModel ) {
386- uniqueFieldList . push ( ...getUniqueFields ( contextModel ) ) ;
383+ if ( ! relationOwner . type . array && ! relationOwner . type . optional ) {
384+ accept ( 'error' , 'Relation field needs to be list or optional' , {
385+ node : relationOwner ,
386+ } ) ;
387+ return ;
387388 }
388389
389- thisRelation . fields ?. forEach ( ( ref ) => {
390- const refField = ref . target . ref as DataModelField ;
391- if ( refField ) {
392- if ( refField . attributes . find ( ( a ) => a . decl . ref ?. name === '@id' || a . decl . ref ?. name === '@unique' ) ) {
393- return ;
394- }
395- if ( uniqueFieldList . some ( ( list ) => list . includes ( refField ) ) ) {
396- return ;
397- }
398- accept (
399- 'error' ,
400- `Field "${ refField . name } " on model "${ containingModel . name } " is part of a one-to-one relation and must be marked as @unique or be part of a model-level @@unique attribute` ,
401- { node : refField } ,
402- ) ;
390+ if ( relationOwner !== field && ! relationOwner . type . array ) {
391+ // one-to-one relation requires defining side's reference field to be @unique
392+ // e.g.:
393+ // model User {
394+ // id String @id @default (cuid())
395+ // data UserData?
396+ // }
397+ // model UserData {
398+ // id String @id @default (cuid())
399+ // user User @relation (fields: [userId], references: [id])
400+ // userId String
401+ // }
402+ //
403+ // UserData.userId field needs to be @unique
404+
405+ const containingModel = field . $container as DataModel ;
406+ const uniqueFieldList = getUniqueFields ( containingModel ) ;
407+
408+ // field is defined in the abstract base model
409+ if ( containingModel !== contextModel ) {
410+ uniqueFieldList . push ( ...getUniqueFields ( contextModel ) ) ;
403411 }
404- } ) ;
412+
413+ thisRelation . fields ?. forEach ( ( ref ) => {
414+ const refField = ref . target . ref as DataModelField ;
415+ if ( refField ) {
416+ if (
417+ refField . attributes . find (
418+ ( a ) => a . decl . ref ?. name === '@id' || a . decl . ref ?. name === '@unique' ,
419+ )
420+ ) {
421+ return ;
422+ }
423+ if ( uniqueFieldList . some ( ( list ) => list . includes ( refField ) ) ) {
424+ return ;
425+ }
426+ accept (
427+ 'error' ,
428+ `Field "${ refField . name } " on model "${ containingModel . name } " is part of a one-to-one relation and must be marked as @unique or be part of a model-level @@unique attribute` ,
429+ { node : refField } ,
430+ ) ;
431+ }
432+ } ) ;
433+ }
405434 }
406435 }
407436
0 commit comments