@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.
- package/CHANGELOG.md +31 -1
- package/lib/auth/ias-auth.js +5 -3
- package/lib/auth/jwt-auth.js +4 -2
- package/lib/compile/cdsc.js +0 -10
- package/lib/compile/for/java.js +9 -5
- package/lib/compile/for/lean_drafts.js +1 -1
- package/lib/compile/to/edm.js +2 -1
- package/lib/compile/to/sql.js +0 -21
- package/lib/compile/to/srvinfo.js +13 -4
- package/lib/dbs/cds-deploy.js +7 -7
- package/lib/env/cds-requires.js +6 -0
- package/lib/index.js +4 -3
- package/lib/linked/classes.js +151 -88
- package/lib/linked/entities.js +28 -23
- package/lib/linked/models.js +57 -36
- package/lib/linked/types.js +42 -104
- package/lib/ql/Whereable.js +3 -3
- package/lib/req/context.js +9 -5
- package/lib/srv/protocols/hcql.js +2 -1
- package/lib/srv/protocols/http.js +7 -7
- package/lib/srv/protocols/index.js +31 -13
- package/lib/srv/protocols/odata-v4.js +79 -58
- package/lib/srv/srv-api.js +7 -6
- package/lib/srv/srv-dispatch.js +1 -12
- package/lib/srv/srv-tx.js +9 -13
- package/lib/utils/cds-utils.js +6 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +11 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +21 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +3 -7
- package/libx/_runtime/cds-services/services/utils/columns.js +6 -3
- package/libx/_runtime/cds.js +0 -13
- package/libx/_runtime/common/generic/input.js +3 -0
- package/libx/_runtime/common/generic/sorting.js +8 -6
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -0
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +7 -1
- package/libx/_runtime/common/utils/keys.js +2 -2
- package/libx/_runtime/common/utils/resolveView.js +2 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
- package/libx/_runtime/common/utils/stream.js +0 -10
- package/libx/_runtime/common/utils/template.js +20 -35
- package/libx/_runtime/db/Service.js +5 -1
- package/libx/_runtime/db/utils/columns.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +14 -2
- package/libx/_runtime/messaging/Outbox.js +7 -5
- package/libx/_runtime/messaging/kafka.js +266 -0
- package/libx/_runtime/messaging/service.js +7 -5
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
- package/libx/common/assert/validation.js +1 -1
- package/libx/odata/index.js +8 -2
- package/libx/odata/middleware/batch.js +340 -0
- package/libx/odata/middleware/create.js +43 -46
- package/libx/odata/middleware/delete.js +27 -15
- package/libx/odata/middleware/error.js +6 -5
- package/libx/odata/middleware/metadata.js +16 -15
- package/libx/odata/middleware/operation.js +107 -59
- package/libx/odata/middleware/parse.js +15 -7
- package/libx/odata/middleware/read.js +150 -24
- package/libx/odata/middleware/service-document.js +17 -6
- package/libx/odata/middleware/stream.js +34 -17
- package/libx/odata/middleware/update.js +123 -87
- package/libx/odata/parse/afterburner.js +131 -28
- package/libx/odata/parse/cqn2odata.js +1 -1
- package/libx/odata/parse/grammar.peggy +4 -5
- package/libx/odata/parse/multipartToJson.js +163 -0
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +29 -47
- package/libx/odata/utils/path.js +72 -0
- package/libx/odata/utils/result.js +123 -20
- package/package.json +1 -1
- 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
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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
|
-
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
12
|
-
|
|
13
|
-
|
|
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 (
|
|
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
|
}
|
package/libx/odata/index.js
CHANGED
|
@@ -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)
|