@sap/cds 6.2.3 → 6.3.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 (74) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/apis/connect.d.ts +1 -1
  3. package/apis/cqn.d.ts +1 -1
  4. package/apis/internal/inference.d.ts +14 -0
  5. package/apis/ql.d.ts +40 -36
  6. package/apis/services.d.ts +23 -6
  7. package/bin/build/buildTaskEngine.js +15 -12
  8. package/bin/build/buildTaskHandler.js +3 -3
  9. package/bin/build/constants.js +2 -0
  10. package/bin/build/provider/buildTaskHandlerEdmx.js +1 -1
  11. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +4 -3
  12. package/bin/build/provider/buildTaskHandlerInternal.js +2 -2
  13. package/bin/build/provider/java/index.js +2 -1
  14. package/bin/build/provider/mtx/index.js +2 -1
  15. package/bin/build/provider/mtx/resourcesTarBuilder.js +3 -2
  16. package/bin/build/provider/mtx-extension/index.js +2 -1
  17. package/bin/build/provider/mtx-sidecar/index.js +3 -1
  18. package/bin/build/util.js +2 -2
  19. package/bin/deploy/to-hana/cfUtil.js +46 -62
  20. package/lib/auth/index.js +2 -1
  21. package/lib/auth/jwt-auth.js +64 -3
  22. package/lib/auth/xsuaa-auth.js +2 -3
  23. package/lib/compile/cdsc.js +1 -0
  24. package/lib/compile/etc/_localized.js +1 -0
  25. package/lib/dbs/cds-deploy.js +2 -1
  26. package/lib/env/cds-env.js +14 -49
  27. package/lib/env/cds-requires.js +13 -7
  28. package/lib/env/defaults.js +4 -0
  29. package/lib/i18n/localize.js +11 -8
  30. package/lib/index.js +1 -1
  31. package/lib/log/cds-log.js +2 -2
  32. package/lib/log/format/cf.js +16 -0
  33. package/lib/log/format/kibana.js +15 -2
  34. package/lib/ql/INSERT.js +12 -11
  35. package/lib/ql/Query.js +14 -7
  36. package/lib/ql/UPSERT.js +1 -0
  37. package/lib/ql/Whereable.js +6 -2
  38. package/lib/ql/cds-ql.js +2 -4
  39. package/lib/req/request.js +2 -0
  40. package/lib/srv/bindings.js +1 -0
  41. package/lib/srv/middlewares/cds-context.js +1 -1
  42. package/lib/srv/srv-dispatch.js +1 -0
  43. package/lib/srv/srv-tx.js +3 -3
  44. package/lib/utils/cds-utils.js +75 -30
  45. package/lib/utils/inflect.js +24 -0
  46. package/libx/_runtime/auth/strategies/ias-auth.js +1 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +9 -1
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +23 -6
  49. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -0
  50. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +27 -15
  51. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -1
  52. package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -10
  53. package/libx/_runtime/cds-services/services/utils/differ.js +6 -4
  54. package/libx/_runtime/common/composition/data.js +29 -40
  55. package/libx/_runtime/common/composition/update.js +6 -19
  56. package/libx/_runtime/common/generic/paging.js +1 -1
  57. package/libx/_runtime/common/utils/resolveView.js +7 -13
  58. package/libx/_runtime/db/utils/generateAliases.js +1 -0
  59. package/libx/_runtime/fiori/generic/before.js +5 -2
  60. package/libx/_runtime/fiori/generic/read.js +11 -4
  61. package/libx/_runtime/hana/execute.js +2 -2
  62. package/libx/_runtime/hana/search2Contains.js +3 -1
  63. package/libx/_runtime/hana/search2cqn4sql.js +1 -0
  64. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  65. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +5 -2
  66. package/libx/_runtime/messaging/enterprise-messaging.js +7 -1
  67. package/libx/_runtime/messaging/file-based.js +1 -1
  68. package/libx/_runtime/messaging/message-queuing.js +5 -2
  69. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  70. package/libx/_runtime/messaging/service.js +5 -3
  71. package/libx/odata/cqn2odata.js +4 -1
  72. package/libx/odata/utils.js +8 -7
  73. package/libx/rest/RestAdapter.js +1 -4
  74. package/package.json +1 -1
package/lib/ql/INSERT.js CHANGED
@@ -7,54 +7,55 @@ module.exports = class Query extends require('./Query') {
7
7
  }
8
8
 
9
9
  into (entity, ...data) {
10
- this.INSERT.into = this._target_name4 (...arguments) // supporting tts
10
+ this[this.cmd].into = this._target_name4 (...arguments) // supporting tts
11
11
  if (data.length) this.entries(...data)
12
12
  return this
13
13
  }
14
14
 
15
15
  entries (...x) {
16
- this.INSERT.entries = is_array(x[0]) ? x[0] : x
16
+ this[this.cmd].entries = is_array(x[0]) ? x[0] : x
17
17
  return this
18
18
  }
19
19
  columns (...x) {
20
- this.INSERT.columns = is_array(x[0]) ? x[0] : x
20
+ this[this.cmd].columns = is_array(x[0]) ? x[0] : x
21
21
  return this
22
22
  }
23
23
  values (...x) {
24
- this.INSERT.values = is_array(x[0]) ? x[0] : x
24
+ this[this.cmd].values = is_array(x[0]) ? x[0] : x
25
25
  return this
26
26
  }
27
27
  rows (...rows) {
28
28
  if (is_array(rows[0]) && is_array(rows[0][0])) rows = rows[0]
29
29
  if (!is_array(rows[0])) this._expected `Arguments ${{rows}} to be an array of arrays`
30
- this.INSERT.rows = rows
30
+ this[this.cmd].rows = rows
31
31
  return this
32
32
  }
33
33
  _rows(rows, ...args) {
34
34
 
35
+ const INSERT = this.cmd
35
36
  if (Array.isArray(rows)) {
36
37
  // check if all the entries in the array are arrays
37
38
  if (rows.every(e => Array.isArray(e))) {
38
- this.INSERT.rows = rows
39
+ this[INSERT].rows = rows
39
40
  // check if array contains one or multiple objects
40
41
  } else if (rows.every(e => typeof e === 'object')) {
41
- this.INSERT.entries = rows
42
+ this[INSERT].entries = rows
42
43
  // the rows have been added as arguments
43
44
  } else if (args.length !== 0) {
44
45
  args.unshift(rows)
45
- this.INSERT.rows = args
46
+ this[INSERT].rows = args
46
47
  } else {
47
- this.INSERT.values = rows
48
+ this[INSERT].values = rows
48
49
  }
49
50
  } else if (typeof rows === 'object') {
50
- this.INSERT.entries = rows
51
+ this[INSERT].entries = rows
51
52
  }
52
53
 
53
54
  return this
54
55
  }
55
56
  as (query) {
56
57
  if (!query || !query.SELECT) this._expected `${{query}} to be a CQN {SELECT} query object`
57
- this.INSERT.as = query
58
+ this[this.cmd].as = query
58
59
  return this
59
60
  }
