UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

85 lines (69 loc) 3.9 kB
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