@@ -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,19 @@ type ModelInfo = {
6568 relationships : Record < string , RelationshipInfo > ;
6669} ;
6770
71+ type Match = {
72+ type : string ;
73+ id : string ;
74+ relationship : string ;
75+ } ;
76+
77+ enum UrlPatterns {
78+ SINGLE = 'single' ,
79+ FETCH_RELATIONSHIP = 'fetchRelationship' ,
80+ RELATIONSHIP = 'relationship' ,
81+ COLLECTION = 'collection' ,
82+ }
83+
6884class InvalidValueError extends Error {
6985 constructor ( public readonly message : string ) {
7086 super ( message ) ;
@@ -220,29 +236,60 @@ class RequestHandler extends APIHandlerBase {
220236 // divider used to separate compound ID fields
221237 private idDivider ;
222238
223- private urlPatterns ;
239+ private urlPatternMap : Record < UrlPatterns , UrlPattern > ;
240+ private modelNameMapping : Record < string , string > ;
241+ private reverseModelNameMapping : Record < string , string > ;
242+ private prefix : string | undefined ;
224243
225244 constructor ( private readonly options : Options ) {
226245 super ( ) ;
227246 this . idDivider = options . idDivider ?? prismaIdDivider ;
228247 const segmentCharset = options . urlSegmentCharset ?? 'a-zA-Z0-9-_~ %' ;
229- this . urlPatterns = this . buildUrlPatterns ( this . idDivider , segmentCharset ) ;
248+
249+ this . prefix = options . prefix ;
250+ this . modelNameMapping = options . modelNameMapping ?? { } ;
251+ this . reverseModelNameMapping = Object . fromEntries (
252+ Object . entries ( this . modelNameMapping ) . map ( ( [ k , v ] ) => [ v , k ] )
253+ ) ;
254+ this . urlPatternMap = this . buildUrlPatternMap ( segmentCharset ) ;
230255 }
231256
232- buildUrlPatterns ( idDivider : string , urlSegmentNameCharset : string ) {
257+ private buildUrlPatternMap ( urlSegmentNameCharset : string ) : Record < UrlPatterns , UrlPattern > {
233258 const options = { segmentValueCharset : urlSegmentNameCharset } ;
259+
260+ const buildPath = ( segments : string [ ] ) => {
261+ return ( this . prefix ?? '' ) + '/' + segments . join ( '/' ) ;
262+ } ;
263+
234264 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 ) ,
265+ [ UrlPatterns . SINGLE ] : new UrlPattern ( buildPath ( [ ':type' , ':id' ] ) , options ) ,
266+ [ UrlPatterns . FETCH_RELATIONSHIP ] : new UrlPattern ( buildPath ( [ ':type' , ':id' , ':relationship' ] ) , options ) ,
267+ [ UrlPatterns . RELATIONSHIP ] : new UrlPattern (
268+ buildPath ( [ ':type' , ':id' , 'relationships' , ':relationship' ] ) ,
269+ options
270+ ) ,
271+ [ UrlPatterns . COLLECTION ] : new UrlPattern ( buildPath ( [ ':type' ] ) , options ) ,
243272 } ;
244273 }
245274
275+ private reverseModelNameMap ( type : string ) : string {
276+ return this . reverseModelNameMapping [ type ] ?? type ;
277+ }
278+
279+ private matchUrlPattern ( path : string , routeType : UrlPatterns ) : Match {
280+ const pattern = this . urlPatternMap [ routeType ] ;
281+ if ( ! pattern ) {
282+ throw new InvalidValueError ( `Unknown route type: ${ routeType } ` ) ;
283+ }
284+
285+ const match = pattern . match ( path ) ;
286+ if ( match ) {
287+ match . type = this . modelNameMapping [ match . type ] ?? match . type ;
288+ match . relationship = this . modelNameMapping [ match . relationship ] ?? match . relationship ;
289+ }
290+ return match ;
291+ }
292+
246293 async handleRequest ( {
247294 prisma,
248295 method,
@@ -274,19 +321,18 @@ class RequestHandler extends APIHandlerBase {
274321 try {
275322 switch ( method ) {
276323 case 'GET' : {
277- let match = this . urlPatterns . single . match ( path ) ;
324+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
278325 if ( match ) {
279326 // single resource read
280327 return await this . processSingleRead ( prisma , match . type , match . id , query ) ;
281328 }
282-
283- match = this . urlPatterns . fetchRelationship . match ( path ) ;
329+ match = this . matchUrlPattern ( path , UrlPatterns . FETCH_RELATIONSHIP ) ;
284330 if ( match ) {
285331 // fetch related resource(s)
286332 return await this . processFetchRelated ( prisma , match . type , match . id , match . relationship , query ) ;
287333 }
288334
289- match = this . urlPatterns . relationship . match ( path ) ;
335+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
290336 if ( match ) {
291337 // read relationship
292338 return await this . processReadRelationship (
@@ -298,7 +344,7 @@ class RequestHandler extends APIHandlerBase {
298344 ) ;
299345 }
300346
301- match = this . urlPatterns . collection . match ( path ) ;
347+ match = this . matchUrlPattern ( path , UrlPatterns . COLLECTION ) ;
302348 if ( match ) {
303349 // collection read
304350 return await this . processCollectionRead ( prisma , match . type , query ) ;
@@ -311,8 +357,7 @@ class RequestHandler extends APIHandlerBase {
311357 if ( ! requestBody ) {
312358 return this . makeError ( 'invalidPayload' ) ;
313359 }
314-
315- let match = this . urlPatterns . collection . match ( path ) ;
360+ let match = this . matchUrlPattern ( path , UrlPatterns . COLLECTION ) ;
316361 if ( match ) {
317362 const body = requestBody as any ;
318363 const upsertMeta = this . upsertMetaSchema . safeParse ( body ) ;
@@ -338,8 +383,7 @@ class RequestHandler extends APIHandlerBase {
338383 ) ;
339384 }
340385 }
341-
342- match = this . urlPatterns . relationship . match ( path ) ;
386+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
343387 if ( match ) {
344388 // relationship creation (collection relationship only)
345389 return await this . processRelationshipCRUD (
@@ -362,8 +406,7 @@ class RequestHandler extends APIHandlerBase {
362406 if ( ! requestBody ) {
363407 return this . makeError ( 'invalidPayload' ) ;
364408 }
365-
366- let match = this . urlPatterns . single . match ( path ) ;
409+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
367410 if ( match ) {
368411 // resource update
369412 return await this . processUpdate (
@@ -376,8 +419,7 @@ class RequestHandler extends APIHandlerBase {
376419 zodSchemas
377420 ) ;
378421 }
379-
380- match = this . urlPatterns . relationship . match ( path ) ;
422+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
381423 if ( match ) {
382424 // relationship update
383425 return await this . processRelationshipCRUD (
@@ -395,13 +437,13 @@ class RequestHandler extends APIHandlerBase {
395437 }
396438
397439 case 'DELETE' : {
398- let match = this . urlPatterns . single . match ( path ) ;
440+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
399441 if ( match ) {
400442 // resource deletion
401443 return await this . processDelete ( prisma , match . type , match . id ) ;
402444 }
403445
404- match = this . urlPatterns . relationship . match ( path ) ;
446+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
405447 if ( match ) {
406448 // relationship deletion (collection relationship only)
407449 return await this . processRelationshipCRUD (
@@ -531,11 +573,13 @@ class RequestHandler extends APIHandlerBase {
531573 }
532574
533575 if ( entity ?. [ relationship ] ) {
576+ const mappedType = this . reverseModelNameMap ( type ) ;
577+ const mappedRelationship = this . reverseModelNameMap ( relationship ) ;
534578 return {
535579 status : 200 ,
536580 body : await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
537581 linkers : {
538- document : new Linker ( ( ) => this . makeLinkUrl ( `/${ type } /${ resourceId } /${ relationship } ` ) ) ,
582+ document : new Linker ( ( ) => this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /${ mappedRelationship } ` ) ) ,
539583 paginator,
540584 } ,
541585 include,
@@ -582,11 +626,13 @@ class RequestHandler extends APIHandlerBase {
582626 }
583627
584628 const entity : any = await prisma [ type ] . findUnique ( args ) ;
629+ const mappedType = this . reverseModelNameMap ( type ) ;
630+ const mappedRelationship = this . reverseModelNameMap ( relationship ) ;
585631
586632 if ( entity ?. _count ?. [ relationship ] !== undefined ) {
587633 // build up paginator
588634 const total = entity ?. _count ?. [ relationship ] as number ;
589- const url = this . makeNormalizedUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` , query ) ;
635+ const url = this . makeNormalizedUrl ( `/${ mappedType } /${ resourceId } /relationships/${ mappedRelationship } ` , query ) ;
590636 const { offset, limit } = this . getPagination ( query ) ;
591637 paginator = this . makePaginator ( url , offset , limit , total ) ;
592638 }
@@ -595,7 +641,7 @@ class RequestHandler extends APIHandlerBase {
595641 const serialized : any = await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
596642 linkers : {
597643 document : new Linker ( ( ) =>
598- this . makeLinkUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` )
644+ this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /relationships/${ mappedRelationship } ` )
599645 ) ,
600646 paginator,
601647 } ,
@@ -680,7 +726,8 @@ class RequestHandler extends APIHandlerBase {
680726 ] ) ;
681727 const total = count as number ;
682728
683- const url = this . makeNormalizedUrl ( `/${ type } ` , query ) ;
729+ const mappedType = this . reverseModelNameMap ( type ) ;
730+ const url = this . makeNormalizedUrl ( `/${ mappedType } ` , query ) ;
684731 const options : Partial < SerializerOptions > = {
685732 include,
686733 linkers : {
@@ -1009,9 +1056,12 @@ class RequestHandler extends APIHandlerBase {
10091056
10101057 const entity : any = await prisma [ type ] . update ( updateArgs ) ;
10111058
1059+ const mappedType = this . reverseModelNameMap ( type ) ;
1060+ const mappedRelationship = this . reverseModelNameMap ( relationship ) ;
1061+
10121062 const serialized : any = await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
10131063 linkers : {
1014- document : new Linker ( ( ) => this . makeLinkUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` ) ) ,
1064+ document : new Linker ( ( ) => this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /relationships/${ mappedRelationship } ` ) ) ,
10151065 } ,
10161066 onlyIdentifier : true ,
10171067 } ) ;
@@ -1147,7 +1197,7 @@ class RequestHandler extends APIHandlerBase {
11471197 }
11481198
11491199 private makeLinkUrl ( path : string ) {
1150- return `${ this . options . endpoint } ${ path } ` ;
1200+ return `${ this . options . endpoint } ${ this . prefix } ${ path } ` ;
11511201 }
11521202
11531203 private buildSerializers ( modelMeta : ModelMeta ) {
@@ -1156,15 +1206,16 @@ class RequestHandler extends APIHandlerBase {
11561206
11571207 for ( const model of Object . keys ( modelMeta . models ) ) {
11581208 const ids = getIdFields ( modelMeta , model ) ;
1209+ const mappedModel = this . reverseModelNameMap ( model ) ;
11591210
11601211 if ( ids . length < 1 ) {
11611212 continue ;
11621213 }
11631214
11641215 const linker = new Linker ( ( items ) =>
11651216 Array . isArray ( items )
1166- ? this . makeLinkUrl ( `/${ model } ` )
1167- : this . makeLinkUrl ( `/${ model } /${ this . getId ( model , items , modelMeta ) } ` )
1217+ ? this . makeLinkUrl ( `/${ mappedModel } ` )
1218+ : this . makeLinkUrl ( `/${ mappedModel } /${ this . getId ( model , items , modelMeta ) } ` )
11681219 ) ;
11691220 linkers [ model ] = linker ;
11701221
@@ -1208,6 +1259,9 @@ class RequestHandler extends APIHandlerBase {
12081259 }
12091260 const fieldIds = getIdFields ( modelMeta , fieldMeta . type ) ;
12101261 if ( fieldIds . length > 0 ) {
1262+ const mappedModel = this . reverseModelNameMap ( model ) ;
1263+ const mappedField = this . reverseModelNameMap ( field ) ;
1264+
12111265 const relator = new Relator (
12121266 async ( data ) => {
12131267 return ( data as any ) [ field ] ;
@@ -1218,16 +1272,16 @@ class RequestHandler extends APIHandlerBase {
12181272 linkers : {
12191273 related : new Linker ( ( primary ) =>
12201274 this . makeLinkUrl (
1221- `/${ lowerCaseFirst ( model ) } /${ this . getId ( model , primary , modelMeta ) } /${ field } `
1275+ `/${ lowerCaseFirst ( mappedModel ) } /${ this . getId ( model , primary , modelMeta ) } /${ mappedField } `
12221276 )
12231277 ) ,
12241278 relationship : new Linker ( ( primary ) =>
12251279 this . makeLinkUrl (
1226- `/${ lowerCaseFirst ( model ) } /${ this . getId (
1280+ `/${ lowerCaseFirst ( mappedModel ) } /${ this . getId (
12271281 model ,
12281282 primary ,
12291283 modelMeta
1230- ) } /relationships/${ field } `
1284+ ) } /relationships/${ mappedField } `
12311285 )
12321286 ) ,
12331287 } ,
0 commit comments