@sap/cds 6.6.1 → 6.7.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 (132) hide show
  1. package/CHANGELOG.md +67 -3
  2. package/README.md +1 -1
  3. package/apis/connect.d.ts +11 -4
  4. package/apis/core.d.ts +1 -1
  5. package/apis/csn.d.ts +1 -0
  6. package/apis/internal/inference.d.ts +15 -2
  7. package/apis/log.d.ts +10 -0
  8. package/apis/serve.d.ts +4 -9
  9. package/apis/services.d.ts +86 -19
  10. package/bin/build/buildTaskEngine.js +16 -42
  11. package/bin/build/constants.js +4 -2
  12. package/bin/build/provider/buildTaskProviderInternal.js +117 -85
  13. package/bin/build/provider/hana/index.js +6 -1
  14. package/bin/build/provider/mtx-extension/index.js +74 -34
  15. package/bin/build/provider/mtx-sidecar/index.js +3 -3
  16. package/bin/build/provider/nodejs/index.js +2 -2
  17. package/bin/build/util.js +63 -14
  18. package/bin/cds-serve.js +6 -0
  19. package/bin/cds.js +20 -4
  20. package/bin/deploy/to-hana/cfUtil.js +15 -1
  21. package/bin/deploy/to-hana/hana.js +1 -1
  22. package/bin/deploy/to-hana/hdiDeployUtil.js +1 -1
  23. package/bin/mtx/in-cds.js +2 -9
  24. package/bin/plugins.js +31 -0
  25. package/bin/serve.js +12 -12
  26. package/lib/compile/etc/_localized.js +1 -1
  27. package/lib/compile/for/lean_drafts.js +22 -6
  28. package/lib/compile/for/nodejs.js +4 -1
  29. package/lib/compile/load.js +4 -2
  30. package/lib/core/index.js +35 -15
  31. package/lib/dbs/cds-deploy.js +129 -133
  32. package/lib/env/cds-env.js +25 -17
  33. package/lib/env/cds-requires.js +10 -40
  34. package/lib/env/compat.js +12 -0
  35. package/lib/env/defaults.js +17 -9
  36. package/lib/env/plugins.js +29 -0
  37. package/lib/env/schemas/cds-rc.json +14 -0
  38. package/lib/index.js +3 -0
  39. package/lib/log/cds-log.js +7 -4
  40. package/lib/ql/CREATE.js +1 -1
  41. package/lib/ql/DELETE.js +1 -1
  42. package/lib/ql/DROP.js +3 -3
  43. package/lib/ql/INSERT.js +1 -1
  44. package/lib/ql/Query.js +14 -6
  45. package/lib/ql/SELECT.js +8 -2
  46. package/lib/ql/UPDATE.js +1 -1
  47. package/lib/ql/Whereable.js +1 -1
  48. package/lib/ql/cds-ql.js +1 -9
  49. package/lib/req/cds-context.js +1 -4
  50. package/lib/req/request.js +63 -2
  51. package/lib/req/response.js +3 -2
  52. package/lib/srv/bindings.js +69 -71
  53. package/lib/srv/cds-connect.js +4 -1
  54. package/lib/srv/cds-serve.js +4 -0
  55. package/lib/srv/middlewares/index.js +37 -6
  56. package/lib/srv/protocols/_legacy.js +1 -1
  57. package/lib/srv/protocols/index.js +1 -1
  58. package/lib/srv/srv-api.js +4 -6
  59. package/lib/srv/srv-dispatch.js +4 -3
  60. package/lib/srv/srv-handlers.js +1 -1
  61. package/lib/srv/srv-methods.js +8 -2
  62. package/lib/utils/cds-test.js +4 -1
  63. package/libx/_runtime/audit/Service.js +8 -9
  64. package/libx/_runtime/audit/generic/personal/index.js +1 -1
  65. package/libx/_runtime/audit/generic/personal/utils.js +1 -1
  66. package/libx/_runtime/audit/utils/v2.js +17 -20
  67. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +11 -4
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +0 -1
  70. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +3 -3
  71. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +4 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +2 -2
  74. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -1
  75. package/libx/_runtime/cds-services/services/Service.js +1 -1
  76. package/libx/_runtime/cds-services/util/assert.js +41 -65
  77. package/libx/_runtime/common/code-ext/WorkerPool.js +90 -0
  78. package/libx/_runtime/common/code-ext/WorkerReq.js +0 -4
  79. package/libx/_runtime/common/code-ext/execute.js +28 -18
  80. package/libx/_runtime/common/code-ext/handlers.js +5 -4
  81. package/libx/_runtime/common/code-ext/worker.js +45 -3
  82. package/libx/_runtime/common/code-ext/workerQueryExecutor.js +8 -7
  83. package/libx/_runtime/common/composition/delete.js +1 -1
  84. package/libx/_runtime/common/composition/update.js +3 -5
  85. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  86. package/libx/_runtime/common/generic/auth/readOnly.js +5 -4
  87. package/libx/_runtime/common/generic/auth/restrict.js +7 -2
  88. package/libx/_runtime/common/generic/crud.js +12 -1
  89. package/libx/_runtime/common/generic/etag.js +11 -3
  90. package/libx/_runtime/common/generic/input.js +8 -6
  91. package/libx/_runtime/common/generic/paging.js +25 -8
  92. package/libx/_runtime/common/generic/put.js +1 -1
  93. package/libx/_runtime/common/generic/sorting.js +0 -1
  94. package/libx/_runtime/common/i18n/messages.properties +1 -0
  95. package/libx/_runtime/common/utils/cqn.js +5 -1
  96. package/libx/_runtime/common/utils/cqn2cqn4sql.js +2 -2
  97. package/libx/_runtime/common/utils/resolveView.js +14 -10
  98. package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -3
  99. package/libx/_runtime/common/utils/templateProcessor.js +15 -17
  100. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +18 -6
  101. package/libx/_runtime/db/Service.js +1 -0
  102. package/libx/_runtime/db/data-conversion/post-processing.js +0 -18
  103. package/libx/_runtime/db/expand/expand-v2.js +2 -2
  104. package/libx/_runtime/db/expand/rawToExpanded.js +6 -6
  105. package/libx/_runtime/db/generic/integrity.js +1 -1
  106. package/libx/_runtime/db/utils/columns.js +5 -5
  107. package/libx/_runtime/fiori/generic/activate.js +3 -3
  108. package/libx/_runtime/fiori/generic/edit.js +1 -1
  109. package/libx/_runtime/fiori/generic/new.js +4 -0
  110. package/libx/_runtime/fiori/lean-draft.js +138 -46
  111. package/libx/_runtime/hana/execute.js +3 -1
  112. package/libx/_runtime/hana/pool.js +10 -2
  113. package/libx/_runtime/messaging/common-utils/AMQPClient.js +6 -1
  114. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  115. package/libx/_runtime/remote/Service.js +16 -13
  116. package/libx/_runtime/remote/utils/client.js +6 -1
  117. package/libx/_runtime/sqlite/Service.js +5 -59
  118. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
  119. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -2
  120. package/libx/_runtime/sqlite/execute.js +3 -1
  121. package/libx/_runtime/types/api.js +12 -3
  122. package/libx/odata/afterburner.js +36 -0
  123. package/libx/odata/cqn2odata.js +1 -1
  124. package/libx/odata/grammar.pegjs +5 -3
  125. package/libx/odata/parser.js +1 -1
  126. package/libx/odata/utils.js +1 -1
  127. package/libx/rest/RestAdapter.js +1 -1
  128. package/libx/rest/RestRequest.js +1 -0
  129. package/package.json +5 -2
  130. package/libx/_runtime/common/code-ext/workerQuery.js +0 -45
  131. package/libx/_runtime/common/constants/limit.js +0 -12
  132. package/libx/_runtime/common/utils/page.js +0 -39
