@sap/cds 7.6.4 → 7.7.1

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 (97) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/_i18n/i18n.properties +3 -0
  3. package/app/index.js +14 -8
  4. package/bin/serve.js +51 -19
  5. package/common.cds +16 -0
  6. package/lib/auth/ias-auth.js +2 -2
  7. package/lib/auth/index.js +1 -1
  8. package/lib/auth/jwt-auth.js +1 -1
  9. package/lib/compile/cdsc.js +23 -11
  10. package/lib/compile/for/nodejs.js +2 -2
  11. package/lib/compile/for/odata.js +4 -0
  12. package/lib/compile/load.js +7 -2
  13. package/lib/compile/to/sql.js +3 -0
  14. package/lib/dbs/cds-deploy.js +197 -220
  15. package/lib/env/defaults.js +2 -1
  16. package/lib/index.js +8 -2
  17. package/lib/linked/types.js +1 -0
  18. package/lib/log/format/json.js +4 -1
  19. package/lib/plugins.js +2 -2
  20. package/lib/ql/SELECT.js +8 -8
  21. package/lib/req/context.js +22 -13
  22. package/lib/req/request.js +10 -4
  23. package/lib/srv/cds-connect.js +9 -3
  24. package/lib/srv/cds-serve.js +5 -3
  25. package/lib/srv/middlewares/ctx-model.js +1 -1
  26. package/lib/srv/protocols/odata-v4.js +38 -9
  27. package/lib/srv/srv-api.js +98 -140
  28. package/lib/srv/srv-models.js +2 -2
  29. package/lib/srv/srv-tx.js +1 -0
  30. package/lib/utils/cds-utils.js +32 -23
  31. package/lib/utils/data.js +1 -1
  32. package/lib/utils/tar.js +1 -1
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -2
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +18 -1
  36. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -1
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +7 -3
  38. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  39. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/index.js +5 -0
  40. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +71 -25
  41. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +10 -2
  42. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +6 -1
  43. package/libx/_runtime/cds-services/util/assert.js +50 -240
  44. package/libx/_runtime/cds.js +5 -0
  45. package/libx/_runtime/common/aspects/any.js +53 -45
  46. package/libx/_runtime/common/generic/input.js +14 -10
  47. package/libx/_runtime/common/generic/paging.js +1 -1
  48. package/libx/_runtime/common/utils/cqn.js +1 -1
  49. package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
  50. package/libx/_runtime/common/utils/keys.js +1 -1
  51. package/libx/_runtime/common/utils/quotingStyles.js +1 -1
  52. package/libx/_runtime/common/utils/resolveStructured.js +4 -1
  53. package/libx/_runtime/common/utils/rewriteAsterisks.js +5 -12
  54. package/libx/_runtime/common/utils/stream.js +2 -16
  55. package/libx/_runtime/common/utils/streamProp.js +16 -6
  56. package/libx/_runtime/common/utils/ucsn.js +1 -0
  57. package/libx/_runtime/db/expand/expandCQNToJoin.js +1 -1
  58. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  59. package/libx/_runtime/db/utils/columns.js +6 -1
  60. package/libx/_runtime/fiori/generic/activate.js +11 -3
  61. package/libx/_runtime/fiori/generic/edit.js +8 -2
  62. package/libx/_runtime/fiori/lean-draft.js +94 -30
  63. package/libx/_runtime/hana/execute.js +2 -5
  64. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +12 -22
  65. package/libx/_runtime/messaging/service.js +6 -2
  66. package/libx/common/assert/index.js +232 -0
  67. package/libx/common/assert/type.js +109 -0
  68. package/libx/common/assert/utils.js +125 -0
  69. package/libx/common/assert/validation.js +109 -0
  70. package/libx/odata/index.js +5 -5
  71. package/libx/odata/middleware/create.js +83 -0
  72. package/libx/odata/middleware/delete.js +38 -0
  73. package/libx/odata/middleware/error.js +8 -0
  74. package/libx/odata/{metadata.js → middleware/metadata.js} +8 -6
  75. package/libx/odata/middleware/operation.js +78 -0
  76. package/libx/odata/middleware/parse.js +11 -0
  77. package/libx/odata/{read.js → middleware/read.js} +42 -20
  78. package/libx/odata/{service-document.js → middleware/service-document.js} +2 -1
  79. package/libx/odata/middleware/stream.js +237 -0
  80. package/libx/odata/middleware/update.js +165 -0
  81. package/libx/odata/{afterburner.js → parse/afterburner.js} +79 -29
  82. package/libx/odata/{cqn2odata.js → parse/cqn2odata.js} +5 -3
  83. package/libx/odata/{parseToCqn.js → parse/parseToCqn.js} +3 -6
  84. package/libx/odata/{utils.js → utils/index.js} +95 -9
  85. package/libx/outbox/index.js +2 -1
  86. package/libx/rest/RestAdapter.js +0 -1
  87. package/libx/rest/middleware/operation.js +6 -4
  88. package/libx/rest/middleware/parse.js +20 -2
  89. package/package.json +1 -1
  90. package/server.js +43 -71
  91. package/libx/odata/create.js +0 -44
  92. package/libx/odata/delete.js +0 -25
  93. package/libx/odata/error.js +0 -12
  94. package/libx/odata/update.js +0 -110
  95. /package/libx/odata/{grammar.peggy → parse/grammar.peggy} +0 -0
  96. /package/libx/odata/{parser.js → parse/parser.js} +0 -0
  97. /package/libx/odata/{result.js → utils/result.js} +0 -0
@@ -95,11 +95,11 @@ class EventContext {
95
95
  if (pd?.value) this[p] = pd.value
96
96
  }
97
97
  let user = u instanceof cds.User ? Object.create(u,{
98
- tenant: {get:()=> this.tenant},
99
- locale: {get:()=> this.locale},
98
+ tenant: {get:()=> cds.utils.deprecated (() => this.tenant, {kind: 'Property', old: 'req.user.tenant', use: 'req.tenant'})()},
99
+ locale: {get:()=> cds.utils.deprecated (() => this.locale, {kind: 'Property', old: 'req.user.locale', use: 'req.locale'})()},
100
100
  }) : Object.defineProperties (new cds.User(u), {
101
- tenant: {get:()=> this.tenant},
102
- locale: {get:()=> this.locale},
101
+ tenant: {get:()=> cds.utils.deprecated (() => this.tenant, {kind: 'Property', old: 'req.user.tenant', use: 'req.tenant'})()},
102
+ locale: {get:()=> cds.utils.deprecated (() => this.locale, {kind: 'Property', old: 'req.user.locale', use: 'req.locale'})()},
103
103
  })
104
104
  super.user = user
105
105
  }
@@ -185,17 +185,26 @@ const _anonymous = new cds.User.default
185
185
 
186
186
  class Features {
187
187
  static for (x) { // normalizes features to an object
188
- if (!x) return
188
+ if (x == null) return
189
189
  if (x === '*') return this.all
190
- const fts = new this
191
- if (Array.isArray(x)) { for (let f of x) fts[f] = true }
192
- else if (typeof x === 'object') { for (let f in x) if (x[f]) fts[f] = true }
193
- else if (typeof x === 'string') { for (let f of x.split(',')) fts[f] = true }
194
- return fts
195
- }
196
- get $hash() { return super.$hash = Object.keys(this).join(',') }
190
+ if (Array.isArray(x)) ; //> go on below
191
+ else if (typeof x === 'object') x = Object.keys(x)
192
+ else if (typeof x === 'string') x = x.split(',')
193
+ if (x.length) return Object.assign (new this, x.reduce((o,f)=>{o[f]=true;return o},{}))
194
+ }
195
+ get given() { return true }
196
+ get $hash() { let h = Object.keys(this).join(','); Object.defineProperty(this,'$hash',{value:h}); return h }
197
+ includes(t) { return t in this }
198
+ has(t) { return t in this }
199
+ map(..._) { return Object.keys(this).map(..._) }
200
+ find(filter) { for (let t in this) if (filter(t)) return t }
201
+ some(filter) { for (let t in this) if (filter(t)) return true }
202
+ every(filter) { for (let t in this) if (!filter(t)) return false }
197
203
  static all = new Proxy ({'*':true},{ has:() => true, get:(_,p) => p === '$hash' ? '*' : true })
198
- static none = new class none extends Features {}
204
+ static none = new class none extends Features {
205
+ get given(){ return false }
206
+ get $hash(){ return '' }
207
+ }
199
208
  }
200
209
 
201
210
  EventContext.prototype._set('_propagated', Object.seal({}))
@@ -1,5 +1,5 @@
1
1
  const { Responses, Errors } = require('./response')
2
- const cds = require('../../lib')
2
+ const cds = require('../../lib'), {production} = cds.env
3
3
 
