@@ -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,71 @@ 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 . modelNameMapping = Object . fromEntries (
249
+ Object . entries ( this . modelNameMapping ) . map ( ( [ k , v ] ) => [ lowerCaseFirst ( k ) , v ] )
250
+ ) ;
251
+ this . reverseModelNameMapping = Object . fromEntries (
252
+ Object . entries ( this . modelNameMapping ) . map ( ( [ k , v ] ) => [ v , k ] )
253
+ ) ;
254
+ this . urlPatternMap = this . buildUrlPatternMap ( segmentCharset ) ;
230
255
}
231
256
232
- buildUrlPatterns ( idDivider : string , urlSegmentNameCharset : string ) {
257
+ private buildUrlPatternMap ( urlSegmentNameCharset : string ) : Record < UrlPatterns , UrlPattern > {
233
258
const options = { segmentValueCharset : urlSegmentNameCharset } ;
259
+
260
+ const buildPath = ( segments : string [ ] ) => {
261
+ return '/' + segments . join ( '/' ) ;
262
+ } ;
263
+
234
264
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 ) ,
243
272
} ;
244
273
}
245
274
275
+ private mapModelName ( modelName : string ) : string {
276
+ return this . modelNameMapping [ modelName ] ?? modelName ;
277
+ }
278
+
279
+ private matchUrlPattern ( path : string , routeType : UrlPatterns ) : Match | undefined {
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
+ return ;
288
+ }
289
+
290
+ if ( match . type in this . modelNameMapping ) {
291
+ throw new InvalidValueError (
292
+ `use the mapped model name: ${ this . modelNameMapping [ match . type ] } and not ${ match . type } `
293
+ ) ;
294
+ }
295
+
296
+ if ( match . type in this . reverseModelNameMapping ) {
297
+ match . type = this . reverseModelNameMapping [ match . type ] ;
298
+ }
299
+
300
+ return match ;
301
+ }
302
+
246
303
async handleRequest ( {
247
304
prisma,
248
305
method,
@@ -274,19 +331,18 @@ class RequestHandler extends APIHandlerBase {
274
331
try {
275
332
switch ( method ) {
276
333
case 'GET' : {
277
- let match = this . urlPatterns . single . match ( path ) ;
334
+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
278
335
if ( match ) {
279
336
// single resource read
280
337
return await this . processSingleRead ( prisma , match . type , match . id , query ) ;
281
338
}
282
-
283
- match = this . urlPatterns . fetchRelationship . match ( path ) ;
339
+ match = this . matchUrlPattern ( path , UrlPatterns . FETCH_RELATIONSHIP ) ;
284
340
if ( match ) {
285
341
// fetch related resource(s)
286
342
return await this . processFetchRelated ( prisma , match . type , match . id , match . relationship , query ) ;
287
343
}
288
344
289
- match = this . urlPatterns . relationship . match ( path ) ;
345
+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
290
346
if ( match ) {
291
347
// read relationship
292
348
return await this . processReadRelationship (
@@ -298,7 +354,7 @@ class RequestHandler extends APIHandlerBase {
298
354
) ;
299
355
}
300
356
301
- match = this . urlPatterns . collection . match ( path ) ;
357
+ match = this . matchUrlPattern ( path , UrlPatterns . COLLECTION ) ;
302
358
if ( match ) {
303
359
// collection read
304
360
return await this . processCollectionRead ( prisma , match . type , query ) ;
@@ -311,8 +367,7 @@ class RequestHandler extends APIHandlerBase {
311
367
if ( ! requestBody ) {
312
368
return this . makeError ( 'invalidPayload' ) ;
313
369
}
314
-
315
- let match = this . urlPatterns . collection . match ( path ) ;
370
+ let match = this . matchUrlPattern ( path , UrlPatterns . COLLECTION ) ;
316
371
if ( match ) {
317
372
const body = requestBody as any ;
318
373
const upsertMeta = this . upsertMetaSchema . safeParse ( body ) ;
@@ -338,8 +393,7 @@ class RequestHandler extends APIHandlerBase {
338
393
) ;
339
394
}
340
395
}
341
-
342
- match = this . urlPatterns . relationship . match ( path ) ;
396
+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
343
397
if ( match ) {
344
398
// relationship creation (collection relationship only)
345
399
return await this . processRelationshipCRUD (
@@ -362,8 +416,7 @@ class RequestHandler extends APIHandlerBase {
362
416
if ( ! requestBody ) {
363
417
return this . makeError ( 'invalidPayload' ) ;
364
418
}
365
-
366
- let match = this . urlPatterns . single . match ( path ) ;
419
+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
367
420
if ( match ) {
368
421
// resource update
369
422
return await this . processUpdate (
@@ -376,8 +429,7 @@ class RequestHandler extends APIHandlerBase {
376
429
zodSchemas
377
430
) ;
378
431
}
379
-
380
- match = this . urlPatterns . relationship . match ( path ) ;
432
+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
381
433
if ( match ) {
382
434
// relationship update
383
435
return await this . processRelationshipCRUD (
@@ -395,13 +447,13 @@ class RequestHandler extends APIHandlerBase {
395
447
}
396
448
397
449
case 'DELETE' : {
398
- let match = this . urlPatterns . single . match ( path ) ;
450
+ let match = this . matchUrlPattern ( path , UrlPatterns . SINGLE ) ;
399
451
if ( match ) {
400
452
// resource deletion
401
453
return await this . processDelete ( prisma , match . type , match . id ) ;
402
454
}
403
455
404
- match = this . urlPatterns . relationship . match ( path ) ;
456
+ match = this . matchUrlPattern ( path , UrlPatterns . RELATIONSHIP ) ;
405
457
if ( match ) {
406
458
// relationship deletion (collection relationship only)
407
459
return await this . processRelationshipCRUD (
@@ -531,11 +583,12 @@ class RequestHandler extends APIHandlerBase {
531
583
}
532
584
533
585
if ( entity ?. [ relationship ] ) {
586
+ const mappedType = this . mapModelName ( type ) ;
534
587
return {
535
588
status : 200 ,
536
589
body : await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
537
590
linkers : {
538
- document : new Linker ( ( ) => this . makeLinkUrl ( `/${ type } /${ resourceId } /${ relationship } ` ) ) ,
591
+ document : new Linker ( ( ) => this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /${ relationship } ` ) ) ,
539
592
paginator,
540
593
} ,
541
594
include,
@@ -582,11 +635,12 @@ class RequestHandler extends APIHandlerBase {
582
635
}
583
636
584
637
const entity : any = await prisma [ type ] . findUnique ( args ) ;
638
+ const mappedType = this . mapModelName ( type ) ;
585
639
586
640
if ( entity ?. _count ?. [ relationship ] !== undefined ) {
587
641
// build up paginator
588
642
const total = entity ?. _count ?. [ relationship ] as number ;
589
- const url = this . makeNormalizedUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` , query ) ;
643
+ const url = this . makeNormalizedUrl ( `/${ mappedType } /${ resourceId } /relationships/${ relationship } ` , query ) ;
590
644
const { offset, limit } = this . getPagination ( query ) ;
591
645
paginator = this . makePaginator ( url , offset , limit , total ) ;
592
646
}
@@ -595,7 +649,7 @@ class RequestHandler extends APIHandlerBase {
595
649
const serialized : any = await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
596
650
linkers : {
597
651
document : new Linker ( ( ) =>
598
- this . makeLinkUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` )
652
+ this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /relationships/${ relationship } ` )
599
653
) ,
600
654
paginator,
601
655
} ,
@@ -680,7 +734,8 @@ class RequestHandler extends APIHandlerBase {
680
734
] ) ;
681
735
const total = count as number ;
682
736
683
- const url = this . makeNormalizedUrl ( `/${ type } ` , query ) ;
737
+ const mappedType = this . mapModelName ( type ) ;
738
+ const url = this . makeNormalizedUrl ( `/${ mappedType } ` , query ) ;
684
739
const options : Partial < SerializerOptions > = {
685
740
include,
686
741
linkers : {
@@ -1009,9 +1064,13 @@ class RequestHandler extends APIHandlerBase {
1009
1064
1010
1065
const entity : any = await prisma [ type ] . update ( updateArgs ) ;
1011
1066
1067
+ const mappedType = this . mapModelName ( type ) ;
1068
+
1012
1069
const serialized : any = await this . serializeItems ( relationInfo . type , entity [ relationship ] , {
1013
1070
linkers : {
1014
- document : new Linker ( ( ) => this . makeLinkUrl ( `/${ type } /${ resourceId } /relationships/${ relationship } ` ) ) ,
1071
+ document : new Linker ( ( ) =>
1072
+ this . makeLinkUrl ( `/${ mappedType } /${ resourceId } /relationships/${ relationship } ` )
1073
+ ) ,
1015
1074
} ,
1016
1075
onlyIdentifier : true ,
1017
1076
} ) ;
@@ -1156,15 +1215,16 @@ class RequestHandler extends APIHandlerBase {
1156
1215
1157
1216
for ( const model of Object . keys ( modelMeta . models ) ) {
1158
1217
const ids = getIdFields ( modelMeta , model ) ;
1218
+ const mappedModel = this . mapModelName ( model ) ;
1159
1219
1160
1220
if ( ids . length < 1 ) {
1161
1221
continue ;
1162
1222
}
1163
1223
1164
1224
const linker = new Linker ( ( items ) =>
1165
1225
Array . isArray ( items )
1166
- ? this . makeLinkUrl ( `/${ model } ` )
1167
- : this . makeLinkUrl ( `/${ model } /${ this . getId ( model , items , modelMeta ) } ` )
1226
+ ? this . makeLinkUrl ( `/${ mappedModel } ` )
1227
+ : this . makeLinkUrl ( `/${ mappedModel } /${ this . getId ( model , items , modelMeta ) } ` )
1168
1228
) ;
1169
1229
linkers [ model ] = linker ;
1170
1230
@@ -1208,6 +1268,8 @@ class RequestHandler extends APIHandlerBase {
1208
1268
}
1209
1269
const fieldIds = getIdFields ( modelMeta , fieldMeta . type ) ;
1210
1270
if ( fieldIds . length > 0 ) {
1271
+ const mappedModel = this . mapModelName ( model ) ;
1272
+
1211
1273
const relator = new Relator (
1212
1274
async ( data ) => {
1213
1275
return ( data as any ) [ field ] ;
@@ -1223,7 +1285,7 @@ class RequestHandler extends APIHandlerBase {
1223
1285
) ,
1224
1286
relationship : new Linker ( ( primary ) =>
1225
1287
this . makeLinkUrl (
1226
- `/${ lowerCaseFirst ( model ) } /${ this . getId (
1288
+ `/${ lowerCaseFirst ( mappedModel ) } /${ this . getId (
1227
1289
model ,
1228
1290
primary ,
1229
1291
modelMeta
0 commit comments