@sap/cds 6.0.4 → 6.1.2

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 (139) hide show
  1. package/CHANGELOG.md +180 -18
  2. package/apis/cds.d.ts +11 -7
  3. package/apis/log.d.ts +124 -0
  4. package/apis/ql.d.ts +72 -15
  5. package/apis/services.d.ts +13 -2
  6. package/bin/build/buildTaskHandler.js +5 -2
  7. package/bin/build/constants.js +4 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
  9. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
  10. package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
  11. package/bin/build/provider/buildTaskProviderInternal.js +22 -14
  12. package/bin/build/provider/hana/index.js +12 -9
  13. package/bin/build/provider/java/index.js +18 -8
  14. package/bin/build/provider/mtx/index.js +7 -4
  15. package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
  16. package/bin/build/provider/mtx-extension/index.js +57 -0
  17. package/bin/build/provider/mtx-sidecar/index.js +46 -18
  18. package/bin/build/provider/nodejs/index.js +34 -13
  19. package/bin/deploy/to-hana/cfUtil.js +7 -2
  20. package/bin/deploy/to-hana/hana.js +20 -25
  21. package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
  22. package/bin/serve.js +7 -4
  23. package/lib/compile/{index.js → cds-compile.js} +0 -0
  24. package/lib/compile/extend.js +15 -5
  25. package/lib/compile/minify.js +1 -15
  26. package/lib/compile/parse.js +1 -1
  27. package/lib/compile/resolve.js +2 -2
  28. package/lib/compile/to/srvinfo.js +6 -4
  29. package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
  30. package/lib/env/{index.js → cds-env.js} +1 -17
  31. package/lib/env/{requires.js → cds-requires.js} +24 -3
  32. package/lib/env/defaults.js +7 -1
  33. package/lib/env/schemas/cds-package.json +11 -0
  34. package/lib/env/schemas/cds-rc.json +614 -0
  35. package/lib/index.js +19 -16
  36. package/lib/log/{errors.js → cds-error.js} +1 -1
  37. package/lib/log/{index.js → cds-log.js} +0 -0
  38. package/lib/log/format/kibana.js +19 -1
  39. package/lib/ql/Query.js +9 -3
  40. package/lib/ql/SELECT.js +2 -2
  41. package/lib/ql/UPDATE.js +2 -2
  42. package/lib/ql/{index.js → cds-ql.js} +4 -10
  43. package/lib/req/context.js +49 -17
  44. package/lib/req/locale.js +5 -1
  45. package/lib/{serve → srv}/adapters.js +23 -19
  46. package/lib/{connect → srv}/bindings.js +0 -0
  47. package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
  48. package/lib/{serve/index.js → srv/cds-serve.js} +0 -0
  49. package/lib/{serve → srv}/factory.js +1 -1
  50. package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
  51. package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
  52. package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
  53. package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
  54. package/lib/srv/srv-models.js +207 -0
  55. package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
  56. package/lib/utils/{tests.js → cds-test.js} +2 -2
  57. package/lib/utils/cds-utils.js +146 -0
  58. package/lib/utils/index.js +2 -145
  59. package/lib/utils/jest.js +43 -0
  60. package/lib/utils/resources/index.js +15 -25
  61. package/lib/utils/resources/tar.js +18 -41
  62. package/libx/_runtime/auth/index.js +14 -11
  63. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
  64. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +3 -5
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
  74. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
  75. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
  76. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
  77. package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
  78. package/libx/_runtime/cds-services/util/errors.js +1 -29
  79. package/libx/_runtime/common/i18n/messages.properties +2 -1
  80. package/libx/_runtime/common/perf/index.js +10 -15
  81. package/libx/_runtime/common/utils/binary.js +3 -4
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
  83. package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
  84. package/libx/_runtime/common/utils/keys.js +14 -6
  85. package/libx/_runtime/common/utils/resolveView.js +1 -1
  86. package/libx/_runtime/common/utils/template.js +1 -1
  87. package/libx/_runtime/db/Service.js +2 -14
  88. package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
  89. package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
  90. package/libx/_runtime/db/generic/input.js +8 -1
  91. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  92. package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
  93. package/libx/_runtime/extensibility/activate.js +47 -47
  94. package/libx/_runtime/extensibility/add.js +22 -13
  95. package/libx/_runtime/extensibility/addExtension.js +17 -13
  96. package/libx/_runtime/extensibility/defaults.js +25 -30
  97. package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
  98. package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
  99. package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
  100. package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
  101. package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
  102. package/libx/_runtime/extensibility/linter.js +32 -0
  103. package/libx/_runtime/extensibility/push.js +77 -20
  104. package/libx/_runtime/extensibility/service.js +29 -12
  105. package/libx/_runtime/extensibility/token.js +57 -0
  106. package/libx/_runtime/extensibility/utils.js +8 -6
  107. package/libx/_runtime/extensibility/validation.js +6 -9
  108. package/libx/_runtime/fiori/generic/new.js +0 -11
  109. package/libx/_runtime/fiori/utils/where.js +1 -1
  110. package/libx/_runtime/hana/Service.js +0 -1
  111. package/libx/_runtime/hana/conversion.js +12 -1
  112. package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
  113. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
  114. package/libx/_runtime/hana/pool.js +6 -10
  115. package/libx/_runtime/hana/search2Contains.js +0 -5
  116. package/libx/_runtime/hana/search2cqn4sql.js +1 -0
  117. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  118. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
  119. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  120. package/libx/_runtime/messaging/service.js +11 -6
  121. package/libx/_runtime/remote/utils/data.js +5 -0
  122. package/libx/_runtime/sqlite/Service.js +7 -6
  123. package/libx/_runtime/sqlite/execute.js +41 -28
  124. package/libx/odata/afterburner.js +79 -2
  125. package/libx/odata/cqn2odata.js +15 -9
  126. package/libx/odata/grammar.pegjs +157 -76
  127. package/libx/odata/index.js +9 -3
  128. package/libx/odata/parser.js +1 -1
  129. package/libx/odata/utils.js +39 -5
  130. package/libx/rest/RestAdapter.js +3 -7
  131. package/libx/rest/middleware/delete.js +4 -5
  132. package/libx/rest/middleware/parse.js +3 -2
  133. package/package.json +3 -3
  134. package/server.js +1 -1
  135. package/srv/extensibility-service.cds +6 -3
  136. package/srv/model-provider.cds +3 -1
  137. package/srv/model-provider.js +86 -106
  138. package/srv/mtx.js +7 -1
  139. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
