@sap/cds 8.9.3 → 8.9.5
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 +18 -0
- package/lib/core/classes.js +1 -0
- package/lib/core/linked-csn.js +1 -0
- package/lib/ql/resolve.js +1 -0
- package/lib/req/locale.js +2 -1
- package/lib/req/validate.js +1 -1
- package/libx/_runtime/common/generic/temporal.js +0 -1
- package/libx/_runtime/common/utils/normalizeTimestamp.js +10 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -2
- package/libx/odata/parse/afterburner.js +1 -2
- package/libx/odata/utils/metadata.js +10 -7
- package/libx/odata/utils/normalizeTimeData.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@
|
|
|
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 8.9.5 - 2025-07-25
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `req.diff` in case of draft entities using associations to joins/unions
|
|
12
|
+
- Locale detection does not enforce `<http-req>.query` to be present. Some protocol adapters do not set it.
|
|
13
|
+
- View metadata for requests with $apply
|
|
14
|
+
- Handling of bad timestamps in URL ($filter and temporals)
|
|
15
|
+
|
|
16
|
+
## Version 8.9.4 - 2025-05-16
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- No longer require `@sap/cds-compiler` versions 6.x as these are not supported with CAP Java 3.
|
|
21
|
+
- Regression in view resolving with mixins
|
|
22
|
+
- View resolving for external service entities aborted too early
|
|
23
|
+
- `cds.Map` validation in action/function parameters
|
|
24
|
+
|
|
7
25
|
## Version 8.9.3 - 2025-05-06
|
|
8
26
|
|
|
9
27
|
### Fixed
|
package/lib/core/classes.js
CHANGED
|
@@ -147,6 +147,7 @@ class type extends any { is(kind) { return kind === 'type' || super.is(kind) }
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
class Map extends struct {
|
|
150
|
+
get '@open' () { return this.set('@open', true) }
|
|
150
151
|
get elements() { return this.set('elements', new LinkedDefinitions) }
|
|
151
152
|
}
|
|
152
153
|
|
package/lib/core/linked-csn.js
CHANGED
|
@@ -47,6 +47,7 @@ class LinkedCSN {
|
|
|
47
47
|
if (p.actions && !d.actions) _set (d,'actions',undefined) //> don't propagate .actions
|
|
48
48
|
if (p.params && !d.params) _set (d,'params',undefined) //> don't propagate .params
|
|
49
49
|
if (d.elements?.localized) _set (d,'texts', defs[d.elements.localized.target])
|
|
50
|
+
if (!Object.hasOwn(d,'_service')) _set (d,'_service',undefined) //> don't propagate ._service
|
|
50
51
|
} else if (d.kind === 'element') {
|
|
51
52
|
if (p.key && !d.key) _set (d,'key',undefined) //> don't propagate .key
|
|
52
53
|
}
|
package/lib/ql/resolve.js
CHANGED
|
@@ -8,6 +8,7 @@ const _isPersistenceTable = target =>
|
|
|
8
8
|
// REVISIT revert after cds-dbs pr
|
|
9
9
|
// REVISIT: Remove once we get rid of old db
|
|
10
10
|
const _abortDB = resolve.abortDB = target => !!(_isPersistenceTable(target)|| !target.query?._target)
|
|
11
|
+
// _service seems to be inherited in projections, so do not consider prototype chain
|
|
11
12
|
const _defaultAbort = tx => e => e._service?.name === tx.definition?.name
|
|
12
13
|
|
|
13
14
|
function resolve(query, tx, abortCondition) {
|
package/lib/req/locale.js
CHANGED
|
@@ -10,7 +10,8 @@ function normalized_locale_from (req) {
|
|
|
10
10
|
|
|
11
11
|
function original_locale_from (req) {
|
|
12
12
|
return !req ? undefined :
|
|
13
|
-
req.query
|
|
13
|
+
// req.query is guaranteed by express, but not by others like websockets or plain Node http
|
|
14
|
+
req.query?.['sap-locale'] || SAP_LANGUAGES[req.query?.['sap-language']] ||
|
|
14
15
|
req.headers['x-sap-request-language'] ||
|
|
15
16
|
req.headers['accept-language']
|
|
16
17
|
}
|
package/lib/req/validate.js
CHANGED
|
@@ -208,7 +208,7 @@ class struct extends $any {
|
|
|
208
208
|
}
|
|
209
209
|
// check values of given data
|
|
210
210
|
for (let each in data) { // will work for structured payloads as well as flattened ones with universal CSN
|
|
211
|
-
let /** @type {$any} */ d = elements[each]
|
|
211
|
+
let /** @type {$any} */ d = Object.hasOwn(elements, each) && elements[each]
|
|
212
212
|
if (!d || (d['@cds.api.ignore'] && ctx.rejectIgnore)) ctx.unknown (each, this, data)
|
|
213
213
|
else if (ctx.cleanse && d._is_readonly() && !d.key) delete data[each]
|
|
214
214
|
// @Core.Immutable processed only for root, children are handled when knowing db state
|
|
@@ -4,7 +4,6 @@ const normalizeTimestamp = require('../utils/normalizeTimestamp')
|
|
|
4
4
|
const _getDateFromQueryOptions = str => {
|
|
5
5
|
if (str) {
|
|
6
6
|
const match = str.match(/^date'(.+)'$/)
|
|
7
|
-
// REVISIT: What happens with invalid date values in query parameter? if match.length > 1
|
|
8
7
|
return normalizeTimestamp(match ? match[1] : str)
|
|
9
8
|
}
|
|
10
9
|
}
|
|
@@ -9,13 +9,22 @@ const _lengthIfNotFoundIndex = (index, length) => (index > -1 ? index : length)
|
|
|
9
9
|
module.exports = value => {
|
|
10
10
|
if (value instanceof Date) value = value.toISOString()
|
|
11
11
|
if (typeof value === 'number') value = new Date(value).toISOString()
|
|
12
|
+
if (typeof value !== 'string') {
|
|
13
|
+
const msg = `Value "${value}" is not a valid Timestamp`
|
|
14
|
+
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
15
|
+
}
|
|
12
16
|
|
|
13
17
|
const decimalPointIndex = _lengthIfNotFoundIndex(value.lastIndexOf('.'), value.length)
|
|
14
18
|
const tzRegexMatch = TZ_REGEX.exec(value)
|
|
15
19
|
const tz = tzRegexMatch?.[0] || ''
|
|
16
20
|
const tzIndex = _lengthIfNotFoundIndex(tzRegexMatch?.index, value.length)
|
|
17
21
|
const dateEndIndex = Math.min(decimalPointIndex, tzIndex)
|
|
18
|
-
|
|
22
|
+
let dt = new Date(value.slice(0, dateEndIndex) + tz)
|
|
23
|
+
if (isNaN(dt)) {
|
|
24
|
+
const msg = `Value "${value}" is not a valid Timestamp`
|
|
25
|
+
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
26
|
+
}
|
|
27
|
+
const dateNoMillisNoTZ = dt.toISOString().slice(0, 19)
|
|
19
28
|
const normalizedFractionalDigits = value
|
|
20
29
|
.slice(dateEndIndex + 1, tzIndex)
|
|
21
30
|
.replace(NON_DIGIT_REGEX, '')
|
|
@@ -32,7 +32,7 @@ const _inverseTransition = transition => {
|
|
|
32
32
|
const ref0 = value.ref[0]
|
|
33
33
|
if (value.ref.length > 1) {
|
|
34
34
|
// ignore flattened columns like author.name
|
|
35
|
-
if (transition.target.elements[ref0]
|
|
35
|
+
if (transition.target.elements[ref0]?.isAssociation) continue
|
|
36
36
|
|
|
37
37
|
const nested = inverseTransition.mapping.get(ref0) || {}
|
|
38
38
|
if (!nested.transition) nested.transition = { mapping: new Map() }
|
|
@@ -551,7 +551,7 @@ const _mappedValue = (col, alias) => {
|
|
|
551
551
|
const getDBTable = target => cds.ql.resolve.table(target)
|
|
552
552
|
|
|
553
553
|
const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
|
|
554
|
-
const el = target.elements[as] || target.query._target
|
|
554
|
+
const el = target.elements[as] || target.query._target?.elements[ref.at(-1)]
|
|
555
555
|
|
|
556
556
|
if (el && el.isAssociation && el.keys) {
|
|
557
557
|
for (const key of el.keys) {
|
|
@@ -12,8 +12,7 @@ function _getDefinition(definition, name, namespace) {
|
|
|
12
12
|
return (
|
|
13
13
|
definition?.definitions?.[name] ||
|
|
14
14
|
definition?.elements?.[name] ||
|
|
15
|
-
(definition.actions && (definition.actions[name] || definition.actions[name.replace(namespace + '.', '')]))
|
|
16
|
-
definition[name]
|
|
15
|
+
(definition.actions && (definition.actions[name] || definition.actions[name.replace(namespace + '.', '')]))
|
|
17
16
|
)
|
|
18
17
|
}
|
|
19
18
|
|
|
@@ -32,6 +32,14 @@ const _lastValidRef = ref => {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
const _getRef = query => {
|
|
36
|
+
if (query.SELECT?.from?.SELECT) return _getRef(query.SELECT.from)
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
query.SELECT?.from?.ref ?? query.UPDATE?.entity?.ref ?? query.INSERT?.into?.ref ?? query.DELETE?.from?.ref ?? []
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
const _toBinaryKeyValue = value => `binary'${value.toString('base64')}'`
|
|
36
44
|
|
|
37
45
|
const _odataContext = (query, options) => {
|
|
@@ -49,10 +57,7 @@ const _odataContext = (query, options) => {
|
|
|
49
57
|
|
|
50
58
|
path += '#'
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
const ref =
|
|
54
|
-
query.SELECT?.from?.ref ?? query.UPDATE?.entity?.ref ?? query.INSERT?.into?.ref ?? query.DELETE?.from?.ref ?? []
|
|
55
|
-
|
|
60
|
+
const ref = _getRef(query)
|
|
56
61
|
const isNavToDraftAdmin = _isNavToDraftAdmin(ref)
|
|
57
62
|
|
|
58
63
|
let edmName
|
|
@@ -73,9 +78,6 @@ const _odataContext = (query, options) => {
|
|
|
73
78
|
path += edmName
|
|
74
79
|
|
|
75
80
|
const isViewWithParams = query._target.kind === 'entity' && query._target.params
|
|
76
|
-
if (isViewWithParams) path += 'Type'
|
|
77
|
-
|
|
78
|
-
const lastRef = ref.at(-1)
|
|
79
81
|
|
|
80
82
|
if (propertyAccess || isNavToDraftAdmin || isViewWithParams) {
|
|
81
83
|
if (!contextAbsoluteUrl && (propertyAccess || isViewWithParams)) path = '../' + path
|
|
@@ -83,6 +85,7 @@ const _odataContext = (query, options) => {
|
|
|
83
85
|
const keyValuePairs = []
|
|
84
86
|
|
|
85
87
|
const lastValidRef = _lastValidRef(ref)
|
|
88
|
+
const lastRef = ref.at(-1)
|
|
86
89
|
const isSibling = lastRef === 'SiblingEntity'
|
|
87
90
|
let _keyValuePairs
|
|
88
91
|
if (lastValidRef.where) {
|
|
@@ -8,7 +8,7 @@ const _processorFn = elementInfo => {
|
|
|
8
8
|
const { row, key } = elementInfo
|
|
9
9
|
if (!(row[key] == null) && row[key] !== '$now') {
|
|
10
10
|
const dt = typeof row[key] === 'string' && new Date(row[key])
|
|
11
|
-
if (!isNaN(dt)) {
|
|
11
|
+
if (dt && !isNaN(dt)) {
|
|
12
12
|
switch (category) {
|
|
13
13
|
case 'cds.DateTime':
|
|
14
14
|
row[key] = new Date(row[key]).toISOString().replace(/\.\d\d\d/, '')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds",
|
|
3
|
-
"version": "8.9.
|
|
3
|
+
"version": "8.9.5",
|
|
4
4
|
"description": "SAP Cloud Application Programming Model - CDS for Node.js",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"keywords": [
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"node": ">=18"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@sap/cds-compiler": "
|
|
36
|
+
"@sap/cds-compiler": "^5",
|
|
37
37
|
"@sap/cds-fiori": "^1",
|
|
38
38
|
"@sap/cds-foss": "^5.0.0"
|
|
39
39
|
},
|