@sap/cds 6.6.1 → 6.7.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 +67 -3
- package/README.md +1 -1
- package/apis/connect.d.ts +11 -4
- package/apis/core.d.ts +1 -1
- package/apis/csn.d.ts +1 -0
- package/apis/internal/inference.d.ts +15 -2
- package/apis/log.d.ts +10 -0
- package/apis/serve.d.ts +4 -9
- package/apis/services.d.ts +86 -19
- package/bin/build/buildTaskEngine.js +16 -42
- package/bin/build/constants.js +4 -2
- package/bin/build/provider/buildTaskProviderInternal.js +117 -85
- package/bin/build/provider/hana/index.js +6 -1
- package/bin/build/provider/mtx-extension/index.js +74 -34
- package/bin/build/provider/mtx-sidecar/index.js +3 -3
- package/bin/build/provider/nodejs/index.js +2 -2
- package/bin/build/util.js +63 -14
- package/bin/cds-serve.js +6 -0
- package/bin/cds.js +20 -4
- package/bin/deploy/to-hana/cfUtil.js +15 -1
- package/bin/deploy/to-hana/hana.js +1 -1
- package/bin/deploy/to-hana/hdiDeployUtil.js +1 -1
- package/bin/mtx/in-cds.js +2 -9
- package/bin/plugins.js +31 -0
- package/bin/serve.js +12 -12
- package/lib/compile/etc/_localized.js +1 -1
- package/lib/compile/for/lean_drafts.js +22 -6
- package/lib/compile/for/nodejs.js +4 -1
- package/lib/compile/load.js +4 -2
- package/lib/core/index.js +35 -15
- package/lib/dbs/cds-deploy.js +129 -133
- package/lib/env/cds-env.js +25 -17
- package/lib/env/cds-requires.js +10 -40
- package/lib/env/compat.js +12 -0
- package/lib/env/defaults.js +17 -9
- package/lib/env/plugins.js +29 -0
- package/lib/env/schemas/cds-rc.json +14 -0
- package/lib/index.js +3 -0
- package/lib/log/cds-log.js +7 -4
- package/lib/ql/CREATE.js +1 -1
- package/lib/ql/DELETE.js +1 -1
- package/lib/ql/DROP.js +3 -3
- package/lib/ql/INSERT.js +1 -1
- package/lib/ql/Query.js +14 -6
- package/lib/ql/SELECT.js +8 -2
- package/lib/ql/UPDATE.js +1 -1
- package/lib/ql/Whereable.js +1 -1
- package/lib/ql/cds-ql.js +1 -9
- package/lib/req/cds-context.js +1 -4
- package/lib/req/request.js +63 -2
- package/lib/req/response.js +3 -2
- package/lib/srv/bindings.js +69 -71
- package/lib/srv/cds-connect.js +4 -1
- package/lib/srv/cds-serve.js +4 -0
- package/lib/srv/middlewares/index.js +37 -6
- package/lib/srv/protocols/_legacy.js +1 -1
- package/lib/srv/protocols/index.js +1 -1
- package/lib/srv/srv-api.js +4 -6
- package/lib/srv/srv-dispatch.js +4 -3
- package/lib/srv/srv-handlers.js +1 -1
- package/lib/srv/srv-methods.js +8 -2
- package/lib/utils/cds-test.js +4 -1
- package/libx/_runtime/audit/Service.js +8 -9
- package/libx/_runtime/audit/generic/personal/index.js +1 -1
- package/libx/_runtime/audit/generic/personal/utils.js +1 -1
- package/libx/_runtime/audit/utils/v2.js +17 -20
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +11 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +4 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -1
- package/libx/_runtime/cds-services/services/Service.js +1 -1
- package/libx/_runtime/cds-services/util/assert.js +41 -65
- package/libx/_runtime/common/code-ext/WorkerPool.js +90 -0
- package/libx/_runtime/common/code-ext/WorkerReq.js +0 -4
- package/libx/_runtime/common/code-ext/execute.js +28 -18
- package/libx/_runtime/common/code-ext/handlers.js +5 -4
- package/libx/_runtime/common/code-ext/worker.js +45 -3
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +8 -7
- package/libx/_runtime/common/composition/delete.js +1 -1
- package/libx/_runtime/common/composition/update.js +3 -5
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/readOnly.js +5 -4
- package/libx/_runtime/common/generic/auth/restrict.js +7 -2
- package/libx/_runtime/common/generic/crud.js +12 -1
- package/libx/_runtime/common/generic/etag.js +11 -3
- package/libx/_runtime/common/generic/input.js +8 -6
- package/libx/_runtime/common/generic/paging.js +25 -8
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +0 -1
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +2 -2
- package/libx/_runtime/common/utils/resolveView.js +14 -10
- package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -3
- package/libx/_runtime/common/utils/templateProcessor.js +15 -17
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +18 -6
- package/libx/_runtime/db/Service.js +1 -0
- package/libx/_runtime/db/data-conversion/post-processing.js +0 -18
- package/libx/_runtime/db/expand/expand-v2.js +2 -2
- package/libx/_runtime/db/expand/rawToExpanded.js +6 -6
- package/libx/_runtime/db/generic/integrity.js +1 -1
- package/libx/_runtime/db/utils/columns.js +5 -5
- package/libx/_runtime/fiori/generic/activate.js +3 -3
- package/libx/_runtime/fiori/generic/edit.js +1 -1
- package/libx/_runtime/fiori/generic/new.js +4 -0
- package/libx/_runtime/fiori/lean-draft.js +138 -46
- package/libx/_runtime/hana/execute.js +3 -1
- package/libx/_runtime/hana/pool.js +10 -2
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +6 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/remote/Service.js +16 -13
- package/libx/_runtime/remote/utils/client.js +6 -1
- package/libx/_runtime/sqlite/Service.js +5 -59
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -2
- package/libx/_runtime/sqlite/execute.js +3 -1
- package/libx/_runtime/types/api.js +12 -3
- package/libx/odata/afterburner.js +36 -0
- package/libx/odata/cqn2odata.js +1 -1
- package/libx/odata/grammar.pegjs +5 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +1 -1
- package/libx/rest/RestAdapter.js +1 -1
- package/libx/rest/RestRequest.js +1 -0
- package/package.json +5 -2
- package/libx/_runtime/common/code-ext/workerQuery.js +0 -45
- package/libx/_runtime/common/constants/limit.js +0 -12
- package/libx/_runtime/common/utils/page.js +0 -39
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
2
|
const OutboxService = require('../messaging/Outbox')
|
|
3
|
-
|
|
4
3
|
const v2utils = require('./utils/v2')
|
|
5
|
-
|
|
6
4
|
const ANONYMOUS = 'anonymous'
|
|
7
5
|
|
|
8
6
|
const _getTenantAndUser = () => ({
|
|
9
|
-
user:
|
|
10
|
-
tenant: cds.context
|
|
7
|
+
user: cds.context?.user?.id ?? ANONYMOUS,
|
|
8
|
+
tenant: cds.context?.tenant
|
|
11
9
|
})
|
|
12
10
|
|
|
13
11
|
module.exports = class AuditLogService extends OutboxService {
|
|
@@ -82,11 +80,8 @@ module.exports = class AuditLogService extends OutboxService {
|
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
// write the logs
|
|
85
|
-
await Promise.all(
|
|
86
|
-
|
|
87
|
-
v2utils.sendDataAccessLog(entry).catch(err => errors.push(err))
|
|
88
|
-
})
|
|
89
|
-
)
|
|
83
|
+
await Promise.all(entries.map(entry => v2utils.sendDataAccessLog(entry).catch(err => errors.push(err))))
|
|
84
|
+
|
|
90
85
|
if (errors.length) {
|
|
91
86
|
throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
|
|
92
87
|
}
|
|
@@ -108,6 +103,7 @@ module.exports = class AuditLogService extends OutboxService {
|
|
|
108
103
|
v2utils.sendDataModificationLog(entry).catch(err => errors.push(err))
|
|
109
104
|
})
|
|
110
105
|
)
|
|
106
|
+
|
|
111
107
|
if (errors.length) {
|
|
112
108
|
throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
|
|
113
109
|
}
|
|
@@ -124,10 +120,12 @@ module.exports = class AuditLogService extends OutboxService {
|
|
|
124
120
|
tenant = parsed.tenant
|
|
125
121
|
delete parsed.tenant
|
|
126
122
|
}
|
|
123
|
+
|
|
127
124
|
if (parsed.user && typeof parsed.user === 'string') {
|
|
128
125
|
user = parsed.user
|
|
129
126
|
delete parsed.user
|
|
130
127
|
}
|
|
128
|
+
|
|
131
129
|
data = JSON.stringify(parsed)
|
|
132
130
|
} catch (e) {
|
|
133
131
|
// ignore
|
|
@@ -157,6 +155,7 @@ module.exports = class AuditLogService extends OutboxService {
|
|
|
157
155
|
v2utils.sendConfigChangeLog(entry).catch(err => errors.push(err))
|
|
158
156
|
})
|
|
159
157
|
)
|
|
158
|
+
|
|
160
159
|
if (errors.length) {
|
|
161
160
|
throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
|
|
162
161
|
}
|
|
@@ -8,7 +8,7 @@ const {
|
|
|
8
8
|
const { auditAccessHandler } = require('./access')
|
|
9
9
|
|
|
10
10
|
exports.impl = cds.service.impl(function () {
|
|
11
|
-
if (!cds.db) return cds.on('connect', srv => srv
|
|
11
|
+
if (!cds.db) return cds.on('connect', srv => srv.isDatabaseService && exports.impl.call(this))
|
|
12
12
|
// REVISIT: diff() doesn't work in srv after phase but foreign key propagation has not yet taken place in srv before phase
|
|
13
13
|
// -> calc diff in db layer and store in audit data structure at context
|
|
14
14
|
// -> REVISIT for GA: clear req._.partialPersistentState?
|
|
@@ -7,7 +7,7 @@ const WRITE = { CREATE: 1, UPDATE: 1, DELETE: 1 }
|
|
|
7
7
|
const getMapKeyForCurrentRequest = req => {
|
|
8
8
|
// running in srv or db layer? -> srv's req.query used as key of diff and logs maps at req.context
|
|
9
9
|
// REVISIT: req._tx should not be used like that!
|
|
10
|
-
return req.tx
|
|
10
|
+
return req.tx.isDatabaseService ? req._.query : req.query
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const getRootEntity = element => {
|
|
@@ -3,26 +3,23 @@ const LOG = cds.log('audit-log')
|
|
|
3
3
|
|
|
4
4
|
const { getObjectAndDataSubject, getAttributeToLog } = require('./log')
|
|
5
5
|
|
|
6
|
-
function connect(credentials) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return resolve()
|
|
24
|
-
}
|
|
25
|
-
})
|
|
6
|
+
async function connect(credentials) {
|
|
7
|
+
let auditLogging
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
11
|
+
auditLogging = require('@sap/audit-logging')
|
|
12
|
+
} catch (error) {
|
|
13
|
+
// not able to require lib -> no audit logging ootb
|
|
14
|
+
return Promise.resolve()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
return await auditLogging.v2(credentials)
|
|
19
|
+
} catch (error) {
|
|
20
|
+
LOG._warn && LOG.warn('Unable to initialize audit-logging client with error:', error)
|
|
21
|
+
return Promise.resolve()
|
|
22
|
+
}
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
function sendDataAccessLog(entry) {
|
|
@@ -21,7 +21,8 @@ const { isStreaming, getStreamProperties } = require('../utils/stream')
|
|
|
21
21
|
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
22
22
|
const getError = require('../../../../common/error')
|
|
23
23
|
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
24
|
-
const {
|
|
24
|
+
const { getPageSize, commonGenericPaging } = require('../../../../common/generic/paging')
|
|
25
|
+
const { handler: commonGenericSorting } = require('../../../../common/generic/sorting')
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Checks whether a bound function or function import is invoked.
|
|
@@ -255,8 +256,12 @@ const _reliablePagingPossible = req => {
|
|
|
255
256
|
if (req.target._isDraftEnabled) return false
|
|
256
257
|
if (cds.context?.http.req.query.$apply) return false
|
|
257
258
|
if (req.query.SELECT.limit.offset?.val ?? req.query.SELECT.limit.offset > 0) return false
|
|
258
|
-
if (req.query.SELECT.orderBy
|
|
259
|
-
return
|
|
259
|
+
if (req.query.SELECT.orderBy?.some(o => !o.ref)) return false
|
|
260
|
+
return (
|
|
261
|
+
!req.query.SELECT.columns ||
|
|
262
|
+
req.query.SELECT.columns.some(c => c === '*' || c.ref?.[0] === '*') ||
|
|
263
|
+
req.query.SELECT.orderBy?.every(o => req.query.SELECT.columns?.some(c => o.ref[0] === c.ref?.[0]))
|
|
264
|
+
)
|
|
260
265
|
}
|
|
261
266
|
|
|
262
267
|
/**
|
|
@@ -270,6 +275,8 @@ const _reliablePagingPossible = req => {
|
|
|
270
275
|
*/
|
|
271
276
|
// eslint-disable-next-line complexity
|
|
272
277
|
const _readCollection = async (tx, req, odataReq) => {
|
|
278
|
+
commonGenericPaging(req)
|
|
279
|
+
commonGenericSorting(req)
|
|
273
280
|
const result = (await tx.dispatch(req)) || []
|
|
274
281
|
if (Array.isArray(req.query)) {
|
|
275
282
|
const adjustedResult = []
|
|
@@ -288,7 +295,7 @@ const _readCollection = async (tx, req, odataReq) => {
|
|
|
288
295
|
} else if (req.query.SELECT.count && !('$count' in result)) result.$count = 0
|
|
289
296
|
|
|
290
297
|
const limit = Array.isArray(req.query)
|
|
291
|
-
?
|
|
298
|
+
? getPageSize(req.query[0]._target).max
|
|
292
299
|
: req.query.SELECT.limit && req.query.SELECT.limit.rows && req.query.SELECT.limit.rows.val
|
|
293
300
|
const top = odataReq.getUriInfo().getQueryOption(QueryOptions.TOP)
|
|
294
301
|
if (limit && limit === result.length && limit !== top && !('$nextLink' in result)) {
|
|
@@ -11,7 +11,6 @@ const { isReturnMinimal } = require('../utils/handlerUtils')
|
|
|
11
11
|
const { readAfterWrite } = require('../utils/readAfterWrite')
|
|
12
12
|
const { toODataResult, postProcess, postProcessMinimal } = require('../utils/result')
|
|
13
13
|
const { hasOmitValuesPreference } = require('../utils/omitValues')
|
|
14
|
-
const { isStreaming } = require('../utils/stream')
|
|
15
14
|
|
|
16
15
|
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
17
16
|
|
|
@@ -36,7 +36,7 @@ const expandToCQN = require('./expandToCQN')
|
|
|
36
36
|
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
37
37
|
const { isStreaming } = require('../utils/stream')
|
|
38
38
|
|
|
39
|
-
const {
|
|
39
|
+
const { getPageSize } = require('../../../../common/generic/paging')
|
|
40
40
|
const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
|
|
41
41
|
|
|
42
42
|
const { ensureUnlocalized } = require('../../../../fiori/utils/handler')
|
|
@@ -139,9 +139,9 @@ const _apply = (uriInfo, queryOptions, entity, model) => {
|
|
|
139
139
|
|
|
140
140
|
const _topSkip = (queryOptions, target, cqn) => {
|
|
141
141
|
if (queryOptions && (queryOptions.$top || queryOptions.$skip)) {
|
|
142
|
-
const top = queryOptions.$top ? parseInt(queryOptions.$top) :
|
|
142
|
+
const top = queryOptions.$top ? parseInt(queryOptions.$top) : getPageSize(target).default
|
|
143
143
|
const skip = parseInt(queryOptions.$skip || 0)
|
|
144
|
-
cqn.limit(Math.min(top,
|
|
144
|
+
cqn.limit(Math.min(top, getPageSize(target).max), skip)
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -9,7 +9,7 @@ const { deepCopy } = require('../../../../common/utils/copy')
|
|
|
9
9
|
const { getSegmentKeyValue } = require('../odata-to-cqn/utils')
|
|
10
10
|
|
|
11
11
|
const _isFunctionInvocation = req =>
|
|
12
|
-
req.getUriInfo().getLastSegment().getFunction || req.getUriInfo().getLastSegment().getFunctionImport
|
|
12
|
+
req.getUriInfo().getLastSegment().getFunction() || req.getUriInfo().getLastSegment().getFunctionImport()
|
|
13
13
|
|
|
14
14
|
const _addStructuredProperties = ([structName, property, ...nestedProperties], paramData, value) => {
|
|
15
15
|
paramData[structName] = paramData[structName] || {}
|
|
@@ -94,14 +94,14 @@ const _columnsFromQuery = (columns, target, options) => {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
const _processFn = columns => {
|
|
97
|
-
return ({ row, key, element,
|
|
97
|
+
return ({ row, key, element, pathSegmentsInfo }) => {
|
|
98
98
|
if (!(key in row) || row[key] === null) return
|
|
99
99
|
let cur = columns
|
|
100
100
|
if (element.parent._isStructured) {
|
|
101
|
-
const prefix =
|
|
101
|
+
const prefix = pathSegmentsInfo.join('/')
|
|
102
102
|
key = `${prefix}/${key}`
|
|
103
103
|
} else {
|
|
104
|
-
for (let p of
|
|
104
|
+
for (let p of pathSegmentsInfo) {
|
|
105
105
|
if (!cur[p]) cur[p] = {}
|
|
106
106
|
cur = cur[p]
|
|
107
107
|
}
|
|
@@ -116,7 +116,7 @@ const _columnsFromData = (data, definition, service) => {
|
|
|
116
116
|
if (!template || !template.elements.size) return ''
|
|
117
117
|
const arrayData = Array.isArray(data) ? data : data ? [data] : []
|
|
118
118
|
for (const row of arrayData) {
|
|
119
|
-
templateProcessor({ processFn: _processFn(columns), row, template, pathOptions: {
|
|
119
|
+
templateProcessor({ processFn: _processFn(columns), row, template, pathOptions: { pathSegmentsInfo: [] } })
|
|
120
120
|
}
|
|
121
121
|
return _stringifyColumnsFromData(columns)
|
|
122
122
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { getPageSize } = require('../../../../common/generic/paging')
|
|
2
2
|
const { findCsnTargetFor } = require('../../../../common/utils/csn')
|
|
3
3
|
|
|
4
4
|
const _getEntitySets = (edm, namespace) => {
|
|
@@ -37,7 +37,7 @@ const oDataConfiguration = (edm, csn) => {
|
|
|
37
37
|
const e = findCsnTargetFor(entitySet, csn, namespace)
|
|
38
38
|
|
|
39
39
|
configuration[entitySet] = {
|
|
40
|
-
maxPageSize:
|
|
40
|
+
maxPageSize: getPageSize(e).max,
|
|
41
41
|
isConcurrent: !!e._etag
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -273,7 +273,7 @@ const _pick = options => (element, target) => {
|
|
|
273
273
|
|
|
274
274
|
if (element['@odata.etag']) categories.push('@odata.etag')
|
|
275
275
|
if (element._type === 'cds.Decimal') categories.push('@cds.Decimal')
|
|
276
|
-
if (cds.db?.
|
|
276
|
+
if (cds.db?.cqn2sql && element._type === 'cds.Boolean') categories.push('@cds.Boolean') // REVISIT: violates modularization -> do we still need that?
|
|
277
277
|
|
|
278
278
|
categories.push(..._assocs(element, target))
|
|
279
279
|
|
|
@@ -53,7 +53,7 @@ class ApplicationService extends cds.Service {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
registerFioriHandlers() {
|
|
56
|
-
if (cds.env.
|
|
56
|
+
if (cds.env.fiori.lean_draft) {
|
|
57
57
|
const { onNew, onPrepare, onEdit, onCancel } = require('../../fiori/lean-draft')
|
|
58
58
|
|
|
59
59
|
for (const each of this.entities)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const LOG = cds.log('app')
|
|
3
|
+
const templatePathSerializer = require('../../common/utils/templateProcessorPathSerializer')
|
|
3
4
|
|
|
4
5
|
// REVISIT: replace with cds.Request
|
|
5
6
|
const getEntry = require('../../common/error/entry')
|
|
@@ -42,7 +43,7 @@ const _enumValues = element => {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// REVISIT: this needs a cleanup!
|
|
45
|
-
const assertError = (code, element, value, key,
|
|
46
|
+
const assertError = (code, element, value, key, path) => {
|
|
46
47
|
let args
|
|
47
48
|
|
|
48
49
|
if (typeof code === 'object') {
|
|
@@ -51,14 +52,12 @@ const assertError = (code, element, value, key, pathSegments = []) => {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
const { name, type, precision, scale } = element
|
|
54
|
-
const path = `${pathSegments.join('/')}${pathSegments.length ? '/' : ''}${name || key}`
|
|
55
|
-
|
|
56
55
|
const error = new Error()
|
|
57
56
|
const errorEntry = {
|
|
58
57
|
code,
|
|
59
58
|
message: code,
|
|
60
|
-
target: path,
|
|
61
|
-
args: args
|
|
59
|
+
target: path ?? element.name ?? key,
|
|
60
|
+
args: args ?? [name ?? key]
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
const assertError = Object.assign(error, getEntry(errorEntry))
|
|
@@ -81,13 +80,9 @@ const assertError = (code, element, value, key, pathSegments = []) => {
|
|
|
81
80
|
return assertError
|
|
82
81
|
}
|
|
83
82
|
|
|
84
|
-
const _checkString = value =>
|
|
85
|
-
return typeof value === 'string'
|
|
86
|
-
}
|
|
83
|
+
const _checkString = value => typeof value === 'string'
|
|
87
84
|
|
|
88
|
-
const _checkNumber = value =>
|
|
89
|
-
return typeof value === 'number'
|
|
90
|
-
}
|
|
85
|
+
const _checkNumber = value => typeof value === 'number'
|
|
91
86
|
|
|
92
87
|
const _checkDecimal = (value, element) => {
|
|
93
88
|
const [left, right] = String(value).split('.')
|
|
@@ -98,38 +93,24 @@ const _checkDecimal = (value, element) => {
|
|
|
98
93
|
)
|
|
99
94
|
}
|
|
100
95
|
|
|
101
|
-
const _checkInteger = value =>
|
|
102
|
-
return _checkNumber(value) && parseInt(value, 10) === value
|
|
103
|
-
}
|
|
96
|
+
const _checkInteger = value => _checkNumber(value) && parseInt(value, 10) === value
|
|
104
97
|
|
|
105
|
-
const _checkBoolean = value =>
|
|
106
|
-
return typeof value === 'boolean'
|
|
107
|
-
}
|
|
98
|
+
const _checkBoolean = value => typeof value === 'boolean'
|
|
108
99
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return Buffer.isBuffer(value) || value.type === 'Buffer'
|
|
112
|
-
}
|
|
100
|
+
// REVISIT: Extension parameter in push is an object with buffer data
|
|
101
|
+
const _checkBuffer = value => Buffer.isBuffer(value) || value.type === 'Buffer'
|
|
113
102
|
|
|
114
103
|
const _checkUUID = value => {
|
|
115
104
|
return _checkString(value) && UUID_REGEX.test(value)
|
|
116
105
|
}
|
|
117
106
|
|
|
118
|
-
const _checkISODate = value =>
|
|
119
|
-
return (_checkString(value) && ISO_DATE_REGEX.test(value)) || value instanceof Date
|
|
120
|
-
}
|
|
107
|
+
const _checkISODate = value => (_checkString(value) && ISO_DATE_REGEX.test(value)) || value instanceof Date
|
|
121
108
|
|
|
122
|
-
const _checkISOTime = value =>
|
|
123
|
-
return _checkString(value) && ISO_TIME_REGEX.test(value)
|
|
124
|
-
}
|
|
109
|
+
const _checkISOTime = value => _checkString(value) && ISO_TIME_REGEX.test(value)
|
|
125
110
|
|
|
126
|
-
const _checkISODateTime = value =>
|
|
127
|
-
return (_checkString(value) && ISO_DATE_TIME_REGEX.test(value)) || value instanceof Date
|
|
128
|
-
}
|
|
111
|
+
const _checkISODateTime = value => (_checkString(value) && ISO_DATE_TIME_REGEX.test(value)) || value instanceof Date
|
|
129
112
|
|
|
130
|
-
const _checkISOTimestamp = value =>
|
|
131
|
-
return (_checkString(value) && ISO_TIMESTAMP_REGEX.test(value)) || value instanceof Date
|
|
132
|
-
}
|
|
113
|
+
const _checkISOTimestamp = value => (_checkString(value) && ISO_TIMESTAMP_REGEX.test(value)) || value instanceof Date
|
|
133
114
|
|
|
134
115
|
const _checkInRange = (val, range) => {
|
|
135
116
|
return _checkISODate(val)
|
|
@@ -137,10 +118,9 @@ const _checkInRange = (val, range) => {
|
|
|
137
118
|
: (val - range[0]) * (val - range[1]) <= 0
|
|
138
119
|
}
|
|
139
120
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
121
|
+
// process.env.CDS_ASSERT_FORMAT_FLAGS not official!
|
|
122
|
+
const _checkRegExpFormat = (val, format) =>
|
|
123
|
+
_checkString(val) && val.match(new RegExp(format, process.env.CDS_ASSERT_FORMAT_FLAGS || 'u'))
|
|
144
124
|
|
|
145
125
|
const CDS_TYPE_CHECKS = {
|
|
146
126
|
'cds.UUID': _checkUUID,
|
|
@@ -215,28 +195,23 @@ const checkStaticElementByKey = (definition, key, value, result = [], ignoreNonM
|
|
|
215
195
|
return result
|
|
216
196
|
}
|
|
217
197
|
|
|
218
|
-
const _isNotFilled = value =>
|
|
219
|
-
|
|
220
|
-
}
|
|
198
|
+
const _isNotFilled = value =>
|
|
199
|
+
value === null || value === undefined || (typeof value === 'string' && value.trim() === '')
|
|
221
200
|
|
|
222
|
-
const _checkMandatoryElement = (element, value, errors, key,
|
|
201
|
+
const _checkMandatoryElement = (element, value, errors, key, pathSegmentsInfo) => {
|
|
223
202
|
if (element.parent?.query?.SELECT?.columns?.find(col => _isNavigationColumn(col, element.name))) return
|
|
224
203
|
if (element._isMandatory && !element.default && _isNotFilled(value)) {
|
|
225
|
-
errors.push(assertError(ASSERT_NOT_NULL, element, value, key,
|
|
204
|
+
errors.push(assertError(ASSERT_NOT_NULL, element, value, key, pathSegmentsInfo))
|
|
226
205
|
}
|
|
227
206
|
}
|
|
228
207
|
|
|
229
|
-
const _isNavigationColumn = (column, searched) =>
|
|
230
|
-
|
|
231
|
-
column.ref && column.ref.length > 1 && (column.as === searched || column.ref[column.ref.length - 1] === searched)
|
|
232
|
-
)
|
|
233
|
-
}
|
|
208
|
+
const _isNavigationColumn = (column, searched) =>
|
|
209
|
+
column.ref?.length > 1 && (column.as === searched || column.ref[column.ref.length - 1] === searched)
|
|
234
210
|
|
|
235
|
-
const _getEnumElement = element =>
|
|
236
|
-
|
|
237
|
-
}
|
|
211
|
+
const _getEnumElement = element =>
|
|
212
|
+
(element['@assert.range'] && element.enum) || element['@assert.enum'] ? element.enum : undefined
|
|
238
213
|
|
|
239
|
-
const _checkEnumElement = (element, value, errors, key,
|
|
214
|
+
const _checkEnumElement = (element, value, errors, key, pathSegmentsInfo) => {
|
|
240
215
|
const enumElements = _getEnumElement(element)
|
|
241
216
|
const enumValues = enumElements && _enumValues(enumElements)
|
|
242
217
|
|
|
@@ -246,15 +221,15 @@ const _checkEnumElement = (element, value, errors, key, pathSegments) => {
|
|
|
246
221
|
? ['"' + value + '"', enumValues.map(ele => '"' + ele + '"').join(', ')]
|
|
247
222
|
: [value, enumValues.join(', ')]
|
|
248
223
|
|
|
249
|
-
errors.push(assertError({ code: ASSERT_ENUM, args }, element, value, key,
|
|
224
|
+
errors.push(assertError({ code: ASSERT_ENUM, args }, element, value, key, pathSegmentsInfo))
|
|
250
225
|
}
|
|
251
226
|
}
|
|
252
227
|
|
|
253
|
-
const _checkRangeElement = (element, value, errors, key,
|
|
228
|
+
const _checkRangeElement = (element, value, errors, key, pathSegmentsInfo) => {
|
|
254
229
|
const rangeElements = element['@assert.range'] && !_getEnumElement(element) ? element['@assert.range'] : undefined
|
|
255
230
|
if (rangeElements && !_checkInRange(value, rangeElements)) {
|
|
256
231
|
const args = [value, ...element['@assert.range']]
|
|
257
|
-
errors.push(assertError({ code: ASSERT_RANGE, args }, element, value, key,
|
|
232
|
+
errors.push(assertError({ code: ASSERT_RANGE, args }, element, value, key, pathSegmentsInfo))
|
|
258
233
|
}
|
|
259
234
|
}
|
|
260
235
|
|
|
@@ -268,19 +243,18 @@ const _checkFormatElement = (element, value, errors, key, pathSegments) => {
|
|
|
268
243
|
/**
|
|
269
244
|
* @param {import('../../types/api').InputConstraints} constraints
|
|
270
245
|
*/
|
|
271
|
-
const checkInputConstraints = ({ element, value, errors, key,
|
|
246
|
+
const checkInputConstraints = ({ element, value, errors, key, pathSegmentsInfo }) => {
|
|
272
247
|
if (!element) return errors
|
|
248
|
+
let path
|
|
273
249
|
|
|
274
|
-
|
|
250
|
+
if (pathSegmentsInfo?.length) path = templatePathSerializer(element.name || key, pathSegmentsInfo)
|
|
251
|
+
_checkMandatoryElement(element, value, errors, key, path)
|
|
275
252
|
|
|
276
253
|
if (value == null) return errors
|
|
277
254
|
|
|
278
|
-
_checkEnumElement(element, value, errors, key,
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
_checkFormatElement(element, value, errors, key, pathSegments)
|
|
283
|
-
|
|
255
|
+
_checkEnumElement(element, value, errors, key, path)
|
|
256
|
+
_checkRangeElement(element, value, errors, key, path)
|
|
257
|
+
_checkFormatElement(element, value, errors, key, path)
|
|
284
258
|
return errors
|
|
285
259
|
}
|
|
286
260
|
|
|
@@ -323,7 +297,7 @@ const assertNotNullError = element => assertError(ASSERT_NOT_NULL, element)
|
|
|
323
297
|
*
|
|
324
298
|
* @param {import('../../types/api').assertTargetMap} assertMap
|
|
325
299
|
* @param {array} errors An array to appends the possible errors.
|
|
326
|
-
* @see {@link https://
|
|
300
|
+
* @see {@link https://cap.cloud.sap/docs/guides/providing-services#assert-target @assert.target} for
|
|
327
301
|
* further information.
|
|
328
302
|
*/
|
|
329
303
|
const assertTargets = async (assertMap, errors) => {
|
|
@@ -358,9 +332,11 @@ const assertTargets = async (assertMap, errors) => {
|
|
|
358
332
|
allTargets
|
|
359
333
|
.filter(t => t.key === target.key)
|
|
360
334
|
.forEach(target => {
|
|
361
|
-
const { row,
|
|
335
|
+
const { row, pathSegmentsInfo } = target.assocInfo
|
|
362
336
|
const key = target.foreignKey.name
|
|
363
|
-
|
|
337
|
+
let path
|
|
338
|
+
if (pathSegmentsInfo?.length) path = templatePathSerializer(key, pathSegmentsInfo)
|
|
339
|
+
const error = assertError('ASSERT_TARGET', target.foreignKey, row[key], key, path)
|
|
364
340
|
errors.push(error)
|
|
365
341
|
})
|
|
366
342
|
})
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
const os = require('os')
|
|
3
|
+
const { Worker } = require('worker_threads')
|
|
4
|
+
|
|
5
|
+
class ExtensionWorker extends Worker {
|
|
6
|
+
constructor(id, workerPath, options) {
|
|
7
|
+
super(workerPath, options)
|
|
8
|
+
this.id = id
|
|
9
|
+
this.tasksAssigned = 0
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class WorkerPool {
|
|
14
|
+
static instances = []
|
|
15
|
+
constructor(workerPath, options) {
|
|
16
|
+
this.workerPath = workerPath
|
|
17
|
+
this.options = options
|
|
18
|
+
this.size = options.size ?? Math.max(os.cpus().length, 1)
|
|
19
|
+
this.idleWorkers = new Set()
|
|
20
|
+
this.workers = []
|
|
21
|
+
WorkerPool.instances.push(this)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#createWorker() {
|
|
25
|
+
const id = cds.utils.uuid()
|
|
26
|
+
const worker = new ExtensionWorker(id, this.workerPath, {
|
|
27
|
+
workerData: { id },
|
|
28
|
+
resourceLimits: this.options.resourceLimits
|
|
29
|
+
})
|
|
30
|
+
worker.on('exit', this.#onWorkerExit.bind(this, worker))
|
|
31
|
+
return worker
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#onWorkerExit(worker) {
|
|
35
|
+
this.idleWorkers.delete(worker)
|
|
36
|
+
this.workers.splice(this.workers.indexOf(worker), 1)
|
|
37
|
+
worker.tasksAssigned = 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
adquire() {
|
|
41
|
+
if (this.idleWorkers.size === 0 && this.workers.length < this.size) {
|
|
42
|
+
const worker = this.#createWorker()
|
|
43
|
+
this.idleWorkers.add(worker)
|
|
44
|
+
this.workers.push(worker)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const worker = this.idleWorkers.values().next().value
|
|
48
|
+
|
|
49
|
+
if (worker) {
|
|
50
|
+
this.idleWorkers.delete(worker)
|
|
51
|
+
worker.tasksAssigned++
|
|
52
|
+
return worker
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const randomWorkerIndex = Math.floor(Math.random() * this.workers.length)
|
|
56
|
+
const busyWorker = this.workers[randomWorkerIndex]
|
|
57
|
+
busyWorker.tasksAssigned++
|
|
58
|
+
return busyWorker
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
release(worker) {
|
|
62
|
+
if (worker.tasksAssigned === 0) return
|
|
63
|
+
|
|
64
|
+
worker.tasksAssigned--
|
|
65
|
+
if (worker.tasksAssigned === 0) {
|
|
66
|
+
this.idleWorkers.add(worker)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async destroy() {
|
|
71
|
+
if (this.workers.length === 0) return
|
|
72
|
+
|
|
73
|
+
const workers = Array.from(this.workers)
|
|
74
|
+
const iterable = workers.map(worker => {
|
|
75
|
+
worker.removeAllListeners()
|
|
76
|
+
return worker.terminate()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
await Promise.all(iterable)
|
|
80
|
+
this.idleWorkers = new Set()
|
|
81
|
+
this.workers = []
|
|
82
|
+
WorkerPool.instances = []
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static async destroyAll() {
|
|
86
|
+
for (const workerPool of WorkerPool.instances) await workerPool.destroy()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = WorkerPool
|