Search

Core Services APIs

Class cds.Service is the base class for all provided and connected services. As such, and complemented by cds.connect and cds.Request, it provides the essential APIs for implementing and consuming services.

Most frequently you would use these APIs from within custom implementations of your provided services. Here’s an example from cap/samples with references to used APIs:

const cds = require('@sap/cds')

class CatalogService extends cds.ApplicationService { async impl(){
  const db = await cds.connect.to ('db')
  const { Books } = db.entities ('sap.capire.bookshop')

  this.on ('submitOrder', async req => {
    const {book,amount} = req.data
    let {stock} = await db.read (Books,book, b => b.stock)
    if (stock >= amount) {
      await db.update (Books,book) .with ({ stock: stock -= amount })
      await this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
      return req.reply ({ stock })
    }
    else return req.error (409,`${amount} exceeds stock for book #${book}`)
  })

  this.after ('READ','Books', each => {
    if (each.stock > 111) each.title += ` -- 11% discount!`
  })
}}

module.exports = { CatalogService }

APIs used:


Subclassing cds.Service
Using cds.connect
Model Reflection

Handler Registration
Using req.data
Querying

Querying
Messaging
Using req.reply

Using req.error


Handler Registration





Content

(Custom) Service Implementations

Service implementations are essentially collections of event handlers registered with service instances to handle incoming requests and event messages. The sections below explain where and how to do so.

Where to Implement Services?

In sibling.js files next to .cds sources

The easiest way to add service implementations is to simply place equally named .js files next to the .cds files containing the respective service definitions. In addition to direct siblings you can place them into relative subdirectories ./lib or ./handlers, allowing layouts like that:

srv/
  # all in one
  foo-srv.cds
  foo-srv.js
  bar-srv.cds
  bar-srv.js
srv/
  lib/
    foo-srv.js
    bar-srv.js
  foo-srv.cds
  bar-srv.cds
srv/
  handlers/
    foo-srv.js
    bar-srv.js
  foo-srv.cds
  bar-srv.cds

In files specified in @impl annotations

Use the @impl annotation to specify alternative files to load implementations from:

// srv/services.cds
service FooService @(impl:'./foo-service.js') {}
service BarService @(impl:'./bar-service.js') {}

Learn more in configuration options on @impl.

How to Implement Services?

As plain functions

The simplest way to provide custom event handlers is to return a function that registers event handler with the instance of cds.Service as follows:

module.exports = function(){ // `this` is the instance of cds.Service
  this.before (['CREATE','UPDATE'],'Books', (req)=>{...})
  this.on ('UPDATE','Books', (req)=>{...})
  this.after ('READ','Books', (books)=>{...})
}

Alternatively you can provide an arrow function like so:

module.exports = (srv)=>{ // `srv` is the instance of cds.Service
  srv.on ('UPDATE','Books', (req)=>{...})
  // ...
}

Getting Code Assists — Wrap the impl function into cds.service.impl(...), which simply returns the function but gives you code assists in tools like VSCode:

const cds = require('@sap/cds')
module.exports = cds.service.impl (function(){ ... })

Adding async/await — Quite frequently you need to call async functions in your impl functions, just declare your impl function as async to use await inside:

const cds = require('@sap/cds')
module.exports = cds.service.impl (async function(){ 
  const SomeOtherService = await cds.connect.to('SomeOtherService')
  // ...
})

Wrapped with cds.service.impl

As subclasses of cds.Service

Starting with @sap/cds 4 you can also return subclasses of cds.Service from service implementation modules like so:

// e.g. in srv/cat-service.js
const cds = require('@sap/cds')
module.exports = class CatalogService extends cds.ApplicationService {
  async init(){
    this.on ('UPDATE','Books', (req)=>{...}) // overrides the default handler
    await super.init()
    this.before (['CREATE','UPDATE'],'Books', (req)=>{...})
    // ...
  }
}

The init() method acts like a parameter-less constructor. Ensure to call await super.init() as in the previous example, to have the base class’s handlers added.

As also shown in the previous example, you may register own handlers before the base class’s ones, to intercept requests before the default handlers snap in.

While you should prefer overloading the init() method as shown in the previous example, sometimes you may also have to override the constructor, which has this signature (in typescript):

class Service {
  constructor (name:String, model:csn, options:object)
}

With multiple implementations in one file

