@sap/cds 5.5.2 → 5.6.0

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 (228) hide show
  1. package/CHANGELOG.md +150 -17
  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 +31 -4
  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/lib/utils/tests.js +5 -3
  40. package/libx/_runtime/audit/Service.js +18 -18
  41. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  42. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  43. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  44. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +6 -0
  45. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  46. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  49. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -1
  50. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  51. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  52. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  53. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  54. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  55. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  56. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  57. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  58. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  59. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  60. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  64. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  67. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  68. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +9 -2
  69. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +14 -19
  70. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  71. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  72. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  73. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  74. package/libx/_runtime/cds-services/util/assert.js +29 -13
  75. package/libx/_runtime/cds.js +2 -1
  76. package/libx/_runtime/common/aspects/Association.js +72 -0
  77. package/libx/_runtime/common/aspects/any.js +8 -45
  78. package/libx/_runtime/common/aspects/entity.js +0 -1
  79. package/libx/_runtime/common/aspects/relation.js +40 -0
  80. package/libx/_runtime/common/aspects/utils.js +73 -1
  81. package/libx/_runtime/common/auth/strategies/utils/uaa.js +1 -12
  82. package/libx/_runtime/common/composition/data.js +3 -2
  83. package/libx/_runtime/common/composition/delete.js +3 -1
  84. package/libx/_runtime/common/composition/tree.js +23 -18
  85. package/libx/_runtime/common/composition/utils.js +34 -8
  86. package/libx/_runtime/common/error/frontend.js +6 -1
  87. package/libx/_runtime/common/generic/auth.js +15 -13
  88. package/libx/_runtime/common/generic/crud.js +2 -2
  89. package/libx/_runtime/common/generic/etag.js +11 -8
  90. package/libx/_runtime/common/generic/input.js +3 -3
  91. package/libx/_runtime/common/generic/paging.js +9 -5
  92. package/libx/_runtime/common/generic/put.js +3 -2
  93. package/libx/_runtime/common/generic/sorting.js +3 -3
  94. package/libx/_runtime/common/generic/temporal.js +3 -3
  95. package/libx/_runtime/common/toggles/alpha.js +1 -1
  96. package/libx/_runtime/common/utils/cqn.js +20 -1
  97. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  98. package/libx/_runtime/common/utils/csn.js +50 -52
  99. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  100. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  101. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  102. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  103. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  104. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  105. package/libx/_runtime/common/utils/resolveView.js +20 -10
  106. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  107. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  108. package/libx/_runtime/common/utils/template.js +54 -46
  109. package/libx/_runtime/db/Service.js +9 -2
  110. package/libx/_runtime/db/expand/expandCQNToJoin.js +11 -25
  111. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  112. package/libx/_runtime/db/generic/create.js +1 -0
  113. package/libx/_runtime/db/generic/input.js +7 -11
  114. package/libx/_runtime/db/generic/integrity.js +2 -2
  115. package/libx/_runtime/db/generic/rewrite.js +2 -5
  116. package/libx/_runtime/db/generic/update.js +1 -0
  117. package/libx/_runtime/db/query/read.js +10 -5
  118. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
  119. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  120. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  121. package/libx/_runtime/db/utils/columns.js +14 -43
  122. package/libx/_runtime/db/utils/deep.js +5 -7
  123. package/libx/_runtime/fiori/generic/activate.js +3 -2
  124. package/libx/_runtime/fiori/generic/before.js +2 -2
  125. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  126. package/libx/_runtime/fiori/generic/delete.js +3 -2
  127. package/libx/_runtime/fiori/generic/edit.js +2 -2
  128. package/libx/_runtime/fiori/generic/new.js +2 -2
  129. package/libx/_runtime/fiori/generic/patch.js +2 -2
  130. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  131. package/libx/_runtime/fiori/generic/read.js +17 -63
  132. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  133. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  134. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  135. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  136. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  137. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  138. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  139. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  140. package/libx/_runtime/fiori/utils/handler.js +3 -13
  141. package/libx/_runtime/fiori/utils/where.js +6 -1
  142. package/libx/_runtime/hana/Service.js +5 -2
  143. package/libx/_runtime/hana/execute.js +1 -1
  144. package/libx/_runtime/hana/pool.js +12 -11
  145. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  146. package/libx/_runtime/hana/searchToContains.js +3 -3
  147. package/libx/_runtime/index.js +5 -2
  148. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  149. package/libx/_runtime/messaging/common-utils/AMQPClient.js +9 -1
  150. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  151. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  152. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  153. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  154. package/libx/_runtime/messaging/message-queuing.js +18 -0
  155. package/libx/_runtime/remote/Service.js +14 -2
  156. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  157. package/libx/_runtime/remote/utils/client.js +117 -23
  158. package/libx/_runtime/sqlite/Service.js +4 -3
  159. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  160. package/libx/_runtime/sqlite/execute.js +1 -1
  161. package/libx/gql/GraphQLAdapter.js +33 -0
  162. package/libx/gql/constants/adapter.js +69 -0
  163. package/libx/gql/constants/cds.js +18 -0
  164. package/libx/gql/constants/graphql.js +33 -0
  165. package/libx/gql/resolvers/crud/create.js +15 -0
  166. package/libx/gql/resolvers/crud/delete.js +24 -0
  167. package/libx/gql/resolvers/crud/index.js +6 -0
  168. package/libx/gql/resolvers/crud/read.js +25 -0
  169. package/libx/gql/resolvers/crud/update.js +31 -0
  170. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  171. package/libx/gql/resolvers/field.js +5 -0
  172. package/libx/gql/resolvers/index.js +7 -0
  173. package/libx/gql/resolvers/mutation.js +23 -0
  174. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  175. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  176. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  177. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  178. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  179. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  180. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  181. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  182. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  183. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  184. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  185. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  186. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  187. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  188. package/libx/gql/resolvers/query.js +13 -0
  189. package/libx/gql/resolvers/root.js +34 -0
  190. package/libx/gql/schema/generate.js +18 -0
  191. package/libx/gql/schema/index.js +5 -0
  192. package/libx/gql/schema/mutation.js +76 -0
  193. package/libx/gql/schema/query.js +108 -0
  194. package/libx/gql/schema/typeDefMap.js +45 -0
  195. package/libx/gql/schema/utils/index.js +54 -0
  196. package/libx/gql/utils/index.js +12 -0
  197. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  198. package/libx/odata/index.js +80 -0
  199. package/libx/odata/odata2cqn/afterburner.js +170 -0
  200. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  201. package/libx/odata/odata2cqn/index.js +3 -0
  202. package/libx/odata/odata2cqn/parser.js +1 -0
  203. package/libx/odata/utils/index.js +64 -0
  204. package/libx/rest/RestAdapter.js +101 -0
  205. package/libx/rest/RestRequest.js +30 -0
  206. package/libx/rest/index.js +3 -0
  207. package/libx/rest/middleware/auth.js +22 -0
  208. package/libx/rest/middleware/content.js +15 -0
  209. package/libx/rest/middleware/create.js +40 -0
  210. package/libx/rest/middleware/delete.js +20 -0
  211. package/libx/rest/middleware/error.js +56 -0
  212. package/libx/rest/middleware/operation.js +39 -0
  213. package/libx/rest/middleware/parse.js +90 -0
  214. package/libx/rest/middleware/read.js +29 -0
  215. package/libx/rest/middleware/update.js +42 -0
  216. package/libx/rest/utils/data.js +65 -0
  217. package/package.json +4 -1
  218. package/server.js +42 -29
  219. package/lib/req/cls.js +0 -39
  220. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  221. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  222. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  223. package/libx/_runtime/common/utils/backlinks.js +0 -83
  224. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  225. package/libx/_runtime/odata/index.js +0 -55
  226. package/libx/_runtime/odata/odata2cqn.js +0 -1
  227. package/libx/_runtime/odata/readToCqn.js +0 -129
  228. 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,44 @@ 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
