@sap/cds 9.8.3 → 9.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/libx/odata/middleware/batch.js +28 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
- The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
5
5
|
- This project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## Version 9.8.4 - 2026-03-26
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `res.statusCode` of batch sub-requests did not consider potential modifications during `srv.on('error')`
|
|
12
|
+
- Restore login challenge for late `401` with mocked authentication in `$batch`
|
|
13
|
+
- Batched request fails when depended upon atomicity group fails
|
|
14
|
+
|
|
7
15
|
## Version 9.8.3 - 2026-03-12
|
|
8
16
|
|
|
9
17
|
### Fixed
|
|
@@ -149,7 +149,12 @@ const _createSubrequest = (request, _req, _res) => {
|
|
|
149
149
|
req.headers['content-type'] ??= _req.headers['content-type']
|
|
150
150
|
if (request.content_id) req.headers['content-id'] = request.content_id
|
|
151
151
|
req.body = request.body
|
|
152
|
-
if (_req._login)
|
|
152
|
+
if (_req._login) {
|
|
153
|
+
req._login = () => {
|
|
154
|
+
_req._login() //> sends 401 to client
|
|
155
|
+
req.res.sendStatus(401) //> fails the subrequest
|
|
156
|
+
}
|
|
157
|
+
}
|
|
153
158
|
// REVISIT: mark as subrequest (only needed for logging)
|
|
154
159
|
req._subrequest = true
|
|
155
160
|
|
|
@@ -392,6 +397,7 @@ const _replaceResponsesWithCommitErrors = (err, responses, ids) => {
|
|
|
392
397
|
const _serializeErrors = responses => {
|
|
393
398
|
for (const response of responses) {
|
|
394
399
|
if (response.status === 'fail' && typeof response.body === 'object') {
|
|
400
|
+
if (response.body.error?.status) response.statusCode = response.body.error.status
|
|
395
401
|
if (response.body.error && !response.body.error.toJSON) response.body.error = _getODataError(response.body.error)
|
|
396
402
|
response.body = JSON.stringify(response.body)
|
|
397
403
|
}
|
|
@@ -435,9 +441,18 @@ const _processBatch = async (srv, router, req, res, next, body, ct, boundary) =>
|
|
|
435
441
|
if (only_gets && cds.env.odata.group_parallel_gets && _only_individual_requests())
|
|
436
442
|
atomicityGroups = [atomicityGroups.reduce((acc, cur) => (acc.push(...cur), acc), [])]
|
|
437
443
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
444
|
+
// IMPORTANT: Avoid sending headers and responses too eagerly, as we might still have to send a 401
|
|
445
|
+
let sendPostlude = () => {} //> only if prelude was sent
|
|
446
|
+
let sendPreludeOnce = () => {
|
|
447
|
+
res.setHeader('Content-Type', isJson ? CT.JSON : CT.MULTIPART + ';boundary=' + boundary)
|
|
448
|
+
res.status(200)
|
|
449
|
+
res.write(isJson ? '{"responses":[' : '')
|
|
450
|
+
sendPreludeOnce = () => {} //> only once
|
|
451
|
+
sendPostlude = () => {
|
|
452
|
+
res.write(isJson ? ']}' : `--${boundary}--${CRLF}`)
|
|
453
|
+
res.end()
|
|
454
|
+
}
|
|
455
|
+
}
|
|
441
456
|
|
|
442
457
|
const queue = []
|
|
443
458
|
let _continue = true
|
|
@@ -501,8 +516,8 @@ const _processBatch = async (srv, router, req, res, next, body, ct, boundary) =>
|
|
|
501
516
|
.catch(err => {
|
|
502
517
|
responses._has_failure = true
|
|
503
518
|
|
|
504
|
-
// abort batch on first failure with odata.continue-on-error: false
|
|
505
|
-
if (!continue_on_error) {
|
|
519
|
+
// abort batch on first failure with odata.continue-on-error: false or if it was a 401
|
|
520
|
+
if (!continue_on_error || err.code == 401 || err.status == 401) {
|
|
506
521
|
_continue = false
|
|
507
522
|
while (queue.length) queue.shift()()
|
|
508
523
|
}
|
|
@@ -511,6 +526,8 @@ const _processBatch = async (srv, router, req, res, next, body, ct, boundary) =>
|
|
|
511
526
|
// here, the commit was rejected even though all requests were successful (e.g., by custom handler or db consistency check)
|
|
512
527
|
_replaceResponsesWithCommitErrors(err, responses, ids)
|
|
513
528
|
}
|
|
529
|
+
|
|
530
|
+
throw err
|
|
514
531
|
})
|
|
515
532
|
.finally(async () => {
|
|
516
533
|
// trigger next in queue
|
|
@@ -524,6 +541,10 @@ const _processBatch = async (srv, router, req, res, next, body, ct, boundary) =>
|
|
|
524
541
|
for (let i = 0; i < agIndex; i++) prevs.push(promises[i])
|
|
525
542
|
await Promise.allSettled(prevs)
|
|
526
543
|
|
|
544
|
+
// don't write to res if already closed (e.g., due to 401 and login)
|
|
545
|
+
if (res.closed) return
|
|
546
|
+
|
|
547
|
+
sendPreludeOnce()
|
|
527
548
|
if (isJson) _writeResponseJson(responses, res)
|
|
528
549
|
else _writeResponseMultipart(responses, res, boundary)
|
|
529
550
|
})
|
|
@@ -543,8 +564,7 @@ const _processBatch = async (srv, router, req, res, next, body, ct, boundary) =>
|
|
|
543
564
|
|
|
544
565
|
await Promise.allSettled(promises)
|
|
545
566
|
|
|
546
|
-
|
|
547
|
-
res.end()
|
|
567
|
+
sendPostlude()
|
|
548
568
|
} catch (e) {
|
|
549
569
|
next(e)
|
|
550
570
|
}
|