@sap/cds 5.5.5 → 5.6.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 (207) hide show
  1. package/CHANGELOG.md +139 -1
  2. package/apis/services.d.ts +31 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/to/sql.js +22 -2
  12. package/lib/connect/bindings.js +2 -1
  13. package/lib/core/reflect.js +4 -1
  14. package/lib/env/index.js +180 -42
  15. package/lib/env/requires.js +16 -1
  16. package/lib/i18n/localize.js +33 -5
  17. package/lib/index.js +3 -3
  18. package/lib/log/format/kibana.js +6 -2
  19. package/lib/ql/Query.js +1 -0
  20. package/lib/ql/SELECT.js +15 -8
  21. package/lib/ql/Whereable.js +5 -0
  22. package/lib/req/context.js +13 -5
  23. package/lib/serve/Service-dispatch.js +8 -1
  24. package/lib/utils/axios.js +7 -0
  25. package/lib/utils/data.js +1 -1
  26. package/lib/utils/tests.js +1 -1
  27. package/libx/_runtime/audit/Service.js +18 -18
  28. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  29. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  30. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  31. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +12 -4
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
  46. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  51. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  53. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  54. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  55. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  56. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  57. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  58. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  59. package/libx/_runtime/cds-services/util/assert.js +29 -13
  60. package/libx/_runtime/cds.js +2 -1
  61. package/libx/_runtime/common/aspects/Association.js +72 -0
  62. package/libx/_runtime/common/aspects/any.js +8 -45
  63. package/libx/_runtime/common/aspects/entity.js +0 -1
  64. package/libx/_runtime/common/aspects/relation.js +40 -0
  65. package/libx/_runtime/common/aspects/utils.js +73 -1
  66. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  67. package/libx/_runtime/common/composition/data.js +3 -2
  68. package/libx/_runtime/common/composition/delete.js +3 -1
  69. package/libx/_runtime/common/composition/tree.js +23 -18
  70. package/libx/_runtime/common/composition/update.js +9 -1
  71. package/libx/_runtime/common/composition/utils.js +34 -8
  72. package/libx/_runtime/common/error/frontend.js +6 -1
  73. package/libx/_runtime/common/generic/auth.js +5 -9
  74. package/libx/_runtime/common/generic/crud.js +2 -2
  75. package/libx/_runtime/common/generic/etag.js +11 -8
  76. package/libx/_runtime/common/generic/input.js +3 -3
  77. package/libx/_runtime/common/generic/paging.js +9 -5
  78. package/libx/_runtime/common/generic/put.js +3 -2
  79. package/libx/_runtime/common/generic/sorting.js +3 -3
  80. package/libx/_runtime/common/generic/temporal.js +3 -3
  81. package/libx/_runtime/common/utils/cqn.js +20 -1
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  83. package/libx/_runtime/common/utils/csn.js +50 -52
  84. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  85. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  86. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  87. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  88. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  89. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  90. package/libx/_runtime/common/utils/resolveView.js +7 -5
  91. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  92. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  93. package/libx/_runtime/common/utils/template.js +54 -46
  94. package/libx/_runtime/db/Service.js +9 -2
  95. package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
  96. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  97. package/libx/_runtime/db/generic/arrayed.js +13 -28
  98. package/libx/_runtime/db/generic/create.js +1 -0
  99. package/libx/_runtime/db/generic/input.js +7 -11
  100. package/libx/_runtime/db/generic/integrity.js +2 -2
  101. package/libx/_runtime/db/generic/rewrite.js +2 -5
  102. package/libx/_runtime/db/generic/update.js +1 -0
  103. package/libx/_runtime/db/query/read.js +9 -4
  104. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  105. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  106. package/libx/_runtime/db/utils/columns.js +14 -43
  107. package/libx/_runtime/fiori/generic/activate.js +3 -2
  108. package/libx/_runtime/fiori/generic/before.js +2 -2
  109. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  110. package/libx/_runtime/fiori/generic/delete.js +3 -2
  111. package/libx/_runtime/fiori/generic/edit.js +3 -3
  112. package/libx/_runtime/fiori/generic/new.js +2 -2
  113. package/libx/_runtime/fiori/generic/patch.js +2 -2
  114. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  115. package/libx/_runtime/fiori/generic/read.js +45 -63
  116. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  117. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  118. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  119. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  120. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  121. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  122. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  123. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  124. package/libx/_runtime/fiori/utils/handler.js +3 -13
  125. package/libx/_runtime/fiori/utils/where.js +6 -1
  126. package/libx/_runtime/hana/pool.js +12 -11
  127. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  128. package/libx/_runtime/hana/searchToContains.js +3 -3
  129. package/libx/_runtime/index.js +5 -2
  130. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  131. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  132. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  133. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  134. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  135. package/libx/_runtime/messaging/message-queuing.js +18 -0
  136. package/libx/_runtime/remote/Service.js +20 -4
  137. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  138. package/libx/_runtime/remote/utils/client.js +117 -23
  139. package/libx/_runtime/sqlite/Service.js +2 -2
  140. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  141. package/libx/gql/GraphQLAdapter.js +33 -0
  142. package/libx/gql/constants/adapter.js +69 -0
  143. package/libx/gql/constants/cds.js +18 -0
  144. package/libx/gql/constants/graphql.js +33 -0
  145. package/libx/gql/resolvers/crud/create.js +15 -0
  146. package/libx/gql/resolvers/crud/delete.js +24 -0
  147. package/libx/gql/resolvers/crud/index.js +6 -0
  148. package/libx/gql/resolvers/crud/read.js +25 -0
  149. package/libx/gql/resolvers/crud/update.js +31 -0
  150. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  151. package/libx/gql/resolvers/field.js +5 -0
  152. package/libx/gql/resolvers/index.js +7 -0
  153. package/libx/gql/resolvers/mutation.js +23 -0
  154. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  155. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  156. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  157. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  158. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  159. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  160. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  161. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  162. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  164. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  165. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  166. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  167. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  168. package/libx/gql/resolvers/query.js +13 -0
  169. package/libx/gql/resolvers/root.js +34 -0
  170. package/libx/gql/schema/generate.js +18 -0
  171. package/libx/gql/schema/index.js +5 -0
  172. package/libx/gql/schema/mutation.js +76 -0
  173. package/libx/gql/schema/query.js +108 -0
  174. package/libx/gql/schema/typeDefMap.js +45 -0
  175. package/libx/gql/schema/utils/index.js +54 -0
  176. package/libx/gql/utils/index.js +12 -0
  177. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  178. package/libx/odata/index.js +80 -0
  179. package/libx/odata/odata2cqn/afterburner.js +170 -0
  180. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  181. package/libx/odata/odata2cqn/index.js +3 -0
  182. package/libx/odata/odata2cqn/parser.js +1 -0
  183. package/libx/odata/utils/index.js +64 -0
  184. package/libx/rest/RestAdapter.js +101 -0
  185. package/libx/rest/RestRequest.js +30 -0
  186. package/libx/rest/index.js +3 -0
  187. package/libx/rest/middleware/auth.js +22 -0
  188. package/libx/rest/middleware/content.js +15 -0
  189. package/libx/rest/middleware/create.js +40 -0
  190. package/libx/rest/middleware/delete.js +20 -0
  191. package/libx/rest/middleware/error.js +56 -0
  192. package/libx/rest/middleware/operation.js +39 -0
  193. package/libx/rest/middleware/parse.js +90 -0
  194. package/libx/rest/middleware/read.js +29 -0
  195. package/libx/rest/middleware/update.js +42 -0
  196. package/libx/rest/utils/data.js +65 -0
  197. package/package.json +4 -1
  198. package/server.js +29 -7
  199. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  200. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  201. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  202. package/libx/_runtime/common/utils/backlinks.js +0 -83
  203. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  204. package/libx/_runtime/odata/index.js +0 -55
  205. package/libx/_runtime/odata/odata2cqn.js +0 -1
  206. package/libx/_runtime/odata/readToCqn.js +0 -129
  207. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -0,0 +1,101 @@
