@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.
Files changed (70) hide show
  1. package/CHANGELOG.md +77 -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 +29 -9
  32. package/lib/srv/middlewares/auth/index.js +3 -2
  33. package/lib/srv/middlewares/auth/jwt-auth.js +19 -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/fiori/lean-draft.js +76 -40
  46. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  47. package/libx/_runtime/remote/Service.js +68 -62
  48. package/libx/_runtime/remote/utils/client.js +29 -216
  49. package/libx/_runtime/remote/utils/query.js +197 -0
  50. package/libx/_runtime/ucl/Service.js +180 -112
  51. package/libx/_runtime/ucl/queries.js +61 -0
  52. package/libx/odata/ODataAdapter.js +1 -4
  53. package/libx/odata/index.js +2 -10
  54. package/libx/odata/middleware/error.js +8 -1
  55. package/libx/odata/middleware/stream.js +1 -1
  56. package/libx/odata/middleware/update.js +12 -2
  57. package/libx/odata/parse/afterburner.js +113 -20
  58. package/libx/odata/parse/cqn2odata.js +1 -3
  59. package/libx/rest/middleware/parse.js +9 -2
  60. package/package.json +2 -2
  61. package/server.js +2 -0
  62. package/srv/app-service.js +1 -0
  63. package/srv/db-service.js +1 -0
  64. package/srv/msg-service.js +1 -0
  65. package/srv/remote-service.js +1 -0
  66. package/srv/ucl-service.cds +32 -0
  67. package/srv/ucl-service.js +1 -0
  68. package/lib/ql/resolve.js +0 -45
  69. package/libx/common/assert/type-strict.js +0 -109
  70. package/libx/common/assert/utils.js +0 -60
@@ -162,9 +162,7 @@ const { ERROR, WARN, INFO, DEBUG, TRACE } = exports.levels = {
162
162
  SILENT:0, ERROR:1, WARN:2, INFO:3, DEBUG:4, TRACE:5, SILLY:5, VERBOSE:5
163
163
  }
164
164
 
