@sap/cds 5.8.4 → 5.9.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 (244) hide show
  1. package/CHANGELOG.md +174 -77
  2. package/app/fiori/preview.js +16 -11
  3. package/app/index.js +1 -1
  4. package/bin/build/buildTaskFactory.js +3 -3
  5. package/bin/build/buildTaskProviderFactory.js +1 -1
  6. package/bin/build/constants.js +1 -1
  7. package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
  8. package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
  9. package/bin/build/provider/buildTaskProviderInternal.js +8 -2
  10. package/bin/build/provider/hana/2migration.js +27 -24
  11. package/bin/build/provider/hana/index.js +17 -18
  12. package/bin/build/provider/hana/migrationtable.js +9 -10
  13. package/bin/build/provider/java-cf/index.js +4 -5
  14. package/bin/build/provider/node-cf/index.js +99 -6
  15. package/bin/cds.js +17 -18
  16. package/bin/deploy/to-hana/cfUtil.js +16 -19
  17. package/bin/deploy/to-hana/hana.js +7 -24
  18. package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
  19. package/bin/mtx/in-cds.js +2 -2
  20. package/bin/serve.js +10 -3
  21. package/bin/utils/modules.js +7 -0
  22. package/bin/version.js +56 -3
  23. package/lib/compile/cdsc.js +26 -3
  24. package/lib/compile/etc/_localized.js +36 -25
  25. package/lib/compile/etc/csv.js +8 -8
  26. package/lib/compile/for/drafts.js +9 -0
  27. package/lib/compile/for/java.js +16 -0
  28. package/lib/compile/for/nodejs.js +12 -0
  29. package/lib/compile/for/odata.js +1 -1
  30. package/lib/compile/index.js +3 -0
  31. package/lib/compile/minify.js +16 -2
  32. package/lib/compile/parse.js +2 -2
  33. package/lib/compile/resolve.js +35 -18
  34. package/lib/compile/to/json.js +3 -1
  35. package/lib/compile/to/sql.js +2 -2
  36. package/lib/compile/to/srvinfo.js +4 -2
  37. package/lib/connect/index.js +1 -1
  38. package/lib/core/entities.js +15 -14
  39. package/lib/core/index.js +39 -36
  40. package/lib/core/reflect.js +4 -2
  41. package/lib/deploy.js +114 -127
  42. package/lib/env/defaults.js +1 -0
  43. package/lib/env/index.js +165 -165
  44. package/lib/env/presets.js +1 -0
  45. package/lib/env/requires.js +120 -49
  46. package/lib/index.js +1 -0
  47. package/lib/log/format/kibana.js +2 -2
  48. package/lib/ql/SELECT.js +10 -0
  49. package/lib/ql/parse.js +1 -0
  50. package/lib/req/cds-context.js +4 -1
  51. package/lib/req/context.js +50 -56
  52. package/lib/req/event.js +1 -6
  53. package/lib/req/locale.js +6 -5
  54. package/lib/req/request.js +2 -0
  55. package/lib/req/user.js +7 -5
  56. package/lib/serve/Service-api.js +10 -7
  57. package/lib/serve/Service-dispatch.js +9 -11
  58. package/lib/serve/Service-methods.js +30 -41
  59. package/lib/serve/Transaction.js +10 -7
  60. package/lib/serve/adapters.js +7 -5
  61. package/lib/serve/index.js +24 -12
  62. package/lib/utils/data.js +1 -1
  63. package/lib/utils/index.js +27 -30
  64. package/lib/utils/resources/index.js +101 -0
  65. package/lib/utils/resources/tar.js +71 -0
  66. package/lib/utils/resources/utils.js +11 -0
  67. package/libx/_runtime/audit/Service.js +36 -39
  68. package/libx/_runtime/audit/generic/personal/access.js +3 -4
  69. package/libx/_runtime/audit/generic/personal/modification.js +3 -4
  70. package/libx/_runtime/audit/utils/v2.js +1 -2
  71. package/libx/_runtime/auth/index.js +126 -84
  72. package/libx/_runtime/auth/strategies/JWT.js +12 -19
  73. package/libx/_runtime/auth/strategies/dummy.js +1 -5
  74. package/libx/_runtime/auth/strategies/dwc.js +11 -9
  75. package/libx/_runtime/auth/strategies/mock.js +0 -4
  76. package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
  77. package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
  78. package/libx/_runtime/auth/utils.js +22 -1
  79. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
  80. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -2
  81. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  82. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
  84. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
  85. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  86. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
  87. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
  88. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
  89. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
  90. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
  91. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
  92. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
  93. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
  94. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
  95. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +50 -0
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
  99. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
  100. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
  101. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
  102. package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
  103. package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
  104. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
  105. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
  106. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
  107. package/libx/_runtime/cds-services/services/Service.js +40 -0
  108. package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
  109. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
  110. package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
  111. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
  112. package/libx/_runtime/cds-services/services/utils/restrictions.js +78 -0
  113. package/libx/_runtime/cds-services/util/assert.js +20 -14
  114. package/libx/_runtime/cds.js +9 -1
  115. package/libx/_runtime/common/aspects/any.js +5 -0
  116. package/libx/_runtime/common/aspects/entity.js +25 -7
  117. package/libx/_runtime/common/aspects/utils.js +2 -2
  118. package/libx/_runtime/common/composition/data.js +6 -0
  119. package/libx/_runtime/common/composition/insert.js +3 -2
  120. package/libx/_runtime/common/composition/tree.js +4 -10
  121. package/libx/_runtime/common/composition/update.js +4 -4
  122. package/libx/_runtime/common/constants/draft.js +29 -26
  123. package/libx/_runtime/common/error/constants.js +2 -2
  124. package/libx/_runtime/common/error/frontend.js +7 -15
  125. package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
  126. package/libx/_runtime/common/generic/auth/constants.js +20 -0
  127. package/libx/_runtime/common/generic/auth/expand.js +54 -0
  128. package/libx/_runtime/common/generic/auth/index.js +32 -0
  129. package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
  130. package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
  131. package/libx/_runtime/common/generic/auth/requires.js +34 -0
  132. package/libx/_runtime/common/generic/auth/restrict.js +296 -0
  133. package/libx/_runtime/common/generic/auth/utils.js +213 -0
  134. package/libx/_runtime/common/generic/crud.js +8 -6
  135. package/libx/_runtime/common/generic/etag.js +1 -1
  136. package/libx/_runtime/common/generic/input.js +35 -35
  137. package/libx/_runtime/common/generic/sorting.js +2 -3
  138. package/libx/_runtime/common/generic/temporal.js +2 -2
  139. package/libx/_runtime/common/i18n/messages.properties +1 -1
  140. package/libx/_runtime/common/toggles/handler.js +21 -0
  141. package/libx/_runtime/common/utils/copy.js +10 -1
  142. package/libx/_runtime/common/utils/cqn2cqn4sql.js +100 -29
  143. package/libx/_runtime/common/utils/csn.js +63 -1
  144. package/libx/_runtime/common/utils/dollar.js +10 -1
  145. package/libx/_runtime/common/utils/draft.js +46 -7
  146. package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
  147. package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
  148. package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
  149. package/libx/_runtime/common/utils/generateOnCond.js +4 -1
  150. package/libx/_runtime/common/utils/quotingStyles.js +2 -0
  151. package/libx/_runtime/common/utils/resolveStructured.js +25 -9
  152. package/libx/_runtime/common/utils/resolveView.js +4 -1
  153. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
  154. package/libx/_runtime/common/utils/structured.js +33 -37
  155. package/libx/_runtime/common/utils/template.js +17 -8
  156. package/libx/_runtime/common/utils/templateProcessor.js +28 -28
  157. package/libx/_runtime/db/data-conversion/post-processing.js +118 -417
  158. package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
  159. package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
  160. package/libx/_runtime/db/generic/index.js +1 -3
  161. package/libx/_runtime/db/generic/input.js +5 -10
  162. package/libx/_runtime/db/generic/rewrite.js +5 -2
  163. package/libx/_runtime/db/generic/structured.js +2 -2
  164. package/libx/_runtime/db/query/delete.js +2 -2
  165. package/libx/_runtime/db/query/insert.js +1 -1
  166. package/libx/_runtime/db/query/update.js +9 -14
  167. package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
  168. package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
  169. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
  170. package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
  171. package/libx/_runtime/db/utils/columns.js +3 -3
  172. package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
  173. package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
  174. package/libx/_runtime/extensibility/mps/index.js +5 -0
  175. package/libx/_runtime/extensibility/mps/service.js +111 -0
  176. package/libx/_runtime/extensibility/mps/tar.js +42 -0
  177. package/libx/_runtime/extensibility/mps/utils.js +11 -0
  178. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
  179. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
  180. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
  181. package/libx/_runtime/extensibility/uiflex/index.js +54 -0
  182. package/libx/_runtime/extensibility/uiflex/service.js +276 -0
  183. package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
  184. package/libx/_runtime/fiori/generic/activate.js +2 -2
  185. package/libx/_runtime/fiori/generic/before.js +4 -4
  186. package/libx/_runtime/fiori/generic/new.js +3 -3
  187. package/libx/_runtime/fiori/generic/patch.js +1 -1
  188. package/libx/_runtime/fiori/generic/read.js +58 -66
  189. package/libx/_runtime/fiori/generic/readOverDraft.js +71 -16
  190. package/libx/_runtime/fiori/utils/handler.js +6 -13
  191. package/libx/_runtime/fiori/utils/where.js +6 -5
  192. package/libx/_runtime/hana/Service.js +4 -10
  193. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
  194. package/libx/_runtime/hana/driver.js +2 -2
  195. package/libx/_runtime/hana/execute.js +27 -74
  196. package/libx/_runtime/hana/pool.js +1 -1
  197. package/libx/_runtime/hana/streaming.js +2 -1
  198. package/libx/_runtime/index.js +6 -6
  199. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
  200. package/libx/_runtime/messaging/Outbox.js +2 -2
  201. package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
  202. package/libx/_runtime/messaging/common-utils/connections.js +5 -7
  203. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
  204. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
  205. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
  206. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
  207. package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
  208. package/libx/_runtime/messaging/file-based.js +5 -5
  209. package/libx/_runtime/messaging/message-queuing.js +14 -12
  210. package/libx/_runtime/messaging/outbox/utils.js +18 -19
  211. package/libx/_runtime/messaging/redis-messaging.js +91 -0
  212. package/libx/_runtime/messaging/service.js +8 -6
  213. package/libx/_runtime/remote/Service.js +44 -8
  214. package/libx/_runtime/remote/utils/client.js +20 -13
  215. package/libx/_runtime/remote/utils/data.js +11 -11
  216. package/libx/_runtime/sqlite/Service.js +6 -9
  217. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
  218. package/libx/_runtime/types/api.js +10 -2
  219. package/libx/common/utils/ucsn.js +109 -0
  220. package/libx/gql/resolvers/crud/update.js +5 -0
  221. package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
  222. package/libx/gql/schema/typeDefMap.js +2 -2
  223. package/libx/odata/afterburner.js +110 -16
  224. package/libx/odata/grammar.pegjs +9 -1
  225. package/libx/odata/parseToCqn.js +39 -0
  226. package/libx/odata/parser.js +1 -1
  227. package/libx/rest/RestAdapter.js +9 -1
  228. package/libx/rest/middleware/input.js +54 -0
  229. package/libx/rest/middleware/operation.js +14 -1
  230. package/libx/rest/middleware/parse.js +11 -7
  231. package/package.json +1 -1
  232. package/server.js +34 -19
  233. package/srv/audit-log.cds +2 -2
  234. package/srv/flex.cds +8 -2
  235. package/srv/flex.js +1 -1
  236. package/srv/mps.cds +23 -0
  237. package/srv/mps.js +1 -0
  238. package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
  239. package/libx/_runtime/common/generic/auth.js +0 -874
  240. package/libx/_runtime/common/toggles/alpha.js +0 -43
  241. package/libx/_runtime/db/generic/arrayed.js +0 -33
  242. package/libx/_runtime/fiori/uiflex/index.js +0 -35
  243. package/libx/_runtime/fiori/uiflex/service.js +0 -150
  244. package/libx/rest/utils/data.js +0 -60
