@sap/cds 7.3.1 → 7.4.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 (110) hide show
  1. package/CHANGELOG.md +69 -3
  2. package/_i18n/i18n_es_MX.properties +110 -0
  3. package/apis/cds.d.ts +13 -12
  4. package/apis/core.d.ts +27 -108
  5. package/apis/cqn.d.ts +15 -18
  6. package/apis/csn.d.ts +95 -60
  7. package/apis/env.d.ts +25 -0
  8. package/apis/events.d.ts +125 -0
  9. package/apis/{reflect.d.ts → linked.d.ts} +29 -38
  10. package/apis/models.d.ts +60 -45
  11. package/apis/ql.d.ts +19 -5
  12. package/apis/{serve.d.ts → server.d.ts} +59 -33
  13. package/apis/services.d.ts +76 -147
  14. package/apis/test.d.ts +1 -1
  15. package/bin/serve.js +3 -0
  16. package/lib/compile/cds-compile.js +2 -2
  17. package/lib/compile/etc/csv.js +2 -1
  18. package/lib/compile/to/edm.js +8 -3
  19. package/lib/compile/to/gql.js +4 -0
  20. package/lib/dbs/cds-deploy.js +52 -4
  21. package/lib/env/cds-requires.js +27 -15
  22. package/lib/env/defaults.js +1 -0
  23. package/lib/env/schemas/index.js +10 -0
  24. package/lib/index.js +7 -4
  25. package/lib/linked/models.js +8 -5
  26. package/lib/ql/CREATE.js +2 -0
  27. package/lib/ql/DELETE.js +1 -0
  28. package/lib/ql/DROP.js +2 -0
  29. package/lib/ql/INSERT.js +2 -22
  30. package/lib/ql/Query.js +59 -22
  31. package/lib/ql/SELECT.js +5 -0
  32. package/lib/ql/STREAM.js +2 -0
  33. package/lib/ql/UPDATE.js +2 -0
  34. package/lib/ql/UPSERT.js +3 -1
  35. package/lib/ql/cds-ql.js +21 -5
  36. package/lib/ql/infer.js +129 -0
  37. package/lib/req/cds-context.js +8 -5
  38. package/lib/srv/cds-connect.js +3 -1
  39. package/lib/utils/axios.js +4 -2
  40. package/lib/utils/data.js +3 -0
  41. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +12 -0
  42. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +27 -9
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +1 -1
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +8 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +11 -8
  46. package/libx/_runtime/common/code-ext/worker.js +5 -16
  47. package/libx/_runtime/common/generic/auth/capabilities.js +11 -2
  48. package/libx/_runtime/common/i18n/messages.properties +1 -0
  49. package/libx/_runtime/common/utils/postProcessing.js +1 -1
  50. package/libx/_runtime/common/utils/resolveView.js +28 -9
  51. package/libx/{common → _runtime/common}/utils/ucsn.js +19 -11
  52. package/libx/_runtime/db/expand/expandCQNToJoin.js +6 -6
  53. package/libx/_runtime/db/expand/rawToExpanded.js +4 -4
  54. package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -1
  55. package/libx/_runtime/db/sql-builder/UpdateBuilder.js +6 -1
  56. package/libx/_runtime/db/sql-builder/dollar.js +7 -7
  57. package/libx/_runtime/fiori/generic/activate.js +2 -2
  58. package/libx/_runtime/fiori/generic/edit.js +25 -45
  59. package/libx/_runtime/fiori/generic/read.js +3 -5
  60. package/libx/_runtime/fiori/lean-draft.js +171 -84
  61. package/libx/_runtime/fiori/utils/delete.js +7 -1
  62. package/libx/_runtime/fiori/utils/handler.js +4 -6
  63. package/libx/_runtime/fiori/utils/lockInfo.js +27 -0
  64. package/libx/_runtime/fiori/utils/where.js +20 -1
  65. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -2
  66. package/libx/_runtime/messaging/Outbox.js +12 -47
  67. package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -3
  68. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +3 -0
  69. package/libx/_runtime/messaging/common-utils/connections.js +1 -1
  70. package/libx/_runtime/messaging/enterprise-messaging.js +12 -13
  71. package/libx/_runtime/messaging/file-based.js +7 -5
  72. package/libx/_runtime/messaging/redis-messaging.js +10 -11
  73. package/libx/_runtime/messaging/service.js +12 -26
  74. package/libx/_runtime/remote/Service.js +52 -36
  75. package/libx/_runtime/remote/utils/client.js +24 -125
  76. package/libx/odata/afterburner.js +16 -6
  77. package/libx/odata/grammar.peggy +26 -7
  78. package/libx/odata/metadata.js +18 -1
  79. package/libx/odata/parser.js +1 -1
  80. package/libx/odata/service-document.js +0 -1
  81. package/libx/odata/utils.js +19 -3
  82. package/libx/{_runtime/messaging/outbox/utils.js → outbox/index.js} +94 -24
  83. package/libx/rest/middleware/parse.js +1 -1
  84. package/package.json +2 -2
  85. package/apis/connect.d.ts +0 -39
  86. package/bin/utils/modules.js +0 -7
  87. package/bin/utils/term.js +0 -56
  88. package/lib/env/schema.js +0 -9
  89. package/lib/linked/queries.js +0 -41
  90. package/lib/srv/protocols/odata-v2-proxy.js +0 -3699
  91. package/libx/common/asserts.js +0 -0
  92. package/libx/common/crud.js +0 -0
  93. package/libx/common/etag.js +0 -0
  94. package/libx/common/localized.js +0 -0
  95. package/libx/common/managed.js +0 -0
  96. package/libx/common/paging.js +0 -0
  97. package/libx/common/readme.md +0 -4
  98. package/libx/common/sorting.js +0 -0
  99. package/libx/common/temporal.js +0 -0
  100. package/libx/connect/auth.js +0 -0
  101. package/libx/connect/perf.js +0 -0
  102. package/libx/connect/readme.md +0 -3
  103. package/libx/fiori/draft/readme.md +0 -1
  104. package/libx/fiori/readme.md +0 -1
  105. package/libx/hana/readme.md +0 -1
  106. package/libx/msg/readme.md +0 -3
  107. package/libx/readme.md +0 -1
  108. package/libx/sqlite/readme.md +0 -1
  109. /package/libx/_runtime/{messaging/common-utils → common/utils}/waitingTime.js +0 -0
  110. /package/libx/{_runtime/messaging/outbox → outbox}/OutboxRunner.js +0 -0
