@sap/cds 7.3.0 → 7.4.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 (109) hide show
  1. package/CHANGELOG.md +65 -3
  2. package/_i18n/i18n_es_MX.properties +110 -0
  3. package/apis/cds.d.ts +13 -12
  4. package/apis/core.d.ts +27 -108
  5. package/apis/cqn.d.ts +15 -18
  6. package/apis/csn.d.ts +95 -60
  7. package/apis/env.d.ts +25 -0
  8. package/apis/events.d.ts +124 -0
  9. package/apis/{reflect.d.ts → linked.d.ts} +27 -38
  10. package/apis/models.d.ts +60 -45
  11. package/apis/ql.d.ts +11 -5
  12. package/apis/{serve.d.ts → server.d.ts} +57 -31
  13. package/apis/services.d.ts +74 -145
  14. package/apis/test.d.ts +1 -1
  15. package/bin/serve.js +3 -0
  16. package/lib/compile/cds-compile.js +2 -2
  17. package/lib/compile/for/lean_drafts.js +1 -1
  18. package/lib/compile/to/edm.js +8 -3
  19. package/lib/compile/to/gql.js +4 -0
  20. package/lib/dbs/cds-deploy.js +52 -4
  21. package/lib/env/cds-requires.js +27 -15
  22. package/lib/env/defaults.js +1 -0
  23. package/lib/env/schemas/index.js +10 -0
  24. package/lib/index.js +8 -5
  25. package/lib/linked/models.js +8 -5
  26. package/lib/ql/CREATE.js +2 -0
  27. package/lib/ql/DELETE.js +1 -0
  28. package/lib/ql/DROP.js +2 -0
  29. package/lib/ql/INSERT.js +2 -22
  30. package/lib/ql/Query.js +59 -22
  31. package/lib/ql/SELECT.js +5 -0
  32. package/lib/ql/STREAM.js +2 -0
  33. package/lib/ql/UPDATE.js +2 -0
  34. package/lib/ql/UPSERT.js +3 -1
  35. package/lib/ql/cds-ql.js +21 -5
  36. package/lib/ql/infer.js +129 -0
  37. package/lib/req/cds-context.js +8 -5
  38. package/lib/srv/cds-connect.js +3 -1
  39. package/lib/utils/axios.js +4 -2
  40. package/lib/utils/cds-utils.js +9 -2
  41. package/lib/utils/data.js +3 -0
  42. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +12 -0
  43. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +26 -8
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +1 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +11 -8
  46. package/libx/_runtime/common/code-ext/worker.js +5 -16
  47. package/libx/_runtime/common/generic/auth/capabilities.js +11 -2
  48. package/libx/_runtime/common/i18n/messages.properties +1 -0
  49. package/libx/_runtime/common/utils/postProcessing.js +1 -1
  50. package/libx/_runtime/common/utils/resolveView.js +20 -1
  51. package/libx/{common → _runtime/common}/utils/ucsn.js +19 -11
  52. package/libx/_runtime/db/expand/expandCQNToJoin.js +2 -2
  53. package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -1
  54. package/libx/_runtime/db/sql-builder/UpdateBuilder.js +6 -1
  55. package/libx/_runtime/db/sql-builder/dollar.js +7 -7
  56. package/libx/_runtime/fiori/generic/activate.js +2 -2
  57. package/libx/_runtime/fiori/generic/edit.js +25 -45
  58. package/libx/_runtime/fiori/generic/read.js +3 -5
  59. package/libx/_runtime/fiori/lean-draft.js +142 -64
  60. package/libx/_runtime/fiori/utils/delete.js +7 -1
  61. package/libx/_runtime/fiori/utils/handler.js +4 -6
  62. package/libx/_runtime/fiori/utils/lockInfo.js +27 -0
  63. package/libx/_runtime/fiori/utils/where.js +20 -1
  64. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -2
  65. package/libx/_runtime/messaging/Outbox.js +12 -47
  66. package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -3
  67. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +3 -0
  68. package/libx/_runtime/messaging/common-utils/connections.js +1 -1
  69. package/libx/_runtime/messaging/enterprise-messaging.js +12 -13
  70. package/libx/_runtime/messaging/file-based.js +7 -5
  71. package/libx/_runtime/messaging/redis-messaging.js +10 -11
  72. package/libx/_runtime/messaging/service.js +12 -26
  73. package/libx/_runtime/remote/Service.js +52 -36
  74. package/libx/_runtime/remote/utils/client.js +22 -123
  75. package/libx/odata/afterburner.js +14 -5
  76. package/libx/odata/grammar.peggy +26 -7
  77. package/libx/odata/metadata.js +18 -1
  78. package/libx/odata/parser.js +1 -1
  79. package/libx/odata/service-document.js +0 -1
  80. package/libx/odata/utils.js +19 -3
  81. package/libx/{_runtime/messaging/outbox/utils.js → outbox/index.js} +94 -24
  82. package/libx/rest/middleware/parse.js +1 -1
  83. package/package.json +2 -2
  84. package/apis/connect.d.ts +0 -39
  85. package/bin/utils/modules.js +0 -7
  86. package/bin/utils/term.js +0 -56
  87. package/lib/env/schema.js +0 -9
  88. package/lib/linked/queries.js +0 -41
  89. package/lib/srv/protocols/odata-v2-proxy.js +0 -3699
  90. package/libx/common/asserts.js +0 -0
  91. package/libx/common/crud.js +0 -0
  92. package/libx/common/etag.js +0 -0
  93. package/libx/common/localized.js +0 -0
  94. package/libx/common/managed.js +0 -0
  95. package/libx/common/paging.js +0 -0
  96. package/libx/common/readme.md +0 -4
  97. package/libx/common/sorting.js +0 -0
  98. package/libx/common/temporal.js +0 -0
  99. package/libx/connect/auth.js +0 -0
  100. package/libx/connect/perf.js +0 -0
  101. package/libx/connect/readme.md +0 -3
  102. package/libx/fiori/draft/readme.md +0 -1
  103. package/libx/fiori/readme.md +0 -1
  104. package/libx/hana/readme.md +0 -1
  105. package/libx/msg/readme.md +0 -3
  106. package/libx/readme.md +0 -1
  107. package/libx/sqlite/readme.md +0 -1
  108. /package/libx/_runtime/{messaging/common-utils → common/utils}/waitingTime.js +0 -0
  109. /package/libx/{_runtime/messaging/outbox → outbox}/OutboxRunner.js +0 -0
