@sap/cds 9.1.0 → 9.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/bin/deploy.js +29 -0
  3. package/bin/serve.js +1 -5
  4. package/lib/compile/etc/csv.js +11 -6
  5. package/lib/compile/load.js +8 -5
  6. package/lib/compile/to/hdbtabledata.js +1 -1
  7. package/lib/dbs/cds-deploy.js +0 -31
  8. package/lib/env/cds-env.js +2 -1
  9. package/lib/env/cds-requires.js +3 -0
  10. package/lib/env/schemas/cds-rc.js +4 -0
  11. package/lib/index.js +38 -38
  12. package/lib/log/cds-error.js +12 -11
  13. package/lib/log/format/json.js +1 -1
  14. package/lib/ql/SELECT.js +31 -0
  15. package/lib/ql/UPDATE.js +3 -1
  16. package/lib/ql/resolve.js +1 -1
  17. package/lib/req/context.js +1 -1
  18. package/lib/req/validate.js +16 -17
  19. package/lib/srv/cds.Service.js +18 -28
  20. package/lib/srv/middlewares/auth/ias-auth.js +31 -4
  21. package/lib/srv/middlewares/auth/jwt-auth.js +11 -1
  22. package/lib/srv/srv-models.js +1 -1
  23. package/lib/srv/srv-tx.js +2 -2
  24. package/lib/utils/cds-utils.js +35 -2
  25. package/lib/utils/csv-reader.js +1 -1
  26. package/lib/utils/version.js +18 -0
  27. package/libx/_runtime/cds.js +1 -1
  28. package/libx/_runtime/common/aspects/any.js +1 -23
  29. package/libx/_runtime/common/generic/input.js +111 -50
  30. package/libx/_runtime/common/generic/sorting.js +1 -1
  31. package/libx/_runtime/common/utils/draft.js +1 -1
  32. package/libx/_runtime/common/utils/entityFromCqn.js +1 -1
  33. package/libx/_runtime/common/utils/propagateForeignKeys.js +1 -1
  34. package/libx/_runtime/common/utils/resolveView.js +2 -2
  35. package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -2
  36. package/libx/_runtime/common/utils/structured.js +2 -2
  37. package/libx/_runtime/common/utils/templateProcessor.js +0 -5
  38. package/libx/_runtime/common/utils/vcap.js +1 -1
  39. package/libx/_runtime/fiori/lean-draft.js +63 -23
  40. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -2
  41. package/libx/_runtime/messaging/file-based.js +2 -1
  42. package/libx/_runtime/messaging/service.js +1 -1
  43. package/libx/_runtime/remote/utils/client.js +1 -1
  44. package/libx/common/assert/utils.js +2 -12
  45. package/libx/common/utils/streaming.js +4 -9
  46. package/libx/http/location.js +1 -0
  47. package/libx/odata/index.js +1 -1
  48. package/libx/odata/middleware/batch.js +6 -1
  49. package/libx/odata/middleware/create.js +1 -1
  50. package/libx/odata/middleware/error.js +22 -19
  51. package/libx/odata/middleware/stream.js +1 -1
  52. package/libx/odata/parse/cqn2odata.js +16 -10
  53. package/libx/odata/parse/grammar.peggy +8 -4
  54. package/libx/odata/parse/parser.js +1 -1
  55. package/libx/odata/utils/index.js +1 -1
  56. package/libx/queue/index.js +3 -3
  57. package/libx/rest/RestAdapter.js +1 -2
  58. package/libx/rest/middleware/create.js +5 -2
  59. package/package.json +2 -2
  60. package/server.js +1 -1
  61. package/bin/deploy/to-hana.js +0 -1
  62. package/lib/utils/check-version.js +0 -9
  63. package/lib/utils/unit.js +0 -19
  64. package/libx/_runtime/cds-services/util/assert.js +0 -181
  65. package/libx/_runtime/types/api.js +0 -129
  66. package/libx/common/assert/validation.js +0 -109
@@ -24,32 +24,20 @@ class ConsumptionAPI {
24
24
  else return this.dispatch (new Event ({ event, data, headers }))
25
25
  }
26
26
 
27
- send (req, path, data, headers) {
28
- if (is_object(req)) return this.dispatch (req instanceof Request ? req : new Request(req))
29
- if (is_object(path)) return this.dispatch (new Request (path.is_linked //...
30
- ? { method:req, entity:path, data, headers }
31
- : { method:req, data:path, headers:data }))
32
- else return this.dispatch (new Request({ method:req, path, data, headers }))
27
+ send (...args) {
28
+ const req = _req4 (...args)
29
+ return this.dispatch (req)
33
30
  }
34
- schedule (method, path, data, headers) {
35
- // not great to normalize args... better way? need to 'merge' with after/every
36
- const req = method instanceof cds.Request ? method : new cds.Request(_nrm4skd(method, path, data, headers))
31
+
32
+ schedule (...args) {
33
+ const req = _req4 (...args), {ms4} = cds.utils
37
34
  return {
38
- after (ms) {
39
- req.queue ??= {}
40
- req.queue.after = ms
41
- return this
42
- },
43
- every (ms) {
44
- req.queue ??= {}
45
- req.queue.every = ms
46
- return this
47
- },
48
- then: (r, e) => {
49
- return cds.queued(this).send(req).then(r, e)
50
- }
35
+ after (t,u) { (req.queue ??= {}).after = ms4(t,u); return this },
36
+ every (t,u) { (req.queue ??= {}).every = ms4(t,u); return this },
37
+ then: (r,e) => cds.queued(this).send(req).then(r,e)
51
38
  }
52
39
  }
40
+
53
41
  get (...args) { return is_rest(args[0]) ? this.send('GET', ...args) : this.read (...args) }
54
42
  put (...args) { return is_rest(args[0]) ? this.send('PUT', ...args) : this.update (...args) }
55
43
  post (...args) { return is_rest(args[0]) ? this.send('POST', ...args) : this.create (...args) }
@@ -179,13 +167,15 @@ const is_query = x => x?.bind || Array.isArray(x) && !x.raw
179
167
  const is_rest = x => typeof x === 'string' && x[0] === '/'
180
168
  const _service_in = m => cds.linked(m).services?.[0]?.name
181
169
  || cds.error.expected `${{model:m}} to be a CSN with a single service definition`
182
- const _nrm4skd = (method, path, data, headers) => {
183
- if (typeof method === 'object') return method
184
- if (typeof path !== 'object') return { method, path, data, headers }
185
- if (path.is_linked) return { method, entity: path, data, headers }
186
- return { method, data: path, headers: data }
187
- }
188
170
 
171
+ const _req4 = (event, path, data, headers) => {
172
+ if (is_query(event)) return new Request({ query: event, data: path, headers })
173
+ if (is_object(event)) return event instanceof Request ? event : new Request(event)
174
+ if (is_object(path)) return new Request (path.is_linked //...
175
+ ? { method:event, entity:path, data, headers }
176
+ : { method:event, data:path, headers:data })
177
+ else return new Request({ method:event, path, data, headers })
178
+ }
189
179
 
190
180
  exports = module.exports = Service
191
181
  exports.Service = Service
@@ -3,7 +3,10 @@ const LOG = cds.log('auth')
3
3
 
4
4
  const {
5
5
  createSecurityContext,
6
+ Token,
6
7
  IdentityService,
8
+ XsuaaService,
9
+ XsuaaToken,
7
10
  errors: { ValidationError }
8
11
  } = require('./xssec')
9
12
 
@@ -18,6 +21,12 @@ module.exports = function ias_auth(config) {
18
21
  'Either bind an IAS instance, or switch to an authentication kind that does not require a binding.'
19
22
  )
20
23
 
24
+ // enable signature cache by default
25
+ serviceConfig.validation ??= {}
26
+ if (!('signatureCache' in serviceConfig.validation)) serviceConfig.validation.signatureCache = { enabled: true }
27
+ // activate decode cache if not already done or explicitely disabled by setting Token.decodeCache to false or undefined
28
+ if (Token.decodeCache === null) Token.enableDecodeCache()
29
+
21
30
  const auth_service = new IdentityService(credentials, serviceConfig)
22
31
  const user_factory = get_user_factory(credentials, skipped_attrs)
23
32
 
@@ -35,25 +44,35 @@ module.exports = function ias_auth(config) {
35
44
  const should_validate =
36
45
  process.env.VCAP_APPLICATION &&
37
46
  JSON.parse(process.env.VCAP_APPLICATION).application_uris?.some(uri => uri.match(/\.cert\./))
38
- const validation_enabled = serviceConfig.validation?.x5t?.enabled || serviceConfig.validation?.proofToken?.enabled
47
+ const validation_configured = serviceConfig.validation?.x5t?.enabled != null || serviceConfig.validation?.proofToken?.enabled != null
39
48
 
40
49
  let validating_auth_service
41
- if (should_validate && !validation_enabled) {
50
+ if (should_validate && !validation_configured) {
42
51
  const _serviceConfig = { ...serviceConfig }
43
52
  _serviceConfig.validation = { x5t: { enabled: true }, proofToken: { enabled: true } }
44
53
  validating_auth_service = new IdentityService(credentials, _serviceConfig)
45
54
  }
46
55
 
56
+ // xsuaa fallback allows to also accept XSUAA tokens during migration to IAS
57
+ // automatically enabled if xsuaa credentials are available
58
+ let xsuaa_service, xsuaa_user_factory
59
+ if (cds.env.requires.xsuaa?.credentials) {
60
+ const { credentials: xsuaa_credentials, config: xsuaa_serviceConfig = {} } = cds.env.requires.xsuaa
61
+ xsuaa_service = new XsuaaService(xsuaa_credentials, xsuaa_serviceConfig)
62
+ const get_xsuaa_user_factory = require('./jwt-auth')._get_user_factory
63
+ xsuaa_user_factory = get_xsuaa_user_factory(xsuaa_credentials, xsuaa_credentials.xsappname, 'xsuaa')
64
+ }
65
+
47
66
  return async function ias_auth(req, _, next) {
48
67
  if (!req.headers.authorization) return next()
49
68
 
50
69
  try {
51
70
  const _auth_service =
52
71
  validating_auth_service && req.host.match(/\.cert\./) ? validating_auth_service : auth_service
53
- const securityContext = await createSecurityContext(_auth_service, { req })
72
+ const securityContext = await createSecurityContext(xsuaa_service ? [_auth_service, xsuaa_service] : _auth_service, { req })
54
73
  const tokenInfo = securityContext.token
55
74
  const ctx = cds.context
56
- ctx.user = user_factory(tokenInfo)
75
+ ctx.user = tokenInfo instanceof XsuaaToken ? xsuaa_user_factory(tokenInfo) : user_factory(tokenInfo)
57
76
  ctx.tenant = tokenInfo.getZoneId()
58
77
  req.authInfo = securityContext //> compat req.authInfo
59
78
  } catch (e) {
@@ -73,6 +92,14 @@ function get_user_factory(credentials, skipped_attrs) {
73
92
  return function user_factory(tokenInfo) {
74
93
  const payload = tokenInfo.getPayload()
75
94
 
95
+ /*
96
+ * NOTE:
97
+ * for easier migration, xssec will offer IAS without policies via so-called XsuaaFallback.
98
+ * in that case, we would need to add the roles here based on the tokenInfo (similar to xsuaa-auth).
99
+ * however, it is not yet clear where the roles will be stored in IAS' tokenInfo object.
100
+ * further, stakeholders would need to configure the "extension" programmatically (e.g., in a custom server.js).
101
+ */
102
+
76
103
  const clientid = tokenInfo.getClientId()
77
104
  if (clientid === payload.sub) {
78
105
  //> grant_type === client_credentials or x509
@@ -3,7 +3,9 @@ const LOG = cds.log('auth')
3
3
 
4
4
  const {
5
5
  createSecurityContext,
6
+ Token,
6
7
  XsuaaService,
8
+ XsaService,
7
9
  errors: { ValidationError }
8
10
  } = require('./xssec')
9
11
 
@@ -16,7 +18,13 @@ module.exports = function jwt_auth(config) {
16
18
  'Either bind an XSUAA instance, or switch to an authentication kind that does not require a binding.'
17
19
  )
18
20
 
19
- const auth_service = new XsuaaService(credentials, serviceConfig)
21
+ // enable signature cache by default
22
+ serviceConfig.validation ??= {}
23
+ if (!('signatureCache' in serviceConfig.validation)) serviceConfig.validation.signatureCache = { enabled: true }
24
+ // activate decode cache if not already done or explicitely disabled by setting Token.decodeCache to false or undefined
25
+ if (Token.decodeCache === null) Token.enableDecodeCache()
26
+
27
+ const auth_service = !credentials.uaadomain ? new XsaService(credentials, serviceConfig) : new XsuaaService(credentials, serviceConfig)
20
28
  const user_factory = get_user_factory(credentials, credentials.xsappname, kind)
21
29
 
22
30
  return async function jwt_auth(req, _, next) {
@@ -76,3 +84,5 @@ function get_user_factory(credentials, xsappname, kind) {
76
84
  return new cds.User({ id, roles, attr, tokenInfo })
77
85
  }
78
86
  }
87
+
88
+ module.exports._get_user_factory = get_user_factory
@@ -31,7 +31,7 @@ class ExtendedModels {
31
31
  } catch (error) {
32
32
  // Better error message for client
33
33
  if (error.status === 404) throw error
34
- cds.error('`extensibility: true` is configured but table "cds.xt.Extensions" does not exist. Please redeploy.', error)
34
+ cds.error(`\`extensibility: true\` is configured but table "cds.xt.Extensions" does not exist - please upgrade tenant '${tenant}'`, error)
35
35
  }
36
36
  if (!_has_extensions) {
37
37
  let k = cache.key4 (tenant = undefined, features)
package/lib/srv/srv-tx.js CHANGED
@@ -1,4 +1,3 @@
1
- /** @typedef {import('./cds.Service')} Service } */
2
1
 
3
2
  const cds = require('../index'), { srv_tx_compat_for_afc = true } = cds.env.features
4
3
  const EventContext = require('../req/context')
@@ -9,8 +8,9 @@ class NestedContext extends EventContext { static for(_) { return _ instanceof E
9
8
  /**
10
9
  * This is the implementation of the `srv.tx(req)` method. It constructs
11
10
  * a new Transaction as a derivate of the `srv` (i.e. {__proto__:srv})
11
+ * @typedef {import('./cds.Service')} Service
12
+ * @this {Service} @param { EventContext } ctx
12
13
  * @returns { Promise<Transaction & Service> }
13
- * @param { EventContext } ctx
14
14
  */
15
15
  module.exports = exports = function srv_tx (ctx,fn) { const srv = this
16
16
 
@@ -20,7 +20,7 @@ exports = module.exports = new class {
20
20
  get uuid() { return super.uuid = require('crypto').randomUUID }
21
21
  get yaml() { const yaml = require('js-yaml'); return super.yaml = Object.assign(yaml,{parse:yaml.load}) }
22
22
  get tar() { return super.tar = process.platform === 'win32' && _tarLib() ? require('./tar-lib') : require('./tar') }
23
- get _unit() { return super._unit = require('./unit') }
23
+ get semver() { return super.semver = require('./version') }
24
24
  }
25
25
 
26
26
  /** @type {import('node:path')} */
@@ -88,7 +88,14 @@ const chimera = Object.getOwnPropertyDescriptors (class Chimera {
88
88
  exports.decodeURIComponent = s => { try { return decodeURIComponent(s) } catch { return s } }
89
89
  exports.decodeURI = s => { try { return decodeURI(s) } catch { return s } }
90
90
 
91
- exports.local = (file) => file && relative(cwd,file)
91
+ /**
92
+ * Computes a relative path from the a working directory to the given relative file,
93
+ * considering a divergent 'outer' root path that is not `cds.root`.
94
+ * Needed for `cds watch/run <dir>` calls.
95
+ * @param {string} file - the relative file path to compute
96
+ * @returns {string} - the relative path
97
+ */
98
+ exports.local = (file) => file && relative(cwd, resolve(cds.root,file))
92
99
 
93
100
  const { prepareStackTrace, stackTraceLimit } = Error
94
101
 
@@ -334,3 +341,29 @@ exports.redacted = function _redacted(cred) {
334
341
  }
335
342
  return cred
336
343
  }
344
+
345
+
346
+ /**
347
+ * Converts a time span with a unit into milliseconds. @example
348
+ * ms4(5,'s') //> 5000
349
+ * ms4('5s') //> 5000
350
+ * @param {number|string} ts - time span as number, or string with unit suffix, with or without spaces
351
+ * @param {string} [unit] - time span unit
352
+ * @returns {number} - time span in milliseconds
353
+ */
354
+ const ms4 = exports.ms4 = (ts, unit, u=unit) => {
355
+ if (typeof ts === 'string') [,ts,u] = /(\d+) ?(\w*)/.exec(ts) || cds.error `Invalid time span format: ${ts}`
356
+ return ts * ms4[u||unit||'ms'] || cds.error `Invalid time span unit: ${unit} in ${ts}`
357
+ }
358
+
359
+ /**
360
+ * Constants for time spans factors to milliseconds. @example
361
+ * const { days, hours, minutes, second } = cds.utils.ms4
362
+ * 4 * days + 3 * hours + 2 * minutes + 1 * second //> 356521000
363
+ */
364
+ const ms = ms4.ms = 1
365
+ ms4.seconds = ms4.second = ms4.s = ms4.sec = 1000 *ms
366
+ ms4.minutes = ms4.minute = ms4.m = ms4.min = 1000 *ms * 60
367
+ ms4.hours = ms4.hour = ms4.h = ms4.hrs = 1000 *ms * 60 * 60
368
+ ms4.days = ms4.day = ms4.d = 1000 *ms * 60 * 60 * 24
369
+ ms4.weeks = ms4.week = ms4.w = 1000 *ms * 60 * 60 * 24 * 7
@@ -1,6 +1,6 @@
1
1
  const { createReadStream, createWriteStream, promises: fsp } = require('fs')
2
2
  const { Readable } = require('stream')
3
- const cds = require('../../lib')
3
+ const cds = require('..')
4
4
 
5
5
  const SEPARATOR = /[,;\t]/
6
6
 
@@ -0,0 +1,18 @@
1
+ const semver = exports = module.exports = (x,y,z) => {
2
+ if (typeof x === 'string') [ x,y,z ] = String(x).split('.')
3
+ return 1e6 * (x||0) + 1e3 * (y||0) + +(z||0)
4
+ }
5
+
6
+ exports.checkNodeVersion = (cds = require ('../../package.json')) => {
7
+ let required = cds.engines?.node?.slice(2) //> e.g. >=22
8
+ let current = process.version.slice(1) //> e.g. v24.4.1
9
+ if (semver(current) >= semver(required)) return; else process.stderr.write (`
10
+ Node.js version ${required} or higher is required for @sap/cds v${cds.version}.
11
+ Current version ${current} does not satisfy this. \n\n`)
12
+ return process.exit(1)
13
+ }
14
+
15
+ exports.check = (x, min, max) => {
16
+ let v = semver(x)
17
+ return (!min || semver(min) <= v) && (!max || v <= semver(max))
18
+ }
@@ -16,7 +16,7 @@ cds.extend(service).with(require('./common/aspects/service'))
16
16
  */
17
17
  cds.Service.prototype._requires_resolving = function (req) {
18
18
  if (req._resolved) return false
19
- if (!this.model) return false
19
+ if (!this.definition) return false
20
20
  if (!req.query || typeof req.query !== 'object') return false
21
21
  if (Array.isArray(req.query)) return false
22
22
  if (Object.keys(req.query).length === 0) return false
@@ -1,20 +1,10 @@
1
- const { foreignKey4 } = require('../../common/utils/foreignKeyPropagations')
1
+ const { foreignKey4 } = require('../utils/foreignKeyPropagations')
2
2
 
3
3
  const _getCommonFieldControl = e => {
4
4
  const cfr = e['@Common.FieldControl']
5
5
  return cfr && cfr['#']
6
6
  }
7
7
 
8
- const _isMandatory = e => {
9
- return (
10
- e['@assert.mandatory'] !== false &&
11
- (e['@mandatory'] ||
12
- e['@Common.FieldControl.Mandatory'] ||
13
- e['@FieldControl.Mandatory'] ||
14
- _getCommonFieldControl(e) === 'Mandatory')
15
- )
16
- }
17
-
18
8
  const _isReadOnly = e => {
19
9
  return (
20
10
  e['@readonly'] ||
@@ -34,22 +24,10 @@ module.exports = class {
34
24
  return this.own('__isStructured', () => !!this.elements && this.kind !== 'entity')
35
25
  }
36
26
 
37
- get _isMandatory() {
38
- return this.own('__isMandatory', () => !this.isAssociation && _isMandatory(this))
39
- }
40
-
41
27
  get _isReadOnly() {
42
28
  return this.own('__isReadOnly', () => !this.key && _isReadOnly(this))
43
29
  }
44
30
 
45
- get _mandatories() {
46
- return this.own(
47
- '__mandatories',
48
- // eslint-disable-next-line no-unused-vars
49
- () => this.elements && Object.entries(this.elements).filter(([_, v]) => v._isMandatory)
50
- )
51
- }
52
-
53
31
  get _foreignKey4() {
54
32
  return this.own('__foreignKey4', () => foreignKey4(this))
55
33
  }
@@ -13,11 +13,11 @@ const LOG = cds.log('app')
13
13
  const { Readable } = require('node:stream')
14
14
 
15
15
  const { enrichDataWithKeysFromWhere } = require('../utils/keys')
16
- const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
16
+ const { DRAFT_COLUMNS_MAP } = require('../constants/draft')
17
17
  const propagateForeignKeys = require('../utils/propagateForeignKeys')
18
- const { checkInputConstraints, assertTargets } = require('../../cds-services/util/assert')
19
18
  const getTemplate = require('../utils/template')
20
19
  const getRowUUIDGeneratorFn = require('../utils/rowUUIDGenerator')
20
+ const templatePathSerializer = require('../utils/templateProcessorPathSerializer')
21
21
 
22
22
  const _shouldSuppressErrorPropagation = (event, value) => {
23
23
  return (
@@ -96,6 +96,113 @@ const _preProcessAssertTarget = (assocInfo, assertMap) => {
96
96
  })
97
97
  }
98
98
 
99
+ const _enumValues = element => {
100
+ return Object.keys(element).map(enumKey => {
101
+ const enum_ = element[enumKey]
102
+ const enumValue = enum_ && enum_.val
103
+
104
+ if (enumValue !== undefined) {
105
+ if (enumValue['=']) return enumValue['=']
106
+ if (enum_ && enum_.literal && enum_.literal === 'number') return Number(enumValue)
107
+ return enumValue
108
+ }
109
+
110
+ return enumKey
111
+ })
112
+ }
113
+
114
+ // REVISIT: this needs a cleanup!
115
+ const _assertError = (code, element, value, key, path) => {
116
+ let args
117
+
118
+ if (typeof code === 'object') {
119
+ args = code.args
120
+ code = code.code
121
+ }
122
+
123
+ const { name, type, precision, scale } = element
124
+ const error = new Error()
125
+ const errorEntry = {
126
+ code,
127
+ message: code,
128
+ target: path ?? element.name ?? key,
129
+ args: args ?? [name ?? key]
130
+ }
131
+
132
+ const assertError = Object.assign(error, errorEntry)
133
+ Object.assign(assertError, {
134
+ entity: element.parent && element.parent.name,
135
+ element: name, // > REVISIT: when is error.element needed?
136
+ type: element.items ? element.items._type : type,
137
+ status: 400,
138
+ value
139
+ })
140
+
141
+ if (element.enum) assertError.enum = _enumValues(element)
142
+ if (precision) assertError.precision = precision
143
+ if (scale) assertError.scale = scale
144
+
145
+ if (element.target) {
146
+ // REVISIT: when does this case apply?
147
+ assertError.target = element.target
148
+ }
149
+
150
+ return assertError
151
+ }
152
+
153
+ /**
154
+ * Check whether the target entity referenced by the association (the reference's target) exists and assert an error if
155
+ * the the reference's target doesn't exist.
156
+ *
157
+ * In other words, use this annotation to check whether a non-null foreign key input in a table has a corresponding
158
+ * primary key (also known as a parent key) in the associated/referenced target table (also known as a parent table).
159
+ *
160
+ * @param {object} assertMap - Map containing the targets to assert.
161
+ * @param {array} errors - Array to collect errors.
162
+ * @see {@link https://cap.cloud.sap/docs/guides/providing-services#assert-target @assert.target} for further information.
163
+ */
164
+ const _assertTargets = async (assertMap, errors) => {
165
+ const { targets: targetsMap, allTargets } = assertMap
166
+ if (targetsMap.size === 0) return
167
+
168
+ const targets = Array.from(targetsMap.values())
169
+ const transactions = targets.map(({ keys, entity }) => {
170
+ const where = Object.assign({}, ...keys)
171
+ return cds.db.exists(entity, where).forShareLock()
172
+ })
173
+ const targetsExistsResults = await Promise.allSettled(transactions)
174
+
175
+ targetsExistsResults.forEach((txPromise, index) => {
176
+ const isPromiseRejected = txPromise.status === 'rejected'
177
+ const shouldAssertError = (txPromise.status === 'fulfilled' && txPromise.value == null) || isPromiseRejected
178
+ if (!shouldAssertError) return
179
+
180
+ const target = targets[index]
181
+ const { element } = target.assocInfo
182
+
183
+ if (isPromiseRejected) {
184
+ LOG._debug &&
185
+ LOG.debug(
186
+ `The transaction to check the @assert.target constraint for foreign key "${element.name}" failed`,
187
+ txPromise.reason
188
+ )
189
+
190
+ throw new Error(txPromise.reason.message)
191
+ }
192
+
193
+ allTargets
194
+ .filter(t => t.key === target.key)
195
+ .forEach(target => {
196
+ const { row, pathSegmentsInfo } = target.assocInfo
197
+ const key = target.foreignKey.name
198
+ let path
199
+ if (pathSegmentsInfo?.length) path = templatePathSerializer(key, pathSegmentsInfo)
200
+ const error = _assertError('ASSERT_TARGET', target.foreignKey, row[key], key, path)
201
+ errors.push(error)
202
+ })
203
+ })
204
+ }
205
+
99
206
  const _processCategory = (req, category, value, elementInfo, assertMap) => {
100
207
  const { row, key, element, isRoot } = elementInfo
101
208
  category = _getSimpleCategory(category)
@@ -166,7 +273,7 @@ const _getProcessorFn = (req, errors, assertMap) => {
166
273
  const event = req.event
167
274
 
168
275
  return elementInfo => {
169
- const { row, key, element, plain, pathSegmentsInfo } = elementInfo
276
+ const { row, key, plain } = elementInfo
170
277
  // ugly pointer passing for sonar
171
278
  const value = { mandatory: false, val: row && row[key] }
172
279
 
@@ -175,9 +282,6 @@ const _getProcessorFn = (req, errors, assertMap) => {
175
282
  }
176
283
 
177
284
  if (_shouldSuppressErrorPropagation(event, value)) return
178
-
179
- // REVISIT: Convert checkInputConstraints to template mechanism
180
- checkInputConstraints({ element, value: value.val, errors, pathSegmentsInfo, event })
181
285
  }
182
286
  }
183
287
 
@@ -276,49 +380,12 @@ async function validate_input(req) {
276
380
  pathSegmentsInfo: []
277
381
  })
278
382
  if (assertMap.targets.size > 0) {
279
- await assertTargets(assertMap, errors)
383
+ await _assertTargets(assertMap, errors)
280
384
  }
281
385
 
282
386
  if (errors.length) for (const error of errors) req.error(error)
283
387
  }
284
388
 
285
- const _getProcessorFnForActionsFunctions =
286
- (errors, opName) =>
287
- ({ row, key, element }) => {
288
- const value = row && row[key]
289
-
290
- // REVISIT: Convert checkInputConstraints to template mechanism
291
- checkInputConstraints({ element, value, errors, key: opName })
292
- }
293
-
294
- const _processActionFunctionRow = (row, param, key, errors, event, service) => {
295
- const values = Array.isArray(row[key]) ? row[key] : [row[key]]
296
-
297
- // unstructured
298
- for (const value of values) {
299
- checkInputConstraints({ element: param, value, errors, key })
300
- }
301
-
302
- // structured
303
- const template = getTemplate('app-input-operation', service, param, {
304
- pick: _pick,
305
- ignore: element => element._isAssociationStrict
306
- })
307
-
308
- template.process(values, _getProcessorFnForActionsFunctions(errors, key))
309
- }
310
-
311
- const _processActionFunction = (row, eventParams, errors, event, service) => {
312
- for (const key in eventParams) {
313
- let param = eventParams[key]
314
-
315
- // .type of action/function behaves different to .type of other csn elements
316
- const _type = param.type
317
- if (!_type && param.items) param = param.items
318
- _processActionFunctionRow(row, param, key, errors, event, service)
319
- }
320
- }
321
-
322
389
  function validate_action(req) {
323
390
  const operation = this.actions?.[req.event] || req.target?.actions?.[req.event]
324
391
  if (!operation) return
@@ -334,12 +401,6 @@ function validate_action(req) {
334
401
  let errs = cds.validate(data, operation, assertOptions)
335
402
  if (errs) return errs.forEach(err => req.error(err))
336
403
 
337
- // REVISIT: we still need the following because cds.validate doesn't check for @mandatory params (both flat and nested)
338
- const errors = []
339
- const arrayData = Array.isArray(data) ? data : [data]
340
- for (const row of arrayData) _processActionFunction(row, operation.params, errors, req.event, this)
341
- if (errors.length) for (const error of errors) req.error(error)
342
-
343
404
  // convert binaries
344
405
  operation.params &&
345
406
  !cds.env.features.base64_binaries &&
@@ -1,5 +1,5 @@
1
1
  const cds = require('../../cds')
2
- const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
2
+ const { DRAFT_COLUMNS_MAP } = require('../constants/draft')
3
3
 
4
4
  const _getStaticOrders = req => {
5
5
  const { target: entity, query } = req
@@ -1,5 +1,5 @@
1
1
  const cds = require('../../../../lib')
2
- const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
2
+ const { DRAFT_COLUMNS_MAP } = require('../constants/draft')
3
3
 
4
4
  const _4sqlite = cds.env.i18n && Array.isArray(cds.env.i18n.for_sqlite) ? cds.env.i18n.for_sqlite : []
5
5
  // compiler reserves 'localized' and raises a corresponding exception if used in models
@@ -1,4 +1,4 @@
1
- const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
1
+ const { ensureNoDraftsSuffix } = require('./draft')
2
2
 
3
3
  const traverseFroms = (cqn, cb, aliasForSet) => {
4
4
  while (cqn.SELECT) cqn = cqn.SELECT.from
@@ -1,6 +1,6 @@
1
1
  const cds = require('../../cds')
2
2
 
3
- const { prefixForStruct } = require('../../common/utils/csn')
3
+ const { prefixForStruct } = require('./csn')
4
4
 
5
5
  const _autoGenerate = e => e && e.isUUID && e.key
6
6
 
@@ -1,7 +1,7 @@
1
1
  const cds = require('../../../../lib')
2
2
  let LOG = cds.log('app')
3
3
 
4
- const { rewriteAsterisks } = require('../../common/utils/rewriteAsterisks')
4
+ const { rewriteAsterisks } = require('./rewriteAsterisks')
5
5
 
6
6
  const _setInverseTransition = (mapping, ref, mapped) => {
7
7
  const existing = mapping.get(ref)
@@ -531,7 +531,7 @@ const _mappedValue = (col, alias) => {
531
531
  const getDBTable = target => cds.ql.resolve.table(target)
532
532
 
533
533
  const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
534
- const el = target.elements[as] || target.query._target.elements[ref.at(-1)]
534
+ const el = target.elements[as] || target.query._target?.elements[ref.at(-1)]
535
535
 
536
536
  if (el && el.isAssociation && el.keys) {
537
537
  for (const key of el.keys) {
@@ -59,7 +59,7 @@ const _resolveTarget = (ref, target) => {
59
59
  if (ref.length > 1) {
60
60
  const element = target.elements[ref[0]]
61
61
  if (element) {
62
- if (element.isAssociation) throw cds.error(`Navigation "${ref.join('/')}" in expand is not supported`)
62
+ if (element.isAssociation) throw cds.error(400, `Navigation "${ref.join('/')}" in expand is not supported`)
63
63
  // structured
64
64
  return _resolveTarget(ref.slice(1), element)
65
65
  } else {
@@ -72,7 +72,7 @@ const _resolveTarget = (ref, target) => {
72
72
  const element = target.elements[_ref]
73
73
  if (element) return element._target
74
74
 
75
- throw cds.error(`Navigation property "${_ref}" is not defined in ${target.name}`, { code: 400 })
75
+ throw cds.error(400, `Navigation property "${_ref}" is not defined in ${target.name}`)
76
76
  }
77
77
 
78
78
  const rewriteExpandAsterisk = (columns, target) => {
@@ -1,6 +1,6 @@
1
1
  const resolveStructured = require('./resolveStructured')
2
- const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
3
- const { traverseFroms } = require('../../common/utils/entityFromCqn')
2
+ const { ensureNoDraftsSuffix } = require('./draft')
3
+ const { traverseFroms } = require('./entityFromCqn')
4
4
 
5
5
  const OPERATIONS_MAP = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in'].reduce((acc, cur) => {
6
6
  acc[cur] = 1
@@ -6,7 +6,6 @@ const _processElement = (processFn, row, key, target, picked = {}, isRoot, pathS
6
6
 
7
7
  if (!plain) return
8
8
 
9
- /** @type import('../../types/api').templateElementInfo */
10
9
  const elementInfo = { row, key, element, target, plain, isRoot, pathSegmentsInfo }
11
10
 
12
11
  if (!element && target._flat2struct?.[key] && elementInfo.pathSegmentsInfo) {
@@ -53,7 +52,6 @@ const _processComplex = (processFn, row, template, key, pathOptions) => {
53
52
  let pathSegmentInfo
54
53
  if (pathOptions.includeKeyValues) {
55
54
  pathOptions.rowUUIDGenerator?.(keyNames, row, template)
56
- /** @type import('../../types/api').pathSegmentInfo */
57
55
  pathSegmentInfo = { key, keyNames, row, elements: template.target.elements, draftKeys: pathOptions.draftKeys }
58
56
  }
59
57
 
@@ -63,9 +61,6 @@ const _processComplex = (processFn, row, template, key, pathOptions) => {
63
61
  }
64
62
  }
65
63
 
66
- /**
67
- * @param {import("../../types/api").TemplateProcessor} args
68
- */
69
64
  const templateProcessor = ({ processFn, data, template, isRoot = true, pathOptions = {} }) => {
70
65
  if (!template || !template.elements.size || !data || typeof data !== 'object') return
71
66
  const dataArr = Array.isArray(data) ? data : [data]
@@ -1,4 +1,4 @@
1
- const cds = require('../../../../libx/_runtime/cds')
1
+ const cds = require('../../cds')
2
2
 
3
3
  const getAppMetadata = () => {
4
4
  const appMetadata = cds.env.app