@sap/cds 7.4.2 → 7.5.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 +94 -0
- package/apis/cds.d.ts +1 -38
- package/apis/core.d.ts +21 -101
- package/apis/cqn.d.ts +18 -76
- package/apis/csn.d.ts +18 -114
- package/apis/events.d.ts +16 -123
- package/apis/internal/inference.d.ts +18 -32
- package/apis/linked.d.ts +18 -97
- package/apis/log.d.ts +19 -164
- package/apis/models.d.ts +18 -180
- package/apis/ql.d.ts +16 -323
- package/apis/reflect.d.ts +32 -0
- package/apis/server.d.ts +18 -135
- package/apis/services.d.ts +18 -380
- package/bin/cds-serve.js +5 -2
- package/bin/serve.js +7 -16
- package/lib/auth/basic-auth.js +3 -1
- package/lib/auth/ias-auth.js +62 -48
- package/lib/auth/ias-claims.js +34 -0
- package/lib/auth/index.js +54 -33
- package/lib/auth/jwt-auth.js +55 -52
- package/lib/compile/cdsc.js +2 -2
- package/lib/compile/to/edm.js +4 -4
- package/lib/compile/to/hdbtabledata.js +5 -8
- package/lib/compile/to/srvinfo.js +2 -2
- package/lib/env/cds-env.js +3 -9
- package/lib/env/cds-requires.js +16 -17
- package/lib/env/compat.js +0 -9
- package/lib/env/defaults.js +17 -6
- package/lib/i18n/localize.js +46 -42
- package/lib/index.js +6 -8
- package/lib/linked/classes.js +7 -118
- package/lib/linked/entities.js +1 -1
- package/lib/log/cds-log.js +15 -10
- package/lib/log/format/aspects/als.js +41 -0
- package/lib/log/format/aspects/cf.js +36 -0
- package/lib/log/format/json.js +96 -0
- package/lib/plugins.js +7 -3
- package/lib/req/context.js +4 -2
- package/lib/srv/cds-connect.js +3 -5
- package/lib/srv/cds-serve.js +13 -26
- package/lib/srv/factory.js +3 -3
- package/lib/srv/middlewares/index.js +0 -2
- package/lib/srv/middlewares/trace.js +2 -3
- package/lib/srv/protocols/_legacy.js +27 -30
- package/lib/srv/protocols/index.js +173 -58
- package/lib/srv/protocols/odata-v4.js +29 -16
- package/lib/srv/srv-api.js +8 -13
- package/lib/srv/srv-handlers.js +14 -14
- package/lib/utils/cds-utils.js +15 -0
- package/libx/_runtime/auth/index.js +4 -5
- package/libx/_runtime/auth/strategies/basic.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +23 -13
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +6 -15
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +10 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +2 -1
- package/libx/_runtime/cds-services/services/utils/columns.js +3 -9
- package/libx/_runtime/cds.js +13 -0
- package/libx/_runtime/common/composition/data.js +3 -0
- package/libx/_runtime/common/composition/delete.js +1 -1
- package/libx/_runtime/common/error/frontend.js +2 -2
- package/libx/_runtime/common/generic/auth/readOnly.js +1 -1
- package/libx/_runtime/common/generic/auth/restrictions.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +4 -5
- package/libx/_runtime/common/utils/csn.js +23 -18
- package/libx/_runtime/common/utils/restrictions.js +6 -15
- package/libx/_runtime/db/generic/input.js +3 -2
- package/libx/_runtime/fiori/generic/readOverDraft.js +2 -5
- package/libx/_runtime/fiori/lean-draft.js +69 -5
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/Outbox.js +3 -8
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
- package/libx/_runtime/messaging/file-based.js +1 -1
- package/libx/_runtime/messaging/service.js +7 -10
- package/libx/_runtime/remote/Service.js +15 -45
- package/libx/_runtime/remote/utils/client.js +20 -33
- package/libx/_runtime/remote/utils/cloudSdkProvider.js +30 -0
- package/libx/_runtime/sqlite/Service.js +2 -2
- package/libx/odata/afterburner.js +29 -21
- package/libx/odata/cqn2odata.js +1 -1
- package/libx/odata/error.js +7 -0
- package/libx/odata/grammar.peggy +16 -20
- package/libx/odata/metadata.js +73 -78
- package/libx/odata/parser.js +1 -1
- package/libx/odata/read.js +94 -0
- package/libx/odata/result.js +91 -0
- package/libx/odata/service-document.js +31 -37
- package/libx/odata/utils.js +2 -1
- package/libx/outbox/index.js +9 -4
- package/libx/rest/RestAdapter.js +68 -67
- package/libx/rest/middleware/create.js +20 -26
- package/libx/rest/middleware/delete.js +5 -3
- package/libx/rest/middleware/error.js +2 -3
- package/libx/rest/middleware/input.js +5 -5
- package/libx/rest/middleware/operation.js +96 -41
- package/libx/rest/middleware/parse.js +4 -6
- package/libx/rest/middleware/payload.js +5 -5
- package/libx/rest/middleware/read.js +11 -17
- package/libx/rest/middleware/update.js +20 -25
- package/package.json +2 -1
- package/server.js +7 -4
- package/srv/outbox.cds +9 -10
- package/apis/env.d.ts +0 -25
- package/apis/test.d.ts +0 -81
- package/apis/utils.d.ts +0 -15
- package/lib/auth/passport-basic.js +0 -14
- package/lib/auth/passport-digest.js +0 -16
- package/lib/env/presets.js +0 -35
- package/lib/log/format/cf.js +0 -16
- package/lib/log/format/kibana.js +0 -92
- package/lib/srv/middlewares/ctx-auth.js +0 -11
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +0 -119
package/lib/i18n/localize.js
CHANGED
|
@@ -6,7 +6,7 @@ const DEBUG = cds.debug('i18n')
|
|
|
6
6
|
const _node_modules = cds.env.cdsc.moduleLookupDirectories.map(d => sep+d.slice(0, -1))
|
|
7
7
|
|
|
8
8
|
module.exports = Object.assign (localize, {
|
|
9
|
-
localize, lookup, bundles4, folders4, folder4, bundle4
|
|
9
|
+
localize, lookup, bundles4, folders4, folder4, bundle4, files4, allLocales4
|
|
10
10
|
})
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -61,11 +61,12 @@ function localize (aString, locale, model = cds.context?.model || cds.model, ext
|
|
|
61
61
|
function bundles4 (model, locales = cds.env.i18n.languages) {
|
|
62
62
|
|
|
63
63
|
const folders = folders4 (model); if (folders.length === 0) return
|
|
64
|
+
const {i18n} = cds.env
|
|
64
65
|
|
|
65
66
|
if (locales.split) locales = locales.split(',')
|
|
66
67
|
if (locales[0] === '*' || locales[0] === 'all') {
|
|
67
68
|
locales = allLocales4 (folders); if (!locales) return {}
|
|
68
|
-
if (!locales.includes(
|
|
69
|
+
if (!locales.includes(i18n.fallback_bundle)) locales.push (i18n.fallback_bundle)
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
DEBUG?.('Languages:', locales)
|
|
@@ -79,23 +80,29 @@ function bundles4 (model, locales = cds.env.i18n.languages) {
|
|
|
79
80
|
})()
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
*/
|
|
87
|
-
function allLocales4 (folders) {
|
|
88
|
-
// find all languages in all folders
|
|
83
|
+
|
|
84
|
+
function files4 (folders_or_model) {
|
|
85
|
+
const {i18n} = cds.env
|
|
86
|
+
const folders = folders_or_model.map ? folders_or_model : folders4(folders_or_model)
|
|
89
87
|
const files = folders.map (folder => readdirSync(folder)
|
|
90
|
-
.filter (e => e.startsWith(
|
|
88
|
+
.filter (e => e.startsWith(i18n.file))
|
|
91
89
|
.map(i18nFile => join (folder, i18nFile))
|
|
92
|
-
).
|
|
93
|
-
|
|
90
|
+
).flat()
|
|
94
91
|
if (files.length === 0) {
|
|
95
92
|
DEBUG?.('No languages for folders:', folders)
|
|
96
93
|
return null
|
|
97
94
|
}
|
|
95
|
+
return files
|
|
96
|
+
}
|
|
98
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Return locales for all bundles found in given folders derived from .json, .properties or .csv files.
|
|
100
|
+
*
|
|
101
|
+
* TODO - .csv file handling seems to be questionable - do we need to check all .csv files additionally for locales ???
|
|
102
|
+
*/
|
|
103
|
+
function allLocales4 (folders_or_model) {
|
|
104
|
+
const files = files4(folders_or_model)
|
|
105
|
+
if (!files) return null
|
|
99
106
|
if (files[0].endsWith('.csv')) {
|
|
100
107
|
return cds.load.csv (files[0])[0].slice(1)
|
|
101
108
|
} else {
|
|
@@ -129,35 +136,29 @@ function bundle4 (model, locale) {
|
|
|
129
136
|
const bundles = model.texts || Object.defineProperty (model,'texts',{value:{}}).texts
|
|
130
137
|
if (locale in bundles) return bundles[locale]
|
|
131
138
|
|
|
132
|
-
const bundle = {}
|
|
133
139
|
const folders = folders4(model)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
function add (lang) {
|
|
149
|
-
for (let each of folders) {
|
|
150
|
-
const suffix = lang === '' ? '' : '_' + lang
|
|
151
|
-
const file = join (each, cds.env.i18n.file), key = file+suffix
|
|
152
|
-
const next = bundle4[key] || (bundle4[key] = (
|
|
153
|
-
loadFromJSON (file, lang) ||
|
|
140
|
+
if (!folders.length) return bundles[locale] = {}
|
|
141
|
+
|
|
142
|
+
const {i18n} = cds.env
|
|
143
|
+
let bundle = null, locales = (
|
|
144
|
+
locale === i18n.fallback_bundle ? [ i18n.fallback_bundle ] :
|
|
145
|
+
locale === i18n.default_language ? [ i18n.fallback_bundle, i18n.default_language ] :
|
|
146
|
+
[ i18n.fallback_bundle, i18n.default_language, locale ]
|
|
147
|
+
)
|
|
148
|
+
for (let each of locales) {
|
|
149
|
+
const b = bundle = Object.create(bundle)
|
|
150
|
+
for (let folder of folders) {
|
|
151
|
+
const file = join (folder, i18n.file), suffix = each ? '_' + each : ''
|
|
152
|
+
const next = bundle4[file + suffix] ??= (
|
|
153
|
+
loadFromJSON (file, each) ||
|
|
154
154
|
cds.load.properties (file + suffix.replace('-','_')) || // e.g. en-UK --> en_UK
|
|
155
155
|
cds.load.properties (file + suffix.match(/\w+/)) || // e.g. en_UK --> en
|
|
156
|
-
loadFromCSV (file,
|
|
157
|
-
)
|
|
158
|
-
Object.assign (
|
|
156
|
+
loadFromCSV (file, each)
|
|
157
|
+
)
|
|
158
|
+
Object.assign (b, next)
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
|
+
return bundles[locale] = bundle
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
/**
|
|
@@ -190,8 +191,9 @@ function folder4 (loc) {
|
|
|
190
191
|
// already cached from a former lookup?
|
|
191
192
|
if (loc in folder4) return folder4[loc]
|
|
192
193
|
// check whether a <loc>/_i18n exists
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
const {i18n} = cds.env
|
|
195
|
+
for (let each of i18n.folders) {
|
|
196
|
+
const f = join (loc, each)
|
|
195
197
|
if (existsSync(f)) return folder4[loc] = f
|
|
196
198
|
}
|
|
197
199
|
//> no --> search up the folder hierarchy up to cds.root, cds.home, or some .../node_modules/<package>
|
|
@@ -202,7 +204,7 @@ function folder4 (loc) {
|
|
|
202
204
|
if (!(
|
|
203
205
|
next.startsWith(cds.root) ||
|
|
204
206
|
next.startsWith(cds.home) ||
|
|
205
|
-
|
|
207
|
+
i18n.root && next.startsWith(i18n.root)
|
|
206
208
|
)) return folder4[loc] = null
|
|
207
209
|
}
|
|
208
210
|
if (!next || next === loc) return folder4[loc] = null
|
|
@@ -212,12 +214,14 @@ function folder4 (loc) {
|
|
|
212
214
|
|
|
213
215
|
|
|
214
216
|
function loadFromJSON (res, lang=cds.env.i18n.default_language) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
let cached = loadFromJSON[res]
|
|
218
|
+
if (!cached) try {
|
|
219
|
+
cached = loadFromJSON[res] = require (resolve (cds.root,res+'.json'))
|
|
218
220
|
} catch(e) {
|
|
219
221
|
if (e.code !== 'MODULE_NOT_FOUND') throw e
|
|
222
|
+
else cached = loadFromJSON[res] = {}
|
|
220
223
|
}
|
|
224
|
+
return cached[lang] || cached[lang.match(/\w+/)?.[0]]
|
|
221
225
|
}
|
|
222
226
|
|
|
223
227
|
function loadFromCSV (res, lang=cds.env.i18n.default_language) {
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
if (process.env.CDS_STRICT_NODE_VERSION !== 'false') require('./utils/check-version')
|
|
2
2
|
|
|
3
|
-
const { extend, lazify, lazified } = require('./lazy')
|
|
4
3
|
const { EventEmitter } = require('node:events')
|
|
4
|
+
const { extend, lazify } = require('./lazy')
|
|
5
5
|
|
|
6
6
|
const cds = module.exports = new class cds extends EventEmitter {
|
|
7
7
|
|
|
@@ -41,13 +41,10 @@ const cds = module.exports = new class cds extends EventEmitter {
|
|
|
41
41
|
get type() { return super.type = this.builtin.classes.type }
|
|
42
42
|
get array() { return super.array = this.builtin.classes.array }
|
|
43
43
|
get struct() { return super.struct = this.builtin.classes.struct }
|
|
44
|
-
get service() { return super.service = extend (this.builtin.classes.service) .with (
|
|
45
|
-
|
|
46
|
-
protocols: lazy => require('./srv/protocols'),
|
|
47
|
-
bindings: lazy => require('./srv/bindings'),
|
|
48
|
-
factory: lazy => require('./srv/factory'),
|
|
44
|
+
get service() { return super.service = extend (this.builtin.classes.service) .with ({
|
|
45
|
+
/** @param {( this:Service, srv:Service )} fn */ impl: fn => fn,
|
|
49
46
|
/** @type Service[] */ providers: []
|
|
50
|
-
})
|
|
47
|
+
})}
|
|
51
48
|
|
|
52
49
|
// Providing and Consuming Services
|
|
53
50
|
/** @type {{ [name:string]: Service }} */
|
|
@@ -57,7 +54,8 @@ const cds = module.exports = new class cds extends EventEmitter {
|
|
|
57
54
|
get server() { return super.server = require('../server') }
|
|
58
55
|
get serve() { return super.serve = require('./srv/cds-serve') }
|
|
59
56
|
get connect() { return super.connect = require('./srv/cds-connect') }
|
|
60
|
-
get outboxed() { return super.outboxed = require('../libx/outbox') }
|
|
57
|
+
get outboxed() { return super.outboxed = require('../libx/outbox').outboxed }
|
|
58
|
+
get unboxed() { return super.unboxed = require('../libx/outbox').unboxed }
|
|
61
59
|
get middlewares() { return super.middlewares = require('./srv/middlewares') }
|
|
62
60
|
get odata() { return super.odata = require('../libx/odata') }
|
|
63
61
|
get auth() { return super.auth = require('./auth') }
|
package/lib/linked/classes.js
CHANGED
|
@@ -30,124 +30,13 @@ class action extends any {}
|
|
|
30
30
|
class context extends any {}
|
|
31
31
|
|
|
32
32
|
class service extends context {
|
|
33
|
-
|
|
34
|
-
static
|
|
35
|
-
get
|
|
36
|
-
get
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*/
|
|
41
|
-
static path4 (srv, options = {}) {
|
|
42
|
-
return this.endpoints4(srv, options)[0]?.path
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Return an array of protocol names to be served by the given service.
|
|
47
|
-
* Ensure that 'odata' shortcuts are expanded to 'odata-v4'.
|
|
48
|
-
*/
|
|
49
|
-
static protocols4 (def, options = {}) {
|
|
50
|
-
const ov4 = p => p === 'odata' ? 'odata-v4' : p
|
|
51
|
-
|
|
52
|
-
if (options.to) return [ov4(options.to)]
|
|
53
|
-
|
|
54
|
-
const protocols = this._protocols
|
|
55
|
-
if (def) {
|
|
56
|
-
// Check @protocol annotation
|
|
57
|
-
let atProtocol = def['@protocol']
|
|
58
|
-
if (atProtocol === 'none') return []
|
|
59
|
-
if (atProtocol) {
|
|
60
|
-
const result = (Array.isArray(atProtocol) ? atProtocol : [atProtocol]).map(p => ov4(typeof p === 'string' ? p : p.kind))
|
|
61
|
-
return [...new Set(result)]
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Check @odata, @rest, ... shortcuts
|
|
65
|
-
const shortcut = Object.keys(protocols).find(p => def['@'+p])
|
|
66
|
-
if (shortcut) return [ov4(shortcut)]
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// No protocol annotation found -> serve odata
|
|
70
|
-
return ['odata-v4']
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
static endpoints4 (srv, options = {}) {
|
|
74
|
-
const def = srv?.definition || srv
|
|
75
|
-
if (!def) return []
|
|
76
|
-
|
|
77
|
-
// Return a sluggified variant of the service's name
|
|
78
|
-
const pathFromServiceName4 = (srv) => {
|
|
79
|
-
return (
|
|
80
|
-
/[^.]+$/.exec(srv.name)[0] //> my.very.CatalogService --> CatalogService
|
|
81
|
-
.replace(/Service$/,'') //> CatalogService --> Catalog
|
|
82
|
-
.replace(/_/g,'-') //> foo_bar_baz --> foo-bar-baz
|
|
83
|
-
.replace(/([a-z0-9])([A-Z])/g, (_,c,C) => c+'-'+C) //> ODataFooBarX9 --> OData-Foo-Bar-X9
|
|
84
|
-
.toLowerCase() //> FOO --> foo
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Return an array of service paths for a given service and protocol.
|
|
89
|
-
// The results are not prefixed with the protocol's path.
|
|
90
|
-
// Can return multiple results to support serving the same protocol on multiple paths:
|
|
91
|
-
// `@protocol: [{kind: 'odata', path: '/a'}, {kind: 'odata', path: '/b'}]`
|
|
92
|
-
const unprefixedPaths4 = (protocol, srv, options) => {
|
|
93
|
-
const def = srv?.definition || srv
|
|
94
|
-
if (options.at) return [options.at]
|
|
95
|
-
if (options.path) return [options.path]
|
|
96
|
-
|
|
97
|
-
const atProtocol = def['@protocol']
|
|
98
|
-
if (atProtocol) {
|
|
99
|
-
let paths = []
|
|
100
|
-
let protocols = Array.isArray(atProtocol) ? atProtocol : [atProtocol]
|
|
101
|
-
for (let p of protocols) {
|
|
102
|
-
let pName = (p.kind || p) === 'odata' ? 'odata-v4' : p.kind || p
|
|
103
|
-
if (pName === protocol) {
|
|
104
|
-
if (p.path) paths.push(p.path)
|
|
105
|
-
else paths.push(def['@path'] || pathFromServiceName4(srv))
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// in case protocol is set via options.at but not contained in @protocol
|
|
110
|
-
if (paths.length === 0) paths.push(def['@path'] || pathFromServiceName4(srv))
|
|
111
|
-
|
|
112
|
-
return paths
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (def['@path']) return [def['@path']]
|
|
116
|
-
return [pathFromServiceName4(srv)]
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const protocols = this.protocols4(def, options)
|
|
120
|
-
|
|
121
|
-
if (protocols.length > 1 && def['@path']?.[0] === '/') {
|
|
122
|
-
throw new Error(`Absolute @path (starting with '/') cannot be set for more than one protocol`)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const endpoints = [], env_protocols = global.cds?.env.protocols
|
|
126
|
-
const serve_on_root = global.cds?.env.features.serve_on_root
|
|
127
|
-
|
|
128
|
-
for (let each of protocols) {
|
|
129
|
-
let p = this._protocols[each] || env_protocols[each]
|
|
130
|
-
if (!p) throw new Error(`Protocol "${each}" is unknown`)
|
|
131
|
-
|
|
132
|
-
const middlewares = global.cds?.requires.middlewares
|
|
133
|
-
const kind = each
|
|
134
|
-
let prefix = middlewares ? p.path || '/' : '/' // > no middlewares, no prefix
|
|
135
|
-
if (!prefix.endsWith('/')) prefix += '/'
|
|
136
|
-
const unprefixedPaths = unprefixedPaths4(each, srv, options)
|
|
137
|
-
for (let path of unprefixedPaths) {
|
|
138
|
-
if (path.startsWith('/')) endpoints.push({ kind, path})
|
|
139
|
-
else {
|
|
140
|
-
// Extra serve in root if serve_on_root=true, but not(!) in case of ...
|
|
141
|
-
// - multiple protocols
|
|
142
|
-
// - empty prefix, which is the case for legacy protocol adapter
|
|
143
|
-
// - middlewares=false (check only needed for handling of custom protocols)
|
|
144
|
-
if (middlewares && serve_on_root && protocols.length === 1 && prefix !== '/') endpoints.push({ kind, path: '/' + path })
|
|
145
|
-
endpoints.push({ kind, path: prefix + path })
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return endpoints
|
|
150
|
-
}
|
|
33
|
+
/** @type <T> (p,v:T) => T */ static _lazy (p,v) { Reflect.defineProperty (this,p,{value:v}); return v }
|
|
34
|
+
static get protocols() { return this._lazy ('protocols', require('../srv/protocols')) }
|
|
35
|
+
static get bindings() { return this._lazy ('bindings', require('../srv/bindings')) }
|
|
36
|
+
static get factory() { return this._lazy ('factory', require('../srv/factory')) }
|
|
37
|
+
static endpoints4(..._) { return this.protocols.endpoints4(..._) }
|
|
38
|
+
static path4(..._) { return this.protocols.path4(..._) }
|
|
39
|
+
get _serves_odata() { return super._serves_odata = service.protocols._serves_odata(this) }
|
|
151
40
|
}
|
|
152
41
|
|
|
153
42
|
class array extends type { is(kind) { return kind === 'array' || super.is(kind) }}
|
package/lib/linked/entities.js
CHANGED
|
@@ -56,7 +56,7 @@ class Association extends struct {
|
|
|
56
56
|
|
|
57
57
|
set keys(k) { super.keys = k }
|
|
58
58
|
get keys() {
|
|
59
|
-
if (this.on || this.is2many || !this._target) return this._keys
|
|
59
|
+
if (this.on || this.is2many || !this._target) return this.set('_keys', undefined)
|
|
60
60
|
const keys=[], tks = this._target.keys
|
|
61
61
|
for (let k in tks) keys.push({ ref: [tks[k].name] })
|
|
62
62
|
return this.keys = keys
|
package/lib/log/cds-log.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
const cds = require
|
|
1
|
+
const cds = require('../index'), conf = cds.env.log
|
|
2
2
|
const log = module.exports = exports = cds_log
|
|
3
3
|
const path = require('path')
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
/**
|
|
6
7
|
* Cache used for all constructed loggers.
|
|
7
8
|
*/
|
|
8
9
|
exports.loggers = {}
|
|
9
10
|
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* Returns a trace logger for the given module if trace is switched on for it,
|
|
12
14
|
* otherwise returns null. All cds runtime packages use this method for their
|
|
@@ -130,20 +132,25 @@ exports.winstonLogger = (options) => (label, level) => {
|
|
|
130
132
|
})
|
|
131
133
|
}
|
|
132
134
|
|
|
135
|
+
|
|
133
136
|
/**
|
|
134
137
|
* Built-in formatters
|
|
135
138
|
*/
|
|
136
|
-
const {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
const { _simple, _mt } = exports.formatters = {
|
|
140
|
+
_simple: (label, level, ...args) => [ `[${label}] -`, ...args ],
|
|
141
|
+
_mt: (label, level, ...args) => {
|
|
139
142
|
const t = cds.context?.tenant; if (t) label += '|'+t
|
|
140
|
-
return
|
|
143
|
+
return _simple (label, level, ...args)
|
|
144
|
+
},
|
|
145
|
+
get plain() {
|
|
146
|
+
return this._plain || (this._plain = cds.requires.multitenancy ? _mt : _simple)
|
|
141
147
|
},
|
|
142
148
|
get json() {
|
|
143
|
-
return this._json || (this._json = require('./format/
|
|
149
|
+
return this._json || (this._json = require('./format/json'))
|
|
144
150
|
}
|
|
145
151
|
}
|
|
146
152
|
|
|
153
|
+
|
|
147
154
|
/**
|
|
148
155
|
* Formats log outputs by returning an array of arguments which are passed to
|
|
149
156
|
* console.log() et al.
|
|
@@ -155,10 +162,7 @@ exports.winstonLogger = (options) => (label, level) => {
|
|
|
155
162
|
* @param {number} level the log level to enable -> 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace
|
|
156
163
|
* @param {any[]} args the arguments passed to Logger.debug|log|info|warn|error()
|
|
157
164
|
*/
|
|
158
|
-
exports.format =
|
|
159
|
-
process.env.NODE_ENV === 'production' && cds.env.features.kibana_formatter ? log.formatters.json :
|
|
160
|
-
cds.requires.multitenancy ? log.formatters.mt : simple
|
|
161
|
-
)
|
|
165
|
+
exports.format = log.formatters[cds.env.log.format || 'plain']
|
|
162
166
|
|
|
163
167
|
|
|
164
168
|
const DEBUG_matches = (m) => process.env.DEBUG?.match(RegExp(`\\b(y|all|${m||'any'})\\b`))
|
|
@@ -166,6 +170,7 @@ const { ERROR, WARN, INFO, DEBUG, TRACE } = exports.levels = {
|
|
|
166
170
|
SILENT:0, ERROR:1, WARN:2, INFO:3, DEBUG:4, TRACE:5, SILLY:5, VERBOSE:5
|
|
167
171
|
}
|
|
168
172
|
|
|
173
|
+
|
|
169
174
|
;(function _init() {
|
|
170
175
|
const conf = cds.env.log
|
|
171
176
|
if (conf.Logger) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const cds = require('../../..')
|
|
2
|
+
|
|
3
|
+
const $remove = Symbol('remove')
|
|
4
|
+
|
|
5
|
+
const _is_custom_fields = (arg, custom_fields) => {
|
|
6
|
+
for (const k in arg) if (!(k in custom_fields)) return false
|
|
7
|
+
return true
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const _is_categories = arg => arg.categories && Array.isArray(arg.categories) && Object.keys(arg).length === 1
|
|
11
|
+
|
|
12
|
+
function als_aspect(module, level, args, toLog) {
|
|
13
|
+
// REVISIT: kibana_custom_fields for backward compatibility. remove in cds^8.
|
|
14
|
+
const { als_custom_fields, kibana_custom_fields } = cds.env.log
|
|
15
|
+
this._CUSTOM_FIELDS ??= kibana_custom_fields ? { ...kibana_custom_fields } : { ...als_custom_fields }
|
|
16
|
+
this._HAS_CUSTOM_FIELDS ??= Object.keys(this._CUSTOM_FIELDS).length > 0
|
|
17
|
+
|
|
18
|
+
// extract custom fields and categories from remaining args (while avoiding as many loops/ iterations as possible)
|
|
19
|
+
if (args.length) {
|
|
20
|
+
let filter4removed = false
|
|
21
|
+
for (let i = 0; i < args.length; i++) {
|
|
22
|
+
const arg = args[i]
|
|
23
|
+
if (typeof arg !== 'object') continue
|
|
24
|
+
if ((this._HAS_CUSTOM_FIELDS && _is_custom_fields(arg, this._CUSTOM_FIELDS)) || _is_categories(arg)) {
|
|
25
|
+
Object.assign(toLog, arg)
|
|
26
|
+
args[i] = $remove
|
|
27
|
+
filter4removed = true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (filter4removed) args.sort((a, b) => (b === $remove) * -1).splice(args.lastIndexOf($remove))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ALS custom fields
|
|
34
|
+
if (this._HAS_CUSTOM_FIELDS) {
|
|
35
|
+
const cf = []
|
|
36
|
+
for (const k in this._CUSTOM_FIELDS) if (toLog[k]) cf.push({ k, v: toLog[k], i: this._CUSTOM_FIELDS[k] })
|
|
37
|
+
if (cf.length) toLog['#cf'] = { string: cf }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = process.env.VCAP_SERVICES?.match(/"label":\s*"application-logs"/) ? als_aspect : () => {}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const cds = require('../../..')
|
|
2
|
+
|
|
3
|
+
const _get_cf_fields = () => {
|
|
4
|
+
const cf_fields = {
|
|
5
|
+
layer: 'cds',
|
|
6
|
+
component_type: 'application',
|
|
7
|
+
container_id: process.env.CF_INSTANCE_IP
|
|
8
|
+
}
|
|
9
|
+
const VCAP_APPLICATION = process.env.VCAP_APPLICATION && JSON.parse(process.env.VCAP_APPLICATION)
|
|
10
|
+
if (VCAP_APPLICATION) {
|
|
11
|
+
Object.assign(cf_fields, {
|
|
12
|
+
component_id: VCAP_APPLICATION.application_id,
|
|
13
|
+
component_name: VCAP_APPLICATION.application_name,
|
|
14
|
+
component_instance: VCAP_APPLICATION.instance_index,
|
|
15
|
+
source_instance: VCAP_APPLICATION.instance_index,
|
|
16
|
+
organization_name: VCAP_APPLICATION.organization_name,
|
|
17
|
+
organization_id: VCAP_APPLICATION.organization_id,
|
|
18
|
+
space_name: VCAP_APPLICATION.space_name,
|
|
19
|
+
space_id: VCAP_APPLICATION.space_id
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
return cf_fields
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function cf_aspect(module, level, args, toLog) {
|
|
26
|
+
this._CF_FIELDS ??= _get_cf_fields()
|
|
27
|
+
|
|
28
|
+
// add static fields from environment
|
|
29
|
+
Object.assign(toLog, this._CF_FIELDS)
|
|
30
|
+
|
|
31
|
+
// add subdomain, if available (use cds.context._ instead of cds.context.http because of messaging)
|
|
32
|
+
const tenant_subdomain = cds.context?._?.req?.authInfo?.getSubdomain?.()
|
|
33
|
+
if (tenant_subdomain) toLog.tenant_subdomain = tenant_subdomain
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = process.env.CF_INSTANCE_GUID ? cf_aspect : () => {}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const cds = require('../../')
|
|
2
|
+
|
|
3
|
+
const util = require('util')
|
|
4
|
+
|
|
5
|
+
const L2L = { 1: 'error', 2: 'warn', 3: 'info', 4: 'debug', 5: 'trace' }
|
|
6
|
+
const HEADER_MAPPINGS = {
|
|
7
|
+
x_vcap_request_id: 'request_id',
|
|
8
|
+
content_length: 'request_size_b'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const _is4xx = ele =>
|
|
12
|
+
(ele.code >= 400 && ele.code < 500) ||
|
|
13
|
+
(ele.status >= 400 && ele.status < 500) ||
|
|
14
|
+
(ele.statusCode >= 400 && ele.statusCode < 500)
|
|
15
|
+
|
|
16
|
+
const _getCircularReplacer = () => {
|
|
17
|
+
const seen = new WeakSet()
|
|
18
|
+
return (key, value) => {
|
|
19
|
+
if (typeof value === 'object' && value !== null) {
|
|
20
|
+
if (seen.has(value)) return 'cyclic'
|
|
21
|
+
seen.add(value)
|
|
22
|
+
}
|
|
23
|
+
return value
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* JSON-based log formatter for use in production
|
|
29
|
+
*/
|
|
30
|
+
module.exports = function format(module, level, ...args) {
|
|
31
|
+
// config
|
|
32
|
+
const { user: log_user, mask_headers, aspects } = cds.env.log
|
|
33
|
+
this._MASK_HEADERS ??= (mask_headers || []).map(s => {
|
|
34
|
+
const parts = s.match(/\/(.+)\/(\w*)/)
|
|
35
|
+
if (parts) return new RegExp(parts[1], parts[2])
|
|
36
|
+
return new RegExp(s)
|
|
37
|
+
})
|
|
38
|
+
this._ASPECTS ??= (aspects || []).map(require)
|
|
39
|
+
|
|
40
|
+
// the object to log
|
|
41
|
+
const toLog = {
|
|
42
|
+
level: L2L[level] || 'info',
|
|
43
|
+
logger: module
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// add correlation
|
|
47
|
+
if (cds.context) {
|
|
48
|
+
const { id, tenant, user } = cds.context
|
|
49
|
+
toLog.correlation_id = id
|
|
50
|
+
if (tenant) toLog.tenant_id = tenant
|
|
51
|
+
// log user id, if configured (data privacy)
|
|
52
|
+
if (user && log_user) toLog.remote_user = user.id
|
|
53
|
+
// if available, add headers (normalized to lowercase and with _ instead of -) with masking as configured and mappings applied
|
|
54
|
+
const req = cds.context._ && cds.context._.req
|
|
55
|
+
if (req && req.headers) {
|
|
56
|
+
for (const k in req.headers) {
|
|
57
|
+
const h = k.replace(/-/g, '_').toLowerCase()
|
|
58
|
+
toLog[h] = (() => {
|
|
59
|
+
if (this._MASK_HEADERS.some(m => k.match(m))) return '***'
|
|
60
|
+
return req.headers[k]
|
|
61
|
+
})()
|
|
62
|
+
if (h in HEADER_MAPPINGS) toLog[HEADER_MAPPINGS[h]] = toLog[h]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
toLog.timestamp = new Date()
|
|
67
|
+
|
|
68
|
+
// merge toLog with passed Error (or error-like object)
|
|
69
|
+
if (args.length && typeof args[0] === 'object' && args[0].message) {
|
|
70
|
+
const err = args.shift()
|
|
71
|
+
toLog.msg = err.message
|
|
72
|
+
if (typeof err.stack === 'string' && !_is4xx(err)) toLog.stacktrace = err.stack.split(/\s*\r?\n\s*/)
|
|
73
|
+
if (Array.isArray(err.details))
|
|
74
|
+
for (const d of err.details)
|
|
75
|
+
if (typeof d.stack === 'string' && !_is4xx(d)) d.stacktrace = d.stack.split(/\s*\r?\n\s*/)
|
|
76
|
+
Object.assign(toLog, err, { level: toLog.level })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// apply aspects
|
|
80
|
+
for (const each of this._ASPECTS) each.call(this, module, level, args, toLog)
|
|
81
|
+
|
|
82
|
+
// append remaining args via util.format()
|
|
83
|
+
if (args.length) toLog.msg = toLog.msg ? util.format(toLog.msg, ...args) : util.format(...args)
|
|
84
|
+
|
|
85
|
+
// REVISIT: should not be necessary with new protocol adapters
|
|
86
|
+
// 4xx: lower to warning (if error)
|
|
87
|
+
if (toLog.level && toLog.level.match(/error/i) && _is4xx(toLog)) toLog.level = 'warn'
|
|
88
|
+
|
|
89
|
+
// return array with the stringified toLog (to avoid multiple log lines) as the sole element
|
|
90
|
+
try {
|
|
91
|
+
return [JSON.stringify(toLog)]
|
|
92
|
+
} catch (e) {
|
|
93
|
+
// try again with removed circular references
|
|
94
|
+
return [JSON.stringify(toLog, _getCircularReplacer())]
|
|
95
|
+
}
|
|
96
|
+
}
|
package/lib/plugins.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
|
|
1
2
|
const cds = require('.')
|
|
2
3
|
|
|
4
|
+
exports.require = require
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* Fetch cds-plugins from project's package dependencies.
|
|
5
8
|
* Used in and made available through cds.env.plugins.
|
|
@@ -9,11 +12,12 @@ exports.fetch = function (DEV = process.env.NODE_ENV !== 'production') {
|
|
|
9
12
|
fetch_plugins_in (cds.home, false)
|
|
10
13
|
fetch_plugins_in (cds.root, DEV)
|
|
11
14
|
function fetch_plugins_in (root, dev) {
|
|
12
|
-
let pkg; try { pkg = require(root + '/package.json') } catch { return }
|
|
15
|
+
let pkg; try { pkg = exports.require(root + '/package.json') } catch { return }
|
|
13
16
|
let deps = { ...pkg.dependencies, ...dev && pkg.devDependencies }
|
|
14
17
|
for (let each in deps) try {
|
|
15
|
-
let impl = require.resolve(each + '/cds-plugin', { paths: [root] })
|
|
16
|
-
|
|
18
|
+
let impl = exports.require.resolve(each + '/cds-plugin', { paths: [root] })
|
|
19
|
+
const packageJson = exports.require.resolve(each + '/package.json', { paths: [root] })
|
|
20
|
+
plugins[each] = { impl, packageJson }
|
|
17
21
|
} catch { /* no cds-plugin.js */ }
|
|
18
22
|
}
|
|
19
23
|
return plugins
|
package/lib/req/context.js
CHANGED
|
@@ -16,8 +16,10 @@ class EventContext {
|
|
|
16
16
|
const ctx = new this (_)
|
|
17
17
|
const base = cds.context
|
|
18
18
|
if (base) {
|
|
19
|
-
|
|
20
|
-
if (
|
|
19
|
+
// we inherit from former cds.currents (except for the timestamp if new root ctx)
|
|
20
|
+
if (_as_root) ctx._set('_propagated', Object.create(base, { timestamp: { value: undefined } }))
|
|
21
|
+
else {
|
|
22
|
+
ctx._set('_propagated', base)
|
|
21
23
|
if (!ctx.context) ctx._set('context', base.context) // all transaction handling works with root contexts
|
|
22
24
|
if (!ctx.tx && base.tx) ctx.tx = base.tx
|
|
23
25
|
}
|
package/lib/srv/cds-connect.js
CHANGED
|
@@ -43,11 +43,9 @@ connect.to = async (datasource, options) => {
|
|
|
43
43
|
throw new Error (`No service definition found for '${required.service || datasource}'`)
|
|
44
44
|
}
|
|
45
45
|
// construct new service instance
|
|
46
|
-
let srv = await new Service (datasource,m,o)
|
|
47
|
-
|
|
46
|
+
let srv = await new Service (datasource,m,o); await srv._init()
|
|
47
|
+
if (o.outbox) srv = cds.outboxed(srv)
|
|
48
48
|
if (datasource === 'db') cds.db = srv
|
|
49
|
-
// outbox the service, if configured
|
|
50
|
-
if (srv.options.outbox) srv = cds.outboxed(srv)
|
|
51
49
|
_done (cds.services[datasource] = srv)
|
|
52
50
|
if (!o.silent) cds.emit ('connect',srv)
|
|
53
51
|
TRACE?.timeEnd(`cds.connect ${datasource} `)
|
|
@@ -55,7 +53,7 @@ connect.to = async (datasource, options) => {
|
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
function options4 (name, _o) {
|
|
58
|
-
const [, kind=_o
|
|
56
|
+
const [, kind=_o?.kind, url ] = /^(\w+):(.*)/.exec(name) || []
|
|
59
57
|
const conf = cds.requires[name] || cds.requires[kind] || cds.requires.kinds[name] || cds.requires.kinds[kind]
|
|
60
58
|
const o = { kind, ...conf, ..._o }
|
|
61
59
|
if (!o.kind && !o.impl && !o.silent) throw cds.error(
|