@sap/cds 7.9.2 → 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 +139 -3656
- 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 +12 -30
- package/lib/auth/index.js +1 -14
- package/lib/auth/jwt-auth.js +14 -30
- 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/srvinfo.js +1 -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 +3 -3
- 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 +86 -37
- 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 +27 -4
- 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 +5 -4
- 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 +16 -12
- 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,7 +1,6 @@
|
|
|
1
1
|
// TODO: split into multiple files
|
|
2
2
|
|
|
3
3
|
const cds = require('../../../')
|
|
4
|
-
const _path = require('./path')
|
|
5
4
|
const _etag = require('./etag')
|
|
6
5
|
|
|
7
6
|
const { toBase64url } = require('../../_runtime/common/utils/binary')
|
|
@@ -227,7 +226,7 @@ const skipToken = (token, cqn) => {
|
|
|
227
226
|
let decoded
|
|
228
227
|
try {
|
|
229
228
|
decoded = JSON.parse(Buffer.from(token, 'base64').toString())
|
|
230
|
-
} catch
|
|
229
|
+
} catch {
|
|
231
230
|
LOG.warn('$skiptoken is not in expected format. Ignoring it.')
|
|
232
231
|
return
|
|
233
232
|
}
|
|
@@ -277,35 +276,103 @@ const skipToken = (token, cqn) => {
|
|
|
277
276
|
}
|
|
278
277
|
|
|
279
278
|
const calculateLocationHeader = (target, srv, result) => {
|
|
280
|
-
const targetName = target.name.replace(`${srv.name}.`, '')
|
|
281
|
-
|
|
282
|
-
const keyValuePairs =
|
|
279
|
+
const targetName = target.name.replace(`${srv.definition.name}.`, '')
|
|
280
|
+
const filteredKeys = target.keys.filter(k => !k.isAssociation).map(k => k.name)
|
|
281
|
+
const keyValuePairs = filteredKeys.reduce((acc, key) => {
|
|
283
282
|
const value = result[key]
|
|
283
|
+
if (value === undefined) return
|
|
284
284
|
if (Buffer.isBuffer(value)) {
|
|
285
285
|
acc[key] = value.toString('base64')
|
|
286
286
|
} else {
|
|
287
|
-
|
|
287
|
+
const _type = target.elements[key]._type
|
|
288
|
+
if (typeof value === 'string' && _type !== 'cds.UUID') acc[key] = `'${value}'`
|
|
289
|
+
else acc[key] = value
|
|
288
290
|
}
|
|
289
291
|
return acc
|
|
290
292
|
}, {})
|
|
291
|
-
|
|
293
|
+
if (!keyValuePairs) return
|
|
292
294
|
let keys
|
|
293
295
|
const entries = Object.entries(keyValuePairs)
|
|
294
296
|
if (entries.length === 1) {
|
|
295
297
|
keys = entries[0][1]
|
|
298
|
+
if (target.elements[entries[0][0]]['@odata.Type'] === 'Edm.String') keys = `'${keys}'`
|
|
296
299
|
} else {
|
|
297
|
-
keys = entries
|
|
300
|
+
keys = entries
|
|
301
|
+
.map(([key, value]) => `${key}=${target.elements[key]['@odata.Type'] === 'Edm.String' ? `'${value}'` : value}`)
|
|
302
|
+
.join(',')
|
|
298
303
|
}
|
|
299
|
-
|
|
300
304
|
return `${targetName}(${keys})`
|
|
301
305
|
}
|
|
302
306
|
|
|
303
307
|
const handleSapMessages = (cdsReq, req, res) => {
|
|
304
|
-
if (!cdsReq.messages || !cdsReq.messages.length) return
|
|
308
|
+
if (res.headersSent || !cdsReq.messages || !cdsReq.messages.length) return
|
|
305
309
|
const msgs = getSapMessages(cdsReq.messages, req)
|
|
306
310
|
if (msgs) res.setHeader('sap-messages', msgs)
|
|
307
311
|
}
|
|
308
312
|
|
|
313
|
+
const isStream = query => {
|
|
314
|
+
const { _propertyAccess, target } = query
|
|
315
|
+
if (!_propertyAccess) return
|
|
316
|
+
|
|
317
|
+
const element = target.elements[_propertyAccess]
|
|
318
|
+
return element._type === 'cds.LargeBinary' && element['@Core.MediaType']
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const isRedirect = query => {
|
|
322
|
+
const { _propertyAccess, target } = query
|
|
323
|
+
if (!_propertyAccess) return
|
|
324
|
+
|
|
325
|
+
const element = target.elements[_propertyAccess]
|
|
326
|
+
return element._type === 'cds.String' && element['@Core.MediaType'] && element['@Core.IsURL']
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const _addKeysDeep = (keys, keysCollector, ignoreManagedBackLinks) => {
|
|
330
|
+
for (const keyName in keys) {
|
|
331
|
+
const key = keys[keyName]
|
|
332
|
+
const foreignKey = key._foreignKey4
|
|
333
|
+
if (key.isAssociation || foreignKey === 'up_' || key['@cds.api.ignore'] === true) continue
|
|
334
|
+
|
|
335
|
+
if (ignoreManagedBackLinks && foreignKey) {
|
|
336
|
+
const navigationElement = keys[foreignKey]
|
|
337
|
+
if (!navigationElement.on && navigationElement._isBacklink) {
|
|
338
|
+
// skip navigation elements that are backlinks
|
|
339
|
+
continue
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if ('elements' in key) {
|
|
344
|
+
_addKeysDeep(key.elements, keysCollector)
|
|
345
|
+
continue
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
keysCollector.push(keyName)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function keysOf(entity, ignoreManagedBackLinks) {
|
|
353
|
+
const keysCollector = []
|
|
354
|
+
if (!entity || !entity.keys) return keysCollector
|
|
355
|
+
|
|
356
|
+
_addKeysDeep(entity.keys, keysCollector, ignoreManagedBackLinks)
|
|
357
|
+
return keysCollector
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// case: single key without name, e.g., Foo(1)
|
|
361
|
+
function addRefToWhereIfNecessary(where, entity) {
|
|
362
|
+
if (!where || where.length !== 1) return 0
|
|
363
|
+
|
|
364
|
+
const isView = !!entity.params
|
|
365
|
+
const keys = isView ? Object.keys(entity.params) : keysOf(entity)
|
|
366
|
+
|
|
367
|
+
if (keys.length !== 1) return 0
|
|
368
|
+
where.unshift(...[{ ref: [keys[0]] }, '='])
|
|
369
|
+
return 1
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function getBoundary(req) {
|
|
373
|
+
return req.headers['content-type']?.match(/boundary=([\d\w'()+_,\-./:=?]{1,70})/i)?.[1]
|
|
374
|
+
}
|
|
375
|
+
|
|
309
376
|
module.exports = {
|
|
310
377
|
cds2edm,
|
|
311
378
|
getSafeNumber,
|
|
@@ -313,7 +380,12 @@ module.exports = {
|
|
|
313
380
|
skipToken,
|
|
314
381
|
calculateLocationHeader,
|
|
315
382
|
handleSapMessages,
|
|
316
|
-
getKeysAndParamsFromPath: _path.getKeysAndParamsFromPath,
|
|
317
383
|
getPreferReturnHeader,
|
|
318
|
-
|
|
384
|
+
isStream,
|
|
385
|
+
isRedirect,
|
|
386
|
+
keysOf,
|
|
387
|
+
addRefToWhereIfNecessary,
|
|
388
|
+
validateIfNoneMatch: _etag.validateIfNoneMatch,
|
|
389
|
+
extractIfNoneMatch: _etag.extractIfNoneMatch,
|
|
390
|
+
getBoundary
|
|
319
391
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const { where2obj } = require('../../_runtime/common/utils/cqn')
|
|
2
|
+
|
|
3
|
+
const _isNavToDraftAdmin = path => path.length > 1 && path[path.length - 1] === 'DraftAdministrativeData'
|
|
4
|
+
|
|
5
|
+
const _lastValidRef = ref => {
|
|
6
|
+
for (let i = ref.length - 1; i >= 0; i--) {
|
|
7
|
+
if (ref[i] in { DraftAdministrativeData: 1, SiblingEntity: 1 }) continue
|
|
8
|
+
return ref[i]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const _odataContext = (query, options) => {
|
|
13
|
+
const { result, isCollection, edmName } = options
|
|
14
|
+
|
|
15
|
+
let path = '$metadata'
|
|
16
|
+
if (query._target.kind === 'service') {
|
|
17
|
+
return path
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
_target: { _isSingleton: isSingleton },
|
|
22
|
+
_propertyAccess: propertyAccess
|
|
23
|
+
} = query
|
|
24
|
+
|
|
25
|
+
path += '#'
|
|
26
|
+
|
|
27
|
+
// REVISIT: subselect is treated as empty array
|
|
28
|
+
const ref =
|
|
29
|
+
query.SELECT?.from?.ref ?? query.UPDATE?.entity?.ref ?? query.INSERT?.into?.ref ?? query.DELETE?.from?.ref ?? []
|
|
30
|
+
|
|
31
|
+
const isNavToDraftAdmin = _isNavToDraftAdmin(ref)
|
|
32
|
+
|
|
33
|
+
if (ref.length > 1) {
|
|
34
|
+
// prepend for relative path
|
|
35
|
+
path = '../'.repeat(ref.length - 1) + path
|
|
36
|
+
}
|
|
37
|
+
const lastRef = ref.at(-1)
|
|
38
|
+
let entityName = isNavToDraftAdmin ? ref[0].id ?? ref[0] : query._target.name ?? edmName
|
|
39
|
+
const serviceName = query._target._service?.name
|
|
40
|
+
|
|
41
|
+
if (query._target._isContained) {
|
|
42
|
+
let cur = query._target
|
|
43
|
+
let refIndex = ref.length - 1
|
|
44
|
+
|
|
45
|
+
entityName = lastRef.id ?? lastRef
|
|
46
|
+
|
|
47
|
+
while (cur._isContained) {
|
|
48
|
+
cur = cur.elements.up_._target
|
|
49
|
+
refIndex--
|
|
50
|
+
const curName = ref[refIndex].id ?? ref[refIndex]
|
|
51
|
+
const where = ref[refIndex].where
|
|
52
|
+
|
|
53
|
+
if (where) {
|
|
54
|
+
let keys
|
|
55
|
+
if (where.length > 3) {
|
|
56
|
+
// multiple keys should contain key name
|
|
57
|
+
const _keys = where2obj(where)
|
|
58
|
+
keys = Object.keys(_keys).map(k => {
|
|
59
|
+
return k + '=' + _keys[k]
|
|
60
|
+
})
|
|
61
|
+
} else {
|
|
62
|
+
// single keys can just contain value
|
|
63
|
+
keys = [where.at(-1).val]
|
|
64
|
+
}
|
|
65
|
+
entityName = curName + '(' + keys.join(',') + ')' + '/' + entityName
|
|
66
|
+
} else {
|
|
67
|
+
// REVISIT: is this correct?
|
|
68
|
+
entityName = curName + '/' + entityName
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (serviceName) {
|
|
74
|
+
entityName = entityName.replace(serviceName + '.', '').replace(/\./g, '_')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
path += entityName
|
|
78
|
+
|
|
79
|
+
if (propertyAccess) {
|
|
80
|
+
path = '../' + path
|
|
81
|
+
|
|
82
|
+
const lastValidRef = _lastValidRef(ref)
|
|
83
|
+
if (lastValidRef.where) {
|
|
84
|
+
let keys
|
|
85
|
+
const isSibling = lastRef === 'SiblingEntity'
|
|
86
|
+
if (lastValidRef.where.length > 3) {
|
|
87
|
+
// multiple keys should contain key name
|
|
88
|
+
const _keys = where2obj(lastValidRef.where)
|
|
89
|
+
keys = Object.keys(_keys).map(k => {
|
|
90
|
+
if (k === 'IsActiveEntity' && isSibling) return k + '=' + !_keys[k]
|
|
91
|
+
return k + '=' + _keys[k]
|
|
92
|
+
})
|
|
93
|
+
} else {
|
|
94
|
+
// single keys can just contain value
|
|
95
|
+
keys = [lastValidRef.where.at(-1).val]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
path += '(' + keys.join(',') + ')'
|
|
99
|
+
} else if (!isSingleton) {
|
|
100
|
+
// use keys from result if not in query
|
|
101
|
+
const _keys = Object.keys(query._target.keys)
|
|
102
|
+
let keyString
|
|
103
|
+
if (_keys.length === 1) {
|
|
104
|
+
keyString = result[_keys[0]]
|
|
105
|
+
} else {
|
|
106
|
+
keyString = _keys.map(k => k.name + '=' + result[k.name]).join(',')
|
|
107
|
+
}
|
|
108
|
+
path += '(' + keyString + ')'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (isNavToDraftAdmin) {
|
|
112
|
+
path += '/' + lastRef
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
path += '/' + propertyAccess
|
|
116
|
+
} else if (isNavToDraftAdmin) {
|
|
117
|
+
const lastValidRef = _lastValidRef(ref)
|
|
118
|
+
if (lastValidRef.where) {
|
|
119
|
+
let keys
|
|
120
|
+
const isSibling = lastRef === 'SiblingEntity'
|
|
121
|
+
if (lastValidRef.where.length > 3) {
|
|
122
|
+
// multiple keys should contain key name
|
|
123
|
+
const _keys = where2obj(lastValidRef.where)
|
|
124
|
+
keys = Object.keys(_keys).map(k => {
|
|
125
|
+
if (k === 'IsActiveEntity' && isSibling) return k + '=' + !_keys[k]
|
|
126
|
+
return k + '=' + _keys[k]
|
|
127
|
+
})
|
|
128
|
+
} else {
|
|
129
|
+
// single keys can just contain value
|
|
130
|
+
keys = [lastValidRef.where.at(-1).val]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
path += '(' + keys.join(',') + ')'
|
|
134
|
+
}
|
|
135
|
+
path += '/' + lastRef
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if ((!isCollection && !isSingleton && !propertyAccess) || (isNavToDraftAdmin && !propertyAccess)) {
|
|
139
|
+
path += '/$entity'
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return path
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* TODO
|
|
147
|
+
*
|
|
148
|
+
* @param {*} query
|
|
149
|
+
* @param {*} [options]
|
|
150
|
+
* @param {*} [options.result]
|
|
151
|
+
* @param {*} [options.isCollection]
|
|
152
|
+
* @param {*} [options.edmName]
|
|
153
|
+
* @returns
|
|
154
|
+
*/
|
|
155
|
+
module.exports = function getODataMetadata(query, options = {}) {
|
|
156
|
+
if (!query._target) return
|
|
157
|
+
|
|
158
|
+
const context = _odataContext(query, options)
|
|
159
|
+
|
|
160
|
+
return { context }
|
|
161
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const cds = require('../../_runtime/cds')
|
|
2
|
+
|
|
3
|
+
const getTemplate = require('../../_runtime/common/utils/template')
|
|
4
|
+
const templateProcessor = require('../../_runtime/common/utils/templateProcessor')
|
|
5
|
+
|
|
6
|
+
const _getParent = (model, name) => {
|
|
7
|
+
const target = model.definitions[name]
|
|
8
|
+
|
|
9
|
+
if (target && target.elements) {
|
|
10
|
+
for (const elementName in target.elements) {
|
|
11
|
+
const element = target.elements[elementName]
|
|
12
|
+
if (element._anchor && element._anchor._isContained) return element._anchor
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const _addEtags = (row, key) => {
|
|
20
|
+
if (!row[key]) return
|
|
21
|
+
row.$etag = row[key].startsWith('W/') ? row[key] : `W/"${row[key]}"`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const _processorFn = () => elementInfo => {
|
|
25
|
+
const { row, plain } = elementInfo
|
|
26
|
+
if (typeof row !== 'object') return
|
|
27
|
+
for (const category of plain.categories) {
|
|
28
|
+
const { row, key } = elementInfo
|
|
29
|
+
switch (category) {
|
|
30
|
+
case '@odata.etag':
|
|
31
|
+
_addEtags(row, key)
|
|
32
|
+
break
|
|
33
|
+
case '@cds.api.ignore':
|
|
34
|
+
delete row[key]
|
|
35
|
+
break
|
|
36
|
+
case 'binary':
|
|
37
|
+
if (Buffer.isBuffer(row[key])) {
|
|
38
|
+
// if the result object gets serialize to json, the buffer shall become a base64 string
|
|
39
|
+
row[key].toJSON = function () {
|
|
40
|
+
return this.toString('base64')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
break
|
|
44
|
+
case 'array':
|
|
45
|
+
row[key] ??= []
|
|
46
|
+
break
|
|
47
|
+
// no default
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const _pick = element => {
|
|
53
|
+
const categories = []
|
|
54
|
+
if (element['@odata.etag']) categories.push('@odata.etag')
|
|
55
|
+
if (element['@cds.api.ignore']) categories.push('@cds.api.ignore')
|
|
56
|
+
if (element._type === 'cds.Binary') categories.push('binary')
|
|
57
|
+
if (element.items) categories.push('array')
|
|
58
|
+
if (categories.length) return { categories }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = function postProcess(target, service, result, isMinimal) {
|
|
62
|
+
if (!result) return
|
|
63
|
+
|
|
64
|
+
let { model } = service
|
|
65
|
+
if (service.isExtensible) model = cds.context?.model || model
|
|
66
|
+
|
|
67
|
+
if (!model.definitions[target.name]) {
|
|
68
|
+
if (model.definitions[target.items?.type]) target = target.items
|
|
69
|
+
else return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const cacheKey = isMinimal ? 'postProcessMinimal' : 'postProcess'
|
|
73
|
+
const parent = _getParent(model, target.name)
|
|
74
|
+
const options = { pick: _pick, ignore: isMinimal ? el => el.isAssociation : undefined }
|
|
75
|
+
const template = getTemplate(cacheKey, service, target, options, parent)
|
|
76
|
+
|
|
77
|
+
if (template.elements.size === 0) return
|
|
78
|
+
|
|
79
|
+
// normalize result to rows
|
|
80
|
+
result = result.value != null && Object.keys(result).filter(k => !k.match(/^\W/)).length === 1 ? result.value : result
|
|
81
|
+
|
|
82
|
+
if (typeof result === 'object' && result != null) {
|
|
83
|
+
const rows = Array.isArray(result) ? result : [result]
|
|
84
|
+
|
|
85
|
+
// process each row
|
|
86
|
+
const processFn = _processorFn()
|
|
87
|
+
for (const row of rows) templateProcessor({ processFn, row, template })
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -1,23 +1,140 @@
|
|
|
1
1
|
const cds = require('../../_runtime/cds')
|
|
2
|
-
const {
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
const { SELECT } = cds.ql
|
|
3
|
+
|
|
4
|
+
const { DRAFT_COLUMNS_MAP } = require('../../_runtime/common/constants/draft')
|
|
5
|
+
|
|
6
|
+
const _keysOf = (row, target) => {
|
|
7
|
+
const keyElements = Object.values(target.keys || {}).filter(v => !v.virtual)
|
|
8
|
+
// > singleton
|
|
9
|
+
if (!keyElements.length) return
|
|
10
|
+
const keys = {}
|
|
11
|
+
for (const key of keyElements) {
|
|
12
|
+
if (key._isAssociationStrict) continue
|
|
13
|
+
if (row[key.name] === undefined) continue // key is not in data, so ignore it
|
|
14
|
+
keys[key.name] = key.elements ? { val: JSON.stringify(row[key.name]) } : row[key.name]
|
|
15
|
+
}
|
|
16
|
+
return keys
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const _getSimpleSelectCQN = (target, data, subject) => {
|
|
20
|
+
let cqn
|
|
21
|
+
|
|
22
|
+
const keys = _keysOf(data, target)
|
|
23
|
+
if (subject?.ref.length > 1) {
|
|
24
|
+
cqn = SELECT.one(subject)
|
|
25
|
+
if (keys) cqn.where(keys)
|
|
26
|
+
} else if (!keys) {
|
|
27
|
+
//> singleton
|
|
28
|
+
cqn = SELECT.one(target)
|
|
29
|
+
} else {
|
|
30
|
+
cqn = SELECT.one(target, keys)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (target.query && target.query.SELECT && target.query.SELECT.orderBy) {
|
|
34
|
+
cqn.SELECT.orderBy = target.query.SELECT.orderBy
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return cqn
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const _mergeExpandCQNs = cqns => {
|
|
41
|
+
const cols = cqns[0].SELECT.columns
|
|
42
|
+
for (const cqn of cqns.slice(1)) {
|
|
43
|
+
for (const col of cqn.SELECT.columns) {
|
|
44
|
+
if (!col.expand) continue
|
|
45
|
+
const idx = cols.findIndex(ele => {
|
|
46
|
+
if (!col.ref) return
|
|
47
|
+
if (ele.ref) return ele.ref[0] === col.ref[0]
|
|
48
|
+
if (ele.as) return ele.as === col.ref[0]
|
|
49
|
+
})
|
|
50
|
+
if (idx === -1) {
|
|
51
|
+
cols.push(col)
|
|
52
|
+
} else {
|
|
53
|
+
const colExists = cols[idx]
|
|
54
|
+
if (colExists.as && colExists.val === null) {
|
|
55
|
+
cols[idx] = col
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
if (col.as && col.val === null) continue
|
|
59
|
+
const mergedExpandCQN = _mergeExpandCQNs([
|
|
60
|
+
{ SELECT: { columns: colExists.expand } },
|
|
61
|
+
{ SELECT: { columns: col.expand } }
|
|
62
|
+
])
|
|
63
|
+
colExists.expand = mergedExpandCQN.SELECT.columns
|
|
64
|
+
}
|
|
13
65
|
}
|
|
14
|
-
} catch (e) {
|
|
15
|
-
// read was not possible because of access restrictions => ignore
|
|
16
|
-
if (!(Number(e.code) in { 401: 1, 403: 1, 404: 1, 405: 1 })) throw e
|
|
17
|
-
result = null
|
|
18
66
|
}
|
|
67
|
+
return cqns[0]
|
|
68
|
+
}
|
|
19
69
|
|
|
20
|
-
|
|
70
|
+
const _getExpandColumn = (data, element) => {
|
|
71
|
+
const key = element.name
|
|
72
|
+
if (!(key in data)) return
|
|
73
|
+
data = data[key]
|
|
74
|
+
if ((Array.isArray(data) && data.length === 0) || data == null) {
|
|
75
|
+
// performance tweak, keep in mind it is only for compositions
|
|
76
|
+
return { val: null, as: key }
|
|
77
|
+
}
|
|
78
|
+
const cqn = Array.isArray(data)
|
|
79
|
+
? _mergeExpandCQNs(data.map(data => _getSelect({ target: element._target, data }, true)))
|
|
80
|
+
: _getSelect({ target: element._target, data }, true)
|
|
81
|
+
return { ref: [key], expand: cqn.SELECT.columns }
|
|
21
82
|
}
|
|
22
83
|
|
|
23
|
-
|
|
84
|
+
const _getColumns = (target, data, prefix = []) => {
|
|
85
|
+
const columns = []
|
|
86
|
+
for (const each in target.elements) {
|
|
87
|
+
if (target.elements[each]['@cds.api.ignore']) continue
|
|
88
|
+
if (each in DRAFT_COLUMNS_MAP) continue
|
|
89
|
+
if (!cds.env.features.stream_compat && target.elements[each].type === 'cds.LargeBinary') continue
|
|
90
|
+
const element = target.elements[each]
|
|
91
|
+
if (element.elements && data[each]) {
|
|
92
|
+
prefix.push(element.name)
|
|
93
|
+
columns.push(..._getColumns(element, data[each], prefix))
|
|
94
|
+
prefix.pop()
|
|
95
|
+
} else if (element.isComposition && !prefix.length) {
|
|
96
|
+
const col = _getExpandColumn(data, element, prefix)
|
|
97
|
+
if (col) columns.push(col)
|
|
98
|
+
} else if (!element.isAssociation) {
|
|
99
|
+
columns.push({ ref: [...prefix, each] })
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return columns
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
* recursively builds a select cqn (depth determined by req.data)
|
|
107
|
+
*/
|
|
108
|
+
const _getSelect = (cdsReq, deep = false) => {
|
|
109
|
+
const { target, data, subject } = cdsReq
|
|
110
|
+
const cqn = _getSimpleSelectCQN(target, data, subject)
|
|
111
|
+
if (deep) cqn.columns(..._getColumns(target, data))
|
|
112
|
+
return cqn
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = (adapter, middleware) => {
|
|
116
|
+
const { service } = adapter
|
|
117
|
+
|
|
118
|
+
const _getQuery =
|
|
119
|
+
middleware === 'create'
|
|
120
|
+
? cdsReq => _getSelect(cdsReq, cdsReq.event === 'CREATE')
|
|
121
|
+
: cdsReq => SELECT.one(cdsReq.subject)
|
|
122
|
+
|
|
123
|
+
return async function readAfterWrite(cdsReq) {
|
|
124
|
+
try {
|
|
125
|
+
const query = _getQuery(cdsReq)
|
|
126
|
+
const result = await service.dispatch(adapter.request4({ query, params: cdsReq.params }))
|
|
127
|
+
|
|
128
|
+
// REVISIT: really needed? -> disable and run odata v2 tests
|
|
129
|
+
// NEW/PATCH must not include DraftAdministrativeData_DraftUUID for plain v4 usage, however required for odata-v2
|
|
130
|
+
if (result && cdsReq.target._isDraftEnabled && cdsReq.headers?.['x-cds-odata-version'] !== 'v2') {
|
|
131
|
+
delete result.DraftAdministrativeData_DraftUUID
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result
|
|
135
|
+
} catch (e) {
|
|
136
|
+
// if read was not possible because of access restrictions then ignore else throw
|
|
137
|
+
if (!(Number(e.code) in { 401: 1, 403: 1, 404: 1, 405: 1 })) throw e
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|