1
+ const cds = require('../_runtime/cds')
2
+
3
+ const express = require('express')
4
+
5
+ const auth = require('./middleware/auth')
6
+ const content = require('./middleware/content')
7
+ const parse = require('./middleware/parse')
8
+
9
+ const create = require('./middleware/create')
10
+ const read = require('./middleware/read')
11
+ const update = require('./middleware/update')
12
+ const deleet = require('./middleware/delete')
13
+ const operation = require('./middleware/operation')
14
+
15
+ const error = require('./middleware/error')
16
+
17
+ // REVISIT: _commit_attempted workaround to avoid double rollback leading to release error
18
+ const _commit_attempted = Symbol()
19
+
20
+ class RestAdapter extends express.Router {
21
+ constructor(srv) {
22
+ super()
23
+
24
+ this.use(express.json())
25
+
26
+ // pass srv-reated stuff to middlewares via req
27
+ this.use('/', (req, res, next) => {
28
+ req._srv = srv
29
+ next()
30
+ })
31
+
32
+ // check @requires as soon as possible (DoS)
33
+ this.use('/', auth)
34
+
35
+ // content-type check
36
+ this.use('/', content)
37
+
38
+ // parse
39
+ this.use('/', parse)
40
+
41
+ // begin tx
42
+ this.use('/', (req, res, next) => {
43
+ // create tx and set as cds.context
44
+ // REVISIT: _model should not be necessary
45
+ req._tx = cds.context = srv.tx({ user: req.user, req, res, _model: req._srv.model })
46
+ next()
47
+ })
48
+
49
+ // POST
50
+ this.post('/*', (req, res, next) => {
51
+ if (req._operation) operation(req, res, next)
52
+ else create(req, res, next)
53
+ })
54
+
55
+ // GET
56
+ this.get('/*', (req, res, next) => {
57
+ if (req._operation) operation(req, res, next)
58
+ else read(req, res, next)
59
+ })
60
+
61
+ // PUT, PATCH, DELETE
62
+ this.put('/*', update)
63
+ this.patch('/*', update)
64
+ this.delete('/*', deleet)
65
+
66
+ // end tx (i.e., commit or rollback)
67
+ this.use('/', async (req, res, next) => {
68
+ const { result, status, location } = req._result
69
+
70
+ // unfortunately, express doesn't catch async errors -> try catch needed
71
+ try {
72
+ req._tx[_commit_attempted] = true
73
+ await req._tx.commit(result)
74
+ } catch (e) {
75
+ return next(e)
76
+ }
77
+
78
+ // TODO: cf. bufferToBase64() in old rest adapter
79
+
80
+ // only set status if not yet modified
81
+ if (status && res.statusCode === 200) res.status(status)
82
+ if (location) res.set('location', location)
83
+ res.send(result)
84
+ })
85
+ this.use('/', (err, req, res, next) => {
86
+ // request may fail during processing or during commit -> both caught here
87
+
88
+ // ignore rollback error, which should never happen
89
+ if (req._tx && !req._tx[_commit_attempted]) req._tx.rollback(err).catch(() => {})
90
+
91
+ next(err)
92
+ })
93
+
94
+ /*
95
+ * error handling
96
+ */
97
+ this.use(error)
98
+ }
99
+ }
100
+
101
+ module.exports = RestAdapter
@@ -0,0 +1,30 @@
1
+ const cds = require('../_runtime/cds')
2
+
3
+ class RestRequest extends cds.Request {
4
+ constructor(args) {
5
+ super(args)
6
+
7
+ // REVISIT: should not be necessary
8
+ /*
9
+ * propagate _ (i.e., req._ and, hence, req._.req/res)
10
+ * -> in the old adapters this is also set in OdataRequest/RestRequest
11
+ */
12
+ Object.setPrototypeOf(this._, cds.context._)
13
+
14
+ /*
15
+ * new req.res api [work in progress -> not official]:
16
+ * - req.res.status(202)
17
+ * - req.res.set('location', '/Books/301')
18
+ * this way is $batch compatible, i.e., the status and headers can be set on "subresponse"
19
+ */
20
+ this._status = null
21
+ this._headers = {}
22
+ const that = this
23
+ this.res = {
24
+ status: s => (that._status = s),
25
+ set: (k, v) => (that._headers[k] = v)
26
+ }
27
+ }
28
+ }
29
+
30
+ module.exports = RestRequest
@@ -0,0 +1,3 @@
1
+ const RestAdapter = require('./RestAdapter')
2
+
3
+ module.exports = srv => new RestAdapter(srv)
@@ -0,0 +1,22 @@
1
+ const { UNAUTHORIZED, FORBIDDEN } = require('../../_runtime/common/utils/auth')
2
+
3
+ const { getRequiresAsArray } = require('../../_runtime/common/utils/auth')
4
+
5
+ module.exports = (req, res, next) => {
6
+ if (!req._srv._requires) req._srv._requires = getRequiresAsArray(req._srv.definition)
7
+
8
+ const { _requires: requires } = req._srv
9
+
10
+ if (req.path !== '/' && requires.length > 0 && !requires.some(r => req.user.is(r))) {
11
+ // > unauthorized or forbidden?
12
+ if (req.user._is_anonymous) {
13
+ if (req.user._challenges) res.set('WWW-Authenticate', req.user._challenges.join(';'))
14
+ // REVISIT: security log in else case?
15
+ throw UNAUTHORIZED
16
+ }
17
+ // REVISIT: security log?
18
+ throw FORBIDDEN
19
+ }
20
+
21
+ next()
22
+ }
@@ -0,0 +1,15 @@
1
+ const PPP = { POST: 1, PUT: 1, PATCH: 1 }
2
+ const UPDATE = { PUT: 1, PATCH: 1 }
3
+
4
+ module.exports = (req, res, next) => {
5
+ if (PPP[req.method]) {
6
+ if (req.headers['content-type'] && req.headers['content-type'] !== 'application/json') {
7
+ throw { statusCode: 415, code: '415', message: 'INVALID_CONTENT_TYPE_ONLY_JSON' }
8
+ }
9
+ if (UPDATE[req.method] && Array.isArray(req.body)) {
10
+ throw { statusCode: 400, code: '400', message: `INVALID_${req.method}` }
11
+ }
12
+ }
13
+
14
+ next()
15
+ }
@@ -0,0 +1,40 @@
1
+ const cds = require('../../_runtime/cds')
2
+ const { INSERT } = cds.ql
3
+
4
+ const RestRequest = require('../RestRequest')
5
+
6
+ const _error4 = rejected =>
7
+ rejected.length > 1 ? { message: 'MULTIPLE_ERRORS', details: rejected.map(r => r.reason) } : rejected[0].reason
8
+
9
+ module.exports = async (_req, _res, next) => {
10
+ const { _srv: srv, _query: query, _target, _data } = _req
11
+
12
+ let result, location
13
+
14
+ // unfortunately, express doesn't catch async errors -> try catch needed
15
+ try {
16
+ // add the data
17
+ query.entries(_data)
18
+ if (query.INSERT.entries.length > 1) {
19
+ // > batch insert
20
+ const reqs = query.INSERT.entries.map(
21
+ entry => new RestRequest({ query: INSERT.into(query.INSERT.into).entries(entry), _target })
22
+ )
23
+ const ress = await Promise.allSettled(reqs.map(req => srv.dispatch(req)))
24
+ const rejected = ress.filter(r => r.status === 'rejected')
25
+ if (rejected.length) throw _error4(rejected)
26
+ result = ress.map(r => r.value)
27
+ } else {
28
+ // > single insert
29
+ const req = new RestRequest({ query, _target })
30
+ result = await srv.dispatch(req)
31
+ location = `../${req.entity.replace(srv.name + '.', '')}`
32
+ for (const k in req.target.keys) location += `/${result[k]}`
33
+ }
34
+ } catch (e) {
35
+ return next(e)
36
+ }
37
+
38
+ _req._result = { result, status: 201, location }
39
+ next()
40
+ }
@@ -0,0 +1,20 @@
1
+ const RestRequest = require('../RestRequest')
2
+
3
+ module.exports = async (_req, _res, next) => {
4
+ const { _srv: srv, _query: query, _target, _params } = _req
5
+
6
+ // unfortunately, express doesn't catch async errors -> try catch needed
7
+ try {
8
+ const req = new RestRequest({ query, _target })
9
+
10
+ // req.data is filled with keys during read and delete
11
+ if (_params) req.data = _params[_params.length - 1]
12
+
13
+ await srv.dispatch(req)
14
+ } catch (e) {
15
+ return next(e)
16
+ }
17
+
18
+ _req._result = { result: null, status: 204 }
19
+ next()
20
+ }
@@ -0,0 +1,56 @@
1
+ const cds = require('../../_runtime/cds')
2
+
3
+ // requesting logger without module on purpose!
4
+ const LOG = cds.log()
5
+
6
+ let _i18n
7
+ const i18n = (...args) => {
8
+ if (!_i18n) _i18n = require('../../_runtime/common/i18n')
9
+ return _i18n(...args)
10
+ }
11
+
12
+ const { normalizeError, isClientError } = require('../../_runtime/common/error/frontend')
13
+
14
+ const _log = err => {
15
+ const level = isClientError(err) ? 'warn' : 'error'
16
+ if ((level === 'warn' && !LOG._warn) || (level === 'error' && !LOG._error)) return
17
+
18
+ // replace messages in toLog with developer texts (i.e., undefined locale)
19
+ const _message = err.message
20
+ const _details = err.details
21
+ err.message = i18n(err.message || err.code, undefined, err.args) || err.message
22
+ if (err.details) {
23
+ const details = []
24
+ for (const d of err.details) {
25
+ details.push(Object.assign({}, d, { message: i18n(d.message || d.code, undefined, d.args) || d.message }))
26
+ }
27
+ err.details = details
28
+ }
29
+
30
+ // log it
31
+ LOG[level](err)
32
+
33
+ // restore
34
+ err.message = _message
35
+ if (_details) err.details = _details
36
+ }
37
+
38
+ // eslint-disable-next-line no-unused-vars
39
+ module.exports = (err, req, res, next) => {
40
+ const { _srv: srv } = req
41
+
42
+ // invoke srv.on('error', function (err, req) { ... }) here in special situations
43
+ let ctx = cds.context
44
+ if (!ctx) {
45
+ // > error before req was dispatched
46
+ ctx = new cds.Request({ req, res: req.res, user: req.user || new cds.User.Anonymous() })
47
+ for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
48
+ }
49
+
50
+ // log the error (4xx -> warn)
51
+ _log(err)
52
+
53
+ const { error, statusCode } = normalizeError(err, req)
54
+
55
+ res.status(statusCode).send({ error })
56
+ }
@@ -0,0 +1,39 @@
1
+ const { validateReturnType } = require('../../_runtime/cds-services/adapter/rest/utils/validation-checks')
2
+
3
+ const RestRequest = require('../RestRequest')
4
+
5
+ module.exports = async (_req, _res, next) => {
6
+ const { _srv: srv, _query: query, _operation: operation, _data: data } = _req
7
+
8
+ let result
9
+
10
+ // unfortunately, express doesn't catch async errors -> try catch needed
11
+ try {
12
+ const req = query
13
+ ? new RestRequest({ query, event: operation.name, data })
14
+ : new RestRequest({ event: operation.name.replace(`${srv.name}.`, ''), data })
15
+ result = await srv.dispatch(req)
16
+ } catch (e) {
17
+ return next(e)
18
+ }
19
+
20
+ if (!operation.returns) {
21
+ _req._result = { status: 204 }
22
+ } else {
23
+ // unfortunately, express doesn't catch async errors -> try catch needed
24
+ try {
25
+ // REVISIT: do not use from old rest adapter
26
+ // REVISIT: new impl should return instead of throwing to avoid try catch
27
+ validateReturnType(operation, result)
28
+ } catch (e) {
29
+ return next(e)
30
+ }
31
+
32
+ // REVISIT: still needed?
33
+ if (!operation.returns.items && Array.isArray(result)) result = result[0]
34
+
35
+ _req._result = { result }
36
+ }
37
+
38
+ next()
39
+ }
@@ -0,0 +1,90 @@
1
+ const cds = require('../../_runtime/cds')
2
+ const { INSERT, SELECT, UPDATE, DELETE } = cds.ql
3
+
4
+ const { getDeepCopy } = require('../utils/data')
5
+
6
+ const { where2obj } = require('../../_runtime/common/utils/cqn')
7
+
8
+ module.exports = (req, res, next) => {
9
+ const { _srv: service } = req
10
+ const { model } = service
11
+
12
+ let query = cds.odata.parse(req.url, { service })
13
+
14
+ // parser always produces selects
15
+ const _target = (req._target = query.SELECT && query.SELECT.from)
16
+ if (!_target) return next()
17
+
18
+ // REVISIT: __target is the csn target definition
19
+ const {
20
+ __target: definition,
21
+ SELECT: { one }
22
+ } = query
23
+ delete query.__target
24
+
25
+ // REVISIT: hack for actions and functions
26
+ let operation, args
27
+ const last = _target.ref[_target.ref.length - 1]
28
+ if (last.operation) {
29
+ operation = last.operation
30
+ if (last.args) args = last.args
31
+ _target.ref.pop()
32
+ }
33
+
34
+ const unbound = _target.ref.length === 0
35
+
36
+ // query based on method
37
+ switch (req.method) {
38
+ case 'GET':
39
+ if (operation) {
40
+ // function
41
+ req._operation = operation = definition.kind === 'function' ? definition : definition.actions[operation]
42
+ if (!unbound) query = one ? SELECT.one(_target) : SELECT.from(_target)
43
+ else query = undefined
44
+ } else {
45
+ // read (nothing to do)
46
+ }
47
+ break
48
+ case 'POST':
49
+ if (operation) {
50
+ // action
51
+ req._operation = operation = definition.kind === 'action' ? definition : definition.actions[operation]
52
+ if (!unbound) query = one ? SELECT.one(_target) : SELECT.from(_target)
53
+ else query = undefined
54
+ } else {
55
+ // create
56
+ if (one) cds.error('POST not allowed on entity', { code: 400 })
57
+ query = INSERT.into(_target)
58
+ }
59
+ break
60
+ case 'PUT':
61
+ case 'PATCH':
62
+ if (!one) throw { statusCode: 400, code: '400', message: `INVALID_${req.method}` }
63
+ query = UPDATE(_target)
64
+ break
65
+ case 'DELETE':
66
+ if (!one) cds.error('DELETE not allowed on collection', { code: 400 })
67
+ query = DELETE.from(_target)
68
+ break
69
+ default:
70
+ // anything to do?
71
+ }
72
+ req._query = query
73
+
74
+ // REVISIT: query._data hack
75
+ // deep copy of body (incl. validations such as correct data type)
76
+ if ((query && (query.INSERT || query.UPDATE)) || (operation && operation.kind === 'action') || args) {
77
+ const [validations, copy] = getDeepCopy(args || req.body, operation || definition, model, req.method !== 'POST')
78
+ if (validations) throw validations
79
+ req._data = copy
80
+ }
81
+
82
+ // REVISIT: req.params as documented
83
+ for (let i = 0; i < _target.ref.length; i++) {
84
+ req._params = req._params || []
85
+ if (_target.ref[i].where) req._params.push(where2obj(_target.ref[i].where))
86
+ else req._params.push({})
87
+ }
88
+
89
+ next()
90
+ }
@@ -0,0 +1,29 @@
1
+ const RestRequest = require('../RestRequest')
2
+
3
+ module.exports = async (_req, _res, next) => {
4
+ const { _srv: srv, _query: query, _target, _params } = _req
5
+
6
+ let result,
7
+ status = 200
8
+
9
+ // unfortunately, express doesn't catch async errors -> try catch needed
10
+ try {
11
+ const req = new RestRequest({ query, _target })
12
+
13
+ // req.data is filled with keys during read and delete
14
+ if (_params) req.data = _params[_params.length - 1]
15
+
16
+ result = await srv.dispatch(req)
17
+
18
+ // 204 or 404?
19
+ if (result === null && query.SELECT.one) {
20
+ if (_target.ref.length > 1) status = 204
21
+ else throw { code: 404 }
22
+ }
23
+ } catch (e) {
24
+ return next(e)
25
+ }
26
+
27
+ _req._result = { result, status }
28
+ next()
29
+ }
@@ -0,0 +1,42 @@
1
+ const cds = require('../../_runtime/cds')
2
+ const { INSERT } = cds.ql
3
+
4
+ const RestRequest = require('../RestRequest')
5
+
6
+ const UPSERT_ALLOWED = !(cds.env.runtime && cds.env.runtime.allow_upsert === false)
7
+
8
+ const { deepCopyObject } = require('../../_runtime/common/utils/copy')
9
+
10
+ module.exports = async (_req, _res, next) => {
11
+ let { _srv: srv, _query: query, _target, _data, _params } = _req
12
+
13
+ let result,
14
+ status = 200
15
+
16
+ // unfortunately, express doesn't catch async errors -> try catch needed
17
+ try {
18
+ // if upsert it allowed, we need to catch 404 and retry with create
19
+ try {
20
+ // add the data (as copy, if upsert allowed)
21
+ query.with(UPSERT_ALLOWED ? deepCopyObject(_data) : _data)
22
+ // REVISIT: if PUT, req.method should be PUT -> Crud2Http maps UPSERT to PUT
23
+ result = await srv.dispatch(new RestRequest({ query, _target, method: _req.method }))
24
+ if (_params) Object.assign(result, _params[_params.length - 1])
25
+ } catch (e) {
26
+ if ((e.code === 404 || e.status === 404 || e.statusCode === 404) && UPSERT_ALLOWED) {
27
+ query = INSERT.into(query.UPDATE.entity).entries(
28
+ _params ? Object.assign(_data, _params[_params.length - 1]) : _data
29
+ )
30
+ result = await srv.dispatch(new RestRequest({ query, _target }))
31
+ status = 201
32
+ } else {
33
+ throw e
34
+ }
35
+ }
36
+ } catch (e) {
37
+ return next(e)
38
+ }
39
+
40
+ _req._result = { result, status }
41
+ next()
42
+ }
@@ -0,0 +1,65 @@
1
+ const cds = require('../../_runtime/cds')
2
+
3
+ const { deepCopyObject } = require('../../_runtime/common/utils/copy')
4
+ const { checkKeys, checkStatic } = require('../../_runtime/cds-services/util/assert')
5
+ const { MULTIPLE_ERRORS } = require('../../_runtime/common/error/constants')
6
+
7
+ // this can be reused for flattening data on db layer, if necessary
8
+ // const _getFlattenedCopy = (data, key, entity) => {
9
+ // const d = {}
10
+ // const prefix = key + '_'
11
+ // // TODO: ignore or preserve unknown?
12
+ // const matches = Object.keys(entity.elements)
13
+ // .filter(ele => ele.startsWith(prefix))
14
+ // .map(ele => ele.replace(prefix, ''))
15
+ // if (matches.length) {
16
+ // const current = data[key]
17
+ // for (const k of matches) if (current[k] !== undefined) d[prefix + k] = current[k]
18
+ // const nested = Object.keys(current).filter(k => current[k] && typeof current[k] === 'object')
19
+ // for (const k of nested) Object.assign(d, _getFlattenedCopy({ [prefix + k]: current[k] }, prefix + k, entity))
20
+ // }
21
+ // return d
22
+ // }
23
+
24
+ const _getDeepCopy = (data, definition, model, validations, skipKeys) => {
25
+ skipKeys || (definition.keys && validations.push(...checkKeys(definition, data)))
26
+ validations.push(...checkStatic(definition, data, true))
27
+
28
+ const d = {}
29
+ for (const k in data) {
30
+ const element = (definition.elements && definition.elements[k]) || (definition.params && definition.params[k])
31
+ if (!element) {
32
+ const { additional_properties } = cds.env.features
33
+ if (additional_properties === 'ignore' || !additional_properties) {
34
+ // ignore input (the default)
35
+ } else if (additional_properties === 'error') {
36
+ validations.push({ message: `Unknown property "${k}"`, code: 400 })
37
+ } else {
38
+ d[k] = data[k] && typeof data[k] === 'object' ? deepCopyObject(data[k]) : data[k]
39
+ }
40
+ } else if (element.isAssociation) {
41
+ d[k] = Array.isArray(data[k])
42
+ ? data[k].map(d => _getDeepCopy(d, model.definitions[element.target], model, validations))
43
+ : _getDeepCopy(data[k], model.definitions[element.target], model, validations)
44
+ } else if (element._isStructured) {
45
+ d[k] = _getDeepCopy(data[k], element, model, validations)
46
+ } else {
47
+ d[k] = data[k]
48
+ }
49
+ }
50
+ return d
51
+ }
52
+
53
+ const getDeepCopy = (data, definition, model, skipKeys) => {
54
+ const validations = []
55
+ const copy = Array.isArray(data)
56
+ ? data.map(d => _getDeepCopy(d, definition, model, validations, skipKeys))
57
+ : _getDeepCopy(data, definition, model, validations, skipKeys)
58
+ if (!validations.length) return [undefined, copy]
59
+ if (validations.length === 1) return [validations[0]]
60
+ return [Object.assign(new Error(MULTIPLE_ERRORS), { details: validations })]
61
+ }
62
+
63
+ module.exports = {
64
+ getDeepCopy
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "5.5.5",
3
+ "version": "5.6.3",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -40,6 +40,9 @@
40
40
  }
