@sap/cds 9.7.0 → 9.8.0
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 +49 -0
- package/_i18n/i18n_en_US_saptrc.properties +1 -56
- package/_i18n/messages_en_US_saptrc.properties +1 -92
- package/eslint.config.mjs +1 -1
- package/lib/compile/for/flows.js +86 -79
- package/lib/compile/for/lean_drafts.js +12 -0
- package/lib/compile/to/json.js +4 -2
- package/lib/env/defaults.js +1 -0
- package/lib/env/serviceBindings.js +15 -5
- package/lib/index.js +1 -1
- package/lib/log/cds-error.js +33 -20
- package/lib/req/spawn.js +2 -2
- package/lib/srv/bindings.js +6 -13
- package/lib/srv/cds.Service.js +8 -36
- package/lib/srv/protocols/hcql.js +19 -2
- package/lib/srv/protocols/http.js +1 -1
- package/lib/utils/cds-utils.js +25 -16
- package/lib/utils/tar-win.js +106 -0
- package/lib/utils/tar.js +23 -158
- package/libx/_runtime/common/generic/crud.js +8 -7
- package/libx/_runtime/common/generic/sorting.js +7 -3
- package/libx/_runtime/common/utils/resolveView.js +47 -40
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -0
- package/libx/_runtime/fiori/lean-draft.js +11 -2
- package/libx/_runtime/messaging/kafka.js +6 -5
- package/libx/_runtime/remote/Service.js +14 -2
- package/libx/_runtime/remote/utils/client.js +2 -4
- package/libx/_runtime/remote/utils/query.js +4 -4
- package/libx/odata/middleware/batch.js +316 -339
- package/libx/odata/middleware/create.js +0 -5
- package/libx/odata/middleware/delete.js +2 -6
- package/libx/odata/middleware/operation.js +10 -8
- package/libx/odata/middleware/read.js +0 -10
- package/libx/odata/middleware/stream.js +1 -0
- package/libx/odata/middleware/update.js +0 -6
- package/libx/odata/parse/afterburner.js +47 -22
- package/libx/odata/parse/cqn2odata.js +6 -1
- package/libx/odata/parse/grammar.peggy +14 -2
- package/libx/odata/parse/multipartToJson.js +2 -1
- package/libx/odata/parse/parser.js +1 -1
- package/package.json +2 -2
|
@@ -11,19 +11,19 @@ module.exports = cds.service.impl(function () {
|
|
|
11
11
|
this.on(['CREATE', 'READ', 'UPDATE', 'UPSERT', 'DELETE'], '*', async function handle_crud_requests(req) {
|
|
12
12
|
|
|
13
13
|
if (!cds.db)
|
|
14
|
-
return req.reject
|
|
14
|
+
return req.reject('NO_DATABASE_CONNECTION') // REVISIT: error message
|
|
15
15
|
|
|
16
16
|
if (!req.query)
|
|
17
|
-
return req.reject
|
|
17
|
+
return req.reject(501, 'The request has no query and cannot be served generically.')
|
|
18
18
|
|
|
19
19
|
if (typeof req.query !== 'string' && req.target?._hasPersistenceSkip)
|
|
20
|
-
return req.reject
|
|
20
|
+
return req.reject(501, `Entity "${req.target.name}" is annotated with "@cds.persistence.skip" and cannot be served generically.`)
|
|
21
21
|
|
|
22
22
|
// validate that all elements in path exist on db, if necessary
|
|
23
23
|
// - INSERT has no where clause to do this in one roundtrip
|
|
24
24
|
// - SELECT returns [] -> really empty collection or invalid path?
|
|
25
25
|
const subject = req.query.INSERT?.into || req.query.SELECT?.from
|
|
26
|
-
const pathExistsQuery = subject?.ref?.length > 1 && SELECT(1).from({ ref: subject.ref.slice(0
|
|
26
|
+
const pathExistsQuery = subject?.ref?.length > 1 && SELECT(1).from({ ref: subject.ref.slice(0, -1) })
|
|
27
27
|
|
|
28
28
|
if (req.event === 'CREATE' && pathExistsQuery) {
|
|
29
29
|
// REVISIT: Why dont't we just run the insert and check affected rows?
|
|
@@ -33,10 +33,10 @@ module.exports = cds.service.impl(function () {
|
|
|
33
33
|
|
|
34
34
|
if (req.event in { DELETE: 1, UPDATE: 1 } && req.target?._isSingleton) {
|
|
35
35
|
if (req.event === 'DELETE' && !req.target['@odata.singleton.nullable'])
|
|
36
|
-
return req.reject
|
|
36
|
+
return req.reject(400, 'SINGLETON_NOT_NULLABLE')
|
|
37
37
|
|
|
38
38
|
const selectSingleton = SELECT.one(req.target)
|
|
39
|
-
const keyColumns = [...(req.target.keys||[])].filter(e => !e.isAssociation).map(e => e.name)
|
|
39
|
+
const keyColumns = [...(req.target.keys || [])].filter(e => !e.isAssociation).map(e => e.name)
|
|
40
40
|
|
|
41
41
|
// if no keys available, select all columns so we can delete the singleton with same content
|
|
42
42
|
if (keyColumns.length) selectSingleton.columns(keyColumns)
|
|
@@ -54,7 +54,8 @@ module.exports = cds.service.impl(function () {
|
|
|
54
54
|
|
|
55
55
|
if (req.event === 'READ' && req.query?.SELECT && req.locale) req.query.SELECT.localized ??= true
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
// REVISIT for cds^10: can we always use cds.db.dispatch(req)?
|
|
58
|
+
const result = req.iterator && !req.objectMode ? await cds.db.dispatch(req) : await cds.run(req.query, req.data)
|
|
58
59
|
|
|
59
60
|
if (req.event === 'READ') {
|
|
60
61
|
// do not execute additional select to distinguish between 412 and 404
|
|
@@ -50,6 +50,12 @@ const _addDefaultSortOrder = (req, select) => {
|
|
|
50
50
|
)
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
const containsAggregation = column => {
|
|
54
|
+
if (column.func) return true
|
|
55
|
+
if (column.xpr) return column.xpr.some(xpr => containsAggregation(xpr))
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
53
59
|
/**
|
|
54
60
|
* 1. query options --> already set in req.query
|
|
55
61
|
* 2. orders from view
|
|
@@ -63,9 +69,7 @@ const handle_sorting = function (req) {
|
|
|
63
69
|
let select = req.query.SELECT
|
|
64
70
|
|
|
65
71
|
// do not sort for /$count queries or queries only using aggregations
|
|
66
|
-
if (select.columns
|
|
67
|
-
return
|
|
68
|
-
}
|
|
72
|
+
if (select.columns?.length && select.columns.every(col => containsAggregation(col))) return
|
|
69
73
|
|
|
70
74
|
if (select.from && select.from.SELECT) {
|
|
71
75
|
// add default sort to root query
|
|
@@ -53,15 +53,15 @@ const _inverseTransition = transition => {
|
|
|
53
53
|
return inverseTransition
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
const revertData = (data, transition, service
|
|
56
|
+
const revertData = (data, transition, service) => {
|
|
57
57
|
if (!transition || !transition.mapping.size) return data
|
|
58
58
|
const inverseTransition = _inverseTransition(transition)
|
|
59
59
|
return Array.isArray(data)
|
|
60
|
-
? data.map(entry => _newData(entry, inverseTransition, true, service
|
|
61
|
-
: _newData(data, inverseTransition, true, service
|
|
60
|
+
? data.map(entry => _newData(entry, inverseTransition, true, service))
|
|
61
|
+
: _newData(data, inverseTransition, true, service)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const _newSubData = (val, key, transition, el, inverse, service, options) => {
|
|
64
|
+
const _newSubData = (val, key, transition, el, inverse, service, options = {}) => {
|
|
65
65
|
if ((!Array.isArray(val) && typeof val === 'object') || (Array.isArray(val) && val.length !== 0)) {
|
|
66
66
|
let mapped = transition.mapping.get(key)
|
|
67
67
|
if (!mapped) {
|
|
@@ -70,7 +70,7 @@ const _newSubData = (val, key, transition, el, inverse, service, options) => {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
if (!mapped.transition) {
|
|
73
|
-
const subTransition = getTransition(el._target, service,
|
|
73
|
+
const subTransition = getTransition(el._target, service, null, options.event)
|
|
74
74
|
mapped.transition = inverse ? _inverseTransition(subTransition) : subTransition
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -196,7 +196,7 @@ const _newColumns = (columns = [], transition, service, withAlias = false, optio
|
|
|
196
196
|
|
|
197
197
|
// reuse _newColumns with new transition
|
|
198
198
|
const expandTarget = def._target
|
|
199
|
-
const subtransition = getTransition(expandTarget, service,
|
|
199
|
+
const subtransition = getTransition(expandTarget, service, null, options.event)
|
|
200
200
|
mapped.transition = subtransition
|
|
201
201
|
newColumn.expand = _newColumns(column.expand, subtransition, service, withAlias, options)
|
|
202
202
|
}
|
|
@@ -523,19 +523,26 @@ const _queryColumns = (target, columns = [], isAborted) => {
|
|
|
523
523
|
return queryColumns
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
const _mappedValue = (col, alias) => {
|
|
526
|
+
const _mappedValue = (col, alias, target) => {
|
|
527
527
|
const key = col.as || col.ref[0]
|
|
528
528
|
|
|
529
529
|
if (col.ref) {
|
|
530
|
-
|
|
530
|
+
let columnRef = col.ref.filter(columnName => columnName !== alias)
|
|
531
|
+
|
|
532
|
+
if (columnRef.length > 1 && target) {
|
|
533
|
+
const firstElement = target.elements?.[columnRef[0]]
|
|
534
|
+
if (firstElement?.foreignKeys) {
|
|
535
|
+
// It's a managed association - use foreignKeys property to get the FK column name
|
|
536
|
+
if (columnRef[1] in firstElement.foreignKeys) columnRef = [`${firstElement.name}_${columnRef[1]}`]
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
531
540
|
return [key, { ref: columnRef }]
|
|
532
541
|
}
|
|
533
542
|
|
|
534
543
|
return [key, { val: col.val }]
|
|
535
544
|
}
|
|
536
545
|
|
|
537
|
-
const getDBTable = target => cds.db.resolve.table(target)
|
|
538
|
-
|
|
539
546
|
const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
|
|
540
547
|
const el = target.elements[as] || target.query._target?.elements[ref.at(-1)]
|
|
541
548
|
|
|
@@ -578,26 +585,27 @@ const _checkForForbiddenViews = (queryTarget, event) => {
|
|
|
578
585
|
}
|
|
579
586
|
}
|
|
580
587
|
|
|
581
|
-
const _getTransitionData = (target, columns, service,
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
+
const _getTransitionData = (target, columns, service, event) => {
|
|
589
|
+
_checkForForbiddenViews(target, event)
|
|
590
|
+
|
|
591
|
+
const _dbAbort = target =>
|
|
592
|
+
!!(Object.prototype.hasOwnProperty.call(target, '@cds.persistence.table') || !target.query?._target)
|
|
593
|
+
const _defaultAbort = target => target._service?.name === service.definition?.name
|
|
594
|
+
const _abort = service.isDatabaseService ? _dbAbort : _defaultAbort
|
|
595
|
+
|
|
596
|
+
const isAborted = _abort(target)
|
|
588
597
|
columns = _queryColumns(target, columns, isAborted)
|
|
589
598
|
|
|
590
599
|
if (isAborted) return { target, transitionColumns: columns }
|
|
591
600
|
|
|
592
601
|
if (!target.query?._target) {
|
|
593
602
|
// for cross service in x4 and DRAFT.DraftAdministrativeData we cannot abort properly
|
|
594
|
-
// therefore return last resolved target
|
|
595
603
|
if (cds.env.features.restrict_service_scope === false) return { target, transitionColumns: columns }
|
|
596
604
|
return undefined
|
|
597
605
|
} else {
|
|
598
606
|
const newTarget = target.query._target
|
|
599
607
|
// continue projection resolving for projections
|
|
600
|
-
return _getTransitionData(newTarget, columns, service,
|
|
608
|
+
return _getTransitionData(newTarget, columns, service, event)
|
|
601
609
|
}
|
|
602
610
|
}
|
|
603
611
|
|
|
@@ -606,24 +614,20 @@ const _getTransitionData = (target, columns, service, options) => {
|
|
|
606
614
|
*
|
|
607
615
|
* @param queryTarget
|
|
608
616
|
* @param service
|
|
609
|
-
* @param
|
|
617
|
+
* @param _dummy - unused positional argument (for db-service < 2.9)
|
|
618
|
+
* @param event
|
|
610
619
|
*/
|
|
611
|
-
const getTransition = (queryTarget, service,
|
|
620
|
+
const getTransition = (queryTarget, service, _dummy, event) => {
|
|
612
621
|
// Never resolve unknown targets (e.g. for drafts)
|
|
613
|
-
if (!queryTarget) {
|
|
614
|
-
return { target: queryTarget, queryTarget, mapping: new Map() }
|
|
615
|
-
}
|
|
622
|
+
if (!queryTarget) return { target: queryTarget, queryTarget, mapping: new Map() }
|
|
616
623
|
|
|
617
|
-
const transitionData = _getTransitionData(queryTarget, [], service,
|
|
618
|
-
skipForbiddenViewCheck,
|
|
619
|
-
event,
|
|
620
|
-
abort: options?.abort
|
|
621
|
-
})
|
|
624
|
+
const transitionData = _getTransitionData(queryTarget, [], service, event)
|
|
622
625
|
if (!transitionData) return undefined
|
|
623
626
|
const { target: _target, transitionColumns } = transitionData
|
|
624
627
|
const query = queryTarget.query
|
|
625
628
|
const alias = query && query.SELECT && query.SELECT.from && query.SELECT.from.as
|
|
626
|
-
const
|
|
629
|
+
const mappingTarget = query?._target
|
|
630
|
+
const mappedColumns = transitionColumns.map(column => _mappedValue(column, alias, mappingTarget))
|
|
627
631
|
const mapping = new Map(mappedColumns)
|
|
628
632
|
return { target: _target, queryTarget, mapping }
|
|
629
633
|
}
|
|
@@ -632,11 +636,7 @@ const _entityTransitionsForTarget = (from, model, service, options) => {
|
|
|
632
636
|
let previousEntity = options.previousEntity
|
|
633
637
|
|
|
634
638
|
if (typeof from === 'string') {
|
|
635
|
-
return (
|
|
636
|
-
model.definitions[from] && [
|
|
637
|
-
getTransition(model.definitions[from], service, undefined, options.event, { abort: options.abort })
|
|
638
|
-
]
|
|
639
|
-
)
|
|
639
|
+
return model.definitions[from] && [getTransition(model.definitions[from], service, null, options.event)]
|
|
640
640
|
}
|
|
641
641
|
|
|
642
642
|
if (typeof from === 'object' && from.SELECT) {
|
|
@@ -651,7 +651,7 @@ const _entityTransitionsForTarget = (from, model, service, options) => {
|
|
|
651
651
|
const entity = model.definitions[element]
|
|
652
652
|
if (entity) {
|
|
653
653
|
previousEntity = entity
|
|
654
|
-
return getTransition(entity, service,
|
|
654
|
+
return getTransition(entity, service, null, options.event)
|
|
655
655
|
}
|
|
656
656
|
}
|
|
657
657
|
|
|
@@ -660,7 +660,7 @@ const _entityTransitionsForTarget = (from, model, service, options) => {
|
|
|
660
660
|
if (entity) {
|
|
661
661
|
// > assoc
|
|
662
662
|
previousEntity = entity
|
|
663
|
-
return getTransition(entity, service,
|
|
663
|
+
return getTransition(entity, service, null, options.event)
|
|
664
664
|
}
|
|
665
665
|
|
|
666
666
|
// > struct
|
|
@@ -674,7 +674,7 @@ const _entityTransitionsForTarget = (from, model, service, options) => {
|
|
|
674
674
|
})
|
|
675
675
|
}
|
|
676
676
|
|
|
677
|
-
const resolveView = (query, model, service
|
|
677
|
+
const resolveView = (query, model, service) => {
|
|
678
678
|
// swap logger
|
|
679
679
|
const _LOG = LOG
|
|
680
680
|
LOG = cds.log(service.kind) // REVISIT: Avoid obtaining loggers per request!
|
|
@@ -699,7 +699,7 @@ const resolveView = (query, model, service, abort) => {
|
|
|
699
699
|
DELETE: ['from', _newDelete]
|
|
700
700
|
}[kind]
|
|
701
701
|
|
|
702
|
-
const options = {
|
|
702
|
+
const options = { event: kind, service, model }
|
|
703
703
|
const transitions = _entityTransitionsForTarget(query[kind][_prop], model, service, options)
|
|
704
704
|
if (!service.isDatabaseService && cds.env.features.restrict_service_scope !== false && transitions.some(t => !t))
|
|
705
705
|
return
|
|
@@ -719,8 +719,15 @@ const resolveView = (query, model, service, abort) => {
|
|
|
719
719
|
return newQuery
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
+
const _resolve_table = target => {
|
|
723
|
+
if (target.query?._target && !Object.prototype.hasOwnProperty.call(target, '@cds.persistence.table'))
|
|
724
|
+
return _resolve_table(target.query._target)
|
|
725
|
+
return target
|
|
726
|
+
}
|
|
727
|
+
|
|
722
728
|
module.exports = {
|
|
723
|
-
getDBTable
|
|
729
|
+
// REVISIT: remove getDBTable with cds^10
|
|
730
|
+
getDBTable: _resolve_table,
|
|
724
731
|
resolveView,
|
|
725
732
|
getTransition,
|
|
726
733
|
revertData
|
|
@@ -95,6 +95,7 @@ const rewriteExpandAsterisk = (columns, target) => {
|
|
|
95
95
|
|
|
96
96
|
for (const elName in target.elements) {
|
|
97
97
|
if (!target.elements[elName]._target) continue
|
|
98
|
+
if (target.elements[elName]._target?._service !== target._service) continue
|
|
98
99
|
if (restrictions.includes(elName)) continue
|
|
99
100
|
if (elName === 'SiblingEntity') continue
|
|
100
101
|
if (columns.find(col => col.expand && col.ref && col.ref[0] === elName)) continue
|
|
@@ -6,6 +6,7 @@ const { Object_keys } = cds.utils
|
|
|
6
6
|
const { Readable, PassThrough } = require('stream')
|
|
7
7
|
|
|
8
8
|
const { getPageSize, commonGenericPaging } = require('../common/generic/paging')
|
|
9
|
+
const { getPreferReturnHeader } = require('../../odata/utils')
|
|
9
10
|
const { handler: commonGenericSorting } = require('../common/generic/sorting')
|
|
10
11
|
const { addEtagColumns } = require('../common/utils/etag')
|
|
11
12
|
const { handleStreamProperties } = require('../common/utils/streamProp')
|
|
@@ -710,7 +711,7 @@ const draftHandle = async function (req) {
|
|
|
710
711
|
let newDraftAction = rootEntity['@Common.DraftRoot.NewAction']
|
|
711
712
|
if (typeof newDraftAction != 'string' || !newDraftAction.length) newDraftAction = false
|
|
712
713
|
else newDraftAction = newDraftAction.split('.').pop()
|
|
713
|
-
const shouldHandleNewDraftAction = isNewDraftViaActionEnabled && req.target === rootEntity
|
|
714
|
+
const shouldHandleNewDraftAction = isNewDraftViaActionEnabled && req.target.name === rootEntity.name
|
|
714
715
|
|
|
715
716
|
// Create active instance of draft-enabled entity
|
|
716
717
|
// REVISIT: New OData adapter only sets `NEW` for drafts... how to distinguish programmatic modifications?
|
|
@@ -781,7 +782,13 @@ const draftHandle = async function (req) {
|
|
|
781
782
|
req.data = createNewResult
|
|
782
783
|
req.res.status(201)
|
|
783
784
|
|
|
784
|
-
|
|
785
|
+
const result = await _readAfterDraftAction.call(this, { req, payload: createNewResult, action: 'draftNew' })
|
|
786
|
+
if (result === null) req.res.status(204)
|
|
787
|
+
req.res.set(
|
|
788
|
+
'location',
|
|
789
|
+
'../' + location4(req.target.actives, this, result || { ...createNewResult, IsActiveEntity: false })
|
|
790
|
+
)
|
|
791
|
+
return result
|
|
785
792
|
}
|
|
786
793
|
|
|
787
794
|
// Handle draft-only events, that can only ever target entities in draft state
|
|
@@ -2442,6 +2449,8 @@ async function onPrepare(req, next) {
|
|
|
2442
2449
|
}
|
|
2443
2450
|
|
|
2444
2451
|
const _readAfterDraftAction = async function ({ req, payload, action }) {
|
|
2452
|
+
if (getPreferReturnHeader(req) === 'minimal') return null
|
|
2453
|
+
|
|
2445
2454
|
const entity = action === 'draftActivate' ? req.target : req.target.drafts
|
|
2446
2455
|
|
|
2447
2456
|
// read after write with query options
|
|
@@ -123,13 +123,14 @@ class KafkaService extends cds.MessagingService {
|
|
|
123
123
|
eachMessage: async raw => {
|
|
124
124
|
try {
|
|
125
125
|
const msg = _normalizeIncomingMessage(raw.message.value.toString())
|
|
126
|
+
|
|
126
127
|
msg.headers = {}
|
|
127
128
|
for (const header in raw.message.headers || {}) {
|
|
128
129
|
msg.headers[header] = raw.message.headers[header]?.toString()
|
|
129
130
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
msg.
|
|
131
|
+
|
|
132
|
+
msg.tenant = msg.headers['x-sap-cap-tenant-id']
|
|
133
|
+
msg.event = msg.headers['x-sap-cap-effective-topic'] ?? msg.headers.type
|
|
133
134
|
if (!msg.event) return
|
|
134
135
|
|
|
135
136
|
await this.processInboundMsg({ tenant: msg.tenant }, msg)
|
|
@@ -210,8 +211,8 @@ async function _getConfig(srv) {
|
|
|
210
211
|
const caCerts = await _getCaCerts(srv)
|
|
211
212
|
|
|
212
213
|
const allBrokers =
|
|
213
|
-
srv.options.credentials.
|
|
214
|
-
srv.options.credentials
|
|
214
|
+
srv.options.credentials['cluster.public']?.['brokers.client_ssl'] ||
|
|
215
|
+
srv.options.credentials.cluster?.['brokers.client_ssl']
|
|
215
216
|
const brokers = allBrokers.split(',')
|
|
216
217
|
|
|
217
218
|
return {
|
|
@@ -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 {
|
|
@@ -240,6 +249,9 @@ class RemoteService extends cds.Service {
|
|
|
240
249
|
const requestConfig = extractRequestConfig(req, query, this)
|
|
241
250
|
requestConfig.headers = _getHeaders(requestConfig.headers, req)
|
|
242
251
|
|
|
252
|
+
if (this.kind === 'hcql' && req.iterator && !req.objectMode)
|
|
253
|
+
requestConfig.headers.accept = 'application/octet-stream,application/json'
|
|
254
|
+
|
|
243
255
|
// REVISIT: we should not have to set the content-type at all for that
|
|
244
256
|
if (requestConfig.headers.accept?.match(/stream|image|tar/)) requestConfig.responseType = 'stream'
|
|
245
257
|
|
|
@@ -43,9 +43,6 @@ const _executeHttpRequest = async ({ requestConfig, destination, destinationOpti
|
|
|
43
43
|
else if (LOG._warn) LOG.warn('Missing JWT token for forwardAuthToken!')
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// Cloud SDK throws error if useCache is activated and jwt is undefined
|
|
47
|
-
if (destination.jwt === undefined) destination.useCache = false
|
|
48
|
-
|
|
49
46
|
if (LOG._debug) _logRequest(requestConfig, destination)
|
|
50
47
|
|
|
51
48
|
// cloud sdk requires a new mechanism to differentiate the priority of headers
|
|
@@ -250,7 +247,8 @@ module.exports.run = async (requestConfig, options) => {
|
|
|
250
247
|
}
|
|
251
248
|
|
|
252
249
|
const { kind, resolvedTarget, returnType } = options
|
|
253
|
-
if (kind === 'hcql')
|
|
250
|
+
if (kind === 'hcql')
|
|
251
|
+
return response.headers?.['content-type']?.includes('application/octet-stream') ? response.data : response.data.data
|
|
254
252
|
if (kind === 'odata-v4') return _purgeODataV4(response.data)
|
|
255
253
|
if (kind === 'odata-v2') return _purgeODataV2(response.data, resolvedTarget, returnType)
|
|
256
254
|
if (kind === 'odata') {
|
|
@@ -26,11 +26,11 @@ const _cqnWithPublicEntries = query => {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const _cqnToHcqlRequestConfig = cqn => {
|
|
29
|
-
if (cqn.SELECT && cqn.SELECT.from) return { method: '
|
|
29
|
+
if (cqn.SELECT && cqn.SELECT.from) return { method: 'POST', data: { SELECT: _cqnWithPublicEntries(cqn.SELECT) } }
|
|
30
30
|
if (cqn.INSERT && cqn.INSERT.into) return { method: 'POST', data: { INSERT: _cqnWithPublicEntries(cqn.INSERT) } }
|
|
31
|
-
if (cqn.UPDATE && cqn.UPDATE.entity) return { method: '
|
|
32
|
-
if (cqn.UPSERT && cqn.UPSERT.into) return { method: '
|
|
33
|
-
if (cqn.DELETE && cqn.DELETE.from) return { method: '
|
|
31
|
+
if (cqn.UPDATE && cqn.UPDATE.entity) return { method: 'POST', data: { UPDATE: _cqnWithPublicEntries(cqn.UPDATE) } }
|
|
32
|
+
if (cqn.UPSERT && cqn.UPSERT.into) return { method: 'POST', data: { UPSERT: _cqnWithPublicEntries(cqn.UPSERT) } }
|
|
33
|
+
if (cqn.DELETE && cqn.DELETE.from) return { method: 'POST', data: { DELETE: _cqnWithPublicEntries(cqn.DELETE) } }
|
|
34
34
|
|
|
35
35
|
cds.error(400, 'Invalid CQN object can not be processed.', JSON.stringify(cqn), _cqnToHcqlRequestConfig)
|
|
36
36
|
}
|