@sap/cds 9.6.4 → 9.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/bin/serve.js +38 -26
  3. package/lib/compile/for/flows.js +100 -20
  4. package/lib/compile/for/lean_drafts.js +0 -47
  5. package/lib/compile/for/nodejs.js +47 -14
  6. package/lib/compile/for/odata.js +20 -0
  7. package/lib/compile/load.js +22 -25
  8. package/lib/compile/minify.js +29 -11
  9. package/lib/compile/parse.js +1 -1
  10. package/lib/compile/resolve.js +133 -76
  11. package/lib/compile/to/csn.js +2 -2
  12. package/lib/dbs/cds-deploy.js +48 -43
  13. package/lib/env/cds-env.js +6 -0
  14. package/lib/env/cds-requires.js +9 -3
  15. package/lib/index.js +3 -1
  16. package/lib/plugins.js +1 -1
  17. package/lib/req/request.js +2 -2
  18. package/lib/srv/bindings.js +10 -5
  19. package/lib/srv/middlewares/auth/index.js +7 -5
  20. package/lib/srv/protocols/hcql.js +8 -3
  21. package/lib/srv/protocols/http.js +1 -1
  22. package/lib/srv/protocols/index.js +1 -0
  23. package/lib/utils/cds-utils.js +28 -1
  24. package/lib/utils/colors.js +1 -1
  25. package/libx/_runtime/common/generic/assert.js +1 -7
  26. package/libx/_runtime/common/generic/flows.js +14 -4
  27. package/libx/_runtime/common/utils/resolveView.js +4 -0
  28. package/libx/_runtime/fiori/lean-draft.js +8 -3
  29. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +4 -0
  30. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +12 -12
  31. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  32. package/libx/_runtime/messaging/http-utils/token.js +18 -3
  33. package/libx/_runtime/messaging/message-queuing.js +7 -7
  34. package/libx/_runtime/remote/Service.js +14 -3
  35. package/libx/_runtime/remote/utils/client.js +1 -0
  36. package/libx/_runtime/remote/utils/query.js +0 -1
  37. package/libx/odata/middleware/batch.js +128 -112
  38. package/libx/odata/middleware/delete.js +2 -1
  39. package/libx/odata/middleware/error.js +7 -3
  40. package/libx/odata/parse/afterburner.js +10 -11
  41. package/libx/odata/parse/grammar.peggy +4 -2
  42. package/libx/odata/parse/parser.js +1 -1
  43. package/libx/odata/utils/odataBind.js +8 -2
  44. package/libx/queue/index.js +1 -0
  45. package/package.json +4 -7
  46. package/srv/outbox.cds +1 -1
  47. package/srv/ucl-service.cds +3 -5
  48. package/bin/colors.js +0 -2
  49. package/libx/_runtime/.eslintrc +0 -14
@@ -26,20 +26,20 @@ class Minifier {
26
26
  // If o.services is specified, only keep matching services and their children
27
27
  const rx = o.services == 'all' || o.services == '/all/i' ? {test:()=>true} : o.services
28
28
  for (let [s,d] of all) if (d.kind === 'service' && rx.test(s)) {
29
- this.keep (s,d); children (s, (c,d) => this.keep (c,d))
29
+ this.keep(s,d); children (s, (c,d) => this.keep(c,d))
30
30
  }
31
31
  } else {
32
32
  // Otherwise first mark all external services and their children as initially skipped
33
33
  for (let [s,d] of all) if (d.kind === 'service' && _skip_service(s,d)) {
34
- skipped[s] = 0; children (s, (c,d) => d.kind in events ? this.keep (c,d) : skipped[c] = s) // used later on in this.keep()
34
+ skipped[s] = 0; children (s, (c,d) => d.kind in events ? this.keep(c,d,s) : skipped[c] = s) // used later on in this.keep()
35
35
  }
36
36
  // Then keep all own services and their children
37
37
  for (let [s,d] of all) if (d.kind === 'service' && !(s in skipped)) {
38
- this.keep (s,d); children (s, (c,d) => d.kind in keep ? this.keep (c,d) : skipped[c] = 0)
38
+ this.keep(s,d); children (s, (c,d) => d.kind in keep ? this.keep(c,d) : skipped[c] = 0)
39
39
  }
40
40
  // Also keep remaining non-service entities
41
41
  for (let [e,d] of all) if (d.kind === 'entity') {
42
- e in kept || e in skipped || _skip_entity(e,d) || this.keep (e,d)
42
+ e in kept || e in skipped || _skip_entity(e,d) || this.keep(e,d)
43
43
  }
44
44
  }
