@sap/cds 7.5.3 → 7.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +79 -21
  2. package/app/index.js +6 -17
  3. package/lib/auth/index.js +3 -0
  4. package/lib/compile/extend.js +9 -4
  5. package/lib/compile/for/lean_drafts.js +3 -4
  6. package/lib/compile/load.js +11 -15
  7. package/lib/compile/minify.js +2 -4
  8. package/lib/compile/to/sql.js +6 -4
  9. package/lib/compile/to/yaml.js +1 -1
  10. package/lib/dbs/cds-deploy.js +7 -13
  11. package/lib/env/defaults.js +1 -10
  12. package/lib/env/schemas/cds-package.js +27 -0
  13. package/lib/env/schemas/cds-rc.js +693 -0
  14. package/lib/env/schemas/index.js +6 -4
  15. package/lib/index.js +40 -47
  16. package/lib/log/cds-error.js +6 -0
  17. package/lib/log/format/aspects/als.js +1 -0
  18. package/lib/log/format/json.js +5 -1
  19. package/lib/ql/Query.js +2 -1
  20. package/lib/ql/cds-ql.js +1 -2
  21. package/lib/ql/infer.js +0 -2
  22. package/lib/req/cds-context.js +1 -1
  23. package/lib/req/request.js +3 -6
  24. package/lib/srv/middlewares/trace.js +2 -2
  25. package/lib/srv/protocols/hcql.js +44 -30
  26. package/lib/srv/protocols/http.js +60 -0
  27. package/lib/srv/protocols/index.js +0 -7
  28. package/lib/srv/protocols/odata-v4.js +8 -2
  29. package/lib/srv/srv-api.js +129 -62
  30. package/lib/srv/srv-handlers.js +0 -1
  31. package/lib/srv/srv-models.js +1 -0
  32. package/lib/utils/cds-utils.js +26 -0
  33. package/lib/utils/check-version.js +10 -13
  34. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +22 -6
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +3 -4
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +89 -21
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +4 -2
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -24
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +1 -7
  40. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ApplyParser.js +3 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +1 -1
  42. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/http/HttpHeaderReader.js +1 -1
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +6 -0
  44. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +0 -5
  45. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -0
  46. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +17 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +22 -2
  48. package/libx/_runtime/cds-services/services/utils/columns.js +1 -2
  49. package/libx/_runtime/common/aspects/Association.js +17 -9
  50. package/libx/_runtime/common/generic/crud.js +13 -22
  51. package/libx/_runtime/common/generic/etag.js +1 -1
  52. package/libx/_runtime/common/generic/input.js +9 -1
  53. package/libx/_runtime/common/generic/paging.js +3 -3
  54. package/libx/_runtime/common/generic/sorting.js +25 -15
  55. package/libx/_runtime/common/generic/stream.js +2 -16
  56. package/libx/_runtime/common/i18n/messages.properties +3 -0
  57. package/libx/_runtime/common/utils/copy.js +5 -0
  58. package/libx/_runtime/common/utils/cqn.js +1 -1
  59. package/libx/_runtime/common/utils/cqn2cqn4sql.js +4 -3
  60. package/libx/_runtime/common/utils/csn.js +0 -49
  61. package/libx/_runtime/common/utils/foreignKeyPropagations.js +5 -5
  62. package/libx/_runtime/common/utils/generateOnCond.js +50 -25
  63. package/libx/_runtime/common/utils/resolveView.js +5 -35
  64. package/libx/_runtime/common/utils/rewriteAsterisks.js +17 -4
  65. package/libx/_runtime/common/utils/stream.js +16 -15
  66. package/libx/_runtime/common/utils/streamProp.js +25 -22
  67. package/libx/_runtime/db/Service.js +27 -8
  68. package/libx/_runtime/db/generic/input.js +6 -1
  69. package/libx/_runtime/db/generic/rewrite.js +3 -2
  70. package/libx/_runtime/db/query/read.js +15 -5
  71. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -11
  72. package/libx/_runtime/db/utils/columns.js +1 -0
  73. package/libx/_runtime/db/utils/stream.js +41 -0
  74. package/libx/_runtime/fiori/generic/read.js +2 -1
  75. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -1
  76. package/libx/_runtime/fiori/lean-draft.js +209 -55
  77. package/libx/_runtime/hana/Service.js +1 -1
  78. package/libx/_runtime/hana/execute.js +53 -14
  79. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +2 -1
  80. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +34 -15
  81. package/libx/_runtime/messaging/file-based.js +4 -3
  82. package/libx/_runtime/messaging/redis-messaging.js +2 -1
  83. package/libx/_runtime/remote/Service.js +2 -1
  84. package/libx/_runtime/remote/utils/client.js +1 -1
  85. package/libx/_runtime/sqlite/Service.js +1 -1
  86. package/libx/_runtime/sqlite/execute.js +17 -5
  87. package/libx/odata/afterburner.js +58 -19
  88. package/libx/odata/cqn2odata.js +6 -8
  89. package/libx/odata/create.js +44 -0
  90. package/libx/odata/delete.js +25 -0
  91. package/libx/odata/error.js +8 -3
  92. package/libx/odata/metadata.js +6 -8
  93. package/libx/odata/service-document.js +1 -1
  94. package/libx/odata/update.js +110 -0
  95. package/libx/odata/utils.js +9 -6
  96. package/libx/outbox/index.js +74 -89
  97. package/libx/rest/RestAdapter.js +0 -3
  98. package/package.json +1 -1
  99. package/lib/env/schemas/cds-package.json +0 -17
  100. package/lib/env/schemas/cds-rc.json +0 -740
  101. package/lib/ql/STREAM.js +0 -90
@@ -13,6 +13,7 @@ const cdsUser = 'cds.internal.user'
13
13
  const $messageProcessorRegistered = Symbol('message processor registered')
14
14
  const $outboxed = Symbol('outboxed')
15
15
  const $unboxed = Symbol('unboxed')
16
+ const $stored_reqs = Symbol('stored_reqs')
16
17
 
17
18
  const _get100NanosecondTimestampISOString = () => {
18
19
  const [now, nanoseconds] = [new Date(), process.hrtime()[1]]
@@ -38,56 +39,6 @@ const hasPersistentOutbox = tenant => {
38
39
  return true
39
40
  }
40
41
 
41
- const isUnrecoverable = (service, error) => {
42
- let unrecoverable = service.isUnrecoverableError && service.isUnrecoverableError(error)
43
- if (unrecoverable === undefined) unrecoverable = error.unrecoverable
44
- return unrecoverable || isStandardError(error)
45
- }
46
-
47
- const processDefault = async (messages, { toBeDeleted, toBeUpdated, options, service }) => {
48
- /** throws if an emit failed due to a programming error
49
- * returns false if an emit failed due to temporary issues **/
50
- const run = async ({ ID, process }) => {
51
- try {
52
- await process()
53
- toBeDeleted.push(ID)
54
- } catch (e) {
55
- if (isStandardError(e)) {
56
- LOG.error(`${service.name}: Programming error detected:`, e)
57
- toBeDeleted.push(ID)
58
- throw new Error(`${service.name}: Programming error detected.`)
59
- }
60
- if (e.unrecoverable) {
61
- LOG.error(`${service.name}: Unrecoverable error:`, e)
62
- if (options.maxAttempts) {
63
- const _msg = { ID, attempts: options.maxAttempts }
64
- if (options.storeLastError !== false) _msg.lastError = e
65
- toBeUpdated.push(_msg)
66
- } else toBeDeleted.push(ID)
67
- } else {
68
- LOG.error(`${service.name}: Emit failed:`, e)
69
- const _msg = { ID }
70
- if (options.storeLastError !== false) _msg.lastError = e
71
- toBeUpdated.push(_msg)
72
- return false
73
- }
74
- }
75
- }
76
- if (options.parallel) {
77
- const first = messages.next()?.value // First try to see if message can be emitted
78
- if (first && (await run(first)) === false) return // No need to process the rest if the emit failed
79
- const res = await Promise.allSettled([...messages].map(run))
80
- const errors = res.filter(r => r.status === 'rejected').map(r => r.reason)
81
- if (errors.length) {
82
- throw new Error(`${service.name}: Programming errors detected.`)
83
- }
84
- } else {
85
- for (const msg of messages) {
86
- if ((await run(msg)) === false) break
87
- }
88
- }
89
- }
90
-
91
42
  const _safeJSONParse = string => {
92
43
  try {
93
44
  return string && JSON.parse(string)
@@ -142,36 +93,64 @@ const processMessages = async (service, tenant, _opts = {}) => {
142
93
  const user = new cds.User.Privileged(userId)
143
94
  if (!msg) continue
144
95
  const res = {
145
- process: () =>
146
- cds._context.run({ user, tenant }, async () => {
147
- try {
148
- return await service.handle(msg)
149
- } catch (e) {
150
- if (isUnrecoverable(service, e)) e.unrecoverable = true
151
- throw e
152
- }
153
- }),
154
96
  ID: _message.ID,
155
97
  msg,
156
- user,
157
- opts
98
+ user
158
99
  }
159
100
  yield res
160
101
  }
161
102
  }
162
103
 
163
- const process = service.options.outbox?.process || this.process || processDefault
164
104
  const toBeDeleted = []
165
105
  const toBeUpdated = []
166
106
  try {
167
- await process(messagesGen(), {
168
- toBeDeleted,
169
- toBeUpdated,
170
- service,
171
- options: opts
172
- })
107
+ const _handleWithErr = async ({ msg, user, ID }) => {
108
+ try {
109
+ await cds._context.run({ user, tenant }, async () => {
110
+ if (opts.handle) await opts.handle.call(service, msg)
111
+ else await service.handle(msg)
112
+ })
113
+ toBeDeleted.push(ID)
114
+ } catch (e) {
115
+ if (isStandardError(e)) {
116
+ LOG.error(`${service.name}: Programming error detected:`, e)
117
+ toBeDeleted.push(ID)
118
+ throw new Error(`${service.name}: Programming error detected.`)
119
+ }
120
+ if (e.unrecoverable) {
121
+ LOG.error(`${service.name}: Unrecoverable error:`, e)
122
+ if (opts.maxAttempts) {
123
+ const _msg = { ID, attempts: opts.maxAttempts }
124
+ if (opts.storeLastError !== false) _msg.lastError = e
125
+ toBeUpdated.push(_msg)
126
+ } else toBeDeleted.push(ID)
127
+ } else {
128
+ LOG.error(`${service.name}: Emit failed:`, e)
129
+ const _msg = { ID }
130
+ if (opts.storeLastError !== false) _msg.lastError = e
131
+ toBeUpdated.push(_msg)
132
+ return false
133
+ }
134
+ }
135
+ }
136
+ const messages = messagesGen()
137
+ // REVISIT: Maybe we can also support handleMany and provide the iterator (for batch processing)
138
+ if (opts.parallel) {
139
+ const first = messages.next()?.value // First try to see if message can be emitted
140
+ if (!(first && (await _handleWithErr(first)) === false)) { // No need to process the rest if the first emit failed
141
+ const res = await Promise.allSettled([...messages].map(_handleWithErr))
142
+ const errors = res.filter(r => r.status === 'rejected').map(r => r.reason)
143
+ if (errors.length) {
144
+ throw new Error(`${service.name}: Programming errors detected.`)
145
+ }
146
+ }
147
+ } else {
148
+ for (const msg of messages) {
149
+ if ((await _handleWithErr(msg)) === false) break
150
+ }
151
+ }
173
152
  } catch (e) {
174
- if (opts.crashOnError !== false) letAppCrash = true
153
+ letAppCrash = true
175
154
  }
176
155
 
177
156
  const queries = []
@@ -250,13 +229,6 @@ const writeInOutbox = async (name, msg, context) => {
250
229
  return cds.tx(context).run(INSERT.into(messagesEntity).entries(outboxMsg))
251
230
  }
252
231
 
253
- // REVIST: Should we also support the following API?
254
- // cds.outboxed(srv, {
255
- // onInsert(msg){ ... }
256
- // onForward(msg){ ... }
257
- // onProcess(msgs){ ... }
258
- // })
259
-
260
232
  function unboxed(srv) {
261
233
  return srv[$unboxed] || srv
262
234
  }
@@ -274,13 +246,20 @@ function outboxed(srv, customOpts) {
274
246
 
275
247
  if (!new.target) Object.defineProperty(srv, $outboxed, { value: outboxedSrv })
276
248
 
249
+ let requiresOpts = cds.requires.outbox
250
+ let serviceOpts = srv.options?.outbox
251
+
252
+ if (typeof requiresOpts === 'string') requiresOpts = { kind: requiresOpts }
253
+ if (typeof serviceOpts === 'string') serviceOpts = { kind: serviceOpts }
254
+
277
255
  const outboxOpts = Object.assign(
278
256
  {},
279
- (typeof cds.requires.outbox === 'object' && cds.requires.outbox) || {},
280
- (typeof srv.options?.outbox === 'object' && srv.options.outbox) || {},
257
+ (typeof requiresOpts === 'object' && requiresOpts) || {},
258
+ (typeof serviceOpts === 'object' && serviceOpts) || {},
281
259
  customOpts || {}
282
260
  )
283
261
 
262
+
284
263
  outboxedSrv.handle = async function (req) {
285
264
  const context = req.context || cds.context
286
265
  if (outboxOpts.kind === 'persistent-outbox' && hasPersistentOutbox(context.tenant)) {
@@ -296,17 +275,23 @@ function outboxed(srv, customOpts) {
296
275
  await writeInOutbox(srv.name, req, context)
297
276
  return
298
277
  }
299
- // REVISIT: Also allow maxAttempts for in-memory outbox?
300
- context.on('succeeded', async () => {
301
- try {
302
- if (req.reply) await originalSrv.send(req)
303
- else await originalSrv.emit(req)
304
- } catch (e) {
305
- LOG.error('Emit failed', { event: req.event, cause: e })
306
- // opts.crashOnError is not official!!!
307
- if (isUnrecoverable(originalSrv, e) && outboxOpts.crashOnError !== false) cds.exit(1)
308
- }
309
- })
278
+ if (!context[$stored_reqs]) {
279
+ context[$stored_reqs] = []
280
+ context.on('succeeded', async () => {
281
+ // REVISIT: Also allow maxAttempts for in-memory outbox?
282
+ for (const _req of context[$stored_reqs]) {
283
+ try {
284
+ if (req.reply) await originalSrv.send(req)
285
+ else await originalSrv.emit(req)
286
+ } catch (e) {
287
+ LOG.error('Emit failed', { event: req.event, cause: e })
288
+ if (isStandardError(e)) cds.exit(1)
289
+ }
290
+ }
291
+ delete context[$stored_reqs]
292
+ })
293
+ }
294
+ context[$stored_reqs].push(req)
310
295
  }
311
296
 
312
297
  return outboxedSrv
@@ -16,7 +16,6 @@ const payload_factory = require('./middleware/payload')
16
16
  const error_factory = require('./middleware/error')
17
17
 
18
18
  const { bufferToBase64 } = require('../_runtime/common/utils/binary')
19
- const { alias2ref } = require('../_runtime/common/utils/csn')
20
19
  const { getAccessRestrictions } = require('../_runtime/common/utils/restrictions')
21
20
 
22
21
  const RestAdapter = function (srv) {
@@ -24,8 +23,6 @@ const RestAdapter = function (srv) {
24
23
  const input = input_factory(srv)
25
24
  const payload = payload_factory(srv)
26
25
 
27
- alias2ref(srv)
28
-
29
26
  const router = express.Router()
30
27
 
31
28
  // -----------------------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "7.5.3",
3
+ "version": "7.6.2",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -1,17 +0,0 @@
1
- {
2
- "title": "JSON schema for CDS configuration in package.json",
3
- "$schema": "https://json-schema.org/draft/2020-12/schema",
4
- "description": "This is a JSON schema representation of the CDS project configuration inside a project root level package.json",
5
- "type": "object",
6
- "properties": {
7
- "extends": {
8
- "description": "Name of the application that shall be extended",
9
- "type": "string"
10
- },
11
- "cds": {
12
- "$ref": "cdsJsonSchema://schemas/cds-rc.json",
13
- "description": "CDS configuration",
14
- "default": {}
15
- }
16
- }
17
- }