@sap/cds 7.4.1 → 7.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/apis/cds.d.ts +1 -38
  3. package/apis/core.d.ts +21 -101
  4. package/apis/cqn.d.ts +18 -76
  5. package/apis/csn.d.ts +18 -114
  6. package/apis/events.d.ts +16 -123
  7. package/apis/internal/inference.d.ts +18 -32
  8. package/apis/linked.d.ts +18 -97
  9. package/apis/log.d.ts +19 -164
  10. package/apis/models.d.ts +18 -180
  11. package/apis/ql.d.ts +16 -322
  12. package/apis/reflect.d.ts +32 -0
  13. package/apis/server.d.ts +18 -135
  14. package/apis/services.d.ts +18 -380
  15. package/bin/cds-serve.js +5 -2
  16. package/bin/serve.js +7 -16
  17. package/lib/auth/basic-auth.js +3 -1
  18. package/lib/auth/ias-auth.js +62 -48
  19. package/lib/auth/ias-claims.js +34 -0
  20. package/lib/auth/index.js +54 -33
  21. package/lib/auth/jwt-auth.js +55 -52
  22. package/lib/compile/cdsc.js +2 -2
  23. package/lib/compile/to/edm.js +4 -4
  24. package/lib/compile/to/hdbtabledata.js +5 -8
  25. package/lib/compile/to/srvinfo.js +2 -2
  26. package/lib/env/cds-env.js +3 -9
  27. package/lib/env/cds-requires.js +16 -17
  28. package/lib/env/compat.js +0 -9
  29. package/lib/env/defaults.js +17 -6
  30. package/lib/i18n/localize.js +46 -42
  31. package/lib/index.js +6 -8
  32. package/lib/linked/classes.js +7 -118
  33. package/lib/linked/entities.js +1 -1
  34. package/lib/log/cds-log.js +15 -10
  35. package/lib/log/format/aspects/als.js +41 -0
  36. package/lib/log/format/aspects/cf.js +36 -0
  37. package/lib/log/format/json.js +96 -0
  38. package/lib/plugins.js +7 -3
  39. package/lib/req/context.js +4 -2
  40. package/lib/srv/cds-connect.js +3 -5
  41. package/lib/srv/cds-serve.js +13 -26
  42. package/lib/srv/factory.js +3 -3
  43. package/lib/srv/middlewares/index.js +0 -2
  44. package/lib/srv/middlewares/trace.js +2 -3
  45. package/lib/srv/protocols/_legacy.js +27 -30
  46. package/lib/srv/protocols/index.js +173 -58
  47. package/lib/srv/protocols/odata-v4.js +29 -16
  48. package/lib/srv/srv-api.js +8 -13
  49. package/lib/srv/srv-handlers.js +14 -14
  50. package/lib/utils/cds-utils.js +15 -0
  51. package/libx/_runtime/auth/index.js +4 -5
  52. package/libx/_runtime/auth/strategies/basic.js +2 -2
  53. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +23 -13
  54. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +6 -15
  55. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +10 -3
  56. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -2
  57. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +2 -1
  58. package/libx/_runtime/cds-services/services/utils/columns.js +3 -9
  59. package/libx/_runtime/cds.js +13 -0
  60. package/libx/_runtime/common/composition/data.js +3 -0
  61. package/libx/_runtime/common/composition/delete.js +1 -1
  62. package/libx/_runtime/common/error/frontend.js +2 -2
  63. package/libx/_runtime/common/generic/auth/readOnly.js +1 -1
  64. package/libx/_runtime/common/generic/auth/restrictions.js +1 -1
  65. package/libx/_runtime/common/generic/sorting.js +4 -5
  66. package/libx/_runtime/common/utils/csn.js +23 -18
  67. package/libx/_runtime/common/utils/propagateForeignKeys.js +2 -1
  68. package/libx/_runtime/common/utils/restrictions.js +6 -15
  69. package/libx/_runtime/db/generic/input.js +3 -2
  70. package/libx/_runtime/fiori/generic/readOverDraft.js +2 -5
  71. package/libx/_runtime/fiori/lean-draft.js +69 -5
  72. package/libx/_runtime/hana/Service.js +1 -1
  73. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  74. package/libx/_runtime/messaging/Outbox.js +3 -8
  75. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  76. package/libx/_runtime/messaging/file-based.js +1 -1
  77. package/libx/_runtime/messaging/service.js +7 -10
  78. package/libx/_runtime/remote/Service.js +15 -45
  79. package/libx/_runtime/remote/utils/client.js +20 -33
  80. package/libx/_runtime/remote/utils/cloudSdkProvider.js +30 -0
  81. package/libx/_runtime/sqlite/Service.js +2 -2
  82. package/libx/odata/afterburner.js +29 -21
  83. package/libx/odata/cqn2odata.js +1 -1
  84. package/libx/odata/error.js +7 -0
  85. package/libx/odata/grammar.peggy +16 -20
  86. package/libx/odata/metadata.js +73 -78
  87. package/libx/odata/parser.js +1 -1
  88. package/libx/odata/read.js +94 -0
  89. package/libx/odata/result.js +91 -0
  90. package/libx/odata/service-document.js +31 -37
  91. package/libx/odata/utils.js +2 -1
  92. package/libx/outbox/index.js +9 -4
  93. package/libx/rest/RestAdapter.js +68 -67
  94. package/libx/rest/middleware/create.js +20 -26
  95. package/libx/rest/middleware/delete.js +5 -3
  96. package/libx/rest/middleware/error.js +2 -3
  97. package/libx/rest/middleware/input.js +5 -5
  98. package/libx/rest/middleware/operation.js +96 -41
  99. package/libx/rest/middleware/parse.js +4 -6
  100. package/libx/rest/middleware/payload.js +5 -5
  101. package/libx/rest/middleware/read.js +11 -17
  102. package/libx/rest/middleware/update.js +20 -25
  103. package/package.json +2 -1
  104. package/server.js +7 -4
  105. package/srv/outbox.cds +9 -10
  106. package/apis/env.d.ts +0 -25
  107. package/apis/test.d.ts +0 -81
  108. package/apis/utils.d.ts +0 -15
  109. package/lib/auth/passport-basic.js +0 -14
  110. package/lib/auth/passport-digest.js +0 -16
  111. package/lib/env/presets.js +0 -35
  112. package/lib/log/format/cf.js +0 -16
  113. package/lib/log/format/kibana.js +0 -92
  114. package/lib/srv/middlewares/ctx-auth.js +0 -11
  115. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +0 -119
