@@ -133,7 +133,8 @@ export interface ApiKey extends Actor {
133133}
134134
135135export interface Response {
136- statusCode : number ;
136+ id ?: string | undefined ;
137+ status : number ;
137138 headers ?:
138139 | {
139140 [ headerName : string ] : any ;
@@ -1022,15 +1023,15 @@ export const runURI = async (
10221023 throw response ;
10231024 }
10241025
1025- const { body : responseBody , statusCode , headers } = response as Response ;
1026+ const { body : responseBody , status , headers } = response as Response ;
10261027
1027- if ( statusCode != null && statusCode >= 400 ) {
1028+ if ( status != null && status >= 400 ) {
10281029 const ErrorClass =
1029- statusCodeToError [ statusCode as keyof typeof statusCodeToError ] ;
1030+ statusCodeToError [ status as keyof typeof statusCodeToError ] ;
10301031 if ( ErrorClass != null ) {
10311032 throw new ErrorClass ( undefined , responseBody , headers ) ;
10321033 }
1033- throw new HttpError ( statusCode , undefined , responseBody , headers ) ;
1034+ throw new HttpError ( status , undefined , responseBody , headers ) ;
10341035 }
10351036
10361037 return responseBody as AnyObject | undefined ;
@@ -1069,7 +1070,7 @@ export const getAffectedIds = async (
10691070 args : HookArgs & {
10701071 tx : Db . Tx ;
10711072 } ,
1072- ) : Promise < number [ ] > => {
1073+ ) : Promise < string [ ] > => {
10731074 const { request } = args ;
10741075 if ( request . affectedIds ) {
10751076 return request . affectedIds ;
@@ -1094,7 +1095,7 @@ const $getAffectedIds = async ({
10941095 tx,
10951096} : HookArgs & {
10961097 tx : Db . Tx ;
1097- } ) : Promise < number [ ] > => {
1098+ } ) : Promise < string [ ] > => {
10981099 if ( ! [ 'PATCH' , 'DELETE' ] . includes ( request . method ) ) {
10991100 // We can only find the affected ids in advance for requests that modify existing records, if they
11001101 // can insert new records (POST/PUT) then we're unable to find the ids until the request has actually run
@@ -1108,6 +1109,7 @@ const $getAffectedIds = async ({
11081109 const parsedRequest : uriParser . ParsedODataRequest &
11091110 Partial < Pick < uriParser . ODataRequest , 'engine' | 'translateVersions' > > =
11101111 await uriParser . parseOData ( {
1112+ id : request . id ,
11111113 method : request . method ,
11121114 url : `/${ request . vocabulary } ${ request . url } ` ,
11131115 } ) ;
@@ -1158,6 +1160,44 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
11581160 api [ vocabulary ] . logger . log ( 'Parsing' , req . method , req . url ) ;
11591161 }
11601162
1163+ if ( req . url === `/${ vocabulary } /$batch` ) {
1164+ const { requests } = req . body as { requests : uriParser . UnparsedRequest [ ] } ;
1165+ const ids = new Set < string > (
1166+ requests
1167+ . map ( ( request ) => request . id )
1168+ . filter ( ( id ) => typeof id === 'string' ) as string [ ] ,
1169+ ) ;
1170+ if ( ids . size !== requests . length ) {
1171+ throw new BadRequestError (
1172+ 'All requests in a batch request must have unique string ids' ,
1173+ ) ;
1174+ }
1175+
1176+ const methods = new Set < string | undefined > (
1177+ requests . map ( ( request ) => request . method ) ,
1178+ ) ;
1179+ if ( methods . has ( undefined ) ) {
1180+ throw new BadRequestError (
1181+ 'Requests of a batch request must have a "method"' ,
1182+ ) ;
1183+ }
1184+
1185+ const urls = new Set < string | undefined > (
1186+ requests . map ( ( request ) => request . url ) ,
1187+ ) ;
1188+ if ( urls . has ( undefined ) ) {
1189+ throw new BadRequestError (
1190+ 'Requests of a batch request must have a "url"' ,
1191+ ) ;
1192+ }
1193+ if ( urls . has ( '/university/$batch' ) ) {
1194+ throw new BadRequestError ( 'Batch requests cannot contain batch requests' ) ;
1195+ }
1196+
1197+ // TODO: make sure req.body.requests is valid structure/typing for req.batch
1198+ req . batch = requests ;
1199+ }
1200+
11611201 // Get the hooks for the current method/vocabulary as we know it,
11621202 // in order to run PREPARSE hooks, before parsing gets us more info
11631203 const { versions } = models [ vocabulary ] ;
@@ -1205,17 +1245,22 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
12051245 await runHooks ( 'PREPARSE' , reqHooks , { req, tx : req . tx } ) ;
12061246 let requests : uriParser . UnparsedRequest [ ] ;
12071247 // Check if it is a single request or a batch
1248+ // console.error('+++++++++++++++++++', req.url, req.batch);
12081249 if ( req . batch != null && req . batch . length > 0 ) {
12091250 requests = req . batch ;
12101251 } else {
12111252 const { method, url, body } = req ;
1212- requests = [ { method, url, data : body } ] ;
1253+ requests = [ { method, url, body } ] ;
12131254 }
1255+ // console.error('+++++++++++++++++++', req.url, requests);
12141256
12151257 const prepareRequest = async (
12161258 parsedRequest : uriParser . ParsedODataRequest &
12171259 Partial < Pick < uriParser . ODataRequest , 'engine' | 'translateVersions' > > ,
12181260 ) : Promise < uriParser . ODataRequest > => {
1261+ // if (process.env.something) {
1262+ // console.error('parsedRequest', parsedRequest);
1263+ // }
12191264 parsedRequest . engine = db . engine ;
12201265 parsedRequest . translateVersions = [ ...versions ] ;
12211266 // Mark that the engine/translateVersions is required now that we've set it
@@ -1226,6 +1271,7 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
12261271 // Add/check the relevant permissions
12271272 try {
12281273 $request . hooks = [ ] ;
1274+ // console.error('a');
12291275 for ( const version of versions ) {
12301276 // We get the hooks list between each `runHooks` so that any resource renames will be used
12311277 // when getting hooks for later versions
@@ -1247,6 +1293,7 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
12471293 request : $request ,
12481294 tx : req . tx ,
12491295 } ) ;
1296+ // console.error('b', version);
12501297 const { resourceRenames } = models [ version ] ;
12511298 if ( resourceRenames ) {
12521299 const resourceName = resolveSynonym ( $request ) ;
@@ -1257,7 +1304,10 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
12571304 }
12581305 }
12591306 }
1307+ // console.error('$request', $request.id);
12601308 const translatedRequest = await uriParser . translateUri ( $request ) ;
1309+ // console.error('translatedRequest', translatedRequest.id);
1310+ // console.error('c');
12611311 return await compileRequest ( translatedRequest ) ;
12621312 } catch ( err : any ) {
12631313 rollbackRequestHooks ( reqHooks ) ;
@@ -1266,17 +1316,18 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
12661316 }
12671317 } ;
12681318
1319+ // console.error('1');
12691320 // Parse the OData requests
12701321 const results = await mappingFn ( requests , async ( requestPart ) => {
12711322 const parsedRequest = await uriParser . parseOData ( requestPart ) ;
1323+ // console.error('2');
12721324
12731325 let request : uriParser . ODataRequest | uriParser . ODataRequest [ ] ;
12741326 if ( Array . isArray ( parsedRequest ) ) {
12751327 request = await controlFlow . mapSeries ( parsedRequest , prepareRequest ) ;
12761328 } else {
12771329 request = await prepareRequest ( parsedRequest ) ;
12781330 }
1279- // Run the request in its own transaction
12801331 return await runTransaction < Response | Response [ ] > (
12811332 req ,
12821333 request ,
@@ -1293,7 +1344,7 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
12931344 }
12941345 } ) ;
12951346 if ( Array . isArray ( request ) ) {
1296- const changeSetResults = new Map < number , Response > ( ) ;
1347+ const changeSetResults = new Map < string , Response > ( ) ;
12971348 const changeSetRunner = runChangeSet ( req , tx ) ;
12981349 for ( const r of request ) {
12991350 await changeSetRunner ( changeSetResults , r ) ;
@@ -1314,7 +1365,7 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
13141365 if (
13151366 ! Array . isArray ( result ) &&
13161367 result . body == null &&
1317- result . statusCode == null
1368+ result . status == null
13181369 ) {
13191370 console . error ( 'No status or body set' , req . url , responses ) ;
13201371 return new InternalRequestError ( ) ;
@@ -1352,10 +1403,10 @@ export const handleODataRequest: Express.Handler = async (req, res, next) => {
13521403
13531404 // Otherwise its a multipart request and we reply with the appropriate multipart response
13541405 } else {
1355- ( res . status ( 200 ) as any ) . sendMulti (
1406+ res . status ( 200 ) . json (
13561407 responses . map ( ( response ) => {
13571408 if ( response instanceof HttpError ) {
1358- response = httpErrorToResponse ( response ) ;
1409+ return httpErrorToResponse ( response ) ;
13591410 } else {
13601411 return response ;
13611412 }
@@ -1394,9 +1445,9 @@ export const handleHttpErrors = (
13941445 return false ;
13951446} ;
13961447const handleResponse = ( res : Express . Response , response : Response ) : void => {
1397- const { body, headers, statusCode } = response as Response ;
1448+ const { body, headers, status } = response as Response ;
13981449 res . set ( headers ) ;
1399- res . status ( statusCode ) ;
1450+ res . status ( status ) ;
14001451 if ( ! body ) {
14011452 res . end ( ) ;
14021453 } else {
@@ -1406,9 +1457,9 @@ const handleResponse = (res: Express.Response, response: Response): void => {
14061457
14071458const httpErrorToResponse = (
14081459 err : HttpError ,
1409- ) : RequiredField < Response , 'statusCode ' > => {
1460+ ) : RequiredField < Response , 'status ' > => {
14101461 return {
1411- statusCode : err . status ,
1462+ status : err . status ,
14121463 body : err . getResponseBody ( ) ,
14131464 headers : err . headers ,
14141465 } ;
@@ -1514,7 +1565,7 @@ const runRequest = async (
15141565const runChangeSet =
15151566 ( req : Express . Request , tx : Db . Tx ) =>
15161567 async (
1517- changeSetResults : Map < number , Response > ,
1568+ changeSetResults : Map < string , Response > ,
15181569 request : uriParser . ODataRequest ,
15191570 ) : Promise < void > => {
15201571 request = updateBinds ( changeSetResults , request ) ;
@@ -1532,7 +1583,7 @@ const runChangeSet =
15321583// deferred untill the request they reference is run and returns an insert ID.
15331584// This function compiles the sql query of a request which has been deferred
15341585const updateBinds = (
1535- changeSetResults : Map < number , Response > ,
1586+ changeSetResults : Map < string , Response > ,
15361587 request : uriParser . ODataRequest ,
15371588) => {
15381589 if ( request . _defer ) {
@@ -1700,7 +1751,8 @@ const respondGet = async (
17001751 ) ;
17011752
17021753 const response = {
1703- statusCode : 200 ,
1754+ id : request . id ,
1755+ status : 200 ,
17041756 body : { d } ,
17051757 headers : { 'content-type' : 'application/json' } ,
17061758 } ;
@@ -1715,14 +1767,15 @@ const respondGet = async (
17151767 } else {
17161768 if ( request . resourceName === '$metadata' ) {
17171769 return {
1718- statusCode : 200 ,
1770+ id : request . id ,
1771+ status : 200 ,
17191772 body : models [ vocab ] . odataMetadata ,
17201773 headers : { 'content-type' : 'xml' } ,
17211774 } ;
17221775 } else {
17231776 // TODO: request.resourceName can be '$serviceroot' or a resource and we should return an odata xml document based on that
17241777 return {
1725- statusCode : 404 ,
1778+ status : 404 ,
17261779 } ;
17271780 }
17281781 }
@@ -1778,7 +1831,7 @@ const respondPost = async (
17781831 }
17791832
17801833 const response = {
1781- statusCode : 201 ,
1834+ status : 201 ,
17821835 body : result . d [ 0 ] ,
17831836 headers : {
17841837 'content-type' : 'application/json' ,
@@ -1826,7 +1879,7 @@ const respondPut = async (
18261879 tx : Db . Tx ,
18271880) : Promise < Response > => {
18281881 const response = {
1829- statusCode : 200 ,
1882+ status : 200 ,
18301883 } ;
18311884 await runHooks ( 'PRERESPOND' , request . hooks , {
18321885 req,
0 commit comments