@sap/cds
Version:
SAP Cloud Application Programming Model - CDS for Node.js
85 lines (69 loc) • 3.9 kB
JavaScript
const cds = require ('..')
const Service = cds.service.factory
const {serve} = cds.service.protocols
const {init} = Service
/** Fluent API */
module.exports = (services, options, o = {...options}) => ({
from (model) { o.from = model; return this },
with (impl) { o.with = impl; return this },
to (prot) { o.to = prot; return this },
at (path) { o.at = path; return this },
in (app) { o.app = app; return this },
then: (r,e) => _cds_serve (services,o) .then (r,e)
})
/**
* @returns { Promise<Record<string,Service>> } single or multiple services, like that:
* @example let { CatalogService } = await cds.serve(...) // single or many
* let CatalogService = await cds.serve(...) // single only
* @import Service from './cds.Service'
*/
async function _cds_serve (services='all', o) {
const TRACE = cds.log('trace'), t0 = TRACE._debug && performance.now()
if (services.service) { Object.assign(o,services); services = o.service }
else if (o.service) { o.from ??= services; services = o.service }
else if (is_files(services)) { o.from ??= services; services = 'all' }
else if (is_csn(services)) { o.from ??= services; services = 'all' }
const srvs = await _construct (services,o)
if (o.app) for (let srv of srvs) if (!_ignore(srv.definition)) {
serve (srv, o.app)
cds.service.providers.push (srv)
o.silent || cds.emit ('serving', srv) // NOTE: only for protocol-served ones (compat behaviour)
}
if (t0) TRACE.debug ('cds.served in'.padEnd(21),':', (performance.now()-t0).toFixed(), 'ms')
if (srvs.length === 1) return Object.defineProperty (o=srvs[0], o.name, {value:o})
return srvs.reduce ((many,s)=>{ many[s.name] = s; return many },{})
}
/**
* Constructs service instances from a model's service definitions.
* @param { string & typeof cds.Service } services
*/
async function _construct (services, o) {
if (services._is_service_class) // shortcut for directly passed service classes
return [ await init (new services (services.name, cds.model, o)) ]
// Resolve/load the model to use subsequently
const csn = !o.from || o.from === '*' ? cds.model || await cds.load('*')
: is_csn(o.from) ? o.from : await cds.load (o.from, { silent: true })
const m = cds.compile.for.nodejs (csn)
// Fetch service definitions from model
let defs = services == 'all' ? m.services : m.services.filter (_choose(_specified(services)))
defs = defs.filter (d => !d['@cds.ignore'] && !d['@cds.serve.ignore']) // skip ignored ones
defs = defs.filter (o.mocked ? _mock_external : _skip_external) // skip externals, or mock
// Construct services, adding upfront promises to cds.services
return Promise.all (defs.map (d => {
const n = cds.requires[d.name]?.name || d.name; o.service = d.name
return cds.services[n] = cds.services[d.name] = new Service(n,m,o) .then(init) .then (srv => {
if (d.is_external) srv.mocked = true
if (d.name !== n) Object.defineProperty (cds.services, d.name, {value:srv})
if (!o.silent) cds.emit (`serving:${n}`, srv)
return cds.services[n] = cds.services[d.name] = srv // replacing upfront promises with real services
})
}))
}
const _specified = services => services.split (/\s*,\s*/).map(s => cds.requires[s]?.service || s)
const _choose = specified => ({name:n}) => specified.some (s => s === n || n.endsWith('.'+s))
const _ignore = d => d?.['@protocol'] === 'none' || d?.['@cds.api.ignore']
const _mock_external = d => _skip_external(d) || !cds.requires[d.name]?.credentials
const _skip_external = d => !d['@external'] && !d['@cds.external'] && !cds.requires[d.name]?.external
|| (d.is_external = true, false) // tagging all external ones, and skipping them
const is_files = x => Array.isArray(x) || typeof x === 'string' && (/[^\w.$]/.test(x) || /\.(cds|csn|json)$/.test(x))
const is_csn = x => x?.definitions