@sap/cds 9.6.4 → 9.7.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 +57 -0
- package/bin/serve.js +38 -26
- package/lib/compile/for/flows.js +100 -20
- package/lib/compile/for/lean_drafts.js +0 -47
- package/lib/compile/for/nodejs.js +47 -14
- package/lib/compile/for/odata.js +20 -0
- package/lib/compile/load.js +22 -25
- package/lib/compile/minify.js +29 -11
- package/lib/compile/parse.js +1 -1
- package/lib/compile/resolve.js +133 -76
- package/lib/compile/to/csn.js +2 -2
- package/lib/dbs/cds-deploy.js +48 -43
- package/lib/env/cds-env.js +6 -0
- package/lib/env/cds-requires.js +9 -3
- package/lib/index.js +3 -1
- package/lib/plugins.js +1 -1
- package/lib/req/request.js +2 -2
- package/lib/srv/bindings.js +10 -5
- package/lib/srv/middlewares/auth/index.js +7 -5
- package/lib/srv/protocols/hcql.js +8 -3
- package/lib/srv/protocols/http.js +1 -1
- package/lib/srv/protocols/index.js +1 -0
- package/lib/utils/cds-utils.js +28 -1
- package/lib/utils/colors.js +1 -1
- package/libx/_runtime/common/generic/assert.js +1 -7
- package/libx/_runtime/common/generic/flows.js +14 -4
- package/libx/_runtime/common/utils/resolveView.js +4 -0
- package/libx/_runtime/fiori/lean-draft.js +8 -3
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +4 -0
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +12 -12
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
- package/libx/_runtime/messaging/http-utils/token.js +18 -3
- package/libx/_runtime/messaging/message-queuing.js +7 -7
- package/libx/_runtime/remote/Service.js +14 -3
- package/libx/_runtime/remote/utils/client.js +1 -0
- package/libx/_runtime/remote/utils/query.js +0 -1
- package/libx/odata/middleware/batch.js +128 -112
- package/libx/odata/middleware/delete.js +2 -1
- package/libx/odata/middleware/error.js +7 -3
- package/libx/odata/parse/afterburner.js +10 -11
- package/libx/odata/parse/grammar.peggy +4 -2
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/odataBind.js +8 -2
- package/libx/queue/index.js +1 -0
- package/package.json +4 -7
- package/srv/outbox.cds +1 -1
- package/srv/ucl-service.cds +3 -5
- package/bin/colors.js +0 -2
- package/libx/_runtime/.eslintrc +0 -14
|
@@ -51,7 +51,7 @@ class HttpAdapter {
|
|
|
51
51
|
const user = cds.context.user
|
|
52
52
|
if (required.some(role => user.has(role))) return next()
|
|
53
53
|
else if (user._is_anonymous) return next(401) // request login
|
|
54
|
-
else throw Object.assign(new Error, { code: 403, reason: `User '${user.id}' is lacking required roles: [${required}]
|
|
54
|
+
else throw Object.assign(new Error, { code: 403, reason: `User '${user.id}' is lacking required roles: [${required}]` })
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -98,6 +98,7 @@ class Protocols {
|
|
|
98
98
|
*/
|
|
99
99
|
endpoints4 (srv, o = srv.options) {
|
|
100
100
|
const def = srv.definition || {}
|
|
101
|
+
if (def.kind !== 'service') return [] //> no endpoints, e.g. for cds.db
|
|
101
102
|
|
|
102
103
|
// get @protocol annotations from service definition
|
|
103
104
|
let annos = o?.to || def['@protocol']
|
package/lib/utils/cds-utils.js
CHANGED
|
@@ -332,13 +332,40 @@ exports.redacted = function _redacted(cred) {
|
|
|
332
332
|
if (Array.isArray(cred)) return cred.map(c => typeof c === 'string' ? '...' : _redacted(c))
|
|
333
333
|
if (typeof cred === 'object') {
|
|
334
334
|
const newCred = Object.assign({}, cred)
|
|
335
|
-
Object.keys(newCred).forEach(k =>
|
|
335
|
+
Object.keys(newCred).forEach(k => {
|
|
336
|
+
if (typeof newCred[k] === 'string' && SECRETS.test(k)) {
|
|
337
|
+
newCred[k] = '...'
|
|
338
|
+
} else if (k === 'uri' && typeof newCred[k] === 'string') {
|
|
339
|
+
// redact secrets in URIs, e.g., s3://access_key_id:secret_access_key@...
|
|
340
|
+
newCred[k] = newCred[k].replace(/\/\/(.*?):(.*?)@/g, '//...:...@')
|
|
341
|
+
} else {
|
|
342
|
+
newCred[k] = _redacted(newCred[k])
|
|
343
|
+
}
|
|
344
|
+
})
|
|
336
345
|
return newCred
|
|
337
346
|
}
|
|
338
347
|
return cred
|
|
339
348
|
}
|
|
340
349
|
|
|
341
350
|
|
|
351
|
+
/**
|
|
352
|
+
* A variant of child_process.exec that returns a promise,
|
|
353
|
+
* which resolves with the command's stdout split into lines.
|
|
354
|
+
* @example
|
|
355
|
+
* await cds.utils.sh `npm ls -lp --depth=0`
|
|
356
|
+
* @param {string|TemplateStringsArray} cmd - the command to execute, may be a tagged template
|
|
357
|
+
* @returns {string[]} array of stdout lines
|
|
358
|
+
*/
|
|
359
|
+
exports.sh = function shell (cmd,..._) {
|
|
360
|
+
if (cmd.raw) cmd = String.raw (cmd,..._) // the cmd may be a tagged template
|
|
361
|
+
let debug = shell.debug ??= cds.debug('shell'); debug?.(cmd)
|
|
362
|
+
let exec = shell.exec ??= require('node:child_process').exec
|
|
363
|
+
return new Promise ((resolve, reject) => exec (cmd, (e, stdout, stderr) => {
|
|
364
|
+
if (e) reject (Object.assign (e, { stdout, stderr }))
|
|
365
|
+
else resolve (stdout?.trim().split('\n'))
|
|
366
|
+
}))
|
|
367
|
+
}
|
|
368
|
+
|
|
342
369
|
/**
|
|
343
370
|
* Converts a time span with a unit into milliseconds. @example
|
|
344
371
|
* ms4(5,'s') //> 5000
|
package/lib/utils/colors.js
CHANGED
|
@@ -3,7 +3,7 @@ module.exports = Object.assign (_colors4 (process.stdout), {
|
|
|
3
3
|
})
|
|
4
4
|
|
|
5
5
|
function _colors4 (stdout_or_stderr = process.stdout) {
|
|
6
|
-
const enabled = stdout_or_stderr.isTTY && !process.env.NO_COLOR || process.env.FORCE_COLOR
|
|
6
|
+
const enabled = (process.env.GITHUB_ACTIONS || stdout_or_stderr.isTTY) && !process.env.NO_COLOR || process.env.FORCE_COLOR
|
|
7
7
|
const color = enabled ? ttl => ttl[0] : ()=>''
|
|
8
8
|
return {
|
|
9
9
|
enabled,
|
|
@@ -127,13 +127,7 @@ module.exports = cds.service.impl(async function () {
|
|
|
127
127
|
if (failedColumns.length === 0) continue
|
|
128
128
|
|
|
129
129
|
const failedAsserts = failedColumns.map(([element, message]) => {
|
|
130
|
-
const error = {
|
|
131
|
-
status: 400,
|
|
132
|
-
code: 'ASSERT',
|
|
133
|
-
target: element,
|
|
134
|
-
numericSeverity: 4,
|
|
135
|
-
'@Common.numericSeverity': 4
|
|
136
|
-
}
|
|
130
|
+
const error = { status: 400, code: 'ASSERT', target: element }
|
|
137
131
|
|
|
138
132
|
// if error function was used in @assert expression -> use its output
|
|
139
133
|
try {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
|
|
3
|
-
const { getFrom } = require('../../../../lib/compile/for/flows')
|
|
4
|
-
|
|
5
3
|
const FLOW_STATUS = '@flow.status'
|
|
6
4
|
const FROM = '@from'
|
|
7
5
|
const TO = '@to'
|
|
@@ -9,6 +7,11 @@ const FLOW_PREVIOUS = '$flow.previous'
|
|
|
9
7
|
|
|
10
8
|
const $transitions_ = Symbol.for('transitions_')
|
|
11
9
|
|
|
10
|
+
const getFrom = action => {
|
|
11
|
+
let from = action[FROM]
|
|
12
|
+
return Array.isArray(from) ? from : [from]
|
|
13
|
+
}
|
|
14
|
+
|
|
12
15
|
function isCurrentStatusInFrom(result, action, statusElementName, statusEnum) {
|
|
13
16
|
const fromList = getFrom(action)
|
|
14
17
|
const allowed = fromList.filter(from => {
|
|
@@ -162,10 +165,17 @@ module.exports = cds.service.impl(function () {
|
|
|
162
165
|
entity[$transitions_] = base.compositions.transitions_
|
|
163
166
|
// track changes on db level
|
|
164
167
|
cds.connect.to('db').then(db => {
|
|
168
|
+
db.before('UPSERT', entity, async req => {
|
|
169
|
+
const status = req.data[statusInfo.statusElementName]
|
|
170
|
+
if (status === undefined) req._upsert_exists = await SELECT.one.from(req.subject).columns([1])
|
|
171
|
+
})
|
|
165
172
|
db.after(['CREATE', 'UPDATE', 'UPSERT'], entity, async (res, req) => {
|
|
166
173
|
if ((res.affectedRows ?? res) !== 1) return
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
let status = req.data[statusInfo.statusElementName]
|
|
175
|
+
if (status === undefined && (req.event === 'CREATE' || (req.event === 'UPSERT' && !req._upsert_exists))) {
|
|
176
|
+
status = entity.elements[statusInfo.statusElementName]?.default?.val
|
|
177
|
+
}
|
|
178
|
+
if (!status) return
|
|
169
179
|
const upKeys = await buildUpKeys(entity, req.data, req.subject)
|
|
170
180
|
const last = await SELECT.one.from(entity[$transitions_].target).orderBy('timestamp desc').where(upKeys)
|
|
171
181
|
if (last?.status !== status) await UPSERT.into(entity[$transitions_].target).entries({ ...upKeys, status })
|
|
@@ -639,6 +639,10 @@ const _entityTransitionsForTarget = (from, model, service, options) => {
|
|
|
639
639
|
)
|
|
640
640
|
}
|
|
641
641
|
|
|
642
|
+
if (typeof from === 'object' && from.SELECT) {
|
|
643
|
+
return _entityTransitionsForTarget(from.SELECT.from, model, service, options)
|
|
644
|
+
}
|
|
645
|
+
|
|
642
646
|
return from.ref.map((f, i) => {
|
|
643
647
|
const element = f.id || f
|
|
644
648
|
if (element === options.alias) return
|
|
@@ -463,12 +463,13 @@ const compileUpdatedDraftMessages = (newMessages, persistedMessages, requestData
|
|
|
463
463
|
if (!message.target) return acc //> silently ignore messages without target
|
|
464
464
|
|
|
465
465
|
message.numericSeverity ??= message['@Common.numericSeverity'] ?? 4
|
|
466
|
+
delete message['@Common.numericSeverity']
|
|
466
467
|
if (message['@Common.additionalTargets']) message.additionalTargets ??= message['@Common.additionalTargets']
|
|
467
|
-
message.additionalTargets = message.additionalTargets?.map(t =>
|
|
468
|
+
message.additionalTargets = message.additionalTargets?.map(t => t.replace(/^in\//, ''))
|
|
468
469
|
delete message['@Common.additionalTargets']
|
|
469
470
|
|
|
470
471
|
// Remove prefix 'in/' in favor of fully qualified path to the target
|
|
471
|
-
const messageTarget = message.target.
|
|
472
|
+
const messageTarget = message.target.replace(/^in\//, '')
|
|
472
473
|
if (message.code) delete message.code // > Expect _only_ message to be set and contain the code
|
|
473
474
|
|
|
474
475
|
// Process the message target produced by validation
|
|
@@ -966,8 +967,12 @@ const draftHandle = async function (req) {
|
|
|
966
967
|
|
|
967
968
|
if (IS_PERSISTED_DRAFT_MESSAGES_ENABLED) {
|
|
968
969
|
_req.on('failed', async error => {
|
|
970
|
+
if (!error && !_req.errors?.length) {
|
|
971
|
+
LOG.debug('Draft activation failed without error information: Skip update of DraftMessages.')
|
|
972
|
+
return
|
|
973
|
+
}
|
|
969
974
|
const errors = []
|
|
970
|
-
if (_req.errors) {
|
|
975
|
+
if (_req.errors?.length) {
|
|
971
976
|
// REVISIT: e._message hack for draft validation messages
|
|
972
977
|
// Errors procesed during 'failed' will have undergone error._normalize at this point
|
|
973
978
|
// > We need to revert the code - message swap _normalize includes
|
|
@@ -4,6 +4,10 @@ const cds = require('../../cds')
|
|
|
4
4
|
const LOG = cds.log('http-messaging') // not public
|
|
5
5
|
|
|
6
6
|
const authorizedRequest = ({ method, uri, path, oa2, tenant, dataObj, headers, tokenStore }) => {
|
|
7
|
+
if (tenant) {
|
|
8
|
+
if (!tokenStore[tenant]) tokenStore[tenant] = {}
|
|
9
|
+
tokenStore = tokenStore[tenant]
|
|
10
|
+
}
|
|
7
11
|
return new Promise((resolve, reject) => {
|
|
8
12
|
if (LOG._debug) LOG.debug({ method, uri, path, oa2, tenant, dataObj, headers, tokenStore })
|
|
9
13
|
;((tokenStore.token && Promise.resolve(tokenStore.token)) || requestToken(oa2, tenant, tokenStore))
|
|
@@ -46,7 +46,7 @@ class EMManagement {
|
|
|
46
46
|
uri: this.options.uri,
|
|
47
47
|
path: `/hub/rest/api/v1/management/messaging/queues/${encodeURIComponent(queueName)}`,
|
|
48
48
|
oa2: this.options.oa2,
|
|
49
|
-
tokenStore: this
|
|
49
|
+
tokenStore: (this._tokenStore ??= {})
|
|
50
50
|
})
|
|
51
51
|
return res.body
|
|
52
52
|
} catch (e) {
|
|
@@ -67,7 +67,7 @@ class EMManagement {
|
|
|
67
67
|
uri: this.options.uri,
|
|
68
68
|
path: `/hub/rest/api/v1/management/messaging/queues`,
|
|
69
69
|
oa2: this.options.oa2,
|
|
70
|
-
tokenStore: this
|
|
70
|
+
tokenStore: (this._tokenStore ??= {})
|
|
71
71
|
})
|
|
72
72
|
return res.body
|
|
73
73
|
} catch (e) {
|
|
@@ -96,7 +96,7 @@ class EMManagement {
|
|
|
96
96
|
path: `/hub/rest/api/v1/management/messaging/queues/${encodeURIComponent(queueName)}`,
|
|
97
97
|
oa2: this.options.oa2,
|
|
98
98
|
dataObj: queueConfig,
|
|
99
|
-
tokenStore: this
|
|
99
|
+
tokenStore: (this._tokenStore ??= {})
|
|
100
100
|
})
|
|
101
101
|
if (res.statusCode === 201) return true
|
|
102
102
|
} catch (e) {
|
|
@@ -121,7 +121,7 @@ class EMManagement {
|
|
|
121
121
|
uri: this.options.uri,
|
|
122
122
|
path: `/hub/rest/api/v1/management/messaging/queues/${encodeURIComponent(queueName)}`,
|
|
123
123
|
oa2: this.options.oa2,
|
|
124
|
-
tokenStore: this
|
|
124
|
+
tokenStore: (this._tokenStore ??= {})
|
|
125
125
|
})
|
|
126
126
|
} catch (e) {
|
|
127
127
|
const error = new Error(`Queue "${queueName}" could not be deleted ${this.subdomainInfo}`)
|
|
@@ -146,7 +146,7 @@ class EMManagement {
|
|
|
146
146
|
path: `/hub/rest/api/v1/management/messaging/queues/${encodeURIComponent(queueName)}/subscriptions`,
|
|
147
147
|
oa2: this.options.oa2,
|
|
148
148
|
target: { kind: 'SUBSCRIPTION', queue: queueName },
|
|
149
|
-
tokenStore: this
|
|
149
|
+
tokenStore: (this._tokenStore ??= {})
|
|
150
150
|
})
|
|
151
151
|
return res.body
|
|
152
152
|
} catch (e) {
|
|
@@ -175,7 +175,7 @@ class EMManagement {
|
|
|
175
175
|
queueName
|
|
176
176
|
)}/subscriptions/${encodeURIComponent(topicPattern)}`,
|
|
177
177
|
oa2: this.options.oa2,
|
|
178
|
-
tokenStore: this
|
|
178
|
+
tokenStore: (this._tokenStore ??= {})
|
|
179
179
|
})
|
|
180
180
|
if (res.statusCode === 201) return true
|
|
181
181
|
} catch (e) {
|
|
@@ -206,7 +206,7 @@ class EMManagement {
|
|
|
206
206
|
queueName
|
|
207
207
|
)}/subscriptions/${encodeURIComponent(topicPattern)}`,
|
|
208
208
|
oa2: this.options.oa2,
|
|
209
|
-
tokenStore: this
|
|
209
|
+
tokenStore: (this._tokenStore ??= {})
|
|
210
210
|
})
|
|
211
211
|
} catch (e) {
|
|
212
212
|
const error = new Error(
|
|
@@ -235,7 +235,7 @@ class EMManagement {
|
|
|
235
235
|
uri: this.optionsMessagingREST.uri,
|
|
236
236
|
path: `/messagingrest/v1/subscriptions/${encodeURIComponent(webhookName)}`,
|
|
237
237
|
oa2: this.optionsMessagingREST.oa2,
|
|
238
|
-
tokenStore: this
|
|
238
|
+
tokenStore: (this._tokenStore ??= {})
|
|
239
239
|
})
|
|
240
240
|
return res.body
|
|
241
241
|
} catch (e) {
|
|
@@ -263,7 +263,7 @@ class EMManagement {
|
|
|
263
263
|
uri: this.optionsMessagingREST.uri,
|
|
264
264
|
path: `/messagingrest/v1/subscriptions/${encodeURIComponent(webhookName)}`,
|
|
265
265
|
oa2: this.optionsMessagingREST.oa2,
|
|
266
|
-
tokenStore: this
|
|
266
|
+
tokenStore: (this._tokenStore ??= {})
|
|
267
267
|
})
|
|
268
268
|
} catch (e) {
|
|
269
269
|
const error = new Error(`Webhook "${webhookName}" could not be deleted ${this.subdomainInfo}`)
|
|
@@ -331,7 +331,7 @@ class EMManagement {
|
|
|
331
331
|
path: '/messagingrest/v1/subscriptions',
|
|
332
332
|
oa2: this.optionsMessagingREST.oa2,
|
|
333
333
|
dataObj,
|
|
334
|
-
tokenStore: this
|
|
334
|
+
tokenStore: (this._tokenStore ??= {})
|
|
335
335
|
})
|
|
336
336
|
if (res.statusCode === 201) return true
|
|
337
337
|
} catch (e) {
|
|
@@ -360,7 +360,7 @@ class EMManagement {
|
|
|
360
360
|
uri: this.optionsMessagingREST.uri,
|
|
361
361
|
path: `/messagingrest/v1/subscriptions/${encodeURIComponent(webhookName)}`,
|
|
362
362
|
oa2: this.optionsMessagingREST.oa2,
|
|
363
|
-
tokenStore: this
|
|
363
|
+
tokenStore: (this._tokenStore ??= {})
|
|
364
364
|
})
|
|
365
365
|
} catch (e) {
|
|
366
366
|
const error = new Error(`Webhook "${webhookName}" could not be deleted ${this.subdomainInfo}`)
|
|
@@ -420,7 +420,7 @@ class EMManagement {
|
|
|
420
420
|
uri: this.options.uri,
|
|
421
421
|
path: `/hub/rest/api/v1/management/messaging/readinessCheck`,
|
|
422
422
|
oa2: this.options.oa2,
|
|
423
|
-
tokenStore: this
|
|
423
|
+
tokenStore: (this._tokenStore ??= {})
|
|
424
424
|
})
|
|
425
425
|
} catch (e) {
|
|
426
426
|
const error = new Error(`Readiness Check failed ${this.subdomainInfo}`)
|
|
@@ -182,7 +182,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
182
182
|
'Content-Type': contentType,
|
|
183
183
|
'x-qos': 1
|
|
184
184
|
},
|
|
185
|
-
tokenStore: {}
|
|
185
|
+
tokenStore: (this._tokenStore ??= {})
|
|
186
186
|
}
|
|
187
187
|
if (tenant) params.tenant = tenant
|
|
188
188
|
await authorizedRequest(params)
|
|
@@ -7,8 +7,8 @@ const _errorObj = result => {
|
|
|
7
7
|
return errorObj
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const requestToken = ({ client, secret, endpoint, mTLS }, tenant, tokenStore) =>
|
|
11
|
-
new Promise((resolve, reject) => {
|
|
10
|
+
const requestToken = ({ client, secret, endpoint, mTLS }, tenant, tokenStore) => {
|
|
11
|
+
const promise = new Promise((resolve, reject) => {
|
|
12
12
|
const options = {
|
|
13
13
|
host: endpoint.replace('/oauth/token', '').replace('https://', ''),
|
|
14
14
|
headers: {}
|
|
@@ -44,7 +44,18 @@ const requestToken = ({ client, secret, endpoint, mTLS }, tenant, tokenStore) =>
|
|
|
44
44
|
reject(_errorObj(result))
|
|
45
45
|
return
|
|
46
46
|
}
|
|
47
|
-
//
|
|
47
|
+
// set timeout to delete token from cache 5 minutes (300 seconds) before it expires
|
|
48
|
+
const expiresIn = json.expires_in || 3600
|
|
49
|
+
const timeout = Math.max(0, (expiresIn - 300) * 1000)
|
|
50
|
+
if (tokenStore.expirationTimeout) clearTimeout(tokenStore.expirationTimeout)
|
|
51
|
+
tokenStore.expires_in = json.expires_in
|
|
52
|
+
tokenStore.expirationTimeout = setTimeout(() => {
|
|
53
|
+
delete tokenStore.token
|
|
54
|
+
delete tokenStore.expires_in
|
|
55
|
+
delete tokenStore.expirationTimeout
|
|
56
|
+
}, timeout)
|
|
57
|
+
|
|
58
|
+
// store token value on tokenStore
|
|
48
59
|
tokenStore.token = json.access_token
|
|
49
60
|
resolve(json.access_token)
|
|
50
61
|
} catch {
|
|
@@ -56,4 +67,8 @@ const requestToken = ({ client, secret, endpoint, mTLS }, tenant, tokenStore) =>
|
|
|
56
67
|
req.end()
|
|
57
68
|
})
|
|
58
69
|
|
|
70
|
+
tokenStore.token = promise
|
|
71
|
+
return promise
|
|
72
|
+
}
|
|
73
|
+
|
|
59
74
|
module.exports = requestToken
|
|
@@ -23,7 +23,7 @@ class MQManagement {
|
|
|
23
23
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
|
|
24
24
|
oa2: this.options.auth.oauth2,
|
|
25
25
|
target: { kind: 'QUEUE', queue: queueName },
|
|
26
|
-
tokenStore: this
|
|
26
|
+
tokenStore: (this._tokenStore ??= {})
|
|
27
27
|
})
|
|
28
28
|
return res.body
|
|
29
29
|
} catch (e) {
|
|
@@ -45,7 +45,7 @@ class MQManagement {
|
|
|
45
45
|
path: `/v1/management/queues`,
|
|
46
46
|
oa2: this.options.auth.oauth2,
|
|
47
47
|
target: { kind: 'QUEUE' },
|
|
48
|
-
tokenStore: this
|
|
48
|
+
tokenStore: (this._tokenStore ??= {})
|
|
49
49
|
})
|
|
50
50
|
return res.body && res.body.results
|
|
51
51
|
} catch (e) {
|
|
@@ -70,7 +70,7 @@ class MQManagement {
|
|
|
70
70
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
|
|
71
71
|
oa2: this.options.auth.oauth2,
|
|
72
72
|
dataObj: queueConfig,
|
|
73
|
-
tokenStore: this
|
|
73
|
+
tokenStore: (this._tokenStore ??= {})
|
|
74
74
|
})
|
|
75
75
|
if (res.statusCode === 201) return true
|
|
76
76
|
} catch (e) {
|
|
@@ -91,7 +91,7 @@ class MQManagement {
|
|
|
91
91
|
uri: this.options.url,
|
|
92
92
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
|
|
93
93
|
oa2: this.options.auth.oauth2,
|
|
94
|
-
tokenStore: this
|
|
94
|
+
tokenStore: (this._tokenStore ??= {})
|
|
95
95
|
})
|
|
96
96
|
} catch (e) {
|
|
97
97
|
const error = new Error(`Queue "${queueName}" could not be deleted`)
|
|
@@ -111,7 +111,7 @@ class MQManagement {
|
|
|
111
111
|
uri: this.options.url,
|
|
112
112
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}/subscriptions/topics`,
|
|
113
113
|
oa2: this.options.auth.oauth2,
|
|
114
|
-
tokenStore: this
|
|
114
|
+
tokenStore: (this._tokenStore ??= {})
|
|
115
115
|
})
|
|
116
116
|
return res.body
|
|
117
117
|
} catch (e) {
|
|
@@ -134,7 +134,7 @@ class MQManagement {
|
|
|
134
134
|
topicPattern
|
|
135
135
|
)}`,
|
|
136
136
|
oa2: this.options.auth.oauth2,
|
|
137
|
-
tokenStore: this
|
|
137
|
+
tokenStore: (this._tokenStore ??= {})
|
|
138
138
|
})
|
|
139
139
|
if (res.statusCode === 201) return true
|
|
140
140
|
} catch (e) {
|
|
@@ -159,7 +159,7 @@ class MQManagement {
|
|
|
159
159
|
oa2: this.options.auth.oauth2,
|
|
160
160
|
|
|
161
161
|
target: { kind: 'SUBSCRIPTION', queue: queueName, topic: topicPattern },
|
|
162
|
-
tokenStore: this
|
|
162
|
+
tokenStore: (this._tokenStore ??= {})
|
|
163
163
|
})
|
|
164
164
|
} catch (e) {
|
|
165
165
|
const error = new Error(`Subscription "${topicPattern}" could not be deleted from queue "${queueName}"`)
|
|
@@ -45,7 +45,7 @@ const _buildPartialUrlFunctions = (url, data, params, kind = 'odata-v4') => {
|
|
|
45
45
|
: `${url}(${funcParams.join(',')})?${queryOptions.join('&')}`
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
const _extractParamsFromData = (data, params = {}) => {
|
|
48
|
+
const _extractParamsFromData = (data = {}, params = {}) => {
|
|
49
49
|
return Object.keys(data).reduce((res, el) => {
|
|
50
50
|
if (params[el]) Object.assign(res, { [el]: data[el] })
|
|
51
51
|
return res
|
|
@@ -142,7 +142,16 @@ const _addHandlerActionFunction = (srv, def, target) => {
|
|
|
142
142
|
srv.on(event, target, async function (req) {
|
|
143
143
|
const shortEntityName = req.target.name.replace(`${this.definition.name}.`, '')
|
|
144
144
|
if (this.kind === 'odata-v2') return _handleV2BoundActionFunction(srv, def, req, event, this.kind)
|
|
145
|
-
|
|
145
|
+
|
|
146
|
+
const action = req.target.actions[req.event]
|
|
147
|
+
const onCollection = !!(
|
|
148
|
+
action['@cds.odata.bindingparameter.collection'] ||
|
|
149
|
+
(action?.params && [...action.params].some(p => p?.items?.type === '$self'))
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const url = onCollection
|
|
153
|
+
? `/${shortEntityName}/${this.definition.name}.${event}`
|
|
154
|
+
: `/${shortEntityName}(${_buildKeys(req, this.kind).join(',')})/${this.definition.name}.${event}`
|
|
146
155
|
return _handleBoundActionFunction(srv, def, req, url)
|
|
147
156
|
})
|
|
148
157
|
} else {
|
|
@@ -260,8 +269,10 @@ class RemoteService extends cds.Service {
|
|
|
260
269
|
returnType: req._returnType
|
|
261
270
|
}
|
|
262
271
|
|
|
272
|
+
// REVISIT: use xssec's getIdToken() for full list of audiences?
|
|
273
|
+
let jwt = cds.context?.user?.authInfo?.token?.jwt
|
|
263
274
|
// REVISIT: i don't believe req.context.headers is an official API
|
|
264
|
-
|
|
275
|
+
if (!jwt) jwt = req?.context?.headers?.authorization?.split(/^bearer /i)[1]
|
|
265
276
|
if (!jwt) jwt = req?.context?.http?.req?.headers?.authorization?.split(/^bearer /i)[1]
|
|
266
277
|
if (jwt) additionalOptions.jwt = jwt
|
|
267
278
|
|
|
@@ -250,6 +250,7 @@ module.exports.run = async (requestConfig, options) => {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
const { kind, resolvedTarget, returnType } = options
|
|
253
|
+
if (kind === 'hcql') return response.data.data
|
|
253
254
|
if (kind === 'odata-v4') return _purgeODataV4(response.data)
|
|
254
255
|
if (kind === 'odata-v2') return _purgeODataV2(response.data, resolvedTarget, returnType)
|
|
255
256
|
if (kind === 'odata') {
|
|
@@ -103,7 +103,6 @@ const KINDS_SUPPORTING_BATCH = { odata: true, 'odata-v2': true, 'odata-v4': true
|
|
|
103
103
|
|
|
104
104
|
const _extractRequestConfig = (req, query, service) => {
|
|
105
105
|
if (service.kind === 'hcql' && typeof query === 'object') return _cqnToHcqlRequestConfig(query)
|
|
106
|
-
if (service.kind === 'hcql') throw new Error('The request has no query and cannot be served by HCQL remote services!')
|
|
107
106
|
if (typeof query === 'object') return _cqnToRequestConfig(query, service, req)
|
|
108
107
|
if (typeof query === 'string') return _stringToRequestConfig(query, req.data, req.target)
|
|
109
108
|
//> no model, no service.definition
|