@sap/cds 9.4.2 → 9.4.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 +18 -1
- package/lib/compile/etc/properties.js +0 -1
- package/lib/compile/for/flows.js +1 -1
- package/lib/compile/load.js +1 -1
- package/lib/i18n/localize.js +1 -1
- package/lib/srv/middlewares/errors.js +2 -1
- package/libx/_runtime/common/generic/input.js +17 -3
- package/libx/_runtime/fiori/lean-draft.js +3 -5
- package/libx/_runtime/messaging/common-utils/connections.js +29 -40
- 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 9.4.4 - 2025-10-23
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Input validation of action parameters in action calls on draft state of draft enabled entities
|
|
12
|
+
- Input validation on `NEW` event of draft choreography
|
|
13
|
+
- Ignore outbox model on Windows
|
|
14
|
+
- `enterprise-messaging-shared`: preserve error listener during reconnect
|
|
15
|
+
|
|
16
|
+
## Version 9.4.3 - 2025-10-10
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Don't continue validation user input if data type is wrong
|
|
21
|
+
- UI annotation generation for status transition flows for Java
|
|
22
|
+
- Increased minimium version of `@sap/cds-compiler` to 6.3.x
|
|
23
|
+
- Undefined error message for early access checks
|
|
24
|
+
|
|
7
25
|
## Version 9.4.2 - 2025-10-08
|
|
8
26
|
|
|
9
27
|
### Fixed
|
|
@@ -53,7 +71,6 @@
|
|
|
53
71
|
- Broken link `cds.auth`
|
|
54
72
|
- Persist original error message in draft validation messages
|
|
55
73
|
- Escaping of `\t` and `\f` in edmx during localization
|
|
56
|
-
- Escaping of JSON escape sequences other than `\"` during localization
|
|
57
74
|
|
|
58
75
|
## Version 9.3.1 - 2025-09-03
|
|
59
76
|
|
|
@@ -40,5 +40,4 @@ function string4 (raw) {
|
|
|
40
40
|
return raw
|
|
41
41
|
.replace(/''/g,"'") .replace(/^"(.*)"$/,"$1") .replace(/^'(.*)'$/,"$1")
|
|
42
42
|
.replace(/\\u[\dA-F]{4}/gi, (match) => String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)))
|
|
43
|
-
.replace(/\\(.)/g, (_, seq) => ({ n: '\n', r: '\r', t: '\t', f: '\f' }[seq] ?? seq))
|
|
44
43
|
}
|
package/lib/compile/for/flows.js
CHANGED
|
@@ -19,7 +19,7 @@ function addOperationAvailableToActions(actions, statusEnum, statusElementName)
|
|
|
19
19
|
for (const action of Object.values(actions)) {
|
|
20
20
|
const fromList = getFrom(action)
|
|
21
21
|
const conditions = fromList.map(from => {
|
|
22
|
-
const value = from['#'] ? statusEnum[from['#']]?.val ??
|
|
22
|
+
const value = from['#'] ? (statusEnum[from['#']]?.val ?? from['#']) : from
|
|
23
23
|
return `$self.${statusElementName} = ${typeof value === 'string' ? `'${value}'` : value}`
|
|
24
24
|
})
|
|
25
25
|
const condition = `(${conditions.join(' OR ')})`
|
package/lib/compile/load.js
CHANGED
|
@@ -13,7 +13,7 @@ module.exports = exports = function load (files, options) {
|
|
|
13
13
|
// REVISIT: bandaid for grow as you go scenario with task queues enabled by default
|
|
14
14
|
let locations
|
|
15
15
|
if (cds.watched) {
|
|
16
|
-
const _is_outbox = p => cds.utils.path.posix.normalize(p).match(
|
|
16
|
+
const _is_outbox = p => cds.utils.path.posix.normalize(p).match(/((\/cds\/srv\/outbox)|(\\cds\\srv\\outbox))(\.cds)?$/)
|
|
17
17
|
const _outbox_only = any?.length === 1 && _is_outbox(any[0]) && (!Array.isArray(files) || !files.some(_is_outbox))
|
|
18
18
|
if (_outbox_only) {
|
|
19
19
|
any = undefined
|
package/lib/i18n/localize.js
CHANGED
|
@@ -85,7 +85,7 @@ exports.edmx = edmx => {
|
|
|
85
85
|
|
|
86
86
|
exports.json = json => {
|
|
87
87
|
if (typeof json === 'object') json = JSON.stringify(json)
|
|
88
|
-
const _json_replacer = s => s
|
|
88
|
+
const _json_replacer = s => s?.replace(/"/g, '\\"')
|
|
89
89
|
return localize(json) .using (_json_replacer)
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -2,13 +2,14 @@ const cds = require('../..'), {i18n} = cds
|
|
|
2
2
|
const LOG = cds.log('error')
|
|
3
3
|
const internals = /\n +at .*(?:node_modules\/express|node:).*/gm
|
|
4
4
|
const is_test = typeof global.it === 'function'
|
|
5
|
+
const { STATUS_CODES } = require('http')
|
|
5
6
|
|
|
6
7
|
module.exports = () => {
|
|
7
8
|
/** @param {import('express').Response} res */
|
|
8
9
|
return function http_error (err, req, res, next) { // eslint-disable-line no-unused-vars
|
|
9
10
|
|
|
10
11
|
// In case of 401 require login if available by auth strategy
|
|
11
|
-
if (typeof err === 'number') err = { code: err }
|
|
12
|
+
if (typeof err === 'number') err = { status: err, code: String(err), message: STATUS_CODES[err] }
|
|
12
13
|
if (err.code == 401 && req._login) return req._login()
|
|
13
14
|
|
|
14
15
|
// Shutdown on uncaught errors, which could be critical programming errors
|
|
@@ -355,7 +355,12 @@ async function validate_input(req) {
|
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
const errs = cds.validate(req.data, req.target, assertOptions)
|
|
358
|
-
if (errs)
|
|
358
|
+
if (errs) {
|
|
359
|
+
errs.forEach(err => req.error(err))
|
|
360
|
+
// data types are incorrect -> stop further processing which could rely on data type
|
|
361
|
+
if (errs.some(e => e.message === 'ASSERT_DATA_TYPE')) req.reject()
|
|
362
|
+
else return
|
|
363
|
+
}
|
|
359
364
|
|
|
360
365
|
// -------------------------------------------------
|
|
361
366
|
// REVISIT: is the below still needed?
|
|
@@ -399,7 +404,12 @@ function validate_action(req) {
|
|
|
399
404
|
protocol: req.protocol
|
|
400
405
|
}
|
|
401
406
|
let errs = cds.validate(data, operation, assertOptions)
|
|
402
|
-
if (errs)
|
|
407
|
+
if (errs) {
|
|
408
|
+
errs.forEach(err => req.error(err))
|
|
409
|
+
// data types are incorrect -> stop further processing which could rely on data type
|
|
410
|
+
if (errs.some(e => e.message === 'ASSERT_DATA_TYPE')) req.reject()
|
|
411
|
+
else return
|
|
412
|
+
}
|
|
403
413
|
|
|
404
414
|
// convert binaries
|
|
405
415
|
operation.params &&
|
|
@@ -419,7 +429,11 @@ validate_action._initial = true
|
|
|
419
429
|
module.exports = cds.service.impl(function () {
|
|
420
430
|
this.before(['CREATE', 'UPDATE', 'NEW'], '*', validate_input)
|
|
421
431
|
for (const each of this.actions) this.before(each, validate_action)
|
|
422
|
-
for (const entity of this.entities)
|
|
432
|
+
for (const entity of this.entities)
|
|
433
|
+
for (let a in entity.actions) {
|
|
434
|
+
this.before(a, entity, validate_action)
|
|
435
|
+
if (entity.drafts) this.before(a, entity.drafts, validate_action)
|
|
436
|
+
}
|
|
423
437
|
})
|
|
424
438
|
|
|
425
439
|
// needed for testing
|
|
@@ -1915,13 +1915,11 @@ async function beforeNew(req) {
|
|
|
1915
1915
|
|
|
1916
1916
|
// Also support deep insertions
|
|
1917
1917
|
for (const key in data) {
|
|
1918
|
+
if (!target.elements[key]) return data
|
|
1918
1919
|
if (data[key] && target.elements[key].isAssociation) delete data[key].IsActiveEntity
|
|
1919
|
-
if (!target.elements[key]
|
|
1920
|
-
// do array trick
|
|
1920
|
+
if (!target.elements[key].isComposition) continue
|
|
1921
1921
|
if (Array.isArray(data[key])) data[key] = data[key].map(v => _cleanseData(v, target.elements[key]._target))
|
|
1922
|
-
else if (typeof data[key] === 'object')
|
|
1923
|
-
data[key] = _cleanseData(data[key], target.elements[key]._target)
|
|
1924
|
-
}
|
|
1922
|
+
else if (typeof data[key] === 'object') data[key] = _cleanseData(data[key], target.elements[key]._target)
|
|
1925
1923
|
}
|
|
1926
1924
|
|
|
1927
1925
|
return data
|
|
@@ -6,54 +6,39 @@ const _rmHandlers = client => {
|
|
|
6
6
|
client.removeAllListeners('disconnected')
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
const _connectUntilConnected = (client, LOG, x) => {
|
|
10
|
-
if (client._reconnecting) return
|
|
11
|
-
client._reconnecting = true
|
|
12
|
-
|
|
13
|
-
const _waitingTime = waitingTime(x)
|
|
14
|
-
setTimeout(() => {
|
|
15
|
-
connect(client, LOG, true)
|
|
16
|
-
.then(() => {
|
|
17
|
-
client._reconnecting = false
|
|
18
|
-
LOG._warn && LOG.warn('Reconnected to Enterprise Messaging Client')
|
|
19
|
-
})
|
|
20
|
-
.catch(e => {
|
|
21
|
-
_rmHandlers(client)
|
|
22
|
-
LOG.error(e)
|
|
23
|
-
|
|
24
|
-
LOG._warn &&
|
|
25
|
-
LOG.warn(
|
|
26
|
-
`Connection to Enterprise Messaging Client lost: Reconnecting in ${Math.round(_waitingTime / 1000)} s`
|
|
27
|
-
)
|
|
28
|
-
client._reconnecting = false
|
|
29
|
-
_connectUntilConnected(client, LOG, x + 1)
|
|
30
|
-
})
|
|
31
|
-
}, _waitingTime)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
9
|
const connect = (client, LOG, keepAlive) => {
|
|
35
10
|
return new Promise((resolve, reject) => {
|
|
36
11
|
_rmHandlers(client)
|
|
37
12
|
|
|
38
13
|
client
|
|
39
14
|
.once('connected', function () {
|
|
40
|
-
|
|
41
|
-
if (client._reconnecting) return
|
|
15
|
+
_rmHandlers(client)
|
|
42
16
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
if (keepAlive) {
|
|
48
|
-
_rmHandlers(client)
|
|
49
|
-
_connectUntilConnected(client, LOG, 0)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
17
|
+
// It's important to _always_ keep an error listener,
|
|
18
|
+
// otherwise it will throw and crash the server
|
|
19
|
+
client.on('error', err => LOG(err?.message))
|
|
52
20
|
|
|
53
|
-
_rmHandlers(client)
|
|
54
|
-
client.once('error', handleReconnection)
|
|
55
21
|
if (keepAlive) {
|
|
56
|
-
client.
|
|
22
|
+
client.on('disconnected', () => {
|
|
23
|
+
let connected = false
|
|
24
|
+
client.once('connected', () => {
|
|
25
|
+
LOG('Reconnected')
|
|
26
|
+
connected = true
|
|
27
|
+
})
|
|
28
|
+
let x = 1
|
|
29
|
+
const _untilConnected = async () => {
|
|
30
|
+
if (!connected) {
|
|
31
|
+
LOG('Reconnecting')
|
|
32
|
+
try {
|
|
33
|
+
client.connect()
|
|
34
|
+
} catch {
|
|
35
|
+
// we try again...
|
|
36
|
+
}
|
|
37
|
+
setTimeout(_untilConnected, waitingTime(x++))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
_untilConnected()
|
|
41
|
+
})
|
|
57
42
|
}
|
|
58
43
|
|
|
59
44
|
resolve(client)
|
|
@@ -65,7 +50,11 @@ const connect = (client, LOG, keepAlive) => {
|
|
|
65
50
|
reject(e)
|
|
66
51
|
})
|
|
67
52
|
|
|
68
|
-
|
|
53
|
+
try {
|
|
54
|
+
client.connect()
|
|
55
|
+
} catch (e) {
|
|
56
|
+
reject(e)
|
|
57
|
+
}
|
|
69
58
|
})
|
|
70
59
|
}
|
|
71
60
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds",
|
|
3
|
-
"version": "9.4.
|
|
3
|
+
"version": "9.4.4",
|
|
4
4
|
"description": "SAP Cloud Application Programming Model - CDS for Node.js",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"keywords": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"node": ">=20"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@sap/cds-compiler": "^6.
|
|
35
|
+
"@sap/cds-compiler": "^6.3",
|
|
36
36
|
"@sap/cds-fiori": "^2",
|
|
37
37
|
"js-yaml": "^4.1.0"
|
|
38
38
|
},
|