@sap/cds 9.0.4 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/bin/deploy.js +29 -0
- package/bin/serve.js +1 -5
- package/lib/compile/etc/csv.js +11 -6
- package/lib/compile/for/lean_drafts.js +29 -7
- package/lib/compile/load.js +8 -5
- package/lib/compile/to/hdbtabledata.js +1 -1
- package/lib/dbs/cds-deploy.js +5 -34
- package/lib/env/cds-env.js +2 -1
- package/lib/env/cds-requires.js +4 -1
- package/lib/env/defaults.js +0 -11
- package/lib/env/schemas/cds-rc.js +218 -6
- package/lib/index.js +38 -38
- package/lib/log/cds-error.js +12 -11
- package/lib/log/format/json.js +1 -1
- package/lib/ql/SELECT.js +31 -0
- package/lib/ql/resolve.js +1 -1
- package/lib/req/context.js +1 -1
- package/lib/req/request.js +1 -1
- package/lib/req/validate.js +17 -19
- package/lib/srv/cds.Service.js +18 -28
- package/lib/srv/middlewares/auth/ias-auth.js +29 -2
- package/lib/srv/middlewares/auth/jwt-auth.js +11 -1
- package/lib/srv/middlewares/auth/xssec.js +1 -1
- package/lib/srv/srv-models.js +1 -1
- package/lib/srv/srv-tx.js +2 -2
- package/lib/utils/cds-utils.js +35 -2
- package/lib/utils/csv-reader.js +1 -1
- package/lib/utils/inflect.js +2 -2
- package/lib/utils/tar.js +60 -23
- package/lib/utils/version.js +18 -0
- package/libx/_runtime/cds.js +1 -1
- package/libx/_runtime/common/aspects/any.js +1 -23
- package/libx/_runtime/common/generic/crud.js +1 -3
- package/libx/_runtime/common/generic/input.js +113 -52
- package/libx/_runtime/common/generic/sorting.js +1 -1
- package/libx/_runtime/common/generic/temporal.js +0 -6
- package/libx/_runtime/common/utils/draft.js +1 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +1 -1
- package/libx/_runtime/common/utils/propagateForeignKeys.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -2
- package/libx/_runtime/common/utils/structured.js +2 -2
- package/libx/_runtime/common/utils/templateProcessor.js +0 -5
- package/libx/_runtime/common/utils/vcap.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +529 -143
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -2
- package/libx/_runtime/messaging/service.js +1 -1
- package/libx/_runtime/remote/utils/client.js +2 -1
- package/libx/common/assert/utils.js +2 -12
- package/libx/common/utils/streaming.js +4 -9
- package/libx/http/location.js +1 -0
- package/libx/odata/ODataAdapter.js +47 -43
- package/libx/odata/index.js +1 -1
- package/libx/odata/middleware/batch.js +6 -2
- package/libx/odata/middleware/create.js +1 -1
- package/libx/odata/middleware/error.js +27 -17
- package/libx/odata/middleware/operation.js +15 -21
- package/libx/odata/middleware/stream.js +1 -1
- package/libx/odata/parse/afterburner.js +22 -8
- package/libx/odata/parse/cqn2odata.js +16 -10
- package/libx/odata/parse/grammar.peggy +185 -134
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +1 -36
- package/libx/odata/utils/metadata.js +34 -1
- package/libx/odata/utils/odataBind.js +2 -1
- package/libx/odata/utils/result.js +22 -20
- package/libx/queue/index.js +7 -4
- package/libx/rest/RestAdapter.js +1 -2
- package/libx/rest/middleware/create.js +5 -2
- package/package.json +2 -2
- package/server.js +1 -1
- package/bin/deploy/to-hana.js +0 -1
- package/lib/utils/check-version.js +0 -9
- package/lib/utils/unit.js +0 -19
- package/libx/_runtime/cds-services/util/assert.js +0 -181
- package/libx/_runtime/types/api.js +0 -129
- package/libx/common/assert/validation.js +0 -109
package/lib/utils/tar.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { PassThrough } = require('stream')
|
|
1
2
|
const child_process = require('child_process')
|
|
2
3
|
const spawn = /\btar\b/.test(process.env.DEBUG) ? (cmd, args, options) => {
|
|
3
4
|
Error.captureStackTrace(spawn,spawn)
|
|
@@ -8,26 +9,36 @@ const spawn = /\btar\b/.test(process.env.DEBUG) ? (cmd, args, options) => {
|
|
|
8
9
|
const cds = require('../index'), { fs, path, mkdirp, exists, rimraf } = cds.utils
|
|
9
10
|
const _resolve = (...x) => path.resolve (cds.root,...x)
|
|
10
11
|
|
|
11
|
-
//
|
|
12
|
+
// ======= ONLY_FOR_WINDOWS ======
|
|
13
|
+
// This section contains logic relevant for Windows OS.
|
|
14
|
+
|
|
15
|
+
// tar does not work properly on Windows w/o this change
|
|
12
16
|
const win = path => {
|
|
13
17
|
if (!path) return path
|
|
14
18
|
if (typeof path === 'string') return path.replace('C:', '//localhost/c$').replace(/\\+/g, '/')
|
|
15
19
|
if (Array.isArray(path)) return path.map(el => win(el))
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
// spawn tar on Windows, using the cli version
|
|
23
|
+
const winSpawnDir = (dir, args) => {
|
|
24
|
+
if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', win(dir), ...win(args)])
|
|
25
|
+
else return spawn ('tar', ['cf', '-', '-C', win(dir), ...win(args)])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// copy a directory recursively on Windows, using fs.promises
|
|
29
|
+
async function winCopyDir(src, dest) {
|
|
19
30
|
if ((await fs.promises.stat(src)).isDirectory()) {
|
|
20
31
|
const entries = await fs.promises.readdir(src)
|
|
21
|
-
return Promise.all(entries.map(async each =>
|
|
32
|
+
return Promise.all(entries.map(async each => winCopyDir(path.join(src, each), path.join(dest, each))))
|
|
22
33
|
} else {
|
|
23
34
|
await fs.promises.mkdir(path.dirname(dest), { recursive: true })
|
|
24
35
|
return fs.promises.copyFile(src, dest)
|
|
25
36
|
}
|
|
26
37
|
}
|
|
27
38
|
|
|
28
|
-
//
|
|
29
|
-
// cli tar has a size limit on Windows
|
|
30
|
-
const
|
|
39
|
+
// copy resources containing files and folders to temp dir on Windows
|
|
40
|
+
// cli tar has a size limit on Windows
|
|
41
|
+
const winCreateTemp = async (root, resources) => {
|
|
31
42
|
// Asynchronously copies the entire content from src to dest.
|
|
32
43
|
const temp = await fs.promises.mkdtemp(`${fs.realpathSync(require('os').tmpdir())}${path.sep}tar-`)
|
|
33
44
|
for (let resource of resources) {
|
|
@@ -43,7 +54,7 @@ const createTemp = async (root, resources) => {
|
|
|
43
54
|
await fs.promises.cp(resource, destination, { recursive: true })
|
|
44
55
|
} else {
|
|
45
56
|
// node < 16
|
|
46
|
-
await
|
|
57
|
+
await winCopyDir(resource, destination)
|
|
47
58
|
}
|
|
48
59
|
}
|
|
49
60
|
}
|
|
@@ -51,6 +62,43 @@ const createTemp = async (root, resources) => {
|
|
|
51
62
|
return temp
|
|
52
63
|
}
|
|
53
64
|
|
|
65
|
+
// spawn tar on Windows, using a temp dir, which is copied from the original dir
|
|
66
|
+
// cli tar has a size limit on Windows
|
|
67
|
+
const winSpawnTempDir = (dir, args) => {
|
|
68
|
+
// Synchronous trick: use a PassThrough as placeholder
|
|
69
|
+
const stdout = new PassThrough()
|
|
70
|
+
const stderr = new PassThrough()
|
|
71
|
+
const c = {
|
|
72
|
+
stdout,
|
|
73
|
+
stderr,
|
|
74
|
+
on: (...a) => { stdout.on(...a); stderr.on(...a); return c },
|
|
75
|
+
once: (...a) => { stdout.once(...a); stderr.once(...a); return c },
|
|
76
|
+
kill: () => {},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// async copy, then swap streams/events
|
|
80
|
+
winCreateTemp(dir, args.shift()).then(tempPath => {
|
|
81
|
+
const real = winSpawnDir(tempPath, args)
|
|
82
|
+
real.stdout.pipe(stdout)
|
|
83
|
+
real.stderr && real.stderr.pipe(stderr)
|
|
84
|
+
const cleanup = () => exists(tempPath) && rimraf(tempPath)
|
|
85
|
+
real.on('close', (...ev) => {
|
|
86
|
+
stdout.emit('close', ...ev)
|
|
87
|
+
stderr.emit('close', ...ev)
|
|
88
|
+
cleanup()
|
|
89
|
+
})
|
|
90
|
+
real.on('error', (...ev) => {
|
|
91
|
+
stdout.emit('error', ...ev)
|
|
92
|
+
stderr.emit('error', ...ev)
|
|
93
|
+
cleanup()
|
|
94
|
+
})
|
|
95
|
+
c.kill = (...ev) => real.kill(...ev)
|
|
96
|
+
})
|
|
97
|
+
return c
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ====== END ONLY_FOR_WINDOWS ======
|
|
101
|
+
|
|
54
102
|
const tarInfo = async (info) => {
|
|
55
103
|
let cmd, param
|
|
56
104
|
if (info === 'version') {
|
|
@@ -118,19 +166,12 @@ exports.create = (dir='.', ...args) => {
|
|
|
118
166
|
if (typeof dir === 'string') dir = _resolve(dir)
|
|
119
167
|
if (Array.isArray(dir)) [ dir, ...args ] = [ cds.root, dir, ...args ]
|
|
120
168
|
|
|
121
|
-
let c
|
|
169
|
+
let c
|
|
122
170
|
args = args.filter(el => el)
|
|
123
|
-
if (process.platform === 'win32') {
|
|
124
|
-
const spawnDir = (dir, args) => {
|
|
125
|
-
if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', win(dir), ...win(args)])
|
|
126
|
-
else return spawn ('tar', ['cf', '-', '-C', win(dir), ...win(args)])
|
|
127
|
-
}
|
|
171
|
+
if (process.platform === 'win32') {
|
|
128
172
|
args.push('.')
|
|
129
|
-
if (Array.isArray(args[0]))
|
|
130
|
-
|
|
131
|
-
} else {
|
|
132
|
-
c = spawnDir(dir, args)
|
|
133
|
-
}
|
|
173
|
+
if (Array.isArray(args[0])) c = winSpawnTempDir(dir, args)
|
|
174
|
+
else c = winSpawnDir(dir, args)
|
|
134
175
|
} else {
|
|
135
176
|
if (Array.isArray(args[0])) {
|
|
136
177
|
args.push (...args.shift().map (f => path.isAbsolute(f) ? path.relative(dir,f) : f))
|
|
@@ -153,11 +194,7 @@ exports.create = (dir='.', ...args) => {
|
|
|
153
194
|
c.stdout.on('data', d => data.push(d))
|
|
154
195
|
c.stderr.on('data', d => stderr += d)
|
|
155
196
|
c.on('close', code => code ? reject(new Error(stderr)) : resolve(Buffer.concat(data)))
|
|
156
|
-
c.on('error', reject)
|
|
157
|
-
if (process.platform === 'win32') {
|
|
158
|
-
c.on('close', () => temp && exists(temp) && rimraf(temp))
|
|
159
|
-
c.on('error', () => temp && exists(temp) && rimraf(temp))
|
|
160
|
-
}
|
|
197
|
+
c.on('error', reject)
|
|
161
198
|
},
|
|
162
199
|
|
|
163
200
|
/**
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const semver = exports = module.exports = (x,y,z) => {
|
|
2
|
+
if (typeof x === 'string') [ x,y,z ] = String(x).split('.')
|
|
3
|
+
return 1e6 * (x||0) + 1e3 * (y||0) + +(z||0)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
exports.checkNodeVersion = (cds = require ('../../package.json')) => {
|
|
7
|
+
let required = cds.engines?.node?.slice(2) //> e.g. >=22
|
|
8
|
+
let current = process.version.slice(1) //> e.g. v24.4.1
|
|
9
|
+
if (semver(current) >= semver(required)) return; else process.stderr.write (`
|
|
10
|
+
Node.js version ${required} or higher is required for @sap/cds v${cds.version}.
|
|
11
|
+
Current version ${current} does not satisfy this. \n\n`)
|
|
12
|
+
return process.exit(1)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
exports.check = (x, min, max) => {
|
|
16
|
+
let v = semver(x)
|
|
17
|
+
return (!min || semver(min) <= v) && (!max || v <= semver(max))
|
|
18
|
+
}
|
package/libx/_runtime/cds.js
CHANGED
|
@@ -16,7 +16,7 @@ cds.extend(service).with(require('./common/aspects/service'))
|
|
|
16
16
|
*/
|
|
17
17
|
cds.Service.prototype._requires_resolving = function (req) {
|
|
18
18
|
if (req._resolved) return false
|
|
19
|
-
if (!this.
|
|
19
|
+
if (!this.definition) return false
|
|
20
20
|
if (!req.query || typeof req.query !== 'object') return false
|
|
21
21
|
if (Array.isArray(req.query)) return false
|
|
22
22
|
if (Object.keys(req.query).length === 0) return false
|
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
const { foreignKey4 } = require('
|
|
1
|
+
const { foreignKey4 } = require('../utils/foreignKeyPropagations')
|
|
2
2
|
|
|
3
3
|
const _getCommonFieldControl = e => {
|
|
4
4
|
const cfr = e['@Common.FieldControl']
|
|
5
5
|
return cfr && cfr['#']
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
const _isMandatory = e => {
|
|
9
|
-
return (
|
|
10
|
-
e['@assert.mandatory'] !== false &&
|
|
11
|
-
(e['@mandatory'] ||
|
|
12
|
-
e['@Common.FieldControl.Mandatory'] ||
|
|
13
|
-
e['@FieldControl.Mandatory'] ||
|
|
14
|
-
_getCommonFieldControl(e) === 'Mandatory')
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
8
|
const _isReadOnly = e => {
|
|
19
9
|
return (
|
|
20
10
|
e['@readonly'] ||
|
|
@@ -34,22 +24,10 @@ module.exports = class {
|
|
|
34
24
|
return this.own('__isStructured', () => !!this.elements && this.kind !== 'entity')
|
|
35
25
|
}
|
|
36
26
|
|
|
37
|
-
get _isMandatory() {
|
|
38
|
-
return this.own('__isMandatory', () => !this.isAssociation && _isMandatory(this))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
27
|
get _isReadOnly() {
|
|
42
28
|
return this.own('__isReadOnly', () => !this.key && _isReadOnly(this))
|
|
43
29
|
}
|
|
44
30
|
|
|
45
|
-
get _mandatories() {
|
|
46
|
-
return this.own(
|
|
47
|
-
'__mandatories',
|
|
48
|
-
// eslint-disable-next-line no-unused-vars
|
|
49
|
-
() => this.elements && Object.entries(this.elements).filter(([_, v]) => v._isMandatory)
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
31
|
get _foreignKey4() {
|
|
54
32
|
return this.own('__foreignKey4', () => foreignKey4(this))
|
|
55
33
|
}
|
|
@@ -78,9 +78,7 @@ module.exports = cds.service.impl(function () {
|
|
|
78
78
|
if (await _targetEntityDoesNotExist(req)) req.reject(404) // REVISIT: add a reasonable error message
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
if (req.constructor.name in { ODataRequest: 1 }) req._.readAfterWrite = true
|
|
83
|
-
if (req.protocol?.match(/odata/)) req._.readAfterWrite = true //> REVISIT for noah
|
|
81
|
+
if (req.protocol?.match(/odata/)) req._.readAfterWrite = true
|
|
84
82
|
|
|
85
83
|
return req.data
|
|
86
84
|
})
|
|
@@ -13,11 +13,11 @@ const LOG = cds.log('app')
|
|
|
13
13
|
const { Readable } = require('node:stream')
|
|
14
14
|
|
|
15
15
|
const { enrichDataWithKeysFromWhere } = require('../utils/keys')
|
|
16
|
-
const { DRAFT_COLUMNS_MAP } = require('
|
|
16
|
+
const { DRAFT_COLUMNS_MAP } = require('../constants/draft')
|
|
17
17
|
const propagateForeignKeys = require('../utils/propagateForeignKeys')
|
|
18
|
-
const { checkInputConstraints, assertTargets } = require('../../cds-services/util/assert')
|
|
19
18
|
const getTemplate = require('../utils/template')
|
|
20
19
|
const getRowUUIDGeneratorFn = require('../utils/rowUUIDGenerator')
|
|
20
|
+
const templatePathSerializer = require('../utils/templateProcessorPathSerializer')
|
|
21
21
|
|
|
22
22
|
const _shouldSuppressErrorPropagation = (event, value) => {
|
|
23
23
|
return (
|
|
@@ -96,6 +96,113 @@ const _preProcessAssertTarget = (assocInfo, assertMap) => {
|
|
|
96
96
|
})
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
const _enumValues = element => {
|
|
100
|
+
return Object.keys(element).map(enumKey => {
|
|
101
|
+
const enum_ = element[enumKey]
|
|
102
|
+
const enumValue = enum_ && enum_.val
|
|
103
|
+
|
|
104
|
+
if (enumValue !== undefined) {
|
|
105
|
+
if (enumValue['=']) return enumValue['=']
|
|
106
|
+
if (enum_ && enum_.literal && enum_.literal === 'number') return Number(enumValue)
|
|
107
|
+
return enumValue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return enumKey
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// REVISIT: this needs a cleanup!
|
|
115
|
+
const _assertError = (code, element, value, key, path) => {
|
|
116
|
+
let args
|
|
117
|
+
|
|
118
|
+
if (typeof code === 'object') {
|
|
119
|
+
args = code.args
|
|
120
|
+
code = code.code
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { name, type, precision, scale } = element
|
|
124
|
+
const error = new Error()
|
|
125
|
+
const errorEntry = {
|
|
126
|
+
code,
|
|
127
|
+
message: code,
|
|
128
|
+
target: path ?? element.name ?? key,
|
|
129
|
+
args: args ?? [name ?? key]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const assertError = Object.assign(error, errorEntry)
|
|
133
|
+
Object.assign(assertError, {
|
|
134
|
+
entity: element.parent && element.parent.name,
|
|
135
|
+
element: name, // > REVISIT: when is error.element needed?
|
|
136
|
+
type: element.items ? element.items._type : type,
|
|
137
|
+
status: 400,
|
|
138
|
+
value
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if (element.enum) assertError.enum = _enumValues(element)
|
|
142
|
+
if (precision) assertError.precision = precision
|
|
143
|
+
if (scale) assertError.scale = scale
|
|
144
|
+
|
|
145
|
+
if (element.target) {
|
|
146
|
+
// REVISIT: when does this case apply?
|
|
147
|
+
assertError.target = element.target
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return assertError
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check whether the target entity referenced by the association (the reference's target) exists and assert an error if
|
|
155
|
+
* the the reference's target doesn't exist.
|
|
156
|
+
*
|
|
157
|
+
* In other words, use this annotation to check whether a non-null foreign key input in a table has a corresponding
|
|
158
|
+
* primary key (also known as a parent key) in the associated/referenced target table (also known as a parent table).
|
|
159
|
+
*
|
|
160
|
+
* @param {object} assertMap - Map containing the targets to assert.
|
|
161
|
+
* @param {array} errors - Array to collect errors.
|
|
162
|
+
* @see {@link https://cap.cloud.sap/docs/guides/providing-services#assert-target @assert.target} for further information.
|
|
163
|
+
*/
|
|
164
|
+
const _assertTargets = async (assertMap, errors) => {
|
|
165
|
+
const { targets: targetsMap, allTargets } = assertMap
|
|
166
|
+
if (targetsMap.size === 0) return
|
|
167
|
+
|
|
168
|
+
const targets = Array.from(targetsMap.values())
|
|
169
|
+
const transactions = targets.map(({ keys, entity }) => {
|
|
170
|
+
const where = Object.assign({}, ...keys)
|
|
171
|
+
return cds.db.exists(entity, where).forShareLock()
|
|
172
|
+
})
|
|
173
|
+
const targetsExistsResults = await Promise.allSettled(transactions)
|
|
174
|
+
|
|
175
|
+
targetsExistsResults.forEach((txPromise, index) => {
|
|
176
|
+
const isPromiseRejected = txPromise.status === 'rejected'
|
|
177
|
+
const shouldAssertError = (txPromise.status === 'fulfilled' && txPromise.value == null) || isPromiseRejected
|
|
178
|
+
if (!shouldAssertError) return
|
|
179
|
+
|
|
180
|
+
const target = targets[index]
|
|
181
|
+
const { element } = target.assocInfo
|
|
182
|
+
|
|
183
|
+
if (isPromiseRejected) {
|
|
184
|
+
LOG._debug &&
|
|
185
|
+
LOG.debug(
|
|
186
|
+
`The transaction to check the @assert.target constraint for foreign key "${element.name}" failed`,
|
|
187
|
+
txPromise.reason
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
throw new Error(txPromise.reason.message)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
allTargets
|
|
194
|
+
.filter(t => t.key === target.key)
|
|
195
|
+
.forEach(target => {
|
|
196
|
+
const { row, pathSegmentsInfo } = target.assocInfo
|
|
197
|
+
const key = target.foreignKey.name
|
|
198
|
+
let path
|
|
199
|
+
if (pathSegmentsInfo?.length) path = templatePathSerializer(key, pathSegmentsInfo)
|
|
200
|
+
const error = _assertError('ASSERT_TARGET', target.foreignKey, row[key], key, path)
|
|
201
|
+
errors.push(error)
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
99
206
|
const _processCategory = (req, category, value, elementInfo, assertMap) => {
|
|
100
207
|
const { row, key, element, isRoot } = elementInfo
|
|
101
208
|
category = _getSimpleCategory(category)
|
|
@@ -166,7 +273,7 @@ const _getProcessorFn = (req, errors, assertMap) => {
|
|
|
166
273
|
const event = req.event
|
|
167
274
|
|
|
168
275
|
return elementInfo => {
|
|
169
|
-
const { row, key,
|
|
276
|
+
const { row, key, plain } = elementInfo
|
|
170
277
|
// ugly pointer passing for sonar
|
|
171
278
|
const value = { mandatory: false, val: row && row[key] }
|
|
172
279
|
|
|
@@ -175,9 +282,6 @@ const _getProcessorFn = (req, errors, assertMap) => {
|
|
|
175
282
|
}
|
|
176
283
|
|
|
177
284
|
if (_shouldSuppressErrorPropagation(event, value)) return
|
|
178
|
-
|
|
179
|
-
// REVISIT: Convert checkInputConstraints to template mechanism
|
|
180
|
-
checkInputConstraints({ element, value: value.val, errors, pathSegmentsInfo, event })
|
|
181
285
|
}
|
|
182
286
|
}
|
|
183
287
|
|
|
@@ -251,7 +355,7 @@ async function validate_input(req) {
|
|
|
251
355
|
}
|
|
252
356
|
|
|
253
357
|
const errs = cds.validate(req.data, req.target, assertOptions)
|
|
254
|
-
if (errs) return req.
|
|
358
|
+
if (errs) return errs.forEach(err => req.error(err))
|
|
255
359
|
|
|
256
360
|
// -------------------------------------------------
|
|
257
361
|
// REVISIT: is the below still needed?
|
|
@@ -276,49 +380,12 @@ async function validate_input(req) {
|
|
|
276
380
|
pathSegmentsInfo: []
|
|
277
381
|
})
|
|
278
382
|
if (assertMap.targets.size > 0) {
|
|
279
|
-
await
|
|
383
|
+
await _assertTargets(assertMap, errors)
|
|
280
384
|
}
|
|
281
385
|
|
|
282
386
|
if (errors.length) for (const error of errors) req.error(error)
|
|
283
387
|
}
|
|
284
388
|
|
|
285
|
-
const _getProcessorFnForActionsFunctions =
|
|
286
|
-
(errors, opName) =>
|
|
287
|
-
({ row, key, element }) => {
|
|
288
|
-
const value = row && row[key]
|
|
289
|
-
|
|
290
|
-
// REVISIT: Convert checkInputConstraints to template mechanism
|
|
291
|
-
checkInputConstraints({ element, value, errors, key: opName })
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const _processActionFunctionRow = (row, param, key, errors, event, service) => {
|
|
295
|
-
const values = Array.isArray(row[key]) ? row[key] : [row[key]]
|
|
296
|
-
|
|
297
|
-
// unstructured
|
|
298
|
-
for (const value of values) {
|
|
299
|
-
checkInputConstraints({ element: param, value, errors, key })
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// structured
|
|
303
|
-
const template = getTemplate('app-input-operation', service, param, {
|
|
304
|
-
pick: _pick,
|
|
305
|
-
ignore: element => element._isAssociationStrict
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
template.process(values, _getProcessorFnForActionsFunctions(errors, key))
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const _processActionFunction = (row, eventParams, errors, event, service) => {
|
|
312
|
-
for (const key in eventParams) {
|
|
313
|
-
let param = eventParams[key]
|
|
314
|
-
|
|
315
|
-
// .type of action/function behaves different to .type of other csn elements
|
|
316
|
-
const _type = param.type
|
|
317
|
-
if (!_type && param.items) param = param.items
|
|
318
|
-
_processActionFunctionRow(row, param, key, errors, event, service)
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
389
|
function validate_action(req) {
|
|
323
390
|
const operation = this.actions?.[req.event] || req.target?.actions?.[req.event]
|
|
324
391
|
if (!operation) return
|
|
@@ -332,13 +399,7 @@ function validate_action(req) {
|
|
|
332
399
|
protocol: req.protocol
|
|
333
400
|
}
|
|
334
401
|
let errs = cds.validate(data, operation, assertOptions)
|
|
335
|
-
if (errs) return req.
|
|
336
|
-
|
|
337
|
-
// REVISIT: we still need the following because cds.validate doesn't check for @mandatory params (both flat and nested)
|
|
338
|
-
const errors = []
|
|
339
|
-
const arrayData = Array.isArray(data) ? data : [data]
|
|
340
|
-
for (const row of arrayData) _processActionFunction(row, operation.params, errors, req.event, this)
|
|
341
|
-
if (errors.length) for (const error of errors) req.error(error)
|
|
402
|
+
if (errs) return errs.forEach(err => req.error(err))
|
|
342
403
|
|
|
343
404
|
// convert binaries
|
|
344
405
|
operation.params &&
|
|
@@ -76,12 +76,6 @@ function handle_temporal_data(req) {
|
|
|
76
76
|
_getDateFromQueryOptions(_queryOptions['sap-valid-to'] ?? normalizeTimestamp('9999-12-31T23:59:59.9999999Z'))
|
|
77
77
|
)
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
// REVISIT: needed without okra
|
|
81
|
-
if (req.constructor.name !== 'ODataRequest') {
|
|
82
|
-
req._['VALID-FROM'] = _['VALID-FROM']
|
|
83
|
-
req._['VALID-TO'] = _['VALID-TO']
|
|
84
|
-
}
|
|
85
79
|
}
|
|
86
80
|
handle_temporal_data._initial = true
|
|
87
81
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const cds = require('../../../../lib')
|
|
2
|
-
const { DRAFT_COLUMNS_MAP } = require('
|
|
2
|
+
const { DRAFT_COLUMNS_MAP } = require('../constants/draft')
|
|
3
3
|
|
|
4
4
|
const _4sqlite = cds.env.i18n && Array.isArray(cds.env.i18n.for_sqlite) ? cds.env.i18n.for_sqlite : []
|
|
5
5
|
// compiler reserves 'localized' and raises a corresponding exception if used in models
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const cds = require('../../../../lib')
|
|
2
2
|
let LOG = cds.log('app')
|
|
3
3
|
|
|
4
|
-
const { rewriteAsterisks } = require('
|
|
4
|
+
const { rewriteAsterisks } = require('./rewriteAsterisks')
|
|
5
5
|
|
|
6
6
|
const _setInverseTransition = (mapping, ref, mapped) => {
|
|
7
7
|
const existing = mapping.get(ref)
|
|
@@ -531,7 +531,7 @@ const _mappedValue = (col, alias) => {
|
|
|
531
531
|
const getDBTable = target => cds.ql.resolve.table(target)
|
|
532
532
|
|
|
533
533
|
const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
|
|
534
|
-
const el = target.elements[as] || target.query._target
|
|
534
|
+
const el = target.elements[as] || target.query._target?.elements[ref.at(-1)]
|
|
535
535
|
|
|
536
536
|
if (el && el.isAssociation && el.keys) {
|
|
537
537
|
for (const key of el.keys) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const resolveStructured = require('./resolveStructured')
|
|
2
|
-
const { ensureNoDraftsSuffix } = require('
|
|
3
|
-
const { traverseFroms } = require('
|
|
2
|
+
const { ensureNoDraftsSuffix } = require('./draft')
|
|
3
|
+
const { traverseFroms } = require('./entityFromCqn')
|
|
4
4
|
|
|
5
5
|
const OPERATIONS_MAP = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in'].reduce((acc, cur) => {
|
|
6
6
|
acc[cur] = 1
|
|
@@ -6,7 +6,6 @@ const _processElement = (processFn, row, key, target, picked = {}, isRoot, pathS
|
|
|
6
6
|
|
|
7
7
|
if (!plain) return
|
|
8
8
|
|
|
9
|
-
/** @type import('../../types/api').templateElementInfo */
|
|
10
9
|
const elementInfo = { row, key, element, target, plain, isRoot, pathSegmentsInfo }
|
|
11
10
|
|
|
12
11
|
if (!element && target._flat2struct?.[key] && elementInfo.pathSegmentsInfo) {
|
|
@@ -53,7 +52,6 @@ const _processComplex = (processFn, row, template, key, pathOptions) => {
|
|
|
53
52
|
let pathSegmentInfo
|
|
54
53
|
if (pathOptions.includeKeyValues) {
|
|
55
54
|
pathOptions.rowUUIDGenerator?.(keyNames, row, template)
|
|
56
|
-
/** @type import('../../types/api').pathSegmentInfo */
|
|
57
55
|
pathSegmentInfo = { key, keyNames, row, elements: template.target.elements, draftKeys: pathOptions.draftKeys }
|
|
58
56
|
}
|
|
59
57
|
|
|
@@ -63,9 +61,6 @@ const _processComplex = (processFn, row, template, key, pathOptions) => {
|
|
|
63
61
|
}
|
|
64
62
|
}
|
|
65
63
|
|
|
66
|
-
/**
|
|
67
|
-
* @param {import("../../types/api").TemplateProcessor} args
|
|
68
|
-
*/
|
|
69
64
|
const templateProcessor = ({ processFn, data, template, isRoot = true, pathOptions = {} }) => {
|
|
70
65
|
if (!template || !template.elements.size || !data || typeof data !== 'object') return
|
|
71
66
|
const dataArr = Array.isArray(data) ? data : [data]
|