@sap/cds 9.2.1 → 9.3.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 +77 -1
- package/_i18n/i18n_es.properties +3 -3
- package/_i18n/i18n_es_MX.properties +3 -3
- package/_i18n/i18n_fr.properties +2 -2
- package/_i18n/messages.properties +6 -0
- package/app/index.js +0 -1
- package/bin/deploy.js +1 -1
- package/bin/serve.js +7 -20
- package/lib/compile/cdsc.js +3 -0
- package/lib/compile/for/flows.js +102 -0
- package/lib/compile/for/nodejs.js +28 -0
- package/lib/compile/to/edm.js +11 -4
- package/lib/core/classes.js +1 -1
- package/lib/core/linked-csn.js +8 -0
- package/lib/dbs/cds-deploy.js +12 -12
- package/lib/env/cds-env.js +1 -1
- package/lib/env/cds-requires.js +21 -20
- package/lib/env/defaults.js +2 -1
- package/lib/index.js +5 -6
- package/lib/log/cds-log.js +6 -5
- package/lib/log/format/aspects/cf.js +2 -2
- package/lib/plugins.js +1 -1
- package/lib/ql/cds-ql.js +0 -3
- package/lib/req/request.js +3 -3
- package/lib/req/response.js +12 -7
- package/lib/srv/bindings.js +17 -17
- package/lib/srv/cds-connect.js +6 -9
- package/lib/srv/cds-serve.js +74 -137
- package/lib/srv/cds.Service.js +49 -0
- package/lib/srv/factory.js +4 -4
- package/lib/srv/middlewares/auth/ias-auth.js +29 -9
- package/lib/srv/middlewares/auth/index.js +3 -2
- package/lib/srv/middlewares/auth/jwt-auth.js +19 -6
- package/lib/srv/protocols/hcql.js +16 -1
- package/lib/srv/srv-dispatch.js +1 -1
- package/lib/utils/cds-utils.js +4 -8
- package/lib/utils/csv-reader.js +27 -7
- package/libx/_runtime/cds.js +0 -6
- package/libx/_runtime/common/Service.js +5 -0
- package/libx/_runtime/common/generic/crud.js +1 -1
- package/libx/_runtime/common/generic/flows.js +106 -0
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/utils/differ.js +5 -15
- package/libx/_runtime/common/utils/resolveView.js +2 -2
- package/libx/_runtime/fiori/lean-draft.js +76 -40
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
- package/libx/_runtime/remote/Service.js +68 -62
- package/libx/_runtime/remote/utils/client.js +29 -216
- package/libx/_runtime/remote/utils/query.js +197 -0
- package/libx/_runtime/ucl/Service.js +180 -112
- package/libx/_runtime/ucl/queries.js +61 -0
- package/libx/odata/ODataAdapter.js +1 -4
- package/libx/odata/index.js +2 -10
- package/libx/odata/middleware/error.js +8 -1
- package/libx/odata/middleware/stream.js +1 -1
- package/libx/odata/middleware/update.js +12 -2
- package/libx/odata/parse/afterburner.js +113 -20
- package/libx/odata/parse/cqn2odata.js +1 -3
- package/libx/rest/middleware/parse.js +9 -2
- package/package.json +2 -2
- package/server.js +2 -0
- package/srv/app-service.js +1 -0
- package/srv/db-service.js +1 -0
- package/srv/msg-service.js +1 -0
- package/srv/remote-service.js +1 -0
- package/srv/ucl-service.cds +32 -0
- package/srv/ucl-service.js +1 -0
- package/lib/ql/resolve.js +0 -45
- package/libx/common/assert/type-strict.js +0 -109
- package/libx/common/assert/utils.js +0 -60
|
@@ -32,11 +32,16 @@ module.exports = function jwt_auth(config) {
|
|
|
32
32
|
|
|
33
33
|
try {
|
|
34
34
|
const securityContext = await createSecurityContext(auth_service, { req })
|
|
35
|
-
const tokenInfo = securityContext.token
|
|
36
35
|
const ctx = cds.context
|
|
37
|
-
ctx.user = user_factory(
|
|
38
|
-
ctx.tenant =
|
|
39
|
-
|
|
36
|
+
ctx.user = user_factory(securityContext)
|
|
37
|
+
ctx.tenant = securityContext.token.getZoneId()
|
|
38
|
+
// REVISIT: remove compat in cds^10
|
|
39
|
+
Object.defineProperty(req, 'authInfo', {
|
|
40
|
+
get() {
|
|
41
|
+
cds.utils.deprecated({ kind: 'API', old: 'cds.context.http.req.authInfo', use: 'cds.context.user.authInfo' })
|
|
42
|
+
return securityContext
|
|
43
|
+
}
|
|
44
|
+
})
|
|
40
45
|
} catch (e) {
|
|
41
46
|
if (e instanceof ValidationError) {
|
|
42
47
|
LOG.warn('Unauthenticated request: ', e)
|
|
@@ -53,7 +58,8 @@ module.exports = function jwt_auth(config) {
|
|
|
53
58
|
function get_user_factory(credentials, xsappname, kind) {
|
|
54
59
|
xsappname = xsappname + '.'
|
|
55
60
|
|
|
56
|
-
return function user_factory(
|
|
61
|
+
return function user_factory(securityContext) {
|
|
62
|
+
const tokenInfo = securityContext.token
|
|
57
63
|
const payload = tokenInfo.getPayload()
|
|
58
64
|
|
|
59
65
|
let id = payload.user_name
|
|
@@ -81,7 +87,14 @@ function get_user_factory(credentials, xsappname, kind) {
|
|
|
81
87
|
attr.email = payload.email
|
|
82
88
|
}
|
|
83
89
|
|
|
84
|
-
return new cds.User({
|
|
90
|
+
return new cds.User({
|
|
91
|
+
id, roles, attr, authInfo: securityContext,
|
|
92
|
+
// REVISIT: remove compat in cds^10
|
|
93
|
+
get tokenInfo() {
|
|
94
|
+
cds.utils.deprecated({ kind: 'API', old: 'cds.context.user.tokenInfo', use: 'cds.context.user.authInfo.token' })
|
|
95
|
+
return securityContext.token
|
|
96
|
+
}
|
|
97
|
+
})
|
|
85
98
|
}
|
|
86
99
|
}
|
|
87
100
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const cds = require('../../index'), {inspect} = cds.utils
|
|
2
2
|
const express = require('express')
|
|
3
|
+
|
|
3
4
|
const LOG = cds.log('hcql')
|
|
4
5
|
const PROD = process.env.NODE_ENV === 'production'
|
|
5
6
|
|
|
@@ -11,7 +12,11 @@ class HCQLAdapter extends require('./http') {
|
|
|
11
12
|
.get ('/\\$csn', this.schema.bind(this)) //> return the CSN as schema
|
|
12
13
|
.use (express.json(this.body_parser_options)) //> for application/json -> cqn
|
|
13
14
|
.use (express.text(this.body_parser_options)) //> for text/plain -> cql -> cqn
|
|
14
|
-
|
|
15
|
+
.use ((req,res,next) => {
|
|
16
|
+
const q = typeof req.body === 'string' ? req.body : inspect(req.body, { depth: 4 })
|
|
17
|
+
LOG.info (req.method, decodeURI (req.baseUrl + req.path), q)
|
|
18
|
+
next()
|
|
19
|
+
})
|
|
15
20
|
// Route for custom actions and functions ...
|
|
16
21
|
const action = this.action.bind(this)
|
|
17
22
|
router.param('action', (r,_,next,a) => a in this.service.actions ? next() : next('route'))
|
|
@@ -40,6 +45,7 @@ class HCQLAdapter extends require('./http') {
|
|
|
40
45
|
router.use (this.crud.bind(this))
|
|
41
46
|
return router
|
|
42
47
|
}
|
|
48
|
+
log (req) { } // eslint-disable-line no-unused-vars
|
|
43
49
|
|
|
44
50
|
|
|
45
51
|
/**
|
|
@@ -73,7 +79,15 @@ class HCQLAdapter extends require('./http') {
|
|
|
73
79
|
if (q.SELECT) {
|
|
74
80
|
if (req.get('Accept-Language')) q.SELECT.localized = true
|
|
75
81
|
if (req.get('X-Total-Count')) q.SELECT.count = true
|
|
82
|
+
// special handling for $search queries
|
|
83
|
+
const {where} = q.SELECT, $search = where?.[0]
|
|
84
|
+
if ($search?.func === '$search') {
|
|
85
|
+
q.SELECT.search = $search.args
|
|
86
|
+
where.splice(0, where[1] === '>' ? 4 : 2) // remove $search(...) > 0.1 and ...
|
|
87
|
+
if (where.length === 0) delete q.SELECT.where // remove empty where clause
|
|
88
|
+
}
|
|
76
89
|
}
|
|
90
|
+
|
|
77
91
|
// got a valid query
|
|
78
92
|
if (LOG._debug) LOG.debug (inspect(q))
|
|
79
93
|
return this.valid(q)
|
|
@@ -96,6 +110,7 @@ class HCQLAdapter extends require('./http') {
|
|
|
96
110
|
if (!results) return res.end()
|
|
97
111
|
if (results.$count) res.set ('X-Total-Count', results.$count)
|
|
98
112
|
if (typeof results === 'object') return res.json (results)
|
|
113
|
+
if (res.req.method === 'DELETE') return res.sendStatus(204)
|
|
99
114
|
else res.send (results)
|
|
100
115
|
}
|
|
101
116
|
|
package/lib/srv/srv-dispatch.js
CHANGED
|
@@ -64,7 +64,7 @@ exports.handle = async function handle (req) {
|
|
|
64
64
|
}()
|
|
65
65
|
if (req.errors) throw req.reject()
|
|
66
66
|
}
|
|
67
|
-
else if (req.
|
|
67
|
+
else if (req.event in (req.target?.actions ?? srv.actions)) throw _unhandled(this,req)
|
|
68
68
|
|
|
69
69
|
// .after handlers run in parallel
|
|
70
70
|
handlers = this.handlers.after.filter (h => h.for(req))
|
package/lib/utils/cds-utils.js
CHANGED
|
@@ -144,14 +144,11 @@ exports.location = function() {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
|
|
147
|
-
exports.exists = function(x) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return fs.existsSync(y)
|
|
151
|
-
}
|
|
147
|
+
exports.exists = function(x) { if (!x) return
|
|
148
|
+
const y = resolve (cds.root,x)
|
|
149
|
+
return fs.existsSync(y)
|
|
152
150
|
}
|
|
153
151
|
|
|
154
|
-
// REVISIT naming: doesn't return boolean
|
|
155
152
|
exports.isdir = function isdir (...args) {
|
|
156
153
|
if (args.length) try {
|
|
157
154
|
const y = resolve (cds.root,...args)
|
|
@@ -161,7 +158,6 @@ exports.isdir = function isdir (...args) {
|
|
|
161
158
|
} catch {/* ignore */}
|
|
162
159
|
}
|
|
163
160
|
|
|
164
|
-
// REVISIT naming: doesn't return boolean
|
|
165
161
|
exports.isfile = function isfile (...args) {
|
|
166
162
|
if (args.length) try {
|
|
167
163
|
const y = resolve (cds.root,...args)
|
|
@@ -189,7 +185,7 @@ exports.read = async function read (file, _encoding) {
|
|
|
189
185
|
} catch(e) {
|
|
190
186
|
throw new Error (`Failed to parse JSON in ${f}: ${e.message}`)
|
|
191
187
|
}
|
|
192
|
-
else return src
|
|
188
|
+
else return process.platform === 'win32' ? src?.replace(/\r\n/g, '\n') : src
|
|
193
189
|
}
|
|
194
190
|
|
|
195
191
|
exports.write = function write (file, data, o) {
|
package/lib/utils/csv-reader.js
CHANGED
|
@@ -16,7 +16,7 @@ exports.readHeader = async function (inStream, o = { ignoreComments: true }) {
|
|
|
16
16
|
let delimiter = ';'
|
|
17
17
|
let cols = []
|
|
18
18
|
let filtered = false
|
|
19
|
-
await _filterLines(inStream, null, (line, readLine) => {
|
|
19
|
+
await _filterLines({ delimiter }, inStream, null, (line, readLine) => {
|
|
20
20
|
if (!cols.length) {
|
|
21
21
|
if (o.ignoreComments && _ignoreLine(line)) {
|
|
22
22
|
filtered = true
|
|
@@ -33,17 +33,16 @@ exports.readHeader = async function (inStream, o = { ignoreComments: true }) {
|
|
|
33
33
|
return { cols, delimiter, filtered }
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
exports.stripComments = async function (file, outStream) {
|
|
37
|
-
|
|
38
|
-
const {
|
|
39
|
-
if (!filtered) return false
|
|
36
|
+
exports.stripComments = async function (file, outStream, trimWhitespaces = false) {
|
|
37
|
+
|
|
38
|
+
const { delimiter } = await exports.readHeader(createReadStream(file))
|
|
40
39
|
|
|
41
40
|
// buffer whole content so that we can write the out file
|
|
42
41
|
const inStream = Readable.from([await fsp.readFile(file)])
|
|
43
42
|
// clears the output file
|
|
44
43
|
outStream = outStream || createWriteStream(file)
|
|
45
44
|
let prelude = true
|
|
46
|
-
await _filterLines(inStream, outStream, line => {
|
|
45
|
+
await _filterLines({ delimiter, trimWhitespaces }, inStream, outStream, line => {
|
|
47
46
|
if (prelude) {
|
|
48
47
|
if (_ignoreLine(line)) return false
|
|
49
48
|
prelude = false
|
|
@@ -58,13 +57,34 @@ function _ignoreLine(line) {
|
|
|
58
57
|
return line[0] === '#' || !line.trim().length
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
function _filterLines(input, out, filter) {
|
|
60
|
+
function _filterLines({ delimiter, trimWhitespaces }, input, out, filter) {
|
|
62
61
|
return new Promise((resolve, reject) => {
|
|
63
62
|
const rl = require('readline').createInterface({ input, crlfDelay: Infinity })
|
|
64
63
|
const resumeOnDrain = () => rl.resume()
|
|
65
64
|
let filtered = false
|
|
66
65
|
rl.on('line', line => {
|
|
67
66
|
if (filter(line, rl)) {
|
|
67
|
+
// Process the line character by character to handle quoted fields properly
|
|
68
|
+
if (trimWhitespaces) {
|
|
69
|
+
const result = []
|
|
70
|
+
let field = ''
|
|
71
|
+
let quoted = false
|
|
72
|
+
|
|
73
|
+
for (const char of line) {
|
|
74
|
+
if (char === '"') {
|
|
75
|
+
quoted = !quoted
|
|
76
|
+
field += char
|
|
77
|
+
} else if (char === delimiter && !quoted) {
|
|
78
|
+
result.push(field.startsWith('"') ? field : field.trim())
|
|
79
|
+
field = ''
|
|
80
|
+
} else {
|
|
81
|
+
field += char
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Don't forget the last field
|
|
85
|
+
result.push(field.startsWith('"') ? field : field.trim())
|
|
86
|
+
line = result.join(delimiter)
|
|
87
|
+
}
|
|
68
88
|
if (out && !out.write(line + '\n')) {
|
|
69
89
|
rl.pause() // pause when writable signals so
|
|
70
90
|
out.removeListener('drain', resumeOnDrain) // avoid too many listeners
|
package/libx/_runtime/cds.js
CHANGED
|
@@ -23,9 +23,3 @@ cds.Service.prototype._requires_resolving = function (req) {
|
|
|
23
23
|
if (req.target?.name?.startsWith(this.definition?.name + '.')) return false
|
|
24
24
|
else return true
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
// FIXME: move resolve out of cds.ql !
|
|
28
|
-
const resolve = require('../../lib/ql/resolve')
|
|
29
|
-
cds.Service.prototype.resolve = function (query) {
|
|
30
|
-
return resolve(query, this)
|
|
31
|
-
}
|
|
@@ -11,6 +11,7 @@ class ApplicationService extends cds.Service {
|
|
|
11
11
|
for (let each of clazz.generics) clazz[each].call(this)
|
|
12
12
|
return super.init()
|
|
13
13
|
}
|
|
14
|
+
|
|
14
15
|
static get generics() {
|
|
15
16
|
return (this._generics ??= new Set([
|
|
16
17
|
...(this.__proto__.generics || []),
|
|
@@ -54,6 +55,10 @@ class ApplicationService extends cds.Service {
|
|
|
54
55
|
return require('./generic/crud')
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
static get handle_flows() {
|
|
59
|
+
return require('./generic/flows')
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
// Overload .handle in order to resolve projections up to a definition that is known by the remote service instance.
|
|
58
63
|
// Result is post processed according to the inverse projection in order to reflect the correct result of the original query.
|
|
59
64
|
async handle(req) {
|
|
@@ -8,7 +8,7 @@ const _targetEntityDoesNotExist = async req => {
|
|
|
8
8
|
|
|
9
9
|
module.exports = cds.service.impl(function () {
|
|
10
10
|
// prettier-ignore
|
|
11
|
-
this.on(['CREATE', 'READ', 'UPDATE', '
|
|
11
|
+
this.on(['CREATE', 'READ', 'UPDATE', 'UPSERT', 'DELETE'], '*', async function handle_crud_requests(req) {
|
|
12
12
|
|
|
13
13
|
if (!cds.db)
|
|
14
14
|
return req.reject ('NO_DATABASE_CONNECTION') // REVISIT: error message
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
|
|
3
|
+
const { getFrom } = require('../../../../lib/compile/for/flows')
|
|
4
|
+
|
|
5
|
+
const FLOW_STATUS = '@flow.status'
|
|
6
|
+
const FROM = '@from'
|
|
7
|
+
const TO = '@to'
|
|
8
|
+
// backwards compat
|
|
9
|
+
const FLOW_FROM = '@flow.from'
|
|
10
|
+
const FLOW_TO = '@flow.to'
|
|
11
|
+
|
|
12
|
+
function buildAllowedCondition(action, statusElementName, statusEnum) {
|
|
13
|
+
const fromList = getFrom(action)
|
|
14
|
+
const conditions = fromList.map(from => `${statusElementName} = '${statusEnum[from].val ?? from}'`)
|
|
15
|
+
return `(${conditions.join(' OR ')})`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function isCurrentStatusInFrom(req, action, statusElementName, statusEnum) {
|
|
19
|
+
const cond = buildAllowedCondition(action, statusElementName, statusEnum)
|
|
20
|
+
const parsedXpr = cds.parse.expr(cond)
|
|
21
|
+
const dbEntity = await SELECT.one.from(req.subject).where(parsedXpr)
|
|
22
|
+
return dbEntity !== undefined
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function checkStatus(req, action, statusElementName, statusEnum) {
|
|
26
|
+
const allowed = await isCurrentStatusInFrom(req, action, statusElementName, statusEnum)
|
|
27
|
+
if (!allowed) {
|
|
28
|
+
const from = getFrom(action)
|
|
29
|
+
req.reject({
|
|
30
|
+
code: 409,
|
|
31
|
+
message: from.length > 1 ? 'INVALID_FLOW_TRANSITION_MULTI' : 'INVALID_FLOW_TRANSITION_SINGLE',
|
|
32
|
+
args: [action.name, statusElementName, from.join(',')]
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* handler registration
|
|
39
|
+
*/
|
|
40
|
+
module.exports = cds.service.impl(function () {
|
|
41
|
+
const entry = []
|
|
42
|
+
const exit = []
|
|
43
|
+
|
|
44
|
+
for (const entity of this.entities) {
|
|
45
|
+
if (!entity.actions || !entity.elements) continue
|
|
46
|
+
|
|
47
|
+
const fromActions = []
|
|
48
|
+
const toActions = []
|
|
49
|
+
for (const action of entity.actions) {
|
|
50
|
+
if (action[FROM] || action[FLOW_FROM]) fromActions.push(action)
|
|
51
|
+
if (action[TO] || action[FLOW_TO]) toActions.push(action)
|
|
52
|
+
}
|
|
53
|
+
if (fromActions.length === 0 && toActions.length === 0) continue
|
|
54
|
+
|
|
55
|
+
let statusElement = Object.values(entity.elements).find(el => el[FLOW_STATUS])
|
|
56
|
+
if (!statusElement) {
|
|
57
|
+
cds.error(
|
|
58
|
+
`Entity ${entity.name} does not have a status element, but its actions have registered @flow annotations.`
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let statusEnum, statusElementName
|
|
63
|
+
if (statusElement.enum) {
|
|
64
|
+
statusEnum = statusElement.enum
|
|
65
|
+
statusElementName = statusElement.name
|
|
66
|
+
} else if (statusElement?._target?.elements['code']) {
|
|
67
|
+
statusEnum = statusElement._target.elements['code'].enum
|
|
68
|
+
statusElementName = statusElement.name + '_code'
|
|
69
|
+
} else {
|
|
70
|
+
cds.error(
|
|
71
|
+
`Status element in entity ${entity.name} is not an enum and does not have a valid target with code enum.`
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
entry.push({ events: fromActions, entity, statusElementName, statusEnum })
|
|
76
|
+
exit.push({ events: toActions, entity, statusElementName, statusEnum })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.prepend(function () {
|
|
80
|
+
for (const each of entry) {
|
|
81
|
+
this.before(
|
|
82
|
+
each.events,
|
|
83
|
+
each.entity,
|
|
84
|
+
Object.assign(
|
|
85
|
+
async function handle_entry_state(req) {
|
|
86
|
+
const action = req.target.actions[req.event]
|
|
87
|
+
await checkStatus(req, action, each.statusElementName, each.statusEnum)
|
|
88
|
+
},
|
|
89
|
+
{ _initial: true }
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const each of exit) {
|
|
95
|
+
async function handle_exit_state(req, next) {
|
|
96
|
+
const res = await next()
|
|
97
|
+
const action = req.target.actions[req.event]
|
|
98
|
+
const to = action[TO] ?? action[FLOW_TO]
|
|
99
|
+
const toKey = to['#'] ?? to['='] ?? to
|
|
100
|
+
await UPDATE(req.subject).with({ [each.statusElementName]: each.statusEnum[toKey].val ?? toKey })
|
|
101
|
+
return res
|
|
102
|
+
}
|
|
103
|
+
this.on(each.events, each.entity, handle_exit_state)
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
|
|
3
|
-
module.exports =
|
|
3
|
+
module.exports = cds.service.impl(function () {
|
|
4
4
|
this.before('READ', '*', handle_paging)
|
|
5
5
|
})
|
|
6
6
|
|
|
@@ -46,5 +46,5 @@ const _addPaging = function ({ SELECT }, target) {
|
|
|
46
46
|
handle_paging._initial = true
|
|
47
47
|
|
|
48
48
|
// needed in lean draft
|
|
49
|
-
exports.getPageSize = getPageSize
|
|
50
|
-
exports.commonGenericPaging = handle_paging
|
|
49
|
+
module.exports.getPageSize = getPageSize
|
|
50
|
+
module.exports.commonGenericPaging = handle_paging
|
|
@@ -21,13 +21,9 @@ const columnRefs = (data, target) => {
|
|
|
21
21
|
.filter(k => !k.isAssociation && !k.virtual)
|
|
22
22
|
.forEach(k => columns.add(k.name))
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
for (const e in row) {
|
|
28
|
-
if (target.elements[e] && !target.elements[e].isAssociation) {
|
|
29
|
-
columns.add(e)
|
|
30
|
-
}
|
|
24
|
+
for (const e in target.elements) {
|
|
25
|
+
if (!target.elements[e].isAssociation) {
|
|
26
|
+
columns.add(e)
|
|
31
27
|
}
|
|
32
28
|
}
|
|
33
29
|
|
|
@@ -44,18 +40,12 @@ const expandColumns = (target, data, columns = [], elementMap = new Map()) => {
|
|
|
44
40
|
|
|
45
41
|
for (const compName in compositions) {
|
|
46
42
|
let compositionData
|
|
47
|
-
if (data
|
|
43
|
+
if (data == null || (Array.isArray(data) && !data.length)) {
|
|
48
44
|
compositionData = null
|
|
49
45
|
} else {
|
|
50
46
|
compositionData = data[compName]
|
|
51
47
|
}
|
|
52
48
|
|
|
53
|
-
// ignore not provided compositions as nothing happens with them (expect deep delete)
|
|
54
|
-
if (compositionData === undefined) {
|
|
55
|
-
// fill columns in case
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
49
|
const composition = compositions[compName]
|
|
60
50
|
|
|
61
51
|
const fqn = composition.parent.name + ':' + composition.name
|
|
@@ -83,7 +73,7 @@ const expandColumns = (target, data, columns = [], elementMap = new Map()) => {
|
|
|
83
73
|
|
|
84
74
|
if (composition.is2many) {
|
|
85
75
|
// expandColumn.expand = getColumnsFromDataOrKeys(compositionData, composition._target)
|
|
86
|
-
if (compositionData
|
|
76
|
+
if (compositionData == null || compositionData.length === 0) {
|
|
87
77
|
// deep delete, get all subitems until recursion depth
|
|
88
78
|
expandColumns(composition._target, null, expandColumn.expand, newElementMap)
|
|
89
79
|
continue
|
|
@@ -528,7 +528,7 @@ const _mappedValue = (col, alias) => {
|
|
|
528
528
|
return [key, { val: col.val }]
|
|
529
529
|
}
|
|
530
530
|
|
|
531
|
-
const getDBTable = target => cds.
|
|
531
|
+
const getDBTable = target => cds.db.resolve.table(target)
|
|
532
532
|
|
|
533
533
|
const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
|
|
534
534
|
const el = target.elements[as] || target.query._target?.elements[ref.at(-1)]
|
|
@@ -575,7 +575,7 @@ const _checkForForbiddenViews = (queryTarget, event) => {
|
|
|
575
575
|
const _getTransitionData = (target, columns, service, options) => {
|
|
576
576
|
let { abort, skipForbiddenViewCheck, event } = options
|
|
577
577
|
// REVISIT revert after cds-dbs pr
|
|
578
|
-
if (!abort) abort =
|
|
578
|
+
if (!abort) abort = service.resolve.abortDB
|
|
579
579
|
// REVISIT: Find less param polluting way to skip forbidden view check for reads
|
|
580
580
|
if (!skipForbiddenViewCheck) _checkForForbiddenViews(target, event)
|
|
581
581
|
const isAborted = abort(target)
|