@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,6 +1,3 @@
|
|
|
1
|
-
const getTemplate = require('../../_runtime/common/utils/template')
|
|
2
|
-
const templateProcessor = require('../../_runtime/common/utils/templateProcessor')
|
|
3
|
-
|
|
4
1
|
const METADATA = {
|
|
5
2
|
$context: '@odata.context',
|
|
6
3
|
$count: '@odata.count',
|
|
@@ -26,6 +23,7 @@ const METADATA = {
|
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
const KEYSTOCLEANUP = {
|
|
26
|
+
// REVISIT: should probably be handled in RemoteService's handle()
|
|
29
27
|
// do not set "@odata.context" as it may be inherited of remote service
|
|
30
28
|
$context: true,
|
|
31
29
|
// REVISIT: okra doesn't support content disposition
|
|
@@ -33,161 +31,57 @@ const KEYSTOCLEANUP = {
|
|
|
33
31
|
$mediaContentDispositionType: true
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
const
|
|
37
|
-
for (const key in METADATA) {
|
|
38
|
-
if (!(key in result)) continue
|
|
39
|
-
if (!KEYSTOCLEANUP[key]) odataResult[METADATA[key]] = result[key]
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const _metadata = (result, propertyName, odataResult) => {
|
|
34
|
+
const _rewriteMetadataDeep = result => {
|
|
44
35
|
for (const key in result) {
|
|
45
|
-
if (typeof result[key] === 'object')
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
else result[METADATA[key]] = result[key]
|
|
50
|
-
}
|
|
51
|
-
if (!propertyName) delete result[key]
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const _cleanupMetadata = (propertyName, result) => {
|
|
56
|
-
if (typeof result !== 'object') return odataResult
|
|
57
|
-
|
|
58
|
-
const odataResult = {}
|
|
59
|
-
if (propertyName) {
|
|
60
|
-
odataResult.value = result[propertyName]
|
|
61
|
-
} else {
|
|
62
|
-
odataResult.value = result
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (Array.isArray(result)) _metadataRoot(result, odataResult)
|
|
66
|
-
_metadata(result, propertyName, odataResult)
|
|
67
|
-
|
|
68
|
-
return odataResult
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const _setContext = (odataResult, info, isCollection) => {
|
|
72
|
-
if (info && info.metadata) {
|
|
73
|
-
const result = isCollection || info.metadata.propertyName ? odataResult : odataResult.value
|
|
74
|
-
|
|
75
|
-
if (result != null) Object.assign(result, { [METADATA.$context]: info.metadata.contextUrl })
|
|
76
|
-
}
|
|
77
|
-
return odataResult
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const _getParent = (model, name) => {
|
|
81
|
-
const target = model.definitions[name]
|
|
82
|
-
|
|
83
|
-
if (target && target.elements) {
|
|
84
|
-
for (const elementName in target.elements) {
|
|
85
|
-
const element = target.elements[elementName]
|
|
86
|
-
if (element._anchor && element._anchor._isContained) return element._anchor
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return null
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const addEtags = (row, key) => {
|
|
94
|
-
if (!row[key]) return
|
|
95
|
-
row['$etag'] = row[key].startsWith('W/') ? row[key] : `W/"${row[key]}"`
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const _processCategory = (category, elementInfo) => {
|
|
99
|
-
const { row, key } = elementInfo
|
|
100
|
-
|
|
101
|
-
switch (category) {
|
|
102
|
-
case '@odata.etag':
|
|
103
|
-
addEtags(row, key)
|
|
104
|
-
break
|
|
105
|
-
case '@cds.api.ignore':
|
|
106
|
-
delete row[key]
|
|
107
|
-
break
|
|
108
|
-
// no default
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const _processorFn = () => elementInfo => {
|
|
113
|
-
const { row, key, plain } = elementInfo
|
|
114
|
-
if (typeof row !== 'object' || !Object.prototype.hasOwnProperty.call(row, key)) return
|
|
115
|
-
const categories = plain.categories
|
|
116
|
-
|
|
117
|
-
for (const category of categories) {
|
|
118
|
-
_processCategory(category, elementInfo)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const _pick = element => {
|
|
123
|
-
const categories = []
|
|
124
|
-
if (element['@odata.etag']) categories.push('@odata.etag')
|
|
125
|
-
if (element['@cds.api.ignore']) categories.push('@cds.api.ignore')
|
|
126
|
-
if (categories.length) return { categories }
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const postProcess = (target, service, result, isMinimal) => {
|
|
130
|
-
const { model } = service
|
|
131
|
-
if (!target || !result || !model || !model.definitions[target.name]) return
|
|
132
|
-
|
|
133
|
-
const cacheKey = isMinimal ? 'postProcessMinimal' : 'postProcess'
|
|
134
|
-
const parent = _getParent(model, target.name)
|
|
135
|
-
const template = getTemplate(
|
|
136
|
-
cacheKey,
|
|
137
|
-
service,
|
|
138
|
-
target,
|
|
139
|
-
{ pick: _pick, ignore: isMinimal ? el => el.isAssociation : undefined },
|
|
140
|
-
parent
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
if (template.elements.size === 0) return
|
|
144
|
-
|
|
145
|
-
// normalize result to rows
|
|
146
|
-
result = result.value != null && Object.keys(result).filter(k => !k.match(/^\W/)).length === 1 ? result.value : result
|
|
147
|
-
|
|
148
|
-
if (typeof result === 'object' && result != null) {
|
|
149
|
-
const rows = Array.isArray(result) ? result : [result]
|
|
150
|
-
|
|
151
|
-
// process each row
|
|
152
|
-
const processFn = _processorFn()
|
|
153
|
-
for (const row of rows) {
|
|
154
|
-
templateProcessor({
|
|
155
|
-
processFn,
|
|
156
|
-
row,
|
|
157
|
-
template
|
|
158
|
-
})
|
|
36
|
+
if (typeof result[key] === 'object' && result[key] != null) _rewriteMetadataDeep(result[key])
|
|
37
|
+
if (key in METADATA && !KEYSTOCLEANUP[key]) {
|
|
38
|
+
result[METADATA[key]] = result[key]
|
|
39
|
+
delete result[key]
|
|
159
40
|
}
|
|
160
41
|
}
|
|
161
42
|
}
|
|
162
43
|
|
|
163
44
|
/**
|
|
164
|
-
*
|
|
45
|
+
* Constructs the odata result object from the result of the service call as well as the provided metadata and additional options.
|
|
165
46
|
*
|
|
166
|
-
* @param {*} result
|
|
167
|
-
* @param {
|
|
168
|
-
* @
|
|
47
|
+
* @param {*} result - the result of the service call, i.e., the payload to return to the client
|
|
48
|
+
* @param {object} metadata - odata metadata
|
|
49
|
+
* @param {string} metadata.context - @odata.context
|
|
50
|
+
* @param {object} [options] - additional options/ instructions
|
|
51
|
+
* @param {boolean} [options.isCollection] - whether the result shall be a collection
|
|
52
|
+
* @param {string} [options.property] - the name of the requested single property, if any
|
|
53
|
+
* @returns {object} - the odata result
|
|
169
54
|
*/
|
|
170
|
-
|
|
55
|
+
module.exports = function getODataResult(result, metadata, options = {}) {
|
|
171
56
|
if (result == null) return ''
|
|
172
57
|
|
|
173
|
-
|
|
174
|
-
if (info) {
|
|
175
|
-
propertyName = info.metadata.propertyName
|
|
176
|
-
isCollection = info.metadata.isCollection
|
|
177
|
-
}
|
|
58
|
+
const { isCollection, property } = options
|
|
178
59
|
|
|
179
60
|
if (isCollection && !Array.isArray(result)) result = [result]
|
|
180
61
|
else if (!isCollection && Array.isArray(result)) result = result[0]
|
|
181
62
|
|
|
182
|
-
|
|
63
|
+
// make sure @odata.context is the first element (per OData spec)
|
|
64
|
+
const odataResult = {
|
|
65
|
+
[METADATA.$context]: metadata.context
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// copy metadata from result to odataResult
|
|
69
|
+
for (const key in METADATA) {
|
|
70
|
+
if (!(key in result)) continue
|
|
71
|
+
if (!KEYSTOCLEANUP[key]) odataResult[METADATA[key]] = result[key]
|
|
72
|
+
}
|
|
183
73
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
_setContext(odataResult, info, isCollection)
|
|
74
|
+
// rewrite metadata in result
|
|
75
|
+
_rewriteMetadataDeep(result)
|
|
187
76
|
|
|
188
|
-
|
|
77
|
+
// add result to odataResult
|
|
78
|
+
if (isCollection) {
|
|
79
|
+
Object.assign(odataResult, { value: result })
|
|
80
|
+
} else if (property) {
|
|
81
|
+
Object.assign(odataResult, { value: result[property] })
|
|
82
|
+
} else {
|
|
83
|
+
Object.assign(odataResult, result)
|
|
84
|
+
}
|
|
189
85
|
|
|
190
86
|
return odataResult
|
|
191
87
|
}
|
|
192
|
-
|
|
193
|
-
module.exports = { toODataResult, postProcess }
|
package/libx/outbox/index.js
CHANGED
|
@@ -42,7 +42,7 @@ const hasPersistentOutbox = tenant => {
|
|
|
42
42
|
const _safeJSONParse = string => {
|
|
43
43
|
try {
|
|
44
44
|
return string && JSON.parse(string)
|
|
45
|
-
} catch
|
|
45
|
+
} catch {
|
|
46
46
|
// Don't throw
|
|
47
47
|
}
|
|
48
48
|
}
|
|
@@ -56,6 +56,7 @@ const processMessages = async (service, tenant, _opts = {}) => {
|
|
|
56
56
|
outboxRunner.run({ name, tenant }, () => {
|
|
57
57
|
let letAppCrash = false
|
|
58
58
|
const config = tenant ? { tenant, user: cds.User.privileged } : { user: cds.User.privileged }
|
|
59
|
+
config.after = 1 // make sure spawn puts its cb on the `timer` queue (via setTimeout), which is also used by `outboxRunner`
|
|
59
60
|
const spawn = cds.spawn(async () => {
|
|
60
61
|
let messages
|
|
61
62
|
try {
|
|
@@ -149,7 +150,7 @@ const processMessages = async (service, tenant, _opts = {}) => {
|
|
|
149
150
|
if ((await _handleWithErr(msg)) === false) break
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
|
-
} catch
|
|
153
|
+
} catch {
|
|
153
154
|
letAppCrash = true
|
|
154
155
|
}
|
|
155
156
|
|
|
@@ -277,7 +278,7 @@ function outboxed(srv, customOpts) {
|
|
|
277
278
|
}
|
|
278
279
|
|
|
279
280
|
if (!context[$stored_reqs]) {
|
|
280
|
-
context[$stored_reqs] = []
|
|
281
|
+
context[$stored_reqs] = []
|
|
281
282
|
context.on('succeeded', async () => {
|
|
282
283
|
// REVISIT: Also allow maxAttempts for in-memory outbox?
|
|
283
284
|
for (const _req of context[$stored_reqs]) {
|
package/libx/rest/RestAdapter.js
CHANGED
|
@@ -1,201 +1,134 @@
|
|
|
1
1
|
const cds = require('../_runtime/cds')
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const
|
|
3
|
+
const parse = require('./middleware/parse')
|
|
4
|
+
const create = require('./middleware/create')
|
|
5
|
+
const read = require('./middleware/read')
|
|
6
|
+
const update = require('./middleware/update')
|
|
7
|
+
const deleet = require('./middleware/delete')
|
|
8
|
+
const operation = require('./middleware/operation')
|
|
9
|
+
const error = require('./middleware/error')
|
|
5
10
|
|
|
6
|
-
const
|
|
11
|
+
const { bufferToBase64 } = require('../_runtime/common/utils/binary')
|
|
7
12
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const update_factory = require('./middleware/update')
|
|
11
|
-
const delete_factory = require('./middleware/delete')
|
|
12
|
-
const operation_factory = require('./middleware/operation')
|
|
13
|
+
const HttpAdapter = require('../../lib/srv/protocols/http')
|
|
14
|
+
const bodyParser4 = require('../odata/middleware/body-parser')
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
// REVISIT: ugly hack -> eliminate
|
|
17
|
+
const { NoaRequest } = require('../odata/ODataAdapter')
|
|
18
|
+
class RestRequest extends NoaRequest {
|
|
19
|
+
get protocol() {
|
|
20
|
+
return 'rest'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const router = express.Router()
|
|
21
|
-
|
|
22
|
-
// -----------------------------------------------------------------------------------------
|
|
23
|
-
// check @requires as soon as possible (DoS)
|
|
24
|
-
//
|
|
25
|
-
const accessRestrictions = getAccessRestrictions(srv)
|
|
26
|
-
router.use((req, res, next) => {
|
|
27
|
-
// ensure there always is a user going forward (not always the case with old or custom auth)
|
|
28
|
-
if (!req.user) req.user = new cds.User.default()
|
|
29
|
-
|
|
30
|
-
// check @restrict and @requires as soon as possible (DoS)
|
|
31
|
-
if (!accessRestrictions.some(r => req.user.is(r))) {
|
|
32
|
-
// > unauthorized or forbidden?
|
|
33
|
-
if (req.user._is_anonymous) {
|
|
34
|
-
// NOTE: "return req._login()" would not invoke custom error handlers
|
|
35
|
-
if (req._login) res.set('www-authenticate', `Basic realm="Users"`)
|
|
36
|
-
else if (req.user._challenges) res.set('www-authenticate', req.user._challenges.join(';'))
|
|
37
|
-
throw cds.error('Unauthorized', { statusCode: 401, code: '401' })
|
|
38
|
-
}
|
|
39
|
-
throw cds.error('Forbidden', { statusCode: 403, code: '403' })
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
next()
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// -----------------------------------------------------------------------------------------
|
|
46
|
-
// service root
|
|
47
|
-
//
|
|
48
|
-
router.head('/', (_, res) => res.json({}))
|
|
49
|
-
router.get('/', (_, res) =>
|
|
50
|
-
res.json({
|
|
51
|
-
entities: Object.keys(srv.entities).map(e => ({ name: e, url: e }))
|
|
52
|
-
})
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
// -----------------------------------------------------------------------------------------
|
|
56
|
-
// parse / validate
|
|
57
|
-
//
|
|
58
|
-
// content-type check
|
|
59
|
-
router.use((req, res, next) => {
|
|
60
|
-
// REVISIT: move that into parse function
|
|
61
|
-
if (req.method in { POST: 1, PUT: 1, PATCH: 1 }) {
|
|
62
|
-
const contentType = req.headers['content-type'] && req.headers['content-type'].split(';')
|
|
63
|
-
if (
|
|
64
|
-
contentType &&
|
|
65
|
-
(!contentType[0].match(/^application\/json$/) || (typeof contentType[1] === 'string' && !contentType[1]))
|
|
66
|
-
) {
|
|
67
|
-
throw cds.error('INVALID_CONTENT_TYPE_ONLY_JSON', { statusCode: 415, code: '415' }) // FIXME: better i18n + use res.status
|
|
68
|
-
}
|
|
24
|
+
class RestAdapter extends HttpAdapter {
|
|
25
|
+
request4(args) {
|
|
26
|
+
return new RestRequest(args)
|
|
27
|
+
}
|
|
69
28
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
29
|
+
get router() {
|
|
30
|
+
const srv = this.service
|
|
31
|
+
const router = super.router
|
|
32
|
+
|
|
33
|
+
const jsonBodyParser = bodyParser4(this)
|
|
34
|
+
|
|
35
|
+
// service root
|
|
36
|
+
router.head('/', (_, res) => res.json({}))
|
|
37
|
+
const entities = Object.keys(srv.entities).map(e => ({ name: e, url: e }))
|
|
38
|
+
router.get('/', (_, res) => res.json({ entities }))
|
|
74
39
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
40
|
+
// validate headers
|
|
41
|
+
router.use((req, res, next) => {
|
|
42
|
+
if (req.method in { POST: 1, PUT: 1, PATCH: 1 } && req.headers['content-type']) {
|
|
43
|
+
const parts = req.headers['content-type'].split(';')
|
|
44
|
+
if (!parts[0].match(/^application\/json$/) || parts[1] === '') {
|
|
45
|
+
throw cds.error('INVALID_CONTENT_TYPE_ONLY_JSON', { statusCode: 415, code: '415' }) // FIXME: better i18n + use res.status
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (req.method in { PUT: 1, PATCH: 1 }) {
|
|
78
49
|
if (req.headers['content-length'] === '0') {
|
|
79
|
-
res.status(400).json({ error: { message: 'Malformed
|
|
50
|
+
res.status(400).json({ error: { message: 'Malformed document', statusCode: 400, code: '400' } })
|
|
80
51
|
return
|
|
81
52
|
}
|
|
82
53
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
;({ result, status } = await update(req))
|
|
128
|
-
break
|
|
129
|
-
case 'DELETE':
|
|
130
|
-
;({ result, status } = await deleet(req))
|
|
131
|
-
break
|
|
54
|
+
|
|
55
|
+
return next()
|
|
56
|
+
})
|
|
57
|
+
router.use(jsonBodyParser)
|
|
58
|
+
router.use(parse(this))
|
|
59
|
+
|
|
60
|
+
// handle
|
|
61
|
+
const operation_middleware = operation(this)
|
|
62
|
+
const create_middleware = create(this)
|
|
63
|
+
const read_middleware = read(this)
|
|
64
|
+
const update_middleware = update(this)
|
|
65
|
+
const delete_middleware = deleet(this)
|
|
66
|
+
router.use(async function dispatch(req, res, next) {
|
|
67
|
+
try {
|
|
68
|
+
let result, status, location
|
|
69
|
+
|
|
70
|
+
if (req._operation) {
|
|
71
|
+
// actions and functions
|
|
72
|
+
;({ result, status } = await operation_middleware(req, res))
|
|
73
|
+
} else {
|
|
74
|
+
// CRUD
|
|
75
|
+
switch (req.method) {
|
|
76
|
+
case 'POST':
|
|
77
|
+
;({ result, status, location } = await create_middleware(req, res))
|
|
78
|
+
break
|
|
79
|
+
case 'HEAD':
|
|
80
|
+
case 'GET':
|
|
81
|
+
;({ result, status } = await read_middleware(req, res))
|
|
82
|
+
break
|
|
83
|
+
case 'PUT':
|
|
84
|
+
case 'PATCH':
|
|
85
|
+
// eslint-disable-next-line no-case-declarations
|
|
86
|
+
const _res = await update_middleware(req, res, next)
|
|
87
|
+
if (_res) ({ result, status } = _res)
|
|
88
|
+
break
|
|
89
|
+
case 'DELETE':
|
|
90
|
+
;({ result, status } = await delete_middleware(req, res))
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (status || result !== undefined) {
|
|
96
|
+
req._result = { result, status, location }
|
|
97
|
+
return next()
|
|
132
98
|
}
|
|
99
|
+
} catch (e) {
|
|
100
|
+
next(e)
|
|
133
101
|
}
|
|
102
|
+
})
|
|
134
103
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
next(e)
|
|
139
|
-
}
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
// -----------------------------------------------------------------------------------------
|
|
143
|
-
// end tx (i.e., commit or rollback)
|
|
144
|
-
//
|
|
145
|
-
router.use(async (req, res, next) => {
|
|
146
|
-
const { result, status, location } = req._result // REVISIT: Ugly voodoo _req._result channel -> eliminate
|
|
147
|
-
|
|
148
|
-
// unfortunately, express doesn't catch async errors -> try catch needed
|
|
149
|
-
try {
|
|
150
|
-
await cds.context?.tx?.commit(result)
|
|
151
|
-
} catch (e) {
|
|
152
|
-
return next(e)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// if authentication or something else within the processing of a cds.Request terminates the request, no need to continue
|
|
156
|
-
if (res.headersSent) return
|
|
157
|
-
|
|
158
|
-
// convert binaries
|
|
159
|
-
let definition = req._operation || req._query.__target
|
|
160
|
-
if (typeof definition === 'string')
|
|
161
|
-
definition =
|
|
162
|
-
srv.model.definitions[definition] ||
|
|
163
|
-
srv.model.definitions[definition.split(':$:')[0]].actions[definition.split(':$:')[1]]
|
|
164
|
-
if (result && srv && definition) bufferToBase64(result, srv, definition)
|
|
165
|
-
|
|
166
|
-
// only set status if not yet modified
|
|
167
|
-
if (status && res.statusCode === 200) res.status(status) // REVISIT: Why only when res.statusCode === 200?
|
|
168
|
-
if (location) res.set('location', location) // REVISIT: When do we redirect?
|
|
169
|
-
if (req.method === 'HEAD')
|
|
170
|
-
// REVISIT: Move that to the implementation of HEAD
|
|
171
|
-
res
|
|
172
|
-
.set({
|
|
173
|
-
'content-type': 'application/json; charset=utf-8',
|
|
174
|
-
'content-length': JSON.stringify(result).length
|
|
175
|
-
})
|
|
176
|
-
.end()
|
|
177
|
-
// need to convert number to string because express interprets integer as status code
|
|
178
|
-
else res.send(typeof result === 'number' ? result.toString() : result) // REVISIT: use req.json() instead?
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
// -----------------------------------------------------------------------------------------
|
|
182
|
-
// error handling
|
|
183
|
-
//
|
|
184
|
-
router.use(async (err, req, res, next) => {
|
|
185
|
-
// REVISIT: should not be neccessary!
|
|
186
|
-
// request may fail during processing or during commit -> both caught here
|
|
187
|
-
|
|
188
|
-
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
189
|
-
await cds.context?.tx?.rollback(err).catch(() => {}) // REVISIT: silently ?!?
|
|
190
|
-
|
|
191
|
-
next(err)
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
if (!cds.env.features.rest_error_handler) {
|
|
195
|
-
router.use(error_factory(srv)) // FIXME: nope -> call next()
|
|
196
|
-
}
|
|
104
|
+
// handle result
|
|
105
|
+
router.use((req, res) => {
|
|
106
|
+
const { result, status, location } = req._result // REVISIT: Ugly voodoo _req._result channel -> eliminate
|
|
197
107
|
|
|
198
|
-
|
|
108
|
+
// if authentication or something else within the processing of a cds.Request terminates the request, no need to continue
|
|
109
|
+
if (res.headersSent) return
|
|
110
|
+
|
|
111
|
+
// convert binaries
|
|
112
|
+
let definition = req._operation || req._query.__target
|
|
113
|
+
if (typeof definition === 'string')
|
|
114
|
+
definition =
|
|
115
|
+
srv.model.definitions[definition] ||
|
|
116
|
+
srv.model.definitions[definition.split(':$:')[0]].actions[definition.split(':$:')[1]]
|
|
117
|
+
if (result && srv && definition) bufferToBase64(result, srv, definition)
|
|
118
|
+
|
|
119
|
+
if (status && res.statusCode === 200) res.status(status) //> only set status if not yet modified
|
|
120
|
+
if (location && !res.getHeader('location')) res.set('location', location)
|
|
121
|
+
|
|
122
|
+
// prettier-ignore
|
|
123
|
+
if (req.method === 'HEAD') res.type('json').set({ 'content-length': JSON.stringify(result).length }).end()
|
|
124
|
+
else res.send(typeof result === 'number' ? result.toString() : result)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// error handling
|
|
128
|
+
router.use(error(this))
|
|
129
|
+
|
|
130
|
+
return router
|
|
131
|
+
}
|
|
199
132
|
}
|
|
200
133
|
|
|
201
134
|
module.exports = RestAdapter
|
|
@@ -1,36 +1,40 @@
|
|
|
1
1
|
const cds = require('../../_runtime/cds')
|
|
2
2
|
const { INSERT } = cds.ql
|
|
3
3
|
|
|
4
|
-
const RestRequest = require('../RestRequest')
|
|
5
|
-
|
|
6
4
|
const _error4 = rejected =>
|
|
7
5
|
rejected.length > 1
|
|
8
6
|
? Object.assign(new Error('MULTIPLE_ERRORS'), { details: rejected.map(r => r.reason) })
|
|
9
7
|
: rejected[0].reason
|
|
10
8
|
|
|
11
|
-
module.exports =
|
|
12
|
-
const {
|
|
9
|
+
module.exports = adapter => {
|
|
10
|
+
const { service } = adapter
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
return async function create(req, res) {
|
|
13
|
+
const { _query: query, _data, _params: params } = req
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
query.entries(_data)
|
|
18
|
-
if (query.INSERT.entries.length > 1) {
|
|
19
|
-
// > batch insert
|
|
20
|
-
const reqs = query.INSERT.entries.map(
|
|
21
|
-
entry => new RestRequest({ query: INSERT.into(query.INSERT.into).entries(entry), _target, params: _params })
|
|
22
|
-
)
|
|
23
|
-
const ress = await Promise.allSettled(reqs.map(req => srv.dispatch(req)))
|
|
24
|
-
const rejected = ress.filter(r => r.status === 'rejected')
|
|
25
|
-
if (rejected.length) throw _error4(rejected)
|
|
26
|
-
result = ress.map(r => r.value)
|
|
27
|
-
} else {
|
|
28
|
-
// > single insert
|
|
29
|
-
const req = new RestRequest({ query, _target, params: _params })
|
|
30
|
-
result = await srv.dispatch(req)
|
|
31
|
-
location = `../${req.entity.replace(srv.name + '.', '')}` // REVISIT: Is it guaranteed that the GET works? Why do we need relative urls?
|
|
32
|
-
for (const k in req.target.keys) location += `/${result[k]}`
|
|
33
|
-
}
|
|
15
|
+
let result, location
|
|
34
16
|
|
|
35
|
-
|
|
17
|
+
// add the data
|
|
18
|
+
query.entries(_data)
|
|
19
|
+
if (query.INSERT.entries.length > 1) {
|
|
20
|
+
// > batch insert
|
|
21
|
+
const cdsReqs = query.INSERT.entries.map(entry => {
|
|
22
|
+
return adapter.request4({ query: INSERT.into(query.INSERT.into).entries(entry), params, req, res })
|
|
23
|
+
})
|
|
24
|
+
const ress = await Promise.allSettled(cdsReqs.map(req => service.dispatch(req)))
|
|
25
|
+
const rejected = ress.filter(r => r.status === 'rejected')
|
|
26
|
+
if (rejected.length) throw _error4(rejected)
|
|
27
|
+
result = ress.map(r => r.value)
|
|
28
|
+
} else {
|
|
29
|
+
// > single insert
|
|
30
|
+
const cdsReq = adapter.request4({ query, params, req, res })
|
|
31
|
+
result = await service.dispatch(cdsReq)
|
|
32
|
+
// REVISIT: location is a restful feature -> share with odata
|
|
33
|
+
// REVISIT: Is it guaranteed that the GET works? Why do we need relative urls?
|
|
34
|
+
location = `../${cdsReq.entity.replace(service.definition.name + '.', '')}`
|
|
35
|
+
for (const k in cdsReq.target.keys) location += `/${result[k]}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { result, status: 201, location }
|
|
39
|
+
}
|
|
36
40
|
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
module.exports = adapter => {
|
|
2
|
+
const { service } = adapter
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
return async function deleet(req, res) {
|
|
5
|
+
const { _query: query, _data: data, _params: params } = req
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
await service.dispatch(adapter.request4({ query, data, params, req, res }))
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
await srv.dispatch(req)
|
|
12
|
-
|
|
13
|
-
return { result: null, status: 204 }
|
|
9
|
+
return { result: null, status: 204 }
|
|
10
|
+
}
|
|
14
11
|
}
|