@sap/cds 6.7.1 → 6.8.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 (95) hide show
  1. package/CHANGELOG.md +50 -1
  2. package/_i18n/i18n.properties +9 -6
  3. package/_i18n/i18n_ar.properties +6 -6
  4. package/_i18n/i18n_cs.properties +6 -6
  5. package/_i18n/i18n_da.properties +6 -6
  6. package/_i18n/i18n_de.properties +6 -6
  7. package/_i18n/i18n_en.properties +6 -6
  8. package/_i18n/i18n_es.properties +6 -6
  9. package/_i18n/i18n_fi.properties +6 -6
  10. package/_i18n/i18n_fr.properties +6 -6
  11. package/_i18n/i18n_hu.properties +6 -6
  12. package/_i18n/i18n_it.properties +6 -6
  13. package/_i18n/i18n_ja.properties +6 -6
  14. package/_i18n/i18n_ko.properties +6 -6
  15. package/_i18n/i18n_ms.properties +6 -6
  16. package/_i18n/i18n_nl.properties +6 -6
  17. package/_i18n/i18n_no.properties +6 -6
  18. package/_i18n/i18n_pl.properties +6 -6
  19. package/_i18n/i18n_pt.properties +6 -6
  20. package/_i18n/i18n_ro.properties +6 -6
  21. package/_i18n/i18n_ru.properties +6 -6
  22. package/_i18n/i18n_sv.properties +6 -6
  23. package/_i18n/i18n_th.properties +6 -6
  24. package/_i18n/i18n_tr.properties +8 -8
  25. package/_i18n/i18n_zh_CN.properties +3 -3
  26. package/_i18n/i18n_zh_TW.properties +6 -6
  27. package/apis/core.d.ts +30 -31
  28. package/apis/csn.d.ts +1 -1
  29. package/apis/ql.d.ts +69 -39
  30. package/apis/serve.d.ts +4 -3
  31. package/apis/services.d.ts +20 -7
  32. package/bin/build/buildTaskEngine.js +1 -1
  33. package/bin/build/index.js +1 -1
  34. package/bin/build/provider/buildTaskProviderInternal.js +9 -6
  35. package/bin/build/provider/hana/index.js +11 -4
  36. package/bin/build/provider/mtx-sidecar/index.js +3 -3
  37. package/bin/build/provider/nodejs/index.js +23 -0
  38. package/bin/version.js +3 -2
  39. package/common.cds +3 -2
  40. package/lib/auth/index.js +3 -0
  41. package/lib/auth/mocked-users.js +13 -0
  42. package/lib/compile/etc/_localized.js +3 -0
  43. package/lib/core/entities.js +7 -3
  44. package/lib/dbs/cds-deploy.js +36 -12
  45. package/lib/env/cds-env.js +47 -14
  46. package/lib/env/cds-requires.js +16 -7
  47. package/lib/env/defaults.js +2 -2
  48. package/lib/env/schemas/cds-rc.json +1 -8
  49. package/lib/index.js +1 -1
  50. package/lib/ql/STREAM.js +89 -0
  51. package/lib/ql/cds-ql.js +2 -1
  52. package/lib/req/request.js +5 -2
  53. package/lib/req/user.js +1 -1
  54. package/lib/srv/middlewares/index.js +9 -7
  55. package/lib/srv/middlewares/trace.js +6 -5
  56. package/lib/srv/srv-api.js +1 -0
  57. package/lib/utils/cds-utils.js +1 -1
  58. package/lib/utils/tar.js +30 -31
  59. package/libx/_runtime/audit/Service.js +96 -37
  60. package/libx/_runtime/audit/generic/personal/utils.js +26 -13
  61. package/libx/_runtime/audit/utils/v2.js +21 -22
  62. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +1 -1
  63. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  64. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +7 -6
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +2 -0
  68. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -1
  69. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +10 -3
  70. package/libx/_runtime/cds-services/services/Service.js +2 -7
  71. package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
  72. package/libx/_runtime/common/aspects/any.js +1 -1
  73. package/libx/_runtime/common/generic/auth/utils.js +30 -41
  74. package/libx/_runtime/common/generic/input.js +14 -8
  75. package/libx/_runtime/common/i18n/messages.properties +1 -1
  76. package/libx/_runtime/db/expand/expandCQNToJoin.js +19 -17
  77. package/libx/_runtime/db/expand/rawToExpanded.js +3 -5
  78. package/libx/_runtime/db/utils/generateAliases.js +1 -1
  79. package/libx/_runtime/fiori/generic/activate.js +1 -1
  80. package/libx/_runtime/fiori/generic/before.js +18 -19
  81. package/libx/_runtime/fiori/generic/prepare.js +1 -1
  82. package/libx/_runtime/fiori/generic/read.js +1 -1
  83. package/libx/_runtime/fiori/lean-draft.js +27 -21
  84. package/libx/_runtime/fiori/utils/handler.js +0 -6
  85. package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +1 -1
  86. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +0 -5
  87. package/libx/_runtime/hana/pool.js +26 -18
  88. package/libx/_runtime/hana/search2Contains.js +1 -1
  89. package/libx/_runtime/hana/search2cqn4sql.js +26 -18
  90. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +23 -16
  91. package/libx/_runtime/messaging/outbox/utils.js +6 -1
  92. package/libx/_runtime/remote/Service.js +64 -38
  93. package/libx/_runtime/remote/utils/client.js +13 -9
  94. package/libx/rest/middleware/read.js +2 -1
  95. package/package.json +1 -1