// srv/services.cds
service FooService {}
service BarService {}
// srv/services.cds
service FooService {}
service BarService {}

Configuration / Annotations

Following are options used to control the construction of services, as either of:

@impl  |  o.impl = class | instance | function | module name

Use the impl option specify the implementation used by this service.

Using the @impl Annotation

Add an @impl annotation to your service definition referring to a file in your project to load the implementation from:

// leading ./ -> relative to the .cds source
service FooService @(impl:'./lib/foo-service') {...}
// otherwise -> relative to project root
service BarService @(impl:'src/lib/bar-service') {...}
Using cds.requires.<service>.impl in Configurations

For required services you can specify a custom implementation to use like so:

"cds": { 
  "requires": {
    "reviews-service": { 
      "kind": "odata", 
      "impl": "srv/external/reviews-service" 
    },
  }
}
As ad-hoc options to cds.serve or cds.connect

You can also specify ad-hoc options in calls to cds.serve or cds.connect like so:

const ReviewsService = await cds.connect.to ('reviews-service', {
  impl: 'srv/external/reviews-service'
})

In this case, you can also specify an impl function, as subclass of cds.Service or an instance of such like so:

const ReviewsService = await cds.connect.to ('reviews-service', {
  impl: 'srv/external/reviews-service'
})

@kind  |  o.kind = cds.requires.<kind>

Use option kind to refer to an entry in the cds.requires config options, which in turn contains presets for the other options, in particular for impl.

For Required Services

In case of required services, you need to specify option kind in respective entries to the cds.requires config option like so:

"cds": { 
  "requires": {
    "reviews-service": { "kind": "odata" },
    "db": { "kind": "sql" },
  }
}

The values usually refer to these pre-defined options:

  • sql, sqlite, hana → for database services
  • odata, rest → for external services
  • enterprise-messaging, file-based-messaging → for messaging services

Run cds env get requires to see all default configurations.

For Provided Services

For services provided by your app, the default is kind = 'app-service', which is configured in built-in defaults like so:

"cds": { 
  "requires": {
    "app-service": { "impl": "@sap/cds/lib/srv/ApplicationService.js" }
  }
}

Run cds env get requires.app-service to see the concrete default config.

In effect, cds.serve constructs instances of cds.ApplicationService by default.

Using Custom Kinds

You can override this by adding a @kind annotation to your service definition:

service FooService @(kind:'app-service') {...} // default
service BarService @(kind:'bar-service') {...} // custom

… backed by a corresponding entry to cds.requires config options:

"cds": { 
  "requires": {
    "bar-service": { "impl": "srv/bar-service.js" },
  }
}

@path  |  o.path = string

@protocol  |  o.protocol = string

@endpoints  |  o.endpoints = […{ path, protocol }]

.path

.protocol

@credentials  |  o.credentials = { url, … }

Specific options, passed to services. For example, in case of a SQLite database service these are driver-specific options node-sqlite3

@model  |  o.model = csn

The 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].

Model Reflection API

srv.name = string

The service’s name, that means, the definition’s name for services constructed with cds.serve, or the name of required services as passed to cds.connect.

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

srv.model = csn

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

This is === cds.model by default, that is, unless you created services yourself with cds.serve, specifying alternative models to load and construct new services from.

srv.definition def

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

srv.namespace string

srv.entities (namespace) {defs}
srv.events (namespace) {defs}
srv.operations (namespace) {defs}

These methods provide convenient access to the linked definitions of all entities, events, and operations (that means, actions, and functions), provided by this service.

Common Usage:

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.

Using Fully Qualified Names:

Many service consumption methods, such as srv.run expect fully qualified names of entities or an entity definition. Using these reflection methods greatly simplifies you coding as it avoids repeating namespaces all over the place.

For example, the fully qualified name of entity Books in cap/samples/bookshop is sap.capire.bookshop.Books. So, queries would have to be written like that using name strings:

const books = await cds.read ('sap.capire.bookshop.Books')

So, rather prefer that pattern:

const { Books } = cds.entities
const books = await cds.read (Books)

Handler Registration API

Register event handlers with instances of cds.Service to add custom logic to serve operations or react to events emitted by these services.

With provided as well as connected services — Most frequently you would add event handlers to provided services. Yet, you can also add handlers to services consumed in your project, even to generic services provided by the CAP framework itself, like the default database service:

const db = await cds.connect('db')
db.before ('INSERT','Orders', req => {...})
db.after ('READ','*', each => {...})

