@sap/cds 5.9.0 → 5.9.3
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 +35 -0
- package/app/fiori/routes.js +15 -8
- package/lib/compile/cdsc.js +1 -19
- package/lib/compile/etc/_localized.js +5 -4
- package/lib/compile/for/drafts.js +1 -1
- package/lib/compile/for/java.js +1 -1
- package/lib/compile/for/nodejs.js +1 -1
- package/lib/compile/for/odata.js +1 -1
- package/lib/connect/bindings.js +1 -1
- package/lib/connect/index.js +2 -3
- package/lib/env/requires.js +1 -1
- package/lib/index.js +2 -1
- package/lib/serve/Service-methods.js +28 -1
- package/lib/serve/adapters.js +6 -6
- package/lib/serve/factory.js +14 -9
- package/lib/serve/index.js +4 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/BatchRequestListBuilder.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +13 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -3
- package/libx/_runtime/cds-services/services/Service.js +1 -1
- package/libx/_runtime/common/composition/data.js +22 -13
- package/libx/_runtime/common/composition/delete.js +14 -12
- package/libx/_runtime/common/generic/auth/restrict.js +3 -1
- package/libx/_runtime/{cds-services/services/utils → common/generic/auth}/restrictions.js +8 -1
- package/libx/_runtime/common/generic/input.js +1 -0
- package/libx/_runtime/common/generic/put.js +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -10
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +39 -65
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +28 -8
- package/libx/_runtime/common/utils/path.js +3 -3
- package/libx/_runtime/common/utils/resolveView.js +3 -0
- package/libx/_runtime/common/utils/structured.js +6 -1
- package/libx/_runtime/db/Service.js +10 -0
- package/libx/_runtime/db/data-conversion/post-processing.js +5 -0
- package/libx/_runtime/db/expand/expand-v2.js +13 -5
- package/libx/_runtime/db/expand/expandCQNToJoin.js +56 -26
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +8 -8
- package/libx/_runtime/db/utils/generateAliases.js +9 -0
- package/libx/_runtime/extensibility/uiflex/handler/transformWRITE.js +1 -0
- package/libx/_runtime/fiori/generic/read.js +83 -31
- package/libx/_runtime/fiori/generic/readOverDraft.js +31 -19
- package/libx/_runtime/fiori/utils/handler.js +3 -0
- package/libx/_runtime/fiori/utils/where.js +38 -25
- package/libx/_runtime/hana/execute.js +18 -1
- package/libx/_runtime/hana/search2cqn4sql.js +4 -1
- package/libx/_runtime/remote/utils/client.js +4 -6
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +19 -12
- package/libx/odata/cqn2odata.js +24 -27
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,41 @@
|
|
|
4
4
|
- The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
5
5
|
- This project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
|
|
7
|
+
## Version 5.9.3 - 2022-04-25
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Since 5.8.2 `req.target` for requests like `srv.put('/MyService.entity')` is defined, but `req.query` undefined (before `req.target` was also undefined). This was leading to accessing undefined, which has been fixed.
|
|
12
|
+
- Custom actions with names conflicting with methods from service base classes, e.g. `run()`, could lead to hard-to-detect errors. This is now detected and avoided with a warning.
|
|
13
|
+
- Typed methods for custom actions were erroneously applied to `cds.db` service, which led to server crashes, e.g. when the action was named `deploy()`.
|
|
14
|
+
- Invalid batch requests were further processed after error response was already sent to client, leading to an InternalServerError
|
|
15
|
+
- Full support of `SELECT` queries with operator expressions (`xpr`)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Version 5.9.2 - 2022-04-07
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- i18n translation for errors did not work correctly in some cases
|
|
23
|
+
- Normalization in custom `getRestrictions`
|
|
24
|
+
- Throw exception by `INSERT` into HANA queries if number of provided rows deviates from number of affected rows returned by hdb to prevent data losses
|
|
25
|
+
- Handler detection for extended services
|
|
26
|
+
- Speed-up in localization handling
|
|
27
|
+
- Draft: navigation via an association to many from a non-draft enabled entity to a draft-enabled entity
|
|
28
|
+
- Limited support of `SELECT` queries with operator expressions (`xpr`)
|
|
29
|
+
|
|
30
|
+
## Version 5.9.1 - 2022-03-31
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Function arguments might be escaped too often
|
|
35
|
+
- URL encoding for remote services for CQN queries
|
|
36
|
+
- `cds serve` during development again redirects URLs with for UI apps in a folder with the same name as a service, so `/foo/webapp` would redirect to `/foo`. This got broken in 5.8.3.
|
|
37
|
+
- Endless loop in localization handling
|
|
38
|
+
- Ensure service impl while extending entity from the service
|
|
39
|
+
- Post-processing of custom draft queries
|
|
40
|
+
- No minifying of CSN artifacts for Java build
|
|
41
|
+
|
|
7
42
|
## Version 5.9.0 - 2022-03-25
|
|
8
43
|
|
|
9
44
|
### Added
|
package/app/fiori/routes.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const cds = require('../../lib')
|
|
2
|
+
const DEBUG = cds.debug('fiori/routes')
|
|
3
|
+
const {dirname, relative, join} = require('path')
|
|
2
4
|
|
|
3
5
|
// Only for local cds runs w/o approuter:
|
|
4
6
|
// If there is a relative URL in UI5's manifest.json for the datasource,
|
|
@@ -11,16 +13,20 @@ cds.on ('bootstrap', app => {
|
|
|
11
13
|
const v2Prefix = (env.odata.v2proxy && env.odata.v2proxy.urlpath) || '/v2'
|
|
12
14
|
const serviceForUri = {}
|
|
13
15
|
|
|
14
|
-
dataSourceURIs (env.folders.app).forEach(
|
|
15
|
-
|
|
16
|
+
dataSourceURIs (env.folders.app).forEach(({appPath, dataSourceUri}) => {
|
|
17
|
+
const uiRoutes = [
|
|
18
|
+
join('/', appPath, dataSourceUri, '*'), // /uiApp/webapp/browse/*
|
|
19
|
+
join('/', appPath, '*', dataSourceUri, '*') // /uiApp/webapp/*/browse/*
|
|
20
|
+
].map(r => r.replace(/\\/g, '/')) // handle Windows \
|
|
21
|
+
DEBUG && DEBUG ('Register routes', uiRoutes)
|
|
22
|
+
|
|
23
|
+
app.use(uiRoutes, ({originalUrl}, res, next)=> {
|
|
16
24
|
// any of our special URLs ($fiori-, $api-docs) ? -> next
|
|
17
25
|
if (originalUrl.startsWith('/$')) return next()
|
|
18
|
-
// is there a service starting with the URL? -> next
|
|
19
|
-
if (cds.service.providers.find (srv => originalUrl.startsWith(srv.path))) return next()
|
|
20
26
|
|
|
21
27
|
// is there a service for '[prefix]/browse' ?
|
|
22
|
-
const srv = serviceForUri[
|
|
23
|
-
cds.service.providers.find (srv => ('/'+
|
|
28
|
+
const srv = serviceForUri[dataSourceUri] || (serviceForUri[dataSourceUri] =
|
|
29
|
+
cds.service.providers.find (srv => ('/'+dataSourceUri).lastIndexOf(srv.path) >=0))
|
|
24
30
|
if (srv) {
|
|
25
31
|
let redirectUrl
|
|
26
32
|
// odata-proxy may be in the line with its /v2 prefix. Make sure we retain it.
|
|
@@ -30,7 +36,7 @@ cds.on ('bootstrap', app => {
|
|
|
30
36
|
else // --> /browse/webapp[/prefix]/browse/ -> /browse
|
|
31
37
|
redirectUrl = originalUrl.substring(originalUrl.lastIndexOf(srv.path+'/'))
|
|
32
38
|
if (originalUrl !== redirectUrl) {// safeguard to prevent running in loops
|
|
33
|
-
|
|
39
|
+
DEBUG && DEBUG ('Redirecting', {src: originalUrl}, '~>', {target: redirectUrl})
|
|
34
40
|
return res.redirect (308, redirectUrl)
|
|
35
41
|
}
|
|
36
42
|
}
|
|
@@ -41,10 +47,11 @@ cds.on ('bootstrap', app => {
|
|
|
41
47
|
function dataSourceURIs (dir) {
|
|
42
48
|
const uris = new Set()
|
|
43
49
|
find (dir, ['*/manifest.json', '*/*/manifest.json']).forEach(file => {
|
|
50
|
+
const appPath = relative(join(cds.root, dir), dirname(file))
|
|
44
51
|
const {dataSources: ds} = JSON.parse(fs.readFileSync(file))['sap.app'] || {}
|
|
45
52
|
Object.keys (ds||[])
|
|
46
53
|
.filter (k => ds[k].uri && !ds[k].uri.startsWith('/')) // only consider relative URLs)
|
|
47
|
-
.forEach(k => uris.add(ds[k].uri))
|
|
54
|
+
.forEach(k => uris.add({ appPath, dataSourceUri: ds[k].uri }))
|
|
48
55
|
})
|
|
49
56
|
return uris
|
|
50
57
|
}
|
package/lib/compile/cdsc.js
CHANGED
|
@@ -105,25 +105,7 @@ const _options = {for: Object.assign (_options4, {
|
|
|
105
105
|
*/
|
|
106
106
|
module.exports = exports = {__proto__:compile, _options,
|
|
107
107
|
for: {__proto__: compile.for,
|
|
108
|
-
odata: (csn,o) =>
|
|
109
|
-
if (features.ucsn) {
|
|
110
|
-
const { cloneCsn } = require('@sap/cds-compiler/lib/model/csnUtils') // REVISIT: This should be done by the compiler
|
|
111
|
-
if (compile.version() >= "2.12.1") {
|
|
112
|
-
const generateDrafts = require('@sap/cds-compiler/lib/transform/draft/odata')
|
|
113
|
-
const compiled = generateDrafts(cloneCsn(csn, {}), { messages: [] })
|
|
114
|
-
compiled.meta._4odata = true
|
|
115
|
-
return compiled
|
|
116
|
-
} else {
|
|
117
|
-
// not yet in compiler branch, can't add drafts
|
|
118
|
-
const cloned = cloneCsn(csn, {})
|
|
119
|
-
cloned.meta._4odata = true
|
|
120
|
-
return cloned
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
const compiled = compile.for.odata (csn, _options.for.odata(o))
|
|
124
|
-
compiled.meta._4odata = true
|
|
125
|
-
return compiled
|
|
126
|
-
},
|
|
108
|
+
odata: (csn,o) => compile.for.odata (csn, _options.for.odata(o)),
|
|
127
109
|
},
|
|
128
110
|
to: {__proto__: compile.to,
|
|
129
111
|
edmx: Object.assign ((csn,o) => compile.to.edmx (csn, _options.for.edm(o)), {
|
|
@@ -96,16 +96,17 @@ function unfold_csn (m) { // NOSONAR
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
const $localized = '$$localized', _is_localized = (d,_path) => {
|
|
100
|
-
if (d.own($localized)) return
|
|
101
|
-
if (!d.elements || d.name.endsWith('.texts')) return false
|
|
99
|
+
const $localized = '$$localized', _is_localized = (d,_path={}) => {
|
|
100
|
+
if (typeof d.own($localized) === 'boolean') return d.own($localized)
|
|
101
|
+
if (!d.elements || d.name.endsWith('.texts')) return d.set($localized,false)
|
|
102
102
|
// if (d.elements.texts && d.elements.texts.target === `${d.name}.texts`) return d.set($localized,true)
|
|
103
103
|
for (let each in d.elements) {
|
|
104
104
|
const e = d.elements [each]
|
|
105
|
-
if (e.localized || e._target && !(_path && e._target.name in _path) && _is_localized(e._target,
|
|
105
|
+
if (e.localized || e._target && !(_path && e._target.name in _path) && _is_localized(e._target,Object.assign(_path, { [d.name]:1 }))) {
|
|
106
106
|
return d.set($localized,true)
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
return d.set($localized,false)
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
|
|
@@ -4,6 +4,6 @@ module.exports = function cds_compile_for_drafts (csn,o) {
|
|
|
4
4
|
const unfold = cds_compile_for_drafts.unfold || (
|
|
5
5
|
cds_compile_for_drafts.unfold = require ('@sap/cds-compiler/lib/transform/draft/odata')
|
|
6
6
|
)
|
|
7
|
-
|
|
7
|
+
csn = JSON.parse (JSON.stringify (csn)) // REVISIT: workaround for bad test setup
|
|
8
8
|
return unfold (csn,o||{})
|
|
9
9
|
}
|
package/lib/compile/for/java.js
CHANGED
|
@@ -4,7 +4,7 @@ const _cached = Symbol('for Java')
|
|
|
4
4
|
module.exports = function cds_compile_for_java (csn,o) {
|
|
5
5
|
if (!csn) return
|
|
6
6
|
const cached = csn[_cached]; if (cached) return cached
|
|
7
|
-
csn = cds.minify (csn)
|
|
7
|
+
// csn = cds.minify (csn)
|
|
8
8
|
csn = cds.compile.for.drafts (csn,o)
|
|
9
9
|
// Add a parsed _where clause for @restrict.{grant,where} annotations
|
|
10
10
|
if (csn.definitions) for (let {'@restrict':rr} of Object.values(csn.definitions)) if (rr) {
|
|
@@ -4,7 +4,7 @@ const _cached = Symbol('for Node.js')
|
|
|
4
4
|
module.exports = function cds_compile_for_nodejs (csn,o) {
|
|
5
5
|
if (!csn) return
|
|
6
6
|
const cached = csn[_cached]; if (cached) return cached
|
|
7
|
-
csn = cds.minify (csn)
|
|
7
|
+
// csn = cds.minify (csn)
|
|
8
8
|
csn = cds.compile.for.drafts (csn,o) //> creates a partial copy -> avoid any cds.linked() before
|
|
9
9
|
csn = cds.compile._localized.unfold_csn (csn)
|
|
10
10
|
csn = cds.linked (csn)
|
package/lib/compile/for/odata.js
CHANGED
package/lib/connect/bindings.js
CHANGED
package/lib/connect/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const cds = require('..'), {one_model} = cds.env.features, LOG = cds.log('cds.connect')
|
|
2
|
-
const factory = require('../serve/factory')
|
|
3
2
|
const _pending = cds.services._pending || {} // used below to chain parallel connect.to(<same>)
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -22,7 +21,7 @@ const connect = module.exports = async function cds_connect (options) {
|
|
|
22
21
|
* @returns { Promise<import('../serve/Service-api')> }
|
|
23
22
|
*/
|
|
24
23
|
connect.to = async (datasource, options) => {
|
|
25
|
-
let Service = factory, _done = x=>x
|
|
24
|
+
let Service = cds.service.factory, _done = x=>x
|
|
26
25
|
if (typeof datasource === 'object') [options,datasource] = [datasource]
|
|
27
26
|
else if (datasource) {
|
|
28
27
|
if (datasource._is_service_class) [ Service, datasource ] = [ datasource, datasource.name ]
|
|
@@ -33,7 +32,7 @@ connect.to = async (datasource, options) => {
|
|
|
33
32
|
// queue parallel requests to a single promise, to avoid creating multiple services
|
|
34
33
|
_pending[datasource] = new Promise (r=>_done=r).finally(()=>{ delete _pending[datasource] })
|
|
35
34
|
}
|
|
36
|
-
const o = Service === factory ? options4 (datasource, options) : {}
|
|
35
|
+
const o = Service === cds.service.factory ? options4 (datasource, options) : {}
|
|
37
36
|
const m = await model4 (o)
|
|
38
37
|
// check if required service definition exists
|
|
39
38
|
const required = cds.requires[datasource]
|
package/lib/env/requires.js
CHANGED
package/lib/index.js
CHANGED
|
@@ -12,6 +12,7 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
|
|
|
12
12
|
/** @type {{ [path:string] : Service }} */ paths: {},
|
|
13
13
|
/** @type Service[] */ providers: [],
|
|
14
14
|
factory: require ('./serve/factory'),
|
|
15
|
+
adapters: require ('./serve/adapters'),
|
|
15
16
|
bindings: require ('./connect/bindings'),
|
|
16
17
|
})}
|
|
17
18
|
/** @type {import './req/context'} */
|
|
@@ -80,7 +81,7 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
|
|
|
80
81
|
ApplicationService: lazy => require('../libx/_runtime/cds-services/services/Service.js'),
|
|
81
82
|
MessagingService: lazy => require('../libx/_runtime/messaging/service.js'),
|
|
82
83
|
DatabaseService: lazy => require('../libx/_runtime/db/Service.js'),
|
|
83
|
-
RemoteService: lazy => require('../libx/_runtime/
|
|
84
|
+
RemoteService: lazy => require('../libx/_runtime/remote/Service.js'),
|
|
84
85
|
AuditLogService: lazy => require('../libx/_runtime/audit/Service.js'),
|
|
85
86
|
odata: require('../libx/odata'),
|
|
86
87
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
const cds = require('..')
|
|
2
|
+
const LOG = cds.log('cds-app-service-methods')
|
|
3
|
+
|
|
1
4
|
|
|
2
5
|
module.exports = (srv) => {
|
|
6
|
+
if (!( //> we only support that for app services
|
|
7
|
+
srv instanceof cds.ApplicationService ||
|
|
8
|
+
srv instanceof cds.RemoteService
|
|
9
|
+
)) return
|
|
3
10
|
for (const each of srv.operations) {
|
|
4
11
|
add_handler_for (srv, each)
|
|
5
12
|
}
|
|
@@ -16,7 +23,23 @@ const add_handler_for = (srv, def) => {
|
|
|
16
23
|
// Use existing methods as handler implementations
|
|
17
24
|
const method = srv[event]
|
|
18
25
|
if (method) {
|
|
19
|
-
if (method._is_stub
|
|
26
|
+
if (method._is_stub) return
|
|
27
|
+
const baseclass = (
|
|
28
|
+
srv.__proto__ === cds.ApplicationService.prototype ? srv.__proto__ :
|
|
29
|
+
srv.__proto__ === cds.RemoteService.prototype ? srv.__proto__ :
|
|
30
|
+
srv.__proto__.__proto__ // in case of class-based impls
|
|
31
|
+
)
|
|
32
|
+
if (method.name in baseclass) return LOG.warn(`WARNING: custom ${def.kind} '${event}()' conflicts with method in base class.
|
|
33
|
+
|
|
34
|
+
Cannot add typed method for custom ${def.kind} '${event}' to service impl of '${srv.name}',
|
|
35
|
+
as this would shadow equally named method in service base class '${baseclass.constructor.name}'.
|
|
36
|
+
Consider choosing a different name for your custom ${def.kind}.
|
|
37
|
+
Learn more at https://cap.cloud.sap/docs/guides/providing-services#actions-and-functions.
|
|
38
|
+
`)
|
|
39
|
+
LOG.debug (`
|
|
40
|
+
Using method ${event} from service class '${baseclass.constructor.name}'
|
|
41
|
+
as handler for ${def.kind} '${event}' in service '${srv.name}'
|
|
42
|
+
`)
|
|
20
43
|
srv.on (event, function ({params,data}) {
|
|
21
44
|
const args = []; if (def.parent) args.push (def.parent)
|
|
22
45
|
for (let p in params) args.push(params[p])
|
|
@@ -26,6 +49,10 @@ const add_handler_for = (srv, def) => {
|
|
|
26
49
|
}
|
|
27
50
|
|
|
28
51
|
// Add stub methods to send request via typed API
|
|
52
|
+
LOG.debug (`
|
|
53
|
+
Adding typed method stub for calling custom ${def.kind} '${event}'
|
|
54
|
+
to service impl '${srv.name}'
|
|
55
|
+
`)
|
|
29
56
|
const stub = srv[event] = function (...args) {
|
|
30
57
|
const req = { event, data:{} }, $ = args[0]
|
|
31
58
|
const target = this.entities [ $ && $.name ? $.name.match(/\w*$/)[0] : $ ]
|
package/lib/serve/adapters.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const lib = require('../../libx/_runtime')
|
|
2
2
|
const registry = {
|
|
3
|
-
rest
|
|
4
|
-
new_rest
|
|
5
|
-
odata
|
|
6
|
-
odata_v2
|
|
7
|
-
odata_v4
|
|
8
|
-
fiori
|
|
3
|
+
get rest() { return lib.to.old_rest },
|
|
4
|
+
get new_rest() { return lib.to.new_rest },
|
|
5
|
+
get odata() { return lib.to.odata_v4 },
|
|
6
|
+
get odata_v2() { return lib.to.odata_v4 },
|
|
7
|
+
get odata_v4() { return lib.to.odata_v4 },
|
|
8
|
+
get fiori() { return lib.to.odata_v4 },
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
|
package/lib/serve/factory.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
const cds = require('..')
|
|
2
|
-
const
|
|
1
|
+
const cds = require('..'), { path, isfile } = cds.utils
|
|
2
|
+
const paths = Array.from (new Set ([ cds.root, ...require.resolve.paths('x') ]))
|
|
3
|
+
const DEBUG = cds.debug('srv.factory'); DEBUG && DEBUG ({ 'cds.root':cds.root, paths })
|
|
3
4
|
|
|
4
5
|
/** @typedef {import('./Service-api')} Service @type { (()=>Service) & (new()=>Service) } */
|
|
5
6
|
const ServiceFactory = function (name, model, options) { //NOSONAR
|
|
@@ -8,6 +9,7 @@ const ServiceFactory = function (name, model, options) { //NOSONAR
|
|
|
8
9
|
const serve = !cds.requires[name] || o.mocked
|
|
9
10
|
const defs = !model ? {[name]:{}} : model.definitions || cds.error (`Invalid argument for 'model': ${model}`)
|
|
10
11
|
const def = !name || name === 'db' ? {} : defs[name] || {}
|
|
12
|
+
DEBUG && DEBUG ({ name, definition:def, options:o })
|
|
11
13
|
|
|
12
14
|
let it /* eslint-disable no-cond-assign */
|
|
13
15
|
if (it = o.with) return _use (it) // from cds.serve (<options>)
|
|
@@ -27,21 +29,24 @@ const ServiceFactory = function (name, model, options) { //NOSONAR
|
|
|
27
29
|
|
|
28
30
|
function _required() {
|
|
29
31
|
const kind = o.kind = serve && def['@kind'] || o.kind || 'app-service'
|
|
30
|
-
if (
|
|
32
|
+
if (_require[kind]) return _require[kind]
|
|
31
33
|
const {impl} = cds.requires[kind] || cds.error (`No configuration found for 'cds.requires.${kind}'`)
|
|
32
|
-
|
|
34
|
+
DEBUG && DEBUG ('requires',{kind,impl})
|
|
35
|
+
return _require[kind] = _require (impl || cds.error (`No 'impl' configured for 'cds.requires.${kind}'`))
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
const _require = (it,d) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
try { var resolved = require.resolve(it) } catch(e) {
|
|
40
|
+
DEBUG && d && DEBUG ('requires',{ service: d.name, source:_source(d), impl:it })
|
|
41
|
+
if (it.startsWith('@sap/cds/')) it = cds.home + it.slice(8) //> for local tests in @sap/cds dev
|
|
42
|
+
if (it.startsWith('./')) it = _relative (d,it.slice(2)) //> relative to <service>.cds
|
|
43
|
+
try { var resolved = require.resolve(it,{paths}) } catch(e) {
|
|
41
44
|
try { resolved = require.resolve(it = path.resolve(cds.root,it)) } catch(e) { // for compatibility
|
|
45
|
+
DEBUG && DEBUG (`Failed loading service implementation from '${it}'`, { 'cds.root':cds.root, paths })
|
|
42
46
|
throw cds.error(`Failed loading service implementation from '${it}'`)
|
|
43
47
|
}
|
|
44
48
|
}
|
|
49
|
+
DEBUG && DEBUG({resolved})
|
|
45
50
|
return require(resolved)
|
|
46
51
|
}
|
|
47
52
|
|
|
@@ -59,7 +64,7 @@ const sibling = (d) => {
|
|
|
59
64
|
let found
|
|
60
65
|
if (process.env.CDS_TYPESCRIPT === 'true') found = isfile(path.join(home, each, file + '.ts'))
|
|
61
66
|
if (!found) found = isfile(path.join(home, each, file + '.js'))
|
|
62
|
-
if (found) return found
|
|
67
|
+
if (found) return found //> equiv to '.'+found.slice(home.length)
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
70
|
|
package/lib/serve/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const { ProtocolAdapter } = require('./adapters')
|
|
2
|
-
const { Service } = require('./factory')
|
|
3
1
|
const cds = require ('..')
|
|
2
|
+
const { ProtocolAdapter } = cds.service.adapters
|
|
3
|
+
const { Service } = cds.service.factory
|
|
4
4
|
const _ready = Symbol(), _pending = cds.services._pending || {}
|
|
5
5
|
|
|
6
6
|
/** @param som - a service name or a model (name or csn) */
|
|
@@ -54,7 +54,8 @@ function cds_serve (som, _options) { // NOSONAR
|
|
|
54
54
|
// Shortcut for directly passed service classes
|
|
55
55
|
if (o.service && o.service._is_service_class) {
|
|
56
56
|
const Service = o.service, d = { name: o.service.name }
|
|
57
|
-
|
|
57
|
+
const srv = _new (Service, d,csn,o)
|
|
58
|
+
return all.push (srv)
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
// Get relevant service definitions from model...
|
|
@@ -66,7 +66,12 @@ function _log(level, arg) {
|
|
|
66
66
|
|
|
67
67
|
// reduce 4xx to warning
|
|
68
68
|
if (isClientError(obj)) {
|
|
69
|
-
if (!LOG._warn)
|
|
69
|
+
if (!LOG._warn) {
|
|
70
|
+
// restore
|
|
71
|
+
obj.message = _message
|
|
72
|
+
if (_details) obj.details = _details
|
|
73
|
+
return
|
|
74
|
+
}
|
|
70
75
|
level = 'warn'
|
|
71
76
|
}
|
|
72
77
|
}
|
|
@@ -89,7 +89,9 @@ class BatchRequestListBuilder {
|
|
|
89
89
|
source
|
|
90
90
|
.pipe(reader)
|
|
91
91
|
.on('finish', () => {
|
|
92
|
-
|
|
92
|
+
//Revisit: if statement needed in node v12 and v14, not in v16.
|
|
93
|
+
//Without it, finish callback reached in 12/14 after error handler was thrown
|
|
94
|
+
if (!source.res || !source.res.headersSent) callback(null, this._requestInBatchList)
|
|
93
95
|
})
|
|
94
96
|
.on('error', callback)
|
|
95
97
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
2
|
const OData = require('../OData')
|
|
3
|
+
const DEBUG = cds.debug('extensibility')
|
|
3
4
|
|
|
4
5
|
const { alias2ref } = require('../../../../common/utils/csn')
|
|
5
6
|
const { BASE_TENANT } = require('../../../../common/utils/extensibilityUtils')
|
|
@@ -17,13 +18,18 @@ function createOdataService(service) {
|
|
|
17
18
|
return odataService
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
async function createNewService(name,
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
await service.
|
|
25
|
-
if (
|
|
26
|
-
|
|
21
|
+
async function createNewService(name, model, options) {
|
|
22
|
+
const { constructor: Service, path } = cds.services[name]
|
|
23
|
+
const service = new Service(name, model, { ...options }) // cloning options to be safe
|
|
24
|
+
if (service.init) await service.prepend(service.init)
|
|
25
|
+
if (options.impl) await service.prepend(options.impl)
|
|
26
|
+
if (path) service.path = path
|
|
27
|
+
DEBUG &&
|
|
28
|
+
DEBUG('Created tenant-specific service:', service.name, '= new', Service.name, {
|
|
29
|
+
_handlers: {
|
|
30
|
+
on: service._handlers.on.map(h => ({ on: h.on, handler: () => {} }))
|
|
31
|
+
}
|
|
32
|
+
})
|
|
27
33
|
return createOdataService(service)
|
|
28
34
|
}
|
|
29
35
|
|
|
@@ -30,9 +30,7 @@ const _adaptSubSelectsDraft = select => {
|
|
|
30
30
|
if (element.SELECT) {
|
|
31
31
|
_adaptSubSelectsDraft(element)
|
|
32
32
|
} else if (element.xpr) {
|
|
33
|
-
|
|
34
|
-
_adaptSubSelectsDraft(ele)
|
|
35
|
-
}
|
|
33
|
+
_adaptSubSelectsDraft({ SELECT: { from: {}, where: element.xpr } })
|
|
36
34
|
}
|
|
37
35
|
}
|
|
38
36
|
}
|
|
@@ -8,7 +8,7 @@ const { postProcess } = require('../../common/utils/postProcessing')
|
|
|
8
8
|
const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
|
|
9
9
|
|
|
10
10
|
// for getRestrictions()
|
|
11
|
-
const { getNormalizedRestrictions, getApplicableRestrictions } = require('
|
|
11
|
+
const { getNormalizedRestrictions, getApplicableRestrictions } = require('../../common/generic/auth/restrictions.js')
|
|
12
12
|
const WRITE_EVENTS = { CREATE: 1, NEW: 1, UPDATE: 1, PATCH: 1, DELETE: 1, CANCEL: 1, EDIT: 1 }
|
|
13
13
|
const CRUD = Object.assign({ READ: 1 }, WRITE_EVENTS)
|
|
14
14
|
|
|
@@ -12,20 +12,13 @@ const { SELECT } = cds.ql
|
|
|
12
12
|
* own utils
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
const where = cqn.UPDATE.where || []
|
|
17
|
-
const persistentObj = Array.isArray(req._.partialPersistentState)
|
|
18
|
-
? req._.partialPersistentState[0]
|
|
19
|
-
: req._.partialPersistentState
|
|
20
|
-
if (!persistentObj) {
|
|
21
|
-
// If no data was found we don't know if it is the same entity
|
|
22
|
-
return false
|
|
23
|
-
}
|
|
24
|
-
const target = getDBTable(req.target)
|
|
25
|
-
if (target.name !== (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) && target.name !== cqn.UPDATE.entity) {
|
|
26
|
-
return false
|
|
27
|
-
}
|
|
15
|
+
const _isSameEntityInWhere = (where, target, persistentObj) => {
|
|
28
16
|
for (let i = 0; i < where.length; i++) {
|
|
17
|
+
if (where[i].xpr) {
|
|
18
|
+
const res = _isSameEntityInWhere(where[i].xpr, target, persistentObj)
|
|
19
|
+
if (!res) return res
|
|
20
|
+
continue
|
|
21
|
+
}
|
|
29
22
|
if (!where[i] || !where[i].ref || !target.elements[where[i].ref]) {
|
|
30
23
|
continue
|
|
31
24
|
}
|
|
@@ -40,6 +33,22 @@ const _isSameEntity = (cqn, req) => {
|
|
|
40
33
|
return true
|
|
41
34
|
}
|
|
42
35
|
|
|
36
|
+
const _isSameEntity = (cqn, req) => {
|
|
37
|
+
const where = cqn.UPDATE.where || []
|
|
38
|
+
const persistentObj = Array.isArray(req._.partialPersistentState)
|
|
39
|
+
? req._.partialPersistentState[0]
|
|
40
|
+
: req._.partialPersistentState
|
|
41
|
+
if (!persistentObj) {
|
|
42
|
+
// If no data was found we don't know if it is the same entity
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
const target = getDBTable(req.target)
|
|
46
|
+
if (target.name !== (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) && target.name !== cqn.UPDATE.entity) {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
return _isSameEntityInWhere(where, target, persistentObj)
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
const _getLinksOfCompTree = compositionTree => {
|
|
44
53
|
const links = []
|
|
45
54
|
for (const link of [...compositionTree.backLinks, ...compositionTree.customBackLinks]) {
|
|
@@ -216,6 +216,18 @@ const resolveNavigationTarget = (cqn, ref, model) => {
|
|
|
216
216
|
return { target, elementName }
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
const _getDataFromOncond = (onCond, parent) => {
|
|
220
|
+
return onCond.reduce((res, e) => {
|
|
221
|
+
if (e.xpr) {
|
|
222
|
+
return Object.assign(res, _getDataFromOncond(e.xpr, parent))
|
|
223
|
+
}
|
|
224
|
+
if (!e.ref || e.ref[0] !== '$$parent') return res
|
|
225
|
+
const fk = e.ref.slice(1).join('_')
|
|
226
|
+
if (!parent.keys[fk]) res[fk] = null
|
|
227
|
+
return res
|
|
228
|
+
}, {})
|
|
229
|
+
}
|
|
230
|
+
|
|
219
231
|
// eslint-disable-next-line complexity
|
|
220
232
|
const getSetNullParentForeignKeyCQNs = async (model, req, dbQuery) => {
|
|
221
233
|
const cqns = []
|
|
@@ -229,12 +241,7 @@ const getSetNullParentForeignKeyCQNs = async (model, req, dbQuery) => {
|
|
|
229
241
|
const element = parent.elements[elementName]
|
|
230
242
|
if (element && element._isCompositionEffective && element.is2one) {
|
|
231
243
|
const onCond = element && parent._relations[element.name].join('$$whatever', '$$parent')
|
|
232
|
-
const data = onCond
|
|
233
|
-
if (!e.ref || e.ref[0] !== '$$parent') return res
|
|
234
|
-
const fk = e.ref.slice(1).join('_')
|
|
235
|
-
if (!parent.keys[fk]) res[fk] = null
|
|
236
|
-
return res
|
|
237
|
-
}, {})
|
|
244
|
+
const data = _getDataFromOncond(onCond, parent)
|
|
238
245
|
cqn.data(data)
|
|
239
246
|
cqn.__4delete = true
|
|
240
247
|
if (Object.keys(data).length) cqns.push(cqn)
|
|
@@ -247,12 +254,7 @@ const getSetNullParentForeignKeyCQNs = async (model, req, dbQuery) => {
|
|
|
247
254
|
for (const element of comp2oneParents) {
|
|
248
255
|
const parent = element.parent
|
|
249
256
|
const onCond = parent._relations[element.name].join('$$child', '$$parent')
|
|
250
|
-
const data = onCond
|
|
251
|
-
if (!e.ref || e.ref[0] !== '$$parent') return res
|
|
252
|
-
const fk = e.ref.slice(1).join('_')
|
|
253
|
-
if (!parent.keys[fk]) res[fk] = null
|
|
254
|
-
return res
|
|
255
|
-
}, {})
|
|
257
|
+
const data = _getDataFromOncond(onCond, parent)
|
|
256
258
|
const columns = getColumns(parent, { _4db: true, onlyKeys: true }).map(c => ({ ref: ['$$parent', c.name] }))
|
|
257
259
|
const as = query.DELETE.from.as || (query.DELETE.from.ref && query.DELETE.from.ref[0]) || query.DELETE.from
|
|
258
260
|
const selectCQN = SELECT.from(`${parent.name} as $$parent`)
|
|
@@ -2,6 +2,7 @@ const cds = require('../../../cds')
|
|
|
2
2
|
|
|
3
3
|
const { reject, getRejectReason, resolveUserAttrs, getAuthRelevantEntity } = require('./utils')
|
|
4
4
|
const { DRAFT_EVENTS, MOD_EVENTS } = require('./constants')
|
|
5
|
+
const { getNormalizedPlainRestrictions } = require('./restrictions')
|
|
5
6
|
|
|
6
7
|
const { cqn2cqn4sql } = require('../../utils/cqn2cqn4sql')
|
|
7
8
|
|
|
@@ -250,7 +251,8 @@ async function handler(req) {
|
|
|
250
251
|
// > no applicable restrictions -> 403
|
|
251
252
|
reject(req, getRejectReason(req, '@restrict', definition))
|
|
252
253
|
}
|
|
253
|
-
|
|
254
|
+
// normalize
|
|
255
|
+
restrictions = getNormalizedPlainRestrictions(restrictions, definition)
|
|
254
256
|
// at least one if the user's roles grants unrestricted access => done
|
|
255
257
|
if (restrictions.some(restrict => !restrict.where)) return
|
|
256
258
|
|
|
@@ -72,7 +72,14 @@ const getApplicableRestrictions = (restrictions, event, user) => {
|
|
|
72
72
|
})
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
const getNormalizedPlainRestrictions = (restrictions, definition) => {
|
|
76
|
+
const result = []
|
|
77
|
+
for (const restriction of restrictions) _addNormalizedRestrict(restriction, result, definition)
|
|
78
|
+
return result
|
|
79
|
+
}
|
|
80
|
+
|
|
75
81
|
module.exports = {
|
|
76
82
|
getNormalizedRestrictions,
|
|
77
|
-
getApplicableRestrictions
|
|
83
|
+
getApplicableRestrictions,
|
|
84
|
+
getNormalizedPlainRestrictions
|
|
78
85
|
}
|
|
@@ -184,6 +184,7 @@ const _getBoundActionBindingParameter = req => {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
function _handler(req) {
|
|
187
|
+
if (!req.query) return // FIXME: the code below expects req.query to be defined
|
|
187
188
|
if (!req.target) return
|
|
188
189
|
|
|
189
190
|
const template = getTemplate('app-input', this, req.target, { pick: _pick })
|