244
+ const { requires } = this, protos = requires && requires._prototypes || {}
170
245
  for (let each in requires) requires[each] = _merged (each)
171
246
  function _merged (key) {
172
- const entry = requires[key]
173
- if (entry._is_merged || entry.kind === key || !(entry.kind in requires)) return entry
247
+ const entry = requires[key] || protos[key]
248
+ if (!entry || entry._is_merged || entry.kind === key || !(entry.kind in requires) && !(entry.kind in protos)) return entry
174
249
  const clone = _merge ({}, _merged (entry.kind)) // first apply inherited data
175
- _merge (clone, entry, false, false, o => _merge({},o)) // then apply overridden data
250
+ _merge (clone, entry, false, false) // then apply overridden data
176
251
  return Object.defineProperty (clone, '_is_merged', {value:true})
177
252
  }
178
253
  }
@@ -190,6 +265,14 @@ class Config {
190
265
  }
191
266
  }
192
267
 
268
+ /**
269
+ * Build VCAP_SERVICES for compatibility (for example for CloudSDK) or for running
270
+ * locally with credentials (hybrid mode).
271
+ */
272
+ _emulate_vcap_services() {
273
+ if (!(this.features && this.features.emulate_vcap_services)) return
274
+ process.env.VCAP_SERVICES = JSON.stringify(build_vcap_services(this))
275
+ }
193
276
 
