@sap/cds 8.8.3 → 8.9.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 (73) hide show
  1. package/CHANGELOG.md +48 -4
  2. package/_i18n/i18n_en_US_saptrc.properties +3 -0
  3. package/bin/colors.js +2 -0
  4. package/bin/test.js +103 -75
  5. package/eslint.config.mjs +16 -4
  6. package/lib/compile/for/lean_drafts.js +4 -0
  7. package/lib/compile/parse.js +26 -6
  8. package/lib/env/cds-env.js +3 -1
  9. package/lib/env/cds-requires.js +0 -3
  10. package/lib/env/schemas/cds-rc.js +11 -0
  11. package/lib/log/format/aspects/cls.js +2 -1
  12. package/lib/log/format/json.js +1 -1
  13. package/lib/plugins.js +2 -3
  14. package/lib/ql/SELECT.js +2 -1
  15. package/lib/ql/cds-ql.js +2 -0
  16. package/lib/ql/cds.ql-predicates.js +6 -4
  17. package/lib/ql/resolve.js +46 -0
  18. package/lib/req/validate.js +1 -0
  19. package/lib/srv/bindings.js +64 -43
  20. package/lib/srv/cds-connect.js +1 -1
  21. package/lib/srv/cds-serve.js +2 -2
  22. package/lib/srv/middlewares/auth/ias-auth.js +2 -0
  23. package/lib/srv/protocols/http.js +2 -2
  24. package/lib/srv/protocols/index.js +1 -1
  25. package/lib/srv/protocols/odata-v4.js +0 -1
  26. package/lib/srv/srv-tx.js +1 -1
  27. package/lib/test/cds-test.js +3 -4
  28. package/lib/utils/cds-utils.js +19 -19
  29. package/lib/utils/colors.js +46 -45
  30. package/lib/utils/csv-reader.js +5 -5
  31. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -2
  32. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -1
  33. package/libx/_runtime/common/Service.js +4 -2
  34. package/libx/_runtime/common/composition/data.js +1 -2
  35. package/libx/_runtime/common/composition/tree.js +6 -4
  36. package/libx/_runtime/common/generic/sorting.js +6 -2
  37. package/libx/_runtime/common/utils/cqn2cqn4sql.js +6 -7
  38. package/libx/_runtime/common/utils/differ.js +1 -1
  39. package/libx/_runtime/common/utils/draft.js +1 -1
  40. package/libx/_runtime/common/utils/foreignKeyPropagations.js +6 -2
  41. package/libx/_runtime/common/utils/keys.js +13 -84
  42. package/libx/_runtime/common/utils/propagateForeignKeys.js +4 -3
  43. package/libx/_runtime/common/utils/resolveView.js +96 -102
  44. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
  45. package/libx/_runtime/common/utils/stream.js +2 -3
  46. package/libx/_runtime/db/utils/columns.js +1 -1
  47. package/libx/_runtime/fiori/lean-draft.js +11 -7
  48. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  49. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -2
  50. package/libx/_runtime/messaging/file-based.js +6 -6
  51. package/libx/_runtime/messaging/kafka.js +5 -7
  52. package/libx/_runtime/messaging/redis-messaging.js +1 -1
  53. package/libx/_runtime/messaging/service.js +11 -4
  54. package/libx/_runtime/remote/Service.js +13 -5
  55. package/libx/_runtime/remote/utils/client.js +1 -0
  56. package/libx/_runtime/ucl/Service.js +135 -126
  57. package/libx/common/utils/path.js +34 -22
  58. package/libx/odata/middleware/create.js +2 -0
  59. package/libx/odata/middleware/operation.js +8 -2
  60. package/libx/odata/middleware/parse.js +1 -1
  61. package/libx/odata/middleware/stream.js +1 -2
  62. package/libx/odata/middleware/update.js +2 -0
  63. package/libx/odata/parse/afterburner.js +17 -9
  64. package/libx/odata/parse/cqn2odata.js +43 -22
  65. package/libx/odata/parse/grammar.peggy +21 -19
  66. package/libx/odata/parse/parser.js +1 -1
  67. package/libx/odata/utils/metadata.js +8 -2
  68. package/libx/odata/utils/odataBind.js +36 -0
  69. package/libx/outbox/index.js +1 -0
  70. package/libx/rest/middleware/operation.js +9 -8
  71. package/libx/rest/middleware/parse.js +1 -0
  72. package/package.json +3 -3
  73. package/lib/i18n/resources.js +0 -150
