@sap/cds 5.8.4 → 5.9.2
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 +198 -77
- package/app/fiori/preview.js +16 -11
- package/app/fiori/routes.js +15 -8
- package/app/index.js +1 -1
- package/bin/build/buildTaskFactory.js +3 -3
- package/bin/build/buildTaskProviderFactory.js +1 -1
- package/bin/build/constants.js +1 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
- package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +8 -2
- package/bin/build/provider/hana/2migration.js +27 -24
- package/bin/build/provider/hana/index.js +17 -18
- package/bin/build/provider/hana/migrationtable.js +9 -10
- package/bin/build/provider/java-cf/index.js +4 -5
- package/bin/build/provider/node-cf/index.js +99 -6
- package/bin/cds.js +17 -18
- package/bin/deploy/to-hana/cfUtil.js +16 -19
- package/bin/deploy/to-hana/hana.js +7 -24
- package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
- package/bin/mtx/in-cds.js +2 -2
- package/bin/serve.js +10 -3
- package/bin/utils/modules.js +7 -0
- package/bin/version.js +56 -3
- package/lib/compile/cdsc.js +7 -2
- package/lib/compile/etc/_localized.js +37 -25
- package/lib/compile/etc/csv.js +8 -8
- package/lib/compile/for/drafts.js +9 -0
- package/lib/compile/for/java.js +16 -0
- package/lib/compile/for/nodejs.js +12 -0
- package/lib/compile/index.js +3 -0
- package/lib/compile/minify.js +16 -2
- package/lib/compile/parse.js +2 -2
- package/lib/compile/resolve.js +35 -18
- package/lib/compile/to/json.js +3 -1
- package/lib/compile/to/sql.js +2 -2
- package/lib/compile/to/srvinfo.js +4 -2
- package/lib/connect/bindings.js +1 -1
- package/lib/connect/index.js +3 -4
- package/lib/core/entities.js +15 -14
- package/lib/core/index.js +39 -36
- package/lib/core/reflect.js +4 -2
- package/lib/deploy.js +114 -127
- package/lib/env/defaults.js +1 -0
- package/lib/env/index.js +165 -165
- package/lib/env/presets.js +1 -0
- package/lib/env/requires.js +121 -50
- package/lib/index.js +2 -0
- package/lib/log/format/kibana.js +2 -2
- package/lib/ql/SELECT.js +10 -0
- package/lib/ql/parse.js +1 -0
- package/lib/req/cds-context.js +4 -1
- package/lib/req/context.js +50 -56
- package/lib/req/event.js +1 -6
- package/lib/req/locale.js +6 -5
- package/lib/req/request.js +2 -0
- package/lib/req/user.js +7 -5
- package/lib/serve/Service-api.js +10 -7
- package/lib/serve/Service-dispatch.js +9 -11
- package/lib/serve/Service-methods.js +30 -41
- package/lib/serve/Transaction.js +10 -7
- package/lib/serve/adapters.js +11 -9
- package/lib/serve/factory.js +14 -9
- package/lib/serve/index.js +28 -15
- package/lib/utils/data.js +1 -1
- package/lib/utils/index.js +27 -30
- package/lib/utils/resources/index.js +101 -0
- package/lib/utils/resources/tar.js +71 -0
- package/lib/utils/resources/utils.js +11 -0
- package/libx/_runtime/audit/Service.js +36 -39
- package/libx/_runtime/audit/generic/personal/access.js +3 -4
- package/libx/_runtime/audit/generic/personal/modification.js +3 -4
- package/libx/_runtime/audit/utils/v2.js +1 -2
- package/libx/_runtime/auth/index.js +126 -84
- package/libx/_runtime/auth/strategies/JWT.js +12 -19
- package/libx/_runtime/auth/strategies/dummy.js +1 -5
- package/libx/_runtime/auth/strategies/dwc.js +11 -9
- package/libx/_runtime/auth/strategies/mock.js +0 -4
- package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
- package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
- package/libx/_runtime/auth/utils.js +22 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +56 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
- package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
- package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
- package/libx/_runtime/cds-services/services/Service.js +40 -0
- package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
- package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
- package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
- package/libx/_runtime/cds-services/util/assert.js +20 -14
- package/libx/_runtime/cds.js +9 -1
- package/libx/_runtime/common/aspects/any.js +5 -0
- package/libx/_runtime/common/aspects/entity.js +25 -7
- package/libx/_runtime/common/aspects/utils.js +2 -2
- package/libx/_runtime/common/composition/data.js +6 -0
- package/libx/_runtime/common/composition/insert.js +3 -2
- package/libx/_runtime/common/composition/tree.js +4 -10
- package/libx/_runtime/common/composition/update.js +4 -4
- package/libx/_runtime/common/constants/draft.js +29 -26
- package/libx/_runtime/common/error/constants.js +2 -2
- package/libx/_runtime/common/error/frontend.js +7 -15
- package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
- package/libx/_runtime/common/generic/auth/constants.js +20 -0
- package/libx/_runtime/common/generic/auth/expand.js +54 -0
- package/libx/_runtime/common/generic/auth/index.js +32 -0
- package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
- package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
- package/libx/_runtime/common/generic/auth/requires.js +34 -0
- package/libx/_runtime/common/generic/auth/restrict.js +298 -0
- package/libx/_runtime/common/generic/auth/restrictions.js +85 -0
- package/libx/_runtime/common/generic/auth/utils.js +213 -0
- package/libx/_runtime/common/generic/crud.js +8 -6
- package/libx/_runtime/common/generic/etag.js +1 -1
- package/libx/_runtime/common/generic/input.js +35 -35
- package/libx/_runtime/common/generic/sorting.js +2 -3
- package/libx/_runtime/common/generic/temporal.js +2 -2
- package/libx/_runtime/common/i18n/messages.properties +1 -1
- package/libx/_runtime/common/toggles/handler.js +21 -0
- package/libx/_runtime/common/utils/copy.js +10 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +111 -35
- package/libx/_runtime/common/utils/csn.js +63 -1
- package/libx/_runtime/common/utils/dollar.js +10 -1
- package/libx/_runtime/common/utils/draft.js +46 -7
- package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
- package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
- package/libx/_runtime/common/utils/generateOnCond.js +4 -1
- package/libx/_runtime/common/utils/quotingStyles.js +2 -0
- package/libx/_runtime/common/utils/resolveStructured.js +25 -9
- package/libx/_runtime/common/utils/resolveView.js +4 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
- package/libx/_runtime/common/utils/structured.js +33 -37
- package/libx/_runtime/common/utils/template.js +17 -8
- package/libx/_runtime/common/utils/templateProcessor.js +28 -28
- package/libx/_runtime/db/data-conversion/post-processing.js +118 -412
- package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
- package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
- package/libx/_runtime/db/generic/index.js +1 -3
- package/libx/_runtime/db/generic/input.js +5 -10
- package/libx/_runtime/db/generic/rewrite.js +5 -2
- package/libx/_runtime/db/generic/structured.js +2 -2
- package/libx/_runtime/db/query/delete.js +2 -2
- package/libx/_runtime/db/query/insert.js +1 -1
- package/libx/_runtime/db/query/update.js +9 -14
- package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +8 -8
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
- package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
- package/libx/_runtime/db/utils/columns.js +3 -3
- package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
- package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
- package/libx/_runtime/extensibility/mps/index.js +5 -0
- package/libx/_runtime/extensibility/mps/service.js +111 -0
- package/libx/_runtime/extensibility/mps/tar.js +42 -0
- package/libx/_runtime/extensibility/mps/utils.js +11 -0
- package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
- package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
- package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
- package/libx/_runtime/extensibility/uiflex/index.js +54 -0
- package/libx/_runtime/extensibility/uiflex/service.js +276 -0
- package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
- package/libx/_runtime/fiori/generic/activate.js +2 -2
- package/libx/_runtime/fiori/generic/before.js +4 -4
- package/libx/_runtime/fiori/generic/new.js +3 -3
- package/libx/_runtime/fiori/generic/patch.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +58 -66
- package/libx/_runtime/fiori/generic/readOverDraft.js +74 -16
- package/libx/_runtime/fiori/utils/handler.js +6 -13
- package/libx/_runtime/fiori/utils/where.js +6 -5
- package/libx/_runtime/hana/Service.js +4 -10
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
- package/libx/_runtime/hana/driver.js +2 -2
- package/libx/_runtime/hana/execute.js +45 -75
- package/libx/_runtime/hana/pool.js +1 -1
- package/libx/_runtime/hana/streaming.js +2 -1
- package/libx/_runtime/index.js +6 -6
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
- package/libx/_runtime/messaging/Outbox.js +2 -2
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
- package/libx/_runtime/messaging/common-utils/connections.js +5 -7
- package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
- package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
- package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
- package/libx/_runtime/messaging/file-based.js +5 -5
- package/libx/_runtime/messaging/message-queuing.js +14 -12
- package/libx/_runtime/messaging/outbox/utils.js +18 -19
- package/libx/_runtime/messaging/redis-messaging.js +91 -0
- package/libx/_runtime/messaging/service.js +8 -6
- package/libx/_runtime/remote/Service.js +44 -8
- package/libx/_runtime/remote/utils/client.js +24 -19
- package/libx/_runtime/remote/utils/data.js +11 -11
- package/libx/_runtime/sqlite/Service.js +6 -9
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
- package/libx/_runtime/types/api.js +10 -2
- package/libx/common/utils/ucsn.js +109 -0
- package/libx/gql/resolvers/crud/update.js +5 -0
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
- package/libx/gql/schema/typeDefMap.js +2 -2
- package/libx/odata/afterburner.js +110 -16
- package/libx/odata/cqn2odata.js +24 -27
- package/libx/odata/grammar.pegjs +9 -1
- package/libx/odata/parseToCqn.js +39 -0
- package/libx/odata/parser.js +1 -1
- package/libx/rest/RestAdapter.js +9 -1
- package/libx/rest/middleware/input.js +54 -0
- package/libx/rest/middleware/operation.js +14 -1
- package/libx/rest/middleware/parse.js +11 -7
- package/package.json +2 -2
- package/server.js +34 -19
- package/srv/audit-log.cds +2 -2
- package/srv/flex.cds +8 -2
- package/srv/flex.js +1 -1
- package/srv/mps.cds +23 -0
- package/srv/mps.js +1 -0
- package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
- package/libx/_runtime/common/generic/auth.js +0 -874
- package/libx/_runtime/common/toggles/alpha.js +0 -43
- package/libx/_runtime/db/generic/arrayed.js +0 -33
- package/libx/_runtime/fiori/uiflex/index.js +0 -35
- package/libx/_runtime/fiori/uiflex/service.js +0 -150
- package/libx/rest/utils/data.js +0 -60
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
const cds = require('../../cds.js')
|
|
2
|
-
const LOG = cds.log('messaging')
|
|
3
2
|
const express = require('express')
|
|
4
3
|
const getTenantInfo = require('./getTenantInfo.js')
|
|
5
|
-
const isSecured = () => cds.requires.
|
|
6
|
-
const { getTenant } = require('../../auth/strategies/
|
|
4
|
+
const isSecured = () => cds.requires.auth && cds.requires.auth.credentials
|
|
5
|
+
const { getTenant } = require('../../auth/strategies/xssecUtils')
|
|
7
6
|
|
|
8
7
|
const _isAll = a => a && a.includes('all')
|
|
9
|
-
const _hasScope = (scope, req) =>
|
|
10
|
-
req && req.authInfo && req.authInfo.checkLocalScope && req.authInfo.checkLocalScope(scope)
|
|
11
8
|
|
|
12
9
|
class EndpointRegistry {
|
|
13
|
-
constructor(basePath) {
|
|
10
|
+
constructor(basePath, LOG) {
|
|
14
11
|
const deployPath = basePath + '/deploy'
|
|
15
12
|
const paths = [basePath, deployPath]
|
|
16
13
|
this.webhookCallbacks = new Map()
|
|
@@ -18,11 +15,20 @@ class EndpointRegistry {
|
|
|
18
15
|
if (isSecured()) {
|
|
19
16
|
const JWTStrategy = require('../../auth/strategies/JWT.js')
|
|
20
17
|
const passport = require('passport')
|
|
21
|
-
|
|
18
|
+
// REVISIT: It's unclear if the credentials from cds.requires.auth need to be used here.
|
|
19
|
+
// In principle, user-facing endpoints might differ from messaging ones.
|
|
20
|
+
passport.use(new JWTStrategy(cds.requires.auth.credentials))
|
|
22
21
|
paths.forEach(path => {
|
|
23
22
|
cds.app.use(path, passport.initialize())
|
|
24
23
|
cds.app.use(path, passport.authenticate('JWT', { session: false }))
|
|
24
|
+
cds.app.use(path, (req, res, next) => {
|
|
25
|
+
// unsuccessful auth doesn't automatically reject!
|
|
26
|
+
if (!req.user) return res.status(401).json({ message: 'Unauthorized' })
|
|
27
|
+
next()
|
|
28
|
+
})
|
|
25
29
|
})
|
|
30
|
+
} else if (process.env.NODE_ENV === 'production') {
|
|
31
|
+
LOG.warn('Messaging endpoints not secured')
|
|
26
32
|
}
|
|
27
33
|
paths.forEach(path => {
|
|
28
34
|
cds.app.use(path, express.json({ type: 'application/*+json' }))
|
|
@@ -43,7 +49,7 @@ class EndpointRegistry {
|
|
|
43
49
|
|
|
44
50
|
cds.app.options(basePath, (req, res) => {
|
|
45
51
|
try {
|
|
46
|
-
if (isSecured() && !
|
|
52
|
+
if (isSecured() && !req.user.is('emcallback')) return res.sendStatus(403)
|
|
47
53
|
res.set('WebHook-Allowed-Origin', req.headers['webhook-request-origin'])
|
|
48
54
|
res.sendStatus(200)
|
|
49
55
|
} catch (error) {
|
|
@@ -53,7 +59,7 @@ class EndpointRegistry {
|
|
|
53
59
|
LOG._debug && LOG.debug('Register inbound endpoint', { basePath, method: 'POST' })
|
|
54
60
|
cds.app.post(basePath, (req, res) => {
|
|
55
61
|
try {
|
|
56
|
-
if (isSecured() && !
|
|
62
|
+
if (isSecured() && !req.user.is('emcallback')) return res.sendStatus(403)
|
|
57
63
|
const queueName = req.query.q
|
|
58
64
|
const authInfo = req.authInfo
|
|
59
65
|
const xAddress = req.headers['x-address']
|
|
@@ -83,7 +89,7 @@ class EndpointRegistry {
|
|
|
83
89
|
})
|
|
84
90
|
cds.app.post(deployPath, async (req, res) => {
|
|
85
91
|
try {
|
|
86
|
-
if (isSecured() && !
|
|
92
|
+
if (isSecured() && !req.user.is('emmanagement')) return res.sendStatus(403)
|
|
87
93
|
const tenants = req.body && !_isAll(req.body.tenants) && req.body.tenants
|
|
88
94
|
const queues = req.body && !_isAll(req.body.queues) && req.body.queues
|
|
89
95
|
const options = { wipeData: req.body && req.body.wipeData }
|
|
@@ -120,9 +126,10 @@ class EndpointRegistry {
|
|
|
120
126
|
const registries = new Map()
|
|
121
127
|
|
|
122
128
|
// REVISIT: Use cds mechanism instead of express? -> Need option method and handler for specifica
|
|
123
|
-
const registerWebhookEndpoints = (basePath, queueName, cb) => {
|
|
129
|
+
const registerWebhookEndpoints = (basePath, queueName, LOG, cb) => {
|
|
124
130
|
const registry =
|
|
125
|
-
registries.get(basePath) ||
|
|
131
|
+
registries.get(basePath) ||
|
|
132
|
+
(registries.set(basePath, new EndpointRegistry(basePath, LOG)) && registries.get(basePath))
|
|
126
133
|
registry.registerWebhookCallback(queueName, cb)
|
|
127
134
|
}
|
|
128
135
|
|
|
@@ -10,7 +10,6 @@ const {
|
|
|
10
10
|
registerDeployEndpoints,
|
|
11
11
|
registerWebhookEndpoints
|
|
12
12
|
} = require('./enterprise-messaging-utils/registerEndpoints.js')
|
|
13
|
-
const LOG = cds.log('messaging')
|
|
14
13
|
const cloudEvents = require('./enterprise-messaging-utils/cloudEvents.js')
|
|
15
14
|
|
|
16
15
|
const BASE_PATH = '/messaging/enterprise-messaging'
|
|
@@ -61,12 +60,12 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
61
60
|
const management = await this.getManagement(subdomain).waitUntilReady()
|
|
62
61
|
await management.undeploy()
|
|
63
62
|
} catch (error) {
|
|
64
|
-
|
|
63
|
+
this.LOG.error('Failed to delete messaging artifacts for subdomain', subdomain, '(', error, ')')
|
|
65
64
|
}
|
|
66
65
|
return next()
|
|
67
66
|
})
|
|
68
67
|
provisioning.on('dependencies', async (req, next) => {
|
|
69
|
-
LOG._info && LOG.info('Include Enterprise-Messaging as SaaS dependency')
|
|
68
|
+
this.LOG._info && this.LOG.info('Include Enterprise-Messaging as SaaS dependency')
|
|
70
69
|
const res = await next()
|
|
71
70
|
const xsappname = this.options.credentials && this.options.credentials.xsappname
|
|
72
71
|
if (xsappname) {
|
|
@@ -80,7 +79,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
80
79
|
|
|
81
80
|
startListening() {
|
|
82
81
|
const doNotDeploy = cds._mtxEnabled && !this.options.deployForProvider
|
|
83
|
-
if (doNotDeploy) LOG._info && LOG.info('Skipping deployment of messaging artifacts for provider account')
|
|
82
|
+
if (doNotDeploy) this.LOG._info && this.LOG.info('Skipping deployment of messaging artifacts for provider account')
|
|
84
83
|
super.startListening({ doNotDeploy })
|
|
85
84
|
if (!doNotDeploy && this.subscribedTopics.size) {
|
|
86
85
|
const management = this.getManagement()
|
|
@@ -97,7 +96,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
97
96
|
|
|
98
97
|
async listenToClient(cb) {
|
|
99
98
|
_checkAppURL(this.optionsApp.appURL)
|
|
100
|
-
registerWebhookEndpoints(BASE_PATH, this.queueName, cb)
|
|
99
|
+
registerWebhookEndpoints(BASE_PATH, this.queueName, this.LOG, cb)
|
|
101
100
|
if (cds._mtxEnabled) {
|
|
102
101
|
await this.addMTXHandlers()
|
|
103
102
|
registerDeployEndpoints(BASE_PATH, this.queueName, async (tenantInfo, options) => {
|
|
@@ -110,7 +109,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
110
109
|
await management.deploy()
|
|
111
110
|
result.succeeded.push(info.tenant)
|
|
112
111
|
} catch (error) {
|
|
113
|
-
|
|
112
|
+
this.LOG.error('Failed to create messaging artifacts for subdomain', info.subdomain, ':', error)
|
|
114
113
|
result.failed.push({ error: error.message, tenant: info.tenant })
|
|
115
114
|
}
|
|
116
115
|
})
|
|
@@ -147,7 +146,8 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
147
146
|
subscribedTopics: this.subscribedTopics,
|
|
148
147
|
alternativeTopics: this.alternativeTopics,
|
|
149
148
|
subdomain: _subdomain,
|
|
150
|
-
namespace: this.options.credentials && this.options.credentials.namespace
|
|
149
|
+
namespace: this.options.credentials && this.options.credentials.namespace,
|
|
150
|
+
LOG: this.LOG
|
|
151
151
|
})
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -174,7 +174,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
174
174
|
headers: {
|
|
175
175
|
'x-qos': 1
|
|
176
176
|
},
|
|
177
|
-
attemptInfo: () => LOG._info && LOG.info('Emit', { topic }),
|
|
177
|
+
attemptInfo: () => this.LOG._info && this.LOG.info('Emit', { topic }),
|
|
178
178
|
errMsg,
|
|
179
179
|
target: { kind: 'MESSAGE', topic },
|
|
180
180
|
tokenStore: {}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
|
-
const LOG = cds.log('messaging')
|
|
3
2
|
|
|
4
3
|
const path = require('path')
|
|
5
4
|
const fs = require('fs').promises
|
|
@@ -25,11 +24,11 @@ class FileBasedMessaging extends MessagingService {
|
|
|
25
24
|
const e = _msg.event
|
|
26
25
|
delete _msg.event
|
|
27
26
|
await this.queued(lock)(this.file)
|
|
28
|
-
LOG._debug && LOG.debug('Emit', { topic: e, file: this.file })
|
|
27
|
+
this.LOG._debug && this.LOG.debug('Emit', { topic: e, file: this.file })
|
|
29
28
|
try {
|
|
30
29
|
await fs.appendFile(this.file, `\n${e} ${JSON.stringify(_msg)}`)
|
|
31
30
|
} catch (e) {
|
|
32
|
-
LOG._debug && LOG.debug('Error', e)
|
|
31
|
+
this.LOG._debug && this.LOG.debug('Error', e)
|
|
33
32
|
} finally {
|
|
34
33
|
unlock(this.file)
|
|
35
34
|
}
|
|
@@ -53,9 +52,10 @@ class FileBasedMessaging extends MessagingService {
|
|
|
53
52
|
if (this.subscribedTopics.has(topic)) {
|
|
54
53
|
const event = this.subscribedTopics.get(topic)
|
|
55
54
|
if (!event) return
|
|
55
|
+
// REVISIT: should we use this.dispatch instead of super.emit for inbound messages?
|
|
56
56
|
super
|
|
57
57
|
.emit({ event, ...json, inbound: true })
|
|
58
|
-
.catch(e => LOG.error('ERROR occured in asynchronous event processing:', e))
|
|
58
|
+
.catch(e => this.LOG.error('ERROR occured in asynchronous event processing:', e))
|
|
59
59
|
} else other.push(each + '\n')
|
|
60
60
|
}
|
|
61
61
|
} catch (e) {
|
|
@@ -65,7 +65,7 @@ class FileBasedMessaging extends MessagingService {
|
|
|
65
65
|
if (other.length < lines.length) await fs.writeFile(this.file, other.join(''))
|
|
66
66
|
this.recent = await touched(this.file)
|
|
67
67
|
} catch (e) {
|
|
68
|
-
LOG._debug && LOG.debug(e)
|
|
68
|
+
this.LOG._debug && this.LOG.debug(e)
|
|
69
69
|
} finally {
|
|
70
70
|
unlock(this.file)
|
|
71
71
|
}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
const cds = require('../cds.js')
|
|
2
1
|
const AMQPWebhookMessaging = require('./AMQPWebhookMessaging')
|
|
3
2
|
const AMQPClient = require('./common-utils/AMQPClient.js')
|
|
4
3
|
|
|
5
4
|
const optionsMessaging = require('./message-queuing-utils/options-messaging.js')
|
|
6
5
|
const optionsManagement = require('./message-queuing-utils/options-management.js')
|
|
7
6
|
const authorizedRequest = require('./common-utils/authorizedRequest')
|
|
8
|
-
const LOG = cds.log('messaging')
|
|
9
7
|
|
|
10
8
|
class MQManagement {
|
|
11
|
-
constructor({ options, queueConfig, queueName, subscribedTopics }) {
|
|
9
|
+
constructor({ options, queueConfig, queueName, subscribedTopics, LOG }) {
|
|
12
10
|
this.options = options
|
|
13
11
|
this.queueConfig = queueConfig
|
|
14
12
|
this.queueName = queueName
|
|
15
13
|
this.subscribedTopics = subscribedTopics
|
|
14
|
+
this.LOG = LOG
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
async getQueue(queueName = this.queueName) {
|
|
@@ -21,7 +20,7 @@ class MQManagement {
|
|
|
21
20
|
uri: this.options.url,
|
|
22
21
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
|
|
23
22
|
oa2: this.options.auth.oauth2,
|
|
24
|
-
attemptInfo: () => LOG._info && LOG.info('Get queue', { queue: queueName }),
|
|
23
|
+
attemptInfo: () => this.LOG._info && this.LOG.info('Get queue', { queue: queueName }),
|
|
25
24
|
errMsg: `Queue "${queueName}" could not be retrieved`,
|
|
26
25
|
target: { kind: 'QUEUE', queue: queueName },
|
|
27
26
|
tokenStore: this
|
|
@@ -35,7 +34,7 @@ class MQManagement {
|
|
|
35
34
|
uri: this.options.url,
|
|
36
35
|
path: `/v1/management/queues`,
|
|
37
36
|
oa2: this.options.auth.oauth2,
|
|
38
|
-
attemptInfo: () => LOG._info && LOG.info('Get queues'),
|
|
37
|
+
attemptInfo: () => this.LOG._info && this.LOG.info('Get queues'),
|
|
39
38
|
errMsg: `Queues could not be retrieved`,
|
|
40
39
|
target: { kind: 'QUEUE' },
|
|
41
40
|
tokenStore: this
|
|
@@ -50,7 +49,7 @@ class MQManagement {
|
|
|
50
49
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
|
|
51
50
|
oa2: this.options.auth.oauth2,
|
|
52
51
|
dataObj: this.queueConfig,
|
|
53
|
-
attemptInfo: () => LOG._info && LOG.info('Create queue', { queue: queueName }),
|
|
52
|
+
attemptInfo: () => this.LOG._info && this.LOG.info('Create queue', { queue: queueName }),
|
|
54
53
|
errMsg: `Queue "${queueName}" could not be created`,
|
|
55
54
|
target: { kind: 'QUEUE', queue: queueName },
|
|
56
55
|
tokenStore: this
|
|
@@ -63,7 +62,7 @@ class MQManagement {
|
|
|
63
62
|
uri: this.options.url,
|
|
64
63
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
|
|
65
64
|
oa2: this.options.auth.oauth2,
|
|
66
|
-
attemptInfo: () => LOG._info && LOG.info('Delete queue', { queue: queueName }),
|
|
65
|
+
attemptInfo: () => this.LOG._info && this.LOG.info('Delete queue', { queue: queueName }),
|
|
67
66
|
errMsg: `Queue "${queueName}" could not be deleted`,
|
|
68
67
|
target: { kind: 'QUEUE', queue: queueName },
|
|
69
68
|
tokenStore: this
|
|
@@ -76,7 +75,7 @@ class MQManagement {
|
|
|
76
75
|
uri: this.options.url,
|
|
77
76
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}/subscriptions/topics`,
|
|
78
77
|
oa2: this.options.auth.oauth2,
|
|
79
|
-
attemptInfo: () => LOG._info && LOG.info('Get subscriptions', { queue: queueName }),
|
|
78
|
+
attemptInfo: () => this.LOG._info && this.LOG.info('Get subscriptions', { queue: queueName }),
|
|
80
79
|
errMsg: `Subscriptions for "${queueName}" could not be retrieved`,
|
|
81
80
|
target: { kind: 'SUBSCRIPTION', queue: queueName },
|
|
82
81
|
tokenStore: this
|
|
@@ -92,7 +91,8 @@ class MQManagement {
|
|
|
92
91
|
topicPattern
|
|
93
92
|
)}`,
|
|
94
93
|
oa2: this.options.auth.oauth2,
|
|
95
|
-
attemptInfo: () =>
|
|
94
|
+
attemptInfo: () =>
|
|
95
|
+
this.LOG._info && this.LOG.info('Create subscription', { topic: topicPattern, queue: queueName }),
|
|
96
96
|
errMsg: `Subscription "${topicPattern}" could not be added to queue "${queueName}"`,
|
|
97
97
|
target: { kind: 'SUBSCRIPTION', queue: queueName, topic: topicPattern },
|
|
98
98
|
tokenStore: this
|
|
@@ -107,7 +107,8 @@ class MQManagement {
|
|
|
107
107
|
topicPattern
|
|
108
108
|
)}`,
|
|
109
109
|
oa2: this.options.auth.oauth2,
|
|
110
|
-
attemptInfo: () =>
|
|
110
|
+
attemptInfo: () =>
|
|
111
|
+
this.LOG._info && this.LOG.info('Delete subscription', { topic: topicPattern, queue: queueName }),
|
|
111
112
|
errMsg: `Subscription "${topicPattern}" could not be deleted from queue "${queueName}"`,
|
|
112
113
|
target: { kind: 'SUBSCRIPTION', queue: queueName, topic: topicPattern },
|
|
113
114
|
tokenStore: this
|
|
@@ -115,7 +116,7 @@ class MQManagement {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
async createQueueAndSubscriptions() {
|
|
118
|
-
LOG._info && LOG.info(`Create messaging artifacts`)
|
|
119
|
+
this.LOG._info && this.LOG.info(`Create messaging artifacts`)
|
|
119
120
|
const created = await this.createQueue()
|
|
120
121
|
if (created && created.statusCode === 200) {
|
|
121
122
|
// We need to make sure to only keep our own subscriptions
|
|
@@ -171,7 +172,8 @@ class MessageQueuing extends AMQPWebhookMessaging {
|
|
|
171
172
|
options: _optionsManagement,
|
|
172
173
|
queueConfig,
|
|
173
174
|
queueName,
|
|
174
|
-
subscribedTopics: this.subscribedTopics
|
|
175
|
+
subscribedTopics: this.subscribedTopics,
|
|
176
|
+
LOG: this.LOG
|
|
175
177
|
})
|
|
176
178
|
return this.management
|
|
177
179
|
}
|
|
@@ -21,9 +21,10 @@ const _getMessagesEntity = () => {
|
|
|
21
21
|
return messagesEntity
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// REVISIT: Is
|
|
24
|
+
// REVISIT: Is this always a reliable way to identify the provider tenant?
|
|
25
|
+
// Are there scenarios where the credentials have a different format?
|
|
25
26
|
const _isProviderTenant = tenant =>
|
|
26
|
-
cds.requires.
|
|
27
|
+
cds.requires.auth && cds.requires.auth.credentials && cds.requires.auth.credentials.identityzoneid === tenant
|
|
27
28
|
|
|
28
29
|
const hasPersistentOutbox = (srv, tenant) => {
|
|
29
30
|
if (!cds.requires.outbox || cds.requires.outbox.kind !== 'persistent-outbox') return false
|
|
@@ -86,16 +87,15 @@ const processMessages = async (service, tenant, _opts = {}) => {
|
|
|
86
87
|
} catch (e) {
|
|
87
88
|
// could potentially be a timeout
|
|
88
89
|
const _waitingTime = waitingTime(opts.attempt)
|
|
89
|
-
LOG.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)
|
|
90
|
+
LOG.error(
|
|
91
|
+
'Outbox SELECT FOR UPDATE failed',
|
|
92
|
+
opts.attempt > 0
|
|
93
|
+
? ''
|
|
94
|
+
: {
|
|
95
|
+
cause: e
|
|
96
|
+
},
|
|
97
|
+
`Retrying in ${Math.round(_waitingTime / 1000)} s`
|
|
98
|
+
)
|
|
99
99
|
outboxRunner.schedule(
|
|
100
100
|
{
|
|
101
101
|
name,
|
|
@@ -127,7 +127,7 @@ const processMessages = async (service, tenant, _opts = {}) => {
|
|
|
127
127
|
const _waitingTime = waitingTime(error.failedMessage.attempts)
|
|
128
128
|
const info = { service: name, event: error.failedMessage.event }
|
|
129
129
|
if (error.failedMessage.attempts > 0) info.cause = error.failedMessage.error
|
|
130
|
-
LOG.
|
|
130
|
+
LOG.error('Emit failed', info, `Retrying in ${Math.round(_waitingTime / 1000)} s`)
|
|
131
131
|
await UPDATE(messagesEntity)
|
|
132
132
|
.where({ ID: error.failedMessage.ID })
|
|
133
133
|
.set({ attempts: { '+=': 1 } })
|
|
@@ -140,12 +140,11 @@ const processMessages = async (service, tenant, _opts = {}) => {
|
|
|
140
140
|
() => processMessages(service, tenant, opts)
|
|
141
141
|
)
|
|
142
142
|
} else {
|
|
143
|
-
LOG.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
143
|
+
LOG.error(
|
|
144
|
+
'Emit failed',
|
|
145
|
+
{ service: name, event: error.failedMessage.event, cause: error.failedMessage.error },
|
|
146
|
+
'Unrecoverable, outbox entry deleted'
|
|
147
|
+
)
|
|
149
148
|
}
|
|
150
149
|
} else {
|
|
151
150
|
outboxRunner.success({ name, tenant })
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* eslint-disable prettier/prettier */
|
|
2
|
+
const redis = require('redis')
|
|
3
|
+
const cds = require('../../../lib')
|
|
4
|
+
const waitingTime = require('./common-utils/waitingTime')
|
|
5
|
+
const normalizeIncomingMessage = require('./common-utils/normalizeIncomingMessage')
|
|
6
|
+
const { hasPersistentOutbox } = require('./outbox/utils')
|
|
7
|
+
|
|
8
|
+
const _handleReconnects = (client, LOG) => {
|
|
9
|
+
client.on('reconnecting', () => {
|
|
10
|
+
LOG.warn('Reconnecting')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
client.on('error', error => {
|
|
14
|
+
LOG.warn('Failed to connect to Redis: ', error)
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class RedisMessaging extends cds.MessagingService {
|
|
19
|
+
async init() {
|
|
20
|
+
await super.init()
|
|
21
|
+
const credentials = this.options && this.options.credentials
|
|
22
|
+
const config = {
|
|
23
|
+
socket: {
|
|
24
|
+
reconnectStrategy: attempts => {
|
|
25
|
+
const _waitingTime = waitingTime(attempts)
|
|
26
|
+
this.LOG.warn(`Connection to Redis lost: Reconnecting in ${Math.round(_waitingTime / 1000)} s`)
|
|
27
|
+
return _waitingTime
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// rediss://someUserName:somePassword@hostName:port -> rediss://:somePassword@hostName:port
|
|
32
|
+
const url = credentials && credentials.uri && credentials.uri.replace(/\/\/.*?:/, '//:')
|
|
33
|
+
if (!url) this.LOG._warn && this.LOG.warn('No Redis credentials found, using default credentials')
|
|
34
|
+
else config.url = url
|
|
35
|
+
this.client = redis.createClient(config)
|
|
36
|
+
_handleReconnects(this.client, this.LOG)
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await this.client.connect()
|
|
40
|
+
} catch (e) {
|
|
41
|
+
throw new Error('Connection to Redis could not be established: ' + e)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this._ready = true
|
|
45
|
+
this.client.on('end', () => {
|
|
46
|
+
this._ready = false
|
|
47
|
+
})
|
|
48
|
+
this.client.on('error', () => {
|
|
49
|
+
this._ready = false
|
|
50
|
+
})
|
|
51
|
+
this.client.on('ready', () => {
|
|
52
|
+
this._ready = true
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
cds.once('listening', () => {
|
|
56
|
+
this.startListening()
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async startListening() {
|
|
61
|
+
let subscriber
|
|
62
|
+
for (const topic of [...this.subscribedTopics].map(kv => kv[0])) {
|
|
63
|
+
if (!subscriber) {
|
|
64
|
+
// For subscriptions we need to duplicate the connection
|
|
65
|
+
subscriber = this.client.duplicate()
|
|
66
|
+
_handleReconnects(subscriber)
|
|
67
|
+
await subscriber.connect()
|
|
68
|
+
}
|
|
69
|
+
this.LOG._info && this.LOG('Create subscription', { topic })
|
|
70
|
+
await subscriber.subscribe(topic, async message => {
|
|
71
|
+
const msg = normalizeIncomingMessage(message)
|
|
72
|
+
msg.event = topic
|
|
73
|
+
try {
|
|
74
|
+
await super.emit(msg)
|
|
75
|
+
} catch (e) {
|
|
76
|
+
this.LOG.error('ERROR occured in asynchronous event processing:', e)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async emit(msg) {
|
|
83
|
+
const _msg = this.message4(msg)
|
|
84
|
+
this.LOG._info && this.LOG.info('Emit', { topic: _msg.event })
|
|
85
|
+
if (!this._ready && hasPersistentOutbox(this, cds.context && cds.context.tenant))
|
|
86
|
+
throw new Error('Redis connection not ready')
|
|
87
|
+
await this.client.publish(_msg.event, JSON.stringify({ data: _msg.data, ...(_msg.headers || {}) }))
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = RedisMessaging
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
|
-
const LOG = cds.log('messaging')
|
|
3
2
|
const queued = require('./common-utils/queued')
|
|
4
3
|
const OutboxService = require('./Outbox')
|
|
5
4
|
|
|
6
5
|
const _topic = declared => declared['@topic'] || declared.name
|
|
7
6
|
|
|
8
7
|
let usedTopicOnce = false
|
|
9
|
-
const _warnAndStripTopicPrefix = event => {
|
|
8
|
+
const _warnAndStripTopicPrefix = (event, LOG) => {
|
|
10
9
|
if (event.startsWith('topic:')) {
|
|
11
10
|
// backwards compatibility
|
|
12
11
|
event = event.replace(/topic:/, '')
|
|
@@ -24,6 +23,7 @@ class MessagingService extends OutboxService {
|
|
|
24
23
|
// enables queued async operations (without awaiting)
|
|
25
24
|
this.queued = queued()
|
|
26
25
|
this.subscribedTopics = new Map()
|
|
26
|
+
this.LOG = cds.log(this.kind ? `${this.kind}|messaging` : 'messaging')
|
|
27
27
|
// Only for one central `messaging` service, otherwise all technical services would register themselves
|
|
28
28
|
if (this.name === 'messaging') {
|
|
29
29
|
this._registeredServices = new Map()
|
|
@@ -81,7 +81,7 @@ class MessagingService extends OutboxService {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
on(event, cb) {
|
|
84
|
-
const _event = _warnAndStripTopicPrefix(event)
|
|
84
|
+
const _event = _warnAndStripTopicPrefix(event, this.LOG)
|
|
85
85
|
// save all subscribed topics (not needed for local-messaging)
|
|
86
86
|
this.subscribedTopics.set(this.prepareTopic(_event, true), _event)
|
|
87
87
|
return super.on(_event, cb)
|
|
@@ -109,9 +109,11 @@ class MessagingService extends OutboxService {
|
|
|
109
109
|
|
|
110
110
|
message4(msg) {
|
|
111
111
|
const _msg = { ...msg }
|
|
112
|
-
if (msg.inbound && !cds.context)
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
if (msg.inbound && !cds.context) {
|
|
113
|
+
// REVISIT: why are all inbound messages executed with privileged user?
|
|
114
|
+
cds.context = { tenant: msg.tenant, user: new cds.User.Privileged() }
|
|
115
|
+
}
|
|
116
|
+
_msg.event = _warnAndStripTopicPrefix(_msg.event, this.LOG)
|
|
115
117
|
if (!_msg.headers) _msg.headers = {}
|
|
116
118
|
if (!_msg.inbound) {
|
|
117
119
|
_msg.headers = { ..._msg.headers } // don't change the original object
|
|
@@ -40,12 +40,13 @@ const _setCorrectValue = (el, data, params, kind) => {
|
|
|
40
40
|
const _buildPartialUrlFunctions = (url, data, params, kind = 'odata-v4') => {
|
|
41
41
|
const funcParams = []
|
|
42
42
|
const queryOptions = []
|
|
43
|
-
|
|
43
|
+
// REVISIT: take params from params after importer fix (the keys should not be part of params)
|
|
44
|
+
for (const param in data) {
|
|
44
45
|
if (kind === 'odata-v2') {
|
|
45
|
-
funcParams.push(`${
|
|
46
|
+
funcParams.push(`${param}=${_setCorrectValue(param, data, params, kind)}`)
|
|
46
47
|
} else {
|
|
47
|
-
funcParams.push(`${
|
|
48
|
-
queryOptions.push(`@${
|
|
48
|
+
funcParams.push(`${param}=@${param}`)
|
|
49
|
+
queryOptions.push(`@${param}=${_setCorrectValue(param, data, params, kind)}`)
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
return kind === 'odata-v2'
|
|
@@ -62,8 +63,17 @@ const _extractParamsFromData = (data, params) => {
|
|
|
62
63
|
|
|
63
64
|
const _buildKeys = (req, kind) => {
|
|
64
65
|
const keys = []
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
if (req.params && req.params.length > 0) {
|
|
67
|
+
const p1 = req.params[0]
|
|
68
|
+
if (typeof p1 !== 'object') return [p1]
|
|
69
|
+
for (const key in req.target.keys) {
|
|
70
|
+
keys.push(`${key}=${formatVal(p1[key], key, req.target, kind)}`)
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
// REVISIT: shall we keep that or remove it?
|
|
74
|
+
for (const key in req.target.keys) {
|
|
75
|
+
keys.push(`${key}=${formatVal(req.data[key], key, req.target, kind)}`)
|
|
76
|
+
}
|
|
67
77
|
}
|
|
68
78
|
return keys
|
|
69
79
|
}
|
|
@@ -83,7 +93,14 @@ const _handleBoundActionFunction = (srv, def, req, url) => {
|
|
|
83
93
|
|
|
84
94
|
const _handleUnboundActionFunction = (srv, def, req, event) => {
|
|
85
95
|
if (def.kind === 'action') {
|
|
86
|
-
return
|
|
96
|
+
// REVISIT: only for "rest" unbound actions/functions, we enforce axios to return a buffer
|
|
97
|
+
// required by cds-mt
|
|
98
|
+
const isBinary =
|
|
99
|
+
srv.kind === 'rest' &&
|
|
100
|
+
def &&
|
|
101
|
+
def.returns &&
|
|
102
|
+
(def.returns.type === 'cds.LargeBinary' || def.returns.type === 'cds.Binary')
|
|
103
|
+
return srv.send({ method: 'POST', path: `/${event}`, data: req.data, _binary: isBinary })
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
const url =
|
|
@@ -97,11 +114,30 @@ const _handleV2ActionFunction = (srv, def, req, event, kind) => {
|
|
|
97
114
|
return def.kind === 'function' ? srv.get(url) : srv.post(url, {})
|
|
98
115
|
}
|
|
99
116
|
|
|
117
|
+
const _handleV2BoundActionFunction = (srv, def, req, event, kind) => {
|
|
118
|
+
const params = []
|
|
119
|
+
const data = req.data
|
|
120
|
+
// REVISIT: take params from def.params, after importer fix (the keys should not be part of params)
|
|
121
|
+
for (const param in req.data) {
|
|
122
|
+
params.push(`${param}=${formatVal(data[param], param, { elements: def.params }, kind)}`)
|
|
123
|
+
}
|
|
124
|
+
const keys = _buildKeys(req, this.kind)
|
|
125
|
+
if (keys.length === 1 && typeof req.params[0] !== 'object') {
|
|
126
|
+
params.push(`${Object.keys(req.target.keys)[0]}=${keys[0]}`)
|
|
127
|
+
} else {
|
|
128
|
+
params.push(...keys)
|
|
129
|
+
}
|
|
130
|
+
const url = `${`/${event}`}?${params.join('&')}`
|
|
131
|
+
return def.kind === 'function' ? srv.get(url) : srv.post(url, {})
|
|
132
|
+
}
|
|
133
|
+
|
|
100
134
|
const _addHandlerActionFunction = (srv, def, target) => {
|
|
101
135
|
const event = def.name.match(/\w*$/)[0]
|
|
102
136
|
if (target) {
|
|
103
137
|
srv.on(event, target, async function (req) {
|
|
104
138
|
const shortEntityName = req.target.name.replace(`${this.namespace}.`, '')
|
|
139
|
+
if (this.kind === 'odata-v2')
|
|
140
|
+
return _handleV2BoundActionFunction(srv, def, req, `${shortEntityName}_${event}`, this.kind)
|
|
105
141
|
const url = `/${shortEntityName}(${_buildKeys(req, this.kind).join(',')})/${this.namespace}.${event}`
|
|
106
142
|
return _handleBoundActionFunction(srv, def, req, url)
|
|
107
143
|
})
|
|
@@ -208,7 +244,7 @@ class RemoteService extends cds.Service {
|
|
|
208
244
|
|
|
209
245
|
if (req.target && req.target.name && this.definition && req.target.name.startsWith(this.definition.name + '.')) {
|
|
210
246
|
const result = await super.handle(req)
|
|
211
|
-
// only post process if alias was
|
|
247
|
+
// only post process if alias was explicitly set in query
|
|
212
248
|
if (_selectOnlyWithAlias(req.query)) {
|
|
213
249
|
return postProcess(req.query, result, this, true)
|
|
214
250
|
}
|