@sap/cds 8.3.1 → 8.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -1
- package/bin/serve.js +9 -2
- package/lib/auth/ias-auth.js +4 -1
- package/lib/auth/jwt-auth.js +4 -1
- package/lib/compile/cdsc.js +1 -1
- package/lib/compile/extend.js +23 -23
- package/lib/compile/to/srvinfo.js +3 -1
- package/lib/{linked → core}/classes.js +8 -6
- package/lib/{linked/models.js → core/linked-csn.js} +4 -0
- package/lib/env/defaults.js +4 -1
- package/lib/i18n/localize.js +2 -2
- package/lib/index.js +43 -59
- package/lib/log/cds-error.js +21 -21
- package/lib/ql/cds-ql.js +5 -5
- package/lib/req/cds-context.js +5 -0
- package/lib/req/context.js +2 -2
- package/lib/req/locale.js +25 -21
- package/lib/srv/cds-serve.js +1 -1
- package/lib/srv/middlewares/errors.js +20 -7
- package/lib/srv/protocols/hcql.js +106 -43
- package/lib/srv/protocols/http.js +2 -2
- package/lib/srv/protocols/index.js +14 -10
- package/lib/srv/protocols/odata-v4.js +2 -26
- package/lib/srv/protocols/okra.js +24 -0
- package/lib/srv/srv-models.js +6 -8
- package/lib/{utils → test}/cds-test.js +5 -5
- package/lib/utils/check-version.js +8 -15
- package/lib/utils/extend.js +20 -0
- package/lib/utils/lazify.js +33 -0
- package/lib/utils/tar.js +39 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +0 -1
- package/libx/_runtime/common/generic/auth/restrict.js +1 -3
- package/libx/_runtime/common/generic/sorting.js +1 -1
- package/libx/_runtime/common/utils/compareJson.js +139 -53
- package/libx/_runtime/common/utils/compareJsonOLD.js +280 -0
- package/libx/_runtime/common/utils/differ.js +9 -1
- package/libx/_runtime/common/utils/resolveView.js +19 -23
- package/libx/_runtime/fiori/lean-draft.js +2 -2
- package/libx/_runtime/messaging/kafka.js +7 -1
- package/libx/_runtime/remote/utils/data.js +30 -24
- package/libx/odata/ODataAdapter.js +17 -7
- package/libx/odata/middleware/batch.js +4 -1
- package/libx/odata/middleware/error.js +6 -0
- package/libx/odata/middleware/operation.js +8 -0
- package/libx/odata/parse/afterburner.js +5 -6
- package/libx/odata/parse/grammar.peggy +3 -4
- package/libx/odata/parse/multipartToJson.js +60 -10
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/metadata.js +31 -1
- package/libx/outbox/index.js +5 -1
- package/package.json +3 -4
- package/server.js +18 -0
- package/lib/lazy.js +0 -51
- package/lib/test/index.js +0 -2
- /package/lib/{linked → core}/entities.js +0 -0
- /package/lib/{linked → core}/types.js +0 -0
- /package/lib/{linked → req}/validate.js +0 -0
- /package/lib/{utils → test}/axios.js +0 -0
- /package/lib/{utils → test}/data.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,39 @@
|
|
|
4
4
|
- The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
5
5
|
- This project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
|
|
7
|
+
## Version 8.4.1 - 2024-11-07
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Validate request method for operations
|
|
12
|
+
- Correctly generate CQN for lambda expressions in new OData adapter
|
|
13
|
+
- `req.diff()` on old database with property transitions
|
|
14
|
+
|
|
15
|
+
## Version 8.4.0 - 2024-10-29
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Set the maximum allowed size of HTTP headers in bytes for `$batch` subrequests via flag `cds.env.odata.max_batch_header_size` (default: 64 KiB)
|
|
20
|
+
- New OData flag `cds.env.odata.context_with_columns` that adds selected and expanded columns to `@odata.context`. Default is `false`
|
|
21
|
+
- New experimental option `--workers` to `cds watch/run/serve` that allows running a `cds.server` cluster
|
|
22
|
+
(process env variable `WORKERS` or `CDS_WORKERS` can be used alternatively)
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- For remote service calls to OData v2 services, less conversions are performed on the returned data
|
|
27
|
+
- Internal API `srv.endpoints` now always is an array of endpoint objects, an empty one if the service is not served to any protocol
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Commands like `cds deploy` now fail with a clear error message if called with an invalid value for `cds.features.assert_integrity` (like `true`)
|
|
32
|
+
- Authentication validation errors (e.g., expired token, wrong audience) are logged as warning
|
|
33
|
+
- Requests using `$apply` will always apply implicit sorting on best effort mechanism
|
|
34
|
+
- Properly handle empty content-type in new OData adapter
|
|
35
|
+
- Error/crash with `cds.features.odata_new_parser` for requests containing `$expand=*` and `$select`, which selects individual columns and star, e.g. `$select=ID,*`
|
|
36
|
+
- Referencing new entities in `$batch` with new OData adapter did not work properly when using non integer content IDs in multipart/mixed
|
|
37
|
+
- `cds.compile.to.serviceinfo` to ignore unknown protocols
|
|
38
|
+
- New OData adapter and `cds.spawn` did not crash on programming errors (for example TypeError)
|
|
39
|
+
|
|
7
40
|
## Version 8.3.1 - 2024-10-08
|
|
8
41
|
|
|
9
42
|
### Fixed
|
|
@@ -128,7 +161,7 @@
|
|
|
128
161
|
- Expand to `DraftAdministrativeData` for active instances of draft-enabled entities over drafts
|
|
129
162
|
- Deduplication of columns for certain on conditions for the legacy database driver
|
|
130
163
|
- For legacy-sqlite/-hana: Add keys to expands with only non-key elements to ensure not returning null for expand.
|
|
131
|
-
- New parser was
|
|
164
|
+
- New parser was too restrictive regarding an empty line at the end of batch body.
|
|
132
165
|
- Error target for operations with complex parameters
|
|
133
166
|
- Remote services: JWT gets found in authorization header
|
|
134
167
|
- Search with invalid characters
|
package/bin/serve.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
module.exports = exports = Object.assign ( serve, {
|
|
3
3
|
options: [
|
|
4
4
|
'--service', '--from', '--to', '--at', '--with',
|
|
5
|
-
'--port',
|
|
5
|
+
'--port', '--workers',
|
|
6
6
|
],
|
|
7
7
|
flags: [
|
|
8
8
|
'--project', '--projects',
|
|
@@ -10,7 +10,7 @@ module.exports = exports = Object.assign ( serve, {
|
|
|
10
10
|
'--mocked', '--with-mocks', '--with-bindings', '--resolve-bindings',
|
|
11
11
|
'--watch',
|
|
12
12
|
],
|
|
13
|
-
shortcuts: [ '-s', undefined, '-2', '-a', '-w', undefined, '-p' ],
|
|
13
|
+
shortcuts: [ '-s', undefined, '-2', '-a', '-w', undefined, undefined, '-p' ],
|
|
14
14
|
help: `
|
|
15
15
|
# SYNOPSIS
|
|
16
16
|
|
|
@@ -75,6 +75,13 @@ module.exports = exports = Object.assign ( serve, {
|
|
|
75
75
|
You can use *cds watch* as shortcut, which is equivalent to:
|
|
76
76
|
*cds serve --with-mocks --in-memory? --watch --project ...*
|
|
77
77
|
|
|
78
|
+
*--workers* <number> | true
|
|
79
|
+
|
|
80
|
+
Spawns <number> of worker processes in a cluster to handle incoming requests.
|
|
81
|
+
If set to 'true', the number of workers is determined by the number of CPUs.
|
|
82
|
+
If omitted, the server runs in non-cluster mode as a single process.
|
|
83
|
+
|
|
84
|
+
|
|
78
85
|
*--mocked*
|
|
79
86
|
|
|
80
87
|
Use this option to launch a _single service_ in a mock server, for
|
package/lib/auth/ias-auth.js
CHANGED
|
@@ -49,7 +49,10 @@ module.exports = function ias_auth(config) {
|
|
|
49
49
|
|
|
50
50
|
const token = req.headers.authorization?.split(/^bearer /i)[1]
|
|
51
51
|
xssec.createSecurityContext(token, credentials, 'IAS', function (err, securityContext, tokenInfo) {
|
|
52
|
-
if (err)
|
|
52
|
+
if (err) {
|
|
53
|
+
const level = (err.statusCode >= 400 && err.statusCode < 500) ? 'warn' : 'error'
|
|
54
|
+
LOG[level]('User could not be authenticated due to error:', err)
|
|
55
|
+
}
|
|
53
56
|
|
|
54
57
|
if (!securityContext) return next(new cds.error('Unauthorized', { code: 401 }))
|
|
55
58
|
|
package/lib/auth/jwt-auth.js
CHANGED
|
@@ -51,7 +51,10 @@ module.exports = function jwt_auth(config) {
|
|
|
51
51
|
|
|
52
52
|
const token = req.headers.authorization.split(/^bearer /i)[1]
|
|
53
53
|
xssec.createSecurityContext(token, credentials, function (err, securityContext, tokenInfo) {
|
|
54
|
-
if (err)
|
|
54
|
+
if (err) {
|
|
55
|
+
const level = (err.statusCode >= 400 && err.statusCode < 500) ? 'warn' : 'error'
|
|
56
|
+
LOG[level]('User could not be authenticated due to error:', err)
|
|
57
|
+
}
|
|
55
58
|
|
|
56
59
|
if (!securityContext) return next(new cds.error('Unauthorized', { code: 401, statusCode: 401 }))
|
|
57
60
|
|
package/lib/compile/cdsc.js
CHANGED
|
@@ -91,7 +91,7 @@ const _options = {for: Object.assign (_options4, {
|
|
|
91
91
|
|
|
92
92
|
const { assert_integrity } = cds.env.features
|
|
93
93
|
if (assert_integrity)
|
|
94
|
-
o.assertIntegrityType = assert_integrity.toUpperCase()
|
|
94
|
+
o.assertIntegrityType = assert_integrity.toString().toUpperCase()
|
|
95
95
|
|
|
96
96
|
return o
|
|
97
97
|
},
|
package/lib/compile/extend.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const cds = new class { get compile(){ return super.compile = require ('./cds-compile') }}
|
|
2
|
-
const
|
|
2
|
+
const extend = require ('../utils/extend')
|
|
3
3
|
|
|
4
4
|
/** @type <T> (target:T) => ({
|
|
5
5
|
with <X,Y,Z> (x:X, y:Y, z:Z): ( T & X & Y & Z )
|
|
@@ -8,32 +8,32 @@ const { extend } = require ('../lazy')
|
|
|
8
8
|
}) */
|
|
9
9
|
module.exports = o => o.definitions ? { with(...csns) {
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
// merge all extension csns
|
|
12
|
+
const csn=o, merged = { definitions: {}, extensions: [] }
|
|
13
|
+
for (const { definitions, extensions } of csns) {
|
|
14
|
+
if (definitions) Object.assign(merged.definitions, definitions)
|
|
15
|
+
if (extensions) merged.extensions.push(...extensions)
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
// extend given base csn with merged extensions
|
|
19
|
+
const extended = cds.compile({
|
|
20
|
+
'base.csn': cds.compile.to.json(csn),
|
|
21
|
+
'ext.csn': cds.compile.to.json(merged)
|
|
22
|
+
})
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
// handle localized extension elements
|
|
25
|
+
for (let ext of merged.extensions) {
|
|
26
|
+
for (let name in ext.elements) {
|
|
27
|
+
const e = ext.elements[name]
|
|
28
|
+
if (e.localized) {
|
|
29
|
+
// add localized element also to respective .texts entity
|
|
30
|
+
const texts = extended.definitions[ext.extend+'.texts']
|
|
31
|
+
texts.elements[name] ??= { ...e, localized:null }
|
|
32
|
+
}
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
|
-
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
extended.$sources = csn.$sources // required to load resources like i18n later on
|
|
37
|
+
return extended
|
|
38
38
|
|
|
39
39
|
}} : extend(o)
|
|
@@ -45,7 +45,9 @@ module.exports = (model, options={}) => {
|
|
|
45
45
|
function _makeNode(service) {
|
|
46
46
|
// make a fake runtime object for the service, adding a `definition` property
|
|
47
47
|
if (!service.definition) Object.defineProperty(service, 'definition', { value: service, enumerable: false })
|
|
48
|
-
const endpoints = cds.service.protocols.endpoints4(service).map(e => Object.assign({}, e, { path: _url4(e.path) }))
|
|
48
|
+
const endpoints = cds.service.protocols.endpoints4(service).map?.(e => Object.assign({}, e, { path: _url4(e.path) }))
|
|
49
|
+
if (!endpoints || !endpoints.length) return
|
|
50
|
+
|
|
49
51
|
return {
|
|
50
52
|
name: service.name,
|
|
51
53
|
urlPath: endpoints[0].path, // legacy
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { extend } = require('../lazy')
|
|
2
1
|
const _proxy = Symbol('_proxy')
|
|
3
2
|
|
|
4
3
|
class any { is (kind) { return this.kind === kind || kind === 'any' }
|
|
@@ -211,9 +210,12 @@ module.exports = {
|
|
|
211
210
|
* class entity { bar(){} }
|
|
212
211
|
* )
|
|
213
212
|
*/
|
|
214
|
-
mixin(...classes) {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
213
|
+
mixin(...classes) {
|
|
214
|
+
const extend = require('../utils/extend')
|
|
215
|
+
for (let each of classes) {
|
|
216
|
+
const clazz = this[each.name]
|
|
217
|
+
if (!clazz) throw new Error(`unknown class '${each.name}'`)
|
|
218
|
+
extend(clazz).with(each)
|
|
219
|
+
}
|
|
220
|
+
},
|
|
219
221
|
}
|
|
@@ -62,6 +62,7 @@ class LinkedCSN {
|
|
|
62
62
|
let i=0, n=ref.length, t=defs[ref[0]]
|
|
63
63
|
for (;;) {
|
|
64
64
|
if (++i === n) return t
|
|
65
|
+
if (t.items) t = t.items
|
|
65
66
|
if (t.target) t = defs[t.target]
|
|
66
67
|
if (t.elements) t = t.elements[ref[i]]
|
|
67
68
|
if (!t) return
|
|
@@ -114,6 +115,9 @@ class LinkedCSN {
|
|
|
114
115
|
for (let s of srvs) Object.defineProperty (srvs, s.name, {value:s})
|
|
115
116
|
return this.set ('services', srvs)
|
|
116
117
|
}
|
|
118
|
+
|
|
119
|
+
/** A common cache for all kinds of things -> keeps the models clean */
|
|
120
|
+
get _cached() { return this.set ('_cached', {}) }
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
|
package/lib/env/defaults.js
CHANGED
|
@@ -50,6 +50,7 @@ const defaults = module.exports = {
|
|
|
50
50
|
protocols: {
|
|
51
51
|
'odata-v4' : { path: '/odata/v4' },
|
|
52
52
|
'odata-v2' : { path: '/odata/v2' },
|
|
53
|
+
'okra' : { path: '/okra' },
|
|
53
54
|
'rest' : { path: '/rest' },
|
|
54
55
|
'hcql' : { path: '/hcql' },
|
|
55
56
|
},
|
|
@@ -186,6 +187,8 @@ const defaults = module.exports = {
|
|
|
186
187
|
refs: undefined,
|
|
187
188
|
proxies: undefined,
|
|
188
189
|
containment: undefined,
|
|
190
|
+
context_with_columns: false,
|
|
191
|
+
max_batch_header_size: '64KiB', // instead of node's 16KiB
|
|
189
192
|
},
|
|
190
193
|
|
|
191
194
|
sql: {
|
|
@@ -248,4 +251,4 @@ const defaults = module.exports = {
|
|
|
248
251
|
|
|
249
252
|
}
|
|
250
253
|
|
|
251
|
-
require('./plugins')(defaults)
|
|
254
|
+
require('./plugins')(defaults)
|
package/lib/i18n/localize.js
CHANGED
|
@@ -28,7 +28,7 @@ function localize (aString, locale, model = cds.context?.model || cds.model, ext
|
|
|
28
28
|
if (typeof aString === 'object') [ aString, model ] = [ model, aString ]
|
|
29
29
|
|
|
30
30
|
// in case of multiple locales, return a generator
|
|
31
|
-
if (Array.isArray(locale)) return (function*(){
|
|
31
|
+
if (Array.isArray(locale) || locale == 'all' || locale == '*') return (function*(){
|
|
32
32
|
let localized, bundles = bundles4 (model, locale)
|
|
33
33
|
if (bundles?.[Symbol.iterator]) for (let [lang,each] of bundles) {
|
|
34
34
|
localized = _localize_with (each)
|
|
@@ -64,7 +64,7 @@ function bundles4 (model, locales = cds.env.i18n.languages) {
|
|
|
64
64
|
const {i18n} = cds.env
|
|
65
65
|
|
|
66
66
|
if (locales.split) locales = locales.split(',')
|
|
67
|
-
if (locales
|
|
67
|
+
if (locales == 'all' || locales == '*') { // NOTE: using == to match 'all' as well as ['all']
|
|
68
68
|
locales = allLocales4 (folders); if (!locales) return {}
|
|
69
69
|
if (!locales.includes(i18n.fallback_bundle)) locales.push (i18n.fallback_bundle)
|
|
70
70
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
if (process.env.CDS_STRICT_NODE_VERSION !== 'false') require ('./utils/check-version')
|
|
2
|
-
!(global.__cds_loaded_from ??= new Set).add(__filename) // track from where we loaded cds
|
|
3
|
-
|
|
4
|
-
/** @typedef { import('./srv/srv-api') } Service */
|
|
1
|
+
if (process.env.CDS_STRICT_NODE_VERSION !== 'false') require ('./utils/check-version.js')
|
|
2
|
+
! (global.__cds_loaded_from ??= new Set).add(__filename) // track from where we loaded cds
|
|
5
3
|
|
|
6
4
|
const { EventEmitter } = require('node:events')
|
|
7
|
-
const { extend, lazify } = require('./lazy')
|
|
8
|
-
|
|
9
5
|
const cds = module.exports = global.cds = new class cds extends EventEmitter {
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
7
|
+
/** @typedef {import('./srv/srv-api.js')} Service */
|
|
8
|
+
/** @type {import('./core/linked-csn.js')} */ model = undefined
|
|
9
|
+
/** @type Service */ db = undefined
|
|
16
10
|
/** CLI args */ cli = { command:'', options:{}, argv:[] }
|
|
17
11
|
/** Working dir */ root = process.cwd()
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
|
|
13
|
+
async emit (eve, ...args) {
|
|
14
|
+
if (eve === 'served') for (let each of this.listeners(eve)) await each.call(this,...args)
|
|
15
|
+
else return super.emit (eve, ...args)
|
|
16
|
+
}
|
|
20
17
|
|
|
21
18
|
// Configuration & Information
|
|
22
19
|
get requires() { return super.requires = this.env.requires._resolved() }
|
|
@@ -41,10 +38,8 @@ const cds = module.exports = global.cds = new class cds extends EventEmitter {
|
|
|
41
38
|
// Model Reflection, Builtin types and classes
|
|
42
39
|
get entities() { return this.db?.entities || this.model?.entities }
|
|
43
40
|
get reflect() { return super.reflect = this.linked }
|
|
44
|
-
get linked() { return super.linked = require('./linked
|
|
45
|
-
get
|
|
46
|
-
get builtin() { return super.builtin = require('./linked/types') }
|
|
47
|
-
get validate() { return super.validate = require('./linked/validate') }
|
|
41
|
+
get linked() { return super.linked = require('./core/linked-csn.js') }
|
|
42
|
+
get builtin() { return super.builtin = require('./core/types.js') }
|
|
48
43
|
get Association() { return super.Association = this.builtin.classes.Association }
|
|
49
44
|
get Composition() { return super.Composition = this.builtin.classes.Composition }
|
|
50
45
|
get entity() { return super.entity = this.builtin.classes.entity }
|
|
@@ -52,17 +47,17 @@ const cds = module.exports = global.cds = new class cds extends EventEmitter {
|
|
|
52
47
|
get type() { return super.type = this.builtin.classes.type }
|
|
53
48
|
get array() { return super.array = this.builtin.classes.array }
|
|
54
49
|
get struct() { return super.struct = this.builtin.classes.struct }
|
|
55
|
-
get service() { return super.service =
|
|
50
|
+
get service() { return super.service = Object.assign (this.builtin.classes.service, {
|
|
56
51
|
/** @param {( this:Service, srv:Service )} fn */ impl: fn => fn,
|
|
57
52
|
/** @type Service[] */ providers: []
|
|
58
53
|
})}
|
|
59
54
|
|
|
60
55
|
// Providing and Consuming Services
|
|
61
|
-
/** @type { Record<string,Service> } */ services =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
get server() { return super.server = require('../server') }
|
|
56
|
+
/** @type { Record<string,Service> } */ services = new class {
|
|
57
|
+
*[Symbol.iterator](){ for (let e in this) yield this[e] }
|
|
58
|
+
get _pending(){ return this.#pending ??= {} } #pending
|
|
59
|
+
}
|
|
60
|
+
get server() { return super.server = require('../server.js') }
|
|
66
61
|
get serve() { return super.serve = require('./srv/cds-serve') }
|
|
67
62
|
get connect() { return super.connect = require('./srv/cds-connect') }
|
|
68
63
|
get outboxed() { return super.outboxed = require('../libx/outbox').outboxed }
|
|
@@ -70,6 +65,7 @@ const cds = module.exports = global.cds = new class cds extends EventEmitter {
|
|
|
70
65
|
get middlewares() { return super.middlewares = require('./srv/middlewares') }
|
|
71
66
|
get odata() { return super.odata = require('../libx/odata') }
|
|
72
67
|
get auth() { return super.auth = require('./auth') }
|
|
68
|
+
shutdown() { this.app?.server && process.exit() } // is overridden in bin/serve.js
|
|
73
69
|
|
|
74
70
|
// Core Services API
|
|
75
71
|
get Service() { return super.Service = require('./srv/srv-api') }
|
|
@@ -77,6 +73,7 @@ const cds = module.exports = global.cds = new class cds extends EventEmitter {
|
|
|
77
73
|
get Request() { return super.Request = require('./req/request') }
|
|
78
74
|
get Event() { return super.Event = require('./req/event') }
|
|
79
75
|
get User() { return super.User = require('./req/user') }
|
|
76
|
+
get validate() { return super.validate = require('./req/validate.js') }
|
|
80
77
|
|
|
81
78
|
// Services, Protocols and Periphery
|
|
82
79
|
get ApplicationService() { return super.ApplicationService = require('../libx/_runtime/common/Service.js') }
|
|
@@ -94,15 +91,13 @@ const cds = module.exports = global.cds = new class cds extends EventEmitter {
|
|
|
94
91
|
get utils() { return super.utils = require('./utils/cds-utils') }
|
|
95
92
|
get error() { return super.error = require('./log/cds-error') }
|
|
96
93
|
get exec() { return super.exec = require('../bin/serve').exec }
|
|
97
|
-
get test() { return super.test = require('./
|
|
94
|
+
get test() { return super.test = require('./test/cds-test.js') }
|
|
98
95
|
get log() { return super.log = require('./log/cds-log') }
|
|
99
96
|
get debug() { return super.debug = this.log.debug }
|
|
100
|
-
get lazify() { return lazify }
|
|
101
|
-
get lazified() { return lazify }
|
|
102
97
|
clone(x) { return structuredClone(x) }
|
|
103
|
-
exit(code){ return cds.shutdown ? cds.shutdown() : process.exit(code) }
|
|
104
98
|
|
|
105
99
|
// Querying and Databases
|
|
100
|
+
get infer(){ return super.infer = require('./ql/infer') }
|
|
106
101
|
get txs() { return super.txs = new this.Service('cds.tx') }
|
|
107
102
|
get ql() { return super.ql = require('./ql/cds-ql') }
|
|
108
103
|
tx (..._) { return (this.db || this.txs).tx(..._) }
|
|
@@ -116,38 +111,27 @@ const cds = module.exports = global.cds = new class cds extends EventEmitter {
|
|
|
116
111
|
delete (..._) { return (this.db || this.error._no_primary_db).delete(..._) }
|
|
117
112
|
disconnect (..._) { return (this.db || this.error._no_primary_db).disconnect(..._) }
|
|
118
113
|
|
|
119
|
-
//
|
|
120
|
-
/** @deprecated */
|
|
114
|
+
// Deprecated stuff to be removed in upcomming releases...
|
|
115
|
+
/** @deprecated */ get lazified() { return this.lazify }
|
|
116
|
+
/** @deprecated */ get lazify() { return super.lazify = require('./utils/lazify.js') }
|
|
121
117
|
/** @deprecated */ get in() { return super.in = cwd => !cwd ? this : {__proto__:this, cwd, env: this.env.for('cds',cwd) } }
|
|
118
|
+
/** @deprecated */ exit(code){ return this.app?.server ? this.shutdown() : process.exit(code) }
|
|
119
|
+
/** @deprecated */ transaction (..._) { return (this.db||this.error._no_primary_db).transaction(..._) }
|
|
122
120
|
}
|
|
123
121
|
|
|
124
|
-
//
|
|
125
|
-
extend (global) .with (class {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
Object.defineProperty (global,p, { configurable:1,
|
|
141
|
-
set(v){ Object.defineProperty (global,p,{ value:v, writable:true }) },
|
|
142
|
-
get(){ cds.test; return global[p] }
|
|
143
|
-
})
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
// Allow for import cds from '@sap/cds' without esModuleInterop
|
|
147
|
-
// FIXME: remove this flag in the next release. Only serves as fallback switch if people report issues with value:cds
|
|
148
|
-
// Setting it to module.exports lead to issues with vitest while setting it to cds apparently works fine.
|
|
149
|
-
if (process.env.CDS_ESM_INTEROP_DEFAULT) {
|
|
150
|
-
Object.defineProperties(module.exports, { default: {value:module.exports}, __esModule: {value:true} })
|
|
151
|
-
} else {
|
|
152
|
-
Object.defineProperties(module.exports, { default: {value:cds}, __esModule: {value:true} })
|
|
153
|
-
}
|
|
122
|
+
// Add global convenience shortcuts for cds.ql and cds.parse commands
|
|
123
|
+
cds.extend (global) .with ( class globals {
|
|
124
|
+
get SELECT() { return cds.ql.SELECT }
|
|
125
|
+
get INSERT() { return cds.ql.INSERT }
|
|
126
|
+
get UPSERT() { return cds.ql.UPSERT }
|
|
127
|
+
get UPDATE() { return cds.ql.UPDATE }
|
|
128
|
+
get DELETE() { return cds.ql.DELETE }
|
|
129
|
+
get CREATE() { return cds.ql.CREATE }
|
|
130
|
+
get DROP() { return cds.ql.DROP }
|
|
131
|
+
get CDL() { return cds.parse.CDL }
|
|
132
|
+
get CQL() { return cds.parse.CQL }
|
|
133
|
+
get CXL() { return cds.parse.CXL }
|
|
134
|
+
} .prototype )
|
|
135
|
+
|
|
136
|
+
// Allow for `import cds from '@sap/cds'` without `esModuleInterop` in tsconfig.json
|
|
137
|
+
Object.defineProperties (module.exports, { default: {value:cds}, __esModule: {value:true} })
|
package/lib/log/cds-error.js
CHANGED
|
@@ -5,30 +5,30 @@ const { format, inspect } = require('../utils/cds-utils')
|
|
|
5
5
|
* Constructs and optionally throws an Error object.
|
|
6
6
|
* Usage variants:
|
|
7
7
|
*
|
|
8
|
-
* cds.error
|
|
9
|
-
* cds.error (
|
|
10
|
-
* cds.error (
|
|
11
|
-
* cds.error
|
|
8
|
+
* cds.error (404, 'Not Found', { code, ... })
|
|
9
|
+
* cds.error ('Not Found', { code, ... })
|
|
10
|
+
* cds.error ({ code, message, ... })
|
|
11
|
+
* cds.error `template string usage variant`
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* useful for usages like that:
|
|
13
|
+
* When called with `new` the newly created Error is returned.
|
|
14
|
+
* When called without `new` the error is thrown immediately.
|
|
15
|
+
* The latter is useful for usages like that:
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* let x = y || cds.error `'y' must be truthy, got: ${y}`
|
|
18
|
+
*
|
|
19
|
+
* @param {number} [status] - HTTP status code
|
|
20
|
+
* @param {string} [message] - Error message
|
|
21
|
+
* @param {object} [details] - Additional error details
|
|
22
|
+
* @param {Function} [caller] - The function calling this
|
|
18
23
|
*/
|
|
19
|
-
const error = exports = module.exports = function cds_error ( message, details, caller ) {
|
|
20
|
-
|
|
21
|
-
if (message
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
;[ caller, details ] = [ details ]
|
|
28
|
-
}
|
|
29
|
-
Error.captureStackTrace (e,caller||error)
|
|
30
|
-
if (details) Object.assign (e,details)
|
|
31
|
-
if (new.target) return e; else throw e
|
|
24
|
+
const error = exports = module.exports = function cds_error ( status, message, details, caller ) {
|
|
25
|
+
if (typeof status !== 'number') [ status, message, details, caller, status ] = status.raw ? [ undefined, error.message(...arguments) ] : [ undefined, status, message, details ]
|
|
26
|
+
if (typeof message === 'object') [ message, details, caller ] = [ undefined, message, details ]
|
|
27
|
+
const err = details?.stack ? details : Object.assign (new Error (message, details), details)
|
|
28
|
+
if (caller) Error.captureStackTrace (err, caller); else Error.captureStackTrace (err, error)
|
|
29
|
+
if (status) err.status = status
|
|
30
|
+
if (new.target) return err
|
|
31
|
+
else throw err
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
|
package/lib/ql/cds-ql.js
CHANGED
|
@@ -14,7 +14,7 @@ require = path => { // eslint-disable-line no-global-assign
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
module.exports = exports = {
|
|
17
|
-
Query,
|
|
17
|
+
Query,
|
|
18
18
|
SELECT: require('./SELECT'),
|
|
19
19
|
INSERT: require('./INSERT'),
|
|
20
20
|
UPSERT: require('./UPSERT'),
|
|
@@ -30,10 +30,10 @@ exports.clone = function (q,_) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
exports.query = function (q) {
|
|
33
|
-
if (q instanceof Query) return q
|
|
34
|
-
let kind = Object.keys(q)[0]
|
|
35
|
-
let clazz = exports[kind]
|
|
36
|
-
return
|
|
33
|
+
if (!q || q instanceof Query) return q
|
|
34
|
+
let kind = Object.keys(q)[0]; if (!kind) return
|
|
35
|
+
let clazz = exports[kind]; if (!clazz) return q
|
|
36
|
+
return new clazz (q[kind])
|
|
37
37
|
},
|
|
38
38
|
|
|
39
39
|
exports._reset = ()=>{ // for strange tests only
|
package/lib/req/cds-context.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { AsyncLocalStorage } = require ('async_hooks')
|
|
2
2
|
const { EventEmitter } = require('events')
|
|
3
|
+
const { isStandardError } = require('../../libx/_runtime/common/error/standardError')
|
|
3
4
|
const EventContext = require('./context'), ec4 = v => {
|
|
4
5
|
if (v instanceof EventContext || typeof v !== 'object') return v
|
|
5
6
|
if (v.context) return v.context
|
|
@@ -38,6 +39,10 @@ module.exports = new class extends AsyncLocalStorage {
|
|
|
38
39
|
const em = new EventEmitter
|
|
39
40
|
if (o?.every) {
|
|
40
41
|
em.timer = setInterval(fx, o.every)
|
|
42
|
+
em.on('failed', e => { if (isStandardError(e) && cds.env.server.shutdown_on_uncaught_errors) {
|
|
43
|
+
cds.log().error('❗️Uncaught', e)
|
|
44
|
+
return cds.shutdown(e)}
|
|
45
|
+
})
|
|
41
46
|
cds.on('shutdown', () => clearInterval(em.timer))
|
|
42
47
|
} else {
|
|
43
48
|
em.timer = (o?.after ? setTimeout(fx, o.after) : setImmediate(fx)).unref()
|
package/lib/req/context.js
CHANGED
|
@@ -99,10 +99,10 @@ class EventContext {
|
|
|
99
99
|
if (l) super.locale = super._locale = l
|
|
100
100
|
}
|
|
101
101
|
get locale() {
|
|
102
|
-
return super.locale = this._propagated.locale || req_locale(this._.req)
|
|
102
|
+
return super.locale = this._propagated.locale || req_locale.from(this._.req)
|
|
103
103
|
}
|
|
104
104
|
get _locale() {
|
|
105
|
-
return super._locale = this._propagated._locale || req_locale.
|
|
105
|
+
return super._locale = this._propagated._locale || req_locale.raw(this._.req)
|
|
106
106
|
|| this.hasOwnProperty('locale') && this.locale // eslint-disable-line no-prototype-builtins
|
|
107
107
|
}
|
|
108
108
|
|
package/lib/req/locale.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
const {i18n} = require('../index').env
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
function normalized_locale_from (req) {
|
|
4
|
+
const header = original_locale_from (req); if (!header) return i18n.default_language
|
|
5
|
+
const locale = header .match (/^[^,; ]*/)[0] .replace (/-/g,'_')
|
|
6
|
+
return SPECIAL[locale] //> keeps language and region for those in i18n.preserved_locales
|
|
7
|
+
|| locale.match(/[A-Za-z]+/)?.[0].toLowerCase() //> keeps language only
|
|
8
|
+
|| i18n.default_language
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function original_locale_from (req) {
|
|
12
|
+
return !req ? undefined :
|
|
13
|
+
req.query['sap-locale'] || SAP_LANGUAGES[req.query['sap-language']] ||
|
|
14
|
+
req.headers['x-sap-request-language'] ||
|
|
15
|
+
req.headers['accept-language']
|
|
16
|
+
}
|
|
10
17
|
|
|
11
18
|
const SAP_LANGUAGES = {
|
|
12
19
|
'1Q': 'en_US_x_saptrc',
|
|
@@ -14,18 +21,15 @@ const SAP_LANGUAGES = {
|
|
|
14
21
|
'3Q': 'en_US_x_saprigi'
|
|
15
22
|
}
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
function req_locale (req) {
|
|
24
|
-
const locale = from_req(req); if (!locale) return i18n.default_language
|
|
25
|
-
const loc = locale.replace(/-/g,'_').match(/^[^,; ]*/)[0]
|
|
26
|
-
return INCLUDE_LIST[loc]
|
|
27
|
-
|| /^([a-z]+)/i.test(loc) && RegExp.$1.toLowerCase()
|
|
28
|
-
|| i18n.default_language
|
|
24
|
+
const SPECIAL = {
|
|
25
|
+
...Object.fromEntries (i18n.preserved_locales.map(l=>[l.toUpperCase(),l])), // REVISIT: why uppercase?
|
|
26
|
+
...Object.fromEntries (i18n.preserved_locales.map(l=>[l,l])),
|
|
27
|
+
en_US_x_saptrc: 'en_US_saptrc',
|
|
28
|
+
en_US_x_sappsd: 'en_US_sappsd',
|
|
29
|
+
en_US_x_saprigi: 'en_US_saprigi',
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
module.exports = Object.assign(
|
|
32
|
+
module.exports = Object.assign (normalized_locale_from, {
|
|
33
|
+
from: normalized_locale_from,
|
|
34
|
+
raw: original_locale_from
|
|
35
|
+
})
|
package/lib/srv/cds-serve.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const cds = require ('..')
|
|
2
2
|
const { Service } = cds.service.factory
|
|
3
|
-
const { serve } = cds.service.protocols
|
|
4
3
|
const _pending = cds.services._pending ??= {}
|
|
5
4
|
const _ready = Symbol()
|
|
6
5
|
const TRACE = cds.debug('trace')
|
|
@@ -102,6 +101,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
|
|
|
102
101
|
|
|
103
102
|
/** Fluent method to serve constructed providers to express app */
|
|
104
103
|
in (app) {
|
|
104
|
+
const { serve } = cds.service.protocols
|
|
105
105
|
if (!cds.env.features.odata_new_adapter && cds.edmxs) ready = ready.then (()=> cds.edmxs)
|
|
106
106
|
ready = ready.then (()=> all.forEach (each => {
|
|
107
107
|
const d = each.definition
|