@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.
Files changed (67) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/apis/connect.d.ts +1 -1
  3. package/apis/cqn.d.ts +1 -1
  4. package/apis/internal/inference.d.ts +14 -0
  5. package/apis/ql.d.ts +40 -36
  6. package/apis/services.d.ts +23 -6
  7. package/bin/build/buildTaskHandler.js +3 -3
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +1 -1
  9. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +4 -3
  10. package/bin/build/provider/buildTaskHandlerInternal.js +2 -2
  11. package/bin/build/provider/java/index.js +2 -1
  12. package/bin/build/provider/mtx/index.js +2 -1
  13. package/bin/build/provider/mtx/resourcesTarBuilder.js +3 -2
  14. package/bin/build/provider/mtx-extension/index.js +2 -1
  15. package/bin/build/provider/mtx-sidecar/index.js +3 -1
  16. package/lib/auth/index.js +2 -1
  17. package/lib/auth/jwt-auth.js +64 -3
  18. package/lib/auth/xsuaa-auth.js +2 -3
  19. package/lib/compile/cdsc.js +1 -0
  20. package/lib/compile/etc/_localized.js +1 -0
  21. package/lib/dbs/cds-deploy.js +2 -1
  22. package/lib/env/cds-env.js +14 -49
  23. package/lib/env/cds-requires.js +13 -7
  24. package/lib/env/defaults.js +4 -0
  25. package/lib/i18n/localize.js +11 -8
  26. package/lib/index.js +1 -1
  27. package/lib/log/cds-log.js +2 -2
  28. package/lib/log/format/cf.js +16 -0
  29. package/lib/log/format/kibana.js +15 -2
  30. package/lib/ql/INSERT.js +12 -11
  31. package/lib/ql/Query.js +14 -7
  32. package/lib/ql/UPSERT.js +1 -0
  33. package/lib/ql/Whereable.js +6 -2
  34. package/lib/ql/cds-ql.js +2 -4
  35. package/lib/req/request.js +2 -0
  36. package/lib/srv/middlewares/cds-context.js +1 -1
  37. package/lib/srv/srv-dispatch.js +1 -0
  38. package/lib/srv/srv-tx.js +3 -3
  39. package/lib/utils/cds-utils.js +75 -30
  40. package/lib/utils/inflect.js +24 -0
  41. package/libx/_runtime/auth/strategies/ias-auth.js +1 -1
  42. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +9 -1
  43. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +23 -6
  44. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -0
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +27 -15
  46. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -1
  47. package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -10
  48. package/libx/_runtime/cds-services/services/utils/differ.js +6 -4
  49. package/libx/_runtime/common/composition/data.js +29 -40
  50. package/libx/_runtime/common/composition/update.js +6 -19
  51. package/libx/_runtime/common/generic/paging.js +1 -1
  52. package/libx/_runtime/common/utils/resolveView.js +7 -13
  53. package/libx/_runtime/db/utils/generateAliases.js +1 -0
  54. package/libx/_runtime/fiori/generic/read.js +11 -4
  55. package/libx/_runtime/hana/execute.js +2 -2
  56. package/libx/_runtime/hana/search2cqn4sql.js +1 -0
  57. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  58. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +5 -2
  59. package/libx/_runtime/messaging/enterprise-messaging.js +7 -1
  60. package/libx/_runtime/messaging/file-based.js +1 -1
  61. package/libx/_runtime/messaging/message-queuing.js +5 -2
  62. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  63. package/libx/_runtime/messaging/service.js +5 -3
  64. package/libx/odata/cqn2odata.js +4 -1
  65. package/libx/odata/utils.js +8 -7
  66. package/libx/rest/RestAdapter.js +1 -4
  67. 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
@@ -11,7 +11,7 @@ export type SELECT = {SELECT:{
11
11
  having? : predicate
12
12
  groupBy? : expr[]
13
13
  orderBy? : ordering_term[]
14
- limit?: { rows:number, offset:number }
14
+ limit?: { rows:val, offset:val }
15
15
  }}
16
16
 
17
17
  export type INSERT = {INSERT:{
@@ -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?: unknown[]) => T
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
- forSharedLock () : this
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<unknown>>>
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
- // cds-typer plural
180
- static into <T extends ArrayConstructable> (entity:T, entries? : object | object[]) : INSERT<SingularType<T>>
181
- into <T extends ArrayConstructable> (entity:T) : this
182
-
183
- static into: TaggedTemplateQueryPart<INSERT<T>>
184
- static into (entity : Definition | string, entries? : object | object[]) : INSERT<any>
185
- static into <T> (entity:Constructable<T>, entries? : object | object[]) : INSERT<T>
186
- static into <T> (entity:T, entries? : T | object | object[]) : INSERT<T>
187
- into: TaggedTemplateQueryPart<this>
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 (entity : Definition | string | ArrayConstructable, primaryKey? : PK) : DELETE<any>
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> (entity:T, primaryKey? : PK) : UPDATE<SingularType<T>>
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>
@@ -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 <T>(entity : Definition | string, key?: any) : SELECT<T>
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 <T>(entity : Definition | string, key?: any) : INSERT<T>
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 <T>(data : object | object[]) : INSERT<T>
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 <T>(entity : Definition | string, key?: any) : UPDATE<T>
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
- run (query : string, args? : any[]|object) : Promise<ResultSet | any>
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._pushFile(dest)
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._pushFile(dest)
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
- _pushFile(filePath) {
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.cds.env.i18n.languages
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 { folders = ['i18n'], file: base = 'i18n' } = this.env.i18n
208
- const file = path.join(bundleDest, folders[0], base + '.json')
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 i18n = await this.collectLanguageBundles(model, this.task.dest)
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 i18n = await this._handler.collectLanguageBundles(model, destSdc)
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 failes otherwise
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) { // REVISIT: eliminate this anti-pattern helper
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
- const i18n = await this.collectLanguageBundles(extCsn, destExt)
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
- await this.collectLanguageBundles(model, destSidecarSrc)
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
- xsuaa: require('./xsuaa-auth'),
28
+ jwt: require('./jwt-auth'),
29
+ xsuaa: require('./jwt-auth'),
29
30
  }))
30
31
 
31
32
  require = _require // eslint-disable-line no-global-assign
@@ -1,3 +1,64 @@
1
- // TODO...
2
- console.trace ('JWT auth is not yet implemented')
3
- module.exports = ()=> (req,res,next)=> next()
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
+ }
@@ -1,3 +1,2 @@
1
- // TODO...
2
- console.trace ('XSUAA auth is not yet implemented')
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')
@@ -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,
@@ -2,6 +2,7 @@ const cds = require('../..'), {env} = cds
2
2
  const DEBUG = cds.debug('alpha|_localized')
3
3
  const _locales_4sql = {
4
4
  sqlite : env.i18n.for_sqlite || env.i18n.for_sql || [],
5
+ h2 : env.i18n.for_sql || [],
5
6
  plain : env.i18n.for_sql || [],
6
7
  }
7
8
 
@@ -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, }