@sap/cds 6.7.2 → 6.8.1

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 (93) hide show
  1. package/CHANGELOG.md +41 -1
  2. package/_i18n/i18n.properties +9 -6
  3. package/_i18n/i18n_ar.properties +6 -6
  4. package/_i18n/i18n_cs.properties +6 -6
  5. package/_i18n/i18n_da.properties +6 -6
  6. package/_i18n/i18n_de.properties +6 -6
  7. package/_i18n/i18n_en.properties +6 -6
  8. package/_i18n/i18n_es.properties +6 -6
  9. package/_i18n/i18n_fi.properties +6 -6
  10. package/_i18n/i18n_fr.properties +6 -6
  11. package/_i18n/i18n_hu.properties +6 -6
  12. package/_i18n/i18n_it.properties +6 -6
  13. package/_i18n/i18n_ja.properties +6 -6
  14. package/_i18n/i18n_ko.properties +6 -6
  15. package/_i18n/i18n_ms.properties +6 -6
  16. package/_i18n/i18n_nl.properties +6 -6
  17. package/_i18n/i18n_no.properties +6 -6
  18. package/_i18n/i18n_pl.properties +6 -6
  19. package/_i18n/i18n_pt.properties +6 -6
  20. package/_i18n/i18n_ro.properties +6 -6
  21. package/_i18n/i18n_ru.properties +6 -6
  22. package/_i18n/i18n_sv.properties +6 -6
  23. package/_i18n/i18n_th.properties +6 -6
  24. package/_i18n/i18n_tr.properties +8 -8
  25. package/_i18n/i18n_zh_CN.properties +3 -3
  26. package/_i18n/i18n_zh_TW.properties +6 -6
  27. package/apis/core.d.ts +30 -31
  28. package/apis/csn.d.ts +1 -1
  29. package/apis/ql.d.ts +69 -39
  30. package/apis/serve.d.ts +4 -3
  31. package/apis/services.d.ts +20 -7
  32. package/bin/build/buildTaskEngine.js +1 -1
  33. package/bin/build/index.js +1 -1
  34. package/bin/build/provider/buildTaskProviderInternal.js +9 -6
  35. package/bin/build/provider/hana/index.js +11 -4
  36. package/bin/build/provider/mtx-sidecar/index.js +3 -3
  37. package/bin/build/provider/nodejs/index.js +23 -0
  38. package/bin/version.js +3 -2
  39. package/common.cds +3 -2
  40. package/lib/auth/index.js +3 -0
  41. package/lib/auth/mocked-users.js +13 -0
  42. package/lib/compile/etc/_localized.js +3 -0
  43. package/lib/core/entities.js +7 -3
  44. package/lib/dbs/cds-deploy.js +36 -12
  45. package/lib/env/cds-env.js +47 -14
  46. package/lib/env/cds-requires.js +16 -7
  47. package/lib/env/defaults.js +2 -2
  48. package/lib/env/schemas/cds-rc.json +1 -8
  49. package/lib/index.js +1 -1
  50. package/lib/ql/STREAM.js +89 -0
  51. package/lib/ql/cds-ql.js +2 -1
  52. package/lib/req/request.js +5 -2
  53. package/lib/req/user.js +1 -1
  54. package/lib/srv/middlewares/index.js +9 -7
  55. package/lib/srv/middlewares/trace.js +6 -5
  56. package/lib/srv/srv-api.js +1 -0
  57. package/lib/utils/cds-utils.js +1 -1
  58. package/lib/utils/tar.js +30 -31
  59. package/libx/_runtime/audit/Service.js +96 -37
  60. package/libx/_runtime/audit/generic/personal/utils.js +26 -13
  61. package/libx/_runtime/audit/utils/v2.js +21 -22
  62. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  63. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  64. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -3
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +2 -0
  67. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -1
  68. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +10 -3
  69. package/libx/_runtime/cds-services/services/Service.js +2 -7
  70. package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
  71. package/libx/_runtime/common/aspects/any.js +1 -1
  72. package/libx/_runtime/common/generic/auth/utils.js +30 -41
  73. package/libx/_runtime/common/i18n/messages.properties +1 -1
  74. package/libx/_runtime/db/expand/expandCQNToJoin.js +19 -17
  75. package/libx/_runtime/db/expand/rawToExpanded.js +3 -5
  76. package/libx/_runtime/db/utils/generateAliases.js +1 -1
  77. package/libx/_runtime/fiori/generic/activate.js +1 -1
  78. package/libx/_runtime/fiori/generic/before.js +18 -19
  79. package/libx/_runtime/fiori/generic/prepare.js +1 -1
  80. package/libx/_runtime/fiori/generic/read.js +1 -1
  81. package/libx/_runtime/fiori/lean-draft.js +8 -6
  82. package/libx/_runtime/fiori/utils/handler.js +0 -6
  83. package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +1 -1
  84. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +0 -5
  85. package/libx/_runtime/hana/pool.js +26 -18
  86. package/libx/_runtime/hana/search2Contains.js +1 -1
  87. package/libx/_runtime/hana/search2cqn4sql.js +26 -18
  88. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +23 -16
  89. package/libx/_runtime/messaging/outbox/utils.js +6 -1
  90. package/libx/_runtime/remote/Service.js +64 -38
  91. package/libx/_runtime/remote/utils/client.js +13 -9
  92. package/libx/rest/middleware/read.js +2 -1
  93. package/package.json +1 -1
@@ -5,21 +5,7 @@ const { isContainsPredicateSupported, search2Contains } = require('./search2Cont
5
5
  const { addAliasToExpression } = require('../db/utils/generateAliases')
6
6
  const targetAlias = 'Target'
7
7
  const textsAlias = 'Texts'
8
- const _generateKeysWhereCondition = (entity, alias1, alias2) => {
9
- const keys = Object.keys(entity.keys).filter(k => !entity.keys[k].isAssociation && !entity.keys[k].virtual)
10
- const where = []
11
- keys.forEach(key => {
12
- if (where.length > 0) where.push('and')
13
- where.push({ ref: [alias1, key] }, '=', { ref: [alias2, key] })
14
- })
15
- return where
16
- }
17
- const _generateContainsColumns = (columns, entity) => {
18
- const columnsTarget = addAliasToExpression(columns, targetAlias)
19
- const columns2SearchText = columns.filter(col => col.ref && entity.elements[col.ref[col.ref.length - 1]].localized)
20
- const columnsText = addAliasToExpression(columns2SearchText, textsAlias)
21
- return [...columnsTarget, ...columnsText]
22
- }
8
+
23
9
  /**
24
10
  * Computes a CQN expression for a search query.
25
11
  *
@@ -40,11 +26,12 @@ const _generateContainsColumns = (columns, entity) => {
40
26
  const search2cqn4sql = (query, entity, options) => {
41
27
  const cqnSearchPhrase = query.SELECT.search
42
28
  if (!cqnSearchPhrase) return query
43
-
44
29
  const localizedAssociation = entity.associations?.localized
30
+
45
31
  if (localizedAssociation) {
46
32
  let { columns: columns2Search = computeColumnsToBeSearched(query, entity), locale } = options
47
33
  const viewAlias = query.SELECT.from.as ? query.SELECT.from.as : 'LocalizedView'
34
+
48
35
  if (!query.SELECT.from.as) {
49
36
  _addAliasToQuery(query, viewAlias)
50
37
  }
@@ -56,14 +43,17 @@ const search2cqn4sql = (query, entity, options) => {
56
43
 
57
44
  // left outer join the target table with the _texts table (the _texts table contains the translated texts)
58
45
  subQuery.leftJoin(localizedAssociation.target, textsAlias).on(onCondition)
46
+
59
47
  // add condition for equal keys of target table and localized view
60
48
  subQuery.where(_generateKeysWhereCondition(entity, targetAlias, viewAlias))
61
49
  const containsColumns = _generateContainsColumns(columns2Search, entity)
50
+
62
51
  let expression
52
+
63
53
  if (isContainsPredicateSupported(query, entity, columns2Search)) {
64
54
  // generate CQN expression with `CONTAINS` predicate for the columns from the target and text table
65
55
  expression = search2Contains(cqnSearchPhrase, containsColumns)
66
- Object.defineProperty(subQuery, '_$searchUsingContains', { value: true, enumerable: true })
56
+ Object.defineProperty(expression, 'searchUsingContains', { value: true, enumerable: true })
67
57
  } else {
68
58
  expression = searchToLike(cqnSearchPhrase, containsColumns)
69
59
  }
@@ -73,11 +63,29 @@ const search2cqn4sql = (query, entity, options) => {
73
63
  // suppress the localize handler from redirecting the subQuery's target to the localized view
74
64
  Object.defineProperty(subQuery, '_suppressLocalization', { value: true })
75
65
  query.where('exists', subQuery)
76
-
77
66
  return query
78
67
  }
79
68
  }
80
69
 
70
+ const _generateKeysWhereCondition = (entity, alias1, alias2) => {
71
+ const keys = Object.keys(entity.keys).filter(key => !entity.keys[key].isAssociation && !entity.keys[key].virtual)
72
+ const where = []
73
+
74
+ keys.forEach(key => {
75
+ if (where.length > 0) where.push('and')
76
+ where.push({ ref: [alias1, key] }, '=', { ref: [alias2, key] })
77
+ })
78
+
79
+ return where
80
+ }
81
+
82
+ const _generateContainsColumns = (columns, entity) => {
83
+ const columnsTarget = addAliasToExpression(columns, targetAlias)
84
+ const columns2SearchText = columns.filter(col => col.ref && entity.elements[col.ref[col.ref.length - 1]].localized)
85
+ const columnsText = addAliasToExpression(columns2SearchText, textsAlias)
86
+ return [...columnsTarget, ...columnsText]
87
+ }
88
+
81
89
  const _addAliasToQuery = (query, alias) => {
82
90
  const SELECT = query.SELECT
83
91
  SELECT.from.as = alias
@@ -2,8 +2,9 @@ const cds = require('../../cds.js')
2
2
  // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
3
3
  const express = require('express')
4
4
  const getTenantInfo = require('./getTenantInfo.js')
5
- const isSecured = () => cds.requires.auth && cds.requires.auth.credentials
6
- const { getTenant } = require('../../auth/strategies/xssecUtils')
5
+ const isSecured = () => cds.requires.auth && (cds.requires.auth.impl || cds.requires.auth.credentials)
6
+ const _require = require('../../common/utils/require')
7
+ const { UNAUTHORIZED } = require('../../auth/utils')
7
8
 
8
9
  const _isAll = a => a && a.includes('all')
9
10
 
@@ -14,18 +15,25 @@ class EndpointRegistry {
14
15
  this.webhookCallbacks = new Map()
15
16
  this.deployCallbacks = new Map()
16
17
  if (isSecured()) {
17
- const JWTStrategy = require('../../auth/strategies/JWT.js')
18
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
19
- const passport = require('passport')
20
- // REVISIT: It's unclear if the credentials from cds.requires.auth need to be used here.
21
- // In principle, user-facing endpoints might differ from messaging ones.
22
- passport.use(new JWTStrategy(cds.requires.auth.credentials))
18
+ if (cds.requires.auth.impl) {
19
+ const impl = _require(cds.resolve(cds.requires.auth.impl))
20
+ paths.forEach(path => cds.app.use(path, impl))
21
+ } else {
22
+ const JWTStrategy = require('../../auth/strategies/JWT.js')
23
+ // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
24
+ const passport = _require('passport')
25
+ // REVISIT: It's unclear if the credentials from cds.requires.auth need to be used here.
26
+ // In principle, user-facing endpoints might differ from messaging ones.
27
+ passport.use(new JWTStrategy(cds.requires.auth.credentials))
28
+ paths.forEach(path => {
29
+ cds.app.use(path, passport.initialize())
30
+ cds.app.use(path, passport.authenticate('JWT', { session: false }))
31
+ })
32
+ }
33
+ // unsuccessful auth doesn't automatically reject!
23
34
  paths.forEach(path => {
24
- cds.app.use(path, passport.initialize())
25
- cds.app.use(path, passport.authenticate('JWT', { session: false }))
26
35
  cds.app.use(path, (req, res, next) => {
27
- // unsuccessful auth doesn't automatically reject!
28
- if (!req.user) return res.status(401).json({ message: 'Unauthorized' })
36
+ if (!req.user) res.status(401).json(UNAUTHORIZED)
29
37
  next()
30
38
  })
31
39
  })
@@ -63,17 +71,16 @@ class EndpointRegistry {
63
71
  try {
64
72
  if (isSecured() && !req.user.is('emcallback')) return res.sendStatus(403)
65
73
  const queueName = req.query.q
66
- const authInfo = req.authInfo
67
74
  const xAddress = req.headers['x-address']
68
75
  const topic = xAddress && xAddress.match(/^topic:(.*)/)[1]
69
76
  const payload = req.body
70
77
  const cb = this.webhookCallbacks.get(queueName)
71
78
  if (!topic || !payload || !queueName || !cb) return res.sendStatus(200)
72
- const tenantId = getTenant(authInfo)
73
- const other = authInfo
79
+ const tenant = req.tenant || req.user.tenant
80
+ const other = tenant
74
81
  ? {
75
82
  _: { req, res }, // For `cds.context.http`
76
- tenant: tenantId
83
+ tenant
77
84
  }
78
85
  : {}
79
86
  if (!cb) return res.sendStatus(200)
@@ -15,6 +15,11 @@ const outboxRunner = new OutboxRunner()
15
15
  const cdsUser = 'cds.internal.user'
16
16
  const messageProcessorRegistered = Symbol('message processor registered')
17
17
 
18
+ const _get100NanosecondTimestampISOString = () => {
19
+ const [now, nanoseconds] = [new Date(), process.hrtime()[1]]
20
+ return now.toISOString().replace('Z', `${nanoseconds}`.padStart(9, '0').substring(3, 7) + 'Z')
21
+ }
22
+
18
23
  const _getMessagesEntity = () => {
19
24
  const messagesDbName = 'cds.outbox.Messages'
20
25
  const messagesEntity = cds.model.definitions[messagesDbName]
@@ -221,7 +226,7 @@ const _createMessage = (name, msg, context) => {
221
226
  const outboxMsg = {
222
227
  ID: cds.utils.uuid(),
223
228
  target: name,
224
- timestamp: new Date().toISOString(), // needs to be different for each emit
229
+ timestamp: _get100NanosecondTimestampISOString(), // needs to be different for each emit
225
230
  msg: JSON.stringify(_msg)
226
231
  }
227
232
  return outboxMsg
@@ -7,6 +7,14 @@ const { getKind, run, getDestination, getAdditionalOptions, getReqOptions } = re
7
7
  const { formatVal } = require('../../odata/utils')
8
8
  const { hasAliasedColumns } = require('./utils/data')
9
9
 
10
+ let _cloudSdkConnectivity
11
+ const cloudSdkConnectivity = () => {
12
+ if (_cloudSdkConnectivity) return _cloudSdkConnectivity
13
+ // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
14
+ _cloudSdkConnectivity = require('@sap-cloud-sdk/connectivity')
15
+ return _cloudSdkConnectivity
16
+ }
17
+
10
18
  const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
11
19
 
12
20
  const _setHeaders = (defaultHeaders, req) => {
@@ -18,6 +26,7 @@ const _setHeaders = (defaultHeaders, req) => {
18
26
  }, {})
19
27
  )
20
28
  }
29
+
21
30
  const _setCorrectValue = (el, data, params, kind) => {
22
31
  return typeof data[el] === 'object' && kind !== 'odata-v2'
23
32
  ? JSON.stringify(data[el])
@@ -151,49 +160,68 @@ const resolvedTargetOfQuery = q => {
151
160
  const transitions = (typeof q === 'object' && (q.SELECT || q.INSERT || q.UPDATE || q.DELETE)._transitions) || []
152
161
  return transitions.length && [transitions.length - 1].target
153
162
  }
163
+
154
164
  let logged
155
165
  let sdkLoggerDisabled
166
+
167
+ const _resolveSelectionStrategy = options => {
168
+ if (typeof options?.selectionStrategy !== 'string') return
169
+ options.selectionStrategy = cloudSdkConnectivity().DestinationSelectionStrategies[options.selectionStrategy]
170
+
171
+ if (typeof options?.selectionStrategy !== 'function') {
172
+ throw new Error(`Unsupported destination selection strategy "${options.selectionStrategy}".`)
173
+ }
174
+ }
175
+
156
176
  class RemoteService extends cds.Service {
157
177
  init() {
158
- if (!this.options.credentials) {
159
- throw new Error(`No credentials configured for "${this.name}".`)
160
- }
178
+ this.kind = getKind(this.options) // TODO: Simplify
161
179
 
162
- this.datasource = this.options.datasource
163
- this.destinationOptions = this.options.destinationOptions
164
- this.destination =
165
- this.options.credentials.destination ||
166
- getDestination((this.definition && this.definition.name) || this.datasource, this.options.credentials)
167
- this.requestTimeout = this.options.credentials.requestTimeout
168
- if (this.requestTimeout == null) this.requestTimeout = 60000
169
- if (cds.env.features.fetch_csrf && !logged) {
170
- // for logging once for all remote services
171
- logged = true
172
- LOG._warn &&
173
- LOG.warn(
174
- 'Configuration option "cds.env.features.fetch_csrf" is deprecated.\n Please use "csrf"/"csrfInBatch" as described in https://cap.cloud.sap/docs/node.js/remote-services'
175
- )
176
- }
180
+ /*
181
+ * set up connectivity stuff if credentials are provided
182
+ * throw error if no credentials are provided and the service has at least one entity or one action/function
183
+ */
184
+ if (this.options.credentials) {
185
+ this.datasource = this.options.datasource
186
+ this.destinationOptions = this.options.destinationOptions
187
+ _resolveSelectionStrategy(this.destinationOptions)
188
+ this.destination =
189
+ this.options.credentials.destination ??
190
+ getDestination(this.definition?.name ?? this.datasource, this.options.credentials)
191
+ this.path = this.options.credentials.path
192
+
193
+ this.requestTimeout = this.options.credentials.requestTimeout
194
+ if (this.requestTimeout == null) this.requestTimeout = 60000
195
+
196
+ // REVISIT: remove cds.env.features.fetch_csrf in next major ^7
197
+ this.csrf = cds.env.features.fetch_csrf ?? this.options.csrf
198
+ this.csrfInBatch = this.options.csrfInBatch
199
+ if (cds.env.features.fetch_csrf && !logged) {
200
+ // for logging once for all remote services
201
+ logged = true
202
+ LOG._warn &&
203
+ LOG.warn(
204
+ 'Configuration option "cds.env.features.fetch_csrf" is deprecated.\n Please use "csrf"/"csrfInBatch" as described in https://cap.cloud.sap/docs/node.js/remote-services'
205
+ )
206
+ }
177
207
 
178
- // REVISIT: use cds.log's logger in cloud sdk
179
-
180
- // disable sdk logger if not in debug mode
181
- if (!LOG._debug && !sdkLoggerDisabled) {
182
- try {
183
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
184
- const sdkUtils = require('@sap-cloud-sdk/util')
185
- sdkUtils.setGlobalLogLevel('error')
186
- // disable sdk logger once
187
- sdkLoggerDisabled = true
188
- } catch (err) {
189
- /* might fail in cds repl due to winston's exception handler, see cap/issues#10134 */
208
+ // REVISIT: use cds.log's logger in cloud sdk
209
+
210
+ // disable sdk logger if not in debug mode
211
+ if (!LOG._debug && !sdkLoggerDisabled) {
212
+ try {
213
+ // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
214
+ const sdkUtils = require('@sap-cloud-sdk/util')
215
+ sdkUtils.setGlobalLogLevel('error')
216
+ // disable sdk logger once
217
+ sdkLoggerDisabled = true
218
+ } catch (err) {
219
+ /* might fail in cds repl due to winston's exception handler, see cap/issues#10134 */
220
+ }
190
221
  }
222
+ } else if ([...this.entities].length || [...this.operations].length) {
223
+ throw new Error(`No credentials configured for "${this.name}".`)
191
224
  }
192
- // REVISIT: remove cds.env.features.fetch_csrf in next major ^7
193
- this.csrf = cds.env.features.fetch_csrf || this.options.csrf
194
- this.csrfInBatch = this.options.csrfInBatch
195
- this.path = this.options.credentials.path
196
- this.kind = getKind(this.options) // TODO: Simplify
197
225
 
198
226
  const clearKeysFromData = function (req) {
199
227
  if (req.target && req.target.keys) for (const k of Object.keys(req.target.keys)) delete req.data[k]
@@ -235,9 +263,7 @@ class RemoteService extends cds.Service {
235
263
  }
236
264
 
237
265
  let result = await run(reqOptions, additionalOptions)
238
-
239
- result =
240
- typeof query === 'object' && query.SELECT && query.SELECT.one && Array.isArray(result) ? result[0] : result
266
+ result = typeof query === 'object' && query.SELECT?.one && Array.isArray(result) ? result[0] : result
241
267
 
242
268
  return result
243
269
  })
@@ -27,14 +27,16 @@ const _executeHttpRequest = async ({ requestConfig, destination, destinationOpti
27
27
  const destinationName = typeof destination === 'string' && destination
28
28
 
29
29
  if (destinationName) {
30
- destination = { destinationName, ...(resolveDestinationOptions(destinationOptions, jwt) || {}) }
30
+ destination = { destinationName, ...(resolveDestinationOptions(destinationOptions, jwt) ?? {}) }
31
31
  } else if (destination.forwardAuthToken) {
32
32
  destination = {
33
33
  ...destination,
34
34
  headers: destination.headers ? { ...destination.headers } : {},
35
35
  authentication: 'NoAuthentication'
36
36
  }
37
+
37
38
  delete destination.forwardAuthToken
39
+
38
40
  if (jwt) {
39
41
  destination.headers.authorization = `Bearer ${jwt}`
40
42
  } else {
@@ -93,12 +95,12 @@ const getDestination = (name, credentials) => {
93
95
  * @returns {import('@sap-cloud-sdk/connectivity').DestinationFetchOptions}
94
96
  */
95
97
  const resolveDestinationOptions = function (options, jwt) {
96
- if (!options && !jwt) return undefined
98
+ if (!options && !jwt) return
97
99
 
98
- const resolvedOptions = Object.assign({}, options || {})
100
+ const resolvedOptions = Object.assign({}, options ?? {})
99
101
  resolvedOptions.jwt = jwt
100
102
 
101
- if (options && options.selectionStrategy) {
103
+ if (options?.selectionStrategy) {
102
104
  resolvedOptions.selectionStrategy = options.selectionStrategy
103
105
  }
104
106
 
@@ -388,12 +390,10 @@ const run = async (
388
390
  }
389
391
 
390
392
  const getJwt = req => {
391
- const headers = req && req.context && req.context.headers
392
- if (headers && headers.authorization) {
393
+ const headers = req?.context?.headers
394
+ if (headers?.authorization) {
393
395
  const token = headers.authorization.match(/^bearer (.+)/i)
394
- if (token) {
395
- return token[1]
396
- }
396
+ if (token) return token[1]
397
397
  }
398
398
 
399
399
  return null
@@ -467,6 +467,10 @@ const getReqOptions = (req, query, service) => {
467
467
  ? _stringToReqOptions(query, req.data, req.target)
468
468
  : _pathToReqOptions(req.method, req.path, req.data, req.target)
469
469
 
470
+ if (service.kind === 'odata-v2' && req.event === 'READ' && reqOptions.url?.match(/(\/any\()|(\/all\()/)) {
471
+ req.reject(501, 'Lambda expressions are not supported in OData v2')
472
+ }
473
+
470
474
  reqOptions.headers = { accept: 'application/json,text/plain' }
471
475
  reqOptions.timeout = service.requestTimeout
472
476
 
@@ -16,13 +16,14 @@ module.exports = async (_req, _res, next) => {
16
16
  result = await srv.dispatch(req)
17
17
 
18
18
  // 204 or 404?
19
- if (result === null && query.SELECT.one) {
19
+ if (result == null && query.SELECT.one) {
20
20
  if (_target.ref.length > 1) status = 204
21
21
  else throw { code: 404 }
22
22
  }
23
23
  } catch (e) {
24
24
  return next(e)
25
25
  }
26
+
26
27
  // compat for mtx returning strings instead of objects
27
28
  if (typeof result === 'object' && result !== null && '$count' in result) {
28
29
  result = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "6.7.2",
3
+ "version": "6.8.1",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [