@sap/cds 5.5.2 → 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.
Files changed (228) hide show
  1. package/CHANGELOG.md +150 -17
  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 +31 -4
  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/lib/utils/tests.js +5 -3
  40. package/libx/_runtime/audit/Service.js +18 -18
  41. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  42. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  43. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  44. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +6 -0
  45. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  46. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  49. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -1
  50. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  51. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  52. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  53. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  54. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  55. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  56. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  57. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  58. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  59. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  60. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  64. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  67. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  68. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +9 -2
  69. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +14 -19
  70. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  71. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  72. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  73. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  74. package/libx/_runtime/cds-services/util/assert.js +29 -13
  75. package/libx/_runtime/cds.js +2 -1
  76. package/libx/_runtime/common/aspects/Association.js +72 -0
  77. package/libx/_runtime/common/aspects/any.js +8 -45
  78. package/libx/_runtime/common/aspects/entity.js +0 -1
  79. package/libx/_runtime/common/aspects/relation.js +40 -0
  80. package/libx/_runtime/common/aspects/utils.js +73 -1
  81. package/libx/_runtime/common/auth/strategies/utils/uaa.js +1 -12
  82. package/libx/_runtime/common/composition/data.js +3 -2
  83. package/libx/_runtime/common/composition/delete.js +3 -1
  84. package/libx/_runtime/common/composition/tree.js +23 -18
  85. package/libx/_runtime/common/composition/utils.js +34 -8
  86. package/libx/_runtime/common/error/frontend.js +6 -1
  87. package/libx/_runtime/common/generic/auth.js +15 -13
  88. package/libx/_runtime/common/generic/crud.js +2 -2
  89. package/libx/_runtime/common/generic/etag.js +11 -8
  90. package/libx/_runtime/common/generic/input.js +3 -3
  91. package/libx/_runtime/common/generic/paging.js +9 -5
  92. package/libx/_runtime/common/generic/put.js +3 -2
  93. package/libx/_runtime/common/generic/sorting.js +3 -3
  94. package/libx/_runtime/common/generic/temporal.js +3 -3
  95. package/libx/_runtime/common/toggles/alpha.js +1 -1
  96. package/libx/_runtime/common/utils/cqn.js +20 -1
  97. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  98. package/libx/_runtime/common/utils/csn.js +50 -52
  99. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  100. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  101. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  102. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  103. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  104. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  105. package/libx/_runtime/common/utils/resolveView.js +20 -10
  106. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  107. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  108. package/libx/_runtime/common/utils/template.js +54 -46
  109. package/libx/_runtime/db/Service.js +9 -2
  110. package/libx/_runtime/db/expand/expandCQNToJoin.js +11 -25
  111. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  112. package/libx/_runtime/db/generic/create.js +1 -0
  113. package/libx/_runtime/db/generic/input.js +7 -11
  114. package/libx/_runtime/db/generic/integrity.js +2 -2
  115. package/libx/_runtime/db/generic/rewrite.js +2 -5
  116. package/libx/_runtime/db/generic/update.js +1 -0
  117. package/libx/_runtime/db/query/read.js +10 -5
  118. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
  119. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  120. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  121. package/libx/_runtime/db/utils/columns.js +14 -43
  122. package/libx/_runtime/db/utils/deep.js +5 -7
  123. package/libx/_runtime/fiori/generic/activate.js +3 -2
  124. package/libx/_runtime/fiori/generic/before.js +2 -2
  125. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  126. package/libx/_runtime/fiori/generic/delete.js +3 -2
  127. package/libx/_runtime/fiori/generic/edit.js +2 -2
  128. package/libx/_runtime/fiori/generic/new.js +2 -2
  129. package/libx/_runtime/fiori/generic/patch.js +2 -2
  130. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  131. package/libx/_runtime/fiori/generic/read.js +17 -63
  132. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  133. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  134. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  135. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  136. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  137. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  138. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  139. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  140. package/libx/_runtime/fiori/utils/handler.js +3 -13
  141. package/libx/_runtime/fiori/utils/where.js +6 -1
  142. package/libx/_runtime/hana/Service.js +5 -2
  143. package/libx/_runtime/hana/execute.js +1 -1
  144. package/libx/_runtime/hana/pool.js +12 -11
  145. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  146. package/libx/_runtime/hana/searchToContains.js +3 -3
  147. package/libx/_runtime/index.js +5 -2
  148. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  149. package/libx/_runtime/messaging/common-utils/AMQPClient.js +9 -1
  150. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  151. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  152. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  153. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  154. package/libx/_runtime/messaging/message-queuing.js +18 -0
  155. package/libx/_runtime/remote/Service.js +14 -2
  156. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  157. package/libx/_runtime/remote/utils/client.js +117 -23
  158. package/libx/_runtime/sqlite/Service.js +4 -3
  159. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  160. package/libx/_runtime/sqlite/execute.js +1 -1
  161. package/libx/gql/GraphQLAdapter.js +33 -0
  162. package/libx/gql/constants/adapter.js +69 -0
  163. package/libx/gql/constants/cds.js +18 -0
  164. package/libx/gql/constants/graphql.js +33 -0
  165. package/libx/gql/resolvers/crud/create.js +15 -0
  166. package/libx/gql/resolvers/crud/delete.js +24 -0
  167. package/libx/gql/resolvers/crud/index.js +6 -0
  168. package/libx/gql/resolvers/crud/read.js +25 -0
  169. package/libx/gql/resolvers/crud/update.js +31 -0
  170. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  171. package/libx/gql/resolvers/field.js +5 -0
  172. package/libx/gql/resolvers/index.js +7 -0
  173. package/libx/gql/resolvers/mutation.js +23 -0
  174. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  175. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  176. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  177. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  178. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  179. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  180. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  181. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  182. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  183. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  184. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  185. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  186. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  187. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  188. package/libx/gql/resolvers/query.js +13 -0
  189. package/libx/gql/resolvers/root.js +34 -0
  190. package/libx/gql/schema/generate.js +18 -0
  191. package/libx/gql/schema/index.js +5 -0
  192. package/libx/gql/schema/mutation.js +76 -0
  193. package/libx/gql/schema/query.js +108 -0
  194. package/libx/gql/schema/typeDefMap.js +45 -0
  195. package/libx/gql/schema/utils/index.js +54 -0
  196. package/libx/gql/utils/index.js +12 -0
  197. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  198. package/libx/odata/index.js +80 -0
  199. package/libx/odata/odata2cqn/afterburner.js +170 -0
  200. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  201. package/libx/odata/odata2cqn/index.js +3 -0
  202. package/libx/odata/odata2cqn/parser.js +1 -0
  203. package/libx/odata/utils/index.js +64 -0
  204. package/libx/rest/RestAdapter.js +101 -0
  205. package/libx/rest/RestRequest.js +30 -0
  206. package/libx/rest/index.js +3 -0
  207. package/libx/rest/middleware/auth.js +22 -0
  208. package/libx/rest/middleware/content.js +15 -0
  209. package/libx/rest/middleware/create.js +40 -0
  210. package/libx/rest/middleware/delete.js +20 -0
  211. package/libx/rest/middleware/error.js +56 -0
  212. package/libx/rest/middleware/operation.js +39 -0
  213. package/libx/rest/middleware/parse.js +90 -0
  214. package/libx/rest/middleware/read.js +29 -0
  215. package/libx/rest/middleware/update.js +42 -0
  216. package/libx/rest/utils/data.js +65 -0
  217. package/package.json +4 -1
  218. package/server.js +42 -29
  219. package/lib/req/cls.js +0 -39
  220. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  221. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  222. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  223. package/libx/_runtime/common/utils/backlinks.js +0 -83
  224. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  225. package/libx/_runtime/odata/index.js +0 -55
  226. package/libx/_runtime/odata/odata2cqn.js +0 -1
  227. package/libx/_runtime/odata/readToCqn.js +0 -129
  228. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -0,0 +1,42 @@
