@sap/cds 9.1.0 → 9.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/bin/deploy.js +29 -0
  3. package/bin/serve.js +1 -5
  4. package/lib/compile/etc/csv.js +11 -6
  5. package/lib/compile/load.js +8 -5
  6. package/lib/compile/to/hdbtabledata.js +1 -1
  7. package/lib/dbs/cds-deploy.js +0 -31
  8. package/lib/env/cds-env.js +2 -1
  9. package/lib/env/cds-requires.js +3 -0
  10. package/lib/env/schemas/cds-rc.js +4 -0
  11. package/lib/index.js +38 -38
  12. package/lib/log/cds-error.js +12 -11
  13. package/lib/log/format/json.js +1 -1
  14. package/lib/ql/SELECT.js +31 -0
  15. package/lib/ql/UPDATE.js +3 -1
  16. package/lib/ql/resolve.js +1 -1
  17. package/lib/req/context.js +1 -1
  18. package/lib/req/validate.js +16 -17
  19. package/lib/srv/cds.Service.js +18 -28
  20. package/lib/srv/middlewares/auth/ias-auth.js +31 -4
  21. package/lib/srv/middlewares/auth/jwt-auth.js +11 -1
  22. package/lib/srv/srv-models.js +1 -1
  23. package/lib/srv/srv-tx.js +2 -2
  24. package/lib/utils/cds-utils.js +35 -2
  25. package/lib/utils/csv-reader.js +1 -1
  26. package/lib/utils/version.js +18 -0
  27. package/libx/_runtime/cds.js +1 -1
  28. package/libx/_runtime/common/aspects/any.js +1 -23
  29. package/libx/_runtime/common/generic/input.js +111 -50
  30. package/libx/_runtime/common/generic/sorting.js +1 -1
  31. package/libx/_runtime/common/utils/draft.js +1 -1
  32. package/libx/_runtime/common/utils/entityFromCqn.js +1 -1
  33. package/libx/_runtime/common/utils/propagateForeignKeys.js +1 -1
  34. package/libx/_runtime/common/utils/resolveView.js +2 -2
  35. package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -2
  36. package/libx/_runtime/common/utils/structured.js +2 -2
  37. package/libx/_runtime/common/utils/templateProcessor.js +0 -5
  38. package/libx/_runtime/common/utils/vcap.js +1 -1
  39. package/libx/_runtime/fiori/lean-draft.js +63 -23
  40. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -2
  41. package/libx/_runtime/messaging/file-based.js +2 -1
  42. package/libx/_runtime/messaging/service.js +1 -1
  43. package/libx/_runtime/remote/utils/client.js +1 -1
  44. package/libx/common/assert/utils.js +2 -12
  45. package/libx/common/utils/streaming.js +4 -9
  46. package/libx/http/location.js +1 -0
  47. package/libx/odata/index.js +1 -1
  48. package/libx/odata/middleware/batch.js +6 -1
  49. package/libx/odata/middleware/create.js +1 -1
  50. package/libx/odata/middleware/error.js +22 -19
  51. package/libx/odata/middleware/stream.js +1 -1
  52. package/libx/odata/parse/cqn2odata.js +16 -10
  53. package/libx/odata/parse/grammar.peggy +8 -4
  54. package/libx/odata/parse/parser.js +1 -1
  55. package/libx/odata/utils/index.js +1 -1
  56. package/libx/queue/index.js +3 -3
  57. package/libx/rest/RestAdapter.js +1 -2
  58. package/libx/rest/middleware/create.js +5 -2
  59. package/package.json +2 -2
  60. package/server.js +1 -1
  61. package/bin/deploy/to-hana.js +0 -1
  62. package/lib/utils/check-version.js +0 -9
  63. package/lib/utils/unit.js +0 -19
  64. package/libx/_runtime/cds-services/util/assert.js +0 -181
  65. package/libx/_runtime/types/api.js +0 -129
  66. package/libx/common/assert/validation.js +0 -109
