@sap/cds 6.0.4 → 6.1.2
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 +180 -18
- package/apis/cds.d.ts +11 -7
- package/apis/log.d.ts +124 -0
- package/apis/ql.d.ts +72 -15
- package/apis/services.d.ts +13 -2
- package/bin/build/buildTaskHandler.js +5 -2
- package/bin/build/constants.js +4 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
- package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
- package/bin/build/provider/buildTaskProviderInternal.js +22 -14
- package/bin/build/provider/hana/index.js +12 -9
- package/bin/build/provider/java/index.js +18 -8
- package/bin/build/provider/mtx/index.js +7 -4
- package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
- package/bin/build/provider/mtx-extension/index.js +57 -0
- package/bin/build/provider/mtx-sidecar/index.js +46 -18
- package/bin/build/provider/nodejs/index.js +34 -13
- package/bin/deploy/to-hana/cfUtil.js +7 -2
- package/bin/deploy/to-hana/hana.js +20 -25
- package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
- package/bin/serve.js +7 -4
- package/lib/compile/{index.js → cds-compile.js} +0 -0
- package/lib/compile/extend.js +15 -5
- package/lib/compile/minify.js +1 -15
- package/lib/compile/parse.js +1 -1
- package/lib/compile/resolve.js +2 -2
- package/lib/compile/to/srvinfo.js +6 -4
- package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
- package/lib/env/{index.js → cds-env.js} +1 -17
- package/lib/env/{requires.js → cds-requires.js} +24 -3
- package/lib/env/defaults.js +7 -1
- package/lib/env/schemas/cds-package.json +11 -0
- package/lib/env/schemas/cds-rc.json +614 -0
- package/lib/index.js +19 -16
- package/lib/log/{errors.js → cds-error.js} +1 -1
- package/lib/log/{index.js → cds-log.js} +0 -0
- package/lib/log/format/kibana.js +19 -1
- package/lib/ql/Query.js +9 -3
- package/lib/ql/SELECT.js +2 -2
- package/lib/ql/UPDATE.js +2 -2
- package/lib/ql/{index.js → cds-ql.js} +4 -10
- package/lib/req/context.js +49 -17
- package/lib/req/locale.js +5 -1
- package/lib/{serve → srv}/adapters.js +23 -19
- package/lib/{connect → srv}/bindings.js +0 -0
- package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
- package/lib/{serve/index.js → srv/cds-serve.js} +0 -0
- package/lib/{serve → srv}/factory.js +1 -1
- package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
- package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
- package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
- package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
- package/lib/srv/srv-models.js +207 -0
- package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
- package/lib/utils/{tests.js → cds-test.js} +2 -2
- package/lib/utils/cds-utils.js +146 -0
- package/lib/utils/index.js +2 -145
- package/lib/utils/jest.js +43 -0
- package/lib/utils/resources/index.js +15 -25
- package/lib/utils/resources/tar.js +18 -41
- package/libx/_runtime/auth/index.js +14 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +3 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
- package/libx/_runtime/cds-services/util/errors.js +1 -29
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/perf/index.js +10 -15
- package/libx/_runtime/common/utils/binary.js +3 -4
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
- package/libx/_runtime/common/utils/keys.js +14 -6
- package/libx/_runtime/common/utils/resolveView.js +1 -1
- package/libx/_runtime/common/utils/template.js +1 -1
- package/libx/_runtime/db/Service.js +2 -14
- package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
- package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
- package/libx/_runtime/db/generic/input.js +8 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
- package/libx/_runtime/extensibility/activate.js +47 -47
- package/libx/_runtime/extensibility/add.js +22 -13
- package/libx/_runtime/extensibility/addExtension.js +17 -13
- package/libx/_runtime/extensibility/defaults.js +25 -30
- package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
- package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
- package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
- package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
- package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
- package/libx/_runtime/extensibility/linter.js +32 -0
- package/libx/_runtime/extensibility/push.js +77 -20
- package/libx/_runtime/extensibility/service.js +29 -12
- package/libx/_runtime/extensibility/token.js +57 -0
- package/libx/_runtime/extensibility/utils.js +8 -6
- package/libx/_runtime/extensibility/validation.js +6 -9
- package/libx/_runtime/fiori/generic/new.js +0 -11
- package/libx/_runtime/fiori/utils/where.js +1 -1
- package/libx/_runtime/hana/Service.js +0 -1
- package/libx/_runtime/hana/conversion.js +12 -1
- package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
- package/libx/_runtime/hana/pool.js +6 -10
- package/libx/_runtime/hana/search2Contains.js +0 -5
- package/libx/_runtime/hana/search2cqn4sql.js +1 -0
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +11 -6
- package/libx/_runtime/remote/utils/data.js +5 -0
- package/libx/_runtime/sqlite/Service.js +7 -6
- package/libx/_runtime/sqlite/execute.js +41 -28
- package/libx/odata/afterburner.js +79 -2
- package/libx/odata/cqn2odata.js +15 -9
- package/libx/odata/grammar.pegjs +157 -76
- package/libx/odata/index.js +9 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +39 -5
- package/libx/rest/RestAdapter.js +3 -7
- package/libx/rest/middleware/delete.js +4 -5
- package/libx/rest/middleware/parse.js +3 -2
- package/package.json +3 -3
- package/server.js +1 -1
- package/srv/extensibility-service.cds +6 -3
- package/srv/model-provider.cds +3 -1
- package/srv/model-provider.js +86 -106
- package/srv/mtx.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
let vm = require('vm')
|
|
2
|
+
if (!vm.__caching__) {
|
|
3
|
+
vm.__caching__ = true
|
|
4
|
+
const scriptCache = {}
|
|
5
|
+
vm._Script = vm._Script || vm.Script
|
|
6
|
+
class CachedScript extends vm._Script {
|
|
7
|
+
constructor(src, options) {
|
|
8
|
+
const cached = scriptCache[options.filename]
|
|
9
|
+
if (cached) {
|
|
10
|
+
return cached
|
|
11
|
+
}
|
|
12
|
+
super(src, options)
|
|
13
|
+
// Assume that .test.js files are only loaded once
|
|
14
|
+
if (!options.filename.endsWith('.test.js')) {
|
|
15
|
+
scriptCache[options.filename] = this
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
vm.Script = CachedScript
|
|
21
|
+
}
|
|
22
|
+
vm = undefined
|
|
23
|
+
|
|
24
|
+
let asyncHooks = require('async_hooks')
|
|
25
|
+
if (!asyncHooks._AsyncLocalStorage) {
|
|
26
|
+
asyncHooks._AsyncLocalStorage = asyncHooks.AsyncLocalStorage
|
|
27
|
+
class TmpAsyncLocalStorage extends asyncHooks._AsyncLocalStorage {
|
|
28
|
+
disable() {
|
|
29
|
+
if (this._timer) clearTimeout(this._timer)
|
|
30
|
+
return super.disable()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_enable() {
|
|
34
|
+
if (this._timer) clearTimeout(this._timer)
|
|
35
|
+
this._timer = setTimeout(() => this.disable(), 60 * 1000)
|
|
36
|
+
this._timer.unref()
|
|
37
|
+
return super._enable()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
asyncHooks.AsyncLocalStorage = TmpAsyncLocalStorage
|
|
42
|
+
}
|
|
43
|
+
asyncHooks = null
|
|
@@ -1,54 +1,44 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
const { packArchive, packArchiveCLI, unpackArchive, unpackArchiveCLI } = require('./tar')
|
|
4
|
+
const { packArchiveCLI, unpackArchiveCLI } = require('./tar')
|
|
6
5
|
const { exists } = require('./utils')
|
|
7
6
|
|
|
8
|
-
// use tar command line interface
|
|
9
7
|
const TEMP_DIR = fs.realpathSync(require('os').tmpdir())
|
|
10
8
|
|
|
11
|
-
const packTarArchive = async (files, root, flat = false
|
|
12
|
-
|
|
9
|
+
const packTarArchive = async (files, root, flat = false) => {
|
|
10
|
+
if (typeof files === 'string') return await packArchiveCLI(files)
|
|
13
11
|
|
|
12
|
+
let tgzBuffer, temp
|
|
14
13
|
try {
|
|
15
14
|
temp = await fs.promises.mkdtemp(`${TEMP_DIR}${path.sep}tar-`)
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
files = files_
|
|
24
|
-
root = temp
|
|
15
|
+
for (const file of files) {
|
|
16
|
+
const fname = flat ? path.basename(file) : path.relative(root, file)
|
|
17
|
+
const destination = path.join(temp, fname)
|
|
18
|
+
const dirname = path.dirname(destination)
|
|
19
|
+
if (!await exists(dirname)) await fs.promises.mkdir(dirname, { recursive: true })
|
|
20
|
+
await fs.promises.copyFile(file, destination)
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
if (cli) {
|
|
29
|
-
const output = path.relative(root, path.join(temp, `${cds.utils.uuid()}.tgz`))
|
|
30
|
-
tgzBuffer = await packArchiveCLI(relativeFiles, root, output)
|
|
31
|
-
} else {
|
|
32
|
-
tgzBuffer = await packArchive(relativeFiles, root)
|
|
33
|
-
}
|
|
23
|
+
tgzBuffer = await packArchiveCLI(temp)
|
|
34
24
|
} finally {
|
|
35
25
|
if (await exists(temp)) {
|
|
36
|
-
await
|
|
26
|
+
await fs.promises.rm(temp, { recursive: true, force: true })
|
|
37
27
|
}
|
|
38
28
|
}
|
|
39
29
|
|
|
40
30
|
return tgzBuffer
|
|
41
31
|
}
|
|
42
32
|
|
|
43
|
-
const unpackTarArchive = async (buffer, folder
|
|
33
|
+
const unpackTarArchive = async (buffer, folder) => {
|
|
44
34
|
const temp = await fs.promises.mkdtemp(`${TEMP_DIR}${path.sep}tar-`)
|
|
45
35
|
const tgz = path.join(temp, 'resources.tgz')
|
|
46
36
|
await fs.promises.writeFile(tgz, Buffer.from(buffer), 'binary')
|
|
47
37
|
|
|
48
38
|
try {
|
|
49
|
-
|
|
39
|
+
await unpackArchiveCLI(tgz, folder)
|
|
50
40
|
} finally {
|
|
51
|
-
if (await exists(temp)) await
|
|
41
|
+
if (await exists(temp)) await fs.promises.rm(temp, { recursive: true, force: true })
|
|
52
42
|
}
|
|
53
43
|
}
|
|
54
44
|
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const exec = require('child_process').exec
|
|
4
|
+
const cds = require('../../../libx/_runtime/cds')
|
|
5
|
+
const { exists } = require('./utils')
|
|
4
6
|
|
|
5
|
-
const
|
|
6
|
-
const tar = require('tar')
|
|
7
|
-
return tar.c(
|
|
8
|
-
{
|
|
9
|
-
gzip: true,
|
|
10
|
-
preservePaths: false,
|
|
11
|
-
cwd: root
|
|
12
|
-
},
|
|
13
|
-
files
|
|
14
|
-
)
|
|
15
|
-
}
|
|
7
|
+
const TEMP_DIR = fs.realpathSync(require('os').tmpdir())
|
|
16
8
|
|
|
17
|
-
const _createArchiveCLI = (
|
|
18
|
-
const cmd = '
|
|
9
|
+
const _createArchiveCLI = (root, output) => {
|
|
10
|
+
const cmd = 'tar -zcvf ' + output + ' -C ' + root + ' .'
|
|
19
11
|
|
|
20
12
|
return new Promise((resolve, reject) => {
|
|
21
13
|
exec(cmd, err => {
|
|
@@ -25,32 +17,19 @@ const _createArchiveCLI = (files, root, output) => {
|
|
|
25
17
|
})
|
|
26
18
|
}
|
|
27
19
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const packArchiveCLI = async (files, root, output) => {
|
|
42
|
-
await _createArchiveCLI(files, root, output)
|
|
43
|
-
|
|
44
|
-
return fs.promises.readFile(path.join(root, output))
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const unpackArchive = async (tgz, folder) => {
|
|
48
|
-
const tar = require('tar')
|
|
49
|
-
await tar.x({
|
|
50
|
-
file: tgz,
|
|
51
|
-
gzip: true,
|
|
52
|
-
C: folder
|
|
53
|
-
})
|
|
20
|
+
const packArchiveCLI = async (root) => {
|
|
21
|
+
const temp = await fs.promises.mkdtemp(`${TEMP_DIR}${path.sep}tar-`)
|
|
22
|
+
const output = path.join(temp, `${cds.utils.uuid()}.tgz`)
|
|
23
|
+
try {
|
|
24
|
+
await _createArchiveCLI(root, output)
|
|
25
|
+
|
|
26
|
+
return fs.promises.readFile(output)
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
if (await exists(temp)) {
|
|
30
|
+
await fs.promises.rm(temp, { recursive: true, force: true })
|
|
31
|
+
}
|
|
32
|
+
}
|
|
54
33
|
}
|
|
55
34
|
|
|
56
35
|
const unpackArchiveCLI = async (tgz, folder) => {
|
|
@@ -65,8 +44,6 @@ const unpackArchiveCLI = async (tgz, folder) => {
|
|
|
65
44
|
}
|
|
66
45
|
|
|
67
46
|
module.exports = {
|
|
68
|
-
packArchive,
|
|
69
47
|
packArchiveCLI,
|
|
70
|
-
unpackArchive,
|
|
71
48
|
unpackArchiveCLI
|
|
72
49
|
}
|
|
@@ -57,7 +57,7 @@ const _log = (req, challenges) => {
|
|
|
57
57
|
LOG.debug(`User "${req.user.id}" request URL`, req.url, '\n', ...challengesLog)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const
|
|
60
|
+
const cap_auth_callback = (req, res, next, internalError, user, challenges) => {
|
|
61
61
|
// An internal error occurs during the authentication process
|
|
62
62
|
if (internalError) {
|
|
63
63
|
// REVISIT: What to do? Security log?
|
|
@@ -83,7 +83,7 @@ const _authCallback = (req, res, next, internalError, user, challenges) => {
|
|
|
83
83
|
|
|
84
84
|
const _mountCustomAuth = (srv, app, config) => {
|
|
85
85
|
const impl = cds.resolve(config.impl)
|
|
86
|
-
app.use(
|
|
86
|
+
app.use(_require(impl))
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
const _mountMockAuth = (srv, app, strategy, config) => {
|
|
@@ -92,12 +92,12 @@ const _mountMockAuth = (srv, app, strategy, config) => {
|
|
|
92
92
|
? new (require('./strategies/dummy'))()
|
|
93
93
|
: new (require('./strategies/mock'))(config, `mock_${srv.name}`)
|
|
94
94
|
|
|
95
|
-
app.use(
|
|
95
|
+
app.use(function cap_auth(req, res, next) {
|
|
96
96
|
let user, challenge
|
|
97
97
|
impl.success = arg => (user = arg)
|
|
98
98
|
impl.fail = arg => (challenge = arg)
|
|
99
99
|
impl.authenticate(req)
|
|
100
|
-
|
|
100
|
+
cap_auth_callback(req, res, next, undefined, user, [challenge])
|
|
101
101
|
})
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -119,10 +119,10 @@ const _mountPassportAuth = (srv, app, strategy, config) => {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// authenticate
|
|
122
|
-
app.use(
|
|
123
|
-
app.use(
|
|
122
|
+
app.use(passport.initialize())
|
|
123
|
+
app.use((req, res, next) => {
|
|
124
124
|
const options = { session: false, failWithError: true }
|
|
125
|
-
const callback =
|
|
125
|
+
const callback = cap_auth_callback.bind(undefined, req, res, next)
|
|
126
126
|
passport.authenticate(strategy === 'jwt' ? 'JWT' : strategy, options, callback)(req, res, next)
|
|
127
127
|
})
|
|
128
128
|
}
|
|
@@ -131,7 +131,9 @@ const _mountPassportAuth = (srv, app, strategy, config) => {
|
|
|
131
131
|
* export authentication middleware
|
|
132
132
|
*/
|
|
133
133
|
// eslint-disable-next-line complexity
|
|
134
|
-
module.exports = (srv,
|
|
134
|
+
module.exports = (srv, options = srv.options) => {
|
|
135
|
+
const handlers = [],
|
|
136
|
+
app = { use: h => handlers.push(h) }
|
|
135
137
|
// NOTE: options.auth is not an official API
|
|
136
138
|
let config = 'auth' in options ? options.auth : cds.env.requires.auth
|
|
137
139
|
if (!config) {
|
|
@@ -147,7 +149,7 @@ module.exports = (srv, app, options) => {
|
|
|
147
149
|
LOG._warn && LOG.warn(`No authentication configured. This is not recommended in production.`)
|
|
148
150
|
}
|
|
149
151
|
// no auth wanted > return
|
|
150
|
-
return
|
|
152
|
+
return handlers
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
// cds.env.requires.auth = { kind: 'xsuaa-auth' } was briefly documented on capire -> also support
|
|
@@ -181,11 +183,12 @@ module.exports = (srv, app, options) => {
|
|
|
181
183
|
(process.env.NODE_ENV === 'production' && config.credentials && config.restrict_all_services)
|
|
182
184
|
) {
|
|
183
185
|
if (!logged) LOG._debug && LOG.debug(`Enforcing authenticated users for all services`)
|
|
184
|
-
app.use(
|
|
186
|
+
app.use(cap_enforce_login)
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
// so we don't log the same stuff multiple times
|
|
188
190
|
logged = true
|
|
191
|
+
return handlers
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
const _strategy4 = config => {
|
|
@@ -196,7 +199,7 @@ const _strategy4 = config => {
|
|
|
196
199
|
throw new Error(`Authentication kind "${config.kind}" is not supported`)
|
|
197
200
|
}
|
|
198
201
|
|
|
199
|
-
const
|
|
202
|
+
const cap_enforce_login = (req, res, next) => {
|
|
200
203
|
if (req.user && req.user.is('authenticated-user')) return next() // pass if user is authenticated
|
|
201
204
|
if (!req.user || req.user._is_anonymous) {
|
|
202
205
|
if (req.user && req.user._challenges) res.set('WWW-Authenticate', req.user._challenges.join(';'))
|
|
@@ -107,8 +107,7 @@ class OData {
|
|
|
107
107
|
constructor(edm, csn, options = {}) {
|
|
108
108
|
this._validateEdm(edm)
|
|
109
109
|
this._options = options
|
|
110
|
-
this.
|
|
111
|
-
this._createOdataService(edm)
|
|
110
|
+
this._createOdataService(edm, csn)
|
|
112
111
|
}
|
|
113
112
|
|
|
114
113
|
_validateEdm(edm) {
|
|
@@ -118,7 +117,7 @@ class OData {
|
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
119
|
|
|
121
|
-
_createOdataService(edm) {
|
|
120
|
+
_createOdataService(edm, csn) {
|
|
122
121
|
const ServiceFactory = require('./okra/odata-server').ServiceFactory
|
|
123
122
|
|
|
124
123
|
// skip okra's validation in production or implicitly for w4 and x4
|
|
@@ -133,7 +132,7 @@ class OData {
|
|
|
133
132
|
effective.odata.proxies ||
|
|
134
133
|
effective.odata.xrefs
|
|
135
134
|
|
|
136
|
-
this._odataService = ServiceFactory.createService(edm, _config(edm,
|
|
135
|
+
this._odataService = ServiceFactory.createService(edm, _config(edm, csn, this._options)).trust(isTrusted)
|
|
137
136
|
|
|
138
137
|
// will be added to express app like app.use('/base/path/', service) and odata-v4 wants app.use('/', service) if basePath is set
|
|
139
138
|
this._odataService.setBasePath('/')
|
|
@@ -165,8 +164,7 @@ class OData {
|
|
|
165
164
|
req,
|
|
166
165
|
res
|
|
167
166
|
} = data
|
|
168
|
-
|
|
169
|
-
const tx = (txs[odataContext.id] = cdsService.tx({ user, req, res, _model: cdsService.model }))
|
|
167
|
+
const tx = (txs[odataContext.id] = cdsService.tx({ user, req, res }))
|
|
170
168
|
cds.context = tx.context
|
|
171
169
|
// for collecting results and errors
|
|
172
170
|
data.results = data.results || {}
|
|
@@ -183,19 +181,8 @@ class OData {
|
|
|
183
181
|
if (errors) {
|
|
184
182
|
// rollback without errors to not trigger srv.on('error') with array
|
|
185
183
|
await tx.rollback()
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const failedRequests = {}
|
|
189
|
-
|
|
190
|
-
for (const e of errors) {
|
|
191
|
-
const { error: err, req } = e
|
|
192
|
-
for (const each of cdsService._handlers._error) each.handler.call(cdsService, err, req)
|
|
193
|
-
const requestId = req._.odataReq.getOdataRequestId()
|
|
194
|
-
const { error, statusCode } = normalizeError(err, req)
|
|
195
|
-
failedRequests[requestId] = Object.assign(error, { statusCode })
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
done(new Error(`Atomicity group "${odataContext.id}" failed`), { failedRequests })
|
|
184
|
+
|
|
185
|
+
done()
|
|
199
186
|
return
|
|
200
187
|
}
|
|
201
188
|
|
|
@@ -226,6 +213,7 @@ class OData {
|
|
|
226
213
|
this._odataService.use(DATA_DELETE_HANDLER, _delete(cdsService))
|
|
227
214
|
|
|
228
215
|
this._odataService.use(ACTION_EXECUTE_HANDLER, _action(cdsService))
|
|
216
|
+
return this
|
|
229
217
|
}
|
|
230
218
|
|
|
231
219
|
/**
|
|
@@ -163,12 +163,9 @@ class ODataRequest extends cds.Request {
|
|
|
163
163
|
*/
|
|
164
164
|
const { user } = req
|
|
165
165
|
|
|
166
|
-
// REVISIT: _model should not be necessary
|
|
167
|
-
const _model = service.model
|
|
168
|
-
|
|
169
166
|
// REVISIT: public API for query options (express style req.query already in use)?
|
|
170
167
|
const _queryOptions = odataReq.getQueryOptions()
|
|
171
|
-
super({ event, target, data, query, user, method, headers, req, res,
|
|
168
|
+
super({ event, target, data, query, user, method, headers, req, res, _queryOptions })
|
|
172
169
|
}
|
|
173
170
|
|
|
174
171
|
/*
|
|
@@ -77,10 +77,7 @@ const action = service => {
|
|
|
77
77
|
} catch (e) {
|
|
78
78
|
err = e
|
|
79
79
|
|
|
80
|
-
if (changeset) {
|
|
81
|
-
// for passing into rollback
|
|
82
|
-
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
83
|
-
} else {
|
|
80
|
+
if (!changeset) {
|
|
84
81
|
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
85
82
|
await tx.rollback(e).catch(() => {})
|
|
86
83
|
}
|
|
@@ -63,10 +63,7 @@ const create = service => {
|
|
|
63
63
|
} catch (e) {
|
|
64
64
|
err = e
|
|
65
65
|
|
|
66
|
-
if (changeset) {
|
|
67
|
-
// for passing into rollback
|
|
68
|
-
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
69
|
-
} else {
|
|
66
|
+
if (!changeset) {
|
|
70
67
|
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
71
68
|
await tx.rollback(e).catch(() => {})
|
|
72
69
|
}
|
|
@@ -44,10 +44,7 @@ const del = service => {
|
|
|
44
44
|
} catch (e) {
|
|
45
45
|
err = e
|
|
46
46
|
|
|
47
|
-
if (changeset) {
|
|
48
|
-
// for passing into rollback
|
|
49
|
-
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
50
|
-
} else {
|
|
47
|
+
if (!changeset) {
|
|
51
48
|
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
52
49
|
await tx.rollback(e).catch(() => {})
|
|
53
50
|
}
|
|
@@ -114,13 +114,13 @@ const getErrorHandler = (crashOnError = true, srv) => {
|
|
|
114
114
|
|
|
115
115
|
// invoke srv.on('error', function (err, req) { ... }) here in special situations
|
|
116
116
|
// REVISIT: if for compat reasons, remove once cds^5.1
|
|
117
|
-
if (srv._handlers._error) {
|
|
117
|
+
if (srv._handlers._error.length) {
|
|
118
118
|
let ctx = cds.context
|
|
119
119
|
if (!ctx) {
|
|
120
120
|
// > error before req was dispatched
|
|
121
121
|
ctx = new cds.Request({ req, res: req.res, user: req.user || new cds.User.Anonymous() })
|
|
122
122
|
for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
|
|
123
|
-
} else
|
|
123
|
+
} else {
|
|
124
124
|
// > error after req was dispatched, e.g., serialization error in okra
|
|
125
125
|
for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
|
|
126
126
|
}
|
|
@@ -5,10 +5,6 @@ const { toODataResult } = require('../utils/result')
|
|
|
5
5
|
const { normalizeError } = require('../../../../common/error/frontend')
|
|
6
6
|
const getError = require('../../../../common/error')
|
|
7
7
|
|
|
8
|
-
const _getMetadata4Tenant = async (tenant, locale, service) => {
|
|
9
|
-
return await cds.mtx.getEdmx(tenant, service.name, locale)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
8
|
/**
|
|
13
9
|
* Provide localized metadata handler.
|
|
14
10
|
*
|
|
@@ -23,21 +19,12 @@ const metadata = service => {
|
|
|
23
19
|
const locale = odataRes.getContract().getLocale()
|
|
24
20
|
|
|
25
21
|
try {
|
|
26
|
-
let edmx
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!edmx) {
|
|
33
|
-
edmx = cds.localize(
|
|
34
|
-
service.model,
|
|
35
|
-
locale,
|
|
36
|
-
// REVISIT: we could cache this in a weak map
|
|
37
|
-
cds.compile.to.edmx(service.model, { service: service.definition.name })
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
22
|
+
let edmx = cds.localize(
|
|
23
|
+
service.model,
|
|
24
|
+
locale,
|
|
25
|
+
// REVISIT: we could cache this in model._cached
|
|
26
|
+
cds.compile.to.edmx(service.model, { service: service.definition.name })
|
|
27
|
+
)
|
|
41
28
|
return next(null, toODataResult(edmx))
|
|
42
29
|
} catch (e) {
|
|
43
30
|
if (LOG._error) {
|
|
@@ -81,7 +81,8 @@ const _getCount = async (tx, readReq) => {
|
|
|
81
81
|
// REVISIT: this process appears to be rather clumsy
|
|
82
82
|
// Copy CQN including from, where and search + changing columns
|
|
83
83
|
const select = SELECT.from(readReq.query.SELECT.from)
|
|
84
|
-
|
|
84
|
+
// { val: 1 } is used on purpose, as "numbers" are not used as param in prepared stmt
|
|
85
|
+
select.SELECT.columns = [{ func: 'count', args: [{ val: 1 }], as: '$count' }]
|
|
85
86
|
|
|
86
87
|
if (readReq.query.SELECT.where) select.SELECT.where = readReq.query.SELECT.where
|
|
87
88
|
if (readReq.query.SELECT.search) select.SELECT.search = readReq.query.SELECT.search
|
|
@@ -484,10 +485,7 @@ const read = service => {
|
|
|
484
485
|
} catch (e) {
|
|
485
486
|
err = e
|
|
486
487
|
|
|
487
|
-
if (changeset) {
|
|
488
|
-
// for passing into rollback
|
|
489
|
-
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
490
|
-
} else {
|
|
488
|
+
if (!changeset) {
|
|
491
489
|
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
492
490
|
await tx.rollback(e).catch(() => {})
|
|
493
491
|
}
|
|
@@ -6,7 +6,7 @@ module.exports = srv => {
|
|
|
6
6
|
const requires = getRequiresAsArray(srv.definition)
|
|
7
7
|
const restricted = isRestricted(srv)
|
|
8
8
|
|
|
9
|
-
return (odataReq, odataRes, next)
|
|
9
|
+
return function ODataRequestHandler(odataReq, odataRes, next) {
|
|
10
10
|
const req = odataReq.getBatchApplicationData()
|
|
11
11
|
? odataReq.getBatchApplicationData().req
|
|
12
12
|
: odataReq.getIncomingRequest()
|
|
@@ -174,10 +174,7 @@ const update = service => {
|
|
|
174
174
|
} catch (e) {
|
|
175
175
|
err = e
|
|
176
176
|
|
|
177
|
-
if (changeset) {
|
|
178
|
-
// for passing into rollback
|
|
179
|
-
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
180
|
-
} else {
|
|
177
|
+
if (!changeset) {
|
|
181
178
|
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
182
179
|
await tx.rollback(e).catch(() => {})
|
|
183
180
|
}
|
|
@@ -68,8 +68,6 @@ const MULTI_LINE_STRING_VALIDATION = new RegExp('^' + SRID + 'MultiLineString\\(
|
|
|
68
68
|
const MULTI_POLYGON_VALIDATION = new RegExp('^' + SRID + 'MultiPolygon\\((' + MULTI_POLYGON + ')\\)$', 'i')
|
|
69
69
|
const COLLECTION_VALIDATION = new RegExp('^' + SRID + 'Collection\\((' + MULTI_GEO_LITERAL + ')\\)$', 'i')
|
|
70
70
|
|
|
71
|
-
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}={2})$/
|
|
72
|
-
|
|
73
71
|
function _getBase64(val) {
|
|
74
72
|
if (isInvalidBase64string(val)) return
|
|
75
73
|
// convert url-safe to standard base64
|
|
@@ -40,8 +40,10 @@ class DeserializerFactory {
|
|
|
40
40
|
let additionalInformation = { hasDelta: false }
|
|
41
41
|
const deserializer = new ResourceJsonDeserializer(edm, jsonContentTypeInfo)
|
|
42
42
|
return (edmObject, value) => {
|
|
43
|
+
const body = deserializer[name](edmObject, value, expand, additionalInformation)
|
|
44
|
+
|
|
43
45
|
return {
|
|
44
|
-
body
|
|
46
|
+
body,
|
|
45
47
|
expand,
|
|
46
48
|
additionalInformation
|
|
47
49
|
}
|
|
@@ -1,7 +1,41 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { alias2ref } = require('../../../common/utils/csn') // REVISIT: eliminate that
|
|
2
|
+
const cds = require('../../../cds')
|
|
3
|
+
const OData = require('./OData')
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* This is the express handler for a specific OData endpoint.
|
|
7
|
+
* Note: the same service can be served at different endpoints.
|
|
8
|
+
*/
|
|
9
|
+
module.exports = srv => {
|
|
10
|
+
const okra = new OkraAdapter(srv)
|
|
11
|
+
return okra.process.bind(okra)
|
|
5
12
|
}
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
function OkraAdapter(srv, model = srv.model) {
|
|
15
|
+
const edm = cds.compile.to.edm(model, { service: srv.definition?.name || srv.name })
|
|
16
|
+
alias2ref(srv, edm) // REVISIT: eliminate that -> done again and again -> search for _alias2ref
|
|
17
|
+
return new OData(edm, model, srv.options).addCDSServiceToChannel(srv)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//////////////////////////////////////////////////////////////////////////////
|
|
21
|
+
//
|
|
22
|
+
// REVISIT: Move to ExtensibilityService
|
|
23
|
+
//
|
|
24
|
+
if (cds.mtx || cds.requires.extensibility || cds.requires.toggles)
|
|
25
|
+
module.exports = srv => {
|
|
26
|
+
const id = `${++unique} - ${srv.path}` // REVISIT: this is to allow running multiple express apps serving same endpoints, as done by some questionable tests
|
|
27
|
+
return function ODataAdapter(req, res) {
|
|
28
|
+
const model = cds.context?.model || srv.model
|
|
29
|
+
if (!model._cached) Object.defineProperty(model, '_cached', { value: {} })
|
|
30
|
+
|
|
31
|
+
// Note: cache is attached to model cache so they get disposed when models are evicted from cache
|
|
32
|
+
let adapters = model._cached._odata_adapters || (model._cached._odata_adapters = {})
|
|
33
|
+
let okra = adapters[id]
|
|
34
|
+
if (!okra) {
|
|
35
|
+
const _srv = { __proto__: srv, _real_srv: srv, model } // REVISIT: we need to do that better in new adapters
|
|
36
|
+
okra = adapters[id] = new OkraAdapter(_srv, model)
|
|
37
|
+
}
|
|
38
|
+
return okra.process(req, res)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let unique = 0
|
|
@@ -22,10 +22,8 @@ const _isNoAccessError = e => Number(e.code) === 403 || Number(e.code) === 401
|
|
|
22
22
|
const _isNotFoundError = e => Number(e.code) === 404
|
|
23
23
|
const _isEntityNotReadableError = e => Number(e.code) === 405
|
|
24
24
|
|
|
25
|
-
const _handleReadError =
|
|
25
|
+
const _handleReadError = err => {
|
|
26
26
|
if (!(_isNoAccessError(err) || _isEntityNotReadableError(err) || _isNotFoundError(err))) throw err
|
|
27
|
-
const log = Object.assign(err, { level: 'ERROR', message: normalizeError(err, req).error.message })
|
|
28
|
-
process.env.NODE_ENV !== 'production' && LOG._warn && LOG.warn(log)
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
const _getOperationQueryColumns = urlQueryOptions => {
|
|
@@ -18,6 +18,10 @@ module.exports = class Differ {
|
|
|
18
18
|
_createSelectColumnsForDelete(entity) {
|
|
19
19
|
const columns = []
|
|
20
20
|
for (const element of Object.values(entity.elements)) {
|
|
21
|
+
// Don't take into account virtual or computed properties to make the diff result
|
|
22
|
+
// consistent with the ones for UPDATE/CREATE (where we don't have access to that
|
|
23
|
+
// information).
|
|
24
|
+
if (!element.key && (element.virtual || element['@Core.Computed'])) continue
|
|
21
25
|
if (element.isComposition) {
|
|
22
26
|
if (element._target._hasPersistenceSkip) continue
|
|
23
27
|
columns.push({
|
|
@@ -9,35 +9,7 @@ const getFeatureNotSupportedError = message => {
|
|
|
9
9
|
return getError(501, `Feature is not supported: ${message}`)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const getAuditLogNotWrittenError = (rootCauseError, phase, event) => {
|
|
13
|
-
const errorMessage =
|
|
14
|
-
!phase || event === 'READ' ? 'Audit log could not be written' : `Audit log could not be written ${phase}`
|
|
15
|
-
const error = new Error(errorMessage)
|
|
16
|
-
error.rootCause = rootCauseError
|
|
17
|
-
return error
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const hasBeenCalledError = (method, query) => {
|
|
21
|
-
return new Error(`Method ${method} has been called before. Invalid CQN: ${JSON.stringify(query)}`)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const unexpectedFunctionCallError = (functionName, expectedFunction) => {
|
|
25
|
-
return new Error(`Cannot build CQN object. Invalid call of "${functionName}" before "${expectedFunction}"`)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const invalidFunctionArgumentError = (statement, arg) => {
|
|
29
|
-
const details = JSON.stringify(arg, (key, value) => (value === undefined ? '__undefined__' : value)).replace(
|
|
30
|
-
/"__undefined__"/g,
|
|
31
|
-
'undefined'
|
|
32
|
-
)
|
|
33
|
-
return new Error(`Cannot build ${statement} statement. Invalid data provided: ${details}`)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
12
|
module.exports = {
|
|
37
13
|
getModelNotDefinedError,
|
|
38
|
-
getFeatureNotSupportedError
|
|
39
|
-
getAuditLogNotWrittenError,
|
|
40
|
-
hasBeenCalledError,
|
|
41
|
-
unexpectedFunctionCallError,
|
|
42
|
-
invalidFunctionArgumentError
|
|
14
|
+
getFeatureNotSupportedError
|
|
43
15
|
}
|
|
@@ -74,8 +74,9 @@ ENTITY_IS_NOT_CRUD=Entity "{0}" is not {1}
|
|
|
74
74
|
ENTITY_IS_NOT_CRUD_VIA_NAVIGATION=Entity "{0}" is not {1} via navigation "{2}"
|
|
75
75
|
ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitly exposed as part of the service
|
|
76
76
|
EXPAND_IS_RESTRICTED=Navigation property "{0}" is not allowed for expand operation
|
|
77
|
-
EXPAND_COUNT_UNSUPPORTED="
|
|
77
|
+
EXPAND_COUNT_UNSUPPORTED="/$count" is not supported for expand operation
|
|
78
78
|
ORDERBY_LAMBDA_UNSUPPORTED="$orderby" does not support lambda
|
|
79
|
+
EXPAND_APPLY_UNSUPPORTED="$apply" is not supported for expand operation
|
|
79
80
|
|
|
80
81
|
# rest protocol adapter
|
|
81
82
|
INVALID_RESOURCE="{0}" is not a valid resource
|