@sap/cds 8.7.2 → 8.8.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 +50 -0
- package/_i18n/i18n.properties +3 -0
- package/_i18n/i18n_cs.properties +6 -6
- package/_i18n/i18n_de.properties +3 -0
- package/_i18n/i18n_en.properties +3 -0
- package/_i18n/i18n_es.properties +3 -0
- package/_i18n/i18n_fr.properties +3 -0
- package/_i18n/i18n_it.properties +3 -0
- package/_i18n/i18n_ja.properties +3 -0
- package/_i18n/i18n_pl.properties +7 -4
- package/_i18n/i18n_pt.properties +3 -0
- package/_i18n/i18n_ru.properties +3 -0
- package/app/index.js +2 -30
- package/lib/compile/parse.js +1 -1
- package/lib/env/cds-env.js +1 -1
- package/lib/env/cds-requires.js +16 -9
- package/lib/env/schemas/cds-package.js +1 -1
- package/lib/env/schemas/cds-rc.js +17 -4
- package/lib/index.js +1 -1
- package/lib/ql/SELECT.js +6 -1
- package/lib/ql/cds.ql-predicates.js +2 -1
- package/lib/req/request.js +5 -2
- package/lib/req/validate.js +4 -2
- package/lib/srv/bindings.js +31 -20
- package/lib/srv/cds-connect.js +1 -1
- package/lib/srv/middlewares/auth/mocked-users.js +1 -0
- package/lib/srv/protocols/okra.js +5 -7
- package/lib/srv/srv-dispatch.js +0 -5
- package/lib/test/cds-test.js +34 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -0
- package/libx/_runtime/common/generic/auth/restrict.js +1 -1
- package/libx/_runtime/common/generic/auth/service.js +2 -2
- package/libx/_runtime/common/generic/auth/utils.js +2 -1
- package/libx/_runtime/common/generic/input.js +1 -1
- package/libx/_runtime/common/utils/binary.js +1 -35
- package/libx/_runtime/common/utils/rewriteAsterisks.js +5 -8
- package/libx/_runtime/fiori/lean-draft.js +2 -4
- package/libx/common/utils/path.js +1 -5
- package/libx/common/utils/streaming.js +76 -0
- package/libx/odata/middleware/create.js +5 -1
- package/libx/odata/middleware/delete.js +1 -1
- package/libx/odata/middleware/operation.js +48 -4
- package/libx/odata/middleware/read.js +1 -1
- package/libx/odata/middleware/stream.js +29 -101
- package/libx/odata/middleware/update.js +1 -1
- package/libx/odata/parse/afterburner.js +21 -1
- package/libx/odata/parse/grammar.peggy +108 -26
- package/libx/odata/parse/multipartToJson.js +17 -10
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/metadata.js +28 -5
- package/libx/odata/utils/normalizeTimeData.js +11 -8
- package/libx/rest/RestAdapter.js +2 -16
- package/libx/rest/middleware/operation.js +38 -18
- package/libx/rest/middleware/parse.js +5 -25
- package/libx/rest/post-processing.js +33 -0
- package/libx/rest/pre-processing.js +38 -0
- package/package.json +1 -1
- package/libx/common/utils/index.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,56 @@
|
|
|
4
4
|
- The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
5
5
|
- This project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## Version 8.8.1 - 2025-03-07
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Requests violating `cds.odata.max_batch_header_size` are terminated with `431 Request Header Fields Too Large` instead of `400 - Bad Request`
|
|
12
|
+
- `cds.parse.<x>` writing directly to `stdout`
|
|
13
|
+
- Instance-based authorization for programmatic action invocations
|
|
14
|
+
- Implicit function parameter calls with Array or Object values
|
|
15
|
+
- OData: Throw an error by `POST` with payload that contains array of entity representation
|
|
16
|
+
- `cds.validate` filters out annotations according to OData V4 spec
|
|
17
|
+
- Crash for requests with invalid time data format
|
|
18
|
+
- Add missing 'and' between conditions in object notation of QL
|
|
19
|
+
- Multiline payloads in `$batch` sub requests
|
|
20
|
+
- Instance-based authorization for modeling like `$user.<property> is null`
|
|
21
|
+
- Respect `cds.odata.contextAbsoluteUrl` in new OData adapter
|
|
22
|
+
- `cds.odata.context_with_columns` also applies to singletons
|
|
23
|
+
|
|
24
|
+
## Version 8.8.0 - 2025-03-03
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- `cds.ql` method `SELECT.hints()` which passes hints to the database query optimizer that can influence the execution plan
|
|
29
|
+
- Schema updates for MTX configuration
|
|
30
|
+
- Deprecate `cds.requires.db.database` in JSON schema
|
|
31
|
+
- Service level restrictions for application service calls can be enforced with `cds.features.service_level_restrictions=true`
|
|
32
|
+
+ With `@sap/cds^9`, this becomes the new default.
|
|
33
|
+
- Support implicit function parameters calls with @prefix
|
|
34
|
+
- `cds.test` now uses package `@cap-js/cds-test` if installed, otherwise prints a hint to install it. With cds 9, this package will be required.
|
|
35
|
+
- Operation response streaming
|
|
36
|
+
+ OData: Operations returning `cds.LargeBinary` annotated with `@Core.MediaType` may send stream responses.
|
|
37
|
+
+ REST: Operations may send stream responses.
|
|
38
|
+
+ Annotations `@Core.MediaType`, `@Core.ContentDisposition.Filename` and `@Core.ContentDisposition.Type` on operation return types will be considered.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- The default index page now shows links to CDS functions with their parameter names but no default values anymore.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Order by virtual fields in draft-related requests
|
|
47
|
+
- Erroneous cleansing when draft activation is invoked programmatically
|
|
48
|
+
- Skip validation for mandatory fields in update scenarios for entities in draft activation
|
|
49
|
+
- Simplified default configuration: `cds.requires.messaging = true`
|
|
50
|
+
- `cds.connect` called with options erroneously filled in `cds.services`
|
|
51
|
+
- Mocked users won't have a tenant in single-tenant mode
|
|
52
|
+
- Allow usage of latest versions of `chai` and `chai-as-promised` on Node >= 23 with the built-in test runner and `mocha`. The `jest` runner is not able though to load these ESM modules.
|
|
53
|
+
- Reject navigations in expand
|
|
54
|
+
- Activation of drafts for entities using `@cds.api.ignore`
|
|
55
|
+
- Prevent uncaught type error during validation of composition entries
|
|
56
|
+
|
|
7
57
|
## Version 8.7.2 - 2025-02-14
|
|
8
58
|
|
|
9
59
|
### Fixed
|
package/_i18n/i18n.properties
CHANGED
package/_i18n/i18n_cs.properties
CHANGED
|
@@ -26,16 +26,16 @@
|
|
|
26
26
|
#----------------------------------------------------------------------------------------------------------------------
|
|
27
27
|
|
|
28
28
|
#XTIT: Created By (Answer to: "Which user has created a certain entity?")
|
|
29
|
-
CreatedBy=Vytvo\u0159il
|
|
29
|
+
CreatedBy=Vytvo\u0159il
|
|
30
30
|
|
|
31
31
|
#XTIT: Created On (Answer to: "When has a certain entity been created?")
|
|
32
|
-
CreatedAt=Vytvo\u0159eno
|
|
32
|
+
CreatedAt=Vytvo\u0159eno
|
|
33
33
|
|
|
34
34
|
#XTIT: Changed By (Answer to: "Which user has changed a certain entity?")
|
|
35
|
-
ChangedBy=
|
|
35
|
+
ChangedBy=Zm\u011Bnil
|
|
36
36
|
|
|
37
37
|
#XTIT: Changed On (Answer to: "When has a certain entity been changed?")
|
|
38
|
-
ChangedAt=Zm\u011Bn\u011Bno
|
|
38
|
+
ChangedAt=Zm\u011Bn\u011Bno
|
|
39
39
|
|
|
40
40
|
#XTIT: Currency
|
|
41
41
|
Currency=M\u011Bna
|
|
@@ -92,7 +92,7 @@ Draft_DraftAdministrativeData=Spr\u00E1vn\u00ED data n\u00E1vrhu
|
|
|
92
92
|
Draft_DraftUUID=N\u00E1vrh (technick\u00E9 ID)
|
|
93
93
|
|
|
94
94
|
#XTIT: Creation time of a draft
|
|
95
|
-
Draft_CreationDateTime=N\u00E1vrh vytvo\u0159en
|
|
95
|
+
Draft_CreationDateTime=N\u00E1vrh vytvo\u0159en
|
|
96
96
|
|
|
97
97
|
#XTIT: User created the draft
|
|
98
98
|
Draft_CreatedByUser=N\u00E1vrh vytvo\u0159il
|
|
@@ -101,7 +101,7 @@ Draft_CreatedByUser=N\u00E1vrh vytvo\u0159il
|
|
|
101
101
|
Draft_DraftIsCreatedByMe=N\u00E1vrh vytvo\u0159en mnou
|
|
102
102
|
|
|
103
103
|
#XTIT: Time a draft was last changed on
|
|
104
|
-
Draft_LastChangeDateTime=N\u00E1vrh naposledy zm\u011Bn\u011Bn
|
|
104
|
+
Draft_LastChangeDateTime=N\u00E1vrh naposledy zm\u011Bn\u011Bn
|
|
105
105
|
|
|
106
106
|
#XTIT: User that changed the draft last
|
|
107
107
|
Draft_LastChangedByUser=N\u00E1vrh naposledy zm\u011Bnil
|
package/_i18n/i18n_de.properties
CHANGED
package/_i18n/i18n_en.properties
CHANGED
package/_i18n/i18n_es.properties
CHANGED
|
@@ -70,6 +70,9 @@ LanguageCode=C\u00F3digo de idioma
|
|
|
70
70
|
#XTIT: Language Code Description
|
|
71
71
|
LanguageCode.Description=C\u00F3digo de idioma seg\u00FAn especificado por ISO 639-1
|
|
72
72
|
|
|
73
|
+
#XTIT Time zone code
|
|
74
|
+
TimeZoneCode=C\u00F3digo de zona horaria
|
|
75
|
+
|
|
73
76
|
#XTIT: User Identifier
|
|
74
77
|
UserID=ID de usuario
|
|
75
78
|
|
package/_i18n/i18n_fr.properties
CHANGED
package/_i18n/i18n_it.properties
CHANGED
package/_i18n/i18n_ja.properties
CHANGED
|
@@ -70,6 +70,9 @@ LanguageCode=\u8A00\u8A9E\u30B3\u30FC\u30C9
|
|
|
70
70
|
#XTIT: Language Code Description
|
|
71
71
|
LanguageCode.Description=ISO 639-1 \u3067\u6307\u5B9A\u3055\u308C\u3066\u3044\u308B\u8A00\u8A9E\u30B3\u30FC\u30C9
|
|
72
72
|
|
|
73
|
+
#XTIT Time zone code
|
|
74
|
+
TimeZoneCode=\u30BF\u30A4\u30E0\u30BE\u30FC\u30F3\u30B3\u30FC\u30C9
|
|
75
|
+
|
|
73
76
|
#XTIT: User Identifier
|
|
74
77
|
UserID=\u30E6\u30FC\u30B6 ID
|
|
75
78
|
|
package/_i18n/i18n_pl.properties
CHANGED
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
CreatedBy=Utworzone przez
|
|
30
30
|
|
|
31
31
|
#XTIT: Created On (Answer to: "When has a certain entity been created?")
|
|
32
|
-
CreatedAt=
|
|
32
|
+
CreatedAt=Data utworzenia
|
|
33
33
|
|
|
34
34
|
#XTIT: Changed By (Answer to: "Which user has changed a certain entity?")
|
|
35
35
|
ChangedBy=Zmienione przez
|
|
36
36
|
|
|
37
37
|
#XTIT: Changed On (Answer to: "When has a certain entity been changed?")
|
|
38
|
-
ChangedAt=
|
|
38
|
+
ChangedAt=Data zmiany
|
|
39
39
|
|
|
40
40
|
#XTIT: Currency
|
|
41
41
|
Currency=Waluta
|
|
@@ -70,6 +70,9 @@ LanguageCode=Kod j\u0119zyka
|
|
|
70
70
|
#XTIT: Language Code Description
|
|
71
71
|
LanguageCode.Description=Kod j\u0119zyka okre\u015Blony zgodnie z norm\u0105 ISO 639-1
|
|
72
72
|
|
|
73
|
+
#XTIT Time zone code
|
|
74
|
+
TimeZoneCode=Kod strefy czasowej
|
|
75
|
+
|
|
73
76
|
#XTIT: User Identifier
|
|
74
77
|
UserID=Identyfikator u\u017Cytkownika
|
|
75
78
|
|
|
@@ -89,7 +92,7 @@ Draft_DraftAdministrativeData=Dane administracyjne wersji roboczej
|
|
|
89
92
|
Draft_DraftUUID=Wersja robocza (identyfikator techniczny)
|
|
90
93
|
|
|
91
94
|
#XTIT: Creation time of a draft
|
|
92
|
-
Draft_CreationDateTime=
|
|
95
|
+
Draft_CreationDateTime=Data utworzenia wersji roboczej
|
|
93
96
|
|
|
94
97
|
#XTIT: User created the draft
|
|
95
98
|
Draft_CreatedByUser=Wersja robocza utworzona przez
|
|
@@ -98,7 +101,7 @@ Draft_CreatedByUser=Wersja robocza utworzona przez
|
|
|
98
101
|
Draft_DraftIsCreatedByMe=Wersja robocza utworzona przeze mnie
|
|
99
102
|
|
|
100
103
|
#XTIT: Time a draft was last changed on
|
|
101
|
-
Draft_LastChangeDateTime=
|
|
104
|
+
Draft_LastChangeDateTime=Data ostatniej zmiany wersji roboczej
|
|
102
105
|
|
|
103
106
|
#XTIT: User that changed the draft last
|
|
104
107
|
Draft_LastChangedByUser=Wersja robocza ostatnio zmieniona przez
|
package/_i18n/i18n_pt.properties
CHANGED
|
@@ -70,6 +70,9 @@ LanguageCode=C\u00F3digo de idioma
|
|
|
70
70
|
#XTIT: Language Code Description
|
|
71
71
|
LanguageCode.Description=C\u00F3digo de idioma como especificado pelo ISO 639-1
|
|
72
72
|
|
|
73
|
+
#XTIT Time zone code
|
|
74
|
+
TimeZoneCode=C\u00F3digo de fuso hor\u00E1rio
|
|
75
|
+
|
|
73
76
|
#XTIT: User Identifier
|
|
74
77
|
UserID=ID do usu\u00E1rio
|
|
75
78
|
|
package/_i18n/i18n_ru.properties
CHANGED
|
@@ -70,6 +70,9 @@ LanguageCode=\u041A\u043E\u0434 \u044F\u0437\u044B\u043A\u0430
|
|
|
70
70
|
#XTIT: Language Code Description
|
|
71
71
|
LanguageCode.Description=\u041A\u043E\u0434 \u044F\u0437\u044B\u043A\u0430 \u0432 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 ISO 639-1
|
|
72
72
|
|
|
73
|
+
#XTIT Time zone code
|
|
74
|
+
TimeZoneCode=\u041A\u043E\u0434 \u0447\u0430\u0441\u043E\u0432\u043E\u0433\u043E \u043F\u043E\u044F\u0441\u0430
|
|
75
|
+
|
|
73
76
|
#XTIT: User Identifier
|
|
74
77
|
UserID=\u0418\u0434. \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F
|
|
75
78
|
|
package/app/index.js
CHANGED
|
@@ -36,7 +36,7 @@ module.exports = { get html(){
|
|
|
36
36
|
<ul>${_operations_in(srv).map (e => `
|
|
37
37
|
<li id="${asHtmlId(srv.name)}-${endpoint.kind}-${asHtmlId(e.name)}" class="operation">
|
|
38
38
|
<div>
|
|
39
|
-
<a href="${endpoint.path}/${e.name}
|
|
39
|
+
<a href="${endpoint.path}/${e.name}" title="${endpoint.path}/${e.name}"><span>${e.name} ${e.params}</span></a>
|
|
40
40
|
</div>
|
|
41
41
|
</li>`).join('')}
|
|
42
42
|
</ul>
|
|
@@ -74,41 +74,13 @@ function _operations_in (service) {
|
|
|
74
74
|
for (let name in operations) {
|
|
75
75
|
const op = cds.model.definitions[service.name + '.' + name]
|
|
76
76
|
if (op?.kind === 'function') {
|
|
77
|
-
const params = '('+ Object.
|
|
78
|
-
let val = _sampleValue(p)
|
|
79
|
-
if (typeof val === 'string') val = encodeURIComponent(`'${val}'`)
|
|
80
|
-
else if (typeof val === 'object') val = encodeURIComponent(`'${JSON.stringify(val)}'`)
|
|
81
|
-
return `${p.name}=${val}`
|
|
82
|
-
}).join(',') + ')'
|
|
77
|
+
const params = '('+ Object.keys(op.params||[]) + ')'
|
|
83
78
|
exposed.push ({ name, params })
|
|
84
79
|
}
|
|
85
80
|
}
|
|
86
81
|
return exposed
|
|
87
82
|
}
|
|
88
83
|
|
|
89
|
-
function _sampleValue (param) {
|
|
90
|
-
if (param.items) // many
|
|
91
|
-
return [ _sampleValue(param.items) ]
|
|
92
|
-
if (param.elements) { // structured
|
|
93
|
-
return Object.entries(param.elements).reduce((all,[n,p]) => {
|
|
94
|
-
all[n] = _sampleValue(p)
|
|
95
|
-
return all
|
|
96
|
-
},{})
|
|
97
|
-
}
|
|
98
|
-
// scalar
|
|
99
|
-
const type = param._type || param.type
|
|
100
|
-
if (type === 'cds.String') return 'hello'
|
|
101
|
-
if (type === 'cds.Boolean') return true
|
|
102
|
-
if (type === 'cds.Decimal'||type === 'cds.Double') return '4.2'
|
|
103
|
-
if (type === 'cds.Date') return '2021-12-31'
|
|
104
|
-
if (type === 'cds.Time') return '23:42:42'
|
|
105
|
-
if (type === 'cds.DateTime') return '2021-12-31T23:42:42Z'
|
|
106
|
-
if (type === 'cds.Timestamp') return '2021-12-31T23:42:42.123Z'
|
|
107
|
-
if (type === 'cds.UUID') return cds.utils.uuid()
|
|
108
|
-
if (type?.match(/cds\..*Int.*/i)) return 42
|
|
109
|
-
return type // fallback
|
|
110
|
-
}
|
|
111
|
-
|
|
112
84
|
function _moreLinks (srv, endpoint, entity, div=true) {
|
|
113
85
|
return (srv.$linkProviders || [])
|
|
114
86
|
.map (linkProv => linkProv(entity, endpoint))
|
package/lib/compile/parse.js
CHANGED
|
@@ -29,7 +29,7 @@ exports.path = function path (x,...etc) {
|
|
|
29
29
|
const [,head,tail] = /^([\w._]+)(?::(\w+))?$/.exec(x)||[]
|
|
30
30
|
if (tail) return {ref:[head,...tail.split('.')]}
|
|
31
31
|
if (head) return {ref:[head]}
|
|
32
|
-
const {SELECT} = cdsc.parse.cql('SELECT from '+x)
|
|
32
|
+
const {SELECT} = cdsc.parse.cql('SELECT from '+x, undefined, { messages: [] })
|
|
33
33
|
return SELECT.from
|
|
34
34
|
}
|
|
35
35
|
|
package/lib/env/cds-env.js
CHANGED
|
@@ -255,7 +255,7 @@ class Config {
|
|
|
255
255
|
if (!val || val._is_linked) return val
|
|
256
256
|
if (val === true) {
|
|
257
257
|
let x = kinds[key]
|
|
258
|
-
if (x) val = x; else return val
|
|
258
|
+
if (x) val = x; else if (key+'-defaults' in kinds) val = {kind:key+'-defaults'}; else return val
|
|
259
259
|
}
|
|
260
260
|
if (typeof val === 'string') {
|
|
261
261
|
let x = kinds[val] || kinds[val+'-'+key] || kinds[key+'-'+val]
|
package/lib/env/cds-requires.js
CHANGED
|
@@ -130,6 +130,7 @@ const _resolve = (preferred, fallback) => {
|
|
|
130
130
|
|
|
131
131
|
const _databases = {
|
|
132
132
|
|
|
133
|
+
"db-defaults": { kind: 'sql' },
|
|
133
134
|
"sql": {
|
|
134
135
|
'[development]': { kind: 'sqlite', credentials: { url: ':memory:' } },
|
|
135
136
|
'[production]': { kind: 'hana' },
|
|
@@ -175,7 +176,6 @@ const _databases = {
|
|
|
175
176
|
"label": "service-manager"
|
|
176
177
|
}
|
|
177
178
|
},
|
|
178
|
-
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
|
|
@@ -197,15 +197,12 @@ const _outbox = {
|
|
|
197
197
|
|
|
198
198
|
const _messaging = {
|
|
199
199
|
|
|
200
|
-
"
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
"file-based-messaging": {
|
|
205
|
-
impl: `${_runtime}/messaging/file-based.js`,
|
|
206
|
-
file:'~/.cds-msg-box',
|
|
207
|
-
outbox: true
|
|
200
|
+
"messaging-defaults": {
|
|
201
|
+
"[development]": { kind: "file-based-messaging" },
|
|
202
|
+
"[production]": { kind: "enterprise-messaging" },
|
|
203
|
+
"[hybrid]": { kind: "enterprise-messaging-amqp" },
|
|
208
204
|
},
|
|
205
|
+
|
|
209
206
|
"default-messaging": {
|
|
210
207
|
"[development]": { kind: "local-messaging" },
|
|
211
208
|
"[hybrid]": { kind: "enterprise-messaging-amqp" },
|
|
@@ -214,6 +211,16 @@ const _messaging = {
|
|
|
214
211
|
"[multitenant]": { kind: "enterprise-messaging-http" }
|
|
215
212
|
}
|
|
216
213
|
},
|
|
214
|
+
|
|
215
|
+
"local-messaging": {
|
|
216
|
+
impl: `${_runtime}/messaging/service.js`,
|
|
217
|
+
local: true
|
|
218
|
+
},
|
|
219
|
+
"file-based-messaging": {
|
|
220
|
+
impl: `${_runtime}/messaging/file-based.js`,
|
|
221
|
+
file:'~/.cds-msg-box',
|
|
222
|
+
outbox: true
|
|
223
|
+
},
|
|
217
224
|
"enterprise-messaging": {
|
|
218
225
|
kind: "enterprise-messaging-http",
|
|
219
226
|
},
|
|
@@ -19,6 +19,7 @@ module.exports = {
|
|
|
19
19
|
{
|
|
20
20
|
properties: {
|
|
21
21
|
cds: {
|
|
22
|
+
description: 'CDS configuration root',
|
|
22
23
|
$ref: '#/$defs/cdsRoot'
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -59,7 +60,8 @@ module.exports = {
|
|
|
59
60
|
enum: [
|
|
60
61
|
'mtx-sidecar',
|
|
61
62
|
'with-mtx-sidecar',
|
|
62
|
-
'java'
|
|
63
|
+
'java',
|
|
64
|
+
'subscription-manager'
|
|
63
65
|
]
|
|
64
66
|
},
|
|
65
67
|
{
|
|
@@ -242,7 +244,9 @@ module.exports = {
|
|
|
242
244
|
properties: {
|
|
243
245
|
database: {
|
|
244
246
|
type: 'string',
|
|
245
|
-
format: 'uri-reference'
|
|
247
|
+
format: 'uri-reference',
|
|
248
|
+
deprecated: true,
|
|
249
|
+
description: 'Deprecated: Use \'url\' instead.'
|
|
246
250
|
}
|
|
247
251
|
}
|
|
248
252
|
},
|
|
@@ -329,6 +333,10 @@ module.exports = {
|
|
|
329
333
|
},
|
|
330
334
|
messaging: {
|
|
331
335
|
oneOf: [
|
|
336
|
+
{
|
|
337
|
+
type: 'boolean',
|
|
338
|
+
description: 'Shortcut to enable messaging.'
|
|
339
|
+
},
|
|
332
340
|
{
|
|
333
341
|
type: 'string',
|
|
334
342
|
description: 'Settings for the primary messaging service (shortcut).',
|
|
@@ -425,7 +433,7 @@ module.exports = {
|
|
|
425
433
|
type: 'boolean'
|
|
426
434
|
},
|
|
427
435
|
{
|
|
428
|
-
$ref: '#/$defs/
|
|
436
|
+
$ref: '#/$defs/extensibilitySettings'
|
|
429
437
|
}
|
|
430
438
|
]
|
|
431
439
|
},
|
|
@@ -802,11 +810,16 @@ module.exports = {
|
|
|
802
810
|
]
|
|
803
811
|
},
|
|
804
812
|
|
|
805
|
-
|
|
813
|
+
extensibilitySettings: {
|
|
806
814
|
type: 'object',
|
|
807
815
|
description: 'Extensibility settings',
|
|
808
816
|
additionalProperties: false,
|
|
809
817
|
properties: {
|
|
818
|
+
'check-existing-extensions': {
|
|
819
|
+
type: 'boolean',
|
|
820
|
+
description: `Specifies whether the extension linter includes existing extensions. Default will be 'true' with the next major release.`,
|
|
821
|
+
default: false
|
|
822
|
+
},
|
|
810
823
|
'element-prefix': {
|
|
811
824
|
type: 'array',
|
|
812
825
|
description: 'Field names must start with one of these strings.',
|
package/lib/index.js
CHANGED
|
@@ -95,7 +95,7 @@ const cds = module.exports = global.cds = new class cds extends EventEmitter {
|
|
|
95
95
|
get utils() { return super.utils = require('./utils/cds-utils') }
|
|
96
96
|
get error() { return super.error = require('./log/cds-error') }
|
|
97
97
|
get exec() { return super.exec = require('../bin/serve').exec }
|
|
98
|
-
get test() { return super.test = require('./test/cds-test
|
|
98
|
+
get test() { return super.test = require('./test/cds-test') }
|
|
99
99
|
get log() { return super.log = require('./log/cds-log') }
|
|
100
100
|
get debug() { return super.debug = this.log.debug }
|
|
101
101
|
clone(x) { return structuredClone(x) }
|
package/lib/ql/SELECT.js
CHANGED
|
@@ -165,10 +165,15 @@ class SELECT extends Whereable {
|
|
|
165
165
|
return this.then(rows => rows.map(callback))
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
valueOf() {
|
|
168
|
+
valueOf () {
|
|
169
169
|
return super.valueOf('SELECT * FROM')
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
hints (...args) {
|
|
173
|
+
if (args.length) this.SELECT.hints = args.flat()
|
|
174
|
+
return this
|
|
175
|
+
}
|
|
176
|
+
|
|
172
177
|
/** @private */ get _target_ref(){ return this.SELECT.from }
|
|
173
178
|
|
|
174
179
|
get elements() { return this.elements = cds.infer.elements4 (this.SELECT.columns, this.source) }
|
|
@@ -48,6 +48,8 @@ function _qbe (o, xpr=[]) {
|
|
|
48
48
|
let count = 0
|
|
49
49
|
for (let k in o) { const x = o[k]
|
|
50
50
|
|
|
51
|
+
if (k !== 'and' && k !== 'or' && count++) xpr.push('and') //> add 'and' between conditions
|
|
52
|
+
|
|
51
53
|
if (k.startsWith('not ')) { xpr.push('not'); k = k.slice(4) }
|
|
52
54
|
switch (k) { // handle special cases like {and:{...}} or {or:{...}}
|
|
53
55
|
case 'between':
|
|
@@ -83,7 +85,6 @@ function _qbe (o, xpr=[]) {
|
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
const a = cds.parse.ref(k) //> turn key into a ref for the left side of the expression
|
|
86
|
-
if (count++) xpr.push('and') //> add 'and' between conditions
|
|
87
88
|
if (!x || typeof x !== 'object') xpr.push (a,'=',{val:x})
|
|
88
89
|
else if (is_array(x)) xpr.push (a,'in',{list:x.map(_val)})
|
|
89
90
|
else if (x.SELECT || x.list) xpr.push (a,'in',x)
|
package/lib/req/request.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
const cds = require('../index'), {production} = cds.env
|
|
1
2
|
const { Responses, Errors } = require('./response')
|
|
2
|
-
const cds = require('../../lib'), {production} = cds.env
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Class Request represents requests received via synchronous protocols.
|
|
@@ -93,7 +93,10 @@ class Request extends require('./event') {
|
|
|
93
93
|
return super.subject = { ref }
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
reply
|
|
96
|
+
reply (results, ...etc) {
|
|
97
|
+
if (etc.length) Object.assign (results, ...etc)
|
|
98
|
+
return this.results = results
|
|
99
|
+
}
|
|
97
100
|
notify (...args) { return this._messages.add (1, ...args) }
|
|
98
101
|
info (...args) { return this._messages.add (2, ...args) }
|
|
99
102
|
warn (...args) { return this._messages.add (3, ...args) }
|
package/lib/req/validate.js
CHANGED
|
@@ -48,7 +48,7 @@ class Validation {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
unknown(e,d,input) {
|
|
51
|
-
if (e.
|
|
51
|
+
if (e.match(/@.*\./)) return delete input[e] //> skip all annotations, like @odata.Type (according to OData spec annotations contain an "@" and a ".")
|
|
52
52
|
d['@open'] || cds.error (`Property "${e}" does not exist in ${d.name}`, {status:400})
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -193,7 +193,9 @@ const $any = class any {
|
|
|
193
193
|
class struct extends $any {
|
|
194
194
|
validate (data, path, /** @type {Validation} */ ctx, elements = this.elements, skip={}) {
|
|
195
195
|
const path_ = !path ? [] : [...path, this.name]; if (path?.row) path_.push({...path})
|
|
196
|
-
|
|
196
|
+
if (data == null) return
|
|
197
|
+
// REVISIT cds^9: rm typeof data !== 'function', misuse in cds-mtxs
|
|
198
|
+
if (typeof data !== 'object' && typeof data !== 'function') return ctx.error ('ASSERT_DATA_TYPE', path_, this.name, data, this.target)
|
|
197
199
|
// check for required elements in case of inserts -- note: null values are handled in the payload loop below
|
|
198
200
|
if (ctx.insert || data && path_.length && this._is_insert(data)) for (let each of this._required (elements)) {
|
|
199
201
|
if (each.name in data) continue // got value for required element
|
package/lib/srv/bindings.js
CHANGED
|
@@ -3,6 +3,7 @@ const cds = require ('..'), DEBUG = cds.debug('serve|bindings',{label:'cds'})
|
|
|
3
3
|
const { readFile, readFileSync, writeFile, writeFileSync } = require ('fs')
|
|
4
4
|
const [ read, write ] = [ readFile, writeFile ].map(require('util').promisify)
|
|
5
5
|
const registry = '~/.cds-services.json'
|
|
6
|
+
const filename = registry.replace(/^~/, require('os').homedir())
|
|
6
7
|
|
|
7
8
|
/** TODO: Add documentation */
|
|
8
9
|
module.exports = class Bindings {
|
|
@@ -17,27 +18,29 @@ module.exports = class Bindings {
|
|
|
17
18
|
return bindings.import() .then (r,e)
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
constructor(
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
23
|
-
this.url = url
|
|
21
|
+
constructor() {
|
|
22
|
+
this.provides = {}
|
|
23
|
+
this.servers = {}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
async load (sync) {
|
|
27
|
-
DEBUG?.('reading bindings from:',
|
|
28
|
-
try {
|
|
27
|
+
DEBUG?.('reading bindings from:', registry)
|
|
28
|
+
try {
|
|
29
|
+
let {cds} = JSON.parse (sync ? readFileSync (filename) : await read (filename))
|
|
30
|
+
Object.assign (this,cds)
|
|
31
|
+
}
|
|
29
32
|
catch { /* ignored */ }
|
|
30
33
|
return this
|
|
31
34
|
}
|
|
32
35
|
async store (sync) {
|
|
33
|
-
DEBUG?.('writing bindings to:',
|
|
34
|
-
const json = JSON.stringify ({cds:this
|
|
35
|
-
return sync ? writeFileSync (
|
|
36
|
+
DEBUG?.('writing bindings to:', registry)
|
|
37
|
+
const json = JSON.stringify ({cds:this},null,' ')
|
|
38
|
+
return sync ? writeFileSync (filename, json) : write (filename, json)
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
async import() {
|
|
39
42
|
const required = cds.requires; if (!required) return this
|
|
40
|
-
const provided = (await this.load()) .
|
|
43
|
+
const provided = (await this.load()) .provides
|
|
41
44
|
for (let each in required) {
|
|
42
45
|
const req = required[each]; if (typeof req !== 'object') continue
|
|
43
46
|
const bound = provided [req.service||each]
|
|
@@ -52,9 +55,14 @@ module.exports = class Bindings {
|
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
async export (services, url) {
|
|
55
|
-
this.cleanup (
|
|
58
|
+
this.cleanup (url)
|
|
59
|
+
const { servers, provides } = this, { pid } = process
|
|
60
|
+
// register our server
|
|
61
|
+
servers[pid] = {
|
|
62
|
+
root: 'file://' + cds.root,
|
|
63
|
+
url
|
|
64
|
+
}
|
|
56
65
|
// register our services
|
|
57
|
-
const provides = this.cds.provides
|
|
58
66
|
for (let each of services) {
|
|
59
67
|
// if (each.name in cds.env.requires) continue
|
|
60
68
|
const options = each.options || {}
|
|
@@ -63,7 +71,8 @@ module.exports = class Bindings {
|
|
|
63
71
|
credentials: {
|
|
64
72
|
...options.credentials,
|
|
65
73
|
url: url + each.path
|
|
66
|
-
}
|
|
74
|
+
},
|
|
75
|
+
server: pid
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
process.on ('exit', ()=>this.purge())
|
|
@@ -72,17 +81,19 @@ module.exports = class Bindings {
|
|
|
72
81
|
|
|
73
82
|
purge() {
|
|
74
83
|
this.load(true)
|
|
75
|
-
DEBUG?.('purging bindings from:',
|
|
84
|
+
DEBUG?.('purging bindings from:', registry)
|
|
76
85
|
this.cleanup()
|
|
77
86
|
this.store(true)
|
|
78
87
|
}
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Remove all services served by this server or at the given url.
|
|
91
|
+
*/
|
|
92
|
+
cleanup (url) {
|
|
93
|
+
const { servers, provides } = this, { pid } = process
|
|
94
|
+
for (let [key,srv] of Object.entries (provides))
|
|
95
|
+
if (srv.server === pid || url && srv.credentials?.url?.startsWith(url)) delete provides [key]
|
|
96
|
+
delete servers [pid]
|
|
86
97
|
return this
|
|
87
98
|
}
|
|
88
99
|
}
|
package/lib/srv/cds-connect.js
CHANGED
|
@@ -45,7 +45,7 @@ connect.to = (datasource, options) => {
|
|
|
45
45
|
// construct new service instance
|
|
46
46
|
let srv = await new Service (datasource,m,o); await (Service._is_service_class ? srv.init?.() : Service.init?.(srv))
|
|
47
47
|
if (o.outbox) srv = cds.outboxed(srv)
|
|
48
|
-
if (datasource) {
|
|
48
|
+
if (datasource && !options) {
|
|
49
49
|
if (datasource === 'db') cds.db = srv
|
|
50
50
|
cds.services[datasource] = srv
|
|
51
51
|
delete _pending[datasource]
|
|
@@ -7,6 +7,7 @@ class MockedUsers {
|
|
|
7
7
|
const tenants = this.tenants = options.tenants || {}
|
|
8
8
|
const users = this.users = options.users || {}
|
|
9
9
|
for (let [k,v] of Object.entries(users)) {
|
|
10
|
+
if (!cds.env.requires.multitenancy) delete v.tenant
|
|
10
11
|
if (typeof v === 'boolean') continue
|
|
11
12
|
if (typeof v === 'string') v = { password:v }
|
|
12
13
|
let id = _configured(v).id || k
|