@sap/cds 5.8.2 → 5.8.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,32 @@
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 5.8.3 - 2022-03-01
8
+
9
+ ### Fixed
10
+
11
+ - `queries` property for application defined destinations of remote services
12
+ - `cds serve --watch` no longer fails if `@sap/cds-dk` is installed only globally
13
+ - `cds serve` during development longer redirects URLs with similar path segments from different services, like `/service/one` and `/service`
14
+ - `cds deploy --to sqlite` now ignores a `_texts.csv` file again if there is a language-specific file like `_texts_en.csv` present
15
+ - Using logical blocks (surrounded with `(` and `)`) in ON-conditions of unmanaged associations and compositions
16
+ - Skip "with parameters" clause if no order by clause or all columns in the order by clause are not strings when using parametrized views on hana
17
+ - Limited support for binary data in OData
18
+ + Using of `base64` string values in `WHERE IN` on hana
19
+ + `base64url` values in `@odata.context` annotation
20
+ - `cds.context` is set in GraphQL adapter
21
+ - Using payloads with `@odata.type` annotating primitive properties no longer crashes the application. `#` in type value may be ommitted. Example:
22
+ ```json
23
+ {
24
+ "ID": 201,
25
+ "title@odata.type": "#String",
26
+ "title": "Wuthering Heights",
27
+ "stock@odata.type": "Int32",
28
+ "stock": 12
29
+ }
30
+ ```
31
+ - Unicode support for i18n bundles
32
+
7
33
  ## Version 5.8.2 - 2022-02-22
8
34
 
9
35
  ### Fixed
@@ -36,7 +62,7 @@
36
62
 
37
63
  ### Added
38
64
 
39
- - Custom `server.js` don't have to export `cds.server` anymore -> we use that by default now.
65
+ - Custom `server.js` don't have to export `cds.server` anymore -> we use that by default now.
40
66
  - In `cds.requires`: Support to replace primitive values with objects
41
67
  - Support filter functions on renamed properties from external service
42
68
  - Results of database queries use `big.js` for values of type `cds.Decimal` and `cds.Integer64` if enabled via `cds.env.features.bigjs`
@@ -101,6 +127,12 @@
101
127
  + Request data properties of types `cds.Date`, `cds.DateTime` and `cds.Timestamp` are converted accordingly to OData V2 specification
102
128
  + Response data properties of types `cds.Decimal`, `cds.DecimalFloat` (deprecated) and `cds.Integer64` are handled properly when using `Accept` header with `IEEE754Compatible=true/false` and `ExponentialDecimals=true/false` format parameters
103
129
 
130
+ ## Version 5.7.6 - 2022-02-23
131
+
132
+ ### Fixed
133
+
134
+ - `draftActivate` action does not return children if not requested
135
+
104
136
  ## Version 5.7.5 - 2022-01-14
105
137
 
106
138
  ### Fixed
