@sap/cds 7.4.1 → 7.5.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 +101 -0
- package/apis/cds.d.ts +1 -38
- package/apis/core.d.ts +21 -101
- package/apis/cqn.d.ts +18 -76
- package/apis/csn.d.ts +18 -114
- package/apis/events.d.ts +16 -123
- package/apis/internal/inference.d.ts +18 -32
- package/apis/linked.d.ts +18 -97
- package/apis/log.d.ts +19 -164
- package/apis/models.d.ts +18 -180
- package/apis/ql.d.ts +16 -322
- package/apis/reflect.d.ts +32 -0
- package/apis/server.d.ts +18 -135
- package/apis/services.d.ts +18 -380
- package/bin/cds-serve.js +5 -2
- package/bin/serve.js +7 -16
- package/lib/auth/basic-auth.js +3 -1
- package/lib/auth/ias-auth.js +62 -48
- package/lib/auth/ias-claims.js +34 -0
- package/lib/auth/index.js +54 -33
- package/lib/auth/jwt-auth.js +55 -52
- package/lib/compile/cdsc.js +2 -2
- package/lib/compile/to/edm.js +4 -4
- package/lib/compile/to/hdbtabledata.js +5 -8
- package/lib/compile/to/srvinfo.js +2 -2
- package/lib/env/cds-env.js +3 -9
- package/lib/env/cds-requires.js +16 -17
- package/lib/env/compat.js +0 -9
- package/lib/env/defaults.js +17 -6
- package/lib/i18n/localize.js +46 -42
- package/lib/index.js +6 -8
- package/lib/linked/classes.js +7 -118
- package/lib/linked/entities.js +1 -1
- package/lib/log/cds-log.js +15 -10
- package/lib/log/format/aspects/als.js +41 -0
- package/lib/log/format/aspects/cf.js +36 -0
- package/lib/log/format/json.js +96 -0
- package/lib/plugins.js +7 -3
- package/lib/req/context.js +4 -2
- package/lib/srv/cds-connect.js +3 -5
- package/lib/srv/cds-serve.js +13 -26
- package/lib/srv/factory.js +3 -3
- package/lib/srv/middlewares/index.js +0 -2
- package/lib/srv/middlewares/trace.js +2 -3
- package/lib/srv/protocols/_legacy.js +27 -30
- package/lib/srv/protocols/index.js +173 -58
- package/lib/srv/protocols/odata-v4.js +29 -16
- package/lib/srv/srv-api.js +8 -13
- package/lib/srv/srv-handlers.js +14 -14
- package/lib/utils/cds-utils.js +15 -0
- package/libx/_runtime/auth/index.js +4 -5
- package/libx/_runtime/auth/strategies/basic.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +23 -13
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +6 -15
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +10 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +2 -1
- package/libx/_runtime/cds-services/services/utils/columns.js +3 -9
- package/libx/_runtime/cds.js +13 -0
- package/libx/_runtime/common/composition/data.js +3 -0
- package/libx/_runtime/common/composition/delete.js +1 -1
- package/libx/_runtime/common/error/frontend.js +2 -2
- package/libx/_runtime/common/generic/auth/readOnly.js +1 -1
- package/libx/_runtime/common/generic/auth/restrictions.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +4 -5
- package/libx/_runtime/common/utils/csn.js +23 -18
- package/libx/_runtime/common/utils/propagateForeignKeys.js +2 -1
- package/libx/_runtime/common/utils/restrictions.js +6 -15
- package/libx/_runtime/db/generic/input.js +3 -2
- package/libx/_runtime/fiori/generic/readOverDraft.js +2 -5
- package/libx/_runtime/fiori/lean-draft.js +69 -5
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/Outbox.js +3 -8
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
- package/libx/_runtime/messaging/file-based.js +1 -1
- package/libx/_runtime/messaging/service.js +7 -10
- package/libx/_runtime/remote/Service.js +15 -45
- package/libx/_runtime/remote/utils/client.js +20 -33
- package/libx/_runtime/remote/utils/cloudSdkProvider.js +30 -0
- package/libx/_runtime/sqlite/Service.js +2 -2
- package/libx/odata/afterburner.js +29 -21
- package/libx/odata/cqn2odata.js +1 -1
- package/libx/odata/error.js +7 -0
- package/libx/odata/grammar.peggy +16 -20
- package/libx/odata/metadata.js +73 -78
- package/libx/odata/parser.js +1 -1
- package/libx/odata/read.js +94 -0
- package/libx/odata/result.js +91 -0
- package/libx/odata/service-document.js +31 -37
- package/libx/odata/utils.js +2 -1
- package/libx/outbox/index.js +9 -4
- package/libx/rest/RestAdapter.js +68 -67
- package/libx/rest/middleware/create.js +20 -26
- package/libx/rest/middleware/delete.js +5 -3
- package/libx/rest/middleware/error.js +2 -3
- package/libx/rest/middleware/input.js +5 -5
- package/libx/rest/middleware/operation.js +96 -41
- package/libx/rest/middleware/parse.js +4 -6
- package/libx/rest/middleware/payload.js +5 -5
- package/libx/rest/middleware/read.js +11 -17
- package/libx/rest/middleware/update.js +20 -25
- package/package.json +2 -1
- package/server.js +7 -4
- package/srv/outbox.cds +9 -10
- package/apis/env.d.ts +0 -25
- package/apis/test.d.ts +0 -81
- package/apis/utils.d.ts +0 -15
- package/lib/auth/passport-basic.js +0 -14
- package/lib/auth/passport-digest.js +0 -16
- package/lib/env/presets.js +0 -35
- package/lib/log/format/cf.js +0 -16
- package/lib/log/format/kibana.js +0 -92
- package/lib/srv/middlewares/ctx-auth.js +0 -11
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +0 -119
package/lib/srv/cds-serve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const cds = require ('..')
|
|
2
|
-
const { ProtocolAdapter } = cds.service.protocols
|
|
3
2
|
const { Service } = cds.service.factory
|
|
3
|
+
const { serve } = cds.service.protocols
|
|
4
4
|
const _ready = Symbol(), _pending = cds.services._pending || {}
|
|
5
5
|
const TRACE = cds.debug('trace')
|
|
6
6
|
|
|
@@ -82,9 +82,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
|
|
|
82
82
|
// cds.services, so they'll be found when they cds.connect to each others.
|
|
83
83
|
let ready = provided.then (()=> Promise.all (all.map (async srv => {
|
|
84
84
|
if (o.service && o.service._is_service_instance) return srv
|
|
85
|
-
srv.
|
|
86
|
-
srv.options.impl && await srv.prepend (srv.options.impl)
|
|
87
|
-
cds.services[srv.name] = srv
|
|
85
|
+
cds.services[srv.name] = await srv._init()
|
|
88
86
|
cds.service.providers.push (srv)
|
|
89
87
|
if (srv[_ready]) srv[_ready](srv)
|
|
90
88
|
return srv
|
|
@@ -104,30 +102,23 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
|
|
|
104
102
|
ready = ready.then (()=> all.forEach (each => {
|
|
105
103
|
const d = each.definition
|
|
106
104
|
if (d['@protocol'] === 'none' || d['@cds.api.ignore']) return each._is_dark = true
|
|
107
|
-
|
|
105
|
+
serve (each, /*in:*/ app)
|
|
108
106
|
if (!o.silent) cds.emit ('serving',each)
|
|
109
107
|
}))
|
|
110
108
|
return this
|
|
111
109
|
},
|
|
112
110
|
|
|
113
|
-
/**
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (each.definition && !each._is_dark) {
|
|
121
|
-
chimera = ProtocolAdapter.for (each)
|
|
122
|
-
Object.setPrototypeOf (chimera, each)
|
|
123
|
-
Object.defineProperty (chimera, 'name', { value: each.name })
|
|
124
|
-
}
|
|
125
|
-
Object.defineProperty (response, each.name, { enumerable:true, value: chimera })
|
|
126
|
-
return chimera
|
|
127
|
-
}})
|
|
128
|
-
}
|
|
111
|
+
/**
|
|
112
|
+
* Finally resolve to a single service or a map of many,
|
|
113
|
+
* which can be used like that: @example
|
|
114
|
+
* let { CatalogService } = await cds.serve(...) // single or many
|
|
115
|
+
* let CatalogService = await cds.serve(...) // single only
|
|
116
|
+
*/
|
|
117
|
+
then: (_resolve, _error) => ready.then ((s)=>{
|
|
129
118
|
TRACE?.timeEnd('cds.serve '+som+' ')
|
|
130
|
-
return _resolve
|
|
119
|
+
if (all.length === 0) return _resolve()
|
|
120
|
+
if (all.length === 1) return _resolve(Object.defineProperty(s=all[0],s.name,{value:s}))
|
|
121
|
+
else return _resolve (all.reduce ((r,s)=>{ r[s.name]=s; return r },{}))
|
|
131
122
|
}, _error),
|
|
132
123
|
|
|
133
124
|
catch: (e) => ready.catch(e)
|
|
@@ -143,8 +134,6 @@ async function _new (Service, d,m,o) {
|
|
|
143
134
|
if (required.name) srv.name = required.name
|
|
144
135
|
if (required.external && o.mocked) srv.mocked = true
|
|
145
136
|
}
|
|
146
|
-
if (!srv.path) srv.path = cds.service.path4(srv,o)
|
|
147
|
-
if (!srv.endpoints) srv.endpoints = cds.service.endpoints4(srv,o)
|
|
148
137
|
_pending[srv.name] = new Promise (r => srv[_ready]=r).finally(()=>{
|
|
149
138
|
delete _pending[srv.name]
|
|
150
139
|
delete srv[_ready]
|
|
@@ -161,5 +150,3 @@ async function _new (Service, d,m,o) {
|
|
|
161
150
|
const is_csn = x => x && x.definitions
|
|
162
151
|
const is_files = x => Array.isArray(x) || typeof x === 'string' && !/^[\w$]*$/.test(x)
|
|
163
152
|
const is_class = x => typeof x === 'function' && x.prototype && /^class\b/.test(x)
|
|
164
|
-
|
|
165
|
-
Object.defineProperty (module.exports, 'path4', { get(){ return cds.service.path4 } })
|
package/lib/srv/factory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cds = require('..'), { path, isfile } = cds.utils
|
|
1
|
+
const cds = require('..'), { path, isfile, _redacted } = cds.utils
|
|
2
2
|
const paths = Array.from (new Set ([ cds.root, ...require.resolve.paths('x') ]))
|
|
3
3
|
const DEBUG = cds.debug('cds.service.factory',); DEBUG && DEBUG ({ 'cds.root':cds.root, paths })
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ const ServiceFactory = function (name, model, options) { //NOSONAR
|
|
|
10
10
|
const serve = !(conf && conf.external && (!o.mocked || conf.credentials))
|
|
11
11
|
const defs = !model ? {[name]:{}} : model.definitions || cds.error `Invalid argument for 'model': ${model}`
|
|
12
12
|
const def = !name || name === 'db' ? {} : defs[name] || {}
|
|
13
|
-
DEBUG
|
|
13
|
+
DEBUG?. ({ name, definition:def, options:_redacted(o) })
|
|
14
14
|
|
|
15
15
|
let it /* eslint-disable no-cond-assign */
|
|
16
16
|
if (it = o.with) return _use (it) // from cds.serve (<options>)
|
|
@@ -60,7 +60,7 @@ const _function = (impl) => !_is_class(impl) ? impl : (srv) => {
|
|
|
60
60
|
const sibling = (d) => {
|
|
61
61
|
const { dir, name } = path.parse(_source(d)), TS = process.env.CDS_TYPESCRIPT
|
|
62
62
|
for (let subdir of ['', './lib', './handlers']) {
|
|
63
|
-
let found = TS && _resolve(dir,subdir,name+'.ts') || _resolve(dir,subdir,name+'.js')
|
|
63
|
+
let found = TS && _resolve(dir,subdir,name+'.ts') || _resolve(dir,subdir,name+'.js') || _resolve(dir,subdir,name+'.mjs')
|
|
64
64
|
if (found) return found //> equiv to '.'+found.slice(home.length)
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const auth = exports.auth = require('../../auth')
|
|
2
2
|
const context = exports.context = require('./cds-context')
|
|
3
|
-
const ctx_auth = exports.ctx_auth = require('./ctx-auth')
|
|
4
3
|
const ctx_model = exports.ctx_model = require('./ctx-model')
|
|
5
4
|
const errors = exports.errors = require('./errors')
|
|
6
5
|
const trace = exports.trace = require('./trace')
|
|
@@ -10,7 +9,6 @@ exports.before = [
|
|
|
10
9
|
context, // provides cds.context
|
|
11
10
|
trace, // provides detailed trace logs when DEBUG=trace
|
|
12
11
|
auth, // provides req.user & tenant
|
|
13
|
-
ctx_auth, // propagates auth results to cds.context
|
|
14
12
|
ctx_model, // fills in cds.context.model, in case of extensibility
|
|
15
13
|
].map(mw => _instantiate(mw))
|
|
16
14
|
|
|
@@ -30,7 +30,6 @@ if (!LOG._debug) module.exports = ()=>[]; else {
|
|
|
30
30
|
class PerfTrace extends Array {
|
|
31
31
|
log (...details) {
|
|
32
32
|
const e = { details, start:performance.now() }
|
|
33
|
-
console.log(e)
|
|
34
33
|
return this.push(e), e
|
|
35
34
|
}
|
|
36
35
|
done (e) {
|
|
@@ -40,7 +39,7 @@ if (!LOG._debug) module.exports = ()=>[]; else {
|
|
|
40
39
|
const t0 = this[0].start; if (!this[0].stop) this[0].stop = performance.now()
|
|
41
40
|
return '\n'+ this.map (e => truncate (format (
|
|
42
41
|
(e.start - t0).toFixed(2).padStart(6), '→',
|
|
43
|
-
(e.stop - t0).toFixed(2).padEnd(6), '=
|
|
42
|
+
(e.stop - t0).toFixed(2).padEnd(6), '=',
|
|
44
43
|
(e.stop - e.start).toFixed(2).padStart(6), 'ms',
|
|
45
44
|
'-', ...e.details))
|
|
46
45
|
).join('\n')
|
|
@@ -110,7 +109,7 @@ function _instrument_better_sqlite (_get_perf) {
|
|
|
110
109
|
get(..._){ try { return x.get(..._) } finally { perf.done(pe) }},
|
|
111
110
|
run(..._){ try { return x.run(..._) } finally { perf.done(pe) }},
|
|
112
111
|
}
|
|
113
|
-
else return x
|
|
112
|
+
else return perf.done(pe), x
|
|
114
113
|
}
|
|
115
114
|
catch(e) { perf.done(pe); throw e }
|
|
116
115
|
}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const cds_context = require('../middlewares/cds-context')()
|
|
4
|
-
const ctx_auth = require('../middlewares/ctx-auth')()
|
|
5
|
-
const { ProtocolAdapter } = require('.')
|
|
6
|
-
|
|
7
|
-
class LegacyProtocolAdapter extends ProtocolAdapter {
|
|
1
|
+
const cds = require('../../index')
|
|
2
|
+
cds.env.features.serve_on_root = true
|
|
8
3
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
protocols['odata'] = { get impl() { return libx.to.odata_v4 } }
|
|
14
|
-
protocols['rest'] = { get impl() { return libx.to.rest } }
|
|
15
|
-
return this.protocols = cds.service._protocols = protocols
|
|
16
|
-
}
|
|
4
|
+
const protocols = require('.'), { serve } = protocols
|
|
5
|
+
protocols['odata-v4'] = { ...protocols['odata-v4'], get impl() { return libx.to.odata_v4 } }
|
|
6
|
+
protocols['odata'] = { ...protocols['odata'], get impl() { return libx.to.odata_v4 } }
|
|
7
|
+
protocols['rest'] = { ...protocols['rest'], get impl() { return libx.to.rest } }
|
|
17
8
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
libx.auth(srv),
|
|
23
|
-
ctx_auth,
|
|
24
|
-
cap_req_logger,
|
|
25
|
-
cds_context_model.middleware4(srv)
|
|
26
|
-
], after:[] })
|
|
27
|
-
}
|
|
9
|
+
const cds_context_model = require('../srv-models')
|
|
10
|
+
const cds_context = require('../middlewares/cds-context')()
|
|
11
|
+
const ctx_auth = require('../../auth')._ctx_auth
|
|
12
|
+
const libx = require('../../../libx/_runtime')
|
|
28
13
|
|
|
29
|
-
|
|
14
|
+
Object.defineProperty (protocols, 'serve', {
|
|
15
|
+
value: function _serve_legacy_adapter4 (srv, app) {
|
|
16
|
+
const endpoints = this.endpoints4 (srv)
|
|
17
|
+
if (endpoints.length > 1)
|
|
18
|
+
throw cds.error `Cannot serve multiple endpoints if cds.requires.middlewares is set to false`
|
|
19
|
+
return serve (srv, app, {
|
|
20
|
+
before: [
|
|
21
|
+
cds_context,
|
|
22
|
+
libx.perf,
|
|
23
|
+
libx.auth(srv), ctx_auth,
|
|
24
|
+
cap_req_logger,
|
|
25
|
+
cds_context_model.middleware4(srv)
|
|
26
|
+
],
|
|
27
|
+
after:[]
|
|
28
|
+
})
|
|
29
|
+
}.bind(protocols)
|
|
30
|
+
})
|
|
30
31
|
|
|
31
|
-
const cds = require('../../index')
|
|
32
32
|
const LOG = cds.log(), DEBUG = cds.debug()
|
|
33
33
|
function cap_req_logger (req,_,next) {
|
|
34
34
|
let url = req.originalUrl
|
|
@@ -42,6 +42,3 @@ function cap_req_logger (req,_,next) {
|
|
|
42
42
|
})
|
|
43
43
|
next()
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
LegacyProtocolAdapter.init()
|
|
47
|
-
module.exports = LegacyProtocolAdapter
|
|
@@ -1,81 +1,196 @@
|
|
|
1
1
|
const cds = require('../../index')
|
|
2
|
-
const {join} = cds.utils.path
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Provides canonic access to configured protocols as well as helper methods.
|
|
5
|
+
* Instance of this class is available as cds.service.protocols.
|
|
6
|
+
*/
|
|
7
|
+
class Protocols {
|
|
8
|
+
|
|
9
|
+
// Built-in protocols
|
|
10
|
+
'odata-v4' = { path: '/odata/v4', impl: './odata-v4' }
|
|
11
|
+
'odata-v2' = '/odata/v2'
|
|
12
|
+
'odata' = this['odata-v4']
|
|
13
|
+
'rest' = '/rest'
|
|
14
|
+
'hcql' = '/hcql'
|
|
15
|
+
|
|
16
|
+
/** Allows changing the default in projects */
|
|
17
|
+
default = 'odata'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
constructor (conf = cds.env.protocols||{}) {
|
|
21
|
+
// Make helpers non-enumerable and bound functions
|
|
22
|
+
const protocols = Object.defineProperties (this, {
|
|
23
|
+
default: { writable: true, enumerable:false, value: this.default }, // makes it non-enumerable
|
|
24
|
+
serve: { writable:true, configurable:true, value: this.serve.bind(this) },
|
|
25
|
+
})
|
|
26
|
+
for (let [k,p] of Object.entries(this)) this[k] = _canonic (k,p)
|
|
27
|
+
for (let [k,p] of Object.entries(conf)) this[k] = _canonic (k,p,'merge')
|
|
28
|
+
function _canonic (kind,p,merge) {
|
|
29
|
+
if (typeof p === 'string') p = { path:p }
|
|
30
|
+
if (merge) p = { ...protocols[kind], ...p }
|
|
31
|
+
if (!p.impl) p.impl = './'+kind
|
|
32
|
+
if (!p.path.startsWith('/')) p.path = '/'+p.path
|
|
33
|
+
if (p.path.endsWith('/')) p.path = p.path.slice(0,-1)
|
|
34
|
+
return p
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
get debug() {
|
|
40
|
+
// Doing this lazy to avoid eager loading of cds.env
|
|
41
|
+
return super.debug = cds.debug('adapters')
|
|
42
|
+
}
|
|
5
43
|
|
|
6
44
|
/**
|
|
7
|
-
*
|
|
45
|
+
* Constructs a new adapter for the given service, and mounts it to an express app.
|
|
8
46
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
47
|
+
serve (srv, /* in: */ app, { before, after } = cds.middlewares) {
|
|
48
|
+
|
|
49
|
+
const endpoints = srv.endpoints = this.endpoints4(srv)
|
|
50
|
+
const cached = srv._adapters ??= {}
|
|
51
|
+
let n = 0
|
|
52
|
+
|
|
53
|
+
for (let { kind, path } of endpoints) {
|
|
54
|
+
|
|
55
|
+
// construct adapter instance from resolved implementation
|
|
56
|
+
let adapter = cached[kind]; if (!adapter) {
|
|
57
|
+
const conf = this[kind] ??= {}
|
|
58
|
+
let { impl } = conf; if (typeof impl !== 'function') {
|
|
59
|
+
if (impl[0] === '.') impl = __dirname+impl.slice(1)
|
|
60
|
+
try { require.resolve(impl) } catch { cds.error `Cannot find impl for protocol adapter: ${impl}` }
|
|
61
|
+
impl = conf.impl = require(impl)
|
|
62
|
+
}
|
|
63
|
+
adapter = cached[kind] = impl.prototype ? new impl(srv, conf) : impl(srv, conf)
|
|
64
|
+
if (!adapter) continue
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// handle first as default adapter
|
|
68
|
+
if (n++ === 0) {
|
|
69
|
+
adapter.path = srv.path = path
|
|
70
|
+
cached._default = adapter
|
|
71
|
+
if (!app) return adapter //> when called without app, construct and return default adapter only
|
|
72
|
+
|
|
73
|
+
// Add a reject-all handler for non-existing static /webapp resources, which would lead
|
|
74
|
+
// to lots of "UriSemanticError: 'webapp' is not an entity set, ..." errors, if the
|
|
75
|
+
// service path and static app root path are the same, e.g. /browse in bookshop.
|
|
76
|
+
if (!path.match(/^\/.+\/.+/)) app.use (`${path}/webapp/`, (_,res) => res.sendStatus(404))
|
|
77
|
+
|
|
78
|
+
// Add a reject-all handler for access to /old/$metadata if new scheme is used
|
|
79
|
+
if (!cds.env.features.serve_on_root && path.startsWith(this[kind].path)) {
|
|
80
|
+
let LOG = cds.log(kind), msg = `PLEASE NOTE:\n
|
|
81
|
+
With @sap/cds version 7, default service paths have changed to '${this[kind].path}/<srv>'.
|
|
82
|
+
If you use SAP Fiori Elements, make sure to adapt the 'dataSources.uri' paths
|
|
83
|
+
in 'manifest.json' files accordingly. For more information see release notes at
|
|
84
|
+
https://cap.cloud.sap/docs/releases/jun23#changed-default-service-path.
|
|
85
|
+
`.replace(/ {9}/g,'')
|
|
86
|
+
app.use(`${path.slice(this[kind].path.length)}/\\$metadata`, (_,res) => {
|
|
87
|
+
res.status(404).send(`<pre>${msg}</pre>`)
|
|
88
|
+
LOG?.warn(msg); LOG = undefined
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// mount adapter to express app
|
|
94
|
+
this.debug?.('app.use(', path, ', ... )')
|
|
95
|
+
app.use (path, before, adapter, after)
|
|
96
|
+
}
|
|
12
97
|
}
|
|
13
98
|
|
|
14
99
|
/**
|
|
15
|
-
* Returns the
|
|
100
|
+
* Returns the endpoints for the given instance of cds.Service.
|
|
101
|
+
* Used in this.serve() and the outcome stored in srv.endpoints property.
|
|
102
|
+
* IMPORTANT: Currently only used internally in this module and should stay
|
|
103
|
+
* that way -> don't use anywhere else.
|
|
104
|
+
* @returns {{ kind:string, path:string }[]} Array of { kind, path } objects
|
|
16
105
|
*/
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
106
|
+
endpoints4 (srv, o = srv.options) {
|
|
107
|
+
const def = srv.definition; if (!def) return
|
|
108
|
+
|
|
109
|
+
// get @protocol annotations from service definition
|
|
110
|
+
let annos = o?.to || def['@protocol']
|
|
111
|
+
if (annos) {
|
|
112
|
+
if (annos === 'none') return
|
|
113
|
+
if (!annos.reduce) annos = [annos]
|
|
114
|
+
}
|
|
115
|
+
// get @odata, @rest annotations
|
|
116
|
+
else {
|
|
117
|
+
annos=[]; for (let kind in this) {
|
|
118
|
+
let path = def['@'+kind] || def['@protocol.'+kind]
|
|
119
|
+
if (path) annos.push ({ kind, path })
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// no annotations at all -> use default protocol
|
|
123
|
+
if (!annos.length) annos.push ({ kind: this.default })
|
|
124
|
+
|
|
125
|
+
// canonicalize to { kind, path } objects
|
|
126
|
+
const no_mws = cds.requires.middlewares === false
|
|
127
|
+
const endpoints = annos.map (each => {
|
|
128
|
+
let { kind = each['='] || each, path } = each
|
|
129
|
+
let { path: prefix } = this[kind] || cds.error `Unknown protocol: ${kind}`
|
|
130
|
+
if (typeof path !== 'string') path = o?.at || o?.path || def['@path'] || _slugified(srv.name)
|
|
131
|
+
if (path[0] !== '/') path = no_mws ? '/'+path : prefix+'/'+path
|
|
132
|
+
return { kind, path }
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// add serve-on-root endpoint if enabled
|
|
136
|
+
if (!no_mws && endpoints.length === 1 && cds.env.features.serve_on_root) {
|
|
137
|
+
let [{ kind, path }] = endpoints, prefix = this[kind].path
|
|
138
|
+
if (prefix && path.startsWith(prefix)) endpoints.unshift ({ kind, path: path.slice(prefix.length) })
|
|
22
139
|
}
|
|
23
|
-
|
|
140
|
+
|
|
141
|
+
return endpoints.length && endpoints
|
|
24
142
|
}
|
|
25
143
|
|
|
144
|
+
|
|
26
145
|
/**
|
|
27
|
-
*
|
|
146
|
+
* For compatibility with old @sap/cds-dk versions <= 7.4.0
|
|
147
|
+
* @deprecated Use def._serves_odata instead
|
|
28
148
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const impl = this.middlewareFor(p), conf = this.protocols[p]
|
|
32
|
-
return cache[p] = impl (srv, conf)
|
|
149
|
+
protocol4 (def) {
|
|
150
|
+
return def._serves_odata ? ['odata'] : []
|
|
33
151
|
}
|
|
34
152
|
|
|
35
153
|
/**
|
|
36
|
-
*
|
|
154
|
+
* Rarely used, e.g., by compile.to.srvinfo, and by compile.to.openapi:
|
|
155
|
+
* NOT PUBLIC API, hence not documented.
|
|
37
156
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (endpoints.length > 1 && !cds.requires.middlewares) {
|
|
44
|
-
cds.error `Cannot serve multiple endpoints if cds.requires.middlewares is set to false`
|
|
45
|
-
}
|
|
157
|
+
path4 (srv,o) {
|
|
158
|
+
if (!srv.definition) srv = { definition: srv, name: srv.name } // fake srv object
|
|
159
|
+
return this.endpoints4(srv,o)?.[0]?.path
|
|
160
|
+
}
|
|
46
161
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
app.use(`*${oldPath}/\\$metadata`, (req,res,next) => {
|
|
60
|
-
if (!logged) {
|
|
61
|
-
logger._warn && logger.warn(`With @sap/cds version 7, the service path has changed to '${endpoint.path}'.
|
|
62
|
-
If you use SAP Fiori Elements, make sure to adapt the 'dataSources.uri' paths
|
|
63
|
-
in 'manifest.json' files accordingly. For more information, see the release notes at
|
|
64
|
-
https://cap.cloud.sap/docs/releases/jun23.`)
|
|
65
|
-
logged = true
|
|
66
|
-
}
|
|
67
|
-
next()
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
}
|
|
162
|
+
/**
|
|
163
|
+
* Internal modules may use this to determine if the service is configured to serve OData.
|
|
164
|
+
* NOT PUBLIC API, hence not documented.
|
|
165
|
+
*/
|
|
166
|
+
_serves_odata (def) {
|
|
167
|
+
const a = def['@protocol']
|
|
168
|
+
const vals = {odata: 1, 'odata-v4': 1}
|
|
169
|
+
if (a) return typeof a === 'string' ? a in vals : a.some(p => (p.kind||p) in vals)
|
|
170
|
+
if (def['@odata']) return true
|
|
171
|
+
if (def['@rest']) return false
|
|
172
|
+
for (let p in this) if (def['@'+p] || def['@protocol.'+p]) return false
|
|
173
|
+
return this.default === 'odata'
|
|
71
174
|
}
|
|
72
175
|
}
|
|
73
176
|
|
|
74
|
-
const protocols = Object.keys(ProtocolAdapter.init())
|
|
75
|
-
// REVISIT remove protocol4 (and protocols variable)
|
|
76
|
-
const protocol4 = (def, _default = protocols[0] || 'odata-v4') => def?.['@protocol'] || protocols.find(p => def['@'+p]) || _default
|
|
77
177
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
178
|
+
// Return a sluggified variant of a service's name
|
|
179
|
+
const _slugified = name => (
|
|
180
|
+
/[^.]+$/.exec(name)[0] //> my.very.CatalogService --> CatalogService
|
|
181
|
+
.replace(/Service$/,'') //> CatalogService --> Catalog
|
|
182
|
+
.replace(/_/g,'-') //> foo_bar_baz --> foo-bar-baz
|
|
183
|
+
.replace(/([a-z0-9])([A-Z])/g, (_,c,C) => c+'-'+C) //> ODataFooBarX9 --> OData-Foo-Bar-X9
|
|
184
|
+
.toLowerCase() //> FOO --> foo
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
const using_old_paths = (path) => `PLEASE NOTE:\n
|
|
189
|
+
With @sap/cds version 7, default service paths have changed to '${path}/<srv>'.
|
|
190
|
+
If you use SAP Fiori Elements, make sure to adapt the 'dataSources.uri' paths
|
|
191
|
+
in 'manifest.json' files accordingly. For more information, see the release notes at
|
|
192
|
+
https://cap.cloud.sap/docs/releases/jun23.
|
|
193
|
+
`
|
|
194
|
+
|
|
195
|
+
module.exports = new Protocols
|
|
196
|
+
if (!cds.requires.middlewares) require('./_legacy')
|
|
@@ -1,25 +1,38 @@
|
|
|
1
|
-
const cds = require('../../index'),
|
|
1
|
+
const cds = require('../../index'),
|
|
2
|
+
{ User } = cds,
|
|
3
|
+
{ decodeURI } = cds.utils
|
|
2
4
|
const libx = require('../../../libx/_runtime')
|
|
3
5
|
const LOG = cds.log('odata')
|
|
6
|
+
const express = require('express') // eslint-disable-line cds/no-missing-dependencies
|
|
4
7
|
|
|
5
|
-
module.exports = function ODataAdapter
|
|
6
|
-
|
|
8
|
+
module.exports = function ODataAdapter(srv) {
|
|
9
|
+
const router = express.Router()
|
|
10
|
+
|
|
11
|
+
router.use((req, _, next) => {
|
|
7
12
|
let u = req.user
|
|
8
13
|
req.user = u instanceof User ? u : new User(u)
|
|
9
14
|
|
|
10
15
|
let url = decodeURI(req.originalUrl)
|
|
11
|
-
LOG && LOG
|
|
12
|
-
if (/\$batch/.test(req.url))
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
LOG && LOG(req.method, url, req.body || '')
|
|
17
|
+
if (/\$batch/.test(req.url))
|
|
18
|
+
req.on('dispatch', req => {
|
|
19
|
+
let path = decodeURI(req._path)
|
|
20
|
+
LOG && LOG('>', req.event, path, req._query || '')
|
|
21
|
+
if (LOG._debug && req.query) LOG.debug(req.query)
|
|
22
|
+
})
|
|
17
23
|
|
|
18
24
|
next()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (cds.env.features.odata_new_adapter) {
|
|
28
|
+
// REVISIT: add middleware for negative cases?
|
|
29
|
+
router.use(/^\/$/, require('../../../libx/odata/service-document')(srv))
|
|
30
|
+
router.use('/\\$metadata', require('../../../libx/odata/metadata')(srv))
|
|
31
|
+
router.get('*', require('../../../libx/odata/read')(srv))
|
|
32
|
+
router.use(require('../../../libx/odata/error')(srv))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
router.use(libx.to.odata_v4(srv))
|
|
36
|
+
|
|
37
|
+
return router
|
|
38
|
+
}
|
package/lib/srv/srv-api.js
CHANGED
|
@@ -15,17 +15,13 @@ class Service extends require('./srv-handlers') {
|
|
|
15
15
|
if (model) this.model = model
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Subclasses commonly override this to register event handlers
|
|
20
|
-
*/
|
|
21
|
-
init() {}
|
|
22
|
-
|
|
23
18
|
/**
|
|
24
19
|
* Subclasses may override this to prepare the given model appropriately
|
|
25
20
|
*/
|
|
26
21
|
set model (csn) {
|
|
27
22
|
if (csn) {
|
|
28
|
-
super.model = cds.compile.for.nodejs(csn)
|
|
23
|
+
let {definitions:defs={}} = super.model = cds.compile.for.nodejs(csn)
|
|
24
|
+
super.definition = defs[this.options?.service] || defs[this.name]
|
|
29
25
|
add_methods_to (this)
|
|
30
26
|
} else {
|
|
31
27
|
super.model = undefined
|
|
@@ -97,14 +93,8 @@ class Service extends require('./srv-handlers') {
|
|
|
97
93
|
/**
|
|
98
94
|
* Model Reflection API...
|
|
99
95
|
*/
|
|
100
|
-
get definition() {
|
|
101
|
-
const defs = this.model && this.model.definitions, o = this.options
|
|
102
|
-
return super.definition = defs && (o && defs[o.service] || defs[this.name] )
|
|
103
|
-
}
|
|
104
|
-
|
|
105
96
|
get namespace() {
|
|
106
|
-
return super.namespace = this.definition
|
|
107
|
-
|| this.model && this.model.namespace
|
|
97
|
+
return super.namespace = this.definition?.name || this.model?.namespace
|
|
108
98
|
|| !(this.isDatabaseService) && !/\W/.test(this.name) && this.name || undefined
|
|
109
99
|
}
|
|
110
100
|
|
|
@@ -131,6 +121,11 @@ class Service extends require('./srv-handlers') {
|
|
|
131
121
|
delete cds.services[this.name]
|
|
132
122
|
}
|
|
133
123
|
|
|
124
|
+
get path() { return super.path = cds.service.protocols.path4(this) }
|
|
125
|
+
set path(p) { super.path = p }
|
|
126
|
+
|
|
127
|
+
get endpoints() { return super.endpoints = cds.service.protocols.endpoints4(this) }
|
|
128
|
+
set endpoints(p) { super.endpoints = p }
|
|
134
129
|
}
|
|
135
130
|
|
|
136
131
|
const { dispatch, handle } = require('./srv-dispatch')
|
package/lib/srv/srv-handlers.js
CHANGED
|
@@ -15,23 +15,23 @@ class EventHandlers {
|
|
|
15
15
|
(r) => r.reject (405, `Event "${r.event}" not allowed for entity "${r.path}".`)
|
|
16
16
|
)}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const _real = this._handlers._real || this._handlers
|
|
25
|
-
const _new = { on:[], before:[], after:[], _initial:[], _error:[], _real }
|
|
26
|
-
await Promise.all (impl_functions.map (fn => { if (is_impl(fn)) {
|
|
27
|
-
this._handlers = _new
|
|
28
|
-
return fn.call (this,this)
|
|
29
|
-
}}))
|
|
30
|
-
for (let handlers in _new) if (_new[handlers].length) _real[handlers] = [ ..._new[handlers], ..._real[handlers] ]
|
|
31
|
-
this._handlers = _real
|
|
18
|
+
prepend (fn) {
|
|
19
|
+
const {_handlers} = this, _new = this._handlers = { on:[], before:[], after:[], _initial:[], _error:[] }
|
|
20
|
+
const x = fn.call (this,this) // NOTE: we need the doubled await to compensate usages of srv.prepend() with missing awaits !!!
|
|
21
|
+
if (x?.then) throw cds.error `srv.prepend() doesn't accept asynchronous functions anymore`
|
|
22
|
+
for (let each in _new) if (_new[each].length) _handlers[each].unshift(..._new[each])
|
|
23
|
+
this._handlers = _handlers
|
|
32
24
|
return this
|
|
33
25
|
}
|
|
34
26
|
|
|
27
|
+
async init() {}
|
|
28
|
+
async _init() {
|
|
29
|
+
const { impl } = this.options
|
|
30
|
+
if (typeof impl === 'function' && !/^class\b/.test(impl))
|
|
31
|
+
await impl.call(this,this)
|
|
32
|
+
await this.init()
|
|
33
|
+
return this
|
|
34
|
+
}
|
|
35
35
|
}
|
|
36
36
|
module.exports = EventHandlers
|
|
37
37
|
|
package/lib/utils/cds-utils.js
CHANGED
|
@@ -200,3 +200,18 @@ if (process.env.JEST_WORKER_ID === undefined) { // jest's ESM support is experi
|
|
|
200
200
|
return import (pathToFileURL(id).href) // must use a file: URL, esp. on Windows for C:\... paths
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Masks password-like strings, also reducing clutter in output
|
|
206
|
+
*/
|
|
207
|
+
const SECRETS = /(password)|(certificate)|(ca)|(clientsecret)|(secret)|(key)|(clientcert)/i
|
|
208
|
+
exports._redacted = function _redacted(cred) {
|
|
209
|
+
if (!cred) return cred
|
|
210
|
+
if (Array.isArray(cred)) return cred.map(_redacted)
|
|
211
|
+
if (typeof cred === 'object') {
|
|
212
|
+
const newCred = Object.assign({}, cred)
|
|
213
|
+
Object.keys(newCred).forEach(k => (typeof newCred[k] === 'string' && SECRETS.test(k)) ? (newCred[k] = '...') : (newCred[k] = _redacted(newCred[k])))
|
|
214
|
+
return newCred
|
|
215
|
+
}
|
|
216
|
+
return cred
|
|
217
|
+
}
|