@sap/cds 5.5.5 → 5.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/CHANGELOG.md +139 -1
  2. package/apis/services.d.ts +31 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/to/sql.js +22 -2
  12. package/lib/connect/bindings.js +2 -1
  13. package/lib/core/reflect.js +4 -1
  14. package/lib/env/index.js +180 -42
  15. package/lib/env/requires.js +16 -1
  16. package/lib/i18n/localize.js +33 -5
  17. package/lib/index.js +3 -3
  18. package/lib/log/format/kibana.js +6 -2
  19. package/lib/ql/Query.js +1 -0
  20. package/lib/ql/SELECT.js +15 -8
  21. package/lib/ql/Whereable.js +5 -0
  22. package/lib/req/context.js +13 -5
  23. package/lib/serve/Service-dispatch.js +8 -1
  24. package/lib/utils/axios.js +7 -0
  25. package/lib/utils/data.js +1 -1
  26. package/lib/utils/tests.js +1 -1
  27. package/libx/_runtime/audit/Service.js +18 -18
  28. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  29. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  30. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  31. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +12 -4
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
  46. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  51. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  53. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  54. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  55. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  56. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  57. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  58. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  59. package/libx/_runtime/cds-services/util/assert.js +29 -13
  60. package/libx/_runtime/cds.js +2 -1
  61. package/libx/_runtime/common/aspects/Association.js +72 -0
  62. package/libx/_runtime/common/aspects/any.js +8 -45
  63. package/libx/_runtime/common/aspects/entity.js +0 -1
  64. package/libx/_runtime/common/aspects/relation.js +40 -0
  65. package/libx/_runtime/common/aspects/utils.js +73 -1
  66. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  67. package/libx/_runtime/common/composition/data.js +3 -2
  68. package/libx/_runtime/common/composition/delete.js +3 -1
  69. package/libx/_runtime/common/composition/tree.js +23 -18
  70. package/libx/_runtime/common/composition/update.js +9 -1
  71. package/libx/_runtime/common/composition/utils.js +34 -8
  72. package/libx/_runtime/common/error/frontend.js +6 -1
  73. package/libx/_runtime/common/generic/auth.js +5 -9
  74. package/libx/_runtime/common/generic/crud.js +2 -2
  75. package/libx/_runtime/common/generic/etag.js +11 -8
  76. package/libx/_runtime/common/generic/input.js +3 -3
  77. package/libx/_runtime/common/generic/paging.js +9 -5
  78. package/libx/_runtime/common/generic/put.js +3 -2
  79. package/libx/_runtime/common/generic/sorting.js +3 -3
  80. package/libx/_runtime/common/generic/temporal.js +3 -3
  81. package/libx/_runtime/common/utils/cqn.js +20 -1
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  83. package/libx/_runtime/common/utils/csn.js +50 -52
  84. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  85. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  86. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  87. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  88. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  89. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  90. package/libx/_runtime/common/utils/resolveView.js +7 -5
  91. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  92. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  93. package/libx/_runtime/common/utils/template.js +54 -46
  94. package/libx/_runtime/db/Service.js +9 -2
  95. package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
  96. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  97. package/libx/_runtime/db/generic/arrayed.js +13 -28
  98. package/libx/_runtime/db/generic/create.js +1 -0
  99. package/libx/_runtime/db/generic/input.js +7 -11
  100. package/libx/_runtime/db/generic/integrity.js +2 -2
  101. package/libx/_runtime/db/generic/rewrite.js +2 -5
  102. package/libx/_runtime/db/generic/update.js +1 -0
  103. package/libx/_runtime/db/query/read.js +9 -4
  104. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  105. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  106. package/libx/_runtime/db/utils/columns.js +14 -43
  107. package/libx/_runtime/fiori/generic/activate.js +3 -2
  108. package/libx/_runtime/fiori/generic/before.js +2 -2
  109. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  110. package/libx/_runtime/fiori/generic/delete.js +3 -2
  111. package/libx/_runtime/fiori/generic/edit.js +3 -3
  112. package/libx/_runtime/fiori/generic/new.js +2 -2
  113. package/libx/_runtime/fiori/generic/patch.js +2 -2
  114. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  115. package/libx/_runtime/fiori/generic/read.js +45 -63
  116. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  117. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  118. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  119. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  120. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  121. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  122. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  123. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  124. package/libx/_runtime/fiori/utils/handler.js +3 -13
  125. package/libx/_runtime/fiori/utils/where.js +6 -1
  126. package/libx/_runtime/hana/pool.js +12 -11
  127. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  128. package/libx/_runtime/hana/searchToContains.js +3 -3
  129. package/libx/_runtime/index.js +5 -2
  130. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  131. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  132. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  133. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  134. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  135. package/libx/_runtime/messaging/message-queuing.js +18 -0
  136. package/libx/_runtime/remote/Service.js +20 -4
  137. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  138. package/libx/_runtime/remote/utils/client.js +117 -23
  139. package/libx/_runtime/sqlite/Service.js +2 -2
  140. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  141. package/libx/gql/GraphQLAdapter.js +33 -0
  142. package/libx/gql/constants/adapter.js +69 -0
  143. package/libx/gql/constants/cds.js +18 -0
  144. package/libx/gql/constants/graphql.js +33 -0
  145. package/libx/gql/resolvers/crud/create.js +15 -0
  146. package/libx/gql/resolvers/crud/delete.js +24 -0
  147. package/libx/gql/resolvers/crud/index.js +6 -0
  148. package/libx/gql/resolvers/crud/read.js +25 -0
  149. package/libx/gql/resolvers/crud/update.js +31 -0
  150. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  151. package/libx/gql/resolvers/field.js +5 -0
  152. package/libx/gql/resolvers/index.js +7 -0
  153. package/libx/gql/resolvers/mutation.js +23 -0
  154. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  155. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  156. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  157. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  158. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  159. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  160. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  161. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  162. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  164. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  165. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  166. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  167. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  168. package/libx/gql/resolvers/query.js +13 -0
  169. package/libx/gql/resolvers/root.js +34 -0
  170. package/libx/gql/schema/generate.js +18 -0
  171. package/libx/gql/schema/index.js +5 -0
  172. package/libx/gql/schema/mutation.js +76 -0
  173. package/libx/gql/schema/query.js +108 -0
  174. package/libx/gql/schema/typeDefMap.js +45 -0
  175. package/libx/gql/schema/utils/index.js +54 -0
  176. package/libx/gql/utils/index.js +12 -0
  177. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  178. package/libx/odata/index.js +80 -0
  179. package/libx/odata/odata2cqn/afterburner.js +170 -0
  180. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  181. package/libx/odata/odata2cqn/index.js +3 -0
  182. package/libx/odata/odata2cqn/parser.js +1 -0
  183. package/libx/odata/utils/index.js +64 -0
  184. package/libx/rest/RestAdapter.js +101 -0
  185. package/libx/rest/RestRequest.js +30 -0
  186. package/libx/rest/index.js +3 -0
  187. package/libx/rest/middleware/auth.js +22 -0
  188. package/libx/rest/middleware/content.js +15 -0
  189. package/libx/rest/middleware/create.js +40 -0
  190. package/libx/rest/middleware/delete.js +20 -0
  191. package/libx/rest/middleware/error.js +56 -0
  192. package/libx/rest/middleware/operation.js +39 -0
  193. package/libx/rest/middleware/parse.js +90 -0
  194. package/libx/rest/middleware/read.js +29 -0
  195. package/libx/rest/middleware/update.js +42 -0
  196. package/libx/rest/utils/data.js +65 -0
  197. package/package.json +4 -1
  198. package/server.js +29 -7
  199. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  200. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  201. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  202. package/libx/_runtime/common/utils/backlinks.js +0 -83
  203. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  204. package/libx/_runtime/odata/index.js +0 -55
  205. package/libx/_runtime/odata/odata2cqn.js +0 -1
  206. package/libx/_runtime/odata/readToCqn.js +0 -129
  207. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