194
277
  //////////////////////////////////////////////////////////////////////////
195
278
  //
@@ -226,7 +309,7 @@ class Config {
226
309
  /**
227
310
  * @returns {Config} dst
228
311
  */
229
- function _merge (dst, src, _profiles, _cloned) {
312
+ function _merge (dst, src, _profiles, _cloned, _profiles_only = false) {
230
313
  const profiled = [], descr = Object.getOwnPropertyDescriptors(src)
231
314
  for (let p in descr) {
232
315
  const pd = descr[p]
@@ -239,39 +322,65 @@ function _merge (dst, src, _profiles, _cloned) {
239
322
  if (_profiles && p[0] === '[') {
240
323
  if (_profiles._defined) _profiles._defined.add (p.slice(1,-1))
241
324
  if (_profiles.has(p.slice(1,-1)))
242
- profiled.push (()=> _merge (dst, src[p], _profiles, _cloned))
325
+ profiled.push (()=> _merge (dst, src[p], _profiles, _cloned, false))
243
326
  continue
244
327
  }
245
328
 
246
329
  const v = pd.value
247
330
  if (typeof v === 'object' && !Array.isArray(v)) {
248
331
  if (!dst[p]) dst[p] = {}; else if (_cloned) dst[p] = _cloned(dst[p])
249
- _merge (dst[p], v, _profiles, _cloned)
332
+ _merge (dst[p], v, _profiles, _cloned, _profiles_only)
250
333
  continue
251
334
  }
252
335
 
253
- if (v !== undefined) dst[p] = v
336
+ if (!_profiles_only && v !== undefined) dst[p] = v
254
337
  }
255
338
  for (let each of profiled) each()
256
339
  return dst
257
340
  }
258
341
 
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] = {})
342
+ function _readFromDir (p, isDir) {
343
+ if (typeof isDir === "undefined") {
344
+ try {
345
+ const entry = fs.statSync(p)
346
+ if (entry.isDirectory()) {
347
+ isDir = true
348
+ } else if (isFile(p, entry)) {
349
+ isDir = false
350
+ } else {
351
+ return undefined
352
+ }
353
+ } catch (e) {
354
+ return undefined
355
+ }
356
+ }
357
+ if (isDir) {
358
+ const result = {}
359
+ const entries = fs.readdirSync(p, {withFileTypes: true})
360
+ for (let entry of entries) {
361
+ const entryPath = path.join(p, entry.name)
362
+ if (entry.isDirectory()) {
363
+ result[entry.name] = _readFromDir(entryPath, true)
364
+ } else if (isFile(entryPath, entry)) {
365
+ result[entry.name] = _readFromDir(entryPath, false)
366
+ }
271
367
  }
272
- o[next] = _value4(env[p])
368
+ return result
369
+ } else {
370
+ return _value4(fs.readFileSync(p, "utf-8"))
371
+ }
372
+ }
373
+
374
+ function isFile(p, entry) {
375
+ if (entry.isFile()) return true
376
+ if (entry.isSymbolicLink()) {
377
+ // Kubernetes credentials use symlinks
378
+ const target = fs.realpathSync(p)
379
+ const targetStat = fs.statSync(target)
380
+
381
+ if (targetStat.isFile()) return true
273
382
  }
274
- return out
383
+ return false
275
384
  }
276
385
 
277
386
  function _value4 (val) {
@@ -288,6 +397,7 @@ function _add_vcap_services_to (env, vcaps={}) {
288
397
  let any
289
398
  for (let service in env.requires) {
290
399
  const conf = env.requires [service]
400
+ if (!conf) continue
291
401
  const { credentials } = (
292
402
  conf.vcap && _fetch (conf.vcap) || //> alternatives, e.g. { name:'foo', tag:'foo' }
293
403
  _fetch ({ name: service }) ||
@@ -297,7 +407,9 @@ function _add_vcap_services_to (env, vcaps={}) {
297
407
  {/* not found */}
298
408
  )
299
409
  // Merge `credentials`. Needed because some app-defined things like `credentials.destination` must survive.
300
- if (credentials) any = conf.credentials = Object.assign ({}, conf.credentials, credentials)
410
+ if (credentials) {
411
+ any = conf.credentials = Object.assign ({}, conf.credentials, credentials)
412
+ }
301
413
  }
302
414
  return any
303
415
 
@@ -347,6 +459,28 @@ function set (o,p,value) {
347
459
  return value
348
460
  }
349
461
 
462
+ function build_vcap_services(env) {
463
+ let v = {}
464
+ let names = new Set()
465
+
466
+ for (const service in env.requires) {
467
+ let { vcap, credentials, binding } = env.requires[service]
468
+ // "binding.vcap" is chosen over "vcap" because it is meta data resolved from the real service (-> cds bind)
469
+ if (binding && binding.vcap) vcap = binding.vcap
470
+ if (vcap && vcap.label && credentials && Object.keys(credentials).length > 0) {
471
+ // Only one entry for a (instance) name. Generate name from label and plan if not given.
472
+ const { label, plan } = vcap
473
+ const name = vcap.name || `instance:${label}:${plan || ""}`
474
+ if (names.has(name)) continue
475
+ names.add(name)
476
+
477
+ if (!v[label]) v[label] = []
478
+ v[label].push(Object.assign({ name }, vcap, { credentials }))
479
+ }
480
+ }
481
+
482
+ return v
483
+ }
350
484
 
351
485
  /** @type Config & typeof DEFAULTS */
352
486
  module.exports = Config.prototype.for('cds')
@@ -9,11 +9,14 @@ module.exports = {
9
9
  },
10
10
  "auth": {
11
11
  '[development]': { kind: 'mocked-auth' },
12
- '[production]': { kind: 'JWT-auth' }
12
+ '[production]': { kind: 'jwt-auth' }
13
13
  },
14
14
  "dummy-auth": {
15
15
  strategy: 'dummy',
16
16
  },
17
+ "basic-auth": {
18
+ kind: "mocked-auth"
19
+ },
17
20
  "mocked-auth": {
18
21
  strategy: 'mock',
19
22
  users: {
@@ -22,10 +25,22 @@ module.exports = {
22
25
  '*': true
23
26
  }
24
27
  },
25
- "JWT-auth": {
28
+ "jwt-auth": {
26
29
  strategy: 'JWT',
27
30
  },
28
- destinations: undefined,
31
+ "xsuaa-auth": {
32
+ strategy: 'xsuaa',
33
+ },
34
+ destinations: {
35
+ vcap: {
36
+ label: 'destination'
37
+ }
38
+ },
39
+ xsuaa: {
40
+ vcap: {
41
+ label: 'xsuaa'
42
+ }
43
+ },
29
44
  monitoring: undefined,
30
45
  logging: undefined,
31
46
  audit: undefined,
@@ -99,6 +114,12 @@ module.exports = {
99
114
  // model: 'AuditLogService.cds',
100
115
  outbox: true,
101
116
  vcap: { label: "auditlog" },
117
+ },
118
+
119
+ _prototypes: {
120
+ "uiflex": {
121
+ "model": "@sap/cds/libx/_runtime/fiori/uiflex/extensibility"
122
+ },
102
123
  }
103
124
  }
104
125
 
@@ -38,10 +38,14 @@ function localizeString (aString, bundle) {
38
38
  if (!bundle) return aString
39
39
  // quick check for presence of any text key, to avoid expensive operation below
40
40
  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
- )
41
+ const isXml = aString.startsWith('<?xml')
42
+ const isJson = /^[{[]/.test(aString)
43
+ return aString.replace (TEXT_KEYS, (_, left='', key, right='') => {
44
+ let val = bundle[key] || key
45
+ if (val && isXml) val = escapeXmlAttr(val)
46
+ else if (val && isJson) val = escapeJson(val)
47
+ return `"${left}${val}${right}"`
48
+ })
45
49
  }
46
50
 
47
51
 
@@ -206,4 +210,27 @@ function loadFromCSV (res, lang=DefaultLanguage) {
206
210
  }
207
211
  }
208
212
 
213
+ // TODO use compiler API for XML escaping
214
+ function escapeXmlAttr (str) {
215
+ // first regex: replace & if not followed by apos; or quot; or gt; or lt; or amp; or #
216
+ // Do not always escape > as it is a marker for {i18n>...} translated string values
217
+ let result = str;
218
+ if (typeof str === 'string') {
219
+ result = str.replace(/&(?!(?:apos|quot|[gl]t|amp);|#)/g, '&amp;')
220
+ .replace(/</g, '&lt;')
221
+ .replace(/"/g, '&quot;')
222
+ .replace(/\r\n|\n/g, '&#xa;');
223
+ if (!result.startsWith('{i18n>') && !result.startsWith('{bi18n'))
224
+ result = result.replace(/>/g, '&gt;')
225
+ }
226
+ return result;
227
+ }
228
+
229
+ function escapeJson (str) { return str
230
+ .replace(/"/g, '\\"')
231
+ .replace(/\\t/g, '\\t')
232
+ .replace(/\\n/g, '\\n')
233
+ }
234
+
235
+
209
236
  /* eslint no-console:off */
package/lib/index.js CHANGED
@@ -15,6 +15,7 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
15
15
  })}
16
16
  get context() { return require('./req/context').for(this) }
17
17
  set context(_){ require('./req/context').for(this,_) }
18
+ get spawn() { return super.spawn = require('./req/context').spawn }
18
19
 
19
20
  emit (eve, ...args) {
20
21
  if (eve === 'served') return Promise.all (this.listeners(eve).map (l => l.call(this,...args)))
@@ -63,11 +64,12 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
63
64
 
64
65
  // Core Services API
65
66
  Service: require ('./serve/Service-api'),
66
- Request: require ('./req/impl'),
67
+ EventContext: require ('./req/context'),
68
+ Request: require ('./req/request'),
67
69
  Event: require ('./req/event'),
68
70
  User: require ('./req/user'),
69
71
  ql: require ('./ql'),
70
- spawn: lazy => cds.Event.spawn,
72
+ tx: (..._) => (cds.db || cds.Service.prototype) .tx (..._),
71
73
  /** @type Service */ db: undefined,
72
74
 
73
75
  // Protocols and Periphery
@@ -75,7 +77,7 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
75
77
  MessagingService: lazy => require('../libx/_runtime/messaging/service.js'),
76
78
  DatabaseService: lazy => require('../libx/_runtime/db/Service.js'),
77
79
  RemoteService: lazy => require('../libx/_runtime/rest/service.js'),
78
- odata: require('../libx/_runtime/odata'),
80
+ odata: require('../libx/odata'),
79
81
 
80
82
  // Helpers
81
83
  localize: require ('./i18n/localize'),
@@ -98,7 +100,6 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
98
100
  extend (cds.__proto__) .with ({
99
101
  get entities(){ return (cds.db||_missing).entities },
100
102
  transaction: (..._) => (cds.db||_missing).transaction(..._),
101
- tx: (..._) => (cds.db||_missing).tx(..._),
102
103
  run: (..._) => (cds.db||_missing).run(..._),
103
104
  foreach: (..._) => (cds.db||_missing).foreach(..._),
104
105
  stream: (..._) => (cds.db||_missing).stream(..._),
@@ -130,10 +131,10 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
130
131
 
131
132
  // Check Node.js version
132
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
133
- 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] }}
134
135
  const required = v('12.18'), given = v(process.version.match(/^v(\d+\.\d+)/)[1])
135
136
  if (given.major < required.major || given.major === required.major && given.minor < required.minor) process.exit (process.stderr.write (`
136
- 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.
137
138
  Current v${given.version} does not satisfy this.
138
139
  \n`) || 1)
139
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/DELETE.js CHANGED
@@ -9,7 +9,7 @@ module.exports = class DELETE extends Whereable {
9
9
  }
10
10
 
11
11
  from(entity, key) {
12
- this.DELETE.from = this._target_name4 (entity)
12
+ this.DELETE.from = this._target_name4 (...arguments) // supporting tts
13
13
  if (key) this.byKey(key)
14
14
  return this
15
15
  }
package/lib/ql/INSERT.js CHANGED
@@ -9,7 +9,7 @@ module.exports = class INSERT extends Query {
9
9
  }
10
10
 
11
11
  into (entity, ...data) {
12
- this.INSERT.into = this._target_name4 (entity)
12
+ this.INSERT.into = this._target_name4 (...arguments) // supporting tts
13
13
  if (data.length) this.entries(...data)
14
14
  return this
15
15
  }
package/lib/ql/Query.js CHANGED
@@ -12,7 +12,7 @@ module.exports = class Query {
12
12
 
13
13
  /** Binds this query to be executed with the given service */
14
14
  bind (srv) {
15
- return Object.defineProperty (this,'_srv',{value:srv})
15
+ return Object.defineProperty (this,'_srv',{ value:srv, configurable:true, writable:true })
16
16
  }
17
17
 
18
18
  /** Turns all queries into Thenables which execute with primary db by default */
@@ -34,15 +34,10 @@ module.exports = class Query {
34
34
  '}'
35
35
  }
36
36
 
37
- _target_ref4 (target) {
37
+ _target_ref4 (target, arg2) {
38
38
 
39
39
  // Resolving this._target --> REVISIT: this is not reliable !!!
40
- Object.defineProperty (this, '_target', { value: target && (
41
- typeof target === 'string' ? { name: target } :
42
- target.name ? target : //> assumed to be a linked csn definition
43
- target.ref ? { name: target.ref[0] } :
44
- target._target || { name: undefined }
45
- ), configurable:true })
40
+ Object.defineProperty (this, '_target', { value: _target4 (target,arg2), configurable:true, writable:true })
46
41
 
47
42
  return target && (
48
43
  typeof target === 'string' ? cds.parse.path(target) :
@@ -56,8 +51,8 @@ module.exports = class Query {
56
51
  }
57
52
 
58
53
  //> REVISIT: should we rather have consistent .from/.entity/.into in CQN?
59
- _target_name4 (target) {
60
- const {ref} = this._target_ref4 (target)
54
+ _target_name4 (...args) {
55
+ const {ref} = this._target_ref4 (...args)
61
56
  return ref.length === 1 && typeof ref[0] === 'string' ? ref[0] : {ref}
62
57
  }
63
58
 
@@ -83,4 +78,12 @@ module.exports = class Query {
83
78
  }
84
79
 
85
80
 
81
+ const _target4 = (target, arg2) => target && (
82
+ typeof target === 'string' ? { name: target } :
83
+ target.name ? target : //> assumed to be a linked csn definition
84
+ target.ref ? { name: target.ref[0] } :
85
+ target.raw ? _target4(arg2) :
86
+ target._target || { name: undefined }
87
+ )
88
+
86
89
  const _name = cds.env.sql.names === 'quoted' ? n =>`"${n}"` : n => n.replace(/[.:]/g,'_')
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
 
package/lib/ql/UPDATE.js CHANGED
@@ -10,7 +10,7 @@ module.exports = class UPDATE extends Whereable {
10
10
  }
11
11
 
12
12
  entity (e, key) {
13
- this.UPDATE.entity = this._target_name4 (e)
13
+ this.UPDATE.entity = this._target_name4 (...arguments) // supporting tts
14
14
  if (key) this.byKey(key)
15
15
  return this
16
16
  }
@@ -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
  }