@@ -63,7 +63,10 @@ export type RestApiHandlerOptions<Schema extends SchemaDef = SchemaDef> = {
6363 /**
6464 * Mapping from model names to unique field name to be used as resource's ID.
6565 */
66- externalIdMapping ?: Record < string , string > ;
66+ /**
67+ * Mapping from model names to unique key name or array of column names to be used as resource's ID.
68+ */
69+ externalIdMapping ?: Record < string , string | string [ ] > ;
6770} ;
6871
6972type RelationshipInfo = {
@@ -260,7 +263,7 @@ export class RestApiHandler<Schema extends SchemaDef = SchemaDef> implements Api
260263 private urlPatternMap : Record < UrlPatterns , UrlPattern > ;
261264 private modelNameMapping : Record < string , string > ;
262265 private reverseModelNameMapping : Record < string , string > ;
263- private externalIdMapping : Record < string , string > ;
266+ private externalIdMapping : Record < string , string | string [ ] > ;
264267
265268 constructor ( private readonly options : RestApiHandlerOptions < Schema > ) {
266269 this . validateOptions ( options ) ;
@@ -296,7 +299,7 @@ export class RestApiHandler<Schema extends SchemaDef = SchemaDef> implements Api
296299 idDivider : z . string ( ) . min ( 1 ) . optional ( ) ,
297300 urlSegmentCharset : z . string ( ) . min ( 1 ) . optional ( ) ,
298301 modelNameMapping : z . record ( z . string ( ) , z . string ( ) ) . optional ( ) ,
299- externalIdMapping : z . record ( z . string ( ) , z . string ( ) ) . optional ( ) ,
302+ externalIdMapping : z . record ( z . string ( ) , z . union ( [ z . string ( ) , z . array ( z . string ( ) ) . min ( 1 ) ] ) ) . optional ( ) ,
300303 } ) ;
301304 const parseResult = schema . safeParse ( options ) ;
302305 if ( ! parseResult . success ) {
@@ -1334,24 +1337,36 @@ export class RestApiHandler<Schema extends SchemaDef = SchemaDef> implements Api
13341337 const modelDef = this . requireModel ( model ) ;
13351338 const modelLower = lowerCaseFirst ( model ) ;
13361339 if ( ! ( modelLower in this . externalIdMapping ) ) {
1337- return Object . values ( modelDef . fields ) . filter ( ( f ) => modelDef . idFields . includes ( f . name ) ) ;
1338- }
1339-
1340- // map external ID name to unique constraint field
1341- const externalIdName = this . externalIdMapping [ modelLower ] ;
1342- for ( const [ name , info ] of Object . entries ( modelDef . uniqueFields ) ) {
1343- if ( name === externalIdName ) {
1344- if ( typeof info . type === 'string' ) {
1345- // single unique field
1346- return [ this . requireField ( model , info . type ) ] ;
1347- } else {
1348- // compound unique fields
1349- return Object . keys ( info ) . map ( ( f ) => this . requireField ( model , f ) ) ;
1340+ return Object . values ( modelDef . fields ) . filter ( ( f ) => ( f as FieldDef ) . name && modelDef . idFields . includes ( ( f as FieldDef ) . name ) ) as FieldDef [ ] ;
1341+ }
1342+
1343+ // map external ID name or columns to unique constraint field(s)
1344+ const externalId = this . externalIdMapping [ modelLower ] ;
1345+ if ( typeof externalId === 'string' ) {
1346+ // Try to match unique key name first
1347+ for ( const [ name , info ] of Object . entries ( modelDef . uniqueFields as Record < string , unknown > ) ) {
1348+ const infoTyped = info as any ;
1349+ if ( name === externalId ) {
1350+ if ( typeof infoTyped . type === 'string' ) {
1351+ // single unique field
1352+ return [ this . requireField ( model , infoTyped . type ) ] ;
1353+ } else {
1354+ // compound unique fields
1355+ return Object . keys ( infoTyped ) . map ( ( f ) => this . requireField ( model , f ) ) ;
1356+ }
13501357 }
13511358 }
1359+ // If not found, treat as a single column name (for single-column unique index with underscores)
1360+ if ( modelDef . fields [ externalId ] ) {
1361+ return [ this . requireField ( model , externalId ) ] ;
1362+ }
1363+ throw new Error ( `Model ${ model } does not have unique key or field ${ externalId } ` ) ;
1364+ } else if ( Array . isArray ( externalId ) ) {
1365+ // Array of column names (for compound or single unique index)
1366+ return externalId . map ( ( col ) => this . requireField ( model , col ) ) ;
1367+ } else {
1368+ throw new Error ( `Invalid externalIdMapping for model ${ model } ` ) ;
13521369 }
1353-
1354- throw new Error ( `Model ${ model } does not have unique key ${ externalIdName } ` ) ;
13551370 }
13561371
13571372 private requireField ( model : string , field : string ) : FieldDef {
@@ -1365,7 +1380,8 @@ export class RestApiHandler<Schema extends SchemaDef = SchemaDef> implements Api
13651380
13661381 private buildTypeMap ( ) {
13671382 this . typeMap = { } ;
1368- for ( const [ model , { fields } ] of Object . entries ( this . schema . models ) ) {
1383+ for ( const [ model , modelVal ] of Object . entries ( this . schema . models as Record < string , unknown > ) ) {
1384+ const fields = ( modelVal as any ) . fields as Record < string , FieldDef > ;
13691385 const idFields = this . getIdFields ( model ) ;
13701386 if ( idFields . length === 0 ) {
13711387 log ( this . options . log , 'warn' , `Not including model ${ model } in the API because it has no ID field` ) ;
@@ -1379,7 +1395,8 @@ export class RestApiHandler<Schema extends SchemaDef = SchemaDef> implements Api
13791395 fields,
13801396 } ) ;
13811397
1382- for ( const [ field , fieldInfo ] of Object . entries ( fields ) ) {
1398+ for ( const [ field , fieldInfoRaw ] of Object . entries ( fields ) ) {
1399+ const fieldInfo = fieldInfoRaw as any ;
13831400 if ( ! fieldInfo . relation ) {
13841401 continue ;
13851402 }
0 commit comments