@sap/cds 7.9.3 → 8.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +126 -3655
- 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 +29 -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/insert.js +2 -2
- 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 +0 -12
- 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 +261 -72
- 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 +8 -8
- 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 +19 -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/tasks/enterprise-messaging-deploy.js +1 -1
- 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,11 +1,16 @@
|
|
|
1
1
|
const cds = require('../../../')
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
const querystring = require('node:querystring')
|
|
4
|
-
const { getKeysAndParamsFromPath, handleSapMessages, validateIfNoneMatch, getPreferReturnHeader } = require('../utils')
|
|
5
|
-
const { handleStreamProperties } = require('../../_runtime/common/utils/streamProp')
|
|
6
4
|
|
|
7
|
-
const
|
|
5
|
+
const { handleSapMessages, validateIfNoneMatch, getPreferReturnHeader } = require('../utils')
|
|
6
|
+
const getODataMetadata = require('../utils/metadata')
|
|
7
|
+
const postProcess = require('../utils/postProcess')
|
|
8
|
+
const getODataResult = require('../utils/result')
|
|
9
|
+
|
|
10
|
+
const { getKeysAndParamsFromPath } = require('../../common/utils')
|
|
11
|
+
|
|
8
12
|
const { getPageSize } = require('../../_runtime/common/generic/paging')
|
|
13
|
+
const { handleStreamProperties } = require('../../_runtime/common/utils/streamProp')
|
|
9
14
|
|
|
10
15
|
const _getCount = result =>
|
|
11
16
|
Array.isArray(result)
|
|
@@ -89,7 +94,7 @@ const resolveProxyExpands = ({ SELECT: { columns }, target: entity }, service) =
|
|
|
89
94
|
|
|
90
95
|
for (const column of columns) {
|
|
91
96
|
if (column.expand) {
|
|
92
|
-
_checkExpandDeep(column, entity.elements[column.ref[0]]._target, service.
|
|
97
|
+
_checkExpandDeep(column, entity.elements[column.ref[0]]._target, service.definition.name)
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
100
|
}
|
|
@@ -107,143 +112,162 @@ const _count = result => {
|
|
|
107
112
|
: result.$count ?? result._counted_ ?? 0
|
|
108
113
|
}
|
|
109
114
|
|
|
110
|
-
//
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
115
|
+
// REVISIT: integrate with default handler
|
|
116
|
+
const _handleArrayOfQueriesFactory = adapter => {
|
|
117
|
+
const { service } = adapter
|
|
118
|
+
|
|
119
|
+
return (req, res, next) => {
|
|
120
|
+
const cdsReq = adapter.request4({ query: req._query, req, res })
|
|
121
|
+
|
|
122
|
+
// NOTES:
|
|
123
|
+
// - only via srv.run in combination with srv.dispatch inside,
|
|
124
|
+
// we automatically either use a single auto-managed tx for the req (i.e., insert and read after write in same tx)
|
|
125
|
+
// or the auto-managed tx opened for the respective atomicity group, if exists
|
|
126
|
+
// - in the then block of .run(), the transaction is committed (i.e., before sending the response) if a single auto-managed tx is used
|
|
127
|
+
return service
|
|
128
|
+
.run(() => {
|
|
129
|
+
return service.dispatch(cdsReq).then(result => {
|
|
130
|
+
// nothing to do
|
|
131
|
+
return result
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
.then(result => {
|
|
135
|
+
handleSapMessages(cdsReq, req, res)
|
|
136
|
+
|
|
137
|
+
if (req.url.match(/\/\$count/)) return res.set('Content-Type', 'text/plain').send(_count(result).toString())
|
|
138
|
+
|
|
139
|
+
const { context: mainOdataContext } = getODataMetadata(req._query[0], {
|
|
140
|
+
result: result[0],
|
|
141
|
+
isCollection: !req._query[0].SELECT.one
|
|
142
|
+
})
|
|
143
|
+
for (let i = 0; i < result.length; i++) {
|
|
144
|
+
const { context: subOdataContext } = getODataMetadata(req._query[i], {
|
|
145
|
+
result: result[i],
|
|
146
|
+
isCollection: !req._query[i].SELECT.one
|
|
147
|
+
})
|
|
148
|
+
// Add OData context, if it deviates from main context
|
|
149
|
+
if (i !== 0 && mainOdataContext !== subOdataContext) {
|
|
150
|
+
result[i].forEach(entry => (entry['@odata.context'] = subOdataContext))
|
|
151
|
+
}
|
|
152
|
+
}
|
|
126
153
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
154
|
+
result = result.flat(Infinity)
|
|
155
|
+
if (cdsReq.query[0].SELECT.count) result.$count = result.length
|
|
156
|
+
|
|
157
|
+
result = getODataResult(
|
|
158
|
+
result,
|
|
159
|
+
{ context: mainOdataContext },
|
|
160
|
+
{ isCollection: !req._query[0].SELECT.one, property: req._query[0]._propertyAccess }
|
|
161
|
+
)
|
|
162
|
+
res.send(result)
|
|
163
|
+
})
|
|
164
|
+
.catch(err => {
|
|
165
|
+
handleSapMessages(cdsReq, req, res)
|
|
166
|
+
|
|
167
|
+
next(err)
|
|
168
|
+
})
|
|
169
|
+
}
|
|
133
170
|
}
|
|
134
171
|
|
|
135
|
-
module.exports =
|
|
136
|
-
|
|
172
|
+
module.exports = adapter => {
|
|
173
|
+
const { service } = adapter
|
|
174
|
+
|
|
175
|
+
const _handleArrayOfQueries = _handleArrayOfQueriesFactory(adapter)
|
|
176
|
+
|
|
177
|
+
return function read(req, res, next) {
|
|
137
178
|
if (getPreferReturnHeader(req)) {
|
|
138
179
|
const msg = `The 'return' preference is not allowed in ${req.method} requests`
|
|
139
180
|
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
140
181
|
}
|
|
141
182
|
|
|
142
183
|
// $apply with concat -> multiple queries with special handling
|
|
143
|
-
if (Array.isArray(req._query)) return _handleArrayOfQueries(
|
|
184
|
+
if (Array.isArray(req._query)) return _handleArrayOfQueries(req, res, next)
|
|
144
185
|
|
|
145
186
|
// REVISIT: better solution for _propertyAccess
|
|
146
187
|
let {
|
|
147
|
-
SELECT: { from },
|
|
188
|
+
SELECT: { from, one },
|
|
148
189
|
target,
|
|
149
190
|
_propertyAccess
|
|
150
191
|
} = req._query
|
|
151
192
|
const { _query: query } = req
|
|
152
193
|
|
|
153
194
|
// payload & params
|
|
154
|
-
const { keys, params } = getKeysAndParamsFromPath(from,
|
|
195
|
+
const { keys, params } = getKeysAndParamsFromPath(from, service)
|
|
155
196
|
const data = keys //> for read and delete, we provide keys in req.data
|
|
156
197
|
|
|
157
198
|
// cdsReq.headers should contain merged headers of envelope and subreq
|
|
158
199
|
const headers = { ...cds.context.http.req.headers, ...req.headers }
|
|
159
200
|
|
|
160
201
|
// we need a cds.Request for multiple reasons, incl. params, headers, sap-messages, read after write, ...
|
|
161
|
-
const cdsReq =
|
|
162
|
-
Object.defineProperty(cdsReq, 'protocol', { value: 'odata-v4' })
|
|
163
|
-
|
|
164
|
-
// API for subrequests of $batch (or incoming request)
|
|
165
|
-
cdsReq.req = req
|
|
166
|
-
cdsReq.res = res
|
|
202
|
+
const cdsReq = adapter.request4({ query, data, params, headers, req, res })
|
|
167
203
|
|
|
168
204
|
// REVISIT: what is this for? some tests fail without it... we should find a better solution!
|
|
169
205
|
Object.defineProperty(query.SELECT, '_4odata', { value: true })
|
|
170
206
|
|
|
171
|
-
// do now to get meta info before the query is rewritten + to know return type
|
|
172
|
-
const info = metaInfo(query, 'READ', srv, {}, req, false)
|
|
173
|
-
|
|
174
|
-
// FIXME: wrong contextUrl for SiblingEntity
|
|
175
|
-
if (info.metadata.contextUrl.match(/\/SiblingEntity\//)) {
|
|
176
|
-
const split = info.metadata.contextUrl.split('/')
|
|
177
|
-
const i = split.findIndex(s => s === 'SiblingEntity')
|
|
178
|
-
split.splice(i, 1)
|
|
179
|
-
if (split[i - 1].match(/IsActiveEntity=false/)) {
|
|
180
|
-
split[i - 1] = split[i - 1].replace('IsActiveEntity=false', 'IsActiveEntity=true')
|
|
181
|
-
info.metadata.contextUrl = split.join('/')
|
|
182
|
-
} else {
|
|
183
|
-
info.metadata.contextUrl = split.join('/').replace(/IsActiveEntity=true/g, 'IsActiveEntity=false')
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const lastPathElement = req.path.split('/').slice(-1)[0]
|
|
188
|
-
|
|
189
207
|
if (cds.env.effective.odata.proxies && cds.env.effective.odata.xrefs) {
|
|
190
208
|
// REVISIT check above is still not perfect solution
|
|
191
|
-
resolveProxyExpands(query,
|
|
209
|
+
resolveProxyExpands(query, service)
|
|
192
210
|
}
|
|
193
211
|
|
|
194
|
-
|
|
212
|
+
if (!query.SELECT.columns) query.SELECT.columns = ['*']
|
|
213
|
+
|
|
214
|
+
handleStreamProperties(target, query.SELECT.columns, service.model)
|
|
195
215
|
|
|
196
216
|
// REVISIT: what is this for? some tests fail without it... we should find a better solution!
|
|
197
217
|
Object.defineProperty(query.SELECT, '_4odata', { value: true })
|
|
198
218
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return res.sendStatus(204)
|
|
210
|
-
} else {
|
|
219
|
+
// NOTES:
|
|
220
|
+
// - only via srv.run in combination with srv.dispatch inside,
|
|
221
|
+
// we automatically either use a single auto-managed tx for the req (i.e., insert and read after write in same tx)
|
|
222
|
+
// or the auto-managed tx opened for the respective atomicity group, if exists
|
|
223
|
+
// - in the then block of .run(), the transaction is committed (i.e., before sending the response) if a single auto-managed tx is used
|
|
224
|
+
return service
|
|
225
|
+
.run(() => {
|
|
226
|
+
return service.dispatch(cdsReq).then(result => {
|
|
227
|
+
// 404
|
|
228
|
+
if (result == null && query.SELECT.one && !(_isNullableSingleton(query) || _isToOneAssoc(query))) {
|
|
211
229
|
throw Object.assign(new Error('404'), { statusCode: 404 })
|
|
212
230
|
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (validateIfNoneMatch(cdsReq, req, result)) {
|
|
216
|
-
return res.status(304).end()
|
|
217
|
-
}
|
|
218
231
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
232
|
+
return result
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
.then(result => {
|
|
236
|
+
handleSapMessages(cdsReq, req, res)
|
|
223
237
|
|
|
238
|
+
// 204
|
|
239
|
+
if (result == null && query.SELECT.one) return res.sendStatus(204)
|
|
224
240
|
if (_propertyAccess && result[_propertyAccess] === null) return res.sendStatus(204)
|
|
225
241
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return res.set('content-type', 'text/plain').send(result.toString())
|
|
229
|
-
}
|
|
242
|
+
// 304
|
|
243
|
+
if (validateIfNoneMatch(cdsReq.target, req.headers?.['if-none-match'], result)) return res.sendStatus(304)
|
|
230
244
|
|
|
231
|
-
if (
|
|
232
|
-
|
|
245
|
+
if (result == null) {
|
|
246
|
+
result = []
|
|
247
|
+
if (req.query.$count) result.$count = 0
|
|
233
248
|
}
|
|
234
249
|
|
|
235
|
-
if (
|
|
236
|
-
postProcess(cdsReq.target,
|
|
237
|
-
if (result
|
|
238
|
-
|
|
250
|
+
if (!one) _calculateNextLink(cdsReq, result)
|
|
251
|
+
postProcess(cdsReq.target, service, result)
|
|
252
|
+
if (result?.$etag) res.set('ETag', result.$etag) //> must be done after post processing
|
|
253
|
+
|
|
254
|
+
const lastSeg = req.path.split('/').slice(-1)[0]
|
|
255
|
+
if (lastSeg === '$count') return res.set('Content-Type', 'text/plain').send(_getCount(result).toString())
|
|
256
|
+
if (lastSeg === '$value' && _propertyAccess) {
|
|
257
|
+
if (cdsReq.target.elements[_propertyAccess].type === 'cds.Binary')
|
|
258
|
+
return res.set('Content-Type', 'application/octet-stream').send(result[_propertyAccess])
|
|
259
|
+
else return res.set('Content-Type', 'text/plain').send(result[_propertyAccess].toString())
|
|
260
|
+
}
|
|
239
261
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
res.
|
|
243
|
-
res.send(isNumber ? result.toString() : result)
|
|
262
|
+
const metadata = getODataMetadata(query, { result, isCollection: !one })
|
|
263
|
+
result = getODataResult(result, metadata, { isCollection: !one, property: _propertyAccess })
|
|
264
|
+
res.send(result)
|
|
244
265
|
})
|
|
245
266
|
.catch(err => {
|
|
267
|
+
// REVISIT: move error middleware -> applies to all these anti patterns
|
|
246
268
|
handleSapMessages(cdsReq, req, res)
|
|
269
|
+
|
|
247
270
|
next(err)
|
|
248
271
|
})
|
|
249
272
|
}
|
|
273
|
+
}
|
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
const cds = require('../../../')
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto')
|
|
4
|
-
const metaInfo = require('../utils/metaInfo')
|
|
5
4
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const getODataMetadata = require('../utils/metadata')
|
|
6
|
+
|
|
7
|
+
const normalize_header = value => value.split(',').map(str => str.trim())
|
|
9
8
|
|
|
10
9
|
const validate_etag = (header, etag) => {
|
|
11
10
|
const normalized = normalize_header(header)
|
|
12
11
|
return normalized.includes(etag) || normalized.includes('*') || normalized.includes('"*"')
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
const generateEtag = s => {
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const generateEtag = s => `W/"${crypto.createHash('sha256').update(s).digest('base64')}"`
|
|
15
|
+
|
|
16
|
+
module.exports = adapter => {
|
|
17
|
+
const { service } = adapter
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (req.method === 'HEAD') return res.end()
|
|
22
|
-
if (req.method !== 'GET') {
|
|
19
|
+
return function service_document(req, res) {
|
|
20
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
23
21
|
const msg = `Method ${req.method} is not allowed for calls to the service endpoint`
|
|
24
22
|
throw Object.assign(new Error(msg), { statusCode: 405 })
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
const
|
|
28
|
-
const csnService =
|
|
25
|
+
const model = cds.context.model || cds.model
|
|
26
|
+
const csnService = model.definitions[service.definition.name]
|
|
29
27
|
|
|
30
28
|
if (req.headers['if-match']) {
|
|
31
29
|
if (csnService.srvDocEtag) {
|
|
@@ -38,25 +36,29 @@ module.exports = srv =>
|
|
|
38
36
|
if (csnService.srvDocEtag) {
|
|
39
37
|
const unchanged = validate_etag(req.headers['if-none-match'], csnService.srvDocEtag)
|
|
40
38
|
if (unchanged) {
|
|
41
|
-
res.set('
|
|
39
|
+
res.set('ETag', csnService.srvDocEtag)
|
|
42
40
|
return res.status(304).end()
|
|
43
41
|
}
|
|
44
42
|
}
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
const srvEntities =
|
|
45
|
+
const srvEntities = model.entities(service.definition.name)
|
|
46
|
+
|
|
48
47
|
// REVISIT: How to identify the exposed entities? api.ignore, autoexposed, ...
|
|
49
48
|
const exposedEntities = Object.keys(srvEntities).filter(
|
|
50
|
-
|
|
49
|
+
entityName => !srvEntities[entityName]['@cds.api.ignore'] && entityName !== 'DraftAdministrativeData'
|
|
51
50
|
)
|
|
52
51
|
|
|
53
52
|
csnService.srvDocEtag = generateEtag(JSON.stringify(exposedEntities))
|
|
54
|
-
res.set('
|
|
53
|
+
res.set('ETag', csnService.srvDocEtag)
|
|
55
54
|
|
|
56
|
-
const
|
|
55
|
+
const { context: odataContext } = getODataMetadata({
|
|
56
|
+
SELECT: { from: { ref: [service.definition.name] } },
|
|
57
|
+
_target: service.definition
|
|
58
|
+
})
|
|
57
59
|
|
|
58
60
|
return res.json({
|
|
59
|
-
'@odata.context':
|
|
61
|
+
'@odata.context': odataContext,
|
|
60
62
|
'@odata.metadataEtag': csnService.srvDocEtag,
|
|
61
63
|
value: exposedEntities.map(e => {
|
|
62
64
|
const e_ = e.replace(/\./g, '_')
|
|
@@ -64,3 +66,4 @@ module.exports = srv =>
|
|
|
64
66
|
})
|
|
65
67
|
})
|
|
66
68
|
}
|
|
69
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
const cds = require('../../../')
|
|
2
|
+
const LOG = cds.log('odata')
|
|
3
|
+
|
|
2
4
|
const { Readable } = require('node:stream')
|
|
5
|
+
|
|
6
|
+
const { handleSapMessages, validateIfNoneMatch, isStream, isRedirect } = require('../utils')
|
|
7
|
+
|
|
8
|
+
const { getKeysAndParamsFromPath } = require('../../common/utils')
|
|
9
|
+
|
|
3
10
|
const getError = require('../../_runtime/common/error')
|
|
4
11
|
const { getTransition } = require('../../_runtime/common/utils/resolveView')
|
|
5
|
-
const LOG = cds.log('odata')
|
|
6
|
-
const { getKeysAndParamsFromPath, handleSapMessages, validateIfNoneMatch } = require('../utils')
|
|
7
12
|
|
|
8
13
|
const _resolveContentProperty = (target, annotName, resolvedProp) => {
|
|
9
14
|
if (target.elements[resolvedProp]) {
|
|
@@ -18,18 +23,6 @@ const _resolveContentProperty = (target, annotName, resolvedProp) => {
|
|
|
18
23
|
return key?.length && key[0]
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
const isStream = query => {
|
|
22
|
-
const { _propertyAccess, target } = query
|
|
23
|
-
if (!_propertyAccess) return
|
|
24
|
-
|
|
25
|
-
const element = target.elements[_propertyAccess]
|
|
26
|
-
return element._type === 'cds.LargeBinary' && element['@Core.MediaType']
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const isStreamByDollarValue = (query, previous, last) => {
|
|
30
|
-
return query.SELECT?.one && last === '$value' && !(previous in query.target.elements)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
26
|
const _addMetadataProperty = (query, property, annotName, odataName) => {
|
|
34
27
|
if (typeof property[annotName] === 'object') {
|
|
35
28
|
const contentProperty = _resolveContentProperty(
|
|
@@ -46,7 +39,7 @@ const _addMetadataProperty = (query, property, annotName, odataName) => {
|
|
|
46
39
|
}
|
|
47
40
|
}
|
|
48
41
|
|
|
49
|
-
const
|
|
42
|
+
const _addStreamMetadata = query => {
|
|
50
43
|
// new odata parser sets streaming property in SELECT.from
|
|
51
44
|
const ref = query.SELECT.columns?.[0].ref || query.SELECT.from.ref
|
|
52
45
|
const propertyName = ref.at(-1)
|
|
@@ -78,7 +71,7 @@ const addStreamMetadata = query => {
|
|
|
78
71
|
}
|
|
79
72
|
}
|
|
80
73
|
|
|
81
|
-
const
|
|
74
|
+
const _validateStream = (req, result) => {
|
|
82
75
|
// REVISIT: compat, should actually be treated as object
|
|
83
76
|
if (!Array.isArray(result)) result = [result]
|
|
84
77
|
|
|
@@ -115,7 +108,7 @@ const _ensureStream = stream => {
|
|
|
115
108
|
return stream_
|
|
116
109
|
}
|
|
117
110
|
|
|
118
|
-
const
|
|
111
|
+
const _normalizeStream = (result, propertyName, lastPathElement, target) => {
|
|
119
112
|
if (!result) return null
|
|
120
113
|
|
|
121
114
|
let readable = result
|
|
@@ -151,8 +144,8 @@ const normalizeStream = (result, propertyName, lastPathElement, target) => {
|
|
|
151
144
|
return readable
|
|
152
145
|
}
|
|
153
146
|
|
|
154
|
-
const
|
|
155
|
-
// backwards compatibility for
|
|
147
|
+
const _setStreamingHeaders = (result, res) => {
|
|
148
|
+
// backwards compatibility for Content-Type in stream
|
|
156
149
|
if (result['$mediaContentType']) res.setHeader('Content-Type', result.$mediaContentType)
|
|
157
150
|
else if (result['*@odata.mediaContentType']) res.setHeader('Content-Type', result['*@odata.mediaContentType'])
|
|
158
151
|
else res.setHeader('Content-Type', 'application/octet-stream')
|
|
@@ -160,22 +153,32 @@ const setStreamingHeaders = (result, res) => {
|
|
|
160
153
|
if ('$mediaContentDispositionFilename' in result) {
|
|
161
154
|
const cdt = result.$mediaContentDispositionType || 'attachment'
|
|
162
155
|
res.setHeader(
|
|
163
|
-
'
|
|
156
|
+
'content-disposition',
|
|
164
157
|
`${cdt}; filename="${encodeURIComponent(result.$mediaContentDispositionFilename)}"`
|
|
165
158
|
)
|
|
166
159
|
}
|
|
167
160
|
}
|
|
168
161
|
|
|
169
|
-
|
|
170
|
-
|
|
162
|
+
module.exports = adapter => {
|
|
163
|
+
const { service } = adapter
|
|
164
|
+
|
|
165
|
+
return function stream(req, res, next) {
|
|
171
166
|
const { _query: query } = req
|
|
172
167
|
|
|
173
168
|
// $apply with concat -> multiple queries with special handling -> read only, no stream?
|
|
174
169
|
if (Array.isArray(query)) return next()
|
|
175
170
|
|
|
176
|
-
|
|
177
|
-
|
|
171
|
+
if (isRedirect(query)) {
|
|
172
|
+
const cdsReq = adapter.request4({ query, req, res })
|
|
173
|
+
service.dispatch(cdsReq).then(result => {
|
|
174
|
+
if (result[query._propertyAccess]) res.set('Location', result[query._propertyAccess])
|
|
175
|
+
return res.sendStatus(307)
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
178
|
|
|
179
|
+
const [previous, lastPathElement] = req.path.split('/').slice(-2)
|
|
180
|
+
const _isStreamByDollarValue =
|
|
181
|
+
query.SELECT?.one && lastPathElement === '$value' && !(previous in query.target.elements)
|
|
179
182
|
if (_isStreamByDollarValue) {
|
|
180
183
|
for (const k in query.target.elements) {
|
|
181
184
|
if (query.target.elements[k]['@Core.MediaType']) {
|
|
@@ -193,64 +196,59 @@ const stream = srv =>
|
|
|
193
196
|
if (!_isStream) return next()
|
|
194
197
|
|
|
195
198
|
if (!query.target['@cds.persistence.skip'] && !isMimeTypeStreamedByDefault) {
|
|
196
|
-
|
|
199
|
+
_addStreamMetadata(query)
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
// we need the cds request, so we can access the modified query, which is cloned due to lean-draft, so we need to use dispatch here and pass a cds req
|
|
200
|
-
const cdsReq =
|
|
201
|
-
Object.defineProperty(cdsReq, 'protocol', { value: 'odata-v4' })
|
|
203
|
+
const cdsReq = adapter.request4({ query, req, res })
|
|
202
204
|
|
|
203
205
|
// for read and delete, we provide keys in req.data
|
|
204
206
|
// payload & params
|
|
205
|
-
const { keys } = getKeysAndParamsFromPath(query.SELECT.from,
|
|
207
|
+
const { keys } = getKeysAndParamsFromPath(query.SELECT.from, service)
|
|
206
208
|
cdsReq.data = keys
|
|
207
209
|
|
|
208
210
|
// REVISIT: what is this for? some tests fail without it... we should find a better solution!
|
|
209
211
|
Object.defineProperty(query.SELECT, '_4odata', { value: true })
|
|
210
212
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
// NOTES:
|
|
214
|
+
// - only via srv.run in combination with srv.dispatch inside,
|
|
215
|
+
// we automatically either use a single auto-managed tx for the req (i.e., insert and read after write in same tx)
|
|
216
|
+
// or the auto-managed tx opened for the respective atomicity group, if exists
|
|
217
|
+
// - in the then block of .run(), the transaction is committed (i.e., before sending the response) if a single auto-managed tx is used
|
|
218
|
+
return service
|
|
219
|
+
.run(() => {
|
|
220
|
+
return service.dispatch(cdsReq).then(async result => {
|
|
221
|
+
_validateStream(req, result)
|
|
216
222
|
|
|
217
|
-
if (validateIfNoneMatch(cdsReq, req, result))
|
|
218
|
-
return res.status(304).end()
|
|
219
|
-
}
|
|
223
|
+
if (validateIfNoneMatch(cdsReq.target, req.headers?.['if-none-match'], result)) return res.sendStatus(304)
|
|
220
224
|
|
|
221
|
-
const stream =
|
|
222
|
-
if (stream === null)
|
|
223
|
-
res.status(204)
|
|
224
|
-
return
|
|
225
|
-
}
|
|
225
|
+
const stream = _normalizeStream(result, query._propertyAccess, lastPathElement, query.target)
|
|
226
|
+
if (stream === null) return res.sendStatus(204)
|
|
226
227
|
|
|
227
|
-
if (pdfMimeType)
|
|
228
|
-
if (!result.$mediaContentType) result.$mediaContentType = 'application/pdf'
|
|
229
|
-
}
|
|
228
|
+
if (pdfMimeType && !result.$mediaContentType) result.$mediaContentType = 'application/pdf'
|
|
230
229
|
|
|
231
|
-
|
|
230
|
+
_setStreamingHeaders(result, res)
|
|
232
231
|
|
|
233
232
|
return new Promise((resolve, reject) => {
|
|
234
|
-
if (res.destroyed) return reject(new Error('Response
|
|
233
|
+
if (res.destroyed) return reject(new Error('Response was closed while streaming'))
|
|
235
234
|
stream.pipe(res)
|
|
236
235
|
stream.on('end', () => resolve(result))
|
|
237
236
|
stream.once('error', reject)
|
|
238
237
|
let finished = false
|
|
239
|
-
res.on('finish', () =>
|
|
240
|
-
|
|
241
|
-
})
|
|
242
|
-
res.on('close', () => !finished && reject(new Error('Response is closed while streaming')))
|
|
238
|
+
res.on('finish', () => (finished = true))
|
|
239
|
+
res.on('close', () => !finished && reject(new Error('Response was closed while streaming')))
|
|
243
240
|
})
|
|
244
241
|
})
|
|
245
242
|
})
|
|
246
243
|
.then(() => {
|
|
247
|
-
|
|
244
|
+
handleSapMessages(cdsReq, req, res)
|
|
245
|
+
|
|
248
246
|
res.end()
|
|
249
247
|
})
|
|
250
|
-
.catch(
|
|
251
|
-
|
|
248
|
+
.catch(err => {
|
|
249
|
+
handleSapMessages(cdsReq, req, res)
|
|
252
250
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
251
|
+
next(err)
|
|
252
|
+
})
|
|
253
|
+
}
|
|
256
254
|
}
|