@sap/cds 5.8.2 → 5.9.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 (252) hide show
  1. package/CHANGELOG.md +214 -78
  2. package/app/fiori/preview.js +16 -11
  3. package/app/fiori/routes.js +3 -0
  4. package/app/index.js +1 -1
  5. package/bin/build/buildTaskFactory.js +3 -3
  6. package/bin/build/buildTaskProviderFactory.js +1 -1
  7. package/bin/build/constants.js +1 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
  9. package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
  10. package/bin/build/provider/buildTaskProviderInternal.js +8 -2
  11. package/bin/build/provider/hana/2migration.js +27 -24
  12. package/bin/build/provider/hana/index.js +17 -18
  13. package/bin/build/provider/hana/migrationtable.js +9 -10
  14. package/bin/build/provider/java-cf/index.js +4 -5
  15. package/bin/build/provider/node-cf/index.js +99 -6
  16. package/bin/cds.js +20 -17
  17. package/bin/deploy/to-hana/cfUtil.js +16 -19
  18. package/bin/deploy/to-hana/hana.js +7 -24
  19. package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
  20. package/bin/mtx/in-cds.js +2 -2
  21. package/bin/serve.js +12 -5
  22. package/bin/utils/modules.js +7 -0
  23. package/bin/version.js +56 -3
  24. package/lib/compile/cdsc.js +26 -3
  25. package/lib/compile/etc/_localized.js +36 -25
  26. package/lib/compile/etc/csv.js +8 -8
  27. package/lib/compile/for/drafts.js +9 -0
  28. package/lib/compile/for/java.js +16 -0
  29. package/lib/compile/for/nodejs.js +12 -0
  30. package/lib/compile/for/odata.js +1 -1
  31. package/lib/compile/index.js +3 -0
  32. package/lib/compile/minify.js +16 -2
  33. package/lib/compile/parse.js +2 -2
  34. package/lib/compile/resolve.js +35 -18
  35. package/lib/compile/to/json.js +3 -1
  36. package/lib/compile/to/sql.js +2 -2
  37. package/lib/compile/to/srvinfo.js +4 -2
  38. package/lib/connect/index.js +1 -1
  39. package/lib/core/entities.js +15 -14
  40. package/lib/core/index.js +39 -36
  41. package/lib/core/reflect.js +4 -2
  42. package/lib/deploy.js +114 -127
  43. package/lib/env/defaults.js +1 -0
  44. package/lib/env/index.js +165 -165
  45. package/lib/env/presets.js +1 -0
  46. package/lib/env/requires.js +120 -49
  47. package/lib/index.js +1 -0
  48. package/lib/log/format/kibana.js +2 -2
  49. package/lib/ql/SELECT.js +10 -0
  50. package/lib/ql/parse.js +1 -0
  51. package/lib/req/cds-context.js +4 -1
  52. package/lib/req/context.js +50 -56
  53. package/lib/req/event.js +1 -6
  54. package/lib/req/locale.js +6 -5
  55. package/lib/req/request.js +2 -0
  56. package/lib/req/user.js +7 -5
  57. package/lib/serve/Service-api.js +10 -7
  58. package/lib/serve/Service-dispatch.js +9 -11
  59. package/lib/serve/Service-methods.js +30 -41
  60. package/lib/serve/Transaction.js +10 -7
  61. package/lib/serve/adapters.js +7 -5
  62. package/lib/serve/index.js +24 -12
  63. package/lib/utils/data.js +1 -1
  64. package/lib/utils/index.js +27 -30
  65. package/lib/utils/resources/index.js +101 -0
  66. package/lib/utils/resources/tar.js +71 -0
  67. package/lib/utils/resources/utils.js +11 -0
  68. package/libx/_runtime/audit/Service.js +36 -39
  69. package/libx/_runtime/audit/generic/personal/access.js +3 -4
  70. package/libx/_runtime/audit/generic/personal/modification.js +3 -4
  71. package/libx/_runtime/audit/utils/v2.js +1 -2
  72. package/libx/_runtime/auth/index.js +126 -84
  73. package/libx/_runtime/auth/strategies/JWT.js +12 -19
  74. package/libx/_runtime/auth/strategies/dummy.js +1 -5
  75. package/libx/_runtime/auth/strategies/dwc.js +11 -9
  76. package/libx/_runtime/auth/strategies/mock.js +0 -4
  77. package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
  78. package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
  79. package/libx/_runtime/auth/utils.js +22 -1
  80. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
  81. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -2
  82. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +13 -0
  84. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
  85. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
  86. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  87. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
  88. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
  89. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
  90. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
  91. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
  92. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +1 -1
  93. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
  94. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -6
  95. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
  96. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +4 -1
  97. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
  99. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +50 -0
  100. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
  101. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
  102. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
  103. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
  104. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
  105. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
  106. package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
  107. package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
  108. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
  109. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
  110. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
  111. package/libx/_runtime/cds-services/services/Service.js +40 -0
  112. package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
  113. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
  114. package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
  115. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
  116. package/libx/_runtime/cds-services/services/utils/restrictions.js +78 -0
  117. package/libx/_runtime/cds-services/util/assert.js +20 -14
  118. package/libx/_runtime/cds.js +9 -1
  119. package/libx/_runtime/common/aspects/any.js +5 -0
  120. package/libx/_runtime/common/aspects/entity.js +25 -7
  121. package/libx/_runtime/common/aspects/utils.js +2 -2
  122. package/libx/_runtime/common/composition/data.js +6 -0
  123. package/libx/_runtime/common/composition/insert.js +3 -2
  124. package/libx/_runtime/common/composition/tree.js +4 -10
  125. package/libx/_runtime/common/composition/update.js +4 -4
  126. package/libx/_runtime/common/constants/draft.js +29 -26
  127. package/libx/_runtime/common/error/constants.js +2 -2
  128. package/libx/_runtime/common/error/frontend.js +7 -15
  129. package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
  130. package/libx/_runtime/common/generic/auth/constants.js +20 -0
  131. package/libx/_runtime/common/generic/auth/expand.js +54 -0
  132. package/libx/_runtime/common/generic/auth/index.js +32 -0
  133. package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
  134. package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
  135. package/libx/_runtime/common/generic/auth/requires.js +34 -0
  136. package/libx/_runtime/common/generic/auth/restrict.js +296 -0
  137. package/libx/_runtime/common/generic/auth/utils.js +213 -0
  138. package/libx/_runtime/common/generic/crud.js +14 -10
  139. package/libx/_runtime/common/generic/etag.js +1 -1
  140. package/libx/_runtime/common/generic/input.js +35 -35
  141. package/libx/_runtime/common/generic/sorting.js +2 -3
  142. package/libx/_runtime/common/generic/temporal.js +2 -2
  143. package/libx/_runtime/common/i18n/index.js +2 -31
  144. package/libx/_runtime/common/i18n/messages.properties +1 -1
  145. package/libx/_runtime/common/toggles/handler.js +21 -0
  146. package/libx/_runtime/common/utils/copy.js +10 -1
  147. package/libx/_runtime/common/utils/cqn2cqn4sql.js +100 -29
  148. package/libx/_runtime/common/utils/csn.js +63 -1
  149. package/libx/_runtime/common/utils/dollar.js +10 -1
  150. package/libx/_runtime/common/utils/draft.js +46 -7
  151. package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
  152. package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
  153. package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
  154. package/libx/_runtime/common/utils/generateOnCond.js +9 -6
  155. package/libx/_runtime/common/utils/quotingStyles.js +2 -0
  156. package/libx/_runtime/common/utils/resolveStructured.js +25 -9
  157. package/libx/_runtime/common/utils/resolveView.js +4 -1
  158. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
  159. package/libx/_runtime/common/utils/structured.js +33 -37
  160. package/libx/_runtime/common/utils/template.js +17 -8
  161. package/libx/_runtime/common/utils/templateProcessor.js +28 -28
  162. package/libx/_runtime/db/data-conversion/post-processing.js +118 -417
  163. package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
  164. package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
  165. package/libx/_runtime/db/generic/index.js +1 -3
  166. package/libx/_runtime/db/generic/input.js +5 -10
  167. package/libx/_runtime/db/generic/rewrite.js +5 -2
  168. package/libx/_runtime/db/generic/structured.js +2 -2
  169. package/libx/_runtime/db/query/delete.js +2 -2
  170. package/libx/_runtime/db/query/insert.js +1 -1
  171. package/libx/_runtime/db/query/update.js +9 -14
  172. package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
  173. package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
  174. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
  175. package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
  176. package/libx/_runtime/db/utils/columns.js +3 -3
  177. package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
  178. package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
  179. package/libx/_runtime/extensibility/mps/index.js +5 -0
  180. package/libx/_runtime/extensibility/mps/service.js +111 -0
  181. package/libx/_runtime/extensibility/mps/tar.js +42 -0
  182. package/libx/_runtime/extensibility/mps/utils.js +11 -0
  183. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
  184. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
  185. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
  186. package/libx/_runtime/extensibility/uiflex/index.js +54 -0
  187. package/libx/_runtime/extensibility/uiflex/service.js +276 -0
  188. package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
  189. package/libx/_runtime/fiori/generic/activate.js +2 -2
  190. package/libx/_runtime/fiori/generic/before.js +4 -4
  191. package/libx/_runtime/fiori/generic/new.js +3 -3
  192. package/libx/_runtime/fiori/generic/patch.js +1 -1
  193. package/libx/_runtime/fiori/generic/read.js +58 -66
  194. package/libx/_runtime/fiori/generic/readOverDraft.js +71 -16
  195. package/libx/_runtime/fiori/utils/handler.js +6 -13
  196. package/libx/_runtime/fiori/utils/where.js +6 -5
  197. package/libx/_runtime/hana/Service.js +4 -10
  198. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +2 -2
  199. package/libx/_runtime/hana/driver.js +2 -2
  200. package/libx/_runtime/hana/execute.js +29 -75
  201. package/libx/_runtime/hana/pool.js +1 -1
  202. package/libx/_runtime/hana/streaming.js +2 -1
  203. package/libx/_runtime/index.js +6 -6
  204. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
  205. package/libx/_runtime/messaging/Outbox.js +2 -2
  206. package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
  207. package/libx/_runtime/messaging/common-utils/connections.js +5 -7
  208. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
  209. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
  210. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
  211. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
  212. package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
  213. package/libx/_runtime/messaging/file-based.js +5 -5
  214. package/libx/_runtime/messaging/message-queuing.js +14 -12
  215. package/libx/_runtime/messaging/outbox/utils.js +18 -19
  216. package/libx/_runtime/messaging/redis-messaging.js +91 -0
  217. package/libx/_runtime/messaging/service.js +8 -6
  218. package/libx/_runtime/remote/Service.js +44 -8
  219. package/libx/_runtime/remote/utils/client.js +25 -13
  220. package/libx/_runtime/remote/utils/data.js +11 -11
  221. package/libx/_runtime/sqlite/Service.js +6 -9
  222. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
  223. package/libx/_runtime/types/api.js +10 -2
  224. package/libx/common/utils/ucsn.js +109 -0
  225. package/libx/gql/resolvers/crud/create.js +6 -1
  226. package/libx/gql/resolvers/crud/delete.js +6 -1
  227. package/libx/gql/resolvers/crud/read.js +6 -1
  228. package/libx/gql/resolvers/crud/update.js +9 -1
  229. package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
  230. package/libx/gql/schema/typeDefMap.js +2 -2
  231. package/libx/odata/afterburner.js +110 -16
  232. package/libx/odata/grammar.pegjs +9 -1
  233. package/libx/odata/parseToCqn.js +39 -0
  234. package/libx/odata/parser.js +1 -1
  235. package/libx/rest/RestAdapter.js +9 -1
  236. package/libx/rest/middleware/input.js +54 -0
  237. package/libx/rest/middleware/operation.js +14 -1
  238. package/libx/rest/middleware/parse.js +11 -7
  239. package/package.json +1 -1
  240. package/server.js +34 -19
  241. package/srv/audit-log.cds +2 -2
  242. package/srv/flex.cds +8 -2
  243. package/srv/flex.js +1 -1
  244. package/srv/mps.cds +23 -0
  245. package/srv/mps.js +1 -0
  246. package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
  247. package/libx/_runtime/common/generic/auth.js +0 -874
  248. package/libx/_runtime/common/toggles/alpha.js +0 -43
  249. package/libx/_runtime/db/generic/arrayed.js +0 -33
  250. package/libx/_runtime/fiori/uiflex/index.js +0 -35
  251. package/libx/_runtime/fiori/uiflex/service.js +0 -150
  252. package/libx/rest/utils/data.js +0 -60
