@@ -259,11 +259,20 @@ 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 ) => f !== field && f . type . reference ?. ref ?. name === contextModel . name ,
267276 ) ;
268277 oppositeFields = oppositeFields . filter ( ( f ) => {
269278 const fieldRel = this . parseRelation ( f ) ;
@@ -322,27 +331,41 @@ export default class DataModelValidator implements AstValidator<DataModel> {
322331
323332 let relationOwner : DataModelField ;
324333
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 ;
334+ if ( field . type . array && oppositeField . type . array ) {
335+ // if both the field is array, then it's an implicit many-to-many relation,
336+ // neither side should have fields/references
337+ for ( const r of [ thisRelation , oppositeRelation ] ) {
338+ if ( r . fields ?. length || r . references ?. length ) {
339+ accept (
340+ 'error' ,
341+ 'Implicit many-to-many relation cannot have "fields" or "references" in @relation attribute' ,
342+ {
343+ node : r === thisRelation ? field : oppositeField ,
344+ } ,
345+ ) ;
346+ }
342347 }
343348 } 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 ) ) {
349+ if ( thisRelation ?. references ?. length && thisRelation . fields ?. length ) {
350+ if ( oppositeRelation ?. references || oppositeRelation ?. fields ) {
351+ accept ( 'error' , '"fields" and "references" must be provided only on one side of relation field' , {
352+ node : oppositeField ,
353+ } ) ;
354+ return ;
355+ } else {
356+ relationOwner = oppositeField ;
357+ }
358+ } else if ( oppositeRelation ?. references ?. length && oppositeRelation . fields ?. length ) {
359+ if ( thisRelation ?. references || thisRelation ?. fields ) {
360+ accept ( 'error' , '"fields" and "references" must be provided only on one side of relation field' , {
361+ node : field ,
362+ } ) ;
363+ return ;
364+ } else {
365+ relationOwner = field ;
366+ }
367+ } else {
368+ // for non-M2M relations, one side must have fields/references
346369 [ field , oppositeField ] . forEach ( ( f ) => {
347370 if ( ! this . isSelfRelation ( f ) ) {
348371 accept (
@@ -352,56 +375,60 @@ export default class DataModelValidator implements AstValidator<DataModel> {
352375 ) ;
353376 }
354377 } ) ;
378+ return ;
355379 }
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- }
365380
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 ) ) ;
381+ if ( ! relationOwner . type . array && ! relationOwner . type . optional ) {
382+ accept ( 'error' , 'Relation field needs to be list or optional' , {
383+ node : relationOwner ,
384+ } ) ;
385+ return ;
387386 }
388387
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- ) ;
388+ if ( relationOwner !== field && ! relationOwner . type . array ) {
389+ // one-to-one relation requires defining side's reference field to be @unique
390+ // e.g.:
391+ // model User {
392+ // id String @id @default (cuid())
393+ // data UserData?
394+ // }
395+ // model UserData {
396+ // id String @id @default (cuid())
397+ // user User @relation (fields: [userId], references: [id])
398+ // userId String
399+ // }
400+ //
401+ // UserData.userId field needs to be @unique
402+
403+ const containingModel = field . $container as DataModel ;
404+ const uniqueFieldList = getUniqueFields ( containingModel ) ;
405+
406+ // field is defined in the abstract base model
407+ if ( containingModel !== contextModel ) {
408+ uniqueFieldList . push ( ...getUniqueFields ( contextModel ) ) ;
403409 }
404- } ) ;
410+
411+ thisRelation . fields ?. forEach ( ( ref ) => {
412+ const refField = ref . target . ref as DataModelField ;
413+ if ( refField ) {
414+ if (
415+ refField . attributes . find (
416+ ( a ) => a . decl . ref ?. name === '@id' || a . decl . ref ?. name === '@unique' ,
417+ )
418+ ) {
419+ return ;
420+ }
421+ if ( uniqueFieldList . some ( ( list ) => list . includes ( refField ) ) ) {
422+ return ;
423+ }
424+ accept (
425+ 'error' ,
426+ `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` ,
427+ { node : refField } ,
428+ ) ;
429+ }
430+ } ) ;
431+ }
405432 }
406433 }
407434
0 commit comments