@@ -15,6 +15,9 @@ cds.on ('bootstrap', app => {
15
15
  app.use('*/'+uri, ({originalUrl}, res, next)=> { // */browse/webapp[/prefix]/browse/
16
16
  // any of our special URLs ($fiori-, $api-docs) ? -> next
17
17
  if (originalUrl.startsWith('/$')) return next()
18
+ // is there a service starting with the URL? -> next
19
+ if (cds.service.providers.find (srv => originalUrl.startsWith(srv.path))) return next()
20
+
18
21
  // is there a service for '[prefix]/browse' ?
19
22
  const srv = serviceForUri[uri] || (serviceForUri[uri] =
20
23
  cds.service.providers.find (srv => ('/'+uri).lastIndexOf(srv.path) >=0))
package/bin/cds.js CHANGED
@@ -15,13 +15,17 @@ const cli = { //NOSONAR
15
15
  if (!argv.length) argv = process.argv.slice(3)
16
16
  if (cmd in this.Shortcuts) cmd = process.argv[2] = this.Shortcuts[cmd]
17
17
  if (process.env.NODE_ENV !== 'test') this.errorHandlers()
18
- const task = _require ('./'+cmd)
19
- || _require ('@sap/cds-dk/bin/'+cmd) // if dk is in installed modules
20
- || _require (_npmGlobalModules()+'/@sap/cds-dk/bin/'+cmd) // needed for running cds in npm scripts
18
+ const task = this.load(cmd)
21
19
  if (!task) return _requires_cdsdk (cmd)
22
20
  return task.apply (this, this.args(task,argv))
23
21
  },
24
22
 
23
+ load (cmd) {
24
+ return _require ('./'+cmd)
25
+ || _require ('@sap/cds-dk/bin/'+cmd) // if dk is in installed modules
26
+ || _require (_npmGlobalModules()+'/@sap/cds-dk/bin/'+cmd) // needed for running cds in npm scripts
27
+ },
28
+
25
29
  args (task, argv) {
26
30
 
27
31
  const { options:o=[], flags:f=[], shortcuts:s=[] } = task
package/bin/serve.js CHANGED
@@ -143,7 +143,7 @@ async function serve (all=[], o={}) { // NOSONAR
143
143
 
144
144
  // IMPORTANT: never load any @sap/cds modules before the chdir above happened!
145
145
  // handle --watch and --project
146
- if (o.watch) return _watch (o.project,o) // cds serve --watch <project>
146
+ if (o.watch) return _watch.call(this, o.project,o) // cds serve --watch <project>
147
147
  if (o.project) _chdir_to (o.project) // cds run --project <project>
148
148
  if (!o.silent) _prepare_logging ()
149
149
 
@@ -240,7 +240,7 @@ function _prepare_logging () { // NOSONAR
240
240
  /** handles --watch option */
241
241
  function _watch (project,o) {
242
242
  o.args = process.argv.slice(2) .filter (a => a !== '--watch' && a !== '-w')
243
- return require('@sap/cds-dk/bin/watch')([project],o)
243
+ return this.load('watch')([project],o)
244
244
  }
245
245
 
246
246
 
package/lib/deploy.js CHANGED
@@ -152,7 +152,7 @@ function init_from_json (db, csn, SILENT) {
152
152
  */
153
153
  function prefer_translated_texts (file, all) {
154
154
  if (/[._]texts\.(json|csv)$/.test (file)) {
155
- const pattern = new RegExp('^'+ path.basename(file) +'_')
155
+ const pattern = new RegExp('^'+ path.parse(file).name +'_')
156
156
  const translated = all.filter (f => pattern.test(f))
157
157
  if (translated.length > 0) {
158
158
  DEBUG && DEBUG (`ignoring '${file}' in favor of [${translated}]`) // eslint-disable-line
@@ -14,6 +14,12 @@ const { setStatusCodeAndHeader, getKeyProperty } = require('../../../../fiori/ut
14
14
  const { toODataResult, postProcess } = require('../utils/result')
15
15
  const { mergeJson } = require('../../../services/utils/compareJson')
16
16
 
17
+ const _isAssocOrCompNotLocalized = (reqTarget, el) => {
18
+ return (
19
+ reqTarget.elements[el].isAssociation && (!reqTarget.texts || reqTarget.elements[el].target !== reqTarget.texts.name)
20
+ )
21
+ }
22
+
17
23
  const _postProcessDraftActivate = async (req, result, service) => {
18
24
  // update req.data (keys needed in readAfterWrite)
19
25
  req.data = result
@@ -25,6 +31,13 @@ const _postProcessDraftActivate = async (req, result, service) => {
25
31
  result.HasActiveEntity = false
26
32
  result.HasDraftEntity = false
27
33
 
34
+ // remove children from result, excluding localized composition 'text'
35
+ if (!cds.env.effective.odata.structs) {
36
+ for (const k in req.target.elements) {
37
+ if (_isAssocOrCompNotLocalized(req.target, k)) delete result[k]
38
+ }
39
+ }
40
+
28
41
  return result
29
42
  }
30
43
 
@@ -50,7 +50,7 @@ class UriHelper {
50
50
  if (value === null) return 'null'
51
51
  if (edmType === EdmPrimitiveTypeKind.String) return "'" + value.replace(REGEXP_SINGLE_QUOTE, "''") + "'"
52
52
  if (edmType === EdmPrimitiveTypeKind.Duration) return "duration'" + value + "'"
53
- if (edmType === EdmPrimitiveTypeKind.Binary) return "binary'" + value + "'"
53
+ if (edmType === EdmPrimitiveTypeKind.Binary) return "binary'" + value.replace(/\//g, '_').replace(/\+/g, '-') + "'"
54
54
  if (edmType.getKind() === EdmTypeKind.DEFINITION) {
55
55
  return UriHelper.toUriLiteral(value, edmType.getUnderlyingType())
56
56
  }
@@ -347,12 +347,9 @@ class ResourceJsonDeserializer {
347
347
  }
348
348
  const expectedTypeName =
349
349
  expectedType.getKind() === EdmTypeKind.PRIMITIVE ? expectedType.getName() : expectedType.getFullQualifiedName()
350
- const typeName =
351
- typeof value === 'string' &&
352
- value.startsWith(isCollection ? '#Collection(' : '#') &&
353
- value.endsWith(isCollection ? ')' : '')
354
- ? value.substring(isCollection ? 12 : 1, value.length - (isCollection ? 1 : 0))
355
- : null
350
+ const typeNameRegex = isCollection ? /^#?Collection\((.*)\)$/ : /^#?(.*)$/
351
+ const matchedTypeName = typeof value === 'string' && value.match(typeNameRegex)
352
+ const typeName = matchedTypeName && matchedTypeName[1]
356
353
  // The type name could be an alias-qualified name; for that case we have to do an EDM look-up.
357
354
  const fullQualifiedName =
358
355
  typeName && typeName.indexOf('.') > 0 && typeName.lastIndexOf('.') < typeName.length - 1
@@ -369,6 +366,8 @@ class ResourceJsonDeserializer {
369
366
  throw new DeserializationError(
370
367
  "The value of '" + name + "' must describe correctly the type '" + expectedType.getFullQualifiedName() + "'."
371
368
  )
369
+ } else {
370
+ delete structureValue[name]
372
371
  }
373
372
  } else if (name.endsWith(JsonAnnotations.BIND) && !isDelta) {
374
373
  const navigationPropertyName = name.substring(0, name.length - JsonAnnotations.BIND.length)
@@ -6,6 +6,7 @@ const AbstractError = commons.errors.AbstractError
6
6
  const UriSyntaxError = commons.errors.UriSyntaxError
7
7
  const IllegalArgumentError = commons.errors.IllegalArgumentError
8
8
  const NotImplementedError = commons.errors.NotImplementedError
9
+ const EdmPrimitiveTypeKind = require('../../odata-commons/edm/EdmPrimitiveTypeKind')
9
10
 
10
11
  /**
11
12
  * UriHelper has utility methods for reading and constructing URIs.
@@ -103,7 +104,9 @@ class UriHelper {
103
104
  for (const key of keys) {
104
105
  url = url ? url + ',' : ''
105
106
  if (keys.length > 1) url += encodeURIComponent(key.name) + '='
106
- url += encodeURIComponent(CommonsUriHelper.toUriLiteral(key.value, key.type))
107
+ url += key.type === EdmPrimitiveTypeKind.Binary
108
+ ? CommonsUriHelper.toUriLiteral(key.value, key.type)
109
+ : encodeURIComponent(CommonsUriHelper.toUriLiteral(key.value, key.type))
107
110
  }
108
111
  return '(' + url + ')'
109
112
  }
@@ -2,7 +2,6 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
 
4
4
  const cds = require('../../cds')
5
- const LOG = cds.log('app')
6
5
 
7
6
  const dirs = (cds.env.i18n && cds.env.i18n.folders) || []
8
7
 
@@ -50,36 +49,8 @@ function init(locale, file) {
50
49
  if (!file) file = findFile(locale)
51
50
  if (!file) return
52
51
 
53
- let raw
54
- try {
55
- raw = fs.readFileSync(file, 'utf-8')
56
- } catch (e) {
57
- if (LOG._warn) {
58
- e.message = `Unable to load file "${file}" for locale "${locale}" due to error: ` + e.message
59
- LOG.warn(e)
60
- }
61
- return
62
- }
63
-
64
- try {
65
- const pairs = raw
66
- .replace(/\r/g, '')
67
- .split(/\n/)
68
- .map(ele => ele.trim())
69
- .filter(ele => ele && !ele.startsWith('#'))
70
- .map(ele => {
71
- const del = ele.indexOf('=')
72
- return [ele.slice(0, del), ele.slice(del + 1)].map(ele => ele.trim())
73
- })
74
- for (const [key, value] of pairs) {
75
- i18ns[locale][key] = value
76
- }
77
- } catch (e) {
78
- if (LOG._warn) {
79
- e.message = `Unable to process file "${file}" for locale "${locale}" due to error: ` + e.message
80
- LOG.warn(e)
81
- }
82
- }
52
+ const props = cds.load.properties(file)
53
+ i18ns[locale] = props
83
54
  }
84
55
 
85
56
  init('default', path.join(__dirname, 'messages.properties'))
@@ -4,9 +4,8 @@ const _toRef = (alias, column) => {
4
4
  }
5
5
 
6
6
  const _adaptRefs = (onCond, path, { select, join }) => {
7
- const adaptedOnCondition = onCond.map(el => {
7
+ const _adaptEl = el => {
8
8
  const ref = el.ref
9
-
10
9
  if (ref) {
11
10
  if (ref[0] === path.join('_') && ref[1]) {
12
11
  return _toRef(select, ref.slice(1))
@@ -18,12 +17,13 @@ const _adaptRefs = (onCond, path, { select, join }) => {
18
17
  }
19
18
 
20
19
  return _toRef(join, ref.slice(0))
20
+ } else if (el.xpr) {
21
+ el.xpr = el.xpr.map(_adaptEl)
21
22
  }
22
23
 
23
24
  return el
24
- })
25
-
26
- return adaptedOnCondition
25
+ }
26
+ return onCond.map(_adaptEl)
27
27
  }
28
28
 
29
29
  const _args = (csnElement, path, aliases) => {
@@ -48,7 +48,7 @@ class CustomSelectBuilder extends SelectBuilder {
48
48
  select.from.ref &&
49
49
  select.from.ref.length === 1 &&
50
50
  // REVISIT this does not work with join and draft!
51
- this._csn.definitions[select.from.ref[0]]
51
+ this._csn.definitions[select.from.ref[0].id || select.from.ref[0]]
52
52
  // TODO FIXME
53
53
  skip =
54
54
  !select.orderBy ||
@@ -51,7 +51,8 @@ function _getOutputParameters(stmt) {
51
51
  const BINARY_TYPES = {
52
52
  12: 'BINARY',
53
53
  13: 'VARBINARY',
54
- 27: 'BLOB'
54
+ 27: 'BLOB',
55
+ 33: 'BSTRING'
55
56
  }
56
57
 
57
58
  function _getBinaries(stmt) {
@@ -67,6 +67,11 @@ const getDestination = (name, credentials) => {
67
67
  throw new Error(`"url" or "destination" property must be configured in "credentials" of "${name}".`)
68
68
  }
69
69
 
70
+ // Cloud SDK wants property "queryParameters" but we have documented "queries"
71
+ if (credentials.queries && !credentials.queryParameters) {
72
+ credentials.queryParameters = credentials.queries
73
+ }
74
+
70
75
  return { name, ...credentials }
71
76
  }
72
77
 
@@ -1,3 +1,5 @@
1
+ const cds = require('../../../../lib')
2
+
1
3
  const { ARGUMENT } = require('../../constants/adapter')
2
4
  const { getArgumentByName, astToEntries } = require('../parse/ast2cqn')
3
5
  const { entriesStructureToEntityStructure } = require('./utils')
@@ -9,7 +11,10 @@ module.exports = async (service, entityFQN, selection) => {
9
11
  const entries = entriesStructureToEntityStructure(service, entityFQN, astToEntries(input))
10
12
  query.entries(entries)
11
13
 
12
- const result = await service.tx(tx => tx.run(query))
14
+ const result = await service.tx(tx => {
15
+ cds.context = tx
16
+ return tx.run(query)
17
+ })
13
18
 
14
19
  return Array.isArray(result) ? result : [result]
15
20
  }
@@ -1,3 +1,5 @@
1
+ const cds = require('../../../../lib')
2
+
1
3
  const { ARGUMENT } = require('../../constants/adapter')
2
4
  const { getArgumentByName, astToWhere } = require('../parse/ast2cqn')
3
5
 
@@ -11,7 +13,10 @@ module.exports = async (service, entityFQN, selection) => {
11
13
 
12
14
  let result
13
15
  try {
14
- result = await service.tx(tx => tx.run(query))
16
+ result = await service.tx(tx => {
17
+ cds.context = tx
18
+ return tx.run(query)
19
+ })
15
20
  } catch (e) {
16
21
  if (e.code === 404) {
17
22
  result = 0
@@ -1,3 +1,5 @@
1
+ const cds = require('../../../../lib')
2
+
1
3
  const { ARGUMENT } = require('../../constants/adapter')
2
4
  const { getArgumentByName, astToColumns, astToWhere, astToOrderBy, astToLimit } = require('../parse/ast2cqn')
3
5
 
@@ -21,5 +23,8 @@ module.exports = async (service, entityFQN, selection) => {
21
23
  query.limit(astToLimit(top, skip))
22
24
  }
23
25
 
24
- return await service.tx(tx => tx.run(query))
26
+ return await service.tx(tx => {
27
+ cds.context = tx
28
+ return tx.run(query)
29
+ })
25
30
  }
@@ -1,3 +1,5 @@
1
+ const cds = require('../../../../lib')
2
+
1
3
  const { ARGUMENT } = require('../../constants/adapter')
2
4
  const { getArgumentByName, astToColumns, astToWhere, astToEntries } = require('../parse/ast2cqn')
3
5
  const { entriesStructureToEntityStructure } = require('./utils')
@@ -24,8 +26,9 @@ module.exports = async (service, entityFQN, selection) => {
24
26
 
25
27
  let resultBeforeUpdate
26
28
  const result = await service.tx(async tx => {
29
+ cds.context = tx
27
30
  // read needs to be done before the update, otherwise the where clause might become invalid (case that properties in where clause are updated by the mutation)
28
- resultBeforeUpdate = await service.tx(tx => tx.run(queryBeforeUpdate))
31
+ resultBeforeUpdate = await tx.run(queryBeforeUpdate)
29
32
  return tx.run(query)
30
33
  })
31
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "5.8.2",
3
+ "version": "5.8.3",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [