@@ -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,37 @@ 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 (
1341+ ( f ) => ( f as FieldDef ) . name && modelDef . idFields . includes ( ( f as FieldDef ) . name ) ,
1342+ ) as FieldDef [ ] ;
1343+ }
1344+
1345+ // map external ID name or columns to unique constraint field(s)
1346+ const externalId = this . externalIdMapping [ modelLower ] ;
1347+ if ( typeof externalId === 'string' ) {
1348+ // Try to match unique key name first
1349+ for ( const [ name , info ] of Object . entries ( modelDef . uniqueFields as Record < string , unknown > ) ) {
1350+ const infoTyped = info as any ;
1351+ if ( name === externalId ) {
1352+ if ( typeof infoTyped . type === 'string' ) {
1353+ // single unique field
1354+ return [ this . requireField ( model , infoTyped . type ) ] ;
1355+ } else {
1356+ // compound unique fields
1357+ return Object . keys ( infoTyped ) . map ( ( f ) => this . requireField ( model , f ) ) ;
1358+ }
13501359 }
13511360 }
1361+ // If not found, treat as a single column name (for single-column unique index with underscores)
1362+ if ( modelDef . fields [ externalId ] ) {
1363+ return [ this . requireField ( model , externalId ) ] ;
1364+ }
1365+ throw new Error ( `Model ${ model } does not have unique key or field ${ externalId } ` ) ;
1366+ } else if ( Array . isArray ( externalId ) ) {
1367+ // Array of column names (for compound or single unique index)
1368+ return externalId . map ( ( col ) => this . requireField ( model , col ) ) ;
13521369 }
1353-
1354- throw new Error ( `Model ${ model } does not have unique key ${ externalIdName } ` ) ;
1370+ throw new Error ( `Invalid externalIdMapping for model ${ model } ` ) ;
13551371 }
13561372
13571373 private requireField ( model : string , field : string ) : FieldDef {
@@ -1365,7 +1381,8 @@ export class RestApiHandler<Schema extends SchemaDef = SchemaDef> implements Api
13651381
13661382 private buildTypeMap ( ) {
13671383 this . typeMap = { } ;
1368- for ( const [ model , { fields } ] of Object . entries ( this . schema . models ) ) {
1384+ for ( const [ model , modelVal ] of Object . entries ( this . schema . models as Record < string , unknown > ) ) {
1385+ const fields = ( modelVal as any ) . fields as Record < string , FieldDef > ;
13691386 const idFields = this . getIdFields ( model ) ;
13701387 if ( idFields . length === 0 ) {
13711388 log ( this . options . log , 'warn' , `Not including model ${ model } in the API because it has no ID field` ) ;
@@ -1379,7 +1396,8 @@ export class RestApiHandler<Schema extends SchemaDef = SchemaDef> implements Api
13791396 fields,
13801397 } ) ;
13811398
1382- for ( const [ field , fieldInfo ] of Object . entries ( fields ) ) {
1399+ for ( const [ field , fieldInfoRaw ] of Object . entries ( fields ) ) {
1400+ const fieldInfo = fieldInfoRaw as any ;
13831401 if ( ! fieldInfo . relation ) {
13841402 continue ;
13851403 }
0 commit comments