4
4
  /**
5
5
  * Class Request represents requests received via synchronous protocols.
@@ -41,7 +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'))
44
+ if (q.DELETE) return this._set ('path', _path4 (q.DELETE,'from'))
45
45
  }
46
46
  const {_} = this
47
47
  if (_.target) return this._set ('path', _.target.name)
@@ -127,7 +127,13 @@ class Request extends require('./event') {
127
127
  reject (...args) {
128
128
  if (args.length === 0 && this.errors) {
129
129
  let errs = this.errors
130
- throw errs.length === 1 ? errs[0] : new cds.error ('MULTIPLE_ERRORS', { details: errs }, this.reject)
130
+ if (errs.length === 1) throw errs[0]
131
+ let me = new cds.error ('MULTIPLE_ERRORS', { details: errs }, this.reject)
132
+ if (!production) me.stack += errs.map (e => '\n---------------------------------\n'+ e.stack
133
+ .replace('\n',': '+ (e.element||e.target||'')+'\n---------------------------------\n')
134
+ .replace(/^Error: /,'')
135
+ ).join('')
136
+ throw me
131
137
  }
132
138
  if (args[0] === 401 && this._.req?._login) this._.req._login()
133
139
  let e = this.error(...args)
@@ -169,7 +175,7 @@ const Http2Crud = {
169
175
  DELETE: 'DELETE',
170
176
  }
171
177
 
172
- const SQL2Crud = {
178
+ const SQL2Crud = {
173
179
  SELECT: 'READ',
174
180
  INSERT: 'CREATE',
175
181
  UPSERT: 'UPSERT',
@@ -22,7 +22,7 @@ const connect = module.exports = async function cds_connect (options) {
22
22
  * @returns { Promise<import('./srv-api')> }
23
23
  */
24
24
  connect.to = async (datasource, options) => {
25
- TRACE?.time(`cds.connect ${datasource} `)
25
+ TRACE?.time(`cds.connect.to ${datasource}`.padEnd(22).slice(0,22))
26
26
  let Service = cds.service.factory, _done = x=>x
27
27
  if (typeof datasource === 'object') [options,datasource] = [datasource]
28
28
  else if (datasource) {
@@ -43,12 +43,18 @@ connect.to = async (datasource, options) => {
43
43
  throw new Error (`No service definition found for '${required.service || datasource}'`)
44
44
  }
45
45
  // construct new service instance
46
- let srv = await new Service (datasource,m,o); await srv._init()
46
+ let srv
47
+ try {
48
+ srv = await new Service (datasource,m,o); await srv._init()
49
+ } catch (e) {
50
+ _pending[datasource] = Promise.reject(e)
51
+ throw e
52
+ }
47
53
  if (o.outbox) srv = cds.outboxed(srv)
48
54
  if (datasource === 'db') cds.db = srv
49
55
  _done (cds.services[datasource] = srv)
50
56
  if (!o.silent) cds.emit ('connect',srv)
51
- TRACE?.timeEnd(`cds.connect ${datasource} `)
57
+ TRACE?.timeEnd(`cds.connect.to ${datasource}`.padEnd(22).slice(0,22))
52
58
  return srv
53
59
  }
54
60
 
@@ -3,11 +3,13 @@ const { Service } = cds.service.factory
3
3
  const { serve } = cds.service.protocols
4
4
  const _ready = Symbol(), _pending = cds.services._pending || {}
5
5
  const TRACE = cds.debug('trace')
6
+ if (TRACE && !cds.env.features.odata_new_adapter) require('./../../libx/_runtime').to.odata_v4
7
+
6
8
 
7
9
  /** @param som - a service name or a model (name or csn) */
8
10
  module.exports = function cds_serve (som, _options) { // NOSONAR
9
11
 
10
- TRACE?.time(`cds.serve ${som} `)
12
+ TRACE?.time(`cds.serve ${som}`.padEnd(22).slice(0,22))
11
13
 
12
14
  if (som && typeof som === 'object' && !is_csn(som) && !is_files(som)) {
13
15
  [som,_options] = [undefined,
@@ -102,7 +104,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
102
104
  ready = ready.then (()=> all.forEach (each => {
103
105
  const d = each.definition
104
106
  if (d['@protocol'] === 'none' || d['@cds.api.ignore']) return each._is_dark = true
105
- serve (each, /*in:*/ app)
107
+ else serve (each, /*to:*/ app)
106
108
  if (!o.silent) cds.emit ('serving',each)
107
109
  }))
108
110
  return this
@@ -115,7 +117,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
115
117
  * let CatalogService = await cds.serve(...) // single only
116
118
  */
117
119
  then: (_resolve, _error) => ready.then ((s)=>{
118
- TRACE?.timeEnd('cds.serve '+som+' ')
120
+ TRACE?.timeEnd(`cds.serve ${som}`.padEnd(22).slice(0,22))
119
121
  if (all.length === 0) return _resolve()
120
122
  if (all.length === 1) return _resolve(Object.defineProperty(s=all[0],s.name,{value:s}))
121
123
  else return _resolve (all.reduce ((r,s)=>{ r[s.name]=s; return r },{}))
@@ -8,7 +8,7 @@ module.exports = ()=> {
8
8
  return async function cds_context_model (req,res, next) {
9
9
  if (req.baseUrl.startsWith('/-/')) return next() //> our own tech services cannot be extended
10
10
  const ctx = cds.context
11
- if (ctx.tenant || ctx.features) try {
11
+ if (ctx.tenant || ctx.features?.given) try {
12
12
  // if (req.headers.features) ctx.user.features = req.headers.features //> currently done in basic-auth only
13
13
  ctx.model = req.__model = await model4 (ctx.tenant, ctx.features) // REVISIT: req.__model is because of Okra
14
14
  } catch (e) {
@@ -4,6 +4,7 @@ const cds = require('../../index'),
4
4
  const libx = require('../../../libx/_runtime')
5
5
  const LOG = cds.log('odata')
6
6
  const express = require('express') // eslint-disable-line cds/no-missing-dependencies
7
+ const { isStream, stream } = require('../../../libx/odata/middleware/stream')
7
8
 
8
9
  module.exports = function ODataAdapter(srv) {
9
10
  const router = express.Router()
@@ -25,17 +26,45 @@ module.exports = function ODataAdapter(srv) {
25
26
  })
26
27
 
27
28
  if (cds.env.features.odata_new_adapter) {
29
+ // REVISIT: odata_new_adapter shouldn't influence odata_new_parser and cds_assert, but we'd need to set marker in cds.context or so to remove this
30
+ cds.env.features.odata_new_parser = true
31
+ cds.env.features.cds_assert = true
28
32
  // REVISIT: add middleware for negative cases?
29
- router.use(/^\/$/, require('../../../libx/odata/service-document')(srv))
30
- router.use('/\\$metadata', require('../../../libx/odata/metadata')(srv))
31
- router.get('*', require('../../../libx/odata/read')(srv))
32
- router.delete('*', require('../../../libx/odata/delete')(srv))
33
+ // service root
34
+ router.use(/^\/$/, require('../../../libx/odata/middleware/service-document')(srv))
35
+ router.use('/\\$metadata', require('../../../libx/odata/middleware/metadata')(srv))
36
+ // parse
37
+ router.use(require('../../../libx/odata/middleware/parse')(srv))
33
38
  // 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))
38
- router.use(require('../../../libx/odata/error')(srv))
39
+ router.use((req, res, next) => {
40
+ const method = req.method
41
+ if (method !== 'PUT') {
42
+ return express.json()(req, res, next)
43
+ }
44
+
45
+ const { _query: query } = req
46
+ if (isStream(query)) {
47
+ req.body = { value: req }
48
+ return next(null, req, res)
49
+ }
50
+
51
+ // TODO: check if the raw body still exists, then we can remove deepCopy() in the handlers
52
+ return express.json()(req, res, next)
53
+ })
54
+
55
+ // handle
56
+ // REVISIT: with old adapter, we return 405 for HEAD requests -> check OData spec
57
+ router.head('*', (_, res) => res.sendStatus(405))
58
+ router.post('*', require('../../../libx/odata/middleware/operation')(srv)) //> action
59
+ router.get('*', require('../../../libx/odata/middleware/operation')(srv)) //> function
60
+ router.post('*', require('../../../libx/odata/middleware/create')(srv))
61
+ router.get('*', stream(srv))
62
+ router.get('*', require('../../../libx/odata/middleware/read')(srv))
63
+ router.put('*', require('../../../libx/odata/middleware/update')(srv))
64
+ router.patch('*', require('../../../libx/odata/middleware/update')(srv))
65
+ router.delete('*', require('../../../libx/odata/middleware/delete')(srv))
66
+ // error
67
+ router.use(require('../../../libx/odata/middleware/error')(srv))
39
68
  } else {
40
69
  router.use(libx.to.odata_v4(srv))
41
70
  }
@@ -1,18 +1,21 @@
1
- const cds = require('..'),
2
- { Event, Request } = cds
3
- const add_methods_to = require('./srv-methods')
4
- const LOG = cds.log()
1
+ //////////////////////////////////////////////////////////////
2
+ //
3
+ // PLEASE DO NOT RUN prettier ON THIS FILE
4
+ //
5
+ //////////////////////////////////////////////////////////////
6
+
7
+ const cds = require('..'), { Event, Request } = cds
8
+ const add_methods_to = require ('./srv-methods')
5
9
 
6
10
  class Service extends require('./srv-handlers') {
7
- constructor(name, model, o) {
11
+
12
+ constructor (name, model, o) {
8
13
  if (is_object(name)) {
9
- ;[model, o] = [name, model]
10
- let srv =
11
- cds.linked(model).services[0] ||
12
- cds.error.expected`${{ model }} passed as first argument to be a CSN with a single service definition`
14
+ [ model, o ] = [ name, model ]
15
+ let srv = cds.linked(model).services[0] || cds.error.expected `${{model}} passed as first argument to be a CSN with a single service definition`
13
16
  name = srv.name
14
17
  }
15
- super(name || new.target.name).options = o || (o = {})
18
+ super (name || new.target.name) .options = o || (o={})
16
19
  if (o.kind) this.kind = o.kind // shortcut
17
20
  if (model) this.model = model
18
21
  }
@@ -20,11 +23,11 @@ class Service extends require('./srv-handlers') {
20
23
  /**
21
24
  * Subclasses may override this to prepare the given model appropriately
22
25
  */
23
- set model(csn) {
26
+ set model (csn) {
24
27
  if (csn) {
25
- let { definitions: defs = {} } = (super.model = cds.compile.for.nodejs(csn))
28
+ let {definitions:defs={}} = super.model = cds.compile.for.nodejs(csn)
26
29
  super.definition = defs[this.options?.service] || defs[this.name]
27
- add_methods_to(this)
30
+ add_methods_to (this)
28
31
  } else {
29
32
  super.model = undefined
30
33
  }
@@ -33,166 +36,101 @@ class Service extends require('./srv-handlers') {
33
36
  /**
34
37
  * Messaging API to emit asynchronous event messages, i.e. instances of `cds.Event`.
35
38
  */
36
- emit(event, data, headers) {
37
- const eve = event instanceof Event ? event : new Event(is_object(event) ? event : { event, data, headers })
38
- return this.dispatch(eve)
39
+ emit (event, data, headers) {
40
+ const eve = event instanceof Event ? event : new Event (
41
+ is_object(event) ? event
42
+ : { event, data, headers }
43
+ )
44
+ return this.dispatch (eve)
39
45
  }
40
46
 
41
47
  /**
42
48
  * REST-style API to send synchronous requests...
43
49
  */
44
- send(method, path, data, headers) {
45
- const req =
46
- method instanceof Request
47
- ? method
48
- : new Request(
49
- is_object(method)
50
- ? method
51
- : is_object(path)
52
- ? { method, data: path, headers: data }
53
- : { method, path, data, headers }
54
- )
55
- return this.dispatch(req)
56
- }
57
- get(...args) {
58
- return is_rest(args[0]) ? this.send('GET', ...args) : this.read(...args)
59
- }
60
- put(...args) {
61
- return is_rest(args[0]) ? this.send('PUT', ...args) : this.update(...args)
62
- }
63
- post(...args) {
64
- return is_rest(args[0]) ? this.send('POST', ...args) : this.create(...args)
65
- }
66
- patch(...args) {
67
- return is_rest(args[0]) ? this.send('PATCH', ...args) : this.update(...args)
68
- }
69
- delete(...args) {
70
- return is_rest(args[0]) ? this.send('DELETE', ...args) : DELETE.from(...args).bind(this)
71
- }
50
+ send (method, path, data, headers) {
51
+ const req = method instanceof Request ? method : new Request (
52
+ is_object(method) ? method :
53
+ is_object(path) ? { method, data:path, headers:data }
54
+ : { method, path, data, headers }
55
+ )
56
+ return this.dispatch (req)
57
+ }
58
+ get (...args) { return is_rest(args[0]) ? this.send('GET', ...args) : this.read (...args) }
59
+ put (...args) { return is_rest(args[0]) ? this.send('PUT', ...args) : this.update (...args) }
60
+ post (...args) { return is_rest(args[0]) ? this.send('POST', ...args) : this.create (...args) }
61
+ patch (...args) { return is_rest(args[0]) ? this.send('PATCH', ...args) : this.update (...args) }
62
+ delete (...args) { return is_rest(args[0]) ? this.send('DELETE',...args) : DELETE.from (...args).bind(this) }
72
63
 
73
64
  /**
74
65
  * Querying API to send synchronous requests...
75
66
  */
76
- run(query, data) {
67
+ run (query, data) {
77
68
  if (typeof query === 'function') {
78
- const ctx = cds.context,
79
- fn = query
80
-
81
- if (!ctx?.tx) return this.tx(fn) // run fn with root tx
82
- else {
83
- if (!ctx.tx._done) return fn(this.tx(ctx)) // run fn with nested tx
84
- else if (ctx.tx._done === 'rolled back')
85
- // > reject
86
- ctx.tx._throw_closed_error()
87
- else return this.tx(fn) // run fn with detached root tx
88
- }
69
+ const ctx = cds.context, fn = query
70
+ if (!ctx?.tx) return this.tx(fn) // run fn with root tx
71
+ if (!ctx.tx._done) return fn(this.tx(ctx)) // run fn with nested tx
72
+ if (ctx.tx._done === 'rolled back') ctx.tx._throw_closed_error()
73
+ else return this.tx(fn) // run fn with detached root tx
89
74
  }
90
-
91
- const req = new Request({ query, data })
92
- return this.dispatch(req)
93
- }
94
- read(...args) {
95
- return is_query(args[0]) ? this.run(...args) : SELECT(...args).bind(this)
96
- }
97
- // Deprecated: To be removed with the next major release
98
- stream(...args) {
99
- cds._logDeprecation('stream method is deprecated and will be removed in upcoming releases!')
100
- if (is_query(args[0])) {
101
- Object.defineProperty(args[0], '_stream', { value: true, enumerable: false })
102
- if (cds.env.features.stream_compat)
103
- Object.defineProperty(args[0], '_streaming', { value: true, enumerable: false })
104
- return this.run(...args).then(result => (result ? Object.values(result)[0] : result))
105
- }
106
- const q = (args ? SELECT.one.columns(args) : SELECT.one).bind(this)
107
- Object.defineProperty(q, '_stream', { value: true, enumerable: false })
108
- if (cds.env.features.stream_compat) Object.defineProperty(q, '_streaming', { value: true, enumerable: false })
109
- return q
110
- }
111
- insert(...args) {
112
- return INSERT(...args).bind(this)
113
- }
114
- create(...args) {
115
- return INSERT.into(...args).bind(this)
116
- }
117
- update(...args) {
118
- return UPDATE.entity(...args).bind(this)
119
- }
120
- upsert(...args) {
121
- return UPSERT(...args).bind(this)
122
- }
123
- exists(...args) {
124
- return SELECT.one([1])
125
- .from(...args)
126
- .bind(this)
75
+ const req = new Request ({ query, data })
76
+ return this.dispatch (req)
127
77
  }
78
+ read (...args) { return is_query(args[0]) ? this.run(...args) : SELECT(...args).bind(this) }
79
+ insert (...args) { return INSERT(...args).bind(this) }
80
+ create (...args) { return INSERT.into(...args).bind(this) }
81
+ update (...args) { return UPDATE.entity(...args).bind(this) }
82
+ upsert (...args) { return UPSERT(...args).bind(this) }
83
+ exists (...args) { return SELECT.one([1]).from(...args).bind(this) }
128
84
 
129
85
  /**
130
86
  * Streaming API variant of .run(). Subclasses should override this to support real streaming.
131
87
  * The default implementation doesn't stream, but simply invokes the callback on each row.
132
88
  * The callback function is invoked with (row, index).
133
89
  */
134
- foreach(query, data, callback) {
135
- if (!callback) [data, callback] = [undefined, data]
136
- return this.run(query, data).then(rows => rows.forEach(callback) || rows)
90
+ foreach (query, data, callback) {
91
+ if (!callback) [ data, callback ] = [ undefined, data ]
92
+ return this.run (query, data) .then (rows => rows.forEach(callback) || rows)
137
93
  }
138
94
 
139
95
  /**
140
96
  * Model Reflection API...
141
97
  */
142
- get namespace() {
143
- return (super.namespace =
144
- this.definition?.name ||
145
- this.model?.namespace ||
146
- (!this.isDatabaseService && !/\W/.test(this.name) && this.name) ||
147
- undefined)
148
- }
149
-
150
- get operations() {
151
- return (super.operations = _reflect(this, d => d.kind === 'action' || d.kind === 'function'))
152
- }
153
- get entities() {
154
- return (super.entities = _reflect(this, d => d.kind === 'entity'))
155
- }
156
- get events() {
157
- return (super.events = _reflect(this, d => d.kind === 'event'))
158
- }
159
- get types() {
160
- return (super.types = _reflect(this, d => !d.kind || d.kind === 'type'))
98
+ get namespace() {
99
+ return super.namespace = this.definition?.name
100
+ || this.model?.namespace
101
+ || !this.isDatabaseService && !/\W/.test(this.name) && this.name
102
+ || undefined
161
103
  }
104
+ get operations() { return super.operations = _reflect (this, d => d.kind === 'action' || d.kind === 'function') }
105
+ get entities() { return super.entities = _reflect (this, d => d.kind === 'entity') }
106
+ get events() { return super.events = _reflect (this, d => d.kind === 'event') }
107
+ get types() { return super.types = _reflect (this, d => !d.kind || d.kind === 'type') }
162
108
 
163
109
  /**
164
110
  * Flag to control whether this service is extensible.
165
111
  * Can be overridden by subclasses.
166
- * REVISIT cds.xt name check should move to respective services
167
112
  */
168
113
  get isExtensible() {
169
- return !this.name?.startsWith('cds.xt.') && this.model === cds.model
114
+ return this.model === cds.model && !this.name?.startsWith('cds.xt.') // REVISIT cds.xt name check should move to respective services
170
115
  }
171
116
 
172
117
  /**
173
- * Subclasses may override this to free private resources
118
+ * Subclasses may override this to free resources when
119
+ * tenants offboard or the service is disposed.
174
120
  */
175
- disconnect(tenant) {
176
- if (this === cds.db) {
177
- if (tenant && cds.db.dbcs) cds.db.dbcs.delete[tenant]
178
- else if (!tenant) cds.db = undefined //> REVISIT: should go into DatabaseService
179
- }
180
- delete cds.services[this.name]
121
+ disconnect (tenant) { // eslint-disable-line no-unused-vars
122
+ // if (this === cds.db) { //> REVISIT: should go into DatabaseService
123
+ // if (!tenant) cds.db = undefined
124
+ // else if (this.dbcs) this.dbcs.delete[tenant] // This code is obviously wrong and never tested -> is that required ?!?
125
+ // }
126
+ // delete cds.services[this.name] // REVISIT: this is in contrast to some tests
181
127
  }
182
128
 
183
- get path() {
184
- return (super.path = cds.service.protocols.path4(this))
185
- }
186
- set path(p) {
187
- super.path = p
188
- }
129
+ get path() { return super.path = cds.service.protocols.path4(this) }
130
+ set path(p) { super.path = p }
189
131
 
190
- get endpoints() {
191
- return (super.endpoints = cds.service.protocols.endpoints4(this))
192
- }
193
- set endpoints(p) {
194
- super.endpoints = p
195
- }
132
+ get endpoints() { return super.endpoints = cds.service.protocols.endpoints4(this) }
133
+ set endpoints(p) { super.endpoints = p }
196
134
  }
197
135
 
198
136
  const { dispatch, handle } = require('./srv-dispatch')
@@ -205,8 +143,28 @@ Service.prototype._is_service_instance = Service._is_service_class = true //> fo
205
143
  module.exports = Service
206
144
 
207
145
  // Helpers...
208
- const _reflect = (srv, filter) => (!srv.model ? [] : srv.model.childrenOf(srv.namespace, filter))
146
+ const _reflect = (srv,filter) => !srv.model ? [] : srv.model.childrenOf (srv.namespace,filter)
209
147
  const is_rest = x => x && typeof x === 'string' && x[0] === '/'
210
- const is_query = x => (x && x.bind) || (is_array(x) && !x.raw)
211
- const is_array = x => Array.isArray(x) && !x.raw
212
- const is_object = x => typeof x === 'object'
148
+ const is_query = x => x && x.bind || is_array(x) && !x.raw
149
+ const is_array = (x) => Array.isArray(x) && !x.raw
150
+ const is_object = (x) => typeof x === 'object'
151
+
152
+ // Deprecated
153
+ /** @deprecated: To be removed with the next major release */
154
+ Service.prototype.stream = cds.utils.deprecated (function (...args) {
155
+ if (is_query(args[0])) {
156
+ Object.defineProperty(args[0], '_stream', { value: true, enumerable: false })
157
+ if (cds.env.features.stream_compat)
158
+ Object.defineProperty(args[0], '_streaming', { value: true, enumerable: false })
159
+ return this.run(...args).then(result => {
160
+ if (!result) return result
161
+ return Array.isArray(result) ? Object.values(result[0])[0] : Object.values(result)[0]})
162
+ }
163
+ const q = (args ? SELECT.one.columns(args) : SELECT.one).bind(this)
164
+ Object.defineProperty(q, '_stream', { value: true, enumerable: false })
165
+ if (cds.env.features.stream_compat) Object.defineProperty(q, '_streaming', { value: true, enumerable: false })
166
+ return q
167
+ }, {
168
+ old: 'srv.stream()',
169
+ use: 'srv.read()'
170
+ })
@@ -182,9 +182,9 @@ if (!extensibility) {
182
182
 
183
183
  // helper to get model for tenant/features
184
184
  const _is_extended = extensibility ? ()=> cds.db.exists('cds.xt.Extensions') : ()=> false
185
- const _get_model4 = (tenant, toggles) => {
185
+ const _get_model4 = (tenant, features) => {
186
186
  const { 'cds.xt.ModelProviderService':mps } = cds.services
187
- return mps.getCsn (tenant, toggles) .then (cds.compile.for.nodejs)
187
+ return mps.getCsn (tenant, features) .then (cds.compile.for.nodejs)
188
188
  }
189
189
 
190
190
 
package/lib/srv/srv-tx.js CHANGED
@@ -58,6 +58,7 @@ class Transaction {
58
58
  return tx
59
59
  }
60
60
 
61
+ /** @param {import('./srv-api')} srv */
61
62
  constructor (srv, ctx) {
62
63
  const tx = { __proto__:srv, _kind: new.target.name, context: ctx }
63
64
  const proto = new.target.prototype
@@ -28,16 +28,6 @@ exports.Object_keys = o => ({
28
28
  })
29
29
 
30
30
 
31
- /**
32
- * CSN-aware function to deep clone objectx
33
- */
34
- exports.clone = (x) => {
35
- const y = structuredClone(x)
36
- if (x.$sources) Object.defineProperty (y, '$sources', { value: x.$sources })
37
- return y
38
- }
39
-
40
-
41
31
  /**
42
32
  * Simple helper to always access results as arrays.
43
33
  */
@@ -76,9 +66,9 @@ exports.exists = function exists (x) {
76
66
  }
77
67
 
78
68
  // REVISIT naming: doesn't return boolean
79
- exports.isdir = function isdir (x) {
80
- if (x) try {
81
- const y = resolve (cds.root,x)
69
+ exports.isdir = function isdir (...args) {
70
+ if (args.length) try {
71
+ const y = resolve (cds.root,...args)
82
72
  const ls = fs.lstatSync(y)
83
73
  if (ls.isDirectory()) return y
84
74
  if (ls.isSymbolicLink()) return isdir (join (dirname(y), fs.readlinkSync(y)))
@@ -86,9 +76,9 @@ exports.isdir = function isdir (x) {
86
76
  }
87
77
 
88
78
  // REVISIT naming: doesn't return boolean
89
- exports.isfile = function isfile (x) {
90
- if (x) try {
91
- const y = resolve (cds.root,x)
79
+ exports.isfile = function isfile (...args) {
80
+ if (args.length) try {
81
+ const y = resolve (cds.root,...args)
92
82
  const ls = fs.lstatSync(y)
93
83
  if (ls.isFile()) return y
94
84
  if (ls.isSymbolicLink()) return isfile (join (dirname(y), fs.readlinkSync(y)))
@@ -190,20 +180,39 @@ exports.find = function find (base, patterns='*', filter=()=>true) {
190
180
  return files
191
181
  }
192
182
 
193
- exports.deprecated = (fn, { kind = 'Method', old = fn.name+'()', use } = {}) => function() {
194
- if (!fn.warned) {
195
- let o={}; Error.captureStackTrace(o)
196
- console.warn (
183
+ exports.deprecated = (fn, { kind = 'Method', old = fn.name+'()', use } = {}) => {
184
+ const yellow = '\x1b[33m'
185
+ const reset = '\x1b[0m'
186
+ if(typeof fn !== 'function') {
187
+ if (cds.env.features.deprecated === 'off') return
188
+
189
+ [kind,old,use] = [fn.kind || 'Configuration',fn.old,fn.use]
190
+ console.log (
191
+ yellow,
197
192
  '\n------------------------------------------------------------------------------',
198
193
  '\nDEPRECATED:', old, '\n',
199
194
  '\n ', kind, old, 'is deprecated and will be removed in upcoming releases!',
200
195
  use ? `\n => Please use ${use} instead.` : '', '\n',
201
- o.stack.replace(/^Error\n\s*at.*\n/,'\n'), '\n',
202
196
  '\n------------------------------------------------------------------------------\n',
197
+ reset
203
198
  )
204
- if (cds.env.features.deprecated !== 'show all') fn.warned = true
199
+ } else return function() {
200
+ if (cds.env.features.deprecated !== 'off' && !fn.warned) {
201
+ let o={}; Error.captureStackTrace(o)
202
+ console.warn (
203
+ yellow,
204
+ '\n------------------------------------------------------------------------------',
205
+ '\nDEPRECATED:', old, '\n',
206
+ '\n ', kind, old, 'is deprecated and will be removed in upcoming releases!',
207
+ use ? `\n => Please use ${use} instead.` : '', '\n',
208
+ o.stack.replace(/^Error\n\s*at.*\n/,'\n'), '\n',
209
+ '\n------------------------------------------------------------------------------\n',
210
+ reset
211
+ )
212
+ if (cds.env.features.deprecated !== 'show all') fn.warned = true
213
+ }
214
+ return fn.apply (this, arguments)
205
215
  }
206
- return fn.apply (this, arguments)
207
216
  }
208
217
 
209
218
  exports.csv = require('./csv-reader')
package/lib/utils/data.js CHANGED
@@ -16,7 +16,7 @@ class DataUtil {
16
16
 
17
17
  async deploy(db) {
18
18
  if (!db) db = await cds.connect.to('db')
19
- await cds.deploy.init(db)
19
+ await cds.deploy.data(db)
20
20
  }
21
21
 
22
22
  async delete(db) {