@sap/cds 7.9.3 → 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 (278) hide show
  1. package/CHANGELOG.md +126 -3655
  2. package/_i18n/i18n_en_US_saptrc.properties +113 -0
  3. package/_i18n/i18n_zh_CN.properties +7 -4
  4. package/app/index.css +129 -0
  5. package/app/index.html +16 -64
  6. package/app/index.js +14 -9
  7. package/bin/args.js +34 -0
  8. package/bin/serve.js +18 -24
  9. package/bin/test.js +97 -0
  10. package/common.cds +5 -12
  11. package/eslint.config.mjs +133 -0
  12. package/lib/auth/basic-auth.js +16 -20
  13. package/lib/auth/dummy-auth.js +1 -1
  14. package/lib/auth/ias-auth.js +9 -41
  15. package/lib/auth/index.js +1 -14
  16. package/lib/auth/jwt-auth.js +10 -40
  17. package/lib/compile/cds-compile.js +1 -2
  18. package/lib/compile/cdsc.js +21 -26
  19. package/lib/compile/etc/_localized.js +1 -6
  20. package/lib/compile/etc/csv.js +1 -1
  21. package/lib/compile/etc/properties.js +1 -1
  22. package/lib/compile/for/java.js +1 -1
  23. package/lib/compile/for/lean_drafts.js +4 -6
  24. package/lib/compile/for/nodejs.js +1 -1
  25. package/lib/compile/parse.js +4 -0
  26. package/lib/compile/resolve.js +4 -4
  27. package/lib/compile/to/edm-files.js +16 -23
  28. package/lib/compile/to/hana.js +27 -0
  29. package/lib/compile/to/json.js +1 -1
  30. package/lib/compile/to/sql.js +5 -1
  31. package/lib/compile/to/yaml.js +3 -3
  32. package/lib/dbs/cds-deploy.js +4 -2
  33. package/lib/env/cds-env.js +10 -14
  34. package/lib/env/cds-requires.js +29 -13
  35. package/lib/env/defaults.js +46 -16
  36. package/lib/env/plugins.js +1 -1
  37. package/lib/env/schemas/cds-rc.js +8 -4
  38. package/lib/env/schemas/index.js +7 -7
  39. package/lib/env/serviceBindings.js +1 -1
  40. package/lib/index.js +12 -10
  41. package/lib/lazy.js +1 -1
  42. package/lib/linked/classes.js +36 -8
  43. package/lib/linked/entities.js +2 -10
  44. package/lib/linked/models.js +2 -1
  45. package/lib/linked/validate.js +292 -0
  46. package/lib/log/cds-error.js +0 -6
  47. package/lib/log/cds-log.js +3 -3
  48. package/lib/log/format/json.js +1 -1
  49. package/lib/log/service/index.js +0 -1
  50. package/lib/plugins.js +2 -2
  51. package/lib/ql/Query.js +2 -10
  52. package/lib/ql/SELECT.js +1 -1
  53. package/lib/ql/Whereable.js +3 -2
  54. package/lib/req/cds-context.js +14 -25
  55. package/lib/req/context.js +23 -25
  56. package/lib/req/request.js +1 -34
  57. package/lib/req/user.js +47 -35
  58. package/lib/srv/bindings.js +1 -1
  59. package/lib/srv/cds-connect.js +4 -4
  60. package/lib/srv/cds-serve.js +2 -2
  61. package/lib/srv/factory.js +1 -1
  62. package/lib/srv/middlewares/cds-context.js +11 -22
  63. package/lib/srv/middlewares/ctx-model.js +2 -3
  64. package/lib/srv/middlewares/errors.js +41 -8
  65. package/lib/srv/middlewares/index.js +3 -3
  66. package/lib/srv/middlewares/trace.js +0 -2
  67. package/lib/srv/protocols/hcql.js +15 -10
  68. package/lib/srv/protocols/http.js +44 -49
  69. package/lib/srv/protocols/index.js +1 -23
  70. package/lib/srv/protocols/odata-v4.js +12 -74
  71. package/lib/srv/protocols/rest.js +1 -13
  72. package/lib/srv/srv-api.js +0 -20
  73. package/lib/srv/srv-dispatch.js +3 -2
  74. package/lib/srv/srv-handlers.js +22 -11
  75. package/lib/srv/srv-methods.js +2 -2
  76. package/lib/srv/srv-models.js +3 -36
  77. package/lib/test/expect.js +343 -0
  78. package/lib/test/index.js +2 -0
  79. package/lib/test/reporter.js +176 -0
  80. package/lib/utils/axios.js +10 -9
  81. package/lib/utils/cds-test.js +85 -36
  82. package/lib/utils/cds-utils.js +54 -7
  83. package/lib/utils/check-version.js +0 -4
  84. package/lib/utils/colors.js +49 -0
  85. package/lib/utils/data.js +5 -4
  86. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -7
  87. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +3 -30
  88. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +6 -12
  89. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
  90. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -1
  91. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -7
  92. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +12 -6
  93. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +2 -4
  94. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +1 -0
  95. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  96. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  97. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -3
  98. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  99. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +1 -2
  100. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -0
  101. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
  102. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +9 -43
  103. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +0 -1
  104. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +8 -3
  105. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +4 -2
  106. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -3
  107. package/libx/_runtime/cds-services/util/assert.js +1 -1
  108. package/libx/_runtime/cds.js +10 -3
  109. package/libx/_runtime/common/Service.js +12 -32
  110. package/libx/_runtime/common/aspects/any.js +1 -0
  111. package/libx/_runtime/common/code-ext/execute.js +1 -1
  112. package/libx/_runtime/common/code-ext/worker.js +0 -1
  113. package/libx/_runtime/common/composition/data.js +0 -1
  114. package/libx/_runtime/common/composition/delete.js +0 -1
  115. package/libx/_runtime/common/composition/insert.js +2 -2
  116. package/libx/_runtime/common/composition/tree.js +0 -1
  117. package/libx/_runtime/common/composition/update.js +3 -3
  118. package/libx/_runtime/common/error/frontend.js +21 -12
  119. package/libx/_runtime/common/error/log.js +36 -0
  120. package/libx/_runtime/common/error/utils.js +2 -5
  121. package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
  122. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  123. package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
  124. package/libx/_runtime/common/generic/auth/restrict.js +23 -42
  125. package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
  126. package/libx/_runtime/common/generic/auth/utils.js +91 -88
  127. package/libx/_runtime/common/generic/crud.js +6 -5
  128. package/libx/_runtime/common/generic/etag.js +7 -12
  129. package/libx/_runtime/common/generic/input.js +70 -68
  130. package/libx/_runtime/common/generic/paging.js +1 -0
  131. package/libx/_runtime/common/generic/sorting.js +1 -0
  132. package/libx/_runtime/common/generic/temporal.js +8 -2
  133. package/libx/_runtime/common/i18n/index.js +1 -1
  134. package/libx/_runtime/common/i18n/messages.properties +3 -1
  135. package/libx/_runtime/common/utils/binary.js +8 -2
  136. package/libx/_runtime/common/utils/compareJson.js +5 -1
  137. package/libx/_runtime/common/utils/copy.js +6 -11
  138. package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
  139. package/libx/_runtime/common/utils/differ.js +3 -6
  140. package/libx/_runtime/common/utils/keys.js +77 -18
  141. package/libx/_runtime/common/utils/postProcess.js +12 -15
  142. package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
  143. package/libx/_runtime/common/utils/resolveView.js +2 -3
  144. package/libx/_runtime/common/utils/restrictions.js +45 -17
  145. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
  146. package/libx/_runtime/common/utils/stream.js +3 -16
  147. package/libx/_runtime/common/utils/streamProp.js +8 -18
  148. package/libx/_runtime/common/utils/structured.js +1 -1
  149. package/libx/_runtime/common/utils/ucsn.js +0 -2
  150. package/libx/_runtime/db/Service.js +0 -72
  151. package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
  152. package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
  153. package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
  154. package/libx/_runtime/db/generic/input.js +3 -8
  155. package/libx/_runtime/db/generic/rewrite.js +1 -0
  156. package/libx/_runtime/db/query/read.js +2 -2
  157. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
  158. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  159. package/libx/_runtime/db/utils/columns.js +2 -6
  160. package/libx/_runtime/fiori/lean-draft.js +138 -56
  161. package/libx/_runtime/hana/Service.js +0 -1
  162. package/libx/_runtime/hana/driver.js +1 -1
  163. package/libx/_runtime/hana/dynatrace.js +1 -2
  164. package/libx/_runtime/hana/pool.js +11 -21
  165. package/libx/_runtime/hana/streaming.js +0 -1
  166. package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
  167. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  168. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
  169. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
  170. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
  171. package/libx/_runtime/messaging/event-broker.js +0 -12
  172. package/libx/_runtime/messaging/file-based.js +3 -3
  173. package/libx/_runtime/messaging/http-utils/token.js +1 -1
  174. package/libx/_runtime/messaging/kafka.js +2 -2
  175. package/libx/_runtime/messaging/redis-messaging.js +0 -1
  176. package/libx/_runtime/remote/Service.js +25 -25
  177. package/libx/_runtime/remote/utils/client.js +4 -5
  178. package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
  179. package/libx/_runtime/remote/utils/data.js +0 -1
  180. package/libx/_runtime/sqlite/Service.js +1 -2
  181. package/libx/_runtime/ucl/Service.js +37 -78
  182. package/libx/common/assert/index.js +22 -21
  183. package/libx/common/assert/type-relaxed.js +39 -0
  184. package/libx/common/assert/utils.js +3 -2
  185. package/libx/common/assert/validation.js +3 -8
  186. package/libx/common/utils/index.js +5 -0
  187. package/libx/common/utils/path.js +51 -0
  188. package/libx/odata/ODataAdapter.js +126 -0
  189. package/libx/odata/index.js +15 -2
  190. package/libx/odata/middleware/batch.js +261 -72
  191. package/libx/odata/middleware/body-parser.js +33 -0
  192. package/libx/odata/middleware/create.js +44 -59
  193. package/libx/odata/middleware/delete.js +23 -12
  194. package/libx/odata/middleware/error.js +30 -6
  195. package/libx/odata/middleware/metadata.js +38 -26
  196. package/libx/odata/middleware/operation.js +93 -69
  197. package/libx/odata/middleware/parse.js +6 -8
  198. package/libx/odata/middleware/read.js +117 -93
  199. package/libx/odata/middleware/service-document.js +22 -19
  200. package/libx/odata/middleware/stream.js +54 -56
  201. package/libx/odata/middleware/update.js +79 -87
  202. package/libx/odata/parse/afterburner.js +191 -175
  203. package/libx/odata/parse/cqn2odata.js +8 -8
  204. package/libx/odata/parse/grammar.peggy +27 -20
  205. package/libx/odata/parse/multipartToJson.js +17 -9
  206. package/libx/odata/parse/parser.js +1 -1
  207. package/libx/odata/utils/etag.js +14 -6
  208. package/libx/odata/utils/index.js +84 -12
  209. package/libx/odata/utils/metadata.js +161 -0
  210. package/libx/odata/utils/postProcess.js +89 -0
  211. package/libx/odata/utils/readAfterWrite.js +134 -17
  212. package/libx/odata/utils/result.js +36 -142
  213. package/libx/outbox/index.js +4 -3
  214. package/libx/rest/RestAdapter.js +115 -182
  215. package/libx/rest/middleware/create.js +28 -24
  216. package/libx/rest/middleware/delete.js +7 -10
  217. package/libx/rest/middleware/error.js +19 -16
  218. package/libx/rest/middleware/operation.js +48 -41
  219. package/libx/rest/middleware/parse.js +128 -126
  220. package/libx/rest/middleware/read.js +20 -27
  221. package/libx/rest/middleware/update.js +26 -31
  222. package/package.json +17 -8
  223. package/server.js +4 -2
  224. package/tasks/enterprise-messaging-deploy.js +1 -1
  225. package/apis/cds.d.ts +0 -3
  226. package/apis/core.d.ts +0 -21
  227. package/apis/cqn.d.ts +0 -18
  228. package/apis/csn.d.ts +0 -21
  229. package/apis/events.d.ts +0 -18
  230. package/apis/internal/inference.d.ts +0 -18
  231. package/apis/linked.d.ts +0 -18
  232. package/apis/log.d.ts +0 -20
  233. package/apis/models.d.ts +0 -18
  234. package/apis/ql.d.ts +0 -18
  235. package/apis/reflect.d.ts +0 -32
  236. package/apis/server.d.ts +0 -18
  237. package/apis/services.d.ts +0 -22
  238. package/bin/cds-serve.js +0 -56
  239. package/lib/compile/to/gql.js +0 -15
  240. package/lib/srv/protocols/_legacy.js +0 -44
  241. package/lib/utils/jest.js +0 -43
  242. package/libx/_runtime/auth/index.js +0 -193
  243. package/libx/_runtime/auth/strategies/JWT.js +0 -37
  244. package/libx/_runtime/auth/strategies/basic.js +0 -20
  245. package/libx/_runtime/auth/strategies/dummy.js +0 -14
  246. package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
  247. package/libx/_runtime/auth/strategies/mock.js +0 -77
  248. package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
  249. package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
  250. package/libx/_runtime/common/perf/index.js +0 -19
  251. package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
  252. package/libx/_runtime/fiori/draft.js +0 -2
  253. package/libx/_runtime/fiori/generic/activate.js +0 -190
  254. package/libx/_runtime/fiori/generic/before.js +0 -201
  255. package/libx/_runtime/fiori/generic/cancel.js +0 -19
  256. package/libx/_runtime/fiori/generic/delete.js +0 -21
  257. package/libx/_runtime/fiori/generic/edit.js +0 -157
  258. package/libx/_runtime/fiori/generic/index.js +0 -25
  259. package/libx/_runtime/fiori/generic/new.js +0 -82
  260. package/libx/_runtime/fiori/generic/patch.js +0 -101
  261. package/libx/_runtime/fiori/generic/prepare.js +0 -57
  262. package/libx/_runtime/fiori/generic/read.js +0 -1340
  263. package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
  264. package/libx/_runtime/fiori/utils/csn.js +0 -13
  265. package/libx/_runtime/fiori/utils/delete.js +0 -114
  266. package/libx/_runtime/fiori/utils/handler.js +0 -264
  267. package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
  268. package/libx/_runtime/fiori/utils/req.js +0 -23
  269. package/libx/_runtime/fiori/utils/stream.js +0 -36
  270. package/libx/_runtime/fiori/utils/where.js +0 -254
  271. package/libx/_runtime/index.js +0 -22
  272. package/libx/odata/utils/handler.js +0 -120
  273. package/libx/odata/utils/metaInfo.js +0 -410
  274. package/libx/odata/utils/path.js +0 -75
  275. package/libx/rest/RestRequest.js +0 -32
  276. package/libx/rest/index.js +0 -3
  277. package/libx/rest/readme.md +0 -1
  278. /package/libx/common/assert/{type.js → type-strict.js} +0 -0
