@sap/cds 8.3.0 → 8.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 +35 -1
- package/bin/serve.js +9 -2
- package/lib/auth/ias-auth.js +4 -1
- package/lib/auth/jwt-auth.js +4 -1
- package/lib/compile/cdsc.js +1 -1
- package/lib/compile/etc/_localized.js +1 -0
- package/lib/compile/extend.js +23 -23
- package/lib/compile/for/lean_drafts.js +5 -0
- package/lib/compile/to/srvinfo.js +3 -1
- package/lib/{linked → core}/classes.js +8 -6
- package/lib/{linked/models.js → core/linked-csn.js} +4 -0
- package/lib/env/defaults.js +4 -1
- package/lib/i18n/localize.js +2 -2
- package/lib/index.js +43 -59
- package/lib/log/cds-error.js +21 -21
- package/lib/ql/cds-ql.js +5 -5
- package/lib/req/cds-context.js +5 -0
- package/lib/req/context.js +2 -2
- package/lib/req/locale.js +25 -21
- package/lib/{linked → req}/validate.js +11 -9
- package/lib/srv/cds-serve.js +1 -1
- package/lib/srv/middlewares/cds-context.js +1 -1
- package/lib/srv/middlewares/errors.js +20 -7
- package/lib/srv/protocols/hcql.js +106 -43
- package/lib/srv/protocols/http.js +2 -2
- package/lib/srv/protocols/index.js +14 -10
- package/lib/srv/protocols/odata-v4.js +2 -26
- package/lib/srv/protocols/okra.js +24 -0
- package/lib/srv/srv-models.js +6 -8
- package/lib/{utils → test}/cds-test.js +5 -5
- package/lib/utils/check-version.js +8 -15
- package/lib/utils/extend.js +20 -0
- package/lib/utils/lazify.js +33 -0
- package/lib/utils/tar.js +39 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +0 -1
- package/libx/_runtime/common/error/frontend.js +18 -4
- package/libx/_runtime/common/generic/auth/restrict.js +1 -3
- package/libx/_runtime/common/generic/sorting.js +1 -1
- package/libx/_runtime/common/utils/compareJson.js +139 -53
- package/libx/_runtime/common/utils/resolveView.js +19 -23
- package/libx/_runtime/fiori/lean-draft.js +2 -2
- package/libx/_runtime/messaging/kafka.js +7 -1
- package/libx/_runtime/remote/utils/data.js +30 -24
- package/libx/odata/ODataAdapter.js +12 -7
- package/libx/odata/middleware/batch.js +3 -0
- package/libx/odata/middleware/error.js +6 -0
- package/libx/odata/parse/afterburner.js +5 -6
- package/libx/odata/parse/multipartToJson.js +12 -8
- package/libx/odata/utils/index.js +3 -2
- package/libx/odata/utils/metadata.js +31 -1
- package/libx/outbox/index.js +5 -1
- package/package.json +3 -4
- package/server.js +18 -0
- package/lib/lazy.js +0 -51
- package/lib/test/index.js +0 -2
- /package/lib/{linked → core}/entities.js +0 -0
- /package/lib/{linked → core}/types.js +0 -0
- /package/lib/{utils → test}/axios.js +0 -0
- /package/lib/{utils → test}/data.js +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
const cds = require('../../../')
|
|
2
|
+
|
|
1
3
|
const { parsers, freeParser, HTTPParser } = require('_http_common')
|
|
2
4
|
const { PassThrough, Readable } = require('stream')
|
|
3
5
|
const streamConsumers = require('stream/consumers')
|
|
6
|
+
|
|
4
7
|
const { getBoundary } = require('../utils')
|
|
5
8
|
|
|
6
9
|
const CRLF = '\r\n'
|
|
@@ -42,16 +45,17 @@ const parseStream = async function* (body, boundary) {
|
|
|
42
45
|
body: streamConsumers.json(wrapper).catch(() => {})
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
const dependencies = [...req.url.matchAll(
|
|
48
|
+
const dependencies = [...req.url.matchAll(/^\/?\$([\d.\-_~a-zA-Z]+)/g)]
|
|
46
49
|
if (dependencies.length) {
|
|
47
50
|
request.dependsOn = []
|
|
48
51
|
for (const dependency of dependencies) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
request.
|
|
52
|
+
const dependencyId = dependency[1]
|
|
53
|
+
const dependsOnRequest = requests.findLast(r => r.content_id == dependencyId) //> prefer content-id
|
|
54
|
+
if (!dependsOnRequest) {
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
request.dependsOn.push(dependsOnRequest.id)
|
|
58
|
+
request.url = request.url.replace(`$${dependencyId}`, `$${dependsOnRequest.id}`)
|
|
55
59
|
}
|
|
56
60
|
if (request.url[1] === '$') request.url = request.url.slice(1)
|
|
57
61
|
}
|
|
@@ -62,7 +66,7 @@ const parseStream = async function* (body, boundary) {
|
|
|
62
66
|
requests.push(request)
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
parser.initialize(HTTPParser.REQUEST, { type: 'HTTPINCOMINGMESSAGE' })
|
|
69
|
+
parser.initialize(HTTPParser.REQUEST, { type: 'HTTPINCOMINGMESSAGE' }, cds.env.odata.max_batch_header_size)
|
|
66
70
|
|
|
67
71
|
if (typeof body === 'string') body = [body]
|
|
68
72
|
|
|
@@ -278,9 +278,11 @@ const skipToken = (token, cqn) => {
|
|
|
278
278
|
const calculateLocationHeader = (target, srv, result) => {
|
|
279
279
|
const targetName = target.name.replace(`${srv.definition.name}.`, '')
|
|
280
280
|
const filteredKeys = [...target.keys].filter(k => !k.isAssociation).map(k => k.name)
|
|
281
|
+
if (!filteredKeys.every(k => k in result)) return
|
|
282
|
+
|
|
281
283
|
const keyValuePairs = filteredKeys.reduce((acc, key) => {
|
|
282
284
|
const value = result[key]
|
|
283
|
-
if (value === undefined) return
|
|
285
|
+
if (value === undefined) return acc
|
|
284
286
|
if (Buffer.isBuffer(value)) {
|
|
285
287
|
acc[key] = value.toString('base64')
|
|
286
288
|
} else {
|
|
@@ -290,7 +292,6 @@ const calculateLocationHeader = (target, srv, result) => {
|
|
|
290
292
|
}
|
|
291
293
|
return acc
|
|
292
294
|
}, {})
|
|
293
|
-
if (!keyValuePairs) return
|
|
294
295
|
let keys
|
|
295
296
|
const entries = Object.entries(keyValuePairs)
|
|
296
297
|
if (entries.length === 1) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { cds2edm } = require('./index')
|
|
2
|
-
|
|
2
|
+
const cds = require('../../../lib')
|
|
3
3
|
const { where2obj } = require('../../_runtime/common/utils/cqn')
|
|
4
4
|
|
|
5
5
|
const _isNavToDraftAdmin = path => path.length > 1 && path[path.length - 1] === 'DraftAdministrativeData'
|
|
@@ -109,6 +109,36 @@ const _odataContext = (query, options) => {
|
|
|
109
109
|
path += '/' + lastRef
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
if (cds.env.odata.context_with_columns && query.SELECT && !isSingleton && !propertyAccess) {
|
|
113
|
+
const { columns } = query.SELECT
|
|
114
|
+
let containments = []
|
|
115
|
+
|
|
116
|
+
function processColumns(column) {
|
|
117
|
+
const refName = column.ref?.[0]
|
|
118
|
+
if (!refName) return ''
|
|
119
|
+
|
|
120
|
+
if (column.expand) {
|
|
121
|
+
// Process nested expands recursively
|
|
122
|
+
const expandSelects = column.expand.map(exp => processColumns(exp)).filter(Boolean)
|
|
123
|
+
return `${refName}(${expandSelects.join(',')})`
|
|
124
|
+
} else if (column.xpr) {
|
|
125
|
+
// Handle xpr cases
|
|
126
|
+
const xprRefName = column.xpr.find(item => item.ref?.[0])?.ref?.[0]
|
|
127
|
+
return xprRefName || ''
|
|
128
|
+
} else if (refName !== '*') {
|
|
129
|
+
return refName
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
columns.forEach(column => {
|
|
134
|
+
const result = processColumns(column)
|
|
135
|
+
if (result) {
|
|
136
|
+
containments.push(result)
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
if (containments.length) path += `(${containments.join(',')})`
|
|
140
|
+
}
|
|
141
|
+
|
|
112
142
|
if ((!isCollection && !isSingleton && !propertyAccess) || (isNavToDraftAdmin && !propertyAccess)) {
|
|
113
143
|
path += '/$entity'
|
|
114
144
|
}
|
package/libx/outbox/index.js
CHANGED
|
@@ -287,7 +287,11 @@ function outboxed(srv, customOpts) {
|
|
|
287
287
|
else await originalSrv.emit(_req)
|
|
288
288
|
} catch (e) {
|
|
289
289
|
LOG.error('Emit failed', { event: _req.event, cause: e })
|
|
290
|
-
if (isStandardError(e))
|
|
290
|
+
if (isStandardError(e)) {
|
|
291
|
+
cds.log().error('❗️Uncaught', e)
|
|
292
|
+
await cds.shutdown(e)
|
|
293
|
+
return
|
|
294
|
+
}
|
|
291
295
|
}
|
|
292
296
|
}
|
|
293
297
|
delete context[$stored_reqs]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.0",
|
|
4
4
|
"description": "SAP Cloud Application Programming Model - CDS for Node.js",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"keywords": [
|
|
@@ -25,11 +25,10 @@
|
|
|
25
25
|
"libx/",
|
|
26
26
|
"srv/",
|
|
27
27
|
"tasks/",
|
|
28
|
-
"server.js",
|
|
29
28
|
"common.cds",
|
|
29
|
+
"server.js",
|
|
30
30
|
"eslint.config.mjs",
|
|
31
|
-
"CHANGELOG.md"
|
|
32
|
-
"LICENSE"
|
|
31
|
+
"CHANGELOG.md"
|
|
33
32
|
],
|
|
34
33
|
"engines": {
|
|
35
34
|
"node": ">=18"
|
package/server.js
CHANGED
|
@@ -17,6 +17,24 @@ module.exports = async function cds_server (options) {
|
|
|
17
17
|
|
|
18
18
|
// prepare express app
|
|
19
19
|
const o = { ...options, __proto__:defaults }
|
|
20
|
+
|
|
21
|
+
// handle cluster mode
|
|
22
|
+
let WORKERS = o.workers || process.env.WORKERS || process.env.CDS_WORKERS
|
|
23
|
+
if (WORKERS) {
|
|
24
|
+
|
|
25
|
+
const LOG = cds.log('cluster|server')
|
|
26
|
+
const cluster = require('cluster')
|
|
27
|
+
if (cluster.isMaster) {
|
|
28
|
+
if (WORKERS in {true:1}) WORKERS = require('os').availableParallelism()
|
|
29
|
+
LOG.info (`Spawning ${WORKERS} workers...`)
|
|
30
|
+
for (let i=0; i < WORKERS; ) cluster.fork ({ id: ++i })
|
|
31
|
+
return { on(){}, once(){}, close(){} } // dummy http server
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
else LOG.info(`Worker ${process.env.id} started - pid: ${process.pid}`)
|
|
35
|
+
// and continue boostrapping cds.server below...
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
const app = cds.app = o.app || express()
|
|
21
39
|
cds.emit ('bootstrap', app)
|
|
22
40
|
|
package/lib/lazy.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/** @type <T> (target:T) => ({
|
|
2
|
-
with <X,Y,Z> (x:X, y:Y, z:Z): ( T & X & Y & Z )
|
|
3
|
-
with <X,Y> (x:X, y:Y): ( T & X & Y )
|
|
4
|
-
with <X> (x:X): ( T & X )
|
|
5
|
-
}) */
|
|
6
|
-
const extend = (target) => ({
|
|
7
|
-
with(...aspects) {
|
|
8
|
-
const excludes = _excludes[typeof target] || {}
|
|
9
|
-
for (let each of aspects) {
|
|
10
|
-
for (let p of Reflect.ownKeys(each)) {
|
|
11
|
-
if (p in excludes) continue
|
|
12
|
-
Reflect.defineProperty (target,p, Reflect.getOwnPropertyDescriptor(each,p))
|
|
13
|
-
}
|
|
14
|
-
if (is_class(target) && is_class(each)) {
|
|
15
|
-
extend(target.prototype).with(each.prototype)
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return target
|
|
19
|
-
},
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
const _excludes = {
|
|
23
|
-
function: Object.assign(Object.create(null),{ name: 1, length: 2, arguments: 3, caller: 4, prototype: 5 }),
|
|
24
|
-
object: Object.assign(Object.create(null),{ constructor: 1 }),
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** @type <T>(target:T) => T */
|
|
28
|
-
const lazify = (o) => {
|
|
29
|
-
if (o.constructor === module.constructor) return lazify_module(o)
|
|
30
|
-
for (let p of Reflect.ownKeys(o)) {
|
|
31
|
-
const d = Reflect.getOwnPropertyDescriptor(o,p)
|
|
32
|
-
if (is_lazy(d.value)) Reflect.defineProperty (o,p,{
|
|
33
|
-
set(v) { Reflect.defineProperty (this,p,{value:v,__proto__:d}) },
|
|
34
|
-
get() { return this[p] = d.value.call(this,p,this) },
|
|
35
|
-
configurable: true,
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
return o
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const lazify_module = (module) => {
|
|
42
|
-
extend(module).with({ set exports(all) {
|
|
43
|
-
extend(module).with({ exports: lazify(all) })
|
|
44
|
-
}})
|
|
45
|
-
return (id) => (lazy) => module.require(id) // eslint-disable-line no-unused-vars
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const is_lazy = (x) => typeof x === 'function' && /^(function\s?)?\(?lazy[,)\t =]/.test(x)
|
|
49
|
-
const is_class = (x) => typeof x === 'function' && x.prototype && /^class\b/.test(x)
|
|
50
|
-
|
|
51
|
-
module.exports = { extend, lazify, lazified:lazify }
|
package/lib/test/index.js
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|