@sap/cds 8.2.2 → 8.2.3

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,20 @@
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.2.3 - 2024-09-20
8
+
9
+ ### Changed
10
+
11
+ - All annotations in input data are skipped and removed from the input by `cds.validate()` - as we did in legacy OData adapter
12
+
13
+ ### Fixed
14
+
15
+ - Unmanaged associations are excluded from `@mandatory` checks
16
+ - Properly reject direct requests to `DraftAdministrativeData`
17
+ - Virtual elements annotated with `@Core.MediaType`
18
+ - OData Requests targeting a specific instance and custom handler returns empty array
19
+ - `cds-serve` and `cds-deploy` now set `cds.cli` information
20
+
7
21
  ## Version 8.2.2 - 2024-09-13
8
22
 
9
23
  ### Fixed
package/bin/serve.js CHANGED
@@ -158,6 +158,9 @@ async function serve (all=[], o={}) {
158
158
  if (o.watch) return _watch.call(this, o.project,o) // cds serve --watch <project>
159
159
  if (o.project) _chdir_to (o.project) // cds run --project <project>
160
160
 
161
+ // let plugins know about the CLI
162
+ cds.cli = { command: 'serve', argv: all, options: o }
163
+
161
164
  // Ensure loading plugins before calling cds.env!
162
165
  await cds.plugins
163
166
 
@@ -384,6 +384,7 @@ const _entity4 = (file, csn) => {
384
384
 
385
385
  /** CLI used as via cds-deploy as deployer for PostgreSQL */
386
386
  if (!module.parent) (async function CLI () {
387
+ cds.cli = { command: 'deploy', argv: process.argv.slice(2), options: {} }
387
388
  await cds.plugins // IMPORTANT: that has to go before any call to cds.env, like through cds.deploy or cds.requires below
388
389
  let db = cds.requires.db
389
390
  try {
@@ -400,6 +401,7 @@ if (!module.parent) (async function CLI () {
400
401
  if (o.username) (db.credentials ??= {}).username = o.username
401
402
  if (o.password) (db.credentials ??= {}).password = o.password
402
403
  }
404
+ cds.cli.options = o
403
405
  db = await cds.connect.to(db);
404
406
  db = await cds.deploy('*',o).to(db)
405
407
  } finally {
@@ -46,7 +46,8 @@ class Validation {
46
46
  return filter.length ? `(${filter})` : `[${index}]`
47
47
  }
48
48
 
49
- unknown(e,d) {
49
+ unknown(e,d,input) {
50
+ if (e.startsWith('@')) return delete input[e] //> skip all annotations, like @odata.Type
50
51
  d['@open'] || cds.error (`Property "${e}" does not exist in ${d.name}`, {status:400})
51
52
  }
52
53
  }
@@ -190,12 +191,13 @@ class struct extends $any {
190
191
  if (each.name in skip) continue // skip uplinks in deep inserts -> see Composition.validate()
191
192
  if (each.$struct in data) continue // got struct for flattened element/fk, e.g. {author:{ID:1}}
192
193
  if (each.elements || each.foreignKeys) continue // skip struct-likes as we check flat payloads above, and deep payloads via struct.validate()
194
+ if (each.isAssociation) continue // unmanaged associations are always ignored (no value like)
193
195
  else ctx.error ('ASSERT_NOT_NULL', path_, each.name) // ASSERT_NOT_NULL should be ASSERT_REQUIRED
194
196
  }
195
197
  // check values of given data
196
198
  for (let each in data) { // will work for structured payloads as well as flattened ones with universal CSN
197
199
  let /** @type {$any} */ d = elements[each]
198
- if (!d || typeof d === 'function') ctx.unknown (each, this, data) // `each` might be a method of LinkedDefinitions, like filter, map, some, every, ...
200
+ if (!d) ctx.unknown (each, this, data)
199
201
  else if (ctx.cleanse && d._is_readonly()) delete data[each]
200
202
  else if (d['@cds.validate'] !== false) d.validate (data[each], path_, ctx)
201
203
  }
@@ -310,4 +312,4 @@ $.LargeBinary.prototype .type_check = v => Buffer.isBuffer(v) || typeof v === 's
310
312
  $.LargeString.prototype .type_check = v => Buffer.isBuffer(v) || typeof v === 'string' || v instanceof Readable
311
313
 
312
314
  // Mixin above class extensions to cds.linked.classes
313
- $.mixin ( Decimal, string, $any, action, array, struct, entity, Association, Composition )
315
+ $.mixin ( Decimal, string, $any, action, array, struct, entity, Association, Composition )
@@ -60,7 +60,7 @@ const handleStreamProperties = (target, columns, model) => {
60
60
  _addColumns(target, columns)
61
61
  } else if (col.ref && (type === 'cds.LargeBinary' || (mediaType && !ignoreMediaType))) {
62
62
  if (mediaType) {
63
- _addColumn(name, mediaType, columns, element['@Core.IsURL'], target)
63
+ if (!element.virtual) _addColumn(name, mediaType, columns, element['@Core.IsURL'], target)
64
64
  columns.splice(index, 1)
65
65
  } else if (!cds.env.features.stream_compat) {
66
66
  columns.splice(index, 1)
@@ -781,7 +781,10 @@ const Read = {
781
781
 
782
782
  // DraftAdministrativeData is only accessible via drafts
783
783
  if (_isCount(query)) return run(query)
784
- if (query._target.name.endsWith('.DraftAdministrativeData')) return run(query._drafts)
784
+ if (query._target.name.endsWith('.DraftAdministrativeData')) {
785
+ if (query.SELECT.from.ref?.length === 1) throw new Error('Invalid draft request') // only via drafts
786
+ return run(query._drafts)
787
+ }
785
788
  if (!query._target._isDraftEnabled) return run(query)
786
789
  if (
787
790
  !query.SELECT.groupBy &&
@@ -262,6 +262,11 @@ module.exports = adapter => {
262
262
 
263
263
  const metadata = getODataMetadata(query, { result, isCollection: !one })
264
264
  result = getODataResult(result, metadata, { isCollection: !one, property: _propertyAccess })
265
+
266
+ if (!result) {
267
+ throw Object.assign(new Error('404'), { statusCode: 404 })
268
+ }
269
+
265
270
  res.send(result)
266
271
  })
267
272
  .catch(err => {
@@ -53,13 +53,15 @@ const _rewriteMetadataDeep = result => {
53
53
  * @returns {object} - the odata result
54
54
  */
55
55
  module.exports = function getODataResult(result, metadata, options = {}) {
56
- if (result == null) return ''
56
+ if (result == null) return
57
57
 
58
58
  const { isCollection, property } = options
59
59
 
60
60
  if (isCollection && !Array.isArray(result)) result = [result]
61
61
  else if (!isCollection && Array.isArray(result)) result = result[0]
62
62
 
63
+ if (result === undefined) return
64
+
63
65
  // make sure @odata.context is the first element (per OData spec)
64
66
  const odataResult = {
65
67
  [METADATA.$context]: metadata.context
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "8.2.2",
3
+ "version": "8.2.3",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [