@sap/cds 7.9.4 → 8.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.
Files changed (276) hide show
  1. package/CHANGELOG.md +128 -3659
  2. package/_i18n/i18n_en_US_saptrc.properties +113 -0
  3. package/_i18n/i18n_zh_CN.properties +7 -4
  4. package/app/index.css +129 -0
  5. package/app/index.html +16 -64
  6. package/app/index.js +14 -9
  7. package/bin/args.js +34 -0
  8. package/bin/serve.js +18 -24
  9. package/bin/test.js +97 -0
  10. package/common.cds +5 -12
  11. package/eslint.config.mjs +133 -0
  12. package/lib/auth/basic-auth.js +16 -20
  13. package/lib/auth/dummy-auth.js +1 -1
  14. package/lib/auth/ias-auth.js +9 -41
  15. package/lib/auth/index.js +1 -14
  16. package/lib/auth/jwt-auth.js +10 -40
  17. package/lib/compile/cds-compile.js +1 -2
  18. package/lib/compile/cdsc.js +21 -26
  19. package/lib/compile/etc/_localized.js +1 -6
  20. package/lib/compile/etc/csv.js +1 -1
  21. package/lib/compile/etc/properties.js +1 -1
  22. package/lib/compile/for/java.js +1 -1
  23. package/lib/compile/for/lean_drafts.js +4 -6
  24. package/lib/compile/for/nodejs.js +1 -1
  25. package/lib/compile/parse.js +4 -0
  26. package/lib/compile/resolve.js +4 -4
  27. package/lib/compile/to/edm-files.js +16 -23
  28. package/lib/compile/to/hana.js +27 -0
  29. package/lib/compile/to/json.js +1 -1
  30. package/lib/compile/to/sql.js +5 -1
  31. package/lib/compile/to/yaml.js +3 -3
  32. package/lib/dbs/cds-deploy.js +4 -2
  33. package/lib/env/cds-env.js +10 -14
  34. package/lib/env/cds-requires.js +30 -13
  35. package/lib/env/defaults.js +46 -16
  36. package/lib/env/plugins.js +1 -1
  37. package/lib/env/schemas/cds-rc.js +8 -4
  38. package/lib/env/schemas/index.js +7 -7
  39. package/lib/env/serviceBindings.js +1 -1
  40. package/lib/index.js +12 -10
  41. package/lib/lazy.js +1 -1
  42. package/lib/linked/classes.js +36 -8
  43. package/lib/linked/entities.js +2 -10
  44. package/lib/linked/models.js +2 -1
  45. package/lib/linked/validate.js +292 -0
  46. package/lib/log/cds-error.js +0 -6
  47. package/lib/log/cds-log.js +3 -3
  48. package/lib/log/format/json.js +1 -1
  49. package/lib/log/service/index.js +0 -1
  50. package/lib/plugins.js +2 -2
  51. package/lib/ql/Query.js +2 -10
  52. package/lib/ql/SELECT.js +1 -1
  53. package/lib/ql/Whereable.js +3 -2
  54. package/lib/req/cds-context.js +14 -25
  55. package/lib/req/context.js +23 -25
  56. package/lib/req/request.js +1 -34
  57. package/lib/req/user.js +47 -35
  58. package/lib/srv/bindings.js +1 -1
  59. package/lib/srv/cds-connect.js +4 -4
  60. package/lib/srv/cds-serve.js +2 -2
  61. package/lib/srv/factory.js +1 -1
  62. package/lib/srv/middlewares/cds-context.js +11 -22
  63. package/lib/srv/middlewares/ctx-model.js +2 -3
  64. package/lib/srv/middlewares/errors.js +41 -8
  65. package/lib/srv/middlewares/index.js +3 -3
  66. package/lib/srv/middlewares/trace.js +0 -2
  67. package/lib/srv/protocols/hcql.js +15 -10
  68. package/lib/srv/protocols/http.js +44 -49
  69. package/lib/srv/protocols/index.js +1 -23
  70. package/lib/srv/protocols/odata-v4.js +12 -74
  71. package/lib/srv/protocols/rest.js +1 -13
  72. package/lib/srv/srv-api.js +0 -20
  73. package/lib/srv/srv-dispatch.js +3 -2
  74. package/lib/srv/srv-handlers.js +22 -11
  75. package/lib/srv/srv-methods.js +2 -2
  76. package/lib/srv/srv-models.js +3 -36
  77. package/lib/test/expect.js +343 -0
  78. package/lib/test/index.js +2 -0
  79. package/lib/test/reporter.js +176 -0
  80. package/lib/utils/axios.js +10 -9
  81. package/lib/utils/cds-test.js +85 -36
  82. package/lib/utils/cds-utils.js +54 -7
  83. package/lib/utils/check-version.js +0 -4
  84. package/lib/utils/colors.js +49 -0
  85. package/lib/utils/data.js +5 -4
  86. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -7
  87. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +3 -30
  88. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +6 -12
  89. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
  90. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -1
  91. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -7
  92. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +12 -6
  93. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +2 -4
  94. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +1 -0
  95. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  96. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  97. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -3
  98. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  99. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +1 -2
  100. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -0
  101. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
  102. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +9 -43
  103. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +0 -1
  104. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +8 -3
  105. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +4 -2
  106. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -3
  107. package/libx/_runtime/cds-services/util/assert.js +1 -1
  108. package/libx/_runtime/cds.js +10 -3
  109. package/libx/_runtime/common/Service.js +12 -32
  110. package/libx/_runtime/common/aspects/any.js +1 -0
  111. package/libx/_runtime/common/code-ext/execute.js +1 -1
  112. package/libx/_runtime/common/code-ext/worker.js +0 -1
  113. package/libx/_runtime/common/composition/data.js +0 -1
  114. package/libx/_runtime/common/composition/delete.js +0 -1
  115. package/libx/_runtime/common/composition/tree.js +0 -1
  116. package/libx/_runtime/common/composition/update.js +3 -3
  117. package/libx/_runtime/common/error/frontend.js +21 -12
  118. package/libx/_runtime/common/error/log.js +36 -0
  119. package/libx/_runtime/common/error/utils.js +2 -5
  120. package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
  121. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  122. package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
  123. package/libx/_runtime/common/generic/auth/restrict.js +23 -42
  124. package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
  125. package/libx/_runtime/common/generic/auth/utils.js +91 -88
  126. package/libx/_runtime/common/generic/crud.js +6 -5
  127. package/libx/_runtime/common/generic/etag.js +7 -12
  128. package/libx/_runtime/common/generic/input.js +70 -68
  129. package/libx/_runtime/common/generic/paging.js +1 -0
  130. package/libx/_runtime/common/generic/sorting.js +1 -0
  131. package/libx/_runtime/common/generic/temporal.js +8 -2
  132. package/libx/_runtime/common/i18n/index.js +1 -1
  133. package/libx/_runtime/common/i18n/messages.properties +3 -1
  134. package/libx/_runtime/common/utils/binary.js +8 -2
  135. package/libx/_runtime/common/utils/compareJson.js +5 -1
  136. package/libx/_runtime/common/utils/copy.js +6 -11
  137. package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
  138. package/libx/_runtime/common/utils/differ.js +3 -6
  139. package/libx/_runtime/common/utils/keys.js +77 -18
  140. package/libx/_runtime/common/utils/postProcess.js +12 -15
  141. package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
  142. package/libx/_runtime/common/utils/resolveView.js +2 -3
  143. package/libx/_runtime/common/utils/restrictions.js +45 -17
  144. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
  145. package/libx/_runtime/common/utils/stream.js +3 -16
  146. package/libx/_runtime/common/utils/streamProp.js +8 -18
  147. package/libx/_runtime/common/utils/structured.js +1 -1
  148. package/libx/_runtime/common/utils/ucsn.js +0 -2
  149. package/libx/_runtime/db/Service.js +0 -72
  150. package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
  151. package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
  152. package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
  153. package/libx/_runtime/db/generic/input.js +3 -8
  154. package/libx/_runtime/db/generic/rewrite.js +1 -0
  155. package/libx/_runtime/db/query/read.js +2 -2
  156. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
  157. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  158. package/libx/_runtime/db/utils/columns.js +2 -6
  159. package/libx/_runtime/fiori/lean-draft.js +138 -56
  160. package/libx/_runtime/hana/Service.js +0 -1
  161. package/libx/_runtime/hana/driver.js +1 -1
  162. package/libx/_runtime/hana/dynatrace.js +1 -2
  163. package/libx/_runtime/hana/pool.js +11 -21
  164. package/libx/_runtime/hana/streaming.js +0 -1
  165. package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
  166. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  167. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
  168. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
  169. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
  170. package/libx/_runtime/messaging/event-broker.js +54 -27
  171. package/libx/_runtime/messaging/file-based.js +3 -3
  172. package/libx/_runtime/messaging/http-utils/token.js +1 -1
  173. package/libx/_runtime/messaging/kafka.js +2 -2
  174. package/libx/_runtime/messaging/redis-messaging.js +0 -1
  175. package/libx/_runtime/remote/Service.js +25 -25
  176. package/libx/_runtime/remote/utils/client.js +4 -5
  177. package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
  178. package/libx/_runtime/remote/utils/data.js +0 -1
  179. package/libx/_runtime/sqlite/Service.js +1 -2
  180. package/libx/_runtime/ucl/Service.js +37 -78
  181. package/libx/common/assert/index.js +22 -21
  182. package/libx/common/assert/type-relaxed.js +39 -0
  183. package/libx/common/assert/utils.js +3 -2
  184. package/libx/common/assert/validation.js +3 -8
  185. package/libx/common/utils/index.js +5 -0
  186. package/libx/common/utils/path.js +51 -0
  187. package/libx/odata/ODataAdapter.js +126 -0
  188. package/libx/odata/index.js +15 -2
  189. package/libx/odata/middleware/batch.js +320 -84
  190. package/libx/odata/middleware/body-parser.js +33 -0
  191. package/libx/odata/middleware/create.js +44 -59
  192. package/libx/odata/middleware/delete.js +23 -12
  193. package/libx/odata/middleware/error.js +30 -6
  194. package/libx/odata/middleware/metadata.js +38 -26
  195. package/libx/odata/middleware/operation.js +93 -69
  196. package/libx/odata/middleware/parse.js +6 -8
  197. package/libx/odata/middleware/read.js +117 -93
  198. package/libx/odata/middleware/service-document.js +22 -19
  199. package/libx/odata/middleware/stream.js +54 -56
  200. package/libx/odata/middleware/update.js +79 -87
  201. package/libx/odata/parse/afterburner.js +191 -175
  202. package/libx/odata/parse/cqn2odata.js +5 -5
  203. package/libx/odata/parse/grammar.peggy +27 -20
  204. package/libx/odata/parse/multipartToJson.js +17 -9
  205. package/libx/odata/parse/parser.js +1 -1
  206. package/libx/odata/utils/etag.js +14 -6
  207. package/libx/odata/utils/index.js +84 -12
  208. package/libx/odata/utils/metadata.js +161 -0
  209. package/libx/odata/utils/postProcess.js +89 -0
  210. package/libx/odata/utils/readAfterWrite.js +134 -17
  211. package/libx/odata/utils/result.js +36 -142
  212. package/libx/outbox/index.js +4 -3
  213. package/libx/rest/RestAdapter.js +115 -182
  214. package/libx/rest/middleware/create.js +28 -24
  215. package/libx/rest/middleware/delete.js +7 -10
  216. package/libx/rest/middleware/error.js +26 -16
  217. package/libx/rest/middleware/operation.js +48 -41
  218. package/libx/rest/middleware/parse.js +128 -126
  219. package/libx/rest/middleware/read.js +20 -27
  220. package/libx/rest/middleware/update.js +26 -31
  221. package/package.json +17 -8
  222. package/server.js +4 -2
  223. package/apis/cds.d.ts +0 -3
  224. package/apis/core.d.ts +0 -21
  225. package/apis/cqn.d.ts +0 -18
  226. package/apis/csn.d.ts +0 -21
  227. package/apis/events.d.ts +0 -18
  228. package/apis/internal/inference.d.ts +0 -18
  229. package/apis/linked.d.ts +0 -18
  230. package/apis/log.d.ts +0 -20
  231. package/apis/models.d.ts +0 -18
  232. package/apis/ql.d.ts +0 -18
  233. package/apis/reflect.d.ts +0 -32
  234. package/apis/server.d.ts +0 -18
  235. package/apis/services.d.ts +0 -22
  236. package/bin/cds-serve.js +0 -56
  237. package/lib/compile/to/gql.js +0 -15
  238. package/lib/srv/protocols/_legacy.js +0 -44
  239. package/lib/utils/jest.js +0 -43
  240. package/libx/_runtime/auth/index.js +0 -193
  241. package/libx/_runtime/auth/strategies/JWT.js +0 -37
  242. package/libx/_runtime/auth/strategies/basic.js +0 -20
  243. package/libx/_runtime/auth/strategies/dummy.js +0 -14
  244. package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
  245. package/libx/_runtime/auth/strategies/mock.js +0 -77
  246. package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
  247. package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
  248. package/libx/_runtime/common/perf/index.js +0 -19
  249. package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
  250. package/libx/_runtime/fiori/draft.js +0 -2
  251. package/libx/_runtime/fiori/generic/activate.js +0 -190
  252. package/libx/_runtime/fiori/generic/before.js +0 -201
  253. package/libx/_runtime/fiori/generic/cancel.js +0 -19
  254. package/libx/_runtime/fiori/generic/delete.js +0 -21
  255. package/libx/_runtime/fiori/generic/edit.js +0 -157
  256. package/libx/_runtime/fiori/generic/index.js +0 -25
  257. package/libx/_runtime/fiori/generic/new.js +0 -82
  258. package/libx/_runtime/fiori/generic/patch.js +0 -101
  259. package/libx/_runtime/fiori/generic/prepare.js +0 -57
  260. package/libx/_runtime/fiori/generic/read.js +0 -1340
  261. package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
  262. package/libx/_runtime/fiori/utils/csn.js +0 -13
  263. package/libx/_runtime/fiori/utils/delete.js +0 -114
  264. package/libx/_runtime/fiori/utils/handler.js +0 -264
  265. package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
  266. package/libx/_runtime/fiori/utils/req.js +0 -23
  267. package/libx/_runtime/fiori/utils/stream.js +0 -36
  268. package/libx/_runtime/fiori/utils/where.js +0 -254
  269. package/libx/_runtime/index.js +0 -22
  270. package/libx/odata/utils/handler.js +0 -120
  271. package/libx/odata/utils/metaInfo.js +0 -410
  272. package/libx/odata/utils/path.js +0 -75
  273. package/libx/rest/RestRequest.js +0 -32
  274. package/libx/rest/index.js +0 -3
  275. package/libx/rest/readme.md +0 -1
  276. /package/libx/common/assert/{type.js → type-strict.js} +0 -0
