@sap/cds 7.0.0 → 7.0.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 +18 -0
- package/lib/compile/for/lean_drafts.js +3 -0
- package/lib/compile/load.js +2 -2
- package/lib/compile/to/srvinfo.js +17 -1
- package/lib/dbs/cds-deploy.js +6 -6
- package/lib/env/cds-env.js +5 -5
- package/lib/srv/middlewares/ctx-model.js +1 -1
- package/lib/srv/protocols/odata-v4.js +9 -4
- package/lib/srv/srv-tx.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
- package/libx/_runtime/fiori/lean-draft.js +9 -0
- package/package.json +1 -1
- package/server.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@
|
|
|
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.0.2 - 2023-07-06
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Glitch in `cds.deploy` if no change was applied
|
|
12
|
+
- Detection of `.cdsrc-private.json` during startup
|
|
13
|
+
- Respect capabilities annotation for draft events
|
|
14
|
+
- `cds compile --to serviceinfo` returns correct service paths again
|
|
15
|
+
|
|
16
|
+
## Version 7.0.1 - 2023-07-03
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Feature toggle detection in single tenant mode
|
|
21
|
+
- Log output for OData $batch requests
|
|
22
|
+
- Avoid "catastrophic backtracking" issue in okra's tokenizer
|
|
23
|
+
- Transaction marked as committed too early
|
|
24
|
+
|
|
7
25
|
## Version 7.0.0 - 2023-06-21
|
|
8
26
|
|
|
9
27
|
### Added
|
|
@@ -72,6 +72,9 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
|
|
|
72
72
|
if (draft['@readonly']) draft['@readonly'] = undefined
|
|
73
73
|
if (draft['@insertonly']) draft['@insertonly'] = undefined
|
|
74
74
|
if (draft['@restrict']) draft['@restrict'] = undefined
|
|
75
|
+
if ('@Capabilities.DeleteRestrictions.Deletable' in draft) draft['@Capabilities.DeleteRestrictions.Deletable'] = undefined
|
|
76
|
+
if ('@Capabilities.InsertRestrictions.Insertable' in draft) draft['@Capabilities.InsertRestrictions.Insertable'] = undefined
|
|
77
|
+
if ('@Capabilities.UpdateRestrictions.Updatable' in draft) draft['@Capabilities.UpdateRestrictions.Updatable'] = undefined
|
|
75
78
|
|
|
76
79
|
// Recursively add drafts for compositions
|
|
77
80
|
for (const each in draft.elements) {
|
package/lib/compile/load.js
CHANGED
|
@@ -13,8 +13,8 @@ module.exports = exports = function load (files, options) {
|
|
|
13
13
|
|
|
14
14
|
exports.parsed = function cds_get (files, options, _flavor) { // NOSONAR
|
|
15
15
|
|
|
16
|
-
TRACE?.time('cds.load model ')
|
|
17
16
|
const o = typeof options === 'string' ? { flavor:options } : options || {}
|
|
17
|
+
if (!o.silent) TRACE?.time('cds.load model ')
|
|
18
18
|
if (!files) files = ['*']; else if (!Array.isArray(files)) files = [files]
|
|
19
19
|
if (o.files || o.flavor === 'files') return cds.resolve(files,o)
|
|
20
20
|
if (o.sources || o.flavor === 'sources') return _sources4 (cds.resolve(files,o))
|
|
@@ -32,7 +32,7 @@ exports.parsed = function cds_get (files, options, _flavor) { // NOSONAR
|
|
|
32
32
|
|
|
33
33
|
const _finalize = (csn,o) => {
|
|
34
34
|
if (!o.silent) cds.emit ('loaded', csn)
|
|
35
|
-
TRACE?.timeEnd('cds.load model ')
|
|
35
|
+
if (!o.silent) TRACE?.timeEnd('cds.load model ')
|
|
36
36
|
return csn
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -35,15 +35,31 @@ module.exports = (model, options={}) => {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
function _makeNode(service) {
|
|
38
|
+
const path = _effectiveNodePath(service)
|
|
38
39
|
return {
|
|
39
40
|
name: service.name,
|
|
40
|
-
urlPath: _url4 (
|
|
41
|
+
urlPath: _url4 (path),
|
|
41
42
|
destination: 'srv-api', // the name to register in xs-app.json
|
|
42
43
|
runtime: 'Node.js',
|
|
43
44
|
location: service.$location
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
// TODO use a function from cds.service... instead
|
|
49
|
+
function _effectiveNodePath(service) {
|
|
50
|
+
if (service['@path']?.[0] === '/') { // absolute path given
|
|
51
|
+
return service['@path']
|
|
52
|
+
}
|
|
53
|
+
const { ProtocolAdapter } = cds.service.protocols
|
|
54
|
+
const prots = ProtocolAdapter.protocols4(service)
|
|
55
|
+
const prot = prots.find(p => p.kind.startsWith('odata')) || prots[0] // prefer odata for compat. reasons
|
|
56
|
+
if (prot.path && prot.path.startsWith('/')) {
|
|
57
|
+
return prot.path
|
|
58
|
+
}
|
|
59
|
+
const rootPath = cds.env.requires.middlewares ? ProtocolAdapter.protocols[prot.kind]?.path || '' : ''
|
|
60
|
+
return join(rootPath, prot.path || cds.service.path4(service))
|
|
61
|
+
}
|
|
62
|
+
|
|
47
63
|
// the URL path that is *likely* effective at runtime
|
|
48
64
|
function _url4 (p) {
|
|
49
65
|
return normalize (p.replace(/^\/+/, '') + '/') //> /foo/bar -> foo/bar/
|
package/lib/dbs/cds-deploy.js
CHANGED
|
@@ -15,7 +15,7 @@ module.exports = exports = function cds_deploy (model,options,csvs) {
|
|
|
15
15
|
DEBUG = cds.debug('deploy')
|
|
16
16
|
return {
|
|
17
17
|
/** @param {import('@sap/cds/lib/srv/srv-api')} db */
|
|
18
|
-
async to(db, o = options ||
|
|
18
|
+
async to(db, o = options || {}) {
|
|
19
19
|
|
|
20
20
|
const TRACE = cds.debug('trace')
|
|
21
21
|
TRACE?.time('cds.deploy db ')
|
|
@@ -90,8 +90,8 @@ exports.create = async function cds_deploy_create (db, csn=db.model, o) {
|
|
|
90
90
|
} else {
|
|
91
91
|
await db.run(`UPDATE cds_model SET csn = ?`, after)
|
|
92
92
|
}
|
|
93
|
-
db.options.schema_evolution = 'auto' // for updating package.json
|
|
94
93
|
}
|
|
94
|
+
o.schema_evolution = 'auto' // for INSERT_from4 below
|
|
95
95
|
// cds deploy --model-only > fills in table cds_model above
|
|
96
96
|
if (o['model-only']) return o.dry && console.log(after)
|
|
97
97
|
// cds deploy -- with auto schema evolution > upgrade by applying delta to former model
|
|
@@ -253,9 +253,6 @@ exports.resources = async function cds_deploy_resources (csn, opts) {
|
|
|
253
253
|
const folders = await cds_deploy_resources.folders(csn, opts)
|
|
254
254
|
const found={}, ts = process.env.CDS_TYPESCRIPT
|
|
255
255
|
for (let folder of folders) {
|
|
256
|
-
// fetching init.js files
|
|
257
|
-
const init_js = ts && isfile(folder,'init.ts') || isfile(folder,'init.js')
|
|
258
|
-
if (init_js) found[init_js] = '*'
|
|
259
256
|
// fetching .csv and .json files
|
|
260
257
|
for (let each of ['data','csv']) {
|
|
261
258
|
const subdir = isdir(folder,each); if (!subdir) continue
|
|
@@ -281,6 +278,9 @@ exports.resources = async function cds_deploy_resources (csn, opts) {
|
|
|
281
278
|
}
|
|
282
279
|
}
|
|
283
280
|
}
|
|
281
|
+
// fetching init.js files -> Note: after .csv files to have that on top, when processing in .reverse order
|
|
282
|
+
const init_js = ts && isfile(folder,'init.ts') || isfile(folder,'init.js')
|
|
283
|
+
if (init_js) found[init_js] = '*'
|
|
284
284
|
}
|
|
285
285
|
return found
|
|
286
286
|
}
|
|
@@ -341,7 +341,7 @@ const _queries4 = (db,csn) => !db.cqn2sql ? q => q : q => {
|
|
|
341
341
|
|
|
342
342
|
|
|
343
343
|
const INSERT_from4 = (db,o) => {
|
|
344
|
-
const schevo =
|
|
344
|
+
const schevo = o?.schema_evolution === 'auto' || db.options.schema_evolution === 'auto'
|
|
345
345
|
const INSERT_into = (schevo ? UPSERT : INSERT).into
|
|
346
346
|
return (file) => ({
|
|
347
347
|
'.json': { into (entity, json) {
|
package/lib/env/cds-env.js
CHANGED
|
@@ -113,14 +113,14 @@ class Config {
|
|
|
113
113
|
...( global._plugins||[] ).map (root => ({
|
|
114
114
|
path: root, file: 'package.json', mapper: x => x.cds
|
|
115
115
|
})),
|
|
116
|
-
{ path: user_home, file: '.cdsrc.json' },
|
|
117
|
-
{ path: home, file: '.cdsrc
|
|
118
|
-
{ path: home, file: '.cdsrc.json' },
|
|
116
|
+
{ path: user_home, file: '.cdsrc.json', mapper: x => x.cds||x },
|
|
117
|
+
{ path: home, file: '.cdsrc.json', mapper: x => x.cds||x },
|
|
119
118
|
{ path: home, file: 'package.json', mapper: _package_json },
|
|
119
|
+
{ path: home, file: '.cdsrc-private.json', mapper: x => x.cds||x },
|
|
120
120
|
]
|
|
121
121
|
function _package_json (pkg) { // fill cds.extends from .extends
|
|
122
122
|
let cds = pkg.cds
|
|
123
|
-
if (pkg.extends) (cds
|
|
123
|
+
if (pkg.extends) (cds??={}).extends = pkg.extends
|
|
124
124
|
return cds
|
|
125
125
|
}
|
|
126
126
|
}
|
|
@@ -434,7 +434,7 @@ class Config {
|
|
|
434
434
|
function _add_static_profiles (_home, profiles) {
|
|
435
435
|
for (let src of ['package.json', '.cdsrc.json']) try {
|
|
436
436
|
const conf = require(path.join(_home,src))
|
|
437
|
-
const cds = src === '.
|
|
437
|
+
const cds = src === 'package.json' ? conf.cds : conf.cds||conf
|
|
438
438
|
if (cds?.profiles) return profiles.push(...cds.profiles)
|
|
439
439
|
if (cds?.profile) return profiles.push(cds.profile)
|
|
440
440
|
} catch (e) { if (e.code !== 'MODULE_NOT_FOUND') throw e }
|
|
@@ -8,7 +8,7 @@ module.exports = ()=> {
|
|
|
8
8
|
return async function cds_context_model (req,res, next) {
|
|
9
9
|
if (req.baseUrl.startsWith('/-/')) return next() //> our own tech services cannot be extended
|
|
10
10
|
const ctx = cds.context
|
|
11
|
-
if (ctx.tenant) try {
|
|
11
|
+
if (ctx.tenant || ctx.features) try {
|
|
12
12
|
// if (req.headers.features) ctx.user.features = req.headers.features //> currently done in basic-auth only
|
|
13
13
|
ctx.model = req.__model = await model4 (ctx.tenant, ctx.features) // REVISIT: req.__model is because of Okra
|
|
14
14
|
} catch (e) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cds = require('../../index'), { User } = cds, {
|
|
1
|
+
const cds = require('../../index'), { User } = cds, { decodeURI } = cds.utils
|
|
2
2
|
const libx = require('../../../libx/_runtime')
|
|
3
3
|
const LOG = cds.log('odata')
|
|
4
4
|
|
|
@@ -6,10 +6,15 @@ module.exports = function ODataAdapter (srv) { return [
|
|
|
6
6
|
(req, _, next) => {
|
|
7
7
|
let u = req.user
|
|
8
8
|
req.user = u instanceof User ? u : new User(u)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
|
|
10
|
+
let url = decodeURI(req.originalUrl)
|
|
11
|
+
LOG && LOG (req.method, url, req.body||'')
|
|
12
|
+
if (/\$batch/.test(req.url)) req.on ('dispatch', (req) => {
|
|
13
|
+
let path = decodeURI(req._path)
|
|
14
|
+
LOG && LOG ('>', req.event, path, req._query||'')
|
|
15
|
+
if (LOG._debug && req.query) LOG.debug (req.query)
|
|
12
16
|
})
|
|
17
|
+
|
|
13
18
|
next()
|
|
14
19
|
},
|
|
15
20
|
libx.to.odata_v4 (srv)
|
package/lib/srv/srv-tx.js
CHANGED
|
@@ -132,10 +132,10 @@ class RootTransaction extends Transaction {
|
|
|
132
132
|
* are informed by emitting 'succeeded' event to them all.
|
|
133
133
|
*/
|
|
134
134
|
async commit (res) {
|
|
135
|
-
this._done = 'committed'
|
|
136
135
|
try {
|
|
137
136
|
await this.context.emit ('commit',res) //> allow custom handlers req.before('commit')
|
|
138
137
|
await super.commit (res)
|
|
138
|
+
this._done = 'committed'
|
|
139
139
|
await this.context.emit ('succeeded',res)
|
|
140
140
|
await this.context.emit ('done')
|
|
141
141
|
} catch (err) {
|
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const UriSyntaxError = require('../errors/UriSyntaxError')
|
|
4
4
|
|
|
5
|
-
const IDENTIFIER =
|
|
6
|
-
'(?:(?:_|\\p{Letter}|\\p{Letter_Number})' +
|
|
7
|
-
'(?:_|\\p{Letter}|\\p{Letter_Number}|\\p{Decimal_Number}' +
|
|
8
|
-
'|\\p{Nonspacing_Mark}|\\p{Spacing_Mark}|\\p{Connector_Punctuation}|\\p{Format}){0,127})'
|
|
5
|
+
const IDENTIFIER = '([_\\p{L}\\p{Nl}][_\\p{L}\\p{Nl}\\p{Nd}\\p{Mn}\\p{Mc}\\p{Pc}\\p{Cf}]{0,127})'
|
|
9
6
|
const IDENTIFIER_REGEXP = new RegExp('^' + IDENTIFIER, 'u')
|
|
10
|
-
const QUALIFIED_NAME_REGEXP = new RegExp('^' + IDENTIFIER + '(
|
|
7
|
+
const QUALIFIED_NAME_REGEXP = new RegExp('^' + IDENTIFIER + '(\\.' + IDENTIFIER + ')+', 'u')
|
|
11
8
|
const PARAMETER_ALIAS_NAME_REGEXP = new RegExp('^@' + IDENTIFIER, 'u')
|
|
12
9
|
|
|
13
10
|
const BOOLEAN_VALUE_REGEXP = new RegExp('^(?:true|false)', 'i')
|
|
@@ -696,9 +693,9 @@ class UriTokenizer {
|
|
|
696
693
|
* @private
|
|
697
694
|
*/
|
|
698
695
|
_nextWithRegularExpression (regexp) {
|
|
699
|
-
const
|
|
700
|
-
if (!
|
|
701
|
-
this._index +=
|
|
696
|
+
const matched = this._parseString.substring(this._index).match(regexp)
|
|
697
|
+
if (!matched) return false
|
|
698
|
+
this._index += matched[0].length
|
|
702
699
|
return true
|
|
703
700
|
}
|
|
704
701
|
|
|
@@ -856,10 +856,13 @@ function expandStarStar(target, recursion = new Map()) {
|
|
|
856
856
|
|
|
857
857
|
async function onNew(req) {
|
|
858
858
|
LOG.debug('new draft')
|
|
859
|
+
if (req.target.actives['@Capabilities.InsertRestrictions.Insertable'] === false || req.target.actives['@readonly'])
|
|
860
|
+
req.reject(405)
|
|
859
861
|
const isDirectAccess = typeof req.query.INSERT.into === 'string' || req.query.INSERT.into.ref?.length === 1
|
|
860
862
|
// Only allowed for pseudo draft roots (entities with this action)
|
|
861
863
|
if (isDirectAccess && !req.target.actives['@Common.DraftRoot.ActivationAction'])
|
|
862
864
|
req.reject(403, 'DRAFT_MODIFICATION_ONLY_VIA_ROOT')
|
|
865
|
+
|
|
863
866
|
let DraftUUID
|
|
864
867
|
if (isDirectAccess) DraftUUID = cds.utils.uuid()
|
|
865
868
|
else {
|
|
@@ -929,6 +932,12 @@ async function onEdit(req) {
|
|
|
929
932
|
if (req.query.SELECT.from.ref.length > 1 || draftParams.IsActiveEntity !== true) {
|
|
930
933
|
req.reject(400, 'Action "draftEdit" can only be called on the root active entity')
|
|
931
934
|
}
|
|
935
|
+
if (
|
|
936
|
+
req.target['@Capabilities.UpdateRestrictions.Updatable'] === false ||
|
|
937
|
+
req.target['@insertonly'] ||
|
|
938
|
+
req.target['@readonly']
|
|
939
|
+
)
|
|
940
|
+
req.reject(405)
|
|
932
941
|
const targetWhere = req.query.SELECT.from.ref[0].where
|
|
933
942
|
|
|
934
943
|
if (draftParams.IsActiveEntity !== true) req.reject(400)
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -92,7 +92,7 @@ const defaults = {
|
|
|
92
92
|
const path = require('path')
|
|
93
93
|
const _app_serve = function (endpoint) { return {
|
|
94
94
|
from: (pkg,folder) => {
|
|
95
|
-
folder = !folder ? pkg : path.resolve(require.resolve(pkg+'/package.json'),'../'+folder)
|
|
95
|
+
folder = !folder ? pkg : path.resolve(require.resolve(pkg+'/package.json',{paths:[cds.root]}),'../'+folder)
|
|
96
96
|
this.use (endpoint, express.static(folder))
|
|
97
97
|
if (!endpoint.endsWith('/webapp')) (this._app_links || (this._app_links = [])) .push (endpoint)
|
|
98
98
|
}
|