@sap/cds 9.7.1 → 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 +48 -0
- package/_i18n/i18n_en_US_saptrc.properties +1 -56
- package/_i18n/messages_en_US_saptrc.properties +1 -92
- package/eslint.config.mjs +4 -1
- package/lib/compile/cds-compile.js +1 -0
- package/lib/compile/for/direct_crud.js +23 -0
- package/lib/compile/for/lean_drafts.js +12 -0
- package/lib/compile/for/odata.js +1 -18
- package/lib/compile/to/edm.js +1 -0
- package/lib/compile/to/json.js +4 -2
- package/lib/env/defaults.js +1 -0
- package/lib/env/serviceBindings.js +15 -5
- package/lib/index.js +1 -1
- package/lib/log/cds-error.js +33 -20
- package/lib/req/spawn.js +2 -2
- package/lib/srv/bindings.js +6 -13
- package/lib/srv/cds.Service.js +8 -36
- package/lib/srv/protocols/hcql.js +19 -2
- package/lib/utils/cds-utils.js +25 -16
- package/lib/utils/tar-win.js +106 -0
- package/lib/utils/tar.js +23 -158
- package/libx/_runtime/common/generic/crud.js +8 -7
- package/libx/_runtime/common/generic/sorting.js +7 -3
- package/libx/_runtime/common/utils/resolveView.js +47 -40
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -0
- package/libx/_runtime/fiori/lean-draft.js +11 -2
- package/libx/_runtime/messaging/kafka.js +6 -5
- package/libx/_runtime/messaging/service.js +3 -1
- package/libx/_runtime/remote/Service.js +3 -0
- package/libx/_runtime/remote/utils/client.js +2 -4
- package/libx/_runtime/remote/utils/query.js +4 -4
- package/libx/odata/middleware/batch.js +323 -339
- package/libx/odata/middleware/create.js +0 -5
- package/libx/odata/middleware/delete.js +0 -5
- package/libx/odata/middleware/operation.js +10 -8
- package/libx/odata/middleware/read.js +0 -10
- package/libx/odata/middleware/stream.js +1 -0
- package/libx/odata/middleware/update.js +0 -6
- package/libx/odata/parse/afterburner.js +47 -22
- package/libx/odata/parse/cqn2odata.js +6 -1
- package/libx/odata/parse/grammar.peggy +14 -2
- package/libx/odata/parse/multipartToJson.js +2 -1
- package/libx/odata/parse/parser.js +1 -1
- package/package.json +2 -2
|
@@ -92,11 +92,6 @@ module.exports = (adapter, isUpsert) => {
|
|
|
92
92
|
.catch(err => {
|
|
93
93
|
handleSapMessages(cdsReq, req, res)
|
|
94
94
|
|
|
95
|
-
// REVISIT: invoke service.on('error') for failed batch subrequests
|
|
96
|
-
if (cdsReq.http.req.path.startsWith('/$batch') && service.handlers._error.length) {
|
|
97
|
-
for (const each of service.handlers._error) each.handler.call(service, err, cdsReq)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
95
|
next(err)
|
|
101
96
|
})
|
|
102
97
|
}
|
|
@@ -63,11 +63,6 @@ module.exports = adapter => {
|
|
|
63
63
|
.catch(err => {
|
|
64
64
|
handleSapMessages(cdsReq, req, res)
|
|
65
65
|
|
|
66
|
-
// REVISIT: invoke service.on('error') for failed batch subrequests
|
|
67
|
-
if (cdsReq.http.req.path.startsWith('/$batch') && service.handlers._error.length) {
|
|
68
|
-
for (const each of service.handlers._error) each.handler.call(service, err, cdsReq)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
66
|
next(err)
|
|
72
67
|
})
|
|
73
68
|
}
|
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
getReadable,
|
|
16
16
|
validateMimetypeIsAcceptedOrThrow
|
|
17
17
|
} = require('../../common/utils/streaming')
|
|
18
|
+
const odataBind = require('../utils/odataBind')
|
|
18
19
|
|
|
19
20
|
const _findEdmNameFor = (definition, namespace, fullyQualified = false) => {
|
|
20
21
|
let name
|
|
@@ -62,6 +63,9 @@ module.exports = adapter => {
|
|
|
62
63
|
|
|
63
64
|
const model = cds.context.model ?? service.model
|
|
64
65
|
|
|
66
|
+
// payload & params
|
|
67
|
+
const data = args || req.body
|
|
68
|
+
|
|
65
69
|
// unbound vs. bound
|
|
66
70
|
let entity, params
|
|
67
71
|
if (model.definitions[operation]) {
|
|
@@ -77,6 +81,12 @@ module.exports = adapter => {
|
|
|
77
81
|
entity = cur
|
|
78
82
|
const keysAndParams = getKeysAndParamsFromPath(req._query.SELECT.from, { model })
|
|
79
83
|
params = keysAndParams.params
|
|
84
|
+
|
|
85
|
+
const onCollection = !!(
|
|
86
|
+
operation['@cds.odata.bindingparameter.collection'] ||
|
|
87
|
+
(operation.params && [...operation.params].some(p => p?.items?.type === '$self'))
|
|
88
|
+
)
|
|
89
|
+
if (operation.kind === 'action' && onCollection) odataBind(data, entity)
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
// validate method
|
|
@@ -87,9 +97,6 @@ module.exports = adapter => {
|
|
|
87
97
|
return next({ code: 405 })
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
// payload & params
|
|
91
|
-
const data = args || req.body
|
|
92
|
-
|
|
93
100
|
// event
|
|
94
101
|
// REVISIT: when is operation.name actually prefixed with the service name?
|
|
95
102
|
const event = operation.name.replace(`${service.definition.name}.`, '')
|
|
@@ -175,11 +182,6 @@ module.exports = adapter => {
|
|
|
175
182
|
.catch(err => {
|
|
176
183
|
handleSapMessages(cdsReq, req, res)
|
|
177
184
|
|
|
178
|
-
// REVISIT: invoke service.on('error') for failed batch subrequests
|
|
179
|
-
if (cdsReq.http.req.path.startsWith('/$batch') && service.handlers._error.length) {
|
|
180
|
-
for (const each of service.handlers._error) each.handler.call(service, err, cdsReq)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
185
|
next(err)
|
|
184
186
|
})
|
|
185
187
|
}
|
|
@@ -165,11 +165,6 @@ const _handleArrayOfQueriesFactory = adapter => {
|
|
|
165
165
|
.catch(err => {
|
|
166
166
|
handleSapMessages(cdsReq, req, res)
|
|
167
167
|
|
|
168
|
-
// REVISIT: invoke service.on('error') for failed batch subrequests
|
|
169
|
-
if (cdsReq.http.req.path.startsWith('/$batch') && service.handlers._error.length) {
|
|
170
|
-
for (const each of service.handlers._error) each.handler.call(service, err, cdsReq)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
168
|
next(err)
|
|
174
169
|
})
|
|
175
170
|
}
|
|
@@ -274,11 +269,6 @@ module.exports = adapter => {
|
|
|
274
269
|
.catch(err => {
|
|
275
270
|
handleSapMessages(cdsReq, req, res)
|
|
276
271
|
|
|
277
|
-
// REVISIT: invoke service.on('error') for failed batch subrequests
|
|
278
|
-
if (cdsReq.http.req.path.startsWith('/$batch') && service.handlers._error.length) {
|
|
279
|
-
for (const each of service.handlers._error) each.handler.call(service, err, cdsReq)
|
|
280
|
-
}
|
|
281
|
-
|
|
282
272
|
next(err)
|
|
283
273
|
})
|
|
284
274
|
}
|
|
@@ -159,12 +159,6 @@ module.exports = adapter => {
|
|
|
159
159
|
return next()
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
// REVISIT: invoke service.on('error') for failed batch subrequests
|
|
163
|
-
if (cdsReq.http.req.path.startsWith('/$batch') && service.handlers._error.length) {
|
|
164
|
-
for (const each of service.handlers._error) each.handler.call(service, err, cdsReq)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// continue with caught error
|
|
168
162
|
next(err)
|
|
169
163
|
})
|
|
170
164
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const cds = require('../../../lib')
|
|
2
|
+
const DEBUG = cds.debug('odata|rest')
|
|
2
3
|
|
|
3
4
|
const { keysOf, addRefToWhereIfNecessary } = require('../utils')
|
|
4
5
|
|
|
@@ -20,6 +21,9 @@ function _getDefinition(definition, name, namespace) {
|
|
|
20
21
|
definition.actions?.[name] ??
|
|
21
22
|
definition.actions?.[name.replace(namespace + '.', '')]
|
|
22
23
|
|
|
24
|
+
if (DEBUG && def && def['@cds.api.ignore']) {
|
|
25
|
+
DEBUG(`${name} is not served because it is annotated with @cds.api.ignore`)
|
|
26
|
+
}
|
|
23
27
|
if (def && !def['@cds.api.ignore']) return def
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -452,7 +456,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
452
456
|
target = current.returns.items ?? current.returns
|
|
453
457
|
}
|
|
454
458
|
} else if (current.isAssociation) {
|
|
455
|
-
if (current._target._service !==
|
|
459
|
+
if (current._target._service !== target._service) {
|
|
456
460
|
// not exposed target
|
|
457
461
|
_doesNotExistError(false, current.name, target.name.replace(namespace + '.', ''), current.kind)
|
|
458
462
|
}
|
|
@@ -569,27 +573,56 @@ function _addKeys(columns, target) {
|
|
|
569
573
|
}
|
|
570
574
|
|
|
571
575
|
/**
|
|
572
|
-
* Recursively, for each depth, remove all other select columns if a select star is present
|
|
573
|
-
*
|
|
576
|
+
* Recursively, for each depth, remove all other select columns if a select star is present (including duplicates) and
|
|
577
|
+
* remove duplicate expand stars. In case of an expand star, also remove all navigations from the select columns, as
|
|
578
|
+
* they would cause duplicate columns to be selected.
|
|
574
579
|
*
|
|
575
580
|
* @param {*} columns CQN `SELECT` columns array.
|
|
581
|
+
* @param {*} target CSN target definition of the current depth.
|
|
576
582
|
*/
|
|
577
|
-
function _removeUnneededColumnsIfHasAsterisk(columns) {
|
|
583
|
+
function _removeUnneededColumnsIfHasAsterisk(columns, target) {
|
|
584
|
+
if (!target) return
|
|
578
585
|
// We need to know if column contains a select * before we can remove other selected columns below
|
|
579
586
|
const hasSelectStar = columns.some(column => column === '*')
|
|
580
587
|
let hasExpandStar = false
|
|
588
|
+
let navigationProps
|
|
589
|
+
|
|
590
|
+
const toRemove = []
|
|
581
591
|
|
|
582
|
-
columns.
|
|
592
|
+
for (let i = 0; i < columns.length; i++) {
|
|
593
|
+
const column = columns[i]
|
|
583
594
|
// Remove other select columns if we have a select star
|
|
584
|
-
if (hasSelectStar && column.ref && !column.expand)
|
|
585
|
-
|
|
595
|
+
if (hasSelectStar && column.ref && !column.expand) toRemove.push(i)
|
|
596
|
+
|
|
586
597
|
if (!column.ref && column.expand?.[0] === '*') {
|
|
587
|
-
if (hasExpandStar)
|
|
588
|
-
|
|
598
|
+
if (hasExpandStar) {
|
|
599
|
+
toRemove.push(i) // Remove duplicate expand stars
|
|
600
|
+
} else {
|
|
601
|
+
hasExpandStar = true
|
|
602
|
+
navigationProps = Object.values(target.elements)
|
|
603
|
+
.filter(element => element.isAssociation || element.isComposition)
|
|
604
|
+
.map(element => element.name)
|
|
605
|
+
}
|
|
589
606
|
}
|
|
590
607
|
// Recursively remove unneeded columns in expand
|
|
591
|
-
if (column.expand
|
|
592
|
-
|
|
608
|
+
if (column.expand && column.ref)
|
|
609
|
+
_removeUnneededColumnsIfHasAsterisk(column.expand, target.elements?.[column.ref[0]]?._target)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (hasExpandStar && navigationProps?.length) {
|
|
613
|
+
for (let i = 0; i < columns.length; i++) {
|
|
614
|
+
const column = columns[i]
|
|
615
|
+
if (column.ref && !column.expand && column.ref.length === 1 && navigationProps.includes(column.ref[0])) {
|
|
616
|
+
toRemove.push(i)
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (!toRemove.length) return
|
|
622
|
+
|
|
623
|
+
// Descending order to avoid issues with splicing while iterating
|
|
624
|
+
const uniqueIndexes = [...new Set(toRemove)].sort((a, b) => b - a)
|
|
625
|
+
for (const index of uniqueIndexes) columns.splice(index, 1)
|
|
593
626
|
}
|
|
594
627
|
|
|
595
628
|
const _structProperty = (ref, target) => {
|
|
@@ -621,7 +654,7 @@ function _processColumns(cqn, target, protocol) {
|
|
|
621
654
|
else if (target.kind === 'action' && target.returns?.kind === 'entity') entity = target.returns
|
|
622
655
|
if (!entity) return
|
|
623
656
|
|
|
624
|
-
_removeUnneededColumnsIfHasAsterisk(columns)
|
|
657
|
+
_removeUnneededColumnsIfHasAsterisk(columns, target)
|
|
625
658
|
rewriteExpandAsterisk(columns, entity)
|
|
626
659
|
|
|
627
660
|
// For OData, add missing key fields to columns
|
|
@@ -767,12 +800,6 @@ const _doesNotExistError = (isExpand, refName, targetName, targetKind) => {
|
|
|
767
800
|
cds.error({ status: 400, message: msg })
|
|
768
801
|
}
|
|
769
802
|
|
|
770
|
-
const _get_service_of = element => {
|
|
771
|
-
if (element._service) return element._service
|
|
772
|
-
if (element.parent) return _get_service_of(element.parent)
|
|
773
|
-
return null
|
|
774
|
-
}
|
|
775
|
-
|
|
776
803
|
function _validateXpr(xpr, target, isOne, model, aliases = []) {
|
|
777
804
|
if (!xpr) return []
|
|
778
805
|
|
|
@@ -834,10 +861,8 @@ function _validateXpr(xpr, target, isOne, model, aliases = []) {
|
|
|
834
861
|
element = _structProperty(x.ref.slice(1), element)
|
|
835
862
|
}
|
|
836
863
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (!element._target || element._target._service !== target_service) {
|
|
840
|
-
const targetName = target_service ? target.name.replace(target_service.name + '.', '') : target.name
|
|
864
|
+
if (!element._target || element._target._service !== target._service) {
|
|
865
|
+
const targetName = target._service ? target.name.replace(target._service.name + '.', '') : target.name
|
|
841
866
|
_doesNotExistError(true, refName, targetName)
|
|
842
867
|
}
|
|
843
868
|
|
|
@@ -12,6 +12,11 @@ const OPERATORS = {
|
|
|
12
12
|
'>=': 'ge'
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
const KNOWN_OPERATORS = {
|
|
16
|
+
...Object.keys(OPERATORS).reduce((acc, cur) => ((acc[cur] = 1), acc), {}),
|
|
17
|
+
...{ eq: 1, ne: 1, lt: 1, gt: 1, le: 1, ge: 1 }
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
const LAMBDA_VARIABLE = 'd'
|
|
16
21
|
|
|
17
22
|
const needArrayProps = Object.fromEntries(
|
|
@@ -210,7 +215,7 @@ function _xpr(expr, target, kind, isLambda, navPrefix = []) {
|
|
|
210
215
|
res.push(OPERATORS[cur] || cur.toLowerCase())
|
|
211
216
|
}
|
|
212
217
|
} else {
|
|
213
|
-
const ref = expr[i - 1] in
|
|
218
|
+
const ref = expr[i - 1] in KNOWN_OPERATORS ? expr[i - 2] : expr[i + 1] in KNOWN_OPERATORS ? expr[i + 2] : null
|
|
214
219
|
const formatted = _format(
|
|
215
220
|
cur,
|
|
216
221
|
ref?.ref && (ref.ref.length ? ref.ref : ref.ref[0]),
|
|
@@ -944,7 +944,19 @@
|
|
|
944
944
|
|
|
945
945
|
const toXprElement = (operand) => {
|
|
946
946
|
if (typeof operand === 'number') return { val: operand }
|
|
947
|
-
if (typeof operand === 'string')
|
|
947
|
+
if (typeof operand === 'string') {
|
|
948
|
+
const safed = safeNumber(operand)
|
|
949
|
+
if (safed !== operand) {
|
|
950
|
+
return { val: safed }
|
|
951
|
+
} else {
|
|
952
|
+
const parsed = parseFloat(operand)
|
|
953
|
+
if (!isNaN(parsed)) {
|
|
954
|
+
return { val: operand, literal: 'number' }
|
|
955
|
+
} else {
|
|
956
|
+
return { ref: [operand] }
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
948
960
|
return operand // xpr
|
|
949
961
|
}
|
|
950
962
|
|
|
@@ -957,7 +969,7 @@
|
|
|
957
969
|
}
|
|
958
970
|
|
|
959
971
|
mathOperand
|
|
960
|
-
=
|
|
972
|
+
= number / negativeIdentifier / identifier / parenthesizedMath
|
|
961
973
|
|
|
962
974
|
negativeIdentifier
|
|
963
975
|
= "-" id:identifier { return { xpr: ['-', { ref: [id] }] } }
|
|
@@ -69,7 +69,8 @@ const _parseStream = async function* (body, boundary) {
|
|
|
69
69
|
body: streamConsumers.json(wrapper).catch(() => {})
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
// Ignore /$metadata URLs -> they are not dependencies
|
|
73
|
+
const dependencies = [...req.url.matchAll(/^\/?\$(?!metadata$)([\d.\-_~a-zA-Z]+)/g)]
|
|
73
74
|
if (dependencies.length) {
|
|
74
75
|
request.dependsOn = []
|
|
75
76
|
for (const dependency of dependencies) {
|