{"id":35790430,"url":"https://github.com/cap-js-community/websocket","last_synced_at":"2026-05-07T08:02:12.937Z","repository":{"id":213520629,"uuid":"732091217","full_name":"cap-js-community/websocket","owner":"cap-js-community","description":"Exposes a WebSocket protocol via WebSocket standard or Socket.IO for CDS services","archived":false,"fork":false,"pushed_at":"2026-05-04T08:52:37.000Z","size":5311,"stargazers_count":20,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-04T10:35:49.098Z","etag":null,"topics":["cap","cds","plugin","redis","socket-io","websocket","ws"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@cap-js-community/websocket","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cap-js-community.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-12-15T16:11:31.000Z","updated_at":"2026-05-04T08:52:42.000Z","dependencies_parsed_at":"2024-03-08T11:29:44.670Z","dependency_job_id":"ac5912dc-2313-4fa2-a8ec-013e907d6493","html_url":"https://github.com/cap-js-community/websocket","commit_stats":null,"previous_names":["cap-js-community/websocket"],"tags_count":44,"template":false,"template_full_name":"cap-js-community/repository-template","purl":"pkg:github/cap-js-community/websocket","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cap-js-community%2Fwebsocket","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cap-js-community%2Fwebsocket/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cap-js-community%2Fwebsocket/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cap-js-community%2Fwebsocket/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cap-js-community","download_url":"https://codeload.github.com/cap-js-community/websocket/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cap-js-community%2Fwebsocket/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32728380,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-07T02:14:30.463Z","status":"ssl_error","status_checked_at":"2026-05-07T02:14:29.405Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cap","cds","plugin","redis","socket-io","websocket","ws"],"created_at":"2026-01-07T08:11:05.567Z","updated_at":"2026-05-07T08:02:12.898Z","avatar_url":"https://github.com/cap-js-community.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WebSocket Adapter for CDS\n\n[![npm version](https://img.shields.io/npm/v/@cap-js-community/websocket)](https://www.npmjs.com/package/@cap-js-community/websocket)\n[![monthly downloads](https://img.shields.io/npm/dm/@cap-js-community/websocket)](https://www.npmjs.com/package/@cap-js-community/websocket)\n[![REUSE status](https://api.reuse.software/badge/github.com/cap-js-community/websocket)](https://api.reuse.software/info/github.com/cap-js-community/websocket)\n[![Main CI](https://github.com/cap-js-community/websocket/actions/workflows/main-ci.yml/badge.svg)](https://github.com/cap-js-community/websocket/commits/main)\n\n## About this Project\n\nExposes a WebSocket protocol via [WebSocket standard](https://developer.mozilla.org/de/docs/Web/API/WebSockets_API)\nor [Socket.IO](https://socket.io) for CDS services. Runs in the context of the [SAP Cloud Application Programming Model (CAP)](https://cap.cloud.sap)\nusing [@sap/cds](https://www.npmjs.com/package/@sap/cds) (CDS Node.js).\n\n## Table of Contents\n\n- [Getting Started](#getting-started)\n- [Usage](#usage)\n  - [Server](#server)\n  - [Client](#client)\n- [Options](#options)\n- [Documentation](#documentation)\n  - [Architecture Overview](#architecture-overview)\n  - [Protocol Annotations](#protocol-annotations)\n  - [WebSocket Server](#websocket-server)\n  - [WebSocket Implementation](#websocket-implementation)\n    - [WebSocket Service](#websocket-service)\n    - [WebSocket Mixin](#websocket-mixin)\n  - [Server Socket](#server-socket)\n  - [Upgrade Request](#upgrade-request)\n  - [Service Facade](#service-facade)\n  - [Middlewares](#middlewares)\n  - [Tenant Isolation](#tenant-isolation)\n  - [Authentication \u0026 Authorization](#authentication--authorization)\n  - [Invocation Context](#invocation-context)\n  - [Transactional Safety](#transactional-safety)\n    - [CDS Persistent Queue](#cds-persistent-queue)\n  - [Client Determination](#client-determination)\n    - [Filtering Operators](#filtering-operators)\n    - [Event Users](#event-users)\n    - [Event Roles](#event-roles)\n    - [Event Contexts](#event-contexts)\n    - [Event Client Identifiers](#event-client-identifiers)\n    - [Event Emit Headers](#event-emit-headers)\n    - [Value Aggregation](#value-aggregation)\n    - [Format Headers](#format-headers)\n    - [Ignore Definitions](#ignore-definitions)\n  - [WebSocket Format](#websocket-format)\n    - [Push Channel Protocol (PCP)](#push-channel-protocol-pcp)\n    - [Event-Driven Side Effects](#event-driven-side-effects)\n    - [Cloud Events](#cloud-events)\n    - [Custom Format](#custom-format)\n    - [Generic Format](#generic-format)\n  - [Connect \u0026 Disconnect](#connect--disconnect)\n  - [Server Client Service](#server-client-service)\n  - [Approuter](#approuter)\n    - [Paths](#paths)\n  - [Operations](#operations)\n    - [Operation Results](#operation-results)\n    - [Unbound Operations](#unbound-operations)\n    - [Special Operations](#special-operations)\n    - [Bound Operations](#bound-operations)\n    - [CRUD Operations](#crud-operations)\n  - [Examples](#examples)\n    - [Fiori (UI5)](#fiori-ui5)\n    - [Todo (UI5)](#todo-ui5)\n    - [Chat (HTML)](#chat-html)\n  - [Unit-Tests](#unit-tests)\n  - [Adapters](#adapters)\n    - [WS Standard Adapters](#ws-standard-adapters)\n    - [Socket.IO Adapters](#socketio-adapters)\n  - [Deployment](#deployment)\n- [Support, Feedback, Contributing](#support-feedback-contributing)\n- [Code of Conduct](#code-of-conduct)\n- [Licensing](#licensing)\n\n## Getting Started\n\n- Run `npm add @cap-js-community/websocket` in `@sap/cds` project\n- Add a WebSocket-enabled CDS service:\n  ```cds\n  @ws\n  service ChatService {\n    event received {\n      text: String;\n    }\n  }\n  ```\n- Emit event from business logic:\n  ```js\n  await srv.emit(\"received\", { text: \"Hello World!\" });\n  ```\n- Start server via `cds-serve`\n- Access the service endpoint via the WebSocket client\n\n## Usage\n\n### Server\n\n- Run `npm add @cap-js-community/websocket` in `@sap/cds` project\n- Create a service to be exposed as websocket protocol: **srv/chat-service.cds**\n  ```cds\n  @protocol: 'websocket'\n  service ChatService {\n    function message(text: String) returns String;\n    event received {\n      text: String;\n    }\n  }\n  ```\n- Implement CDS websocket service: **srv/chat-service.js**\n  ```js\n  module.exports = (srv) =\u003e {\n    srv.on(\"message\", async (req) =\u003e {\n      await srv.emit(\"received\", req.data);\n      return req.data.text;\n    });\n  };\n  ```\n\n### Client\n\nIn browser environment implement the websocket client: **index.html**\n\n#### WebSocket Standard (`kind: ws`)\n\n- Connect with WebSocket\n  ```js\n  const protocol = window.location.protocol === \"https:\" ? \"wss://\" : \"ws://\";\n  const socket = new WebSocket(protocol + window.location.host + \"/ws/chat\");\n  ```\n- Emit event\n  ```js\n  socket.send(\n    JSON.stringify({\n      event: \"message\",\n      data: { text: input.value },\n    }),\n  );\n  ```\n- Listen to event\n  ```js\n  socket.addEventListener(\"message\", (message) =\u003e {\n    const payload = JSON.parse(message.data);\n    switch (payload.event) {\n      case \"received\":\n        console.log(payload.data.text);\n        break;\n    }\n  });\n  ```\n\n#### Socket.IO (`kind: socket.io`)\n\n- Connect with the Socket.IO client\n  ```js\n  const socket = io(\"/ws/chat\");\n  ```\n- Emit event\n  ```js\n  socket.emit(\"message\", { text: \"Hello World\" });\n  ```\n- Listen to event\n  ```js\n  socket.on(\"received\", (message) =\u003e {\n    console.log(message.text);\n  });\n  ```\n\n## Options\n\nThe CDS websocket modules can be configured with the following options:\n\n- **kind: String**: Websocket implementation kind (`ws`, `socket.io`). Default is `'ws'`.\n- **impl: String**: Websocket implementation path. Module provides default for kind.\n- **options: Object**: Websocket implementation configuration options. Default is `{}`.\n- **adapter: Object**: Websocket adapter configuration options. Default is `{}`.\n- **adapter.impl: String**: Websocket adapter implementation (`redis`, `@socket.io/redis-adapter`, `@socket.io/redis-streams-adapter`). Default is `''`.\n- **adapter.options: Object**: Websocket adapter implementation options. Default is `{}`.\n- **adapter.options.key: String**: Websocket adapter channel prefix. Default is `websocket`.\n- **adapter.config: Object**: Websocket adapter implementation configurations (i.e. Redis client options). Default is `{}`.\n- **adapter.active: Boolean**: Enable websocket adapter. Default is `true`.\n- **adapter.local: Boolean**: Enable websocket adapter in local environment. Default is `false`.\n\n\u003e All CDS Websocket options can also be specified as part of CDS project-specific configuration\n\u003e under section `cds.websocket` and accessed during runtime via `cds.env.websocket`.\n\n## Documentation\n\n### Architecture Overview\n\n![WebSocket Overview](./docs/assets/overview.svg)\n\nThe CDS Websocket module supports the following use-cases:\n\n- Connect multiple websocket clients (browser and non-browser) to CAP server websockets\n- Process websockets messages as CDS entity CRUD, action and function calls\n- Broadcast CDS events across local server websockets and multi-instance server websockets (via Redis)\n- Broadcast CDS events across multiple CAP server applications and application instances (via Redis)\n- Tenant-ware emits/broadcasts CDS events from server websockets to websocket clients (browser and non-browser)\n- Emit/Broadcast CDS events to a subset of websocket clients leveraging users, event contexts or client identifiers\n- Websocket events support different formats (JSON, PCP, CloudEvents or custom format)\n\n### Protocol Annotations\n\nThe CDS WebSocket module supports the following protocol definitions options in CDS:\n\n- `@ws`\n- `@websocket`\n- `@protocol: 'ws'`\n- `@protocol: 'websocket'`\n- `@protocol: [{ kind: 'ws', path: 'chat' }]`\n- `@protocol: [{ kind: 'websocket', path: 'chat' }]`\n\nIf a protocol path is not specified (e.g., via `@path`), it is determined from service name.\n\nIf the specified path is relative (i.e., does not start with a slash `/`), it is appended to the default protocol path e.g. `/ws`.\nIf the path is absolute (i.e., starts with a slash `/`), it is used as is.\n\nExamples:\n\n- `@path: 'chat`: Service is exposed at `/ws/chat`\n- `@path: '/chat`: Service is exposed at `/chat`\n\n### WebSocket Server\n\nThe CDS websocket server is exposed on `cds` object implementation-independent at `cds.ws` and implementation-specific\nat\n`cds.wss` for WebSocket Standard or `cds.io` for Socket.IO. Additional listeners can be registered bypassing CDS\ndefinitions and runtime.\nWebSocket server options can be provided via `cds.websocket.options`.\n\nDefault protocol path is `/ws` and can be overwritten via `cds.env.protocols.websocket.path` resp.\n`cds.env.protocols.ws.path`;\n\n### WebSocket Implementation\n\nThe CDS websocket server supports the following two websocket implementations:\n\n- [WebSocket Standard](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) (via\n  Node.js [ws](https://www.npmjs.com/package/ws) package): `cds.websocket.kind: \"ws\"` **(default)**\n- [Socket.IO](https://socket.io): `cds.websocket.kind: \"socket.io\"`\n- **Custom Server**: A custom websocket server implementation can be provided via a path relative to the project root\n  with the configuration `cds.websocket.impl` (e.g. `cds.websocket.impl: './server/xyz.js'`).\n\nThe server implementation abstracts from the concrete websocket implementation. The websocket client still needs to be\nimplemented websocket-implementation-specific.\n\n#### WebSocket Service\n\nAnnotated services with websocket protocol are exposed at endpoint: `/ws/\u003cservice-path\u003e`:\n\nWebsocket client connection happens as follows for exposed endpoints:\n\n- **WS**: `const socket = new WebSocket(\"ws://localhost:4004/ws/chat\");`\n- **Socket.IO**: `const socket = io(\"ws/chat\")`\n\n#### WebSocket Mixin\n\nNon-websocket services can contain events and operations that are exposed or accessible as websocket events\nvia the concept of mixin websocket services. Mixin event and operations need to be annotated with `@websocket` or `@ws`.\n\n**Hint:**\n\n\u003e Non-websocket service events and operations are only active when at least one websocket-enabled service is available\n\u003e (i.e., websocket protocol adapter is active).\n\n##### Event Mixin\n\nWebsocket services can contain events that are exposed as websocket events. Emitting an event on the service,\nbroadcasts the event to all websocket clients.\n\n```cds\n  @protocol: 'ws'\n  @path: 'chat'\n  service ChatService {\n    event received {\n      text: String;\n    }\n  }\n```\n\nIn addition, also non-websocket services can contain events that are exposed as websocket events:\n\n```cds\n  @protocol: 'odata'\n  @path: 'chat'\n  service ChatService {\n    entity Chat as projection on chat.Chat;\n    function message(text: String) returns String;\n    @websocket\n    event received {\n      text: String;\n    }\n  }\n```\n\nAlthough the service is exposed as an OData protocol at `/odata/v4/chat`, the service events annotated with `@websocket`\nor `@ws` are exposed as websocket events under the websocket protocol path as follows:\n\n`/ws/chat` (as derived from `/odata/v4/chat`).\n\nEntities are not exposed, as the service itself is not marked as websocket protocol.\n\nThe service path can be overruled on service or event level via `@websocket.path` or `@ws.path` annotation as follows:\n\n```cds\n@ws\n@ws.path: 'fns-websocket'\n@ws.format: 'pcp'\nevent notify {\n    text : String\n};\n```\n\nThe specified event path must match the service path of another websocket-enabled CDS service, otherwise the event is\nnot processed. In addition, the websocket format can be specified on service or event level via `@websocket.format` or `@ws.format`\nannotation for websocket events of non-websocket services.\n\n##### Operation Mixin\n\nWebsocket services can contain (unbound) operations (action, function) that are accessible via websocket events.\nCalling an operation via websocket event triggers the respective service operation.\n\n```cds\n  @protocol: 'ws'\n  @path: 'chat'\n  service ChatService {\n    action sendMessage(text: String);\n  }\n```\n\nIn addition, also non-websocket services can contain (unbound) operations that are accessible via websocket events:\n\n```cds\n  @protocol: 'odata'\n  @path: 'chat'\n  service ChatService {\n    entity Chat as projection on chat.Chat;\n\n    @ws\n    action sendMessage(text: String);\n  }\n```\n\nAlthough the service is exposed as an OData protocol at `/odata/v4/chat`, the service operations annotated with `@websocket`\nor `@ws` are accessible via websocket event under the websocket protocol path as follows:\n\n`/ws/chat` (as derived from `/odata/v4/chat`).\n\nEntities (including their bound operations) are not exposed, as the service itself is not marked as websocket protocol.\nThe service path can be overruled on service or operation level via `@websocket.path` or `@ws.path` annotation as follows:\n\n```cds\n@ws\n@ws.path: 'fns-websocket'\naction chat(text: String);\n```\n\nThe specified operation path must match the service path of another websocket-enabled CDS service, otherwise the operation is\nnot processed. In addition, the websocket format can be specified on service or operation level via `@websocket.format` or `@ws.format`\nannotation for websocket operations of non-websocket services.\n\n### Server Socket\n\nEach CDS handler request context is extended to hold the current server `socket` instance of the event.\nIt can be accessed via the service websocket facade via `req.context.ws.service` or `cds.context.ws.service`.\nIn addition, the native websocket server socket can be accessed via `req.context.ws.socket` or `cds.context.ws.socket`.\nEvents can be directly emitted via the native `socket`, bypassing CDS runtime, if necessary.\n\n### Upgrade Request\n\nThe upgrade request is automatically handled by CDS Websocket plugin. The original http(s) socket upgrade request is\naccessible in websocket initiated calls via `req.context.ws.socket.request.queryOptions` in the CDS websocket service handler context.\nQuery options therefore also can be accessed via `req.context.ws.socket.request.queryOptions`.\n\nUnknown paths that do not match any registered websocket service are rejected.\nFor kind `ws`, custom routes can be registered via `cds.ws.route` to handle upgrade requests for specific paths:\n\n```js\ncds.ws.route(\"/custom\", (request, socket, head) =\u003e {\n  cds.wss.handleUpgrade(request, socket, head, (ws) =\u003e {\n    ws.on(\"message\", (message) =\u003e {\n      ws.send(\"test\");\n    });\n  });\n});\n```\n\nFor a matching route, the upgrade request is left unhandled, so that other `upgrade`\nlisteners on the same HTTP server can pick up and process the request.\n\n### Service Facade\n\nThe service facade provides native access to websocket implementation independent of CDS context\nand is accessible on socket via `socket.facade` or in CDS context via `req.context.ws.service`.\nIt abstracts from the concrete websocket implementation by exposing the following public interface:\n\n- `service: Object`: Service definition\n- `path: String`: Service path\n- `socket: Object`: Server socket\n- `context: Object`: CDS context object for the websocket server socket\n- `on(event: String, callback: Function)`: Register websocket event\n- `async emit(event: String, data: Object, headers: Object)`: Emit websocket event with data and headers\n- `async broadcast(event: String, data: Object, headers: Object?, filter: { user: {include: String[], exclude: String[]}?, role: {include: String[], exclude: String[]}?, context: {include: String[], exclude: String[]}?, identifier: {include: String[], exclude: String[]}? }?)`:\n  Broadcast websocket event (except to sender) by optionally restrict to users, contexts or identifiers and optionally providing headers\n- `async broadcastAll(event: String, data: Object, headers: Object?, filter: { user: {include: String[], exclude: String[]}?, role: {include: String[], exclude: String[]}?, context: {include: String[], exclude: String[]}?, identifier: {include: String[], exclude: String[]}? }?)`:\n  Broadcast websocket event (including to sender) by optionally restrict to users, contexts or identifiers and optionally providing headers\n- `async enter(context: String)`: Enter a context\n- `async exit(context: String)`: Exit a context\n- `async disconnect()`: Disconnect server socket\n- `onDisconnect(callback: Function)`: Register callback function called on disconnect of server socket\n\n### Middlewares\n\nFor each server websocket connection the standard CDS middlewares are applied. That means that especially the correct\nCDS context is set up and the configured authorization strategy is applied.\n\n### Tenant Isolation\n\nWebSockets are processed tenant aware. Especially for broadcasting events, tenant isolation is ensured that only\nwebsocket clients connected to the same tenant are notified in the tenant context. Tenant isolation is also ensured\nover remote distribution via Redis.\n\n### Authentication \u0026 Authorization\n\nAuthentication works best via [AppRouter](https://www.npmjs.com/package/@sap/approuter) (e.g., using a UAA configuration),\nas the auth token is forwarded via authorization header bearer token by AppRouter to backend websocket call.\n\nCDS auth strategy (e.g. `cds.requires.auth.kind: 'xsuaa'`) is applied and CDS auth middleware processes the auth token and\nset the auth info accordingly. Authorization scopes are checked as defined in the CDS services `@requires` annotations\nand authorization restrictions are checked as defined in the CDS services `@restrict` annotations.\n\nAuthentication can also be performed without AppRouter as long as the WebSocket Upgrade request contains\na valid authorization header in accordance to the CDS auth strategy defined in CDS env:\n\nExample for xsuaa based CDS auth strategy:\n\n```json\n{\n  \"cds\": {\n    \"requires\": {\n      \"auth\": {\n        \"kind\": \"xsuaa\"\n      }\n    }\n  }\n}\n```\n\nHeaders of Websocket Upgrade request can be accessed via `req.http.req.headers` in websocket service handlers.\n\n#### Local Development\n\nFor local development, authentication is skipped for authentication kind `mocked` and `basic`.\nIf login is required (`cds.requires.auth.login_required: true`), first mocked user (see `cds.requires.auth.users`)\nis taken as authenticated user for websocket connection (e.g. `alice`),\notherwise the websocket connection is established with an anonymous user.\n\nUsing authentication kind `basic` or `cds.requires.auth.login_required: true` is not recommended for local development,\nas the websocket connection is established via WebSocket Upgrade request, which does not support interactive login.\nNevertheless, it could be simulated by hardcoding an authentication cookie in browser as follows:\n\n```js\ndocument.cookie = \"X-Authorization=Basic YWxpY2U6YWxpY2U; path=/\"; // mock auth for alice\n```\n\n### Invocation Context\n\nIn the context of a WebSocket enabled CDS service, WebSockets events can be directly emitted to the service in the event\nhandler:\n\n```js\nsrv.on(\"action\", async (req) =\u003e {\n  await srv.emit(\"message\", req.data);\n});\n```\n\nIn case, the context of invocation is not a WebSocket service, e.g., call is coming from OData or Rest request,\nstill the WebService events can be published by connecting to the WebSocket enabled service as follows:\n\n```js\nconst wsService = await cds.connect.to(\"WSService\");\nawait wsService.tx(req).emit(\"message\", req.data);\n```\n\n`cds.conntect.to` can be used to connect to any WebSocket enabled service, to emit events to the WebSocket service.\nUse `tx(req)` before `emit` in order to propagate the current context to the WebSocket enabled service,\nto especially ensure tenant and user propagation for broadcasting.\n\n### Transactional Safety\n\nIn most situations only websocket events shall be broadcast, in case the primary transaction succeeded.\nIt can be done manually, by emitting CDS event as part of the `req.on(\"succeeded\")` handler.\n\n```js\nreq.on(\"succeeded\", async () =\u003e {\n  await srv.emit(\"received\", req.data);\n});\n```\n\nAlternatively you can leverage the CAP in-memory queue via `cds.queued` as follows:\n\n```js\nconst chatService = cds.queued(await cds.connect.to(\"ChatService\"));\nawait chatService.tx(req).emit(\"received\", req.data);\n```\n\nThis has the benefit that the event emitting is coupled with the success of the primary transaction.\nStill, the asynchronous event processing could fail and would not be retried anymore.\nThat's where the CDS persistent queue comes into play.\n\n#### CDS Persistent Queue\n\nWebsocket events can also be sent via the CDS persistent queue. That means the CDS events triggering the websocket\nbroadcast\nare added to the CDS persistent queue when the primary transaction succeeded. The events are processed asynchronously\nand transactional safe in a separate transaction. It is ensured that the event is processed in any case, as queue\nkeeps the queue entry open until the event processing succeeds.\n\nThe transactional safety can be achieved using `cds.queued` with kind `persistent-queue` as follows:\n\n```js\nconst chatService = cds.queued(await cds.connect.to(\"ChatService\"), {\n  kind: \"persistent-queue\",\n});\nawait chatService.tx(req).emit(\"received\", req.data);\n```\n\nIn that case, the websocket event is broadcast to websocket clients exactly once, when the primary transaction succeeds.\nIn case of execution errors, the event broadcast is retried automatically, while processing the persistent queue.\n\nWebsocket services with `queued` or `outboxed` configuration in respective `cds.requires` section are automatically\nqueued/outboxed.\n\n### Client Determination\n\nThe client determination during WebSocket event broadcasting/emitting is based on the following filtering layers of the event:\n\n- **Tenant**: Only websocket clients connected to the same event tenant are notified.\n- **Service**: Only websocket clients connected to the same event service are notified.\n- **User**: Only websocket clients connected to the current or defined event users are notified.\n- **Role**: Only websocket clients connected to the users with specific roles are notified.\n- **Context**: Only websocket clients in the part of the defined event contexts are notified.\n- **Client Identifier**: Only websocket clients with the defined event client identifier are notified.\n\nTenant and service are determined automatically based on the CDS context and are applied per default to ensure tenant\nand service isolation. User, role, context and client identifiers are optional and are determined based on the event data or event emit headers.\nThey can be combined arbitrarily to filter the websocket clients to be notified.\n\nThe client filtering options are depicted in the following diagram:\n\n![Client Determination Overview](./docs/assets/client_determination.svg)\n\nThe diagram shows the mandatory filtering layer `tenant` and `service` and the optional filtering layers `user`, `role`\n`context` and `client identifier`. The `+` and `-` symbols on the optional filter layers indicating the possibility to\ninclude (`+`) or exclude (`-`) filtering conditions as described in the upcoming sections:\n\n- `+`: Include filtering condition. Inclusion is additive by default.\n- `-`: Exclude filtering condition. Exclusion is subtractive and has precedence over inclusion.\n- `or`: Include/Exclude filtering layers are combined via `or` operator by default.\n- `and`: Include/Exclude filtering layers can be combined via `and` operator.\n\n#### Filtering Operators\n\nThe default filtering operator is additive (`or` operator). This means, if multiple filtering layers are defined\nthe filtering conditions are combined via `or` operator. Filtering values within the same layer are always combined with`or` operator.\nInclude filters and exclude filters are always combined via `and not` operator.\n\n**Example:**\n\n```\n(userA or contextA) and !(roleA or contextB)\n```\n\nThe filtering operators can be changed to be restrictive (`and` operator) with regard to filtering layers via the following options:\n\n**Include Filters:**\n\n- Project config via env `cds.websocket.operator.include: 'and'`\n- Service level via annotation `@websocket.operator.include: 'and'` or `@ws.operator.include: 'and'`\n- Event level via annotation `@websocket.operator.include: 'and'` or `@ws.operator.include: 'and'`\n- Header level via emit header `operatorInclude: 'and'` or `includeOperator: 'and'`\n\n**Exclude Filters:**\n\n- Project config via env `cds.websocket.operator.exclude: 'and'`\n- Service level via annotation `@websocket.operator.exclude: 'and'` or `@ws.operator.exclude: 'and'`\n- Event level via annotation `@websocket.operator.exclude: 'and'` or `@ws.operator.exclude: 'and'`\n- Header level via emit header `operatorExclude: 'and'` or `excludeOperator: 'and'`\n\n**Example:**\n\n```\n(userA and contextA) and !(roleA and contextB)\n```\n\n#### Event Users\n\n##### Current User\n\nEvents are broadcast to all websocket clients, including clients established in the context of the current context user.\nTo influence event broadcasting based on current context user, the annotation `@websocket.user` or `@ws.user` is\navailable on event level and event element level (alternatives include `@websocket.broadcast.user` or `@ws.broadcast.user`):\n\nValid annotation values are:\n\n- **Event level**:\n  - `'includeCurrent'`: Current context user is statically included during broadcasting to websocket clients.\n    Only websocket clients established in the context of that user are respected during event broadcast.\n  - `'excludeCurrent'`: Current context user is statically excluded during broadcasting to websocket clients.\n    All websocket clients established in context to that user are not respected during event broadcast.\n- **Event element level**:\n  - `'includeCurrent'`: Current context user is dynamically included during broadcasting to websocket clients,\n    based on the value of the annotated event element. If truthy, only websocket clients established in the context of\n    that user are respected during event broadcast.\n  - `'excludeCurrent'`: Current context user is dynamically excluded during broadcasting to websocket clients,\n    based on the value of the annotated event element. If truthy, all websocket clients established in the context of that\n    user are not respected during event broadcast.\n\nFurthermore, also additional equivalent annotations alternatives are available:\n\n- Include current user:\n  - `@websocket.currentUser.include: Boolean` (shorthand: `@websocket.currentUser: Boolean`)\n  - `@ws.currentUser.include: Boolean` (shorthand: `@ws.currentUser: Boolean`)\n  - `@websocket.broadcast.currentUser.include: Boolean` (shorthand: `@websocket.broadcast.currentUser: Boolean`)\n  - `@ws.broadcast.currentUser.include: Boolean` (shorthand: `@ws.broadcast.currentUser: Boolean`)\n\n- Exclude current user:\n  - `@websocket.currentUser.exclude: Boolean`\n  - `@ws.currentUser.exclude: Boolean`\n  - `@websocket.broadcast.currentUser.exclude: Boolean`\n  - `@ws.broadcast.currentUser.exclude: Boolean`\n\n**Examples:**\n\n**Event Level:**\n\n```cds\n@websocket.user: 'includeCurrent'\nevent received {\n  name: String;\n  text: String;\n}\n```\n\nEvent is published only to websocket clients established in context to the current context user.\n\n**Event Element Level:**\n\n```cds\nevent received {\n  name: String;\n  text: String;\n  @websocket.currentUser.exclude\n  flag: Boolean\n}\n```\n\nEvent is published only to websocket clients not established in context to the current context user, if the event data of\n`flag` is truthy.\n\n#### Defined Users\n\nEvents are broadcast to all websocket clients. To influence event broadcasting based on defined users,\nthe following annotations to include or exclude defined users are available:\n\n- Include user(s):\n  - `@websocket.user.include` (shorthand: `@websocket.user`)\n  - `@ws.user.include` (shorthand: `@ws.user`)\n  - `@websocket.broadcast.user.include` (shorthand: `@websocket.broadcast.user`)\n  - `@ws.broadcast.user.include` (shorthand: `@ws.broadcast.user`)\n\n- Exclude user(s):\n  - `@websocket.user.exclude`\n  - `@ws.user.exclude`\n  - `@websocket.broadcast.user.exclude`\n  - `@ws.broadcast.user.exclude`\n\nValid annotation values are:\n\n- **Event level**:\n  - Type: `String[] | String`\n  - Provide static unique users to include or exclude from event broadcasting\n  - Value can be a single user or an array of users\n- **Event element level**:\n  - Type: `Boolean`\n  - Value from event data for the annotated element is used as users to include to or exclude from event broadcasting\n  - Value can be a single user or an array of users\n\n**Examples:**\n\n**Event Level:**\n\n```cds\n@websocket.user.exclude: 'ABC'\nevent received {\n  name: String;\n  text: String;\n}\n```\n\nEvent is published to all users except the user `ABC`.\n\n**Event Element Level:**\n\n```cds\nevent received {\n  name: String;\n  text: String;\n  @websocket.user.include\n  users: many String;\n}\n```\n\nEvent is only published to all users listed in the event data of `users`.\n\n#### Event Roles\n\nEvents are broadcast to all websocket clients. To influence event broadcasting based on user roles,\nthe roles to be considered for event broadcasting can be defined via service annotation `@requires`\nor in addition via CDS env configuration `cds.websocket.roles`:\n\n```json\n{\n  \"cds\": {\n    \"websocket\": {\n      \"roles\": [\"admin\"]\n    }\n  }\n}\n```\n\nThe following annotations to include or exclude users based on their roles are available:\n\n- Include user(s) with roles:\n  - `@websocket.role.include` (shorthand: `@websocket.role`)\n  - `@ws.role.include` (shorthand: `@ws.role`)\n  - `@websocket.broadcast.role.include` (shorthand: `@websocket.broadcast.role`)\n  - `@ws.broadcast.role.include` (shorthand: `@ws.broadcast.role`)\n\n- Exclude user(s) with roles:\n  - `@websocket.role.exclude`\n  - `@ws.role.exclude`\n  - `@websocket.broadcast.role.exclude`\n  - `@ws.broadcast.role.exclude`\n\nValid annotation values are:\n\n- **Event level**:\n  - Type: `String[] | String`\n  - Provide static unique user roles to include or exclude from event broadcasting\n  - Value can be a single user role or an array of user roles\n- **Event element level**:\n  - Type: `Boolean`\n  - Value from event data for the annotated element is used as user roles to include to or exclude from event broadcasting\n  - Value can be a single user role or an array of users roles\n\n**Examples:**\n\n**Event Level:**\n\n```cds\n@websocket.role.exclude: 'ABC'\nevent received {\n  name: String;\n  text: String;\n}\n```\n\nEvent is published to all users except the user having role `ABC`.\n\n**Event Element Level:**\n\n```cds\nevent received {\n  name: String;\n  text: String;\n  @websocket.role.include\n  roles: many String;\n}\n```\n\nEvent is only published to all user having roles listed in the event data of `roles`.\n\n#### Event Contexts\n\nIt is possible to broadcast events to a subset of clients. By entering or exiting contexts, the server can be instructed\nto determine to which subset of clients the event shall be emitted, based on the event. To specify which data parts of the\nevent are leveraged for setting up the context, the annotation `@websocket.context` or `@ws.context` is available on\nevent element level (alternatives include `@websocket.broadcast.context` or `@ws.broadcast.context`). For static\ncontexts\nthe annotation can also be used on the event level, providing a static event context string.\n\nTo influence event broadcasting based on event contexts, the following annotations to include or exclude contexts are\navailable:\n\n- Include context(s):\n  - `@websocket.context.include` (shorthand: `@websocket.context`)\n  - `@ws.context.include` (shorthand: `@ws.context`)\n  - `@websocket.broadcast.context.include` (shorthand: `@websocket.broadcast.context`)\n  - `@ws.broadcast.context.include` (shorthand: `@ws.broadcast.context`)\n\n- Exclude context(s):\n  - `@websocket.context.exclude: String[] | String`\n  - `@ws.context.exclude: String[] | String`\n  - `@websocket.broadcast.context.exclude: String[] | String`\n  - `@ws.broadcast.context.exclude: String[] | String`\n\nValid annotation values are:\n\n- **Event level**:\n  - Type: `String[] | String`\n  - Provide static contexts to include or exclude from event broadcasting\n  - Value can be a single context or an array of contexts\n- **Event element level**:\n  - Type: `Boolean`\n  - Value from event data for the annotated element is used as context to include to or exclude from event\n    broadcasting\n  - Value can be a single context or an array of contexts\n\n**Examples:**\n\n**Event Level:**\n\n```cds\n@websocket.context: 'ABC'\nevent received {\n  ID: UUID;\n  text: String;\n}\n```\n\nEvent is only published to all clients in context `ABC`.\n\n**Event Element Level:**\n\n```cds\nevent received {\n  @websocket.context\n  ID: UUID;\n  text: String;\n}\n```\n\nEvent is only published to all clients in context of the event data of `ID`.\n\nThe annotation can be used on multiple event elements setting up different event contexts in parallel\nif the event shall be broadcast/emitted into multiple contexts at the same time.\n\n```cds\nevent received {\n  @websocket.context\n  ID: UUID;\n  @websocket.context\n  name: String;\n  text: String;\n}\n```\n\nEvent contexts can also be established via event elements of `many` or `array of` type:\n\n```cds\nevent received {\n  @websocket.context\n  ID: many UUID;\n  text: String;\n}\n```\n\nThis allows setting up an unspecified number of different event contexts in parallel during runtime.\n\nEvent contexts support all CDS/JS types. The serialization is performed as follows:\n\n- `Date`: `context.toISOString()`\n- `Object`: `JSON.stringify(context)`\n- other: `String(context)`\n\nTo manage event contexts, the following options exist:\n\n- **Server side**: Call websocket service facade\n  - CDS context object `req` exposes the websocket facade via `req.context.ws.service` providing the following context\n    functions\n    - **Enter Context**: `enter(context: String)` - Enter the current server socket into the passed context\n    - **Exit Context**: `exit(context: String)` - Exit the current server socket from the passed context\n    - **Reset Contexts**: `reset()` - Resets all contexts\n- **Client side**: Emit `wsContext` event from client socket.\n  - **Enter Context**:\n    - WS Standard:\n      ```js\n      socket.send(JSON.stringify({ event: \"wsContext\", data: { context: \"...\" } }));\n      ```\n    - Socket.IO:\n      ```js\n      socket.emit(\"wsContext\", { context: \"...\" });\n      ```\n  - **Exit**:\n    - WS Standard:\n      ```js\n      socket.send(JSON.stringify({ event: \"wsContext\", data: { context: \"...\", exit: true } }));\n      ```\n    - Socket.IO:\n      ```js\n      socket.emit(\"wsContext\", { context: \"...\", exit: true });\n      ```\n  - **Reset**:\n    - WS Standard:\n      ```js\n      socket.send(JSON.stringify({ event: \"wsContext\", data: { reset: true } }));\n      ```\n    - Socket.IO:\n      ```js\n      socket.emit(\"wsContext\", { reset: true });\n      ```\n\nSingle context can be entered/exited via `context: String` parameter. Multiple contexts can be entered/excited\nfor the same server socket at the same time via `contexts: String[]` parameter.\nParameter `reset: Boolean` can be used to reset all entered contexts for a server socket.\nReset and enter context can be used within a single `wsContext` call.\nFirst, all contexts are reset and then the new contexts are entered.\n\nFurthermore, a service operation named `wsContext` is invoked, if existing on the websocket enabled CDS service:\n\n```cds\naction wsContext(context: String, contexts: array of String, exit: Boolean, reset: Boolean);\n```\n\nEvent context isolation is also ensured over remote distribution via Redis.\n\nFor Socket.IO (`kind: socket.io`) contexts are implemented leveraging [Socket.IO rooms](https://socket.io/docs/v4/rooms/).\n\n#### Event Client Identifiers\n\nEvents are broadcast to all websocket clients, including clients that performed certain actions. When events are sent\nas part of the websocket context, access to the current socket is given, but if actions are performed outside the websocket context,\nthere are no means to identify the client that performed the action.\n\nThat's where the event client identifier comes into play. Client identifier is unique consumer-provided strings that\nare provided during the websocket connection to identify the websocket client as well as in other request cases (e.g.,\nOData call).\nWhen an OData call with a client identifier is performed, it can be used to restrict the websocket event broadcasting.\n\nIn some cases, the websocket clients shall be restricted on an instance basis. There are use-cases that only certain\nclients are informed about an event and also in other cases the client shall not be informed about the event that was\ntriggered by the same client (maybe via a different channel, e.g., OData).\nTherefore, websocket clients can be identified optionally by a unique identifier provided as URL parameter option\n`?id=\u003cglobally unique value\u003e`.\n\nThe annotation `@websocket.identifier.include` or `@ws.identifier.include` is available on event level and event element\nlevel to influence event-broadcasting-based websocket client identifier to include certain clients based on their\nidentifier (not listed clients are no longer respected when set)\n(alternatives include `@websocket.broadcast.identifier.include` or `@ws.broadcast.identifier.include`):\n\nThe annotation `@websocket.identifier.exclude` or `@ws.identifier.exclude` is available on event level and event element\nlevel to influence event-broadcasting-based websocket client identifier to exclude certain clients based on their\nidentifier (alternatives include `@websocket.broadcast.identifier.exclude` or `@ws.broadcast.identifier.exclude`):\n\nThe full list of annotations is:\n\n- Include client identifier(s):\n  - `@websocket.identifier.include` (shorthand: `@websocket.identifier`)\n  - `@ws.identifier.include` (shorthand: `@ws.identifier`)\n  - `@websocket.broadcast.identifier.include` (shorthand: `@websocket.broadcast.identifier`)\n  - `@ws.broadcast.identifier.include` (shorthand: `@ws.broadcast.identifier`)\n\n- Exclude client identifiers(s):\n  - `@websocket.identifier.exclude: String[] | String`\n  - `@ws.identifier.exclude: String[] | String`\n  - `@websocket.broadcast.identifier.exclude: String[] | String`\n  - `@ws.broadcast.identifier.exclude: String[] | String`\n\nValid annotation values are:\n\n- **Event level**:\n  - Type: `String[] | String`\n  - Provide static unique identifiers to include or exclude clients from event broadcasting\n  - Value can be a single identifier string or an array of identifier strings\n- **Event element level**:\n  - Type: `Boolean`\n  - Value from event data for the annotated element is used as unique identifiers to include or exclude websocket\n    clients from event broadcasting\n  - Value can be a single identifier string or an array of identifier strings\n\n**Examples:**\n\n**Event Level:**\n\n```cds\n@websocket.identifier.include: 'ABC'\nevent received {\n  ID: UUID;\n  text: String;\n}\n```\n\nEvent is only published to all clients with identifier `ABC`.\n\n**Event Element Level:**\n\n```cds\nevent received {\n  ID: UUID;\n  @websocket.identifier.include\n  ids: many String;\n}\n```\n\nEvent is only published to all clients with identifiers listed in the event data of `ids`.\n\n##### Identifier Setup\n\n###### Client Side\n\nThe unique identifier can be provided for a websocket client as follows:\n\n- WS Standard:\n  ```js\n  socket = new WebSocket(\"ws://localhost:4004/ws/chat?id=1234\");\n  ```\n- Socket.IO:\n  ```js\n  const socket = io(\"/ws/chat?id=1234\");\n  ```\n\n###### Server Side\n\nThe unique identifier can be provided for a websocket client server connection on server side via `req.id = '1234'`\nfor the WebSocket Upgrade request (`req`). Custom CDS middleware can be registered via `cds.middlewares.add`\nto set the request unique client identifier as part of the WebSocket Upgrade request.\n\nBe aware that CDS middlewares are running for any CDS request. Restriction to WebSocket upgrade requests only\ncan be done as follows:\n\n```js\nif (req.upgrade) {\n  req.id = \"1234\";\n}\n```\n\n#### Event Emit Headers\n\nThe websocket implementation allows providing event emit headers to dynamically control websocket processing.\nThe following headers are available:\n\n- Include the current user in the event publication (see section Event Users → Current User):\n  - `wsCurrentUser: Boolean` (shorthand)\n  - `wsCurrentUser.include: Boolean`\n  - `wsCurrentUserInclude: Boolean`\n  - `currentUser: Boolean` (shorthand)\n  - `currentUser.include: Boolean`\n  - `currentUserInclude: Boolean`\n- Exclude current user from event publication (see section Event Users -\u003e Current User)\n  - `wsCurrentUser.exclude: Boolean`\n  - `wsCurrentUserExclude: Boolean`\n  - `currentUser.exclude: Boolean`\n  - `currentUserExclude: Boolean`\n- Include one or many users in an event publication (see section Event Users → Defined Users):\n  - `wsUsers: String[] | String` (shorthand)\n  - `wsUser: String[] | String` (shorthand)\n  - `wsUser.include: String[] | String`\n  - `wsUserInclude: String[] | String`\n  - `users: String[] | String` (shorthand)\n  - `user: String[] | String` (shorthand)\n  - `user.include: String[] | String`\n  - `userInclude: String[] | String`\n- Exclude one or many users from the event publication (see section Event Users → Defined Users)\n  - `wsUser.exclude: String[] | String`\n  - `wsUserExclude: String[] | String`\n  - `user.exclude: String[] | String`\n  - `userExclude: String[] | String`\n- Include one or many user roles in an event publication (see section Event Users → Defined Roles):\n  - `wsRoles: String[] | String` (shorthand)\n  - `wsRole: String[] | String` (shorthand)\n  - `wsRole.include: String[] | String`\n  - `wsRoleInclude: String[] | String`\n  - `roles: String[] | String` (shorthand)\n  - `role: String[] | String` (shorthand)\n  - `role.include: String[] | String`\n  - `roleInclude: String[] | String`\n- Exclude one or many user roles from the event publication (see section Event Users → Defined Roles)\n  - `wsRole.exclude: String[] | String`\n  - `wsRoleExclude: String[] | String`\n  - `role.exclude: String[] | String`\n  - `roleExclude: String[] | String`\n- Include one or many contexts in an event publication (see section Event Contexts)\n  - `wsContext: String[] | String` (shorthand)\n  - `wsContexts: String[] | String` (shorthand)\n  - `wsContext.include: String[] | String`\n  - `wsContextInclude: String[] | String`\n  - `context: String[] | String` (shorthand)\n  - `contexts: String[] | String` (shorthand)\n  - `context.include: String[] | String`\n  - `contextInclude: String[] | String`\n- Exclude one or many contexts from the event publication (see section Event Contexts)\n  - `wsContext.exclude: String[] | String`\n  - `wsContextExclude: String[] | String`\n  - `context.exclude: String[] | String`\n  - `contextExclude: String[] | String`\n- Include one or many client identifiers in an event publication (see section Event Client Identifier)\n  - `wsIdentifier: String[] | String` (shorthand)\n  - `wsIdentifiers: String[] | String` (shorthand)\n  - `wsIdentifier.include: String[] | String`\n  - `wsIdentifierInclude: String[] | String`\n  - `identifier: String[] | String` (shorthand)\n  - `identifiers: String[] | String` (shorthand)\n  - `identifier.include: String[] | String`\n  - `identifierInclude: String[] | String`\n- Exclude one or many client identifiers from event publication (see section Event Client Identifier)\n  - `wsIdentifier.exclude: String[] | String`\n  - `wsIdentifierExclude: String[] | String`\n  - `identifier.exclude: String[] | String`\n  - `identifierExclude: String[] | String`\n\nEmitting events with headers can be performed as follows:\n\n```js\nawait srv.emit(\"customEvent\", { ... }, {\n  contexts: [\"...\"],\n  currentUser: {\n    exclude: req.data.type === \"1\"\n  },\n  user: {\n    include: \"...\",\n    exclude: [\"...\"],\n  },\n  roles: [\"...\"],\n  identifier: {\n    include: [\"...\"],\n    exclude: \"...\",\n  },\n});\n```\n\n##### Event HTTP Headers\n\nIn addition to the above event emit headers, HTTP conform headers can be specified starting with `x-websocket-` or `x-ws-`  \nprefix. The lower case header names are converted to camel-cased header names removing prefix, e.g. `x-ws-current-user` becomes `currentUser`.\nHeader string values are parsed according to their value to types `Boolean`, `Number` or `String`.\n\nFormat-specific HTTP conform headers can be defined in formatter named subsection, `x-websocket-\u003cformat\u003e-` or `x-ws-\u003cformat\u003e-`.\n\n**Examples** (for format `cloudevent`):\n\n- `x-ws-cloudevent-subject: 'xyz'` becomes nested JSON header object `{ \"cloudevent\": { subject: \"xyz\" } }` in `cloudvent` formatter.\n- `x-ws-cloudevent-value: '1'` becomes nested JSON header object `{ \"cloudevent\": { value: 1 } }` in `cloudvent` formatter.\n\n#### Value Aggregation\n\nThe respective event annotations (described in the sections above) are respected in addition to event emit header\nspecification. All event annotation values (static or dynamic) and header values are aggregated during event\nemitted according to their kind. Values of all headers and annotations of the same semantic type are unified for\nsingle and array values.\n\n#### Format Headers\n\nIn addition to the above event emit headers, format-specific event headers can be specified in the `websocket` or `ws` section\nduring event emitted.\n\n```js\nawait srv.emit(\"customEvent\", { ... }, {\n  ws: {\n    a: 1,\n    cloudevent: {\n      e: true\n    }\n  },\n  websocket: {\n    b: \"c\"\n  }\n});\n```\n\nThese headers are made available to the format `compose(event, data, headers)` function, to be included in the\ncomposed WebSocket message, if applicable (e.g., format: `pcp`, `cloudevent`). Format-specific headers can also be defined\nin formatter named subsection, e.g. `ws.cloudevent.e: true` (for format `cloudevent`), to avoid conflicts.\n\n#### Ignore Definitions\n\nTo ignore elements and parameters during event processing, the annotation `@websocket.ignore` or `@ws.ignore` is available\non event element and operation parameter level. The annotation can be used to exclude elements and parameters from the WebSocket event.\n\n### WebSocket Format\n\nPer default the CDS websocket format is `json`, as CDS internally works with JSON objects.\n\nWS Standard and Socket.IO support JSON format as follows:\n\n- **WS Standard**: Message is serialized to a JSON object with format `{ event, data }`\n- **Socket.IO**: Events and JSON objects are intrinsically supported. No additional serialization is necessary.\n\n#### Push Channel Protocol (PCP)\n\nCDS WebSocket module supports the Push Channel Protocol (PCP) out-of-the-box.\n\nA PCP message has the following structure:\n\n```text\npcp-action:MESSAGE\npcp-body-type:text\nfield1:value1\nfield2:value2\n\nthis is the body!\n```\n\nTo configure the PCP format, the service needs to be annotated in addition with `@websocket.format: 'pcp'` or\n`@ws.format: 'pcp'`:\n\n```cds\n@ws\n@ws.format: 'pcp'\nservice PCPService {\n  // ...\n}\n```\n\nWith this configuration WebSocket events consume or produce PCP formatted messages.\nTo configure the PCP message format, the following annotations are available:\n\n- **Operation level**:\n  - `@websocket.pcp.action, @ws.pcp.action: String`: Correlate `pcp-action` in a PCP message to identify the CDS\n    operation via annotation. If not defined, the operation name is correlated.\n- **Operation parameter level**:\n  - `@websocket.pcp.message, @ws.pcp.message: Boolean`: Correlate the PCP message body to the operation parameter\n    representing the message.\n- **Event level**:\n  - `@websocket.pcp.event, @ws.pcp.event: Boolean | String`: Expose the CDS event (Boolean) or the passed value (String) as `pcp-event` field in the PCP message.\n  - `@websocket.pcp.message, @ws.pcp.message: String`: Expose a static message text as PCP message body.\n  - `@websocket.pcp.action, @ws.pcp.action: String`: Exposes a static action as `pcp-action` field in the PCP message.\n    Default `MESSAGE`.\n- **Event element level**:\n  - `@websocket.pcp.message, @ws.pcp.message: Boolean`: Expose the string value of the annotated event element as PCP\n    message body.\n  - `@websocket.pcp.action, @ws.pcp.action: Boolean`: Expose the string value of the annotated event element as\n    `pcp-action` field in the PCP message. Default `MESSAGE`.\n\n##### Manage Contexts\n\nTo manage contexts in format `pcp`, `wsContext` event can be emitted in the following way:\n\n- Model `wsContext` CDS service operation as follows:\n  ```cds\n  @ws.pcp.action: 'wsContext'\n  action wsContext(context: String, exit: Boolean, reset: Boolean);\n  ```\n- Call `wsContext` message in `pcp` format like this:\n\n  ```\n  pcp-action:wsContext\n  pcp-body-type:text\n  context:context\n  exit:false\n  reset:true\n\n  wsContext\n  ```\n\nThe PCP action needs to match an operation with annotation `@websocket.pcp.action` or `@ws.pcp.action`.\nModeled action parameters `context`, `exit` and `reset` are mapped from `pcp` message fields.\n\n#### Event-Driven Side Effects\n\nPCP format can be used to emit Fiori Elements Event-Driven Side Effects via WebSocket events.\nWebsocket kind `ws` must be configured (default), as UI5 only supports WebSocket protocol for Fiori Elements side effects.\n\nFirst OData V4 service metadata is extended for side effects to automatically connect to the corresponding websocket endpoint and channel:\n\n```cds\n@ws\n@odata\n@Common : {\n  WebSocketBaseURL : 'ws/fiori',\n  WebSocketChannel #sideEffects: 'sideeffects'\n}\nservice FioriService {\n   ...\n}\n```\n\nPath of `WebSocketBaseURL` shall be defined relatively (i.e. no leading slash) to the OData service URL,\nto be correctly resolved in Fiori Elements, especially in context of WorkZone.\n\nEvent-driven side effects are configured in PCP format enabled service via the following annotations:\n\n- **Event level**:\n  - `@websocket.pcp.sideEffect, @ws.pcp.sideEffect: Boolean`: Expose event as Fiori Side Effect in the PCP message.\n  - `@websocket.pcp.channel, @ws.pcp.channel: String`: Specify the PCP side effects channel in the PCP message. In addition, the common annotation `@Common.WebSocketChannel` is respected (on event and service level).\n\nExample:\n\n```cds\n@ws.pcp.sideEffect\n@ws.pcp.channel: 'sideeffects'\nevent sideEffect {\n    sideEffectSource: String;\n}\n```\n\nPCP Channel can be omitted, if the common annotation `@Common.WebSocketChannel` is defined in the same service on service level.\nFurthermore, using websocket mixins a websocket event can be defined in the corresponding OData service as follows:\n\nExample:\n\n```cds\n@Common: {\n    WebSocketBaseURL: 'ws/fiori',\n    WebSocketChannel #sideEffects: 'sideeffects',\n}\nservice FioriService {\n  @ws: { format: 'pcp', pcp: { sideEffect } }\n  event sideEffect {\n    sideEffectSource: String;\n  }\n}\n```\n\nTo consume side effects in Fiori Elements, an CDS entity can be annotated with side effects specifying `SourceEvents` as follows:\n\nExample:\n\n```cds\n@Common.SideEffects #nameUpdated: {\n   SourceEvents    : ['sideEffect'],\n   TargetProperties: ['name']\n}\nentity Header { ... }\n```\n\nThe event `sideEffect` is emitted on server side via CDS `emit`, as follows:\n\n```js\nawait srv.emit(\"sideEffect\", {\n  sideEffectSource: \"/Header(ID='e0582b6a-6d93-46d9-bd28-98723a285d40')\",\n});\n```\n\nThis results in the following PCP message sent via websocket protocol to Fiori Elements:\n\n```\npcp-action:MESSAGE\npcp-channel:sideeffects\nsideEffectSource:/Header(ID='e0582b6a-6d93-46d9-bd28-98723a285d40')\nsideEffectEventName:sideEffect\nserverAction:RaiseSideEffect\n\n\n```\n\nDetails can be found for Fiori Elements [Event-Driven Side Effects](https://ui5.sap.com/#/topic/27c9c3bad6eb4d99bc18a661fdb5e246).\nFiori Elements V4 applications listening on service and channel will process the side effects, and update the UI accordingly.\n\n**Hint:**\n\n\u003e When using mixin events for OData services, please note that at least one websocket-enabled service is available\n\u003e (i.e., websocket protocol adapter is active). In addition, a service can also be service via multiple protocols using the `@protocols: ['odata', 'ws']`.\n\n#### Cloud Events\n\nCDS WebSocket module supports the [Cloud Events](https://cloudevents.io) specification out-of-the-box, according to\n[WebSockets Protocol Binding for CloudEvents](https://github.com/cloudevents/spec/blob/main/cloudevents/bindings/websockets-protocol-binding.md).\n\nA Cloud Event message has the following structure:\n\n```json\n{\n  \"specversion\": \"1.0\",\n  \"type\": \"com.example.someevent\",\n  \"source\": \"/mycontext\",\n  \"subject\": null,\n  \"id\": \"C234-1234-1234\",\n  \"time\": \"2018-04-05T17:31:00Z\",\n  \"comexampleextension1\": \"value\",\n  \"comexampleothervalue\": 5,\n  \"datacontenttype\": \"application/json\",\n  \"data\": {\n    \"appinfoA\": \"abc\",\n    \"appinfoB\": 123,\n    \"appinfoC\": true\n  }\n}\n```\n\nTo configure the CloudEvents format, the service needs to be annotated in addition with `@websocket.format: 'cloudevent'` or\n`@ws.format: 'cloudevent'`.\n\n```cds\n@ws\n@ws.format: 'cloudevent'\nservice CloudEventService {\n  // ...\n}\n```\n\nTo create a Cloud Event compatible CDS event, either the event is modeled as a CDS service event according to the specification\nor a CDS event is mapped via annotations to a Cloud Event compatible event.\n\n##### Modeling Cloud Event\n\nCloud event can be explicitly modeled as a CDS event, matching the specification of cloud event attributes.\n\n**Example:**\n\n```cds\nevent cloudEvent {\n    specversion : String;\n    type : String;\n    source : String;\n    subject : String;\n    id : String;\n    time : String;\n    comexampleextension1 : String;\n    comexampleothervalue : String;\n    datacontenttype : String;\n    data: {\n        appinfoA : String;\n        appinfoB : Integer;\n        appinfoC : Boolean;\n    }\n}\n```\n\nThe CDS event `cloudEvent` is explicitly modeled according to the Cloud Event specification.\nThe event data is passed inbound and outbound in the exact same representation as the JSON object, as specified.\nNo additional annotations are necessary to be defined.\n\n##### Mapping Cloud Event\n\nCDS events can also be mapped to Cloud Event compatible events via headers and CDS annotations. The implementation is based on the\n`generic` formatter (see a section below), that allows to map CDS events to Cloud Event compatible events based on\nclouded event-specific headers and wildcard annotations, starting with `@websocket.cloudevent.\u003cannotation\u003e` or `@ws.cloudevent.\u003cannotation\u003e`\nto match the Cloud Event specific attributes.\n\nThe provided header values in the `websocket` or `ws` section are mapped to the cloud event attributes generically, if available.\n\n**Example:**\n\n```js\nawait srv.emit(\n  \"cloudEvent\",\n  {\n    appinfoA,\n    appinfoB,\n    appinfoC,\n  },\n  {\n    ws: {\n      specversion: \"1.0\",\n      type: \"com.example.someevent.cloudEvent4\",\n      source: \"/mycontext\",\n      subject: req.data._subject || \"example\",\n      id: \"C234-1234-1234\",\n      time: \"2018-04-05T17:31:00Z\",\n      comexampleextension1: \"value\",\n      comexampleothervalue: 5,\n      datacontenttype: \"application/json\",\n    },\n  },\n);\n```\n\nSubsequently, the following annotations are respected:\n\n- **Event level**:\n  - `@websocket.cloudevent.\u003cattribute\u003e: \u003cvalue\u003e`\n  - Type: `any` (according to Cloud Event JSON format)\n  - Provide static cloud event attribute value, according to cloud event specification\n- **Event element level**:\n  - `@websocket.cloudevent.\u003cattribute\u003e`\n  - Type: `Boolean`\n  - Value from event data for the annotated element is used as a dynamic cloud event attribute value, according to cloud event attribute specification\n\n**Examples:**\n\n**Event Level:**\n\n```cds\n@ws.cloudevent.specversion         : '1.0'\n@ws.cloudevent.type                : 'com.example.someevent'\n@ws.cloudevent.source              : '/mycontext'\n@ws.cloudevent.subject             : 'example'\n@ws.cloudevent.id                  : 'C234-1234-1234'\n@ws.cloudevent.time                : '2018-04-05T17:31:00Z'\n@ws.cloudevent.comexampleextension1: 'value'\n@ws.cloudevent.comexampleothervalue: 5\n@ws.cloudevent.datacontenttype     : 'application/json'\nevent cloudEvent2 {\n    appinfoA : String;\n    appinfoB : Integer;\n    appinfoC : Boolean;\n}\n```\n\nEvent is published via cloud event sub-protocol, with the specified static cloud event attributes.\nThe CDS event data is consumed as a cloud event data section.\n\n**Event Element Level:**\n\n```cds\nevent cloudEvent3 {\n    @ws.cloudevent.specversion\n    specversion     : String\n    @ws.cloudevent.type\n    type            : String\n    @ws.cloudevent.source\n    source          : String\n    @ws.cloudevent.subject\n    subject         : String\n    @ws.cloudevent.id\n    id              : String\n    @ws.cloudevent.time\n    time            : String\n    @ws.cloudevent.comexampleextension1\n    extension1      : String\n    @ws.cloudevent.comexampleothervalue\n    othervalue      : String\n    @ws.cloudevent.datacontenttype\n    datacontenttype : String;\n    appinfoA        : String;\n    appinfoB        : Integer;\n    appinfoC        : Boolean;\n}\n```\n\nEvent is published via cloud event sub-protocol, with the specified dynamic cloud event attributes derived from\nCDS event elements. Annotated elements are consumed as cloud event attributes, non-annotated elements are consumed as\na cloud event data section.\n\nStatic and dynamic annotations can be combined. Static values have precedence over dynamic values, if defined.\n\n##### Cloud Event Operation\n\nCDS service operations (actions or functions) can also be exposed via cloud event. The operation name is derived from the `@websocket.cloudevent.name` or\n`@ws.cloudevent.name` annotation. Emitting a cloud-event-based websocket event that matches the annotation value of `name`, calls the\nrespective service operation handler.\n\nThe operation parameter structure can be either modeled according to the Cloud Event specification using the attributes as parameter names or\nmapped via annotations like `@websocket.cloudevent.\u003cannotation\u003e` or `@ws.cloudevent.\u003cannotation\u003e` to a Cloud Event compatible structure.\n\nThe following annotations are respected:\n\n- **Operation level**:\n  - `@websocket.cloudevent.\u003cattribute\u003e: \u003cvalue\u003e`\n  - Type: `any` (according to Cloud Event JSON format)\n  - Provide static cloud event attribute value, according to cloud event specification\n- **Operation parameter level**:\n  - `@websocket.cloudevent.\u003cattribute\u003e`\n  - Type: `Boolean`\n  - Value from the operation parameter for the annotated element is used as a dynamic cloud event attribute value, according to cloud event attribute specification\n\n**Examples:**\n\n**Model Operation Parameters:**\n\n```cds\ntype CloudEventDataType : {\n    appinfoA : String;\n    appinfoB : Integer;\n    appinfoC : Boolean;\n};\n\n@ws.cloudevent.name: 'com.example.someevent'\naction sendCloudEventModel( subject : String, comexampleextension1 : String, comexampleothervalue : Integer, data: CloudEventDataType) returns Boolean;\n```\n\n**Map Operation Parameters:**\n\n```cds\n@ws.cloudevent.name: 'com.example.someevent'\n@ws.cloudevent.subject: 'cloud'\naction sendCloudEventMap(\n  @ws.cloudevent.subject subject : String,\n  @ws.cloudevent.comexampleextension1 extension1 : String,\n  @ws.cloudevent.comexampleothervalue othervalue : Integer,\n  appinfoA : String,\n  appinfoB : Integer,\n  appinfoC : Boolean\n  @ws.ignore appinfoD : String\n) returns Boolean;\n```\n\nUnmapped operation parameters are consumed as a cloud event data section and can be skipped for a cloud event data section\nvia `@ws.ignore`, if not necessary.\n\n##### Manage Contexts\n\nTo manage contexts in format `cloudevent`, `wsContext` event can be emitted in the following way:\n\n- Model `wsContext` CDS service operation as follows:\n  ```\n  @ws.cloudevent.name: 'event.ws.context'\n  action wsContext(context: String, exit: Boolean, reset: Boolean);\n  ```\n- Call `wsContext` message in `cloudevents` format like this:\n  ```\n  {\n    specversion: \"1.0\",\n    type: \"event.ws.context\",\n    source: \"CloudEventService\",\n    data: {\n      context: \"context\",\n      exit: false,\n      reset: true,\n    }\n  }\n  ```\n\nThe cloud event type needs to match an operation with annotation `@websocket.cloudevent.name` or `@ws.cloudevent.name`.\nModeled action parameters `context`, `exit` and `reset` are mapped from `cloudevent` message data section.\n\n##### Cloud Event Format Alternative\n\nAlternatives for format `cloudevent` also allows using the plural name `@websocket.format: 'cloudevents'` or `@ws.format: 'cloudevents'`,\nif preferred. All headers and annotations are also named in plural form accordingly, e.g. `@ws.cloudevents.name`, etc.\n\n#### Custom Format\n\nA custom websocket format implementation can be provided via a path relative to the project root\nin `@websocket.format` resp. `@ws.format` annotation (e.g. `@ws.format: './format/xyz.js'`).\n\nThe custom format class needs to implement the following functions:\n\n- **parse(data)**: Parse the event data into internal data (JSON), i.e. `{ event, data, headers }`\n- **compose(event, data, headers)**: Compose the internal event data (JSON) and event headers into a formatted string.\n  For kind `socket.io`, it can also be a JSON object.\n\nIn addition, it can implement the following functions (optional):\n\n- **constructor(service, origin)**: Setup instance with service definition and origin format on creation\n\n#### Generic Format\n\nAdditionally, a custom formatter can be based on the generic implementation `src/format/generic.js` providing a name and identifier.\nValues are derived via CDS annotations based on wildcard annotations\n`@websocket.\u003cformat\u003e.\u003cannotation\u003e` or `@ws.\u003cformat\u003e.\u003cannotation\u003e` using the formatter name.\n\nIn addition, provided header values in the `websocket` or `ws` section are also used to derive values from.\nFormat-specific headers can also be defined in formatter named subsection, e.g. `ws.cloudevent.e: true` (for format `cloudevent`),\nto avoid conflicts.\n\nThe following generic implementation specifics are included:\n\n- **parse:** Data is parsed generically\n  - Parsing is based on formatter-specific wildcard annotations on operation level (static) or operation parameter level (dynamic), if available.\n  - CDS operation (action or function) is derived from generic annotation `@websocket.\u003cformat\u003e.name` or `@ws.\u003cformat\u003e.name`.\n  - Operation identification is based on the formatter identifier (default `name`) on event data, that can be specified per formatter.\n  - Data is passed further as-is, in case no CDS annotations are present for format\n- **compose:** Data is composed generically\n  - The first data is composed based on headers, if available (see section Format Headers)\n  - Subsequently, formatter-specific wildcard annotations on event level (static) or event element level (dynamic) are processed\n\nThe generic formatter can also directly be used via annotations `@websocket.format: 'generic'` or `@ws.format: 'generic'`.\nValues are derived from data via CDS annotations based on wildcard annotations `@websocket.generic.\u003cannotation\u003e` or `@ws.generic.\u003cannotation\u003e`\nand headers from subsections `websocket.generic.\u003cheader\u003e` or `ws.generic.\u003cheader\u003e`.\n\n### Connect \u0026 Disconnect\n\nEvery time a server socket is connected via a websocket client, the CDS service is notified by calling the corresponding\nservice operation:\n\n- `Connect`: Invoke service operation `wsConnect()`, if available\n- `Disconnect`: Invoke service operation `wsDisconnect()`, if available\n  - `wsDisconnect()`: No parameters are passed\n  - `wsDisconnect(reason: String)`: Disconnect reason is passed as parameter\n\n### Server Client Service\n\nA server-side websocket client can be accessed via CDS service architecture. To connect to a websocket exposed\nCDS service as a client, a service can be defined as follows in CDS environment (`cds.env`) specifying `kind` property.\n\n#### WebSocket Standard (`kind: websocket-client-ws`)\n\n```json\n{\n  \"cds\": {\n    \"requires\": {\n      \"chat-ws\": {\n        \"kind\": \"websocket-client-ws\",\n        \"format\": \"json\",\n        \"url\": \"ws://localhost:4004/ws/chat\",\n        \"headers\": {\n          \"Authorization\": \"Basic ...\"\n        }\n      }\n    }\n  }\n}\n```\n\nDuring runtime, access to the websocket service client can be established, events emitted and received as follows:\n\n```js\nconst client = await cds.connect.to(\"chat-ws\");\nclient.on(\"received\", (message) =\u003e {});\nclient.emit(\"message\", { text: \"test\" });\n```\n\nCustom service runtime options can be provided via:\n\n```js\nconst client = await cds.connect.to(\"chat-ws\", {\n  url: `ws://localhost:4005/ws/chat`,\n  headers: {\n    authorization: \"...\",\n  },\n});\n```\n\n#### Socket.IO (`kind: websocket-client-socket.io`)\n\n```json\n{\n  \"cds\": {\n    \"requires\": {\n      \"chat-socket.io\": {\n        \"kind\": \"websocket-client-socket.io\",\n        \"format\": \"json\",\n        \"url\": \"http://localhost:4004/ws/chat\",\n        \"headers\": {\n          \"Authorization\": \"Basic ...\"\n        }\n      }\n    }\n  }\n}\n```\n\nDuring runtime, access to the websocket service client can be established, events emitted and received as follows:\n\n```js\nconst client = await cds.connect.to(\"chat-socket.io\");\nclient.on(\"received\", (message) =\u003e {});\nclient.emit(\"message\", { text: \"test\" });\n```\n\nCustom runtime options can be provided via:\n\n```js\nconst client = await cds.connect.to(\"chat-socket.io\", {\n  url: `ws://localhost:4005/ws/chat`,\n  headers: {\n    authorization: \"...\",\n  },\n});\n```\n\n### Approuter\n\nAuthorization is best provided in production by [the Approuter](https://www.npmjs.com/package/@sap/approuter) component (e.g., via XSUAA auth).\nValid UAA bindings for Approuter and backend are necessary so that the authorization flow is working.\nLocally, the following default environment files need to exist:\n\n- `test/_env/default-env.json`\n  ```json\n  {\n    \"VCAP_SERVICES\": {\n      \"xsuaa\": [{}]\n    }\n  }\n  ```\n- `test/_env/approuter/default-services.json`\n  ```json\n  {\n    \"uaa\": {}\n  }\n  ```\n\nApprouter is configured to support websockets in `xs-app.json` according\nto [@sap/approuter - websockets property](https://www.npmjs.com/package/@sap/approuter#websockets-property):\n\n```json\n{\n  \"websockets\": {\n    \"enabled\": true\n  }\n}\n```\n\nFor local testing without approuter a mocked basic authorization is hardcoded in `flp.html/index.html`.\n\n#### Paths\n\nUI websocket paths need to be properly routed to the backend service. The following scenarios are relevant:\n\n- **Standalone**:\n  - No Approuter is in place, therefore UI websocket paths must directly match the backend service paths.\n\n- **Approuter**:\n  - The Approuter routes websocket paths to the backend service and the protocol may be rewritten.\n  - Approuter `xs-app.json` may configure routes, so that UI websocket paths are rerouted to the backend service paths.\n  - **Example**: (`xs-app.json`):\n    ```json\n    {\n      \"source\": \"^/ws/(.*)$\",\n      \"target\": \"$1\",\n      \"destination\": \"backend-url\"\n    }\n    ```\n  - To access UI with and without Approuter (local development), the backend service can adjust for the Approuter path prefix in the `upgrade` server request.\n  - **Example:** (`server.js`) - Approuter routes `/ws/chat` to backend service path `/chat`\n    ```js\n    cds.on(\"listening\", async ({ server, url }) =\u003e {\n      server.on(\"upgrade\", function (req) {\n        if (req.url.startsWith(\"/ws\")) {\n          req.url = req.url.replace(\"/ws\", \"\");\n        }\n      });\n    });\n    ```\n\n- **Workzone**:\n  - The managed Approuter of Workzone routes UI websocket paths to backend services for each HTML5 application.\n  - Websocket paths in the UI must be relative to the loaded app, so that they can be correctly delegated through the app’s `xs-app.json` routing configuration.\n  - The correct relative path can be determined dynamically via the app manifest.\n  - **Example:** (`Component.js`)\n    ```js\n    new WebSocket(this.getManifestObject().resolveUri(\"ws/chat\"));\n    ```\n\n### Operations\n\nOperations comprise actions and functions in the CDS service that are\nexposed by CDS service either unbound (static level) or bound (entity instance level).\nOperations are exposed as part of the websocket protocol as described below.\n\n#### Operation Results\n\nOperation results will be provided via an optional websocket acknowledgement callback.\n\n\u003e Operation results are only supported with Socket.IO (`kind: socket.io`) using acknowledgement callbacks.\n\nPlease make sure, when passing an acknowledgement callback in `emit` function, that all parameters (event, data, headers)\nare provided before passing the callback function as last parameter:\n\n```js\nsocket.emit(event, data, headers, (result) =\u003e {\n  // ...\n});\n```\n\n#### Unbound Operations\n\nEach unbound function and action is exposed as a websocket event.\nThe signature (parameters and return type) is passed through without additional modification.\nOperation result will be provided as part of the acknowledgment callback.\n\n#### Special operations\n\nThe websocket adapter tries to call the following special operations on the CDS service, if available:\n\n- `wsConnect`: Callback to notify that a socket was connected\n- `wsDisconnect`: Callback to notify that a socket was disconnected\n- `wsContext`: Callback to notify that a socket changed the event context (details see section Event Contexts)\n\n#### Bound Operations\n\nEach service entity is exposed as a CRUD interface via as special events as\nproposed [here](https://socket.io/get-started/basic-crud-application/).\nThe event is prefixed with the entity name and has the CRUD operation as suffix, e.g. `Books:create`.\nIn addition, also bound functions and actions are included in these schemas, e.g. `Books:sell`.\nThe signature (parameters and return type) is passed through without additional modification.\nIt is expected that the event payload contains the primary key information.\nCRUD/action/function result will be provided as part of the acknowledgment callback.\n\n#### CRUD Operations\n\nCreate, Read, Update and Delete (CRUD) actions are mapped to websocket events as follows:\n\n- `\u003centity\u003e:create`: Create an entity instance\n- `\u003centity\u003e:read`: Read an entity instance by a key\n- `\u003centity\u003e:readDeep`: Read an entity instance deep (incl. deep compositions) by a key\n- `\u003centity\u003e:update`: Update an entity instance by key\n- `\u003centity\u003e:delete`: Delete an entity instance by key\n- `\u003centity\u003e:list`: List all entity instances\n- `\u003centity\u003e:\u003coperation\u003e`: Call a bound entity operation (action/function)\n\nEvents can be emitted and the response can be retrieved via acknowledgment callback (`kind: socket.io` only).\n\n##### CRUD Broadcast Events\n\nCRUD events that modify entities automatically emit another event after successful processing:\n\n- `\u003centity\u003e:create =\u003e \u003centity\u003e:created`: Entity instance has been updated\n- `\u003centity\u003e:update =\u003e \u003centity\u003e:updated`: Entity instance has been created\n- `\u003centity\u003e:delete =\u003e \u003centity\u003e:deleted`: Entity instance has been deleted\n\nBecause of security concerns, it can be controlled which data of those events is broadcast,\nvia annotations `@websocket.broadcast` or `@ws.broadcast` on entity level.\n\n- Propagate only key via one of the following options (default, if no annotation is present):\n  - `@websocket.broadcast = 'key'`\n  - `@websocket.broadcast.content = 'key'`\n  - `@ws.broadcast = 'key'`\n  - `@ws.broadcast.content = 'key'`\n- Propagate complete entity data via one of the following options:\n  - `@websocket.broadcast = 'data'`\n  - `@websocket.broadcast.content = 'data'`\n  - `@ws.broadcast = 'data'`\n  - `@ws.broadcast.content = 'data'`\n- Propagate no data (hence suppress CRUD broadcast event) via one of the following options:\n  - `@websocket.broadcast = 'none'`\n  - `@websocket.broadcast.content = 'none'`\n  - `@ws.broadcast = 'none'`\n  - `@ws.broadcast.content = 'none'`\n\nIf the CRUD broadcast event is modeled as part of a CDS service, the annotations above are ignored for that event,\nand the broadcast data is filtered along the event elements. As character `:` is not allowed in CDS service event names,\ncharacter `:` is replaced by a scoped event name using character `.`.\n\n**Example:**\nWebSocket Event: `Object:created` is mapped to CDS Service Event: `Object.created`\n\nPer default, events are broadcast to every connected socket, except the socket that was called with the CRUD event.\nTo also include the triggering socket within the broadcast, this can be controlled via annotations\n`@websocket.broadcast.all` or `@ws.broadcast.all` on entity level.\n\n### Examples\n\n#### Bookshop (UI5)\n\nThe example UI5 `bookshop` application using WebSockets can be found at [examples/bookshop](./examples/bookshop).\nIt leverages the [Fiori Side Effects](#event-driven-side-effects) to perform UI updates based on WebSocket events.\n\n**Source**: [Fiori Side Effects Enabled CDS Service](examples/bookshop/srv/cat-service.cds)\n\nThe example bookshop application can be started by:\n\n- Open: `cd examples/bookshop`\n- Install: `npm install`\n- Start: `cds watch`\n- Browser: http://localhost:4004/fiori-apps.html\n\n#### Fiori (UI5)\n\nThe example UI5 `fiori` application using WebSockets can be found at [test/\\_env/app/fiori](./test/_env/app/fiori).\nIt leverages the [Fiori Side Effects](#event-driven-side-effects) to perform UI updates based on WebSocket events.\n\n\u003cvideo src=\"https://github.com/user-attachments/assets/480c0436-ddac-4135-81bf-ba2592d6e16f\"\u003e\u003c/video\u003e\n**Source**: [Fiori Side Effects Enabled CDS Service](test/_env/srv/fiori.cds)\n\nThe example application can be started by:\n\n- Basic Auth\n  - Starting backend: `npm start`\n  - Open in browser: http://localhost:4004/flp.html#Books-display\n- XSUAA Auth:\n  - Starting approuter: `npm run start:approuter`\n  - Starting backend: `npm run start:uaa`\n  - Open in the browser: http://localhost:5001/flp.html#Books-display\n\n#### Todo (UI5)\n\nThe example UI5 `todo` application using WebSockets can be found at [test/\\_env/app/todo](./test/_env/app/todo).\n\nThe example application can be started by:\n\n- Basic Auth\n  - Starting backend: `npm start`\n  - Open in browser: http://localhost:4004/flp.html#Todo-manage\n- XSUAA Auth:\n  - Starting approuter: `npm run start:approuter`\n  - Starting backend: `npm run start:uaa`\n  - Open in the browser: http://localhost:5001/flp.html#Todo-manage\n\n#### Chat (HTML)\n\nAn example `chat` application using WebSockets can be found at [test/\\_env/app/chat_ws](./test/_env/app/chat_ws) and\n[test/\\_env/app/chat_chat_socketio](./test/_env/app/chat_socketio).\n\nThe example application can be started by:\n\n- Basic Auth:\n  - WebSocket Standard: `npm start`\n    - Browser: http://localhost:4004/chat_ws/index.html\n  - Socket.IO: `npm run start:socketio`\n    - Browser: http://localhost:4004/chat_socketio/index.html\n- XSUAA Auth:\n  - Approuter: `npm run start:approuter`\n  - WebSocket Standard: `npm run start:uaa`\n    - Browser: http://localhost:5001/chat_ws/index.html\n  - Socket.IO: `npm run start:socketio:uaa`\n    - Browser: http://localhost:5001/chat_socketio/index.html\n\n### Unit-Tests\n\nUnit-test can be found in folder `test` and can be executed via `npm test`;\nThe basic unit-test setup for WebSockets in CDS context looks as follows:\n\n#### WS\n\n```js\n\"use strict\";\n\nconst cds = require(\"@sap/cds\");\nconst WebSocket = require(\"ws\");\n\ncds.test(__dirname + \"/..\");\n\nconst authorization = `Basic ${Buffer.from(\"alice:alice\").toString(\"base64\")}`;\n\ndescribe(\"WebSocket\", () =\u003e {\n  let socket;\n\n  beforeAll((done) =\u003e {\n    const port = cds.app.server.address().port;\n    socket = new WebSocket(`ws://localhost:${port}/ws/chat`, {\n      headers: {\n        authorization,\n      },\n    });\n  });\n\n  afterAll(() =\u003e {\n    socket.close();\n    cds.ws.close();\n  });\n\n  test(\"Test\", (done) =\u003e {\n    socket.send(\n      JSON.stringify({\n        event: \"event\",\n        data: {},\n      }),\n    );\n    done();\n  });\n});\n```\n\n#### Socket.io\n\n```js\n\"use strict\";\n\nconst cds = require(\"@sap/cds\");\nconst ioc = require(\"socket.io-client\");\n\ncds.test(__dirname + \"/..\");\n\ncds.env.websocket.kind = \"socket.io\";\n\nconst authorization = `Basic ${Buffer.from(\"alice:alice\").toString(\"base64\")}`;\n\ndescribe(\"WebSocket\", () =\u003e {\n  let socket;\n\n  beforeAll((done) =\u003e {\n    const port = cds.app.server.address().port;\n    socket = ioc(`http://localhost:${port}/ws/chat`, {\n      extraHeaders: {\n        authorization,\n      },\n    });\n    socket.on(\"connect\", done);\n  });\n\n  afterAll(() =\u003e {\n    socket.disconnect();\n    cds.ws.close();\n  });\n\n  test(\"Test\", (done) =\u003e {\n    socket.emit(\"event\", {}, (result) =\u003e {\n      expect(result).toBeDefined();\n      done();\n    });\n  });\n});\n```\n\n### Adapters\n\nAn Adapter is a server-side component that is responsible for broadcasting events to all or a subset of clients.\n\n#### WS Standard Adapters\n\nThe following adapters for WS Standard are supported out-of-the-box.\n\n##### Redis\n\nEvery event sent to multiple clients is sent to all matching clients connected to the current server\nand published in a Redis channel and received by the other websocket servers of the cluster.\nThe app needs to be bound to a Redis service instance to set up and connect the Redis client.\n\nTo use the Redis Adapter (basic publish/subscribe), the following steps have to be performed:\n\n- Set `cds.websocket.adapter.impl: \"redis\"`\n- Application needs to be bound to a Redis instance\n  - Cloud Foundry: Redis is automatically active\n    - Use option `cds.websocket.adapter.active: false` to disable Redis adapter\n  - Other Environment (e.g., Kyma): Redis is NOT automatically active\n    - Use option `cds.websocket.adapter.active: true` to enable Redis adapter\n  - Local: Redis is NOT automatically active\n    - Use option `cds.websocket.adapter.local: true` to enable Redis adapter locally\n    - Local Redis configuration needs to be in environment (e.g., CAP hybrid testing or `default-env.json`)\n      - Redis can be configured for local testing as described in the following documentation: https://cap-js-community.github.io/event-queue/setup/#configure-redis\n- Redis Adapter options can be specified via `cds.websocket.adapter.options`\n- Redis channel key can be specified via `cds.websocket.adapter.options.key`. Default value is `websocket`.\n- Redis client connection configuration can be passed via `cds.websocket.adapter.config`\n- Redis lookup is performed via `cds.env`.\n  - Default lookup is done via VCAP `label: \"redis-cache\"`\n  - Custom Redis lookup can be specified via `cds.requires.redis-websocket`.\n\n    **Example:**\n\n    ```json\n    {\n      \"cds\": {\n        \"requires\": {\n          \"redis-websocket\": {\n            \"vcap\": {\n              \"tag\": \"ws-redis\"\n            }\n          }\n        }\n      }\n    }\n    ```\n\n  - A shared Redis service across multiple consumers can be configured via `cds.requires.redis`:\n\n    **Example:**\n\n    ```json\n    {\n      \"cds\": {\n        \"requires\": {\n          \"redis-websocket\": false,\n          \"redis\": {\n            \"vcap\": {\n              \"tag\": \"ws-redis\"\n            }\n          }\n        }\n      }\n    }\n    ```\n\n  - Redis service options can be specified via `cds.requires.redis-websocket.options` resp. `cds.requires.redis.options`.\n\n##### Custom Adapter\n\nA custom websocket adapter implementation can be provided via a path relative to the project root\nwith the configuration `cds.websocket.adapter.impl` (e.g. `cds.websocket.adapter.impl: './adapter/xyz.js'`).\n\nThe custom adapter class needs to implement the following functions:\n\n- **on(service, path)**: Register an adapter subscription\n- **emit(service, path, message)**: Emit an adapter event message\n\nIn addition, it can implement the following functions (optional):\n\n- **constructor(server, config)**: Setup instance on creation\n- **setup()**: Perform some async setup activities (optional)\n\n#### Socket.IO Adapters\n\nThe following adapters for Socket.IO are supported out-of-the-box.\n\n##### Redis Adapter\n\nTo use the Redis Adapter, the following steps have to be performed:\n\n- Install Redis Adapter dependency:\n  `npm install @socket.io/index-adapter`\n- Set `cds.websocket.adapter.impl: \"@socket.io/index-adapter\"`\n- Application needs to be bound to a Redis instance\n  - Locally a `default-env.json` file need to exist with index configuration\n- Redis Adapter options can be specified via `cds.websocket.adapter.options`\n- Redis channel key can be specified via `cds.websocket.adapter.options.key`. Default value is `socket.io`.\n\nDetails:\nhttps://socket.io/docs/v4/index-adapter/\n\n##### Redis Streams Adapter\n\nTo use the Redis Stream Adapter, the following steps have to be performed:\n\n- Install Redis Streams Adapter dependency:\n  `npm install @socket.io/index-streams-adapter`\n- Set `cds.websocket.adapter.impl: \"@socket.io/index-streams-adapter\"`\n- Application needs to be bound to a Redis instance\n  - Locally a `default-env.json` file need to exist with index configuration\n- Redis Streams Adapter options can be specified via `cds.websocket.adapter.options`\n- Redis channel key can be specified via `cds.websocket.adapter.options.streamName`. Default value is `socket.io`.\n\nDetails:\nhttps://socket.io/docs/v4/index-streams-adapter/\n\n##### Custom Adapter\n\nA custom websocket adapter implementation can be provided via a path relative to the project root\nwith the configuration `cds.websocket.adapter.impl` (e.g. `cds.websocket.adapter.impl: './adapter/xyz.js'`).\n\nThe custom adapter needs to fulfill the Socket.IO adapter interface (https://socket.io/docs/v4/adapter/).\n\n### Deployment\n\nThis module also works on a deployed infrastructure like Cloud Foundry (CF) or Kubernetes (K8s).\n\nAn example Cloud Foundry deployment can be found in `test/_env`:\n\n- `cd test/_env`\n  - Use [manifest.yml](test/_env/manifest.yml)\n- `npm run cf:push`\n  - Prepares modules `approuter` and `backend` in `test/_env` and pushes to Cloud Foundry\n    - Approuter performs authentication flow with XSUAA and forwards to backend\n    - Backend serves endpoints (websocket, odata) and UI apps (runs on an in-memory SQLite3 database)\n\nIn deployed infrastructure, websocket protocol is exposed via Web Socket Secure (WSS) at `wss://` over an encrypted TLS\nconnection.\nFor WebSocket standard the following setup in the browser environment is recommended to cover a deployed and local use-case:\n\n```js\nconst protocol = window.location.protocol === \"https:\" ? \"wss://\" : \"ws://\";\nconst socket = new WebSocket(protocol + window.location.host + \"/ws/chat\");\n```\n\nFor UI5, applications class [sap.ui.core.ws.WebSocket](https://sapui5.hana.ondemand.com/sdk/#/api/sap.ui.core.ws.WebSocket)\ncan be used, which automatically selects the correct protocol (`ws://` or `wss://`) based on the application protocol.\n\nExample Usage:\n\n- [/app//todo/webapp/Component.js](test/_env/app/todo/webapp/Component.js)\n\n## Support, Feedback, Contributing\n\nThis project is open to feature requests/suggestions, bug reports, etc.\nvia [GitHub issues](https://github.com/cap-js-community/websocket/issues). Contribution and feedback are encouraged and\nalways welcome. For more information about how to contribute, the project structure, as well as additional contribution\ninformation, see our [Contribution Guidelines](CONTRIBUTING.md).\n\n## Code of Conduct\n\nWe as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for\neveryone. By participating in this project, you agree to abide by its [Code of Conduct](CODE_OF_CONDUCT.md) at all\ntimes.\n\n## Licensing\n\nCopyright 2025 SAP SE or an SAP affiliate company and websocket contributors. Please see our [LICENSE](LICENSE) for\ncopyright and license information. Detailed information including third-party components and their licensing/copyright\ninformation is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js-community/websocket).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcap-js-community%2Fwebsocket","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcap-js-community%2Fwebsocket","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcap-js-community%2Fwebsocket/lists"}