60
61
  valueOf() {
package/lib/ql/Query.js CHANGED
@@ -5,16 +5,23 @@ class Query {
5
5
 
6
6
  constructor(_={}) { this[this.cmd] = _ }
7
7
 
8
+ alias (a) {
9
+ let _ = this[this.cmd] ;(_.from || _.into || _.entity).as = a
10
+ return this
11
+ }
12
+
8
13
  /** Creates a derived instance that initially inherits all properties. */
9
- clone(){
10
- const inherited = Object.create (this[this.cmd])
11
- return {__proto__:this, [this.cmd]: inherited }
14
+ clone (_) {
15
+ const cmd = this.cmd || Object.keys(this)[0]
16
+ return {__proto__:this, [cmd]: {__proto__:this[cmd],..._} }
12
17
  }
13
18
 
14
- flat(){
15
- const flat = this[this.cmd]
16
- for (let x=flat; x.__proto__;) Object.assign(flat, x = x.__proto__)
17
- return new this.constructor (flat)
19
+ flat (q=this) {
20
+ let x = q.cmd || Object.keys(q)[0], y = q[x]
21
+ let protos = [y]; for (let o=y; o.__proto__;) protos.push (o = o.__proto__)
22
+ q[x] = Object.assign ({}, ...protos.reverse())
23
+ if (y.columns) for (let c of y.columns) if (c.SELECT) (this||Query.prototype).flat(c)
24
+ return q
18
25
  }
19
26
 
20
27
  /** Binds this query to be executed with the given service */
@@ -0,0 +1 @@
1
+ module.exports = class Query extends require('./INSERT') {}
@@ -43,7 +43,7 @@ class Query extends require('./Query') {
43
43
  }
44
44
 
45
45
  const predicate4 = (args, _clause) => {
46
- if (args.length === 0) return; const x = args[0]
46
+ if (args.length === 0) return; /* else */ const x = args[0]
47
47
  if (x.raw) return parse.CXL(...args).xpr
48
48
  if (args.length === 1 && typeof x === 'object') {
49
49
  if (is_array(x)) return x
@@ -67,7 +67,11 @@ const _object_predicate = ([arg], _clause) => { // e.g. .where ({ID:4711, stock:
67
67
  continue
68
68
  }
69
69
  if (k === 'exists') {
70
- pred.push(null, 'exists', ...predicate4([x],_clause))
70
+ pred.push(pred.length && 'and', 'exists', typeof x === 'object' ? x : { ref: x.split('.') })
71
+ continue
72
+ }
73
+ if (k === 'not exists') {
74
+ pred.push(pred.length && 'and', 'not', 'exists', typeof x === 'object' ? x : { ref: x.split('.') })
71
75
  continue
72
76
  }
73
77
  else pred.push('and', parse.expr(k))
package/lib/ql/cds-ql.js CHANGED
@@ -11,12 +11,10 @@ require = path => { // eslint-disable-line no-global-assign
11
11
  }
12
12
 
13
13
  module.exports = Object.assign (_deprecated_srv_ql, { cdr: true,
14
- Query, clone(q) {
15
- const cmd = q.cmd || Object.keys(q)[0]
16
- return {__proto__:q, [cmd]: {__proto__:q[cmd] }}
17
- },
14
+ Query, clone: (q,_) => Query.prototype.clone.call(q,_),
18
15
  SELECT: require('./SELECT'),
19
16
  INSERT: require('./INSERT'),
17
+ UPSERT: require('./UPSERT'),
20
18
  UPDATE: require('./UPDATE'),
21
19
  DELETE: require('./DELETE'),
22
20
  CREATE: require('./CREATE'),
@@ -38,6 +38,7 @@ class Request extends require('./event') {
38
38
  const q = this.query; if (this.query) { // IMPORTANT: Bulk queries don't have a _.query
39
39
  if (q.SELECT) return this._set ('path', _path4 (q.SELECT,'from'))
40
40
  if (q.INSERT) return this._set ('path', _path4 (q.INSERT,'into'))
41
+ if (q.UPSERT) return this._set ('path', _path4 (q.UPSERT,'into'))
41
42
  if (q.UPDATE) return this._set ('path', _path4 (q.UPDATE,'entity'))
42
43
  if (q.DELETE) return this._set ('path', _path4 (q.DELETE,'from'))
43
44
  }
@@ -104,6 +105,7 @@ const Http2Crud = {
104
105
  const SQL2Crud = {
105
106
  SELECT: 'READ',
106
107
  INSERT: 'CREATE',
108
+ UPSERT: 'UPSERT',
107
109
  UPDATE: 'UPDATE',
108
110
  DELETE: 'DELETE',
109
111
  BEGIN: 'BEGIN',
@@ -69,6 +69,7 @@ module.exports = class Bindings {
69
69
  }
70
70
  }
71
71
  cds.on ('shutdown', ()=>this.purge())
72
+ process.on ('exit', ()=>this.purge()) // last resort e.g. in case of errors
72
73
  return this.store()
73
74
  }
74
75
 
@@ -4,7 +4,7 @@ module.exports = ()=> {
4
4
 
5
5
  /** @type { import('express').Handler } */
6
6
  async function cds_context_provider (req, res, next) {
7
- const ctx = new cds.EventContext
7
+ const ctx = {}
8
8
  ctx.http = { req, res }
9
9
  ctx.id = _id4(req)
10
10
  ctx.user = req.user
@@ -102,6 +102,7 @@ const _ensure_target = (srv,req) => {
102
102
  if (p) _ensure_fqn (req,'path',srv, p.startsWith('/') ? p.slice(1) : p)
103
103
  else if (q.SELECT) _ensure_fqn (q.SELECT,'from',srv)
104
104
  else if (q.INSERT) _ensure_fqn (q.INSERT,'into',srv)
105
+ else if (q.UPSERT) _ensure_fqn (q.UPSERT,'into',srv)
105
106
  else if (q.UPDATE) _ensure_fqn (q.UPDATE,'entity',srv)
106
107
  else if (q.DELETE) _ensure_fqn (q.DELETE,'from',srv)
107
108
  }
package/lib/srv/srv-tx.js CHANGED
@@ -17,7 +17,7 @@ class NestedContext extends EventContext {
17
17
  /**
18
18
  * This is the implementation of the `srv.tx(req)` method. It constructs
19
19
  * a new Transaction as a derivate of the `srv` (i.e. {__proto__:srv})
20
- * @returns { Transaction & import('./srv-api') }
20
+ * @returns { Promise<Transaction & import('./srv-api')> }
21
21
  * @param { EventContext } ctx
22
22
  */
23
23
  function srv_tx (ctx,fn) { const srv = this
@@ -92,7 +92,7 @@ class Transaction {
92
92
 
93
93
  /*
94
94
  * srv.on('error', function (err, req) { ... })
95
- * synchroneous modification of passed error only
95
+ * synchronous modification of passed error only
96
96
  * err is undefined if nested tx (cf. "root.before ('failed', ()=> this.rollback())")
97
97
  */
98
98
  if (err) for (const each of this._handlers._error) each.handler.call(this, err, this.context)
@@ -192,7 +192,7 @@ const _begin = async function (req) {
192
192
  return this.ready = this.__proto__.dispatch.call (this,req)
193
193
  // Protection against unintended tx.run() after root tx.commit/rollback()
194
194
  if (typeof this.ready === 'string' || !this.ready && this.context.tx._done) {
195
- if (!cds_tx_protection) this.ready = this.begin() // compatibiliy to former behavior, which allowed tx.run() after commit/rollback
195
+ if (!cds_tx_protection) this.ready = this.begin() // compatibility to former behavior, which allowed tx.run() after commit/rollback
196
196
  else throw cds.error (
197
197
  `Transaction is ${this.ready || this.context.tx._done}, no subsequent .run allowed, without prior .begin`,
198
198
  { code: 'TRANSACTION_CLOSED' }
@@ -2,6 +2,7 @@ const cwd = process.env._original_cwd || process.cwd()
2
2
  const cds = require('../index')
3
3
 
4
4
  const ux = module.exports = exports = new class {
5
+ get inflect() { return super.inflect = require('./inflect') }
5
6
  get inspect() { return super.inspect = require('util').inspect }
6
7
  get uuid() { return super.uuid = require('@sap/cds-foss').uuid }
7
8
  get tar() { return super.tar = require('./tar') }
@@ -10,20 +11,50 @@ const ux = module.exports = exports = new class {
10
11
  const path = exports.path = require('path'), { dirname, extname, join, resolve, relative } = path
11
12
  const fs = exports.fs = Object.assign (ux,require('fs')) //> for compatibility
12
13
 
13
- exports.decodeURIComponent = s => { try { return decodeURIComponent(s) } catch { return s } }
14
- exports.decodeURI = s => { try { return decodeURI(s) } catch { return s } }
15
-
16
- exports.local = (file) => relative(cwd,file)
17
14
 
18
- exports.readdir = async function (x) {
19
- const d = resolve (cds.root,x)
20
- return fs.promises.readdir(d)
15
+ /**
16
+ * Variant of `Object.keys()` which includes all keys inherited from the
17
+ * given object's prototypes.
18
+ */
19
+ exports.Object_keys = o => ({
20
+ [Symbol.iterator]: function*(){ for (let k in o) yield k },
21
+ forEach(f){ let i=0; for (let k in o) f(k,i++,o) },
22
+ filter(f){ let i=0, r=[]; for (let k in o) f(k,i++,o) && r.push(k); return r },
23
+ map(f){ let i=0, r=[]; for (let k in o) r.push(f(k,i++,o)); return r },
24
+ some(f){ for (let k in o) if (f(k)) return true },
25
+ find(f){ for (let k in o) if (f(k)) return k },
26
+ })
27
+
28
+
29
+ /**
30
+ * Simple helper to always access results as arrays.
31
+ */
32
+ exports.results = oa => {
33
+ return Array.isArray(oa) ? oa : oa != null ? [oa] : []
21
34
  }
22
35
 
23
- exports.stat = async function (x) {
24
- const d = resolve (cds.root,x)
25
- return fs.promises.stat(d)
36
+
37
+ /**
38
+ * Should be used in data providers, i.e., db services to return single
39
+ * rows in response to SELECT.one queries.
40
+ */
41
+ exports.chimera = oa => {
42
+ return Array.isArray(oa) ? oa : Object.defineProperties(oa,chimera)
26
43
  }
44
+ const chimera = Object.getOwnPropertyDescriptors (class Chimera {
45
+ *[Symbol.iterator] (){ yield this }
46
+ forEach(f){ f(this,0,this) }
47
+ filter(f){ return f(this,0,this) ? [this] : [] }
48
+ map(f){ return [f(this,0,this)] }
49
+ some(f){ return f(this,0,this) }
50
+ find(f){ if (f(this,0,this)) return this }
51
+ }.prototype)
52
+
53
+
54
+ exports.decodeURIComponent = s => { try { return decodeURIComponent(s) } catch { return s } }
55
+ exports.decodeURI = s => { try { return decodeURI(s) } catch { return s } }
56
+
57
+ exports.local = (file) => relative(cwd,file)
27
58
 
28
59
  exports.exists = function exists (x) {
29
60
  if (x) {
@@ -50,6 +81,16 @@ exports.isfile = function isfile (x) {
50
81
  } catch(e){/* ignore */}
51
82
  }
52
83
 
84
+ exports.stat = async function (x) {
85
+ const d = resolve (cds.root,x)
86
+ return fs.promises.stat(d)
87
+ }
88
+
89
+ exports.readdir = async function (x) {
90
+ const d = resolve (cds.root,x)
91
+ return fs.promises.readdir(d)
92
+ }
93
+
53
94
  exports.read = async function read (file, _encoding) {
54
95
  const f = resolve (cds.root,file)
55
96
  const src = await fs.promises.readFile (f, _encoding !== 'json' && _encoding || 'utf8')
@@ -64,18 +105,37 @@ exports.write = function write (file, data, o) {
64
105
  return fs.mkdirp (dirname(f)).then (()=> fs.promises.writeFile (f,data,o))
65
106
  }
66
107
 
108
+ exports.copy = function copy (x,y) {
109
+ if (arguments.length === 1) return {to:(...path) => copy(x,join(...path))}
110
+ const src = resolve (cds.root,x)
111
+ const dst = resolve (cds.root,y)
112
+ if (fs.promises.cp) return fs.promises.cp (src,dst,{recursive:true})
113
+ return fs.mkdirp (dirname(dst)) .then (async ()=>{
114
+ if (fs.isdir(src)) {
115
+ const entries = await fs.promises.readdir(src)
116
+ return Promise.all (entries.map (async each => {
117
+ const e = join (src,each)
118
+ const f = join (dst,each)
119
+ return copy (e,f)
120
+ }))
121
+ } else {
122
+ return fs.promises.copyFile (src,dst)
123
+ }
124
+ })
125
+ }
126
+
67
127
  exports.mkdirp = async function (...path) {
68
128
  const d = resolve (cds.root,...path)
69
129
  await fs.promises.mkdir (d,{recursive:true})
70
130
  return d
71
131
  }
72
132
 
73
- exports.rmdir = (...path) => {
133
+ exports.rmdir = async function (...path) {
74
134
  const d = resolve (cds.root,...path)
75
135
  return fs.promises.rm (d, {recursive:true})
76
136
  }
77
137
 
78
- exports.rimraf = (...path) => {
138
+ exports.rimraf = async function (...path) {
79
139
  const d = resolve (cds.root,...path)
80
140
  return fs.promises.rm (d, {recursive:true,force:true})
81
141
  }
@@ -85,23 +145,6 @@ exports.rm = async function rm (x) {
85
145
  return fs.promises.rm(y)
86
146
  }
87
147
 
88
- exports.copy = async function copy (x,y) {
89
- const src = resolve (cds.root,x)
90
- const dst = resolve (cds.root,y)
91
- if (fs.promises.cp) return fs.promises.cp (src,dst,{recursive:true})
92
- await fs.mkdirp (dirname(dst))
93
- if (fs.isdir(src)) {
94
- const entries = await fs.promises.readdir(src)
95
- return Promise.all (entries.map (async each => {
96
- const e = join (src,each)
97
- const f = join (dst,each)
98
- return copy (e,f)
99
- }))
100
- } else {
101
- return fs.promises.copyFile (src,dst)
102
- }
103
- }
104
-
105
148
  exports.find = function find (base, patterns='*', filter=()=>true) {
106
149
  const files=[]; base = resolve (cds.root,base)
107
150
  if (typeof patterns === 'string') patterns = patterns.split(',')
@@ -134,7 +177,9 @@ exports.find = function find (base, patterns='*', filter=()=>true) {
134
177
  }
135
178
 
136
179
 
137
- // internal utility to load a file through ESM or CommonJs. TODO find a better place.
180
+ /**
181
+ * Internal utility to load a file through ESM or CommonJs. TODO find a better place.
182
+ */
138
183
  exports._import = id => require(id)
139
184
  if (typeof jest === 'undefined') { // jest's ESM support is experimental: https://jestjs.io/docs/ecmascript-modules
140
185
  const { pathToFileURL } = require('url')
@@ -0,0 +1,24 @@
1
+ const last = /\w+$/
2
+
3
+ exports.singular4 = (dn,stripped) => {
4
+ let n = dn.name || dn; if (stripped) n = n.match(last)[0]
5
+ return dn['@singular'] || (
6
+ /.*species|news$/i.test(n) ? n :
7
+ /.*ess$/.test(n) ? n : // Address
8
+ /.*ees$/.test(n) ? n.slice(0, -1) : // Employees --> Employee
9
+ /.*[sz]es$/.test(n) ? n.slice(0, -2) :
10
+ /.*[^aeiou]ies$/.test(n) ? n.slice(0, -3) + 'y' : // Deliveries --> Delivery
11
+ /.*s$/.test(n) ? n.slice(0, -1) :
12
+ n
13
+ )
14
+ }
15
+
16
+ exports.plural4 = (dn,stripped) => {
17
+ let n = dn.name || dn; if (stripped) n = n.match(last)[0]
18
+ return dn['@plural'] || (
19
+ /.*analysis|status|species|news$/i.test(n) ? n :
20
+ /.*[^aeiou]y$/.test(n) ? n.slice(0,-1) + 'ies' :
21
+ /.*(s|x|z|ch|sh)$/.test(n) ? n + 'es' :
22
+ n + 's'
23
+ )
24
+ }
@@ -30,7 +30,7 @@ module.exports = function ias_auth(config) {
30
30
  This is NOT recommended in production!
31
31
  `)
32
32
 
33
- return
33
+ return (req, res, next) => next()
34
34
  }
35
35
 
36
36
  passport.use('IAS', new JWTStrategy(config.credentials))
@@ -133,7 +133,15 @@ const getErrorHandler = (crashOnError = true, srv) => {
133
133
 
134
134
  // add content id if not generated by okra ("~...")
135
135
  const contentId = odataReq.getOdataRequestId()
136
- if (contentId && !contentId.match(/^~/)) err['@Core.ContentID'] = contentId
136
+ if (contentId && !contentId.match(/^~/)) {
137
+ err['@Core.ContentID'] = contentId
138
+ // UI5 expects us to add the content ID to each detail message
139
+ if (err.details) {
140
+ err.details.forEach(entry => {
141
+ entry['@Core.ContentID'] = contentId
142
+ })
143
+ }
144
+ }
137
145
 
138
146
  const { error, statusCode } = normalizeError(err, req)
139
147
  // REVISIT: We should also pass stack traces in development
@@ -19,12 +19,29 @@ const metadata = service => {
19
19
  const locale = odataRes.getContract().getLocale()
20
20
 
21
21
  try {
22
- let edmx = cds.localize(
23
- service.model,
24
- locale,
25
- // REVISIT: we could cache this in model._cached
26
- cds.compile.to.edmx(service.model, { service: service.definition.name })
27
- )
22
+ const { 'cds.xt.ModelProviderService': mps } = cds.services
23
+ // REVISIT: The following block should replaced with the one commented bellow after the next release of cds-mtxs.
24
+ // Currently lkg tests fail w/o the try-catch.
25
+ let edmx
26
+ try {
27
+ if (mps) edmx = await mps.getEdmx({ tenant, model: service.model, service: service.definition.name, locale })
28
+ // eslint-disable-next-line no-empty
29
+ } catch (_) {}
30
+ if (!edmx)
31
+ edmx = cds.localize(
32
+ service.model,
33
+ locale,
34
+ cds.compile.to.edmx(service.model, { service: service.definition.name })
35
+ )
36
+ /*
37
+ let edmx = mps
38
+ ? await mps.getEdmx({ tenant, model: service.model, service: service.definition.name, locale })
39
+ : cds.localize(
40
+ service.model,
41
+ locale,
42
+ // REVISIT: we could cache this in model._cached
43
+ cds.compile.to.edmx(service.model, { service: service.definition.name })
44
+ ) */
28
45
  return next(null, toODataResult(edmx))
29
46
  } catch (e) {
30
47
  if (LOG._error) {
@@ -460,6 +460,7 @@ const read = service => {
460
460
 
461
461
  const changeset = odataReq.getAtomicityGroupId()
462
462
  const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
463
+ // REVISIT: can be removed when pluggable middlewares are active
463
464
  cds.context = tx
464
465
 
465
466
  let result, err
@@ -85,6 +85,11 @@ class ValueValidator {
85
85
  this._mode = mode
86
86
  }
87
87
 
88
+ get property() {
89
+ return this._mode === 'decode' &&
90
+ this._valueConverter._propertyOrReturnType &&
91
+ this._valueConverter._propertyOrReturnType.getName()
92
+ }
88
93
  /**
89
94
  * Validates value of Edm.Binary type.
90
95
  * @param {Buffer} value - Edm.Binary value as base64 or base64url string
@@ -257,7 +262,10 @@ class ValueValidator {
257
262
  value +
258
263
  ' (JavaScript ' +
259
264
  typeof value +
260
- '). The length of the ' +
265
+ ')' +
266
+ (this.property ? ' for property "' + this.property + '"' : '') +
267
+ '. ' +
268
+ 'The length of the ' +
261
269
  typeName +
262
270
  ' value must not be greater than the MaxLength facet value (' +
263
271
  maxLength +
@@ -344,7 +352,9 @@ class ValueValidator {
344
352
  value +
345
353
  ' (JavaScript ' +
346
354
  typeof value +
347
- '). ' +
355
+ ')' +
356
+ (this.property ? ' for property "' + this.property + '"' : '') +
357
+ '. ' +
348
358
  'The number of milliseconds does not correspond to the Precision facet value (' +
349
359
  precision +
350
360
  ').'
@@ -368,15 +378,16 @@ class ValueValidator {
368
378
  if (Number.isNaN(bigValue)) {
369
379
  throw this._valueError(value, 'Edm.Decimal', 'number or a string representing a number')
370
380
  }
371
-
372
381
  // check that the value has no more digits than specified for precision
373
382
  if (precision !== null && precision !== undefined && bigValue.c.length > precision) {
374
383
  throw new IllegalArgumentError(
375
- 'Invalid value ' +
384
+ 'Invalid value ' +
376
385
  value +
377
386
  ' (JavaScript ' +
378
387
  typeof value +
379
- '). ' +
388
+ ')' +
389
+ (this.property ? ' for property "' + this.property + '"' : '') +
390
+ '. ' +
380
391
  'The specified Edm.Decimal value does not correspond to the Precision facet value (' +
381
392
  precision +
382
393
  ').'
@@ -397,7 +408,9 @@ class ValueValidator {
397
408
  value +
398
409
  ' (JavaScript ' +
399
410
  typeof value +
400
- '). ' +
411
+ ')' +
412
+ (this.property ? ' for property "' + this.property + '"' : '') +
413
+ '. ' +
401
414
  'If Precision is equal to Scale, a single zero must precede the decimal point ' +
402
415
  'in the Edm.Decimal value.'
403
416
  )
@@ -413,7 +426,9 @@ class ValueValidator {
413
426
  value +
414
427
  ' (JavaScript ' +
415
428
  typeof value +
416
- '). ' +
429
+ ')' +
430
+ (this.property ? ' for property "' + this.property + '"' : '') +
431
+ '. ' +
417
432
  'The number of digits to the left of the decimal point must not be greater than ' +
418
433
  'Precision minus Scale, i.e., ' +
419
434
  (precision - scale) +
@@ -425,11 +440,13 @@ class ValueValidator {
425
440
  const decimalPart = bigValue.minus(integerPart)
426
441
  if (decimalPart.c.length > scale && !decimalPart.eq(0)) {
427
442
  throw new IllegalArgumentError(
428
- 'Invalid value ' +
443
+ 'Invalid value ' +
429
444
  value +
430
445
  ' (JavaScript ' +
431
446
  typeof value +
432
- '). ' +
447
+ ')' +
448
+ (this.property ? ' for property "' + this.property + '"' : '') +
449
+ '. ' +
433
450
  'The specified Edm.Decimal value has more digits to the right of the decimal point ' +
434
451
  'than allowed by the Scale facet value (' +
435
452
  scale +
@@ -705,18 +722,13 @@ class ValueValidator {
705
722
  * @private
706
723
  */
707
724
  _valueError (value, typeName, requiredText) {
708
- const property =
709
- this._mode === 'decode' &&
710
- this._valueConverter._propertyOrReturnType &&
711
- this._valueConverter._propertyOrReturnType.getName()
712
-
713
725
  const msg =
714
726
  'Invalid value ' +
715
727
  (typeName.includes('Geo') ? JSON.stringify(value) : value) +
716
728
  ' (JavaScript ' +
717
729
  typeof value +
718
730
  ')' +
719
- (property ? ' for property "' + property + '"' : '') +
731
+ (this.property ? ' for property "' + this.property + '"' : '') +
720
732
  '. ' +
721
733
  'A ' +
722
734
  requiredText +
@@ -58,7 +58,7 @@ const readAfterWrite = async (req, srv, { operation, isBefore } = { isBefore: fa
58
58
  // gracefully set location and no body if no read auth or not readable capability
59
59
  let result
60
60
  try {
61
- const _req = new Request({ query, event: 'READ', _: req._ })
61
+ const _req = new Request({ query, event: 'READ', _: req._, params: req.params })
62
62
  result = await srv.dispatch(_req)
63
63
  if (result && req.target._isDraftEnabled) removeDraftUUIDIfNecessary(req)(result)
64
64
  if (result === null && !isBefore && (_isWriteWithResponse(req) || _isDraftAction(req))) {