@sap/cds 6.2.3 → 6.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 (67) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/apis/connect.d.ts +1 -1
  3. package/apis/cqn.d.ts +1 -1
  4. package/apis/internal/inference.d.ts +14 -0
  5. package/apis/ql.d.ts +40 -36
  6. package/apis/services.d.ts +23 -6
  7. package/bin/build/buildTaskHandler.js +3 -3
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +1 -1
  9. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +4 -3
  10. package/bin/build/provider/buildTaskHandlerInternal.js +2 -2
  11. package/bin/build/provider/java/index.js +2 -1
  12. package/bin/build/provider/mtx/index.js +2 -1
  13. package/bin/build/provider/mtx/resourcesTarBuilder.js +3 -2
  14. package/bin/build/provider/mtx-extension/index.js +2 -1
  15. package/bin/build/provider/mtx-sidecar/index.js +3 -1
  16. package/lib/auth/index.js +2 -1
  17. package/lib/auth/jwt-auth.js +64 -3
  18. package/lib/auth/xsuaa-auth.js +2 -3
  19. package/lib/compile/cdsc.js +1 -0
  20. package/lib/compile/etc/_localized.js +1 -0
  21. package/lib/dbs/cds-deploy.js +2 -1
  22. package/lib/env/cds-env.js +14 -49
  23. package/lib/env/cds-requires.js +13 -7
  24. package/lib/env/defaults.js +4 -0
  25. package/lib/i18n/localize.js +11 -8
  26. package/lib/index.js +1 -1
  27. package/lib/log/cds-log.js +2 -2
  28. package/lib/log/format/cf.js +16 -0
  29. package/lib/log/format/kibana.js +15 -2
  30. package/lib/ql/INSERT.js +12 -11
  31. package/lib/ql/Query.js +14 -7
  32. package/lib/ql/UPSERT.js +1 -0
  33. package/lib/ql/Whereable.js +6 -2
  34. package/lib/ql/cds-ql.js +2 -4
  35. package/lib/req/request.js +2 -0
  36. package/lib/srv/middlewares/cds-context.js +1 -1
  37. package/lib/srv/srv-dispatch.js +1 -0
  38. package/lib/srv/srv-tx.js +3 -3
  39. package/lib/utils/cds-utils.js +75 -30
  40. package/lib/utils/inflect.js +24 -0
  41. package/libx/_runtime/auth/strategies/ias-auth.js +1 -1
  42. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +9 -1
  43. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +23 -6
  44. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -0
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +27 -15
  46. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -1
  47. package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -10
  48. package/libx/_runtime/cds-services/services/utils/differ.js +6 -4
  49. package/libx/_runtime/common/composition/data.js +29 -40
  50. package/libx/_runtime/common/composition/update.js +6 -19
  51. package/libx/_runtime/common/generic/paging.js +1 -1
  52. package/libx/_runtime/common/utils/resolveView.js +7 -13
  53. package/libx/_runtime/db/utils/generateAliases.js +1 -0
  54. package/libx/_runtime/fiori/generic/read.js +11 -4
  55. package/libx/_runtime/hana/execute.js +2 -2
  56. package/libx/_runtime/hana/search2cqn4sql.js +1 -0
  57. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  58. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +5 -2
  59. package/libx/_runtime/messaging/enterprise-messaging.js +7 -1
  60. package/libx/_runtime/messaging/file-based.js +1 -1
  61. package/libx/_runtime/messaging/message-queuing.js +5 -2
  62. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  63. package/libx/_runtime/messaging/service.js +5 -3
  64. package/libx/odata/cqn2odata.js +4 -1
  65. package/libx/odata/utils.js +8 -7
  66. package/libx/rest/RestAdapter.js +1 -4
  67. package/package.json +1 -1
@@ -87,12 +87,15 @@ class EMManagement {
87
87
  this.subdomain ? { queue: queueName, subdomain: this.subdomain } : { queue: queueName }
88
88
  )
89
89
  try {
90
+ const queueConfig = this.queueConfig && { ...this.queueConfig }
91
+ if (queueConfig?.deadMsgQueue)
92
+ queueConfig.deadMsgQueue = queueConfig.deadMsgQueue.replace(/\$namespace/g, this.namespace)
90
93
  const res = await authorizedRequest({
91
94
  method: 'PUT',
92
95
  uri: this.options.uri,
93
96
  path: `/hub/rest/api/v1/management/messaging/queues/${encodeURIComponent(queueName)}`,
94
97
  oa2: this.options.oa2,
95
- dataObj: this.queueConfig,
98
+ dataObj: queueConfig,
96
99
  tokenStore: this
97
100
  })
98
101
  if (res.statusCode === 201) return true
@@ -389,7 +392,7 @@ class EMManagement {
389
392
  this.LOG._info && this.LOG.info('Unchanged subscriptions', unchangedSubs, ' ', this.subdomainInfo)
390
393
  await Promise.all([
391
394
  ...obsoleteSubs.map(s => this.deleteSubscription(s)),
392
- ...additionalSubs.map(async s => this.createSubscription(s))
395
+ ...additionalSubs.map(async t => this.createSubscription(t))
393
396
  ])
394
397
  return
395
398
  }
@@ -139,7 +139,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
139
139
  const doNotDeploy = _multitenancyEnabled() && !this.options.deployForProvider
140
140
  if (doNotDeploy) this.LOG._info && this.LOG.info('Skipping deployment of messaging artifacts for provider account')
141
141
  super.startListening({ doNotDeploy })
142
- if (!doNotDeploy && this.subscribedTopics.size) {
142
+ if (!doNotDeploy && (this._listenToAll || this.subscribedTopics.size)) {
143
143
  const management = this.getManagement()
144
144
  // Webhooks will perform an OPTIONS call on creation to check the availability of the app.
145
145
  // On systems like Cloud Foundry the app URL will only be advertised once
@@ -218,6 +218,11 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
218
218
  const topic = msg.event
219
219
  const message = { ...(msg.headers || {}), data: msg.data }
220
220
 
221
+ const contentType =
222
+ msg.headers && ['id', 'source', 'specversion', 'type'].every(el => el in msg.headers)
223
+ ? 'application/cloudevents+json'
224
+ : 'application/json'
225
+
221
226
  await this.queued(() => {})()
222
227
 
223
228
  try {
@@ -229,6 +234,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
229
234
  tenant,
230
235
  dataObj: message,
231
236
  headers: {
237
+ 'Content-Type': contentType,
232
238
  'x-qos': 1
233
239
  },
234
240
  tokenStore: {}
@@ -35,7 +35,7 @@ class FileBasedMessaging extends MessagingService {
35
35
  }
36
36
 
37
37
  startWatching() {
38
- if (!this.subscribedTopics.size) return
38
+ if (!this._listenToAll && !this.subscribedTopics.size) return
39
39
  const watcher = async () => {
40
40
  if (!(await touched(this.file, this.recent))) return // > not touched since last check
41
41
  // REVISIT: Bad if lock file wasn't cleaned up (due to crashes...)
@@ -61,12 +61,15 @@ class MQManagement {
61
61
  async createQueue(queueName = this.queueName) {
62
62
  this.LOG._info && this.LOG.info('Create queue', { queue: queueName })
63
63
  try {
64
+ const queueConfig = this.queueConfig && { ...this.queueConfig }
65
+ if (queueConfig?.deadMessageQueue)
66
+ queueConfig.deadMessageQueue = queueConfig.deadMessageQueue.replace(/\$namespace/g, '')
64
67
  const res = await authorizedRequest({
65
68
  method: 'PUT',
66
69
  uri: this.options.url,
67
70
  path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
68
71
  oa2: this.options.auth.oauth2,
69
- dataObj: this.queueConfig,
72
+ dataObj: queueConfig,
70
73
  tokenStore: this
71
74
  })
72
75
  if (res.statusCode === 201) return true
@@ -182,7 +185,7 @@ class MQManagement {
182
185
  .filter(s => !existingSubscriptions.some(e => s === e))
183
186
  await Promise.all([
184
187
  ...obsoleteSubs.map(s => this.deleteSubscription(s)),
185
- ...additionalSubs.map(s => this.createSubscription(s))
188
+ ...additionalSubs.map(t => this.createSubscription(t))
186
189
  ])
187
190
  return
188
191
  }
@@ -50,7 +50,7 @@ const _processSingleMessage = async (service, message, succeededMessages) => {
50
50
  // Promise resolve is necessary because we want to set `cds.context` only
51
51
  // inside this call
52
52
  return Promise.resolve().then(async () => {
53
- if (userId) cds.context.user = new cds.User.Privileged(userId)
53
+ if (userId) cds.context = { user: new cds.User.Privileged(userId) }
54
54
  try {
55
55
  service._emitImmediate && (await service._emitImmediate(msg))
56
56
  succeededMessages.push(message.ID)
@@ -97,7 +97,8 @@ class MessagingService extends OutboxService {
97
97
  on(event, cb) {
98
98
  const _event = _warnAndStripTopicPrefix(event, this.LOG)
99
99
  // save all subscribed topics (not needed for local-messaging)
100
- this.subscribedTopics.set(this.prepareTopic(_event, true), _event)
100
+ if (event !== '*') this.subscribedTopics.set(this.prepareTopic(_event, true), _event)
101
+ else this._listenToAll = true
101
102
  return super.on(_event, cb)
102
103
  }
103
104
 
@@ -134,8 +135,9 @@ class MessagingService extends OutboxService {
134
135
  const subscribedEvent =
135
136
  this.subscribedTopics.get(_msg.event) ||
136
137
  (this.wildcarded && this.subscribedTopics.get(this.wildcarded(_msg.event)))
137
- if (!subscribedEvent) throw new Error(`No handler for incoming message with topic '${_msg.event}' found.`)
138
- _msg.event = subscribedEvent
138
+ if (!subscribedEvent && !this._listenToAll)
139
+ throw new Error(`No handler for incoming message with topic '${_msg.event}' found.`)
140
+ _msg.event = subscribedEvent || _msg.event
139
141
  }
140
142
  return _msg
141
143
  }
@@ -516,12 +516,15 @@ const _select = (cqn, kind, model) => {
516
516
  const _insert = (cqn, kind, model) => {
517
517
  const INSERT = getProp(cqn, 'INSERT')
518
518
  const { url } = _from(getProp(INSERT, 'into'), kind, model)
519
- const body = Array.isArray(INSERT.entries) && INSERT.entries.length === 1 ? INSERT.entries[0] : INSERT.entries
519
+ const body = _copyData(
520
+ Array.isArray(INSERT.entries) && INSERT.entries.length === 1 ? INSERT.entries[0] : INSERT.entries
521
+ )
520
522
  return { method: 'POST', path: url, body }
521
523
  }
522
524
 
523
525
  const _copyData = data => {
524
526
  // only works on flat structures
527
+ if (Array.isArray(data)) return data.map(_copyData)
525
528
  const copied = {}
526
529
  for (const property in data) {
527
530
  copied[property] =
@@ -52,10 +52,9 @@ const formatVal = (val, elementName, csnTarget, kind) => {
52
52
  if (typeof val === 'boolean') return val
53
53
  if (typeof val === 'number') return getSafeNumber(val)
54
54
  if (!csnTarget && typeof val === 'string' && UUID.test(val)) return kind === 'odata-v2' ? `guid'${val}'` : val
55
- const { type } = _getElement(csnTarget, elementName)
56
-
55
+ const element = _getElement(csnTarget, elementName)
57
56
  if (kind === 'odata-v2') {
58
- switch (type) {
57
+ switch (element.type) {
59
58
  case 'cds.Decimal':
60
59
  case 'cds.Integer64':
61
60
  case 'cds.Int64':
@@ -64,20 +63,22 @@ const formatVal = (val, elementName, csnTarget, kind) => {
64
63
  case 'cds.LargeBinary':
65
64
  return `binary'${toBase64url(val)}'`
66
65
  case 'cds.Date':
67
- return `datetime'${val}T00:00:00'`
66
+ return element['@odata.Type'] === 'Edm.DateTimeOffset'
67
+ ? `datetimeoffset'${val}T00:00:00'`
68
+ : `datetime'${val}T00:00:00'`
68
69
  case 'cds.DateTime':
69
- return `datetime'${val}'`
70
+ return element['@odata.Type'] === 'Edm.DateTimeOffset' ? `datetimeoffset'${val}'` : `datetime'${val}'`
70
71
  case 'cds.Time':
71
72
  return `time'${_PT(val.split(':'))}'`
72
73
  case 'cds.Timestamp':
73
- return `datetimeoffset'${val}'`
74
+ return element['@odata.Type'] === 'Edm.DateTime' ? `datetime'${val}'` : `datetimeoffset'${val}'`
74
75
  case 'cds.UUID':
75
76
  return `guid'${val}'`
76
77
  default:
77
78
  return `'${val}'`
78
79
  }
79
80
  } else {
80
- switch (type) {
81
+ switch (element.type) {
81
82
  case 'cds.Binary':
82
83
  case 'cds.LargeBinary':
83
84
  return `binary'${toBase64url(val)}'`
@@ -197,10 +197,7 @@ const RestAdapter = function (srv) {
197
197
 
198
198
  next(err)
199
199
  })
200
-
201
- if (!cds.env.features.rest_error_handler) {
202
- router.use(error) // FIXME: nope -> call next()
203
- }
200
+ router.use(error) // FIXME: nope -> call next()
204
201
 
205
202
  return router
206
203
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "6.2.3",
3
+ "version": "6.3.0",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [