@sap/cds 9.2.1 → 9.3.1

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +85 -1
  2. package/_i18n/i18n_es.properties +3 -3
  3. package/_i18n/i18n_es_MX.properties +3 -3
  4. package/_i18n/i18n_fr.properties +2 -2
  5. package/_i18n/messages.properties +6 -0
  6. package/app/index.js +0 -1
  7. package/bin/deploy.js +1 -1
  8. package/bin/serve.js +7 -20
  9. package/lib/compile/cdsc.js +3 -0
  10. package/lib/compile/for/flows.js +102 -0
  11. package/lib/compile/for/nodejs.js +28 -0
  12. package/lib/compile/to/edm.js +11 -4
  13. package/lib/core/classes.js +1 -1
  14. package/lib/core/linked-csn.js +8 -0
  15. package/lib/dbs/cds-deploy.js +12 -12
  16. package/lib/env/cds-env.js +1 -1
  17. package/lib/env/cds-requires.js +21 -20
  18. package/lib/env/defaults.js +2 -1
  19. package/lib/index.js +5 -6
  20. package/lib/log/cds-log.js +6 -5
  21. package/lib/log/format/aspects/cf.js +2 -2
  22. package/lib/plugins.js +1 -1
  23. package/lib/ql/cds-ql.js +0 -3
  24. package/lib/req/request.js +3 -3
  25. package/lib/req/response.js +12 -7
  26. package/lib/srv/bindings.js +17 -17
  27. package/lib/srv/cds-connect.js +6 -9
  28. package/lib/srv/cds-serve.js +74 -137
  29. package/lib/srv/cds.Service.js +49 -0
  30. package/lib/srv/factory.js +4 -4
  31. package/lib/srv/middlewares/auth/ias-auth.js +33 -9
  32. package/lib/srv/middlewares/auth/index.js +3 -2
  33. package/lib/srv/middlewares/auth/jwt-auth.js +20 -6
  34. package/lib/srv/protocols/hcql.js +16 -1
  35. package/lib/srv/srv-dispatch.js +1 -1
  36. package/lib/utils/cds-utils.js +4 -8
  37. package/lib/utils/csv-reader.js +27 -7
  38. package/libx/_runtime/cds.js +0 -6
  39. package/libx/_runtime/common/Service.js +5 -0
  40. package/libx/_runtime/common/generic/crud.js +1 -1
  41. package/libx/_runtime/common/generic/flows.js +106 -0
  42. package/libx/_runtime/common/generic/paging.js +3 -3
  43. package/libx/_runtime/common/utils/differ.js +5 -15
  44. package/libx/_runtime/common/utils/resolveView.js +2 -2
  45. package/libx/_runtime/common/utils/rewriteAsterisks.js +10 -4
  46. package/libx/_runtime/fiori/lean-draft.js +76 -40
  47. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  48. package/libx/_runtime/messaging/service.js +7 -0
  49. package/libx/_runtime/remote/Service.js +68 -62
  50. package/libx/_runtime/remote/utils/client.js +29 -216
  51. package/libx/_runtime/remote/utils/query.js +197 -0
  52. package/libx/_runtime/ucl/Service.js +180 -112
  53. package/libx/_runtime/ucl/queries.js +61 -0
  54. package/libx/odata/ODataAdapter.js +1 -4
  55. package/libx/odata/index.js +2 -10
  56. package/libx/odata/middleware/error.js +8 -1
  57. package/libx/odata/middleware/stream.js +1 -1
  58. package/libx/odata/middleware/update.js +12 -2
  59. package/libx/odata/parse/afterburner.js +113 -20
  60. package/libx/odata/parse/cqn2odata.js +1 -3
  61. package/libx/rest/middleware/parse.js +9 -2
  62. package/package.json +2 -2
  63. package/server.js +2 -0
  64. package/srv/app-service.js +1 -0
  65. package/srv/db-service.js +1 -0
  66. package/srv/msg-service.js +1 -0
  67. package/srv/remote-service.js +1 -0
  68. package/srv/ucl-service.cds +32 -0
  69. package/srv/ucl-service.js +1 -0
  70. package/lib/ql/resolve.js +0 -45
  71. package/libx/common/assert/type-strict.js +0 -109
  72. package/libx/common/assert/utils.js +0 -60
package/CHANGELOG.md CHANGED
@@ -4,6 +4,84 @@
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.1 - 2025-09-03
8
+
9
+ ### Fixed
10
+
11
+ - In messaging services, propagated headers (e.g. `x-correlation-id`) will not be automatically propagated for `format: 'cloudevents'`
12
+ - Avoid deprecation warning for `cds.context.user.tokenInfo`
13
+ - Consider `@Capabilities.ExpandRestrictions.NonExpandableProperties` annotation and ignore fields referenced by the annotation, when rewriting asterisk expand into columns
14
+
15
+ ## Version 9.3.0 - 2025-08-29
16
+
17
+ ### Added
18
+
19
+ - New method `collect()` has been added to `LinkedCSN`, which can be used like that:
20
+ ```js
21
+ const federated_entities = cds.linked(csn).collect (d => d.is_entity && d['@federated'])
22
+ ```
23
+ - Remote services can now be configured without `kind`, for example:
24
+ ```json
25
+ { "cds": { "requires": { "SomeService": true }}}
26
+ ```
27
+ - Automatic protocol selection is applied if a required service is configured as above
28
+ and the remote services is served via multiple protocols. For example, if the above
29
+ service would be declared like that, the best protocol would be chosen automatically
30
+ (`hcql` in this case):
31
+ ```cds
32
+ @hcql @rest @odata service SomeService {...}
33
+ ```
34
+ - Method `cds.connect.to()` now allows to connect to remote services with just an http url.
35
+ For example use that from `cds repl` like that:
36
+ ```js
37
+ srv = await cds.connect.to ('http://localhost:4004/hcql/books')
38
+ await srv.read `ID, title, author.name from Books`
39
+ ```
40
+ - Property `cds.User.authInfo` as generic container for authentication-related information
41
+ + For `@sap/xssec`-based authentication strategies, `cds.context.user.authInfo` is an instance of `@sap/xssec`'s `SecurityContext`
42
+ - Support for state transition flows (`@flow`):
43
+ + Generic handlers for validating entry (`@from`) and exit (`@to`) states
44
+ + 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`
45
+ - Experimental support for consuming remote HCQL services (`cds.requires.<remote>.kind = 'hcql'`)
46
+ - Infrastructure for implementing the tenant mapping notification of Unified Customer Landscape's (UCL) Service Provider Integration Interface (SPII) API
47
+ + Bootstrap the `UCLService` via `cds.requires.ucl = true` and implement the assign and unassign operations like so:
48
+ ```js
49
+ // custom server.js
50
+ cds.on('served', async () => {
51
+ const ucl = await cds.connect.to('ucl')
52
+ ucl.on('assign', async function(req) { ... })
53
+ ucl.on('unassign', async function(req) { ... })
54
+ })
55
+ ```
56
+ + Currently, only the synchronous interaction pattern is supported!
57
+ - The targets of `@Common.Text` are added to the default search targets
58
+ - Patch Level Validations are enabled by default. Opt-out with `cds.fiori.draft_messages=false`
59
+ - Enable custom aggregations for currency codes and units of measure
60
+
61
+ ### Changed
62
+
63
+ - `UCLService` only pushes the application template to UCL if `cds.requires.ucl.applicationTemplate` is present
64
+ - `cds.User.tokenInfo` is deprecated. Use `cds.context.user.authInfo.token` instead.
65
+ - Undocumented compat `cds.context.http.req.authInfo` is deprecated. Use `cds.context.user.authInfo` instead.
66
+ - Delete all persisted draft messages, when the first request targeting a draft child without containment is handled.
67
+ - cds build now trims leading or trailing whitespace characters for all values in CSV files deployed to SAP HANA.
68
+
69
+ ### Fixed
70
+
71
+ - Errors when reading complementary drafts
72
+ - Apply configurations in case `cds.env` was loaded before `cds.log` is initialized.
73
+ - `req.diff` resolves correctly deleted nested composition by deep update
74
+ - `cds-deploy` did not terminate correctly even though deployment was successful
75
+ - Requests to an unimplemented unbound action/ function are rejected
76
+ - Custom app-service implementations configured through `cds.requires.app-service.impl` is now correctly resolved (again)
77
+ - Validation of UUID format for navigation by key
78
+
79
+ ### Removed
80
+
81
+ - Internal property `cds.services._pending` was removed => use `cds.services` instead.
82
+ - Internal property `srv._is_dark` was removed => use `!srv.endpoints?.length` instead.
83
+ - Internal method `cds.env.requires._resolved` was removed => use `cds.requires` instead.
84
+
7
85
  ## Version 9.2.1 - 2025-08-15
8
86
 
9
87
  ### Fixed
@@ -41,7 +119,7 @@
41
119
  - Runtime error in transaction handling in messaging services when used with outbox
42
120
  - Always use `cds.context` middleware for `enterprise-messaging` endpoints
43
121
  - 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`.
122
+ - Support for logging of correct error locations with `cds watch` and `cds run`.
45
123
  - Double-unescaping of values in double quotes during OData URL parsing
46
124
  - Throw explicit error if the result of a media data query is not an instance of `Readable`, rather than responding with `No Content`
47
125
  - When loading `.csv` files quoted strings containing the separator (comma or semicolon) where erroneously
@@ -204,6 +282,12 @@
204
282
  - Deprecated stripping of unnecessary topic prefix `topic:` in messaging
205
283
  - Deprecated messaging `Outbox` class. Please use config or `cds.outboxed(srv)` to outbox your service.
206
284
 
285
+ ## Version 8.9.6 - 2025-07-29
286
+
287
+ ### Fixed
288
+
289
+ - Batch insert using `INSERT.entries()` on draft enabled entities
290
+
207
291
  ## Version 8.9.5 - 2025-07-25
208
292
 
209
293
  ### Fixed
@@ -26,16 +26,16 @@
26
26
  #----------------------------------------------------------------------------------------------------------------------
27
27
 
28
28
  #XTIT: Created By (Answer to: "Which user has created a certain entity?")
29
- CreatedBy=Creado por
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=Modificado por
35
+ ChangedBy=Autor de modificaci\u00F3n
36
36
 
37
37
  #XTIT: Changed On (Answer to: "When has a certain entity been changed?")
38
- ChangedAt=Modificado el
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=Creado por
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=Modificado por
35
+ ChangedBy=Autor de modificaci\u00F3n
36
36
 
37
37
  #XTIT: Changed On (Answer to: "When has a certain entity been changed?")
38
- ChangedAt=Modificado el
38
+ ChangedAt=Fecha de modificaci\u00F3n
39
39
 
40
40
  #XTIT: Currency
41
41
  Currency=Moneda
@@ -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] - loading server from', { file: local(server_js) })
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}`, credentials ? redacted(credentials) : '')
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._source && !srv._source.startsWith('@sap'))
300
- details.impl = local(srv._source)
301
- if (srv.endpoints.length === 1)
302
- details.path = srv.endpoints[0].path // for brevity, omit 'kind' if there is only one protocol
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`)
@@ -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
@@ -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, next = ()=> result ??= o.service === 'all' ? _many ('.xml',
40
- cdsc.to.edmx.all (csn,o)
41
- ) : cdsc.to.edmx (csn,o)
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
  }
@@ -190,7 +190,7 @@ class LinkedDefinitions {
190
190
  }
191
191
 
192
192
 
193
- module.exports = {
193
+ exports = module.exports = {
194
194
 
195
195
  LinkedDefinitions,
196
196
 
@@ -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)
@@ -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?.(GREY, ' > init from', local(file), RESET))
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 evoution > drop-create db
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,model,credentials}] of Object.entries (cds.env.requires)) {
347
- if (!model) continue //> not for internal services like cds.requires.odata
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
- DEBUG?.('excluding external entities for', service, '...')
350
- const prefix = service+'.'
351
- for (let each in csn.definitions) if (each.startsWith(prefix)) _exclude (each)
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) {
@@ -509,7 +509,7 @@ function _readYaml (file) {
509
509
 
510
510
  function _readEnv (file) {
511
511
  if (isfile(file)) {
512
- const ENV = _readEnv.parser = require('../compile/etc/properties')
512
+ const ENV = _readEnv.parser ??= require('../compile/etc/properties')
513
513
  return ENV.parse (fs.readFileSync(file,'utf-8'))
514
514
  }
515
515
  }
@@ -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
- _resolved() {
24
- const dict = Object.create (this)
25
- for (let [name,e] of Object.entries (this)) if (e.service) {
26
- if (e.service in dict && !e.override && e.service !== name) {
27
- throw new Error (`Datasource name '${e.service}' conflicts with service definition, referred to in 'cds.requires.${name}'`)
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 dict
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: `${_runtime}/common/Service.js`
93
+ impl: `@sap/cds/srv/app-service.js`,
96
94
  },
97
95
  "rest": {
98
- impl: `${_runtime}/remote/Service.js`,
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: `${_runtime}/remote/Service.js`,
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: `${_runtime}/remote/Service.js`,
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: `${_runtime}/remote/Service.js`,
112
+ impl: `@sap/cds/srv/remote-service.js`,
111
113
  external: true
112
114
  },
113
115
  "graphql": { // REVISIT: we should introduce .version
114
- impl: `${_runtime}/remote/Service.js`,
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
- impl: `${_runtime}/ucl/Service.js`,
246
- host: 'compass-gateway-sap-mtls.mps.kyma.cloud.sap',
247
- path: '/director/graphql',
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
@@ -56,7 +56,8 @@ module.exports = {
56
56
  lean_draft: true,
57
57
  wrap_multiple_errors: true,
58
58
  draft_lock_timeout: true,
59
- draft_deletion_timeout: true
59
+ draft_deletion_timeout: true,
60
+ draft_messages: true
60
61
  },
61
62
 
62
63
  ql: {