@sap/cds 5.5.5 → 5.6.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.
Files changed (207) hide show
  1. package/CHANGELOG.md +139 -1
  2. package/apis/services.d.ts +31 -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/to/sql.js +22 -2
  12. package/lib/connect/bindings.js +2 -1
  13. package/lib/core/reflect.js +4 -1
  14. package/lib/env/index.js +180 -42
  15. package/lib/env/requires.js +16 -1
  16. package/lib/i18n/localize.js +33 -5
  17. package/lib/index.js +3 -3
  18. package/lib/log/format/kibana.js +6 -2
  19. package/lib/ql/Query.js +1 -0
  20. package/lib/ql/SELECT.js +15 -8
  21. package/lib/ql/Whereable.js +5 -0
  22. package/lib/req/context.js +13 -5
  23. package/lib/serve/Service-dispatch.js +8 -1
  24. package/lib/utils/axios.js +7 -0
  25. package/lib/utils/data.js +1 -1
  26. package/lib/utils/tests.js +1 -1
  27. package/libx/_runtime/audit/Service.js +18 -18
  28. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  29. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  30. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  31. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +12 -4
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
  46. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  51. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  53. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  54. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  55. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  56. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  57. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  58. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  59. package/libx/_runtime/cds-services/util/assert.js +29 -13
  60. package/libx/_runtime/cds.js +2 -1
  61. package/libx/_runtime/common/aspects/Association.js +72 -0
  62. package/libx/_runtime/common/aspects/any.js +8 -45
  63. package/libx/_runtime/common/aspects/entity.js +0 -1
  64. package/libx/_runtime/common/aspects/relation.js +40 -0
  65. package/libx/_runtime/common/aspects/utils.js +73 -1
  66. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  67. package/libx/_runtime/common/composition/data.js +3 -2
  68. package/libx/_runtime/common/composition/delete.js +3 -1
  69. package/libx/_runtime/common/composition/tree.js +23 -18
  70. package/libx/_runtime/common/composition/update.js +9 -1
  71. package/libx/_runtime/common/composition/utils.js +34 -8
  72. package/libx/_runtime/common/error/frontend.js +6 -1
  73. package/libx/_runtime/common/generic/auth.js +5 -9
  74. package/libx/_runtime/common/generic/crud.js +2 -2
  75. package/libx/_runtime/common/generic/etag.js +11 -8
  76. package/libx/_runtime/common/generic/input.js +3 -3
  77. package/libx/_runtime/common/generic/paging.js +9 -5
  78. package/libx/_runtime/common/generic/put.js +3 -2
  79. package/libx/_runtime/common/generic/sorting.js +3 -3
  80. package/libx/_runtime/common/generic/temporal.js +3 -3
  81. package/libx/_runtime/common/utils/cqn.js +20 -1
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  83. package/libx/_runtime/common/utils/csn.js +50 -52
  84. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  85. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  86. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  87. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  88. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  89. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  90. package/libx/_runtime/common/utils/resolveView.js +7 -5
  91. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  92. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  93. package/libx/_runtime/common/utils/template.js +54 -46
  94. package/libx/_runtime/db/Service.js +9 -2
  95. package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
  96. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  97. package/libx/_runtime/db/generic/arrayed.js +13 -28
  98. package/libx/_runtime/db/generic/create.js +1 -0
  99. package/libx/_runtime/db/generic/input.js +7 -11
  100. package/libx/_runtime/db/generic/integrity.js +2 -2
  101. package/libx/_runtime/db/generic/rewrite.js +2 -5
  102. package/libx/_runtime/db/generic/update.js +1 -0
  103. package/libx/_runtime/db/query/read.js +9 -4
  104. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  105. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  106. package/libx/_runtime/db/utils/columns.js +14 -43
  107. package/libx/_runtime/fiori/generic/activate.js +3 -2
  108. package/libx/_runtime/fiori/generic/before.js +2 -2
  109. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  110. package/libx/_runtime/fiori/generic/delete.js +3 -2
  111. package/libx/_runtime/fiori/generic/edit.js +3 -3
  112. package/libx/_runtime/fiori/generic/new.js +2 -2
  113. package/libx/_runtime/fiori/generic/patch.js +2 -2
  114. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  115. package/libx/_runtime/fiori/generic/read.js +45 -63
  116. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  117. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  118. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  119. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  120. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  121. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  122. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  123. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  124. package/libx/_runtime/fiori/utils/handler.js +3 -13
  125. package/libx/_runtime/fiori/utils/where.js +6 -1
  126. package/libx/_runtime/hana/pool.js +12 -11
  127. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  128. package/libx/_runtime/hana/searchToContains.js +3 -3
  129. package/libx/_runtime/index.js +5 -2
  130. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  131. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  132. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  133. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  134. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  135. package/libx/_runtime/messaging/message-queuing.js +18 -0
  136. package/libx/_runtime/remote/Service.js +20 -4
  137. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  138. package/libx/_runtime/remote/utils/client.js +117 -23
  139. package/libx/_runtime/sqlite/Service.js +2 -2
  140. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  141. package/libx/gql/GraphQLAdapter.js +33 -0
  142. package/libx/gql/constants/adapter.js +69 -0
  143. package/libx/gql/constants/cds.js +18 -0
  144. package/libx/gql/constants/graphql.js +33 -0
  145. package/libx/gql/resolvers/crud/create.js +15 -0
  146. package/libx/gql/resolvers/crud/delete.js +24 -0
  147. package/libx/gql/resolvers/crud/index.js +6 -0
  148. package/libx/gql/resolvers/crud/read.js +25 -0
  149. package/libx/gql/resolvers/crud/update.js +31 -0
  150. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  151. package/libx/gql/resolvers/field.js +5 -0
  152. package/libx/gql/resolvers/index.js +7 -0
  153. package/libx/gql/resolvers/mutation.js +23 -0
  154. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  155. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  156. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  157. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  158. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  159. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  160. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  161. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  162. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  164. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  165. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  166. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  167. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  168. package/libx/gql/resolvers/query.js +13 -0
  169. package/libx/gql/resolvers/root.js +34 -0
  170. package/libx/gql/schema/generate.js +18 -0
  171. package/libx/gql/schema/index.js +5 -0
  172. package/libx/gql/schema/mutation.js +76 -0
  173. package/libx/gql/schema/query.js +108 -0
  174. package/libx/gql/schema/typeDefMap.js +45 -0
  175. package/libx/gql/schema/utils/index.js +54 -0
  176. package/libx/gql/utils/index.js +12 -0
  177. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  178. package/libx/odata/index.js +80 -0
  179. package/libx/odata/odata2cqn/afterburner.js +170 -0
  180. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  181. package/libx/odata/odata2cqn/index.js +3 -0
  182. package/libx/odata/odata2cqn/parser.js +1 -0
  183. package/libx/odata/utils/index.js +64 -0
  184. package/libx/rest/RestAdapter.js +101 -0
  185. package/libx/rest/RestRequest.js +30 -0
  186. package/libx/rest/index.js +3 -0
  187. package/libx/rest/middleware/auth.js +22 -0
  188. package/libx/rest/middleware/content.js +15 -0
  189. package/libx/rest/middleware/create.js +40 -0
  190. package/libx/rest/middleware/delete.js +20 -0
  191. package/libx/rest/middleware/error.js +56 -0
  192. package/libx/rest/middleware/operation.js +39 -0
  193. package/libx/rest/middleware/parse.js +90 -0
  194. package/libx/rest/middleware/read.js +29 -0
  195. package/libx/rest/middleware/update.js +42 -0
  196. package/libx/rest/utils/data.js +65 -0
  197. package/package.json +4 -1
  198. package/server.js +29 -7
  199. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  200. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  201. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  202. package/libx/_runtime/common/utils/backlinks.js +0 -83
  203. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  204. package/libx/_runtime/odata/index.js +0 -55
  205. package/libx/_runtime/odata/odata2cqn.js +0 -1
  206. package/libx/_runtime/odata/readToCqn.js +0 -129
  207. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -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 generateQuery = require('../cqn2odata')
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 _executeHttpRequest = (...args) => {
18
- if (!_cloudSdkCore) _cloudSdkCore = require('@sap-cloud-sdk/core')
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 opts = args[1]
21
- if (PPPD[opts.method] && cds.env.features.fetch_csrf) {
22
- if (args.length === 3) args[2].fetchCsrfToken = true
23
- else args.push({ fetchCsrfToken: true })
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
- return _cloudSdkCore.executeHttpRequest(...args)
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
- const run = async (reqOptions, { destination, jwt, kind, resolvedTarget, suppressRemoteResponseBody }) => {
179
- const dest = typeof destination === 'string' ? { destinationName: destination, jwt } : destination
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(dest, reqOptions)
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, reqOptions, suppressRemoteResponseBody)
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
- reqOptions.headers.accept.includes('text/html') ||
202
- reqOptions.headers.accept.includes('text/*') ||
203
- reqOptions.headers.accept.includes('*/*')
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, reqOptions, suppressRemoteResponseBody)
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, reqOptions.headers)
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, reqOptions.headers)
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 = generateQuery(query, kind, model)
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 + ';\n')
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
- const _foreignKeyPropagations = foreignKeyPropagations(element)
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
@@ -0,0 +1,69 @@
1
+ const { SCALAR_TYPES } = require('./graphql')
2
+
3
+ // TODO own scalar types
4
+ const CDS_TO_GRAPHQL_TYPES = {
5
+ 'cds.Binary': SCALAR_TYPES.STRING,
6
+ 'cds.Boolean': SCALAR_TYPES.BOOLEAN,
7
+ 'cds.Date': SCALAR_TYPES.STRING,
8
+ 'cds.DateTime': SCALAR_TYPES.STRING,
9
+ 'cds.Decimal': SCALAR_TYPES.FLOAT,
10
+ 'cds.DecimalFloat': SCALAR_TYPES.FLOAT,
11
+ 'cds.Double': SCALAR_TYPES.STRING,
12
+ 'cds.Integer': SCALAR_TYPES.INT,
13
+ 'cds.Integer64': SCALAR_TYPES.STRING,
14
+ 'cds.LargeBinary': SCALAR_TYPES.STRING,
15
+ 'cds.LargeString': SCALAR_TYPES.STRING,
16
+ 'cds.String': SCALAR_TYPES.STRING,
17
+ 'cds.Time': SCALAR_TYPES.STRING,
18
+ 'cds.Timestamp': SCALAR_TYPES.STRING,
19
+ 'cds.UUID': SCALAR_TYPES.ID
20
+ }
21
+
22
+ const ARGUMENT = {
23
+ INPUT: 'input',
24
+ FILTER: 'filter',
25
+ ORDER_BY: 'orderBy',
26
+ TOP: 'top',
27
+ SKIP: 'skip'
28
+ }
29
+
30
+ const HELPER_TYPES = {
31
+ SORT_DIRECTION: 'SortDirection'
32
+ }
33
+
34
+ const EQUALITY_OPERATOR = {
35
+ EQ: 'eq',
36
+ NE: 'ne',
37
+ GT: 'gt',
38
+ GE: 'ge',
39
+ LE: 'le',
40
+ LT: 'lt'
41
+ }
42
+
43
+ const STRING_MATCH_OPERATOR = {
44
+ STARTSWITH: 'startswith',
45
+ ENDSWITH: 'endswith',
46
+ CONTAINS: 'contains'
47
+ }
48
+
49
+ const MUTATION_PREFIX = {
50
+ CREATE: 'create',
51
+ UPDATE: 'update',
52
+ DELETE: 'delete'
53
+ }
54
+
55
+ const INPUT_OBJECT_SUFFIX = {
56
+ INPUT: 'input',
57
+ CREATE: 'C',
58
+ UPDATE: 'U'
59
+ }
60
+
61
+ module.exports = {
62
+ CDS_TO_GRAPHQL_TYPES,
63
+ ARGUMENT,
64
+ HELPER_TYPES,
65
+ EQUALITY_OPERATOR,
66
+ STRING_MATCH_OPERATOR,
67
+ MUTATION_PREFIX,
68
+ INPUT_OBJECT_SUFFIX
69
+ }
@@ -0,0 +1,18 @@
1
+ const { EQUALITY_OPERATOR, STRING_MATCH_OPERATOR } = require('./adapter')
2
+
3
+ const GQL_TO_CDS_QL_OPERATOR = {
4
+ [EQUALITY_OPERATOR.EQ]: '=',
5
+ [EQUALITY_OPERATOR.NE]: '<>',
6
+ [EQUALITY_OPERATOR.GT]: '>',
7
+ [EQUALITY_OPERATOR.GE]: '>=',
8
+ [EQUALITY_OPERATOR.LE]: '<=',
9
+ [EQUALITY_OPERATOR.LT]: '<'
10
+ }
11
+
12
+ const GQL_TO_CDS_STRING_MATCH_OPERATOR = {
13
+ [STRING_MATCH_OPERATOR.STARTSWITH]: 'like',
14
+ [STRING_MATCH_OPERATOR.ENDSWITH]: 'like',
15
+ [STRING_MATCH_OPERATOR.CONTAINS]: 'like'
16
+ }
17
+
18
+ module.exports = { GQL_TO_CDS_QL_OPERATOR, GQL_TO_CDS_STRING_MATCH_OPERATOR }
@@ -0,0 +1,33 @@
1
+ const GQL_ROOT = {
2
+ QUERY: 'Query',
3
+ MUTATION: 'Mutation'
4
+ }
5
+
6
+ const GQL_KEYWORDS = {
7
+ TYPE: 'type',
8
+ ENUM: 'enum',
9
+ INPUT: 'input'
10
+ }
11
+
12
+ const SCALAR_TYPES = {
13
+ INT: 'Int',
14
+ FLOAT: 'Float',
15
+ STRING: 'String',
16
+ BOOLEAN: 'Boolean',
17
+ ID: 'ID'
18
+ }
19
+
20
+ // String starts with [ and ends with ]
21
+ // Capture group captures in between brackets
22
+ const GQL_LIST_REGEX = /^\[(.+)\]$/
23
+
24
+ const AST_NODE_KIND = {
25
+ ListValue: 'ListValue',
26
+ ObjectValue: 'ObjectValue',
27
+ ObjectField: 'ObjectField',
28
+ FragmentSpread: 'FragmentSpread',
29
+ Variable: 'Variable',
30
+ Name: 'Name'
31
+ }
32
+
33
+ module.exports = { GQL_ROOT, GQL_KEYWORDS, SCALAR_TYPES, GQL_LIST_REGEX, AST_NODE_KIND }
@@ -0,0 +1,15 @@
1
+ const { ARGUMENT } = require('../../constants/adapter')
2
+ const { getArgumentByName, astToEntries } = require('../parse/ast2cqn')
3
+ const { entriesStructureToEntityStructure } = require('./utils')
4
+
5
+ module.exports = async (service, entityFQN, selection) => {
6
+ let query = service.create(entityFQN)
7
+
8
+ const input = getArgumentByName(selection.arguments, ARGUMENT.INPUT)
9
+ const entries = entriesStructureToEntityStructure(service, entityFQN, astToEntries(input))
10
+ query.entries(entries)
11
+
12
+ const result = await service.tx(tx => tx.run(query))
13
+
14
+ return Array.isArray(result) ? result : [result]
15
+ }
@@ -0,0 +1,24 @@
1
+ const { ARGUMENT } = require('../../constants/adapter')
2
+ const { getArgumentByName, astToWhere } = require('../parse/ast2cqn')
3
+
4
+ module.exports = async (service, entityFQN, selection) => {
5
+ let query = service.delete(entityFQN)
6
+
7
+ const filter = getArgumentByName(selection.arguments, ARGUMENT.FILTER)
8
+ if (filter) {
9
+ query.where(astToWhere(filter))
10
+ }
11
+
12
+ let result
13
+ try {
14
+ result = await service.tx(tx => tx.run(query))
15
+ } catch (e) {
16
+ if (e.code === 404) {
17
+ result = 0
18
+ } else {
19
+ throw e
20
+ }
21
+ }
22
+
23
+ return result
24
+ }
@@ -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