@sap/cds 8.3.0 → 8.3.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 CHANGED
@@ -4,6 +4,15 @@
4
4
  - The format is based on [Keep a Changelog](http://keepachangelog.com/).
5
5
  - This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## Version 8.3.1 - 2024-10-08
8
+
9
+ ### Fixed
10
+
11
+ - Erroneous caching in `cds.validate`
12
+ - Precedence of request headers for `cds.context.id`
13
+ - For `quoted` names, overwrite `@cds.persistence.name` for drafts and localized views properly
14
+ - Do not use hana error code as http status code
15
+
7
16
  ## Version 8.3.0 - 2024-09-30
8
17
 
9
18
  ### Added
@@ -83,6 +83,7 @@ function unfold_csn (m) { // NOSONAR
83
83
  function _add_proxy4 (d, name, callback) {
84
84
  if (name in m.definitions) return DEBUG && DEBUG ('NOT overriding existing:', name)
85
85
  const x = {__proto__:d, name }; DEBUG && DEBUG ('adding proxy:', x)
86
+ if (d['@cds.persistence.name']) x['@cds.persistence.name'] = `localized.${d['@cds.persistence.name']}`
86
87
  Object.defineProperty (m.definitions, name, {value:x,writable:true,configurable:true})
87
88
  if (callback) callback(x)
88
89
  }
@@ -64,6 +64,11 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
64
64
  elements: { ...active.elements, ...Draft.elements },
65
65
  query: undefined
66
66
  }
67
+
68
+ // for quoted names, we need to overwrite the cds.persistence.name of the derived, draft entity
69
+ if (active['@cds.persistence.name'])
70
+ draft['@cds.persistence.name'] = active['@cds.persistence.name'] + '_drafts'
71
+
67
72
  Object.defineProperty(model.definitions, _draftEntity, { value: draft })
68
73
  Object.defineProperty(active, 'drafts', { value: draft })
69
74
  Object.defineProperty(active, 'actives', { value: active })
@@ -241,14 +241,16 @@ class Association extends struct {
241
241
  /** Compositions are like nested entities, validating deep input against their target entity definitions. */
242
242
  class Composition extends entity {
243
243
  validate (data, path, ctx) { if (!data) return
244
- const elements = this._target.elements
245
- const uplinks = {} // statically determine the uplinks for this composition
246
- if (this.on) for (let {ref} of this.on) if (ref?.[0] === this.name) {
247
- const fk = ref[1], fk_ = fk+'_'; uplinks[fk] = true
248
- for (let e in elements) if (e.startsWith(fk_)) uplinks[e] = true
249
- }
250
- this.validate = (data, path, ctx) => super.validate (data, path, ctx, elements, uplinks)
251
- this.validate (data, path, ctx) // call first time
244
+ const _validate = this.own('_validate', () => {
245
+ const elements = this._target.elements
246
+ const uplinks = {} // statically determine the uplinks for this composition
247
+ if (this.on) for (let {ref} of this.on) if (ref?.[0] === this.name) {
248
+ const fk = ref[1], fk_ = fk+'_'; uplinks[fk] = true
249
+ for (let e in elements) if (e.startsWith(fk_)) uplinks[e] = true
250
+ }
251
+ return (data, path, ctx) => super.validate (data, path, ctx, elements, uplinks)
252
+ })
253
+ _validate (data, path, ctx)
252
254
  }
253
255
  }
254
256
 
@@ -312,4 +314,4 @@ $.LargeBinary.prototype .type_check = v => Buffer.isBuffer(v) || typeof v === 's
312
314
  $.LargeString.prototype .type_check = v => Buffer.isBuffer(v) || typeof v === 'string' || v instanceof Readable
313
315
 
314
316
  // Mixin above class extensions to cds.linked.classes
315
- $.mixin ( Decimal, string, $any, action, array, struct, entity, Association, Composition )
317
+ $.mixin ( Decimal, string, $any, action, array, struct, entity, Association, Composition )
@@ -9,7 +9,7 @@ const { EventContext } = cds
9
9
  module.exports = () => {
10
10
  /** @type { import('express').Handler } */
11
11
  return function cds_context (req, res, next) {
12
- const id = req.headers[corr_id] ??= req.headers[req_id] || req.headers[vr_id] || req.headers[crippled_corr_id] || uuid()
12
+ const id = req.headers[corr_id] ??= req.headers[crippled_corr_id] || req.headers[req_id] || req.headers[vr_id] || uuid()
13
13
  const ctx = EventContext.for ({ id, http: { req, res } })
14
14
  res.set ('X-Correlation-ID', id) // Note: we use capitalized style here as that's common standard in HTTP world
15
15
  cds._context.run (ctx, next)
@@ -51,7 +51,7 @@ const _rewriteError = error => {
51
51
  (code.startsWith('SQLITE_CONSTRAINT') && (message.match(/COMMIT/) || message.match(/FOREIGN KEY/))) ||
52
52
  (code === '155' && message.match(/fk constraint violation/))
53
53
  ) {
54
- // > foreign key constaint violation no sqlite/ hana
54
+ // > foreign key constaint violation on sqlite/ hana
55
55
  error.code = '400'
56
56
  error.message = 'FK_CONSTRAINT_VIOLATION'
57
57
  return
@@ -63,17 +63,33 @@ const _rewriteError = error => {
63
63
  }
64
64
  }
65
65
 
66
+ const _isInHttpResponseCodeRange = errorCode => errorCode >= 300 && errorCode <= 599
67
+
68
+ const BAD_REQUEST_ERRORS = new Set(['ENTITY_ALREADY_EXISTS', 'FK_CONSTRAINT_VIOLATION', 'UNIQUE_CONSTRAINT_VIOLATION'])
69
+
66
70
  const _normalize = (err, locale, formatterFn = _getFiltered) => {
67
71
  // REVISIT: code and message rewriting
68
72
  _rewriteError(err)
69
73
 
74
+ const { message: originalMessage } = err
75
+
70
76
  // message (i18n)
71
77
  err.message = getErrorMessage(err, locale)
72
78
 
73
79
  // ensure code is set and a string
74
80
  err.code = String(err.code || 'null')
75
81
 
76
- let statusCode = err.status || err.statusCode || (_isAllowedError(err.code) && err.code)
82
+ // determine status code from error
83
+ let statusCode = err.status || err.statusCode //> REVISIT: why prefer status over statusCode?
84
+ // well-defined bad request errors
85
+ if (!statusCode && BAD_REQUEST_ERRORS.has(originalMessage)) statusCode = 400
86
+ if (!statusCode && _isInHttpResponseCodeRange(err.code)) {
87
+ if ('sqlState' in err) {
88
+ // HANA/ database error -> don't use code as status code
89
+ } else {
90
+ statusCode = err.code
91
+ }
92
+ }
77
93
 
78
94
  // details
79
95
  if (err.details) {
@@ -95,8 +111,6 @@ const _normalize = (err, locale, formatterFn = _getFiltered) => {
95
111
  return { error, statusCode }
96
112
  }
97
113
 
98
- const _isAllowedError = errorCode => errorCode >= 300 && errorCode < 505
99
-
100
114
  // - for one unique value, we use it
101
115
  // - if at least one 5xx exists, we use 500
102
116
  // - else if at least one 4xx exists, we use 400
@@ -278,9 +278,11 @@ const skipToken = (token, cqn) => {
278
278
  const calculateLocationHeader = (target, srv, result) => {
279
279
  const targetName = target.name.replace(`${srv.definition.name}.`, '')
280
280
  const filteredKeys = [...target.keys].filter(k => !k.isAssociation).map(k => k.name)
281
+ if (!filteredKeys.every(k => k in result)) return
282
+
281
283
  const keyValuePairs = filteredKeys.reduce((acc, key) => {
282
284
  const value = result[key]
283
- if (value === undefined) return
285
+ if (value === undefined) return acc
284
286
  if (Buffer.isBuffer(value)) {
285
287
  acc[key] = value.toString('base64')
286
288
  } else {
@@ -290,7 +292,6 @@ const calculateLocationHeader = (target, srv, result) => {
290
292
  }
291
293
  return acc
292
294
  }, {})
293
- if (!keyValuePairs) return
294
295
  let keys
295
296
  const entries = Object.entries(keyValuePairs)
296
297
  if (entries.length === 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "8.3.0",
3
+ "version": "8.3.1",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [