@sap/cds 5.5.3 → 5.6.1

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 (227) hide show
  1. package/CHANGELOG.md +134 -1
  2. package/apis/services.d.ts +27 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/index.js +1 -1
  12. package/lib/compile/to/sql.js +22 -2
  13. package/lib/connect/bindings.js +2 -1
  14. package/lib/connect/index.js +1 -1
  15. package/lib/core/infer.js +1 -1
  16. package/lib/core/reflect.js +3 -1
  17. package/lib/env/index.js +175 -41
  18. package/lib/env/requires.js +24 -3
  19. package/lib/i18n/localize.js +33 -5
  20. package/lib/index.js +7 -6
  21. package/lib/log/format/kibana.js +6 -2
  22. package/lib/ql/DELETE.js +1 -1
  23. package/lib/ql/INSERT.js +1 -1
  24. package/lib/ql/Query.js +13 -10
  25. package/lib/ql/SELECT.js +15 -8
  26. package/lib/ql/UPDATE.js +1 -1
  27. package/lib/ql/Whereable.js +5 -0
  28. package/lib/req/context.js +87 -37
  29. package/lib/req/{impl.js → request.js} +1 -1
  30. package/lib/req/{res.js → response.js} +0 -0
  31. package/lib/serve/Service-api.js +1 -1
  32. package/lib/serve/Service-dispatch.js +12 -2
  33. package/lib/serve/Service-handlers.js +21 -7
  34. package/lib/serve/Service-methods.js +1 -1
  35. package/lib/serve/Transaction.js +7 -6
  36. package/lib/serve/index.js +1 -1
  37. package/lib/utils/axios.js +7 -0
  38. package/lib/utils/data.js +1 -1
  39. package/libx/_runtime/audit/Service.js +18 -18
  40. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  41. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  42. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  43. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +6 -0
  44. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  45. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  50. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  51. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  52. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  53. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  54. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  55. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  56. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  57. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  58. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  59. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  64. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  67. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +9 -2
  68. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  69. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  70. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  71. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  72. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  73. package/libx/_runtime/cds-services/util/assert.js +29 -13
  74. package/libx/_runtime/cds.js +2 -1
  75. package/libx/_runtime/common/aspects/Association.js +72 -0
  76. package/libx/_runtime/common/aspects/any.js +8 -45
  77. package/libx/_runtime/common/aspects/entity.js +0 -1
  78. package/libx/_runtime/common/aspects/relation.js +40 -0
  79. package/libx/_runtime/common/aspects/utils.js +73 -1
  80. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  81. package/libx/_runtime/common/composition/data.js +3 -2
  82. package/libx/_runtime/common/composition/delete.js +3 -1
  83. package/libx/_runtime/common/composition/tree.js +23 -18
  84. package/libx/_runtime/common/composition/utils.js +34 -8
  85. package/libx/_runtime/common/error/frontend.js +6 -1
  86. package/libx/_runtime/common/generic/auth.js +15 -13
  87. package/libx/_runtime/common/generic/crud.js +2 -2
  88. package/libx/_runtime/common/generic/etag.js +11 -8
  89. package/libx/_runtime/common/generic/input.js +3 -3
  90. package/libx/_runtime/common/generic/paging.js +9 -5
  91. package/libx/_runtime/common/generic/put.js +3 -2
  92. package/libx/_runtime/common/generic/sorting.js +3 -3
  93. package/libx/_runtime/common/generic/temporal.js +3 -3
  94. package/libx/_runtime/common/toggles/alpha.js +1 -1
  95. package/libx/_runtime/common/utils/cqn.js +20 -1
  96. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  97. package/libx/_runtime/common/utils/csn.js +50 -52
  98. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  99. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  100. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  101. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  102. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  103. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  104. package/libx/_runtime/common/utils/resolveView.js +19 -9
  105. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  106. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  107. package/libx/_runtime/common/utils/template.js +54 -46
  108. package/libx/_runtime/db/Service.js +9 -2
  109. package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
  110. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  111. package/libx/_runtime/db/generic/create.js +1 -0
  112. package/libx/_runtime/db/generic/input.js +7 -11
  113. package/libx/_runtime/db/generic/integrity.js +2 -2
  114. package/libx/_runtime/db/generic/rewrite.js +2 -5
  115. package/libx/_runtime/db/generic/update.js +1 -0
  116. package/libx/_runtime/db/query/read.js +10 -5
  117. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
  118. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  119. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  120. package/libx/_runtime/db/utils/columns.js +14 -43
  121. package/libx/_runtime/db/utils/deep.js +5 -7
  122. package/libx/_runtime/fiori/generic/activate.js +3 -2
  123. package/libx/_runtime/fiori/generic/before.js +2 -2
  124. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  125. package/libx/_runtime/fiori/generic/delete.js +3 -2
  126. package/libx/_runtime/fiori/generic/edit.js +2 -2
  127. package/libx/_runtime/fiori/generic/new.js +2 -2
  128. package/libx/_runtime/fiori/generic/patch.js +2 -2
  129. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  130. package/libx/_runtime/fiori/generic/read.js +17 -63
  131. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  132. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  133. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  134. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  135. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  136. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  137. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  138. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  139. package/libx/_runtime/fiori/utils/handler.js +3 -13
  140. package/libx/_runtime/fiori/utils/where.js +6 -1
  141. package/libx/_runtime/hana/Service.js +5 -2
  142. package/libx/_runtime/hana/execute.js +1 -1
  143. package/libx/_runtime/hana/pool.js +12 -11
  144. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  145. package/libx/_runtime/hana/searchToContains.js +3 -3
  146. package/libx/_runtime/index.js +5 -2
  147. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  148. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  149. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  150. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  151. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  152. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  153. package/libx/_runtime/messaging/message-queuing.js +18 -0
  154. package/libx/_runtime/remote/Service.js +14 -2
  155. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  156. package/libx/_runtime/remote/utils/client.js +117 -23
  157. package/libx/_runtime/sqlite/Service.js +4 -3
  158. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  159. package/libx/_runtime/sqlite/execute.js +1 -1
  160. package/libx/gql/GraphQLAdapter.js +33 -0
  161. package/libx/gql/constants/adapter.js +69 -0
  162. package/libx/gql/constants/cds.js +18 -0
  163. package/libx/gql/constants/graphql.js +33 -0
  164. package/libx/gql/resolvers/crud/create.js +15 -0
  165. package/libx/gql/resolvers/crud/delete.js +24 -0
  166. package/libx/gql/resolvers/crud/index.js +6 -0
  167. package/libx/gql/resolvers/crud/read.js +25 -0
  168. package/libx/gql/resolvers/crud/update.js +31 -0
  169. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  170. package/libx/gql/resolvers/field.js +5 -0
  171. package/libx/gql/resolvers/index.js +7 -0
  172. package/libx/gql/resolvers/mutation.js +23 -0
  173. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  174. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  175. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  176. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  177. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  178. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  179. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  180. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  181. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  182. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  183. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  184. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  185. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  186. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  187. package/libx/gql/resolvers/query.js +13 -0
  188. package/libx/gql/resolvers/root.js +34 -0
  189. package/libx/gql/schema/generate.js +18 -0
  190. package/libx/gql/schema/index.js +5 -0
  191. package/libx/gql/schema/mutation.js +76 -0
  192. package/libx/gql/schema/query.js +108 -0
  193. package/libx/gql/schema/typeDefMap.js +45 -0
  194. package/libx/gql/schema/utils/index.js +54 -0
  195. package/libx/gql/utils/index.js +12 -0
  196. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  197. package/libx/odata/index.js +80 -0
  198. package/libx/odata/odata2cqn/afterburner.js +170 -0
  199. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  200. package/libx/odata/odata2cqn/index.js +3 -0
  201. package/libx/odata/odata2cqn/parser.js +1 -0
  202. package/libx/odata/utils/index.js +64 -0
  203. package/libx/rest/RestAdapter.js +101 -0
  204. package/libx/rest/RestRequest.js +30 -0
  205. package/libx/rest/index.js +3 -0
  206. package/libx/rest/middleware/auth.js +22 -0
  207. package/libx/rest/middleware/content.js +15 -0
  208. package/libx/rest/middleware/create.js +40 -0
  209. package/libx/rest/middleware/delete.js +20 -0
  210. package/libx/rest/middleware/error.js +56 -0
  211. package/libx/rest/middleware/operation.js +39 -0
  212. package/libx/rest/middleware/parse.js +90 -0
  213. package/libx/rest/middleware/read.js +29 -0
  214. package/libx/rest/middleware/update.js +42 -0
  215. package/libx/rest/utils/data.js +65 -0
  216. package/package.json +4 -1
  217. package/server.js +42 -29
  218. package/lib/req/cls.js +0 -39
  219. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  220. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  221. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  222. package/libx/_runtime/common/utils/backlinks.js +0 -83
  223. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  224. package/libx/_runtime/odata/index.js +0 -55
  225. package/libx/_runtime/odata/odata2cqn.js +0 -1
  226. package/libx/_runtime/odata/readToCqn.js +0 -129
  227. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -12,41 +12,20 @@ class EventContext {
12
12
 
13
13
  toString() { return `${this.event} ${this.path}` }
14
14
 
15
- static new (data) {
16
- const base = cds.context
17
- if (base && features.cds_tx_inheritance) {
18
- const { tenant, user, locale, _ } = base
19
- data = { user: user && Object.create(user), tenant, locale, _, ...data }
20
- // REVISIT: Do we have to propagate hidden subdomain as well?
21
- // let subdomain = _ && _.req && _.req && _.req.authInfo && _.req.authInfo.getSubdomain()
22
- // if (subdomain) {
23
- // if (!data._) data._ = {}
24
- // if (!data._.req) data._.req = {}
25
- // if (!data._.req.authInfo) data._.req.authInfo = { setSubdomain(v){ this.subdomain = v } }
26
- // if (!data._.req.authInfo.getSubdomain()) data._.req.authInfo.setSubdomain (subdomain)
27
- // }
28
- } else if (data && 'user' in data) {
29
- data = { user:1, ...data } // IMPORTANT: ensure user is first to be assigned to call setter
15
+ static new (data={}, base = cds.context) {
16
+ if (data && base && features.cds_tx_inheritance) {
17
+ let u = _inherit('user', u => typeof u === 'object' ? Object.create(u) : u)
18
+ if (!u || !u.tenant) _inherit('tenant')
19
+ if (!u || !u.locale) _inherit('locale')
30
20
  }
31
- return new this (data)
32
- }
33
-
34
- static spawn (background_job,o) {
35
- const fn = async ()=> {
36
- // create a new transaction for each run of the background job
37
- // which inherits from the current event context by default
38
- const tx = cds.context = cds.tx({...o})
39
- try {
40
- await background_job(tx)
41
- await tx.commit()
42
- } catch(e) {
43
- console.trace (`ERROR occured in background job:`, e) // eslint-disable-line no-console
44
- await tx.rollback()
45
- }
21
+ function _inherit (p,fn) {
22
+ if (p in data) return data[p]
23
+ let pd = Reflect.getOwnPropertyDescriptor(base,p); if (!pd) return
24
+ return data[p] = fn ? fn(pd.value) : pd.value
46
25
  }
47
- if (o && o.after) return setTimeout (fn, o.after)
48
- if (o && o.every) return setInterval (fn, o.every)
49
- else return setImmediate (fn)
26
+ // IMPORTANT: ensure user is first to be assigned to call setter_
27
+ if (data.user) data = { user:1, ...data }
28
+ return new this (data)
50
29
  }
51
30
 
52
31
  constructor(_={}) {
@@ -107,7 +86,7 @@ class EventContext {
107
86
  if (t) super.tenant = this.user.tenant = t
108
87
  }
109
88
  get tenant() {
110
- return this.tenant = this._propagated('tenant') || this.user.tenant
89
+ return this.tenant = this.user.tenant || this._propagated('tenant')
111
90
  }
112
91
 
113
92
  set user(u) {
@@ -124,7 +103,7 @@ class EventContext {
124
103
  if (l) super.locale = this.user.locale = l
125
104
  }
126
105
  get locale() {
127
- return this.locale = this._propagated('locale') || this.user.locale
106
+ return this.locale = this.user.locale || this._propagated('locale')
128
107
  }
129
108
 
130
109
  get timestamp() {
@@ -177,7 +156,78 @@ class EventContext {
177
156
  }
178
157
  }
179
158
 
180
- EventContext.propagateHeaders = [ 'x-correlation-id' ]
159
+
160
+ /**
161
+ * Continuation Local Storage for cds.context
162
+ */
163
+ const cls = (cds,v) => {
164
+
165
+ if (cds.env.features.cls) {
166
+
167
+ const { executionAsyncResource:current, createHook } = module.require ('async_hooks')
168
+ const _context = Symbol('cds.context')
169
+
170
+ createHook ({ init(id,t,tid, res) {
171
+ const cr = current(); if (!cr) return
172
+ const ctx = cr[_context]
173
+ if (ctx) res[_context] = ctx
174
+ }}).enable()
175
+
176
+ Reflect.defineProperty (cds,'context',{ enumerable:1,
177
+ set(v) {
178
+ const cr = current(); if (!cr) return
179
+ const ctx = typeof v !== 'object' ? v : v.context || (v instanceof EventContext ? v : EventContext.new(v,false))
180
+ cr[_context] = ctx
181
+ },
182
+ get() {
183
+ const cr = current(); if (!cr) return undefined
184
+ return cr[_context]
185
+ },
186
+ })
187
+
188
+ } else {
189
+
190
+ Reflect.defineProperty (cds,'context',{ enumerable:1,
191
+ get:()=> undefined,
192
+ set:()=> {},
193
+ })
194
+
195
+ }
196
+
197
+ return v ? cds.context = v : cds.context
198
+ }
199
+
200
+
201
+ function cds_spawn (o,fn) {
202
+ if (typeof o === 'function') [ fn, o ] = [ o, fn ]
203
+ if (!o) o = {}
204
+ const em = new EventEmitter()
205
+ const fx = async ()=> {
206
+ // create a new transaction for each run of the background job
207
+ // which inherits from the current event context by default
208
+ const tx = cds.context = cds.tx({...o})
209
+ try {
210
+ const res = await fn(tx)
211
+ await tx.commit()
212
+ for (const handler of em.listeners('succeeded')) await handler(res)
213
+ for (const handler of em.listeners('done')) await handler()
214
+ } catch(e) {
215
+ console.trace (`ERROR occured in background job:`, e) // eslint-disable-line no-console
216
+ await tx.rollback()
217
+ for (const handler of em.listeners('failed')) await handler(e)
218
+ for (const handler of em.listeners('done')) await handler()
219
+ }
220
+ }
221
+ const timer = (
222
+ (o && o.after) ? setTimeout (fx, o.after) :
223
+ (o && o.every) ? setInterval (fx, o.every) :
224
+ setImmediate (fx) )
225
+ return Object.assign(em, { timer })
226
+ }
227
+
228
+
181
229
  module.exports = Object.assign (EventContext,{
182
- /** @type {(cds,v)=>EventContext} */ for: require('./cls')
230
+ /** @type {(cds,v)=>EventContext} */ for: cls,
231
+ spawn: cds_spawn,
232
+ propagateHeaders: [ 'x-correlation-id' ]
183
233
  })
@@ -1,4 +1,4 @@
1
- const { Responses, Errors } = require('./res')
1
+ const { Responses, Errors } = require('./response')
2
2
 
3
3
  /**
4
4
  * Class Request represents requests received via synchronous protocols.
File without changes
@@ -77,7 +77,7 @@ class Service extends require('./Service-handlers') {
77
77
  get namespace() {
78
78
  return super.namespace = this.definition && this.definition.name
79
79
  || this.model && this.model.namespace
80
- || this.name !== 'db' && !/\W/.test(this.name) && this.name || undefined
80
+ || !(this instanceof cds.DatabaseService) && !/\W/.test(this.name) && this.name || undefined
81
81
  }
82
82
 
83
83
  get operations() { return super.operations = _reflect (this, d => d.kind === 'action' || d.kind === 'function') }
@@ -4,7 +4,7 @@ const cds = require ('../index')
4
4
  * The default implementation of the `srv.dispatch(req)` ensures everything
5
5
  * is prepared before calling `srv.handle(req)`
6
6
  * @typedef {import('./Service-api')} Service
7
- * @typedef {import('../req/impl')} Request
7
+ * @typedef {import('../req/request')} Request
8
8
  * @this {Service}
9
9
  * @param {Request} req
10
10
  * @returns {Promise} resolving to the outcome/return value of last .on handler
@@ -32,7 +32,17 @@ exports.dispatch = async function dispatch (req) { //NOSONAR
32
32
 
33
33
  // Ensure target and fqns
34
34
  if (!req.target) _ensure_target (this,req)
35
- if (req.query && typeof req.query === 'object' && !req.query._srv) Object.defineProperty (req.query,'_srv',{value:this})
35
+ if (typeof req.query === 'object') {
36
+ if (req.query._target !== req.target) Object.defineProperty (req.query,'_target',{ value:req.target, configurable:true, writable:true })
37
+ if (!req.query._srv) Object.defineProperty (req.query,'_srv',{ value:this, configurable:true, writable:true })
38
+ }
39
+
40
+ // REVISIT: Ensure req._.req and req._.res in case of srv.run(query)?!
41
+ /*
42
+ if (this instanceof cds.ApplicationService && !req._.req) {
43
+ // TODO: add req and res to req._ from tx
44
+ }
45
+ */
36
46
 
37
47
  return this.handle(req)
38
48
  }
@@ -3,7 +3,7 @@ const cds = require('..'), {expected} = cds.error
3
3
  class EventHandlers {
4
4
 
5
5
  constructor (name) {
6
- this._handlers = { on:[], before:[], after:[], _initial:[], _error:[] }
6
+ this._handlers = { _initial:[], before:[], on:[], after:[], _error:[] }
7
7
  this.name = name
8
8
  }
9
9
 
@@ -77,17 +77,31 @@ const _register = function (srv, phase, event, path, handler) { //NOSONAR
77
77
 
78
78
  // Finally register with a filter function to match requests to be handled
79
79
  const _handlers = srv._handlers [event === 'error' ? '_error' : (handler._initial ? '_initial' : phase)] // REVISIT: remove _initial handlers
80
- _handlers.push ({ handler, for: (
81
- event && path ? (req) => (event === req.event) && (path === req.path || path === req.entity) :
82
- event ? (req) => (event === req.event) :
83
- path ? (req) => (path === req.path || path === req.entity) :
84
- () => true
85
- )})
80
+ _handlers.push (new EventHandler (phase, event, path, handler))
86
81
 
87
82
  if (phase === 'on') cds.emit('subscribe',srv,event) //> inform messaging service
88
83
  return srv
89
84
  }
90
85
 
86
+
87
+ class EventHandler {
88
+ constructor (phase, event, path, handler) {
89
+ this[phase] = event || '*'
90
+ if (path) this.path = path
91
+ this.handler = handler
92
+ Object.defineProperties (this, { // non-enumerable properties to improve debugging
93
+ _initial: { value: handler._initial },
94
+ for: { value:
95
+ event && path ? (req) => (event === req.event) && (path === req.path || path === req.entity) :
96
+ event ? (req) => (event === req.event) :
97
+ path ? (req) => (path === req.path || path === req.entity) :
98
+ /* else: */ () => true
99
+ }
100
+ })
101
+ }
102
+ }
103
+
104
+
91
105
  const is_impl = x => typeof x === 'function' && !(x.prototype && /^class\b/.test(x))
92
106
  const is_array = Array.isArray
93
107
  const AlternativeEvents = {
@@ -47,7 +47,7 @@ const add_handler_for = (srv, def) => {
47
47
 
48
48
  // object variant of params, ensure at least one param is present in args[0]
49
49
  // REVISIT: still not bullet proof, but parameters might be optional
50
- if (typeof args[0] === 'object' && args.length === 1 && Object.keys(def.params).some(p => p in args[0])) {
50
+ if ( args[0] !== null && typeof args[0] === 'object' && args.length === 1 && Object.keys(def.params).some(p => p in args[0])) {
51
51
  const params = args.shift()
52
52
  for (const p in def.params) {
53
53
  data[p] = params[p]
@@ -6,12 +6,18 @@ const _context = Symbol()
6
6
  * a new Transaction as a derivate of the `srv` (i.e. {__proto__:srv})
7
7
  * @returns { Transaction & import('./Service-api') }
8
8
  */
9
- module.exports = function tx (req,o) { const srv = this
9
+ module.exports = function tx (req,fn) { const srv = this
10
10
  if (srv.context) return srv
11
11
  if (!req) {
12
12
  // called as srv.tx() -> new root transaction
13
13
  return RootTransaction.for (srv, Context.new())
14
14
  }
15
+ if (typeof req === 'function') [ req, fn ] = [ Context.new(), req ]
16
+ if (typeof fn === 'function') {
17
+ // auto-committed transaction, i.e. cds.tx (tx => {...})
18
+ const tx = srv.tx(req)
19
+ return Promise.resolve(tx).then(fn) .then (tx.commit,tx.rollback)
20
+ }
15
21
  if (req instanceof Context) {
16
22
  // called for a nested req -> nested tx
17
23
  if (req.context !== req) return NestedTransaction.for (srv, req.context)
@@ -20,11 +26,6 @@ module.exports = function tx (req,o) { const srv = this
20
26
  // called for a top-level req -> root tx
21
27
  else return RootTransaction.for (srv, req)
22
28
  }
23
- if (typeof req === 'function') {
24
- // auto-committed transaction, i.e. cds.tx (tx => {...})
25
- const tx = srv.tx(o)
26
- return Promise.resolve().then (()=> req(tx)) .then (tx.commit,tx.rollback)
27
- }
28
29
  if (req[_context]) {
29
30
  // called again for an arbitrary context object -> see below
30
31
  return NestedTransaction.for (srv, req[_context])
@@ -85,7 +85,7 @@ function cds_serve (som, _options) { // NOSONAR
85
85
  ready = ready.then (()=>{
86
86
  for (let each of all) {
87
87
  ProtocolAdapter.serve(each).in(app)
88
- cds.emit ('serving',each)
88
+ if (!o.silent) cds.emit ('serving',each)
89
89
  }
90
90
  })
91
91
  return fluent
@@ -36,6 +36,13 @@ const _args = (args) => {
36
36
  }
37
37
 
38
38
  const _error = (e) => {
39
+ if (e.code === 'ECONNREFUSED' && e.port === 80 /*unchanged default port*/) {
40
+ // retain original error properties (code,...)
41
+ e = Object.assign(new Error(e.message +
42
+ '\nIt seems that the server was not started. Make sure to call \'cds.test(...)\' or \'cds.test.run(...)\'.'),e)
43
+ e.stack = null // stack is just clutter here
44
+ throw e
45
+ }
39
46
  if (!e.response) throw e
40
47
  if (!e.response.data) throw e
41
48
  if (!e.response.data.error) throw new Error(e.message + '\n\n' + e.response.data)
package/lib/utils/data.js CHANGED
@@ -6,7 +6,7 @@ class DataUtil {
6
6
  if (!db) db = await cds.connect.to('db')
7
7
  if (!this._deletes) {
8
8
  this._deletes = []
9
- for (const entity of db.model.minified().all('entity')) {
9
+ for (const entity of db.model.each('entity')) {
10
10
  if (!entity.query && entity['@cds.persistence.skip'] !== true) {
11
11
  this._deletes.push(cds.ql.DELETE.from(entity))
12
12
  }
@@ -3,6 +3,13 @@ const LOG = cds.log('audit-log')
3
3
 
4
4
  const v2utils = require('./utils/v2')
5
5
 
6
+ const ANONYMOUS = 'anonymous'
7
+
8
+ const _getTenantAndUser = () => ({
9
+ user: (cds.context && cds.context.user && cds.context.user.id) || ANONYMOUS,
10
+ tenant: (cds.context && cds.context.tenant) || ANONYMOUS
11
+ })
12
+
6
13
  module.exports = class AuditLogService extends cds.MessagingService {
7
14
  async init() {
8
15
  // call MessagingService's init, which handles outboxing
@@ -13,7 +20,8 @@ module.exports = class AuditLogService extends cds.MessagingService {
13
20
  this.ready = !!this.alc
14
21
  }
15
22
 
16
- async emit(event, data) {
23
+ async emit(first, second) {
24
+ const { event, data } = typeof first === 'object' ? first : { event: first, data: second }
17
25
  if (!this.options.outbox) return this.send(event, data)
18
26
 
19
27
  if (this.ready && this[event]) {
@@ -33,10 +41,7 @@ module.exports = class AuditLogService extends cds.MessagingService {
33
41
  async dataAccessLog({ accesses }) {
34
42
  if (!this.ready) throw new Error('AuditLogService not connected')
35
43
 
36
- const {
37
- tenant,
38
- user: { id: user }
39
- } = cds.context
44
+ const { tenant, user } = _getTenantAndUser()
40
45
 
41
46
  // build the logs
42
47
  const { entries, errors } = v2utils.buildDataAccessLogs(this.alc, accesses, tenant, user)
@@ -59,10 +64,7 @@ module.exports = class AuditLogService extends cds.MessagingService {
59
64
  async dataModificationLog({ modifications }) {
60
65
  if (!this.ready) throw new Error('AuditLogService not connected')
61
66
 
62
- const {
63
- tenant,
64
- user: { id: user }
65
- } = cds.context
67
+ const { tenant, user } = _getTenantAndUser()
66
68
 
67
69
  // build the logs
68
70
  const { entries, errors } = v2utils.buildDataModificationLogs(this.alc, modifications, tenant, user)
@@ -87,8 +89,9 @@ module.exports = class AuditLogService extends cds.MessagingService {
87
89
  // cds.context not always set on auth-related errors -> try to extract from data
88
90
  let user, tenant
89
91
  if (cds.context) {
90
- tenant = cds.context.tenant
91
- user = cds.context.user && cds.context.user.id
92
+ const tenantAndUser = _getTenantAndUser()
93
+ tenant = tenantAndUser.tenant
94
+ user = tenantAndUser.user
92
95
  } else {
93
96
  try {
94
97
  const parsed = JSON.parse(data)
@@ -101,10 +104,10 @@ module.exports = class AuditLogService extends cds.MessagingService {
101
104
  delete parsed.user
102
105
  }
103
106
  data = JSON.stringify(parsed)
104
- } catch (e) {
105
- // ignore
106
- }
107
+ } catch (e) {}
107
108
  }
109
+ if (!tenant) tenant = ANONYMOUS
110
+ if (!user) user = ANONYMOUS
108
111
 
109
112
  // build the log
110
113
  const entry = v2utils.buildSecurityLog(this.alc, action, data, tenant, user)
@@ -117,10 +120,7 @@ module.exports = class AuditLogService extends cds.MessagingService {
117
120
  async configChangeLog({ action, success, configurations }) {
118
121
  if (!this.ready) throw new Error('AuditLogService not connected')
119
122
 
120
- const {
121
- tenant,
122
- user: { id: user }
123
- } = cds.context
123
+ const { tenant, user } = _getTenantAndUser()
124
124
 
125
125
  // build the logs
126
126
  const { entries, errors } = v2utils.buildConfigChangeLogs(this.alc, configurations, tenant, user)
@@ -40,7 +40,7 @@ const _processorFnAccess = (accessLogs, model, req) => {
40
40
  element.parent['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' &&
41
41
  accessLog.dataSubject.id.length === 0 // > id still an array -> promise not yet set
42
42
  ) {
43
- addDataSubjectForDetailsEntity(row, accessLog, req, entity, model)
43
+ addDataSubjectForDetailsEntity(row, accessLog, req, entity, model, element)
44
44
  }
45
45
  }
46
46
  }
@@ -41,7 +41,8 @@ const _getOldAndNew = (action, row, key) => {
41
41
  const _addAttribute = (log, action, row, key) => {
42
42
  if (!log.attributes.find(ele => ele.name === key)) {
43
43
  const { oldValue, newValue } = _getOldAndNew(action, row, key)
44
- if (oldValue !== newValue) log.attributes.push({ name: key, oldValue, newValue })
44
+ if (oldValue !== newValue)
45
+ log.attributes.push({ name: key, oldValue: String(oldValue), newValue: String(newValue) })
45
46
  }
46
47
  }
47
48
 
@@ -75,7 +76,7 @@ const _processorFnModification = (modificationLogs, model, req, beforeWrite) =>
75
76
  element.parent['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' &&
76
77
  modificationLog.dataSubject.id.length === 0 // > id still an array -> promise not yet set
77
78
  ) {
78
- addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model)
79
+ addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model, element)
79
80
  }
80
81
  }
81
82
 
@@ -1,7 +1,6 @@
1
1
  const cds = require('../../../cds')
2
2
 
3
3
  const { getDataSubject } = require('../../../common/utils/csn')
4
- const { getBackLinks, isSelfManaged } = require('../../../common/utils/backlinks')
5
4
 
6
5
  const WRITE = { CREATE: 1, UPDATE: 1, DELETE: 1 }
7
6
  const ASPECTS = { CREATE: '_auditCreate', READ: '_auditRead', UPDATE: '_auditUpdate', DELETE: '_auditDelete' }
@@ -23,8 +22,11 @@ const getPick = event => {
23
22
  const categories = []
24
23
  if (!element.isAssociation && element.key) categories.push('ObjectID')
25
24
  if (
26
- element.parent['@PersonalData.EntitySemantics'] === 'DataSubject' &&
27
- element['@PersonalData.FieldSemantics'] === 'DataSubjectID'
25
+ element['@PersonalData.FieldSemantics'] === 'DataSubjectID' &&
26
+ // item element of arrayed element has no parent, but
27
+ // at the moment annotation on item level is not supported
28
+ element.parent &&
29
+ element.parent['@PersonalData.EntitySemantics'] === 'DataSubject'
28
30
  )
29
31
  categories.push('DataSubjectID')
30
32
  if (event in WRITE && element['@PersonalData.IsPotentiallyPersonal']) categories.push('IsPotentiallyPersonal')
@@ -55,14 +57,15 @@ const createLogEntry = (logs, entity, row) => {
55
57
  }
56
58
 
57
59
  const addObjectID = (log, row, key) => {
58
- if (!log.dataObject.id.find(ele => ele.keyName === key)) log.dataObject.id.push({ keyName: key, value: row[key] })
60
+ if (!log.dataObject.id.find(ele => ele.keyName === key))
61
+ log.dataObject.id.push({ keyName: key, value: String(row[key]) })
59
62
  }
60
63
 
61
64
  const addDataSubject = (log, row, key, entity) => {
62
65
  if (!log.dataSubject.type) log.dataSubject.type = entity.name
63
66
  if (!log.dataSubject.id.find(ele => ele.key === key)) {
64
67
  const value = row[key] || (row._old && row._old[key])
65
- log.dataSubject.id.push({ keyName: key, value })
68
+ log.dataSubject.id.push({ keyName: key, value: String(value) })
66
69
  }
67
70
  }
68
71
 
@@ -77,82 +80,39 @@ const _addKeysToWhere = (child, row) => {
77
80
  return keysWithValue
78
81
  }
79
82
 
80
- const _addForeignKeysToWhere = (parent, child, assoc) => {
81
- const foreignKeys = []
82
- const backlinks = getBackLinks(assoc)
83
- let parentName, childName
84
- if (assoc.isComposition && assoc.is2one && !assoc.on) {
85
- // look for foreign keys in parent
86
- parentName = parent.name
87
- childName = child.name
88
- } else if (assoc.is2many && isSelfManaged(assoc)) {
89
- // look for foreign keys in child
90
- parentName = child.name
91
- childName = parent.name
92
- }
93
-
94
- backlinks.forEach(el => {
95
- if (foreignKeys.length > 0) foreignKeys.push('and')
96
- foreignKeys.push({ ref: [parentName, el.entityKey] }, '=', {
97
- ref: [childName, el.targetKey]
98
- })
99
- })
100
- return foreignKeys
101
- }
102
-
103
- const _buildWhere = (child, dataSubjectInfo, row) => {
104
- const where = []
105
- where.push(
106
- ..._addForeignKeysToWhere(dataSubjectInfo.entity, child, dataSubjectInfo.assoc),
107
- 'and',
108
- ..._addKeysToWhere(child, row)
109
- )
110
- return where
111
- }
112
-
113
- const _buildSubSelect = (child, dataSubjectInfo, row) => {
114
- let previousCqn
83
+ const _buildSubSelect = (model, child, { element, up }, row, previousCqn) => {
84
+ const entity = element.parent
115
85
  const childCqn = SELECT.from(child.name)
116
86
  .columns(Object.keys(child.keys))
117
- .where(_buildWhere(child, dataSubjectInfo.previous[0] ? dataSubjectInfo.previous[0] : dataSubjectInfo, row))
118
- if (dataSubjectInfo.previous.length > 0) {
119
- let currentCQN
120
-
121
- dataSubjectInfo.previous.forEach((el, i) => {
122
- const nextEl = dataSubjectInfo.previous[i + 1]
123
- const args = nextEl
124
- ? [nextEl.entity, el.entity, nextEl.assoc]
125
- : [dataSubjectInfo.entity, el.entity, dataSubjectInfo.assoc]
126
-
127
- currentCQN = SELECT.from(el.entity.name)
128
- .columns(Object.keys(el.entity.keys))
129
- .where([..._addForeignKeysToWhere(...args), 'and', 'exists', previousCqn || childCqn])
130
-
131
- previousCqn = currentCQN
132
- })
87
+ .where(entity._relations[element.name].join(child.name, entity.name))
88
+ if (previousCqn) {
89
+ childCqn.where('exists', previousCqn)
90
+ } else {
91
+ childCqn.where(_addKeysToWhere(child, row))
133
92
  }
134
- return previousCqn || childCqn
93
+ if (up) return _buildSubSelect(model, entity, up, {}, childCqn)
94
+ return childCqn
135
95
  }
136
96
 
137
- const _getDataSubjectIdPromise = (child, dataSubjectInfo, row, req) => {
97
+ const _getDataSubjectIdPromise = (child, dataSubjectInfo, row, req, model) => {
138
98
  const root = dataSubjectInfo.entity
139
99
  const cqn = SELECT.from(root.name)
140
100
  .columns(Object.keys(root.keys))
141
- .where(['exists', _buildSubSelect(child, dataSubjectInfo, row)])
101
+ .where(['exists', _buildSubSelect(model, child, dataSubjectInfo.up, row)])
142
102
  return cds
143
103
  .tx(req)
144
104
  .run(cqn)
145
105
  .then(res => {
146
106
  const id = []
147
- for (const k in res[0]) id.push({ keyName: k, value: res[0][k] })
107
+ for (const k in res[0]) id.push({ keyName: k, value: String(res[0][k]) })
148
108
  return id
149
109
  })
150
110
  }
151
111
 
152
- const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
112
+ const addDataSubjectForDetailsEntity = (row, log, req, entity, model, element) => {
153
113
  const role = entity['@PersonalData.DataSubjectRole']
154
114
 
155
- const dataSubjectInfo = getDataSubject(entity, model, role)
115
+ const dataSubjectInfo = getDataSubject(entity, model, role, element)
156
116
 
157
117
  log.dataSubject.type = dataSubjectInfo.entity.name
158
118
 
@@ -165,7 +125,7 @@ const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
165
125
  if (!req.context._audit.dataSubjects.has(mapKey)) req.context._audit.dataSubjects.set(mapKey, new Map())
166
126
  const map = req.context._audit.dataSubjects.get(mapKey)
167
127
  if (map.has(role)) log.dataSubject.id = map.get(role)
168
- else map.set(role, _getDataSubjectIdPromise(entity, dataSubjectInfo, row, req))
128
+ else map.set(role, _getDataSubjectIdPromise(entity, dataSubjectInfo, row, req, model))
169
129
  }
170
130
 
171
131
  const resolveDataSubjectPromises = async logs => {
@@ -3,6 +3,8 @@ const LOG = cds.log('odata')
3
3
 
4
4
  const OData = require('./OData')
5
5
 
6
+ const { alias2ref } = require('../../../common/utils/csn')
7
+
6
8
  function _createNewService(name, csn, defaultOptions) {
7
9
  const reflectedModel = cds.linked(cds.compile.for.odata(csn))
8
10
  const options = Object.assign({}, defaultOptions, { reflectedModel })
@@ -13,6 +15,8 @@ function _createNewService(name, csn, defaultOptions) {
13
15
  service._isExtended = true
14
16
 
15
17
  const edm = cds.compile.to.edm(csn, { service: name })
18
+ alias2ref(service, edm)
19
+
16
20
  const odataService = new OData(edm, csn, options)
17
21
  odataService.addCDSServiceToChannel(service)
18
22
 
@@ -84,6 +88,7 @@ class Dispatcher {
84
88
  * @returns {Promise}
85
89
  */
86
90
  async dispatch(req, res) {
91
+ // here, req is express' req -> req.tenant not available
87
92
  if (cds._mtxEnabled && req.user && req.user.tenant) {
88
93
  // enable mtx, if not done yet
89
94
  if (!this._extMap) {
@@ -95,6 +100,7 @@ class Dispatcher {
95
100
 
96
101
  const { alpha_toggles: alphaToggles } = cds.env.features
97
102
 
103
+ // here, req is express' req -> req.tenant not available
98
104
  const hash = alphaToggles ? cds.mtx._getHash(req) : req.user.tenant
99
105
 
100
106
  // add hash to map, if not done yet