@sap/cds 7.3.1 → 7.4.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 +69 -3
- package/_i18n/i18n_es_MX.properties +110 -0
- package/apis/cds.d.ts +13 -12
- package/apis/core.d.ts +27 -108
- package/apis/cqn.d.ts +15 -18
- package/apis/csn.d.ts +95 -60
- package/apis/env.d.ts +25 -0
- package/apis/events.d.ts +125 -0
- package/apis/{reflect.d.ts → linked.d.ts} +29 -38
- package/apis/models.d.ts +60 -45
- package/apis/ql.d.ts +19 -5
- package/apis/{serve.d.ts → server.d.ts} +59 -33
- package/apis/services.d.ts +76 -147
- package/apis/test.d.ts +1 -1
- package/bin/serve.js +3 -0
- package/lib/compile/cds-compile.js +2 -2
- package/lib/compile/etc/csv.js +2 -1
- package/lib/compile/to/edm.js +8 -3
- package/lib/compile/to/gql.js +4 -0
- package/lib/dbs/cds-deploy.js +52 -4
- package/lib/env/cds-requires.js +27 -15
- package/lib/env/defaults.js +1 -0
- package/lib/env/schemas/index.js +10 -0
- package/lib/index.js +7 -4
- package/lib/linked/models.js +8 -5
- package/lib/ql/CREATE.js +2 -0
- package/lib/ql/DELETE.js +1 -0
- package/lib/ql/DROP.js +2 -0
- package/lib/ql/INSERT.js +2 -22
- package/lib/ql/Query.js +59 -22
- package/lib/ql/SELECT.js +5 -0
- package/lib/ql/STREAM.js +2 -0
- package/lib/ql/UPDATE.js +2 -0
- package/lib/ql/UPSERT.js +3 -1
- package/lib/ql/cds-ql.js +21 -5
- package/lib/ql/infer.js +129 -0
- package/lib/req/cds-context.js +8 -5
- package/lib/srv/cds-connect.js +3 -1
- package/lib/utils/axios.js +4 -2
- package/lib/utils/data.js +3 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +12 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +27 -9
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +8 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +11 -8
- package/libx/_runtime/common/code-ext/worker.js +5 -16
- package/libx/_runtime/common/generic/auth/capabilities.js +11 -2
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/common/utils/postProcessing.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +28 -9
- package/libx/{common → _runtime/common}/utils/ucsn.js +19 -11
- package/libx/_runtime/db/expand/expandCQNToJoin.js +6 -6
- package/libx/_runtime/db/expand/rawToExpanded.js +4 -4
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -1
- package/libx/_runtime/db/sql-builder/UpdateBuilder.js +6 -1
- package/libx/_runtime/db/sql-builder/dollar.js +7 -7
- package/libx/_runtime/fiori/generic/activate.js +2 -2
- package/libx/_runtime/fiori/generic/edit.js +25 -45
- package/libx/_runtime/fiori/generic/read.js +3 -5
- package/libx/_runtime/fiori/lean-draft.js +171 -84
- package/libx/_runtime/fiori/utils/delete.js +7 -1
- package/libx/_runtime/fiori/utils/handler.js +4 -6
- package/libx/_runtime/fiori/utils/lockInfo.js +27 -0
- package/libx/_runtime/fiori/utils/where.js +20 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -2
- package/libx/_runtime/messaging/Outbox.js +12 -47
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -3
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +3 -0
- package/libx/_runtime/messaging/common-utils/connections.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +12 -13
- package/libx/_runtime/messaging/file-based.js +7 -5
- package/libx/_runtime/messaging/redis-messaging.js +10 -11
- package/libx/_runtime/messaging/service.js +12 -26
- package/libx/_runtime/remote/Service.js +52 -36
- package/libx/_runtime/remote/utils/client.js +24 -125
- package/libx/odata/afterburner.js +16 -6
- package/libx/odata/grammar.peggy +26 -7
- package/libx/odata/metadata.js +18 -1
- package/libx/odata/parser.js +1 -1
- package/libx/odata/service-document.js +0 -1
- package/libx/odata/utils.js +19 -3
- package/libx/{_runtime/messaging/outbox/utils.js → outbox/index.js} +94 -24
- package/libx/rest/middleware/parse.js +1 -1
- package/package.json +2 -2
- package/apis/connect.d.ts +0 -39
- package/bin/utils/modules.js +0 -7
- package/bin/utils/term.js +0 -56
- package/lib/env/schema.js +0 -9
- package/lib/linked/queries.js +0 -41
- package/lib/srv/protocols/odata-v2-proxy.js +0 -3699
- package/libx/common/asserts.js +0 -0
- package/libx/common/crud.js +0 -0
- package/libx/common/etag.js +0 -0
- package/libx/common/localized.js +0 -0
- package/libx/common/managed.js +0 -0
- package/libx/common/paging.js +0 -0
- package/libx/common/readme.md +0 -4
- package/libx/common/sorting.js +0 -0
- package/libx/common/temporal.js +0 -0
- package/libx/connect/auth.js +0 -0
- package/libx/connect/perf.js +0 -0
- package/libx/connect/readme.md +0 -3
- package/libx/fiori/draft/readme.md +0 -1
- package/libx/fiori/readme.md +0 -1
- package/libx/hana/readme.md +0 -1
- package/libx/msg/readme.md +0 -3
- package/libx/readme.md +0 -1
- package/libx/sqlite/readme.md +0 -1
- /package/libx/_runtime/{messaging/common-utils → common/utils}/waitingTime.js +0 -0
- /package/libx/{_runtime/messaging/outbox → outbox}/OutboxRunner.js +0 -0
|
@@ -1,53 +1,18 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
|
+
const LOG = cds.log()
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
processMessages,
|
|
5
|
-
registerMessageProcessor,
|
|
6
|
-
writeInOutbox,
|
|
7
|
-
hasPersistentOutbox,
|
|
8
|
-
isUnrecoverable
|
|
9
|
-
} = require('./outbox/utils')
|
|
4
|
+
let logged
|
|
10
5
|
|
|
11
|
-
class
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// REVISIT: Also allow to overwrite this.send
|
|
18
|
-
this._emitImmediate = this.emit
|
|
19
|
-
this.emit = async function (...args) {
|
|
20
|
-
const msg = typeof args[0] === 'object' ? args[0] : { event: args[0], data: args[1], headers: args[2] }
|
|
21
|
-
const context = this.context || cds.context
|
|
22
|
-
if (this.options.outbox && context) {
|
|
23
|
-
const outboxOpts = Object.assign(
|
|
24
|
-
{},
|
|
25
|
-
(typeof cds.requires.outbox === 'object' && cds.requires.outbox) || {},
|
|
26
|
-
(this.options && typeof this.options.outbox === 'object' && this.options.outbox) || {}
|
|
27
|
-
)
|
|
28
|
-
if (hasPersistentOutbox(this, context.tenant)) {
|
|
29
|
-
// returns true if not yet registered
|
|
30
|
-
if (registerMessageProcessor(this.name, context)) {
|
|
31
|
-
context.on('succeeded', () => processMessages(this, context.tenant, outboxOpts))
|
|
32
|
-
}
|
|
33
|
-
await writeInOutbox(this.name, msg, context)
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
// Revisit: Also allow maxAttempts?
|
|
37
|
-
context.on('succeeded', async () => {
|
|
38
|
-
try {
|
|
39
|
-
await this._emitImmediate(msg)
|
|
40
|
-
} catch (e) {
|
|
41
|
-
LOG.error('Emit failed', { event: msg.event, cause: e })
|
|
42
|
-
// opts.crashOnError is not official!!!
|
|
43
|
-
if (isUnrecoverable(this, e) && outboxOpts.crashOnError !== false) cds.exit(1)
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
|
-
return this._emitImmediate(msg)
|
|
6
|
+
module.exports = class Outbox extends cds.Service {
|
|
7
|
+
constructor(...args) {
|
|
8
|
+
if (!logged && LOG._warn) {
|
|
9
|
+
// prettier-ignore
|
|
10
|
+
LOG.warn('Internal class `OutboxService` is deprecated and will be removed. Services are outboxable via config or `cds.outboxed()`.')
|
|
11
|
+
logged = true
|
|
49
12
|
}
|
|
13
|
+
|
|
14
|
+
super(...args)
|
|
15
|
+
|
|
16
|
+
if (this.options.outbox) return cds.outboxed(this)
|
|
50
17
|
}
|
|
51
18
|
}
|
|
52
|
-
|
|
53
|
-
module.exports = OutboxService
|
|
@@ -2,7 +2,6 @@ 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 ClientAmqp = require('@sap/xb-msg-amqp-v100').Client
|
|
4
4
|
const { connect, disconnect } = require('./connections')
|
|
5
|
-
const { hasPersistentOutbox } = require('../outbox/utils')
|
|
6
5
|
|
|
7
6
|
const addDataListener = (client, queue, prefix, cb) =>
|
|
8
7
|
new Promise(resolve => {
|
|
@@ -79,8 +78,7 @@ class AMQPClient {
|
|
|
79
78
|
async emit(msg) {
|
|
80
79
|
if (!this.client) await this.connect()
|
|
81
80
|
// REVISIT: Is this a robust way to find out if the connection is working?
|
|
82
|
-
if (
|
|
83
|
-
throw new Error('AMQP: Sender is not open')
|
|
81
|
+
if (msg._fromOutbox && !this.sender.opened()) throw new Error('AMQP: Sender is not open')
|
|
84
82
|
await emit(msg, this.stream, this.prefix.topic, this.service.LOG)
|
|
85
83
|
if (!this.keepAlive) return this.disconnect()
|
|
86
84
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const https = require('https')
|
|
2
2
|
const requestToken = require('../http-utils/token')
|
|
3
|
+
const cds = require('../../cds')
|
|
4
|
+
const LOG = cds.log('http-messaging') // not public
|
|
3
5
|
|
|
4
6
|
const authorizedRequest = ({ method, uri, path, oa2, tenant, dataObj, headers, tokenStore }) => {
|
|
5
7
|
return new Promise((resolve, reject) => {
|
|
8
|
+
if (LOG._debug) LOG.debug({ method, uri, path, oa2, tenant, dataObj, headers, tokenStore })
|
|
6
9
|
;((tokenStore.token && Promise.resolve(tokenStore.token)) || requestToken(oa2, tenant, tokenStore))
|
|
7
10
|
.catch(err => reject(err))
|
|
8
11
|
.then(token => {
|
|
@@ -45,10 +45,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
45
45
|
const provisioningSrv = await cds.connect.to('cds.xt.SaasProvisioningService')
|
|
46
46
|
deploymentSrv.impl(() => {
|
|
47
47
|
deploymentSrv.after('subscribe', async (_res, req) => {
|
|
48
|
-
const {
|
|
49
|
-
let subdomain
|
|
50
|
-
const tenantInfo = await getTenantInfo(tenant)
|
|
51
|
-
subdomain = tenantInfo.subdomain
|
|
48
|
+
const { subscribedSubdomain: subdomain } = req.data.metadata
|
|
52
49
|
const management = await this.getManagement(subdomain).waitUntilReady()
|
|
53
50
|
await management.deploy()
|
|
54
51
|
})
|
|
@@ -158,35 +155,37 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
158
155
|
})
|
|
159
156
|
}
|
|
160
157
|
|
|
161
|
-
async
|
|
162
|
-
|
|
158
|
+
async handle(msg) {
|
|
159
|
+
if (msg.inbound) return super.handle(msg)
|
|
160
|
+
const _msg = this.message4(msg)
|
|
163
161
|
const _optionsMessagingREST = optionsMessagingREST(this.options)
|
|
164
162
|
const context = this.context || cds.context
|
|
165
|
-
const tenant = context && context.tenant
|
|
166
|
-
const topic =
|
|
167
|
-
const message = { ...(
|
|
163
|
+
const tenant = cds.requires.multitenancy && context && context.tenant
|
|
164
|
+
const topic = _msg.event
|
|
165
|
+
const message = { ...(_msg.headers || {}), data: _msg.data }
|
|
168
166
|
|
|
169
167
|
const contentType =
|
|
170
|
-
|
|
168
|
+
_msg.headers && ['id', 'source', 'specversion', 'type'].every(el => el in _msg.headers)
|
|
171
169
|
? 'application/cloudevents+json'
|
|
172
170
|
: 'application/json'
|
|
173
171
|
|
|
174
172
|
await this.queued(() => {})()
|
|
175
173
|
|
|
176
174
|
try {
|
|
177
|
-
|
|
175
|
+
const params = {
|
|
178
176
|
method: 'POST',
|
|
179
177
|
uri: _optionsMessagingREST.uri,
|
|
180
178
|
path: `/messagingrest/v1/topics/${encodeURIComponent(topic)}/messages`,
|
|
181
179
|
oa2: _optionsMessagingREST.oa2,
|
|
182
|
-
tenant,
|
|
183
180
|
dataObj: message,
|
|
184
181
|
headers: {
|
|
185
182
|
'Content-Type': contentType,
|
|
186
183
|
'x-qos': 1
|
|
187
184
|
},
|
|
188
185
|
tokenStore: {}
|
|
189
|
-
}
|
|
186
|
+
}
|
|
187
|
+
if (tenant) params.tenant = tenant
|
|
188
|
+
await authorizedRequest(params)
|
|
190
189
|
} catch (e) {
|
|
191
190
|
// Note: If the topic rules don't allow the topic, we get a 403 (which is a strange choice by Event Mesh)
|
|
192
191
|
if (e && (e.statusCode === 400 || e.statusCode === 403)) e.unrecoverable = true
|
|
@@ -19,7 +19,8 @@ class FileBasedMessaging extends MessagingService {
|
|
|
19
19
|
return super.init()
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
async
|
|
22
|
+
async handle(msg) {
|
|
23
|
+
if (msg.inbound) return super.handle(msg)
|
|
23
24
|
const _msg = this.message4(msg)
|
|
24
25
|
const e = _msg.event
|
|
25
26
|
delete _msg.event
|
|
@@ -53,10 +54,11 @@ class FileBasedMessaging extends MessagingService {
|
|
|
53
54
|
if (this.subscribedTopics.has(topic)) {
|
|
54
55
|
const event = this.subscribedTopics.get(topic)
|
|
55
56
|
if (!event) return
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
this.tx(tx =>
|
|
58
|
+
tx
|
|
59
|
+
.emit({ event, ...json, inbound: true })
|
|
60
|
+
.catch(e => this.LOG.error('ERROR occured in asynchronous event processing:', e))
|
|
61
|
+
)
|
|
60
62
|
} else other.push(each + '\n')
|
|
61
63
|
}
|
|
62
64
|
} catch (e) {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
2
2
|
const redis = require('redis')
|
|
3
3
|
const cds = require('../../../lib')
|
|
4
|
-
const waitingTime = require('
|
|
4
|
+
const waitingTime = require('../common/utils/waitingTime')
|
|
5
5
|
const normalizeIncomingMessage = require('./common-utils/normalizeIncomingMessage')
|
|
6
|
-
const { hasPersistentOutbox } = require('./outbox/utils')
|
|
7
6
|
|
|
8
7
|
const _handleReconnects = (client, LOG) => {
|
|
9
8
|
client.on('reconnecting', () => {
|
|
@@ -57,6 +56,14 @@ class RedisMessaging extends cds.MessagingService {
|
|
|
57
56
|
})
|
|
58
57
|
}
|
|
59
58
|
|
|
59
|
+
async handle(msg) {
|
|
60
|
+
if (msg.inbound) return super.handle(msg)
|
|
61
|
+
const _msg = this.message4(msg)
|
|
62
|
+
this.LOG._info && this.LOG.info('Emit', { topic: _msg.event })
|
|
63
|
+
if (!this._ready && msg._fromOutbox) throw new Error('Redis connection not ready')
|
|
64
|
+
await this.client.publish(_msg.event, JSON.stringify({ data: _msg.data, ...(_msg.headers || {}) }))
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
async startListening() {
|
|
61
68
|
let subscriber
|
|
62
69
|
for (const topic of [...this.subscribedTopics].map(kv => kv[0])) {
|
|
@@ -71,21 +78,13 @@ class RedisMessaging extends cds.MessagingService {
|
|
|
71
78
|
const msg = normalizeIncomingMessage(message)
|
|
72
79
|
msg.event = topic
|
|
73
80
|
try {
|
|
74
|
-
await
|
|
81
|
+
await this.tx({ user: cds.User.privileged }, tx => tx.emit(msg))
|
|
75
82
|
} catch (e) {
|
|
76
83
|
this.LOG.error('ERROR occured in asynchronous event processing:', e)
|
|
77
84
|
}
|
|
78
85
|
})
|
|
79
86
|
}
|
|
80
87
|
}
|
|
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
88
|
}
|
|
90
89
|
|
|
91
90
|
module.exports = RedisMessaging
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
2
|
const queued = require('./common-utils/queued')
|
|
3
|
-
const OutboxService = require('./Outbox')
|
|
4
3
|
const ExtendedModels = require('../../../lib/srv/srv-models')
|
|
5
4
|
|
|
6
5
|
const appId = require('./common-utils/appId')
|
|
@@ -21,7 +20,7 @@ const _warnAndStripTopicPrefix = (event, LOG) => {
|
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
// There's currently no mechanism to detect mocked services, this is the best we can do.
|
|
24
|
-
class MessagingService extends
|
|
23
|
+
class MessagingService extends cds.Service {
|
|
25
24
|
init() {
|
|
26
25
|
// enables queued async operations (without awaiting)
|
|
27
26
|
this.queued = queued()
|
|
@@ -58,9 +57,10 @@ class MessagingService extends OutboxService {
|
|
|
58
57
|
// calls to srv.emit are forwarded to this.emit, which is expected to
|
|
59
58
|
// be overwritten by subclasses to write events to message channel
|
|
60
59
|
const topic = _topic(declared)
|
|
61
|
-
srv.on(event, msg => {
|
|
60
|
+
srv.on(event, async msg => {
|
|
62
61
|
const { data, headers } = msg
|
|
63
|
-
|
|
62
|
+
const messaging = await cds.connect.to('messaging') // needed for potential outbox
|
|
63
|
+
return messaging.tx(msg).emit({ event: topic, data, headers })
|
|
64
64
|
})
|
|
65
65
|
}
|
|
66
66
|
})
|
|
@@ -77,29 +77,15 @@ class MessagingService extends OutboxService {
|
|
|
77
77
|
return super.init()
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
async
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
user: cds.User.privileged,
|
|
88
|
-
...(_msg._?.req && { req: _msg._.req }),
|
|
89
|
-
...(_msg._?.res && { res: _msg._.res })
|
|
90
|
-
},
|
|
91
|
-
async () => {
|
|
92
|
-
if (cds.model) {
|
|
93
|
-
const ctx = cds.context
|
|
94
|
-
ctx.model = await ExtendedModels.model4(ctx.tenant, ctx.features)
|
|
95
|
-
}
|
|
96
|
-
const msg = new cds.Event(this.message4(_msg))
|
|
97
|
-
return super.emit(msg)
|
|
98
|
-
}
|
|
99
|
-
)
|
|
80
|
+
async handle(msg) {
|
|
81
|
+
if (msg.inbound) {
|
|
82
|
+
if (cds.model) {
|
|
83
|
+
const ctx = cds.context
|
|
84
|
+
ctx.model = await ExtendedModels.model4(ctx.tenant, ctx.features)
|
|
85
|
+
}
|
|
86
|
+
return super.handle(this.message4(msg))
|
|
100
87
|
}
|
|
101
|
-
|
|
102
|
-
return super.emit(msg)
|
|
88
|
+
return super.handle(msg)
|
|
103
89
|
}
|
|
104
90
|
|
|
105
91
|
on(event, cb) {
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
2
|
const LOG = cds.log('remote')
|
|
3
3
|
|
|
4
|
+
const { run, getReqOptions } = require('./utils/client')
|
|
5
|
+
const { hasAliasedColumns } = require('./utils/data')
|
|
6
|
+
|
|
4
7
|
const { resolveView, getTransition, restoreLink, findQueryTarget } = require('../common/utils/resolveView')
|
|
5
8
|
const { postProcess } = require('../common/utils/postProcessing')
|
|
6
|
-
|
|
9
|
+
|
|
7
10
|
const { formatVal } = require('../../odata/utils')
|
|
8
|
-
const { hasAliasedColumns } = require('./utils/data')
|
|
9
11
|
|
|
10
|
-
let _cloudSdkConnectivity
|
|
11
|
-
const
|
|
12
|
+
let _cloudSdkConnectivity
|
|
13
|
+
const _getCloudSdkConnectivity = () => {
|
|
12
14
|
if (_cloudSdkConnectivity) return _cloudSdkConnectivity
|
|
13
15
|
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
14
16
|
_cloudSdkConnectivity = require('@sap-cloud-sdk/connectivity')
|
|
15
17
|
return _cloudSdkConnectivity
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
|
|
20
|
+
let _cloudSdkResilience
|
|
21
|
+
const _getCloudSdkResilience = () => {
|
|
22
|
+
if (_cloudSdkResilience) return _cloudSdkResilience
|
|
19
23
|
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
20
24
|
_cloudSdkResilience = require('@sap-cloud-sdk/resilience')
|
|
21
25
|
return _cloudSdkResilience
|
|
@@ -96,7 +100,9 @@ const _handleBoundActionFunction = (srv, def, req, url) => {
|
|
|
96
100
|
if (def.params) {
|
|
97
101
|
const data = _extractParamsFromData(req.data, def.params)
|
|
98
102
|
url = _buildPartialUrlFunctions(url, data, def.params)
|
|
99
|
-
} else
|
|
103
|
+
} else {
|
|
104
|
+
url = `${url}()`
|
|
105
|
+
}
|
|
100
106
|
|
|
101
107
|
return srv.get(url)
|
|
102
108
|
}
|
|
@@ -105,11 +111,7 @@ const _handleUnboundActionFunction = (srv, def, req, event) => {
|
|
|
105
111
|
if (def.kind === 'action') {
|
|
106
112
|
// REVISIT: only for "rest" unbound actions/functions, we enforce axios to return a buffer
|
|
107
113
|
// required by cds-mt
|
|
108
|
-
const isBinary =
|
|
109
|
-
srv.kind === 'rest' &&
|
|
110
|
-
def &&
|
|
111
|
-
def.returns &&
|
|
112
|
-
(def.returns.type === 'cds.LargeBinary' || def.returns.type === 'cds.Binary')
|
|
114
|
+
const isBinary = srv.kind === 'rest' && def?.returns?.type.match(/binary/i)
|
|
113
115
|
const { headers, data } = req
|
|
114
116
|
|
|
115
117
|
return srv.send({ method: 'POST', path: `/${event}`, headers, data, _binary: isBinary })
|
|
@@ -163,14 +165,12 @@ const _addHandlerActionFunction = (srv, def, target) => {
|
|
|
163
165
|
const url = `/${shortEntityName}(${_buildKeys(req, this.kind).join(',')})/${this.namespace}.${event}`
|
|
164
166
|
return _handleBoundActionFunction(srv, def, req, url)
|
|
165
167
|
})
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
} else {
|
|
169
|
+
srv.on(event, async function (req) {
|
|
170
|
+
if (this.kind === 'odata-v2') return _handleV2ActionFunction(srv, def, req, event, this.kind)
|
|
171
|
+
return _handleUnboundActionFunction(srv, def, req, event)
|
|
172
|
+
})
|
|
168
173
|
}
|
|
169
|
-
|
|
170
|
-
srv.on(event, async function (req) {
|
|
171
|
-
if (this.kind === 'odata-v2') return _handleV2ActionFunction(srv, def, req, event, this.kind)
|
|
172
|
-
return _handleUnboundActionFunction(srv, def, req, event)
|
|
173
|
-
})
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
const _selectOnlyWithAlias = q => q?.SELECT && !q.SELECT._transitions && q.SELECT?.columns?.some(hasAliasedColumns)
|
|
@@ -180,20 +180,38 @@ const resolvedTargetOfQuery = q => {
|
|
|
180
180
|
return transitions.length && [transitions.length - 1].target
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
let logged
|
|
184
|
-
|
|
185
183
|
const _resolveSelectionStrategy = options => {
|
|
186
184
|
if (typeof options?.selectionStrategy !== 'string') return
|
|
187
|
-
options.selectionStrategy = cloudSdkConnectivity().DestinationSelectionStrategies[options.selectionStrategy]
|
|
188
185
|
|
|
186
|
+
options.selectionStrategy = _getCloudSdkConnectivity().DestinationSelectionStrategies[options.selectionStrategy]
|
|
189
187
|
if (typeof options?.selectionStrategy !== 'function') {
|
|
190
188
|
throw new Error(`Unsupported destination selection strategy "${options.selectionStrategy}".`)
|
|
191
189
|
}
|
|
192
190
|
}
|
|
193
191
|
|
|
192
|
+
const _getKind = options => {
|
|
193
|
+
const kind = (options.credentials && options.credentials.kind) || options.kind
|
|
194
|
+
if (typeof kind === 'object') {
|
|
195
|
+
const k = Object.keys(kind).find(
|
|
196
|
+
key => key === 'odata' || key === 'odata-v4' || key === 'odata-v2' || key === 'rest'
|
|
197
|
+
)
|
|
198
|
+
// odata-v4 is equivalent of odata
|
|
199
|
+
return k === 'odata-v4' ? 'odata' : k
|
|
200
|
+
}
|
|
201
|
+
return kind
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const _getDestination = (name, credentials) => {
|
|
205
|
+
// Cloud SDK wants property "queryParameters" but we have documented "queries"
|
|
206
|
+
if (credentials.queries && !credentials.queryParameters) credentials.queryParameters = credentials.queries
|
|
207
|
+
return { name, ...credentials }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let logged
|
|
211
|
+
|
|
194
212
|
class RemoteService extends cds.Service {
|
|
195
213
|
init() {
|
|
196
|
-
this.kind =
|
|
214
|
+
this.kind = _getKind(this.options) // TODO: Simplify
|
|
197
215
|
|
|
198
216
|
/*
|
|
199
217
|
* set up connectivity stuff if credentials are provided
|
|
@@ -205,7 +223,7 @@ class RemoteService extends cds.Service {
|
|
|
205
223
|
_resolveSelectionStrategy(this.destinationOptions)
|
|
206
224
|
this.destination =
|
|
207
225
|
this.options.credentials.destination ??
|
|
208
|
-
|
|
226
|
+
_getDestination(this.definition?.name ?? this.datasource, this.options.credentials)
|
|
209
227
|
this.path = this.options.credentials.path
|
|
210
228
|
|
|
211
229
|
// `requestTimeout` API is kept as it was public
|
|
@@ -250,27 +268,24 @@ class RemoteService extends cds.Service {
|
|
|
250
268
|
this.on('*', async (req, next) => {
|
|
251
269
|
const { query } = req
|
|
252
270
|
if (!query && !(typeof req.path === 'string')) return next()
|
|
271
|
+
|
|
253
272
|
// early validation on first request for use case without remote API
|
|
254
273
|
// ideally, that's done on bootstrap of the remote service
|
|
255
274
|
if (typeof this.destination === 'object' && !this.destination.url)
|
|
256
275
|
throw new Error(`"url" or "destination" property must be configured in "credentials" of "${this.name}".`)
|
|
257
276
|
if (this._resilienceMiddlewares && !this._resilienceMiddlewares.timeout)
|
|
258
|
-
this._resilienceMiddlewares.timeout =
|
|
277
|
+
this._resilienceMiddlewares.timeout = _getCloudSdkResilience().timeout(this.requestTimeout)
|
|
259
278
|
|
|
260
|
-
const resolvedTarget = resolvedTargetOfQuery(query) || getTransition(req.target, this).target
|
|
261
279
|
const reqOptions = getReqOptions(req, query, this)
|
|
262
280
|
reqOptions.headers = _setHeaders(reqOptions.headers, req)
|
|
281
|
+
|
|
282
|
+
const { kind, destination, destinationOptions, csrf, csrfInBatch } = this
|
|
283
|
+
const resolvedTarget = resolvedTargetOfQuery(query) || getTransition(req.target, this).target
|
|
263
284
|
const returnType = req._returnType
|
|
264
|
-
const additionalOptions =
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
resolvedTarget,
|
|
269
|
-
returnType,
|
|
270
|
-
this.destinationOptions,
|
|
271
|
-
this.csrf,
|
|
272
|
-
this.csrfInBatch
|
|
273
|
-
)
|
|
285
|
+
const additionalOptions = { destination, kind, resolvedTarget, returnType, destinationOptions, csrf, csrfInBatch }
|
|
286
|
+
|
|
287
|
+
const jwt = req?.context?.headers?.authorization?.split(/^bearer /i)[1]
|
|
288
|
+
if (jwt) additionalOptions.jwt = jwt
|
|
274
289
|
|
|
275
290
|
// hidden compat flag in order to suppress logging response body of failed request
|
|
276
291
|
if (req._suppressRemoteResponseBody) {
|
|
@@ -334,4 +349,5 @@ class RemoteService extends cds.Service {
|
|
|
334
349
|
}
|
|
335
350
|
|
|
336
351
|
RemoteService.prototype.isExternal = true
|
|
352
|
+
|
|
337
353
|
module.exports = RemoteService
|