@sap/cds 9.2.1 → 9.3.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 +77 -1
- package/_i18n/i18n_es.properties +3 -3
- package/_i18n/i18n_es_MX.properties +3 -3
- package/_i18n/i18n_fr.properties +2 -2
- package/_i18n/messages.properties +6 -0
- package/app/index.js +0 -1
- package/bin/deploy.js +1 -1
- package/bin/serve.js +7 -20
- package/lib/compile/cdsc.js +3 -0
- package/lib/compile/for/flows.js +102 -0
- package/lib/compile/for/nodejs.js +28 -0
- package/lib/compile/to/edm.js +11 -4
- package/lib/core/classes.js +1 -1
- package/lib/core/linked-csn.js +8 -0
- package/lib/dbs/cds-deploy.js +12 -12
- package/lib/env/cds-env.js +1 -1
- package/lib/env/cds-requires.js +21 -20
- package/lib/env/defaults.js +2 -1
- package/lib/index.js +5 -6
- package/lib/log/cds-log.js +6 -5
- package/lib/log/format/aspects/cf.js +2 -2
- package/lib/plugins.js +1 -1
- package/lib/ql/cds-ql.js +0 -3
- package/lib/req/request.js +3 -3
- package/lib/req/response.js +12 -7
- package/lib/srv/bindings.js +17 -17
- package/lib/srv/cds-connect.js +6 -9
- package/lib/srv/cds-serve.js +74 -137
- package/lib/srv/cds.Service.js +49 -0
- package/lib/srv/factory.js +4 -4
- package/lib/srv/middlewares/auth/ias-auth.js +29 -9
- package/lib/srv/middlewares/auth/index.js +3 -2
- package/lib/srv/middlewares/auth/jwt-auth.js +19 -6
- package/lib/srv/protocols/hcql.js +16 -1
- package/lib/srv/srv-dispatch.js +1 -1
- package/lib/utils/cds-utils.js +4 -8
- package/lib/utils/csv-reader.js +27 -7
- package/libx/_runtime/cds.js +0 -6
- package/libx/_runtime/common/Service.js +5 -0
- package/libx/_runtime/common/generic/crud.js +1 -1
- package/libx/_runtime/common/generic/flows.js +106 -0
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/utils/differ.js +5 -15
- package/libx/_runtime/common/utils/resolveView.js +2 -2
- package/libx/_runtime/fiori/lean-draft.js +76 -40
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
- package/libx/_runtime/remote/Service.js +68 -62
- package/libx/_runtime/remote/utils/client.js +29 -216
- package/libx/_runtime/remote/utils/query.js +197 -0
- package/libx/_runtime/ucl/Service.js +180 -112
- package/libx/_runtime/ucl/queries.js +61 -0
- package/libx/odata/ODataAdapter.js +1 -4
- package/libx/odata/index.js +2 -10
- package/libx/odata/middleware/error.js +8 -1
- package/libx/odata/middleware/stream.js +1 -1
- package/libx/odata/middleware/update.js +12 -2
- package/libx/odata/parse/afterburner.js +113 -20
- package/libx/odata/parse/cqn2odata.js +1 -3
- package/libx/rest/middleware/parse.js +9 -2
- package/package.json +2 -2
- package/server.js +2 -0
- package/srv/app-service.js +1 -0
- package/srv/db-service.js +1 -0
- package/srv/msg-service.js +1 -0
- package/srv/remote-service.js +1 -0
- package/srv/ucl-service.cds +32 -0
- package/srv/ucl-service.js +1 -0
- package/lib/ql/resolve.js +0 -45
- package/libx/common/assert/type-strict.js +0 -109
- package/libx/common/assert/utils.js +0 -60
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,76 @@
|
|
|
4
4
|
- The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
5
5
|
- This project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## Version 9.3.0 - 2025-08-29
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- New method `collect()` has been added to `LinkedCSN`, which can be used like that:
|
|
12
|
+
```js
|
|
13
|
+
const federated_entities = cds.linked(csn).collect (d => d.is_entity && d['@federated'])
|
|
14
|
+
```
|
|
15
|
+
- Remote services can now be configured without `kind`, for example:
|
|
16
|
+
```json
|
|
17
|
+
{ "cds": { "requires": { "SomeService": true }}}
|
|
18
|
+
```
|
|
19
|
+
- Automatic protocol selection is applied if a required service is configured as above
|
|
20
|
+
and the remote services is served via multiple protocols. For example, if the above
|
|
21
|
+
service would be declared like that, the best protocol would be chosen automatically
|
|
22
|
+
(`hcql` in this case):
|
|
23
|
+
```cds
|
|
24
|
+
@hcql @rest @odata service SomeService {...}
|
|
25
|
+
```
|
|
26
|
+
- Method `cds.connect.to()` now allows to connect to remote services with just an http url.
|
|
27
|
+
For example use that from `cds repl` like that:
|
|
28
|
+
```js
|
|
29
|
+
srv = await cds.connect.to ('http://localhost:4004/hcql/books')
|
|
30
|
+
await srv.read `ID, title, author.name from Books`
|
|
31
|
+
```
|
|
32
|
+
- Property `cds.User.authInfo` as generic container for authentication-related information
|
|
33
|
+
+ For `@sap/xssec`-based authentication strategies, `cds.context.user.authInfo` is an instance of `@sap/xssec`'s `SecurityContext`
|
|
34
|
+
- Support for state transition flows (`@flow`):
|
|
35
|
+
+ Generic handlers for validating entry (`@from`) and exit (`@to`) states
|
|
36
|
+
+ Automatic addition of necessary annotations for Fiori UIs (`@Common.SideEffects` and `@Core.OperationAvailable`) during compile to EDMX with feature flag `cds.features.compile_for_flows = true`
|
|
37
|
+
- Experimental support for consuming remote HCQL services (`cds.requires.<remote>.kind = 'hcql'`)
|
|
38
|
+
- Infrastructure for implementing the tenant mapping notification of Unified Customer Landscape's (UCL) Service Provider Integration Interface (SPII) API
|
|
39
|
+
+ Bootstrap the `UCLService` via `cds.requires.ucl = true` and implement the assign and unassign operations like so:
|
|
40
|
+
```js
|
|
41
|
+
// custom server.js
|
|
42
|
+
cds.on('served', async () => {
|
|
43
|
+
const ucl = await cds.connect.to('ucl')
|
|
44
|
+
ucl.on('assign', async function(req) { ... })
|
|
45
|
+
ucl.on('unassign', async function(req) { ... })
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
+ Currently, only the synchronous interaction pattern is supported!
|
|
49
|
+
- The targets of `@Common.Text` are added to the default search targets
|
|
50
|
+
- Patch Level Validations are enabled by default. Opt-out with `cds.fiori.draft_messages=false`
|
|
51
|
+
- Enable custom aggregations for currency codes and units of measure
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- `UCLService` only pushes the application template to UCL if `cds.requires.ucl.applicationTemplate` is present
|
|
56
|
+
- `cds.User.tokenInfo` is deprecated. Use `cds.context.user.authInfo.token` instead.
|
|
57
|
+
- Undocumented compat `cds.context.http.req.authInfo` is deprecated. Use `cds.context.user.authInfo` instead.
|
|
58
|
+
- Delete all persisted draft messages, when the first request targeting a draft child without containment is handled.
|
|
59
|
+
- cds build now trims leading or trailing whitespace characters for all values in CSV files deployed to SAP HANA.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- Errors when reading complementary drafts
|
|
64
|
+
- Apply configurations in case `cds.env` was loaded before `cds.log` is initialized.
|
|
65
|
+
- `req.diff` resolves correctly deleted nested composition by deep update
|
|
66
|
+
- `cds-deploy` did not terminate correctly even though deployment was successful
|
|
67
|
+
- Requests to an unimplemented unbound action/ function are rejected
|
|
68
|
+
- Custom app-service implementations configured through `cds.requires.app-service.impl` is now correctly resolved (again)
|
|
69
|
+
- Validation of UUID format for navigation by key
|
|
70
|
+
|
|
71
|
+
### Removed
|
|
72
|
+
|
|
73
|
+
- Internal property `cds.services._pending` was removed => use `cds.services` instead.
|
|
74
|
+
- Internal property `srv._is_dark` was removed => use `!srv.endpoints?.length` instead.
|
|
75
|
+
- Internal method `cds.env.requires._resolved` was removed => use `cds.requires` instead.
|
|
76
|
+
|
|
7
77
|
## Version 9.2.1 - 2025-08-15
|
|
8
78
|
|
|
9
79
|
### Fixed
|
|
@@ -41,7 +111,7 @@
|
|
|
41
111
|
- Runtime error in transaction handling in messaging services when used with outbox
|
|
42
112
|
- Always use `cds.context` middleware for `enterprise-messaging` endpoints
|
|
43
113
|
- Crash during Location header generation caused by custom response not matching the entity definition.
|
|
44
|
-
- Support for logging of correct error locations with `cds watch` and `cds run`.
|
|
114
|
+
- Support for logging of correct error locations with `cds watch` and `cds run`.
|
|
45
115
|
- Double-unescaping of values in double quotes during OData URL parsing
|
|
46
116
|
- Throw explicit error if the result of a media data query is not an instance of `Readable`, rather than responding with `No Content`
|
|
47
117
|
- When loading `.csv` files quoted strings containing the separator (comma or semicolon) where erroneously
|
|
@@ -204,6 +274,12 @@
|
|
|
204
274
|
- Deprecated stripping of unnecessary topic prefix `topic:` in messaging
|
|
205
275
|
- Deprecated messaging `Outbox` class. Please use config or `cds.outboxed(srv)` to outbox your service.
|
|
206
276
|
|
|
277
|
+
## Version 8.9.6 - 2025-07-29
|
|
278
|
+
|
|
279
|
+
### Fixed
|
|
280
|
+
|
|
281
|
+
- Batch insert using `INSERT.entries()` on draft enabled entities
|
|
282
|
+
|
|
207
283
|
## Version 8.9.5 - 2025-07-25
|
|
208
284
|
|
|
209
285
|
### Fixed
|
package/_i18n/i18n_es.properties
CHANGED
|
@@ -26,16 +26,16 @@
|
|
|
26
26
|
#----------------------------------------------------------------------------------------------------------------------
|
|
27
27
|
|
|
28
28
|
#XTIT: Created By (Answer to: "Which user has created a certain entity?")
|
|
29
|
-
CreatedBy=
|
|
29
|
+
CreatedBy=Autor
|
|
30
30
|
|
|
31
31
|
#XTIT: Created On (Answer to: "When has a certain entity been created?")
|
|
32
32
|
CreatedAt=Fecha de creaci\u00F3n
|
|
33
33
|
|
|
34
34
|
#XTIT: Changed By (Answer to: "Which user has changed a certain entity?")
|
|
35
|
-
ChangedBy=
|
|
35
|
+
ChangedBy=Autor de modificaci\u00F3n
|
|
36
36
|
|
|
37
37
|
#XTIT: Changed On (Answer to: "When has a certain entity been changed?")
|
|
38
|
-
ChangedAt=
|
|
38
|
+
ChangedAt=Fecha de modificaci\u00F3n
|
|
39
39
|
|
|
40
40
|
#XTIT: Currency
|
|
41
41
|
Currency=Moneda
|
|
@@ -26,16 +26,16 @@
|
|
|
26
26
|
#----------------------------------------------------------------------------------------------------------------------
|
|
27
27
|
|
|
28
28
|
#XTIT: Created By (Answer to: "Which user has created a certain entity?")
|
|
29
|
-
CreatedBy=
|
|
29
|
+
CreatedBy=Autor
|
|
30
30
|
|
|
31
31
|
#XTIT: Created On (Answer to: "When has a certain entity been created?")
|
|
32
32
|
CreatedAt=Fecha de creaci\u00F3n
|
|
33
33
|
|
|
34
34
|
#XTIT: Changed By (Answer to: "Which user has changed a certain entity?")
|
|
35
|
-
ChangedBy=
|
|
35
|
+
ChangedBy=Autor de modificaci\u00F3n
|
|
36
36
|
|
|
37
37
|
#XTIT: Changed On (Answer to: "When has a certain entity been changed?")
|
|
38
|
-
ChangedAt=
|
|
38
|
+
ChangedAt=Fecha de modificaci\u00F3n
|
|
39
39
|
|
|
40
40
|
#XTIT: Currency
|
|
41
41
|
Currency=Moneda
|
package/_i18n/i18n_fr.properties
CHANGED
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
CreatedBy=Auteur de la cr\u00E9ation
|
|
30
30
|
|
|
31
31
|
#XTIT: Created On (Answer to: "When has a certain entity been created?")
|
|
32
|
-
CreatedAt=Date de cr\u00E9ation
|
|
32
|
+
CreatedAt=Date/Heure de cr\u00E9ation
|
|
33
33
|
|
|
34
34
|
#XTIT: Changed By (Answer to: "Which user has changed a certain entity?")
|
|
35
35
|
ChangedBy=Auteur de la modification
|
|
36
36
|
|
|
37
37
|
#XTIT: Changed On (Answer to: "When has a certain entity been changed?")
|
|
38
|
-
ChangedAt=Date de modification
|
|
38
|
+
ChangedAt=Date/Heure de modification
|
|
39
39
|
|
|
40
40
|
#XTIT: Currency
|
|
41
41
|
Currency=Devise
|
|
@@ -97,3 +97,9 @@ DRAFT_ACTIVE_DELETE_FORBIDDEN_DRAFT_EXISTS = Entity cannot be deleted because a
|
|
|
97
97
|
|
|
98
98
|
# singleton
|
|
99
99
|
SINGLETON_NOT_NULLABLE = The singleton entity is not nullable
|
|
100
|
+
|
|
101
|
+
# flows
|
|
102
|
+
#XMSG: Action "acceptTravel" requires "travelStatus" to be "Open".
|
|
103
|
+
INVALID_FLOW_TRANSITION_SINGLE=Action "{0}" requires "{1}" to be "{2}".
|
|
104
|
+
#XMSG: Action "cancelTravel" requires "travelStatus" to be one of the following values: Open,Accepted.
|
|
105
|
+
INVALID_FLOW_TRANSITION_MULTI=Action "{0}" requires "{1}" to be one of the following values: {2}.
|
package/app/index.js
CHANGED
|
@@ -18,7 +18,6 @@ module.exports = { get html(){
|
|
|
18
18
|
).join('\n') || '— none —'
|
|
19
19
|
)
|
|
20
20
|
.replace ('{{services}}', cds.service.providers
|
|
21
|
-
.filter(srv => !srv._is_dark)
|
|
22
21
|
.flatMap(srv => srv.endpoints.map(endpoint => ({srv, endpoint})))
|
|
23
22
|
.map (({srv, endpoint}) => `
|
|
24
23
|
<div id="${asHtmlId(srv.name)}-${endpoint.kind}">
|
package/bin/deploy.js
CHANGED
|
@@ -18,7 +18,7 @@ Promise.resolve(cds.plugins).then(async () => {
|
|
|
18
18
|
}
|
|
19
19
|
console.debug('Deploying with options:', db, o)
|
|
20
20
|
cds.db = await cds.connect.to(db)
|
|
21
|
-
return await cds.deploy('*',o).to(db)
|
|
21
|
+
return await cds.deploy('*',o).to(cds.db)
|
|
22
22
|
})
|
|
23
23
|
.catch (e => { console.error(e); process.exitCode = 1 })
|
|
24
24
|
.finally (() => cds.db?.disconnect?.())
|
package/bin/serve.js
CHANGED
|
@@ -171,14 +171,6 @@ async function serve (all=[], o={}) {
|
|
|
171
171
|
// Ensure loading plugins before calling cds.env!
|
|
172
172
|
await cds.plugins
|
|
173
173
|
|
|
174
|
-
const TRACE = cds.debug('trace')
|
|
175
|
-
TRACE?.time('total startup time'.padEnd(22))
|
|
176
|
-
if (TRACE) {
|
|
177
|
-
TRACE?.time('require express'.padEnd(22))
|
|
178
|
-
require('express')
|
|
179
|
-
TRACE?.timeEnd('require express'.padEnd(22))
|
|
180
|
-
}
|
|
181
|
-
|
|
182
174
|
// Load local server.js early in order to allow setting custom cds.log.Loggers
|
|
183
175
|
const cds_server = await _local_server_js() || cds.server
|
|
184
176
|
if (!o.silent) _prepare_logging ()
|
|
@@ -255,7 +247,6 @@ async function serve (all=[], o={}) {
|
|
|
255
247
|
process.on('message', msg => msg.close && _shutdown()) // by `cds watch` on Windows
|
|
256
248
|
}
|
|
257
249
|
|
|
258
|
-
TRACE?.timeEnd('total startup time'.padEnd(22))
|
|
259
250
|
return server
|
|
260
251
|
})
|
|
261
252
|
}
|
|
@@ -264,7 +255,7 @@ async function _local_server_js() {
|
|
|
264
255
|
const _local = file => isfile(file) || isfile (path.join(cds.env.folders.srv,file))
|
|
265
256
|
let server_js = process.env.CDS_TYPESCRIPT && _local('server.ts') || _local('server.js')
|
|
266
257
|
if (server_js) {
|
|
267
|
-
console.log ('[cds] -
|
|
258
|
+
console.log ('[cds] - bootstrapping from', { file: local(server_js) })
|
|
268
259
|
let fn = await cds.utils._import(server_js)
|
|
269
260
|
if (fn && fn.default) fn = fn.default // default ESM export
|
|
270
261
|
return typeof fn === 'function' ? fn : cds.server
|
|
@@ -290,24 +281,20 @@ function _prepare_logging () { // NOSONAR
|
|
|
290
281
|
|
|
291
282
|
// print information about each connected service
|
|
292
283
|
cds.on ('connect', ({name,kind,options:{use,credentials,impl}})=>{
|
|
293
|
-
LOG.info (`connect to ${name} > ${use||kind||impl}`,
|
|
284
|
+
LOG.info (`connect to ${name} > ${use||kind||impl||'(builtin)'}`, redacted(credentials) || '')
|
|
294
285
|
})
|
|
295
286
|
|
|
296
287
|
// print information about each provided service
|
|
297
288
|
cds.on ('serving', (srv) => {
|
|
298
|
-
const details = {}
|
|
299
|
-
if (srv.
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
|
|
303
|
-
else if (srv.endpoints.length > 1)
|
|
304
|
-
details.endpoints = srv.endpoints // full endpoint details if more than one
|
|
305
|
-
LOG.info (`${srv.mocked ? 'mocking' : 'serving'} ${srv.name}`, details)
|
|
289
|
+
const details = {}, loc = srv.definition?.$location, src = srv._source
|
|
290
|
+
if (srv.endpoints.length) details.at = srv.endpoints.map(ep => ep.path)
|
|
291
|
+
if (loc?.file) details.decl = local(loc.file)+':'+loc.line
|
|
292
|
+
if (src && !src.startsWith('@sap')) details.impl = local(src)
|
|
293
|
+
LOG.info (srv.mocked ? 'mocking' : 'serving', srv.name, details)
|
|
306
294
|
})
|
|
307
295
|
|
|
308
296
|
// print info when we are finally on air
|
|
309
297
|
cds.once ('listening', ({url})=>{
|
|
310
|
-
console.log()
|
|
311
298
|
LOG.info ('server listening on',{url})
|
|
312
299
|
LOG.info ('server', 'v'+cds.version, 'launched in', performance.now().toFixed(0),'ms')
|
|
313
300
|
if (process.stdin.isTTY) LOG.info (`[ terminate with ^C ]\n`)
|
package/lib/compile/cdsc.js
CHANGED
|
@@ -17,6 +17,9 @@ function _options4 (src, ...mappings) {
|
|
|
17
17
|
let v = dst[k]; if (v === undefined) continue
|
|
18
18
|
let m = map[k]; if (typeof m === 'function') m(dst,v); else dst[m] = v
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
dst.draftMessages ??= cds.env.fiori.draft_messages // REVISIT: Is this the appropriate way to forward config to compiler?
|
|
22
|
+
|
|
20
23
|
// Optionally add .messages array to avoid compiler writing messages to stderr
|
|
21
24
|
dst.messages = dst.messages || []
|
|
22
25
|
// Finally override with options from cds.env.cdsc
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// REVISIT: to be moved to cds-compiler later
|
|
2
|
+
|
|
3
|
+
const cds = require('../../..')
|
|
4
|
+
|
|
5
|
+
const FLOW_STATUS = '@flow.status'
|
|
6
|
+
const FROM = '@from'
|
|
7
|
+
const TO = '@to'
|
|
8
|
+
// backwards compat
|
|
9
|
+
const FLOW_FROM = '@flow.from'
|
|
10
|
+
const FLOW_TO = '@flow.to'
|
|
11
|
+
|
|
12
|
+
const getFrom = action => {
|
|
13
|
+
let from = action[FROM] ?? action[FLOW_FROM]
|
|
14
|
+
from = Array.isArray(from) ? from : [from]
|
|
15
|
+
return from.map(f => f['#'] ?? f['='] ?? f)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function addOperationAvailableToActions(actions, statusEnum, statusElementName) {
|
|
19
|
+
for (const action of Object.values(actions)) {
|
|
20
|
+
const fromList = getFrom(action)
|
|
21
|
+
const conditions = fromList.map(from => `$self.${statusElementName} = '${statusEnum[from].val ?? from}'`)
|
|
22
|
+
const condition = `(${conditions.join(' OR ')})`
|
|
23
|
+
const parsedXpr = cds.parse.expr(condition)
|
|
24
|
+
action['@Core.OperationAvailable'] ??= {
|
|
25
|
+
...parsedXpr,
|
|
26
|
+
['=']: condition
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function addSideEffectToActions(actions, statusElementName) {
|
|
32
|
+
for (const action of Object.values(actions)) {
|
|
33
|
+
const properties = []
|
|
34
|
+
if (statusElementName.endsWith('.code')) {
|
|
35
|
+
const baseName = statusElementName.slice(0, -5)
|
|
36
|
+
properties.push(`in/${statusElementName}`)
|
|
37
|
+
properties.push(`in/${baseName}/*`)
|
|
38
|
+
properties.push(`in/${baseName}_code`)
|
|
39
|
+
} else {
|
|
40
|
+
properties.push(`in/${statusElementName}`)
|
|
41
|
+
}
|
|
42
|
+
const sideEffect = '@Common.SideEffects.TargetProperties'
|
|
43
|
+
if (action[sideEffect]) {
|
|
44
|
+
action[sideEffect].push(...properties)
|
|
45
|
+
} else {
|
|
46
|
+
action[sideEffect] = properties
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveStatusEnum(csn, codeElem) {
|
|
52
|
+
if (codeElem.enum !== undefined) return codeElem.enum
|
|
53
|
+
if (codeElem.type) {
|
|
54
|
+
const typeDef = csn.definitions[codeElem.type]
|
|
55
|
+
return typeDef ? typeDef.enum : undefined
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function enhanceCSNwithFlowAnnotations4FE(csn) {
|
|
60
|
+
for (const definition of Object.values(csn.definitions)) {
|
|
61
|
+
if (definition.kind !== 'entity') continue
|
|
62
|
+
const entity = definition
|
|
63
|
+
|
|
64
|
+
if (!entity.elements || !entity.actions) continue
|
|
65
|
+
for (const [elemName, element] of Object.entries(entity.elements)) {
|
|
66
|
+
if (!element[FLOW_STATUS]) continue
|
|
67
|
+
|
|
68
|
+
const fromActions = []
|
|
69
|
+
const toActions = []
|
|
70
|
+
for (const action of Object.values(entity.actions)) {
|
|
71
|
+
if (action[FROM] || action[FLOW_FROM]) fromActions.push(action)
|
|
72
|
+
if (action[TO] || action[FLOW_TO]) toActions.push(action)
|
|
73
|
+
}
|
|
74
|
+
if (fromActions.length === 0 && toActions.length === 0) continue
|
|
75
|
+
if (element.enum) {
|
|
76
|
+
// Element is an enum directly
|
|
77
|
+
addSideEffectToActions(toActions, elemName)
|
|
78
|
+
addOperationAvailableToActions(fromActions, element.enum, elemName)
|
|
79
|
+
} else if (element.target) {
|
|
80
|
+
// Element is an association to a codelist
|
|
81
|
+
const targetDef = csn.definitions[element.target]
|
|
82
|
+
if (targetDef?.elements?.code) {
|
|
83
|
+
const codeElem = targetDef.elements.code
|
|
84
|
+
const statusEnum = resolveStatusEnum(csn, codeElem)
|
|
85
|
+
if (statusEnum) {
|
|
86
|
+
addSideEffectToActions(toActions, elemName + '.code')
|
|
87
|
+
addOperationAvailableToActions(fromActions, statusEnum, elemName + '.code')
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} else if (element['@odata.foreignKey4']) {
|
|
91
|
+
// when compiling to edmx, the foreign key is also annotated with @flow.status, but has no info about the target
|
|
92
|
+
continue
|
|
93
|
+
} else {
|
|
94
|
+
cds.error(
|
|
95
|
+
`Status element in entity ${entity.name} is not an enum and does not have a valid target with code enum.`
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { enhanceCSNwithFlowAnnotations4FE, getFrom }
|
|
@@ -9,6 +9,34 @@ function _compile_for_nodejs (csn, o) {
|
|
|
9
9
|
dsn = unfold_csn(dsn)
|
|
10
10
|
dsn = cds.linked(dsn)
|
|
11
11
|
cds.compile.for.lean_drafts(dsn, o)
|
|
12
|
+
|
|
13
|
+
// @Common.Text → @cds.search
|
|
14
|
+
text_to_search: for (const name in dsn.definitions) {
|
|
15
|
+
const def = dsn.definitions[name]
|
|
16
|
+
if (!def.associations) continue
|
|
17
|
+
|
|
18
|
+
// if any @cds.search.* === true, @Common.Text should be ignored
|
|
19
|
+
for (const key in def) if (key.startsWith('@cds.search') && def[key]) continue text_to_search
|
|
20
|
+
|
|
21
|
+
let searched = false
|
|
22
|
+
for (const name in def.associations) {
|
|
23
|
+
const assoc = def.associations[name]
|
|
24
|
+
if (assoc['@Common.Text']?.['='] && def[`@cds.search.${assoc['@Common.Text']['=']}`] !== false) {
|
|
25
|
+
def[`@cds.search.${assoc['@Common.Text']['=']}`] = true
|
|
26
|
+
if (!searched) {
|
|
27
|
+
// add all string elements to @cds.search, if not annotated with @cds.search = false
|
|
28
|
+
for (const el in def.elements) {
|
|
29
|
+
const search_el = def.elements[el]
|
|
30
|
+
if (search_el._type === 'cds.String' && def[`@cds.search.${search_el.name}`] !== false) {
|
|
31
|
+
def[`@cds.search.${search_el.name}`] = true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
searched = true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
12
40
|
Object.defineProperty(csn, '_4nodejs', { value: dsn })
|
|
13
41
|
Object.defineProperty(dsn, '_4nodejs', { value: dsn })
|
|
14
42
|
Object.assign (dsn.meta, csn.meta, dsn.meta) // merge meta data, as it may have been enhanced
|
package/lib/compile/to/edm.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
const cdsc = require ('../cdsc')
|
|
2
1
|
const cds = require ('../../index')
|
|
2
|
+
const cdsc = require ('../cdsc')
|
|
3
3
|
const TRACE = cds.debug('trace')
|
|
4
4
|
|
|
5
|
+
const { enhanceCSNwithFlowAnnotations4FE } = require("../for/flows")
|
|
6
|
+
|
|
5
7
|
if (cds.env.features.precompile_edms !== false) {
|
|
6
8
|
const _precompiled = new WeakMap
|
|
7
9
|
const to_edm = cdsc.to.edm
|
|
@@ -36,9 +38,14 @@ function cds_compile_to_edmx (csn,_o) {
|
|
|
36
38
|
csn = _4odata(csn,o)
|
|
37
39
|
TRACE?.time('cds.compile 2edmx'.padEnd(22))
|
|
38
40
|
try {
|
|
39
|
-
let result
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
let result
|
|
42
|
+
const next = () => {
|
|
43
|
+
if (!result) {
|
|
44
|
+
if (cds.env.features.compile_for_flows) enhanceCSNwithFlowAnnotations4FE(csn)
|
|
45
|
+
result = o.service === 'all' ? _many('.xml', cdsc.to.edmx.all(csn, o)) : cdsc.to.edmx(csn, o)
|
|
46
|
+
}
|
|
47
|
+
return result
|
|
48
|
+
}
|
|
42
49
|
cds.emit ('compile.to.edmx', csn, o, next)
|
|
43
50
|
return next() //> in case no handler called next
|
|
44
51
|
}
|
package/lib/core/classes.js
CHANGED
package/lib/core/linked-csn.js
CHANGED
|
@@ -82,6 +82,14 @@ class LinkedCSN {
|
|
|
82
82
|
return Object.values(defs).filter(_is(x))
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
collect (picker, collector) {
|
|
86
|
+
if (!collector) collector = picker, picker = () => true
|
|
87
|
+
let d, x, collected = []
|
|
88
|
+
for (d of this.definitions)
|
|
89
|
+
if (picker(d) && (x = collector(d)) !== undefined) collected.push (x)
|
|
90
|
+
return collected
|
|
91
|
+
}
|
|
92
|
+
|
|
85
93
|
foreach (x, v, defs=this.definitions) {
|
|
86
94
|
const y=_is(x), visit = typeof v !== 'function' ? x : (d,n,p) => y(d) && v(d,n,p)
|
|
87
95
|
for (let name in defs) visit (defs[name],name)
|
package/lib/dbs/cds-deploy.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cds = require('../index'), { local, path } = cds.utils
|
|
1
|
+
const cds = require('../index'), { local, path, colors: { DIMMED, RESET } } = cds.utils
|
|
2
2
|
const DEBUG = cds.debug('deploy')
|
|
3
3
|
const TRACE = cds.debug('trace')
|
|
4
4
|
|
|
@@ -10,7 +10,6 @@ const deploy = module.exports = function cds_deploy (model, options, csvs) {
|
|
|
10
10
|
/* eslint-disable no-console */
|
|
11
11
|
|
|
12
12
|
// prepare logging
|
|
13
|
-
const [ GREY, RESET ] = process.stdout.isTTY && !process.env.NO_COLOR || process.env.FORCE_COLOR ? ['\x1b[2m', '\x1b[0m' ] : ['','']
|
|
14
13
|
const LOG = !o.silent && !o.dry && cds.log('deploy')._info ? console.log : undefined
|
|
15
14
|
|
|
16
15
|
// prepare model
|
|
@@ -38,7 +37,7 @@ const deploy = module.exports = function cds_deploy (model, options, csvs) {
|
|
|
38
37
|
try {
|
|
39
38
|
await db.run (async tx => {
|
|
40
39
|
let any = await deploy.schema (tx, model, o)
|
|
41
|
-
if (any || csvs) await deploy.data (tx, model, o, csvs, file => LOG?.(
|
|
40
|
+
if (any || csvs) await deploy.data (tx, model, o, csvs, file => LOG?.(DIMMED, ' > init from', local(file), RESET))
|
|
42
41
|
})
|
|
43
42
|
LOG?.('/> successfully deployed to', descr, '\n')
|
|
44
43
|
} catch (e) {
|
|
@@ -92,7 +91,7 @@ deploy.schema = async function (db, csn = db.model, o) {
|
|
|
92
91
|
creas = createsAndAlters
|
|
93
92
|
drops = d
|
|
94
93
|
} else {
|
|
95
|
-
// cds deploy -- w/o auto schema
|
|
94
|
+
// cds deploy -- w/o auto schema evolution > drop-create db
|
|
96
95
|
creas = cds.compile.to.sql(csn,o) // NOTE: this used to call cds.linked(cds.minify) and thereby corrupted the passed in csn
|
|
97
96
|
}
|
|
98
97
|
|
|
@@ -155,12 +154,13 @@ deploy.schema = async function (db, csn = db.model, o) {
|
|
|
155
154
|
deploy.data = async function (db, csn = db.model, o, srces, log=()=>{}) {
|
|
156
155
|
|
|
157
156
|
const t = cds.context?.tenant; if (t && t === cds.requires.multitenancy?.t0) return
|
|
157
|
+
const data = await deploy.prepare (csn,srces); if (!data.length) return
|
|
158
|
+
// IMPORTANT: That ^^^^^^^^^^^^^^^^^^^ has to be called with the original csn, not the compile.for.nodejs one created below!
|
|
158
159
|
|
|
159
160
|
return db.run (async tx => {
|
|
160
161
|
TRACE?.time('cds.deploy data'.padEnd(22))
|
|
161
162
|
|
|
162
|
-
const m = tx.model = cds.compile.for.nodejs(csn) // NOTE: this used to create a redundant 4nodejs model for the same csn
|
|
163
|
-
const data = await deploy.prepare (m,srces)
|
|
163
|
+
const m = tx.model = cds.compile.for.nodejs(csn) // REVISIT: Why do we need that?!? // NOTE: this used to create a redundant 4nodejs model for the same csn
|
|
164
164
|
const query = _queries4 (db,m)
|
|
165
165
|
const INSERT_from = INSERT_from4 (db,m,o)
|
|
166
166
|
|
|
@@ -343,13 +343,13 @@ deploy.include_external_entities_in = function (csn) {
|
|
|
343
343
|
/** Exclude external entities from the given model */
|
|
344
344
|
deploy.exclude_external_entities_in = function (csn) {
|
|
345
345
|
// IMPORTANT to use cds.env.requires below, not cds.requires !!
|
|
346
|
-
for (let [each,{service=each,
|
|
347
|
-
if (!
|
|
346
|
+
for (let [each,{service=each,credentials}] of Object.entries (cds.env.requires)) {
|
|
347
|
+
if (!csn.definitions[service]?.['@cds.external']) continue
|
|
348
348
|
if (!credentials && csn._mocked) continue //> not for mocked unbound services
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
349
|
+
DEBUG?.('excluding external entities for', service, '...')
|
|
350
|
+
const prefix = service+'.'
|
|
351
|
+
for (let each in csn.definitions) if (each.startsWith(prefix)) _exclude (each)
|
|
352
|
+
}
|
|
353
353
|
return csn
|
|
354
354
|
|
|
355
355
|
function _exclude (each) {
|
package/lib/env/cds-env.js
CHANGED
|
@@ -509,7 +509,7 @@ function _readYaml (file) {
|
|
|
509
509
|
|
|
510
510
|
function _readEnv (file) {
|
|
511
511
|
if (isfile(file)) {
|
|
512
|
-
const ENV = _readEnv.parser
|
|
512
|
+
const ENV = _readEnv.parser ??= require('../compile/etc/properties')
|
|
513
513
|
return ENV.parse (fs.readFileSync(file,'utf-8'))
|
|
514
514
|
}
|
|
515
515
|
}
|
package/lib/env/cds-requires.js
CHANGED
|
@@ -20,15 +20,13 @@ exports = module.exports = {
|
|
|
20
20
|
* plus additional entries for all cds.required.<name>.service
|
|
21
21
|
* @returns {import('./cds-requires')}
|
|
22
22
|
*/
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
for (let [name,e] of Object.entries
|
|
26
|
-
if (e.service in
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
else dict[e.service] = { ...e, name }
|
|
23
|
+
_resolved() {
|
|
24
|
+
const requires = { __proto__: this }
|
|
25
|
+
for (let [name,e] of Object.entries(this)) if (e.service) {
|
|
26
|
+
if (!(e.service in requires) || e.override || e.service !== name) requires[e.service] = { ...e, name }
|
|
27
|
+
else throw new Error (`Config 'cds.requires.${name}.service = ${e.service}' conflicts with 'cds.requires.${e.service}'`)
|
|
30
28
|
}
|
|
31
|
-
return
|
|
29
|
+
return requires
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
|
|
@@ -92,26 +90,30 @@ const _services = {
|
|
|
92
90
|
|
|
93
91
|
"app-service": {
|
|
94
92
|
// this is the default implementation used for provided services
|
|
95
|
-
impl:
|
|
93
|
+
impl: `@sap/cds/srv/app-service.js`,
|
|
96
94
|
},
|
|
97
95
|
"rest": {
|
|
98
|
-
impl:
|
|
96
|
+
impl: `@sap/cds/srv/remote-service.js`,
|
|
97
|
+
external: true
|
|
98
|
+
},
|
|
99
|
+
"hcql": {
|
|
100
|
+
impl: `@sap/cds/srv/remote-service.js`,
|
|
99
101
|
external: true
|
|
100
102
|
},
|
|
101
103
|
"odata": {
|
|
102
|
-
impl:
|
|
104
|
+
impl: `@sap/cds/srv/remote-service.js`,
|
|
103
105
|
external: true
|
|
104
106
|
},
|
|
105
107
|
"odata-v2": { // REVISIT: we should introduce .version
|
|
106
|
-
impl:
|
|
108
|
+
impl: `@sap/cds/srv/remote-service.js`,
|
|
107
109
|
external: true
|
|
108
110
|
},
|
|
109
111
|
"odata-v4": { // REVISIT: we should introduce .version
|
|
110
|
-
impl:
|
|
112
|
+
impl: `@sap/cds/srv/remote-service.js`,
|
|
111
113
|
external: true
|
|
112
114
|
},
|
|
113
115
|
"graphql": { // REVISIT: we should introduce .version
|
|
114
|
-
impl:
|
|
116
|
+
impl: `@sap/cds/srv/remote-service.js`,
|
|
115
117
|
external: true
|
|
116
118
|
},
|
|
117
119
|
|
|
@@ -242,9 +244,11 @@ const _messaging = {
|
|
|
242
244
|
const _platform_services = {
|
|
243
245
|
|
|
244
246
|
ucl: {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
247
|
+
model: '@sap/cds/srv/ucl-service.cds',
|
|
248
|
+
// REVISIT: how to configure such that a cds.connect.to('ucl') returns the already initialized UCLService?
|
|
249
|
+
// service: 'UCLService',
|
|
250
|
+
// impl: `${_runtime}/ucl/Service.js`,
|
|
251
|
+
// REVISIT: remove default as only needed for push mode
|
|
248
252
|
vcap: { label: 'xsuaa' }
|
|
249
253
|
},
|
|
250
254
|
|
|
@@ -272,6 +276,3 @@ exports.kinds = {
|
|
|
272
276
|
..._messaging,
|
|
273
277
|
..._platform_services,
|
|
274
278
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
Object.defineProperty(exports,'_resolved',{value:exports._resolved,enumerable:false}) // hide it in outputs
|
package/lib/env/defaults.js
CHANGED
package/lib/index.js
CHANGED
|
@@ -72,7 +72,6 @@ const cds = exports = module.exports = global.cds = new class cds extends EventE
|
|
|
72
72
|
// Providing and Consuming Services
|
|
73
73
|
/** @type { Record<string,Service> } */ services = new class {
|
|
74
74
|
*[Symbol.iterator](){ for (let e in this) yield this[e] }
|
|
75
|
-
get _pending(){ return this.#pending ??= {} } #pending
|
|
76
75
|
}
|
|
77
76
|
get server() { return super.server = require('../server.js') }
|
|
78
77
|
get serve() { return super.serve = require('./srv/cds-serve.js') }
|
|
@@ -95,10 +94,10 @@ const cds = exports = module.exports = global.cds = new class cds extends EventE
|
|
|
95
94
|
get validate() { return super.validate = require('./req/validate.js') }
|
|
96
95
|
|
|
97
96
|
// Services, Protocols and Periphery
|
|
98
|
-
get ApplicationService() { return super.ApplicationService = require('../
|
|
99
|
-
get MessagingService() { return super.MessagingService = require('../
|
|
100
|
-
get DatabaseService() { return super.DatabaseService = require('
|
|
101
|
-
get RemoteService() { return super.RemoteService = require('../
|
|
97
|
+
get ApplicationService() { return super.ApplicationService = require('../srv/app-service.js') }
|
|
98
|
+
get MessagingService() { return super.MessagingService = require('../srv/msg-service.js') }
|
|
99
|
+
get DatabaseService() { return super.DatabaseService = require('../srv/db-service.js') }
|
|
100
|
+
get RemoteService() { return super.RemoteService = require('../srv/remote-service.js') }
|
|
102
101
|
|
|
103
102
|
// Helpers
|
|
104
103
|
get utils() { return super.utils = require('./utils/cds-utils.js') }
|
|
@@ -151,4 +150,4 @@ function D (old,use,fn) { return G (old, cds.utils.deprecated (fn,{kind:'Global
|
|
|
151
150
|
function G (p,v) { Object.defineProperty (global, p, { value:v, enumerable:1,configurable:1}); return v }
|
|
152
151
|
|
|
153
152
|
// Allow for `import cds from '@sap/cds'` without `esModuleInterop` in tsconfig.json
|
|
154
|
-
Object.defineProperties (
|
|
153
|
+
Object.defineProperties (exports, { default: {value:cds}, __esModule: {value:true} })
|