1
+ const cds = require('../../_runtime/cds')
2
+ const { INSERT } = cds.ql
3
+
4
+ const RestRequest = require('../RestRequest')
5
+
6
+ const UPSERT_ALLOWED = !(cds.env.runtime && cds.env.runtime.allow_upsert === false)
7
+
8
+ const { deepCopyObject } = require('../../_runtime/common/utils/copy')
9
+
10
+ module.exports = async (_req, _res, next) => {
11
+ let { _srv: srv, _query: query, _target, _data, _params } = _req
12
+
13
+ let result,
14
+ status = 200
15
+
16
+ // unfortunately, express doesn't catch async errors -> try catch needed
17
+ try {
18
+ // if upsert it allowed, we need to catch 404 and retry with create
19
+ try {
20
+ // add the data (as copy, if upsert allowed)
21
+ query.with(UPSERT_ALLOWED ? deepCopyObject(_data) : _data)
22
+ // REVISIT: if PUT, req.method should be PUT -> Crud2Http maps UPSERT to PUT
23
+ result = await srv.dispatch(new RestRequest({ query, _target, method: _req.method }))
24
+ if (_params) Object.assign(result, _params[_params.length - 1])
25
+ } catch (e) {
26
+ if ((e.code === 404 || e.status === 404 || e.statusCode === 404) && UPSERT_ALLOWED) {
27
+ query = INSERT.into(query.UPDATE.entity).entries(
28
+ _params ? Object.assign(_data, _params[_params.length - 1]) : _data
29
+ )
30
+ result = await srv.dispatch(new RestRequest({ query, _target }))
31
+ status = 201
32
+ } else {
33
+ throw e
34
+ }
35
+ }
36
+ } catch (e) {
37
+ return next(e)
38
+ }
39
+
40
+ _req._result = { result, status }
41
+ next()
42
+ }
@@ -0,0 +1,65 @@
1
+ const cds = require('../../_runtime/cds')
2
+
3
+ const { deepCopyObject } = require('../../_runtime/common/utils/copy')
4
+ const { checkKeys, checkStatic } = require('../../_runtime/cds-services/util/assert')
5
+ const { MULTIPLE_ERRORS } = require('../../_runtime/common/error/constants')
6
+
7
+ // this can be reused for flattening data on db layer, if necessary
8
+ // const _getFlattenedCopy = (data, key, entity) => {
9
+ // const d = {}
10
+ // const prefix = key + '_'
11
+ // // TODO: ignore or preserve unknown?
12
+ // const matches = Object.keys(entity.elements)
13
+ // .filter(ele => ele.startsWith(prefix))
14
+ // .map(ele => ele.replace(prefix, ''))
15
+ // if (matches.length) {
16
+ // const current = data[key]
17
+ // for (const k of matches) if (current[k] !== undefined) d[prefix + k] = current[k]
18
+ // const nested = Object.keys(current).filter(k => current[k] && typeof current[k] === 'object')
19
+ // for (const k of nested) Object.assign(d, _getFlattenedCopy({ [prefix + k]: current[k] }, prefix + k, entity))
20
+ // }
21
+ // return d
22
+ // }
23
+
24
+ const _getDeepCopy = (data, definition, model, validations, skipKeys) => {
25
+ skipKeys || (definition.keys && validations.push(...checkKeys(definition, data)))
26
+ validations.push(...checkStatic(definition, data, true))
27
+
28
+ const d = {}
29
+ for (const k in data) {
30
+ const element = (definition.elements && definition.elements[k]) || (definition.params && definition.params[k])
31
+ if (!element) {
32
+ const { additional_properties } = cds.env.features
33
+ if (additional_properties === 'ignore' || !additional_properties) {
34
+ // ignore input (the default)
35
+ } else if (additional_properties === 'error') {
36
+ validations.push({ message: `Unknown property "${k}"`, code: 400 })
37
+ } else {
38
+ d[k] = data[k] && typeof data[k] === 'object' ? deepCopyObject(data[k]) : data[k]
39
+ }
40
+ } else if (element.isAssociation) {
41
+ d[k] = Array.isArray(data[k])
42
+ ? data[k].map(d => _getDeepCopy(d, model.definitions[element.target], model, validations))
43
+ : _getDeepCopy(data[k], model.definitions[element.target], model, validations)
44
+ } else if (element._isStructured) {
45
+ d[k] = _getDeepCopy(data[k], element, model, validations)
46
+ } else {
47
+ d[k] = data[k]
48
+ }
49
+ }
50
+ return d
51
+ }
52
+
53
+ const getDeepCopy = (data, definition, model, skipKeys) => {
54
+ const validations = []
55
+ const copy = Array.isArray(data)
56
+ ? data.map(d => _getDeepCopy(d, definition, model, validations, skipKeys))
57
+ : _getDeepCopy(data, definition, model, validations, skipKeys)
58
+ if (!validations.length) return [undefined, copy]
59
+ if (validations.length === 1) return [validations[0]]
60
+ return [Object.assign(new Error(MULTIPLE_ERRORS), { details: validations })]
61
+ }
62
+
63
+ module.exports = {
64
+ getDeepCopy
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "5.5.2",
3
+ "version": "5.6.0",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -40,6 +40,9 @@
40
40
  }
