@sap/cds 6.0.4 → 6.1.2

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 (139) hide show
  1. package/CHANGELOG.md +180 -18
  2. package/apis/cds.d.ts +11 -7
  3. package/apis/log.d.ts +124 -0
  4. package/apis/ql.d.ts +72 -15
  5. package/apis/services.d.ts +13 -2
  6. package/bin/build/buildTaskHandler.js +5 -2
  7. package/bin/build/constants.js +4 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
  9. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
  10. package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
  11. package/bin/build/provider/buildTaskProviderInternal.js +22 -14
  12. package/bin/build/provider/hana/index.js +12 -9
  13. package/bin/build/provider/java/index.js +18 -8
  14. package/bin/build/provider/mtx/index.js +7 -4
  15. package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
  16. package/bin/build/provider/mtx-extension/index.js +57 -0
  17. package/bin/build/provider/mtx-sidecar/index.js +46 -18
  18. package/bin/build/provider/nodejs/index.js +34 -13
  19. package/bin/deploy/to-hana/cfUtil.js +7 -2
  20. package/bin/deploy/to-hana/hana.js +20 -25
  21. package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
  22. package/bin/serve.js +7 -4
  23. package/lib/compile/{index.js → cds-compile.js} +0 -0
  24. package/lib/compile/extend.js +15 -5
  25. package/lib/compile/minify.js +1 -15
  26. package/lib/compile/parse.js +1 -1
  27. package/lib/compile/resolve.js +2 -2
  28. package/lib/compile/to/srvinfo.js +6 -4
  29. package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
  30. package/lib/env/{index.js → cds-env.js} +1 -17
  31. package/lib/env/{requires.js → cds-requires.js} +24 -3
  32. package/lib/env/defaults.js +7 -1
  33. package/lib/env/schemas/cds-package.json +11 -0
  34. package/lib/env/schemas/cds-rc.json +614 -0
  35. package/lib/index.js +19 -16
  36. package/lib/log/{errors.js → cds-error.js} +1 -1
  37. package/lib/log/{index.js → cds-log.js} +0 -0
  38. package/lib/log/format/kibana.js +19 -1
  39. package/lib/ql/Query.js +9 -3
  40. package/lib/ql/SELECT.js +2 -2
  41. package/lib/ql/UPDATE.js +2 -2
  42. package/lib/ql/{index.js → cds-ql.js} +4 -10
  43. package/lib/req/context.js +49 -17
  44. package/lib/req/locale.js +5 -1
  45. package/lib/{serve → srv}/adapters.js +23 -19
  46. package/lib/{connect → srv}/bindings.js +0 -0
  47. package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
  48. package/lib/{serve/index.js → srv/cds-serve.js} +0 -0
  49. package/lib/{serve → srv}/factory.js +1 -1
  50. package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
  51. package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
  52. package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
  53. package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
  54. package/lib/srv/srv-models.js +207 -0
  55. package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
  56. package/lib/utils/{tests.js → cds-test.js} +2 -2
  57. package/lib/utils/cds-utils.js +146 -0
  58. package/lib/utils/index.js +2 -145
  59. package/lib/utils/jest.js +43 -0
  60. package/lib/utils/resources/index.js +15 -25
  61. package/lib/utils/resources/tar.js +18 -41
  62. package/libx/_runtime/auth/index.js +14 -11
  63. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
  64. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +3 -5
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
  74. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
  75. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
  76. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
  77. package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
  78. package/libx/_runtime/cds-services/util/errors.js +1 -29
  79. package/libx/_runtime/common/i18n/messages.properties +2 -1
  80. package/libx/_runtime/common/perf/index.js +10 -15
  81. package/libx/_runtime/common/utils/binary.js +3 -4
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
  83. package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
  84. package/libx/_runtime/common/utils/keys.js +14 -6
  85. package/libx/_runtime/common/utils/resolveView.js +1 -1
  86. package/libx/_runtime/common/utils/template.js +1 -1
  87. package/libx/_runtime/db/Service.js +2 -14
  88. package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
  89. package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
  90. package/libx/_runtime/db/generic/input.js +8 -1
  91. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  92. package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
  93. package/libx/_runtime/extensibility/activate.js +47 -47
  94. package/libx/_runtime/extensibility/add.js +22 -13
  95. package/libx/_runtime/extensibility/addExtension.js +17 -13
  96. package/libx/_runtime/extensibility/defaults.js +25 -30
  97. package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
  98. package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
  99. package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
  100. package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
  101. package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
  102. package/libx/_runtime/extensibility/linter.js +32 -0
  103. package/libx/_runtime/extensibility/push.js +77 -20
  104. package/libx/_runtime/extensibility/service.js +29 -12
  105. package/libx/_runtime/extensibility/token.js +57 -0
  106. package/libx/_runtime/extensibility/utils.js +8 -6
  107. package/libx/_runtime/extensibility/validation.js +6 -9
  108. package/libx/_runtime/fiori/generic/new.js +0 -11
  109. package/libx/_runtime/fiori/utils/where.js +1 -1
  110. package/libx/_runtime/hana/Service.js +0 -1
  111. package/libx/_runtime/hana/conversion.js +12 -1
  112. package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
  113. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
  114. package/libx/_runtime/hana/pool.js +6 -10
  115. package/libx/_runtime/hana/search2Contains.js +0 -5
  116. package/libx/_runtime/hana/search2cqn4sql.js +1 -0
  117. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  118. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
  119. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  120. package/libx/_runtime/messaging/service.js +11 -6
  121. package/libx/_runtime/remote/utils/data.js +5 -0
  122. package/libx/_runtime/sqlite/Service.js +7 -6
  123. package/libx/_runtime/sqlite/execute.js +41 -28
  124. package/libx/odata/afterburner.js +79 -2
  125. package/libx/odata/cqn2odata.js +15 -9
  126. package/libx/odata/grammar.pegjs +157 -76
  127. package/libx/odata/index.js +9 -3
  128. package/libx/odata/parser.js +1 -1
  129. package/libx/odata/utils.js +39 -5
  130. package/libx/rest/RestAdapter.js +3 -7
  131. package/libx/rest/middleware/delete.js +4 -5
  132. package/libx/rest/middleware/parse.js +3 -2
  133. package/package.json +3 -3
  134. package/server.js +1 -1
  135. package/srv/extensibility-service.cds +6 -3
  136. package/srv/model-provider.cds +3 -1
  137. package/srv/model-provider.js +86 -106
  138. package/srv/mtx.js +7 -1
  139. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