165
-
166
- // If cds.env is not yet loaded, update the loggers when it is
167
- if (conf === defaults) cds.once ('env', ()=>{
165
+ function applyLogConfig() {
168
166
  let { loggers, format } = exports, fmt = format
169
167
  let { plain } = log.formatters
170
168
  conf = cds.env.log
@@ -191,10 +189,13 @@ if (conf === defaults) cds.once ('env', ()=>{
191
189
  if (fmt !== format) {
192
190
  for (let each in loggers) loggers[each] .setFormat (fmt)
193
191
  }
194
-
192
+
195
193
  _init()
196
- }); else _init()
194
+ }
197
195
 
196
+ // If cds.env is not yet loaded, update the loggers when it is
197
+ if (conf === defaults) cds.once ('env', applyLogConfig);
198
+ else applyLogConfig() // otherwise apply the current config
198
199
 
199
200
  function _init() {
200
201
  if (conf.Logger) {
@@ -28,8 +28,8 @@ function cf_aspect(module, level, args, toLog) {
28
28
  // add static fields from environment
29
29
  Object.assign(toLog, this._CF_FIELDS)
30
30
 
31
- // add subdomain, if available (use cds.context._ instead of cds.context.http because of messaging)
32
- const tenant_subdomain = cds.context?._?.req?.authInfo?.getSubdomain?.()
31
+ // add subdomain, if available
32
+ const tenant_subdomain = cds.context?.user?.authInfo?.getSubdomain?.()
33
33
  if (tenant_subdomain) toLog.tenant_subdomain = tenant_subdomain
34
34
  }
35
35
 
package/lib/plugins.js CHANGED
@@ -11,7 +11,7 @@ const prio_plugins = {
11
11
  */
12
12
  exports.fetch = function (DEV = process.env.NODE_ENV !== 'production') {
13
13
  DEBUG?.time ('[cds.plugins] - fetched plugins in')
14
- const plugins = {}
14
+ const plugins = JSON.parse(process.env.CDS_PLUGINS||'{}')
15
15
  fetch_plugins_in (cds.home, false)
16
16
  fetch_plugins_in (cds.root, DEV)
17
17
  function fetch_plugins_in (root, dev) {
package/lib/ql/cds-ql.js CHANGED
@@ -35,9 +35,6 @@ exports.DELETE = require('./DELETE')
35
35
  exports.CREATE = require('./CREATE')
36
36
  exports.DROP = require('./DROP')
37
37
 
38
- exports.resolve = require('./resolve');
39
-
40
-
41
38
  exports.predicate = require('./cds.ql-predicates')
42
39
  exports.columns = require('./cds.ql-projections')
43
40
  /** @import cqn from './cqn' */
@@ -1,5 +1,5 @@
1
1
  const cds = require('../index')
2
- const { Responses, Errors } = require('./response')
2
+ const { Responses, Errors, prepareError } = require('./response')
3
3
 
4
4
  /**
5
5
  * Class Request represents requests received via synchronous protocols.
@@ -104,13 +104,13 @@ class Request extends require('./event') {
104
104
  warn (...args) { return this._messages.add (3, ...args) }
105
105
  error (...args) { return this._errors.add (null, ...args) }
106
106
  reject (...args) {
107
- if (args.length === 0 && this.errors) {
107
+ if (args.length === 0 && this.errors?.length) {
108
108
  if (this.errors.length === 1) throw this.errors[0]
109
109
  const err = new cds.error ('MULTIPLE_ERRORS', { details: this.errors })
110
110
  delete err.stack
111
111
  throw err
112
112
  }
113
- let e = this._errors.add(4, ...args)
113
+ let e = prepareError(4, ...args)
114
114
  if (!('stack' in e)) Error.captureStackTrace (e = Object.assign(new Error,e), this.reject)
115
115
  if (!('message' in e)) e.message = String (e.code || e.status)
116
116
  throw e
@@ -1,12 +1,8 @@
1
1
  const cds = require ('../index')
2
2
  const placeholders = [...'x'.repeat(9)].map((x,i) => `{${i+1}}`)
3
3
 
4
- /**
5
- * Messages Collector, used for `req.errors` and `req.messages`
6
- */
7
- class Responses extends Array {
8
- add (severity, code, message, target, args) {
9
- let e // be filled in below...
4
+ function prepareError(severity, code, message, target, args) {
5
+ let e // be filled in below...
10
6
  if (code?.raw) {
11
7
  if (typeof message === 'object') {
12
8
  target = Object.keys(message)[0]
@@ -31,6 +27,15 @@ class Responses extends Array {
31
27
  }
32
28
  }
33
29
  if (severity) e.numericSeverity ??= severity
30
+ return e
31
+ }
32
+
33
+ /**
34
+ * Messages Collector, used for `req.errors` and `req.messages`
35
+ */
36
+ class Responses extends Array {
37
+ add (severity, code, message, target, args) {
38
+ const e = prepareError(severity, code, message, target, args)
34
39
  this.push(e)
35
40
  return e
36
41
  }
@@ -44,4 +49,4 @@ class Errors extends Responses {
44
49
  }
45
50
  }
46
51
 
47
- module.exports = { Responses, Errors }
52
+ module.exports = { Responses, Errors, prepareError }
@@ -20,20 +20,28 @@ class Bindings {
20
20
  bind (service) {
21
21
  let required = cds.requires [service]
22
22
  let binding = this.provides [required?.service || service]
23
- if (binding) {
23
+ if (binding?.endpoints) {
24
+ const server = this.servers [binding.server]
25
+ const kind = [ required.kind, 'hcql', 'rest', 'odata' ].find (k => k in binding.endpoints)
26
+ const path = binding.endpoints [kind]
24
27
  // in case of cds.requires.Foo = { ... }
25
28
  if (typeof required === 'object') required.credentials = {
26
29
  ...required.credentials,
27
- ...binding.credentials
30
+ ...binding.credentials,
31
+ url: server.url + path
28
32
  }
29
33
  // in case of cds.requires.Foo = true
30
- else required = cds.requires[service] = {
34
+ else required = cds.requires[service] = cds.env.requires[service] = {
31
35
  ...cds.requires.kinds [binding.kind],
32
- ...binding
36
+ credentials: {
37
+ ...binding.credentials,
38
+ url: server.url + path
39
+ }
33
40
  }
41
+ required.kind = kind
34
42
  // REVISIT: temporary fix to inherit kind as well for mocked odata services
35
43
  // otherwise mocking with two services does not work for kind:odata-v2
36
- if (required.kind === 'odata-v2' || required.kind === 'odata-v4') required.kind = 'odata'
44
+ if (kind === 'odata-v2' || kind === 'odata-v4') required.kind = 'odata'
37
45
  }
38
46
  return required
39
47
  }
@@ -79,20 +87,12 @@ class Bindings {
79
87
  url
80
88
  }
81
89
  // register our services
82
- for (let each of services) {
90
+ for (let srv of services) {
83
91
  // if (each.name in cds.env.requires) continue
84
- const options = each.options || {}
85
- provides[each.name] = {
86
- kind: options.to || each.endpoints[0]?.kind || 'odata',
87
- credentials: {
88
- ...options.credentials,
89
- url: url + each.path
90
- },
91
- server: pid
92
+ provides[srv.name] = {
93
+ endpoints: Object.fromEntries (srv.endpoints.map (ep => [ ep.kind, ep.path ])),
94
+ server: pid,
92
95
  }
93
- // if (each.endpoints.length > 1) provides[each.name].other = each.endpoints.slice(1).map(
94
- // ep => ({ kind: ep.kind, url: url + ep.path })
95
- // )
96
96
  }
97
97
  process.on ('exit', ()=> this.purge())
98
98
  cds.on ('shutdown', ()=> this.purge())
@@ -1,5 +1,4 @@
1
1
  const cds = require('..'), LOG = cds.log('cds.connect')
2
- const _pending = cds.services._pending ??= {} // used below to chain parallel connect.to(<same>)
3
2
  const TRACE = cds.debug('trace')
4
3
 
5
4
  /**
@@ -27,10 +26,7 @@ connect.to = (datasource, options) => {
27
26
  else if (datasource) {
28
27
  if (datasource._is_service_class) [ Service, datasource ] = [ datasource, datasource.name ] // .to(ServiceClass)
29
28
  else if (datasource.name) datasource = datasource.name // .to({ name: 'Service' }) from cds-typer
30
- if (!options) { //> specifying ad-hoc options disallows caching
31
- if (datasource in cds.services) return Promise.resolve (cds.services[datasource])
32
- if (datasource in _pending) return _pending[datasource]
33
- }
29
+ if (!options && datasource in cds.services) return Promise.resolve (cds.services[datasource])
34
30
  }
35
31
  const promise = (async()=>{
36
32
  TRACE?.time(`cds.connect.to ${datasource}`.padEnd(22).slice(0,22))
@@ -45,24 +41,25 @@ connect.to = (datasource, options) => {
45
41
  // construct new service instance
46
42
  let srv = await new Service (datasource,m,o); await (Service._is_service_class ? srv.init?.() : Service.init?.(srv))
47
43
  if (!srv.isDatabaseService && _is_queued(o)) srv = cds.queued(srv)
48
- if (datasource && !options) {
44
+ if (!options && datasource) {
49
45
  if (datasource === 'db') cds.db = srv
50
46
  cds.services[datasource] = srv
51
- delete _pending[datasource]
52
47
  }
53
48
  if (!o.silent) cds.emit ('connect',srv)
54
49
  TRACE?.timeEnd(`cds.connect.to ${datasource}`.padEnd(22).slice(0,22))
55
50
  return srv
56
51
  })()
57
52
  // queue parallel requests to a single promise, to avoid creating multiple services
58
- if (datasource && !options) _pending[datasource] = promise
53
+ if (!options && datasource) cds.services[datasource] = promise
59
54
  return promise
60
55
  }
61
56
 
62
57
  function options4 (name, _o) {
63
- const [, kind=_o?.kind, url ] = /^(\w+):(.*)/.exec(name) || []
58
+ if (name?.startsWith('http:')) [_o,name] = [{ url:name }]
59
+ const [, kind=_o?.kind, url=_o?.url ] = /^(\w+):(.*)/.exec(name) || []
64
60
  const conf = cds.service.bindings.at(name) || cds.requires[name] || cds.requires[kind] || cds.requires.kinds[name] || cds.requires.kinds[kind]
65
61
  const o = { kind, ...conf, ..._o }
62
+ if (!o.kind) o.kind = (url||conf?.credentials?.url)?.match (/\/(hcql|rest|odata)\//)?.[1]
66
63
  if (!o.kind && !o.impl && !o.silent) throw cds.error(
67
64
  conf ? `Configuration for 'cds.requires.${name}' lacks mandatory property 'kind' or 'impl'` :
68
65
  name ? `Didn't find a configuration for 'cds.requires.${name}' in ${cds.root}` :
@@ -1,147 +1,84 @@
1
1
  const cds = require ('..')
2
2
  const Service = cds.service.factory
3
- const _pending = cds.services._pending ??= {}
4
- const _ready = Symbol()
5
- const TRACE = cds.debug('trace')
6
-
7
-
8
- /** @param som - a service name or a model (name or csn) */
9
- module.exports = function cds_serve (som, _options) { // NOSONAR
10
-
11
- TRACE?.time(`cds.serve ${som}`.padEnd(22).slice(0,22))
12
-
13
- if (som && typeof som === 'object' && !is_csn(som) && !is_files(som)) {
14
- [som,_options] = [undefined,
15
- som._is_service_class ? { service:som, from:'*' } :
16
- som
17
- ]
18
- }
19
- else if (Array.isArray(som) && som.length === 1) som = som[0]
20
- const o = {..._options} // we must not modify inbound data
21
-
22
- // Ensure options are filled in canonically based on defaults
23
- const options = Promise.resolve(o).then (o => { // noformat
24
- if (o.service) { o.from ||( o.from = som); return o }
25
- if (o.from) { o.service ||( o.service = som); return o }
26
- if (som === 'all') { o.service ='all'; o.from = '*' ; return o }
27
- if (is_csn(som)) { o.service ='all'; o.from = som ; return o }
28
- if (is_files(som)) { o.service ='all'; o.from = som ; return o }
29
- if (is_class(som)) { o.service = som; o.from = '?' ; return o }
30
- else { o.service = som; o.from = '*' ; return o }
31
- })
32
-
33
- // Load/resolve the model asynchronously...
34
- const loaded = options.then (async ({from}=o) => {
35
- if (!from || from === 'all' || from === '*') from = cds.model || '*'
36
- if (from.definitions) return from
37
- if (from === '?') try { return cds.model || await cds.load('*',o) } catch { return }
38
- return cds.load(from, {...o, silent:true })
39
- })
40
-
41
- // Pass 1: Construct service provider instances...
42
- const all=[], provided = loaded.then (async csn => { // NOSONAR
43
-
44
- // Shortcut for directly passed service classes
45
- if (o.service?._is_service_class) {
46
- const Service = o.service, d = { name: o.service.name }
47
- const srv = await _new (Service, d,csn,o)
48
- return all.push (srv)
49
- }
50
-
51
- // Get relevant service definitions from model...
52
- let {services} = csn = cds.compile.for.nodejs (csn)
53
- const required = cds.requires
54
- if (o.service && o.service !== 'all') {
55
- // skip services not chosen by o.service, if specified
56
- const specified = o.service.split(/\s*,\s*/).map (s => required[s] && required[s].service || s )
57
- // matching exact or unqualified name
58
- services = services.filter (s => specified.some (n => s.name === n || s.name.endsWith('.'+n)))
59
- if (!services.length) throw cds.error (`No such service: '${o.service}'`)
60
- }
61
- services = services.filter (d => !(
62
- // skip all services marked to be ignored
63
- d['@cds.ignore'] || d['@cds.serve.ignore'] ||
64
- // skip external services, unless asked to mock them and unbound
65
- (d['@cds.external'] || required[d.name]?.external) && (!o.mocked || required[d.name]?.credentials)
66
- ))
67
- if (services.length > 1 && o.at) {
68
- throw cds.error `You cannot specify 'path' for multiple services`
69
- }
70
-
71
- // Construct service instances and register them to cds.services
72
- all.push (... await Promise.all (services.map (d => _new (Service,d,csn,o))))
73
- })
74
-
75
- // Pass 2: Finalize service bootstrapping by calling their impl functions.
76
- // Note: doing that in a second pass guarantees all own services are in
77
- // cds.services, so they'll be found when they cds.connect to each others.
78
- let ready = provided.then (()=> Promise.all (all.map (async srv => {
79
- cds.services[srv.name] = await Service.init (srv)
3
+ const {serve} = cds.service.protocols
4
+ const {init} = Service
5
+
6
+
7
+ /** Fluent API */
8
+ module.exports = (services, options, o = {...options}) => ({
9
+ from (model) { o.from = model; return this },
10
+ with (impl) { o.with = impl; return this },
11
+ to (prot) { o.to = prot; return this },
12
+ at (path) { o.at = path; return this },
13
+ in (app) { o.app = app; return this },
14
+ then: (r,e) => _cds_serve (services,o) .then (r,e)
15
+ })
16
+
17
+
18
+ /**
19
+ * @returns { Promise<Record<string,Service>> } single or multiple services, like that:
20
+ * @example let { CatalogService } = await cds.serve(...) // single or many
21
+ * let CatalogService = await cds.serve(...) // single only
22
+ * @import Service from './cds.Service'
23
+ */
24
+ async function _cds_serve (services='all', o) {
25
+ const TRACE = cds.log('trace'), t0 = TRACE._debug && performance.now()
26
+
27
+ if (services.service) { Object.assign(o,services); services = o.service }
28
+ else if (o.service) { o.from ??= services; services = o.service }
29
+ else if (is_files(services)) { o.from ??= services; services = 'all' }
30
+ else if (is_csn(services)) { o.from ??= services; services = 'all' }
31
+
32
+ const srvs = await _construct (services,o)
33
+ if (o.app) for (let srv of srvs) if (!_ignore(srv.definition)) {
34
+ serve (srv, o.app)
80
35
  cds.service.providers.push (srv)
81
- srv[_ready]?.(srv)
82
- return srv
83
- })))
84
-
85
-
86
- // Return fluent API to fill in remaining options...
87
- return {
88
-
89
- from (model) { o.from = model; return this },
90
- with (impl) { o.with = impl; return this },
91
- at (path) { o.at = path; return this },
92
- to (protocol) { o.to = protocol; return this },
93
-
94
- /** Fluent method to serve constructed providers to express app */
95
- in (app) {
96
- const { serve } = cds.service.protocols
97
- ready = ready.then (()=> all.forEach (each => {
98
- const d = each.definition || {}
99
- if (d['@protocol'] === 'none' || d['@cds.api.ignore']) return each._is_dark = true
100
- else serve (each, /*to:*/ app)
101
- if (!o.silent) cds.emit ('serving',each)
102
- }))
103
- return this
104
- },
105
-
106
- /**
107
- * Finally resolve to a single service or a map of many,
108
- * which can be used like that: @example
109
- * let { CatalogService } = await cds.serve(...) // single or many
110
- * let CatalogService = await cds.serve(...) // single only
111
- */
112
- then: (_resolve, _error) => ready.then ((s)=>{
113
- TRACE?.timeEnd(`cds.serve ${som}`.padEnd(22).slice(0,22))
114
- if (all.length === 0) return _resolve()
115
- if (all.length === 1) return _resolve(Object.defineProperty(s=all[0],s.name,{value:s}))
116
- else return _resolve (all.reduce ((r,s)=>{ r[s.name]=s; return r },{}))
117
- }, _error),
118
-
119
- catch: (e) => ready.catch(e)
36
+ o.silent || cds.emit ('serving', srv) // NOTE: only for protocol-served ones (compat behaviour)
120
37
  }
38
+
39
+ if (t0) TRACE.debug ('cds.served in'.padEnd(21),':', (performance.now()-t0).toFixed(), 'ms')
40
+ if (srvs.length === 1) return Object.defineProperty (o=srvs[0], o.name, {value:o})
41
+ return srvs.reduce ((many,s)=>{ many[s.name] = s; return many },{})
121
42
  }
122
43
 
123
44
 
124
- async function _new (Service, d,m,o) {
125
- const srv = await new Service (d.name,m,o)
126
- const required = cds.requires[d.name]
127
- if (required) {
128
- // Object.assign (srv.options, required)
129
- if (required.name) srv.name = required.name
130
- if (required.external && o.mocked) srv.mocked = true
131
- }
132
- _pending[srv.name] = new Promise (r => srv[_ready]=r).finally(()=>{
133
- delete _pending[srv.name]
134
- delete srv[_ready]
135
- if (srv.mocked) {
136
- let service = cds.env.requires[srv.name]?.service
137
- if (service && !cds.services[service]) Object.defineProperty (cds.services, service, {value:srv})
138
- }
139
- if (!o.silent) cds.emit (`serving:${srv.name}`, srv)
140
- })
141
- return srv
45
+ /**
46
+ * Constructs service instances from a model's service definitions.
47
+ * @param { string & typeof cds.Service } services
48
+ */
49
+ async function _construct (services, o) {
50
+ if (services._is_service_class) // shortcut for directly passed service classes
51
+ return [ await init (new services (services.name, cds.model, o)) ]
52
+
53
+ // Resolve/load the model to use subsequently
54
+ const csn = !o.from || o.from === '*' ? cds.model || await cds.load('*')
55
+ : is_csn(o.from) ? o.from : await cds.load (o.from, { silent: true })
56
+ const m = cds.compile.for.nodejs (csn)
57
+
58
+ // Fetch service definitions from model
59
+ let defs = services == 'all' ? m.services : m.services.filter (_choose(_specified(services)))
60
+ defs = defs.filter (d => !d['@cds.ignore'] && !d['@cds.serve.ignore']) // skip ignored ones
61
+ defs = defs.filter (o.mocked ? _mock_external : _skip_external) // skip externals, or mock
62
+
63
+ // Construct services, adding upfront promises to cds.services
64
+ return Promise.all (defs.map (d => {
65
+ const n = cds.requires[d.name]?.name || d.name; o.service = d.name
66
+ return cds.services[n] = cds.services[d.name] = new Service(n,m,o) .then(init) .then (srv => {
67
+ if (d.is_external) srv.mocked = true
68
+ if (d.name !== n) Object.defineProperty (cds.services, d.name, {value:srv})
69
+ if (!o.silent) cds.emit (`serving:${n}`, srv)
70
+ return cds.services[n] = cds.services[d.name] = srv // replacing upfront promises with real services
71
+ })
72
+ }))
142
73
  }
143
74
 
144
75
 
145
- const is_csn = x => x && x.definitions
146
- const is_files = x => Array.isArray(x) || typeof x === 'string' && !/^[\w$]*$/.test(x)
147
- const is_class = x => typeof x === 'function' && x.prototype && /^class\b/.test(x)
76
+ const _specified = services => services.split (/\s*,\s*/).map(s => cds.requires[s]?.service || s)
77
+ const _choose = specified => ({name:n}) => specified.some (s => s === n || n.endsWith('.'+s))
78
+ const _ignore = d => d?.['@protocol'] === 'none' || d?.['@cds.api.ignore']
79
+ const _mock_external = d => _skip_external(d) || !cds.requires[d.name]?.credentials
80
+ const _skip_external = d => !d['@external'] && !d['@cds.external'] && !cds.requires[d.name]?.external
81
+ || (d.is_external = true, false) // tagging all external ones, and skipping them
82
+
83
+ const is_files = x => Array.isArray(x) || typeof x === 'string' && (/[^\w.$]/.test(x) || /\.(cds|csn|json)$/.test(x))
84
+ const is_csn = x => x?.definitions
@@ -154,6 +154,55 @@ class Service extends ReflectionAPI {
154
154
  /** @deprecated */ get operations() { return this.actions }
155
155
  /** @deprecated */ get transaction() { return this.tx }
156
156
  /** @deprecated */ get isExtensible() { return this.model === cds.model && !this.name?.startsWith('cds.xt.') }
157
+
158
+ get resolve() {
159
+ if (this._resolve) return this._resolve
160
+
161
+ const { resolveView, getTransition } = require('../../libx/_runtime/common/utils/resolveView')
162
+ const PERSISTENCE_TABLE = '@cds.persistence.table'
163
+
164
+ const _isPersistenceTable = target =>
165
+ Object.prototype.hasOwnProperty.call(target, PERSISTENCE_TABLE) && target[PERSISTENCE_TABLE]
166
+ const _defaultAbort = tx => e => e._service?.name === tx.definition?.name
167
+
168
+ this._resolve = (query, abortCondition) => {
169
+ const ctx = cds.context
170
+ const model = ctx?.model || this.model
171
+ return resolveView(query, model, this, abortCondition || _defaultAbort(this))
172
+ }
173
+
174
+ // REVISIT: Remove argument `skipForbiddenViewCheck` once we get rid of composition tree
175
+ this._resolve.transitions = (query, abortCondition, skipForbiddenViewCheck) => {
176
+ const target = query && typeof query === 'object' ? cds.infer.target(query) || query?._target : undefined
177
+ const _tx = typeof tx === 'function' ? cds.context?.tx : this
178
+ return getTransition(target, _tx, skipForbiddenViewCheck, undefined, {
179
+ abort: abortCondition ?? (this.isDatabaseService ? this.resolve._abortDB : _defaultAbort(this))
180
+ })
181
+ }
182
+
183
+ this._resolve.resolve4db = query => {
184
+ return this.resolve(query, this, this.resolve.abortDB)
185
+ }
186
+
187
+ // REVISIT: Remove once we get rid of composition tree
188
+ this._resolve.table = target => {
189
+ if (target.query?._target && !_isPersistenceTable(target)) {
190
+ return this.resolve.table(target.query._target)
191
+ }
192
+ return target
193
+ }
194
+
195
+ // REVISIT: Remove once we get rid of old db
196
+ this._resolve.abortDB = target => {
197
+ return !!(_isPersistenceTable(target) || !target.query?._target)
198
+ }
199
+
200
+ this._resolve.transitions4db = (query, skipForbiddenViewCheck) => {
201
+ return this.resolve.transitions(query, this.resolve.abortDB, skipForbiddenViewCheck)
202
+ }
203
+
204
+ return this._resolve
205
+ }
157
206
  }
158
207
 
159
208
  const { dispatch, handle } = require('./srv-dispatch')
@@ -2,7 +2,7 @@ const cds = require('..'), { path, isfile } = cds.utils
2
2
  /**
3
3
  * NOTE: Need this typed helper variable to be able to use IntelliSense for calls with new keyword.
4
4
  * @import Service from './cds.Service'
5
- * @type new() => Service
5
+ * @type new() => Service & Promise<Service>
6
6
  */
7
7
  const factory = ServiceFactory
8
8
  module.exports = exports = factory
@@ -37,7 +37,7 @@ function ServiceFactory (name, model, options) {
37
37
  }}
38
38
 
39
39
  function _kind (kind = o.kind ??= def['@kind'] || 'app-service') {
40
- const {impl} = cds.env.requires.kinds[kind] || cds.error `No configuration found for 'cds.requires.kinds.${kind}'`
40
+ const {impl} = cds.requires[kind] || cds.requires.kinds[kind] || cds.error `No configuration found for 'cds.requires.${kind}'`
41
41
  return impl || cds.error `No 'impl' configured for 'cds.requires.kinds.${kind}'`
42
42
  }
43
43
  }
@@ -67,8 +67,8 @@ const _legacy = impl => { // legacy hello-world style class
67
67
 
68
68
 
69
69
  /**
70
- * Called by cds.connect() and cds.serve() for cds.service.impl-style implementations.
71
- * @protected
70
+ * @protected Called by cds.connect() and cds.serve() for cds.service.impl-style implementations.
71
+ * @param {Service} srv
72
72
  */
73
73
  exports.init = async function (srv) {
74
74
  const {impl} = srv.options; if (typeof impl === 'function' && !impl._is_service_class)
@@ -43,7 +43,7 @@ module.exports = function ias_auth(config) {
43
43
 
44
44
  const should_validate =
45
45
  process.env.VCAP_APPLICATION &&
46
- JSON.parse(process.env.VCAP_APPLICATION).application_uris?.some(uri => uri.match(/\.cert\./))
46
+ JSON.parse(process.env.VCAP_APPLICATION).application_uris?.some(uri => uri.match(/\.cert\.|\.mesh\.cf\./))
47
47
  const validation_configured = serviceConfig.validation?.x5t?.enabled != null || serviceConfig.validation?.proofToken?.enabled != null
48
48
 
49
49
  let validating_auth_service
@@ -68,13 +68,18 @@ module.exports = function ias_auth(config) {
68
68
 
69
69
  try {
70
70
  const _auth_service =
71
- validating_auth_service && req.host.match(/\.cert\./) ? validating_auth_service : auth_service
71
+ validating_auth_service && req.hostname.match(/\.cert\.|\.mesh\.cf\./) ? validating_auth_service : auth_service
72
72
  const securityContext = await createSecurityContext(xsuaa_service ? [_auth_service, xsuaa_service] : _auth_service, { req })
73
- const tokenInfo = securityContext.token
74
73
  const ctx = cds.context
75
- ctx.user = tokenInfo instanceof XsuaaToken ? xsuaa_user_factory(tokenInfo) : user_factory(tokenInfo)
76
- ctx.tenant = tokenInfo.getZoneId()
77
- req.authInfo = securityContext //> compat req.authInfo
74
+ ctx.user = securityContext.token instanceof XsuaaToken ? xsuaa_user_factory(securityContext) : user_factory(securityContext)
75
+ ctx.tenant = securityContext.token.getZoneId()
76
+ // REVISIT: remove compat in cds^10
77
+ Object.defineProperty(req, 'authInfo', {
78
+ get() {
79
+ cds.utils.deprecated({ kind: 'API', old: 'cds.context.http.req.authInfo', use: 'cds.context.user.authInfo' })
80
+ return securityContext
81
+ }
82
+ })
78
83
  } catch (e) {
79
84
  if (e instanceof ValidationError) {
80
85
  LOG.warn('Unauthenticated request: ', e)
@@ -89,7 +94,8 @@ module.exports = function ias_auth(config) {
89
94
  }
90
95
 
91
96
  function get_user_factory(credentials, skipped_attrs) {
92
- return function user_factory(tokenInfo) {
97
+ return function user_factory(securityContext) {
98
+ const tokenInfo = securityContext.token
93
99
  const payload = tokenInfo.getPayload()
94
100
 
95
101
  /*
@@ -107,7 +113,14 @@ function get_user_factory(credentials, skipped_attrs) {
107
113
  if (Array.isArray(payload.ias_apis)) payload.ias_apis.forEach(r => (roles[r] = 1))
108
114
  if (clientid === credentials.clientid) roles['internal-user'] = 1
109
115
  else delete roles['internal-user']
110
- return new cds.User({ id: 'system', roles, tokenInfo })
116
+ return new cds.User({
117
+ id: 'system', roles, authInfo: securityContext,
118
+ // REVISIT: remove compat in cds^10
119
+ get tokenInfo() {
120
+ cds.utils.deprecated({ kind: 'API', old: 'cds.context.user.tokenInfo', use: 'cds.context.user.authInfo.token' })
121
+ return securityContext.token
122
+ }
123
+ })
111
124
  }
112
125
 
113
126
  // add all unknown attributes to req.user.attr in order to keep public API small
@@ -125,7 +138,14 @@ function get_user_factory(credentials, skipped_attrs) {
125
138
  if (attr.given_name) attr.givenName = attr.given_name
126
139
  if (attr.family_name) attr.familyName = attr.family_name
127
140
 
128
- return new cds.User({ id: payload.sub, attr, tokenInfo })
141
+ return new cds.User({
142
+ id: payload.sub, attr, authInfo: securityContext,
143
+ // REVISIT: remove compat in cds^10
144
+ get tokenInfo() {
145
+ cds.utils.deprecated({ kind: 'API', old: 'cds.context.user.tokenInfo', use: 'cds.context.user.authInfo.token' })
146
+ return securityContext.token
147
+ }
148
+ })
129
149
  }
130
150
  }
131
151
 
@@ -29,14 +29,15 @@ module.exports = function auth_factory (o) {
29
29
  // from cds.requires.auth in the log or error output below.
30
30
 
31
31
  // try resolving the impl, throw if not found
32
- const config = { kind, impl: cds.utils.local(impl) }
32
+ const config = { kind, impl }
33
33
  // use cds.resolve() to allow './srv/auth.js' and 'srv/auth.js' -> REVISIT: cds.resolve() is not needed here, and not meant for that !
34
34
  try { impl = require.resolve (cds.resolve (impl)?.[0], {paths:[cds.root]}) } catch {
35
35
  throw cds.error `Didn't find auth implementation for ${config}`
36
36
  }
37
37
 
38
38
  // load the auth middleware from the resolved path
39
- cds.log().info ('using auth strategy', config, '\n')
39
+ config.impl = cds.utils.local(impl)
40
+ cds.log().info ('using auth strategy', config)
40
41
  let auth = require (impl)
41
42
 
42
43
  // default export of ESM / .ts auth