41
41
  },
42
42
  "lint-staged": {
43
+ "libx/odata/odata2cqn/grammar.pegjs": [
44
+ "npm run pegjs:odata2cqn && git add libx/odata/odata2cqn/parser.js"
45
+ ],
43
46
  "{libx,tests/_runtime}/**/*.js": [
44
47
  "npx prettier --write"
45
48
  ]
package/server.js CHANGED
@@ -40,16 +40,23 @@ module.exports = async function cds_server (options, o = { ...options, __proto__
40
40
  if (o.logger) app.use (o.logger) //> basic request logging
41
41
  if (o.toggler) app.use (o.toggler) //> feature toggler
42
42
 
43
+ // give uiflex a chance to plug into everything
44
+ if (cds.requires.extensibility) await require('./libx/_runtime/fiori/uiflex')() // REVISIT: later this should be a ext umbrella service
45
+
43
46
  // load specified models or all in project
44
47
  const csn = await cds.load (o.from||'*', {mocked:o.mocked})
45
- cds.model = o.from = cds.linked (cds.compile.for.odata(csn))
48
+ const m = cds.linked(csn).minified()
49
+ cds.model = o.from = cds.linked (cds.compile.for.odata(m))
46
50
 
47
51
  // connect to essential framework services if required
48
- const _init = o.in_memory && (db => cds.deploy(csn).to(db,o))
52
+ const _init = o.in_memory && (db => cds.deploy(m).to(db,o))
49
53
  if (cds.requires.db) cds.db = await cds.connect.to ('db') .then (_init)
50
54
  if (cds.requires.messaging) await cds.connect.to ('messaging')
51
55
  if (cds.requires.multitenancy) await cds.mtx.in (app)
52
56
 
57
+ // serve graphql
58
+ if (cds.env.features.graphql) serve_graphql(app)
59
+
53
60
  // serve all services declared in models
54
61
  await cds.serve (o.service,o).in (app)
55
62
  await cds.emit ('served', cds.services) //> hook for listeners
@@ -66,18 +73,9 @@ module.exports = async function cds_server (options, o = { ...options, __proto__
66
73
  //
67
74
  const defaults = {
68
75
 
69
- // CORS
70
- get cors() {
71
- return (req, res, next) => {
72
- const { origin } = req.headers
73
- if (origin) res.set('access-control-allow-origin', origin)
74
- if (origin && req.method === 'OPTIONS')
75
- return res.set('access-control-allow-methods', 'GET,HEAD,PUT,PATCH,POST,DELETE').end()
76
- next()
77
- }
78
- },
76
+ cors, correlate,
79
77
 
80
- get static() { return cds.env.folders.app }, //> defaults to ./app
78
+ get static() { return cds.env.folders.app }, //> defaults to ./app
81
79
 
82
80
  // default generic index.html page
83
81
  get index() {
@@ -91,22 +89,6 @@ const defaults = {
91
89
  return express.static (favicon, {maxAge:'14d'})
92
90
  },
93
91
 
94
- // REVISIT: we need to align that with lib/req/context.js#L68 and remove redundancies
95
- // ensure correlation id and set intermediate or augment cds.context until tx opened in protocol adpater
96
- get correlate() {
97
- return (req,_,next) => {
98
- if (!cds.context) cds.context = {}
99
- const id = cds.context.id
100
- || req.headers['x-correlation-id'] || req.headers['x-correlationid']
101
- || req.headers['x-request-id'] || req.headers['x-vcap-request-id']
102
- || cds.utils.uuid()
103
- cds.context.id = req.headers['x-correlation-id'] = id
104
- if (!cds.context._) cds.context._ = {}
105
- if (!cds.context._.req) cds.context._.req = req
106
- next()
107
- }
108
- },
109
-
110
92
  // default request logger
111
93
  get logger() {
112
94
  const LOG = cds.log(), DEBUG = cds.debug('server')
@@ -138,5 +120,36 @@ const _app_serve = function (endpoint) { return {
138
120
  }}
139
121
 
140
122
 
123
+ // register graphql router on served event
124
+ function serve_graphql (app) {
125
+ cds.on('served', services => {
126
+ const GraphQLAdapter = require('./libx/gql/GraphQLAdapter')
127
+ app.use(new GraphQLAdapter(services, { graphiql: true }))
128
+ cds.log()("serving GraphQL endpoint for all services { at: '/graphql' }")
129
+ })
130
+ }
131
+
132
+
133
+ function cors (req, res, next) {
134
+ const { origin } = req.headers
135
+ if (origin) res.set('access-control-allow-origin', origin)
136
+ if (origin && req.method === 'OPTIONS')
137
+ return res.set('access-control-allow-methods', 'GET,HEAD,PUT,PATCH,POST,DELETE').end()
138
+ next()
139
+ }
140
+
141
+ function correlate (req,_,next) {
142
+ if (!cds.context) cds.context = {}
143
+ const id = cds.context.id
144
+ || req.headers['x-correlation-id'] || req.headers['x-correlationid']
145
+ || req.headers['x-request-id'] || req.headers['x-vcap-request-id']
146
+ || cds.utils.uuid()
147
+ cds.context.id = req.headers['x-correlation-id'] = id
148
+ if (!cds.context._) cds.context._ = {}
149
+ if (!cds.context._.req) cds.context._.req = req
150
+ next()
151
+ }
152
+
153
+
141
154
  // -------------------------------------------------------------------------
142
155
  if (!module.parent) module.exports ({from:process.argv[2]})
package/lib/req/cls.js DELETED
@@ -1,39 +0,0 @@
1
- /**
2
- * Continuation Local Storage support for req.context -> used through cds.context.
3
- */
4
- module.exports = (cds,v) => {
5
-
6
- if (cds.env.features.cls) {
7
-
8
- const { executionAsyncResource:current, createHook } = module.require ('async_hooks')
9
- const _context = Symbol('cds.context')
10
-
11
- createHook ({ init(id,t,tid, res) {
12
- const cr = current(); if (!cr) return
13
- const ctx = cr[_context]
14
- if (ctx) res[_context] = ctx
15
- }}).enable()
16
-
17
- Reflect.defineProperty (cds,'context',{ enumerable:1,
18
- set(v) {
19
- const cr = current()
20
- if (cr) cr[_context] = v && v.context || v
21
- },
22
- get() {
23
- const cr = current()
24
- return cr ? cr[_context] : undefined
25
- },
26
- })
27
-
28
- } else {
29
-
30
- Reflect.defineProperty (cds,'context',{ enumerable:1,
31
- get:()=> undefined,
32
- set:()=> {},
33
- })
34
-
35
- }
36
-
37
- return v ? cds.context = v : cds.context
38
-
39
- }
@@ -1,53 +0,0 @@
1
- /* istanbul ignore file */
2
-
3
- // REVISIT: probably needed for personal data handling
4
-
5
- const _getDiff = (data, status) => {
6
- const diffs = []
7
-
8
- for (const key in data) {
9
- if (!key.startsWith('*@odata.')) {
10
- diffs.push({ name: key, [status]: data[key] })
11
- }
12
- }
13
-
14
- return diffs
15
- }
16
-
17
- const _getDiffForUpdate = (oldData, newData) => {
18
- const diffs = []
19
-
20
- // Diff added if field in newData is not present or has a different value in oldData.
21
- // No diff added if a field is not present in newData.
22
- for (const key in newData) {
23
- if (!key.startsWith('*@odata.') && oldData[key] !== newData[key]) {
24
- diffs.push({ name: key, old: oldData[key], new: newData[key] })
25
- }
26
- }
27
-
28
- return diffs
29
- }
30
-
31
- /**
32
- * Compare to objects on first level.
33
- * Returns a list of changes like {name: 'key', old: 'val', new: 'val'}
34
- *
35
- * @param {string} event
36
- * @param {object} oldData
37
- * @param {object} newData
38
- * @returns {Array}
39
- */
40
- const diff = (event, oldData = {}, newData = {}) => {
41
- switch (event) {
42
- case 'CREATE':
43
- return _getDiff(newData, 'new')
44
- case 'UPDATE':
45
- return _getDiffForUpdate(oldData, newData)
46
- case 'DELETE':
47
- return _getDiff(oldData, 'old')
48
- default:
49
- return []
50
- }
51
- }
52
-
53
- module.exports = diff
@@ -1,247 +0,0 @@
1
- /* istanbul ignore file */
2
-
3
- const { getAuditLogNotWrittenError } = require('./errors')
4
-
5
- const _getCredentials = auditLog => {
6
- return require('./xsenv')('auditlog', auditLog)
7
- }
8
-
9
- const _keysToString = keys => {
10
- const strings = []
11
- for (const key in keys) {
12
- strings.push(`${key}: ${keys[key]}`)
13
- }
14
-
15
- return strings.join(', ')
16
- }
17
-
18
- const _logAttributes = (log, attributes) => {
19
- for (const attribute in attributes) {
20
- log = log.attribute({ name: attribute })
21
- }
22
- }
23
-
24
- const _logTenant = (log, tenant) => {
25
- log = tenant ? log.tenant(tenant) : log
26
- }
27
-
28
- /**
29
- * Logs securityMessage that user is not authorized.
30
- *
31
- * @param auditLogger - the audit logger
32
- * @param credentials - credentials for audit log instance
33
- * @param logger - logger object
34
- * @param {object} args - user if provided via basic auth, if not => ip address
35
- * @param args.user
36
- * @param args.ip ip address of the user
37
- * @private
38
- */
39
- const _logUnauthorized = (auditLogger, credentials, logger, { user, ip }) => {
40
- auditLogger.v2(credentials, function (err, auditLog) {
41
- if (err) {
42
- // TODO: Decide for a more meaningful error message
43
- return logger.error(`Error occurred while writing audit log: ${err}`)
44
- }
45
-
46
- let log = auditLog.securityMessage('Unsuccessful login attempt').by(user)
47
- log = ip ? log.externalIP(ip) : log
48
-
49
- log.log(function (err) {
50
- if (err) {
51
- return logger.error(`Error occurred while writing audit log: ${err}`)
52
- }
53
- })
54
- })
55
- }
56
-
57
- /**
58
- * Logs securityMessage that user does not have sufficient permissions.
59
- *
60
- * @param auditLogger - the audit logger
61
- * @param credentials - credentials for audit log instance
62
- * @param logger - logger object
63
- * @param {object} args
64
- * @param args.user - user that has not sufficient privileges
65
- * @param args.ip - ip address of the user
66
- * @param args.tenant - tenant of the user
67
- * @private
68
- */
69
- const _logMissingPermissions = (auditLogger, credentials, logger, { user, ip, tenant }) => {
70
- auditLogger.v2(credentials, function (err, auditLog) {
71
- if (err) {
72
- return logger.error(`Error occurred while writing audit log: ${err}`)
73
- }
74
-
75
- let log = auditLog.securityMessage('User does not have required permissions').by(user)
76
- log = ip ? log.externalIP(ip) : log
77
- log = tenant ? log.tenant(tenant) : log
78
-
79
- log.log(function (err) {
80
- if (err) {
81
- return logger.error(`Error occurred while writing audit log: ${err}`)
82
- }
83
- })
84
- })
85
- }
86
-
87
- /**
88
- *
89
- * Logs data read access.
90
- *
91
- * @param auditLogger - the audit logger
92
- * @param credentials - credentials for audit log instance
93
- * @param auditLogData - audit log input like dataSubject, the changed sensitive attributes or the user and tenant
94
- * @returns {Promise} which rejects if audit log cannot be written
95
- * @private
96
- */
97
- const _logReadAccess = (auditLogger, credentials, auditLogData) => {
98
- return new Promise((resolve, reject) => {
99
- auditLogger.v2(credentials, function (err, auditLog) {
100
- if (err) {
101
- return reject(getAuditLogNotWrittenError(err, 'before commit', 'READ'))
102
- }
103
-
104
- const { dataSubject, attributes, auditObject, user, tenant } = auditLogData
105
-
106
- try {
107
- let log = auditLog.read({
108
- type: auditObject.type,
109
- id: { key: _keysToString(auditObject.keys) }
110
- })
111
-
112
- _logAttributes(log, attributes)
113
- log = log.dataSubject({
114
- type: dataSubject.type,
115
- id: dataSubject.keys,
116
- role: dataSubject.role
117
- })
118
-
119
- _logTenant(log, tenant)
120
- log = log.by(user)
121
-
122
- log.log(function (err) {
123
- if (err) {
124
- return reject(getAuditLogNotWrittenError(err, 'before commit', 'READ'))
125
- }
126
- resolve()
127
- })
128
- } catch (err) {
129
- return reject(getAuditLogNotWrittenError(err, 'before commit', 'READ'))
130
- }
131
- })
132
- })
133
- }
134
-
135
- /**
136
- *
137
- * Logs diff of a data manipulation event.
138
- *
139
- * @param auditLogger - the audit logger
140
- * @param credentials - credentials for audit log instance
141
- * @param {object} args
142
- * @param args.context - the context object
143
- * @param args.auditLogData - audit log input like dataSubject, the changed sensitive attributes or the user and tenant
144
- * @param args.phase - the phase the logDataChange handler was triggered in
145
- * @returns {Promise}
146
- * @private
147
- */
148
- const _logDataChange = (auditLogger, credentials, { context, auditLogData, phase }) => {
149
- const { dataSubject, attributes, auditObject, diff, user, tenant } = auditLogData
150
- return new Promise((resolve, reject) => {
151
- auditLogger.v2(credentials, function (err, auditLog) {
152
- if (err) {
153
- return reject(getAuditLogNotWrittenError(err, phase))
154
- }
155
-
156
- try {
157
- let log = auditLog.update({
158
- type: auditObject.type,
159
- id: { key: _keysToString(auditObject.keys) }
160
- })
161
-
162
- for (const difference of diff) {
163
- if (attributes[difference.name]) {
164
- log = log.attribute(Object.assign({ new: 'null', old: 'null' }, difference))
165
- }
166
- }
167
-
168
- log = log.dataSubject({
169
- type: dataSubject.type,
170
- id: dataSubject.keys,
171
- role: dataSubject.role
172
- })
173
-
174
- log = tenant ? log.tenant(tenant) : log
175
- log = log.by(user)
176
-
177
- log.logPrepare(function (err) {
178
- if (err) {
179
- return reject(getAuditLogNotWrittenError(err, phase))
180
- }
181
- context._.auditLogContinuation = log
182
- resolve()
183
- })
184
- } catch (err) {
185
- return reject(getAuditLogNotWrittenError(err, phase))
186
- }
187
- })
188
- })
189
- }
190
-
191
- /**
192
- * Initializes the audit log object.
193
- * If options.auditLog provided, it looks via xsenv for a configured audit log instance.
194
- * If not, logs the audit events to the logger object (if provided via options, if not to console).
195
- *
196
- * @param {object} auditLog - the service options
197
- * @param {object} logger - the logger object
198
- * @returns {object} - with convenience methods logUnauthorized and logMissingPermissions to write audit logs
199
- */
200
- const initialize = (auditLog, logger) => {
201
- // REVISIT: use xsenv directly and remove xsenv util
202
- let auditLogConfig = _getCredentials(auditLog)
203
-
204
- try {
205
- const auditLogger = require('@sap/audit-logging')
206
- if (!auditLogConfig) {
207
- auditLogConfig = { logToConsole: true }
208
- }
209
-
210
- return {
211
- logDataChange: info => {
212
- return _logDataChange(auditLogger, auditLogConfig, info)
213
- },
214
- logReadAccess: info => {
215
- return _logReadAccess(auditLogger, auditLogConfig, info)
216
- },
217
- logUnauthorized: info => {
218
- return _logUnauthorized(auditLogger, auditLogConfig, logger, info)
219
- },
220
- logMissingPermissions: info => {
221
- return _logMissingPermissions(auditLogger, auditLogConfig, logger, info)
222
- }
223
- }
224
- } catch (err) {
225
- if (auditLogConfig) {
226
- // this should crash the app if audit log was defined in VCAP and module could not be loaded
227
- setImmediate(() => {
228
- throw err
229
- })
230
- }
231
-
232
- return {
233
- logDataChange: info => {
234
- logger.log(info)
235
- return Promise.resolve()
236
- },
237
- logReadAccess: info => {
238
- logger.log(info)
239
- return Promise.resolve()
240
- },
241
- logUnauthorized: logger.warn,
242
- logMissingPermissions: logger.warn
243
- }
244
- }
245
- }
246
-
247
- module.exports = initialize
@@ -1,51 +0,0 @@
1
- /* istanbul ignore file */
2
-
3
- const _findByType = type => {
4
- try {
5
- return require('@sap/xsenv').getServices({
6
- [type]: {
7
- tag: type
8
- }
9
- })
10
- } catch (e) {}
11
- }
12
-
13
- const _structured = filter => {
14
- return Object.keys(filter).some(key => {
15
- return typeof filter[key] === 'object'
16
- })
17
- }
18
-
19
- const _filter = (serviceName, filter) => {
20
- if (_structured(filter)) {
21
- return filter
22
- }
23
-
24
- return { [serviceName]: filter }
25
- }
26
-
27
- const _getServices = (serviceName, filter) => {
28
- if (typeof filter === 'object') {
29
- return require('@sap/xsenv').getServices(_filter(serviceName, filter))
30
- }
31
-
32
- return _findByType(serviceName)
33
- }
34
-
35
- /**
36
- * Get credentials for service by name and/or filter object.
37
- *
38
- * @param {string} serviceName
39
- * @param {object} [filter]
40
- * @returns {*}
41
- * @private
42
- */
43
- const getCredentialsFromXsEnv = (serviceName, filter) => {
44
- const services = _getServices(serviceName, filter)
45
-
46
- if (services) {
47
- return services[Object.keys(services)[0]]
48
- }
49
- }
50
-
51
- module.exports = getCredentialsFromXsEnv