@sap/cds 7.8.2 → 7.9.0

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.
Files changed (136) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/_i18n/i18n_ar.properties +3 -0
  3. package/_i18n/i18n_cs.properties +3 -0
  4. package/_i18n/i18n_da.properties +3 -0
  5. package/_i18n/i18n_es_MX.properties +3 -0
  6. package/_i18n/i18n_fi.properties +3 -0
  7. package/_i18n/i18n_hu.properties +6 -0
  8. package/_i18n/i18n_ko.properties +3 -0
  9. package/_i18n/i18n_ms.properties +3 -0
  10. package/_i18n/i18n_nl.properties +3 -0
  11. package/_i18n/i18n_no.properties +3 -0
  12. package/_i18n/i18n_ro.properties +3 -0
  13. package/_i18n/i18n_sv.properties +3 -0
  14. package/_i18n/i18n_th.properties +3 -0
  15. package/_i18n/i18n_tr.properties +6 -0
  16. package/_i18n/i18n_zh_TW.properties +3 -0
  17. package/bin/serve.js +5 -5
  18. package/lib/auth/basic-auth.js +1 -1
  19. package/lib/compile/cdsc.js +33 -6
  20. package/lib/compile/etc/_localized.js +14 -7
  21. package/lib/compile/for/lean_drafts.js +9 -0
  22. package/lib/compile/to/edm-files.js +116 -0
  23. package/lib/compile/to/edm.js +8 -1
  24. package/lib/compile/to/hdbtabledata.js +3 -3
  25. package/lib/compile/to/sql.js +4 -2
  26. package/lib/compile/to/yaml.js +22 -21
  27. package/lib/dbs/cds-deploy.js +5 -6
  28. package/lib/env/cds-env.js +7 -0
  29. package/lib/env/cds-requires.js +20 -1
  30. package/lib/env/defaults.js +21 -5
  31. package/lib/env/schemas/cds-package.js +1 -1
  32. package/lib/env/schemas/cds-rc.js +85 -4
  33. package/lib/index.js +1 -1
  34. package/lib/linked/entities.js +10 -0
  35. package/lib/linked/models.js +1 -1
  36. package/lib/plugins.js +1 -1
  37. package/lib/ql/INSERT.js +17 -3
  38. package/lib/ql/Query.js +4 -0
  39. package/lib/ql/infer.js +1 -1
  40. package/lib/req/request.js +1 -1
  41. package/lib/srv/cds-serve.js +1 -0
  42. package/lib/srv/middlewares/cds-context.js +1 -1
  43. package/lib/srv/protocols/odata-v4.js +5 -6
  44. package/lib/srv/srv-models.js +9 -2
  45. package/lib/utils/cds-test.js +2 -0
  46. package/lib/utils/cds-utils.js +9 -4
  47. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  50. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -1
  51. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +3 -6
  52. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +22 -10
  53. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -4
  54. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +4 -3
  55. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +1 -1
  56. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +4 -1
  57. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  58. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +38 -1
  59. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +2 -2
  60. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +32 -21
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +1 -2
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -10
  64. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +3 -1
  65. package/libx/_runtime/cds-services/services/utils/compareJson.js +2 -274
  66. package/libx/_runtime/{cds-services/services → common}/Service.js +39 -29
  67. package/libx/_runtime/common/generic/auth/autoexpose.js +41 -0
  68. package/libx/_runtime/common/generic/auth/index.js +2 -0
  69. package/libx/_runtime/common/generic/auth/readOnly.js +0 -11
  70. package/libx/_runtime/common/generic/auth/restrict.js +6 -5
  71. package/libx/_runtime/common/generic/auth/utils.js +1 -1
  72. package/libx/_runtime/common/generic/crud.js +5 -8
  73. package/libx/_runtime/common/generic/etag.js +8 -6
  74. package/libx/_runtime/common/generic/sorting.js +2 -2
  75. package/libx/_runtime/common/i18n/messages.properties +1 -0
  76. package/libx/_runtime/{cds-services/services → common}/utils/columns.js +4 -4
  77. package/libx/_runtime/common/utils/compareJson.js +274 -0
  78. package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
  79. package/libx/_runtime/{cds-services/services → common}/utils/differ.js +8 -8
  80. package/libx/_runtime/common/utils/ensureIEEE754.js +29 -0
  81. package/libx/_runtime/common/utils/{postProcessing.js → postProcess.js} +1 -3
  82. package/libx/_runtime/common/utils/resolveView.js +0 -16
  83. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
  84. package/libx/_runtime/common/utils/search2cqn4sql.js +1 -1
  85. package/libx/_runtime/common/utils/streamProp.js +9 -2
  86. package/libx/_runtime/common/utils/ucsn.js +1 -1
  87. package/libx/_runtime/db/expand/expandCQNToJoin.js +5 -3
  88. package/libx/_runtime/db/generic/rewrite.js +7 -13
  89. package/libx/_runtime/fiori/generic/activate.js +1 -1
  90. package/libx/_runtime/fiori/generic/edit.js +1 -1
  91. package/libx/_runtime/fiori/generic/prepare.js +1 -1
  92. package/libx/_runtime/fiori/lean-draft.js +151 -46
  93. package/libx/_runtime/fiori/utils/handler.js +1 -1
  94. package/libx/_runtime/hana/execute.js +6 -2
  95. package/libx/_runtime/hana/search2cqn4sql.js +1 -1
  96. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
  97. package/libx/_runtime/messaging/event-broker.js +212 -0
  98. package/libx/_runtime/remote/Service.js +9 -32
  99. package/libx/_runtime/remote/utils/client.js +13 -21
  100. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +7 -1
  101. package/libx/_runtime/sqlite/execute.js +8 -3
  102. package/libx/_runtime/ucl/Service.js +259 -0
  103. package/libx/common/assert/index.js +5 -11
  104. package/libx/common/assert/validation.js +6 -1
  105. package/libx/odata/index.js +47 -25
  106. package/libx/odata/middleware/batch.js +8 -7
  107. package/libx/odata/middleware/create.js +42 -16
  108. package/libx/odata/middleware/delete.js +18 -11
  109. package/libx/odata/middleware/metadata.js +15 -14
  110. package/libx/odata/middleware/operation.js +30 -40
  111. package/libx/odata/middleware/parse.js +2 -3
  112. package/libx/odata/middleware/read.js +59 -52
  113. package/libx/odata/middleware/service-document.js +7 -7
  114. package/libx/odata/middleware/stream.js +26 -24
  115. package/libx/odata/middleware/update.js +53 -92
  116. package/libx/odata/parse/afterburner.js +45 -47
  117. package/libx/odata/parse/grammar.peggy +3 -3
  118. package/libx/odata/parse/multipartToJson.js +10 -22
  119. package/libx/odata/parse/parser.js +1 -1
  120. package/libx/odata/utils/etag.js +13 -0
  121. package/libx/odata/utils/handler.js +120 -0
  122. package/libx/odata/utils/index.js +15 -2
  123. package/libx/odata/utils/metaInfo.js +410 -0
  124. package/libx/odata/utils/path.js +5 -2
  125. package/libx/odata/utils/readAfterWrite.js +23 -0
  126. package/libx/odata/utils/result.js +4 -5
  127. package/libx/rest/RestAdapter.js +4 -13
  128. package/libx/rest/middleware/parse.js +40 -7
  129. package/package.json +1 -1
  130. package/server.js +1 -0
  131. package/libx/_runtime/cds-services/util/dataProcessUtils.js +0 -93
  132. package/libx/_runtime/common/utils/thenable.js +0 -51
  133. package/libx/_runtime/rest/service.js +0 -2
  134. package/libx/odata/parse/parseToCqn.js +0 -39
  135. package/libx/rest/middleware/input.js +0 -54
  136. package/libx/rest/middleware/payload.js +0 -13