For synchronous as well as asynchronous events — The APIs documented below are used to register handlers for synchronous requests as well as for subscription to asynchronous events. → Find details on the latter in srv.on (async event).

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

Handlers registered with this method are run in sequence, with each handler being able to terminate the sequence (cf. Outcomes below). This termination capability in combination with registering handlers using [srv.prepend], lets you register custom handlers to run instead of the generic ones. If you want to defer to the generic handlers, invoke next in your custom handler (see Handling Examples below).

Arguments:

  • event — name of a single event or an array of multiple such
  • path — entity CSN object, name of an exposed entity, a path, or an array of multiple such
  • handler(req, next) — the handler function

A service handler registered by entity name can use a relative or fully qualified name, containing the service namespace. A database handler needs a fully qualified entity name.

learn more about the req argument in handler functions.

Registration Examples:

// Direct request, for example, GET /Books
srv.on('READ','Books', (req)=>{...})
// Registration by fully-qualified name, for example, GET /Books
srv.on('READ','serviceName.Books', (req)=>{...})
// Navigation requests, for example, GET /Authors/201/authors
srv.on('READ','Authors/books', (req)=>{ let [ID] = req.params; ...})
// Registration by CSN, for example, GET /Books
srv.on('READ', Books, (req)=>{...})
// Calls to unbound actions/functions
srv.on('cancelOrder', (req)=>{...})
// Calls to bound actions/functions
srv.on('cancel','Order', (req)=>{...})

Handling Examples:

Requests can be handled in one of the following ways:

  1. Call [req.reply] synchronously or asynchronously:
srv.on('READ','Books', async (req)=> req.reply(await cds.tx(req).run(...))
srv.on('READ','Books', (req)=> req.reply(...))
  1. Return results or a Promise resolving to some → framework calls [req.reply]:
srv.on('READ','Books', (req)=> cds.tx(req).run(...))
srv.on('READ','Books', (req)=> [ ... ])
  1. Return a cds.ql object → framework does cds.run and [req.reply]:
srv.on('READ','Books', ()=> SELECT.from(Books))
  1. Call next as in express.js to delegate to handlers down the chain:
srv.on('READ','Books', (req,next)=>{
  if (...) return SELECT.from(Books) //> ... handle req my own
  else return next()  //> delegate to next/default handlers
})

IMPORTANT: Ensure to properly add calls to next() in your promise chain, either using await or by passing through its return value as shown in the previous example.

  1. Reject the request through [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 ... })

Shortcuts:

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

const {Books} = srv.entities
srv.on('READ','Books', (req)=> cds.tx(req).run(SELECT.from(Books))
srv.on('READ','Books', ()=> SELECT.from(Books))
srv.on('READ','Books', SELECT.from(Books))

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 isn’t 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.on ('*', ()=> {/* handles all events on all entities */})
})

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

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

Arguments are the same as in srv.on, except that the handler only has the request argument (that is, handler(req)).

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: 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:

  • All before handlers are executed in order of their registration.
  • Always return a promise if your handlers trigger asynchronous tasks.
  • All promises returned are collected in a Promise.all and
  • The next phase running the .on handlers starts when all promises resolved.

Hence: You can terminate requests early by throwing exceptions or through [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, except that the handler has the result set array as first and the request as second argument (that is, handler(data, req)).

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) ...
})

Only synchronous modifications are allowed. That is, replacements and asynchronous modifications like in the following two examples aren’t possible:

this.after('READ','Books', (books)=>{
  return \<something else...\>
})
this.after('READ','Books', async (each)=>{
  const orders = await SELECT.from('Orders').where({book_ID: each.ID})
  orders.length > 100 && each.description = `Bestseller! ${each.description}`
})

Reasoning: Multiple after handlers can be added by different involved packages, including extension packages, and each of which would expect the same input. If one would exchange the results, the chain would break.

To replace/ asynchronously modify the result, use on handlers as follows:

this.on('READ','Books', async (req,next)=>{
  const books = await next()
  return \<something else...\>
})

In case the result is a primitive, for example, a string, then also synchronous modifications must be done in an on handler as the change would stay local to the after handler (pass by value).

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:

  • event — name of a single event or an array of multiple such
  • entity — name of an exposed entity or an array of multiple such

Examples:

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

srv.prepend (function)