package/lib/env/index.js CHANGED
@@ -1,10 +1,9 @@
1
1
  const { isfile, fs } = require('../utils')
2
2
  const DEFAULTS = require('./defaults'), defaults = require.resolve ('./defaults')
3
- const user_home = require('os').homedir()
3
+ const os_user_home = require('os').homedir()
4
4
  const compat = require('./compat')
5
5
  const path = require('path')
6
6
 
7
-
8
7
  /**
9
8
  * Both a config inctance as well as factory for.
10
9
  */
@@ -30,38 +29,73 @@ class Config {
30
29
  this._profiles = _determineProfilesFrom (process.env)
31
30
  this._profiles._defined = new Set()
32
31
 
33
- // compat requires default values
32
+ // 1. set compat requires default values
34
33
  if (_context === 'cds' && _defaults) this.add (DEFAULTS, defaults)
35
34
  if (_context === 'cds' && _defaults) compat (this)
36
35
  if (!_home) return
37
36
 
38
- // 1. fill-in defaults for process.env, unless already defined
37
+ // fill-in defaults to process.env, unless already defined => 4.
39
38
  this._add_to_process_env (_home, 'default-env.json')
40
- // 1.5 additional env for dev
39
+
40
+ // additional env for dev => 4.
41
41
  if (process.env.NODE_ENV !== 'production') {
42
42
  this._add_to_process_env (_home, '.env')
43
43
  }
44
44
 
45
+ const sources = Config.sources(_home, _context)
46
+
45
47
  // 2. read config sources in defined order
46
- if (_context === 'cds') {
47
- this._load (user_home, '.cdsrc.json')
48
- this._load (_home, '.cdsrc.json')
48
+ for (const source of sources) {
49
+ this._load(source.path, source.file, source.mapper, this._profiles, false)
50
+ }
51
+
52
+ // 3. read important (!) profiles from config sources in defined order
53
+ const overwriteProfiles = new Set(this.profiles.map( profile => `${profile}!` ).filter( profile => this._profiles._defined.has( profile ) ));
54
+ if (overwriteProfiles.size > 0) {
55
+ for (const source of sources) {
56
+ this._load(source.path, source.file, source.mapper, overwriteProfiles, true)
57
+ }
49
58
  }
50
- this._load (_home, 'package.json', p => p[_context])
51
- this.add (_process_env4(_context), '{process.env}')
52
59
 
53
- // 3. link dependant services (through kind/use)
60
+ // 4. add process env
61
+ this._add_process_env(_context, _home)
62
+
63
+ // 6. link dependant services (through kind/use)
54
64
  this._link_required_services()
55
- // 4. complete service configurations from VCAP
65
+ // 7. complete service configurations from VCAP
56
66
  this._add_vcap_services (process.env.VCAP_SERVICES)
57
67
 
58
- // 5. Add compatibility for mtx
68
+ // 8. Add compatibility for mtx
59
69
  if (this.requires && this.requires.db) {
60
70
  if (this.requires.multitenancy !== undefined) {
61
71
  Object.defineProperty(this.requires.db, 'multiTenant', { value: !!this.requires.multitenancy })
62
72
  }
63
73
  else if (this.requires.db.multiTenant !== undefined) this.requires.multitenancy = this.requires.db.multiTenant
64
74
  }
75
+
76
+ // Only if feature is enabled
77
+ this._emulate_vcap_services()
78
+ }
79
+
80
+ /**
81
+ * Get configuration sources
82
+ *
83
+ * @param {string} home Project home
84
+ * @param {string} context configuration context literal
85
+ */
86
+ static sources(home, context = 'cds', ) {
87
+ if (!home) throw new Error('Missing parameter "home".')
88
+ const user_home = process.env.CDS_USER_HOME || os_user_home
89
+
90
+ let sources = [
91
+ { name: 'USER_HOME', path: user_home, file: '.cdsrc.json' },
92
+ { name: 'PROJECT', path: home, file: '.cdsrc.json' },
93
+ { name: 'PACKAGE', path: home, file: 'package.json', mapper: p => p[context] },
94
+ { name: 'PRIVATE', path: home, file: '.cdsrc-private.json' }
95
+ ]
96
+
97
+ if (context !== 'cds') sources = sources.filter( source => source.name === 'PACKAGE' )
98
+ return sources
65
99
  }
66
100
 
67
101
  /**
@@ -122,7 +156,7 @@ class Config {
122
156
  * For BAS only: get all defined profiles (could include some from the defaults)
123
157
  */
124
158
  get "defined-profiles" () {
125
- return Array.from (this._profiles._defined)
159
+ return Array.from (new Set(Array.from(this._profiles._defined).map( profile => profile.endsWith("!") ? profile.slice(0, -1) : profile)))
126
160
  }
127
161
 
128
162
  get profiles() {
@@ -137,15 +171,26 @@ class Config {
137
171
  //
138
172
 
139
173
 
140
- _load (cwd, file, _conf=o=>o) {
174
+ /**
175
+ * Load from JSON file or directory
176
+ *
177
+ * No profile support!
178
+ */
179
+ _loadFromPath (_path, _basePath) {
180
+ if (_basePath && !path.isAbsolute(_path)) _path = path.join(_basePath, _path)
181
+ const json = _readJson (_path) || _readFromDir (_path)
182
+ if (json) this.add (json, _path, new Set())
183
+ }
184
+
185
+ _load (cwd, file, _conf=o=>o, profiles, profiles_only) {
141
186
  const json = _readJson (file = path.join(cwd, file)) // only support JSON
142
- if (json) this.add (_conf (json), file)
187
+ if (json) this.add (_conf (json), file, profiles, profiles_only)
143
188
  }
144
189
 
145
- add (conf, /*from:*/ _src) {
190
+ add (conf, /*from:*/ _src, profiles = this._profiles, profiles_only = false) {
146
191
  if (!conf) return this
147
192
  if (_src) this._sources.push (_src)
148
- _merge (this, conf, this._profiles)
193
+ _merge (this, conf, profiles, undefined, profiles_only)
149
194
  return this
150
195
  }
151
196
 
@@ -165,14 +210,48 @@ class Config {
165
210
  }
166
211
  }
167
212
 
213
+
214
+ _add_process_env (prefix, basePath) {
215
+ const {env} = process
216
+ const PREF = prefix.toUpperCase(), my = { CONFIG: PREF+'_CONFIG', ENV: PREF+'_ENV' }
217
+ const configEnvValue = env[my.CONFIG]
218
+ let config
219
+ try {
220
+ // CDS_CONFIG={ /* json */}
221
+ config = JSON.parse (configEnvValue)
222
+ } catch (e) {
223
+ // CDS_CONFIG=/path/to/config.json *OR* CDS_CONFIG=/path/to/config/dir
224
+ if (configEnvValue && typeof configEnvValue === "string") this._loadFromPath (configEnvValue, basePath)
225
+ }
226
+
227
+ if (!config) config = {}
228
+ const pref_ = RegExp('^'+prefix+'[._]','i')
229
+ for (let p in env) if (!(p in my) && pref_.test(p)) {
230
+ const key = /[a-z]/.test(p) ? p : p.toLowerCase() //> CDS_FOO_BAR -> cds_foo_bar
231
+ const path = key.slice(prefix.length+1) .split (key[prefix.length]) //> ['foo','bar']
232
+ for (var o=config,next;;) {
233
+ next = path.shift()
234
+ if (!path.length) break
235
+ o = o[next] || (o[next] = {})
236
+ }
237
+ o[next] = _value4(env[p])
238
+ }
239
+
240
+ this.add(config, '{process.env}')
241
+ }
242
+
168
243
  _link_required_services () {
169
- const { requires } = this
170
- for (let each in requires) requires[each] = _merged (each)
244
+ const { requires } = this, protos = requires && requires._prototypes || {}
245
+ for (let each in requires) {
246
+ requires[each] = _merged (each)
247
+ // if we got an invalid value, remove it (would anyways cause trouble down the road)
248
+ if (!requires[each]) delete requires[each]
249
+ }
171
250
  function _merged (key) {
172
- const entry = requires[key]
173
- if (entry._is_merged || entry.kind === key || !(entry.kind in requires)) return entry
251
+ const entry = requires[key] || protos[key]
252
+ if (!entry || entry._is_merged || entry.kind === key || !(entry.kind in requires) && !(entry.kind in protos)) return entry
174
253
  const clone = _merge ({}, _merged (entry.kind)) // first apply inherited data
175
- _merge (clone, entry, false, false, o => _merge({},o)) // then apply overridden data
254
+ _merge (clone, entry, false, false) // then apply overridden data
176
255
  return Object.defineProperty (clone, '_is_merged', {value:true})
177
256
  }
178
257
  }
@@ -190,6 +269,14 @@ class Config {
190
269
  }
191
270
  }
192
271
 
272
+ /**
273
+ * Build VCAP_SERVICES for compatibility (for example for CloudSDK) or for running
274
+ * locally with credentials (hybrid mode).
275
+ */
276
+ _emulate_vcap_services() {
277
+ if (!(this.features && this.features.emulate_vcap_services)) return
278
+ process.env.VCAP_SERVICES = JSON.stringify(build_vcap_services(this))
279
+ }
193
280
 
194
281
  //////////////////////////////////////////////////////////////////////////
195
282
  //
@@ -226,7 +313,7 @@ class Config {
226
313
  /**
227
314
  * @returns {Config} dst
228
315
  */
229
- function _merge (dst, src, _profiles, _cloned) {
316
+ function _merge (dst, src, _profiles, _cloned, _profiles_only = false) {
230
317
  const profiled = [], descr = Object.getOwnPropertyDescriptors(src)
231
318
  for (let p in descr) {
232
319
  const pd = descr[p]
@@ -239,39 +326,65 @@ function _merge (dst, src, _profiles, _cloned) {
239
326
  if (_profiles && p[0] === '[') {
240
327
  if (_profiles._defined) _profiles._defined.add (p.slice(1,-1))
241
328
  if (_profiles.has(p.slice(1,-1)))
242
- profiled.push (()=> _merge (dst, src[p], _profiles, _cloned))
329
+ profiled.push (()=> _merge (dst, src[p], _profiles, _cloned, false))
243
330
  continue
244
331
  }
245
332
 
246
333
  const v = pd.value
247
334
  if (typeof v === 'object' && !Array.isArray(v)) {
248
335
  if (!dst[p]) dst[p] = {}; else if (_cloned) dst[p] = _cloned(dst[p])
249
- _merge (dst[p], v, _profiles, _cloned)
336
+ _merge (dst[p], v, _profiles, _cloned, _profiles_only)
250
337
  continue
251
338
  }
252
339
 
253
- if (v !== undefined) dst[p] = v
340
+ if (!_profiles_only && v !== undefined) dst[p] = v
254
341
  }
255
342
  for (let each of profiled) each()
256
343
  return dst
257
344
  }
258
345
 
259
- function _process_env4 (prefix) {
260
- const {env} = process
261
- const PREF = prefix.toUpperCase(), my = { CONFIG: PREF+'_CONFIG', ENV: PREF+'_ENV' }
262
- const out = JSON.parse (env[my.CONFIG] || '{}')
263
- const pref_ = RegExp('^'+prefix+'[._]','i')
264
- for (let p in env) if (!(p in my) && pref_.test(p)) {
265
- const key = /[a-z]/.test(p) ? p : p.toLowerCase() //> CDS_FOO_BAR -> cds_foo_bar
266
- const path = key.slice(prefix.length+1) .split (key[prefix.length]) //> ['foo','bar']
267
- for (var o=out,next;;) {
268
- next = path.shift()
269
- if (!path.length) break
270
- o = o[next] || (o[next] = {})
346
+ function _readFromDir (p, isDir) {
347
+ if (typeof isDir === "undefined") {
348
+ try {
349
+ const entry = fs.statSync(p)
350
+ if (entry.isDirectory()) {
351
+ isDir = true
352
+ } else if (isFile(p, entry)) {
353
+ isDir = false
354
+ } else {
355
+ return undefined
356
+ }
357
+ } catch (e) {
358
+ return undefined
359
+ }
360
+ }
361
+ if (isDir) {
362
+ const result = {}
363
+ const entries = fs.readdirSync(p, {withFileTypes: true})
364
+ for (let entry of entries) {
365
+ const entryPath = path.join(p, entry.name)
366
+ if (entry.isDirectory()) {
367
+ result[entry.name] = _readFromDir(entryPath, true)
368
+ } else if (isFile(entryPath, entry)) {
369
+ result[entry.name] = _readFromDir(entryPath, false)
370
+ }
271
371
  }
272
- o[next] = _value4(env[p])
372
+ return result
373
+ } else {
374
+ return _value4(fs.readFileSync(p, "utf-8"))
375
+ }
376
+ }
377
+
378
+ function isFile(p, entry) {
379
+ if (entry.isFile()) return true
380
+ if (entry.isSymbolicLink()) {
381
+ // Kubernetes credentials use symlinks
382
+ const target = fs.realpathSync(p)
383
+ const targetStat = fs.statSync(target)
384
+
385
+ if (targetStat.isFile()) return true
273
386
  }
274
- return out
387
+ return false
275
388
  }
276
389
 
277
390
  function _value4 (val) {
@@ -288,6 +401,7 @@ function _add_vcap_services_to (env, vcaps={}) {
288
401
  let any
289
402
  for (let service in env.requires) {
290
403
  const conf = env.requires [service]
404
+ if (!conf) continue
291
405
  const { credentials } = (
292
406
  conf.vcap && _fetch (conf.vcap) || //> alternatives, e.g. { name:'foo', tag:'foo' }
293
407
  _fetch ({ name: service }) ||
@@ -297,7 +411,9 @@ function _add_vcap_services_to (env, vcaps={}) {
297
411
  {/* not found */}
298
412
  )
299
413
  // Merge `credentials`. Needed because some app-defined things like `credentials.destination` must survive.
300
- if (credentials) any = conf.credentials = Object.assign ({}, conf.credentials, credentials)
414
+ if (credentials) {
415
+ any = conf.credentials = Object.assign ({}, conf.credentials, credentials)
416
+ }
301
417
  }
302
418
  return any
303
419
 
@@ -347,6 +463,28 @@ function set (o,p,value) {
347
463
  return value
348
464
  }
349
465
 
466
+ function build_vcap_services(env) {
467
+ let v = {}
468
+ let names = new Set()
469
+
470
+ for (const service in env.requires) {
471
+ let { vcap, credentials, binding } = env.requires[service]
472
+ // "binding.vcap" is chosen over "vcap" because it is meta data resolved from the real service (-> cds bind)
473
+ if (binding && binding.vcap) vcap = binding.vcap
474
+ if (vcap && vcap.label && credentials && Object.keys(credentials).length > 0) {
475
+ // Only one entry for a (instance) name. Generate name from label and plan if not given.
476
+ const { label, plan } = vcap
477
+ const name = vcap.name || `instance:${label}:${plan || ""}`
478
+ if (names.has(name)) continue
479
+ names.add(name)
480
+
481
+ if (!v[label]) v[label] = []
482
+ v[label].push(Object.assign({ name }, vcap, { credentials }))
483
+ }
484
+ }
485
+
486
+ return v
487
+ }
350
488
 
351
489
  /** @type Config & typeof DEFAULTS */
352
490
  module.exports = Config.prototype.for('cds')
@@ -31,7 +31,16 @@ module.exports = {
31
31
  "xsuaa-auth": {
32
32
  strategy: 'xsuaa',
33
33
  },
34
- destinations: undefined,
34
+ destinations: {
35
+ vcap: {
36
+ label: 'destination'
37
+ }
38
+ },
39
+ xsuaa: {
40
+ vcap: {
41
+ label: 'xsuaa'
42
+ }
43
+ },
35
44
  monitoring: undefined,
36
45
  logging: undefined,
37
46
  audit: undefined,
@@ -105,6 +114,12 @@ module.exports = {
105
114
  // model: 'AuditLogService.cds',
106
115
  outbox: true,
107
116
  vcap: { label: "auditlog" },
117
+ },
118
+
119
+ _prototypes: {
120
+ "uiflex": {
121
+ "model": "@sap/cds/libx/_runtime/fiori/uiflex/extensibility"
122
+ },
108
123
  }
109
124
  }
110
125
 
@@ -35,13 +35,18 @@ function localize (model, /*with:*/ locale, aString) {
35
35
  const TEXT_KEY_MARKER = 'i18n>'
36
36
  const TEXT_KEYS = /"([^"{]+)?{b?i18n>([^"}]+)}([^"]+)?"/g
37
37
  function localizeString (aString, bundle) {
38
- if (!bundle) return aString
38
+ if (!bundle || !aString) return aString
39
+ if (typeof aString === 'object') aString = JSON.stringify(aString, null, 2)
39
40
  // quick check for presence of any text key, to avoid expensive operation below
40
41
  if (aString.indexOf(TEXT_KEY_MARKER) < 0) return aString
41
-
42
- return aString.replace (TEXT_KEYS, // always creates a new loong string
43
- (_, left='', key, right='') => `"${left}${bundle[key]||key}${right}"`
44
- )
42
+ const isXml = aString.startsWith('<?xml')
43
+ const isJson = /^[{[]/.test(aString)
44
+ return aString.replace (TEXT_KEYS, (_, left='', key, right='') => {
45
+ let val = bundle[key] || key
46
+ if (val && isXml) val = escapeXmlAttr(val)
47
+ else if (val && isJson) val = escapeJson(val)
48
+ return `"${left}${val}${right}"`
49
+ })
45
50
  }
46
51
 
47
52
 
@@ -206,4 +211,27 @@ function loadFromCSV (res, lang=DefaultLanguage) {
206
211
  }
207
212
  }
208
213
 
214
+ // TODO use compiler API for XML escaping
215
+ function escapeXmlAttr (str) {
216
+ // first regex: replace & if not followed by apos; or quot; or gt; or lt; or amp; or #
217
+ // Do not always escape > as it is a marker for {i18n>...} translated string values
218
+ let result = str;
219
+ if (typeof str === 'string') {
220
+ result = str.replace(/&(?!(?:apos|quot|[gl]t|amp);|#)/g, '&amp;')
221
+ .replace(/</g, '&lt;')
222
+ .replace(/"/g, '&quot;')
223
+ .replace(/\r\n|\n/g, '&#xa;');
224
+ if (!result.startsWith('{i18n>') && !result.startsWith('{bi18n'))
225
+ result = result.replace(/>/g, '&gt;')
226
+ }
227
+ return result;
228
+ }
229
+
230
+ function escapeJson (str) { return str
231
+ .replace(/"/g, '\\"')
232
+ .replace(/\\t/g, '\\t')
233
+ .replace(/\\n/g, '\\n')
234
+ }
235
+
236
+
209
237
  /* eslint no-console:off */
package/lib/index.js CHANGED
@@ -77,7 +77,7 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
77
77
  MessagingService: lazy => require('../libx/_runtime/messaging/service.js'),
78
78
  DatabaseService: lazy => require('../libx/_runtime/db/Service.js'),
79
79
  RemoteService: lazy => require('../libx/_runtime/rest/service.js'),
80
- odata: require('../libx/_runtime/odata'),
80
+ odata: require('../libx/odata'),
81
81
 
82
82
  // Helpers
83
83
  localize: require ('./i18n/localize'),
@@ -131,10 +131,10 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
131
131
 
132
132
  // Check Node.js version
133
133
  if (process.env.CDS_STRICT_NODE_VERSION !== 'false' && !process.env['WORKSPACE_ID']) { // FIXME remove as soon as BAS is ready for the Node version check below
134
- const v = version => { let vv = version.split('.'); return { version, major: +vv[0], minor: +vv[0] }}
134
+ const v = version => { let vv = version.split('.'); return { version, major: +vv[0], minor: +vv[1] }}
135
135
  const required = v('12.18'), given = v(process.version.match(/^v(\d+\.\d+)/)[1])
136
136
  if (given.major < required.major || given.major === required.major && given.minor < required.minor) process.exit (process.stderr.write (`
137
- Node.js v${required.version} or higher is required for @sap/cds ${cds.version}.
137
+ Node.js v${required.version} or higher is required for @sap/cds.
138
138
  Current v${given.version} does not satisfy this.
139
139
  \n`) || 1)
140
140
  }
@@ -7,6 +7,9 @@ const _l2l = { 1: 'error', 2: 'warn', 3: 'info', 4: 'debug', 5: 'trace' }
7
7
  * log formatter for kibana
8
8
  */
9
9
  module.exports = (module, level, ...args) => {
10
+ // config
11
+ const { user: log_user , kibana_custom_fields } = cds.env.log
12
+
10
13
  // build the object to log
11
14
  const toLog = {
12
15
  level: _l2l[level] || 'info',
@@ -16,9 +19,11 @@ module.exports = (module, level, ...args) => {
16
19
 
17
20
  // add correlation
18
21
  if (cds.context) {
19
- const { id, tenant } = cds.context
22
+ const { id, tenant, user } = cds.context
20
23
  toLog.correlation_id = id
21
24
  if (tenant) toLog.tenant_id = tenant
25
+ // log user id, if configured (data privacy)
26
+ if (user && log_user) toLog.remote_user = user.id
22
27
  // add headers, if available, with _ instead of -
23
28
  const req = cds.context._ && cds.context._.req
24
29
  if (req && req.headers) for (const k in req.headers) toLog[k.replace(/-/g, '_')] = req.headers[k]
@@ -43,7 +48,6 @@ module.exports = (module, level, ...args) => {
43
48
  }
44
49
 
45
50
  // kibana custom fields
46
- const { kibana_custom_fields } = cds.env.log
47
51
  if (kibana_custom_fields) {
48
52
  const cf = []
49
53
  for (const k in kibana_custom_fields) if (toLog[k]) cf.push({ k, v: toLog[k], i: kibana_custom_fields[k] })
package/lib/ql/Query.js CHANGED
@@ -77,6 +77,7 @@ module.exports = class Query {
77
77
 
78
78
  }
79
79
 
80
+
80
81
  const _target4 = (target, arg2) => target && (
81
82
  typeof target === 'string' ? { name: target } :
82
83
  target.name ? target : //> assumed to be a linked csn definition
package/lib/ql/SELECT.js CHANGED
@@ -61,9 +61,13 @@ module.exports = class SELECT extends Whereable {
61
61
  from (target, second, third) {
62
62
  this.SELECT.from = target === '*' || this._target_ref4 (...arguments)
63
63
  if (!target.raw && second) {
64
- const cols = _columns_or_not (second)
65
- cols ? this._add('columns',cols) : this.byKey(second)
66
- if (third) this.columns(third)
64
+ if (third) {
65
+ this.byKey(second)
66
+ this.columns(third)
67
+ } else {
68
+ const cols = _columns_or_not (second)
69
+ cols ? this._add('columns',cols) : this.byKey(second)
70
+ }
67
71
  }
68
72
  return this
69
73
  }
@@ -124,15 +128,18 @@ module.exports = class SELECT extends Whereable {
124
128
 
125
129
 
126
130
  const _columns = (args) => {
127
- if (args[0].raw) {
128
- if (args[0][0] === '{') return SELECT_('from X ',args).columns
129
- else return SELECT_('from X {',args,'}').columns
130
- } else return _columns_or_not(args[0]) || args.map(_column_expr)
131
+ const x = args[0]
132
+ if (x.raw) {
133
+ if (x[0] === '{') return SELECT_('from X ',args).columns
134
+ else return SELECT_('from X {',args,'}').columns
135
+ } else {
136
+ if (typeof x === 'string' && x[0] === '{') return parse.cql('SELECT from X '+ x).SELECT.columns
137
+ else return _columns_or_not(x) || args.map(_column_expr)
138
+ }
131
139
  }
132
140
 
133
141
  const _columns_or_not = (x) => {
134
142
  if (typeof x === 'function') return _projection4(x)
135
- if (typeof x === 'string' && x[0] === '{') return parse.cql('SELECT from X '+ x).SELECT.columns
136
143
  if (Array.isArray(x)) return x.map(_column_expr)
137
144
  }
138
145
 
@@ -1,4 +1,5 @@
1
1
  const { error } = require ('../index')
2
+ const cds = require('../index')
2
3
  const parse = require('./parse')
3
4
 
4
5
  class Whereable extends require('./Query') {
@@ -35,6 +36,10 @@ class Whereable extends require('./Query') {
35
36
  byKey(key) {
36
37
  if (typeof key !== 'object') key = { [Object.keys(this._target.keys||{ID:1})[0]]: key }
37
38
  if (this.SELECT) this.SELECT.one = true
39
+ if (cds.env.features.keys_into_where) return this.where(key)
40
+ if (this.UPDATE) { this.UPDATE.entity = { ref: [{ id: this.UPDATE.entity, where: predicate4([key]) }] }; return this }
41
+ if (this.SELECT) { this.SELECT.from.ref[this.SELECT.from.ref.length-1] = { id: this.SELECT.from.ref[this.SELECT.from.ref.length-1], where: predicate4([key]) }; return this }
42
+ if (this.DELETE) { this.DELETE.from = { ref: [{ id: this.DELETE.from, where: predicate4([key]) }] }; return this }
38
43
  return this.where(key)
39
44
  }
40
45
  }
@@ -199,22 +199,30 @@ class EventContext {
199
199
 
200
200
 
201
201
  function cds_spawn (o,fn) {
202
- if (typeof o === 'function' && typeof fn === 'object') [ fn, o ] = [ o, fn ]
202
+ if (typeof o === 'function') [ fn, o ] = [ o, fn ]
203
+ if (!o) o = {}
204
+ const em = new EventEmitter()
203
205
  const fx = async ()=> {
204
206
  // create a new transaction for each run of the background job
205
207
  // which inherits from the current event context by default
206
208
  const tx = cds.context = cds.tx({...o})
207
209
  try {
208
- await fn(tx)
210
+ const res = await fn(tx)
209
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()
210
214
  } catch(e) {
211
215
  console.trace (`ERROR occured in background job:`, e) // eslint-disable-line no-console
212
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()
213
219
  }
214
220
  }
215
- if (o && o.after) return setTimeout (fx, o.after)
216
- if (o && o.every) return setInterval (fx, o.every)
217
- else return setImmediate (fx)
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 })
218
226
  }
219
227
 
220
228
 
@@ -33,10 +33,17 @@ exports.dispatch = async function dispatch (req) { //NOSONAR
33
33
  // Ensure target and fqns
34
34
  if (!req.target) _ensure_target (this,req)
35
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 })
36
+ if (req.query._target !== req.target) Object.defineProperty (req.query,'_target',{ value:req.target, configurable:true, writable:true })
37
37
  if (!req.query._srv) Object.defineProperty (req.query,'_srv',{ value:this, configurable:true, writable:true })
38
38
  }
39
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
+ */
46
+
40
47
  return this.handle(req)
41
48
  }
42
49
 
@@ -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
  }
@@ -134,4 +134,4 @@ function initLogging() {
134
134
  /** @type Test.run & Test */
135
135
  module.exports = Object.setPrototypeOf (Test.run, Test.prototype)
136
136
 
137
- /* eslint no-console: off */
137
+ /* eslint no-console: off */