@sap/cds 8.3.1 → 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 +26 -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/extend.js +23 -23
- 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/srv/cds-serve.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/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/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/{linked → req}/validate.js +0 -0
- /package/lib/{utils → test}/axios.js +0 -0
- /package/lib/{utils → test}/data.js +0 -0
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
const { isStandardError } = require('../../../libx/_runtime/common/error/standardError')
|
|
2
|
+
|
|
1
3
|
const production = process.env.NODE_ENV === 'production'
|
|
2
4
|
const cds = require ('../..')
|
|
3
5
|
const LOG = cds.log('error')
|
|
4
6
|
const { inspect } = cds.utils
|
|
5
7
|
|
|
8
|
+
|
|
6
9
|
module.exports = () => {
|
|
7
|
-
return function http_error
|
|
10
|
+
return async function http_error(error, req, res, next) {
|
|
11
|
+
if (isStandardError(error) && cds.env.server.shutdown_on_uncaught_errors) {
|
|
12
|
+
cds.log().error('❗️Uncaught', error)
|
|
13
|
+
await cds.shutdown(error)
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
8
16
|
|
|
9
17
|
// In case of 401 require login if available by auth strategy
|
|
10
18
|
if (typeof error === 'number') error = { code: error }
|
|
@@ -14,7 +22,8 @@ module.exports = () => {
|
|
|
14
22
|
const status = error.statusCode || error.status || Number(error.code) || 500
|
|
15
23
|
delete error.statusCode
|
|
16
24
|
delete error.status
|
|
17
|
-
if (!production && error.stack)
|
|
25
|
+
if (!production && error.stack)
|
|
26
|
+
error.stack = error.stack.replace(/\n {4}at .*(?:node_modules\/express|node:internal).*/g, '')
|
|
18
27
|
|
|
19
28
|
if (400 <= status && status < 500) {
|
|
20
29
|
LOG.warn (status, '>', inspect(error))
|
|
@@ -24,19 +33,23 @@ module.exports = () => {
|
|
|
24
33
|
|
|
25
34
|
// Expose as little information as possible in production, and as much as possible in development
|
|
26
35
|
if (production) {
|
|
27
|
-
Object.defineProperties
|
|
36
|
+
Object.defineProperties(error, {
|
|
28
37
|
message: { enumerable: true },
|
|
29
|
-
user: { enumerable: false }
|
|
38
|
+
user: { enumerable: false }
|
|
30
39
|
})
|
|
31
40
|
} else {
|
|
32
|
-
Object.defineProperties
|
|
41
|
+
Object.defineProperties(error, {
|
|
33
42
|
message: { enumerable: true },
|
|
34
|
-
stack: { enumerable: true }
|
|
43
|
+
stack: { enumerable: true }
|
|
35
44
|
})
|
|
36
45
|
}
|
|
37
46
|
|
|
47
|
+
if (res.headersSent) {
|
|
48
|
+
return next(error)
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
// Send the error response
|
|
39
|
-
return res.status(status).json({error})
|
|
52
|
+
return res.status(status).json({ error })
|
|
40
53
|
|
|
41
54
|
// Note: express returns errors as XML, we prefer JSON
|
|
42
55
|
// _next (error)
|
|
@@ -1,57 +1,120 @@
|
|
|
1
|
+
const cds = require('../../index'), {inspect} = cds.utils
|
|
1
2
|
const express = require('express')
|
|
2
|
-
const
|
|
3
|
-
const DEBUG = cds.debug('hcql')
|
|
4
|
-
const { inspect } = require('util')
|
|
3
|
+
const LOG = cds.log('hcql')
|
|
5
4
|
|
|
6
5
|
class HCQLAdapter extends require('./http') {
|
|
7
6
|
|
|
8
7
|
get router() {
|
|
8
|
+
|
|
9
9
|
const srv = this.service
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
10
|
+
const router = super.router
|
|
11
|
+
.get ('/\\$csn', this.schema.bind(this)) //> return the CSN as schema
|
|
12
|
+
.use (express.json(this.body_parser_options)) //> for application/json -> cqn
|
|
13
|
+
.use (express.text(this.body_parser_options)) //> for text/plain -> cql -> cqn
|
|
14
|
+
|
|
15
|
+
// Route for custom actions and functions ...
|
|
16
|
+
const action = this.action.bind(this)
|
|
17
|
+
router.param('action', (req,_,next,a) => { (req.action = a) in srv.actions ? next() : next('route') })
|
|
18
|
+
router.route('/:action')
|
|
19
|
+
.post (action)
|
|
20
|
+
.get (action)
|
|
21
|
+
.all ((req,res,next) => next(501))
|
|
22
|
+
|
|
23
|
+
// Route for REST-style convenience shortcuts with queries in URL + body ...
|
|
24
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
25
|
+
const $ = cb => (req,_,next) => { req.body = cb(req.params,req); next() }
|
|
26
|
+
router.param('entity', (req,_,next,a) => { a in srv.entities ? next() : next(404) })
|
|
27
|
+
router.route('/:entity/:id?(%20?:tail)?')
|
|
28
|
+
.get ($(({entity,id,tail}, req) => {
|
|
29
|
+
const body = typeof req.body === 'string' ? req.body : ''
|
|
30
|
+
return tail || body ? {SELECT:{
|
|
31
|
+
...CQL(`SELECT from _ ${body} ${tail||''}`).SELECT,
|
|
32
|
+
...SELECT.from (entity,id).SELECT
|
|
33
|
+
}} : SELECT.from (entity,id)
|
|
34
|
+
}))
|
|
35
|
+
.post ($(({entity}, {query,body}) => INSERT.into (entity) .entries ({...query,...body})))
|
|
36
|
+
.put ($(({entity,id}, {query,body}) => UPDATE (entity,id) .with ({...query,...body})))
|
|
37
|
+
.patch ($(({entity,id}, {query,body}) => UPDATE (entity,id) .with ({...query,...body})))
|
|
38
|
+
.delete ($(({entity,id}) => DELETE.from (entity, id)))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The ultimate handler for CRUD requests
|
|
42
|
+
router.use (this.crud.bind(this))
|
|
43
|
+
return router
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Handle requests to custom actions and functions.
|
|
49
|
+
*/
|
|
50
|
+
action (req, res, next) {
|
|
51
|
+
return this.service.send (req.action, { ...req.query, ...req.body })
|
|
52
|
+
.then (results => this.reply (results, res))
|
|
53
|
+
.catch (next)
|
|
46
54
|
}
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The ultimate handler for all CRUD requests.
|
|
59
|
+
*/
|
|
60
|
+
crud (req, res, next) {
|
|
61
|
+
let query = this.query4 (req)
|
|
62
|
+
return this.service.run (query)
|
|
63
|
+
.then (results => this.reply (results, res))
|
|
64
|
+
.catch (next)
|
|
51
65
|
}
|
|
52
|
-
}
|
|
53
66
|
|
|
54
|
-
|
|
55
|
-
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Constructs an instance of cds.ql.Query from an incoming request body,
|
|
70
|
+
* which is expected to be a plain CQN object or a CQL string.
|
|
71
|
+
*/
|
|
72
|
+
query4 (/** @type express.Request */ req) {
|
|
73
|
+
let b = req.body; if (typeof b === 'string') b = cds.parse.cql(b)
|
|
74
|
+
let q = req.body = cds.ql.query(b); if (!q) return this.error (400, 'Invalid query', { query: req.body })
|
|
75
|
+
// assert valid target entity
|
|
76
|
+
if (q.target?._unresolved && this.service.definition) {
|
|
77
|
+
q.target = q._target = this.service.entities [q.target.name]
|
|
78
|
+
|| this.error (404, `'${q.target.name}' is not an entity served by '${this.service.name}'.`, { query:q })
|
|
79
|
+
}
|
|
80
|
+
// handle request headers
|
|
81
|
+
if (q.SELECT) {
|
|
82
|
+
if (req.get('Accept-Language')) q.SELECT.localized = true
|
|
83
|
+
if (req.get('X-Total-Count')) q.SELECT.count = true
|
|
84
|
+
}
|
|
85
|
+
// got a valid query
|
|
86
|
+
if (LOG._debug) LOG.debug (inspect(q))
|
|
87
|
+
return q
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Serialize the results into response.
|
|
92
|
+
*/
|
|
93
|
+
reply (results, /** @type express.Response */ res) {
|
|
94
|
+
if (!results) return res.end()
|
|
95
|
+
if (results.$count) res.set ('X-Total-Count', results.$count)
|
|
96
|
+
if (typeof results === 'object') return res.json (results)
|
|
97
|
+
else res.send (results)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Throw an Error with given status and message.
|
|
102
|
+
*/
|
|
103
|
+
error (status, message, details) {
|
|
104
|
+
if (typeof status === 'string') [ message, details, status ] = [ status, message ]
|
|
105
|
+
let err = Object.assign (new Error(message), details)
|
|
106
|
+
if (status) err.status = status
|
|
107
|
+
if (new.target) return err
|
|
108
|
+
else throw err
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Return the CSN as schema in response to /<srv>/$csn requests
|
|
113
|
+
*/
|
|
114
|
+
schema (_, res) {
|
|
115
|
+
let csn = cds.minify (this.service.model, { service: this.service.name })
|
|
116
|
+
return res.json (csn)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
56
119
|
|
|
57
120
|
module.exports = HCQLAdapter
|
|
@@ -15,9 +15,9 @@ class HttpAdapter {
|
|
|
15
15
|
return this.router //> constructed by getter
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/** The actual Router factory. Subclasses override this to add specific handlers. */
|
|
18
|
+
/** The actual Router factory. Subclasses override this to add specific handlers. @returns {express.Router} */
|
|
19
19
|
get router() {
|
|
20
|
-
let router = super.router =
|
|
20
|
+
let router = super.router = new express.Router
|
|
21
21
|
this.use (this.http_log)
|
|
22
22
|
this.use (this.requires_check)
|
|
23
23
|
return router
|
|
@@ -46,7 +46,7 @@ class Protocols {
|
|
|
46
46
|
*/
|
|
47
47
|
serve (srv, /* in: */ app, { before, after } = cds.middlewares) {
|
|
48
48
|
|
|
49
|
-
const endpoints = srv.endpoints
|
|
49
|
+
const endpoints = srv.endpoints ??= this.endpoints4(srv)
|
|
50
50
|
const cached = srv._adapters ??= {}
|
|
51
51
|
let n = 0
|
|
52
52
|
|
|
@@ -101,7 +101,7 @@ class Protocols {
|
|
|
101
101
|
// get @protocol annotations from service definition
|
|
102
102
|
let annos = o?.to || def['@protocol']
|
|
103
103
|
if (annos) {
|
|
104
|
-
if (annos === 'none') return
|
|
104
|
+
if (annos === 'none' || annos['='] === 'none') return []
|
|
105
105
|
if (!annos.reduce) annos = [annos]
|
|
106
106
|
}
|
|
107
107
|
// get @odata, @rest annotations
|
|
@@ -117,13 +117,14 @@ class Protocols {
|
|
|
117
117
|
// canonicalize to { kind, path } objects
|
|
118
118
|
const endpoints = annos.map (each => {
|
|
119
119
|
let { kind = each['='] || each, path } = each
|
|
120
|
-
if (!(kind in this))
|
|
120
|
+
if (!(kind in this))
|
|
121
|
+
return cds.log('adapters').warn ('ignoring unknown protocol:', kind)
|
|
121
122
|
if (typeof path !== 'string') path = o?.at || o?.path || def['@path'] || _slugified(srv.name)
|
|
122
123
|
if (path[0] !== '/') path = this[kind].path + '/' + path // prefix with protocol path
|
|
123
124
|
return { kind, path }
|
|
124
125
|
}) .filter (e => e) //> skipping unknown protocols
|
|
125
126
|
|
|
126
|
-
return endpoints
|
|
127
|
+
return endpoints //.length ? endpoints : null
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
|
|
@@ -141,7 +142,8 @@ class Protocols {
|
|
|
141
142
|
*/
|
|
142
143
|
path4 (srv,o) {
|
|
143
144
|
if (!srv.definition) srv = { definition: srv, name: srv.name } // fake srv object
|
|
144
|
-
|
|
145
|
+
const endpoints = srv.endpoints ??= this.endpoints4(srv,o)
|
|
146
|
+
return endpoints[0]?.path
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
/**
|
|
@@ -152,11 +154,13 @@ class Protocols {
|
|
|
152
154
|
const protocols={}; let any
|
|
153
155
|
|
|
154
156
|
// check @protocol annotation -> deprecated, only for 'none'
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
let a = def['@protocol']
|
|
158
|
+
if (a) {
|
|
159
|
+
const pa = a['='] || a
|
|
160
|
+
if (pa === 'none') return protocols
|
|
161
|
+
if (typeof pa === 'string') any = protocols[pa] = 1
|
|
162
|
+
else for (let p of pa) any = protocols[p.kind||p] = 1
|
|
163
|
+
}
|
|
160
164
|
|
|
161
165
|
// @odata, @rest, ... annotations -> preferred
|
|
162
166
|
else for (let p in this) if (def['@'+p] || def['@protocol.'+p]) any = protocols[p] = 1
|
|
@@ -1,31 +1,7 @@
|
|
|
1
|
-
const cds = require('../../index')
|
|
2
|
-
|
|
1
|
+
const cds = require('../../index')
|
|
3
2
|
if (cds.env.features.odata_new_adapter) {
|
|
4
3
|
cds.log().info('using new OData adapter')
|
|
5
|
-
|
|
6
4
|
module.exports = require('../../../libx/odata/ODataAdapter')
|
|
7
5
|
} else {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const legacy_adapter_factory = require('../../../libx/_runtime/cds-services/adapter/odata-v4/to')
|
|
11
|
-
const LOG = cds.log('odata')
|
|
12
|
-
|
|
13
|
-
module.exports = function ODataAdapter(srv) {
|
|
14
|
-
const router = require('express').Router()
|
|
15
|
-
|
|
16
|
-
router.use(function odata_log(req, _, next) {
|
|
17
|
-
let url = decodeURI(req.originalUrl)
|
|
18
|
-
LOG && LOG(req.method, url, req.body || '')
|
|
19
|
-
if (/\$batch/.test(req.url))
|
|
20
|
-
req.on('dispatch', req => {
|
|
21
|
-
let path = decodeURI(req._.odataReq?._rawODataPath || '')
|
|
22
|
-
LOG && LOG('>', req.event, path, req._queryOptions || '')
|
|
23
|
-
if (LOG._debug && req.query) LOG.debug(req.query) //> why only for batch subrequests?
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
next()
|
|
27
|
-
})
|
|
28
|
-
router.use(legacy_adapter_factory(srv))
|
|
29
|
-
return router
|
|
30
|
-
}
|
|
6
|
+
module.exports = require('./okra')
|
|
31
7
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const cds = require('../../index'), { decodeURI } = cds.utils
|
|
2
|
+
cds.log().info('using legacy OData adapter')
|
|
3
|
+
|
|
4
|
+
const legacy_adapter4 = require('../../../libx/_runtime/cds-services/adapter/odata-v4/to')
|
|
5
|
+
const LOG = cds.log('okra')
|
|
6
|
+
|
|
7
|
+
module.exports = function ODataAdapter(srv) {
|
|
8
|
+
const router = require('express').Router()
|
|
9
|
+
|
|
10
|
+
router.use(function odata_log(req, _, next) {
|
|
11
|
+
let url = decodeURI(req.originalUrl)
|
|
12
|
+
LOG && LOG(req.method, url, req.body || '')
|
|
13
|
+
if (/\$batch/.test(req.url))
|
|
14
|
+
req.on('dispatch', req => {
|
|
15
|
+
let path = decodeURI(req._.odataReq?._rawODataPath || '')
|
|
16
|
+
LOG && LOG('>', req.event, path, req._queryOptions || '')
|
|
17
|
+
if (LOG._debug && req.query) LOG.debug(req.query) //> why only for batch subrequests?
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
next()
|
|
21
|
+
})
|
|
22
|
+
router.use(legacy_adapter4(srv))
|
|
23
|
+
return router
|
|
24
|
+
}
|
package/lib/srv/srv-models.js
CHANGED
|
@@ -73,13 +73,13 @@ class ExtendedModels {
|
|
|
73
73
|
const model = this[key]; if (!model) return
|
|
74
74
|
if (model.then) return model //> promised model to avoid race conditions
|
|
75
75
|
|
|
76
|
-
const {
|
|
77
|
-
if (Date.now() -
|
|
76
|
+
const { $touched: touched } = model, interval = ExtendedModels.checkInterval
|
|
77
|
+
if (Date.now() - touched < interval) return model //> checked recently
|
|
78
78
|
|
|
79
79
|
else return this[key] = (async()=>{ // temporarily replace cache entry by promise to avoid race conditions...
|
|
80
80
|
|
|
81
81
|
const has_new_extensions = await cds.db.exists('cds.xt.Extensions') .where ({
|
|
82
|
-
timestamp: { '>': new Date(
|
|
82
|
+
timestamp: { '>': new Date(touched).toISOString() } // REVISIT: better store epoc time in db?
|
|
83
83
|
// REVISIT: GAP: CAP runtime should allow Date objects + Date.now() for all date+time types !
|
|
84
84
|
})
|
|
85
85
|
if (has_new_extensions) { // new extensions arrived -> refresh model in cache
|
|
@@ -87,7 +87,7 @@ class ExtendedModels {
|
|
|
87
87
|
cds.emit('cds.xt.TENANT_UPDATED', { tenant })
|
|
88
88
|
return _get_model4 (tenant, toggles.split(','))
|
|
89
89
|
} else { // no new extensions...
|
|
90
|
-
|
|
90
|
+
model.$touched = Date.now() // check again in 1 min or so
|
|
91
91
|
return model // keep cached model in cache
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -107,7 +107,7 @@ class ExtendedModels {
|
|
|
107
107
|
*/
|
|
108
108
|
add (key, model, touched = Date.now()) {
|
|
109
109
|
if (model) {
|
|
110
|
-
|
|
110
|
+
model.$touched ??= touched
|
|
111
111
|
return this[key] = model
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -118,10 +118,8 @@ class ExtendedModels {
|
|
|
118
118
|
*/
|
|
119
119
|
startSentinel(){
|
|
120
120
|
this.sentinel = setInterval (()=>{ for (let [key,m] of Object.entries(this)) {
|
|
121
|
-
if (
|
|
122
|
-
if (Date.now() - m._cached.touched > ExtendedModels.sentinelInterval) {
|
|
121
|
+
if (Date.now() - m.$touched > ExtendedModels.sentinelInterval)
|
|
123
122
|
delete this [key]
|
|
124
|
-
}
|
|
125
123
|
}}, ExtendedModels.sentinelInterval).unref()
|
|
126
124
|
}
|
|
127
125
|
|
|
@@ -35,13 +35,13 @@ class Test extends require('./axios') {
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
// gracefully shutdown cds server...
|
|
38
|
-
after (()=>{
|
|
39
|
-
|
|
38
|
+
after (async ()=>{
|
|
39
|
+
await cds.shutdown()
|
|
40
40
|
// cds.service.providers = []
|
|
41
41
|
// delete cds.services
|
|
42
42
|
// delete cds.plugins
|
|
43
43
|
// delete cds.env
|
|
44
|
-
|
|
44
|
+
await cds.utils.rimraf (process.env.cds_test_temp)
|
|
45
45
|
})
|
|
46
46
|
|
|
47
47
|
return this
|
|
@@ -186,7 +186,7 @@ let _expect = undefined
|
|
|
186
186
|
global.afterAll = global.after = (msg,fn) => repl.on?.('exit',fn||msg)
|
|
187
187
|
global.beforeEach = global.afterEach = ()=>{}
|
|
188
188
|
global.describe = ()=>{}
|
|
189
|
-
global.expect = _expect = require('
|
|
189
|
+
global.expect = _expect = require('./expect')
|
|
190
190
|
|
|
191
191
|
} else if (is_mocha) { // it's mocha
|
|
192
192
|
|
|
@@ -220,7 +220,7 @@ let _expect = undefined
|
|
|
220
220
|
global.afterAll = global.after = (msg,fn) => after(fn||msg)
|
|
221
221
|
global.beforeEach = beforeEach
|
|
222
222
|
global.afterEach = afterEach
|
|
223
|
-
global.expect = _expect = require('
|
|
223
|
+
global.expect = _expect = require('./expect')
|
|
224
224
|
// suite was introduced in Node 22
|
|
225
225
|
suite?.('<next>', ()=>{}) //> to signal the start of a test file
|
|
226
226
|
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
)
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Node.js v${required.version} or higher is required for @sap/cds.
|
|
10
|
-
Current v${given.version} does not satisfy this.
|
|
11
|
-
\n`) || 1)
|
|
12
|
-
|
|
13
|
-
function _major_minor (version) {
|
|
14
|
-
let [ major, minor ] = version.split('.').map(x => +x)
|
|
15
|
-
return { version, major, minor }
|
|
1
|
+
let version = /(\d+)(?:\.(\d+).*)?/ .exec (require('../../package.json').engines.node)
|
|
2
|
+
let given = /(\d+)(?:\.(\d+).*)?/ .exec (process.version)
|
|
3
|
+
if (+given[1] < +version[1] || given[1] == version[1] && +given[2] < +version[2]) {
|
|
4
|
+
process.stderr.write (`
|
|
5
|
+
Node.js version ${version[0]} or higher is required for @sap/cds.
|
|
6
|
+
Current version ${given[0]} does not satisfy this.
|
|
7
|
+
\n`)
|
|
8
|
+
process.exit(1)
|
|
16
9
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
module.exports = (target) => ({ with (...aspects) {
|
|
7
|
+
const t = is_class(target) ? target.prototype : target
|
|
8
|
+
const excludes = _excludes [typeof t] || {}
|
|
9
|
+
for (let each of aspects) {
|
|
10
|
+
const a = is_class(each) ? each.prototype : each
|
|
11
|
+
for (let p of Reflect.ownKeys(a)) {
|
|
12
|
+
if (p in excludes) continue
|
|
13
|
+
Reflect.defineProperty (t,p, Reflect.getOwnPropertyDescriptor(a,p))
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return target
|
|
17
|
+
}})
|
|
18
|
+
|
|
19
|
+
const _excludes = { object:{}, function: function(){}, }
|
|
20
|
+
const is_class = (x) => typeof x === 'function' && x.prototype && /^class\b/.test(x)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** @type <T>(target:T) => T */
|
|
2
|
+
const lazify = module.exports = (o) => {
|
|
3
|
+
if (o.constructor === module.constructor) return lazify_module(o)
|
|
4
|
+
for (let p of Reflect.ownKeys(o)) {
|
|
5
|
+
const d = Reflect.getOwnPropertyDescriptor(o,p)
|
|
6
|
+
if (is_lazy(d.value)) Reflect.defineProperty (o,p,{
|
|
7
|
+
set(v) { Reflect.defineProperty (this,p,{value:v,__proto__:d}) },
|
|
8
|
+
get() { return this[p] = d.value.call(this,p,this) },
|
|
9
|
+
configurable: true,
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
return o
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Used to lazify a module's exports.
|
|
17
|
+
* @example
|
|
18
|
+
* require = lazify (module)
|
|
19
|
+
* module.exports = {
|
|
20
|
+
* foo: require ('foo') // will be lazy-loaded
|
|
21
|
+
* }
|
|
22
|
+
* @returns {(id:string)=>{}} a funtion to use instead of require
|
|
23
|
+
*/
|
|
24
|
+
const lazify_module = (module) => {
|
|
25
|
+
// monkey-patch module.exports setter to lazify all
|
|
26
|
+
Object.defineProperty (module, 'exports',{ set(all) {
|
|
27
|
+
Object.defineProperty (module, 'exports',{ value:lazify(all) })
|
|
28
|
+
}})
|
|
29
|
+
// return a function to use instead of require
|
|
30
|
+
return (id) => (lazy) => module.require(id) // eslint-disable-line no-unused-vars
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const is_lazy = (x) => typeof x === 'function' && /^(function\s?)?\(?lazy[,)\t =]/.test(x)
|
package/lib/utils/tar.js
CHANGED
|
@@ -51,6 +51,44 @@ const createTemp = async (root, resources) => {
|
|
|
51
51
|
return temp
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
const tarInfo = async (info) => {
|
|
55
|
+
let cmd, param
|
|
56
|
+
if (info === 'version') {
|
|
57
|
+
cmd = 'tar'
|
|
58
|
+
param = ['--version']
|
|
59
|
+
} else {
|
|
60
|
+
cmd = process.platform === 'win32' ? 'where' : 'which'
|
|
61
|
+
param = ['tar']
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const c = spawn (cmd, param)
|
|
65
|
+
|
|
66
|
+
return {__proto__:c,
|
|
67
|
+
then (resolve, reject) {
|
|
68
|
+
let data=[], stderr=''
|
|
69
|
+
c.stdout.on('data', d => {
|
|
70
|
+
data.push(d)
|
|
71
|
+
})
|
|
72
|
+
c.stderr.on('data', d => stderr += d)
|
|
73
|
+
c.on('close', code => {
|
|
74
|
+
code ? reject(new Error(stderr)) : resolve(Buffer.concat(data).toString().replace(/\n/g,'').replace(/\r/g,''))
|
|
75
|
+
})
|
|
76
|
+
c.on('error', reject)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const logDebugTar = async () => {
|
|
82
|
+
const LOG = cds.log('tar')
|
|
83
|
+
if (!LOG?._debug) return
|
|
84
|
+
try {
|
|
85
|
+
LOG (`tar version: ${await tarInfo('version')}`)
|
|
86
|
+
LOG (`tar path: ${await tarInfo('path')}`)
|
|
87
|
+
} catch (err) {
|
|
88
|
+
LOG('tar error', err)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
54
92
|
/**
|
|
55
93
|
* Creates a tar archive, to an in-memory Buffer, or piped to write stream or file.
|
|
56
94
|
* @example ```js
|
|
@@ -76,7 +114,7 @@ const createTemp = async (root, resources) => {
|
|
|
76
114
|
* - `.to()` is a convenient shortcut to pipe the output into a write stream
|
|
77
115
|
*/
|
|
78
116
|
exports.create = async (dir='.', ...args) => {
|
|
79
|
-
|
|
117
|
+
logDebugTar()
|
|
80
118
|
if (typeof dir === 'string') dir = _resolve(dir)
|
|
81
119
|
if (Array.isArray(dir)) [ dir, ...args ] = [ cds.root, dir, ...args ]
|
|
82
120
|
|
|
@@ -42,7 +42,6 @@ if (cds.requires.extensibility || cds.requires.toggles) {
|
|
|
42
42
|
// prettier-ignore
|
|
43
43
|
return function ODataAdapter(req, res) {
|
|
44
44
|
const model = cds.context?.model || srv.model
|
|
45
|
-
if (!model._cached) Object.defineProperty (model, '_cached', { value: { touched: Date.now() } })
|
|
46
45
|
const adapters = model._cached._odata_adapters ??= {}
|
|
47
46
|
const okra = adapters[id] ??= new adapter4 ({ __proto__: srv, _real_srv: srv, model })
|
|
48
47
|
return okra (req, res)
|
|
@@ -5,8 +5,6 @@ const { reject, getRejectReason, resolveUserAttrs, getAuthRelevantEntity } = req
|
|
|
5
5
|
const { DRAFT_EVENTS, MOD_EVENTS } = require('./constants')
|
|
6
6
|
const { getNormalizedPlainRestrictions } = require('./restrictions')
|
|
7
7
|
|
|
8
|
-
const { cqn2cqn4sql } = require('../../utils/cqn2cqn4sql')
|
|
9
|
-
|
|
10
8
|
const _getResolvedApplicables = (applicables, req) => {
|
|
11
9
|
const resolvedApplicables = []
|
|
12
10
|
|
|
@@ -189,7 +187,7 @@ const _getRestrictedCount = async (req, model, resolvedApplicables) => {
|
|
|
189
187
|
const restrictionForTarget = _getRestrictionForTarget(resolvedApplicables, req.target)
|
|
190
188
|
if (restrictionForTarget) selectRestricted.where(restrictionForTarget)
|
|
191
189
|
|
|
192
|
-
const { n } = await dbtx.run(
|
|
190
|
+
const { n } = await dbtx.run(selectRestricted)
|
|
193
191
|
return n
|
|
194
192
|
}
|
|
195
193
|
|
|
@@ -76,7 +76,7 @@ const commonGenericSorting = function (req) {
|
|
|
76
76
|
|
|
77
77
|
if (select.from && select.from.SELECT) {
|
|
78
78
|
// add default sort to root query
|
|
79
|
-
|
|
79
|
+
_addDefaultSortOrder(req, select)
|
|
80
80
|
|
|
81
81
|
// apply default sort to bottom-most sub-query
|
|
82
82
|
while (select.from.SELECT) select = select.from.SELECT
|