@sap/cds-dk
Advanced tools
Comparing version 8.0.3 to 8.1.0
@@ -49,3 +49,3 @@ const term = require('../lib/util/term') | ||
Bind to a given application (Cloud Foundry only). | ||
Bind services of a given application (Cloud Foundry only). | ||
@@ -168,7 +168,7 @@ *--no-create-service-key* | ||
if (options.targets.length >= 2 && options.serviceArg) { | ||
throw `Service argument cannot be specified together with multiple targets (${term.bold('--to')}) services. Use one service per call or omit the service argument.`; | ||
throw `Service argument cannot be specified together with multiple target (${term.bold('--to')}) services. Use one service per call or omit the service argument.`; | ||
} | ||
if (options.targets.length >= 2 && options.kind) { | ||
throw `The option '--kind' cannot be specified together with multiple targets (${term.bold('--to')}) services. Use one service per call or omit the ${term.bold('--kind')} option.`; | ||
throw `The option '--kind' cannot be specified together with multiple target (${term.bold('--to')}) services. Use one service per call or omit the ${term.bold('--kind')} option.`; | ||
} | ||
@@ -175,0 +175,0 @@ |
@@ -58,4 +58,11 @@ #!/usr/bin/env node | ||
const unknown = new Set | ||
const args = this.args(task, argv, unknown) | ||
// REVISIT: Have to load plugins to allow for plugin-contributed flags! | ||
if (cmd === 'add') { | ||
cds.cli = { command: 'add' } | ||
await cds.plugins | ||
for (const [, Plugin] of cds.add.registered) { | ||
task.options.push(...Object.keys((new Plugin).options()).map(key => '--'+key)) | ||
} | ||
} | ||
const args = this.args(task, argv) | ||
args[0].push(...appendArgs) | ||
@@ -68,13 +75,2 @@ cds.cli = { | ||
if (cmd === 'add') { // cds add plugins allow additional custom options | ||
await cds.plugins | ||
for (const [, Plugin] of cds.add.registered) { | ||
const options = Object.keys((new Plugin).options()).map(key => '--'+key) | ||
task.options.push(options) | ||
options.forEach(o => unknown.delete(o)) | ||
} | ||
} | ||
if (unknown.size === 1) throw 'Invalid option: ' + Array.from(unknown)[0] | ||
if (unknown.size > 1) throw 'Invalid options: ' + Array.from(unknown).join(', ') | ||
if (args[1]?.['resolve-bindings']) await _resolveBindings({ silent: cmd === 'env' }) | ||
@@ -111,3 +107,3 @@ | ||
// TODO replace w/ common arg parser from node | ||
args (task, argv, unknown) { | ||
args (task, argv) { | ||
@@ -126,3 +122,3 @@ const { options:o=[], flags:f=[], shortcuts:s=[] } = task | ||
else if (_global.test(a)) add_global(a, _flags[a] || argv[++i]) | ||
else unknown.add(a) | ||
else throw 'Invalid option: '+ a | ||
} | ||
@@ -129,0 +125,0 @@ // consistent production setting for NODE_ENV and CDS_ENV |
@@ -64,6 +64,2 @@ const { join } = require('path') | ||
if (previousWord === '--add') { | ||
return util.completionFs.readTemplates(argv); | ||
} | ||
if (previousWord === '--java:package') { | ||
@@ -75,4 +71,5 @@ const excludes = ['_merging']; | ||
if (!currentWord && init.options?.includes(previousWord)) { | ||
return []; | ||
const templates = await util.completionFs.readTemplates(argv); | ||
if (previousWord === '--add' || argv.includes('--add') && previousWord.endsWith(',')) { | ||
return templates; | ||
} | ||
@@ -79,0 +76,0 @@ |
@@ -64,3 +64,4 @@ const { join, resolve } = require ('path') | ||
function info({ root:cwd=process.cwd() }) { | ||
const env = require('../lib').env.for('cds', cwd) | ||
const cds = require('../lib') | ||
const env = cds.env.for('cds', cwd) | ||
env.cli ??= {}; env.cli.version ??= {} | ||
@@ -78,6 +79,7 @@ const javaInfo = !env.cli.version.skipjava && env['project-nature'] === 'java' | ||
..._versions_from(_pkg_path('@sap/cds', __dirname)), // @sap/cds from here | ||
..._versions_from(cwd), // ... or from local app dependencies | ||
..._versions_from(_pkg_path('@sap/cds', cds.home)), // ... or local @sap/cds | ||
..._versions_from(cwd), // ... more from local app dependencies | ||
..._versions_from(_pkg_path('@sap/cds-compiler', cwd, __dirname)), // compiler is no direct dependency, so list it explicitly | ||
'Node.js': process.version, | ||
'home': require('../lib/cds').home, // effective (local) cds | ||
'home': cds.home, // effective cds home | ||
} | ||
@@ -84,0 +86,0 @@ // sort Node entries |
@@ -29,3 +29,3 @@ const fs = require('fs') | ||
dest: this.task.dest, | ||
hana: [] | ||
hana: new Set() | ||
} | ||
@@ -232,3 +232,2 @@ } | ||
const promises = [] | ||
const relDest = path.relative(this.task.dest, this.task.options.compileDest) | ||
const options = { ...this.options(), messages: this.messages, dirs: csvDirs, baseDir: this.task.options.compileDest } | ||
@@ -245,3 +244,3 @@ | ||
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) { | ||
this._result.hana.push(path.join(relDest, file)) | ||
this._result.hana.add(path.relative(this.task.dest, tableDataPath)) | ||
} | ||
@@ -302,3 +301,3 @@ promises.push(this.write(tableData).to(tableDataPath)) | ||
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) { | ||
this._result.hana.push(path.join(relDest, file)) | ||
this._result.hana.add(path.join(relDest, file)) | ||
} | ||
@@ -352,3 +351,3 @@ promises.push(this.write(content).to(path.join(this.task.options.compileDest, file))) | ||
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) { | ||
this._result.hana.push(path.join("src", fileName)) | ||
this._result.hana.add(path.join("src", fileName)) | ||
} | ||
@@ -362,3 +361,3 @@ promises.push(this.write(content).to(path.join(dbSrcDir, fileName))) | ||
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) { | ||
this._result.hana.push(path.join(relDestDir, fileName)) | ||
this._result.hana.add(path.join(relDestDir, fileName)) | ||
} | ||
@@ -365,0 +364,0 @@ promises.push(this.write(content).to(path.join(this.task.options.compileDest, fileName))) |
@@ -48,3 +48,3 @@ const path = require('path') | ||
const destSidecar = this.task.dest | ||
const destSidecarSrc = path.join(destSidecar, cds.env.folders.srv) | ||
const destSidecarSrc = path.join(destSidecar, sidecarEnv.folders.srv) | ||
const model = this._compileSidecarSync(sidecarEnv) | ||
@@ -133,4 +133,11 @@ await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME)) | ||
// synchronous compilation | ||
return cds.load(modelFilePaths, { sync: true, ...this.options() }) | ||
// using synchronous compilation as cds.root and cds.env have been switched to mtx/sidecar | ||
// using different messages array ensures that cds compilation doesn't fail because of | ||
// existing compilation error messages that have been downgraded to warnings | ||
// see cap/issues/issues/16401#issuecomment-7205397 | ||
const messages = [] | ||
const model = cds.load(modelFilePaths, { sync: true, messages }) | ||
messages.forEach(msg => this.messages.push(msg)) | ||
return model | ||
} finally { | ||
@@ -137,0 +144,0 @@ // restore project scope |
@@ -5,8 +5,9 @@ const cds = require('../cds') | ||
options = Object.assign({ | ||
assocnames: false, // include association names | ||
elements : false, // include all elements { falsy | 'all'|true | 'keys' } | ||
min : false, // minify the model, i.e. remove unused definitions, available in CLI | ||
namespaces: true, // group entities by namespace/service | ||
queries : false, // include relations for queries | ||
service : undefined // filter service by name, available in CLI | ||
assocnames: false, // include association names | ||
elements : false, // include all elements { falsy | 'all'|true | 'keys' } | ||
min : false, // minify the model, i.e. remove unused definitions, available in CLI | ||
namespaces: true, // group entities by namespace/service | ||
queries : false, // include relations for queries | ||
service : undefined, // filter service by name, available in CLI | ||
direction : undefined // direction of the graph | ||
}, cds.env.mermaid ?? {}, options) | ||
@@ -31,2 +32,3 @@ | ||
let diag = '' | ||
const direction = options.direction ? ` direction ${options.direction}\n` : '' | ||
@@ -61,4 +63,6 @@ // if requested, filter by one service | ||
// create namespace groups and classes | ||
Object.keys(namespaces).forEach(ns => { | ||
if (!diag.length) diag = `classDiagram\n` | ||
Object.keys(namespaces) | ||
.filter (ns => namespaces[ns].length) // empty namespaces can cause rendering errors, so skip them | ||
.forEach(ns => { | ||
if (!diag.length) diag = `classDiagram\n${direction}` | ||
diag += ` namespace ${ns.replaceAll('.','_')} {\n` | ||
@@ -71,3 +75,3 @@ namespaces[ns].forEach (e => { | ||
diag += ` {\n` | ||
e.elements.forEach (el => { | ||
e.elements?.forEach (el => { | ||
if ((elemOpts === 'all' || elemOpts === true) || (el.key && elemOpts == 'keys')) { | ||
@@ -92,4 +96,4 @@ diag += ` +${el._type?.replace('cds.','')} ${el.name}` | ||
entities.forEach(e => { | ||
if (!diag.length) diag = `classDiagram\n` | ||
e.elements.forEach (el => { | ||
if (!diag.length) diag = `classDiagram\n${direction}` | ||
e.elements?.forEach (el => { | ||
if (el instanceof cds.Association) { | ||
@@ -108,3 +112,5 @@ if (el.target.endsWith('.texts')) return | ||
if (e.query && options.queries) { | ||
diag += ` \`${e.name}\` ..> \`${e.query.target.name}\`\n` | ||
resolveTargets(e.query).forEach(t => { | ||
diag += ` \`${e.name}\` ..> \`${t}\`\n` | ||
}) | ||
} | ||
@@ -115,1 +121,10 @@ }) | ||
} | ||
function resolveTargets (query) { | ||
if (query.target) return [ query.target.name ] // simple query, like projection on | ||
// SELECT: { from: { args: [{ ref: ['t1'] }, { ref: ['t2'] }], join: ..., on: ... } } | ||
const targets = query.SELECT?.from?.args?.filter(arg => arg.ref?.length).map (arg => arg.ref[0]) | ||
return targets | ||
} |
@@ -9,3 +9,2 @@ const path = require('path'); | ||
const { build } = require('../../build'); | ||
const buildConstants = require('../../build/constants'); | ||
@@ -20,2 +19,3 @@ const hdi = require('./hdiDeployUtil'); | ||
const { bind, storeBindings } = require('../../bind'); | ||
const { BUILD_TASK_HANA, OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, OUTPUT_MODE_RESULT } = require('../../build/constants'); | ||
@@ -78,9 +78,9 @@ const DEBUG = cds.debug('cli'); | ||
for (const { task } of buildResults) { | ||
for (const { task, result } of buildResults) { | ||
if (dry) { | ||
const model = await cds.load(task.src); | ||
const data = cds.compile.to.hdbtable(model); | ||
for (const [src, { file }] of data) { | ||
console.log(`-- ${file}`); | ||
console.log(src); | ||
const sortedResult = Array.from(result.hana).sort(); | ||
for (const file of sortedResult) { | ||
const srcPath = path.join(result.dest, file); | ||
console.log(`-- ${path.relative(cds.root, srcPath)}`); | ||
console.log(await read(srcPath)); | ||
console.log(); | ||
@@ -227,3 +227,5 @@ } | ||
const buildTaskOptions = { | ||
for: buildConstants.BUILD_TASK_HANA | ||
for: BUILD_TASK_HANA, | ||
src: cds.env.folders.db, | ||
[OUTPUT_MODE]: OUTPUT_MODE_FILESYSTEM | OUTPUT_MODE_RESULT | ||
} | ||
@@ -230,0 +232,0 @@ |
@@ -62,4 +62,12 @@ const path = require('path'); | ||
console.log(`Using HDI deployer from ${libPath}`) | ||
console.log(`HDI deployer path: ${libPath}`) | ||
const libRoot = path.dirname(libPath); | ||
try { | ||
const libPackageJson = require(path.join(libRoot, 'package.json')); | ||
console.log(`HDI deployer version: ${libPackageJson.version}`); | ||
} catch { | ||
// ignore | ||
} | ||
// let any error go through and abort deploy | ||
@@ -66,0 +74,0 @@ return require(libPath); |
@@ -8,3 +8,3 @@ module.exports = { | ||
JAVA_LTS_VERSIONS: [17, 21], | ||
MAVEN_ARCHETYPE_VERSION: '3.0.0', | ||
MAVEN_ARCHETYPE_VERSION: '3.0.1', | ||
@@ -61,3 +61,6 @@ OPTIONS: Object.freeze({ | ||
TYPER: 'typer', | ||
TYPESCRIPT: 'typescript' | ||
TYPESCRIPT: 'typescript', | ||
EVENT_QUEUE: 'event-queue', | ||
COV2AP: 'cov2ap', | ||
WEBSOCKET: 'websocket' | ||
}), | ||
@@ -74,3 +77,6 @@ | ||
MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/developing-applications/building#the-maven-archetype`, | ||
MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html` | ||
MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`, | ||
CAP_JS_COMMUNITY_ODATA_V2: 'https://github.com/cap-js-community/odata-v2-adapter', | ||
CAP_JS_COMMUNITY_EVENT_QUEUE: 'https://github.com/cap-js-community/event-queue', | ||
CAP_JS_COMMUNITY_WEBSOCKET: 'https://github.com/cap-js-community/websocket' | ||
}), | ||
@@ -77,0 +83,0 @@ |
@@ -25,10 +25,12 @@ const path = require('path') | ||
static help({ exclude = [] } = {}) { | ||
if (!cds.add) return // use case shell completion: cds.add is undefined and not needed | ||
const plugins = this.readPlugins(exclude) | ||
const nameFixedLength = Math.max(...plugins.map(plugin => plugin.name.length)) | ||
return plugins | ||
.filter(({module}) => module.help()) | ||
.map(({name, module}) => { | ||
const help = module.help?.() ?? '' | ||
return ` *${name}*${' '.repeat(nameFixedLength - name.length)} - ${help}` | ||
return ` *${name}*${' '.repeat(nameFixedLength - name.length)} - ${module.help()}` | ||
}) | ||
.join('\n\n') | ||
.join('\n') | ||
} | ||
@@ -180,3 +182,3 @@ | ||
const max = entries.reduce((max, {name}) => Math.max(max, name.length), 0) | ||
const allFacetsText = entries.map(({name, module}) => { | ||
const allFacetsText = entries.filter(({module})=> module.help()).map(({name, module}) => { | ||
const help = module.help?.() ?? '' | ||
@@ -183,0 +185,0 @@ return `${term.bold(name) + ' '.repeat(max - name.length)} ${term.dim(help)}` |
@@ -7,2 +7,3 @@ const cds = require('../cds') | ||
const DEBUG = cds.debug('cli') | ||
const TRACE = cds.debug('trace') | ||
@@ -92,3 +93,3 @@ module.exports = new class ProjectReader { | ||
* @property {boolean} hasHtml5Repo Indicates if the project uses the SAP BTP HTML5 Application Repository. | ||
* @property {boolean} hasPortal Indicates if the project uses the SAP Cloud Portal serivce. | ||
* @property {boolean} hasPortal Indicates if the project uses the SAP Cloud Portal service. | ||
* @property {boolean} hasDestination Indicates if the project uses the SAP BTP Destination service. | ||
@@ -111,3 +112,3 @@ * @property {boolean} hasMTXRoute Indicates if the approuter should have a multitenant route. | ||
const { appVersion, appName, appId, appDescription, jdkVersion } = this | ||
DEBUG?.({ env }) | ||
TRACE?.({ env }) | ||
@@ -193,5 +194,4 @@ const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env) | ||
DEBUG?.({ project }) | ||
return project | ||
} | ||
} |
@@ -39,3 +39,3 @@ const { join } = require('path') | ||
project.servicePlan = plan | ||
if (hasMultitenancy) { | ||
@@ -42,0 +42,0 @@ await merge(__dirname, 'files/package.sidecar.json').into('mtx/sidecar/package.json') |
@@ -15,2 +15,6 @@ const { join } = require('path') | ||
static hasInProduction() { | ||
return exists(MANIFEST_FILE) | ||
} | ||
async canRun() { | ||
@@ -17,0 +21,0 @@ if (cds.cli.options.force) { |
@@ -152,2 +152,3 @@ const https = require('node:https') | ||
const headers = auth ? `Content-Type: application/json\n{{auth}}` : `Content-Type: application/json` | ||
const headerAuth = auth ? '{{auth}}' : '' | ||
@@ -160,6 +161,7 @@ service.urlPaths.forEach((urlPath) => { | ||
service.entities.forEach((e) => { | ||
const entityNameSimple = e.name.slice(e.name.lastIndexOf('.') + 1) | ||
const entityNameSimple = e.name.slice(serviceName.length + 1) | ||
const nameSlug = slug(entityNameSimple) | ||
const url = `{{server}}/${urlPath}${entityNameSimple}` | ||
const requestTitle = `\n### ${e.name}` | ||
requests.push(`${requestTitle}\nGET ${url}\n${headers}\n`) | ||
const requestTitle = `\n### ${entityNameSimple}` | ||
requests.push(`${requestTitle}\n# @name ${nameSlug}_GET\nGET ${url}\n${headerAuth}\n`) | ||
if (e.readOnly) return | ||
@@ -169,19 +171,22 @@ | ||
if (!entityData) return | ||
const postBody = JSON.stringify(entityData, null, 2) | ||
let id | ||
const compositeKey = Object.keys(e.keys).length > 1 | ||
if (compositeKey) { | ||
id = e.keys.map(k => k.type === 'cds.String' ? `${k.name}='${entityData[k.name]}'` : `${k.name}=${entityData[k.name]}`).join(',') | ||
if (e.isDraft) { | ||
const key = Object.keys(e.keys)[0] // draft keys are always single | ||
delete entityData[key] // draft keys should always by auto generated (either UUID or through custom logic), so remove them from the payload | ||
const postBody = JSON.stringify(entityData, null, 2) | ||
generateDraftRequests(requests, requestTitle, url, headers, headerAuth, postBody, key) | ||
} | ||
else { | ||
const postBody = JSON.stringify(entityData, null, 2) | ||
let id | ||
const compositeKey = Object.keys(e.keys).length > 1 | ||
if (compositeKey) { | ||
id = e.keys.map(k => k.type === 'cds.String' ? `${k.name}='${entityData[k.name]}'` : `${k.name}=${entityData[k.name]}`).join(',') | ||
} else { | ||
id = entityData[Object.keys(e.keys)[0]] | ||
} | ||
if (e.isDraft) { | ||
generateDraftRequests(requests, requestTitle, url, headers, postBody, id) | ||
} else { | ||
id = entityData[Object.keys(e.keys)[0]] | ||
} | ||
compositeKey ? id = `(${id})` : id = `/${id}` | ||
requests.push(`${requestTitle}\nPOST ${url}\n${headers}\n\n${postBody}\n`) | ||
requests.push(`${requestTitle}\nPATCH ${url}${id}\n${headers}\n\n${postBody}\n`) | ||
requests.push(`${requestTitle}\nDELETE ${url}${id}\n${headers}\n`) | ||
requests.push(`${requestTitle}\n# @name ${nameSlug}_POST\nPOST ${url}\n${headers}\n\n${postBody}\n`) | ||
requests.push(`${requestTitle}\n# @name ${nameSlug}_PATCH\nPATCH ${url}${id}\n${headers}\n\n${postBody}\n`) | ||
requests.push(`${requestTitle}\n# @name ${nameSlug}_DELETE\nDELETE ${url}${id}\n${headers}\n`) | ||
} | ||
@@ -191,3 +196,4 @@ }) | ||
service.actions.forEach((a) => { | ||
const url = `{{server}}/${urlPath}${a.name.split(".")[1]}` | ||
const simpleNameAction = a.name.slice(serviceName.length + 1) | ||
const url = `{{server}}/${urlPath}${simpleNameAction}` | ||
@@ -204,3 +210,3 @@ let actionPayload = {} | ||
} | ||
requests.push(`\n### ${a.name}\nPOST ${url}\n${headers}\n\n${JSON.stringify(actionPayload, null, 2)}\n`) | ||
requests.push(`\n### ${simpleNameAction}\n# @name ${simpleNameAction}_POST\nPOST ${url}\n${headers}\n\n${JSON.stringify(actionPayload, null, 2)}\n`) | ||
}) | ||
@@ -212,10 +218,15 @@ | ||
function generateDraftRequests(requests, requestTitle, url, headers, postBody, id) { | ||
requests.push(`${requestTitle} Drafts \nGET ${url}?$filter=(IsActiveEntity eq false)\n${headers}\n`) // unencoded URL for better readability (client can handle this) | ||
requests.push(`${requestTitle} Drafts \nPOST ${url}\n${headers}\n\n${postBody}\n`) | ||
requests.push(`\n### Please paste the server-generated draft id here e.g. ID=101 or ID=101,secodKey='stringValue' \n@draftID = ${id}\n`) | ||
requests.push(`${requestTitle} Patch Draft \nPATCH ${url}({{draftID}},IsActiveEntity=false)\n${headers}\n\n${postBody}\n`) | ||
requests.push(`${requestTitle} Prepare Draft \nPOST ${url}({{draftID}},IsActiveEntity=false)/AdminService.draftPrepare\n${headers}\n\n{}\n`) | ||
requests.push(`${requestTitle} Activate Draft \nPOST ${url}({{draftID}},IsActiveEntity=false)/AdminService.draftActivate\n${headers}\n\n{}\n`) | ||
function generateDraftRequests(requests, title, url, headers, headerAuth, postBody, key) { | ||
const nameDraftsCreate = slug(title+'_Draft_POST') | ||
requests.push(`${title} Drafts GET\n# @name ${slug(title+'_Drafts_GET')}\nGET ${url}?$filter=(IsActiveEntity eq false)\n${headerAuth}\n`) // unencoded URL for better readability (client can handle this) | ||
requests.push(`${title} Draft POST\n# @name ${nameDraftsCreate}\nPOST ${url}\n${headers}\n\n${postBody}\n`) | ||
requests.push(`\n### Result from POST request above\n@draftID = {{${nameDraftsCreate}.response.body.$.${key}}}\n`) | ||
requests.push(`${title} Draft PATCH\n# @name ${slug(title+'_Draft_Patch')}\nPATCH ${url}(${key}={{draftID}},IsActiveEntity=false)\n${headers}\n\n${postBody}\n`) | ||
requests.push(`${title} Draft Prepare\n# @name ${slug(title+'_Draft_Prepare')}\nPOST ${url}(${key}={{draftID}},IsActiveEntity=false)/AdminService.draftPrepare\n${headers}\n\n{}\n`) | ||
requests.push(`${title} Draft Activate\n# @name ${slug(title+'_Draft_Activate')}\nPOST ${url}(${key}={{draftID}},IsActiveEntity=false)/AdminService.draftActivate\n${headers}\n\n{}\n`) | ||
} | ||
function slug(str) { | ||
return str.trim().replace(/[#\\. ]/g, '') | ||
} | ||
} | ||
@@ -257,3 +268,1 @@ | ||
} | ||
@@ -58,3 +58,2 @@ { | ||
"settings": { | ||
"synchronizationMode": "None", | ||
"operationMode": "Server", | ||
@@ -61,0 +60,0 @@ "autoExpandSelect": true, |
@@ -74,3 +74,2 @@ { | ||
"settings": { | ||
"synchronizationMode": "None", | ||
"operationMode": "Server", | ||
@@ -77,0 +76,0 @@ "autoExpandSelect": true, |
@@ -1,12 +0,17 @@ | ||
const cds = require('@sap/cds/lib') | ||
const cds = require('@sap/cds') | ||
module.exports = class AdminService extends cds.ApplicationService { init(){ | ||
this.before ('NEW','Books.drafts', genid) | ||
const { Books } = this.entities | ||
/** | ||
* Generate IDs for new Books drafts | ||
*/ | ||
this.before ('NEW', Books.drafts, async (req) => { | ||
if (req.data.ID) return | ||
const { ID:id1 } = await SELECT.one.from(Books).columns('max(ID) as ID') | ||
const { ID:id2 } = await SELECT.one.from(Books.drafts).columns('max(ID) as ID') | ||
req.data.ID = Math.max(id1||0, id2||0) + 1 | ||
}) | ||
return super.init() | ||
}} | ||
/** Generate primary keys for target entity in request */ | ||
async function genid (req) { | ||
const {ID} = await cds.tx(req).run (SELECT.one.from(req.target.actives).columns('max(ID) as ID')) | ||
req.data.ID = ID - ID % 100 + 100 + 1 | ||
} |
const cds = require('@sap/cds') | ||
const { Books } = cds.entities ('sap.capire.bookshop') | ||
class CatalogService extends cds.ApplicationService { init(){ | ||
class CatalogService extends cds.ApplicationService { init() { | ||
// Reduce stock of ordered books if available stock suffices | ||
this.on ('submitOrder', async req => { | ||
const {book,quantity} = req.data | ||
let {stock} = await SELECT `stock` .from (Books,book) | ||
if (stock >= quantity) { | ||
await UPDATE (Books,book) .with (`stock -=`, quantity) | ||
await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id }) | ||
return { stock } | ||
} | ||
else return req.error (409,`${quantity} exceeds stock for book #${book}`) | ||
}) | ||
const { Books } = cds.entities('sap.capire.bookshop') | ||
const { ListOfBooks } = this.entities | ||
// Add some discount for overstocked books | ||
this.after ('each','ListOfBooks', book => { | ||
this.after('each', ListOfBooks, book => { | ||
if (book.stock > 111) book.title += ` -- 11% discount!` | ||
}) | ||
// Reduce stock of ordered books if available stock suffices | ||
this.on('submitOrder', async req => { | ||
let { book:id, quantity } = req.data | ||
let book = await SELECT.from (Books, id, b => b.stock) | ||
// Validate input data | ||
if (!book) return req.error (404, `Book #${id} doesn't exist`) | ||
if (quantity < 1) return req.error (400, `quantity has to be 1 or more`) | ||
if (quantity > book.stock) return req.error (409, `${quantity} exceeds stock for book #${id}`) | ||
// Reduce stock in database and return updated stock value | ||
await UPDATE (Books, id) .with ({ stock: book.stock -= quantity }) | ||
return book | ||
}) | ||
// Emit event when an order has been submitted | ||
this.after('submitOrder', async (_,req) => { | ||
let { book, quantity } = req.data | ||
await this.emit('OrderedBook', { book, quantity, buyer: req.user.id }) | ||
}) | ||
// Delegate requests to the underlying generic service | ||
return super.init() | ||
}} | ||
module.exports = { CatalogService } | ||
module.exports = CatalogService |
{ | ||
"devDependencies": { | ||
"typescript": "^5", | ||
"@cap-js/cds-types": "<1", | ||
"@types/node": "^20" | ||
"@cap-js/cds-types": "^0.6", | ||
"@types/node": "^20", | ||
"typescript": "^5" | ||
}, | ||
@@ -7,0 +7,0 @@ "scripts": { |
module.exports = class extends require('../../plugin') { | ||
help() { | ||
return 'SAP BTP Work Zone' | ||
} | ||
@@ -6,0 +3,0 @@ requires() { |
{ | ||
"name": "@sap/cds-dk", | ||
"version": "8.0.3", | ||
"version": "8.1.0", | ||
"lockfileVersion": 3, | ||
@@ -9,3 +9,3 @@ "requires": true, | ||
"name": "@sap/cds-dk", | ||
"version": "8.0.3", | ||
"version": "8.1.0", | ||
"license": "SEE LICENSE IN LICENSE", | ||
@@ -46,5 +46,8 @@ "dependencies": { | ||
"node_modules/@cap-js/db-service": { | ||
"version": "1.11.0", | ||
"version": "1.12.0", | ||
"license": "SEE LICENSE", | ||
"optional": true, | ||
"dependencies": { | ||
"generic-pool": "^3.9.0" | ||
}, | ||
"peerDependencies": { | ||
@@ -107,3 +110,3 @@ "@sap/cds": ">=7.9" | ||
"node_modules/@eslint/config-array": { | ||
"version": "0.17.0", | ||
"version": "0.17.1", | ||
"license": "Apache-2.0", | ||
@@ -141,3 +144,3 @@ "dependencies": { | ||
"node_modules/@eslint/js": { | ||
"version": "9.7.0", | ||
"version": "9.8.0", | ||
"license": "MIT", | ||
@@ -207,3 +210,3 @@ "engines": { | ||
"node_modules/@sap/cds": { | ||
"version": "8.0.4", | ||
"version": "8.1.0", | ||
"license": "SEE LICENSE IN LICENSE", | ||
@@ -234,3 +237,3 @@ "dependencies": { | ||
"node_modules/@sap/cds-compiler": { | ||
"version": "5.0.6", | ||
"version": "5.1.0", | ||
"license": "SEE LICENSE IN LICENSE", | ||
@@ -875,3 +878,3 @@ "dependencies": { | ||
"node_modules/eslint": { | ||
"version": "9.7.0", | ||
"version": "9.8.0", | ||
"license": "MIT", | ||
@@ -881,5 +884,5 @@ "dependencies": { | ||
"@eslint-community/regexpp": "^4.11.0", | ||
"@eslint/config-array": "^0.17.0", | ||
"@eslint/config-array": "^0.17.1", | ||
"@eslint/eslintrc": "^3.1.0", | ||
"@eslint/js": "9.7.0", | ||
"@eslint/js": "9.8.0", | ||
"@humanwhocodes/module-importer": "^1.0.1", | ||
@@ -2433,3 +2436,3 @@ "@humanwhocodes/retry": "^0.3.0", | ||
"node_modules/yaml": { | ||
"version": "2.4.5", | ||
"version": "2.5.0", | ||
"license": "ISC", | ||
@@ -2436,0 +2439,0 @@ "bin": { |
{ | ||
"name": "@sap/cds-dk", | ||
"version": "8.0.3", | ||
"version": "8.1.0", | ||
"description": "Command line client and development toolkit for the SAP Cloud Application Programming Model", | ||
@@ -5,0 +5,0 @@ "homepage": "https://cap.cloud.sap/", |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1682810
412
35267
180