Use srv.prepend in order to register handlers, which shall be executed before already registered handlers. In particular, this can be used to override handlers from reused services as in cap/samples/fiori/srv/mashup.js:

For example, the following would register a handler for inserting Orders that runs instead of the default handlers of the connected database service.

const CatalogService = await cds.connect.to ('CatalogService')
const ReviewsService = await cds.connect.to ('ReviewsService')

//
// Delegate requests to read reviews to the ReviewsService
// Note: prepend is neccessary to intercept generic default handler
//
CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => {
  console.debug ('> delegating request to ReviewsService')
  const [id] = req.params, { columns, limit } = req.query.SELECT
  return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)})
}))

You could also use that to add support for custom behavior to generic database services:

const db = await cds.connect('db')
db.prepend (()=>{
  db.on ('INSERT','Orders', req => {...})
})

srv.<action/function> (…args) response

In case you declared (unbound) custom actions and functions in the service definition of a connected or provided service, the respective instance of cds.Service will automatically be equipped with corresponding JavaScript methods, allowing you to write concise and comprehensible code.

For example, with that service definition (taken from cap/samples/bookshop):

service CatalogService { ...
  action submitOrder (book: Books:ID, amount: Integer);
}

You can invoke the declared submitOrder action from your code as follows:

const cats = cds.connect.to('CatalogService')
const res1 = await cats.submitOrder ({ book:111, amount:1 })
const res2 = await cats.submitOrder (111,1) // positional args variant

Messaging API

srv.emit (event, data?, headers?)

This is the basic method to send both, asynchronous event messages, as well as synchronous requests. The implementation constructs an instance of cds.Request, which is then dispatched through all registered event handlers.

Common Usages:

You can use srv.emit as a basic and flexible alternative to srv.run, for example to send queries plus additional request headers to remote services:

const query = SELECT.from(Foo), tx = srv.tx(req)
tx.run (query)       //> without custom headers
tx.emit ({ query })  //> equivalent to tx.run()
tx.emit ({ query, headers:{...} })

A typical usage for asynchronous messages looks like that:

this.emit ({ event:'reviewed', data:{ subject, rating }, ... })
this.emit ('reviewed', { subject, rating }) //> see below

Learn more about Event Messages. Learn more about Requests.

Returns a Promise resolving to the response of respective event handlers.

For results of queries, see srv.run.

srv.emit ({ event, data?, headers? })

Convenience variant to srv.emit, which allows to specify the primary properties for emitting asynchronous event messages.
Here’s an example from cap/samples/reviews:

this.emit ('reviewed', { subject, rating })

srv.on (event, handler) this

Subscribe to asynchronous events by registering handlers using the common srv.on() method.

There’s one major difference, though: Handlers for synchronous requests execute as interceptors, which pass control to subsequent handlers by calling next(), handlers for asynchronous events execute as listeners with all registered handlers being executed without calling next().

REST-style API

srv.send (event, data?, headers?) results

TODO

srv.send ({ event, data?, headers? }) results

TODO

Convenient Shortcuts:

srv.get (entity | path, data?) results

srv.put (entity | path, data?) results

srv.post (entity | path, data?) results

srv.patch (entity | path, data?) results

srv.delete (entity | path, data?) results

These methods are HTTP method-style counterparts to the CRUD-style convenience methods. As with these, the returned queries can be executed with await.

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

Common Usages:

await srv.get(Books).where({ID:111})
await srv.post(Books).entries({ID:111, ...})
await srv.patch(Books,111).with({...})
await srv.delete(Books,111)

These are equivalent to:

await srv.read(Books).where({ID:111})
await srv.create(Books).entries({ID:111, ...})
await srv.update(Books,111).with({...})
await srv.delete(Books,111)

Note: UPDATE translates to PATCH, not PUT.

Plain REST Usages:

While the previous usage samples still constructed queries, and rather worked as using syntax aliases, you can also use these methods to send plain, arbitrary HTTP requests to remote services. Just pass a string starting with / as the first argument to do so:

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')

Querying API

srv.run (query) results

This is the central function to run queries. It expects a query or an array of queries in CQN notation, and returns a Promise resolving to the queries’ results.

Common Usage:

const srv = await cds.connect.to ('SomeService') 
const { Books, Authors } = srv.entities //> reflection
const books = await srv.run (SELECT.from(Books))
const [books, authors] = await srv.run ([SELECT.from(Books), SELECT.from(Authors)])

Learn more about service-related reflection using srv.entities. Learn more about srv.run variant to send native query string.

If an array of queries is passed to srv.run, queries are run in parallel.

Returns a Promise resolving to…

  • to a number-like object indicating affected rows in case of UPDATE, DELETE.
  • an iterable allowing to reflect on (generated) primary keys
  • an array of rows for SELECT queries

For example, you can use results like so:

const tx = cds.tx(req)
const [ Emily, Charlotte ] = await tx.run (INSERT.into (Authors,[
   {name:'Emily Brontë'},
   {name:'Charlotte Brontë'},
]))
await tx.run (INSERT.into(Books).columns('title','author_ID').rows(
   [ 'Wuthering Heights', Emily.ID ],
   [ 'Jayne Eyre', Charlotte.ID ],
))
const books = await tx.run (SELECT.from(Books))
const affectedRows = UPDATE(Books,books[0].ID)
  .with(`stock -=`,order.amount)
  .where(`stock>=`,order.amount)
if (affectedRows < 1) req.reject(409,'Sold out, sorry')

srv.run (string, args?) results

Variant of srv.run which accepts native query strings, as understood by the receiving service, instead of CQN queries. For example, a SQL string in case of a connected SQL database.

Common Usage:

cds.run (`SELECT * from sap_capire_bookshop_Books`)
cds.run (`CALL Some_Stored_Procedure (11,'foo')`)
cds.run (`SELECT * from Some_Table_Function (11,'foo')`)

You need to pass fully qualified database table names.

Passing Arguments:

Argument args is an optional array or object of binding parameters bound to respective placeholders in the query using one of these placeholders:

  • :name — named parameters bound to respective entries in an args object
  • ? — positional parameters bound to entries of args in order of occurrence
cds.run ('SELECT * from Authors where name like ?',['%Poe%'])

Prefer that over concatenating values into query strings to avoid SQL injection.

Convenient Shortcuts:

srv.read (entity, key?, projection?) SELECT query

srv.create (entity, key?) INSERT query

srv.update (entity, key?) UPDATE query

srv.delete (entity, key?) DELETE query

These methods construct queries in a fluent method-call style instead of the Embedded QL style provided by cds.ql. The returned queries can be executed with await.

A typical usage is as follows:

const books = await cds.read('Books').orderBy('title')

Essentially, each of these 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(). For example, think of the implementation of cds.read as follows:

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

The following three variants to read Books are equivalent:

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

srv.insert (data) .into (entity) INSERT query

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

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

Streaming API

srv.stream (column) .from (entity) .where (filter) Readable Stream

This method allows streaming binary data properties. It returns a read stream which can be used to pipe to write streams, as shown in the following examples.

const stream = srv.stream().from('T', {ID: 1}, a => a.data)
stream.pipe(process.stdout)
const stream = srv.stream('data').from('T', {ID: 1})
stream.pipe(process.stdout)
const stream = srv.stream('data').from('T').where({ID: 1})
stream.pipe(process.stdout)

srv.stream (cqn) Promise < Readable Stream >

This is a variant of srv.stream, which accepts a SELECT query as input and returns a Promise resolving to result stream when the query matched to an existing row in the database. The query is expected to select a single column and a single data row. Otherwise, an error is thrown.

const stream = await srv.stream( SELECT('image').from('Foo',111) )
stream.pipe(process.stdout)

srv.foreach (entity | query, args?, callback) Promise

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.

Common Usages:

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

As depicted in the second line, a plain entity name can be used for the entity argument in which case it’s expanded to a SELECT * from ....

Transactions API

srv.tx (req?) tx <srv,req>

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

this.on ('READ','Books', async (req)=>{
  const srv = await cds.connect.to('another-service')
  const { Books } = srv.entities
  const tx = srv.tx(req)
  const books = await tx.run (SELECT.from (Books))
  return books
})

The returned object supports the same methods to read or write data than the service it’s called on, that is, the ones listed below.

Arguments:

  • context can be an arbitrary object to which the transaction is assigned. Most frequently it’s the [req] argument in an event handler. If omitted, a new transaction will be started.

Analysis:

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

  • appropriate client credential flows and tenant isolation
  • propagation of requisite request context properties, such as the current user’s preferred language

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

srv.begin () tx

tx.commit (result) result

tx.rollback (error?) throws error

Show/Hide Beta Features