@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.
Files changed (132) hide show
  1. package/CHANGELOG.md +67 -3
  2. package/README.md +1 -1
  3. package/apis/connect.d.ts +11 -4
  4. package/apis/core.d.ts +1 -1
  5. package/apis/csn.d.ts +1 -0
  6. package/apis/internal/inference.d.ts +15 -2
  7. package/apis/log.d.ts +10 -0
  8. package/apis/serve.d.ts +4 -9
  9. package/apis/services.d.ts +86 -19
  10. package/bin/build/buildTaskEngine.js +16 -42
  11. package/bin/build/constants.js +4 -2
  12. package/bin/build/provider/buildTaskProviderInternal.js +117 -85
  13. package/bin/build/provider/hana/index.js +6 -1
  14. package/bin/build/provider/mtx-extension/index.js +74 -34
  15. package/bin/build/provider/mtx-sidecar/index.js +3 -3
  16. package/bin/build/provider/nodejs/index.js +2 -2
  17. package/bin/build/util.js +63 -14
  18. package/bin/cds-serve.js +6 -0
  19. package/bin/cds.js +20 -4
  20. package/bin/deploy/to-hana/cfUtil.js +15 -1
  21. package/bin/deploy/to-hana/hana.js +1 -1
  22. package/bin/deploy/to-hana/hdiDeployUtil.js +1 -1
  23. package/bin/mtx/in-cds.js +2 -9
  24. package/bin/plugins.js +31 -0
  25. package/bin/serve.js +12 -12
  26. package/lib/compile/etc/_localized.js +1 -1
  27. package/lib/compile/for/lean_drafts.js +22 -6
  28. package/lib/compile/for/nodejs.js +4 -1
  29. package/lib/compile/load.js +4 -2
  30. package/lib/core/index.js +35 -15
  31. package/lib/dbs/cds-deploy.js +129 -133
  32. package/lib/env/cds-env.js +25 -17
  33. package/lib/env/cds-requires.js +10 -40
  34. package/lib/env/compat.js +12 -0
  35. package/lib/env/defaults.js +17 -9
  36. package/lib/env/plugins.js +29 -0
  37. package/lib/env/schemas/cds-rc.json +14 -0
  38. package/lib/index.js +3 -0
  39. package/lib/log/cds-log.js +7 -4
  40. package/lib/ql/CREATE.js +1 -1
  41. package/lib/ql/DELETE.js +1 -1
  42. package/lib/ql/DROP.js +3 -3
  43. package/lib/ql/INSERT.js +1 -1
  44. package/lib/ql/Query.js +14 -6
  45. package/lib/ql/SELECT.js +8 -2
  46. package/lib/ql/UPDATE.js +1 -1
  47. package/lib/ql/Whereable.js +1 -1
  48. package/lib/ql/cds-ql.js +1 -9
  49. package/lib/req/cds-context.js +1 -4
  50. package/lib/req/request.js +63 -2
  51. package/lib/req/response.js +3 -2
  52. package/lib/srv/bindings.js +69 -71
  53. package/lib/srv/cds-connect.js +4 -1
  54. package/lib/srv/cds-serve.js +4 -0
  55. package/lib/srv/middlewares/index.js +37 -6
  56. package/lib/srv/protocols/_legacy.js +1 -1
  57. package/lib/srv/protocols/index.js +1 -1
  58. package/lib/srv/srv-api.js +4 -6
  59. package/lib/srv/srv-dispatch.js +4 -3
  60. package/lib/srv/srv-handlers.js +1 -1
  61. package/lib/srv/srv-methods.js +8 -2
  62. package/lib/utils/cds-test.js +4 -1
  63. package/libx/_runtime/audit/Service.js +8 -9
  64. package/libx/_runtime/audit/generic/personal/index.js +1 -1
  65. package/libx/_runtime/audit/generic/personal/utils.js +1 -1
  66. package/libx/_runtime/audit/utils/v2.js +17 -20
  67. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +11 -4
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +0 -1
  70. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +3 -3
  71. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +4 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +2 -2
  74. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -1
  75. package/libx/_runtime/cds-services/services/Service.js +1 -1
  76. package/libx/_runtime/cds-services/util/assert.js +41 -65
  77. package/libx/_runtime/common/code-ext/WorkerPool.js +90 -0
  78. package/libx/_runtime/common/code-ext/WorkerReq.js +0 -4
  79. package/libx/_runtime/common/code-ext/execute.js +28 -18
  80. package/libx/_runtime/common/code-ext/handlers.js +5 -4
  81. package/libx/_runtime/common/code-ext/worker.js +45 -3
  82. package/libx/_runtime/common/code-ext/workerQueryExecutor.js +8 -7
  83. package/libx/_runtime/common/composition/delete.js +1 -1
  84. package/libx/_runtime/common/composition/update.js +3 -5
  85. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  86. package/libx/_runtime/common/generic/auth/readOnly.js +5 -4
  87. package/libx/_runtime/common/generic/auth/restrict.js +7 -2
  88. package/libx/_runtime/common/generic/crud.js +12 -1
  89. package/libx/_runtime/common/generic/etag.js +11 -3
  90. package/libx/_runtime/common/generic/input.js +8 -6
  91. package/libx/_runtime/common/generic/paging.js +25 -8
  92. package/libx/_runtime/common/generic/put.js +1 -1
  93. package/libx/_runtime/common/generic/sorting.js +0 -1
  94. package/libx/_runtime/common/i18n/messages.properties +1 -0
  95. package/libx/_runtime/common/utils/cqn.js +5 -1
  96. package/libx/_runtime/common/utils/cqn2cqn4sql.js +2 -2
  97. package/libx/_runtime/common/utils/resolveView.js +14 -10
  98. package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -3
  99. package/libx/_runtime/common/utils/templateProcessor.js +15 -17
  100. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +18 -6
  101. package/libx/_runtime/db/Service.js +1 -0
  102. package/libx/_runtime/db/data-conversion/post-processing.js +0 -18
  103. package/libx/_runtime/db/expand/expand-v2.js +2 -2
  104. package/libx/_runtime/db/expand/rawToExpanded.js +6 -6
  105. package/libx/_runtime/db/generic/integrity.js +1 -1
  106. package/libx/_runtime/db/utils/columns.js +5 -5
  107. package/libx/_runtime/fiori/generic/activate.js +3 -3
  108. package/libx/_runtime/fiori/generic/edit.js +1 -1
  109. package/libx/_runtime/fiori/generic/new.js +4 -0
  110. package/libx/_runtime/fiori/lean-draft.js +138 -46
  111. package/libx/_runtime/hana/execute.js +3 -1
  112. package/libx/_runtime/hana/pool.js +10 -2
  113. package/libx/_runtime/messaging/common-utils/AMQPClient.js +6 -1
  114. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  115. package/libx/_runtime/remote/Service.js +16 -13
  116. package/libx/_runtime/remote/utils/client.js +6 -1
  117. package/libx/_runtime/sqlite/Service.js +5 -59
  118. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
  119. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -2
  120. package/libx/_runtime/sqlite/execute.js +3 -1
  121. package/libx/_runtime/types/api.js +12 -3
  122. package/libx/odata/afterburner.js +36 -0
  123. package/libx/odata/cqn2odata.js +1 -1
  124. package/libx/odata/grammar.pegjs +5 -3
  125. package/libx/odata/parser.js +1 -1
  126. package/libx/odata/utils.js +1 -1
  127. package/libx/rest/RestAdapter.js +1 -1
  128. package/libx/rest/RestRequest.js +1 -0
  129. package/package.json +5 -2
  130. package/libx/_runtime/common/code-ext/workerQuery.js +0 -45
  131. package/libx/_runtime/common/constants/limit.js +0 -12
  132. 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: (cds.context && cds.context.user && cds.context.user.id) || ANONYMOUS,
