Search

Node.js API Reference

as provided through @sap/cds

This is the reference documentation for the Node.js APIs, both for design-time as well as runtime usage. All APIs can be accessed through the singleton cds facade object; a typical usage looks like this:

const cds = await require('@sap/cds').connect('db')
const {Books} = cds.entities
cds.run (
  INSERT.into (Books) .entries ({title:'Catweazle'})
)

These APIs are the center of all cds operations, for example, the cds command-line interface is just a thin wrapper around these APIs.

Conventions

We use the following conventions to describe method signatures:

cds.connect.to (service, options?) service
cds.serve (service) … service
.from (model)
.at (path)

Try clicking on the arguments and return types to visit specific documentation.

Index

cds.connect service

Use cds.connect to connect to local or external services; the latter include databases and are usually configured upfront in order to connect to them.

Note: By default, bootstrapping is done by the framework, including connecting to the primary data source. So, you need cds.connect only if you want to control the bootstrapping yourself, e.g. in a custom express server.js.

cds.connect.to (name?, options?) service

Connects to a required service and returns a Promise resolving to a corresponding Service instance. Subsequent invocations with the same service name all return the same instance.

Common Usage:

const srv = await cds.connect.to ('some-service')
srv.run (SELECT.from('Books'))

Arguments:

If both, service and options are given, the ad-hoc options are merged with the configured ones (overriding the configured values on same properties). If service is omitted, this is an ad-hoc connection only.

Caching:

Service instances are cached in cds.services, thus subsequent connects with the same service name return the initially connected one. As services constructed by cds.serve are registered with cds.services as well, a connect will also find and return them as local service connections.

Alternative Usages:

Connect to a required service (→ standard for productive code)

cds.connect.to ('db')

Ad-hoc connection (→ only for tests)

cds.connect.to ({kind:'sqlite', credentials:{database:'my.db'}})

Shortcut for ad-hoc connections

cds.connect.to ('sqlite:my.db')

cds.connect (options?) cds

Connects to the primary data source. Do this once in your code and subsequently use the service requests methods provided by the singleton cds instance, such as cds.transaction, cds.run, etc.

This is actually a shortcut to cds.connect.to plus wiring the connected service as the primary data source.

Common Usage:

cds.connect() //... to primary data source, i.e. 'db'
const { Books } = cds.entities
cds.run (SELECT.from(Books))

Arguments:

Returns cds itself

… which acts as a proxy to the connected primary datasource, supporting all the service requests methods, i.e. the following two code samples are equivalent:

await cds.connect()
const { Books } = cds.entities ('my.bookshop')
const books = await cds.run (SELECT.from(Books))
const db = await cds.connect.to ('db')
const { Books } = db.entities ('my.bookshop')
const books = await db.run (SELECT.from(Books))

Connect Options

The following options are supported, both in service configurations as well as the ad-hoc options parameter to cds.connect:

.kind : id | module name

specifies which service client implementation to use; which can be one of:

.model : string | csn

Specifies the service or data model to use for this connection, either as an already parsed csn or as a filename of a model, which is then loaded with cds.load.

.credentials : { … }

Specific options, which are passed to native driver libs, e.g. node-sqlite3

.pool : { … }

For databases and external services connection pooling is supported. For messaging services there is no connection pooling in order to preserve the order of messages. Default values depend on the service kind.

.multiTenant : boolean

Specifies if multitenancy should be enabled. Defaults to false.

Configuring required services

When connecting to services using cds.connect.to, the provided service names are used to lookup details and credentials from respective configurations, e.g. in your package.json:

  "cds": {
    "requires": {
      "db": {
        "model": "db/bookshop",
        "kind": "sqlite",
        "credentials": {
          "database": "db/bookshop.db"
        }
      },
      "cat-service": {
        "kind": "odata",
        "model": "srv/external/cat-service",
        "credentials": {
          "url": "https://localhost:4004/cat"
        }
      }
    }
  }

Required service db is the designated primary data source, i.e. the one being used when calling cds.connect without no arguments.

The content per service configuration are the connect options as accepted and documented for cds.connect. Content of the credentials sections are specific to the respective backend/driver.

Binding required services

— TODO —

At runtime, the information in there is overriden from entries in environment variables, such as VCAP_SERVICES.

cds.serve… service(s)

Use cds.serve in to construct service providers from the service definitions in corresponding CDS models. Subsequently you can add event handlers to the constructed providers.

Note: By default, bootstrapping of servers is handled by the framework automatically. So, you only need cds.serve if you want to control the bootstrapping yourself, e.g. in a custom express server.js.

cds.serve (name) … service or { services }

Initiates a fluent API chain to construct service providers; use the methods documented below to add more options.

Common Usages:

const { CatalogService } = await cds.serve ('my-services')
const app = require('express')()
cds.serve('all') .in (app)

Arguments:

cds.serve('CatalogService')  //> serve a single service
cds.serve('all')             //> serve all services found

You may alternatively specify a string starting with './' or refer to a file name with a non-identifer character in it, like '-' below, as a convenient shortcut to serve all services from that model:

cds.serve('./reviews-service')  //> is not an identifer thru '/'
cds.serve('reviews-service')    //> dito by '-', hence both act as:
cds.serve('all').from('./reviews-service')

The method returns a fluent API object, which is also a Promise resolving to either an object with 'all' constructed service providers, or to the single one created in case you specified a single service:

const { CatalogService, AdminService } = await cds.serve('all')
const ReviewsService = await cds.serve('ReviewsService')

Caching:

The constructed service providers are cached in cds.services which (a) makes them accessible to cds.connect, as well as (b) allows us to extend already constructed services through subsequent invocation of cds.serve.

Common usages and defaults

Most commonly, you’d use cds.serve in a custom server.js file to add all the services to your express.js app as follows:

const app = require('express')()
cds.serve('all').in(app)
app.listen()

This uses these defaults for all options:

Option Description Default
cds.serve … which services to construct 'all' services
.from models to load definitions from './srv' folder
.in express app to mount to — none —
.to client protocol to serve to 'fiori'
.at endpoint path to serve at @path or .name
.with implementation function @impl or ._source.js

Alternatively you can construct services individually, also from other models, and also mount them yourself, as document in the subsequent sections on individual fluent API options.

.from (model)

Allows to determine the CDS model(s) to fetch service definitions from, which can be specified as one of:

The latter allows you to cds.load or dynamically construct models yourself and pass in the CSN models, as in this example:

const csn = await cds.load('my-services.cds')
cds.serve('all').from(csn)...

If omitted, './srv' is used as default.

.to (protocol)

Allows to specify the protocol through which to expose the service. Currently supported values are:

If omitted, 'fiori' is used as default.

.at (path)

Allows to programmatically specify the mount point for the service.

Note that this is only possible when constructing single services:

cds.serve('CatalogService').at('/cat')
cds.serve('all').at('/cat') //> error

If omitted, the mount point is determined from annotation @path, if present, or from the service’s lowercase name, excluding trailing Service.

service MyService @(path:'/cat'){...}  //> served at: /cat
service CatalogService {...}           //> served at: /catalog

.in (express app)

Adds all service providers as routers to the given express app.

const app = require('express')()
cds.serve('all').in(app)
app.listen()

.with (impl function)

Allows to specify a function that adds event handlers to the service provider, either as a function or as a string referring to a separate node module containing the function.

cds.serve('./srv/cat-service.cds') .with ('./srv/cat-service.js')
cds.serve('./srv/cat-service') .with (srv => {
  srv.on ('READ','Books', (req) => req.reply([...]))
})

learn more about adding event handlers

Note that this is only possible when constructing single services:

cds.serve('CatalogService') .with (srv=>{...})
cds.serve('all') .with (srv=>{...})  //> error

If omitted, an implementation is resolved from annotation @impl, if present, or from a .js file with the same basename than the CDS model, e.g.:

service MyService @(impl:'cat-service.js'){...}
srv/cat-service.cds  #> CDS model with service definition
srv/cat-service.js   #> service implementation used by default

cds.services { services }

All constructed or connected services are registered and cached in cds.services. Each service instance provides access to metadata through the properties and methods listed below.

cds.services { services }

A dictionary and cache of all services constructed through cds.serve as well as connected to by cds.connect so far.

srv.entities, types (namespace) {defs}

These methods provide convenient access to the linked definitions of all entities and types managed by this service.

const db = await cds.connect.to('db')
const { Books, Authors } = db.entities('my.booskhop')

These methods are actually shortcuts to their counterparts provided by linked models, with the default namespace being the service definition’s name.

srv.definition def

The linked service definition contained in the model which served as the blueprint for the given service instance.

srv.model csn

The linked model from which this service’s definition was loaded.

srv.name string

The service’s name, i.e.:

cds.serve ('CatalogService')   //> .name = 'CatalogService'
cds.connect.to ('audit-log')   //> .name = 'audit-log'
cds.connect.to ('db')          //> .name = 'db'

srv.options { … }

The effective connect options used for connecting to this service.

srv.on/before/after (event)

Register event handlers with provided or connected services to add custom logic to serve operations or react to events emitted by these services.

Where to register event handlers?

You can register event handlers wherever you have access to a provided or connected service instance, which includes these options:

  1. In code using the outcomes of cds.serve or cds.connect:
    const {CatalogService} = await cds.serve('./cat-service')
    CatalogService.on ('READ','Books', req => {...})
    
    const db = await cds.connect('db')
    db.on ('INSERT','Orders', req => {...})
    
  2. In an inline function passed to cds.serve.with():
    cds.serve('./cat-service') .with (srv=>{
     srv.on ('READ','Books', req => {...})
    })
    
  3. In an inline function passed to srv.impl():
    const {CatalogService} = await cds.serve('./cat-service')
    CatalogService.impl (srv=>{
     srv.on ('READ','Books', req => {...})
    })
    
  4. In a .js file specified thru a string passed to cds.serve.with():
    cds.serve('./cat-service') .with ('./cat-service.js')
    
  5. In a .js file specified thru annotation @impl on a service definition:
    service CatalogService @(impl:'cat-service.js') {...}
    
  6. In a .js file placed next to the .cds file containing the service definitions:
    // cat-service.cds
    service CatalogService {...}
    
    // cat-service.js
    module.exports = (srv)=>{
     srv.on ('READ','Books', req => {...})
    }
    

srv.on (event, entity?, handler) this

Handlers registered with this method are run as a chain of promises, with each handler being able to terminate the chain. Use that in srv.impl functions to register handler to run instead of the generic ones.

Arguments:

learn more about the req argument in handler functions

Outcomes:

The request can be handled in one of the following ways:

  1. Reject the request via req.reject, throw, or returning a rejecting Promise.
    srv.on('READ','Books', (req)=> req.reject(409,'...'))
    srv.on('READ','Books', (req)=> Promise.reject(...))
    srv.on('READ','Books', (req)=> { throw ... })
    
  2. Call req.reply synchronously or asynchronously → ends the handling
    srv.on('READ','Books', (req)=> req.reply('Hello World!'))
    srv.on('READ','Books', (req)=> cds.run(...).then(req.reply))
    
  3. Return results or a Promise resolving to some → framework does req.reply
    srv.on('READ','Books', ()=> cds.run(SELECT.from(Books)))
    srv.on('READ','Books', ()=> "Hello World!")
    
  4. Return a cds.ql object → framework does cds.run and req.reply
    srv.on('READ','Books', ()=> SELECT.from(Books))
    
  5. Return nothing/falsy → passes control to next / default handlers
    srv.on('READ','Books', ()=> console.log('like before handler'))
    
  6. Use next as in express.js to explicitly delegate to default handlers
    srv.on('READ','Books', (req,next)=>{
      if (...) return SELECT.from(Books) //> ... handle req my own
      else return next()  //> delegate to next/default handlers
    })
    

Shortcuts:

As a yet more conveniet shortcut to 4. you can just pass in the cds.ql object instead of a handler function; so, overall, these examples are equivalent:

srv.on('READ','Books', (req)=> {
  const tx = cds.transaction (req)
  return tx.run(SELECT.from(Books)) .then (req.reply)
)
srv.on('READ','Books', (req)=> {
  const tx = cds.transaction (req)
  return tx.run(SELECT.from(Books)) // framework will reply
)
srv.on('READ','Books', ()=> SELECT.from(Books))
srv.on('READ','Books', SELECT.from(Books))

srv.before (event, entity?, handler) this

Registers a handler to run before the ones registered with .on(), i.e. before the generic handlers. Commonly used to add custom input validations.

Arguments are the same as in srv.on

Examples:

this.before ('CREATE','Order', (req)=>{
  const order = req.data
  if (order.amount > 11)  throw 'Order amount must not exceed 11'
})

learn more about the req argument in handler functions

You can as well trigger additional operations in before handlers:

srv.before ('CREATE','Order', (req)=>{
  const order = req.data
  return UPDATE(Books).set ('stock -=', order.amount)
})

Note, that this example would fail assumed you have a constraint on book stocks to not become negative, so the UPDATE also acts as a validation.

Note:

Hence: You can terminate requests early by throwing exceptions or via req.reject in the handlers’ synchronous parts. Asynchronous parts however run in parallel, so you can’t react on side-effects between .before handlers.

srv.after (event, entity?, handler) this

Registers a handler to run after the generic handler. To be more precise: It runs on the results returned by the generic handler (which is asynchronous to the incoming handlers). Use it to modify the response.

Arguments are the same as in srv.on

Example:

this.after('READ','Books', (books)=>{
  for (let each of books) each.stock > 111 && each.discount='11%'
})

Name the param each as convenience shortcut for a per-row handler:

this.after('READ','Books', (each)=>{
  each.stock > 111 && each.discount='11%'
})

Note: This is only meant for synchronous code due to performance.

Use the secondary param req in case you need to reflect on the inbound request:

this.after('READ','Books', (books,req)=>{
  if (req.data.ID) ...
})


learn more about the req argument in handler functions

srv.reject (event, entity?) this

Registers a generic handler that automatically rejects incoming request with a standard error message. You can specify multiple events and entities.

Arguments:

Examples:

this.reject ('READ', 'Orders')
this.reject (['CREATE','UPDATE','DELETE'], ['Books','Authors'])

srv.impl ((srv)=>{…})

Use this method to provide a service implementation function which adds several event handlers to the service provider.

const {CatalogService} = cds.serve('all')
CatalogService .impl (srv => {
  srv.on ('READ','Books', (req) => req.reply([...]))
})

Handlers added in subsequent invocations are added and executed before the ones from prior invocations. This allows projects reusing a service to override handlers from the imported package. For example:

const CatalogService = cds.connect.to('CatalogService')
CatalogService .impl (srv => { // will be called prior to the ones above
  srv.on ('READ','Books', ...)
})

cds.service.impl ((srv)=>{…})

In case you placed an implementation function in a separate file, you may want to wrap your service implementation function by this method to gain code assists in tools, e.g. IntelliSense in vscode, e.g.:

module.exports = cds.service.impl ((srv)=>{
  srv.on ('READ','Books', (req)=>{...})
})

The implementation simply returns the passed in function.

Multiple handlers for same events

Arbitrary numbers of handlers for the same events can be registered. Those registered with .on are executed in order of their registration, while those registered with .before or .after the execution order is not guaranteed; they might be executed in parallel.

cds.serve('cat-service') .with (function(){
  this.on('READ', Books, ()=>{/* called first */})
  this.on('READ', Books, ()=>{/* called second */})
})

Single handlers for multiple events

Omit the <entity> argument to register handlers for all entities. Or add handlers for all events as well as standard express.js middlewares with method .use:

cds.serve('cat-service') .with (function(){
  this.on ('READ', ()=> {/* handles read events on all entities */})
  this.use ((cds)=>{/* handler for all events */})
  this.use ((req,res,next)=>{/* standard express middleware */})
})

srv.run (query) results

Each provided or connected service supports the following client API to read and write data. The central methods are srv.transaction and srv.run, the others are largely convenient variants.

srv.transaction (context?) tx

Starts a new transaction or joins an already started one for the given context. Usually this is used in event handlers as follows:

const ds = cds.connect.to('another-service')
srv.on ('READ','Books', (req)=>{
  const tx = ds.transaction(req)
  return tx.run (SELECT.from ('Books'))
})

The returned object supports the very same methods to read or write data than the service it is called on, i.e. the ones listed below.

Arguments:

Analysis:

Use this method whenever you call another service from the event handlers of your service. It ensures…

For database services this method in addition starts atomic transactions which are auctomatically committed or rolled back when an error occurs.

srv/tx.run (query, args?) results

This is the central function to run queries. It accepts CQN objects or plain query strings, e.g. a SQL string in case of a connected SQL database and returns a Promise resolving to the queries’ results.

Common Usage:

const srv = cds.connect.to ('SomeService') // could be a local service, a remote one or a databasde
const books = await srv.run (SELECT.from('my.bookshop.Books'))

Arguments:

The following placeholders for binding parameters are supported:

Returns a Promise resolving to…

↳ results.metadata csn signature

Provides access to metadata about the result set, in the same way and notation as elements in structured types in CSN.

Usage Variants:

cds.run (SELECT.from('Books'))
cds.run (`SELECT * from Books`)
cds.run (`CALL SomeStoredProcedure (11,'foo')`)
cds.run (`SELECT * from SomeFunction (11,'foo')`)

Using Fully-Qualified Names:

Important: In all variants, i.e. with plain strings or CQN objects as arguments, srv.run expects all references to targets be expressed as fully-qualified names. For example the fully qualified name of the exposed entity Books of the following model is some.namespace.SomeService.Books:

namespace some.namespace;
service SomeService {
  entity Books ...
}

… and a respective data access would have to look like that:

const srv = cds.connect.to ('some.namespace.SomeService')
const books = await srv.run (SELECT.from('some.namespace.SomeService.Books'))

It is recommended to use the model reflection options provided through srv.entities to ease that task as follows:

const srv = cds.connect.to ('some.namespace.SomeService')
const { Books } = srv.entities //> imports the CSN definition of Books
const books = await srv.run (SELECT.from(Books))

srv/tx.run (context?, tx=>{…}) results

In addition to individual statements, cds.run also accepts a function which can contain several statements in its body.

The blocks represent managed units of work:

in parallel

In its plain usage, statements in the function’s body are executed asyncronously and the returned Promise resolves if all statements have been successfully run.

cds.run (()=>{

  INSERT.into('Books').rows(
    [ 01, 'Wuthering Heights', 11, 7 ],
    [ 02, 'Eleonora', 12, 2 ],
    [ 03, 'Catweazle', 13, 3 ]
  )
  SELECT.from('Books').then(console.log)

}) .then (()=>
  console.log ('all done')
)

serialized

As the statements in the code snippet above are executed asynchronously, the SELECT might return an empty result set when it runs before the INSERT. To avoid this, add a "serialized" pragma to the block like so:

transactions

Add a "transaction" pragma to wrap the statements within a block into a transaction that is committed when all contained statements are finally run successfully and rolled back in case of an error/exception:

cds.run (()=>{"transaction"
  UPDATE('Books').set ('stock -=',order.amount)
  .where('ID=',order.book, 'stock >=',order.amount)
  .then (n => { if (!n.affectedRows)  throw new Error('sold out') })
  INSERT.into('Orders').entries (order)
})

Both statements in the snippet above are run asynchronously, nevertheless they will only be commited if no technical error occurs and if the ordered book is not sold out → the trown execption leads to a rollback.

You can combine both pragmas "serialized" and "transaction" into "serialized transaction".

nested in each other

You can nest blocks of statements as follows:

cds.run (cds=>{ // runs inner statements and cds.runs in parallel...

  INSERT.into('Books').rows(/*...*/)
  INSERT.into('Authors').rows(/*...*/)

  cds.run (cds=>{"serialized" // runs serialized...
    // ...
    cds.run (cds=>{ // runs in parallel...
      // ...
    })
  })

  cds.run (cds=>{"transaction" // runs a transaction
    // ...
    cds.run (cds=>{"transaction" // nested transaction
      // ...
    })
  })
})

cds.run actually calls the provided block function with a single argument which represents the current unit of work. While you don’t have to care about this argument when not nesting block, you do have to do so and use it as shown in the example above when you want to nest blocks.

Nested transaction don’t send a BEGIN nor a COMMIT to the database, i.e. they just participate in an outer transaction. This is useful when have reuse functions that need a transaction themselves, and you call them sometimes directly, sometimes from code that already started a transaction.

srv/tx.foreach (entity | query, args?, callback) void

Executes the statement and processes the result set one row by one. Use that instead of cds.run if you expect large result sets as processes it in a streaming-like fashion instead of materializing the full set in memory before.

cds.foreach (SELECT.from('Foo'), each => log (each))
cds.foreach ('Foo', each => log (each))

A plain entity name can be used for the stmt argument in which case it is expanded to a SELECT * from ....

srv/tx.create (entity) .entries? (data) query

srv/tx.read (entity, key?) .where? (cond) query

srv/tx.update (entity, key?) .with? (data) query

srv/tx.delete (entity, key?) .where? (cond) query

srv/tx.insert (data) .into (entity) query

These methods construct and send requests in a CRUD method-call style instead of the Embedded QL style provided by cds.ql.

Common Usage:

await cds.read('Books').where({ID:111})

Arguments:

Essentially, each of this methods simply starts a fluent query construction using their cds.ql counterparts, which can be continued using the respective tail methods and sent to the service backend upon invocation of .then(). E.g. think of the implementation of cds.read as follows:

read (entity) {
  const srv = this
  return Object.assign (SELECT.from(entity), {
    then (r,e) { return srv.run(this) .then(r,e) }
  }
}

Alternative Usages:

In effect, the following three variant to read books are equivalent:

const books1 = await db.run(SELECT.from(Books).where({ID:111}))
const books2 = await db.read(Books).where({ID:111})
const books3 = await db.get(Books).where({ID:111})
const books4 = await SELECT.from(Books).where({ID:111})

Method insert is just a SQL-reminiscent variant of create with the following being equivalent:

srv.insert(data) .into (entity)
srv.create(entity) .entries (data)

srv/tx.get (entity | path, data?) query

srv/tx.put (entity | path, data?) query

srv/tx.post (entity | path, data?) query

srv/tx.patch (entity | path, data?) query

srv/tx.delete (entity | path)

REST and OData services support these basic http methods which are mapped to their CRUD counterpart as documented below. They can be used to construct data queries as with the CRUD variants as well as be used to send plain HTTP requests.

Common Usage:

const books2 = await srv.get(Books,111)

Mapping to CRUD:

The following are equivalent:

srv.create(Books).entries({ID:111})
srv.post(Books,{ID:111})
srv.update(Books).set({ID:111})
srv.patch(Books,{ID:111})

Note: In case of REST and OData services, update translates to PATCH, not PUT.

Alternative Usages:

Sending data-oriented requests:

srv.post(Books) .entries({ID:111})   //> create
srv.get(Books) .where({ID:111})      //> read
srv.patch(Books) .set({ID:111})      //> update
srv.delete(Books) .where ({ID:111})  //> delete

Sending arbitrary HTTP requests:

srv.post('some/arbitrary/path', {foo:'bar'})
srv.get('some/arbitrary/path/111')
srv.patch('some/arbitrary/path', {foo:'bar'})
srv.delete('some/arbitrary/path/111')

srv/tx.<custom op> (…args) response

Calls custom-defined operations as declared in the service’s definition.

cat.submitOrder ({book:111, amount:1}) .then (console.log)

Upon cds.connect, a method is added to the returned service client for each action or function find in the assigned service definition.

srv.new/edit draft

The methods below are reserved for handling draft editing, which stores edit state on the server and allows interrupting edit sessions to continue later on from the same or a different client.

srv.new (entity) draft

srv.edit (entity, key, locked=true) draft

srv.patch (entity, draft.id) .with (data)

srv.cancel (entity, draft.id)

srv.save (entity, draft.id) key

These methods control the draft choreography as follows:

In between edit / new and save the actual edited data is sent to the server via one or more patch requests to the draft entity, e.g:

const draft = await cds.edit ('Books',111)
cds.patch ('Books',draft) .with ({stock:44})
cds.patch ('Books',draft) .with ({changedAt:Date.now()})
cds.save ('Books',draft)

Note: The save operation always triggers two events in the provider: the 'SAVE' event to prepare and a CREATE or UPDATE event as if the respective operation would have been invoked without a draft session.

Validations can be put on the final CREATE or UPDATE event as in non-draft implementations or on SAVE event which is specific to drafts only. In addition you can add field-level validations on the individual PATCH events which come in during the draft session to provide early feedback:

srv.before ('PATCH','Books', req => {
  // validations to run during editing
})
srv.before ('SAVE','Books', req => {
  // validations to run at final save only
})

srv.lock (entity, key, timeout?) lock

srv.release (lock.id)

These methods allow locking an entity for editing, mutually excluding others from interferring with edits, hence avoiding lost-update situations (pessimistically).

srv.emit (event)

Register event handlers with a provided or connected service to add your domain-specific custom logic to service operations, react to events emitted by these services and emit custom events.

srv.emit (event, entity?, payload, headers?) this

srv.on (‘connect’, socket => {…}) this

↳ socket.on (event, entity?, (…args) => {…}) this

↳ socket.emit (event, entity?, …args) this

req context

Event handlers registered via service.on, .before, .after all accept an argument, which provides single point of API contact for accessing request input, reading/writing data and sending responses.

req.method string

The HTTP method of the incoming request, such as…

  • POST
  • GET
  • PUT
  • PATCH
  • DELETE

req.event string

The name of the incoming event, which can be one of…

  • The name of an incoming CRUD request like CREATE
  • The name of an emitted CRUD event like UPDATED
  • The name of a custom operation like cancelOrder.
  • The name of a custom event like cancelledOrder

req.target def

Refers to the current request’s target entity definition, if any; undefined for custom operations and events.

see Entity Definitions in the CSN reference for details

req.query cqn

Captures the incoming request as a CQN query object. For example, an HTTP request like GET http://.../Books would be captured as follows:

req.query = {SELECT:{from:{ref:['Books']}}}

This is undefined for custom operations

req.data {…}

Captures all query parameters as well as http post bodies as a single object.

req.reply (data)

Serializes the data object in a protocol specific way and sends the response.

req.info (code?, msg)

Messages are collected and put into header. Request continues without an error. The msg argument may be a plain string or an error object (msg instanceof Error).

Multiple calls to req.info in the same or different request handlers are allowed: the individual messages are collected and replied to the caller.

req.error (code?, msg, target?)

Returns an error message to the client. If the first argument is a number, it is used as the HTTP response header code. The msg argument may be a plain string or an error object (msg instanceof Error).

target can be used in case of value invalidation to provide the affected property name.

Multiple calls to req.error in the same or different request handlers are allowed: the individual messages are collected and replied to the caller. Yet only one of which can set the response header code.

req.reject (code?, msg?, target?)

Rejects the request with the given HTTP response code and message.

req.on (event, handler)

Use this method to register handlers, executed when the whole request is finished.

req.on('succeeded', () => {...}) // request succeeded
req.on('failed', () => {...}) // request failed
req.on('done', () => {...}) // request succeeded/failed

Note: Inside the handlers you don’t have access to a database connection.

req._ {…}

Provides access to original inbound protocol-specific request objects. For events triggered by a HTTP request it contains the original req and res objects as obtained from express.js.

def:

req.assert…

srv.before ('INSERT','Books', validateBook)
function validateBook (req) {
  const expect = req.assert
  expect('title').is.specified().maxLength(111)
  expect('author').exists()
  expect('changedAt').is.readOnly()
  expect('status').is.inRange (['new', 'old'])
  expect('string').matches (/pattern/)
}

assert (field) .meets (condition)

assert (field) .equals (value)

assert (field) .is … (value)

assert (field) .is.maxLength(n)

assert (field) .is.not … (value)

assert (field) .is.kindOf (type)

assert (field) .is.specified()

assert (field) .is.readOnly()

assert (field) .is.inRange (…)

assert (field) .exists()

assert (field) .matches (string | pattern)

assert (field) .contains (string | pattern)

assert (field) .startsWith (string | pattern)

assert (field) .endsWith (string | pattern)

assert (field) .not …

Building Queries

cds.ql… query

Use the fluent API functions documented below to construct queries, captured as CQN objects, which you execute later on using the srv.run methods of your connected services.

let foos = SELECT.from('Foo')
let bars = SELECT.from(foos).where({kind:'bar'})
cds.foreach (bars, console.log)

While the API resembes well-known SQL constructs, cds.ql is not locked in to SQL but can be used to capture any kind of queries as well as applied to any kind of data sources, including NoSQL or OData.

SELECT .from (entity, columns?) …

SELECT.from ('Books').where ({author:11})
SELECT.from (Books,[
  'ID', 'title', 'author.name as name'
]).where ({stock:{'>':11}})

The first argument is either a string of an entity or an entity definition as obtained from a data source’s .entities method.

The optional second argument is the same as in .columns.

.columns (…columns)

The arguments are column expressions, which can be one of….

  • standard SQL column expression strings, e.g. name, x as y
  • Simple expression objects, e.g. {x:'y'}
  • CQN expression objects, e.g. {ref:['name']}, {ref:['x'], as:'y'}

Instead of passing in a arguments list, you can alternatively pass in a single array with all columns.

.excluding (…paths)

→ see CDS Queries excluding elements

.where / having / groupBy / orderBy (expr) / limit (rows, offset)

These methods allow to fill in the respective standard SQL or CQL clauses. The arguments form expressions as explained in section Fluent Expression below.

SELECT (…columns) .from (entity) …

This is a syntactical sugar variant to SELECT.from(entity) .columns(...), closer to and hence a reminiscence to standard SQL. The arguments are the same as in .columns

SELECT .one (entity, columns?) …

This is a variant of SELECT.from that captures we are interested in only the first entry. When executed, a single entry is returned instead of a result set, i.e. the following are equivalent:

const one = await SELECT.one(Authors).where({ID:111})
const all = await SELECT.from(Authors).where({ID:111})
const one = all[0]

SELECT .distinct (entity, columns?) …

The equivalent of SQL’s SELECT DISTINCT ... from entity.




INSERT .into (entity) …

Constructs an INSERT statement. In addition to the standard INSERT.into(Foo) .columns(...) .values(...) it provides the variants .rows and .entries which can automatically be mapped to prepared statements and hence processed more efficiently.

.columns (…)

Specifies the columns for wich values are provided in a subsequently specified .rows or .columns. Find examples below…

.values (…)

Specifies the values which positionally match to specified .columns. Both, .columns and .values can alternatively wrapped into an array. E.g. the following examples are equivalent:

INSERT.into (Books) .columns (
  'ID', 'title', 'author_id', 'stock'
) .values (
  201, 'Wuthering Heights', 101, 12
)
INSERT.into (Books) .columns ([
  'ID', 'title', 'author_id', 'stock'
]) .values ([
  201, 'Wuthering Heights', 101, 12
])

.rows (…)

Allows inserting multiple rows with one statement where each row is an array of values with positional match to specified .columns, e.g. as could be read from a CSV source.

INSERT.into (Books) .columns (
  'ID', 'title', 'author_id', 'stock'
) .rows (
  [ 201, 'Wuthering Heights', 101, 12 ],
  [ 251, 'The Raven', 150, 333 ],
  [ 252, 'Eleonora', 150, 234 ]
)

.entries (…)

Allows inserting multiple rows with one statement where each row is a record with named values, e.g. as could be read from a JSON source.

INSERT.into (Books) .entries (
  { ID:201, title:'Wuthering Heights', author_id:101, stock:12 },
  { ID:251, title:'The Raven', author_id:150, stock:333 },
  { ID:271, title:'Catweazle', author_id:170, stock:222 }
)

.as (query)

Constructs a INSERT into SELECT statement.

INSERT.into('Bar') .as (SELECT.from('Foo'))

INSERT (…entries) .into (entity) …

This is just a syntactical sugar variant to INSERT.into(...).entries(...).




UPDATE (entity) …

UPDATE('Books') .set ({
  stock:{'-=':1},
  lastChanged:Date.now()
}) .where ({ID:01})

.set (…)

Specifies the key-value pairs, with keys being element names of the target entity and values being expressions

.where (expr)

Allows add standard SQL WHERE clauses. The arguments form expressions as explained in section Fluent Expression below.




DELETE .from (entity) …

DELETE.from('Books').where ({stock:{'<':1}})

.where (expr)

Allows add standard SQL WHERE clauses. The arguments form expressions as explained in section Fluent Expression below.




Fluent Expressions…

There are two primary variants to specify expressions, e.g. in .where or .orderBy clauses.

1. as alternating string / value lists

The arguments are an alternating list of (native) query fragments and values or CQN expression objects as in these examples:

SELECT.from('Books').where (
  `author.name in`, SELECT('name').from(Authors)
)
SELECT.from('Foo').where (`name like`, '%foo%', `and (
  ratio between`, 0.1, `and`, 0.3,
  `or ratio >`, 0.9,
`)`)

This variant gives maximum flexibility in constructing expressions, including using native operations which are not even understood by CDS itself. Still it is ensured that all values are passed through the database drivers’ argument bindings, hence avoiding SQL injection.

2. as a simple expression object

… with keys being references to elements and values being either literals or or CQN expression objects as in these examples:

SELECT.from('Foo').where ({ name: {like: '%foo%'}, {or:{
  ratio: {between: 0.1, and: 0.3}, or: {ratio:{'>': 0.9}}
}})

cds.env… options

cds.env provides access to an effective environment. This environment has been built-up from various configuration sources as described here.

Example: Assume configuration is done via the following section in package.json.

...
  "cds": {
    "build": {
      "target": "_out"
    },
    "sql_mapping": "plain"
  }
...

Then, these properties can be accessed as:

let t = cds.env.build.target  //> t = "_out"
let s = cds.env.sql_mapping   //> s = "plain"

cds.env.for (node) cds.env

todo …

cds.load (files) csn

Loads and parses a model from one or more files into a single effective model.

Arguments:

Note: It is recommended to omit file suffixes to leverage automatic loading from pre-compiled CSN files instead of CDL sources.

Returns a Promise resolving to parsed model in CSN format, which…

Usages:

// load a model from a single source
const csn = await cds.load('my-model')
// load a a model from several sources
const csn = await cds.load(['db','srv'])
// load relative to current working directory
cds.load('relative-to-cwd')
cds.load('./relative-to-cwd')
// load relative to current node module
cds.load(__dirname+'/relative-to-this-module')

cds.parse (cdl) csn

This is both, a function acting as a shortcut to cds.parse.cdl as well as the root for set of individual functions to parse whole CDL models, as well as individual CQL queries and expressions. Use it as follows:

cds.parse (`entity Foo{}`)  //= shortcut to:
cds.parse.cdl (`entity Foo{}`)
cds.parse.cql (`SELECT * from Foo`)
e = cds.parse.expr (`foo.bar > 9`)
x = cds.parse.xpr (`foo.bar > 9`)
r = cds.parse.ref (`foo.bar`)

The last three lines would return these CXN expressions respectively:

e = {xpr:[ {ref:['foo', 'bar']}, '>', {val:9} ] }
x = [ {ref:['foo', 'bar']}, '>', {val:9} ]
r = ['foo', 'bar']


The individual methods are:

cds.parse.cdl (cdl) csn

Parses a source string in CDL syntax and returns it as a parsed model according to the CSN spec.

cds.parse.cql (cql) cqn

Parses a source string in CQL syntax and returns it as a parsed query according to the CQN spec.

cds.parse.expr (cql expr) expr

Parses a source string in CQL expression syntax and returns it as a parsed expression according to the CQN Expressions spec.

cds.parse.xpr (cql expr) expr.xpr

Convenience shortcut to cds.parse.expr(x).xpr

cds.parse.ref (cql expr) expr.ref

Convenience shortcut to cds.parse.expr(x).ref

cds.reflect (csn) csn

Use these methods to reflect given models.

cds.reflect / linked (csn) ReflectedModel

Both methods, cds.reflect and cds.linked construct and return decorators for given parsed models, which provide the reflection methods documented below.

Returned instances are cached, so subsequent calls to cds.reflect with the same parsed model return the same cached instance.

let model = await cds.load ('some-cds-model')
let reflected = cds.reflect (model)       //> result is cached
let reflected2 = cds.reflect (model)      //> === reflected
let reflected3 = cds.reflect (reflected)  //> === reflected

Linking means protoype-chaining all definitions, including element definitions, to their base definitions up to one of cds.builtin.types. This in turn allows to use all the methods as documented below on all definitions and elements thereof.

↳ m.exports / entities / services (namespace) {defs}

Provide convenient access to a model’s definitions within a given namespace.

Typical usages:

let m = cds.reflect (csn)
let { CatalogService } = m.services()
let { Books, Authors } = m.entities ('my.bookshop')

If no namespace is specified, the model’s declared namespace is used, if any.

The methods each return an object of respective definitions. Object descruturing operators allow to easily access single definitions by name as shown above.

↳ m.each* (x, defs?) Iterator<defs>

Fetches definitions matching the given filter, returning an iterator on them.

let m = cds.reflect (csn)
for (let d of m.each('entity')) {
  console.log (d.kind, d.name)
}

The first argument x speficies a filter to match definitions, wich can be one of…

Derived kinds are supported, e.g. m.each('struct') matches structs as well as entities; kind 'any' matches all.

The second optional argument defs allows to specify the definitions to fetch in, defaults to this.definitions.

↳ m.all (x, defs) [defs]

Convenience shortcut to [... model.each()], e.g. the following are equivalent:

m.all('entity')        //> using shortcut
[...m.each('entity')]  //> using spread operator

↳ m.find (x, defs) def

Convenience shortcut to fetch definitions matching the given filter, returning the first match, if any. For example:

let service = m.find('service')

The implementation uses to .each() as follows:

for (let any of m.each('service'))  return any

↳ m.foreach / forall (x, visitor, defs)

Calls the visitor for each definition matching the given filter. foreach iterates through the passed in defs only, forall in addition walks through all nested element definitions hierarchically.

Examples:

// print the names of all services
let m = cds.reflect(csn)
m.foreach ('service', s => console.log(s.name))
// print the names of all Associations in Books element
let { Books } = m.entities()
m.foreach ('Association', a => console.log(a.name), Books.elements)
// print the names of all Associations in the model
m.forall ('Association', a => console.log(a.name))
// print hierarchy of all definitions recursively
let m = cds.linked(csn)
m.forall (d => {
  let s=''; for (let p=d.parent; p; p=p.parent)  s += '  - '
  console.log (s, d.kind, d.name)
})

cds.builtin.types

The dictionary of built-in root definitions. These are linked definitions which serve as prototypes of derived definitions in linked models.

let model = cds.parse (`
   entity Books { author: Association to Authors; }
   entity Authors { key ID: UUID; }
`)
let m = cds.linked(model)
let { Books, Authors } = m.entities
let isEntity = Books instanceof cds.entity
let keys = Books.keys
let { author } = Books.elements
if (author.is2many) ...

See also: cds.builtin.classes

↳ any instanceof built-in class true|false

You can use JavaScript’s standard instanceof in combination with the built-in classes to check a linked definition’s type:

let m = cds.linked(csn)
let { Foo } = m.entities
if (Foo instanceof cds.entity)  console.log ("it's an entity")

↳ any.name / kind string

Kind is one of context, service, entity, type, element or annotation as documented in the CSN specification.

↳ any.parent def

Refers to the parent definition of elements. Is undefined for top-level definitions.

↳ entity.keys {defs}

Returns an object with an entity definition’s declared primary keys by name. The returned object adheres to the specification of CSN Definitions.

↳ assoc._target def

Refers to the association’s resolved target definition.

↳ assoc.is2one / is2many true|false

Convenient shortcut to check whether an association definition is to-one or to-many. See also the specification of CSN Associations

cds.builtin.classes

This property gives you access to the roots of cds’s type system: (indentation indicates inheritance)

any
  type
    scalar
      boolean
      number
      date
      string
    struct
      entity
    Association
      Composition
  context
    service

A few prominent ones of the above classes are available through these shortcuts:

You can also add mixin behavior to selected classes:

cds.entity .mixin ({
  hello(){ console.log ("I'm an entity named", this.name) }
})
cds.Association .mixin ({
  hello(){ console.log ("I'm an Association to", this._target.name) }
})

cds.compile (csn) …

Entry point to a collection of different backend processors. Each of which expects a parsed model in CSN format as input and generates different output.

cds.compile (csn) .for (target)

cds.compile (csn) .to (target)

Fluent API variants for the methods below. E.g. the following pairs are equivalent alternative ways to use cds.compile:

cds.compile(csn).for('odata')
cds.compile.for.odata(csn)
cds.compile(csn).to('yml')
cds.compile.to.yml(csn)

Fluent API variant to use the methods above. E.g. the following pairs are equivalent alternative ways to use cds.compile:

cds.compile.for.odata (csn) csn

Applies unfold.all and in addition resolves certain specific shortcut annotations to their standard-OData counterparts.

cds.compile.to.json (csn) json

Renders the passed in csn model to a formatted JSON string.

cds.compile.to.yaml (csn) yaml

Renders the passed in csn model to a formatted YAML string.

cds.compile.to.cdl (csn) CDL

Reconstructs CDL source code for the given csn model.

cds.compile.to.sql (csn, options) SQL DDL

Generates SQL DDL statements for the given model. Returns an array with the generated statements unless called with option as=’str’, e.g.

cds.compile.to.sql (csn, {as:'str'})

cds.compile.to.hana (csn) hdbcds

Reconstructs CDL source code for the given csn model specialized for deployment to HANA CDS. Returns a generator is returned that yields [ src, {name,suffix} ] for each resulting .hdbcds file. For example, use it as follows:

let all = cds.compile.to.hana (csn)
for (let [src,{name,suffix}] of all)  console.log (name+suffix,src)

cds.compile.to.edm (csn, options) edm

Compiles and returns an OData v4 EDM model object for the passed in model, which is expected to contain at least one service definition. If more than one service definitions are contained use {service:...} option parameter to …

In case of the latter, a generator is returned that yields [ edm, {name} ] for each service. For example, use it as follows:

// for one service
let edm = cds.compile.to.edm (csn, {service:'Catalog'})
console.log (edm)
// for all services
let all = cds.compile.to.edm (csn, {service:'all'})
for (let [edm,{name}] of all)  console.log (name,edm)

cds.compile.to.edmx (csn, options) edmx

Compiles and returns an OData EDMX model for the passed in model, which is expected to contain at least one service definition. If more than one service definitions use the same options as documented for cds.compile.to.edm above.

The following additional options are supported:

cds.compile.to.annos (csn) edmx

Convenient shortcut to easily get an annotations.xml output for a given service model. Mainly provided for easy invocation from command line interface. E.g. the following uses are equivalent:

cds.compile.to.edmx (csn, {annos:'only'})
cds.compile.to.annos (csn)
# command line
cds compile my-srv -2 annos
cds compile my-srv --to annos

cds.compile.to.swgr (csn) swgr

Compiles and returns an OpenAPI definition for the passed in model. Returns an array with the generated statements unless called with option as:

cds.compile.to.swgr (csn)              //> returns an object
cds.compile.to.swgr (csn, {as:'json'})
cds.compile.to.swgr (csn, {as:'yml'})
cds.compile.to.swgr (csn, {as:'str'})  //> defaults to {as:'yml'}