Skip to content

Latest commit

 

History

History
461 lines (248 loc) · 16.5 KB

events.md

File metadata and controls

461 lines (248 loc) · 16.5 KB
redirect_from status
node.js/requests
released

Events and Requests

[[toc]]

cds. context {.property}

This property provides seemingly static access to the current cds.EventContext, that is, the current tenant, user , locale, and so on, from wherever you are in your code. For example:

let { tenant, user } = cds.context

Usually that context is set by inbound middleware.

The property is realized as a so-called continuation-local variable, implemented using Node.js' async local storage technique, and a getter/setter pair: The getter is a shortcut forgetStore(). The setter coerces values into valid instances of cds.EventContext. For example:

[dev] cds repl
> cds.context = { tenant:'t1', user:'u2' }
> let ctx = cds.context
> ctx instanceof cds.EventContext  //> true
> ctx.user instanceof cds.User     //> true
> ctx.tenant === 't1'              //> true
> ctx.user.id === 'u2'             //> true

If a transaction object is assigned, its tx.context is used, hence cds.context = tx acts as a convenience shortcut for cds.context = tx.context:

let tx = cds.context = cds.tx({ ... })
cds.context === tx.context  //> true

::: tip

Prefer local req objects in your handlers for accessing event context properties, as each access to cds.context happens through AsyncLocalStorage.getStore(), which induces some minor overhead.

:::

