@sap/cds 6.1.2 → 6.2.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 +92 -8
- package/apis/cds.d.ts +18 -6
- package/apis/connect.d.ts +1 -1
- package/apis/cqn.d.ts +1 -1
- package/apis/log.d.ts +23 -5
- package/apis/ql.d.ts +128 -61
- package/apis/services.d.ts +11 -0
- package/apis/test.d.ts +61 -0
- package/apis/utils.d.ts +15 -0
- package/app/fiori/preview.js +1 -0
- package/bin/build/buildTaskEngine.js +70 -22
- package/bin/build/buildTaskFactory.js +18 -11
- package/bin/build/buildTaskHandler.js +1 -1
- package/bin/build/buildTaskProviderFactory.js +3 -13
- package/bin/build/constants.js +0 -1
- package/bin/build/index.js +14 -6
- package/bin/build/provider/buildTaskHandlerEdmx.js +2 -3
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -2
- package/bin/build/provider/buildTaskHandlerInternal.js +3 -6
- package/bin/build/provider/buildTaskProviderInternal.js +51 -39
- package/bin/build/provider/fiori/index.js +3 -3
- package/bin/build/provider/hana/2migration.js +1 -1
- package/bin/build/provider/hana/index.js +34 -27
- package/bin/build/provider/java/index.js +6 -7
- package/bin/build/provider/mtx/index.js +20 -18
- package/bin/build/provider/mtx/resourcesTarBuilder.js +8 -11
- package/bin/build/provider/mtx-sidecar/index.js +13 -17
- package/bin/build/provider/nodejs/index.js +8 -7
- package/bin/build/util.js +22 -4
- package/bin/cds.js +8 -4
- package/bin/deploy/to-hana/cfUtil.js +53 -18
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +37 -30
- package/lib/auth/basic-auth.js +33 -0
- package/lib/auth/dummy-auth.js +7 -0
- package/lib/auth/ias-auth.js +2 -0
- package/lib/auth/index.js +31 -0
- package/lib/auth/jwt-auth.js +3 -0
- package/lib/auth/mocked-users.js +72 -0
- package/lib/auth/passport-basic.js +12 -0
- package/lib/auth/passport-digest.js +14 -0
- package/lib/auth/xsuaa-auth.js +3 -0
- package/lib/compile/cds-compile.js +3 -3
- package/lib/compile/to/cdl.js +5 -1
- package/lib/compile/to/edm.js +8 -0
- package/lib/compile/to/gql.js +1 -0
- package/lib/compile/to/json.js +30 -5
- package/lib/compile/to/sql.js +3 -1
- package/lib/core/index.js +5 -1
- package/lib/dbs/cds-deploy.js +36 -6
- package/lib/env/cds-env.js +15 -5
- package/lib/env/cds-requires.js +51 -58
- package/lib/env/defaults.js +1 -0
- package/lib/env/schemas/cds-package.json +4 -0
- package/lib/env/schemas/cds-rc.json +63 -77
- package/lib/i18n/localize.js +16 -5
- package/lib/index.js +9 -4
- package/lib/log/cds-error.js +4 -6
- package/lib/log/cds-log.js +89 -53
- package/lib/log/service/index.js +1 -0
- package/lib/ql/CREATE.js +2 -5
- package/lib/ql/DELETE.js +1 -1
- package/lib/ql/DROP.js +1 -3
- package/lib/ql/INSERT.js +3 -3
- package/lib/ql/Query.js +10 -23
- package/lib/ql/SELECT.js +1 -2
- package/lib/ql/UPDATE.js +2 -2
- package/lib/ql/Whereable.js +7 -15
- package/lib/ql/cds-ql.js +9 -3
- package/lib/req/cds-context.js +11 -3
- package/lib/req/context.js +29 -23
- package/lib/req/locale.js +9 -5
- package/lib/req/request.js +1 -0
- package/lib/req/user.js +2 -1
- package/lib/srv/cds-connect.js +1 -1
- package/lib/srv/cds-serve.js +21 -14
- package/lib/srv/middlewares/cds-context.js +29 -0
- package/lib/srv/middlewares/ctx-model.js +24 -0
- package/lib/srv/middlewares/errors.js +9 -0
- package/lib/srv/middlewares/index.js +22 -0
- package/lib/srv/middlewares/sap-statistics.js +13 -0
- package/lib/srv/middlewares/trace.js +102 -0
- package/lib/srv/protocols/_legacy.js +42 -0
- package/lib/srv/protocols/graphql.js +39 -0
- package/lib/srv/protocols/hcql.js +37 -0
- package/lib/srv/protocols/index.js +86 -0
- package/lib/srv/protocols/odata-v2-proxy.js +3767 -0
- package/lib/srv/protocols/odata-v2.js +26 -0
- package/lib/srv/protocols/odata-v4.js +16 -0
- package/lib/srv/protocols/rest.js +13 -0
- package/lib/srv/srv-api.js +5 -0
- package/lib/srv/srv-models.js +4 -6
- package/lib/utils/axios.js +3 -2
- package/lib/utils/cds-test.js +27 -21
- package/lib/utils/cds-utils.js +19 -20
- package/lib/utils/tar.js +175 -0
- package/libx/_runtime/audit/generic/personal/utils.js +18 -7
- package/libx/_runtime/audit/utils/v2.js +1 -0
- package/libx/_runtime/auth/index.js +4 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +76 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +15 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriInfo.js +5 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +12 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js +47 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -2
- package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -1
- package/libx/_runtime/cds-services/util/assert.js +7 -0
- package/libx/_runtime/common/aspects/relation.js +1 -1
- package/libx/_runtime/common/composition/data.js +61 -15
- package/libx/_runtime/common/composition/delete.js +0 -1
- package/libx/_runtime/common/composition/insert.js +0 -1
- package/libx/_runtime/common/composition/tree.js +4 -10
- package/libx/_runtime/common/composition/update.js +44 -21
- package/libx/_runtime/common/generic/auth/capabilities.js +8 -10
- package/libx/_runtime/common/generic/crud.js +1 -2
- package/libx/_runtime/common/generic/etag.js +4 -4
- package/libx/_runtime/common/generic/input.js +21 -6
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/generic/put.js +7 -4
- package/libx/_runtime/common/generic/sorting.js +4 -4
- package/libx/_runtime/common/generic/temporal.js +3 -6
- package/libx/_runtime/common/i18n/messages.properties +0 -7
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/csn.js +0 -28
- package/libx/_runtime/common/utils/draft.js +8 -1
- package/libx/_runtime/common/utils/path.js +7 -1
- package/libx/_runtime/common/utils/propagateForeignKeys.js +122 -0
- package/libx/_runtime/common/utils/resolveView.js +2 -3
- package/libx/_runtime/common/utils/template.js +2 -3
- package/libx/_runtime/db/data-conversion/post-processing.js +3 -44
- package/libx/_runtime/db/generic/input.js +6 -6
- package/libx/_runtime/db/sql-builder/dataTypes.js +4 -0
- package/libx/_runtime/fiori/generic/activate.js +2 -2
- package/libx/_runtime/fiori/generic/before.js +40 -72
- package/libx/_runtime/fiori/generic/cancel.js +2 -2
- package/libx/_runtime/fiori/generic/delete.js +2 -2
- package/libx/_runtime/fiori/generic/edit.js +2 -2
- package/libx/_runtime/fiori/generic/new.js +3 -5
- package/libx/_runtime/fiori/generic/patch.js +49 -43
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +27 -37
- package/libx/_runtime/fiori/utils/where.js +4 -2
- package/libx/_runtime/hana/Service.js +1 -3
- package/libx/_runtime/hana/conversion.js +3 -0
- package/libx/_runtime/hana/driver.js +33 -3
- package/libx/_runtime/hana/dynatrace.js +1 -0
- package/libx/_runtime/hana/search2Contains.js +12 -1
- package/libx/_runtime/hana/search2cqn4sql.js +10 -27
- package/libx/_runtime/hana/streaming.js +1 -0
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -0
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +5 -2
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -0
- package/libx/_runtime/messaging/enterprise-messaging.js +62 -3
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/redis-messaging.js +1 -0
- package/libx/_runtime/remote/Service.js +2 -2
- package/libx/_runtime/remote/utils/client.js +35 -11
- package/libx/_runtime/remote/utils/data.js +7 -2
- package/libx/_runtime/sqlite/Service.js +18 -7
- package/libx/_runtime/sqlite/conversion.js +3 -0
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +3 -3
- package/libx/_runtime/sqlite/localized.js +8 -8
- package/libx/odata/afterburner.js +39 -7
- package/libx/odata/cqn2odata.js +6 -3
- package/libx/odata/grammar.pegjs +66 -18
- package/libx/odata/index.js +3 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -0
- package/libx/rest/RestAdapter.js +62 -43
- package/libx/rest/middleware/input.js +2 -3
- package/libx/rest/middleware/parse.js +2 -1
- package/libx/rest/middleware/update.js +1 -1
- package/package.json +2 -2
- package/server.js +5 -4
- package/srv/mtx.cds +1 -1
- package/srv/mtx.js +4 -24
- package/lib/srv/adapters.js +0 -85
- package/lib/utils/resources/index.js +0 -48
- package/lib/utils/resources/tar.js +0 -49
- package/lib/utils/resources/utils.js +0 -11
- package/libx/_runtime/db/utils/propagateForeignKeys.js +0 -93
- package/libx/_runtime/extensibility/activate.js +0 -69
- package/libx/_runtime/extensibility/add.js +0 -50
- package/libx/_runtime/extensibility/addExtension.js +0 -72
- package/libx/_runtime/extensibility/defaults.js +0 -34
- package/libx/_runtime/extensibility/handler/transformREAD.js +0 -121
- package/libx/_runtime/extensibility/handler/transformRESULT.js +0 -51
- package/libx/_runtime/extensibility/handler/transformWRITE.js +0 -64
- package/libx/_runtime/extensibility/linter/allowlist_checker.js +0 -373
- package/libx/_runtime/extensibility/linter/annotations_checker.js +0 -113
- package/libx/_runtime/extensibility/linter/checker_base.js +0 -20
- package/libx/_runtime/extensibility/linter/namespace_checker.js +0 -180
- package/libx/_runtime/extensibility/linter.js +0 -32
- package/libx/_runtime/extensibility/push.js +0 -118
- package/libx/_runtime/extensibility/service.js +0 -38
- package/libx/_runtime/extensibility/token.js +0 -57
- package/libx/_runtime/extensibility/utils.js +0 -131
- package/libx/_runtime/extensibility/validation.js +0 -50
- package/libx/_runtime/extensibility/views.js +0 -12
- package/srv/extensibility-service.cds +0 -59
- package/srv/extensibility-service.js +0 -1
- package/srv/extensions.cds +0 -8
- package/srv/model-provider.cds +0 -61
- package/srv/model-provider.js +0 -143
package/lib/srv/cds-serve.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const cds = require ('..')
|
|
2
|
-
const { ProtocolAdapter } = cds.service.
|
|
2
|
+
const { ProtocolAdapter } = cds.service.protocols
|
|
3
3
|
const { Service } = cds.service.factory
|
|
4
4
|
const _ready = Symbol(), _pending = cds.services._pending || {}
|
|
5
5
|
|
|
@@ -63,7 +63,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
|
|
|
63
63
|
// skip all services marked to be ignored
|
|
64
64
|
d['@cds.ignore'] || d['@cds.serve.ignore'] ||
|
|
65
65
|
// skip external services, unless asked to mock them and unbound
|
|
66
|
-
(d['@cds.external'] || required[d.name]?.external) && (!o.mocked || required[d.name]
|
|
66
|
+
(d['@cds.external'] || required[d.name]?.external) && (!o.mocked || required[d.name]?.credentials)
|
|
67
67
|
))
|
|
68
68
|
if (services.length > 1 && o.at) {
|
|
69
69
|
throw cds.error `You cannot specify 'path' for multiple services`
|
|
@@ -80,7 +80,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
|
|
|
80
80
|
if (o.service && o.service._is_service_instance) return srv
|
|
81
81
|
srv.init && await srv.prepend (srv.init)
|
|
82
82
|
srv.options.impl && await srv.prepend (srv.options.impl)
|
|
83
|
-
cds.services[srv.name] =
|
|
83
|
+
cds.services[srv.name] = srv
|
|
84
84
|
cds.service.providers.push (srv)
|
|
85
85
|
if (srv[_ready]) srv[_ready](srv)
|
|
86
86
|
return srv
|
|
@@ -100,24 +100,30 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
|
|
|
100
100
|
ready = ready.then (()=> all.forEach (each => {
|
|
101
101
|
const d = each.definition
|
|
102
102
|
if (d['@protocol'] === 'none' || d['@cds.api.ignore']) return each._is_dark = true
|
|
103
|
-
ProtocolAdapter.serve(each,
|
|
103
|
+
ProtocolAdapter.serve (each, /*in:*/ app)
|
|
104
104
|
if (!o.silent) cds.emit ('serving',each)
|
|
105
105
|
}))
|
|
106
106
|
return this
|
|
107
107
|
},
|
|
108
108
|
|
|
109
109
|
/** Finally resolve to a single picked provider or a map of all */
|
|
110
|
-
then: (
|
|
111
|
-
if (all.length === 0) return
|
|
112
|
-
|
|
113
|
-
for (
|
|
114
|
-
|
|
110
|
+
then: (_resolve, _error) => ready.then (()=>{
|
|
111
|
+
if (all.length === 0) return _resolve()
|
|
112
|
+
const response = all.length === 1 ? all[0] : {}
|
|
113
|
+
for (const each of all) {
|
|
114
|
+
Object.defineProperty (response, each.name, { enumerable:true, configurable:true, get(){
|
|
115
|
+
let chimera = each
|
|
116
|
+
if (each.definition && !each._is_dark) {
|
|
117
|
+
chimera = ProtocolAdapter.for (each)
|
|
118
|
+
Object.setPrototypeOf (chimera, each)
|
|
119
|
+
Object.defineProperty (chimera, 'name', { value: each.name })
|
|
120
|
+
}
|
|
121
|
+
Object.defineProperty (response, each.name, { enumerable:true, value: chimera })
|
|
122
|
+
return chimera
|
|
123
|
+
}})
|
|
115
124
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
return resolve (response)
|
|
120
|
-
}, failed),
|
|
125
|
+
return _resolve (response)
|
|
126
|
+
}, _error),
|
|
121
127
|
|
|
122
128
|
catch: (e) => ready.catch(e)
|
|
123
129
|
}
|
|
@@ -140,6 +146,7 @@ async function _new (Service, d,m,o) {
|
|
|
140
146
|
let service = cds.env.requires[srv.name]?.service
|
|
141
147
|
if (service && !cds.services[service]) Object.defineProperty (cds.services, service, {value:srv})
|
|
142
148
|
}
|
|
149
|
+
if (!o.silent) cds.emit (`serving:${srv.name}`, srv)
|
|
143
150
|
})
|
|
144
151
|
return srv
|
|
145
152
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module.exports = ()=> {
|
|
2
|
+
|
|
3
|
+
const cds = require ('../../index')
|
|
4
|
+
|
|
5
|
+
/** @type { import('express').Handler } */
|
|
6
|
+
async function cds_context_provider (req, res, next) {
|
|
7
|
+
const ctx = new cds.EventContext
|
|
8
|
+
ctx.http = { req, res }
|
|
9
|
+
ctx.id = _id4(req)
|
|
10
|
+
ctx.user = req.user
|
|
11
|
+
ctx.tenant = req.tenant || ctx.user?.tenant
|
|
12
|
+
cds._context.run (ctx, next)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { uuid } = cds.utils
|
|
16
|
+
const _id4 = (req) => {
|
|
17
|
+
let id = req.headers['x-correlation-id'] = (
|
|
18
|
+
req.headers['x-correlation-id'] ||
|
|
19
|
+
req.headers['x-correlationid'] ||
|
|
20
|
+
req.headers['x-request-id'] ||
|
|
21
|
+
req.headers['x-vcap-request-id'] ||
|
|
22
|
+
uuid()
|
|
23
|
+
)
|
|
24
|
+
req.res.set('X-Correlation-ID', id)
|
|
25
|
+
return id
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return cds_context_provider
|
|
29
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module.exports = ()=>{
|
|
2
|
+
const cds = require ('../../index')
|
|
3
|
+
if (cds.requires.extensibility || cds.requires.toggles || cds.mtx) {
|
|
4
|
+
const { model4 } = require('../srv-models')
|
|
5
|
+
return async function cds_context_model (req,res, next) {
|
|
6
|
+
if (req.baseUrl.startsWith('/-/')) return next() //> our own tech services cannot be extended
|
|
7
|
+
const ctx = cds.context
|
|
8
|
+
if (ctx.tenant) try {
|
|
9
|
+
// if (req.headers.features) ctx.user.features = req.headers.features //> currently done in basic-auth only
|
|
10
|
+
ctx.model = req.__model = await model4 (ctx.tenant, ctx.features) // REVISIT: req.__model is because of Okra
|
|
11
|
+
} catch (e) {
|
|
12
|
+
console.error(e)
|
|
13
|
+
return res.status(503) .json ({ // REVISIT: we should throw a simple error, nothing else! -> this is overly OData-specific!
|
|
14
|
+
error: { code: '503', message:
|
|
15
|
+
process.env.NODE_ENV === 'production' ? 'Service Unavailable' :
|
|
16
|
+
'Unable to get context-specific model due to: ' + e.message
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
next()
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else return []
|
|
24
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module.exports = ()=> function cds_error_handler (err, req, res, next) {
|
|
2
|
+
// console.error (err)
|
|
3
|
+
// const n = Number(err.status || err.statusCode)
|
|
4
|
+
// res.status (n >= 400 && n < 600 ? n : 500)
|
|
5
|
+
// err.status = 400
|
|
6
|
+
// res.json ({ ...err, message: err.message, stack: err.stack })
|
|
7
|
+
// if (err.status === 401 && req.login) return req.login()
|
|
8
|
+
next (err)
|
|
9
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const auth = exports.auth = require('../../auth')
|
|
2
|
+
const context = exports.context = require('./cds-context')
|
|
3
|
+
const ctx_model = exports.ctx_model = require('./ctx-model')
|
|
4
|
+
const errors = exports.errors = require('./errors')
|
|
5
|
+
const trace = exports.trace = require('./trace')
|
|
6
|
+
|
|
7
|
+
// middlewares running before protocol adapters
|
|
8
|
+
exports.before = [
|
|
9
|
+
trace(), // provides detailed trace logs when DEBUG=trace
|
|
10
|
+
auth(), // provides req.user & tenant
|
|
11
|
+
context(), // provides cds.context
|
|
12
|
+
ctx_model(), // fills in cds.context.model
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
exports.after = [
|
|
16
|
+
// usually error middlewares
|
|
17
|
+
errors(),
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
exports.bootstrap = ()=>{
|
|
21
|
+
require('../protocols')()
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { performance:{now} } = require ('perf_hooks')
|
|
2
|
+
|
|
3
|
+
module.exports = (prec = 1000) => function sap_statistics (req, res, next) {
|
|
4
|
+
if (req.query['sap-statistics'] || req.headers['sap-statistics']) {
|
|
5
|
+
const { writeHead } = res, t0 = now()
|
|
6
|
+
res.writeHead = function (...args) {
|
|
7
|
+
const total = ((now() - t0) / prec).toFixed(2)
|
|
8
|
+
if (res.statusCode < 400) res.setHeader('sap-statistics', `total=${total}`)
|
|
9
|
+
writeHead.call(this, ...args)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
next()
|
|
13
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const cds = require ('../../index'), { decodeURIComponent } = cds.utils
|
|
2
|
+
const LOG = cds.log('trace')
|
|
3
|
+
if (!LOG._debug) module.exports = ()=>[]; else {
|
|
4
|
+
|
|
5
|
+
module.exports = (o={}) => {
|
|
6
|
+
|
|
7
|
+
// normalize options
|
|
8
|
+
let { truncate:t = 111, skip = { BEGIN:1, COMMIT:2, ROLLBACK:3 } } = o || {}
|
|
9
|
+
if (typeof t !== 'function') o.truncate = !t ? s=>s : s => s.length <= t ? s : s.slice(0,t)+' ...'
|
|
10
|
+
if (typeof skip !== 'function') o.skip = !skip ? ()=>false : x => (x.event||x) in skip
|
|
11
|
+
const _perf = e => !o.skip(e) && cds.context?.http?.req._perf
|
|
12
|
+
|
|
13
|
+
// instrument framework services
|
|
14
|
+
_instrument_cds_services (_perf)
|
|
15
|
+
_instrument_better_sqlite (_perf)
|
|
16
|
+
_instrument_sqlite (_perf)
|
|
17
|
+
|
|
18
|
+
// the express middleware function
|
|
19
|
+
return function cap_perf_logger (req, res, next) {
|
|
20
|
+
let perf = req._perf || (req._perf = new PerfTrace)
|
|
21
|
+
perf.log (req.method, decodeURIComponent(req.originalUrl))
|
|
22
|
+
res.on('finish', ()=> LOG.debug ('elapsed times:', perf.toString(o)))
|
|
23
|
+
next()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { performance:{now} } = require ('perf_hooks')
|
|
28
|
+
const { format } = require ('util')
|
|
29
|
+
|
|
30
|
+
class PerfTrace extends Array {
|
|
31
|
+
log (...details) {
|
|
32
|
+
const e = { details, start:now() }
|
|
33
|
+
return this.push(e), e
|
|
34
|
+
}
|
|
35
|
+
done (e) {
|
|
36
|
+
return e.stop = now()
|
|
37
|
+
}
|
|
38
|
+
toString ({truncate}) {
|
|
39
|
+
const t0 = this[0].start; if (!this[0].stop) this[0].stop = now()
|
|
40
|
+
return '\n'+ this.map (e => truncate (format (
|
|
41
|
+
(e.start - t0).toFixed(2).padStart(6), '→',
|
|
42
|
+
(e.stop - t0).toFixed(2).padEnd(6), '= ',
|
|
43
|
+
(e.stop - e.start).toFixed(2).padStart(6), 'ms',
|
|
44
|
+
'-', ...e.details))
|
|
45
|
+
).join('\n')
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
function _instrument_cds_services (_get_perf) {
|
|
52
|
+
const me = _instrument_cds_services; if (me.done) return; else me.done = true
|
|
53
|
+
const { handle } = cds.Service.prototype
|
|
54
|
+
cds.Service.prototype.handle = function (req) {
|
|
55
|
+
const perf = _get_perf(req)
|
|
56
|
+
if (perf) {
|
|
57
|
+
const pe = perf.log (this.name, '-', req.event, req.path||'')
|
|
58
|
+
var _done = r => { perf.done(pe); return r }
|
|
59
|
+
}
|
|
60
|
+
return handle.apply (this, arguments) .then (_done)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function _instrument_sqlite (_get_perf) {
|
|
65
|
+
const me = _instrument_sqlite; if (me.done) return; else me.done = true
|
|
66
|
+
try { require.resolve('sqlite3') } catch { return }
|
|
67
|
+
// eslint-disable-next-line cds/no-missing-dependencies
|
|
68
|
+
const sqlite = require('sqlite3').Database.prototype
|
|
69
|
+
for (let each of ['all', 'get', 'run', 'prepare']) {
|
|
70
|
+
const _super = sqlite[each]
|
|
71
|
+
sqlite[each] = function (q, ..._) {
|
|
72
|
+
const perf = _get_perf(q)
|
|
73
|
+
if (perf) {
|
|
74
|
+
const pe = perf.log ('sqlite -', q)
|
|
75
|
+
const callback = _[_.length-1]; _[_.length-1] = function(){
|
|
76
|
+
perf.done(pe)
|
|
77
|
+
callback.apply (this, arguments)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return _super.call (this, q, ..._)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function _instrument_better_sqlite (_get_perf) {
|
|
86
|
+
const me = _instrument_better_sqlite; if (me.done) return; else me.done = true
|
|
87
|
+
try { require.resolve('better-sqlite3') } catch { return }
|
|
88
|
+
// eslint-disable-next-line cds/no-missing-dependencies
|
|
89
|
+
const sqlite = require('better-sqlite3').prototype
|
|
90
|
+
for (let each of ['exec', 'prepare']) {
|
|
91
|
+
const _super = sqlite[each]
|
|
92
|
+
sqlite[each] = function (q, ..._) {
|
|
93
|
+
const perf = _get_perf(q)
|
|
94
|
+
if (perf) {
|
|
95
|
+
const pe = perf.log ('sqlite -', q)
|
|
96
|
+
try { return _super.call (this, q, ..._) }
|
|
97
|
+
finally { perf.done(pe) }
|
|
98
|
+
}
|
|
99
|
+
else return _super.call (this, q, ..._)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const libx = require('../../../libx/_runtime')
|
|
2
|
+
const cds_context_model = require('../srv-models')
|
|
3
|
+
const { ProtocolAdapter } = require('.')
|
|
4
|
+
|
|
5
|
+
class LegacyProtocolAdapter extends ProtocolAdapter {
|
|
6
|
+
|
|
7
|
+
static init() {
|
|
8
|
+
return this.protocols = {
|
|
9
|
+
"odata": { get impl() { return libx.to.odata_v4 } },
|
|
10
|
+
"rest": { get impl() { return libx.to.rest } },
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static serve (srv, /* in: */ app) {
|
|
15
|
+
return super.serve (srv, app, { before: [
|
|
16
|
+
// async (req, res, next) => { await 1; next() }, // REVISIT: AsyncResource.bind() -> enable to break cds/tests/_runtime/odata/__tests__/integration/crud-with-mtx.test.js with existing, non-middleware mode, *w/o* fix to BufferedWriter
|
|
17
|
+
cap_req_logger,
|
|
18
|
+
libx.perf,
|
|
19
|
+
libx.auth(srv),
|
|
20
|
+
cds_context_model.middleware4(srv)
|
|
21
|
+
], after:[] })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const cds = require('../../index')
|
|
27
|
+
const LOG = cds.log(), DEBUG = cds.debug()
|
|
28
|
+
function cap_req_logger (req,_,next) {
|
|
29
|
+
let url = req.originalUrl
|
|
30
|
+
try { url = decodeURI(req.originalUrl) } catch (e) { /* decodeURI throws error for invalid urls */ }
|
|
31
|
+
LOG && LOG (req.method, url, req.body||'')
|
|
32
|
+
if (/\$batch/.test(req.url)) req.on ('dispatch', (req) => {
|
|
33
|
+
let path = req._path
|
|
34
|
+
try { path = decodeURI(req._path) } catch (e) { /* decodeURI throws error for invalid urls */ }
|
|
35
|
+
LOG && LOG ('>', req.event, path, req._query||'')
|
|
36
|
+
if (DEBUG && req.query) DEBUG (req.query)
|
|
37
|
+
})
|
|
38
|
+
next()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
LegacyProtocolAdapter.init()
|
|
42
|
+
module.exports = LegacyProtocolAdapter
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const cds = require ('../../index'), { decodeURIComponent } = cds.utils
|
|
2
|
+
const LOG = cds.log('graphql')
|
|
3
|
+
|
|
4
|
+
const GraphQLAdapter = require('@sap/cds-graphql/lib') // eslint-disable-line cds/no-missing-dependencies
|
|
5
|
+
const express = require ('express') // eslint-disable-line cds/no-missing-dependencies
|
|
6
|
+
|
|
7
|
+
function CDSGraphQLAdapter (options) {
|
|
8
|
+
|
|
9
|
+
const {services} = options
|
|
10
|
+
|
|
11
|
+
return express.Router()
|
|
12
|
+
.use (express.json()) //> required in the slug handlers and logger below
|
|
13
|
+
|
|
14
|
+
/** Convenience slug route for /graphql/srv/entity/id */
|
|
15
|
+
.use ('/:path/:entity/:id?(%20:query)?', (req,_,next) => {
|
|
16
|
+
// TODO: add filter by id -> then remove the // eslint-disable-line
|
|
17
|
+
let { entity, id, query } = req.params // eslint-disable-line no-unused-vars
|
|
18
|
+
if (query) req.body = { query }
|
|
19
|
+
if (entity) req.body.query = req.body.query.replace(/{/, `{ ${entity} {`) +'}'
|
|
20
|
+
next() //> goes on below
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
/** Convenience slug route for /graphql/srv */
|
|
24
|
+
.use ('/:path', (req, res, next) => {
|
|
25
|
+
let srv = services [req.params.path]
|
|
26
|
+
if (req.body) req.body.query = req.body.query.replace(/{/, `{ ${srv.name} {`) +'}'
|
|
27
|
+
next() //> goes on below
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
.use ((req,_,next)=>{
|
|
31
|
+
LOG.info (req.method, req.body?.query || decodeURIComponent(req.query.query))
|
|
32
|
+
next()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
/** The global /graphql route */
|
|
36
|
+
.use (new GraphQLAdapter (services, options))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = CDSGraphQLAdapter
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const cds = require('../../index'), { decodeURIComponent } = cds.utils
|
|
2
|
+
const LOG = cds.log('hcql')
|
|
3
|
+
const express = require('express') // eslint-disable-line cds/no-missing-dependencies
|
|
4
|
+
|
|
5
|
+
module.exports = function HCQLAdapter (srv) {
|
|
6
|
+
|
|
7
|
+
return express.Router()
|
|
8
|
+
.use(express.json()) //> for application/json -> cqn
|
|
9
|
+
.use(express.text()) //> for text/plain -> cql -> cqn
|
|
10
|
+
|
|
11
|
+
/** Returns CSN schema in response to /<srv>/$csn requests */
|
|
12
|
+
.get('/\\$csn', (_, res) => {
|
|
13
|
+
let csn = cds.minify (cds.model, { service: srv.name })
|
|
14
|
+
res.json(csn)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
/** Convenience route for REST-style request formats like that: */
|
|
18
|
+
.get('/:entity/:id?(%20:tail)?', (req, _, next) => {
|
|
19
|
+
let { entity, id, tail } = req.params, q = SELECT.from(entity, id)
|
|
20
|
+
if (is_string(req.body)) tail = req.body
|
|
21
|
+
else if (is_array(req.body)) q.columns(req.body)
|
|
22
|
+
else Object.assign(q.SELECT, req.body)
|
|
23
|
+
if (tail) q = { SELECT: { ...CQL(`SELECT from _ ${tail}`).SELECT, ...q.SELECT } }
|
|
24
|
+
req.body = q; next() // delegating to main handler
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
/** The actual protocol adapter. */
|
|
28
|
+
.use((req, res, next) => {
|
|
29
|
+
let q = req.body; if (is_string(q)) q = CQL(q)
|
|
30
|
+
LOG.info (req.method, decodeURIComponent(req.originalUrl), { ...q })
|
|
31
|
+
return srv.run(q).then(r => res.json(r)).catch(next)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const is_string = x => typeof x === 'string'
|
|
37
|
+
const is_array = Array.isArray
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const cds = require('../../index')
|
|
2
|
+
const {join} = cds.utils.path
|
|
3
|
+
|
|
4
|
+
class ProtocolAdapter {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Provides canonicalized protocols configurations
|
|
8
|
+
*/
|
|
9
|
+
static init (protocols = { ...cds.env.protocols }) {
|
|
10
|
+
for (let [k,o] of Object.entries(protocols)) if (typeof o === 'string') protocols[k] = {path:o}
|
|
11
|
+
if (!protocols.odata) protocols.odata = { impl: join(__dirname,'odata-v4') }
|
|
12
|
+
if (!protocols.rest) protocols.rest = { impl: join(__dirname,'rest') }
|
|
13
|
+
return this.protocols = protocols
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns the middleware impl for the given protocol
|
|
18
|
+
*/
|
|
19
|
+
static middlewareFor (p) {
|
|
20
|
+
const conf = this.protocols[p] || (this.protocols[p] = {})
|
|
21
|
+
let { impl = join(__dirname,p) } = conf; if (typeof impl !== 'function') {
|
|
22
|
+
try { require.resolve(impl) } catch { cds.error `Cannot find protocol adapter implementation: ${impl}` }
|
|
23
|
+
impl = conf.impl = require(impl)
|
|
24
|
+
}
|
|
25
|
+
return impl
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Constructs a new adapter for the given service, or returns a formerly constructed one
|
|
30
|
+
*/
|
|
31
|
+
static for (srv, p = srv.options?.to || protocol4(srv.definition)) {
|
|
32
|
+
const cache = srv._adapters || (srv._adapters={}); if (p in cache) return cache[p]
|
|
33
|
+
const impl = this.middlewareFor(p), conf = this.protocols[p]
|
|
34
|
+
return cache[p] = impl (srv, conf)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Constructs a new adapter for the given service, and mounts it to an express app.
|
|
39
|
+
*/
|
|
40
|
+
static serve (srv, /* in: */ app, { before, after } = cds.middlewares) {
|
|
41
|
+
const adapter = this.for(srv); if (!adapter) return
|
|
42
|
+
app.use (srv.path+'/webapp/', (_,res) => res.sendStatus(404))
|
|
43
|
+
app.use (srv.path, before, adapter, after)
|
|
44
|
+
return adapter
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Serve protocols at configured paths, if any; e.g. /hcql/browse, /graphql/...
|
|
49
|
+
*/
|
|
50
|
+
static serveAll (protocols = Object.entries(ProtocolAdapter.protocols).filter(([,o]) => o.path)) {
|
|
51
|
+
if (protocols?.length) cds.once ('served', ()=>{
|
|
52
|
+
const LOG = cds.log(), DEBUG = cds.debug('adapters')
|
|
53
|
+
for (let [ protocol, options ] of protocols) {
|
|
54
|
+
let globalAdapter = false
|
|
55
|
+
for (let srv of cds.service.providers) {
|
|
56
|
+
if (!protocol4(srv.definition,null)) {
|
|
57
|
+
let adapter = ProtocolAdapter.middlewareFor(protocol); if (!adapter) continue
|
|
58
|
+
if (is_global(adapter)) { globalAdapter = adapter; break }
|
|
59
|
+
_serve (adapter(srv,options), options.path + srv.path, protocol, options)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (globalAdapter) {
|
|
63
|
+
options = { ...options, services: cds.service.providers
|
|
64
|
+
.filter (srv => { let p = protocol4(srv.definition,null); return !p || p === protocol })
|
|
65
|
+
.reduce ((all,srv) => (all[srv.path.slice(1)] = srv, all), {})
|
|
66
|
+
}
|
|
67
|
+
_serve (globalAdapter(options), options.path, protocol, options)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function _serve (adapter, path, protocol, options) {
|
|
71
|
+
if (!_serve.first) { _serve.first = true; console.log() }
|
|
72
|
+
if (!_serve[protocol]) LOG.info ('serving', _serve[protocol] = { protocol, at: options.path })
|
|
73
|
+
DEBUG?.('app.use(', path, ', ... )')
|
|
74
|
+
cds.app.use (path, cds.middlewares.before, adapter, cds.middlewares.after)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
const protocols = Object.keys(ProtocolAdapter.init())
|
|
82
|
+
const protocol4 = (def, _default = protocols[0]) => def['@protocol'] || protocols.find(p => def['@'+p]) || _default
|
|
83
|
+
const is_global = adapter => adapter.length === 1 && !/^(function )?(\w+\s+)?\((srv|service)/.test(adapter)
|
|
84
|
+
|
|
85
|
+
module.exports = Object.assign (ProtocolAdapter.serveAll, { ProtocolAdapter, protocol4 })
|
|
86
|
+
if (!cds.requires.middlewares) module.exports.ProtocolAdapter = require('./_legacy')
|