@sap/cds 7.2.0 → 7.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 (63) hide show
  1. package/CHANGELOG.md +174 -126
  2. package/README.md +1 -1
  3. package/apis/connect.d.ts +1 -1
  4. package/apis/core.d.ts +6 -4
  5. package/apis/serve.d.ts +1 -1
  6. package/apis/services.d.ts +51 -31
  7. package/apis/test.d.ts +24 -10
  8. package/bin/serve.js +4 -3
  9. package/common.cds +4 -4
  10. package/lib/auth/ias-auth.js +7 -8
  11. package/lib/compile/cdsc.js +5 -7
  12. package/lib/compile/etc/csv.js +22 -11
  13. package/lib/dbs/cds-deploy.js +1 -2
  14. package/lib/env/cds-env.js +26 -20
  15. package/lib/env/defaults.js +4 -3
  16. package/lib/env/schema.js +9 -0
  17. package/lib/i18n/localize.js +83 -77
  18. package/lib/index.js +6 -2
  19. package/lib/linked/classes.js +13 -13
  20. package/lib/plugins.js +41 -45
  21. package/lib/req/user.js +2 -2
  22. package/lib/srv/protocols/_legacy.js +0 -1
  23. package/lib/srv/protocols/odata-v4.js +4 -0
  24. package/lib/utils/axios.js +7 -1
  25. package/lib/utils/cds-test.js +140 -133
  26. package/lib/utils/cds-utils.js +1 -1
  27. package/lib/utils/check-version.js +6 -0
  28. package/lib/utils/data.js +19 -6
  29. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +20 -19
  30. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +10 -1
  31. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +1 -1
  32. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +2 -3
  33. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +0 -14
  34. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
  35. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/BatchRequestListBuilder.js +5 -2
  36. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/handler/MetadataHandler.js +1 -1
  37. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/handler/ServiceHandler.js +1 -1
  38. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -2
  39. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +1 -3
  40. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +1 -1
  41. package/libx/_runtime/common/composition/update.js +18 -2
  42. package/libx/_runtime/common/error/frontend.js +46 -34
  43. package/libx/_runtime/common/generic/auth/capabilities.js +33 -14
  44. package/libx/_runtime/common/generic/input.js +1 -1
  45. package/libx/_runtime/common/generic/paging.js +1 -0
  46. package/libx/_runtime/common/i18n/messages.properties +1 -0
  47. package/libx/_runtime/common/utils/cqn2cqn4sql.js +3 -3
  48. package/libx/_runtime/db/query/update.js +48 -30
  49. package/libx/_runtime/fiori/lean-draft.js +23 -24
  50. package/libx/_runtime/hana/conversion.js +3 -2
  51. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
  52. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  53. package/libx/_runtime/remote/Service.js +11 -26
  54. package/libx/_runtime/remote/utils/client.js +3 -2
  55. package/libx/_runtime/remote/utils/data.js +5 -7
  56. package/libx/odata/{grammar.pegjs → grammar.peggy} +1 -1
  57. package/libx/odata/metadata.js +121 -0
  58. package/libx/odata/parser.js +1 -1
  59. package/libx/odata/service-document.js +61 -0
  60. package/libx/odata/utils.js +102 -48
  61. package/libx/rest/RestAdapter.js +2 -2
  62. package/libx/rest/middleware/error.js +1 -1
  63. package/package.json +1 -1
