diff --git a/README.md b/README.md index 6b20143..9de1a69 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ For more information, please see [SAP Cloud Application Event Hub](https://help. ### webhookSizeLimit -To set a size limit for events accepted by the webhook, set the ``webhookSizeLimit``parameter in the ``package.json`` file in the root folder of your app, e.g. +To set a size limit for events accepted by the webhook, set the `webhookSizeLimit`parameter in the `package.json` file in the root folder of your app, e.g. ```jsonc "cds": { @@ -76,7 +76,101 @@ To set a size limit for events accepted by the webhook, set the ``webhookSizeLim } ``` -If the parameter is not set, the [global request body size limit](https://pages.github.tools.sap/cap/docs/node.js/cds-server#maximum-request-body-size) ``cds.env.server.body_parser.limit`` is taken into account. If this parameter is not set either, the default value of ``1mb``is used. +If the parameter is not set, the [global request body size limit](https://pages.github.tools.sap/cap/docs/node.js/cds-server#maximum-request-body-size) `cds.env.server.body_parser.limit` is taken into account. If this parameter is not set either, the default value of `1mb`is used. + +## ORD Integration + +When both `@cap-js/event-broker` and `@cap-js/ord` plugins are installed, the Event Broker plugin can expose consumed events as an **Integration Dependency** in the ORD document. + +### Using messaging.subscribe() + +Use the `subscribe()` method instead of `messaging.on()` to declare event subscriptions with ORD metadata in a single place: + +```javascript +// srv/server.js +const cds = require("@sap/cds"); + +cds.on("loaded", async () => { + const messaging = await cds.connect.to("messaging"); + + // Subscribe to event with ORD Integration Dependency metadata + messaging.subscribe( + "sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1", + { + eventResourceOrdId: "sap.s4:eventResource:CE_BUSINESSPARTNEREVENTS:v1", + }, + async (event) => { + console.log("Event received:", event); + }, + ); +}); +``` + +The `eventResourceOrdId` value should match the event resource identifier from the SAP Business Accelerator Hub or your event source's ORD document. + +### Multiple Event Types per Event Resource + +A single event resource can contain multiple event types. Simply use the same `eventResourceOrdId` for related events: + +```javascript +// Both events belong to the same CE_BUSINESSPARTNEREVENTS resource +messaging.subscribe( + "sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1", + { eventResourceOrdId: "sap.s4:eventResource:CE_BUSINESSPARTNEREVENTS:v1" }, + handleBPChanged, +); + +messaging.subscribe( + "sap.s4.beh.businesspartner.v1.BusinessPartner.Created.v1", + { eventResourceOrdId: "sap.s4:eventResource:CE_BUSINESSPARTNEREVENTS:v1" }, + handleBPCreated, +); +``` + +The plugin automatically groups event types by their `eventResourceOrdId`. + +### How it works + +At runtime, when services are served, the Event Broker plugin: + +1. Collects all event subscriptions registered via `messaging.subscribe()` with `eventResourceOrdId` option +2. Groups event types by their event resource ORD ID +3. Registers the eventResources with the ORD plugin's Extension API + +### Example ORD Output + +```json +{ + "integrationDependencies": [ + { + "ordId": "customer.myapp:integrationDependency:consumedEvents:v1", + "title": "Consumed Events", + "aspects": [ + { + "title": "Subscribed Event Types", + "eventResources": [ + { + "ordId": "sap.s4:eventResource:CE_BUSINESSPARTNEREVENTS:v1", + "subset": [ + { + "eventType": "sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1" + }, + { + "eventType": "sap.s4.beh.businesspartner.v1.BusinessPartner.Created.v1" + } + ] + } + ] + } + ] + } + ] +} +``` + +### Backwards Compatibility + +The `subscribe()` method is **non-breaking** - existing code using `messaging.on()` continues to work. However, only events registered via `messaging.subscribe()` with the `eventResourceOrdId` option will appear in the ORD Integration Dependency. ## Support, Feedback, Contributing diff --git a/cds-plugin.js b/cds-plugin.js index 95ea4fb..36f1360 100644 --- a/cds-plugin.js +++ b/cds-plugin.js @@ -124,6 +124,16 @@ function _validateCertificate(req, res, next) { } } +// Logger for ORD integration (outside class context) +const LOG = cds.log('event-broker') + +/** + * Global registry for programmatic ORD event resource mappings. + * Populated via EventBroker.subscribe() method. + * @type {Map} eventType -> ordId + */ +const programmaticOrdMappings = new Map() + class EventBroker extends cds.MessagingService { async init() { await super.init() @@ -350,6 +360,89 @@ class EventBroker extends cds.MessagingService { res.status(500).json({ message: 'Internal Server Error!' }) } } + + /** + * Subscribe to an event with ORD metadata. + * This consolidates event subscription and ORD Integration Dependency declaration. + * + * @example + * const messaging = await cds.connect.to("messaging") + * messaging.subscribe("sap.s4.beh.salesorder.v1.SalesOrder.Changed.v1", { + * eventResourceOrdId: "sap.s4:eventResource:CE_SALESORDEREVENTS:v1" + * }, async (event) => { + * console.log("Event received:", event) + * }) + * + * @param {string} event - The event type to subscribe to + * @param {object} options - Options for ORD Integration Dependency + * @param {string} options.eventResourceOrdId - The ORD ID of the event resource (e.g., "sap.s4:eventResource:...") + * @param {Function} handler - The event handler function + */ + subscribe(event, options, handler) { + // Store ORD mapping if provided + if (options?.eventResourceOrdId) { + programmaticOrdMappings.set(event, options.eventResourceOrdId) + } + + // Delegate to standard messaging.on() + return this.on(event, handler) + } +} + +// ============================================================================ +// ORD Integration Dependency Provider +// ============================================================================ + +/** + * Register Integration Dependency provider with ORD plugin. + * Called once when services are ready. + */ +function registerOrdIntegrationDependencyProvider() { + // Check if ORD plugin is available + let ordPlugin + try { + ordPlugin = require('@cap-js/ord') + } catch (e) { + // ORD plugin not installed - that's fine + return + } + + if (!ordPlugin.registerIntegrationDependencyProvider) { + // Older ORD plugin version without Extension API + return + } + + // Register provider function + ordPlugin.registerIntegrationDependencyProvider(() => { + // Simply return eventResources from programmatic mappings + // No need to check subscribedTopics - if subscribe() was called, it's subscribed + if (programmaticOrdMappings.size === 0) { + return null + } + + // Group events by ordId + const ordIdToEvents = new Map() + for (const [eventType, ordId] of programmaticOrdMappings) { + if (!ordIdToEvents.has(ordId)) { + ordIdToEvents.set(ordId, []) + } + ordIdToEvents.get(ordId).push(eventType) + } + + // Build eventResources array + const eventResources = [] + for (const [ordId, events] of ordIdToEvents) { + eventResources.push({ ordId, events }) + } + + LOG.info(`Providing ${eventResources.length} eventResource(s) for ORD Integration Dependency`) + return { eventResources } + }) } +// Register when services are served (runtime only) +cds.once('served', () => { + registerOrdIntegrationDependencyProvider() +}) + module.exports = EventBroker