@sap/cds 7.9.2 → 8.0.3

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 (279) hide show
  1. package/CHANGELOG.md +139 -3656
  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 +12 -30
  15. package/lib/auth/index.js +1 -14
  16. package/lib/auth/jwt-auth.js +14 -30
  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/srvinfo.js +1 -1
  32. package/lib/compile/to/yaml.js +3 -3
  33. package/lib/dbs/cds-deploy.js +4 -2
  34. package/lib/env/cds-env.js +10 -14
  35. package/lib/env/cds-requires.js +29 -13
  36. package/lib/env/defaults.js +46 -16
  37. package/lib/env/plugins.js +1 -1
  38. package/lib/env/schemas/cds-rc.js +8 -4
  39. package/lib/env/schemas/index.js +7 -7
  40. package/lib/env/serviceBindings.js +1 -1
  41. package/lib/index.js +12 -10
  42. package/lib/lazy.js +1 -1
  43. package/lib/linked/classes.js +36 -8
  44. package/lib/linked/entities.js +2 -10
  45. package/lib/linked/models.js +2 -1
  46. package/lib/linked/validate.js +292 -0
  47. package/lib/log/cds-error.js +0 -6
  48. package/lib/log/cds-log.js +3 -3
  49. package/lib/log/format/json.js +1 -1
  50. package/lib/log/service/index.js +0 -1
  51. package/lib/plugins.js +3 -3
  52. package/lib/ql/Query.js +2 -10
  53. package/lib/ql/SELECT.js +1 -1
  54. package/lib/ql/Whereable.js +3 -2
  55. package/lib/req/cds-context.js +14 -25
  56. package/lib/req/context.js +23 -25
  57. package/lib/req/request.js +1 -34
  58. package/lib/req/user.js +47 -35
  59. package/lib/srv/bindings.js +1 -1
  60. package/lib/srv/cds-connect.js +4 -4
  61. package/lib/srv/cds-serve.js +2 -2
  62. package/lib/srv/factory.js +1 -1
  63. package/lib/srv/middlewares/cds-context.js +11 -22
  64. package/lib/srv/middlewares/ctx-model.js +2 -3
  65. package/lib/srv/middlewares/errors.js +41 -8
  66. package/lib/srv/middlewares/index.js +3 -3
  67. package/lib/srv/middlewares/trace.js +0 -2
  68. package/lib/srv/protocols/hcql.js +15 -10
  69. package/lib/srv/protocols/http.js +44 -49
  70. package/lib/srv/protocols/index.js +1 -23
  71. package/lib/srv/protocols/odata-v4.js +12 -74
  72. package/lib/srv/protocols/rest.js +1 -13
  73. package/lib/srv/srv-api.js +0 -20
  74. package/lib/srv/srv-dispatch.js +3 -2
  75. package/lib/srv/srv-handlers.js +22 -11
  76. package/lib/srv/srv-methods.js +2 -2
  77. package/lib/srv/srv-models.js +3 -36
  78. package/lib/test/expect.js +343 -0
  79. package/lib/test/index.js +2 -0
  80. package/lib/test/reporter.js +176 -0
  81. package/lib/utils/axios.js +10 -9
  82. package/lib/utils/cds-test.js +86 -37
  83. package/lib/utils/cds-utils.js +54 -7
  84. package/lib/utils/check-version.js +0 -4
  85. package/lib/utils/colors.js +49 -0
  86. package/lib/utils/data.js +5 -4
  87. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -7
  88. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +3 -30
  89. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +6 -12
  90. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
  91. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -1
  92. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -7
  93. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +12 -6
  94. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +2 -4
  95. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +1 -0
  96. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  97. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  98. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -3
  99. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  100. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +1 -2
  101. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -0
  102. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
  103. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +9 -43
  104. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +0 -1
  105. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +8 -3
  106. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +4 -2
  107. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -3
  108. package/libx/_runtime/cds-services/util/assert.js +1 -1
  109. package/libx/_runtime/cds.js +10 -3
  110. package/libx/_runtime/common/Service.js +12 -32
  111. package/libx/_runtime/common/aspects/any.js +1 -0
  112. package/libx/_runtime/common/code-ext/execute.js +1 -1
  113. package/libx/_runtime/common/code-ext/worker.js +0 -1
  114. package/libx/_runtime/common/composition/data.js +0 -1
  115. package/libx/_runtime/common/composition/delete.js +0 -1
  116. package/libx/_runtime/common/composition/insert.js +2 -2
  117. package/libx/_runtime/common/composition/tree.js +0 -1
  118. package/libx/_runtime/common/composition/update.js +3 -3
  119. package/libx/_runtime/common/error/frontend.js +21 -12
  120. package/libx/_runtime/common/error/log.js +36 -0
  121. package/libx/_runtime/common/error/utils.js +2 -5
  122. package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
  123. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  124. package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
  125. package/libx/_runtime/common/generic/auth/restrict.js +23 -42
  126. package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
  127. package/libx/_runtime/common/generic/auth/utils.js +91 -88
  128. package/libx/_runtime/common/generic/crud.js +6 -5
  129. package/libx/_runtime/common/generic/etag.js +7 -12
  130. package/libx/_runtime/common/generic/input.js +70 -68
  131. package/libx/_runtime/common/generic/paging.js +1 -0
  132. package/libx/_runtime/common/generic/sorting.js +1 -0
  133. package/libx/_runtime/common/generic/temporal.js +8 -2
  134. package/libx/_runtime/common/i18n/index.js +1 -1
  135. package/libx/_runtime/common/i18n/messages.properties +3 -1
  136. package/libx/_runtime/common/utils/binary.js +8 -2
  137. package/libx/_runtime/common/utils/compareJson.js +5 -1
  138. package/libx/_runtime/common/utils/copy.js +6 -11
  139. package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
  140. package/libx/_runtime/common/utils/differ.js +3 -6
  141. package/libx/_runtime/common/utils/keys.js +77 -18
  142. package/libx/_runtime/common/utils/postProcess.js +12 -15
  143. package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
  144. package/libx/_runtime/common/utils/resolveView.js +2 -3
  145. package/libx/_runtime/common/utils/restrictions.js +45 -17
  146. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
  147. package/libx/_runtime/common/utils/stream.js +3 -16
  148. package/libx/_runtime/common/utils/streamProp.js +8 -18
  149. package/libx/_runtime/common/utils/structured.js +1 -1
  150. package/libx/_runtime/common/utils/ucsn.js +0 -2
  151. package/libx/_runtime/db/Service.js +0 -72
  152. package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
  153. package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
  154. package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
  155. package/libx/_runtime/db/generic/input.js +3 -8
  156. package/libx/_runtime/db/generic/rewrite.js +27 -4
  157. package/libx/_runtime/db/query/read.js +2 -2
  158. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
  159. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  160. package/libx/_runtime/db/utils/columns.js +2 -6
  161. package/libx/_runtime/fiori/lean-draft.js +138 -56
  162. package/libx/_runtime/hana/Service.js +0 -1
  163. package/libx/_runtime/hana/driver.js +1 -1
  164. package/libx/_runtime/hana/dynatrace.js +1 -2
  165. package/libx/_runtime/hana/pool.js +11 -21
  166. package/libx/_runtime/hana/streaming.js +0 -1
  167. package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
  168. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  169. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
  170. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
  171. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
  172. package/libx/_runtime/messaging/event-broker.js +0 -12
  173. package/libx/_runtime/messaging/file-based.js +3 -3
  174. package/libx/_runtime/messaging/http-utils/token.js +1 -1
  175. package/libx/_runtime/messaging/kafka.js +2 -2
  176. package/libx/_runtime/messaging/redis-messaging.js +0 -1
  177. package/libx/_runtime/remote/Service.js +25 -25
  178. package/libx/_runtime/remote/utils/client.js +4 -5
  179. package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
  180. package/libx/_runtime/remote/utils/data.js +0 -1
  181. package/libx/_runtime/sqlite/Service.js +1 -2
  182. package/libx/_runtime/ucl/Service.js +37 -78
  183. package/libx/common/assert/index.js +22 -21
  184. package/libx/common/assert/type-relaxed.js +39 -0
  185. package/libx/common/assert/utils.js +3 -2
  186. package/libx/common/assert/validation.js +3 -8
  187. package/libx/common/utils/index.js +5 -0
  188. package/libx/common/utils/path.js +51 -0
  189. package/libx/odata/ODataAdapter.js +126 -0
  190. package/libx/odata/index.js +15 -2
  191. package/libx/odata/middleware/batch.js +261 -72
  192. package/libx/odata/middleware/body-parser.js +33 -0
  193. package/libx/odata/middleware/create.js +44 -59
  194. package/libx/odata/middleware/delete.js +23 -12
  195. package/libx/odata/middleware/error.js +30 -6
  196. package/libx/odata/middleware/metadata.js +38 -26
  197. package/libx/odata/middleware/operation.js +93 -69
  198. package/libx/odata/middleware/parse.js +6 -8
  199. package/libx/odata/middleware/read.js +117 -93
  200. package/libx/odata/middleware/service-document.js +22 -19
  201. package/libx/odata/middleware/stream.js +54 -56
  202. package/libx/odata/middleware/update.js +79 -87
  203. package/libx/odata/parse/afterburner.js +191 -175
  204. package/libx/odata/parse/cqn2odata.js +8 -8
  205. package/libx/odata/parse/grammar.peggy +27 -20
  206. package/libx/odata/parse/multipartToJson.js +17 -9
  207. package/libx/odata/parse/parser.js +1 -1
  208. package/libx/odata/utils/etag.js +14 -6
  209. package/libx/odata/utils/index.js +84 -12
  210. package/libx/odata/utils/metadata.js +161 -0
  211. package/libx/odata/utils/postProcess.js +89 -0
  212. package/libx/odata/utils/readAfterWrite.js +134 -17
  213. package/libx/odata/utils/result.js +36 -142
  214. package/libx/outbox/index.js +5 -4
  215. package/libx/rest/RestAdapter.js +115 -182
  216. package/libx/rest/middleware/create.js +28 -24
  217. package/libx/rest/middleware/delete.js +7 -10
  218. package/libx/rest/middleware/error.js +19 -16
  219. package/libx/rest/middleware/operation.js +48 -41
  220. package/libx/rest/middleware/parse.js +128 -126
  221. package/libx/rest/middleware/read.js +20 -27
  222. package/libx/rest/middleware/update.js +26 -31
  223. package/package.json +16 -12
  224. package/server.js +4 -2
  225. package/tasks/enterprise-messaging-deploy.js +1 -1
  226. package/apis/cds.d.ts +0 -3
  227. package/apis/core.d.ts +0 -21
  228. package/apis/cqn.d.ts +0 -18
  229. package/apis/csn.d.ts +0 -21
  230. package/apis/events.d.ts +0 -18
  231. package/apis/internal/inference.d.ts +0 -18
  232. package/apis/linked.d.ts +0 -18
  233. package/apis/log.d.ts +0 -20
  234. package/apis/models.d.ts +0 -18
  235. package/apis/ql.d.ts +0 -18
  236. package/apis/reflect.d.ts +0 -32
  237. package/apis/server.d.ts +0 -18
  238. package/apis/services.d.ts +0 -22
  239. package/bin/cds-serve.js +0 -56
  240. package/lib/compile/to/gql.js +0 -15
  241. package/lib/srv/protocols/_legacy.js +0 -44
  242. package/lib/utils/jest.js +0 -43
  243. package/libx/_runtime/auth/index.js +0 -193
  244. package/libx/_runtime/auth/strategies/JWT.js +0 -37
  245. package/libx/_runtime/auth/strategies/basic.js +0 -20
  246. package/libx/_runtime/auth/strategies/dummy.js +0 -14
  247. package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
  248. package/libx/_runtime/auth/strategies/mock.js +0 -77
  249. package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
  250. package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
  251. package/libx/_runtime/common/perf/index.js +0 -19
  252. package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
  253. package/libx/_runtime/fiori/draft.js +0 -2
  254. package/libx/_runtime/fiori/generic/activate.js +0 -190
  255. package/libx/_runtime/fiori/generic/before.js +0 -201
  256. package/libx/_runtime/fiori/generic/cancel.js +0 -19
  257. package/libx/_runtime/fiori/generic/delete.js +0 -21
  258. package/libx/_runtime/fiori/generic/edit.js +0 -157
  259. package/libx/_runtime/fiori/generic/index.js +0 -25
  260. package/libx/_runtime/fiori/generic/new.js +0 -82
  261. package/libx/_runtime/fiori/generic/patch.js +0 -101
  262. package/libx/_runtime/fiori/generic/prepare.js +0 -57
  263. package/libx/_runtime/fiori/generic/read.js +0 -1340
  264. package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
  265. package/libx/_runtime/fiori/utils/csn.js +0 -13
  266. package/libx/_runtime/fiori/utils/delete.js +0 -114
  267. package/libx/_runtime/fiori/utils/handler.js +0 -264
  268. package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
  269. package/libx/_runtime/fiori/utils/req.js +0 -23
  270. package/libx/_runtime/fiori/utils/stream.js +0 -36
  271. package/libx/_runtime/fiori/utils/where.js +0 -254
  272. package/libx/_runtime/index.js +0 -22
  273. package/libx/odata/utils/handler.js +0 -120
  274. package/libx/odata/utils/metaInfo.js +0 -410
  275. package/libx/odata/utils/path.js +0 -75
  276. package/libx/rest/RestRequest.js +0 -32
  277. package/libx/rest/index.js +0 -3
  278. package/libx/rest/readme.md +0 -1
  279. /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,5 @@