45
45
 
@@ -58,8 +58,8 @@ class Minifier {
58
58
  if (d.target) this.keep (d.target) // has to go first w/o return for redirected targets
59
59
  if (d.type in this.defs) return this.keep (d.type) // return to avoid endless recursion
60
60
  if (d.type?.ref) return this.keep (d.type.ref[0]) // return to avoid endless recursion
61
- if (d.projection) this.view (d.projection)
62
- if (d.query) this.view (d.query)
61
+ if (d.projection) this.view (d.projection,d)
62
+ if (d.query) this.view (d.query,d)
63
63
  if (d.items) this.walk (d.items)
64
64
  if (d.returns) this.walk (d.returns)
65
65
  for (let e in d.elements) this.walk (d.elements[e])
@@ -69,20 +69,38 @@ class Minifier {
69
69
  // Note: this ^^^^^^^^^^^^ is required for cdsc.recompile; with delete d.includes, redirects in AFC broke
70
70
  }
71
71
 
72
- view (q) {
72
+ view (q,v) {
73
73
  if (q.SELECT) q = q.SELECT // i.e. entity as select from ...
74
74
  if (q.mixin) for (let e in q.mixin) this.walk (q.mixin[e])
75
- if (q.from?.ref) return this.keep (_source(q.from.ref[0])) // keep sources of views
75
+ if (q.from?.ref) {
76
+ let r = q.from.ref[0]; if (r.id) r = r.id; if (r in this.kept) return
77
+ if (v?.['@cds.minify'] === 'unused-elements') return this.keep_columns (q)
78
+ else return this.keep (r) // keep sources of views
79
+ }
76
80
  if (q.from?.join) return q.from.args.forEach (from => this.view ({from}))
77
81
  if (q.SET) return q.SET.args.forEach (q => this.view (q.SELECT||q))
78
- function _source (r) { return r.id || r }
79
82
  }
80
83
 
81
- keep (n,d) {
84
+ keep_columns (q) {
85
+ let r = q.from.ref[0]; if (r.id) r = r.id; if (r in this.kept) return
86
+ let e = this.defs[r]; if (!e) return
87
+ let all = e.elements, kept = {}
88
+ if (q.columns) _keep (q.columns)
89
+ if (q.where) _keep (q.where)
90
+ e.elements = kept
91
+ return this.keep (r,e)
92
+ function _keep (xx) { for (let x of xx) {
93
+ if (x.func) { _keep (x.args); continue }
94
+ if (x.xpr) { _keep (x.xpr); continue }
95
+ if (x.ref) { let [r] = x.ref; if (r in all) kept[r] = all[r] }
96
+ }}
97
+ }
98
+
99
+ keep (n,d,parent) {
82
100
  if (n in this.kept) return; else d ??= this.defs[n]
83
101
  if (d) this.walk (this.kept[n] = d, n); else return
84
102
  let texts = this.defs[n+'.texts']; if (texts) this.keep (n+'.texts', texts)
85
- let parent = this.skipped[n]; if (parent) this.keep(parent) // keep initially skipped services
103
+ let p = parent ?? this.skipped[n]; if (p) this.keep(p) // keep initially skipped services
86
104
  }
87
105
 
88
106
  }
@@ -128,7 +128,7 @@ const native = {
128
128
  SESSION_USER : { func: 'session_user' },
129
129
  SYSUUID : { func: 'sysuuid' },
130
130
  }
131
- const is_cqn = x => typeof x === 'object' && (
131
+ const is_cqn = x => x !== null && typeof x === 'object' && (
132
132
  'ref' in x ||
133
133
  'val' in x ||
134
134
  'xpr' in x ||
@@ -1,109 +1,166 @@
1
- const { resolve, join, sep } = require('path')
2
- const { readdirSync } = require('fs')
3
- const suffixes = [ '.csn', '.cds', sep+'index.csn', sep+'index.cds', sep+'csn.json' ]
4
-
1
+ const resolve = module.exports = exports = cds_resolve
2
+ const cds = require('..'), {fs,path} = cds.utils
5
3
 
6
4
  /**
7
- * Resolves given model references to an array of absolute filenames.
8
- * For the model references, all these are accepted:
9
- * - with suffix or without → will append `.csn|cds`, `/index.csn|cds`
10
- * - absolute refs like `@sap/cds/common`
11
- * - local refs with leading `.` or without, e.g. `srv/cat-service`
12
- * - directory names → will fetch all contained `.csn` and `.cds` files
13
- * - arrays of any of the above
14
- * @returns and array of absolute filenames
15
- */
16
- module.exports = exports = function cds_resolve (model, o={}) { // NOSONAR
5
+ * Resolves given model references to an array of absolute filenames.
6
+ * For the model references, all these are accepted:
7
+ * - with suffix or without → will append `.csn|cds`, `/index.csn|cds`
8
+ * - absolute refs like `@sap/cds/common`
9
+ * - local refs with leading `.` or without, e.g. `srv/cat-service`
10
+ * - directory names → will fetch all contained `.csn` and `.cds` files
11
+ * - arrays of any of the above
12
+ * @returns and array of absolute filenames
13
+ */
14
+ function cds_resolve (refs,o) {
15
+ if (refs?._resolved) return refs //> already resolved
16
+ let o2 = resolve.options(o)
17
+ if (Array.isArray(refs)) return resolve.many (refs.flat(),o2)
18
+ else return resolve.single (refs,o2)
19
+ }
20
+
21
+ resolve.locations = (model='*', o) => resolve(model, { dry:true, ...o })
22
+
23
+ resolve.options = function (options) {
24
+
25
+ const o = options === false ? { dry:true } : {...options}
26
+ const cwd = o.root ??= cds.root
27
+
28
+ // return cached prior result, if any
29
+ const caches = o.cache || exports.cache
30
+ if (cwd in caches) return { ...caches[cwd], ...o }
31
+ else caches[cwd] = { paths: [ cwd ], cached:{} }
32
+
33
+ // prepare module lookup paths
34
+ const paths = [ cwd ]
35
+ const node_modules = (o.env||cds.env).cdsc.moduleLookupDirectories
36
+ const a = cwd.split(path.sep), n = a.length
37
+ for (let each of node_modules) paths.push (
38
+ ...a.map ((_,i,a)=> a.slice(0,n-i).join(path.sep) + path.sep + each)
39
+ )
40
+
41
+ // cache and return
42
+ const cache = caches[cwd] = { paths, cached:{} }
43
+ return { ...o, ...cache }
44
+ }
45
+
46
+
47
+ resolve.all = function (o) {
48
+
49
+ const _required = env => Object.values(env.requires) .map (r => r.model) .filter(x=>x) .flat()
50
+ const env = o.env || cds.env
51
+
52
+ // resolve(...,false) => return cds.env.roots + all required models
53
+ if (o.dry) return [ ...env.roots, ...new Set(_required(env)) ]
54
+
55
+ // return cached prior result, if any
56
+ const {cached} = o; if ('*' in cached) return cached['*']
57
+ else cached['*'] = [] // important to avoid endless recursion on '*'
58
+
59
+ // resolve all roots, plus all cds.required.models (unless csn.json already there)
60
+ const files = resolve.many (env.roots,o) || []
61
+ const is_csn_json = files.length === 1 && files[0].endsWith('csn.json')
62
+ if (!is_csn_json) files.push (...resolve.many (_required(env),o)||[])
63
+
64
+ // cache and return resolved files
65
+ return cached['*'] = _resolved (files)
66
+ }
67
+
68
+
69
+ resolve.many = function (models, o) {
70
+ const resolved = _distinct(models) .reduce ((p,n) => p.concat (resolve.single(n,o)||[]), [])
71
+ return o.dry ? _distinct(resolved.flat()) : _resolved (resolved)
72
+ }
73
+
74
+
75
+ resolve.single = function (model,o) {
76
+
17
77
  if (!model || model === '--') return
18
- if (model._resolved) return model
19
- if (model === '*') return _resolve_all(o,this)
20
- if (Array.isArray(model)) {
21
- const resolved = [... new Set(model)] .reduce ((prev,next) => prev.concat (this.resolve(next,o)||[]), [])
22
- return o.dry || o === false ? [...new Set(resolved.flat())] : _resolved (resolved)
23
- }
24
- if (model.endsWith('/*')) return _resolve_subdirs_in(model,o,this)
78
+ if (model == '*') return resolve.all(o)
79
+
80
+ // handle subdirectory patterns like 'app/*', 'srv/*', 'fts/*'
81
+ if (model.endsWith('/*')) return resolve.nested (model,o)
25
82
 
26
- const cwd = o.root || this.root, local = resolve (cwd,model)
27
- const context = _paths(cwd,o,this), {cached} = context
83
+ // check cache, and return if already resolved
84
+ const {cached} = o, local = path.resolve (o.root, model)
28
85
  let id = model.startsWith('.') ? local : model
29
- if (id in cached) return cached[id]
86
+ if (id in cached) return cached[id]
30
87
 
31
88
  // expand @sap/cds by cds.home
32
- if (id.startsWith('@sap/cds/')) id = this.home + id.slice(8)
89
+ if (id.startsWith('@sap/cds/')) id = cds.home + id.slice(8)
33
90
 
34
91
  // fetch file with .cds/.csn suffix as is
35
92
  if (/\.(csn|cds)$/.test(id)) try {
36
- return cached[id] = _resolved ([ _resolve (id,context) ])
93
+ return cached[id] = _resolved ([ _resolve (id,o) ])
37
94
  } catch {/* ignored */}
38
95
 
39
96
  // try to resolve file with one of the suffixes
40
- for (let tail of o.suffixes || suffixes) try {
41
- return cached[id] = _resolved ([ _resolve (id+tail,context) ])
97
+ for (let tail of o.suffixes || exports.suffixes) try {
98
+ return cached[id] = _resolved ([ _resolve (id+tail,o) ])
42
99
  } catch {/* ignored */}
43
100
 
44
- // fetch all in a directory
101
+ // fetch all in a folder
45
102
  if (o.all !== false) try {
46
- const files = readdirSync(local), all=[], unique={}
47
- for (let f of files) if (f.endsWith('.csn')) {
48
- all.push (unique[f.slice(0,-4)] = join(local,f))
49
- }
50
- for (let f of files) if (f.endsWith('.cds')) {
51
- unique[f.slice(0,-4)] || all.push (join(local,f))
52
- }
53
- return cached[id] = _resolved (all)
103
+ return cached[id] = _resolved (resolve.folder (local,o))
54
104
  } catch {/* ignored */}
55
105
 
56
106
  // fetch file without suffix
57
107
  if (o.any !== false && !id.endsWith('/')) try { // NOTE: this also finds .js files!
58
- return cached[id] = _resolved ([ _resolve (id,context) ])
108
+ return cached[id] = _resolved ([ _resolve (id,o) ])
59
109
  } catch {/* ignored */}
60
110
 
111
+ // not found
112
+ return cached[id] = null
61
113
  }
62
114
 
63
115
 
64
- exports.cache = {}
116
+ resolve.folder = function (folder) {
117
+ const files = fs.readdirSync(folder), all=[], unique={}
118
+ for (let f of files) if (f.endsWith('.csn')) {
119
+ all.push (unique[f.slice(0,-4)] = path.join(folder,f))
120
+ }
121
+ for (let f of files) if (f.endsWith('.cds')) {
122
+ unique[f.slice(0,-4)] || all.push (path.join(folder,f))
123
+ }
124
+ return all
125
+ }
65
126
 
66
127
 
67
- const _required = (cds,env=cds.env) => Object.values(env.requires) .map (r => r.model) .filter(x=>x)
68
- const _resolve = require('module')._resolveFilename
128
+ resolve.nested = function (pattern,o) {
69
129
 
70
- function _resolve_all (o,cds) {
71
- const {roots} = o.env || cds.env; if (o.dry || o === false) return [ ...roots, ...new Set(_required(cds).flat()) ]
72
- const cache = o.cache || exports.cache
73
- const cached = cache['*']; if (cached) return cached
74
- cache['*'] = [] // important to avoid endless recursion on '*'
75
- const sources = cds.resolve (roots,o) || []
76
- if (!(sources.length === 1 && sources[0].endsWith('csn.json'))) // REVISIT: why is that? -> pre-compiled gen/csn.json?
77
- sources.push (...cds.resolve (_required(cds,o.env),o)||[])
78
- return cache['*'] = _resolved (sources)
79
- }
130
+ // return cached prior result, if any
131
+ const {cached} = o; if (!o.dry && pattern in cached) return cached[pattern]
80
132
 
81
- function _resolve_subdirs_in (pattern='fts/*',o,cds) {
82
- const cache = o.cache || exports.cache
83
- const cached = cache[pattern]; if (cached && !o.dry && o !== false) return cached
84
- const folder = pattern.slice(0,-2), dir = resolve (o.root || cds.root, folder)
85
- try {
86
- const dirs = readdirSync(dir) .filter (e => cds.utils.isdir(dir+sep+e)) .map (e => folder+sep+e+sep)
87
- if (o.dry || o === false) return dirs
88
- return cache[pattern] = cds.resolve (dirs,o) || undefined
89
- } catch(e) {
90
- if (e.code === 'ENOENT')
91
- return cache[pattern] = undefined
92
- }
93
- }
133
+ // fetch all subdirectories matching the pattern
134
+ const folder = pattern.slice(0,-2), dir = path.resolve (o.root, folder)
135
+ try { var nested = fs.readdirSync(dir) .filter (d => is_dir(path.join(dir,d))) .map (d => path.join(folder,d,path.sep)) }
136
+ catch(e) { if (e.code === 'ENOENT') return cached[pattern] = null }
94
137
 
95
- function _paths (dir,o,cds) {
96
- const cache = o.cache || exports.cache
97
- const cached = cache[dir]; if (cached) return cached
98
- const a = dir.split(sep), n = a.length, paths = [ dir ]
99
- const { cdsc: { moduleLookupDirectories }} = o.env ?? cds.env
100
- for (const mld of moduleLookupDirectories) { // node_modules/ usually, more for Java
101
- paths.push(...a.map ((_,i,a)=> a.slice(0,n-i).join(sep)+sep+mld))
102
- }
103
- return cache[dir] = { paths, cached:{} }
138
+ // don't resolve, if resolve(...,false)
139
+ if (o.dry) return nested
140
+
141
+ // resolve files for those dirs
142
+ const files = nested?.map (d => resolve.single (d,o)).flat()
143
+ return cached[pattern] = _resolved (files.filter(x=>x))
104
144
  }
105
145
 
106
- function _resolved (array) {
107
- if (!array || !array.length) return
108
- return Object.defineProperty ([...new Set (array)], '_resolved', {value:true})
146
+
147
+ resolve.module = function (module_name, o = resolve.options()) {
148
+ try { return _resolve (module_name,o) }
149
+ catch { return null }
109
150
  }
151
+
152
+
153
+ exports.suffixes = [ // always .csn before .cds to prefer compiled files
154
+ '.csn',
155
+ '.cds',
156
+ path.sep+'index.csn',
157
+ path.sep+'index.cds',
158
+ path.sep+'csn.json'
159
+ ]
160
+ exports.cache = {}
161
+
162
+
163
+ const _resolve = require('module')._resolveFilename
164
+ const _resolved = files => !files?.length ? null : Object.defineProperty (_distinct(files), '_resolved', {value:true})
165
+ const _distinct = files => [...new Set (files)]
166
+ const is_dir = file => fs.statSync(file).isDirectory()
@@ -36,8 +36,8 @@ function cds_compile_to_csn (model, options, _flavor) {
36
36
  // REVISIT: experimental implementation to detect external APIs
37
37
  for (let each in csn.definitions) {
38
38
  const d = csn.definitions[each]
39
- if (d.kind === 'service' && cds.requires[each]?.external && (!o.mocked || cds.requires[each].credentials)) {
40
- Object.defineProperty (d,'@cds.external', { value: cds.requires[each].kind || true })
39
+ if (d.kind === 'service' && !d['@cds.external'] && cds.requires[each]?.external && (!o.mocked || cds.requires[each].credentials)) {
40
+ Object.defineProperty (d,'@cds.external', { value: !!cds.requires[each].kind || true })
41
41
  }
42
42
  }
43
43
  if (!csn.meta) csn.meta = {}
@@ -275,53 +275,55 @@ deploy.prepare = async function (csn, srces) {
275
275
  }
276
276
 
277
277
 
278
- /** Resolve initial data resources for given model */
279
- deploy.resources = async function (csn, opts) {
280
- if (!csn || !csn.definitions) csn = await cds.load (csn||'*') .then (cds.minify)
281
- const { fs, isdir, isfile } = cds.utils
282
- const folders = await deploy.folders(csn, opts)
283
- const found={}, ts = process.env.CDS_TYPESCRIPT
284
- for (let folder of folders) {
285
- // fetching .csv and .json files
286
- for (let each of ['data','csv']) {
287
- const subdir = isdir(folder,each); if (!subdir) continue
288
- const files = await fs.promises.readdir (subdir)
289
- for (let fx of files) {
290
- if (fx[0] === '-') continue
291
- const ext = path.extname(fx); if (ext in {'.csv':1,'.json':2}) {
292
- const f = fx.slice(0,-ext.length)
293
- if (/[._]texts$/.test(f) && files.some(g => g.startsWith(f+'_'))) {
294
- // ignores 'Books_texts.csv/json' if there is any 'Books_texts_LANG.csv/json'
295
- DEBUG?.(`ignoring '${fx}' in favor of translated ones`)
296
- continue
297
- }
298
- const e = _entity4(f,csn); if (!e || e['@cds.persistence.skip'] === true) continue
299
- if (cds.env.features.deploy_data_onconflict === 'replace' && !/[._]texts_/.test(f)) {
300
- const seenBefore = Object.entries(found).find(([,entity]) => entity === e.name )
301
- if (seenBefore) {
302
- DEBUG?.(`Conflict for '${e.name}': replacing '${local(seenBefore[0])}' with '${local(path.join(subdir,fx))}'`)
303
- continue
304
- }
305
- }
306
- found[path.join(subdir,fx)] = e.name
307
- }
308
- }
309
- }
310
- // fetching init.js files -> Note: after .csv files to have that on top, when processing in .reverse order
278
+ /**
279
+ * Resolve initial data resources for given model.
280
+ * Found resources are executed in reverse order of discovery!
281
+ */
282
+ deploy.resources = async function (csn,o) {
283
+
284
+ const { fs, isdir, isfile } = cds.utils, ts = process.env.CDS_TYPESCRIPT
285
+ const subfolders = ['data','csv']
286
+ const filetypes = ['.csv','.json']
287
+ const found={}, neighborhood = new Set
288
+
289
+ // fetching csvs in models' neighborhood ...
290
+ if (!csn?.definitions) csn = await cds.load (csn||'*') .then (cds.minify)
291
+ for (let each of csn.$sources) {
292
+ let d = path.dirname (each)
293
+ if (d === cds.home || neighborhood.has(d)) continue; else neighborhood.add (d)
294
+ for (let data of subfolders) await fetch_csvs_in (path.join (d,data))
295
+ }
296
+
297
+ // fetching csvs in db/data and test/data
298
+ let d = cds.env.requires.db?.data || (
299
+ // REVISIT: fallback for some very special cds build and mtxs tests with mocked cds.env?
300
+ o?.testdata ? ['db/data','test/data'] : ['db/data']
301
+ )
302
+ if (d) for (let each of d) await fetch_csvs_in (each)
303
+
304
+ // fetching init.js files in models' neighborhood ...
305
+ for (let folder of neighborhood) {
311
306
  const init_js = ts && isfile(folder,'init.ts') || isfile(folder,'init.js')
312
307
  if (init_js) found[init_js] = '*'
313
308
  }
314
309
  return found
315
- }
316
-
317
-
318
- /** Resolve folders to fetch for initial data resources for given model */
319
- deploy.folders = async function (csn, o={}) {
320
- if (!csn || !csn.definitions) csn = await cds.load (csn||'*') .then (cds.minify)
321
- const folders = new Set (csn.$sources.map (path.dirname) .filter (f => f !== cds.home))
322
- if (cds.env.folders.db) folders.add (path.resolve(cds.root, cds.env.folders.db))
323
- if (o.testdata) folders.add (path.resolve(cds.root,'test/'))
324
- return folders
310
+
311
+ async function fetch_csvs_in (dir) {
312
+ const subdir = isdir(dir); if (!subdir) return
313
+ const files = await fs.promises.readdir (subdir)
314
+ files.forEach (fx => { if (fx[0] !== '-') {
315
+ const ext = path.extname(fx); if (!filetypes.includes(ext)) return
316
+ const f = fx.slice(0,-ext.length)
317
+ // ignore '_texts.csv' if there is any '_texts_<lang>.csv'
318
+ if (/[._]texts$/.test(f) && files.some(g => g.startsWith(f+'_')))
319
+ return DEBUG?.(`ignoring '${fx}' in favor of translated ones`)
320
+ const e = _entity4(f,csn); if (!e || e['@cds.persistence.skip'] === true) return
321
+ if (cds.env.features.deploy_data_onconflict === 'replace' && !/[._]texts_/.test(f))
322
+ for (let any in found) if (found[any] === e.name)
323
+ return DEBUG?.(`Conflict for '${e.name}': replacing '${local(any)}' with '${local(path.join(subdir,fx))}'`)
324
+ found[path.join(subdir,fx)] = e.name
325
+ }})
326
+ }
325
327
  }
326
328
 
327
329
 
@@ -335,6 +337,9 @@ deploy.include_external_entities_in = function (csn) {
335
337
  DEBUG?.('including mocked', each)
336
338
  delete def['@cds.persistence.skip']
337
339
  }
340
+ // DON'T CLEANUP: Please keep these comments in for future reference!
341
+ // REVISIT: if (def['@cds.external'] === 2) delete def['@cds.external']
342
+ // REVISIT: if (def['@cds.external'] === 2) def['@cds.external'] = 1
338
343
  }
339
344
  deploy.exclude_external_entities_in (csn)
340
345
  return csn
@@ -395,6 +395,12 @@ class Config {
395
395
  _fetch ({ type: conf.dialect || conf.kind })
396
396
 
397
397
  function _fetch (predicate) {
398
+ if (Array.isArray(predicate)) {
399
+ for (const p of predicate) {
400
+ const found = _fetch(p)
401
+ if (found) return found
402
+ }
403
+ }
398
404
  const filters = []
399
405
  for (let k in predicate) {
400
406
  const v = predicate[k]; if (!v) continue
@@ -119,21 +119,27 @@ const _services = {
119
119
 
120
120
  }
121
121
 
122
+ const _db_data = {
123
+ '[development]': { data: [ 'db/data', 'db/csv', 'test/data' ] },
124
+ '[production]': { data: [ 'db/data', 'db/csv' ] },
125
+ }
122
126
 
123
127
  const _databases = {
124
128
 
125
- "db-defaults": { kind: 'sql' },
129
+ "db-defaults": { kind: 'sql', ..._db_data }, //> applied only for cds.requires.db: true
130
+
126
131
  "sql": {
127
132
  '[development]': { kind: 'sqlite', credentials: { url: ':memory:' } },
128
133
  '[production]': { kind: 'hana' },
129
134
  },
130
135
 
131
136
  "sqlite": {
132
- impl: '@cap-js/sqlite',
133
- credentials: { url: 'db.sqlite' },
137
+ impl: '@cap-js/sqlite', credentials: { url: 'db.sqlite' },
138
+ ..._db_data,
134
139
  },
135
140
  "hana": {
136
141
  impl: '@cap-js/hana',
142
+ ..._db_data,
137
143
  },
138
144
  "hana-cloud": {
139
145
  kind: 'hana', "deploy-format": "hdbtable",
package/lib/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  if (process.env.CDS_STRICT_NODE_VERSION !== 'false') require('./utils/version').check()
2
- ! (global.__cds_loaded_from ??= new Set).add(__filename.toLowerCase()) // track from where we loaded cds, lowercase to avoid path duplicates on Windows
3
2
 
4
3
  const { AsyncLocalStorage } = require ('async_hooks')
5
4
  const context = new AsyncLocalStorage
@@ -152,3 +151,6 @@ function G (p,v) { Object.defineProperty (global, p, { value:v, enumerable:1,con
152
151
 
153
152
  // Allow for `import cds from '@sap/cds'` without `esModuleInterop` in tsconfig.json
154
153
  Object.defineProperties (exports, { default: {value:cds}, __esModule: {value:true} })
154
+
155
+ // track from where we loaded cds, lowercase to avoid path duplicates on Windows
156
+ !(global.__cds_loaded_from ??= new Set).add(cds.home.toLowerCase())
package/lib/plugins.js CHANGED
@@ -1,4 +1,4 @@
1
- const DEBUG = /plugins/.test(process.env.DEBUG) ? console : undefined // eslint-disable-line no-console
1
+ const DEBUG = /\b(y|all|plugins)\b/.test(process.env.DEBUG) ? console : undefined // eslint-disable-line no-console
2
2
  const cds = require('.')
3
3
  const prio_plugins = {
4
4
  '@sap/cds-mtxs': true, // plugins may register handlers for mtxs services
@@ -1,5 +1,5 @@
1
1
  const cds = require('../index')
2
- const { Responses, Errors, prepareError } = require('./response')
2
+ const { Responses, Errors } = require('./response')
3
3
 
4
4
  /**
5
5
  * Class Request represents requests received via synchronous protocols.
@@ -110,7 +110,7 @@ class Request extends require('./event') {
110
110
  delete err.stack
111
111
  throw err
112
112
  }
113
- let e = prepareError(4, ...args)
113
+ let e = this._errors.add (4, ...args)
114
114
  if (!('stack' in e)) Error.captureStackTrace (e = Object.assign(new Error,e), this.reject)
115
115
  if (!('message' in e)) e.message = String (e.code || e.status)
116
116
  throw e
@@ -11,17 +11,18 @@ class Bindings {
11
11
  #bound = {}
12
12
 
13
13
  then (r,e) {
14
+ const info = ()=> LOG.info ('using bindings from:', { registry })
15
+ if (cds.watched) cds.prependOnceListener ('connect',info); else info()
14
16
  delete Bindings.prototype.then // only once per process
15
- cds.prependOnceListener ('connect', ()=> LOG.info ('connect using bindings from:', { registry }))
16
17
  cds.once('listening', server => this.export (cds.service.providers, server.url))
17
18
  return this.import() .then (r,e)
18
19
  }
19
20
 
20
21
  bind (service) {
21
22
  let required = cds.requires [service]
22
- let binding = this.provides [required?.service || service]
23
+ let binding = this.provides [service]
23
24
 
24
- if (binding?.endpoints && !required.credentials) {
25
+ if (binding?.endpoints && !required?.credentials) {
25
26
  // > Re-route requests to the mock service running locally
26
27
 
27
28
  const server = this.servers [binding.server]
@@ -45,12 +46,16 @@ class Bindings {
45
46
  }
46
47
 
47
48
  required.kind = kind
49
+
48
50
  // REVISIT: temporary fix to inherit kind as well for mocked odata services
49
51
  // otherwise mocking with two services does not work for kind:odata-v2
50
52
  if (kind === 'odata-v2' || kind === 'odata-v4') required.kind = 'odata'
53
+
54
+ // Finally, ensure the changes are mirrored in cds.requires overlays as well
55
+ if (required.service) cds.requires[required.service] = required
51
56
  }
52
57
 
53
- return required
58
+ return this.#bound [service] = required
54
59
  }
55
60
 
56
61
  // used by cds.connect
@@ -81,7 +86,7 @@ class Bindings {
81
86
 
82
87
  async import() {
83
88
  await this.load()
84
- for (let each in cds.requires) this.bind (each)
89
+ for (let each in cds.env.requires) this.bind (each)
85
90
  return this
86
91
  }
87
92
 
@@ -29,14 +29,16 @@ module.exports = function auth_factory (o) {
29
29
  // from cds.requires.auth in the log or error output below.
30
30
 
31
31
  // try resolving the impl, throw if not found
32
- const config = { kind, impl }
33
- // use cds.resolve() to allow './srv/auth.js' and 'srv/auth.js' -> REVISIT: cds.resolve() is not needed here, and not meant for that !
34
- try { impl = require.resolve (cds.resolve (impl)?.[0], {paths:[cds.root]}) } catch {
35
- throw cds.error `Didn't find auth implementation for ${config}`
32
+ const config = { kind }
33
+ try { impl = require.resolve (impl, {paths:[cds.root]}) } catch {
34
+ try { impl = require.resolve ('./'+impl, {paths:[cds.root]}) } catch {
35
+ throw cds.error `Didn't find auth implementation for ${config}`
36
+ }
36
37
  }
37
38
 
38
39
  // load the auth middleware from the resolved path
39
- config.impl = cds.utils.local(impl)
40
+ const builtin = cds.utils.path.join (cds.home,'lib')
41
+ if (!impl.match(builtin)) config.impl = cds.utils.local(impl)
40
42
  cds.log().info ('using auth strategy', config)
41
43
  let auth = require (impl)
42
44
 
@@ -43,6 +43,13 @@ class HCQLAdapter extends require('./http') {
43
43
 
44
44
  // The ultimate handler for CRUD requests
45
45
  router.use (this.crud.bind(this))
46
+
47
+ // Error formatting
48
+ router.use ((err, req, res, next) => {
49
+ err.$response = e => ({ errors: [ { ...e, message: e.message } ] })
50
+ next(err)
51
+ })
52
+
46
53
  return router
47
54
  }
48
55
  log (req) { } // eslint-disable-line no-unused-vars
@@ -111,9 +118,7 @@ class HCQLAdapter extends require('./http') {
111
118
  if (q.INSERT) res.statusCode = 201
112
119
  if (results == null) return res.sendStatus(204)
113
120
  if (results.$count) res.set ('X-Total-Count', results.$count)
114
- if (typeof results === 'object') return res.json (results)
115
- if (typeof results === 'number') results = String (results)
116
- res.set('Content-Type','application/json').send (results)
121
+ return res.json ({ data: results })
117
122
  }
118
123
 
119
124
  /**