@sap/cds 9.0.2 → 9.0.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 +19 -0
- package/bin/serve.js +11 -9
- package/lib/i18n/locale.js +2 -1
- package/lib/log/cds-error.js +8 -7
- package/lib/srv/cds-connect.js +1 -1
- package/libx/_runtime/common/generic/temporal.js +0 -1
- package/libx/_runtime/common/utils/normalizeTimestamp.js +10 -1
- package/libx/odata/parse/afterburner.js +1 -2
- package/libx/odata/parse/cqn2odata.js +2 -2
- package/libx/odata/utils/metadata.js +9 -4
- package/libx/odata/utils/normalizeTimeData.js +1 -1
- package/libx/queue/index.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@
|
|
|
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.0.4 - 2025-06-18
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- In some cases, the app crashed if an element was named like a reserved CSN key.
|
|
12
|
+
- Locale detection does not enforce `<http-req>.query` to be present. Some protocol adapters do not set it.
|
|
13
|
+
- `between` operator for remote OData requests
|
|
14
|
+
- `cds serve/watch` and `cds.test()` no longer try to connect to an SQLite database if none is configured.
|
|
15
|
+
- `cds.connect.to` for required queueable services if no persistence is configured
|
|
16
|
+
- Persistent queue is not enabled if no persistence is configured
|
|
17
|
+
|
|
18
|
+
## Version 9.0.3 - 2025-06-04
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Handling of bad timestamps in URL ($filter and temporals)
|
|
23
|
+
- View metadata for requests with $apply
|
|
24
|
+
- Server crash for some URLs
|
|
25
|
+
|
|
7
26
|
## Version 9.0.2 - 2025-05-28
|
|
8
27
|
|
|
9
28
|
### Changed
|
package/bin/serve.js
CHANGED
|
@@ -337,16 +337,18 @@ function _chdir_to (project) {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
|
|
340
|
-
/** handles --in-memory option */
|
|
340
|
+
/** handles --in-memory[?] option */
|
|
341
341
|
function _in_memory (o) {
|
|
342
|
-
const {env} = cds, db = env.requires.db
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
342
|
+
const {env} = cds, db = env.requires.db, sqlite = env.requires.kinds.sqlite
|
|
343
|
+
try {
|
|
344
|
+
if (o['in-memory'] || (o['in-memory?'] && !db && require.resolve(sqlite.impl, {paths: [cds.root, cds.home]}))) {
|
|
345
|
+
env.add ({ requires: { db: {
|
|
346
|
+
kind:'sqlite', ...sqlite,
|
|
347
|
+
credentials: db?.credentials?.url ? {url:':memory:'} : {database:':memory:'}
|
|
348
|
+
}}})
|
|
349
|
+
return true
|
|
350
|
+
}
|
|
351
|
+
} catch (e) { if (e.code !== 'MODULE_NOT_FOUND') throw e }
|
|
350
352
|
if (db && db.credentials && (db.credentials.database || db.credentials.url) === ':memory:') {
|
|
351
353
|
return true
|
|
352
354
|
}
|
package/lib/i18n/locale.js
CHANGED
|
@@ -18,7 +18,8 @@ function normalized_locale_from (req) {
|
|
|
18
18
|
|
|
19
19
|
function raw_locale_from (req) {
|
|
20
20
|
return !req ? undefined :
|
|
21
|
-
req.query
|
|
21
|
+
// req.query is guaranteed by express, but not by others like websockets or plain Node http
|
|
22
|
+
req.query?.['sap-locale'] || SAP_LANGUAGES[req.query?.['sap-language']] ||
|
|
22
23
|
req.headers['x-sap-request-language'] ||
|
|
23
24
|
req.headers['accept-language']
|
|
24
25
|
}
|
package/lib/log/cds-error.js
CHANGED
|
@@ -62,13 +62,14 @@ exports.expected = ([,type], arg) => {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
TypeError
|
|
67
|
-
ReferenceError
|
|
68
|
-
SyntaxError
|
|
69
|
-
RangeError
|
|
70
|
-
URIError
|
|
71
|
-
|
|
65
|
+
const system_errors = [
|
|
66
|
+
TypeError,
|
|
67
|
+
ReferenceError,
|
|
68
|
+
SyntaxError,
|
|
69
|
+
RangeError,
|
|
70
|
+
URIError
|
|
71
|
+
]
|
|
72
|
+
exports.isSystemError = err => system_errors.some(e => err instanceof e)
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
//
|
package/lib/srv/cds-connect.js
CHANGED
|
@@ -44,7 +44,7 @@ connect.to = (datasource, options) => {
|
|
|
44
44
|
}
|
|
45
45
|
// construct new service instance
|
|
46
46
|
let srv = await new Service (datasource,m,o); await (Service._is_service_class ? srv.init?.() : Service.init?.(srv))
|
|
47
|
-
if (!
|
|
47
|
+
if (!srv.isDatabaseService && _is_queued(o)) srv = cds.queued(srv)
|
|
48
48
|
if (datasource && !options) {
|
|
49
49
|
if (datasource === 'db') cds.db = srv
|
|
50
50
|
cds.services[datasource] = srv
|
|
@@ -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, '')
|
|
@@ -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
|
|
|
@@ -167,8 +167,8 @@ function _xpr(expr, target, kind, isLambda, navPrefix = []) {
|
|
|
167
167
|
const operator = isOrIsNotValue[1] /* 'is not' */ ? 'ne' : 'eq'
|
|
168
168
|
res.push(...[operator, _format({ val: isOrIsNotValue[2] })])
|
|
169
169
|
} else if (cur === 'between') {
|
|
170
|
-
//
|
|
171
|
-
const between = [expr[i - 1], '
|
|
170
|
+
// between is inclusive, so we need to use ge and le
|
|
171
|
+
const between = [expr[i - 1], 'ge', expr[i + 1], 'and', expr[i - 1], 'le', expr[i + 3]]
|
|
172
172
|
// cleanup previous ref
|
|
173
173
|
res.pop()
|
|
174
174
|
res.push(`(${_xpr(between, target, kind, isLambda, navPrefix)})`)
|
|
@@ -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
|
|
@@ -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/libx/queue/index.js
CHANGED
|
@@ -36,6 +36,7 @@ const _isProviderTenant = tenant =>
|
|
|
36
36
|
cds.requires.multitenancy.t0 === tenant
|
|
37
37
|
|
|
38
38
|
const _hasPersistentQueue = tenant => {
|
|
39
|
+
if (!cds.db) return false // no persistence configured
|
|
39
40
|
if (cds.requires.multitenancy && tenant && _isProviderTenant(tenant)) return false // no persistence for provider account
|
|
40
41
|
return true
|
|
41
42
|
}
|
|
@@ -423,6 +424,7 @@ const _createTask = (name, msg, context, taskOpts) => {
|
|
|
423
424
|
delete _msg.query._target
|
|
424
425
|
delete _msg.query.__target
|
|
425
426
|
delete _msg.query.target
|
|
427
|
+
delete _msg.data // `req.data` should be a getter to whatever is in `req.query`
|
|
426
428
|
}
|
|
427
429
|
const taskMsg = {
|
|
428
430
|
ID: cds.utils.uuid(),
|