@sap/cds 6.8.4 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/CHANGELOG.md +66 -4
  2. package/README.md +0 -1
  3. package/bin/cds-serve.js +50 -3
  4. package/bin/deploy/to-hana.js +1 -0
  5. package/bin/serve.js +16 -20
  6. package/lib/auth/basic-auth.js +6 -4
  7. package/lib/auth/index.js +4 -3
  8. package/lib/auth/jwt-auth.js +2 -5
  9. package/lib/compile/cds-compile.js +34 -89
  10. package/lib/compile/cdsc.js +11 -0
  11. package/lib/compile/etc/properties.js +2 -2
  12. package/lib/compile/for/lean_drafts.js +36 -69
  13. package/lib/compile/for/nodejs.js +2 -1
  14. package/lib/compile/load.js +3 -3
  15. package/lib/compile/minify.js +2 -0
  16. package/lib/compile/to/csn.js +74 -0
  17. package/{bin/build/provider/hana/2tabledata.js → lib/compile/to/hdbtabledata.js} +4 -6
  18. package/lib/compile/to/json.js +1 -1
  19. package/lib/compile/to/sql.js +8 -6
  20. package/lib/dbs/cds-deploy.js +174 -114
  21. package/lib/env/cds-env.js +64 -79
  22. package/lib/env/cds-requires.js +11 -28
  23. package/lib/env/defaults.js +13 -3
  24. package/lib/env/plugins.js +1 -12
  25. package/lib/env/presets.js +25 -21
  26. package/lib/index.js +121 -147
  27. package/lib/{core/reflect.js → linked/models.js} +2 -2
  28. package/lib/{core/infer.js → linked/queries.js} +2 -0
  29. package/lib/{core/index.js → linked/types.js} +2 -1
  30. package/lib/log/cds-error.js +13 -7
  31. package/lib/log/format/cf.js +1 -1
  32. package/lib/plugins.js +49 -0
  33. package/lib/ql/Query.js +0 -9
  34. package/lib/ql/STREAM.js +0 -1
  35. package/lib/req/context.js +2 -7
  36. package/lib/req/request.js +6 -2
  37. package/lib/req/response.js +23 -10
  38. package/lib/srv/middlewares/ctx-model.js +2 -2
  39. package/lib/srv/middlewares/errors.js +1 -1
  40. package/lib/srv/protocols/_legacy.js +1 -0
  41. package/lib/srv/protocols/graphql.js +7 -16
  42. package/lib/srv/protocols/index.js +59 -45
  43. package/lib/srv/protocols/odata-v2-proxy.js +2 -70
  44. package/lib/srv/protocols/odata-v4.js +9 -4
  45. package/lib/srv/srv-api.js +9 -3
  46. package/lib/srv/srv-dispatch.js +12 -9
  47. package/lib/srv/srv-models.js +4 -21
  48. package/lib/srv/srv-tx.js +15 -12
  49. package/lib/utils/cds-test.js +14 -9
  50. package/lib/utils/cds-utils.js +2 -12
  51. package/lib/utils/check-version.js +17 -0
  52. package/{bin/build → lib/utils}/csv-reader.js +23 -24
  53. package/libx/_runtime/auth/index.js +27 -23
  54. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
  55. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  56. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
  57. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
  58. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
  59. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
  60. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
  61. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
  62. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
  63. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
  64. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
  65. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
  66. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
  68. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  69. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
  70. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
  71. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
  72. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
  73. package/libx/_runtime/cds-services/services/Service.js +79 -107
  74. package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
  75. package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
  76. package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
  77. package/libx/_runtime/cds-services/util/assert.js +65 -2
  78. package/libx/_runtime/common/composition/data.js +1 -0
  79. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  80. package/libx/_runtime/common/generic/auth/restrict.js +5 -10
  81. package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
  82. package/libx/_runtime/common/generic/auth/utils.js +1 -2
  83. package/libx/_runtime/common/generic/crud.js +32 -16
  84. package/libx/_runtime/common/generic/etag.js +133 -104
  85. package/libx/_runtime/common/generic/input.js +6 -21
  86. package/libx/_runtime/common/generic/put.js +1 -1
  87. package/libx/_runtime/common/generic/stream.js +52 -0
  88. package/libx/_runtime/common/generic/temporal.js +25 -8
  89. package/libx/_runtime/common/i18n/messages.properties +0 -2
  90. package/libx/_runtime/common/utils/cqn.js +1 -1
  91. package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
  92. package/libx/_runtime/common/utils/csn.js +0 -51
  93. package/libx/_runtime/common/utils/etag.js +30 -0
  94. package/libx/_runtime/common/utils/keys.js +1 -1
  95. package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
  96. package/libx/_runtime/common/utils/path.js +1 -1
  97. package/libx/_runtime/common/utils/resolveView.js +2 -1
  98. package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
  99. package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
  100. package/libx/_runtime/common/utils/stream.js +140 -0
  101. package/libx/_runtime/common/utils/streamProp.js +29 -12
  102. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
  103. package/libx/_runtime/db/generic/index.js +0 -2
  104. package/libx/_runtime/db/query/delete.js +2 -2
  105. package/libx/_runtime/db/query/insert.js +2 -2
  106. package/libx/_runtime/db/query/read.js +2 -2
  107. package/libx/_runtime/db/query/run.js +2 -2
  108. package/libx/_runtime/db/query/update.js +2 -2
  109. package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
  110. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
  111. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
  112. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
  113. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
  114. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
  115. package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
  116. package/libx/_runtime/fiori/draft.js +2 -0
  117. package/libx/_runtime/fiori/generic/activate.js +8 -9
  118. package/libx/_runtime/fiori/generic/before.js +30 -20
  119. package/libx/_runtime/fiori/generic/cancel.js +5 -3
  120. package/libx/_runtime/fiori/generic/delete.js +5 -3
  121. package/libx/_runtime/fiori/generic/edit.js +7 -7
  122. package/libx/_runtime/fiori/generic/index.js +10 -16
  123. package/libx/_runtime/fiori/generic/new.js +5 -3
  124. package/libx/_runtime/fiori/generic/patch.js +11 -8
  125. package/libx/_runtime/fiori/generic/prepare.js +13 -6
  126. package/libx/_runtime/fiori/generic/read.js +12 -6
  127. package/libx/_runtime/fiori/lean-draft.js +207 -152
  128. package/libx/_runtime/fiori/utils/delete.js +10 -5
  129. package/libx/_runtime/fiori/utils/req.js +17 -5
  130. package/libx/_runtime/fiori/utils/stream.js +36 -0
  131. package/libx/_runtime/hana/Service.js +12 -9
  132. package/libx/_runtime/hana/conversion.js +10 -15
  133. package/libx/_runtime/hana/driver.js +2 -0
  134. package/libx/_runtime/hana/execute.js +28 -6
  135. package/libx/_runtime/hana/pool.js +36 -122
  136. package/libx/_runtime/hana/search2cqn4sql.js +34 -36
  137. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
  138. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
  139. package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
  140. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  141. package/libx/_runtime/remote/Service.js +20 -1
  142. package/libx/_runtime/remote/utils/client.js +3 -5
  143. package/libx/_runtime/sqlite/Service.js +4 -6
  144. package/libx/_runtime/sqlite/conversion.js +3 -13
  145. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
  146. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
  147. package/libx/_runtime/sqlite/execute.js +5 -16
  148. package/libx/odata/afterburner.js +22 -6
  149. package/libx/odata/grammar.pegjs +6 -1
  150. package/libx/odata/parser.js +1 -1
  151. package/libx/rest/RestAdapter.js +16 -9
  152. package/libx/rest/RestRequest.js +1 -1
  153. package/libx/rest/middleware/input.js +2 -1
  154. package/libx/rest/middleware/operation.js +1 -0
  155. package/libx/rest/middleware/parse.js +3 -2
  156. package/libx/rest/middleware/payload.js +9 -8
  157. package/libx/rest/middleware/read.js +1 -0
  158. package/package.json +9 -16
  159. package/server.js +1 -1
  160. package/app/fiori/preview.js +0 -270
  161. package/app/fiori/routes.js +0 -59
  162. package/bin/build/buildTaskEngine.js +0 -360
  163. package/bin/build/buildTaskFactory.js +0 -283
  164. package/bin/build/buildTaskHandler.js +0 -241
  165. package/bin/build/buildTaskProvider.js +0 -22
  166. package/bin/build/buildTaskProviderFactory.js +0 -175
  167. package/bin/build/cds.js +0 -5
  168. package/bin/build/constants.js +0 -66
  169. package/bin/build/index.js +0 -58
  170. package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
  171. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
  172. package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
  173. package/bin/build/provider/buildTaskProviderInternal.js +0 -383
  174. package/bin/build/provider/fiori/index.js +0 -171
  175. package/bin/build/provider/hana/2migration.js +0 -179
  176. package/bin/build/provider/hana/index.js +0 -505
  177. package/bin/build/provider/hana/migrationtable.js +0 -472
  178. package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
  179. package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
  180. package/bin/build/provider/hana/template/.hdinamespace +0 -4
  181. package/bin/build/provider/hana/template/package.json +0 -12
  182. package/bin/build/provider/hana/template/undeploy.json +0 -5
  183. package/bin/build/provider/java/index.js +0 -111
  184. package/bin/build/provider/java-cf/index.js +0 -1
  185. package/bin/build/provider/mtx/index.js +0 -268
  186. package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
  187. package/bin/build/provider/mtx-extension/index.js +0 -131
  188. package/bin/build/provider/mtx-sidecar/index.js +0 -137
  189. package/bin/build/provider/node-cf/index.js +0 -1
  190. package/bin/build/provider/nodejs/index.js +0 -192
  191. package/bin/build/util.js +0 -299
  192. package/bin/cds.js +0 -125
  193. package/bin/deploy/to-hana/cfUtil.js +0 -355
  194. package/bin/deploy/to-hana/gitUtil.js +0 -57
  195. package/bin/deploy/to-hana/hana.js +0 -306
  196. package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
  197. package/bin/deploy/to-hana/index.js +0 -16
  198. package/bin/deploy/to-hana/mtaUtil.js +0 -170
  199. package/bin/mtx/in-cds.js +0 -17
  200. package/bin/plugins.js +0 -32
  201. package/bin/run.js +0 -24
  202. package/bin/utils/log.js +0 -24
  203. package/bin/version.js +0 -178
  204. package/libx/_runtime/audit/Service.js +0 -222
  205. package/libx/_runtime/audit/generic/personal/access.js +0 -61
  206. package/libx/_runtime/audit/generic/personal/index.js +0 -56
  207. package/libx/_runtime/audit/generic/personal/modification.js +0 -132
  208. package/libx/_runtime/audit/generic/personal/utils.js +0 -186
  209. package/libx/_runtime/audit/utils/log.js +0 -23
  210. package/libx/_runtime/audit/utils/v2.js +0 -176
  211. package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
  212. package/libx/_runtime/db/generic/integrity.js +0 -455
  213. package/srv/audit-log.cds +0 -87
  214. package/srv/mtx.cds +0 -2
  215. package/srv/mtx.js +0 -8
  216. /package/lib/{core → linked}/classes.js +0 -0
  217. /package/lib/{core → linked}/entities.js +0 -0