Class cds.EventContext { #cds-event-context }

Instances of this class represent the invocation context of incoming requests and event messages, such as tenant, user, and locale. Classes cds.Event and cds.Request inherit from it and hence provide access to the event context properties:

this.on ('*', req => {
  let { tenant, user } = req
  ...
})

In addition, you can access the current event context from wherever you are in your code via the continuation-local variable cds.context:

  let { tenant, user } = cds.context

. http {.property}

If the inbound process came from an HTTP channel, this property provides access to express's common req and res objects. The property is propagated from cds.context to all child requests. So, on all handlers, even the ones in your database services, you can always access that property like so:

this.on ('*', req => {
  let { res } = req.http
  res.send('Hello!')
})

. id {.property}

A unique string used for request correlation.

For inbound HTTP requests the implementation fills it from these sources in order of precedence:

  • x-correlation-id header
  • x-correlationid header
  • x-request-id header
  • x-vcap-request-id header
  • a newly created UUID

On outgoing HTTP messages, it's propagated as x-correlation-id header.

For inbound CloudEvents messages, it's taken from the id context property and propagated to the same on outgoing CloudEvents messages.

. locale {.property}

The current user's preferred locale, taken from the HTTP Accept-Language header of incoming requests and resolved to normalized.

. tenant {.property}

A unique string identifying the current tenant, or undefined if not in multitenancy mode. In the case of multitenant operation, this string is used for tenant isolation, for example as keys in the database connection pools.

. timestamp {.property}

A constant timestamp for the current request being processed, as an instance of Date. The CAP framework uses that to fill in values for the CDS pseudo variable $now, with the guaranteed same value.

Learn more in the Managed Data guide.{.learn-more}

. user {.property}

The current user, an instance of cds.User as identified and verified by the authentication strategy. If no user is authenticated, cds.User.anonymous is returned.

See reference docs for cds.User.{.learn-more .indent}

Class cds.Event { #cds-event}

Class cds.Event represents event messages in asynchronous messaging, providing access to the event name, payload data, and optional headers. It also serves as the base class for cds.Request and hence for all synchronous interactions.

. event {.property}

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

  • The name of an incoming CRUD request like CREATE, READ, UPDATE, DELETE
  • The name of a custom action or function like submitOrder
  • The name of a custom event like OrderedBook

. data {.property}

Contains the event data. For example, the HTTP body for CREATE or UPDATE requests, or the payload of an asynchronous event message.

Use req.data for modifications as shown in the following:

this.before ('UPDATE',Books, req => {
  req.data.author = 'Schmidt'  // [!code ++]
  req.query.UPDATE.data.author = 'Schmidt'  // [!code --]
})

. headers {.property}

Provides access to headers of the event message or request. In the case of asynchronous event messages, it's the headers information sent by the event source. For HTTP requests it's the standard Node.js request headers.

eve. before 'commit' {.event}

eve. on 'succeeded' {.event}

eve. on 'failed' {.event}

eve. on 'done' {.event}

Register handlers to these events on a per event / request basis. The events are executed when the whole top-level request handling is finished

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

req.before('commit', () => {...}) // immediately before calling commit
req.on('succeeded', () => {...}) // request succeeded, after commit
req.on('failed', () => {...}) // request failed, after rollback
req.on('done', () => {...}) // request succeeded/failed, after all

::: danger The events succeeded , failed, and done are emitted after the current transaction ended. Hence, they run outside framework-managed transactions, and handlers can't veto the commit anymore. :::

To veto requests, either use the req.before('commit') hook, or service-level before COMMIT handlers.

To do something that requires databases in succeeded/failed handlers, use cds.spawn(), or one of the other options of manual transactions. Preferably use a variant with automatic commit/ rollback.

Example:

req.on('done', async () => {
  await cds.tx(async () => {
    await UPDATE `Stats` .set `views = views + 1` .where `book_ID = ${book.ID}`
  })
})

Additional note about OData: For requests that are part of a changeset, the events are emitted once the entire changeset was completed. If at least one of the requests in the changeset fails, following the atomicity property ("all or nothing"), all requests fail.

Class cds.Request { #cds-request }

Class cds.Request extends cds.Event with additional features to represent and deal with synchronous requests to services in event handlers, such as the query, additional request parameters, the authenticated user, and methods to send responses.

. _ {.property}

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

::: warning Please refrain from using internal properties of that object, that is, the ones starting with '_'. They might be removed in any future release without notice. :::

. method {.property}

The HTTP method of the incoming request:

msg.event msg.method
CREATE POST
READ GET
UPDATE PATCH
DELETE DELETE

{style="font-style:italic;width:auto;"}

. target {.property}

Refers to the current request's target entity definition, if any; undefined for unbound actions/functions and events. The returned definition is a linked definition as reflected from the CSN model.

For OData navigation requests along associations, msg.target refers to the last target. For example:

OData Request req.target
Books AdminService.Books
Books/201/author AdminService.Authors
Books(201)/author AdminService.Authors

{style="font-style:italic;width:80%;"}

See also req.path to learn how to access full navigation paths.{.learn-more} See Entity Definitions in the CSN reference.{.learn-more} Learn more about linked models and definitions.{.learn-more}

. path {.property}

Captures the full canonicalized path information of incoming requests with navigation. For requests without navigation, req.path is identical to req.target.name (or req.entity, which is a shortcut for that).

Examples based on cap/samples/bookshop AdminService:

OData Request req.path req.target.name
Books AdminService.Books AdminService.Books
Books/201/author AdminService.Books/author AdminService.Authors
Books(201)/author AdminService.Books/author AdminService.Authors
{style="font-style:italic"}

See also req.target{.learn-more}

. entity {.property}

This is a convenience shortcut to msg.target.name.

. params {.property}

Provides access to parameters in URL paths as an iterable with the contents matching the positional occurrence of parameters in the url path. In the case of compound parameters, the respective entry is the key value pairs as given in the URL.

For example, the parameters in an HTTP request like that:

GET /catalog/Authors(101)/books(title='Eleonora',edition=2) HTTP/1.1

The provided parameters can be accessed as follows:

const [ author, book ] = req.params
// > author === 101
// > book === { title: 'Eleonora', edition: 2 }

. query {.property}

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

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

If bound custom operations req.query contains the query to the entity, on which the bound custom operation is called. For unbound custom operations, req.query contains an empty object.

. subject {.property}

Acts as a pointer to one or more instances targeted by the request. It can be used as input for cds.ql as follows:

SELECT.one.from(req.subject)   //> returns single object
SELECT.from(req.subject)      //> returns one or many in array
UPDATE(req.subject)          //> updates one or many
DELETE(req.subject)         //> deletes one or many

It's available for CRUD events and bound actions.

req. reply() {.method}

Stores the given results in req.results, which is then sent back to the client, rendered in a protocol-specific way.

req. reject() {.method}

Rejects the request with the given HTTP response code and single message. Additionally, req.reject throws an error based on the passed arguments. Hence, no additional code and handlers is executed once req.reject has been invoked.

Arguments are the same as for req.error{.learn-more}

req. error() {.method}

req. warn() {.method}

req. info() {.method}

req. notify() {.method}

Use these methods to collect messages or errors and return them in the request response to the caller. The method variants reflect different severity levels. Use them as follows:

Variants

Method Collected in Typical UI Severity
req.notify() req.messages Toasters 1
req.info() req.messages Dialog 2
req.warn() req.messages Dialog 3
req.error() req.errors Dialog 4

{style="font-style:italic;width:80%;"}

Note: messages with a severity less than 4 are collected and accessible in property req.messages, while error messages are collected in property req.errors. The latter allows to easily check, whether errors occurred with:

if (req.errors) //> get out somehow...

Arguments

  • code Number (Optional) - Represents the error code associated with the message. If the number is in the range of HTTP status codes and the error has a severity of 4, this argument sets the HTTP response status code.
  • message String | Object | Error - See below for details on the non-string version.
  • target String (Optional) - The name of an input field/element a message is related to.
  • args Array (Optional) - Array of placeholder values. See Localized Messages for details.

::: tip target property for UI5 OData model The target property is evaluated by the UI5 OData model and needs to be set according to Server Messages in the OData V4 Model. :::

Using an Object as Argument

You can also pass an object as the sole argument, which then contains the properties code, message, target, and args. Additional properties are preserved until the error or message is sanitized for the client. In case of an error, the additional property status can be used to specify the HTTP status code of the response.

req.error ({
  code: 'Some-Custom-Code',
  message: 'Some Custom Error Message',
  target: 'some_field',
  status: 418
})

Additional properties can be added as well, for example to be used in custom error handlers.

In OData responses, notifications get collected and put into HTTP response header sap-messages as a stringified array, while the others are collected in the respective response body properties (→ see OData Error Responses).

Error Sanitization

In production, errors should never disclose any internal information that could be used by malicious actors. Hence, we sanitize all server-side errors thrown by the CAP framework. That is, all errors with a 5xx status code (the default status code is 500) are returned to the client with only the respective generic message (example: 500 Internal Server Error). Errors defined by app developers aren't sanitized and returned to the client unchanged.

Additionally, the OData protocol specifies which properties an error object may have. If a custom property shall reach the client, it must be prefixed with @ to not be purged.

req. diff() {.method}

Use this asynchronous method to calculate the difference between the data on the database and the passed data (defaults to req.data, if not passed). Note that the usage of req.diff only makes sense in before handlers as they are run before the actual change was persisted on the database.

This triggers database requests.

const diff = await req.diff()