@sap/cds 6.3.2 → 6.4.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 (128) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/apis/cds.d.ts +3 -1
  3. package/apis/core.d.ts +118 -90
  4. package/apis/cqn.d.ts +11 -2
  5. package/apis/internal/inference.d.ts +7 -2
  6. package/apis/ql.d.ts +49 -11
  7. package/apis/serve.d.ts +8 -1
  8. package/apis/services.d.ts +311 -305
  9. package/bin/build/buildTaskEngine.js +28 -36
  10. package/bin/build/buildTaskFactory.js +32 -81
  11. package/bin/build/buildTaskHandler.js +3 -2
  12. package/bin/build/buildTaskProvider.js +2 -2
  13. package/bin/build/buildTaskProviderFactory.js +5 -14
  14. package/bin/build/constants.js +0 -1
  15. package/bin/build/provider/buildTaskHandlerEdmx.js +7 -6
  16. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +6 -5
  17. package/bin/build/provider/buildTaskHandlerInternal.js +9 -30
  18. package/bin/build/provider/buildTaskProviderInternal.js +70 -58
  19. package/bin/build/provider/fiori/index.js +6 -5
  20. package/bin/build/provider/hana/2migration.js +20 -3
  21. package/bin/build/provider/hana/2tabledata.js +1 -0
  22. package/bin/build/provider/hana/index.js +40 -17
  23. package/bin/build/provider/java/index.js +10 -10
  24. package/bin/build/provider/mtx/index.js +25 -16
  25. package/bin/build/provider/mtx/resourcesTarBuilder.js +22 -27
  26. package/bin/build/provider/mtx-extension/index.js +3 -2
  27. package/bin/build/provider/mtx-sidecar/index.js +16 -15
  28. package/bin/build/provider/nodejs/index.js +14 -56
  29. package/bin/build/util.js +56 -16
  30. package/bin/deploy/to-hana/cfUtil.js +2 -0
  31. package/bin/deploy/to-hana/gitUtil.js +1 -1
  32. package/bin/deploy/to-hana/hana.js +45 -38
  33. package/bin/deploy/to-hana/hdiDeployUtil.js +17 -12
  34. package/bin/deploy/to-hana/mtaUtil.js +13 -14
  35. package/bin/mtx/in-cds.js +3 -1
  36. package/bin/serve.js +1 -1
  37. package/bin/version.js +2 -1
  38. package/lib/auth/index.js +17 -15
  39. package/lib/compile/cds-compile.js +1 -0
  40. package/lib/compile/cdsc.js +1 -0
  41. package/lib/compile/etc/_localized.js +2 -2
  42. package/lib/compile/for/lean_drafts.js +83 -0
  43. package/lib/compile/for/nodejs.js +1 -0
  44. package/lib/compile/minify.js +2 -1
  45. package/lib/compile/to/gql.js +1 -1
  46. package/lib/compile/to/sql.js +11 -1
  47. package/lib/core/entities.js +1 -1
  48. package/lib/core/index.js +9 -9
  49. package/lib/core/infer.js +1 -0
  50. package/lib/dbs/cds-deploy.js +97 -41
  51. package/lib/env/cds-env.js +9 -10
  52. package/lib/env/cds-requires.js +8 -2
  53. package/lib/env/defaults.js +0 -4
  54. package/lib/env/schemas/cds-rc.json +38 -0
  55. package/lib/ql/SELECT.js +10 -4
  56. package/lib/srv/bindings.js +1 -1
  57. package/lib/srv/factory.js +1 -1
  58. package/lib/srv/middlewares/cds-context.js +0 -2
  59. package/lib/srv/middlewares/ctx-auth.js +11 -0
  60. package/lib/srv/middlewares/ctx-model.js +22 -20
  61. package/lib/srv/middlewares/index.js +7 -9
  62. package/lib/srv/protocols/_legacy.js +4 -0
  63. package/lib/srv/protocols/graphql.js +2 -2
  64. package/lib/srv/protocols/index.js +7 -3
  65. package/lib/srv/srv-api.js +1 -0
  66. package/lib/srv/srv-methods.js +1 -1
  67. package/lib/utils/cds-utils.js +11 -0
  68. package/lib/utils/data.js +2 -2
  69. package/lib/utils/inflect.js +13 -12
  70. package/lib/utils/tar.js +43 -13
  71. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -2
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  73. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  74. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -1
  75. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -15
  76. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -1
  77. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
  78. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/UriSyntaxError.js +1 -1
  79. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +6 -1
  80. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +1 -1
  81. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -12
  82. package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +1 -7
  83. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +4 -0
  84. package/libx/_runtime/cds-services/services/Service.js +23 -1
  85. package/libx/_runtime/cds-services/util/assert.js +0 -41
  86. package/libx/_runtime/common/composition/data.js +5 -1
  87. package/libx/_runtime/common/generic/auth/utils.js +3 -3
  88. package/libx/_runtime/common/generic/crud.js +1 -1
  89. package/libx/_runtime/common/generic/input.js +4 -24
  90. package/libx/_runtime/common/generic/paging.js +10 -9
  91. package/libx/_runtime/common/utils/cqn2cqn4sql.js +31 -0
  92. package/libx/_runtime/common/utils/csn.js +21 -15
  93. package/libx/_runtime/common/utils/draft.js +2 -1
  94. package/libx/_runtime/common/utils/resolveView.js +27 -4
  95. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -1
  96. package/libx/_runtime/common/utils/rowUUIDGenerator.js +21 -0
  97. package/libx/_runtime/common/utils/templateProcessor.js +12 -15
  98. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +23 -0
  99. package/libx/_runtime/db/expand/expandCQNToJoin.js +29 -12
  100. package/libx/_runtime/db/generic/input.js +7 -13
  101. package/libx/_runtime/db/sql-builder/InsertBuilder.js +5 -1
  102. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +24 -0
  103. package/libx/_runtime/db/sql-builder/annotations.js +6 -3
  104. package/libx/_runtime/db/sql-builder/index.js +2 -0
  105. package/libx/_runtime/db/sql-builder/sqlFactory.js +9 -0
  106. package/libx/_runtime/db/utils/columns.js +4 -2
  107. package/libx/_runtime/fiori/generic/read.js +1 -12
  108. package/libx/_runtime/fiori/lean-draft.js +657 -0
  109. package/libx/_runtime/fiori/utils/handler.js +1 -1
  110. package/libx/_runtime/hana/Service.js +1 -1
  111. package/libx/_runtime/hana/execute.js +5 -5
  112. package/libx/_runtime/hana/pool.js +16 -1
  113. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -1
  114. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
  115. package/libx/_runtime/messaging/enterprise-messaging.js +2 -3
  116. package/libx/_runtime/messaging/outbox/utils.js +109 -70
  117. package/libx/_runtime/messaging/service.js +16 -7
  118. package/libx/_runtime/remote/Service.js +15 -2
  119. package/libx/_runtime/remote/utils/client.js +41 -11
  120. package/libx/_runtime/sqlite/Service.js +4 -1
  121. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +56 -0
  122. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +41 -0
  123. package/libx/_runtime/sqlite/customBuilder/index.js +5 -0
  124. package/libx/_runtime/sqlite/execute.js +1 -1
  125. package/libx/_runtime/types/api.js +2 -2
  126. package/libx/rest/RestAdapter.js +15 -13
  127. package/package.json +1 -1
  128. package/server.js +2 -19
package/lib/core/index.js CHANGED
@@ -26,6 +26,7 @@ const roots = _roots ({
26
26
  Association: {type:'type'},
27
27
  Composition: {type:'Association'},
28
28
  service: {type:'context'},
29
+ $self: {}, //> to support polymorphic self links like in: action foo( self: [many] $self, ...)
29
30
  })
30
31
 
31
32
  /**
@@ -35,13 +36,12 @@ const roots = _roots ({
35
36
  function _roots (defs) {
36
37
  const linked = { any: classes.any.prototype }
37
38
  for (const t in defs) {
38
- if (t in classes) {
39
- linked[t] = classes[t].prototype
40
- continue
39
+ if (t in classes) linked[t] = classes[t].prototype
40
+ else {
41
+ const c = class extends classes[defs[t].type || 'any'] {}
42
+ classes[t] = Object.defineProperty (c, 'name', {value:t})
43
+ linked[t] = Object.defineProperty (c.prototype, 'name', {value:t})
41
44
  }
42
- const c = class extends classes[defs[t].type || 'any'] {}
43
- linked[t] = Object.defineProperty (c.prototype, 'name', {value:t})
44
- classes[t] = Object.defineProperty (c, 'name', {value:t})
45
45
  }
46
46
  return linked
47
47
  }
@@ -56,9 +56,9 @@ const types = _common ({ __proto__: roots,
56
56
  Int16: {type:'Integer'},
57
57
  Int32: {type:'Integer'},
58
58
  Int64: {type:'Integer'},
59
- Integer16: {type:'Integer'},
60
- Integer32: {type:'Integer'},
61
- Integer64: {type:'Integer'},
59
+ Integer16: {type:'Int16'},
60
+ Integer32: {type:'Int32'},
61
+ Integer64: {type:'Int64'},
62
62
  Decimal: {type:'number'},
63
63
  DecimalFloat: {type:'number'},
64
64
  Float: {type:'number'},
package/lib/core/infer.js CHANGED
@@ -5,6 +5,7 @@ module.exports = (q,defs) => {
5
5
  if (!q._target || q._target.kind !== 'entity') Object.defineProperty (q, '_target', {value:(
6
6
  q.SELECT ? _resolve (q.SELECT.from, defs) :
7
7
  q.INSERT ? _resolve (q.INSERT.into, defs) :
8
+ q.UPSERT ? _resolve (q.UPSERT.into, defs) :
8
9
  q.UPDATE ? _resolve (q.UPDATE.entity, defs) :
9
10
  q.DELETE ? _resolve (q.DELETE.from, defs) :
10
11
  _resolve (undefined)
@@ -1,4 +1,4 @@
1
- const cds = require('../index'), { local, inspect } = cds.utils
1
+ const cds = require('../index'), { local, inspect } = cds.utils, { UPSERT } = cds.ql
2
2
  const DEBUG = cds.debug('deploy')
3
3
 
4
4
  const colors = !!process.stdout.isTTY && !!process.stderr.isTTY
@@ -14,7 +14,7 @@ const term = {
14
14
  * deploy create tables and views in case of a SQL database, then fills
15
15
  * in initial data, if present.
16
16
  */
