@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.
- package/CHANGELOG.md +180 -18
- package/apis/cds.d.ts +11 -7
- package/apis/log.d.ts +124 -0
- package/apis/ql.d.ts +72 -15
- package/apis/services.d.ts +13 -2
- package/bin/build/buildTaskHandler.js +5 -2
- package/bin/build/constants.js +4 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
- package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
- package/bin/build/provider/buildTaskProviderInternal.js +22 -14
- package/bin/build/provider/hana/index.js +12 -9
- package/bin/build/provider/java/index.js +18 -8
- package/bin/build/provider/mtx/index.js +7 -4
- package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
- package/bin/build/provider/mtx-extension/index.js +57 -0
- package/bin/build/provider/mtx-sidecar/index.js +46 -18
- package/bin/build/provider/nodejs/index.js +34 -13
- package/bin/deploy/to-hana/cfUtil.js +7 -2
- package/bin/deploy/to-hana/hana.js +20 -25
- package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
- package/bin/serve.js +7 -4
- package/lib/compile/{index.js → cds-compile.js} +0 -0
- package/lib/compile/extend.js +15 -5
- package/lib/compile/minify.js +1 -15
- package/lib/compile/parse.js +1 -1
- package/lib/compile/resolve.js +2 -2
- package/lib/compile/to/srvinfo.js +6 -4
- package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
- package/lib/env/{index.js → cds-env.js} +1 -17
- package/lib/env/{requires.js → cds-requires.js} +24 -3
- package/lib/env/defaults.js +7 -1
- package/lib/env/schemas/cds-package.json +11 -0
- package/lib/env/schemas/cds-rc.json +614 -0
- package/lib/index.js +19 -16
- package/lib/log/{errors.js → cds-error.js} +1 -1
- package/lib/log/{index.js → cds-log.js} +0 -0
- package/lib/log/format/kibana.js +19 -1
- package/lib/ql/Query.js +9 -3
- package/lib/ql/SELECT.js +2 -2
- package/lib/ql/UPDATE.js +2 -2
- package/lib/ql/{index.js → cds-ql.js} +4 -10
- package/lib/req/context.js +49 -17
- package/lib/req/locale.js +5 -1
- package/lib/{serve → srv}/adapters.js +23 -19
- package/lib/{connect → srv}/bindings.js +0 -0
- package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
- package/lib/{serve/index.js → srv/cds-serve.js} +0 -0
- package/lib/{serve → srv}/factory.js +1 -1
- package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
- package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
- package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
- package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
- package/lib/srv/srv-models.js +207 -0
- package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
- package/lib/utils/{tests.js → cds-test.js} +2 -2
- package/lib/utils/cds-utils.js +146 -0
- package/lib/utils/index.js +2 -145
- package/lib/utils/jest.js +43 -0
- package/lib/utils/resources/index.js +15 -25
- package/lib/utils/resources/tar.js +18 -41
- package/libx/_runtime/auth/index.js +14 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +3 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
- package/libx/_runtime/cds-services/util/errors.js +1 -29
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/perf/index.js +10 -15
- package/libx/_runtime/common/utils/binary.js +3 -4
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
- package/libx/_runtime/common/utils/keys.js +14 -6
- package/libx/_runtime/common/utils/resolveView.js +1 -1
- package/libx/_runtime/common/utils/template.js +1 -1
- package/libx/_runtime/db/Service.js +2 -14
- package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
- package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
- package/libx/_runtime/db/generic/input.js +8 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
- package/libx/_runtime/extensibility/activate.js +47 -47
- package/libx/_runtime/extensibility/add.js +22 -13
- package/libx/_runtime/extensibility/addExtension.js +17 -13
- package/libx/_runtime/extensibility/defaults.js +25 -30
- package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
- package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
- package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
- package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
- package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
- package/libx/_runtime/extensibility/linter.js +32 -0
- package/libx/_runtime/extensibility/push.js +77 -20
- package/libx/_runtime/extensibility/service.js +29 -12
- package/libx/_runtime/extensibility/token.js +57 -0
- package/libx/_runtime/extensibility/utils.js +8 -6
- package/libx/_runtime/extensibility/validation.js +6 -9
- package/libx/_runtime/fiori/generic/new.js +0 -11
- package/libx/_runtime/fiori/utils/where.js +1 -1
- package/libx/_runtime/hana/Service.js +0 -1
- package/libx/_runtime/hana/conversion.js +12 -1
- package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
- package/libx/_runtime/hana/pool.js +6 -10
- package/libx/_runtime/hana/search2Contains.js +0 -5
- package/libx/_runtime/hana/search2cqn4sql.js +1 -0
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +11 -6
- package/libx/_runtime/remote/utils/data.js +5 -0
- package/libx/_runtime/sqlite/Service.js +7 -6
- package/libx/_runtime/sqlite/execute.js +41 -28
- package/libx/odata/afterburner.js +79 -2
- package/libx/odata/cqn2odata.js +15 -9
- package/libx/odata/grammar.pegjs +157 -76
- package/libx/odata/index.js +9 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +39 -5
- package/libx/rest/RestAdapter.js +3 -7
- package/libx/rest/middleware/delete.js +4 -5
- package/libx/rest/middleware/parse.js +3 -2
- package/package.json +3 -3
- package/server.js +1 -1
- package/srv/extensibility-service.cds +6 -3
- package/srv/model-provider.cds +3 -1
- package/srv/model-provider.js +86 -106
- package/srv/mtx.js +7 -1
- 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('./
|
|
20
|
+
* @returns { Transaction & import('./srv-api') }
|
|
9
21
|
* @param { EventContext } ctx
|
|
10
22
|
*/
|
|
11
|
-
|
|
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 =
|
|
19
|
-
|
|
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
|
-
//
|
|
39
|
-
return
|
|
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,
|
|
49
|
-
|
|
50
|
-
if (
|
|
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,
|
|
57
|
+
if (!tx) txs.set (srv, tx = new this (srv,ctx))
|
|
53
58
|
return tx
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
constructor (srv,
|
|
57
|
-
const tx = { __proto__:srv, context:
|
|
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
|
|
106
|
-
* @param {EventContext}
|
|
114
|
+
* Register the new transaction with the given context.
|
|
115
|
+
* @param {EventContext} ctx
|
|
107
116
|
*/
|
|
108
|
-
static for (srv,
|
|
109
|
-
|
|
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
|
|
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 {
|
|
169
|
+
* @param {EventContext} ctx
|
|
155
170
|
*/
|
|
156
|
-
constructor (srv,
|
|
157
|
-
super (srv,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if ('end' in srv)
|
|
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
|
package/lib/utils/index.js
CHANGED
|
@@ -1,145 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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')
|