@sap/cds 6.7.2 → 6.8.1
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 +41 -1
- package/_i18n/i18n.properties +9 -6
- package/_i18n/i18n_ar.properties +6 -6
- package/_i18n/i18n_cs.properties +6 -6
- package/_i18n/i18n_da.properties +6 -6
- package/_i18n/i18n_de.properties +6 -6
- package/_i18n/i18n_en.properties +6 -6
- package/_i18n/i18n_es.properties +6 -6
- package/_i18n/i18n_fi.properties +6 -6
- package/_i18n/i18n_fr.properties +6 -6
- package/_i18n/i18n_hu.properties +6 -6
- package/_i18n/i18n_it.properties +6 -6
- package/_i18n/i18n_ja.properties +6 -6
- package/_i18n/i18n_ko.properties +6 -6
- package/_i18n/i18n_ms.properties +6 -6
- package/_i18n/i18n_nl.properties +6 -6
- package/_i18n/i18n_no.properties +6 -6
- package/_i18n/i18n_pl.properties +6 -6
- package/_i18n/i18n_pt.properties +6 -6
- package/_i18n/i18n_ro.properties +6 -6
- package/_i18n/i18n_ru.properties +6 -6
- package/_i18n/i18n_sv.properties +6 -6
- package/_i18n/i18n_th.properties +6 -6
- package/_i18n/i18n_tr.properties +8 -8
- package/_i18n/i18n_zh_CN.properties +3 -3
- package/_i18n/i18n_zh_TW.properties +6 -6
- package/apis/core.d.ts +30 -31
- package/apis/csn.d.ts +1 -1
- package/apis/ql.d.ts +69 -39
- package/apis/serve.d.ts +4 -3
- package/apis/services.d.ts +20 -7
- package/bin/build/buildTaskEngine.js +1 -1
- package/bin/build/index.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +9 -6
- package/bin/build/provider/hana/index.js +11 -4
- package/bin/build/provider/mtx-sidecar/index.js +3 -3
- package/bin/build/provider/nodejs/index.js +23 -0
- package/bin/version.js +3 -2
- package/common.cds +3 -2
- package/lib/auth/index.js +3 -0
- package/lib/auth/mocked-users.js +13 -0
- package/lib/compile/etc/_localized.js +3 -0
- package/lib/core/entities.js +7 -3
- package/lib/dbs/cds-deploy.js +36 -12
- package/lib/env/cds-env.js +47 -14
- package/lib/env/cds-requires.js +16 -7
- package/lib/env/defaults.js +2 -2
- package/lib/env/schemas/cds-rc.json +1 -8
- package/lib/index.js +1 -1
- package/lib/ql/STREAM.js +89 -0
- package/lib/ql/cds-ql.js +2 -1
- package/lib/req/request.js +5 -2
- package/lib/req/user.js +1 -1
- package/lib/srv/middlewares/index.js +9 -7
- package/lib/srv/middlewares/trace.js +6 -5
- package/lib/srv/srv-api.js +1 -0
- package/lib/utils/cds-utils.js +1 -1
- package/lib/utils/tar.js +30 -31
- package/libx/_runtime/audit/Service.js +96 -37
- package/libx/_runtime/audit/generic/personal/utils.js +26 -13
- package/libx/_runtime/audit/utils/v2.js +21 -22
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +10 -3
- package/libx/_runtime/cds-services/services/Service.js +2 -7
- package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
- package/libx/_runtime/common/aspects/any.js +1 -1
- package/libx/_runtime/common/generic/auth/utils.js +30 -41
- package/libx/_runtime/common/i18n/messages.properties +1 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +19 -17
- package/libx/_runtime/db/expand/rawToExpanded.js +3 -5
- package/libx/_runtime/db/utils/generateAliases.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +1 -1
- package/libx/_runtime/fiori/generic/before.js +18 -19
- package/libx/_runtime/fiori/generic/prepare.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +8 -6
- package/libx/_runtime/fiori/utils/handler.js +0 -6
- package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +1 -1
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +0 -5
- package/libx/_runtime/hana/pool.js +26 -18
- package/libx/_runtime/hana/search2Contains.js +1 -1
- package/libx/_runtime/hana/search2cqn4sql.js +26 -18
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +23 -16
- package/libx/_runtime/messaging/outbox/utils.js +6 -1
- package/libx/_runtime/remote/Service.js +64 -38
- package/libx/_runtime/remote/utils/client.js +13 -9
- package/libx/rest/middleware/read.js +2 -1
- package/package.json +1 -1
|
@@ -5,21 +5,7 @@ const { isContainsPredicateSupported, search2Contains } = require('./search2Cont
|
|
|
5
5
|
const { addAliasToExpression } = require('../db/utils/generateAliases')
|
|
6
6
|
const targetAlias = 'Target'
|
|
7
7
|
const textsAlias = 'Texts'
|
|
8
|
-
|
|
9
|
-
const keys = Object.keys(entity.keys).filter(k => !entity.keys[k].isAssociation && !entity.keys[k].virtual)
|
|
10
|
-
const where = []
|
|
11
|
-
keys.forEach(key => {
|
|
12
|
-
if (where.length > 0) where.push('and')
|
|
13
|
-
where.push({ ref: [alias1, key] }, '=', { ref: [alias2, key] })
|
|
14
|
-
})
|
|
15
|
-
return where
|
|
16
|
-
}
|
|
17
|
-
const _generateContainsColumns = (columns, entity) => {
|
|
18
|
-
const columnsTarget = addAliasToExpression(columns, targetAlias)
|
|
19
|
-
const columns2SearchText = columns.filter(col => col.ref && entity.elements[col.ref[col.ref.length - 1]].localized)
|
|
20
|
-
const columnsText = addAliasToExpression(columns2SearchText, textsAlias)
|
|
21
|
-
return [...columnsTarget, ...columnsText]
|
|
22
|
-
}
|
|
8
|
+
|
|
23
9
|
/**
|
|
24
10
|
* Computes a CQN expression for a search query.
|
|
25
11
|
*
|
|
@@ -40,11 +26,12 @@ const _generateContainsColumns = (columns, entity) => {
|
|
|
40
26
|
const search2cqn4sql = (query, entity, options) => {
|
|
41
27
|
const cqnSearchPhrase = query.SELECT.search
|
|
42
28
|
if (!cqnSearchPhrase) return query
|
|
43
|
-
|
|
44
29
|
const localizedAssociation = entity.associations?.localized
|
|
30
|
+
|
|
45
31
|
if (localizedAssociation) {
|
|
46
32
|
let { columns: columns2Search = computeColumnsToBeSearched(query, entity), locale } = options
|
|
47
33
|
const viewAlias = query.SELECT.from.as ? query.SELECT.from.as : 'LocalizedView'
|
|
34
|
+
|
|
48
35
|
if (!query.SELECT.from.as) {
|
|
49
36
|
_addAliasToQuery(query, viewAlias)
|
|
50
37
|
}
|
|
@@ -56,14 +43,17 @@ const search2cqn4sql = (query, entity, options) => {
|
|
|
56
43
|
|
|
57
44
|
// left outer join the target table with the _texts table (the _texts table contains the translated texts)
|
|
58
45
|
subQuery.leftJoin(localizedAssociation.target, textsAlias).on(onCondition)
|
|
46
|
+
|
|
59
47
|
// add condition for equal keys of target table and localized view
|
|
60
48
|
subQuery.where(_generateKeysWhereCondition(entity, targetAlias, viewAlias))
|
|
61
49
|
const containsColumns = _generateContainsColumns(columns2Search, entity)
|
|
50
|
+
|
|
62
51
|
let expression
|
|
52
|
+
|
|
63
53
|
if (isContainsPredicateSupported(query, entity, columns2Search)) {
|
|
64
54
|
// generate CQN expression with `CONTAINS` predicate for the columns from the target and text table
|
|
65
55
|
expression = search2Contains(cqnSearchPhrase, containsColumns)
|
|
66
|
-
Object.defineProperty(
|
|
56
|
+
Object.defineProperty(expression, 'searchUsingContains', { value: true, enumerable: true })
|
|
67
57
|
} else {
|
|
68
58
|
expression = searchToLike(cqnSearchPhrase, containsColumns)
|
|
69
59
|
}
|
|
@@ -73,11 +63,29 @@ const search2cqn4sql = (query, entity, options) => {
|
|
|
73
63
|
// suppress the localize handler from redirecting the subQuery's target to the localized view
|
|
74
64
|
Object.defineProperty(subQuery, '_suppressLocalization', { value: true })
|
|
75
65
|
query.where('exists', subQuery)
|
|
76
|
-
|
|
77
66
|
return query
|
|
78
67
|
}
|
|
79
68
|
}
|
|
80
69
|
|
|
70
|
+
const _generateKeysWhereCondition = (entity, alias1, alias2) => {
|
|
71
|
+
const keys = Object.keys(entity.keys).filter(key => !entity.keys[key].isAssociation && !entity.keys[key].virtual)
|
|
72
|
+
const where = []
|
|
73
|
+
|
|
74
|
+
keys.forEach(key => {
|
|
75
|
+
if (where.length > 0) where.push('and')
|
|
76
|
+
where.push({ ref: [alias1, key] }, '=', { ref: [alias2, key] })
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return where
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const _generateContainsColumns = (columns, entity) => {
|
|
83
|
+
const columnsTarget = addAliasToExpression(columns, targetAlias)
|
|
84
|
+
const columns2SearchText = columns.filter(col => col.ref && entity.elements[col.ref[col.ref.length - 1]].localized)
|
|
85
|
+
const columnsText = addAliasToExpression(columns2SearchText, textsAlias)
|
|
86
|
+
return [...columnsTarget, ...columnsText]
|
|
87
|
+
}
|
|
88
|
+
|
|
81
89
|
const _addAliasToQuery = (query, alias) => {
|
|
82
90
|
const SELECT = query.SELECT
|
|
83
91
|
SELECT.from.as = alias
|
|
@@ -2,8 +2,9 @@ const cds = require('../../cds.js')
|
|
|
2
2
|
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
3
3
|
const express = require('express')
|
|
4
4
|
const getTenantInfo = require('./getTenantInfo.js')
|
|
5
|
-
const isSecured = () => cds.requires.auth && cds.requires.auth.credentials
|
|
6
|
-
const
|
|
5
|
+
const isSecured = () => cds.requires.auth && (cds.requires.auth.impl || cds.requires.auth.credentials)
|
|
6
|
+
const _require = require('../../common/utils/require')
|
|
7
|
+
const { UNAUTHORIZED } = require('../../auth/utils')
|
|
7
8
|
|
|
8
9
|
const _isAll = a => a && a.includes('all')
|
|
9
10
|
|
|
@@ -14,18 +15,25 @@ class EndpointRegistry {
|
|
|
14
15
|
this.webhookCallbacks = new Map()
|
|
15
16
|
this.deployCallbacks = new Map()
|
|
16
17
|
if (isSecured()) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
if (cds.requires.auth.impl) {
|
|
19
|
+
const impl = _require(cds.resolve(cds.requires.auth.impl))
|
|
20
|
+
paths.forEach(path => cds.app.use(path, impl))
|
|
21
|
+
} else {
|
|
22
|
+
const JWTStrategy = require('../../auth/strategies/JWT.js')
|
|
23
|
+
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
24
|
+
const passport = _require('passport')
|
|
25
|
+
// REVISIT: It's unclear if the credentials from cds.requires.auth need to be used here.
|
|
26
|
+
// In principle, user-facing endpoints might differ from messaging ones.
|
|
27
|
+
passport.use(new JWTStrategy(cds.requires.auth.credentials))
|
|
28
|
+
paths.forEach(path => {
|
|
29
|
+
cds.app.use(path, passport.initialize())
|
|
30
|
+
cds.app.use(path, passport.authenticate('JWT', { session: false }))
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
// unsuccessful auth doesn't automatically reject!
|
|
23
34
|
paths.forEach(path => {
|
|
24
|
-
cds.app.use(path, passport.initialize())
|
|
25
|
-
cds.app.use(path, passport.authenticate('JWT', { session: false }))
|
|
26
35
|
cds.app.use(path, (req, res, next) => {
|
|
27
|
-
|
|
28
|
-
if (!req.user) return res.status(401).json({ message: 'Unauthorized' })
|
|
36
|
+
if (!req.user) res.status(401).json(UNAUTHORIZED)
|
|
29
37
|
next()
|
|
30
38
|
})
|
|
31
39
|
})
|
|
@@ -63,17 +71,16 @@ class EndpointRegistry {
|
|
|
63
71
|
try {
|
|
64
72
|
if (isSecured() && !req.user.is('emcallback')) return res.sendStatus(403)
|
|
65
73
|
const queueName = req.query.q
|
|
66
|
-
const authInfo = req.authInfo
|
|
67
74
|
const xAddress = req.headers['x-address']
|
|
68
75
|
const topic = xAddress && xAddress.match(/^topic:(.*)/)[1]
|
|
69
76
|
const payload = req.body
|
|
70
77
|
const cb = this.webhookCallbacks.get(queueName)
|
|
71
78
|
if (!topic || !payload || !queueName || !cb) return res.sendStatus(200)
|
|
72
|
-
const
|
|
73
|
-
const other =
|
|
79
|
+
const tenant = req.tenant || req.user.tenant
|
|
80
|
+
const other = tenant
|
|
74
81
|
? {
|
|
75
82
|
_: { req, res }, // For `cds.context.http`
|
|
76
|
-
tenant
|
|
83
|
+
tenant
|
|
77
84
|
}
|
|
78
85
|
: {}
|
|
79
86
|
if (!cb) return res.sendStatus(200)
|
|
@@ -15,6 +15,11 @@ const outboxRunner = new OutboxRunner()
|
|
|
15
15
|
const cdsUser = 'cds.internal.user'
|
|
16
16
|
const messageProcessorRegistered = Symbol('message processor registered')
|
|
17
17
|
|
|
18
|
+
const _get100NanosecondTimestampISOString = () => {
|
|
19
|
+
const [now, nanoseconds] = [new Date(), process.hrtime()[1]]
|
|
20
|
+
return now.toISOString().replace('Z', `${nanoseconds}`.padStart(9, '0').substring(3, 7) + 'Z')
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
const _getMessagesEntity = () => {
|
|
19
24
|
const messagesDbName = 'cds.outbox.Messages'
|
|
20
25
|
const messagesEntity = cds.model.definitions[messagesDbName]
|
|
@@ -221,7 +226,7 @@ const _createMessage = (name, msg, context) => {
|
|
|
221
226
|
const outboxMsg = {
|
|
222
227
|
ID: cds.utils.uuid(),
|
|
223
228
|
target: name,
|
|
224
|
-
timestamp:
|
|
229
|
+
timestamp: _get100NanosecondTimestampISOString(), // needs to be different for each emit
|
|
225
230
|
msg: JSON.stringify(_msg)
|
|
226
231
|
}
|
|
227
232
|
return outboxMsg
|
|
@@ -7,6 +7,14 @@ const { getKind, run, getDestination, getAdditionalOptions, getReqOptions } = re
|
|
|
7
7
|
const { formatVal } = require('../../odata/utils')
|
|
8
8
|
const { hasAliasedColumns } = require('./utils/data')
|
|
9
9
|
|
|
10
|
+
let _cloudSdkConnectivity
|
|
11
|
+
const cloudSdkConnectivity = () => {
|
|
12
|
+
if (_cloudSdkConnectivity) return _cloudSdkConnectivity
|
|
13
|
+
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
14
|
+
_cloudSdkConnectivity = require('@sap-cloud-sdk/connectivity')
|
|
15
|
+
return _cloudSdkConnectivity
|
|
16
|
+
}
|
|
17
|
+
|
|
10
18
|
const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
|
|
11
19
|
|
|
12
20
|
const _setHeaders = (defaultHeaders, req) => {
|
|
@@ -18,6 +26,7 @@ const _setHeaders = (defaultHeaders, req) => {
|
|
|
18
26
|
}, {})
|
|
19
27
|
)
|
|
20
28
|
}
|
|
29
|
+
|
|
21
30
|
const _setCorrectValue = (el, data, params, kind) => {
|
|
22
31
|
return typeof data[el] === 'object' && kind !== 'odata-v2'
|
|
23
32
|
? JSON.stringify(data[el])
|
|
@@ -151,49 +160,68 @@ const resolvedTargetOfQuery = q => {
|
|
|
151
160
|
const transitions = (typeof q === 'object' && (q.SELECT || q.INSERT || q.UPDATE || q.DELETE)._transitions) || []
|
|
152
161
|
return transitions.length && [transitions.length - 1].target
|
|
153
162
|
}
|
|
163
|
+
|
|
154
164
|
let logged
|
|
155
165
|
let sdkLoggerDisabled
|
|
166
|
+
|
|
167
|
+
const _resolveSelectionStrategy = options => {
|
|
168
|
+
if (typeof options?.selectionStrategy !== 'string') return
|
|
169
|
+
options.selectionStrategy = cloudSdkConnectivity().DestinationSelectionStrategies[options.selectionStrategy]
|
|
170
|
+
|
|
171
|
+
if (typeof options?.selectionStrategy !== 'function') {
|
|
172
|
+
throw new Error(`Unsupported destination selection strategy "${options.selectionStrategy}".`)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
156
176
|
class RemoteService extends cds.Service {
|
|
157
177
|
init() {
|
|
158
|
-
|
|
159
|
-
throw new Error(`No credentials configured for "${this.name}".`)
|
|
160
|
-
}
|
|
178
|
+
this.kind = getKind(this.options) // TODO: Simplify
|
|
161
179
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
/*
|
|
181
|
+
* set up connectivity stuff if credentials are provided
|
|
182
|
+
* throw error if no credentials are provided and the service has at least one entity or one action/function
|
|
183
|
+
*/
|
|
184
|
+
if (this.options.credentials) {
|
|
185
|
+
this.datasource = this.options.datasource
|
|
186
|
+
this.destinationOptions = this.options.destinationOptions
|
|
187
|
+
_resolveSelectionStrategy(this.destinationOptions)
|
|
188
|
+
this.destination =
|
|
189
|
+
this.options.credentials.destination ??
|
|
190
|
+
getDestination(this.definition?.name ?? this.datasource, this.options.credentials)
|
|
191
|
+
this.path = this.options.credentials.path
|
|
192
|
+
|
|
193
|
+
this.requestTimeout = this.options.credentials.requestTimeout
|
|
194
|
+
if (this.requestTimeout == null) this.requestTimeout = 60000
|
|
195
|
+
|
|
196
|
+
// REVISIT: remove cds.env.features.fetch_csrf in next major ^7
|
|
197
|
+
this.csrf = cds.env.features.fetch_csrf ?? this.options.csrf
|
|
198
|
+
this.csrfInBatch = this.options.csrfInBatch
|
|
199
|
+
if (cds.env.features.fetch_csrf && !logged) {
|
|
200
|
+
// for logging once for all remote services
|
|
201
|
+
logged = true
|
|
202
|
+
LOG._warn &&
|
|
203
|
+
LOG.warn(
|
|
204
|
+
'Configuration option "cds.env.features.fetch_csrf" is deprecated.\n Please use "csrf"/"csrfInBatch" as described in https://cap.cloud.sap/docs/node.js/remote-services'
|
|
205
|
+
)
|
|
206
|
+
}
|
|
177
207
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
208
|
+
// REVISIT: use cds.log's logger in cloud sdk
|
|
209
|
+
|
|
210
|
+
// disable sdk logger if not in debug mode
|
|
211
|
+
if (!LOG._debug && !sdkLoggerDisabled) {
|
|
212
|
+
try {
|
|
213
|
+
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
214
|
+
const sdkUtils = require('@sap-cloud-sdk/util')
|
|
215
|
+
sdkUtils.setGlobalLogLevel('error')
|
|
216
|
+
// disable sdk logger once
|
|
217
|
+
sdkLoggerDisabled = true
|
|
218
|
+
} catch (err) {
|
|
219
|
+
/* might fail in cds repl due to winston's exception handler, see cap/issues#10134 */
|
|
220
|
+
}
|
|
190
221
|
}
|
|
222
|
+
} else if ([...this.entities].length || [...this.operations].length) {
|
|
223
|
+
throw new Error(`No credentials configured for "${this.name}".`)
|
|
191
224
|
}
|
|
192
|
-
// REVISIT: remove cds.env.features.fetch_csrf in next major ^7
|
|
193
|
-
this.csrf = cds.env.features.fetch_csrf || this.options.csrf
|
|
194
|
-
this.csrfInBatch = this.options.csrfInBatch
|
|
195
|
-
this.path = this.options.credentials.path
|
|
196
|
-
this.kind = getKind(this.options) // TODO: Simplify
|
|
197
225
|
|
|
198
226
|
const clearKeysFromData = function (req) {
|
|
199
227
|
if (req.target && req.target.keys) for (const k of Object.keys(req.target.keys)) delete req.data[k]
|
|
@@ -235,9 +263,7 @@ class RemoteService extends cds.Service {
|
|
|
235
263
|
}
|
|
236
264
|
|
|
237
265
|
let result = await run(reqOptions, additionalOptions)
|
|
238
|
-
|
|
239
|
-
result =
|
|
240
|
-
typeof query === 'object' && query.SELECT && query.SELECT.one && Array.isArray(result) ? result[0] : result
|
|
266
|
+
result = typeof query === 'object' && query.SELECT?.one && Array.isArray(result) ? result[0] : result
|
|
241
267
|
|
|
242
268
|
return result
|
|
243
269
|
})
|
|
@@ -27,14 +27,16 @@ const _executeHttpRequest = async ({ requestConfig, destination, destinationOpti
|
|
|
27
27
|
const destinationName = typeof destination === 'string' && destination
|
|
28
28
|
|
|
29
29
|
if (destinationName) {
|
|
30
|
-
destination = { destinationName, ...(resolveDestinationOptions(destinationOptions, jwt)
|
|
30
|
+
destination = { destinationName, ...(resolveDestinationOptions(destinationOptions, jwt) ?? {}) }
|
|
31
31
|
} else if (destination.forwardAuthToken) {
|
|
32
32
|
destination = {
|
|
33
33
|
...destination,
|
|
34
34
|
headers: destination.headers ? { ...destination.headers } : {},
|
|
35
35
|
authentication: 'NoAuthentication'
|
|
36
36
|
}
|
|
37
|
+
|
|
37
38
|
delete destination.forwardAuthToken
|
|
39
|
+
|
|
38
40
|
if (jwt) {
|
|
39
41
|
destination.headers.authorization = `Bearer ${jwt}`
|
|
40
42
|
} else {
|
|
@@ -93,12 +95,12 @@ const getDestination = (name, credentials) => {
|
|
|
93
95
|
* @returns {import('@sap-cloud-sdk/connectivity').DestinationFetchOptions}
|
|
94
96
|
*/
|
|
95
97
|
const resolveDestinationOptions = function (options, jwt) {
|
|
96
|
-
if (!options && !jwt) return
|
|
98
|
+
if (!options && !jwt) return
|
|
97
99
|
|
|
98
|
-
const resolvedOptions = Object.assign({}, options
|
|
100
|
+
const resolvedOptions = Object.assign({}, options ?? {})
|
|
99
101
|
resolvedOptions.jwt = jwt
|
|
100
102
|
|
|
101
|
-
if (options
|
|
103
|
+
if (options?.selectionStrategy) {
|
|
102
104
|
resolvedOptions.selectionStrategy = options.selectionStrategy
|
|
103
105
|
}
|
|
104
106
|
|
|
@@ -388,12 +390,10 @@ const run = async (
|
|
|
388
390
|
}
|
|
389
391
|
|
|
390
392
|
const getJwt = req => {
|
|
391
|
-
const headers = req
|
|
392
|
-
if (headers
|
|
393
|
+
const headers = req?.context?.headers
|
|
394
|
+
if (headers?.authorization) {
|
|
393
395
|
const token = headers.authorization.match(/^bearer (.+)/i)
|
|
394
|
-
if (token)
|
|
395
|
-
return token[1]
|
|
396
|
-
}
|
|
396
|
+
if (token) return token[1]
|
|
397
397
|
}
|
|
398
398
|
|
|
399
399
|
return null
|
|
@@ -467,6 +467,10 @@ const getReqOptions = (req, query, service) => {
|
|
|
467
467
|
? _stringToReqOptions(query, req.data, req.target)
|
|
468
468
|
: _pathToReqOptions(req.method, req.path, req.data, req.target)
|
|
469
469
|
|
|
470
|
+
if (service.kind === 'odata-v2' && req.event === 'READ' && reqOptions.url?.match(/(\/any\()|(\/all\()/)) {
|
|
471
|
+
req.reject(501, 'Lambda expressions are not supported in OData v2')
|
|
472
|
+
}
|
|
473
|
+
|
|
470
474
|
reqOptions.headers = { accept: 'application/json,text/plain' }
|
|
471
475
|
reqOptions.timeout = service.requestTimeout
|
|
472
476
|
|
|
@@ -16,13 +16,14 @@ module.exports = async (_req, _res, next) => {
|
|
|
16
16
|
result = await srv.dispatch(req)
|
|
17
17
|
|
|
18
18
|
// 204 or 404?
|
|
19
|
-
if (result
|
|
19
|
+
if (result == null && query.SELECT.one) {
|
|
20
20
|
if (_target.ref.length > 1) status = 204
|
|
21
21
|
else throw { code: 404 }
|
|
22
22
|
}
|
|
23
23
|
} catch (e) {
|
|
24
24
|
return next(e)
|
|
25
25
|
}
|
|
26
|
+
|
|
26
27
|
// compat for mtx returning strings instead of objects
|
|
27
28
|
if (typeof result === 'object' && result !== null && '$count' in result) {
|
|
28
29
|
result = {
|