@sap/cds 7.9.4 → 8.0.4
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 +128 -3659
- package/_i18n/i18n_en_US_saptrc.properties +113 -0
- package/_i18n/i18n_zh_CN.properties +7 -4
- package/app/index.css +129 -0
- package/app/index.html +16 -64
- package/app/index.js +14 -9
- package/bin/args.js +34 -0
- package/bin/serve.js +18 -24
- package/bin/test.js +97 -0
- package/common.cds +5 -12
- package/eslint.config.mjs +133 -0
- package/lib/auth/basic-auth.js +16 -20
- package/lib/auth/dummy-auth.js +1 -1
- package/lib/auth/ias-auth.js +9 -41
- package/lib/auth/index.js +1 -14
- package/lib/auth/jwt-auth.js +10 -40
- package/lib/compile/cds-compile.js +1 -2
- package/lib/compile/cdsc.js +21 -26
- package/lib/compile/etc/_localized.js +1 -6
- package/lib/compile/etc/csv.js +1 -1
- package/lib/compile/etc/properties.js +1 -1
- package/lib/compile/for/java.js +1 -1
- package/lib/compile/for/lean_drafts.js +4 -6
- package/lib/compile/for/nodejs.js +1 -1
- package/lib/compile/parse.js +4 -0
- package/lib/compile/resolve.js +4 -4
- package/lib/compile/to/edm-files.js +16 -23
- package/lib/compile/to/hana.js +27 -0
- package/lib/compile/to/json.js +1 -1
- package/lib/compile/to/sql.js +5 -1
- package/lib/compile/to/yaml.js +3 -3
- package/lib/dbs/cds-deploy.js +4 -2
- package/lib/env/cds-env.js +10 -14
- package/lib/env/cds-requires.js +30 -13
- package/lib/env/defaults.js +46 -16
- package/lib/env/plugins.js +1 -1
- package/lib/env/schemas/cds-rc.js +8 -4
- package/lib/env/schemas/index.js +7 -7
- package/lib/env/serviceBindings.js +1 -1
- package/lib/index.js +12 -10
- package/lib/lazy.js +1 -1
- package/lib/linked/classes.js +36 -8
- package/lib/linked/entities.js +2 -10
- package/lib/linked/models.js +2 -1
- package/lib/linked/validate.js +292 -0
- package/lib/log/cds-error.js +0 -6
- package/lib/log/cds-log.js +3 -3
- package/lib/log/format/json.js +1 -1
- package/lib/log/service/index.js +0 -1
- package/lib/plugins.js +2 -2
- package/lib/ql/Query.js +2 -10
- package/lib/ql/SELECT.js +1 -1
- package/lib/ql/Whereable.js +3 -2
- package/lib/req/cds-context.js +14 -25
- package/lib/req/context.js +23 -25
- package/lib/req/request.js +1 -34
- package/lib/req/user.js +47 -35
- package/lib/srv/bindings.js +1 -1
- package/lib/srv/cds-connect.js +4 -4
- package/lib/srv/cds-serve.js +2 -2
- package/lib/srv/factory.js +1 -1
- package/lib/srv/middlewares/cds-context.js +11 -22
- package/lib/srv/middlewares/ctx-model.js +2 -3
- package/lib/srv/middlewares/errors.js +41 -8
- package/lib/srv/middlewares/index.js +3 -3
- package/lib/srv/middlewares/trace.js +0 -2
- package/lib/srv/protocols/hcql.js +15 -10
- package/lib/srv/protocols/http.js +44 -49
- package/lib/srv/protocols/index.js +1 -23
- package/lib/srv/protocols/odata-v4.js +12 -74
- package/lib/srv/protocols/rest.js +1 -13
- package/lib/srv/srv-api.js +0 -20
- package/lib/srv/srv-dispatch.js +3 -2
- package/lib/srv/srv-handlers.js +22 -11
- package/lib/srv/srv-methods.js +2 -2
- package/lib/srv/srv-models.js +3 -36
- package/lib/test/expect.js +343 -0
- package/lib/test/index.js +2 -0
- package/lib/test/reporter.js +176 -0
- package/lib/utils/axios.js +10 -9
- package/lib/utils/cds-test.js +85 -36
- package/lib/utils/cds-utils.js +54 -7
- package/lib/utils/check-version.js +0 -4
- package/lib/utils/colors.js +49 -0
- package/lib/utils/data.js +5 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +3 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +6 -12
- 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 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +12 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +2 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +1 -0
- 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/index.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -3
- 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/edm/AbstractEdmStructuredType.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +9 -43
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +4 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -3
- package/libx/_runtime/cds-services/util/assert.js +1 -1
- package/libx/_runtime/cds.js +10 -3
- package/libx/_runtime/common/Service.js +12 -32
- package/libx/_runtime/common/aspects/any.js +1 -0
- package/libx/_runtime/common/code-ext/execute.js +1 -1
- package/libx/_runtime/common/code-ext/worker.js +0 -1
- package/libx/_runtime/common/composition/data.js +0 -1
- package/libx/_runtime/common/composition/delete.js +0 -1
- package/libx/_runtime/common/composition/tree.js +0 -1
- package/libx/_runtime/common/composition/update.js +3 -3
- package/libx/_runtime/common/error/frontend.js +21 -12
- package/libx/_runtime/common/error/log.js +36 -0
- package/libx/_runtime/common/error/utils.js +2 -5
- package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
- package/libx/_runtime/common/generic/auth/restrict.js +23 -42
- package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
- package/libx/_runtime/common/generic/auth/utils.js +91 -88
- package/libx/_runtime/common/generic/crud.js +6 -5
- package/libx/_runtime/common/generic/etag.js +7 -12
- package/libx/_runtime/common/generic/input.js +70 -68
- package/libx/_runtime/common/generic/paging.js +1 -0
- package/libx/_runtime/common/generic/sorting.js +1 -0
- package/libx/_runtime/common/generic/temporal.js +8 -2
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/i18n/messages.properties +3 -1
- package/libx/_runtime/common/utils/binary.js +8 -2
- package/libx/_runtime/common/utils/compareJson.js +5 -1
- package/libx/_runtime/common/utils/copy.js +6 -11
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
- package/libx/_runtime/common/utils/differ.js +3 -6
- package/libx/_runtime/common/utils/keys.js +77 -18
- package/libx/_runtime/common/utils/postProcess.js +12 -15
- package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -3
- package/libx/_runtime/common/utils/restrictions.js +45 -17
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
- package/libx/_runtime/common/utils/stream.js +3 -16
- package/libx/_runtime/common/utils/streamProp.js +8 -18
- package/libx/_runtime/common/utils/structured.js +1 -1
- package/libx/_runtime/common/utils/ucsn.js +0 -2
- package/libx/_runtime/db/Service.js +0 -72
- package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
- package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
- package/libx/_runtime/db/generic/input.js +3 -8
- package/libx/_runtime/db/generic/rewrite.js +1 -0
- package/libx/_runtime/db/query/read.js +2 -2
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/utils/columns.js +2 -6
- package/libx/_runtime/fiori/lean-draft.js +138 -56
- package/libx/_runtime/hana/Service.js +0 -1
- package/libx/_runtime/hana/driver.js +1 -1
- package/libx/_runtime/hana/dynatrace.js +1 -2
- package/libx/_runtime/hana/pool.js +11 -21
- package/libx/_runtime/hana/streaming.js +0 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
- package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
- package/libx/_runtime/messaging/event-broker.js +54 -27
- package/libx/_runtime/messaging/file-based.js +3 -3
- package/libx/_runtime/messaging/http-utils/token.js +1 -1
- package/libx/_runtime/messaging/kafka.js +2 -2
- package/libx/_runtime/messaging/redis-messaging.js +0 -1
- package/libx/_runtime/remote/Service.js +25 -25
- package/libx/_runtime/remote/utils/client.js +4 -5
- package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
- package/libx/_runtime/remote/utils/data.js +0 -1
- package/libx/_runtime/sqlite/Service.js +1 -2
- package/libx/_runtime/ucl/Service.js +37 -78
- package/libx/common/assert/index.js +22 -21
- package/libx/common/assert/type-relaxed.js +39 -0
- package/libx/common/assert/utils.js +3 -2
- package/libx/common/assert/validation.js +3 -8
- package/libx/common/utils/index.js +5 -0
- package/libx/common/utils/path.js +51 -0
- package/libx/odata/ODataAdapter.js +126 -0
- package/libx/odata/index.js +15 -2
- package/libx/odata/middleware/batch.js +320 -84
- package/libx/odata/middleware/body-parser.js +33 -0
- package/libx/odata/middleware/create.js +44 -59
- package/libx/odata/middleware/delete.js +23 -12
- package/libx/odata/middleware/error.js +30 -6
- package/libx/odata/middleware/metadata.js +38 -26
- package/libx/odata/middleware/operation.js +93 -69
- package/libx/odata/middleware/parse.js +6 -8
- package/libx/odata/middleware/read.js +117 -93
- package/libx/odata/middleware/service-document.js +22 -19
- package/libx/odata/middleware/stream.js +54 -56
- package/libx/odata/middleware/update.js +79 -87
- package/libx/odata/parse/afterburner.js +191 -175
- package/libx/odata/parse/cqn2odata.js +5 -5
- package/libx/odata/parse/grammar.peggy +27 -20
- package/libx/odata/parse/multipartToJson.js +17 -9
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/etag.js +14 -6
- package/libx/odata/utils/index.js +84 -12
- package/libx/odata/utils/metadata.js +161 -0
- package/libx/odata/utils/postProcess.js +89 -0
- package/libx/odata/utils/readAfterWrite.js +134 -17
- package/libx/odata/utils/result.js +36 -142
- package/libx/outbox/index.js +4 -3
- package/libx/rest/RestAdapter.js +115 -182
- package/libx/rest/middleware/create.js +28 -24
- package/libx/rest/middleware/delete.js +7 -10
- package/libx/rest/middleware/error.js +26 -16
- package/libx/rest/middleware/operation.js +48 -41
- package/libx/rest/middleware/parse.js +128 -126
- package/libx/rest/middleware/read.js +20 -27
- package/libx/rest/middleware/update.js +26 -31
- package/package.json +17 -8
- package/server.js +4 -2
- package/apis/cds.d.ts +0 -3
- package/apis/core.d.ts +0 -21
- package/apis/cqn.d.ts +0 -18
- package/apis/csn.d.ts +0 -21
- package/apis/events.d.ts +0 -18
- package/apis/internal/inference.d.ts +0 -18
- package/apis/linked.d.ts +0 -18
- package/apis/log.d.ts +0 -20
- package/apis/models.d.ts +0 -18
- package/apis/ql.d.ts +0 -18
- package/apis/reflect.d.ts +0 -32
- package/apis/server.d.ts +0 -18
- package/apis/services.d.ts +0 -22
- package/bin/cds-serve.js +0 -56
- package/lib/compile/to/gql.js +0 -15
- package/lib/srv/protocols/_legacy.js +0 -44
- package/lib/utils/jest.js +0 -43
- package/libx/_runtime/auth/index.js +0 -193
- package/libx/_runtime/auth/strategies/JWT.js +0 -37
- package/libx/_runtime/auth/strategies/basic.js +0 -20
- package/libx/_runtime/auth/strategies/dummy.js +0 -14
- package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
- package/libx/_runtime/auth/strategies/mock.js +0 -77
- package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
- package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
- package/libx/_runtime/common/perf/index.js +0 -19
- package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
- package/libx/_runtime/fiori/draft.js +0 -2
- package/libx/_runtime/fiori/generic/activate.js +0 -190
- package/libx/_runtime/fiori/generic/before.js +0 -201
- package/libx/_runtime/fiori/generic/cancel.js +0 -19
- package/libx/_runtime/fiori/generic/delete.js +0 -21
- package/libx/_runtime/fiori/generic/edit.js +0 -157
- package/libx/_runtime/fiori/generic/index.js +0 -25
- package/libx/_runtime/fiori/generic/new.js +0 -82
- package/libx/_runtime/fiori/generic/patch.js +0 -101
- package/libx/_runtime/fiori/generic/prepare.js +0 -57
- package/libx/_runtime/fiori/generic/read.js +0 -1340
- package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
- package/libx/_runtime/fiori/utils/csn.js +0 -13
- package/libx/_runtime/fiori/utils/delete.js +0 -114
- package/libx/_runtime/fiori/utils/handler.js +0 -264
- package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
- package/libx/_runtime/fiori/utils/req.js +0 -23
- package/libx/_runtime/fiori/utils/stream.js +0 -36
- package/libx/_runtime/fiori/utils/where.js +0 -254
- package/libx/_runtime/index.js +0 -22
- package/libx/odata/utils/handler.js +0 -120
- package/libx/odata/utils/metaInfo.js +0 -410
- package/libx/odata/utils/path.js +0 -75
- package/libx/rest/RestRequest.js +0 -32
- package/libx/rest/index.js +0 -3
- package/libx/rest/readme.md +0 -1
- /package/libx/common/assert/{type.js → type-strict.js} +0 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
const cds = require('..')
|
|
2
|
+
|
|
3
|
+
/** Validates given input data against a request target definition.
|
|
4
|
+
* @param {entity} target the linked definition to check against, usually an entity definition
|
|
5
|
+
* @returns {Error[]|undefined} an array of errors or undefined if no errors occurred
|
|
6
|
+
*/
|
|
7
|
+
const conf = module.exports = exports = function validate (data, target, options={}) {
|
|
8
|
+
const vc = new Validation (data, target, options)
|
|
9
|
+
target.validate (data, null, vc)
|
|
10
|
+
return vc.errors
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/** Instances represent single validations and are mainly used to record errors during validation. */
|
|
15
|
+
class Validation {
|
|
16
|
+
|
|
17
|
+
constructor (data, target, options={}) {
|
|
18
|
+
this.data = data
|
|
19
|
+
this.target = target
|
|
20
|
+
this.options = options
|
|
21
|
+
this.insert = options.insert ?? options.mandatories
|
|
22
|
+
this.cleanse = options.cleanse !== false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
error (code, path, leaf, val, ...args) {
|
|
26
|
+
const err = (this.errors ??= new ValidationErrors).add (code)
|
|
27
|
+
if (this.options.path) path = [ this.options.path, ...path ] // e.g. used to prefic 'in/' for actions
|
|
28
|
+
if (path) err.target = (!leaf ? path : path.concat(leaf)).reduce?.((p,n)=> (
|
|
29
|
+
n?.row ? p + this.filter4(n) : //> some/entity(ID=1)...
|
|
30
|
+
typeof n === 'number' ? p + `[${n}]` : //> some/array[1]...
|
|
31
|
+
p && n ? p+'/'+n : n //> some/element/...
|
|
32
|
+
),'')
|
|
33
|
+
if (val) err.args = [ val, ...args ]
|
|
34
|
+
return err
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
filter4 ({ def, row, index }) {
|
|
38
|
+
const entity = def._target || def, filter=[]
|
|
39
|
+
for (let k in entity.keys) {
|
|
40
|
+
let v = row[k]
|
|
41
|
+
if (v === undefined) if (k === 'IsActiveEntity') v = false; else continue
|
|
42
|
+
else if (typeof v === 'string' && !entity.elements[k].isUUID || entity.elements[k]['@odata.Type'] === 'Edm.String') v = `'${v}'`
|
|
43
|
+
filter.push (`${k}=${v}`)
|
|
44
|
+
}
|
|
45
|
+
return filter.length ? `(${filter})` : `[${index}]`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
unknown(e,d) {
|
|
49
|
+
d['@open'] || cds.error (`Property "${e}" does not exist in ${d.name}`, {status:400})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
/** ValidationErrors avoid expensive creation of stack traces */
|
|
55
|
+
class ValidationErrors extends Array {
|
|
56
|
+
add (error) {
|
|
57
|
+
const err = Object.create (ValidationErrors.proto)
|
|
58
|
+
err.message = err.stack = error
|
|
59
|
+
this.push (err)
|
|
60
|
+
return err
|
|
61
|
+
}
|
|
62
|
+
static proto = Object.create (Error.prototype, {
|
|
63
|
+
message: { writable:true, configurable:true },
|
|
64
|
+
stack: { writable:true, configurable:true, value: '<none>' },
|
|
65
|
+
code: { value: '400', writable:true }, // REVISIT: should be 'ASSERT_'... (i.e. msg) but we need to adjust all tests, and have a code catalogue
|
|
66
|
+
statusCode: { value: 400 }, // REVISIT: should go into mappings in adapter's error handlers -> requires a code catalogue // REVISIT: .statusCode vs .status?
|
|
67
|
+
numericSeverity: { value: 4, enumerable: true }, // REVISIT: that is OData-specific
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
exports.ValidationErrors = ValidationErrors
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
/** Adding basic validation capabilities to linked definitions. */
|
|
74
|
+
const $any = class any {
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Central method for validating input data against CSN definitions.
|
|
78
|
+
* @param {any} value the input value to validate
|
|
79
|
+
* @param {Array} path the path prefix to use for error messages
|
|
80
|
+
* @param {Validation} ctx the request object used to record errors
|
|
81
|
+
*/
|
|
82
|
+
validate (value, path, ctx) {
|
|
83
|
+
this.check_asserts (value, path, ctx)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Checks the type of provided input values as well as @asserts specified.
|
|
88
|
+
* On first call, it constructs an optimized instance-specific override of
|
|
89
|
+
* this method for subsequent usages, with statically determined checks.
|
|
90
|
+
*/
|
|
91
|
+
check_asserts (val, path, /** @type {Validation} */ ctx) {
|
|
92
|
+
// if (this['@cds.validate'] === false) return this.set ('check_asserts', ()=>{})
|
|
93
|
+
const asserts = []
|
|
94
|
+
const type_check = conf.strict && this.strict_check || this.type_check
|
|
95
|
+
if (type_check) {
|
|
96
|
+
asserts.push ((v,p,ctx) => v == null || type_check(v) || ctx.error ('ASSERT_DATA_TYPE', p, this.name, v, this ))
|
|
97
|
+
}
|
|
98
|
+
if (this._is_mandatory()) {
|
|
99
|
+
asserts.push ((v,p,ctx) => v != null && v.trim?.() !== '' || ctx.error ('ASSERT_NOT_NULL', p, this.name, v)) // ASSERT_NOT_NULL is misleading -> should be ASSERT_REQUIRED
|
|
100
|
+
}
|
|
101
|
+
if (this['@assert.format']) {
|
|
102
|
+
const format = new RegExp(this['@assert.format'],'u')
|
|
103
|
+
asserts.push ((v,p,ctx) => v == null || format.test(v) || ctx.error ('ASSERT_FORMAT', p, this.name, v, format))
|
|
104
|
+
}
|
|
105
|
+
if (this['@assert.range'] && !this.enum) {
|
|
106
|
+
const [ min, max ] = this['@assert.range']
|
|
107
|
+
asserts.push ((v,p,ctx) => v == null || min <= v && v <= max || ctx.error ('ASSERT_RANGE', p, this.name, v, min, max))
|
|
108
|
+
}
|
|
109
|
+
if (this['@assert.enum'] || this['@assert.range'] && this.enum) {
|
|
110
|
+
const vals = Object.entries(this.enum).map(([k,v]) => 'val' in v ? v.val : k)
|
|
111
|
+
const enums = vals.reduce((a,v) => (a[v]=true, a),{})
|
|
112
|
+
asserts.push ((v,p,ctx) => v == null || v in enums || vals.some(x => x == v) || ctx.error ('ASSERT_ENUM', p, this.name, typeof v === 'string' ? `"${v}"` : v, vals.join(', ')))
|
|
113
|
+
}
|
|
114
|
+
if (!asserts.length) return this.check_asserts = ()=>{} // nothing to do
|
|
115
|
+
this.set ('check_asserts', (v,p,ctx) => asserts.forEach (a => a(v,p,ctx)))
|
|
116
|
+
this.check_asserts (val, path, ctx) // call first time
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_is_mandatory (d=this) {
|
|
120
|
+
return d.own('_mandatory', ()=> {
|
|
121
|
+
if (d['@readonly']) return false // readonly -> not mandatory
|
|
122
|
+
if (d['@mandatory']) return true
|
|
123
|
+
if (d['@Common.FieldControl']?.['#'] === 'Mandatory') return true
|
|
124
|
+
else return false
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
_is_readonly (d=this) {
|
|
129
|
+
return d.own('_readonly', ()=> {
|
|
130
|
+
if (d['@readonly']) return true
|
|
131
|
+
if (d['@cds.on.insert']) return true
|
|
132
|
+
if (d['@cds.on.update']) return true
|
|
133
|
+
if (d['@Core.Computed']) return true
|
|
134
|
+
if (d['@Common.FieldControl']?.['#'] === 'ReadOnly') return true
|
|
135
|
+
if (d['@cds.api.ignore']) return true
|
|
136
|
+
else return false
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Checks if a nested row of a deep update is in turn to be inserted or updated.
|
|
142
|
+
* This is the case if the row date does not contain all primary key elements of the target entity.
|
|
143
|
+
*/
|
|
144
|
+
_is_insert (row) {
|
|
145
|
+
const entity = this._target || this
|
|
146
|
+
const keys = Object.keys (entity.keys||{})
|
|
147
|
+
if (!keys.length) return this.set('_is_insert', ()=> true), true
|
|
148
|
+
else this.set('_is_insert', data => typeof data === 'object' && !keys.every(k => k in data))
|
|
149
|
+
return this._is_insert (row)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_required (elements) {
|
|
153
|
+
const _required = Object.values(elements).filter(this._is_mandatory)
|
|
154
|
+
this.set('_required', ()=> _required)
|
|
155
|
+
return _required
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Forward declaration for universal CSN */
|
|
159
|
+
get $struct() { return this['@odata.foreignKey4'] }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Structs iterate over their elements to validate them. */
|
|
163
|
+
class struct extends $any {
|
|
164
|
+
validate (data, path, /** @type {Validation} */ ctx, elements = this.elements, skip={}) {
|
|
165
|
+
const path_ = !path ? [] : [...path, this.name]; if (path?.row) path_.push({...path})
|
|
166
|
+
// check for required elements in case of inserts -- note: null values are handled in the payload loop below
|
|
167
|
+
if (ctx.insert || data && path_.length && this._is_insert(data)) for (let each of this._required (elements)) {
|
|
168
|
+
if (each.name in data) continue // got value for required element
|
|
169
|
+
if (each.name in skip) continue // skip uplinks in deep inserts -> see Composition.validate()
|
|
170
|
+
if (each.$struct in data) continue // got struct for flattened element/fk, e.g. {author:{ID:1}}
|
|
171
|
+
if (each.elements || each.foreignKeys) continue // skip struct-likes as we check flat payloads above, and deep payloads via struct.validate()
|
|
172
|
+
else ctx.error ('ASSERT_NOT_NULL', path_, each.name) // ASSERT_NOT_NULL should be ASSERT_REQUIRED
|
|
173
|
+
}
|
|
174
|
+
// check values of given data
|
|
175
|
+
for (let each in data) { // will work for structured payloads as well as flattened ones with universal CSN
|
|
176
|
+
let /** @type {$any} */ d = elements[each]
|
|
177
|
+
if (!d || typeof d === 'function') ctx.unknown (each, this, data) // `each` might be a method of LinkedDefinitions, like filter, map, some, every, ...
|
|
178
|
+
else if (ctx.cleanse && d._is_readonly()) delete data[each]
|
|
179
|
+
else if (d['@cds.validate'] !== false) d.validate (data[each], path_, ctx)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Array definitions validate the entries of an array against their items definition. */
|
|
185
|
+
class array extends $any {
|
|
186
|
+
validate (data, path, /** @type {Validation} */ ctx) {
|
|
187
|
+
if (data == null) return super.validate (data, path, ctx)
|
|
188
|
+
if (!Array.isArray(data)) return ctx.error ('ASSERT_ARRAY', path, this.name)
|
|
189
|
+
const path_ = path?.concat(this.name)
|
|
190
|
+
const /** @type {$any} */ items = { __proto__:this.items, name: undefined }
|
|
191
|
+
data.forEach ((entry,i) => items.validate (entry, path_.concat(i), ctx))
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Entities support both as input: single records as well as arrays of which. */
|
|
196
|
+
class entity extends struct {
|
|
197
|
+
validate (data, path, ctx, ...more) {
|
|
198
|
+
const _path4 = !path ? ()=>path : (row,i) => ({__proto__:path, index:i, row, def:this})
|
|
199
|
+
if (!Array.isArray(data)) return super.validate (data, _path4(data), ctx, ...more)
|
|
200
|
+
return data.forEach ((row,i) => super.validate (row, _path4(row,i), ctx, ...more))
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Actions are struct-like, with their parameters as elements to validate. */
|
|
205
|
+
class action extends struct {
|
|
206
|
+
validate (data, path, ctx) {
|
|
207
|
+
if (this.params) super.validate (data, path, ctx, this.params)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Managed associations are struct-like, with foreign keys as elements to validate. */
|
|
212
|
+
class Association extends struct {
|
|
213
|
+
validate (data, path, ctx) {
|
|
214
|
+
if (this.foreignKeys) super.validate (data, path, ctx, this.foreignKeys)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Compositions are like nested entities, validating deep input against their target entity definitions. */
|
|
219
|
+
class Composition extends entity {
|
|
220
|
+
validate (data, path, ctx) { if (!data) return
|
|
221
|
+
const elements = this._target.elements
|
|
222
|
+
const uplinks = {} // statically determine the uplinks for this composition
|
|
223
|
+
if (this.on) for (let {ref} of this.on) if (ref?.[0] === this.name) {
|
|
224
|
+
const fk = ref[1], fk_ = fk+'_'; uplinks[fk] = true
|
|
225
|
+
for (let e in elements) if (e.startsWith(fk_)) uplinks[e] = true
|
|
226
|
+
}
|
|
227
|
+
this.validate = (data, path, ctx) => super.validate (data, path, ctx, elements, uplinks)
|
|
228
|
+
this.validate (data, path, ctx) // call first time
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
// Type checks ---------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
$any.prototype.type_check = undefined
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* This getter constructs and returns a type check function for the declared precision and scale.
|
|
239
|
+
* Precision is the total number of digits, scale the number of digits after the decimal point.
|
|
240
|
+
*/
|
|
241
|
+
class Decimal extends $any { get type_check() {
|
|
242
|
+
const { precision:p, scale:s } = this, rx = RegExp (
|
|
243
|
+
!p ? `^[+-]?\\d+(?:\\.\\d+)?$` :
|
|
244
|
+
!s ? `^[+-]?\\d{1,${p}}$` :
|
|
245
|
+
p === s ? `^[+-]?0(?:\\.\\d{1,${s}})?$` :
|
|
246
|
+
/* p,s */ `^[+-]?\\d{1,${p-s}}(?:\\.\\d{1,${s}})?$`
|
|
247
|
+
)
|
|
248
|
+
return v => rx.test(v)
|
|
249
|
+
}}
|
|
250
|
+
|
|
251
|
+
class string extends $any { get type_check() {
|
|
252
|
+
const { length:l } = this; return l
|
|
253
|
+
? v => typeof v === 'string' && v.length <= l
|
|
254
|
+
: v => typeof v === 'string'
|
|
255
|
+
}}
|
|
256
|
+
|
|
257
|
+
const {Readable} = require('stream')
|
|
258
|
+
const _range_check = (range, min=-range) => v => min <= v && v < range
|
|
259
|
+
const _regex_check = (rx) => v => rx.test(v)
|
|
260
|
+
const _date_check = (...parts) => {
|
|
261
|
+
const rx = RegExp('^'+parts.map(p => p.source||p).join('')+'$')
|
|
262
|
+
return v => v instanceof Date || rx.test(v)
|
|
263
|
+
}
|
|
264
|
+
const YYYY = /\d{4}/
|
|
265
|
+
const MM = /-(0[1-9]|1[0-2])/
|
|
266
|
+
const DD = /-(0[1-9]|[12]\d|3[01])/
|
|
267
|
+
const hh = /[0-2]\d/
|
|
268
|
+
const mm = /:[0-5]\d/
|
|
269
|
+
const ss = /(?::[0-5]\d)?/
|
|
270
|
+
const ms = /(?::[0-5]\d(?:\.\d+)?)?/
|
|
271
|
+
const tz = /(?:Z|[+-][0-2]\d:?[0-5]\d)?/
|
|
272
|
+
|
|
273
|
+
const $ = cds.linked.classes
|
|
274
|
+
$.UUID.prototype .strict_check = _regex_check (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
|
|
275
|
+
$.boolean.prototype .strict_check = v => typeof v === 'boolean'
|
|
276
|
+
$.boolean.prototype .type_check = v => typeof v === 'boolean' || v === 0 || v === 1
|
|
277
|
+
$.number.prototype .type_check = v => !isNaN(v)
|
|
278
|
+
$.Integer.prototype .type_check = _range_check (2**53)
|
|
279
|
+
$.Int16.prototype .type_check = _range_check (2**15)
|
|
280
|
+
$.Int32.prototype .type_check = _range_check (2**31)
|
|
281
|
+
$.Int64.prototype .type_check = _range_check (2n**63n)
|
|
282
|
+
$.UInt8.prototype .type_check = _range_check (256+1,0)
|
|
283
|
+
$.Time.prototype .type_check = _date_check (hh,mm,ss)
|
|
284
|
+
$.Date.prototype .type_check = _date_check (YYYY,MM,DD)
|
|
285
|
+
$.DateTime.prototype .type_check = _date_check (YYYY,MM,DD,'(?:T',hh,mm,ss,tz,')?')
|
|
286
|
+
$.Timestamp.prototype .type_check = _date_check (YYYY,MM,DD,'(?:T',hh,mm,ms,tz,')?')
|
|
287
|
+
$.Binary.prototype .type_check = v => Buffer.isBuffer(v) || typeof v === 'string'
|
|
288
|
+
$.LargeBinary.prototype .type_check = v => Buffer.isBuffer(v) || typeof v === 'string' || v instanceof Readable
|
|
289
|
+
$.LargeString.prototype .type_check = v => Buffer.isBuffer(v) || typeof v === 'string' || v instanceof Readable
|
|
290
|
+
|
|
291
|
+
// Mixin above class extensions to cds.linked.classes
|
|
292
|
+
$.mixin ( Decimal, string, $any, action, array, struct, entity, Association, Composition )
|
package/lib/log/cds-error.js
CHANGED
|
@@ -85,9 +85,3 @@ exports._no_primary_db = new Proxy ({},{ get: function fn(_,p) { error (`Not con
|
|
|
85
85
|
cds ${process.argv[2]} --in-memory` : ''}`
|
|
86
86
|
|
|
87
87
|
,{},fn) }})
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
exports._outdated_dk = () => error `
|
|
91
|
-
This application uses @sap/cds version >= 7, which is not compatible with the installed @sap/cds-dk version 6.
|
|
92
|
-
Either update @sap/cds-dk to version 7 or downgrade @sap/cds to version 6 instead.
|
|
93
|
-
`
|
package/lib/log/cds-log.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const cds = require('../index'), conf = cds.env.log
|
|
2
2
|
const log = module.exports = exports = cds_log
|
|
3
3
|
const path = require('path')
|
|
4
|
+
/* eslint-disable no-console */
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -88,7 +89,6 @@ exports.debug = function cds_debug (id, options) {
|
|
|
88
89
|
* @param {string} [label] the module for which a logger is requested
|
|
89
90
|
* @param {number} [level] the log level to enable -> 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace
|
|
90
91
|
*/
|
|
91
|
-
/* eslint-disable no-console */
|
|
92
92
|
exports.Logger = (label, level) => {
|
|
93
93
|
const fmt = (level,args) => logger.format (label,level,...args)
|
|
94
94
|
const logger = {
|
|
@@ -112,7 +112,7 @@ function Logger (label, level) { return exports.Logger (label, level) }
|
|
|
112
112
|
* .debug(), .info(), .warn(), .error(), etc.
|
|
113
113
|
*/
|
|
114
114
|
exports.winstonLogger = (options) => (label, level) => {
|
|
115
|
-
const winston = require("winston")
|
|
115
|
+
const winston = require("winston")
|
|
116
116
|
const logger = winston.createLogger({
|
|
117
117
|
levels: log.levels, level: Object.keys(log.levels)[level],
|
|
118
118
|
transports: [new winston.transports.Console()],
|
|
@@ -175,7 +175,7 @@ const { ERROR, WARN, INFO, DEBUG, TRACE } = exports.levels = {
|
|
|
175
175
|
const conf = cds.env.log
|
|
176
176
|
if (conf.Logger) {
|
|
177
177
|
let resolvedPath
|
|
178
|
-
try { resolvedPath = require.resolve(conf.Logger) } catch {
|
|
178
|
+
try { resolvedPath = require.resolve(conf.Logger) } catch {
|
|
179
179
|
try { resolvedPath = require.resolve(path.join(cds.root, conf.Logger)) } catch {
|
|
180
180
|
throw new Error(`Cannot find logger at "${conf.Logger}"`)
|
|
181
181
|
}
|
package/lib/log/format/json.js
CHANGED
|
@@ -100,7 +100,7 @@ module.exports = function format(module, level, ...args) {
|
|
|
100
100
|
// return array with the stringified toLog (to avoid multiple log lines) as the sole element
|
|
101
101
|
try {
|
|
102
102
|
return [JSON.stringify(toLog)]
|
|
103
|
-
} catch
|
|
103
|
+
} catch {
|
|
104
104
|
// try again with removed circular references
|
|
105
105
|
return [JSON.stringify(toLog, _getCircularReplacer())]
|
|
106
106
|
}
|
package/lib/log/service/index.js
CHANGED
package/lib/plugins.js
CHANGED
|
@@ -29,7 +29,7 @@ exports.fetch = function (DEV = process.env.NODE_ENV !== 'production') {
|
|
|
29
29
|
*/
|
|
30
30
|
exports.activate = async function () {
|
|
31
31
|
const DEBUG = cds.debug ('plugins', {label:'cds'})
|
|
32
|
-
DEBUG
|
|
32
|
+
DEBUG?.time ('[cds] - loaded plugins in')
|
|
33
33
|
const { plugins } = cds.env, { local } = cds.utils
|
|
34
34
|
await Promise.all (Object.entries(plugins) .map (async ([ plugin, conf ]) => {
|
|
35
35
|
DEBUG?.(`loading plugin ${plugin}:`, { impl: local(conf.impl) })
|
|
@@ -44,6 +44,6 @@ exports.activate = async function () {
|
|
|
44
44
|
}
|
|
45
45
|
return p
|
|
46
46
|
}))
|
|
47
|
-
DEBUG
|
|
47
|
+
DEBUG?.timeEnd ('[cds] - loaded plugins in')
|
|
48
48
|
return plugins
|
|
49
49
|
}
|
package/lib/ql/Query.js
CHANGED
|
@@ -36,9 +36,8 @@ class Query {
|
|
|
36
36
|
/** Turns all queries into Thenables which execute with primary db by default */
|
|
37
37
|
get then() {
|
|
38
38
|
const srv = this._srv || cds.db || cds.error `Can't execute query as no primary database is connected.`
|
|
39
|
-
const q = new AsyncResource('await cds.query')
|
|
40
|
-
|
|
41
|
-
return (r,e) => q.runInAsyncScope (srv.run, srv, this) .then(rt => { rt = this._stream && rt ? Object.values(rt)[0] : rt; return r(rt) }, e)
|
|
39
|
+
const q = new AsyncResource('await cds.query')
|
|
40
|
+
return (r,e) => q.runInAsyncScope (srv.run, srv, this).then (r,e)
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
_target4 (...args) {
|
|
@@ -120,11 +119,4 @@ class Query {
|
|
|
120
119
|
|
|
121
120
|
const _name = cds.env.sql.names === 'quoted' ? n =>`"${n}"` : n => n.replace(/[.:]/g,'_')
|
|
122
121
|
|
|
123
|
-
if (cds.env.ql.quirks_mode) Object.defineProperty (Query.prototype, '_target4', {
|
|
124
|
-
value: function (...args) {
|
|
125
|
-
const { ref, as } = this._target_ref4 (...args)
|
|
126
|
-
return ref.length === 1 && typeof ref[0] === 'string' && !as ? ref[0] : as ? {ref, as} : {ref}
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
|
|
130
122
|
module.exports = Query
|
package/lib/ql/SELECT.js
CHANGED
|
@@ -48,7 +48,7 @@ module.exports = class Query extends Whereable {
|
|
|
48
48
|
if (c) return this._add('columns',c)
|
|
49
49
|
if (typeof cols === 'string') {
|
|
50
50
|
try { parse.path(cols) }
|
|
51
|
-
catch
|
|
51
|
+
catch { //> it can't be a from
|
|
52
52
|
try { return this.columns(...arguments) }
|
|
53
53
|
catch(e) {
|
|
54
54
|
if (!e.message.startsWith('CDS compilation failed')) throw e
|
package/lib/ql/Whereable.js
CHANGED
|
@@ -44,9 +44,9 @@ class Query extends require('./Query') {
|
|
|
44
44
|
if (typeof key !== 'object' || key === null) key = { [Object.keys(this._target.keys||{ID:1})[0]]: key }
|
|
45
45
|
if (this.SELECT) this.SELECT.one = true
|
|
46
46
|
if (cds.env.features.keys_into_where) return this.where(key)
|
|
47
|
-
if (this.UPDATE) { this.UPDATE.entity = { ref: [{ id:
|
|
47
|
+
if (this.UPDATE) { this.UPDATE.entity = { ref: [{ id: this.UPDATE.entity.ref.at(-1), where: predicate4([key]) }] }; return this }
|
|
48
48
|
if (this.SELECT) { this.SELECT.from.ref[this.SELECT.from.ref.length-1] = { id: this.SELECT.from.ref.at(-1), where: predicate4([key]) }; return this }
|
|
49
|
-
if (this.DELETE) { this.DELETE.from = { ref: [{ id:
|
|
49
|
+
if (this.DELETE) { this.DELETE.from = { ref: [{ id: this.DELETE.from.ref.at(-1), where: predicate4([key]) }] }; return this }
|
|
50
50
|
return this.where(key)
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -97,6 +97,7 @@ const _object_predicate = ([arg], _clause) => { // e.g. .where ({ID:4711, stock:
|
|
|
97
97
|
else if (is_cqn(x)) pred.push('=', x)
|
|
98
98
|
else if (x instanceof Buffer) pred.push('=', {val:x})
|
|
99
99
|
else if (x instanceof RegExp) pred.push('like', {val:x})
|
|
100
|
+
else if (x instanceof Date) pred.push('=', {val:x})
|
|
100
101
|
else if (typeof x === 'object') for (let op in x) pred.push(op, val(x[op]))
|
|
101
102
|
else if (_clause === 'on' && typeof x === 'string') pred.push('=', { ref: x.split('.') })
|
|
102
103
|
else pred.push('=', {val:x})
|
package/lib/req/cds-context.js
CHANGED
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
const { AsyncLocalStorage } = require ('async_hooks')
|
|
2
2
|
const { EventEmitter } = require('events')
|
|
3
|
-
const EventContext = require('./context')
|
|
3
|
+
const EventContext = require('./context'), ec4 = v => {
|
|
4
|
+
if (v instanceof EventContext || typeof v !== 'object') return v
|
|
5
|
+
if (v.context) return v.context
|
|
6
|
+
else return EventContext.for(v)
|
|
7
|
+
}
|
|
4
8
|
|
|
5
9
|
module.exports = new class extends AsyncLocalStorage {
|
|
6
10
|
|
|
7
|
-
run(v,fn,...args) { return super.run (
|
|
8
|
-
enterWith(v) { return super.enterWith (
|
|
9
|
-
|
|
10
|
-
_context4(v) {
|
|
11
|
-
if (v instanceof EventContext || typeof v !== 'object') return v
|
|
12
|
-
if (v.context) return v.context
|
|
13
|
-
return EventContext.for(v)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** @returns {EventContext} */
|
|
17
|
-
_for (cds,v) {
|
|
18
|
-
Reflect.defineProperty (cds,'context', { enumerable:1, ... {
|
|
19
|
-
set:(v) => this.enterWith(v),
|
|
20
|
-
get:()=> this.getStore(),
|
|
21
|
-
}})
|
|
22
|
-
return cds.context = v // IMPORTANT: we need to set it initially, to get it all wired up correctly
|
|
23
|
-
}
|
|
11
|
+
run(v,fn,...args) { return super.run (ec4(v),fn,...args) }
|
|
12
|
+
enterWith(v) { return super.enterWith (ec4(v)) }
|
|
24
13
|
|
|
25
14
|
spawn (o,fn, /** @type {import('../index')} cds */ cds=this) {
|
|
26
15
|
if (typeof o === 'function') [fn,o] = [o,fn] //> for compatibility
|
|
@@ -37,22 +26,22 @@ module.exports = new class extends AsyncLocalStorage {
|
|
|
37
26
|
tx.model = cds.context.model
|
|
38
27
|
}
|
|
39
28
|
return Promise.resolve(fn(tx))
|
|
40
|
-
.then (tx.commit, e =>
|
|
29
|
+
.then (tx.commit, e => {
|
|
30
|
+
cds.log().error(`ERROR occurred in background job:`, e)
|
|
31
|
+
return tx.rollback(e)
|
|
32
|
+
})
|
|
41
33
|
.then (res => Promise.all(em.listeners('succeeded').map(each => each(res))))
|
|
42
34
|
.catch (err => Promise.all(em.listeners('failed').map(each => each(err))))
|
|
43
35
|
.finally (() => Promise.all(em.listeners('done').map(each => each())))
|
|
44
36
|
})
|
|
45
37
|
}
|
|
46
38
|
const em = new EventEmitter
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
em.timer = setInterval(fx, every)
|
|
39
|
+
if (o?.every) {
|
|
40
|
+
em.timer = setInterval(fx, o.every)
|
|
50
41
|
cds.on('shutdown', () => clearInterval(em.timer))
|
|
51
42
|
} else {
|
|
52
|
-
em.timer = (after ? setTimeout(fx, after) : setImmediate(fx)).unref()
|
|
43
|
+
em.timer = (o?.after ? setTimeout(fx, o.after) : setImmediate(fx)).unref()
|
|
53
44
|
}
|
|
54
45
|
return em
|
|
55
46
|
}
|
|
56
47
|
}
|
|
57
|
-
|
|
58
|
-
const _error = (err, cds) => { cds.log().error(`ERROR occurred in background job:`, err); return err }
|
package/lib/req/context.js
CHANGED
|
@@ -3,10 +3,6 @@ const async_events = { succeeded:1, failed:1, done:1, commit:1 }
|
|
|
3
3
|
const req_locale = require('./locale')
|
|
4
4
|
const { EventEmitter } = require('events')
|
|
5
5
|
|
|
6
|
-
// getter functions extracted to show deprecation warning only once
|
|
7
|
-
const _getTenant = req => req.tenant
|
|
8
|
-
const _getLocale = req => req.locale
|
|
9
|
-
|
|
10
6
|
/**
|
|
11
7
|
* This is the base class for `cds.Events` and `cds.Requests`,
|
|
12
8
|
* providing the transaction context nature to all instances.
|
|
@@ -20,7 +16,6 @@ class EventContext {
|
|
|
20
16
|
const ctx = new this (_)
|
|
21
17
|
const base = cds.context
|
|
22
18
|
if (base) {
|
|
23
|
-
// we inherit from former cds.currents (except for the timestamp if new root ctx)
|
|
24
19
|
if (_as_root) ctx._set('_propagated', Object.create(base, { timestamp: { value: undefined } }))
|
|
25
20
|
else {
|
|
26
21
|
ctx._set('_propagated', base)
|
|
@@ -90,26 +85,14 @@ class EventContext {
|
|
|
90
85
|
if (t) super.tenant = t
|
|
91
86
|
}
|
|
92
87
|
get tenant() {
|
|
93
|
-
return super.tenant = this._propagated.tenant
|
|
88
|
+
return super.tenant = this._propagated.tenant || this._.req?.tenant
|
|
94
89
|
}
|
|
95
90
|
|
|
96
91
|
set user(u) {
|
|
97
|
-
|
|
98
|
-
let pd = Reflect.getOwnPropertyDescriptor(u,p)
|
|
99
|
-
if (pd?.value) this[p] = pd.value
|
|
100
|
-
}
|
|
101
|
-
let user = u instanceof cds.User ? Object.create(u,{
|
|
102
|
-
tenant: {get:()=> cds.utils.deprecated (_getTenant, {kind: 'Property', old: 'req.user.tenant', use: 'req.tenant'})(this)},
|
|
103
|
-
locale: {get:()=> cds.utils.deprecated (_getLocale, {kind: 'Property', old: 'req.user.locale', use: 'req.locale'})(this)},
|
|
104
|
-
}) : Object.defineProperties (new cds.User(u), {
|
|
105
|
-
tenant: {get:()=> cds.utils.deprecated (_getTenant, {kind: 'Property', old: 'req.user.tenant', use: 'req.tenant'})(this)},
|
|
106
|
-
locale: {get:()=> cds.utils.deprecated (_getLocale, {kind: 'Property', old: 'req.user.locale', use: 'req.locale'})(this)},
|
|
107
|
-
})
|
|
108
|
-
super.user = user
|
|
92
|
+
super.user = cds.User(u)
|
|
109
93
|
}
|
|
110
94
|
get user() {
|
|
111
|
-
|
|
112
|
-
return this.user // IMPORTANT: first set this.user then return it separately to ensure we return the compat-wrapped objects
|
|
95
|
+
return super.user = this._propagated.user || cds.User (this._.req?.user)
|
|
113
96
|
}
|
|
114
97
|
|
|
115
98
|
set locale(l) {
|
|
@@ -124,13 +107,13 @@ class EventContext {
|
|
|
124
107
|
}
|
|
125
108
|
|
|
126
109
|
get _features() {
|
|
127
|
-
return super._features = this._propagated._features || Features.for (this.
|
|
110
|
+
return super._features = this._propagated._features || Features.for (this._.req?.features || this.user.features)
|
|
128
111
|
}
|
|
129
112
|
get features() {
|
|
130
113
|
return super.features = this._features || Features.none
|
|
131
114
|
}
|
|
132
115
|
set features(v) {
|
|
133
|
-
super.features = Features.for(v)
|
|
116
|
+
super.features = Features.for(v) || Features.none
|
|
134
117
|
}
|
|
135
118
|
|
|
136
119
|
get model() {
|
|
@@ -184,11 +167,26 @@ class EventContext {
|
|
|
184
167
|
get _tx() { return this.tx } // REVISIT: for compatibility to bade usages of req._tx
|
|
185
168
|
}
|
|
186
169
|
|
|
187
|
-
const _anonymous = new cds.User.default
|
|
188
|
-
|
|
189
170
|
|
|
190
171
|
class Features {
|
|
191
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Returns an instance of this class for different supported input variants to specify features:
|
|
174
|
+
*
|
|
175
|
+
* - an `array` of feature names of features to be enabled
|
|
176
|
+
* - a `string` with a comma-separated list of feature names
|
|
177
|
+
* - the string `'*'` to generically enable _all_ features
|
|
178
|
+
* - an `object` with feature names as keys and boolean values true/false
|
|
179
|
+
* - `null` or `undefined` or an _empty_ `string` or `array` or `object` to indicate no features
|
|
180
|
+
*
|
|
181
|
+
* Note that the returned instance is effectively an object that has all enabled feature
|
|
182
|
+
* names as keys with the value true. In particualar, that means if the input is an object,
|
|
183
|
+
* with some flags set to false, the returned instance will not have these keys at all.
|
|
184
|
+
*
|
|
185
|
+
* Hence, users of cds.context.features can simply check for the presence of a feature
|
|
186
|
+
* with the like of `if ('foo' in cds.context.features)`...
|
|
187
|
+
* @returns {Features}
|
|
188
|
+
*/
|
|
189
|
+
static for (x) {
|
|
192
190
|
if (x == null) return
|
|
193
191
|
if (x === '*') return this.all
|
|
194
192
|
if (Array.isArray(x)) ; //> go on below
|
package/lib/req/request.js
CHANGED
|
@@ -90,33 +90,7 @@ class Request extends require('./event') {
|
|
|
90
90
|
|
|
91
91
|
const ref = subject.ref || [ subject ]
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// special handling for old draft logic
|
|
96
|
-
let subjectRef = []
|
|
97
|
-
ref.forEach ((item, i) => {
|
|
98
|
-
// to one assoc & comp
|
|
99
|
-
if (typeof item === 'string') {
|
|
100
|
-
subjectRef.push(item)
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// create key value pair without IsActiveEntity & adjust root target
|
|
105
|
-
const keys = {}
|
|
106
|
-
let id = item.id
|
|
107
|
-
if (!id) return
|
|
108
|
-
for (let j = 0; j < item?.where?.length; j = j + 4) {
|
|
109
|
-
const key = item.where[j].ref[0]
|
|
110
|
-
const value = item.where[j + 2].val
|
|
111
|
-
if (key !== 'IsActiveEntity') keys[key] = value
|
|
112
|
-
else if (i === 0 && key === 'IsActiveEntity' && value === false) id = item.id + '_drafts'
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// create copy
|
|
116
|
-
const {SELECT:{from:{ref}}} = SELECT.from(id,keys)
|
|
117
|
-
subjectRef.push(...ref)
|
|
118
|
-
})
|
|
119
|
-
return super.subject = {ref: subjectRef}
|
|
93
|
+
return super.subject = { ref }
|
|
120
94
|
}
|
|
121
95
|
|
|
122
96
|
reply (results) { return this.results = results }
|
|
@@ -135,7 +109,6 @@ class Request extends require('./event') {
|
|
|
135
109
|
).join('')
|
|
136
110
|
throw me
|
|
137
111
|
}
|
|
138
|
-
if (args[0] === 401 && this._.req?._login) this._.req._login()
|
|
139
112
|
let e = this.error(...args)
|
|
140
113
|
if (!e.stack) Error.captureStackTrace (e = Object.assign(new Error,e), this.reject)
|
|
141
114
|
throw e
|
|
@@ -144,12 +117,6 @@ class Request extends require('./event') {
|
|
|
144
117
|
// Lazily create message collectors for .errors and .messages
|
|
145
118
|
/** @private */ get _messages() { return this.messages = this._set ('_messages', new Responses) }
|
|
146
119
|
/** @private */ get _errors() { return this.errors = this._set ('_errors', new Errors) }
|
|
147
|
-
|
|
148
|
-
// REVISIT: Used for request logging in cds.server
|
|
149
|
-
// REVISIT: _.odataReq stuff should go into subclass ODataRequest
|
|
150
|
-
get _path() { return this._set ('_path', this._.odataReq ? this._.odataReq._url.pathname : this._.req && this._.req.path) }
|
|
151
|
-
get _query() { return this._set ('_query', this._.odataReq ? this._.odataReq._queryOptions : this._.req && this._.req.query) }
|
|
152
|
-
|
|
153
120
|
}
|
|
154
121
|
|
|
155
122
|
module.exports = Request
|