@@ -50,6 +50,9 @@ export type Options = {
5050 * it should be included in the charset.
5151 */
5252 urlSegmentCharset ?: string ;
53+
54+ modelNameMapping ?: Record < string , string > ;
55+ prefix ?: string ;
5356} ;
5457
5558type RelationshipInfo = {
@@ -65,6 +68,20 @@ type ModelInfo = {
6568 relationships : Record < string , RelationshipInfo > ;
6669} ;
6770
71+ type Match = {
72+ type : string ;
73+ id : string ;
74+ relationship : string ;
75+ prefix : string ;
76+ } ;
77+
78+ enum UrlPatterns {
79+ SINGLE = 'single' ,
80+ FETCH_RELATIONSHIP = 'fetchRelationship' ,
81+ RELATIONSHIP = 'relationship' ,
82+ COLLECTION = 'collection' ,
83+ }
84+
6885class InvalidValueError extends Error {
6986 constructor ( public readonly message : string ) {
7087 super ( message ) ;
@@ -220,29 +237,60 @@ class RequestHandler extends APIHandlerBase {
220237 // divider used to separate compound ID fields
221238 private idDivider ;
222239
223- private urlPatterns ;
240+ private urlPatternMap : Record < UrlPatterns , UrlPattern > ;
241+ private modelNameMapping : Record < string , string > ;
242+ private reverseModelNameMapping : Record < string , string > ;
243+ private prefix : string | undefined ;
224244
225245 constructor ( private readonly options : Options ) {
226246 super ( ) ;
227247 this . idDivider = options . idDivider ?? prismaIdDivider ;
228248 const segmentCharset = options . urlSegmentCharset ?? 'a-zA-Z0-9-_~ %' ;
229- this . urlPatterns = this . buildUrlPatterns ( this . idDivider , segmentCharset ) ;
249+
250+ this . prefix = options . prefix ;
251+ this . modelNameMapping = options . modelNameMapping ?? { } ;
252+ this . reverseModelNameMapping = Object . fromEntries (
253+ Object . entries ( this . modelNameMapping ) . map ( ( [ k , v ] ) => [ v , k ] )
254+ ) ;
255+ this . urlPatternMap = this . buildUrlPatternMap ( segmentCharset ) ;
230256 }
231257
232- buildUrlPatterns ( idDivider : string , urlSegmentNameCharset : string ) {
258+ private buildUrlPatternMap ( urlSegmentNameCharset : string ) : Record < UrlPatterns , UrlPattern > {
233259 const options = { segmentValueCharset : urlSegmentNameCharset } ;
260+
261+ const buildPath = ( segments : string [ ] ) => {
262+ return this . prefix + '/' + segments . join ( '/' ) ;
263+ } ;
264+
234265 return {
235- // collection operations
236- collection : new UrlPattern ( '/:type' , options ) ,
237- // single resource operations
238- single : new UrlPattern ( '/:type/:id' , options ) ,
239- // related entity fetching
240- fetchRelationship : new UrlPattern ( '/:type/:id/:relationship' , options ) ,
241- // relationship operations
242- relationship : new UrlPattern ( '/:type/:id/relationships/:relationship' , options ) ,
266+ [ UrlPatterns . SINGLE ] : new UrlPattern ( buildPath ( [ ':type' , ':id' ] ) , options ) ,
267+ [ UrlPatterns . FETCH_RELATIONSHIP ] : new UrlPattern ( buildPath ( [ ':type' , ':id' , ':relationship' ] ) , options ) ,
268+ [ UrlPatterns . RELATIONSHIP ] : new UrlPattern (
269+ buildPath ( [ ':type' , ':id' , 'relationships' , ':relationship' ] ) ,
270+ options
271+ ) ,
272+ [ UrlPatterns . COLLECTION ] : new UrlPattern ( buildPath ( [ ':type' ] ) , options ) ,
243273 } ;
244274 }
245275
276+ private reverseModelNameMap ( type : string ) : string {
277+ return this . reverseModelNameMapping [ type ] ?? type ;
278+ }
279+
280+ private matchUrlPattern ( path : string , routeType : UrlPatterns ) : Match {
281+ const pattern = this . urlPatternMap [ routeType ] ;
282+ if ( ! pattern ) {
283+ throw new InvalidValueError ( `Unknown route type: ${ routeType } ` ) ;
284+ }
285+
286+ const match = pattern . match ( path ) ;
287+ if ( match ) {
288+ match . type = this . modelNameMapping [ match . type ] ?? match . type ;
289+ match . relationship = this . modelNameMapping [ match . relationship ] ?? match . relationship ;
290+ }
291+ return match ;
292+ }
293+
246294 async handleRequest ( {
247295 prisma,
248296 method,
@@ -274,19 +322,18 @@ class RequestHandler extends APIHandlerBase {
274322 try {
275323 switch ( method ) {
276324 case 'GET' : {
277- let match = this . urlPatterns . single . match ( path ) ;
325+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
278326 if ( match ) {
279327 // single resource read
280328 return await this . processSingleRead ( prisma , match . type , match . id , query ) ;
281329 }
282-
283- match = this . urlPatterns . fetchRelationship . match ( path ) ;
330+ match = this . matchUrlPattern ( path , UrlPatterns . FETCH_RELATIONSHIP ) ;
284331 if ( match ) {
285332 // fetch related resource(s)
286333 return await this . processFetchRelated ( prisma , match . type , match . id , match . relationship , query ) ;
287334 }
288335
289- match = this . urlPatterns . relationship . match ( path ) ;
336+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
290337 if ( match ) {
291338 // read relationship
292339 return await this . processReadRelationship (
@@ -298,7 +345,7 @@ class RequestHandler extends APIHandlerBase {
298345 ) ;
299346 }
300347
301- match = this . urlPatterns . collection . match ( path ) ;
348+ match = this . matchUrlPattern ( path , UrlPatterns . COLLECTION ) ;
302349 if ( match ) {
303350 // collection read
304351 return await this . processCollectionRead ( prisma , match . type , query ) ;
@@ -311,8 +358,7 @@ class RequestHandler extends APIHandlerBase {
311358 if ( ! requestBody ) {
312359 return this . makeError ( 'invalidPayload' ) ;
313360 }
314-
315- let match = this . urlPatterns . collection . match ( path ) ;
361+ let match = this . matchUrlPattern ( path , UrlPatterns . COLLECTION ) ;
316362 if ( match ) {
317363 const body = requestBody as any ;
318364 const upsertMeta = this . upsertMetaSchema . safeParse ( body ) ;
@@ -338,8 +384,7 @@ class RequestHandler extends APIHandlerBase {
338384 ) ;
339385 }
340386 }
341-
342- match = this . urlPatterns . relationship . match ( path ) ;
387+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
343388 if ( match ) {
344389 // relationship creation (collection relationship only)
345390 return await this . processRelationshipCRUD (
@@ -362,8 +407,7 @@ class RequestHandler extends APIHandlerBase {
362407 if ( ! requestBody ) {
363408 return this . makeError ( 'invalidPayload' ) ;
364409 }
365-
366- let match = this . urlPatterns . single . match ( path ) ;
410+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
367411 if ( match ) {
368412 // resource update
369413 return await this . processUpdate (
@@ -376,8 +420,7 @@ class RequestHandler extends APIHandlerBase {
376420 zodSchemas
377421 ) ;
378422 }
379-
380- match = this . urlPatterns . relationship . match ( path ) ;
423+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
381424 if ( match ) {
382425 // relationship update
383426 return await this . processRelationshipCRUD (
@@ -395,13 +438,13 @@ class RequestHandler extends APIHandlerBase {
395438 }
396439
397440 case 'DELETE' : {
398- let match = this . urlPatterns . single . match ( path ) ;
441+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
399442 if ( match ) {
400443 // resource deletion
401444 return await this . processDelete ( prisma , match . type , match . id ) ;
402445 }
403446
404- match = this . urlPatterns . relationship . match ( path ) ;
447+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
405448 if ( match ) {
406449 // relationship deletion (collection relationship only)
407450 return await this . processRelationshipCRUD (
@@ -531,11 +574,13 @@ class RequestHandler extends APIHandlerBase {
531574 }
532575
533576 if ( entity ?. [ relationship ] ) {
577+ const mappedType = this . reverseModelNameMap ( type ) ;
578+ const mappedRelationship = this . reverseModelNameMap ( relationship ) ;
534579 return {
535580 status : 200 ,
536581 body : await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
537582 linkers : {
538- document : new Linker ( ( ) => this . makeLinkUrl ( `/${ type } /${ resourceId } /${ relationship } ` ) ) ,
583+ document : new Linker ( ( ) => this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /${ mappedRelationship } ` ) ) ,
539584 paginator,
540585 } ,
541586 include,
@@ -582,11 +627,13 @@ class RequestHandler extends APIHandlerBase {
582627 }
583628
584629 const entity : any = await prisma [ type ] . findUnique ( args ) ;
630+ const mappedType = this . reverseModelNameMap ( type ) ;
631+ const mappedRelationship = this . reverseModelNameMap ( relationship ) ;
585632
586633 if ( entity ?. _count ?. [ relationship ] !== undefined ) {
587634 // build up paginator
588635 const total = entity ?. _count ?. [ relationship ] as number ;
589- const url = this . makeNormalizedUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` , query ) ;
636+ const url = this . makeNormalizedUrl ( `/${ mappedType } /${ resourceId } /relationships/${ mappedRelationship } ` , query ) ;
590637 const { offset, limit } = this . getPagination ( query ) ;
591638 paginator = this . makePaginator ( url , offset , limit , total ) ;
592639 }
@@ -595,7 +642,7 @@ class RequestHandler extends APIHandlerBase {
595642 const serialized : any = await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
596643 linkers : {
597644 document : new Linker ( ( ) =>
598- this . makeLinkUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` )
645+ this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /relationships/${ mappedRelationship } ` )
599646 ) ,
600647 paginator,
601648 } ,
@@ -680,7 +727,8 @@ class RequestHandler extends APIHandlerBase {
680727 ] ) ;
681728 const total = count as number ;
682729
683- const url = this . makeNormalizedUrl ( `/${ type } ` , query ) ;
730+ const mappedType = this . reverseModelNameMap ( type ) ;
731+ const url = this . makeNormalizedUrl ( `/${ mappedType } ` , query ) ;
684732 const options : Partial < SerializerOptions > = {
685733 include,
686734 linkers : {
@@ -1009,9 +1057,12 @@ class RequestHandler extends APIHandlerBase {
10091057
10101058 const entity : any = await prisma [ type ] . update ( updateArgs ) ;
10111059
1060+ const mappedType = this . reverseModelNameMap ( type ) ;
1061+ const mappedRelationship = this . reverseModelNameMap ( relationship ) ;
1062+
10121063 const serialized : any = await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
10131064 linkers : {
1014- document : new Linker ( ( ) => this . makeLinkUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` ) ) ,
1065+ document : new Linker ( ( ) => this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /relationships/${ mappedRelationship } ` ) ) ,
10151066 } ,
10161067 onlyIdentifier : true ,
10171068 } ) ;
@@ -1147,7 +1198,7 @@ class RequestHandler extends APIHandlerBase {
11471198 }
11481199
11491200 private makeLinkUrl ( path : string ) {
1150- return `${ this . options . endpoint } ${ path } ` ;
1201+ return `${ this . options . endpoint } ${ this . prefix } ${ path } ` ;
11511202 }
11521203
11531204 private buildSerializers ( modelMeta : ModelMeta ) {
@@ -1156,15 +1207,16 @@ class RequestHandler extends APIHandlerBase {
11561207
11571208 for ( const model of Object . keys ( modelMeta . models ) ) {
11581209 const ids = getIdFields ( modelMeta , model ) ;
1210+ const mappedModel = this . reverseModelNameMap ( model ) ;
11591211
11601212 if ( ids . length < 1 ) {
11611213 continue ;
11621214 }
11631215
11641216 const linker = new Linker ( ( items ) =>
11651217 Array . isArray ( items )
1166- ? this . makeLinkUrl ( `/${ model } ` )
1167- : this . makeLinkUrl ( `/${ model } /${ this . getId ( model , items , modelMeta ) } ` )
1218+ ? this . makeLinkUrl ( `/${ mappedModel } ` )
1219+ : this . makeLinkUrl ( `/${ mappedModel } /${ this . getId ( model , items , modelMeta ) } ` )
11681220 ) ;
11691221 linkers [ model ] = linker ;
11701222
@@ -1208,6 +1260,9 @@ class RequestHandler extends APIHandlerBase {
12081260 }
12091261 const fieldIds = getIdFields ( modelMeta , fieldMeta . type ) ;
12101262 if ( fieldIds . length > 0 ) {
1263+ const mappedModel = this . reverseModelNameMap ( model ) ;
1264+ const mappedField = this . reverseModelNameMap ( field ) ;
1265+
12111266 const relator = new Relator (
12121267 async ( data ) => {
12131268 return ( data as any ) [ field ] ;
@@ -1218,16 +1273,16 @@ class RequestHandler extends APIHandlerBase {
12181273 linkers : {
12191274 related : new Linker ( ( primary ) =>
12201275 this . makeLinkUrl (
1221- `/${ lowerCaseFirst ( model ) } /${ this . getId ( model , primary , modelMeta ) } /${ field } `
1276+ `/${ lowerCaseFirst ( mappedModel ) } /${ this . getId ( model , primary , modelMeta ) } /${ mappedField } `
12221277 )
12231278 ) ,
12241279 relationship : new Linker ( ( primary ) =>
12251280 this . makeLinkUrl (
1226- `/${ lowerCaseFirst ( model ) } /${ this . getId (
1281+ `/${ lowerCaseFirst ( mappedModel ) } /${ this . getId (
12271282 model ,
12281283 primary ,
12291284 modelMeta
1230- ) } /relationships/${ field } `
1285+ ) } /relationships/${ mappedField } `
12311286 )
12321287 ) ,
12331288 } ,
0 commit comments