@sap/cds 5.5.5 → 5.6.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 +107 -1
- package/apis/services.d.ts +27 -1
- package/app/index.js +22 -11
- package/bin/build/buildTaskFactory.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +1 -1
- package/bin/build/provider/fiori/index.js +1 -1
- package/bin/build/provider/hana/2migration.js +8 -7
- package/bin/build/provider/java-cf/index.js +1 -1
- package/bin/deploy/to-hana/hana.js +1 -17
- package/common.cds +8 -0
- package/lib/compile/to/sql.js +22 -2
- package/lib/connect/bindings.js +2 -1
- package/lib/core/reflect.js +3 -1
- package/lib/env/index.js +175 -41
- package/lib/env/requires.js +16 -1
- package/lib/i18n/localize.js +31 -4
- package/lib/index.js +3 -3
- package/lib/log/format/kibana.js +6 -2
- package/lib/ql/Query.js +1 -0
- package/lib/ql/SELECT.js +15 -8
- package/lib/ql/Whereable.js +5 -0
- package/lib/req/context.js +13 -5
- package/lib/serve/Service-dispatch.js +8 -1
- package/lib/utils/axios.js +7 -0
- package/lib/utils/data.js +1 -1
- package/lib/utils/tests.js +1 -1
- package/libx/_runtime/audit/Service.js +18 -18
- package/libx/_runtime/audit/generic/personal/access.js +1 -1
- package/libx/_runtime/audit/generic/personal/modification.js +3 -2
- package/libx/_runtime/audit/generic/personal/utils.js +23 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +14 -19
- package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
- package/libx/_runtime/cds-services/util/assert.js +29 -13
- package/libx/_runtime/cds.js +2 -1
- package/libx/_runtime/common/aspects/Association.js +72 -0
- package/libx/_runtime/common/aspects/any.js +8 -45
- package/libx/_runtime/common/aspects/entity.js +0 -1
- package/libx/_runtime/common/aspects/relation.js +40 -0
- package/libx/_runtime/common/aspects/utils.js +73 -1
- package/libx/_runtime/common/auth/strategies/utils/uaa.js +1 -12
- package/libx/_runtime/common/composition/data.js +3 -2
- package/libx/_runtime/common/composition/delete.js +3 -1
- package/libx/_runtime/common/composition/tree.js +23 -18
- package/libx/_runtime/common/composition/utils.js +34 -8
- package/libx/_runtime/common/error/frontend.js +6 -1
- package/libx/_runtime/common/generic/auth.js +5 -9
- package/libx/_runtime/common/generic/crud.js +2 -2
- package/libx/_runtime/common/generic/etag.js +11 -8
- package/libx/_runtime/common/generic/input.js +3 -3
- package/libx/_runtime/common/generic/paging.js +9 -5
- package/libx/_runtime/common/generic/put.js +3 -2
- package/libx/_runtime/common/generic/sorting.js +3 -3
- package/libx/_runtime/common/generic/temporal.js +3 -3
- package/libx/_runtime/common/utils/cqn.js +20 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
- package/libx/_runtime/common/utils/csn.js +50 -52
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
- package/libx/_runtime/common/utils/generateOnCond.js +40 -70
- package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
- package/libx/_runtime/common/utils/postProcessing.js +3 -0
- package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
- package/libx/_runtime/common/utils/resolveStructured.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +7 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
- package/libx/_runtime/common/utils/template.js +54 -46
- package/libx/_runtime/db/Service.js +9 -2
- package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
- package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
- package/libx/_runtime/db/generic/create.js +1 -0
- package/libx/_runtime/db/generic/input.js +7 -11
- package/libx/_runtime/db/generic/integrity.js +2 -2
- package/libx/_runtime/db/generic/rewrite.js +2 -5
- package/libx/_runtime/db/generic/update.js +1 -0
- package/libx/_runtime/db/query/read.js +9 -4
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
- package/libx/_runtime/db/sql-builder/annotations.js +1 -0
- package/libx/_runtime/db/utils/columns.js +14 -43
- package/libx/_runtime/fiori/generic/activate.js +3 -2
- package/libx/_runtime/fiori/generic/before.js +2 -2
- package/libx/_runtime/fiori/generic/cancel.js +3 -2
- package/libx/_runtime/fiori/generic/delete.js +3 -2
- package/libx/_runtime/fiori/generic/edit.js +2 -2
- package/libx/_runtime/fiori/generic/new.js +2 -2
- package/libx/_runtime/fiori/generic/patch.js +2 -2
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +17 -63
- package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
- package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
- package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
- package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
- package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
- package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
- package/libx/_runtime/fiori/uiflex/index.js +35 -0
- package/libx/_runtime/fiori/uiflex/utils.js +78 -0
- package/libx/_runtime/fiori/utils/handler.js +3 -13
- package/libx/_runtime/fiori/utils/where.js +6 -1
- package/libx/_runtime/hana/pool.js +12 -11
- package/libx/_runtime/hana/search2cqn4sql.js +34 -43
- package/libx/_runtime/hana/searchToContains.js +3 -3
- package/libx/_runtime/index.js +5 -2
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +9 -1
- package/libx/_runtime/messaging/common-utils/connections.js +11 -14
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
- package/libx/_runtime/messaging/message-queuing.js +18 -0
- package/libx/_runtime/remote/Service.js +14 -2
- package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
- package/libx/_runtime/remote/utils/client.js +117 -23
- package/libx/_runtime/sqlite/Service.js +2 -2
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
- package/libx/gql/GraphQLAdapter.js +33 -0
- package/libx/gql/constants/adapter.js +69 -0
- package/libx/gql/constants/cds.js +18 -0
- package/libx/gql/constants/graphql.js +33 -0
- package/libx/gql/resolvers/crud/create.js +15 -0
- package/libx/gql/resolvers/crud/delete.js +24 -0
- package/libx/gql/resolvers/crud/index.js +6 -0
- package/libx/gql/resolvers/crud/read.js +25 -0
- package/libx/gql/resolvers/crud/update.js +31 -0
- package/libx/gql/resolvers/crud/utils/index.js +36 -0
- package/libx/gql/resolvers/field.js +5 -0
- package/libx/gql/resolvers/index.js +7 -0
- package/libx/gql/resolvers/mutation.js +23 -0
- package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
- package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
- package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
- package/libx/gql/resolvers/parse/ast/index.js +3 -0
- package/libx/gql/resolvers/parse/ast/meta.js +4 -0
- package/libx/gql/resolvers/parse/ast/variable.js +7 -0
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
- package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
- package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
- package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
- package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
- package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
- package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
- package/libx/gql/resolvers/parse/utils/index.js +8 -0
- package/libx/gql/resolvers/query.js +13 -0
- package/libx/gql/resolvers/root.js +34 -0
- package/libx/gql/schema/generate.js +18 -0
- package/libx/gql/schema/index.js +5 -0
- package/libx/gql/schema/mutation.js +76 -0
- package/libx/gql/schema/query.js +108 -0
- package/libx/gql/schema/typeDefMap.js +45 -0
- package/libx/gql/schema/utils/index.js +54 -0
- package/libx/gql/utils/index.js +12 -0
- package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
- package/libx/odata/index.js +80 -0
- package/libx/odata/odata2cqn/afterburner.js +170 -0
- package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
- package/libx/odata/odata2cqn/index.js +3 -0
- package/libx/odata/odata2cqn/parser.js +1 -0
- package/libx/odata/utils/index.js +64 -0
- package/libx/rest/RestAdapter.js +101 -0
- package/libx/rest/RestRequest.js +30 -0
- package/libx/rest/index.js +3 -0
- package/libx/rest/middleware/auth.js +22 -0
- package/libx/rest/middleware/content.js +15 -0
- package/libx/rest/middleware/create.js +40 -0
- package/libx/rest/middleware/delete.js +20 -0
- package/libx/rest/middleware/error.js +56 -0
- package/libx/rest/middleware/operation.js +39 -0
- package/libx/rest/middleware/parse.js +90 -0
- package/libx/rest/middleware/read.js +29 -0
- package/libx/rest/middleware/update.js +42 -0
- package/libx/rest/utils/data.js +65 -0
- package/package.json +4 -1
- package/server.js +20 -2
- package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
- package/libx/_runtime/cds-services/util/auditlog.js +0 -247
- package/libx/_runtime/cds-services/util/xsenv.js +0 -51
- package/libx/_runtime/common/utils/backlinks.js +0 -83
- package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
- package/libx/_runtime/odata/index.js +0 -55
- package/libx/_runtime/odata/odata2cqn.js +0 -1
- package/libx/_runtime/odata/readToCqn.js +0 -129
- package/libx/_runtime/remote/cqn2odata/index.js +0 -2
package/lib/env/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
const { isfile, fs } = require('../utils')
|
|
2
2
|
const DEFAULTS = require('./defaults'), defaults = require.resolve ('./defaults')
|
|
3
|
-
const
|
|
3
|
+
const os_user_home = require('os').homedir()
|
|
4
4
|
const compat = require('./compat')
|
|
5
5
|
const path = require('path')
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
/**
|
|
9
8
|
* Both a config inctance as well as factory for.
|
|
10
9
|
*/
|
|
@@ -30,38 +29,73 @@ class Config {
|
|
|
30
29
|
this._profiles = _determineProfilesFrom (process.env)
|
|
31
30
|
this._profiles._defined = new Set()
|
|
32
31
|
|
|
33
|
-
// compat requires default values
|
|
32
|
+
// 1. set compat requires default values
|
|
34
33
|
if (_context === 'cds' && _defaults) this.add (DEFAULTS, defaults)
|
|
35
34
|
if (_context === 'cds' && _defaults) compat (this)
|
|
36
35
|
if (!_home) return
|
|
37
36
|
|
|
38
|
-
//
|
|
37
|
+
// fill-in defaults to process.env, unless already defined => 4.
|
|
39
38
|
this._add_to_process_env (_home, 'default-env.json')
|
|
40
|
-
|
|
39
|
+
|
|
40
|
+
// additional env for dev => 4.
|
|
41
41
|
if (process.env.NODE_ENV !== 'production') {
|
|
42
42
|
this._add_to_process_env (_home, '.env')
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
const sources = Config.sources(_home, _context)
|
|
46
|
+
|
|
45
47
|
// 2. read config sources in defined order
|
|
46
|
-
|
|
47
|
-
this._load
|
|
48
|
-
|
|
48
|
+
for (const source of sources) {
|
|
49
|
+
this._load(source.path, source.file, source.mapper, this._profiles, false)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 3. read important (!) profiles from config sources in defined order
|
|
53
|
+
const overwriteProfiles = new Set(this.profiles.map( profile => `${profile}!` ).filter( profile => this._profiles._defined.has( profile ) ));
|
|
54
|
+
if (overwriteProfiles.size > 0) {
|
|
55
|
+
for (const source of sources) {
|
|
56
|
+
this._load(source.path, source.file, source.mapper, overwriteProfiles, true)
|
|
57
|
+
}
|
|
49
58
|
}
|
|
50
|
-
this._load (_home, 'package.json', p => p[_context])
|
|
51
|
-
this.add (_process_env4(_context), '{process.env}')
|
|
52
59
|
|
|
53
|
-
//
|
|
60
|
+
// 4. add process env
|
|
61
|
+
this._add_process_env(_context, _home)
|
|
62
|
+
|
|
63
|
+
// 6. link dependant services (through kind/use)
|
|
54
64
|
this._link_required_services()
|
|
55
|
-
//
|
|
65
|
+
// 7. complete service configurations from VCAP
|
|
56
66
|
this._add_vcap_services (process.env.VCAP_SERVICES)
|
|
57
67
|
|
|
58
|
-
//
|
|
68
|
+
// 8. Add compatibility for mtx
|
|
59
69
|
if (this.requires && this.requires.db) {
|
|
60
70
|
if (this.requires.multitenancy !== undefined) {
|
|
61
71
|
Object.defineProperty(this.requires.db, 'multiTenant', { value: !!this.requires.multitenancy })
|
|
62
72
|
}
|
|
63
73
|
else if (this.requires.db.multiTenant !== undefined) this.requires.multitenancy = this.requires.db.multiTenant
|
|
64
74
|
}
|
|
75
|
+
|
|
76
|
+
// Only if feature is enabled
|
|
77
|
+
this._emulate_vcap_services()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get configuration sources
|
|
82
|
+
*
|
|
83
|
+
* @param {string} home Project home
|
|
84
|
+
* @param {string} context configuration context literal
|
|
85
|
+
*/
|
|
86
|
+
static sources(home, context = 'cds', ) {
|
|
87
|
+
if (!home) throw new Error('Missing parameter "home".')
|
|
88
|
+
const user_home = process.env.CDS_USER_HOME || os_user_home
|
|
89
|
+
|
|
90
|
+
let sources = [
|
|
91
|
+
{ name: 'USER_HOME', path: user_home, file: '.cdsrc.json' },
|
|
92
|
+
{ name: 'PROJECT', path: home, file: '.cdsrc.json' },
|
|
93
|
+
{ name: 'PACKAGE', path: home, file: 'package.json', mapper: p => p[context] },
|
|
94
|
+
{ name: 'PRIVATE', path: home, file: '.cdsrc-private.json' }
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
if (context !== 'cds') sources = sources.filter( source => source.name === 'PACKAGE' )
|
|
98
|
+
return sources
|
|
65
99
|
}
|
|
66
100
|
|
|
67
101
|
/**
|
|
@@ -122,7 +156,7 @@ class Config {
|
|
|
122
156
|
* For BAS only: get all defined profiles (could include some from the defaults)
|
|
123
157
|
*/
|
|
124
158
|
get "defined-profiles" () {
|
|
125
|
-
return Array.from (this._profiles._defined)
|
|
159
|
+
return Array.from (new Set(Array.from(this._profiles._defined).map( profile => profile.endsWith("!") ? profile.slice(0, -1) : profile)))
|
|
126
160
|
}
|
|
127
161
|
|
|
128
162
|
get profiles() {
|
|
@@ -137,15 +171,26 @@ class Config {
|
|
|
137
171
|
//
|
|
138
172
|
|
|
139
173
|
|
|
140
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Load from JSON file or directory
|
|
176
|
+
*
|
|
177
|
+
* No profile support!
|
|
178
|
+
*/
|
|
179
|
+
_loadFromPath (_path, _basePath) {
|
|
180
|
+
if (_basePath && !path.isAbsolute(_path)) _path = path.join(_basePath, _path)
|
|
181
|
+
const json = _readJson (_path) || _readFromDir (_path)
|
|
182
|
+
if (json) this.add (json, _path, new Set())
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_load (cwd, file, _conf=o=>o, profiles, profiles_only) {
|
|
141
186
|
const json = _readJson (file = path.join(cwd, file)) // only support JSON
|
|
142
|
-
if (json) this.add (_conf (json), file)
|
|
187
|
+
if (json) this.add (_conf (json), file, profiles, profiles_only)
|
|
143
188
|
}
|
|
144
189
|
|
|
145
|
-
add (conf, /*from:*/ _src) {
|
|
190
|
+
add (conf, /*from:*/ _src, profiles = this._profiles, profiles_only = false) {
|
|
146
191
|
if (!conf) return this
|
|
147
192
|
if (_src) this._sources.push (_src)
|
|
148
|
-
_merge (this, conf,
|
|
193
|
+
_merge (this, conf, profiles, undefined, profiles_only)
|
|
149
194
|
return this
|
|
150
195
|
}
|
|
151
196
|
|
|
@@ -165,14 +210,44 @@ class Config {
|
|
|
165
210
|
}
|
|
166
211
|
}
|
|
167
212
|
|
|
213
|
+
|
|
214
|
+
_add_process_env (prefix, basePath) {
|
|
215
|
+
const {env} = process
|
|
216
|
+
const PREF = prefix.toUpperCase(), my = { CONFIG: PREF+'_CONFIG', ENV: PREF+'_ENV' }
|
|
217
|
+
const configEnvValue = env[my.CONFIG]
|
|
218
|
+
let config
|
|
219
|
+
try {
|
|
220
|
+
// CDS_CONFIG={ /* json */}
|
|
221
|
+
config = JSON.parse (configEnvValue)
|
|
222
|
+
} catch (e) {
|
|
223
|
+
// CDS_CONFIG=/path/to/config.json *OR* CDS_CONFIG=/path/to/config/dir
|
|
224
|
+
if (configEnvValue && typeof configEnvValue === "string") this._loadFromPath (configEnvValue, basePath)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!config) config = {}
|
|
228
|
+
const pref_ = RegExp('^'+prefix+'[._]','i')
|
|
229
|
+
for (let p in env) if (!(p in my) && pref_.test(p)) {
|
|
230
|
+
const key = /[a-z]/.test(p) ? p : p.toLowerCase() //> CDS_FOO_BAR -> cds_foo_bar
|
|
231
|
+
const path = key.slice(prefix.length+1) .split (key[prefix.length]) //> ['foo','bar']
|
|
232
|
+
for (var o=config,next;;) {
|
|
233
|
+
next = path.shift()
|
|
234
|
+
if (!path.length) break
|
|
235
|
+
o = o[next] || (o[next] = {})
|
|
236
|
+
}
|
|
237
|
+
o[next] = _value4(env[p])
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.add(config, '{process.env}')
|
|
241
|
+
}
|
|
242
|
+
|
|
168
243
|
_link_required_services () {
|
|
169
|
-
const { requires } = this
|
|
244
|
+
const { requires } = this, protos = requires && requires._prototypes || {}
|
|
170
245
|
for (let each in requires) requires[each] = _merged (each)
|
|
171
246
|
function _merged (key) {
|
|
172
|
-
const entry = requires[key]
|
|
173
|
-
if (entry._is_merged || entry.kind === key || !(entry.kind in requires)) return entry
|
|
247
|
+
const entry = requires[key] || protos[key]
|
|
248
|
+
if (!entry || entry._is_merged || entry.kind === key || !(entry.kind in requires) && !(entry.kind in protos)) return entry
|
|
174
249
|
const clone = _merge ({}, _merged (entry.kind)) // first apply inherited data
|
|
175
|
-
_merge (clone, entry, false, false
|
|
250
|
+
_merge (clone, entry, false, false) // then apply overridden data
|
|
176
251
|
return Object.defineProperty (clone, '_is_merged', {value:true})
|
|
177
252
|
}
|
|
178
253
|
}
|
|
@@ -190,6 +265,14 @@ class Config {
|
|
|
190
265
|
}
|
|
191
266
|
}
|
|
192
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Build VCAP_SERVICES for compatibility (for example for CloudSDK) or for running
|
|
270
|
+
* locally with credentials (hybrid mode).
|
|
271
|
+
*/
|
|
272
|
+
_emulate_vcap_services() {
|
|
273
|
+
if (!(this.features && this.features.emulate_vcap_services)) return
|
|
274
|
+
process.env.VCAP_SERVICES = JSON.stringify(build_vcap_services(this))
|
|
275
|
+
}
|
|
193
276
|
|
|
194
277
|
//////////////////////////////////////////////////////////////////////////
|
|
195
278
|
//
|
|
@@ -226,7 +309,7 @@ class Config {
|
|
|
226
309
|
/**
|
|
227
310
|
* @returns {Config} dst
|
|
228
311
|
*/
|
|
229
|
-
function _merge (dst, src, _profiles, _cloned) {
|
|
312
|
+
function _merge (dst, src, _profiles, _cloned, _profiles_only = false) {
|
|
230
313
|
const profiled = [], descr = Object.getOwnPropertyDescriptors(src)
|
|
231
314
|
for (let p in descr) {
|
|
232
315
|
const pd = descr[p]
|
|
@@ -239,39 +322,65 @@ function _merge (dst, src, _profiles, _cloned) {
|
|
|
239
322
|
if (_profiles && p[0] === '[') {
|
|
240
323
|
if (_profiles._defined) _profiles._defined.add (p.slice(1,-1))
|
|
241
324
|
if (_profiles.has(p.slice(1,-1)))
|
|
242
|
-
profiled.push (()=> _merge (dst, src[p], _profiles, _cloned))
|
|
325
|
+
profiled.push (()=> _merge (dst, src[p], _profiles, _cloned, false))
|
|
243
326
|
continue
|
|
244
327
|
}
|
|
245
328
|
|
|
246
329
|
const v = pd.value
|
|
247
330
|
if (typeof v === 'object' && !Array.isArray(v)) {
|
|
248
331
|
if (!dst[p]) dst[p] = {}; else if (_cloned) dst[p] = _cloned(dst[p])
|
|
249
|
-
_merge (dst[p], v, _profiles, _cloned)
|
|
332
|
+
_merge (dst[p], v, _profiles, _cloned, _profiles_only)
|
|
250
333
|
continue
|
|
251
334
|
}
|
|
252
335
|
|
|
253
|
-
if (v !== undefined) dst[p] = v
|
|
336
|
+
if (!_profiles_only && v !== undefined) dst[p] = v
|
|
254
337
|
}
|
|
255
338
|
for (let each of profiled) each()
|
|
256
339
|
return dst
|
|
257
340
|
}
|
|
258
341
|
|
|
259
|
-
function
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
342
|
+
function _readFromDir (p, isDir) {
|
|
343
|
+
if (typeof isDir === "undefined") {
|
|
344
|
+
try {
|
|
345
|
+
const entry = fs.statSync(p)
|
|
346
|
+
if (entry.isDirectory()) {
|
|
347
|
+
isDir = true
|
|
348
|
+
} else if (isFile(p, entry)) {
|
|
349
|
+
isDir = false
|
|
350
|
+
} else {
|
|
351
|
+
return undefined
|
|
352
|
+
}
|
|
353
|
+
} catch (e) {
|
|
354
|
+
return undefined
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (isDir) {
|
|
358
|
+
const result = {}
|
|
359
|
+
const entries = fs.readdirSync(p, {withFileTypes: true})
|
|
360
|
+
for (let entry of entries) {
|
|
361
|
+
const entryPath = path.join(p, entry.name)
|
|
362
|
+
if (entry.isDirectory()) {
|
|
363
|
+
result[entry.name] = _readFromDir(entryPath, true)
|
|
364
|
+
} else if (isFile(entryPath, entry)) {
|
|
365
|
+
result[entry.name] = _readFromDir(entryPath, false)
|
|
366
|
+
}
|
|
271
367
|
}
|
|
272
|
-
|
|
368
|
+
return result
|
|
369
|
+
} else {
|
|
370
|
+
return _value4(fs.readFileSync(p, "utf-8"))
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function isFile(p, entry) {
|
|
375
|
+
if (entry.isFile()) return true
|
|
376
|
+
if (entry.isSymbolicLink()) {
|
|
377
|
+
// Kubernetes credentials use symlinks
|
|
378
|
+
const target = fs.realpathSync(p)
|
|
379
|
+
const targetStat = fs.statSync(target)
|
|
380
|
+
|
|
381
|
+
if (targetStat.isFile()) return true
|
|
273
382
|
}
|
|
274
|
-
return
|
|
383
|
+
return false
|
|
275
384
|
}
|
|
276
385
|
|
|
277
386
|
function _value4 (val) {
|
|
@@ -288,6 +397,7 @@ function _add_vcap_services_to (env, vcaps={}) {
|
|
|
288
397
|
let any
|
|
289
398
|
for (let service in env.requires) {
|
|
290
399
|
const conf = env.requires [service]
|
|
400
|
+
if (!conf) continue
|
|
291
401
|
const { credentials } = (
|
|
292
402
|
conf.vcap && _fetch (conf.vcap) || //> alternatives, e.g. { name:'foo', tag:'foo' }
|
|
293
403
|
_fetch ({ name: service }) ||
|
|
@@ -297,7 +407,9 @@ function _add_vcap_services_to (env, vcaps={}) {
|
|
|
297
407
|
{/* not found */}
|
|
298
408
|
)
|
|
299
409
|
// Merge `credentials`. Needed because some app-defined things like `credentials.destination` must survive.
|
|
300
|
-
if (credentials)
|
|
410
|
+
if (credentials) {
|
|
411
|
+
any = conf.credentials = Object.assign ({}, conf.credentials, credentials)
|
|
412
|
+
}
|
|
301
413
|
}
|
|
302
414
|
return any
|
|
303
415
|
|
|
@@ -347,6 +459,28 @@ function set (o,p,value) {
|
|
|
347
459
|
return value
|
|
348
460
|
}
|
|
349
461
|
|
|
462
|
+
function build_vcap_services(env) {
|
|
463
|
+
let v = {}
|
|
464
|
+
let names = new Set()
|
|
465
|
+
|
|
466
|
+
for (const service in env.requires) {
|
|
467
|
+
let { vcap, credentials, binding } = env.requires[service]
|
|
468
|
+
// "binding.vcap" is chosen over "vcap" because it is meta data resolved from the real service (-> cds bind)
|
|
469
|
+
if (binding && binding.vcap) vcap = binding.vcap
|
|
470
|
+
if (vcap && vcap.label && credentials && Object.keys(credentials).length > 0) {
|
|
471
|
+
// Only one entry for a (instance) name. Generate name from label and plan if not given.
|
|
472
|
+
const { label, plan } = vcap
|
|
473
|
+
const name = vcap.name || `instance:${label}:${plan || ""}`
|
|
474
|
+
if (names.has(name)) continue
|
|
475
|
+
names.add(name)
|
|
476
|
+
|
|
477
|
+
if (!v[label]) v[label] = []
|
|
478
|
+
v[label].push(Object.assign({ name }, vcap, { credentials }))
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return v
|
|
483
|
+
}
|
|
350
484
|
|
|
351
485
|
/** @type Config & typeof DEFAULTS */
|
|
352
486
|
module.exports = Config.prototype.for('cds')
|
package/lib/env/requires.js
CHANGED
|
@@ -31,7 +31,16 @@ module.exports = {
|
|
|
31
31
|
"xsuaa-auth": {
|
|
32
32
|
strategy: 'xsuaa',
|
|
33
33
|
},
|
|
34
|
-
destinations:
|
|
34
|
+
destinations: {
|
|
35
|
+
vcap: {
|
|
36
|
+
label: 'destination'
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
xsuaa: {
|
|
40
|
+
vcap: {
|
|
41
|
+
label: 'xsuaa'
|
|
42
|
+
}
|
|
43
|
+
},
|
|
35
44
|
monitoring: undefined,
|
|
36
45
|
logging: undefined,
|
|
37
46
|
audit: undefined,
|
|
@@ -105,6 +114,12 @@ module.exports = {
|
|
|
105
114
|
// model: 'AuditLogService.cds',
|
|
106
115
|
outbox: true,
|
|
107
116
|
vcap: { label: "auditlog" },
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
_prototypes: {
|
|
120
|
+
"uiflex": {
|
|
121
|
+
"model": "@sap/cds/libx/_runtime/fiori/uiflex/extensibility"
|
|
122
|
+
},
|
|
108
123
|
}
|
|
109
124
|
}
|
|
110
125
|
|
package/lib/i18n/localize.js
CHANGED
|
@@ -38,10 +38,14 @@ function localizeString (aString, bundle) {
|
|
|
38
38
|
if (!bundle) return aString
|
|
39
39
|
// quick check for presence of any text key, to avoid expensive operation below
|
|
40
40
|
if (aString.indexOf(TEXT_KEY_MARKER) < 0) return aString
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const isXml = aString.startsWith('<?xml')
|
|
42
|
+
const isJson = /^[{[]/.test(aString)
|
|
43
|
+
return aString.replace (TEXT_KEYS, (_, left='', key, right='') => {
|
|
44
|
+
let val = bundle[key] || key
|
|
45
|
+
if (val && isXml) val = escapeXmlAttr(val)
|
|
46
|
+
else if (val && isJson) val = escapeJson(val)
|
|
47
|
+
return `"${left}${val}${right}"`
|
|
48
|
+
})
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
|
|
@@ -206,4 +210,27 @@ function loadFromCSV (res, lang=DefaultLanguage) {
|
|
|
206
210
|
}
|
|
207
211
|
}
|
|
208
212
|
|
|
213
|
+
// TODO use compiler API for XML escaping
|
|
214
|
+
function escapeXmlAttr (str) {
|
|
215
|
+
// first regex: replace & if not followed by apos; or quot; or gt; or lt; or amp; or #
|
|
216
|
+
// Do not always escape > as it is a marker for {i18n>...} translated string values
|
|
217
|
+
let result = str;
|
|
218
|
+
if (typeof str === 'string') {
|
|
219
|
+
result = str.replace(/&(?!(?:apos|quot|[gl]t|amp);|#)/g, '&')
|
|
220
|
+
.replace(/</g, '<')
|
|
221
|
+
.replace(/"/g, '"')
|
|
222
|
+
.replace(/\r\n|\n/g, '
');
|
|
223
|
+
if (!result.startsWith('{i18n>') && !result.startsWith('{bi18n'))
|
|
224
|
+
result = result.replace(/>/g, '>')
|
|
225
|
+
}
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function escapeJson (str) { return str
|
|
230
|
+
.replace(/"/g, '\\"')
|
|
231
|
+
.replace(/\\t/g, '\\t')
|
|
232
|
+
.replace(/\\n/g, '\\n')
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
209
236
|
/* eslint no-console:off */
|
package/lib/index.js
CHANGED
|
@@ -77,7 +77,7 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
|
|
|
77
77
|
MessagingService: lazy => require('../libx/_runtime/messaging/service.js'),
|
|
78
78
|
DatabaseService: lazy => require('../libx/_runtime/db/Service.js'),
|
|
79
79
|
RemoteService: lazy => require('../libx/_runtime/rest/service.js'),
|
|
80
|
-
odata: require('../libx/
|
|
80
|
+
odata: require('../libx/odata'),
|
|
81
81
|
|
|
82
82
|
// Helpers
|
|
83
83
|
localize: require ('./i18n/localize'),
|
|
@@ -131,10 +131,10 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
|
|
|
131
131
|
|
|
132
132
|
// Check Node.js version
|
|
133
133
|
if (process.env.CDS_STRICT_NODE_VERSION !== 'false' && !process.env['WORKSPACE_ID']) { // FIXME remove as soon as BAS is ready for the Node version check below
|
|
134
|
-
const v = version => { let vv = version.split('.'); return { version, major: +vv[0], minor: +vv[
|
|
134
|
+
const v = version => { let vv = version.split('.'); return { version, major: +vv[0], minor: +vv[1] }}
|
|
135
135
|
const required = v('12.18'), given = v(process.version.match(/^v(\d+\.\d+)/)[1])
|
|
136
136
|
if (given.major < required.major || given.major === required.major && given.minor < required.minor) process.exit (process.stderr.write (`
|
|
137
|
-
Node.js v${required.version} or higher is required for @sap/cds
|
|
137
|
+
Node.js v${required.version} or higher is required for @sap/cds.
|
|
138
138
|
Current v${given.version} does not satisfy this.
|
|
139
139
|
\n`) || 1)
|
|
140
140
|
}
|
package/lib/log/format/kibana.js
CHANGED
|
@@ -7,6 +7,9 @@ const _l2l = { 1: 'error', 2: 'warn', 3: 'info', 4: 'debug', 5: 'trace' }
|
|
|
7
7
|
* log formatter for kibana
|
|
8
8
|
*/
|
|
9
9
|
module.exports = (module, level, ...args) => {
|
|
10
|
+
// config
|
|
11
|
+
const { user: log_user , kibana_custom_fields } = cds.env.log
|
|
12
|
+
|
|
10
13
|
// build the object to log
|
|
11
14
|
const toLog = {
|
|
12
15
|
level: _l2l[level] || 'info',
|
|
@@ -16,9 +19,11 @@ module.exports = (module, level, ...args) => {
|
|
|
16
19
|
|
|
17
20
|
// add correlation
|
|
18
21
|
if (cds.context) {
|
|
19
|
-
const { id, tenant } = cds.context
|
|
22
|
+
const { id, tenant, user } = cds.context
|
|
20
23
|
toLog.correlation_id = id
|
|
21
24
|
if (tenant) toLog.tenant_id = tenant
|
|
25
|
+
// log user id, if configured (data privacy)
|
|
26
|
+
if (user && log_user) toLog.remote_user = user.id
|
|
22
27
|
// add headers, if available, with _ instead of -
|
|
23
28
|
const req = cds.context._ && cds.context._.req
|
|
24
29
|
if (req && req.headers) for (const k in req.headers) toLog[k.replace(/-/g, '_')] = req.headers[k]
|
|
@@ -43,7 +48,6 @@ module.exports = (module, level, ...args) => {
|
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
// kibana custom fields
|
|
46
|
-
const { kibana_custom_fields } = cds.env.log
|
|
47
51
|
if (kibana_custom_fields) {
|
|
48
52
|
const cf = []
|
|
49
53
|
for (const k in kibana_custom_fields) if (toLog[k]) cf.push({ k, v: toLog[k], i: kibana_custom_fields[k] })
|
package/lib/ql/Query.js
CHANGED
package/lib/ql/SELECT.js
CHANGED
|
@@ -61,9 +61,13 @@ module.exports = class SELECT extends Whereable {
|
|
|
61
61
|
from (target, second, third) {
|
|
62
62
|
this.SELECT.from = target === '*' || this._target_ref4 (...arguments)
|
|
63
63
|
if (!target.raw && second) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
if (third) {
|
|
65
|
+
this.byKey(second)
|
|
66
|
+
this.columns(third)
|
|
67
|
+
} else {
|
|
68
|
+
const cols = _columns_or_not (second)
|
|
69
|
+
cols ? this._add('columns',cols) : this.byKey(second)
|
|
70
|
+
}
|
|
67
71
|
}
|
|
68
72
|
return this
|
|
69
73
|
}
|
|
@@ -124,15 +128,18 @@ module.exports = class SELECT extends Whereable {
|
|
|
124
128
|
|
|
125
129
|
|
|
126
130
|
const _columns = (args) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
const x = args[0]
|
|
132
|
+
if (x.raw) {
|
|
133
|
+
if (x[0] === '{') return SELECT_('from X ',args).columns
|
|
134
|
+
else return SELECT_('from X {',args,'}').columns
|
|
135
|
+
} else {
|
|
136
|
+
if (typeof x === 'string' && x[0] === '{') return parse.cql('SELECT from X '+ x).SELECT.columns
|
|
137
|
+
else return _columns_or_not(x) || args.map(_column_expr)
|
|
138
|
+
}
|
|
131
139
|
}
|
|
132
140
|
|
|
133
141
|
const _columns_or_not = (x) => {
|
|
134
142
|
if (typeof x === 'function') return _projection4(x)
|
|
135
|
-
if (typeof x === 'string' && x[0] === '{') return parse.cql('SELECT from X '+ x).SELECT.columns
|
|
136
143
|
if (Array.isArray(x)) return x.map(_column_expr)
|
|
137
144
|
}
|
|
138
145
|
|
package/lib/ql/Whereable.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { error } = require ('../index')
|
|
2
|
+
const cds = require('../index')
|
|
2
3
|
const parse = require('./parse')
|
|
3
4
|
|
|
4
5
|
class Whereable extends require('./Query') {
|
|
@@ -35,6 +36,10 @@ class Whereable extends require('./Query') {
|
|
|
35
36
|
byKey(key) {
|
|
36
37
|
if (typeof key !== 'object') key = { [Object.keys(this._target.keys||{ID:1})[0]]: key }
|
|
37
38
|
if (this.SELECT) this.SELECT.one = true
|
|
39
|
+
if (cds.env.features.keys_into_where) return this.where(key)
|
|
40
|
+
if (this.UPDATE) { this.UPDATE.entity = { ref: [{ id: this.UPDATE.entity, where: predicate4([key]) }] }; return this }
|
|
41
|
+
if (this.SELECT) { this.SELECT.from.ref[this.SELECT.from.ref.length-1] = { id: this.SELECT.from.ref[this.SELECT.from.ref.length-1], where: predicate4([key]) }; return this }
|
|
42
|
+
if (this.DELETE) { this.DELETE.from = { ref: [{ id: this.DELETE.from, where: predicate4([key]) }] }; return this }
|
|
38
43
|
return this.where(key)
|
|
39
44
|
}
|
|
40
45
|
}
|
package/lib/req/context.js
CHANGED
|
@@ -199,22 +199,30 @@ class EventContext {
|
|
|
199
199
|
|
|
200
200
|
|
|
201
201
|
function cds_spawn (o,fn) {
|
|
202
|
-
if (typeof o === 'function'
|
|
202
|
+
if (typeof o === 'function') [ fn, o ] = [ o, fn ]
|
|
203
|
+
if (!o) o = {}
|
|
204
|
+
const em = new EventEmitter()
|
|
203
205
|
const fx = async ()=> {
|
|
204
206
|
// create a new transaction for each run of the background job
|
|
205
207
|
// which inherits from the current event context by default
|
|
206
208
|
const tx = cds.context = cds.tx({...o})
|
|
207
209
|
try {
|
|
208
|
-
await fn(tx)
|
|
210
|
+
const res = await fn(tx)
|
|
209
211
|
await tx.commit()
|
|
212
|
+
for (const handler of em.listeners('succeeded')) await handler(res)
|
|
213
|
+
for (const handler of em.listeners('done')) await handler()
|
|
210
214
|
} catch(e) {
|
|
211
215
|
console.trace (`ERROR occured in background job:`, e) // eslint-disable-line no-console
|
|
212
216
|
await tx.rollback()
|
|
217
|
+
for (const handler of em.listeners('failed')) await handler(e)
|
|
218
|
+
for (const handler of em.listeners('done')) await handler()
|
|
213
219
|
}
|
|
214
220
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
221
|
+
const timer = (
|
|
222
|
+
(o && o.after) ? setTimeout (fx, o.after) :
|
|
223
|
+
(o && o.every) ? setInterval (fx, o.every) :
|
|
224
|
+
setImmediate (fx) )
|
|
225
|
+
return Object.assign(em, { timer })
|
|
218
226
|
}
|
|
219
227
|
|
|
220
228
|
|
|
@@ -33,10 +33,17 @@ exports.dispatch = async function dispatch (req) { //NOSONAR
|
|
|
33
33
|
// Ensure target and fqns
|
|
34
34
|
if (!req.target) _ensure_target (this,req)
|
|
35
35
|
if (typeof req.query === 'object') {
|
|
36
|
-
if (req.query._target !== req.
|
|
36
|
+
if (req.query._target !== req.target) Object.defineProperty (req.query,'_target',{ value:req.target, configurable:true, writable:true })
|
|
37
37
|
if (!req.query._srv) Object.defineProperty (req.query,'_srv',{ value:this, configurable:true, writable:true })
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// REVISIT: Ensure req._.req and req._.res in case of srv.run(query)?!
|
|
41
|
+
/*
|
|
42
|
+
if (this instanceof cds.ApplicationService && !req._.req) {
|
|
43
|
+
// TODO: add req and res to req._ from tx
|
|
44
|
+
}
|
|
45
|
+
*/
|
|
46
|
+
|
|
40
47
|
return this.handle(req)
|
|
41
48
|
}
|
|
42
49
|
|
package/lib/utils/axios.js
CHANGED
|
@@ -36,6 +36,13 @@ const _args = (args) => {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const _error = (e) => {
|
|
39
|
+
if (e.code === 'ECONNREFUSED' && e.port === 80 /*unchanged default port*/) {
|
|
40
|
+
// retain original error properties (code,...)
|
|
41
|
+
e = Object.assign(new Error(e.message +
|
|
42
|
+
'\nIt seems that the server was not started. Make sure to call \'cds.test(...)\' or \'cds.test.run(...)\'.'),e)
|
|
43
|
+
e.stack = null // stack is just clutter here
|
|
44
|
+
throw e
|
|
45
|
+
}
|
|
39
46
|
if (!e.response) throw e
|
|
40
47
|
if (!e.response.data) throw e
|
|
41
48
|
if (!e.response.data.error) throw new Error(e.message + '\n\n' + e.response.data)
|
package/lib/utils/data.js
CHANGED
|
@@ -6,7 +6,7 @@ class DataUtil {
|
|
|
6
6
|
if (!db) db = await cds.connect.to('db')
|
|
7
7
|
if (!this._deletes) {
|
|
8
8
|
this._deletes = []
|
|
9
|
-
for (const entity of db.model.
|
|
9
|
+
for (const entity of db.model.each('entity')) {
|
|
10
10
|
if (!entity.query && entity['@cds.persistence.skip'] !== true) {
|
|
11
11
|
this._deletes.push(cds.ql.DELETE.from(entity))
|
|
12
12
|
}
|
package/lib/utils/tests.js
CHANGED