@sap/cds 6.4.1 → 6.6.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 +79 -6
- package/README.md +5 -0
- package/apis/cqn.d.ts +14 -3
- package/apis/ql.d.ts +8 -8
- package/apis/services.d.ts +37 -65
- package/apis/test.d.ts +7 -0
- package/bin/build/buildTaskEngine.js +9 -14
- package/bin/build/buildTaskFactory.js +1 -1
- package/bin/build/buildTaskHandler.js +3 -14
- package/bin/build/index.js +8 -2
- package/bin/build/provider/buildTaskProviderInternal.js +18 -13
- package/bin/build/provider/fiori/index.js +5 -10
- package/bin/build/provider/hana/2migration.js +11 -2
- package/bin/build/provider/hana/index.js +17 -14
- package/bin/build/provider/hana/template/.hdiconfig-hanacloud +137 -0
- package/bin/build/provider/hana/template/package.json +3 -0
- package/bin/build/provider/mtx/resourcesTarBuilder.js +12 -3
- package/bin/build/provider/mtx-extension/index.js +57 -37
- package/bin/build/provider/mtx-sidecar/index.js +1 -1
- package/bin/build/util.js +18 -1
- package/bin/cds.js +1 -5
- package/bin/deploy/to-hana/hana.js +10 -3
- package/bin/serve.js +36 -20
- package/common.cds +7 -0
- package/lib/auth/jwt-auth.js +8 -7
- package/lib/compile/for/lean_drafts.js +55 -6
- package/lib/compile/minify.js +3 -3
- package/lib/dbs/cds-deploy.js +18 -17
- 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/index.js +4 -2
- 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/cds-context.js +3 -3
- package/lib/req/response.js +8 -3
- package/lib/req/user.js +12 -2
- package/lib/srv/bindings.js +1 -2
- package/lib/srv/cds-serve.js +2 -1
- package/lib/srv/middlewares/trace.js +31 -15
- package/lib/srv/protocols/odata-v2-proxy.js +8 -8
- package/lib/srv/srv-handlers.js +26 -7
- package/lib/srv/srv-methods.js +2 -2
- package/lib/srv/srv-models.js +8 -3
- package/lib/utils/cds-test.js +7 -5
- package/lib/utils/cds-utils.js +3 -1
- package/lib/utils/tar.js +6 -3
- package/libx/_runtime/auth/strategies/JWT.js +1 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +3 -2
- 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/OData.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +8 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +11 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +8 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +14 -14
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ResourceJsonSerializer.js +3 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +3 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +7 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +0 -3
- package/libx/_runtime/cds-services/services/Service.js +11 -19
- package/libx/_runtime/cds-services/services/utils/columns.js +42 -40
- package/libx/_runtime/cds-services/util/assert.js +7 -1
- package/libx/_runtime/common/code-ext/WorkerReq.js +81 -0
- package/libx/_runtime/common/code-ext/config.js +13 -0
- package/libx/_runtime/common/code-ext/execute.js +113 -0
- package/libx/_runtime/common/code-ext/handlers.js +49 -0
- package/libx/_runtime/common/code-ext/worker.js +40 -0
- package/libx/_runtime/common/code-ext/workerQuery.js +45 -0
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +36 -0
- package/libx/_runtime/common/composition/data.js +5 -2
- package/libx/_runtime/common/composition/tree.js +2 -0
- package/libx/_runtime/common/generic/auth/restrict.js +1 -1
- package/libx/_runtime/common/generic/crud.js +4 -0
- package/libx/_runtime/common/generic/etag.js +3 -1
- package/libx/_runtime/common/generic/input.js +12 -14
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +47 -22
- package/libx/_runtime/common/utils/path.js +5 -26
- package/libx/_runtime/common/utils/search2cqn4sql.js +16 -9
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +19 -13
- package/libx/_runtime/db/data-conversion/post-processing.js +1 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +7 -4
- package/libx/_runtime/db/expand/rawToExpanded.js +3 -2
- package/libx/_runtime/db/generic/input.js +2 -2
- package/libx/_runtime/db/generic/integrity.js +1 -0
- package/libx/_runtime/db/generic/virtual.js +1 -0
- package/libx/_runtime/db/query/read.js +3 -2
- package/libx/_runtime/db/utils/localized.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +7 -1
- package/libx/_runtime/fiori/generic/before.js +9 -1
- package/libx/_runtime/fiori/generic/edit.js +8 -1
- package/libx/_runtime/fiori/generic/new.js +2 -0
- package/libx/_runtime/fiori/generic/patch.js +2 -0
- package/libx/_runtime/fiori/generic/prepare.js +2 -0
- package/libx/_runtime/fiori/generic/read.js +16 -5
- package/libx/_runtime/fiori/generic/readOverDraft.js +2 -0
- package/libx/_runtime/fiori/lean-draft.js +505 -241
- package/libx/_runtime/fiori/utils/delete.js +2 -0
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -5
- package/libx/_runtime/hana/pool.js +1 -1
- package/libx/_runtime/hana/search2cqn4sql.js +51 -51
- package/libx/_runtime/messaging/Outbox.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -0
- package/libx/_runtime/messaging/enterprise-messaging.js +2 -6
- package/libx/_runtime/messaging/file-based.js +1 -2
- package/libx/_runtime/messaging/outbox/OutboxRunner.js +1 -1
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +0 -1
- package/libx/_runtime/remote/Service.js +1 -0
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +19 -3
- package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +0 -18
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +0 -18
- package/libx/_runtime/sqlite/customBuilder/CustomSelectBuilder.js +0 -24
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -1
- package/libx/_runtime/sqlite/customBuilder/index.js +47 -32
- package/libx/odata/afterburner.js +23 -8
- package/libx/odata/cqn2odata.js +1 -1
- package/libx/odata/grammar.pegjs +3 -4
- package/libx/odata/index.js +5 -1
- package/libx/odata/parseToCqn.js +3 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +58 -1
- package/libx/rest/middleware/parse.js +26 -4
- package/package.json +1 -1
- package/server.js +1 -1
- package/libx/_runtime/sqlite/customBuilder/CustomDeleteBuilder.js +0 -17
- package/libx/_runtime/sqlite/customBuilder/CustomReferenceBuilder.js +0 -11
- package/libx/_runtime/sqlite/customBuilder/CustomUpdateBuilder.js +0 -17
- /package/bin/build/provider/hana/template/{.hdiconfig → .hdiconfig-haas} +0 -0
package/lib/auth/jwt-auth.js
CHANGED
|
@@ -24,20 +24,21 @@ module.exports = function jwt_auth(config) {
|
|
|
24
24
|
.use(passport.authenticate(config.kind, { session: false }))
|
|
25
25
|
.use((req, res, next) => {
|
|
26
26
|
const payload = req.tokenInfo.getPayload()
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
let id = req.user.id
|
|
29
|
-
|
|
29
|
+
let _is_system, _is_internal
|
|
30
|
+
|
|
30
31
|
let roles = payload.scope.map(s => s.replace(new RegExp(`^(${config.credentials.xsappname + '.'})`), ''))
|
|
31
32
|
roles.push('identified-user')
|
|
32
33
|
if (payload.grant_type) {
|
|
33
34
|
// > not "weak"
|
|
34
35
|
roles.push('authenticated-user')
|
|
35
|
-
|
|
36
|
+
|
|
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,11 +50,11 @@ 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
|
})
|
|
56
|
-
.use((err, req, res,
|
|
57
|
+
.use((err, req, res, _next) => {
|
|
57
58
|
if (req.tokenInfo) {
|
|
58
59
|
LOG?.debug('error during token validation', req.tokenInfo.getErrorObject())
|
|
59
60
|
}
|
|
@@ -1,5 +1,43 @@
|
|
|
1
|
-
const cds = require
|
|
2
|
-
|
|
1
|
+
const cds = require('../../index')
|
|
2
|
+
|
|
3
|
+
function _getBacklinkName(on) {
|
|
4
|
+
const i = on.findIndex(e => e.ref && e.ref[0] === '$self')
|
|
5
|
+
if (i === -1) return
|
|
6
|
+
let ref
|
|
7
|
+
if (on[i + 1] && on[i + 1] === '=') ref = on[i + 2].ref
|
|
8
|
+
if (on[i - 1] && on[i - 1] === '=') ref = on[i - 2].ref
|
|
9
|
+
return ref && ref[ref.length - 1]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function _isCompositionBacklink(e) {
|
|
13
|
+
if (!e.isAssociation) return
|
|
14
|
+
if (!e._target?.associations) return
|
|
15
|
+
if (!(!e.isComposition && (e.keys || e.on))) return
|
|
16
|
+
for (const anchor of Object.values(e._target.associations)) {
|
|
17
|
+
if (!(anchor.isComposition && anchor.on?.length > 2)) continue
|
|
18
|
+
if (_getBacklinkName(anchor.on) === e.name && anchor.target === e.parent.name) return anchor
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const IGNORED_ANNOTATIONS = [
|
|
23
|
+
'@assert.range',
|
|
24
|
+
'@assert.enum',
|
|
25
|
+
'@assert.format',
|
|
26
|
+
'@assert.target',
|
|
27
|
+
'@mandatory',
|
|
28
|
+
'@Core.Immutable',
|
|
29
|
+
'@readonly',
|
|
30
|
+
'@cds.on.update',
|
|
31
|
+
'@cds.on.insert',
|
|
32
|
+
'@Core.Computed',
|
|
33
|
+
'@Common.FieldControl.Readonly',
|
|
34
|
+
'@Common.FieldControl.Mandatory',
|
|
35
|
+
'@FieldControl.Mandatory',
|
|
36
|
+
'@FieldControl.ReadOnly',
|
|
37
|
+
'@Common.FieldControl'
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
module.exports = function cds_compile_for_lean_drafts(csn) {
|
|
3
41
|
const DRAFT_ELEMENTS = new Set([
|
|
4
42
|
'IsActiveEntity',
|
|
5
43
|
'HasDraftEntity',
|
|
@@ -22,6 +60,13 @@ module.exports = function cds_compile_for_lean_drafts(csn, o) {
|
|
|
22
60
|
return on
|
|
23
61
|
}
|
|
24
62
|
|
|
63
|
+
function _isDraft(def) {
|
|
64
|
+
return (
|
|
65
|
+
def.associations?.DraftAdministrativeData ||
|
|
66
|
+
(def.own('@odata.draft.enabled') && def.own('@Common.DraftRoot.ActivationAction'))
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
25
70
|
const { Draft } = cds.linked(`
|
|
26
71
|
entity ActiveEntity { key ID: UUID; }
|
|
27
72
|
entity Draft {
|
|
@@ -49,15 +94,21 @@ module.exports = function cds_compile_for_lean_drafts(csn, o) {
|
|
|
49
94
|
const draft = { __proto__: active, name: _draftEntity, elements: { ...active.elements, ...Draft.elements } }
|
|
50
95
|
Object.defineProperty(model.definitions, _draftEntity, { value: draft })
|
|
51
96
|
Object.defineProperty(active, 'drafts', { value: draft })
|
|
97
|
+
Object.defineProperty(draft, 'actives', { value: active })
|
|
52
98
|
draft['@cds.persistence.table'] = _draftEntity
|
|
99
|
+
if (draft['@restrict']) draft['@restrict'] = undefined
|
|
53
100
|
// Recursively add drafts for compositions
|
|
54
101
|
for (const each in draft.elements) {
|
|
55
102
|
const e = draft.elements[each]
|
|
56
103
|
const newEl = Object.create(e)
|
|
57
|
-
if (e.isComposition || (e.isAssociation && e['@odata.draft.enclosed']) || e
|
|
104
|
+
if (e.isComposition || (e.isAssociation && e['@odata.draft.enclosed']) || _isCompositionBacklink(e)) {
|
|
105
|
+
if (e._target['@odata.draft.enabled'] === false) continue // happens for texts if @fiori.draft.enabled is not set
|
|
58
106
|
_redirect(newEl, draftEntity(e._target, model))
|
|
59
107
|
}
|
|
60
108
|
newEl.parent = draft
|
|
109
|
+
for (const ignoredAnno of IGNORED_ANNOTATIONS) {
|
|
110
|
+
if (newEl[ignoredAnno]) newEl[ignoredAnno] = undefined
|
|
111
|
+
}
|
|
61
112
|
draft.elements[each] = newEl
|
|
62
113
|
}
|
|
63
114
|
// TODO: Redirect associations to localized
|
|
@@ -65,9 +116,7 @@ module.exports = function cds_compile_for_lean_drafts(csn, o) {
|
|
|
65
116
|
}
|
|
66
117
|
for (const name in csn.definitions) {
|
|
67
118
|
const def = csn.definitions[name]
|
|
68
|
-
if (!def
|
|
69
|
-
continue
|
|
70
|
-
// so that database ignores them
|
|
119
|
+
if (!_isDraft(def)) continue
|
|
71
120
|
;[
|
|
72
121
|
'IsActiveEntity',
|
|
73
122
|
'HasDraftEntity',
|
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/dbs/cds-deploy.js
CHANGED
|
@@ -107,9 +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
|
-
|
|
111
|
-
const in_memory = (creds?.url || creds?.database) === ':memory:';
|
|
112
|
-
if (!in_memory && schemaEvo) {
|
|
110
|
+
if (schemaEvo) {
|
|
113
111
|
const { afterImage: afterCsn, drops, createsAndAlters: creas } = cds.compile.to.sql.delta (csn, o, beforeCsn);
|
|
114
112
|
if(beforeCsn === undefined) {
|
|
115
113
|
// If this is the first deployment done with automatic schema evolution, generate everything as if it was a drop create
|
|
@@ -134,13 +132,12 @@ exports.create = async function (db, csn=db.model, o) {
|
|
|
134
132
|
const schemaEvo = (db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto')
|
|
135
133
|
if(db.deploy && !schemaEvo) {
|
|
136
134
|
// reset CSN state saved in db - if there is any
|
|
137
|
-
await db.run('DROP table if exists cds_Model;');
|
|
135
|
+
if(!o.dry) await db.run('DROP table if exists cds_Model;');
|
|
138
136
|
return db.deploy(csn, o);
|
|
139
137
|
}
|
|
140
138
|
|
|
141
|
-
const in_memory = db.options?.credentials?.url === ':memory:';
|
|
142
139
|
let beforeCsn
|
|
143
|
-
if (
|
|
140
|
+
if (schemaEvo) try {
|
|
144
141
|
const [{ csn }] = await db.read('cds.Model')
|
|
145
142
|
beforeCsn = JSON.parse(csn);
|
|
146
143
|
} catch(e) {
|
|
@@ -160,7 +157,7 @@ exports.create = async function (db, csn=db.model, o) {
|
|
|
160
157
|
} else return db.run (async tx => {
|
|
161
158
|
await tx.run(drops)
|
|
162
159
|
await tx.run(creas)
|
|
163
|
-
if (
|
|
160
|
+
if (schemaEvo) {
|
|
164
161
|
await tx.update('cds.Model').with({ csn: JSON.stringify(afterCsn) })
|
|
165
162
|
}
|
|
166
163
|
return true
|
|
@@ -169,27 +166,31 @@ exports.create = async function (db, csn=db.model, o) {
|
|
|
169
166
|
|
|
170
167
|
|
|
171
168
|
exports.init = (db, csn=db.model, o, csvs, log=()=>{}) => db.run (async tx => {
|
|
169
|
+
|
|
170
|
+
const {tenant} = cds.context; if (tenant && tenant === cds.requires.multitenancy?.t0) return
|
|
171
|
+
const schemaEvo = db.options?.schema_evolution === 'auto' || o?.schema_evolution === 'auto'
|
|
172
172
|
const resources = await exports.resources(csn, {testdata: cds.env.features.test_data})
|
|
173
173
|
const inits=[]
|
|
174
|
-
const schemaEvo = (db.options?.schema_evolution === 'auto' || o?.schema_evolution === 'auto')
|
|
175
|
-
|
|
176
174
|
|
|
177
175
|
if (csvs) {
|
|
178
176
|
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
|
-
|
|
177
|
+
for(let [file,src] of Object.entries(csvs)) {
|
|
178
|
+
const entity = _entity4(path.basename(file, '.csv'), csn)
|
|
179
|
+
if (entity?.name) {
|
|
180
|
+
const q = INSERT_from_csv (entity.name,src,schemaEvo); if (!q) continue
|
|
181
|
+
if (db.kind === 'better-sqlite') _add_missing_pks2(q)
|
|
182
|
+
q._target = ccsn.definitions[entity.name]
|
|
183
|
+
inits.push (tx.run(q) .catch (e => {
|
|
184
|
+
throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ inspect(q) })
|
|
185
|
+
}))
|
|
186
|
+
}
|
|
186
187
|
}
|
|
187
188
|
} else {
|
|
188
189
|
for (let [file,e] of Object.entries(resources)) {
|
|
189
190
|
if (e === '*') { // init.js/ts
|
|
190
191
|
let x = await cds.utils._import(file); if (!x) continue
|
|
191
192
|
if (x.default) x = x.default // default ESM export
|
|
192
|
-
inits.push (!x.then && typeof x === 'function' ? x(
|
|
193
|
+
inits.push (!x.then && typeof x === 'function' ? x(tx,csn) : x)
|
|
193
194
|
log (file)
|
|
194
195
|
} else { // from .csv or .json
|
|
195
196
|
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/index.js
CHANGED
|
@@ -12,8 +12,9 @@ const facade = class cds extends require('events') {
|
|
|
12
12
|
set context(_){ this._context._for(this,_) }
|
|
13
13
|
get spawn() { return super.spawn = this._context.spawn }
|
|
14
14
|
|
|
15
|
-
emit (eve, ...args) {
|
|
16
|
-
if (eve === 'served') return Promise.all (this.listeners(eve).map (l => l.call(this,...args)))
|
|
15
|
+
async emit (eve, ...args) {
|
|
16
|
+
// if (eve === 'served') return Promise.all (this.listeners(eve).map (l => l.call(this,...args)))
|
|
17
|
+
if (eve === 'served') for (let l of this.listeners(eve)) await l.call(this,...args)
|
|
17
18
|
else return super.emit (eve, ...args)
|
|
18
19
|
}
|
|
19
20
|
}
|
|
@@ -93,6 +94,7 @@ const cds = module.exports = extend (new facade) .with ({
|
|
|
93
94
|
test: require ('./utils/cds-test'),
|
|
94
95
|
log: require ('./log/cds-log'), debug: lazy => cds.log.debug,
|
|
95
96
|
exec: require ('../bin/cds'),
|
|
97
|
+
exit: (code) => cds.shutdown ? cds.shutdown() : process.exit(code),
|
|
96
98
|
clone: m => JSON.parse (JSON.stringify(m)),
|
|
97
99
|
lazified, lazify,
|
|
98
100
|
|
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/cds-context.js
CHANGED
|
@@ -47,9 +47,9 @@ module.exports = new class extends AsyncLocalStorage {
|
|
|
47
47
|
})
|
|
48
48
|
}
|
|
49
49
|
const em = new EventEmitter; em.timer = (
|
|
50
|
-
(o && o.after) ? setTimeout(fx, o.after) :
|
|
51
|
-
(o && o.every) ? setInterval(fx, o.every) :
|
|
52
|
-
setImmediate(fx)
|
|
50
|
+
(o && o.after) ? setTimeout(fx, o.after).unref() :
|
|
51
|
+
(o && o.every) ? setInterval(fx, o.every).unref() :
|
|
52
|
+
setImmediate(fx).unref()
|
|
53
53
|
)
|
|
54
54
|
return em
|
|
55
55
|
}
|
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 {
|
package/lib/req/user.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const PSEUDO_ROLES = ['system-user', 'internal-user']
|
|
2
|
+
|
|
1
3
|
class User {
|
|
2
4
|
|
|
3
5
|
constructor (_) {
|
|
@@ -9,14 +11,22 @@ class User {
|
|
|
9
11
|
if (typeof _ === 'string') { this.id = _; return }
|
|
10
12
|
for (let each in _) super[each === '_roles' ? 'roles' : each] = _[each] // overrides getters
|
|
11
13
|
const roles = this.hasOwnProperty('roles') && this.roles // eslint-disable-line no-prototype-builtins
|
|
12
|
-
if (Array.isArray(roles)) this.roles = roles.reduce ((p,n)=>{p[n]=1; return p},{})
|
|
14
|
+
if (Array.isArray(roles)) this.roles = roles.filter(r => !PSEUDO_ROLES.includes(r)).reduce ((p,n)=>{p[n]=1; return p},{})
|
|
15
|
+
else PSEUDO_ROLES.forEach(r => delete this.roles[r])
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
get attr() { return super.attr = {} }
|
|
16
19
|
get roles(){ return super.roles = {} }
|
|
17
20
|
get _roles(){ return this.roles } // compatibility
|
|
18
21
|
|
|
19
|
-
is (role) {
|
|
22
|
+
is (role) {
|
|
23
|
+
return role === 'any' ||
|
|
24
|
+
role === 'identified-user' ||
|
|
25
|
+
role === 'system-user' && this._is_system ||
|
|
26
|
+
role === 'internal-user' && this._is_internal ||
|
|
27
|
+
role === 'authenticated-user' && this.authLevel !== 'weak' ||
|
|
28
|
+
!!this.roles[role]
|
|
29
|
+
}
|
|
20
30
|
valueOf() { return this.id }
|
|
21
31
|
|
|
22
32
|
}
|
package/lib/srv/bindings.js
CHANGED
package/lib/srv/cds-serve.js
CHANGED
|
@@ -56,7 +56,8 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
|
|
|
56
56
|
if (o.service && o.service !== 'all') {
|
|
57
57
|
// skip services not chosen by o.service, if specified
|
|
58
58
|
const specified = o.service.split(/\s*,\s*/).map (s => required[s] && required[s].service || s )
|
|
59
|
-
|
|
59
|
+
// matching exact or unqualified name
|
|
60
|
+
services = services.filter (s => specified.some (n => s.name === n || s.name.endsWith('.'+n)))
|
|
60
61
|
if (!services.length) throw cds.error (`No such service: '${o.service}'`)
|
|
61
62
|
}
|
|
62
63
|
services = services.filter (d => !(
|
|
@@ -66,15 +66,22 @@ function _instrument_sqlite (_get_perf) {
|
|
|
66
66
|
try { require.resolve('sqlite3') } catch { return }
|
|
67
67
|
// eslint-disable-next-line cds/no-missing-dependencies
|
|
68
68
|
const sqlite = require('sqlite3').Database.prototype
|
|
69
|
-
for (let each of ['all', 'get', 'run', 'prepare'])
|
|
70
|
-
|
|
71
|
-
sqlite[
|
|
72
|
-
|
|
69
|
+
for (let each of ['all', 'get', 'run', 'prepare']) _wrap(each,sqlite)
|
|
70
|
+
function _wrap (op,sqlite) {
|
|
71
|
+
const _super = sqlite[op]
|
|
72
|
+
sqlite[op] = function (q, ..._) {
|
|
73
|
+
const perf = _get_perf(q) //> q is a SQL command like BEGIN, COMMIT, ROLLBACK, SELECT ...
|
|
73
74
|
if (perf) {
|
|
74
|
-
const pe = perf.log ('
|
|
75
|
-
const callback = _[_.length-1]; _[_.length-1] = function(){
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const pe = perf.log ('sqlite3', '-', q)
|
|
76
|
+
const callback = _[_.length-1]; _[_.length-1] = function(ps){
|
|
77
|
+
if (op === 'prepare') callback.apply (this, {
|
|
78
|
+
all: _wrap('all',ps),
|
|
79
|
+
get: _wrap('get',ps),
|
|
80
|
+
run: _wrap('run',ps),
|
|
81
|
+
}); else {
|
|
82
|
+
perf.done(pe)
|
|
83
|
+
callback.apply (this, arguments)
|
|
84
|
+
}
|
|
78
85
|
}
|
|
79
86
|
}
|
|
80
87
|
return _super.call (this, q, ..._)
|
|
@@ -87,14 +94,23 @@ function _instrument_better_sqlite (_get_perf) {
|
|
|
87
94
|
try { require.resolve('better-sqlite3') } catch { return }
|
|
88
95
|
// eslint-disable-next-line cds/no-missing-dependencies
|
|
89
96
|
const sqlite = require('better-sqlite3').prototype
|
|
90
|
-
for (let each of ['exec', 'prepare'])
|
|
91
|
-
|
|
92
|
-
sqlite[
|
|
93
|
-
|
|
97
|
+
for (let each of ['exec', 'prepare']) _wrap(each,sqlite)
|
|
98
|
+
function _wrap (op,sqlite) {
|
|
99
|
+
const _super = sqlite[op]
|
|
100
|
+
sqlite[op] = function (q, ..._) {
|
|
101
|
+
const perf = _get_perf(q) //> q is a SQL command like BEGIN, COMMIT, ROLLBACK, SELECT ...
|
|
94
102
|
if (perf) {
|
|
95
|
-
const pe = perf.log ('
|
|
96
|
-
try {
|
|
97
|
-
|
|
103
|
+
const pe = perf.log ('better-sqlite3', '-', q)
|
|
104
|
+
try {
|
|
105
|
+
const x = _super.call (this, q, ..._)
|
|
106
|
+
if (op === 'prepare') return {
|
|
107
|
+
all(..._){ try { return x.all(..._) } finally { perf.done(pe) }},
|
|
108
|
+
get(..._){ try { return x.get(..._) } finally { perf.done(pe) }},
|
|
109
|
+
run(..._){ try { return x.run(..._) } finally { perf.done(pe) }},
|
|
110
|
+
}
|
|
111
|
+
else return x
|
|
112
|
+
}
|
|
113
|
+
catch(e) { perf.done(pe); throw e }
|
|
98
114
|
}
|
|
99
115
|
else return _super.call (this, q, ..._)
|
|
100
116
|
}
|