@sap/cds 7.0.2 → 7.1.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 +60 -3
- package/_i18n/i18n_ar.properties +3 -0
- package/_i18n/i18n_cs.properties +4 -1
- package/_i18n/i18n_da.properties +3 -0
- package/_i18n/i18n_de.properties +3 -0
- package/_i18n/i18n_en.properties +3 -0
- package/_i18n/i18n_es.properties +3 -0
- package/_i18n/i18n_fi.properties +3 -0
- package/_i18n/i18n_fr.properties +3 -0
- package/_i18n/i18n_it.properties +3 -0
- package/_i18n/i18n_ja.properties +3 -0
- package/_i18n/i18n_ko.properties +3 -0
- package/_i18n/i18n_ms.properties +3 -0
- package/_i18n/i18n_nl.properties +3 -0
- package/_i18n/i18n_no.properties +3 -0
- package/_i18n/i18n_pl.properties +3 -0
- package/_i18n/i18n_pt.properties +3 -0
- package/_i18n/i18n_ro.properties +3 -0
- package/_i18n/i18n_ru.properties +3 -0
- package/_i18n/i18n_sv.properties +3 -0
- package/_i18n/i18n_th.properties +3 -0
- package/_i18n/i18n_zh_CN.properties +3 -0
- package/_i18n/i18n_zh_TW.properties +3 -0
- package/apis/core.d.ts +26 -30
- package/apis/cqn.d.ts +1 -0
- package/apis/ql.d.ts +2 -0
- package/apis/serve.d.ts +9 -0
- package/apis/services.d.ts +3 -2
- package/lib/compile/for/lean_drafts.js +22 -19
- package/lib/compile/to/srvinfo.js +7 -19
- package/lib/dbs/cds-deploy.js +11 -6
- package/lib/env/cds-env.js +3 -4
- package/lib/env/presets.js +14 -9
- package/lib/env/schemas/cds-package.json +3 -1
- package/lib/env/schemas/cds-rc.json +0 -4
- package/lib/linked/classes.js +112 -12
- package/lib/linked/entities.js +3 -0
- package/lib/linked/models.js +2 -1
- package/lib/ql/SELECT.js +1 -0
- package/lib/ql/Whereable.js +1 -0
- package/lib/srv/cds-serve.js +2 -1
- package/lib/srv/protocols/_legacy.js +7 -6
- package/lib/srv/protocols/index.js +30 -55
- package/lib/utils/tar.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/format/ResponseContentNegotiator.js +12 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/MetadataCache.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +10 -4
- package/libx/_runtime/cds-services/services/utils/columns.js +8 -2
- package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
- package/libx/_runtime/common/composition/data.js +49 -29
- package/libx/_runtime/common/composition/update.js +0 -1
- package/libx/_runtime/common/composition/utils.js +1 -1
- package/libx/_runtime/common/generic/crud.js +1 -1
- package/libx/_runtime/common/generic/input.js +18 -13
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +115 -35
- package/libx/_runtime/common/utils/rewriteAsterisks.js +21 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +1 -1
- package/libx/_runtime/db/generic/rewrite.js +5 -4
- package/libx/_runtime/db/query/read.js +10 -9
- package/libx/_runtime/db/query/update.js +9 -18
- package/libx/_runtime/db/utils/deep.js +6 -5
- package/libx/_runtime/db/utils/normalizeTimeData.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +14 -19
- package/libx/_runtime/fiori/generic/edit.js +2 -5
- package/libx/_runtime/hana/streaming.js +3 -4
- package/libx/_runtime/remote/utils/client.js +9 -5
- package/libx/odata/afterburner.js +5 -2
- package/libx/rest/RestAdapter.js +2 -2
- package/libx/rest/middleware/error.js +4 -1
- package/libx/rest/middleware/operation.js +1 -1
- package/package.json +3 -3
- package/lib/srv/protocols/graphql.js +0 -30
|
@@ -11,7 +11,6 @@ const {
|
|
|
11
11
|
const { readAndDeleteKeywords, isActiveEntityRequested, getKeyData } = require('../utils/where')
|
|
12
12
|
const { isDraftRootEntity } = require('../../fiori/utils/csn')
|
|
13
13
|
const { getColumns } = require('../../cds-services/services/utils/columns')
|
|
14
|
-
|
|
15
14
|
const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
|
|
16
15
|
|
|
17
16
|
const _getRootCQN = (context, requestActiveData) => {
|
|
@@ -120,52 +119,49 @@ const fioriGenericActivate = async function (req, next) {
|
|
|
120
119
|
if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
|
|
121
120
|
|
|
122
121
|
const { draftData, activeData, adminData } = await _draftCompositionTree(this, req)
|
|
123
|
-
|
|
124
122
|
if (!draftData) req.reject(404)
|
|
125
123
|
|
|
126
|
-
if (adminData.InProcessByUser !== req.user.id)
|
|
124
|
+
if (adminData.InProcessByUser !== req.user.id) {
|
|
127
125
|
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [adminData.InProcessByUser])
|
|
126
|
+
}
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
* create or update
|
|
131
|
-
*/
|
|
128
|
+
// create or update
|
|
132
129
|
let query, event
|
|
133
130
|
if (activeData) {
|
|
134
131
|
readAndDeleteKeywords(['IsActiveEntity'], req.query.SELECT.from.ref[0].where)
|
|
135
132
|
event = 'UPDATE'
|
|
136
|
-
//
|
|
133
|
+
// REVISIT: setting data should be part of ql
|
|
137
134
|
query = UPDATE(req.target).where(req.query.SELECT.from.ref[0].where)
|
|
138
135
|
query.UPDATE.data = draftData
|
|
139
|
-
query._activeData = activeData
|
|
140
136
|
} else {
|
|
141
137
|
event = 'CREATE'
|
|
142
138
|
query = INSERT.into(req.target).entries(draftData)
|
|
143
139
|
}
|
|
144
140
|
|
|
145
141
|
// REVISIT: _draftMetadata
|
|
146
|
-
const
|
|
142
|
+
const request = new cds.Request({ event, query, data: draftData, _draftMetadata: adminData })
|
|
147
143
|
|
|
148
144
|
// REVISIT: should not be necessary
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
request._ = Object.assign(request._, req._)
|
|
146
|
+
request._.params = req.params
|
|
147
|
+
request._.query = req.query
|
|
152
148
|
|
|
153
149
|
// use finally to preserve r.messages in success or error case
|
|
154
150
|
let result
|
|
155
151
|
try {
|
|
156
|
-
result = await this.dispatch(
|
|
152
|
+
result = await this.dispatch(request)
|
|
157
153
|
} finally {
|
|
158
154
|
// REVISIT: should not be necessary
|
|
159
|
-
if (
|
|
155
|
+
if (request.messages) for (const m of request.messages) req.info(m)
|
|
160
156
|
}
|
|
161
157
|
|
|
162
|
-
|
|
163
|
-
* delete draft data
|
|
164
|
-
*/
|
|
158
|
+
// delete draft data
|
|
165
159
|
const deleteDraftAdminCqn = getDeleteDraftAdminCqn(adminData.DraftUUID)
|
|
166
160
|
const draftTablesToDeleteFrom = [req.target.name + '_drafts']
|
|
167
|
-
for (const [entity] of getCompositionTargets(req.target, this).entries())
|
|
161
|
+
for (const [entity] of getCompositionTargets(req.target, this).entries()) {
|
|
168
162
|
draftTablesToDeleteFrom.push(entity + '_drafts')
|
|
163
|
+
}
|
|
164
|
+
|
|
169
165
|
await cds.db.tx(req).run([
|
|
170
166
|
deleteDraftAdminCqn,
|
|
171
167
|
...draftTablesToDeleteFrom.map(dt => {
|
|
@@ -178,7 +174,6 @@ const fioriGenericActivate = async function (req, next) {
|
|
|
178
174
|
// REVISIT: we need to use okra API here because it must be set in the batched request
|
|
179
175
|
// status code must be set in handler to allow overriding for FE V2
|
|
180
176
|
if (event === 'CREATE') req?._?.odataRes?.setStatusCode(201, { overwrite: true })
|
|
181
|
-
|
|
182
177
|
return result
|
|
183
178
|
}
|
|
184
179
|
|
|
@@ -33,10 +33,7 @@ const _getInsertAdminDataCQN = ({ user }, draftUUID, time) => {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
const _getLockWhere = (where, columnsMap) => {
|
|
36
|
-
if (columnsMap.size === 0)
|
|
37
|
-
return where
|
|
38
|
-
}
|
|
39
|
-
|
|
36
|
+
if (columnsMap.size === 0) return where
|
|
40
37
|
const whereKeys = Object.keys(where)
|
|
41
38
|
const lockWhere = {}
|
|
42
39
|
|
|
@@ -57,6 +54,7 @@ const _select = async (lockRecordCQN, draftExistsCQN, selectCQNs, req, dbtx) =>
|
|
|
57
54
|
if (drafts.length) req.reject(409, 'DRAFT_ALREADY_EXISTS')
|
|
58
55
|
req.reject(409, 'ENTITY_LOCKED')
|
|
59
56
|
}
|
|
57
|
+
|
|
60
58
|
const promisesResults = await Promise.allSettled([dbtx.run(draftExistsCQN), ...selectCQNs.map(cqn => dbtx.run(cqn))])
|
|
61
59
|
const firstRejected = promisesResults.find(r => r.status === 'rejected')
|
|
62
60
|
if (firstRejected) req.reject(firstRejected.reason)
|
|
@@ -165,7 +163,6 @@ const fioriGenericEdit = async function (req, next) {
|
|
|
165
163
|
// REVISIT: we need to use okra API here because it must be set in the batched request
|
|
166
164
|
// status code must be set in handler to allow overriding for FE V2
|
|
167
165
|
req?._?.odataRes?.setStatusCode(201, { overwrite: true })
|
|
168
|
-
|
|
169
166
|
return results[0][0]
|
|
170
167
|
}
|
|
171
168
|
|
|
@@ -18,7 +18,8 @@ const _loadStreamExtensionIfNeeded = () => {
|
|
|
18
18
|
const streamExtension = _loadStreamExtensionIfNeeded()
|
|
19
19
|
|
|
20
20
|
function hasStreamInsert(insert, model) {
|
|
21
|
-
if (!model) return
|
|
21
|
+
if (!model) return false
|
|
22
|
+
|
|
22
23
|
const name = insert.into.ref ? insert.into.ref[0] : insert.into
|
|
23
24
|
const into = model.definitions[ensureNoDraftsSuffix(name)]
|
|
24
25
|
if (!into) return false
|
|
@@ -38,9 +39,7 @@ function hasStreamInsert(insert, model) {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function hasStreamUpdate(update, model) {
|
|
41
|
-
if (!model)
|
|
42
|
-
return true
|
|
43
|
-
}
|
|
42
|
+
if (!model) return false
|
|
44
43
|
|
|
45
44
|
const entity = model.definitions[ensureNoDraftsSuffix((update.entity.ref && update.entity.ref[0]) || update.entity)]
|
|
46
45
|
if (!entity) return false
|
|
@@ -49,11 +49,15 @@ const _executeHttpRequest = async ({ requestConfig, destination, destinationOpti
|
|
|
49
49
|
requestOptions = { fetchCsrfToken: requestConfig._autoBatch ? csrfInBatch === true : csrf === true }
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
LOG._debug
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
data
|
|
56
|
-
|
|
52
|
+
if (LOG._debug) {
|
|
53
|
+
const req2log = { headers: _sanitizeHeaders({ ...requestConfig.headers }) }
|
|
54
|
+
if (requestConfig.method !== 'GET' && requestConfig.method !== 'DELETE')
|
|
55
|
+
req2log.data = requestConfig.data && SANITIZE_VALUES ? deepSanitize(requestConfig.data) : requestConfig.data
|
|
56
|
+
LOG.debug(
|
|
57
|
+
`${requestConfig.method} ${destination.url || `<${destination.destinationName}>`}${requestConfig.url}`,
|
|
58
|
+
req2log
|
|
59
|
+
)
|
|
60
|
+
}
|
|
57
61
|
|
|
58
62
|
// cloud sdk requires a new mechanism to differentiate the priority of headers
|
|
59
63
|
// "custom" keeps the highest priority as before
|
|
@@ -3,6 +3,7 @@ const cds = require('../_runtime/cds')
|
|
|
3
3
|
const { where2obj, resolveFromSelect } = require('../_runtime/common/utils/cqn')
|
|
4
4
|
const { findCsnTargetFor } = require('../_runtime/common/utils/csn')
|
|
5
5
|
const normalizeTimestamp = require('../_runtime/common/utils/normalizeTimestamp')
|
|
6
|
+
const { rewriteExpandAsterisk } = require('../_runtime/common/utils/rewriteAsterisks')
|
|
6
7
|
|
|
7
8
|
const _addKeysDeep = (keys, keysCollector, ignoreManagedBacklinks) => {
|
|
8
9
|
for (const keyName in keys) {
|
|
@@ -400,7 +401,7 @@ function _addKeys(columns, target) {
|
|
|
400
401
|
}
|
|
401
402
|
|
|
402
403
|
// remove duplicate * in expand (e.g. expand=*,*)
|
|
403
|
-
function
|
|
404
|
+
function _removeDuplicateAsterisk(columns) {
|
|
404
405
|
let hasExpandStar = false
|
|
405
406
|
for (let i = columns.length - 1; i > 0; i--) {
|
|
406
407
|
const column = columns[i]
|
|
@@ -423,7 +424,9 @@ function _processColumns(cqn, target) {
|
|
|
423
424
|
if (target.kind === 'entity') entity = target
|
|
424
425
|
else if (target.kind === 'action' && target.returns?.kind === 'entity') entity = target.returns
|
|
425
426
|
if (!entity) return
|
|
426
|
-
|
|
427
|
+
_removeDuplicateAsterisk(columns)
|
|
428
|
+
|
|
429
|
+
rewriteExpandAsterisk(columns, entity)
|
|
427
430
|
if (cds.env.features.odata_new_parser) _addKeys(columns, entity)
|
|
428
431
|
}
|
|
429
432
|
|
package/libx/rest/RestAdapter.js
CHANGED
|
@@ -194,12 +194,12 @@ const RestAdapter = function (srv) {
|
|
|
194
194
|
|
|
195
195
|
// -----------------------------------------------------------------------------------------
|
|
196
196
|
// error handling
|
|
197
|
-
router.use((err, req, res, next) => {
|
|
197
|
+
router.use(async (err, req, res, next) => {
|
|
198
198
|
// REVISIT: should not be neccessary!
|
|
199
199
|
// request may fail during processing or during commit -> both caught here
|
|
200
200
|
|
|
201
201
|
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
202
|
-
cds.context?.tx?.rollback(err).catch(() => {}) // REVISIT: silently ?!?
|
|
202
|
+
await cds.context?.tx?.rollback(err).catch(() => {}) // REVISIT: silently ?!?
|
|
203
203
|
|
|
204
204
|
next(err)
|
|
205
205
|
})
|
|
@@ -39,13 +39,16 @@ const _log = err => {
|
|
|
39
39
|
module.exports = (err, req, res, next) => {
|
|
40
40
|
const { _srv: srv } = req
|
|
41
41
|
|
|
42
|
+
// REVISIT: invoking service.on('error') handlers needs a cleanup with new protocol adapters!!!
|
|
42
43
|
// invoke srv.on('error', function (err, req) { ... }) here in special situations
|
|
43
44
|
let ctx = cds.context
|
|
44
45
|
if (!ctx) {
|
|
45
46
|
// > error before req was dispatched
|
|
46
47
|
ctx = new cds.Request({ req, res: req.res, user: req.user || new cds.User.Anonymous() })
|
|
48
|
+
for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
|
|
49
|
+
} else if (ctx._tx?._done !== 'rolled back') {
|
|
50
|
+
for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
|
|
47
51
|
}
|
|
48
|
-
for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
|
|
49
52
|
|
|
50
53
|
// log the error (4xx -> warn)
|
|
51
54
|
_log(err)
|
|
@@ -12,7 +12,7 @@ module.exports = async (_req, _res, next) => {
|
|
|
12
12
|
try {
|
|
13
13
|
const req = query
|
|
14
14
|
? new RestRequest({ query, event: operation.name, data, params: _params })
|
|
15
|
-
: new RestRequest({ event: operation.name.replace(`${srv.
|
|
15
|
+
: new RestRequest({ event: operation.name.replace(`${srv.namespace}.`, ''), data, params: _params })
|
|
16
16
|
result = await srv.dispatch(req)
|
|
17
17
|
} catch (e) {
|
|
18
18
|
return next(e)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds",
|
|
3
|
-
"version": "7.0
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"description": "SAP Cloud Application Programming Model - CDS for Node.js",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"keywords": [
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"node": ">=16"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@sap/cds-compiler": "
|
|
37
|
-
"@sap/cds-fiori": "
|
|
36
|
+
"@sap/cds-compiler": "^4",
|
|
37
|
+
"@sap/cds-fiori": "^1",
|
|
38
38
|
"@sap/cds-foss": "^4"
|
|
39
39
|
},
|
|
40
40
|
"cds": {
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
const GraphQLAdapter = require('@cap-js/graphql') // eslint-disable-line cds/no-missing-dependencies
|
|
2
|
-
const express = require ('express') // eslint-disable-line cds/no-missing-dependencies
|
|
3
|
-
|
|
4
|
-
function CDSGraphQLAdapter (options) {
|
|
5
|
-
const { services } = options
|
|
6
|
-
|
|
7
|
-
return express.Router()
|
|
8
|
-
.use (express.json()) //> required in the slug handlers and logger below
|
|
9
|
-
|
|
10
|
-
// convenience slug route for /graphql/srv/entity/id
|
|
11
|
-
.use ('/:path/:entity/:id?(%20:query)?', (req, _, next) => {
|
|
12
|
-
// TODO: add filter by id -> then remove the // eslint-disable-line
|
|
13
|
-
const { entity, query } = req.params // eslint-disable-line no-unused-vars
|
|
14
|
-
if (query) req.body = { query }
|
|
15
|
-
if (entity) req.body.query = req.body.query.replace(/{/, `{ ${entity} {`) +'}'
|
|
16
|
-
next() //> goes on below
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
// convenience slug route for /graphql/srv
|
|
20
|
-
.use ('/:path', (req, res, next) => {
|
|
21
|
-
const srv = services [req.params.path]
|
|
22
|
-
if (req.body) req.body.query = req.body.query.replace(/{/, `{ ${srv.name} {`) +'}'
|
|
23
|
-
next() //> goes on below
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
// the global /graphql route
|
|
27
|
-
.use (new GraphQLAdapter (options))
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
module.exports = CDSGraphQLAdapter
|