17
- exports = module.exports = function cds_deploy (model,options) { return {
17
+ exports = module.exports = function cds_deploy (model,options,csvs) { return {
18
18
 
19
19
  /** @param {cds.Service} db */
20
20
  async to (db, o = options || cds.options || {}) {
@@ -40,10 +40,10 @@ exports = module.exports = function cds_deploy (model,options) { return {
40
40
 
41
41
  // create tables & views...
42
42
  const any = await exports.create (db,model,o)
43
- if (!any) return db
43
+ if (!any && !csvs) return db
44
44
 
45
45
  // fill in initial data...
46
- await exports.init (db,model, file => LOG(
46
+ await exports.init (db,model,o,csvs, file => LOG(
47
47
  term.info(` > init from ${local(file)}`)
48
48
  ))
49
49
 
@@ -105,55 +105,113 @@ exports.exclude_external_entities_in = function (csn) { // NOSONAR
105
105
  }
106
106
  }
107
107
 
108
-
109
- exports.create = async function (db, csn=db.model, o) {
110
- if (!db.deploy || o.lean) {
111
- const creates = cds.compile.to.sql (csn,o); if (!creates || creates.length === 0) return
112
- const drops = creates.map (each => {
108
+ function getSqls(db, csn, o, beforeCsn) {
109
+ const schemaEvo = (db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto')
110
+ const creds = db.options?.credentials
111
+ const in_memory = (creds?.url || creds?.database) === ':memory:';
112
+ if (!in_memory && schemaEvo) {
113
+ const { afterImage: afterCsn, drops, createsAndAlters: creas } = cds.compile.to.sql.delta (csn, o, beforeCsn);
114
+ if(beforeCsn === undefined) {
115
+ // If this is the first deployment done with automatic schema evolution, generate everything as if it was a drop create
116
+ // but set the afterCsn in the db so we can calculate a delta going forward
117
+ return { afterCsn, drops: creas.map (each => {
118
+ let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) "?([^\s"(]+)/im) || []
119
+ return `DROP ${kind} IF EXISTS ${entity};`
120
+ }).reverse(), creas };
121
+ }
122
+ return { afterCsn, drops, creas };
123
+ } else {
124
+ const creas = cds.compile.to.sql(csn, o);
125
+ const drops = creas.map (each => {
113
126
  let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) "?([^\s"(]+)/im) || []
114
127
  return `DROP ${kind} IF EXISTS ${entity};`
115
- }).reverse()
116
- if (o.dry) {
117
- console.log(); for (let each of drops) console.log(each)
118
- console.log(); for (let each of creates) console.log(each,'\n')
119
- return
120
- } else return db.run (async tx => {
121
- await tx.run(drops)
122
- await tx.run(creates)
123
- return true
124
- })
128
+ }).reverse();
129
+ return { afterCsn: {}, drops, creas };
125
130
  }
126
- else return db.deploy (csn,o)
127
131
  }
128
132
 
133
+ exports.create = async function (db, csn=db.model, o) {
134
+ const schemaEvo = (db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto')
135
+ if(db.deploy && !schemaEvo) {
136
+ // reset CSN state saved in db - if there is any
137
+ await db.run('DROP table if exists cds_Model;');
138
+ return db.deploy(csn, o);
139
+ }
129
140
 
130
- exports.init = (db, csn=db.model, log=()=>{}) => db.run (async tx => {
141
+ const in_memory = db.options?.credentials?.url === ':memory:';
142
+ let beforeCsn
143
+ if (!in_memory && schemaEvo) try {
144
+ const [{ csn }] = await db.read('cds.Model')
145
+ beforeCsn = JSON.parse(csn);
146
+ } catch(e) {
147
+ if (e.message.includes('no such table: cds_Model')) {
148
+ await db.run('CREATE table cds_Model ( csn CLOB )')
149
+ await db.insert({csn:''}).into('cds.Model')
150
+ }
151
+ }
152
+
153
+ const { afterCsn, drops, creas } = getSqls(db, csn, o, beforeCsn);
154
+
155
+ if (!creas || creas.length === 0) return
156
+ if (o.dry) {
157
+ console.log(); for (let each of drops) console.log(each,'\n')
158
+ console.log(); for (let each of creas) console.log(each,'\n')
159
+ return
160
+ } else return db.run (async tx => {
161
+ await tx.run(drops)
162
+ await tx.run(creas)
163
+ if (!in_memory && schemaEvo) {
164
+ await tx.update('cds.Model').with({ csn: JSON.stringify(afterCsn) })
165
+ }
166
+ return true
167
+ })
168
+ }
169
+
170
+
171
+ exports.init = (db, csn=db.model, o, csvs, log=()=>{}) => db.run (async tx => {
131
172
  const resources = await exports.resources(csn, {testdata: cds.env.features.test_data})
132
173
  const inits=[]
133
- for (let [file,e] of Object.entries(resources)) {
134
- if (e === '*') { // init.js/ts
135
- let x = await cds.utils._import(file); if (!x) continue
136
- if (x.default) x = x.default // default ESM export
137
- inits.push (!x.then && typeof x === 'function' ? x(db,csn) : x)
138
- log (file)
139
- } else { // from .csv or .json
140
- const INSERT_into = _from_csv_or_json [path.extname(file)]
141
- const src = await read(file,'utf8'); if (!src) continue
142
- const q = INSERT_into (e,src); if (!q) continue
174
+ const schemaEvo = (db.options?.schema_evolution === 'auto' || o?.schema_evolution === 'auto')
175
+
176
+
177
+ if (csvs) {
178
+ const ccsn = cds.compile.for['nodejs'](csn) // compile to calculate keys for newly added entities
179
+ for(let [e,src] of Object.entries(csvs)) {
180
+ const q = INSERT_from_csv (e,src,schemaEvo); if (!q) continue
143
181
  if (db.kind === 'better-sqlite') _add_missing_pks2(q)
144
- log (file,e)
182
+ q._target = ccsn.definitions[e]
145
183
  inits.push (tx.run(q) .catch (e => {
146
184
  throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ inspect(q) })
147
185
  }))
148
186
  }
187
+ } else {
188
+ for (let [file,e] of Object.entries(resources)) {
189
+ if (e === '*') { // init.js/ts
190
+ let x = await cds.utils._import(file); if (!x) continue
191
+ if (x.default) x = x.default // default ESM export
192
+ inits.push (!x.then && typeof x === 'function' ? x(db,csn) : x)
193
+ log (file)
194
+ } else { // from .csv or .json
195
+ const INSERT_into = _from_csv_or_json [path.extname(file)]
196
+ const src = await read(file,'utf8'); if (!src) continue
197
+ const q = INSERT_into (e,src,schemaEvo); if (!q) continue
198
+ if (db.kind === 'better-sqlite') _add_missing_pks2(q)
199
+ if (cds.requires['cds.xt.ModelProviderService']?.kind === 'in-sidecar') q._target = csn.definitions[e]
200
+ log (file,e)
201
+ inits.push (tx.run(q) .catch (e => {
202
+ throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ inspect(q) })
203
+ }))
204
+ }
205
+ }
149
206
  }
207
+
150
208
  await Promise.all (inits)
151
209
 
152
210
  function _add_missing_pks2 (q) {
153
- const {columns,rows} = q.INSERT // REVISIT: .entries are covered by current runtime. Should eventually also be handled here, as we likely don't do so in new db services
211
+ const {columns,rows} = q.UPSERT || q.INSERT // REVISIT: .entries are covered by current runtime. Should eventually also be handled here, as we likely don't do so in new db services
154
212
  if (columns) {
155
213
  const entity = csn.definitions[q._target.name], {uuid} = cds.utils
156
- for (let k in entity.keys) if (!columns.includes(k)) {
214
+ for (let k in entity.keys) if (!columns.includes(k) && !entity.keys[k].isAssociation) {
157
215
  columns.push(k)
158
216
  const t = entity.keys[k]._type, pk = t === 'cds.UUID' ? uuid : index => index+1
159
217
  rows.forEach ((row,index) => row.push(pk(index)))
@@ -216,7 +274,7 @@ const _entity4 = (file,csn) => {
216
274
  if (!entity) {
217
275
  if (/(.+)[._]texts_?/.test(name)) { // 'Books.texts', 'Books.texts_de'
218
276
  const base = csn.definitions [RegExp.$1]
219
- return base && _entity4 (base.elements.texts.target,csn)
277
+ return base?.elements?.texts && _entity4 (base.elements.texts.target, csn)
220
278
  }
221
279
  else return DEBUG && DEBUG (`warning: ${name} not in model`)
222
280
  }
@@ -228,16 +286,14 @@ const _entity4 = (file,csn) => {
228
286
  return entity.name ? entity : { name, __proto__:entity }
229
287
  }
230
288
 
231
- const INSERT_from_csv = (entity, csv) => {
289
+ const INSERT_from_csv = (entity, csv, schemaEvo) => {
232
290
  let [ cols, ...rows ] = cds.parse.csv (csv)
233
- if (rows.length > 0) return INSERT.into (entity) .columns (cols) .rows (rows)
234
- // if (rows.length > 0) return UPSERT.into (entity) .columns (cols) .rows (rows)
291
+ if (rows.length > 0) return (schemaEvo ? UPSERT : INSERT).into (entity) .columns (cols) .rows (rows)
235
292
  }
236
293
 
237
- const INSERT_from_json = (entity, json) => {
294
+ const INSERT_from_json = (entity, json, schemaEvo) => {
238
295
  let records = JSON.parse (json)
239
- if (records.length > 0) return INSERT.into (entity) .entries (records)
240
- // if (records.length > 0) return UPSERT.into (entity) .entries (records)
296
+ if (records.length > 0) return (schemaEvo ? UPSERT : INSERT).into (entity) .entries (records)
241
297
  }
242
298
 
243
299
  const _from_csv_or_json = { '.json': INSERT_from_json, '.csv': INSERT_from_csv, }
@@ -64,19 +64,16 @@ class Config {
64
64
  }
65
65
  }
66
66
 
67
- // 4. link dependent services (through kind/use)
68
- this._link_required_services()
69
-
70
- // 5. add process env
67
+ // 4. add process env before linking to allow things like CDS_requires_db=sql
71
68
  this._add_process_env(_context, _home)
72
69
 
73
- // 6. link dependent services again -> e.g. to allow things like CDS_requires_db=sql
70
+ // 5. link dependent services
74
71
  this._link_required_services()
75
72
 
76
- // 7. complete service configurations from cloud service bindings
73
+ // 6. complete service configurations from cloud service bindings
77
74
  this._add_cloud_service_bindings(process.env)
78
75
 
79
- // 8. Add compatibility for mtx
76
+ // 7. Add compatibility for mtx
80
77
  if (this.requires && this.requires.db) {
81
78
  if (this.requires.multitenancy !== undefined) {
82
79
  Object.defineProperty(this.requires.db, 'multiTenant', { value: !!this.requires.multitenancy })
@@ -84,7 +81,7 @@ class Config {
84
81
  else if (this.requires.db.multiTenant !== undefined) this.requires.multitenancy = this.requires.db.multiTenant
85
82
  }
86
83
 
87
- // 9. apply presets
84
+ // 8. apply presets
88
85
  presets (this)
89
86
 
90
87
  // Only if feature is enabled
@@ -230,10 +227,12 @@ class Config {
230
227
  if (!config) config = {}
231
228
  const pref_ = RegExp('^'+prefix+'[._]','i')
232
229
  for (let p in env) if (!(p in my) && pref_.test(p)) {
233
- const key = /[a-z]/.test(p) ? p : p.toLowerCase() //> CDS_FOO_BAR -> cds_foo_bar
234
- const path = key.slice(prefix.length+1) .split (key[prefix.length]) //> ['foo','bar']
230
+ const pEsc = p.replace(/__/g, '!!') // escaping of _ by __ : protect __ so that it's not split below
231
+ const key = /[a-z]/.test(pEsc) ? pEsc : pEsc.toLowerCase() //> CDS_FOO_BAR -> cds_foo_bar
232
+ let path = key.slice(prefix.length+1) .split (key[prefix.length]) //> ['foo','bar']
235
233
  for (var o=config,next;;) {
236
234
  next = path.shift()
235
+ next = next.replace(/!!/g, '_') // undo !! protection and reduce __ to _
237
236
  if (!path.length) break
238
237
  if (!path[0]) next = next+'-'+path.shift()+path.shift() // foo__bar -> foo-bar
239
238
  o = o[next] || (o[next] = {})
@@ -7,6 +7,10 @@ exports = module.exports = {
7
7
  middlewares: true
8
8
  },
9
9
 
10
+ "[schevo]": {
11
+ db: { schema_evolution: 'auto' },
12
+ },
13
+
10
14
  db: undefined,
11
15
  messaging: undefined,
12
16
  multitenancy: undefined,
@@ -251,8 +255,10 @@ require = (id) => { // eslint-disable-line no-global-assign
251
255
  }
252
256
 
253
257
  // REVISIT: we should have a real modular plugin technique for cds.env
254
- // eslint-disable-next-line cds/no-missing-dependencies
255
- const _mtxs = !process.env.OLD_MTX && require('@sap/cds-mtxs/lib/env-requires') || {
258
+ const utils = require ('../utils/cds-utils')
259
+ const _mtxs = !utils._oldMtx()
260
+ // eslint-disable-next-line cds/no-missing-dependencies
261
+ && require('@sap/cds-mtxs/lib/env-requires') || {
256
262
  "toggles": {
257
263
  model: "@sap/cds/srv/mtx"
258
264
  },
@@ -12,10 +12,6 @@ module.exports = {
12
12
  },
13
13
 
14
14
  features: {
15
- "[schevo]": {
16
- schema_evolution: true,
17
- },
18
- schema_evolution: false,
19
15
  folders: 'fts/*', // where to find feature toggles -> switch on by default when released
20
16
  cls: major > 12 || major == 12 && minor >= 18,
21
17
  live_reload: !production,
@@ -237,6 +237,10 @@
237
237
  }
238
238
  ]
239
239
  },
240
+ "middlewares": {
241
+ "type": "boolean",
242
+ "description": "Enables new middlewares support (experimental)."
243
+ },
240
244
  "multitenancy": {
241
245
  "type": "boolean",
242
246
  "description": "Shortcut to enable multitenancy."
@@ -360,6 +364,40 @@
360
364
  ]
361
365
  }
362
366
  }
367
+ },
368
+ "protocols": {
369
+ "type": "object",
370
+ "description": "List protocols to enable in addition to the default protocol adaptors.",
371
+ "additionalProperties": true,
372
+ "patternProperties": {
373
+ ".+": {
374
+ "oneOf": [
375
+ {
376
+ "type": "boolean",
377
+ "description": "Enables a built-in protocol adapter with defaults"
378
+ },
379
+ {
380
+ "type": "string",
381
+ "description": "The endpoint to serve this protocol at."
382
+ },
383
+ {
384
+ "type": "object",
385
+ "default": {},
386
+ "additionalProperties": true,
387
+ "properties": {
388
+ "path": {
389
+ "type": "string",
390
+ "description": "The endpoint to serve this protocol at."
391
+ },
392
+ "impl": {
393
+ "type": "string",
394
+ "description": "The module pathname of the protocol adapter implementation."
395
+ }
396
+ }
397
+ }
398
+ ]
399
+ }
400
+ }
363
401
  }
364
402
  },
365
403
  "$defs": {
package/lib/ql/SELECT.js CHANGED
@@ -6,16 +6,22 @@ module.exports = class Query extends Whereable {
6
6
  static _api() {
7
7
  const $ = Object.assign
8
8
  return $((..._) => new this()._select_or_from(..._), {
9
- one: $((...x) => new this({one:true})._select_or_from(...x),{
10
- columns: (..._) => new this({one:true}).columns(..._),
11
- from: (..._) => new this({one:true}).from(..._),
9
+ localized: $((...x) => new this({localized:true})._select_or_from(...x),{
10
+ columns: (..._) => new this({localized:true}).columns(..._),
11
+ from: (..._) => new this({localized:true}).from(..._),
12
12
  }),
13
13
  distinct: $((...x) => new this({distinct:true})._select_or_from(...x),{
14
14
  columns: (..._) => new this({distinct:true}).columns(..._),
15
15
  from: (..._) => new this({distinct:true}).from(..._),
16
16
  }),
17
+ one: $((...x) => new this({one:true})._select_or_from(...x),{
18
+ columns: (..._) => new this({one:true}).columns(..._),
19
+ from: (..._) => new this({one:true}).from(..._),
20
+ }),
21
+ from: $((..._) => new this().from(..._), {
22
+ localized: (..._) => new this({localized:true}).from(..._)
23
+ }),
17
24
  columns: (..._) => new this().columns(..._),
18
- from: (..._) => new this().from(..._),
19
25
  })
20
26
  }
21
27
 
@@ -12,7 +12,7 @@ module.exports = class Bindings {
12
12
  static get registry(){ return registry }
13
13
 
14
14
  static then(r,e) {
15
- const LOG = cds.log('serve', { prefix:'cds' })
15
+ const LOG = cds.log('cds.serve', { prefix:'cds' })
16
16
  const bindings = new Bindings
17
17
  cds.prependOnceListener ('connect', ()=> LOG._info && LOG.info ('connect using bindings from:', { registry }))
18
18
  cds.once('listening', ({url})=> bindings.export (cds.service.providers, url))
@@ -1,6 +1,6 @@
1
1
  const cds = require('..'), { path, isfile } = cds.utils
2
2
  const paths = Array.from (new Set ([ cds.root, ...require.resolve.paths('x') ]))
3
- const DEBUG = cds.debug('srv.factory'); DEBUG && DEBUG ({ 'cds.root':cds.root, paths })
3
+ const DEBUG = cds.debug('cds.service.factory',); DEBUG && DEBUG ({ 'cds.root':cds.root, paths })
4
4
 
5
5
  /** @typedef {import('./srv-api')} Service @type { (()=>Service) & (new()=>Service) } */
6
6
  const ServiceFactory = function (name, model, options) { //NOSONAR
@@ -7,8 +7,6 @@ module.exports = ()=> {
7
7
  const ctx = {}
8
8
  ctx.http = { req, res }
9
9
  ctx.id = _id4(req)
10
- ctx.user = req.user
11
- ctx.tenant = req.tenant || ctx.user?.tenant
12
10
  cds._context.run (ctx, next)
13
11
  }
14
12
 
@@ -0,0 +1,11 @@
1
+ const cds = require ('../../index')
2
+
3
+ /**
4
+ * Propagates auth results to cds.context
5
+ */
6
+ module.exports = ()=> function cds_context_auth (req, res, next) {
7
+ const ctx = cds.context
8
+ ctx.user = req.user
9
+ ctx.tenant = req.tenant || ctx.user?.tenant
10
+ next()
11
+ }
@@ -1,24 +1,26 @@
1
- module.exports = ()=>{
1
+ module.exports = ()=> {
2
+
2
3
  const cds = require ('../../index')
3
- if (cds.requires.extensibility || cds.requires.toggles || cds.mtx) {
4
- const { model4 } = require('../srv-models')
5
- return async function cds_context_model (req,res, next) {
6
- if (req.baseUrl.startsWith('/-/')) return next() //> our own tech services cannot be extended
7
- const ctx = cds.context
8
- if (ctx.tenant) try {
9
- // if (req.headers.features) ctx.user.features = req.headers.features //> currently done in basic-auth only
10
- ctx.model = req.__model = await model4 (ctx.tenant, ctx.features) // REVISIT: req.__model is because of Okra
11
- } catch (e) {
12
- console.error(e)
13
- return res.status(503) .json ({ // REVISIT: we should throw a simple error, nothing else! -> this is overly OData-specific!
14
- error: { code: '503', message:
15
- process.env.NODE_ENV === 'production' ? 'Service Unavailable' :
16
- 'Unable to get context-specific model due to: ' + e.message
17
- }
18
- })
19
- }
20
- next()
4
+ const context_model_required = cds.requires.extensibility || cds.requires.toggles || cds.mtx
5
+ if (!context_model_required) return []
6
+
7
+ const { model4 } = require('../srv-models')
8
+ return async function cds_context_model (req,res, next) {
9
+ if (req.baseUrl.startsWith('/-/')) return next() //> our own tech services cannot be extended
10
+ const ctx = cds.context
11
+ if (ctx.tenant) try {
12
+ // if (req.headers.features) ctx.user.features = req.headers.features //> currently done in basic-auth only
13
+ ctx.model = req.__model = await model4 (ctx.tenant, ctx.features) // REVISIT: req.__model is because of Okra
14
+ } catch (e) {
15
+ console.error(e)
16
+ return res.status(503) .json ({ // REVISIT: we should throw a simple error, nothing else! -> this is overly OData-specific!
17
+ error: { code: '503', message:
18
+ process.env.NODE_ENV === 'production' ? 'Service Unavailable' :
19
+ 'Unable to get context-specific model due to: ' + e.message
20
+ }
21
+ })
21
22
  }
23
+ next()
22
24
  }
23
- else return []
25
+
24
26
  }
@@ -1,22 +1,20 @@
1
1
  const auth = exports.auth = require('../../auth')
2
2
  const context = exports.context = require('./cds-context')
3
+ const ctx_auth = exports.ctx_auth = require('./ctx-auth')
3
4
  const ctx_model = exports.ctx_model = require('./ctx-model')
4
5
  const errors = exports.errors = require('./errors')
5
6
  const trace = exports.trace = require('./trace')
6
7
 
7
8
  // middlewares running before protocol adapters
8
9
  exports.before = [
9
- trace(), // provides detailed trace logs when DEBUG=trace
10
- auth(), // provides req.user & tenant
11
- context(), // provides cds.context
12
- ctx_model(), // fills in cds.context.model
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
13
15
  ]
14
16
 
17
+ // middlewares running after protocol adapters -> usually error middlewares
15
18
  exports.after = [
16
- // usually error middlewares
17
19
  errors(),
18
20
  ]
19
-
20
- exports.bootstrap = ()=>{
21
- require('../protocols')()
22
- }
@@ -1,5 +1,7 @@
1
1
  const libx = require('../../../libx/_runtime')
2
2
  const cds_context_model = require('../srv-models')
3
+ const cds_context = require('../middlewares/cds-context')()
4
+ const ctx_auth = require('../middlewares/ctx-auth')()
3
5
  const { ProtocolAdapter } = require('.')
4
6
 
5
7
  class LegacyProtocolAdapter extends ProtocolAdapter {
@@ -14,9 +16,11 @@ class LegacyProtocolAdapter extends ProtocolAdapter {
14
16
  static serve (srv, /* in: */ app) {
15
17
  return super.serve (srv, app, { before: [
16
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
+ cds_context,
17
20
  cap_req_logger,
18
21
  libx.perf,
19
22
  libx.auth(srv),
23
+ ctx_auth,
20
24
  cds_context_model.middleware4(srv)
21
25
  ], after:[] })
22
26
  }
@@ -1,7 +1,7 @@
1
1
  const cds = require ('../../index'), { decodeURIComponent } = cds.utils
2
2
  const LOG = cds.log('graphql')
3
3
 
4
- const GraphQLAdapter = require('@sap/cds-graphql/lib') // eslint-disable-line cds/no-missing-dependencies
4
+ const GraphQLAdapter = require('@cap-js/graphql') // eslint-disable-line cds/no-missing-dependencies
5
5
  const express = require ('express') // eslint-disable-line cds/no-missing-dependencies
6
6
 
7
7
  function CDSGraphQLAdapter (options) {
@@ -33,7 +33,7 @@ function CDSGraphQLAdapter (options) {
33
33
  })
34
34
 
35
35
  /** The global /graphql route */
36
- .use (new GraphQLAdapter (services, options))
36
+ .use (new GraphQLAdapter (options))
37
37
  }
38
38
 
39
39
  module.exports = CDSGraphQLAdapter
@@ -10,7 +10,6 @@ class ProtocolAdapter {
10
10
  for (let [k,o] of Object.entries(protocols)) if (typeof o === 'string') protocols[k] = {path:o}
11
11
  if (!protocols.odata) protocols.odata = { impl: join(__dirname,'odata-v4') }
12
12
  if (!protocols.rest) protocols.rest = { impl: join(__dirname,'rest') }
13
-
14
13
  // odata must always be first for fallback
15
14
  return this.protocols = { odata: protocols.odata, ...protocols }
16
15
  }
@@ -84,5 +83,10 @@ const protocols = Object.keys(ProtocolAdapter.init())
84
83
  const protocol4 = (def, _default = protocols[0]) => def['@protocol'] || protocols.find(p => def['@'+p]) || _default
85
84
  const is_global = adapter => adapter.length === 1 && !/^(function )?(\w+\s+)?\((srv|service)/.test(adapter)
86
85
 
87
- module.exports = Object.assign (ProtocolAdapter.serveAll, { ProtocolAdapter, protocol4 })
88
- if (!cds.requires.middlewares) module.exports.ProtocolAdapter = require('./_legacy')
86
+ module.exports = { ProtocolAdapter, protocol4 }
87
+ if (cds.env.protocols) {
88
+ cds.middlewares = require('../middlewares')
89
+ ProtocolAdapter.serveAll()
90
+ } else if (!cds.requires.middlewares) {
91
+ module.exports.ProtocolAdapter = require('./_legacy')
92
+ }
@@ -76,6 +76,7 @@ class Service extends require('./srv-handlers') {
76
76
  insert (...args) { return INSERT(...args).bind(this) }
77
77
  create (...args) { return INSERT.into(...args).bind(this) }
78
78
  update (...args) { return UPDATE.entity(...args).bind(this) }
79
+ upsert (...args) { return UPSERT(...args).bind(this) }
79
80
  exists (...args) { return SELECT.one([1]).from(...args).bind(this) }
80
81
 
81
82
  /**
@@ -1,5 +1,5 @@
1
1
  const cds = require('..')
2
- const LOG = cds.log('cds-app-service-methods')
2
+ const LOG = cds.log('cds.serve',{label:'cds'})
3
3
 
4
4
 
5
5
  module.exports = (srv) => {
@@ -176,6 +176,17 @@ exports.find = function find (base, patterns='*', filter=()=>true) {
176
176
  return files
177
177
  }
178
178
 
179
+ // TODO find a better place
180
+ exports._oldMtx = function _oldMtx() {
181
+ function _hasMtxDependency() {
182
+ try {
183
+ return require(join(cds.root, 'package.json'))?.dependencies?.['@sap/cds-mtx']
184
+ } catch(e) {
185
+ return false
186
+ }
187
+ }
188
+ return (_hasMtxDependency() || process.env.OLD_MTX?.toLowerCase() === 'true') ?? false
189
+ }
179
190
 
180
191
  /**
181
192
  * Internal utility to load a file through ESM or CommonJs. TODO find a better place.
package/lib/utils/data.js CHANGED
@@ -13,8 +13,8 @@ class DataUtil {
13
13
  }
14
14
  }
15
15
  if (this._deletes.length > 0) {
16
- const log = cds.log('deploy')
17
- if (log._info) log.info('Deleting all data in', this._deletes)
16
+ const LOG = cds.log('deploy')
17
+ if (!this._autoReset) LOG.info('Deleting all data for', db.model.each('entity'))
18
18
  await db.run(this._deletes)
19
19
  }
20
20
  }