@@ -22,19 +22,6 @@ import { LoggerConfig, Response } from '../../types';
2222import { APIHandlerBase , RequestContext } from '../base' ;
2323import { logWarning , registerCustomSerializers } from '../utils' ;
2424
25- const urlPatterns = {
26- // collection operations
27- collection : new UrlPattern ( '/:type' ) ,
28- // single resource operations
29- single : new UrlPattern ( '/:type/:id' ) ,
30- // related entity fetching
31- fetchRelationship : new UrlPattern ( '/:type/:id/:relationship' ) ,
32- // relationship operations
33- relationship : new UrlPattern ( '/:type/:id/relationships/:relationship' ) ,
34- } ;
35-
36- export const idDivider = '_' ;
37-
3825/**
3926 * Request handler options
4027 */
@@ -52,6 +39,19 @@ export type Options = {
5239 * Defaults to 100. Set to Infinity to disable pagination.
5340 */
5441 pageSize ?: number ;
42+
43+ /**
44+ * The divider used to separate compound ID fields in the URL.
45+ * Defaults to '_'.
46+ */
47+ idDivider ?: string ;
48+
49+ /**
50+ * The charset used for URL segment values. Defaults to `a-zA-Z0-9-_~ %`. You can change it if your entity's ID values
51+ * allow different characters. Specifically, if your models use compound IDs and the idDivider is set to a different value,
52+ * it should be included in the charset.
53+ */
54+ urlSegmentCharset ?: string ;
5555} ;
5656
5757type RelationshipInfo = {
@@ -93,6 +93,8 @@ const FilterOperations = [
9393
9494type FilterOperationType = ( typeof FilterOperations ) [ number ] | undefined ;
9595
96+ const prismaIdDivider = '_' ;
97+
9698registerCustomSerializers ( ) ;
9799
98100/**
@@ -210,8 +212,30 @@ class RequestHandler extends APIHandlerBase {
210212 // all known types and their metadata
211213 private typeMap : Record < string , ModelInfo > ;
212214
215+ // divider used to separate compound ID fields
216+ private idDivider ;
217+
218+ private urlPatterns ;
219+
213220 constructor ( private readonly options : Options ) {
214221 super ( ) ;
222+ this . idDivider = options . idDivider ?? prismaIdDivider ;
223+ const segmentCharset = options . urlSegmentCharset ?? 'a-zA-Z0-9-_~ %' ;
224+ this . urlPatterns = this . buildUrlPatterns ( this . idDivider , segmentCharset ) ;
225+ }
226+
227+ buildUrlPatterns ( idDivider : string , urlSegmentNameCharset : string ) {
228+ const options = { segmentValueCharset : urlSegmentNameCharset } ;
229+ return {
230+ // collection operations
231+ collection : new UrlPattern ( '/:type' , options ) ,
232+ // single resource operations
233+ single : new UrlPattern ( '/:type/:id' , options ) ,
234+ // related entity fetching
235+ fetchRelationship : new UrlPattern ( '/:type/:id/:relationship' , options ) ,
236+ // relationship operations
237+ relationship : new UrlPattern ( '/:type/:id/relationships/:relationship' , options ) ,
238+ } ;
215239 }
216240
217241 async handleRequest ( {
@@ -245,19 +269,19 @@ class RequestHandler extends APIHandlerBase {
245269 try {
246270 switch ( method ) {
247271 case 'GET' : {
248- let match = urlPatterns . single . match ( path ) ;
272+ let match = this . urlPatterns . single . match ( path ) ;
249273 if ( match ) {
250274 // single resource read
251275 return await this . processSingleRead ( prisma , match . type , match . id , query ) ;
252276 }
253277
254- match = urlPatterns . fetchRelationship . match ( path ) ;
278+ match = this . urlPatterns . fetchRelationship . match ( path ) ;
255279 if ( match ) {
256280 // fetch related resource(s)
257281 return await this . processFetchRelated ( prisma , match . type , match . id , match . relationship , query ) ;
258282 }
259283
260- match = urlPatterns . relationship . match ( path ) ;
284+ match = this . urlPatterns . relationship . match ( path ) ;
261285 if ( match ) {
262286 // read relationship
263287 return await this . processReadRelationship (
@@ -269,7 +293,7 @@ class RequestHandler extends APIHandlerBase {
269293 ) ;
270294 }
271295
272- match = urlPatterns . collection . match ( path ) ;
296+ match = this . urlPatterns . collection . match ( path ) ;
273297 if ( match ) {
274298 // collection read
275299 return await this . processCollectionRead ( prisma , match . type , query ) ;
@@ -283,13 +307,13 @@ class RequestHandler extends APIHandlerBase {
283307 return this . makeError ( 'invalidPayload' ) ;
284308 }
285309
286- let match = urlPatterns . collection . match ( path ) ;
310+ let match = this . urlPatterns . collection . match ( path ) ;
287311 if ( match ) {
288312 // resource creation
289313 return await this . processCreate ( prisma , match . type , query , requestBody , modelMeta , zodSchemas ) ;
290314 }
291315
292- match = urlPatterns . relationship . match ( path ) ;
316+ match = this . urlPatterns . relationship . match ( path ) ;
293317 if ( match ) {
294318 // relationship creation (collection relationship only)
295319 return await this . processRelationshipCRUD (
@@ -313,7 +337,7 @@ class RequestHandler extends APIHandlerBase {
313337 return this . makeError ( 'invalidPayload' ) ;
314338 }
315339
316- let match = urlPatterns . single . match ( path ) ;
340+ let match = this . urlPatterns . single . match ( path ) ;
317341 if ( match ) {
318342 // resource update
319343 return await this . processUpdate (
@@ -327,7 +351,7 @@ class RequestHandler extends APIHandlerBase {
327351 ) ;
328352 }
329353
330- match = urlPatterns . relationship . match ( path ) ;
354+ match = this . urlPatterns . relationship . match ( path ) ;
331355 if ( match ) {
332356 // relationship update
333357 return await this . processRelationshipCRUD (
@@ -345,13 +369,13 @@ class RequestHandler extends APIHandlerBase {
345369 }
346370
347371 case 'DELETE' : {
348- let match = urlPatterns . single . match ( path ) ;
372+ let match = this . urlPatterns . single . match ( path ) ;
349373 if ( match ) {
350374 // resource deletion
351375 return await this . processDelete ( prisma , match . type , match . id ) ;
352376 }
353377
354- match = urlPatterns . relationship . match ( path ) ;
378+ match = this . urlPatterns . relationship . match ( path ) ;
355379 if ( match ) {
356380 // relationship deletion (collection relationship only)
357381 return await this . processRelationshipCRUD (
@@ -391,7 +415,7 @@ class RequestHandler extends APIHandlerBase {
391415 return this . makeUnsupportedModelError ( type ) ;
392416 }
393417
394- const args : any = { where : this . makeIdFilter ( typeInfo . idFields , resourceId ) } ;
418+ const args : any = { where : this . makePrismaIdFilter ( typeInfo . idFields , resourceId ) } ;
395419
396420 // include IDs of relation fields so that they can be serialized
397421 this . includeRelationshipIds ( type , args , 'include' ) ;
@@ -456,7 +480,7 @@ class RequestHandler extends APIHandlerBase {
456480
457481 select = select ?? { [ relationship ] : true } ;
458482 const args : any = {
459- where : this . makeIdFilter ( typeInfo . idFields , resourceId ) ,
483+ where : this . makePrismaIdFilter ( typeInfo . idFields , resourceId ) ,
460484 select,
461485 } ;
462486
@@ -514,7 +538,7 @@ class RequestHandler extends APIHandlerBase {
514538 }
515539
516540 const args : any = {
517- where : this . makeIdFilter ( typeInfo . idFields , resourceId ) ,
541+ where : this . makePrismaIdFilter ( typeInfo . idFields , resourceId ) ,
518542 select : this . makeIdSelect ( typeInfo . idFields ) ,
519543 } ;
520544
@@ -753,7 +777,7 @@ class RequestHandler extends APIHandlerBase {
753777 if ( relationInfo . isCollection ) {
754778 createPayload . data [ key ] = {
755779 connect : enumerate ( data . data ) . map ( ( item : any ) => ( {
756- [ this . makeIdKey ( relationInfo . idFields ) ] : item . id ,
780+ [ this . makePrismaIdKey ( relationInfo . idFields ) ] : item . id ,
757781 } ) ) ,
758782 } ;
759783 } else {
@@ -762,15 +786,15 @@ class RequestHandler extends APIHandlerBase {
762786 }
763787 createPayload . data [ key ] = {
764788 connect : {
765- [ this . makeIdKey ( relationInfo . idFields ) ] : data . data . id ,
789+ [ this . makePrismaIdKey ( relationInfo . idFields ) ] : data . data . id ,
766790 } ,
767791 } ;
768792 }
769793
770794 // make sure ID fields are included for result serialization
771795 createPayload . include = {
772796 ...createPayload . include ,
773- [ key ] : { select : { [ this . makeIdKey ( relationInfo . idFields ) ] : true } } ,
797+ [ key ] : { select : { [ this . makePrismaIdKey ( relationInfo . idFields ) ] : true } } ,
774798 } ;
775799 }
776800 }
@@ -807,7 +831,7 @@ class RequestHandler extends APIHandlerBase {
807831 }
808832
809833 const updateArgs : any = {
810- where : this . makeIdFilter ( typeInfo . idFields , resourceId ) ,
834+ where : this . makePrismaIdFilter ( typeInfo . idFields , resourceId ) ,
811835 select : {
812836 ...typeInfo . idFields . reduce ( ( acc , field ) => ( { ...acc , [ field . name ] : true } ) , { } ) ,
813837 [ relationship ] : { select : this . makeIdSelect ( relationInfo . idFields ) } ,
@@ -842,7 +866,7 @@ class RequestHandler extends APIHandlerBase {
842866 updateArgs . data = {
843867 [ relationship ] : {
844868 connect : {
845- [ this . makeIdKey ( relationInfo . idFields ) ] : parsed . data . data . id ,
869+ [ this . makePrismaIdKey ( relationInfo . idFields ) ] : parsed . data . data . id ,
846870 } ,
847871 } ,
848872 } ;
@@ -866,7 +890,7 @@ class RequestHandler extends APIHandlerBase {
866890 updateArgs . data = {
867891 [ relationship ] : {
868892 [ relationVerb ] : enumerate ( parsed . data . data ) . map ( ( item : any ) =>
869- this . makeIdFilter ( relationInfo . idFields , item . id )
893+ this . makePrismaIdFilter ( relationInfo . idFields , item . id )
870894 ) ,
871895 } ,
872896 } ;
@@ -907,7 +931,7 @@ class RequestHandler extends APIHandlerBase {
907931 }
908932
909933 const updatePayload : any = {
910- where : this . makeIdFilter ( typeInfo . idFields , resourceId ) ,
934+ where : this . makePrismaIdFilter ( typeInfo . idFields , resourceId ) ,
911935 data : { ...attributes } ,
912936 } ;
913937
@@ -926,7 +950,7 @@ class RequestHandler extends APIHandlerBase {
926950 if ( relationInfo . isCollection ) {
927951 updatePayload . data [ key ] = {
928952 set : enumerate ( data . data ) . map ( ( item : any ) => ( {
929- [ this . makeIdKey ( relationInfo . idFields ) ] : item . id ,
953+ [ this . makePrismaIdKey ( relationInfo . idFields ) ] : item . id ,
930954 } ) ) ,
931955 } ;
932956 } else {
@@ -935,13 +959,13 @@ class RequestHandler extends APIHandlerBase {
935959 }
936960 updatePayload . data [ key ] = {
937961 set : {
938- [ this . makeIdKey ( relationInfo . idFields ) ] : data . data . id ,
962+ [ this . makePrismaIdKey ( relationInfo . idFields ) ] : data . data . id ,
939963 } ,
940964 } ;
941965 }
942966 updatePayload . include = {
943967 ...updatePayload . include ,
944- [ key ] : { select : { [ this . makeIdKey ( relationInfo . idFields ) ] : true } } ,
968+ [ key ] : { select : { [ this . makePrismaIdKey ( relationInfo . idFields ) ] : true } } ,
945969 } ;
946970 }
947971 }
@@ -960,7 +984,7 @@ class RequestHandler extends APIHandlerBase {
960984 }
961985
962986 await prisma [ type ] . delete ( {
963- where : this . makeIdFilter ( typeInfo . idFields , resourceId ) ,
987+ where : this . makePrismaIdFilter ( typeInfo . idFields , resourceId ) ,
964988 } ) ;
965989 return {
966990 status : 204 ,
@@ -1110,7 +1134,7 @@ class RequestHandler extends APIHandlerBase {
11101134 if ( ids . length === 0 ) {
11111135 return undefined ;
11121136 } else {
1113- return data [ ids . map ( ( id ) => id . name ) . join ( idDivider ) ] ;
1137+ return data [ this . makeIdKey ( ids ) ] ;
11141138 }
11151139 }
11161140
@@ -1206,15 +1230,16 @@ class RequestHandler extends APIHandlerBase {
12061230 return r . toString ( ) ;
12071231 }
12081232
1209- private makeIdFilter ( idFields : FieldInfo [ ] , resourceId : string ) {
1233+ private makePrismaIdFilter ( idFields : FieldInfo [ ] , resourceId : string ) {
12101234 if ( idFields . length === 1 ) {
12111235 return { [ idFields [ 0 ] . name ] : this . coerce ( idFields [ 0 ] . type , resourceId ) } ;
12121236 } else {
12131237 return {
1214- [ idFields . map ( ( idf ) => idf . name ) . join ( idDivider ) ] : idFields . reduce (
1238+ // TODO: support `@@id` with custom name
1239+ [ idFields . map ( ( idf ) => idf . name ) . join ( prismaIdDivider ) ] : idFields . reduce (
12151240 ( acc , curr , idx ) => ( {
12161241 ...acc ,
1217- [ curr . name ] : this . coerce ( curr . type , resourceId . split ( idDivider ) [ idx ] ) ,
1242+ [ curr . name ] : this . coerce ( curr . type , resourceId . split ( this . idDivider ) [ idx ] ) ,
12181243 } ) ,
12191244 { }
12201245 ) ,
@@ -1230,11 +1255,16 @@ class RequestHandler extends APIHandlerBase {
12301255 }
12311256
12321257 private makeIdKey ( idFields : FieldInfo [ ] ) {
1233- return idFields . map ( ( idf ) => idf . name ) . join ( idDivider ) ;
1258+ return idFields . map ( ( idf ) => idf . name ) . join ( this . idDivider ) ;
1259+ }
1260+
1261+ private makePrismaIdKey ( idFields : FieldInfo [ ] ) {
1262+ // TODO: support `@@id` with custom name
1263+ return idFields . map ( ( idf ) => idf . name ) . join ( prismaIdDivider ) ;
12341264 }
12351265
12361266 private makeCompoundId ( idFields : FieldInfo [ ] , item : any ) {
1237- return idFields . map ( ( idf ) => item [ idf . name ] ) . join ( idDivider ) ;
1267+ return idFields . map ( ( idf ) => item [ idf . name ] ) . join ( this . idDivider ) ;
12381268 }
12391269
12401270 private includeRelationshipIds ( model : string , args : any , mode : 'select' | 'include' ) {
@@ -1557,11 +1587,11 @@ class RequestHandler extends APIHandlerBase {
15571587 const values = value . split ( ',' ) . filter ( ( i ) => i ) ;
15581588 const filterValue =
15591589 values . length > 1
1560- ? { OR : values . map ( ( v ) => this . makeIdFilter ( info . idFields , v ) ) }
1561- : this . makeIdFilter ( info . idFields , value ) ;
1590+ ? { OR : values . map ( ( v ) => this . makePrismaIdFilter ( info . idFields , v ) ) }
1591+ : this . makePrismaIdFilter ( info . idFields , value ) ;
15621592 return { some : filterValue } ;
15631593 } else {
1564- return { is : this . makeIdFilter ( info . idFields , value ) } ;
1594+ return { is : this . makePrismaIdFilter ( info . idFields , value ) } ;
15651595 }
15661596 } else {
15671597 const coerced = this . coerce ( fieldInfo . type , value ) ;
0 commit comments