@sap/cds 7.6.4 → 7.7.1
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 +39 -1
- package/_i18n/i18n.properties +3 -0
- package/app/index.js +14 -8
- package/bin/serve.js +51 -19
- package/common.cds +16 -0
- package/lib/auth/ias-auth.js +2 -2
- package/lib/auth/index.js +1 -1
- package/lib/auth/jwt-auth.js +1 -1
- package/lib/compile/cdsc.js +23 -11
- package/lib/compile/for/nodejs.js +2 -2
- package/lib/compile/for/odata.js +4 -0
- package/lib/compile/load.js +7 -2
- package/lib/compile/to/sql.js +3 -0
- package/lib/dbs/cds-deploy.js +197 -220
- package/lib/env/defaults.js +2 -1
- package/lib/index.js +8 -2
- package/lib/linked/types.js +1 -0
- package/lib/log/format/json.js +4 -1
- package/lib/plugins.js +2 -2
- package/lib/ql/SELECT.js +8 -8
- package/lib/req/context.js +22 -13
- package/lib/req/request.js +10 -4
- package/lib/srv/cds-connect.js +9 -3
- package/lib/srv/cds-serve.js +5 -3
- package/lib/srv/middlewares/ctx-model.js +1 -1
- package/lib/srv/protocols/odata-v4.js +38 -9
- package/lib/srv/srv-api.js +98 -140
- package/lib/srv/srv-models.js +2 -2
- package/lib/srv/srv-tx.js +1 -0
- package/lib/utils/cds-utils.js +32 -23
- package/lib/utils/data.js +1 -1
- package/lib/utils/tar.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +18 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +7 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/index.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +71 -25
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +10 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +6 -1
- package/libx/_runtime/cds-services/util/assert.js +50 -240
- package/libx/_runtime/cds.js +5 -0
- package/libx/_runtime/common/aspects/any.js +53 -45
- package/libx/_runtime/common/generic/input.js +14 -10
- package/libx/_runtime/common/generic/paging.js +1 -1
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/keys.js +1 -1
- package/libx/_runtime/common/utils/quotingStyles.js +1 -1
- package/libx/_runtime/common/utils/resolveStructured.js +4 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +5 -12
- package/libx/_runtime/common/utils/stream.js +2 -16
- package/libx/_runtime/common/utils/streamProp.js +16 -6
- package/libx/_runtime/common/utils/ucsn.js +1 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +1 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/utils/columns.js +6 -1
- package/libx/_runtime/fiori/generic/activate.js +11 -3
- package/libx/_runtime/fiori/generic/edit.js +8 -2
- package/libx/_runtime/fiori/lean-draft.js +94 -30
- package/libx/_runtime/hana/execute.js +2 -5
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +12 -22
- package/libx/_runtime/messaging/service.js +6 -2
- package/libx/common/assert/index.js +232 -0
- package/libx/common/assert/type.js +109 -0
- package/libx/common/assert/utils.js +125 -0
- package/libx/common/assert/validation.js +109 -0
- package/libx/odata/index.js +5 -5
- package/libx/odata/middleware/create.js +83 -0
- package/libx/odata/middleware/delete.js +38 -0
- package/libx/odata/middleware/error.js +8 -0
- package/libx/odata/{metadata.js → middleware/metadata.js} +8 -6
- package/libx/odata/middleware/operation.js +78 -0
- package/libx/odata/middleware/parse.js +11 -0
- package/libx/odata/{read.js → middleware/read.js} +42 -20
- package/libx/odata/{service-document.js → middleware/service-document.js} +2 -1
- package/libx/odata/middleware/stream.js +237 -0
- package/libx/odata/middleware/update.js +165 -0
- package/libx/odata/{afterburner.js → parse/afterburner.js} +79 -29
- package/libx/odata/{cqn2odata.js → parse/cqn2odata.js} +5 -3
- package/libx/odata/{parseToCqn.js → parse/parseToCqn.js} +3 -6
- package/libx/odata/{utils.js → utils/index.js} +95 -9
- package/libx/outbox/index.js +2 -1
- package/libx/rest/RestAdapter.js +0 -1
- package/libx/rest/middleware/operation.js +6 -4
- package/libx/rest/middleware/parse.js +20 -2
- package/package.json +1 -1
- package/server.js +43 -71
- package/libx/odata/create.js +0 -44
- package/libx/odata/delete.js +0 -25
- package/libx/odata/error.js +0 -12
- package/libx/odata/update.js +0 -110
- /package/libx/odata/{grammar.peggy → parse/grammar.peggy} +0 -0
- /package/libx/odata/{parser.js → parse/parser.js} +0 -0
- /package/libx/odata/{result.js → utils/result.js} +0 -0
package/lib/req/context.js
CHANGED
|
@@ -95,11 +95,11 @@ class EventContext {
|
|
|
95
95
|
if (pd?.value) this[p] = pd.value
|
|
96
96
|
}
|
|
97
97
|
let user = u instanceof cds.User ? Object.create(u,{
|
|
98
|
-
tenant: {get:()=> this.tenant},
|
|
99
|
-
locale: {get:()=> this.locale},
|
|
98
|
+
tenant: {get:()=> cds.utils.deprecated (() => this.tenant, {kind: 'Property', old: 'req.user.tenant', use: 'req.tenant'})()},
|
|
99
|
+
locale: {get:()=> cds.utils.deprecated (() => this.locale, {kind: 'Property', old: 'req.user.locale', use: 'req.locale'})()},
|
|
100
100
|
}) : Object.defineProperties (new cds.User(u), {
|
|
101
|
-
tenant: {get:()=> this.tenant},
|
|
102
|
-
locale: {get:()=> this.locale},
|
|
101
|
+
tenant: {get:()=> cds.utils.deprecated (() => this.tenant, {kind: 'Property', old: 'req.user.tenant', use: 'req.tenant'})()},
|
|
102
|
+
locale: {get:()=> cds.utils.deprecated (() => this.locale, {kind: 'Property', old: 'req.user.locale', use: 'req.locale'})()},
|
|
103
103
|
})
|
|
104
104
|
super.user = user
|
|
105
105
|
}
|
|
@@ -185,17 +185,26 @@ const _anonymous = new cds.User.default
|
|
|
185
185
|
|
|
186
186
|
class Features {
|
|
187
187
|
static for (x) { // normalizes features to an object
|
|
188
|
-
if (
|
|
188
|
+
if (x == null) return
|
|
189
189
|
if (x === '*') return this.all
|
|
190
|
-
|
|
191
|
-
if (
|
|
192
|
-
else if (typeof x === '
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
get $hash() {
|
|
190
|
+
if (Array.isArray(x)) ; //> go on below
|
|
191
|
+
else if (typeof x === 'object') x = Object.keys(x)
|
|
192
|
+
else if (typeof x === 'string') x = x.split(',')
|
|
193
|
+
if (x.length) return Object.assign (new this, x.reduce((o,f)=>{o[f]=true;return o},{}))
|
|
194
|
+
}
|
|
195
|
+
get given() { return true }
|
|
196
|
+
get $hash() { let h = Object.keys(this).join(','); Object.defineProperty(this,'$hash',{value:h}); return h }
|
|
197
|
+
includes(t) { return t in this }
|
|
198
|
+
has(t) { return t in this }
|
|
199
|
+
map(..._) { return Object.keys(this).map(..._) }
|
|
200
|
+
find(filter) { for (let t in this) if (filter(t)) return t }
|
|
201
|
+
some(filter) { for (let t in this) if (filter(t)) return true }
|
|
202
|
+
every(filter) { for (let t in this) if (!filter(t)) return false }
|
|
197
203
|
static all = new Proxy ({'*':true},{ has:() => true, get:(_,p) => p === '$hash' ? '*' : true })
|
|
198
|
-
static none = new class none extends Features {
|
|
204
|
+
static none = new class none extends Features {
|
|
205
|
+
get given(){ return false }
|
|
206
|
+
get $hash(){ return '' }
|
|
207
|
+
}
|
|
199
208
|
}
|
|
200
209
|
|
|
201
210
|
EventContext.prototype._set('_propagated', Object.seal({}))
|
package/lib/req/request.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { Responses, Errors } = require('./response')
|
|
2
|
-
const cds = require('../../lib')
|
|
2
|
+
const cds = require('../../lib'), {production} = cds.env
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Class Request represents requests received via synchronous protocols.
|
|
@@ -41,7 +41,7 @@ class Request extends require('./event') {
|
|
|
41
41
|
if (q.INSERT) return this._set ('path', _path4 (q.INSERT,'into'))
|
|
42
42
|
if (q.UPSERT) return this._set ('path', _path4 (q.UPSERT,'into'))
|
|
43
43
|
if (q.UPDATE) return this._set ('path', _path4 (q.UPDATE,'entity'))
|
|
44
|
-
if (q.DELETE) return this._set ('path', _path4 (q.DELETE,'from'))
|
|
44
|
+
if (q.DELETE) return this._set ('path', _path4 (q.DELETE,'from'))
|
|
45
45
|
}
|
|
46
46
|
const {_} = this
|
|
47
47
|
if (_.target) return this._set ('path', _.target.name)
|
|
@@ -127,7 +127,13 @@ class Request extends require('./event') {
|
|
|
127
127
|
reject (...args) {
|
|
128
128
|
if (args.length === 0 && this.errors) {
|
|
129
129
|
let errs = this.errors
|
|
130
|
-
|
|
130
|
+
if (errs.length === 1) throw errs[0]
|
|
131
|
+
let me = new cds.error ('MULTIPLE_ERRORS', { details: errs }, this.reject)
|
|
132
|
+
if (!production) me.stack += errs.map (e => '\n---------------------------------\n'+ e.stack
|
|
133
|
+
.replace('\n',': '+ (e.element||e.target||'')+'\n---------------------------------\n')
|
|
134
|
+
.replace(/^Error: /,'')
|
|
135
|
+
).join('')
|
|
136
|
+
throw me
|
|
131
137
|
}
|
|
132
138
|
if (args[0] === 401 && this._.req?._login) this._.req._login()
|
|
133
139
|
let e = this.error(...args)
|
|
@@ -169,7 +175,7 @@ const Http2Crud = {
|
|
|
169
175
|
DELETE: 'DELETE',
|
|
170
176
|
}
|
|
171
177
|
|
|
172
|
-
const SQL2Crud = {
|
|
178
|
+
const SQL2Crud = {
|
|
173
179
|
SELECT: 'READ',
|
|
174
180
|
INSERT: 'CREATE',
|
|
175
181
|
UPSERT: 'UPSERT',
|
package/lib/srv/cds-connect.js
CHANGED
|
@@ -22,7 +22,7 @@ const connect = module.exports = async function cds_connect (options) {
|
|
|
22
22
|
* @returns { Promise<import('./srv-api')> }
|
|
23
23
|
*/
|
|
24
24
|
connect.to = async (datasource, options) => {
|
|
25
|
-
TRACE?.time(`cds.connect ${datasource}
|
|
25
|
+
TRACE?.time(`cds.connect.to ${datasource}`.padEnd(22).slice(0,22))
|
|
26
26
|
let Service = cds.service.factory, _done = x=>x
|
|
27
27
|
if (typeof datasource === 'object') [options,datasource] = [datasource]
|
|
28
28
|
else if (datasource) {
|
|
@@ -43,12 +43,18 @@ connect.to = async (datasource, options) => {
|
|
|
43
43
|
throw new Error (`No service definition found for '${required.service || datasource}'`)
|
|
44
44
|
}
|
|
45
45
|
// construct new service instance
|
|
46
|
-
let srv
|
|
46
|
+
let srv
|
|
47
|
+
try {
|
|
48
|
+
srv = await new Service (datasource,m,o); await srv._init()
|
|
49
|
+
} catch (e) {
|
|
50
|
+
_pending[datasource] = Promise.reject(e)
|
|
51
|
+
throw e
|
|
52
|
+
}
|
|
47
53
|
if (o.outbox) srv = cds.outboxed(srv)
|
|
48
54
|
if (datasource === 'db') cds.db = srv
|
|
49
55
|
_done (cds.services[datasource] = srv)
|
|
50
56
|
if (!o.silent) cds.emit ('connect',srv)
|
|
51
|
-
TRACE?.timeEnd(`cds.connect ${datasource}
|
|
57
|
+
TRACE?.timeEnd(`cds.connect.to ${datasource}`.padEnd(22).slice(0,22))
|
|
52
58
|
return srv
|
|
53
59
|
}
|
|
54
60
|
|
package/lib/srv/cds-serve.js
CHANGED
|
@@ -3,11 +3,13 @@ const { Service } = cds.service.factory
|
|
|
3
3
|
const { serve } = cds.service.protocols
|
|
4
4
|
const _ready = Symbol(), _pending = cds.services._pending || {}
|
|
5
5
|
const TRACE = cds.debug('trace')
|
|
6
|
+
if (TRACE && !cds.env.features.odata_new_adapter) require('./../../libx/_runtime').to.odata_v4
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
/** @param som - a service name or a model (name or csn) */
|
|
8
10
|
module.exports = function cds_serve (som, _options) { // NOSONAR
|
|
9
11
|
|
|
10
|
-
TRACE?.time(`cds.serve ${som}
|
|
12
|
+
TRACE?.time(`cds.serve ${som}`.padEnd(22).slice(0,22))
|
|
11
13
|
|
|
12
14
|
if (som && typeof som === 'object' && !is_csn(som) && !is_files(som)) {
|
|
13
15
|
[som,_options] = [undefined,
|
|
@@ -102,7 +104,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
|
|
|
102
104
|
ready = ready.then (()=> all.forEach (each => {
|
|
103
105
|
const d = each.definition
|
|
104
106
|
if (d['@protocol'] === 'none' || d['@cds.api.ignore']) return each._is_dark = true
|
|
105
|
-
serve (each, /*
|
|
107
|
+
else serve (each, /*to:*/ app)
|
|
106
108
|
if (!o.silent) cds.emit ('serving',each)
|
|
107
109
|
}))
|
|
108
110
|
return this
|
|
@@ -115,7 +117,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
|
|
|
115
117
|
* let CatalogService = await cds.serve(...) // single only
|
|
116
118
|
*/
|
|
117
119
|
then: (_resolve, _error) => ready.then ((s)=>{
|
|
118
|
-
TRACE?.timeEnd(
|
|
120
|
+
TRACE?.timeEnd(`cds.serve ${som}`.padEnd(22).slice(0,22))
|
|
119
121
|
if (all.length === 0) return _resolve()
|
|
120
122
|
if (all.length === 1) return _resolve(Object.defineProperty(s=all[0],s.name,{value:s}))
|
|
121
123
|
else return _resolve (all.reduce ((r,s)=>{ r[s.name]=s; return r },{}))
|
|
@@ -8,7 +8,7 @@ module.exports = ()=> {
|
|
|
8
8
|
return async function cds_context_model (req,res, next) {
|
|
9
9
|
if (req.baseUrl.startsWith('/-/')) return next() //> our own tech services cannot be extended
|
|
10
10
|
const ctx = cds.context
|
|
11
|
-
if (ctx.tenant || ctx.features) try {
|
|
11
|
+
if (ctx.tenant || ctx.features?.given) try {
|
|
12
12
|
// if (req.headers.features) ctx.user.features = req.headers.features //> currently done in basic-auth only
|
|
13
13
|
ctx.model = req.__model = await model4 (ctx.tenant, ctx.features) // REVISIT: req.__model is because of Okra
|
|
14
14
|
} catch (e) {
|
|
@@ -4,6 +4,7 @@ const cds = require('../../index'),
|
|
|
4
4
|
const libx = require('../../../libx/_runtime')
|
|
5
5
|
const LOG = cds.log('odata')
|
|
6
6
|
const express = require('express') // eslint-disable-line cds/no-missing-dependencies
|
|
7
|
+
const { isStream, stream } = require('../../../libx/odata/middleware/stream')
|
|
7
8
|
|
|
8
9
|
module.exports = function ODataAdapter(srv) {
|
|
9
10
|
const router = express.Router()
|
|
@@ -25,17 +26,45 @@ module.exports = function ODataAdapter(srv) {
|
|
|
25
26
|
})
|
|
26
27
|
|
|
27
28
|
if (cds.env.features.odata_new_adapter) {
|
|
29
|
+
// REVISIT: odata_new_adapter shouldn't influence odata_new_parser and cds_assert, but we'd need to set marker in cds.context or so to remove this
|
|
30
|
+
cds.env.features.odata_new_parser = true
|
|
31
|
+
cds.env.features.cds_assert = true
|
|
28
32
|
// REVISIT: add middleware for negative cases?
|
|
29
|
-
|
|
30
|
-
router.use(
|
|
31
|
-
router.
|
|
32
|
-
|
|
33
|
+
// service root
|
|
34
|
+
router.use(/^\/$/, require('../../../libx/odata/middleware/service-document')(srv))
|
|
35
|
+
router.use('/\\$metadata', require('../../../libx/odata/middleware/metadata')(srv))
|
|
36
|
+
// parse
|
|
37
|
+
router.use(require('../../../libx/odata/middleware/parse')(srv))
|
|
33
38
|
// REVISIT do we want to build our own body parser logic?
|
|
34
|
-
router.use(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
router.use((req, res, next) => {
|
|
40
|
+
const method = req.method
|
|
41
|
+
if (method !== 'PUT') {
|
|
42
|
+
return express.json()(req, res, next)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { _query: query } = req
|
|
46
|
+
if (isStream(query)) {
|
|
47
|
+
req.body = { value: req }
|
|
48
|
+
return next(null, req, res)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// TODO: check if the raw body still exists, then we can remove deepCopy() in the handlers
|
|
52
|
+
return express.json()(req, res, next)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// handle
|
|
56
|
+
// REVISIT: with old adapter, we return 405 for HEAD requests -> check OData spec
|
|
57
|
+
router.head('*', (_, res) => res.sendStatus(405))
|
|
58
|
+
router.post('*', require('../../../libx/odata/middleware/operation')(srv)) //> action
|
|
59
|
+
router.get('*', require('../../../libx/odata/middleware/operation')(srv)) //> function
|
|
60
|
+
router.post('*', require('../../../libx/odata/middleware/create')(srv))
|
|
61
|
+
router.get('*', stream(srv))
|
|
62
|
+
router.get('*', require('../../../libx/odata/middleware/read')(srv))
|
|
63
|
+
router.put('*', require('../../../libx/odata/middleware/update')(srv))
|
|
64
|
+
router.patch('*', require('../../../libx/odata/middleware/update')(srv))
|
|
65
|
+
router.delete('*', require('../../../libx/odata/middleware/delete')(srv))
|
|
66
|
+
// error
|
|
67
|
+
router.use(require('../../../libx/odata/middleware/error')(srv))
|
|
39
68
|
} else {
|
|
40
69
|
router.use(libx.to.odata_v4(srv))
|
|
41
70
|
}
|
package/lib/srv/srv-api.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
//////////////////////////////////////////////////////////////
|
|
2
|
+
//
|
|
3
|
+
// PLEASE DO NOT RUN prettier ON THIS FILE
|
|
4
|
+
//
|
|
5
|
+
//////////////////////////////////////////////////////////////
|
|
6
|
+
|
|
7
|
+
const cds = require('..'), { Event, Request } = cds
|
|
8
|
+
const add_methods_to = require ('./srv-methods')
|
|
5
9
|
|
|
6
10
|
class Service extends require('./srv-handlers') {
|
|
7
|
-
|
|
11
|
+
|
|
12
|
+
constructor (name, model, o) {
|
|
8
13
|
if (is_object(name)) {
|
|
9
|
-
|
|
10
|
-
let srv =
|
|
11
|
-
cds.linked(model).services[0] ||
|
|
12
|
-
cds.error.expected`${{ model }} passed as first argument to be a CSN with a single service definition`
|
|
14
|
+
[ model, o ] = [ name, model ]
|
|
15
|
+
let srv = cds.linked(model).services[0] || cds.error.expected `${{model}} passed as first argument to be a CSN with a single service definition`
|
|
13
16
|
name = srv.name
|
|
14
17
|
}
|
|
15
|
-
super(name || new.target.name).options = o || (o
|
|
18
|
+
super (name || new.target.name) .options = o || (o={})
|
|
16
19
|
if (o.kind) this.kind = o.kind // shortcut
|
|
17
20
|
if (model) this.model = model
|
|
18
21
|
}
|
|
@@ -20,11 +23,11 @@ class Service extends require('./srv-handlers') {
|
|
|
20
23
|
/**
|
|
21
24
|
* Subclasses may override this to prepare the given model appropriately
|
|
22
25
|
*/
|
|
23
|
-
set model(csn) {
|
|
26
|
+
set model (csn) {
|
|
24
27
|
if (csn) {
|
|
25
|
-
let {
|
|
28
|
+
let {definitions:defs={}} = super.model = cds.compile.for.nodejs(csn)
|
|
26
29
|
super.definition = defs[this.options?.service] || defs[this.name]
|
|
27
|
-
add_methods_to(this)
|
|
30
|
+
add_methods_to (this)
|
|
28
31
|
} else {
|
|
29
32
|
super.model = undefined
|
|
30
33
|
}
|
|
@@ -33,166 +36,101 @@ class Service extends require('./srv-handlers') {
|
|
|
33
36
|
/**
|
|
34
37
|
* Messaging API to emit asynchronous event messages, i.e. instances of `cds.Event`.
|
|
35
38
|
*/
|
|
36
|
-
emit(event, data, headers) {
|
|
37
|
-
const eve = event instanceof Event ? event : new Event(
|
|
38
|
-
|
|
39
|
+
emit (event, data, headers) {
|
|
40
|
+
const eve = event instanceof Event ? event : new Event (
|
|
41
|
+
is_object(event) ? event
|
|
42
|
+
: { event, data, headers }
|
|
43
|
+
)
|
|
44
|
+
return this.dispatch (eve)
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
/**
|
|
42
48
|
* REST-style API to send synchronous requests...
|
|
43
49
|
*/
|
|
44
|
-
send(method, path, data, headers) {
|
|
45
|
-
const req =
|
|
46
|
-
method
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
get(...args) {
|
|
58
|
-
return is_rest(args[0]) ? this.send('GET', ...args) : this.read(...args)
|
|
59
|
-
}
|
|
60
|
-
put(...args) {
|
|
61
|
-
return is_rest(args[0]) ? this.send('PUT', ...args) : this.update(...args)
|
|
62
|
-
}
|
|
63
|
-
post(...args) {
|
|
64
|
-
return is_rest(args[0]) ? this.send('POST', ...args) : this.create(...args)
|
|
65
|
-
}
|
|
66
|
-
patch(...args) {
|
|
67
|
-
return is_rest(args[0]) ? this.send('PATCH', ...args) : this.update(...args)
|
|
68
|
-
}
|
|
69
|
-
delete(...args) {
|
|
70
|
-
return is_rest(args[0]) ? this.send('DELETE', ...args) : DELETE.from(...args).bind(this)
|
|
71
|
-
}
|
|
50
|
+
send (method, path, data, headers) {
|
|
51
|
+
const req = method instanceof Request ? method : new Request (
|
|
52
|
+
is_object(method) ? method :
|
|
53
|
+
is_object(path) ? { method, data:path, headers:data }
|
|
54
|
+
: { method, path, data, headers }
|
|
55
|
+
)
|
|
56
|
+
return this.dispatch (req)
|
|
57
|
+
}
|
|
58
|
+
get (...args) { return is_rest(args[0]) ? this.send('GET', ...args) : this.read (...args) }
|
|
59
|
+
put (...args) { return is_rest(args[0]) ? this.send('PUT', ...args) : this.update (...args) }
|
|
60
|
+
post (...args) { return is_rest(args[0]) ? this.send('POST', ...args) : this.create (...args) }
|
|
61
|
+
patch (...args) { return is_rest(args[0]) ? this.send('PATCH', ...args) : this.update (...args) }
|
|
62
|
+
delete (...args) { return is_rest(args[0]) ? this.send('DELETE',...args) : DELETE.from (...args).bind(this) }
|
|
72
63
|
|
|
73
64
|
/**
|
|
74
65
|
* Querying API to send synchronous requests...
|
|
75
66
|
*/
|
|
76
|
-
run(query, data) {
|
|
67
|
+
run (query, data) {
|
|
77
68
|
if (typeof query === 'function') {
|
|
78
|
-
const ctx = cds.context,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
else
|
|
83
|
-
if (!ctx.tx._done) return fn(this.tx(ctx)) // run fn with nested tx
|
|
84
|
-
else if (ctx.tx._done === 'rolled back')
|
|
85
|
-
// > reject
|
|
86
|
-
ctx.tx._throw_closed_error()
|
|
87
|
-
else return this.tx(fn) // run fn with detached root tx
|
|
88
|
-
}
|
|
69
|
+
const ctx = cds.context, fn = query
|
|
70
|
+
if (!ctx?.tx) return this.tx(fn) // run fn with root tx
|
|
71
|
+
if (!ctx.tx._done) return fn(this.tx(ctx)) // run fn with nested tx
|
|
72
|
+
if (ctx.tx._done === 'rolled back') ctx.tx._throw_closed_error()
|
|
73
|
+
else return this.tx(fn) // run fn with detached root tx
|
|
89
74
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return this.dispatch(req)
|
|
93
|
-
}
|
|
94
|
-
read(...args) {
|
|
95
|
-
return is_query(args[0]) ? this.run(...args) : SELECT(...args).bind(this)
|
|
96
|
-
}
|
|
97
|
-
// Deprecated: To be removed with the next major release
|
|
98
|
-
stream(...args) {
|
|
99
|
-
cds._logDeprecation('stream method is deprecated and will be removed in upcoming releases!')
|
|
100
|
-
if (is_query(args[0])) {
|
|
101
|
-
Object.defineProperty(args[0], '_stream', { value: true, enumerable: false })
|
|
102
|
-
if (cds.env.features.stream_compat)
|
|
103
|
-
Object.defineProperty(args[0], '_streaming', { value: true, enumerable: false })
|
|
104
|
-
return this.run(...args).then(result => (result ? Object.values(result)[0] : result))
|
|
105
|
-
}
|
|
106
|
-
const q = (args ? SELECT.one.columns(args) : SELECT.one).bind(this)
|
|
107
|
-
Object.defineProperty(q, '_stream', { value: true, enumerable: false })
|
|
108
|
-
if (cds.env.features.stream_compat) Object.defineProperty(q, '_streaming', { value: true, enumerable: false })
|
|
109
|
-
return q
|
|
110
|
-
}
|
|
111
|
-
insert(...args) {
|
|
112
|
-
return INSERT(...args).bind(this)
|
|
113
|
-
}
|
|
114
|
-
create(...args) {
|
|
115
|
-
return INSERT.into(...args).bind(this)
|
|
116
|
-
}
|
|
117
|
-
update(...args) {
|
|
118
|
-
return UPDATE.entity(...args).bind(this)
|
|
119
|
-
}
|
|
120
|
-
upsert(...args) {
|
|
121
|
-
return UPSERT(...args).bind(this)
|
|
122
|
-
}
|
|
123
|
-
exists(...args) {
|
|
124
|
-
return SELECT.one([1])
|
|
125
|
-
.from(...args)
|
|
126
|
-
.bind(this)
|
|
75
|
+
const req = new Request ({ query, data })
|
|
76
|
+
return this.dispatch (req)
|
|
127
77
|
}
|
|
78
|
+
read (...args) { return is_query(args[0]) ? this.run(...args) : SELECT(...args).bind(this) }
|
|
79
|
+
insert (...args) { return INSERT(...args).bind(this) }
|
|
80
|
+
create (...args) { return INSERT.into(...args).bind(this) }
|
|
81
|
+
update (...args) { return UPDATE.entity(...args).bind(this) }
|
|
82
|
+
upsert (...args) { return UPSERT(...args).bind(this) }
|
|
83
|
+
exists (...args) { return SELECT.one([1]).from(...args).bind(this) }
|
|
128
84
|
|
|
129
85
|
/**
|
|
130
86
|
* Streaming API variant of .run(). Subclasses should override this to support real streaming.
|
|
131
87
|
* The default implementation doesn't stream, but simply invokes the callback on each row.
|
|
132
88
|
* The callback function is invoked with (row, index).
|
|
133
89
|
*/
|
|
134
|
-
foreach(query, data, callback) {
|
|
135
|
-
if (!callback)
|
|
136
|
-
return this.run(query, data).then(rows => rows.forEach(callback) || rows)
|
|
90
|
+
foreach (query, data, callback) {
|
|
91
|
+
if (!callback) [ data, callback ] = [ undefined, data ]
|
|
92
|
+
return this.run (query, data) .then (rows => rows.forEach(callback) || rows)
|
|
137
93
|
}
|
|
138
94
|
|
|
139
95
|
/**
|
|
140
96
|
* Model Reflection API...
|
|
141
97
|
*/
|
|
142
|
-
get namespace()
|
|
143
|
-
return
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
undefined)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
get operations() {
|
|
151
|
-
return (super.operations = _reflect(this, d => d.kind === 'action' || d.kind === 'function'))
|
|
152
|
-
}
|
|
153
|
-
get entities() {
|
|
154
|
-
return (super.entities = _reflect(this, d => d.kind === 'entity'))
|
|
155
|
-
}
|
|
156
|
-
get events() {
|
|
157
|
-
return (super.events = _reflect(this, d => d.kind === 'event'))
|
|
158
|
-
}
|
|
159
|
-
get types() {
|
|
160
|
-
return (super.types = _reflect(this, d => !d.kind || d.kind === 'type'))
|
|
98
|
+
get namespace() {
|
|
99
|
+
return super.namespace = this.definition?.name
|
|
100
|
+
|| this.model?.namespace
|
|
101
|
+
|| !this.isDatabaseService && !/\W/.test(this.name) && this.name
|
|
102
|
+
|| undefined
|
|
161
103
|
}
|
|
104
|
+
get operations() { return super.operations = _reflect (this, d => d.kind === 'action' || d.kind === 'function') }
|
|
105
|
+
get entities() { return super.entities = _reflect (this, d => d.kind === 'entity') }
|
|
106
|
+
get events() { return super.events = _reflect (this, d => d.kind === 'event') }
|
|
107
|
+
get types() { return super.types = _reflect (this, d => !d.kind || d.kind === 'type') }
|
|
162
108
|
|
|
163
109
|
/**
|
|
164
110
|
* Flag to control whether this service is extensible.
|
|
165
111
|
* Can be overridden by subclasses.
|
|
166
|
-
* REVISIT cds.xt name check should move to respective services
|
|
167
112
|
*/
|
|
168
113
|
get isExtensible() {
|
|
169
|
-
return !this.name?.startsWith('cds.xt.')
|
|
114
|
+
return this.model === cds.model && !this.name?.startsWith('cds.xt.') // REVISIT cds.xt name check should move to respective services
|
|
170
115
|
}
|
|
171
116
|
|
|
172
117
|
/**
|
|
173
|
-
* Subclasses may override this to free
|
|
118
|
+
* Subclasses may override this to free resources when
|
|
119
|
+
* tenants offboard or the service is disposed.
|
|
174
120
|
*/
|
|
175
|
-
disconnect(tenant) {
|
|
176
|
-
if (this === cds.db) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
delete cds.services[this.name]
|
|
121
|
+
disconnect (tenant) { // eslint-disable-line no-unused-vars
|
|
122
|
+
// if (this === cds.db) { //> REVISIT: should go into DatabaseService
|
|
123
|
+
// if (!tenant) cds.db = undefined
|
|
124
|
+
// else if (this.dbcs) this.dbcs.delete[tenant] // This code is obviously wrong and never tested -> is that required ?!?
|
|
125
|
+
// }
|
|
126
|
+
// delete cds.services[this.name] // REVISIT: this is in contrast to some tests
|
|
181
127
|
}
|
|
182
128
|
|
|
183
|
-
get path() {
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
set path(p) {
|
|
187
|
-
super.path = p
|
|
188
|
-
}
|
|
129
|
+
get path() { return super.path = cds.service.protocols.path4(this) }
|
|
130
|
+
set path(p) { super.path = p }
|
|
189
131
|
|
|
190
|
-
get endpoints() {
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
set endpoints(p) {
|
|
194
|
-
super.endpoints = p
|
|
195
|
-
}
|
|
132
|
+
get endpoints() { return super.endpoints = cds.service.protocols.endpoints4(this) }
|
|
133
|
+
set endpoints(p) { super.endpoints = p }
|
|
196
134
|
}
|
|
197
135
|
|
|
198
136
|
const { dispatch, handle } = require('./srv-dispatch')
|
|
@@ -205,8 +143,28 @@ Service.prototype._is_service_instance = Service._is_service_class = true //> fo
|
|
|
205
143
|
module.exports = Service
|
|
206
144
|
|
|
207
145
|
// Helpers...
|
|
208
|
-
const _reflect = (srv,
|
|
146
|
+
const _reflect = (srv,filter) => !srv.model ? [] : srv.model.childrenOf (srv.namespace,filter)
|
|
209
147
|
const is_rest = x => x && typeof x === 'string' && x[0] === '/'
|
|
210
|
-
const is_query = x =>
|
|
211
|
-
const is_array = x => Array.isArray(x) && !x.raw
|
|
212
|
-
const is_object = x => typeof x === 'object'
|
|
148
|
+
const is_query = x => x && x.bind || is_array(x) && !x.raw
|
|
149
|
+
const is_array = (x) => Array.isArray(x) && !x.raw
|
|
150
|
+
const is_object = (x) => typeof x === 'object'
|
|
151
|
+
|
|
152
|
+
// Deprecated
|
|
153
|
+
/** @deprecated: To be removed with the next major release */
|
|
154
|
+
Service.prototype.stream = cds.utils.deprecated (function (...args) {
|
|
155
|
+
if (is_query(args[0])) {
|
|
156
|
+
Object.defineProperty(args[0], '_stream', { value: true, enumerable: false })
|
|
157
|
+
if (cds.env.features.stream_compat)
|
|
158
|
+
Object.defineProperty(args[0], '_streaming', { value: true, enumerable: false })
|
|
159
|
+
return this.run(...args).then(result => {
|
|
160
|
+
if (!result) return result
|
|
161
|
+
return Array.isArray(result) ? Object.values(result[0])[0] : Object.values(result)[0]})
|
|
162
|
+
}
|
|
163
|
+
const q = (args ? SELECT.one.columns(args) : SELECT.one).bind(this)
|
|
164
|
+
Object.defineProperty(q, '_stream', { value: true, enumerable: false })
|
|
165
|
+
if (cds.env.features.stream_compat) Object.defineProperty(q, '_streaming', { value: true, enumerable: false })
|
|
166
|
+
return q
|
|
167
|
+
}, {
|
|
168
|
+
old: 'srv.stream()',
|
|
169
|
+
use: 'srv.read()'
|
|
170
|
+
})
|
package/lib/srv/srv-models.js
CHANGED
|
@@ -182,9 +182,9 @@ if (!extensibility) {
|
|
|
182
182
|
|
|
183
183
|
// helper to get model for tenant/features
|
|
184
184
|
const _is_extended = extensibility ? ()=> cds.db.exists('cds.xt.Extensions') : ()=> false
|
|
185
|
-
const _get_model4 = (tenant,
|
|
185
|
+
const _get_model4 = (tenant, features) => {
|
|
186
186
|
const { 'cds.xt.ModelProviderService':mps } = cds.services
|
|
187
|
-
return mps.getCsn (tenant,
|
|
187
|
+
return mps.getCsn (tenant, features) .then (cds.compile.for.nodejs)
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
|
package/lib/srv/srv-tx.js
CHANGED
package/lib/utils/cds-utils.js
CHANGED
|
@@ -28,16 +28,6 @@ exports.Object_keys = o => ({
|
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
/**
|
|
32
|
-
* CSN-aware function to deep clone objectx
|
|
33
|
-
*/
|
|
34
|
-
exports.clone = (x) => {
|
|
35
|
-
const y = structuredClone(x)
|
|
36
|
-
if (x.$sources) Object.defineProperty (y, '$sources', { value: x.$sources })
|
|
37
|
-
return y
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
31
|
/**
|
|
42
32
|
* Simple helper to always access results as arrays.
|
|
43
33
|
*/
|
|
@@ -76,9 +66,9 @@ exports.exists = function exists (x) {
|
|
|
76
66
|
}
|
|
77
67
|
|
|
78
68
|
// REVISIT naming: doesn't return boolean
|
|
79
|
-
exports.isdir = function isdir (
|
|
80
|
-
if (
|
|
81
|
-
const y = resolve (cds.root
|
|
69
|
+
exports.isdir = function isdir (...args) {
|
|
70
|
+
if (args.length) try {
|
|
71
|
+
const y = resolve (cds.root,...args)
|
|
82
72
|
const ls = fs.lstatSync(y)
|
|
83
73
|
if (ls.isDirectory()) return y
|
|
84
74
|
if (ls.isSymbolicLink()) return isdir (join (dirname(y), fs.readlinkSync(y)))
|
|
@@ -86,9 +76,9 @@ exports.isdir = function isdir (x) {
|
|
|
86
76
|
}
|
|
87
77
|
|
|
88
78
|
// REVISIT naming: doesn't return boolean
|
|
89
|
-
exports.isfile = function isfile (
|
|
90
|
-
if (
|
|
91
|
-
const y = resolve (cds.root
|
|
79
|
+
exports.isfile = function isfile (...args) {
|
|
80
|
+
if (args.length) try {
|
|
81
|
+
const y = resolve (cds.root,...args)
|
|
92
82
|
const ls = fs.lstatSync(y)
|
|
93
83
|
if (ls.isFile()) return y
|
|
94
84
|
if (ls.isSymbolicLink()) return isfile (join (dirname(y), fs.readlinkSync(y)))
|
|
@@ -190,20 +180,39 @@ exports.find = function find (base, patterns='*', filter=()=>true) {
|
|
|
190
180
|
return files
|
|
191
181
|
}
|
|
192
182
|
|
|
193
|
-
exports.deprecated = (fn, { kind = 'Method', old = fn.name+'()', use } = {}) =>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
183
|
+
exports.deprecated = (fn, { kind = 'Method', old = fn.name+'()', use } = {}) => {
|
|
184
|
+
const yellow = '\x1b[33m'
|
|
185
|
+
const reset = '\x1b[0m'
|
|
186
|
+
if(typeof fn !== 'function') {
|
|
187
|
+
if (cds.env.features.deprecated === 'off') return
|
|
188
|
+
|
|
189
|
+
[kind,old,use] = [fn.kind || 'Configuration',fn.old,fn.use]
|
|
190
|
+
console.log (
|
|
191
|
+
yellow,
|
|
197
192
|
'\n------------------------------------------------------------------------------',
|
|
198
193
|
'\nDEPRECATED:', old, '\n',
|
|
199
194
|
'\n ', kind, old, 'is deprecated and will be removed in upcoming releases!',
|
|
200
195
|
use ? `\n => Please use ${use} instead.` : '', '\n',
|
|
201
|
-
o.stack.replace(/^Error\n\s*at.*\n/,'\n'), '\n',
|
|
202
196
|
'\n------------------------------------------------------------------------------\n',
|
|
197
|
+
reset
|
|
203
198
|
)
|
|
204
|
-
|
|
199
|
+
} else return function() {
|
|
200
|
+
if (cds.env.features.deprecated !== 'off' && !fn.warned) {
|
|
201
|
+
let o={}; Error.captureStackTrace(o)
|
|
202
|
+
console.warn (
|
|
203
|
+
yellow,
|
|
204
|
+
'\n------------------------------------------------------------------------------',
|
|
205
|
+
'\nDEPRECATED:', old, '\n',
|
|
206
|
+
'\n ', kind, old, 'is deprecated and will be removed in upcoming releases!',
|
|
207
|
+
use ? `\n => Please use ${use} instead.` : '', '\n',
|
|
208
|
+
o.stack.replace(/^Error\n\s*at.*\n/,'\n'), '\n',
|
|
209
|
+
'\n------------------------------------------------------------------------------\n',
|
|
210
|
+
reset
|
|
211
|
+
)
|
|
212
|
+
if (cds.env.features.deprecated !== 'show all') fn.warned = true
|
|
213
|
+
}
|
|
214
|
+
return fn.apply (this, arguments)
|
|
205
215
|
}
|
|
206
|
-
return fn.apply (this, arguments)
|
|
207
216
|
}
|
|
208
217
|
|
|
209
218
|
exports.csv = require('./csv-reader')
|