@sap/cds 8.6.2 → 8.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 +24 -0
- package/_i18n/i18n_en_US_saptrc.properties +4 -7
- package/bin/serve.js +3 -1
- package/lib/compile/for/lean_drafts.js +1 -1
- package/lib/compile/for/nodejs.js +1 -0
- package/lib/compile/to/sql.js +12 -8
- package/lib/core/classes.js +3 -4
- package/lib/core/types.js +1 -0
- package/lib/env/cds-requires.js +2 -2
- package/lib/ql/cds-ql.js +8 -1
- package/lib/ql/cds.ql-Query.js +9 -2
- package/lib/req/validate.js +1 -2
- package/lib/srv/cds-connect.js +1 -1
- package/lib/srv/cds-serve.js +2 -9
- package/lib/srv/cds.Service.js +0 -1
- package/lib/srv/factory.js +56 -71
- package/lib/srv/middlewares/auth/ias-auth.js +44 -14
- package/lib/srv/middlewares/auth/jwt-auth.js +45 -16
- package/lib/srv/middlewares/auth/xssec.js +1 -1
- package/lib/srv/middlewares/errors.js +8 -10
- package/lib/utils/cds-utils.js +5 -1
- package/lib/utils/tar-lib.js +58 -0
- package/libx/_runtime/common/Service.js +0 -4
- package/libx/_runtime/common/generic/input.js +1 -1
- package/libx/_runtime/common/utils/csn.js +5 -1
- package/libx/_runtime/fiori/lean-draft.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-shared.js +7 -3
- package/libx/odata/middleware/create.js +4 -0
- package/libx/odata/middleware/delete.js +2 -0
- package/libx/odata/middleware/operation.js +2 -0
- package/libx/odata/middleware/read.js +4 -0
- package/libx/odata/middleware/stream.js +4 -0
- package/libx/odata/middleware/update.js +4 -0
- package/libx/odata/parse/afterburner.js +2 -2
- package/libx/odata/parse/multipartToJson.js +0 -1
- package/libx/odata/utils/normalizeTimeData.js +43 -0
- package/libx/odata/utils/readAfterWrite.js +1 -1
- package/libx/outbox/index.js +1 -1
- package/libx/rest/RestAdapter.js +2 -2
- package/package.json +6 -2
- package/lib/srv/protocols/odata-v2.js +0 -26
- package/libx/_runtime/common/code-ext/WorkerPool.js +0 -90
- package/libx/_runtime/common/code-ext/WorkerReq.js +0 -77
- package/libx/_runtime/common/code-ext/config.js +0 -13
- package/libx/_runtime/common/code-ext/execute.js +0 -123
- package/libx/_runtime/common/code-ext/handlers.js +0 -50
- package/libx/_runtime/common/code-ext/worker.js +0 -70
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +0 -37
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
const { isStandardError } = require('../../../libx/_runtime/common/error/standardError')
|
|
2
2
|
|
|
3
3
|
const production = process.env.NODE_ENV === 'production'
|
|
4
|
-
const cds = require
|
|
4
|
+
const cds = require('../..')
|
|
5
5
|
const LOG = cds.log('error')
|
|
6
6
|
const { inspect } = cds.utils
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
module.exports = () => {
|
|
9
|
+
// eslint-disable-next-line no-unused-vars
|
|
10
10
|
return async function http_error(error, req, res, next) {
|
|
11
11
|
if (isStandardError(error) && cds.env.server.shutdown_on_uncaught_errors) {
|
|
12
12
|
cds.log().error('❗️Uncaught', error)
|
|
13
13
|
await cds.shutdown(error)
|
|
14
|
-
return
|
|
14
|
+
return
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// In case of 401 require login if available by auth strategy
|
|
@@ -26,11 +26,13 @@ module.exports = () => {
|
|
|
26
26
|
error.stack = error.stack.replace(/\n {4}at .*(?:node_modules\/express|node:internal).*/g, '')
|
|
27
27
|
|
|
28
28
|
if (400 <= status && status < 500) {
|
|
29
|
-
LOG.warn
|
|
29
|
+
LOG.warn(status, '>', inspect(error))
|
|
30
30
|
} else {
|
|
31
|
-
LOG.error
|
|
31
|
+
LOG.error(status, '>', inspect(error))
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
if (res.headersSent) return
|
|
35
|
+
|
|
34
36
|
// Expose as little information as possible in production, and as much as possible in development
|
|
35
37
|
if (production) {
|
|
36
38
|
Object.defineProperties(error, {
|
|
@@ -44,14 +46,10 @@ module.exports = () => {
|
|
|
44
46
|
})
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
if (res.headersSent) {
|
|
48
|
-
return next(error)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
49
|
// Send the error response
|
|
52
50
|
return res.status(status).json({ error })
|
|
53
51
|
|
|
54
52
|
// Note: express returns errors as XML, we prefer JSON
|
|
55
|
-
//
|
|
53
|
+
// next(error)
|
|
56
54
|
}
|
|
57
55
|
}
|
package/lib/utils/cds-utils.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
const cwd = process.env._original_cwd || process.cwd()
|
|
2
2
|
const cds = require('../index')
|
|
3
3
|
|
|
4
|
+
/* eslint no-empty: ["error", { "allowEmptyCatch": true }] */
|
|
5
|
+
// eslint-disable-next-line no-unused-vars
|
|
6
|
+
const _tarLib = () => { try { return require('tar') } catch(_) {} }
|
|
7
|
+
|
|
4
8
|
module.exports = exports = new class {
|
|
5
9
|
get colors() { return super.colors = require('./colors') }
|
|
6
10
|
get inflect() { return super.inflect = require('./inflect') }
|
|
@@ -16,7 +20,7 @@ module.exports = exports = new class {
|
|
|
16
20
|
get uuid() { return super.uuid = require('crypto').randomUUID }
|
|
17
21
|
get yaml() { return super.yaml = require('@sap/cds-foss').yaml }
|
|
18
22
|
get pool() { return super.pool = require('@sap/cds-foss').pool }
|
|
19
|
-
get tar()
|
|
23
|
+
get tar() { return super.tar = process.platform === 'win32' && _tarLib() ? require('./tar-lib') : require('./tar') }
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
/** @type {import('node:path')} */
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const cds = require('../index'), { path, mkdirp } = cds.utils
|
|
2
|
+
const tar = require('tar')
|
|
3
|
+
const { Readable } = require('stream')
|
|
4
|
+
const cons = require('stream/consumers')
|
|
5
|
+
|
|
6
|
+
const _resolve = (...x) => path.resolve (cds.root,...x)
|
|
7
|
+
|
|
8
|
+
exports.create = async (root, ...args) => {
|
|
9
|
+
if (typeof root === 'string') root = _resolve(root)
|
|
10
|
+
if (Array.isArray(root)) [ root, ...args ] = [ cds.root, root, ...args ]
|
|
11
|
+
|
|
12
|
+
const options = {}
|
|
13
|
+
if (args.includes('-z')) options.gzip = true
|
|
14
|
+
const index = args.findIndex(el => el === '-f')
|
|
15
|
+
if (index>=0) options.file = _resolve(args[index+1])
|
|
16
|
+
options.cwd = root
|
|
17
|
+
|
|
18
|
+
let dirs = []
|
|
19
|
+
for (let i=0; i<args.length; i++) {
|
|
20
|
+
if (args[i] === '-z' || args[i] === '-f') break
|
|
21
|
+
if (Array.isArray(args[i])) args[i].forEach(a => dirs.push(_resolve(a)))
|
|
22
|
+
else if (typeof args[i] === 'string') dirs.push(_resolve(args[i]))
|
|
23
|
+
}
|
|
24
|
+
if (!dirs.length) dirs.push(root)
|
|
25
|
+
dirs = dirs.map(d => path.relative(root, d))
|
|
26
|
+
|
|
27
|
+
const stream = await tar.c(options, dirs)
|
|
28
|
+
|
|
29
|
+
return stream && await cons.buffer(stream)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
exports.extract = (archive, ...args) => ({
|
|
33
|
+
async to (dest) {
|
|
34
|
+
if (typeof dest === 'string') dest = _resolve(dest)
|
|
35
|
+
const stream = Readable.from(archive)
|
|
36
|
+
|
|
37
|
+
const options = { C: dest }
|
|
38
|
+
if (args.includes('-z')) options.gzip = true
|
|
39
|
+
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const tr = tar.x(options)
|
|
42
|
+
stream.pipe(tr)
|
|
43
|
+
tr.on('close', () => resolve())
|
|
44
|
+
tr.on('error', e => reject(e))
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const tar_ = exports
|
|
50
|
+
exports.c = tar_.create
|
|
51
|
+
exports.cz = (d,...args) => tar_.c (d, ...args, '-z')
|
|
52
|
+
exports.cf = (t,d,...args) => tar_.c (d, ...args, '-f',t)
|
|
53
|
+
exports.czf = (t,d,...args) => tar_.c (d, ...args, '-z', '-f',t)
|
|
54
|
+
exports.czfd = (t,...args) => mkdirp(path.dirname(t)).then (()=> tar_.czf (t,...args))
|
|
55
|
+
exports.x = tar_.xf = tar_.extract
|
|
56
|
+
exports.xz = tar_.xzf = a => tar_.x (a, '-z')
|
|
57
|
+
exports.xv = tar_.xvf = a => tar_.x (a)
|
|
58
|
+
exports.xvz = tar_.xvzf = a => tar_.x (a, '-z')
|
|
@@ -62,10 +62,6 @@ class ApplicationService extends cds.Service {
|
|
|
62
62
|
require('./generic/sorting').call(this)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
static handle_code_ext() {
|
|
66
|
-
if (cds.env.requires.extensibility?.code) require('./code-ext/handlers').call(this)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
65
|
static handle_fiori() {
|
|
70
66
|
require('../fiori/lean-draft').impl.call(this)
|
|
71
67
|
}
|
|
@@ -212,7 +212,7 @@ const _pick = element => {
|
|
|
212
212
|
// should be a db feature, as we cannot handle completely on service level (cf. deep update)
|
|
213
213
|
// -> add to attic env behavior once new dbs handle this
|
|
214
214
|
// also happens in validate but because of draft activate we have to do it twice (where cleansing is suppressed)
|
|
215
|
-
if (element['@Core.Immutable']) {
|
|
215
|
+
if (element['@Core.Immutable'] && !element.key) {
|
|
216
216
|
categories.push('immutable')
|
|
217
217
|
}
|
|
218
218
|
|
|
@@ -115,19 +115,23 @@ const prefixForStruct = element => {
|
|
|
115
115
|
function getDraftTreeRoot(entity, model) {
|
|
116
116
|
if (entity.own('__draftTreeRoot')) return entity.__draftTreeRoot
|
|
117
117
|
|
|
118
|
+
const previous = new Set() // track visited entities to identify hierarchies
|
|
118
119
|
let parent
|
|
119
120
|
let current = entity
|
|
120
121
|
while (current && !current['@Common.DraftRoot.ActivationAction']) {
|
|
122
|
+
previous.add(current.name)
|
|
121
123
|
const parents = []
|
|
122
124
|
for (const k in model.definitions) {
|
|
125
|
+
if (previous.has(k)) continue
|
|
123
126
|
const e = model.definitions[k]
|
|
124
127
|
if (e.kind !== 'entity' || !e.compositions) continue
|
|
125
128
|
for (const c in e.compositions)
|
|
126
129
|
if (
|
|
127
130
|
e.compositions[c].target === current.name ||
|
|
128
131
|
e.compositions[c].target === current.name.replace(/\.drafts/, '')
|
|
129
|
-
)
|
|
132
|
+
) {
|
|
130
133
|
parents.push(e)
|
|
134
|
+
}
|
|
131
135
|
}
|
|
132
136
|
if (parents.length > 1 && parents.some(p => p !== parents[0])) {
|
|
133
137
|
// > unable to determine single parent
|
|
@@ -1509,7 +1509,7 @@ function expandStarStar(target, draftActivate, recursion = new Map()) {
|
|
|
1509
1509
|
}
|
|
1510
1510
|
|
|
1511
1511
|
async function onNewCleanse(req) {
|
|
1512
|
-
cds.validate(req.data, req.target, {})
|
|
1512
|
+
cds.validate(req.data, req.target, { insert: true })
|
|
1513
1513
|
}
|
|
1514
1514
|
onNewCleanse._initial = true
|
|
1515
1515
|
async function onNew(req) {
|
|
@@ -14,13 +14,17 @@ class EnterpriseMessagingShared extends AMQPWebhookMessaging {
|
|
|
14
14
|
|
|
15
15
|
getClient() {
|
|
16
16
|
if (this.client) return this.client
|
|
17
|
+
this.client = new AMQPClient(this.getClientOptions())
|
|
18
|
+
return this.client
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getClientOptions() {
|
|
17
22
|
const optionsAMQP = optionsMessagingAMQP(this.options)
|
|
18
|
-
|
|
23
|
+
return {
|
|
19
24
|
optionsAMQP,
|
|
20
25
|
prefix: { topic: 'topic:', queue: 'queue:' },
|
|
21
26
|
service: this
|
|
22
|
-
}
|
|
23
|
-
return this.client
|
|
27
|
+
}
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
getManagement() {
|
|
@@ -6,6 +6,7 @@ const getODataMetadata = require('../utils/metadata')
|
|
|
6
6
|
const postProcess = require('../utils/postProcess')
|
|
7
7
|
const readAfterWrite4 = require('../utils/readAfterWrite')
|
|
8
8
|
const getODataResult = require('../utils/result')
|
|
9
|
+
const normalizeTimeData = require('../utils/normalizeTimeData')
|
|
9
10
|
|
|
10
11
|
const { getKeysAndParamsFromPath } = require('../../common/utils')
|
|
11
12
|
|
|
@@ -32,6 +33,7 @@ module.exports = (adapter, isUpsert) => {
|
|
|
32
33
|
|
|
33
34
|
// payload & params
|
|
34
35
|
const data = req.body
|
|
36
|
+
normalizeTimeData(data, model, target)
|
|
35
37
|
const { keys, params } = getKeysAndParamsFromPath(from, { model })
|
|
36
38
|
// add keys from url into payload (overwriting if already present)
|
|
37
39
|
Object.assign(data, keys)
|
|
@@ -66,6 +68,8 @@ module.exports = (adapter, isUpsert) => {
|
|
|
66
68
|
})
|
|
67
69
|
})
|
|
68
70
|
.then(result => {
|
|
71
|
+
if (res.headersSent) return
|
|
72
|
+
|
|
69
73
|
handleSapMessages(cdsReq, req, res)
|
|
70
74
|
|
|
71
75
|
// case: read after write returns no results, e.g., due to auth (academic but possible)
|
|
@@ -101,6 +101,8 @@ module.exports = adapter => {
|
|
|
101
101
|
return service
|
|
102
102
|
.run(() => service.dispatch(cdsReq))
|
|
103
103
|
.then(result => {
|
|
104
|
+
if (res.headersSent) return
|
|
105
|
+
|
|
104
106
|
handleSapMessages(cdsReq, req, res)
|
|
105
107
|
|
|
106
108
|
if (operation.returns?.items && result == null) result = []
|
|
@@ -128,6 +128,8 @@ const _handleArrayOfQueriesFactory = adapter => {
|
|
|
128
128
|
})
|
|
129
129
|
})
|
|
130
130
|
.then(result => {
|
|
131
|
+
if (res.headersSent) return
|
|
132
|
+
|
|
131
133
|
handleSapMessages(cdsReq, req, res)
|
|
132
134
|
|
|
133
135
|
if (req.url.match(/\/\$count/)) return res.set('Content-Type', 'text/plain').send(_count(result).toString())
|
|
@@ -238,6 +240,8 @@ module.exports = adapter => {
|
|
|
238
240
|
})
|
|
239
241
|
})
|
|
240
242
|
.then(result => {
|
|
243
|
+
if (res.headersSent) return
|
|
244
|
+
|
|
241
245
|
handleSapMessages(cdsReq, req, res)
|
|
242
246
|
|
|
243
247
|
// 204
|
|
@@ -218,6 +218,8 @@ module.exports = adapter => {
|
|
|
218
218
|
return service
|
|
219
219
|
.run(() => {
|
|
220
220
|
return service.dispatch(cdsReq).then(async result => {
|
|
221
|
+
if (res.headersSent) return
|
|
222
|
+
|
|
221
223
|
_validateStream(req, result)
|
|
222
224
|
|
|
223
225
|
if (validateIfNoneMatch(cdsReq.target, req.headers?.['if-none-match'], result)) return res.sendStatus(304)
|
|
@@ -241,6 +243,8 @@ module.exports = adapter => {
|
|
|
241
243
|
})
|
|
242
244
|
})
|
|
243
245
|
.then(() => {
|
|
246
|
+
if (res.headersSent) return
|
|
247
|
+
|
|
244
248
|
handleSapMessages(cdsReq, req, res)
|
|
245
249
|
|
|
246
250
|
res.end()
|
|
@@ -6,6 +6,7 @@ const getODataMetadata = require('../utils/metadata')
|
|
|
6
6
|
const postProcess = require('../utils/postProcess')
|
|
7
7
|
const readAfterWrite4 = require('../utils/readAfterWrite')
|
|
8
8
|
const getODataResult = require('../utils/result')
|
|
9
|
+
const normalizeTimeData = require('../utils/normalizeTimeData')
|
|
9
10
|
|
|
10
11
|
const { getKeysAndParamsFromPath } = require('../../common/utils')
|
|
11
12
|
|
|
@@ -69,6 +70,7 @@ module.exports = adapter => {
|
|
|
69
70
|
|
|
70
71
|
// payload & params
|
|
71
72
|
const data = _propertyAccess ? { [_propertyAccess]: req.body.value } : req.body
|
|
73
|
+
normalizeTimeData(data, model, target)
|
|
72
74
|
const { keys, params } = getKeysAndParamsFromPath(from, { model })
|
|
73
75
|
// add keys from url into payload (overwriting if already present)
|
|
74
76
|
if (!_propertyAccess) Object.assign(data, keys)
|
|
@@ -112,6 +114,8 @@ module.exports = adapter => {
|
|
|
112
114
|
})
|
|
113
115
|
})
|
|
114
116
|
.then(result => {
|
|
117
|
+
if (res.headersSent) return
|
|
118
|
+
|
|
115
119
|
handleSapMessages(cdsReq, req, res)
|
|
116
120
|
|
|
117
121
|
// case: read after write returns no results, e.g., due to auth (academic but possible)
|
|
@@ -447,7 +447,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
447
447
|
_resolveAliasesInXpr(ref[i].where, current)
|
|
448
448
|
_processWhere(ref[i].where, current)
|
|
449
449
|
}
|
|
450
|
-
} else if (current.kind === 'element' && current.elements && i < ref.length - 1) {
|
|
450
|
+
} else if (current.kind === 'element' && current.type !== 'cds.Map' && current.elements && i < ref.length - 1) {
|
|
451
451
|
// > structured
|
|
452
452
|
continue
|
|
453
453
|
} else {
|
|
@@ -474,7 +474,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
474
474
|
Object.defineProperty(cqn, '_propertyAccess', { value: current.name, enumerable: false })
|
|
475
475
|
|
|
476
476
|
// if we end up with structured, keep path as is, if we end up with property in structured, cut off property
|
|
477
|
-
if (!current.elements) from.ref.splice(-1)
|
|
477
|
+
if (!current.elements || current.type === 'cds.Map') from.ref.splice(-1)
|
|
478
478
|
break
|
|
479
479
|
} else if (Object.keys(target.elements).includes(current.name)) {
|
|
480
480
|
if (!cqn.SELECT.columns) cqn.SELECT.columns = []
|
|
@@ -138,7 +138,6 @@ const _parseStream = async function* (body, boundary) {
|
|
|
138
138
|
|
|
139
139
|
if (typeof ret !== 'number') {
|
|
140
140
|
if (ret.message === 'Parse Error') {
|
|
141
|
-
// console.trace(ret, ret.bytesParsed ? `\n\nin:\n${changed.substr(0, ret.bytesParsed + 1)}\n\n` : '')
|
|
142
141
|
ret.statusCode = 400
|
|
143
142
|
ret.message = `Error while parsing batch body at position ${ret.bytesParsed}: ${ret.reason}`
|
|
144
143
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const normalizeTimestamp = require('../../_runtime/common/utils/normalizeTimestamp')
|
|
2
|
+
const getTemplate = require('../../_runtime/common/utils/template')
|
|
3
|
+
|
|
4
|
+
const _processorFn = elementInfo => {
|
|
5
|
+
const { row, plain } = elementInfo
|
|
6
|
+
if (typeof row !== 'object') return
|
|
7
|
+
for (const category of plain.categories) {
|
|
8
|
+
const { row, key } = elementInfo
|
|
9
|
+
if (!(row[key] == null) && row[key] !== '$now') {
|
|
10
|
+
switch (category) {
|
|
11
|
+
case 'cds.DateTime':
|
|
12
|
+
row[key] = new Date(row[key]).toISOString().replace(/\.\d\d\d/, '')
|
|
13
|
+
break
|
|
14
|
+
case 'cds.Timestamp':
|
|
15
|
+
row[key] = normalizeTimestamp(row[key])
|
|
16
|
+
break
|
|
17
|
+
// no default
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const _pick = element => {
|
|
24
|
+
const categories = []
|
|
25
|
+
if (element.type === 'cds.DateTime') categories.push('cds.DateTime')
|
|
26
|
+
if (element.type === 'cds.Timestamp') categories.push('cds.Timestamp')
|
|
27
|
+
if (categories.length) return { categories }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = function normalizeTimeData(data, model, target) {
|
|
31
|
+
if (
|
|
32
|
+
!data ||
|
|
33
|
+
(Array.isArray(data) && data.length === 0) ||
|
|
34
|
+
(typeof data === 'object' && Object.keys(data).length === 0)
|
|
35
|
+
) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
const template = getTemplate('normalize-datetime', { model }, target, { pick: _pick })
|
|
39
|
+
|
|
40
|
+
if (template.elements.size === 0) return
|
|
41
|
+
|
|
42
|
+
template.process(data, _processorFn)
|
|
43
|
+
}
|
|
@@ -88,7 +88,7 @@ const _getColumns = (target, data, prefix = []) => {
|
|
|
88
88
|
if (each in DRAFT_COLUMNS_MAP) continue
|
|
89
89
|
if (!cds.env.features.stream_compat && target.elements[each].type === 'cds.LargeBinary') continue
|
|
90
90
|
const element = target.elements[each]
|
|
91
|
-
if (element.elements && data[each]) {
|
|
91
|
+
if (element.elements && data[each] && element.type !== 'cds.Map') {
|
|
92
92
|
prefix.push(element.name)
|
|
93
93
|
columns.push(..._getColumns(element, data[each], prefix))
|
|
94
94
|
prefix.pop()
|
package/libx/outbox/index.js
CHANGED
|
@@ -188,7 +188,7 @@ const processMessages = async (service, tenant, _opts = {}) => {
|
|
|
188
188
|
if (toBeDeleted.length === opts.chunkSize) {
|
|
189
189
|
processMessages(service, tenant, opts) // We only processed max. opts.chunkSize, so there might be more
|
|
190
190
|
} else {
|
|
191
|
-
LOG.
|
|
191
|
+
LOG._debug && LOG.debug(`${name}: All messages processed`)
|
|
192
192
|
}
|
|
193
193
|
}, config)
|
|
194
194
|
spawn.on('done', () => {
|
package/libx/rest/RestAdapter.js
CHANGED
|
@@ -115,11 +115,11 @@ class RestAdapter extends HttpAdapter {
|
|
|
115
115
|
|
|
116
116
|
// handle result
|
|
117
117
|
router.use((req, res) => {
|
|
118
|
-
const { result, status, location } = req._result // REVISIT: Ugly voodoo _req._result channel -> eliminate
|
|
119
|
-
|
|
120
118
|
// if authentication or something else within the processing of a cds.Request terminates the request, no need to continue
|
|
121
119
|
if (res.headersSent) return
|
|
122
120
|
|
|
121
|
+
const { result, status, location } = req._result // REVISIT: Ugly voodoo _req._result channel -> eliminate
|
|
122
|
+
|
|
123
123
|
// post process
|
|
124
124
|
let definition = req._operation || req._query.__target
|
|
125
125
|
if (typeof definition === 'string')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.7.0",
|
|
4
4
|
"description": "SAP Cloud Application Programming Model - CDS for Node.js",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"keywords": [
|
|
@@ -39,11 +39,15 @@
|
|
|
39
39
|
"@sap/cds-foss": "^5.0.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"express": "
|
|
42
|
+
"express": "^4",
|
|
43
|
+
"tar": "^7"
|
|
43
44
|
},
|
|
44
45
|
"peerDependenciesMeta": {
|
|
45
46
|
"express": {
|
|
46
47
|
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"tar": {
|
|
50
|
+
"optional": true
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const cds = require('../../index'), { decodeURIComponent } = cds.utils
|
|
2
|
-
const LOG = cds.log('odata-v2')
|
|
3
|
-
const logger = function cap_legacy_req_logger (req,_,next) {
|
|
4
|
-
if (/\$batch$/.test(req.url)) {
|
|
5
|
-
const prefix = decodeURIComponent(req.originalUrl).replace('$batch','')
|
|
6
|
-
req.on ('dispatch', (req) => {
|
|
7
|
-
LOG && LOG (req.event, prefix+decodeURIComponent(req._path), req._query||'')
|
|
8
|
-
if (LOG._debug && req.query) LOG.debug (req.query)
|
|
9
|
-
})
|
|
10
|
-
} else {
|
|
11
|
-
LOG && LOG (req.method, decodeURIComponent(req.originalUrl), req.body||'')
|
|
12
|
-
}
|
|
13
|
-
next()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const ODataV2Proxy = require('./odata-v2-proxy') // ('@sap/cds-odata-v2-adapter-proxy')
|
|
17
|
-
module.exports = function ODataV2Adapter (srv) {
|
|
18
|
-
const proxy = new ODataV2Proxy ({
|
|
19
|
-
sourcePath: srv.path,
|
|
20
|
-
targetPath: '/odata/v4',
|
|
21
|
-
target: 'auto', // to detect server url + port dynamically
|
|
22
|
-
logLevel: 'warn',
|
|
23
|
-
...srv.options, path:""
|
|
24
|
-
})
|
|
25
|
-
return [ logger, proxy ]
|
|
26
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
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
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
const { parentPort } = require('worker_threads')
|
|
2
|
-
const { Responses, Errors } = require('../../../../lib/req/response')
|
|
3
|
-
|
|
4
|
-
class WorkerReq {
|
|
5
|
-
constructor(contextId, reqData) {
|
|
6
|
-
this.contextId = contextId
|
|
7
|
-
Object.assign(this, reqData)
|
|
8
|
-
this.postMessages = []
|
|
9
|
-
this.messages = this.messages ?? []
|
|
10
|
-
this.errors = this.errors ?? new Errors()
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
#push(args) {
|
|
14
|
-
this.postMessages.push({
|
|
15
|
-
kind: 'run',
|
|
16
|
-
target: 'req',
|
|
17
|
-
...args
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
notify(...args) {
|
|
22
|
-
this.#push({
|
|
23
|
-
prop: 'notify',
|
|
24
|
-
args
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
const notify = Responses.get(1, ...args)
|
|
28
|
-
this.messages.push(notify)
|
|
29
|
-
return notify
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
info(...args) {
|
|
33
|
-
this.#push({
|
|
34
|
-
prop: 'info',
|
|
35
|
-
args
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
const info = Responses.get(2, ...args)
|
|
39
|
-
this.messages.push(info)
|
|
40
|
-
return info
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
warn(...args) {
|
|
44
|
-
this.#push({
|
|
45
|
-
prop: 'warn',
|
|
46
|
-
args
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
const warn = Responses.get(3, ...args)
|
|
50
|
-
this.messages.push(warn)
|
|
51
|
-
return warn
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
error(...args) {
|
|
55
|
-
this.#push({
|
|
56
|
-
prop: 'error',
|
|
57
|
-
args
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
let error = Responses.get(4, ...args)
|
|
61
|
-
if (!error.stack) Error.captureStackTrace((error = Object.assign(new Error(), error)), this.error)
|
|
62
|
-
this.errors.push(error)
|
|
63
|
-
return error
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
reject(...args) {
|
|
67
|
-
parentPort.postMessage({
|
|
68
|
-
contextId: this.contextId,
|
|
69
|
-
kind: 'run',
|
|
70
|
-
target: 'req',
|
|
71
|
-
prop: 'reject',
|
|
72
|
-
args
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
module.exports = WorkerReq
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const os = require('os')
|
|
2
|
-
const totalMemory = os.totalmem() // total amount of system memory in bytes
|
|
3
|
-
const maxOldGenerationSizeMb = Math.floor(totalMemory / 1024 ** 2 / 8) // max size of the main heap in MB
|
|
4
|
-
const maxYoungGenerationSizeMb = Math.floor(totalMemory / 1024 ** 2 / 8) // max size of a heap space for recently created objects
|
|
5
|
-
|
|
6
|
-
module.exports = {
|
|
7
|
-
timeout: 10000,
|
|
8
|
-
resourceLimits: {
|
|
9
|
-
maxOldGenerationSizeMb,
|
|
10
|
-
maxYoungGenerationSizeMb,
|
|
11
|
-
stackSizeMb: 4 // default
|
|
12
|
-
}
|
|
13
|
-
}
|