@sap/cds 5.5.5 → 5.6.0
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 +107 -1
- package/apis/services.d.ts +27 -1
- package/app/index.js +22 -11
- package/bin/build/buildTaskFactory.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +1 -1
- package/bin/build/provider/fiori/index.js +1 -1
- package/bin/build/provider/hana/2migration.js +8 -7
- package/bin/build/provider/java-cf/index.js +1 -1
- package/bin/deploy/to-hana/hana.js +1 -17
- package/common.cds +8 -0
- package/lib/compile/to/sql.js +22 -2
- package/lib/connect/bindings.js +2 -1
- package/lib/core/reflect.js +3 -1
- package/lib/env/index.js +175 -41
- package/lib/env/requires.js +16 -1
- package/lib/i18n/localize.js +31 -4
- package/lib/index.js +3 -3
- package/lib/log/format/kibana.js +6 -2
- package/lib/ql/Query.js +1 -0
- package/lib/ql/SELECT.js +15 -8
- package/lib/ql/Whereable.js +5 -0
- package/lib/req/context.js +13 -5
- package/lib/serve/Service-dispatch.js +8 -1
- package/lib/utils/axios.js +7 -0
- package/lib/utils/data.js +1 -1
- package/lib/utils/tests.js +1 -1
- package/libx/_runtime/audit/Service.js +18 -18
- package/libx/_runtime/audit/generic/personal/access.js +1 -1
- package/libx/_runtime/audit/generic/personal/modification.js +3 -2
- package/libx/_runtime/audit/generic/personal/utils.js +23 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +14 -19
- package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
- package/libx/_runtime/cds-services/util/assert.js +29 -13
- package/libx/_runtime/cds.js +2 -1
- package/libx/_runtime/common/aspects/Association.js +72 -0
- package/libx/_runtime/common/aspects/any.js +8 -45
- package/libx/_runtime/common/aspects/entity.js +0 -1
- package/libx/_runtime/common/aspects/relation.js +40 -0
- package/libx/_runtime/common/aspects/utils.js +73 -1
- package/libx/_runtime/common/auth/strategies/utils/uaa.js +1 -12
- package/libx/_runtime/common/composition/data.js +3 -2
- package/libx/_runtime/common/composition/delete.js +3 -1
- package/libx/_runtime/common/composition/tree.js +23 -18
- package/libx/_runtime/common/composition/utils.js +34 -8
- package/libx/_runtime/common/error/frontend.js +6 -1
- package/libx/_runtime/common/generic/auth.js +5 -9
- package/libx/_runtime/common/generic/crud.js +2 -2
- package/libx/_runtime/common/generic/etag.js +11 -8
- package/libx/_runtime/common/generic/input.js +3 -3
- package/libx/_runtime/common/generic/paging.js +9 -5
- package/libx/_runtime/common/generic/put.js +3 -2
- package/libx/_runtime/common/generic/sorting.js +3 -3
- package/libx/_runtime/common/generic/temporal.js +3 -3
- package/libx/_runtime/common/utils/cqn.js +20 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
- package/libx/_runtime/common/utils/csn.js +50 -52
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
- package/libx/_runtime/common/utils/generateOnCond.js +40 -70
- package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
- package/libx/_runtime/common/utils/postProcessing.js +3 -0
- package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
- package/libx/_runtime/common/utils/resolveStructured.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +7 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
- package/libx/_runtime/common/utils/template.js +54 -46
- package/libx/_runtime/db/Service.js +9 -2
- package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
- package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
- package/libx/_runtime/db/generic/create.js +1 -0
- package/libx/_runtime/db/generic/input.js +7 -11
- package/libx/_runtime/db/generic/integrity.js +2 -2
- package/libx/_runtime/db/generic/rewrite.js +2 -5
- package/libx/_runtime/db/generic/update.js +1 -0
- package/libx/_runtime/db/query/read.js +9 -4
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
- package/libx/_runtime/db/sql-builder/annotations.js +1 -0
- package/libx/_runtime/db/utils/columns.js +14 -43
- package/libx/_runtime/fiori/generic/activate.js +3 -2
- package/libx/_runtime/fiori/generic/before.js +2 -2
- package/libx/_runtime/fiori/generic/cancel.js +3 -2
- package/libx/_runtime/fiori/generic/delete.js +3 -2
- package/libx/_runtime/fiori/generic/edit.js +2 -2
- package/libx/_runtime/fiori/generic/new.js +2 -2
- package/libx/_runtime/fiori/generic/patch.js +2 -2
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +17 -63
- package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
- package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
- package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
- package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
- package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
- package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
- package/libx/_runtime/fiori/uiflex/index.js +35 -0
- package/libx/_runtime/fiori/uiflex/utils.js +78 -0
- package/libx/_runtime/fiori/utils/handler.js +3 -13
- package/libx/_runtime/fiori/utils/where.js +6 -1
- package/libx/_runtime/hana/pool.js +12 -11
- package/libx/_runtime/hana/search2cqn4sql.js +34 -43
- package/libx/_runtime/hana/searchToContains.js +3 -3
- package/libx/_runtime/index.js +5 -2
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +9 -1
- package/libx/_runtime/messaging/common-utils/connections.js +11 -14
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
- package/libx/_runtime/messaging/message-queuing.js +18 -0
- package/libx/_runtime/remote/Service.js +14 -2
- package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
- package/libx/_runtime/remote/utils/client.js +117 -23
- package/libx/_runtime/sqlite/Service.js +2 -2
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
- package/libx/gql/GraphQLAdapter.js +33 -0
- package/libx/gql/constants/adapter.js +69 -0
- package/libx/gql/constants/cds.js +18 -0
- package/libx/gql/constants/graphql.js +33 -0
- package/libx/gql/resolvers/crud/create.js +15 -0
- package/libx/gql/resolvers/crud/delete.js +24 -0
- package/libx/gql/resolvers/crud/index.js +6 -0
- package/libx/gql/resolvers/crud/read.js +25 -0
- package/libx/gql/resolvers/crud/update.js +31 -0
- package/libx/gql/resolvers/crud/utils/index.js +36 -0
- package/libx/gql/resolvers/field.js +5 -0
- package/libx/gql/resolvers/index.js +7 -0
- package/libx/gql/resolvers/mutation.js +23 -0
- package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
- package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
- package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
- package/libx/gql/resolvers/parse/ast/index.js +3 -0
- package/libx/gql/resolvers/parse/ast/meta.js +4 -0
- package/libx/gql/resolvers/parse/ast/variable.js +7 -0
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
- package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
- package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
- package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
- package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
- package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
- package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
- package/libx/gql/resolvers/parse/utils/index.js +8 -0
- package/libx/gql/resolvers/query.js +13 -0
- package/libx/gql/resolvers/root.js +34 -0
- package/libx/gql/schema/generate.js +18 -0
- package/libx/gql/schema/index.js +5 -0
- package/libx/gql/schema/mutation.js +76 -0
- package/libx/gql/schema/query.js +108 -0
- package/libx/gql/schema/typeDefMap.js +45 -0
- package/libx/gql/schema/utils/index.js +54 -0
- package/libx/gql/utils/index.js +12 -0
- package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
- package/libx/odata/index.js +80 -0
- package/libx/odata/odata2cqn/afterburner.js +170 -0
- package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
- package/libx/odata/odata2cqn/index.js +3 -0
- package/libx/odata/odata2cqn/parser.js +1 -0
- package/libx/odata/utils/index.js +64 -0
- package/libx/rest/RestAdapter.js +101 -0
- package/libx/rest/RestRequest.js +30 -0
- package/libx/rest/index.js +3 -0
- package/libx/rest/middleware/auth.js +22 -0
- package/libx/rest/middleware/content.js +15 -0
- package/libx/rest/middleware/create.js +40 -0
- package/libx/rest/middleware/delete.js +20 -0
- package/libx/rest/middleware/error.js +56 -0
- package/libx/rest/middleware/operation.js +39 -0
- package/libx/rest/middleware/parse.js +90 -0
- package/libx/rest/middleware/read.js +29 -0
- package/libx/rest/middleware/update.js +42 -0
- package/libx/rest/utils/data.js +65 -0
- package/package.json +4 -1
- package/server.js +20 -2
- package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
- package/libx/_runtime/cds-services/util/auditlog.js +0 -247
- package/libx/_runtime/cds-services/util/xsenv.js +0 -51
- package/libx/_runtime/common/utils/backlinks.js +0 -83
- package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
- package/libx/_runtime/odata/index.js +0 -55
- package/libx/_runtime/odata/odata2cqn.js +0 -1
- package/libx/_runtime/odata/readToCqn.js +0 -129
- package/libx/_runtime/remote/cqn2odata/index.js +0 -2
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const { computeColumnsToBeSearched } = require('../cds-services/services/utils/columns')
|
|
2
2
|
const searchToLike = require('../common/utils/searchToLike')
|
|
3
3
|
const { isContainsPredicateSupported, searchToContains } = require('./searchToContains')
|
|
4
|
-
const { getOnCond } = require('../common/utils/generateOnCond')
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Computes a CQN expression for a search query.
|
|
@@ -15,30 +14,27 @@ const { getOnCond } = require('../common/utils/generateOnCond')
|
|
|
15
14
|
* But in contrast to the explicitly written `LIKE ?`, the parameter is already resolved to its concrete value, making
|
|
16
15
|
* it better optimizable by the HANA optimizer.
|
|
17
16
|
*
|
|
18
|
-
* @param {object}
|
|
17
|
+
* @param {object} query The CQN object
|
|
19
18
|
* @param {import('@sap/cds-compiler/lib/api/main').CSN} entity The target entity for the search query
|
|
20
19
|
* @param {import('../types/api').search2cqnOptions} [options]
|
|
21
20
|
* @returns {object} The modified CQN object
|
|
22
21
|
*/
|
|
23
|
-
const search2cqn4sql = (
|
|
24
|
-
const cqnSearchPhrase =
|
|
25
|
-
if (!cqnSearchPhrase) return
|
|
22
|
+
const search2cqn4sql = (query, entity, options) => {
|
|
23
|
+
const cqnSearchPhrase = query.SELECT.search
|
|
24
|
+
if (!cqnSearchPhrase) return query
|
|
26
25
|
|
|
27
|
-
let { columns = computeColumnsToBeSearched(
|
|
26
|
+
let { columns: columnsToBeSearched = computeColumnsToBeSearched(query, entity), locale } = options
|
|
28
27
|
const localizedAssociation = _getLocalizedAssociation(entity)
|
|
29
28
|
|
|
30
29
|
// If the localized association is defined for the target entity,
|
|
31
30
|
// there should be at least one localized element.
|
|
32
31
|
const resolveLocalizedTextsAtRuntime = !!localizedAssociation
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// The `_suppressLocalization` property is:
|
|
37
|
-
// enumerable: false (default), writable: false (default)
|
|
38
|
-
Object.defineProperty(cqn, '_suppressLocalization', { value: true })
|
|
33
|
+
// suppress the localize handler from redirecting the query's target to the localized view
|
|
34
|
+
Object.defineProperty(query, '_suppressLocalization', { value: true })
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
const onCondition =
|
|
36
|
+
if (resolveLocalizedTextsAtRuntime) {
|
|
37
|
+
const onCondition = entity._relations[localizedAssociation.name].join(localizedAssociation.target, entity.name)
|
|
42
38
|
|
|
43
39
|
// replace $user_locale placeholder with the user locale or the HANA session context
|
|
44
40
|
onCondition[onCondition.length - 2] = { val: locale || "SESSION_CONTEXT('LOCALE')" }
|
|
@@ -46,31 +42,26 @@ const search2cqn4sql = (cqn, entity, options) => {
|
|
|
46
42
|
// inner join the target table with the _texts table (the _texts table contains
|
|
47
43
|
// the translated texts)
|
|
48
44
|
const localizedEntityName = localizedAssociation.target
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
// to prevent a SQL ambiguity error. E.g., SqlError message: column ambiguously
|
|
54
|
-
// defined.
|
|
55
|
-
cqn.SELECT.columns = _unshiftEntityNameToColumnRef(entity, cqn.SELECT.columns)
|
|
56
|
-
columns = _unshiftEntityNameToColumnRef(entity, columns)
|
|
57
|
-
if (cqn.SELECT.groupBy) cqn.SELECT.groupBy = _unshiftEntityNameToColumnRef(entity, cqn.SELECT.groupBy)
|
|
45
|
+
query.join(localizedEntityName).on(onCondition)
|
|
46
|
+
|
|
47
|
+
// prevent SQL ambiguity error for columns with the same name
|
|
48
|
+
columnsToBeSearched = _addAliasToColumns(query, entity, columnsToBeSearched)
|
|
58
49
|
} // else --> resolve localized texts via localized view (default)
|
|
59
50
|
|
|
60
|
-
const useContains =
|
|
51
|
+
const useContains = isContainsPredicateSupported(query)
|
|
61
52
|
let expression
|
|
62
53
|
|
|
63
54
|
if (useContains) {
|
|
64
|
-
expression = searchToContains(cqnSearchPhrase,
|
|
55
|
+
expression = searchToContains(cqnSearchPhrase, columnsToBeSearched)
|
|
65
56
|
} else {
|
|
66
57
|
// No CONTAINS optimization possible. The search implementation for localized
|
|
67
58
|
// texts falls back to the LIKE predicate.
|
|
68
|
-
expression = searchToLike(cqnSearchPhrase,
|
|
59
|
+
expression = searchToLike(cqnSearchPhrase, columnsToBeSearched)
|
|
69
60
|
}
|
|
70
61
|
|
|
71
62
|
// REVISIT: find out here if where or having must be used
|
|
72
|
-
|
|
73
|
-
return
|
|
63
|
+
query._aggregated ? query.having(expression) : query.where(expression)
|
|
64
|
+
return query
|
|
74
65
|
}
|
|
75
66
|
|
|
76
67
|
const _getLocalizedAssociation = entity => {
|
|
@@ -78,28 +69,28 @@ const _getLocalizedAssociation = entity => {
|
|
|
78
69
|
return associations && associations.localized
|
|
79
70
|
}
|
|
80
71
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
select: localizedAssociation.target,
|
|
86
|
-
join: entity.name
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const _unshiftEntityNameToColumnRef = (entity, columns) => {
|
|
72
|
+
// The inner join modifies the original SELECT ... FROM query and adds ambiguity,
|
|
73
|
+
// therefore add the table/entity name (as a preceding element) to the columns ref
|
|
74
|
+
// to prevent a SQL ambiguity error.
|
|
75
|
+
const _addAliasToColumns = (query, entity, columnsToBeSearched) => {
|
|
92
76
|
const localizedEntityName = _getLocalizedAssociation(entity).target
|
|
93
77
|
const elements = entity.elements
|
|
94
|
-
|
|
95
|
-
|
|
78
|
+
const entityName = entity.name
|
|
79
|
+
const _addAliasToColumn = (entityName, localizedEntityName, elements) => column => {
|
|
96
80
|
const columnRef = column.ref
|
|
97
81
|
if (!columnRef) return column
|
|
98
82
|
const columnName = columnRef[0]
|
|
99
83
|
const localizedElement = elements[columnName].localized
|
|
100
|
-
const
|
|
101
|
-
return { ref: [
|
|
102
|
-
}
|
|
84
|
+
const targetEntityName = localizedElement ? localizedEntityName : entityName
|
|
85
|
+
return { ref: [targetEntityName, columnName] }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
query.SELECT.columns = query.SELECT.columns.map(_addAliasToColumn(entityName, localizedEntityName, elements))
|
|
89
|
+
const columns = columnsToBeSearched.map(_addAliasToColumn(entityName, localizedEntityName, elements))
|
|
90
|
+
|
|
91
|
+
if (query.SELECT.groupBy) {
|
|
92
|
+
query.SELECT.groupBy = query.SELECT.groupBy.map(_addAliasToColumn(entityName, localizedEntityName, elements))
|
|
93
|
+
}
|
|
103
94
|
|
|
104
95
|
return columns
|
|
105
96
|
}
|
|
@@ -64,12 +64,12 @@ const searchToContains = (cqnSearchPhrase, columns) => {
|
|
|
64
64
|
return expression
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const isContainsPredicateSupported =
|
|
68
|
-
const cqnSearchPhrase =
|
|
67
|
+
const isContainsPredicateSupported = query => {
|
|
68
|
+
const cqnSearchPhrase = query.SELECT.search
|
|
69
69
|
|
|
70
70
|
// REVISIT: In the future, to further optimize search queries, you might
|
|
71
71
|
// want to remove the following condition(s).
|
|
72
|
-
if (
|
|
72
|
+
if (query._aggregated) return false
|
|
73
73
|
|
|
74
74
|
// REVISIT: search terms starting with whitespace after a `NOT` operator does not
|
|
75
75
|
// return the expected result on SAP HANA (BCP 2180256508). In addition, double
|
package/libx/_runtime/index.js
CHANGED
|
@@ -5,9 +5,12 @@ module.exports = {
|
|
|
5
5
|
return this._odatav4 || (this._odatav4 = require('./cds-services/adapter/odata-v4/to'))
|
|
6
6
|
},
|
|
7
7
|
|
|
8
|
-
/** @type {import('./cds-services/adapter/rest/to')} */
|
|
9
8
|
get rest() {
|
|
10
|
-
|
|
9
|
+
if (!this._rest) {
|
|
10
|
+
if (global.cds.env.features.rest_new_adapter) this._rest = require('../rest')
|
|
11
|
+
else this._rest = require('./cds-services/adapter/rest/to')
|
|
12
|
+
}
|
|
13
|
+
return this._rest
|
|
11
14
|
}
|
|
12
15
|
},
|
|
13
16
|
|
|
@@ -37,7 +37,7 @@ class AMQPWebhookMessaging extends MessagingService {
|
|
|
37
37
|
// Some messaging systems don't adhere to the standard that the payload has a `data` property.
|
|
38
38
|
// For these cases, we interpret the whole payload as `data`.
|
|
39
39
|
let data, headers
|
|
40
|
-
if ('data' in _payload) {
|
|
40
|
+
if (typeof _payload === 'object' && 'data' in _payload) {
|
|
41
41
|
data = _payload.data
|
|
42
42
|
headers = { ..._payload }
|
|
43
43
|
delete headers.data
|
|
@@ -3,6 +3,14 @@ const LOG = cds.log('messaging')
|
|
|
3
3
|
const ClientAmqp = require('@sap/xb-msg-amqp-v100').Client
|
|
4
4
|
const { connect, disconnect } = require('./connections')
|
|
5
5
|
|
|
6
|
+
const _JSONorString = string => {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(string)
|
|
9
|
+
} catch (e) {
|
|
10
|
+
return string
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
const addDataListener = (client, queue, prefix, cb) =>
|
|
7
15
|
new Promise((resolve, reject) => {
|
|
8
16
|
const source = `${prefix}${queue}`
|
|
@@ -11,7 +19,7 @@ const addDataListener = (client, queue, prefix, cb) =>
|
|
|
11
19
|
.attach(source)
|
|
12
20
|
.on('data', async raw => {
|
|
13
21
|
const buffer = Buffer.concat(raw.payload.chunks)
|
|
14
|
-
const payload =
|
|
22
|
+
const payload = _JSONorString(buffer.toString())
|
|
15
23
|
const topic = raw.source.properties.to.replace(/^topic:\/*/, '')
|
|
16
24
|
await cb(topic, payload, null, { done: raw.done, failed: raw.failed })
|
|
17
25
|
})
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const LOG = cds.log('messaging')
|
|
3
3
|
|
|
4
|
-
const MAX_NUMBER_RECONNECTS = 1000
|
|
5
4
|
const MAX_WAITING_TIME = 1480000
|
|
6
5
|
|
|
7
6
|
const _waitingTime = x => (x > 18 ? MAX_WAITING_TIME : (Math.pow(1.5, x) + Math.random()) * 1000)
|
|
8
7
|
|
|
9
|
-
const
|
|
8
|
+
const _connectUntilConnected = (client, x) => {
|
|
10
9
|
setTimeout(() => {
|
|
11
10
|
connect(client, true).catch(e => {
|
|
12
|
-
LOG._warn && LOG.warn(`Connection to Enterprise Messaging Client lost: Unsuccessful attempt to reconnect (${
|
|
13
|
-
|
|
14
|
-
if (n < MAX_NUMBER_RECONNECTS) _periodicallyReconnect(client, x + 1, n + 1)
|
|
11
|
+
LOG._warn && LOG.warn(`Connection to Enterprise Messaging Client lost: Unsuccessful attempt to reconnect (${x}).`)
|
|
12
|
+
_connectUntilConnected(client, x + 1)
|
|
15
13
|
})
|
|
16
14
|
}, _waitingTime(x))
|
|
17
15
|
}
|
|
@@ -30,22 +28,21 @@ const connect = (client, keepAlive) => {
|
|
|
30
28
|
client.disconnect()
|
|
31
29
|
})
|
|
32
30
|
|
|
31
|
+
if (keepAlive) {
|
|
32
|
+
client.once('disconnected', () => {
|
|
33
|
+
client.removeAllListeners('error')
|
|
34
|
+
client.removeAllListeners('connected')
|
|
35
|
+
_connectUntilConnected(client, 0)
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
resolve(this)
|
|
34
40
|
})
|
|
35
41
|
.once('error', err => {
|
|
36
|
-
client.removeAllListeners('disconnected')
|
|
37
42
|
client.removeAllListeners('connected')
|
|
38
43
|
reject(err)
|
|
39
44
|
})
|
|
40
45
|
|
|
41
|
-
if (keepAlive) {
|
|
42
|
-
client.once('disconnected', () => {
|
|
43
|
-
client.removeAllListeners('error')
|
|
44
|
-
client.removeAllListeners('connected')
|
|
45
|
-
_periodicallyReconnect(client, 0, 0)
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
46
|
client.connect()
|
|
50
47
|
})
|
|
51
48
|
}
|
|
@@ -6,7 +6,7 @@ const _queueName = ({ appName, appID, ownNamespace }) => {
|
|
|
6
6
|
const queueName = (options, optionsApp = {}) => {
|
|
7
7
|
const namespace = options.credentials && options.credentials.namespace
|
|
8
8
|
if (options.queue && options.queue.name) {
|
|
9
|
-
if (
|
|
9
|
+
if (namespace) return options.queue.name.replace(/\$namespace/g, namespace)
|
|
10
10
|
return options.queue.name
|
|
11
11
|
}
|
|
12
12
|
const ownNamespace = namespace
|
|
@@ -65,7 +65,8 @@ class EndpointRegistry {
|
|
|
65
65
|
const other = authInfo
|
|
66
66
|
? {
|
|
67
67
|
_: { req: { authInfo, headers: {}, query: {} } }, // for messaging to retrieve subdomain
|
|
68
|
-
user: new cds.User.Privileged(
|
|
68
|
+
user: new cds.User.Privileged(),
|
|
69
|
+
tenant: tenantId
|
|
69
70
|
}
|
|
70
71
|
: {}
|
|
71
72
|
if (!cb) return res.sendStatus(200)
|
|
@@ -29,6 +29,20 @@ class MQManagement {
|
|
|
29
29
|
return res.body
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
async getQueues() {
|
|
33
|
+
const res = await authorizedRequest({
|
|
34
|
+
method: 'GET',
|
|
35
|
+
uri: this.options.url,
|
|
36
|
+
path: `/v1/management/queues`,
|
|
37
|
+
oa2: this.options.auth.oauth2,
|
|
38
|
+
attemptInfo: () => LOG._info && LOG.info('Get queues'),
|
|
39
|
+
errMsg: `Queues could not be retrieved`,
|
|
40
|
+
target: { kind: 'QUEUE' },
|
|
41
|
+
tokenStore: this
|
|
42
|
+
})
|
|
43
|
+
return res.body && res.body.results
|
|
44
|
+
}
|
|
45
|
+
|
|
32
46
|
createQueue(queueName = this.queueName) {
|
|
33
47
|
return authorizedRequest({
|
|
34
48
|
method: 'PUT',
|
|
@@ -121,6 +135,10 @@ class MQManagement {
|
|
|
121
135
|
}
|
|
122
136
|
await Promise.all([...this.subscribedTopics].map(kv => kv[0]).map(t => this.createSubscription(t)))
|
|
123
137
|
}
|
|
138
|
+
|
|
139
|
+
waitUntilReady() {
|
|
140
|
+
return this
|
|
141
|
+
}
|
|
124
142
|
}
|
|
125
143
|
|
|
126
144
|
class MessageQueuing extends AMQPWebhookMessaging {
|
|
@@ -12,7 +12,7 @@ if (!LOG._debug) {
|
|
|
12
12
|
const { resolveView, getTransition, restoreLink, findQueryTarget } = require('../common/utils/resolveView')
|
|
13
13
|
const { postProcess } = require('../common/utils/postProcessing')
|
|
14
14
|
const { getKind, run, getDestination, getAdditionalOptions, getReqOptions } = require('./utils/client')
|
|
15
|
-
const { formatVal } = require('
|
|
15
|
+
const { formatVal } = require('../../odata/utils')
|
|
16
16
|
|
|
17
17
|
const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
|
|
18
18
|
|
|
@@ -125,6 +125,7 @@ class RemoteService extends cds.Service {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
this.datasource = this.options.datasource
|
|
128
|
+
this.destinationOptions = this.options.destinationOptions
|
|
128
129
|
this.destination =
|
|
129
130
|
this.options.credentials.destination ||
|
|
130
131
|
getDestination((this.definition && this.definition.name) || this.datasource, this.options.credentials)
|
|
@@ -133,6 +134,11 @@ class RemoteService extends cds.Service {
|
|
|
133
134
|
this.path = this.options.credentials.path
|
|
134
135
|
this.kind = getKind(this.options) // TODO: Simplify
|
|
135
136
|
|
|
137
|
+
const clearKeysFromData = function (req) {
|
|
138
|
+
if (req.target && req.target.keys) for (const k of Object.keys(req.target.keys)) delete req.data[k]
|
|
139
|
+
}
|
|
140
|
+
this.before('UPDATE', '*', Object.assign(clearKeysFromData, { _initial: true }))
|
|
141
|
+
|
|
136
142
|
for (const each of this.entities) {
|
|
137
143
|
for (const a in each.actions) {
|
|
138
144
|
_addHandlerActionFunction(this, each.actions[a], each)
|
|
@@ -153,7 +159,13 @@ class RemoteService extends cds.Service {
|
|
|
153
159
|
const resolvedTarget = resolvedTargetOfQuery(query) || getTransition(req.target, this).target
|
|
154
160
|
const reqOptions = getReqOptions(req, query, this)
|
|
155
161
|
reqOptions.headers = _setHeaders(reqOptions.headers, req)
|
|
156
|
-
const additionalOptions = getAdditionalOptions(
|
|
162
|
+
const additionalOptions = getAdditionalOptions(
|
|
163
|
+
req,
|
|
164
|
+
this.destination,
|
|
165
|
+
this.kind,
|
|
166
|
+
resolvedTarget,
|
|
167
|
+
this.destinationOptions
|
|
168
|
+
)
|
|
157
169
|
|
|
158
170
|
// hidden compat flag in order to suppress logging response body of failed request
|
|
159
171
|
if (req._suppressRemoteResponseBody) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import sdkCore from '@sap-cloud-sdk/core'
|
|
2
|
+
export interface DestinationOptions extends Omit<sdkCore.DestinationOptions, 'selectionStrategy'> {
|
|
3
|
+
/*
|
|
4
|
+
* @see https://sap.github.io/cloud-sdk/api/1.50.0/modules/sap_cloud_sdk_core#DestinationSelectionStrategies
|
|
5
|
+
*/
|
|
6
|
+
selectionStrategy?: 'alwaysProvider' | 'alwaysSubscriber' | 'subscriberFirst'
|
|
7
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const LOG = cds.log('remote')
|
|
3
|
-
const cdsLocale = require('../../../../lib/req/locale.js')
|
|
4
3
|
|
|
5
|
-
const
|
|
4
|
+
const cdsLocale = require('../../../../lib/req/locale')
|
|
5
|
+
|
|
6
6
|
const { convertV2ResponseData } = require('./dataConversion')
|
|
7
7
|
|
|
8
8
|
let _cloudSdkCore
|
|
@@ -14,16 +14,38 @@ const PPPD = {
|
|
|
14
14
|
DELETE: 1
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
|
|
17
|
+
const KINDS_SUPPORTING_BATCH = { odata: 1, 'odata-v2': 1, 'odata-v4': 1 }
|
|
18
|
+
|
|
19
|
+
const _executeHttpRequest = async ({ requestConfig, destination, destinationOptions, jwt }) => {
|
|
20
|
+
const { getDestination, executeHttpRequest } = cloudSdkCore()
|
|
19
21
|
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const destinationName = typeof destination === 'string' && destination
|
|
23
|
+
if (destinationName) {
|
|
24
|
+
destination = await getDestination(destinationName, resolveDestinationOptions(destinationOptions, jwt))
|
|
25
|
+
} else if (destination.forwardAuthToken) {
|
|
26
|
+
destination = {
|
|
27
|
+
...destination,
|
|
28
|
+
headers: destination.headers ? { ...destination.headers } : {},
|
|
29
|
+
authentication: 'NoAuthentication'
|
|
30
|
+
}
|
|
31
|
+
delete destination.forwardAuthToken
|
|
32
|
+
if (jwt) {
|
|
33
|
+
destination.headers.authorization = `Bearer ${jwt}`
|
|
34
|
+
} else {
|
|
35
|
+
LOG._warn && LOG.warn('Missing JWT token for forwardAuthToken')
|
|
36
|
+
}
|
|
24
37
|
}
|
|
25
38
|
|
|
26
|
-
|
|
39
|
+
let requestOptions
|
|
40
|
+
if (PPPD[requestConfig.method] && cds.env.features.fetch_csrf) {
|
|
41
|
+
requestOptions = { fetchCsrfToken: true }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return executeHttpRequest(destination, requestConfig, requestOptions)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const cloudSdkCore = function () {
|
|
48
|
+
return _cloudSdkCore || (_cloudSdkCore = require('@sap-cloud-sdk/core'))
|
|
27
49
|
}
|
|
28
50
|
|
|
29
51
|
const getDestination = (name, credentials) => {
|
|
@@ -34,6 +56,26 @@ const getDestination = (name, credentials) => {
|
|
|
34
56
|
return { name, ...credentials }
|
|
35
57
|
}
|
|
36
58
|
|
|
59
|
+
/**
|
|
60
|
+
* @param {import('./client-types').DestinationOptions} [options]
|
|
61
|
+
* @param {string} [jwt]
|
|
62
|
+
* @returns {import('@sap-cloud-sdk/core').DestinationOptions}
|
|
63
|
+
*/
|
|
64
|
+
const resolveDestinationOptions = function (options, jwt) {
|
|
65
|
+
if (!options && !jwt) return undefined
|
|
66
|
+
|
|
67
|
+
const resolvedOptions = Object.assign({}, options || {})
|
|
68
|
+
resolvedOptions.userJwt = jwt
|
|
69
|
+
|
|
70
|
+
if (options && options.selectionStrategy) {
|
|
71
|
+
resolvedOptions.selectionStrategy = cloudSdkCore().DestinationSelectionStrategies[options.selectionStrategy]
|
|
72
|
+
if (!resolvedOptions.selectionStrategy)
|
|
73
|
+
throw new Error(`Unsupported destination selection strategy "${options.selectionStrategy}".`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return resolvedOptions
|
|
77
|
+
}
|
|
78
|
+
|
|
37
79
|
const getKind = options => {
|
|
38
80
|
const kind = (options.credentials && options.credentials.kind) || options.kind
|
|
39
81
|
if (typeof kind === 'object') {
|
|
@@ -175,17 +217,19 @@ const _getSanitizedError = (e, reqOptions, suppressRemoteResponseBody) => {
|
|
|
175
217
|
return e
|
|
176
218
|
}
|
|
177
219
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
220
|
+
// eslint-disable-next-line complexity
|
|
221
|
+
const run = async (
|
|
222
|
+
requestConfig,
|
|
223
|
+
{ destination, jwt, kind, resolvedTarget, suppressRemoteResponseBody, destinationOptions }
|
|
224
|
+
) => {
|
|
181
225
|
let response
|
|
182
226
|
try {
|
|
183
|
-
response = await _executeHttpRequest(
|
|
227
|
+
response = await _executeHttpRequest({ requestConfig, destination, destinationOptions, jwt })
|
|
184
228
|
} catch (e) {
|
|
185
229
|
// > axios received status >= 400 -> gateway error
|
|
186
230
|
e.message = e.message ? 'Error during request to remote service: ' + e.message : 'Request to remote service failed.'
|
|
187
231
|
|
|
188
|
-
const sanitizedError = _getSanitizedError(e,
|
|
232
|
+
const sanitizedError = _getSanitizedError(e, requestConfig, suppressRemoteResponseBody)
|
|
189
233
|
|
|
190
234
|
LOG._warn && LOG.warn(sanitizedError)
|
|
191
235
|
|
|
@@ -198,15 +242,15 @@ const run = async (reqOptions, { destination, jwt, kind, resolvedTarget, suppres
|
|
|
198
242
|
response.headers['content-type'] &&
|
|
199
243
|
response.headers['content-type'].includes('text/html') &&
|
|
200
244
|
!(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
245
|
+
requestConfig.headers.accept.includes('text/html') ||
|
|
246
|
+
requestConfig.headers.accept.includes('text/*') ||
|
|
247
|
+
requestConfig.headers.accept.includes('*/*')
|
|
204
248
|
)
|
|
205
249
|
) {
|
|
206
250
|
const e = new Error("Received content-type 'text/html' which is not part of accepted content types")
|
|
207
251
|
e.response = response
|
|
208
252
|
|
|
209
|
-
const sanitizedError = _getSanitizedError(e,
|
|
253
|
+
const sanitizedError = _getSanitizedError(e, requestConfig, suppressRemoteResponseBody)
|
|
210
254
|
|
|
211
255
|
LOG._warn && LOG.warn(sanitizedError)
|
|
212
256
|
|
|
@@ -216,13 +260,38 @@ const run = async (reqOptions, { destination, jwt, kind, resolvedTarget, suppres
|
|
|
216
260
|
})
|
|
217
261
|
}
|
|
218
262
|
|
|
263
|
+
// get result of $batch
|
|
264
|
+
// does only support read requests as of now
|
|
265
|
+
if (requestConfig._autoBatch) {
|
|
266
|
+
// response data splitted by empty lines
|
|
267
|
+
// 1. entry contains batch id and batch headers
|
|
268
|
+
// 2. entry contains request status code and request headers
|
|
269
|
+
// 3. entry contains data or error
|
|
270
|
+
const responseDataSplitted = response.data.split('\r\n\r\n')
|
|
271
|
+
// remove closing batch id
|
|
272
|
+
const [content] = responseDataSplitted[2].split('\r\n')
|
|
273
|
+
const contentJSON = JSON.parse(content)
|
|
274
|
+
|
|
275
|
+
if (responseDataSplitted[1].startsWith('HTTP/1.1 2')) {
|
|
276
|
+
response.data = contentJSON
|
|
277
|
+
}
|
|
278
|
+
if (responseDataSplitted[1].startsWith('HTTP/1.1 4') || responseDataSplitted[1].startsWith('HTTP/1.1 5')) {
|
|
279
|
+
contentJSON.message = contentJSON.message
|
|
280
|
+
? 'Error during request to remote service: ' + contentJSON.message
|
|
281
|
+
: 'Request to remote service failed.'
|
|
282
|
+
const sanitizedError = _getSanitizedError(contentJSON, requestConfig)
|
|
283
|
+
LOG._warn && LOG.warn(sanitizedError)
|
|
284
|
+
throw Object.assign(new Error(contentJSON.message), { statusCode: 502, innererror: sanitizedError })
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
219
288
|
if (kind === 'odata-v4') return _purgeODataV4(response.data)
|
|
220
|
-
if (kind === 'odata-v2') return _purgeODataV2(response.data, resolvedTarget,
|
|
289
|
+
if (kind === 'odata-v2') return _purgeODataV2(response.data, resolvedTarget, requestConfig.headers)
|
|
221
290
|
if (kind === 'odata') {
|
|
222
291
|
if (typeof response.data !== 'object') return response.data
|
|
223
292
|
// try to guess if we need to purge v2 or v4
|
|
224
293
|
if (response.data.d) {
|
|
225
|
-
return _purgeODataV2(response.data, resolvedTarget,
|
|
294
|
+
return _purgeODataV2(response.data, resolvedTarget, requestConfig.headers)
|
|
226
295
|
}
|
|
227
296
|
return _purgeODataV4(response.data)
|
|
228
297
|
}
|
|
@@ -241,7 +310,7 @@ const getJwt = req => {
|
|
|
241
310
|
}
|
|
242
311
|
|
|
243
312
|
const _cqnToReqOptions = (query, kind, model) => {
|
|
244
|
-
const queryObject =
|
|
313
|
+
const queryObject = cds.odata.urlify(query, { kind, model })
|
|
245
314
|
return {
|
|
246
315
|
method: queryObject.method,
|
|
247
316
|
url: encodeURI(
|
|
@@ -312,14 +381,39 @@ const getReqOptions = (req, query, service) => {
|
|
|
312
381
|
}
|
|
313
382
|
reqOptions.url = formatPath(reqOptions.url)
|
|
314
383
|
|
|
384
|
+
// batch envelope if needed
|
|
385
|
+
if (
|
|
386
|
+
KINDS_SUPPORTING_BATCH[service.kind] &&
|
|
387
|
+
reqOptions.method === 'GET' &&
|
|
388
|
+
reqOptions.url.length > ((cds.env.remote && cds.env.remote.max_get_url_length) || 1028)
|
|
389
|
+
) {
|
|
390
|
+
reqOptions._autoBatch = true
|
|
391
|
+
reqOptions.data = [
|
|
392
|
+
'--batch1',
|
|
393
|
+
'Content-Type: application/http',
|
|
394
|
+
'Content-Transfer-Encoding: binary',
|
|
395
|
+
'',
|
|
396
|
+
`${reqOptions.method} ${reqOptions.url.replace(/^\//, '')} HTTP/1.1`,
|
|
397
|
+
...Object.keys(reqOptions.headers).map(k => `${k}: ${reqOptions.headers[k]}`),
|
|
398
|
+
'',
|
|
399
|
+
'',
|
|
400
|
+
'--batch1--',
|
|
401
|
+
''
|
|
402
|
+
].join('\r\n')
|
|
403
|
+
reqOptions.method = 'POST'
|
|
404
|
+
reqOptions.headers.accept = 'multipart/mixed'
|
|
405
|
+
reqOptions.headers['content-type'] = 'multipart/mixed; boundary=batch1'
|
|
406
|
+
reqOptions.url = '/$batch'
|
|
407
|
+
}
|
|
408
|
+
|
|
315
409
|
if (service.path) reqOptions.url = `${encodeURI(service.path)}${reqOptions.url}`
|
|
316
410
|
|
|
317
411
|
return reqOptions
|
|
318
412
|
}
|
|
319
413
|
|
|
320
|
-
const getAdditionalOptions = (req, destination, kind, resolvedTarget) => {
|
|
414
|
+
const getAdditionalOptions = (req, destination, kind, resolvedTarget, destinationOptions) => {
|
|
321
415
|
const jwt = getJwt(req)
|
|
322
|
-
const additionalOptions = { destination, kind, resolvedTarget }
|
|
416
|
+
const additionalOptions = { destination, kind, resolvedTarget, destinationOptions }
|
|
323
417
|
if (jwt) additionalOptions.jwt = jwt
|
|
324
418
|
return additionalOptions
|
|
325
419
|
}
|
|
@@ -71,7 +71,7 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
71
71
|
*/
|
|
72
72
|
// all others, i.e. CREATE, DROP table, ...
|
|
73
73
|
this.on('*', function (req) {
|
|
74
|
-
return this._run(this.model, this.dbc, req.query || req.event, req)
|
|
74
|
+
return this._run(this.model, this.dbc, req.query || req.event, req, req.data)
|
|
75
75
|
})
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -189,7 +189,7 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
189
189
|
log('DROP TABLE IF EXISTS ' + entity + ';')
|
|
190
190
|
}
|
|
191
191
|
log()
|
|
192
|
-
for (const each of createEntities) log(each + '
|
|
192
|
+
for (const each of createEntities) log(each + '\n')
|
|
193
193
|
return
|
|
194
194
|
}
|
|
195
195
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
const { foreignKeyPropagations } = require('../common/utils/foreignKeyPropagations')
|
|
2
1
|
const getError = require('../common/error')
|
|
3
2
|
|
|
4
3
|
function _convertRefForAssocToOneManaged(element, refEntry) {
|
|
5
4
|
const maybeManagedKey = refEntry.ref.join('_')
|
|
6
|
-
|
|
7
|
-
if (_foreignKeyPropagations.filter(key => key.parentFieldName === maybeManagedKey)[0]) {
|
|
5
|
+
if (element._foreignKeys.find(key => key.parentElement && key.parentElement.name === maybeManagedKey)) {
|
|
8
6
|
refEntry.ref = [maybeManagedKey]
|
|
9
7
|
} else {
|
|
10
8
|
throw getError(501, 'Path expressions in query options are not supported on SQLite')
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const cds = require('../_runtime/cds')
|
|
2
|
+
|
|
3
|
+
const express = require('express')
|
|
4
|
+
const { graphqlHTTP } = require('express-graphql')
|
|
5
|
+
const { makeExecutableSchema } = require('@graphql-tools/schema')
|
|
6
|
+
|
|
7
|
+
const { generate } = require('./schema')
|
|
8
|
+
const { fieldResolver, createRootResolvers } = require('./resolvers')
|
|
9
|
+
|
|
10
|
+
class GraphQLAdapter extends express.Router {
|
|
11
|
+
constructor(services, options) {
|
|
12
|
+
super()
|
|
13
|
+
const mergedOptions = { ...defaultOptions, ...options }
|
|
14
|
+
|
|
15
|
+
const path = mergedOptions.path
|
|
16
|
+
delete mergedOptions.path
|
|
17
|
+
|
|
18
|
+
const applicationServices = Object.values(services).filter(service => service instanceof cds.ApplicationService)
|
|
19
|
+
|
|
20
|
+
const typeDefs = generate(applicationServices)
|
|
21
|
+
const resolvers = createRootResolvers(applicationServices)
|
|
22
|
+
|
|
23
|
+
const schema = makeExecutableSchema({ typeDefs, resolvers })
|
|
24
|
+
|
|
25
|
+
this.use(path, graphqlHTTP({ fieldResolver, schema, ...mergedOptions }))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const defaultOptions = {
|
|
30
|
+
path: '/graphql'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = GraphQLAdapter
|