@@ -1,56 +1,71 @@
1
1
 
2
- const cds = require ('..'), DEBUG = cds.debug('serve|bindings',{label:'cds'})
3
- const { readFile, readFileSync, writeFile, writeFileSync } = require ('fs')
4
- const [ read, write ] = [ readFile, writeFile ].map(require('util').promisify)
2
+ const cds = require ('..'), {fs} = cds.utils
3
+ const LOG = cds.log('serve|bindings',{label:'cds'})
5
4
  const registry = '~/.cds-services.json'
6
- const filename = registry.replace(/^~/, require('os').homedir())
7
5
 
8
- /** TODO: Add documentation */
9
- module.exports = class Bindings {
6
+ class Bindings {
10
7
 
11
- static get registry(){ return registry }
8
+ provides = {}
9
+ servers = {}
10
+ #bound = {}
12
11
 
13
- static then(r,e) {
14
- const LOG = cds.log('cds.serve', { prefix:'cds' })
15
- const bindings = new Bindings
16
- cds.prependOnceListener ('connect', ()=> LOG._info && LOG.info ('connect using bindings from:', { registry }))
17
- cds.once('listening', ({url})=> bindings.export (cds.service.providers, url))
18
- return bindings.import() .then (r,e)
12
+ then (r,e) {
13
+ delete Bindings.prototype.then // only once per process
14
+ cds.prependOnceListener ('connect', ()=> LOG.info ('connect using bindings from:', { registry }))
15
+ cds.once('listening', server => this.export (cds.service.providers, server.url))
16
+ return this.import() .then (r,e)
19
17
  }
20
18
 
21
- constructor() {
22
- this.provides = {}
23
- this.servers = {}
19
+ bind (service) {
20
+ let required = cds.requires [service]
21
+ let binding = this.provides [required?.service || service]
22
+ if (binding) {
23
+ // in case of cds.requires.Foo = { ... }
24
+ if (typeof required === 'object') required.credentials = {
25
+ ...required.credentials,
26
+ ...binding.credentials
27
+ }
28
+ // in case of cds.requires.Foo = true
29
+ else required = cds.requires[service] = {
30
+ ...cds.requires.kinds [binding.kind],
31
+ ...binding
32
+ }
33
+ // REVISIT: temporary fix to inherit kind as well for mocked odata services
34
+ // otherwise mocking with two services does not work for kind:odata-v2
35
+ if (required.kind === 'odata-v2' || required.kind === 'odata-v4') required.kind = 'odata'
36
+ }
37
+ return required
38
+ }
39
+
40
+ // used by cds.connect
41
+ at (service) {
42
+ return this.#bound [service] ??= this.bind (service)
24
43
  }
25
44
 
26
- async load (sync) {
27
- DEBUG?.('reading bindings from:', registry)
45
+ get registry() {
46
+ return Bindings.registry ??= registry.replace(/^~/, require('os').homedir())
47
+ }
48
+
49
+ async load (read = fs.promises.readFile) {
50
+ LOG.debug ('reading bindings from:', registry)
28
51
  try {
29
- let {cds} = JSON.parse (sync ? readFileSync (filename) : await read (filename))
30
- Object.assign (this,cds)
52
+ let src = read (this.registry)
53
+ let {cds} = JSON.parse (src.then ? await src : src)
54
+ Object.assign (this, cds)
31
55
  }
32
56
  catch { /* ignored */ }
33
57
  return this
34
58
  }
35
- async store (sync) {
36
- DEBUG?.('writing bindings to:', registry)
37
- const json = JSON.stringify ({cds:this},null,' ')
38
- return sync ? writeFileSync (filename, json) : write (filename, json)
59
+
60
+ async store (write = fs.promises.writeFile) {
61
+ LOG.debug ('writing bindings to:', registry)
62
+ const json = JSON.stringify ({ cds: this },null,' ')
63
+ return write (this.registry, json)
39
64
  }
40
65
 
41
66
  async import() {
42
- const required = cds.requires; if (!required) return this
43
- const provided = (await this.load()) .provides
44
- for (let each in required) {
45
- const req = required[each]; if (typeof req !== 'object') continue
46
- const bound = provided [req.service||each]
47
- if (bound) {
48
- Object.assign (req.credentials || (req.credentials = {}), bound.credentials)
49
- // REVISIT: temporary fix to inherit kind as well for mocked odata services
50
- // otherwise mocking with two services does not work for kind:odata-v2
51
- if (req.kind === 'odata-v2' || req.kind === 'odata-v4') req.kind = 'odata'
52
- }
53
- }
67
+ await this.load()
68
+ for (let each in cds.requires) this.bind (each)
54
69
  return this
55
70
  }
56
71
 
@@ -67,23 +82,28 @@ module.exports = class Bindings {
67
82
  // if (each.name in cds.env.requires) continue
68
83
  const options = each.options || {}
69
84
  provides[each.name] = {
70
- kind: options.to || 'odata',
85
+ kind: options.to || each.endpoints[0]?.kind || 'odata',
71
86
  credentials: {
72
87
  ...options.credentials,
73
88
  url: url + each.path
74
89
  },
75
90
  server: pid
76
91
  }
92
+ // if (each.endpoints.length > 1) provides[each.name].other = each.endpoints.slice(1).map(
93
+ // ep => ({ kind: ep.kind, url: url + ep.path })
94
+ // )
77
95
  }
78
- process.on ('exit', ()=>this.purge())
96
+ process.on ('exit', ()=> this.purge())
97
+ cds.on ('shutdown', ()=> this.purge())
79
98
  return this.store()
80
99
  }
81
100
 
82
101
  purge() {
83
- this.load(true)
84
- DEBUG?.('purging bindings from:', registry)
102
+ if (this.done) return; else this.done = true
103
+ this.load (fs.readFileSync)
104
+ LOG.debug ('purging bindings from:', registry)
85
105
  this.cleanup()
86
- this.store(true)
106
+ this.store (fs.writeFileSync)
87
107
  }
88
108
 
89
109
  /**
@@ -100,6 +120,7 @@ module.exports = class Bindings {
100
120
 
101
121
  const {NODE_ENV} = process.env
102
122
  if (NODE_ENV === 'test' || global.it || cds.env.no_bindings) {
103
- module['exports'] = { then: (r) => r() }
123
+ Object.defineProperty (module, 'exports', { value: { at: ()=> undefined }})
124
+ } else {
125
+ module.exports = new Bindings
104
126
  }
105
- /* eslint no-console:off */
@@ -61,7 +61,7 @@ connect.to = (datasource, options) => {
61
61
 
62
62
  function options4 (name, _o) {
63
63
  const [, kind=_o?.kind, url ] = /^(\w+):(.*)/.exec(name) || []
64
- const conf = cds.requires[name] || cds.requires[kind] || cds.requires.kinds[name] || cds.requires.kinds[kind]
64
+ const conf = cds.service.bindings.at(name) || cds.requires[name] || cds.requires[kind] || cds.requires.kinds[name] || cds.requires.kinds[kind]
65
65
  const o = { kind, ...conf, ..._o }
66
66
  if (!o.kind && !o.impl && !o.silent) throw cds.error(
67
67
  conf ? `Configuration for 'cds.requires.${name}' lacks mandatory property 'kind' or 'impl'` :
@@ -35,7 +35,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
35
35
  const loaded = options.then (async ({from}=o) => {
36
36
  if (!from || from === 'all' || from === '*') from = cds.model || '*'
37
37
  if (from.definitions) return from
38
- if (from === '?') try { return await cds.load('*',o) } catch { return }
38
+ if (from === '?') try { return cds.model || await cds.load('*',o) } catch { return }
39
39
  return cds.load(from, {...o, silent:true })
40
40
  })
41
41
 
@@ -97,7 +97,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
97
97
  const { serve } = cds.service.protocols
98
98
  if (!cds.env.features.odata_new_adapter && cds.edmxs) ready = ready.then (()=> cds.edmxs)
99
99
  ready = ready.then (()=> all.forEach (each => {
100
- const d = each.definition
100
+ const d = each.definition || {}
101
101
  if (d['@protocol'] === 'none' || d['@cds.api.ignore']) return each._is_dark = true
102
102
  else serve (each, /*to:*/ app)
103
103
  if (!o.silent) cds.emit ('serving',each)
@@ -57,7 +57,9 @@ module.exports = function ias_auth(config) {
57
57
  const clientid = tokenInfo.getClientId()
58
58
  if (clientid === payload.sub) { //> grant_type === client_credentials or x509
59
59
  const roles = { 'system-user': 1 }
60
+ if (Array.isArray(payload.ias_apis)) payload.ias_apis.forEach(r => (roles[r] = 1))
60
61
  if (clientid === credentials.clientid) roles['internal-user'] = 1
62
+ else delete roles['internal-user']
61
63
  return new cds.User({ id: 'system', roles, tokenInfo })
62
64
  }
63
65
 
@@ -44,7 +44,7 @@ class HttpAdapter {
44
44
 
45
45
  /** Returns a handler to check required roles, or null if no check required. */
46
46
  get requires_check() {
47
- const d = this.service.definition
47
+ const d = this.service.definition; if (!d) return null
48
48
  const roles = d['@requires'] || d['@restrict']?.map(r => r.to).flat().filter(r => r)
49
49
  const required = !roles?.length ? restricted_by_default : Array.isArray(roles) ? roles : [roles]
50
50
  return required && function requires_check (req, res, next) {
@@ -57,7 +57,7 @@ class HttpAdapter {
57
57
 
58
58
  get body_parser_options() {
59
59
  let options = cds.env.server.body_parser
60
- let limit = this.service.definition['@cds.server.body_parser.limit']
60
+ let limit = this.service.definition?.['@cds.server.body_parser.limit']
61
61
  if (limit) options = { ...options, limit }
62
62
  return super.body_parser_options = options
63
63
  }
@@ -96,7 +96,7 @@ class Protocols {
96
96
  * @returns {{ kind:string, path:string }[]} Array of { kind, path } objects
97
97
  */
98
98
  endpoints4 (srv, o = srv.options) {
99
- const def = srv.definition; if (!def) return
99
+ const def = srv.definition || {}
100
100
 
101
101
  // get @protocol annotations from service definition
102
102
  let annos = o?.to || def['@protocol']
@@ -1,6 +1,5 @@
1
1
  const cds = require('../../index')
2
2
  if (cds.env.features.odata_new_adapter) {
3
- cds.log().info('using new OData adapter')
4
3
  module.exports = require('../../../libx/odata/ODataAdapter')
5
4
  } else {
6
5
  module.exports = require('./okra')
package/lib/srv/srv-tx.js CHANGED
@@ -99,7 +99,7 @@ class Transaction {
99
99
  * err is undefined if nested tx (cf. "root.before ('failed', ()=> this.rollback())")
100
100
  */
101
101
  // FIXME: with noa, this.context === cds.context and not the individual cds.Request
102
- if (err) for (const each of this.handlers._error) each.handler.call(this, err, this.context)
102
+ if (err && this.handlers?._error) for (const each of this.handlers._error) each.handler.call(this, err, this.context)
103
103
 
104
104
  if (this.ready) { //> nothing to do if no transaction started at all
105
105
  // don't actually roll back if already committed (e.g., error thrown in on succeeded or on done)
@@ -177,7 +177,7 @@ class Test extends require('./axios') {
177
177
  }}
178
178
  }
179
179
  set expect(x) { super.expect = x }
180
- get expect() { return _expect || this.chai.expect }
180
+ get expect() { return this.chai.expect }
181
181
  get assert() { return this.chai.assert }
182
182
  get should() { return this.chai.should() }
183
183
  }
@@ -191,7 +191,6 @@ Object.setPrototypeOf (exports, Test.prototype)
191
191
 
192
192
 
193
193
  // Provide same global functions for jest and mocha
194
- let _expect = undefined
195
194
  ;(function _support_jest_and_mocha() {
196
195
  const _global = p => Object.getOwnPropertyDescriptor(global,p)?.value
197
196
  const is_jest = _global('beforeAll')
@@ -203,7 +202,7 @@ let _expect = undefined
203
202
  global.afterAll = global.after = (msg,fn) => repl.on?.('exit',fn||msg)
204
203
  global.beforeEach = global.afterEach = ()=>{}
205
204
  global.describe = ()=>{}
206
- global.expect = _expect = require('./expect')
205
+ global.expect = require('./expect')
207
206
 
208
207
  } else if (is_mocha) { // it's mocha
209
208
 
@@ -237,7 +236,7 @@ let _expect = undefined
237
236
  global.afterAll = global.after = (msg,fn) => after(fn||msg)
238
237
  global.beforeEach = beforeEach
239
238
  global.afterEach = afterEach
240
- global.expect = _expect = require('./expect')
239
+ global.expect = require('./expect')
241
240
  // suite was introduced in Node 22
242
241
  suite?.('<next>', ()=>{}) //> to signal the start of a test file
243
242
 
@@ -5,7 +5,7 @@ const cds = require('../index')
5
5
  // eslint-disable-next-line no-unused-vars
6
6
  const _tarLib = () => { try { return require('tar') } catch(_) {} }
7
7
 
8
- module.exports = exports = new class {
8
+ exports = module.exports = new class {
9
9
  get colors() { return super.colors = require('./colors') }
10
10
  get inflect() { return super.inflect = require('./inflect') }
11
11
  get inspect() {
@@ -20,11 +20,11 @@ module.exports = exports = new class {
20
20
  get uuid() { return super.uuid = require('crypto').randomUUID }
21
21
  get yaml() { return super.yaml = require('@sap/cds-foss').yaml }
22
22
  get pool() { return super.pool = require('@sap/cds-foss').pool }
23
- get tar() { return super.tar = process.platform === 'win32' && _tarLib() ? require('./tar-lib') : require('./tar') }
23
+ get tar() { return super.tar = process.platform === 'win32' && _tarLib() ? require('./tar-lib') : require('./tar') }
24
24
  }
25
25
 
26
26
  /** @type {import('node:path')} */
27
- const path = exports.path = require('path'), { dirname, extname, join, resolve, relative } = path
27
+ const path = exports.path = require('path'), { dirname, join, resolve, relative } = path
28
28
 
29
29
  /** @type {import('node:fs')} */
30
30
  const fs = exports.fs = Object.assign (exports,require('fs')) //> for compatibility
@@ -200,17 +200,20 @@ exports.mkdirp = async function (...path) {
200
200
 
201
201
  exports.rmdir = async function (...path) {
202
202
  const d = resolve (cds.root,...path)
203
- return fs.promises.rm (d, {recursive:true})
203
+ await fs.promises.rm (d, {recursive:true})
204
+ return d
204
205
  }
205
206
 
206
207
  exports.rimraf = async function (...path) {
207
208
  const d = resolve (cds.root,...path)
208
- return fs.promises.rm (d, {recursive:true,force:true})
209
+ await fs.promises.rm (d, {recursive:true,force:true})
210
+ return d
209
211
  }
210
212
 
211
213
  exports.rm = async function rm (x) {
212
214
  const y = resolve (cds.root,x)
213
- return fs.promises.rm(y)
215
+ await fs.promises.rm(y)
216
+ return y
214
217
  }
215
218
 
216
219
  exports.find = function find (base, patterns='*', filter=()=>true) {
@@ -284,20 +287,17 @@ exports.deprecated = (fn, { kind = 'Method', old = fn.name+'()', use } = {}) =>
284
287
  exports.csv = require('./csv-reader')
285
288
 
286
289
  /**
287
- * Internal utility to load a file through ESM or CommonJs. TODO find a better place.
290
+ * Loads a file through ESM or CommonJs.
291
+ * @returns { Promise<any> }
288
292
  */
289
- exports._import = id => require(id)
290
- if (process.env.JEST_WORKER_ID === undefined) { // jest's ESM support is experimental: https://jestjs.io/docs/ecmascript-modules
291
- const { pathToFileURL } = require('url')
292
- exports._import = id => {
293
- if (extname(id) === '.ts') {
294
- try {
295
- return require(id) // ts-node w/ ESM not working ootb (cap/issues#14463), so try CommonJS first
296
- } catch (err) {
297
- if (err.code !== 'ERR_REQUIRE_ESM') throw err
298
- // else: this means ts-node is configured w/ an ESM loader, so fall through and try w/ ESM
299
- }
300
- }
293
+ // TODO find a better place.
294
+ exports._import = id => {
295
+ try {
296
+ return require(id) // try CommonJS first
297
+ } catch (err) {
298
+ if (err.code !== 'ERR_REQUIRE_ESM') throw err
299
+ // else try w/ ESM
300
+ const { pathToFileURL } = require('url')
301
301
  return import (pathToFileURL(id).href) // must use a file: URL, esp. on Windows for C:\... paths
302
302
  }
303
303
  }
@@ -1,50 +1,51 @@
1
1
  const enabled = process.stdout.isTTY && !process.env.NO_COLOR || process.env.FORCE_COLOR
2
- const colors = {
2
+ const color = enabled ? ttl => ttl[0] : ()=>''
3
+
4
+ module.exports = {
3
5
  enabled,
4
- RESET: enabled ? '\x1b[0m' : '',
5
- BOLD: enabled ? '\x1b[1m' : '',
6
- BRIGHT: enabled ? '\x1b[1m' : '',
7
- DIMMED: enabled ? '\x1b[2m' : '',
8
- ITALIC: enabled ? '\x1b[3m' : '',
9
- UNDER: enabled ? '\x1b[4m' : '',
10
- BLINK: enabled ? '\x1b[5m' : '',
11
- FLASH: enabled ? '\x1b[6m' : '',
12
- INVERT: enabled ? '\x1b[7m' : '',
13
- BLACK: enabled ? '\x1b[30m' : '',
14
- RED: enabled ? '\x1b[31m' : '',
15
- GREEN: enabled ? '\x1b[32m' : '',
16
- YELLOW: enabled ? '\x1b[33m' : '',
17
- BLUE: enabled ? '\x1b[34m' : '',
18
- PINK: enabled ? '\x1b[35m' : '',
19
- CYAN: enabled ? '\x1b[36m' : '',
20
- LIGHT_GRAY: enabled ? '\x1b[37m' : '',
21
- DEFAULT: enabled ? '\x1b[39m' : '',
22
- GRAY: enabled ? '\x1b[90m' : '',
23
- LIGHT_RED: enabled ? '\x1b[91m' : '',
24
- LIGHT_GREEN: enabled ? '\x1b[92m' : '',
25
- LIGHT_YELLOW: enabled ? '\x1b[93m' : '',
26
- LIGHT_BLUE: enabled ? '\x1b[94m' : '',
27
- LIGHT_PINK: enabled ? '\x1b[95m' : '',
28
- LIGHT_CYAN: enabled ? '\x1b[96m' : '',
29
- WHITE: enabled ? '\x1b[97m' : '',
6
+ RESET: color `\x1b[0m`,
7
+ BOLD: color `\x1b[1m`,
8
+ BRIGHT: color `\x1b[1m`,
9
+ DIMMED: color `\x1b[2m`,
10
+ ITALIC: color `\x1b[3m`,
11
+ UNDER: color `\x1b[4m`,
12
+ BLINK: color `\x1b[5m`,
13
+ FLASH: color `\x1b[6m`,
14
+ INVERT: color `\x1b[7m`,
15
+ BLACK: color `\x1b[30m`,
16
+ RED: color `\x1b[31m`,
17
+ GREEN: color `\x1b[32m`,
18
+ YELLOW: color `\x1b[33m`,
19
+ BLUE: color `\x1b[34m`,
20
+ PINK: color `\x1b[35m`,
21
+ CYAN: color `\x1b[36m`,
22
+ LIGHT_GRAY: color `\x1b[37m`,
23
+ DEFAULT: color `\x1b[39m`,
24
+ GRAY: color `\x1b[90m`,
25
+ LIGHT_RED: color `\x1b[91m`,
26
+ LIGHT_GREEN: color `\x1b[92m`,
27
+ LIGHT_YELLOW: color `\x1b[93m`,
28
+ LIGHT_BLUE: color `\x1b[94m`,
29
+ LIGHT_PINK: color `\x1b[95m`,
30
+ LIGHT_CYAN: color `\x1b[96m`,
31
+ WHITE: color `\x1b[97m`,
30
32
  bg: {
31
- BLACK: enabled ? '\x1b[40m' : '',
32
- RED: enabled ? '\x1b[41m' : '',
33
- GREEN: enabled ? '\x1b[42m' : '',
34
- YELLOW: enabled ? '\x1b[43m' : '',
35
- BLUE: enabled ? '\x1b[44m' : '',
36
- PINK: enabled ? '\x1b[45m' : '',
37
- CYAN: enabled ? '\x1b[46m' : '',
38
- WHITE: enabled ? '\x1b[47m' : '',
39
- DEFAULT: enabled ? '\x1b[49m' : '',
40
- LIGHT_GRAY: enabled ? '\x1b[100m' : '',
41
- LIGHT_RED: enabled ? '\x1b[101m' : '',
42
- LIGHT_GREEN: enabled ? '\x1b[102m' : '',
43
- LIGHT_YELLOW: enabled ? '\x1b[103m' : '',
44
- LIGHT_BLUE: enabled ? '\x1b[104m' : '',
45
- LIGHT_PINK: enabled ? '\x1b[105m' : '',
46
- LIGHT_CYAN: enabled ? '\x1b[106m' : '',
47
- LIGHT_WHITE: enabled ? '\x1b[107m' : '',
33
+ BLACK: color `\x1b[40m`,
34
+ RED: color `\x1b[41m`,
35
+ GREEN: color `\x1b[42m`,
36
+ YELLOW: color `\x1b[43m`,
37
+ BLUE: color `\x1b[44m`,
38
+ PINK: color `\x1b[45m`,
39
+ CYAN: color `\x1b[46m`,
40
+ WHITE: color `\x1b[47m`,
41
+ DEFAULT: color `\x1b[49m`,
42
+ LIGHT_GRAY: color `\x1b[100m`,
43
+ LIGHT_RED: color `\x1b[101m`,
44
+ LIGHT_GREEN: color `\x1b[102m`,
45
+ LIGHT_YELLOW: color `\x1b[103m`,
46
+ LIGHT_BLUE: color `\x1b[104m`,
47
+ LIGHT_PINK: color `\x1b[105m`,
48
+ LIGHT_CYAN: color `\x1b[106m`,
49
+ LIGHT_WHITE: color `\x1b[107m`,
48
50
  },
49
51
  }
50
- module.exports = colors
@@ -3,16 +3,16 @@ const { Readable } = require('stream')
3
3
  const cds = require('../../lib')
4
4
 
5
5
  const SEPARATOR = /[,;\t]/
6
- module.exports = { parse: cds.parse.csv, readHeader, stripComments, serialize }
7
6
 
7
+ exports.parse = cds.parse.csv
8
8
 
9
- function serialize(rows, columns, bom = '\ufeff') {
9
+ exports.serialize = function (rows, columns, bom = '\ufeff') {
10
10
  let csv = bom + (columns || Object.keys(rows[0])).join(';') + "\n"
11
11
  for (let key in rows) csv += `${key};${rows[key]}\r\n`
12
12
  return csv
13
13
  }
14
14
 
15
- async function readHeader(inStream, o = { ignoreComments: true }) {
15
+ exports.readHeader = async function (inStream, o = { ignoreComments: true }) {
16
16
  let delimiter = ';'
17
17
  let cols = []
18
18
  let filtered = false
@@ -33,9 +33,9 @@ async function readHeader(inStream, o = { ignoreComments: true }) {
33
33
  return { cols, delimiter, filtered }
34
34
  }
35
35
 
36
- async function stripComments(file, outStream) {
36
+ exports.stripComments = async function (file, outStream) {
37
37
  // most files don't need filtering, so do a quick check first
38
- const { filtered } = await readHeader(createReadStream(file))
38
+ const { filtered } = await exports.readHeader(createReadStream(file))
39
39
  if (!filtered) return false
40
40
 
41
41
  // buffer whole content so that we can write the out file
@@ -22,7 +22,6 @@ const { resolveStructuredName } = require('../utils/handlerUtils')
22
22
  const getError = require('../../../../common/error')
23
23
  const { getSapMessages } = require('../../../../common/error/frontend')
24
24
  const { getPageSize } = require('../../../../common/generic/paging')
25
- const { getTransition } = require('../../../../common/utils/resolveView')
26
25
  const { Readable } = require('stream')
27
26
 
28
27
  /**
@@ -312,7 +311,7 @@ const _resolveContentProperty = (req, annotName, resolvedProp) => {
312
311
  LOG.warn(
313
312
  `"${annotName}" in entity "${req.target.name}" points to property "${resolvedProp}" which was renamed or is not part of the projection. You must update the annotation value.`
314
313
  )
315
- const mapping = getTransition(req.target, cds.db).mapping
314
+ const mapping = cds.ql.resolve.transitions(req.query, cds.db).mapping
316
315
  const key = [...mapping.entries()].find(({ 1: val }) => val.ref[0] === resolvedProp)
317
316
  return key?.length && key[0]
318
317
  }
@@ -11,7 +11,7 @@ const createToCQN = require('./createToCQN')
11
11
  const deleteToCQN = require('./deleteToCQN')
12
12
 
13
13
  const parseToCqn = (component, service, target, data, odataReq, upsert) => {
14
- let query = cds.odata.parse(odataReq.getIncomingRequest().url, { service })
14
+ let query = cds.odata.parse(odataReq.getIncomingRequest().url, { service, protocol: 'odata' })
15
15
 
16
16
  // for concat
17
17
  if (component === 'READ' && Array.isArray(query)) return query
@@ -1,6 +1,6 @@
1
1
  const cds = require('../cds')
2
2
 
3
- const { resolveView, findQueryTarget } = require('./utils/resolveView')
3
+ const { findQueryTarget } = require('./utils/resolveView')
4
4
  const postProcess = require('./utils/postProcess')
5
5
 
6
6
  /**
@@ -76,10 +76,12 @@ class ApplicationService extends cds.Service {
76
76
  let result
77
77
  let target = req.target
78
78
 
79
+ // REVISIT: We must not allow arbitrary CQNs, so this shouldn't be here!
79
80
  if (!this._requires_resolving(req)) {
80
81
  result = await super.handle(req)
81
82
  } else {
82
- const query = resolveView(req.query, this.model, this)
83
+ const query = cds.ql.resolve(req.query, this)
84
+ if (!query) throw new Error(`Target ${target.name} cannot be resolved for service ${this.name}`)
83
85
  target = findQueryTarget(query) || req.target
84
86
  // REVISIT: get rid of _4odata
85
87
  if (req.query.SELECT?._4odata) Object.defineProperty(query.SELECT, '_4odata', { value: true })
@@ -2,7 +2,6 @@ const { getCompositionTree } = require('./tree')
2
2
  const ctUtils = require('./utils')
3
3
  const { getEntityNameFromUpdateCQN } = require('../utils/cqn')
4
4
  const { ensureNoDraftsSuffix } = require('../utils/draft')
5
- const { getDBTable } = require('../utils/resolveView')
6
5
  const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
7
6
  const cds = require('../../cds')
8
7
  const { DRAFT_COLUMNS_MAP } = require('../constants/draft')
@@ -49,7 +48,7 @@ const _isSameEntity = (cqn, req) => {
49
48
  return false
50
49
  }
51
50
 
52
- const target = getDBTable(req.target)
51
+ const target = cds.ql.resolve.table(req.target)
53
52
  if (target.name !== (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) && target.name !== cqn.UPDATE.entity) {
54
53
  return false
55
54
  }
@@ -1,7 +1,5 @@
1
1
  const cds = require('../../cds')
2
2
 
3
- const { getTransition, getDBTable } = require('../utils/resolveView')
4
-
5
3
  const { prefixForStruct } = require('../../common/utils/csn')
6
4
 
7
5
  /*
@@ -30,7 +28,11 @@ const _resolvedElement = (element, service) => {
30
28
  if (!element.target) return element
31
29
  // skip forbidden view check if association to view with foreign key in target
32
30
  const skipForbiddenViewCheck = element._isAssociationStrict
33
- const { target, mapping } = getTransition(element._target, service, skipForbiddenViewCheck)
31
+ const { target, mapping } = cds.ql.resolve.transitions4db(
32
+ { target: element._target },
33
+ cds.db.tx(service),
34
+ skipForbiddenViewCheck
35
+ ) // must use db service
34
36
  const newElement = { target: target.name, _target: target }
35
37
  Object.setPrototypeOf(newElement, element)
36
38
  if (element.on) {
@@ -164,7 +166,7 @@ const _getCompositionTreeRec = ({
164
166
  const _resolvedEntityName = (entityName, definitions) => {
165
167
  const target = definitions[entityName]
166
168
  if (!target) return entityName
167
- const resolved = getDBTable(target)
169
+ const resolved = cds.ql.resolve.table(target)
168
170
  return resolved.name
169
171
  }
170
172
 
@@ -20,7 +20,7 @@ const _getStaticOrders = req => {
20
20
  const keys = [...(entity.keys || [])].filter(k => !k.isAssociation).map(k => k.name)
21
21
  for (const key of keys) {
22
22
  if (!(key in DRAFT_COLUMNS_MAP) && !defaultOrders.some(o => o.by['='] === key)) {
23
- ordersFromKeys.push({ by: { '=': key } })
23
+ ordersFromKeys.push({ by: { '=': key }, implicit: true })
24
24
  }
25
25
  }
26
26
  }
@@ -53,7 +53,11 @@ const _addDefaultSortOrder = (req, select) => {
53
53
  select.orderBy.push(
54
54
  ...staticOrders
55
55
  .filter(d => !select.orderBy.find(o => o.ref && o.ref.join('_') === d.by['=']))
56
- .map(d => ({ ref: [d.by['=']], sort: d.desc ? 'desc' : 'asc' }))
56
+ .map(d => {
57
+ const orderByRef = { ref: [d.by['=']], sort: d.desc ? 'desc' : 'asc' }
58
+ if (d.implicit) orderByRef.implicit = true
59
+ return orderByRef
60
+ })
57
61
  )
58
62
  }
59
63