@sap/cds 7.7.3 → 7.8.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 (75) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/lib/auth/ias-auth.js +5 -3
  3. package/lib/auth/jwt-auth.js +4 -2
  4. package/lib/compile/cdsc.js +0 -10
  5. package/lib/compile/for/java.js +9 -5
  6. package/lib/compile/for/lean_drafts.js +1 -1
  7. package/lib/compile/to/edm.js +2 -1
  8. package/lib/compile/to/sql.js +0 -21
  9. package/lib/compile/to/srvinfo.js +13 -4
  10. package/lib/dbs/cds-deploy.js +7 -7
  11. package/lib/env/cds-requires.js +6 -0
  12. package/lib/index.js +4 -3
  13. package/lib/linked/classes.js +151 -88
  14. package/lib/linked/entities.js +28 -23
  15. package/lib/linked/models.js +57 -36
  16. package/lib/linked/types.js +42 -104
  17. package/lib/ql/Whereable.js +3 -3
  18. package/lib/req/context.js +9 -5
  19. package/lib/srv/protocols/hcql.js +2 -1
  20. package/lib/srv/protocols/http.js +7 -7
  21. package/lib/srv/protocols/index.js +31 -13
  22. package/lib/srv/protocols/odata-v4.js +79 -58
  23. package/lib/srv/srv-api.js +7 -6
  24. package/lib/srv/srv-dispatch.js +1 -12
  25. package/lib/srv/srv-tx.js +9 -13
  26. package/lib/utils/cds-utils.js +6 -5
  27. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +11 -8
  28. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +21 -12
  29. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -3
  30. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -7
  31. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +5 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -1
  33. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +3 -7
  34. package/libx/_runtime/cds-services/services/utils/columns.js +6 -3
  35. package/libx/_runtime/cds.js +0 -13
  36. package/libx/_runtime/common/generic/input.js +3 -0
  37. package/libx/_runtime/common/generic/sorting.js +8 -6
  38. package/libx/_runtime/common/i18n/messages.properties +1 -0
  39. package/libx/_runtime/common/utils/cqn.js +5 -0
  40. package/libx/_runtime/common/utils/foreignKeyPropagations.js +7 -1
  41. package/libx/_runtime/common/utils/keys.js +2 -2
  42. package/libx/_runtime/common/utils/resolveView.js +2 -1
  43. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
  44. package/libx/_runtime/common/utils/stream.js +0 -10
  45. package/libx/_runtime/common/utils/template.js +20 -35
  46. package/libx/_runtime/db/Service.js +5 -1
  47. package/libx/_runtime/db/utils/columns.js +1 -1
  48. package/libx/_runtime/fiori/lean-draft.js +14 -2
  49. package/libx/_runtime/messaging/Outbox.js +7 -5
  50. package/libx/_runtime/messaging/kafka.js +266 -0
  51. package/libx/_runtime/messaging/service.js +7 -5
  52. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
  53. package/libx/common/assert/validation.js +1 -1
  54. package/libx/odata/index.js +8 -2
  55. package/libx/odata/middleware/batch.js +340 -0
  56. package/libx/odata/middleware/create.js +43 -46
  57. package/libx/odata/middleware/delete.js +27 -15
  58. package/libx/odata/middleware/error.js +6 -5
  59. package/libx/odata/middleware/metadata.js +16 -15
  60. package/libx/odata/middleware/operation.js +107 -59
  61. package/libx/odata/middleware/parse.js +15 -7
  62. package/libx/odata/middleware/read.js +150 -24
  63. package/libx/odata/middleware/service-document.js +17 -6
  64. package/libx/odata/middleware/stream.js +34 -17
  65. package/libx/odata/middleware/update.js +123 -87
  66. package/libx/odata/parse/afterburner.js +131 -28
  67. package/libx/odata/parse/cqn2odata.js +1 -1
  68. package/libx/odata/parse/grammar.peggy +4 -5
  69. package/libx/odata/parse/multipartToJson.js +163 -0
  70. package/libx/odata/parse/parser.js +1 -1
  71. package/libx/odata/utils/index.js +29 -47
  72. package/libx/odata/utils/path.js +72 -0
  73. package/libx/odata/utils/result.js +123 -20
  74. package/package.json +1 -1
  75. package/server.js +4 -0
