@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
@@ -1,12 +1,9 @@
1
- const cds = require('../index'), { local, inspect } = cds.utils, { UPSERT } = cds.ql
1
+ const cds = require('../index'), { local } = cds.utils
2
+ const COLORS = !!process.stdout.isTTY && !!process.stderr.isTTY
3
+ const GREY = COLORS ? '\x1b[2m' : ''
4
+ const RESET = COLORS ? '\x1b[0m' : ''
2
5
  const DEBUG = cds.debug('deploy')
3
-
4
- const colors = !!process.stdout.isTTY && !!process.stderr.isTTY
5
- const term = {
6
- x1b2: colors ? '\x1b[2m' : '',
7
- x1b0: colors ? '\x1b[0m' : '',
8
- info: colors ? (s => term.x1b2 + s + term.x1b0) : (s => s)
9
- }
6
+ const TRACE = cds.debug('trace')
10
7
 
11
8
  /**
12
9
  * Implementation of `cds.deploy` common to all databases.
@@ -18,18 +15,10 @@ exports = module.exports = function cds_deploy (model,options,csvs) { return {
18
15
 
19
16
  /** @param {cds.Service} db */
20
17
  async to (db, o = options || cds.options || {}) {
21
- if (!model) throw new Error('Must provide a model or a path to model, received: ' + model)
22
- const LOG = o.silent || !cds.log('deploy')._info ? ()=>{} : console.log
18
+ TRACE?.time ('cds.deploy db ')
23
19
 
24
- if (model && !model.definitions) {
25
- model = await cds.load (model) .then (cds.minify)
26
- if (DEBUG) try {
27
- DEBUG (`loaded model from ${model.$sources.length} file(s):\n${term.x1b2}`)
28
- for (let each of model.$sources) console.log (' ', local(each))
29
- } finally {
30
- console.log (term.x1b0)
31
- }
32
- }
20
+ if (!model) throw new Error('Must provide a model or a path to model, received: ' + model)
21
+ if (model && !model.definitions) model = await cds.load(model).then(cds.minify)
33
22
 
34
23
  if (o.mocked) exports.include_external_entities_in (model)
35
24
  else exports.exclude_external_entities_in (model)
@@ -39,18 +28,18 @@ exports = module.exports = function cds_deploy (model,options,csvs) { return {
39
28
  if (!db.model) db.model = model
40
29
 
41
30
  // create tables & views...
31
+ const LOG = o.silent || !cds.log('deploy')._info ? ()=>{} : console.log
42
32
  const any = await exports.create (db,model,o)
43
33
  if (!any && !csvs) return db
44
34
 
45
35
  // fill in initial data...
46
- await exports.init (db,model,o,csvs, file => LOG(
47
- term.info(` > init from ${local(file)}`)
48
- ))
36
+ await exports.init (db,model,o,csvs, file => LOG (GREY,` > init from ${local(file)}`, RESET))
49
37
 
50
38
  // done
51
- const file = db.getDbUrl(cds.context?.tenant)
52
- if (file !== ':memory:') LOG (`/> successfully deployed to ./${file}\n`)
53
- else LOG (`/> successfully deployed to sqlite in-memory db\n`)
39
+ let url = db.url4 (cds.context?.tenant); if (url === ':memory:') url = 'in-memory database.'
40
+ LOG ('/> successfully deployed to', url, '\n')
41
+
42
+ TRACE?.timeEnd ('cds.deploy db ')
54
43
  return db
55
44
  },
56
45
 
@@ -105,127 +94,104 @@ exports.exclude_external_entities_in = function (csn) { // NOSONAR
105
94
  }
106
95
  }
107
96
 
108
- function getSqls(db, csn, o, beforeCsn) {
109
- const schemaEvo = (db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto')
110
- if (schemaEvo) {
111
- const { afterImage: afterCsn, drops, createsAndAlters: creas } = cds.compile.to.sql.delta (csn, o, beforeCsn);
112
- if(beforeCsn === undefined) {
113
- // If this is the first deployment done with automatic schema evolution, generate everything as if it was a drop create
114
- // but set the afterCsn in the db so we can calculate a delta going forward
115
- return { afterCsn, drops: creas.map (each => {
116
- let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) "?([^\s"(]+)/im) || []
117
- return `DROP ${kind} IF EXISTS ${entity};`
118
- }).reverse(), creas };
119
- }
120
- return { afterCsn, drops, creas };
121
- } else {
122
- const creas = cds.compile.to.sql(csn, o);
123
- const drops = creas.map (each => {
124
- let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) "?([^\s"(]+)/im) || []
125
- return `DROP ${kind} IF EXISTS ${entity};`
126
- }).reverse();
127
- return { afterCsn: {}, drops, creas };
128
- }
129
- }
130
97
 
131
- exports.create = async function (db, csn=db.model, o) {
132
- const schemaEvo = (db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto')
133
- if(db.deploy && !schemaEvo) {
134
- // reset CSN state saved in db - if there is any
135
- if(!o.dry) await db.run('DROP table if exists cds_Model;');
136
- return db.deploy(csn, o);
98
+ exports.create = async function cds_deploy_create (db, csn=db.model, o) {
99
+
100
+ let drops, creas, schevo = db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto'
101
+ if (schevo) {
102
+ // REVISIT: replace by db-specific ways to read/updated recent deployed model
103
+ let before = await db.run (async tx => { try {
104
+ let [{ csn }] = await tx.run('SELECT csn from cds_Model')
105
+ return JSON.parse(csn)
106
+ } catch(e) { if (!e.message.includes('no such table')) throw e
107
+ await tx.run(`CREATE table cds_Model (csn CLOB)`)
108
+ await tx.run(`INSERT into cds_Model values ('null')`)
109
+ }})
110
+ const { afterImage, drops:_drops, createsAndAlters:_creas } = cds.compile.to.sql.delta (csn, o, before)
111
+ creas = _creas.concat (o.dry ? [] : UPDATE('cds.Model').with({ csn: JSON.stringify(afterImage) }))
112
+ drops = before ? _drops : _drops4(_creas)
113
+ } else {
114
+ creas = cds.compile.to.sql (csn, o)
115
+ drops = _drops4(creas) .concat ('DROP table if exists cds_Model;')
137
116
  }
117
+ if (!creas || creas.length === 0) return
138
118
 
139
- let beforeCsn
140
- if (schemaEvo) try {
141
- const [{ csn }] = await db.read('cds.Model')
142
- beforeCsn = JSON.parse(csn);
143
- } catch(e) {
144
- if (e.message.includes('no such table: cds_Model')) {
145
- await db.run('CREATE table cds_Model ( csn CLOB )')
146
- await db.insert({csn:''}).into('cds.Model')
147
- }
119
+ function _drops4 (creas) {
120
+ return creas.map (each => {
121
+ let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) ("[^"]+"|[^\s(]+)/im) || []
122
+ return `DROP ${kind} IF EXISTS ${entity};`
123
+ }).reverse()
148
124
  }
149
125
 
150
- const { afterCsn, drops, creas } = getSqls(db, csn, o, beforeCsn);
151
-
152
- if (!creas || creas.length === 0) return
153
126
  if (o.dry) {
154
- console.log(); for (let each of drops) console.log(each,'\n')
127
+ console.log(); for (let each of drops) console.log(each)
155
128
  console.log(); for (let each of creas) console.log(each,'\n')
156
- return
157
- } else return db.run (async tx => {
129
+ }
130
+ else return db.run (async tx => {
131
+ // This runs in a new transaction if called from CLI, while joining
132
+ // existing root tx, e.g. when called from DeploymentService.
133
+ if (!schevo && await tx.exists('sqlite.schema').where({name:'cds_xt_Extensions'})) {
134
+ // Poor man's schema evolution for extensions in drop-create deployments
135
+ drops = drops.filter(d => !d.includes('cds_xt_Extensions'))
136
+ creas = creas.filter(c => !c.includes('cds_xt_Extensions'))
137
+ }
138
+ // Set the context model while deploying for cqn42sql in new db layers
139
+ tx.model = cds.compile.for.nodejs(csn)
158
140
  await tx.run(drops)
159
141
  await tx.run(creas)
160
- if (schemaEvo) {
161
- await tx.update('cds.Model').with({ csn: JSON.stringify(afterCsn) })
162
- }
163
142
  return true
164
143
  })
165
144
  }
166
145
 
167
146
 
168
- exports.init = (db, csn=db.model, o, csvs, log=()=>{}) => db.run (async tx => {
169
-
170
- const {tenant} = cds.context; if (tenant && tenant === cds.requires.multitenancy?.t0) return
171
- const schemaEvo = db.options?.schema_evolution === 'auto' || o?.schema_evolution === 'auto'
172
- const resources = await exports.resources(csn, {testdata: cds.env.features.test_data})
173
- const inits=[]
174
-
175
- if (csvs) {
176
- const ccsn = cds.compile.for['nodejs'](csn) // compile to calculate keys for newly added entities
177
- for(let [file,src] of Object.entries(csvs)) {
178
- const entity = _entity4(path.basename(file, '.csv'), csn)
179
- if (entity?.name) {
180
- const q = INSERT_from_csv (entity.name,src,schemaEvo); if (!q) continue
181
- if (db.kind === 'better-sqlite') _add_missing_pks2(q)
182
- q._target = ccsn.definitions[entity.name]
183
- inits.push (tx.run(q) .catch (e => {
184
- throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ inspect(q) })
185
- }))
186
- }
187
- }
188
- } else {
189
- for (let [file,e] of Object.entries(resources)) {
190
- if (e === '*') { // init.js/ts
191
- let x = await cds.utils._import(file); if (!x) continue
192
- if (x.default) x = x.default // default ESM export
193
- inits.push (!x.then && typeof x === 'function' ? x(tx,csn) : x)
194
- log (file)
195
- } else { // from .csv or .json
196
- const INSERT_into = _from_csv_or_json [path.extname(file)]
197
- const src = await read(file,'utf8'); if (!src) continue
198
- const q = INSERT_into (e,src,schemaEvo); if (!q) continue
199
- if (db.kind === 'better-sqlite') _add_missing_pks2(q)
200
- if (cds.requires['cds.xt.ModelProviderService']?.kind === 'in-sidecar') q._target = csn.definitions[e]
201
- log (file,e)
202
- inits.push (tx.run(q) .catch (e => {
203
- throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ inspect(q) })
204
- }))
205
- }
206
- }
207
- }
147
+ exports.init = async function cds_deploy_init (db, csn=db.model, o, srces, log=()=>{}) {
148
+ const t = cds.context?.tenant; if (t && t === cds.requires.multitenancy?.t0) return
149
+ return db.run (async tx => {
208
150
 
209
- await Promise.all (inits)
151
+ const m = tx.model = cds.compile.for.nodejs(csn) //> use correct model while deploying
152
+ const data = await exports.data (m,srces)
153
+ const query = _queries4 (db,m)
154
+ const INSERT_from = INSERT_from4 (db,o)
210
155
 
211
- function _add_missing_pks2 (q) {
212
- 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
213
- if (columns) {
214
- const entity = csn.definitions[q._target.name], {uuid} = cds.utils
215
- for (let k in entity.keys) if (!columns.includes(k) && !entity.keys[k].isAssociation) {
216
- columns.push(k)
217
- const t = entity.keys[k]._type, pk = t === 'cds.UUID' ? uuid : index => index+1
218
- rows.forEach ((row,index) => row.push(pk(index)))
156
+ for await (let [ file, entity, src ] of data) {
157
+ log (file)
158
+ if (entity) {
159
+ const q = INSERT_from (file) .into (entity, src)
160
+ if (q) try { await tx.run (query(q)) } catch(e) {
161
+ throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ cds.utils.inspect(q) })
162
+ }
163
+ } else { //> init.js/ts case
164
+ if (typeof src === 'function') src(tx,csn)
219
165
  }
220
166
  }
221
- }
167
+ })
168
+ }
222
169
 
223
- })
170
+
171
+ /** Prepare input from .csv, .json, init.js, ... */
172
+ exports.data = async function cds_deploy_prepare_data (csn, srces) {
173
+ // In case of extension deployment .csv or .json input are provided through argument `srces`.
174
+ if (srces) return Object.entries(srces) .map (([file, src]) => {
175
+ let e = _entity4 (path.basename(file,'.csv'), csn)
176
+ return [ file, e, src ]
177
+ })
178
+ // If not, we load them from cds.deploy.resources(csn)
179
+ const resources = await exports.resources(csn, { testdata: cds.env.features.test_data })
180
+ return Object.entries(resources) .map (async ([file,e]) => {
181
+ if (e === '*') {
182
+ let init_js = await cds.utils._import (file)
183
+ return [ file, null, init_js.default || init_js ]
184
+ } else {
185
+ let src = await read (file, 'utf8')
186
+ return [ file, e, src ]
187
+ }
188
+ })
189
+ }
224
190
 
225
191
 
226
- exports.resources = async function (csn, opts) {
192
+ exports.resources = async function cds_deploy_resources (csn, opts) {
227
193
  if (!csn || !csn.definitions) csn = await cds.load (csn||'*') .then (cds.minify)
228
- const folders = await exports.resources.folders(csn, opts)
194
+ const folders = await cds_deploy_resources.folders(csn, opts)
229
195
  const found={}, ts = process.env.CDS_TYPESCRIPT
230
196
  for (let folder of folders) {
231
197
  // fetching init.js files
@@ -237,7 +203,7 @@ exports.resources = async function (csn, opts) {
237
203
  const files = await readdir (subdir)
238
204
  for (let fx of files) {
239
205
  if (fx[0] === '-') continue
240
- const ext = path.extname(fx); if (ext in _from_csv_or_json) {
206
+ const ext = path.extname(fx); if (ext in {'.csv':1,'.json':2}) {
241
207
  const f = fx.slice(0,-ext.length)
242
208
  if (/[._]texts$/.test(f) && files.some(g => g.startsWith(f+'_'))) {
243
209
  // ignores 'Books_texts.csv/json' if there is any 'Books_texts_LANG.csv/json'
@@ -287,17 +253,47 @@ const _entity4 = (file,csn) => {
287
253
  return entity.name ? entity : { name, __proto__:entity }
288
254
  }
289
255
 
290
- const INSERT_from_csv = (entity, csv, schemaEvo) => {
291
- let [ cols, ...rows ] = cds.parse.csv (csv)
292
- if (rows.length > 0) return (schemaEvo ? UPSERT : INSERT).into (entity) .columns (cols) .rows (rows)
256
+
257
+ /** Prepare special handling for new db services */
258
+ const _queries4 = (db,csn) => !db.cqn2sql ? q => q : q => {
259
+ const { columns, rows } = q.INSERT || q.UPSERT; if (!columns) return q // REVISIT: .entries are covered by current runtime -> should eventually also be handled here
260
+ const entity = csn.definitions[q._target.name]
261
+
262
+ // Fill in missing primary keys...
263
+ const { uuid } = cds.utils
264
+ for (let k in entity.keys) if (entity.keys[k].isUUID && !columns.includes(k)) {
265
+ columns.push(k)
266
+ rows.forEach(row => row.push(uuid()))
267
+ }
268
+
269
+ // Fill in missing managed data...
270
+ const pseudos = { $user: 'anonymous', $now: (new Date).toISOString() }
271
+ for (let k in entity.elements) {
272
+ const managed = entity.elements[k]['@cds.on.insert']?.['=']
273
+ if (managed && !columns.includes(k)) {
274
+ columns.push(k)
275
+ rows.forEach(row => row.push(pseudos[managed]))
276
+ }
277
+ }
278
+
279
+ return q
293
280
  }
294
281
 
295
- const INSERT_from_json = (entity, json, schemaEvo) => {
296
- let records = JSON.parse (json)
297
- if (records.length > 0) return (schemaEvo ? UPSERT : INSERT).into (entity) .entries (records)
282
+
283
+ const INSERT_from4 = (db,o) => {
284
+ const schevo = db.options?.schema_evolution === 'auto' || o?.schema_evolution === 'auto'
285
+ const INSERT_into = (schevo ? UPSERT : INSERT).into
286
+ return (file) => ({
287
+ '.json': { into (entity, json) {
288
+ let records = JSON.parse(json)
289
+ if (records.length > 0) return INSERT_into(entity).entries(records)
290
+ }},
291
+ '.csv': { into (entity, csv) {
292
+ let [cols, ...rows] = cds.parse.csv(csv)
293
+ if (rows.length > 0) return INSERT_into(entity).columns(cols).rows(rows)
294
+ }},
295
+ }) [path.extname(file)]
298
296
  }
299
297
 
300
- const _from_csv_or_json = { '.json': INSERT_from_json, '.csv': INSERT_from_csv, }
301
298
  const _skip = e => !e || e['@cds.persistence.skip'] === true
302
-
303
299
  /* eslint-disable no-console */
@@ -32,6 +32,11 @@ class Config {
32
32
  const { NODE_ENV, CDS_ENV } = process.env, profiles = []
33
33
  if (NODE_ENV) profiles.push (NODE_ENV)
34
34
  if (CDS_ENV) profiles.push (...CDS_ENV.split(/\s*,\s*/))
35
+ if (_home) try {
36
+ const { cds } = require(path.join(_home,'package.json'))
37
+ if (cds?.profiles) profiles.push(...cds.profiles)
38
+ if (cds?.profile) profiles.push(cds.profile)
39
+ } catch {/* ignore */}
35
40
  if (!profiles.includes('production')) profiles.push('development')
36
41
  this._profiles = new Set (profiles)
37
42
  this._profiles._defined = new Set()
@@ -127,8 +132,9 @@ class Config {
127
132
  * Retrieves the value for a config option, specified as a property path.
128
133
  */
129
134
  get (option) {
130
- if (!option) return
131
- return option.split('.').reduce ((p,n)=> p && p[n], this)
135
+ if (!option) return
136
+ let path = option.includes('/') ? option.split('/') : option.split('.')
137
+ return path.reduce ((p,n)=> p && p[n], this)
132
138
  }
133
139
 
134
140
  get profiles() {
@@ -244,7 +250,7 @@ class Config {
244
250
  }
245
251
 
246
252
  _link_required_services () {
247
- const { requires } = this; if (!requires) return
253
+ const { requires, _profiles } = this; if (!requires) return
248
254
  const kinds = requires.kinds || {}
249
255
  Object.defineProperty (requires, 'kinds', { value:kinds, enumerable:false }) // for cds env
250
256
  // Object.setPrototypeOf (requires, kinds)
@@ -254,27 +260,28 @@ class Config {
254
260
  if (!val || val._is_linked) return val
255
261
  if (val === true) {
256
262
  let x = kinds[key]
257
- if (!x) return val
258
- else val = x
263
+ if (x) val = x; else return val
259
264
  }
260
265
  if (typeof val === 'string') {
261
266
  let x = kinds[val] || kinds[val+'-'+key] || kinds[key+'-'+val]
262
- if (!x) return val
263
- else val = { kind: val }
267
+ if (x) val = {kind:val}; else return val
264
268
  }
265
- const preset1 = kinds[key]
266
- if (typeof preset1 === 'object' && preset1 !== val) {
267
- const top = val, base = _merge ({},_linked(key,preset1)), {kind} = base
268
- val = _merge (base, top, false, false) // apply/override with top-level data
269
- if (kind) val.kind = kind // but inherited kind wins
269
+ let k = val.kind, p, preset = kinds[p=k] || kinds[p=k+'-'+key] || kinds[p=key+'-'+k]
270
+ if (!preset?.$root) {
271
+ const preset1 = kinds[key]
272
+ if (typeof preset1 === 'object' && preset1 !== val) {
273
+ const top = val, base = _merge ({},_linked(key,preset1)), {kind} = base
274
+ val = _merge (base, top, false, false) // apply/override with top-level data
275
+ if (kind) val.kind = kind // but inherited kind wins
276
+ }
270
277
  }
271
- let k = val.kind, p, preset = k && kinds[p=k] || kinds[p=k+'-'+key] || kinds[p=key+'-'+k]
272
278
  if (typeof preset === 'object' && preset !== val) {
273
- const top = val, base = _merge ({},_linked(p,preset)), {kind} = base
274
- val = _merge (base, top, false, false) // apply/override with top-level data
279
+ const top = val, base = _merge ({},_linked(p,preset), _profiles), {kind} = base
280
+ val = _merge (base, top, _profiles, false) // apply/override with top-level data
275
281
  if (kind) val.kind = kind // but inherited kind wins
276
282
  }
277
- return Object.defineProperty (val, '_is_linked', {value:true})
283
+ if (typeof val === 'object') Object.defineProperty (val, '_is_linked', {value:true})
284
+ return val
278
285
  }
279
286
  }
280
287
 
@@ -435,7 +442,8 @@ function _merge (dst, src, _profiles, _cloned, _profiles_only = false) {
435
442
  const profile = p.slice(1,-1)
436
443
  if (_profiles._defined) _profiles._defined.add (profile)
437
444
  if (_profiles.has(profile)) {
438
- profiled.push ({ profile, merge: () => _merge (dst, src[p], _profiles, _cloned, false)})
445
+ let o = src[p]; if (typeof o === 'string') o = {kind:o}
446
+ profiled.push ({ profile, merge: () => _merge (dst, o, _profiles, _cloned, false)})
439
447
  }
440
448
  continue
441
449
  }
@@ -2,16 +2,8 @@ const _runtime = '@sap/cds/libx/_runtime'
2
2
 
3
3
  exports = module.exports = {
4
4
 
5
- middlewares: false,
6
- "[middlewares]": {
7
- middlewares: true
8
- },
9
-
10
- "[schevo]": {
11
- db: { schema_evolution: 'auto' },
12
- },
13
-
14
- db: undefined,
5
+ middlewares: undefined, "[middlewares]": { middlewares: true },
6
+ db: undefined, "[schevo]": { db: { schema_evolution: 'auto' }, },
15
7
  messaging: undefined,
16
8
  multitenancy: undefined,
17
9
  extensibility: undefined,
@@ -127,19 +119,20 @@ const _databases = {
127
119
  "sql": {
128
120
  '[development]': { kind: 'sqlite', credentials: { url: ':memory:' } },
129
121
  '[production]': { kind: 'hana' },
130
- '[better-sqlite]': { kind: 'better-sqlite' },
131
122
  },
132
- "better-sqlite": _compat_to_use({
123
+ "better-sqlite": {
133
124
  credentials: { url: ":memory:" },
134
125
  impl: "@cap-js/sqlite",
135
- }),
136
- "sql-mt": {
126
+ kind: 'sqlite'
127
+ },
128
+ "sql-mt": { // For compatibility only
137
129
  '[development]': { kind: 'sqlite' },
138
130
  '[production]': { kind: 'hana-mt' },
139
131
  },
140
132
  "sqlite": _compat_to_use({
141
- credentials: { url: 'db.sqlite' },
133
+ '[better-sqlite]': { impl: '@cap-js/sqlite' },
142
134
  impl: `${_runtime}/sqlite/Service.js`,
135
+ credentials: { url: 'db.sqlite' },
143
136
  }),
144
137
  "hana": _compat_to_use ({
145
138
  impl: `${_runtime}/hana/Service.js`,
@@ -246,37 +239,12 @@ const _platform_services = {
246
239
 
247
240
  }
248
241
 
249
- require = (id) => { // eslint-disable-line no-global-assign
250
- try {
251
- return module.require(id)
252
- } catch(e) {
253
- if (e.code !== 'MODULE_NOT_FOUND') throw e
254
- }
255
- }
256
-
257
- // REVISIT: we should have a real modular plugin technique for cds.env
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') || {
262
- "toggles": {
263
- model: "@sap/cds/srv/mtx"
264
- },
265
- "multitenancy": {
266
- model: "@sap/cds/srv/mtx",
267
- kind: "saas-registry",
268
- },
269
- "extensibility": {
270
- model: "@sap/cds/srv/mtx",
271
- },
272
- }
273
242
 
274
243
  exports.kinds = {
275
244
  ..._authentication_strategies,
276
245
  ..._databases,
277
246
  ..._services,
278
247
  ..._messaging,
279
- ..._mtxs,
280
248
  ..._platform_services,
281
249
  }
282
250
 
@@ -285,3 +253,5 @@ function _compat_to_use(o) { return Object.defineProperties (o,{
285
253
  // NOTE: Property .use is for compatibility only -> use .dialect instead!
286
254
  use: { get(){ return this.dialect || this.kind }, configurable:true, enumerable:true },
287
255
  })}
256
+
257
+ Object.defineProperty(exports,'_resolved',{value:exports._resolved,enumerable:false}) // hide it in outputs
package/lib/env/compat.js CHANGED
@@ -89,4 +89,16 @@ module.exports = function (conf) {
89
89
  }
90
90
  }
91
91
  })
92
+
93
+ Object.defineProperties (conf.features, {
94
+ fiori_preview: {
95
+ get: ()=> _.fiori.preview,
96
+ set: (v) => { _.fiori.preview = v },
97
+ },
98
+ fiori_routes: {
99
+ get: ()=> _.fiori.routes,
100
+ set: (v) => { _.fiori.routes = v },
101
+ },
102
+ })
103
+
92
104
  }
@@ -1,8 +1,7 @@
1
- const path = require('path')
2
- const [,major,minor] = /v(\d+)\.(\d+)/.exec(process.version)
3
1
  const production = process.env.NODE_ENV === 'production'
2
+ const path = require('path')
4
3
 
5
- module.exports = {
4
+ const defaults = module.exports = {
6
5
 
7
6
  requires: require('./cds-requires'),
8
7
 
@@ -13,10 +12,7 @@ module.exports = {
13
12
 
14
13
  features: {
15
14
  folders: 'fts/*', // where to find feature toggles -> switch on by default when released
16
- cls: major > 12 || major == 12 && minor >= 18,
17
15
  live_reload: !production,
18
- fiori_preview: !production,
19
- fiori_routes: !production,
20
16
  in_memory_db: !production,
21
17
  test_data: !production,
22
18
  test_mocks: !production,
@@ -30,10 +26,20 @@ module.exports = {
30
26
  assert_integrity: false,
31
27
  cds_tx_protection: true,
32
28
  cds_tx_inheritance: true,
29
+ },
30
+
31
+ fiori: {
32
+ preview: !production,
33
+ routes: !production,
33
34
  lean_draft: false,
34
- '[lean-draft]': {
35
- lean_draft: true,
36
- }
35
+ draft_compat: false,
36
+ '[better-sqlite]': { lean_draft: true },
37
+ '[lean-draft]': { lean_draft: true },
38
+ '[draft-compat]': { draft_compat: true },
39
+ },
40
+
41
+ ql: {
42
+ quirks_mode: true, // IMPORTANT: Remove that for cds7 !!
37
43
  },
38
44
 
39
45
  log: {
@@ -162,3 +168,5 @@ module.exports = {
162
168
  },
163
169
 
164
170
  }
171
+
172
+ require('./plugins')(defaults)
@@ -0,0 +1,29 @@
1
+ // REVISIT: we should have a real modular plugin technique for cds.env
2
+ module.exports = function add_mtx_env (env) {
3
+
4
+ const mtx_env = old_mtx_env() || require('@sap/cds-mtxs/env') // eslint-disable-line cds/no-missing-dependencies
5
+ if (mtx_env) {
6
+ const {requires} = env, {kinds} = requires
7
+ Object.assign (env, mtx_env, {requires})
8
+ Object.assign (requires, mtx_env.requires, {kinds})
9
+ Object.assign (kinds, mtx_env.requires?.kinds)
10
+ }
11
+
12
+ function require (id) {
13
+ try { return module.require(id) }
14
+ catch(e) { if (e.code !== 'MODULE_NOT_FOUND') throw e }
15
+ }
16
+
17
+ function old_mtx_env() {
18
+ const {_oldMtx} = require('../utils/cds-utils')
19
+ if (_oldMtx()) return {
20
+ requires: { kinds: {
21
+ "multitenancy": { model: "@sap/cds/srv/mtx", kind: "saas-registry" },
22
+ "extensibility": { model: "@sap/cds/srv/mtx" },
23
+ // "toggles": { model: "@sap/cds/srv/mtx" },
24
+ }
25
+ }
26
+ }}
27
+
28
+ return env
29
+ }
@@ -5,6 +5,20 @@
5
5
  "type": "object",
6
6
  "additionalProperties": true,
7
7
  "properties": {
8
+ "[development]": { "type": "object" },
9
+ "[production]": { "type": "object" },
10
+ "[hybrid]": { "type": "object" },
11
+ "profile": {
12
+ "description": "A single static profile",
13
+ "anyOf": [
14
+ { "enum": [ "mtx-sidecar", "with-mtx-sidecar" ] },
15
+ { "type": "string" }
16
+ ]
17
+ },
18
+ "profiles": {
19
+ "description": "An array of profiles",
20
+ "type": "array", "items": { "$ref": "#/properties/profile" }
21
+ },
8
22
  "folders": {
9
23
  "type": "object",
10
24
  "description": "Only set folders if you don't want to use the defaults 'app/', 'db/', 'srv/'.",
package/lib/index.js CHANGED
@@ -77,6 +77,9 @@ const cds = module.exports = extend (new facade) .with ({
77
77
  User: require ('./req/user'),
78
78
  ql: lazy => require ('./ql/cds-ql'),
79
79
  tx: (..._) => (cds.db || cds.Service.prototype) .tx (..._),
80
+ inferred: lazy => require('@cap-js/sqlite/lib/ql/cds.infer'), // eslint-disable-line cds/no-missing-dependencies
81
+ cqn2sql: lazy => require('@cap-js/sqlite/lib/db/sql/cqn2sql'), // eslint-disable-line cds/no-missing-dependencies
82
+ cqn4sql: lazy => require('@cap-js/sqlite/lib/db/sql/cqn4sql'), // eslint-disable-line cds/no-missing-dependencies
80
83
  /** @type Service */ db: undefined,
81
84
 
82
85
  // Protocols and Periphery
@@ -65,9 +65,12 @@ function cds_log (module, options) { // NOSONAR
65
65
  * Shortcut to `cds.log(...).debug`, returning undefined if `cds.log(...)._debug` is false.
66
66
  * @param {string} [module] the module for which a logger is requested
67
67
  */
68
- exports.debug = function cds_debug (module) {
69
- const L = this.log (module)
70
- if (L._debug) return L.debug
68
+ exports.debug = function cds_debug (id, options) {
69
+ const L = cds_log (id, options)
70
+ if (L._debug) return Object.assign(L.debug, {
71
+ time: label => console.time (`[${id}] - ${label}`),
72
+ timeEnd: label => console.timeEnd (`[${id}] - ${label}`),
73
+ })
71
74
  }
72
75
 
73
76
 
@@ -87,7 +90,7 @@ exports.Logger = (label, level) => {
87
90
  const fmt = (level,args) => logger.format (label,level,...args)
88
91
  const logger = {
89
92
  format: exports.format, // use logger.format as this could be changed dynamically
90
- trace: level < TRACE ? ()=>{} : (...args) => console.trace (...fmt(TRACE,args)),
93
+ trace: level < DEBUG ? ()=>{} : (...args) => console.trace (...fmt(TRACE,args)),
91
94
  debug: level < DEBUG ? ()=>{} : (...args) => console.debug (...fmt(DEBUG,args)),
92
95
  log: level < INFO ? ()=>{} : (...args) => console.log (...fmt(INFO,args)),
93
96
  info: level < INFO ? ()=>{} : (...args) => console.info (...fmt(INFO,args)),