@sap/cds 6.1.2 → 6.1.3

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 CHANGED
@@ -4,6 +4,21 @@
4
4
  - The format is based on [Keep a Changelog](http://keepachangelog.com/).
5
5
  - This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## Version 6.1.3 - 2022-09-13
8
+
9
+ ### Added
10
+
11
+ - Configuration to change maximum body size in bytes for remote requests: `cds.env.remote.max_body_length: 1000` sets it to 1 MB
12
+
13
+ ### Changed
14
+
15
+ - For structured input, foreign keys are generated as non-enumerable properties on application-service layer
16
+
17
+ ### Fixed
18
+
19
+ - Deep insert/update/upsert requests where the key of an association is provided in a structured format will not be rejected anymore if the target has default or mandatory fields
20
+ - For some configurations, mtxs services were bootstrapped twice
21
+
7
22
  ## Version 6.1.2 - 2022-09-05
8
23
 
9
24
  ### Fixed
@@ -4,8 +4,6 @@ const {
4
4
  Request,
5
5
  ql: { SELECT }
6
6
  } = cds
7
- const LOG = cds.log()
8
- const { normalizeError } = require('../../../../common/error/frontend')
9
7
 
10
8
  const { getDeepSelect, getSimpleSelectCQN } = require('./handlerUtils')
11
9
  const { hasDeepUpdate } = require('../../../../common/composition/update')
@@ -155,7 +155,9 @@ const _skipToMany = (entity, prop) => {
155
155
  }
156
156
 
157
157
  const _iteratePropsInNewEntry = (newEntry, keys, result, oldEntry, entity) => {
158
- for (const prop in newEntry) {
158
+ // On app-service layer, generated foreign keys are not enumerable,
159
+ // include them here too.
160
+ for (const prop of Object.getOwnPropertyNames(newEntry)) {
159
161
  if (keys.includes(prop)) {
160
162
  _addKeysToResult(result, prop, newEntry, oldEntry)
161
163
  continue
@@ -277,6 +277,9 @@ const checkIfAssocDeep = (element, value, req) => {
277
277
  // managed to one
278
278
  Object.keys(value).forEach(prop => {
279
279
  if (typeof value[prop] !== 'object') {
280
+ const foreignKey = element._foreignKeys.find(fk => fk.childElement.name === prop)
281
+ if (foreignKey) return
282
+
280
283
  const key = element.keys.find(element => element.ref[0] === prop)
281
284
  if (key) return
282
285
 
@@ -11,6 +11,7 @@ const cds = require('../../cds')
11
11
  const LOG = cds.log('app')
12
12
  const { enrichDataWithKeysFromWhere } = require('../utils/keys')
13
13
  const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
14
+ const propagateForeignKeys = require('../utils/propagateForeignKeys')
14
15
  const { checkInputConstraints, assertTargets } = require('../../cds-services/util/assert')
15
16
  const getTemplate = require('../utils/template')
16
17
  const templateProcessor = require('../utils/templateProcessor')
@@ -126,6 +127,11 @@ const _processCategory = (req, category, value, elementInfo, assertMap) => {
126
127
  const { row, key, element, isRoot } = elementInfo
127
128
  category = _getSimpleCategory(category)
128
129
 
130
+ if (category === 'propagateForeignKeys') {
131
+ propagateForeignKeys(key, row, element._foreignKeys, element.isComposition, { enumerable: false })
132
+ return
133
+ }
134
+
129
135
  // remember mandatory
130
136
  if (category === 'mandatory') {
131
137
  value.mandatory = true
@@ -186,6 +192,11 @@ const _pick = element => {
186
192
  // collect actions to apply
187
193
  const categories = []
188
194
 
195
+ // REVISIT: element._foreignKeys.length seems to be a very broad check
196
+ if (element.isAssociation && element._foreignKeys.length) {
197
+ categories.push({ category: 'propagateForeignKeys' })
198
+ }
199
+
189
200
  if (element['@assert.range'] || element['@assert.enum'] || element['@assert.format']) {
190
201
  categories.push('assert')
191
202
  }
@@ -241,7 +252,10 @@ async function _handler(req) {
241
252
  if (!req.query) return // FIXME: the code below expects req.query to be defined
242
253
  if (!req.target) return
243
254
 
244
- const template = getTemplate('app-input', this, req.target, { pick: _pick })
255
+ const template = getTemplate('app-input', this, req.target, {
256
+ pick: _pick,
257
+ ignore: element => element._isAssociationStrict
258
+ })
245
259
  if (template.elements.size === 0) return
246
260
 
247
261
  const errors = []
@@ -301,7 +315,8 @@ const _processActionFunctionRow = (row, param, key, errors, event, service) => {
301
315
 
302
316
  // structured
303
317
  const template = getTemplate('app-input-operation', service, param, {
304
- pick: _pick
318
+ pick: _pick,
319
+ ignore: element => element._isAssociationStrict
305
320
  })
306
321
 
307
322
  if (template && template.elements.size) {
@@ -64,7 +64,10 @@ function _handler(req) {
64
64
  const { elements } = req.target
65
65
  for (const k in req.data) if (k in elements && elements[k]['@Core.MediaType']) return
66
66
 
67
- const template = getTemplate('app-put', this, req.target, { pick: _pick })
67
+ const template = getTemplate('app-put', this, req.target, {
68
+ pick: _pick,
69
+ ignore: element => element._isAssociationStrict
70
+ })
68
71
  if (template.elements.size === 0) return
69
72
 
70
73
  // REVISIT: req.data should point into req.query
@@ -1,5 +1,4 @@
1
1
  const cds = require('../../cds')
2
- const LOG = cds.log('app')
3
2
 
4
3
  const _getDateFromQueryOptions = str => {
5
4
  if (str) {
@@ -11,8 +10,6 @@ const _getDateFromQueryOptions = str => {
11
10
 
12
11
  const _isDate = dateStr => !dateStr.includes(':')
13
12
  const _isTimestamp = dateStr => dateStr.includes('.')
14
- const _isWarningRequired = (warning, queryOptions) =>
15
- !warning && queryOptions && (queryOptions['sap-valid-from'] || queryOptions['sap-valid-to'])
16
13
  const _isAsOfNow = queryOptions =>
17
14
  !queryOptions || (!queryOptions['sap-valid-at'] && !queryOptions['sap-valid-to'] && !queryOptions['sap-valid-from'])
18
15
 
@@ -0,0 +1,122 @@
1
+ const cds = require('../../cds')
2
+
3
+ const { prefixForStruct } = require('../../common/utils/csn')
4
+
5
+ const _autoGenerate = e => e && e.isUUID && e.key
6
+
7
+ const _set = (row, value, element, enumerable) => {
8
+ if (!element.parent.elements[element.name]) return // only when in model
9
+ if (!enumerable && element.foreignKeySource) {
10
+ // only for foreign keys
11
+ Object.defineProperty(row, element.name, {
12
+ get() {
13
+ return value
14
+ },
15
+ set(v) {
16
+ // Make sure that it becomes enumerable again if set manually afterwards
17
+ Object.defineProperty(row, element.name, { value: v, configurable: true, enumerable: true })
18
+ },
19
+ enumerable: false,
20
+ configurable: true
21
+ })
22
+ } else {
23
+ row[element.name] = value
24
+ }
25
+ }
26
+
27
+ const _generateParentField = ({ parentElement }, row, enumerable) => {
28
+ if (_autoGenerate(parentElement) && !row[parentElement.name]) {
29
+ _set(row, cds.utils.uuid(), parentElement, enumerable)
30
+ }
31
+ }
32
+
33
+ const _generateChildField = ({ deep, childElement }, childRow, enumerable) => {
34
+ if (deep) {
35
+ _generateChildField(deep.propagation, childRow[deep.targetName], enumerable)
36
+ } else if (_autoGenerate(childElement) && childRow && !childRow[childElement.name]) {
37
+ _set(childRow, cds.utils.uuid(), childElement, enumerable)
38
+ }
39
+ }
40
+
41
+ const _getNestedVal = (row, prefix) => {
42
+ let val = row
43
+ const splitted = prefix.split('_')
44
+ splitted.pop() // remove last `_`
45
+ let k = ''
46
+
47
+ while (splitted.length > 0) {
48
+ k += splitted.shift()
49
+ if (k in val) {
50
+ val = val[k]
51
+ k = ''
52
+ } else {
53
+ k += '_'
54
+ }
55
+ }
56
+
57
+ return val
58
+ }
59
+
60
+ const _propagateToChild = ({ parentElement, childElement, parentFieldValue }, row, childRow, enumerable) => {
61
+ if (!childElement || !childElement.parent.elements[childElement.name]) return
62
+ if (parentElement) {
63
+ const prefix = prefixForStruct(parentElement)
64
+ if (prefix) {
65
+ const nested = _getNestedVal(row, prefix)
66
+ _set(childRow, nested[parentElement.name], childElement, enumerable)
67
+ } else {
68
+ _set(childRow, row[parentElement.name], childElement, enumerable)
69
+ }
70
+ } else if (parentFieldValue !== undefined) {
71
+ _set(childRow, parentFieldValue, childElement, enumerable)
72
+ }
73
+ }
74
+
75
+ const _propagateToParent = ({ parentElement, childElement, deep }, childRow, row, enumerable) => {
76
+ if (deep) {
77
+ _propagateToParent(deep.propagation, childRow[deep.targetName], childRow, enumerable)
78
+ }
79
+ if (parentElement && childElement && childRow && childElement.name in childRow) {
80
+ _set(row, childRow[childElement.name], parentElement, enumerable)
81
+ }
82
+ }
83
+
84
+ module.exports = (
85
+ tKey,
86
+ row,
87
+ foreignKeyPropagations,
88
+ isCompositionEffective,
89
+ { deleteAssocs = false, enumerable = true } = {}
90
+ ) => {
91
+ if (!row || !(tKey in row)) return
92
+ if (row[tKey] === null) {
93
+ for (const foreignKeyPropagation of foreignKeyPropagations) {
94
+ if (!foreignKeyPropagation.fillChild) {
95
+ _set(row, null, foreignKeyPropagation.parentElement, enumerable)
96
+ }
97
+ }
98
+ if (deleteAssocs && !isCompositionEffective) delete row[tKey]
99
+ return
100
+ }
101
+
102
+ const childRows = Array.isArray(row[tKey]) ? row[tKey] : [row[tKey]]
103
+
104
+ for (const childRow of childRows) {
105
+ if (!childRow) return
106
+
107
+ for (const foreignKeyPropagation of foreignKeyPropagations) {
108
+ if (foreignKeyPropagation.fillChild) {
109
+ // propagate or generate in parent
110
+ const pk = foreignKeyPropagation.parentElement && foreignKeyPropagation.parentElement.name
111
+ if (pk && !(pk in row)) _propagateToParent(foreignKeyPropagation, childRow, row, enumerable)
112
+ if (!(pk in row)) _generateParentField(foreignKeyPropagation, row, enumerable)
113
+
114
+ if (isCompositionEffective) _propagateToChild(foreignKeyPropagation, row, childRow, enumerable)
115
+ } else {
116
+ _generateChildField(foreignKeyPropagation, childRow, enumerable)
117
+ _propagateToParent(foreignKeyPropagation, childRow, row, enumerable)
118
+ }
119
+ }
120
+ }
121
+ if (deleteAssocs && !isCompositionEffective) delete row[tKey]
122
+ }
@@ -67,7 +67,7 @@ const _getNextTarget = (model, element, currentPath = []) => {
67
67
  * @param {object} targetEntity The target entity which needs to be traversed
68
68
  * @param {object} callbacks
69
69
  * @param {function} callbacks.pick Callback function to pick elements. If it returns a truthy value, the element will be picked. The returned value is part of the template.
70
- * @param {function} callbacks.ignore Callback function to ignore elements. If it returns a truthy value, the element will be ignored.
70
+ * @param {function} callbacks.ignore Callback function to ignore the target of an element. If it returns a truthy value, the element's target will be ignored.
71
71
  * @param {object} [parent=null] The parent entity
72
72
  * @param {Map} [_entityMap] This parameter is an implementation side-effect — don't use it
73
73
  * @param {array} [targetPath=[]]
@@ -92,8 +92,6 @@ function _getTemplate(model, cache, targetEntity, callbacks, parent = null, _ent
92
92
 
93
93
  for (const elementName in elements) {
94
94
  const element = elements[elementName]
95
- if (ignore && ignore(element, targetEntity, parent)) continue
96
-
97
95
  _pick(pick, element, targetEntity, parent, templateElements, elementName)
98
96
 
99
97
  if (element.items) {
@@ -101,6 +99,7 @@ function _getTemplate(model, cache, targetEntity, callbacks, parent = null, _ent
101
99
  }
102
100
 
103
101
  const { nextTargetName, nextTarget } = _getNextTarget(model, element, currentPath)
102
+ if (ignore && ignore(element)) continue
104
103
  const nextTargetCached = _entityMap.get(nextTargetName)
105
104
 
106
105
  if (nextTargetCached) {
@@ -14,7 +14,7 @@ const cds = require('../../cds')
14
14
  const normalizeTimeData = require('../utils/normalizeTimeData')
15
15
 
16
16
  const { enrichDataWithKeysFromWhere } = require('../../common/utils/keys')
17
- const propagateForeignKeys = require('../utils/propagateForeignKeys')
17
+ const propagateForeignKeys = require('../../common/utils/propagateForeignKeys')
18
18
  const getTemplate = require('../../common/utils/template')
19
19
  const templateProcessor = require('../../common/utils/templateProcessor')
20
20
 
@@ -32,8 +32,8 @@ const _processComplexCategory = ({ row, key, val, category, req, element }) => {
32
32
  category = category.category
33
33
 
34
34
  // propagate keys
35
- if (category === 'propagateForeignKeys' && key in row) {
36
- propagateForeignKeys(key, row, element._foreignKeys, element.isComposition)
35
+ if (category === 'propagateForeignKeys') {
36
+ propagateForeignKeys(key, row, element._foreignKeys, element.isComposition, { deleteAssocs: true })
37
37
  return
38
38
  }
39
39
 
@@ -1,11 +1,9 @@
1
1
  const cds = require('../../cds')
2
- const { INSERT, SELECT, UPDATE } = cds.ql
2
+ const { INSERT, UPDATE } = cds.ql
3
3
 
4
4
  const onDraftActivate = require('./activate')._handler
5
5
  const { isNavigationToMany } = require('../utils/req')
6
- const { getKeysCondition } = require('../utils/where')
7
6
  const { ensureDraftsSuffix } = require('../utils/handler')
8
- const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
9
7
 
10
8
  const _getUpdateDraftAdminCQN = ({ user, timestamp }, draftUUID) => {
11
9
  return UPDATE('DRAFT.DraftAdministrativeData')
@@ -1,13 +1,7 @@
1
1
  const cds = require('../../cds')
2
2
  const { UPDATE, SELECT } = cds.ql
3
3
 
4
- const {
5
- getUpdateDraftAdminCQN,
6
- removeDraftUUIDIfNecessary,
7
- ensureDraftsSuffix,
8
- ensureNoDraftsSuffix,
9
- addColumnAlias
10
- } = require('../utils/handler')
4
+ const { getUpdateDraftAdminCQN, ensureDraftsSuffix, ensureNoDraftsSuffix, addColumnAlias } = require('../utils/handler')
11
5
  const { getKeysCondition } = require('../utils/where')
12
6
  const { getColumns } = require('../../cds-services/services/utils/columns')
13
7
  const { DRAFT_COLUMNS_CQN } = require('../../common/constants/draft')
@@ -24,8 +24,8 @@ const _sanitizeHeaders = headers => {
24
24
 
25
25
  const _executeHttpRequest = async ({ requestConfig, destination, destinationOptions, jwt }) => {
26
26
  const { executeHttpRequestWithOrigin } = cloudSdk()
27
-
28
27
  const destinationName = typeof destination === 'string' && destination
28
+
29
29
  if (destinationName) {
30
30
  destination = { destinationName, ...(resolveDestinationOptions(destinationOptions, jwt) || {}) }
31
31
  } else if (destination.forwardAuthToken) {
@@ -59,7 +59,13 @@ const _executeHttpRequest = async ({ requestConfig, destination, destinationOpti
59
59
 
60
60
  // cloud sdk requires a new mechanism to differentiate the priority of headers
61
61
  // "custom" keeps the highest priority as before
62
- requestConfig = { ...requestConfig, headers: { custom: { ...requestConfig.headers } } }
62
+ requestConfig = {
63
+ ...(cds.env?.remote?.max_body_length && { maxBodyLength: cds.env.remote.max_body_length }),
64
+ ...requestConfig,
65
+ headers: {
66
+ custom: { ...requestConfig.headers }
67
+ }
68
+ }
63
69
 
64
70
  return executeHttpRequestWithOrigin(destination, requestConfig, requestOptions)
65
71
  }
@@ -145,10 +151,12 @@ function _defineProperty(obj, property, value) {
145
151
  const map = (..._) => _defineProperty(_map.call(obj, ..._), property, value)
146
152
  props.map = { value: map, enumerable: false, configurable: true, writable: true }
147
153
  }
154
+
148
155
  props[property] = { value: value, enumerable: false, configurable: true, writable: true }
149
156
  for (const prop in props) {
150
157
  Object.defineProperty(obj, prop, props[prop])
151
158
  }
159
+
152
160
  return obj
153
161
  }
154
162
 
@@ -156,14 +164,17 @@ function _normalizeMetadata(prefix, data, results) {
156
164
  const target = results !== undefined ? results : data
157
165
  if (typeof target !== 'object' || target === null) return target
158
166
  const metadataKeys = Object.keys(data).filter(k => prefix.test(k))
167
+
159
168
  for (const k of metadataKeys) {
160
169
  const $ = k.replace(prefix, '$')
161
170
  _defineProperty(target, $, data[k])
162
171
  delete target[k]
163
172
  }
173
+
164
174
  if (Array.isArray(target)) {
165
175
  return target.map(row => _normalizeMetadata(prefix, row))
166
176
  }
177
+
167
178
  // check properties for all and prop.results for odata v2
168
179
  for (const [key, value] of Object.entries(target)) {
169
180
  if (value && typeof value === 'object') {
@@ -171,6 +182,7 @@ function _normalizeMetadata(prefix, data, results) {
171
182
  target[key] = _normalizeMetadata(prefix, value, nestedResults)
172
183
  }
173
184
  }
185
+
174
186
  return target
175
187
  }
176
188
  const _getPurgedRespActionFunc = (data, returnType) => {
@@ -181,6 +193,7 @@ const _getPurgedRespActionFunc = (data, returnType) => {
181
193
  return data[key]
182
194
  }
183
195
  }
196
+
184
197
  return data
185
198
  }
186
199
 
@@ -198,6 +211,7 @@ const _purgeODataV2 = (data, target, returnType, reqHeaders) => {
198
211
  ieee754Compatible,
199
212
  exponentialDecimals
200
213
  )
214
+
201
215
  return _normalizeMetadata(/^__/, data, convertedResponse)
202
216
  }
203
217
 
@@ -217,6 +231,7 @@ const _getSanitizedError = (e, reqOptions, options = { suppressRemoteResponseBod
217
231
  url: e.config ? e.config.baseURL + e.config.url : reqOptions.url,
218
232
  headers: e.config ? e.config.headers : reqOptions.headers
219
233
  }
234
+
220
235
  if (options.batchRequest) {
221
236
  e.request.body = reqOptions.data
222
237
  }
@@ -227,9 +242,11 @@ const _getSanitizedError = (e, reqOptions, options = { suppressRemoteResponseBod
227
242
  statusText: e.response.statusText,
228
243
  headers: e.response.headers
229
244
  }
245
+
230
246
  if (e.response.data && !options.suppressRemoteResponseBody) {
231
247
  response.body = e.response.data
232
248
  }
249
+
233
250
  e.response = response
234
251
  }
235
252
 
@@ -270,12 +287,7 @@ const run = async (
270
287
  response = await _executeHttpRequest({ requestConfig, destination, destinationOptions, jwt })
271
288
  } catch (e) {
272
289
  // > axios received status >= 400 -> gateway error
273
- const msg =
274
- (e.response &&
275
- e.response.data &&
276
- e.response.data.error &&
277
- ((e.response.data.error.message && e.response.data.error.message.value) || e.response.data.error.message)) ||
278
- e.message
290
+ const msg = e?.response?.data?.error?.message?.value ?? e?.response?.data?.error?.message ?? e.message
279
291
  e.message = msg ? 'Error during request to remote service: \n' + msg : 'Request to remote service failed.'
280
292
 
281
293
  const sanitizedError = _getSanitizedError(e, requestConfig, {
@@ -284,7 +296,6 @@ const run = async (
284
296
 
285
297
  const err = Object.assign(new Error(e.message), { statusCode: 502, reason: sanitizedError })
286
298
  LOG._warn && LOG.warn(err)
287
-
288
299
  throw err
289
300
  }
290
301
 
@@ -312,7 +323,6 @@ const run = async (
312
323
  })
313
324
 
314
325
  LOG._warn && LOG.warn(err)
315
-
316
326
  throw err
317
327
  }
318
328
 
@@ -331,6 +341,7 @@ const run = async (
331
341
  if (responseDataSplitted[1].startsWith('HTTP/1.1 2')) {
332
342
  response.data = contentJSON
333
343
  }
344
+
334
345
  if (responseDataSplitted[1].startsWith('HTTP/1.1 4') || responseDataSplitted[1].startsWith('HTTP/1.1 5')) {
335
346
  const innerError = contentJSON.error || contentJSON
336
347
  innerError.status = Number(responseDataSplitted[1].match(/HTTP.*(\d{3})/m)[1])
@@ -357,6 +368,7 @@ const run = async (
357
368
  }
358
369
  return _purgeODataV4(response.data)
359
370
  }
371
+
360
372
  return response.data
361
373
  }
362
374
 
@@ -368,6 +380,7 @@ const getJwt = req => {
368
380
  return token[1]
369
381
  }
370
382
  }
383
+
371
384
  return null
372
385
  }
373
386
 
@@ -382,9 +395,11 @@ const _cqnToReqOptions = (query, service, req) => {
382
395
  .replace(/\( /g, '(')
383
396
  .replace(/ \)/g, ')')
384
397
  }
398
+
385
399
  if (queryObject.method !== 'GET' && queryObject.method !== 'HEAD') {
386
400
  reqOptions.data = kind === 'odata-v2' ? convertV2PayloadData(queryObject.body, req.target) : queryObject.body
387
401
  }
402
+
388
403
  return reqOptions
389
404
  }
390
405
 
@@ -395,9 +410,11 @@ const _stringToReqOptions = (query, data, target) => {
395
410
  method: cleanQuery.substring(0, blankIndex).toUpperCase(),
396
411
  url: encodeURI(formatPath(cleanQuery.substring(blankIndex, cleanQuery.length).trim()))
397
412
  }
413
+
398
414
  if (data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD') {
399
415
  reqOptions.data = this.kind === 'odata-v2' ? Object.assign({}, convertV2PayloadData(data, target)) : data
400
416
  }
417
+
401
418
  return reqOptions
402
419
  }
403
420
 
@@ -412,10 +429,12 @@ const _pathToReqOptions = (method, path, data, target) => {
412
429
  // normalize in case parts[2] already starts with /
413
430
  url = url.replace(/^\/\//, '/')
414
431
  }
432
+
415
433
  const reqOptions = { method, url }
416
434
  if (data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD') {
417
435
  reqOptions.data = this.kind === 'odata-v2' ? Object.assign({}, convertV2PayloadData(data, target)) : data
418
436
  }
437
+
419
438
  return reqOptions
420
439
  }
421
440
 
@@ -1,10 +1,9 @@
1
1
  // const cds = require('../../_runtime/cds')
2
2
  const getTemplate = require('../../_runtime/common/utils/template')
3
3
  const templateProcessor = require('../../_runtime/common/utils/templateProcessor')
4
- const { checkStaticElementByKey, assertNotNullError } = require('../../_runtime/cds-services/util/assert')
4
+ const { checkStaticElementByKey } = require('../../_runtime/cds-services/util/assert')
5
5
  const { MULTIPLE_ERRORS } = require('../../_runtime/common/error/constants')
6
6
 
7
-
8
7
  //
9
8
  // REVISIT: We need to decipher what we are doing here...
10
9
  //
@@ -20,7 +19,7 @@ const _picker = () => {
20
19
  }
21
20
 
22
21
  const _processorFn = errors => {
23
- return ({ row, key, plain: categories, target, element }) => {
22
+ return ({ row, key, plain: categories, target }) => {
24
23
  // REVISIT move validation to generic asserter => see PR 717
25
24
  if (categories['static_validation'] && row[key] != null) {
26
25
  const validations = checkStaticElementByKey(target, key, row[key])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "6.1.2",
3
+ "version": "6.1.3",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -1,5 +1,5 @@
1
+ // using { cds.xt.TAR } from './model-provider'; //> IMPORTANT: don't add this as it will cause services loaded twice
1
2
  using { cds.xt.Extensions } from './extensions';
2
- using { cds.xt.TAR } from './model-provider';
3
3
 
4
4
  @protocol: 'rest'
5
5
  @(requires : 'authenticated-user')
@@ -8,7 +8,8 @@ service cds.xt.ExtensibilityService @(path:'/-/cds/extensibility', impl:'@sap/cd
8
8
  // TODO: async jobs
9
9
 
10
10
  type ActivationLevel : Extensions:activated;
11
- type CSN: String; // REVISIT: should reuse cds.xt.CSN
11
+ type TAR : LargeBinary;
12
+ type CSN : String; // REVISIT: should reuse cds.xt.CSN
12
13
  type CSN_OR_CDL: String;
13
14
 
14
15
  // UIFLEX API
@@ -46,7 +47,7 @@ service cds.xt.ExtensibilityService @(path:'/-/cds/extensibility', impl:'@sap/cd
46
47
 
47
48
  @(requires : ['cds.ExtensionDeveloper'])
48
49
  action push (
49
- extension : TAR,
50
+ extension : LargeBinary, // REVISIT: Using TAR here leads to a strange type check failure
50
51
  tag : Extensions:tag
51
52
  // activate : ActivationLevel
52
53
  );
package/srv/mtx.js CHANGED
@@ -8,19 +8,28 @@ class MTXServices extends cds.Service { async init(){
8
8
  }
9
9
  // else...
10
10
  DEBUG && DEBUG ('bootstrapping MTX services...')
11
+ let defs = cds.model.definitions
11
12
  let sources = []
12
- if (cds.requires.multitenancy) {
13
- if (!('cds.xt.DeploymentService' in cds.requires)) {
14
- sources.push('@sap/cds-mtxs/srv/deployment-service')
15
- if (!('cds.xt.SaasProvisioningService' in cds.requires)) sources.push('@sap/cds-mtxs/srv/cf/saas-provisioning-service')
16
- }
13
+
14
+ if (cds.requires.multitenancy && !('cds.xt.SaasProvisioningService' in defs)) {
15
+ sources.push('@sap/cds-mtxs/srv/cf/saas-provisioning-service')
16
+ sources.push('@sap/cds/srv/model-provider')
17
+ }
18
+
19
+ if (cds.requires.multitenancy && !('cds.xt.DeploymentService' in defs)) {
20
+ sources.push('@sap/cds-mtxs/srv/deployment-service')
21
+ sources.push('@sap/cds/srv/model-provider')
17
22
  }
18
- if (cds.requires.extensibility) {
19
- if (!('cds.xt.ExtensibilityService' in cds.requires)) sources.push('@sap/cds/srv/extensibility-service')
23
+
24
+ if (cds.requires.extensibility && !('cds.xt.ExtensibilityService' in defs)) {
25
+ sources.push('@sap/cds/srv/extensibility-service')
26
+ sources.push('@sap/cds/srv/model-provider')
20
27
  }
21
- if (cds.requires.toggles) {
22
- if (!('cds.xt.ModelProviderService' in cds.requires)) sources.push('@sap/cds/srv/model-provider')
28
+
29
+ if (cds.requires.toggles && !('cds.xt.ModelProviderService' in defs)) {
30
+ sources.push('@sap/cds/srv/model-provider')
23
31
  }
32
+
24
33
  let models = cds.resolve(sources); if (!models) return
25
34
  let base = cds.model.$sources; models = models.filter(m => !base.includes(m))
26
35
  if (models.length) return cds.serve(models).in(cds.app)
@@ -1,93 +0,0 @@
1
- const cds = require('../../cds')
2
-
3
- const { prefixForStruct } = require('../../common/utils/csn')
4
-
5
- const _autoGenerate = e => e && e.isUUID && e.key
6
-
7
- const _generateParentField = ({ parentElement }, row) => {
8
- if (_autoGenerate(parentElement) && !row[parentElement.name]) {
9
- row[parentElement.name] = cds.utils.uuid()
10
- }
11
- }
12
-
13
- const _generateChildField = ({ deep, childElement }, childRow) => {
14
- if (deep) {
15
- _generateChildField(deep.propagation, childRow[deep.targetName])
16
- } else if (_autoGenerate(childElement) && childRow && !childRow[childElement.name]) {
17
- childRow[childElement.name] = cds.utils.uuid()
18
- }
19
- }
20
-
21
- const _getNestedVal = (row, prefix) => {
22
- let val = row
23
- const splitted = prefix.split('_')
24
- splitted.pop() // remove last `_`
25
- let k = ''
26
-
27
- while (splitted.length > 0) {
28
- k += splitted.shift()
29
- if (k in val) {
30
- val = val[k]
31
- k = ''
32
- } else {
33
- k += '_'
34
- }
35
- }
36
-
37
- return val
38
- }
39
-
40
- const _propagateToChid = ({ parentElement, childElement, parentFieldValue }, row, childRow) => {
41
- if (!childElement) return
42
- if (parentElement) {
43
- const prefix = prefixForStruct(parentElement)
44
- if (prefix) {
45
- const nested = _getNestedVal(row, prefix)
46
- childRow[childElement.name] = nested[parentElement.name]
47
- } else {
48
- childRow[childElement.name] = row[parentElement.name]
49
- }
50
- } else if (parentFieldValue !== undefined) {
51
- childRow[childElement.name] = parentFieldValue
52
- }
53
- }
54
-
55
- const _propagateToParent = ({ parentElement, childElement, deep }, childRow, row) => {
56
- if (deep) {
57
- _propagateToParent(deep.propagation, childRow[deep.targetName], childRow)
58
- }
59
- if (parentElement && childElement && childRow && childElement.name in childRow) {
60
- row[parentElement.name] = childRow[childElement.name]
61
- }
62
- }
63
-
64
- module.exports = (tKey, row, foreignKeyPropagations, isCompositionEffective) => {
65
- if (row[tKey] === null) {
66
- for (const foreignKeyPropagation of foreignKeyPropagations) {
67
- if (!foreignKeyPropagation.fillChild) row[foreignKeyPropagation.parentElement.name] = null
68
- }
69
- if (!isCompositionEffective) delete row[tKey]
70
- return
71
- }
72
-
73
- const childRows = Array.isArray(row[tKey]) ? row[tKey] : [row[tKey]]
74
-
75
- for (const childRow of childRows) {
76
- if (!childRow) return
77
-
78
- for (const foreignKeyPropagation of foreignKeyPropagations) {
79
- if (foreignKeyPropagation.fillChild) {
80
- // propagate or generate in parent
81
- const pk = foreignKeyPropagation.parentElement && foreignKeyPropagation.parentElement.name
82
- if (pk && !(pk in row)) _propagateToParent(foreignKeyPropagation, childRow, row)
83
- if (!(pk in row)) _generateParentField(foreignKeyPropagation, row)
84
-
85
- if (isCompositionEffective) _propagateToChid(foreignKeyPropagation, row, childRow)
86
- } else {
87
- _generateChildField(foreignKeyPropagation, childRow)
88
- _propagateToParent(foreignKeyPropagation, childRow, row)
89
- }
90
- }
91
- }
92
- if (!isCompositionEffective) delete row[tKey]
93
- }