@sap/cds 6.6.2 → 6.7.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 (130) hide show
  1. package/CHANGELOG.md +59 -2
  2. package/README.md +1 -1
  3. package/apis/connect.d.ts +11 -4
  4. package/apis/core.d.ts +1 -1
  5. package/apis/csn.d.ts +1 -0
  6. package/apis/internal/inference.d.ts +15 -2
  7. package/apis/log.d.ts +10 -0
  8. package/apis/serve.d.ts +4 -9
  9. package/apis/services.d.ts +86 -19
  10. package/bin/build/buildTaskEngine.js +16 -42
  11. package/bin/build/constants.js +4 -2
  12. package/bin/build/provider/buildTaskProviderInternal.js +117 -85
  13. package/bin/build/provider/hana/index.js +6 -1
  14. package/bin/build/provider/mtx-extension/index.js +74 -34
  15. package/bin/build/provider/mtx-sidecar/index.js +3 -3
  16. package/bin/build/provider/nodejs/index.js +2 -2
  17. package/bin/build/util.js +63 -14
  18. package/bin/cds-serve.js +6 -0
  19. package/bin/cds.js +20 -4
  20. package/bin/deploy/to-hana/cfUtil.js +15 -1
  21. package/bin/mtx/in-cds.js +2 -9
  22. package/bin/plugins.js +31 -0
  23. package/bin/serve.js +12 -12
  24. package/lib/compile/etc/_localized.js +1 -1
  25. package/lib/compile/for/lean_drafts.js +22 -6
  26. package/lib/compile/for/nodejs.js +4 -1
  27. package/lib/compile/load.js +4 -2
  28. package/lib/core/index.js +35 -15
  29. package/lib/dbs/cds-deploy.js +129 -133
  30. package/lib/env/cds-env.js +25 -17
  31. package/lib/env/cds-requires.js +10 -40
  32. package/lib/env/compat.js +12 -0
  33. package/lib/env/defaults.js +17 -9
  34. package/lib/env/plugins.js +29 -0
  35. package/lib/env/schemas/cds-rc.json +14 -0
  36. package/lib/index.js +3 -0
  37. package/lib/log/cds-log.js +7 -4
  38. package/lib/ql/CREATE.js +1 -1
  39. package/lib/ql/DELETE.js +1 -1
  40. package/lib/ql/DROP.js +3 -3
  41. package/lib/ql/INSERT.js +1 -1
  42. package/lib/ql/Query.js +14 -6
  43. package/lib/ql/SELECT.js +8 -2
  44. package/lib/ql/UPDATE.js +1 -1
  45. package/lib/ql/Whereable.js +1 -1
  46. package/lib/ql/cds-ql.js +1 -9
  47. package/lib/req/cds-context.js +1 -4
  48. package/lib/req/request.js +63 -2
  49. package/lib/req/response.js +3 -2
  50. package/lib/srv/bindings.js +69 -71
  51. package/lib/srv/cds-connect.js +4 -1
  52. package/lib/srv/cds-serve.js +4 -0
  53. package/lib/srv/middlewares/index.js +37 -6
  54. package/lib/srv/protocols/_legacy.js +1 -1
  55. package/lib/srv/protocols/index.js +1 -1
  56. package/lib/srv/srv-api.js +4 -6
  57. package/lib/srv/srv-dispatch.js +4 -3
  58. package/lib/srv/srv-handlers.js +1 -1
  59. package/lib/srv/srv-methods.js +8 -2
  60. package/lib/utils/cds-test.js +4 -1
  61. package/libx/_runtime/audit/Service.js +8 -9
  62. package/libx/_runtime/audit/generic/personal/index.js +1 -1
  63. package/libx/_runtime/audit/generic/personal/utils.js +1 -1
  64. package/libx/_runtime/audit/utils/v2.js +17 -20
  65. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +11 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +0 -1
  68. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +3 -3
  69. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
  70. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +4 -4
  71. package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +2 -2
  72. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -1
  73. package/libx/_runtime/cds-services/services/Service.js +1 -1
  74. package/libx/_runtime/cds-services/util/assert.js +41 -65
  75. package/libx/_runtime/common/code-ext/WorkerPool.js +90 -0
  76. package/libx/_runtime/common/code-ext/WorkerReq.js +0 -4
  77. package/libx/_runtime/common/code-ext/execute.js +28 -18
  78. package/libx/_runtime/common/code-ext/handlers.js +5 -4
  79. package/libx/_runtime/common/code-ext/worker.js +45 -3
  80. package/libx/_runtime/common/code-ext/workerQueryExecutor.js +8 -7
  81. package/libx/_runtime/common/composition/delete.js +1 -1
  82. package/libx/_runtime/common/composition/update.js +3 -5
  83. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  84. package/libx/_runtime/common/generic/auth/readOnly.js +5 -4
  85. package/libx/_runtime/common/generic/auth/restrict.js +7 -2
  86. package/libx/_runtime/common/generic/crud.js +12 -1
  87. package/libx/_runtime/common/generic/etag.js +11 -3
  88. package/libx/_runtime/common/generic/input.js +8 -6
  89. package/libx/_runtime/common/generic/paging.js +25 -8
  90. package/libx/_runtime/common/generic/put.js +1 -1
  91. package/libx/_runtime/common/generic/sorting.js +0 -1
  92. package/libx/_runtime/common/i18n/messages.properties +1 -0
  93. package/libx/_runtime/common/utils/cqn.js +5 -1
  94. package/libx/_runtime/common/utils/cqn2cqn4sql.js +2 -2
  95. package/libx/_runtime/common/utils/resolveView.js +14 -10
  96. package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -3
  97. package/libx/_runtime/common/utils/templateProcessor.js +15 -17
  98. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +18 -6
  99. package/libx/_runtime/db/Service.js +1 -0
  100. package/libx/_runtime/db/data-conversion/post-processing.js +0 -18
  101. package/libx/_runtime/db/expand/expand-v2.js +2 -2
  102. package/libx/_runtime/db/expand/rawToExpanded.js +6 -6
  103. package/libx/_runtime/db/generic/integrity.js +1 -1
  104. package/libx/_runtime/db/utils/columns.js +5 -5
  105. package/libx/_runtime/fiori/generic/activate.js +3 -3
  106. package/libx/_runtime/fiori/generic/edit.js +1 -1
  107. package/libx/_runtime/fiori/generic/new.js +4 -0
  108. package/libx/_runtime/fiori/lean-draft.js +138 -46
  109. package/libx/_runtime/hana/execute.js +3 -1
  110. package/libx/_runtime/hana/pool.js +10 -2
  111. package/libx/_runtime/messaging/common-utils/AMQPClient.js +6 -1
  112. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  113. package/libx/_runtime/remote/Service.js +16 -13
  114. package/libx/_runtime/remote/utils/client.js +6 -1
  115. package/libx/_runtime/sqlite/Service.js +5 -59
  116. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
  117. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -2
  118. package/libx/_runtime/sqlite/execute.js +3 -1
  119. package/libx/_runtime/types/api.js +12 -3
  120. package/libx/odata/afterburner.js +36 -0
  121. package/libx/odata/cqn2odata.js +1 -1
  122. package/libx/odata/grammar.pegjs +5 -3
  123. package/libx/odata/parser.js +1 -1
  124. package/libx/odata/utils.js +1 -1
  125. package/libx/rest/RestAdapter.js +1 -1
  126. package/libx/rest/RestRequest.js +1 -0
  127. package/package.json +5 -2
  128. package/libx/_runtime/common/code-ext/workerQuery.js +0 -45
  129. package/libx/_runtime/common/constants/limit.js +0 -12
  130. package/libx/_runtime/common/utils/page.js +0 -39