@@ -1,9 +1,7 @@
1
1
  const cds = require('../../cds.js')
2
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
3
2
  const express = require('express')
4
3
  const getTenantInfo = require('./getTenantInfo.js')
5
4
  const isSecured = () => cds.requires.auth && (cds.requires.auth.impl || cds.requires.auth.credentials)
6
- const _require = require('../../common/utils/require')
7
5
  const { ODATA_UNAUTHORIZED } = require('../../common/error/constants')
8
6
 
9
7
  const _isAll = a => a && a.includes('all')
@@ -15,60 +13,48 @@ class EndpointRegistry {
15
13
  this.deployCallbacks = new Map()
16
14
  if (isSecured()) {
17
15
  if (cds.requires.auth.impl) {
18
- if (cds.env.requires.middlewares !== false) {
19
- cds.app.use(basePath, cds.middlewares.before) // contains auth, trace, context
20
- } else {
21
- const impl = _require(cds.resolve(cds.requires.auth.impl))
22
- cds.app.use(basePath, impl)
23
- }
16
+ cds.app.use(basePath, cds.middlewares.before) // contains auth, trace, context
24
17
  } else {
25
- if (cds.env.requires.middlewares !== false) {
26
- const jwt_auth = require('../../../../lib/auth/jwt-auth.js')
27
- cds.app.use(basePath, jwt_auth(cds.requires.auth))
28
- } else {
29
- const JWTStrategy = require('../../auth/strategies/JWT.js')
30
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
31
- const passport = _require('passport')
32
- // REVISIT: It's unclear if the credentials from cds.requires.auth need to be used here.
33
- // In principle, user-facing endpoints might differ from messaging ones.
34
- passport.use(new JWTStrategy(cds.requires.auth.credentials))
35
- cds.app.use(basePath, passport.initialize())
36
- cds.app.use(basePath, passport.authenticate('JWT', { session: false }))
37
- }
18
+ const jwt_auth = require('../../../../lib/auth/jwt-auth.js')
19
+ cds.app.use(basePath, cds.middlewares.context())
20
+ cds.app.use(basePath, jwt_auth(cds.requires.auth))
38
21
  }
39
22
  // unsuccessful auth doesn't automatically reject!
40
23
  cds.app.use(basePath, (req, res, next) => {
41
24
  // REVISIT: we should probably pass an error into next so that a (custom) error middleware can handle it
42
- if (!req.user) res.status(401).json({ error: ODATA_UNAUTHORIZED })
25
+ if (!cds.context.user._is_anonymous) res.status(401).json({ error: ODATA_UNAUTHORIZED })
43
26
  next()
44
27
  })
45
28
  } else if (process.env.NODE_ENV === 'production') {
46
29
  LOG.warn('Messaging endpoints not secured')
30
+ } else {
31
+ // auth middlewares set cds.context.user
32
+ cds.app.use(basePath, cds.middlewares.context())
47
33
  }
48
34
  cds.app.use(basePath, express.json({ type: 'application/*+json' }))
49
- cds.app.use(basePath, express.json())
35
+ cds.app.use(basePath, express.json()) // REVISIT: Do we need both?
50
36
  cds.app.use(basePath, express.urlencoded({ extended: true }))
51
37
  LOG._debug && LOG.debug('Register inbound endpoint', { basePath, method: 'OPTIONS' })
52
38
 
53
39
  // Clear cds.context as it would interfere with subsequent transactions
54
- cds.app.use(basePath, (_req, _res, next) => {
55
- cds.context = undefined
56
- next()
57
- })
40
+ // cds.app.use(basePath, (_req, _res, next) => {
41
+ // cds.context = undefined // REVISIT: Why is that necessary?
42
+ // next()
43
+ // })
58
44
 
59
45
  cds.app.options(basePath, (req, res) => {
60
46
  try {
61
- if (isSecured() && !req.user.is('emcallback')) return res.sendStatus(403)
47
+ if (isSecured() && !cds.context.user.is('emcallback')) return res.sendStatus(403)
62
48
  res.set('webhook-allowed-origin', req.headers['webhook-request-origin'])
63
49
  res.sendStatus(200)
64
- } catch (error) {
50
+ } catch {
65
51
  res.sendStatus(500)
66
52
  }
67
53
  })
68
54
  LOG._debug && LOG.debug('Register inbound endpoint', { basePath, method: 'POST' })
69
55
  cds.app.post(basePath, (req, res) => {
70
56
  try {
71
- if (isSecured() && !req.user.is('emcallback')) return res.sendStatus(403)
57
+ if (isSecured() && !cds.context.user.is('emcallback')) return res.sendStatus(403)
72
58
  const queueName = req.query.q
73
59
  if (!queueName) {
74
60
  LOG.error('Query parameter `q` not found.')
@@ -83,7 +69,7 @@ class EndpointRegistry {
83
69
  const payload = req.body
84
70
  const cb = this.webhookCallbacks.get(queueName)
85
71
  if (!cb) return res.sendStatus(200)
86
- const tenant = req.tenant || req.user?.tenant
72
+ const { tenant } = cds.context
87
73
  const other = tenant
88
74
  ? {
89
75
  _: { req, res }, // For `cds.context.http`
@@ -106,7 +92,7 @@ class EndpointRegistry {
106
92
  })
107
93
  cds.app.post(deployPath, async (req, res) => {
108
94
  try {
109
- if (isSecured() && !req.user.is('emmanagement')) return res.sendStatus(403)
95
+ if (isSecured() && !cds.context.user.is('emmanagement')) return res.sendStatus(403)
110
96
  const tenants = req.body && !_isAll(req.body.tenants) && req.body.tenants
111
97
  const queues = req.body && !_isAll(req.body.queues) && req.body.queues
112
98
  const options = { wipeData: req.body && req.body.wipeData }
@@ -123,7 +109,7 @@ class EndpointRegistry {
123
109
  const hasError = results.some(r => r.failed.length)
124
110
  if (hasError) return res.status(500).send(results)
125
111
  return res.status(201).send(results)
126
- } catch (mtxError) {
112
+ } catch {
127
113
  // REVISIT: Still needed with cds-mtxs?
128
114
  // If an unknown tenant id is provided, cds-mtx will crash ("Cannot read property 'hanaClient' of undefined")
129
115
  return res.sendStatus(500)
@@ -1,6 +1,6 @@
1
1
  const cds = require('../cds')
2
2
 
3
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
3
+ const normalizeIncomingMessage = require('./common-utils/normalizeIncomingMessage')
4
4
  const express = require('express')
5
5
  const https = require('https')
6
6
  const crypto = require('crypto')
@@ -90,17 +90,6 @@ function _validateCertificate(req, res, next) {
90
90
  }
91
91
  }
92
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
93
  let instantiated = false
105
94
 
106
95
  class EventBroker extends cds.MessagingService {
@@ -153,30 +142,57 @@ class EventBroker extends cds.MessagingService {
153
142
 
154
143
  // TODO: What if we're in single tenant variant?
155
144
  try {
156
- const ceSource = `${this.options.credentials.ceSource[0]}/${cds.context.tenant}`
157
145
  const hostname = this.options.credentials.eventing.http.x509.url.replace(/^https?:\/\//, '')
158
- // TODO Cloud Events Handler CAP
146
+
147
+ // take over and cleanse cloudevents headers
148
+ const headers = { ...(msg.headers ?? {}) }
149
+
150
+ const ceId = headers.id
151
+ delete headers.id
152
+
153
+ const ceSource = headers.source
154
+ delete headers.source
155
+
156
+ const ceType = headers.type
157
+ delete headers.type
158
+
159
+ const ceSpecversion = headers.specversion
160
+ delete headers.specversion
161
+
162
+ // const ceDatacontenttype = headers.datacontenttype // not part of the HTTP API
163
+ delete headers.datacontenttype
164
+
165
+ // const ceTime = headers.time // not part of the HTTP API
166
+ delete headers.time
167
+
159
168
  const options = {
160
169
  hostname: hostname,
161
170
  method: 'POST',
162
171
  headers: {
163
- 'ce-id': cds.utils.uuid(),
172
+ 'ce-id': ceId,
164
173
  'ce-source': ceSource,
165
- 'ce-type': msg.event,
166
- 'ce-specversion': '1.0',
167
- 'Content-Type': 'application/json'
174
+ 'ce-type': ceType,
175
+ 'ce-specversion': ceSpecversion,
176
+ 'Content-Type': 'application/json' // because of { data, ...headers } format
168
177
  },
169
178
  agent: this.agent
170
179
  }
171
180
  this.LOG.debug('HTTP headers:', JSON.stringify(options.headers))
172
181
  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
182
+ // what about headers?
183
+ // TODO: Clarify if we should send `{ data, ...headers }` vs. `data` + HTTP headers (`ce-*`)
184
+ await request(options, { data: msg.data, ...headers }) // TODO: fetch does not work with mTLS as of today, requires another module. see https://github.com/nodejs/node/issues/48977
174
185
  if (this.LOG._info) this.LOG.info('Emit', { topic: msg.event })
175
186
  } catch (e) {
176
187
  this.LOG.error('Emit failed:', e.message)
177
188
  }
178
189
  }
179
190
 
191
+ prepareHeaders(headers, event) {
192
+ if (!('source' in headers)) headers.source = `${this.options.credentials.ceSource[0]}/${cds.context.tenant}`
193
+ super.prepareHeaders(headers, event)
194
+ }
195
+
180
196
  async registerWebhookEndpoints() {
181
197
  const webhookBasePath = this.options.webhookPath || '/-/cds/event-broker/webhook'
182
198
  cds.app.post(webhookBasePath, _validateCertificate.bind(this))
@@ -188,15 +204,26 @@ class EventBroker extends cds.MessagingService {
188
204
  try {
189
205
  const event = req.headers['ce-type'] // TG27: type contains namespace, so there's no collision
190
206
  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
207
+
208
+ // take over cloudevents headers (`ce-*`) without the prefix
209
+ const headers = {}
210
+ for (const header in req.headers) {
211
+ if (header.startsWith('ce-')) headers[header.slice(3)] = req.headers[header]
197
212
  }
198
- const context = { user: cds.User.privileged }
199
- if (tenant) context.tenant = tenant // TODO: In single tenant case, we don't need a tenant
213
+
214
+ const msg = normalizeIncomingMessage(req.body)
215
+ msg.event = event
216
+ Object.assign(msg.headers, headers)
217
+ if (tenant) msg.tenant = tenant
218
+
219
+ // for cds.context.http
220
+ msg._ = {}
221
+ msg._.req = req
222
+ msg._.res = res
223
+
224
+ const context = { user: cds.User.privileged, _: msg._ }
225
+ if (msg.tenant) context.tenant = msg.tenant
226
+
200
227
  await this.tx(context, tx => tx.emit(msg))
201
228
  this.LOG.debug('Event processed successfully.')
202
229
  return res.status(200).json({ message: 'OK' })
@@ -10,7 +10,7 @@ class FileBasedMessaging extends MessagingService {
10
10
  this.file = resolve(this.options.file || (this.options.credentials && this.options.credentials.file))
11
11
  try {
12
12
  await fs.lstat(this.file)
13
- } catch (e) {
13
+ } catch {
14
14
  await fs.writeFile(this.file, '\n')
15
15
  }
16
16
  cds.once('listening', () => {
@@ -62,7 +62,7 @@ class FileBasedMessaging extends MessagingService {
62
62
  )
63
63
  } else other.push(each + '\n')
64
64
  }
65
- } catch (e) {
65
+ } catch {
66
66
  // ignore invalid messages
67
67
  }
68
68
  }
@@ -89,7 +89,7 @@ const lock = async (file, n = 11) => {
89
89
  try {
90
90
  while (n--) await fs.lstat(lock).then(() => n && sleep(150))
91
91
  return false
92
- } catch (_) {
92
+ } catch {
93
93
  // lock file does not exist -> create it
94
94
  await fs.writeFile(lock, 'locked')
95
95
  return true
@@ -47,7 +47,7 @@ const requestToken = ({ client, secret, endpoint, mTLS }, tenant, tokenStore) =>
47
47
  // store token on tokenStore
48
48
  tokenStore.token = json.access_token
49
49
  resolve(json.access_token)
50
- } catch (e) {
50
+ } catch {
51
51
  reject(_errorObj(result))
52
52
  }
53
53
  })
@@ -151,7 +151,7 @@ const appId = require('./common-utils/appId')
151
151
  function _JSONorString(string) {
152
152
  try {
153
153
  return JSON.parse(string)
154
- } catch (e) {
154
+ } catch {
155
155
  return string
156
156
  }
157
157
  }
@@ -260,7 +260,7 @@ async function _getCaCerts(srv) {
260
260
  try {
261
261
  const certNext = await fetch(srv.options.credentials.urls.cert_next).then(r => r.text())
262
262
  return [certCurrent, certNext]
263
- } catch (_e) {
263
+ } catch {
264
264
  return [certCurrent]
265
265
  }
266
266
  }
@@ -1,4 +1,3 @@
1
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
2
1
  const redis = require('redis')
3
2
  const cds = require('../../../lib')
4
3
  const waitingTime = require('../common/utils/waitingTime')
@@ -7,8 +7,6 @@ const { resolveView, getTransition, findQueryTarget } = require('../common/utils
7
7
  const postProcess = require('../common/utils/postProcess')
8
8
  const { formatVal } = require('../../odata/utils')
9
9
 
10
- const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
11
-
12
10
  const _setHeaders = (defaultHeaders, req) => {
13
11
  return Object.assign(
14
12
  defaultHeaders,
@@ -34,6 +32,8 @@ const _buildPartialUrlFunctions = (url, data, params, kind = 'odata-v4') => {
34
32
 
35
33
  // REVISIT: take params from params after importer fix (the keys should not be part of params)
36
34
  for (const param in _extractParamsFromData(data, params)) {
35
+ if (data[param] === undefined) continue
36
+
37
37
  if (kind === 'odata-v2') {
38
38
  funcParams.push(`${param}=${_setCorrectValue(param, data, params, kind)}`)
39
39
  } else {
@@ -142,9 +142,9 @@ const _addHandlerActionFunction = (srv, def, target) => {
142
142
 
143
143
  if (target) {
144
144
  srv.on(event, target, async function (req) {
145
- const shortEntityName = req.target.name.replace(`${this.namespace}.`, '')
145
+ const shortEntityName = req.target.name.replace(`${this.definition.name}.`, '')
146
146
  if (this.kind === 'odata-v2') return _handleV2BoundActionFunction(srv, def, req, event, this.kind)
147
- const url = `/${shortEntityName}(${_buildKeys(req, this.kind).join(',')})/${this.namespace}.${event}`
147
+ const url = `/${shortEntityName}(${_buildKeys(req, this.kind).join(',')})/${this.definition.name}.${event}`
148
148
  return _handleBoundActionFunction(srv, def, req, url)
149
149
  })
150
150
  } else {
@@ -155,7 +155,8 @@ const _addHandlerActionFunction = (srv, def, target) => {
155
155
  }
156
156
  }
157
157
 
158
- const _selectOnlyWithAlias = q => q?.SELECT && !q.SELECT._transitions && q.SELECT?.columns?.some(hasAliasedColumns)
158
+ const _isSelectWithAliasedColumns = q =>
159
+ q?.SELECT && !q.SELECT._transitions && q.SELECT.columns?.some(hasAliasedColumns)
159
160
 
160
161
  const resolvedTargetOfQuery = q => {
161
162
  const transitions = (typeof q === 'object' && (q.SELECT || q.INSERT || q.UPDATE || q.DELETE)._transitions) || []
@@ -200,6 +201,12 @@ class RemoteService extends cds.Service {
200
201
  if (this.options.credentials) {
201
202
  this.datasource = this.options.datasource
202
203
  this.destinationOptions = this.options.destinationOptions
204
+ // set useCache by default
205
+ if (!this.destinationOptions) {
206
+ this.destinationOptions = { useCache: true }
207
+ } else if (typeof this.destinationOptions.useCache === 'undefined') {
208
+ this.destinationOptions.useCache = true
209
+ }
203
210
  _resolveSelectionStrategy(this.destinationOptions)
204
211
  this.destination =
205
212
  this.options.credentials.destination ??
@@ -271,29 +278,22 @@ class RemoteService extends cds.Service {
271
278
  // Overload .handle in order to resolve projections up to a definition that is known by the remote service instance.
272
279
  // Result is post processed according to the inverse projection in order to reflect the correct result of the original query.
273
280
  async handle(req) {
274
- if (req._resolved) return super.handle(req)
281
+ let result
275
282
 
276
- if (req.target?.name?.startsWith(this.definition?.name + '.')) {
277
- let result = await super.handle(req)
278
- // only post process if alias was explicitly set in query
279
- if (_selectOnlyWithAlias(req.query)) result = postProcess(req.query, result, this, true)
280
- return result
281
- }
282
-
283
- // req.query can be:
284
- // - empty object in case of unbound action/function
285
- // - undefined/null in case of plain string queries
286
- if (this.model && _isSimpleCqnQuery(req.query)) {
287
- const q = resolveView(req.query, this.model, this)
288
- const t = findQueryTarget(q) || req.target
289
-
290
- // REVISIT: We need to provide target explicitly because it's cached already within ensure_target
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)
293
- return postProcess(q, result, this, true)
283
+ if (!this._requires_resolving(req)) {
284
+ result = await super.handle(req)
285
+ // we need to post process if alias was explicitly set in query
286
+ if (_isSelectWithAliasedColumns(req.query)) result = postProcess(req.query, result, this, true)
287
+ } else {
288
+ const query = resolveView(req.query, this.model, this)
289
+ const target = findQueryTarget(query) || req.target
290
+ // we need to provide target explicitly because it's cached within ensure_target
291
+ const _req = new cds.Request({ query, target, _resolved: true, headers: req.headers, method: req.method })
292
+ result = await super.dispatch(_req)
293
+ result = postProcess(query, result, this, true)
294
294
  }
295
295
 
296
- return super.handle(req)
296
+ return result
297
297
  }
298
298
  }
299
299
 
@@ -33,6 +33,8 @@ const _executeHttpRequest = async ({ requestConfig, destination, destinationOpti
33
33
  if (jwt) destination.headers.authorization = `Bearer ${jwt}`
34
34
  else LOG._warn && LOG.warn('Missing JWT token for forwardAuthToken')
35
35
  }
36
+ // Cloud SDK throws error if useCache is activated and jwt is undefined
37
+ if (destination.jwt === undefined) delete destination.useCache
36
38
 
37
39
  if (LOG._debug) {
38
40
  const req2log = { headers: _sanitizeHeaders({ ...requestConfig.headers }) }
@@ -128,7 +130,6 @@ function _normalizeMetadata(prefix, data, results) {
128
130
  const _getPurgedRespActionFunc = (data, returnType) => {
129
131
  // return type is primitive value or inline/complex type
130
132
  if (returnType.kind === 'type' && !returnType.items && Object.values(data).length === 1) {
131
- // eslint-disable-next-line no-unreachable-loop
132
133
  for (const key in data) {
133
134
  return data[key]
134
135
  }
@@ -207,14 +208,13 @@ const _getSanitizedError = (e, reqOptions, options = { suppressRemoteResponseBod
207
208
  // AxiosError's toJSON() method doesn't include the request and response objects
208
209
  if (e.__proto__.toJSON) {
209
210
  e.toJSON = function () {
210
- return { ...this.__proto__.toJSON(), request: this.request, response: this.response }
211
+ return { ...e.__proto__.toJSON(), request: this.request, response: this.response }
211
212
  }
212
213
  }
213
214
 
214
215
  return e
215
216
  }
216
217
 
217
- // eslint-disable-next-line complexity
218
218
  const run = async (requestConfig, options) => {
219
219
  let response
220
220
 
@@ -360,14 +360,13 @@ const _hasHeader = (headers, header) =>
360
360
  .map(k => k.toLowerCase())
361
361
  .includes(header)
362
362
 
363
- // eslint-disable-next-line complexity
364
363
  const getReqOptions = (req, query, service) => {
365
364
  const reqOptions =
366
365
  typeof query === 'object'
367
366
  ? _cqnToReqOptions(query, service, req)
368
367
  : typeof query === 'string'
369
368
  ? _stringToReqOptions(query, req.data, req.target)
370
- : _pathToReqOptions(req.method, req.path, req.data, req.target, service.name)
369
+ : _pathToReqOptions(req.method, req.path, req.data, req.target, service.definition?.name || service.namespace) //> no model, no service.definition
371
370
 
372
371
  if (service.kind === 'odata-v2' && req.event === 'READ' && reqOptions.url?.match(/(\/any\()|(\/all\()/)) {
373
372
  req.reject(501, 'Lambda expressions are not supported in OData v2')
@@ -1,7 +1,6 @@
1
1
  let _cloudSdkConnectivity
2
2
  const getCloudSdkConnectivity = () => {
3
3
  if (_cloudSdkConnectivity) return _cloudSdkConnectivity
4
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
5
4
  _cloudSdkConnectivity = require('@sap-cloud-sdk/connectivity')
6
5
  return _cloudSdkConnectivity
7
6
  }
@@ -9,7 +8,6 @@ const getCloudSdkConnectivity = () => {
9
8
  let _cloudSdkResilience
10
9
  const getCloudSdkResilience = () => {
11
10
  if (_cloudSdkResilience) return _cloudSdkResilience
12
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
13
11
  _cloudSdkResilience = require('@sap-cloud-sdk/resilience')
14
12
  return _cloudSdkResilience
15
13
  }
@@ -18,7 +16,6 @@ let _cloudSdk
18
16
  const getCloudSdk = () => {
19
17
  if (_cloudSdk) return _cloudSdk
20
18
 
21
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
22
19
  _cloudSdk = require('@sap-cloud-sdk/http-client')
23
20
  return _cloudSdk
24
21
  }
@@ -64,7 +64,6 @@ const _convertActionFuncResponse = (returnType, convertValueFn) => data => {
64
64
  return convertValueFn(data, returnType.items || returnType)
65
65
  }
66
66
 
67
- // eslint-disable-next-line complexity
68
67
  const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, element) => {
69
68
  if (value == null) return value
70
69
 
@@ -18,7 +18,6 @@ const execute = require('./execute')
18
18
 
19
19
  const _new = url => {
20
20
  if (url && url !== ':memory:') url = cds.utils.path.resolve(cds.root, url)
21
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
22
21
  if (!_sqlite) _sqlite = require('sqlite3')
23
22
  return new Promise((resolve, reject) => {
24
23
  const dbc = new _sqlite.Database(url, err => {
@@ -75,7 +74,7 @@ module.exports = class SQLiteDatabase extends DatabaseService {
75
74
  this.before(['CREATE', 'UPDATE', 'UPSERT'], '*', this._input) // > has to run before rewrite
76
75
  this.before(['CREATE', 'READ', 'UPDATE', 'DELETE', 'UPSERT'], '*', this._rewrite)
77
76
 
78
- if (cds.env.fiori.lean_draft && !cds.db?.cqn2sql) this.before('READ', '*', convertDraftAdminPathExpression)
77
+ if (!cds.db?.cqn2sql) this.before('READ', '*', convertDraftAdminPathExpression)
79
78
  this.before('READ', '*', convertAssocToOneManaged)
80
79
  this.before('READ', '*', localized) // > has to run after rewrite
81
80
  this.before('READ', '*', this._virtual)