1
1
  const cds = require('../cds')
2
2
 
3
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
4
3
  const express = require('express')
5
4
  const https = require('https')
6
5
  const crypto = require('crypto')
@@ -90,17 +89,6 @@ function _validateCertificate(req, res, next) {
90
89
  }
91
90
  }
92
91
 
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
92
  let instantiated = false
105
93
 
106
94
  class EventBroker extends cds.MessagingService {
@@ -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)
@@ -1,65 +1,48 @@
1
1
  const cds = require('../cds')
2
2
  const LOG = cds.log('ucl')
3
+ const fs = require('fs').promises
3
4
 
4
5
  const https = require('https')
5
6
 
6
7
  class UCLService extends cds.Service {
7
8
  async init() {
8
9
  await super.init()
9
- this.validate()
10
- this._register()
11
- this.agent = this.getAgent()
12
- }
13
10
 
14
- getAgent() {
15
- try {
16
- if (this.options.x509.certPath && this.options.x509.pkeyPath) {
17
- return new https.Agent({
18
- cert: cds.utils.fs.readFileSync(cds.utils.path.resolve(cds.root, this.options.x509.certPath)),
19
- key: cds.utils.fs.readFileSync(cds.utils.path.resolve(cds.root, this.options.x509.pkeyPath))
20
- })
21
- }
22
- } catch (error) {
23
- if (LOG) LOG.error('GetCredentials', { error: error.message })
24
- throw error
11
+ for (const _required of ['namespace', 'systemType', 'systemDescription']) {
12
+ if (!this.options[_required])
13
+ throw new Error(
14
+ `The UCL service requires mandatory parameter \`${_required}\`, please provide it as described in the documentation.`
15
+ )
25
16
  }
26
- }
27
17
 
28
- async _registerProvisioningEvents() {
29
- var provisioning
30
- try {
31
- provisioning = await cds.connect.to('cds.xt.SaasProvisioningService')
32
- } catch (error) {
18
+ if (!cds.requires.multitenancy && cds.env.profile !== 'mtx-sidecar')
33
19
  throw new Error(
34
- "Provisioning service 'cds.xt.SaasProvisioningService' can not be found, therefore mode is not multitenant. Single tenant applications are not supported."
20
+ 'The UCL service requires multitenancy, please enable it in your cds configuration with `cds.requires.multitenancy` or by using the mtx sidecar.'
35
21
  )
36
- }
37
- if (provisioning) {
22
+ if (!this.options.credentials)
23
+ throw new Error('No credentials found for the UCL service, please bind the service to your app.')
24
+
25
+ if (!this.options.x509.certPath || !this.options.x509.pkeyPath)
26
+ throw new Error('The UCL service requires the options `x509.certPath` and `x509.pkeyPath`.')
27
+ const [cert, key] = await Promise.all([
28
+ fs.readFile(cds.utils.path.resolve(cds.root, this.options.x509.certPath)),
29
+ fs.readFile(cds.utils.path.resolve(cds.root, this.options.x509.pkeyPath))
30
+ ])
31
+ this.agent = new https.Agent({ cert, key })
32
+
33
+ const existingTemplate = await this.readTemplate()
34
+ const template = existingTemplate ? await this.updateTemplate(existingTemplate) : await this.createTemplate() // TODO: Make sure return value is correct
35
+
36
+ cds.once('listening', async () => {
37
+ const provisioning = await cds.connect.to('cds.xt.SaasProvisioningService')
38
38
  provisioning.prepend(() => {
39
39
  provisioning.on('dependencies', async (_, next) => {
40
- let dependencies = await next()
41
- const xsappnameCMPClone = await this._getUCLDependency()
42
- dependencies.push({ xsappname: xsappnameCMPClone })
40
+ const dependencies = await next()
41
+ dependencies.push({ xsappname: template.labels.xsappnameCMPClone })
43
42
  return dependencies
44
43
  })
45
44
  })
46
- }
47
- }
48
-
49
- validate() {
50
- if (!this.options.namespace) {
51
- throw new Error(
52
- 'UCL integrator requires an application namespace. You can set environment variable SAP_APPLICATION_NAMESPACE or you can give namespace as an option in your cds.requires section as described in documentation'
53
- )
54
- }
55
- if (!cds.requires.multitenancy && cds.env.profile !== 'mtx-sidecar') {
56
- throw new Error('[ucl] - Currently only multitenant applications are supported.')
57
- }
58
- if (!this.options.systemType || !this.options.systemDescription) {
59
- throw new Error(
60
- 'systemType and systemDescription is obligatory parameters, please fill as shown in documentation'
61
- )
62
- }
45
+ })
63
46
  }
64
47
 
65
48
  async readTemplate() {
@@ -85,10 +68,10 @@ class UCLService extends cds.Service {
85
68
  }
86
69
  `
87
70
  const variables = { key: 'xsappname', value: `"${xsappname}"` }
88
- return (await this.request(query, variables)).applicationTemplates.data[0]
71
+ return (await this._request(query, variables)).applicationTemplates.data[0]
89
72
  }
90
73
 
91
- async _createTemplate() {
74
+ async createTemplate() {
92
75
  const xsappname = this.options.credentials.xsappname
93
76
  const query = `mutation {
94
77
  result: createApplicationTemplate (
@@ -124,13 +107,13 @@ class UCLService extends cds.Service {
124
107
  }
125
108
  }`
126
109
  try {
127
- return this.handleResponse(await this.request(query))
110
+ return this._handleResponse(await this._request(query))
128
111
  } catch (e) {
129
- this.handleResponse(e)
112
+ this._handleResponse(e)
130
113
  }
131
114
  }
132
115
 
133
- handleResponse(result) {
116
+ _handleResponse(result) {
134
117
  if (result.response && result.response.errors) {
135
118
  let errorMessage = result.response.errors[0].message
136
119
  throw new Error(errorMessage)
@@ -151,18 +134,11 @@ class UCLService extends cds.Service {
151
134
  description
152
135
  }
153
136
  }`
154
- return this.handleResponse(await this.request(query))
155
- }
156
-
157
- async _getUCLDependency() {
158
- if (!this.template) {
159
- throw Error('Application template not found on UCL!')
160
- }
161
- return this.template.labels.xsappnameCMPClone
137
+ return this._handleResponse(await this._request(query))
162
138
  }
163
139
 
164
140
  // Replace with fetch
165
- async request(query, variables) {
141
+ async _request(query, variables) {
166
142
  const opts = {
167
143
  host: this.options.host,
168
144
  path: this.options.path,
@@ -201,17 +177,7 @@ class UCLService extends cds.Service {
201
177
  })
202
178
  }
203
179
 
204
- async _registerApplicationTemplate() {
205
- this.template = await this.readTemplate()
206
- if (!this.template) {
207
- LOG.info('Application Template cannot be found therefore created.')
208
- await this._createTemplate()
209
- } else {
210
- await this._updateTemplate(this.template)
211
- }
212
- }
213
-
214
- async _updateTemplate(template) {
180
+ async updateTemplate(template) {
215
181
  const query = `mutation {
216
182
  result: updateApplicationTemplate(
217
183
  id: "${template.id}"
@@ -240,20 +206,13 @@ class UCLService extends cds.Service {
240
206
  }
241
207
  }`
242
208
  try {
243
- const response = this.handleResponse(await this.request(query))
209
+ const response = this._handleResponse(await this._request(query))
244
210
  LOG.info('Application template updated successfully.')
245
211
  return response
246
212
  } catch (e) {
247
- this.handleResponse(e)
213
+ this._handleResponse(e)
248
214
  }
249
215
  }
250
-
251
- _register() {
252
- cds.once('listening', async () => {
253
- await this._registerApplicationTemplate()
254
- this._registerProvisioningEvents()
255
- })
256
- }
257
216
  }
258
217
 
259
218
  module.exports = UCLService