@@ -625,7 +625,8 @@ const _getTransitionData = (target, columns, service, skipForbiddenViewCheck) =>
625
625
  * @param service
626
626
  * @param skipForbiddenViewCheck
627
627
  */
628
- const getTransition = (queryTarget, service, skipForbiddenViewCheck) => {
628
+ const getTransition = (queryTarget, service, skipForbiddenViewCheck, event) => {
629
+ if (event) _event = event
629
630
  // Never resolve unknown targets (e.g. for drafts)
630
631
  if (!queryTarget) {
631
632
  return { target: queryTarget, queryTarget, mapping: new Map() }
@@ -42,7 +42,7 @@ const _resolveTarget = (ref, target) => {
42
42
  const element = target.elements[_ref]
43
43
  if (element) return element._target
44
44
 
45
- throw cds.error(`Navigation property '${_ref}' is not defined in type '${target.name}'`, { code: 400 })
45
+ throw cds.error(`Navigation property '${_ref}' is not defined in '${target.name}'`, { code: 400 })
46
46
  }
47
47
 
48
48
  const rewriteExpandAsterisk = (columns, target) => {
@@ -4,8 +4,6 @@ const { SELECT } = cds.ql
4
4
  const { deepCopyArray } = require('./copy')
5
5
  const { getTransition } = require('./resolveView')
6
6
  const { cqn2cqn4sql } = require('./cqn2cqn4sql')
7
- const getTemplate = require('./template')
8
- const templateProcessor = require('./templateProcessor')
9
7
  const { adaptStreamCQN } = require('../../fiori/utils/stream.js')
10
8
  const { isPathToDraft } = require('./cqn')
11
9
 
@@ -116,12 +114,4 @@ const enhanceStreamResult = async (req, query, result, model) => {
116
114
  }
117
115
  }
118
116
 
119
- const pick = element => {
120
- return element['@Core.IsURL'] || element['@Core.MediaType']
121
- }
122
-
123
- const processFn = ({ row, key }) => {
124
- delete row[key]
125
- }
126
-
127
117
  module.exports = { enhanceStreamResult }
@@ -107,9 +107,14 @@ function _getTemplate(model, cache, targetEntity, callbacks, parent = null, _ent
107
107
  } else if (nextTarget) {
108
108
  // For associations and _typed_ structured elements, there's a (cacheable) target,
109
109
  // inline structures must be handled separately.
110
- const subTemplate = _isInlineStructured(element)
111
- ? _getTemplate(model, cache, nextTarget, callbacks, targetEntity, _entityMap, currentPath)
112
- : cache.for(nextTarget, getTemplate(model, callbacks, targetEntity, _entityMap))
110
+ let subTemplate
111
+ if (_isInlineStructured(element))
112
+ subTemplate = _getTemplate(model, cache, nextTarget, callbacks, targetEntity, _entityMap, currentPath)
113
+ else if (cache.has(nextTarget)) subTemplate = cache.get(nextTarget)
114
+ else {
115
+ subTemplate = _getTemplate(model, cache, nextTarget, callbacks, targetEntity, _entityMap)
116
+ cache.set(nextTarget, subTemplate)
117
+ }
113
118
  _addSubTemplate(templateElements, elementName, subTemplate)
114
119
  }
115
120
  }
@@ -117,43 +122,23 @@ function _getTemplate(model, cache, targetEntity, callbacks, parent = null, _ent
117
122
  return template
118
123
  }
119
124
 
120
- const getTemplate =
121
- (model, ...args) =>
122
- (target, cache) =>
123
- _getTemplate(model, cache, target, ...args)
124
-
125
- const getCache = (anything, cache, newCacheFn) => {
126
- let _cached = cache.get(anything)
127
- if (_cached) return _cached
128
-
129
- _cached = (typeof newCacheFn === 'function' && newCacheFn(anything, cache)) || new Map()
130
- _cached.for = (_usecase, _newCacheFn) => getCache(_usecase, _cached, _newCacheFn)
131
- cache.set(anything, _cached)
132
- return _cached
133
- }
134
-
135
125
  module.exports = (usecase, tx, target, ...args) => {
136
- // get model first as it may be added to tx (cf. "_ensureModel") // REVISIT: _ensureModel is gone
126
+ if (!target) return
127
+
128
+ // REVISIT: tx.model === cds.context.model, but keep for usage stability
137
129
  const model = tx.model
138
130
  if (!model) return
139
131
 
140
- // double-check with get target from model
141
- // since target might come from anywhere like via cqn etc
142
- if (!target) return
143
- const root = (model && model.definitions[target.name]) || target
132
+ const root = model.definitions[target.name] || target
144
133
  if (!root) return
145
134
 
146
- // tx could be the service itself -> prefer ApplicationService
147
- // REVISIT: context._tx is not a stable API -> pls do not rely on that
148
- const service = tx.context ? (tx.context.tx && Object.getPrototypeOf(tx.context.tx)) || Object.getPrototypeOf(tx) : tx
149
- if (!service) return
135
+ if (!model._templateCache) Object.defineProperty(model, '_templateCache', { value: new Map() })
136
+ if (!model._templateCache.get(usecase)) model._templateCache.set(usecase, new WeakMap())
150
137
 
151
- // cache templates at service for garbage collection
152
- if (usecase && !service._templateCache) service._templateCache = new Map()
153
- // model can be also a subset from tx
154
-
155
- // if no usecase, don't save cache on the service object
156
- return getCache(usecase, usecase ? service._templateCache : new Map())
157
- .for(model)
158
- .for(root, getTemplate(model, ...args))
138
+ let tmplt = model._templateCache.get(usecase).get(root)
139
+ if (!tmplt) {
140
+ tmplt = _getTemplate(model, model._templateCache.get(usecase), root, ...args)
141
+ model._templateCache.get(usecase).set(root, tmplt)
142
+ }
143
+ return tmplt
159
144
  }
@@ -12,6 +12,10 @@ const queries = require('./query')
12
12
  */
13
13
  const generic = require('./generic')
14
14
 
15
+ // not able to pass in stream method into cds.utils.deprecated for unknown reason
16
+ // workaround: extract and deprecate helper function
17
+ const _streamDeprecation = () => {}
18
+
15
19
  class DatabaseService extends cds.Service {
16
20
  constructor(...args) {
17
21
  super(...args)
@@ -91,7 +95,7 @@ class DatabaseService extends cds.Service {
91
95
  }
92
96
 
93
97
  stream(query) {
94
- cds._logDeprecation('stream method is deprecated and will be removed in upcoming releases!')
98
+ cds.utils.deprecated(_streamDeprecation, { old: 'cds.stream' })()
95
99
  // aynchronous API: cds.stream(query)
96
100
  if (typeof query === 'object') {
97
101
  // eslint-disable-next-line no-async-promise-executor
@@ -22,7 +22,7 @@ const getColumns = (entity, { _4db, onlyKeys, omitStream } = { _4db: true, onlyK
22
22
  // REVISIT!!!
23
23
  const { structs = cds.env.features.ucsn_struct_conversion } = cds.env.effective.odata
24
24
  const { lean_draft } = cds.env.fiori
25
- const elements = lean_draft ? entity.elements : Object.getPrototypeOf(entity.elements) || entity.elements
25
+ const elements = entity.elements
26
26
  for (const elementName in elements) {
27
27
  const element = elements[elementName]
28
28
  if (element['@cds.api.ignore']) continue
@@ -2,7 +2,10 @@ const cds = require('../cds'),
2
2
  { Object_keys } = cds.utils
3
3
  const { getTransition } = require('../common/utils/resolveView')
4
4
  const { getKeyData } = require('./utils/where')
5
- const { getPageSize } = require('../common/generic/paging')
5
+
6
+ const { getPageSize, commonGenericPaging } = require('../common/generic/paging')
7
+ const { handler: commonGenericSorting } = require('../common/generic/sorting')
8
+
6
9
  const LOG = cds.log('fiori|drafts')
7
10
  const original = Symbol('original')
8
11
  const DRAFT_PARAMS = Symbol('draftParams')
@@ -283,6 +286,10 @@ cds.ApplicationService.prototype.handle = async function (req) {
283
286
  }
284
287
 
285
288
  if (req.event === 'READ') {
289
+ // apply paging and sorting on original query for protocol adapters relying on it
290
+ commonGenericPaging(req)
291
+ commonGenericSorting(req)
292
+
286
293
  if (
287
294
  !Object.keys(draftParams).length &&
288
295
  !req.query._target.name?.endsWith('DraftAdministrativeData') &&
@@ -401,6 +408,10 @@ cds.ApplicationService.prototype.handle = async function (req) {
401
408
  req.reject(400, 'Action "draftActivate" can only be called on the root draft entity')
402
409
  }
403
410
 
411
+ if (req.target._etag && !req.headers['if-match'] && !req.headers['if-none-match']) {
412
+ req.reject(428)
413
+ }
414
+
404
415
  const targetDraft = req.target.drafts
405
416
  const cols = expandStarStar(targetDraft)
406
417
 
@@ -528,6 +539,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
528
539
  }
529
540
 
530
541
  req.query = query
542
+
531
543
  return handle(req)
532
544
  }
533
545
 
@@ -761,7 +773,7 @@ const Read = {
761
773
  : [...ownDrafts, ...actives]
762
774
 
763
775
  // runtime sort required
764
- if (ownDrafts.length > 0 && actives.length > 0) {
776
+ if (orderByExpr && ownDrafts.length > 0 && actives.length > 0) {
765
777
  const locale = cds.context.locale.replaceAll('_', '-')
766
778
  const collatorMap = new Map()
767
779
  const elementNamesToSort = orderByExpr.map(orderByExp => orderByExp.ref.join('_'))
@@ -1,13 +1,15 @@
1
1
  const cds = require('../cds')
2
2
 
3
- module.exports = class Outbox extends cds.Service {
3
+ class Outbox extends cds.Service {
4
4
  constructor(...args) {
5
- cds._logDeprecation(
6
- 'Internal class `OutboxService` is deprecated and will be removed. Services are outboxable via config or `cds.outboxed()`.'
7
- )
8
-
9
5
  super(...args)
10
6
 
11
7
  if (this.options.outbox) return cds.outboxed(this)
12
8
  }
13
9
  }
10
+
11
+ module.exports = cds.utils.deprecated(() => Outbox, {
12
+ kind: 'Class',
13
+ old: 'Outbox',
14
+ use: 'config or cds.outboxed() to outbox your service'
15
+ })()
@@ -0,0 +1,266 @@
1
+ const cds = require('../cds')
2
+ // eslint-disable-next-line
3
+ const { Kafka } = require('kafkajs')
4
+
5
+ class KafkaService extends cds.MessagingService {
6
+ async init() {
7
+ await super.init()
8
+
9
+ // We might also support subscribeTopic and publishTopic in the future
10
+ // but we keep it as simple as possible for now.
11
+
12
+ this._topics_in_kafka = new Set()
13
+
14
+ this._cachedKeyFns = new Map()
15
+
16
+ // TODO: Make configurable
17
+ this.appId = appId()
18
+
19
+ if (!this.options.local && !this.options.credentials) {
20
+ throw new Error(
21
+ 'No Kafka credentials found.\n\nHint: You need to bind your application to a Kafa service instance.'
22
+ )
23
+ }
24
+
25
+ const config = this.options.local ? await _getConfigLocal(this) : await _getConfig(this)
26
+ this.client = new Kafka(config)
27
+
28
+ // check for proper credentials
29
+ const producer = this.client.producer()
30
+ await producer.connect()
31
+ await producer.disconnect()
32
+
33
+ cds.once('listening', () => {
34
+ this.startListening()
35
+ })
36
+ }
37
+
38
+ async handle(msg) {
39
+ if (msg.inbound) return super.handle(msg)
40
+ const _msg = this.message4(msg)
41
+ this.LOG._info && this.LOG.info('Emit', { topic: _msg.event })
42
+ if (!this.producer) {
43
+ this.producer = this.client.producer()
44
+ await this.producer.connect()
45
+ }
46
+ // In the future, we might allow to set the topic manually:
47
+ // const topic = _msg.headers?.["@kafka.topic"] ?? this.options.topic;
48
+ const topic = this.options.topic
49
+ let key = _msg.headers['@kafka.key']
50
+
51
+ if (!key) {
52
+ let keyFn = this._cachedKeyFns.get(_msg.event)
53
+ if (keyFn === undefined) {
54
+ // `null` means there is no keyFn for that event
55
+ keyFn = _getKeyFn(_msg.event)
56
+ this._cachedKeyFns.set(_msg.event, keyFn)
57
+ }
58
+ key = keyFn?.(_msg.data)
59
+ }
60
+
61
+ const headers = { ...(_msg.headers || {}) }
62
+ headers['x-sap-cap-effective-topic'] = _msg.event
63
+ delete headers['@kafka.key']
64
+ // delete headers["@kafka.topic"];
65
+ const tenant = cds.context?.tenant
66
+ if (tenant) headers['x-sap-cap-tenant-id'] = tenant
67
+ const message = {
68
+ value: (typeof _msg.data === 'string' && _msg.data) || JSON.stringify(_msg.data),
69
+ headers
70
+ }
71
+ if (key) message.key = key
72
+ await this._ensureTopicsExist([topic])
73
+ const payload = {
74
+ topic,
75
+ messages: [message]
76
+ }
77
+ if (this.LOG._debug) this.LOG.debug('Sending', payload)
78
+ await this.producer.send(payload)
79
+ }
80
+
81
+ async _ensureTopicsExist(topics) {
82
+ const toBeCheckedTopics = topics.filter(t => !this._topics_in_kafka.has(t))
83
+ if (!toBeCheckedTopics.length) return
84
+ const admin = this.client.admin()
85
+ await admin.connect()
86
+
87
+ const existingTopics = await admin.listTopics()
88
+ const missingTopics = toBeCheckedTopics.filter(t => !existingTopics.includes(t))
89
+ if (missingTopics.length) {
90
+ this.LOG._info && this.LOG.info(`Creating topics: ${missingTopics}`)
91
+ await admin.createTopics({ topics: missingTopics.map(t => ({ topic: t })) })
92
+ // Let's just cache used ones
93
+ for (const missingTopic of missingTopics) this._topics_in_kafka.add(missingTopic)
94
+ }
95
+ }
96
+
97
+ async startListening() {
98
+ const consumer = this.client.consumer({ groupId: this.appId })
99
+ await consumer.connect()
100
+
101
+ // In the future, we might allow to support the annotation @kafka.topic
102
+ // but then, we'd need to collect them like this:
103
+ // const topics = []
104
+ // const messaging = cds.env.requires.messaging && await cds.connect.to('messaging')
105
+ // if (messaging) {
106
+ // for (const [_, declared] of messaging._registeredEvents) {
107
+ // for (const [_, event] of this.subscribedTopics) {
108
+ // const kafkaTopic = declared['@kafka.topic']
109
+ // // `this` only knows about the subscribed topics (from `@topic` of fully-qualified name)
110
+ // if (kafkaTopic && (declared['@topic'] === event || declared.name === event)) {
111
+ // topics.push(kafkaTopic)
112
+ // }
113
+ // }
114
+ // }
115
+ // }
116
+ // this.subscribeTopics = this.subscribeTopics.concat(topics)
117
+
118
+ await this._ensureTopicsExist([this.options.topic])
119
+
120
+ this.LOG._info && this.LOG.info(`Subscribe to ${this.options.topic}`)
121
+ await consumer.subscribe({ topics: [this.options.topic] })
122
+ await consumer.run({
123
+ eachMessage: async raw => {
124
+ try {
125
+ const msg = _normalizeIncomingMessage(raw.message.value.toString())
126
+ msg.headers = {}
127
+ for (const header in raw.message.headers || {}) {
128
+ msg.headers[header] = raw.message.headers[header]?.toString()
129
+ }
130
+ msg.event =
131
+ raw.message.headers['x-sap-cap-effective-topic']?.toString() ?? raw.message.headers.type?.toString()
132
+ msg.tenant = raw.message.headers['x-sap-cap-tenant-id']
133
+ if (!msg.event) return
134
+
135
+ await this.tx({ user: cds.User.privileged, tenant: msg.tenant }, tx => tx.emit(msg))
136
+ } catch (e) {
137
+ if (e.code === 'NO_HANDLER_FOUND') return // consume
138
+ this.LOG.error('ERROR occured in asynchronous event processing:', e)
139
+ throw e
140
+ }
141
+ }
142
+ })
143
+ }
144
+ }
145
+
146
+ module.exports = KafkaService
147
+
148
+ // TODO: Make public?
149
+ const appId = require('./common-utils/appId')
150
+
151
+ function _JSONorString(string) {
152
+ try {
153
+ return JSON.parse(string)
154
+ } catch (e) {
155
+ return string
156
+ }
157
+ }
158
+
159
+ function _normalizeIncomingMessage(message) {
160
+ const _payload = _JSONorString(message)
161
+ let data, headers
162
+ if (typeof _payload === 'object' && 'data' in _payload) {
163
+ data = _payload.data
164
+ headers = { ..._payload }
165
+ delete headers.data
166
+ } else {
167
+ data = _payload
168
+ headers = {}
169
+ }
170
+
171
+ return {
172
+ data,
173
+ headers,
174
+ inbound: true
175
+ }
176
+ }
177
+
178
+ function _getDef(topicOrEvent) {
179
+ const found = cds?.model.definitions[topicOrEvent]
180
+ if (found) return found
181
+
182
+ for (const def in cds.model?.definitions) {
183
+ const definition = cds.model.definitions[def]
184
+ if (definition['@topic'] === topicOrEvent) return definition
185
+ }
186
+ }
187
+
188
+ function _getKeyFn(topicOrEvent) {
189
+ const definition = _getDef(topicOrEvent)
190
+ if (!definition) return null
191
+ const keys = []
192
+ for (const el in definition.elements) {
193
+ // definition.keys doesn't seem to work
194
+ const element = definition.elements[el]
195
+ if (element.key) keys.push(element.name)
196
+ }
197
+ if (keys.length) {
198
+ keys.sort()
199
+ return data =>
200
+ JSON.stringify(
201
+ keys.reduce((res, curr) => {
202
+ res[curr] = data[curr]
203
+ return res
204
+ }, {})
205
+ )
206
+ }
207
+ return null
208
+ }
209
+
210
+ async function _getConfig(srv) {
211
+ const caCerts = await _getCaCerts(srv)
212
+ return {
213
+ clientId: srv.appId,
214
+ // logLevel: 4,
215
+ connectionTimeout: 15000,
216
+ authenticationTimeout: 15000,
217
+ brokers: srv.options.credentials.cluster?.['brokers.client_ssl'].split(','),
218
+ ssl: {
219
+ rejectUnauthorized: true,
220
+ ca: caCerts,
221
+ key: srv.options.credentials.clientkey,
222
+ cert: srv.options.credentials.clientcert
223
+ }
224
+ }
225
+ }
226
+
227
+ async function _getConfigLocal() {
228
+ const creds = await fetch('http://localhost:8004/v1/credentials', {
229
+ headers: { encoding: 'utf-8', accept: 'application/json' }
230
+ }).then(r => r.json())
231
+
232
+ const url = 'http://localhost:8005/v1/undefined/token'
233
+ const token = await fetch(url, {
234
+ method: 'POST',
235
+ body: JSON.stringify({ grant_type: 'client_credentials' }),
236
+ headers: {
237
+ accept: 'application/json',
238
+ 'content-type': 'application/json',
239
+ encoding: 'utf-8',
240
+ Authorization: 'Basic ' + Buffer.from(`${creds.username}:${creds.password}`, 'utf-8').toString('base64')
241
+ }
242
+ })
243
+ .then(r => r.json())
244
+ .then(r => r.access_token)
245
+ return {
246
+ brokers: ['127.0.0.1:19093', '127.0.0.1:29093', '127.0.0.1:39093'],
247
+ ssl: {
248
+ rejectUnauthorized: false
249
+ },
250
+ sasl: {
251
+ mechanism: 'plain',
252
+ username: creds.username,
253
+ password: token
254
+ }
255
+ }
256
+ }
257
+
258
+ async function _getCaCerts(srv) {
259
+ const certCurrent = await fetch(srv.options.credentials.urls.cert_current).then(r => r.text())
260
+ try {
261
+ const certNext = await fetch(srv.options.credentials.urls.cert_next).then(r => r.text())
262
+ return [certCurrent, certNext]
263
+ } catch (_e) {
264
+ return [certCurrent]
265
+ }
266
+ }
@@ -6,12 +6,14 @@ const appId = require('./common-utils/appId')
6
6
 
7
7
  const _topic = declared => declared['@topic'] || declared.name
8
8
 
9
+ const _stripTopicPrefix = event => event.replace(/topic:/, '')
10
+
9
11
  const _warnAndStripTopicPrefix = event => {
10
- if (event.startsWith('topic:')) {
11
- cds._logDeprecation('The topic prefix `topic:` is deprecated and has no effect. Please remove it.')
12
- // backwards compatibility
13
- event = event.replace(/topic:/, '')
14
- }
12
+ if (event.startsWith('topic:'))
13
+ event = cds.utils.deprecated(_stripTopicPrefix, {
14
+ kind: '',
15
+ old: 'Unnecessary topic prefix "topic:"'
16
+ })(event)
15
17
  return event
16
18
  }
17
19
 
@@ -2,6 +2,7 @@ const cds = require('../cds')
2
2
 
3
3
  function sqliteConvertDraftAdminPathExpression(req) {
4
4
  if (req.query?.SELECT?.from?.SET) return // not supported, won't happen in draft
5
+ if (req.query?.SELECT?.from?.args) return // not supported, won't happen in draft
5
6
  if (
6
7
  !req.query?.SELECT ||
7
8
  !req.query?._target?.name?.endsWith('.drafts') ||
@@ -64,7 +64,7 @@ const checkMandatory = (v, ele, errs, path, k) => {
64
64
  // do not complain about ???
65
65
  if (ele.parent?.query?.SELECT?.columns?.find(col => _isNavigationColumn(col, ele.name))) return
66
66
 
67
- if (!ele.default && _isNotFilled(v)) {
67
+ if (_isNotFilled(v)) {
68
68
  const target = getTarget(path, k)
69
69
  errs.push(new cds.error('ASSERT_NOT_NULL', { target, statusCode: 400, code: '400' }))
70
70
  }
@@ -28,7 +28,9 @@ const strict = {
28
28
  minute: 1,
29
29
  second: 1,
30
30
  time: 1,
31
- now: 1
31
+ now: 1,
32
+ round: 1,
33
+ date: 1
32
34
  }
33
35
  }
34
36
 
@@ -54,6 +56,7 @@ const enhanceCqn = (cqn, options) => {
54
56
  if (cqn.__target) query.__target = cqn.__target
55
57
  if (cqn._propertyAccess)
56
58
  Object.defineProperty(query, '_propertyAccess', { value: cqn._propertyAccess, enumerable: false })
59
+
57
60
  return query
58
61
  }
59
62
 
@@ -83,6 +86,9 @@ module.exports = {
83
86
  }
84
87
 
85
88
  let offset = err.location && err.location.start.offset
89
+ if (!offset && err.statusCode && err.message) {
90
+ throw err
91
+ }
86
92
  if (options.baseUrl) {
87
93
  // we need to add the number of chars from base url to the offset
88
94
  offset += options.baseUrl.length
@@ -94,7 +100,7 @@ module.exports = {
94
100
  throw err
95
101
  }
96
102
 
97
- //cqn is an array, if concat is used
103
+ // cqn is an array, if concat is used
98
104
  if (Array.isArray(cqn)) {
99
105
  for (let i = 0; i < cqn.length; i++) {
100
106
  cqn[i] = enhanceCqn(cqn[i], options)