@sap/cds 6.7.2 → 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.
- package/CHANGELOG.md +41 -1
- package/_i18n/i18n.properties +9 -6
- package/_i18n/i18n_ar.properties +6 -6
- package/_i18n/i18n_cs.properties +6 -6
- package/_i18n/i18n_da.properties +6 -6
- package/_i18n/i18n_de.properties +6 -6
- package/_i18n/i18n_en.properties +6 -6
- package/_i18n/i18n_es.properties +6 -6
- package/_i18n/i18n_fi.properties +6 -6
- package/_i18n/i18n_fr.properties +6 -6
- package/_i18n/i18n_hu.properties +6 -6
- package/_i18n/i18n_it.properties +6 -6
- package/_i18n/i18n_ja.properties +6 -6
- package/_i18n/i18n_ko.properties +6 -6
- package/_i18n/i18n_ms.properties +6 -6
- package/_i18n/i18n_nl.properties +6 -6
- package/_i18n/i18n_no.properties +6 -6
- package/_i18n/i18n_pl.properties +6 -6
- package/_i18n/i18n_pt.properties +6 -6
- package/_i18n/i18n_ro.properties +6 -6
- package/_i18n/i18n_ru.properties +6 -6
- package/_i18n/i18n_sv.properties +6 -6
- package/_i18n/i18n_th.properties +6 -6
- package/_i18n/i18n_tr.properties +8 -8
- package/_i18n/i18n_zh_CN.properties +3 -3
- package/_i18n/i18n_zh_TW.properties +6 -6
- package/apis/core.d.ts +30 -31
- package/apis/csn.d.ts +1 -1
- package/apis/ql.d.ts +69 -39
- package/apis/serve.d.ts +4 -3
- package/apis/services.d.ts +20 -7
- package/bin/build/buildTaskEngine.js +1 -1
- package/bin/build/index.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +9 -6
- package/bin/build/provider/hana/index.js +11 -4
- package/bin/build/provider/mtx-sidecar/index.js +3 -3
- package/bin/build/provider/nodejs/index.js +23 -0
- package/bin/version.js +3 -2
- package/common.cds +3 -2
- package/lib/auth/index.js +3 -0
- package/lib/auth/mocked-users.js +13 -0
- package/lib/compile/etc/_localized.js +3 -0
- package/lib/core/entities.js +7 -3
- package/lib/dbs/cds-deploy.js +36 -12
- package/lib/env/cds-env.js +47 -14
- package/lib/env/cds-requires.js +16 -7
- package/lib/env/defaults.js +2 -2
- package/lib/env/schemas/cds-rc.json +1 -8
- package/lib/index.js +1 -1
- package/lib/ql/STREAM.js +89 -0
- package/lib/ql/cds-ql.js +2 -1
- package/lib/req/request.js +5 -2
- package/lib/req/user.js +1 -1
- package/lib/srv/middlewares/index.js +9 -7
- package/lib/srv/middlewares/trace.js +6 -5
- package/lib/srv/srv-api.js +1 -0
- package/lib/utils/cds-utils.js +1 -1
- package/lib/utils/tar.js +30 -31
- package/libx/_runtime/audit/Service.js +96 -37
- package/libx/_runtime/audit/generic/personal/utils.js +26 -13
- package/libx/_runtime/audit/utils/v2.js +21 -22
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +10 -3
- package/libx/_runtime/cds-services/services/Service.js +2 -7
- package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
- package/libx/_runtime/common/aspects/any.js +1 -1
- package/libx/_runtime/common/generic/auth/utils.js +30 -41
- package/libx/_runtime/common/i18n/messages.properties +1 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +19 -17
- package/libx/_runtime/db/expand/rawToExpanded.js +3 -5
- package/libx/_runtime/db/utils/generateAliases.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +1 -1
- package/libx/_runtime/fiori/generic/before.js +18 -19
- package/libx/_runtime/fiori/generic/prepare.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +8 -6
- package/libx/_runtime/fiori/utils/handler.js +0 -6
- package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +1 -1
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +0 -5
- package/libx/_runtime/hana/pool.js +26 -18
- package/libx/_runtime/hana/search2Contains.js +1 -1
- package/libx/_runtime/hana/search2cqn4sql.js +26 -18
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +23 -16
- package/libx/_runtime/messaging/outbox/utils.js +6 -1
- package/libx/_runtime/remote/Service.js +64 -38
- package/libx/_runtime/remote/utils/client.js +13 -9
- package/libx/rest/middleware/read.js +2 -1
- package/package.json +1 -1
package/lib/dbs/cds-deploy.js
CHANGED
|
@@ -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
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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 (
|
|
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`)
|
|
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)) {
|
package/lib/env/cds-env.js
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
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
|
|
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
|
package/lib/env/cds-requires.js
CHANGED
|
@@ -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
|
-
"
|
|
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
|
-
|
|
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"
|
package/lib/env/defaults.js
CHANGED
|
@@ -31,8 +31,8 @@ const defaults = module.exports = {
|
|
|
31
31
|
fiori: {
|
|
32
32
|
preview: !production,
|
|
33
33
|
routes: !production,
|
|
34
|
-
lean_draft:
|
|
35
|
-
draft_compat:
|
|
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
|
package/lib/ql/STREAM.js
ADDED
|
@@ -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:
|
|
21
|
+
DROP: require('./DROP'),
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
module.exports._reset = ()=>{ // for strange tests only
|
package/lib/req/request.js
CHANGED
|
@@ -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'
|
|
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)
|
|
35
|
-
if (before) return exports.before.splice (
|
|
36
|
-
if (after) return exports.before.splice (
|
|
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 ===
|
|
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
|
-
|
|
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(
|
|
77
|
+
const callback = _[_.length-1]; _[_.length-1] = function(){
|
|
77
78
|
if (op === 'prepare') callback.apply (this, {
|
|
78
|
-
all: _wrap('all',
|
|
79
|
-
get: _wrap('get',
|
|
80
|
-
run: _wrap('run',
|
|
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)
|
package/lib/srv/srv-api.js
CHANGED
|
@@ -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) }
|
package/lib/utils/cds-utils.js
CHANGED
|
@@ -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 (
|
|
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 (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
c.
|
|
119
|
-
c.
|
|
120
|
-
c.on('
|
|
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',
|
|
129
|
-
c.on('error',
|
|
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(
|
|
147
|
-
out.on('close',
|
|
148
|
-
c.on('error',
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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(
|
|
188
|
-
x.on('close',
|
|
189
|
-
|
|
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
|
|
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(
|
|
211
|
-
x.on('close',()
|
|
212
|
-
x.on('error',
|
|
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
|
}
|