UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

80 lines (74 loc) 3.92 kB
const cds = require('..'), LOG = cds.log('cds.connect') const TRACE = cds.debug('trace') /** * Connect to a service as primary datasource, i.e. cds.db. */ const connect = module.exports = async function cds_connect (options) { if (typeof options === 'object' && cds.db) throw cds.error ( `Re-connect to primary db with potentially different options is not allowed!` ) if (typeof options === 'string') cds.db = await connect.to (options) else await connect.to ('db',options) return cds } /** * Connect to a specific service, either served locally, with ad-hoc options * or with options configured in cds.env.requires.<datasource>. * @param { string|Function|object } [datasource] * @param {{ kind?:String, impl?:String }} [options] * @returns { Promise<import('./cds.Service')> } */ connect.to = (datasource, options) => { let Service = cds.service.factory if (typeof datasource === 'object' && !datasource.name) [options,datasource] = [datasource] // .to({ options }) else if (datasource) { if (datasource._is_service_class) [ Service, datasource ] = [ datasource, datasource.name ] // .to(ServiceClass) else if (datasource.name) datasource = datasource.name // .to({ name: 'Service' }) from cds-typer if (!options && datasource in cds.services) return Promise.resolve (cds.services[datasource]) } const promise = (async()=>{ TRACE?.time(`cds.connect.to ${datasource}`.padEnd(22).slice(0,22)) const o = Service._is_service_class ? {} : options4 (datasource, options) const m = await model4 (o) // check if required service definition exists const required = cds.requires[datasource] if (required?.model?.length && datasource !== 'db' && !m.definitions[required.service||datasource]) { LOG.error(`No service definition found for '${required.service || datasource}', as required by 'cds.requires.${datasource}':`, required) throw new Error (`No service definition found for '${required.service || datasource}'`) } // construct new service instance let srv = await new Service (datasource,m,o); await (Service._is_service_class ? srv.init?.() : Service.init?.(srv)) if (!srv.isDatabaseService && _is_queued(o)) srv = cds.queued(srv) if (!options && datasource) { if (datasource === 'db') cds.db = srv cds.services[datasource] = srv } if (!o.silent) cds.emit ('connect',srv) TRACE?.timeEnd(`cds.connect.to ${datasource}`.padEnd(22).slice(0,22)) return srv })() // queue parallel requests to a single promise, to avoid creating multiple services if (!options && datasource) cds.services[datasource] = promise return promise } function options4 (name, _o) { if (name?.startsWith('http:')) [_o,name] = [{ url:name }] const [, kind=_o?.kind, url=_o?.url ] = /^(\w+):(.*)/.exec(name) || [] const conf = cds.service.bindings.at(name) || cds.requires[name] || cds.requires[kind] || cds.requires.kinds[name] || cds.requires.kinds[kind] const o = { kind, ...conf, ..._o } if (!o.kind) o.kind = (url||conf?.credentials?.url)?.match (/\/(hcql|rest|odata)\//)?.[1] if (!o.kind && !o.impl && !o.silent) throw cds.error( conf ? `Configuration for 'cds.requires.${name}' lacks mandatory property 'kind' or 'impl'` : name ? `Didn't find a configuration for 'cds.requires.${name}' in ${cds.root}` : `Provided options object lacks mandatory property 'kind' or 'impl'` ) if (url) o.credentials = { ...o.credentials, url } return o } function model4 (o) { if (o.model?.definitions) return o.model // got a CSN already? -> use it if (cds.model) return cds.model // use global model if available if (o.model) return cds.load (o.model) // load specified model from file } // at least one option is truthy and none is false const _is_queued = o => (o.queued || o.outboxed || o.outbox) && o.queued !== false && o.outboxed !== false && o.outbox !== false