@sap/cds 7.3.1 → 7.4.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.
- package/CHANGELOG.md +58 -3
- package/_i18n/i18n_es_MX.properties +110 -0
- package/apis/cds.d.ts +13 -12
- package/apis/core.d.ts +27 -108
- package/apis/cqn.d.ts +15 -18
- package/apis/csn.d.ts +95 -60
- package/apis/env.d.ts +25 -0
- package/apis/events.d.ts +124 -0
- package/apis/{reflect.d.ts → linked.d.ts} +27 -38
- package/apis/models.d.ts +60 -45
- package/apis/ql.d.ts +11 -5
- package/apis/{serve.d.ts → server.d.ts} +57 -31
- package/apis/services.d.ts +74 -145
- package/apis/test.d.ts +1 -1
- package/bin/serve.js +3 -0
- package/lib/compile/cds-compile.js +2 -2
- package/lib/compile/to/edm.js +8 -3
- package/lib/compile/to/gql.js +4 -0
- package/lib/dbs/cds-deploy.js +52 -4
- package/lib/env/cds-requires.js +27 -15
- package/lib/env/defaults.js +1 -0
- package/lib/env/schemas/index.js +10 -0
- package/lib/index.js +7 -4
- package/lib/linked/models.js +8 -5
- package/lib/ql/CREATE.js +2 -0
- package/lib/ql/DELETE.js +1 -0
- package/lib/ql/DROP.js +2 -0
- package/lib/ql/INSERT.js +2 -22
- package/lib/ql/Query.js +59 -22
- package/lib/ql/SELECT.js +5 -0
- package/lib/ql/STREAM.js +2 -0
- package/lib/ql/UPDATE.js +2 -0
- package/lib/ql/UPSERT.js +3 -1
- package/lib/ql/cds-ql.js +21 -5
- package/lib/ql/infer.js +129 -0
- package/lib/req/cds-context.js +8 -5
- package/lib/srv/cds-connect.js +3 -1
- package/lib/utils/axios.js +4 -2
- package/lib/utils/data.js +3 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +12 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +26 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +11 -8
- package/libx/_runtime/common/code-ext/worker.js +5 -16
- package/libx/_runtime/common/generic/auth/capabilities.js +11 -2
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/common/utils/postProcessing.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +20 -1
- package/libx/{common → _runtime/common}/utils/ucsn.js +19 -11
- package/libx/_runtime/db/expand/expandCQNToJoin.js +2 -2
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -1
- package/libx/_runtime/db/sql-builder/UpdateBuilder.js +6 -1
- package/libx/_runtime/db/sql-builder/dollar.js +7 -7
- package/libx/_runtime/fiori/generic/activate.js +2 -2
- package/libx/_runtime/fiori/generic/edit.js +25 -45
- package/libx/_runtime/fiori/generic/read.js +3 -5
- package/libx/_runtime/fiori/lean-draft.js +142 -64
- package/libx/_runtime/fiori/utils/delete.js +7 -1
- package/libx/_runtime/fiori/utils/handler.js +4 -6
- package/libx/_runtime/fiori/utils/lockInfo.js +27 -0
- package/libx/_runtime/fiori/utils/where.js +20 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -2
- package/libx/_runtime/messaging/Outbox.js +12 -47
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -3
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +3 -0
- package/libx/_runtime/messaging/common-utils/connections.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +12 -13
- package/libx/_runtime/messaging/file-based.js +7 -5
- package/libx/_runtime/messaging/redis-messaging.js +10 -11
- package/libx/_runtime/messaging/service.js +12 -26
- package/libx/_runtime/remote/Service.js +52 -36
- package/libx/_runtime/remote/utils/client.js +22 -123
- package/libx/odata/afterburner.js +14 -5
- package/libx/odata/grammar.peggy +26 -7
- package/libx/odata/metadata.js +18 -1
- package/libx/odata/parser.js +1 -1
- package/libx/odata/service-document.js +0 -1
- package/libx/odata/utils.js +19 -3
- package/libx/{_runtime/messaging/outbox/utils.js → outbox/index.js} +94 -24
- package/libx/rest/middleware/parse.js +1 -1
- package/package.json +2 -2
- package/apis/connect.d.ts +0 -39
- package/bin/utils/modules.js +0 -7
- package/bin/utils/term.js +0 -56
- package/lib/env/schema.js +0 -9
- package/lib/linked/queries.js +0 -41
- package/lib/srv/protocols/odata-v2-proxy.js +0 -3699
- package/libx/common/asserts.js +0 -0
- package/libx/common/crud.js +0 -0
- package/libx/common/etag.js +0 -0
- package/libx/common/localized.js +0 -0
- package/libx/common/managed.js +0 -0
- package/libx/common/paging.js +0 -0
- package/libx/common/readme.md +0 -4
- package/libx/common/sorting.js +0 -0
- package/libx/common/temporal.js +0 -0
- package/libx/connect/auth.js +0 -0
- package/libx/connect/perf.js +0 -0
- package/libx/connect/readme.md +0 -3
- package/libx/fiori/draft/readme.md +0 -1
- package/libx/fiori/readme.md +0 -1
- package/libx/hana/readme.md +0 -1
- package/libx/msg/readme.md +0 -3
- package/libx/readme.md +0 -1
- package/libx/sqlite/readme.md +0 -1
- /package/libx/_runtime/{messaging/common-utils → common/utils}/waitingTime.js +0 -0
- /package/libx/{_runtime/messaging/outbox → outbox}/OutboxRunner.js +0 -0
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('./
|
|
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.
|
|
149
|
-
|
|
149
|
+
Object.defineProperties(module.exports, {
|
|
150
|
+
default: { value: module.exports },
|
|
151
|
+
__esModule: { value: true },
|
|
152
|
+
})
|
package/lib/linked/models.js
CHANGED
|
@@ -24,10 +24,13 @@ class LinkedCSN extends any {
|
|
|
24
24
|
d.items ? array.prototype :
|
|
25
25
|
/* else: */ any.prototype
|
|
26
26
|
)
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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('
|
|
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
package/lib/ql/DELETE.js
CHANGED
package/lib/ql/DROP.js
CHANGED
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
|
-
|
|
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
|
-
|
|
39
|
+
_target4 (...args) {
|
|
40
|
+
return this._target_ref4 (...args)
|
|
41
|
+
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
Object.defineProperty (this, '_target', { value: _target4 (target,arg2), configurable:true, writable:true })
|
|
43
|
+
_target_ref4 (target, arg2) {
|
|
43
44
|
|
|
44
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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', {
|
|
81
|
-
|
|
82
|
-
|
|
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
package/lib/ql/UPDATE.js
CHANGED
package/lib/ql/UPSERT.js
CHANGED
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,
|
|
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
|
-
|
|
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) {
|
package/lib/ql/infer.js
ADDED
|
@@ -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
|
+
})
|
package/lib/req/cds-context.js
CHANGED
|
@@ -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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
}
|
package/lib/srv/cds-connect.js
CHANGED
|
@@ -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
|
-
|
|
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} `)
|
package/lib/utils/axios.js
CHANGED
|
@@ -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
|
@@ -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
|
*
|
|
@@ -20,14 +27,25 @@ const metadata = service => {
|
|
|
20
27
|
|
|
21
28
|
try {
|
|
22
29
|
const { 'cds.xt.ModelProviderService': mps } = cds.services
|
|
23
|
-
let edmx
|
|
24
|
-
|
|
25
|
-
:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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) {
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js
CHANGED
|
@@ -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
|
|