@@ -4,6 +4,18 @@ const GREY = COLORS ? '\x1b[2m' : ''
4
4
  const RESET = COLORS ? '\x1b[0m' : ''
5
5
  const DEBUG = cds.debug('deploy')
6
6
  const TRACE = cds.debug('trace')
7
+ const nativeSchevoSqls = {
8
+ 'postgres': {
9
+ create: `CREATE table cds_model (csn text)`,
10
+ insert: `INSERT into cds_model values ('null')`,
11
+ drop: `DROP table if exists cds_model;`
12
+ },
13
+ 'sqlite': {
14
+ create: `CREATE table cds_Model (csn CLOB)`,
15
+ insert: `INSERT into cds_Model values ('null')`,
16
+ drop: `DROP table if exists cds_Model;`
17
+ }
18
+ }
7
19
 
8
20
  /**
9
21
  * Implementation of `cds.deploy` common to all databases.
@@ -96,23 +108,34 @@ exports.exclude_external_entities_in = function (csn) { // NOSONAR
96
108
 
97
109
 
98
110
  exports.create = async function cds_deploy_create (db, csn=db.model, o) {
111
+ // REVISIT: How to find out if we use better-sqlite?
112
+ if (db._source === '@cap-js/sqlite') {
113
+ // it's required to set both properties
114
+ o.betterSqliteSessionVariables = true
115
+ o.sqlDialect = 'sqlite'
116
+ }
99
117
 
118
+ const sqls = nativeSchevoSqls[db.options?.dialect || db.options?.kind || 'sqlite'];
100
119
  let drops, creas, schevo = db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto'
101
120
  if (schevo) {
102
121
  // 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)
122
+ let before;
123
+ try {
124
+ const [{csn}] = await db.read('cds.Model');
125
+ before = JSON.parse(csn);
126
+ } catch(e) {
127
+ if (!e.message.includes('no such table') && !e.message.includes('relation "cds_model" does not exist')) throw e
128
+ // TODO: Put both into one tx - read can not be part of it, as the transaction gets killed in PG once an error appears
129
+ await db.run(sqls.create)
130
+ await db.run(sqls.insert)
131
+ before = null;
132
+ }
133
+ const { afterImage, drops:_drops, createsAndAlters:_creas } = cds.compile.to.sql.delta (csn, {...o, ...db.options}, before)
111
134
  creas = _creas.concat (o.dry ? [] : UPDATE('cds.Model').with({ csn: JSON.stringify(afterImage) }))
112
135
  drops = before ? _drops : _drops4(_creas)
113
136
  } else {
114
- creas = cds.compile.to.sql (csn, o)
115
- drops = _drops4(creas) .concat ('DROP table if exists cds_Model;')
137
+ creas = cds.compile.to.sql (csn, {...o, ...db.options})
138
+ drops = _drops4(creas) .concat (sqls.drop)
116
139
  }
117
140
  if (!creas || creas.length === 0) return
118
141
 
@@ -130,7 +153,7 @@ exports.create = async function cds_deploy_create (db, csn=db.model, o) {
130
153
  else return db.run (async tx => {
131
154
  // This runs in a new transaction if called from CLI, while joining
132
155
  // existing root tx, e.g. when called from DeploymentService.
133
- if (!schevo && await tx.exists('sqlite.schema').where({name:'cds_xt_Extensions'})) {
156
+ if (!schevo && db.kind == 'sqlite' && await tx.exists('sqlite.schema').where({name:'cds_xt_Extensions'})) {
134
157
  // Poor man's schema evolution for extensions in drop-create deployments
135
158
  drops = drops.filter(d => !d.includes('cds_xt_Extensions'))
136
159
  creas = creas.filter(c => !c.includes('cds_xt_Extensions'))
@@ -207,7 +230,8 @@ exports.resources = async function cds_deploy_resources (csn, opts) {
207
230
  const f = fx.slice(0,-ext.length)
208
231
  if (/[._]texts$/.test(f) && files.some(g => g.startsWith(f+'_'))) {
209
232
  // ignores 'Books_texts.csv/json' if there is any 'Books_texts_LANG.csv/json'
210
- DEBUG && DEBUG (`ignoring '${fx}' in favor of translated ones`); continue
233
+ DEBUG && DEBUG (`ignoring '${fx}' in favor of translated ones`)
234
+ continue
211
235
  }
212
236
  const e = _entity4(f,csn); if (_skip(e)) continue
213
237
  if (cds.env.features.deploy_data_onconflict === 'replace' && !/[._]texts_/.test(f)) {
@@ -32,11 +32,7 @@ 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
+ if (_home) _add_static_profiles (_home, profiles);
40
36
  if (!profiles.includes('production')) profiles.push('development')
41
37
  this._profiles = new Set (profiles)
42
38
  this._profiles._defined = new Set()
@@ -78,17 +74,34 @@ class Config {
78
74
  // 6. complete service configurations from cloud service bindings
79
75
  this._add_cloud_service_bindings(process.env)
80
76
 
81
- // 7. Add compatibility for mtx
82
- if (this.requires && this.requires.db) {
83
- if (this.requires.multitenancy !== undefined) {
84
- Object.defineProperty(this.requires.db, 'multiTenant', { value: !!this.requires.multitenancy })
77
+ // 7. Add compatibility and correlations for mtx, better-sqlite, and lean draft
78
+ const db = this.requires?.db
79
+ if (this.requires?.db) {
80
+ if (this.requires.multitenancy !== undefined)
81
+ Object.defineProperty (db, 'multiTenant', { value: !!this.requires.multitenancy })
82
+ else if (db.multiTenant !== undefined)
83
+ this.requires.multitenancy = db.multiTenant
84
+ // Automatically use new sqlite service if in package dependencies
85
+ const new_sqlite_service = '@cap-js/sqlite'
86
+ if (db.kind === 'sqlite') try {
87
+ const pkg = require(path.join(_home,'package.json'))
88
+ if (pkg.dependencies?.[new_sqlite_service] || pkg.devDependencies?.[new_sqlite_service]) {
89
+ db.impl = new_sqlite_service
90
+ }
91
+ } catch (e) {
92
+ if (e.code !== 'MODULE_NOT_FOUND') throw e
93
+ }
94
+ // Add correlation b/w better-sqlite and lean-draft
95
+ if (this.fiori && this.fiori.lean_draft === undefined && db.impl === new_sqlite_service) {
96
+ this.fiori.lean_draft = true
85
97
  }
86
- else if (this.requires.db.multiTenant !== undefined) this.requires.multitenancy = this.requires.db.multiTenant
87
98
  }
88
99
 
100
+
89
101
  // 8. apply presets
90
102
  presets (this)
91
103
 
104
+
92
105
  // Only if feature is enabled
93
106
  if (this.features && this.features.emulate_vcap_services) {
94
107
  this._emulate_vcap_services()
@@ -124,6 +137,12 @@ class Config {
124
137
  add (conf, /*from:*/ _src, profiles = this._profiles, profiles_only = false) {
125
138
  if (!conf) return this
126
139
  if (_src) this._sources.push (_src)
140
+ const reqs = conf.requires
141
+ if (reqs) { // normalize requires.x = kind to requires.x = {kind}
142
+ for (let each in reqs) {
143
+ if (typeof reqs[each] === 'string') reqs[each] = {kind:conf.requires[each]}
144
+ }
145
+ }
127
146
  _merge (this, conf, profiles, undefined, profiles_only)
128
147
  return this
129
148
  }
