@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +69 -3
  2. package/_i18n/i18n_es_MX.properties +110 -0
  3. package/apis/cds.d.ts +13 -12
  4. package/apis/core.d.ts +27 -108
  5. package/apis/cqn.d.ts +15 -18
  6. package/apis/csn.d.ts +95 -60
  7. package/apis/env.d.ts +25 -0
  8. package/apis/events.d.ts +125 -0
  9. package/apis/{reflect.d.ts → linked.d.ts} +29 -38
  10. package/apis/models.d.ts +60 -45
  11. package/apis/ql.d.ts +19 -5
  12. package/apis/{serve.d.ts → server.d.ts} +59 -33
  13. package/apis/services.d.ts +76 -147
  14. package/apis/test.d.ts +1 -1
  15. package/bin/serve.js +3 -0
  16. package/lib/compile/cds-compile.js +2 -2
  17. package/lib/compile/etc/csv.js +2 -1
  18. package/lib/compile/to/edm.js +8 -3
  19. package/lib/compile/to/gql.js +4 -0
  20. package/lib/dbs/cds-deploy.js +52 -4
  21. package/lib/env/cds-requires.js +27 -15
  22. package/lib/env/defaults.js +1 -0
  23. package/lib/env/schemas/index.js +10 -0
  24. package/lib/index.js +7 -4
  25. package/lib/linked/models.js +8 -5
  26. package/lib/ql/CREATE.js +2 -0
  27. package/lib/ql/DELETE.js +1 -0
  28. package/lib/ql/DROP.js +2 -0
  29. package/lib/ql/INSERT.js +2 -22
  30. package/lib/ql/Query.js +59 -22
  31. package/lib/ql/SELECT.js +5 -0
  32. package/lib/ql/STREAM.js +2 -0
  33. package/lib/ql/UPDATE.js +2 -0
  34. package/lib/ql/UPSERT.js +3 -1
  35. package/lib/ql/cds-ql.js +21 -5
  36. package/lib/ql/infer.js +129 -0
  37. package/lib/req/cds-context.js +8 -5
  38. package/lib/srv/cds-connect.js +3 -1
  39. package/lib/utils/axios.js +4 -2
  40. package/lib/utils/data.js +3 -0
  41. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +12 -0
  42. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +27 -9
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +1 -1
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +8 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +11 -8
  46. package/libx/_runtime/common/code-ext/worker.js +5 -16
  47. package/libx/_runtime/common/generic/auth/capabilities.js +11 -2
  48. package/libx/_runtime/common/i18n/messages.properties +1 -0
  49. package/libx/_runtime/common/utils/postProcessing.js +1 -1
  50. package/libx/_runtime/common/utils/resolveView.js +28 -9
  51. package/libx/{common → _runtime/common}/utils/ucsn.js +19 -11
  52. package/libx/_runtime/db/expand/expandCQNToJoin.js +6 -6
  53. package/libx/_runtime/db/expand/rawToExpanded.js +4 -4
  54. package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -1
  55. package/libx/_runtime/db/sql-builder/UpdateBuilder.js +6 -1
  56. package/libx/_runtime/db/sql-builder/dollar.js +7 -7
  57. package/libx/_runtime/fiori/generic/activate.js +2 -2
  58. package/libx/_runtime/fiori/generic/edit.js +25 -45
  59. package/libx/_runtime/fiori/generic/read.js +3 -5
  60. package/libx/_runtime/fiori/lean-draft.js +171 -84
  61. package/libx/_runtime/fiori/utils/delete.js +7 -1
  62. package/libx/_runtime/fiori/utils/handler.js +4 -6
  63. package/libx/_runtime/fiori/utils/lockInfo.js +27 -0
  64. package/libx/_runtime/fiori/utils/where.js +20 -1
  65. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -2
  66. package/libx/_runtime/messaging/Outbox.js +12 -47
  67. package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -3
  68. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +3 -0
  69. package/libx/_runtime/messaging/common-utils/connections.js +1 -1
  70. package/libx/_runtime/messaging/enterprise-messaging.js +12 -13
  71. package/libx/_runtime/messaging/file-based.js +7 -5
  72. package/libx/_runtime/messaging/redis-messaging.js +10 -11
  73. package/libx/_runtime/messaging/service.js +12 -26
  74. package/libx/_runtime/remote/Service.js +52 -36
  75. package/libx/_runtime/remote/utils/client.js +24 -125
  76. package/libx/odata/afterburner.js +16 -6
  77. package/libx/odata/grammar.peggy +26 -7
  78. package/libx/odata/metadata.js +18 -1
  79. package/libx/odata/parser.js +1 -1
  80. package/libx/odata/service-document.js +0 -1
  81. package/libx/odata/utils.js +19 -3
  82. package/libx/{_runtime/messaging/outbox/utils.js → outbox/index.js} +94 -24
  83. package/libx/rest/middleware/parse.js +1 -1
  84. package/package.json +2 -2
  85. package/apis/connect.d.ts +0 -39
  86. package/bin/utils/modules.js +0 -7
  87. package/bin/utils/term.js +0 -56
  88. package/lib/env/schema.js +0 -9
  89. package/lib/linked/queries.js +0 -41
  90. package/lib/srv/protocols/odata-v2-proxy.js +0 -3699
  91. package/libx/common/asserts.js +0 -0
  92. package/libx/common/crud.js +0 -0
  93. package/libx/common/etag.js +0 -0
  94. package/libx/common/localized.js +0 -0
  95. package/libx/common/managed.js +0 -0
  96. package/libx/common/paging.js +0 -0
  97. package/libx/common/readme.md +0 -4
  98. package/libx/common/sorting.js +0 -0
  99. package/libx/common/temporal.js +0 -0
  100. package/libx/connect/auth.js +0 -0
  101. package/libx/connect/perf.js +0 -0
  102. package/libx/connect/readme.md +0 -3
  103. package/libx/fiori/draft/readme.md +0 -1
  104. package/libx/fiori/readme.md +0 -1
  105. package/libx/hana/readme.md +0 -1
  106. package/libx/msg/readme.md +0 -3
  107. package/libx/readme.md +0 -1
  108. package/libx/sqlite/readme.md +0 -1
  109. /package/libx/_runtime/{messaging/common-utils → common/utils}/waitingTime.js +0 -0
  110. /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
