@sap/cds 6.4.0 → 6.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 (88) hide show
  1. package/CHANGELOG.md +59 -3
  2. package/apis/cds.d.ts +2 -0
  3. package/apis/cqn.d.ts +14 -3
  4. package/apis/ql.d.ts +12 -8
  5. package/apis/services.d.ts +39 -64
  6. package/apis/test.d.ts +7 -0
  7. package/bin/build/buildTaskEngine.js +9 -12
  8. package/bin/build/buildTaskHandler.js +3 -14
  9. package/bin/build/index.js +8 -2
  10. package/bin/build/provider/buildTaskProviderInternal.js +8 -7
  11. package/bin/build/provider/hana/template/package.json +3 -0
  12. package/bin/build/provider/mtx/resourcesTarBuilder.js +13 -4
  13. package/bin/build/provider/mtx-extension/index.js +41 -38
  14. package/bin/build/util.js +17 -0
  15. package/bin/deploy/to-hana/hdiDeployUtil.js +11 -5
  16. package/bin/serve.js +6 -2
  17. package/common.cds +7 -0
  18. package/lib/auth/index.js +17 -15
  19. package/lib/auth/jwt-auth.js +4 -3
  20. package/lib/compile/for/lean_drafts.js +1 -1
  21. package/lib/compile/minify.js +3 -3
  22. package/lib/core/index.js +1 -0
  23. package/lib/dbs/cds-deploy.js +13 -10
  24. package/lib/env/cds-requires.js +1 -1
  25. package/lib/env/defaults.js +5 -1
  26. package/lib/env/schemas/cds-rc.json +74 -3
  27. package/lib/lazy.js +6 -8
  28. package/lib/log/cds-error.js +2 -2
  29. package/lib/ql/Whereable.js +22 -11
  30. package/lib/ql/cds-ql.js +1 -1
  31. package/lib/req/response.js +8 -3
  32. package/lib/req/user.js +12 -2
  33. package/lib/srv/middlewares/cds-context.js +0 -2
  34. package/lib/srv/middlewares/ctx-auth.js +11 -0
  35. package/lib/srv/middlewares/ctx-model.js +22 -20
  36. package/lib/srv/middlewares/index.js +7 -9
  37. package/lib/srv/protocols/_legacy.js +4 -0
  38. package/lib/srv/protocols/graphql.js +2 -2
  39. package/lib/srv/protocols/index.js +7 -3
  40. package/lib/srv/srv-api.js +1 -0
  41. package/lib/srv/srv-models.js +6 -1
  42. package/lib/utils/cds-utils.js +3 -1
  43. package/lib/utils/data.js +2 -2
  44. package/lib/utils/tar.js +37 -12
  45. package/libx/_runtime/auth/strategies/JWT.js +1 -0
  46. package/libx/_runtime/auth/strategies/ias-auth.js +2 -1
  47. package/libx/_runtime/auth/strategies/mock.js +12 -1
  48. package/libx/_runtime/auth/strategies/xssecUtils.js +7 -8
  49. package/libx/_runtime/auth/strategies/xsuaa.js +1 -0
  50. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -2
  51. package/libx/_runtime/cds-services/services/Service.js +3 -0
  52. package/libx/_runtime/cds-services/services/utils/columns.js +35 -36
  53. package/libx/_runtime/common/code-ext/WorkerReq.js +79 -0
  54. package/libx/_runtime/common/code-ext/config.js +13 -0
  55. package/libx/_runtime/common/code-ext/execute.js +106 -0
  56. package/libx/_runtime/common/code-ext/handlers.js +49 -0
  57. package/libx/_runtime/common/code-ext/worker.js +36 -0
  58. package/libx/_runtime/common/code-ext/workerQuery.js +45 -0
  59. package/libx/_runtime/common/code-ext/workerQueryExecutor.js +33 -0
  60. package/libx/_runtime/common/generic/crud.js +5 -1
  61. package/libx/_runtime/common/generic/paging.js +8 -7
  62. package/libx/_runtime/common/i18n/index.js +1 -1
  63. package/libx/_runtime/common/utils/cqn2cqn4sql.js +47 -11
  64. package/libx/_runtime/common/utils/path.js +5 -25
  65. package/libx/_runtime/common/utils/resolveView.js +2 -0
  66. package/libx/_runtime/common/utils/search2cqn4sql.js +13 -9
  67. package/libx/_runtime/db/expand/expandCQNToJoin.js +2 -1
  68. package/libx/_runtime/db/sql-builder/InsertBuilder.js +5 -1
  69. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +9 -32
  70. package/libx/_runtime/db/sql-builder/annotations.js +6 -3
  71. package/libx/_runtime/db/utils/localized.js +1 -1
  72. package/libx/_runtime/fiori/generic/activate.js +4 -0
  73. package/libx/_runtime/fiori/generic/before.js +8 -1
  74. package/libx/_runtime/fiori/generic/edit.js +5 -0
  75. package/libx/_runtime/fiori/generic/read.js +8 -3
  76. package/libx/_runtime/fiori/lean-draft.js +12 -1
  77. package/libx/_runtime/hana/Service.js +1 -1
  78. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -5
  79. package/libx/_runtime/hana/execute.js +5 -5
  80. package/libx/_runtime/hana/pool.js +1 -1
  81. package/libx/_runtime/hana/search2cqn4sql.js +51 -51
  82. package/libx/_runtime/sqlite/Service.js +1 -1
  83. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +20 -38
  84. package/libx/odata/afterburner.js +6 -3
  85. package/libx/odata/cqn2odata.js +1 -1
  86. package/libx/rest/middleware/parse.js +26 -4
  87. package/package.json +1 -1
  88. package/server.js +2 -20
package/lib/req/user.js CHANGED
@@ -1,3 +1,5 @@
1
+ const PSEUDO_ROLES = ['system-user', 'internal-user']
2
+
1
3
  class User {
2
4
 
3
5
  constructor (_) {
@@ -9,14 +11,22 @@ class User {
9
11
  if (typeof _ === 'string') { this.id = _; return }
10
12
  for (let each in _) super[each === '_roles' ? 'roles' : each] = _[each] // overrides getters
11
13
  const roles = this.hasOwnProperty('roles') && this.roles // eslint-disable-line no-prototype-builtins
12
- if (Array.isArray(roles)) this.roles = roles.reduce ((p,n)=>{p[n]=1; return p},{})
14
+ if (Array.isArray(roles)) this.roles = roles.filter(r => !PSEUDO_ROLES.includes(r)).reduce ((p,n)=>{p[n]=1; return p},{})
15
+ else PSEUDO_ROLES.forEach(r => delete this.roles[r])
13
16
  }
14
17
 
15
18
  get attr() { return super.attr = {} }
16
19
  get roles(){ return super.roles = {} }
17
20
  get _roles(){ return this.roles } // compatibility
18
21
 
19
- is (role) { return role === 'any' || role === 'identified-user' || role === 'authenticated-user' && this.authLevel !== 'weak' || !!this.roles[role] }
22
+ is (role) {
23
+ return role === 'any' ||
24
+ role === 'identified-user' ||
25
+ role === 'system-user' && this._is_system ||
26
+ role === 'internal-user' && this._is_internal ||
27
+ role === 'authenticated-user' && this.authLevel !== 'weak' ||
28
+ !!this.roles[role]
29
+ }
20
30
  valueOf() { return this.id }
21
31
 
22
32
  }
@@ -7,8 +7,6 @@ module.exports = ()=> {
7
7
  const ctx = {}
8
8
  ctx.http = { req, res }
9
9
  ctx.id = _id4(req)
10
- ctx.user = req.user
11
- ctx.tenant = req.tenant || ctx.user?.tenant
12
10
  cds._context.run (ctx, next)
13
11
  }
14
12
 
@@ -0,0 +1,11 @@
1
+ const cds = require ('../../index')
2
+
3
+ /**
4
+ * Propagates auth results to cds.context
5
+ */
6
+ module.exports = ()=> function cds_context_auth (req, res, next) {
7
+ const ctx = cds.context
8
+ ctx.user = req.user
9
+ ctx.tenant = req.tenant || ctx.user?.tenant
10
+ next()
11
+ }
@@ -1,24 +1,26 @@
1
- module.exports = ()=>{
1
+ module.exports = ()=> {
2
+
2
3
  const cds = require ('../../index')
3
- if (cds.requires.extensibility || cds.requires.toggles || cds.mtx) {
4
- const { model4 } = require('../srv-models')
5
- return async function cds_context_model (req,res, next) {
6
- if (req.baseUrl.startsWith('/-/')) return next() //> our own tech services cannot be extended
7
- const ctx = cds.context
8
- if (ctx.tenant) try {
9
- // if (req.headers.features) ctx.user.features = req.headers.features //> currently done in basic-auth only
10
- ctx.model = req.__model = await model4 (ctx.tenant, ctx.features) // REVISIT: req.__model is because of Okra
11
- } catch (e) {
12
- console.error(e)
13
- return res.status(503) .json ({ // REVISIT: we should throw a simple error, nothing else! -> this is overly OData-specific!
14
- error: { code: '503', message:
15
- process.env.NODE_ENV === 'production' ? 'Service Unavailable' :
16
- 'Unable to get context-specific model due to: ' + e.message
17
- }
18
- })
19
- }
20
- next()
4
+ const context_model_required = cds.requires.extensibility || cds.requires.toggles || cds.mtx
5
+ if (!context_model_required) return []
6
+
7
+ const { model4 } = require('../srv-models')
8
+ return async function cds_context_model (req,res, next) {
9
+ if (req.baseUrl.startsWith('/-/')) return next() //> our own tech services cannot be extended
10
+ const ctx = cds.context
11
+ if (ctx.tenant) try {
12
+ // if (req.headers.features) ctx.user.features = req.headers.features //> currently done in basic-auth only
13
+ ctx.model = req.__model = await model4 (ctx.tenant, ctx.features) // REVISIT: req.__model is because of Okra
14
+ } catch (e) {
15
+ console.error(e)
16
+ return res.status(503) .json ({ // REVISIT: we should throw a simple error, nothing else! -> this is overly OData-specific!
17
+ error: { code: '503', message:
18
+ process.env.NODE_ENV === 'production' ? 'Service Unavailable' :
19
+ 'Unable to get context-specific model due to: ' + e.message
20
+ }
21
+ })
21
22
  }
23
+ next()
22
24
  }
23
- else return []
25
+
24
26
  }
@@ -1,22 +1,20 @@
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')
3
4
  const ctx_model = exports.ctx_model = require('./ctx-model')
4
5
  const errors = exports.errors = require('./errors')
5
6
  const trace = exports.trace = require('./trace')
6
7
 
7
8
  // middlewares running before protocol adapters
8
9
  exports.before = [
9
- trace(), // provides detailed trace logs when DEBUG=trace
10
- auth(), // provides req.user & tenant
11
- context(), // provides cds.context
12
- ctx_model(), // fills in cds.context.model
10
+ context(), // provides cds.context
11
+ trace(), // provides detailed trace logs when DEBUG=trace
12
+ auth(), // provides req.user & tenant
13
+ ctx_auth(), // propagates auth results to cds.context
14
+ ctx_model(), // fills in cds.context.model, in case of extensibility
13
15
  ]
14
16
 
17
+ // middlewares running after protocol adapters -> usually error middlewares
15
18
  exports.after = [
16
- // usually error middlewares
17
19
  errors(),
18
20
  ]
19
-
20
- exports.bootstrap = ()=>{
21
- require('../protocols')()
22
- }
@@ -1,5 +1,7 @@
1
1
  const libx = require('../../../libx/_runtime')
2
2
  const cds_context_model = require('../srv-models')
3
+ const cds_context = require('../middlewares/cds-context')()
4
+ const ctx_auth = require('../middlewares/ctx-auth')()
3
5
  const { ProtocolAdapter } = require('.')
4
6
 
5
7
  class LegacyProtocolAdapter extends ProtocolAdapter {
@@ -14,9 +16,11 @@ class LegacyProtocolAdapter extends ProtocolAdapter {
14
16
  static serve (srv, /* in: */ app) {
15
17
  return super.serve (srv, app, { before: [
16
18
  // 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
19
+ cds_context,
17
20
  cap_req_logger,
18
21
  libx.perf,
19
22
  libx.auth(srv),
23
+ ctx_auth,
20
24
  cds_context_model.middleware4(srv)
21
25
  ], after:[] })
22
26
  }
@@ -1,7 +1,7 @@
1
1
  const cds = require ('../../index'), { decodeURIComponent } = cds.utils
2
2
  const LOG = cds.log('graphql')
3
3
 
4
- const GraphQLAdapter = require('@sap/cds-graphql/lib') // eslint-disable-line cds/no-missing-dependencies
4
+ const GraphQLAdapter = require('@cap-js/graphql') // eslint-disable-line cds/no-missing-dependencies
5
5
  const express = require ('express') // eslint-disable-line cds/no-missing-dependencies
6
6
 
7
7
  function CDSGraphQLAdapter (options) {
@@ -33,7 +33,7 @@ function CDSGraphQLAdapter (options) {
33
33
  })
34
34
 
35
35
  /** The global /graphql route */
36
- .use (new GraphQLAdapter (services, options))
36
+ .use (new GraphQLAdapter (options))
37
37
  }
38
38
 
39
39
  module.exports = CDSGraphQLAdapter
@@ -10,7 +10,6 @@ class ProtocolAdapter {
10
10
  for (let [k,o] of Object.entries(protocols)) if (typeof o === 'string') protocols[k] = {path:o}
11
11
  if (!protocols.odata) protocols.odata = { impl: join(__dirname,'odata-v4') }
12
12
  if (!protocols.rest) protocols.rest = { impl: join(__dirname,'rest') }
13
-
14
13
  // odata must always be first for fallback
15
14
  return this.protocols = { odata: protocols.odata, ...protocols }
16
15
  }
@@ -84,5 +83,10 @@ const protocols = Object.keys(ProtocolAdapter.init())
84
83
  const protocol4 = (def, _default = protocols[0]) => def['@protocol'] || protocols.find(p => def['@'+p]) || _default
85
84
  const is_global = adapter => adapter.length === 1 && !/^(function )?(\w+\s+)?\((srv|service)/.test(adapter)
86
85
 
87
- module.exports = Object.assign (ProtocolAdapter.serveAll, { ProtocolAdapter, protocol4 })
88
- if (!cds.requires.middlewares) module.exports.ProtocolAdapter = require('./_legacy')
86
+ module.exports = { ProtocolAdapter, protocol4 }
87
+ if (cds.env.protocols) {
88
+ cds.middlewares = require('../middlewares')
89
+ ProtocolAdapter.serveAll()
90
+ } else if (!cds.requires.middlewares) {
91
+ module.exports.ProtocolAdapter = require('./_legacy')
92
+ }
@@ -76,6 +76,7 @@ class Service extends require('./srv-handlers') {
76
76
  insert (...args) { return INSERT(...args).bind(this) }
77
77
  create (...args) { return INSERT.into(...args).bind(this) }
78
78
  update (...args) { return UPDATE.entity(...args).bind(this) }
79
+ upsert (...args) { return UPSERT(...args).bind(this) }
79
80
  exists (...args) { return SELECT.one([1]).from(...args).bind(this) }
80
81
 
81
82
  /**
@@ -57,7 +57,12 @@ class ExtendedModels {
57
57
  else return cache[key] = (async()=>{ // temporarily add promise to cache to avoid race conditions...
58
58
 
59
59
  // If tenant doesn't have extensions check cache with tenant = undefined
60
- const _has_extensions = tenant && extensibility && await _is_extended(tenant)
60
+ let _has_extensions = false
61
+ try {
62
+ _has_extensions = tenant && extensibility && await _is_extended(tenant)
63
+ } catch (error) {
64
+ LOG.error('`extensibility: true` is configured but table "cds.xt.Extensions" does not exist. Please redeploy.', error)
65
+ }
61
66
  if (!_has_extensions) {
62
67
  let k = cache.key4 (tenant = undefined, features)
63
68
  let cached = cache.at(k); if (cached) return cached
@@ -63,6 +63,7 @@ exports.exists = function exists (x) {
63
63
  }
64
64
  }
65
65
 
66
+ // REVISIT naming: doesn't return boolean
66
67
  exports.isdir = function isdir (x) {
67
68
  if (x) try {
68
69
  const y = resolve (cds.root,x)
@@ -72,6 +73,7 @@ exports.isdir = function isdir (x) {
72
73
  } catch(e){/* ignore */}
73
74
  }
74
75
 
76
+ // REVISIT naming: doesn't return boolean
75
77
  exports.isfile = function isfile (x) {
76
78
  if (x) try {
77
79
  const y = resolve (cds.root,x)
@@ -100,7 +102,7 @@ exports.read = async function read (file, _encoding) {
100
102
  exports.write = function write (file, data, o) {
101
103
  if (arguments.length === 1) return {to:(...path) => write(join(...path),file)}
102
104
  if (typeof data === 'object' && !Buffer.isBuffer(data))
103
- data = JSON.stringify(data, null, ' '.repeat(o && o.spaces))
105
+ data = JSON.stringify(data, null, ' '.repeat(o && o.spaces)) + require('os').EOL
104
106
  const f = resolve (cds.root,file)
105
107
  return fs.mkdirp (dirname(f)).then (()=> fs.promises.writeFile (f,data,o))
106
108
  }
package/lib/utils/data.js CHANGED
@@ -13,8 +13,8 @@ class DataUtil {
13
13
  }
14
14
  }
15
15
  if (this._deletes.length > 0) {
16
- const log = cds.log('deploy')
17
- if (log._info) log.info('Deleting all data in', this._deletes)
16
+ const LOG = cds.log('deploy')
17
+ if (!this._autoReset) LOG.info('Deleting all data for', db.model.each('entity'))
18
18
  await db.run(this._deletes)
19
19
  }
20
20
  }
package/lib/utils/tar.js CHANGED
@@ -15,16 +15,37 @@ const win = path => {
15
15
  if (Array.isArray(path)) return path.map(el => win(el))
16
16
  }
17
17
 
18
- // Copy files to temp dir on Windows and pack temp dir.
18
+ async function copyDir(src, dest) {
19
+ if ((await fs.promises.stat(src)).isDirectory()) {
20
+ const entries = await fs.promises.readdir(src)
21
+ return Promise.all(entries.map(async each => copyDir(path.join(src, each), path.join(dest, each))))
22
+ } else {
23
+ await fs.promises.mkdir(path.dirname(dest), { recursive: true })
24
+ return fs.promises.copyFile(src, dest)
25
+ }
26
+ }
27
+
28
+ // Copy resources containing files and folders to temp dir on Windows and pack temp dir.
19
29
  // cli tar has a size limit on Windows.
20
- const createTemp = async (root, files) => {
21
- const temp = await fs.promises.mkdtemp(`${fs.realpathSync(require('os').tmpdir())}${path.sep}tar-`)
22
- for (const file of files) {
23
- const fname = path.relative(root, file)
24
- const destination = path.join(temp, fname)
25
- const dirname = path.dirname(destination)
26
- if (!await exists(dirname)) await fs.promises.mkdir(dirname, { recursive: true })
27
- await fs.promises.copyFile(file, destination)
30
+ const createTemp = async (root, resources) => {
31
+ // Asynchronously copies the entire content from src to dest.
32
+ const temp = await fs.promises.mkdtemp(`${fs.realpathSync(require('os').tmpdir())}${path.sep}tar-`)
33
+ for (let resource of resources) {
34
+ const destination = path.join(temp, path.relative(root, resource))
35
+ if ((await fs.promises.stat(resource)).isFile()) {
36
+ const dirName = path.dirname(destination)
37
+ if (!await exists(dirName)) {
38
+ await fs.promises.mkdir(dirName, { recursive: true })
39
+ }
40
+ await fs.promises.copyFile(resource, destination)
41
+ } else {
42
+ if (fs.promises.cp) {
43
+ await fs.promises.cp(resource, destination, { recursive: true })
44
+ } else {
45
+ // node < 16
46
+ await copyDir(resource, destination)
47
+ }
48
+ }
28
49
  }
29
50
 
30
51
  return temp
@@ -60,6 +81,7 @@ exports.create = async (dir='.', ...args) => {
60
81
  if (Array.isArray(dir)) [ dir, ...args ] = [ cds.root, dir, ...args ]
61
82
 
62
83
  let c, temp
84
+ args = args.filter(el => el)
63
85
  if (process.platform === 'win32') {
64
86
  const spawnDir = (dir, args) => {
65
87
  if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', win(dir), ...win(args)])
@@ -74,8 +96,11 @@ exports.create = async (dir='.', ...args) => {
74
96
  c = spawnDir(dir, args)
75
97
  }
76
98
  } else {
77
- if (Array.isArray(args[0])) args.push (...args.shift().map (f => path.isAbsolute(f) ? path.relative(dir,f) : f))
78
- else args.push('.')
99
+ if (Array.isArray(args[0])) {
100
+ args.push (...args.shift().map (f => path.isAbsolute(f) ? path.relative(dir,f) : f))
101
+ } else {
102
+ args.push('.')
103
+ }
79
104
 
80
105
  c = spawn ('tar', ['c', '-C', dir, ...args])
81
106
  }
@@ -214,5 +239,5 @@ exports.t = tar.tf = tar.list
214
239
  // ---------------------------------------------------------------------------------
215
240
  // Compatibility...
216
241
 
217
- exports.packTarArchive = (files,d) => d ? tar.cz (d,files) : tar.cz (files)
242
+ exports.packTarArchive = (resources,d) => d ? tar.cz (d,resources) : tar.cz (resources)
218
243
  exports.unpackTarArchive = (x,dir) => tar.xz(x).to(dir)
@@ -24,6 +24,7 @@ class JWTStrategy extends JS {
24
24
  roles: xssecUtils.getRoles(['any', 'identified-user'], info, credentials),
25
25
  attr: xssecUtils.getAttrForJWT(info)
26
26
  })
27
+ xssecUtils.addRolesFromGrantType(user, info, credentials)
27
28
  const tenant = xssecUtils.getTenant(info)
28
29
  if (tenant) user.tenant = tenant
29
30
  // call "super.success"
@@ -42,9 +42,10 @@ module.exports = function ias_auth(config) {
42
42
  if (req.tokenInfo.getClientId() === req.tokenInfo.getSubject()) {
43
43
  req.user = new cds.User({
44
44
  id: 'system',
45
- roles: ['system-user', 'authenticated-user'],
45
+ roles: ['authenticated-user'],
46
46
  attr: {}
47
47
  })
48
+ req.user._is_system = true
48
49
  } else {
49
50
  // add all unknown attributes to req.user.attr in order to keep public API small
50
51
  const payload = req.tokenInfo.getPayload()
@@ -21,6 +21,17 @@ class MockStrategy {
21
21
  if (user.password && user.password !== password) return this.fail(CHALLENGE)
22
22
 
23
23
  const { features } = req.headers
24
+ // Only in the mock strategy the pseudo roles are kept in the role list.
25
+ // In all other cases pseudo roles are filtered out.
26
+ if (user.roles) {
27
+ if (Array.isArray(user.roles)) {
28
+ if (user.roles.includes('system-user')) user._is_system = true
29
+ if (user.roles.includes('internal-user')) user._is_internal = true
30
+ } else {
31
+ if ('system-user' in user.roles) user._is_system = true
32
+ if ('internal-user' in user.roles) user._is_internal = true
33
+ }
34
+ }
24
35
  this.success(new cds.User(features ? { ...user, features } : user))
25
36
  }
26
37
  }
@@ -44,7 +55,7 @@ const _init_users = (users, tenants = {}) => {
44
55
  Array.isArray(user.roles) ? user.roles.push(...scopes) : (user.roles = scopes)
45
56
  }
46
57
  if (user.jwt.grant_type === 'client_credentials' || user.jwt.grant_type === 'client_x509') {
47
- user.roles.push('system-user')
58
+ user._is_system = true
48
59
  }
49
60
  if (!user.tenant && user.jwt.zid) user.tenant = user.jwt.zid
50
61
  }
@@ -5,21 +5,19 @@ const getUserId = (user, info) => {
5
5
  return user.id || (info && info.getClientId && info.getClientId())
6
6
  }
7
7
 
8
- const _addRolesFromGrantType = (roles, info, credentials) => {
8
+ const addRolesFromGrantType = (user, info, credentials) => {
9
9
  const grantType = info && (info.grantType || (info.getGrantType && info.getGrantType()))
10
10
  if (grantType) {
11
11
  // > not "weak"
12
- roles.push('authenticated-user')
12
+ user.roles['authenticated-user'] = true
13
13
  if (grantType in CLIENT) {
14
- roles.push('system-user')
15
- if (info.getClientId() === credentials.clientid) roles.push('internal-user')
14
+ user._is_system = true
15
+ if (info.getClientId() === credentials.clientid) user._is_internal = true
16
16
  }
17
17
  }
18
18
  }
19
19
 
20
- const getRoles = (roles, info, credentials) => {
21
- _addRolesFromGrantType(roles, info, credentials)
22
-
20
+ const getRoles = (roles, info) => {
23
21
  // convert to object
24
22
  roles = Object.assign(...roles.map(ele => ({ [ele]: true })))
25
23
 
@@ -90,5 +88,6 @@ module.exports = {
90
88
  getRoles,
91
89
  getAttrForJWT,
92
90
  getAttrForXSSEC,
93
- getTenant
91
+ getTenant,
92
+ addRolesFromGrantType
94
93
  }
@@ -25,6 +25,7 @@ class XSUAAStrategy extends JS {
25
25
  roles: xssecUtils.getRoles(['any', 'identified-user'], info, credentials),
26
26
  attr: xssecUtils.getAttrForXSSEC(info)
27
27
  })
28
+ xssecUtils.addRolesFromGrantType(user, info, credentials)
28
29
  const tenant = xssecUtils.getTenant(info)
29
30
  if (tenant) user.tenant = tenant
30
31
  // call "super.success"
@@ -60,9 +60,8 @@ const action = service => {
60
60
  await tx.commit(result)
61
61
  }
62
62
 
63
- if (isReturnMinimal(req) || result === null) odataRes.setStatusCode(204)
63
+ if (isReturnMinimal(req) || result === null) odataRes.setStatusCode(204, { overwrite: true })
64
64
  else if (req.event === 'draftActivate' || req.event === 'EDIT') {
65
- odataRes.setStatusCode(201)
66
65
  const keys = Object.keys(req.target.keys).filter(k => {
67
66
  return k !== 'IsActiveEntity' && !req.target.keys[k]._isAssociationStrict
68
67
  })
@@ -22,6 +22,9 @@ class ApplicationService extends cds.Service {
22
22
  require('../../common/generic/temporal').call(this, this)
23
23
  require('../../common/generic/paging').call(this, this) // > paging must be executed before sorting
24
24
  require('../../common/generic/sorting').call(this, this)
25
+
26
+ if (cds.env.requires.extensibility?.code) require('../../common/code-ext/handlers').call(this, this)
27
+
25
28
  this.registerFioriHandlers(this)
26
29
  this.registerPersonalDataHandlers(this)
27
30
  this.registerCrudHandlers(this) // default .on handlers, have to go last
@@ -122,44 +122,43 @@ const _getSearchableColumns = entity => {
122
122
  * @returns {import('../../../types/api').ColumnRefs}
123
123
  */
124
124
  const computeColumnsToBeSearched = (cqn, entity = { __searchableColumns: [] }, alias) => {
125
- // if there is a group by clause, only columns in it may be searched
126
- let toBeSearched =
127
- entity.own('__searchableColumns') || entity.set('__searchableColumns', _getSearchableColumns(entity))
128
-
129
- if (cqn.SELECT.groupBy) toBeSearched = toBeSearched.filter(tbs => cqn.SELECT.groupBy.some(gb => gb.ref[0] === tbs))
130
- toBeSearched = toBeSearched.map(c => {
131
- const col = { ref: [c] }
132
- if (alias) col.ref.unshift(alias)
133
- return col
134
- })
135
-
136
- // add aggregations
137
- cqn.SELECT.columns &&
138
- cqn.SELECT.columns.forEach(column => {
139
- if (column.func) {
140
- // exclude $count by SELECT of number of Items in a Collection
141
- if (
142
- cqn.SELECT.columns.length === 1 &&
143
- column.func === 'count' &&
144
- (column.as === '_counted_' || column.as === '$count')
145
- )
125
+ let toBeSearched = []
126
+
127
+ // aggregations case
128
+ // in the new parser groupBy is moved to sub select.
129
+ if (cqn._aggregated || /* new parser */ cqn.SELECT.groupBy || cqn.SELECT?.from?.SELECT?.groupBy) {
130
+ cqn.SELECT.columns &&
131
+ cqn.SELECT.columns.forEach(column => {
132
+ if (column.func) {
133
+ // exclude $count by SELECT of number of Items in a Collection
134
+ if (
135
+ cqn.SELECT.columns.length === 1 &&
136
+ column.func === 'count' &&
137
+ (column.as === '_counted_' || column.as === '$count')
138
+ )
139
+ return
140
+
141
+ toBeSearched.push(column)
146
142
  return
147
-
148
- toBeSearched.push(column)
149
- return
150
- }
151
-
152
- const columnRef = column.ref
153
- if (columnRef) {
154
- const columnName = columnRef[columnRef.length - 1]
155
- const csnColumn = entity.elements[columnName]
156
- if (csnColumn) return
157
- const col = { ref: [columnName] }
158
- if (alias) col.ref.unshift(alias)
159
- toBeSearched.push(col)
160
- }
143
+ }
144
+
145
+ const columnRef = column.ref
146
+ if (columnRef) {
147
+ column = { ref: [...column.ref] }
148
+ if (alias) column.ref.unshift(alias)
149
+ toBeSearched.push(column)
150
+ }
151
+ })
152
+ } else {
153
+ toBeSearched = entity.own('__searchableColumns') || entity.set('__searchableColumns', _getSearchableColumns(entity))
154
+
155
+ if (cqn.SELECT.groupBy) toBeSearched = toBeSearched.filter(tbs => cqn.SELECT.groupBy.some(gb => gb.ref[0] === tbs))
156
+ toBeSearched = toBeSearched.map(c => {
157
+ const col = { ref: [c] }
158
+ if (alias) col.ref.unshift(alias)
159
+ return col
161
160
  })
162
-
161
+ }
163
162
  return toBeSearched
164
163
  }
165
164
 
@@ -0,0 +1,79 @@
1
+ const { parentPort } = require('worker_threads')
2
+ const { Responses, Errors } = require('../../../../lib/req/response')
3
+
4
+ class WorkerReq {
5
+ constructor(reqData) {
6
+ Object.assign(this, reqData)
7
+ this.postMessages = []
8
+ this.messages = this.messages ?? []
9
+ this.errors = this.errors ?? new Errors()
10
+ }
11
+
12
+ #push(args) {
13
+ this.postMessages.push({
14
+ kind: 'run',
15
+ target: 'req',
16
+ ...args
17
+ })
18
+ }
19
+
20
+ notify(...args) {
21
+ this.#push({
22
+ prop: 'notify',
23
+ args
24
+ })
25
+
26
+ const notify = Responses.get(1, ...args)
27
+ this.messages.push(notify)
28
+ return notify
29
+ }
30
+
31
+ info(...args) {
32
+ this.#push({
33
+ prop: 'info',
34
+ args
35
+ })
36
+
37
+ const info = Responses.get(2, ...args)
38
+ this.messages.push(info)
39
+ return info
40
+ }
41
+
42
+ warn(...args) {
43
+ this.#push({
44
+ prop: 'warn',
45
+ args
46
+ })
47
+
48
+ const warn = Responses.get(3, ...args)
49
+ this.messages.push(warn)
50
+ return warn
51
+ }
52
+
53
+ error(...args) {
54
+ this.#push({
55
+ prop: 'error',
56
+ args
57
+ })
58
+
59
+ let error = Responses.get(4, ...args)
60
+ if (!error.stack) Error.captureStackTrace((error = Object.assign(new Error(), error)), this.error)
61
+ this.errors.push(error)
62
+ return error
63
+ }
64
+
65
+ reject(...args) {
66
+ parentPort.postMessage({
67
+ kind: 'run',
68
+ target: 'req',
69
+ prop: 'reject',
70
+ args
71
+ })
72
+
73
+ let error = Responses.get(4, ...args)
74
+ if (!error.stack) Error.captureStackTrace((error = Object.assign(new Error(), error)), this.reject)
75
+ throw error
76
+ }
77
+ }
78
+
79
+ module.exports = WorkerReq
@@ -0,0 +1,13 @@
1
+ const os = require('os')
2
+ const totalMemory = os.totalmem() // total amount of system memory in bytes
3
+ const maxOldGenerationSizeMb = Math.floor(totalMemory / 1024 ** 2 / 8) // max size of the main heap in MB
4
+ const maxYoungGenerationSizeMb = Math.floor(totalMemory / 1024 ** 2 / 8) // max size of a heap space for recently created objects
5
+
6
+ module.exports = {
7
+ timeout: 10000,
8
+ resourceLimits: {
9
+ maxOldGenerationSizeMb,
10
+ maxYoungGenerationSizeMb,
11
+ stackSizeMb: 4 // default
12
+ }
13
+ }