@@ -1,181 +0,0 @@
1
- const cds = require('../../cds')
2
- const LOG = cds.log('app')
3
- const templatePathSerializer = require('../../common/utils/templateProcessorPathSerializer')
4
-
5
- const typeCheckers = require('../../../common/assert/type-strict')
6
- const { 'cds.Decimal': checkDecimal } = typeCheckers
7
- const { checkMandatory, checkEnum, checkRange, checkFormat } = require('../../../common/assert/validation')
8
-
9
- const ASSERT_RANGE = 'ASSERT_RANGE'
10
- const ASSERT_FORMAT = 'ASSERT_FORMAT'
11
- const ASSERT_DATA_TYPE = 'ASSERT_DATA_TYPE'
12
- const ASSERT_ENUM = 'ASSERT_ENUM'
13
- const ASSERT_NOT_NULL = 'ASSERT_NOT_NULL'
14
-
15
- const _enumValues = element => {
16
- return Object.keys(element).map(enumKey => {
17
- const enum_ = element[enumKey]
18
- const enumValue = enum_ && enum_.val
19
-
20
- if (enumValue !== undefined) {
21
- if (enumValue['=']) return enumValue['=']
22
- if (enum_ && enum_.literal && enum_.literal === 'number') return Number(enumValue)
23
- return enumValue
24
- }
25
-
26
- return enumKey
27
- })
28
- }
29
-
30
- // REVISIT: this needs a cleanup!
31
- const assertError = (code, element, value, key, path) => {
32
- let args
33
-
34
- if (typeof code === 'object') {
35
- args = code.args
36
- code = code.code
37
- }
38
-
39
- const { name, type, precision, scale } = element
40
- const error = new Error()
41
- const errorEntry = {
42
- code,
43
- message: code,
44
- target: path ?? element.name ?? key,
45
- args: args ?? [name ?? key]
46
- }
47
-
48
- const assertError = Object.assign(error, errorEntry)
49
- Object.assign(assertError, {
50
- entity: element.parent && element.parent.name,
51
- element: name, // > REVISIT: when is error.element needed?
52
- type: element.items ? element.items._type : type,
53
- status: 400,
54
- value
55
- })
56
-
57
- if (element.enum) assertError.enum = _enumValues(element)
58
- if (precision) assertError.precision = precision
59
- if (scale) assertError.scale = scale
60
-
61
- if (element.target) {
62
- // REVISIT: when does this case apply?
63
- assertError.target = element.target
64
- }
65
-
66
- return assertError
67
- }
68
-
69
- /**
70
- * @param {import('../../types/api').InputConstraints} constraints
71
- */
72
- const checkInputConstraints = ({ element, value, errors, key, pathSegmentsInfo }) => {
73
- if (!element) return errors
74
-
75
- let path
76
- if (pathSegmentsInfo?.length) path = templatePathSerializer(element.name || key, pathSegmentsInfo)
77
-
78
- // not nice, but best option for keeping new cds.assert() clean
79
- if (element._isMandatory) {
80
- const mandatoryErrors = []
81
- checkMandatory(value, element, mandatoryErrors, [], key)
82
- if (mandatoryErrors.length) {
83
- errors.push(...mandatoryErrors.map(() => assertError({ code: ASSERT_NOT_NULL }, element, value, key, path)))
84
- }
85
- }
86
-
87
- if (value == null) return errors
88
-
89
- // not nice, but best option for keeping new cds.assert() clean
90
- const enumErrors = []
91
- checkEnum(value, element, enumErrors, [], key)
92
- if (enumErrors.length) {
93
- errors.push(...enumErrors.map(e => assertError({ code: ASSERT_ENUM, args: e.args }, element, value, key, path)))
94
- }
95
-
96
- // not nice, but best option for keeping new cds.assert() clean
97
- const rangeErrors = []
98
- checkRange(value, element, rangeErrors, [], key)
99
- if (rangeErrors.length) {
100
- errors.push(...rangeErrors.map(e => assertError({ code: ASSERT_RANGE, args: e.args }, element, value, key, path)))
101
- }
102
-
103
- // not nice, but best option for keeping new cds.assert() clean
104
- const formatErrors = []
105
- checkFormat(value, element, formatErrors, [], key)
106
- if (formatErrors.length) {
107
- errors.push(...formatErrors.map(e => assertError({ code: ASSERT_FORMAT, args: e.args }, element, value, key, path)))
108
- }
109
-
110
- if (element.type === 'cds.Decimal') {
111
- // not nice, but best option for keeping new cds.assert() clean
112
- const decimalErrors = []
113
- checkDecimal(value, element, decimalErrors, [], key)
114
- if (decimalErrors.length) {
115
- errors.push(
116
- ...decimalErrors.map(e => assertError({ code: ASSERT_DATA_TYPE, args: e.args }, element, value, key, path))
117
- )
118
- }
119
- }
120
-
121
- return errors
122
- }
123
-
124
- /**
125
- * Check whether the target entity referenced by the association (the reference's target) exists and assert an error if
126
- * the the reference's target doesn't exist.
127
- *
128
- * In other words, use this annotation to check whether a non-null foreign key input in a table has a corresponding
129
- * primary key (also known as a parent key) in the associated/referenced target table (also known as a parent table).
130
- *
131
- * @param {import('../../types/api').assertTargetMap} assertMap
132
- * @param {array} errors An array to appends the possible errors.
133
- * @see {@link https://cap.cloud.sap/docs/guides/providing-services#assert-target @assert.target} for
134
- * further information.
135
- */
136
- const assertTargets = async (assertMap, errors) => {
137
- const { targets: targetsMap, allTargets } = assertMap
138
- if (targetsMap.size === 0) return
139
-
140
- const targets = Array.from(targetsMap.values())
141
- const transactions = targets.map(({ keys, entity }) => {
142
- const where = Object.assign({}, ...keys)
143
- return cds.db.exists(entity, where).forShareLock()
144
- })
145
- const targetsExistsResults = await Promise.allSettled(transactions)
146
-
147
- targetsExistsResults.forEach((txPromise, index) => {
148
- const isPromiseRejected = txPromise.status === 'rejected'
149
- const shouldAssertError = (txPromise.status === 'fulfilled' && txPromise.value == null) || isPromiseRejected
150
- if (!shouldAssertError) return
151
-
152
- const target = targets[index]
153
- const { element } = target.assocInfo
154
-
155
- if (isPromiseRejected) {
156
- LOG._debug &&
157
- LOG.debug(
158
- `The transaction to check the @assert.target constraint for foreign key "${element.name}" failed`,
159
- txPromise.reason
160
- )
161
-
162
- throw new Error(txPromise.reason.message)
163
- }
164
-
165
- allTargets
166
- .filter(t => t.key === target.key)
167
- .forEach(target => {
168
- const { row, pathSegmentsInfo } = target.assocInfo
169
- const key = target.foreignKey.name
170
- let path
171
- if (pathSegmentsInfo?.length) path = templatePathSerializer(key, pathSegmentsInfo)
172
- const error = assertError('ASSERT_TARGET', target.foreignKey, row[key], key, path)
173
- errors.push(error)
174
- })
175
- })
176
- }
177
-
178
- module.exports = {
179
- checkInputConstraints,
180
- assertTargets
181
- }
@@ -1,129 +0,0 @@
1
- // Columns
2
-
3
- /**
4
- * @typedef {object} ColumnRef
5
- * @property {string[]} ref
6
- * @property {function} func
7
- */
8
-
9
- /**
10
- * @typedef {Array<ColumnRef>} ColumnRefs
11
- */
12
-
13
- // Input constraints
14
-
15
- /**
16
- * @typedef {object} InputConstraints
17
- * @property {object} element
18
- * @property {*} value
19
- * @property {Array} errors
20
- * @property {string} [key]
21
- * @property {pathSegmentInfo[]} [pathSegmentsInfo]
22
- * @property {string} event
23
- */
24
-
25
- // ON condition
26
-
27
- /**
28
- * @typedef {object} ONConditionAliases
29
- * @property {string} select
30
- * @property {string} join
31
- */
32
-
33
- /**
34
- * @typedef {object} ONConditionOptions
35
- * @property {string | Array} [associationNames]
36
- * @property {object} [csn]
37
- * @property {ONConditionAliases} [aliases]
38
- * @property {boolean} [resolveView=true]
39
- */
40
-
41
- // Template processor
42
-
43
- /**
44
- * @typedef {object} TemplateProcessorInfo
45
- * @property {entity} target
46
- * @property {Map} elements
47
- */
48
-
49
- /**
50
- * @typedef {object} TemplateProcessorPathOptions
51
- * @property {object} [draftKeys]
52
- * @property {function} [rowUUIDGenerator]
53
- * @property {string[]} [segments=[]] - Path segments to relate the error message.
54
- * @property {boolean} [includeKeyValues=false] Indicates whether the key values are included in the path segments
55
- * The path segments are used to build the error target (a relative resource path)
56
- */
57
-
58
- /**
59
- * @typedef {object} TemplateProcessor
60
- * @property {Function} processFn
61
- * @property {object} data
62
- * @property {TemplateProcessorInfo} template
63
- * @property {boolean} [isRoot=true]
64
- * @property {TemplateProcessorPathOptions} [pathOptions=null]
65
- */
66
-
67
- /**
68
- * @typedef {object} pathSegmentInfo
69
- * @property {string} key
70
- * @property {string[]} keyNames
71
- * @property {object} row
72
- * @property {object} elements
73
- * @property {string[]} draftKeys
74
- */
75
-
76
- /**
77
- * @typedef {object} templateElementInfo
78
- * @property {object} row
79
- * @property {string} key
80
- * @property {object} element
81
- * @property {boolean} plain
82
- * @property {entity} target
83
- * @property {boolean} isRoot
84
- * @property {string[] | Array<pathSegmentInfo>} [pathSegmentsInfo]
85
- */
86
-
87
- // Search
88
-
89
- /**
90
- * @typedef {object} searchContainsArg
91
- * @property {ColumnRefs} [list] The columns to
92
- * be searched
93
- * @property {string} [val] The search string
94
- */
95
-
96
- /**
97
- * @typedef {Array<searchContainsArg>} searchContainsArgs
98
- */
99
-
100
- /**
101
- * @typedef {object} searchContainsExp
102
- * @property {string} func='contains' The function name
103
- * @property {searchContainsArgs} args
104
- */
105
-
106
- /**
107
- * @typedef {object} search2cqnOptions
108
- * @property {ColumnRefs} [columns] The columns to be searched
109
- * @property {string} locale The user locale
110
- */
111
-
112
- // Assert targets map
113
-
114
- /**
115
- * @typedef {object} assertTargetMap
116
- * @property {Map<string, targetMaps>} targets
117
- * @property {targetMaps[]} allTargets
118
- */
119
-
120
- /**
121
- * @typedef {object} targetMaps
122
- * @property {string} key
123
- * @property {entity} entity
124
- * @property {object} keys
125
- * @property {object} foreignKey
126
- * @property {templateElementInfo} assocInfo
127
- */
128
-
129
- module.exports = {}
@@ -1,109 +0,0 @@
1
- const { cds } = global
2
-
3
- const {
4
- 'cds.Date': checkISODate,
5
- 'cds.Time': checkISOTime,
6
- 'cds.DateTime': checkISODateTime,
7
- 'cds.Timestamp': checkISOTimestamp,
8
- 'cds.String': checkString
9
- } = require('./type-strict')
10
- const { getTarget, resolveCDSType } = require('./utils')
11
-
12
- const _isNavigationColumn = (col, as) => col.ref?.length > 1 && (col.as === as || col.ref[col.ref.length - 1] === as)
13
-
14
- // REVISIT: mandatory is actually not the same as not null or empty string
15
- const _isNotFilled = val => val === null || val === undefined || (typeof val === 'string' && val.trim() === '')
16
-
17
- const _getEnumElement = ele => ((ele['@assert.range'] && ele.enum) ? ele.enum : undefined)
18
-
19
- const _enumValues = ele => {
20
- return Object.keys(ele).map(enumKey => {
21
- const enum_ = ele[enumKey]
22
- const enumValue = enum_ && enum_.val
23
- if (enumValue !== undefined) {
24
- if (enumValue['=']) return enumValue['=']
25
- if (enum_ && enum_.literal && enum_.literal === 'number') return Number(enumValue)
26
- return enumValue
27
- }
28
- return enumKey
29
- })
30
- }
31
-
32
- const _checkDateValue = (val, r1, r2) => {
33
- const dateVal = new Date(val)
34
- return (dateVal - new Date(r1)) * (dateVal - new Date(r2)) <= 0
35
- }
36
-
37
- const _toDate = val => `2000-01-01T${val}Z`
38
-
39
- const _checkInRange = (val, range, type) => {
40
- switch (type) {
41
- case 'cds.Date':
42
- return checkISODate(val) && _checkDateValue(val, range[0], range[1])
43
- case 'cds.DateTime':
44
- return checkISODateTime(val) && _checkDateValue(val, range[0], range[1])
45
- case 'cds.Timestamp':
46
- return checkISOTimestamp(val) && _checkDateValue(val, range[0], range[1])
47
- case 'cds.Time':
48
- return checkISOTime(val) && _checkDateValue(_toDate(val), _toDate(range[0]), _toDate(range[1]))
49
- default:
50
- return (val - range[0]) * (val - range[1]) <= 0
51
- }
52
- }
53
-
54
- // process.env.CDS_ASSERT_FORMAT_FLAGS is not official!
55
- const _checkRegExpFormat = (val, format) =>
56
- checkString(val) && val.match(new RegExp(format, process.env.CDS_ASSERT_FORMAT_FLAGS || 'u'))
57
-
58
- const checkMandatory = (v, ele, errs, path, k) => {
59
- // REVISIT: correct to not complain?
60
- // do not complain about missing foreign keys in children
61
- if (path.length && ele['@odata.foreignKey4']) return
62
-
63
- // TODO: which case is this?
64
- // do not complain about ???
65
- if (ele.parent?.query?.SELECT?.columns?.find(col => _isNavigationColumn(col, ele.name))) return
66
-
67
- if (_isNotFilled(v)) {
68
- const target = getTarget(path, k)
69
- errs.push(new cds.error('ASSERT_NOT_NULL', { target, statusCode: 400, code: '400' }))
70
- }
71
- }
72
-
73
- const checkEnum = (v, ele, errs, path, k) => {
74
- const enumElements = _getEnumElement(ele)
75
- const enumValues = enumElements && _enumValues(enumElements)
76
- if (enumElements && !enumValues.some(ev => ev == v)) { //> use == for automatic type coercion
77
- const args =
78
- typeof v === 'string'
79
- ? ['"' + v + '"', enumValues.map(ele => '"' + ele + '"').join(', ')]
80
- : [v, enumValues.join(', ')]
81
- const target = getTarget(path, k)
82
- errs.push(new cds.error('ASSERT_ENUM', { args, target, statusCode: 400, code: '400' }))
83
- }
84
- }
85
-
86
- const checkRange = (v, ele, errs, path, k) => {
87
- const rangeElements = ele['@assert.range'] && !_getEnumElement(ele) ? ele['@assert.range'] : undefined
88
- if (rangeElements && !_checkInRange(v, rangeElements, resolveCDSType(ele))) {
89
- const args = [v, ...ele['@assert.range']]
90
- const target = getTarget(path, k)
91
- errs.push(new cds.error('ASSERT_RANGE', { args, target, statusCode: 400, code: '400' }))
92
- }
93
- }
94
-
95
- const checkFormat = (v, ele, errs, path, k) => {
96
- const formatElements = ele['@assert.format']
97
- if (formatElements && !_checkRegExpFormat(v, formatElements)) {
98
- const args = [v, formatElements]
99
- const target = getTarget(path, k)
100
- errs.push(new cds.error('ASSERT_FORMAT', { args, target, statusCode: 400, code: '400' }))
101
- }
102
- }
103
-
104
- module.exports = {
105
- checkMandatory,
106
- checkEnum,
107
- checkRange,
108
- checkFormat
109
- }