@sap/cds 6.8.4 → 7.0.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 (214) hide show
  1. package/CHANGELOG.md +58 -5
  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 +1 -1
  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 +1 -1
  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/srv-api.js +9 -3
  45. package/lib/srv/srv-dispatch.js +12 -9
  46. package/lib/srv/srv-models.js +4 -21
  47. package/lib/srv/srv-tx.js +15 -12
  48. package/lib/utils/cds-test.js +14 -9
  49. package/lib/utils/cds-utils.js +2 -12
  50. package/lib/utils/check-version.js +17 -0
  51. package/{bin/build → lib/utils}/csv-reader.js +23 -24
  52. package/libx/_runtime/auth/index.js +27 -23
  53. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
  54. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  55. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
  56. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
  57. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
  58. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
  59. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
  60. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
  61. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
  62. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
  63. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
  64. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
  65. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
  66. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
  68. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
  69. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
  70. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
  71. package/libx/_runtime/cds-services/services/Service.js +79 -107
  72. package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
  73. package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
  74. package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
  75. package/libx/_runtime/cds-services/util/assert.js +65 -2
  76. package/libx/_runtime/common/composition/data.js +1 -0
  77. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  78. package/libx/_runtime/common/generic/auth/restrict.js +5 -10
  79. package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
  80. package/libx/_runtime/common/generic/auth/utils.js +1 -2
  81. package/libx/_runtime/common/generic/crud.js +32 -16
  82. package/libx/_runtime/common/generic/etag.js +133 -104
  83. package/libx/_runtime/common/generic/input.js +6 -21
  84. package/libx/_runtime/common/generic/put.js +1 -1
  85. package/libx/_runtime/common/generic/stream.js +52 -0
  86. package/libx/_runtime/common/generic/temporal.js +25 -8
  87. package/libx/_runtime/common/i18n/messages.properties +0 -2
  88. package/libx/_runtime/common/utils/cqn.js +1 -1
  89. package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
  90. package/libx/_runtime/common/utils/csn.js +0 -51
  91. package/libx/_runtime/common/utils/etag.js +30 -0
  92. package/libx/_runtime/common/utils/keys.js +1 -1
  93. package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
  94. package/libx/_runtime/common/utils/path.js +1 -1
  95. package/libx/_runtime/common/utils/resolveView.js +2 -1
  96. package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
  97. package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
  98. package/libx/_runtime/common/utils/stream.js +140 -0
  99. package/libx/_runtime/common/utils/streamProp.js +29 -12
  100. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
  101. package/libx/_runtime/db/generic/index.js +0 -2
  102. package/libx/_runtime/db/query/delete.js +2 -2
  103. package/libx/_runtime/db/query/insert.js +2 -2
  104. package/libx/_runtime/db/query/read.js +2 -2
  105. package/libx/_runtime/db/query/run.js +2 -2
  106. package/libx/_runtime/db/query/update.js +2 -2
  107. package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
  108. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
  109. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
  110. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
  111. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
  112. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
  113. package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
  114. package/libx/_runtime/fiori/draft.js +2 -0
  115. package/libx/_runtime/fiori/generic/activate.js +8 -9
  116. package/libx/_runtime/fiori/generic/before.js +30 -20
  117. package/libx/_runtime/fiori/generic/cancel.js +5 -3
  118. package/libx/_runtime/fiori/generic/delete.js +5 -3
  119. package/libx/_runtime/fiori/generic/edit.js +7 -7
  120. package/libx/_runtime/fiori/generic/index.js +10 -16
  121. package/libx/_runtime/fiori/generic/new.js +5 -3
  122. package/libx/_runtime/fiori/generic/patch.js +11 -8
  123. package/libx/_runtime/fiori/generic/prepare.js +13 -6
  124. package/libx/_runtime/fiori/generic/read.js +12 -6
  125. package/libx/_runtime/fiori/lean-draft.js +207 -152
  126. package/libx/_runtime/fiori/utils/delete.js +10 -5
  127. package/libx/_runtime/fiori/utils/req.js +17 -5
  128. package/libx/_runtime/fiori/utils/stream.js +36 -0
  129. package/libx/_runtime/hana/Service.js +12 -9
  130. package/libx/_runtime/hana/conversion.js +10 -15
  131. package/libx/_runtime/hana/driver.js +2 -0
  132. package/libx/_runtime/hana/execute.js +28 -6
  133. package/libx/_runtime/hana/pool.js +36 -122
  134. package/libx/_runtime/hana/search2cqn4sql.js +34 -36
  135. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
  136. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
  137. package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
  138. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  139. package/libx/_runtime/remote/Service.js +20 -1
  140. package/libx/_runtime/remote/utils/client.js +3 -5
  141. package/libx/_runtime/sqlite/Service.js +4 -6
  142. package/libx/_runtime/sqlite/conversion.js +3 -13
  143. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
  144. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
  145. package/libx/_runtime/sqlite/execute.js +5 -16
  146. package/libx/odata/afterburner.js +22 -6
  147. package/libx/odata/grammar.pegjs +6 -1
  148. package/libx/odata/parser.js +1 -1
  149. package/libx/rest/RestAdapter.js +16 -9
  150. package/libx/rest/RestRequest.js +1 -1
  151. package/libx/rest/middleware/input.js +2 -1
  152. package/libx/rest/middleware/operation.js +1 -0
  153. package/libx/rest/middleware/parse.js +3 -2
  154. package/libx/rest/middleware/payload.js +9 -8
  155. package/libx/rest/middleware/read.js +1 -0
  156. package/package.json +9 -16
  157. package/app/fiori/preview.js +0 -270
  158. package/app/fiori/routes.js +0 -59
  159. package/bin/build/buildTaskEngine.js +0 -360
  160. package/bin/build/buildTaskFactory.js +0 -283
  161. package/bin/build/buildTaskHandler.js +0 -241
  162. package/bin/build/buildTaskProvider.js +0 -22
  163. package/bin/build/buildTaskProviderFactory.js +0 -175
  164. package/bin/build/cds.js +0 -5
  165. package/bin/build/constants.js +0 -66
  166. package/bin/build/index.js +0 -58
  167. package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
  168. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
  169. package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
  170. package/bin/build/provider/buildTaskProviderInternal.js +0 -383
  171. package/bin/build/provider/fiori/index.js +0 -171
  172. package/bin/build/provider/hana/2migration.js +0 -179
  173. package/bin/build/provider/hana/index.js +0 -505
  174. package/bin/build/provider/hana/migrationtable.js +0 -472
  175. package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
  176. package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
  177. package/bin/build/provider/hana/template/.hdinamespace +0 -4
  178. package/bin/build/provider/hana/template/package.json +0 -12
  179. package/bin/build/provider/hana/template/undeploy.json +0 -5
  180. package/bin/build/provider/java/index.js +0 -111
  181. package/bin/build/provider/java-cf/index.js +0 -1
  182. package/bin/build/provider/mtx/index.js +0 -268
  183. package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
  184. package/bin/build/provider/mtx-extension/index.js +0 -131
  185. package/bin/build/provider/mtx-sidecar/index.js +0 -137
  186. package/bin/build/provider/node-cf/index.js +0 -1
  187. package/bin/build/provider/nodejs/index.js +0 -192
  188. package/bin/build/util.js +0 -299
  189. package/bin/cds.js +0 -125
  190. package/bin/deploy/to-hana/cfUtil.js +0 -355
  191. package/bin/deploy/to-hana/gitUtil.js +0 -57
  192. package/bin/deploy/to-hana/hana.js +0 -306
  193. package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
  194. package/bin/deploy/to-hana/index.js +0 -16
  195. package/bin/deploy/to-hana/mtaUtil.js +0 -170
  196. package/bin/mtx/in-cds.js +0 -17
  197. package/bin/plugins.js +0 -32
  198. package/bin/run.js +0 -24
  199. package/bin/utils/log.js +0 -24
  200. package/bin/version.js +0 -178
  201. package/libx/_runtime/audit/Service.js +0 -222
  202. package/libx/_runtime/audit/generic/personal/access.js +0 -61
  203. package/libx/_runtime/audit/generic/personal/index.js +0 -56
  204. package/libx/_runtime/audit/generic/personal/modification.js +0 -132
  205. package/libx/_runtime/audit/generic/personal/utils.js +0 -186
  206. package/libx/_runtime/audit/utils/log.js +0 -23
  207. package/libx/_runtime/audit/utils/v2.js +0 -176
  208. package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
  209. package/libx/_runtime/db/generic/integrity.js +0 -455
  210. package/srv/audit-log.cds +0 -87
  211. package/srv/mtx.cds +0 -2
  212. package/srv/mtx.js +0 -8
  213. /package/lib/{core → linked}/classes.js +0 -0
  214. /package/lib/{core → linked}/entities.js +0 -0