package/lib/req/user.js CHANGED
@@ -1,12 +1,23 @@
1
+ /**
2
+ * Users factory function which can be used as follows:
3
+ * - `User(<string>)` - returns a new user instance with the given string as id
4
+ * - `User(<object>)` - returns a new user instance with the given properties
5
+ * - `User(<none>)` - returns the default user if the argument is undefined
6
+ * - `User(<user>)` - returns the given user if it's an instance of `User`
7
+ * - `new User(...)` - always constructs a new instance of User
8
+ */
9
+ module.exports = exports = function (u) {
10
+ return new.target ? new User(u) :
11
+ u === undefined ? exports.default :
12
+ u instanceof User ? u :
13
+ new User(u)
14
+ }
15
+
16
+ /** Class representing users */
1
17
  class User {
2
18
 
3
19
  constructor (_) {
4
- if (_ === undefined) {
5
- if (new.target === Privileged) return
6
- if (new.target === Anonymous) return
7
- else return new User.default
8
- }
9
- else if (typeof _ === 'string') this.id = _
20
+ if (typeof _ === 'string') this.id = _
10
21
  else Object.assign(this,_)
11
22
  }
12
23
 
@@ -15,50 +26,51 @@ class User {
15
26
 
16
27
  get roles() { return super.roles = {} }
17
28
  set roles(r) {
18
- super.roles = Array.isArray(r) ? r.reduce((p, n) => { p[n]=1; return p }, {}) : r
29
+ super.roles = Array.isArray(r) ? r.reduce((p,n) => (p[n]=1, p), {}) : r
19
30
  }
20
31
 
32
+ has (role) { return this.is(role) }
21
33
  is (role) {
22
34
  return (
23
35
  role === 'authenticated-user' ||
24
36
  role === 'identified-user' ||
25
37
  role === 'any' ||
26
- !!this.roles[role]
38
+ role in this.roles // REVISIT: This may break something, did in the past, but we don't know anymore. we should know.
27
39
  )
28
40
  }
29
41
  valueOf() { return this.id }
30
-
31
- // compatibility
32
- get _roles(){ return this.roles }
33
- set _roles(r){ this.roles = r }
34
42
  }
35
43
 
36
- /**
37
- * Subclass representing non-identified unauthenticated users.
38
- */
39
- class Anonymous extends User { is(role) { return role === 'any' }}
40
- Object.defineProperties (Anonymous.prototype, {
41
- id: {value:'anonymous',writable:true},
42
- roles: {value:{}},
43
- attr: {value:{}},
44
- _is_anonymous: {value:true},
44
+
45
+ /** Subclass representing unauthenticated users. */
46
+ class Anonymous extends User {}
47
+ Object.assign (Anonymous.prototype, {
48
+ is: role => role === 'any',
49
+ _is_anonymous: true,
50
+ id: 'anonymous',
51
+ roles: {},
52
+ attr: {},
45
53
  })
54
+ exports.anonymous = exports.default = Object.seal (new Anonymous)
55
+ exports.Anonymous = Anonymous
46
56
 
47
- /**
48
- * Subclass for executing code with superuser privileges.
49
- */
50
- class Privileged extends User { is(){ return true }}
51
- Object.defineProperties (Privileged.prototype, {
52
- id: {value:'privileged',writable:true},
53
- roles: {value:{},writable:true},
54
- attr: {value:{},writable:true},
55
- _is_privileged: {value:true},
57
+
58
+ /** Subclass for executing code with superuser privileges. */
59
+ class Privileged extends User {}
60
+ Object.assign (Privileged.prototype, {
61
+ is: () => true,
62
+ _is_privileged: true,
63
+ id: 'privileged',
64
+ roles: {},
65
+ attr: {},
56
66
  })
67
+ exports.privileged = Object.seal (new Privileged)
68
+ exports.Privileged = Privileged
57
69
 
58
70
 
59
- // exports -----------------
60
- module.exports = exports = Object.assign (User, {
61
- Anonymous, anonymous : Object.seal (new Anonymous),
62
- Privileged, privileged : Object.seal (new Privileged),
63
- default: Anonymous,
71
+ // Allow setting default user by class for compatibility, e.g.: User.default = User.Privileged
72
+ Object.defineProperty (exports, 'default', {
73
+ set(v) { this._default = typeof v === 'function' ? new v : v },
74
+ get() { return this._default },
64
75
  })
76
+ exports._default = exports.anonymous
@@ -26,7 +26,7 @@ module.exports = class Bindings {
26
26
  async load (sync) {
27
27
  DEBUG?.('reading bindings from:', this._source)
28
28
  try { Object.assign (this, JSON.parse (sync ? readFileSync (this._source) : await read (this._source))) }
29
- catch (e) { /* ignored */ }
29
+ catch { /* ignored */ }
30
30
  return this
31
31
  }
32
32
  async store (sync) {
@@ -1,4 +1,4 @@
1
- const cds = require('..'), {one_model} = cds.env.features, LOG = cds.log('cds.connect')
1
+ const cds = require('..'), LOG = cds.log('cds.connect')
2
2
  const _pending = cds.services._pending || {} // used below to chain parallel connect.to(<same>)
3
3
  const TRACE = cds.debug('trace')
4
4
 
@@ -72,7 +72,7 @@ function options4 (name, _o) {
72
72
  }
73
73
 
74
74
  function model4 (o) {
75
- if (o.model && o.model.definitions) return o.model
76
- if (one_model && cds.model) return cds.model
77
- else return o.model && cds.load(o.model)
75
+ if (o.model?.definitions) return o.model // got a CSN already? -> use it
76
+ if (cds.model) return cds.model // use global model if available
77
+ if (o.model) return cds.load (o.model) // load specified model from file
78
78
  }
@@ -3,7 +3,7 @@ const { Service } = cds.service.factory
3
3
  const { serve } = cds.service.protocols
4
4
  const _ready = Symbol(), _pending = cds.services._pending || {}
5
5
  const TRACE = cds.debug('trace')
6
- if (TRACE && !cds.env.features.odata_new_adapter) require('./../../libx/_runtime').to.odata_v4
6
+ if (TRACE && !cds.env.features.odata_new_adapter) require('./../../libx/_runtime/cds-services/adapter/odata-v4/to')
7
7
 
8
8
 
9
9
  /** @param som - a service name or a model (name or csn) */
@@ -36,7 +36,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
36
36
  const loaded = options.then (async ({from}=o) => {
37
37
  if (!from || from === 'all' || from === '*') from = cds.model || '*'
38
38
  if (from.definitions) return from
39
- if (from === '?') try { return await cds.load('*',o) } catch(e){ return }
39
+ if (from === '?') try { return await cds.load('*',o) } catch { return }
40
40
  return cds.load(from, {...o, silent:true })
41
41
  })
42
42
 
@@ -41,7 +41,7 @@ const _require = (it,d) => {
41
41
  DEBUG && d && DEBUG ('requires',{ service: d.name, source:_source(d), impl:it })
42
42
  if (it.startsWith('@sap/cds/')) it = cds.home + it.slice(8) //> for local tests in @sap/cds dev
43
43
  if (it.startsWith('./')) it = _relative (d,it.slice(2)) //> relative to <service>.cds
44
- try { var resolved = require.resolve(it,{paths}) } catch(e) {
44
+ try { var resolved = require.resolve(it,{paths}) } catch {
45
45
  try { resolved = require.resolve(path.join(cds.root,it)) } catch(e) { // compatibility
46
46
  throw cds.error `Failed loading service implementation from '${it}' ${{ Reason:e, paths, 'cds.root':cds.root }}`
47
47
  }
@@ -1,27 +1,16 @@
1
- module.exports = ()=> {
2
-
3
- const cds = require ('../../index')
1
+ const cds = require ('../../index')
2
+ const corr_id = 'x-correlation-id'
3
+ const req_id = 'x-request-id'
4
+ const vr_id = 'x-vcap-request-id'
5
+ const { uuid } = cds.utils
6
+ const { EventContext } = cds
4
7
 
8
+ module.exports = () => {
5
9
  /** @type { import('express').Handler } */
6
- async function cds_context_provider (req, res, next) {
7
- const ctx = {}
8
- ctx.http = { req, res }
9
- ctx.id = _id4(req)
10
+ return function cds_context (req, res, next) {
11
+ const id = req.headers[corr_id] ??= req.headers[req_id] || req.headers[vr_id] || uuid()
12
+ const ctx = EventContext.for ({ id, http: { req, res } })
13
+ res.set ('X-Correlation-ID', id) // Note: we use capitalized style here as that's common standard in HTTP world
10
14
  cds._context.run (ctx, next)
11
15
  }
12
-
13
- const { uuid } = cds.utils
14
- const _id4 = (req) => {
15
- let id = req.headers['x-correlation-id'] = (
16
- req.headers['x-correlation-id'] ||
17
- req.headers['x-correlationid'] ||
18
- req.headers['x-request-id'] ||
19
- req.headers['x-vcap-request-id'] ||
20
- uuid()
21
- )
22
- req.res.set('x-correlation-id', id)
23
- return id
24
- }
25
-
26
- return cds_context_provider
27
16
  }
@@ -1,6 +1,6 @@
1
1
  module.exports = ()=> {
2
2
 
3
- const cds = require ('../../index')
3
+ const cds = require ('../../index'), LOG = cds.log()
4
4
  const context_model_required = cds.requires.extensibility || cds.requires.toggles
5
5
  if (!context_model_required) return []
6
6
 
@@ -9,10 +9,9 @@ module.exports = ()=> {
9
9
  if (req.baseUrl.startsWith('/-/')) return next() //> our own tech services cannot be extended
10
10
  const ctx = cds.context
11
11
  if (ctx.tenant || ctx.features?.given) try {
12
- // if (req.headers.features) ctx.user.features = req.headers.features //> currently done in basic-auth only
13
12
  ctx.model = req.__model = await model4 (ctx.tenant, ctx.features) // REVISIT: req.__model is because of Okra
14
13
  } catch (e) {
15
- console.error(e)
14
+ LOG.error(e)
16
15
  return res.status(503) .json ({ // REVISIT: we should throw a simple error, nothing else! -> this is overly OData-specific!
17
16
  error: { code: '503', message:
18
17
  process.env.NODE_ENV === 'production' ? 'Service Unavailable' :
@@ -1,9 +1,42 @@
1
- module.exports = ()=> function cds_error_handler (err, req, res, next) {
2
- // console.error (err)
3
- // const n = Number(err.status || err.statusCode)
4
- // res.status (n >= 400 && n < 600 ? n : 500)
5
- // err.status = 400
6
- // res.json ({ ...err, message: err.message, stack: err.stack })
7
- // if (err.status === 401 && req._login) return req._login()
8
- next (err)
1
+ const production = process.env.NODE_ENV === 'production'
2
+ const cds = require ('../..'), LOG = cds.log('error')
3
+
4
+ module.exports = () => {
5
+ return function http_error (error, req, res, _next) { // eslint-disable-line no-unused-vars
6
+
7
+ // In case of 401 require login if available by auth strategy
8
+ if (typeof error === 'number') error = { code: error }
9
+ if (error.code == 401 && req._login) return req._login()
10
+
11
+ // Prepare and log the error object
12
+ const status = error.statusCode || error.status || Number(error.code) || 500
13
+ delete error.statusCode
14
+ delete error.status
15
+ if (!production && error.stack) error.stack = error.stack.replace(/\n {4}at .*(?:node_modules\/express|node:internal).*/g,'')
16
+
17
+ if (400 <= status && status < 500) {
18
+ LOG.warn (status, '>', error)
19
+ } else {
20
+ LOG.error (status, '>', error)
21
+ }
22
+
23
+ // Expose as little information as possible in production, and as much as possible in development
24
+ if (production) {
25
+ Object.defineProperties (error, {
26
+ message: { enumerable: true },
27
+ user: { enumerable: false },
28
+ })
29
+ } else {
30
+ Object.defineProperties (error, {
31
+ message: { enumerable: true },
32
+ stack: { enumerable: true },
33
+ })
34
+ }
35
+
36
+ // Send the error response
37
+ return res.status(status).json({error})
38
+
39
+ // Note: express returns errors as XML, we prefer JSON
40
+ // _next (error)
41
+ }
9
42
  }
@@ -8,14 +8,14 @@ const trace = exports.trace = require('./trace')
8
8
  exports.before = [
9
9
  context, // provides cds.context
10
10
  trace, // provides detailed trace logs when DEBUG=trace
11
- auth, // provides req.user & tenant
11
+ auth, // provides cds.context.user & .tenant
12
12
  ctx_model, // fills in cds.context.model, in case of extensibility
13
13
  ].map(mw => _instantiate(mw))
14
14
 
15
15
  // middlewares running after protocol adapters -> usually error middlewares
16
16
  exports.after = [
17
- errors(),
18
- ]
17
+ errors, // provides final error handling
18
+ ].map(mw => _instantiate(mw))
19
19
 
20
20
  /**
21
21
  * Convenience method to add custom middlewares like so:
@@ -65,7 +65,6 @@ let sqlite
65
65
  function _instrument_sqlite (_get_perf) {
66
66
  const me = _instrument_sqlite; if (me.done) return; else me.done = true
67
67
  try { require.resolve('sqlite3') } catch { return }
68
- // eslint-disable-next-line cds/no-missing-dependencies
69
68
  sqlite = require('sqlite3').Database.prototype
70
69
  for (let each of ['all', 'get', 'run', 'prepare']) _wrap(each,sqlite)
71
70
  function _wrap (op,sqlite) {
@@ -93,7 +92,6 @@ function _instrument_sqlite (_get_perf) {
93
92
  function _instrument_better_sqlite (_get_perf) {
94
93
  const me = _instrument_better_sqlite; if (me.done) return; else me.done = true
95
94
  try { require.resolve('better-sqlite3') } catch { return }
96
- // eslint-disable-next-line cds/no-missing-dependencies
97
95
  const sqlite = require('better-sqlite3').prototype
98
96
  for (let each of ['exec', 'prepare']) _wrap(each,sqlite)
99
97
  function _wrap (op,sqlite) {
@@ -1,17 +1,18 @@
1
- const express = require('express') // eslint-disable-line cds/no-missing-dependencies
1
+ const express = require('express')
2
2
  const cds = require('../../index')
3
+ const LOG = cds.log('hcql')
3
4
  const { inspect } = require('util')
4
5
 
5
6
  class HCQLAdapter extends require('./http') {
6
7
 
7
- schema4 (srv) {
8
- return cds.minify (cds.model, { service: srv.name })
9
- }
10
-
11
- router4 (srv) { return super.router4 (srv)
8
+ get router() {
9
+ const srv = this.service
10
+ return super.router
12
11
 
13
- /** Return CSN schema in response to /<srv>/$csn requests */
14
- .get('/\\$csn', (_, res) => res.json (this.schema4(srv)))
12
+ /**
13
+ * Return CSN schema in response to /<srv>/$csn requests
14
+ */
15
+ .get('/\\$csn', (_, res) => res.json(this.schema))
15
16
 
16
17
  .use(express.json()) //> for application/json -> cqn
17
18
  .use(express.text()) //> for text/plain -> cql -> cqn
@@ -35,13 +36,17 @@ class HCQLAdapter extends require('./http') {
35
36
  */
36
37
  .use((req, res, next) => {
37
38
  let q = this.query4(req)
38
- this.logger.info (req.method, decodeURIComponent(req.originalUrl), inspect(q,{colors:true,depth:11}))
39
+ LOG._info && LOG.info(req.method, decodeURIComponent(req.originalUrl), inspect(q, { colors: true, depth: 11 }))
39
40
  return srv.run(q).then(r => res.json(r)).catch(next)
40
41
  })
41
42
  }
42
43
 
44
+ get schema() {
45
+ return cds.minify (cds.model, { service: this.service.name })
46
+ }
47
+
43
48
  query4 (req) {
44
- if (typeof req.body === 'string') return cds.parse.cql (req.body)
49
+ if (typeof req.body === 'string') return cds.parse.cql(req.body)
45
50
  return req.body //> a plain CQN object
46
51
  }
47
52
  }
@@ -1,60 +1,55 @@
1
- const express = require('express') // eslint-disable-line cds/no-missing-dependencies
2
- const cds = require('../../index')
1
+ const cds = require('../../index'), { decodeURI } = cds.utils
2
+ const express = require('express')
3
+ const production = process.env.NODE_ENV === 'production'
4
+ const restrict_all = cds.env.requires.auth?.restrict_all_services !== false
5
+ const restricted_by_default = production && restrict_all ? ['authenticated-user'] : false
3
6
 
4
7
 
5
- module.exports = class HttpAdapter {
8
+ class HttpAdapter {
6
9
 
7
- constructor (srv) {
8
- this.kind = this.constructor.name.replace(/Adapter$/,'').toLowerCase()
9
- this.logger = cds.log (this.kind)
10
- return this.router4 (srv)
10
+ /** Constructs and returns a new express.Router */
11
+ constructor (srv,o={}) {
12
+ this.kind = o.kind || this.constructor.name.replace(/Adapter$/,'').toLowerCase()
13
+ this.service = srv
14
+ this.options = o
15
+ return this.router //> constructed by getter
11
16
  }
12
17
 
13
- router4 (srv) {
14
- return express.Router()
15
- .use(function req_logger(req,res,next) {
16
- // this.log expected to be implemented by subclass
17
- this?.log?.(req)
18
- next()
19
- }.bind(this))
20
- .use(this.early_access_check4(srv))
18
+ /** The actual Router factory. Subclasses override this to add specific handlers. */
19
+ get router() {
20
+ let router = super.router = (new express.Router) .use (this.http_log.bind(this))
21
+ let assert_roles = this.requires_check()
22
+ if (assert_roles) router.use (assert_roles)
23
+ return router
21
24
  }
22
25
 
23
- /** Does early checks on required roles to reject early */
24
- early_access_check4 (srv) {
25
-
26
- // Resolve required roles statically once....
27
- const d = srv.definition
28
- const roles = d['@requires'] || d['@restrict']?.map(r => r.to).flat()
29
- || cds.env.requires.auth?.restrict_all_services !== false && process.env.NODE_ENV === 'production' && ['authenticated-user']
30
-
31
- // ... and return a handler function accordingly -> PROBLEM: Extensibility
32
- if (!roles) return (req, res, next) => next() //> no handlers required
33
-
34
- const required_roles = Array.isArray(roles) ? roles : [roles]
35
- return function early_access_check (req, res, next) {
36
- let u = req.user; if (!u?.is) u = new cds.User(u) // revisit
37
- if (required_roles.some(r => u.is(r))) return next()
38
- // Following demonstrates how to directly send responses from here...
39
- // However, in order to allow others to plug in error handlers throwing errors is better.
40
- // For example, also for ourselves to obfucscate error details in production mode.
41
- // throw cds.error ({ status: 403, code: 'REQUIRES_AUTH_USER', details: `Requires any of [ ${roles} ]` })
42
- if (!u._is_anonymous) return res.status(403).send(`User '${u.id}' is lacking required roles: [ ${roles} ]`)
43
- else if (!req._login) return res.status(401).send('Requires authenticated user')
44
- else return req._login()
45
- }
26
+ /** Handler to log all incoming requests */
27
+ http_log (r,_,next) {
28
+ this.logger = cds.log(this.kind)
29
+ this.log(r)
30
+ next()
46
31
  }
47
- }
48
32
 
49
- /*
50
- function MTXRouter (srv) {
51
- this.routers = {
52
- t1_fta: 'new HCQLAdapter(srv.for(t1))',
53
- t1_fta_ftb: 'new HCQLAdapter(srv.for(t1))',
54
- t2: 'new HCQLAdapter(srv.for(t2))',
33
+ /** Subclasses may override this method to log incoming requests. */
34
+ log (req, LOG = this.logger) { LOG._info && LOG.info (
35
+ req.method,
36
+ decodeURI (req.baseUrl + req.path),
37
+ Object.keys (req.query).length ? { ...req.query } : ''
38
+ )}
39
+
40
+ /** Returns a handler to check required roles, or null if no check required. */
41
+ requires_check() {
42
+ const d = this.service.definition
43
+ const roles = d['@requires'] || d['@restrict']?.map(r => r.to).flat().filter(r => r)
44
+ const required = !roles?.length ? restricted_by_default : Array.isArray(roles) ? roles : [roles]
45
+ if (required) return function requires_check (req, res, next) {
46
+ const user = cds.context.user
47
+ if (required.some(role => user.has(role))) return next()
48
+ else if (user._is_anonymous) return next(401) // request login
49
+ else throw Object.assign(new Error, { code: 403, reason: `User '${user.id}' is lacking required roles: [${required}]`, user, required })
50
+ }
55
51
  }
56
- return express.Router().use((req,res,next) => {
57
- return this.routers[req.tenant].handle(req,res,next)
58
- })
59
52
  }
60
- */
53
+
54
+
55
+ module.exports = HttpAdapter
@@ -80,20 +80,6 @@ class Protocols {
80
80
  // to lots of "UriSemanticError: 'webapp' is not an entity set, ..." errors, if the
81
81
  // service path and static app root path are the same, e.g. /browse in bookshop.
82
82
  if (!path.match(/^\/.+\/.+/)) app.use (`${path}/webapp/`, (_,res) => res.sendStatus(404))
83
-
84
- // Add a reject-all handler for access to /old/$metadata if new scheme is used
85
- if (!cds.env.features.serve_on_root && path.startsWith(this[kind].path)) {
86
- let LOG = cds.log(kind), msg = `PLEASE NOTE:\n
87
- With @sap/cds version 7, default service paths have changed to '${this[kind].path}/<srv>'.
88
- If you use SAP Fiori Elements, make sure to adapt the 'dataSources.uri' paths
89
- in 'manifest.json' files accordingly. For more information see release notes at
90
- https://cap.cloud.sap/docs/releases/jun23#changed-default-service-path.
91
- `.replace(/ {9}/g,'')
92
- app.use(`${path.slice(this[kind].path.length)}/\\$metadata`, (_,res) => {
93
- res.status(404).send(`<pre>${msg}</pre>`)
94
- LOG?.warn(msg); LOG = undefined
95
- })
96
- }
97
83
  }
98
84
 
99
85
  // mount adapter to express app
@@ -129,21 +115,14 @@ class Protocols {
129
115
  if (!annos.length) annos.push ({ kind: this.default })
130
116
 
131
117
  // canonicalize to { kind, path } objects
132
- const no_mws = cds.requires.middlewares === false
133
118
  const endpoints = annos.map (each => {
134
119
  let { kind = each['='] || each, path } = each
135
120
  let { path: prefix } = this[kind] || cds.error `Unknown protocol: ${kind}`
136
121
  if (typeof path !== 'string') path = o?.at || o?.path || def['@path'] || _slugified(srv.name)
137
- if (path[0] !== '/') path = no_mws ? '/'+path : prefix+'/'+path
122
+ if (path[0] !== '/') path = prefix+'/'+path
138
123
  return { kind, path }
139
124
  })
140
125
 
141
- // add serve-on-root endpoint if enabled
142
- if (!no_mws && endpoints.length === 1 && cds.env.features.serve_on_root) {
143
- let [{ kind, path }] = endpoints, prefix = this[kind].path
144
- if (prefix && path.startsWith(prefix)) endpoints.unshift ({ kind, path: path.slice(prefix.length) })
145
- }
146
-
147
126
  return endpoints.length && endpoints
148
127
  }
149
128
 
@@ -204,4 +183,3 @@ const _slugified = name => (
204
183
 
205
184
 
206
185
  module.exports = new Protocols
207
- if (!cds.requires.middlewares) require('./_legacy')
@@ -1,93 +1,31 @@
1
- const cds = require('../../index'),
2
- { User } = cds,
3
- { decodeURI } = cds.utils
4
- const express = require('express') // eslint-disable-line cds/no-missing-dependencies
1
+ const cds = require('../../index'), { decodeURI } = cds.utils
5
2
 
6
3
  if (cds.env.features.odata_new_adapter) {
7
4
  cds.log().info('using new OData adapter')
8
5
 
9
- const { isStream, stream } = require('../../../libx/odata/middleware/stream')
10
- const BaseProtocolAdapter = require('./http')
11
-
12
- module.exports = class ODataAdapter extends BaseProtocolAdapter {
13
- log(req) {
14
- let u = req.user
15
- req.user = u instanceof User ? u : new User(u)
16
-
17
- // REVISIT: how to handle logging of batch subrequests? (note: service is missing from path)
18
- let url = decodeURI(req.originalUrl)
19
- this.logger.info(req.method, url, req.body || '')
20
- if (/\$batch/.test(req.url))
21
- req.on('dispatch', req => {
22
- let path = decodeURI(req._path)
23
- this.logger.info('>', req.event, path, req._query || '')
24
- if (this.logger._debug && req.query) this.logger.debug(req.query)
25
- })
26
- }
27
-
28
- router4(srv) {
29
- const router = super.router4(srv)
30
- const jsonBodyParser = express.json()
31
-
32
- return (
33
- router
34
- // REVISIT: add middleware for negative cases?
35
- // service root
36
- .use(/^\/$/, require('../../../libx/odata/middleware/service-document')(srv))
37
- .use('/\\$metadata', require('../../../libx/odata/middleware/metadata')(srv))
38
- // parse
39
- .use(require('../../../libx/odata/middleware/parse')(srv))
40
- // REVISIT do we want to build our own body parser logic?
41
- .use((req, res, next) => {
42
- // REVISIT: body of batch subrequests are already deserialized
43
- if (typeof req.body === 'object') return next()
44
- if (req.method === 'PUT' && isStream(req._query)) {
45
- req.body = { value: req }
46
- return next()
47
- }
48
- // TODO: check if the raw body still exists, then we can remove deepCopy() in the handlers
49
- return jsonBodyParser(req, res, next)
50
- })
51
- // batch
52
- .post('/\\$batch', require('../../../libx/odata/middleware/batch')(srv, router))
53
- // handle
54
- // REVISIT: with old adapter, we return 405 for HEAD requests -> check OData spec
55
- .head('*', (_, res) => res.sendStatus(405))
56
- .post('*', require('../../../libx/odata/middleware/operation')(srv)) //> action
57
- .get('*', require('../../../libx/odata/middleware/operation')(srv)) //> function
58
- .post('*', require('../../../libx/odata/middleware/create')(srv))
59
- .get('*', stream(srv))
60
- .get('*', require('../../../libx/odata/middleware/read')(srv))
61
- .put('*', require('../../../libx/odata/middleware/update')(srv, router))
62
- .patch('*', require('../../../libx/odata/middleware/update')(srv, router))
63
- .delete('*', require('../../../libx/odata/middleware/delete')(srv))
64
- // error
65
- .use(require('../../../libx/odata/middleware/error')(srv))
66
- )
67
- }
68
- }
6
+ module.exports = require('../../../libx/odata/ODataAdapter')
69
7
  } else {
70
- const libx = require('../../../libx/_runtime')
8
+ cds.log().info('using legacy OData adapter')
9
+
10
+ const legacy_adapter_factory = require('../../../libx/_runtime/cds-services/adapter/odata-v4/to')
71
11
  const LOG = cds.log('odata')
72
- module.exports = function ODataAdapter(srv) {
73
- const router = express.Router()
74
12
 
75
- router.use((req, _, next) => {
76
- let u = req.user
77
- req.user = u instanceof User ? u : new User(u)
13
+ module.exports = function ODataAdapter(srv) {
14
+ const router = require('express').Router()
78
15
 
16
+ router.use(function odata_log(req, _, next) {
79
17
  let url = decodeURI(req.originalUrl)
80
18
  LOG && LOG(req.method, url, req.body || '')
81
19
  if (/\$batch/.test(req.url))
82
20
  req.on('dispatch', req => {
83
- let path = decodeURI(req._path)
84
- LOG && LOG('>', req.event, path, req._query || '')
85
- if (LOG._debug && req.query) LOG.debug(req.query)
21
+ let path = decodeURI(req._.odataReq?._rawODataPath || '')
22
+ LOG && LOG('>', req.event, path, req._queryOptions || '')
23
+ if (LOG._debug && req.query) LOG.debug(req.query) //> why only for batch subrequests?
86
24
  })
87
25
 
88
26
  next()
89
27
  })
90
- router.use(libx.to.odata_v4(srv))
28
+ router.use(legacy_adapter_factory(srv))
91
29
  return router
92
30
  }
93
31
  }
@@ -1,13 +1 @@
1
- const cds = require('../../index'), { User } = cds, { decodeURIComponent } = cds.utils
2
- const libx = require('../../../libx/_runtime')
3
- const LOG = cds.log('rest')
4
-
5
- module.exports = function RestAdapter (srv) { return [
6
- (req, _, next) => {
7
- let u = req.user
8
- req.user = u instanceof User ? u : new User(u)
9
- LOG (req.method, decodeURIComponent(req.originalUrl), req.body||'')
10
- next()
11
- },
12
- libx.to.rest (srv)
13
- ]}
1
+ module.exports = require('../../../libx/rest/RestAdapter')