@@ -1,19 +1,17 @@
1
- const cds = require('../../cds')
2
- const waitingTime = require('../common-utils/waitingTime')
3
- const OutboxRunner = require('./OutboxRunner')
4
- const { isStandardError } = require('../../common/error/standardError')
1
+ const cds = require('../_runtime/cds')
5
2
  const LOG = cds.log('persistent-outbox')
6
- const util = require('util')
7
- const _safeJSONParse = string => {
8
- try {
9
- return string && JSON.parse(string)
10
- } catch (_e) {
11
- // Don't throw
12
- }
13
- }
3
+
4
+ const { inspect } = require('util')
5
+
6
+ const OutboxRunner = require('./OutboxRunner')
14
7
  const outboxRunner = new OutboxRunner()
8
+
9
+ const waitingTime = require('../_runtime/common/utils/waitingTime')
10
+ const { isStandardError } = require('../_runtime/common/error/standardError')
11
+
15
12
  const cdsUser = 'cds.internal.user'
16
- const messageProcessorRegistered = Symbol('message processor registered')
13
+ const $messageProcessorRegistered = Symbol('message processor registered')
14
+ const $outboxed = Symbol('outboxed')
17
15
 
18
16
  const _get100NanosecondTimestampISOString = () => {
19
17
  const [now, nanoseconds] = [new Date(), process.hrtime()[1]]
@@ -33,10 +31,8 @@ const _getMessagesEntity = () => {
33
31
  const _isProviderTenant = tenant =>
34
32
  cds.requires.auth && cds.requires.auth.credentials && cds.requires.auth.credentials.identityzoneid === tenant
35
33
 
36
- const hasPersistentOutbox = (srv, tenant) => {
34
+ const hasPersistentOutbox = tenant => {
37
35
  if (!cds.requires.outbox || cds.requires.outbox.kind !== 'persistent-outbox') return false
38
- if (srv.options && srv.options.outbox && srv.options.outbox.kind && srv.options.outbox.kind !== 'persistent-outbox')
39
- return false
40
36
  if (cds.requires.multitenancy && tenant && _isProviderTenant(tenant)) return false // no persistence for provider account
41
37
  return true
42
38
  }
@@ -91,6 +87,14 @@ const processDefault = async (messages, { toBeDeleted, toBeUpdated, options, ser
91
87
  }
92
88
  }
93
89
 
90
+ const _safeJSONParse = string => {
91
+ try {
92
+ return string && JSON.parse(string)
93
+ } catch (_e) {
94
+ // Don't throw
95
+ }
96
+ }
97
+
94
98
  // Note: This function can also run for each tenant on startup
95
99
  const processMessages = async (service, tenant, _opts = {}) => {
96
100
  const opts = Object.assign({ attempt: 0 }, _opts)
@@ -127,8 +131,11 @@ const processMessages = async (service, tenant, _opts = {}) => {
127
131
  let currMaxAttempts = 0
128
132
  const messagesGen = function* () {
129
133
  for (const _message of messages) {
130
- const msg = _safeJSONParse(_message.msg)
131
- const userId = msg[cdsUser]
134
+ const _msg = _safeJSONParse(_message.msg)
135
+ const userId = _msg[cdsUser]
136
+ const msg = _msg._fromSend ? new cds.Request(_msg) : new cds.Event(_msg)
137
+ delete msg._fromSend
138
+ Object.defineProperty(msg, '_fromOutbox', { value: true, enumerable: false })
132
139
  delete msg[cdsUser]
133
140
  currMaxAttempts = Math.max(_message.attempts || 0, currMaxAttempts)
134
141
  const user = new cds.User.Privileged(userId)
@@ -137,7 +144,7 @@ const processMessages = async (service, tenant, _opts = {}) => {
137
144
  process: () =>
138
145
  cds._context.run({ user, tenant }, async () => {
139
146
  try {
140
- return service._emitImmediate && (await service._emitImmediate(msg))
147
+ return await service.handle(msg)
141
148
  } catch (e) {
142
149
  if (isUnrecoverable(service, e)) e.unrecoverable = true
143
150
  throw e
@@ -160,7 +167,6 @@ const processMessages = async (service, tenant, _opts = {}) => {
160
167
  toBeDeleted,
161
168
  toBeUpdated,
162
169
  service,
163
- emit: service._emitImmediate.bind(service),
164
170
  options: opts
165
171
  })
166
172
  } catch (e) {
@@ -177,7 +183,7 @@ const processMessages = async (service, tenant, _opts = {}) => {
177
183
  attempts: { '+=': 1 }
178
184
  }
179
185
  Object.assign(data, toBeUpdatedMsg)
180
- if (data.lastError && typeof data.lastError !== 'string') data.lastError = util.inspect(data.lastError)
186
+ if (data.lastError && typeof data.lastError !== 'string') data.lastError = inspect(data.lastError)
181
187
  queries.push(UPDATE(messagesEntity).where({ ID: toBeUpdatedMsg.ID }).set(data))
182
188
  }
183
189
  }
@@ -212,7 +218,7 @@ const processMessages = async (service, tenant, _opts = {}) => {
212
218
  }
213
219
 
214
220
  const registerMessageProcessor = (name, context) => {
215
- const registry = context[messageProcessorRegistered] || (context[messageProcessorRegistered] = new Set())
221
+ const registry = context[$messageProcessorRegistered] || (context[$messageProcessorRegistered] = new Set())
216
222
  if (!registry.has(name)) {
217
223
  registry.add(name)
218
224
  return true
@@ -221,7 +227,13 @@ const registerMessageProcessor = (name, context) => {
221
227
  }
222
228
 
223
229
  const _createMessage = (name, msg, context) => {
224
- const _msg = { ...msg, [cdsUser]: context.user.id }
230
+ const _msg = { [cdsUser]: context.user.id }
231
+ if (msg._fromSend || msg.reply) _msg._fromSend = true // send or emit?
232
+ if (msg.inbound) _msg.inbound = msg.inbound
233
+ if (msg.event) _msg.event = msg.event
234
+ if (msg.data) _msg.data = msg.data
235
+ if (msg.headers) _msg.headers = msg.headers
236
+ if (msg.query) _msg.query = msg.query
225
237
  const outboxMsg = {
226
238
  ID: cds.utils.uuid(),
227
239
  target: name,
@@ -237,4 +249,62 @@ const writeInOutbox = async (name, msg, context) => {
237
249
  return cds.tx(context).run(INSERT.into(messagesEntity).entries(outboxMsg))
238
250
  }
239
251
 
240
- module.exports = { processMessages, registerMessageProcessor, writeInOutbox, hasPersistentOutbox, isUnrecoverable }
252
+ // REVIST: Should we also support the following API?
253
+ // cds.outboxed(srv, {
254
+ // onInsert(msg){ ... }
255
+ // onForward(msg){ ... }
256
+ // onProcess(msgs){ ... }
257
+ // })
258
+
259
+ function outboxed(srv, customOpts) {
260
+ // outbox max. once
261
+ if (!new.target) {
262
+ const former = srv[$outboxed]
263
+ if (former) return former
264
+ }
265
+
266
+ const originalSrv = srv.immediate || srv
267
+ const outboxedSrv = Object.create(originalSrv)
268
+ outboxedSrv.immediate = originalSrv
269
+
270
+ if (!new.target) Object.defineProperty(srv, $outboxed, { value: outboxedSrv })
271
+
272
+ const outboxOpts = Object.assign(
273
+ {},
274
+ (typeof cds.requires.outbox === 'object' && cds.requires.outbox) || {},
275
+ (typeof srv.options?.outbox === 'object' && srv.options.outbox) || {},
276
+ customOpts || {}
277
+ )
278
+
279
+ outboxedSrv.handle = async function (req) {
280
+ const context = req.context || cds.context
281
+ if (outboxOpts.kind === 'persistent-outbox' && hasPersistentOutbox(context.tenant)) {
282
+ // returns true if not yet registered
283
+ if (registerMessageProcessor(srv.name, context)) {
284
+ // NOTE: What if there are different outbox options for the same service?!
285
+ // There could be messages for srv1 with { maxAttempts: 1 }
286
+ // and messages for srv1 with { maxAttempts: 9 }.
287
+ // How would they be processed? I'd rather not have dedicated
288
+ // service names or store serialized options for each message.
289
+ context.on('succeeded', () => processMessages(originalSrv, context.tenant, outboxOpts))
290
+ }
291
+ await writeInOutbox(srv.name, req, context)
292
+ return
293
+ }
294
+ // REVISIT: Also allow maxAttempts for in-memory outbox?
295
+ context.on('succeeded', async () => {
296
+ try {
297
+ if (req.reply) await originalSrv.send(req)
298
+ else await originalSrv.emit(req)
299
+ } catch (e) {
300
+ LOG.error('Emit failed', { event: req.event, cause: e })
301
+ // opts.crashOnError is not official!!!
302
+ if (isUnrecoverable(originalSrv, e) && outboxOpts.crashOnError !== false) cds.exit(1)
303
+ }
304
+ })
305
+ }
306
+
307
+ return outboxedSrv
308
+ }
309
+
310
+ module.exports = outboxed
@@ -3,7 +3,7 @@ const { INSERT, SELECT, UPDATE, DELETE } = cds.ql
3
3
 
4
4
  const { where2obj } = require('../../_runtime/common/utils/cqn')
5
5
 
6
- const { convertStructured } = require('../../common/utils/ucsn')
6
+ const { convertStructured } = require('../../_runtime/common/utils/ucsn')
7
7
  const { deepCopy } = require('../../_runtime/common/utils/copy')
8
8
 
9
9
  module.exports = (req, res, next) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "7.3.0",
3
+ "version": "7.4.0",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "@sap/cds-compiler": "^4",
37
37
  "@sap/cds-fiori": "^1",
38
- "@sap/cds-foss": "^4"
38
+ "@sap/cds-foss": "^5.0.0"
39
39
  },
40
40
  "cds": {
41
41
  "plugins": [
package/apis/connect.d.ts DELETED
@@ -1,39 +0,0 @@
1
- import { Service } from "./services"
2
-
3
- export = cds
4
- declare class cds {
5
-
6
- connect : {
7
- /**
8
- * Connects to a specific datasource.
9
- * @see [capire](https://cap.cloud.sap/docs/node.js/cds-connect#cds-connect-to)
10
- */
11
- to (datasource: string, options?: ConnectOptions) : Promise<Service>
12
-
13
- /**
14
- * Connects to a specific datasource via options.
15
- * @see [capire](https://cap.cloud.sap/docs/node.js/cds-connect#cds-connect-to)
16
- */
17
- to (options: ConnectOptions) : Promise<Service>
18
-
19
- /**
20
- * Connects the primary datasource.
21
- * @see [capire](https://cap.cloud.sap/docs/node.js/cds-connect)
22
- */
23
- (options?: string | ConnectOptions) : Promise<typeof cds> //> cds.connect(<options>)
24
- }
25
-
26
- /**
27
- * Emitted whenever a specific service is connected for the first time.
28
- */
29
- on (event : 'connect', listener : (srv : Service) => void) : this
30
-
31
- }
32
-
33
- type ConnectOptions = {
34
- impl?: string,
35
- service?: string,
36
- kind?:string,
37
- model?:string,
38
- credentials?: object
39
- }
@@ -1,7 +0,0 @@
1
- module.exports = {
2
- npmGlobalModules: function() {
3
- try {
4
- return require ('child_process').execSync('npm root -g').toString().trim()
5
- } catch (err) { return }
6
- }
7
- }
package/bin/utils/term.js DELETED
@@ -1,56 +0,0 @@
1
- // FIXME: Nicht gut, das wir das zweimal redundant haben!
2
- const debug = process.env.DEBUG
3
-
4
- // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
5
- const t = exports.codes = {
6
- reset: '\x1b[0m', // Default
7
- bold: '\x1b[1m', // Bold/Bright
8
- link: '\x1b[4m', // underline
9
- red: '\x1b[91m', // Bright Foreground Red
10
- green: '\x1b[32m', // Foreground Green
11
- orange: '\x1b[38;2;255;140;0m' // darker orange, works with bright and dark background
12
- }
13
-
14
- Object.defineProperty(exports, 'isTTY', { get: () => process.stdin.isTTY && process.stderr.isTTY })
15
-
16
- const as = exports.as = (codes, o) => {
17
- return exports.isTTY ? (codes + o + t.reset) : ('' + o)
18
- }
19
-
20
- const asErr = exports.error = o => debug ? o : as(t.red + t.bold, o)
21
- const asWarn = exports.warn = o => debug ? o : as(t.orange + t.bold, o)
22
- const asInfo = exports.info = o => debug ? o : as(t.green + t.bold, o)
23
- exports.warn = o => as(t.orange, o)
24
- exports.info = o => as(t.green, o)
25
- exports.link = o => as(t.link, o)
26
- exports.bold = o => as(t.bold, o)
27
-
28
- const format = exports.format = (o, severity='Error', asInternalError=false, withStack=false) => {
29
- switch (severity) {
30
- case 'Error' : return format.error (o, asInternalError, withStack)
31
- case 'Warning': return format.warn (o)
32
- default : return format.info (o)
33
- }
34
- }
35
-
36
- // decorate.error, .warning, .info
37
- // 'Error: foo' -> '[ERROR] foo' (Maven-like, allows for better grepping in logs)
38
- Object.assign (format, {
39
- error: (o, asInternalError, withStack) => {
40
- if (debug) return o
41
- if (asInternalError) {
42
- return `[${asErr('INTERNAL ERROR')}] ${o.stack || o.toString()}\n`
43
- }
44
- return `[${asErr('ERROR')}] ${toString(o, 'Error', withStack)}`
45
- },
46
- warn: o => debug ? o : `[${asWarn('WARNING')}] ${toString(o, 'Warning')}`,
47
- info: o => debug ? o : `[${asInfo('INFO')}] ${toString(o, 'Info')}`,
48
- })
49
-
50
- function toString(o, severity, withStack) {
51
- if (!o || !o.toString) return o
52
- return (withStack && o.stack ? o.stack : o.toString())
53
- // strips the 'Error: ' prefix in the message, so that we can add our own prefix
54
- .replace(new RegExp('^' + severity + ': ', 'i'), '') // beginning
55
- .replace(new RegExp(' ' + severity + ':' , 'i'), '') // middle
56
- }
package/lib/env/schema.js DELETED
@@ -1,9 +0,0 @@
1
- const cds = require('../index')
2
- const { join } = cds.utils.path
3
-
4
-
5
- module.exports = {
6
- default4: async (name) => {
7
- return cds.utils.read(join(__dirname, 'schemas', name), 'json')
8
- }
9
- }
@@ -1,41 +0,0 @@
1
- const { entity } = require('./entities')
2
-
3
- /** Lazily resolves a query's _target property */
4
- module.exports = (q,defs) => {
5
- if (!q._target || q._target.kind !== 'entity') Object.defineProperty (q, '_target', {value:(
6
- q.SELECT ? _resolve (q.SELECT.from, defs) :
7
- q.INSERT ? _resolve (q.INSERT.into, defs) :
8
- q.UPSERT ? _resolve (q.UPSERT.into, defs) :
9
- q.UPDATE ? _resolve (q.UPDATE.entity, defs) :
10
- q.DELETE ? _resolve (q.DELETE.from, defs) :
11
- q.STREAM?.from ? _resolve (q.STREAM.from, defs) :
12
- q.STREAM?.into ? _resolve (q.STREAM.into, defs) :
13
- _resolve (undefined)
14
- ), configurable:true, writable:true })
15
- return q._target
16
- }
17
-
18
- const _resolve = (from, defs) => {
19
- if (!from || from.name) return from
20
- while (from.SELECT) from = from.SELECT.from
21
- if (from.join || from.set) return //_unresolved()
22
- if (from.ref) {
23
- if (from.ref.length === 1) {
24
- from = from.ref[0]
25
- if (from.id) from = from.id
26
- } else {
27
- let target = {elements:defs}
28
- for (let each of from.ref) {
29
- const e = each.id || each
30
- const a = target.elements[e]; if (!a) return _unresolved (target.name +':'+e)
31
- target = defs [a.target || a.name]; if (!target) return _unresolved (a.target)
32
- }
33
- return target
34
- }
35
- }
36
- return defs[from] || _unresolved(from)
37
- }
38
-
39
- const _unresolved = (name) => {
40
- return { name, __proto__:entity.prototype, _unresolved:true }
41
- }