10
- tenant: cds.context && cds.context.tenant
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
- entries.map(entry => {
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 instanceof cds.DatabaseService && exports.impl.call(this))
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 instanceof cds.DatabaseService ? req._.query : req.query
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
- return new Promise((resolve, reject) => {
8
- let auditLogging
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 (e) {
13
- // not able to require lib -> no audit logging ootb
14
- return resolve()
15
- }
16
- try {
17
- auditLogging.v2(credentials, function (err, auditLog) {
18
- if (err) return reject(err)
19
- resolve(auditLog)
20
- })
21
- } catch (e) {
22
- LOG._warn && LOG.warn('Unable to initialize audit-logging client with error:', e)
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) {
@@ -255,6 +255,8 @@ class ODataRequest extends cds.Request {
255
255
  }
256
256
  })
257
257
 
258
+ Object.defineProperty(this, '_isOData', { value: true })
259
+
258
260
  /*
259
261
  * req.validateEtag()
260
262
  */
@@ -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 { getMaxPageSize } = require('../../../../common/utils/page')
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.some(o => !o.ref)) return false
259
- return req.query.SELECT.orderBy.every(o => req.query.SELECT.columns.some(c => o.ref[0] === c.ref[0]))
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
- ? getMaxPageSize(req.query[0]._target)
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 { getMaxPageSize, getDefaultPageSize } = require('../../../../common/utils/page')
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) : getDefaultPageSize(target)
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, getMaxPageSize(target)), skip)
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, pathSegments }) => {
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 = pathSegments.join('/')
101
+ const prefix = pathSegmentsInfo.join('/')
102
102
  key = `${prefix}/${key}`
103
103
  } else {
104
- for (let p of pathSegments) {
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: { pathSegments: [] } })
119
+ templateProcessor({ processFn: _processFn(columns), row, template, pathOptions: { pathSegmentsInfo: [] } })
120
120
  }
121
121
  return _stringifyColumnsFromData(columns)
122
122
  }
@@ -1,4 +1,4 @@
1
- const { getMaxPageSize } = require('../../../../common/utils/page')
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: getMaxPageSize(e),
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?.kind === 'better-sqlite' && element._type === 'cds.Boolean') categories.push('@cds.Boolean')
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.features.lean_draft) {
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, pathSegments = []) => {
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 || [name || key]
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
- const _checkBuffer = value => {
110
- // REVISIT: Extension parameter in push is an object with buffer data
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
- const _checkRegExpFormat = (val, format) => {
141
- // process.env.CDS_ASSERT_FORMAT_FLAGS not official!
142
- return _checkString(val) && val.match(new RegExp(format, process.env.CDS_ASSERT_FORMAT_FLAGS || 'u'))
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
- return value === null || value === undefined || (typeof value === 'string' && value.trim() === '')
220
- }
198
+ const _isNotFilled = value =>
199
+ value === null || value === undefined || (typeof value === 'string' && value.trim() === '')
221
200
 
222
- const _checkMandatoryElement = (element, value, errors, key, pathSegments) => {
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, pathSegments))
204
+ errors.push(assertError(ASSERT_NOT_NULL, element, value, key, pathSegmentsInfo))
226
205
  }
227
206
  }
228
207
 
229
- const _isNavigationColumn = (column, searched) => {
230
- return (
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
- return (element['@assert.range'] && element.enum) || element['@assert.enum'] ? element.enum : undefined
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, pathSegments) => {
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, pathSegments))
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, pathSegments) => {
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, pathSegments))
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, pathSegments }) => {
246
+ const checkInputConstraints = ({ element, value, errors, key, pathSegmentsInfo }) => {
272
247
  if (!element) return errors
248
+ let path
273
249
 
274
- _checkMandatoryElement(element, value, errors, key, pathSegments)
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, pathSegments)
279
-
280
- _checkRangeElement(element, value, errors, key, pathSegments)
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://pages.github.tools.sap/cap/docs/guides/providing-services#assert-target @assert.target} for
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, pathSegments } = target.assocInfo
335
+ const { row, pathSegmentsInfo } = target.assocInfo
362
336
  const key = target.foreignKey.name
363
- const error = assertError('ASSERT_TARGET', target.foreignKey, row[key], key, pathSegments)
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
@@ -71,10 +71,6 @@ class WorkerReq {
71
71
  prop: 'reject',
72
72
  args
73
73
  })
74
-
75
- let error = Responses.get(4, ...args)
76
- if (!error.stack) Error.captureStackTrace((error = Object.assign(new Error(), error)), this.reject)
77
- throw error
78
74
  }
79
75
  }
80
76