From 2fcb47bbde3cbab26a6aaebe0f04a5f53c41eaab Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 23 Mar 2026 13:56:31 +0100 Subject: [PATCH 01/12] read me improvements --- README.md | 258 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 203 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index bc18972..1d592a2 100644 --- a/README.md +++ b/README.md @@ -2,46 +2,92 @@ # Setup -### Execute following commands to install dependencies after cloning the project: +## Run the sample in the Plugin -Make sure to follow https://cap.cloud.sap/docs/get-started/ to install global dependencies that are required for CAP aplication development. +Make sure to follow https://cap.cloud.sap/docs/get-started/ to install global dependencies that are required for CAP application development. + +Install the dependencies for the plugin and build the project: ``` -npm install -npm i -g tsx +npm run i npm run build ``` -### To run the sample application, execute: +### Running the bookshop example + +Using cds-tsx: + +``` +npm i -g tsx +cd tests/bookshop && npm run build +cd tests/bookshop && cds-tsx w +``` + +Using cds watch: + +``` +npm run compile +cd tests/bookshop && npm run build +cd tests/bookshop && cds watch +``` + +### Troubleshooting + +`npm run clean:all` cleans all generated files and rebuilds them +`npm run clean:build` cleans the build files and rebuilds them +`npm run clean:types` cleans the generated cds-typer files and rebuilds them + +## To use the plugin as a CAP developer + +To add the plugin to your CAP Node.js application, run: + +``` +npm run add @cap-js/process +``` + +### Binding against SBPA instance + +Binding is not necessary for trying out the plugin locally. +The annotation and programmatic approaches against the generic ProcessService work without any bindings against SBPA. + +Login to Cloud Foundry: ``` -cd /tests/bookshop -cds watch +cf login --sso ``` -## To use the plugin as a CAP developer: +Bind to a ProcessService instance: -- (in future): run `npm add @cap-js/process` - - before first release: `npm add git+https://github.com/cap-js/process.git` -- Login to cf `cf login ...` -- Bind to process service instance: - - `cds bind ProcessService -to ` +``` + cds bind ProcessService -2 +``` + +This will create a `cdsrc-private.json` file containing the credentials. -Start developing 🙂 +### Importing Processes as a Service -# Current annotation implementation: +The plugin allows you to import existing SBPA processes as CDS services. To do so, you first need to bind against an existing SBPA instance. +Imported processes ensure type safety and enable build-time validation. +Without importing a specific process, the programmatic approach is still possible through the generic ProcessService. +However, build-time validation will not check whether all mandatory inputs required to start a process are provided. -Important: for process events defined on 'DELETE' operation, a before handler fetches the entity that will be deleted and stores it in `req._Process.[Start|Suspend|Resume|Cancel]` so that it can be used in our `service.after` handler. +``` +cds bind --exec -- cds-tsx import --from process --name +``` -## For starting a process: +# Annotations -- `@bpm.process.start` -- Start a process (or classic workflow), either after entity creation, update, deletion, read, or any custom action including all entity elements unless at least one `@bpm.process.input` is given - - if no attribute is annotated with`@bpm.process.input`, all attributes of that entity will be fetched and are part of the context for process input. Associations will not be expanded in that case +Important: For process events defined on the `DELETE` operation, a `before` handler fetches the entity that will be deleted and stores it in `req._Process.[Start|Suspend|Resume|Cancel]` so that it can be used in the `service.after` handler. + +## For starting a process + +- `@bpm.process.start` -- Start a process (or classic workflow) after entity creation, update, deletion, read, or any custom action, including all entity elements unless at least one `@bpm.process.start.inputs` entry is given + - If no attribute is annotated with `@bpm.process.input`, all attributes of that entity will be fetched and included as process input context. Associations will not be expanded in that case. - `@bpm.process.start.id` -- definition ID for deployed process - `@bpm.process.start.on` - - `@bpm.process.start.if` -- only starting process if expression is true -- `@bpm.process.start.inputs` -- array of input mappings that control which entity fields are passed as process context (optional) -- if a businessKey is annotated on the entity using `@bpm.process.businessKey`, at process start this businessKey expression will be evaluated. If the length of the businessKey exceeds SBPAs character limit of 255, the request will also be rejected as process start will fail for that case + - `@bpm.process.start.if` -- Only start the process if the expression evaluates to true +- `@bpm.process.start.inputs` -- Array of input mappings that control which entity fields are passed as process context (optional) +- If a `businessKey` is annotated on the entity using `@bpm.process.businessKey`, it will be evaluated at process start. If the length of the business key exceeds SBPA's character limit of 255, the request will be rejected, as process start will fail in that case. ### Input Mapping @@ -305,15 +351,15 @@ service MyService { } ``` -## For cancelling/resuming/suspending a process +## For cancelling, resuming, or suspending a process -- `@bpm.process.` -- Cancel/Suspend/Resume any processes bound to the entity (using entityKey as businessKey in SBPA) +- `@bpm.process.` -- Cancel/Suspend/Resume any processes bound to the entity (using the entity key as business key in SBPA) - `@bpm.process..on` - - `@bpm.process..cascade` -- boolean (optional, defaults to false) - - `@bpm.process..if` -- only starting process if expression is true - - example: `@bpm.process.suspend.if: (weight > 10)` -- for cancelling/resuming/suspending it is required to have a businessKey expression annotated on the entity using `@bpm.process.businessKey`. If no businessKey is annotated, the request will be rejected - - example: `@bpm.process.businessKey: (id || '-' || name)` + - `@bpm.process..cascade` -- Boolean (optional, defaults to false) + - `@bpm.process..if` -- Only trigger the action if the expression evaluates to true + - Example: `@bpm.process.suspend.if: (weight > 10)` +- For cancelling, resuming, or suspending, it is required to have a business key expression annotated on the entity using `@bpm.process.businessKey`. If no business key is annotated, the request will be rejected. + - Example: `@bpm.process.businessKey: (id || '-' || name)` Example: @@ -335,7 +381,7 @@ service MyService { ``` -# Current build time validation +# Build-Time Validation Validation occurs during `cds build` and produces **errors** (hard failures that stop the build) or **warnings** (soft failures that are logged but don't stop the build). @@ -382,54 +428,139 @@ When both `@bpm.process.start.id` and `@bpm.process.start.on` are present and th - A bound action defined on the entity - `@bpm.process..cascade` is optional (defaults to false); if provided, must be a boolean - `@bpm.process..if` must be a valid CDS expression (if present) -- if any annotation with `@bpm.process.` is defined, a valid businessKey expression must be defined using `@bpm.process.businessKey` - - example: `@bpm.process.businessKey: (id || '-' || name)` would concatenate id and name with a '-' string as a business key - - the businessKey definition here must reflect the one configured in SBPA Process Builder +- If any annotation with `@bpm.process.` is defined, a valid business key expression must be defined using `@bpm.process.businessKey`. + - Example: `@bpm.process.businessKey: (id || '-' || name)` would concatenate `id` and `name` with a `-` separator as a business key. + - The business key definition must match the one configured in the SBPA Process Builder. ### Warnings - Unknown annotations under `@bpm.process..*` trigger a warning listing allowed annotations -# Current programmatic approach +# Programmatic Approach + +The plugin provides two ways to interact with SBPA processes programmatically: + +1. **Imported Process Services** -- Import a specific SBPA process to get a typed CDS service with full type safety and build-time validation. +2. **Generic ProcessService** -- Use the built-in `ProcessService` directly for untyped, flexible process management without importing a specific process. + +Both approaches work locally (in-memory), in hybrid mode (against a real SBPA instance), and in production. + +## Generic ProcessService + +The generic `ProcessService` is a built-in CDS service that ships with the plugin. It provides low-level events and functions for managing workflow instances without requiring any process imports. This is useful for quick prototyping, dynamic process management, or cases where type safety is not needed. -## Importing a Service +The `ProcessService` is automatically configured based on the CDS profile: -To use the programmatic approach with types, you need to import an existing SBPA process. This requires credentials via `cds bind` and being logged in to Cloud Foundry. +- **Development**: Uses an in-memory local workflow store (no credentials needed) +- **Hybrid**: Connects to a real SBPA instance via `cds bind` +- **Production**: Connects to SBPA through VCAP service bindings -### From SBPA (Remote Import) +### Service Definition + +The generic `ProcessService` defines the following events and functions: + +| Operation | Type | Description | +| --------------------------- | -------- | ----------------------------------------------------------------- | +| `start` | event | Start a workflow instance with a `definitionId` and `context` | +| `cancel` | event | Cancel all running/suspended instances matching a `businessKey` | +| `suspend` | event | Suspend all running instances matching a `businessKey` | +| `resume` | event | Resume all suspended instances matching a `businessKey` | +| `getAttributes` | function | Retrieve attributes for a specific process instance | +| `getOutputs` | function | Retrieve outputs for a specific process instance | +| `getInstancesByBusinessKey` | function | Find process instances by business key and optional status filter | + +### Usage + +```typescript +const processService = await cds.connect.to('ProcessService'); + +// Start a process +await processService.emit('start', { + definitionId: 'eu12.myorg.myproject.myProcess', + context: { orderId: '12345', amount: 100.0 }, +}); + +// Cancel all running instances for a business key +await processService.emit('cancel', { + businessKey: 'order-12345', + cascade: false, +}); + +// Suspend running instances +await processService.emit('suspend', { + businessKey: 'order-12345', + cascade: false, +}); + +// Resume suspended instances +await processService.emit('resume', { + businessKey: 'order-12345', + cascade: false, +}); + +// Query instances by business key +const instances = await processService.send('getInstancesByBusinessKey', { + businessKey: 'order-12345', + status: ['RUNNING', 'SUSPENDED'], +}); + +// Get attributes of a specific instance +const attributes = await processService.send('getAttributes', { + processInstanceId: 'instance-uuid', +}); + +// Get outputs of a specific instance +const outputs = await processService.send('getOutputs', { + processInstanceId: 'instance-uuid', +}); +``` + +> **Note:** The generic ProcessService uses `emit` for lifecycle events (start, cancel, suspend, resume) which are processed asynchronously through the CDS outbox, and `send` for query functions (getAttributes, getOutputs, getInstancesByBusinessKey) which return data synchronously. + +## Imported Process Services (Typed) + +For full type safety and build-time validation, you can import a specific SBPA process. This generates a typed CDS service with input/output types derived from the process definition. + +### Importing a Service + +To import a process, you need credentials via `cds bind` and must be logged in to Cloud Foundry. + +#### From SBPA (Remote Import) Import your SBPA process directly from the API: **Note:** For remote imports, you must have ProcessService credentials bound (e.g., via `cds bind process -2 `). The plugin will automatically resolve the bindings at import time. ```bash -cds import --from process --name eu12.bpm-horizon-walkme.sdshipmentprocessor.shipmentHandler --no-copy +cds bind --exec -- cds-tsx import --from process --name eu12.bpm-horizon-walkme.sdshipmentprocessor.shipmentHandler --no-copy ``` -If you want to have it as a cds instead of a csn you can add --as cds at the end. If you want to reimport the process use the --force flag at the end. The flag `no-copy` is very important, as otherwise the process will be saved locally on both `./srv/workflows`and `./srv/external` folder which would result in cds runtime issues, as the json is not a valid csn model and cannot be stored in the `.srv/external` directory. +If you want to have it as a cds instead of a csn you can add --as cds at the end. If you want to reimport the process use the --force flag at the end. The flag `no-copy` is very important, as otherwise the process will be saved locally on both `./workflows`and `./srv/external` folder which would result in cds runtime issues, as the json is not a valid csn model and cannot be stored in the `.srv/external` directory. -### From Local JSON File +#### From Local JSON File -If you already have a process definition JSON file (e.g., exported or previously fetched), you can generate the CSN model directly from it without needing credentials: +If you already have a process definition JSON file (e.g., exported or previously fetched), you can generate the CDS model directly from it without needing credentials: ```bash -cds import --from process ./srv/workflows/eu12.bpm-horizon-walkme.sdshipmentprocessor.shipmentHandler.json --no-copy +cds import --from process ./workflows/eu12.bpm-horizon-walkme.sdshipmentprocessor.shipmentHandler.json --no-copy ``` ### What Gets Generated -This will generate: +The import generates: -- A CDS service definition in `./srv/workflows/` +- A CDS service definition in `./workflows/` - Types via `cds-typer` for full TypeScript support - Generic handlers for the actions and functions in the imported service ## For starting a process ```typescript +import ShipmentHandlerService from '#cds-models/eu12/myorg/myproject/ShipmentHandlerService'; + const processService = await cds.connect.to(ShipmentHandlerService); -const processInstance = await processService.start({ +await processService.start({ businesskey: 'order-12345', startingShipment: { identifier: 'shipment_001', @@ -438,38 +569,55 @@ const processInstance = await processService.start({ }); ``` -## For suspending/resuming/cancelling a process +The `start` action accepts a typed `ProcessInputs` object that matches the process definition's input schema. The plugin validates inputs against the process definition at build time. + +### Suspending, Resuming, and Cancelling a Process ```typescript -// Suspend +// Suspend all running instances for a business key await processService.suspend({ businessKey: 'order-12345', cascade: false }); -// Resume +// Resume all suspended instances for a business key await processService.resume({ businessKey: 'order-12345', cascade: false }); -// Cancel +// Cancel all running/suspended instances for a business key await processService.cancel({ businessKey: 'order-12345', cascade: false }); ``` -## For getAttributes and getOutputs +The `cascade` parameter is optional and defaults to `false`. When set to `true`, child process instances are also affected. -### Missing +### Querying Process Instances ```typescript -const attributes = await processService.getAttributes({ processInstanceId: 'instance-uuid' }); +// Get all instances matching a business key, optionally filtered by status +const instances = await processService.getInstancesByBusinessKey({ + businessKey: 'order-12345', + status: ['RUNNING', 'SUSPENDED'], +}); -const outputs = await processService.getOutputs({ processInstanceId: 'instance-uuid' }); +// Get attributes for a specific process instance +const attributes = await processService.getAttributes({ + processInstanceId: 'instance-uuid', +}); + +// Get outputs for a specific process instance +const outputs = await processService.getOutputs({ + processInstanceId: 'instance-uuid', +}); ``` +Valid status values are: `RUNNING`, `SUSPENDED`, `CANCELED`, `ERRONEOUS`, `COMPLETED`. +If no status filter is provided, all statuses except `CANCELED` are returned. + # CAP - Process Plugin ## Support, Feedback, Contributing -This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/cap-js//issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md). +This project is open to feature requests/suggestions, bug reports, etc. via [GitHub issues](https://github.com/cap-js//issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md). ## Security / Disclosure -If you find any bug that may be a security problem, please follow our instructions at [in our security policy](https://github.com/cap-js//security/policy) on how to report it. Please do not create GitHub issues for security-related doubts or problems. +If you find any bug that may be a security problem, please follow the instructions in our [security policy](https://github.com/cap-js//security/policy) on how to report it. Please do not create GitHub issues for security-related doubts or problems. ## Code of Conduct From 683ca8ec6d13befe8c9e93973193cadd39a3845a Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 23 Mar 2026 15:02:25 +0100 Subject: [PATCH 02/12] improve improvements --- README.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 1d592a2..cc512c6 100644 --- a/README.md +++ b/README.md @@ -440,20 +440,15 @@ When both `@bpm.process.start.id` and `@bpm.process.start.on` are present and th The plugin provides two ways to interact with SBPA processes programmatically: -1. **Imported Process Services** -- Import a specific SBPA process to get a typed CDS service with full type safety and build-time validation. -2. **Generic ProcessService** -- Use the built-in `ProcessService` directly for untyped, flexible process management without importing a specific process. +1. **Generic ProcessService** -- Use the built-in `ProcessService` directly for untyped, flexible process management without importing a specific process. +2. **Specific / Imported ProcessService** -- Provides the types and sits on top of the generic ProcessService. Both approaches work locally (in-memory), in hybrid mode (against a real SBPA instance), and in production. ## Generic ProcessService The generic `ProcessService` is a built-in CDS service that ships with the plugin. It provides low-level events and functions for managing workflow instances without requiring any process imports. This is useful for quick prototyping, dynamic process management, or cases where type safety is not needed. - -The `ProcessService` is automatically configured based on the CDS profile: - -- **Development**: Uses an in-memory local workflow store (no credentials needed) -- **Hybrid**: Connects to a real SBPA instance via `cds bind` -- **Production**: Connects to SBPA through VCAP service bindings +The generic `ProcessService` allows setting the business key to mimic the behavior of the real SBPA workflow. The business key in the header is only used when the application runs locally, so to avoid issues, the business key should be built the same way as in the actual process. ### Service Definition @@ -532,7 +527,7 @@ Import your SBPA process directly from the API: **Note:** For remote imports, you must have ProcessService credentials bound (e.g., via `cds bind process -2 `). The plugin will automatically resolve the bindings at import time. ```bash -cds bind --exec -- cds-tsx import --from process --name eu12.bpm-horizon-walkme.sdshipmentprocessor.shipmentHandler --no-copy +cds bind --exec -- cds-tsx import --from process --name eu12.myorg.myproject.myProcess --no-copy ``` If you want to have it as a cds instead of a csn you can add --as cds at the end. If you want to reimport the process use the --force flag at the end. The flag `no-copy` is very important, as otherwise the process will be saved locally on both `./workflows`and `./srv/external` folder which would result in cds runtime issues, as the json is not a valid csn model and cannot be stored in the `.srv/external` directory. @@ -549,11 +544,15 @@ cds import --from process ./workflows/eu12.bpm-horizon-walkme.sdshipmentprocesso The import generates: -- A CDS service definition in `./workflows/` -- Types via `cds-typer` for full TypeScript support -- Generic handlers for the actions and functions in the imported service +- A CDS service definition in `./srv/external/` (annotated with `@bpm.process` and `@protocol: 'none'`) +- Typed `ProcessInputs`, `ProcessOutputs`, `ProcessAttribute`, and `ProcessInstance` types based on the process definition +- Typed actions: `start`, `suspend`, `resume`, `cancel` +- Typed functions: `getAttributes`, `getOutputs`, `getInstancesByBusinessKey` +- A process definition JSON in `./workflows/` -## For starting a process +After importing, run `cds-typer` to generate TypeScript types for the imported service. + +### Starting a Process ```typescript import ShipmentHandlerService from '#cds-models/eu12/myorg/myproject/ShipmentHandlerService'; @@ -609,6 +608,11 @@ const outputs = await processService.getOutputs({ Valid status values are: `RUNNING`, `SUSPENDED`, `CANCELED`, `ERRONEOUS`, `COMPLETED`. If no status filter is provided, all statuses except `CANCELED` are returned. +### Important + +- The typed process service does not currently support local development. +- The process import is currently only possible via the command line. + # CAP - Process Plugin ## Support, Feedback, Contributing From 26aa1e382afddf828fe1568006e645c75a549a0a Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 23 Mar 2026 15:59:43 +0100 Subject: [PATCH 03/12] fix for readme --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cc512c6..932e402 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# CAP - Process Plugin + [![REUSE status](https://api.reuse.software/badge/github.com/cap-js/process)](https://api.reuse.software/info/github.com/cap-js/process) # Setup @@ -371,6 +373,7 @@ service MyService { cascade: true | false, // optional, defaults to false when: () } + @bpm.process.businessKey(myElement || '-' || myElement2) entity MyProjection as projection on MyEntity { myElement, myElement2, @@ -407,7 +410,6 @@ When both `@bpm.process.start.id` and `@bpm.process.start.on` are present and th **Errors:** -- The process definition must have a `businesskey` input - Entity attributes specified in `@bpm.process.start.inputs` (or all direct attributes if `inputs` is omitted) must exist in the process definition inputs - Mandatory inputs from the process definition must be present in the entity @@ -605,8 +607,8 @@ const outputs = await processService.getOutputs({ }); ``` -Valid status values are: `RUNNING`, `SUSPENDED`, `CANCELED`, `ERRONEOUS`, `COMPLETED`. -If no status filter is provided, all statuses except `CANCELED` are returned. +Valid status values are: `RUNNING`, `SUSPENDED`, `CANCELLED`, `ERRONEOUS`, `COMPLETED`. +If no status filter is provided, all statuses except `CANCELLED` are returned. ### Important From 9e17efd555260a6ab13349342b43e038d88e49db Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Tue, 24 Mar 2026 10:04:38 +0100 Subject: [PATCH 04/12] fix --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 932e402..8e39a24 100644 --- a/README.md +++ b/README.md @@ -513,6 +513,7 @@ const outputs = await processService.send('getOutputs', { ``` > **Note:** The generic ProcessService uses `emit` for lifecycle events (start, cancel, suspend, resume) which are processed asynchronously through the CDS outbox, and `send` for query functions (getAttributes, getOutputs, getInstancesByBusinessKey) which return data synchronously. +> Make sure to check whether the outbox is correctly used. If not, refer to cds.queued to make sure it is used. ## Imported Process Services (Typed) @@ -615,8 +616,6 @@ If no status filter is provided, all statuses except `CANCELLED` are returned. - The typed process service does not currently support local development. - The process import is currently only possible via the command line. -# CAP - Process Plugin - ## Support, Feedback, Contributing This project is open to feature requests/suggestions, bug reports, etc. via [GitHub issues](https://github.com/cap-js//issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md). From 75ece8cfe08e6c5f9afc14e9c3a1a51e74151854 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Tue, 24 Mar 2026 10:29:23 +0100 Subject: [PATCH 05/12] fix --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8e39a24..5166e7b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Make sure to follow https://cap.cloud.sap/docs/get-started/ to install global de Install the dependencies for the plugin and build the project: ``` -npm run i +npm i npm run build ``` @@ -21,23 +21,25 @@ Using cds-tsx: ``` npm i -g tsx -cd tests/bookshop && npm run build -cd tests/bookshop && cds-tsx w +cd tests/bookshop +npm run build +cds-tsx w ``` Using cds watch: ``` npm run compile -cd tests/bookshop && npm run build -cd tests/bookshop && cds watch +cd tests/bookshop +npm run build +cds watch ``` ### Troubleshooting -`npm run clean:all` cleans all generated files and rebuilds them -`npm run clean:build` cleans the build files and rebuilds them -`npm run clean:types` cleans the generated cds-typer files and rebuilds them +- `npm run clean:all` cleans all generated files and rebuilds them +- `npm run clean:build` cleans the build files and rebuilds them +- `npm run clean:types` cleans the generated cds-typer files and rebuilds them ## To use the plugin as a CAP developer From 6917d7fb4e05e7c6a5cf39ee0e0d4ccaf1e506f4 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Tue, 24 Mar 2026 12:14:32 +0100 Subject: [PATCH 06/12] removed old flags --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 5166e7b..aa1c8d3 100644 --- a/README.md +++ b/README.md @@ -535,8 +535,6 @@ Import your SBPA process directly from the API: cds bind --exec -- cds-tsx import --from process --name eu12.myorg.myproject.myProcess --no-copy ``` -If you want to have it as a cds instead of a csn you can add --as cds at the end. If you want to reimport the process use the --force flag at the end. The flag `no-copy` is very important, as otherwise the process will be saved locally on both `./workflows`and `./srv/external` folder which would result in cds runtime issues, as the json is not a valid csn model and cannot be stored in the `.srv/external` directory. - #### From Local JSON File If you already have a process definition JSON file (e.g., exported or previously fetched), you can generate the CDS model directly from it without needing credentials: From e8b1f39ace580b9d0d1ec5496d0f37f3a8290d31 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Wed, 25 Mar 2026 15:34:03 +0100 Subject: [PATCH 07/12] initial --- README.md | 270 +++++++++++++++++++++++++++++------------------------- 1 file changed, 143 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index aa1c8d3..00e90c8 100644 --- a/README.md +++ b/README.md @@ -2,59 +2,21 @@ [![REUSE status](https://api.reuse.software/badge/github.com/cap-js/process)](https://api.reuse.software/info/github.com/cap-js/process) -# Setup +## Setup -## Run the sample in the Plugin - -Make sure to follow https://cap.cloud.sap/docs/get-started/ to install global dependencies that are required for CAP application development. - -Install the dependencies for the plugin and build the project: - -``` -npm i -npm run build -``` - -### Running the bookshop example - -Using cds-tsx: - -``` -npm i -g tsx -cd tests/bookshop -npm run build -cds-tsx w -``` - -Using cds watch: - -``` -npm run compile -cd tests/bookshop -npm run build -cds watch -``` - -### Troubleshooting - -- `npm run clean:all` cleans all generated files and rebuilds them -- `npm run clean:build` cleans the build files and rebuilds them -- `npm run clean:types` cleans the generated cds-typer files and rebuilds them - -## To use the plugin as a CAP developer +### Quickstart To add the plugin to your CAP Node.js application, run: ``` -npm run add @cap-js/process +npm add @cap-js/process ``` -### Binding against SBPA instance +That's it — the annotation and programmatic approaches against the generic ProcessService work without any bindings against SBPA. No process import is required to get started. -Binding is not necessary for trying out the plugin locally. -The annotation and programmatic approaches against the generic ProcessService work without any bindings against SBPA. +### Binding against SBPA Instance -Login to Cloud Foundry: +To connect to a real SBPA instance, login to Cloud Foundry: ``` cf login --sso @@ -72,27 +34,101 @@ This will create a `cdsrc-private.json` file containing the credentials. The plugin allows you to import existing SBPA processes as CDS services. To do so, you first need to bind against an existing SBPA instance. Imported processes ensure type safety and enable build-time validation. -Without importing a specific process, the programmatic approach is still possible through the generic ProcessService. -However, build-time validation will not check whether all mandatory inputs required to start a process are provided. ``` cds bind --exec -- cds-tsx import --from process --name ``` -# Annotations +## Annotations Important: For process events defined on the `DELETE` operation, a `before` handler fetches the entity that will be deleted and stores it in `req._Process.[Start|Suspend|Resume|Cancel]` so that it can be used in the `service.after` handler. -## For starting a process +### Starting a Process - `@bpm.process.start` -- Start a process (or classic workflow) after entity creation, update, deletion, read, or any custom action, including all entity elements unless at least one `@bpm.process.start.inputs` entry is given - If no attribute is annotated with `@bpm.process.input`, all attributes of that entity will be fetched and included as process input context. Associations will not be expanded in that case. - `@bpm.process.start.id` -- definition ID for deployed process - `@bpm.process.start.on` - - `@bpm.process.start.if` -- Only start the process if the expression evaluates to true - `@bpm.process.start.inputs` -- Array of input mappings that control which entity fields are passed as process context (optional) - If a `businessKey` is annotated on the entity using `@bpm.process.businessKey`, it will be evaluated at process start. If the length of the business key exceeds SBPA's character limit of 255, the request will be rejected, as process start will fail in that case. +```cds +service MyService { + + @bpm.process.start: { + id: '.', + on: 'CREATE | UPDATE | DELETE | boundAction', + inputs: [ + $self.field1, + { path: $self.field2, as: 'AliasName' }, + $self.items, + $self.items.nestedField + ] + } + entity MyEntity { + key ID : UUID; + field1 : String; + field2 : String; + items : Composition of many ChildEntity on items.parent = $self; + }; + +} +``` + +> See [Input Mapping](#input-mapping) below for detailed examples on controlling which entity fields are passed as process context. + +### Cancelling, Resuming, or Suspending a Process + +- `@bpm.process.` -- Cancel/Suspend/Resume any processes bound to the entity (using the entity key as business key in SBPA) + - `@bpm.process..on` + - `@bpm.process..cascade` -- Boolean (optional, defaults to false) +- For cancelling, resuming, or suspending, it is required to have a business key expression annotated on the entity using `@bpm.process.businessKey`. If no business key is annotated, the request will be rejected. + - Example: `@bpm.process.businessKey: (id || '-' || name)` + +Example: + +```cds +service MyService { + + @bpm.process.: { + on: 'CREATE | UPDATE | DELETE | boundAction', + cascade: true | false // optional, defaults to false + } + @bpm.process.businessKey(myElement || '-' || myElement2) + entity MyProjection as projection on MyEntity { + myElement, + myElement2, + myElement3 + }; + +} + +``` + +### Conditional Execution + +The `.if` annotation is available on all process operations (`start`, `cancel`, `suspend`, `resume`). It accepts a CDS expression and ensures the operation is only triggered when the expression evaluates to true. + +- `@bpm.process.start.if` -- Only start the process if the expression evaluates to true +- `@bpm.process..if` -- Only trigger the action if the expression evaluates to true + +Examples: + +```cds +// Only start the process if the order status is 'approved' +@bpm.process.start: { + id: 'orderProcess', + on: 'UPDATE', + if: (status = 'approved') +} + +// Only suspend the process if weight exceeds 10 +@bpm.process.suspend: { + on: 'UPDATE', + if: (weight > 10) +} +``` + ### Input Mapping The `inputs` array controls which entity fields are passed as context when starting a process. @@ -329,70 +365,13 @@ entity ShipmentItems { }; ``` -### Complete Example - -```cds -service MyService { - - @bpm.process.start: { - id: '.', - on: 'CREATE | UPDATE | DELETE | boundAction', - if: (), - inputs: [ - $self.field1, - { path: $self.field2, as: 'AliasName' }, - $self.items, - $self.items.nestedField - ] - } - entity MyEntity { - key ID : UUID; - field1 : String; - field2 : String; - items : Composition of many ChildEntity on items.parent = $self; - }; - -} -``` - -## For cancelling, resuming, or suspending a process - -- `@bpm.process.` -- Cancel/Suspend/Resume any processes bound to the entity (using the entity key as business key in SBPA) - - `@bpm.process..on` - - `@bpm.process..cascade` -- Boolean (optional, defaults to false) - - `@bpm.process..if` -- Only trigger the action if the expression evaluates to true - - Example: `@bpm.process.suspend.if: (weight > 10)` -- For cancelling, resuming, or suspending, it is required to have a business key expression annotated on the entity using `@bpm.process.businessKey`. If no business key is annotated, the request will be rejected. - - Example: `@bpm.process.businessKey: (id || '-' || name)` - -Example: - -```cds -service MyService { - - @bpm.process.: { - on: 'CREATE | UPDATE | DELETE | boundAction', - cascade: true | false, // optional, defaults to false - when: () - } - @bpm.process.businessKey(myElement || '-' || myElement2) - entity MyProjection as projection on MyEntity { - myElement, - myElement2, - myElement3 - }; - -} - -``` - -# Build-Time Validation +## Build-Time Validation Validation occurs during `cds build` and produces **errors** (hard failures that stop the build) or **warnings** (soft failures that are logged but don't stop the build). -## Process Start +### Process Start -### Required Annotations (Errors) +#### Required Annotations (Errors) - `@bpm.process.start.id` and `@bpm.process.start.on` are mutually required — if one is present, the other must also be present - `@bpm.process.start.id` must be a string @@ -401,12 +380,12 @@ Validation occurs during `cds build` and produces **errors** (hard failures that - A bound action defined on the entity - `@bpm.process.start.if` must be a valid CDS expression (if present) -### Warnings +#### Warnings - Unknown annotations under `@bpm.process.start.*` trigger a warning listing allowed annotations - If no imported process definition is found for the given `id`, a warning is issued as input validation is skipped -### Input Validation (when process definition is found) +#### Input Validation (when process definition is found) When both `@bpm.process.start.id` and `@bpm.process.start.on` are present and the process definition is imported: @@ -423,9 +402,9 @@ When both `@bpm.process.start.id` and `@bpm.process.start.on` are present and th **Note:** Associations and compositions are recursively validated, and cycles in entity associations are detected and reported as errors. -## Process Cancel/Suspend/Resume +### Process Cancel/Suspend/Resume -### Required Annotations (Errors) +#### Required Annotations (Errors) - `@bpm.process..on` is required for cancel/suspend/resume operations and must be a string representing either: - A CRUD operation: `CREATE`, `READ`, `UPDATE`, or `DELETE` @@ -436,11 +415,11 @@ When both `@bpm.process.start.id` and `@bpm.process.start.on` are present and th - Example: `@bpm.process.businessKey: (id || '-' || name)` would concatenate `id` and `name` with a `-` separator as a business key. - The business key definition must match the one configured in the SBPA Process Builder. -### Warnings +#### Warnings - Unknown annotations under `@bpm.process..*` trigger a warning listing allowed annotations -# Programmatic Approach +## Programmatic Approach The plugin provides two ways to interact with SBPA processes programmatically: @@ -449,12 +428,12 @@ The plugin provides two ways to interact with SBPA processes programmatically: Both approaches work locally (in-memory), in hybrid mode (against a real SBPA instance), and in production. -## Generic ProcessService +### Generic ProcessService The generic `ProcessService` is a built-in CDS service that ships with the plugin. It provides low-level events and functions for managing workflow instances without requiring any process imports. This is useful for quick prototyping, dynamic process management, or cases where type safety is not needed. The generic `ProcessService` allows setting the business key to mimic the behavior of the real SBPA workflow. The business key in the header is only used when the application runs locally, so to avoid issues, the business key should be built the same way as in the actual process. -### Service Definition +#### Service Definition The generic `ProcessService` defines the following events and functions: @@ -468,7 +447,7 @@ The generic `ProcessService` defines the following events and functions: | `getOutputs` | function | Retrieve outputs for a specific process instance | | `getInstancesByBusinessKey` | function | Find process instances by business key and optional status filter | -### Usage +#### Usage ```typescript const processService = await cds.connect.to('ProcessService'); @@ -517,15 +496,15 @@ const outputs = await processService.send('getOutputs', { > **Note:** The generic ProcessService uses `emit` for lifecycle events (start, cancel, suspend, resume) which are processed asynchronously through the CDS outbox, and `send` for query functions (getAttributes, getOutputs, getInstancesByBusinessKey) which return data synchronously. > Make sure to check whether the outbox is correctly used. If not, refer to cds.queued to make sure it is used. -## Imported Process Services (Typed) +### Imported Process Services (Typed) For full type safety and build-time validation, you can import a specific SBPA process. This generates a typed CDS service with input/output types derived from the process definition. -### Importing a Service +#### Importing a Service To import a process, you need credentials via `cds bind` and must be logged in to Cloud Foundry. -#### From SBPA (Remote Import) +##### From SBPA (Remote Import) Import your SBPA process directly from the API: @@ -535,7 +514,7 @@ Import your SBPA process directly from the API: cds bind --exec -- cds-tsx import --from process --name eu12.myorg.myproject.myProcess --no-copy ``` -#### From Local JSON File +##### From Local JSON File If you already have a process definition JSON file (e.g., exported or previously fetched), you can generate the CDS model directly from it without needing credentials: @@ -543,7 +522,7 @@ If you already have a process definition JSON file (e.g., exported or previously cds import --from process ./workflows/eu12.bpm-horizon-walkme.sdshipmentprocessor.shipmentHandler.json --no-copy ``` -### What Gets Generated +#### What Gets Generated The import generates: @@ -555,7 +534,7 @@ The import generates: After importing, run `cds-typer` to generate TypeScript types for the imported service. -### Starting a Process +#### Starting a Process ```typescript import ShipmentHandlerService from '#cds-models/eu12/myorg/myproject/ShipmentHandlerService'; @@ -573,7 +552,7 @@ await processService.start({ The `start` action accepts a typed `ProcessInputs` object that matches the process definition's input schema. The plugin validates inputs against the process definition at build time. -### Suspending, Resuming, and Cancelling a Process +#### Suspending, Resuming, and Cancelling a Process ```typescript // Suspend all running instances for a business key @@ -588,7 +567,7 @@ await processService.cancel({ businessKey: 'order-12345', cascade: false }); The `cascade` parameter is optional and defaults to `false`. When set to `true`, child process instances are also affected. -### Querying Process Instances +#### Querying Process Instances ```typescript // Get all instances matching a business key, optionally filtered by status @@ -611,11 +590,48 @@ const outputs = await processService.getOutputs({ Valid status values are: `RUNNING`, `SUSPENDED`, `CANCELLED`, `ERRONEOUS`, `COMPLETED`. If no status filter is provided, all statuses except `CANCELLED` are returned. -### Important +#### Limitations - The typed process service does not currently support local development. - The process import is currently only possible via the command line. +## Running the Sample + +Make sure to follow https://cap.cloud.sap/docs/get-started/ to install global dependencies that are required for CAP application development. + +Install the dependencies for the plugin and build the project: + +``` +npm i +npm run build +``` + +### Running the Bookshop Example + +Using cds-tsx: + +``` +npm i -g tsx +cd tests/bookshop +npm run build +cds-tsx w +``` + +Using cds watch: + +``` +npm run compile +cd tests/bookshop +npm run build +cds watch +``` + +### Troubleshooting + +- `npm run clean:all` cleans all generated files and rebuilds them +- `npm run clean:build` cleans the build files and rebuilds them +- `npm run clean:types` cleans the generated cds-typer files and rebuilds them + ## Support, Feedback, Contributing This project is open to feature requests/suggestions, bug reports, etc. via [GitHub issues](https://github.com/cap-js//issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md). From b7736dc0d226ce2a2a695b0004f35602de17f5d3 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Wed, 25 Mar 2026 15:40:40 +0100 Subject: [PATCH 08/12] small enhancements --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 00e90c8..105e515 100644 --- a/README.md +++ b/README.md @@ -41,14 +41,10 @@ cds bind --exec -- cds-tsx import --from process --name ## Annotations -Important: For process events defined on the `DELETE` operation, a `before` handler fetches the entity that will be deleted and stores it in `req._Process.[Start|Suspend|Resume|Cancel]` so that it can be used in the `service.after` handler. - ### Starting a Process -- `@bpm.process.start` -- Start a process (or classic workflow) after entity creation, update, deletion, read, or any custom action, including all entity elements unless at least one `@bpm.process.start.inputs` entry is given - - If no attribute is annotated with `@bpm.process.input`, all attributes of that entity will be fetched and included as process input context. Associations will not be expanded in that case. - - `@bpm.process.start.id` -- definition ID for deployed process - - `@bpm.process.start.on` +- `@bpm.process.start.id` -- definition ID for deployed process +- `@bpm.process.start.on` -- event on which the process should be started (CRUD operation or custom bound action) - `@bpm.process.start.inputs` -- Array of input mappings that control which entity fields are passed as process context (optional) - If a `businessKey` is annotated on the entity using `@bpm.process.businessKey`, it will be evaluated at process start. If the length of the business key exceeds SBPA's character limit of 255, the request will be rejected, as process start will fail in that case. @@ -79,7 +75,7 @@ service MyService { ### Cancelling, Resuming, or Suspending a Process -- `@bpm.process.` -- Cancel/Suspend/Resume any processes bound to the entity (using the entity key as business key in SBPA) +- `@bpm.process.` -- Cancel/Suspend/Resume any processes with the given businessKey - `@bpm.process..on` - `@bpm.process..cascade` -- Boolean (optional, defaults to false) - For cancelling, resuming, or suspending, it is required to have a business key expression annotated on the entity using `@bpm.process.businessKey`. If no business key is annotated, the request will be rejected. From d6b177c2a1a037922cdc20920cf25266296155c0 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Wed, 25 Mar 2026 15:48:37 +0100 Subject: [PATCH 09/12] table of content --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 105e515..8998294 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,31 @@ [![REUSE status](https://api.reuse.software/badge/github.com/cap-js/process)](https://api.reuse.software/info/github.com/cap-js/process) +## Table of Contents + +- [Setup](#setup) + - [Quickstart](#quickstart) + - [Binding against SBPA Instance](#binding-against-sbpa-instance) + - [Importing Processes as a Service](#importing-processes-as-a-service) +- [Annotations](#annotations) + - [Starting a Process](#starting-a-process) + - [Cancelling, Resuming, or Suspending a Process](#cancelling-resuming-or-suspending-a-process) + - [Conditional Execution](#conditional-execution) + - [Input Mapping](#input-mapping) +- [Build-Time Validation](#build-time-validation) + - [Process Start](#process-start) + - [Process Cancel/Suspend/Resume](#process-cancelsuspendresume) +- [Programmatic Approach](#programmatic-approach) + - [Generic ProcessService](#generic-processservice) + - [Imported Process Services (Typed)](#imported-process-services-typed) +- [Running the Sample](#running-the-sample) + - [Running the Bookshop Example](#running-the-bookshop-example) + - [Troubleshooting](#troubleshooting) +- [Support, Feedback, Contributing](#support-feedback-contributing) +- [Security / Disclosure](#security--disclosure) +- [Code of Conduct](#code-of-conduct) +- [Licensing](#licensing) + ## Setup ### Quickstart From 36d2744ab4ea4ab27f1f84d07e94a9254376d51f Mon Sep 17 00:00:00 2001 From: I569192 Date: Thu, 26 Mar 2026 14:31:52 +0100 Subject: [PATCH 10/12] remove cds bind --exec --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8998294..46bef92 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ The plugin allows you to import existing SBPA processes as CDS services. To do s Imported processes ensure type safety and enable build-time validation. ``` -cds bind --exec -- cds-tsx import --from process --name +cds import --from process --name ``` ## Annotations @@ -529,10 +529,10 @@ To import a process, you need credentials via `cds bind` and must be logged in t Import your SBPA process directly from the API: -**Note:** For remote imports, you must have ProcessService credentials bound (e.g., via `cds bind process -2 `). The plugin will automatically resolve the bindings at import time. +**Note:** For remote imports, you must have ProcessService credentials bound. `cds import --from process` will resolve the credentials. ```bash -cds bind --exec -- cds-tsx import --from process --name eu12.myorg.myproject.myProcess --no-copy +cds import --from process --name eu12.myorg.myproject.myProcess ``` ##### From Local JSON File From a0754b9ffefc0d00f3d85927b28a2aae1d1abca1 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 14:58:48 +0100 Subject: [PATCH 11/12] change from other pr --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 46bef92..d466351 100644 --- a/README.md +++ b/README.md @@ -540,7 +540,7 @@ cds import --from process --name eu12.myorg.myproject.myProcess If you already have a process definition JSON file (e.g., exported or previously fetched), you can generate the CDS model directly from it without needing credentials: ```bash -cds import --from process ./workflows/eu12.bpm-horizon-walkme.sdshipmentprocessor.shipmentHandler.json --no-copy +cds import --from process ./srv/workflows/eu12.myorg.myproject.myProcess.json ``` #### What Gets Generated @@ -551,7 +551,7 @@ The import generates: - Typed `ProcessInputs`, `ProcessOutputs`, `ProcessAttribute`, and `ProcessInstance` types based on the process definition - Typed actions: `start`, `suspend`, `resume`, `cancel` - Typed functions: `getAttributes`, `getOutputs`, `getInstancesByBusinessKey` -- A process definition JSON in `./workflows/` +- A process definition JSON in `./srv/workflows/` After importing, run `cds-typer` to generate TypeScript types for the imported service. From 16aa02121c0ee13a5035345ca96274f2f06fefbb Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 15:53:52 +0100 Subject: [PATCH 12/12] fix --- README.md | 275 +++++++++++++++++++++++------------------------------- 1 file changed, 119 insertions(+), 156 deletions(-) diff --git a/README.md b/README.md index d466351..37b095d 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ - [Cancelling, Resuming, or Suspending a Process](#cancelling-resuming-or-suspending-a-process) - [Conditional Execution](#conditional-execution) - [Input Mapping](#input-mapping) +- [Programmatic Approach](#programmatic-approach) + - [Specific Process Services](#specific-process-services) + - [Generic ProcessService](#generic-processservice) - [Build-Time Validation](#build-time-validation) - [Process Start](#process-start) - [Process Cancel/Suspend/Resume](#process-cancelsuspendresume) -- [Programmatic Approach](#programmatic-approach) - - [Generic ProcessService](#generic-processservice) - - [Imported Process Services (Typed)](#imported-process-services-typed) - [Running the Sample](#running-the-sample) - [Running the Bookshop Example](#running-the-bookshop-example) - [Troubleshooting](#troubleshooting) @@ -39,6 +39,8 @@ npm add @cap-js/process That's it — the annotation and programmatic approaches against the generic ProcessService work without any bindings against SBPA. No process import is required to get started. +You can have a look at the sample in [Status management](./tests/sample/status-management/README.md), or you can jump directly to the documentation of either [Annotations](#annotations) or the [Programmatic Approach](#programmatic-approach). + ### Binding against SBPA Instance To connect to a real SBPA instance, login to Cloud Foundry: @@ -50,11 +52,9 @@ cf login --sso Bind to a ProcessService instance: ``` - cds bind ProcessService -2 +cds bind ProcessService -2 ``` -This will create a `cdsrc-private.json` file containing the credentials. - ### Importing Processes as a Service The plugin allows you to import existing SBPA processes as CDS services. To do so, you first need to bind against an existing SBPA instance. @@ -386,138 +386,16 @@ entity ShipmentItems { }; ``` -## Build-Time Validation - -Validation occurs during `cds build` and produces **errors** (hard failures that stop the build) or **warnings** (soft failures that are logged but don't stop the build). - -### Process Start - -#### Required Annotations (Errors) - -- `@bpm.process.start.id` and `@bpm.process.start.on` are mutually required — if one is present, the other must also be present -- `@bpm.process.start.id` must be a string -- `@bpm.process.start.on` must be a string representing either: - - A CRUD operation: `CREATE`, `READ`, `UPDATE`, or `DELETE` - - A bound action defined on the entity -- `@bpm.process.start.if` must be a valid CDS expression (if present) - -#### Warnings - -- Unknown annotations under `@bpm.process.start.*` trigger a warning listing allowed annotations -- If no imported process definition is found for the given `id`, a warning is issued as input validation is skipped - -#### Input Validation (when process definition is found) - -When both `@bpm.process.start.id` and `@bpm.process.start.on` are present and the process definition is imported: - -**Errors:** - -- Entity attributes specified in `@bpm.process.start.inputs` (or all direct attributes if `inputs` is omitted) must exist in the process definition inputs -- Mandatory inputs from the process definition must be present in the entity - -**Warnings:** - -- Type mismatches between entity attributes and process definition inputs -- Array cardinality mismatches (entity is array but process expects single value or vice versa) -- Mandatory flag mismatches (process input is mandatory but entity attribute is not marked as `@mandatory`) - -**Note:** Associations and compositions are recursively validated, and cycles in entity associations are detected and reported as errors. - -### Process Cancel/Suspend/Resume - -#### Required Annotations (Errors) - -- `@bpm.process..on` is required for cancel/suspend/resume operations and must be a string representing either: - - A CRUD operation: `CREATE`, `READ`, `UPDATE`, or `DELETE` - - A bound action defined on the entity -- `@bpm.process..cascade` is optional (defaults to false); if provided, must be a boolean -- `@bpm.process..if` must be a valid CDS expression (if present) -- If any annotation with `@bpm.process.` is defined, a valid business key expression must be defined using `@bpm.process.businessKey`. - - Example: `@bpm.process.businessKey: (id || '-' || name)` would concatenate `id` and `name` with a `-` separator as a business key. - - The business key definition must match the one configured in the SBPA Process Builder. - -#### Warnings - -- Unknown annotations under `@bpm.process..*` trigger a warning listing allowed annotations - ## Programmatic Approach The plugin provides two ways to interact with SBPA processes programmatically: -1. **Generic ProcessService** -- Use the built-in `ProcessService` directly for untyped, flexible process management without importing a specific process. -2. **Specific / Imported ProcessService** -- Provides the types and sits on top of the generic ProcessService. +1. **Specific ProcessService** -- Provides a process specific abstraction on the process as a CAP service. +2. **Generic ProcessService** -- Provides a generic abstraction on the [SBPA workflow api](https://api.sap.com/api/SPA_Workflow_Runtime/overview) as a CAP service. Both approaches work locally (in-memory), in hybrid mode (against a real SBPA instance), and in production. -### Generic ProcessService - -The generic `ProcessService` is a built-in CDS service that ships with the plugin. It provides low-level events and functions for managing workflow instances without requiring any process imports. This is useful for quick prototyping, dynamic process management, or cases where type safety is not needed. -The generic `ProcessService` allows setting the business key to mimic the behavior of the real SBPA workflow. The business key in the header is only used when the application runs locally, so to avoid issues, the business key should be built the same way as in the actual process. - -#### Service Definition - -The generic `ProcessService` defines the following events and functions: - -| Operation | Type | Description | -| --------------------------- | -------- | ----------------------------------------------------------------- | -| `start` | event | Start a workflow instance with a `definitionId` and `context` | -| `cancel` | event | Cancel all running/suspended instances matching a `businessKey` | -| `suspend` | event | Suspend all running instances matching a `businessKey` | -| `resume` | event | Resume all suspended instances matching a `businessKey` | -| `getAttributes` | function | Retrieve attributes for a specific process instance | -| `getOutputs` | function | Retrieve outputs for a specific process instance | -| `getInstancesByBusinessKey` | function | Find process instances by business key and optional status filter | - -#### Usage - -```typescript -const processService = await cds.connect.to('ProcessService'); - -// Start a process -await processService.emit('start', { - definitionId: 'eu12.myorg.myproject.myProcess', - context: { orderId: '12345', amount: 100.0 }, -}); - -// Cancel all running instances for a business key -await processService.emit('cancel', { - businessKey: 'order-12345', - cascade: false, -}); - -// Suspend running instances -await processService.emit('suspend', { - businessKey: 'order-12345', - cascade: false, -}); - -// Resume suspended instances -await processService.emit('resume', { - businessKey: 'order-12345', - cascade: false, -}); - -// Query instances by business key -const instances = await processService.send('getInstancesByBusinessKey', { - businessKey: 'order-12345', - status: ['RUNNING', 'SUSPENDED'], -}); - -// Get attributes of a specific instance -const attributes = await processService.send('getAttributes', { - processInstanceId: 'instance-uuid', -}); - -// Get outputs of a specific instance -const outputs = await processService.send('getOutputs', { - processInstanceId: 'instance-uuid', -}); -``` - -> **Note:** The generic ProcessService uses `emit` for lifecycle events (start, cancel, suspend, resume) which are processed asynchronously through the CDS outbox, and `send` for query functions (getAttributes, getOutputs, getInstancesByBusinessKey) which return data synchronously. -> Make sure to check whether the outbox is correctly used. If not, refer to cds.queued to make sure it is used. - -### Imported Process Services (Typed) +### Specific Process Services For full type safety and build-time validation, you can import a specific SBPA process. This generates a typed CDS service with input/output types derived from the process definition. @@ -616,42 +494,127 @@ If no status filter is provided, all statuses except `CANCELLED` are returned. - The typed process service does not currently support local development. - The process import is currently only possible via the command line. -## Running the Sample +### Generic ProcessService -Make sure to follow https://cap.cloud.sap/docs/get-started/ to install global dependencies that are required for CAP application development. +The generic `ProcessService` is a built-in CDS service that ships with the plugin. It provides low-level events and functions for managing workflow instances without requiring any process imports. This is useful for quick prototyping, dynamic process management, or cases where type safety is not needed. +The generic `ProcessService` allows setting the business key to mimic the behavior of the real SBPA workflow. The business key in the header is only used when the application runs locally, so to avoid issues, the business key should be built the same way as in the actual process. -Install the dependencies for the plugin and build the project: +#### Service Definition -``` -npm i -npm run build -``` +The generic `ProcessService` defines the following events and functions: -### Running the Bookshop Example +| Operation | Type | Description | +| --------------------------- | -------- | ----------------------------------------------------------------- | +| `start` | event | Start a workflow instance with a `definitionId` and `context` | +| `cancel` | event | Cancel all running/suspended instances matching a `businessKey` | +| `suspend` | event | Suspend all running instances matching a `businessKey` | +| `resume` | event | Resume all suspended instances matching a `businessKey` | +| `getAttributes` | function | Retrieve attributes for a specific process instance | +| `getOutputs` | function | Retrieve outputs for a specific process instance | +| `getInstancesByBusinessKey` | function | Find process instances by business key and optional status filter | -Using cds-tsx: +#### Usage -``` -npm i -g tsx -cd tests/bookshop -npm run build -cds-tsx w -``` +```typescript +const processService = await cds.connect.to('ProcessService'); -Using cds watch: +// Start a process +await processService.emit('start', { + definitionId: 'eu12.myorg.myproject.myProcess', + context: { orderId: '12345', amount: 100.0 }, +}); +// Cancel all running instances for a business key +await processService.emit('cancel', { + businessKey: 'order-12345', + cascade: false, +}); + +// Suspend running instances +await processService.emit('suspend', { + businessKey: 'order-12345', + cascade: false, +}); + +// Resume suspended instances +await processService.emit('resume', { + businessKey: 'order-12345', + cascade: false, +}); + +// Query instances by business key +const instances = await processService.send('getInstancesByBusinessKey', { + businessKey: 'order-12345', + status: ['RUNNING', 'SUSPENDED'], +}); + +// Get attributes of a specific instance +const attributes = await processService.send('getAttributes', { + processInstanceId: 'instance-uuid', +}); + +// Get outputs of a specific instance +const outputs = await processService.send('getOutputs', { + processInstanceId: 'instance-uuid', +}); ``` -npm run compile -cd tests/bookshop -npm run build -cds watch -``` -### Troubleshooting +> **Note:** The generic ProcessService uses `emit` for lifecycle events (start, cancel, suspend, resume) which are processed asynchronously through the CDS outbox, and `send` for query functions (getAttributes, getOutputs, getInstancesByBusinessKey) which return data synchronously. +> Make sure to check whether the outbox is correctly used. If not, refer to cds.queued to make sure it is used. + +## Build-Time Validation + +Validation occurs during `cds build` and produces **errors** (hard failures that stop the build) or **warnings** (soft failures that are logged but don't stop the build). + +### Process Start + +#### Required Annotations (Errors) + +- `@bpm.process.start.id` and `@bpm.process.start.on` are mutually required — if one is present, the other must also be present +- `@bpm.process.start.id` must be a string +- `@bpm.process.start.on` must be a string representing either: + - A CRUD operation: `CREATE`, `READ`, `UPDATE`, or `DELETE` + - A bound action defined on the entity +- `@bpm.process.start.if` must be a valid CDS expression (if present) + +#### Warnings + +- Unknown annotations under `@bpm.process.start.*` trigger a warning listing allowed annotations +- If no imported process definition is found for the given `id`, a warning is issued as input validation is skipped + +#### Input Validation (when process definition is found) + +When both `@bpm.process.start.id` and `@bpm.process.start.on` are present and the process definition is imported: + +**Errors:** + +- Entity attributes specified in `@bpm.process.start.inputs` (or all direct attributes if `inputs` is omitted) must exist in the process definition inputs +- Mandatory inputs from the process definition must be present in the entity + +**Warnings:** + +- Type mismatches between entity attributes and process definition inputs +- Array cardinality mismatches (entity is array but process expects single value or vice versa) +- Mandatory flag mismatches (process input is mandatory but entity attribute is not marked as `@mandatory`) + +**Note:** Associations and compositions are recursively validated, and cycles in entity associations are detected and reported as errors. + +### Process Cancel/Suspend/Resume + +#### Required Annotations (Errors) -- `npm run clean:all` cleans all generated files and rebuilds them -- `npm run clean:build` cleans the build files and rebuilds them -- `npm run clean:types` cleans the generated cds-typer files and rebuilds them +- `@bpm.process..on` is required for cancel/suspend/resume operations and must be a string representing either: + - A CRUD operation: `CREATE`, `READ`, `UPDATE`, or `DELETE` + - A bound action defined on the entity +- `@bpm.process..cascade` is optional (defaults to false); if provided, must be a boolean +- `@bpm.process..if` must be a valid CDS expression (if present) +- If any annotation with `@bpm.process.` is defined, a valid business key expression must be defined using `@bpm.process.businessKey`. + - Example: `@bpm.process.businessKey: (id || '-' || name)` would concatenate `id` and `name` with a `-` separator as a business key. + - The business key definition must match the one configured in the SBPA Process Builder. + +#### Warnings + +- Unknown annotations under `@bpm.process..*` trigger a warning listing allowed annotations ## Support, Feedback, Contributing