package/lib/ql/CREATE.js CHANGED
@@ -10,7 +10,7 @@ module.exports = class Query extends require('./Query') {
10
10
  if (elements)
11
11
  this.CREATE.entity = { elements: elements, kind: 'entity', name:e }
12
12
  else
13
- this.CREATE.entity = e && e.elements ? e : this._target_name4(e)
13
+ this.CREATE.entity = e && e.elements ? e : this._target4(e)
14
14
  return this
15
15
  }
16
16
 
package/lib/ql/DELETE.js CHANGED
@@ -9,7 +9,7 @@ module.exports = class Query extends Whereable {
9
9
  }
10
10
 
11
11
  from(entity, key) {
12
- this.DELETE.from = this._target_name4 (...arguments) // supporting tts
12
+ this.DELETE.from = this._target4 (...arguments) // supporting tts
13
13
  if (key) this.byKey(key)
14
14
  return this
15
15
  }
package/lib/ql/DROP.js CHANGED
@@ -7,17 +7,17 @@ module.exports = class Query extends require('./Query') {
7
7
  })
8
8
  }
9
9
  entity(e) {
10
- this.DROP.entity = this._target_name4 (e)
10
+ this.DROP.entity = this._target4 (e)
11
11
  return this
12
12
  }
13
13
  table(e) {
14
14
  const {DROP} = this
15
- DROP.entity = DROP.table = this._target_name4 (e)
15
+ DROP.entity = DROP.table = this._target4 (e)
16
16
  return this
17
17
  }
18
18
  view(e) {
19
19
  const {DROP} = this
20
- DROP.entity = DROP.view = this._target_name4 (e)
20
+ DROP.entity = DROP.view = this._target4 (e)
21
21
  return this
22
22
  }
23
23
  }
package/lib/ql/INSERT.js CHANGED
@@ -7,7 +7,7 @@ module.exports = class Query extends require('./Query') {
7
7
  }
8
8
 
9
9
  into (entity, ...data) {
10
- this[this.cmd].into = this._target_name4 (...arguments) // supporting tts
10
+ this[this.cmd].into = this._target4 (...arguments) // supporting tts
11
11
  if (data.length) this.entries(...data)
12
12
  return this
13
13
  }
package/lib/ql/Query.js CHANGED
@@ -52,12 +52,6 @@ class Query {
52
52
  || this._expected `${{target}} to be an entity path string, a CSN definition, a {ref}, a {SELECT}, or a {SET}`
53
53
  }
54
54
 
55
- //> REVISIT: should we rather have consistent .from/.entity/.into in CQN?
56
- _target_name4 (...args) {
57
- const {ref, as} = this._target_ref4 (...args)
58
- return ref.length === 1 && typeof ref[0] === 'string' && !as ? ref[0] : as ? {ref, as} : {ref}
59
- }
60
-
61
55
  _expected (...args) {
62
56
  return cds.error.expected (...args)
63
57
  }
@@ -72,8 +66,15 @@ class Query {
72
66
  return `${cmd} ${_name(this._target.name)} `
73
67
  }
74
68
 
69
+ forSQL (db = cds.db || cds) { return _flat(db.cqn4sql(this)) }
70
+ toSQL (db = cds.db || cds) { return _2sql(db.cqn2sql(this)) }
71
+ toSql (db = cds.db || cds) { return this.toSQL(db).sql }
72
+
75
73
  }
76
74
 
75
+ // skip .cqn property when in repl
76
+ const _2sql = cds.repl ? ({sql,values}) => ({sql,values}) : (x => x)
77
+ const _flat = Query.prototype.flat
77
78
 