@@ -0,0 +1,10 @@
1
+ const { readFile } = require('fs').promises
2
+ const { join } = require('path')
3
+
4
+ module.exports = new class {
5
+ async default4(name) {
6
+ let file = join(__dirname, name)
7
+ let json = await readFile(file, 'utf8')
8
+ return JSON.parse(json)
9
+ }
10
+ }
package/lib/index.js CHANGED
@@ -10,8 +10,8 @@ const cds = module.exports = new class cds extends EventEmitter {
10
10
  get plugins() { return super.plugins = require('./plugins').activate() }
11
11
  get version() { return super.version = require('../package.json').version }
12
12
  get env() { return super.env = require('./env/cds-env').for('cds',this.root) }
13
- get schema() { return super.schema = require('./env/schema') } // dynamic schema loading
14
13
  get home() { return super.home = __dirname.slice(0,-4) }
14
+ get schema() { return super.schema = require('./env/schemas') } // REVISIT: Better move that to cds-dk?
15
15
  cli = { command:'', options:{}, argv:[] }
16
16
  root = process.cwd()
17
17
 
@@ -32,7 +32,7 @@ const cds = module.exports = new class cds extends EventEmitter {
32
32
  // Model Reflection, Builtin types and classes
33
33
  get reflect() { return super.reflect = this.linked }
34
34
  get linked() { return super.linked = require('./linked/models') }
35
- get infer() { return super.infer = require('./linked/queries') }
35
+ get infer() { return super.infer = require('./ql/infer') }
36
36
  get builtin() { return super.builtin = require('./linked/types') }
37
37
  get Association() { return super.Association = this.builtin.classes.Association }
38
38
  get Composition() { return super.Composition = this.builtin.classes.Composition }
@@ -57,6 +57,7 @@ const cds = module.exports = new class cds extends EventEmitter {
57
57
  get server() { return super.server = require('../server') }
58
58
  get serve() { return super.serve = require('./srv/cds-serve') }
59
59
  get connect() { return super.connect = require('./srv/cds-connect') }
60
+ get outboxed() { return super.outboxed = require('../libx/outbox') }
60
61
  get middlewares() { return super.middlewares = require('./srv/middlewares') }
61
62
  get odata() { return super.odata = require('../libx/odata') }
62
63
  get auth() { return super.auth = require('./auth') }
@@ -145,5 +146,7 @@ global.cds = cds // REVISIT: using global.cds seems wrong
145
146
  if (process.env.CDS_JEST_MEM_FIX && typeof jest !== 'undefined') require('./utils/jest.js')
146
147
 
147
148
  // Allow for import cds from '@sap/cds' without esModuleInterop
148
- Object.defineProperty(module.exports, "__esModule", { value: true });
149
- module.exports.default = module.exports
149
+ Object.defineProperties(module.exports, {
150
+ default: { value: module.exports },
151
+ __esModule: { value: true },
152
+ })
@@ -24,10 +24,13 @@ class LinkedCSN extends any {
24
24
  d.items ? array.prototype :
25
25
  /* else: */ any.prototype
26
26
  )
27
- if (p.key && !d.key && d.kind === 'element') Object.defineProperty (d,'key',{value:undefined}) //> don't propagate .key
28
- if (p.params && !d.params && d.kind === 'entity') Object.defineProperty (d,'params',{value:undefined}) //> don't propagate .params
29
- if (p.actions && !d.actions && d.kind === 'entity') Object.defineProperty (d,'actions',{value:undefined}) //> don't propagate .actions
30
- if (d.elements && d.elements.localized) Object.defineProperty (d,'texts',{value: defs [d.elements.localized.target] })
27
+ if (d.kind === 'entity') {
28
+ if (p.actions && !d.actions) _set (d,'actions',undefined) //> don't propagate .actions
29
+ if (p.params && !d.params) _set (d,'params',undefined) //> don't propagate .params
30
+ if (d.elements?.localized) _set (d,'texts', defs[d.elements.localized.target])
31
+ } else if (d.kind === 'element') {
32
+ if (p.key && !d.key) _set (d,'key',undefined) //> don't propagate .key
33
+ }
31
34
  try { return Object.setPrototypeOf(d,p) } //> link d to resolved proto
32
35
  catch(e) { //> cyclic proto error
33
36
  let msg = d.name; for (; p && p.name; p = p.__proto__) msg += ' > '+p.name
@@ -99,7 +102,7 @@ class LinkedCSN extends any {
99
102
 
100
103
  const _unresolved = (x,unknown=any) => ({ name:x, __proto__:unknown.prototype, _unresolved:true })
101
104
  const _builtin = x => types[x] || typeof x === 'string' && x.startsWith('cds.hana.') && any.prototype
102
- const _infer = require('./queries'), _not_inferred = _unresolved('<query>',entity)
105
+ const _infer = require('../ql/infer'), _not_inferred = _unresolved('<query>',entity)
103
106
  const _set = (o,p,v) => Object.defineProperty (o,p,{value:v,enumerable:false,configurable:1,writable:1})
104
107
  const _own = (o,p) => { const pd = Reflect.getOwnPropertyDescriptor(o,p); return pd && pd.value }
105
108
  const _is = x => {
package/lib/ql/CREATE.js CHANGED
@@ -19,4 +19,6 @@ module.exports = class Query extends require('./Query') {
19
19
  this.CREATE.as = query
20
20
  return this
21
21
  }
22
+
23
+ get _target_ref(){ return this.CREATE.entity }
22
24
  }
package/lib/ql/DELETE.js CHANGED
@@ -18,4 +18,5 @@ module.exports = class Query extends Whereable {
18
18
  return super.valueOf('DELETE FROM')
19
19
  }
20
20
 
21
+ get _target_ref(){ return this.DELETE.from }
21
22
  }
package/lib/ql/DROP.js CHANGED
@@ -20,4 +20,6 @@ module.exports = class Query extends require('./Query') {
20
20
  DROP.entity = DROP.view = this._target4 (e)
21
21
  return this
22
22
  }
23
+
24
+ get _target_ref(){ return this.DROP.entity }
23
25
  }
package/lib/ql/INSERT.js CHANGED
@@ -30,29 +30,7 @@ module.exports = class Query extends require('./Query') {
30
30
  this[this.cmd].rows = rows
31
31
  return this
32
32
  }
33
- _rows(rows, ...args) {
34
33
 
35
- const INSERT = this.cmd
36
- if (Array.isArray(rows)) {
37
- // check if all the entries in the array are arrays
38
- if (rows.every(e => Array.isArray(e))) {
39
- this[INSERT].rows = rows
40
- // check if array contains one or multiple objects
41
- } else if (rows.every(e => typeof e === 'object')) {
42
- this[INSERT].entries = rows
43
- // the rows have been added as arguments
44
- } else if (args.length !== 0) {
45
- args.unshift(rows)
46
- this[INSERT].rows = args
47
- } else {
48
- this[INSERT].values = rows
49
- }
50
- } else if (typeof rows === 'object') {
51
- this[INSERT].entries = rows
52
- }
53
-
54
- return this
55
- }
56
34
  as (query) {
57
35
  if (!query || !query.SELECT) this._expected `${{query}} to be a CQN {SELECT} query object`
58
36
  this[this.cmd].as = query
@@ -61,6 +39,8 @@ module.exports = class Query extends require('./Query') {
61
39
  valueOf() {
62
40
  return super.valueOf('INSERT INTO')
63
41
  }
42
+
43
+ get _target_ref(){ return this.INSERT.into }
64
44
  }
65
45
 
66
46
  const is_array = Array.isArray
package/lib/ql/Query.js CHANGED
@@ -6,7 +6,7 @@ class Query {
6
6
  constructor(_={}) { this[this.cmd] = _ }
7
7
 
8
8
  alias (a) {
9
- let _ = this[this.cmd] ;(_.from || _.into || _.entity).as = a
9
+ this._target_ref.as = a
10
10
  return this
11
11
  }
12
12
 
@@ -36,20 +36,34 @@ class Query {
36
36
  return (r,e) => q.runInAsyncScope (srv.run, srv, this) .then (r,e)
37
37
  }
38
38
 
39
- _target_ref4 (target, arg2) {
39
+ _target4 (...args) {
40
+ return this._target_ref4 (...args)
41
+ }
40
42
 
41
- // Resolving this._target --> REVISIT: this is not reliable !!!
42
- Object.defineProperty (this, '_target', { value: _target4 (target,arg2), configurable:true, writable:true })
43
+ _target_ref4 (target, arg2) {
43
44
 
44
- return target && (
45
+ // If called with a linked entity -> use it as this.target
46
+ if (target instanceof cds.entity) this.target = target
47
+
48
+ // REVISIT: this._target is not reliable !!!
49
+ this._set ('_target', function _target(t) { return t && (
50
+ typeof t === 'string' ? { name: t } :
51
+ t.ref ? { name: t.ref[0] } :
52
+ t.raw ? _target(arg2) :
53
+ t.SELECT ? _target(t.SELECT.from) :
54
+ t //> default is assumed to be a csn definition or a look-alike or a SELECT
55
+ )}(target))
56
+
57
+ // Determine from.ref or from.SELECT
58
+ const from = target && (
59
+ target.name ? {ref:[target.name]} :
45
60
  typeof target === 'string' ? cds.parse.path(target) :
61
+ target.raw ? cds.parse.path(...arguments) :
46
62
  target.ref ? target :
47
63
  target.SELECT ? target :
48
- target.SET ? target :
49
- target.raw ? cds.parse.path(...arguments) :
50
- target.name ? {ref:[target.name]} : 0
64
+ target.SET ? target : 0
51
65
  )
52
- || this._expected `${{target}} to be an entity path string, a CSN definition, a {ref}, a {SELECT}, or a {SET}`
66
+ return from || this._expected `${{target}} to be an entity path string, a CSN definition, a {ref}, a {SELECT}, or a {SET}`
53
67
  }
54
68
 
55
69
  _expected (...args) {
@@ -62,27 +76,50 @@ class Query {
62
76
  return this
63
77
  }
64
78
 
79
+ _set (property, value) {
80
+ Reflect.defineProperty (this, property, { value, configurable:true, writable:true })
81
+ return value
82
+ }
83
+
65
84
  valueOf (cmd=this.cmd) {
66
85
  return `${cmd} ${_name(this._target.name)} `
67
86
  }
68
- }
69
87
 
70
- const _target4 = (target, arg2) => target && (
71
- typeof target === 'string' ? { name: target } :
72
- target.name ? target : //> assumed to be a linked csn definition
73
- target.ref ? { name: target.ref[0] } :
74
- target.raw ? _target4(arg2) :
75
- target._target || { name: undefined }
76
- )
88
+ get _target_ref() {
89
+ throw cds.error `Query subclass ${this.constructor.name} must implement '_target_ref'`
90
+ }
91
+
92
+ /**
93
+ * Returns the inferred query's source, which is the entity referred
94
+ * to in SELECT.from, INSERT.into, UPDATE.entity, or DELETE.from,
95
+ * or a sub query specified in SELECT.from, INSERT.into,
96
+ */
97
+ get source() {
98
+ const m = this._srv?.model || cds.context?.model || cds.model
99
+ return cds.infer (this, m?.definitions, 'get source')
100
+ }
101
+ set source(t) { this._set('source',t) }
102
+
103
+ /**
104
+ * Returns the inferred query's target, which is the entity referred
105
+ * to in SELECT.from, INSERT.into, UPDATE.entity, or DELETE.from.
106
+ * In case of a sub query specified in SELECT.from, INSERT.into,
107
+ * returns the target of the sub query, recursively.
108
+ */
109
+ get target() {
110
+ const m = this._srv?.model || cds.context?.model || cds.model
111
+ return cds.infer (this, m?.definitions)
112
+ }
113
+ set target(t) { this._set('target',t) }
114
+ }
77
115
 
78
116
  const _name = cds.env.sql.names === 'quoted' ? n =>`"${n}"` : n => n.replace(/[.:]/g,'_')
79
117
 
80
- Object.defineProperty (Query.prototype, '_target4', { value: (
81
- cds.env.ql.quirks_mode === false ? Query.prototype._target_ref4
82
- : function (...args) {
83
- const {ref, as} = this._target_ref4 (...args)
118
+ if (cds.env.ql.quirks_mode) Object.defineProperty (Query.prototype, '_target4', {
119
+ value: function (...args) {
120
+ const { ref, as } = this._target_ref4 (...args)
84
121
  return ref.length === 1 && typeof ref[0] === 'string' && !as ? ref[0] : as ? {ref, as} : {ref}
85
122
  }
86
- )})
123
+ })
87
124
 
88
125
  module.exports = Query
package/lib/ql/SELECT.js CHANGED
@@ -151,6 +151,11 @@ module.exports = class Query extends Whereable {
151
151
  valueOf() {
152
152
  return super.valueOf('SELECT * FROM')
153
153
  }
154
+
155
+ get _target_ref(){ return this.SELECT.from }
156
+
157
+ get elements() { return this.elements = cds.infer.elements4 (this.SELECT.columns, this.source) }
158
+ set elements(e) { this._set('elements',e) }
154
159
  }
155
160
 
156
161
 
package/lib/ql/STREAM.js CHANGED
@@ -77,6 +77,8 @@ module.exports = class Query extends Whereable {
77
77
  this.STREAM.data = x
78
78
  return this
79
79
  }
80
+
81
+ get _target_ref(){ return this.STREAM.into || this.STREAM.from }
80
82
  }
81
83
 
82
84
 
package/lib/ql/UPDATE.js CHANGED
@@ -64,6 +64,8 @@ module.exports = class Query extends Whereable {
64
64
 
65
65
  return this
66
66
  }
67
+
68
+ get _target_ref(){ return this.UPDATE.entity }
67
69
  }
68
70
 
69
71
 
package/lib/ql/UPSERT.js CHANGED
@@ -1 +1,3 @@
1
- module.exports = class Query extends require('./INSERT') {}
1
+ module.exports = class Query extends require('./INSERT') {
2
+ get _target_ref(){ return this.UPSERT.into }
3
+ }
package/lib/ql/cds-ql.js CHANGED
@@ -1,16 +1,20 @@
1
1
  const Query = require('./Query')
2
2
  require = path => { // eslint-disable-line no-global-assign
3
3
  const clazz = module.require (path); if (!clazz._api) return clazz
4
+ const kind = path.match(/\w+$/)[0]
5
+ Object.defineProperties (clazz.prototype, {
6
+ kind: { value: kind },
7
+ cmd: { value: kind }
8
+ })
4
9
  const api = clazz._api()
5
- Object.defineProperty (clazz.prototype, 'cmd', { value: path.match(/\w+$/)[0] })
6
10
  return Object.assign (function (...args) {
7
11
  if (new.target) return new clazz (...args) // allows: new SELECT
8
12
  return api (...args) // allows: SELECT(...).from()
9
- }, api)
13
+ }, { class: clazz }, api)
10
14
  }
11
15
 
12
- module.exports = {
13
- Query, clone: (q,_) => Query.prototype.clone.call(q,_),
16
+ module.exports = exports = {
17
+ Query,
14
18
  STREAM: require('./STREAM'),
15
19
  SELECT: require('./SELECT'),
16
20
  INSERT: require('./INSERT'),
@@ -21,7 +25,19 @@ module.exports = {
21
25
  DROP: require('./DROP'),
22
26
  }
23
27
 
24
- module.exports._reset = ()=>{ // for strange tests only
28
+ exports.clone = function (q,_) {
29
+ // q = this.query(q)
30
+ return Query.prototype.clone.call(q,_)
31
+ }
32
+
33
+ exports.query = function (q) {
34
+ if (q instanceof Query) return q
35
+ let kind = Object.keys(q)[0]
36
+ let clazz = exports[kind]
37
+ return !clazz ? q : new clazz (q[kind])
38
+ },
39
+
40
+ exports._reset = ()=>{ // for strange tests only
25
41
  const cds = require('../index')
26
42
  const _name = cds.env.sql.names === 'quoted' ? n =>`"${n}"` : n => n.replace(/[.:]/g,'_')
27
43
  Object.defineProperty (Query.prototype,'valueOf',{ configurable:1, value: function(cmd=this.cmd) {
@@ -0,0 +1,129 @@
1
+ const cds = require('../index')
2
+
3
+ /**
4
+ * Infers the target entity of a query
5
+ */
6
+ function infer (q, defs, _get_source) {
7
+ if (q._target instanceof cds.entity) return q._target
8
+ let from = q._target_ref
9
+ || q.SELECT?.from
10
+ || q.INSERT?.into
11
+ || q.UPSERT?.into
12
+ || q.UPDATE?.entity
13
+ || q.DELETE?.from
14
+ || q.STREAM?.from
15
+ || q.STREAM?.into
16
+ let source = infer_from (from, defs)
17
+ let target = source?.SELECT ? infer(source, defs) : source
18
+
19
+ Object.defineProperties (q, {
20
+ _target: { value: target, configurable:true, writable:true },
21
+ target: { value: target, configurable:true, writable:true },
22
+ source: { value: source, configurable:true, writable:true },
23
+ })
24
+ return _get_source ? source : target
25
+
26
+ function infer_from (from, defs={}) {
27
+ if (!from) return undefined
28
+ if (from.ref) return infer_ref (from.ref, defs) || _unresolved(from.ref[0])
29
+ if (from.SELECT) return from
30
+ if (from.SET || from.args) return undefined //> UNIONs and JOINs are not supported
31
+ return defs[from] || _unresolved(from) //> from is a string in quirks mode
32
+ }
33
+
34
+ function infer_ref (ref, defs) {
35
+ let target = {elements:defs}
36
+ for (let r of ref) {
37
+ const e = target.elements?.[r.id||r]; if (!e) return
38
+ target = (
39
+ e._target || //> for already linked associations
40
+ defs[e.target] || //> for not yet linked associations
41
+ e //> for structs
42
+ )
43
+ }
44
+ return target
45
+ }
46
+
47
+ function _unresolved (x) {
48
+ return { name: x.id || x, __proto__: cds.entity.prototype, _unresolved:true }
49
+ }
50
+ }
51
+
52
+
53
+ /**
54
+ * Infers the elements according to a query's columns.
55
+ * @param {Array} columns - the query's columns, or a nested .expand or .inline columns
56
+ * @param {Object} source - the query's source entity or sub select
57
+ */
58
+ function elements4 (columns, source) {
59
+
60
+ // SELECT from Books; SELECT * from Books
61
+ if (!columns || columns.length === 1 && columns[0] === '*') return source?.elements
62
+
63
+ const elements = {}; columns.forEach (c => {
64
+
65
+ // 1) SELECT *, ... from Books
66
+ if (c === '*') {
67
+ return Object.assign (elements, source.elements)
68
+ }
69
+
70
+ const ref = c.ref?.map(r => r.id??r)
71
+ const as = c.as || ref?.join('_') || c.func || c.val || cds.error `Alias required for column expressions ${c}`
72
+ let d = source, is2many
73
+
74
+ // 2) SELECT ... : String from Books
75
+ if (c.cast) {
76
+ return elements[as] = builtin [c.cast.type]
77
+ }
78
+
79
+ // 3) SELECT title, author.name from Books
80
+ if (c.ref && d?.elements) {
81
+ for (let r of ref) d = (d.SELECT ? d : d._target||d).elements?.[r]
82
+ || cds.error `Couldn't resolve element "${ref.join('/')}" in ${source.kind} ${source.name||''} ${Object.keys(source.elements)}`
83
+ if (d._target) { is2many = d.is2many; d = d._target }
84
+ // ... d is further processed in steps 5,6,7 below
85
+ }
86
+
87
+ // 4) SELECT 1, 2+3, count(*) from Books; SELECT type, name from sqlite.schema
88
+ else if (!c.expand) {
89
+ return elements[as] = _typeof(c) // { ..._typeof(c), name: as }
90
+ }
91
+
92
+ // 5) SELECT author.books { title } from Books
93
+ if (c.expand) {
94
+ if (d.items) { d = d.items; is2many = true }
95
+ d = new cds.struct ({ elements: elements4 (c.expand, d) }) //> { a, b, c } as x
96
+ return elements[as] = is2many ? new cds.array ({ items: d }) : d
97
+ }
98
+
99
+ // 6) SELECT author.books.{ title } from Books
100
+ if (c.inline) {
101
+ const nested = elements4 (c.inline, d)
102
+ for (let n in nested) elements[as+'_'+n] = nested[n]
103
+ }
104
+
105
+ // 7) SELECT title, author.name from Books
106
+ else return elements[as] = d // NOTE: the else is neccessary after step 5 above
107
+ })
108
+ return elements
109
+
110
+ function _typeof (c) {
111
+ if (c.val !== undefined) return builtin [typeof c.val] || builtin [ Number.isInteger(c.val) ? 'Integer' : 'Decimal' ]
112
+ if (c.func === 'count') return builtin.Integer
113
+ if (c.xpr?.length === 1) return _typeof(c.xpr[0])
114
+ return unknown
115
+ }
116
+ }
117
+
118
+ const unknown = Object.freeze (new cds.type ({ _unresolved:true }))
119
+ const builtin = function _init (){
120
+ const bi={}, bt = cds.builtin.types
121
+ for (let t of Object.keys(bt)) bi[t] = bi[t.slice(4)] = { type:t, __proto__: bt[t] }
122
+ bi.boolean = bi['cds.Boolean']
123
+ bi.string = bi['cds.String']
124
+ return bi
125
+ }()
126
+
127
+ module.exports = exports = Object.assign (infer, {
128
+ elements4, unknown
129
+ })
@@ -43,11 +43,14 @@ module.exports = new class extends AsyncLocalStorage {
43
43
  .finally (() => Promise.all(em.listeners('done').map(each => each())))
44
44
  })
45
45
  }
46
- const em = new EventEmitter; em.timer = (
47
- (o && o.after) ? setTimeout(fx, o.after).unref() :
48
- (o && o.every) ? setInterval(fx, o.every).unref() :
49
- setImmediate(fx).unref()
50
- )
46
+ const em = new EventEmitter
47
+ const { every, after } = o || {}
48
+ if (every) {
49
+ em.timer = setInterval(fx, every)
50
+ cds.on('shutdown', () => clearInterval(em.timer))
51
+ } else {
52
+ em.timer = (after ? setTimeout(fx, after) : setImmediate(fx)).unref()
53
+ }
51
54
  return em
52
55
  }
53
56
  }
@@ -43,9 +43,11 @@ connect.to = async (datasource, options) => {
43
43
  throw new Error (`No service definition found for '${required.service || datasource}'`)
44
44
  }
45
45
  // construct new service instance
46
- const srv = await new Service (datasource,m,o)
46
+ let srv = await new Service (datasource,m,o)
47
47
  await srv.prepend (srv.init, srv.options.impl)
48
48
  if (datasource === 'db') cds.db = srv
49
+ // outbox the service, if configured
50
+ if (srv.options.outbox) srv = cds.outboxed(srv)
49
51
  _done (cds.services[datasource] = srv)
50
52
  if (!o.silent) cds.emit ('connect',srv)
51
53
  TRACE?.timeEnd(`cds.connect ${datasource} `)
@@ -1,9 +1,10 @@
1
+ const http = require('http')
1
2
  class Axios {
2
-
3
3
  get axios() {
4
4
  // eslint-disable-next-line cds/no-missing-dependencies
5
5
  const axios = require('axios').create ({
6
6
  headers: { 'Content-Type': 'application/json' },
7
+ httpAgent: new http.Agent({ keepAlive: false}),
7
8
  baseURL: this.url,
8
9
  })
9
10
  // fill in baseURL on subsequent this.url = url, after server has started
@@ -44,6 +45,7 @@ const _args = (args) => {
44
45
 
45
46
  const _error = (e) => {
46
47
  Error.captureStackTrace (e,_error) //> adds the stack trace from caller code
48
+ if (e.errors) e = e.errors[0] // Node 20 sends AggregationErros
47
49
  if (e.code && e.port === 80 /* default port */) throw Object.assign (e, {
48
50
  message: e.message + '\nIt seems that the server was not started. Make sure to call \'cds.test(...)\' or \'cds.test.run(...)\'.',
49
51
  stack: null // stack is just clutter here
@@ -52,7 +54,7 @@ const _error = (e) => {
52
54
  if (message) e.message = code && code !== 'null' ? `${code} - ${message}` : message
53
55
  // Promote toJSON from prototype to own property to make it iterable
54
56
  // eslint-disable-next-line no-self-assign
55
- if (typeof jest !== 'undefined') e.toJSON = e.toJSON
57
+ if (typeof jest !== 'undefined') e.toJSON = e.toJSON // REVISIT: what is this for?
56
58
  throw e
57
59
  }
58
60
 
package/lib/utils/data.js CHANGED
@@ -27,6 +27,9 @@ class DataUtil {
27
27
  if (!entity.query && entity['@cds.persistence.skip'] !== true) {
28
28
  this._deletes.push(DELETE.from(entity))
29
29
  }
30
+ if (entity.drafts) {
31
+ this._deletes.push(DELETE.from(entity.drafts))
32
+ }
30
33
  }
31
34
  }
32
35
  if (this._deletes.length > 0) {
@@ -83,6 +83,7 @@ const _betterOkraError = err => {
83
83
  * @returns {Function}
84
84
  */
85
85
  const getErrorHandler = (crashOnError = true, srv) => {
86
+ // eslint-disable-next-line complexity
86
87
  return async (odataReq, odataRes, next, err) => {
87
88
  // REVISIT: crashOnError
88
89
  if (isStandardError(err) && crashOnError) {
@@ -148,6 +149,17 @@ const getErrorHandler = (crashOnError = true, srv) => {
148
149
  // REVISIT: We should also pass stack traces in development
149
150
  // if (!cds.env.production) error.stack = err.stack
150
151
 
152
+ if (cds.env.fiori.wrap_multiple_errors === false) {
153
+ // According to the Fiori Elements Failed Message specification, the format must be:
154
+ // Root level: First error, Details: Other errors
155
+ if (error.details) {
156
+ const [firstDetail, ...restDetails] = error.details
157
+ Object.assign(error, firstDetail)
158
+ if (restDetails.length) error.details = restDetails
159
+ else delete error.details
160
+ }
161
+ }
162
+
151
163
  next(null, Object.assign(error, { statusCode }))
152
164
  }
153
165
  }
@@ -1,3 +1,4 @@
1
+ const { join } = require('path')
1
2
  const cds = require('../../../../cds')
2
3
  const LOG = cds.log('odata')
3
4
 
@@ -5,6 +6,12 @@ const { toODataResult } = require('../utils/result')
5
6
  const { normalizeError } = require('../../../../common/error/frontend')
6
7
  const getError = require('../../../../common/error')
7
8
 
9
+ const mpSupportsEmptyLocale = () => {
10
+ const pkg = require(join('@sap/cds-mtxs', 'package.json'))
11
+ const version = pkg.version.match(/^(\d+\.)?(\d+\.)?(\*|\d+)$/).map(Number)
12
+ return version[1] > 1 || (version[1] === 1 && version[2] >= 12)
13
+ }
14
+
8
15
  /**
9
16
  * Provide localized metadata handler.
10
17
  *
@@ -14,20 +21,31 @@ const getError = require('../../../../common/error')
14
21
  const metadata = service => {
15
22
  return async (odataReq, odataRes, next) => {
16
23
  const req = odataReq.getIncomingRequest()
17
- const tenant = req.user && req.user.tenant
24
+ const tenant = req.tenant ?? req.user?.tenant
18
25
  // REVISIT: can we take locale from user, or is there some odata special wrt metadata?
19
26
  const locale = odataRes.getContract().getLocale()
20
27
 
21
28
  try {
22
29
  const { 'cds.xt.ModelProviderService': mps } = cds.services
23
- let edmx = mps
24
- ? await mps.getEdmx({ tenant, model: service.model, service: service.definition.name, locale })
25
- : cds.localize(
26
- service.model,
27
- locale,
28
- // REVISIT: we could cache this in model._cached
29
- cds.compile.to.edmx(service.model, { service: service.definition.name })
30
- )
30
+ let edmx
31
+ if (mps) {
32
+ // REVISIT: remove check later
33
+ if (mpSupportsEmptyLocale()) {
34
+ edmx = await mps.getEdmx({ tenant, model: service.model, service: service.definition.name })
35
+ const extBundle = cds.env.requires.extensibility && (await mps.getI18n({ tenant, locale }))
36
+ edmx = cds.localize(service.model, locale, edmx, extBundle)
37
+ } else {
38
+ edmx = await mps.getEdmx({ tenant, model: service.model, service: service.definition.name, locale })
39
+ }
40
+ } else {
41
+ edmx = cds.localize(
42
+ service.model,
43
+ locale,
44
+ // REVISIT: we could cache this in model._cached
45
+ cds.compile.to.edmx(service.model, { service: service.definition.name })
46
+ )
47
+ }
48
+
31
49
  return next(null, toODataResult(edmx))
32
50
  } catch (e) {
33
51
  if (LOG._error) {
@@ -36,7 +36,7 @@ class BatchProcessor {
36
36
  * @returns {Promise} the overall result
37
37
  */
38
38
  process () {
39
- if(cds.odata.batch_limit < this._batchContext.getRequestList().length) return Promise.reject({statusCode: 429, message: 'Batch request contains too many requests'})
39
+ if(cds.env.odata.batch_limit < this._batchContext.getRequestList().length) return Promise.reject({statusCode: 429, message: 'Batch request contains too many requests'})
40
40
  const request = this._batchContext.getRequest()
41
41
  const componentManager = request.getService().getComponentManager()
42
42
 
@@ -212,7 +212,14 @@ class TrustedResourceJsonSerializer {
212
212
  _serializeEntity (result, entityType, data, expandItems, odataPath, structurePath) {
213
213
  this._serializeAnnotations(result, data, MetaProperties.ETAG, MetaProperties.CONTEXT)
214
214
 
215
- this._serializeStructure(result, entityType, data, expandItems, odataPath, structurePath || [])
215
+ const { okra_skip_query_options, odata_new_parser } = cds.env.features
216
+ if (okra_skip_query_options && odata_new_parser) {
217
+ Object.entries(data).forEach(([key, value]) => {
218
+ if (!key.startsWith('*')) result[key] = value
219
+ })
220
+ } else {
221
+ this._serializeStructure(result, entityType, data, expandItems, odataPath, structurePath || [])
222
+ }
216
223
 
217
224
  // Add annotation '"@odata.id": null' for an entity of a transient type if some properties have been
218
225
  // aggregated away or if not all of the base type's properties have been serialized.