@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +60 -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/lib/compile/for/lean_drafts.js +22 -19
  30. package/lib/compile/to/srvinfo.js +7 -19
  31. package/lib/dbs/cds-deploy.js +11 -6
  32. package/lib/env/cds-env.js +3 -4
  33. package/lib/env/presets.js +14 -9
  34. package/lib/env/schemas/cds-package.json +3 -1
  35. package/lib/env/schemas/cds-rc.json +0 -4
  36. package/lib/linked/classes.js +112 -12
  37. package/lib/linked/entities.js +3 -0
  38. package/lib/linked/models.js +2 -1
  39. package/lib/ql/SELECT.js +1 -0
  40. package/lib/ql/Whereable.js +1 -0
  41. package/lib/srv/cds-serve.js +2 -1
  42. package/lib/srv/protocols/_legacy.js +7 -6
  43. package/lib/srv/protocols/index.js +30 -55
  44. package/lib/utils/tar.js +2 -2
  45. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -4
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/format/ResponseContentNegotiator.js +12 -0
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +3 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/MetadataCache.js +1 -1
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +1 -4
  51. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +10 -4
  52. package/libx/_runtime/cds-services/services/utils/columns.js +8 -2
  53. package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
  54. package/libx/_runtime/common/composition/data.js +49 -29
  55. package/libx/_runtime/common/composition/update.js +0 -1
  56. package/libx/_runtime/common/composition/utils.js +1 -1
  57. package/libx/_runtime/common/generic/crud.js +1 -1
  58. package/libx/_runtime/common/generic/input.js +18 -13
  59. package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
  60. package/libx/_runtime/common/utils/resolveView.js +115 -35
  61. package/libx/_runtime/common/utils/rewriteAsterisks.js +21 -0
  62. package/libx/_runtime/common/utils/search2cqn4sql.js +1 -1
  63. package/libx/_runtime/db/generic/rewrite.js +5 -4
  64. package/libx/_runtime/db/query/read.js +10 -9
  65. package/libx/_runtime/db/query/update.js +9 -18
  66. package/libx/_runtime/db/utils/deep.js +6 -5
  67. package/libx/_runtime/db/utils/normalizeTimeData.js +1 -1
  68. package/libx/_runtime/fiori/generic/activate.js +14 -19
  69. package/libx/_runtime/fiori/generic/edit.js +2 -5
  70. package/libx/_runtime/hana/streaming.js +3 -4
  71. package/libx/_runtime/remote/utils/client.js +9 -5
  72. package/libx/odata/afterburner.js +5 -2
  73. package/libx/rest/RestAdapter.js +2 -2
  74. package/libx/rest/middleware/error.js +4 -1
  75. package/libx/rest/middleware/operation.js +1 -1
  76. package/package.json +3 -3
  77. 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
- // 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)
@@ -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 true
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
- 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
@@ -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
 
@@ -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.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.2",
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": ">=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