@@ -1,7 +1,7 @@
1
1
  const cds = require('../../cds')
2
2
  const { Object_keys } = cds.utils
3
3
  const { UPDATE, SELECT } = cds.ql
4
- const { getColumns } = require('../../cds-services/services/utils/columns')
4
+ const { getColumns } = require('../../common/utils/columns')
5
5
  const { ensureNoDraftsSuffix, ensureDraftsSuffix, ensureUnlocalized } = require('../../common/utils/draft')
6
6
  const getTemplate = require('../../common/utils/template')
7
7
 
@@ -404,10 +404,14 @@ async function executeSelectStreamCQN({ model, query, dbc, user, locale, txTimes
404
404
  if (result.length === 0) return
405
405
 
406
406
  if (cds.env.features.stream_compat) {
407
- const val = Object.values(result[0])[0]
407
+ const res = _convertNames(result[0], query.SELECT?.columns)
408
+ let [key, val] = Object.entries(res)[0]
408
409
  if (val === null) return null
409
410
 
410
- return { value: val }
411
+ res.value = val
412
+ delete res[key]
413
+
414
+ return res
411
415
  } else {
412
416
  return dbc.name === 'hdb' ? result[0] : _convertNames(result[0], query.SELECT?.columns)
413
417
  }
@@ -1,5 +1,5 @@
1
1
  const cds = require('../cds')
2
- const { computeColumnsToBeSearched } = require('../cds-services/services/utils/columns')
2
+ const { computeColumnsToBeSearched } = require('../common/utils/columns')
3
3
  const searchToLike = require('../common/utils/searchToLike')
4
4
  const { isContainsPredicateSupported, search2Contains } = require('./search2Contains')
5
5
  const { addAliasToExpression } = require('../db/utils/generateAliases')
@@ -11,7 +11,6 @@ const _isAll = a => a && a.includes('all')
11
11
  class EndpointRegistry {
12
12
  constructor(basePath, LOG) {
13
13
  const deployPath = basePath + '/deploy'
14
- const paths = [basePath, deployPath]
15
14
  this.webhookCallbacks = new Map()
16
15
  this.deployCallbacks = new Map()
17
16
  if (isSecured()) {
@@ -60,7 +59,7 @@ class EndpointRegistry {
60
59
  cds.app.options(basePath, (req, res) => {
61
60
  try {
62
61
  if (isSecured() && !req.user.is('emcallback')) return res.sendStatus(403)
63
- res.set('WebHook-Allowed-Origin', req.headers['webhook-request-origin'])
62
+ res.set('webhook-allowed-origin', req.headers['webhook-request-origin'])
64
63
  res.sendStatus(200)
65
64
  } catch (error) {
66
65
  res.sendStatus(500)
@@ -0,0 +1,212 @@
1
+ const cds = require('../cds')
2
+
3
+ // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
4
+ const express = require('express')
5
+ const https = require('https')
6
+ const crypto = require('crypto')
7
+
8
+ async function request(options, data) {
9
+ return new Promise((resolve, reject) => {
10
+ const req = https.request(options, res => {
11
+ const chunks = []
12
+ res.on('data', chunk => {
13
+ chunks.push(chunk)
14
+ })
15
+ res.on('end', () => {
16
+ const response = {
17
+ statusCode: res.statusCode,
18
+ headers: res.headers,
19
+ body: Buffer.concat(chunks).toString()
20
+ }
21
+ if (res.statusCode > 299) {
22
+ reject({ message: response.body })
23
+ } else {
24
+ resolve(response)
25
+ }
26
+ })
27
+ })
28
+ req.on('error', error => {
29
+ reject(error)
30
+ })
31
+ if (data) {
32
+ req.write(JSON.stringify(data))
33
+ }
34
+ req.end()
35
+ })
36
+ }
37
+
38
+ function _validateCertificate(req, res, next) {
39
+ this.LOG.debug('event broker trying to authenticate via mTLS')
40
+
41
+ if (req.headers['x-ssl-client-verify'] !== '0') {
42
+ this.LOG.info('cf did not validate client certificate.')
43
+ return res.status(401).json({ message: 'Authentication Failed' })
44
+ }
45
+
46
+ if (!req.headers['x-forwarded-client-cert']) {
47
+ this.LOG.info('no certificate in xfcc header.')
48
+ return res.status(401).json({ message: 'Authentication Failed' })
49
+ }
50
+
51
+ const bindingCert = new crypto.X509Certificate(this.options.credentials.certificate).toLegacyObject()
52
+ const clientCert = new crypto.X509Certificate(
53
+ `-----BEGIN CERTIFICATE-----\n${req.headers['x-forwarded-client-cert']}\n-----END CERTIFICATE-----`
54
+ ).toLegacyObject()
55
+
56
+ const cfSubject = Buffer.from(req.headers['x-ssl-client-subject-cn'], 'base64').toString()
57
+ if (bindingCert.subject.CN !== clientCert.subject.CN || bindingCert.subject.CN !== cfSubject) {
58
+ this.LOG.info('certificate subject does not match')
59
+ return res.status(401).json({ message: 'Authentication Failed' })
60
+ }
61
+ this.LOG.debug('incoming Subject CN is valid.')
62
+
63
+ if (bindingCert.issuer.CN !== clientCert.issuer.CN) {
64
+ this.LOG.info('Certificate issuer subject does not match')
65
+ return res.status(401).json({ message: 'Authentication Failed' })
66
+ }
67
+ this.LOG.debug('incoming issuer subject CN is valid.')
68
+
69
+ if (bindingCert.issuer.O !== clientCert.issuer.O) {
70
+ this.LOG.info('Certificate issuer org does not match')
71
+ return res.status(401).json({ message: 'Authentication Failed' })
72
+ }
73
+ this.LOG.debug('incoming Issuer Org is valid.')
74
+
75
+ if (bindingCert.issuer.OU !== clientCert.issuer.OU) {
76
+ this.LOG.info('certificate issuer OU does not match')
77
+ return res.status(401).json({ message: 'Authentication Failed' })
78
+ }
79
+ this.LOG.debug('certificate issuer OU is valid.')
80
+
81
+ const valid_from = new Date(clientCert.valid_from)
82
+ const valid_to = new Date(clientCert.valid_to)
83
+ const now = new Date(Date.now())
84
+ if (valid_from <= now && valid_to >= now) {
85
+ this.LOG.debug('certificate validation completed')
86
+ next()
87
+ } else {
88
+ this.LOG.error('Certificate expired')
89
+ return res.status(401).json({ message: 'Authentication Failed' })
90
+ }
91
+ }
92
+
93
+ // TODO: seems unused
94
+ function _checkAppDomains() {
95
+ const pattern = /.*\.cert\.cfapps\..*\.hana\.ondemand\.com/
96
+ const uris = JSON.parse(process.env.VCAP_APPLICATION).application_uris
97
+ const matchFound = uris.some(uri => pattern.test(uri))
98
+ if (matchFound)
99
+ this.LOG.warn(
100
+ `*.cert.cfapps.*.hana.ondemand.com domain is in use, this is not recommended in production! Please use 'mesh.cf.<region>.hana.ondemand.com' instead!`
101
+ )
102
+ }
103
+
104
+ let instantiated = false
105
+
106
+ class EventBroker extends cds.MessagingService {
107
+ async init() {
108
+ // TODO: Only needed if there are subscriptions
109
+ if (instantiated)
110
+ throw new Error('Event Broker service must be a singleton service, you cannot have more than one instance.')
111
+ instantiated = true
112
+ await super.init()
113
+ cds.once('listening', () => {
114
+ this.startListening()
115
+ })
116
+ this.agent = this.getAgent()
117
+ }
118
+
119
+ getAgent() {
120
+ try {
121
+ if (this.options.x509.certPath && this.options.x509.pkeyPath) {
122
+ return new https.Agent({
123
+ cert: cds.utils.fs.readFileSync(cds.utils.path.resolve(cds.root, this.options.x509.certPath)),
124
+ key: cds.utils.fs.readFileSync(cds.utils.path.resolve(cds.root, this.options.x509.pkeyPath))
125
+ })
126
+ }
127
+ } catch (error) {
128
+ if (this.LOG) this.LOG.error('GetCredentials', { error: error.message })
129
+ throw error
130
+ }
131
+ }
132
+
133
+ async handle(msg) {
134
+ if (msg.inbound) return super.handle(msg)
135
+ const _msg = this.message4(msg)
136
+ await this.emitToEventBroker(_msg)
137
+ }
138
+
139
+ async startListening() {
140
+ if (!this._listenToAll.value && !this.subscribedTopics.size) return
141
+ await this.registerWebhookEndpoints()
142
+ }
143
+
144
+ async emitToEventBroker(msg) {
145
+ // TODO: CSN definition probably not needed, just in case...
146
+ // See if there's a CSN entry for that event
147
+ // const found = cds?.model.definitions[topicOrEvent]
148
+ // if (found) return found // case for fully-qualified event name
149
+ // for (const def in cds.model?.definitions) {
150
+ // const definition = cds.model.definitions[def]
151
+ // if (definition['@topic'] === topicOrEvent) return definition
152
+ // }
153
+
154
+ // TODO: What if we're in single tenant variant?
155
+ try {
156
+ const ceSource = `${this.options.credentials.ceSource[0]}/${cds.context.tenant}`
157
+ const hostname = this.options.credentials.eventing.http.x509.url.replace(/^https?:\/\//, '')
158
+ // TODO Cloud Events Handler CAP
159
+ const options = {
160
+ hostname: hostname,
161
+ method: 'POST',
162
+ headers: {
163
+ 'ce-id': cds.utils.uuid(),
164
+ 'ce-source': ceSource,
165
+ 'ce-type': msg.event,
166
+ 'ce-specversion': '1.0',
167
+ 'Content-Type': 'application/json'
168
+ },
169
+ agent: this.agent
170
+ }
171
+ this.LOG.debug('HTTP headers:', JSON.stringify(options.headers))
172
+ this.LOG.debug('HTTP body:', JSON.stringify(msg.data))
173
+ await request(options, msg.data) // TODO: fetch does not work with mTLS as of today, requires another module. see https://github.com/nodejs/node/issues/48977
174
+ if (this.LOG._info) this.LOG.info('Emit', { topic: msg.event })
175
+ } catch (e) {
176
+ this.LOG.error('Emit failed:', e.message)
177
+ }
178
+ }
179
+
180
+ async registerWebhookEndpoints() {
181
+ const webhookBasePath = this.options.webhookPath || '/-/cds/event-broker/webhook'
182
+ cds.app.post(webhookBasePath, _validateCertificate.bind(this))
183
+ cds.app.post(webhookBasePath, express.json())
184
+ cds.app.post(webhookBasePath, this.onEventReceived.bind(this))
185
+ }
186
+
187
+ async onEventReceived(req, res) {
188
+ try {
189
+ const event = req.headers['ce-type'] // TG27: type contains namespace, so there's no collision
190
+ const tenant = req.headers['ce-sapconsumertenant']
191
+ const msg = {
192
+ inbound: true,
193
+ event,
194
+ tenant,
195
+ data: req.body ? req.body : undefined,
196
+ headers: req.headers
197
+ }
198
+ cds.context = { user: cds.User.privileged }
199
+ if (tenant) cds.context.tenant = tenant // TODO: In single tenant case, we don't need a tenant
200
+ const tx = await this.tx()
201
+ await tx.emit(msg)
202
+ this.LOG.debug('Event processed successfully.')
203
+ return res.status(200).json({ message: 'OK' })
204
+ } catch (e) {
205
+ this.LOG.error('ERROR during inbound event processing:', e) // TODO: How does Event Broker do error handling?
206
+ res.status(500).json({ message: 'Internal Server Error!' })
207
+ throw e
208
+ }
209
+ }
210
+ }
211
+
212
+ module.exports = EventBroker
@@ -3,8 +3,8 @@ const cds = require('../cds')
3
3
  const { run, getReqOptions } = require('./utils/client')
4
4
  const { getCloudSdk, getCloudSdkConnectivity, getCloudSdkResilience } = require('./utils/cloudSdkProvider')
5
5
  const { hasAliasedColumns } = require('./utils/data')
6
- const { resolveView, getTransition, restoreLink, findQueryTarget } = require('../common/utils/resolveView')
7
- const { postProcess } = require('../common/utils/postProcessing')
6
+ const { resolveView, getTransition, findQueryTarget } = require('../common/utils/resolveView')
7
+ const postProcess = require('../common/utils/postProcess')
8
8
  const { formatVal } = require('../../odata/utils')
9
9
 
10
10
  const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
@@ -268,51 +268,28 @@ class RemoteService extends cds.Service {
268
268
  })
269
269
  }
270
270
 
271
- // FIXME: This is a dirty hack for this situation:
272
- // - This PR has cds.Service.model setter to always consistently apply cds.compile.for.odata, also for RemoteServices, which wasn't the case before
273
- // - because of that tests/_runtime/remote/__tests__/integration/odata.test.js fails, which relies on the former behavior of RemoteServices
274
- // NOTE: that test would never have worked for RemoteServices bootstrapped from single cds.model, which is always cds.compiled.for.odata
275
- // REVISIT: should become obsolete with Universal CSN
276
- // set model(m) {
277
- // const fn = cds.compile.for.odata
278
- // try {
279
- // cds.compile.for.odata = m => m
280
- // super.model = m
281
- // } finally {
282
- // cds.compile.for.odata = fn
283
- // }
284
- // }
285
-
286
271
  // Overload .handle in order to resolve projections up to a definition that is known by the remote service instance.
287
272
  // Result is post processed according to the inverse projection in order to reflect the correct result of the original query.
288
273
  async handle(req) {
289
- // compat mode
290
- if (req._resolved || cds.env.features.resolve_views === false) return super.handle(req)
291
-
292
- if (req.target && req.target.name && this.definition && req.target.name.startsWith(this.definition.name + '.')) {
293
- const result = await super.handle(req)
274
+ if (req._resolved) return super.handle(req)
294
275
 
276
+ if (req.target?.name?.startsWith(this.definition?.name + '.')) {
277
+ let result = await super.handle(req)
295
278
  // only post process if alias was explicitly set in query
296
- if (_selectOnlyWithAlias(req.query)) {
297
- return postProcess(req.query, result, this, true)
298
- }
299
-
279
+ if (_selectOnlyWithAlias(req.query)) result = postProcess(req.query, result, this, true)
300
280
  return result
301
281
  }
302
282
 
303
283
  // req.query can be:
304
284
  // - empty object in case of unbound action/function
305
285
  // - undefined/null in case of plain string queries
306
- if (_isSimpleCqnQuery(req.query) && this.model) {
286
+ if (this.model && _isSimpleCqnQuery(req.query)) {
307
287
  const q = resolveView(req.query, this.model, this)
308
288
  const t = findQueryTarget(q) || req.target
309
289
 
310
- // compat
311
- restoreLink(req)
312
-
313
290
  // REVISIT: We need to provide target explicitly because it's cached already within ensure_target
314
- const newReq = new cds.Request({ query: q, target: t, headers: req.headers, _resolved: true, method: req.method })
315
- const result = await super.dispatch(newReq)
291
+ const _req = new cds.Request({ query: q, target: t, _resolved: true, headers: req.headers, method: req.method })
292
+ const result = await super.dispatch(_req)
316
293
  return postProcess(q, result, this, true)
317
294
  }
318
295
 
@@ -166,27 +166,17 @@ const TYPES_TO_REMOVE = { function: 1, object: 1 }
166
166
  const PROPS_TO_IGNORE = { cause: 1, name: 1 }
167
167
 
168
168
  const _getSanitizedError = (e, reqOptions, options = { suppressRemoteResponseBody: false, batchRequest: false }) => {
169
- e.request = {
169
+ const request = {
170
170
  method: reqOptions.method,
171
171
  url: e.config ? e.config.baseURL + e.config.url : reqOptions.url,
172
172
  headers: e.config ? e.config.headers : reqOptions.headers
173
173
  }
174
-
175
- if (options.batchRequest) {
176
- e.request.body = reqOptions.data
177
- }
174
+ if (options.batchRequest) request.body = reqOptions.data
175
+ e.request = request
178
176
 
179
177
  if (e.response) {
180
- const response = {
181
- status: e.response.status,
182
- statusText: e.response.statusText,
183
- headers: e.response.headers
184
- }
185
-
186
- if (e.response.data && !options.suppressRemoteResponseBody) {
187
- response.body = e.response.data
188
- }
189
-
178
+ const response = { status: e.response.status, statusText: e.response.statusText, headers: e.response.headers }
179
+ if (e.response.data && !options.suppressRemoteResponseBody) response.body = e.response.data
190
180
  e.response = response
191
181
  }
192
182
 
@@ -214,6 +204,11 @@ const _getSanitizedError = (e, reqOptions, options = { suppressRemoteResponseBod
214
204
  e = _e
215
205
  }
216
206
 
207
+ // AxiosError's toJSON() method doesn't include the request and response objects
208
+ e.toJSON = function () {
209
+ return { ...this.__proto__.toJSON(), request: this.request, response: this.response }
210
+ }
211
+
217
212
  return e
218
213
  }
219
214
 
@@ -232,7 +227,7 @@ const run = async (requestConfig, options) => {
232
227
  } catch (e) {
233
228
  // > axios received status >= 400 -> gateway error
234
229
  const msg = e?.response?.data?.error?.message?.value ?? e?.response?.data?.error?.message ?? e.message
235
- e.message = msg ? 'Error during request to remote service: \n' + msg : 'Request to remote service failed.'
230
+ e.message = msg ? 'Error during request to remote service: ' + msg : 'Request to remote service failed.'
236
231
  const sanitizedError = _getSanitizedError(e, requestConfig, { suppressRemoteResponseBody })
237
232
  const err = Object.assign(new Error(e.message), { statusCode: 502, reason: sanitizedError })
238
233
  LOG._warn && LOG.warn(err)
@@ -251,11 +246,8 @@ const run = async (requestConfig, options) => {
251
246
  const e = new Error("Received content-type 'text/html' which is not part of accepted content types")
252
247
  e.response = response
253
248
  const sanitizedError = _getSanitizedError(e, requestConfig, { suppressRemoteResponseBody })
254
- const err = Object.assign(new Error(`Error during request to remote service: ${e.message}`), {
255
- statusCode: 502,
256
- reason: sanitizedError
257
- })
258
-
249
+ const message = 'Error during request to remote service: ' + e.message
250
+ const err = Object.assign(new Error(message), { statusCode: 502, reason: sanitizedError })
259
251
  LOG._warn && LOG.warn(err)
260
252
  throw err
261
253
  }
@@ -28,8 +28,14 @@ const _convert = (refEntries, req) => {
28
28
  // only check refs in format {ref: ['assoc', 'id']}
29
29
  continue
30
30
  }
31
+ let element
32
+ if (req.target.elements[refEntry.ref[0]]) {
33
+ element = req.target.elements[refEntry.ref[0]]
34
+ } else if (req.target.elements[refEntry.ref[1]]?.isAssociation) {
35
+ // fallback: if first ref is not an element and second is an association, we assume first is an alias
36
+ element = req.target.elements[refEntry.ref[1]]
37
+ }
31
38
 
32
- const element = req.target.elements[refEntry.ref[0]]
33
39
  if (!element || !element.is2one) return
34
40
 
35
41
  _convertRefForAssocToOneManaged(element, refEntry)
@@ -302,7 +302,7 @@ function executeGenericCQN(model, dbc, cqn, user, locale, txTimestamp) {
302
302
 
303
303
  // REVISIT: consider deleting this function after removing stream_compat
304
304
  async function executeSelectStreamCQN({ model, dbc, query, user, locale, txTimestamp }) {
305
- const result = await executeSelectCQN(model, dbc, query, user, locale, txTimestamp)
305
+ let result = await executeSelectCQN(model, dbc, query, user, locale, txTimestamp)
306
306
 
307
307
  if (result == null || result.length === 0) {
308
308
  return
@@ -313,7 +313,9 @@ async function executeSelectStreamCQN({ model, dbc, query, user, locale, txTimes
313
313
  return result
314
314
  }
315
315
 
316
- let val = Array.isArray(result) ? Object.values(result[0])[0] : Object.values(result)[0]
316
+ // REVISIT: following code to be deleted after cds.env.features.stream_compat is removed
317
+ if (Array.isArray(result)) result = result[0]
318
+ let [key, val] = Object.entries(result)[0]
317
319
  if (val === null) {
318
320
  return null
319
321
  }
@@ -325,7 +327,10 @@ async function executeSelectStreamCQN({ model, dbc, query, user, locale, txTimes
325
327
  stream_.push(val)
326
328
  stream_.push(null)
327
329
 
328
- return { value: stream_ }
330
+ result.value = stream_
331
+ delete result[key]
332
+
333
+ return result
329
334
  }
330
335
 
331
336
  module.exports = {