@sap/cds 7.7.2 → 7.8.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 +32 -1
- package/lib/auth/ias-auth.js +5 -3
- package/lib/auth/jwt-auth.js +4 -2
- package/lib/compile/cdsc.js +0 -10
- package/lib/compile/for/java.js +9 -5
- package/lib/compile/for/lean_drafts.js +1 -1
- package/lib/compile/to/edm.js +2 -1
- package/lib/compile/to/sql.js +0 -21
- package/lib/compile/to/srvinfo.js +13 -4
- package/lib/dbs/cds-deploy.js +7 -7
- package/lib/env/cds-requires.js +6 -0
- package/lib/index.js +4 -3
- package/lib/linked/classes.js +151 -88
- package/lib/linked/entities.js +27 -23
- package/lib/linked/models.js +57 -36
- package/lib/linked/types.js +42 -104
- package/lib/log/format/json.js +6 -2
- package/lib/ql/Whereable.js +3 -3
- package/lib/req/context.js +8 -4
- package/lib/srv/protocols/hcql.js +2 -1
- package/lib/srv/protocols/http.js +7 -7
- package/lib/srv/protocols/index.js +31 -13
- package/lib/srv/protocols/odata-v4.js +79 -58
- package/lib/srv/srv-api.js +7 -6
- package/lib/srv/srv-dispatch.js +1 -12
- package/lib/srv/srv-tx.js +9 -13
- package/lib/utils/cds-utils.js +6 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +11 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +21 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +3 -7
- package/libx/_runtime/cds-services/services/utils/columns.js +6 -3
- package/libx/_runtime/cds.js +0 -13
- package/libx/_runtime/common/generic/auth/restrict.js +1 -1
- package/libx/_runtime/common/generic/input.js +5 -2
- package/libx/_runtime/common/generic/sorting.js +8 -6
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -0
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +7 -1
- package/libx/_runtime/common/utils/keys.js +2 -2
- package/libx/_runtime/common/utils/resolveView.js +2 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
- package/libx/_runtime/common/utils/stream.js +0 -10
- package/libx/_runtime/common/utils/template.js +20 -35
- package/libx/_runtime/db/Service.js +5 -1
- package/libx/_runtime/db/utils/columns.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +14 -2
- package/libx/_runtime/messaging/Outbox.js +7 -5
- package/libx/_runtime/messaging/kafka.js +266 -0
- package/libx/_runtime/messaging/service.js +7 -5
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
- package/libx/common/assert/validation.js +1 -1
- package/libx/odata/index.js +8 -2
- package/libx/odata/middleware/batch.js +340 -0
- package/libx/odata/middleware/create.js +43 -46
- package/libx/odata/middleware/delete.js +27 -15
- package/libx/odata/middleware/error.js +6 -5
- package/libx/odata/middleware/metadata.js +16 -15
- package/libx/odata/middleware/operation.js +107 -59
- package/libx/odata/middleware/parse.js +15 -7
- package/libx/odata/middleware/read.js +150 -24
- package/libx/odata/middleware/service-document.js +17 -6
- package/libx/odata/middleware/stream.js +34 -17
- package/libx/odata/middleware/update.js +123 -87
- package/libx/odata/parse/afterburner.js +131 -28
- package/libx/odata/parse/cqn2odata.js +1 -1
- package/libx/odata/parse/grammar.peggy +4 -5
- package/libx/odata/parse/multipartToJson.js +163 -0
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +29 -47
- package/libx/odata/utils/path.js +72 -0
- package/libx/odata/utils/result.js +123 -20
- package/package.json +1 -1
- package/server.js +4 -0
package/lib/linked/entities.js
CHANGED
|
@@ -1,40 +1,47 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
1
|
+
const { LinkedDefinitions, struct, type } = require ('./classes')
|
|
2
|
+
const _is_cached = Symbol('is cached')
|
|
3
|
+
const _derived = Symbol('derived')
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
class entity extends struct {
|
|
5
7
|
is (kind) { return kind === 'entity' || kind === 'view' && !!this.query || super.is(kind) }
|
|
6
8
|
get keys() {
|
|
7
|
-
return this.
|
|
9
|
+
return this._elements ('key')
|
|
8
10
|
}
|
|
9
11
|
get associations() {
|
|
10
|
-
return this.
|
|
12
|
+
return this._elements ('isAssociation')
|
|
11
13
|
}
|
|
12
14
|
get compositions() {
|
|
13
|
-
return this.
|
|
15
|
+
return this._elements ('isComposition')
|
|
16
|
+
}
|
|
17
|
+
_elements (kind, _default = null) {
|
|
18
|
+
const derived = this.own(_derived, ()=>({}))
|
|
19
|
+
if (kind in derived) return derived[kind]
|
|
20
|
+
const {elements} = this, dict = new LinkedDefinitions; let any = _default
|
|
21
|
+
for (let e in elements) if (elements[e][kind]) (any = dict)[e] = elements[e]
|
|
22
|
+
return derived[kind] = any
|
|
14
23
|
}
|
|
24
|
+
|
|
15
25
|
get drafts() {
|
|
16
26
|
// Remove this getter when old draft is removed
|
|
17
|
-
if (cds.env.fiori.lean_draft) return null
|
|
18
27
|
return this.own('_drafts') || this.set('_drafts', this.elements?.HasDraftEntity && {
|
|
19
28
|
name: this.name + '_drafts', keys: this.keys,
|
|
20
29
|
toString(){ return this.name.replace(/\./g,'_') }
|
|
21
30
|
})
|
|
22
31
|
}
|
|
32
|
+
|
|
33
|
+
toJSON() {
|
|
34
|
+
return this.own('query') ? {...this} : super.toJSON(this)
|
|
35
|
+
}
|
|
23
36
|
toString() {
|
|
24
37
|
return this.name.replace(/\./g,'_')
|
|
25
38
|
}
|
|
26
|
-
_elements (filter) {
|
|
27
|
-
const {elements} = this, dict={}; let any
|
|
28
|
-
for (let e in elements) if (filter(elements[e])) (any = dict)[e] = elements[e]
|
|
29
|
-
return any
|
|
30
|
-
}
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
|
|
34
|
-
class Association extends
|
|
42
|
+
class Association extends type {
|
|
35
43
|
|
|
36
44
|
is(kind) { return kind === 'Association' || super.is(kind) }
|
|
37
|
-
get isAssociation(){ return true }
|
|
38
45
|
|
|
39
46
|
get is2many() { return !this.is2one }
|
|
40
47
|
get is2one() {
|
|
@@ -45,11 +52,13 @@ class Association extends struct {
|
|
|
45
52
|
set foreignKeys(k) { this.set('foreignKeys', k) }
|
|
46
53
|
get foreignKeys() {
|
|
47
54
|
const keys = this.keys; if (!keys) return this.foreignKeys = undefined
|
|
48
|
-
const foreignKeys =
|
|
55
|
+
const foreignKeys = new LinkedDefinitions
|
|
49
56
|
for (const k of keys) {
|
|
50
57
|
const el = k.ref.reduce((target,n)=> target.elements[n], this._target)
|
|
51
|
-
const as = k.as || k.ref
|
|
52
|
-
foreignKeys
|
|
58
|
+
const as = k.as || k.ref.at(-1)
|
|
59
|
+
foreignKeys[as] = Object.create (el, { name:{value:as} }) //, parent:{value:this} })
|
|
60
|
+
// REVISIT: We should change the line above to set parent correctly, as in the commented tail part.
|
|
61
|
+
// But that breaks code in _runtime which expects it to be the wrong parent.
|
|
53
62
|
}
|
|
54
63
|
return this.foreignKeys = foreignKeys
|
|
55
64
|
}
|
|
@@ -106,19 +115,14 @@ class Association extends struct {
|
|
|
106
115
|
return value
|
|
107
116
|
}
|
|
108
117
|
}
|
|
109
|
-
else return
|
|
118
|
+
else return struct.prototype.dataIn.call (this, d, prefix)
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
|
-
const _is_cached = Symbol('_cached')
|
|
113
|
-
|
|
114
121
|
|
|
115
122
|
class Composition extends Association {
|
|
116
123
|
|
|
117
124
|
is(kind) { return kind === 'Composition' || super.is(kind) }
|
|
118
|
-
get isComposition(){ return true }
|
|
119
|
-
get isManagedComposition(){ return this._target && this._target.kind !== 'entity' }
|
|
120
125
|
|
|
121
126
|
}
|
|
122
127
|
|
|
123
|
-
|
|
124
128
|
module.exports = { entity, Association, Composition }
|
package/lib/linked/models.js
CHANGED
|
@@ -1,28 +1,45 @@
|
|
|
1
|
-
const { types, classes
|
|
2
|
-
const
|
|
1
|
+
const { types, classes } = require('./types')
|
|
2
|
+
const { LinkedDefinitions, any } = classes
|
|
3
|
+
const _kinds = {
|
|
4
|
+
annotation:1,
|
|
5
|
+
context:1,
|
|
6
|
+
service:1,
|
|
7
|
+
entity:1,
|
|
8
|
+
action:1,
|
|
9
|
+
event:1
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
|
|
4
|
-
class LinkedCSN
|
|
13
|
+
class LinkedCSN {
|
|
5
14
|
|
|
6
15
|
constructor(x) {
|
|
7
|
-
|
|
16
|
+
|
|
17
|
+
if (typeof x === 'string') x = require('../index').compile(x) //> for convenience in repl
|
|
18
|
+
const defs = x.definitions = _iterable (x.definitions)
|
|
19
|
+
for (let d in defs) _link (defs[d],d)
|
|
20
|
+
return Object.setPrototypeOf (x, new.target.prototype)
|
|
21
|
+
|
|
8
22
|
function _link (d, name, parent, _kind) {
|
|
9
23
|
if (name) _set (d,'name', name)
|
|
10
24
|
if (parent) { _set (d,'parent', parent); if (!d.kind) _set (d,'kind', _kind || 'element') }
|
|
11
|
-
if (d.kind === 'service') {
|
|
25
|
+
if (d.kind === 'service') {
|
|
26
|
+
for (let e in defs) if (e.startsWith(name+'.')) _set (defs[e],'_service',d)
|
|
27
|
+
_set (d,'model',x) //> required for getters like service.entities
|
|
28
|
+
}
|
|
12
29
|
else if (d.target) _set (d,'_target', _target(d.target) || _link (d.target,name,d))
|
|
13
30
|
else if (d.projection) _set (d,'query', {SELECT:d.projection})
|
|
14
31
|
else if (d.returns) _link (d.returns)
|
|
15
32
|
else if (d.items) _link (d.items)
|
|
16
|
-
for (let e in d.elements)
|
|
17
|
-
for (let a in d.actions)
|
|
18
|
-
for (let p in d.params)
|
|
33
|
+
for (let e in _iterable(d.elements)) _link (d.elements[e],e,d)
|
|
34
|
+
for (let a in _iterable(d.actions)) _link (d.actions[a],a,d,'action')
|
|
35
|
+
for (let p in _iterable(d.params)) _link (d.params[p],p,d,'param')
|
|
19
36
|
let p = ( //> determine the definition's prototype ...
|
|
20
37
|
d.type ? _typeof (d.type) || _resolve (d.type) :
|
|
21
|
-
d.query ? _infer (d.query, defs) ||
|
|
22
|
-
d.kind in _kinds ?
|
|
23
|
-
d.elements ? struct
|
|
24
|
-
d.items ? array
|
|
25
|
-
/* else: */ any
|
|
38
|
+
d.query ? _infer (d.query, defs) || types.entity :
|
|
39
|
+
d.kind in _kinds ? types[d.kind] :
|
|
40
|
+
d.elements ? types.struct :
|
|
41
|
+
d.items ? types.array :
|
|
42
|
+
/* else: */ types.any
|
|
26
43
|
)
|
|
27
44
|
if (d.kind === 'entity') {
|
|
28
45
|
if (p.actions && !d.actions) _set (d,'actions',undefined) //> don't propagate .actions
|
|
@@ -49,7 +66,6 @@ class LinkedCSN extends any {
|
|
|
49
66
|
if (!t) return
|
|
50
67
|
}
|
|
51
68
|
}}
|
|
52
|
-
return Object.setPrototypeOf (x, new.target.prototype)
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
*each (x, defs=this.definitions) {
|
|
@@ -79,39 +95,44 @@ class LinkedCSN extends any {
|
|
|
79
95
|
return this
|
|
80
96
|
}
|
|
81
97
|
|
|
82
|
-
childrenOf (x, filter=()=>true, defs = this.definitions) {
|
|
83
|
-
const
|
|
84
|
-
const
|
|
98
|
+
childrenOf (x, filter = ()=>true, defs = this.definitions) {
|
|
99
|
+
const children = namespace => !namespace ? children : this.childrenOf (namespace,filter)
|
|
100
|
+
const prefix = !x ? '' : typeof x === 'string' ? x+'.' : ((x = x.namespace || x.name)) ? x+'.' : ''
|
|
85
101
|
for (let fqn in defs) if (fqn.startsWith(prefix)) {
|
|
86
|
-
const d = defs[fqn]
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const _texts = defs [fqn.slice(0,-6)+'_texts']
|
|
90
|
-
if (_texts) _set (children, name.slice(0,-6)+'_texts', d) // REVISIT: last arg should be _texts but runtime tests fail with that
|
|
91
|
-
}
|
|
92
|
-
}
|
|
102
|
+
const d = defs[fqn]; if (!filter(d)) continue
|
|
103
|
+
else children[fqn.slice(prefix.length)] = d
|
|
104
|
+
if (d.is_entity && fqn.endsWith('.texts')) _set (children, fqn.slice(prefix.length,-6)+'_texts', d)
|
|
93
105
|
}
|
|
94
|
-
return
|
|
106
|
+
return _iterable (children)
|
|
95
107
|
}
|
|
96
108
|
|
|
97
|
-
get exports() { return this.set ('exports', this.childrenOf (this
|
|
98
|
-
get entities() { return this.set ('entities', this.childrenOf (this, d => d
|
|
99
|
-
get services() {
|
|
109
|
+
get exports() { return this.set ('exports', this.childrenOf (this)) }
|
|
110
|
+
get entities() { return this.set ('entities', this.childrenOf (this, d => d.is_entity)) }
|
|
111
|
+
get services() {
|
|
112
|
+
let srvs = this.all (d => d.is_service)
|
|
113
|
+
for (let s of srvs) Object.defineProperty (srvs, s.name, {value:s})
|
|
114
|
+
return this.set ('services', srvs)
|
|
115
|
+
}
|
|
100
116
|
}
|
|
101
117
|
|
|
102
118
|
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
119
|
+
const _iterable = defs => defs && Object.setPrototypeOf (defs, LinkedDefinitions.prototype)
|
|
120
|
+
const _unresolved = (x, unknown = types.any) => ({ name:x, __proto__:unknown, _unresolved:true })
|
|
121
|
+
const _builtin = x => types[x] || x.startsWith?.('cds.hana.') && types.any
|
|
122
|
+
const _infer = require('../ql/infer')
|
|
123
|
+
const _set = (o,p,v,e=false) => Object.defineProperty (o,p,{ value:v, enumerable:e, configurable:1, writable:1 })
|
|
107
124
|
const _own = (o,p) => { const pd = Reflect.getOwnPropertyDescriptor(o,p); return pd && pd.value }
|
|
108
125
|
const _is = x => {
|
|
109
126
|
if (typeof x === 'string') return x === 'any' ? ()=>true : d => d.is(x)
|
|
110
|
-
if (typeof x === 'function') return x.prototype
|
|
127
|
+
if (typeof x === 'function') return x.prototype?.is_linked ? d => d instanceof x : x
|
|
111
128
|
throw new Error ('invalid filter for model reflection: '+ x)
|
|
112
129
|
}
|
|
113
130
|
|
|
131
|
+
|
|
132
|
+
_set (LinkedCSN.prototype, 'set', any.prototype.set) //> inherit this.set() from any
|
|
133
|
+
LinkedCSN.prototype.set('is_linked', true)
|
|
134
|
+
any.prototype.set('is_linked', true)
|
|
135
|
+
|
|
136
|
+
|
|
114
137
|
/** @returns {LinkedCSN} */
|
|
115
|
-
module.exports = x => x
|
|
116
|
-
typeof x === 'string' ? require('../index').compile(x) : x
|
|
117
|
-
)
|
|
138
|
+
module.exports = Object.assign (x => x.is_linked ? x : new LinkedCSN (x), { LinkedCSN, LinkedDefinitions, types, classes })
|
package/lib/linked/types.js
CHANGED
|
@@ -1,116 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
CSN models itself. Besides the actual root types, a set of common and
|
|
4
|
-
recommended scalar types is added to builtin.types.
|
|
5
|
-
*/
|
|
6
|
-
const classes = Object.assign (
|
|
1
|
+
|
|
2
|
+
const classes = exports.classes = Object.assign (
|
|
7
3
|
require('./classes'),
|
|
8
4
|
require('./entities'),
|
|
9
5
|
)
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
string: {type:'scalar'},
|
|
18
|
-
number: {type:'scalar'},
|
|
19
|
-
boolean: {type:'scalar'},
|
|
20
|
-
date: {type:'scalar'},
|
|
21
|
-
array: {type:'type'},
|
|
22
|
-
struct: {type:'type'},
|
|
23
|
-
aspect: {type:'struct'},
|
|
24
|
-
entity: {type:'struct'},
|
|
25
|
-
event: {type:'struct'},
|
|
26
|
-
Association: {type:'type'},
|
|
27
|
-
Composition: {type:'Association'},
|
|
28
|
-
context: {},
|
|
29
|
-
service: {type:'context'},
|
|
30
|
-
$self: {}, //> to support polymorphic self links like in: action foo( self: [many] $self, ...)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Turns the given CSN definitions into linked definitions with classes.
|
|
35
|
-
* @type <T>(csn:T) => T
|
|
36
|
-
*/
|
|
37
|
-
function _roots (defs) {
|
|
38
|
-
const linked = { any: classes.any.prototype }
|
|
39
|
-
for (const t in defs) {
|
|
40
|
-
if (t in classes) linked[t] = classes[t].prototype
|
|
41
|
-
else {
|
|
42
|
-
const c = class extends classes[defs[t].type || 'any'] {}
|
|
43
|
-
classes[t] = Object.defineProperty (c, 'name', {value:t})
|
|
44
|
-
linked[t] = Object.defineProperty (c.prototype, 'name', {value:t})
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return linked
|
|
7
|
+
const protos = { $self: new classes.any } //> to support polymorphic self links like in: action foo( self: [many] $self, ...)
|
|
8
|
+
const types = exports.types = {__proto__:protos}
|
|
9
|
+
for (let k in classes) if (k !== 'LinkedDefinitions' && k !== 'mixins') {
|
|
10
|
+
const t = protos[k] = classes[k].prototype, k2 = 'cds.'+k
|
|
11
|
+
if (k < 'a') Object.defineProperty (t, 'name', {value:k})
|
|
12
|
+
if (k < 'a') Object.defineProperty (types[k2] = t, '_type', {value:k2})
|
|
48
13
|
}
|
|
49
14
|
|
|
50
15
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Boolean: {type:'boolean'},
|
|
57
|
-
Integer: {type:'number'},
|
|
58
|
-
UInt8: {type:'Integer'},
|
|
59
|
-
Int16: {type:'Integer'}, Integer16: {type:'Int16'},
|
|
60
|
-
Int32: {type:'Integer'}, Integer32: {type:'Int32'},
|
|
61
|
-
Int64: {type:'Integer'}, Integer64: {type:'Int64'},
|
|
62
|
-
Float: {type:'number'},
|
|
63
|
-
Double: {type:'Float'},
|
|
64
|
-
Decimal: {type:'Float'}, DecimalFloat: {type:'Decimal'},
|
|
65
|
-
Date: {type:'date'},
|
|
66
|
-
Time: {type:'date'},
|
|
67
|
-
DateTime: {type:'date'},
|
|
68
|
-
Timestamp: {type:'DateTime'},
|
|
69
|
-
Vector: {type:'Binary'},
|
|
16
|
+
Object.assign (protos, types.deprecated = {
|
|
17
|
+
'cds.DecimalFloat': Object.defineProperty (new classes.Decimal, '_type', { value:'cds.DecimalFloat' }),
|
|
18
|
+
'cds.Integer16': new classes.Int16,
|
|
19
|
+
'cds.Integer32': new classes.Int32,
|
|
20
|
+
'cds.Integer64': new classes.Int64,
|
|
70
21
|
})
|
|
71
22
|
|
|
72
|
-
/**
|
|
73
|
-
* Link all definitions, essentially by: d.__proto__ = resolved (d.type),
|
|
74
|
-
* and prefixes all common types with a namespace 'cds'.
|
|
75
|
-
* @type <T>(csn:T) => T & roots
|
|
76
|
-
*/
|
|
77
|
-
function _common (defs) {
|
|
78
|
-
const prefixed = {__proto__:defs}
|
|
79
|
-
for (let [name,d] of Object.entries(defs)) {
|
|
80
|
-
defs[name] = Object.defineProperty({ ...d, __proto__: defs[d.type] }, 'name', {value:name})
|
|
81
|
-
Object.defineProperty (prefixed['cds.'+name] = defs[name], '_type', {value:'cds.'+name})
|
|
82
|
-
}
|
|
83
|
-
for (let name of ['Association','Composition']) {
|
|
84
|
-
Object.defineProperty (prefixed['cds.'+name] = defs[name], '_type', {value:'cds.'+name})
|
|
85
|
-
}
|
|
86
|
-
return prefixed
|
|
87
|
-
}
|
|
88
23
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
* Time.now() //> 14:43:18
|
|
97
|
-
* Date.today() //> 2023-02-10
|
|
98
|
-
* ```
|
|
99
|
-
*/
|
|
100
|
-
function _add_convenience_functions(){
|
|
101
|
-
Object.defineProperties (types.Date, {
|
|
102
|
-
today: { value: ()=> (new Date).toISOString().slice(0,10) },
|
|
103
|
-
now: { value: ()=> (new Date).toISOString() },
|
|
104
|
-
})
|
|
24
|
+
protos.service.set('is_service',true)
|
|
25
|
+
protos.struct.set('is_struct',true)
|
|
26
|
+
protos.entity.set('is_entity',true)
|
|
27
|
+
protos.Association.set('isAssociation',true)
|
|
28
|
+
protos.Composition.set('isComposition',true)
|
|
29
|
+
protos.UUID.set('isUUID',true)
|
|
30
|
+
protos.UUID.set('length',36)
|
|
105
31
|
|
|
106
|
-
Object.defineProperties (types.Time, {
|
|
107
|
-
now: { value: ()=> (new Date).toISOString().slice(11,19) },
|
|
108
|
-
})
|
|
109
32
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Adds convenience functions which can be used like that:
|
|
35
|
+
* ```js
|
|
36
|
+
* var { Date, Time, DateTime } = cds.builtin.types
|
|
37
|
+
* DateTime.now() //> 2023-02-10T14:41:36.218Z
|
|
38
|
+
* Date.now() //> 2023-02-10T14:41:36.218Z
|
|
39
|
+
* Time.now() //> 14:43:18
|
|
40
|
+
* Date.today() //> 2023-02-10
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function _add_convenience_functions(){
|
|
44
|
+
Object.defineProperties (types.Date, {
|
|
45
|
+
today: { value: ()=> (new Date).toISOString().slice(0,10) },
|
|
46
|
+
now: { value: ()=> (new Date).toISOString() },
|
|
47
|
+
})
|
|
48
|
+
Object.defineProperties (types.Time, {
|
|
49
|
+
now: { value: ()=> (new Date).toISOString().slice(11,19) },
|
|
50
|
+
})
|
|
51
|
+
Object.defineProperties (types.DateTime, {
|
|
52
|
+
now: { value: ()=> (new Date).toISOString() },
|
|
53
|
+
})
|
|
54
|
+
}; _add_convenience_functions()
|
package/lib/log/format/json.js
CHANGED
|
@@ -74,9 +74,13 @@ module.exports = function format(module, level, ...args) {
|
|
|
74
74
|
const err = args.shift()
|
|
75
75
|
toLog.msg = `${toLog.msg ? toLog.msg + ' ' : ''}${err.message}`
|
|
76
76
|
if (typeof err.stack === 'string' && !_is4xx(err)) toLog.stacktrace = err.stack.split(/\s*\r?\n\s*/)
|
|
77
|
-
if (Array.isArray(err.details))
|
|
78
|
-
for (const d of err.details)
|
|
77
|
+
if (Array.isArray(err.details)) {
|
|
78
|
+
for (const d of err.details) {
|
|
79
|
+
// preserve message property through stringification
|
|
80
|
+
if (d.message) Object.defineProperty(d, 'message', { value: d.message, enumerable: true })
|
|
79
81
|
if (typeof d.stack === 'string' && !_is4xx(d)) d.stacktrace = d.stack.split(/\s*\r?\n\s*/)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
80
84
|
Object.assign(toLog, err, { level: toLog.level })
|
|
81
85
|
}
|
|
82
86
|
|
package/lib/ql/Whereable.js
CHANGED
|
@@ -44,9 +44,9 @@ class Query extends require('./Query') {
|
|
|
44
44
|
if (typeof key !== 'object') key = { [Object.keys(this._target.keys||{ID:1})[0]]: key }
|
|
45
45
|
if (this.SELECT) this.SELECT.one = true
|
|
46
46
|
if (cds.env.features.keys_into_where) return this.where(key)
|
|
47
|
-
if (this.UPDATE) { this.UPDATE.entity = { ref: [{ id: this.UPDATE.entity, where: predicate4([key]) }] }; return this }
|
|
48
|
-
if (this.SELECT) { this.SELECT.from.ref[this.SELECT.from.ref.length-1] = { id: this.SELECT.from.ref
|
|
49
|
-
if (this.DELETE) { this.DELETE.from = { ref: [{ id: this.DELETE.from, where: predicate4([key]) }] }; return this }
|
|
47
|
+
if (this.UPDATE) { this.UPDATE.entity = { ref: [{ id: cds.env.ql.quirks_mode ? this.UPDATE.entity : this.UPDATE.entity.ref.at(-1), where: predicate4([key]) }] }; return this }
|
|
48
|
+
if (this.SELECT) { this.SELECT.from.ref[this.SELECT.from.ref.length-1] = { id: this.SELECT.from.ref.at(-1), where: predicate4([key]) }; return this }
|
|
49
|
+
if (this.DELETE) { this.DELETE.from = { ref: [{ id: cds.env.ql.quirks_mode ? this.DELETE.from : this.DELETE.from.ref.at(-1), where: predicate4([key]) }] }; return this }
|
|
50
50
|
return this.where(key)
|
|
51
51
|
}
|
|
52
52
|
}
|
package/lib/req/context.js
CHANGED
|
@@ -3,6 +3,10 @@ const async_events = { succeeded:1, failed:1, done:1, commit:1 }
|
|
|
3
3
|
const req_locale = require('./locale')
|
|
4
4
|
const { EventEmitter } = require('events')
|
|
5
5
|
|
|
6
|
+
// getter functions extracted to show deprecation warning only once
|
|
7
|
+
const _getTenant = req => req.tenant
|
|
8
|
+
const _getLocale = req => req.locale
|
|
9
|
+
|
|
6
10
|
/**
|
|
7
11
|
* This is the base class for `cds.Events` and `cds.Requests`,
|
|
8
12
|
* providing the transaction context nature to all instances.
|
|
@@ -95,11 +99,11 @@ class EventContext {
|
|
|
95
99
|
if (pd?.value) this[p] = pd.value
|
|
96
100
|
}
|
|
97
101
|
let user = u instanceof cds.User ? Object.create(u,{
|
|
98
|
-
tenant: {get:()=> cds.utils.deprecated (
|
|
99
|
-
locale: {get:()=> cds.utils.deprecated (
|
|
102
|
+
tenant: {get:()=> cds.utils.deprecated (_getTenant, {kind: 'Property', old: 'req.user.tenant', use: 'req.tenant'})(this)},
|
|
103
|
+
locale: {get:()=> cds.utils.deprecated (_getLocale, {kind: 'Property', old: 'req.user.locale', use: 'req.locale'})(this)},
|
|
100
104
|
}) : Object.defineProperties (new cds.User(u), {
|
|
101
|
-
tenant: {get:()=> cds.utils.deprecated (
|
|
102
|
-
locale: {get:()=> cds.utils.deprecated (
|
|
105
|
+
tenant: {get:()=> cds.utils.deprecated (_getTenant, {kind: 'Property', old: 'req.user.tenant', use: 'req.tenant'})(this)},
|
|
106
|
+
locale: {get:()=> cds.utils.deprecated (_getLocale, {kind: 'Property', old: 'req.user.locale', use: 'req.locale'})(this)},
|
|
103
107
|
})
|
|
104
108
|
super.user = user
|
|
105
109
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const express = require('express') // eslint-disable-line cds/no-missing-dependencies
|
|
2
2
|
const cds = require('../../index')
|
|
3
|
+
const { inspect } = require('util')
|
|
3
4
|
|
|
4
5
|
class HCQLAdapter extends require('./http') {
|
|
5
6
|
|
|
@@ -34,7 +35,7 @@ class HCQLAdapter extends require('./http') {
|
|
|
34
35
|
*/
|
|
35
36
|
.use((req, res, next) => {
|
|
36
37
|
let q = this.query4(req)
|
|
37
|
-
this.
|
|
38
|
+
this.logger.info (req.method, decodeURIComponent(req.originalUrl), inspect(q,{colors:true,depth:11}))
|
|
38
39
|
return srv.run(q).then(r => res.json(r)).catch(next)
|
|
39
40
|
})
|
|
40
41
|
}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
const express = require('express') // eslint-disable-line cds/no-missing-dependencies
|
|
2
2
|
const cds = require('../../index')
|
|
3
|
-
const { inspect } = require('util')
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
module.exports = class HttpAdapter {
|
|
7
6
|
|
|
8
7
|
constructor (srv) {
|
|
9
8
|
this.kind = this.constructor.name.replace(/Adapter$/,'').toLowerCase()
|
|
10
|
-
this.
|
|
9
|
+
this.logger = cds.log (this.kind)
|
|
11
10
|
return this.router4 (srv)
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
router4 (srv) {
|
|
15
14
|
return express.Router()
|
|
15
|
+
.use(function req_logger(req,res,next) {
|
|
16
|
+
// this.log expected to be implemented by subclass
|
|
17
|
+
this?.log?.(req)
|
|
18
|
+
next()
|
|
19
|
+
}.bind(this))
|
|
16
20
|
.use(this.early_access_check4(srv))
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
log (req, cqn) {
|
|
20
|
-
this.log.info (req.method, decodeURIComponent(req.originalUrl), inspect(cqn,{colors:true,depth:11}))
|
|
21
|
-
}
|
|
22
|
-
|
|
23
23
|
/** Does early checks on required roles to reject early */
|
|
24
24
|
early_access_check4 (srv) {
|
|
25
25
|
|
|
26
26
|
// Resolve required roles statically once....
|
|
27
27
|
const d = srv.definition
|
|
28
28
|
const roles = d['@requires'] || d['@restrict']?.map(r => r.to).flat()
|
|
29
|
-
|| cds.env.requires.auth?.restrict_all_services !== false && ['authenticated-user']
|
|
29
|
+
|| cds.env.requires.auth?.restrict_all_services !== false && process.env.NODE_ENV === 'production' && ['authenticated-user']
|
|
30
30
|
|
|
31
31
|
// ... and return a handler function accordingly -> PROBLEM: Extensibility
|
|
32
32
|
if (!roles) return (req, res, next) => next() //> no handlers required
|
|
@@ -50,6 +50,12 @@ class Protocols {
|
|
|
50
50
|
const cached = srv._adapters ??= {}
|
|
51
51
|
let n = 0
|
|
52
52
|
|
|
53
|
+
if (app) {
|
|
54
|
+
// disable express etag handling
|
|
55
|
+
app.disable?.('etag')
|
|
56
|
+
// app.disable('x-powered-by')
|
|
57
|
+
}
|
|
58
|
+
|
|
53
59
|
for (let { kind, path } of endpoints) {
|
|
54
60
|
|
|
55
61
|
// construct adapter instance from resolved implementation
|
|
@@ -99,8 +105,8 @@ class Protocols {
|
|
|
99
105
|
/**
|
|
100
106
|
* Returns the endpoints for the given instance of cds.Service.
|
|
101
107
|
* Used in this.serve() and the outcome stored in srv.endpoints property.
|
|
102
|
-
* IMPORTANT: Currently only used internally in this module
|
|
103
|
-
* that way -> don't use anywhere else.
|
|
108
|
+
* IMPORTANT: Currently only used internally in this module, e.g. serviceinfo,
|
|
109
|
+
* and should stay that way -> don't use anywhere else.
|
|
104
110
|
* @returns {{ kind:string, path:string }[]} Array of { kind, path } objects
|
|
105
111
|
*/
|
|
106
112
|
endpoints4 (srv, o = srv.options) {
|
|
@@ -144,14 +150,14 @@ class Protocols {
|
|
|
144
150
|
|
|
145
151
|
/**
|
|
146
152
|
* For compatibility with old @sap/cds-dk versions <= 7.4.0
|
|
147
|
-
* @deprecated Use def.
|
|
153
|
+
* @deprecated Use def.protocols instead
|
|
148
154
|
*/
|
|
149
155
|
protocol4 (def) {
|
|
150
|
-
return def.
|
|
156
|
+
return def.protocols?.odata ? ['odata'] : []
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
/**
|
|
154
|
-
* Rarely used, e.g., by compile.to.
|
|
160
|
+
* Rarely used, e.g., by compile.to.openapi:
|
|
155
161
|
* NOT PUBLIC API, hence not documented.
|
|
156
162
|
*/
|
|
157
163
|
path4 (srv,o) {
|
|
@@ -163,14 +169,26 @@ class Protocols {
|
|
|
163
169
|
* Internal modules may use this to determine if the service is configured to serve OData.
|
|
164
170
|
* NOT PUBLIC API, hence not documented.
|
|
165
171
|
*/
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
for (def) {
|
|
173
|
+
const protocols={}; let any
|
|
174
|
+
|
|
175
|
+
// check @protocol annotation -> deprecated, only for 'none'
|
|
176
|
+
const anno = def['@protocol']
|
|
177
|
+
|
|
178
|
+
if (anno === 'none') return protocols
|
|
179
|
+
if (typeof anno === 'string') any = protocols [anno] = 1
|
|
180
|
+
else if (anno) for (let p of anno) any = protocols[p.kind||p] = 1
|
|
181
|
+
|
|
182
|
+
// @odata, @rest, ... annotations -> preferred
|
|
183
|
+
else for (let p in this) if (def['@'+p] || def['@protocol.'+p]) any = protocols[p] = 1
|
|
184
|
+
|
|
185
|
+
// add default protocol, if none is annotated
|
|
186
|
+
if (!any) protocols[this.default] = 1
|
|
187
|
+
|
|
188
|
+
// allow for simple 'odata' in srv.protocols checks
|
|
189
|
+
if (protocols['odata-v4']) protocols.odata = 1
|
|
190
|
+
|
|
191
|
+
return protocols
|
|
174
192
|
}
|
|
175
193
|
}
|
|
176
194
|
|