@@ -1,18 +1,17 @@
1
- const cds = require('../cds.js')
2
1
  const AMQPWebhookMessaging = require('./AMQPWebhookMessaging')
3
2
  const AMQPClient = require('./common-utils/AMQPClient.js')
4
3
 
5
4
  const optionsMessaging = require('./message-queuing-utils/options-messaging.js')
6
5
  const optionsManagement = require('./message-queuing-utils/options-management.js')
7
6
  const authorizedRequest = require('./common-utils/authorizedRequest')
8
- const LOG = cds.log('messaging')
9
7
 
10
8
  class MQManagement {
11
- constructor({ options, queueConfig, queueName, subscribedTopics }) {
9
+ constructor({ options, queueConfig, queueName, subscribedTopics, LOG }) {
12
10
  this.options = options
13
11
  this.queueConfig = queueConfig
14
12
  this.queueName = queueName
15
13
  this.subscribedTopics = subscribedTopics
14
+ this.LOG = LOG
16
15
  }
17
16
 
18
17
  async getQueue(queueName = this.queueName) {
@@ -21,7 +20,7 @@ class MQManagement {
21
20
  uri: this.options.url,
22
21
  path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
23
22
  oa2: this.options.auth.oauth2,
24
- attemptInfo: () => LOG._info && LOG.info('Get queue', { queue: queueName }),
23
+ attemptInfo: () => this.LOG._info && this.LOG.info('Get queue', { queue: queueName }),
25
24
  errMsg: `Queue "${queueName}" could not be retrieved`,
26
25
  target: { kind: 'QUEUE', queue: queueName },
27
26
  tokenStore: this
@@ -35,7 +34,7 @@ class MQManagement {
35
34
  uri: this.options.url,
36
35
  path: `/v1/management/queues`,
37
36
  oa2: this.options.auth.oauth2,
38
- attemptInfo: () => LOG._info && LOG.info('Get queues'),
37
+ attemptInfo: () => this.LOG._info && this.LOG.info('Get queues'),
39
38
  errMsg: `Queues could not be retrieved`,
40
39
  target: { kind: 'QUEUE' },
41
40
  tokenStore: this
@@ -50,7 +49,7 @@ class MQManagement {
50
49
  path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
51
50
  oa2: this.options.auth.oauth2,
52
51
  dataObj: this.queueConfig,
53
- attemptInfo: () => LOG._info && LOG.info('Create queue', { queue: queueName }),
52
+ attemptInfo: () => this.LOG._info && this.LOG.info('Create queue', { queue: queueName }),
54
53
  errMsg: `Queue "${queueName}" could not be created`,
55
54
  target: { kind: 'QUEUE', queue: queueName },
56
55
  tokenStore: this
@@ -63,7 +62,7 @@ class MQManagement {
63
62
  uri: this.options.url,
64
63
  path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
65
64
  oa2: this.options.auth.oauth2,
66
- attemptInfo: () => LOG._info && LOG.info('Delete queue', { queue: queueName }),
65
+ attemptInfo: () => this.LOG._info && this.LOG.info('Delete queue', { queue: queueName }),
67
66
  errMsg: `Queue "${queueName}" could not be deleted`,
68
67
  target: { kind: 'QUEUE', queue: queueName },
69
68
  tokenStore: this
@@ -76,7 +75,7 @@ class MQManagement {
76
75
  uri: this.options.url,
77
76
  path: `/v1/management/queues/${encodeURIComponent(queueName)}/subscriptions/topics`,
78
77
  oa2: this.options.auth.oauth2,
79
- attemptInfo: () => LOG._info && LOG.info('Get subscriptions', { queue: queueName }),
78
+ attemptInfo: () => this.LOG._info && this.LOG.info('Get subscriptions', { queue: queueName }),
80
79
  errMsg: `Subscriptions for "${queueName}" could not be retrieved`,
81
80
  target: { kind: 'SUBSCRIPTION', queue: queueName },
82
81
  tokenStore: this
@@ -92,7 +91,8 @@ class MQManagement {
92
91
  topicPattern
93
92
  )}`,
94
93
  oa2: this.options.auth.oauth2,
95
- attemptInfo: () => LOG._info && LOG.info('Create subscription', { topic: topicPattern, queue: queueName }),
94
+ attemptInfo: () =>
95
+ this.LOG._info && this.LOG.info('Create subscription', { topic: topicPattern, queue: queueName }),
96
96
  errMsg: `Subscription "${topicPattern}" could not be added to queue "${queueName}"`,
97
97
  target: { kind: 'SUBSCRIPTION', queue: queueName, topic: topicPattern },
98
98
  tokenStore: this
@@ -107,7 +107,8 @@ class MQManagement {
107
107
  topicPattern
108
108
  )}`,
109
109
  oa2: this.options.auth.oauth2,
110
- attemptInfo: () => LOG._info && LOG.info('Delete subscription', { topic: topicPattern, queue: queueName }),
110
+ attemptInfo: () =>
111
+ this.LOG._info && this.LOG.info('Delete subscription', { topic: topicPattern, queue: queueName }),
111
112
  errMsg: `Subscription "${topicPattern}" could not be deleted from queue "${queueName}"`,
112
113
  target: { kind: 'SUBSCRIPTION', queue: queueName, topic: topicPattern },
113
114
  tokenStore: this
@@ -115,7 +116,7 @@ class MQManagement {
115
116
  }
116
117
 
117
118
  async createQueueAndSubscriptions() {
118
- LOG._info && LOG.info(`Create messaging artifacts`)
119
+ this.LOG._info && this.LOG.info(`Create messaging artifacts`)
119
120
  const created = await this.createQueue()
120
121
  if (created && created.statusCode === 200) {
121
122
  // We need to make sure to only keep our own subscriptions
@@ -171,7 +172,8 @@ class MessageQueuing extends AMQPWebhookMessaging {
171
172
  options: _optionsManagement,
172
173
  queueConfig,
173
174
  queueName,
174
- subscribedTopics: this.subscribedTopics
175
+ subscribedTopics: this.subscribedTopics,
176
+ LOG: this.LOG
175
177
  })
176
178
  return this.management
177
179
  }
@@ -21,9 +21,10 @@ const _getMessagesEntity = () => {
21
21
  return messagesEntity
22
22
  }
23
23
 
24
- // REVISIT: Is there a better way?
24
+ // REVISIT: Is this always a reliable way to identify the provider tenant?
25
+ // Are there scenarios where the credentials have a different format?
25
26
  const _isProviderTenant = tenant =>
26
- cds.requires.uaa && cds.requires.uaa.credentials && cds.requires.uaa.credentials.identityzoneid === tenant
27
+ cds.requires.auth && cds.requires.auth.credentials && cds.requires.auth.credentials.identityzoneid === tenant
27
28
 
28
29
  const hasPersistentOutbox = (srv, tenant) => {
29
30
  if (!cds.requires.outbox || cds.requires.outbox.kind !== 'persistent-outbox') return false
@@ -86,16 +87,15 @@ const processMessages = async (service, tenant, _opts = {}) => {
86
87
  } catch (e) {
87
88
  // could potentially be a timeout
88
89
  const _waitingTime = waitingTime(opts.attempt)
89
- LOG._error &&
90
- LOG.error(
91
- 'Outbox SELECT FOR UPDATE failed',
92
- opts.attempt > 0
93
- ? ''
94
- : {
95
- cause: e
96
- },
97
- `Retrying in ${Math.round(_waitingTime / 1000)} s`
98
- )
90
+ LOG.error(
91
+ 'Outbox SELECT FOR UPDATE failed',
92
+ opts.attempt > 0
93
+ ? ''
94
+ : {
95
+ cause: e
96
+ },
97
+ `Retrying in ${Math.round(_waitingTime / 1000)} s`
98
+ )
99
99
  outboxRunner.schedule(
100
100
  {
101
101
  name,
@@ -127,7 +127,7 @@ const processMessages = async (service, tenant, _opts = {}) => {
127
127
  const _waitingTime = waitingTime(error.failedMessage.attempts)
128
128
  const info = { service: name, event: error.failedMessage.event }
129
129
  if (error.failedMessage.attempts > 0) info.cause = error.failedMessage.error
130
- LOG._error && LOG.error('Emit failed', info, `Retrying in ${Math.round(_waitingTime / 1000)} s`)
130
+ LOG.error('Emit failed', info, `Retrying in ${Math.round(_waitingTime / 1000)} s`)
131
131
  await UPDATE(messagesEntity)
132
132
  .where({ ID: error.failedMessage.ID })
133
133
  .set({ attempts: { '+=': 1 } })
@@ -140,12 +140,11 @@ const processMessages = async (service, tenant, _opts = {}) => {
140
140
  () => processMessages(service, tenant, opts)
141
141
  )
142
142
  } else {
143
- LOG._error &&
144
- LOG.error(
145
- 'Emit failed',
146
- { service: name, event: error.failedMessage.event, cause: error.failedMessage.error },
147
- 'Unrecoverable, outbox entry deleted'
148
- )
143
+ LOG.error(
144
+ 'Emit failed',
145
+ { service: name, event: error.failedMessage.event, cause: error.failedMessage.error },
146
+ 'Unrecoverable, outbox entry deleted'
147
+ )
149
148
  }
150
149
  } else {
151
150
  outboxRunner.success({ name, tenant })
@@ -0,0 +1,91 @@
1
+ /* eslint-disable prettier/prettier */
2
+ const redis = require('redis')
3
+ const cds = require('../../../lib')
4
+ const waitingTime = require('./common-utils/waitingTime')
5
+ const normalizeIncomingMessage = require('./common-utils/normalizeIncomingMessage')
6
+ const { hasPersistentOutbox } = require('./outbox/utils')
7
+
8
+ const _handleReconnects = (client, LOG) => {
9
+ client.on('reconnecting', () => {
10
+ LOG.warn('Reconnecting')
11
+ })
12
+
13
+ client.on('error', error => {
14
+ LOG.warn('Failed to connect to Redis: ', error)
15
+ })
16
+ }
17
+
18
+ class RedisMessaging extends cds.MessagingService {
19
+ async init() {
20
+ await super.init()
21
+ const credentials = this.options && this.options.credentials
22
+ const config = {
23
+ socket: {
24
+ reconnectStrategy: attempts => {
25
+ const _waitingTime = waitingTime(attempts)
26
+ this.LOG.warn(`Connection to Redis lost: Reconnecting in ${Math.round(_waitingTime / 1000)} s`)
27
+ return _waitingTime
28
+ }
29
+ }
30
+ }
31
+ // rediss://someUserName:somePassword@hostName:port -> rediss://:somePassword@hostName:port
32
+ const url = credentials && credentials.uri && credentials.uri.replace(/\/\/.*?:/, '//:')
33
+ if (!url) this.LOG._warn && this.LOG.warn('No Redis credentials found, using default credentials')
34
+ else config.url = url
35
+ this.client = redis.createClient(config)
36
+ _handleReconnects(this.client, this.LOG)
37
+
38
+ try {
39
+ await this.client.connect()
40
+ } catch (e) {
41
+ throw new Error('Connection to Redis could not be established: ' + e)
42
+ }
43
+
44
+ this._ready = true
45
+ this.client.on('end', () => {
46
+ this._ready = false
47
+ })
48
+ this.client.on('error', () => {
49
+ this._ready = false
50
+ })
51
+ this.client.on('ready', () => {
52
+ this._ready = true
53
+ })
54
+
55
+ cds.once('listening', () => {
56
+ this.startListening()
57
+ })
58
+ }
59
+
60
+ async startListening() {
61
+ let subscriber
62
+ for (const topic of [...this.subscribedTopics].map(kv => kv[0])) {
63
+ if (!subscriber) {
64
+ // For subscriptions we need to duplicate the connection
65
+ subscriber = this.client.duplicate()
66
+ _handleReconnects(subscriber)
67
+ await subscriber.connect()
68
+ }
69
+ this.LOG._info && this.LOG('Create subscription', { topic })
70
+ await subscriber.subscribe(topic, async message => {
71
+ const msg = normalizeIncomingMessage(message)
72
+ msg.event = topic
73
+ try {
74
+ await super.emit(msg)
75
+ } catch (e) {
76
+ this.LOG.error('ERROR occured in asynchronous event processing:', e)
77
+ }
78
+ })
79
+ }
80
+ }
81
+
82
+ async emit(msg) {
83
+ const _msg = this.message4(msg)
84
+ this.LOG._info && this.LOG.info('Emit', { topic: _msg.event })
85
+ if (!this._ready && hasPersistentOutbox(this, cds.context && cds.context.tenant))
86
+ throw new Error('Redis connection not ready')
87
+ await this.client.publish(_msg.event, JSON.stringify({ data: _msg.data, ...(_msg.headers || {}) }))
88
+ }
89
+ }
90
+
91
+ module.exports = RedisMessaging
@@ -1,12 +1,11 @@
1
1
  const cds = require('../cds')
2
- const LOG = cds.log('messaging')
3
2
  const queued = require('./common-utils/queued')
4
3
  const OutboxService = require('./Outbox')
5
4
 
6
5
  const _topic = declared => declared['@topic'] || declared.name
7
6
 
8
7
  let usedTopicOnce = false
9
- const _warnAndStripTopicPrefix = event => {
8
+ const _warnAndStripTopicPrefix = (event, LOG) => {
10
9
  if (event.startsWith('topic:')) {
11
10
  // backwards compatibility
12
11
  event = event.replace(/topic:/, '')
@@ -24,6 +23,7 @@ class MessagingService extends OutboxService {
24
23
  // enables queued async operations (without awaiting)
25
24
  this.queued = queued()
26
25
  this.subscribedTopics = new Map()
26
+ this.LOG = cds.log(this.kind ? `${this.kind}|messaging` : 'messaging')
27
27
  // Only for one central `messaging` service, otherwise all technical services would register themselves
28
28
  if (this.name === 'messaging') {
29
29
  this._registeredServices = new Map()
@@ -81,7 +81,7 @@ class MessagingService extends OutboxService {
81
81
  }
82
82
 
83
83
  on(event, cb) {
84
- const _event = _warnAndStripTopicPrefix(event)
84
+ const _event = _warnAndStripTopicPrefix(event, this.LOG)
85
85
  // save all subscribed topics (not needed for local-messaging)
86
86
  this.subscribedTopics.set(this.prepareTopic(_event, true), _event)
87
87
  return super.on(_event, cb)
@@ -109,9 +109,11 @@ class MessagingService extends OutboxService {
109
109
 
110
110
  message4(msg) {
111
111
  const _msg = { ...msg }
112
- if (msg.inbound && !cds.context)
113
- _msg.user = msg.tenant ? new cds.User.Privileged({ tenant: msg.tenant }) : new cds.User.Privileged()
114
- _msg.event = _warnAndStripTopicPrefix(_msg.event)
112
+ if (msg.inbound && !cds.context) {
113
+ // REVISIT: why are all inbound messages executed with privileged user?
114
+ cds.context = { tenant: msg.tenant, user: new cds.User.Privileged() }
115
+ }
116
+ _msg.event = _warnAndStripTopicPrefix(_msg.event, this.LOG)
115
117
  if (!_msg.headers) _msg.headers = {}
116
118
  if (!_msg.inbound) {
117
119
  _msg.headers = { ..._msg.headers } // don't change the original object
@@ -40,12 +40,13 @@ const _setCorrectValue = (el, data, params, kind) => {
40
40
  const _buildPartialUrlFunctions = (url, data, params, kind = 'odata-v4') => {
41
41
  const funcParams = []
42
42
  const queryOptions = []
43
- for (const el in data) {
43
+ // REVISIT: take params from params after importer fix (the keys should not be part of params)
44
+ for (const param in data) {
44
45
  if (kind === 'odata-v2') {
45
- funcParams.push(`${el}=${_setCorrectValue(el, data, params, kind)}`)
46
+ funcParams.push(`${param}=${_setCorrectValue(param, data, params, kind)}`)
46
47
  } else {
47
- funcParams.push(`${el}=@${el}`)
48
- queryOptions.push(`@${el}=${_setCorrectValue(el, data, params, kind)}`)
48
+ funcParams.push(`${param}=@${param}`)
49
+ queryOptions.push(`@${param}=${_setCorrectValue(param, data, params, kind)}`)
49
50
  }
50
51
  }
51
52
  return kind === 'odata-v2'
@@ -62,8 +63,17 @@ const _extractParamsFromData = (data, params) => {
62
63
 
63
64
  const _buildKeys = (req, kind) => {
64
65
  const keys = []
65
- for (const key in req.target.keys) {
66
- keys.push(`${key}=${formatVal(req.data[key], key, req.target, kind)}`)
66
+ if (req.params && req.params.length > 0) {
67
+ const p1 = req.params[0]
68
+ if (typeof p1 !== 'object') return [p1]
69
+ for (const key in req.target.keys) {
70
+ keys.push(`${key}=${formatVal(p1[key], key, req.target, kind)}`)
71
+ }
72
+ } else {
73
+ // REVISIT: shall we keep that or remove it?
74
+ for (const key in req.target.keys) {
75
+ keys.push(`${key}=${formatVal(req.data[key], key, req.target, kind)}`)
76
+ }
67
77
  }
68
78
  return keys
69
79
  }
@@ -83,7 +93,14 @@ const _handleBoundActionFunction = (srv, def, req, url) => {
83
93
 
84
94
  const _handleUnboundActionFunction = (srv, def, req, event) => {
85
95
  if (def.kind === 'action') {
86
- return srv.post(`/${event}`, req.data)
96
+ // REVISIT: only for "rest" unbound actions/functions, we enforce axios to return a buffer
97
+ // required by cds-mt
98
+ const isBinary =
99
+ srv.kind === 'rest' &&
100
+ def &&
101
+ def.returns &&
102
+ (def.returns.type === 'cds.LargeBinary' || def.returns.type === 'cds.Binary')
103
+ return srv.send({ method: 'POST', path: `/${event}`, data: req.data, _binary: isBinary })
87
104
  }
88
105
 
89
106
  const url =
@@ -97,11 +114,30 @@ const _handleV2ActionFunction = (srv, def, req, event, kind) => {
97
114
  return def.kind === 'function' ? srv.get(url) : srv.post(url, {})
98
115
  }
99
116
 
117
+ const _handleV2BoundActionFunction = (srv, def, req, event, kind) => {
118
+ const params = []
119
+ const data = req.data
120
+ // REVISIT: take params from def.params, after importer fix (the keys should not be part of params)
121
+ for (const param in req.data) {
122
+ params.push(`${param}=${formatVal(data[param], param, { elements: def.params }, kind)}`)
123
+ }
124
+ const keys = _buildKeys(req, this.kind)
125
+ if (keys.length === 1 && typeof req.params[0] !== 'object') {
126
+ params.push(`${Object.keys(req.target.keys)[0]}=${keys[0]}`)
127
+ } else {
128
+ params.push(...keys)
129
+ }
130
+ const url = `${`/${event}`}?${params.join('&')}`
131
+ return def.kind === 'function' ? srv.get(url) : srv.post(url, {})
132
+ }
133
+
100
134
  const _addHandlerActionFunction = (srv, def, target) => {
101
135
  const event = def.name.match(/\w*$/)[0]
102
136
  if (target) {
103
137
  srv.on(event, target, async function (req) {
104
138
  const shortEntityName = req.target.name.replace(`${this.namespace}.`, '')
139
+ if (this.kind === 'odata-v2')
140
+ return _handleV2BoundActionFunction(srv, def, req, `${shortEntityName}_${event}`, this.kind)
105
141
  const url = `/${shortEntityName}(${_buildKeys(req, this.kind).join(',')})/${this.namespace}.${event}`
106
142
  return _handleBoundActionFunction(srv, def, req, url)
107
143
  })
@@ -208,7 +244,7 @@ class RemoteService extends cds.Service {
208
244
 
209
245
  if (req.target && req.target.name && this.definition && req.target.name.startsWith(this.definition.name + '.')) {
210
246
  const result = await super.handle(req)
211
- // only post process if alias was explicitely set in query
247
+ // only post process if alias was explicitly set in query
212
248
  if (_selectOnlyWithAlias(req.query)) {
213
249
  return postProcess(req.query, result, this, true)
214
250
  }
@@ -3,8 +3,6 @@ const LOG = cds.log('remote')
3
3
 
4
4
  const SANITIZE_VALUES = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
5
5
 
6
- const cdsLocale = require('../../../../lib/req/locale')
7
-
8
6
  const { convertV2ResponseData, deepSanitize, convertV2PayloadData } = require('./data')
9
7
 
10
8
  let _cloudSdkCore
@@ -67,6 +65,11 @@ const getDestination = (name, credentials) => {
67
65
  throw new Error(`"url" or "destination" property must be configured in "credentials" of "${name}".`)
68
66
  }
69
67
 
68
+ // Cloud SDK wants property "queryParameters" but we have documented "queries"
69
+ if (credentials.queries && !credentials.queryParameters) {
70
+ credentials.queryParameters = credentials.queries
71
+ }
72
+
70
73
  return { name, ...credentials }
71
74
  }
72
75
 
@@ -244,10 +247,11 @@ const run = async (
244
247
 
245
248
  const sanitizedError = _getSanitizedError(e, requestConfig, suppressRemoteResponseBody)
246
249
 
247
- LOG._warn && LOG.warn(sanitizedError)
248
-
249
250
  // REVISIT: switch from innererror to reason in cds^6
250
- throw Object.assign(new Error(e.message), { statusCode: 502, innererror: sanitizedError })
251
+ const err = Object.assign(new Error(e.message), { statusCode: 502, innererror: sanitizedError })
252
+ LOG._warn && LOG.warn(err)
253
+
254
+ throw err
251
255
  }
252
256
 
253
257
  // text/html indicates a redirect -> reject
@@ -266,13 +270,15 @@ const run = async (
266
270
 
267
271
  const sanitizedError = _getSanitizedError(e, requestConfig, suppressRemoteResponseBody)
268
272
 
269
- LOG._warn && LOG.warn(sanitizedError)
270
-
271
273
  // REVISIT: switch from innererror to reason in cds^6
272
- throw Object.assign(new Error(`Error during request to remote service: ${e.message}`), {
274
+ const err = Object.assign(new Error(`Error during request to remote service: ${e.message}`), {
273
275
  statusCode: 502,
274
276
  innererror: sanitizedError
275
277
  })
278
+
279
+ LOG._warn && LOG.warn(err)
280
+
281
+ throw err
276
282
  }
277
283
 
278
284
  // get result of $batch
@@ -295,10 +301,13 @@ const run = async (
295
301
  ? 'Error during request to remote service: ' + contentJSON.message
296
302
  : 'Request to remote service failed.'
297
303
  const sanitizedError = _getSanitizedError(contentJSON, requestConfig)
298
- LOG._warn && LOG.warn(sanitizedError)
304
+
305
+ const err = Object.assign(new Error(contentJSON.message), { statusCode: 502, innererror: sanitizedError })
306
+
307
+ LOG._warn && LOG.warn(err)
299
308
 
300
309
  // REVISIT: switch from innererror to reason in cds^6
301
- throw Object.assign(new Error(contentJSON.message), { statusCode: 502, innererror: sanitizedError })
310
+ throw err
302
311
  }
303
312
  }
304
313
 
@@ -394,9 +403,7 @@ const getReqOptions = (req, query, service) => {
394
403
  if (!_hasHeader(req.headers, 'accept-language')) {
395
404
  // Forward the locale properties from the original request (including region variants or weight factors),
396
405
  // if not given, it's taken from the user's locale (normalized and simplified)
397
- const locale =
398
- (req.context && req.context._ && req.context._.req && cdsLocale.from_req(req.context._.req)) ||
399
- (req.user && req.user.locale)
406
+ const locale = req._locale
400
407
  if (locale) reqOptions.headers['accept-language'] = locale
401
408
  }
402
409
 
@@ -437,6 +444,11 @@ const getReqOptions = (req, query, service) => {
437
444
 
438
445
  if (service.path) reqOptions.url = `${encodeURI(service.path)}${reqOptions.url}`
439
446
 
447
+ // set axios responseType to 'arraybuffer' if returning binary in rest
448
+ if (req._binary) {
449
+ reqOptions.responseType = 'arraybuffer'
450
+ }
451
+
440
452
  return reqOptions
441
453
  }
442
454
 
@@ -60,15 +60,15 @@ const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, elemen
60
60
 
61
61
  const type = _elementType(element)
62
62
 
63
- if (['cds.Boolean'].includes(type)) {
63
+ if (type === 'cds.Boolean') {
64
64
  if (value === 'true') {
65
65
  value = true
66
66
  } else if (value === 'false') {
67
67
  value = false
68
68
  }
69
- } else if (['cds.Integer'].includes(type)) {
69
+ } else if (type === 'cds.Integer') {
70
70
  value = parseInt(value, 10)
71
- } else if (['cds.Decimal', 'cds.Integer64', 'cds.DecimalFloat'].includes(type)) {
71
+ } else if (type === 'cds.Decimal' || type === 'cds.DecimalFloat' || type === 'cds.Integer64') {
72
72
  const bigValue = big(value)
73
73
  if (ieee754Compatible) {
74
74
  // TODO test with arrayed => element.items.scale?
@@ -77,15 +77,15 @@ const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, elemen
77
77
  // OData V2 does not even mention ieee754Compatible, but V4 requires JSON number if ieee754Compatible=false
78
78
  value = bigValue.toNumber()
79
79
  }
80
- } else if (['cds.Double'].includes(type)) {
80
+ } else if (type === 'cds.Double') {
81
81
  value = parseFloat(value)
82
- } else if (['cds.Time'].includes(type)) {
82
+ } else if (type === 'cds.Time') {
83
83
  const match = value.match(DurationRegex)
84
84
 
85
85
  if (match) {
86
86
  value = `${match[4] || '00'}:${match[5] || '00'}:${match[6] || '00'}`
87
87
  }
88
- } else if (['cds.Date', 'cds.DateTime', 'cds.Timestamp'].includes(type)) {
88
+ } else if (type === 'cds.Timestamp' || type === 'cds.DateTime' || type === 'cds.Date') {
89
89
  const match = value.match(/\/Date\((.*)\)\//)
90
90
  const ticksAndOffset = match && match.pop()
91
91
 
@@ -93,9 +93,9 @@ const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, elemen
93
93
  value = new Date(_calculateTicksOffsetSum(ticksAndOffset)).toISOString() // always UTC
94
94
  }
95
95
 
96
- if (['cds.DateTime'].includes(type)) {
96
+ if (type === 'cds.DateTime') {
97
97
  value = value.slice(0, 19) + 'Z' // Cut millis
98
- } else if (['cds.Date'].includes(type)) {
98
+ } else if (type === 'cds.Date') {
99
99
  value = value.slice(0, 10) // Cut time
100
100
  }
101
101
  }
@@ -136,15 +136,15 @@ const _elementType = element => {
136
136
  let type
137
137
 
138
138
  if (element) {
139
- type = element.type
139
+ type = element._type
140
140
 
141
141
  if (element['@odata.Type']) {
142
142
  const odataType = element['@odata.Type'].match(/\w+$/)
143
143
  type = (odataType && DataTypeOData[odataType[0]]) || type
144
144
  }
145
145
 
146
- if (!type && element.items && element.items.type) {
147
- type = element.items.type
146
+ if (!type && element.items && element.items._type) {
147
+ type = element.items._type
148
148
  }
149
149
  }
150
150
 
@@ -91,17 +91,12 @@ module.exports = class SQLiteDatabase extends DatabaseService {
91
91
 
92
92
  _registerAfterHandlers() {
93
93
  // REVISIT: after phase runs in parallel -> side effects possible!
94
- const { effective } = cds.env
94
+ const { effective, features } = cds.env
95
95
 
96
- if (effective.odata.structs) {
96
+ if (effective.odata.structs && !features.ucsn_struct_conversion) {
97
97
  // REVISIT: only register for entities that contain structured or navigation to it
98
98
  this.after(['READ'], '*', this._structured)
99
99
  }
100
-
101
- if (effective.odata.version !== 'v2') {
102
- // REVISIT: only register for entities that contain arrayed or navigation to it
103
- this.after(['READ'], '*', this._arrayed)
104
- }
105
100
  }
106
101
 
107
102
  /*
@@ -110,7 +105,9 @@ module.exports = class SQLiteDatabase extends DatabaseService {
110
105
  async acquire(arg) {
111
106
  // in non-multi-tenant scenarios, the default db should be returned regardless of arg
112
107
  let tenant = 'anonymous'
113
- if (this.options.multiTenant && arg) {
108
+ const isMultitenant = 'multiTenant' in this.options ? this.options.multiTenant : cds.env.requires.multitenancy
109
+ // REVISIT: there should already be compat for the multitenancy flag, why is it not working here?
110
+ if (isMultitenant && arg) {
114
111
  if (typeof arg === 'string') tenant = arg
115
112
  else tenant = arg.tenant || (arg.user && arg.user.tenant) || tenant // > REVISIT: remove fallback arg.user.tenant with cds^6
116
113
  }
@@ -120,7 +117,7 @@ module.exports = class SQLiteDatabase extends DatabaseService {
120
117
  const credentials = this.options.credentials || this.options || {}
121
118
  let dbUrl = credentials.database || credentials.url || credentials.host || ':memory:'
122
119
 
123
- if (this.options.multiTenant && dbUrl.endsWith('.db')) {
120
+ if (isMultitenant && dbUrl.endsWith('.db')) {
124
121
  dbUrl = dbUrl.split('.db')[0] + '_' + tenant + '.db'
125
122
  }
126
123
 
@@ -8,7 +8,10 @@ const dateTimeFunctions = new Map([
8
8
  ['hour', "'%H'"],
9
9
  ['minute', "'%M'"]
10
10
  ])
11
- const standadFunctions = ['locate', 'substring', 'to_date', 'to_time']
11
+ const STANDAD_FUNCTIONS_MAP = ['locate', 'substring', 'to_date', 'to_time'].reduce((acc, cur) => {
12
+ acc[cur] = 1
13
+ return acc
14
+ }, {})
12
15
 
13
16
  class CustomFunctionBuilder extends FunctionBuilder {
14
17
  get ExpressionBuilder() {
@@ -35,7 +38,7 @@ class CustomFunctionBuilder extends FunctionBuilder {
35
38
 
36
39
  if (dateTimeFunctions.has(functionName)) {
37
40
  this._timeFunction(functionName, args)
38
- } else if (standadFunctions.includes(functionName)) {
41
+ } else if (functionName in STANDAD_FUNCTIONS_MAP) {
39
42
  this._standardFunction(functionName, args)
40
43
  } else if (functionName === 'seconds_between') {
41
44
  this._secondsBetweenFunction(args)
@@ -49,7 +49,7 @@
49
49
  * @typedef {object} TemplateProcessorPathOptions
50
50
  * @property {object} [extraKeys]
51
51
  * @property {function} [rowKeysGenerator]
52
- * @property {string[]} [segments=[]] - Path segments to relate the error message.
52
+ * @property {pathSegment[]} [path=[]] - Path segments to relate the error message.
53
53
  * @property {boolean} [includeKeyValues=false] Indicates whether the key values are included in the path segments
54
54
  * The path segments are used to build the error target (a relative resource path)
55
55
  */
@@ -70,7 +70,14 @@
70
70
  * @property {object} element
71
71
  * @property {boolean} plain
72
72
  * @property {boolean} isRoot
73
- * @property {Array<String>} [pathSegments]
73
+ * @property {Array<pathSegment>} [path]
74
+ */
75
+
76
+ /**
77
+ * @typedef {object} pathSegment
78
+ * @property {string} key
79
+ * @property {number} idx
80
+ * @property {string} url
74
81
  */
75
82
 
76
83
  // Search
@@ -102,6 +109,7 @@
102
109
  * @typedef {object} cqn2cqn4sqlOptions
103
110
  * @property {boolean} [suppressSearch=false] Indicates whether the search handler is called.
104
111
  * @property {boolean} [_4fiori]
112
+ * @property {boolean} [_4db]
105
113
  * @property {import('../db/Service')} [service]
106
114
  */
107
115