@@ -0,0 +1,207 @@
1
+ // REVISIT: all handler caches (incl. templates) need to go into cached models -> eviction
2
+ // REVISIT: all edmx caches also have to be hooked in here
3
+
4
+ const cds = require ('../index')
5
+ const LOG = cds.log()
6
+
7
+ const {normalizeError} = require('../../libx/_runtime/common/error/frontend')
8
+ const getError = require('../../libx/_runtime/common/error')
9
+
10
+ /**
11
+ * Implements a static cache for all tenant/features-specific models.
12
+ * Cache keys are strings of the form `<tenant>:<comma-separated-features>`.
13
+ * The base model `cds.model` is also in the cache with key `':'`, i.e.,
14
+ * with undefined tenant and no activated features.
15
+ */
16
+ class ExtendedModels {
17
+
18
+ /**
19
+ * Returns an express middleware to be used with the given srv to set cds.context.model.
20
+ * Uses `this.model4(...)` to get the respective tenant/features-specific models.
21
+ * @returns {(req,res)=>{}}
22
+ */
23
+ static middleware4 (srv) {
24
+ if (!(srv instanceof cds.ApplicationService)) return [] //> no middleware to add // REVISIT: move to `srv.isExtensible`
25
+ if (!srv.isExtensible) return [] //> no middleware to add
26
+ else return async function cds_context_model (req,res,next) {
27
+ if (!req.user?.tenant) return next()
28
+ const ctx = cds.context = cds.EventContext.for({ http: { req, res } })
29
+ ctx.user = req.user // REVISIT: should move to auth middleware?
30
+ try {
31
+ ctx.model = req.__model = await ExtendedModels.model4 (ctx.tenant, ctx.features)
32
+ } catch (e) {
33
+ LOG.error (Object.assign(e, { message: 'Unable to get service from service map due to error: ' + e.message }))
34
+
35
+ // return 503 to client
36
+ // REVISIT: Error handling!
37
+ const { error } = normalizeError(getError(Object.assign(e, { statusCode: 503 })), req)
38
+
39
+ return res.status(503).json({ error })
40
+ }
41
+ next()
42
+ }
43
+ }
44
+
45
+
46
+ /**
47
+ * Returns the model to use for given tenant and features.
48
+ * Loaded models are cached, with eviction on inactivity of tenants,
49
+ * and automatically refreshed when new extensions are made.
50
+ * @returns a CSN compiled.for.nodejs and cds.linked
51
+ */
52
+ static async model4 (tenant, features) {
53
+
54
+ const {cache} = ExtendedModels, key = cache.key4 (tenant, features)
55
+ const cached = cache.at(key); if (cached) return cached
56
+ if (key === ':') return cache.add (':', cds.compile.for.nodejs(cds.model))
57
+ else return cache[key] = (async()=>{ // temporarily add promise to cache to avoid race conditions...
58
+
59
+ // If tenant doesn't have extensions check cache with tenant = undefined
60
+ const _has_extensions = tenant && extensibility && await _is_extended(tenant)
61
+ if (!_has_extensions) {
62
+ let k = cache.key4 (tenant = undefined, features)
63
+ let cached = cache.at(k); if (cached) return cached
64
+ else if (k === ':') return cache.add (':', cds.compile.for.nodejs(cds.model))
65
+ }
66
+
67
+ // None cached -> obtain and cache specific model from ModelProvider
68
+ return await _get_model4 (tenant, Object.keys(features))
69
+
70
+ })()
71
+ .then (m => cache.add(key,m)) // replace promise in cache by real model
72
+ .catch (e => { delete cache[key]; throw e })
73
+ }
74
+
75
+
76
+ /**
77
+ * Constructs and returns a cache key for given tenant and features.
78
+ * @param {string} tenant string or `undefined` as obtained from `cds.context.tenant`
79
+ * @param {object} features object as obtained from `cds.context.features`
80
+ * @returns {string} of the form `<tenant>:<comma-separated-features>`
81
+ */
82
+ key4 (tenant, features) {
83
+ return `${tenant||''}:${features.$hash}`
84
+ }
85
+
86
+
87
+ /**
88
+ * Returns the currently cached model, or a promised one.
89
+ * Promises are added to the cache to avoid race conditions with parallel requests.
90
+ * This implementation regularly checks for new extensions, and transparently
91
+ * refreshes cached models if so.
92
+ * This method is overridden with a simple `return this[key]` when extensibility
93
+ * is switched off.
94
+ * @param {string} key as obtained through `cache.key4(t,f)`
95
+ * @returns { LinkedCSN | Promise<LinkedCSN> }
96
+ */
97
+ at (key) {
98
+ const model = this[key]; if (!model) return
99
+ if (model.then) return model //> promised model to avoid race conditions
100
+
101
+ const {_cached} = model, interval = ExtendedModels.checkInterval
102
+ if (!_cached.touched) return model
103
+ if (Date.now() - _cached.touched < interval) return model //> checked recently
104
+
105
+ else return this[key] = (async()=>{ // temporarily replace cache entry by promise to avoid race conditions...
106
+
107
+ const has_new_extensions = await cds.db.exists('cds.xt.Extensions') .where ({
108
+ timestamp: { '>': new Date(_cached.touched).toISOString() } // REVISIT: better store epoc time in db?
109
+ // REVISIT: GAP: CAP runtime should allow Date objects + Date.now() for all date+time types !
110
+ })
111
+ if (has_new_extensions) { // new extensions arrived -> refresh model in cache
112
+ let [ tenant = undefined, toggles ] = key.split(':')
113
+ return _get_model4 (tenant, toggles.split(','))
114
+ } else { // no new extensions...
115
+ _cached.touched = Date.now() // check again in 1 min or so
116
+ return model // keep cached model in cache
117
+ }
118
+
119
+ })()
120
+ .then (m => this.add(key,m)) // replace promise in cache by real model
121
+ .catch (e => { delete this[key]; throw e })
122
+ }
123
+
124
+
125
+ /**
126
+ * Adds a model into the cache under the given key.
127
+ * Only use that method to add loaded models, while using direct assignment
128
+ * to add promises, e.g. `cache[key] = promised_model`.
129
+ * @param {string} key as obtained through `cache.key4(t,f)`
130
+ * @param {LinkedCSN} model the loaded and linked model
131
+ * @returns the given `model`
132
+ */
133
+ add (key, model, touched = Date.now()) {
134
+ if (model) {
135
+ if (!model._cached) Object.defineProperty (model,'_cached',{ value:{key,touched} })
136
+ return this[key] = model
137
+ }
138
+ }
139
+
140
+
141
+ /**
142
+ * When started, regularly evicts models for inactive tenants.
143
+ */
144
+ startSentinel(){
145
+ this.sentinel = setInterval (()=>{ for (let [key,m] of Object.entries(this)) {
146
+ if (!m._cached) continue // `m` can also be `this.sentinel`
147
+ if (Date.now() - m._cached.touched > ExtendedModels.sentinelInterval) {
148
+ delete this [key]
149
+ }
150
+ }}, ExtendedModels.sentinelInterval)
151
+ cds.on('shutdown', ()=> clearInterval(this.sentinel))
152
+ }
153
+
154
+
155
+ /** The cache instance used by `middleware4()` and `model4()`. */
156
+ static cache = new ExtendedModels
157
+
158
+ /** Time interval in ms to check for new extensions and refresh models, if so. */
159
+ static checkInterval = cds.requires.extensibility?.tenantCheckInterval || cds.mtx?.tenantCheckInterval || 1 * 60 * 1000
160
+
161
+ /** Time interval in ms after which to evict models for inactive tenants. */
162
+ static sentinelInterval = cds.requires.extensibility?.evictionInterval || 3600*1000
163
+
164
+ }
165
+ module.exports = ExtendedModels
166
+
167
+
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // Support for old MTX
171
+
172
+ const old_mtx = cds.mtx
173
+ if (old_mtx) {
174
+ if (!cds.requires.extensibility) cds.requires.extensibility = true // REVISIT: extensibility was always true in old MTX?
175
+ ExtendedModels.prototype.at = function (key) { return this[key] }
176
+ old_mtx.eventEmitter.on (old_mtx.events.TENANT_UPDATED, async (tenant='') => {
177
+ delete ExtendedModels.cache [tenant+':']
178
+ })
179
+ }
180
+
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Optimizations for single-tenancy modes
184
+
185
+ const extensibility = cds.requires.extensibility
186
+ if (!extensibility) {
187
+ ExtendedModels.prototype.at = function (key) { return this[key] }
188
+ if (!cds.requires.toggles) ExtendedModels.middleware4 = ()=> []
189
+ }
190
+
191
+
192
+ // helper to get model for tenant/features
193
+ const _is_extended = old_mtx ? t => old_mtx.isExtended(t) : extensibility ? ()=> cds.db.exists('cds.xt.Extensions') : ()=> false
194
+ const _get_model4 = old_mtx ? async (tenant) => {
195
+ const isExtended = tenant && await old_mtx.isExtended(tenant) // REVISIT: avoid await
196
+ if (isExtended) return old_mtx.getCsn(tenant) .then (cds.compile.for.nodejs)
197
+ } : (tenant, toggles) => {
198
+ const { 'cds.xt.ModelProviderService':mps } = cds.services
199
+ return mps.getCsn (tenant, toggles) .then (cds.compile.for.nodejs)
200
+ }
201
+
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // Optimizations for single-tenancy modes
205
+
206
+ if (cds.requires.multitenancy && typeof global.it === 'undefined') ExtendedModels.cache.startSentinel()
207
+ // REVISIT: how to do ^that^ correctly with jest?
@@ -1,42 +1,47 @@
1
1
  const cds = require('../index'), { cds_tx_protection } = cds.env.features
