@sap/cds 7.7.3 → 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.
Files changed (75) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/lib/auth/ias-auth.js +5 -3
  3. package/lib/auth/jwt-auth.js +4 -2
  4. package/lib/compile/cdsc.js +0 -10
  5. package/lib/compile/for/java.js +9 -5
  6. package/lib/compile/for/lean_drafts.js +1 -1
  7. package/lib/compile/to/edm.js +2 -1
  8. package/lib/compile/to/sql.js +0 -21
  9. package/lib/compile/to/srvinfo.js +13 -4
  10. package/lib/dbs/cds-deploy.js +7 -7
  11. package/lib/env/cds-requires.js +6 -0
  12. package/lib/index.js +4 -3
  13. package/lib/linked/classes.js +151 -88
  14. package/lib/linked/entities.js +27 -23
  15. package/lib/linked/models.js +57 -36
  16. package/lib/linked/types.js +42 -104
  17. package/lib/ql/Whereable.js +3 -3
  18. package/lib/req/context.js +8 -4
  19. package/lib/srv/protocols/hcql.js +2 -1
  20. package/lib/srv/protocols/http.js +7 -7
  21. package/lib/srv/protocols/index.js +31 -13
  22. package/lib/srv/protocols/odata-v4.js +79 -58
  23. package/lib/srv/srv-api.js +7 -6
  24. package/lib/srv/srv-dispatch.js +1 -12
  25. package/lib/srv/srv-tx.js +9 -13
  26. package/lib/utils/cds-utils.js +6 -5
  27. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +11 -8
  28. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +21 -12
  29. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -3
  30. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -7
  31. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +5 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -1
  33. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +3 -7
  34. package/libx/_runtime/cds-services/services/utils/columns.js +6 -3
  35. package/libx/_runtime/cds.js +0 -13
  36. package/libx/_runtime/common/generic/input.js +3 -0
  37. package/libx/_runtime/common/generic/sorting.js +8 -6
  38. package/libx/_runtime/common/i18n/messages.properties +1 -0
  39. package/libx/_runtime/common/utils/cqn.js +5 -0
  40. package/libx/_runtime/common/utils/foreignKeyPropagations.js +7 -1
  41. package/libx/_runtime/common/utils/keys.js +2 -2
  42. package/libx/_runtime/common/utils/resolveView.js +2 -1
  43. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
  44. package/libx/_runtime/common/utils/stream.js +0 -10
  45. package/libx/_runtime/common/utils/template.js +20 -35
  46. package/libx/_runtime/db/Service.js +5 -1
  47. package/libx/_runtime/db/utils/columns.js +1 -1
  48. package/libx/_runtime/fiori/lean-draft.js +14 -2
  49. package/libx/_runtime/messaging/Outbox.js +7 -5
  50. package/libx/_runtime/messaging/kafka.js +266 -0
  51. package/libx/_runtime/messaging/service.js +7 -5
  52. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
  53. package/libx/common/assert/validation.js +1 -1
  54. package/libx/odata/index.js +8 -2
  55. package/libx/odata/middleware/batch.js +340 -0
  56. package/libx/odata/middleware/create.js +43 -46
  57. package/libx/odata/middleware/delete.js +27 -15
  58. package/libx/odata/middleware/error.js +6 -5
  59. package/libx/odata/middleware/metadata.js +16 -15
  60. package/libx/odata/middleware/operation.js +107 -59
  61. package/libx/odata/middleware/parse.js +15 -7
  62. package/libx/odata/middleware/read.js +150 -24
  63. package/libx/odata/middleware/service-document.js +17 -6
  64. package/libx/odata/middleware/stream.js +34 -17
  65. package/libx/odata/middleware/update.js +123 -87
  66. package/libx/odata/parse/afterburner.js +131 -28
  67. package/libx/odata/parse/cqn2odata.js +1 -1
  68. package/libx/odata/parse/grammar.peggy +4 -5
  69. package/libx/odata/parse/multipartToJson.js +163 -0
  70. package/libx/odata/parse/parser.js +1 -1
  71. package/libx/odata/utils/index.js +29 -47
  72. package/libx/odata/utils/path.js +72 -0
  73. package/libx/odata/utils/result.js +123 -20
  74. package/package.json +1 -1
  75. package/server.js +4 -0
@@ -1,40 +1,47 @@
1
- const cds = require('../')
2
- const { struct } = require ('./classes')
1
+ const { LinkedDefinitions, struct, type } = require ('./classes')
2
+ const _is_cached = Symbol('is cached')
3
+ const _derived = Symbol('derived')
3
4
 
4
- class entity extends struct {
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.own('_keys') || this.set('_keys', this._elements (e => e.key))
9
+ return this._elements ('key')
8
10
  }
9
11
  get associations() {
10
- return this.own('_associations') || this.set('_associations', this._elements (e => e instanceof Association))
12
+ return this._elements ('isAssociation')
11
13
  }
12
14
  get compositions() {
13
- return this.own('_compositions') || this.set('_compositions', this._elements (e => e instanceof Composition))
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 struct {
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[k.ref.length-1]
52
- foreignKeys [as] = el.name === as ? el : { __proto__:el, name:as }
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 super.dataIn (d,prefix)
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 }
@@ -1,28 +1,45 @@
1
- const { types, classes:{ service, entity, action, event, any, struct, array, context, annotation } } = require('./types')
2
- const _kinds = { annotation, context, service, action, event, entity, view:entity }
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 extends any {
13
+ class LinkedCSN {
5
14
 
6
15
  constructor(x) {
7
- const defs = x.definitions; for (let d in defs) _link (defs[d],d)
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') { for (let e in defs) if (e.startsWith(name+'.')) _set (defs[e],'_service',d) }
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) _link (d.elements[e],e,d)
17
- for (let a in d.actions) _link (d.actions[a],a,d,'action')
18
- for (let p in d.params) _link (d.params[p],p,d,'param')
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) || _not_inferred :
22
- d.kind in _kinds ? _kinds[d.kind].prototype :
23
- d.elements ? struct.prototype :
24
- d.items ? array.prototype :
25
- /* else: */ any.prototype
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 ns = !x ? false : typeof x === 'string' ? x : x.namespace || x.name, prefix = ns ? ns+'.' : ''
84
- const children = (ns) => !ns ? children : this.childrenOf (ns,filter)
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], name = fqn.slice(prefix.length)
87
- if (filter(d,name)) { children[name] = d
88
- if (d.is('entity') && fqn.endsWith('.texts')) { // REVISIT: to bridge transition to cv2 only
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 _set (children, Symbol.iterator, function*(){ for (let e in this) yield this[e] })
106
+ return _iterable (children)
95
107
  }
96
108
 
97
- get exports() { return this.set ('exports', this.childrenOf (this, (_,rn)=>!rn.includes('.'))) }
98
- get entities() { return this.set ('entities', this.childrenOf (this, d => d instanceof entity)) }
99
- get services() { return this.set ('services', this.all('service')) }
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 _unresolved = (x,unknown=any) => ({ name:x, __proto__:unknown.prototype, _unresolved:true })
104
- const _builtin = x => types[x] || typeof x === 'string' && x.startsWith('cds.hana.') && any.prototype
105
- const _infer = require('../ql/infer'), _not_inferred = _unresolved('<query>',entity)
106
- const _set = (o,p,v) => Object.defineProperty (o,p,{value:v,enumerable:false,configurable:1,writable:1})
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 instanceof any ? d => d instanceof x : x
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 instanceof any ? x : new LinkedCSN (
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 })
@@ -1,116 +1,54 @@
1
- /*
2
- This is the inner core of cds: its type system, bootstrapped from in-place
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
- /** Type system roots -> can be used with instanceof */
13
- const roots = _roots ({
14
- any: classes.any.prototype,
15
- type: {},
16
- scalar: {type:'type'},
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
- /** Construct builtin.types as dictionary of all roots and common types */
52
- const types = _common ({ __proto__: roots,
53
- UUID: {type:'string',length:36,isUUID:true},
54
- String: {type:'string'}, LargeString: {type:'String'},
55
- Binary: {type:'string'}, LargeBinary: {type:'Binary'},
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
- * Adds convenience functions which can be used like that:
92
- * ```js
93
- * var { Date, Time, DateTime } = cds.builtin.types
94
- * DateTime.now() //> 2023-02-10T14:41:36.218Z
95
- * Date.now() //> 2023-02-10T14:41:36.218Z
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
- Object.defineProperties (types.DateTime, {
111
- now: { value: ()=> (new Date).toISOString() },
112
- })
113
- }
114
- )()
115
-
116
- module.exports = { types, classes }
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()
@@ -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[this.SELECT.from.ref.length-1], where: predicate4([key]) }; return this }
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
  }
@@ -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 (() => this.tenant, {kind: 'Property', old: 'req.user.tenant', use: 'req.tenant'})()},
99
- locale: {get:()=> cds.utils.deprecated (() => this.locale, {kind: 'Property', old: 'req.user.locale', use: 'req.locale'})()},
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 (() => this.tenant, {kind: 'Property', old: 'req.user.tenant', use: 'req.tenant'})()},
102
- locale: {get:()=> cds.utils.deprecated (() => this.locale, {kind: 'Property', old: 'req.user.locale', use: 'req.locale'})()},
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.log(req,q)
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.log = cds.log (this.kind)
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 and should stay
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._serves_odata instead
153
+ * @deprecated Use def.protocols instead
148
154
  */
149
155
  protocol4 (def) {
150
- return def._serves_odata ? ['odata'] : []
156
+ return def.protocols?.odata ? ['odata'] : []
151
157
  }
152
158
 
153
159
  /**
154
- * Rarely used, e.g., by compile.to.srvinfo, and by compile.to.openapi:
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
- _serves_odata (def) {
167
- const a = def['@protocol']
168
- const vals = {odata: 1, 'odata-v4': 1}
169
- if (a) return typeof a === 'string' ? a in vals : a.some(p => (p.kind||p) in vals)
170
- if (def['@odata']) return true
171
- if (def['@rest']) return false
172
- for (let p in this) if (def['@'+p] || def['@protocol.'+p]) return false
173
- return this.default === 'odata'
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