- const {
4
- processMessages,
5
- registerMessageProcessor,
6
- writeInOutbox,
7
- hasPersistentOutbox,
8
- isUnrecoverable
9
- } = require('./outbox/utils')
4
+ let logged
10
5
 
11
- class OutboxService extends cds.Service {
12
- // eslint-disable-next-line require-await
13
- async init() {
14
- // REVISIT: add 'outbox' to list of module names?
15
- const LOG = cds.log(this.name)
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 (hasPersistentOutbox(this.service, cds.context && cds.context.tenant) && !this.sender.opened())
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 => {
@@ -1,4 +1,4 @@
1
- const waitingTime = require('./waitingTime')
1
+ const waitingTime = require('../../common/utils/waitingTime')
2
2
 
3
3
  const _connectUntilConnected = (client, LOG, x) => {
4
4
  const _waitingTime = waitingTime(x)
@@ -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 { tenant } = req.data
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 emit(event, ...etc) {
162
- const msg = this.message4(event, ...etc)
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 = msg.event
167
- const message = { ...(msg.headers || {}), data: msg.data }
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
- msg.headers && ['id', 'source', 'specversion', 'type'].every(el => el in msg.headers)
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
- await authorizedRequest({
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 emit(msg) {
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
- // REVISIT: should we use this.dispatch instead of super.emit for inbound messages?
57
- super
58
- .emit({ event, ...json, inbound: true })
59
- .catch(e => this.LOG.error('ERROR occured in asynchronous event processing:', e))
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('./common-utils/waitingTime')
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 super.emit(msg)
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 OutboxService {
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
- return this.tx(msg).emit({ event: topic, data, headers })
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 emit(event, data, headers) {
81
- const _msg = typeof event === 'object' ? event : { event, data, headers }
82
- if (_msg instanceof cds.Event) return super.emit(_msg)
83
- if (_msg.inbound && !cds.context) {
84
- return cds._context.run(
85
- {
86
- tenant: _msg.tenant,
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
- const msg = new cds.Event(this.message4(_msg))
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
- const { getKind, run, getDestination, getAdditionalOptions, getReqOptions } = require('./utils/client')
9
+
7
10
  const { formatVal } = require('../../odata/utils')
8
- const { hasAliasedColumns } = require('./utils/data')
9
11
 
10
- let _cloudSdkConnectivity, _cloudSdkResilience
11
- const cloudSdkConnectivity = () => {
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
- const cloudSdkResilience = () => {
18
- if (_cloudSdkResilience !== undefined) return _cloudSdkResilience
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 url = `${url}()`
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
- return
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 = getKind(this.options) // TODO: Simplify
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
- getDestination(this.definition?.name ?? this.datasource, this.options.credentials)
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 = cloudSdkResilience().timeout(this.requestTimeout)
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 = getAdditionalOptions(
265
- req,
266
- this.destination,
267
- this.kind,
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