@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
package/lib/index.js CHANGED
@@ -1,9 +1,23 @@
1
- if (process.env.CDS_STRICT_NODE_VERSION !== 'false') require('./utils/check-version')
1
+ if (process.env.CDS_STRICT_NODE_VERSION !== 'false') require ('./utils/check-version')
2
+ !(global.__cds_loaded_from ??= new Set).add(__filename) // track from where we loaded cds
3
+
4
+ /** @typedef { import('../apis/linked.js').LinkedCSN } Linked */
5
+ /** @typedef { import('./srv/srv-api') } Service */
2
6
 
3
7
  const { EventEmitter } = require('node:events')
4
8
  const { extend, lazify } = require('./lazy')
5
9
 
6
- const cds = module.exports = new class cds extends EventEmitter {
10
+ const cds = module.exports = global.cds = new class cds extends EventEmitter {
11
+
12
+ async emit (eve, ...args) {
13
+ if (eve === 'served') for (let l of this.listeners(eve)) await l.call(this,...args)
14
+ else return super.emit (eve, ...args)
15
+ }
16
+
17
+ /** CLI args */ cli = { command:'', options:{}, argv:[] }
18
+ /** Working dir */ root = process.cwd()
19
+ /** @type Linked */ model = undefined
20
+ /** @type Service */ db = undefined
7
21
 
8
22
  // Configuration & Information
9
23
  get requires() { return super.requires = this.env.requires._resolved() }
@@ -12,8 +26,6 @@ const cds = module.exports = new class cds extends EventEmitter {
12
26
  get env() { return super.env = require('./env/cds-env').for('cds',this.root) }
13
27
  get home() { return super.home = __dirname.slice(0,-4) }
14
28
  get schema() { return super.schema = require('./env/schemas') } // REVISIT: Better move that to cds-dk?
15
- cli = { command:'', options:{}, argv:[] }
16
- root = process.cwd()
17
29
 
18
30
  // Loading and Compiling Models
19
31
  get compiler() { return super.compiler = require('./compile/cdsc') }
@@ -26,10 +38,9 @@ const cds = module.exports = new class cds extends EventEmitter {
26
38
  get extend() { return super.extend = require('./compile/extend') }
27
39
  get deploy() { return super.deploy = require('./dbs/cds-deploy') }
28
40
  get localize() { return super.localize = require('./i18n/localize') }
29
- /** @type {{definitions:{},extensions:[]}} */ model = undefined
30
- /** @type Service */ db = undefined
31
41
 
32
42
  // Model Reflection, Builtin types and classes
43
+ get entities() { return this.db?.entities || this.model?.entities }
33
44
  get reflect() { return super.reflect = this.linked }
34
45
  get linked() { return super.linked = require('./linked/models') }
35
46
  get infer() { return super.infer = require('./ql/infer') }
@@ -47,10 +58,10 @@ const cds = module.exports = new class cds extends EventEmitter {
47
58
  })}
48
59
 
49
60
  // Providing and Consuming Services
50
- /** @type {{ [name:string]: Service }} */
51
- services = Object.defineProperties (new class { *[Symbol.iterator](){ for (let e in this) yield this[e] } },{
52
- _pending: {value:{}}
53
- })
61
+ /** @type { Record<string,Service> } */ services = Object.defineProperties (
62
+ new class { *[Symbol.iterator](){ for (let e in this) yield this[e] } },
63
+ { _pending: {value:{}} }
64
+ )
54
65
  get server() { return super.server = require('../server') }
55
66
  get serve() { return super.serve = require('./srv/cds-serve') }
56
67
  get connect() { return super.connect = require('./srv/cds-connect') }
@@ -60,14 +71,7 @@ const cds = module.exports = new class cds extends EventEmitter {
60
71
  get odata() { return super.odata = require('../libx/odata') }
61
72
  get auth() { return super.auth = require('./auth') }
62
73
 
63
- // awaiting async functions for some events
64
- async emit (eve, ...args) {
65
- if (eve === 'served') for (let l of this.listeners(eve)) await l.call(this,...args)
66
- else return super.emit (eve, ...args)
67
- }
68
-
69
74
  // Core Services API
70
- /** @typedef {import './srv/srv-api'} Service */
71
75
  get Service() { return super.Service = require('./srv/srv-api') }
72
76
  get EventContext() { return super.EventContext = require('./req/context') }
73
77
  get Request() { return super.Request = require('./req/request') }
@@ -85,7 +89,6 @@ const cds = module.exports = new class cds extends EventEmitter {
85
89
  get context() { return this._context._for(this) }
86
90
  set context(_) { this._context._for(this,_) }
87
91
  get spawn() { return super.spawn = this._context.spawn }
88
- tx(..._) { return (this.db || this.Service.prototype) .tx (..._) }
89
92
 
90
93
  // Helpers
91
94
  get utils() { return super.utils = require('./utils/cds-utils') }
@@ -94,31 +97,29 @@ const cds = module.exports = new class cds extends EventEmitter {
94
97
  get test() { return super.test = require('./utils/cds-test') }
95
98
  get log() { return super.log = require('./log/cds-log') }
96
99
  get debug() { return super.debug = this.log.debug }
97
- get lazify(){ return lazify }
98
- get lazified(){ return lazify }
99
- clone(m) { return JSON.parse (JSON.stringify(m)) }
100
+ get lazify() { return lazify }
101
+ get lazified() { return lazify }
102
+ get clone() { return super.clone = this.utils.clone }
100
103
  exit(code){ return cds.shutdown ? cds.shutdown() : process.exit(code) }
101
104
 
102
105
  // Querying and Databases
103
- get ql() { return super.ql = require('./ql/cds-ql') }
104
- get entities() { return this.db?.entities || this.model?.entities }
105
- run (..._) { return (this.db||this.error._no_primary_db).run(..._) }
106
- foreach (..._) { return (this.db||this.error._no_primary_db).foreach(..._) }
107
- stream (..._) { return (this.db||this.error._no_primary_db).stream(..._) }
108
- read (..._) { return (this.db||this.error._no_primary_db).read(..._) }
109
- create (..._) { return (this.db||this.error._no_primary_db).create(..._) }
110
- insert (..._) { return (this.db||this.error._no_primary_db).insert(..._) }
111
- update (..._) { return (this.db||this.error._no_primary_db).update(..._) }
112
- delete (..._) { return (this.db||this.error._no_primary_db).delete(..._) }
113
- disconnect (..._) { return (this.db||this.error._no_primary_db).disconnect(..._) }
114
- transaction (..._) { return (this.db||this.error._no_primary_db).transaction(..._) }
106
+ get ql() { return super.ql = require('./ql/cds-ql') }
107
+ tx (..._) { return (this.db || this.Service.prototype) .tx (..._) }
108
+ run (..._) { return (this.db || this.error._no_primary_db).run(..._) }
109
+ foreach (..._) { return (this.db || this.error._no_primary_db).foreach(..._) }
110
+ stream (..._) { return (this.db || this.error._no_primary_db).stream(..._) }
111
+ read (..._) { return (this.db || this.error._no_primary_db).read(..._) }
112
+ create (..._) { return (this.db || this.error._no_primary_db).create(..._) }
113
+ insert (..._) { return (this.db || this.error._no_primary_db).insert(..._) }
114
+ update (..._) { return (this.db || this.error._no_primary_db).update(..._) }
115
+ upsert (..._) { return (this.db || this.error._no_primary_db).upsert(..._) }
116
+ delete (..._) { return (this.db || this.error._no_primary_db).delete(..._) }
117
+ disconnect (..._) { return (this.db || this.error._no_primary_db).disconnect(..._) }
115
118
 
116
119
  // legacy and to be moved stuff -> hidden for tools in cds.__proto__
117
- /** @deprecated */ in (cwd) { return !cwd ? this : {__proto__:this, cwd, env: this.env.for('cds',cwd) } }
118
- get build() { return () => cds.error('\
119
- This application uses @sap/cds version >= 7, which is not compatible with the installed @sap/cds-dk version 6. \
120
- Either update @sap/cds-dk to version 7 or downgrade @sap/cds to version 6 instead.\
121
- ')}
120
+ /** @deprecated */ transaction (..._) { return (this.db||this.error._no_primary_db).transaction(..._) }
121
+ /** @deprecated */ get build() { return super.build = this.error._outdated_dk }
122
+ /** @deprecated */ get in() { return super.in = cwd => !cwd ? this : {__proto__:this, cwd, env: this.env.for('cds',cwd) } }
122
123
  }
123
124
 
124
125
  // add global cds.ql commands
@@ -128,7 +129,6 @@ extend (global) .with (class {
128
129
  static get UPSERT() { return cds.ql.UPSERT }
129
130
  static get UPDATE() { return cds.ql.UPDATE }
130
131
  static get DELETE() { return cds.ql.DELETE }
131
- static get STREAM() { return cds.ql.STREAM }
132
132
  static get CREATE() { return cds.ql.CREATE }
133
133
  static get DROP() { return cds.ql.DROP }
134
134
  static get CDL() { return cds.parse.CDL }
@@ -136,15 +136,8 @@ extend (global) .with (class {
136
136
  static get CXL() { return cds.parse.CXL }
137
137
  })
138
138
 
139
- // can be used to later check that one has only one cds object
140
- ;(global.__cds_loaded_from ??= new Set).add(__filename)
141
- global.cds = cds // REVISIT: using global.cds seems wrong
142
-
143
139
  // install jest util if jest is defined
144
140
  if (process.env.CDS_JEST_MEM_FIX && typeof jest !== 'undefined') require('./utils/jest.js')
145
141
 
146
142
  // Allow for import cds from '@sap/cds' without esModuleInterop
147
- Object.defineProperties(module.exports, {
148
- default: { value: module.exports },
149
- __esModule: { value: true },
150
- })
143
+ Object.defineProperties(module.exports, { default: {value:module.exports}, __esModule: {value:true} })
@@ -85,3 +85,9 @@ exports._no_primary_db = new Proxy ({},{ get: function fn(_,p) { error (`Not con
85
85
  cds ${process.argv[2]} --in-memory` : ''}`
86
86
 
87
87
  ,{},fn) }})
88
+
89
+
90
+ exports._outdated_dk = () => error `
91
+ This application uses @sap/cds version >= 7, which is not compatible with the installed @sap/cds-dk version 6.
92
+ Either update @sap/cds-dk to version 7 or downgrade @sap/cds to version 6 instead.
93
+ `
@@ -3,6 +3,7 @@ const cds = require('../../..')
3
3
  const $remove = Symbol('remove')
4
4
 
5
5
  const _is_custom_fields = (arg, custom_fields) => {
6
+ if (!Object.keys(arg).length) return false
6
7
  for (const k in arg) if (!(k in custom_fields)) return false
7
8
  return true
8
9
  }
@@ -65,10 +65,14 @@ module.exports = function format(module, level, ...args) {
65
65
  }
66
66
  toLog.timestamp = new Date()
67
67
 
68
+ // start message with leading string args (if any)
69
+ const i = args.findIndex(arg => typeof arg === 'object' && arg.message)
70
+ if (i > 0 && args.slice(0, i).every(arg => typeof arg === 'string')) toLog.msg = args.splice(0, i).join(' ')
71
+
68
72
  // merge toLog with passed Error (or error-like object)
69
73
  if (args.length && typeof args[0] === 'object' && args[0].message) {
70
74
  const err = args.shift()
71
- toLog.msg = err.message
75
+ toLog.msg = `${toLog.msg ? toLog.msg + ' ' : ''}${err.message}`
72
76
  if (typeof err.stack === 'string' && !_is4xx(err)) toLog.stacktrace = err.stack.split(/\s*\r?\n\s*/)
73
77
  if (Array.isArray(err.details))
74
78
  for (const d of err.details)
package/lib/ql/Query.js CHANGED
@@ -33,7 +33,8 @@ class Query {
33
33
  get then() {
34
34
  const srv = this._srv || cds.db || cds.error `Can't execute query as no primary database is connected.`
35
35
  const q = new AsyncResource('await cds.query')
36
- return (r,e) => q.runInAsyncScope (srv.run, srv, this) .then (r,e)
36
+ // Temporary solution for cds.stream in .then. Remove with the next major release.
37
+ return (r,e) => q.runInAsyncScope (srv.run, srv, this) .then(rt => { rt = this._stream && rt ? Object.values(rt)[0] : rt; r(rt) }, e)
37
38
  }
38
39
 
39
40
  _target4 (...args) {
package/lib/ql/cds-ql.js CHANGED
@@ -14,8 +14,7 @@ require = path => { // eslint-disable-line no-global-assign
14
14
  }
15
15
 
16
16
  module.exports = exports = {
17
- Query,
18
- STREAM: require('./STREAM'),
17
+ Query,
19
18
  SELECT: require('./SELECT'),
20
19
  INSERT: require('./INSERT'),
21
20
  UPSERT: require('./UPSERT'),
package/lib/ql/infer.js CHANGED
@@ -11,8 +11,6 @@ function infer (q, defs, _get_source) {
11
11
  || q.UPSERT?.into
12
12
  || q.UPDATE?.entity
13
13
  || q.DELETE?.from
14
- || q.STREAM?.from
15
- || q.STREAM?.into
16
14
  let source = infer_from (from, defs)
17
15
  let target = source?.SELECT ? infer(source, defs) : source
18
16
 
@@ -55,4 +55,4 @@ module.exports = new class extends AsyncLocalStorage {
55
55
  }
56
56
  }
57
57
 
58
- const _error = (err, cds) => { cds.log().error(`ERROR occured in background job:`, err); return err }
58
+ const _error = (err, cds) => { cds.log().error(`ERROR occurred in background job:`, err); return err }
@@ -41,8 +41,7 @@ class Request extends require('./event') {
41
41
  if (q.INSERT) return this._set ('path', _path4 (q.INSERT,'into'))
42
42
  if (q.UPSERT) return this._set ('path', _path4 (q.UPSERT,'into'))
43
43
  if (q.UPDATE) return this._set ('path', _path4 (q.UPDATE,'entity'))
44
- if (q.DELETE) return this._set ('path', _path4 (q.DELETE,'from'))
45
- if (q.STREAM) return this._set ('path', _path4 (q.STREAM,'from') || _path4 (q.STREAM,'into'))
44
+ if (q.DELETE) return this._set ('path', _path4 (q.DELETE,'from'))
46
45
  }
47
46
  const {_} = this
48
47
  if (_.target) return this._set ('path', _.target.name)
@@ -66,8 +65,7 @@ class Request extends require('./event') {
66
65
  q?.INSERT?.into ||
67
66
  q?.UPSERT?.into ||
68
67
  q?.UPDATE?.entity ||
69
- q?.DELETE?.from ||
70
- q?.STREAM?.from || q?.STREAM?.into
68
+ q?.DELETE?.from
71
69
  )
72
70
 
73
71
  const {target} = this; if (!target) return undefined
@@ -171,8 +169,7 @@ const Http2Crud = {
171
169
  DELETE: 'DELETE',
172
170
  }
173
171
 
174
- const SQL2Crud = {
175
- STREAM: 'STREAM',
172
+ const SQL2Crud = {
176
173
  SELECT: 'READ',
177
174
  INSERT: 'CREATE',
178
175
  UPSERT: 'UPSERT',
@@ -55,9 +55,9 @@ function _instrument_cds_services (_get_perf) {
55
55
  const perf = _get_perf(req)
56
56
  if (perf) {
57
57
  const pe = perf.log (this.name, '-', req.event, req.path||'')
58
- var _done = r => { perf.done(pe); return r }
58
+ var _done = () => perf.done(pe)
59
59
  }
60
- return handle.apply (this, arguments) .then (_done)
60
+ return handle.apply (this, arguments) .finally (_done)
61
61
  }
62
62
  }
63
63
 
@@ -1,37 +1,51 @@
1
- const cds = require('../../index'), { decodeURIComponent } = cds.utils
2
- const LOG = cds.log('hcql')
3
1
  const express = require('express') // eslint-disable-line cds/no-missing-dependencies
2
+ const cds = require('../../index')
4
3
 
5
- module.exports = function HCQLAdapter (srv) {
6
-
7
- return express.Router()
8
- .use(express.json()) //> for application/json -> cqn
9
- .use(express.text()) //> for text/plain -> cql -> cqn
10
-
11
- /** Returns CSN schema in response to /<srv>/$csn requests */
12
- .get('/\\$csn', (_, res) => {
13
- let csn = cds.minify (cds.model, { service: srv.name })
14
- res.json(csn)
15
- })
16
-
17
- /** Convenience route for REST-style request formats like that: */
18
- .get('/:entity/:id?(%20:tail)?', (req, _, next) => {
19
- let { entity, id, tail } = req.params, q = SELECT.from(entity, id)
20
- if (is_string(req.body)) tail = req.body
21
- else if (is_array(req.body)) q.columns(req.body)
22
- else Object.assign(q.SELECT, req.body)
23
- if (tail) q = { SELECT: { ...CQL(`SELECT from _ ${tail}`).SELECT, ...q.SELECT } }
24
- req.body = q; next() // delegating to main handler
25
- })
26
-
27
- /** The actual protocol adapter. */
28
- .use((req, res, next) => {
29
- let q = req.body; if (is_string(q)) q = CQL(q)
30
- LOG.info (req.method, decodeURIComponent(req.originalUrl), { ...q })
31
- return srv.run(q).then(r => res.json(r)).catch(next)
32
- })
4
+ class HCQLAdapter extends require('./http') {
33
5
 
6
+ schema4 (srv) {
7
+ return cds.minify (cds.model, { service: srv.name })
8
+ }
9
+
10
+ router4 (srv) { return super.router4 (srv)
11
+
12
+ /** Return CSN schema in response to /<srv>/$csn requests */
13
+ .get('/\\$csn', (_, res) => res.json (this.schema4(srv)))
14
+
15
+ .use(express.json()) //> for application/json -> cqn
16
+ .use(express.text()) //> for text/plain -> cql -> cqn
17
+
18
+ /**
19
+ * Convenience route for REST-style request formats like that:
20
+ * GET /browse/Books { ID, title, author.name as author } where stock < 100
21
+ * GET /browse/Books/201 { ID, title, author.name as author }
22
+ */
23
+ .get('/:entity/:id?(%20:tail)?', (req, _, next) => {
24
+ let { entity, id, tail } = req.params, q = SELECT.from(entity, id)
25
+ if (is_string(req.body)) tail = req.body
26
+ else if (is_array(req.body)) q.columns(req.body)
27
+ else Object.assign(q.SELECT, req.body)
28
+ if (tail) q = { SELECT: { ...CQL(`SELECT from _ ${tail}`).SELECT, ...q.SELECT } }
29
+ req.body = q; next() // delegating to main handler
30
+ })
31
+
32
+ /**
33
+ * The actual protocol adapter, handling all requests.
34
+ */
35
+ .use((req, res, next) => {
36
+ let q = this.query4(req)
37
+ this.log(req,q)
38
+ return srv.run(q).then(r => res.json(r)).catch(next)
39
+ })
40
+ }
41
+
42
+ query4 (req) {
43
+ if (typeof req.body === 'string') return cds.parse.cql (req.body)
44
+ return req.body //> a plain CQN object
45
+ }
34
46
  }
35
47
 
36
48
  const is_string = x => typeof x === 'string'
37
49
  const is_array = Array.isArray
50
+
51
+ module.exports = HCQLAdapter
@@ -0,0 +1,60 @@
1
+ const express = require('express') // eslint-disable-line cds/no-missing-dependencies
2
+ const cds = require('../../index')
3
+ const { inspect } = require('util')
4
+
5
+
6
+ module.exports = class HttpAdapter {
7
+
8
+ constructor (srv) {
9
+ this.kind = this.constructor.name.replace(/Adapter$/,'').toLowerCase()
10
+ this.log = cds.log (this.kind)
11
+ return this.router4 (srv)
12
+ }
13
+
14
+ router4 (srv) {
15
+ return express.Router()
16
+ .use(this.early_access_check4(srv))
17
+ }
18
+
19
+ log (req, cqn) {
20
+ this.log.info (req.method, decodeURIComponent(req.originalUrl), inspect(cqn,{colors:true,depth:11}))
21
+ }
22
+
23
+ /** Does early checks on required roles to reject early */
24
+ early_access_check4 (srv) {
25
+
26
+ // Resolve required roles statically once....
27
+ const d = srv.definition
28
+ const roles = d['@requires'] || d['@restrict']?.map(r => r.to).flat()
29
+ || cds.env.requires.auth?.restrict_all_services !== false && ['authenticated-user']
30
+
31
+ // ... and return a handler function accordingly -> PROBLEM: Extensibility
32
+ if (!roles) return (req, res, next) => next() //> no handlers required
33
+
34
+ const required_roles = Array.isArray(roles) ? roles : [roles]
35
+ return function early_access_check (req, res, next) {
36
+ let u = req.user; if (!u?.is) u = new cds.User(u) // revisit
37
+ if (required_roles.some(r => u.is(r))) return next()
38
+ // Following demonstrates how to directly send responses from here...
39
+ // However, in order to allow others to plug in error handlers throwing errors is better.
40
+ // For example, also for ourselves to obfucscate error details in production mode.
41
+ // throw cds.error ({ status: 403, code: 'REQUIRES_AUTH_USER', details: `Requires any of [ ${roles} ]` })
42
+ if (!u._is_anonymous) return res.status(403).send(`User '${u.id}' is lacking required roles: [ ${roles} ]`)
43
+ else if (!req._login) return res.status(401).send('Requires authenticated user')
44
+ else return req._login()
45
+ }
46
+ }
47
+ }
48
+
49
+ /*
50
+ function MTXRouter (srv) {
51
+ this.routers = {
52
+ t1_fta: 'new HCQLAdapter(srv.for(t1))',
53
+ t1_fta_ftb: 'new HCQLAdapter(srv.for(t1))',
54
+ t2: 'new HCQLAdapter(srv.for(t2))',
55
+ }
56
+ return express.Router().use((req,res,next) => {
57
+ return this.routers[req.tenant].handle(req,res,next)
58
+ })
59
+ }
60
+ */
@@ -185,12 +185,5 @@ const _slugified = name => (
185
185
  )
186
186
 
187
187
 
188
- const using_old_paths = (path) => `PLEASE NOTE:\n
189
- With @sap/cds version 7, default service paths have changed to '${path}/<srv>'.
190
- If you use SAP Fiori Elements, make sure to adapt the 'dataSources.uri' paths
191
- in 'manifest.json' files accordingly. For more information, see the release notes at
192
- https://cap.cloud.sap/docs/releases/jun23.
193
- `
194
-
195
188
  module.exports = new Protocols
196
189
  if (!cds.requires.middlewares) require('./_legacy')
@@ -29,10 +29,16 @@ module.exports = function ODataAdapter(srv) {
29
29
  router.use(/^\/$/, require('../../../libx/odata/service-document')(srv))
30
30
  router.use('/\\$metadata', require('../../../libx/odata/metadata')(srv))
31
31
  router.get('*', require('../../../libx/odata/read')(srv))
32
+ router.delete('*', require('../../../libx/odata/delete')(srv))
33
+ // REVISIT do we want to build our own body parser logic?
34
+ router.use(express.json())
35
+ router.post('*', require('../../../libx/odata/create')(srv))
36
+ router.put('*', require('../../../libx/odata/update')(srv))
37
+ router.patch('*', require('../../../libx/odata/update')(srv))
32
38
  router.use(require('../../../libx/odata/error')(srv))
39
+ } else {
40
+ router.use(libx.to.odata_v4(srv))
33
41
  }
34
42
 
35
- router.use(libx.to.odata_v4(srv))
36
-
37
43
  return router
38
44
  }