@sap/cds 7.9.4 → 8.0.4
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 +128 -3659
- package/_i18n/i18n_en_US_saptrc.properties +113 -0
- package/_i18n/i18n_zh_CN.properties +7 -4
- package/app/index.css +129 -0
- package/app/index.html +16 -64
- package/app/index.js +14 -9
- package/bin/args.js +34 -0
- package/bin/serve.js +18 -24
- package/bin/test.js +97 -0
- package/common.cds +5 -12
- package/eslint.config.mjs +133 -0
- package/lib/auth/basic-auth.js +16 -20
- package/lib/auth/dummy-auth.js +1 -1
- package/lib/auth/ias-auth.js +9 -41
- package/lib/auth/index.js +1 -14
- package/lib/auth/jwt-auth.js +10 -40
- package/lib/compile/cds-compile.js +1 -2
- package/lib/compile/cdsc.js +21 -26
- package/lib/compile/etc/_localized.js +1 -6
- package/lib/compile/etc/csv.js +1 -1
- package/lib/compile/etc/properties.js +1 -1
- package/lib/compile/for/java.js +1 -1
- package/lib/compile/for/lean_drafts.js +4 -6
- package/lib/compile/for/nodejs.js +1 -1
- package/lib/compile/parse.js +4 -0
- package/lib/compile/resolve.js +4 -4
- package/lib/compile/to/edm-files.js +16 -23
- package/lib/compile/to/hana.js +27 -0
- package/lib/compile/to/json.js +1 -1
- package/lib/compile/to/sql.js +5 -1
- package/lib/compile/to/yaml.js +3 -3
- package/lib/dbs/cds-deploy.js +4 -2
- package/lib/env/cds-env.js +10 -14
- package/lib/env/cds-requires.js +30 -13
- package/lib/env/defaults.js +46 -16
- package/lib/env/plugins.js +1 -1
- package/lib/env/schemas/cds-rc.js +8 -4
- package/lib/env/schemas/index.js +7 -7
- package/lib/env/serviceBindings.js +1 -1
- package/lib/index.js +12 -10
- package/lib/lazy.js +1 -1
- package/lib/linked/classes.js +36 -8
- package/lib/linked/entities.js +2 -10
- package/lib/linked/models.js +2 -1
- package/lib/linked/validate.js +292 -0
- package/lib/log/cds-error.js +0 -6
- package/lib/log/cds-log.js +3 -3
- package/lib/log/format/json.js +1 -1
- package/lib/log/service/index.js +0 -1
- package/lib/plugins.js +2 -2
- package/lib/ql/Query.js +2 -10
- package/lib/ql/SELECT.js +1 -1
- package/lib/ql/Whereable.js +3 -2
- package/lib/req/cds-context.js +14 -25
- package/lib/req/context.js +23 -25
- package/lib/req/request.js +1 -34
- package/lib/req/user.js +47 -35
- package/lib/srv/bindings.js +1 -1
- package/lib/srv/cds-connect.js +4 -4
- package/lib/srv/cds-serve.js +2 -2
- package/lib/srv/factory.js +1 -1
- package/lib/srv/middlewares/cds-context.js +11 -22
- package/lib/srv/middlewares/ctx-model.js +2 -3
- package/lib/srv/middlewares/errors.js +41 -8
- package/lib/srv/middlewares/index.js +3 -3
- package/lib/srv/middlewares/trace.js +0 -2
- package/lib/srv/protocols/hcql.js +15 -10
- package/lib/srv/protocols/http.js +44 -49
- package/lib/srv/protocols/index.js +1 -23
- package/lib/srv/protocols/odata-v4.js +12 -74
- package/lib/srv/protocols/rest.js +1 -13
- package/lib/srv/srv-api.js +0 -20
- package/lib/srv/srv-dispatch.js +3 -2
- package/lib/srv/srv-handlers.js +22 -11
- package/lib/srv/srv-methods.js +2 -2
- package/lib/srv/srv-models.js +3 -36
- package/lib/test/expect.js +343 -0
- package/lib/test/index.js +2 -0
- package/lib/test/reporter.js +176 -0
- package/lib/utils/axios.js +10 -9
- package/lib/utils/cds-test.js +85 -36
- package/lib/utils/cds-utils.js +54 -7
- package/lib/utils/check-version.js +0 -4
- package/lib/utils/colors.js +49 -0
- package/lib/utils/data.js +5 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +3 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +6 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +12 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +2 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +9 -43
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +4 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -3
- package/libx/_runtime/cds-services/util/assert.js +1 -1
- package/libx/_runtime/cds.js +10 -3
- package/libx/_runtime/common/Service.js +12 -32
- package/libx/_runtime/common/aspects/any.js +1 -0
- package/libx/_runtime/common/code-ext/execute.js +1 -1
- package/libx/_runtime/common/code-ext/worker.js +0 -1
- package/libx/_runtime/common/composition/data.js +0 -1
- package/libx/_runtime/common/composition/delete.js +0 -1
- package/libx/_runtime/common/composition/tree.js +0 -1
- package/libx/_runtime/common/composition/update.js +3 -3
- package/libx/_runtime/common/error/frontend.js +21 -12
- package/libx/_runtime/common/error/log.js +36 -0
- package/libx/_runtime/common/error/utils.js +2 -5
- package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
- package/libx/_runtime/common/generic/auth/restrict.js +23 -42
- package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
- package/libx/_runtime/common/generic/auth/utils.js +91 -88
- package/libx/_runtime/common/generic/crud.js +6 -5
- package/libx/_runtime/common/generic/etag.js +7 -12
- package/libx/_runtime/common/generic/input.js +70 -68
- package/libx/_runtime/common/generic/paging.js +1 -0
- package/libx/_runtime/common/generic/sorting.js +1 -0
- package/libx/_runtime/common/generic/temporal.js +8 -2
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/i18n/messages.properties +3 -1
- package/libx/_runtime/common/utils/binary.js +8 -2
- package/libx/_runtime/common/utils/compareJson.js +5 -1
- package/libx/_runtime/common/utils/copy.js +6 -11
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
- package/libx/_runtime/common/utils/differ.js +3 -6
- package/libx/_runtime/common/utils/keys.js +77 -18
- package/libx/_runtime/common/utils/postProcess.js +12 -15
- package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -3
- package/libx/_runtime/common/utils/restrictions.js +45 -17
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
- package/libx/_runtime/common/utils/stream.js +3 -16
- package/libx/_runtime/common/utils/streamProp.js +8 -18
- package/libx/_runtime/common/utils/structured.js +1 -1
- package/libx/_runtime/common/utils/ucsn.js +0 -2
- package/libx/_runtime/db/Service.js +0 -72
- package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
- package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
- package/libx/_runtime/db/generic/input.js +3 -8
- package/libx/_runtime/db/generic/rewrite.js +1 -0
- package/libx/_runtime/db/query/read.js +2 -2
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/utils/columns.js +2 -6
- package/libx/_runtime/fiori/lean-draft.js +138 -56
- package/libx/_runtime/hana/Service.js +0 -1
- package/libx/_runtime/hana/driver.js +1 -1
- package/libx/_runtime/hana/dynatrace.js +1 -2
- package/libx/_runtime/hana/pool.js +11 -21
- package/libx/_runtime/hana/streaming.js +0 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
- package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
- package/libx/_runtime/messaging/event-broker.js +54 -27
- package/libx/_runtime/messaging/file-based.js +3 -3
- package/libx/_runtime/messaging/http-utils/token.js +1 -1
- package/libx/_runtime/messaging/kafka.js +2 -2
- package/libx/_runtime/messaging/redis-messaging.js +0 -1
- package/libx/_runtime/remote/Service.js +25 -25
- package/libx/_runtime/remote/utils/client.js +4 -5
- package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
- package/libx/_runtime/remote/utils/data.js +0 -1
- package/libx/_runtime/sqlite/Service.js +1 -2
- package/libx/_runtime/ucl/Service.js +37 -78
- package/libx/common/assert/index.js +22 -21
- package/libx/common/assert/type-relaxed.js +39 -0
- package/libx/common/assert/utils.js +3 -2
- package/libx/common/assert/validation.js +3 -8
- package/libx/common/utils/index.js +5 -0
- package/libx/common/utils/path.js +51 -0
- package/libx/odata/ODataAdapter.js +126 -0
- package/libx/odata/index.js +15 -2
- package/libx/odata/middleware/batch.js +320 -84
- package/libx/odata/middleware/body-parser.js +33 -0
- package/libx/odata/middleware/create.js +44 -59
- package/libx/odata/middleware/delete.js +23 -12
- package/libx/odata/middleware/error.js +30 -6
- package/libx/odata/middleware/metadata.js +38 -26
- package/libx/odata/middleware/operation.js +93 -69
- package/libx/odata/middleware/parse.js +6 -8
- package/libx/odata/middleware/read.js +117 -93
- package/libx/odata/middleware/service-document.js +22 -19
- package/libx/odata/middleware/stream.js +54 -56
- package/libx/odata/middleware/update.js +79 -87
- package/libx/odata/parse/afterburner.js +191 -175
- package/libx/odata/parse/cqn2odata.js +5 -5
- package/libx/odata/parse/grammar.peggy +27 -20
- package/libx/odata/parse/multipartToJson.js +17 -9
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/etag.js +14 -6
- package/libx/odata/utils/index.js +84 -12
- package/libx/odata/utils/metadata.js +161 -0
- package/libx/odata/utils/postProcess.js +89 -0
- package/libx/odata/utils/readAfterWrite.js +134 -17
- package/libx/odata/utils/result.js +36 -142
- package/libx/outbox/index.js +4 -3
- package/libx/rest/RestAdapter.js +115 -182
- package/libx/rest/middleware/create.js +28 -24
- package/libx/rest/middleware/delete.js +7 -10
- package/libx/rest/middleware/error.js +26 -16
- package/libx/rest/middleware/operation.js +48 -41
- package/libx/rest/middleware/parse.js +128 -126
- package/libx/rest/middleware/read.js +20 -27
- package/libx/rest/middleware/update.js +26 -31
- package/package.json +17 -8
- package/server.js +4 -2
- package/apis/cds.d.ts +0 -3
- package/apis/core.d.ts +0 -21
- package/apis/cqn.d.ts +0 -18
- package/apis/csn.d.ts +0 -21
- package/apis/events.d.ts +0 -18
- package/apis/internal/inference.d.ts +0 -18
- package/apis/linked.d.ts +0 -18
- package/apis/log.d.ts +0 -20
- package/apis/models.d.ts +0 -18
- package/apis/ql.d.ts +0 -18
- package/apis/reflect.d.ts +0 -32
- package/apis/server.d.ts +0 -18
- package/apis/services.d.ts +0 -22
- package/bin/cds-serve.js +0 -56
- package/lib/compile/to/gql.js +0 -15
- package/lib/srv/protocols/_legacy.js +0 -44
- package/lib/utils/jest.js +0 -43
- package/libx/_runtime/auth/index.js +0 -193
- package/libx/_runtime/auth/strategies/JWT.js +0 -37
- package/libx/_runtime/auth/strategies/basic.js +0 -20
- package/libx/_runtime/auth/strategies/dummy.js +0 -14
- package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
- package/libx/_runtime/auth/strategies/mock.js +0 -77
- package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
- package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
- package/libx/_runtime/common/perf/index.js +0 -19
- package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
- package/libx/_runtime/fiori/draft.js +0 -2
- package/libx/_runtime/fiori/generic/activate.js +0 -190
- package/libx/_runtime/fiori/generic/before.js +0 -201
- package/libx/_runtime/fiori/generic/cancel.js +0 -19
- package/libx/_runtime/fiori/generic/delete.js +0 -21
- package/libx/_runtime/fiori/generic/edit.js +0 -157
- package/libx/_runtime/fiori/generic/index.js +0 -25
- package/libx/_runtime/fiori/generic/new.js +0 -82
- package/libx/_runtime/fiori/generic/patch.js +0 -101
- package/libx/_runtime/fiori/generic/prepare.js +0 -57
- package/libx/_runtime/fiori/generic/read.js +0 -1340
- package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
- package/libx/_runtime/fiori/utils/csn.js +0 -13
- package/libx/_runtime/fiori/utils/delete.js +0 -114
- package/libx/_runtime/fiori/utils/handler.js +0 -264
- package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
- package/libx/_runtime/fiori/utils/req.js +0 -23
- package/libx/_runtime/fiori/utils/stream.js +0 -36
- package/libx/_runtime/fiori/utils/where.js +0 -254
- package/libx/_runtime/index.js +0 -22
- package/libx/odata/utils/handler.js +0 -120
- package/libx/odata/utils/metaInfo.js +0 -410
- package/libx/odata/utils/path.js +0 -75
- package/libx/rest/RestRequest.js +0 -32
- package/libx/rest/index.js +0 -3
- package/libx/rest/readme.md +0 -1
- /package/libx/common/assert/{type.js → type-strict.js} +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const cds = require('../../_runtime/cds')
|
|
2
2
|
|
|
3
|
+
/*
|
|
4
|
+
|
|
3
5
|
// requesting logger without module on purpose!
|
|
4
6
|
const LOG = cds.log()
|
|
5
7
|
|
|
@@ -36,23 +38,31 @@ const _log = err => {
|
|
|
36
38
|
if (_details) err.details = _details
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// invoke srv.on('error', function (err, req) { ... }) here in special situations
|
|
43
|
-
let ctx = cds.context
|
|
44
|
-
if (!ctx) {
|
|
45
|
-
// > error before req was dispatched
|
|
46
|
-
ctx = new cds.Request({ req, res: req.res, user: req.user || new cds.User.default })
|
|
47
|
-
for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
|
|
48
|
-
} else if (ctx._tx?._done !== 'rolled back') {
|
|
49
|
-
for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
|
|
50
|
-
}
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
const _log = require('../../_runtime/common/error/log')
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
const { normalizeError } = require('../../_runtime/common/error/frontend')
|
|
46
|
+
|
|
47
|
+
function noop_error_middleware(err, req, res, next) {
|
|
48
|
+
next(err)
|
|
49
|
+
}
|
|
54
50
|
|
|
55
|
-
|
|
51
|
+
module.exports = () => {
|
|
52
|
+
// REVISIT: unofficial hack for afc!!!
|
|
53
|
+
if (cds.env.features.rest_error_handler === false) return noop_error_middleware
|
|
56
54
|
|
|
57
|
-
|
|
55
|
+
return function rest_error(err, req, res, next) {
|
|
56
|
+
if (err == 401 || err.code == 401) return next(err) // speed up logins, at least temporary until we reviewed and eliminated overhead that may be involved below
|
|
57
|
+
// REVISIT: keep?
|
|
58
|
+
// log the error (4xx -> warn)
|
|
59
|
+
_log(err)
|
|
60
|
+
|
|
61
|
+
const { error, statusCode } = normalizeError(err, req)
|
|
62
|
+
|
|
63
|
+
// NOTE: express also looks for numbers in err.status or err.statusCode
|
|
64
|
+
error.statusCode = statusCode
|
|
65
|
+
|
|
66
|
+
next(error)
|
|
67
|
+
}
|
|
58
68
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
const cds = require('../../_runtime/cds')
|
|
2
2
|
|
|
3
|
-
const RestRequest = require('../RestRequest')
|
|
4
|
-
|
|
5
3
|
const { checkStatic } = require('../../_runtime/cds-services/util/assert')
|
|
6
4
|
const getError = require('../../_runtime/common/error')
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
// REVISIT: strict or relaxed type checkers?
|
|
7
|
+
const typeCheckers = require('../../common/assert/type-strict')
|
|
9
8
|
|
|
10
9
|
// REVISIT: use i18n
|
|
11
10
|
const _enrichErrorDetails = (isPrimitive, error) => {
|
|
@@ -38,7 +37,6 @@ const _validateReturnType = (operation, data) => {
|
|
|
38
37
|
|
|
39
38
|
// .type of action/function behaves different to .type of other csn elements
|
|
40
39
|
// Return type contains primitives
|
|
41
|
-
// eslint-disable-next-line no-proto
|
|
42
40
|
const _type = typeof returnType._type === 'object' ? returnType.__proto__._type : returnType._type // REVISIT: super dirty hack for compiler's to.edmx polluting the csn definitions with ._type -> please use Symbols instead
|
|
43
41
|
const typeChecker = typeCheckers[_type] // IMPORTANT: use ._type
|
|
44
42
|
if (typeChecker) {
|
|
@@ -71,45 +69,54 @@ const _validateReturnType = (operation, data) => {
|
|
|
71
69
|
return true
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const { _query: query, _operation: operation, _data: data, _params } = _req
|
|
77
|
-
|
|
78
|
-
let result
|
|
79
|
-
|
|
80
|
-
const req = query
|
|
81
|
-
? new RestRequest({ query, event: operation.name, data, params: _params })
|
|
82
|
-
: new RestRequest({ event: operation.name.replace(`${srv.namespace}.`, ''), data, params: _params })
|
|
83
|
-
result = await srv.dispatch(req)
|
|
84
|
-
|
|
85
|
-
if (!operation.returns) return { status: 204 }
|
|
86
|
-
|
|
87
|
-
// REVISIT: do not use from old rest adapter
|
|
88
|
-
// REVISIT: new impl should return instead of throwing to avoid try catch
|
|
89
|
-
_validateReturnType(operation, result)
|
|
90
|
-
|
|
91
|
-
// set content-type header to text/plain for returned primitive data types, except for boolean
|
|
92
|
-
const returnType = operation.returns._type
|
|
93
|
-
if (
|
|
94
|
-
!_res.get('content-type') &&
|
|
95
|
-
!operation.returns.items &&
|
|
96
|
-
returnType &&
|
|
97
|
-
cds.builtin.types[returnType] &&
|
|
98
|
-
returnType !== 'cds.Boolean'
|
|
99
|
-
) {
|
|
100
|
-
_res.set('content-type', 'text/plain')
|
|
101
|
-
}
|
|
72
|
+
module.exports = adapter => {
|
|
73
|
+
const { service } = adapter
|
|
102
74
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
75
|
+
return async function operation(req, res) {
|
|
76
|
+
const { _query: query, _operation: operation, _data: data, _params: params } = req
|
|
77
|
+
|
|
78
|
+
let result
|
|
79
|
+
|
|
80
|
+
result = await service.dispatch(
|
|
81
|
+
adapter.request4(
|
|
82
|
+
Object.assign(
|
|
83
|
+
query
|
|
84
|
+
? { query, event: operation.name }
|
|
85
|
+
: { event: operation.name.replace(`${service.definition.name}.`, '') },
|
|
86
|
+
{ data, params, req, res }
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if (!operation.returns) return { status: 204 }
|
|
92
|
+
|
|
93
|
+
// REVISIT: do not use from old rest adapter
|
|
94
|
+
// REVISIT: new impl should return instead of throwing to avoid try catch
|
|
95
|
+
_validateReturnType(operation, result)
|
|
96
|
+
|
|
97
|
+
// set content-type header to text/plain for returned primitive data types, except for boolean
|
|
98
|
+
const returnType = operation.returns._type
|
|
99
|
+
if (
|
|
100
|
+
!res.get('content-type') &&
|
|
101
|
+
!operation.returns.items &&
|
|
102
|
+
returnType &&
|
|
103
|
+
cds.builtin.types[returnType] &&
|
|
104
|
+
returnType !== 'cds.Boolean'
|
|
105
|
+
) {
|
|
106
|
+
res.set('Content-Type', 'text/plain')
|
|
107
|
+
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
// REVISIT: Still needed with cds-mtxs?
|
|
110
|
+
// mtx compat, modeled as string but object returned
|
|
111
|
+
if (operation.returns._type === 'cds.String' && typeof result === 'object') {
|
|
112
|
+
res.set('Content-Type', 'application/json')
|
|
113
|
+
}
|
|
111
114
|
|
|
112
|
-
|
|
115
|
+
// REVISIT: still needed?
|
|
116
|
+
if (!operation.returns.items && Array.isArray(result)) result = result[0]
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
if (result === undefined) return { status: 204 }
|
|
119
|
+
|
|
120
|
+
return { result }
|
|
121
|
+
}
|
|
115
122
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
const cds = require('../../_runtime/cds')
|
|
2
2
|
const { INSERT, SELECT, UPDATE, DELETE } = cds.ql
|
|
3
3
|
|
|
4
|
+
const { getKeysAndParamsFromPath } = require('../../common/utils')
|
|
5
|
+
|
|
4
6
|
const { base64ToBuffer } = require('../../_runtime/common/utils/binary')
|
|
5
|
-
const { deepCopy } = require('../../_runtime/common/utils/copy')
|
|
6
|
-
const { where2obj } = require('../../_runtime/common/utils/cqn')
|
|
7
7
|
const { convertStructured } = require('../../_runtime/common/utils/ucsn')
|
|
8
|
-
|
|
9
8
|
const getTemplate = require('../../_runtime/common/utils/template')
|
|
10
9
|
const templateProcessor = require('../../_runtime/common/utils/templateProcessor')
|
|
10
|
+
|
|
11
11
|
const { checkStaticElementByKey } = require('../../_runtime/cds-services/util/assert')
|
|
12
12
|
|
|
13
13
|
const _processorFn = errors => {
|
|
@@ -30,140 +30,142 @@ const _picker = element => {
|
|
|
30
30
|
|
|
31
31
|
const _cache = req => `rest-input;skip-key-validation:${req.method !== 'POST'}`
|
|
32
32
|
|
|
33
|
-
module.exports =
|
|
34
|
-
|
|
35
|
-
// original request including a marker, we don't need to provide the baseUrl here.
|
|
36
|
-
let query = cds.odata.parse(req.url, { service: srv, baseUrl: req.baseUrl })
|
|
37
|
-
|
|
38
|
-
// parser always produces selects
|
|
39
|
-
const _target = (req._target = query.SELECT && query.SELECT.from)
|
|
40
|
-
if (!_target) return next()
|
|
41
|
-
|
|
42
|
-
// REVISIT: __target is the csn target definition
|
|
43
|
-
let {
|
|
44
|
-
__target: definition,
|
|
45
|
-
SELECT: { one }
|
|
46
|
-
} = query
|
|
47
|
-
if (typeof definition === 'string') {
|
|
48
|
-
definition =
|
|
49
|
-
srv.model.definitions[definition] ||
|
|
50
|
-
srv.model.definitions[definition.split(':$:')[0]].actions[definition.split(':$:')[1]]
|
|
51
|
-
}
|
|
52
|
-
delete query.__target
|
|
53
|
-
|
|
54
|
-
// REVISIT: hack for actions and functions
|
|
55
|
-
let operation, args
|
|
56
|
-
const last = _target.ref[_target.ref.length - 1]
|
|
57
|
-
if (last.operation) {
|
|
58
|
-
operation = last.operation
|
|
59
|
-
if (last.args) args = last.args
|
|
60
|
-
_target.ref.pop()
|
|
61
|
-
}
|
|
33
|
+
module.exports = adapter => {
|
|
34
|
+
const { service } = adapter
|
|
62
35
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
36
|
+
return function parse(req, res, next) {
|
|
37
|
+
// REVISIT: Once we don't display the error message location in terms of an offset, but instead a copy of the
|
|
38
|
+
// original request including a marker, we don't need to provide the baseUrl here.
|
|
39
|
+
let query = cds.odata.parse(req.url, { service, baseUrl: req.baseUrl, protocol: 'rest' })
|
|
40
|
+
|
|
41
|
+
// parser always produces selects
|
|
42
|
+
const _target = (req._target = query.SELECT && query.SELECT.from)
|
|
43
|
+
if (!_target) return next()
|
|
44
|
+
|
|
45
|
+
// REVISIT: __target is the csn target definition
|
|
46
|
+
let {
|
|
47
|
+
__target: definition,
|
|
48
|
+
SELECT: { from, one }
|
|
49
|
+
} = query
|
|
50
|
+
if (typeof definition === 'string') {
|
|
51
|
+
definition =
|
|
52
|
+
service.model.definitions[definition] ||
|
|
53
|
+
service.model.definitions[definition.split(':$:')[0]].actions[definition.split(':$:')[1]]
|
|
54
|
+
}
|
|
55
|
+
delete query.__target
|
|
56
|
+
|
|
57
|
+
// req.__proto__.method is set in case of upsert
|
|
58
|
+
const isUpsert = req.__proto__.method in { PUT: 1, PATCH: 1 }
|
|
59
|
+
|
|
60
|
+
// REVISIT: hack for actions and functions
|
|
61
|
+
let operation, args
|
|
62
|
+
const last = _target.ref[_target.ref.length - 1]
|
|
63
|
+
if (last.operation) {
|
|
64
|
+
operation = last.operation
|
|
65
|
+
if (last.args) args = last.args
|
|
66
|
+
_target.ref.pop()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const unbound = _target.ref.length === 0
|
|
70
|
+
|
|
71
|
+
// query based on method
|
|
72
|
+
switch (req.method) {
|
|
73
|
+
case 'HEAD':
|
|
74
|
+
case 'GET':
|
|
75
|
+
if (operation) {
|
|
76
|
+
req._operation = operation = definition
|
|
77
|
+
if (operation.kind === 'action') cds.error('Action must be called by POST', { code: 400 })
|
|
78
|
+
if (!unbound) query = one ? SELECT.one(_target) : SELECT.from(_target)
|
|
79
|
+
else query = undefined
|
|
98
80
|
} else {
|
|
99
|
-
|
|
81
|
+
// read (nothing to do)
|
|
100
82
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
let errorMsg
|
|
109
|
-
if (definition) {
|
|
110
|
-
errorMsg = `${definition.kind.charAt(0).toUpperCase() + definition.kind.slice(1)} must be called by ${
|
|
111
|
-
definition.kind === 'action' ? 'POST' : 'GET'
|
|
112
|
-
}`
|
|
83
|
+
break
|
|
84
|
+
case 'POST':
|
|
85
|
+
if (operation) {
|
|
86
|
+
req._operation = operation = definition
|
|
87
|
+
if (operation.kind === 'function') cds.error('Function must be called by GET', { code: 400 })
|
|
88
|
+
if (!unbound) query = one ? SELECT.one(_target) : SELECT.from(_target)
|
|
89
|
+
else query = undefined
|
|
113
90
|
} else {
|
|
114
|
-
|
|
91
|
+
// create
|
|
92
|
+
if (one && !isUpsert) cds.error('POST not allowed on entity', { code: 400 })
|
|
93
|
+
query = INSERT.into(_target)
|
|
115
94
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
95
|
+
break
|
|
96
|
+
case 'PUT':
|
|
97
|
+
case 'PATCH':
|
|
98
|
+
if (operation) {
|
|
99
|
+
let errorMsg
|
|
100
|
+
if (definition) {
|
|
101
|
+
errorMsg = `${definition.kind.charAt(0).toUpperCase() + definition.kind.slice(1)} must be called by ${
|
|
102
|
+
definition.kind === 'action' ? 'POST' : 'GET'
|
|
103
|
+
}`
|
|
104
|
+
} else {
|
|
105
|
+
errorMsg = `That action/function must be called by POST/GET`
|
|
106
|
+
}
|
|
107
|
+
cds.error(errorMsg, { code: 400 })
|
|
108
|
+
}
|
|
109
|
+
if (!one) throw { statusCode: 400, code: '400', message: `INVALID_${req.method}` }
|
|
110
|
+
query = UPDATE(_target)
|
|
111
|
+
break
|
|
112
|
+
case 'DELETE':
|
|
113
|
+
if (operation) {
|
|
114
|
+
let errorMsg
|
|
115
|
+
if (definition) {
|
|
116
|
+
errorMsg = `${definition.kind.charAt(0).toUpperCase() + definition.kind.slice(1)} must be called by ${
|
|
117
|
+
definition.kind === 'action' ? 'POST' : 'GET'
|
|
118
|
+
}`
|
|
119
|
+
} else {
|
|
120
|
+
errorMsg = `That action/function must be called by POST/GET`
|
|
121
|
+
}
|
|
122
|
+
cds.error(errorMsg, { code: 400 })
|
|
140
123
|
}
|
|
141
|
-
|
|
124
|
+
if (!one) cds.error('DELETE not allowed on collection', { code: 400 })
|
|
125
|
+
query = DELETE.from(_target)
|
|
126
|
+
break
|
|
127
|
+
default:
|
|
128
|
+
// anything to do?
|
|
129
|
+
}
|
|
130
|
+
req._query = query // REVISIT: req._query should not be a standard API
|
|
131
|
+
if (query && definition) req._query.__target = definition.name
|
|
132
|
+
|
|
133
|
+
const { keys, params } = getKeysAndParamsFromPath(from, service)
|
|
134
|
+
req._data = keys
|
|
135
|
+
if (params) req._params = params
|
|
136
|
+
|
|
137
|
+
// REVISIT: query._data hack
|
|
138
|
+
if (
|
|
139
|
+
(query && (query.INSERT || query.UPDATE || query.DELETE)) ||
|
|
140
|
+
(operation && operation.kind === 'action') ||
|
|
141
|
+
args
|
|
142
|
+
) {
|
|
143
|
+
if (operation && (operation.kind === 'action' || operation.kind === 'function') && !operation.params) {
|
|
144
|
+
req._data = {}
|
|
142
145
|
} else {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
errs = []
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
const payload = args || req.body
|
|
147
|
+
if (!operation) Object.assign(payload, keys)
|
|
148
|
+
if (!cds.env.features.cds_validate) {
|
|
149
|
+
const errs = []
|
|
150
|
+
convertStructured(service, operation || definition, payload, {
|
|
151
|
+
cleanupStruct: cds.env.features.rest_struct_data
|
|
152
|
+
})
|
|
153
|
+
const template = getTemplate(_cache(req), service, definition, { pick: _picker })
|
|
154
|
+
if (template && template.elements.size) {
|
|
155
|
+
for (const row of Array.isArray(payload) ? payload : [payload]) {
|
|
156
|
+
templateProcessor({ processFn: _processorFn(errs), row, template })
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (errs.length) {
|
|
160
|
+
if (errs.length === 1) throw errs[0]
|
|
161
|
+
throw Object.assign(new Error('MULTIPLE_ERRORS'), { statusCode: 400, details: errs })
|
|
149
162
|
}
|
|
150
163
|
}
|
|
164
|
+
base64ToBuffer(payload, service, definition)
|
|
165
|
+
req._data = payload
|
|
151
166
|
}
|
|
152
|
-
if (errs?.length) {
|
|
153
|
-
if (errs.length === 1) throw errs[0]
|
|
154
|
-
throw Object.assign(new Error('MULTIPLE_ERRORS'), { statusCode: 400, details: errs })
|
|
155
|
-
}
|
|
156
|
-
base64ToBuffer(payload, srv, definition)
|
|
157
|
-
req._data = payload
|
|
158
167
|
}
|
|
159
|
-
}
|
|
160
168
|
|
|
161
|
-
|
|
162
|
-
for (let i = 0; i < _target.ref.length; i++) {
|
|
163
|
-
req._params = req._params || []
|
|
164
|
-
if (_target.ref[i].where) req._params.push(where2obj(_target.ref[i].where))
|
|
165
|
-
else if (_target.ref.length !== 1) req._params.push({})
|
|
169
|
+
next()
|
|
166
170
|
}
|
|
167
|
-
|
|
168
|
-
next()
|
|
169
171
|
}
|
|
@@ -1,35 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
module.exports = adapter => {
|
|
2
|
+
const { service } = adapter
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
return async function read(req, res) {
|
|
5
|
+
const { _query: query, _target: target, _data: data, _params: params } = req
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
let result,
|
|
8
|
+
status = 200
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
result = await service.dispatch(adapter.request4({ query, data, params, req, res }))
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// 204 or 404?
|
|
17
|
-
if (result == null && query.SELECT.one) {
|
|
18
|
-
if (_target.ref.length > 1) status = 204
|
|
19
|
-
else throw { code: 404 }
|
|
20
|
-
}
|
|
12
|
+
// 204 or 404?
|
|
13
|
+
if (result == null && query.SELECT.one) {
|
|
14
|
+
if (target.ref.length > 1) status = 204
|
|
15
|
+
else throw { code: 404 }
|
|
16
|
+
}
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
// REVISIT: Still needed with cds-mtxs?
|
|
19
|
+
// compat for mtx returning strings instead of objects
|
|
20
|
+
if (typeof result === 'object' && result !== null && '$count' in result) {
|
|
21
|
+
result = { count: result.$count, value: result }
|
|
22
|
+
} else if (typeof result === 'number') {
|
|
23
|
+
result = result.toString()
|
|
28
24
|
}
|
|
29
|
-
} else if (typeof result === 'number') {
|
|
30
|
-
// TODO check if this is needed
|
|
31
|
-
result = result.toString()
|
|
32
|
-
}
|
|
33
25
|
|
|
34
|
-
|
|
26
|
+
return { result, status }
|
|
27
|
+
}
|
|
35
28
|
}
|
|
@@ -1,40 +1,35 @@
|
|
|
1
1
|
const cds = require('../../_runtime/cds')
|
|
2
|
-
const { INSERT } = cds.ql
|
|
3
|
-
|
|
4
|
-
const RestRequest = require('../RestRequest')
|
|
5
2
|
|
|
6
3
|
const UPSERT_ALLOWED = !(cds.env.runtime && cds.env.runtime.allow_upsert === false)
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
module.exports = adapter => {
|
|
6
|
+
const { router, service } = adapter
|
|
7
|
+
|
|
8
|
+
return async function update(req, res, next) {
|
|
9
|
+
let { _query: query, _data: data, _params: params } = req
|
|
10
|
+
|
|
11
|
+
let result,
|
|
12
|
+
status = 200
|
|
13
|
+
|
|
14
|
+
// if upsert it allowed, we need to catch 404 and retry with create
|
|
15
|
+
try {
|
|
16
|
+
query.data(data)
|
|
17
|
+
// REVISIT: if PUT, req.method should be PUT -> Crud2Http maps UPSERT to PUT
|
|
18
|
+
result = await service.dispatch(adapter.request4({ query, method: req.method, params, req, res }))
|
|
19
|
+
} catch (e) {
|
|
20
|
+
const is404 = e.code === 404 || e.status === 404 || e.statusCode === 404
|
|
21
|
+
const isForcedInsert =
|
|
22
|
+
(e.code === 412 || e.status === 412 || e.statusCode === 412) && req.headers['if-none-match'] === '*'
|
|
23
|
+
if ((is404 || isForcedInsert) && UPSERT_ALLOWED) {
|
|
24
|
+
// -> redirect to POST
|
|
25
|
+
const _body = JSON.parse(req._raw)
|
|
26
|
+
const _req = Object.assign(Object.create(req), { method: 'POST', body: _body })
|
|
27
|
+
return router.handle(_req, res, next)
|
|
28
|
+
}
|
|
15
29
|
|
|
16
|
-
// if upsert it allowed, we need to catch 404 and retry with create
|
|
17
|
-
try {
|
|
18
|
-
// add the data (as copy, if upsert allowed)
|
|
19
|
-
query.data(UPSERT_ALLOWED ? deepCopyObject(_data) : _data)
|
|
20
|
-
|
|
21
|
-
// REVISIT: if PUT, req.method should be PUT -> Crud2Http maps UPSERT to PUT
|
|
22
|
-
result = await srv.dispatch(new RestRequest({ query, _target, method: _req.method, params: _params }))
|
|
23
|
-
if (_params && result) Object.assign(result, _params[_params.length - 1])
|
|
24
|
-
} catch (e) {
|
|
25
|
-
const is404 = e.code === 404 || e.status === 404 || e.statusCode === 404
|
|
26
|
-
const isForcedInsert =
|
|
27
|
-
(e.code === 412 || e.status === 412 || e.statusCode === 412) && _req.headers['if-none-match'] === '*'
|
|
28
|
-
if ((is404 || isForcedInsert) && UPSERT_ALLOWED) {
|
|
29
|
-
query = INSERT.into(query.UPDATE.entity).entries(
|
|
30
|
-
_params ? Object.assign(_data, _params[_params.length - 1]) : _data
|
|
31
|
-
)
|
|
32
|
-
result = await srv.dispatch(new RestRequest({ query, _target, params: _params }))
|
|
33
|
-
status = 201
|
|
34
|
-
} else {
|
|
35
30
|
throw e
|
|
36
31
|
}
|
|
37
|
-
}
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
return { result, status }
|
|
34
|
+
}
|
|
40
35
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.4",
|
|
4
4
|
"description": "SAP Cloud Application Programming Model - CDS for Node.js",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"keywords": [
|
|
@@ -9,33 +9,42 @@
|
|
|
9
9
|
],
|
|
10
10
|
"author": "SAP SE (https://www.sap.com)",
|
|
11
11
|
"license": "SEE LICENSE IN LICENSE",
|
|
12
|
-
"typings": "apis/cds.d.ts",
|
|
13
12
|
"main": "lib/index.js",
|
|
14
13
|
"bin": {
|
|
15
14
|
"cds-deploy": "lib/dbs/cds-deploy.js",
|
|
16
|
-
"cds-serve": "bin/
|
|
15
|
+
"cds-serve": "bin/serve.js",
|
|
16
|
+
"cds-test": "bin/test.js",
|
|
17
|
+
"chest": "bin/test.js"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
20
|
+
"_i18n/",
|
|
19
21
|
"apis/",
|
|
20
22
|
"app/",
|
|
21
23
|
"bin/",
|
|
22
24
|
"lib/",
|
|
23
25
|
"libx/",
|
|
24
|
-
"tasks/",
|
|
25
26
|
"srv/",
|
|
26
|
-
"
|
|
27
|
+
"tasks/",
|
|
27
28
|
"server.js",
|
|
28
29
|
"common.cds",
|
|
30
|
+
"eslint.config.mjs",
|
|
29
31
|
"CHANGELOG.md",
|
|
30
32
|
"LICENSE"
|
|
31
33
|
],
|
|
32
34
|
"engines": {
|
|
33
|
-
"node": ">=
|
|
35
|
+
"node": ">=18"
|
|
34
36
|
},
|
|
35
37
|
"dependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@sap/cds-compiler": "^4",
|
|
38
|
+
"@sap/cds-compiler": ">=5",
|
|
38
39
|
"@sap/cds-fiori": "^1",
|
|
39
40
|
"@sap/cds-foss": "^5.0.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"express": ">=4"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"express": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
40
49
|
}
|
|
41
50
|
}
|
package/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const express = require('express')
|
|
1
|
+
const express = require('express')
|
|
2
2
|
const cds = require('./lib')
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -62,6 +62,7 @@ const defaults = {
|
|
|
62
62
|
return cds.utils.isdir (cds.env.folders.app)
|
|
63
63
|
},
|
|
64
64
|
get index() {
|
|
65
|
+
if (!cds.env.server.index) return undefined
|
|
65
66
|
const index = require ('./app/index.js')
|
|
66
67
|
return (_,res) => res.send (index.html)
|
|
67
68
|
},
|
|
@@ -70,7 +71,8 @@ const defaults = {
|
|
|
70
71
|
return express.static (favicon, {maxAge:'14d'})
|
|
71
72
|
},
|
|
72
73
|
get cors() {
|
|
73
|
-
|
|
74
|
+
if (!cds.env.server.cors) return undefined
|
|
75
|
+
return function cds_cors (req, res, next) {
|
|
74
76
|
const { origin } = req.headers
|
|
75
77
|
if (origin) {
|
|
76
78
|
res.set('access-control-allow-origin', origin)
|