text: const { ServiceProvider, ProtocolAdapter } = require ('./classes')
  const cds = require ('../cds'), {one_model} = cds.env.features
  
  /**
   * @param {string|{definitions}} som - a service name or a model (name or csn)
   */
  exports = module.exports = function cds_serve (som, _options) { // NOSONAR
  
    if (Array.isArray(som) && som.length === 1) som = som[0]
    else if (typeof som === 'object') [som,_options] = [undefined,som]
    const o = {..._options} // we must not modify inbound data
  
    // 1) Use fluent API to fill in remaining options...
    const fluent = {
      from (model)  { o.from = model;    return this },
      with (impl)   { o.with = impl;     return this },
      at (path)     { o.at   = path;     return this },
      to (protocol) { o.to   = protocol; return this },
    }
  
    // 2) Ensure options are filled in canonically based on defaults
    const options = Promise.resolve(o).then (o => {
      if (o.service)           { o.from     ||( o.from    = som); return o }
      if (o.from)              { o.service  ||( o.service = som); return o }
      if (!is_identifier(som)) { o.service ='all'; o.from = som ; return o }
      else                     { o.service = som;  o.from ='all'; return o }
    })
  
    // 3) Load/resolve the model asynchronously...
    const loaded = options.then (({from}=o) => {
      if (!from || from === 'all') from = '*'
      if (cds.model && one_model && from === '*') return cds.model
      if (from.definitions) return from
      const cached = cds_serve, key = `${from}`
      return cached[key] || (cached[key] = cds.load(from))
    })
  
    // 4) Construct providers...
    const provided=[]
    let ready = loaded.then (csn => { // NOSONAR
  
      const {services} = cds.reflect(csn)
      for (let d of services) {
        // skip the ones not chosen by o.service
        if (o.service && o.service !== 'all' && !d.name.endsWith (o.service)) continue
        // skip all marked to be ignored
        if (d['@cds.ignore'] || d['@cds.serve.ignore'])  continue
        // skip required ones unless asked to mock them
        const required = cds.env.requires[d.name]
        if (required && (!o.mocked || required.credentials)) continue
        // serve all others ...
        const srv = new ServiceProvider (d.name,csn,o)
        srv.$impl = exports.impl4 (d,o.with)
        srv.path = exports.path4 (d,o.at)
        // register provided service
        cds.service.providers.push (srv)
        cds.services[d.name] = srv
        provided.push (srv)
      }
  
      if (( o.at || o.with ) && provided.length > 1) {
        throw cds.error `You cannot specify 'path' or 'impl' for multiple services`
      }
  
      // activate service implementations
      for (let each of provided) {
        if (each.$impl)  each.impl (each.$impl._impl)
      }
  
    })
  
    // 5) Fluent method to serve constructed providers to express app
    fluent.in = (app) => {
      ready = ready.then (()=>{
        for (let each of provided) {
          ProtocolAdapter.serve(each).in(app)
          cds.emit ('serving',each)
        }
      })
      return fluent
    }
  
    // 6) Finally resolve to a single picked provider or a map of all
    fluent.then = (resolve, failed) => ready.then (()=>{
      if (provided.length === 0) return resolve()
      let all={}
      for (let each of provided) {
        all[each.name] = ProtocolAdapter.serve(each).asRouter()
      }
      if (provided.length === 1 && provided[0].name.endsWith (o.service)) {
        all = Object.assign (provided[0], all)
      }
      return resolve (all)
    }, failed)
    fluent.catch = (e) => ready.catch(e)
  
    return fluent
  }
  
  
  
  /**
   * Resolve a service endpoint path to mount it to as follows...
   * Use _path or def[@path] if given with leading '/' prepended if necessary.
   * Otherwise, use the service definition name with stripped 'Service'
   */
  exports.path4 = function (def, _path = def['@path']) {
  	if (_path)  return _path.replace(/^[^/]/, c => '/'+c)
  	else  return '/' + ( // generate one from the service's name
  		/[^.]+$/.exec(def.name)[0]  //> my.very.CatalogService --> CatalogService
  		.replace(/Service$/, '')        //> CatalogService --> Catalog
  		.replace(/^[A-Z]/, c => c.toLowerCase())  //> Catalog --> catalog
  		.replace(/[A-Z][a-z0-9]/g, c => '-'+c.toLowerCase())  //> FooBarX9 --> foo-bar-x9
  		.replace(/[A-Z]/g, c => c.toLowerCase())  //> FOO --> foo
  		.replace(/_/g,'-')  //> foo_bar --> foo-bar
  	)
  }
  
  
  /**
   * Resolve a service implementation function as follows...
   * 1. if _impl is a function --> got it
   * 2. if _impl is a string --> `require (it)` (using model source if available)
   * 3. if we got the model's source --> `require (<basename>.js) || require (js/<basename>.js) || require (handlers/<basename>.js)`
   */
  exports.impl4 = function (def, _impl = def['@impl'], cwd=process.cwd()) { // NOSONAR
  
  	// is it a direct impl passed via cds.serve(...).with (<>) ?
  	if (typeof _impl === 'function')  return _found (_impl.name || '<in code>', _impl)
  
  	// determine model's neighbourhood dir to search in...
  	// Note: @source has precedence over $location as the latter
  	// may refer to a merged csn file if loaded from csn.json
  	const _source = def['@source'] || def.$location && def.$location.file
  	const dir = _source && isdir(dirname(_source)) || '.'
  	const root = resolve(dir)
  
  	// fetch impl specified through @impl or via ds.serve(...).with (<>)
  	// search in model's neighbourhood as well as relative to cwd
  	if (_impl) {
  		if (!_impl.endsWith('.js'))  _impl += '.js'
  		return _fetch (_impl, /*in:*/ root, cwd)
  		|| cds.error (`Didn't find specified implementation module for ${def.name} at: ${_impl}`)
  	}
  
  	// search for <service-file>.js in model's neighbourhood
  	const twin = parse(_source).name + '.js'
  	return _fetch (twin, /*in:*/ root, './js', './handlers')
  
  }
  
  
  // Helpers ...
  const { isdir, isfile, path:{ dirname, parse, resolve }} = cds.utils
  
  function _fetch (file, /*in:*/ root, ...subfolders) {
  	for (let each of [root,...subfolders]) {
  		const found = isfile (resolve (root, each, file))
  		if (found)  return _found (found)
  	}
  }
  
  function _found (_source, _impl) {
  	return _impl ? { _source, _impl } : {
  		_source, get _impl() { return module.require(this._source) }
  	}
  }
  
  const is_identifier = x => typeof x === 'string' && /^[\w$]*$/.test(x)
  