@sap/cds 6.3.1 → 6.4.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.
- package/CHANGELOG.md +87 -0
- package/apis/cds.d.ts +1 -1
- package/apis/core.d.ts +118 -90
- package/apis/cqn.d.ts +11 -2
- package/apis/internal/inference.d.ts +7 -2
- package/apis/ql.d.ts +45 -11
- package/apis/serve.d.ts +8 -1
- package/apis/services.d.ts +303 -305
- package/bin/build/buildTaskEngine.js +28 -36
- package/bin/build/buildTaskFactory.js +32 -81
- package/bin/build/buildTaskHandler.js +3 -2
- package/bin/build/buildTaskProvider.js +2 -2
- package/bin/build/buildTaskProviderFactory.js +5 -14
- package/bin/build/constants.js +0 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +7 -6
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +6 -5
- package/bin/build/provider/buildTaskHandlerInternal.js +9 -30
- package/bin/build/provider/buildTaskProviderInternal.js +70 -58
- package/bin/build/provider/fiori/index.js +6 -5
- package/bin/build/provider/hana/2migration.js +20 -3
- package/bin/build/provider/hana/2tabledata.js +1 -0
- package/bin/build/provider/hana/index.js +40 -17
- package/bin/build/provider/java/index.js +10 -10
- package/bin/build/provider/mtx/index.js +25 -16
- package/bin/build/provider/mtx/resourcesTarBuilder.js +22 -27
- package/bin/build/provider/mtx-extension/index.js +3 -2
- package/bin/build/provider/mtx-sidecar/index.js +16 -15
- package/bin/build/provider/nodejs/index.js +14 -56
- package/bin/build/util.js +56 -16
- package/bin/deploy/to-hana/cfUtil.js +4 -1
- package/bin/deploy/to-hana/gitUtil.js +1 -1
- package/bin/deploy/to-hana/hana.js +45 -38
- package/bin/deploy/to-hana/hdiDeployUtil.js +8 -9
- package/bin/deploy/to-hana/mtaUtil.js +13 -14
- package/bin/mtx/in-cds.js +3 -1
- package/bin/serve.js +1 -1
- package/bin/version.js +2 -1
- package/lib/compile/cds-compile.js +1 -0
- package/lib/compile/cdsc.js +1 -0
- package/lib/compile/etc/_localized.js +2 -2
- package/lib/compile/for/lean_drafts.js +83 -0
- package/lib/compile/for/nodejs.js +1 -0
- package/lib/compile/minify.js +2 -1
- package/lib/compile/parse.js +2 -1
- package/lib/compile/to/gql.js +1 -1
- package/lib/compile/to/sql.js +11 -1
- package/lib/core/entities.js +1 -1
- package/lib/core/index.js +8 -9
- package/lib/core/infer.js +1 -0
- package/lib/dbs/cds-deploy.js +97 -41
- package/lib/env/cds-env.js +9 -10
- package/lib/env/cds-requires.js +8 -2
- package/lib/env/defaults.js +0 -4
- package/lib/env/schemas/cds-rc.json +38 -0
- package/lib/ql/SELECT.js +10 -4
- package/lib/srv/bindings.js +1 -1
- package/lib/srv/factory.js +1 -1
- package/lib/srv/protocols/index.js +3 -1
- package/lib/srv/srv-methods.js +1 -1
- package/lib/utils/cds-utils.js +11 -0
- package/lib/utils/inflect.js +13 -12
- package/lib/utils/tar.js +53 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -15
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/UriSyntaxError.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +6 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +1 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +4 -0
- package/libx/_runtime/cds-services/services/Service.js +23 -1
- package/libx/_runtime/cds-services/util/assert.js +0 -41
- package/libx/_runtime/common/composition/data.js +5 -1
- package/libx/_runtime/common/generic/auth/utils.js +3 -3
- package/libx/_runtime/common/generic/input.js +4 -24
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/utils/csn.js +21 -15
- package/libx/_runtime/common/utils/draft.js +2 -1
- package/libx/_runtime/common/utils/resolveView.js +25 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -1
- package/libx/_runtime/common/utils/rowUUIDGenerator.js +21 -0
- package/libx/_runtime/common/utils/templateProcessor.js +12 -15
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +23 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +29 -12
- package/libx/_runtime/db/generic/input.js +7 -13
- package/libx/_runtime/db/sql-builder/UpsertBuilder.js +47 -0
- package/libx/_runtime/db/sql-builder/index.js +2 -0
- package/libx/_runtime/db/sql-builder/sqlFactory.js +9 -0
- package/libx/_runtime/db/utils/columns.js +4 -2
- package/libx/_runtime/fiori/generic/read.js +1 -12
- package/libx/_runtime/fiori/lean-draft.js +657 -0
- package/libx/_runtime/fiori/utils/handler.js +1 -1
- package/libx/_runtime/hana/pool.js +16 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +2 -3
- package/libx/_runtime/messaging/outbox/utils.js +109 -70
- package/libx/_runtime/messaging/service.js +16 -7
- package/libx/_runtime/remote/Service.js +15 -2
- package/libx/_runtime/remote/utils/client.js +41 -11
- package/libx/_runtime/sqlite/Service.js +3 -0
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +56 -0
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +59 -0
- package/libx/_runtime/sqlite/customBuilder/index.js +5 -0
- package/libx/_runtime/sqlite/execute.js +1 -1
- package/libx/_runtime/types/api.js +2 -2
- package/libx/rest/RestAdapter.js +15 -13
- package/package.json +1 -1
- package/server.js +1 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
const cds = require('../cds'),
|
|
2
|
+
{ Object_keys, results } = cds.utils
|
|
3
|
+
const LOG = cds.log('fiori|drafts')
|
|
4
|
+
|
|
5
|
+
const DRAFT_ELEMENTS = new Set([
|
|
6
|
+
'IsActiveEntity',
|
|
7
|
+
'HasDraftEntity',
|
|
8
|
+
'HasActiveEntity',
|
|
9
|
+
'DraftAdministrativeData',
|
|
10
|
+
'DraftAdministrativeData_DraftUUID',
|
|
11
|
+
'SiblingEntity'
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
const REDUCED_DRAFT_ELEMENTS = new Set(['IsActiveEntity', 'HasDraftEntity', 'SiblingEntity'])
|
|
15
|
+
|
|
16
|
+
const DRAFT_ADMIN_ELEMENTS = [
|
|
17
|
+
'DraftUUID',
|
|
18
|
+
'LastChangedByUser',
|
|
19
|
+
'CreatedByUser',
|
|
20
|
+
'InProcessByUser',
|
|
21
|
+
'DraftIsCreatedByMe',
|
|
22
|
+
'DraftIsProcessedByMe'
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const DRAFT_CANCEL_TIMEOUT_IN_MIN = () =>
|
|
26
|
+
(cds.env.drafts?.cancellationTimeout && Number(cds.env.drafts?.cancellationTimeout)) || 15
|
|
27
|
+
|
|
28
|
+
const h = cds.ApplicationService.prototype.handle
|
|
29
|
+
cds.ApplicationService.prototype.handle = function (req) {
|
|
30
|
+
const handle = h.bind(this)
|
|
31
|
+
|
|
32
|
+
const _newReq = (req, query) => {
|
|
33
|
+
// REVISIT: This is a bit hacky -> better way?
|
|
34
|
+
query._target = undefined
|
|
35
|
+
cds.infer(query, this.model.definitions)
|
|
36
|
+
const _req = cds.Request.for(req._)
|
|
37
|
+
_req.query = query
|
|
38
|
+
_req.event = req.event
|
|
39
|
+
_req.target = query._target
|
|
40
|
+
_req._.params = req.params
|
|
41
|
+
_req._.query = query
|
|
42
|
+
_req._ = req._
|
|
43
|
+
return _req
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!req.query || req.query._draftParams) return handle(req)
|
|
47
|
+
const query = _cleansed(req.query, this.model)
|
|
48
|
+
const draftParams = query._draftParams
|
|
49
|
+
if (req.event !== 'READ') {
|
|
50
|
+
const _req = _newReq(req, query)
|
|
51
|
+
return handle(_req)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const read =
|
|
55
|
+
draftParams.IsActiveEntity === false && draftParams.SiblingEntity_IsActiveEntity === null
|
|
56
|
+
? Read.all
|
|
57
|
+
: draftParams.IsActiveEntity === true &&
|
|
58
|
+
draftParams.SiblingEntity_IsActiveEntity === null &&
|
|
59
|
+
draftParams.DraftAdministrativeData_InProcessByUser === 'not null'
|
|
60
|
+
? Read.lockedByAnotherUser
|
|
61
|
+
: draftParams.IsActiveEntity === true &&
|
|
62
|
+
draftParams.SiblingEntity_IsActiveEntity === null &&
|
|
63
|
+
draftParams.DraftAdministrativeData_InProcessByUser === ''
|
|
64
|
+
? Read.unsavedChangesByAnotherUser
|
|
65
|
+
: draftParams.IsActiveEntity === true && draftParams.HasDraftEntity === false
|
|
66
|
+
? Read.unchanged
|
|
67
|
+
: draftParams.IsActiveEntity === true
|
|
68
|
+
? Read.onlyActives
|
|
69
|
+
: draftParams.IsActiveEntity === false
|
|
70
|
+
? Read.ownDrafts
|
|
71
|
+
: Read.onlyActives
|
|
72
|
+
const run = async query => {
|
|
73
|
+
const _req = _newReq(req, query)
|
|
74
|
+
return handle(_req)
|
|
75
|
+
}
|
|
76
|
+
return read(run, query)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const Read = {
|
|
80
|
+
onlyActives: async function (run, query, { ignoreDrafts } = {}) {
|
|
81
|
+
// DraftAdministrativeData is only accessible via drafts
|
|
82
|
+
if (query._target.name.endsWith('.DraftAdministrativeData')) return run(query._drafts)
|
|
83
|
+
const actives = await run(query)
|
|
84
|
+
if (!actives || (Array.isArray(actives) && !actives.length) || !query._target.drafts) return actives
|
|
85
|
+
let drafts
|
|
86
|
+
if (ignoreDrafts) drafts = []
|
|
87
|
+
else {
|
|
88
|
+
try {
|
|
89
|
+
drafts = await Read.complementaryDrafts(run, query, actives)
|
|
90
|
+
} catch (e) {
|
|
91
|
+
drafts = []
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
Read.merge(query._target, actives, drafts, (row, other) =>
|
|
95
|
+
other
|
|
96
|
+
? Object.assign(row, other, { IsActiveEntity: true, HasActiveEntity: false, HasDraftEntity: true })
|
|
97
|
+
: Object.assign(row, {
|
|
98
|
+
IsActiveEntity: true,
|
|
99
|
+
HasActiveEntity: false,
|
|
100
|
+
HasDraftEntity: false,
|
|
101
|
+
DraftAdministrativeData: null,
|
|
102
|
+
DraftAdministrativeData_DraftUUID: null
|
|
103
|
+
})
|
|
104
|
+
)
|
|
105
|
+
return actives
|
|
106
|
+
},
|
|
107
|
+
unchanged: async function (run, query) {
|
|
108
|
+
const draftsQuery = query._drafts
|
|
109
|
+
const keys = Object_keys(query._target.keys).filter(k => k !== 'IsActiveEntity')
|
|
110
|
+
draftsQuery.SELECT.count = undefined
|
|
111
|
+
draftsQuery.SELECT.limit = undefined
|
|
112
|
+
draftsQuery.SELECT.orderBy = undefined
|
|
113
|
+
draftsQuery.SELECT.columns = keys.map(k => ({ ref: [k] }))
|
|
114
|
+
|
|
115
|
+
const drafts = await run(draftsQuery)
|
|
116
|
+
const res = Read.onlyActives(run, query.where(Read.whereNotIn(query._target, drafts)), {
|
|
117
|
+
ignoreDrafts: true
|
|
118
|
+
})
|
|
119
|
+
return res
|
|
120
|
+
},
|
|
121
|
+
ownDrafts: async function (run, query) {
|
|
122
|
+
if (!query._target.drafts) return run(query._drafts)
|
|
123
|
+
const drafts = await run(
|
|
124
|
+
query._drafts.where({ ref: ['DraftAdministrativeData', 'InProcessByUser'] }, '=', cds.context.user.id)
|
|
125
|
+
)
|
|
126
|
+
Read.merge(query._target, drafts, [], row =>
|
|
127
|
+
Object.assign(row, {
|
|
128
|
+
IsActiveEntity: false,
|
|
129
|
+
HasDraftEntity: false
|
|
130
|
+
})
|
|
131
|
+
)
|
|
132
|
+
return drafts
|
|
133
|
+
},
|
|
134
|
+
all: async function (run, query) {
|
|
135
|
+
query._drafts.SELECT.count = true
|
|
136
|
+
const drafts = await run(
|
|
137
|
+
query._drafts.where({ ref: ['DraftAdministrativeData', 'InProcessByUser'] }, '=', cds.context.user.id)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
const skip = query.SELECT.limit?.offset?.val
|
|
141
|
+
const top = query.SELECT.limit?.rows?.val
|
|
142
|
+
|
|
143
|
+
const appliedSkip = drafts.length ? skip : drafts.$count
|
|
144
|
+
|
|
145
|
+
const topActives = top && Math.max(0, top - drafts.length)
|
|
146
|
+
let actives
|
|
147
|
+
if (topActives === 0) {
|
|
148
|
+
actives = []
|
|
149
|
+
} else {
|
|
150
|
+
const skipActives = skip && skip - appliedSkip
|
|
151
|
+
query.SELECT.count = true
|
|
152
|
+
actives = await run(query.limit(topActives, skipActives).where(Read.whereNotIn(query._target, drafts)))
|
|
153
|
+
const draftsFromActives = await Read.complementaryDrafts(run, query, actives)
|
|
154
|
+
Read.merge(query._target, actives, draftsFromActives, (row, other) => {
|
|
155
|
+
if (other) {
|
|
156
|
+
Object.assign(row, other, {
|
|
157
|
+
IsActiveEntity: true,
|
|
158
|
+
HasDraftEntity: true,
|
|
159
|
+
HasActiveEntity: false
|
|
160
|
+
})
|
|
161
|
+
} else {
|
|
162
|
+
Object.assign(row, {
|
|
163
|
+
IsActiveEntity: true,
|
|
164
|
+
HasDraftEntity: false,
|
|
165
|
+
HasActiveEntity: false,
|
|
166
|
+
DraftAdministrativeData_DraftUUID: null,
|
|
167
|
+
DraftAdministrativeData: null
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
Read.merge(query._target, drafts, [], row =>
|
|
173
|
+
Object.assign(row, {
|
|
174
|
+
IsActiveEntity: false,
|
|
175
|
+
HasDraftEntity: false
|
|
176
|
+
})
|
|
177
|
+
)
|
|
178
|
+
const totalCount = actives.$count + drafts.$count
|
|
179
|
+
const result = Object.assign([...drafts, ...actives], { $count: totalCount })
|
|
180
|
+
return result
|
|
181
|
+
},
|
|
182
|
+
activesFromDrafts: async function (run, query, { isLocked = true }) {
|
|
183
|
+
const draftsQuery = query._drafts
|
|
184
|
+
const keys = Object_keys(query._target.keys).filter(k => k !== 'IsActiveEntity')
|
|
185
|
+
const additionalCols = draftsQuery.SELECT.columns
|
|
186
|
+
? draftsQuery.SELECT.columns.filter(
|
|
187
|
+
c => c.ref && ['DraftAdministrativeData', 'DraftAdministrativeData_DraftUUID'].includes(c.ref[0])
|
|
188
|
+
)
|
|
189
|
+
: [{ ref: ['DraftAdministrativeData_DraftUUID'] }]
|
|
190
|
+
draftsQuery.SELECT.columns = keys.map(k => ({ ref: [k] })).concat(additionalCols)
|
|
191
|
+
draftsQuery.where({
|
|
192
|
+
HasActiveEntity: true,
|
|
193
|
+
'DraftAdministrativeData.InProcessByUser': { '!=': cds.context.user.id },
|
|
194
|
+
'DraftAdministrativeData.LastChangeDateTime': {
|
|
195
|
+
[isLocked ? '>' : '<']: Read.lockshiftedNow
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
const drafts = await run(draftsQuery)
|
|
199
|
+
const actives = drafts.length
|
|
200
|
+
? await run(query.where(Read.whereIn(query._target, drafts)))
|
|
201
|
+
: Object.assign([], { $count: 0 })
|
|
202
|
+
Read.merge(query._target, actives, drafts, (row, other) =>
|
|
203
|
+
other
|
|
204
|
+
? Object.assign(row, other, { IsActiveEntity: true, HasDraftEntity: true, HasActiveEntity: false })
|
|
205
|
+
: Object.assign({ IsActiveEntity: true, HasDraftEntity: false, HasActiveEntity: false })
|
|
206
|
+
)
|
|
207
|
+
return actives
|
|
208
|
+
},
|
|
209
|
+
unsavedChangesByAnotherUser: async function (run, query) {
|
|
210
|
+
return Read.activesFromDrafts(run, query, { isLocked: false })
|
|
211
|
+
},
|
|
212
|
+
lockedByAnotherUser: async function (run, query) {
|
|
213
|
+
return Read.activesFromDrafts(run, query, { isLocked: true })
|
|
214
|
+
},
|
|
215
|
+
get lockshiftedNow() {
|
|
216
|
+
return new Date(Date.now() - DRAFT_CANCEL_TIMEOUT_IN_MIN() * 60 * 1000).toISOString()
|
|
217
|
+
},
|
|
218
|
+
whereNotIn: (target, data) => Read.whereIn(target, data, true),
|
|
219
|
+
whereIn: (target, data, not = false) => {
|
|
220
|
+
const keys = Object_keys(target.keys).filter(k => k !== 'IsActiveEntity')
|
|
221
|
+
const dataArray = data ? (Array.isArray(data) ? data : [data]) : []
|
|
222
|
+
return [
|
|
223
|
+
{ list: keys.map(k => ({ ref: [k] })) },
|
|
224
|
+
not ? 'not in' : 'in',
|
|
225
|
+
{ list: dataArray.map(r => ({ list: keys.map(k => ({ val: r[k] })) })) }
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
complementaryDrafts: (run, query, _actives) => {
|
|
229
|
+
const actives = Array.isArray(_actives) ? _actives : [_actives]
|
|
230
|
+
if (!actives.length) return []
|
|
231
|
+
const drafts = cds.ql.clone(query._drafts)
|
|
232
|
+
drafts.SELECT.where = Read.whereIn(query._target, actives)
|
|
233
|
+
const relevantColumns = ['DraftAdministrativeData', 'DraftAdministrativeData_DraftUUID']
|
|
234
|
+
drafts.SELECT.columns = (
|
|
235
|
+
drafts.SELECT.columns?.filter(c => c.ref && relevantColumns.includes(c.ref[0])) ||
|
|
236
|
+
relevantColumns.map(k => ({ ref: [k] }))
|
|
237
|
+
).concat(
|
|
238
|
+
Object_keys(query._target.keys)
|
|
239
|
+
.filter(k => k !== 'IsActiveEntity')
|
|
240
|
+
.map(k => ({ ref: [k] }))
|
|
241
|
+
)
|
|
242
|
+
drafts.SELECT.count = undefined
|
|
243
|
+
drafts.SELECT.one = undefined
|
|
244
|
+
return run(drafts)
|
|
245
|
+
},
|
|
246
|
+
merge: (target, data, otherData, cb) => {
|
|
247
|
+
if (!data) return
|
|
248
|
+
const dataArray = Array.isArray(data) ? data : [data]
|
|
249
|
+
if (!dataArray.length) return
|
|
250
|
+
const otherDataArray = Array.isArray(otherData) ? otherData : otherData ? [otherData] : []
|
|
251
|
+
if (otherDataArray.length) {
|
|
252
|
+
const _keys = Object_keys(target.keys).filter(k => k !== 'IsActiveEntity')
|
|
253
|
+
const _hash = row => _keys.map(k => row[k]).reduce((res, curr) => res + '|$|' + curr, '')
|
|
254
|
+
const d2hash = new Map()
|
|
255
|
+
for (const row of otherDataArray) d2hash.set(_hash(row), row)
|
|
256
|
+
for (const row of dataArray) {
|
|
257
|
+
const other = d2hash.get(_hash(row))
|
|
258
|
+
cb(row, other)
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
for (const row of dataArray) {
|
|
262
|
+
cb(row, undefined)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Creates a clone of the query, cleanses and collects all draft parameters into ._draftParams.
|
|
270
|
+
*/
|
|
271
|
+
function _cleansed(query, model, target) {
|
|
272
|
+
const draftParams = {} //> used to collect draft filter criteria
|
|
273
|
+
const q = _cleanseQuery(query, draftParams)
|
|
274
|
+
if (query.SELECT) {
|
|
275
|
+
let cache
|
|
276
|
+
const getDrafts = () => {
|
|
277
|
+
if (cache) return cache
|
|
278
|
+
const draftsQuery = _cleanseQuery(query, {}) // could just clone `q` but the latter is ruined by database layer
|
|
279
|
+
const [root, ...tail] = draftsQuery.SELECT.from.ref
|
|
280
|
+
const draft = model.definitions[root.id || root].drafts
|
|
281
|
+
draftsQuery.SELECT.from = {
|
|
282
|
+
ref: [root.id ? { ...root, id: draft.name } : draft.name, ...tail]
|
|
283
|
+
}
|
|
284
|
+
if (query.SELECT.columns && query._target.drafts)
|
|
285
|
+
draftsQuery.SELECT.columns = query.SELECT.columns.filter(c => !REDUCED_DRAFT_ELEMENTS.has(c.ref?.[0]))
|
|
286
|
+
|
|
287
|
+
if (draftsQuery._target.name.endsWith('.DraftAdministrativeData')) {
|
|
288
|
+
draftsQuery.SELECT.columns = _tweakAdminCols(draftsQuery.SELECT.columns)
|
|
289
|
+
} else if (q._target.drafts) {
|
|
290
|
+
draftsQuery.SELECT.columns = _tweakAdminExpand(draftsQuery.SELECT.columns)
|
|
291
|
+
}
|
|
292
|
+
Object.defineProperty(draftsQuery, '_draftParams', { value: draftParams, enumerable: false })
|
|
293
|
+
cache = draftsQuery
|
|
294
|
+
return draftsQuery
|
|
295
|
+
}
|
|
296
|
+
Object.defineProperty(q, '_drafts', {
|
|
297
|
+
get() {
|
|
298
|
+
return getDrafts()
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
Object.defineProperty(q, '_draftParams', { value: draftParams, enumerable: false })
|
|
304
|
+
return q
|
|
305
|
+
|
|
306
|
+
function _cleanseQuery(query, draftParams) {
|
|
307
|
+
const q = cds.ql.clone(query)
|
|
308
|
+
|
|
309
|
+
const ref = q.SELECT?.from.ref || q.UPDATE?.entity.ref || q.INSERT?.into.ref || q.DELETE?.from.ref
|
|
310
|
+
const cqn = q.SELECT || q.UPDATE || q.INSERT || q.DELETE
|
|
311
|
+
|
|
312
|
+
if (ref) {
|
|
313
|
+
const cleansedRef = ref.map(r => (r.where ? { ...r, where: _cleanseWhere(r.where, draftParams) } : r))
|
|
314
|
+
if (q.SELECT) q.SELECT.from = { ...q.SELECT.from, ref: cleansedRef }
|
|
315
|
+
else if (q.DELETE) q.DELETE.from = { ...q.DELETE.from, ref: cleansedRef }
|
|
316
|
+
else if (q.UPDATE) q.UPDATE.entity = { ...q.UPDATE.entity, ref: cleansedRef }
|
|
317
|
+
else if (q.INSERT) q.INSERT.into = { ...q.INSERT.into, ref: cleansedRef }
|
|
318
|
+
|
|
319
|
+
const siblingIdx = cleansedRef.findIndex(r => r === 'SiblingEntity')
|
|
320
|
+
if (siblingIdx !== -1) {
|
|
321
|
+
cleansedRef.splice(siblingIdx, 1)
|
|
322
|
+
draftParams.IsActiveEntity = !draftParams.IsActiveEntity
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (cqn.where) cqn.where = _cleanseWhere(cqn.where, draftParams)
|
|
327
|
+
if (cqn.columns) cqn.columns = q.SELECT.columns.filter(c => !DRAFT_ELEMENTS.has(c.ref?.[0]))
|
|
328
|
+
if (cqn.orderBy) cqn.orderBy = _cleanseWhere(cqn.orderBy, {})
|
|
329
|
+
|
|
330
|
+
return q
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function _tweakAdminExpand(columns) {
|
|
334
|
+
if (!columns) return columns
|
|
335
|
+
return columns.map(col => {
|
|
336
|
+
if (col.ref?.[0] === 'DraftAdministrativeData') {
|
|
337
|
+
return { ...col, expand: _tweakAdminCols(col.expand) }
|
|
338
|
+
}
|
|
339
|
+
return col
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function _tweakAdminCols(columns) {
|
|
344
|
+
if (!columns) columns = DRAFT_ADMIN_ELEMENTS.map(k => ({ ref: [k] }))
|
|
345
|
+
return columns.map(col => {
|
|
346
|
+
const name = col.ref?.[0]
|
|
347
|
+
if (!name) return col
|
|
348
|
+
switch (name) {
|
|
349
|
+
case 'DraftAdministrativeData':
|
|
350
|
+
return { ...col, expand: _tweakAdminCols(col.expand) }
|
|
351
|
+
case 'DraftIsCreatedByMe':
|
|
352
|
+
return {
|
|
353
|
+
xpr: [
|
|
354
|
+
'case',
|
|
355
|
+
'when',
|
|
356
|
+
{ ref: ['CreatedByUser'] },
|
|
357
|
+
'=',
|
|
358
|
+
{ val: cds.context.user.id },
|
|
359
|
+
'then',
|
|
360
|
+
{ val: true },
|
|
361
|
+
'else',
|
|
362
|
+
{ val: false },
|
|
363
|
+
'end'
|
|
364
|
+
],
|
|
365
|
+
as: 'DraftIsCreatedByMe',
|
|
366
|
+
cast: { type: 'cds.Boolean' }
|
|
367
|
+
}
|
|
368
|
+
case 'InProcessByUser':
|
|
369
|
+
return {
|
|
370
|
+
xpr: [
|
|
371
|
+
'case',
|
|
372
|
+
'when',
|
|
373
|
+
{ ref: ['LastChangeDateTime'] },
|
|
374
|
+
'<',
|
|
375
|
+
{ val: Read.lockshiftedNow },
|
|
376
|
+
'then',
|
|
377
|
+
{ val: '' },
|
|
378
|
+
'else',
|
|
379
|
+
{ ref: ['InProcessByUser'] },
|
|
380
|
+
'end'
|
|
381
|
+
],
|
|
382
|
+
as: 'InProcessByUser',
|
|
383
|
+
cast: { type: 'cds.String' }
|
|
384
|
+
}
|
|
385
|
+
case 'DraftIsProcessedByMe':
|
|
386
|
+
return {
|
|
387
|
+
xpr: [
|
|
388
|
+
'case',
|
|
389
|
+
'when',
|
|
390
|
+
{ ref: ['InProcessByUser'] },
|
|
391
|
+
'=',
|
|
392
|
+
{ val: cds.context.user.id },
|
|
393
|
+
'then',
|
|
394
|
+
{ val: true },
|
|
395
|
+
'else',
|
|
396
|
+
{ val: false },
|
|
397
|
+
'end'
|
|
398
|
+
],
|
|
399
|
+
as: 'DraftIsProcessedByMe',
|
|
400
|
+
cast: { type: 'cds.Boolean' }
|
|
401
|
+
}
|
|
402
|
+
default:
|
|
403
|
+
return col
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function _cleanseWhere(xpr, draftParams) {
|
|
409
|
+
const cleansed = []
|
|
410
|
+
for (let i = 0; i < xpr.length; ++i) {
|
|
411
|
+
let x = xpr[i],
|
|
412
|
+
e = x.ref?.[0]
|
|
413
|
+
if (DRAFT_ELEMENTS.has(e)) {
|
|
414
|
+
let { val } = xpr[i + 2]
|
|
415
|
+
draftParams[x.ref.join('_')] = xpr[i + 1] === '!=' ? 'not ' + val : val
|
|
416
|
+
i += 3
|
|
417
|
+
continue
|
|
418
|
+
}
|
|
419
|
+
if (x.xpr) {
|
|
420
|
+
x = { xpr: _cleanseWhere(x.xpr, draftParams) }
|
|
421
|
+
if (!x.xpr) {
|
|
422
|
+
i += 1
|
|
423
|
+
continue
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
cleansed.push(x)
|
|
427
|
+
}
|
|
428
|
+
const last = cleansed[cleansed.length - 1]
|
|
429
|
+
if (last === 'and' || last === 'or') cleansed.pop()
|
|
430
|
+
if (cleansed.length) return cleansed
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function _draftIsLocked(LastChangeDateTime) {
|
|
435
|
+
return DRAFT_CANCEL_TIMEOUT_IN_MIN() * 60 * 1000 > Date.now() - Date.parse(LastChangeDateTime)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function onNewDraft(req) {
|
|
439
|
+
LOG.debug('new draft')
|
|
440
|
+
const isRoot = typeof req.query.INSERT.into === 'string'
|
|
441
|
+
let DraftUUID
|
|
442
|
+
if (isRoot) DraftUUID = cds.utils.uuid()
|
|
443
|
+
else {
|
|
444
|
+
const rootData = await SELECT.one(req.query.INSERT.into.ref[0].id + '.drafts', d => {
|
|
445
|
+
d.DraftAdministrativeData_DraftUUID,
|
|
446
|
+
d.DraftAdministrativeData(a => {
|
|
447
|
+
a.LastChangeDateTime, a.InProcessByUser
|
|
448
|
+
})
|
|
449
|
+
}).where(req.query.INSERT.into.ref[0].where)
|
|
450
|
+
if (!rootData) req.reject(404)
|
|
451
|
+
if (
|
|
452
|
+
!rootData.DraftAdministrativeData.InProcessByUser === req.user.id &&
|
|
453
|
+
_draftIsLocked(rootData.DraftAdministrativeData.LastChangeDateTime)
|
|
454
|
+
)
|
|
455
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
456
|
+
DraftUUID = rootData.DraftAdministrativeData_DraftUUID
|
|
457
|
+
}
|
|
458
|
+
const timestamp = cds.context.timestamp.toISOString() // REVISIT: toISOString should be done on db layer
|
|
459
|
+
const adminDataCQN = isRoot
|
|
460
|
+
? INSERT.into('DRAFT.DraftAdministrativeData').entries({
|
|
461
|
+
DraftUUID,
|
|
462
|
+
CreationDateTime: timestamp,
|
|
463
|
+
CreatedByUser: req.user.id,
|
|
464
|
+
LastChangeDateTime: timestamp,
|
|
465
|
+
LastChangedByUser: req.user.id,
|
|
466
|
+
DraftIsCreatedByMe: true, // Dummy values
|
|
467
|
+
DraftIsProcessedByMe: true, // Dummy values
|
|
468
|
+
InProcessByUser: req.user.id
|
|
469
|
+
})
|
|
470
|
+
: UPDATE('DRAFT.DraftAdministrativeData')
|
|
471
|
+
.data({
|
|
472
|
+
InProcessByUser: req.user.id,
|
|
473
|
+
LastChangedByUser: req.user.id,
|
|
474
|
+
LastChangeDateTime: timestamp
|
|
475
|
+
})
|
|
476
|
+
.where({ DraftUUID })
|
|
477
|
+
|
|
478
|
+
const draftData = Object.assign(
|
|
479
|
+
{ DraftAdministrativeData_DraftUUID: DraftUUID, HasActiveEntity: false },
|
|
480
|
+
req.query.INSERT.entries[0]
|
|
481
|
+
)
|
|
482
|
+
delete draftData.IsActiveEntity
|
|
483
|
+
const draftCQN = INSERT.into(req.target.drafts).entries(draftData)
|
|
484
|
+
|
|
485
|
+
// TODO: do this? req.query._draftParams.IsActiveEntity = false
|
|
486
|
+
await Promise.all([adminDataCQN, draftCQN].map(cqn => cds.run(cqn)))
|
|
487
|
+
req._.readAfterWrite = true
|
|
488
|
+
return { ...draftData, IsActiveEntity: false }
|
|
489
|
+
}
|
|
490
|
+
exports.onNewDraft = onNewDraft
|
|
491
|
+
|
|
492
|
+
async function onDraftPrepare(req) {
|
|
493
|
+
LOG.debug('prepare draft')
|
|
494
|
+
|
|
495
|
+
const draftParams = req.query._draftParams
|
|
496
|
+
const where = req.query.SELECT.from.ref[0].where
|
|
497
|
+
if (req.query.SELECT.from.ref.length > 1 || draftParams.IsActiveEntity !== false) {
|
|
498
|
+
req.reject(400, 'Action "draftPrepare" can only be called on the root draft entity')
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const keys = Object_keys(req.target.keys).filter(k => k !== 'IsActiveEntity')
|
|
502
|
+
const data = await SELECT.one
|
|
503
|
+
.from(req.target.drafts, d => {
|
|
504
|
+
d.DraftAdministrativeData(a => a.InProcessByUser)
|
|
505
|
+
})
|
|
506
|
+
.columns(keys)
|
|
507
|
+
.where(where)
|
|
508
|
+
if (!data) req.reject(404)
|
|
509
|
+
if (data.DraftAdministrativeData.InProcessByUser !== req.user.id) req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
510
|
+
delete data.DraftAdministrativeData
|
|
511
|
+
return { ...data, IsActiveEntity: false }
|
|
512
|
+
}
|
|
513
|
+
exports.onDraftPrepare = onDraftPrepare
|
|
514
|
+
|
|
515
|
+
// This function is better defined on DB layer
|
|
516
|
+
function expandStarStar(target, recursion = new Map()) {
|
|
517
|
+
const MAX_RECURSION_DEPTH = (cds.env.features.recursion_depth && Number(cds.env.features.recursion_depth)) || 4
|
|
518
|
+
// TODO: Remove draftcolumns from payload
|
|
519
|
+
const columns = []
|
|
520
|
+
for (const el in target.elements) {
|
|
521
|
+
const element = target.elements[el]
|
|
522
|
+
if (!element.isAssociation && !DRAFT_ELEMENTS.has(el)) columns.push({ ref: [el] })
|
|
523
|
+
if (!element.isComposition) continue
|
|
524
|
+
const _key = target.name + ':' + el
|
|
525
|
+
let cache = recursion.get(_key)
|
|
526
|
+
if (!cache) cache = 1 && recursion.set(_key, cache)
|
|
527
|
+
if (cache >= MAX_RECURSION_DEPTH) return
|
|
528
|
+
columns.push({ ref: [el], expand: expandStarStar(element._target, recursion) })
|
|
529
|
+
}
|
|
530
|
+
return columns
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function onDraftEdit(req) {
|
|
534
|
+
LOG.debug('edit active')
|
|
535
|
+
const draftParams = req.query._draftParams
|
|
536
|
+
const targetWhere = req.query.SELECT.from.ref[0].where
|
|
537
|
+
|
|
538
|
+
if (draftParams.IsActiveEntity !== true) req.reject(400)
|
|
539
|
+
|
|
540
|
+
const DraftUUID = cds.utils.uuid()
|
|
541
|
+
|
|
542
|
+
const cols = expandStarStar(req.target)
|
|
543
|
+
const _addDraftColumns = (target, columns) => {
|
|
544
|
+
if (target.drafts) {
|
|
545
|
+
columns.push({ val: true, as: 'HasActiveEntity' })
|
|
546
|
+
columns.push({ val: DraftUUID, as: 'DraftAdministrativeData_DraftUUID' })
|
|
547
|
+
}
|
|
548
|
+
for (const col of columns) {
|
|
549
|
+
if (col.expand) {
|
|
550
|
+
const el = target.elements[col.ref[0]]
|
|
551
|
+
_addDraftColumns(el._target, col.expand)
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
_addDraftColumns(req.target, cols)
|
|
556
|
+
|
|
557
|
+
const [res, draftExists] = await Promise.all([
|
|
558
|
+
SELECT.one.from(req.target).columns(cols).where(targetWhere),
|
|
559
|
+
SELECT.one(req.target.drafts).columns('1 as _exists').where(targetWhere)
|
|
560
|
+
])
|
|
561
|
+
if (!res) req.reject(404)
|
|
562
|
+
if (draftExists) req.reject(409, 'DRAFT_ALREADY_EXISTS')
|
|
563
|
+
|
|
564
|
+
const timestamp = cds.context.timestamp.toISOString() // REVISIT: toISOString should be done on db layer
|
|
565
|
+
await INSERT.into('DRAFT.DraftAdministrativeData').entries({
|
|
566
|
+
DraftUUID,
|
|
567
|
+
CreationDateTime: timestamp,
|
|
568
|
+
CreatedByUser: req.user.id,
|
|
569
|
+
LastChangeDateTime: timestamp,
|
|
570
|
+
LastChangedByUser: req.user.id,
|
|
571
|
+
DraftIsCreatedByMe: true, // Dummy values
|
|
572
|
+
DraftIsProcessedByMe: true, // Dummy values
|
|
573
|
+
InProcessByUser: req.user.id
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
const targetDraft = req.target.drafts
|
|
577
|
+
await INSERT.into(targetDraft).entries(res)
|
|
578
|
+
return { ...res, IsActiveEntity: false } // REVISIT: Flatten?
|
|
579
|
+
}
|
|
580
|
+
exports.onDraftEdit = onDraftEdit
|
|
581
|
+
|
|
582
|
+
async function onDraftActivate(req) {
|
|
583
|
+
LOG.debug('activate draft')
|
|
584
|
+
// TODO: Read all drafts, send deep update/insert (based on HasActiveEntity, which should be fille with new!)
|
|
585
|
+
// It would be great if we'd have a SELECT ** to deeply expand the entity (along compositions), that should
|
|
586
|
+
// be implemented in expand implementation.
|
|
587
|
+
const targetDraft = req.target.drafts
|
|
588
|
+
const targetWhere = req.query.SELECT.from.ref[0].where
|
|
589
|
+
const res = await SELECT.one
|
|
590
|
+
.from(targetDraft)
|
|
591
|
+
.columns(expandStarStar(targetDraft))
|
|
592
|
+
.columns(['HasActiveEntity', 'DraftAdministrativeData_DraftUUID'])
|
|
593
|
+
.where(targetWhere)
|
|
594
|
+
const DraftAdministrativeData_DraftUUID = res.DraftAdministrativeData_DraftUUID
|
|
595
|
+
delete res.DraftAdministrativeData_DraftUUID
|
|
596
|
+
const HasActiveEntity = res.HasActiveEntity
|
|
597
|
+
delete res.HasActiveEntity
|
|
598
|
+
// TODO: Deep delete!
|
|
599
|
+
await Promise.all([
|
|
600
|
+
DELETE.from(targetDraft).where(targetWhere),
|
|
601
|
+
DELETE.from('DRAFT.DraftAdministrativeData').where({ DraftUUID: DraftAdministrativeData_DraftUUID })
|
|
602
|
+
])
|
|
603
|
+
// TODO: Check for InProcessByUser
|
|
604
|
+
|
|
605
|
+
let event, query
|
|
606
|
+
if (HasActiveEntity) {
|
|
607
|
+
query = UPDATE(req.target).data(res).where(targetWhere)
|
|
608
|
+
event = 'UPDATE'
|
|
609
|
+
} else {
|
|
610
|
+
query = INSERT.into(req.target).entries(res)
|
|
611
|
+
event = 'CREATE'
|
|
612
|
+
}
|
|
613
|
+
const r = new cds.Request({ event, query, data: res })
|
|
614
|
+
const result = await this.dispatch(r)
|
|
615
|
+
return Object.assign(result, { IsActiveEntity: true })
|
|
616
|
+
}
|
|
617
|
+
exports.onDraftActivate = onDraftActivate
|
|
618
|
+
|
|
619
|
+
async function onPatch(req) {
|
|
620
|
+
LOG.debug('patch draft')
|
|
621
|
+
const targetDraft = req.target.drafts
|
|
622
|
+
const targetWhere = req.query.UPDATE.entity.ref[0].where
|
|
623
|
+
const res = await SELECT.one.from(targetDraft).columns('DraftAdministrativeData_DraftUUID').where(targetWhere)
|
|
624
|
+
if (!res) req.reject(404)
|
|
625
|
+
await UPDATE('DRAFT.DraftAdministrativeData')
|
|
626
|
+
.data({
|
|
627
|
+
InProcessByUser: req.user.id,
|
|
628
|
+
LastChangedByUser: req.user.id,
|
|
629
|
+
LastChangeDateTime: new Date()
|
|
630
|
+
})
|
|
631
|
+
.where({ DraftUUID: res.DraftAdministrativeData_DraftUUID })
|
|
632
|
+
|
|
633
|
+
const updateData = { ...req.data }
|
|
634
|
+
delete updateData.IsActiveEntity
|
|
635
|
+
await UPDATE(targetDraft).data(updateData).where(targetWhere)
|
|
636
|
+
return req.data
|
|
637
|
+
}
|
|
638
|
+
exports.onPatch = onPatch
|
|
639
|
+
|
|
640
|
+
async function onDelete(req) {
|
|
641
|
+
LOG.debug('delete')
|
|
642
|
+
const targetDraft = req.target.drafts
|
|
643
|
+
const draftParams = req.query._draftParams
|
|
644
|
+
const targetWhere = req.query.DELETE.from.ref[0].where
|
|
645
|
+
const res = await SELECT.one.from(targetDraft).columns('DraftAdministrativeData_DraftUUID').where(targetWhere)
|
|
646
|
+
if (!res && draftParams.IsActiveEntity === false) req.reject(404)
|
|
647
|
+
const deletes = !res
|
|
648
|
+
? []
|
|
649
|
+
: [
|
|
650
|
+
DELETE.from('DRAFT.DraftAdministrativeData').where({ DraftUUID: res.DraftAdministrativeData_DraftUUID }),
|
|
651
|
+
DELETE.from(targetDraft).where(targetWhere)
|
|
652
|
+
]
|
|
653
|
+
if (draftParams.IsActiveEntity) deletes.push(DELETE.from(req.target).where(targetWhere))
|
|
654
|
+
await Promise.all(deletes)
|
|
655
|
+
return req.data
|
|
656
|
+
}
|
|
657
|
+
exports.onDelete = onDelete
|
|
@@ -184,7 +184,7 @@ const _aliased = (arr, columns, alias) =>
|
|
|
184
184
|
|
|
185
185
|
// Only works for root entity, otherwise the relative position needs to be adapted
|
|
186
186
|
const removeDraftUUIDIfNecessary = req =>
|
|
187
|
-
req.
|
|
187
|
+
req.http?.req?.headers?.['x-cds-odata-version'] === 'v2'
|
|
188
188
|
? () => {}
|
|
189
189
|
: result => delete result.DraftAdministrativeData_DraftUUID
|
|
190
190
|
|
|
@@ -7,6 +7,17 @@ const hana = require('./driver')
|
|
|
7
7
|
const _require = require('../common/utils/require')
|
|
8
8
|
const getError = require('../common/error')
|
|
9
9
|
|
|
10
|
+
function multiTenantServiceManager() {
|
|
11
|
+
try {
|
|
12
|
+
// Make sure cds-mtxs APIs are loaded
|
|
13
|
+
require('@sap/cds-mtxs/lib') // eslint-disable-line cds/no-missing-dependencies
|
|
14
|
+
} catch (e) {
|
|
15
|
+
if (e.code === 'MODULE_NOT_FOUND') return null
|
|
16
|
+
else throw e
|
|
17
|
+
}
|
|
18
|
+
return cds.env.requires['cds.xt.DeploymentService']?.['old-instance-manager'] ? null : cds.xt?.serviceManager
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
function multiTenantInstanceManager(config = cds.env.requires.db) {
|
|
11
22
|
const { credentials } = config
|
|
12
23
|
|
|
@@ -63,10 +74,14 @@ async function credentials4(tenant, db) {
|
|
|
63
74
|
if (!db._instance_manager) {
|
|
64
75
|
const opts = db.options && db.options.credentials ? db.options : undefined
|
|
65
76
|
db._instance_manager = cds.requires.multitenancy
|
|
66
|
-
? await multiTenantInstanceManager(opts)
|
|
77
|
+
? multiTenantServiceManager() ?? (await multiTenantInstanceManager(opts))
|
|
67
78
|
: singleTenantInstanceManager(opts)
|
|
68
79
|
}
|
|
69
80
|
|
|
81
|
+
if (cds.xt?.serviceManager && !cds.env.requires['cds.xt.DeploymentService']?.['old-instance-manager']) {
|
|
82
|
+
return (await db._instance_manager.get(tenant)).credentials
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
return new Promise((resolve, reject) => {
|
|
71
86
|
db._instance_manager.get(tenant, (err, res) => {
|
|
72
87
|
if (err) return reject(err)
|