@sap/cds 7.7.0 → 7.7.2
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 +17 -0
- package/lib/compile/cds-compile.js +0 -1
- package/lib/compile/etc/_localized.js +1 -15
- package/lib/compile/for/java.js +17 -3
- package/lib/compile/for/nodejs.js +1 -1
- package/lib/log/format/json.js +3 -0
- package/lib/utils/tar.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -1
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +1 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +1 -3
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +12 -22
- package/libx/odata/utils/index.js +7 -3
- package/libx/rest/middleware/update.js +4 -1
- package/package.json +1 -1
- package/lib/compile/for/drafts.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@
|
|
|
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 7.7.2 - 2024-03-11
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Requests to actions/functions on entities in draft state via navigation.
|
|
12
|
+
- PUT/PATCH with if-none-match: * forces insert
|
|
13
|
+
|
|
14
|
+
## Version 7.7.1 - 2024-03-06
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- JWT authentication for Event Mesh endpoints
|
|
19
|
+
- `cds.log`'s json formatter: ensure `type` is set (required on kubernetes until CLS defaults this)
|
|
20
|
+
- Erroneously generated foreign keys in `req.data` for UPDATE using path expressions
|
|
21
|
+
- `INSERT.columns.rows` for multiple nested composition of aspects
|
|
22
|
+
- Paths passed to `tar` on Windows are now normalized to use forward slashes.
|
|
23
|
+
|
|
7
24
|
## Version 7.7.0 - 2024-02-26
|
|
8
25
|
|
|
9
26
|
### Added
|
|
@@ -6,7 +6,6 @@ const compile = module.exports = Object.assign (cds_compile, {
|
|
|
6
6
|
for: new class {
|
|
7
7
|
get java(){ return super.java = require('./for/java') }
|
|
8
8
|
get nodejs() { return super.nodejs = require('./for/nodejs') }
|
|
9
|
-
get drafts() { return super.drafts = require('./for/drafts') }
|
|
10
9
|
get lean_drafts() { return super.lean_drafts = require('./for/lean_drafts') }
|
|
11
10
|
get odata() { return super.odata = require('./for/odata') }
|
|
12
11
|
get sql() { return super.sql = require('./for/sql') }
|
|
@@ -98,21 +98,7 @@ function unfold_csn (m) { // NOSONAR
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
const _is_localized = (
|
|
102
|
-
!env.features._ucsn_ ? d => d.own('$localized') // as set by compiler in compile.for.odata
|
|
103
|
-
: (d,_path={}) => d.own('$localized', ()=>{ // calculate our own for effective CSNs
|
|
104
|
-
// if (d.elements.texts && d.elements.texts.target === `${d.name}.texts`) return d.set($localized,true)
|
|
105
|
-
if (!d.elements || d.name.endsWith('.texts')) return false
|
|
106
|
-
for (let each in d.elements) {
|
|
107
|
-
const e = d.elements [each]
|
|
108
|
-
if (e.localized || e._target && !(_path && e._target.name in _path) && _is_localized(e._target,Object.assign(_path, { [d.name]:1 }))) {
|
|
109
|
-
return true
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return false
|
|
113
|
-
})
|
|
114
|
-
)
|
|
115
|
-
|
|
101
|
+
const _is_localized = d => d.own('$localized') // as set by compiler in compile.for.odata
|
|
116
102
|
|
|
117
103
|
// feature-toggled exports
|
|
118
104
|
module.exports = { unfold_csn, unfold_ddl }
|
package/lib/compile/for/java.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
// The only entry point to produce CSNs consumed by the Java Runtime
|
|
2
|
+
|
|
1
3
|
const cds = require ('../../index')
|
|
2
4
|
|
|
5
|
+
// TODO: compiler functions to clarify - publish as individual API functions or
|
|
6
|
+
// have a compiler API function for.java?
|
|
7
|
+
function _4java (csn,o) {
|
|
8
|
+
const compile = require ('../cdsc');
|
|
9
|
+
const { addTenantFields } = require ('@sap/cds-compiler/lib/transform/addTenantFields');
|
|
10
|
+
const _4draft = require ('@sap/cds-compiler/lib/transform/draft/odata');
|
|
11
|
+
const dsn = JSON.parse (JSON.stringify (csn)) // REVISIT: workaround for bad test setup
|
|
12
|
+
o = compile._options.for.odata(o); // get compiler options, see compile.for.odata
|
|
13
|
+
if (o.tenantDiscriminator) addTenantFields (dsn, o);
|
|
14
|
+
return _4draft (dsn, o);
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
module.exports = function cds_compile_for_java (csn,o) {
|
|
4
18
|
if ('_4java' in csn) return csn._4java
|
|
5
|
-
|
|
6
|
-
dsn = cds.compile.for.
|
|
19
|
+
// cds.minify (csn) ?
|
|
20
|
+
const dsn = (!cds.env.features._ucsn_) ? cds.compile.for.odata (csn,o||{}) : _4java (csn,o||{});
|
|
7
21
|
if (dsn.definitions) for (let [name,d] of Object.entries(dsn.definitions)) {
|
|
8
22
|
// Add @cds.external to external services
|
|
9
23
|
if (d.kind === 'service' && name in cds.requires) d['@cds.external'] = true
|
|
@@ -16,4 +30,4 @@ module.exports = function cds_compile_for_java (csn,o) {
|
|
|
16
30
|
Object.defineProperty (csn, '_4java', {value:dsn})
|
|
17
31
|
Object.defineProperty (dsn, '_4java', {value:dsn})
|
|
18
32
|
return dsn
|
|
19
|
-
}
|
|
33
|
+
}
|
|
@@ -6,7 +6,7 @@ module.exports = function cds_compile_for_nodejs (csn,o) {
|
|
|
6
6
|
if ('_4nodejs' in csn) return csn._4nodejs
|
|
7
7
|
TRACE?.time('cds.compile 4nodejs'.padEnd(22))
|
|
8
8
|
let dsn = csn // cds.minify (csn)
|
|
9
|
-
dsn = cds.compile.for.
|
|
9
|
+
dsn = cds.compile.for.odata (csn,o) //> creates a partial copy -> avoid any cds.linked() before
|
|
10
10
|
dsn = unfold_csn (dsn)
|
|
11
11
|
dsn = cds.linked (dsn)
|
|
12
12
|
if (cds.env.fiori.lean_draft) cds.compile.for.lean_drafts(dsn, o)
|
package/lib/log/format/json.js
CHANGED
|
@@ -86,6 +86,9 @@ module.exports = function format(module, level, ...args) {
|
|
|
86
86
|
// append remaining args via util.format()
|
|
87
87
|
if (args.length) toLog.msg = toLog.msg ? util.format(toLog.msg, ...args) : util.format(...args)
|
|
88
88
|
|
|
89
|
+
// ensure type (required on kubernetes if logs are pulled instead of pushed through binding)
|
|
90
|
+
toLog.type ??= 'log'
|
|
91
|
+
|
|
89
92
|
// REVISIT: should not be necessary with new protocol adapters
|
|
90
93
|
// 4xx: lower to warning (if error)
|
|
91
94
|
if (toLog.level && toLog.level.match(/error/i) && _is4xx(toLog)) toLog.level = 'warn'
|
package/lib/utils/tar.js
CHANGED
|
@@ -11,7 +11,7 @@ const _resolve = (...x) => path.resolve (cds.root,...x)
|
|
|
11
11
|
// tar does not work properly on Windows (by npm/jest tests) w/o this change
|
|
12
12
|
const win = path => {
|
|
13
13
|
if (!path) return path
|
|
14
|
-
if (typeof path === 'string') return path.replace('C:', '//localhost/c$')
|
|
14
|
+
if (typeof path === 'string') return path.replace('C:', '//localhost/c$').replace(/\\+/g, '/')
|
|
15
15
|
if (Array.isArray(path)) return path.map(el => win(el))
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -101,7 +101,9 @@ const _updateThenCreate = async (req, odataReq, odataRes, tx) => {
|
|
|
101
101
|
result = await tx.dispatch(req)
|
|
102
102
|
} catch (e) {
|
|
103
103
|
const is404 = e.code === 404 || e.status === 404 || e.statusCode === 404
|
|
104
|
-
|
|
104
|
+
const isForcedInsert =
|
|
105
|
+
(e.code === 412 || e.status === 412 || e.statusCode === 412) && req.headers['if-none-match'] === '*'
|
|
106
|
+
if ((is404 || isForcedInsert) && _isUpsertAllowed(req)) {
|
|
105
107
|
// PUT/ PATCH with if-match header means "only if already exists", i.e., no insert if not
|
|
106
108
|
if (req.headers['if-match'])
|
|
107
109
|
throw Object.assign(new Error('412'), { statusCode: 412 })
|
|
@@ -37,7 +37,7 @@ function where2obj(where, target = null, data = {}) {
|
|
|
37
37
|
// optional validation if target is passed
|
|
38
38
|
if (target) {
|
|
39
39
|
const colEl = target.elements[colName]
|
|
40
|
-
if (!colEl || !
|
|
40
|
+
if (!colEl || !colEl.key) continue
|
|
41
41
|
}
|
|
42
42
|
const opWhere = where[i + 1]
|
|
43
43
|
const valWhere = where[i + 2]
|
|
@@ -803,7 +803,7 @@ class JoinCQNFromExpanded {
|
|
|
803
803
|
return 'DRAFT.DraftAdministrativeData'
|
|
804
804
|
}
|
|
805
805
|
|
|
806
|
-
if (isActiveRequired && !defaultLanguage) {
|
|
806
|
+
if (this._SELECT.localized !== false && isActiveRequired && !defaultLanguage) {
|
|
807
807
|
const locale = this._locale ? `${this._locale}.` : ''
|
|
808
808
|
const localized = `localized.${locale}${target}`
|
|
809
809
|
if (this._csn.definitions[localized]) {
|
|
@@ -243,7 +243,7 @@ class InsertBuilder extends BaseBuilder {
|
|
|
243
243
|
if (this.uuidKeys) {
|
|
244
244
|
for (const key of this.uuidKeys) {
|
|
245
245
|
if (!flattenColumnMap.get(key)) {
|
|
246
|
-
columns.push(
|
|
246
|
+
columns.push(this._quoteElement(key))
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
}
|
|
@@ -469,9 +469,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
469
469
|
if (req.target.actions?.[req.event] && draftParams.IsActiveEntity === false) {
|
|
470
470
|
if (query.SELECT?.from?.ref) query.SELECT.from.ref = _redirectRefToDrafts(query.SELECT.from.ref, this.model)
|
|
471
471
|
const rootQuery = query.clone()
|
|
472
|
-
|
|
473
|
-
columns.push({ ref: ['DraftAdministrativeData'], expand: [{ ref: ['InProcessByUser'] }] })
|
|
474
|
-
rootQuery.SELECT.columns = columns
|
|
472
|
+
rootQuery.SELECT.columns = [{ ref: ['DraftAdministrativeData'], expand: [{ ref: ['InProcessByUser'] }] }]
|
|
475
473
|
rootQuery.SELECT.one = true
|
|
476
474
|
rootQuery.SELECT.from = { ref: [query.SELECT.from.ref[0]] }
|
|
477
475
|
const root = await cds.run(rootQuery)
|
|
@@ -17,15 +17,15 @@ class EndpointRegistry {
|
|
|
17
17
|
if (isSecured()) {
|
|
18
18
|
if (cds.requires.auth.impl) {
|
|
19
19
|
if (cds.env.requires.middlewares !== false) {
|
|
20
|
-
|
|
20
|
+
cds.app.use(basePath, cds.middlewares.before) // contains auth, trace, context
|
|
21
21
|
} else {
|
|
22
22
|
const impl = _require(cds.resolve(cds.requires.auth.impl))
|
|
23
|
-
|
|
23
|
+
cds.app.use(basePath, impl)
|
|
24
24
|
}
|
|
25
25
|
} else {
|
|
26
26
|
if (cds.env.requires.middlewares !== false) {
|
|
27
27
|
const jwt_auth = require('../../../../lib/auth/jwt-auth.js')
|
|
28
|
-
|
|
28
|
+
cds.app.use(basePath, jwt_auth(cds.requires.auth))
|
|
29
29
|
} else {
|
|
30
30
|
const JWTStrategy = require('../../auth/strategies/JWT.js')
|
|
31
31
|
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
@@ -33,28 +33,22 @@ class EndpointRegistry {
|
|
|
33
33
|
// REVISIT: It's unclear if the credentials from cds.requires.auth need to be used here.
|
|
34
34
|
// In principle, user-facing endpoints might differ from messaging ones.
|
|
35
35
|
passport.use(new JWTStrategy(cds.requires.auth.credentials))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
cds.app.use(path, passport.authenticate('JWT', { session: false }))
|
|
39
|
-
})
|
|
36
|
+
cds.app.use(basePath, passport.initialize())
|
|
37
|
+
cds.app.use(basePath, passport.authenticate('JWT', { session: false }))
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
40
|
// unsuccessful auth doesn't automatically reject!
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
next()
|
|
48
|
-
})
|
|
41
|
+
cds.app.use(basePath, (req, res, next) => {
|
|
42
|
+
// REVISIT: we should probably pass an error into next so that a (custom) error middleware can handle it
|
|
43
|
+
if (!req.user) res.status(401).json({ error: ODATA_UNAUTHORIZED })
|
|
44
|
+
next()
|
|
49
45
|
})
|
|
50
46
|
} else if (process.env.NODE_ENV === 'production') {
|
|
51
47
|
LOG.warn('Messaging endpoints not secured')
|
|
52
48
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
cds.app.use(path, express.urlencoded({ extended: true }))
|
|
57
|
-
})
|
|
49
|
+
cds.app.use(basePath, express.json({ type: 'application/*+json' }))
|
|
50
|
+
cds.app.use(basePath, express.json())
|
|
51
|
+
cds.app.use(basePath, express.urlencoded({ extended: true }))
|
|
58
52
|
LOG._debug && LOG.debug('Register inbound endpoint', { basePath, method: 'OPTIONS' })
|
|
59
53
|
|
|
60
54
|
// Clear cds.context as it would interfere with subsequent transactions
|
|
@@ -62,10 +56,6 @@ class EndpointRegistry {
|
|
|
62
56
|
cds.context = undefined
|
|
63
57
|
next()
|
|
64
58
|
})
|
|
65
|
-
cds.app.use(deployPath, (_req, _res, next) => {
|
|
66
|
-
cds.context = undefined
|
|
67
|
-
next()
|
|
68
|
-
})
|
|
69
59
|
|
|
70
60
|
cds.app.options(basePath, (req, res) => {
|
|
71
61
|
try {
|
|
@@ -295,9 +295,13 @@ const getKeysFromPath = (from, srv) => {
|
|
|
295
295
|
const join = [...relation[0].xpr]
|
|
296
296
|
while (join.length >= 3) {
|
|
297
297
|
const [left, _, right] = join.splice(0, 4)
|
|
298
|
-
if (left.ref?.[0] === 'target')
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
if (left.ref?.[0] === 'target') {
|
|
299
|
+
if (left.ref[1] in keys) break // we already added the foreign key for the last segment
|
|
300
|
+
keys[left.ref[1]] = 'val' in right ? right.val : seg_keys[right.ref[1]]
|
|
301
|
+
} else if (right.ref?.[0] === 'target') {
|
|
302
|
+
if (right.ref[1] in keys) break // we already added the foreign key for the last segment
|
|
303
|
+
keys[right.ref[1]] = 'val' in left ? left.val : seg_keys[left.ref[1]]
|
|
304
|
+
} else {
|
|
301
305
|
// REVISIT: what to do here?
|
|
302
306
|
}
|
|
303
307
|
}
|
|
@@ -22,7 +22,10 @@ module.exports = srv => async _req => {
|
|
|
22
22
|
result = await srv.dispatch(new RestRequest({ query, _target, method: _req.method, params: _params }))
|
|
23
23
|
if (_params && result) Object.assign(result, _params[_params.length - 1])
|
|
24
24
|
} catch (e) {
|
|
25
|
-
|
|
25
|
+
const is404 = e.code === 404 || e.status === 404 || e.statusCode === 404
|
|
26
|
+
const isForcedInsert =
|
|
27
|
+
(e.code === 412 || e.status === 412 || e.statusCode === 412) && _req.headers['if-none-match'] === '*'
|
|
28
|
+
if ((is404 || isForcedInsert) && UPSERT_ALLOWED) {
|
|
26
29
|
query = INSERT.into(query.UPDATE.entity).entries(
|
|
27
30
|
_params ? Object.assign(_data, _params[_params.length - 1]) : _data
|
|
28
31
|
)
|
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
let _unfold = (..._) => (_unfold = require ('@sap/cds-compiler/lib/transform/draft/odata')) (..._)
|
|
2
|
-
const cds = require ('../../index')
|
|
3
|
-
module.exports = function cds_compile_for_drafts (csn,o) {
|
|
4
|
-
if (!cds.env.features._ucsn_) return cds.compile.for.odata (csn,o)
|
|
5
|
-
csn = JSON.parse (JSON.stringify (csn)) // REVISIT: workaround for bad test setup
|
|
6
|
-
csn = _unfold (csn,o||{})
|
|
7
|
-
return csn
|
|
8
|
-
}
|