@@ -278,7 +297,7 @@ class Config {
278
297
  if (typeof preset === 'object' && preset !== val) {
279
298
  const top = val, base = _merge ({},_linked(p,preset), _profiles), {kind} = base
280
299
  val = _merge (base, top, _profiles, false) // apply/override with top-level data
281
- if (kind) val.kind = kind // but inherited kind wins
300
+ if (kind) val.kind = kind // but inherited kind wins
282
301
  }
283
302
  if (typeof val === 'object') Object.defineProperty (val, '_is_linked', {value:true})
284
303
  return val
@@ -418,13 +437,27 @@ class Config {
418
437
  }
419
438
 
420
439
 
421
-
422
-
423
440
  //////////////////////////////////////////////////////////////////////////
424
441
  //
425
442
  // Local Helpers...
426
443
  //
427
444
 
445
+ /**
446
+ * Allows to set profiles in package.json or .cdsrc.json like so:
447
+ * ```json
448
+ * { "cds": { "profiles": ["mtx-sidecar","java"] } }
449
+ * { "cds": { "profile": "mtx-sidecar" } }
450
+ * ```
451
+ */
452
+ function _add_static_profiles (_home, profiles) {
453
+ for (let src of ['package.json', '.cdsrc.json']) try {
454
+ const conf = require(path.join(_home,src))
455
+ const cds = src === '.cdsrc.json' ? conf : conf.cds
456
+ if (cds?.profiles) return profiles.push(...cds.profiles)
457
+ if (cds?.profile) return profiles.push(cds.profile)
458
+ } catch (e) { if (e.code !== 'MODULE_NOT_FOUND') throw e }
459
+ }
460
+
428
461
  /**
429
462
  * @returns {Config} dst
430
463
  */
@@ -442,7 +475,7 @@ function _merge (dst, src, _profiles, _cloned, _profiles_only = false) {
442
475
  const profile = p.slice(1,-1)
443
476
  if (_profiles._defined) _profiles._defined.add (profile)
444
477
  if (_profiles.has(profile)) {
445
- let o = src[p]; if (typeof o === 'string') o = {kind:o}
478
+ let o = src[p]; if (typeof o !== 'object') continue
446
479
  profiled.push ({ profile, merge: () => _merge (dst, o, _profiles, _cloned, false)})
447
480
  }
448
481
  continue
@@ -120,27 +120,36 @@ const _databases = {
120
120
  '[development]': { kind: 'sqlite', credentials: { url: ':memory:' } },
121
121
  '[production]': { kind: 'hana' },
122
122
  },
123
+
124
+ "sqlite": _compat_to_use({
125
+ '[better-sqlite]': { impl: '@cap-js/sqlite' },
126
+ impl: `${_runtime}/sqlite/Service.js`,
127
+ credentials: { url: 'db.sqlite' },
128
+ }),
129
+
123
130
  "better-sqlite": {
124
131
  credentials: { url: ":memory:" },
125
132
  impl: "@cap-js/sqlite",
126
133
  kind: 'sqlite'
127
134
  },
128
- "sql-mt": { // For compatibility only
129
- '[development]': { kind: 'sqlite' },
130
- '[production]': { kind: 'hana-mt' },
131
- },
132
- "sqlite": _compat_to_use({
133
- '[better-sqlite]': { impl: '@cap-js/sqlite' },
135
+ "legacy-sqlite": _compat_to_use({
134
136
  impl: `${_runtime}/sqlite/Service.js`,
135
137
  credentials: { url: 'db.sqlite' },
138
+ kind: 'sqlite'
136
139
  }),
140
+
137
141
  "hana": _compat_to_use ({
138
142
  impl: `${_runtime}/hana/Service.js`,
139
143
  }),
140
144
  "hana-cloud": _compat_to_use ({
141
145
  kind: 'hana', "deploy-format": "hdbtable",
142
146
  }),
143
- "hana-mt": _compat_to_use ({
147
+
148
+ "sql-mt": { // For compatibility only
149
+ '[development]': { kind: 'sqlite' },
150
+ '[production]': { kind: 'hana-mt' },
151
+ },
152
+ "hana-mt": _compat_to_use ({ // For compatibility only
144
153
  kind: 'hana', "deploy-format": "hdbtable",
145
154
  "vcap": {
146
155
  "label": "service-manager"
@@ -31,8 +31,8 @@ const defaults = module.exports = {
31
31
  fiori: {
32
32
  preview: !production,
33
33
  routes: !production,
34
- lean_draft: false,
35
- draft_compat: false,
34
+ lean_draft: undefined,
35
+ draft_compat: undefined,
36
36
  '[better-sqlite]': { lean_draft: true },
37
37
  '[lean-draft]': { lean_draft: true },
38
38
  '[draft-compat]': { draft_compat: true },
@@ -86,6 +86,7 @@
86
86
  "java",
87
87
  "hana",
88
88
  "mtx-sidecar",
89
+ "mtx-extension",
89
90
  "mtx",
90
91
  "fiori",
91
92
  "node-cf",
@@ -611,18 +612,10 @@
611
612
  "const": "hana-cloud",
612
613
  "description": "SAP HANA Cloud"
613
614
  },
614
- {
615
- "const": "hana-mt",
616
- "description": "Multitenant SAP HANA Cloud"
617
- },
618
615
  {
619
616
  "const": "sql",
620
617
  "description": "In-memory SQLite (development), SAP HANA (production)"
621
618
  },
622
- {
623
- "const": "sql-mt",
624
- "description": "File-based SQLite (development), Multitenant SAP HANA Cloud (production)"
625
- },
626
619
  {
627
620
  "const": "sqlite",
628
621
  "description": "File-based SQLite"
package/lib/index.js CHANGED
@@ -137,7 +137,7 @@ extend (cds.__proto__) .with (lazified ({
137
137
  const odp = Object.defineProperty, _global = (_,...pp) => pp.forEach (p => odp(global,p,{
138
138
  configurable:true, get:()=>{ let v=cds[_][p]; odp(this,p,{value:v}); return v }
139
139
  }))
140
- _global ('ql','SELECT','INSERT','UPSERT','UPDATE','DELETE','CREATE','DROP')
140
+ _global ('ql','SELECT','INSERT','UPSERT','UPDATE','DELETE','CREATE','DROP','STREAM')
141
141
  _global ('parse','CDL','CQL','CXL')
142
142
 
143
143
  // Check Node.js version
@@ -0,0 +1,89 @@
1
+ const Whereable = require('./Whereable'), { parse } = Whereable
2
+
3
+ module.exports = class Query extends Whereable {
4
+
5
+ static _api() {
6
+ const $ = Object.assign
7
+ return $((..._) => new this()._column_or_data_or_from(..._), {
8
+ into: (..._) => new this().into(..._),
9
+ from: (..._) => new this().from(..._),
10
+ column: (..._) => new this().column(..._),
11
+ columns: (..._) => new this().columns(..._),
12
+ })
13
+ }
14
+
15
+ _column_or_data_or_from (col, ...more) { // srv.read`title`.from`Books` or srv.read`Books` ?
16
+ if (!col) return this
17
+ else if (col.name) return this.from (...arguments) //> clearly a from
18
+ else if (col.raw) { // tagged template string
19
+ if (col[0].startsWith('from ')) { // STREAM`from ...`, with an arbitrary long CQL tail...
20
+ Object.assign (this.STREAM, STREAM_(' ',arguments))
21
+ return this
22
+ } else if (col[0][0] === '{') { // STREAM`{a,b}`... -> it's column
23
+ let {column:c} = STREAM_('from X', arguments)
24
+ return this._add('column',c)
25
+ } else { // STREAM`Foo` -> ambiguous -> try parsing as column...
26
+ let {column:c} = STREAM_('from X {', arguments, '}')
27
+ if (c.length > 1 || !c[0].ref) return this._add('column',c)
28
+ // else cols = c[0] //> goes on below...
29
+ }
30
+ } else { // STREAM('foo')
31
+ if (Array.isArray(col)) return this.columns(col)
32
+ if (typeof col === 'string') return this.column(col)
33
+ else return this.data(col)
34
+ }
35
+
36
+ // return a proxy assuming it's a from and switching to
37
+ // column on a subsequent call of .from, if any.
38
+ const {STREAM:_} = this
39
+ return Object.defineProperties (this.from (col, ...more), {
40
+ from: { configurable:true, value:(...args) => { delete this.from
41
+ return this.from (...args) .column (col, ...more)
42
+ }}
43
+ })
44
+ }
45
+
46
+ column (col) {
47
+ this._add ('column',col)
48
+ return this
49
+ }
50
+
51
+ columns (cols) {
52
+ this._add ('columns',cols)
53
+ return this
54
+ }
55
+
56
+ from (target, key, column) {
57
+ this.STREAM.from = this._target_ref4 (...arguments)
58
+ if (!target.raw && key) {
59
+ this.byKey(key)
60
+ if (typeof column === 'function') {
61
+ const cols=[]; column (new Proxy (column,{ get: (_,p) => cols.push(p) }))
62
+ cols.length === 1 ? this.column(cols[0]) : this.columns(cols)
63
+ }
64
+ else if (column) this.column(column)
65
+ }
66
+ return this
67
+ }
68
+
69
+ into (target, key) {
70
+ this.STREAM.into = this._target_ref4 (...arguments)
71
+ if (!target.raw && key) {
72
+ this.byKey(key)
73
+ }
74
+ return this
75
+ }
76
+
77
+ data (x) {
78
+ this.STREAM.data = x
79
+ return this
80
+ }
81
+ }
82
+
83
+
84
+ const {CQL} = parse, STREAM_ = (prefix, [ strings, ...more ], suffix) => {
85
+ const tts = [...strings]; tts.raw = true
86
+ if (prefix) tts[0] = `STREAM ${prefix} ${tts[0]}`
87
+ if (suffix) tts[tts.length-1] += ` ${suffix}`
88
+ return CQL(tts,...more).STREAM
89
+ }
package/lib/ql/cds-ql.js CHANGED
@@ -11,13 +11,14 @@ require = path => { // eslint-disable-line no-global-assign
11
11
 
12
12
  module.exports = {
13
13
  Query, clone: (q,_) => Query.prototype.clone.call(q,_),
14
+ STREAM: require('./STREAM'),
14
15
  SELECT: require('./SELECT'),
15
16
  INSERT: require('./INSERT'),
16
17
  UPSERT: require('./UPSERT'),
17
18
  UPDATE: require('./UPDATE'),
18
19
  DELETE: require('./DELETE'),
19
20
  CREATE: require('./CREATE'),
20
- DROP: require('./DROP'),
21
+ DROP: require('./DROP'),
21
22
  }
22
23
 
23
24
  module.exports._reset = ()=>{ // for strange tests only
@@ -42,6 +42,7 @@ class Request extends require('./event') {
42
42
  if (q.UPSERT) return this._set ('path', _path4 (q.UPSERT,'into'))
43
43
  if (q.UPDATE) return this._set ('path', _path4 (q.UPDATE,'entity'))
44
44
  if (q.DELETE) return this._set ('path', _path4 (q.DELETE,'from'))
45
+ if (q.STREAM) return this._set ('path', _path4 (q.STREAM,'from') || _path4 (q.STREAM,'into'))
45
46
  }
46
47
  const {_} = this
47
48
  if (_.target) return this._set ('path', _.target.name)
@@ -65,7 +66,8 @@ class Request extends require('./event') {
65
66
  q?.INSERT?.into ||
66
67
  q?.UPSERT?.into ||
67
68
  q?.UPDATE?.entity ||
68
- q?.DELETE?.from
69
+ q?.DELETE?.from ||
70
+ q?.STREAM?.from || q?.STREAM?.into
69
71
  )
70
72
 
71
73
  const {target} = this; if (!target) return undefined
@@ -113,7 +115,7 @@ class Request extends require('./event') {
113
115
 
114
116
  // create copy
115
117
  const {SELECT:{from:{ref}}} = SELECT.from(id,keys)
116
- subjectRef.push(...ref)
118
+ subjectRef.push(...ref)
117
119
  })
118
120
  return super.subject = {ref: subjectRef}
119
121
  }
@@ -165,6 +167,7 @@ const Http2Crud = {
165
167
  }
166
168
 
167
169
  const SQL2Crud = {
170
+ STREAM: 'STREAM',
168
171
  SELECT: 'READ',
169
172
  INSERT: 'CREATE',
170
173
  UPSERT: 'UPSERT',
package/lib/req/user.js CHANGED
@@ -24,7 +24,7 @@ class User {
24
24
  role === 'identified-user' ||
25
25
  role === 'system-user' && this._is_system ||
26
26
  role === 'internal-user' && this._is_internal ||
27
- role === 'authenticated-user' && this.authLevel !== 'weak' ||
27
+ role === 'authenticated-user' ||
28
28
  !!this.roles[role]
29
29
  }
30
30
  valueOf() { return this.id }
@@ -12,7 +12,7 @@ exports.before = [
12
12
  auth, // provides req.user & tenant
13
13
  ctx_auth, // propagates auth results to cds.context
14
14
  ctx_model, // fills in cds.context.model, in case of extensibility
15
- ].map(_instantiate)
15
+ ].map(mw => _instantiate(mw))
16
16
 
17
17
  // middlewares running after protocol adapters -> usually error middlewares
18
18
  exports.after = [
@@ -29,18 +29,20 @@ exports.after = [
29
29
  * cds.middlewares.add (mymw) // to the end
30
30
  * ```
31
31
  */
32
- exports.add = (new_mw, { at: index, before, after, options }) => {
33
- let mw = new_mw (options)
34
- if (index) return exports.before.splice (index, 0, mw)
35
- if (before) return exports.before.splice (index, _index4(before), mw)
36
- if (after) return exports.before.splice (index, _index4(after)+1, mw)
32
+ exports.add = (new_mw, { at: index, before, after, options } = {}) => {
33
+ let mw = _isNotWrapped(new_mw) ? new_mw : new_mw (options)
34
+ if (index !== undefined) return exports.before.splice (index, 0, mw)
35
+ if (before) return exports.before.splice (_index4(before), 0, mw)
36
+ if (after) return exports.before.splice (_index4(after)+1, 0, mw)
37
37
  else return exports.before.push(mw)
38
38
  }
39
39
 
40
+ const _isNotWrapped = mw => (typeof mw === 'function' && mw.length === 3) || Array.isArray(mw)
41
+
40
42
  function _index4 (middleware) {
41
43
  if (typeof middleware === 'string') middleware = exports[middleware]
42
44
  if (!middleware) throw new Error (`Didn't find a middleware matching ${{middleware}}`)
43
- const index = exports.before.findIndex(mw => mw.factory === before)
45
+ const index = exports.before.findIndex(mw => mw.factory === middleware)
44
46
  if (index === -1) throw new Error (`Didn't find ${{middleware}} in cds.middlewares.before`)
45
47
  return index
46
48
  }
@@ -61,11 +61,12 @@ function _instrument_cds_services (_get_perf) {
61
61
  }
62
62
  }
63
63
 
64
+ let sqlite
64
65
  function _instrument_sqlite (_get_perf) {
65
66
  const me = _instrument_sqlite; if (me.done) return; else me.done = true
66
67
  try { require.resolve('sqlite3') } catch { return }
67
68
  // eslint-disable-next-line cds/no-missing-dependencies
68
- const sqlite = require('sqlite3').Database.prototype
69
+ sqlite = require('sqlite3').Database.prototype
69
70
  for (let each of ['all', 'get', 'run', 'prepare']) _wrap(each,sqlite)
70
71
  function _wrap (op,sqlite) {
71
72
  const _super = sqlite[op]
@@ -73,11 +74,11 @@ function _instrument_sqlite (_get_perf) {
73
74
  const perf = _get_perf(q) //> q is a SQL command like BEGIN, COMMIT, ROLLBACK, SELECT ...
74
75
  if (perf) {
75
76
  const pe = perf.log ('sqlite3', '-', q)
76
- const callback = _[_.length-1]; _[_.length-1] = function(ps){
77
+ const callback = _[_.length-1]; _[_.length-1] = function(){
77
78
  if (op === 'prepare') callback.apply (this, {
78
- all: _wrap('all',ps),
79
- get: _wrap('get',ps),
80
- run: _wrap('run',ps),
79
+ all: _wrap('all',sqlite),
80
+ get: _wrap('get',sqlite),
81
+ run: _wrap('run',sqlite),
81
82
  }); else {
82
83
  perf.done(pe)
83
84
  callback.apply (this, arguments)
@@ -71,6 +71,7 @@ class Service extends require('./srv-handlers') {
71
71
  return this.dispatch (req)
72
72
  }
73
73
  read (...args) { return is_query(args[0]) ? this.run(...args) : SELECT(...args).bind(this) }
74
+ stream (...args) { return is_query(args[0]) ? this.run(...args) : STREAM(...args).bind(this) }
74
75
  insert (...args) { return INSERT(...args).bind(this) }
75
76
  create (...args) { return INSERT.into(...args).bind(this) }
76
77
  update (...args) { return UPDATE.entity(...args).bind(this) }
@@ -194,7 +194,7 @@ exports._oldMtx = function _oldMtx() {
194
194
  * Internal utility to load a file through ESM or CommonJs. TODO find a better place.
195
195
  */
196
196
  exports._import = id => require(id)
197
- if (typeof jest === 'undefined') { // jest's ESM support is experimental: https://jestjs.io/docs/ecmascript-modules
197
+ if (process.env.JEST_WORKER_ID === undefined) { // jest's ESM support is experimental: https://jestjs.io/docs/ecmascript-modules
198
198
  const { pathToFileURL } = require('url')
199
199
  exports._import = id => {
200
200
  if (extname(id) === '.ts') return require(id) // ts-node w/ ESM not working (cap/issues#11980)
package/lib/utils/tar.js CHANGED
@@ -83,7 +83,7 @@ exports.create = async (dir='.', ...args) => {
83
83
  let c, temp
84
84
  args = args.filter(el => el)
85
85
  if (process.platform === 'win32') {
86
- const spawnDir = (dir, args) => {
86
+ const spawnDir = (dir, args) => {
87
87
  if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', win(dir), ...win(args)])
88
88
  else return spawn ('tar', ['cf', '-', '-C', win(dir), ...win(args)])
89
89
  }
@@ -101,7 +101,7 @@ exports.create = async (dir='.', ...args) => {
101
101
  } else {
102
102
  args.push('.')
103
103
  }
104
-
104
+
105
105
  c = spawn ('tar', ['c', '-C', dir, ...args])
106
106
  }
107
107
 
@@ -112,21 +112,15 @@ exports.create = async (dir='.', ...args) => {
112
112
  * in-memory Buffer holding the tar output, hence enabling this usage:
113
113
  * @example const buffer = await tar.c('src/dir')
114
114
  */
115
- then (r,e) {
116
- const bb=[]
117
- const eb=[]
118
- c.stdout.on('data', b => bb.push(b))
119
- c.stderr.on('data', b => eb.push(b))
120
- c.on('close', (code) => {
121
- if (code === 0) {
122
- return r(Buffer.concat(bb))
123
- }
124
- e(new Error('tar: ' + Buffer.concat(eb)))
125
- })
126
- c.on('error', e)
115
+ then (resolve, reject) {
116
+ let data=[], stderr=''
117
+ c.stdout.on('data', d => data.push(d))
118
+ c.stderr.on('data', d => stderr += d)
119
+ c.on('close', code => code ? reject(new Error(stderr)) : resolve(Buffer.concat(data)))
120
+ c.on('error', reject)
127
121
  if (process.platform === 'win32') {
128
- c.on('close', async () => temp && exists(temp) && await rimraf(temp))
129
- c.on('error', async () => temp && exists(temp) && await rimraf(temp))
122
+ c.on('close', () => temp && exists(temp) && rimraf(temp))
123
+ c.on('error', () => temp && exists(temp) && rimraf(temp))
130
124
  }
131
125
  },
132
126
 
@@ -143,9 +137,9 @@ exports.create = async (dir='.', ...args) => {
143
137
  }
144
138
  // Returning a thenable ChildProcess.stdout
145
139
  return {__proto__: c.stdout.pipe (out),
146
- then(r,e) {
147
- out.on('close',r)
148
- c.on('error',e)
140
+ then (resolve, reject) {
141
+ out.on('close', code => code ? reject(code) : resolve())
142
+ c.on('error', reject)
149
143
  }
150
144
  }
151
145
  }
@@ -175,18 +169,21 @@ exports.extract = (archive, ...args) => ({
175
169
  to (...dest) {
176
170
  if (typeof dest === 'string') dest = _resolve(...dest)
177
171
  const input = typeof archive !== 'string' || archive == '-' ? '-' : _resolve(archive)
178
- const x = spawn('tar', ['xf', win(input), '-C', win(dest), ...args])
172
+ const x = spawn('tar', ['xf', win(input), '-C', win(dest), ...args], { env: { COPYFILE_DISABLE: 1 }})
179
173
  if (archive === '-') return x.stdin
180
174
  if (Buffer.isBuffer(archive)) archive = require('stream').Readable.from (archive)
181
175
  if (typeof archive !== 'string') archive.pipe (x.stdin)
182
- if (args.includes('-v')) {
183
- var list = '';
184
- ;(process.platform === 'linux' ? x.stdout : x.stderr) .on ('data', d => list+=d)
185
- }
176
+ let stdout='', stderr=''
177
+ x.stdout.on ('data', d => stdout += d)
178
+ x.stderr.on ('data', d => stderr += d)
186
179
  return {__proto__:x,
187
- then(r,e) {
188
- x.on('close',()=>r(list ? list.split('\n').slice(0,-1).map(x => x.replace(/^x |\r/g,'')) : undefined))
189
- x.on('error',e)
180
+ then (resolve, reject) {
181
+ x.on('close', code => {
182
+ if (code) return reject (new Error(stderr))
183
+ if (process.platform === 'linux') stdout = stderr
184
+ resolve (stdout ? stdout.split('\n').slice(0,-1).map(x => x.replace(/^x |\r/g,'')): undefined)
185
+ })
186
+ x.on('error', reject)
190
187
  }
191
188
  }
192
189
  },
@@ -205,11 +202,13 @@ exports.extract = (archive, ...args) => ({
205
202
  exports.list = (archive, ...more) => {
206
203
  const input = typeof archive !== 'string' ? '-' : archive === '-' ? archive : _resolve(archive)
207
204
  const x = spawn(`tar tf`, [ input, ...more ], { shell:true })
208
- let list = ''; x.stdout.on ('data', d => list+=d)
205
+ let stdout='', stderr=''
206
+ x.stdout.on ('data', d => stdout += d)
207
+ x.stderr.on ('data', d => stderr += d)
209
208
  return {__proto__:x,
210
- then(r,e) {
211
- x.on('close',()=>r(list.split('\n').slice(0,-1)))
212
- x.on('error',e)
209
+ then (resolve, reject) {
210
+ x.on('close', code => code ? reject(new Error(stderr)) : resolve(stdout.split('\n').slice(0,-1)))
211
+ x.on('error', reject)
213
212
  }
214
213
  }
215
214
  }