@@ -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] || {};
@@ -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,7 +132,7 @@ 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'
135
+ this._done = 'committed'
128
136
  try {
129
137
  await this.context.emit ('commit',res) //> allow custom handlers req.before('commit')
130
138
  await super.commit (res)
@@ -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)
@@ -1,52 +1,51 @@
1
- const {createReadStream, createWriteStream, readFile:rf} = require('fs')
2
- const readFile = require('util').promisify(rf)
3
- const {Readable} = require('stream')
1
+ const { createReadStream, createWriteStream, promises: fsp } = require('fs')
2
+ const { Readable } = require('stream')
4
3
  const cds = require('../../lib')
5
4
 
6
5
  const SEPARATOR = /[,;\t]/
7
6
  module.exports = { parse: cds.parse.csv, readHeader, stripComments, serialize }
8
7
 
9
8
 
10
- function serialize (rows, columns, bom='\ufeff') {
11
- let csv = bom + ( columns || Object.keys(rows[0]) ).join(';') +"\n"
12
- for (let key in rows) csv += `${key};${rows[key]}\r\n`
9
+ function serialize(rows, columns, bom = '\ufeff') {
10
+ let csv = bom + (columns || Object.keys(rows[0])).join(';') + "\n"
11
+ for (let key in rows) csv += `${key};${rows[key]}\r\n`
13
12
  return csv
14
13
  }
15
14
 
16
- async function readHeader (inStream, o={ignoreComments:true}) {
15
+ async function readHeader(inStream, o = { ignoreComments: true }) {
17
16
  let delimiter = ';'
18
17
  let cols = []
19
18
  let filtered = false
20
- await _filterLines (inStream, null, (line, readLine) => {
19
+ await _filterLines(inStream, null, (line, readLine) => {
21
20
  if (!cols.length) {
22
21
  if (o.ignoreComments && _ignoreLine(line)) {
23
22
  filtered = true
24
23
  return false
25
24
  }
26
- [delimiter] = SEPARATOR.exec(line)||[';']
27
- cols = line.split(delimiter) .map (each => each.trim()) .filter(each=>each.length)
25
+ [delimiter] = SEPARATOR.exec(line) || [';']
26
+ cols = line.split(delimiter).map(each => each.trim()).filter(each => each.length)
28
27
  readLine.close() // signal that we have seen enough --> this only ends the readLine interface
29
28
  }
30
29
  return true
31
30
  })
32
31
 
33
32
  inStream.destroy() // destroy the stream to avoid leaks of file descriptors
34
- return {cols, delimiter, filtered}
33
+ return { cols, delimiter, filtered }
35
34
  }
36
35
 
37
36
  async function stripComments(file, outStream) {
38
37
  // most files don't need filtering, so do a quick check first
39
38
  const { filtered } = await readHeader(createReadStream(file))
40
- if (!filtered) return false
39
+ if (!filtered) return false
41
40
 
42
41
  // buffer whole content so that we can write the out file
43
- const inStream = Readable.from([await readFile(file)])
42
+ const inStream = Readable.from([await fsp.readFile(file)])
44
43
  // clears the output file
45
44
  outStream = outStream || createWriteStream(file)
46
45
  let prelude = true
47
- await _filterLines (inStream, outStream, line => {
46
+ await _filterLines(inStream, outStream, line => {
48
47
  if (prelude) {
49
- if (_ignoreLine(line)) return false
48
+ if (_ignoreLine(line)) return false
50
49
  prelude = false
51
50
  }
52
51
  // skip empty lines - HANA cannot handle them, e.g. at end of the file
@@ -59,14 +58,14 @@ function _ignoreLine(line) {
59
58
  return line[0] === '#' || !line.trim().length
60
59
  }
61
60
 
62
- function _filterLines (input, out, filter) {
63
- return new Promise((resolve, reject)=> {
64
- const rl = require('readline').createInterface({input, crlfDelay: Infinity})
61
+ function _filterLines(input, out, filter) {
62
+ return new Promise((resolve, reject) => {
63
+ const rl = require('readline').createInterface({ input, crlfDelay: Infinity })
65
64
  const resumeOnDrain = () => rl.resume()
66
65
  let filtered = false
67
- rl.on ('line', line => {
68
- if (filter (line, rl)) {
69
- if (out && !out.write (line+'\n')) {
66
+ rl.on('line', line => {
67
+ if (filter(line, rl)) {
68
+ if (out && !out.write(line + '\n')) {
70
69
  rl.pause() // pause when writable signals so
71
70
  out.removeListener('drain', resumeOnDrain) // avoid too many listeners
72
71
  out.once('drain', resumeOnDrain)
@@ -74,8 +73,8 @@ function _filterLines (input, out, filter) {
74
73
  }
75
74
  else filtered |= true
76
75
  })
77
- rl.on ('error', reject)
78
- rl.on ('close', () => out ? out.end() : resolve(filtered))
79
- if (out) out.on('finish', () => resolve(filtered))
76
+ rl.on('error', reject)
77
+ rl.on('close', () => out ? out.end() : resolve(filtered))
78
+ if (out) out.on('finish', () => resolve(filtered))
80
79
  })
81
80
  }