@sap/cds 9.8.0 → 9.8.1
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/eslint.config.mjs +3 -0
- package/lib/compile/cds-compile.js +1 -0
- package/lib/compile/for/direct_crud.js +23 -0
- package/lib/compile/for/odata.js +1 -18
- package/lib/compile/to/edm.js +1 -0
- package/libx/_runtime/messaging/service.js +3 -1
- package/libx/odata/middleware/batch.js +19 -12
- 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.1 - 2026-03-09
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- OData batch parallel processing: Preserve request sequence for OData v2
|
|
12
|
+
- In inbound messaging, only load the extended model if there is a tenant
|
|
13
|
+
- Ensure `cds.fiori.direct_crud` is considered during `cds build`
|
|
14
|
+
|
|
7
15
|
## Version 9.8.0 - 2026-03-06
|
|
8
16
|
|
|
9
17
|
### Added
|
package/eslint.config.mjs
CHANGED
|
@@ -6,6 +6,7 @@ const compile = module.exports = Object.assign (cds_compile, {
|
|
|
6
6
|
for: new class {
|
|
7
7
|
get java(){ return super.java = require('./for/java') }
|
|
8
8
|
get nodejs() { return super.nodejs = require('./for/nodejs') }
|
|
9
|
+
get direct_crud() { return super.direct_crud = require('./for/direct_crud') }
|
|
9
10
|
get lean_drafts() { return super.lean_drafts = require('./for/lean_drafts') }
|
|
10
11
|
get flows() { return super.flows = require('./for/flows') }
|
|
11
12
|
get assert() { return super.assert = require('./for/assert') }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const $compiled_for_direct_crud = Symbol('compiled_for_direct_crud')
|
|
2
|
+
const DRAFT_NEW = 'draftNew'
|
|
3
|
+
|
|
4
|
+
module.exports = function cds_compile_for_direct_crud(csn) {
|
|
5
|
+
if (csn[$compiled_for_direct_crud]) return csn
|
|
6
|
+
csn[$compiled_for_direct_crud] = true
|
|
7
|
+
|
|
8
|
+
for (const each in csn.definitions) {
|
|
9
|
+
const def = csn.definitions[each]
|
|
10
|
+
if (!def['@Common.DraftRoot.NewAction'] && def['@odata.draft.enabled']) {
|
|
11
|
+
const srvName = Object.keys(csn.definitions)
|
|
12
|
+
.filter(k => csn.definitions[k].kind === 'service')
|
|
13
|
+
.find(k => each.startsWith(`${k}.`))
|
|
14
|
+
def['@Common.DraftRoot.NewAction'] = `${srvName}.${DRAFT_NEW}`
|
|
15
|
+
const params = { in: { items: { type: '$self' } } }
|
|
16
|
+
// for UI pop-up asking for values for non-UUID keys
|
|
17
|
+
Object.keys(def.elements)
|
|
18
|
+
.filter(k => k !== 'IsActiveEntity' && def.elements[k].key && def.elements[k].type !== 'cds.UUID')
|
|
19
|
+
.forEach(k => (params[k] = { type: def.elements[k].type }))
|
|
20
|
+
def.actions[DRAFT_NEW] = { kind: 'action', params, returns: { type: each } }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
package/lib/compile/for/odata.js
CHANGED
|
@@ -9,24 +9,7 @@ module.exports = function cds_compile_for_odata (csn,_o) {
|
|
|
9
9
|
let dsn = compile.for.odata (csn,o)
|
|
10
10
|
if (o.sql_mapping) dsn['@sql_mapping'] = o.sql_mapping //> compat4 old Java stack
|
|
11
11
|
|
|
12
|
-
if (cds.env.fiori.direct_crud)
|
|
13
|
-
const DRAFT_NEW = 'draftNew'
|
|
14
|
-
for (const each in dsn.definitions) {
|
|
15
|
-
const def = dsn.definitions[each]
|
|
16
|
-
if (!def['@Common.DraftRoot.NewAction'] && def['@Common.DraftRoot.ActivationAction']) {
|
|
17
|
-
const srvName = Object.keys(dsn.definitions)
|
|
18
|
-
.filter(k => dsn.definitions[k].kind === 'service')
|
|
19
|
-
.find(k => each.startsWith(`${k}.`))
|
|
20
|
-
def['@Common.DraftRoot.NewAction'] = `${srvName}.${DRAFT_NEW}`
|
|
21
|
-
const params = { in: { items: { type: '$self' } } }
|
|
22
|
-
// for UI pop-up asking for values for non-UUID keys
|
|
23
|
-
Object.keys(def.elements)
|
|
24
|
-
.filter(k => k !== 'IsActiveEntity' && def.elements[k].key && def.elements[k].type !== 'cds.UUID')
|
|
25
|
-
.forEach(k => (params[k] = { type: def.elements[k].type }))
|
|
26
|
-
def.actions[DRAFT_NEW] = { kind: 'action', params, returns: { type: each } }
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
12
|
+
if (cds.env.fiori.direct_crud) cds.compile.for.direct_crud(dsn)
|
|
30
13
|
|
|
31
14
|
Object.defineProperty (csn, '_4odata', {value:dsn})
|
|
32
15
|
Object.defineProperty (dsn, '_4odata', {value:dsn})
|
package/lib/compile/to/edm.js
CHANGED
|
@@ -42,6 +42,7 @@ function cds_compile_to_edmx (csn,_o) {
|
|
|
42
42
|
const next = () => {
|
|
43
43
|
if (!result) {
|
|
44
44
|
if (cds.env.features.annotate_for_flows) enhanceCSNwithFlowAnnotations4FE(csn)
|
|
45
|
+
if (cds.env.fiori.direct_crud) cds.compile.for.direct_crud(csn)
|
|
45
46
|
result = o.service === 'all' ? _many('.xml', cdsc.to.edmx.all(csn, o)) : cdsc.to.edmx(csn, o)
|
|
46
47
|
}
|
|
47
48
|
return result
|
|
@@ -77,9 +77,11 @@ module.exports = class MessagingService extends cds.Service {
|
|
|
77
77
|
if (!cds.context) cds.context = {}
|
|
78
78
|
if (ctx.tenant) cds.context.tenant = ctx.tenant
|
|
79
79
|
if (!ctx.user) ctx.user = cds.User.privileged
|
|
80
|
+
// REVISIT: is cds.context.model really needed?
|
|
80
81
|
// this.tx expects cds.context.model
|
|
81
|
-
if (cds.model && (cds.env.requires.extensibility || cds.env.requires.toggles))
|
|
82
|
+
if (ctx.tenant && cds.model && (cds.env.requires.extensibility || cds.env.requires.toggles))
|
|
82
83
|
cds.context.model = await ExtendedModels.model4(ctx.tenant, ctx.features || {})
|
|
84
|
+
else if (cds.model) cds.context.model = cds.model
|
|
83
85
|
const me = this.options.inboxed || this.options.inbox ? cds.queued(this) : this
|
|
84
86
|
return await me.tx(ctx, tx => tx.emit(msg))
|
|
85
87
|
}
|
|
@@ -441,7 +441,7 @@ const _processBatch = async (srv, router, req, res, next, body, ct, boundary) =>
|
|
|
441
441
|
|
|
442
442
|
const queue = []
|
|
443
443
|
let _continue = true
|
|
444
|
-
const _queued_exec = (atomicityGroup, responses = []) => {
|
|
444
|
+
const _queued_exec = (atomicityGroup, agIndex, responses = []) => {
|
|
445
445
|
const groupId = atomicityGroup[0].atomicityGroup
|
|
446
446
|
|
|
447
447
|
return new Promise(resolve => queue.push(resolve)).then(() => {
|
|
@@ -487,16 +487,23 @@ const _processBatch = async (srv, router, req, res, next, body, ct, boundary) =>
|
|
|
487
487
|
|
|
488
488
|
// ensure all subrequests run in this tx
|
|
489
489
|
// (if first subrequest fails without really opening the tx, the rest are executed in a "dangling tx")
|
|
490
|
-
return Promise.allSettled(subrequests)
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
490
|
+
return Promise.allSettled(subrequests)
|
|
491
|
+
.then(ress => {
|
|
492
|
+
// wait for all previous atomicity groups (ignoring errors via allSettled) for odata v2
|
|
493
|
+
const prevs = []
|
|
494
|
+
for (let i = 0; i < agIndex; i++) prevs.push(promises[i])
|
|
495
|
+
return Promise.allSettled(prevs).then(() => ress)
|
|
496
|
+
})
|
|
497
|
+
.then(ress => {
|
|
498
|
+
const failed = ress.filter(({ status }) => status === 'rejected')
|
|
499
|
+
if (!failed.length) return
|
|
500
|
+
// throw first error and call srv.on('error') for the others
|
|
501
|
+
const first = failed.shift()
|
|
502
|
+
if (srv.handlers._error?.length)
|
|
503
|
+
for (const other of failed)
|
|
504
|
+
for (const each of srv.handlers._error) each.handler.call(srv, other.reason, cds.context)
|
|
505
|
+
throw first.reason
|
|
506
|
+
})
|
|
500
507
|
})
|
|
501
508
|
.catch(err => {
|
|
502
509
|
responses._has_failure = true
|
|
@@ -528,7 +535,7 @@ const _processBatch = async (srv, router, req, res, next, body, ct, boundary) =>
|
|
|
528
535
|
|
|
529
536
|
// queue execution of atomicity groups
|
|
530
537
|
const promises = []
|
|
531
|
-
for (
|
|
538
|
+
for (let i = 0; i < atomicityGroups.length; i++) promises.push(_queued_exec(atomicityGroups[i], i))
|
|
532
539
|
|
|
533
540
|
// trigger first max_parallel in queue
|
|
534
541
|
for (let i = 0; i < max_parallel; i++) if (queue.length) queue.shift()()
|