@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.
Files changed (76) hide show
  1. package/CHANGELOG.md +54 -3
  2. package/_i18n/i18n_ar.properties +3 -0
  3. package/_i18n/i18n_cs.properties +4 -1
  4. package/_i18n/i18n_da.properties +3 -0
  5. package/_i18n/i18n_de.properties +3 -0
  6. package/_i18n/i18n_en.properties +3 -0
  7. package/_i18n/i18n_es.properties +3 -0
  8. package/_i18n/i18n_fi.properties +3 -0
  9. package/_i18n/i18n_fr.properties +3 -0
  10. package/_i18n/i18n_it.properties +3 -0
  11. package/_i18n/i18n_ja.properties +3 -0
  12. package/_i18n/i18n_ko.properties +3 -0
  13. package/_i18n/i18n_ms.properties +3 -0
  14. package/_i18n/i18n_nl.properties +3 -0
  15. package/_i18n/i18n_no.properties +3 -0
  16. package/_i18n/i18n_pl.properties +3 -0
  17. package/_i18n/i18n_pt.properties +3 -0
  18. package/_i18n/i18n_ro.properties +3 -0
  19. package/_i18n/i18n_ru.properties +3 -0
  20. package/_i18n/i18n_sv.properties +3 -0
  21. package/_i18n/i18n_th.properties +3 -0
  22. package/_i18n/i18n_zh_CN.properties +3 -0
  23. package/_i18n/i18n_zh_TW.properties +3 -0
  24. package/apis/core.d.ts +26 -30
  25. package/apis/cqn.d.ts +1 -0
  26. package/apis/ql.d.ts +2 -0
  27. package/apis/serve.d.ts +9 -0
  28. package/apis/services.d.ts +3 -2
  29. package/bin/serve.js +2 -2
  30. package/lib/compile/for/lean_drafts.js +21 -18
  31. package/lib/compile/to/srvinfo.js +1 -17
  32. package/lib/dbs/cds-deploy.js +11 -6
  33. package/lib/env/cds-env.js +3 -4
  34. package/lib/env/presets.js +14 -9
  35. package/lib/env/schemas/cds-package.json +3 -1
  36. package/lib/env/schemas/cds-rc.json +0 -4
  37. package/lib/linked/classes.js +112 -12
  38. package/lib/linked/entities.js +3 -0
  39. package/lib/ql/Whereable.js +1 -0
  40. package/lib/srv/cds-serve.js +2 -1
  41. package/lib/srv/protocols/_legacy.js +7 -6
  42. package/lib/srv/protocols/index.js +30 -72
  43. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -4
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/format/ResponseContentNegotiator.js +12 -0
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +3 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/MetadataCache.js +1 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +1 -4
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +10 -4
  49. package/libx/_runtime/cds-services/services/utils/columns.js +8 -2
  50. package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
  51. package/libx/_runtime/common/composition/data.js +49 -29
  52. package/libx/_runtime/common/composition/update.js +0 -1
  53. package/libx/_runtime/common/composition/utils.js +1 -1
  54. package/libx/_runtime/common/generic/crud.js +2 -2
  55. package/libx/_runtime/common/generic/input.js +18 -13
  56. package/libx/_runtime/common/utils/cqn2cqn4sql.js +6 -2
  57. package/libx/_runtime/common/utils/resolveView.js +115 -35
  58. package/libx/_runtime/common/utils/rewriteAsterisks.js +21 -0
  59. package/libx/_runtime/common/utils/search2cqn4sql.js +1 -1
  60. package/libx/_runtime/db/generic/rewrite.js +5 -4
  61. package/libx/_runtime/db/query/read.js +10 -9
  62. package/libx/_runtime/db/query/update.js +9 -18
  63. package/libx/_runtime/db/utils/deep.js +6 -5
  64. package/libx/_runtime/db/utils/localized.js +1 -1
  65. package/libx/_runtime/db/utils/normalizeTimeData.js +1 -1
  66. package/libx/_runtime/fiori/generic/activate.js +14 -19
  67. package/libx/_runtime/fiori/generic/edit.js +3 -6
  68. package/libx/_runtime/fiori/lean-draft.js +3 -2
  69. package/libx/_runtime/hana/localized.js +1 -1
  70. package/libx/_runtime/hana/search2cqn4sql.js +3 -1
  71. package/libx/_runtime/remote/utils/client.js +9 -5
  72. package/libx/_runtime/sqlite/localized.js +1 -1
  73. package/libx/odata/afterburner.js +5 -2
  74. package/libx/rest/middleware/operation.js +1 -1
  75. package/package.json +3 -3
  76. 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] === undefined || entry[name] === null)) {
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
- // REVSIIT: setting data should be part of ql
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 r = new cds.Request({ event, query, data: draftData, _draftMetadata: adminData })
142
+ const request = new cds.Request({ event, query, data: draftData, _draftMetadata: adminData })
147
143
 
148
144
  // REVISIT: should not be necessary
149
- r._ = Object.assign(r._, req._)
150
- r._.params = req.params
151
- r._.query = req.query
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(r)
152
+ result = await this.dispatch(request)
157
153
  } finally {
158
154
  // REVISIT: should not be necessary
159
- if (r.messages) for (const m of r.messages) req.info(m)
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
- Object.defineProperty(q, '_suppressLocalization', { value: true })
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._suppressLocalization = true // in the future we should be able to just set activeCQN.SELECT.localized = false
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
- this._datasource.run(activeCQN),
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._suppressLocalization) return
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
- Object.defineProperty(subQuery, '_suppressLocalization', { value: true })
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
- LOG.debug(`${requestConfig.method} ${destination.url || `<${destination.destinationName}>`}${requestConfig.url}`, {
54
- headers: _sanitizeHeaders({ ...requestConfig.headers }),
55
- data: requestConfig.data && SANITIZE_VALUES ? deepSanitize(requestConfig.data) : requestConfig.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._suppressLocalization) return
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 _removeDuplicateAsterix(columns) {
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
- _removeDuplicateAsterix(columns)
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.name}.`, ''), data, params: _params })
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",
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": ">=3.9",
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