@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 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
  }
@@ -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 ?? statusEnum[from['#']]['$path'].at(-1) : from
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 ')})`
@@ -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(/\/cds\/srv\/outbox(\.cds)?$/)
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
@@ -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 && JSON.stringify(s).slice(1,-1)
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) return errs.forEach(err => req.error(err))
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) return errs.forEach(err => req.error(err))
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) for (let a in entity.actions) this.before(a, entity, validate_action)
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]?.isComposition) continue
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
- const handleReconnection = err => {
41
- if (client._reconnecting) return
15
+ _rmHandlers(client)
42
16
 
43
- if (err && LOG._error) {
44
- err.message = 'Client error: ' + err.message
45
- LOG.error(err)
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.once('disconnected', handleReconnection)
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
- client.connect()
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.2",
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.1",
35
+ "@sap/cds-compiler": "^6.3",
36
36
  "@sap/cds-fiori": "^2",
37
37
  "js-yaml": "^4.1.0"
38
38
  },