@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,3 +1,5 @@
1
+ /*
2
+
1
3
  const cds = require('../../_runtime/cds')
2
4
 
3
5
  // requesting logger without module on purpose!
@@ -36,23 +38,24 @@ const _log = err => {
36
38
  if (_details) err.details = _details
37
39
  }
38
40
 
39
- // eslint-disable-next-line no-unused-vars
40
- module.exports = srv => (err, req, res, next) => {
41
- // REVISIT: invoking service.on('error') handlers needs a cleanup with new protocol adapters!!!
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.default })
47
- for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
48
- } else if (ctx._tx?._done !== 'rolled back') {
49
- for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
50
- }
41
+ */
42
+
43
+ const _log = require('../../_runtime/common/error/log')
51
44
 
52
- // log the error (4xx -> warn)
53
- _log(err)
45
+ const { normalizeError } = require('../../_runtime/common/error/frontend')
54
46
 
55
- const { error, statusCode } = normalizeError(err, req)
47
+ module.exports = () => {
48
+ return function rest_error(err, req, res, next) {
49
+ if (err == 401 || err.code == 401) return next(err) // speed up logins, at least temporary until we reviewed and eliminated overhead that may be involved below
50
+ // REVISIT: keep?
51
+ // log the error (4xx -> warn)
52
+ _log(err)
56
53
 
57
- res.status(statusCode).send({ error })
54
+ const { error, statusCode } = normalizeError(err, req)
55
+
56
+ // NOTE: express also looks for numbers in err.status or err.statusCode
57
+ error.statusCode = statusCode
58
+
59
+ next(error)
60
+ }
58
61
  }
@@ -1,11 +1,10 @@
1
1
  const cds = require('../../_runtime/cds')
2
2
 
3
- const RestRequest = require('../RestRequest')
4
-
5
3
  const { checkStatic } = require('../../_runtime/cds-services/util/assert')
6
4
  const getError = require('../../_runtime/common/error')
7
5
 
8
- const typeCheckers = require('../../common/assert/type')
6
+ // REVISIT: strict or relaxed type checkers?
7
+ const typeCheckers = require('../../common/assert/type-strict')
9
8
 
10
9
  // REVISIT: use i18n
11
10
  const _enrichErrorDetails = (isPrimitive, error) => {
@@ -38,7 +37,6 @@ const _validateReturnType = (operation, data) => {
38
37
 
39
38
  // .type of action/function behaves different to .type of other csn elements
40
39
  // Return type contains primitives
41
- // eslint-disable-next-line no-proto
42
40
  const _type = typeof returnType._type === 'object' ? returnType.__proto__._type : returnType._type // REVISIT: super dirty hack for compiler's to.edmx polluting the csn definitions with ._type -> please use Symbols instead
43
41
  const typeChecker = typeCheckers[_type] // IMPORTANT: use ._type
44
42
  if (typeChecker) {
@@ -71,45 +69,54 @@ const _validateReturnType = (operation, data) => {
71
69
  return true
72
70
  }
73
71
 
74
- // TODO: add headers to return object to avoid passing _res
75
- module.exports = srv => async (_req, _res) => {
76
- const { _query: query, _operation: operation, _data: data, _params } = _req
77
-
78
- let result
79
-
80
- const req = query
81
- ? new RestRequest({ query, event: operation.name, data, params: _params })
82
- : new RestRequest({ event: operation.name.replace(`${srv.namespace}.`, ''), data, params: _params })
83
- result = await srv.dispatch(req)
84
-
85
- if (!operation.returns) return { status: 204 }
86
-
87
- // REVISIT: do not use from old rest adapter
88
- // REVISIT: new impl should return instead of throwing to avoid try catch
89
- _validateReturnType(operation, result)
90
-
91
- // set content-type header to text/plain for returned primitive data types, except for boolean
92
- const returnType = operation.returns._type
93
- if (
94
- !_res.get('content-type') &&
95
- !operation.returns.items &&
96
- returnType &&
97
- cds.builtin.types[returnType] &&
98
- returnType !== 'cds.Boolean'
99
- ) {
100
- _res.set('content-type', 'text/plain')
101
- }
72
+ module.exports = adapter => {
73
+ const { service } = adapter
102
74
 
103
- // REVISIT: Still needed with cds-mtxs?
104
- // mtx compat, modeled as string but object returned
105
- if (operation.returns._type === 'cds.String' && typeof result === 'object') {
106
- _res.set('content-type', 'application/json')
107
- }
75
+ return async function operation(req, res) {
76
+ const { _query: query, _operation: operation, _data: data, _params: params } = req
77
+
78
+ let result
79
+
80
+ result = await service.dispatch(
81
+ adapter.request4(
82
+ Object.assign(
83
+ query
84
+ ? { query, event: operation.name }
85
+ : { event: operation.name.replace(`${service.definition.name}.`, '') },
86
+ { data, params, req, res }
87
+ )
88
+ )
89
+ )
90
+
91
+ if (!operation.returns) return { status: 204 }
92
+
93
+ // REVISIT: do not use from old rest adapter
94
+ // REVISIT: new impl should return instead of throwing to avoid try catch
95
+ _validateReturnType(operation, result)
96
+
97
+ // set content-type header to text/plain for returned primitive data types, except for boolean
98
+ const returnType = operation.returns._type
99
+ if (
100
+ !res.get('content-type') &&
101
+ !operation.returns.items &&
102
+ returnType &&
103
+ cds.builtin.types[returnType] &&
104
+ returnType !== 'cds.Boolean'
105
+ ) {
106
+ res.set('Content-Type', 'text/plain')
107
+ }
108
108
 
109
- // REVISIT: still needed?
110
- if (!operation.returns.items && Array.isArray(result)) result = result[0]
109
+ // REVISIT: Still needed with cds-mtxs?
110
+ // mtx compat, modeled as string but object returned
111
+ if (operation.returns._type === 'cds.String' && typeof result === 'object') {
112
+ res.set('Content-Type', 'application/json')
113
+ }
111
114
 
112
- if (result === undefined) return { status: 204 }
115
+ // REVISIT: still needed?
116
+ if (!operation.returns.items && Array.isArray(result)) result = result[0]
113
117
 
114
- return { result }
118
+ if (result === undefined) return { status: 204 }
119
+
120
+ return { result }
121
+ }
115
122
  }
@@ -1,13 +1,13 @@
1
1
  const cds = require('../../_runtime/cds')
2
2
  const { INSERT, SELECT, UPDATE, DELETE } = cds.ql
3
3
 
4
+ const { getKeysAndParamsFromPath } = require('../../common/utils')
5
+
4
6
  const { base64ToBuffer } = require('../../_runtime/common/utils/binary')
5
- const { deepCopy } = require('../../_runtime/common/utils/copy')
6
- const { where2obj } = require('../../_runtime/common/utils/cqn')
7
7
  const { convertStructured } = require('../../_runtime/common/utils/ucsn')
8
-
9
8
  const getTemplate = require('../../_runtime/common/utils/template')
10
9
  const templateProcessor = require('../../_runtime/common/utils/templateProcessor')
10
+
11
11
  const { checkStaticElementByKey } = require('../../_runtime/cds-services/util/assert')
12
12
 
13
13
  const _processorFn = errors => {
@@ -30,140 +30,142 @@ const _picker = element => {
30
30
 
31
31
  const _cache = req => `rest-input;skip-key-validation:${req.method !== 'POST'}`
32
32
 
33
- module.exports = srv => (req, res, next) => {
34
- // REVISIT: Once we don't display the error message location in terms of an offset, but instead a copy of the
35
- // original request including a marker, we don't need to provide the baseUrl here.
36
- let query = cds.odata.parse(req.url, { service: srv, baseUrl: req.baseUrl })
37
-
38
- // parser always produces selects
39
- const _target = (req._target = query.SELECT && query.SELECT.from)
40
- if (!_target) return next()
41
-
42
- // REVISIT: __target is the csn target definition
43
- let {
44
- __target: definition,
45
- SELECT: { one }
46
- } = query
47
- if (typeof definition === 'string') {
48
- definition =
49
- srv.model.definitions[definition] ||
50
- srv.model.definitions[definition.split(':$:')[0]].actions[definition.split(':$:')[1]]
51
- }
52
- delete query.__target
53
-
54
- // REVISIT: hack for actions and functions
55
- let operation, args
56
- const last = _target.ref[_target.ref.length - 1]
57
- if (last.operation) {
58
- operation = last.operation
59
- if (last.args) args = last.args
60
- _target.ref.pop()
61
- }
33
+ module.exports = adapter => {
34
+ const { service } = adapter
62
35
 
63
- const unbound = _target.ref.length === 0
64
-
65
- // query based on method
66
- switch (req.method) {
67
- case 'HEAD':
68
- case 'GET':
69
- if (operation) {
70
- req._operation = operation = definition
71
- if (operation.kind === 'action') cds.error('Action must be called by POST', { code: 400 })
72
- if (!unbound) query = one ? SELECT.one(_target) : SELECT.from(_target)
73
- else query = undefined
74
- } else {
75
- // read (nothing to do)
76
- }
77
- break
78
- case 'POST':
79
- if (operation) {
80
- req._operation = operation = definition
81
- if (operation.kind === 'function') cds.error('Function must be called by GET', { code: 400 })
82
- if (!unbound) query = one ? SELECT.one(_target) : SELECT.from(_target)
83
- else query = undefined
84
- } else {
85
- // create
86
- if (one) cds.error('POST not allowed on entity', { code: 400 })
87
- query = INSERT.into(_target)
88
- }
89
- break
90
- case 'PUT':
91
- case 'PATCH':
92
- if (operation) {
93
- let errorMsg
94
- if (definition) {
95
- errorMsg = `${definition.kind.charAt(0).toUpperCase() + definition.kind.slice(1)} must be called by ${
96
- definition.kind === 'action' ? 'POST' : 'GET'
97
- }`
36
+ return function parse(req, res, next) {
37
+ // REVISIT: Once we don't display the error message location in terms of an offset, but instead a copy of the
38
+ // original request including a marker, we don't need to provide the baseUrl here.
39
+ let query = cds.odata.parse(req.url, { service, baseUrl: req.baseUrl, protocol: 'rest' })
40
+
41
+ // parser always produces selects
42
+ const _target = (req._target = query.SELECT && query.SELECT.from)
43
+ if (!_target) return next()
44
+
45
+ // REVISIT: __target is the csn target definition
46
+ let {
47
+ __target: definition,
48
+ SELECT: { from, one }
49
+ } = query
50
+ if (typeof definition === 'string') {
51
+ definition =
52
+ service.model.definitions[definition] ||
53
+ service.model.definitions[definition.split(':$:')[0]].actions[definition.split(':$:')[1]]
54
+ }
55
+ delete query.__target
56
+
57
+ // req.__proto__.method is set in case of upsert
58
+ const isUpsert = req.__proto__.method in { PUT: 1, PATCH: 1 }
59
+
60
+ // REVISIT: hack for actions and functions
61
+ let operation, args
62
+ const last = _target.ref[_target.ref.length - 1]
63
+ if (last.operation) {
64
+ operation = last.operation
65
+ if (last.args) args = last.args
66
+ _target.ref.pop()
67
+ }
68
+
69
+ const unbound = _target.ref.length === 0
70
+
71
+ // query based on method
72
+ switch (req.method) {
73
+ case 'HEAD':
74
+ case 'GET':
75
+ if (operation) {
76
+ req._operation = operation = definition
77
+ if (operation.kind === 'action') cds.error('Action must be called by POST', { code: 400 })
78
+ if (!unbound) query = one ? SELECT.one(_target) : SELECT.from(_target)
79
+ else query = undefined
98
80
  } else {
99
- errorMsg = `That action/function must be called by POST/GET`
81
+ // read (nothing to do)
100
82
  }
101
- cds.error(errorMsg, { code: 400 })
102
- }
103
- if (!one) throw { statusCode: 400, code: '400', message: `INVALID_${req.method}` }
104
- query = UPDATE(_target)
105
- break
106
- case 'DELETE':
107
- if (operation) {
108
- let errorMsg
109
- if (definition) {
110
- errorMsg = `${definition.kind.charAt(0).toUpperCase() + definition.kind.slice(1)} must be called by ${
111
- definition.kind === 'action' ? 'POST' : 'GET'
112
- }`
83
+ break
84
+ case 'POST':
85
+ if (operation) {
86
+ req._operation = operation = definition
87
+ if (operation.kind === 'function') cds.error('Function must be called by GET', { code: 400 })
88
+ if (!unbound) query = one ? SELECT.one(_target) : SELECT.from(_target)
89
+ else query = undefined
113
90
  } else {
114
- errorMsg = `That action/function must be called by POST/GET`
91
+ // create
92
+ if (one && !isUpsert) cds.error('POST not allowed on entity', { code: 400 })
93
+ query = INSERT.into(_target)
115
94
  }
116
- cds.error(errorMsg, { code: 400 })
117
- }
118
- if (!one) cds.error('DELETE not allowed on collection', { code: 400 })
119
- query = DELETE.from(_target)
120
- break
121
- default:
122
- // anything to do?
123
- }
124
- req._query = query // REVISIT: req._query should not be a standard API
125
- if (query && definition) req._query.__target = definition.name
126
-
127
- // REVISIT: query._data hack
128
- if ((query && (query.INSERT || query.UPDATE || query.DELETE)) || (operation && operation.kind === 'action') || args) {
129
- if (operation && (operation.kind === 'action' || operation.kind === 'function') && !operation.params) {
130
- req._data = {}
131
- } else {
132
- // TODO: add keys from url into payload (overwriting if already present) -> document this behavior, also for OData
133
- const payload = deepCopy(args || req.body)
134
- let errs
135
- if (cds.env.features.cds_assert) {
136
- const assertOptions = {
137
- filter: true,
138
- http: { req },
139
- mandatories: req.method === 'POST' || req.method === 'PUT' || undefined
95
+ break
96
+ case 'PUT':
97
+ case 'PATCH':
98
+ if (operation) {
99
+ let errorMsg
100
+ if (definition) {
101
+ errorMsg = `${definition.kind.charAt(0).toUpperCase() + definition.kind.slice(1)} must be called by ${
102
+ definition.kind === 'action' ? 'POST' : 'GET'
103
+ }`
104
+ } else {
105
+ errorMsg = `That action/function must be called by POST/GET`
106
+ }
107
+ cds.error(errorMsg, { code: 400 })
108
+ }
109
+ if (!one) throw { statusCode: 400, code: '400', message: `INVALID_${req.method}` }
110
+ query = UPDATE(_target)
111
+ break
112
+ case 'DELETE':
113
+ if (operation) {
114
+ let errorMsg
115
+ if (definition) {
116
+ errorMsg = `${definition.kind.charAt(0).toUpperCase() + definition.kind.slice(1)} must be called by ${
117
+ definition.kind === 'action' ? 'POST' : 'GET'
118
+ }`
119
+ } else {
120
+ errorMsg = `That action/function must be called by POST/GET`
121
+ }
122
+ cds.error(errorMsg, { code: 400 })
140
123
  }
141
- errs = cds.assert(payload, definition, assertOptions)
124
+ if (!one) cds.error('DELETE not allowed on collection', { code: 400 })
125
+ query = DELETE.from(_target)
126
+ break
127
+ default:
128
+ // anything to do?
129
+ }
130
+ req._query = query // REVISIT: req._query should not be a standard API
131
+ if (query && definition) req._query.__target = definition.name
132
+
133
+ const { keys, params } = getKeysAndParamsFromPath(from, service)
134
+ req._data = keys
135
+ if (params) req._params = params
136
+
137
+ // REVISIT: query._data hack
138
+ if (
139
+ (query && (query.INSERT || query.UPDATE || query.DELETE)) ||
140
+ (operation && operation.kind === 'action') ||
141
+ args
142
+ ) {
143
+ if (operation && (operation.kind === 'action' || operation.kind === 'function') && !operation.params) {
144
+ req._data = {}
142
145
  } else {
143
- convertStructured(srv, operation || definition, payload, { cleanupStruct: cds.env.features.rest_struct_data })
144
- const template = getTemplate(_cache(req), srv, definition, { pick: _picker })
145
- if (template && template.elements.size) {
146
- errs = []
147
- for (const row of Array.isArray(payload) ? payload : [payload]) {
148
- templateProcessor({ processFn: _processorFn(errs), row, template })
146
+ const payload = args || req.body
147
+ if (!operation) Object.assign(payload, keys)
148
+ if (!cds.env.features.cds_validate) {
149
+ const errs = []
150
+ convertStructured(service, operation || definition, payload, {
151
+ cleanupStruct: cds.env.features.rest_struct_data
152
+ })
153
+ const template = getTemplate(_cache(req), service, definition, { pick: _picker })
154
+ if (template && template.elements.size) {
155
+ for (const row of Array.isArray(payload) ? payload : [payload]) {
156
+ templateProcessor({ processFn: _processorFn(errs), row, template })
157
+ }
158
+ }
159
+ if (errs.length) {
160
+ if (errs.length === 1) throw errs[0]
161
+ throw Object.assign(new Error('MULTIPLE_ERRORS'), { statusCode: 400, details: errs })
149
162
  }
150
163
  }
164
+ base64ToBuffer(payload, service, definition)
165
+ req._data = payload
151
166
  }
152
- if (errs?.length) {
153
- if (errs.length === 1) throw errs[0]
154
- throw Object.assign(new Error('MULTIPLE_ERRORS'), { statusCode: 400, details: errs })
155
- }
156
- base64ToBuffer(payload, srv, definition)
157
- req._data = payload
158
167
  }
159
- }
160
168
 
161
- // REVISIT: req.params as documented
162
- for (let i = 0; i < _target.ref.length; i++) {
163
- req._params = req._params || []
164
- if (_target.ref[i].where) req._params.push(where2obj(_target.ref[i].where))
165
- else if (_target.ref.length !== 1) req._params.push({})
169
+ next()
166
170
  }
167
-
168
- next()
169
171
  }
@@ -1,35 +1,28 @@
1
- const RestRequest = require('../RestRequest')
1
+ module.exports = adapter => {
2
+ const { service } = adapter
2
3
 
3
- module.exports = srv => async _req => {
4
- const { _query: query, _target, _params } = _req
4
+ return async function read(req, res) {
5
+ const { _query: query, _target: target, _data: data, _params: params } = req
5
6
 
6
- let result,
7
- status = 200
7
+ let result,
8
+ status = 200
8
9
 
9
- const req = new RestRequest({ query, _target, params: _params, _req })
10
+ result = await service.dispatch(adapter.request4({ query, data, params, req, res }))
10
11
 
11
- // req.data is filled with keys during read and delete
12
- if (_params) req.data = _params[_params.length - 1]
13
-
14
- result = await srv.dispatch(req)
15
-
16
- // 204 or 404?
17
- if (result == null && query.SELECT.one) {
18
- if (_target.ref.length > 1) status = 204
19
- else throw { code: 404 }
20
- }
12
+ // 204 or 404?
13
+ if (result == null && query.SELECT.one) {
14
+ if (target.ref.length > 1) status = 204
15
+ else throw { code: 404 }
16
+ }
21
17
 
22
- // REVISIT: Still needed with cds-mtxs?
23
- // compat for mtx returning strings instead of objects
24
- if (typeof result === 'object' && result !== null && '$count' in result) {
25
- result = {
26
- count: result.$count,
27
- value: result
18
+ // REVISIT: Still needed with cds-mtxs?
19
+ // compat for mtx returning strings instead of objects
20
+ if (typeof result === 'object' && result !== null && '$count' in result) {
21
+ result = { count: result.$count, value: result }
22
+ } else if (typeof result === 'number') {
23
+ result = result.toString()
28
24
  }
29
- } else if (typeof result === 'number') {
30
- // TODO check if this is needed
31
- result = result.toString()
32
- }
33
25
 
34
- return { result, status }
26
+ return { result, status }
27
+ }
35
28
  }
@@ -1,40 +1,35 @@
1
1
  const cds = require('../../_runtime/cds')
2
- const { INSERT } = cds.ql
3
-
4
- const RestRequest = require('../RestRequest')
5
2
 
6
3
  const UPSERT_ALLOWED = !(cds.env.runtime && cds.env.runtime.allow_upsert === false)
7
4
 
8
- const { deepCopyObject } = require('../../_runtime/common/utils/copy')
9
-
10
- module.exports = srv => async _req => {
11
- let { _query: query, _target, _data, _params } = _req
12
-
13
- let result,
14
- status = 200
5
+ module.exports = adapter => {
6
+ const { router, service } = adapter
7
+
8
+ return async function update(req, res, next) {
9
+ let { _query: query, _data: data, _params: params } = req
10
+
11
+ let result,
12
+ status = 200
13
+
14
+ // if upsert it allowed, we need to catch 404 and retry with create
15
+ try {
16
+ query.data(data)
17
+ // REVISIT: if PUT, req.method should be PUT -> Crud2Http maps UPSERT to PUT
18
+ result = await service.dispatch(adapter.request4({ query, method: req.method, params, req, res }))
19
+ } catch (e) {
20
+ const is404 = e.code === 404 || e.status === 404 || e.statusCode === 404
21
+ const isForcedInsert =
22
+ (e.code === 412 || e.status === 412 || e.statusCode === 412) && req.headers['if-none-match'] === '*'
23
+ if ((is404 || isForcedInsert) && UPSERT_ALLOWED) {
24
+ // -> redirect to POST
25
+ const _body = JSON.parse(req._raw)
26
+ const _req = Object.assign(Object.create(req), { method: 'POST', body: _body })
27
+ return router.handle(_req, res, next)
28
+ }
15
29
 
16
- // if upsert it allowed, we need to catch 404 and retry with create
17
- try {
18
- // add the data (as copy, if upsert allowed)
19
- query.data(UPSERT_ALLOWED ? deepCopyObject(_data) : _data)
20
-
21
- // REVISIT: if PUT, req.method should be PUT -> Crud2Http maps UPSERT to PUT
22
- result = await srv.dispatch(new RestRequest({ query, _target, method: _req.method, params: _params }))
23
- if (_params && result) Object.assign(result, _params[_params.length - 1])
24
- } catch (e) {
25
- const is404 = e.code === 404 || e.status === 404 || e.statusCode === 404
26
- const isForcedInsert =
27
- (e.code === 412 || e.status === 412 || e.statusCode === 412) && _req.headers['if-none-match'] === '*'
28
- if ((is404 || isForcedInsert) && UPSERT_ALLOWED) {
29
- query = INSERT.into(query.UPDATE.entity).entries(
30
- _params ? Object.assign(_data, _params[_params.length - 1]) : _data
31
- )
32
- result = await srv.dispatch(new RestRequest({ query, _target, params: _params }))
33
- status = 201
34
- } else {
35
30
  throw e
36
31
  }
37
- }
38
32
 
39
- return { result, status }
33
+ return { result, status }
34
+ }
40
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "7.9.2",
3
+ "version": "8.0.3",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -9,38 +9,42 @@
9
9
  ],
10
10
  "author": "SAP SE (https://www.sap.com)",
11
11
  "license": "SEE LICENSE IN LICENSE",
12
- "typings": "apis/cds.d.ts",
13
12
  "main": "lib/index.js",
14
13
  "bin": {
15
14
  "cds-deploy": "lib/dbs/cds-deploy.js",
16
- "cds-serve": "bin/cds-serve.js"
15
+ "cds-serve": "bin/serve.js",
16
+ "cds-test": "bin/test.js",
17
+ "chest": "bin/test.js"
17
18
  },
18
19
  "files": [
20
+ "_i18n/",
19
21
  "apis/",
20
22
  "app/",
21
23
  "bin/",
22
24
  "lib/",
23
25
  "libx/",
24
- "tasks/",
25
26
  "srv/",
26
- "_i18n/",
27
+ "tasks/",
27
28
  "server.js",
28
29
  "common.cds",
30
+ "eslint.config.mjs",
29
31
  "CHANGELOG.md",
30
32
  "LICENSE"
31
33
  ],
32
34
  "engines": {
33
- "node": ">=16"
35
+ "node": ">=18"
34
36
  },
35
37
  "dependencies": {
36
- "@cap-js/cds-types": "<1",
37
- "@sap/cds-compiler": "^4",
38
+ "@sap/cds-compiler": ">=5",
38
39
  "@sap/cds-fiori": "^1",
39
40
  "@sap/cds-foss": "^5.0.0"
40
41
  },
41
- "cds": {
42
- "plugins": [
43
- "@sap/cds-fiori"
44
- ]
42
+ "peerDependencies": {
43
+ "express": ">=4"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "express": {
47
+ "optional": true
48
+ }
45
49
  }
46
50
  }
package/server.js CHANGED
@@ -1,4 +1,4 @@
1
- const express = require('express')// eslint-disable-line cds/no-missing-dependencies
1
+ const express = require('express')
2
2
  const cds = require('./lib')
3
3
 
4
4
  /**
@@ -62,6 +62,7 @@ const defaults = {
62
62
  return cds.utils.isdir (cds.env.folders.app)
63
63
  },
64
64
  get index() {
65
+ if (!cds.env.server.index) return undefined
65
66
  const index = require ('./app/index.js')
66
67
  return (_,res) => res.send (index.html)
67
68
  },
@@ -70,7 +71,8 @@ const defaults = {
70
71
  return express.static (favicon, {maxAge:'14d'})
71
72
  },
72
73
  get cors() {
73
- return process.env.NODE_ENV === 'production' ? null : (req, res, next) => {
74
+ if (!cds.env.server.cors) return undefined
75
+ return function cds_cors (req, res, next) {
74
76
  const { origin } = req.headers
75
77
  if (origin) {
76
78
  res.set('access-control-allow-origin', origin)