@sap/cds 6.2.3 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/apis/connect.d.ts +1 -1
- package/apis/cqn.d.ts +1 -1
- package/apis/internal/inference.d.ts +14 -0
- package/apis/ql.d.ts +40 -36
- package/apis/services.d.ts +23 -6
- package/bin/build/buildTaskHandler.js +3 -3
- package/bin/build/provider/buildTaskHandlerEdmx.js +1 -1
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +4 -3
- package/bin/build/provider/buildTaskHandlerInternal.js +2 -2
- package/bin/build/provider/java/index.js +2 -1
- package/bin/build/provider/mtx/index.js +2 -1
- package/bin/build/provider/mtx/resourcesTarBuilder.js +3 -2
- package/bin/build/provider/mtx-extension/index.js +2 -1
- package/bin/build/provider/mtx-sidecar/index.js +3 -1
- package/lib/auth/index.js +2 -1
- package/lib/auth/jwt-auth.js +64 -3
- package/lib/auth/xsuaa-auth.js +2 -3
- package/lib/compile/cdsc.js +1 -0
- package/lib/compile/etc/_localized.js +1 -0
- package/lib/dbs/cds-deploy.js +2 -1
- package/lib/env/cds-env.js +14 -49
- package/lib/env/cds-requires.js +13 -7
- package/lib/env/defaults.js +4 -0
- package/lib/i18n/localize.js +11 -8
- package/lib/index.js +1 -1
- package/lib/log/cds-log.js +2 -2
- package/lib/log/format/cf.js +16 -0
- package/lib/log/format/kibana.js +15 -2
- package/lib/ql/INSERT.js +12 -11
- package/lib/ql/Query.js +14 -7
- package/lib/ql/UPSERT.js +1 -0
- package/lib/ql/Whereable.js +6 -2
- package/lib/ql/cds-ql.js +2 -4
- package/lib/req/request.js +2 -0
- package/lib/srv/middlewares/cds-context.js +1 -1
- package/lib/srv/srv-dispatch.js +1 -0
- package/lib/srv/srv-tx.js +3 -3
- package/lib/utils/cds-utils.js +75 -30
- package/lib/utils/inflect.js +24 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +9 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +23 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +27 -15
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -10
- package/libx/_runtime/cds-services/services/utils/differ.js +6 -4
- package/libx/_runtime/common/composition/data.js +29 -40
- package/libx/_runtime/common/composition/update.js +6 -19
- package/libx/_runtime/common/generic/paging.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +7 -13
- package/libx/_runtime/db/utils/generateAliases.js +1 -0
- package/libx/_runtime/fiori/generic/read.js +11 -4
- package/libx/_runtime/hana/execute.js +2 -2
- package/libx/_runtime/hana/search2cqn4sql.js +1 -0
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +5 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +7 -1
- package/libx/_runtime/messaging/file-based.js +1 -1
- package/libx/_runtime/messaging/message-queuing.js +5 -2
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +5 -3
- package/libx/odata/cqn2odata.js +4 -1
- package/libx/odata/utils.js +8 -7
- package/libx/rest/RestAdapter.js +1 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,52 @@
|
|
|
4
4
|
- The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
5
5
|
- This project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
|
|
7
|
+
## Version 6.3.0 - 2022-10-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Additional type signatures for service methods in the query API
|
|
12
|
+
- In case of error in a batch request, the @Core.ContentID is added to the details of the error message
|
|
13
|
+
- Extensibility: Use i18n files from extensions in edmx calculation
|
|
14
|
+
- In messaging, you can listen to all messages in a queue by subscribing to `'*'`
|
|
15
|
+
- Improved Log formatting for Cloud Foundry
|
|
16
|
+
- In remote services: Correct OData type conversion when using an imported csn
|
|
17
|
+
- Types for `SELECT.forUpdate({wait})`
|
|
18
|
+
|
|
19
|
+
- `cds.ql` now provides a dedicated method `.alias()` to choose table aliases, e.g.:
|
|
20
|
+
```js
|
|
21
|
+
SELECT.from(Authors).alias(a)
|
|
22
|
+
```
|
|
23
|
+
> Note: unfortunately we can't use method `.as()` instead of `.alias()` for compatibility reasons
|
|
24
|
+
|
|
25
|
+
- `cds.ql` now supports constructing queries with `where exists` clauses, e.g.:
|
|
26
|
+
```js
|
|
27
|
+
SELECT.from(Authors).where({exists:'books'})
|
|
28
|
+
SELECT.from(Authors).where({'not exists':'books'})
|
|
29
|
+
SELECT.from(Authors).alias('a').where({ exists: // or 'not exists'
|
|
30
|
+
SELECT.from(Books).where({author_ID:{ref:['a','ID']}})
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
> Note: last query is equivalent to first
|
|
34
|
+
- `cds compile` and `cds deploy` now also support dialect `h2`
|
|
35
|
+
- New (easier) `jwt` and `xsuaa` authentication middleware for pluggable middlewares
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- In `enterprise-messaging`, emitting CloudEvents messages sets the HTTP header `Content-Type: application/cloudevents+json`
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Change signature of cqn `SELECT.limit.offset` and `SELECT.limit.rows` to `val` instead of `number`
|
|
44
|
+
- Parsing of store procedure SQL calls including the schema name. For example, `CALL "SCHEMA"."PROC"(?)` and `CALL SCHEMA.PROC(?)`
|
|
45
|
+
- Add property name in the error message on validation of the value
|
|
46
|
+
- Kibana and Cloud Foundry formatter: do not log cookie header value
|
|
47
|
+
- Missing SQL aliases for `$search` queries combined with `$orderBy` query option
|
|
48
|
+
- The return value of `cds.connect` is now correctly typed as a `Promise`
|
|
49
|
+
- `req.data` is no longer modified for remote services in the case of `odata-v2` inserts
|
|
50
|
+
- `cds.localize` no longer ignores i18n files defined within CDS model scope and outside project scope
|
|
51
|
+
- Don't modify query in `fioriGenericRead` handler
|
|
52
|
+
|
|
7
53
|
## Version 6.2.3 - 2022-10-21
|
|
8
54
|
|
|
9
55
|
### Fixed
|
|
@@ -88,6 +134,8 @@
|
|
|
88
134
|
- `cds deploy` and `cds run/serve/watch` no longer print terminal escape sequences (`x1b...`) if they run non-interactively.
|
|
89
135
|
- Some fields in entities like `path` generated invalid sql
|
|
90
136
|
|
|
137
|
+
### Removed
|
|
138
|
+
|
|
91
139
|
## Version 6.1.3 - 2022-09-13
|
|
92
140
|
|
|
93
141
|
### Added
|
package/apis/connect.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ declare class cds {
|
|
|
15
15
|
* Connects the primary datasource.
|
|
16
16
|
* @see [capire](https://cap.cloud.sap/docs/node.js/api#cds-connect)
|
|
17
17
|
*/
|
|
18
|
-
(options?: string | ConnectOptions) : typeof cds //> cds.connect(<options>)
|
|
18
|
+
(options?: string | ConnectOptions) : Promise<typeof cds> //> cds.connect(<options>)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
package/apis/cqn.d.ts
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Types in this file are not part of the API.
|
|
2
|
+
// They are merely meant as definitions that are used
|
|
3
|
+
// in several places within the API.
|
|
4
|
+
|
|
5
|
+
// any class (not value) of array to represent plural types used in cds-typer.
|
|
6
|
+
// Mainly used as pattern match for SingularType
|
|
7
|
+
//type ArrayConstructable = Constructable<Array<unknown>>
|
|
8
|
+
export interface ArrayConstructable<T = any[]> {
|
|
9
|
+
new(...args: any[]): T[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// concrete singular type.
|
|
13
|
+
// `SingularType<Books>` == `Book`.
|
|
14
|
+
export type SingularType<T extends ArrayConstructable<T>> = InstanceType<T>[number]
|
package/apis/ql.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {Definition} from "./csn"
|
|
2
2
|
import * as CQN from "./cqn"
|
|
3
|
+
import { ArrayConstructable, SingularType } from "./internal/inference"
|
|
3
4
|
|
|
4
5
|
export type Query = CQN.Query
|
|
5
6
|
|
|
@@ -12,7 +13,7 @@ interface Constructable<T> {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export class cds_ql {
|
|
15
|
-
ql:QL & ((context:object) => QL)
|
|
16
|
+
ql:QL<any> & ((context:object) => QL<any>)
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export type PK = number | string | object
|
|
@@ -48,25 +49,14 @@ type Proxy_<T> = {
|
|
|
48
49
|
get: (path: string) => any // Proxy<unknown>
|
|
49
50
|
} & QLExtensions
|
|
50
51
|
|
|
51
|
-
type Proxy<T> = (T extends Subqueryable<infer U>
|
|
52
|
+
export type Proxy<T> = (T extends Subqueryable<infer U>
|
|
52
53
|
? (Omit<T, ""> & Subqueryable<Proxy<U>>) // drop ((x: T) => T) in favour of (x: Proxy<T>) => Proxy<T>)
|
|
53
54
|
: (Proxy_<T>))
|
|
54
55
|
& QLExtensions
|
|
55
56
|
|
|
56
|
-
// any class (not value) of array to represent plural types used in cds-typer.
|
|
57
|
-
// Mainly used as pattern match for SingularType
|
|
58
|
-
//type ArrayConstructable = Constructable<Array<unknown>>
|
|
59
|
-
interface ArrayConstructable<T> {
|
|
60
|
-
new(...args: any[]): T
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// concrete singular type.
|
|
64
|
-
// `SingularType<Books>` == `Book`.
|
|
65
|
-
type SingularType<T extends ArrayConstructable<T>> = InstanceType<T>[number]
|
|
66
|
-
|
|
67
57
|
// Alias for projections
|
|
68
58
|
// https://cap.cloud.sap/docs/node.js/cds-ql?q=projection#projection-functions
|
|
69
|
-
type Projection<T> = (e:T)=>void
|
|
59
|
+
export type Projection<T> = (e:T)=>void
|
|
70
60
|
|
|
71
61
|
// Type for query pieces that can either be chained to build more complex queries or
|
|
72
62
|
// awaited to materialise the result:
|
|
@@ -80,7 +70,7 @@ type Projection<T> = (e:T)=>void
|
|
|
80
70
|
// `Awaitable<T> = T extends unknown<infer I> ? (T & Promise<I>) : never`
|
|
81
71
|
// (at the time of writing, infering the first generic parameter of ANY type
|
|
82
72
|
// does not seem to be possible.)
|
|
83
|
-
type Awaitable<T, I> = T & Promise<I>
|
|
73
|
+
export type Awaitable<T, I> = T & Promise<I>
|
|
84
74
|
|
|
85
75
|
// all the functionality of an instance of SELECT, but directly callable:
|
|
86
76
|
// new SELECT(...).(...) == SELECT(...)
|
|
@@ -91,7 +81,7 @@ export type StaticSELECT<T> = typeof SELECT
|
|
|
91
81
|
& SELECT_one // as it is not directly quantified, ...
|
|
92
82
|
& SELECT_from // ...we should expect both a scalar and a list
|
|
93
83
|
|
|
94
|
-
declare class QL {
|
|
84
|
+
declare class QL<T> {
|
|
95
85
|
SELECT : StaticSELECT<T>
|
|
96
86
|
INSERT : typeof INSERT
|
|
97
87
|
& ((...entries:object[]) => INSERT<any>) & ((entries:object[]) => INSERT<any>)
|
|
@@ -107,7 +97,7 @@ declare class QL {
|
|
|
107
97
|
// when run in strict mode.
|
|
108
98
|
// This signature has to be added to a method as intersection type.
|
|
109
99
|
// Defining overloads with it will override preceding signatures and the other way around.
|
|
110
|
-
type TaggedTemplateQueryPart<T> = (strings: TemplateStringsArray, ...params
|
|
100
|
+
type TaggedTemplateQueryPart<T> = (strings: TemplateStringsArray, ...params: unknown[]) => T
|
|
111
101
|
|
|
112
102
|
export class SELECT<T> extends ConstructedQuery {
|
|
113
103
|
static one : SELECT_one & { from: SELECT_one }
|
|
@@ -134,19 +124,32 @@ export class SELECT<T> extends ConstructedQuery {
|
|
|
134
124
|
& ((...expr : string[]) => this)
|
|
135
125
|
limit: TaggedTemplateQueryPart<this>
|
|
136
126
|
& ((rows : number, offset? : number) => this)
|
|
137
|
-
|
|
138
|
-
forUpdate () : this
|
|
127
|
+
forShareLock () : this
|
|
128
|
+
forUpdate ({wait}? : {wait?: number}) : this
|
|
129
|
+
|
|
130
|
+
// Not yet public
|
|
131
|
+
// fullJoin (other: string, as: string) : this
|
|
132
|
+
// leftJoin (other: string, as: string) : this
|
|
133
|
+
// rightJoin (other: string, as: string) : this
|
|
134
|
+
// innerJoin (other: string, as: string) : this
|
|
135
|
+
// join (other: string, as: string, kind?: string) : this
|
|
136
|
+
// on : TaggedTemplateQueryPart<this>
|
|
137
|
+
// & ((...expr : string[]) => this)
|
|
138
|
+
// & ((predicate:object) => this)
|
|
139
|
+
|
|
139
140
|
SELECT : CQN.SELECT
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
|
|
143
144
|
type SELECT_one =
|
|
145
|
+
TaggedTemplateQueryPart<Awaitable<SELECT<unknown>, InstanceType<any>>>
|
|
146
|
+
&
|
|
144
147
|
// calling with class
|
|
145
|
-
(<T extends ArrayConstructable
|
|
148
|
+
(<T extends ArrayConstructable<any>>
|
|
146
149
|
(entityType: T, projection?: Projection<Proxy<SingularType<T>>>)
|
|
147
150
|
=> Awaitable<SELECT<SingularType<T>>, SingularType<T>>)
|
|
148
151
|
&
|
|
149
|
-
(<T extends ArrayConstructable
|
|
152
|
+
(<T extends ArrayConstructable<any>>
|
|
150
153
|
(entityType: T, primaryKey : PK, projection?: Projection<Proxy<SingularType<T>>>)
|
|
151
154
|
=> Awaitable<SELECT<SingularType<T>>, SingularType<T>>)
|
|
152
155
|
|
|
@@ -158,14 +161,14 @@ type SELECT_one =
|
|
|
158
161
|
|
|
159
162
|
type SELECT_from =
|
|
160
163
|
// tagged template
|
|
161
|
-
TaggedTemplateQueryPart<Awaitable<SELECT<unknown>, InstanceType<
|
|
164
|
+
TaggedTemplateQueryPart<Awaitable<SELECT<unknown>, InstanceType<any>>>
|
|
162
165
|
&
|
|
163
166
|
// calling with class
|
|
164
|
-
(<T extends ArrayConstructable
|
|
167
|
+
(<T extends ArrayConstructable<any>>
|
|
165
168
|
(entityType: T, projection?: Projection<Proxy<SingularType<T>>>)
|
|
166
169
|
=> Awaitable<SELECT<T>, InstanceType<T>>)
|
|
167
170
|
&
|
|
168
|
-
(<T extends ArrayConstructable
|
|
171
|
+
(<T extends ArrayConstructable<any>>
|
|
169
172
|
(entityType: T, primaryKey : PK, projection?: Projection<SingularType<T>>)
|
|
170
173
|
=> Awaitable<SELECT<T>, InstanceType<T>>)
|
|
171
174
|
// calling with definition
|
|
@@ -176,16 +179,15 @@ type SELECT_from =
|
|
|
176
179
|
|
|
177
180
|
|
|
178
181
|
export class INSERT<T> extends ConstructedQuery {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
into (entity : Definition | string) : this
|
|
182
|
+
static into : (<T extends ArrayConstructable<any>> (entity:T, entries? : object | object[]) => INSERT<SingularType<T>>)
|
|
183
|
+
& (TaggedTemplateQueryPart<INSERT<unknown>>)
|
|
184
|
+
& ((entity : Definition | string, entries? : object | object[]) => INSERT<any>)
|
|
185
|
+
& (<T> (entity:Constructable<T>, entries? : object | object[]) => INSERT<T>)
|
|
186
|
+
& (<T> (entity:T, entries? : T | object | object[]) => INSERT<T>)
|
|
187
|
+
|
|
188
|
+
into: (<T extends ArrayConstructable> (entity:T) => this)
|
|
189
|
+
& TaggedTemplateQueryPart<this>
|
|
190
|
+
& ((entity : Definition | string) => this)
|
|
189
191
|
data (block : (e:T)=>void) : this
|
|
190
192
|
entries (...entries : object[]) : this
|
|
191
193
|
columns (...col: string[]) : this
|
|
@@ -195,7 +197,9 @@ export class INSERT<T> extends ConstructedQuery {
|
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
export class DELETE<T> extends ConstructedQuery {
|
|
198
|
-
static from
|
|
200
|
+
static from:
|
|
201
|
+
TaggedTemplateQueryPart<Awaitable<SELECT<unknown>, InstanceType<any>>>
|
|
202
|
+
& ((entity : Definition | string | ArrayConstructable, primaryKey? : PK) => DELETE<any>)
|
|
199
203
|
byKey (primaryKey? : PK) : this
|
|
200
204
|
where (predicate:object) : this
|
|
201
205
|
where (...expr : any[]) : this
|
|
@@ -206,7 +210,7 @@ export class DELETE<T> extends ConstructedQuery {
|
|
|
206
210
|
|
|
207
211
|
export class UPDATE<T> extends ConstructedQuery {
|
|
208
212
|
// cds-typer plural
|
|
209
|
-
static entity <T extends ArrayConstructable
|
|
213
|
+
static entity <T extends ArrayConstructable<any>> (entity:T, primaryKey? : PK) : UPDATE<SingularType<T>>
|
|
210
214
|
|
|
211
215
|
static entity (entity : Definition | string, primaryKey? : PK) : UPDATE<any>
|
|
212
216
|
static entity <T> (entity:Constructable<T>, primaryKey? : PK) : UPDATE<T>
|
package/apis/services.d.ts
CHANGED
|
@@ -1,34 +1,50 @@
|
|
|
1
1
|
import { SELECT, INSERT, UPDATE, DELETE, Query, ConstructedQuery } from './ql'
|
|
2
|
+
import { Projection, Proxy, Awaitable } from './ql'
|
|
3
|
+
import { SingularType, ArrayConstructable } from './internal/inference'
|
|
2
4
|
import { LinkedModel, Definition, Definitions } from './reflect'
|
|
3
5
|
import { csn, type } from "./csn"
|
|
4
6
|
// import { Service } from './cds'
|
|
5
7
|
|
|
6
|
-
|
|
7
8
|
export class QueryAPI {
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run)
|
|
11
12
|
*/
|
|
12
|
-
read
|
|
13
|
+
read: {
|
|
14
|
+
<T extends ArrayConstructable<any>>(entity : T, key?: any) : Awaitable<SELECT<T>, InstanceType<T>>
|
|
15
|
+
<T>(entity : Definition | string, key?: any) : SELECT<T>
|
|
16
|
+
}
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run)
|
|
16
20
|
*/
|
|
17
|
-
create
|
|
21
|
+
create: {
|
|
22
|
+
<T extends ArrayConstructable<any>>(entity : T, key?: any) : INSERT<T>
|
|
23
|
+
<T>(entity : Definition | string, key?: any) : INSERT<T>
|
|
24
|
+
}
|
|
18
25
|
|
|
19
26
|
/**
|
|
20
27
|
* @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run)
|
|
21
28
|
*/
|
|
22
|
-
insert
|
|
29
|
+
insert: {
|
|
30
|
+
<T extends ArrayConstructable<any>>(data : T) : INSERT<T>
|
|
31
|
+
<T>(data : object | object[]) : INSERT<T>
|
|
32
|
+
}
|
|
23
33
|
|
|
24
34
|
/**
|
|
25
35
|
* @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run)
|
|
26
36
|
*/
|
|
27
|
-
update
|
|
37
|
+
update: {
|
|
38
|
+
<T extends ArrayConstructable<any>>(entity : T, key?: any) : UPDATE<T>
|
|
39
|
+
<T>(entity : Definition | string, key?: any) : UPDATE<T>
|
|
40
|
+
}
|
|
28
41
|
|
|
29
42
|
/**
|
|
30
43
|
* @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run)
|
|
31
44
|
*/
|
|
45
|
+
// as delete is the only one of the CRUD methods from QueryAPI
|
|
46
|
+
// that is extended in Service, we have to add the second signature down there
|
|
47
|
+
// (TS error 2425)
|
|
32
48
|
delete <T>(entity : Definition | string, key?: any) : DELETE<T>
|
|
33
49
|
|
|
34
50
|
/**
|
|
@@ -44,7 +60,7 @@ export class QueryAPI {
|
|
|
44
60
|
/**
|
|
45
61
|
* @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run-sql)
|
|
46
62
|
*/
|
|
47
|
-
|
|
63
|
+
run (query : string, args? : any[]|object) : Promise<ResultSet | any>
|
|
48
64
|
|
|
49
65
|
/**
|
|
50
66
|
* @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run)
|
|
@@ -184,6 +200,7 @@ export class Service extends QueryAPI {
|
|
|
184
200
|
/**
|
|
185
201
|
* @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run)
|
|
186
202
|
*/
|
|
203
|
+
delete <T extends ArrayConstructable<any>>(entity : T, key?: any): DELETE<T>
|
|
187
204
|
delete <T>(entity : Definition | string, key?: any) : DELETE<T>
|
|
188
205
|
|
|
189
206
|
// The central method to dispatch events
|
|
@@ -117,7 +117,7 @@ class BuildTaskHandler {
|
|
|
117
117
|
// relative to build task's destination path
|
|
118
118
|
dest = path.resolve(this.task.dest, dest)
|
|
119
119
|
}
|
|
120
|
-
this.
|
|
120
|
+
this.pushFile(dest)
|
|
121
121
|
if (this._hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_DEFAULT)) {
|
|
122
122
|
await fs.mkdir(path.dirname(dest), { recursive: true })
|
|
123
123
|
await fs.writeFile(dest, typeof data === "object" && !Buffer.isBuffer(data) ? JSON.stringify(data, null, 2) : data)
|
|
@@ -151,7 +151,7 @@ class BuildTaskHandler {
|
|
|
151
151
|
// relative to build task's destination path
|
|
152
152
|
dest = path.resolve(this.task.dest, dest)
|
|
153
153
|
}
|
|
154
|
-
this.
|
|
154
|
+
this.pushFile(dest)
|
|
155
155
|
if (this._hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_DEFAULT)) {
|
|
156
156
|
if (fs.cp) { // Node.js >= 16.7
|
|
157
157
|
return fs.cp(src, dest, { recursive: true })
|
|
@@ -205,7 +205,7 @@ class BuildTaskHandler {
|
|
|
205
205
|
* Adds the given fully qualified file path to the list of files that are written by this build task.
|
|
206
206
|
* @param {string} filePath
|
|
207
207
|
*/
|
|
208
|
-
|
|
208
|
+
pushFile(filePath) {
|
|
209
209
|
this._written.add(filePath)
|
|
210
210
|
}
|
|
211
211
|
/**
|
|
@@ -26,7 +26,7 @@ class BuildTaskHandlerEdmx extends BuildTaskHandlerFeatureToggles {
|
|
|
26
26
|
const result = this.cds.compile.to.edmx(model, options)
|
|
27
27
|
|
|
28
28
|
if (result) {
|
|
29
|
-
let langs = this.task.options.lang || this.
|
|
29
|
+
let langs = this.task.options.lang || this.env.i18n.languages
|
|
30
30
|
if (langs.split) { // string to array
|
|
31
31
|
langs = langs.split(',')
|
|
32
32
|
}
|
|
@@ -43,8 +43,9 @@ class FeatureToggleBuilder extends BuildTaskHandlerInternal {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
async collectAllLanguageBundles(dictionary, paths, destBase, destFts) {
|
|
46
|
+
const i18nFolder = this.env.i18n.folders?.[0] || 'i18n'
|
|
46
47
|
// create language bundle for base model
|
|
47
|
-
const i18n = await this.collectLanguageBundles(dictionary.base, destBase)
|
|
48
|
+
const i18n = await this.collectLanguageBundles(dictionary.base, path.join(destBase, i18nFolder))
|
|
48
49
|
if (i18n) {
|
|
49
50
|
this._result.languageBundles = i18n.bundles
|
|
50
51
|
}
|
|
@@ -54,7 +55,7 @@ class FeatureToggleBuilder extends BuildTaskHandlerInternal {
|
|
|
54
55
|
for (const ftName in dictionary.features) {
|
|
55
56
|
// attach the sources information for i18n location reference
|
|
56
57
|
dictionary.features[ftName]['$sources'] = paths.features[ftName]
|
|
57
|
-
await this.collectLanguageBundles(dictionary.features[ftName], path.join(destFts, this.ftsName, ftName))
|
|
58
|
+
await this.collectLanguageBundles(dictionary.features[ftName], path.join(destFts, this.ftsName, ftName, i18nFolder))
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -108,7 +109,7 @@ class FeatureToggleBuilder extends BuildTaskHandlerInternal {
|
|
|
108
109
|
|
|
109
110
|
// replace require paths by base model path to ensure precedence of feature annotations
|
|
110
111
|
// see https://pages.github.tools.sap/cap/docs/cds/compiler-messages#anno-duplicate-unrelated-layer
|
|
111
|
-
ftCsn.requires = [path.join(path.relative(ftPath, destBase), DEFAULT_CSN_FILE_NAME).replace(/\\/g,'/')]
|
|
112
|
+
ftCsn.requires = [path.join(path.relative(ftPath, destBase), DEFAULT_CSN_FILE_NAME).replace(/\\/g, '/')]
|
|
112
113
|
|
|
113
114
|
await this.compileToJson(ftCsn, path.join(ftPath, DEFAULT_CSN_FILE_NAME))
|
|
114
115
|
await this._validateFeature(ftPath)
|
|
@@ -204,8 +204,8 @@ class BuildTaskHandlerInternal extends BuildTaskHandler {
|
|
|
204
204
|
bundles = {}
|
|
205
205
|
}
|
|
206
206
|
// copied from ../compile/i18n.js
|
|
207
|
-
const {
|
|
208
|
-
const file = path.join(bundleDest,
|
|
207
|
+
const { file: base = 'i18n' } = this.env.i18n
|
|
208
|
+
const file = path.join(bundleDest, base + '.json')
|
|
209
209
|
|
|
210
210
|
// bundleDest might be null
|
|
211
211
|
if (bundleDest && Object.keys(bundles).length > 0) {
|
|
@@ -48,7 +48,8 @@ class JavaModuleBuilder extends BuildTaskHandlerEdmx {
|
|
|
48
48
|
|
|
49
49
|
if (this.hasBuildOption(CONTENT_LANGUAGE_BUNDLES, true)) {
|
|
50
50
|
// collect and write language bundles into single i18n.json file
|
|
51
|
-
const
|
|
51
|
+
const i18nFolder = this.env.i18n.folders?.[0] || 'i18n'
|
|
52
|
+
const i18n = await this.collectLanguageBundles(model, path.join(this.task.dest, i18nFolder))
|
|
52
53
|
if (i18n) {
|
|
53
54
|
this._result.languageBundles = i18n.bundles
|
|
54
55
|
}
|
|
@@ -96,7 +96,8 @@ class ClassicMtxBuilder {
|
|
|
96
96
|
}))
|
|
97
97
|
|
|
98
98
|
// collect and write language bundles into single i18n.json file
|
|
99
|
-
const
|
|
99
|
+
const i18nFolder = this._handler.env.i18n.folders?.[0] || 'i18n'
|
|
100
|
+
const i18n = await this._handler.collectLanguageBundles(model, path.join(destSdc, i18nFolder))
|
|
100
101
|
if (i18n) {
|
|
101
102
|
this._handler._result.languageBundles = i18n.bundles
|
|
102
103
|
}
|
|
@@ -15,16 +15,17 @@ class ResourcesTarBuilder {
|
|
|
15
15
|
async createTar(dest, model) {
|
|
16
16
|
const { root, files } = await this._getResources(model)
|
|
17
17
|
if (files.length === 0) {
|
|
18
|
-
// packTarArchive
|
|
18
|
+
// packTarArchive fails otherwise
|
|
19
19
|
this.handler.pushMessage("No deployment resources found - skip resources.tgz", WARNING)
|
|
20
20
|
return
|
|
21
21
|
}
|
|
22
22
|
await this.writeTarFile(files, root, path.join(dest, DEFAULT_TAR_NAME))
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
async writeTarFile(files, root, tarFile) {
|
|
25
|
+
async writeTarFile(files, root, tarFile) {
|
|
26
26
|
const { tar } = require('../../../../lib').utils
|
|
27
27
|
await tar.czfd (tarFile, root, files) // REVISIT: tar.czfd was created for this case only -> it ensures the target's dir exists
|
|
28
|
+
this.handler.pushFile(tarFile)
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
async _getResources(model) {
|
|
@@ -33,7 +33,8 @@ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
33
33
|
await this.compileToJson(extCsn, csnFile)
|
|
34
34
|
allFiles.push(csnFile)
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// static i18n folder name as runtime does not use the CDS config of the extension project
|
|
37
|
+
const i18n = await this.collectLanguageBundles(extCsn, path.join(destExt, 'i18n'))
|
|
37
38
|
if (i18n) {
|
|
38
39
|
allFiles.push(i18n.file)
|
|
39
40
|
}
|
|
@@ -44,9 +44,11 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
|
|
|
44
44
|
async _buildNodeApp(sidecarEnv) {
|
|
45
45
|
const destSidecar = this.task.dest
|
|
46
46
|
const destSidecarSrc = path.join(destSidecar, this.env.folders.srv)
|
|
47
|
+
const i18nFolder = this.env.i18n.folders?.[0] || 'i18n'
|
|
47
48
|
const model = this._compileSidecarSync(sidecarEnv)
|
|
48
49
|
await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME))
|
|
49
|
-
|
|
50
|
+
|
|
51
|
+
await this.collectLanguageBundles(model, path.join(destSidecarSrc, i18nFolder))
|
|
50
52
|
await this.copyProjectRootContent(this.task.src, destSidecar)
|
|
51
53
|
}
|
|
52
54
|
|
package/lib/auth/index.js
CHANGED
|
@@ -25,7 +25,8 @@ module.exports = lazified (Object.assign (auth_factory, {
|
|
|
25
25
|
basic: require('./basic-auth'),
|
|
26
26
|
dummy: require('./dummy-auth'),
|
|
27
27
|
ias: require('./ias-auth'),
|
|
28
|
-
|
|
28
|
+
jwt: require('./jwt-auth'),
|
|
29
|
+
xsuaa: require('./jwt-auth'),
|
|
29
30
|
}))
|
|
30
31
|
|
|
31
32
|
require = _require // eslint-disable-line no-global-assign
|
package/lib/auth/jwt-auth.js
CHANGED
|
@@ -1,3 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const cds = require('../')
|
|
2
|
+
const _require = require('../../libx/_runtime/common/utils/require')
|
|
3
|
+
// _require for better error message
|
|
4
|
+
const express = _require('express')
|
|
5
|
+
const passport = _require('passport')
|
|
6
|
+
const { JWTStrategy } = _require('@sap/xssec')
|
|
7
|
+
const LOG = cds.log('auth')
|
|
8
|
+
|
|
9
|
+
module.exports = function jwt_auth(config) {
|
|
10
|
+
// warn if no credentials
|
|
11
|
+
if (!config.credentials) {
|
|
12
|
+
LOG._warn &&
|
|
13
|
+
LOG.warn(`
|
|
14
|
+
No XSUAA instance bound to application, but "${config.kind}" configured.
|
|
15
|
+
This is NOT recommended in production!
|
|
16
|
+
`)
|
|
17
|
+
|
|
18
|
+
return (req,res,next) => next()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
passport.use(config.kind, new JWTStrategy(config.credentials))
|
|
22
|
+
return express
|
|
23
|
+
.Router()
|
|
24
|
+
.use(passport.authenticate(config.kind, { session: false }))
|
|
25
|
+
.use((req, res, next) => {
|
|
26
|
+
const payload = req.tokenInfo.getPayload()
|
|
27
|
+
|
|
28
|
+
let id = req.user.id
|
|
29
|
+
|
|
30
|
+
let roles = payload.scope.map(s => s.replace(new RegExp(`^(${config.credentials.xsappname + '.'})`), ''))
|
|
31
|
+
roles.push('identified-user')
|
|
32
|
+
if (payload.grant_type) {
|
|
33
|
+
// > not "weak"
|
|
34
|
+
roles.push('authenticated-user')
|
|
35
|
+
|
|
36
|
+
const CLIENT = { client_credentials: 1, client_x509: 1 }
|
|
37
|
+
if (payload.grant_type in CLIENT) {
|
|
38
|
+
id = 'system'
|
|
39
|
+
roles.push('system-user')
|
|
40
|
+
if (req.tokenInfo.getClientId() === config.credentials.clientid) roles.push('internal-user')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let attr = req.authInfo.getAttributes() || {}
|
|
45
|
+
if (config.kind === 'xsuaa') {
|
|
46
|
+
attr.logonName = req.authInfo.getLogonName()
|
|
47
|
+
attr.givenName = req.authInfo.getGivenName()
|
|
48
|
+
attr.familyName = req.authInfo.getFamilyName()
|
|
49
|
+
attr.email = req.authInfo.getEmail()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
req.user = new cds.User({ id, roles, attr })
|
|
53
|
+
req.tenant = req.tokenInfo.getZoneId?.()
|
|
54
|
+
next()
|
|
55
|
+
})
|
|
56
|
+
.use((err, req, res, next) => {
|
|
57
|
+
if (req.tokenInfo) {
|
|
58
|
+
LOG?.debug('error during token validation', req.tokenInfo.getErrorObject())
|
|
59
|
+
}
|
|
60
|
+
// REVISIT: reject request immediately as our other auth strategies do
|
|
61
|
+
// should we call next(err)? -> I don't think so; it's not an error, is it?
|
|
62
|
+
res.status(401).json({ code: '401', message: 'Unauthorized' }) // REVISIT: this is OData style?
|
|
63
|
+
})
|
|
64
|
+
}
|
package/lib/auth/xsuaa-auth.js
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
module.exports = ()=> (req,res,next)=> next()
|
|
1
|
+
// REVISIT: "kind": "xsuaa-auth" does not work because cds.requires logic does not resolve it. Intentional?
|
|
2
|
+
module.exports = require('./jwt-auth')
|
package/lib/compile/cdsc.js
CHANGED
|
@@ -103,6 +103,7 @@ const _options = {for: Object.assign (_options4, {
|
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Return a derivate of cdsc, with the most prominent
|
|
106
|
+
* @type { import('@sap/cds-compiler') }
|
|
106
107
|
*/
|
|
107
108
|
module.exports = exports = {__proto__:compile, _options,
|
|
108
109
|
for: {__proto__: compile.for,
|
package/lib/dbs/cds-deploy.js
CHANGED
|
@@ -143,7 +143,6 @@ exports.init = (db, csn=db.model, log=()=>{}) => db.run (async tx => {
|
|
|
143
143
|
if (db.kind === 'better-sqlite') _add_missing_pks2(q)
|
|
144
144
|
log (file,e)
|
|
145
145
|
inits.push (tx.run(q) .catch (e => {
|
|
146
|
-
Error.captureStackTrace(e)
|
|
147
146
|
throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ inspect(q) })
|
|
148
147
|
}))
|
|
149
148
|
}
|
|
@@ -232,11 +231,13 @@ const _entity4 = (file,csn) => {
|
|
|
232
231
|
const INSERT_from_csv = (entity, csv) => {
|
|
233
232
|
let [ cols, ...rows ] = cds.parse.csv (csv)
|
|
234
233
|
if (rows.length > 0) return INSERT.into (entity) .columns (cols) .rows (rows)
|
|
234
|
+
// if (rows.length > 0) return UPSERT.into (entity) .columns (cols) .rows (rows)
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
const INSERT_from_json = (entity, json) => {
|
|
238
238
|
let records = JSON.parse (json)
|
|
239
239
|
if (records.length > 0) return INSERT.into (entity) .entries (records)
|
|
240
|
+
// if (records.length > 0) return UPSERT.into (entity) .entries (records)
|
|
240
241
|
}
|
|
241
242
|
|
|
242
243
|
const _from_csv_or_json = { '.json': INSERT_from_json, '.csv': INSERT_from_csv, }
|