@@ -12,53 +12,42 @@ module.exports = (srv) => {
12
12
 
13
13
  const add_handler_for = (srv, def) => {
14
14
  const event = def.name.match(/\w*$/)[0]
15
+
16
+ // Use existing methods as handler implementations
15
17
  const method = srv[event]
16
18
  if (method) {
17
- // use existing method as handler
18
- if (method._handled || method.name in srv.__proto__) return
19
- srv.on (event, ({data}) => {
19
+ if (method._is_stub || method.name in srv.__proto__.__proto__) return
20
+ srv.on (event, function ({params,data}) {
20
21
  const args = []; if (def.parent) args.push (def.parent)
22
+ for (let p in params) args.push(params[p])
21
23
  for (let p in data) args.push(data[p])
22
- return method.apply (srv,args)
24
+ return method.apply (this,args)
23
25
  })
24
- method._handled = true
25
- } else {
26
- // add method to emit request
27
- const method = srv[event] = function (...args) {
28
- const [$] = args, target = this.entities [ $ && $.name ? $.name.match(/\w*$/)[0] : $ ]
29
- const data = {}
30
- if (target) { //> it's a bound action
31
- def = target.actions[event]; args.shift()
32
- // In case of bound actions, first argument(s) are expected to be the target's primary key,
33
- // which we need to fill in to named properties in data
34
- // object variant of keys, ensure keys are present in args[0] because of complex param
35
- if (typeof args[0] === 'object' && Object.keys(target.keys).every(k => k in args[0])) {
36
- const keys = args.shift()
37
- for (const p in target.keys) {
38
- data[p] = keys[p]
39
- }
40
- } else { // positional
41
- for (let p in target.keys) {
42
- data[p] = args.shift()
43
- }
44
- }
45
-
46
- }
47
-
48
- // object variant of params, ensure at least one param is present in args[0]
49
- // REVISIT: still not bullet proof, but parameters might be optional
50
- if (def.params) {
51
- if (args[0] !== null && typeof args[0] === 'object' && args.length === 1 && Object.keys(def.params).some(p => p in args[0])) {
52
- const params = args.shift()
53
- for (const p in def.params) data[p] = params[p]
54
- } else { // positional
55
- for (let p in def.params) data[p] = args.shift()
56
- }
57
- }
26
+ }
58
27
 
59
- // REVISIT: What happens with name clashes of keys and params?
60
- return this.send ({ event, target, data })
28
+ // Add stub methods to send request via typed API
29
+ const stub = srv[event] = function (...args) {
30
+ const req = { event, data:{} }, $ = args[0]
31
+ const target = this.entities [ $ && $.name ? $.name.match(/\w*$/)[0] : $ ]
32
+ if (target) { //> bound action/function?
33
+ req.target = target; args.shift() // first argument is the target entity name
34
+ req.params = [ args.shift() ] // second argument is the target's primary key
61
35
  }
62
- method._handled = true
36
+ const {params} = target ? target.actions[event] : def
37
+ if (params) req.data = _named(args,params) || _positional(args,params)
38
+ return this.send (req)
63
39
  }
40
+ stub._is_stub = true
64
41
  }
42
+
43
+
44
+ const _named = (args, declared) => {
45
+ if (args.length > 1) return
46
+ const a = args[0]
47
+ if (! a || typeof a !== 'object') return
48
+ if (Object.keys(a).every (k => k in declared)) return a
49
+ }
50
+
51
+ const _positional = (args, declared) => Object.keys(declared).reduce (
52
+ (data,k,i) => { data[k] = args[i]; return data }, {}
53
+ )
@@ -1,5 +1,7 @@
1
1
  const cds = require('../index'), { cds_tx_protection } = cds.env.features
2
2
  const EventContext = require('../req/context')
3
+ class RootContext extends EventContext {}
4
+
3
5
 
4
6
  /**
5
7
  * This is the implementation of the `srv.tx(req)` method. It constructs
@@ -28,12 +30,10 @@ module.exports = function tx (ctx,fn) { const srv = this
28
30
  // `ctx` is a plain context object or undefined
29
31
  if (ctx) { // REVISIT: This is for compatibility with AFC only
30
32
  if (ctx._txed_before) return NestedTransaction.for (srv, ctx._txed_before)
31
- else {
32
- Object.defineProperty(ctx, '_txed_before', { value: EventContext.for(ctx) }) // > must be non-enumerable
33
- return RootTransaction.for (srv, ctx._txed_before)
34
- }
33
+ else Object.defineProperty(ctx, '_txed_before', { value: RootContext.for(ctx) }) // > must be non-enumerable
34
+ return RootTransaction.for (srv, ctx._txed_before)
35
35
  }
36
- return RootTransaction.for (srv, EventContext.for(ctx))
36
+ return RootTransaction.for (srv, RootContext.for(ctx))
37
37
  }
38
38
 
39
39
 
@@ -174,8 +174,11 @@ const _begin = async function (req) {
174
174
  return this.ready = this.__proto__.dispatch.call (this,req)
175
175
  // Protection against unintended tx.run() after tx.commit/rollback()
176
176
  if (typeof this.ready === 'string' || !this.ready && this.context._done) {
177
- if (cds_tx_protection) throw Object.assign(new Error (`Transaction is ${this.ready || this.context._done}, no subsequent .run allowed, without prior .begin`), { code: 'TRANSACTION_CLOSED' })
178
- else this.ready = this.begin() // compatibiliy to former behavior, which allowed tx.run() after commit/rollback
177
+ if (!cds_tx_protection) this.ready = this.begin() // compatibiliy to former behavior, which allowed tx.run() after commit/rollback
178
+ else throw cds.error (
179
+ `Transaction is ${this.ready || this.context._done}, no subsequent .run allowed, without prior .begin`,
180
+ { code: 'TRANSACTION_CLOSED' }
181
+ )
179
182
  }
180
183
  else if (!this.ready) this.ready = this.begin()
181
184
  await this.ready
@@ -1,6 +1,7 @@
1
1
  const lib = require('../../libx/_runtime')
2
2
  const registry = {
3
- rest: lib.to.rest,
3
+ rest: lib.to.old_rest,
4
+ new_rest: lib.to.new_rest,
4
5
  odata: lib.to.odata_v4,
5
6
  odata_v2: lib.to.odata_v4,
6
7
  odata_v4: lib.to.odata_v4,
@@ -11,6 +12,7 @@ const registry = {
11
12
  class ProtocolAdapter {
12
13
 
13
14
  static at (protocol) {
15
+ if (protocol === 'rest' && global.cds.env.features.rest_new_adapter) protocol = 'new_rest'
14
16
  const factory = registry[protocol]; if (factory) return factory
15
17
  else throw new Error (`Service protocol ${protocol} is not supported`)
16
18
  }
@@ -30,12 +32,12 @@ class ProtocolAdapter {
30
32
  /**
31
33
  * Mounts the adapter to an express app.
32
34
  */
33
- in (app) {
34
- const srv = this.service
35
+ in (app, at) {
36
+ const srv = this.service, path = at || srv.path
35
37
  lib.perf (app)
36
38
  lib.auth (srv, app, srv.options)
37
- app.use (srv.path+'/webapp/', (_,res)=> res.sendStatus(404))
38
- app.use (srv.path, this)
39
+ app.use (path+'/webapp/', (_,res)=> res.sendStatus(404))
40
+ app.use (path, this)
39
41
  return srv
40
42
  }
41
43
 
@@ -6,8 +6,14 @@ const _ready = Symbol(), _pending = cds.services._pending || {}
6
6
  /** @param som - a service name or a model (name or csn) */
7
7
  function cds_serve (som, _options) { // NOSONAR
8
8
 
9
- if (Array.isArray(som) && som.length === 1) som = som[0]
10
- else if (typeof som === 'object' && !is_csn(som)) [som,_options] = [undefined,som]
9
+ if (som && typeof som === 'object' && !is_csn(som)) {
10
+ [som,_options] = [undefined,
11
+ som._is_service_instance ? { service:som, from:'*' } :
12
+ som._is_service_class ? { service:som, from:'*' } :
13
+ som
14
+ ]
15
+ }
16
+ else if (Array.isArray(som) && som.length === 1) som = som[0]
11
17
  const o = {..._options} // we must not modify inbound data
12
18
 
13
19
  // 1) Use fluent API to fill in remaining options...
@@ -40,6 +46,11 @@ function cds_serve (som, _options) { // NOSONAR
40
46
  // 4) Pass 1: Construct service provider instances...
41
47
  const all=[], provided = loaded.then (csn => { // NOSONAR
42
48
 
49
+ // Shortcut for directly passed service instances
50
+ if (o.service && o.service._is_service_instance) {
51
+ return all.push (o.service)
52
+ }
53
+
43
54
  // Shortcut for directly passed service classes
44
55
  if (o.service && o.service._is_service_class) {
45
56
  const Service = o.service, d = { name: o.service.name }
@@ -47,19 +58,19 @@ function cds_serve (som, _options) { // NOSONAR
47
58
  }
48
59
 
49
60
  // Get relevant service definitions from model...
50
- let {services} = csn = cds.linked (cds.compile.for.odata (csn))
51
- let specified = o.service
52
- if (specified && specified !== 'all') {
61
+ let {services} = csn = cds.compile.for.nodejs (csn)
62
+ const required = cds.requires
63
+ if (o.service && o.service !== 'all') {
53
64
  // skip services not chosen by o.service, if specified
54
- if (cds.requires[specified]) specified = cds.requires[specified].service || specified
55
- services = services.filter (s => s.name.endsWith (specified))
56
- if (!services.length) throw cds.error (`No such service: '${specified}'`)
65
+ const specified = o.service.split(/\s*,\s*/).map (s => required[s] && required[s].service || s )
66
+ services = services.filter (s => specified.some (n => s.name.endsWith(n)))
67
+ if (!services.length) throw cds.error (`No such service: '${o.service}'`)
57
68
  }
58
69
  services = services.filter (d => !(
59
70
  // skip all services marked to be ignored
60
71
  d['@cds.ignore'] || d['@cds.serve.ignore'] ||
61
72
  // skip external services, unless asked to mock them and unbound
62
- cds.requires[d.name] && (!o.mocked || cds.requires[d.name].credentials)
73
+ required[d.name] && (!o.mocked || required[d.name].credentials)
63
74
  ))
64
75
  if (services.length > 1 && o.at) {
65
76
  throw cds.error `You cannot specify 'path' for multiple services`
@@ -73,11 +84,12 @@ function cds_serve (som, _options) { // NOSONAR
73
84
  // Note: doing that in a second pass guarantees all own services are in
74
85
  // cds.services, so they'll be found when they cds.connect to each others.
75
86
  let ready = provided.then (()=> Promise.all (all.map (async srv => {
87
+ if (o.service && o.service._is_service_instance) return srv
76
88
  srv.init && await srv.prepend (srv.init)
77
89
  srv.options.impl && await srv.prepend (srv.options.impl)
78
90
  cds.services[srv.name] = cds.service.paths[srv.path] = srv
79
91
  cds.service.providers.push (srv)
80
- srv[_ready](srv)
92
+ if (srv[_ready]) srv[_ready](srv)
81
93
  return srv
82
94
  })))
83
95
 
@@ -93,7 +105,7 @@ function cds_serve (som, _options) { // NOSONAR
93
105
 
94
106
  for (let each of all) {
95
107
  each._edm = edms[each.name]
96
- ProtocolAdapter.serve(each).in(app)
108
+ ProtocolAdapter.serve(each,o.to).in(app)
97
109
  if (!o.silent) cds.emit ('serving',each)
98
110
  }
99
111
  })
@@ -105,7 +117,7 @@ function cds_serve (som, _options) { // NOSONAR
105
117
  if (all.length === 0) return resolve()
106
118
  let response={}
107
119
  for (let each of all) {
108
- response[each.name] = !each.definition ? each : ProtocolAdapter.serve(each).asRouter()
120
+ response[each.name] = !each.definition ? each : ProtocolAdapter.serve(each,o.to).asRouter()
109
121
  }
110
122
  if (all.length === 1 && all[0].name.endsWith (o.service)) {
111
123
  response = Object.assign (all[0], response)
package/lib/utils/data.js CHANGED
@@ -23,7 +23,7 @@ class DataUtil {
23
23
  async reset(db) {
24
24
  if (!db) db = await cds.connect.to('db')
25
25
  await this.delete(db)
26
- await cds.deploy(db.model).to(db, {ddl:false})
26
+ await cds.deploy.init(db)
27
27
  }
28
28
 
29
29
  autoReset(enabled) { this._autoReset = enabled; return this }
@@ -1,16 +1,17 @@
1
1
  const cwd = process.env._original_cwd || process.cwd()
2
2
  const path = require ('path'), { dirname, join, resolve, sep, relative } = path
3
- const fs = require ('fs'), { lstat, access, rm, rmdir, readdir, unlink } = fs.promises
4
- const rw = fs.constants.R_OK | fs.constants.W_OK
3
+ const fs = require('fs')
5
4
  const cds = global.cds
6
5
 
7
- module.exports = exports = {
8
- get uuid() { return $set (this, 'uuid', require('@sap/cds-foss').uuid) }
6
+ const all = module.exports = exports = { ...fs,
7
+ get inspect() { return $set (this, 'inspect', require('util').inspect) },
8
+ get uuid() { return $set (this, 'uuid', require('@sap/cds-foss').uuid) },
9
9
  }
10
10
 
11
- exports.local = (file) => '.' + sep + relative(cwd,file)
11
+ exports.fs = all
12
12
  exports.path = path
13
- exports.fs = fs
13
+
14
+ exports.local = (file) => '.' + sep + relative(cwd,file)
14
15
 
15
16
  exports.readdir = async function (x) {
16
17
  const d = resolve (cds.root,x)
@@ -54,45 +55,41 @@ exports.read = async function read (file, _encoding) {
54
55
  }
55
56
 
56
57
  exports.write = function write (file, data, o) {
57
- if (arguments.length === 1) return {to:f => write(f,file)}
58
- if (typeof data === 'object') data = JSON.stringify(data, null, ' '.repeat(o && o.spaces))
58
+ if (arguments.length === 1) return {to:(...path) => write(join(...path),file)}
59
+ if (typeof data === 'object' && !Buffer.isBuffer(data))
60
+ data = JSON.stringify(data, null, ' '.repeat(o && o.spaces))
59
61
  const f = resolve (cds.root,file)
60
- return mkdirp (dirname(f)).then (()=> fs.promises.writeFile (f,data))
62
+ return mkdirp (dirname(f)).then (()=> fs.promises.writeFile (f,data,o))
61
63
  }
62
64
 
63
- exports.mkdirp = async function mkdirp (dir) {
64
- const d = resolve (cds.root,dir)
65
- try { await access(d,rw) } catch(_) {
66
- await mkdirp (dirname(d))
67
- try { await fs.promises.mkdir(d) }
68
- catch(e) { if (e.code !== 'EEXIST' /*may still happen with parallel calls*/) throw e }
69
- }
65
+ exports.mkdirp = async function (...path) {
66
+ const d = resolve (cds.root,...path)
67
+ await fs.promises.mkdir (d,{recursive:true})
70
68
  return d
71
69
  }
72
70
 
73
- exports.rm = async function rm (x) {
74
- const y = resolve (cds.root,x)
75
- return unlink(y)
71
+ exports.rmdir = (...path) => {
72
+ const d = resolve (cds.root,...path)
73
+ return (fs.promises.rm || fs.promises.rmdir) (d, {recursive:true})
74
+ }
75
+
76
+ exports.rimraf = (...path) => {
77
+ const d = resolve (cds.root,...path)
78
+ return (fs.promises.rm || fs.promises.rmdir) (d, {recursive:true,force:true})
76
79
  }
77
80
 
78
- exports.rimraf = async function rimraf (dir) {
79
- if (rm) return rm (dir, {recursive:true,force:true}) //> added in Node 14
80
- const d = resolve(cds.root,dir)
81
- const entries = await readdir(d)
82
- await Promise.all (entries.map (async each => {
83
- const e = join (d,each)
84
- const ls = await lstat(e)
85
- return ls.isDirectory() ? rimraf(e) : unlink(e)
86
- }))
87
- return rmdir(d)
81
+ exports.rm = async function rm (x) {
82
+ const y = resolve (cds.root,x)
83
+ return (fs.promises.rm || fs.promises.unlink)(y)
88
84
  }
89
85
 
90
86
  exports.copy = async function copy (x,y) {
91
87
  const src = resolve (cds.root,x)
92
88
  const dst = resolve (cds.root,y)
89
+ if (fs.promises.cp) return fs.promises.cp (src,dst,{recursive:true})
93
90
  await mkdirp (dirname(dst))
94
91
  if (isdir(src)) {
95
- const entries = await readdir(src)
92
+ const entries = await fs.promises.readdir(src)
96
93
  return Promise.all (entries.map (async each => {
97
94
  const e = join (src,each)
98
95
  const f = join (src,each)
@@ -0,0 +1,101 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
4
+ const cds = require('../../../libx/_runtime/cds')
5
+ const { packArchive, packArchiveCLI, unpackArchive, unpackArchiveCLI } = require('./tar')
6
+ const { exists } = require('./utils')
7
+
8
+ // use tar command line interface
9
+ const TAR_CLI = true
10
+ const TEMP_DIR = fs.realpathSync(require('os').tmpdir())
11
+
12
+ const findCsvFiles = async (folder, deep = false) => {
13
+ const files = await fs.promises.readdir(folder)
14
+ const result = []
15
+
16
+ for (const file of files) {
17
+ const filePath = path.join(folder, file)
18
+ if (deep && (await fs.promises.stat(filePath)).isDirectory()) {
19
+ const files = await findCsvFiles(filePath)
20
+ result.push(...files)
21
+ } else {
22
+ if (path.extname(file) === '.csv') {
23
+ result.push(filePath)
24
+ }
25
+ }
26
+ }
27
+
28
+ return result
29
+ }
30
+
31
+ const collectCsvFiles = async sources => {
32
+ const folders = sources.map(file => path.dirname(file))
33
+ const uniqueFolders = [...new Set(folders)].filter(folder => !/node_modules/.test(folder))
34
+ const db = (cds.env.folders && cds.env.folders.db) || 'db/'
35
+ let dbFolder = path.join(global.cds.root, db)
36
+ if (dbFolder.endsWith('/') || dbFolder.endsWith('\\')) dbFolder = dbFolder.slice(0, -1)
37
+ if (!uniqueFolders.includes(dbFolder)) uniqueFolders.push(dbFolder)
38
+
39
+ const result = []
40
+ for (const folder of uniqueFolders) {
41
+ const dataPath = path.join(folder, 'data')
42
+ const dataFiles = (await exists(dataPath)) ? await findCsvFiles(dataPath) : []
43
+ const csvPath = path.join(folder, 'csv')
44
+ const csvFiles = (await exists(csvPath)) ? await findCsvFiles(csvPath) : []
45
+ result.push(...dataFiles, ...csvFiles)
46
+ }
47
+
48
+ return result
49
+ }
50
+
51
+ const packTarArchive = async (files, root, flat = false, cli = true) => {
52
+ let tgzBuffer, temp
53
+
54
+ try {
55
+ temp = await fs.promises.mkdtemp(`${TEMP_DIR}${path.sep}tar-`)
56
+ if (flat) {
57
+ const files_ = []
58
+ for (const file of files) {
59
+ const destination = path.join(temp, path.basename(file))
60
+ await fs.promises.copyFile(file, destination)
61
+ files_.push(destination)
62
+ }
63
+ files = files_
64
+ root = temp
65
+ }
66
+
67
+ const relativeFiles = files.map(file => path.relative(root, file))
68
+ if (cli) {
69
+ const output = path.relative(root, path.join(temp, `${cds.utils.uuid()}.tgz`))
70
+ tgzBuffer = await packArchiveCLI(relativeFiles, root, output)
71
+ } else {
72
+ tgzBuffer = await packArchive(relativeFiles, root)
73
+ }
74
+ } finally {
75
+ if (await exists(temp)) {
76
+ await (fs.promises.rm || fs.promises.rmdir)(temp, { recursive: true, force: true })
77
+ }
78
+ }
79
+
80
+ return tgzBuffer
81
+ }
82
+
83
+ const unpackTarArchive = async (buffer, folder, cli = true) => {
84
+ const temp = await fs.promises.mkdtemp(`${TEMP_DIR}${path.sep}tar-`)
85
+ const tgz = path.join(temp, 'resources.tgz')
86
+ await fs.promises.writeFile(tgz, buffer, 'binary')
87
+
88
+ try {
89
+ cli ? await unpackArchiveCLI(tgz, folder) : await unpackArchive(tgz, folder)
90
+ } finally {
91
+ if (await exists(temp)) await (fs.promises.rm || fs.promises.rmdir)(temp, { recursive: true, force: true })
92
+ }
93
+ }
94
+
95
+ module.exports = {
96
+ TAR_CLI,
97
+ findCsvFiles,
98
+ collectCsvFiles,
99
+ packTarArchive,
100
+ unpackTarArchive
101
+ }
@@ -0,0 +1,71 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const tar = require('tar')
4
+ const exec = require('child_process').exec
5
+
6
+ const _createArchive = (files, root) => {
7
+ return tar.c(
8
+ {
9
+ gzip: true,
10
+ preservePaths: false,
11
+ cwd: root
12
+ },
13
+ files
14
+ )
15
+ }
16
+
17
+ const _createArchiveCLI = (files, root, output) => {
18
+ const cmd = 'cd ' + root + ' && tar -czf ' + output + ' ' + files.join(' ')
19
+
20
+ return new Promise((resolve, reject) => {
21
+ exec(cmd, err => {
22
+ if (err) reject(err)
23
+ else resolve()
24
+ })
25
+ })
26
+ }
27
+
28
+ const packArchive = (files, root) => {
29
+ _createArchive(files, root)
30
+ const stream = _createArchive(files, root)
31
+ return new Promise((resolve, reject) => {
32
+ const chunks = []
33
+ stream.on('data', data => chunks.push(data))
34
+ stream.on('error', error => reject(error))
35
+ stream.on('end', () => {
36
+ resolve(Buffer.concat(chunks))
37
+ })
38
+ })
39
+ }
40
+
41
+ const packArchiveCLI = async (files, root, output) => {
42
+ await _createArchiveCLI(files, root, output)
43
+
44
+ return fs.promises.readFile(path.join(root, output))
45
+ }
46
+
47
+ const unpackArchive = async (tgz, folder) => {
48
+ await tar.x({
49
+ file: tgz,
50
+ gzip: true,
51
+ C: folder
52
+ })
53
+ }
54
+
55
+ const unpackArchiveCLI = async (tgz, folder) => {
56
+ const cmd = 'tar -xzf ' + tgz + ' -C ' + folder
57
+
58
+ return new Promise((resolve, reject) => {
59
+ exec(cmd, err => {
60
+ if (err) reject(err)
61
+ else resolve()
62
+ })
63
+ })
64
+ }
65
+
66
+ module.exports = {
67
+ packArchive,
68
+ packArchiveCLI,
69
+ unpackArchive,
70
+ unpackArchiveCLI
71
+ }
@@ -0,0 +1,11 @@
1
+ const fs = require('fs')
2
+
3
+ const exists = async fileOrDir => {
4
+ try {
5
+ return await fs.promises.stat(fileOrDir)
6
+ } catch (_) {
7
+ return false
8
+ }
9
+ }
10
+
11
+ module.exports = { exists }
@@ -15,23 +15,23 @@ module.exports = class AuditLogService extends OutboxService {
15
15
  // call OutboxService's init, which handles outboxing
16
16
  await super.init()
17
17
 
18
- // register handlers
19
- this.on('dataAccessLog', this.dataAccessLog)
20
- this.on('dataModificationLog', this.dataModificationLog)
21
- this.on('securityLog', this.securityLog)
22
- this.on('configChangeLog', this.configChangeLog)
23
-
24
18
  // connect to audit log service
25
- // REVISIT for GA: throw error on connect issue instead of warn and this.ready?
26
19
  this.alc = await v2utils.connect(this.options.credentials)
27
- this.ready = !!this.alc
20
+
21
+ // register handlers, if connected
22
+ if (this.alc) {
23
+ this.on('dataAccessLog', this._dataAccessLog)
24
+ this.on('dataModificationLog', this._dataModificationLog)
25
+ this.on('securityLog', this._securityLog)
26
+ this.on('configChangeLog', this._configChangeLog)
27
+ }
28
28
  }
29
29
 
30
30
  async emit(first, second) {
31
31
  const { event, data } = typeof first === 'object' ? first : { event: first, data: second }
32
32
  if (!this.options.outbox) return this.send(event, data)
33
33
 
34
- if (this.ready && this[event]) {
34
+ if (this[event]) {
35
35
  try {
36
36
  // this will open a new tx -> preserve user
37
37
  await super.send(new cds.Request({ method: event, data, user: cds.context.user }))
@@ -45,14 +45,34 @@ module.exports = class AuditLogService extends OutboxService {
45
45
  }
46
46
 
47
47
  async send(event, data) {
48
- if (this.ready && this[event]) return super.send(event, data)
48
+ if (this[event]) return super.send(event, data)
49
+ }
50
+
51
+ /*
52
+ * api (await AuditLogService.<event>(data))
53
+ */
54
+
55
+ async dataAccessLog(data) {
56
+ return super.send('dataAccessLog', data)
49
57
  }
50
58
 
51
- async dataAccessLog(arg) {
52
- const accesses = arg.accesses || arg.data.accesses
59
+ async dataModificationLog(data) {
60
+ return super.send('dataModificationLog', data)
61
+ }
53
62
 
54
- if (!this.ready) throw new Error('AuditLogService not connected')
63
+ async securityLog(data) {
64
+ return super.send('securityLog', data)
65
+ }
66
+
67
+ async configChangeLog(data) {
68
+ return super.send('configChangeLog', data)
69
+ }
55
70
 
71
+ /*
72
+ * impl
73
+ */
74
+
75
+ async _dataAccessLog({ data: { accesses } }) {
56
76
  const { tenant, user } = _getTenantAndUser()
57
77
 
58
78
  // build the logs
@@ -73,11 +93,7 @@ module.exports = class AuditLogService extends OutboxService {
73
93
  }
74
94
 
75
95
  // REVISIT: modification.action not used in auditlog v2
76
- async dataModificationLog(arg) {
77
- const modifications = arg.modifications || arg.data.modifications
78
-
79
- if (!this.ready) throw new Error('AuditLogService not connected')
80
-
96
+ async _dataModificationLog({ data: { modifications } }) {
81
97
  const { tenant, user } = _getTenantAndUser()
82
98
 
83
99
  // build the logs
@@ -97,15 +113,7 @@ module.exports = class AuditLogService extends OutboxService {
97
113
  }
98
114
  }
99
115
 
100
- async securityLog(arg) {
101
- let { action, data } = arg
102
- if (arg.data && arg.data.action) {
103
- action = arg.data.action
104
- data = arg.data.data
105
- }
106
-
107
- if (!this.ready) throw new Error('AuditLogService not connected')
108
-
116
+ async _securityLog({ data: { action, data } }) {
109
117
  let { tenant, user } = _getTenantAndUser()
110
118
 
111
119
  // cds.context may not be proper on auth-related errors -> try to extract from data
@@ -132,18 +140,7 @@ module.exports = class AuditLogService extends OutboxService {
132
140
  }
133
141
 
134
142
  // REVISIT: action and success not used in auditlog v2
135
- async configChangeLog(arg) {
136
- let { action, success, configurations } = arg
137
- if (arg.data) {
138
- // eslint-disable-next-line no-unused-vars
139
- action = arg.data.action
140
- // eslint-disable-next-line no-unused-vars
141
- success = arg.data.success
142
- configurations = arg.data.configurations
143
- }
144
-
145
- if (!this.ready) throw new Error('AuditLogService not connected')
146
-
143
+ async _configChangeLog({ data: { action, success, configurations } }) {
147
144
  const { tenant, user } = _getTenantAndUser()
148
145
 
149
146
  // build the logs
@@ -13,7 +13,7 @@ const {
13
13
  resolveDataSubjectPromises
14
14
  } = require('./utils')
15
15
 
16
- let als
16
+ let auditLogService
17
17
 
18
18
  const _processorFnAccess = (accessLogs, model, req) => {
19
19
  return ({ row, key, element, plain }) => {
@@ -70,15 +70,14 @@ const _getDataAccessLogs = (data, req, tx) => {
70
70
  }
71
71
 
72
72
  const auditAccessHandler = async function (data, req) {
73
- als = als || (await cds.connect.to('audit-log'))
74
- if (!als.ready) return
73
+ auditLogService = auditLogService || (await cds.connect.to('audit-log'))
75
74
 
76
75
  const accessLogs = _getDataAccessLogs(data, req, this)
77
76
  // REVISIT: a function called resolveDataSubjectPromises should not also convert an object to an array
78
77
  let accesses = await resolveDataSubjectPromises(accessLogs)
79
78
  accesses = accesses.filter(ele => ele.attributes.length)
80
79
 
81
- if (accesses.length) await als.emit('dataAccessLog', { accesses })
80
+ if (accesses.length) await auditLogService.emit('dataAccessLog', { accesses })
82
81
  }
83
82
 
84
83
  module.exports = {