@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.
- package/CHANGELOG.md +59 -2
- package/README.md +1 -1
- package/apis/connect.d.ts +11 -4
- package/apis/core.d.ts +1 -1
- package/apis/csn.d.ts +1 -0
- package/apis/internal/inference.d.ts +15 -2
- package/apis/log.d.ts +10 -0
- package/apis/serve.d.ts +4 -9
- package/apis/services.d.ts +86 -19
- package/bin/build/buildTaskEngine.js +16 -42
- package/bin/build/constants.js +4 -2
- package/bin/build/provider/buildTaskProviderInternal.js +117 -85
- package/bin/build/provider/hana/index.js +6 -1
- package/bin/build/provider/mtx-extension/index.js +74 -34
- package/bin/build/provider/mtx-sidecar/index.js +3 -3
- package/bin/build/provider/nodejs/index.js +2 -2
- package/bin/build/util.js +63 -14
- package/bin/cds-serve.js +6 -0
- package/bin/cds.js +20 -4
- package/bin/deploy/to-hana/cfUtil.js +15 -1
- package/bin/mtx/in-cds.js +2 -9
- package/bin/plugins.js +31 -0
- package/bin/serve.js +12 -12
- package/lib/compile/etc/_localized.js +1 -1
- package/lib/compile/for/lean_drafts.js +22 -6
- package/lib/compile/for/nodejs.js +4 -1
- package/lib/compile/load.js +4 -2
- package/lib/core/index.js +35 -15
- package/lib/dbs/cds-deploy.js +129 -133
- package/lib/env/cds-env.js +25 -17
- package/lib/env/cds-requires.js +10 -40
- package/lib/env/compat.js +12 -0
- package/lib/env/defaults.js +17 -9
- package/lib/env/plugins.js +29 -0
- package/lib/env/schemas/cds-rc.json +14 -0
- package/lib/index.js +3 -0
- package/lib/log/cds-log.js +7 -4
- package/lib/ql/CREATE.js +1 -1
- package/lib/ql/DELETE.js +1 -1
- package/lib/ql/DROP.js +3 -3
- package/lib/ql/INSERT.js +1 -1
- package/lib/ql/Query.js +14 -6
- package/lib/ql/SELECT.js +8 -2
- package/lib/ql/UPDATE.js +1 -1
- package/lib/ql/Whereable.js +1 -1
- package/lib/ql/cds-ql.js +1 -9
- package/lib/req/cds-context.js +1 -4
- package/lib/req/request.js +63 -2
- package/lib/req/response.js +3 -2
- package/lib/srv/bindings.js +69 -71
- package/lib/srv/cds-connect.js +4 -1
- package/lib/srv/cds-serve.js +4 -0
- package/lib/srv/middlewares/index.js +37 -6
- package/lib/srv/protocols/_legacy.js +1 -1
- package/lib/srv/protocols/index.js +1 -1
- package/lib/srv/srv-api.js +4 -6
- package/lib/srv/srv-dispatch.js +4 -3
- package/lib/srv/srv-handlers.js +1 -1
- package/lib/srv/srv-methods.js +8 -2
- package/lib/utils/cds-test.js +4 -1
- package/libx/_runtime/audit/Service.js +8 -9
- package/libx/_runtime/audit/generic/personal/index.js +1 -1
- package/libx/_runtime/audit/generic/personal/utils.js +1 -1
- package/libx/_runtime/audit/utils/v2.js +17 -20
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +11 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +4 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -1
- package/libx/_runtime/cds-services/services/Service.js +1 -1
- package/libx/_runtime/cds-services/util/assert.js +41 -65
- package/libx/_runtime/common/code-ext/WorkerPool.js +90 -0
- package/libx/_runtime/common/code-ext/WorkerReq.js +0 -4
- package/libx/_runtime/common/code-ext/execute.js +28 -18
- package/libx/_runtime/common/code-ext/handlers.js +5 -4
- package/libx/_runtime/common/code-ext/worker.js +45 -3
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +8 -7
- package/libx/_runtime/common/composition/delete.js +1 -1
- package/libx/_runtime/common/composition/update.js +3 -5
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/readOnly.js +5 -4
- package/libx/_runtime/common/generic/auth/restrict.js +7 -2
- package/libx/_runtime/common/generic/crud.js +12 -1
- package/libx/_runtime/common/generic/etag.js +11 -3
- package/libx/_runtime/common/generic/input.js +8 -6
- package/libx/_runtime/common/generic/paging.js +25 -8
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +0 -1
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +2 -2
- package/libx/_runtime/common/utils/resolveView.js +14 -10
- package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -3
- package/libx/_runtime/common/utils/templateProcessor.js +15 -17
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +18 -6
- package/libx/_runtime/db/Service.js +1 -0
- package/libx/_runtime/db/data-conversion/post-processing.js +0 -18
- package/libx/_runtime/db/expand/expand-v2.js +2 -2
- package/libx/_runtime/db/expand/rawToExpanded.js +6 -6
- package/libx/_runtime/db/generic/integrity.js +1 -1
- package/libx/_runtime/db/utils/columns.js +5 -5
- package/libx/_runtime/fiori/generic/activate.js +3 -3
- package/libx/_runtime/fiori/generic/edit.js +1 -1
- package/libx/_runtime/fiori/generic/new.js +4 -0
- package/libx/_runtime/fiori/lean-draft.js +138 -46
- package/libx/_runtime/hana/execute.js +3 -1
- package/libx/_runtime/hana/pool.js +10 -2
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +6 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/remote/Service.js +16 -13
- package/libx/_runtime/remote/utils/client.js +6 -1
- package/libx/_runtime/sqlite/Service.js +5 -59
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -2
- package/libx/_runtime/sqlite/execute.js +3 -1
- package/libx/_runtime/types/api.js +12 -3
- package/libx/odata/afterburner.js +36 -0
- package/libx/odata/cqn2odata.js +1 -1
- package/libx/odata/grammar.pegjs +5 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +1 -1
- package/libx/rest/RestAdapter.js +1 -1
- package/libx/rest/RestRequest.js +1 -0
- package/package.json +5 -2
- package/libx/_runtime/common/code-ext/workerQuery.js +0 -45
- package/libx/_runtime/common/constants/limit.js +0 -12
- package/libx/_runtime/common/utils/page.js +0 -39
package/lib/srv/bindings.js
CHANGED
|
@@ -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
|
-
|
|
10
|
+
static get registry(){ return registry }
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
72
|
-
return this.store()
|
|
67
|
+
}
|
|
73
68
|
}
|
|
69
|
+
process.on ('exit', ()=>this.purge())
|
|
70
|
+
return this.store()
|
|
71
|
+
}
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
92
|
+
module['exports'] = { then: (r) => r() }
|
|
95
93
|
}
|
|
96
94
|
/* eslint no-console:off */
|
package/lib/srv/cds-connect.js
CHANGED
|
@@ -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
|
|
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
|
|
package/lib/srv/cds-serve.js
CHANGED
|
@@ -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
|
|
11
|
-
trace
|
|
12
|
-
auth
|
|
13
|
-
ctx_auth
|
|
14
|
-
ctx_model
|
|
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 }
|
package/lib/srv/srv-api.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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') }
|
package/lib/srv/srv-dispatch.js
CHANGED
|
@@ -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
|
|
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 ?
|
package/lib/srv/srv-handlers.js
CHANGED
|
@@ -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.
|
|
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
|
package/lib/srv/srv-methods.js
CHANGED
|
@@ -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 =
|
|
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
|
|
92
|
+
Object.defineProperties(stub,{
|
|
93
|
+
name: {value: /[^.]+$/.exec(srv.name)[0] +'.'+ event},
|
|
94
|
+
_is_stub: {value:true},
|
|
95
|
+
})
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
|
package/lib/utils/cds-test.js
CHANGED
|
@@ -164,4 +164,7 @@ const spy = (o,f) => {
|
|
|
164
164
|
|
|
165
165
|
|
|
166
166
|
/** @type Test & ()=>Test */
|
|
167
|
-
module.exports = Object.
|
|
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:
|
|
10
|
-
tenant: cds.context
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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) {
|
|
@@ -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 {
|
|
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
|
|
259
|
-
return
|
|
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
|
-
?
|
|
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 {
|
|
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) :
|
|
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,
|
|
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,
|
|
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 =
|
|
101
|
+
const prefix = pathSegmentsInfo.join('/')
|
|
102
102
|
key = `${prefix}/${key}`
|
|
103
103
|
} else {
|
|
104
|
-
for (let p of
|
|
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: {
|
|
119
|
+
templateProcessor({ processFn: _processFn(columns), row, template, pathOptions: { pathSegmentsInfo: [] } })
|
|
120
120
|
}
|
|
121
121
|
return _stringifyColumnsFromData(columns)
|
|
122
122
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
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:
|
|
40
|
+
maxPageSize: getPageSize(e).max,
|
|
41
41
|
isConcurrent: !!e._etag
|
|
42
42
|
}
|
|
43
43
|
|