@sap/cds 8.8.2 → 8.9.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.
- package/CHANGELOG.md +46 -4
- package/_i18n/i18n_en_US_saptrc.properties +3 -0
- package/bin/colors.js +2 -0
- package/bin/test.js +103 -75
- package/eslint.config.mjs +16 -4
- package/lib/compile/for/lean_drafts.js +4 -0
- package/lib/compile/parse.js +26 -6
- package/lib/env/cds-env.js +3 -1
- package/lib/env/cds-requires.js +0 -3
- package/lib/env/schemas/cds-rc.js +11 -0
- package/lib/log/format/aspects/cls.js +2 -1
- package/lib/log/format/json.js +1 -1
- package/lib/plugins.js +2 -3
- package/lib/ql/SELECT.js +2 -1
- package/lib/ql/cds-ql.js +2 -0
- package/lib/ql/cds.ql-predicates.js +6 -4
- package/lib/ql/resolve.js +46 -0
- package/lib/req/validate.js +1 -0
- package/lib/srv/bindings.js +64 -43
- package/lib/srv/cds-connect.js +1 -1
- package/lib/srv/cds-serve.js +2 -2
- package/lib/srv/middlewares/auth/ias-auth.js +2 -0
- package/lib/srv/protocols/http.js +2 -2
- package/lib/srv/protocols/index.js +1 -1
- package/lib/srv/protocols/odata-v4.js +0 -1
- package/lib/srv/srv-tx.js +1 -1
- package/lib/test/cds-test.js +3 -4
- package/lib/utils/cds-utils.js +19 -19
- package/lib/utils/colors.js +46 -45
- package/lib/utils/csv-reader.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -1
- package/libx/_runtime/common/Service.js +4 -2
- package/libx/_runtime/common/composition/data.js +1 -2
- package/libx/_runtime/common/composition/tree.js +6 -4
- package/libx/_runtime/common/generic/sorting.js +6 -2
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +6 -7
- package/libx/_runtime/common/utils/differ.js +1 -1
- package/libx/_runtime/common/utils/draft.js +1 -1
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +6 -2
- package/libx/_runtime/common/utils/keys.js +13 -84
- package/libx/_runtime/common/utils/propagateForeignKeys.js +4 -3
- package/libx/_runtime/common/utils/resolveView.js +96 -102
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
- package/libx/_runtime/common/utils/stream.js +2 -3
- package/libx/_runtime/db/utils/columns.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +11 -7
- package/libx/_runtime/messaging/common-utils/connections.js +6 -2
- package/libx/_runtime/messaging/kafka.js +3 -4
- package/libx/_runtime/remote/Service.js +13 -5
- package/libx/_runtime/remote/utils/client.js +1 -0
- package/libx/_runtime/ucl/Service.js +135 -126
- package/libx/common/utils/path.js +34 -22
- package/libx/odata/middleware/create.js +2 -0
- package/libx/odata/middleware/operation.js +8 -2
- package/libx/odata/middleware/parse.js +1 -1
- package/libx/odata/middleware/stream.js +1 -2
- package/libx/odata/middleware/update.js +2 -0
- package/libx/odata/parse/afterburner.js +17 -9
- package/libx/odata/parse/cqn2odata.js +3 -1
- package/libx/odata/parse/grammar.peggy +21 -19
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/metadata.js +8 -2
- package/libx/odata/utils/odataBind.js +36 -0
- package/libx/outbox/index.js +1 -0
- package/libx/rest/middleware/operation.js +9 -8
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +3 -3
- package/lib/i18n/resources.js +0 -150
package/lib/srv/bindings.js
CHANGED
|
@@ -1,56 +1,71 @@
|
|
|
1
1
|
|
|
2
|
-
const cds = require ('..'),
|
|
3
|
-
const
|
|
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
|
-
|
|
9
|
-
module.exports = class Bindings {
|
|
6
|
+
class Bindings {
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
provides = {}
|
|
9
|
+
servers = {}
|
|
10
|
+
#bound = {}
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
cds.
|
|
17
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
this.
|
|
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
|
-
|
|
27
|
-
|
|
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
|
|
30
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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.
|
|
84
|
-
|
|
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(
|
|
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
|
|
123
|
+
Object.defineProperty (module, 'exports', { value: { at: ()=> undefined }})
|
|
124
|
+
} else {
|
|
125
|
+
module.exports = new Bindings
|
|
104
126
|
}
|
|
105
|
-
/* eslint no-console:off */
|
package/lib/srv/cds-connect.js
CHANGED
|
@@ -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'` :
|
package/lib/srv/cds-serve.js
CHANGED
|
@@ -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
|
|
99
|
+
const def = srv.definition || {}
|
|
100
100
|
|
|
101
101
|
// get @protocol annotations from service definition
|
|
102
102
|
let annos = o?.to || def['@protocol']
|
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)
|
package/lib/test/cds-test.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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 =
|
|
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
|
|
package/lib/utils/cds-utils.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
290
|
+
* Loads a file through ESM or CommonJs.
|
|
291
|
+
* @returns { Promise<any> }
|
|
288
292
|
*/
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
}
|
package/lib/utils/colors.js
CHANGED
|
@@ -1,50 +1,51 @@
|
|
|
1
1
|
const enabled = process.stdout.isTTY && !process.env.NO_COLOR || process.env.FORCE_COLOR
|
|
2
|
-
const
|
|
2
|
+
const color = enabled ? ttl => ttl[0] : ()=>''
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
3
5
|
enabled,
|
|
4
|
-
RESET:
|
|
5
|
-
BOLD:
|
|
6
|
-
BRIGHT:
|
|
7
|
-
DIMMED:
|
|
8
|
-
ITALIC:
|
|
9
|
-
UNDER:
|
|
10
|
-
BLINK:
|
|
11
|
-
FLASH:
|
|
12
|
-
INVERT:
|
|
13
|
-
BLACK:
|
|
14
|
-
RED:
|
|
15
|
-
GREEN:
|
|
16
|
-
YELLOW:
|
|
17
|
-
BLUE:
|
|
18
|
-
PINK:
|
|
19
|
-
CYAN:
|
|
20
|
-
LIGHT_GRAY:
|
|
21
|
-
DEFAULT:
|
|
22
|
-
GRAY:
|
|
23
|
-
LIGHT_RED:
|
|
24
|
-
LIGHT_GREEN:
|
|
25
|
-
LIGHT_YELLOW:
|
|
26
|
-
LIGHT_BLUE:
|
|
27
|
-
LIGHT_PINK:
|
|
28
|
-
LIGHT_CYAN:
|
|
29
|
-
WHITE:
|
|
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:
|
|
32
|
-
RED:
|
|
33
|
-
GREEN:
|
|
34
|
-
YELLOW:
|
|
35
|
-
BLUE:
|
|
36
|
-
PINK:
|
|
37
|
-
CYAN:
|
|
38
|
-
WHITE:
|
|
39
|
-
DEFAULT:
|
|
40
|
-
LIGHT_GRAY:
|
|
41
|
-
LIGHT_RED:
|
|
42
|
-
LIGHT_GREEN:
|
|
43
|
-
LIGHT_YELLOW:
|
|
44
|
-
LIGHT_BLUE:
|
|
45
|
-
LIGHT_PINK:
|
|
46
|
-
LIGHT_CYAN:
|
|
47
|
-
LIGHT_WHITE:
|
|
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
|
package/lib/utils/csv-reader.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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 } =
|
|
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 =
|
|
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 =>
|
|
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
|
|