@@ -1,7 +1,5 @@
1
- const DEBUG = /\b(y|all|serve|bindings)\b/.test (process.env.DEBUG) && console.warn
2
- // || console.debug
3
1
 
4
- const cds = require ('..')
2
+ const cds = require ('..'), DEBUG = cds.debug('serve|bindings',{label:'cds'})
5
3
  const { readFile, readFileSync, writeFile, writeFileSync } = require ('fs')
6
4
  const [ read, write ] = [ readFile, writeFile ].map(require('util').promisify)
7
5
  const registry = '~/.cds-services.json'
@@ -9,88 +7,88 @@ const registry = '~/.cds-services.json'
9
7
  /** TODO: Add documentation */
10
8
  module.exports = class Bindings {
11
9
 
12
- static get registry(){ return registry }
10
+ static get registry(){ return registry }
13
11
 
14
- static then(r,e) {
15
- const LOG = cds.log('cds.serve', { prefix:'cds' })
16
- const bindings = new Bindings
17
- cds.prependOnceListener ('connect', ()=> LOG._info && LOG.info ('connect using bindings from:', { registry }))
18
- cds.once('listening', ({url})=> bindings.export (cds.service.providers, url))
19
- return bindings.import() .then (r,e)
20
- }
12
+ static then(r,e) {
13
+ const LOG = cds.log('cds.serve', { prefix:'cds' })
14
+ const bindings = new Bindings
15
+ cds.prependOnceListener ('connect', ()=> LOG._info && LOG.info ('connect using bindings from:', { registry }))
16
+ cds.once('listening', ({url})=> bindings.export (cds.service.providers, url))
17
+ return bindings.import() .then (r,e)
18
+ }
21
19
 
22
- constructor(url) {
23
- this._source = require ('path') .resolve (cds.root, registry.replace(/^~/, require('os').homedir()))
24
- this.cds = {provides:{}}
25
- this.url = url
26
- }
20
+ constructor(url) {
21
+ this._source = require ('path') .resolve (cds.root, registry.replace(/^~/, require('os').homedir()))
22
+ this.cds = {provides:{}}
23
+ this.url = url
24
+ }
27
25
 
28
- async load (sync) {
29
- DEBUG && DEBUG('[cds] - reading bindings from:', this._source)
30
- try { Object.assign (this, JSON.parse (sync ? readFileSync (this._source) : await read (this._source))) }
31
- catch (e) { /* ignored */ }
32
- return this
33
- }
34
- async store (sync) {
35
- DEBUG && DEBUG ('[cds] - writing bindings to:', this._source)
36
- const json = JSON.stringify ({cds:this.cds},null,' ')
37
- return sync ? writeFileSync (this._source, json) : write (this._source, json)
38
- }
26
+ async load (sync) {
27
+ DEBUG?.('reading bindings from:', this._source)
28
+ try { Object.assign (this, JSON.parse (sync ? readFileSync (this._source) : await read (this._source))) }
29
+ catch (e) { /* ignored */ }
30
+ return this
31
+ }
32
+ async store (sync) {
33
+ DEBUG?.('writing bindings to:', this._source)
34
+ const json = JSON.stringify ({cds:this.cds},null,' ')
35
+ return sync ? writeFileSync (this._source, json) : write (this._source, json)
36
+ }
39
37
 
40
- async import() {
41
- const required = cds.requires; if (!required) return this
42
- const provided = (await this.load()) .cds.provides
43
- for (let each in required) {
44
- const req = required[each]; if (typeof req !== 'object') continue
45
- const bound = provided [req.service||each]
46
- if (bound) {
47
- Object.assign (req.credentials || (req.credentials = {}), bound.credentials)
48
- // REVISIT: temporary fix to inherit kind as well for mocked odata services
49
- // otherwise mocking with two services does not work for kind:odata-v2
50
- if (req.kind === 'odata-v2' || req.kind === 'odata-v4') req.kind = 'odata'
51
- }
52
- }
53
- return this
38
+ async import() {
39
+ const required = cds.requires; if (!required) return this
40
+ const provided = (await this.load()) .cds.provides
41
+ for (let each in required) {
42
+ const req = required[each]; if (typeof req !== 'object') continue
43
+ const bound = provided [req.service||each]
44
+ if (bound) {
45
+ Object.assign (req.credentials || (req.credentials = {}), bound.credentials)
46
+ // REVISIT: temporary fix to inherit kind as well for mocked odata services
47
+ // otherwise mocking with two services does not work for kind:odata-v2
48
+ if (req.kind === 'odata-v2' || req.kind === 'odata-v4') req.kind = 'odata'
49
+ }
54
50
  }
51
+ return this
52
+ }
55
53
 
56
- async export (services, url) {
57
- this.cleanup (this.url = url)
58
- // register our services
59
- const provides = this.cds.provides
60
- for (let each of services) {
61
- // if (each.name in cds.env.requires) continue
62
- const options = each.options || {}
63
- provides[each.name] = {
64
- kind: options.to || 'odata',
65
- credentials: {
66
- ...options.credentials,
67
- url: url + each.path
68
- }
69
- }
54
+ async export (services, url) {
55
+ this.cleanup (this.url = url)
56
+ // register our services
57
+ const provides = this.cds.provides
58
+ for (let each of services) {
59
+ // if (each.name in cds.env.requires) continue
60
+ const options = each.options || {}
61
+ provides[each.name] = {
62
+ kind: options.to || 'odata',
63
+ credentials: {
64
+ ...options.credentials,
65
+ url: url + each.path
70
66
  }
71
- process.on ('exit', ()=>this.purge())
72
- return this.store()
67
+ }
73
68
  }
69
+ process.on ('exit', ()=>this.purge())
70
+ return this.store()
71
+ }
74
72
 
75
- purge() {
76
- this.load(true)
77
- DEBUG && DEBUG ('[cds] - purging bindings from:', this._source)
78
- this.cleanup()
79
- this.store(true)
80
- }
73
+ purge() {
74
+ this.load(true)
75
+ DEBUG?.('purging bindings from:', this._source)
76
+ this.cleanup()
77
+ this.store(true)
78
+ }
81
79
 
82
- cleanup (url=this.url) {
83
- // remove all services served at the same url
84
- const all = this.cds.provides
85
- for (let [key,srv] of Object.entries (all)) {
86
- if (srv.credentials && srv.credentials.url && srv.credentials.url.startsWith(url)) delete all [key]
87
- }
88
- return this
80
+ cleanup (url=this.url) {
81
+ // remove all services served at the same url
82
+ const all = this.cds.provides
83
+ for (let [key,srv] of Object.entries (all)) {
84
+ if (srv.credentials && srv.credentials.url && srv.credentials.url.startsWith(url)) delete all [key]
89
85
  }
86
+ return this
87
+ }
90
88
  }
91
89
 
92
90
  const {NODE_ENV} = process.env
93
91
  if (NODE_ENV === 'test' || global.it || cds.env.no_bindings) {
94
- module['exports'] = { then: (r) => r() }
92
+ module['exports'] = { then: (r) => r() }
95
93
  }
96
94
  /* eslint no-console:off */
@@ -1,5 +1,6 @@
1
1
  const cds = require('..'), {one_model} = cds.env.features, LOG = cds.log('cds.connect')
2
2
  const _pending = cds.services._pending || {} // used below to chain parallel connect.to(<same>)
3
+ const TRACE = cds.debug('trace')
3
4
 
4
5
  /**
5
6
  * Connect to a service as primary datasource, i.e. cds.db.
@@ -21,6 +22,7 @@ const connect = module.exports = async function cds_connect (options) {
21
22
  * @returns { Promise<import('./srv-api')> }
22
23
  */
23
24
  connect.to = async (datasource, options) => {
25
+ TRACE?.time(`cds.connect ${datasource} `)
24
26
  let Service = cds.service.factory, _done = x=>x
25
27
  if (typeof datasource === 'object') [options,datasource] = [datasource]
26
28
  else if (datasource) {
@@ -36,7 +38,7 @@ connect.to = async (datasource, options) => {
36
38
  const m = await model4 (o)
37
39
  // check if required service definition exists
38
40
  const required = cds.requires[datasource]
39
- if (required && required.model && datasource !== 'db' && !m.definitions[required.service||datasource]) {
41
+ if (required?.model?.length && datasource !== 'db' && !m.definitions[required.service||datasource]) {
40
42
  LOG.error(`No service definition found for '${required.service || datasource}', as required by 'cds.requires.${datasource}':`, required)
41
43
  throw new Error (`No service definition found for '${required.service || datasource}'`)
42
44
  }
@@ -46,6 +48,7 @@ connect.to = async (datasource, options) => {
46
48
  if (datasource === 'db') cds.db = srv
47
49
  _done (cds.services[datasource] = srv)
48
50
  if (!o.silent) cds.emit ('connect',srv)
51
+ TRACE?.timeEnd(`cds.connect ${datasource} `)
49
52
  return srv
50
53
  }
51
54
 
@@ -2,10 +2,13 @@ const cds = require ('..')
2
2
  const { ProtocolAdapter } = cds.service.protocols
3
3
  const { Service } = cds.service.factory
4
4
  const _ready = Symbol(), _pending = cds.services._pending || {}
5
+ const TRACE = cds.debug('trace')
5
6
 
6
7
  /** @param som - a service name or a model (name or csn) */
7
8
  module.exports = function cds_serve (som, _options) { // NOSONAR
8
9
 
10
+ TRACE?.time(`cds.serve ${som} `)
11
+
9
12
  if (som && typeof som === 'object' && !is_csn(som) && !is_files(som)) {
10
13
  [som,_options] = [undefined,
11
14
  som._is_service_instance ? { service:som, from:'*' } :
@@ -123,6 +126,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
123
126
  return chimera
124
127
  }})
125
128
  }
129
+ TRACE?.timeEnd('cds.serve '+som+' ')
126
130
  return _resolve (response)
127
131
  }, _error),
128
132
 
@@ -7,14 +7,45 @@ const trace = exports.trace = require('./trace')
7
7
 
8
8
  // middlewares running before protocol adapters
9
9
  exports.before = [
10
- context(), // provides cds.context
11
- trace(), // provides detailed trace logs when DEBUG=trace
12
- auth(), // provides req.user & tenant
13
- ctx_auth(), // propagates auth results to cds.context
14
- ctx_model(), // fills in cds.context.model, in case of extensibility
15
- ]
10
+ context, // provides cds.context
11
+ trace, // provides detailed trace logs when DEBUG=trace
12
+ auth, // provides req.user & tenant
13
+ ctx_auth, // propagates auth results to cds.context
14
+ ctx_model, // fills in cds.context.model, in case of extensibility
15
+ ].map(_instantiate)
16
16
 
17
17
  // middlewares running after protocol adapters -> usually error middlewares
18
18
  exports.after = [
19
19
  errors(),
20
20
  ]
21
+
22
+ /**
23
+ * Convenience method to add custom middlewares like so:
24
+ * ```js
25
+ * cds.middlewares.add (mymw, {at:0}) // to the front
26
+ * cds.middlewares.add (mymw, {at:2})
27
+ * cds.middlewares.add (mymw, {before:'auth'})
28
+ * cds.middlewares.add (mymw, {after:'auth'})
29
+ * cds.middlewares.add (mymw) // to the end
30
+ * ```
31
+ */
32
+ exports.add = (new_mw, { at: index, before, after, options }) => {
33
+ let mw = new_mw (options)
34
+ if (index) return exports.before.splice (index, 0, mw)
35
+ if (before) return exports.before.splice (index, _index4(before), mw)
36
+ if (after) return exports.before.splice (index, _index4(after)+1, mw)
37
+ else return exports.before.push(mw)
38
+ }
39
+
40
+ function _index4 (middleware) {
41
+ if (typeof middleware === 'string') middleware = exports[middleware]
42
+ if (!middleware) throw new Error (`Didn't find a middleware matching ${{middleware}}`)
43
+ const index = exports.before.findIndex(mw => mw.factory === before)
44
+ if (index === -1) throw new Error (`Didn't find ${{middleware}} in cds.middlewares.before`)
45
+ return index
46
+ }
47
+
48
+ function _instantiate (factory,o) {
49
+ let mw = factory(o)
50
+ return mw && Object.assign(mw,{factory})
51
+ }
@@ -17,10 +17,10 @@ class LegacyProtocolAdapter extends ProtocolAdapter {
17
17
  return super.serve (srv, app, { before: [
18
18
  // async (req, res, next) => { await 1; next() }, // REVISIT: AsyncResource.bind() -> enable to break cds/tests/_runtime/odata/__tests__/integration/crud-with-mtx.test.js with existing, non-middleware mode, *w/o* fix to BufferedWriter
19
19
  cds_context,
20
- cap_req_logger,
21
20
  libx.perf,
22
21
  libx.auth(srv),
23
22
  ctx_auth,
23
+ cap_req_logger,
24
24
  cds_context_model.middleware4(srv)
25
25
  ], after:[] })
26
26
  }
@@ -80,7 +80,7 @@ class ProtocolAdapter {
80
80
 
81
81
 
82
82
  const protocols = Object.keys(ProtocolAdapter.init())
83
- const protocol4 = (def, _default = protocols[0]) => def['@protocol'] || protocols.find(p => def['@'+p]) || _default
83
+ const protocol4 = (def, _default = protocols[0]) => def?.['@protocol'] || protocols.find(p => def['@'+p]) || _default
84
84
  const is_global = adapter => adapter.length === 1 && !/^(function )?(\w+\s+)?\((srv|service)/.test(adapter)
85
85
 
86
86
  module.exports = { ProtocolAdapter, protocol4 }
@@ -63,11 +63,9 @@ class Service extends require('./srv-handlers') {
63
63
  run (query, data) {
64
64
  if (typeof query === 'function') {
65
65
  const ctx = cds.context, fn = query
66
- if (ctx?.tx && !ctx.tx._done) {
67
- return fn (this.tx(ctx)) // with nested tx
68
- } else {
69
- return this.tx (fn) // with root tx
70
- }
66
+ if (ctx?.tx && !ctx.tx._done)
67
+ return fn (this.tx(ctx)) // run fn with nested tx
68
+ else return this.tx(fn) // run fn with root tx
71
69
  }
72
70
  const req = new Request ({ query, data })
73
71
  return this.dispatch (req)
@@ -100,7 +98,7 @@ class Service extends require('./srv-handlers') {
100
98
  get namespace() {
101
99
  return super.namespace = this.definition && this.definition.name
102
100
  || this.model && this.model.namespace
103
- || !(this instanceof cds.DatabaseService) && !/\W/.test(this.name) && this.name || undefined
101
+ || !(this.isDatabaseService) && !/\W/.test(this.name) && this.name || undefined
104
102
  }
105
103
 
106
104
  get operations() { return super.operations = _reflect (this, d => d.kind === 'action' || d.kind === 'function') }
@@ -16,10 +16,11 @@ exports.dispatch = async function dispatch (req) { //NOSONAR
16
16
  const ctx = cds.context
17
17
  if (ctx?.tx && !ctx.tx._done) {
18
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
  }
20
+
21
+ return this.tx (tx => tx.dispatch(req)) // with root tx
22
22
  }
23
+
23
24
  if (!req.tx) req.tx = this // `this` is a tx from now on...
24
25
 
25
26
  // Inform potential listeners // REVISIT: -> this should move into protocol adapters
@@ -112,7 +113,7 @@ const _ensure_target = (srv,req) => {
112
113
 
113
114
  const _ensure_fqn = (x,p,srv, name = x[p]) => {
114
115
  if (typeof name === 'string') {
115
- if (srv instanceof cds.DatabaseService) return
116
+ if (srv.isDatabaseService) return
116
117
  if (srv.model && name in srv.model.definitions) return
117
118
  if (name.startsWith(srv.namespace)) return
118
119
  if (name.endsWith('_drafts')) return // REVISIT: rather fix test/fiori/localized-draft.test.js ?
@@ -91,7 +91,7 @@ const _register = function (srv, phase, event, path, handler) { //NOSONAR
91
91
  if (!path.startsWith(srv.name+'.')) path = `${srv.name}.${path}`
92
92
  }
93
93
 
94
- if (cds.env.features.lean_draft && cds.env.features.lean_draft_compatibility) {
94
+ if (cds.env.fiori.lean_draft && cds.env.fiori.draft_compat) {
95
95
  const entity = path && srv.model?.definitions[path.name || path]
96
96
  if (['PATCH', 'CANCEL', 'NEW'].includes(event)) {
97
97
  // delegate to drafts
@@ -58,7 +58,10 @@ const add_handler_for = (srv, def) => {
58
58
  `)
59
59
  const stub = srv[event] = function (...args) {
60
60
  const req = { event, data:{} }, $ = args[0]
61
- const target = this.entities [ $ && $.name ? $.name.match(/\w*$/)[0] : $ ]
61
+ const target = $ && (
62
+ this.model.definitions[ $.name ]
63
+ || this.entities[ $.name?.replace(`${this.name}.`,'') || $ ]
64
+ )
62
65
  if (target) { //> bound action/function?
63
66
  req.target = target; args.shift() // first argument is the target entity name
64
67
  req.params = [ args.shift() ] // second argument is the target's primary key
@@ -86,7 +89,10 @@ const add_handler_for = (srv, def) => {
86
89
 
87
90
  return this.send (req)
88
91
  }
89
- stub._is_stub = true
92
+ Object.defineProperties(stub,{
93
+ name: {value: /[^.]+$/.exec(srv.name)[0] +'.'+ event},
94
+ _is_stub: {value:true},
95
+ })
90
96
  }
91
97
 
92
98
 
@@ -164,4 +164,7 @@ const spy = (o,f) => {
164
164
 
165
165
 
166
166
  /** @type Test & ()=>Test */
167
- module.exports = Object.setPrototypeOf ((..._) => (new Test).run(..._), Test.prototype)
167
+ module.exports = Object.assign (
168
+ Object.setPrototypeOf ((..._) => (new Test).run(..._), Test.prototype),
169
+ { Test }
170
+ )
@@ -1,13 +1,11 @@
1
1
  const cds = require('../cds')
2
2
  const OutboxService = require('../messaging/Outbox')
3
-
4
3
  const v2utils = require('./utils/v2')
5
-
6
4
  const ANONYMOUS = 'anonymous'
7
5
 
8
6
  const _getTenantAndUser = () => ({
9
- user: (cds.context && cds.context.user && cds.context.user.id) || ANONYMOUS,
10
- tenant: cds.context && cds.context.tenant
7
+ user: cds.context?.user?.id ?? ANONYMOUS,
8
+ tenant: cds.context?.tenant
11
9
  })
12
10
 
13
11
  module.exports = class AuditLogService extends OutboxService {
@@ -82,11 +80,8 @@ module.exports = class AuditLogService extends OutboxService {
82
80
  }
83
81
 
84
82
  // write the logs
85
- await Promise.all(
86
- entries.map(entry => {
87
- v2utils.sendDataAccessLog(entry).catch(err => errors.push(err))
88
- })
89
- )
83
+ await Promise.all(entries.map(entry => v2utils.sendDataAccessLog(entry).catch(err => errors.push(err))))
84
+
90
85
  if (errors.length) {
91
86
  throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
92
87
  }
@@ -108,6 +103,7 @@ module.exports = class AuditLogService extends OutboxService {
108
103
  v2utils.sendDataModificationLog(entry).catch(err => errors.push(err))
109
104
  })
110
105
  )
106
+
111
107
  if (errors.length) {
112
108
  throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
113
109
  }
@@ -124,10 +120,12 @@ module.exports = class AuditLogService extends OutboxService {
124
120
  tenant = parsed.tenant
125
121
  delete parsed.tenant
126
122
  }
123
+
127
124
  if (parsed.user && typeof parsed.user === 'string') {
128
125
  user = parsed.user
129
126
  delete parsed.user
130
127
  }
128
+
131
129
  data = JSON.stringify(parsed)
132
130
  } catch (e) {
133
131
  // ignore
@@ -157,6 +155,7 @@ module.exports = class AuditLogService extends OutboxService {
157
155
  v2utils.sendConfigChangeLog(entry).catch(err => errors.push(err))
158
156
  })
159
157
  )
158
+
160
159
  if (errors.length) {
161
160
  throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
162
161
  }
@@ -8,7 +8,7 @@ const {
8
8
  const { auditAccessHandler } = require('./access')
9
9
 
10
10
  exports.impl = cds.service.impl(function () {
11
- if (!cds.db) return cds.on('connect', srv => srv instanceof cds.DatabaseService && exports.impl.call(this))
11
+ if (!cds.db) return cds.on('connect', srv => srv.isDatabaseService && exports.impl.call(this))
12
12
  // REVISIT: diff() doesn't work in srv after phase but foreign key propagation has not yet taken place in srv before phase
13
13
  // -> calc diff in db layer and store in audit data structure at context
14
14
  // -> REVISIT for GA: clear req._.partialPersistentState?
@@ -7,7 +7,7 @@ const WRITE = { CREATE: 1, UPDATE: 1, DELETE: 1 }
7
7
  const getMapKeyForCurrentRequest = req => {
8
8
  // running in srv or db layer? -> srv's req.query used as key of diff and logs maps at req.context
9
9
  // REVISIT: req._tx should not be used like that!
10
- return req.tx instanceof cds.DatabaseService ? req._.query : req.query
10
+ return req.tx.isDatabaseService ? req._.query : req.query
11
11
  }
12
12
 
13
13
  const getRootEntity = element => {
@@ -3,26 +3,23 @@ const LOG = cds.log('audit-log')
3
3
 
4
4
  const { getObjectAndDataSubject, getAttributeToLog } = require('./log')
5
5
 
6
- function connect(credentials) {
7
- return new Promise((resolve, reject) => {
8
- let auditLogging
9
- try {
10
- // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
11
- auditLogging = require('@sap/audit-logging')
12
- } catch (e) {
13
- // not able to require lib -> no audit logging ootb
14
- return resolve()
15
- }
16
- try {
17
- auditLogging.v2(credentials, function (err, auditLog) {
18
- if (err) return reject(err)
19
- resolve(auditLog)
20
- })
21
- } catch (e) {
22
- LOG._warn && LOG.warn('Unable to initialize audit-logging client with error:', e)
23
- return resolve()
24
- }
25
- })
6
+ async function connect(credentials) {
7
+ let auditLogging
8
+
9
+ try {
10
+ // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
11
+ auditLogging = require('@sap/audit-logging')
12
+ } catch (error) {
13
+ // not able to require lib -> no audit logging ootb
14
+ return Promise.resolve()
15
+ }
16
+
17
+ try {
18
+ return await auditLogging.v2(credentials)
19
+ } catch (error) {
20
+ LOG._warn && LOG.warn('Unable to initialize audit-logging client with error:', error)
21
+ return Promise.resolve()
22
+ }
26
23
  }
27
24
 
28
25
  function sendDataAccessLog(entry) {
@@ -255,6 +255,8 @@ class ODataRequest extends cds.Request {
255
255
  }
256
256
  })
257
257
 
258
+ Object.defineProperty(this, '_isOData', { value: true })
259
+
258
260
  /*
259
261
  * req.validateEtag()
260
262
  */
@@ -21,7 +21,8 @@ const { isStreaming, getStreamProperties } = require('../utils/stream')
21
21
  const { resolveStructuredName } = require('../utils/handlerUtils')
22
22
  const getError = require('../../../../common/error')
23
23
  const { getSapMessages } = require('../../../../common/error/frontend')
24
- const { getMaxPageSize } = require('../../../../common/utils/page')
24
+ const { getPageSize, commonGenericPaging } = require('../../../../common/generic/paging')
25
+ const { handler: commonGenericSorting } = require('../../../../common/generic/sorting')
25
26
 
26
27
  /**
27
28
  * Checks whether a bound function or function import is invoked.
@@ -255,8 +256,12 @@ const _reliablePagingPossible = req => {
255
256
  if (req.target._isDraftEnabled) return false
256
257
  if (cds.context?.http.req.query.$apply) return false
257
258
  if (req.query.SELECT.limit.offset?.val ?? req.query.SELECT.limit.offset > 0) return false
258
- if (req.query.SELECT.orderBy.some(o => !o.ref)) return false
259
- return req.query.SELECT.orderBy.every(o => req.query.SELECT.columns.some(c => o.ref[0] === c.ref[0]))
259
+ if (req.query.SELECT.orderBy?.some(o => !o.ref)) return false
260
+ return (
261
+ !req.query.SELECT.columns ||
262
+ req.query.SELECT.columns.some(c => c === '*' || c.ref?.[0] === '*') ||
263
+ req.query.SELECT.orderBy?.every(o => req.query.SELECT.columns?.some(c => o.ref[0] === c.ref?.[0]))
264
+ )
260
265
  }
261
266
 
262
267
  /**
@@ -270,6 +275,8 @@ const _reliablePagingPossible = req => {
270
275
  */
271
276
  // eslint-disable-next-line complexity
272
277
  const _readCollection = async (tx, req, odataReq) => {
278
+ commonGenericPaging(req)
279
+ commonGenericSorting(req)
273
280
  const result = (await tx.dispatch(req)) || []
274
281
  if (Array.isArray(req.query)) {
275
282
  const adjustedResult = []
@@ -288,7 +295,7 @@ const _readCollection = async (tx, req, odataReq) => {
288
295
  } else if (req.query.SELECT.count && !('$count' in result)) result.$count = 0
289
296
 
290
297
  const limit = Array.isArray(req.query)
291
- ? getMaxPageSize(req.query[0]._target)
298
+ ? getPageSize(req.query[0]._target).max
292
299
  : req.query.SELECT.limit && req.query.SELECT.limit.rows && req.query.SELECT.limit.rows.val
293
300
  const top = odataReq.getUriInfo().getQueryOption(QueryOptions.TOP)
294
301
  if (limit && limit === result.length && limit !== top && !('$nextLink' in result)) {
@@ -11,7 +11,6 @@ const { isReturnMinimal } = require('../utils/handlerUtils')
11
11
  const { readAfterWrite } = require('../utils/readAfterWrite')
12
12
  const { toODataResult, postProcess, postProcessMinimal } = require('../utils/result')
13
13
  const { hasOmitValuesPreference } = require('../utils/omitValues')
14
- const { isStreaming } = require('../utils/stream')
15
14
 
16
15
  const { getSapMessages } = require('../../../../common/error/frontend')
17
16
 
@@ -36,7 +36,7 @@ const expandToCQN = require('./expandToCQN')
36
36
  const { resolveStructuredName } = require('../utils/handlerUtils')
37
37
  const { isStreaming } = require('../utils/stream')
38
38
 
39
- const { getMaxPageSize, getDefaultPageSize } = require('../../../../common/utils/page')
39
+ const { getPageSize } = require('../../../../common/generic/paging')
40
40
  const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
41
41
 
42
42
  const { ensureUnlocalized } = require('../../../../fiori/utils/handler')
@@ -139,9 +139,9 @@ const _apply = (uriInfo, queryOptions, entity, model) => {
139
139
 
140
140
  const _topSkip = (queryOptions, target, cqn) => {
141
141
  if (queryOptions && (queryOptions.$top || queryOptions.$skip)) {
142
- const top = queryOptions.$top ? parseInt(queryOptions.$top) : getDefaultPageSize(target)
142
+ const top = queryOptions.$top ? parseInt(queryOptions.$top) : getPageSize(target).default
143
143
  const skip = parseInt(queryOptions.$skip || 0)
144
- cqn.limit(Math.min(top, getMaxPageSize(target)), skip)
144
+ cqn.limit(Math.min(top, getPageSize(target).max), skip)
145
145
  }
146
146
  }
147
147
 
@@ -9,7 +9,7 @@ const { deepCopy } = require('../../../../common/utils/copy')
9
9
  const { getSegmentKeyValue } = require('../odata-to-cqn/utils')
10
10
 
11
11
  const _isFunctionInvocation = req =>
12
- req.getUriInfo().getLastSegment().getFunction || req.getUriInfo().getLastSegment().getFunctionImport
12
+ req.getUriInfo().getLastSegment().getFunction() || req.getUriInfo().getLastSegment().getFunctionImport()
13
13
 
14
14
  const _addStructuredProperties = ([structName, property, ...nestedProperties], paramData, value) => {
15
15
  paramData[structName] = paramData[structName] || {}
@@ -94,14 +94,14 @@ const _columnsFromQuery = (columns, target, options) => {
94
94
  }
95
95
 
96
96
  const _processFn = columns => {
97
- return ({ row, key, element, pathSegments }) => {
97
+ return ({ row, key, element, pathSegmentsInfo }) => {
98
98
  if (!(key in row) || row[key] === null) return
99
99
  let cur = columns
100
100
  if (element.parent._isStructured) {
101
- const prefix = pathSegments.join('/')
101
+ const prefix = pathSegmentsInfo.join('/')
102
102
  key = `${prefix}/${key}`
103
103
  } else {
104
- for (let p of pathSegments) {
104
+ for (let p of pathSegmentsInfo) {
105
105
  if (!cur[p]) cur[p] = {}
106
106
  cur = cur[p]
107
107
  }
@@ -116,7 +116,7 @@ const _columnsFromData = (data, definition, service) => {
116
116
  if (!template || !template.elements.size) return ''
117
117
  const arrayData = Array.isArray(data) ? data : data ? [data] : []
118
118
  for (const row of arrayData) {
119
- templateProcessor({ processFn: _processFn(columns), row, template, pathOptions: { pathSegments: [] } })
119
+ templateProcessor({ processFn: _processFn(columns), row, template, pathOptions: { pathSegmentsInfo: [] } })
120
120
  }
121
121
  return _stringifyColumnsFromData(columns)
122
122
  }
@@ -1,4 +1,4 @@
1
- const { getMaxPageSize } = require('../../../../common/utils/page')
1
+ const { getPageSize } = require('../../../../common/generic/paging')
2
2
  const { findCsnTargetFor } = require('../../../../common/utils/csn')
3
3
 
4
4
  const _getEntitySets = (edm, namespace) => {
@@ -37,7 +37,7 @@ const oDataConfiguration = (edm, csn) => {
37
37
  const e = findCsnTargetFor(entitySet, csn, namespace)
38
38
 
39
39
  configuration[entitySet] = {
40
- maxPageSize: getMaxPageSize(e),
40
+ maxPageSize: getPageSize(e).max,
41
41
  isConcurrent: !!e._etag
42
42
  }
43
43