@@ -50,6 +50,8 @@ export type Options = {
50
50
* it should be included in the charset.
51
51
*/
52
52
urlSegmentCharset ?: string ;
53
+
54
+ modelNameMapping ?: Record < string , string > ;
53
55
} ;
54
56
55
57
type RelationshipInfo = {
@@ -65,6 +67,19 @@ type ModelInfo = {
65
67
relationships : Record < string , RelationshipInfo > ;
66
68
} ;
67
69
70
+ type Match = {
71
+ type : string ;
72
+ id : string ;
73
+ relationship : string ;
74
+ } ;
75
+
76
+ enum UrlPatterns {
77
+ SINGLE = 'single' ,
78
+ FETCH_RELATIONSHIP = 'fetchRelationship' ,
79
+ RELATIONSHIP = 'relationship' ,
80
+ COLLECTION = 'collection' ,
81
+ }
82
+
68
83
class InvalidValueError extends Error {
69
84
constructor ( public readonly message : string ) {
70
85
super ( message ) ;
@@ -220,29 +235,57 @@ class RequestHandler extends APIHandlerBase {
220
235
// divider used to separate compound ID fields
221
236
private idDivider ;
222
237
223
- private urlPatterns ;
238
+ private urlPatternMap : Record < UrlPatterns , UrlPattern > ;
239
+ private modelNameMapping : Record < string , string > ;
240
+ private reverseModelNameMapping : Record < string , string > ;
224
241
225
242
constructor ( private readonly options : Options ) {
226
243
super ( ) ;
227
244
this . idDivider = options . idDivider ?? prismaIdDivider ;
228
245
const segmentCharset = options . urlSegmentCharset ?? 'a-zA-Z0-9-_~ %' ;
229
- this . urlPatterns = this . buildUrlPatterns ( this . idDivider , segmentCharset ) ;
246
+
247
+ this . modelNameMapping = options . modelNameMapping ?? { } ;
248
+ this . reverseModelNameMapping = Object . fromEntries (
249
+ Object . entries ( this . modelNameMapping ) . map ( ( [ k , v ] ) => [ v , k ] )
250
+ ) ;
251
+ this . urlPatternMap = this . buildUrlPatternMap ( segmentCharset ) ;
230
252
}
231
253
232
- buildUrlPatterns ( idDivider : string , urlSegmentNameCharset : string ) {
254
+ private buildUrlPatternMap ( urlSegmentNameCharset : string ) : Record < UrlPatterns , UrlPattern > {
233
255
const options = { segmentValueCharset : urlSegmentNameCharset } ;
256
+
257
+ const buildPath = ( segments : string [ ] ) => {
258
+ return '/' + segments . join ( '/' ) ;
259
+ } ;
260
+
234
261
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 ) ,
262
+ [ UrlPatterns . SINGLE ] : new UrlPattern ( buildPath ( [ ':type' , ':id' ] ) , options ) ,
263
+ [ UrlPatterns . FETCH_RELATIONSHIP ] : new UrlPattern ( buildPath ( [ ':type' , ':id' , ':relationship' ] ) , options ) ,
264
+ [ UrlPatterns . RELATIONSHIP ] : new UrlPattern (
265
+ buildPath ( [ ':type' , ':id' , 'relationships' , ':relationship' ] ) ,
266
+ options
267
+ ) ,
268
+ [ UrlPatterns . COLLECTION ] : new UrlPattern ( buildPath ( [ ':type' ] ) , options ) ,
243
269
} ;
244
270
}
245
271
272
+ private reverseModelNameMap ( type : string ) : string {
273
+ return this . reverseModelNameMapping [ type ] ?? type ;
274
+ }
275
+
276
+ private matchUrlPattern ( path : string , routeType : UrlPatterns ) : Match {
277
+ const pattern = this . urlPatternMap [ routeType ] ;
278
+ if ( ! pattern ) {
279
+ throw new InvalidValueError ( `Unknown route type: ${ routeType } ` ) ;
280
+ }
281
+
282
+ const match = pattern . match ( path ) ;
283
+ if ( match ) {
284
+ match . type = this . modelNameMapping [ match . type ] ?? match . type ;
285
+ }
286
+ return match ;
287
+ }
288
+
246
289
async handleRequest ( {
247
290
prisma,
248
291
method,
@@ -274,19 +317,18 @@ class RequestHandler extends APIHandlerBase {
274
317
try {
275
318
switch ( method ) {
276
319
case 'GET' : {
277
- let match = this . urlPatterns . single . match ( path ) ;
320
+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
278
321
if ( match ) {
279
322
// single resource read
280
323
return await this . processSingleRead ( prisma , match . type , match . id , query ) ;
281
324
}
282
-
283
- match = this . urlPatterns . fetchRelationship . match ( path ) ;
325
+ match = this . matchUrlPattern ( path , UrlPatterns . FETCH_RELATIONSHIP ) ;
284
326
if ( match ) {
285
327
// fetch related resource(s)
286
328
return await this . processFetchRelated ( prisma , match . type , match . id , match . relationship , query ) ;
287
329
}
288
330
289
- match = this . urlPatterns . relationship . match ( path ) ;
331
+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
290
332
if ( match ) {
291
333
// read relationship
292
334
return await this . processReadRelationship (
@@ -298,7 +340,7 @@ class RequestHandler extends APIHandlerBase {
298
340
) ;
299
341
}
300
342
301
- match = this . urlPatterns . collection . match ( path ) ;
343
+ match = this . matchUrlPattern ( path , UrlPatterns . COLLECTION ) ;
302
344
if ( match ) {
303
345
// collection read
304
346
return await this . processCollectionRead ( prisma , match . type , query ) ;
@@ -311,8 +353,7 @@ class RequestHandler extends APIHandlerBase {
311
353
if ( ! requestBody ) {
312
354
return this . makeError ( 'invalidPayload' ) ;
313
355
}
314
-
315
- let match = this . urlPatterns . collection . match ( path ) ;
356
+ let match = this . matchUrlPattern ( path , UrlPatterns . COLLECTION ) ;
316
357
if ( match ) {
317
358
const body = requestBody as any ;
318
359
const upsertMeta = this . upsertMetaSchema . safeParse ( body ) ;
@@ -338,8 +379,7 @@ class RequestHandler extends APIHandlerBase {
338
379
) ;
339
380
}
340
381
}
341
-
342
- match = this . urlPatterns . relationship . match ( path ) ;
382
+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
343
383
if ( match ) {
344
384
// relationship creation (collection relationship only)
345
385
return await this . processRelationshipCRUD (
@@ -362,8 +402,7 @@ class RequestHandler extends APIHandlerBase {
362
402
if ( ! requestBody ) {
363
403
return this . makeError ( 'invalidPayload' ) ;
364
404
}
365
-
366
- let match = this . urlPatterns . single . match ( path ) ;
405
+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
367
406
if ( match ) {
368
407
// resource update
369
408
return await this . processUpdate (
@@ -376,8 +415,7 @@ class RequestHandler extends APIHandlerBase {
376
415
zodSchemas
377
416
) ;
378
417
}
379
-
380
- match = this . urlPatterns . relationship . match ( path ) ;
418
+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
381
419
if ( match ) {
382
420
// relationship update
383
421
return await this . processRelationshipCRUD (
@@ -395,13 +433,13 @@ class RequestHandler extends APIHandlerBase {
395
433
}
396
434
397
435
case 'DELETE' : {
398
- let match = this . urlPatterns . single . match ( path ) ;
436
+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
399
437
if ( match ) {
400
438
// resource deletion
401
439
return await this . processDelete ( prisma , match . type , match . id ) ;
402
440
}
403
441
404
- match = this . urlPatterns . relationship . match ( path ) ;
442
+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
405
443
if ( match ) {
406
444
// relationship deletion (collection relationship only)
407
445
return await this . processRelationshipCRUD (
@@ -531,11 +569,12 @@ class RequestHandler extends APIHandlerBase {
531
569
}
532
570
533
571
if ( entity ?. [ relationship ] ) {
572
+ const mappedType = this . reverseModelNameMap ( type ) ;
534
573
return {
535
574
status : 200 ,
536
575
body : await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
537
576
linkers : {
538
- document : new Linker ( ( ) => this . makeLinkUrl ( `/${ type } /${ resourceId } /${ relationship } ` ) ) ,
577
+ document : new Linker ( ( ) => this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /${ relationship } ` ) ) ,
539
578
paginator,
540
579
} ,
541
580
include,
@@ -582,11 +621,12 @@ class RequestHandler extends APIHandlerBase {
582
621
}
583
622
584
623
const entity : any = await prisma [ type ] . findUnique ( args ) ;
624
+ const mappedType = this . reverseModelNameMap ( type ) ;
585
625
586
626
if ( entity ?. _count ?. [ relationship ] !== undefined ) {
587
627
// build up paginator
588
628
const total = entity ?. _count ?. [ relationship ] as number ;
589
- const url = this . makeNormalizedUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` , query ) ;
629
+ const url = this . makeNormalizedUrl ( `/${ mappedType } /${ resourceId } /relationships/${ relationship } ` , query ) ;
590
630
const { offset, limit } = this . getPagination ( query ) ;
591
631
paginator = this . makePaginator ( url , offset , limit , total ) ;
592
632
}
@@ -595,7 +635,7 @@ class RequestHandler extends APIHandlerBase {
595
635
const serialized : any = await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
596
636
linkers : {
597
637
document : new Linker ( ( ) =>
598
- this . makeLinkUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` )
638
+ this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /relationships/${ relationship } ` )
599
639
) ,
600
640
paginator,
601
641
} ,
@@ -680,7 +720,8 @@ class RequestHandler extends APIHandlerBase {
680
720
] ) ;
681
721
const total = count as number ;
682
722
683
- const url = this . makeNormalizedUrl ( `/${ type } ` , query ) ;
723
+ const mappedType = this . reverseModelNameMap ( type ) ;
724
+ const url = this . makeNormalizedUrl ( `/${ mappedType } ` , query ) ;
684
725
const options : Partial < SerializerOptions > = {
685
726
include,
686
727
linkers : {
@@ -1009,9 +1050,13 @@ class RequestHandler extends APIHandlerBase {
1009
1050
1010
1051
const entity : any = await prisma [ type ] . update ( updateArgs ) ;
1011
1052
1053
+ const mappedType = this . reverseModelNameMap ( type ) ;
1054
+
1012
1055
const serialized : any = await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
1013
1056
linkers : {
1014
- document : new Linker ( ( ) => this . makeLinkUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` ) ) ,
1057
+ document : new Linker ( ( ) =>
1058
+ this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /relationships/${ relationship } ` )
1059
+ ) ,
1015
1060
} ,
1016
1061
onlyIdentifier : true ,
1017
1062
} ) ;
@@ -1156,15 +1201,16 @@ class RequestHandler extends APIHandlerBase {
1156
1201
1157
1202
for ( const model of Object . keys ( modelMeta . models ) ) {
1158
1203
const ids = getIdFields ( modelMeta , model ) ;
1204
+ const mappedModel = this . reverseModelNameMap ( model ) ;
1159
1205
1160
1206
if ( ids . length < 1 ) {
1161
1207
continue ;
1162
1208
}
1163
1209
1164
1210
const linker = new Linker ( ( items ) =>
1165
1211
Array . isArray ( items )
1166
- ? this . makeLinkUrl ( `/${ model } ` )
1167
- : this . makeLinkUrl ( `/${ model } /${ this . getId ( model , items , modelMeta ) } ` )
1212
+ ? this . makeLinkUrl ( `/${ mappedModel } ` )
1213
+ : this . makeLinkUrl ( `/${ mappedModel } /${ this . getId ( model , items , modelMeta ) } ` )
1168
1214
) ;
1169
1215
linkers [ model ] = linker ;
1170
1216
@@ -1208,6 +1254,9 @@ class RequestHandler extends APIHandlerBase {
1208
1254
}
1209
1255
const fieldIds = getIdFields ( modelMeta , fieldMeta . type ) ;
1210
1256
if ( fieldIds . length > 0 ) {
1257
+ const mappedModel = this . reverseModelNameMap ( model ) ;
1258
+ const mappedField = this . reverseModelNameMap ( field ) ;
1259
+
1211
1260
const relator = new Relator (
1212
1261
async ( data ) => {
1213
1262
return ( data as any ) [ field ] ;
@@ -1218,16 +1267,20 @@ class RequestHandler extends APIHandlerBase {
1218
1267
linkers : {
1219
1268
related : new Linker ( ( primary ) =>
1220
1269
this . makeLinkUrl (
1221
- `/${ lowerCaseFirst ( model ) } /${ this . getId ( model , primary , modelMeta ) } /${ field } `
1270
+ `/${ lowerCaseFirst ( mappedModel ) } /${ this . getId (
1271
+ model ,
1272
+ primary ,
1273
+ modelMeta
1274
+ ) } /${ mappedField } `
1222
1275
)
1223
1276
) ,
1224
1277
relationship : new Linker ( ( primary ) =>
1225
1278
this . makeLinkUrl (
1226
- `/${ lowerCaseFirst ( model ) } /${ this . getId (
1279
+ `/${ lowerCaseFirst ( mappedModel ) } /${ this . getId (
1227
1280
model ,
1228
1281
primary ,
1229
1282
modelMeta
1230
- ) } /relationships/${ field } `
1283
+ ) } /relationships/${ mappedField } `
1231
1284
)
1232
1285
) ,
1233
1286
} ,
0 commit comments