@@ -1,6 +1,6 @@
1
1
  const cds = require ('..')
2
- const { ProtocolAdapter } = cds.service.protocols
3
2
  const { Service } = cds.service.factory
3
+ const { serve } = cds.service.protocols
4
4
  const _ready = Symbol(), _pending = cds.services._pending || {}
5
5
  const TRACE = cds.debug('trace')
6
6
 
@@ -82,9 +82,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
82
82
  // cds.services, so they'll be found when they cds.connect to each others.
83
83
  let ready = provided.then (()=> Promise.all (all.map (async srv => {
84
84
  if (o.service && o.service._is_service_instance) return srv
85
- srv.init && await srv.prepend (srv.init)
86
- srv.options.impl && await srv.prepend (srv.options.impl)
87
- cds.services[srv.name] = srv
85
+ cds.services[srv.name] = await srv._init()
88
86
  cds.service.providers.push (srv)
89
87
  if (srv[_ready]) srv[_ready](srv)
90
88
  return srv
@@ -104,30 +102,23 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
104
102
  ready = ready.then (()=> all.forEach (each => {
105
103
  const d = each.definition
106
104
  if (d['@protocol'] === 'none' || d['@cds.api.ignore']) return each._is_dark = true
107
- ProtocolAdapter.serve (each, /*in:*/ app)
105
+ serve (each, /*in:*/ app)
108
106
  if (!o.silent) cds.emit ('serving',each)
109
107
  }))
110
108
  return this
111
109
  },
112
110
 
113
- /** Finally resolve to a single picked provider or a map of all */
114
- then: (_resolve, _error) => ready.then (()=>{
115
- if (all.length === 0) return _resolve()
116
- const response = all.length === 1 ? all[0] : {}
117
- for (const each of all) {
118
- Object.defineProperty (response, each.name, { enumerable:true, configurable:true, get(){
119
- let chimera = each
120
- if (each.definition && !each._is_dark) {
121
- chimera = ProtocolAdapter.for (each)
122
- Object.setPrototypeOf (chimera, each)
123
- Object.defineProperty (chimera, 'name', { value: each.name })
124
- }
125
- Object.defineProperty (response, each.name, { enumerable:true, value: chimera })
126
- return chimera
127
- }})
128
- }
111
+ /**
112
+ * Finally resolve to a single service or a map of many,
113
+ * which can be used like that: @example
114
+ * let { CatalogService } = await cds.serve(...) // single or many
115
+ * let CatalogService = await cds.serve(...) // single only
116
+ */
117
+ then: (_resolve, _error) => ready.then ((s)=>{
129
118
  TRACE?.timeEnd('cds.serve '+som+' ')
130
- return _resolve (response)
119
+ if (all.length === 0) return _resolve()
120
+ if (all.length === 1) return _resolve(Object.defineProperty(s=all[0],s.name,{value:s}))
121
+ else return _resolve (all.reduce ((r,s)=>{ r[s.name]=s; return r },{}))
131
122
  }, _error),
132
123
 
133
124
  catch: (e) => ready.catch(e)
@@ -143,8 +134,6 @@ async function _new (Service, d,m,o) {
143
134
  if (required.name) srv.name = required.name
144
135
  if (required.external && o.mocked) srv.mocked = true
145
136
  }
146
- if (!srv.path) srv.path = cds.service.path4(srv,o)
147
- if (!srv.endpoints) srv.endpoints = cds.service.endpoints4(srv,o)
148
137
  _pending[srv.name] = new Promise (r => srv[_ready]=r).finally(()=>{
149
138
  delete _pending[srv.name]
150
139
  delete srv[_ready]
@@ -161,5 +150,3 @@ async function _new (Service, d,m,o) {
161
150
  const is_csn = x => x && x.definitions
162
151
  const is_files = x => Array.isArray(x) || typeof x === 'string' && !/^[\w$]*$/.test(x)
163
152
  const is_class = x => typeof x === 'function' && x.prototype && /^class\b/.test(x)
164
-
165
- Object.defineProperty (module.exports, 'path4', { get(){ return cds.service.path4 } })
@@ -1,4 +1,4 @@
1
- const cds = require('..'), { path, isfile } = cds.utils
1
+ const cds = require('..'), { path, isfile, _redacted } = cds.utils
2
2
  const paths = Array.from (new Set ([ cds.root, ...require.resolve.paths('x') ]))
3
3
  const DEBUG = cds.debug('cds.service.factory',); DEBUG && DEBUG ({ 'cds.root':cds.root, paths })
4
4
 
@@ -10,7 +10,7 @@ const ServiceFactory = function (name, model, options) { //NOSONAR
10
10
  const serve = !(conf && conf.external && (!o.mocked || conf.credentials))
11
11
  const defs = !model ? {[name]:{}} : model.definitions || cds.error `Invalid argument for 'model': ${model}`
12
12
  const def = !name || name === 'db' ? {} : defs[name] || {}
13
- DEBUG && DEBUG ({ name, definition:def, options:o })
13
+ DEBUG?. ({ name, definition:def, options:_redacted(o) })
14
14
 
15
15
  let it /* eslint-disable no-cond-assign */
16
16
  if (it = o.with) return _use (it) // from cds.serve (<options>)
@@ -60,7 +60,7 @@ const _function = (impl) => !_is_class(impl) ? impl : (srv) => {
60
60
  const sibling = (d) => {
61
61
  const { dir, name } = path.parse(_source(d)), TS = process.env.CDS_TYPESCRIPT
62
62
  for (let subdir of ['', './lib', './handlers']) {
63
- let found = TS && _resolve(dir,subdir,name+'.ts') || _resolve(dir,subdir,name+'.js')
63
+ let found = TS && _resolve(dir,subdir,name+'.ts') || _resolve(dir,subdir,name+'.js') || _resolve(dir,subdir,name+'.mjs')
64
64
  if (found) return found //> equiv to '.'+found.slice(home.length)
65
65
  }
66
66
  }
@@ -1,6 +1,5 @@
1
1
  const auth = exports.auth = require('../../auth')
2
2
  const context = exports.context = require('./cds-context')
3
- const ctx_auth = exports.ctx_auth = require('./ctx-auth')
4
3
  const ctx_model = exports.ctx_model = require('./ctx-model')
5
4
  const errors = exports.errors = require('./errors')
6
5
  const trace = exports.trace = require('./trace')
@@ -10,7 +9,6 @@ exports.before = [
10
9
  context, // provides cds.context
11
10
  trace, // provides detailed trace logs when DEBUG=trace
12
11
  auth, // provides req.user & tenant
13
- ctx_auth, // propagates auth results to cds.context
14
12
  ctx_model, // fills in cds.context.model, in case of extensibility
15
13
  ].map(mw => _instantiate(mw))
16
14
 
@@ -30,7 +30,6 @@ if (!LOG._debug) module.exports = ()=>[]; else {
30
30
  class PerfTrace extends Array {
31
31
  log (...details) {
32
32
  const e = { details, start:performance.now() }
33
- console.log(e)
34
33
  return this.push(e), e
35
34
  }
36
35
  done (e) {
@@ -40,7 +39,7 @@ if (!LOG._debug) module.exports = ()=>[]; else {
40
39
  const t0 = this[0].start; if (!this[0].stop) this[0].stop = performance.now()
41
40
  return '\n'+ this.map (e => truncate (format (
42
41
  (e.start - t0).toFixed(2).padStart(6), '→',
43
- (e.stop - t0).toFixed(2).padEnd(6), '= ',
42
+ (e.stop - t0).toFixed(2).padEnd(6), '=',
44
43
  (e.stop - e.start).toFixed(2).padStart(6), 'ms',
45
44
  '-', ...e.details))
46
45
  ).join('\n')
@@ -110,7 +109,7 @@ function _instrument_better_sqlite (_get_perf) {
110
109
  get(..._){ try { return x.get(..._) } finally { perf.done(pe) }},
111
110
  run(..._){ try { return x.run(..._) } finally { perf.done(pe) }},
112
111
  }
113
- else return x
112
+ else return perf.done(pe), x
114
113
  }
115
114
  catch(e) { perf.done(pe); throw e }
116
115
  }
@@ -1,34 +1,34 @@
1
- const libx = require('../../../libx/_runtime')
2
- const cds_context_model = require('../srv-models')
3
- const cds_context = require('../middlewares/cds-context')()
4
- const ctx_auth = require('../middlewares/ctx-auth')()
5
- const { ProtocolAdapter } = require('.')
6
-
7
- class LegacyProtocolAdapter extends ProtocolAdapter {
1
+ const cds = require('../../index')
2
+ cds.env.features.serve_on_root = true
8
3
 
9
- static init(protocols = { ...cds.env.protocols }) {
10
- // Legacy protocol behavior for odata and rest,
11
- // but ensure that additional protocols like graphql can be configured
12
- protocols['odata-v4'] = { get impl() { return libx.to.odata_v4 } }
13
- protocols['odata'] = { get impl() { return libx.to.odata_v4 } }
14
- protocols['rest'] = { get impl() { return libx.to.rest } }
15
- return this.protocols = cds.service._protocols = protocols
16
- }
4
+ const protocols = require('.'), { serve } = protocols
5
+ protocols['odata-v4'] = { ...protocols['odata-v4'], get impl() { return libx.to.odata_v4 } }
6
+ protocols['odata'] = { ...protocols['odata'], get impl() { return libx.to.odata_v4 } }
7
+ protocols['rest'] = { ...protocols['rest'], get impl() { return libx.to.rest } }
17
8
 
18
- static serve (srv, /* in: */ app) {
19
- return super.serve (srv, app, { before: [
20
- cds_context,
21
- libx.perf,
22
- libx.auth(srv),
23
- ctx_auth,
24
- cap_req_logger,
25
- cds_context_model.middleware4(srv)
26
- ], after:[] })
27
- }
9
+ const cds_context_model = require('../srv-models')
10
+ const cds_context = require('../middlewares/cds-context')()
11
+ const ctx_auth = require('../../auth')._ctx_auth
12
+ const libx = require('../../../libx/_runtime')
28
13
 
29
- }
14
+ Object.defineProperty (protocols, 'serve', {
15
+ value: function _serve_legacy_adapter4 (srv, app) {
16
+ const endpoints = this.endpoints4 (srv)
17
+ if (endpoints.length > 1)
18
+ throw cds.error `Cannot serve multiple endpoints if cds.requires.middlewares is set to false`
19
+ return serve (srv, app, {
20
+ before: [
21
+ cds_context,
22
+ libx.perf,
23
+ libx.auth(srv), ctx_auth,
24
+ cap_req_logger,
25
+ cds_context_model.middleware4(srv)
26
+ ],
27
+ after:[]
28
+ })
29
+ }.bind(protocols)
30
+ })
30
31
 
31
- const cds = require('../../index')
32
32
  const LOG = cds.log(), DEBUG = cds.debug()
33
33
  function cap_req_logger (req,_,next) {
34
34
  let url = req.originalUrl
@@ -42,6 +42,3 @@ function cap_req_logger (req,_,next) {
42
42
  })
43
43
  next()
44
44
  }
45
-
46
- LegacyProtocolAdapter.init()
47
- module.exports = LegacyProtocolAdapter
@@ -1,81 +1,196 @@
1
1
  const cds = require('../../index')
2
- const {join} = cds.utils.path
3
2
 
4
- class ProtocolAdapter {
3
+ /**
4
+ * Provides canonic access to configured protocols as well as helper methods.
5
+ * Instance of this class is available as cds.service.protocols.
6
+ */
7
+ class Protocols {
8
+
9
+ // Built-in protocols
10
+ 'odata-v4' = { path: '/odata/v4', impl: './odata-v4' }
11
+ 'odata-v2' = '/odata/v2'
12
+ 'odata' = this['odata-v4']
13
+ 'rest' = '/rest'
14
+ 'hcql' = '/hcql'
15
+
16
+ /** Allows changing the default in projects */
17
+ default = 'odata'
18
+
19
+
20
+ constructor (conf = cds.env.protocols||{}) {
21
+ // Make helpers non-enumerable and bound functions
22
+ const protocols = Object.defineProperties (this, {
23
+ default: { writable: true, enumerable:false, value: this.default }, // makes it non-enumerable
24
+ serve: { writable:true, configurable:true, value: this.serve.bind(this) },
25
+ })
26
+ for (let [k,p] of Object.entries(this)) this[k] = _canonic (k,p)
27
+ for (let [k,p] of Object.entries(conf)) this[k] = _canonic (k,p,'merge')
28
+ function _canonic (kind,p,merge) {
29
+ if (typeof p === 'string') p = { path:p }
30
+ if (merge) p = { ...protocols[kind], ...p }
31
+ if (!p.impl) p.impl = './'+kind
32
+ if (!p.path.startsWith('/')) p.path = '/'+p.path
33
+ if (p.path.endsWith('/')) p.path = p.path.slice(0,-1)
34
+ return p
35
+ }
36
+ }
37
+
38
+
39
+ get debug() {
40
+ // Doing this lazy to avoid eager loading of cds.env
41
+ return super.debug = cds.debug('adapters')
42
+ }
5
43
 
6
44
  /**
7
- * Provides canonicalized protocols configurations
45
+ * Constructs a new adapter for the given service, and mounts it to an express app.
8
46
  */
9
- static init (protocols = { ...cds.env.protocols }) {
10
- for (let each in protocols) protocols[each].kind = each
11
- return this.protocols = cds.service._protocols = protocols
47
+ serve (srv, /* in: */ app, { before, after } = cds.middlewares) {
48
+
49
+ const endpoints = srv.endpoints = this.endpoints4(srv)
50
+ const cached = srv._adapters ??= {}
51
+ let n = 0
52
+
53
+ for (let { kind, path } of endpoints) {
54
+
55
+ // construct adapter instance from resolved implementation
56
+ let adapter = cached[kind]; if (!adapter) {
57
+ const conf = this[kind] ??= {}
58
+ let { impl } = conf; if (typeof impl !== 'function') {
59
+ if (impl[0] === '.') impl = __dirname+impl.slice(1)
60
+ try { require.resolve(impl) } catch { cds.error `Cannot find impl for protocol adapter: ${impl}` }
61
+ impl = conf.impl = require(impl)
62
+ }
63
+ adapter = cached[kind] = impl.prototype ? new impl(srv, conf) : impl(srv, conf)
64
+ if (!adapter) continue
65
+ }
66
+
67
+ // handle first as default adapter
68
+ if (n++ === 0) {
69
+ adapter.path = srv.path = path
70
+ cached._default = adapter
71
+ if (!app) return adapter //> when called without app, construct and return default adapter only
72
+
73
+ // Add a reject-all handler for non-existing static /webapp resources, which would lead
74
+ // to lots of "UriSemanticError: 'webapp' is not an entity set, ..." errors, if the
75
+ // service path and static app root path are the same, e.g. /browse in bookshop.
76
+ if (!path.match(/^\/.+\/.+/)) app.use (`${path}/webapp/`, (_,res) => res.sendStatus(404))
77
+
78
+ // Add a reject-all handler for access to /old/$metadata if new scheme is used
79
+ if (!cds.env.features.serve_on_root && path.startsWith(this[kind].path)) {
80
+ let LOG = cds.log(kind), msg = `PLEASE NOTE:\n
81
+ With @sap/cds version 7, default service paths have changed to '${this[kind].path}/<srv>'.
82
+ If you use SAP Fiori Elements, make sure to adapt the 'dataSources.uri' paths
83
+ in 'manifest.json' files accordingly. For more information see release notes at
84
+ https://cap.cloud.sap/docs/releases/jun23#changed-default-service-path.
85
+ `.replace(/ {9}/g,'')
86
+ app.use(`${path.slice(this[kind].path.length)}/\\$metadata`, (_,res) => {
87
+ res.status(404).send(`<pre>${msg}</pre>`)
88
+ LOG?.warn(msg); LOG = undefined
89
+ })
90
+ }
91
+ }
92
+
93
+ // mount adapter to express app
94
+ this.debug?.('app.use(', path, ', ... )')
95
+ app.use (path, before, adapter, after)
96
+ }
12
97
  }
13
98
 
14
99
  /**
15
- * Returns the middleware impl for the given protocol
100
+ * Returns the endpoints for the given instance of cds.Service.
101
+ * Used in this.serve() and the outcome stored in srv.endpoints property.
102
+ * IMPORTANT: Currently only used internally in this module and should stay
103
+ * that way -> don't use anywhere else.
104
+ * @returns {{ kind:string, path:string }[]} Array of { kind, path } objects
16
105
  */
17
- static middlewareFor (p) {
18
- const conf = this.protocols[p] || (this.protocols[p] = {})
19
- let { impl = join(__dirname,p) } = conf; if (typeof impl !== 'function') {
20
- try { require.resolve(impl) } catch { cds.error `Cannot find protocol adapter implementation: ${impl}` }
21
- impl = conf.impl = require(impl)
106
+ endpoints4 (srv, o = srv.options) {
107
+ const def = srv.definition; if (!def) return
108
+
109
+ // get @protocol annotations from service definition
110
+ let annos = o?.to || def['@protocol']
111
+ if (annos) {
112
+ if (annos === 'none') return
113
+ if (!annos.reduce) annos = [annos]
114
+ }
115
+ // get @odata, @rest annotations
116
+ else {
117
+ annos=[]; for (let kind in this) {
118
+ let path = def['@'+kind] || def['@protocol.'+kind]
119
+ if (path) annos.push ({ kind, path })
120
+ }
121
+ }
122
+ // no annotations at all -> use default protocol
123
+ if (!annos.length) annos.push ({ kind: this.default })
124
+
125
+ // canonicalize to { kind, path } objects
126
+ const no_mws = cds.requires.middlewares === false
127
+ const endpoints = annos.map (each => {
128
+ let { kind = each['='] || each, path } = each
129
+ let { path: prefix } = this[kind] || cds.error `Unknown protocol: ${kind}`
130
+ if (typeof path !== 'string') path = o?.at || o?.path || def['@path'] || _slugified(srv.name)
131
+ if (path[0] !== '/') path = no_mws ? '/'+path : prefix+'/'+path
132
+ return { kind, path }
133
+ })
134
+
135
+ // add serve-on-root endpoint if enabled
136
+ if (!no_mws && endpoints.length === 1 && cds.env.features.serve_on_root) {
137
+ let [{ kind, path }] = endpoints, prefix = this[kind].path
138
+ if (prefix && path.startsWith(prefix)) endpoints.unshift ({ kind, path: path.slice(prefix.length) })
22
139
  }
23
- return impl
140
+
141
+ return endpoints.length && endpoints
24
142
  }
25
143
 
144
+
26
145
  /**
27
- * Constructs a new adapter for the given service, or returns a formerly constructed one
146
+ * For compatibility with old @sap/cds-dk versions <= 7.4.0
147
+ * @deprecated Use def._serves_odata instead
28
148
  */
29
- static for (srv, p = srv.options?.to || protocol4(srv.definition)) { // TODO default for param p?
30
- const cache = srv._adapters || (srv._adapters={}); if (p in cache) return cache[p]
31
- const impl = this.middlewareFor(p), conf = this.protocols[p]
32
- return cache[p] = impl (srv, conf)
149
+ protocol4 (def) {
150
+ return def._serves_odata ? ['odata'] : []
33
151
  }
34
152
 
35
153
  /**
36
- * Constructs a new adapter for the given service, and mounts it to an express app.
154
+ * Rarely used, e.g., by compile.to.srvinfo, and by compile.to.openapi:
155
+ * NOT PUBLIC API, hence not documented.
37
156
  */
38
- static serve (srv, /* in: */ app, { before, after } = cds.middlewares) {
39
- const logger = cds.log('adapters')
40
-
41
- const endpoints = cds.service.endpoints4(srv, srv.options)
42
-
43
- if (endpoints.length > 1 && !cds.requires.middlewares) {
44
- cds.error `Cannot serve multiple endpoints if cds.requires.middlewares is set to false`
45
- }
157
+ path4 (srv,o) {
158
+ if (!srv.definition) srv = { definition: srv, name: srv.name } // fake srv object
159
+ return this.endpoints4(srv,o)?.[0]?.path
160
+ }
46
161
 
47
- for (let endpoint of endpoints) {
48
- let adapter = this.for(srv, endpoint.kind); if (!adapter) continue
49
- // REVISIT: still needed with new handling in @sap/cds-fiori?
50
- app.use (`${endpoint.path}/webapp/`, (_,res) => res.sendStatus(404))
51
- app.use (adapter.path = endpoint.path, before, adapter, after)
52
- logger._debug && logger.debug('app.use(', endpoint.path, ', ... )')
53
-
54
- // REVISIT: move to @sap/cds-fiori
55
- // log warning for changed path if $metadata is accessed
56
- if (!cds.env.features.serve_on_root && endpoint.path.startsWith(this.protocols[endpoint.kind].path)) {
57
- const oldPath = endpoint.path.replace(this.protocols[endpoint.kind].path, '')
58
- let logged = false
59
- app.use(`*${oldPath}/\\$metadata`, (req,res,next) => {
60
- if (!logged) {
61
- logger._warn && logger.warn(`With @sap/cds version 7, the service path has changed to '${endpoint.path}'.
62
- If you use SAP Fiori Elements, make sure to adapt the 'dataSources.uri' paths
63
- in 'manifest.json' files accordingly. For more information, see the release notes at
64
- https://cap.cloud.sap/docs/releases/jun23.`)
65
- logged = true
66
- }
67
- next()
68
- })
69
- }
70
- }
162
+ /**
163
+ * Internal modules may use this to determine if the service is configured to serve OData.
164
+ * NOT PUBLIC API, hence not documented.
165
+ */
166
+ _serves_odata (def) {
167
+ const a = def['@protocol']
168
+ const vals = {odata: 1, 'odata-v4': 1}
169
+ if (a) return typeof a === 'string' ? a in vals : a.some(p => (p.kind||p) in vals)
170
+ if (def['@odata']) return true
171
+ if (def['@rest']) return false
172
+ for (let p in this) if (def['@'+p] || def['@protocol.'+p]) return false
173
+ return this.default === 'odata'
71
174
  }
72
175
  }
73
176
 
74
- const protocols = Object.keys(ProtocolAdapter.init())
75
- // REVISIT remove protocol4 (and protocols variable)
76
- const protocol4 = (def, _default = protocols[0] || 'odata-v4') => def?.['@protocol'] || protocols.find(p => def['@'+p]) || _default
77
177
 
78
- module.exports = { ProtocolAdapter, protocol4 }
79
- if (!cds.requires.middlewares) {
80
- module.exports.ProtocolAdapter = require('./_legacy')
81
- }
178
+ // Return a sluggified variant of a service's name
179
+ const _slugified = name => (
180
+ /[^.]+$/.exec(name)[0] //> my.very.CatalogService --> CatalogService
181
+ .replace(/Service$/,'') //> CatalogService --> Catalog
182
+ .replace(/_/g,'-') //> foo_bar_baz --> foo-bar-baz
183
+ .replace(/([a-z0-9])([A-Z])/g, (_,c,C) => c+'-'+C) //> ODataFooBarX9 --> OData-Foo-Bar-X9
184
+ .toLowerCase() //> FOO --> foo
185
+ )
186
+
187
+
188
+ const using_old_paths = (path) => `PLEASE NOTE:\n
189
+ With @sap/cds version 7, default service paths have changed to '${path}/<srv>'.
190
+ If you use SAP Fiori Elements, make sure to adapt the 'dataSources.uri' paths
191
+ in 'manifest.json' files accordingly. For more information, see the release notes at
192
+ https://cap.cloud.sap/docs/releases/jun23.
193
+ `
194
+
195
+ module.exports = new Protocols
196
+ if (!cds.requires.middlewares) require('./_legacy')
@@ -1,25 +1,38 @@
1
- const cds = require('../../index'), { User } = cds, { decodeURI } = cds.utils
1
+ const cds = require('../../index'),
2
+ { User } = cds,
3
+ { decodeURI } = cds.utils
2
4
  const libx = require('../../../libx/_runtime')
3
5
  const LOG = cds.log('odata')
6
+ const express = require('express') // eslint-disable-line cds/no-missing-dependencies
4
7
 
5
- module.exports = function ODataAdapter (srv) { return [
6
- (req, _, next) => {
8
+ module.exports = function ODataAdapter(srv) {
9
+ const router = express.Router()
10
+
11
+ router.use((req, _, next) => {
7
12
  let u = req.user
8
13
  req.user = u instanceof User ? u : new User(u)
9
14
 
10
15
  let url = decodeURI(req.originalUrl)
11
- LOG && LOG (req.method, url, req.body||'')
12
- if (/\$batch/.test(req.url)) req.on ('dispatch', (req) => {
13
- let path = decodeURI(req._path)
14
- LOG && LOG ('>', req.event, path, req._query||'')
15
- if (LOG._debug && req.query) LOG.debug (req.query)
16
- })
16
+ LOG && LOG(req.method, url, req.body || '')
17
+ if (/\$batch/.test(req.url))
18
+ req.on('dispatch', req => {
19
+ let path = decodeURI(req._path)
20
+ LOG && LOG('>', req.event, path, req._query || '')
21
+ if (LOG._debug && req.query) LOG.debug(req.query)
22
+ })
17
23
 
18
24
  next()
19
- },
20
- ...cds.env.features.odata_new_adapter ? [
21
- require('../../../libx/odata/service-document')(srv),
22
- require('../../../libx/odata/metadata')(srv),
23
- ] : [],
24
- libx.to.odata_v4 (srv)
25
- ]}
25
+ })
26
+
27
+ if (cds.env.features.odata_new_adapter) {
28
+ // REVISIT: add middleware for negative cases?
29
+ router.use(/^\/$/, require('../../../libx/odata/service-document')(srv))
30
+ router.use('/\\$metadata', require('../../../libx/odata/metadata')(srv))
31
+ router.get('*', require('../../../libx/odata/read')(srv))
32
+ router.use(require('../../../libx/odata/error')(srv))
33
+ }
34
+
35
+ router.use(libx.to.odata_v4(srv))
36
+
37
+ return router
38
+ }
@@ -15,17 +15,13 @@ class Service extends require('./srv-handlers') {
15
15
  if (model) this.model = model
16
16
  }
17
17
 
18
- /**
19
- * Subclasses commonly override this to register event handlers
20
- */
21
- init() {}
22
-
23
18
  /**
24
19
  * Subclasses may override this to prepare the given model appropriately
25
20
  */
26
21
  set model (csn) {
27
22
  if (csn) {
28
- super.model = cds.compile.for.nodejs(csn)
23
+ let {definitions:defs={}} = super.model = cds.compile.for.nodejs(csn)
24
+ super.definition = defs[this.options?.service] || defs[this.name]
29
25
  add_methods_to (this)
30
26
  } else {
31
27
  super.model = undefined
@@ -97,14 +93,8 @@ class Service extends require('./srv-handlers') {
97
93
  /**
98
94
  * Model Reflection API...
99
95
  */
100
- get definition() {
101
- const defs = this.model && this.model.definitions, o = this.options
102
- return super.definition = defs && (o && defs[o.service] || defs[this.name] )
103
- }
104
-
105
96
  get namespace() {
106
- return super.namespace = this.definition && this.definition.name
107
- || this.model && this.model.namespace
97
+ return super.namespace = this.definition?.name || this.model?.namespace
108
98
  || !(this.isDatabaseService) && !/\W/.test(this.name) && this.name || undefined
109
99
  }
110
100
 
@@ -131,6 +121,11 @@ class Service extends require('./srv-handlers') {
131
121
  delete cds.services[this.name]
132
122
  }
133
123
 
124
+ get path() { return super.path = cds.service.protocols.path4(this) }
125
+ set path(p) { super.path = p }
126
+
127
+ get endpoints() { return super.endpoints = cds.service.protocols.endpoints4(this) }
128
+ set endpoints(p) { super.endpoints = p }
134
129
  }
135
130
 
136
131
  const { dispatch, handle } = require('./srv-dispatch')
@@ -15,23 +15,23 @@ class EventHandlers {
15
15
  (r) => r.reject (405, `Event "${r.event}" not allowed for entity "${r.path}".`)
16
16
  )}
17
17
 
18
- async prepend (...impl_functions) {
19
- // IMPORTANT: We might be called in parallel -> the ._handlers._real
20
- // game below avoids loosing registrations due to race conditions.
21
- // Note also that {__proto__:this, _handlers:_new} doesn't work as
22
- // usages frequently look like that: srv.prepend(()=>srv.on(...)),
23
- // which means the derived srv instance would be bypassed.
24
- const _real = this._handlers._real || this._handlers
25
- const _new = { on:[], before:[], after:[], _initial:[], _error:[], _real }
26
- await Promise.all (impl_functions.map (fn => { if (is_impl(fn)) {
27
- this._handlers = _new
28
- return fn.call (this,this)
29
- }}))
30
- for (let handlers in _new) if (_new[handlers].length) _real[handlers] = [ ..._new[handlers], ..._real[handlers] ]
31
- this._handlers = _real
18
+ prepend (fn) {
19
+ const {_handlers} = this, _new = this._handlers = { on:[], before:[], after:[], _initial:[], _error:[] }
20
+ const x = fn.call (this,this) // NOTE: we need the doubled await to compensate usages of srv.prepend() with missing awaits !!!
21
+ if (x?.then) throw cds.error `srv.prepend() doesn't accept asynchronous functions anymore`
22
+ for (let each in _new) if (_new[each].length) _handlers[each].unshift(..._new[each])
23
+ this._handlers = _handlers
32
24
  return this
33
25
  }
34
26
 
27
+ async init() {}
28
+ async _init() {
29
+ const { impl } = this.options
30
+ if (typeof impl === 'function' && !/^class\b/.test(impl))
31
+ await impl.call(this,this)
32
+ await this.init()
33
+ return this
34
+ }
35
35
  }
36
36
  module.exports = EventHandlers
37
37
 
@@ -200,3 +200,18 @@ if (process.env.JEST_WORKER_ID === undefined) { // jest's ESM support is experi
200
200
  return import (pathToFileURL(id).href) // must use a file: URL, esp. on Windows for C:\... paths
201
201
  }
202
202
  }
203
+
204
+ /**
205
+ * Masks password-like strings, also reducing clutter in output
206
+ */
207
+ const SECRETS = /(password)|(certificate)|(ca)|(clientsecret)|(secret)|(key)|(clientcert)/i
208
+ exports._redacted = function _redacted(cred) {
209
+ if (!cred) return cred
210
+ if (Array.isArray(cred)) return cred.map(_redacted)
211
+ if (typeof cred === 'object') {
212
+ const newCred = Object.assign({}, cred)
213
+ Object.keys(newCred).forEach(k => (typeof newCred[k] === 'string' && SECRETS.test(k)) ? (newCred[k] = '...') : (newCred[k] = _redacted(newCred[k])))
214
+ return newCred
215
+ }
216
+ return cred
217
+ }