@sap/cds 5.5.3 → 5.6.1

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.
Files changed (227) hide show
  1. package/CHANGELOG.md +134 -1
  2. package/apis/services.d.ts +27 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/index.js +1 -1
  12. package/lib/compile/to/sql.js +22 -2
  13. package/lib/connect/bindings.js +2 -1
  14. package/lib/connect/index.js +1 -1
  15. package/lib/core/infer.js +1 -1
  16. package/lib/core/reflect.js +3 -1
  17. package/lib/env/index.js +175 -41
  18. package/lib/env/requires.js +24 -3
  19. package/lib/i18n/localize.js +33 -5
  20. package/lib/index.js +7 -6
  21. package/lib/log/format/kibana.js +6 -2
  22. package/lib/ql/DELETE.js +1 -1
  23. package/lib/ql/INSERT.js +1 -1
  24. package/lib/ql/Query.js +13 -10
  25. package/lib/ql/SELECT.js +15 -8
  26. package/lib/ql/UPDATE.js +1 -1
  27. package/lib/ql/Whereable.js +5 -0
  28. package/lib/req/context.js +87 -37
  29. package/lib/req/{impl.js → request.js} +1 -1
  30. package/lib/req/{res.js → response.js} +0 -0
  31. package/lib/serve/Service-api.js +1 -1
  32. package/lib/serve/Service-dispatch.js +12 -2
  33. package/lib/serve/Service-handlers.js +21 -7
  34. package/lib/serve/Service-methods.js +1 -1
  35. package/lib/serve/Transaction.js +7 -6
  36. package/lib/serve/index.js +1 -1
  37. package/lib/utils/axios.js +7 -0
  38. package/lib/utils/data.js +1 -1
  39. package/libx/_runtime/audit/Service.js +18 -18
  40. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  41. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  42. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  43. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +6 -0
  44. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  45. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  50. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  51. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  52. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  53. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  54. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  55. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  56. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  57. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  58. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  59. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  64. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  67. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +9 -2
  68. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  69. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  70. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  71. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  72. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  73. package/libx/_runtime/cds-services/util/assert.js +29 -13
  74. package/libx/_runtime/cds.js +2 -1
  75. package/libx/_runtime/common/aspects/Association.js +72 -0
  76. package/libx/_runtime/common/aspects/any.js +8 -45
  77. package/libx/_runtime/common/aspects/entity.js +0 -1
  78. package/libx/_runtime/common/aspects/relation.js +40 -0
  79. package/libx/_runtime/common/aspects/utils.js +73 -1
  80. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  81. package/libx/_runtime/common/composition/data.js +3 -2
  82. package/libx/_runtime/common/composition/delete.js +3 -1
  83. package/libx/_runtime/common/composition/tree.js +23 -18
  84. package/libx/_runtime/common/composition/utils.js +34 -8
  85. package/libx/_runtime/common/error/frontend.js +6 -1
  86. package/libx/_runtime/common/generic/auth.js +15 -13
  87. package/libx/_runtime/common/generic/crud.js +2 -2
  88. package/libx/_runtime/common/generic/etag.js +11 -8
  89. package/libx/_runtime/common/generic/input.js +3 -3
  90. package/libx/_runtime/common/generic/paging.js +9 -5
  91. package/libx/_runtime/common/generic/put.js +3 -2
  92. package/libx/_runtime/common/generic/sorting.js +3 -3
  93. package/libx/_runtime/common/generic/temporal.js +3 -3
  94. package/libx/_runtime/common/toggles/alpha.js +1 -1
  95. package/libx/_runtime/common/utils/cqn.js +20 -1
  96. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  97. package/libx/_runtime/common/utils/csn.js +50 -52
  98. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  99. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  100. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  101. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  102. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  103. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  104. package/libx/_runtime/common/utils/resolveView.js +19 -9
  105. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  106. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  107. package/libx/_runtime/common/utils/template.js +54 -46
  108. package/libx/_runtime/db/Service.js +9 -2
  109. package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
  110. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  111. package/libx/_runtime/db/generic/create.js +1 -0
  112. package/libx/_runtime/db/generic/input.js +7 -11
  113. package/libx/_runtime/db/generic/integrity.js +2 -2
  114. package/libx/_runtime/db/generic/rewrite.js +2 -5
  115. package/libx/_runtime/db/generic/update.js +1 -0
  116. package/libx/_runtime/db/query/read.js +10 -5
  117. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
  118. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  119. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  120. package/libx/_runtime/db/utils/columns.js +14 -43
  121. package/libx/_runtime/db/utils/deep.js +5 -7
  122. package/libx/_runtime/fiori/generic/activate.js +3 -2
  123. package/libx/_runtime/fiori/generic/before.js +2 -2
  124. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  125. package/libx/_runtime/fiori/generic/delete.js +3 -2
  126. package/libx/_runtime/fiori/generic/edit.js +2 -2
  127. package/libx/_runtime/fiori/generic/new.js +2 -2
  128. package/libx/_runtime/fiori/generic/patch.js +2 -2
  129. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  130. package/libx/_runtime/fiori/generic/read.js +17 -63
  131. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  132. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  133. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  134. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  135. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  136. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  137. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  138. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  139. package/libx/_runtime/fiori/utils/handler.js +3 -13
  140. package/libx/_runtime/fiori/utils/where.js +6 -1
  141. package/libx/_runtime/hana/Service.js +5 -2
  142. package/libx/_runtime/hana/execute.js +1 -1
  143. package/libx/_runtime/hana/pool.js +12 -11
  144. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  145. package/libx/_runtime/hana/searchToContains.js +3 -3
  146. package/libx/_runtime/index.js +5 -2
  147. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  148. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  149. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  150. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  151. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  152. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  153. package/libx/_runtime/messaging/message-queuing.js +18 -0
  154. package/libx/_runtime/remote/Service.js +14 -2
  155. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  156. package/libx/_runtime/remote/utils/client.js +117 -23
  157. package/libx/_runtime/sqlite/Service.js +4 -3
  158. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  159. package/libx/_runtime/sqlite/execute.js +1 -1
  160. package/libx/gql/GraphQLAdapter.js +33 -0
  161. package/libx/gql/constants/adapter.js +69 -0
  162. package/libx/gql/constants/cds.js +18 -0
  163. package/libx/gql/constants/graphql.js +33 -0
  164. package/libx/gql/resolvers/crud/create.js +15 -0
  165. package/libx/gql/resolvers/crud/delete.js +24 -0
  166. package/libx/gql/resolvers/crud/index.js +6 -0
  167. package/libx/gql/resolvers/crud/read.js +25 -0
  168. package/libx/gql/resolvers/crud/update.js +31 -0
  169. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  170. package/libx/gql/resolvers/field.js +5 -0
  171. package/libx/gql/resolvers/index.js +7 -0
  172. package/libx/gql/resolvers/mutation.js +23 -0
  173. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  174. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  175. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  176. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  177. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  178. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  179. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  180. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  181. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  182. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  183. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  184. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  185. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  186. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  187. package/libx/gql/resolvers/query.js +13 -0
  188. package/libx/gql/resolvers/root.js +34 -0
  189. package/libx/gql/schema/generate.js +18 -0
  190. package/libx/gql/schema/index.js +5 -0
  191. package/libx/gql/schema/mutation.js +76 -0
  192. package/libx/gql/schema/query.js +108 -0
  193. package/libx/gql/schema/typeDefMap.js +45 -0
  194. package/libx/gql/schema/utils/index.js +54 -0
  195. package/libx/gql/utils/index.js +12 -0
  196. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  197. package/libx/odata/index.js +80 -0
  198. package/libx/odata/odata2cqn/afterburner.js +170 -0
  199. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  200. package/libx/odata/odata2cqn/index.js +3 -0
  201. package/libx/odata/odata2cqn/parser.js +1 -0
  202. package/libx/odata/utils/index.js +64 -0
  203. package/libx/rest/RestAdapter.js +101 -0
  204. package/libx/rest/RestRequest.js +30 -0
  205. package/libx/rest/index.js +3 -0
  206. package/libx/rest/middleware/auth.js +22 -0
  207. package/libx/rest/middleware/content.js +15 -0
  208. package/libx/rest/middleware/create.js +40 -0
  209. package/libx/rest/middleware/delete.js +20 -0
  210. package/libx/rest/middleware/error.js +56 -0
  211. package/libx/rest/middleware/operation.js +39 -0
  212. package/libx/rest/middleware/parse.js +90 -0
  213. package/libx/rest/middleware/read.js +29 -0
  214. package/libx/rest/middleware/update.js +42 -0
  215. package/libx/rest/utils/data.js +65 -0
  216. package/package.json +4 -1
  217. package/server.js +42 -29
  218. package/lib/req/cls.js +0 -39
  219. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  220. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  221. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  222. package/libx/_runtime/common/utils/backlinks.js +0 -83
  223. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  224. package/libx/_runtime/odata/index.js +0 -55
  225. package/libx/_runtime/odata/odata2cqn.js +0 -1
  226. package/libx/_runtime/odata/readToCqn.js +0 -129
  227. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -0,0 +1,6 @@
1
+ const executeCreate = require('./create')
2
+ const executeRead = require('./read')
3
+ const executeUpdate = require('./update')
4
+ const executeDelete = require('./delete')
5
+
6
+ module.exports = { executeCreate, executeRead, executeUpdate, executeDelete }
@@ -0,0 +1,25 @@
1
+ const { ARGUMENT } = require('../../constants/adapter')
2
+ const { getArgumentByName, astToColumns, astToWhere, astToOrderBy, astToLimit } = require('../parse/ast2cqn')
3
+
4
+ module.exports = async (service, entityFQN, selection) => {
5
+ let query = service.read(entityFQN)
6
+ query.columns(astToColumns(selection.selectionSet.selections))
7
+
8
+ const filter = getArgumentByName(selection.arguments, ARGUMENT.FILTER)
9
+ if (filter) {
10
+ query.where(astToWhere(filter))
11
+ }
12
+
13
+ const orderBy = getArgumentByName(selection.arguments, ARGUMENT.ORDER_BY)
14
+ if (orderBy) {
15
+ query.orderBy(astToOrderBy(orderBy))
16
+ }
17
+
18
+ const top = getArgumentByName(selection.arguments, ARGUMENT.TOP)
19
+ const skip = getArgumentByName(selection.arguments, ARGUMENT.SKIP)
20
+ if (top) {
21
+ query.limit(astToLimit(top, skip))
22
+ }
23
+
24
+ return await service.tx(tx => tx.run(query))
25
+ }
@@ -0,0 +1,31 @@
1
+ const { ARGUMENT } = require('../../constants/adapter')
2
+ const { getArgumentByName, astToColumns, astToWhere, astToEntries } = require('../parse/ast2cqn')
3
+ const { entriesStructureToEntityStructure } = require('./utils')
4
+
5
+ module.exports = async (service, entityFQN, selection) => {
6
+ const filter = getArgumentByName(selection.arguments, ARGUMENT.FILTER)
7
+
8
+ let queryBeforeUpdate = service.read(entityFQN)
9
+ queryBeforeUpdate.columns(astToColumns(selection.selectionSet.selections))
10
+
11
+ if (filter) {
12
+ queryBeforeUpdate.where(astToWhere(filter))
13
+ }
14
+
15
+ const resultBeforeUpdate = await service.tx(tx => tx.run(queryBeforeUpdate))
16
+
17
+ let query = service.update(entityFQN)
18
+
19
+ if (filter) {
20
+ query.where(astToWhere(filter))
21
+ }
22
+
23
+ const input = getArgumentByName(selection.arguments, ARGUMENT.INPUT)
24
+ const entries = entriesStructureToEntityStructure(service, entityFQN, astToEntries(input))
25
+ query.with(entries)
26
+
27
+ const result = await service.tx(tx => tx.run(query))
28
+
29
+ // Merge selected fields with updated data
30
+ return resultBeforeUpdate.map(original => ({ ...original, ...result }))
31
+ }
@@ -0,0 +1,36 @@
1
+ const getEntityNameWithoutServicePrefix = (service, entityFQN) => entityFQN.replace(`${service.name}.`, '')
2
+
3
+ const getEntityByFQN = (service, entityFQN) => service.entities[getEntityNameWithoutServicePrefix(service, entityFQN)]
4
+
5
+ const objectStructureToEntityStructure = (service, entityFQN, entry) => {
6
+ const entity = getEntityByFQN(service, entityFQN)
7
+ for (const [k, v] of Object.entries(entry)) {
8
+ const element = entity.elements[k]
9
+ if (element.isComposition || element.isAssociation) {
10
+ if (Array.isArray(v)) {
11
+ if (element.is2one) {
12
+ entry[k] = v[0]
13
+ }
14
+ } else if (typeof v === 'object') {
15
+ if (element.is2many) {
16
+ entry[k] = [v]
17
+ }
18
+ }
19
+ entriesStructureToEntityStructure(service, element.target, v)
20
+ }
21
+ }
22
+ return entry
23
+ }
24
+
25
+ const entriesStructureToEntityStructure = (service, entityFQN, entries) => {
26
+ if (Array.isArray(entries)) {
27
+ for (const entry of entries) {
28
+ objectStructureToEntityStructure(service, entityFQN, entry)
29
+ }
30
+ } else {
31
+ objectStructureToEntityStructure(service, entityFQN, entries)
32
+ }
33
+ return entries
34
+ }
35
+
36
+ module.exports = { entriesStructureToEntityStructure }
@@ -0,0 +1,5 @@
1
+ // The GraphQL.js defaultFieldResolver does not support returning aliased values that resolve to fields with aliases
2
+ module.exports = (source, args, context, info) => {
3
+ const responseKey = info.fieldNodes[0].alias ? info.fieldNodes[0].alias.value : info.fieldName
4
+ return source[responseKey]
5
+ }
@@ -0,0 +1,7 @@
1
+ const fieldResolver = require('./field')
2
+ const createRootResolvers = require('./root')
3
+
4
+ module.exports = {
5
+ fieldResolver,
6
+ createRootResolvers
7
+ }
@@ -0,0 +1,23 @@
1
+ const cds = require('../../_runtime/cds')
2
+ const LOG = cds.log('graphql')
3
+
4
+ const { MUTATION_PREFIX } = require('../constants/adapter')
5
+ const { cdsName } = require('../utils')
6
+ const { executeCreate, executeUpdate, executeDelete } = require('./crud')
7
+
8
+ const GQL_MUTATION_REGEX = new RegExp(`^(${Object.values(MUTATION_PREFIX).join('|')})_(.+)$`)
9
+
10
+ const actionToExecuteFunction = {
11
+ [MUTATION_PREFIX.CREATE]: executeCreate,
12
+ [MUTATION_PREFIX.UPDATE]: executeUpdate,
13
+ [MUTATION_PREFIX.DELETE]: executeDelete
14
+ }
15
+
16
+ module.exports = async (service, gqlName, field) => {
17
+ const { 1: mutationAction, 2: gqlNameWithoutAction } = gqlName.match(GQL_MUTATION_REGEX)
18
+ const entityFQN = `${service.name}.${cdsName(gqlNameWithoutAction)}`
19
+
20
+ LOG.log(`mutation on ${entityFQN}`)
21
+
22
+ return await actionToExecuteFunction[mutationAction](service, entityFQN, field)
23
+ }
@@ -0,0 +1,51 @@
1
+ const { isVariable, isListValue, isObjectValue } = require('../utils')
2
+ const fragmentSpreadSelections = require('./fragment')
3
+ const substituteVariable = require('./variable')
4
+ const removeMetaFieldsFromSelections = require('./meta')
5
+
6
+ const traverseObjectValue = (info, objectValue) =>
7
+ objectValue.fields.map(field => traverseArgumentOrObjectField(info, field))
8
+
9
+ const traverseListValue = (info, listValue) => {
10
+ for (let i = 0; i < listValue.values.length; i++) {
11
+ const value = listValue.values[i]
12
+ if (isVariable(value)) {
13
+ listValue.values[i] = substituteVariable(info, value)
14
+ } else if (isObjectValue(value)) {
15
+ traverseObjectValue(info, value)
16
+ }
17
+ }
18
+ }
19
+
20
+ const traverseArgumentOrObjectField = (info, argumentOrObjectField) => {
21
+ const value = argumentOrObjectField.value
22
+ if (isVariable(value)) {
23
+ argumentOrObjectField.value = substituteVariable(info, value)
24
+ } else if (isListValue(value)) {
25
+ traverseListValue(info, value)
26
+ } else if (isObjectValue(value)) {
27
+ traverseObjectValue(info, value)
28
+ }
29
+ }
30
+
31
+ const traverseSelectionSet = (info, selectionSet) => {
32
+ selectionSet.selections = fragmentSpreadSelections(info, selectionSet.selections)
33
+ selectionSet.selections = removeMetaFieldsFromSelections(selectionSet.selections)
34
+ selectionSet.selections.map(field => traverseField(info, field))
35
+ }
36
+
37
+ const traverseField = (info, field) => {
38
+ if (field.selectionSet) {
39
+ traverseSelectionSet(info, field.selectionSet)
40
+ }
41
+
42
+ field.arguments.map(arg => traverseArgumentOrObjectField(info, arg))
43
+ }
44
+
45
+ const traverseFieldNodes = (info, fieldNodes) => fieldNodes.map(fieldNode => traverseField(info, fieldNode))
46
+
47
+ module.exports = info => {
48
+ const deepClonedFieldNodes = JSON.parse(JSON.stringify(info.fieldNodes))
49
+ traverseFieldNodes(info, deepClonedFieldNodes)
50
+ return deepClonedFieldNodes
51
+ }
@@ -0,0 +1,11 @@
1
+ const { isFragmentSpread } = require('../utils')
2
+
3
+ const getFragmentDefinitionForFragmentSpread = (info, fragmentSpread) => info.fragments[fragmentSpread.name.value]
4
+
5
+ const substituteFragment = (info, fragmentSpread) =>
6
+ getFragmentDefinitionForFragmentSpread(info, fragmentSpread).selectionSet.selections
7
+
8
+ const fragmentSpreadSelections = (info, selections) =>
9
+ selections.flatMap(selection => (isFragmentSpread(selection) ? substituteFragment(info, selection) : [selection]))
10
+
11
+ module.exports = fragmentSpreadSelections
@@ -0,0 +1,39 @@
1
+ const { AST_NODE_KIND } = require('../../../constants/graphql')
2
+
3
+ const valueToGenericScalarValue = value => ({
4
+ kind: 'GenericScalarValue',
5
+ value
6
+ })
7
+
8
+ const keyToName = key => ({
9
+ kind: AST_NODE_KIND.Name,
10
+ value: key
11
+ })
12
+
13
+ const keyValueToObjectField = (k, v) => ({
14
+ kind: AST_NODE_KIND.ObjectField,
15
+ name: keyToName(k),
16
+ value: variableToValue(v)
17
+ })
18
+
19
+ const objectToObjectValue = object => ({
20
+ kind: AST_NODE_KIND.ObjectValue,
21
+ fields: Object.entries(object).map(([k, v]) => keyValueToObjectField(k, v))
22
+ })
23
+
24
+ const arrayToListValue = array => ({
25
+ kind: AST_NODE_KIND.ListValue,
26
+ values: array.map(a => objectToObjectValue(a))
27
+ })
28
+
29
+ const variableToValue = variable => {
30
+ if (Array.isArray(variable)) {
31
+ return arrayToListValue(variable)
32
+ } else if (typeof variable === 'object') {
33
+ return objectToObjectValue(variable)
34
+ } else {
35
+ return valueToGenericScalarValue(variable)
36
+ }
37
+ }
38
+
39
+ module.exports = variableValue => variableToValue(variableValue)
@@ -0,0 +1,3 @@
1
+ const enrichAST = require('./enrich')
2
+
3
+ module.exports = { enrichAST }
@@ -0,0 +1,4 @@
1
+ const removeMetaFieldsFromSelections = selections =>
2
+ selections.filter(selection => selection.name.value !== '__typename')
3
+
4
+ module.exports = removeMetaFieldsFromSelections
@@ -0,0 +1,7 @@
1
+ const objectToAST = require('./fromObject')
2
+
3
+ const getVariableValueForVariable = (info, variable) => info.variableValues[variable.name.value]
4
+
5
+ const substituteVariable = (info, variable) => objectToAST(getVariableValueForVariable(info, variable))
6
+
7
+ module.exports = substituteVariable
@@ -0,0 +1,42 @@
1
+ const { getArgumentByName } = require('./utils')
2
+ const astToWhere = require('./where')
3
+ const astToOrderBy = require('./orderBy')
4
+ const astToLimit = require('./limit')
5
+ const { ARGUMENT } = require('../../../constants/adapter')
6
+
7
+ const astToColumns = selections => {
8
+ let columns = []
9
+
10
+ for (const selection of selections) {
11
+ const column = { ref: [selection.name.value] }
12
+ if (selection.alias) {
13
+ column.as = selection.alias.value
14
+ }
15
+
16
+ if (selection.selectionSet && selection.selectionSet.selections) {
17
+ column.expand = astToColumns(selection.selectionSet.selections)
18
+ }
19
+
20
+ const filter = getArgumentByName(selection.arguments, ARGUMENT.FILTER)
21
+ if (filter) {
22
+ column.where = astToWhere(filter).xpr
23
+ }
24
+
25
+ const orderBy = getArgumentByName(selection.arguments, ARGUMENT.ORDER_BY)
26
+ if (orderBy) {
27
+ column.orderBy = astToOrderBy(orderBy)
28
+ }
29
+
30
+ const top = getArgumentByName(selection.arguments, ARGUMENT.TOP)
31
+ const skip = getArgumentByName(selection.arguments, ARGUMENT.SKIP)
32
+ if (top) {
33
+ column.limit = astToLimit(top, skip)
34
+ }
35
+
36
+ columns.push(column)
37
+ }
38
+
39
+ return columns
40
+ }
41
+
42
+ module.exports = astToColumns
@@ -0,0 +1,31 @@
1
+ const { isListValue, isObjectValue } = require('../utils')
2
+
3
+ const parseObjectField = objectField => {
4
+ const value = objectField.value
5
+ if (isListValue(value)) {
6
+ return parseListValue(value)
7
+ } else if (isObjectValue(value)) {
8
+ return parseObjectValue(value)
9
+ } else {
10
+ return value.value
11
+ }
12
+ }
13
+
14
+ const parseObjectValue = objectValue =>
15
+ objectValue.fields.reduce((entry, objectField) => {
16
+ entry[objectField.name.value] = parseObjectField(objectField)
17
+ return entry
18
+ }, {})
19
+
20
+ const parseListValue = listValue => listValue.values.map(value => parseObjectValue(value))
21
+
22
+ const astToEntries = inputArg => {
23
+ const value = inputArg.value
24
+ if (isListValue(value)) {
25
+ return parseListValue(value)
26
+ } else if (isObjectValue(value)) {
27
+ return parseObjectValue(value)
28
+ }
29
+ }
30
+
31
+ module.exports = astToEntries
@@ -0,0 +1,8 @@
1
+ const { getArgumentByName } = require('./utils')
2
+ const astToColumns = require('./columns')
3
+ const astToWhere = require('./where')
4
+ const astToOrderBy = require('./orderBy')
5
+ const astToLimit = require('./limit')
6
+ const astToEntries = require('./entries')
7
+
8
+ module.exports = { getArgumentByName, astToColumns, astToWhere, astToOrderBy, astToLimit, astToEntries }
@@ -0,0 +1,6 @@
1
+ const astToLimit = (topArg, skipArg) => ({
2
+ rows: { val: topArg.value.value },
3
+ offset: { val: (skipArg && skipArg.value.value) || 0 }
4
+ })
5
+
6
+ module.exports = astToLimit
@@ -0,0 +1,24 @@
1
+ const { isListValue, isObjectValue } = require('../utils')
2
+
3
+ const objectFieldToOrderBy = objectField => ({
4
+ ref: [objectField.name.value],
5
+ sort: objectField.value.value
6
+ })
7
+
8
+ const parseObjectValue = objectValue => {
9
+ // OrderBy objects are supposed to contain only a single field
10
+ return objectFieldToOrderBy(objectValue.fields[0])
11
+ }
12
+
13
+ const parseListValue = listValue => listValue.values.map(value => parseObjectValue(value))
14
+
15
+ const astToOrderBy = orderByArg => {
16
+ const value = orderByArg.value
17
+ if (isListValue(value)) {
18
+ return parseListValue(value)
19
+ } else if (isObjectValue(value)) {
20
+ return [parseObjectValue(value)]
21
+ }
22
+ }
23
+
24
+ module.exports = astToOrderBy
@@ -0,0 +1,3 @@
1
+ const getArgumentByName = (args, name) => args.find(arg => arg.name.value === name)
2
+
3
+ module.exports = { getArgumentByName }
@@ -0,0 +1,70 @@
1
+ const { STRING_MATCH_OPERATOR } = require('../../../constants/adapter')
2
+ const { GQL_TO_CDS_QL_OPERATOR, GQL_TO_CDS_STRING_MATCH_OPERATOR } = require('../../../constants/cds')
3
+ const { isListValue, isObjectValue } = require('../utils')
4
+
5
+ const stringMatchOperatorToLikeString = (operator, string) => {
6
+ switch (operator) {
7
+ case STRING_MATCH_OPERATOR.STARTSWITH:
8
+ return `${string}%`
9
+ case STRING_MATCH_OPERATOR.ENDSWITH:
10
+ return `%${string}`
11
+ case STRING_MATCH_OPERATOR.CONTAINS:
12
+ return `%${string}%`
13
+ }
14
+ }
15
+
16
+ const arrayInsertBetweenFlat = (array, element) => {
17
+ return [...array].flatMap((e, index) => (index < array.length - 1 ? [e, element] : [e])).flat()
18
+ }
19
+
20
+ const joinedXprFrom_xprs = (_xprs, operator) => {
21
+ return { xpr: arrayInsertBetweenFlat(_xprs, operator) }
22
+ }
23
+
24
+ const gqlOperatorToCdsOperator = gqlOperator =>
25
+ GQL_TO_CDS_QL_OPERATOR[gqlOperator] || GQL_TO_CDS_STRING_MATCH_OPERATOR[gqlOperator]
26
+
27
+ const gqlValueToCdsValue = (cdsOperator, gqlOperator, gqlValue) =>
28
+ cdsOperator === 'like' ? stringMatchOperatorToLikeString(gqlOperator, gqlValue) : gqlValue
29
+
30
+ const objectFieldTo_xpr = (objectField, columnName) => {
31
+ const gqlOperator = objectField.name.value
32
+ const cdsOperator = gqlOperatorToCdsOperator(gqlOperator)
33
+ const gqlValue = objectField.value.value
34
+ const cdsValue = gqlValueToCdsValue(cdsOperator, gqlOperator, gqlValue)
35
+
36
+ return [{ ref: [columnName] }, cdsOperator, { val: cdsValue }]
37
+ }
38
+
39
+ const parseObjectField = (objectField, columnName) => {
40
+ const value = objectField.value
41
+ const name = objectField.name.value
42
+ if (isListValue(value)) {
43
+ return parseListValue(value, name)
44
+ } else if (isObjectValue(value)) {
45
+ return parseObjectValue(value, name)
46
+ } else {
47
+ return objectFieldTo_xpr(objectField, columnName)
48
+ }
49
+ }
50
+
51
+ const parseObjectValue = (objectValue, columnName) => {
52
+ const _xprs = objectValue.fields.map(field => parseObjectField(field, columnName))
53
+ return _xprs.length === 1 ? _xprs[0] : joinedXprFrom_xprs(_xprs, 'and')
54
+ }
55
+
56
+ const parseListValue = (listValue, columnName) => {
57
+ const _xprs = listValue.values.map(value => parseObjectValue(value, columnName))
58
+ return _xprs.length === 1 ? _xprs[0] : joinedXprFrom_xprs(_xprs, 'or')
59
+ }
60
+
61
+ const astToWhere = filterArg => {
62
+ const value = filterArg.value
63
+ if (isListValue(value)) {
64
+ return parseListValue(value)
65
+ } else if (isObjectValue(value)) {
66
+ return parseObjectValue(value)
67
+ }
68
+ }
69
+
70
+ module.exports = astToWhere
@@ -0,0 +1,8 @@
1
+ const { AST_NODE_KIND } = require('../../../constants/graphql')
2
+
3
+ const isFragmentSpread = value => value.kind === AST_NODE_KIND.FragmentSpread
4
+ const isListValue = value => value.kind === AST_NODE_KIND.ListValue
5
+ const isObjectValue = value => value.kind === AST_NODE_KIND.ObjectValue
6
+ const isVariable = value => value.kind === AST_NODE_KIND.Variable
7
+
8
+ module.exports = { isFragmentSpread, isListValue, isObjectValue, isVariable }
@@ -0,0 +1,13 @@
1
+ const cds = require('../../_runtime/cds')
2
+ const LOG = cds.log('graphql')
3
+
4
+ const { cdsName } = require('../utils')
5
+ const { executeRead } = require('./crud')
6
+
7
+ module.exports = async (service, gqlName, field) => {
8
+ const entityFQN = `${service.name}.${cdsName(gqlName)}`
9
+
10
+ LOG.log(`query on ${entityFQN}`)
11
+
12
+ return await executeRead(service, entityFQN, field)
13
+ }
@@ -0,0 +1,34 @@
1
+ const { gqlName } = require('../utils')
2
+ const resolveQuery = require('./query')
3
+ const resolveMutation = require('./mutation')
4
+ const { enrichAST } = require('./parse/ast')
5
+
6
+ const wrapResolver = (service, resolver) => (root, args, context, info) => {
7
+ const response = {}
8
+
9
+ const enrichedFieldNodes = enrichAST(info)
10
+
11
+ for (const fieldNode of enrichedFieldNodes) {
12
+ for (const field of fieldNode.selectionSet.selections) {
13
+ const gqlName = field.name.value
14
+ const responseKey = field.alias ? field.alias.value : gqlName
15
+
16
+ response[responseKey] = resolver(service, gqlName, field)
17
+ }
18
+ }
19
+
20
+ return response
21
+ }
22
+
23
+ module.exports = services => {
24
+ const Query = {}
25
+ const Mutation = {}
26
+
27
+ for (const service of services) {
28
+ const gqlServiceName = gqlName(service.name)
29
+ Query[gqlServiceName] = wrapResolver(service, resolveQuery)
30
+ Mutation[gqlServiceName] = wrapResolver(service, resolveMutation)
31
+ }
32
+
33
+ return { Query, Mutation }
34
+ }
@@ -0,0 +1,18 @@
1
+ const { typeDefMapToMutationStringArray } = require('./mutation')
2
+ const { typeDefMapToQueryStringArray } = require('./query')
3
+ const { servicesToTypeDefMap } = require('./typeDefMap')
4
+
5
+ const typeDefMapToSchemaString = typeDefs => {
6
+ let schema = []
7
+
8
+ schema.push(...typeDefMapToQueryStringArray(typeDefs))
9
+
10
+ schema.push(...typeDefMapToMutationStringArray(typeDefs))
11
+
12
+ return schema.join('\n')
13
+ }
14
+
15
+ module.exports = services => {
16
+ const typeDefMap = servicesToTypeDefMap(services)
17
+ return typeDefMapToSchemaString(typeDefMap)
18
+ }
@@ -0,0 +1,5 @@
1
+ const generate = require('./generate')
2
+
3
+ module.exports = {
4
+ generate
5
+ }
@@ -0,0 +1,76 @@
1
+ const { ARGUMENT, MUTATION_PREFIX, INPUT_OBJECT_SUFFIX } = require('../constants/adapter')
2
+ const { GQL_ROOT, GQL_KEYWORDS, SCALAR_TYPES } = require('../constants/graphql')
3
+ const { generateSchemaObject, typeToArgumentType, isTypeScalar, appendSuffixToType } = require('./utils')
4
+
5
+ const typeDefMapToMutationStringArray = typeDefs => {
6
+ let schema = []
7
+
8
+ // Create root mutation (containing services)
9
+ schema.push(
10
+ ...generateSchemaObject(
11
+ GQL_KEYWORDS.TYPE,
12
+ GQL_ROOT.MUTATION,
13
+ Object.keys(typeDefs).reduce((fields, serviceName) => {
14
+ fields[serviceName] = `${serviceName}_${INPUT_OBJECT_SUFFIX.INPUT}`
15
+ return fields
16
+ }, {})
17
+ )
18
+ )
19
+
20
+ // Create types for mutations from services (each containing entities)
21
+ for (const [serviceName, entities] of Object.entries(typeDefs)) {
22
+ const fields = {}
23
+ for (const entityName of Object.keys(entities)) {
24
+ const entityNameWithoutServicePrefix = entityName.replace(`${serviceName}_`, '')
25
+
26
+ const createMutationName = `${MUTATION_PREFIX.CREATE}_${entityNameWithoutServicePrefix}`
27
+ const createMutationArgs = `(${ARGUMENT.INPUT}: [${entityName}_${INPUT_OBJECT_SUFFIX.CREATE}]!)`
28
+ fields[`${createMutationName}${createMutationArgs}`] = `[${entityName}]`
29
+
30
+ const updateMutationName = `${MUTATION_PREFIX.UPDATE}_${entityNameWithoutServicePrefix}`
31
+ const updateMutationArgs = `(${ARGUMENT.FILTER}: ${typeToArgumentType(entityName, ARGUMENT.FILTER)}, ${
32
+ ARGUMENT.INPUT
33
+ }: ${entityName}_${INPUT_OBJECT_SUFFIX.UPDATE}!)`
34
+ fields[`${updateMutationName}${updateMutationArgs}`] = `[${entityName}]`
35
+
36
+ const deleteMutationName = `${MUTATION_PREFIX.DELETE}_${entityNameWithoutServicePrefix}`
37
+ const deleteMutationArgs = `(${ARGUMENT.FILTER}: ${typeToArgumentType(entityName, ARGUMENT.FILTER)})`
38
+ fields[`${deleteMutationName}${deleteMutationArgs}`] = SCALAR_TYPES.INT
39
+ }
40
+ schema.push(...generateSchemaObject(GQL_KEYWORDS.TYPE, `${serviceName}_${INPUT_OBJECT_SUFFIX.INPUT}`, fields))
41
+ }
42
+
43
+ // Create input types for create mutations from entities (each containing elements)
44
+ for (const entities of Object.values(typeDefs)) {
45
+ for (const [entityName, elements] of Object.entries(entities)) {
46
+ const fields = {}
47
+ for (const [elementName, elementType] of Object.entries(elements)) {
48
+ if (isTypeScalar(elementType)) {
49
+ fields[elementName] = elementType
50
+ } else {
51
+ fields[elementName] = appendSuffixToType(elementType, INPUT_OBJECT_SUFFIX.CREATE)
52
+ }
53
+ }
54
+ schema.push(...generateSchemaObject(GQL_KEYWORDS.INPUT, `${entityName}_${INPUT_OBJECT_SUFFIX.CREATE}`, fields))
55
+ }
56
+ }
57
+
58
+ // Create input types for update mutations from entities (each containing elements)
59
+ for (const entities of Object.values(typeDefs)) {
60
+ for (const [entityName, elements] of Object.entries(entities)) {
61
+ const fields = {}
62
+ for (const [elementName, elementType] of Object.entries(elements)) {
63
+ if (isTypeScalar(elementType)) {
64
+ fields[elementName] = elementType
65
+ } else {
66
+ fields[elementName] = appendSuffixToType(elementType, INPUT_OBJECT_SUFFIX.UPDATE)
67
+ }
68
+ }
69
+ schema.push(...generateSchemaObject(GQL_KEYWORDS.INPUT, `${entityName}_${INPUT_OBJECT_SUFFIX.UPDATE}`, fields))
70
+ }
71
+ }
72
+
73
+ return schema
74
+ }
75
+
76
+ module.exports = { typeDefMapToMutationStringArray }