@@ -18,7 +18,9 @@ class Config {
18
18
  */
19
19
  for (context, cwd, _defaults=true) {
20
20
  if (!cwd) cwd = this._home || require('..').root || process.cwd()
21
- return new Config (context, cwd, _defaults)
21
+ let env = new Config (context, cwd, _defaults)
22
+ global.cds?.emit('env',env)
23
+ return env
22
24
  }
23
25
 
24
26
 
@@ -28,7 +30,10 @@ class Config {
28
30
  constructor (_context, _home, _defaults=true) {
29
31
  Object.assign (this, { _context, _home, _sources:[] })
30
32
 
31
- // 0. determine profiles from NODE_ENV + CDS_ENV
33
+ // Capture stack trace to report cds.env usage before cds.test()
34
+ if (global.test) Error.captureStackTrace(this,Config.prototype.for)
35
+
36
+ // Determine profiles from NODE_ENV + CDS_ENV
32
37
  const { NODE_ENV, CDS_ENV } = process.env, profiles = []
33
38
  if (NODE_ENV) profiles.push (NODE_ENV)
34
39
  if (CDS_ENV) profiles.push (...CDS_ENV.split(/\s*,\s*/))
@@ -39,21 +44,17 @@ class Config {
39
44
  this._profiles._defined = new Set
40
45
  this._profiles._important = []
41
46
 
42
- // 1. set compat requires default values
47
+ // Set compat requires default values
43
48
  if (_context === 'cds' && _defaults) this.add (DEFAULTS, defaults)
44
49
  if (_context === 'cds' && _defaults) compat (this)
45
50
  if (!_home) return
46
51
 
47
- // fill-in defaults to process.env, unless already defined => 4.
52
+ // Fill-in process.env from .env files...
48
53
  this._add_to_process_env (_home, 'default-env.json')
54
+ if (this._profiles.has('development')) this._add_to_process_env (_home, '.env')
49
55
 
50
- // additional env for dev => 4.
51
- if (!this._profiles.has('production')) {
52
- this._add_to_process_env (_home, '.env')
53
- }
54
-
55
- // 2. read config sources in defined order
56
- const sources = Config.sources(_home, _context)
56
+ // Read config sources in defined order
57
+ const sources = this.sources(_home, _context)
57
58
  for (let { path:dir, file, mapper = x=>x } of sources) {
58
59
  let src = path.join(dir,file)
59
60
  let json = _readJson(src,{paths:[_home]}); if (!json) continue
@@ -64,17 +65,17 @@ class Config {
64
65
  this.add (mapper(json), src)
65
66
  }
66
67
 
67
- // 3. apply important (!) profiles from config sources
68
+ // Apply important (!) profiles from config sources
68
69
  for (let each of this._profiles._important) each()
69
70
  delete this._profiles._important
70
71
 
71
- // 4. add process env before linking to allow things like CDS_requires_db=sql
72
+ // Add process env before linking to allow things like CDS_requires_db=sql
72
73
  this._add_process_env(_context, _home)
73
74
 
74
- // 5. link dependent services
75
+ // Link cds.requires services to cds.requires.kinds
75
76
  this._link_required_services()
76
77
 
77
- // 6. Add compatibility and correlations for mtx
78
+ // Add compatibility and correlations for mtx
78
79
  const db = this.requires?.db
79
80
  if (this.requires?.db) {
80
81
  if (this.requires.multitenancy !== undefined)
@@ -84,10 +85,10 @@ class Config {
84
85
  }
85
86
  if (this.requires?.multitenancy && this.requires.db?.kind === 'hana' && !this.requires.db.vcap) Object.assign(this.requires.db, { vcap: { label: 'service-manager' } })
86
87
 
87
- // 7. complete service configurations from cloud service bindings
88
+ // Complete service configurations from cloud service bindings
88
89
  this._add_cloud_service_bindings(process.env)
89
90
 
90
- // 8. apply presets
91
+ // Apply presets
91
92
  presets (this)
92
93
 
93
94
 
@@ -99,18 +100,17 @@ class Config {
99
100
 
100
101
  /**
101
102
  * Get configuration sources
102
- *
103
103
  * @param {string} home Project home
104
104
  * @param {string} context configuration context literal
105
105
  */
106
- static sources (home, context = 'cds') {
106
+ sources (home, context = 'cds') {
107
107
  if (!home) throw new Error('Missing parameter "home".')
108
108
  if (context !== 'cds') return [
109
109
  { path: home, file: 'package.json', mapper: x => x[context] },
110
110
  ]
111
111
  const user_home = process.env.CDS_USER_HOME || require('os').homedir()
112
112
  return [
113
- ...( global._plugins||[] ).map (root => ({
113
+ ...Object.keys(this.plugins).map (root => ({
114
114
  path: root, file: 'package.json', mapper: x => x.cds
115
115
  })),
116
116
  { path: user_home, file: '.cdsrc.json', mapper: x => x.cds||x },
@@ -126,6 +126,12 @@ class Config {
126
126
  }
127
127
 
128
128
 
129
+ get plugins() {
130
+ let DEV = this._profiles.has('development')
131
+ return super.plugins = require('../plugins').fetch(DEV)
132
+ }
133
+
134
+
129
135
  add (conf, /*from:*/ _src, profiles = this._profiles) {
130
136
  if (!conf) return this
131
137
  if (_src) this._sources.push (_src)
@@ -1,5 +1,5 @@
1
+ const { join } = require('path')
1
2
  const production = process.env.NODE_ENV === 'production'
2
- const path = require('path')
3
3
 
4
4
  const defaults = module.exports = {
5
5
 
@@ -14,9 +14,10 @@ const defaults = module.exports = {
14
14
  port: 4004,
15
15
  },
16
16
 
17
+ // kept for backwards compatibility
17
18
  schemas: {
18
- 'cds-rc.json': path.join(__dirname, 'schemas/cds-rc.json'),
19
- 'cds-package.json': path.join(__dirname, 'schemas/cds-package.json'),
19
+ 'cds-rc.json': join(__dirname, 'schemas/cds-rc.json'),
20
+ 'cds-package.json': join(__dirname, 'schemas/cds-package.json'),
20
21
  },
21
22
 
22
23
  features: {
@@ -0,0 +1,9 @@
1
+ const cds = require('../index')
2
+ const { join } = cds.utils.path
3
+
4
+
5
+ module.exports = {
6
+ default4: async (name) => {
7
+ return cds.utils.read(join(__dirname, 'schemas', name), 'json')
8
+ }
9
+ }
@@ -1,4 +1,4 @@
1
- const cds = require ('..')
1
+ const cds = require('..')
2
2
  const {existsSync, readdirSync} = require ('fs')
3
3
  const {join,dirname,resolve,parse,sep} = require ('path')
4
4
 
@@ -6,68 +6,75 @@ const DEBUG = cds.debug('i18n')
6
6
  const _node_modules = cds.env.cdsc.moduleLookupDirectories.map(d => sep+d.slice(0, -1))
7
7
 
8
8
  module.exports = Object.assign (localize, {
9
- bundles4, folders4, folder4, bundle4
9
+ localize, lookup, bundles4, folders4, folder4, bundle4
10
10
  })
11
11
 
12
+ /**
13
+ * Can be used like that:
14
+ * @example cds.i18n.lookup('CreatedAt','de')
15
+ */
16
+ function lookup (key, locale, model = cds.context?.model || cds.model) {
17
+ let bundle = bundle4 (model, locale)
18
+ return bundle?.[key]
19
+ }
12
20
 
13
- function localize (model, /*with:*/ locale, aString, extBundle) {
14
- const _localize = bundle => localizeString (aString, bundle, extBundle)
21
+ /**
22
+ * Can be used like that:
23
+ * @example cds.localize(edmx,'de')
24
+ */
25
+ function localize (aString, locale, model = cds.context?.model || cds.model, ext={}) {
15
26
 
16
- const bundle = bundles4 (model, locale)
17
- if (Array.isArray(locale)) { // array of multiple locales
18
- return (function*(){
19
- let any;
20
- if(bundle && bundle[Symbol.iterator]) { // try iteration only if bundle is set
21
- for (let [lang,each] of bundle) yield any = [ _localize(each), {lang} ]
22
- }
23
- if (!any) yield [ _localize(), {lang:''} ]
24
- })()
25
- } else { // a single locale string
26
- return _localize(bundle)
27
+ // support for legacy signature
28
+ if (typeof aString === 'object') [ aString, model ] = [ model, aString ]
29
+
30
+ // in case of multiple locales, return a generator
31
+ if (Array.isArray(locale)) return (function*(){
32
+ let localized, bundles = bundles4 (model, locale)
33
+ if (bundles?.[Symbol.iterator]) for (let [lang,each] of bundles) {
34
+ localized = _localize_with (each)
35
+ yield [ localized, {lang} ]
36
+ }
37
+ if (!localized) yield [ aString, {lang:''} ]
38
+ })()
39
+
40
+ // otherwise return a single localized string
41
+ let bundle = bundle4 (model, locale)
42
+ return _localize_with (bundle)
43
+
44
+ function _localize_with (bundle) {
45
+ if (!bundle || !aString) return aString
46
+ if (typeof aString === 'object') aString = JSON.stringify(aString)
47
+ const escape = aString.startsWith('<?xml') ? escapeXmlAttr : /^[{[]/.test(aString) ? escapeJson : v=>v
48
+ return aString.replace (/{i18n>([^}]+)}/g, (_, key) => {
49
+ const val = ext[key] || bundle[key]
50
+ return !val ? key : escape(val)
51
+ })
27
52
  }
28
53
  }
29
54
 
30
- const TEXT_KEY_MARKER = 'i18n>'
31
- const TEXT_KEYS = /{b?i18n>([^"}]+)}/g
32
- function localizeString (aString, bundle, extBundle) {
33
- if (!bundle || !aString) return aString
34
- if (typeof aString === 'object') aString = JSON.stringify(aString, null, 2)
35
- // quick check for presence of any text key, to avoid expensive operation below
36
- if (!aString.includes(TEXT_KEY_MARKER)) return aString
37
- const escape = aString.startsWith('<?xml') ? escapeXmlAttr : /^[{[]/.test(aString) ? escapeJson : v=>v
38
- return aString.replace (TEXT_KEYS, (_, key) => {
39
- const val = (extBundle && extBundle[key]) || bundle[key]
40
- return val ? escape(val) : key
41
- })
42
- }
43
55
 
44
56
 
45
57
  /**
46
58
  * Returns all property bundles, i.e. one for each available translation language,
47
59
  * for the given model.
48
60
  */
49
- function bundles4 (model, locales) { // NOSONAR
50
-
51
- const folders = folders4 (model)
52
- if (folders.length === 0) return //> no folders, hence no bundles found at all
53
- if (typeof locales === 'string') return bundle4 (model, locales) // single locale string --> single bundle
54
- if (!locales) locales = cds.env.i18n.languages // default
55
- if (locales.split) locales = locales.split(',')
56
- // if no languages are specified, use all available
57
- if (locales.length === 1 && (locales[0] === '*' || locales[0] === 'all')) {
58
- locales = allLocales4 (folders)
59
- if (!locales) return {}
61
+ function bundles4 (model, locales = cds.env.i18n.languages) {
62
+
63
+ const folders = folders4 (model); if (folders.length === 0) return
64
+
65
+ if (locales.split) locales = locales.split(',')
66
+ if (locales[0] === '*' || locales[0] === 'all') {
67
+ locales = allLocales4 (folders); if (!locales) return {}
60
68
  if (!locales.includes(cds.env.i18n.fallback_bundle)) locales.push (cds.env.i18n.fallback_bundle)
61
69
  }
62
- DEBUG && DEBUG ('Languages:', locales)
70
+
71
+ DEBUG?.('Languages:', locales)
63
72
 
64
73
  return (function*(){
65
74
  for (let each of locales) {
66
- let bundle = bundle4 (model, each)
67
- if (bundle) {
68
- DEBUG && DEBUG (bundle)
69
- yield [ each, bundle ]
70
- }
75
+ let bundle = bundle4 (model, each); if(!bundle) continue
76
+ DEBUG?.(bundle)
77
+ yield [ each, bundle ]
71
78
  }
72
79
  })()
73
80
  }
@@ -79,13 +86,13 @@ function bundles4 (model, locales) { // NOSONAR
79
86
  */
80
87
  function allLocales4 (folders) {
81
88
  // find all languages in all folders
82
- const files = folders
83
- .map (folder => readdirSync(folder)
84
- .filter (e => e.startsWith(cds.env.i18n.file))
85
- .map(i18nFile => join (folder, i18nFile))
86
- ).reduce ((files, file) => files.concat(file)) // flatten
89
+ const files = folders.map (folder => readdirSync(folder)
90
+ .filter (e => e.startsWith(cds.env.i18n.file))
91
+ .map(i18nFile => join (folder, i18nFile))
92
+ ).reduce ((files, file) => files.concat(file)) // flatten
93
+
87
94
  if (files.length === 0) {
88
- DEBUG && DEBUG ('No languages for folders:', folders)
95
+ DEBUG?.('No languages for folders:', folders)
89
96
  return null
90
97
  }
91
98
 
@@ -117,17 +124,26 @@ function allLocales4 (folders) {
117
124
  */
118
125
  function bundle4 (model, locale) {
119
126
 
120
- const folders = folders4 (model); if (!folders.length) return //> no folders, hence no bundles found at all
127
+ if (typeof model === 'string') [ model, locale ] = [ cds.context?.model || cds.model, model ]
128
+
129
+ const bundles = model.texts || Object.defineProperty (model,'texts',{value:{}}).texts
130
+ if (locale in bundles) return bundles[locale]
131
+
121
132
  const bundle = {}
133
+ const folders = folders4(model)
134
+ try {
135
+ if (!folders.length) return bundle
136
+
137
+ add (cds.env.i18n.fallback_bundle) // e.g. i18n.properties
138
+ if (locale === cds.env.i18n.fallback_bundle) return bundle
122
139
 
123
- add (cds.env.i18n.fallback_bundle) // e.g. i18n.properties
124
- if (locale === cds.env.i18n.fallback_bundle) return bundle
140
+ add (cds.env.i18n.default_language) // e.g. i18n_en.properties
141
+ if (locale === cds.env.i18n.default_language) return bundle
125
142
 
126
- add (cds.env.i18n.default_language) // e.g. i18n_en.properties
127
- if (locale === cds.env.i18n.default_language) return bundle
143
+ add (locale) // e.g. i18n_de.properties
144
+ return bundle
128
145
 
129
- add (locale) // e.g. i18n_de.properties
130
- return bundle
146
+ } finally { bundles[locale] = bundle }
131
147
 
132
148
  function add (lang) {
133
149
  for (let each of folders) {
@@ -154,18 +170,16 @@ function folders4 (model) {
154
170
  // foo/bar.cds
155
171
  // foo/node_modules/reuse-level-1/model.cds
156
172
  // foo/node_modules/reuse-level-2/model.cds
157
- if (!model.$sources) return []
158
- const srcFolders = new Set (model.$sources.map (dirname))
159
- const folders = []
173
+ const folders = [] // using an array here to not screw up the folder order
174
+ const srcFolders = new Set ((model.$sources||[]).map(dirname))
160
175
  srcFolders.forEach(src => {
161
- let folder = folder4 (src)
176
+ const folder = folder4 (src)
162
177
  if (folder && !folders.includes(folder)) {
163
- folders.push(folder) // use an array here to not screw up the folder order
178
+ folders.push(folder)
164
179
  }
165
180
  })
166
-
167
- Object.defineProperty (model, '_i18nfolders', {value:folders})
168
- return folders.reverse()
181
+ Object.defineProperty (model, '_i18nfolders', {value:folders.reverse()})
182
+ return folders
169
183
  }
170
184
 
171
185
  /**
@@ -202,7 +216,7 @@ function loadFromJSON (res, lang=cds.env.i18n.default_language) {
202
216
  const bundles = require (resolve (cds.root,res+'.json'))
203
217
  return bundles[lang] || bundles [(lang.match(/\w+/)||[])[0]]
204
218
  } catch(e) {
205
- if (e.code !== 'MODULE_NOT_FOUND') throw e
219
+ if (e.code !== 'MODULE_NOT_FOUND') throw e
206
220
  }
207
221
  }
208
222
 
@@ -231,17 +245,9 @@ function escapeXmlAttr (str) {
231
245
  .replace(/</g, '&lt;')
232
246
  .replace(/"/g, '&quot;')
233
247
  .replace(/\r\n|\n/g, '&#xa;');
234
- if (!result.startsWith('{i18n>') && !result.startsWith('{bi18n'))
235
- result = result.replace(/>/g, '&gt;')
248
+ if (!result.startsWith('{i18n>')) result = result.replace(/>/g, '&gt;')
236
249
  }
237
250
  return result;
238
251
  }
239
252
 
240
- function escapeJson (str) { return str
241
- .replace(/"/g, '\\"')
242
- .replace(/\\t/g, '\\t')
243
- .replace(/\\n/g, '\\n')
244
- }
245
-
246
-
247
- /* eslint no-console:off */
253
+ const escapeJson = str => str.replace(/"/g, '\\"')
package/lib/index.js CHANGED
@@ -7,9 +7,10 @@ const cds = module.exports = new class cds extends EventEmitter {
7
7
 
8
8
  // Configuration & Information
9
9
  get requires() { return super.requires = this.env.requires._resolved() }
10
- get plugins() { return super.plugins = require('./plugins') }
10
+ get plugins() { return super.plugins = require('./plugins').activate() }
11
11
  get version() { return super.version = require('../package.json').version }
12
12
  get env() { return super.env = require('./env/cds-env').for('cds',this.root) }
13
+ get schema() { return super.schema = require('./env/schema') } // dynamic schema loading
13
14
  get home() { return super.home = __dirname.slice(0,-4) }
14
15
  cli = { command:'', options:{}, argv:[] }
15
16
  root = process.cwd()
@@ -25,7 +26,6 @@ const cds = module.exports = new class cds extends EventEmitter {
25
26
  get extend() { return super.extend = require('./compile/extend') }
26
27
  get deploy() { return super.deploy = require('./dbs/cds-deploy') }
27
28
  get localize() { return super.localize = require('./i18n/localize') }
28
- get build() { return () => { throw new Error('This application uses @sap/cds version >= 7, which is not compatible with the installed @sap/cds-dk version 6. Either update @sap/cds-dk to version 7 or downgrade @sap/cds to version 6 instead.') } }
29
29
  /** @type {{definitions:{},extensions:[]}} */ model = undefined
30
30
  /** @type Service */ db = undefined
31
31
 
@@ -116,6 +116,10 @@ const cds = module.exports = new class cds extends EventEmitter {
116
116
 
117
117
  // legacy and to be moved stuff -> hidden for tools in cds.__proto__
118
118
  /** @deprecated */ in (cwd) { return !cwd ? this : {__proto__:this, cwd, env: this.env.for('cds',cwd) } }
119
+ /** @deprecated */ get build() { return () => cds.error('\
120
+ This application uses @sap/cds version >= 7, which is not compatible with the installed @sap/cds-dk version 6. \
121
+ Either update @sap/cds-dk to version 7 or downgrade @sap/cds to version 6 instead.\
122
+ ')}
119
123
  }
120
124
 
121
125
  // add global cds.ql commands
@@ -1,6 +1,3 @@
1
- const { serve_on_root } = global.cds?.env.features || {}
2
- const { middlewares } = global.cds?.env.requires || {}
3
- const { protocols: envProtocols } = global.cds?.env || {}
4
1
  const { extend } = require('../lazy')
5
2
 
6
3
  class any {
@@ -79,7 +76,7 @@ class service extends context {
79
76
 
80
77
  // Return a sluggified variant of the service's name
81
78
  const pathFromServiceName4 = (srv) => {
82
- return (
79
+ return (
83
80
  /[^.]+$/.exec(srv.name)[0] //> my.very.CatalogService --> CatalogService
84
81
  .replace(/Service$/,'') //> CatalogService --> Catalog
85
82
  .replace(/_/g,'-') //> foo_bar_baz --> foo-bar-baz
@@ -87,7 +84,7 @@ class service extends context {
87
84
  .toLowerCase() //> FOO --> foo
88
85
  )
89
86
  }
90
-
87
+
91
88
  // Return an array of service paths for a given service and protocol.
92
89
  // The results are not prefixed with the protocol's path.
93
90
  // Can return multiple results to support serving the same protocol on multiple paths:
@@ -108,10 +105,10 @@ class service extends context {
108
105
  else paths.push(def['@path'] || pathFromServiceName4(srv))
109
106
  }
110
107
  }
111
-
108
+
112
109
  // in case protocol is set via options.at but not contained in @protocol
113
110
  if (paths.length === 0) paths.push(def['@path'] || pathFromServiceName4(srv))
114
-
111
+
115
112
  return paths
116
113
  }
117
114
 
@@ -125,15 +122,18 @@ class service extends context {
125
122
  throw new Error(`Absolute @path (starting with '/') cannot be set for more than one protocol`)
126
123
  }
127
124
 
128
- const endpoints = []
129
- for (let pName of protocols) {
130
- let p = this._protocols[pName] || envProtocols[pName]
131
- if (!p) throw new Error(`Protocol "${pName}" is unknown`)
125
+ const endpoints = [], env_protocols = global.cds?.env.protocols
126
+ const serve_on_root = global.cds?.env.features.serve_on_root
127
+
128
+ for (let each of protocols) {
129
+ let p = this._protocols[each] || env_protocols[each]
130
+ if (!p) throw new Error(`Protocol "${each}" is unknown`)
132
131
 
133
- const kind = pName
132
+ const middlewares = global.cds?.requires.middlewares
133
+ const kind = each
134
134
  let prefix = middlewares ? p.path || '/' : '/' // > no middlewares, no prefix
135
135
  if (!prefix.endsWith('/')) prefix += '/'
136
- const unprefixedPaths = unprefixedPaths4(pName, srv, options)
136
+ const unprefixedPaths = unprefixedPaths4(each, srv, options)
137
137
  for (let path of unprefixedPaths) {
138
138
  if (path.startsWith('/')) endpoints.push({ kind, path})
139
139
  else {
package/lib/plugins.js CHANGED
@@ -1,49 +1,45 @@
1
- // IMPORTANT We must not use cds.debug/log/env here to allow plugins to change env.defaults
2
- const DEBUG = /\by|all|plugins\b/.test(process.env.DEBUG)
3
- const cds = require('.'), { local } = cds.utils
1
+ const cds = require('.')
4
2
 
5
- module.exports = (async function load_plugins (log = console.log) {
6
-
7
- if (DEBUG) console.time('[cds] - loaded plugins in')
3
+ /**
4
+ * Fetch cds-plugins from project's package dependencies.
5
+ * Used in and made available through cds.env.plugins.
6
+ */
7
+ exports.fetch = function (DEV = process.env.NODE_ENV !== 'production') {
8
8
  const plugins = {}
9
-
10
- load_from(cds.home) // load well-known plugins from our dependencies
11
- load_from(cds.root)
12
-
13
- global._plugins = Object.keys(plugins) // used in cds.env
14
- await Promise.all(Object.values(plugins).map(load => load()))
15
- if (DEBUG) console.timeEnd('[cds] - loaded plugins in')
16
-
9
+ fetch_plugins_in (cds.home, false)
10
+ fetch_plugins_in (cds.root, DEV)
11
+ function fetch_plugins_in (root, dev) {
12
+ let pkg; try { pkg = require(root + '/package.json') } catch { return }
13
+ let deps = { ...pkg.dependencies, ...dev && pkg.devDependencies }
14
+ for (let each in deps) try {
15
+ let impl = require.resolve(each + '/cds-plugin', { paths: [root] })
16
+ plugins[each] = { impl }
17
+ } catch { /* no cds-plugin.js */ }
18
+ }
17
19
  return plugins
18
-
19
- function load_from(pkg_path) {
20
- let pkg; try { pkg = require(pkg_path + '/package.json') } catch { return }
21
-
22
- if (pkg.cds?.plugins) {
23
- for (let each of pkg.cds.plugins) {
24
- let plugin = typeof each === 'string' ? each : each.impl
25
- if (plugin.startsWith('.'))
26
- plugin = require.resolve(pkg_path + '/' + plugin)
27
- _load_plugin(plugin, plugin, each)
28
- }
29
- } else {
30
- const deps = { ...pkg.dependencies, ...(process.env.NODE_ENV !== 'production' && pkg.devDependencies) }
31
- const plugins = []
32
- for (let each in deps) try {
33
- let plugin = require.resolve(each + '/cds-plugin', {paths:[pkg_path]})
34
- plugins.push (()=>_load_plugin(each, plugin))
35
- } catch { /* no plugin */ }
36
- for (let each of plugins) each()
37
- }
38
-
39
- function _load_plugin (name, impl, conf) {
40
- plugins[name] = async() => {
41
- // TODO support ESM plugins. But see cap/cds/pull/1838#issuecomment-1177200 !
42
- const plugin = plugins[name] = require(impl)
43
- if (DEBUG) log('[cds] - loaded plugin:', { impl: local(impl) })
44
- if (plugin.activate) await plugin.activate(conf)
45
- }
20
+ }
21
+
22
+ /**
23
+ * Load and activate cds-plugins from project's package dependencies.
24
+ * Used in and made available through cds.plugins.
25
+ */
26
+ exports.activate = async function () {
27
+ const DEBUG = cds.debug ('plugins', {label:'cds'})
28
+ DEBUG && console.time ('[cds] - loaded plugins in')
29
+ const { plugins } = cds.env, { local } = cds.utils
30
+ await Promise.all (Object.entries(plugins) .map (async ([ plugin, conf ]) => {
31
+ DEBUG?.(`loading plugin ${plugin}:`, { impl: local(conf.impl) })
32
+ // TODO: support ESM plugins. But see cap/cds/pull/1838#issuecomment-1177200 !
33
+ const p = require (conf.impl)
34
+ if(p.activate) {
35
+ cds.log('plugins').warn(`WARNING: \n
36
+ Returning an 'activate' function is deprecated and won't be
37
+ supported in future releases. Please return a Promise with 'module.exports' instead.
38
+ `)
39
+ await p.activate(conf)
46
40
  }
47
- }
48
-
49
- })()
41
+ return p
42
+ }))
43
+ DEBUG && console.timeEnd ('[cds] - loaded plugins in')
44
+ return plugins
45
+ }
package/lib/req/user.js CHANGED
@@ -13,9 +13,9 @@ class User {
13
13
  get attr() { return super.attr = {} }
14
14
  set attr(a) { super.attr = a }
15
15
 
16
- get roles(){ return super.roles = {} }
16
+ get roles() { return super.roles = {} }
17
17
  set roles(r) {
18
- super.roles = !Array.isArray(r) ? r : r.reduce((p,n)=>{ p[n]=1; return p },{})
18
+ super.roles = Array.isArray(r) ? r.reduce((p, n) => { p[n]=1; return p }, {}) : r
19
19
  }
20
20
 
21
21
  is (role) {
@@ -17,7 +17,6 @@ class LegacyProtocolAdapter extends ProtocolAdapter {
17
17
 
18
18
  static serve (srv, /* in: */ app) {
19
19
  return super.serve (srv, app, { before: [
20
- // async (req, res, next) => { await 1; next() }, // REVISIT: AsyncResource.bind() -> enable to break cds/tests/_runtime/odata/__tests__/integration/crud-with-mtx.test.js with existing, non-middleware mode, *w/o* fix to BufferedWriter
21
20
  cds_context,
22
21
  libx.perf,
23
22
  libx.auth(srv),
@@ -17,5 +17,9 @@ module.exports = function ODataAdapter (srv) { return [
17
17
 
18
18
  next()
19
19
  },
20
+ ...cds.env.features.odata_new_adapter ? [
21
+ require('../../../libx/odata/service-document')(srv),
22
+ require('../../../libx/odata/metadata')(srv),
23
+ ] : [],
20
24
  libx.to.odata_v4 (srv)
21
25
  ]}
@@ -2,10 +2,16 @@ class Axios {
2
2
 
3
3
  get axios() {
4
4
  // eslint-disable-next-line cds/no-missing-dependencies
5
- return super.axios = require('axios').create ({
5
+ const axios = require('axios').create ({
6
6
  headers: { 'Content-Type': 'application/json' },
7
7
  baseURL: this.url,
8
8
  })
9
+ // fill in baseURL on subsequent this.url = url, after server has started
10
+ Reflect.defineProperty (this, 'url', { configurable: true, set: url => {
11
+ Reflect.defineProperty (this, 'url', { value: url })
12
+ axios.defaults.baseURL = url
13
+ }})
14
+ return super.axios = axios
9
15
  }
10
16
  get (..._) { return this.axios.get (..._args(_)) .catch(_error) }
11
17
  put (..._) { return this.axios.put (..._args(_)) .catch(_error) }