Comparing version 4.4.10 to 4.5.1
declare type cds_facade = | ||
typeof import ('@sap/cds-reflect') | ||
typeof import ('./core') | ||
& import ('./models') | ||
@@ -4,0 +4,0 @@ & import ('./connect') |
import edm from "@sap/cds-compiler/lib/edm/edm" | ||
import { Query, expr, _xpr } from "@sap/cds-reflect/apis/cqn" | ||
import { CSN } from "@sap/cds-reflect/apis/csn" | ||
import { Query, expr, _xpr } from "./cqn" | ||
import { CSN } from "./csn" | ||
@@ -5,0 +5,0 @@ type csn = CSN |
@@ -1,3 +0,3 @@ | ||
import {Definition} from "@sap/cds-reflect/apis/csn" | ||
import * as CQN from "@sap/cds-reflect/apis/cqn" | ||
import {Definition} from "./csn" | ||
import * as CQN from "./cqn" | ||
import { STATUS_CODES } from "http" | ||
@@ -4,0 +4,0 @@ |
import { Service, ServiceImpl } from "./services" | ||
import { LinkedDefinition } from "@sap/cds-reflect/apis/reflected" | ||
import { csn } from "@sap/cds-reflect/apis/csn" | ||
import { LinkedDefinition } from "./reflect" | ||
import { csn } from "./csn" | ||
import * as http from "http"; | ||
@@ -5,0 +5,0 @@ |
import { SELECT, INSERT, UPDATE, DELETE, Query, ConstructedQuery } from './ql' | ||
import { LinkedModel, Definition, Definitions } from '@sap/cds-reflect/apis/reflected' | ||
import { csn, type } from "@sap/cds-reflect/apis/csn" | ||
import { LinkedModel, Definition, Definitions } from './reflect' | ||
import { csn, type } from "./csn" | ||
// import { Service } from './cds' | ||
@@ -53,7 +53,10 @@ | ||
// Messaging API | ||
emit (eve: Events, entity: Target, data?: object) : this | ||
emit (eve: Events, data?: object) : this | ||
async emit (eve: Events, data?: object, headers?: object) : this | ||
// Http API | ||
async send (eve: Events, entity: Target, data?: object, headers?: object) : this | ||
async send (eve: Events, data?: object, headers?: object) : this | ||
// Provider API | ||
prepend (fn: ServiceImpl): this | ||
async prepend (fn: ServiceImpl): this | ||
on (eve: Events, entity: Target, handler: OnEventHandler): this | ||
@@ -60,0 +63,0 @@ on (eve: Events, handler: OnEventHandler): this |
@@ -1,74 +0,78 @@ | ||
const { app, env, service:{providers} } = require('../../lib') | ||
const cds = require('../../lib') | ||
cds.on('served', ()=>{ | ||
const mountPoint = '/$fiori-preview' | ||
const appID = 'preview-app' | ||
const _appURL = (srv, entity) => `${mountPoint}/${srv}/${entity}#${appID}` | ||
const _componentURL = (srv, entity) => `${mountPoint}/${srv}/${entity}/app` | ||
const { app, env, service:{providers} } = cds | ||
function _manifest(serviceName, entityName) { | ||
const [serviceProv, serviceInfo] = _validate(serviceName, entityName) | ||
const manifest = { | ||
_version: '1.8.0', | ||
'sap.app': { | ||
id: 'preview', | ||
type: 'application', | ||
title: `Preview ‒ List of ${serviceProv.name}.${entityName}`, | ||
description: 'Preview Application', | ||
dataSources: { | ||
mainService: { | ||
uri: `${serviceProv.path}/`, | ||
type: 'OData', | ||
settings: { | ||
odataVersion: '4.0' | ||
const mountPoint = '/$fiori-preview' | ||
const appID = 'preview-app' | ||
const _appURL = (srv, entity) => `${mountPoint}/${srv}/${entity}#${appID}` | ||
const _componentURL = (srv, entity) => `${mountPoint}/${srv}/${entity}/app` | ||
function _manifest(serviceName, entityName) { | ||
const [serviceProv, serviceInfo] = _validate(serviceName, entityName) | ||
const manifest = { | ||
_version: '1.8.0', | ||
'sap.app': { | ||
id: 'preview', | ||
type: 'application', | ||
title: `Preview ‒ List of ${serviceProv.name}.${entityName}`, | ||
description: 'Preview Application', | ||
dataSources: { | ||
mainService: { | ||
uri: `${serviceProv.path}/`, | ||
type: 'OData', | ||
settings: { | ||
odataVersion: '4.0' | ||
} | ||
} | ||
} | ||
}, | ||
}, | ||
}, | ||
'sap.ui5': { | ||
dependencies: { | ||
libs: { | ||
'sap.fe.templates': {} | ||
} | ||
}, | ||
models: { | ||
'': { | ||
dataSource: 'mainService', | ||
settings: { | ||
synchronizationMode: 'None', | ||
operationMode: 'Server', | ||
autoExpandSelect: true, | ||
earlyRequests: true, | ||
groupProperties: { | ||
default: { | ||
submit: 'Auto' | ||
'sap.ui5': { | ||
dependencies: { | ||
libs: { | ||
'sap.fe.templates': {} | ||
} | ||
}, | ||
models: { | ||
'': { | ||
dataSource: 'mainService', | ||
settings: { | ||
synchronizationMode: 'None', | ||
operationMode: 'Server', | ||
autoExpandSelect: true, | ||
earlyRequests: true, | ||
groupProperties: { | ||
default: { | ||
submit: 'Auto' | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
routing: { | ||
routes: [ | ||
{ | ||
name: `${entityName}ListRoute`, | ||
target: `${entityName}ListTarget`, | ||
pattern: ':?query:', | ||
}, | ||
{ | ||
name: `${entityName}DetailsRoute`, | ||
target: `${entityName}DetailsTarget`, | ||
pattern: `${entityName}({key}):?query:`, | ||
} | ||
], | ||
targets: { | ||
[`${entityName}ListTarget`]: { | ||
type: 'Component', | ||
id: `${entityName}ListTarget`, | ||
name: 'sap.fe.templates.ListReport', | ||
options: { | ||
settings: { | ||
entitySet: `${entityName}`, | ||
navigation: { | ||
[`${entityName}`]: { | ||
detail: { | ||
route: `${entityName}DetailsRoute` | ||
}, | ||
routing: { | ||
routes: [ | ||
{ | ||
name: `${entityName}ListRoute`, | ||
target: `${entityName}ListTarget`, | ||
pattern: ':?query:', | ||
}, | ||
{ | ||
name: `${entityName}DetailsRoute`, | ||
target: `${entityName}DetailsTarget`, | ||
pattern: `${entityName}({key}):?query:`, | ||
} | ||
], | ||
targets: { | ||
[`${entityName}ListTarget`]: { | ||
type: 'Component', | ||
id: `${entityName}ListTarget`, | ||
name: 'sap.fe.templates.ListReport', | ||
options: { | ||
settings: { | ||
entitySet: `${entityName}`, | ||
navigation: { | ||
[`${entityName}`]: { | ||
detail: { | ||
route: `${entityName}DetailsRoute` | ||
} | ||
} | ||
@@ -78,181 +82,183 @@ } | ||
} | ||
} | ||
}, | ||
[`${entityName}DetailsTarget`]: { | ||
type: 'Component', | ||
id: `${entityName}DetailsTarget`, | ||
name: 'sap.fe.templates.ObjectPage', | ||
options: { | ||
settings: { | ||
entitySet: `${entityName}`, | ||
navigation: {} | ||
}, | ||
[`${entityName}DetailsTarget`]: { | ||
type: 'Component', | ||
id: `${entityName}DetailsTarget`, | ||
name: 'sap.fe.templates.ObjectPage', | ||
options: { | ||
settings: { | ||
entitySet: `${entityName}`, | ||
navigation: {} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
}, | ||
}, | ||
contentDensities: { | ||
compact: true, | ||
cozy: true | ||
}, | ||
'sap.ui': { | ||
technology: 'UI5', | ||
fullWidth: true | ||
}, | ||
'sap.fiori': { | ||
registrationIds: [], | ||
archeType: 'transactional' | ||
}, | ||
} | ||
contentDensities: { | ||
compact: true, | ||
cozy: true | ||
}, | ||
'sap.ui': { | ||
technology: 'UI5', | ||
fullWidth: true | ||
}, | ||
'sap.fiori': { | ||
registrationIds: [], | ||
archeType: 'transactional' | ||
}, | ||
} | ||
const { routing } = manifest['sap.ui5'] | ||
for (const {navProperty, targetEntity} of serviceInfo) { | ||
// add a route for the navigation property | ||
routing.routes.push( | ||
{ | ||
name: `${navProperty}Route`, | ||
target: `${navProperty}Target`, | ||
pattern: `${entityName}({key})/${navProperty}({key2}):?query:`, | ||
const { routing } = manifest['sap.ui5'] | ||
for (const {navProperty, targetEntity} of serviceInfo) { | ||
// add a route for the navigation property | ||
routing.routes.push( | ||
{ | ||
name: `${navProperty}Route`, | ||
target: `${navProperty}Target`, | ||
pattern: `${entityName}({key})/${navProperty}({key2}):?query:`, | ||
} | ||
) | ||
// add a route target leading to the target entity | ||
routing.targets[`${navProperty}Target`] = { | ||
type: 'Component', | ||
id: `${navProperty}Target`, | ||
name: 'sap.fe.templates.ObjectPage', | ||
options: { | ||
settings: { | ||
entitySet: targetEntity | ||
} | ||
} | ||
} | ||
) | ||
// add a route target leading to the target entity | ||
routing.targets[`${navProperty}Target`] = { | ||
type: 'Component', | ||
id: `${navProperty}Target`, | ||
name: 'sap.fe.templates.ObjectPage', | ||
options: { | ||
settings: { | ||
entitySet: targetEntity | ||
// wire the new route from the source entity's navigation (see above) | ||
routing.targets[`${entityName}DetailsTarget`].options.settings.navigation[navProperty] = { | ||
detail: { | ||
route: `${navProperty}Route` | ||
} | ||
} | ||
} | ||
// wire the new route from the source entity's navigation (see above) | ||
routing.targets[`${entityName}DetailsTarget`].options.settings.navigation[navProperty] = { | ||
detail: { | ||
route: `${navProperty}Route` | ||
} | ||
} | ||
return manifest | ||
} | ||
return manifest | ||
} | ||
function _html(serviceName, entityName) { | ||
_validate(serviceName, entityName) | ||
let ui5Version = (env.preview && env.preview.ui5 && env.preview.ui5.version) || '' | ||
let ui5Host = (env.preview && env.preview.ui5 && env.preview.ui5.host) || `https://sapui5.hana.ondemand.com/${ui5Version}` | ||
if (!ui5Host.endsWith('/')) ui5Host += '/' | ||
function _html(serviceName, entityName) { | ||
_validate(serviceName, entityName) | ||
let ui5Version = (env.preview && env.preview.ui5 && env.preview.ui5.version) || '' | ||
let ui5Host = (env.preview && env.preview.ui5 && env.preview.ui5.host) || `https://sapui5.hana.ondemand.com/${ui5Version}` | ||
if (!ui5Host.endsWith('/')) ui5Host += '/' | ||
// copied from UI5's test-resources/sap/ushell/shells/sandbox/fioriSandbox.html | ||
return ` | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Preview for ${serviceName}.${entityName}</title> | ||
<script> | ||
window["sap-ushell-config"] = { | ||
defaultRenderer: "fiori2", | ||
applications: { | ||
"${appID}": { | ||
title: "Browse ${entityName}", | ||
description: "from ${serviceName}", | ||
additionalInformation: "SAPUI5.Component=app", | ||
applicationType : "URL", | ||
url: "${_componentURL(serviceName, entityName)}", | ||
navigationMode: "embedded" | ||
// copied from UI5's test-resources/sap/ushell/shells/sandbox/fioriSandbox.html | ||
return ` | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Preview for ${serviceName}.${entityName}</title> | ||
<script> | ||
window["sap-ushell-config"] = { | ||
defaultRenderer: "fiori2", | ||
applications: { | ||
"${appID}": { | ||
title: "Browse ${entityName}", | ||
description: "from ${serviceName}", | ||
additionalInformation: "SAPUI5.Component=app", | ||
applicationType : "URL", | ||
url: "${_componentURL(serviceName, entityName)}", | ||
navigationMode: "embedded" | ||
} | ||
} | ||
} | ||
} | ||
</script> | ||
<script id="sap-ushell-bootstrap" src="${ui5Host}test-resources/sap/ushell/bootstrap/sandbox.js"></script> | ||
<script id="sap-ui-bootstrap" src="${ui5Host}resources/sap-ui-core.js" | ||
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout" data-sap-ui-compatVersion="edge" | ||
data-sap-ui-theme="sap_fiori_3" data-sap-ui-frameOptions="allow"> | ||
</script> | ||
<script src="${ui5Host}test-resources/sap/ushell/bootstrap/standalone.js"></script> | ||
<script> | ||
// load and register Fiori2 icon font | ||
jQuery.sap.require("sap.ushell.iconfonts"); | ||
jQuery.sap.require("sap.ushell.services.AppConfiguration"); | ||
sap.ushell.iconfonts.registerFiori2IconFont(); | ||
sap.ui.getCore().attachInit(function() { sap.ushell.Container.createRenderer().placeAt("content") }) | ||
</script> | ||
</head> | ||
<body class="sapUiBody sapUShellFullHeight" id="content"></body> | ||
</html> | ||
` | ||
} | ||
</script> | ||
<script id="sap-ushell-bootstrap" src="${ui5Host}test-resources/sap/ushell/bootstrap/sandbox.js"></script> | ||
<script id="sap-ui-bootstrap" src="${ui5Host}resources/sap-ui-core.js" | ||
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout" data-sap-ui-compatVersion="edge" | ||
data-sap-ui-theme="sap_fiori_3" data-sap-ui-frameOptions="allow"> | ||
</script> | ||
<script src="${ui5Host}test-resources/sap/ushell/bootstrap/standalone.js"></script> | ||
<script> | ||
// load and register Fiori2 icon font | ||
jQuery.sap.require("sap.ushell.iconfonts"); | ||
jQuery.sap.require("sap.ushell.services.AppConfiguration"); | ||
sap.ushell.iconfonts.registerFiori2IconFont(); | ||
sap.ui.getCore().attachInit(function() { sap.ushell.Container.createRenderer().placeAt("content") }) | ||
</script> | ||
</head> | ||
<body class="sapUiBody sapUShellFullHeight" id="content"></body> | ||
</html> | ||
` | ||
} | ||
function _componentJs(serviceName, entityName) { | ||
const manifest = _manifest(serviceName, entityName) | ||
return `sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) { | ||
"use strict"; | ||
return AppComponent.extend("preview.Component", { | ||
metadata: { manifest: ${JSON.stringify(manifest, null, 2)} } | ||
}); | ||
});` | ||
} | ||
function _componentJs(serviceName, entityName) { | ||
const manifest = _manifest(serviceName, entityName) | ||
return `sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) { | ||
"use strict"; | ||
return AppComponent.extend("preview.Component", { | ||
metadata: { manifest: ${JSON.stringify(manifest, null, 2)} } | ||
}); | ||
});` | ||
} | ||
function _validate(serviceName, entityName) { | ||
const serviceProv = providers.find (s => s.name === serviceName) | ||
if (!serviceProv) throw _badRequest (`No such service '${serviceName}'. Available: [${providers.map(p => p.name)}]`) | ||
return _serviceInfo (serviceProv, entityName) | ||
} | ||
function _validate(serviceName, entityName) { | ||
const serviceProv = providers.find (s => s.name === serviceName) | ||
if (!serviceProv) throw _badRequest (`No such service '${serviceName}'. Available: [${providers.map(p => p.name)}]`) | ||
return _serviceInfo (serviceProv, entityName) | ||
} | ||
function _serviceInfo (serviceProv, entityName) { | ||
const entities = serviceProv.model.entities(serviceProv.name) | ||
const entity = entities[entityName] | ||
if (!entity) throw _badRequest (`No such entity '${entityName}' in service '${serviceProv.name}'`) | ||
return [serviceProv, serviceProv.model.all ('Association', entity.elements) | ||
.filter (a => | ||
!a.target.endsWith('_texts') && | ||
!a.target.endsWith('DraftAdministrativeData') && | ||
a.name !== 'SiblingEntity') | ||
.map (a => { return { navProperty: a.name, targetEntity: a.target.split('.')[1] } }) | ||
] | ||
} | ||
function _serviceInfo (serviceProv, entityName) { | ||
const entities = serviceProv.model.entities(serviceProv.name) | ||
const entity = entities[entityName] | ||
if (!entity) throw _badRequest (`No such entity '${entityName}' in service '${serviceProv.name}'`) | ||
return [serviceProv, serviceProv.model.all ('Association', entity.elements) | ||
.filter (a => | ||
!a.target.endsWith('_texts') && | ||
!a.target.endsWith('DraftAdministrativeData') && | ||
a.name !== 'SiblingEntity') | ||
.map (a => { return { navProperty: a.name, targetEntity: a.target.split('.')[1] } }) | ||
] | ||
} | ||
const _badRequest = (message) => { const err = new Error (message); err.statusCode = 400; return err} | ||
const _badRequest = (message) => { const err = new Error (message); err.statusCode = 400; return err} | ||
// fetch and instrument all OData providers | ||
const any = providers.filter (srv => | ||
srv._adapters [Object.keys(srv._adapters) .find (a => a.startsWith ('odata'))] | ||
) | ||
.map(srv => { | ||
// called from ../index.js to provide the data for the HTML link | ||
const link = linkProvider(srv) | ||
srv.$linkProviders ? srv.$linkProviders.push (link) : srv.$linkProviders = [link] | ||
return link | ||
}) | ||
.length | ||
// install middlewares once | ||
if (any) { | ||
const router = require('express').Router() | ||
// UI5 component | ||
router.get ('/:service/:entity/app/Component.js', ({ params }, resp) => resp.send(_componentJs(params.service, params.entity))) | ||
// html | ||
router.get ('/:service/:entity', ({ params }, resp) => resp.send(_html(params.service, params.entity))) | ||
// legacy URL pattern from cds 3.x | ||
router.get ('/', ({ query }, resp, next) => { | ||
if (query.service && query.entity) resp.redirect (308, _appURL(query.service, query.entity)) | ||
else next() | ||
// fetch and instrument all OData providers | ||
const any = providers.filter (srv => | ||
srv._adapters [Object.keys(srv._adapters) .find (a => a.startsWith ('odata'))] | ||
) | ||
.map(srv => { | ||
// called from ../index.js to provide the data for the HTML link | ||
const link = linkProvider(srv) | ||
srv.$linkProviders ? srv.$linkProviders.push (link) : srv.$linkProviders = [link] | ||
return link | ||
}) | ||
app.use(mountPoint.replace('$','\\$'), router) | ||
} | ||
.length | ||
function linkProvider(service) { | ||
return (entity) => { | ||
if (!entity) return | ||
return { | ||
href: _appURL(service.name, entity), | ||
title: 'Preview in Fiori elements', | ||
name: 'Fiori' | ||
// install middlewares once | ||
if (any) { | ||
const router = require('express').Router() | ||
// UI5 component | ||
router.get ('/:service/:entity/app/Component.js', ({ params }, resp) => resp.send(_componentJs(params.service, params.entity))) | ||
// html | ||
router.get ('/:service/:entity', ({ params }, resp) => resp.send(_html(params.service, params.entity))) | ||
// legacy URL pattern from cds 3.x | ||
router.get ('/', ({ query }, resp, next) => { | ||
if (query.service && query.entity) resp.redirect (308, _appURL(query.service, query.entity)) | ||
else next() | ||
}) | ||
app.use(mountPoint.replace('$','\\$'), router) | ||
} | ||
function linkProvider(service) { | ||
return (entity) => { | ||
if (!entity) return | ||
return { | ||
href: _appURL(service.name, entity), | ||
title: 'Preview in Fiori elements', | ||
name: 'Fiori' | ||
} | ||
} | ||
} | ||
} | ||
}) |
@@ -6,10 +6,15 @@ | ||
const { find, path, readFileSync } = cds.utils, {app} = cds.env.folders | ||
const htmls = find (app, ['*.html', '*/*.html', '*/*/*.html']).map(file => path.relative(app, file).replace (/\\/g,'/')) | ||
const odata = srv => Object.keys(srv._adapters).find (a => a.startsWith ('odata')) | ||
const metadata = srv => odata(srv) ? ` / <a href="${srv.path}/$metadata">$metadata</a>` : `` | ||
const html = readFileSync(path.join(__dirname,'index.html'),'utf-8') .replace ( | ||
'{{content}}', | ||
htmls.map(html => `\n<span><a href="${html}">/${html}</a></span>`).join(',') + | ||
cds.service.providers.map (srv => ` | ||
const html = readFileSync(path.join(__dirname,'index.html'),'utf-8') | ||
// .replace ('{{subtitle}}', 'Version ' + cds.version) | ||
.replace (/{{package}}/g, _project()) | ||
.replace (/{{app}}/g, app.replace(/*trailing slash*/ /\/$/, '')) | ||
.replace ('{{apps}}', find (app, ['*.html', '*/*.html', '*/*/*.html']).map( | ||
file => path.relative(app, file).replace (/\\/g,'/')).map( | ||
html => `\n<li><a href="${html}">/${html}</a></li>` | ||
).join('\n') || '— none —' | ||
) | ||
.replace ('{{services}}', cds.service.providers.map (srv => ` | ||
<h3> | ||
@@ -21,3 +26,3 @@ <a href="${srv.path}">${srv.path}</a>${metadata(srv)} ${_moreLinks(srv)} | ||
<li> | ||
<a href="${srv.path}/${e}">${e}</a> ${_moreLinks(srv, e)} | ||
<a href="${srv.path}/${e}?$top=11">${e}</a> ${_moreLinks(srv, e)} | ||
</li>`}).join('')} | ||
@@ -49,4 +54,14 @@ </ul> | ||
.sort ((l1, l2) => l1.name.localeCompare(l2)) | ||
.map (l => ` <a class="preview" href="${l.href}" title="${l.title||l.name}"> …in ${l.name}</a>`) | ||
.map (l => ` <a class="preview" href="${l.href}" title="${l.title||l.name}"> → ${l.name} preview </a>`) | ||
.join (' ') | ||
} | ||
function _project(){ | ||
const cwd = process.cwd() | ||
try { | ||
const pj = require(cwd+'/package.json') | ||
return `${pj.name} ${pj.version}` | ||
} catch(e) { | ||
return `${cwd}` | ||
} | ||
} |
106
bin/serve.js
@@ -124,4 +124,2 @@ module.exports = Object.assign ( _serve, { | ||
const is_in_memory = o => o && o.credentials && o.credentials.database === ':memory:' | ||
const production = process.env.NODE_ENV === 'production' | ||
const {existsSync:exists} = require ('fs') | ||
@@ -173,29 +171,23 @@ const {dirname,resolve} = require ('path') | ||
if (o.watch) return _watch (o.project,o) // cds serve --watch <project> | ||
if (o.project) { // cds serve --in <project> | ||
process.env._original_cwd = process.cwd() | ||
try { process.chdir (o.project) } | ||
catch(e){ | ||
try { process.chdir (dirname (require.resolve(o.project+'/package.json'))) } | ||
catch(_){ throw e } | ||
} | ||
process.on('exit', ()=> process.chdir (process.env._original_cwd)) | ||
} | ||
if (o.project) _in_project (o.project) // cds run --project <project> | ||
// IMPORTANT: never load any @sap/cds modules before the chdir above happened! | ||
const cds = _prepare_cds() | ||
const cds = _prepare_cds (o,require('../lib')) | ||
// handle --in-memory resp. --in-memory? (requires cds.env) | ||
o.in_memory = o['in-memory'] || !production && o['in-memory?'] && !cds.requires.db | ||
if (o.in_memory) cds.env.add ({requires: { db: { | ||
kind:'sqlite', ...cds.requires.sqlite, | ||
credentials:{database:':memory:'} | ||
}}}) | ||
else o.in_memory = is_in_memory(cds.requires.db) | ||
// The following things are meant for dev mode, which can be overruled by feature flagse... | ||
const {features} = cds.env | ||
{ | ||
// load service bindings when mocking or asked to | ||
if (features.mocked_bindings && o.mocked || o['with-bindings']) await cds.service.bindings | ||
// load service bindings when mocking or asked to | ||
if (o.mocked || o['with-bindings']) await cds.service.bindings | ||
// handle --in-memory resp. --in-memory? (requires cds.env) | ||
if (features.in_memory_db) o.in_memory = _in_memory (o,cds.env) | ||
// add dev helper for Fiori URLs | ||
if (process.env.NODE_ENV!=='production') require('../app/fiori/routes') | ||
// add dev helper for Fiori URLs | ||
if (features.fiori_routes) require('../app/fiori/routes') | ||
// add fiori preview links to default index.html | ||
if (features.fiori_preview) require('../app/fiori/preview') | ||
} | ||
// bootstrap server from project-local server.js or from @sap/cds/server.js | ||
@@ -205,13 +197,10 @@ const server_js = _local('server.js') || _local(cds.env.folders.srv,'server.js') || cds.server | ||
// add fiori preview links to default index.html | ||
if (cds.env.features.fiori_preview) require('../app/fiori/preview') | ||
// synchronize with the server events | ||
return new Promise((resolve, reject) => { | ||
server.on('error', e => reject(e)) // startup errors like EADDRINUSE | ||
const done = ()=> { | ||
cds.emit('listening', { server, url: `http://localhost:${server.address().port}` }) | ||
resolve(server) | ||
server.once ('listening',done); if (server.listening) done() | ||
server.on ('error', e => reject(e)) // startup errors like EADDRINUSE | ||
function done () { | ||
cds.emit ('listening', { server, url: `http://localhost:${server.address().port}` }) | ||
resolve (server) | ||
} | ||
server.listening ? done() : server.on('listening',done) | ||
}) | ||
@@ -221,6 +210,5 @@ } | ||
function _prepare_cds () { // NOSONAR | ||
/** @param {import('../lib')} cds */ | ||
function _prepare_cds (o,cds) { // NOSONAR | ||
const cds = require('../lib') | ||
const cds_requires = Object.create(cds.requires) | ||
@@ -247,2 +235,3 @@ for (let entry of Object.values(cds.requires)) { | ||
console.log ('\x1b[0m') | ||
if (o.mocked) require('../lib/db/deploy').include_external_entities_in(model) | ||
}) | ||
@@ -274,8 +263,3 @@ | ||
function _local (...path) { | ||
const file = resolve(...path) | ||
if (exists(file)) return require (file) | ||
} | ||
/** handles --watch option */ | ||
function _watch (project,o) { | ||
@@ -296,3 +280,43 @@ o.args = process.argv.slice(2) .filter (a => a !== '--watch' && a !== '-w') | ||
// mascades password-like strings, also reducing clutter in output | ||
/** handles --project option */ | ||
function _in_project (project) { | ||
// save the former process.cwd() | ||
const former = process.env._original_cwd = process.cwd() | ||
try { // using the given project as dirname, e.g. './bookshop' | ||
process.chdir (project) | ||
} catch(e) { | ||
try { // using the given project as a node package name, e.g. '@capire/bookshop' | ||
process.chdir (dirname (require.resolve(project+'/package.json'))) | ||
} catch(_){ throw e } | ||
} | ||
// restore the former process.cwd() | ||
process.on('exit', ()=> process.chdir (former)) | ||
} | ||
/** handles --in-memory option */ | ||
function _in_memory (o,env) { | ||
const db = env.requires.db | ||
if (o['in-memory'] || o['in-memory?'] && !db) { | ||
env.add ({ requires: { db: { | ||
kind:'sqlite', ...env.requires.sqlite, | ||
credentials:{database:':memory:'} | ||
}}}) | ||
return true | ||
} | ||
if (db && db.credentials && db.credentials.database === ':memory:') { | ||
return true | ||
} | ||
} | ||
/** used to load local server.js */ | ||
function _local (...path) { | ||
const file = resolve(...path) | ||
if (exists(file)) return require (file) | ||
} | ||
/** mascades password-like strings, also reducing clutter in output */ | ||
function _redacted (cred) { | ||
@@ -299,0 +323,0 @@ const secrets = /(password)|(certificate)|(ca)/i // 'certificate' and 'ca' on HANA |
@@ -1,3 +0,2 @@ | ||
/* eslint-disable no-console */ | ||
const Severities = ['Error', 'Warning', 'Info', 'Debug'] | ||
const { sortMessagesSeverityAware, deduplicateMessages } = require('@sap/cds-compiler') | ||
@@ -12,6 +11,7 @@ // sorts, filters, and writes compilation messages to console | ||
messages.forEach (m => { if (!m.severity) m.severity = 'Error' }) | ||
messages = messages.filter (m => level.includes (m.severity)) | ||
messages = _sortUnique (messages) | ||
deduplicateMessages(messages) | ||
messages = sortMessagesSeverityAware (messages) | ||
for (let m of messages) { | ||
@@ -25,32 +25,2 @@ // show stack for resolution issues since there the requiring code location is in the stack | ||
function _sortUnique (a, comparator=_compareCompilationMessage) { | ||
let arr = []; | ||
for (let i = 0; i < a.length; i++) { | ||
const hasDup = arr.some(v => comparator(v, a[i]) === 0) | ||
if (!hasDup) arr.push(a[i]) | ||
} | ||
return arr.sort (comparator) | ||
} | ||
function _compareCompilationMessage (a, b) { | ||
let rc = Severities.indexOf (a.severity) - Severities.indexOf (b.severity); if (rc !== 0) return rc | ||
rc = eq( a.message, b.message ); if (rc !== 0) return rc | ||
if (a.location && b.location) { | ||
let aend = a.location.end || a.location.start; | ||
let bend = b.location.end || b.location.start; | ||
return ( eq( a.location.filename, b.location.filename ) || | ||
eq( a.location.start.line, b.location.start.line ) || | ||
eq( a.location.start.column, b.location.start.column ) || | ||
eq( aend.line, bend.line ) || | ||
eq( aend.column, bend.column )) | ||
} | ||
else | ||
return (!a.location ? (!b.location ? 0 : 1) : -1) | ||
} | ||
function eq(x, y) { | ||
return (x === y) ? 0 : (x > y) ? 1 : -1 | ||
} | ||
function _effectiveLogLevel (options) { | ||
@@ -71,1 +41,3 @@ const logLevel = options && options['log-level'] || require('../../lib').env["log-level"] | ||
} | ||
/* eslint no-console:off */ |
@@ -7,2 +7,31 @@ # Change Log | ||
## Version 4.5.1 - 2021-02-01 | ||
### Fixed | ||
- Update `@sap/cds-runtime` dependency | ||
## Version 4.5.0 - 2021-02-01 | ||
### Added | ||
- `cds.server` provides an option to switch off automatically generated `index.html` served at `/`: | ||
Do that in a custom `server.js`: | ||
```js | ||
const cds = require('@sap/cds') | ||
// ... | ||
module.exports = (o) => cds.server({ ...o, index:false }) | ||
``` | ||
- The default `index.html` now honors the system's setting for dark mode. | ||
- Former package `@sap/cds-reflect` is now embedded in `@sap/cds` | ||
### Changed | ||
- Fiori preview is now disabled if `NODE_ENV` is `production`, to avoid any runtime overhead there. You can enable it with configuration `cds.features.fiori_preview: true`. | ||
### Fixed | ||
- `cds build` now correctly supports multitenant applications defining multiple database modules, e.g. one database for tenant related data and one for shared data. | ||
- `cds deploy --to hana` does no longer fail with an invalid service name error if '.' is used in the MTA ID. | ||
## Version 4.4.10 - 2021-01-18 | ||
@@ -19,2 +48,3 @@ | ||
## Version 4.4.8 - 2021-01-07 | ||
@@ -105,2 +135,8 @@ | ||
## Version 4.3.2 - 2020-12-18 | ||
### Fixed | ||
- use `@sap/cds-runtime~2.6` | ||
## Version 4.3.1 - 2020-11-20 | ||
@@ -107,0 +143,0 @@ |
@@ -49,4 +49,3 @@ const cds = require('..') | ||
const name = localized_(locale, each) | ||
const source = localized_(locale, d.source) | ||
const x = {__proto__:d, name, source } | ||
const x = {__proto__:d, name } | ||
Object.defineProperty (m.definitions, name, {value:x}) | ||
@@ -53,0 +52,0 @@ pass2.push ([x,locale]) |
@@ -6,3 +6,3 @@ const fs = require('@sap/cds-foss')('fs-extra') | ||
const DEBUG = process.env.DEBUG | ||
const HDI_CONTAINER_TYPE = 'com.sap.xs.hdi-container' | ||
const HDI_CONTAINER_TYPES = ['com.sap.xs.hdi-container'] | ||
const UTF_8 = 'utf-8' | ||
@@ -47,3 +47,3 @@ const MTA_YAML = 'mta.yaml' | ||
if (mta.ID) { | ||
details.name = mta.ID.replace('.', '-') | ||
details.name = mta.ID.replace(/\./g, '-') | ||
} | ||
@@ -148,3 +148,3 @@ } | ||
if (mta && Array.isArray(mta.resources)) { | ||
const hdiResources = mta.resources.filter(resource => resource.type === HDI_CONTAINER_TYPE) | ||
const hdiResources = mta.resources.filter(resource => HDI_CONTAINER_TYPES.includes(resource.type)) | ||
@@ -151,0 +151,0 @@ if (hdiResources.length > 0) { |
@@ -6,4 +6,4 @@ /* eslint-disable no-empty */ | ||
const BuildTaskFactory = require('../buildTaskFactory') | ||
const { setProperty, BuildMessage, BuildError } = require('../util') | ||
const { BUILD_TASK_HANA, BUILD_TASK_MTX, FOLDER_GEN, SEVERITY_WARNING } = require('../constants') | ||
const { BuildMessage, BuildError } = require('../util') | ||
const { BUILD_TASK_HANA, FOLDER_GEN, SEVERITY_WARNING } = require('../constants') | ||
const DEBUG = process.env.DEBUG | ||
@@ -29,21 +29,3 @@ | ||
async build() { | ||
// custom build tasks for srv and db modules might be defined | ||
let tasks = await this._getTasks() | ||
if (tasks.length === 0) { | ||
this.logger.log("[cds] - no modules containing cds model files found, skipping build") | ||
return | ||
} | ||
const hanaTask = tasks.find(task => task.for === BUILD_TASK_HANA) | ||
const models = tasks.reduce((acc, task) => { | ||
if (task.options) { | ||
if (Array.isArray(task.options.model)) { | ||
task.options.model.forEach(model => acc.add(model)) | ||
} else if (task.options.model) { | ||
acc.add(task.options.model) | ||
} | ||
} | ||
return acc | ||
}, new Set()) | ||
const modelPaths = this.cds.resolve(Array.from(models), this.buildOptions) | ||
const modelPaths = this.resolveModel() | ||
if (!modelPaths || modelPaths.length === 0) { | ||
@@ -59,4 +41,7 @@ this.logger.log("[cds] - no model found, skipping build") | ||
// custom build tasks for srv and db modules might be defined | ||
const tenantDbPath = await this._getHanaTenantDbPath(modelPaths) | ||
// validate extension whitlists defined for this SaaS application | ||
this._validateExtensionWhitelists(model, hanaTask) | ||
this._validateExtensionWhitelists(model, tenantDbPath) | ||
@@ -81,3 +66,3 @@ // copy base model sources | ||
// copy native hana content and templates | ||
await this._copyNativeContent(this.task.src, this.task.dest, hanaTask) | ||
await this._copyNativeContent(this.task.src, this.task.dest, tenantDbPath) | ||
} | ||
@@ -99,3 +84,3 @@ | ||
async _copyNativeContent(src, dest, hanaTask) { | ||
async _copyNativeContent(src, dest, tenantDbPath) { | ||
// copying tmplates | ||
@@ -108,9 +93,6 @@ const tplSrc = path.join(src, FOLDER_TEMPLATES) | ||
if (hanaTask) { | ||
// copy hana related content from db module | ||
const dbSrc = path.resolve(src, hanaTask.src) | ||
const dbDest = path.resolve(dest, path.relative(this.buildOptions.root, dbSrc)) | ||
await this._copyNativeHanaContent(dbSrc, dbDest) | ||
} else { | ||
this.logger.warn('[cds] - no db module found, skip copying native HANA artefacts') | ||
if (tenantDbPath) { | ||
// copy any static HANA artefacts, e.g. .csv files | ||
const dbDest = path.resolve(dest, path.relative(this.buildOptions.root, tenantDbPath)) | ||
await this._copyNativeHanaContent(tenantDbPath, dbDest) | ||
} | ||
@@ -147,3 +129,3 @@ } | ||
*/ | ||
_validateExtensionWhitelists(csn, hanaTask) { | ||
_validateExtensionWhitelists(csn, tenantDbPath) { | ||
let entityWhitelist = this.env.get("mtx.entity-whitelist") | ||
@@ -153,5 +135,4 @@ let serviceWhitelist = this.env.get("mtx.service-whitelist") | ||
// for java projects mtx configuration is part of sidecar config | ||
if (!entityWhitelist && !serviceWhitelist) { | ||
const dbPath = path.resolve(this.buildOptions.root, hanaTask ? hanaTask.src : this.env.folders.db) | ||
const dbEnv = this.env.for("cds", dbPath) | ||
if (!entityWhitelist && !serviceWhitelist && tenantDbPath) { | ||
const dbEnv = this.env.for("cds", tenantDbPath) | ||
if (dbEnv && dbEnv.get("mtx")) { | ||
@@ -194,39 +175,34 @@ entityWhitelist = dbEnv.get("mtx.entity-whitelist") | ||
* A build task of type 'hana' is enforced in order to copy existing native hana artefacts later on. | ||
* | ||
* @returns {Array<Object>} the array of build tasks. | ||
* | ||
* @param {Array<string>} modelPaths - the .cds root model files defined by this build task's model options | ||
* @returns {string} the src folder of the tenant db module | ||
*/ | ||
async _getTasks() { | ||
async _getHanaTenantDbPath(modelPaths) { | ||
let tasks = this.buildOptions.tasks || [] | ||
tasks = tasks.filter(task => task.for !== BUILD_TASK_MTX) | ||
if (tasks.length === 0 || !tasks.find(task => task.for === BUILD_TASK_HANA)) { | ||
const dbKind = this.env.get("requires.db.kind") | ||
// ensure that the hana task is contained even if this mtx task has been executed solely using "cds build --for mtx" | ||
if (!tasks.find(task => task.for === BUILD_TASK_HANA)) { | ||
//mtx task might have been executed as separate task | ||
const buildTaskFactory = new BuildTaskFactory(this.logger, this.cds) | ||
let derivedTasks = [] | ||
try { | ||
if (dbKind !== "hana") { | ||
// temporarily switch db.kind to 'hana' to make sure a hana build task | ||
// is created as mtx is only supporting hana | ||
setProperty(this.env, "requires.db.kind", "hana") | ||
} | ||
derivedTasks = await buildTaskFactory._createTasksFromConfig(this.buildOptions) | ||
} | ||
finally { | ||
if (dbKind !== "hana") { | ||
setProperty(this.env, "requires.db.kind", dbKind) | ||
} | ||
} | ||
tasks = buildTaskFactory._getExistingTasks(this.buildOptions) | ||
if (tasks.length === 0) { | ||
// no custom tasks, use derived tasks | ||
tasks = derivedTasks | ||
tasks = await buildTaskFactory._createTasksFromConfig(this.buildOptions) | ||
} | ||
else { | ||
// merge hana task into custom tasks | ||
// assuming that custom tasks have been defined for service modules | ||
tasks = tasks.concat(derivedTasks.find(task => task.for === BUILD_TASK_HANA)) | ||
} | ||
// the SaaS app might use a tenant aware db as well as a shared db deployed using static hdi-deployer | ||
// pick the hana build task refering to the tenant aware db - the src path has to be contained in this build task's model options | ||
const hanaDbPaths = tasks.filter(task => task.for === BUILD_TASK_HANA).map(hanaTask => path.resolve(this.buildOptions.root, hanaTask.src || this.env.folders.db)) | ||
let tenantDbPath = hanaDbPaths.find(hanaDbPath => hanaDbPaths.length === 1 || modelPaths.some(modelPath => path.dirname(modelPath) === hanaDbPath)) | ||
if (!tenantDbPath) { | ||
tenantDbPath = path.join(this.buildOptions.root, this.env.folders.db) | ||
if (hanaDbPaths.length === 0) { | ||
this.pushMessages(new BuildMessage(`[cds] - no 'hana' build task found, use default location '${tenantDbPath}'`, SEVERITY_WARNING)) | ||
} else { | ||
this.pushMessages(new BuildMessage(`[cds] - no 'hana' build task found matching model scope '${this.task.options.model}', use default location '${tenantDbPath}'`, SEVERITY_WARNING)) | ||
} | ||
} | ||
return tasks | ||
return tenantDbPath | ||
} | ||
} | ||
module.exports = MtxModuleBuilder |
@@ -6,3 +6,3 @@ const cdsc = require ('@sap/cds-compiler') | ||
if (typeof _o === 'string') _o = {version:_o} | ||
const o = _4odata ? { ...env.effective.cdsc(_o), ..._o, ...env.cdsc } : { ..._o, ...env.cdsc } | ||
const o = _4odata ? { ..._o, ...env.effective.cdsc(_o) } : { ..._o, ...env.cdsc } | ||
const names = o.names || o.sql_mapping || env.sql.names | ||
@@ -9,0 +9,0 @@ if (names !== 'plain') o.sqlMapping = names |
@@ -1,2 +0,2 @@ | ||
const cds = require ('@sap/cds-reflect'), {lazified} = cds | ||
const {lazify:lazified} = require ('../core/lazy') | ||
require = lazified (module) // eslint-disable-line | ||
@@ -70,5 +70,5 @@ | ||
if (!cds.env.features.snapi) { | ||
if (!global.cds.env.features.snapi) { | ||
if (!global.it) console.warn ('[cds] - features.snapi not set -> using old compiler...') // eslint-disable-line no-console | ||
require('./old')(compile) | ||
} |
@@ -56,4 +56,3 @@ const cdsc = require ('@sap/cds-compiler') | ||
// adjust for existing/former implementation | ||
const {snapi} = require('..').env.features | ||
if (!snapi) { | ||
if (!global.cds.env.features.snapi) { | ||
const _parse = Object.assign ((...args) => _parse.cdl(...args), parse, { | ||
@@ -60,0 +59,0 @@ cdl: (...args) => (_parse.cdl = require('./cdsv').parse_only) (...args), |
@@ -1,16 +0,24 @@ | ||
exports.for = (cds,v) => { if (cds.env.features.cls) { | ||
exports.for = (cds,v) => { | ||
const [,major,minor] = /v(\d+)\.(\d+)/.exec(process.version) | ||
if (major > 12 || major == 12 && minor >= 18) { | ||
if (cds.env.features.cls) { | ||
const { executionAsyncResource:current, createHook } = module.require ('async_hooks') | ||
const _context = Symbol('cds.req.context') | ||
const _context = Symbol('cds.context') | ||
createHook ({ init(id,t,tid, res) { | ||
res[_context] = current()[_context] | ||
const cr = current(); if (!cr) return | ||
const ctx = cr[_context] | ||
if (ctx) res[_context] = ctx | ||
}}).enable() | ||
Reflect.defineProperty (cds,'context',{ enumerable:1, | ||
set(v) { current()[_context] = v && v.context || v }, | ||
get:()=> current()[_context], | ||
set(v) { | ||
const cr = current() | ||
if (cr) cr[_context] = v && v.context || v | ||
}, | ||
get() { | ||
const cr = current() | ||
if (cr) return cr[_context] | ||
else return undefined | ||
}, | ||
}) | ||
@@ -29,2 +37,2 @@ | ||
}} | ||
} |
@@ -53,31 +53,36 @@ const cds = require('..') | ||
exports.include_external_entities_in = function (model) { | ||
if (model._mocked) return model; else Object.defineProperty(model,'_mocked',{value:true}) | ||
for (let each in model.definitions) { | ||
const def = model.definitions[each] | ||
if (def['@cds.persistence.mock'] === false) continue | ||
if (def['@cds.persistence.skip'] === true) { | ||
delete def['@cds.persistence.skip'] | ||
if (model._xsn) delete model._xsn.definitions[each]['@cds.persistence.skip'] | ||
} | ||
const def = model.definitions[each] | ||
if (def['@cds.persistence.mock'] === false) continue | ||
if (def['@cds.persistence.skip'] === true) { | ||
DEBUG && DEBUG ('including mocked', each) | ||
delete def['@cds.persistence.skip'] | ||
if (model._xsn) delete model._xsn.definitions[each]['@cds.persistence.skip'] | ||
} | ||
} | ||
exports.exclude_external_entities_in (model,'if-bound') | ||
return model | ||
} | ||
exports.exclude_external_entities_in = function (model) { // NOSONAR | ||
for (let [each,{service=each}] of Object.entries (cds.requires)) { | ||
if (!each.model) continue //> not for internal services like cds.requires.odata | ||
exports.exclude_external_entities_in = function (csn,_bound) { // NOSONAR | ||
for (let [each,{service=each,model,credentials}] of Object.entries (cds.requires)) { | ||
if (!model) continue //> not for internal services like cds.requires.odata | ||
if (_bound && !credentials) continue | ||
DEBUG && DEBUG ('excluding external entities for', service, '...') | ||
const prefix = service+'.' | ||
for (let each in model.definitions) if (each.startsWith(prefix)) { | ||
for (let each in csn.definitions) if (each.startsWith(prefix)) { | ||
DEBUG && DEBUG ('excluding external entity', each) | ||
_exclude (each) | ||
} | ||
} | ||
return model | ||
return csn | ||
function _exclude (each) { | ||
const def = model.definitions[each]; if (def.kind !== 'entity') return | ||
const def = csn.definitions[each]; if (def.kind !== 'entity') return | ||
def['@cds.persistence.skip'] = true | ||
if (model._xsn) model._xsn.definitions[each]['@cds.persistence.skip'] = {val:true} | ||
DEBUG && DEBUG ('excluded imported', each) | ||
if (csn._xsn) csn._xsn.definitions[each]['@cds.persistence.skip'] = {val:true} | ||
// propagate to all views... | ||
for (let other in model.definitions) { | ||
const q = model.definitions[other].query | ||
for (let other in csn.definitions) { | ||
const q = csn.definitions[other].query | ||
if (q && q.SELECT && q.SELECT.from && q.SELECT.from.ref && q.SELECT.from.ref[0] === each) | ||
@@ -84,0 +89,0 @@ _exclude (other) |
@@ -0,9 +1,15 @@ | ||
const [,major,minor] = /v(\d+)\.(\d+)/.exec(process.version) | ||
const production = process.env.NODE_ENV === 'production' | ||
module.exports = { | ||
features: { | ||
fiori_preview: true, | ||
cls: major > 12 || major == 12 && minor >= 18, | ||
fiori_preview: !production, | ||
fiori_routes: !production, | ||
in_memory_db: !production, | ||
mocked_bindings: !production, | ||
skip_unused: true, | ||
one_model: true, | ||
snapi: true, | ||
cls: true, | ||
localized: true, | ||
@@ -116,3 +122,3 @@ skip_reclassification: false, | ||
// proxies:false, | ||
}, | ||
}, | ||
v4: { | ||
@@ -129,3 +135,5 @@ version: 'v4', | ||
structs:true, | ||
refs:false, //> proxies:false, | ||
refs:false, | ||
proxies:false, | ||
xrefs:false, | ||
safe_ax:true, | ||
@@ -137,3 +145,5 @@ }, | ||
structs:true, | ||
refs:true, //> proxies:true, | ||
refs:true, | ||
proxies:true, | ||
xrefs:true, | ||
safe_ax:true, | ||
@@ -140,0 +150,0 @@ }, |
@@ -352,25 +352,7 @@ const DEFAULTS = require('./defaults'), defaults = require.resolve ('./defaults') | ||
if (flavor) odata.version = flavor.version | ||
///////////////////////////////////////////////// | ||
// Interims compatibility to cds-runtime... | ||
if (odata.format === 'structured') { | ||
odata.structs = true | ||
if (odata._foreignKeys === undefined) odata.refs = true | ||
} | ||
if (odata._foreignKeys !== undefined) odata.refs = !odata._foreignKeys | ||
if (odata.__proxies !== undefined) odata.proxies = odata.__proxies | ||
///////////////////////////////////////////////// | ||
if (odata.structs && odata.refs === undefined) odata.refs = false //> decouple .structs and .refs | ||
if (odata.refs && odata.proxies === undefined) odata.proxies = false //> decouple .refs and .proxies | ||
// if (odata.structs && odata.refs === undefined) odata.refs = false //> decouple .structs and .refs | ||
// if (odata.refs && odata.proxies === undefined) odata.proxies = false //> decouple .refs and .proxies | ||
// if (odata.refs && odata.xrefs === undefined) odata.xrefs = false //> decouple .refs and .xrefs | ||
delete odata.flavors | ||
delete odata.flavor | ||
///////////////////////////////////////////////// | ||
// Interims compatibility to cds-runtime... | ||
odata.format = odata.structs ? 'structured' : 'flat' | ||
odata._foreignKeys = !odata.refs | ||
odata.__proxies = odata.proxies | ||
///////////////////////////////////////////////// | ||
return odata | ||
@@ -387,5 +369,7 @@ } | ||
if ((x = odata.refs) !== undefined && !('odataForeignKeys' in cdsc)) cdsc.odataForeignKeys = !x | ||
if ((x = odata.proxies) !== undefined) { | ||
if (!('odataProxies' in cdsc)) cdsc.odataProxies = x | ||
if (!cdsc.beta || !('odataProxies' in cdsc.beta)) (cdsc.beta || (cdsc.beta={})).odataProxies = x | ||
if ((x = odata.xrefs) !== undefined && !('odataXServiceRefs' in cdsc)) cdsc.odataXServiceRefs = x | ||
if ((x = odata.proxies) !== undefined && !('odataProxies' in cdsc)) cdsc.odataProxies = x | ||
if (compiler_v1) { | ||
if (cdsc.odataProxies || cdsc.odataXServiceRefs) (cdsc.beta || (cdsc.beta={})).odataProxies = true | ||
if (cdsc.odataXServiceRefs) delete cdsc.odataProxies | ||
} | ||
@@ -395,4 +379,6 @@ return cdsc | ||
const compiler_v1 = require('@sap/cds-compiler/package.json').version.startsWith('1.') | ||
/** @type Config & typeof DEFAULTS */ | ||
module.exports = Config.prototype.for ('cds', process_cwd) | ||
/* eslint no-console:0 */ |
if (global.cds) Object.assign(module,{exports:global.cds}) ; else { | ||
const core = require ('@sap/cds-reflect/lib'), { extend, lazified } = core | ||
const { extend, lazify, lazify:lazified } = require ('./core/lazy') | ||
const core = new class cds extends require('events'){} | ||
const c = lazy => cds.builtin.classes [lazy] | ||
require = lazified (module) // eslint-disable-line | ||
@@ -9,2 +11,21 @@ | ||
// Builtin types and classes | ||
builtin: require ('./core/csn'), | ||
service: lazy => extend (cds.builtin.classes.service) .with (lazified ({ | ||
bindings: require ('./srv/bindings'), | ||
factory: require('./srv/factory'), | ||
providers:[], | ||
impl: x=>x, | ||
})), | ||
Association:c, Composition:c, | ||
entity:c, | ||
type:c, | ||
array:c, | ||
struct:c, | ||
// Model Reflection | ||
reflect: require ('./core/reflect'), | ||
linked: require ('./core/linked'), | ||
infer: require ('./core/infer'), | ||
// Loading and Compiling Models | ||
@@ -18,4 +39,3 @@ model: undefined, | ||
// Providing and Consuming Services | ||
services: new core.Iterable, | ||
service: require ('./srv'), | ||
services: new class Iterable { *[Symbol.iterator]() {for (let e in this) yield this[e]}}, | ||
serve: require ('./serve'), | ||
@@ -43,4 +63,5 @@ server: require ('../server'), | ||
test: require ('./utils/tests'), | ||
log: require ('./utils/logging'), | ||
debug (..._) { const L = this.log(..._); return L._debug && L.debug }, | ||
log: require ('./utils/logging'), debug: lazy => cds.log.debug, | ||
clone: m => JSON.parse (JSON.stringify(m)), | ||
lazified, lazify, extend, | ||
@@ -47,0 +68,0 @@ // Configuration & Information |
@@ -385,3 +385,10 @@ /* eslint-disable no-inner-declarations */ | ||
module.exports = Object.assign (()=>exports, lib.statements, { | ||
module.exports = Object.assign (function(){ | ||
// eslint-disable-next-line no-console | ||
console.trace(` | ||
'const { SELECT, ... } = srv.ql(req)' is deprecated and superceded by 'cds.context'. | ||
Please use 'cds.tx(req).run( <query> )' instead. | ||
`) | ||
return module.exports | ||
}, lib.statements, { | ||
SELECT: SELECT._api(), | ||
@@ -394,11 +401,11 @@ INSERT: INSERT._api(), | ||
// if (cds.env.features.cls || cds.env.features.debug_queries) { | ||
// const { AsyncResource } = require('async_hooks') | ||
// const { then } = Query.prototype | ||
// cds.extend (Query) .with (class { | ||
// get then(){ | ||
// const q = new AsyncResource('cds.Query') | ||
// return (r,e) => q.runInAsyncScope (then,this,r,e) | ||
// } | ||
// }) | ||
// } | ||
if (cds.env.features.cls && cds.env.features.debug_queries) { | ||
const { AsyncResource } = require('async_hooks') | ||
const { then } = Query.prototype | ||
cds.extend (Query) .with (class { | ||
get then(){ | ||
const q = new AsyncResource('cds.Query') | ||
return (r,e) => q.runInAsyncScope (then,this,r,e) | ||
} | ||
}) | ||
} |
@@ -0,4 +1,4 @@ | ||
const {env:{features}} = require('..') | ||
const async_events = { succeeded:1, failed:1, done:1 } | ||
const { EventEmitter } = require('events') | ||
const { inspect } = require('util') | ||
const User = require ('./user') | ||
@@ -14,7 +14,20 @@ | ||
constructor(_) { Object.assign (this,this._=_||{}) } | ||
toString() { return `${this.event} ${this.path}` } | ||
[inspect.custom]() { return `${this.constructor.name} ${inspect(this._)}` } | ||
constructor(_={}) { | ||
Object.assign (this, this._set('_',_)) | ||
} | ||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||
// REVISIT: temporary workarounds until https://github.wdf.sap.corp/cdx/cds-runtime/pull/964 is released | ||
// Remove this getter when https://github.wdf.sap.corp/cdx/cds-runtime/pull/964 is released | ||
get statements(){ return global.cds.ql } | ||
// Keep method _set, but remove 'enumerable:true' when https://github.wdf.sap.corp/cdx/cds-runtime/pull/964 is released | ||
_set (property, value) { | ||
Object.defineProperty (this, property, {value,writable:true,enumerable:true}) | ||
return value | ||
} | ||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||
// | ||
@@ -25,3 +38,3 @@ // Emitting and listening to succeeded / failed / done events | ||
/** @returns {EventEmitter} */ get emitter() { | ||
return this._emitter || (this._emitter = this.context ? this.context.emitter : new EventEmitter) | ||
return this._emitter || (this._emitter = this._propagated('emitter') || new EventEmitter) | ||
} | ||
@@ -56,2 +69,10 @@ | ||
set context(c) { if (c) super.context = c } | ||
get context() { return this } | ||
set id(c) { if (c) super.id = c } | ||
get id() { | ||
return this.id = this._propagated('id') || this.headers[ 'x-correlation-id' ] | ||
} | ||
set tenant(t) { | ||
@@ -61,3 +82,3 @@ if (t) super.tenant = this.user.tenant = t | ||
get tenant() { | ||
return this.tenant = this.context ? this.context.tenant : this.user.tenant | ||
return this.tenant = this._propagated('tenant') || this.user.tenant | ||
} | ||
@@ -71,3 +92,3 @@ | ||
get user() { | ||
return this.user = this.context ? this.context.user : new User | ||
return this.user = this._propagated('user') || new User | ||
} | ||
@@ -79,7 +100,7 @@ | ||
get locale() { | ||
return this.locale = this.context ? this.context.locale : this.user.locale | ||
return this.locale = this._propagated('locale') || this.user.locale | ||
} | ||
get timestamp() { | ||
return super.timestamp = this.context ? this.context.timestamp : Date.now() | ||
return super.timestamp = this._propagated('timestamp') || Date.now() | ||
} | ||
@@ -92,4 +113,3 @@ | ||
} else { | ||
const headers = {} | ||
const outer = this.context && this.context.headers | ||
const headers={}, outer = this._propagated('headers') | ||
if (outer) for (let each of EventContext.propagateHeaders) { | ||
@@ -107,2 +127,7 @@ if (each in outer) headers[each] = outer[each] | ||
_propagated (p) { | ||
const ctx = this.context | ||
if (ctx !== this) return ctx[p] | ||
} | ||
set _tx(tx) { | ||
@@ -114,5 +139,8 @@ Object.defineProperty (this,'_tx',{value:tx}) //> allowed only once! | ||
// REVISIT: Eliminate req._children | ||
const reqs = ctx._children || (ctx._children = {}) | ||
const all = reqs[tx.name] || (reqs[tx.name] = []) | ||
all.push(this) | ||
// only collect children if integrity checks are active | ||
if (features.assert_integrity !== false) { | ||
const reqs = ctx._children || (ctx._children = {}) | ||
const all = reqs[tx.name] || (reqs[tx.name] = []) | ||
all.push(this) | ||
} | ||
} | ||
@@ -123,4 +151,6 @@ } | ||
/** REVISIT: remove -> @deprecated */ | ||
get _model() { return super._model = this._tx && this._tx.model || this.context && this.context._model } | ||
set _model(m){ super._model = m } | ||
get _model() { | ||
return super._model = this._tx && this._tx.model || this._propagated('_model') | ||
} | ||
} | ||
@@ -127,0 +157,0 @@ |
@@ -7,5 +7,10 @@ const EventContext = require ('./context') | ||
*/ | ||
class EventMessage extends EventContext {} | ||
class EventMessage extends EventContext { | ||
static for (eve) { | ||
if (eve instanceof this) return eve | ||
if (typeof eve === 'object') return new this(eve) | ||
} | ||
} | ||
module.exports = exports = EventMessage | ||
exports.Context = EventContext |
@@ -12,3 +12,3 @@ const { Responses, Errors } = require('./res') | ||
get method() { | ||
return super.method = Crud2Http[this.event] || this.event | ||
return this._set ('method', Crud2Http[this.event] || this.event) | ||
} | ||
@@ -18,17 +18,13 @@ | ||
get event() { | ||
if (this._.method) return super.event = Http2Crud[this._.method] || this._.method | ||
if (this.query) return super.event = Query2Crud(this.query) | ||
return super.event = undefined | ||
if (this._.method) return this._set ('event', Http2Crud[this._.method] || this._.method) | ||
if (this.query) return this._set ('event', Query2Crud(this.query)) | ||
return this._set ('event', undefined) | ||
} | ||
set entity(e) { | ||
if (e) super.entity = e.name ? (this.target = e).name : e | ||
} | ||
set entity(e) { if (e) super.entity = e.name ? (this.target = e).name : e } | ||
get entity() { | ||
return super.entity = this.target && this.target.name | ||
return this._set ('entity', this.target && this.target.name) | ||
} | ||
set path(p) { | ||
if (p) super.path = p.startsWith('/') ? p.slice(1) : p | ||
} | ||
set path(p) { if (p) super.path = p.startsWith('/') ? p.slice(1) : p } | ||
get path() { | ||
@@ -38,19 +34,21 @@ const {_} = this | ||
const q = this.query | ||
if (q.SELECT) return super.path = _path4 (q.SELECT,'from') | ||
if (q.INSERT) return super.path = _path4 (q.INSERT,'into') | ||
if (q.UPDATE) return super.path = _path4 (q.UPDATE,'entity') | ||
if (q.DELETE) return super.path = _path4 (q.DELETE,'from') | ||
if (q.SELECT) return this._set ('path', _path4 (q.SELECT,'from')) | ||
if (q.INSERT) return this._set ('path', _path4 (q.INSERT,'into')) | ||
if (q.UPDATE) return this._set ('path', _path4 (q.UPDATE,'entity')) | ||
if (q.DELETE) return this._set ('path', _path4 (q.DELETE,'from')) | ||
} | ||
if (_.target) return super.path = _.target.name | ||
if (_.entity) return super.path = _.entity.name || _.entity | ||
else return super.path = undefined | ||
if (_.target) return this._set ('path', _.target.name) | ||
if (_.entity) return this._set ('path', _.entity.name || _.entity) | ||
else return this._set ('path', undefined) | ||
} | ||
set data(d) { if(d) super.data = d } //> undefined data is frequently passed in via ._ | ||
set data(d) { if (d) super.data = d } | ||
get data() { | ||
const q = this.query; if (!q) return super.data = undefined | ||
const { INSERT:I, UPDATE:U } = q | ||
if (I) return super.data = I.rows || I.values || I.entries && (I.entries.length > 1 ? I.entries : I.entries[0]) ||{} | ||
if (U) return super.data = U.data ||{} | ||
return super.data = {} | ||
const q = this.query | ||
if (!q) return this._set ('data', undefined) | ||
const I = q.INSERT | ||
if (I) return this._set ('data', I.rows || I.values || I.entries && (I.entries.length > 1 ? I.entries : I.entries[0]) ||{}) | ||
const U = q.UPDATE | ||
if (U) return this._set ('data', U.data ||{}) | ||
return this._set ('data', {}) | ||
} | ||
@@ -69,9 +67,10 @@ | ||
// Lazily create message collectors for .errors and .messages | ||
/** @private */ get _messages() { return this.messages = super._messages = new Responses } | ||
/** @private */ get _errors() { return this.errors = super._errors = new Errors } | ||
/** @private */ get _messages() { return this.messages = this._set ('_messages', new Responses) } | ||
/** @private */ get _errors() { return this.errors = this._set ('_errors', new Errors) } | ||
// REVISIT: Used for request logging in cds.server | ||
// REVISIT: _.odataReq stuff should go into subclass ODataRequest | ||
get _path() { return super._path = this._.odataReq ? this._.odataReq._url.pathname : this._.req && this._.req.path } | ||
get _query() { return super._query = this._.odataReq ? this._.odataReq._queryOptions : this._.req && this._.req.query } | ||
get _path() { return this._set ('_path', this._.odataReq ? this._.odataReq._url.pathname : this._.req && this._.req.path) } | ||
get _query() { return this._set ('_query', this._.odataReq ? this._.odataReq._queryOptions : this._.req && this._.req.query) } | ||
} | ||
@@ -103,12 +102,15 @@ | ||
const SQL2Crud = { | ||
SELECT: 'READ', | ||
INSERT: 'CREATE', | ||
UPDATE: 'UPDATE', | ||
DELETE: 'DELETE', | ||
CREATE: 'CREATE ENTITY', | ||
DROP: 'DROP ENTITY', | ||
SELECT: 'READ', | ||
INSERT: 'CREATE', | ||
UPDATE: 'UPDATE', | ||
DELETE: 'DELETE', | ||
BEGIN: 'BEGIN', | ||
COMMIT: 'COMMIT', | ||
ROLLBACK: 'ROLLBACK', | ||
CREATE: 'CREATE ENTITY', | ||
DROP: 'DROP ENTITY', | ||
} | ||
const Query2Crud = (q) => { | ||
if (typeof q === 'string') return q.match(/^\s*(\w+)/) && SQL2Crud[RegExp.$1] || q | ||
if (typeof q === 'string') return SQL2Crud[q] || /^\s*(\w+)/.test(q) && SQL2Crud[RegExp.$1] || q | ||
else for (let each in q) if (each in SQL2Crud) return SQL2Crud[each] | ||
@@ -136,8 +138,2 @@ } | ||
// REVISIT: remove after req.statements isn't used anymore in @sap/cds-runtime | ||
statements: { | ||
get() { return super.statements = global.cds.ql }, | ||
set(ql) { super.statements = ql }, | ||
}, | ||
}) |
@@ -42,4 +42,2 @@ const { ProtocolAdapter } = require('./srv/adapters') | ||
return cds.load(from) | ||
// const cached = cds_serve, key = `${from}` | ||
// return cached[key] || (cached[key] = cds.load(from)) | ||
}) | ||
@@ -79,3 +77,3 @@ | ||
// 5) Pass 2: Finalize service bootrapping by calling their impl functions. | ||
// 5) Pass 2: Finalize service bootstrapping by calling their impl functions. | ||
// Note: doing that in a second pass guarantees all own services are in | ||
@@ -82,0 +80,0 @@ // cds.services, so they'll be found when they cds.connect to each others. |
@@ -15,3 +15,3 @@ const add_methods_to = require ('./Service-methods') | ||
/** | ||
* Subclasses may override this to prepare the given model | ||
* Subclasses may override this to prepare the given model appropriately | ||
*/ | ||
@@ -24,35 +24,17 @@ set model(m) { | ||
/** | ||
* Subclasses may override this to free additional resources | ||
* Messaging API to emit asynchronous event messages, i.e. instances of `cds.Event`. | ||
*/ | ||
disconnect (tenant) { // eslint-disable-line no-unused-vars | ||
if (this === cds.db) cds.db = undefined //> REVISIT: should go into DatabaseService | ||
delete cds.services[this.name] | ||
} | ||
/** | ||
* Emits asynchronous event messages, i.e. instances of `cds.Event`, constructed from given arguments. | ||
*/ | ||
emit (event, data, headers) { | ||
if (event.query) return this.send (event) // REVISIT: Remove that when new runtime & tests are released | ||
if (event in sync_events) return this.send ({ event, data, headers }) // REVISIT: Remove that when new runtime & tests are released | ||
if (event instanceof cds.Event) return this.dispatch (event) | ||
if (typeof event === 'object') { | ||
if ((event.event || event.method) in sync_events) return this.dispatch (new cds.Request(event)) | ||
return this.dispatch (new cds.Event(event)) | ||
} | ||
else return this.dispatch (new cds.Event ({ event, data, headers })) | ||
const res = this._compat_sync (event, data, headers); if (res) return res | ||
const eve = cds.Event.for(event) || new cds.Event({ event, data, headers }) | ||
return this.dispatch (eve) | ||
} | ||
/** | ||
* Sends synchronous requests, i.e. instances of `cds.Request`, constructed from given arguments. | ||
* REST-style API to send synchronous requests... | ||
*/ | ||
send (method, path, data, headers) { | ||
if (method instanceof cds.Request) return this.dispatch (method) | ||
if (typeof method === 'object') return this.dispatch (new cds.Request(method)) | ||
else return this.dispatch (new cds.Request ({ method, path, data, headers })) | ||
const req = cds.Request.for(method) || new cds.Request({ method, path, data, headers }) | ||
return this.dispatch (req) | ||
} | ||
/** | ||
* REST-style API... | ||
*/ | ||
get (path, data) { return is_rest(path) ? this.send('GET', path,data) : this.read (path, data) } | ||
@@ -65,5 +47,8 @@ put (path, data) { return is_rest(path) ? this.send('PUT', path,data) : this.update (path, data) } | ||
/** | ||
* Querying API... | ||
* Querying API to send synchronous requests... | ||
*/ | ||
run (query,data) { return this.send (data ? { query, data } : { query }) } | ||
run (query, data) { | ||
const req = new cds.Request ({ query, data }) | ||
return this.dispatch (req) | ||
} | ||
read (...args) { return SELECT.from(...args).bind(this) } | ||
@@ -73,2 +58,8 @@ insert (...args) { return INSERT(...args).bind(this) } | ||
update (...args) { return UPDATE.entity(...args).bind(this) } | ||
/** | ||
* Streaming API variant of .run(). Subclasses should override this to support real streaming. | ||
* The default implementation doesn't stream, but simply invokes the callback on each row. | ||
* The callback function is invoked with (row, index). | ||
*/ | ||
foreach (query, data, callback) { | ||
@@ -89,5 +80,13 @@ if (!callback) [ data, callback ] = [ undefined, data ] | ||
|| this.model && this.model.namespace | ||
|| this.name !== 'db' && !/[^\w.]/.test(this.name) && this.name //> REVISIT: should go into DatabaseService | ||
|| this.name !== 'db' && !/\W/.test(this.name) && this.name || undefined | ||
} | ||
/** | ||
* Subclasses may override this to free private resources | ||
*/ | ||
disconnect (tenant) { // eslint-disable-line no-unused-vars | ||
if (this === cds.db) cds.db = undefined //> REVISIT: should go into DatabaseService | ||
delete cds.services[this.name] | ||
} | ||
} | ||
@@ -97,2 +96,3 @@ module.exports = Service | ||
Service.prototype.dispatch = require('./Service-dispatch') | ||
Service.prototype._compat_sync = require('./Service-compat') | ||
Service.prototype._implicit_next = cds.env.features.implicit_next | ||
@@ -105,2 +105,1 @@ Service.prototype._is_service_instance = Service._is_service_class = true //> for factory | ||
const is_rest = x => x && typeof x === 'string' && x[0] === '/' | ||
const sync_events = { CREATE:1, READ:1, UPDATE:1, DELETE:1, INSERT:1, GET:1, PUT:1, POST:1, PATCH:1, BEGIN:1, COMMIT:1, ROLLBACK:1 } |
@@ -7,3 +7,3 @@ /** | ||
*/ | ||
module.exports = async function srv_dispatch (req) { //NOSONAR | ||
module.exports = async function dispatch (req) { //NOSONAR | ||
@@ -14,4 +14,6 @@ // Ensure we are in a proper transaction | ||
if (txc) return this.tx(txc).dispatch(req) | ||
const tx = this.tx(req) //> else start a new top-level tx, which we need to commit/rollback | ||
return tx.dispatch(req) .then (tx.commit, tx.rollback) | ||
else try { //> start a new top-level tx, which we need to commit/rollback | ||
const tx = cds.context = this.tx(req) | ||
return tx.dispatch(req) .then (tx.commit, tx.rollback) | ||
} finally { cds.context = txc } | ||
} | ||
@@ -89,3 +91,4 @@ // `this` is a tx from now on... | ||
if (srv.namespace) { // ensure fully-qualified names | ||
if (req._.path) _ensure_fqn (req,'path',srv) | ||
const p = req._.path | ||
if (p) _ensure_fqn (req,'path',srv, p.startsWith('/') ? p.slice(1) : p) | ||
else if (q.SELECT) _ensure_fqn (q.SELECT,'from',srv) | ||
@@ -102,4 +105,3 @@ else if (q.INSERT) _ensure_fqn (q.INSERT,'into',srv) | ||
const _ensure_fqn = (x,p,srv) => { | ||
const name = x[p] | ||
const _ensure_fqn = (x,p,srv, name = x[p]) => { | ||
if (typeof name === 'string') { | ||
@@ -106,0 +108,0 @@ if (srv.model && name in srv.model.definitions) return |
@@ -6,3 +6,3 @@ const cds = require('..') | ||
constructor (name) { | ||
this._handlers = { on:[], before:[], after:[], _initial:[] } | ||
this._handlers = { on:[], before:[], after:[], _initial:[] } //, _error:[] } | ||
this.name = name | ||
@@ -16,3 +16,3 @@ } | ||
const handler = (req) => req.reject( | ||
405, `Event "${event}" not allowed${req.target ? ` for entity "${req.target.name}"`: ''}.` | ||
405, `Event "${req.event}" not allowed${req.target ? ` for entity "${req.target.name}"`: ''}.` | ||
) | ||
@@ -24,3 +24,3 @@ handler._initial = true | ||
async prepend (...impl_functions) { | ||
const {_handlers} = this, _new = this._handlers = { on:[], before:[], after:[], _initial:[] } | ||
const {_handlers} = this, _new = this._handlers = { on:[], before:[], after:[], _initial:[] } //, _error:[] } | ||
await Promise.all (impl_functions.map (fn => is_impl(fn) && fn.call (this,this))) | ||
@@ -71,3 +71,6 @@ for (let each in _new) if (_new[each].length) _handlers[each] = [ ..._new[each], ..._handlers[each] ] | ||
if (handler._initial) phase = '_initial' | ||
srv.subscribe (phase, event, path, handler) | ||
// REVISIT + PARKED: Should we add srv.on('error',(err)=>...)? | ||
// else if (event === 'error') phase = '_error' | ||
else if (phase === 'on') cds.emit('subscribe',srv,event) | ||
srv._handlers[phase].push ({ event, path, handler }) | ||
} | ||
@@ -77,8 +80,2 @@ return srv | ||
// REVISIT: This exists for messaging impl only -> we should get rid of it and re-inline it to _register | ||
EventHandlers.prototype.subscribe = function (phase, event, path, handler) { | ||
this._handlers[phase].push ({ event, path, handler }) | ||
if (phase === 'on') cds.emit('subscribe',this,event) | ||
} | ||
const _expected = (arg,type) => cds.error(`Expected ${type} for argument '${Object.keys(arg)[0]}' but got: ${Object.values(arg)[0]}`) | ||
@@ -85,0 +82,0 @@ const is_class = x => typeof x === 'function' && x.prototype && /^class\b/.test(x) |
@@ -9,3 +9,3 @@ const cds = require('../index'), { Context } = cds.Request | ||
*/ | ||
module.exports = function (req) { const srv = this | ||
module.exports = function tx (req) { const srv = this | ||
if (srv.context) return srv | ||
@@ -18,3 +18,3 @@ if (!req) { | ||
// called for a nested req -> nested tx | ||
if (req.context) return NestedTransaction.for (srv, req.context) | ||
if (req.context !== req) return NestedTransaction.for (srv, req.context) | ||
// called for a req with a root tx -> nested tx | ||
@@ -42,8 +42,6 @@ if (req._tx) return NestedTransaction.for (srv, req) | ||
static for (srv,root) { | ||
const txs = root.transactions || (root.transactions = new Map) | ||
let txs = root.transactions | ||
if (!txs) Object.defineProperty(root, 'transactions', {value: txs = new Map}) | ||
let tx = txs.get (srv) | ||
if (!tx) { | ||
tx = new this (srv,root) | ||
txs.set (srv,tx) | ||
} | ||
if (!tx) txs.set (srv, tx = new this (srv,root)) | ||
return tx | ||
@@ -53,9 +51,6 @@ } | ||
constructor (srv,root) { | ||
const tx = _init ({ __proto__:srv, context:root }) | ||
const proto = new.target.prototype | ||
const tx = Object.create(srv) | ||
if ('begin' in srv) tx.dispatch = tx.start = Transaction.prototype.start | ||
tx.commit = proto.commit.bind(tx) | ||
tx.rollback = proto.rollback.bind(tx) | ||
tx.context = root | ||
tx.ready = false | ||
return tx | ||
@@ -65,17 +60,4 @@ } | ||
/** | ||
* Used before the first srv.dispatch in a transaction, to ensure the | ||
* service's implementation of .begin is called appropriately. | ||
*/ | ||
async start (req) { | ||
if (req.query || req.event !== 'BEGIN') { | ||
if (!this.ready) this.ready = this.begin() | ||
await this.ready //> parallel .dispatch events queue here | ||
delete this.dispatch | ||
} | ||
return this.__proto__.dispatch.call (this,req) | ||
} | ||
/** | ||
* In addition to srv.commit, resets the transaction to initial state, | ||
* in order to re-.start on subsequently .dispatched events. | ||
* in order to re-start on subsequently dispatched events. | ||
*/ | ||
@@ -85,4 +67,3 @@ async commit (res) { | ||
if (this.__proto__.commit) await this.__proto__.commit.call (this,res) | ||
this.dispatch = this.start | ||
this.ready = false | ||
_init (this) | ||
} | ||
@@ -94,9 +75,13 @@ return res | ||
* In addition to srv.rollback, resets the transaction to initial state, | ||
* in order to re-.start on subsequently .dispatched events. | ||
* in order to re-start on subsequently dispatched events. | ||
*/ | ||
async rollback (err) { | ||
// REVISIT + PARKED: Should we add srv.on('error',(err)=>...)? | ||
// const err_handlers = this._handlers._error | ||
// if (err_handlers.length) { | ||
// await Promise.all (err_handlers.map (each => each.handler.call (this,err))) | ||
// } | ||
if (this.ready) { //> nothing to do if no transaction started at all | ||
if (this.__proto__.rollback) await this.__proto__.rollback.call (this,err) | ||
this.dispatch = this.start | ||
this.ready = false | ||
_init (this) | ||
} | ||
@@ -157,1 +142,20 @@ if (err) throw err | ||
} | ||
/** | ||
* Ensure the service's implementation of .begin is called appropriately | ||
* before any .dispatch. | ||
*/ | ||
const _init = (tx) => { | ||
if ('begin' in tx) tx.dispatch = begin_and_dispatch | ||
tx.ready = false | ||
return tx | ||
} | ||
async function begin_and_dispatch (req) { | ||
if (!req.query && req.method === 'BEGIN') // IMPORTANT: !req.query is to exclude batch requests | ||
return this.ready = this.__proto__.dispatch.call (this,req) | ||
if (!this.ready) this.ready = this.begin() | ||
await this.ready | ||
delete this.dispatch | ||
return this.dispatch (req) | ||
} |
@@ -18,6 +18,11 @@ /** | ||
* @param {string} [module] the module for which a logger is requested | ||
* @param {string|number} [level] the log level to enable -> 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace | ||
* @param {string|number|{ level, prefix }} [level] the log level to enable -> 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace | ||
* @param {string} [prefix] a prefix to prepend to each log output, default: '[cds.<module>] -' | ||
*/ | ||
module.exports = exports = function cds_log (module, level, prefix = module ? `[cds.${module}] -` : `[cds] -`) { // NOSONAR | ||
module.exports = exports = function cds_log (module, level, prefix) { // NOSONAR | ||
if (typeof level === 'object') { | ||
prefix = level.prefix | ||
level = level.level | ||
} | ||
if (!prefix) prefix = module ? `[cds.${module.match(/^[^|]+/)}] -` : `[cds] -` | ||
if (!level) { | ||
@@ -42,2 +47,13 @@ const regex = RegExp(`\\b(y|all|${module||'any'})\\b`) | ||
/** | ||
* Shortcut to `cds.log(...).debug`, returning undefined if `cds.log(...)._debug` is false. | ||
* @param {string} [module] the module for which a logger is requested | ||
* @param {string|{ level, prefix }} [prefix] a prefix to prepend to each log output, default: '[cds.<module>] -' | ||
*/ | ||
exports.debug = function cds_debug (module, prefix) { | ||
const L = this.log (module, typeof prefix === 'object' ? prefix : {prefix}) | ||
return L._debug && L.debug | ||
} | ||
/** | ||
* Constructs a new Logger with the method signature of `{ debug, log, info, warn, error }` | ||
@@ -58,5 +74,5 @@ * from console. The default implementation actually maps it to `global.console`. | ||
trace: level < TRACE ? ()=>{} : (...args) => console.trace (prefix, ...args), | ||
debug: level < DEBUG ? ()=>{} : (...args) => console.debug (prefix, ...args), | ||
log: level < INFO ? ()=>{} : (...args) => console.log (prefix, ...args), | ||
info: level < INFO ? ()=>{} : (...args) => console.info (prefix, ...args), | ||
debug: level < DEBUG ? ()=>{} : (...args) => console.warn (prefix, ...args), | ||
log: level < INFO ? ()=>{} : (...args) => console.warn (prefix, ...args), | ||
info: level < INFO ? ()=>{} : (...args) => console.warn (prefix, ...args), | ||
warn: level < WARN ? ()=>{} : (...args) => console.warn (prefix, ...args), | ||
@@ -63,0 +79,0 @@ error: level < ERROR ? ()=>{} : (...args) => console.error (prefix, ...args), |
{ | ||
"name": "@sap/cds", | ||
"version": "4.4.10", | ||
"version": "4.5.1", | ||
"description": "SAP Cloud Application Programming Model - CDS for Node.js", | ||
@@ -29,7 +29,6 @@ "homepage": "https://cap.cloud.sap/", | ||
"dependencies": { | ||
"@sap/cds-compiler": "^1.43.1", | ||
"@sap/cds-reflect": "^2.13.0", | ||
"@sap/cds-runtime": "~2.7.0", | ||
"@sap/cds-compiler": "^1.49.0", | ||
"@sap/cds-runtime": "~2.8.0", | ||
"@sap/cds-foss": "^2" | ||
} | ||
} |
@@ -21,3 +21,3 @@ const express = require('express') | ||
* @param {express.Application} options.app - filenames of models to load; default: '*' | ||
* @param {express.Handler} options.index_html - custom handler for / | ||
* @param {express.Handler} options.index - custom handler for / | ||
* @param {express.Handler} options.favicon - custom handler for /favicon.ico | ||
@@ -30,29 +30,25 @@ * @param {express.Handler} options.logger - custom request logger middleware | ||
const app = cds.app = o.app || express() | ||
cds.emit('bootstrap',app) // hook for project-local server.js | ||
cds.emit ('bootstrap',app) //> hook for project-local server.js | ||
// mount static resources and common middlewares... | ||
// mount static resources and logger middleware | ||
app.use (express.static (cds.env.folders.app)) //> defaults to ./app | ||
app.use ('/favicon.ico', o.favicon) //> if none in ./app | ||
app.get ('/', o.index_html) //> if none in ./app | ||
app.use (o.logger) //> basic request logging | ||
if (o.index) app.get ('/',o.index) //> if none in ./app | ||
if (o.logger) app.use (o.logger) //> basic request logging | ||
// load specified models or all in project | ||
let model = cds.model = await cds.load (o.from) | ||
const csn = await cds.load (o.from||'*') | ||
cds.model = cds.linked (cds.compile.for.odata(csn)) | ||
// bootstrap --in-memory db if requested | ||
if (o.in_memory) await cds.deploy (model,o) | ||
// connect to essential framework services if required | ||
if (cds.requires.messaging) await cds.connect.to ('messaging') | ||
if (cds.requires.db) cds.db = await cds.connect.to ('db') | ||
// pre-unfold models to minimize memory consumption | ||
if (cds.env.features.snapi) model = cds.model = cds.linked(cds.compile.for.odata(model)) | ||
// bootstrap in-memory db if requested | ||
if (o.in_memory) await cds.deploy (csn).to (cds.db,o) | ||
// connect to primary database if required | ||
if (cds.requires.db) cds.db = await cds.connect.to('db') | ||
// serve all services declared in models | ||
await cds.serve (o.service,o).in (app) | ||
cds.emit ('served', cds.services) //> hook for listeners | ||
// bootstrap messaging service if required | ||
if (cds.requires.messaging) await cds.connect.to('messaging') | ||
// construct and mount modelled services | ||
/* cds.services = */ await cds.serve(o).from (model) .in (app) | ||
cds.emit ('served', cds.services) | ||
// start http server | ||
@@ -70,3 +66,3 @@ return app.listen (o.port || process.env.PORT || 4004) | ||
// default generic index.html page | ||
get index_html() { | ||
get index() { | ||
const index = require ('./app/index.js') | ||
@@ -98,2 +94,2 @@ return (_,res) => res.send (index.html) | ||
// ------------------------------------------------------------------------- | ||
if (!module.parent) module.exports (process.argv[2]) | ||
if (!module.parent) module.exports ({from:process.argv[2]}) |
Sorry, the diff of this file is not supported yet
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
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
658334
3
149
10729
71
+ Added@sap/cds-runtime@2.8.7(transitive)
- Removed@sap/cds-reflect@^2.13.0
- Removed@sap/cds-reflect@2.13.5(transitive)
- Removed@sap/cds-runtime@2.7.10(transitive)
Updated@sap/cds-compiler@^1.49.0
Updated@sap/cds-runtime@~2.8.0