2
2
  const EventContext = require('../req/context')
3
- class RootContext extends EventContext {}
3
+ class RootContext extends EventContext {
4
+ static for(_) {
5
+ if (_ instanceof EventContext) return _
6
+ else return super.for(_,'as root')
7
+ }
8
+ }
9
+ class NestedContext extends EventContext {
10
+ static for(_) {
11
+ if (_ instanceof EventContext) return _
12
+ else return super.for(_)
13
+ }
14
+ }
15
+
4
16
 
5
17
  /**
6
18
  * This is the implementation of the `srv.tx(req)` method. It constructs
7
19
  * a new Transaction as a derivate of the `srv` (i.e. {__proto__:srv})
8
- * @returns { Transaction & import('./Service-api') }
20
+ * @returns { Transaction & import('./srv-api') }
9
21
  * @param { EventContext } ctx
10
22
  */
11
- module.exports = function tx (ctx,fn) { const srv = this
23
+ function srv_tx (ctx,fn) { const srv = this
12
24
 
13
25
  if (srv.context) return srv // srv.tx().tx() -> idempotent
26
+ if (!ctx) return RootTransaction.for (srv)
27
+
28
+ // Creating root or nested txes for existing contexts
29
+ if (ctx instanceof EventContext) {
30
+ if (ctx.tx) return NestedTransaction.for (srv, ctx)
31
+ else return RootTransaction.for (srv, ctx)
32
+ }
14
33
 
15
34
  // Last arg may be a function -> srv.tx (tx => { ... })
16
35
  if (typeof ctx === 'function') [ ctx, fn ] = [ undefined, ctx ]
17
36
  if (typeof fn === 'function') {
18
- const tx = srv.tx(ctx), fx = ()=> Promise.resolve(fn(tx)).then(tx.commit,tx.rollback)
19
- const gc = cds.context, _has_tx = gc && gc.tx && !gc.tx._done
20
- return _has_tx ? fx() : cds._context.run(tx,fx)
21
- }
22
-
23
- if (ctx) {
24
- if (ctx.context) ctx = ctx.context
25
-
26
- // REVISIT: This is for compatibility with former srv.tx(req) usages
27
- if (ctx instanceof EventContext) {
28
- if (ctx.tx) return NestedTransaction.for (srv, ctx)
29
- else return RootTransaction.for (srv, ctx)
30
- }
31
-
32
- // REVISIT: This is for compatibility with AFC only
33
- if (ctx._txed_before) return NestedTransaction.for (srv, ctx._txed_before)
34
- else Object.defineProperty(ctx, '_txed_before', { value: ctx = RootContext.for(ctx) })
35
- return RootTransaction.for (srv, ctx)
37
+ const tx = RootTransaction.for (srv, ctx)
38
+ return cds._context.run (tx, ()=> Promise.resolve(fn(tx)) .then (tx.commit, tx.rollback))
36
39
  }
37
40
 
38
- // `ctx` is a plain context object or undefined
39
- return RootTransaction.for (srv, RootContext.for(ctx))
41
+ // REVISIT: following is for compatibility with AFC only -> we should get rid of that
42
+ if (ctx._txed_before) return NestedTransaction.for (srv, ctx._txed_before)
43
+ else Object.defineProperty (ctx, '_txed_before', { value: ctx = RootContext.for(ctx) })
44
+ return RootTransaction.for (srv, ctx)
40
45
  }
41
46
 
42
47
 
@@ -45,19 +50,23 @@ class Transaction {
45
50
  /**
46
51
  * Returns an already started tx for given srv, or creates a new instance
47
52
  */
48
- static for (srv,root) {
49
- let txs = root.transactions
50
- if (!txs) Object.defineProperty(root, 'transactions', {value: txs = new Map})
53
+ static for (srv, ctx) {
54
+ const txs = ctx.context.transactions || ctx.context._set ('transactions', new Map)
55
+ if (srv._real_srv) srv = srv._real_srv // REVISIT: srv._real_srv is a dirty hack for current Okra Adapters
51
56
  let tx = txs.get (srv)
52
- if (!tx) txs.set (srv, tx = new this (srv,root))
57
+ if (!tx) txs.set (srv, tx = new this (srv,ctx))
53
58
  return tx
54
59
  }
55
60
 
56
- constructor (srv,root) {
57
- const tx = { __proto__:srv, context:root }
61
+ constructor (srv, ctx) {
62
+ const tx = { __proto__:srv, _kind: new.target.name, context: ctx }
58
63
  const proto = new.target.prototype
59
64
  tx.commit = proto.commit.bind(tx)
60
65
  tx.rollback = proto.rollback.bind(tx)
66
+ if (srv.isExtensible) {
67
+ const m = cds.context?.model
68
+ if (m) tx.model = m
69
+ }
61
70
  return _init(tx)
62
71
  }
63
72
 
@@ -102,11 +111,12 @@ class Transaction {
102
111
  class RootTransaction extends Transaction {
103
112
 
104
113
  /**
105
- * Register the new transaction with the root context.
106
- * @param {EventContext} root
114
+ * Register the new transaction with the given context.
115
+ * @param {EventContext} ctx
107
116
  */
108
- static for (srv,root) {
109
- return root.tx = super.for (srv,root)
117
+ static for (srv, ctx) {
118
+ ctx = RootContext.for (ctx?.tx?._done ? {} : ctx)
119
+ return ctx.tx = super.for (srv, ctx)
110
120
  }
111
121
 
112
122
  /**
@@ -148,16 +158,21 @@ class RootTransaction extends Transaction {
148
158
 
149
159
  class NestedTransaction extends Transaction {
150
160
 
161
+ static for (srv,ctx) {
162
+ ctx = NestedContext.for (ctx)
163
+ return super.for (srv, ctx)
164
+ }
165
+
151
166
  /**
152
- * Registers event listeners with the root context, to commit or rollback
167
+ * Registers event listeners with the given context, to commit or rollback
153
168
  * when the root tx is about to commit or rollback.
154
- * @param {import ('../req/context')} root
169
+ * @param {EventContext} ctx
155
170
  */
156
- constructor (srv,root) {
157
- super (srv,root)
158
- root.before ('succeeded', ()=> this.commit())
159
- root.before ('failed', ()=> this.rollback())
160
- if ('end' in srv) root.once ('done', ()=> srv.end())
171
+ constructor (srv,ctx) {
172
+ super (srv,ctx)
173
+ ctx.before ('succeeded', ()=> this.commit())
174
+ ctx.before ('failed', ()=> this.rollback())
175
+ if ('end' in srv) ctx.once ('done', ()=> srv.end())
161
176
  }
162
177
 
163
178
  }
@@ -188,3 +203,5 @@ const _begin = async function (req) {
188
203
  delete this.dispatch
189
204
  return this.dispatch (req)
190
205
  }
206
+
207
+ module.exports = srv_tx
@@ -25,7 +25,7 @@ class Test extends require('./axios') {
25
25
  this.server = server
26
26
  this.url = url
27
27
  })
28
- try { return cds.exec (cmd, ...args, '--port','0') }
28
+ try { return cds.exec (cmd, ...args, ...(args.includes('--port') ? [] : ['--port', '0'])) }
29
29
  catch (e) { if (is_mocha) console.error(e) } // eslint-disable-line no-console
30
30
  })
31
31
 
@@ -97,7 +97,7 @@ function support_jest_and_mocha() {
97
97
  }
98
98
  after(()=>{
99
99
  delete global.cds
100
- for (let k in require.cache) delete require.cache[k]
100
+ for (let k in require.cache) delete require.cache[k] // REVISIT: Whay are we doing that?
101
101
  })
102
102
  } else if (is_jest) { // it's jest
103
103
  global.before = (msg,fn) => global.beforeAll(fn||msg)
@@ -0,0 +1,146 @@
1
+ const cwd = process.env._original_cwd || process.cwd()
2
+ const path = require ('path'), { dirname, extname, join, resolve, relative } = path
3
+ const fs = require('fs')
4
+ const cds = require('../index')
5
+
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
+ }
10
+
11
+ exports.fs = all
12
+ exports.path = path
13
+
14
+ exports.local = (file) => relative(cwd,file)
15
+
16
+ exports.readdir = async function (x) {
17
+ const d = resolve (cds.root,x)
18
+ return fs.promises.readdir(d)
19
+ }
20
+
21
+ exports.stat = async function (x) {
22
+ const d = resolve (cds.root,x)
23
+ return fs.promises.stat(d)
24
+ }
25
+
26
+ exports.exists = function exists (x) {
27
+ if (x) {
28
+ const y = resolve (cds.root,x)
29
+ return fs.existsSync(y)
30
+ }
31
+ }
32
+
33
+ exports.isdir = function isdir (x) {
34
+ if (x) try {
35
+ const y = resolve (cds.root,x)
36
+ const ls = fs.lstatSync(y)
37
+ if (ls.isDirectory()) return y
38
+ if (ls.isSymbolicLink()) return isdir (join (dirname(y), fs.readlinkSync(y)))
39
+ } catch(e){/* ignore */}
40
+ }
41
+
42
+ exports.isfile = function isfile (x) {
43
+ if (x) try {
44
+ const y = resolve (cds.root,x)
45
+ const ls = fs.lstatSync(y)
46
+ if (ls.isFile()) return y
47
+ if (ls.isSymbolicLink()) return isfile (join (dirname(y), fs.readlinkSync(y)))
48
+ } catch(e){/* ignore */}
49
+ }
50
+
51
+ exports.read = async function read (file, _encoding) {
52
+ const f = resolve (cds.root,file)
53
+ const src = await fs.promises.readFile (f, _encoding !== 'json' && _encoding || 'utf8')
54
+ return _encoding === 'json' || !_encoding && f.endsWith('.json') ? JSON.parse(src) : src
55
+ }
56
+
57
+ exports.write = function write (file, data, o) {
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))
61
+ const f = resolve (cds.root,file)
62
+ return mkdirp (dirname(f)).then (()=> fs.promises.writeFile (f,data,o))
63
+ }
64
+
65
+ exports.mkdirp = async function (...path) {
66
+ const d = resolve (cds.root,...path)
67
+ await fs.promises.mkdir (d,{recursive:true})
68
+ return d
69
+ }
70
+
71
+ exports.rmdir = (...path) => {
72
+ const d = resolve (cds.root,...path)
73
+ return fs.promises.rm (d, {recursive:true})
74
+ }
75
+
76
+ exports.rimraf = (...path) => {
77
+ const d = resolve (cds.root,...path)
78
+ return fs.promises.rm (d, {recursive:true,force:true})
79
+ }
80
+
81
+ exports.rm = async function rm (x) {
82
+ const y = resolve (cds.root,x)
83
+ return fs.promises.rm(y)
84
+ }
85
+
86
+ exports.copy = async function copy (x,y) {
87
+ const src = resolve (cds.root,x)
88
+ const dst = resolve (cds.root,y)
89
+ if (fs.promises.cp) return fs.promises.cp (src,dst,{recursive:true})
90
+ await mkdirp (dirname(dst))
91
+ if (isdir(src)) {
92
+ const entries = await fs.promises.readdir(src)
93
+ return Promise.all (entries.map (async each => {
94
+ const e = join (src,each)
95
+ const f = join (dst,each)
96
+ return copy (e,f)
97
+ }))
98
+ } else {
99
+ return fs.promises.copyFile (src,dst)
100
+ }
101
+ }
102
+
103
+ exports.find = function find (base, patterns='*', filter=()=>true) {
104
+ const files=[]; base = resolve (cds.root,base)
105
+ if (typeof patterns === 'string') patterns = patterns.split(',')
106
+ if (typeof filter === 'string') filter = this[filter]
107
+ patterns.forEach (pattern => {
108
+ const star = pattern.indexOf('*')
109
+ if (star >= 0) {
110
+ const head = pattern.slice(0,star).replace(/[^/\\]*$/,'')
111
+ const dir = join (base,head)
112
+ try {
113
+ const ls = fs.lstatSync(dir)
114
+ if (ls.isDirectory()) {
115
+ const [,suffix,tail] = /([^/\\]*)?(?:.(.*))?/.exec (pattern.slice(star+1))
116
+ const prefix = pattern.slice(head.length,star)
117
+ let entries = fs.readdirSync(dir) //.filter (_filter)
118
+ if (prefix) entries = entries.filter (e => e.startsWith(prefix)); if (!entries.length) return
119
+ if (suffix) entries = entries.filter (e => e.endsWith(suffix)); if (!entries.length) return
120
+ let paths = entries.map (e=>join(dir,e))
121
+ if (filter) paths = paths.filter (filter); if (!paths.length) return
122
+ if (tail) for (let _files of paths.map (e=>find (e,tail,filter))) files.push (..._files)
123
+ else files.push (...paths)
124
+ }
125
+ } catch(e) {/* ignore */}
126
+ } else {
127
+ const file = join (base, pattern)
128
+ if (fs.existsSync(file)) files.push (file)
129
+ }
130
+ })
131
+ return files
132
+ }
133
+
134
+
135
+ // internal utility to load a file through ESM or CommonJs. TODO find a better place.
136
+ const { pathToFileURL } = require('url')
137
+ exports._import = async function(filePath) {
138
+ return typeof jest !== 'undefined' || extname(filePath) === '.ts' // ts-node w/ ESM not working (cap/issues#11980)
139
+ ? require(filePath)
140
+ : import (pathToFileURL(filePath).href) // must use a file: URL, esp. on Windows for C:\... paths
141
+ }
142
+
143
+
144
+ /** @type <T>(o,p,v:T) => T */
145
+ const $set = (o,p,v) => { Object.defineProperty(o,p,{value:v}); return v }
146
+ const { mkdirp, isdir } = exports
@@ -1,145 +1,2 @@
1
- const cwd = process.env._original_cwd || process.cwd()
2
- const path = require ('path'), { dirname, extname, join, resolve, sep, relative } = path
3
- const fs = require('fs')
4
- const cds = require('../index')
5
-
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
- }
10
-
11
- exports.fs = all
12
- exports.path = path
13
-
14
- exports.local = (file) => relative(cwd,file)
15
-
16
- exports.readdir = async function (x) {
17
- const d = resolve (cds.root,x)
18
- return fs.promises.readdir(d)
19
- }
20
-
21
- exports.stat = async function (x) {
22
- const d = resolve (cds.root,x)
23
- return fs.promises.stat(d)
24
- }
25
-
26
- exports.exists = function exists (x) {
27
- if (x) {
28
- const y = resolve (cds.root,x)
29
- return fs.existsSync(y)
30
- }
31
- }
32
-
33
- exports.isdir = function isdir (x) {
34
- if (x) try {
35
- const y = resolve (cds.root,x)
36
- const ls = fs.lstatSync(y)
37
- if (ls.isDirectory()) return y
38
- if (ls.isSymbolicLink()) return isdir (join (dirname(y), fs.readlinkSync(y)))
39
- } catch(e){/* ignore */}
40
- }
41
-
42
- exports.isfile = function isfile (x) {
43
- if (x) try {
44
- const y = resolve (cds.root,x)
45
- const ls = fs.lstatSync(y)
46
- if (ls.isFile()) return y
47
- if (ls.isSymbolicLink()) return isfile (join (dirname(y), fs.readlinkSync(y)))
48
- } catch(e){/* ignore */}
49
- }
50
-
51
- exports.read = async function read (file, _encoding) {
52
- const f = resolve (cds.root,file)
53
- const src = await fs.promises.readFile (f, _encoding !== 'json' && _encoding || 'utf8')
54
- return _encoding === 'json' || !_encoding && f.endsWith('.json') ? JSON.parse(src) : src
55
- }
56
-
57
- exports.write = function write (file, data, o) {
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))
61
- const f = resolve (cds.root,file)
62
- return mkdirp (dirname(f)).then (()=> fs.promises.writeFile (f,data,o))
63
- }
64
-
65
- exports.mkdirp = async function (...path) {
66
- const d = resolve (cds.root,...path)
67
- await fs.promises.mkdir (d,{recursive:true})
68
- return d
69
- }
70
-
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})
79
- }
80
-
81
- exports.rm = async function rm (x) {
82
- const y = resolve (cds.root,x)
83
- return (fs.promises.rm || fs.promises.unlink)(y)
84
- }
85
-
86
- exports.copy = async function copy (x,y) {
87
- const src = resolve (cds.root,x)
88
- const dst = resolve (cds.root,y)
89
- if (fs.promises.cp) return fs.promises.cp (src,dst,{recursive:true})
90
- await mkdirp (dirname(dst))
91
- if (isdir(src)) {
92
- const entries = await fs.promises.readdir(src)
93
- return Promise.all (entries.map (async each => {
94
- const e = join (src,each)
95
- const f = join (dst,each)
96
- return copy (e,f)
97
- }))
98
- } else {
99
- return fs.promises.copyFile (src,dst)
100
- }
101
- }
102
-
103
- exports.find = function find (base, patterns='*', filter=()=>true) {
104
- const files=[]; base = resolve (cds.root,base)
105
- if (typeof patterns === 'string') patterns = patterns.split(',')
106
- if (typeof filter === 'string') filter = this[filter]
107
- patterns.forEach (pattern => {
108
- const star = pattern.indexOf('*')
109
- if (star >= 0) {
110
- const head = pattern.slice(0,star).replace(/[^/\\]*$/,'')
111
- const dir = join (base,head)
112
- try {
113
- const ls = fs.lstatSync(dir)
114
- if (ls.isDirectory()) {
115
- const [,suffix,tail] = /([^/\\]*)?(?:.(.*))?/.exec (pattern.slice(star+1))
116
- const prefix = pattern.slice(head.length,star)
117
- let entries = fs.readdirSync(dir) //.filter (_filter)
118
- if (prefix) entries = entries.filter (e => e.startsWith(prefix)); if (!entries.length) return
119
- if (suffix) entries = entries.filter (e => e.endsWith(suffix)); if (!entries.length) return
120
- let paths = entries.map (e=>join(dir,e))
121
- if (filter) paths = paths.filter (filter); if (!paths.length) return
122
- if (tail) for (let _files of paths.map (e=>find (e,tail,filter))) files.push (..._files)
123
- else files.push (...paths)
124
- }
125
- } catch(e) {/* ignore */}
126
- } else {
127
- const file = join (base, pattern)
128
- if (fs.existsSync(file)) files.push (file)
129
- }
130
- })
131
- return files
132
- }
133
-
134
- // internal utility to load a file through ESM or CommonJs. TODO find a better place.
135
- const { pathToFileURL } = require('url')
136
- exports._import = async function(filePath) {
137
- return typeof jest !== 'undefined' || extname(filePath) === '.ts' // ts-node w/ ESM not working (cap/issues#11980)
138
- ? require(filePath)
139
- : import (pathToFileURL(filePath).href) // on Windows, must use a file: URL for ESM import
140
- }
141
-
142
-
143
- /** @type <T>(o,p,v:T) => T */
144
- const $set = (o,p,v) => { Object.defineProperty(o,p,{value:v}); return v }
145
- const { mkdirp, isdir } = exports
1
+ // for compatibility with old releases of cds-lint
2
+ module.exports = require('./cds-utils')