@@ -60,7 +60,7 @@ exports.expected = ([,type], arg) => {
60
60
  //
61
61
 
62
62
  exports._duplicate_cds = (...locations) => {
63
- const { local } = require('../utils')
63
+ const { local } = require('../utils/cds-utils')
64
64
  throw error `Duplicate @sap/cds/common!
65
65
 
66
66
  There are duplicate versions of @sap/cds loaded from these locations:
File without changes
@@ -56,6 +56,24 @@ module.exports = (module, level, ...args) => {
56
56
  if (cf.length) toLog['#cf'] = { string: cf }
57
57
  }
58
58
 
59
+ const getCircularReplacer = () => {
60
+ const seen = new WeakSet()
61
+ return (key, value) => {
62
+ if (typeof value === "object" && value !== null) {
63
+ if (seen.has(value)) {
64
+ return 'cyclic'
65
+ }
66
+ seen.add(value)
67
+ }
68
+ return value
69
+ }
70
+ }
71
+
59
72
  // return array with the stringified toLog (to avoid multiple log lines) as the sole element
60
- return [util.inspect(toLog, { compact: true, breakLength: Infinity, depth: null })]
73
+ try {
74
+ return [JSON.stringify(toLog)]
75
+ } catch (e) {
76
+ // try again with removed circular references
77
+ return [JSON.stringify(toLog, getCircularReplacer())]
78
+ }
61
79
  }
package/lib/ql/Query.js CHANGED
@@ -1,7 +1,8 @@
1
+ const { AsyncResource } = require('async_hooks')
1
2
  const { inspect } = require('util')
2
3
  const cds = require('../index')
3
4
 
4
- module.exports = class Query {
5
+ class Query {
5
6
 
6
7
  constructor(_={}) { this[this.cmd] = _ }
7
8
 
@@ -16,8 +17,10 @@ module.exports = class Query {
16
17
  }
17
18
 
18
19
  /** Turns all queries into Thenables which execute with primary db by default */
19
- then (r,e) {
20
- return (this._srv || cds.db) .run (this) .then (r,e)
20
+ get then() {
21
+ const srv = this._srv || cds.db || cds.error `Can't execute query as no primary database is connected.`
22
+ const q = new AsyncResource('await cds.query')
23
+ return (r,e) => q.runInAsyncScope (srv.run, srv, this) .then (r,e)
21
24
  }
22
25
 
23
26
  /** Beautifies output in REPL */
@@ -87,3 +90,6 @@ const _target4 = (target, arg2) => target && (
87
90
  )
88
91
 
89
92
  const _name = cds.env.sql.names === 'quoted' ? n =>`"${n}"` : n => n.replace(/[.:]/g,'_')
93
+
94
+
95
+ module.exports = Query
package/lib/ql/SELECT.js CHANGED
@@ -94,7 +94,7 @@ module.exports = class SELECT extends Whereable {
94
94
  return Object.defineProperty(this, '_where_or_having', { value: 'on', configurable: true })
95
95
  }
96
96
  on (...args) {
97
- if (!this.SELECT.from || !this.SELECT.from.join)
97
+ if (!this.SELECT.from || !this.SELECT.from.join || !this.SELECT.from.args) // `join` can also be a function, e.g. in SELECT.from(SELECT.from('foo'))
98
98
  throw new Error(`Invalid call of "SELECT.on()" without prior call of "SELECT.join()"`)
99
99
  return this._where (args,'and','on')
100
100
  }
@@ -116,7 +116,7 @@ module.exports = class SELECT extends Whereable {
116
116
 
117
117
  limit (rows, offset) {
118
118
  if (is_number(rows) || rows) this.SELECT.limit = rows.rows ? rows : { rows: {val:rows} }
119
- if (is_number(offset)) this.SELECT.limit.offset = { val: offset }
119
+ if (is_number(offset)) (this.SELECT.limit = (this.SELECT.limit || {})) .offset = { val: offset }
120
120
  return this
121
121
  }
122
122
 
package/lib/ql/UPDATE.js CHANGED
@@ -54,8 +54,8 @@ module.exports = class UPDATE extends Whereable {
54
54
  const o = args[0]
55
55
  for (let col in o) {
56
56
  let op = '=', v = o[col]
57
- if (typeof v === 'object' && !(v === null || v.map || v.pipe || v instanceof Buffer || v instanceof Date)) {
58
- let o = Object.keys(v)[0] || this._expected `${{v}} to be an object with an operator as single key`
57
+ if (typeof v === 'object' && !(v === null || v.map || v.pipe || v instanceof Buffer || v instanceof Date || v instanceof RegExp)) {
58
+ let o = Object.keys(v)[0] //|| this._expected `${{v}} to be an object with an operator as single key`
59
59
  if (o in operators) v = v[op=o]
60
60
  }
61
61
  _add (this, col, op, v && (v.val !== undefined || v.ref || v.xpr || v.func || v.SELECT) ? v : {val:v})
@@ -1,4 +1,5 @@
1
1
  const cds = require('../index')
2
+ const Query = require('./Query')
2
3
  require = path => { // eslint-disable-line no-global-assign
3
4
  const clazz = module.require (path); if (!clazz._api) return clazz
4
5
  Object.defineProperty (clazz.prototype, 'cmd', { value: path.match(/\w+$/)[0] })
@@ -6,12 +7,14 @@ require = path => { // eslint-disable-line no-global-assign
6
7
  }
7
8
 
8
9
  module.exports = Object.assign (_deprecated_srv_ql, { cdr: true,
10
+ Query,
9
11
  SELECT: require('./SELECT'),
10
12
  INSERT: require('./INSERT'),
11
13
  UPDATE: require('./UPDATE'),
12
14
  DELETE: require('./DELETE'),
13
15
  CREATE: require('./CREATE'),
14
16
  DROP: require('./DROP'),
17
+ clone(q) { return Query.prototype.clone.call(q) }
15
18
  })
16
19
 
17
20
  function _deprecated_srv_ql() { // eslint-disable-next-line no-console
@@ -22,18 +25,9 @@ function _deprecated_srv_ql() { // eslint-disable-next-line no-console
22
25
  return module.exports
23
26
  }
24
27
 
25
- if (cds.env.features.cls && cds.env.features.debug_queries) {
26
- const Query = require('./Query'), { then } = Query.prototype
27
- const { AsyncResource } = require('async_hooks')
28
- Object.defineProperty (Query.prototype,'then',{ get(){
29
- const q = new AsyncResource('cds.Query')
30
- return (r,e) => q.runInAsyncScope (then,this,r,e)
31
- }})
32
- }
33
-
34
28
  module.exports._reset = ()=>{ // for strange tests only
35
29
  const _name = cds.env.sql.names === 'quoted' ? n =>`"${n}"` : n => n.replace(/[.:]/g,'_')
36
- Object.defineProperty (require('./Query').prototype,'valueOf',{ configurable:1, value: function(cmd=this.cmd) {
30
+ Object.defineProperty (Query.prototype,'valueOf',{ configurable:1, value: function(cmd=this.cmd) {
37
31
  return `${cmd} ${_name(this._target.name)} `
38
32
  }})
39
33
  return this
@@ -12,11 +12,15 @@ const { EventEmitter } = require('events')
12
12
  class EventContext {
13
13
 
14
14
  /** Creates a new instance that inherits from cds.context */
15
- static for (_) {
15
+ static for (_,_as_root) {
16
16
  const ctx = new this (_)
17
- if (features.cds_tx_inheritance) {
18
- const base = cds.context
19
- if (base) ctx._set('_propagated', base)
17
+ const base = cds.context
18
+ if (base) {
19
+ ctx._set('_propagated', base) // we inherit from former cds.currents
20
+ if (!_as_root) {
21
+ if (!ctx.context) ctx._set('context', base.context) // all transaction handling works with root contexts
22
+ if (!ctx.tx && base.tx) ctx.tx = base.tx
23
+ }
20
24
  }
21
25
  return ctx
22
26
  }
@@ -38,7 +42,7 @@ class EventContext {
38
42
  //
39
43
 
40
44
  get emitter() {
41
- return this.context._emitter || (this.context._emitter = new EventEmitter)
45
+ return this.context._emitter || this.context._set('_emitter', new EventEmitter)
42
46
  }
43
47
 
44
48
  async emit (event,...args) {
@@ -104,6 +108,24 @@ class EventContext {
104
108
  || this.hasOwnProperty('locale') && this.locale // eslint-disable-line no-prototype-builtins
105
109
  }
106
110
 
111
+ get _features() {
112
+ return super._features = this._propagated._features || _features4 (this.http?.req?.features || this.user?.features || this.http?.req?.user?.features)
113
+ }
114
+ get features() {
115
+ return super.features = this._features || noFeatures
116
+ }
117
+ set features(v) {
118
+ super.features = _features4(v)
119
+ }
120
+
121
+ get model() {
122
+ const m = this._propagated.model || this.http?.req.__model // IMPORTANT: Never use that anywhere else
123
+ return this._set('model',m)
124
+ }
125
+ set model(m) {
126
+ super.model = m
127
+ }
128
+
107
129
  get timestamp() {
108
130
  return super.timestamp = this._propagated.timestamp || new Date
109
131
  }
@@ -139,30 +161,40 @@ class EventContext {
139
161
  */
140
162
  set tx (tx) {
141
163
  Object.defineProperty (this,'tx',{value:tx}) //> allowed only once!
142
- const ctx = tx.context
143
- if (ctx && ctx !== this) {
144
- if (!this.hasOwnProperty('context')) this.context = ctx // eslint-disable-line no-prototype-builtins
145
-
164
+ const root = tx.context?.context
165
+ if (root && root !== this) {
166
+ if (!this.hasOwnProperty('context')) this.context = root // eslint-disable-line no-prototype-builtins
146
167
  if (features.assert_integrity && features.assert_integrity_type == 'RT') {
147
- const reqs = ctx._children || ctx._set('_children', {})
168
+ const reqs = root._children || root._set('_children', {})
148
169
  const all = reqs[tx.name] || (reqs[tx.name] = [])
149
170
  all.push(this)
150
171
  }
151
172
  }
152
173
  }
153
174
  get _tx() { return this.tx } // REVISIT: for compatibility to bade usages of req._tx
154
-
155
-
156
- /** REVISIT: remove -> @deprecated */
157
- set _model(m){ Object.defineProperty(this,'_model',{value:m}) }
158
- get _model() {
159
- return this._model = this.tx && this.tx.model || this._propagated._model
160
- }
161
175
  }
162
176
 
163
177
  const _TENANT_LOCALE = { tenant:1, locale:2 }
164
178
  const _anonymous = new cds.User.default
165
179
 
180
+ const _features4 = features => { // normalizes features to an object
181
+ if (!features) return
182
+ if (features === '*') return allFeatures
183
+ const o = (
184
+ Array.isArray(features) ? features.reduce((fts,f)=>{ fts[f] = true; return fts },{}) :
185
+ typeof features === 'object' ? Object.fromEntries (Object.entries(features).filter(([,v])=>v)) :
186
+ (''+features).split(',').reduce((fts,f)=>{ fts[f] = true; return fts },{})
187
+ )
188
+ return Object.defineProperty (o,'$hash',$hash)
189
+ }
190
+ const $hash = {
191
+ get() { return this.$hash = Object.keys(this).join(',') },
192
+ set(v){ Object.defineProperty(this,'$hash',{value:v}) },
193
+ configurable:true
194
+ }
195
+ const allFeatures = new Proxy ({'*':true},{ has:() => true, get:(_,p) => p === '$hash' ? '*' : true })
196
+ const noFeatures = {__proto__:{ $hash:'' }}
197
+
166
198
  EventContext.prototype._set('_propagated', Object.seal({}))
167
199
  EventContext.propagateHeaders = [ 'x-correlation-id' ]
168
200
  module.exports = EventContext
package/lib/req/locale.js CHANGED
@@ -4,7 +4,11 @@ const INCLUDE_LIST = i18n.preserved_locales.reduce((p,n)=>{
4
4
  p[n]=n; p[n.toUpperCase()]=n; return p
5
5
  },{
6
6
  en_US_x_saptrc: 'en_US_saptrc',
7
- en_US_x_sappsd: 'en_US_sappsd'
7
+ en_US_x_sappsd: 'en_US_sappsd',
8
+ en_US_x_saprigi: 'en_US_saprigi',
9
+ '1Q': 'en_US_saptrc',
10
+ '2Q': 'en_US_sappsd',
11
+ '3Q': 'en_US_saprigi'
8
12
  })
9
13
 
10
14
  const from_req = req => req && (
@@ -32,22 +32,11 @@ class ProtocolAdapter {
32
32
  /**
33
33
  * Mounts the adapter to an express app.
34
34
  */
35
- in (app, at) {
36
- const LOG = cds.log(), DEBUG = cds.debug('server')
37
- const srv = this.service, path = at || srv.path
38
- app.use (path+'/webapp/', (_,res)=> res.sendStatus(404))
39
- lib.auth (srv, app, srv.options)
40
- lib.perf (app)
41
- app.use (path, (req,_,next) => {
42
- if (req.user?.tenant && !cds.context?.tenant) cds.context = { user: req.user } // REVISIT: should move to auth middleware
43
- LOG && LOG (req.method, decodeURI(req.originalUrl), req.body||'')
44
- if (/\$batch/.test(req.url)) req.on ('dispatch', (req) => {
45
- LOG && LOG ('>', req.event, decodeURI(req._path), req._query||'')
46
- if (DEBUG && req.query) DEBUG (req.query)
47
- })
48
- next()
49
- })
50
- app.use (path, this)
35
+ in (app) {
36
+ const srv = this.service
37
+ app.use (srv.path+'/webapp/', (_,res)=> res.sendStatus(404))
38
+ const cds_context_model = require('./srv-models')
39
+ app.use (srv.path, logger, lib.perf, lib.auth(srv), cds_context_model.middleware4(srv), this)
51
40
  return srv
52
41
  }
53
42
 
@@ -72,10 +61,25 @@ class ProtocolAdapter {
72
61
 
73
62
  const _protocol4 = (srv) => {
74
63
  const {to} = srv.options; if (to) return to
75
- const def = srv.definition
76
- return !def ? default_protocol : def['@protocol'] || def['@rest'] && 'rest' || def['@odata'] && 'odata_v4' || default_protocol
64
+ return _protocol4Service(srv.definition)
77
65
  }
78
66
 
67
+ const _protocol4Service = (service) => {
68
+ return !service ? default_protocol : service['@protocol'] || service['@rest'] && 'rest' || service['@odata'] && 'odata_v4' || default_protocol
69
+ }
70
+
71
+
72
+ const LOG = cds.log(), DEBUG = cds.debug('server')
73
+ const logger = function cap_req_logger (req,_,next) {
74
+ LOG && LOG (req.method, decodeURI(req.originalUrl), req.body||'')
75
+ if (/\$batch/.test(req.url)) req.on ('dispatch', (req) => {
76
+ LOG && LOG ('>', req.event, decodeURI(req._path), req._query||'')
77
+ if (DEBUG && req.query) DEBUG (req.query)
78
+ })
79
+ next()
80
+ }
81
+
82
+
79
83
  const default_protocol = 'odata_v4'
80
84
  const _prototype = Object.getOwnPropertyDescriptors (ProtocolAdapter.prototype)
81
- module.exports = { ProtocolAdapter }
85
+ module.exports = { ProtocolAdapter, _protocol4Service }
File without changes
@@ -18,7 +18,7 @@ const connect = module.exports = async function cds_connect (options) {
18
18
  * or with options configured in cds.env.requires.<datasource>.
19
19
  * @param {string} [datasource]
20
20
  * @param {{ kind?:String, impl?:String }} [options]
21
- * @returns { Promise<import('../serve/Service-api')> }
21
+ * @returns { Promise<import('./srv-api')> }
22
22
  */
23
23
  connect.to = async (datasource, options) => {
24
24
  let Service = cds.service.factory, _done = x=>x
File without changes
@@ -2,7 +2,7 @@ const cds = require('..'), { path, isfile } = cds.utils
2
2
  const paths = Array.from (new Set ([ cds.root, ...require.resolve.paths('x') ]))
3
3
  const DEBUG = cds.debug('srv.factory'); DEBUG && DEBUG ({ 'cds.root':cds.root, paths })
4
4
 
5
- /** @typedef {import('./Service-api')} Service @type { (()=>Service) & (new()=>Service) } */
5
+ /** @typedef {import('./srv-api')} Service @type { (()=>Service) & (new()=>Service) } */
6
6
  const ServiceFactory = function (name, model, options) { //NOSONAR
7
7
 
8
8
  const o = { ...options } // avoid changing shared options
@@ -1,13 +1,13 @@
1
1
  const cds = require('..'), { Event, Request } = cds
2
- const add_methods_to = require ('./Service-methods')
2
+ const add_methods_to = require ('./srv-methods')
3
3
 
4
4
 
5
- class Service extends require('./Service-handlers') {
5
+ class Service extends require('./srv-handlers') {
6
6
 
7
7
  constructor (name, model, o) {
8
8
  if (is_object(name)) {
9
9
  [ model, o ] = [ name, model ]
10
- let srv = cds.linked(model).services[0] || cds.error.expected `${{arg0:name}} to be a CSN with a service definition`
10
+ let srv = cds.linked(model).services[0] || cds.error.expected `${{model}} passed as first argument to be a CSN with a single service definition`
11
11
  name = srv.name
12
12
  }
13
13
  super (name || new.target.name) .options = o || (o={})
@@ -56,6 +56,14 @@ class Service extends require('./Service-handlers') {
56
56
  * Querying API to send synchronous requests...
57
57
  */
58
58
  run (query, data) {
59
+ if (typeof query === 'function') {
60
+ const ctx = cds.context, fn = query
61
+ if (ctx?.tx && !ctx.tx._done) {
62
+ return fn (this.tx(ctx)) // with nested tx
63
+ } else {
64
+ return this.tx (fn) // with root tx
65
+ }
66
+ }
59
67
  const req = new Request ({ query, data })
60
68
  return this.dispatch (req)
61
69
  }
@@ -94,6 +102,13 @@ class Service extends require('./Service-handlers') {
94
102
  get events() { return super.events = _reflect (this, d => d.kind === 'event') }
95
103
  get types() { return super.types = _reflect (this, d => !d.kind || d.kind === 'type') }
96
104
 
105
+ /**
106
+ * Flag to control whether this service is extensible.
107
+ * Can be overridden by subclasses.
108
+ * REVISIT cds.xt name check should move to respective services
109
+ */
110
+ get isExtensible() { return !this.name?.startsWith('cds.xt.') && this.model === cds.model}
111
+
97
112
  /**
98
113
  * Subclasses may override this to free private resources
99
114
  */
@@ -107,10 +122,11 @@ class Service extends require('./Service-handlers') {
107
122
 
108
123
  }
109
124
 
110
- const { dispatch, handle } = require('./Service-dispatch')
111
- Service.prototype.dispatch = dispatch
125
+ const { dispatch, handle } = require('./srv-dispatch')
126
+ Service.prototype.tx = require('./srv-tx')
112
127
  Service.prototype.handle = handle
113
- Service.prototype.transaction = Service.prototype.tx = require('./Transaction')
128
+ Service.prototype.dispatch = dispatch
129
+ Service.prototype.transaction = Service.prototype.tx
114
130
  Service.prototype._implicit_next = cds.env.features.implicit_next
115
131
  Service.prototype._is_service_instance = Service._is_service_class = true //> for factory
116
132
  module.exports = Service
@@ -3,7 +3,7 @@ const cds = require ('../index')
3
3
  /**
4
4
  * The default implementation of the `srv.dispatch(req)` ensures everything
5
5
  * is prepared before calling `srv.handle(req)`
6
- * @typedef {import('./Service-api')} Service
6
+ * @typedef {import('./srv-api')} Service
7
7
  * @typedef {import('../req/request')} Request
8
8
  * @this {Service}
9
9
  * @param {Request} req
@@ -13,18 +13,23 @@ exports.dispatch = async function dispatch (req) { //NOSONAR
13
13
 
14
14
  // Ensure we are in a proper transaction
15
15
  if (!this.context) {
16
- const gc = cds.context
17
- if (gc && gc.tx && !gc.tx._done) return this.tx(gc).dispatch(req) // with nested tx
18
- else return this.tx(tx => tx.dispatch(req)) // as root tx
16
+ const ctx = cds.context
17
+ if (ctx?.tx && !ctx.tx._done) {
18
+ return this.tx (ctx) .dispatch(req) // with nested tx
19
+ } else {
20
+ return this.tx (tx => tx.dispatch(req)) // with root tx
21
+ }
19
22
  }
20
23
  if (!req.tx) req.tx = this // `this` is a tx from now on...
21
24
 
22
25
  // Inform potential listeners // REVISIT: -> this should move into protocol adapters
23
- if (_is_root(req)) req._.req.emit ('dispatch',req)
26
+ let _is_root = req.constructor.name in { ODataRequest:1, RestRequest:2 }
27
+ if (_is_root) req._.req.emit ('dispatch',req)
24
28
 
25
29
  // Handle batches of queries
26
- if (_is_array(req.query))
27
- return Promise.all (req.query.map (q => this.dispatch ({query:q,__proto__:req,context:req})))
30
+ if (_is_array(req.query)) return Promise.all (req.query.map (
31
+ q => this.dispatch ({ query:q, context: req.context, __proto__:req })
32
+ ))
28
33
 
29
34
  // Ensure target and fqns
30
35
  if (!req.target) _ensure_target (this,req)
@@ -88,7 +93,6 @@ exports.handle = async function handle (req) {
88
93
  }
89
94
 
90
95
 
91
- const _is_root = (req) => /OData|REST/i.test(req.constructor.name)
92
96
  const _is_array = Array.isArray
93
97
  const _dummy = ()=>{} // REVISIT: required for some messaging tests which obviously still expect and call next()
94
98
 
@@ -107,6 +111,7 @@ const _ensure_target = (srv,req) => {
107
111
 
108
112
  const _ensure_fqn = (x,p,srv, name = x[p]) => {
109
113
  if (typeof name === 'string') {
114
+ if (srv instanceof cds.DatabaseService) return
110
115
  if (srv.model && name in srv.model.definitions) return
111
116
  if (name.startsWith(srv.namespace)) return
112
117
  if (name.endsWith('_drafts')) return // REVISIT: rather fix test/fiori/localized-draft.test.js ?
@@ -1,4 +1,5 @@
1
1
  const cds = require('..'), {expected} = cds.error
2
+ const LOG = cds.log()
2
3
 
3
4
  class EventHandlers {
4
5
 
@@ -41,6 +42,15 @@ const _register = function (srv, phase, event, path, handler) { //NOSONAR
41
42
 
42
43
  if (!handler) [ handler, path ] = [ path ] // argument path is optional
43
44
  if (typeof handler !== 'function') expected `${{handler}} to be a function`
45
+ if (handler._is_stub) {
46
+ LOG.warn (`\n
47
+ WARNING: You are trying to register a frameworks-generated stub method for
48
+ custom action/function '${event}' in implementation of service '${srv.name}'.
49
+ We're ignoring that as we already registered the according handler.
50
+ Please fix your implementation, i.e., just don't register that handler.
51
+ `)
52
+ return srv
53
+ }
44
54
 
45
55
  // Canonicalize event argument
46
56
  if (!event || event === '*') event = undefined
@@ -3,19 +3,21 @@ const LOG = cds.log('cds-app-service-methods')
3
3
 
4
4
 
5
5
  module.exports = (srv) => {
6
- if (!( //> we only support that for app services
6
+ if (srv.model && ( //> we only support that for app services
7
7
  srv instanceof cds.ApplicationService ||
8
8
  srv instanceof cds.RemoteService ||
9
9
  srv._add_stub_methods
10
- )) return
11
- for (const each of srv.operations) {
12
- add_handler_for (srv, each)
13
- }
14
- for (const each of srv.entities) {
15
- for (const a in each.actions) {
16
- add_handler_for (srv, each.actions[a])
10
+ )) {
11
+ for (const each of srv.operations) {
12
+ add_handler_for (srv, each)
13
+ }
14
+ for (const each of srv.entities) {
15
+ for (const a in each.actions) {
16
+ add_handler_for (srv, each.actions[a])
17
+ }
17
18
  }
18
19
  }
20
+ return srv
19
21
  }
20
22
 
21
23
  const add_handler_for = (srv, def) => {