@sap/cds 7.5.3 → 7.6.2
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 +79 -21
- package/app/index.js +6 -17
- package/lib/auth/index.js +3 -0
- package/lib/compile/extend.js +9 -4
- package/lib/compile/for/lean_drafts.js +3 -4
- package/lib/compile/load.js +11 -15
- package/lib/compile/minify.js +2 -4
- package/lib/compile/to/sql.js +6 -4
- package/lib/compile/to/yaml.js +1 -1
- package/lib/dbs/cds-deploy.js +7 -13
- package/lib/env/defaults.js +1 -10
- package/lib/env/schemas/cds-package.js +27 -0
- package/lib/env/schemas/cds-rc.js +693 -0
- package/lib/env/schemas/index.js +6 -4
- package/lib/index.js +40 -47
- package/lib/log/cds-error.js +6 -0
- package/lib/log/format/aspects/als.js +1 -0
- package/lib/log/format/json.js +5 -1
- package/lib/ql/Query.js +2 -1
- package/lib/ql/cds-ql.js +1 -2
- package/lib/ql/infer.js +0 -2
- package/lib/req/cds-context.js +1 -1
- package/lib/req/request.js +3 -6
- package/lib/srv/middlewares/trace.js +2 -2
- package/lib/srv/protocols/hcql.js +44 -30
- package/lib/srv/protocols/http.js +60 -0
- package/lib/srv/protocols/index.js +0 -7
- package/lib/srv/protocols/odata-v4.js +8 -2
- package/lib/srv/srv-api.js +129 -62
- package/lib/srv/srv-handlers.js +0 -1
- package/lib/srv/srv-models.js +1 -0
- package/lib/utils/cds-utils.js +26 -0
- package/lib/utils/check-version.js +10 -13
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +22 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +89 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +4 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -24
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +1 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ApplyParser.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/http/HttpHeaderReader.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +6 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +0 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +17 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +22 -2
- package/libx/_runtime/cds-services/services/utils/columns.js +1 -2
- package/libx/_runtime/common/aspects/Association.js +17 -9
- package/libx/_runtime/common/generic/crud.js +13 -22
- package/libx/_runtime/common/generic/etag.js +1 -1
- package/libx/_runtime/common/generic/input.js +9 -1
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/generic/sorting.js +25 -15
- package/libx/_runtime/common/generic/stream.js +2 -16
- package/libx/_runtime/common/i18n/messages.properties +3 -0
- package/libx/_runtime/common/utils/copy.js +5 -0
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +4 -3
- package/libx/_runtime/common/utils/csn.js +0 -49
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +5 -5
- package/libx/_runtime/common/utils/generateOnCond.js +50 -25
- package/libx/_runtime/common/utils/resolveView.js +5 -35
- package/libx/_runtime/common/utils/rewriteAsterisks.js +17 -4
- package/libx/_runtime/common/utils/stream.js +16 -15
- package/libx/_runtime/common/utils/streamProp.js +25 -22
- package/libx/_runtime/db/Service.js +27 -8
- package/libx/_runtime/db/generic/input.js +6 -1
- package/libx/_runtime/db/generic/rewrite.js +3 -2
- package/libx/_runtime/db/query/read.js +15 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -11
- package/libx/_runtime/db/utils/columns.js +1 -0
- package/libx/_runtime/db/utils/stream.js +41 -0
- package/libx/_runtime/fiori/generic/read.js +2 -1
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +209 -55
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/execute.js +53 -14
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +2 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +34 -15
- package/libx/_runtime/messaging/file-based.js +4 -3
- package/libx/_runtime/messaging/redis-messaging.js +2 -1
- package/libx/_runtime/remote/Service.js +2 -1
- package/libx/_runtime/remote/utils/client.js +1 -1
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/execute.js +17 -5
- package/libx/odata/afterburner.js +58 -19
- package/libx/odata/cqn2odata.js +6 -8
- package/libx/odata/create.js +44 -0
- package/libx/odata/delete.js +25 -0
- package/libx/odata/error.js +8 -3
- package/libx/odata/metadata.js +6 -8
- package/libx/odata/service-document.js +1 -1
- package/libx/odata/update.js +110 -0
- package/libx/odata/utils.js +9 -6
- package/libx/outbox/index.js +74 -89
- package/libx/rest/RestAdapter.js +0 -3
- package/package.json +1 -1
- package/lib/env/schemas/cds-package.json +0 -17
- package/lib/env/schemas/cds-rc.json +0 -740
- package/lib/ql/STREAM.js +0 -90
package/lib/srv/srv-api.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
const cds = require('..'),
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const cds = require('..'),
|
|
2
|
+
{ Event, Request } = cds
|
|
3
|
+
const add_methods_to = require('./srv-methods')
|
|
4
|
+
const LOG = cds.log()
|
|
4
5
|
|
|
5
6
|
class Service extends require('./srv-handlers') {
|
|
6
|
-
|
|
7
|
-
constructor (name, model, o) {
|
|
7
|
+
constructor(name, model, o) {
|
|
8
8
|
if (is_object(name)) {
|
|
9
|
-
[
|
|
10
|
-
let srv =
|
|
9
|
+
;[model, o] = [name, model]
|
|
10
|
+
let srv =
|
|
11
|
+
cds.linked(model).services[0] ||
|
|
12
|
+
cds.error.expected`${{ model }} passed as first argument to be a CSN with a single service definition`
|
|
11
13
|
name = srv.name
|
|
12
14
|
}
|
|
13
|
-
super
|
|
15
|
+
super(name || new.target.name).options = o || (o = {})
|
|
14
16
|
if (o.kind) this.kind = o.kind // shortcut
|
|
15
17
|
if (model) this.model = model
|
|
16
18
|
}
|
|
@@ -18,11 +20,11 @@ class Service extends require('./srv-handlers') {
|
|
|
18
20
|
/**
|
|
19
21
|
* Subclasses may override this to prepare the given model appropriately
|
|
20
22
|
*/
|
|
21
|
-
set model
|
|
23
|
+
set model(csn) {
|
|
22
24
|
if (csn) {
|
|
23
|
-
let {definitions:defs={}} = super.model = cds.compile.for.nodejs(csn)
|
|
25
|
+
let { definitions: defs = {} } = (super.model = cds.compile.for.nodejs(csn))
|
|
24
26
|
super.definition = defs[this.options?.service] || defs[this.name]
|
|
25
|
-
add_methods_to
|
|
27
|
+
add_methods_to(this)
|
|
26
28
|
} else {
|
|
27
29
|
super.model = undefined
|
|
28
30
|
}
|
|
@@ -31,89 +33,146 @@ class Service extends require('./srv-handlers') {
|
|
|
31
33
|
/**
|
|
32
34
|
* Messaging API to emit asynchronous event messages, i.e. instances of `cds.Event`.
|
|
33
35
|
*/
|
|
34
|
-
emit
|
|
35
|
-
const eve = event instanceof Event ? event : new Event
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
return this.dispatch (eve)
|
|
36
|
+
emit(event, data, headers) {
|
|
37
|
+
const eve = event instanceof Event ? event : new Event(is_object(event) ? event : { event, data, headers })
|
|
38
|
+
return this.dispatch(eve)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* REST-style API to send synchronous requests...
|
|
43
43
|
*/
|
|
44
|
-
send
|
|
45
|
-
const req =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
send(method, path, data, headers) {
|
|
45
|
+
const req =
|
|
46
|
+
method instanceof Request
|
|
47
|
+
? method
|
|
48
|
+
: new Request(
|
|
49
|
+
is_object(method)
|
|
50
|
+
? method
|
|
51
|
+
: is_object(path)
|
|
52
|
+
? { method, data: path, headers: data }
|
|
53
|
+
: { method, path, data, headers }
|
|
54
|
+
)
|
|
55
|
+
return this.dispatch(req)
|
|
56
|
+
}
|
|
57
|
+
get(...args) {
|
|
58
|
+
return is_rest(args[0]) ? this.send('GET', ...args) : this.read(...args)
|
|
59
|
+
}
|
|
60
|
+
put(...args) {
|
|
61
|
+
return is_rest(args[0]) ? this.send('PUT', ...args) : this.update(...args)
|
|
62
|
+
}
|
|
63
|
+
post(...args) {
|
|
64
|
+
return is_rest(args[0]) ? this.send('POST', ...args) : this.create(...args)
|
|
65
|
+
}
|
|
66
|
+
patch(...args) {
|
|
67
|
+
return is_rest(args[0]) ? this.send('PATCH', ...args) : this.update(...args)
|
|
68
|
+
}
|
|
69
|
+
delete(...args) {
|
|
70
|
+
return is_rest(args[0]) ? this.send('DELETE', ...args) : DELETE.from(...args).bind(this)
|
|
71
|
+
}
|
|
55
72
|
|
|
56
73
|
/**
|
|
57
74
|
* Querying API to send synchronous requests...
|
|
58
75
|
*/
|
|
59
|
-
run
|
|
76
|
+
run(query, data) {
|
|
60
77
|
if (typeof query === 'function') {
|
|
61
|
-
const ctx = cds.context,
|
|
78
|
+
const ctx = cds.context,
|
|
79
|
+
fn = query
|
|
62
80
|
|
|
63
|
-
if (!ctx?.tx) return this.tx(fn)
|
|
81
|
+
if (!ctx?.tx) return this.tx(fn) // run fn with root tx
|
|
64
82
|
else {
|
|
65
|
-
if (!ctx.tx._done) return fn(this.tx(ctx))
|
|
66
|
-
else if (ctx.tx._done === 'rolled back')
|
|
83
|
+
if (!ctx.tx._done) return fn(this.tx(ctx)) // run fn with nested tx
|
|
84
|
+
else if (ctx.tx._done === 'rolled back')
|
|
85
|
+
// > reject
|
|
67
86
|
ctx.tx._throw_closed_error()
|
|
68
|
-
else return this.tx(fn)
|
|
87
|
+
else return this.tx(fn) // run fn with detached root tx
|
|
69
88
|
}
|
|
70
89
|
}
|
|
71
90
|
|
|
72
|
-
const req = new Request
|
|
73
|
-
return this.dispatch
|
|
91
|
+
const req = new Request({ query, data })
|
|
92
|
+
return this.dispatch(req)
|
|
93
|
+
}
|
|
94
|
+
read(...args) {
|
|
95
|
+
return is_query(args[0]) ? this.run(...args) : SELECT(...args).bind(this)
|
|
96
|
+
}
|
|
97
|
+
// Deprecated: To be removed with the next major release
|
|
98
|
+
stream(...args) {
|
|
99
|
+
cds._logDeprecation('stream method is deprecated and will be removed in upcoming releases!')
|
|
100
|
+
if (is_query(args[0])) {
|
|
101
|
+
Object.defineProperty(args[0], '_stream', { value: true, enumerable: false })
|
|
102
|
+
if (cds.env.features.stream_compat)
|
|
103
|
+
Object.defineProperty(args[0], '_streaming', { value: true, enumerable: false })
|
|
104
|
+
return this.run(...args).then(result => (result ? Object.values(result)[0] : result))
|
|
105
|
+
}
|
|
106
|
+
const q = (args ? SELECT.one.columns(args) : SELECT.one).bind(this)
|
|
107
|
+
Object.defineProperty(q, '_stream', { value: true, enumerable: false })
|
|
108
|
+
if (cds.env.features.stream_compat) Object.defineProperty(q, '_streaming', { value: true, enumerable: false })
|
|
109
|
+
return q
|
|
110
|
+
}
|
|
111
|
+
insert(...args) {
|
|
112
|
+
return INSERT(...args).bind(this)
|
|
113
|
+
}
|
|
114
|
+
create(...args) {
|
|
115
|
+
return INSERT.into(...args).bind(this)
|
|
116
|
+
}
|
|
117
|
+
update(...args) {
|
|
118
|
+
return UPDATE.entity(...args).bind(this)
|
|
119
|
+
}
|
|
120
|
+
upsert(...args) {
|
|
121
|
+
return UPSERT(...args).bind(this)
|
|
122
|
+
}
|
|
123
|
+
exists(...args) {
|
|
124
|
+
return SELECT.one([1])
|
|
125
|
+
.from(...args)
|
|
126
|
+
.bind(this)
|
|
74
127
|
}
|
|
75
|
-
read (...args) { return is_query(args[0]) ? this.run(...args) : SELECT(...args).bind(this) }
|
|
76
|
-
stream (...args) { return is_query(args[0]) ? this.run(...args) : STREAM(...args).bind(this) }
|
|
77
|
-
insert (...args) { return INSERT(...args).bind(this) }
|
|
78
|
-
create (...args) { return INSERT.into(...args).bind(this) }
|
|
79
|
-
update (...args) { return UPDATE.entity(...args).bind(this) }
|
|
80
|
-
upsert (...args) { return UPSERT(...args).bind(this) }
|
|
81
|
-
exists (...args) { return SELECT.one([1]).from(...args).bind(this) }
|
|
82
128
|
|
|
83
129
|
/**
|
|
84
130
|
* Streaming API variant of .run(). Subclasses should override this to support real streaming.
|
|
85
131
|
* The default implementation doesn't stream, but simply invokes the callback on each row.
|
|
86
132
|
* The callback function is invoked with (row, index).
|
|
87
133
|
*/
|
|
88
|
-
foreach
|
|
89
|
-
if (!callback)
|
|
90
|
-
return this.run
|
|
134
|
+
foreach(query, data, callback) {
|
|
135
|
+
if (!callback) [data, callback] = [undefined, data]
|
|
136
|
+
return this.run(query, data).then(rows => rows.forEach(callback) || rows)
|
|
91
137
|
}
|
|
92
138
|
|
|
93
139
|
/**
|
|
94
140
|
* Model Reflection API...
|
|
95
141
|
*/
|
|
96
|
-
get namespace()
|
|
97
|
-
return super.namespace
|
|
98
|
-
|
|
142
|
+
get namespace() {
|
|
143
|
+
return (super.namespace =
|
|
144
|
+
this.definition?.name ||
|
|
145
|
+
this.model?.namespace ||
|
|
146
|
+
(!this.isDatabaseService && !/\W/.test(this.name) && this.name) ||
|
|
147
|
+
undefined)
|
|
99
148
|
}
|
|
100
149
|
|
|
101
|
-
get operations() {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
get
|
|
150
|
+
get operations() {
|
|
151
|
+
return (super.operations = _reflect(this, d => d.kind === 'action' || d.kind === 'function'))
|
|
152
|
+
}
|
|
153
|
+
get entities() {
|
|
154
|
+
return (super.entities = _reflect(this, d => d.kind === 'entity'))
|
|
155
|
+
}
|
|
156
|
+
get events() {
|
|
157
|
+
return (super.events = _reflect(this, d => d.kind === 'event'))
|
|
158
|
+
}
|
|
159
|
+
get types() {
|
|
160
|
+
return (super.types = _reflect(this, d => !d.kind || d.kind === 'type'))
|
|
161
|
+
}
|
|
105
162
|
|
|
106
163
|
/**
|
|
107
164
|
* Flag to control whether this service is extensible.
|
|
108
165
|
* Can be overridden by subclasses.
|
|
109
166
|
* REVISIT cds.xt name check should move to respective services
|
|
110
167
|
*/
|
|
111
|
-
get isExtensible() {
|
|
168
|
+
get isExtensible() {
|
|
169
|
+
return !this.name?.startsWith('cds.xt.') && this.model === cds.model
|
|
170
|
+
}
|
|
112
171
|
|
|
113
172
|
/**
|
|
114
173
|
* Subclasses may override this to free private resources
|
|
115
174
|
*/
|
|
116
|
-
disconnect
|
|
175
|
+
disconnect(tenant) {
|
|
117
176
|
if (this === cds.db) {
|
|
118
177
|
if (tenant && cds.db.dbcs) cds.db.dbcs.delete[tenant]
|
|
119
178
|
else if (!tenant) cds.db = undefined //> REVISIT: should go into DatabaseService
|
|
@@ -121,11 +180,19 @@ class Service extends require('./srv-handlers') {
|
|
|
121
180
|
delete cds.services[this.name]
|
|
122
181
|
}
|
|
123
182
|
|
|
124
|
-
get path() {
|
|
125
|
-
|
|
183
|
+
get path() {
|
|
184
|
+
return (super.path = cds.service.protocols.path4(this))
|
|
185
|
+
}
|
|
186
|
+
set path(p) {
|
|
187
|
+
super.path = p
|
|
188
|
+
}
|
|
126
189
|
|
|
127
|
-
get endpoints() {
|
|
128
|
-
|
|
190
|
+
get endpoints() {
|
|
191
|
+
return (super.endpoints = cds.service.protocols.endpoints4(this))
|
|
192
|
+
}
|
|
193
|
+
set endpoints(p) {
|
|
194
|
+
super.endpoints = p
|
|
195
|
+
}
|
|
129
196
|
}
|
|
130
197
|
|
|
131
198
|
const { dispatch, handle } = require('./srv-dispatch')
|
|
@@ -138,8 +205,8 @@ Service.prototype._is_service_instance = Service._is_service_class = true //> fo
|
|
|
138
205
|
module.exports = Service
|
|
139
206
|
|
|
140
207
|
// Helpers...
|
|
141
|
-
const _reflect = (srv,filter) => !srv.model ? [] : srv.model.childrenOf
|
|
208
|
+
const _reflect = (srv, filter) => (!srv.model ? [] : srv.model.childrenOf(srv.namespace, filter))
|
|
142
209
|
const is_rest = x => x && typeof x === 'string' && x[0] === '/'
|
|
143
|
-
const is_query = x => x && x.bind || is_array(x) && !x.raw
|
|
144
|
-
const is_array =
|
|
145
|
-
const is_object =
|
|
210
|
+
const is_query = x => (x && x.bind) || (is_array(x) && !x.raw)
|
|
211
|
+
const is_array = x => Array.isArray(x) && !x.raw
|
|
212
|
+
const is_object = x => typeof x === 'object'
|
package/lib/srv/srv-handlers.js
CHANGED
package/lib/srv/srv-models.js
CHANGED
|
@@ -62,6 +62,7 @@ class ExtendedModels {
|
|
|
62
62
|
_has_extensions = tenant && extensibility && await _is_extended(tenant)
|
|
63
63
|
} catch (error) {
|
|
64
64
|
// Better error message for client
|
|
65
|
+
if (error.status === 404) throw error
|
|
65
66
|
cds.error('`extensibility: true` is configured but table "cds.xt.Extensions" does not exist. Please redeploy.', error)
|
|
66
67
|
}
|
|
67
68
|
if (!_has_extensions) {
|
package/lib/utils/cds-utils.js
CHANGED
|
@@ -28,6 +28,16 @@ exports.Object_keys = o => ({
|
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* CSN-aware function to deep clone objectx
|
|
33
|
+
*/
|
|
34
|
+
exports.clone = (x) => {
|
|
35
|
+
const y = structuredClone(x)
|
|
36
|
+
if (x.$sources) Object.defineProperty (y, '$sources', { value: x.$sources })
|
|
37
|
+
return y
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
31
41
|
/**
|
|
32
42
|
* Simple helper to always access results as arrays.
|
|
33
43
|
*/
|
|
@@ -180,6 +190,22 @@ exports.find = function find (base, patterns='*', filter=()=>true) {
|
|
|
180
190
|
return files
|
|
181
191
|
}
|
|
182
192
|
|
|
193
|
+
exports.deprecated = (fn, { kind = 'Method', old = fn.name+'()', use } = {}) => function() {
|
|
194
|
+
if (!fn.warned) {
|
|
195
|
+
let o={}; Error.captureStackTrace(o)
|
|
196
|
+
console.warn (
|
|
197
|
+
'\n------------------------------------------------------------------------------',
|
|
198
|
+
'\nDEPRECATED:', old, '\n',
|
|
199
|
+
'\n ', kind, old, 'is deprecated and will be removed in upcoming releases!',
|
|
200
|
+
use ? `\n => Please use ${use} instead.` : '', '\n',
|
|
201
|
+
o.stack.replace(/^Error\n\s*at.*\n/,'\n'), '\n',
|
|
202
|
+
'\n------------------------------------------------------------------------------\n',
|
|
203
|
+
)
|
|
204
|
+
if (cds.env.features.deprecated !== 'show all') fn.warned = true
|
|
205
|
+
}
|
|
206
|
+
return fn.apply (this, arguments)
|
|
207
|
+
}
|
|
208
|
+
|
|
183
209
|
exports.csv = require('./csv-reader')
|
|
184
210
|
|
|
185
211
|
/**
|
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
const
|
|
2
|
-
let [ major, minor ] = version.split('.').map(x => +x)
|
|
3
|
-
return { version, major, minor }
|
|
4
|
-
}
|
|
5
|
-
const required = major_minor(
|
|
1
|
+
const required = _major_minor(
|
|
6
2
|
require('../../package.json').engines.node.match(/>=(.*)/)[1]
|
|
7
3
|
)
|
|
8
|
-
const given =
|
|
4
|
+
const given = _major_minor(
|
|
9
5
|
process.version.match(/^v(\d+\.\d+)/)[1]
|
|
10
6
|
)
|
|
11
7
|
|
|
12
|
-
if (given.major < required.major || given.major === required.major && given.minor < required.minor) process.exit (
|
|
13
|
-
process.stderr.write (`
|
|
8
|
+
if (given.major < required.major || given.major === required.major && given.minor < required.minor) process.exit (process.stderr.write (`
|
|
14
9
|
Node.js v${required.version} or higher is required for @sap/cds.
|
|
15
10
|
Current v${given.version} does not satisfy this.
|
|
16
|
-
\n`
|
|
17
|
-
) || 1)
|
|
11
|
+
\n`) || 1)
|
|
18
12
|
|
|
19
|
-
if (given.major < 18)
|
|
20
|
-
process.stderr.write (`WARNING: \n
|
|
13
|
+
if (given.major < 18) process.stderr.write (`WARNING: \n
|
|
21
14
|
Node.js v${given.major} has reached end of life. Please upgrade to v18 or higher.
|
|
22
|
-
`)
|
|
15
|
+
\n`)
|
|
16
|
+
|
|
17
|
+
function _major_minor (version) {
|
|
18
|
+
let [ major, minor ] = version.split('.').map(x => +x)
|
|
19
|
+
return { version, major, minor }
|
|
23
20
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/* eslint-disable complexity */
|
|
2
2
|
const cds = require('../../../cds')
|
|
3
|
-
// requesting logger without module on purpose!
|
|
4
|
-
const LOG = cds.log()
|
|
5
3
|
|
|
6
4
|
const {
|
|
7
5
|
uri: {
|
|
@@ -63,6 +61,26 @@ function _add4Odata(query) {
|
|
|
63
61
|
else Object.defineProperty(query.SELECT, '_4odata', { value: true })
|
|
64
62
|
}
|
|
65
63
|
|
|
64
|
+
function _addAllColumns(query) {
|
|
65
|
+
if (Array.isArray(query)) {
|
|
66
|
+
query.forEach(q => {
|
|
67
|
+
if (!q.SELECT.from.SELECT) _addAllColumns(q)
|
|
68
|
+
})
|
|
69
|
+
} else {
|
|
70
|
+
if (!query.SELECT.columns) query.SELECT.columns = ['*']
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _handleStreamProperties(target, query, model) {
|
|
75
|
+
if (Array.isArray(query)) {
|
|
76
|
+
query.forEach(q => {
|
|
77
|
+
_handleStreamProperties(target, q, model)
|
|
78
|
+
})
|
|
79
|
+
} else {
|
|
80
|
+
handleStreamProperties(target, query.SELECT.columns, model)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
/**
|
|
67
85
|
* Class representing an OData request.
|
|
68
86
|
* @extends cds.Request
|
|
@@ -95,8 +113,8 @@ class ODataRequest extends cds.Request {
|
|
|
95
113
|
const { event, unbound } = info
|
|
96
114
|
if (event === 'READ') {
|
|
97
115
|
_add4Odata(query)
|
|
98
|
-
|
|
99
|
-
if (!isStreaming(segments))
|
|
116
|
+
_addAllColumns(query)
|
|
117
|
+
if (!isStreaming(segments)) _handleStreamProperties(target, query, service.model)
|
|
100
118
|
}
|
|
101
119
|
const _queryOptions = odataReq.getQueryOptions()
|
|
102
120
|
// prettier-ignore
|
|
@@ -155,8 +173,6 @@ class ODataRequest extends cds.Request {
|
|
|
155
173
|
else if (type === 'DELETE' && data.IsActiveEntity !== true) event = 'CANCEL'
|
|
156
174
|
}
|
|
157
175
|
|
|
158
|
-
if (query.STREAM) event = 'STREAM'
|
|
159
|
-
|
|
160
176
|
// mark query as for an OData READ
|
|
161
177
|
if (event === 'READ') Object.defineProperty(query.SELECT, '_4odata', { value: true })
|
|
162
178
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { join } = require('path')
|
|
2
1
|
const cds = require('../../../../cds')
|
|
3
2
|
const LOG = cds.log('odata')
|
|
4
3
|
|
|
@@ -7,9 +6,9 @@ const { normalizeError } = require('../../../../common/error/frontend')
|
|
|
7
6
|
const getError = require('../../../../common/error')
|
|
8
7
|
|
|
9
8
|
const mpSupportsEmptyLocale = () => {
|
|
10
|
-
const pkg = require(
|
|
11
|
-
const
|
|
12
|
-
return
|
|
9
|
+
const pkg = require(require.resolve('@sap/cds-mtxs/package.json'))
|
|
10
|
+
const [major, minor] = pkg.version.split('.').map(Number)
|
|
11
|
+
return major > 1 || (major === 1 && minor >= 12)
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
|
-
|
|
2
|
+
const LOG = cds.log('odata')
|
|
3
3
|
const ODataRequest = require('../ODataRequest')
|
|
4
4
|
|
|
5
5
|
const {
|
|
@@ -23,7 +23,9 @@ const getError = require('../../../../common/error')
|
|
|
23
23
|
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
24
24
|
const { getPageSize, commonGenericPaging } = require('../../../../common/generic/paging')
|
|
25
25
|
const { handler: commonGenericSorting } = require('../../../../common/generic/sorting')
|
|
26
|
-
const {
|
|
26
|
+
const { transformRedirectProperties } = require('../../../../common/utils/stream')
|
|
27
|
+
const { getTransition } = require('../../../../common/utils/resolveView')
|
|
28
|
+
const { Readable } = require('stream')
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Checks whether a bound function or function import is invoked.
|
|
@@ -274,6 +276,7 @@ const _readCollection = async (tx, req, odataReq) => {
|
|
|
274
276
|
? getPageSize(req.query[0]._target).max
|
|
275
277
|
: req.query.SELECT.limit && req.query.SELECT.limit.rows && req.query.SELECT.limit.rows.val
|
|
276
278
|
const top = odataReq.getUriInfo().getQueryOption(QueryOptions.TOP) || parseInt(odataReq._queryOptions?.$top)
|
|
279
|
+
|
|
277
280
|
if (limit && limit === result.length && limit !== top && !('$nextLink' in result)) {
|
|
278
281
|
const token = odataReq.getUriInfo().getQueryOption(QueryOptions.SKIPTOKEN) || odataReq._queryOptions?.$skiptoken
|
|
279
282
|
if (cds.env.query.limit.reliablePaging && _reliablePagingPossible(req)) {
|
|
@@ -298,6 +301,64 @@ const _readCollection = async (tx, req, odataReq) => {
|
|
|
298
301
|
return toODataResult(result, req)
|
|
299
302
|
}
|
|
300
303
|
|
|
304
|
+
const _resolveContentProperty = (req, annotName, resolvedProp) => {
|
|
305
|
+
if (req.target.elements[resolvedProp]) {
|
|
306
|
+
return resolvedProp
|
|
307
|
+
} else {
|
|
308
|
+
LOG._warn &&
|
|
309
|
+
LOG.warn(
|
|
310
|
+
`"${annotName}" in entity "${req.target.name}" points to property "${resolvedProp}" which was renamed or is not part of the projection. You must update the annotation value.`
|
|
311
|
+
)
|
|
312
|
+
const mapping = getTransition(req.target, cds.db).mapping
|
|
313
|
+
const key = [...mapping.entries()].find(({ 1: val }) => val.ref[0] === resolvedProp)
|
|
314
|
+
return key?.length && key[0]
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const _addMetadataProperty = (req, property, annotName, odataName) => {
|
|
319
|
+
if (typeof property[annotName] === 'object') {
|
|
320
|
+
const contentProperty = _resolveContentProperty(req, annotName, property[annotName]['='].replaceAll(/\./g, '_'))
|
|
321
|
+
req.target.elements[contentProperty]
|
|
322
|
+
? req.query.SELECT.columns.push({ ref: [contentProperty], as: odataName })
|
|
323
|
+
: LOG._warn &&
|
|
324
|
+
LOG.warn(`"${annotName.split('.')[1]}" ${contentProperty} not found in entity "${req.target.name}".`)
|
|
325
|
+
} else {
|
|
326
|
+
req.query.SELECT.columns.push({ val: property[annotName], as: odataName })
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const _addStreamMetadata = req => {
|
|
331
|
+
// new odata parser sets streaming property in SELECT.from
|
|
332
|
+
const ref = req.query.SELECT.columns?.[0].ref || req.query.SELECT.from.ref
|
|
333
|
+
const propertyName = ref[ref.length - 1]
|
|
334
|
+
let mediaTypeProperty
|
|
335
|
+
for (let key in req.target.elements) {
|
|
336
|
+
const val = req.target.elements[key]
|
|
337
|
+
if (val['@Core.MediaType'] && val.name === propertyName) {
|
|
338
|
+
mediaTypeProperty = val
|
|
339
|
+
break
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
_addMetadataProperty(req, mediaTypeProperty, '@Core.MediaType', '$mediaContentType')
|
|
344
|
+
|
|
345
|
+
if (mediaTypeProperty['@Core.ContentDisposition.Filename']) {
|
|
346
|
+
_addMetadataProperty(
|
|
347
|
+
req,
|
|
348
|
+
mediaTypeProperty,
|
|
349
|
+
'@Core.ContentDisposition.Filename',
|
|
350
|
+
'$mediaContentDispositionFilename'
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (mediaTypeProperty['@Core.ContentDisposition.Type']) {
|
|
355
|
+
req.query.SELECT.columns.push({
|
|
356
|
+
val: mediaTypeProperty['@Core.ContentDisposition.Type'],
|
|
357
|
+
as: '$mediaContentDispositionType'
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
301
362
|
/**
|
|
302
363
|
* Reading the full entity or only a property of it is alike.
|
|
303
364
|
* In case of an entity, odata-v4 wants the value an object structure,
|
|
@@ -309,7 +370,8 @@ const _readCollection = async (tx, req, odataReq) => {
|
|
|
309
370
|
* @private
|
|
310
371
|
*/
|
|
311
372
|
const _readStream = async (tx, req) => {
|
|
312
|
-
req.query._streaming = true
|
|
373
|
+
if (cds.env.features.stream_compat) req.query._streaming = true
|
|
374
|
+
else if (!req.target['@cds.persistence.skip']) _addStreamMetadata(req)
|
|
313
375
|
|
|
314
376
|
let result = await tx.dispatch(req)
|
|
315
377
|
|
|
@@ -330,8 +392,10 @@ const _readStream = async (tx, req) => {
|
|
|
330
392
|
if (result[0] === null) return null
|
|
331
393
|
|
|
332
394
|
result = result[0]
|
|
333
|
-
const readable = result.value
|
|
334
395
|
|
|
396
|
+
const readable = cds.env.features.stream_compat
|
|
397
|
+
? result.value
|
|
398
|
+
: Object.values(result).find(v => v instanceof Readable)
|
|
335
399
|
if (readable) {
|
|
336
400
|
readable.on('error', () => {
|
|
337
401
|
readable.removeAllListeners('error')
|
|
@@ -393,26 +457,20 @@ const _readAndTransform = (tx, req, odataReq) => {
|
|
|
393
457
|
return _readCollection(tx, req, odataReq)
|
|
394
458
|
}
|
|
395
459
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (segments[segments.length - 1]._isStreamByDollarValue) {
|
|
403
|
-
for (const k in req.target.elements) {
|
|
404
|
-
if (req.target.elements[k]['@Core.MediaType']) {
|
|
405
|
-
req.query.SELECT.columns = [{ ref: [k] }]
|
|
406
|
-
break
|
|
407
|
-
}
|
|
460
|
+
// REVISIT: move to afterburner
|
|
461
|
+
if (segments[segments.length - 1]._isStreamByDollarValue) {
|
|
462
|
+
for (const k in req.target.elements) {
|
|
463
|
+
if (req.target.elements[k]['@Core.MediaType']) {
|
|
464
|
+
req.query.SELECT.columns = [{ ref: [k] }]
|
|
465
|
+
break
|
|
408
466
|
}
|
|
409
|
-
|
|
410
|
-
return _readStream(tx, req)
|
|
411
467
|
}
|
|
412
468
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
469
|
+
return _readStream(tx, req)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (isStreaming(segments)) {
|
|
473
|
+
return _readStream(tx, req)
|
|
416
474
|
}
|
|
417
475
|
|
|
418
476
|
if (req.target._isSingleton) {
|
|
@@ -443,6 +501,14 @@ const _removeKeysForParams = result => {
|
|
|
443
501
|
return options
|
|
444
502
|
}
|
|
445
503
|
|
|
504
|
+
const _ensureStream = result => {
|
|
505
|
+
// temp workaround for url streaming
|
|
506
|
+
const stream_ = new Readable()
|
|
507
|
+
stream_.push(result.value)
|
|
508
|
+
stream_.push(null)
|
|
509
|
+
result.value = stream_
|
|
510
|
+
}
|
|
511
|
+
|
|
446
512
|
/**
|
|
447
513
|
* The handler that will be registered with odata-v4.
|
|
448
514
|
*
|
|
@@ -490,6 +556,8 @@ const read = service => {
|
|
|
490
556
|
// for passing into commit
|
|
491
557
|
odataReq.getBatchApplicationData().results[changeset].push({ result, req })
|
|
492
558
|
} else if (result.value && isStreaming(odataReq.getUriInfo().getPathSegments())) {
|
|
559
|
+
// REVISIT: temp workaround for url streaming
|
|
560
|
+
if (!(result.value instanceof Readable)) _ensureStream(result)
|
|
493
561
|
result.value.on('end', () => tx.commit(result).catch(() => {}))
|
|
494
562
|
result.value.once('error', err => tx.rollback(err).catch(() => {}))
|
|
495
563
|
} else {
|
|
@@ -11,18 +11,20 @@ const {
|
|
|
11
11
|
ENTITY_COLLECTION,
|
|
12
12
|
NAVIGATION_TO_MANY,
|
|
13
13
|
NAVIGATION_TO_ONE,
|
|
14
|
-
PRIMITIVE_PROPERTY
|
|
14
|
+
PRIMITIVE_PROPERTY,
|
|
15
|
+
SINGLETON
|
|
15
16
|
} = require('../okra/odata-server').uri.UriResource.ResourceKind
|
|
16
17
|
|
|
17
18
|
const SUPPORTED_SEGMENT_KINDS = {
|
|
18
19
|
[BOUND_ACTION]: 1,
|
|
19
20
|
[BOUND_FUNCTION]: 1,
|
|
21
|
+
[COUNT]: 1,
|
|
20
22
|
[ENTITY]: 1,
|
|
21
23
|
[ENTITY_COLLECTION]: 1,
|
|
22
24
|
[NAVIGATION_TO_ONE]: 1,
|
|
23
25
|
[NAVIGATION_TO_MANY]: 1,
|
|
24
26
|
[PRIMITIVE_PROPERTY]: 1,
|
|
25
|
-
[
|
|
27
|
+
[SINGLETON]: 1
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -44,7 +44,6 @@ const { isStreaming } = require('../utils/stream')
|
|
|
44
44
|
const { getPageSize } = require('../../../../common/generic/paging')
|
|
45
45
|
const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
|
|
46
46
|
const { handleStreamProperties } = require('../../../../common/utils/streamProp')
|
|
47
|
-
const { isNewStream } = require('../../../../common/utils/stream')
|
|
48
47
|
|
|
49
48
|
const { ensureUnlocalized } = require('../../../../fiori/utils/handler')
|
|
50
49
|
|
|
@@ -316,28 +315,6 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
316
315
|
const segments = uriInfo.getPathSegments()
|
|
317
316
|
isPathSupported(SUPPORTED_SEGMENT_KINDS, segments)
|
|
318
317
|
|
|
319
|
-
if (isNewStream()) {
|
|
320
|
-
let propertyParam
|
|
321
|
-
if (isStreaming(segments)) {
|
|
322
|
-
propertyParam = getPropertyParam(segments)
|
|
323
|
-
} else if (segments[segments.length - 1]._isStreamByDollarValue) {
|
|
324
|
-
// REVISIT: Issue with multiple streaming properties ? Also in read.js
|
|
325
|
-
for (const k in target.elements) {
|
|
326
|
-
if (target.elements[k]['@Core.MediaType']) {
|
|
327
|
-
propertyParam = { ref: [k] }
|
|
328
|
-
break
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (propertyParam) {
|
|
334
|
-
const isView = target.params && Object.keys(target.params).length > 0
|
|
335
|
-
return STREAM.from(isView ? convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service)).column(
|
|
336
|
-
propertyParam.ref[0]
|
|
337
|
-
)
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
318
|
const queryOptions = odataReq.getQueryOptions()
|
|
342
319
|
const entity = service.model.definitions[ensureUnlocalized(target.name)]
|
|
343
320
|
const propertyParam = getPropertyParam(segments)
|
|
@@ -375,7 +352,7 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
375
352
|
// keep target as input because of localized view
|
|
376
353
|
const cqn = SELECT.from(isView ? convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service), select)
|
|
377
354
|
if (!cqn.SELECT.columns) cqn.SELECT.columns = ['*']
|
|
378
|
-
if (!streaming) handleStreamProperties(target, cqn, service.model
|
|
355
|
+
if (!streaming) handleStreamProperties(target, cqn.SELECT.columns, service.model)
|
|
379
356
|
|
|
380
357
|
const isCount =
|
|
381
358
|
isCollectionOrToMany &&
|