41
41
  },
42
42
  "lint-staged": {
43
+ "libx/odata/odata2cqn/grammar.pegjs": [
44
+ "npm run pegjs:odata2cqn && git add libx/odata/odata2cqn/parser.js"
45
+ ],
43
46
  "{libx,tests/_runtime}/**/*.js": [
44
47
  "npx prettier --write"
45
48
  ]
package/server.js CHANGED
@@ -40,16 +40,23 @@ module.exports = async function cds_server (options, o = { ...options, __proto__
40
40
  if (o.logger) app.use (o.logger) //> basic request logging
41
41
  if (o.toggler) app.use (o.toggler) //> feature toggler
42
42
 
43
+ // give uiflex a chance to plug into everything
44
+ if (cds.requires.extensibility) await require('./libx/_runtime/fiori/uiflex')() // REVISIT: later this should be a ext umbrella service
45
+
43
46
  // load specified models or all in project
44
47
  const csn = await cds.load (o.from||'*', {mocked:o.mocked})
45
- cds.model = o.from = cds.linked (cds.compile.for.odata(csn))
48
+ const m = cds.linked(csn).minified()
49
+ cds.model = o.from = cds.linked (cds.compile.for.odata(m))
46
50
 
47
51
  // connect to essential framework services if required
48
- const _init = o.in_memory && (db => cds.deploy(csn).to(db,o))
52
+ const _init = o.in_memory && (db => cds.deploy(m).to(db,o))
49
53
  if (cds.requires.db) cds.db = await cds.connect.to ('db') .then (_init)
50
54
  if (cds.requires.messaging) await cds.connect.to ('messaging')
51
55
  if (cds.requires.multitenancy) await cds.mtx.in (app)
52
56
 
57
+ // serve graphql
58
+ if (cds.env.features.graphql) serve_graphql(app)
59
+
53
60
  // serve all services declared in models
54
61
  await cds.serve (o.service,o).in (app)
55
62
  await cds.emit ('served', cds.services) //> hook for listeners
@@ -113,6 +120,16 @@ const _app_serve = function (endpoint) { return {
113
120
  }}
114
121
 
115
122
 
123
+ // register graphql router on served event
124
+ function serve_graphql (app) {
125
+ cds.on('served', services => {
126
+ const GraphQLAdapter = require('./libx/gql/GraphQLAdapter')
127
+ app.use(new GraphQLAdapter(services, { graphiql: true }))
128
+ cds.log()("serving GraphQL endpoint for all services { at: '/graphql' }")
129
+ })
130
+ }
131
+
132
+
116
133
  function cors (req, res, next) {
117
134
  const { origin } = req.headers
118
135
  if (origin) res.set('access-control-allow-origin', origin)
@@ -121,17 +138,22 @@ function cors (req, res, next) {
121
138
  next()
122
139
  }
123
140
 
124
- function correlate (req,_,next) {
125
- if (!cds.context) cds.context = {}
126
- const id = cds.context.id
127
- || req.headers['x-correlation-id'] || req.headers['x-correlationid']
141
+ function correlate (req, res, next) {
142
+ // derive correlation id from req
143
+ const id = req.headers['x-correlation-id'] || req.headers['x-correlationid']
128
144
  || req.headers['x-request-id'] || req.headers['x-vcap-request-id']
129
145
  || cds.utils.uuid()
130
- cds.context.id = req.headers['x-correlation-id'] = id
146
+ // new intermediate cds.context, if necessary
147
+ if (!cds.context) cds.context = { id }
148
+ // guarantee x-correlation-id going forward and set on res
149
+ req.headers['x-correlation-id'] = id
150
+ res.set('x-correlation-id', id)
151
+ // guaranteed access to cds.context._.req -> REVISIT
131
152
  if (!cds.context._) cds.context._ = {}
132
153
  if (!cds.context._.req) cds.context._.req = req
133
154
  next()
134
155
  }
135
156
 
157
+
136
158
  // -------------------------------------------------------------------------
137
159
  if (!module.parent) module.exports ({from:process.argv[2]})