@sap/cds 6.4.0 → 6.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 +59 -3
- package/apis/cds.d.ts +2 -0
- package/apis/cqn.d.ts +14 -3
- package/apis/ql.d.ts +12 -8
- package/apis/services.d.ts +39 -64
- package/apis/test.d.ts +7 -0
- package/bin/build/buildTaskEngine.js +9 -12
- package/bin/build/buildTaskHandler.js +3 -14
- package/bin/build/index.js +8 -2
- package/bin/build/provider/buildTaskProviderInternal.js +8 -7
- package/bin/build/provider/hana/template/package.json +3 -0
- package/bin/build/provider/mtx/resourcesTarBuilder.js +13 -4
- package/bin/build/provider/mtx-extension/index.js +41 -38
- package/bin/build/util.js +17 -0
- package/bin/deploy/to-hana/hdiDeployUtil.js +11 -5
- package/bin/serve.js +6 -2
- package/common.cds +7 -0
- package/lib/auth/index.js +17 -15
- package/lib/auth/jwt-auth.js +4 -3
- package/lib/compile/for/lean_drafts.js +1 -1
- package/lib/compile/minify.js +3 -3
- package/lib/core/index.js +1 -0
- package/lib/dbs/cds-deploy.js +13 -10
- package/lib/env/cds-requires.js +1 -1
- package/lib/env/defaults.js +5 -1
- package/lib/env/schemas/cds-rc.json +74 -3
- package/lib/lazy.js +6 -8
- package/lib/log/cds-error.js +2 -2
- package/lib/ql/Whereable.js +22 -11
- package/lib/ql/cds-ql.js +1 -1
- package/lib/req/response.js +8 -3
- package/lib/req/user.js +12 -2
- package/lib/srv/middlewares/cds-context.js +0 -2
- package/lib/srv/middlewares/ctx-auth.js +11 -0
- package/lib/srv/middlewares/ctx-model.js +22 -20
- package/lib/srv/middlewares/index.js +7 -9
- package/lib/srv/protocols/_legacy.js +4 -0
- package/lib/srv/protocols/graphql.js +2 -2
- package/lib/srv/protocols/index.js +7 -3
- package/lib/srv/srv-api.js +1 -0
- package/lib/srv/srv-models.js +6 -1
- package/lib/utils/cds-utils.js +3 -1
- package/lib/utils/data.js +2 -2
- package/lib/utils/tar.js +37 -12
- package/libx/_runtime/auth/strategies/JWT.js +1 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +2 -1
- package/libx/_runtime/auth/strategies/mock.js +12 -1
- package/libx/_runtime/auth/strategies/xssecUtils.js +7 -8
- package/libx/_runtime/auth/strategies/xsuaa.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -2
- package/libx/_runtime/cds-services/services/Service.js +3 -0
- package/libx/_runtime/cds-services/services/utils/columns.js +35 -36
- package/libx/_runtime/common/code-ext/WorkerReq.js +79 -0
- package/libx/_runtime/common/code-ext/config.js +13 -0
- package/libx/_runtime/common/code-ext/execute.js +106 -0
- package/libx/_runtime/common/code-ext/handlers.js +49 -0
- package/libx/_runtime/common/code-ext/worker.js +36 -0
- package/libx/_runtime/common/code-ext/workerQuery.js +45 -0
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +33 -0
- package/libx/_runtime/common/generic/crud.js +5 -1
- package/libx/_runtime/common/generic/paging.js +8 -7
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +47 -11
- package/libx/_runtime/common/utils/path.js +5 -25
- package/libx/_runtime/common/utils/resolveView.js +2 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +13 -9
- package/libx/_runtime/db/expand/expandCQNToJoin.js +2 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +5 -1
- package/libx/_runtime/db/sql-builder/UpsertBuilder.js +9 -32
- package/libx/_runtime/db/sql-builder/annotations.js +6 -3
- package/libx/_runtime/db/utils/localized.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +4 -0
- package/libx/_runtime/fiori/generic/before.js +8 -1
- package/libx/_runtime/fiori/generic/edit.js +5 -0
- package/libx/_runtime/fiori/generic/read.js +8 -3
- package/libx/_runtime/fiori/lean-draft.js +12 -1
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -5
- package/libx/_runtime/hana/execute.js +5 -5
- package/libx/_runtime/hana/pool.js +1 -1
- package/libx/_runtime/hana/search2cqn4sql.js +51 -51
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +20 -38
- package/libx/odata/afterburner.js +6 -3
- package/libx/odata/cqn2odata.js +1 -1
- package/libx/rest/middleware/parse.js +26 -4
- package/package.json +1 -1
- package/server.js +2 -20
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
2
3
|
const cds = require('../../cds')
|
|
4
|
+
|
|
3
5
|
const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
|
|
4
6
|
const { FOLDER_GEN } = require('../../constants')
|
|
5
7
|
const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder')
|
|
@@ -13,47 +15,48 @@ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
async build() {
|
|
18
|
+
const { src, dest } = this.task
|
|
19
|
+
const destExt = path.join(dest, 'ext')
|
|
20
|
+
|
|
21
|
+
await this.copy(path.join(src, 'package.json')).to(path.join(destExt, 'package.json'))
|
|
22
|
+
|
|
23
|
+
// copy handlers
|
|
24
|
+
const folders = [path.join(src, cds.env.folders.srv, 'handlers')]
|
|
25
|
+
await this.copyNativeContent(src, destExt, res => {
|
|
26
|
+
if (fs.statSync(res).isDirectory()) {
|
|
27
|
+
return folders.some(folder => folder.startsWith(res))
|
|
28
|
+
}
|
|
29
|
+
if (folders.includes(path.dirname(res)) && /\.js$/.test(res)) {
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
16
34
|
const model = await this.model()
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// extension CSN using parsed format
|
|
28
|
-
const options = { ...this.options(), flavor: 'parsed' }
|
|
29
|
-
const extCsn = await cds.load(this.resolveModel(), options)
|
|
30
|
-
if (extCsn.requires) {
|
|
31
|
-
extCsn.requires.length = 0
|
|
32
|
-
}
|
|
33
|
-
const csnFile = path.join(destExt, 'extension.csn')
|
|
34
|
-
await this.compileToJson(extCsn, csnFile)
|
|
35
|
-
allFiles.push(csnFile)
|
|
36
|
-
|
|
37
|
-
// static i18n folder name as runtime does not use the CDS config of the extension project
|
|
38
|
-
const i18n = await this.collectLanguageBundles(extCsn, path.join(destExt, 'i18n'))
|
|
39
|
-
if (i18n) {
|
|
40
|
-
allFiles.push(i18n.file)
|
|
41
|
-
}
|
|
35
|
+
if (model) {
|
|
36
|
+
// extension CSN using parsed format
|
|
37
|
+
const options = { ...this.options(), flavor: 'parsed' }
|
|
38
|
+
const extCsn = await cds.load(this.resolveModel(), options)
|
|
39
|
+
if (extCsn.requires) {
|
|
40
|
+
extCsn.requires.length = 0
|
|
41
|
+
}
|
|
42
|
+
await this.compileToJson(extCsn, path.join(destExt, 'extension.csn'))
|
|
43
|
+
|
|
44
|
+
await this.collectLanguageBundles(extCsn, path.join(destExt, 'i18n'))
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
)
|
|
46
|
+
const files = Object.keys(await cds.deploy.resources(model))
|
|
47
|
+
if (files.length > 0) {
|
|
48
|
+
const dataDest = path.join(destExt, 'data')
|
|
49
|
+
await Promise.all(
|
|
50
|
+
files
|
|
51
|
+
.filter(file => /\.csv$/.test(file))
|
|
52
|
+
.map(csv => {
|
|
53
|
+
return this.copy(csv).to(path.join(dataDest, path.basename(csv)))
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
}
|
|
55
57
|
}
|
|
56
|
-
|
|
58
|
+
// add all resources contained in the 'ext' folder
|
|
59
|
+
await new ResourcesTarBuilder(this).writeTarFile(path.join(this.task.dest, 'extension.tgz'), destExt)
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
module.exports = MtxExtensionModuleBuilder
|
package/bin/build/util.js
CHANGED
|
@@ -187,6 +187,22 @@ function flatten(modelPaths) {
|
|
|
187
187
|
}, [])
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Copy a file or directory. The directory can have contents.
|
|
192
|
+
* REVISIT: 'fs.promises.cp' replacement for nodejs 14
|
|
193
|
+
* @param src <String> Note that if src is a directory it will copy everything inside of this directory, not the entire directory itself.
|
|
194
|
+
* @param dest <String> Note that if src is a file, dest cannot be a directory.
|
|
195
|
+
*/
|
|
196
|
+
async function copy(src, dest) {
|
|
197
|
+
if ((await fs.promises.stat(src)).isDirectory()) {
|
|
198
|
+
const entries = await fs.promises.readdir(src)
|
|
199
|
+
return Promise.all(entries.map(async each => copy(path.join(src, each), path.join(dest, each))))
|
|
200
|
+
} else {
|
|
201
|
+
await fs.promises.mkdir(path.dirname(dest), { recursive: true })
|
|
202
|
+
return fs.promises.copyFile(src, dest)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
190
206
|
class BuildMessage extends Error {
|
|
191
207
|
constructor(message, severity = SEVERITY_ERROR) {
|
|
192
208
|
super(message)
|
|
@@ -228,6 +244,7 @@ module.exports = {
|
|
|
228
244
|
resolveRequiredSapModels,
|
|
229
245
|
getDefaultModelOptions,
|
|
230
246
|
flatten,
|
|
247
|
+
copy,
|
|
231
248
|
BuildMessage,
|
|
232
249
|
BuildError
|
|
233
250
|
}
|
|
@@ -97,9 +97,15 @@ Add it either as a devDependency using 'npm install -D ${this.deployerName}' or
|
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
async _npmSearchPaths(cwd) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
// REVISIT: we shouldn't have to rely on `npm` on the server
|
|
101
|
+
try {
|
|
102
|
+
const npmRootCall = await execAsync('npm root -g');
|
|
103
|
+
const globalNodeModules = npmRootCall.stdout.toString().trim();
|
|
104
|
+
return [cwd, globalNodeModules, '@sap/hdi-deploy']
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (/Command failed: npm.*not found/s.test(error.message)) return [cwd, '@sap/hdi-deploy']
|
|
107
|
+
else throw error
|
|
108
|
+
}
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
|
|
@@ -107,10 +113,10 @@ Add it either as a devDependency using 'npm install -D ${this.deployerName}' or
|
|
|
107
113
|
const hdiDeployLib = await this._getHdiDeployLib(dbDir);
|
|
108
114
|
return new Promise((resolve, reject) => {
|
|
109
115
|
const callbacks = {
|
|
110
|
-
stderrCB: error =>
|
|
116
|
+
stderrCB: error => LOG.error(error.toString())
|
|
111
117
|
}
|
|
112
118
|
if (LOG.level !== SILENT) {
|
|
113
|
-
callbacks.stdoutCB = (data) =>
|
|
119
|
+
callbacks.stdoutCB = (data) => LOG.log(data.toString());
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
hdiDeployLib.deploy(dbDir, env, (error, response) => {
|
package/bin/serve.js
CHANGED
|
@@ -6,7 +6,7 @@ module.exports = Object.assign ( serve, {
|
|
|
6
6
|
flags: [
|
|
7
7
|
'--project', '--projects',
|
|
8
8
|
'--in-memory', '--in-memory?',
|
|
9
|
-
'--mocked', '--with-mocks', '--with-bindings',
|
|
9
|
+
'--mocked', '--with-mocks', '--with-bindings', '--resolve-bindings',
|
|
10
10
|
'--watch',
|
|
11
11
|
],
|
|
12
12
|
shortcuts: [ '-s', undefined, '-2', '-a', '-w', undefined, '-p' ],
|
|
@@ -98,6 +98,10 @@ module.exports = Object.assign ( serve, {
|
|
|
98
98
|
All required services are bound automatically upon bootstrapping.
|
|
99
99
|
Option *--with-mocks* subsumes this option.
|
|
100
100
|
|
|
101
|
+
*--resolve-bindings* (beta)
|
|
102
|
+
|
|
103
|
+
Resolve remote service bindings configured via *cds bind*.
|
|
104
|
+
|
|
101
105
|
*--in-memory[?]*
|
|
102
106
|
|
|
103
107
|
Automatically adds a transient in-memory database bootstrapped on
|
|
@@ -265,7 +269,7 @@ function _prepare_logging () { // NOSONAR
|
|
|
265
269
|
|
|
266
270
|
// print info when we are finally on air
|
|
267
271
|
cds.once ('listening', ({url})=>{
|
|
268
|
-
|
|
272
|
+
LOG.info ()
|
|
269
273
|
LOG.info ('server listening on',{url})
|
|
270
274
|
_timer && console.timeEnd (_timer)
|
|
271
275
|
if (process.stdin.isTTY) LOG.info (`[ terminate with ^C ]\n`)
|
package/common.cds
CHANGED
|
@@ -72,6 +72,13 @@ context sap.common {
|
|
|
72
72
|
name : localized String(255) @title : '{i18n>Name}';
|
|
73
73
|
descr : localized String(1000) @title : '{i18n>Description}';
|
|
74
74
|
}
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* Aspect that is included by generated `.texts` entities for localized entities.
|
|
78
|
+
*/
|
|
79
|
+
aspect TextsAspect {
|
|
80
|
+
key locale: Locale;
|
|
81
|
+
}
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
|
package/lib/auth/index.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
|
|
2
|
+
const cds = require ('../index'), { path, local } = cds.utils
|
|
3
|
+
|
|
4
|
+
const _require = require; require = cds.lazified (module) // eslint-disable-line no-global-assign
|
|
5
|
+
module.exports = Object.assign (auth_factory, {
|
|
6
|
+
mocked: require('./basic-auth'),
|
|
7
|
+
basic: require('./basic-auth'),
|
|
8
|
+
dummy: require('./dummy-auth'),
|
|
9
|
+
ias: require('./ias-auth'),
|
|
10
|
+
jwt: require('./jwt-auth'),
|
|
11
|
+
xsuaa: require('./jwt-auth'),
|
|
12
|
+
})
|
|
13
|
+
require = _require // eslint-disable-line no-global-assign
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Constructs one of the above middlewares as configured
|
|
18
|
+
*/
|
|
2
19
|
function auth_factory (options) {
|
|
3
|
-
const cds = require ('../index'), { path, local } = cds.utils
|
|
4
20
|
const o = { ...options, ...cds.requires.auth }
|
|
5
21
|
let kind = o.kind || o.strategy
|
|
6
22
|
let middleware = cds.auth[kind]
|
|
@@ -16,17 +32,3 @@ function auth_factory (options) {
|
|
|
16
32
|
}
|
|
17
33
|
return middleware(o)
|
|
18
34
|
}
|
|
19
|
-
|
|
20
|
-
const { lazified } = require('../lazy')
|
|
21
|
-
const _require = require; require = lazified (module) // eslint-disable-line no-global-assign
|
|
22
|
-
|
|
23
|
-
module.exports = lazified (Object.assign (auth_factory, {
|
|
24
|
-
mocked: require('./basic-auth'),
|
|
25
|
-
basic: require('./basic-auth'),
|
|
26
|
-
dummy: require('./dummy-auth'),
|
|
27
|
-
ias: require('./ias-auth'),
|
|
28
|
-
jwt: require('./jwt-auth'),
|
|
29
|
-
xsuaa: require('./jwt-auth'),
|
|
30
|
-
}))
|
|
31
|
-
|
|
32
|
-
require = _require // eslint-disable-line no-global-assign
|
package/lib/auth/jwt-auth.js
CHANGED
|
@@ -26,6 +26,7 @@ module.exports = function jwt_auth(config) {
|
|
|
26
26
|
const payload = req.tokenInfo.getPayload()
|
|
27
27
|
|
|
28
28
|
let id = req.user.id
|
|
29
|
+
let _is_system, _is_internal
|
|
29
30
|
|
|
30
31
|
let roles = payload.scope.map(s => s.replace(new RegExp(`^(${config.credentials.xsappname + '.'})`), ''))
|
|
31
32
|
roles.push('identified-user')
|
|
@@ -36,8 +37,8 @@ module.exports = function jwt_auth(config) {
|
|
|
36
37
|
const CLIENT = { client_credentials: 1, client_x509: 1 }
|
|
37
38
|
if (payload.grant_type in CLIENT) {
|
|
38
39
|
id = 'system'
|
|
39
|
-
|
|
40
|
-
if (req.tokenInfo.getClientId() === config.credentials.clientid)
|
|
40
|
+
_is_system = true
|
|
41
|
+
if (req.tokenInfo.getClientId() === config.credentials.clientid) _is_internal = true
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -49,7 +50,7 @@ module.exports = function jwt_auth(config) {
|
|
|
49
50
|
attr.email = req.authInfo.getEmail()
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
req.user = new cds.User({ id, roles, attr })
|
|
53
|
+
req.user = new cds.User({ id, roles, attr, _is_system, _is_internal })
|
|
53
54
|
req.tenant = req.tokenInfo.getZoneId?.()
|
|
54
55
|
next()
|
|
55
56
|
})
|
|
@@ -54,7 +54,7 @@ module.exports = function cds_compile_for_lean_drafts(csn, o) {
|
|
|
54
54
|
for (const each in draft.elements) {
|
|
55
55
|
const e = draft.elements[each]
|
|
56
56
|
const newEl = Object.create(e)
|
|
57
|
-
if (e.isComposition || (e.isAssociation && e['@odata.draft.enclosed']) || e.
|
|
57
|
+
if (e.isComposition || (e.isAssociation && e['@odata.draft.enclosed']) || e._isCompositionBacklink) {
|
|
58
58
|
_redirect(newEl, draftEntity(e._target, model))
|
|
59
59
|
}
|
|
60
60
|
newEl.parent = draft
|
package/lib/compile/minify.js
CHANGED
|
@@ -35,10 +35,10 @@ module.exports = function cds_minify (csn, _roots) { // IMPORTANT: don't add cds
|
|
|
35
35
|
}
|
|
36
36
|
function _visit (d) {
|
|
37
37
|
if (typeof d === 'string') {
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
d = all[d]
|
|
39
|
+
if (!d) return // builtins like cds.String
|
|
40
40
|
} else if (d.ref) return d.ref.reduce((p,n) => {
|
|
41
|
-
let d = (p.elements ||
|
|
41
|
+
let d = (p.elements || all[p.target || p.type].elements)[n.id || n] // > n.id -> view with parameters
|
|
42
42
|
if (d) _visit(d)
|
|
43
43
|
return d
|
|
44
44
|
},{elements:all})
|
package/lib/core/index.js
CHANGED
package/lib/dbs/cds-deploy.js
CHANGED
|
@@ -107,7 +107,7 @@ exports.exclude_external_entities_in = function (csn) { // NOSONAR
|
|
|
107
107
|
|
|
108
108
|
function getSqls(db, csn, o, beforeCsn) {
|
|
109
109
|
const schemaEvo = (db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto')
|
|
110
|
-
const creds = db.options?.credentials
|
|
110
|
+
const creds = db.options?.credentials
|
|
111
111
|
const in_memory = (creds?.url || creds?.database) === ':memory:';
|
|
112
112
|
if (!in_memory && schemaEvo) {
|
|
113
113
|
const { afterImage: afterCsn, drops, createsAndAlters: creas } = cds.compile.to.sql.delta (csn, o, beforeCsn);
|
|
@@ -134,7 +134,7 @@ exports.create = async function (db, csn=db.model, o) {
|
|
|
134
134
|
const schemaEvo = (db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto')
|
|
135
135
|
if(db.deploy && !schemaEvo) {
|
|
136
136
|
// reset CSN state saved in db - if there is any
|
|
137
|
-
await db.run('DROP table if exists cds_Model;');
|
|
137
|
+
if(!o.dry) await db.run('DROP table if exists cds_Model;');
|
|
138
138
|
return db.deploy(csn, o);
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -176,20 +176,23 @@ exports.init = (db, csn=db.model, o, csvs, log=()=>{}) => db.run (async tx => {
|
|
|
176
176
|
|
|
177
177
|
if (csvs) {
|
|
178
178
|
const ccsn = cds.compile.for['nodejs'](csn) // compile to calculate keys for newly added entities
|
|
179
|
-
for(let [
|
|
180
|
-
const
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
179
|
+
for(let [file,src] of Object.entries(csvs)) {
|
|
180
|
+
const entity = _entity4(path.basename(file, '.csv'), csn)
|
|
181
|
+
if (entity?.name) {
|
|
182
|
+
const q = INSERT_from_csv (entity.name,src,schemaEvo); if (!q) continue
|
|
183
|
+
if (db.kind === 'better-sqlite') _add_missing_pks2(q)
|
|
184
|
+
q._target = ccsn.definitions[entity.name]
|
|
185
|
+
inits.push (tx.run(q) .catch (e => {
|
|
186
|
+
throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ inspect(q) })
|
|
187
|
+
}))
|
|
188
|
+
}
|
|
186
189
|
}
|
|
187
190
|
} else {
|
|
188
191
|
for (let [file,e] of Object.entries(resources)) {
|
|
189
192
|
if (e === '*') { // init.js/ts
|
|
190
193
|
let x = await cds.utils._import(file); if (!x) continue
|
|
191
194
|
if (x.default) x = x.default // default ESM export
|
|
192
|
-
inits.push (!x.then && typeof x === 'function' ? x(
|
|
195
|
+
inits.push (!x.then && typeof x === 'function' ? x(tx,csn) : x)
|
|
193
196
|
log (file)
|
|
194
197
|
} else { // from .csv or .json
|
|
195
198
|
const INSERT_into = _from_csv_or_json [path.extname(file)]
|
package/lib/env/cds-requires.js
CHANGED
package/lib/env/defaults.js
CHANGED
|
@@ -246,8 +246,26 @@
|
|
|
246
246
|
"description": "Shortcut to enable multitenancy."
|
|
247
247
|
},
|
|
248
248
|
"extensibility": {
|
|
249
|
-
"
|
|
250
|
-
|
|
249
|
+
"oneOf": [
|
|
250
|
+
{
|
|
251
|
+
"type": "boolean",
|
|
252
|
+
"description": "Shortcut to enable extensibility."
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"type": "object",
|
|
256
|
+
"description": "Extensibility configuration options.",
|
|
257
|
+
"properties": {
|
|
258
|
+
"tenantCheckInterval": {
|
|
259
|
+
"type": "number",
|
|
260
|
+
"description": "Time interval in ms to check for new extensions and refreshed models."
|
|
261
|
+
},
|
|
262
|
+
"evictionInterval": {
|
|
263
|
+
"type": "number",
|
|
264
|
+
"description": "Time interval in ms after which to evict models for inactive tenants."
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
]
|
|
251
269
|
},
|
|
252
270
|
"toggles": {
|
|
253
271
|
"type": "boolean",
|
|
@@ -264,7 +282,7 @@
|
|
|
264
282
|
},
|
|
265
283
|
{
|
|
266
284
|
"type": "object",
|
|
267
|
-
"description": "
|
|
285
|
+
"description": "ModelProviderService configuration options.",
|
|
268
286
|
"additionalProperties": true,
|
|
269
287
|
"properties": {
|
|
270
288
|
"root": {
|
|
@@ -295,6 +313,37 @@
|
|
|
295
313
|
},
|
|
296
314
|
{
|
|
297
315
|
"$ref": "#/$defs/servicePresetSidecar"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"type": "object",
|
|
319
|
+
"description": "DeploymentService configuration options.",
|
|
320
|
+
"properties": {
|
|
321
|
+
"hdi": {
|
|
322
|
+
"type": "object",
|
|
323
|
+
"description": "Bundles HDI-specific settings.",
|
|
324
|
+
"properties": {
|
|
325
|
+
"create": {
|
|
326
|
+
"type": "object",
|
|
327
|
+
"description": "HDI container provisioning parameters.",
|
|
328
|
+
"properties": {
|
|
329
|
+
"database_id": {
|
|
330
|
+
"type": "string",
|
|
331
|
+
"description": "HANA Cloud instance ID."
|
|
332
|
+
},
|
|
333
|
+
"additionalProperties": true
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
"bind": {
|
|
337
|
+
"type": "object",
|
|
338
|
+
"description": "HDI container binding parameters."
|
|
339
|
+
},
|
|
340
|
+
"deploy": {
|
|
341
|
+
"type": "object",
|
|
342
|
+
"description": "HDI deployment parameters as defined on https://www.npmjs.com/package/@sap/hdi-deploy#supported-features"
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
298
347
|
}
|
|
299
348
|
]
|
|
300
349
|
},
|
|
@@ -303,6 +352,28 @@
|
|
|
303
352
|
"oneOf": [
|
|
304
353
|
{
|
|
305
354
|
"$ref": "#/$defs/serviceActivation"
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"type": "object",
|
|
358
|
+
"description": "SaasProvisioningService configuration options.",
|
|
359
|
+
"additionalProperties": true,
|
|
360
|
+
"properties": {
|
|
361
|
+
"jobs": {
|
|
362
|
+
"type": "object",
|
|
363
|
+
"description": "Configuration options for the built-in async job executor.",
|
|
364
|
+
"properties": {
|
|
365
|
+
"workerSize": {
|
|
366
|
+
"type": "number",
|
|
367
|
+
"description": "Number of workers running in parallel per database."
|
|
368
|
+
},
|
|
369
|
+
"clusterSize": {
|
|
370
|
+
"type": "number",
|
|
371
|
+
"description": "Number of databases executing parallel tasks."
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
"additionalProperties": true
|
|
375
|
+
}
|
|
376
|
+
}
|
|
306
377
|
}
|
|
307
378
|
]
|
|
308
379
|
}
|
package/lib/lazy.js
CHANGED
|
@@ -9,7 +9,7 @@ const extend = (target) => ({
|
|
|
9
9
|
for (let each of aspects) {
|
|
10
10
|
for (let p of Reflect.ownKeys(each)) {
|
|
11
11
|
if (p in excludes) continue
|
|
12
|
-
|
|
12
|
+
Reflect.defineProperty (target,p, Reflect.getOwnPropertyDescriptor(each,p))
|
|
13
13
|
}
|
|
14
14
|
if (is_class(target) && is_class(each)) {
|
|
15
15
|
extend(target.prototype).with(each.prototype)
|
|
@@ -28,10 +28,10 @@ const _excludes = {
|
|
|
28
28
|
const lazify = (o) => {
|
|
29
29
|
if (o.constructor === module.constructor) return lazify_module(o)
|
|
30
30
|
for (let p of Reflect.ownKeys(o)) {
|
|
31
|
-
const d =
|
|
32
|
-
if (is_lazy(d.value))
|
|
33
|
-
set(v) {
|
|
34
|
-
get() { return this[p] = d.value(p,this) },
|
|
31
|
+
const d = Reflect.getOwnPropertyDescriptor(o,p)
|
|
32
|
+
if (is_lazy(d.value)) Reflect.defineProperty (o,p,{
|
|
33
|
+
set(v) { Reflect.defineProperty (this,p,{value:v,__proto__:d}) },
|
|
34
|
+
get() { return this[p] = d.value.call(this,p,this) },
|
|
35
35
|
configurable: true,
|
|
36
36
|
})
|
|
37
37
|
}
|
|
@@ -45,9 +45,7 @@ const lazify_module = (module) => {
|
|
|
45
45
|
return (id) => (lazy) => module.require(id)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
const is_lazy = (x) => typeof x === 'function' &&
|
|
48
|
+
const is_lazy = (x) => typeof x === 'function' && /^(function\s?)?\(?lazy[,)\t =]/.test(x)
|
|
49
49
|
const is_class = (x) => typeof x === 'function' && x.prototype && /^class\b/.test(x)
|
|
50
|
-
const describe = Reflect.getOwnPropertyDescriptor
|
|
51
|
-
const define = Reflect.defineProperty
|
|
52
50
|
|
|
53
51
|
module.exports = { extend, lazify, lazified:lazify }
|
package/lib/log/cds-error.js
CHANGED
|
@@ -47,9 +47,9 @@ exports.message = (strings,...values) => String.raw(strings,...values.map(_forma
|
|
|
47
47
|
* typeof x === 'string' || cds.error.expected `${{x}} to be a string`
|
|
48
48
|
* //> Error: Expected argument 'x' to be a string, but got: { foo: 'bar' }
|
|
49
49
|
*/
|
|
50
|
-
exports.expected = ([,type], arg) => {
|
|
50
|
+
const expected = exports.expected = ([,type], arg) => {
|
|
51
51
|
const [ name, value ] = Object.entries(arg)[0]
|
|
52
|
-
return error `Expected argument '${name}'${type}, but got: ${require('util').inspect(value,{depth:11})}
|
|
52
|
+
return error (`Expected argument '${name}'${type}, but got: ${require('util').inspect(value,{depth:11})}`, undefined, expected)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
|
package/lib/ql/Whereable.js
CHANGED
|
@@ -7,25 +7,33 @@ class Query extends require('./Query') {
|
|
|
7
7
|
where(...x) { return this._where (x,'and','where') }
|
|
8
8
|
and(...x) { return this._where (x,'and') }
|
|
9
9
|
or(...x) { return this._where (x,'or') }
|
|
10
|
-
_where (args, and_or,
|
|
11
|
-
let pred = predicate4(args,
|
|
10
|
+
_where (args, and_or, _where) {
|
|
11
|
+
let pred = predicate4(args, _where)
|
|
12
12
|
if (pred && pred.length > 0) {
|
|
13
13
|
let _ = this[this.cmd]
|
|
14
|
-
|
|
14
|
+
const clause = _where ?? (
|
|
15
15
|
_.having ? 'having' :
|
|
16
16
|
_.where ? 'where' :
|
|
17
17
|
_.from?.on ? 'on' :
|
|
18
18
|
error (`Invalid attempt to call '${this.cmd}.${and_or}()' before a prior call to '${this.cmd}.where()'`)
|
|
19
19
|
)
|
|
20
|
-
if (
|
|
21
|
-
let left = Reflect.getOwnPropertyDescriptor(_,
|
|
22
|
-
if (!left) {
|
|
20
|
+
if (clause === 'on') _ = _.from
|
|
21
|
+
let left = Reflect.getOwnPropertyDescriptor(_,clause)?.value
|
|
22
|
+
if (!left) { //> .where() called first time
|
|
23
|
+
// SELECT.from `X` .where `x or y` .and `z` -> SELECT from X where (x or y) and z
|
|
23
24
|
if (pred.includes('or')) this._left_has_or = true
|
|
24
|
-
_[
|
|
25
|
-
} else {
|
|
26
|
-
if (
|
|
25
|
+
_[clause] = pred
|
|
26
|
+
} else { //> .where(), .and(), .or() called successively
|
|
27
|
+
if (_where) {
|
|
28
|
+
// SELECT.from `X` .where `x` .or `y` .where `z` -> SELECT from X where (x or y) and z
|
|
29
|
+
if (left.includes('or')) left = [{xpr:left}]
|
|
30
|
+
} else if (and_or === 'and') {
|
|
31
|
+
// SELECT.from `X` .where `x` .or `y` .and `z` -> SELECT from X where x or y and z
|
|
32
|
+
if (this._left_has_or) { left = [{xpr:left}]; delete this._left_has_or }
|
|
33
|
+
}
|
|
34
|
+
// SELECT.from `X` .where `x` .and `y or z` -> SELECT from X where x and (y or z)
|
|
27
35
|
if (pred.includes('or')) pred = [{xpr:pred}]
|
|
28
|
-
_[
|
|
36
|
+
_[clause] = [ ...left, and_or, ...pred ]
|
|
29
37
|
}
|
|
30
38
|
}
|
|
31
39
|
return this
|
|
@@ -44,7 +52,10 @@ class Query extends require('./Query') {
|
|
|
44
52
|
|
|
45
53
|
const predicate4 = (args, _clause) => {
|
|
46
54
|
if (args.length === 0) return; /* else */ const x = args[0]
|
|
47
|
-
if (x.raw)
|
|
55
|
+
if (x.raw) {
|
|
56
|
+
let cxn = parse.CXL(...args)
|
|
57
|
+
return cxn.xpr ?? [cxn] //> the fallback is for single-item exprs like `1` or `ref`
|
|
58
|
+
}
|
|
48
59
|
if (args.length === 1 && typeof x === 'object') {
|
|
49
60
|
if (is_array(x)) return x
|
|
50
61
|
if (is_cqn(x)) return args
|
package/lib/ql/cds-ql.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const cds = require('../index')
|
|
2
1
|
const Query = require('./Query')
|
|
3
2
|
require = path => { // eslint-disable-line no-global-assign
|
|
4
3
|
const clazz = module.require (path); if (!clazz._api) return clazz
|
|
@@ -30,6 +29,7 @@ function _deprecated_srv_ql() { // eslint-disable-next-line no-console
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
module.exports._reset = ()=>{ // for strange tests only
|
|
32
|
+
const cds = require('../index')
|
|
33
33
|
const _name = cds.env.sql.names === 'quoted' ? n =>`"${n}"` : n => n.replace(/[.:]/g,'_')
|
|
34
34
|
Object.defineProperty (Query.prototype,'valueOf',{ configurable:1, value: function(cmd=this.cmd) {
|
|
35
35
|
return `${cmd} ${_name(this._target.name)} `
|
package/lib/req/response.js
CHANGED
|
@@ -4,8 +4,8 @@ const cds = require ('../index')
|
|
|
4
4
|
* Messages Collector, used for `req.errors` and `req.messages`
|
|
5
5
|
*/
|
|
6
6
|
class Responses extends Array {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
static get (severity, code, message, target, args) {
|
|
8
|
+
let e // be filled in below...
|
|
9
9
|
if (typeof code === 'object') e = code; else {
|
|
10
10
|
if (typeof code === 'number') e = { code }; else [ code, message, target, args, e ] = [ undefined, code, message, target, {} ]
|
|
11
11
|
if (typeof message === 'object') e = Object.assign(message,e); else {
|
|
@@ -16,9 +16,14 @@ class Responses extends Array {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
if (!e.numericSeverity) e.numericSeverity = severity
|
|
19
|
-
this.push(e)
|
|
20
19
|
return e
|
|
21
20
|
}
|
|
21
|
+
|
|
22
|
+
add (...args) {
|
|
23
|
+
const response = Responses.get(...args)
|
|
24
|
+
this.push(response)
|
|
25
|
+
return response
|
|
26
|
+
}
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
class Errors extends Responses {
|