@sap/cds 6.3.1 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/apis/cds.d.ts +1 -1
- package/apis/core.d.ts +118 -90
- package/apis/cqn.d.ts +11 -2
- package/apis/internal/inference.d.ts +7 -2
- package/apis/ql.d.ts +45 -11
- package/apis/serve.d.ts +8 -1
- package/apis/services.d.ts +303 -305
- package/bin/build/buildTaskEngine.js +28 -36
- package/bin/build/buildTaskFactory.js +32 -81
- package/bin/build/buildTaskHandler.js +3 -2
- package/bin/build/buildTaskProvider.js +2 -2
- package/bin/build/buildTaskProviderFactory.js +5 -14
- package/bin/build/constants.js +0 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +7 -6
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +6 -5
- package/bin/build/provider/buildTaskHandlerInternal.js +9 -30
- package/bin/build/provider/buildTaskProviderInternal.js +70 -58
- package/bin/build/provider/fiori/index.js +6 -5
- package/bin/build/provider/hana/2migration.js +20 -3
- package/bin/build/provider/hana/2tabledata.js +1 -0
- package/bin/build/provider/hana/index.js +40 -17
- package/bin/build/provider/java/index.js +10 -10
- package/bin/build/provider/mtx/index.js +25 -16
- package/bin/build/provider/mtx/resourcesTarBuilder.js +22 -27
- package/bin/build/provider/mtx-extension/index.js +3 -2
- package/bin/build/provider/mtx-sidecar/index.js +16 -15
- package/bin/build/provider/nodejs/index.js +14 -56
- package/bin/build/util.js +56 -16
- package/bin/deploy/to-hana/cfUtil.js +4 -1
- package/bin/deploy/to-hana/gitUtil.js +1 -1
- package/bin/deploy/to-hana/hana.js +45 -38
- package/bin/deploy/to-hana/hdiDeployUtil.js +8 -9
- package/bin/deploy/to-hana/mtaUtil.js +13 -14
- package/bin/mtx/in-cds.js +3 -1
- package/bin/serve.js +1 -1
- package/bin/version.js +2 -1
- package/lib/compile/cds-compile.js +1 -0
- package/lib/compile/cdsc.js +1 -0
- package/lib/compile/etc/_localized.js +2 -2
- package/lib/compile/for/lean_drafts.js +83 -0
- package/lib/compile/for/nodejs.js +1 -0
- package/lib/compile/minify.js +2 -1
- package/lib/compile/parse.js +2 -1
- package/lib/compile/to/gql.js +1 -1
- package/lib/compile/to/sql.js +11 -1
- package/lib/core/entities.js +1 -1
- package/lib/core/index.js +8 -9
- package/lib/core/infer.js +1 -0
- package/lib/dbs/cds-deploy.js +97 -41
- package/lib/env/cds-env.js +9 -10
- package/lib/env/cds-requires.js +8 -2
- package/lib/env/defaults.js +0 -4
- package/lib/env/schemas/cds-rc.json +38 -0
- package/lib/ql/SELECT.js +10 -4
- package/lib/srv/bindings.js +1 -1
- package/lib/srv/factory.js +1 -1
- package/lib/srv/protocols/index.js +3 -1
- package/lib/srv/srv-methods.js +1 -1
- package/lib/utils/cds-utils.js +11 -0
- package/lib/utils/inflect.js +13 -12
- package/lib/utils/tar.js +53 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -2
- 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/delete.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -15
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/UriSyntaxError.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +6 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +1 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +4 -0
- package/libx/_runtime/cds-services/services/Service.js +23 -1
- package/libx/_runtime/cds-services/util/assert.js +0 -41
- package/libx/_runtime/common/composition/data.js +5 -1
- package/libx/_runtime/common/generic/auth/utils.js +3 -3
- package/libx/_runtime/common/generic/input.js +4 -24
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/utils/csn.js +21 -15
- package/libx/_runtime/common/utils/draft.js +2 -1
- package/libx/_runtime/common/utils/resolveView.js +25 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -1
- package/libx/_runtime/common/utils/rowUUIDGenerator.js +21 -0
- package/libx/_runtime/common/utils/templateProcessor.js +12 -15
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +23 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +29 -12
- package/libx/_runtime/db/generic/input.js +7 -13
- package/libx/_runtime/db/sql-builder/UpsertBuilder.js +47 -0
- package/libx/_runtime/db/sql-builder/index.js +2 -0
- package/libx/_runtime/db/sql-builder/sqlFactory.js +9 -0
- package/libx/_runtime/db/utils/columns.js +4 -2
- package/libx/_runtime/fiori/generic/read.js +1 -12
- package/libx/_runtime/fiori/lean-draft.js +657 -0
- package/libx/_runtime/fiori/utils/handler.js +1 -1
- package/libx/_runtime/hana/pool.js +16 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +2 -3
- package/libx/_runtime/messaging/outbox/utils.js +109 -70
- package/libx/_runtime/messaging/service.js +16 -7
- package/libx/_runtime/remote/Service.js +15 -2
- package/libx/_runtime/remote/utils/client.js +41 -11
- package/libx/_runtime/sqlite/Service.js +3 -0
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +56 -0
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +59 -0
- package/libx/_runtime/sqlite/customBuilder/index.js +5 -0
- package/libx/_runtime/sqlite/execute.js +1 -1
- package/libx/_runtime/types/api.js +2 -2
- package/libx/rest/RestAdapter.js +15 -13
- package/package.json +1 -1
- package/server.js +1 -0
package/lib/dbs/cds-deploy.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cds = require('../index'), { local, inspect } = cds.utils
|
|
1
|
+
const cds = require('../index'), { local, inspect } = cds.utils, { UPSERT } = cds.ql
|
|
2
2
|
const DEBUG = cds.debug('deploy')
|
|
3
3
|
|
|
4
4
|
const colors = !!process.stdout.isTTY && !!process.stderr.isTTY
|
|
@@ -14,7 +14,7 @@ const term = {
|
|
|
14
14
|
* deploy create tables and views in case of a SQL database, then fills
|
|
15
15
|
* in initial data, if present.
|
|
16
16
|
*/
|
|
17
|
-
exports = module.exports = function cds_deploy (model,options) { return {
|
|
17
|
+
exports = module.exports = function cds_deploy (model,options,csvs) { return {
|
|
18
18
|
|
|
19
19
|
/** @param {cds.Service} db */
|
|
20
20
|
async to (db, o = options || cds.options || {}) {
|
|
@@ -40,10 +40,10 @@ exports = module.exports = function cds_deploy (model,options) { return {
|
|
|
40
40
|
|
|
41
41
|
// create tables & views...
|
|
42
42
|
const any = await exports.create (db,model,o)
|
|
43
|
-
if (!any) return db
|
|
43
|
+
if (!any && !csvs) return db
|
|
44
44
|
|
|
45
45
|
// fill in initial data...
|
|
46
|
-
await exports.init (db,model, file => LOG(
|
|
46
|
+
await exports.init (db,model,o,csvs, file => LOG(
|
|
47
47
|
term.info(` > init from ${local(file)}`)
|
|
48
48
|
))
|
|
49
49
|
|
|
@@ -105,55 +105,113 @@ exports.exclude_external_entities_in = function (csn) { // NOSONAR
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
function getSqls(db, csn, o, beforeCsn) {
|
|
109
|
+
const schemaEvo = (db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto')
|
|
110
|
+
const creds = db.options?.credentials
|
|
111
|
+
const in_memory = (creds?.url || creds?.database) === ':memory:';
|
|
112
|
+
if (!in_memory && schemaEvo) {
|
|
113
|
+
const { afterImage: afterCsn, drops, createsAndAlters: creas } = cds.compile.to.sql.delta (csn, o, beforeCsn);
|
|
114
|
+
if(beforeCsn === undefined) {
|
|
115
|
+
// If this is the first deployment done with automatic schema evolution, generate everything as if it was a drop create
|
|
116
|
+
// but set the afterCsn in the db so we can calculate a delta going forward
|
|
117
|
+
return { afterCsn, drops: creas.map (each => {
|
|
118
|
+
let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) "?([^\s"(]+)/im) || []
|
|
119
|
+
return `DROP ${kind} IF EXISTS ${entity};`
|
|
120
|
+
}).reverse(), creas };
|
|
121
|
+
}
|
|
122
|
+
return { afterCsn, drops, creas };
|
|
123
|
+
} else {
|
|
124
|
+
const creas = cds.compile.to.sql(csn, o);
|
|
125
|
+
const drops = creas.map (each => {
|
|
113
126
|
let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) "?([^\s"(]+)/im) || []
|
|
114
127
|
return `DROP ${kind} IF EXISTS ${entity};`
|
|
115
|
-
}).reverse()
|
|
116
|
-
|
|
117
|
-
console.log(); for (let each of drops) console.log(each)
|
|
118
|
-
console.log(); for (let each of creates) console.log(each,'\n')
|
|
119
|
-
return
|
|
120
|
-
} else return db.run (async tx => {
|
|
121
|
-
await tx.run(drops)
|
|
122
|
-
await tx.run(creates)
|
|
123
|
-
return true
|
|
124
|
-
})
|
|
128
|
+
}).reverse();
|
|
129
|
+
return { afterCsn: {}, drops, creas };
|
|
125
130
|
}
|
|
126
|
-
else return db.deploy (csn,o)
|
|
127
131
|
}
|
|
128
132
|
|
|
133
|
+
exports.create = async function (db, csn=db.model, o) {
|
|
134
|
+
const schemaEvo = (db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto')
|
|
135
|
+
if(db.deploy && !schemaEvo) {
|
|
136
|
+
// reset CSN state saved in db - if there is any
|
|
137
|
+
await db.run('DROP table if exists cds_Model;');
|
|
138
|
+
return db.deploy(csn, o);
|
|
139
|
+
}
|
|
129
140
|
|
|
130
|
-
|
|
141
|
+
const in_memory = db.options?.credentials?.url === ':memory:';
|
|
142
|
+
let beforeCsn
|
|
143
|
+
if (!in_memory && schemaEvo) try {
|
|
144
|
+
const [{ csn }] = await db.read('cds.Model')
|
|
145
|
+
beforeCsn = JSON.parse(csn);
|
|
146
|
+
} catch(e) {
|
|
147
|
+
if (e.message.includes('no such table: cds_Model')) {
|
|
148
|
+
await db.run('CREATE table cds_Model ( csn CLOB )')
|
|
149
|
+
await db.insert({csn:''}).into('cds.Model')
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const { afterCsn, drops, creas } = getSqls(db, csn, o, beforeCsn);
|
|
154
|
+
|
|
155
|
+
if (!creas || creas.length === 0) return
|
|
156
|
+
if (o.dry) {
|
|
157
|
+
console.log(); for (let each of drops) console.log(each,'\n')
|
|
158
|
+
console.log(); for (let each of creas) console.log(each,'\n')
|
|
159
|
+
return
|
|
160
|
+
} else return db.run (async tx => {
|
|
161
|
+
await tx.run(drops)
|
|
162
|
+
await tx.run(creas)
|
|
163
|
+
if (!in_memory && schemaEvo) {
|
|
164
|
+
await tx.update('cds.Model').with({ csn: JSON.stringify(afterCsn) })
|
|
165
|
+
}
|
|
166
|
+
return true
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
exports.init = (db, csn=db.model, o, csvs, log=()=>{}) => db.run (async tx => {
|
|
131
172
|
const resources = await exports.resources(csn, {testdata: cds.env.features.test_data})
|
|
132
173
|
const inits=[]
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const INSERT_into = _from_csv_or_json [path.extname(file)]
|
|
141
|
-
const src = await read(file,'utf8'); if (!src) continue
|
|
142
|
-
const q = INSERT_into (e,src); if (!q) continue
|
|
174
|
+
const schemaEvo = (db.options?.schema_evolution === 'auto' || o?.schema_evolution === 'auto')
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if (csvs) {
|
|
178
|
+
const ccsn = cds.compile.for['nodejs'](csn) // compile to calculate keys for newly added entities
|
|
179
|
+
for(let [e,src] of Object.entries(csvs)) {
|
|
180
|
+
const q = INSERT_from_csv (e,src,schemaEvo); if (!q) continue
|
|
143
181
|
if (db.kind === 'better-sqlite') _add_missing_pks2(q)
|
|
144
|
-
|
|
182
|
+
q._target = ccsn.definitions[e]
|
|
145
183
|
inits.push (tx.run(q) .catch (e => {
|
|
146
184
|
throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ inspect(q) })
|
|
147
185
|
}))
|
|
148
186
|
}
|
|
187
|
+
} else {
|
|
188
|
+
for (let [file,e] of Object.entries(resources)) {
|
|
189
|
+
if (e === '*') { // init.js/ts
|
|
190
|
+
let x = await cds.utils._import(file); if (!x) continue
|
|
191
|
+
if (x.default) x = x.default // default ESM export
|
|
192
|
+
inits.push (!x.then && typeof x === 'function' ? x(db,csn) : x)
|
|
193
|
+
log (file)
|
|
194
|
+
} else { // from .csv or .json
|
|
195
|
+
const INSERT_into = _from_csv_or_json [path.extname(file)]
|
|
196
|
+
const src = await read(file,'utf8'); if (!src) continue
|
|
197
|
+
const q = INSERT_into (e,src,schemaEvo); if (!q) continue
|
|
198
|
+
if (db.kind === 'better-sqlite') _add_missing_pks2(q)
|
|
199
|
+
if (cds.requires['cds.xt.ModelProviderService']?.kind === 'in-sidecar') q._target = csn.definitions[e]
|
|
200
|
+
log (file,e)
|
|
201
|
+
inits.push (tx.run(q) .catch (e => {
|
|
202
|
+
throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ inspect(q) })
|
|
203
|
+
}))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
149
206
|
}
|
|
207
|
+
|
|
150
208
|
await Promise.all (inits)
|
|
151
209
|
|
|
152
210
|
function _add_missing_pks2 (q) {
|
|
153
|
-
const {columns,rows} = q.INSERT // REVISIT: .entries are covered by current runtime. Should eventually also be handled here, as we likely don't do so in new db services
|
|
211
|
+
const {columns,rows} = q.UPSERT || q.INSERT // REVISIT: .entries are covered by current runtime. Should eventually also be handled here, as we likely don't do so in new db services
|
|
154
212
|
if (columns) {
|
|
155
213
|
const entity = csn.definitions[q._target.name], {uuid} = cds.utils
|
|
156
|
-
for (let k in entity.keys) if (!columns.includes(k)) {
|
|
214
|
+
for (let k in entity.keys) if (!columns.includes(k) && !entity.keys[k].isAssociation) {
|
|
157
215
|
columns.push(k)
|
|
158
216
|
const t = entity.keys[k]._type, pk = t === 'cds.UUID' ? uuid : index => index+1
|
|
159
217
|
rows.forEach ((row,index) => row.push(pk(index)))
|
|
@@ -216,7 +274,7 @@ const _entity4 = (file,csn) => {
|
|
|
216
274
|
if (!entity) {
|
|
217
275
|
if (/(.+)[._]texts_?/.test(name)) { // 'Books.texts', 'Books.texts_de'
|
|
218
276
|
const base = csn.definitions [RegExp.$1]
|
|
219
|
-
return base && _entity4 (base.elements.texts.target,csn)
|
|
277
|
+
return base?.elements?.texts && _entity4 (base.elements.texts.target, csn)
|
|
220
278
|
}
|
|
221
279
|
else return DEBUG && DEBUG (`warning: ${name} not in model`)
|
|
222
280
|
}
|
|
@@ -228,16 +286,14 @@ const _entity4 = (file,csn) => {
|
|
|
228
286
|
return entity.name ? entity : { name, __proto__:entity }
|
|
229
287
|
}
|
|
230
288
|
|
|
231
|
-
const INSERT_from_csv = (entity, csv) => {
|
|
289
|
+
const INSERT_from_csv = (entity, csv, schemaEvo) => {
|
|
232
290
|
let [ cols, ...rows ] = cds.parse.csv (csv)
|
|
233
|
-
if (rows.length > 0) return INSERT.into (entity) .columns (cols) .rows (rows)
|
|
234
|
-
// if (rows.length > 0) return UPSERT.into (entity) .columns (cols) .rows (rows)
|
|
291
|
+
if (rows.length > 0) return (schemaEvo ? UPSERT : INSERT).into (entity) .columns (cols) .rows (rows)
|
|
235
292
|
}
|
|
236
293
|
|
|
237
|
-
const INSERT_from_json = (entity, json) => {
|
|
294
|
+
const INSERT_from_json = (entity, json, schemaEvo) => {
|
|
238
295
|
let records = JSON.parse (json)
|
|
239
|
-
if (records.length > 0) return INSERT.into (entity) .entries (records)
|
|
240
|
-
// if (records.length > 0) return UPSERT.into (entity) .entries (records)
|
|
296
|
+
if (records.length > 0) return (schemaEvo ? UPSERT : INSERT).into (entity) .entries (records)
|
|
241
297
|
}
|
|
242
298
|
|
|
243
299
|
const _from_csv_or_json = { '.json': INSERT_from_json, '.csv': INSERT_from_csv, }
|
package/lib/env/cds-env.js
CHANGED
|
@@ -64,19 +64,16 @@ class Config {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// 4.
|
|
68
|
-
this._link_required_services()
|
|
69
|
-
|
|
70
|
-
// 5. add process env
|
|
67
|
+
// 4. add process env before linking to allow things like CDS_requires_db=sql
|
|
71
68
|
this._add_process_env(_context, _home)
|
|
72
69
|
|
|
73
|
-
//
|
|
70
|
+
// 5. link dependent services
|
|
74
71
|
this._link_required_services()
|
|
75
72
|
|
|
76
|
-
//
|
|
73
|
+
// 6. complete service configurations from cloud service bindings
|
|
77
74
|
this._add_cloud_service_bindings(process.env)
|
|
78
75
|
|
|
79
|
-
//
|
|
76
|
+
// 7. Add compatibility for mtx
|
|
80
77
|
if (this.requires && this.requires.db) {
|
|
81
78
|
if (this.requires.multitenancy !== undefined) {
|
|
82
79
|
Object.defineProperty(this.requires.db, 'multiTenant', { value: !!this.requires.multitenancy })
|
|
@@ -84,7 +81,7 @@ class Config {
|
|
|
84
81
|
else if (this.requires.db.multiTenant !== undefined) this.requires.multitenancy = this.requires.db.multiTenant
|
|
85
82
|
}
|
|
86
83
|
|
|
87
|
-
//
|
|
84
|
+
// 8. apply presets
|
|
88
85
|
presets (this)
|
|
89
86
|
|
|
90
87
|
// Only if feature is enabled
|
|
@@ -230,10 +227,12 @@ class Config {
|
|
|
230
227
|
if (!config) config = {}
|
|
231
228
|
const pref_ = RegExp('^'+prefix+'[._]','i')
|
|
232
229
|
for (let p in env) if (!(p in my) && pref_.test(p)) {
|
|
233
|
-
const
|
|
234
|
-
const
|
|
230
|
+
const pEsc = p.replace(/__/g, '!!') // escaping of _ by __ : protect __ so that it's not split below
|
|
231
|
+
const key = /[a-z]/.test(pEsc) ? pEsc : pEsc.toLowerCase() //> CDS_FOO_BAR -> cds_foo_bar
|
|
232
|
+
let path = key.slice(prefix.length+1) .split (key[prefix.length]) //> ['foo','bar']
|
|
235
233
|
for (var o=config,next;;) {
|
|
236
234
|
next = path.shift()
|
|
235
|
+
next = next.replace(/!!/g, '_') // undo !! protection and reduce __ to _
|
|
237
236
|
if (!path.length) break
|
|
238
237
|
if (!path[0]) next = next+'-'+path.shift()+path.shift() // foo__bar -> foo-bar
|
|
239
238
|
o = o[next] || (o[next] = {})
|
package/lib/env/cds-requires.js
CHANGED
|
@@ -7,6 +7,10 @@ exports = module.exports = {
|
|
|
7
7
|
middlewares: true
|
|
8
8
|
},
|
|
9
9
|
|
|
10
|
+
"[schevo]": {
|
|
11
|
+
db: { schema_evolution: 'auto' },
|
|
12
|
+
},
|
|
13
|
+
|
|
10
14
|
db: undefined,
|
|
11
15
|
messaging: undefined,
|
|
12
16
|
multitenancy: undefined,
|
|
@@ -251,8 +255,10 @@ require = (id) => { // eslint-disable-line no-global-assign
|
|
|
251
255
|
}
|
|
252
256
|
|
|
253
257
|
// REVISIT: we should have a real modular plugin technique for cds.env
|
|
254
|
-
|
|
255
|
-
const _mtxs = !
|
|
258
|
+
const utils = require ('../utils/cds-utils')
|
|
259
|
+
const _mtxs = !utils._oldMtx()
|
|
260
|
+
// eslint-disable-next-line cds/no-missing-dependencies
|
|
261
|
+
&& require('@sap/cds-mtxs/lib/env-requires') || {
|
|
256
262
|
"toggles": {
|
|
257
263
|
model: "@sap/cds/srv/mtx"
|
|
258
264
|
},
|
package/lib/env/defaults.js
CHANGED
|
@@ -12,10 +12,6 @@ module.exports = {
|
|
|
12
12
|
},
|
|
13
13
|
|
|
14
14
|
features: {
|
|
15
|
-
"[schevo]": {
|
|
16
|
-
schema_evolution: true,
|
|
17
|
-
},
|
|
18
|
-
schema_evolution: false,
|
|
19
15
|
folders: 'fts/*', // where to find feature toggles -> switch on by default when released
|
|
20
16
|
cls: major > 12 || major == 12 && minor >= 18,
|
|
21
17
|
live_reload: !production,
|
|
@@ -237,6 +237,10 @@
|
|
|
237
237
|
}
|
|
238
238
|
]
|
|
239
239
|
},
|
|
240
|
+
"middlewares": {
|
|
241
|
+
"type": "boolean",
|
|
242
|
+
"description": "Enables new middlewares support (experimental)."
|
|
243
|
+
},
|
|
240
244
|
"multitenancy": {
|
|
241
245
|
"type": "boolean",
|
|
242
246
|
"description": "Shortcut to enable multitenancy."
|
|
@@ -360,6 +364,40 @@
|
|
|
360
364
|
]
|
|
361
365
|
}
|
|
362
366
|
}
|
|
367
|
+
},
|
|
368
|
+
"protocols": {
|
|
369
|
+
"type": "object",
|
|
370
|
+
"description": "List protocols to enable in addition to the default protocol adaptors.",
|
|
371
|
+
"additionalProperties": true,
|
|
372
|
+
"patternProperties": {
|
|
373
|
+
".+": {
|
|
374
|
+
"oneOf": [
|
|
375
|
+
{
|
|
376
|
+
"type": "boolean",
|
|
377
|
+
"description": "Enables a built-in protocol adapter with defaults"
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"type": "string",
|
|
381
|
+
"description": "The endpoint to serve this protocol at."
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"type": "object",
|
|
385
|
+
"default": {},
|
|
386
|
+
"additionalProperties": true,
|
|
387
|
+
"properties": {
|
|
388
|
+
"path": {
|
|
389
|
+
"type": "string",
|
|
390
|
+
"description": "The endpoint to serve this protocol at."
|
|
391
|
+
},
|
|
392
|
+
"impl": {
|
|
393
|
+
"type": "string",
|
|
394
|
+
"description": "The module pathname of the protocol adapter implementation."
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
}
|
|
400
|
+
}
|
|
363
401
|
}
|
|
364
402
|
},
|
|
365
403
|
"$defs": {
|
package/lib/ql/SELECT.js
CHANGED
|
@@ -6,16 +6,22 @@ module.exports = class Query extends Whereable {
|
|
|
6
6
|
static _api() {
|
|
7
7
|
const $ = Object.assign
|
|
8
8
|
return $((..._) => new this()._select_or_from(..._), {
|
|
9
|
-
|
|
10
|
-
columns: (..._) => new this({
|
|
11
|
-
from: (..._) => new this({
|
|
9
|
+
localized: $((...x) => new this({localized:true})._select_or_from(...x),{
|
|
10
|
+
columns: (..._) => new this({localized:true}).columns(..._),
|
|
11
|
+
from: (..._) => new this({localized:true}).from(..._),
|
|
12
12
|
}),
|
|
13
13
|
distinct: $((...x) => new this({distinct:true})._select_or_from(...x),{
|
|
14
14
|
columns: (..._) => new this({distinct:true}).columns(..._),
|
|
15
15
|
from: (..._) => new this({distinct:true}).from(..._),
|
|
16
16
|
}),
|
|
17
|
+
one: $((...x) => new this({one:true})._select_or_from(...x),{
|
|
18
|
+
columns: (..._) => new this({one:true}).columns(..._),
|
|
19
|
+
from: (..._) => new this({one:true}).from(..._),
|
|
20
|
+
}),
|
|
21
|
+
from: $((..._) => new this().from(..._), {
|
|
22
|
+
localized: (..._) => new this({localized:true}).from(..._)
|
|
23
|
+
}),
|
|
17
24
|
columns: (..._) => new this().columns(..._),
|
|
18
|
-
from: (..._) => new this().from(..._),
|
|
19
25
|
})
|
|
20
26
|
}
|
|
21
27
|
|
package/lib/srv/bindings.js
CHANGED
|
@@ -12,7 +12,7 @@ module.exports = class Bindings {
|
|
|
12
12
|
static get registry(){ return registry }
|
|
13
13
|
|
|
14
14
|
static then(r,e) {
|
|
15
|
-
const LOG = cds.log('serve', { prefix:'cds' })
|
|
15
|
+
const LOG = cds.log('cds.serve', { prefix:'cds' })
|
|
16
16
|
const bindings = new Bindings
|
|
17
17
|
cds.prependOnceListener ('connect', ()=> LOG._info && LOG.info ('connect using bindings from:', { registry }))
|
|
18
18
|
cds.once('listening', ({url})=> bindings.export (cds.service.providers, url))
|
package/lib/srv/factory.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const cds = require('..'), { path, isfile } = cds.utils
|
|
2
2
|
const paths = Array.from (new Set ([ cds.root, ...require.resolve.paths('x') ]))
|
|
3
|
-
const DEBUG = cds.debug('
|
|
3
|
+
const DEBUG = cds.debug('cds.service.factory',); DEBUG && DEBUG ({ 'cds.root':cds.root, paths })
|
|
4
4
|
|
|
5
5
|
/** @typedef {import('./srv-api')} Service @type { (()=>Service) & (new()=>Service) } */
|
|
6
6
|
const ServiceFactory = function (name, model, options) { //NOSONAR
|
|
@@ -10,7 +10,9 @@ class ProtocolAdapter {
|
|
|
10
10
|
for (let [k,o] of Object.entries(protocols)) if (typeof o === 'string') protocols[k] = {path:o}
|
|
11
11
|
if (!protocols.odata) protocols.odata = { impl: join(__dirname,'odata-v4') }
|
|
12
12
|
if (!protocols.rest) protocols.rest = { impl: join(__dirname,'rest') }
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
// odata must always be first for fallback
|
|
15
|
+
return this.protocols = { odata: protocols.odata, ...protocols }
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
package/lib/srv/srv-methods.js
CHANGED
package/lib/utils/cds-utils.js
CHANGED
|
@@ -176,6 +176,17 @@ exports.find = function find (base, patterns='*', filter=()=>true) {
|
|
|
176
176
|
return files
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
// TODO find a better place
|
|
180
|
+
exports._oldMtx = function _oldMtx() {
|
|
181
|
+
function _hasMtxDependency() {
|
|
182
|
+
try {
|
|
183
|
+
return require(join(cds.root, 'package.json'))?.dependencies?.['@sap/cds-mtx']
|
|
184
|
+
} catch(e) {
|
|
185
|
+
return false
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return (_hasMtxDependency() || process.env.OLD_MTX?.toLowerCase() === 'true') ?? false
|
|
189
|
+
}
|
|
179
190
|
|
|
180
191
|
/**
|
|
181
192
|
* Internal utility to load a file through ESM or CommonJs. TODO find a better place.
|
package/lib/utils/inflect.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
const last = /\w+$/
|
|
2
1
|
|
|
3
|
-
|
|
2
|
+
this.singular4 = (dn,stripped) => {
|
|
4
3
|
let n = dn.name || dn; if (stripped) n = n.match(last)[0]
|
|
5
4
|
return dn['@singular'] || (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
/species|news$/i.test(n) ? n :
|
|
6
|
+
/ess$/.test(n) ? n : // Address
|
|
7
|
+
/ees$/.test(n) ? n.slice(0, -1) : // Employees --> Employee
|
|
8
|
+
/[sz]es$/.test(n) ? n.slice(0, -2) :
|
|
9
|
+
/[^aeiou]ies$/.test(n) ? n.slice(0, -3) + 'y' : // Deliveries --> Delivery
|
|
10
|
+
/s$/.test(n) ? n.slice(0, -1) :
|
|
12
11
|
n
|
|
13
12
|
)
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
this.plural4 = (dn,stripped) => {
|
|
17
16
|
let n = dn.name || dn; if (stripped) n = n.match(last)[0]
|
|
18
17
|
return dn['@plural'] || (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
/analysis|status|species|sheep|news$/i.test(n) ? n :
|
|
19
|
+
/[^aeiou]y$/.test(n) ? n.slice(0,-1) + 'ies' :
|
|
20
|
+
/(s|x|z|ch|sh)$/.test(n) ? n + 'es' :
|
|
22
21
|
n + 's'
|
|
23
22
|
)
|
|
24
23
|
}
|
|
24
|
+
|
|
25
|
+
const last = /\w+$/
|
package/lib/utils/tar.js
CHANGED
|
@@ -5,7 +5,7 @@ const spawn = /\btar\b/.test(process.env.DEBUG) ? (cmd, args, options) => {
|
|
|
5
5
|
return child_process.spawn(cmd, args, options)
|
|
6
6
|
} : child_process.spawn
|
|
7
7
|
|
|
8
|
-
const cds = require('../index'), { fs, path, mkdirp } = cds.utils
|
|
8
|
+
const cds = require('../index'), { fs, path, mkdirp, exists, rimraf } = cds.utils
|
|
9
9
|
const _resolve = (...x) => path.resolve (cds.root,...x)
|
|
10
10
|
|
|
11
11
|
// tar does not work properly on Windows (by npm/jest tests) w/o this change
|
|
@@ -15,6 +15,21 @@ const win = path => {
|
|
|
15
15
|
if (Array.isArray(path)) return path.map(el => win(el))
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// Copy files to temp dir on Windows and pack temp dir.
|
|
19
|
+
// cli tar has a size limit on Windows.
|
|
20
|
+
const createTemp = async (root, files) => {
|
|
21
|
+
const temp = await fs.promises.mkdtemp(`${fs.realpathSync(require('os').tmpdir())}${path.sep}tar-`)
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const fname = path.relative(root, file)
|
|
24
|
+
const destination = path.join(temp, fname)
|
|
25
|
+
const dirname = path.dirname(destination)
|
|
26
|
+
if (!await exists(dirname)) await fs.promises.mkdir(dirname, { recursive: true })
|
|
27
|
+
await fs.promises.copyFile(file, destination)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return temp
|
|
31
|
+
}
|
|
32
|
+
|
|
18
33
|
/**
|
|
19
34
|
* Creates a tar archive, to an in-memory Buffer, or piped to write stream or file.
|
|
20
35
|
* @example ```js
|
|
@@ -39,15 +54,31 @@ const win = path => {
|
|
|
39
54
|
* - `.then()` collects the tar output into an in-memory `Buffer`
|
|
40
55
|
* - `.to()` is a convenient shortcut to pipe the output into a write stream
|
|
41
56
|
*/
|
|
42
|
-
exports.create = (dir='.', ...args) => {
|
|
57
|
+
exports.create = async (dir='.', ...args) => {
|
|
43
58
|
|
|
44
59
|
if (typeof dir === 'string') dir = _resolve(dir)
|
|
45
60
|
if (Array.isArray(dir)) [ dir, ...args ] = [ cds.root, dir, ...args ]
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
|
|
62
|
+
let c, temp
|
|
63
|
+
if (process.platform === 'win32') {
|
|
64
|
+
const spawnDir = (dir, args) => {
|
|
65
|
+
if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', win(dir), ...win(args)])
|
|
66
|
+
else return spawn ('tar', ['cf', '-', '-C', win(dir), ...win(args)])
|
|
67
|
+
}
|
|
68
|
+
args.push('.')
|
|
69
|
+
if (Array.isArray(args[0])) {
|
|
70
|
+
temp = await createTemp(dir, args[0])
|
|
71
|
+
args.shift()
|
|
72
|
+
c = spawnDir(temp, args)
|
|
73
|
+
} else {
|
|
74
|
+
c = spawnDir(dir, args)
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
if (Array.isArray(args[0])) args.push (...args.shift().map (f => path.isAbsolute(f) ? path.relative(dir,f) : f))
|
|
78
|
+
else args.push('.')
|
|
79
|
+
|
|
80
|
+
c = spawn ('tar', ['c', '-C', dir, ...args])
|
|
81
|
+
}
|
|
51
82
|
|
|
52
83
|
return {__proto__:c, // returning a thenable + fluent ChildProcess...
|
|
53
84
|
|
|
@@ -57,9 +88,21 @@ exports.create = (dir='.', ...args) => {
|
|
|
57
88
|
* @example const buffer = await tar.c('src/dir')
|
|
58
89
|
*/
|
|
59
90
|
then (r,e) {
|
|
60
|
-
const bb=[]
|
|
61
|
-
|
|
91
|
+
const bb=[]
|
|
92
|
+
const eb=[]
|
|
93
|
+
c.stdout.on('data', b => bb.push(b))
|
|
94
|
+
c.stderr.on('data', b => eb.push(b))
|
|
95
|
+
c.on('close', (code) => {
|
|
96
|
+
if (code === 0) {
|
|
97
|
+
return r(Buffer.concat(bb))
|
|
98
|
+
}
|
|
99
|
+
e(new Error('tar: ' + Buffer.concat(eb)))
|
|
100
|
+
})
|
|
62
101
|
c.on('error', e)
|
|
102
|
+
if (process.platform === 'win32') {
|
|
103
|
+
c.on('close', async () => temp && exists(temp) && await rimraf(temp))
|
|
104
|
+
c.on('error', async () => temp && exists(temp) && await rimraf(temp))
|
|
105
|
+
}
|
|
63
106
|
},
|
|
64
107
|
|
|
65
108
|
/**
|
|
@@ -107,7 +150,7 @@ exports.extract = (archive, ...args) => ({
|
|
|
107
150
|
to (...dest) {
|
|
108
151
|
if (typeof dest === 'string') dest = _resolve(...dest)
|
|
109
152
|
const input = typeof archive !== 'string' || archive == '-' ? '-' : _resolve(archive)
|
|
110
|
-
const x = spawn(
|
|
153
|
+
const x = spawn('tar', ['xf', win(input), '-C', win(dest), ...args])
|
|
111
154
|
if (archive === '-') return x.stdin
|
|
112
155
|
if (Buffer.isBuffer(archive)) archive = require('stream').Readable.from (archive)
|
|
113
156
|
if (typeof archive !== 'string') archive.pipe (x.stdin)
|
|
@@ -162,10 +162,10 @@ class ODataRequest extends cds.Request {
|
|
|
162
162
|
* super
|
|
163
163
|
*/
|
|
164
164
|
const { user } = req
|
|
165
|
-
|
|
165
|
+
const tenant = req.tenant || user?.tenant
|
|
166
166
|
// REVISIT: public API for query options (express style req.query already in use)?
|
|
167
167
|
const _queryOptions = odataReq.getQueryOptions()
|
|
168
|
-
super({ event, target, data, query, user, method, headers, req, res, _queryOptions })
|
|
168
|
+
super({ event, target, data, query, user, method, headers, req, res, _queryOptions, tenant })
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/*
|
|
@@ -82,7 +82,7 @@ const action = service => {
|
|
|
82
82
|
await tx.rollback(e).catch(() => {})
|
|
83
83
|
}
|
|
84
84
|
} finally {
|
|
85
|
-
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.
|
|
85
|
+
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
86
86
|
|
|
87
87
|
if (err) next(err)
|
|
88
88
|
else next(null, toODataResult(result, req))
|
|
@@ -68,7 +68,7 @@ const create = service => {
|
|
|
68
68
|
await tx.rollback(e).catch(() => {})
|
|
69
69
|
}
|
|
70
70
|
} finally {
|
|
71
|
-
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.
|
|
71
|
+
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
72
72
|
|
|
73
73
|
if (err) next(err)
|
|
74
74
|
else next(null, toODataResult(result, req))
|
|
@@ -49,7 +49,7 @@ const del = service => {
|
|
|
49
49
|
await tx.rollback(e).catch(() => {})
|
|
50
50
|
}
|
|
51
51
|
} finally {
|
|
52
|
-
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.
|
|
52
|
+
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
53
53
|
|
|
54
54
|
if (err) next(err)
|
|
55
55
|
else next(null, null)
|
|
@@ -20,20 +20,6 @@ const metadata = service => {
|
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
22
|
const { 'cds.xt.ModelProviderService': mps } = cds.services
|
|
23
|
-
// REVISIT: The following block should replaced with the one commented bellow after the next release of cds-mtxs.
|
|
24
|
-
// Currently lkg tests fail w/o the try-catch.
|
|
25
|
-
let edmx
|
|
26
|
-
try {
|
|
27
|
-
if (mps) edmx = await mps.getEdmx({ tenant, model: service.model, service: service.definition.name, locale })
|
|
28
|
-
// eslint-disable-next-line no-empty
|
|
29
|
-
} catch (_) {}
|
|
30
|
-
if (!edmx)
|
|
31
|
-
edmx = cds.localize(
|
|
32
|
-
service.model,
|
|
33
|
-
locale,
|
|
34
|
-
cds.compile.to.edmx(service.model, { service: service.definition.name })
|
|
35
|
-
)
|
|
36
|
-
/*
|
|
37
23
|
let edmx = mps
|
|
38
24
|
? await mps.getEdmx({ tenant, model: service.model, service: service.definition.name, locale })
|
|
39
25
|
: cds.localize(
|
|
@@ -41,7 +27,7 @@ const metadata = service => {
|
|
|
41
27
|
locale,
|
|
42
28
|
// REVISIT: we could cache this in model._cached
|
|
43
29
|
cds.compile.to.edmx(service.model, { service: service.definition.name })
|
|
44
|
-
)
|
|
30
|
+
)
|
|
45
31
|
return next(null, toODataResult(edmx))
|
|
46
32
|
} catch (e) {
|
|
47
33
|
if (LOG._error) {
|
|
@@ -491,7 +491,7 @@ const read = service => {
|
|
|
491
491
|
await tx.rollback(e).catch(() => {})
|
|
492
492
|
}
|
|
493
493
|
} finally {
|
|
494
|
-
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.
|
|
494
|
+
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
495
495
|
|
|
496
496
|
if (err) next(err)
|
|
497
497
|
else next(null, result, additional)
|