78
79
  const _target4 = (target, arg2) => target && (
79
80
  typeof target === 'string' ? { name: target } :
@@ -85,5 +86,12 @@ const _target4 = (target, arg2) => target && (
85
86
 
86
87
  const _name = cds.env.sql.names === 'quoted' ? n =>`"${n}"` : n => n.replace(/[.:]/g,'_')
87
88
 
89
+ Object.defineProperty (Query.prototype, '_target4', { value: (
90
+ cds.env.ql.quirks_mode === false ? Query.prototype._target_ref4
91
+ : function (...args) {
92
+ const {ref, as} = this._target_ref4 (...args)
93
+ return ref.length === 1 && typeof ref[0] === 'string' && !as ? ref[0] : as ? {ref, as} : {ref}
94
+ }
95
+ )})
88
96
 
89
97
  module.exports = Query
package/lib/ql/SELECT.js CHANGED
@@ -104,8 +104,14 @@ module.exports = class Query extends Whereable {
104
104
  return this._where (args,'and','on')
105
105
  }
106
106
 
107
- having(...x) {
108
- return this._where (x,'and','having')
107
+ having(...args) {
108
+ return this._where (args,'and','having')
109
+ }
110
+
111
+ search (...args) {
112
+ let _xpr=[]; for (let val of args) _xpr.push('or',{val})
113
+ this.SELECT.search = _xpr.slice(1)
114
+ return this
109
115
  }
110
116
 
111
117
  groupBy (...args) {
package/lib/ql/UPDATE.js CHANGED
@@ -10,7 +10,7 @@ module.exports = class Query extends Whereable {
10
10
  }
11
11
 
12
12
  entity (e, key) {
13
- this.UPDATE.entity = this._target_name4 (...arguments) // supporting tts
13
+ this.UPDATE.entity = this._target4 (...arguments) // supporting tts
14
14
  if (key) this.byKey(key)
15
15
  return this
16
16
  }
@@ -18,7 +18,7 @@ class Query extends require('./Query') {
18
18
  error (`Invalid attempt to call '${this.cmd}.${and_or}()' before a prior call to '${this.cmd}.where()'`)
19
19
  )
20
20
  if (clause === 'on') _ = _.from
21
- let left = Reflect.getOwnPropertyDescriptor(_,clause)?.value
21
+ let left = _[clause]
22
22
  if (!left) { //> .where() called first time
23
23
  // SELECT.from `X` .where `x or y` .and `z` -> SELECT from X where (x or y) and z
24
24
  if (pred.includes('or')) this._left_has_or = true
package/lib/ql/cds-ql.js CHANGED
@@ -9,7 +9,7 @@ require = path => { // eslint-disable-line no-global-assign
9
9
  }, api)
10
10
  }
11
11
 
12
- module.exports = Object.assign (_deprecated_srv_ql, { cdr: true,
12
+ module.exports = {
13
13
  Query, clone: (q,_) => Query.prototype.clone.call(q,_),
14
14
  SELECT: require('./SELECT'),
15
15
  INSERT: require('./INSERT'),
@@ -18,14 +18,6 @@ module.exports = Object.assign (_deprecated_srv_ql, { cdr: true,
18
18
  DELETE: require('./DELETE'),
19
19
  CREATE: require('./CREATE'),
20
20
  DROP: require('./DROP'),
21
- })
22
-
23
- function _deprecated_srv_ql() { // eslint-disable-next-line no-console
24
- console.trace(`
25
- Method 'srv.ql(req)' is deprecated and superceded by 'cds.context'.
26
- Please use global SELECT instead of 'const { SELECT } = srv.ql(req)'.
27
- `)
28
- return module.exports
29
21
  }
30
22
 
31
23
  module.exports._reset = ()=>{ // for strange tests only
@@ -15,12 +15,9 @@ module.exports = new class extends AsyncLocalStorage {
15
15
 
16
16
  /** @returns {EventContext} */
17
17
  _for (cds,v) {
18
- Reflect.defineProperty (cds,'context', { enumerable:1, ... cds.env.features.cls ? {
18
+ Reflect.defineProperty (cds,'context', { enumerable:1, ... {
19
19
  set:(v) => this.enterWith(v),
20
20
  get:()=> this.getStore(),
21
- } : {
22
- get:()=> undefined,
23
- set:()=> {},
24
21
  }})
25
22
  return cds.context = v // IMPORTANT: we need to set it initially, to get it all wired up correctly
26
23
  }
@@ -1,4 +1,5 @@
1
1
  const { Responses, Errors } = require('./response')
2
+ const cds = require('../../lib')
2
3
 
3
4
  /**
4
5
  * Class Request represents requests received via synchronous protocols.
@@ -56,6 +57,67 @@ class Request extends require('./event') {
56
57
  return this._set ('data', {})
57
58
  }
58
59
 
60
+ set subject(d) { if (d) super.subject = d }
61
+ get subject() {
62
+ const q = this.query
63
+ let subject = (
64
+ q?.SELECT?.from ||
65
+ q?.INSERT?.into ||
66
+ q?.UPSERT?.into ||
67
+ q?.UPDATE?.entity ||
68
+ q?.DELETE?.from
69
+ )
70
+
71
+ const {target} = this; if (!target) return undefined
72
+
73
+ // programmatic action calls as of now only in format /Books(201)/action
74
+ if (!subject) {
75
+ let where = []
76
+ for (const param of this.params) {
77
+ if (typeof param === 'object') {
78
+ for (const key in param) {
79
+ if (key in target.keys) {
80
+ if (where.length > 1) where.push('and')
81
+ where.push({ ref: [key] }, '=', { val: param[key] })
82
+ }
83
+ }
84
+ } else {
85
+ where.push({ ref: [Object.keys(target.keys)[0]] }, '=', { val: param })
86
+ }
87
+ }
88
+ subject = { id: target.name, where }
89
+ }
90
+
91
+ const ref = subject.ref || [ subject ]
92
+
93
+ if(!target.drafts || cds.env.fiori.lean_draft) return super.subject = { ref }
94
+
95
+ // special handling for old draft logic
96
+ let subjectRef = []
97
+ ref.forEach ((item, i) => {
98
+ // to one assoc & comp
99
+ if (typeof item === 'string') {
100
+ subjectRef.push(item)
101
+ return
102
+ }
103
+
104
+ // create key value pair without IsActiveEntity & adjust root target
105
+ const keys = {}
106
+ let id = item.id
107
+ for (let j = 0; j < item?.where?.length; j = j + 4) {
108
+ const key = item.where[j].ref[0]
109
+ const value = item.where[j + 2].val
110
+ if (key !== 'IsActiveEntity') keys[key] = value
111
+ else if (i === 0 && key === 'IsActiveEntity' && value === false) id = item.id + '_drafts'
112
+ }
113
+
114
+ // create copy
115
+ const {SELECT:{from:{ref}}} = SELECT.from(id,keys)
116
+ subjectRef.push(...ref)
117
+ })
118
+ return super.subject = {ref: subjectRef}
119
+ }
120
+
59
121
  reply (results) { return this.results = results }
60
122
  notify (...args) { return this._messages.add (1, ...args) }
61
123
  info (...args) { return this._messages.add (2, ...args) }
@@ -116,8 +178,7 @@ const SQL2Crud = {
116
178
  }
117
179
 
118
180
  const Query2Crud = (q) => {
119
- if (typeof q === 'string') return SQL2Crud[q] || /^\s*(\w+)/i.test(q) && SQL2Crud[RegExp.$1.toUpperCase()] || q
120
- else for (let each in q) if (each in SQL2Crud) return SQL2Crud[each]
181
+ for (let each in q) if (each in SQL2Crud) return SQL2Crud[each]
121
182
  }
122
183
 
123
184
  const _path4 = (x,p) => {
@@ -4,8 +4,9 @@ const cds = require ('../index')
4
4
  * Messages Collector, used for `req.errors` and `req.messages`
5
5
  */
6
6
  class Responses extends Array {
7
- static get (severity, code, message, target, args) {
8
- let e // be filled in below...
7
+ static get (severity, code, message, target, args, ...more) {
8
+ if (code?.raw) [ message, code ] = [ String.raw (code, message, target, args, ...more) ]
9
+ let e // be filled in below...
9
10
  if (typeof code === 'object') e = code; else {
10
11
  if (typeof code === 'number') e = { code }; else [ code, message, target, args, e ] = [ undefined, code, message, target, {} ]
11
12
  if (typeof message === 'object') e = Object.assign(message,e); else {
@@ -1,7 +1,5 @@
1
- const DEBUG = /\b(y|all|serve|bindings)\b/.test (process.env.DEBUG) && console.warn
2
- // || console.debug
3
1
 
4
- const cds = require ('..')
2
+ const cds = require ('..'), DEBUG = cds.debug('serve|bindings',{label:'cds'})
5
3
  const { readFile, readFileSync, writeFile, writeFileSync } = require ('fs')
6
4
  const [ read, write ] = [ readFile, writeFile ].map(require('util').promisify)
7
5
  const registry = '~/.cds-services.json'
@@ -9,88 +7,88 @@ const registry = '~/.cds-services.json'
9
7
  /** TODO: Add documentation */
10
8
  module.exports = class Bindings {
11
9
 
12
- static get registry(){ return registry }
10
+ static get registry(){ return registry }
13
11
 
14
- static then(r,e) {
15
- const LOG = cds.log('cds.serve', { prefix:'cds' })
16
- const bindings = new Bindings
17
- cds.prependOnceListener ('connect', ()=> LOG._info && LOG.info ('connect using bindings from:', { registry }))
18
- cds.once('listening', ({url})=> bindings.export (cds.service.providers, url))
19
- return bindings.import() .then (r,e)
20
- }
12
+ static then(r,e) {
13
+ const LOG = cds.log('cds.serve', { prefix:'cds' })
14
+ const bindings = new Bindings
15
+ cds.prependOnceListener ('connect', ()=> LOG._info && LOG.info ('connect using bindings from:', { registry }))
16
+ cds.once('listening', ({url})=> bindings.export (cds.service.providers, url))
17
+ return bindings.import() .then (r,e)
18
+ }
21
19
 
22
- constructor(url) {
23
- this._source = require ('path') .resolve (cds.root, registry.replace(/^~/, require('os').homedir()))
24
- this.cds = {provides:{}}
25
- this.url = url
26
- }
20
+ constructor(url) {
21
+ this._source = require ('path') .resolve (cds.root, registry.replace(/^~/, require('os').homedir()))
22
+ this.cds = {provides:{}}
23
+ this.url = url
24
+ }
27
25
 
28
- async load (sync) {
29
- DEBUG && DEBUG('[cds] - reading bindings from:', this._source)
30
- try { Object.assign (this, JSON.parse (sync ? readFileSync (this._source) : await read (this._source))) }
31
- catch (e) { /* ignored */ }
32
- return this
33
- }
34
- async store (sync) {
35
- DEBUG && DEBUG ('[cds] - writing bindings to:', this._source)
36
- const json = JSON.stringify ({cds:this.cds},null,' ')
37
- return sync ? writeFileSync (this._source, json) : write (this._source, json)
38
- }
26
+ async load (sync) {
27
+ DEBUG?.('reading bindings from:', this._source)
28
+ try { Object.assign (this, JSON.parse (sync ? readFileSync (this._source) : await read (this._source))) }
29
+ catch (e) { /* ignored */ }
30
+ return this
31
+ }
32
+ async store (sync) {
33
+ DEBUG?.('writing bindings to:', this._source)
34
+ const json = JSON.stringify ({cds:this.cds},null,' ')
35
+ return sync ? writeFileSync (this._source, json) : write (this._source, json)
36
+ }
39
37
 
40
- async import() {
41
- const required = cds.requires; if (!required) return this
42
- const provided = (await this.load()) .cds.provides
43
- for (let each in required) {
44
- const req = required[each]; if (typeof req !== 'object') continue
45
- const bound = provided [req.service||each]
46
- if (bound) {
47
- Object.assign (req.credentials || (req.credentials = {}), bound.credentials)
48
- // REVISIT: temporary fix to inherit kind as well for mocked odata services
49
- // otherwise mocking with two services does not work for kind:odata-v2
50
- if (req.kind === 'odata-v2' || req.kind === 'odata-v4') req.kind = 'odata'
51
- }
52
- }
53
- return this
38
+ async import() {
39
+ const required = cds.requires; if (!required) return this
40
+ const provided = (await this.load()) .cds.provides
41
+ for (let each in required) {
42
+ const req = required[each]; if (typeof req !== 'object') continue
43
+ const bound = provided [req.service||each]
44
+ if (bound) {
45
+ Object.assign (req.credentials || (req.credentials = {}), bound.credentials)
46
+ // REVISIT: temporary fix to inherit kind as well for mocked odata services
47
+ // otherwise mocking with two services does not work for kind:odata-v2
48
+ if (req.kind === 'odata-v2' || req.kind === 'odata-v4') req.kind = 'odata'
49
+ }
54
50
  }
51
+ return this
52
+ }
55
53
 
56
- async export (services, url) {
57
- this.cleanup (this.url = url)
58
- // register our services
59
- const provides = this.cds.provides
60
- for (let each of services) {
61
- // if (each.name in cds.env.requires) continue
62
- const options = each.options || {}
63
- provides[each.name] = {
64
- kind: options.to || 'odata',
65
- credentials: {
66
- ...options.credentials,
67
- url: url + each.path
68
- }
69
- }
54
+ async export (services, url) {
55
+ this.cleanup (this.url = url)
56
+ // register our services
57
+ const provides = this.cds.provides
58
+ for (let each of services) {
59
+ // if (each.name in cds.env.requires) continue
60
+ const options = each.options || {}
61
+ provides[each.name] = {
62
+ kind: options.to || 'odata',
63
+ credentials: {
64
+ ...options.credentials,
65
+ url: url + each.path
70
66
  }
71
- process.on ('exit', ()=>this.purge())
72
- return this.store()
67
+ }
73
68
  }
69
+ process.on ('exit', ()=>this.purge())
70
+ return this.store()
71
+ }
74
72
 
75
- purge() {
76
- this.load(true)
77
- DEBUG && DEBUG ('[cds] - purging bindings from:', this._source)
78
- this.cleanup()
79
- this.store(true)
80
- }
73
+ purge() {
74
+ this.load(true)
75
+ DEBUG?.('purging bindings from:', this._source)
76
+ this.cleanup()
77
+ this.store(true)
78
+ }
81
79
 
82
- cleanup (url=this.url) {
83
- // remove all services served at the same url
84
- const all = this.cds.provides
85
- for (let [key,srv] of Object.entries (all)) {
86
- if (srv.credentials && srv.credentials.url && srv.credentials.url.startsWith(url)) delete all [key]
87
- }
88
- return this
80
+ cleanup (url=this.url) {
81
+ // remove all services served at the same url
82
+ const all = this.cds.provides
83
+ for (let [key,srv] of Object.entries (all)) {
84
+ if (srv.credentials && srv.credentials.url && srv.credentials.url.startsWith(url)) delete all [key]
89
85
  }
86
+ return this
87
+ }
90
88
  }
91
89
 
92
90
  const {NODE_ENV} = process.env
93
91
  if (NODE_ENV === 'test' || global.it || cds.env.no_bindings) {
94
- module['exports'] = { then: (r) => r() }
92
+ module['exports'] = { then: (r) => r() }
95
93
  }
96
94
  /* eslint no-console:off */
@@ -1,5 +1,6 @@
1
1
  const cds = require('..'), {one_model} = cds.env.features, LOG = cds.log('cds.connect')
2
2
  const _pending = cds.services._pending || {} // used below to chain parallel connect.to(<same>)
3
+ const TRACE = cds.debug('trace')
3
4
 
4
5
  /**
5
6
  * Connect to a service as primary datasource, i.e. cds.db.
@@ -21,6 +22,7 @@ const connect = module.exports = async function cds_connect (options) {
21
22
  * @returns { Promise<import('./srv-api')> }
22
23
  */
23
24
  connect.to = async (datasource, options) => {
25
+ TRACE?.time(`cds.connect ${datasource} `)
24
26
  let Service = cds.service.factory, _done = x=>x
25
27
  if (typeof datasource === 'object') [options,datasource] = [datasource]
26
28
  else if (datasource) {
@@ -36,7 +38,7 @@ connect.to = async (datasource, options) => {
36
38
  const m = await model4 (o)
37
39
  // check if required service definition exists
38
40
  const required = cds.requires[datasource]
39
- if (required && required.model && datasource !== 'db' && !m.definitions[required.service||datasource]) {
41
+ if (required?.model?.length && datasource !== 'db' && !m.definitions[required.service||datasource]) {
40
42
  LOG.error(`No service definition found for '${required.service || datasource}', as required by 'cds.requires.${datasource}':`, required)
41
43
  throw new Error (`No service definition found for '${required.service || datasource}'`)
42
44
  }
@@ -46,6 +48,7 @@ connect.to = async (datasource, options) => {
46
48
  if (datasource === 'db') cds.db = srv
47
49
  _done (cds.services[datasource] = srv)
48
50
  if (!o.silent) cds.emit ('connect',srv)
51
+ TRACE?.timeEnd(`cds.connect ${datasource} `)
49
52
  return srv
50
53
  }
51
54
 
@@ -2,10 +2,13 @@ const cds = require ('..')
2
2
  const { ProtocolAdapter } = cds.service.protocols
3
3
  const { Service } = cds.service.factory
4
4
  const _ready = Symbol(), _pending = cds.services._pending || {}
5
+ const TRACE = cds.debug('trace')
5
6
 
6
7
  /** @param som - a service name or a model (name or csn) */
7
8
  module.exports = function cds_serve (som, _options) { // NOSONAR
8
9
 
10
+ TRACE?.time(`cds.serve ${som} `)
11
+
9
12
  if (som && typeof som === 'object' && !is_csn(som) && !is_files(som)) {
10
13
  [som,_options] = [undefined,
11
14
  som._is_service_instance ? { service:som, from:'*' } :
@@ -123,6 +126,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
123
126
  return chimera
124
127
  }})
125
128
  }
129
+ TRACE?.timeEnd('cds.serve '+som+' ')
126
130
  return _resolve (response)
127
131
  }, _error),
128
132
 
@@ -7,14 +7,45 @@ const trace = exports.trace = require('./trace')
7
7
 
8
8
  // middlewares running before protocol adapters
9
9
  exports.before = [
10
- context(), // provides cds.context
11
- trace(), // provides detailed trace logs when DEBUG=trace
12
- auth(), // provides req.user & tenant
13
- ctx_auth(), // propagates auth results to cds.context
14
- ctx_model(), // fills in cds.context.model, in case of extensibility
15
- ]
10
+ context, // provides cds.context
11
+ trace, // provides detailed trace logs when DEBUG=trace
12
+ auth, // provides req.user & tenant
13
+ ctx_auth, // propagates auth results to cds.context
14
+ ctx_model, // fills in cds.context.model, in case of extensibility
15
+ ].map(_instantiate)
16
16
 
17
17
  // middlewares running after protocol adapters -> usually error middlewares
18
18
  exports.after = [
19
19
  errors(),
20
20
  ]
21
+
22
+ /**
23
+ * Convenience method to add custom middlewares like so:
24
+ * ```js
25
+ * cds.middlewares.add (mymw, {at:0}) // to the front
26
+ * cds.middlewares.add (mymw, {at:2})
27
+ * cds.middlewares.add (mymw, {before:'auth'})
28
+ * cds.middlewares.add (mymw, {after:'auth'})
29
+ * cds.middlewares.add (mymw) // to the end
30
+ * ```
31
+ */
32
+ exports.add = (new_mw, { at: index, before, after, options }) => {
33
+ let mw = new_mw (options)
34
+ if (index) return exports.before.splice (index, 0, mw)
35
+ if (before) return exports.before.splice (index, _index4(before), mw)
36
+ if (after) return exports.before.splice (index, _index4(after)+1, mw)
37
+ else return exports.before.push(mw)
38
+ }
39
+
40
+ function _index4 (middleware) {
41
+ if (typeof middleware === 'string') middleware = exports[middleware]
42
+ if (!middleware) throw new Error (`Didn't find a middleware matching ${{middleware}}`)
43
+ const index = exports.before.findIndex(mw => mw.factory === before)
44
+ if (index === -1) throw new Error (`Didn't find ${{middleware}} in cds.middlewares.before`)
45
+ return index
46
+ }
47
+
48
+ function _instantiate (factory,o) {
49
+ let mw = factory(o)
50
+ return mw && Object.assign(mw,{factory})
51
+ }
@@ -17,10 +17,10 @@ class LegacyProtocolAdapter extends ProtocolAdapter {
17
17
  return super.serve (srv, app, { before: [
18
18
  // async (req, res, next) => { await 1; next() }, // REVISIT: AsyncResource.bind() -> enable to break cds/tests/_runtime/odata/__tests__/integration/crud-with-mtx.test.js with existing, non-middleware mode, *w/o* fix to BufferedWriter
19
19
  cds_context,
20
- cap_req_logger,
21
20
  libx.perf,
22
21
  libx.auth(srv),
23
22
  ctx_auth,
23
+ cap_req_logger,
24
24
  cds_context_model.middleware4(srv)
25
25
  ], after:[] })
26
26
  }
@@ -80,7 +80,7 @@ class ProtocolAdapter {
80
80
 
81
81
 
82
82
  const protocols = Object.keys(ProtocolAdapter.init())
83
- const protocol4 = (def, _default = protocols[0]) => def['@protocol'] || protocols.find(p => def['@'+p]) || _default
83
+ const protocol4 = (def, _default = protocols[0]) => def?.['@protocol'] || protocols.find(p => def['@'+p]) || _default
84
84
  const is_global = adapter => adapter.length === 1 && !/^(function )?(\w+\s+)?\((srv|service)/.test(adapter)
85
85
 
86
86
  module.exports = { ProtocolAdapter, protocol4 }
@@ -63,11 +63,9 @@ class Service extends require('./srv-handlers') {
63
63
  run (query, data) {
64
64
  if (typeof query === 'function') {
65
65
  const ctx = cds.context, fn = query
66
- if (ctx?.tx && !ctx.tx._done) {
67
- return fn (this.tx(ctx)) // with nested tx
68
- } else {
69
- return this.tx (fn) // with root tx
70
- }
66
+ if (ctx?.tx && !ctx.tx._done)
67
+ return fn (this.tx(ctx)) // run fn with nested tx
68
+ else return this.tx(fn) // run fn with root tx
71
69
  }
72
70
  const req = new Request ({ query, data })
73
71
  return this.dispatch (req)
@@ -100,7 +98,7 @@ class Service extends require('./srv-handlers') {
100
98
  get namespace() {
101
99
  return super.namespace = this.definition && this.definition.name
102
100
  || this.model && this.model.namespace
103
- || !(this instanceof cds.DatabaseService) && !/\W/.test(this.name) && this.name || undefined
101
+ || !(this.isDatabaseService) && !/\W/.test(this.name) && this.name || undefined
104
102
  }
105
103
 
106
104
  get operations() { return super.operations = _reflect (this, d => d.kind === 'action' || d.kind === 'function') }
@@ -16,10 +16,11 @@ exports.dispatch = async function dispatch (req) { //NOSONAR
16
16
  const ctx = cds.context
17
17
  if (ctx?.tx && !ctx.tx._done) {
18
18
  return this.tx (ctx) .dispatch(req) // with nested tx
19
- } else {
20
- return this.tx (tx => tx.dispatch(req)) // with root tx
21
19
  }
20
+
21
+ return this.tx (tx => tx.dispatch(req)) // with root tx
22
22
  }
23
+
23
24
  if (!req.tx) req.tx = this // `this` is a tx from now on...
24
25
 
25
26
  // Inform potential listeners // REVISIT: -> this should move into protocol adapters
@@ -112,7 +113,7 @@ const _ensure_target = (srv,req) => {
112
113
 
113
114
  const _ensure_fqn = (x,p,srv, name = x[p]) => {
114
115
  if (typeof name === 'string') {
115
- if (srv instanceof cds.DatabaseService) return
116
+ if (srv.isDatabaseService) return
116
117
  if (srv.model && name in srv.model.definitions) return
117
118
  if (name.startsWith(srv.namespace)) return
118
119
  if (name.endsWith('_drafts')) return // REVISIT: rather fix test/fiori/localized-draft.test.js ?
@@ -91,7 +91,7 @@ const _register = function (srv, phase, event, path, handler) { //NOSONAR
91
91
  if (!path.startsWith(srv.name+'.')) path = `${srv.name}.${path}`
92
92
  }
93
93
 
94
- if (cds.env.features.lean_draft && cds.env.features.lean_draft_compatibility) {
94
+ if (cds.env.fiori.lean_draft && cds.env.fiori.draft_compat) {
95
95
  const entity = path && srv.model?.definitions[path.name || path]
96
96
  if (['PATCH', 'CANCEL', 'NEW'].includes(event)) {
97
97
  // delegate to drafts
@@ -58,7 +58,10 @@ const add_handler_for = (srv, def) => {
58
58
  `)
59
59
  const stub = srv[event] = function (...args) {
60
60
  const req = { event, data:{} }, $ = args[0]
61
- const target = this.entities [ $ && $.name ? $.name.match(/\w*$/)[0] : $ ]
61
+ const target = $ && (
62
+ this.model.definitions[ $.name ]
63
+ || this.entities[ $.name?.replace(`${this.name}.`,'') || $ ]
64
+ )
62
65
  if (target) { //> bound action/function?
63
66
  req.target = target; args.shift() // first argument is the target entity name
64
67
  req.params = [ args.shift() ] // second argument is the target's primary key
@@ -86,7 +89,10 @@ const add_handler_for = (srv, def) => {
86
89
 
87
90
  return this.send (req)
88
91
  }
89
- stub._is_stub = true
92
+ Object.defineProperties(stub,{
93
+ name: {value: /[^.]+$/.exec(srv.name)[0] +'.'+ event},
94
+ _is_stub: {value:true},
95
+ })
90
96
  }
91
97
 
92
98
 
@@ -164,4 +164,7 @@ const spy = (o,f) => {
164
164
 
165
165
 
166
166
  /** @type Test & ()=>Test */
167
- module.exports = Object.setPrototypeOf ((..._) => (new Test).run(..._), Test.prototype)
167
+ module.exports = Object.assign (
168
+ Object.setPrototypeOf ((..._) => (new Test).run(..._), Test.prototype),
169
+ { Test }
170
+ )