@@ -1,38 +1,29 @@
1
- const cds = require ('../../index'), { decodeURIComponent } = cds.utils
2
- const LOG = cds.log('graphql')
3
-
4
1
  const GraphQLAdapter = require('@cap-js/graphql') // eslint-disable-line cds/no-missing-dependencies
5
2
  const express = require ('express') // eslint-disable-line cds/no-missing-dependencies
6
3
 
7
4
  function CDSGraphQLAdapter (options) {
8
-
9
- const {services} = options
5
+ const { services } = options
10
6
 
11
7
  return express.Router()
12
8
  .use (express.json()) //> required in the slug handlers and logger below
13
9
 
14
- /** Convenience slug route for /graphql/srv/entity/id */
15
- .use ('/:path/:entity/:id?(%20:query)?', (req,_,next) => {
10
+ // convenience slug route for /graphql/srv/entity/id
11
+ .use ('/:path/:entity/:id?(%20:query)?', (req, _, next) => {
16
12
  // TODO: add filter by id -> then remove the // eslint-disable-line
17
- let { entity, id, query } = req.params // eslint-disable-line no-unused-vars
13
+ const { entity, query } = req.params // eslint-disable-line no-unused-vars
18
14
  if (query) req.body = { query }
19
15
  if (entity) req.body.query = req.body.query.replace(/{/, `{ ${entity} {`) +'}'
20
16
  next() //> goes on below
21
17
  })
22
18
 
23
- /** Convenience slug route for /graphql/srv */
19
+ // convenience slug route for /graphql/srv
24
20
  .use ('/:path', (req, res, next) => {
25
- let srv = services [req.params.path]
21
+ const srv = services [req.params.path]
26
22
  if (req.body) req.body.query = req.body.query.replace(/{/, `{ ${srv.name} {`) +'}'
27
23
  next() //> goes on below
28
24
  })
29
25
 
30
- .use ((req,_,next)=>{
31
- LOG.info (req.method, req.body?.query || decodeURIComponent(req.query.query))
32
- next()
33
- })
34
-
35
- /** The global /graphql route */
26
+ // the global /graphql route
36
27
  .use (new GraphQLAdapter (options))
37
28
  }
38
29
 
@@ -7,11 +7,7 @@ class ProtocolAdapter {
7
7
  * Provides canonicalized protocols configurations
8
8
  */
9
9
  static init (protocols = { ...cds.env.protocols }) {
10
- for (let [k,o] of Object.entries(protocols)) if (typeof o === 'string') protocols[k] = {path:o}
11
- if (!protocols.odata) protocols.odata = { impl: join(__dirname,'odata-v4') }
12
- if (!protocols.rest) protocols.rest = { impl: join(__dirname,'rest') }
13
- // odata must always be first for fallback
14
- return this.protocols = { odata: protocols.odata, ...protocols }
10
+ return this.protocols = protocols
15
11
  }
16
12
 
17
13
  /**
@@ -29,64 +25,82 @@ class ProtocolAdapter {
29
25
  /**
30
26
  * Constructs a new adapter for the given service, or returns a formerly constructed one
31
27
  */
32
- static for (srv, p = srv.options?.to || protocol4(srv.definition)) {
28
+ static for (srv, p = srv.options?.to || protocol4(srv.definition)) { // TODO default for param p?
33
29
  const cache = srv._adapters || (srv._adapters={}); if (p in cache) return cache[p]
34
30
  const impl = this.middlewareFor(p), conf = this.protocols[p]
35
31
  return cache[p] = impl (srv, conf)
36
32
  }
37
33
 
38
34
  /**
39
- * Constructs a new adapter for the given service, and mounts it to an express app.
35
+ * Returns the defined protocols for the given service
40
36
  */
41
- static serve (srv, /* in: */ app, { before, after } = cds.middlewares) {
42
- const adapter = this.for(srv); if (!adapter) return
43
- app.use (srv.path+'/webapp/', (_,res) => res.sendStatus(404))
44
- app.use (srv.path, before, adapter, after)
45
- return adapter
37
+ static protocols4 (def) {
38
+ // String, provided via srv.options.to
39
+ if(typeof def === 'string') {
40
+ return [{ kind: def }]
41
+ }
42
+
43
+ // @protocol
44
+ let atProtocol = def?.['@protocol']
45
+ if (atProtocol) {
46
+ if (!Array.isArray(atProtocol)) atProtocol = [atProtocol]
47
+ return atProtocol.map(p => typeof p === 'string' ? { kind: p } : p)
48
+ }
49
+
50
+ // @odata, @rest, ...
51
+ const atProtocolDirect = Object.keys(this.protocols).find(p => def?.['@'+p])
52
+ if (atProtocolDirect) return [{ kind: atProtocolDirect }]
53
+
54
+ // No protocol annotation found -> serve odata
55
+ return [{ kind: Object.keys(this.protocols)[0] || 'odata-v4' }]
46
56
  }
47
57
 
48
58
  /**
49
- * Serve protocols at configured paths, if any; e.g. /hcql/browse, /graphql/...
59
+ * Constructs a new adapter for the given service, and mounts it to an express app.
50
60
  */
51
- static serveAll (protocols = Object.entries(ProtocolAdapter.protocols).filter(([,o]) => o.path)) {
52
- if (protocols?.length) cds.once ('served', ()=>{
53
- const LOG = cds.log(), DEBUG = cds.debug('adapters')
54
- for (let [ protocol, options ] of protocols) {
55
- let globalAdapter = false
56
- for (let srv of cds.service.providers) {
57
- if (!protocol4(srv.definition,null)) {
58
- let adapter = ProtocolAdapter.middlewareFor(protocol); if (!adapter) continue
59
- if (is_global(adapter)) { globalAdapter = adapter; break }
60
- _serve (adapter(srv,options), options.path + srv.path, protocol, options)
61
- }
62
- }
63
- if (globalAdapter) {
64
- options = { ...options, services: cds.service.providers
65
- .filter (srv => { let p = protocol4(srv.definition,null); return !p || p === protocol })
66
- .reduce ((all,srv) => (all[srv.path.slice(1)] = srv, all), {})
67
- }
68
- _serve (globalAdapter(options), options.path, protocol, options)
61
+ static serve (srv, /* in: */ app, { before, after } = cds.middlewares) {
62
+ const DEBUG = cds.debug('adapters')
63
+ const protocols = this.protocols4(srv.options?.to || srv.definition)
64
+
65
+ if (protocols.length > 1 && !cds.requires.middlewares) {
66
+ cds.error `Cannot serve multiple protocols if cds.requires.middlewares is set to false`
67
+ }
68
+
69
+ const srvDefPath = srv.definition?.['@path']
70
+ if (protocols.length > 1 && srvDefPath?.[0] === '/') {
71
+ cds.error `Cannot serve entity with absolute @path (starting with '/') for more than one protocol`
72
+ }
73
+
74
+ let adapter, path, prefix
75
+ for (let p of protocols) {
76
+ adapter = this.for(srv, p.kind); if (!adapter) continue
77
+ path = srv.options?.at || srv.options?.path || p.path || srvDefPath || cds.service.path4(srv).slice(1)
78
+
79
+ if (path[0] !== '/') {
80
+ if (protocols.length === 1 && cds.env.features.serve_on_root) {
81
+ app.use (`/${path}/webapp/`, (_,res) => res.sendStatus(404))
82
+ DEBUG?.('app.use(', path, ', ... )')
83
+ app.use (`/${path}`, before, adapter, after)
69
84
  }
85
+
86
+ prefix = this.protocols[p.kind].path
87
+ prefix = prefix ? (prefix.endsWith('/') ? prefix : prefix + '/') : '/'
88
+ path = prefix + path
70
89
  }
71
- function _serve (adapter, path, protocol, options) {
72
- if (!_serve.first) { _serve.first = true; console.log() }
73
- if (!_serve[protocol]) LOG.info ('serving', _serve[protocol] = { protocol, at: options.path })
74
- DEBUG?.('app.use(', path, ', ... )')
75
- cds.app.use (path, cds.middlewares.before, adapter, cds.middlewares.after)
76
- }
77
- })
90
+
91
+ DEBUG?.('app.use(', path, ', ... )')
92
+ app.use (path, before, adapter, after)
93
+ // REVISIT this doesn't handle multiple protocols correctly
94
+ srv.path = path
95
+ }
78
96
  }
79
97
  }
80
98
 
81
-
82
99
  const protocols = Object.keys(ProtocolAdapter.init())
83
- const protocol4 = (def, _default = protocols[0]) => def?.['@protocol'] || protocols.find(p => def['@'+p]) || _default
84
- const is_global = adapter => adapter.length === 1 && !/^(function )?(\w+\s+)?\((srv|service)/.test(adapter)
100
+ // REVISIT remove protocol4 (and protocols variable)
101
+ const protocol4 = (def, _default = protocols[0] || 'odata-v4') => def?.['@protocol'] || protocols.find(p => def['@'+p]) || _default
85
102
 
86
103
  module.exports = { ProtocolAdapter, protocol4 }
87
- if (cds.env.protocols) {
88
- cds.middlewares = require('../middlewares')
89
- ProtocolAdapter.serveAll()
90
- } else if (!cds.requires.middlewares) {
104
+ if (!cds.env.protocols && !cds.requires.middlewares) {
91
105
  module.exports.ProtocolAdapter = require('./_legacy')
92
106
  }
@@ -125,8 +125,6 @@ function convertToNodeHeaders(webHeaders) {
125
125
  * @param {string} options.target Target which points to OData V4 backend host:port. Use 'auto' to infer the target from server url after listening. Default is e.g. 'http://localhost:4004'.
126
126
  * @param {string} options.targetPath Target path to which is redirected. Default is ''.
127
127
  * @param {object} options.services Service mapping object from url path name to service name. Default is {}.
128
- * @param {boolean} options.mtxRemote CDS model is retrieved remotely via MTX endpoint for multitenant scenario (old MTX only). Default is false.
129
- * @param {string} options.mtxEndpoint Endpoint to retrieve MTX metadata when option 'mtxRemote' is active (old MTX only). Default is '/mtx/v1'.
130
128
  * @param {boolean} options.ieee754Compatible Edm.Decimal and Edm.Int64 are serialized IEEE754 compatible. Default is true.
131
129
  * @param {number} options.fileUploadSizeLimit File upload file size limit (in bytes). Default is 10485760 (10 MB).
132
130
  * @param {boolean} options.continueOnError Indicates to OData V4 backend to continue on error. Default is false.
@@ -172,8 +170,6 @@ function cov2ap(options = {}) {
172
170
  let target = optionWithFallback("target", `http://${DefaultHost}:${port}`);
173
171
  const logLevel = optionWithFallback("logLevel", 'info');
174
172
  const services = optionWithFallback("services", {});
175
- const mtxRemote = optionWithFallback("mtxRemote", false);
176
- const mtxEndpoint = optionWithFallback("mtxEndpoint", "/mtx/v1");
177
173
  const ieee754Compatible = optionWithFallback("ieee754Compatible", true);
178
174
  const fileUploadSizeLimit = optionWithFallback("fileUploadSizeLimit", 10 * 1024 * 1024);
179
175
  const continueOnError = optionWithFallback("continueOnError", false);
@@ -228,11 +224,6 @@ function cov2ap(options = {}) {
228
224
  service.$linkProviders.push(provider);
229
225
  });
230
226
 
231
- if (cds.mtx && cds.mtx.eventEmitter && cds.env.requires && cds.env.requires.multitenancy) {
232
- cds.mtx.eventEmitter.on(cds.mtx.events.TENANT_UPDATED, (tenant) => {
233
- delete proxyCache[tenant];
234
- });
235
- }
236
227
  // TODO: Cache invalidation for Streamlined MTX (when extensibility is supported)
237
228
 
238
229
  router.use(`${path}/:service`, async (req, res, next) => {
@@ -604,14 +595,8 @@ function cov2ap(options = {}) {
604
595
 
605
596
  async function getMetadata(req, service) {
606
597
  let metadata;
607
- if (req.tenant) {
608
- if (mtxRemote && mtxEndpoint) {
609
- metadata = await getTenantMetadataRemote(req, service);
610
- } else if (cds.mtx && cds.env.requires && cds.env.requires.multitenancy) {
611
- metadata = await getTenantMetadataLocal(req, service);
612
- } else if (cds.env.requires && cds.env.requires["cds.xt.ModelProviderService"]) {
613
- metadata = await getTenantMetadataStreamlined(req, service);
614
- }
598
+ if (req.tenant && cds.env.requires?.["cds.xt.ModelProviderService"]) {
599
+ metadata = await getTenantMetadataStreamlined(req, service);
615
600
  }
616
601
  if (!metadata) {
617
602
  metadata = await getDefaultMetadata(req, service);
@@ -619,59 +604,6 @@ function cov2ap(options = {}) {
619
604
  return metadata;
620
605
  }
621
606
 
622
- async function getTenantMetadataRemote(req, service) {
623
- const mtxBasePath =
624
- mtxEndpoint.startsWith("http://") || mtxEndpoint.startsWith("https://") ? mtxEndpoint : `${target}${mtxEndpoint}`;
625
- return await prepareMetadata(
626
- req.tenant,
627
- async (tenant) => {
628
- const response = await fetch(`${mtxBasePath}/metadata/csn/${tenant}`, {
629
- method: "GET",
630
- headers: propagateHeaders(req),
631
- });
632
- if (!response.ok) {
633
- throw new Error(await response.text());
634
- }
635
- return response.json();
636
- },
637
- async (tenant, service, locale) => {
638
- const response = await fetch(
639
- `${mtxBasePath}/metadata/edmx/${tenant}?name=${service}&language=${locale}&odataVersion=v2`,
640
- {
641
- method: "GET",
642
- headers: propagateHeaders(req),
643
- }
644
- );
645
- if (!response.ok) {
646
- throw new Error(await response.text());
647
- }
648
- return response.text();
649
- },
650
- service,
651
- determineLocale(req)
652
- );
653
- }
654
-
655
- async function getTenantMetadataLocal(req, service) {
656
- proxyCache[req.tenant] = proxyCache[req.tenant] || {};
657
- const isExtended = await callCached(proxyCache[req.tenant], "isExtended", () => {
658
- return cds.mtx.isExtended(req.tenant);
659
- });
660
- if (isExtended) {
661
- return await prepareMetadata(
662
- req.tenant,
663
- async (tenant) => {
664
- return await cds.mtx.getCsn(tenant);
665
- },
666
- async (tenant, service, locale) => {
667
- return await cds.mtx.getEdmx(tenant, service, locale, "v2");
668
- },
669
- service,
670
- determineLocale(req)
671
- );
672
- }
673
- }
674
-
675
607
  async function getTenantMetadataStreamlined(req, service) {
676
608
  const { "cds.xt.ModelProviderService": mps } = cds.services;
677
609
  proxyCache[req.tenant] = proxyCache[req.tenant] || {};
@@ -1,4 +1,4 @@
1
- const cds = require('../../index'), { User } = cds, { decodeURIComponent } = cds.utils
1
+ const cds = require('../../index'), { User } = cds, { decodeURI } = cds.utils
2
2
  const libx = require('../../../libx/_runtime')
3
3
  const LOG = cds.log('odata')
4
4
 
@@ -6,10 +6,15 @@ module.exports = function ODataAdapter (srv) { return [
6
6
  (req, _, next) => {
7
7
  let u = req.user
8
8
  req.user = u instanceof User ? u : new User(u)
9
- req.on ('dispatch', (eve) => {
10
- LOG && LOG (req.method, req.baseUrl + decodeURIComponent(eve._path), eve._query||'')
11
- if (LOG._debug && eve.query) LOG.debug (eve.query)
9
+
10
+ let url = decodeURI(req.originalUrl)
11
+ LOG && LOG (req.method, url, req.body||'')
12
+ if (/\$batch/.test(req.url)) req.on ('dispatch', (req) => {
13
+ let path = decodeURI(req._path)
14
+ LOG && LOG ('>', req.event, path, req._query||'')
15
+ if (LOG._debug && req.query) LOG.debug (req.query)
12
16
  })
17
+
13
18
  next()
14
19
  },
15
20
  libx.to.odata_v4 (srv)
@@ -63,10 +63,16 @@ class Service extends require('./srv-handlers') {
63
63
  run (query, data) {
64
64
  if (typeof query === 'function') {
65
65
  const ctx = cds.context, fn = query
66
- if (ctx?.tx && !ctx.tx._done)
67
- return fn (this.tx(ctx)) // run fn with nested tx
68
- else return this.tx(fn) // run fn with root tx
66
+
67
+ if (!ctx?.tx) return this.tx(fn) // run fn with root tx
68
+ else {
69
+ if (!ctx.tx._done) return fn(this.tx(ctx)) // run fn with nested tx
70
+ else if (ctx.tx._done === 'rolled back') // > reject
71
+ ctx.tx._throw_closed_error()
72
+ else return this.tx(fn) // run fn with detached root tx
73
+ }
69
74
  }
75
+
70
76
  const req = new Request ({ query, data })
71
77
  return this.dispatch (req)
72
78
  }
@@ -14,11 +14,14 @@ exports.dispatch = async function dispatch (req) { //NOSONAR
14
14
  // Ensure we are in a proper transaction
15
15
  if (!this.context) {
16
16
  const ctx = cds.context
17
- if (ctx?.tx && !ctx.tx._done) {
18
- return this.tx (ctx) .dispatch(req) // with nested tx
19
- }
20
17
 
21
- return this.tx (tx => tx.dispatch(req)) // with root tx
18
+ if (!ctx?.tx) return this.tx(tx => tx.dispatch(req)) // with root tx
19
+ else {
20
+ if (!ctx.tx._done) return this.tx(ctx).dispatch(req) // with nested tx
21
+ else if (ctx.tx._done === 'rolled back') // > reject
22
+ ctx.tx._throw_closed_error()
23
+ else return this.tx(tx => tx.dispatch(req)) // with detached root tx
24
+ }
22
25
  }
23
26
 
24
27
  if (!req.tx) req.tx = this // `this` is a tx from now on...
@@ -57,14 +60,14 @@ exports.handle = async function handle (req) {
57
60
  handlers = this._handlers._initial.filter (h => h.for(req))
58
61
  if (handlers.length) {
59
62
  for (const each of handlers) await each.handler.call (this,req)
60
- if (req.errors) throw req.errors.throwable()
63
+ if (req.errors) throw req.reject()
61
64
  }
62
65
 
63
66
  // .before handlers run in parallel
64
67
  handlers = this._handlers.before.filter (h => h.for(req))
65
68
  if (handlers.length) {
66
69
  await Promise.all (handlers.map (each => each.handler.call (this,req)))
67
- if (req.errors) throw req.errors.throwable()
70
+ if (req.errors) throw req.reject()
68
71
  }
69
72
 
70
73
  // .on handlers run in parallel for async events, and as interceptors stack for sync requests
@@ -78,16 +81,16 @@ exports.handle = async function handle (req) {
78
81
  if (r.results) return r.results
79
82
  if (srv._implicit_next) return next()
80
83
  }()
81
- if (req.errors) throw req.errors.throwable()
84
+ if (req.errors) throw req.reject()
82
85
  }
83
86
  else if (req.query) throw _unhandled (this,req)
84
87
 
85
88
  // .after handlers run in parallel
86
89
  handlers = this._handlers.after.filter (h => h.for(req))
87
90
  if (handlers.length) {
88
- const results = cds.env.features.arrayed_after && req.event === 'READ' && !_is_array(req.results) ? [req.results] : req.results // REVISIT: remove this in a future release after some grace period
91
+ const results = req.event === 'READ' && !_is_array(req.results) ? (req.results == null ? [] : [req.results]) : req.results
89
92
  await Promise.all (handlers.map (each => each.handler.call (this, results, req)))
90
- if (req.errors) throw req.errors.throwable()
93
+ if (req.errors) throw req.reject()
91
94
  }
92
95
 
93
96
  return req.results //> done
@@ -24,7 +24,7 @@ class ExtendedModels {
24
24
  if (!(srv instanceof cds.ApplicationService)) return [] //> no middleware to add // REVISIT: move to `srv.isExtensible`
25
25
  if (!srv.isExtensible) return [] //> no middleware to add
26
26
  else return async function cds_context_model (req,res,next) {
27
- if (!req.user?.tenant) return next()
27
+ if (!req.tenant && !req.user?.tenant && !req.features && !req.user?.features) return next()
28
28
  const ctx = cds.context = cds.EventContext.for({ http: { req, res } })
29
29
  ctx.user = req.user // REVISIT: should move to auth middleware?
30
30
  try {
@@ -161,7 +161,7 @@ class ExtendedModels {
161
161
  static cache = new ExtendedModels
162
162
 
163
163
  /** Time interval in ms to check for new extensions and refresh models, if so. */
164
- static checkInterval = cds.requires.extensibility?.tenantCheckInterval || cds.mtx?.tenantCheckInterval || 1 * 60 * 1000
164
+ static checkInterval = cds.requires.extensibility?.tenantCheckInterval || 1 * 60 * 1000
165
165
 
166
166
  /** Time interval in ms after which to evict models for inactive tenants. */
167
167
  static sentinelInterval = cds.requires.extensibility?.evictionInterval || 3600*1000
@@ -169,21 +169,6 @@ class ExtendedModels {
169
169
  }
170
170
  module.exports = ExtendedModels
171
171
 
172
-
173
-
174
- // ---------------------------------------------------------------------------
175
- // Support for old MTX
176
-
177
- const old_mtx = cds.mtx
178
- if (old_mtx) {
179
- if (!cds.requires.extensibility) cds.requires.extensibility = true // REVISIT: extensibility was always true in old MTX?
180
- ExtendedModels.prototype.at = function (key) { return this[key] }
181
- old_mtx.eventEmitter.on (old_mtx.events.TENANT_UPDATED, async (tenant='') => {
182
- delete ExtendedModels.cache [tenant+':']
183
- })
184
- }
185
-
186
-
187
172
  // ---------------------------------------------------------------------------
188
173
  // Optimizations for single-tenancy modes
189
174
 
@@ -195,10 +180,8 @@ if (!extensibility) {
195
180
 
196
181
 
197
182
  // helper to get model for tenant/features
198
- const _is_extended = old_mtx ? t => old_mtx.isExtended(t) : extensibility ? ()=> cds.db.exists('cds.xt.Extensions') : ()=> false
199
- const _get_model4 = old_mtx ? async (tenant) => {
200
- return old_mtx.getCsn(tenant) .then (cds.compile.for.nodejs)
201
- } : (tenant, toggles) => {
183
+ const _is_extended = extensibility ? ()=> cds.db.exists('cds.xt.Extensions') : ()=> false
184
+ const _get_model4 = (tenant, toggles) => {
202
185
  const { 'cds.xt.ModelProviderService':mps } = cds.services
203
186
  return mps.getCsn (tenant, toggles) .then (cds.compile.for.nodejs)
204
187
  }
package/lib/srv/srv-tx.js CHANGED
@@ -1,4 +1,4 @@
1
- const cds = require('../index'), { cds_tx_protection } = cds.env.features
1
+ const cds = require('../index')
2
2
  const EventContext = require('../req/context')
3
3
  class RootContext extends EventContext {
4
4
  static for(_) {
@@ -26,13 +26,13 @@ function srv_tx (ctx,fn) { const srv = this
26
26
  if (!ctx) return RootTransaction.for (srv)
27
27
 
28
28
  // Creating root or nested txes for existing contexts
29
- if (ctx instanceof EventContext) {
29
+ if (typeof ctx === 'function') [ ctx, fn ] = [ undefined, ctx ]
30
+ else if (ctx instanceof EventContext) {
30
31
  if (ctx.tx) return NestedTransaction.for (srv, ctx)
31
32
  else return RootTransaction.for (srv, ctx)
32
33
  }
33
34
 
34
35
  // Last arg may be a function -> srv.tx (tx => { ... })
35
- if (typeof ctx === 'function') [ ctx, fn ] = [ undefined, ctx ]
36
36
  if (typeof fn === 'function') {
37
37
  const tx = RootTransaction.for (srv, ctx)
38
38
  return cds._context.run (tx, ()=> Promise.resolve(fn(tx)) .then (tx.commit, tx.rollback))
@@ -63,6 +63,7 @@ class Transaction {
63
63
  const proto = new.target.prototype
64
64
  tx.commit = proto.commit.bind(tx)
65
65
  tx.rollback = proto.rollback.bind(tx)
66
+ tx._throw_closed_error = proto._throw_closed_error.bind(tx)
66
67
  if (srv.isExtensible) {
67
68
  const m = cds.context?.model
68
69
  if (m) tx.model = m
@@ -105,6 +106,13 @@ class Transaction {
105
106
  if (err) throw err
106
107
  }
107
108
 
109
+ _throw_closed_error () {
110
+ throw cds.error (
111
+ `Transaction is ${this._done}, no subsequent .run allowed, without prior .begin`,
112
+ { code: 'TRANSACTION_CLOSED' }
113
+ )
114
+ }
115
+
108
116
  }
109
117
 
110
118
 
@@ -124,10 +132,10 @@ class RootTransaction extends Transaction {
124
132
  * are informed by emitting 'succeeded' event to them all.
125
133
  */
126
134
  async commit (res) {
127
- if (cds_tx_protection) this._done = 'committed'
128
135
  try {
129
136
  await this.context.emit ('commit',res) //> allow custom handlers req.before('commit')
130
137
  await super.commit (res)
138
+ this._done = 'committed'
131
139
  await this.context.emit ('succeeded',res)
132
140
  await this.context.emit ('done')
133
141
  } catch (err) {
@@ -144,7 +152,7 @@ class RootTransaction extends Transaction {
144
152
  // nothing to do if transaction already rolled back (we need to check here as well to not emit failed twice)
145
153
  if (this.ready === 'rolled back') return
146
154
 
147
- if (cds_tx_protection) this._done = 'rolled back'
155
+ this._done = 'rolled back'
148
156
  try {
149
157
  await this.context.emit ('failed',err)
150
158
  await super.rollback (err)
@@ -191,13 +199,8 @@ const _begin = async function (req) {
191
199
  if (!req.query && req.method === 'BEGIN') // IMPORTANT: !req.query is to exclude batch requests
192
200
  return this.ready = this.__proto__.dispatch.call (this,req)
193
201
  // Protection against unintended tx.run() after root tx.commit/rollback()
194
- if (typeof this.ready === 'string' || !this.ready && this.context.tx._done) {
195
- if (!cds_tx_protection) this.ready = this.begin() // compatibility to former behavior, which allowed tx.run() after commit/rollback
196
- else throw cds.error (
197
- `Transaction is ${this.ready || this.context.tx._done}, no subsequent .run allowed, without prior .begin`,
198
- { code: 'TRANSACTION_CLOSED' }
199
- )
200
- }
202
+ if (typeof this.ready === 'string' || !this.ready && this.context.tx._done)
203
+ this.context.tx._throw_closed_error()
201
204
  else if (!this.ready) this.ready = this.begin()
202
205
  await this.ready
203
206
  delete this.dispatch
@@ -9,23 +9,28 @@ class Test extends require('./axios') {
9
9
  run (cmd='.', ...args) {
10
10
 
11
11
  const {cds} = this; this.cmd = cmd, this.args = args
12
- if (!/^(serve|run)$/.test(cmd)) try {
13
- const project = cds.utils.isdir(cmd) || require.resolve (cmd+'/package.json').slice(0,-13)
14
- this.cmd = cmd = 'serve'; args.push ('--in-memory?')
15
- this.in (project)
16
- } catch(e) {
17
- throw cds.error (`No such folder or package '${process.cwd()}' -> '${cmd}'`)
12
+ if (!/^(serve|run)$/.test(cmd)) {
13
+ try {
14
+ const project = cds.utils.isdir(cmd) || require.resolve (cmd+'/package.json').slice(0,-13)
15
+ this.cmd = cmd = 'serve'; args.push ('--in-memory?')
16
+ this.in (project)
17
+ } catch(e) {
18
+ throw cds.error (`No such folder or package '${process.cwd()}' -> '${cmd}'`)
19
+ }
20
+ } else if ('run' === cmd && args.length > 0) { // `run <project>` -> `serve --project <project>`
21
+ if (!args.includes('--project')) args.unshift('--project')
18
22
  }
19
23
 
20
24
  // launch cds server...
21
- before (`launching ${cmd} ${args.join(' ')}...`, () => {
25
+ before (`launching ${cmd} ${args.join(' ')}...`, async () => {
26
+ await cds.plugins
22
27
  cds.once ('listening', ({server,url}) => {
23
28
  const axp = Reflect.getOwnPropertyDescriptor(this,'axios')
24
29
  if (axp) axp.value.defaults.baseURL = url
25
30
  this.server = server
26
31
  this.url = url
27
32
  })
28
- try { return cds.exec (cmd, ...args, ...(args.includes('--port') ? [] : ['--port', '0'])) }
33
+ try { return cds.exec (...args, ...(args.includes('--port') ? [] : ['--port', '0'])) }
29
34
  catch (e) { if (is_mocha) console.error(e) } // eslint-disable-line no-console
30
35
  })
31
36
 
@@ -112,7 +117,7 @@ function support_jest_and_mocha() {
112
117
  global.beforeEach = ()=>{}
113
118
  global.afterEach = ()=>{}
114
119
  global.after = global.afterAll = (fn) => {
115
- const repl = global.cds.repl
120
+ const repl = global.cds?.repl
116
121
  repl && repl.on('exit',fn)
117
122
  }
118
123
  }
@@ -102,7 +102,7 @@ exports.read = async function read (file, _encoding) {
102
102
  exports.write = function write (file, data, o) {
103
103
  if (arguments.length === 1) return {to:(...path) => write(join(...path),file)}
104
104
  if (typeof data === 'object' && !Buffer.isBuffer(data))
105
- data = JSON.stringify(data, null, ' '.repeat(o && o.spaces)) + require('os').EOL
105
+ data = JSON.stringify(data, null, ' '.repeat(o && o.spaces)) + '\n'
106
106
  const f = resolve (cds.root,file)
107
107
  return fs.mkdirp (dirname(f)).then (()=> fs.promises.writeFile (f,data,o))
108
108
  }
@@ -178,17 +178,7 @@ exports.find = function find (base, patterns='*', filter=()=>true) {
178
178
  return files
179
179
  }
180
180
 
181
- // TODO find a better place
182
- exports._oldMtx = function _oldMtx() {
183
- function _hasMtxDependency() {
184
- try {
185
- return require(join(cds.root, 'package.json'))?.dependencies?.['@sap/cds-mtx']
186
- } catch(e) {
187
- return false
188
- }
189
- }
190
- return (_hasMtxDependency() || process.env.OLD_MTX?.toLowerCase() === 'true') ?? false
191
- }
181
+ exports.csv = require('./csv-reader')
192
182
 
193
183
  /**
194
184
  * Internal utility to load a file through ESM or CommonJs. TODO find a better place.
@@ -0,0 +1,17 @@
1
+ const major_minor = version => {
2
+ let [ major, minor ] = version.split('.').map(x => +x)
3
+ return { version, major, minor }
4
+ }
5
+ const required = major_minor(
6
+ require('../../package.json').engines.node.match(/>=(.*)/)[1]
7
+ )
8
+ const given = major_minor(
9
+ process.version.match(/^v(\d+\.\d+)/)[1]
10
+ )
11
+
12
+ if (given.major < required.major || given.major === required.major && given.minor < required.minor) process.exit (
13
+ process.stderr.write (`
14
+ Node.js v${required.version} or higher is required for @sap/cds.
15
+ Current v${given.version} does not satisfy this.
16
+ \n`
17
+ ) || 1)