@@ -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