@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
@@ -149,23 +149,3 @@ const is_rest = x => x && typeof x === 'string' && x[0] === '/'
149
149
  const is_query = x => x && x.bind || is_array(x) && !x.raw
150
150
  const is_array = (x) => Array.isArray(x) && !x.raw
151
151
  const is_object = (x) => typeof x === 'object'
152
-
153
- // Deprecated
154
- /** @deprecated: To be removed with the next major release */
155
- Service.prototype.stream = cds.utils.deprecated (function (...args) {
156
- if (is_query(args[0])) {
157
- Object.defineProperty(args[0], '_stream', { value: true, enumerable: false })
158
- if (cds.env.features.stream_compat)
159
- Object.defineProperty(args[0], '_streaming', { value: true, enumerable: false })
160
- return this.run(...args).then(result => {
161
- if (!result) return result
162
- return Array.isArray(result) ? Object.values(result[0])[0] : Object.values(result)[0]})
163
- }
164
- const q = (args ? SELECT.one.columns(args) : SELECT.one).bind(this)
165
- Object.defineProperty(q, '_stream', { value: true, enumerable: false })
166
- if (cds.env.features.stream_compat) Object.defineProperty(q, '_streaming', { value: true, enumerable: false })
167
- return q
168
- }, {
169
- old: 'srv.stream()',
170
- use: 'srv.read()'
171
- })
@@ -15,8 +15,9 @@ exports.dispatch = async function dispatch (req) { //NOSONAR
15
15
  if (!this.context) return this.run (tx => tx.dispatch(req))
16
16
  if (!req.tx) req.tx = this // `this` is a tx from now on...
17
17
 
18
- // Inform potential listeners // REVISIT: -> this should move into protocol adapters
19
- let _is_root = req.constructor.name in { ODataRequest:1, RestRequest:2 }
18
+ // REVISIT: remove together with okra
19
+ // Inform potential listeners
20
+ let _is_root = req.constructor.name in { ODataRequest:1 }
20
21
  if (_is_root) req._.req.emit ('dispatch',req)
21
22
 
22
23
  // Handle batches of queries
@@ -4,7 +4,13 @@ const LOG = cds.log()
4
4
  class EventHandlers {
5
5
 
6
6
  constructor (name) {
7
- this._handlers = { _initial:[], before:[], on:[], after:[], _error:[] }
7
+ this._handlers = {
8
+ /** @type {EventHandler[]} */ _initial:[],
9
+ /** @type {EventHandler[]} */ before:[],
10
+ /** @type {EventHandler[]} */ on:[],
11
+ /** @type {EventHandler[]} */ after:[],
12
+ /** @type {EventHandler[]} */ _error:[]
13
+ }
8
14
  this.name = name
9
15
  }
10
16
 
@@ -68,9 +74,12 @@ const _register = function (srv, phase, event, path, handler) { //NOSONAR
68
74
  for (let each of ['CREATE','UPDATE']) _register (srv, phase, each, path, handler)
69
75
  return this
70
76
  }
71
- else if (event === 'each' || phase === 'after' && /^\(?each\)?/.test(handler)) {
72
- const h=handler; event = 'READ'
73
- handler = (rows,req) => is_array(rows) ? rows.forEach (r => h(r,req)) : rows && h(rows,req)
77
+ else if (phase === 'after' && ( event === 'each' //> srv.after ('each', Book, b => ...) // event 'each' => READ each
78
+ || event === 'READ' && path?.is_singular //> srv.after ('READ', Book, b => ...) // Book is a singular def from cds-typer
79
+ || event === 'READ' && /^\(?each\b/.test(handler) //> srv.after ('READ', Book, each => ...) // handler's first param is named 'each'
80
+ )) {
81
+ event = 'READ' // override event='each' to 'READ'
82
+ const h=handler; handler = (rows,req) => is_array(rows) ? rows.forEach (r => h(r,req)) : rows && h(rows,req)
74
83
  }
75
84
  else if (typeof event === 'object') {
76
85
  // extract action name from an action definition's fqn
@@ -91,7 +100,7 @@ const _register = function (srv, phase, event, path, handler) { //NOSONAR
91
100
  if (!path.startsWith(srv.name+'.')) path = `${srv.name}.${path}`
92
101
  }
93
102
 
94
- if (cds.env.fiori.lean_draft && cds.env.fiori.draft_compat) {
103
+ if (cds.env.fiori.draft_compat) {
95
104
  const entity = path && srv.model?.definitions[path.name || path]
96
105
  if (['PATCH', 'CANCEL', 'NEW'].includes(event)) {
97
106
  // delegate to drafts
@@ -120,14 +129,16 @@ class EventHandler {
120
129
  this.handler = handler
121
130
  Object.defineProperties (this, { // non-enumerable properties to improve debugging
122
131
  _initial: { value: handler._initial },
123
- for: { value:
124
- event && path ? (req) => (event === req.event) && (path === req.path || path === req.entity) :
125
- event ? (req) => (event === req.event) :
126
- path ? (req) => (path === req.path || path === req.entity) :
127
- /* else: */ () => true
128
- }
132
+ for: { value: this.for(event,path) }
129
133
  })
130
134
  }
135
+ /** Factory for the actual filter method this.for, assigned above */
136
+ for (event, path) {
137
+ if (event && path) return req => event === req.event && (path === req.path || path === req.entity)
138
+ if (event) return req => event === req.event
139
+ if (path) return req => path === req.path || path === req.entity
140
+ else return () => true
141
+ }
131
142
  }
132
143
 
133
144
 
@@ -67,7 +67,7 @@ const add_handler_for = (srv, def) => {
67
67
  req.params = [ args.shift() ] // second argument is the target's primary key
68
68
  }
69
69
  const {params} = target ? target.actions[event] : def
70
- if (params) req.data = _named(args,params) || _positional(args,params)
70
+ if (params) req.data = _named(args,params) || _positional(args,params)
71
71
 
72
72
  // ensure legacy compat, keys in req.data
73
73
  if(cds.env.features.keys_in_data_compat && target) {
@@ -104,5 +104,5 @@ const _named = (args, declared) => {
104
104
  }
105
105
 
106
106
  const _positional = (args, declared) => Object.keys(declared).reduce (
107
- (data,k,i) => { data[k] = args[i]; return data }, {}
107
+ (data,k,i) => { if (args[i] !== undefined) data[k] = args[i]; return data }, {}
108
108
  )
@@ -2,10 +2,6 @@
2
2
  // REVISIT: all edmx caches also have to be hooked in here
3
3
 
4
4
  const cds = require ('../index')
5
- const LOG = cds.log()
6
-
7
- const {normalizeError} = require('../../libx/_runtime/common/error/frontend')
8
- const getError = require('../../libx/_runtime/common/error')
9
5
 
10
6
  /**
11
7
  * Implements a static cache for all tenant/features-specific models.
@@ -15,34 +11,6 @@ const getError = require('../../libx/_runtime/common/error')
15
11
  */
16
12
  class ExtendedModels {
17
13
 
18
- /**
19
- * Returns an express middleware to be used with the given srv to set cds.context.model.
20
- * Uses `this.model4(...)` to get the respective tenant/features-specific models.
21
- * @returns {(req,res)=>{}}
22
- */
23
- static middleware4 (srv) {
24
- if (!(srv instanceof cds.ApplicationService)) return [] //> no middleware to add // REVISIT: move to `srv.isExtensible`
25
- if (!srv.isExtensible) return [] //> no middleware to add
26
- else return async function cds_context_model (req,res,next) {
27
- if (!req.tenant && !req.user?.tenant && !req.features && !req.user?.features) return next()
28
- const ctx = cds.context = cds.EventContext.for({ http: { req, res } })
29
- ctx.user = req.user // REVISIT: should move to auth middleware?
30
- try {
31
- ctx.model = req.__model = await ExtendedModels.model4 (ctx.tenant, ctx.features)
32
- } catch (e) {
33
- LOG.error (Object.assign(e, { message: 'Unable to get service from service map due to error: ' + e.message }))
34
-
35
- // return 503 to client
36
- // REVISIT: Error handling!
37
- const { error } = normalizeError(getError(Object.assign(e, { statusCode: 503 })), req)
38
-
39
- return res.status(503).json({ error })
40
- }
41
- next()
42
- }
43
- }
44
-
45
-
46
14
  /**
47
15
  * Returns the model to use for given tenant and features.
48
16
  * Loaded models are cached, with eviction on inactivity of tenants,
@@ -87,7 +55,7 @@ class ExtendedModels {
87
55
  * @returns {string} of the form `<tenant>:<comma-separated-features>`
88
56
  */
89
57
  key4 (tenant, features) {
90
- return `${tenant||''}:${features.$hash}`
58
+ return `${tenant||''}:${features?.$hash}`
91
59
  }
92
60
 
93
61
 
@@ -158,7 +126,7 @@ class ExtendedModels {
158
126
  }
159
127
 
160
128
 
161
- /** The cache instance used by `middleware4()` and `model4()`. */
129
+ /** The cache instance used by `model4()`. */
162
130
  static cache = new ExtendedModels
163
131
 
164
132
  /** Time interval in ms to check for new extensions and refresh models, if so. */
@@ -176,7 +144,6 @@ module.exports = ExtendedModels
176
144
  const extensibility = cds.requires.extensibility
177
145
  if (!extensibility) {
178
146
  ExtendedModels.prototype.at = function (key) { return this[key] }
179
- if (!cds.requires.toggles) ExtendedModels.middleware4 = ()=> []
180
147
  }
181
148
 
182
149
 
@@ -189,7 +156,7 @@ const _get_model4 = async (tenant, features) => {
189
156
  if (cds.edmxs) await cds.compile.to.edmx.files ( csn,
190
157
  // adding tenant and features to runtime model, for later use when fetching generated files
191
158
  nsn.tenant = tenant,
192
- nsn.features = features.join()
159
+ nsn.features = features?.$hash
193
160
  )
194
161
  return nsn
195
162
  }
@@ -0,0 +1,343 @@
1
+ const { inspect } = require('node:util')
2
+ const format = x => inspect(
3
+ is.error(x) ? x.message
4
+ : typeof x === 'object' && 'status' in x && 'body' in x ? { status: x.status, body: x.body }
5
+ : typeof x === 'object' && 'status' in x && 'data' in x ? { status: x.status, data: x.data }
6
+ : x,
7
+ { colors: true, sorted: true, depth: 11 }
8
+ )
9
+
10
+ const expect = module.exports = actual => {
11
+ const chainable = function (x) { return this.call(x) }; delete chainable.length
12
+ return Object.setPrototypeOf(chainable, new Assertion(actual))
13
+ }
14
+
15
+ const is = new class {
16
+ Array = Array.isArray
17
+ Error = x => x instanceof Error || x?.stack && x.message
18
+ Symbol = x => typeof x === 'symbol'
19
+ Object = x => typeof x === 'object' // && x && !is.array(x)
20
+ String = x => typeof x === 'string' || x instanceof String
21
+ Number = x => typeof x === 'number' || x instanceof Number
22
+ Boolean = x => typeof x === 'boolean' || x instanceof Boolean
23
+ Promise = x => x instanceof Promise
24
+ RegExp = x => x instanceof RegExp
25
+ Date = x => x instanceof Date
26
+ Set = x => x instanceof Set
27
+ Map = x => x instanceof Map
28
+ array = this.Array
29
+ error = this.Error
30
+ symbol = this.Symbol
31
+ object = this.Object
32
+ string = this.String
33
+ number = this.Number
34
+ boolean = this.Boolean
35
+ promise = this.Promise
36
+ regexp = this.RegExp
37
+ date = this.Date
38
+ set = this.Set
39
+ map = this.Map
40
+ /** Jest-style any matcher */
41
+ any = expect.any = type => {
42
+ if (type === undefined) return () => true
43
+ else return this [type.name || type] || (x => x instanceof type)
44
+ }
45
+ }
46
+
47
+
48
+ class Core {
49
+
50
+ constructor (actual) { this._ = actual }
51
+
52
+ /** The central method to throw an AssertionError. */
53
+ expected ([a, be, ...etc], ...args) {
54
+ const raw = [a, (this._not ? ' NOT' : '') + be, ...etc]
55
+ const err = new expected({ raw }, ...args)
56
+ // err.operator = be.trim().replace(/^to /,'')
57
+ // err.expected = args[1]
58
+ // err.actual = args[0]
59
+ throw err
60
+ }
61
+
62
+ should ([be, ...etc], ...args) {
63
+ return this.expected(['', ' to ' + be, ...etc], this._, ...args)
64
+ }
65
+
66
+ /** The central method to check assertions. */
67
+ assert (check, _fail = () => false) {
68
+ const outcome = check(this._)
69
+ if (this._not ? outcome : !outcome) return _fail(outcome)
70
+ else return this
71
+ }
72
+
73
+ instanceof (x) {
74
+ return this.assert(a => a instanceof x) || this.should`be an instance of ${x.name || x}`
75
+ }
76
+
77
+ kindof (x) {
78
+ return this.assert(is.any(x)) || this.should`be kind of ${x?.name || x}`
79
+ }
80
+
81
+ equals (x, _fail = () => this.should`strictly equal ${x}`) {
82
+ if (typeof x === 'function') return this.assert(x)
83
+ if (this._deep) return this.eqls(x)
84
+ return this.assert(a => a === x, _fail)
85
+ }
86
+
87
+ eqls (x, _fail = () => this.should`deeply equal ${x}`) {
88
+ if (typeof x === 'function') return this.assert(x)
89
+ return this.assert(a => compare(a, x, true), _fail)
90
+ }
91
+
92
+ subset (x, _fail = () => this.should`contain subset ${x}`) {
93
+ return this.assert(a => {
94
+ if (is.array(a) && is.array(x)) return x.every(x => a.some(o => compare(o, x, false)))
95
+ if (is.array(a) && !is.array(x)) return a.some(o => compare(o, x, false))
96
+ else return compare(a, x, false)
97
+ }, _fail)
98
+ }
99
+
100
+ matches (x, _fail = () => this.should`match ${x}`) {
101
+ return this.assert(a => {
102
+ if (is.regexp(x)) return x.test(a)
103
+ if (is.string(x)) return a.includes?.(x)
104
+ if (is.object(x)) return this.subset(x) && !this._not //> to avoid doubled not
105
+ // if (is.array(x)) return x.every(x => a.includes(x))
106
+ }, _fail)
107
+ }
108
+
109
+ includes (x, _fail = () => this.should`include ${x}`) {
110
+ return this.assert(a => {
111
+ if (!a) expected`an array or string or set or object but got ${a}`
112
+ if (is.string(a)) return a.includes(x)
113
+ if (is.array(a)) return a.includes(x) || this._deep && a.some(o => compare(o, x, true))
114
+ if (is.set(a)) return a.has(x)
115
+ if (this._deep && is.object(a)) return compare(a, x, true)
116
+ }, _fail)
117
+ }
118
+
119
+ oneOf (x, _fail = () => this.should`be one of ${x}`) {
120
+ return this.assert(a => x.includes(a), _fail)
121
+ }
122
+
123
+ throws (x, _fail = () => this.should`throw ${x}`) {
124
+ if (is.promise(this._)) return this.rejectsWith(x)
125
+ return this.assert(a => {
126
+ if (typeof a === 'function') try { a(); return false } catch (err) { if (!x) return true; else a = err }
127
+ if (typeof x.test === 'function') return x.test(a)
128
+ if (typeof x === 'function') return x(a)
129
+ if (typeof x === 'string') return a == x || a.code == x || a.message?.includes(x)
130
+ if (typeof x === 'object') return compare(a, x, false)
131
+ }, _fail)
132
+ }
133
+
134
+ rejectsWith (x) {
135
+ if (this._not) return Promise.resolve(this._).catch(
136
+ e => expected`promise to be fulfilled but it was rejected with ${e}`
137
+ )
138
+ else return Promise.resolve(this._).then(
139
+ y => expected`promise to be rejected but it was fulfilled with ${y}`,
140
+ e => {
141
+ if (x) expect(e).throws(x, () => expected`promise to be rejected with ${x} but got ${e}`)
142
+ return e
143
+ }
144
+ )
145
+ }
146
+
147
+ length (ln) {
148
+ return this.assert(a => (a.length ?? String(a).length) === ln, () => this.should`have length ${ln}`)
149
+ }
150
+
151
+ property (p, v) {
152
+ const has = !this._own ? (a, p) => p in a : Reflect.getOwnPropertyDescriptor
153
+ const get = (a, p) => has(a, p) ? a[p] : $not_found, $not_found = {}
154
+ const y = this.assert(() => true) && !this._nested ? get(this._, p) : p.split('.').reduce((a, p) => get(a, p), this._)
155
+ if (y === $not_found) return this._not || (this._nested
156
+ ? this.should`have nested property ${p}`
157
+ : this.should`have property ${p}`)
158
+ const that = Object.assign(expect(), this, { _: y })
159
+ if (v !== undefined) return that.eqls(v, () => this._nested
160
+ ? this.should`have nested property ${p} with value ${v}`
161
+ : this.should`have property ${p} with value ${v}`)
162
+ return that
163
+ }
164
+
165
+ keys (...keys) {
166
+ if (is.array(keys[0])) keys = keys[0]
167
+ return this.assert(a => keys.every(k => k in a)) || this.should`have all keys ${keys}`
168
+ }
169
+
170
+ gt (x) { return this.assert(a => a > x) || this.should`be > ${x}` }
171
+ lt (x) { return this.assert(a => a < x) || this.should`be < ${x}` }
172
+ gte (x) { return this.assert(a => a >= x) || this.should`be >= ${x}` }
173
+ lte (x) { return this.assert(a => a <= x) || this.should`be <= ${x}` }
174
+ within (x, y) { return this.assert(a => x <= a && a <= y) || this.should`be within ${[x, y]}` }
175
+ }
176
+
177
+
178
+ class Chai extends Core {
179
+
180
+ // linguistic chaining
181
+
182
+ get to () { return this }
183
+ get be () { this.call = this.equals; return this }
184
+ get is () { this.call = this.equals; return this }
185
+ get at () { return this }
186
+ get of () { return this }
187
+ get and () { return this }
188
+ get but () { return this }
189
+ get has () { this.call = this.property; return this }
190
+ get have () { this.call = this.property; return this }
191
+ get that () { return this }
192
+ get does () { return this }
193
+ get with () { return this }
194
+ get also () { return this }
195
+ get still () { return this }
196
+ get which () { return this }
197
+ get eventually () {
198
+ this.assert = (fn, _fail) => Promise.resolve(this._).then(a => expect(a).assert(fn, _fail))
199
+ return this
200
+ }
201
+
202
+ // flags changing behaviour of subsequent methods in the chain
203
+
204
+ get not () { this._not = true; return this }
205
+ get own () { this._own = true; return this }
206
+ get deep () { this._deep = true; return this }
207
+ get nested () { this._nested = true; return this }
208
+ get ordered () { return unsupported() }
209
+ get any () { return unsupported() }
210
+ get all () { return this }
211
+
212
+ get undefined () { return this.assert(a => a === undefined) || this.should`be undefined` }
213
+ get exist () { return this.assert(a => a != undefined) || this.should`exist` }
214
+ get truthy () { return this.assert(a => !!a) || this.should`be truthy` }
215
+ get falsy () { return this.assert(a => !a) || this.should`be falsy` }
216
+ get null () { return this.assert(a => a === null) || this.should`be ${null}` }
217
+ get true () { return this.assert(a => a === true) || this.should`be ${true}` }
218
+ get false () { return this.assert(a => a === false) || this.should`be ${false}` }
219
+ get empty () { return this.assert(a => !a?.length === 0 || Object.keys(a).length === 0) || this.should`be empty` }
220
+ get NaN () { return this.assert(a => isNaN(a)) || this.should`be ${NaN}` }
221
+ get ok () { return this.truthy }
222
+
223
+ get containSubset () { return this.subset }
224
+ get contains () { return this.includes }
225
+ get contain () { return this.includes }
226
+ get include () { return this.includes }
227
+ get match () { return this.matches }
228
+ get equal () { return this.equals }
229
+ get eq () { return this.equals }
230
+ get eql () { return this.eqls }
231
+ get exists () { return this.defined }
232
+ get lengthOf () { return this.length }
233
+ get instanceOf () { return this.instanceof }
234
+ get kindOf () { return this.kindof }
235
+ get kind () { return this.kindof }
236
+ get an () { this.call = this.kindof; return this }
237
+ get a () { this.call = this.kindof; return this }
238
+ get key () { return this.keys }
239
+
240
+ get below () { return this.lt }
241
+ get above () { return this.gt }
242
+ get most () { return this.lte }
243
+ get least () { return this.gte }
244
+ get lessThan () { return this.lt }
245
+ get greaterThan () { return this.gt }
246
+ get lessThanOrEqual () { return this.lte }
247
+ get greaterThanOrEqual () { return this.gte }
248
+
249
+ get throw () { return this.throws }
250
+ get fulfilled () { return this.not.rejectsWith() }
251
+ get rejected () { return this.rejectsWith() }
252
+ get rejectedWith () { return this.rejectsWith }
253
+ }
254
+
255
+
256
+ class Jest extends Chai {
257
+
258
+ get rejects () { return this }
259
+ get toBe () { return this.equals }
260
+ get toEqual () { return this.eqls }
261
+ get toMatch () { return this.matches }
262
+ get toMatchObject () { return this.matches }
263
+ get toContain () { return this.includes }
264
+ get toThrow () { return this.throws }
265
+ get toThrowError () { return this.throws }
266
+ get toBeGreaterThan () { return this.gt }
267
+ get toBeLessThan () { return this.lt }
268
+ get toBeGreaterThanOrEqual () { return this.gte }
269
+ get toBeLessThanOrEqual () { return this.lte }
270
+ get toHaveProperty () { return this.nested.property }
271
+ get toHaveLength () { return this.length }
272
+
273
+ toBeFalsy () { return this.falsy }
274
+ toBeTruthy () { return this.truthy }
275
+ toBeDefined () { return this.defined }
276
+ toBeUndefined () { return this.undefined }
277
+ toBeInstanceOf () { return this.instanceof }
278
+ toMatchSnapshot () { unsupported('toMatchSnapshot') }
279
+
280
+ static expect() {
281
+ expect.stringMatching = x => a => (is.regexp(x) ? x : RegExp(x)).test?.(a)
282
+ expect.arrayContaining = x => a => x.every(e => a.includes(e))
283
+ expect.any = is.any
284
+ }
285
+ }
286
+ Jest.expect()
287
+
288
+
289
+ class Assertion extends Jest {
290
+ toString() { return `[ expect: ${format(this._)} ]` }
291
+ }
292
+
293
+
294
+ class AssertionError extends Error {
295
+ constructor (m, caller = Assertion.prototype.should) { Error.captureStackTrace (super(m), caller) }
296
+ get caller() { return Assertion.prototype.should }
297
+ get code() { return 'ERR_ASSERTION' }
298
+ }
299
+
300
+ // function AssertionError(m){
301
+ // Error.captureStackTrace (this,this.caller)
302
+ // this.message = m
303
+ // }
304
+ // AssertionError.prototype = Object.create (Error.prototype, {constructor:{value: AssertionError }})
305
+ // AssertionError.__proto__ = Error
306
+
307
+
308
+ expect.fail = function (actual, expected, message) {
309
+ if (arguments.length === 1) throw new AssertionError (actual, expect.fail)
310
+ if (arguments.length === 3) throw Object.assign (new AssertionError (message, expect.fail), { expected, actual })
311
+ }
312
+
313
+ function expected (strings, ...args) {
314
+ const err = new AssertionError ('expected ' + String.raw(strings, ...args.map(format)))
315
+ if (new.target) return err; else throw err
316
+ }
317
+
318
+ function unsupported (method) {
319
+ const ignore = unsupported.skip ??= (process.env._chest_skip || '')?.split(',').reduce((p, c) => (p[c] = 1, p), {})
320
+ if (!method) return new Error(`unsupported`)
321
+ if (method in ignore) return () => { }
322
+ else throw new Error(`
323
+ Method expect .${method}() is not yet supported.
324
+ Use --skip ${method} to skip checks.
325
+ `)
326
+ }
327
+
328
+ function compare (a, b, strict) {
329
+ if (a == b) return true
330
+ return function _recurse (a, b) {
331
+ if (!a || typeof a !== 'object') return false
332
+ if (!b || typeof b !== 'object') return false
333
+ if (strict)
334
+ for (let k of Object.keys(a)) if (!(k in b))
335
+ return false
336
+ for (let k in b) {
337
+ const v = a[k], x = b[k]; if (v === x) continue
338
+ if (typeof x === 'function') { if (x(v)) continue; else return false }
339
+ if (!_recurse(v, x)) return false
340
+ }
341
+ return true
342
+ }(a, b)
343
+ }
@@ -0,0 +1,2 @@
1
+ // we might move cds-test.js here, but for now we just re-export it
2
+ module.exports = require('../utils/cds-test')