@sap/cds 7.0.3 → 7.1.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 +54 -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/bin/serve.js +2 -2
- package/lib/compile/for/lean_drafts.js +21 -18
- package/lib/compile/to/srvinfo.js +1 -17
- 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/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 -72
- 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 +2 -2
- package/libx/_runtime/common/generic/input.js +18 -13
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +6 -2
- 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/localized.js +1 -1
- 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 +3 -6
- package/libx/_runtime/fiori/lean-draft.js +3 -2
- package/libx/_runtime/hana/localized.js +1 -1
- package/libx/_runtime/hana/search2cqn4sql.js +3 -1
- package/libx/_runtime/remote/utils/client.js +9 -5
- package/libx/_runtime/sqlite/localized.js +1 -1
- package/libx/odata/afterburner.js +5 -2
- package/libx/rest/middleware/operation.js +1 -1
- package/package.json +3 -3
- package/lib/srv/protocols/graphql.js +0 -30
|
@@ -16,7 +16,7 @@ const _isToConvert = type => type === 'cds.DateTime' || type === 'cds.Timestamp'
|
|
|
16
16
|
|
|
17
17
|
const _convertDateTimeEntry = (entry, element, model) => {
|
|
18
18
|
const { name, type, _target } = element
|
|
19
|
-
if (!(entry[name]
|
|
19
|
+
if (!(entry[name] == null)) {
|
|
20
20
|
if (_isToConvert(type) && entry[name] !== '$now') {
|
|
21
21
|
entry[name] = _convertDateTimeElement(entry[name], element)
|
|
22
22
|
}
|
|
@@ -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)
|
|
@@ -117,7 +115,7 @@ const fioriGenericEdit = async function (req, next) {
|
|
|
117
115
|
for (const q of selectCQNs) {
|
|
118
116
|
const entity = definitions[q.SELECT.from.ref[0]]
|
|
119
117
|
if (entity && !entity.name.match(/\.texts$/)) {
|
|
120
|
-
|
|
118
|
+
q.SELECT.localized = false
|
|
121
119
|
}
|
|
122
120
|
}
|
|
123
121
|
|
|
@@ -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
|
|
|
@@ -967,7 +967,7 @@ async function onEdit(req) {
|
|
|
967
967
|
existingDraft[DRAFT_PARAMS] = draftParams
|
|
968
968
|
|
|
969
969
|
const activeCQN = SELECT.one.from(req.target).columns(cols).where(targetWhere)
|
|
970
|
-
activeCQN.
|
|
970
|
+
activeCQN.SELECT.localized = false
|
|
971
971
|
|
|
972
972
|
const activeCheck = SELECT.one(req.target).columns([1]).where(targetWhere).forUpdate()
|
|
973
973
|
activeCheck[DRAFT_PARAMS] = draftParams
|
|
@@ -979,7 +979,8 @@ async function onEdit(req) {
|
|
|
979
979
|
} catch {} // eslint-disable-line no-empty
|
|
980
980
|
|
|
981
981
|
const [res, draft] = await _promiseAll([
|
|
982
|
-
|
|
982
|
+
// REVISIT: inofficial compat flag just in case it breaks something -> do not document
|
|
983
|
+
cds.env.fiori.read_actives_from_db ? this._datasource.run(activeCQN) : this.run(activeCQN),
|
|
983
984
|
// no user check must be done here...
|
|
984
985
|
existingDraft
|
|
985
986
|
])
|
|
@@ -21,7 +21,7 @@ const localizedHandler = function (req) {
|
|
|
21
21
|
if (!req.locale) return
|
|
22
22
|
|
|
23
23
|
// suppress localization by instruction
|
|
24
|
-
if (query.
|
|
24
|
+
if (query.SELECT.localized === false) return
|
|
25
25
|
|
|
26
26
|
// suppress localization for pure counts
|
|
27
27
|
const columns = query.SELECT.columns
|
|
@@ -60,8 +60,10 @@ const search2cqn4sql = (query, entity, options) => {
|
|
|
60
60
|
subQuery.where(expression)
|
|
61
61
|
|
|
62
62
|
// suppress the localize handler from redirecting the subQuery's target to the localized view
|
|
63
|
-
|
|
63
|
+
subQuery.SELECT.localized = false
|
|
64
|
+
|
|
64
65
|
query.where('exists', subQuery)
|
|
66
|
+
|
|
65
67
|
return query
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -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
|
|
@@ -33,7 +33,7 @@ const sqliteLocalized = function (req) {
|
|
|
33
33
|
if (!req.locale) return
|
|
34
34
|
|
|
35
35
|
// suppress localization by instruction
|
|
36
|
-
if (query.
|
|
36
|
+
if (query.SELECT.localized === false) return
|
|
37
37
|
|
|
38
38
|
// suppress localization for pure counts
|
|
39
39
|
const columns = query.SELECT.columns
|
|
@@ -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
|
|
|
@@ -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.
|
|
3
|
+
"version": "7.1.1",
|
|
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
|