@cap-js/process 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +635 -0
  3. package/cds-plugin.js +2 -0
  4. package/dist/cds-plugin.js +4 -0
  5. package/dist/cds-plugin.js.map +1 -0
  6. package/dist/lib/api/index.js +23 -0
  7. package/dist/lib/api/index.js.map +1 -0
  8. package/dist/lib/api/local-workflow-store.js +85 -0
  9. package/dist/lib/api/local-workflow-store.js.map +1 -0
  10. package/dist/lib/api/process-api-client.js +103 -0
  11. package/dist/lib/api/process-api-client.js.map +1 -0
  12. package/dist/lib/api/workflow-client.js +174 -0
  13. package/dist/lib/api/workflow-client.js.map +1 -0
  14. package/dist/lib/auth/credentials.js +17 -0
  15. package/dist/lib/auth/credentials.js.map +1 -0
  16. package/dist/lib/auth/index.js +11 -0
  17. package/dist/lib/auth/index.js.map +1 -0
  18. package/dist/lib/auth/token-cache.js +67 -0
  19. package/dist/lib/auth/token-cache.js.map +1 -0
  20. package/dist/lib/auth/token-provider.js +32 -0
  21. package/dist/lib/auth/token-provider.js.map +1 -0
  22. package/dist/lib/build/constants.js +106 -0
  23. package/dist/lib/build/constants.js.map +1 -0
  24. package/dist/lib/build/index.js +22 -0
  25. package/dist/lib/build/index.js.map +1 -0
  26. package/dist/lib/build/plugin.js +126 -0
  27. package/dist/lib/build/plugin.js.map +1 -0
  28. package/dist/lib/build/validation-utils.js +309 -0
  29. package/dist/lib/build/validation-utils.js.map +1 -0
  30. package/dist/lib/build/validations.js +185 -0
  31. package/dist/lib/build/validations.js.map +1 -0
  32. package/dist/lib/constants.js +78 -0
  33. package/dist/lib/constants.js.map +1 -0
  34. package/dist/lib/handlers/annotationCache.js +47 -0
  35. package/dist/lib/handlers/annotationCache.js.map +1 -0
  36. package/dist/lib/handlers/annotationHandlers.js +57 -0
  37. package/dist/lib/handlers/annotationHandlers.js.map +1 -0
  38. package/dist/lib/handlers/index.js +26 -0
  39. package/dist/lib/handlers/index.js.map +1 -0
  40. package/dist/lib/handlers/onDeleteUtils.js +63 -0
  41. package/dist/lib/handlers/onDeleteUtils.js.map +1 -0
  42. package/dist/lib/handlers/processActionHandler.js +55 -0
  43. package/dist/lib/handlers/processActionHandler.js.map +1 -0
  44. package/dist/lib/handlers/processCancel.js +28 -0
  45. package/dist/lib/handlers/processCancel.js.map +1 -0
  46. package/dist/lib/handlers/processResume.js +28 -0
  47. package/dist/lib/handlers/processResume.js.map +1 -0
  48. package/dist/lib/handlers/processService.js +125 -0
  49. package/dist/lib/handlers/processService.js.map +1 -0
  50. package/dist/lib/handlers/processStart.js +156 -0
  51. package/dist/lib/handlers/processStart.js.map +1 -0
  52. package/dist/lib/handlers/processSuspend.js +28 -0
  53. package/dist/lib/handlers/processSuspend.js.map +1 -0
  54. package/dist/lib/handlers/utils.js +124 -0
  55. package/dist/lib/handlers/utils.js.map +1 -0
  56. package/dist/lib/index.js +43 -0
  57. package/dist/lib/index.js.map +1 -0
  58. package/dist/lib/processImport.js +449 -0
  59. package/dist/lib/processImport.js.map +1 -0
  60. package/dist/lib/processImportRegistration.js +27 -0
  61. package/dist/lib/processImportRegistration.js.map +1 -0
  62. package/dist/lib/shared/businessKey-helper.js +27 -0
  63. package/dist/lib/shared/businessKey-helper.js.map +1 -0
  64. package/dist/lib/shared/input-parser.js +650 -0
  65. package/dist/lib/shared/input-parser.js.map +1 -0
  66. package/dist/lib/types/csn-extensions.js +37 -0
  67. package/dist/lib/types/csn-extensions.js.map +1 -0
  68. package/dist/srv/BTPProcessService.js +117 -0
  69. package/dist/srv/BTPProcessService.js.map +1 -0
  70. package/dist/srv/localProcessService.js +152 -0
  71. package/dist/srv/localProcessService.js.map +1 -0
  72. package/package.json +98 -1
  73. package/srv/BTPProcessService.cds +42 -0
  74. package/srv/BTPProcessService.js +2 -0
  75. package/srv/localProcessService.cds +3 -0
  76. package/srv/localProcessService.js +2 -0
package/README.md ADDED
@@ -0,0 +1,635 @@
1
+ # CAP - Process Plugin
2
+
3
+ [![REUSE status](https://api.reuse.software/badge/github.com/cap-js/process)](https://api.reuse.software/info/github.com/cap-js/process)
4
+
5
+ CAP Plugin to interact with SAP Build Process Automation to manage processes.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Setup](#setup)
10
+ - [Quickstart](#quickstart)
11
+ - [Binding against SBPA Instance](#binding-against-sbpa-instance)
12
+ - [Importing Processes as a Service](#importing-processes-as-a-service)
13
+ - [Annotations](#annotations)
14
+ - [Starting a Process](#starting-a-process)
15
+ - [Cancelling, Resuming, or Suspending a Process](#cancelling-resuming-or-suspending-a-process)
16
+ - [Conditional Execution](#conditional-execution)
17
+ - [Input Mapping](#input-mapping)
18
+ - [Programmatic Approach](#programmatic-approach)
19
+ - [Specific Process Services](#specific-process-services)
20
+ - [Generic ProcessService](#generic-processservice)
21
+ - [Build-Time Validation](#build-time-validation)
22
+ - [Process Start](#process-start)
23
+ - [Process Cancel/Suspend/Resume](#process-cancelsuspendresume)
24
+ - [Running the Sample](#running-the-sample)
25
+ - [Running the Bookshop Example](#running-the-bookshop-example)
26
+ - [Troubleshooting](#troubleshooting)
27
+ - [Support, Feedback, Contributing](#support-feedback-contributing)
28
+ - [Security / Disclosure](#security--disclosure)
29
+ - [Code of Conduct](#code-of-conduct)
30
+ - [Licensing](#licensing)
31
+
32
+ ## Setup
33
+
34
+ ### Quickstart
35
+
36
+ To add the plugin to your CAP Node.js application, run:
37
+
38
+ ```
39
+ npm add @cap-js/process
40
+ ```
41
+
42
+ 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.
43
+
44
+ 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).
45
+
46
+ ### Binding against SBPA Instance
47
+
48
+ To connect to a real SBPA instance, login to Cloud Foundry:
49
+
50
+ ```
51
+ cf login --sso
52
+ ```
53
+
54
+ Bind to a ProcessService instance:
55
+
56
+ ```
57
+ cds bind ProcessService -2 <sbpa-service-instance>
58
+ ```
59
+
60
+ ### Importing Processes as a Service
61
+
62
+ 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.
63
+ Imported processes ensure type safety and enable build-time validation.
64
+
65
+ ```
66
+ cds import --from process --name <Process_ID>
67
+ ```
68
+
69
+ ## Annotations
70
+
71
+ ### Starting a Process
72
+
73
+ - `@bpm.process.start.id` -- definition ID for deployed process
74
+ - `@bpm.process.start.on` -- event on which the process should be started (CRUD operation or custom bound action)
75
+ - `@bpm.process.start.inputs` -- Array of input mappings that control which entity fields are passed as process context (optional)
76
+ - 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.
77
+
78
+ ```cds
79
+ service MyService {
80
+
81
+ @bpm.process.start: {
82
+ id: '<projectId>.<processId>',
83
+ on: 'CREATE | UPDATE | DELETE | boundAction',
84
+ inputs: [
85
+ $self.field1,
86
+ { path: $self.field2, as: 'AliasName' },
87
+ $self.items,
88
+ $self.items.nestedField
89
+ ]
90
+ }
91
+ entity MyEntity {
92
+ key ID : UUID;
93
+ field1 : String;
94
+ field2 : String;
95
+ items : Composition of many ChildEntity on items.parent = $self;
96
+ };
97
+
98
+ }
99
+ ```
100
+
101
+ > See [Input Mapping](#input-mapping) below for detailed examples on controlling which entity fields are passed as process context.
102
+
103
+ ### Cancelling, Resuming, or Suspending a Process
104
+
105
+ - `@bpm.process.<cancel|resume|suspend>` -- Cancel/Suspend/Resume any processes with the given businessKey
106
+ - `@bpm.process.<cancel|resume|suspend>.on`
107
+ - `@bpm.process.<cancel|resume|suspend>.cascade` -- Boolean (optional, defaults to false)
108
+ - 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.
109
+ - Example: `@bpm.process.businessKey: (id || '-' || name)`
110
+
111
+ Example:
112
+
113
+ ```cds
114
+ service MyService {
115
+
116
+ @bpm.process.<cancel|suspend|resume>: {
117
+ on: 'CREATE | UPDATE | DELETE | boundAction',
118
+ cascade: true | false // optional, defaults to false
119
+ }
120
+ @bpm.process.businessKey(myElement || '-' || myElement2)
121
+ entity MyProjection as projection on MyEntity {
122
+ myElement,
123
+ myElement2,
124
+ myElement3
125
+ };
126
+
127
+ }
128
+
129
+ ```
130
+
131
+ ### Conditional Execution
132
+
133
+ 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.
134
+
135
+ - `@bpm.process.start.if` -- Only start the process if the expression evaluates to true
136
+ - `@bpm.process.<cancel|resume|suspend>.if` -- Only trigger the action if the expression evaluates to true
137
+
138
+ Examples:
139
+
140
+ ```cds
141
+ // Only start the process if the order status is 'approved'
142
+ @bpm.process.start: {
143
+ id: 'orderProcess',
144
+ on: 'UPDATE',
145
+ if: (status = 'approved')
146
+ }
147
+
148
+ // Only suspend the process if weight exceeds 10
149
+ @bpm.process.suspend: {
150
+ on: 'UPDATE',
151
+ if: (weight > 10)
152
+ }
153
+ ```
154
+
155
+ ### Input Mapping
156
+
157
+ The `inputs` array controls which entity fields are passed as context when starting a process.
158
+
159
+ #### No `inputs` Array (Default Behavior)
160
+
161
+ When `inputs` is not specified, **all direct attributes** of the entity are fetched and passed as process context. Associations and compositions are **not expanded** - only scalar fields are included.
162
+
163
+ ```cds
164
+ @bpm.process.start: {
165
+ id: 'orderProcess',
166
+ on: 'CREATE'
167
+ }
168
+ entity Orders {
169
+ key ID : UUID;
170
+ status : String(20);
171
+ total : Decimal(15, 2);
172
+ items : Composition of many OrderItems on items.order = $self;
173
+ };
174
+ // Context: { ID, status, total, businesskey }
175
+ // Note: 'items' composition is NOT included
176
+ ```
177
+
178
+ #### Simple Field Selection
179
+
180
+ Use `$self.fieldName` to include specific fields.
181
+
182
+ ```cds
183
+ @bpm.process.start: {
184
+ id: 'orderProcess',
185
+ on: 'CREATE',
186
+ inputs: [
187
+ $self.ID,
188
+ $self.status
189
+ ]
190
+ }
191
+ entity Orders {
192
+ key ID : UUID;
193
+ status : String(20);
194
+ total : Decimal(15, 2); // Not included
195
+ };
196
+ // Context: { ID, status, businesskey }
197
+ ```
198
+
199
+ #### Wildcard: All Scalar Fields (`$self`)
200
+
201
+ Use `$self` alone (without a field name) to include **all scalar fields** of the entity. This is useful when you want all entity fields plus specific compositions.
202
+
203
+ ```cds
204
+ @bpm.process.start: {
205
+ id: 'orderProcess',
206
+ on: 'CREATE',
207
+ inputs: [
208
+ $self, // All scalar fields: ID, status, shipmentDate, totalValue
209
+ $self.items // Plus the composition with all its scalar fields
210
+ ]
211
+ }
212
+ entity Orders {
213
+ key ID : UUID;
214
+ status : String(20);
215
+ shipmentDate : Date;
216
+ totalValue : Decimal(15, 2);
217
+ items : Composition of many OrderItems on items.parent = $self;
218
+ };
219
+ // Context: { ID, status, shipmentDate, totalValue, businesskey, items: [{ ID, title, quantity, parent_ID }, ...] }
220
+ ```
221
+
222
+ **Note:** `$self` alone behaves identically to the default behavior (no `inputs` array), but allows you to combine it with explicit composition expansions in the same inputs array.
223
+
224
+ #### Field Aliasing
225
+
226
+ Use `{ path: $self.fieldName, as: 'TargetName' }` to rename fields for the process.
227
+
228
+ ```cds
229
+ @bpm.process.start: {
230
+ id: 'orderProcess',
231
+ on: 'CREATE',
232
+ inputs: [
233
+ $self.ID,
234
+ { path: $self.total, as: 'OrderAmount' }
235
+ ]
236
+ }
237
+ entity Orders {
238
+ key ID : UUID;
239
+ total : Decimal(15, 2);
240
+ };
241
+ // Context: { ID, OrderAmount, businesskey }
242
+ ```
243
+
244
+ #### Compositions and Associations
245
+
246
+ **Include composition without child field selection (`$self.items`):**
247
+
248
+ When you include a composition without specifying any nested fields (e.g., `$self.items` alone), **all direct attributes** of the child entity are expanded. This behaves like the default behavior but for the nested entity.
249
+
250
+ ```cds
251
+ @bpm.process.start: {
252
+ id: 'orderProcess',
253
+ on: 'CREATE',
254
+ inputs: [
255
+ $self.ID,
256
+ $self.items // Expands all direct attributes of OrderItems
257
+ ]
258
+ }
259
+ entity Orders {
260
+ key ID : UUID;
261
+ items : Composition of many OrderItems on items.order = $self;
262
+ };
263
+
264
+ entity OrderItems {
265
+ key ID : UUID;
266
+ order : Association to Orders;
267
+ product : String(200);
268
+ quantity : Integer;
269
+ };
270
+ // Context: { ID, businesskey, items: [{ ID, product, quantity }, ...] }
271
+ // Note: 'order' association in child is NOT included (associations not expanded)
272
+ ```
273
+
274
+ **Include composition with selected child fields (`$self.items.field`):**
275
+
276
+ When you specify nested field paths like `$self.items.ID` or `$self.items.product`, only those specific fields are included from the child entity.
277
+
278
+ ```cds
279
+ @bpm.process.start: {
280
+ id: 'orderProcess',
281
+ on: 'CREATE',
282
+ inputs: [
283
+ $self.ID,
284
+ $self.items.ID,
285
+ $self.items.product
286
+ // quantity is NOT included
287
+ ]
288
+ }
289
+ entity Orders {
290
+ key ID : UUID;
291
+ items : Composition of many OrderItems on items.order = $self;
292
+ };
293
+
294
+ entity OrderItems {
295
+ key ID : UUID;
296
+ order : Association to Orders;
297
+ product : String(200);
298
+ quantity : Integer;
299
+ };
300
+ // Context: { ID, businesskey, items: [{ ID, product }, ...] }
301
+ ```
302
+
303
+ **Alias composition and nested fields:**
304
+
305
+ ```cds
306
+ @bpm.process.start: {
307
+ id: 'orderProcess',
308
+ on: 'CREATE',
309
+ inputs: [
310
+ $self.ID,
311
+ { path: $self.items, as: 'OrderLines' },
312
+ $self.items.ID,
313
+ { path: $self.items.product, as: 'ProductName' }
314
+ ]
315
+ }
316
+ entity Orders {
317
+ key ID : UUID;
318
+ items : Composition of many OrderItems on items.order = $self;
319
+ };
320
+
321
+ entity OrderItems {
322
+ key ID : UUID;
323
+ product : String(200);
324
+ };
325
+ // Context: { ID, businesskey, OrderLines: [{ ID, ProductName }, ...] }
326
+ ```
327
+
328
+ **Combining wildcards with aliases:**
329
+
330
+ You can combine wildcard expansion (`$self` or `$self.items`) with specific field aliases. The wildcard expands all fields, and the alias adds the field again with the new name.
331
+
332
+ ```cds
333
+ @bpm.process.start: {
334
+ id: 'orderProcess',
335
+ on: 'CREATE',
336
+ inputs: [
337
+ $self, // All scalar fields: ID, status, total
338
+ { path: $self.ID, as: 'OrderId' }, // Add ID again as 'OrderId'
339
+ $self.items, // All child fields: ID, product, quantity
340
+ { path: $self.items.ID, as: 'ItemId' } // Add items.ID again as 'ItemId'
341
+ ]
342
+ }
343
+ entity Orders {
344
+ key ID : UUID;
345
+ status : String(20);
346
+ total : Decimal(15, 2);
347
+ items : Composition of many OrderItems on items.order = $self;
348
+ };
349
+
350
+ entity OrderItems {
351
+ key ID : UUID;
352
+ product : String(200);
353
+ quantity : Integer;
354
+ };
355
+ // Context: {
356
+ // ID, OrderId, // ID appears twice (original + alias)
357
+ // status, total, businesskey,
358
+ // items: [{
359
+ // ID, ItemId, // ID appears twice in each item
360
+ // product, quantity
361
+ // }, ...]
362
+ // }
363
+ ```
364
+
365
+ #### Deep Paths (Cyclic Relationships)
366
+
367
+ For entities with cyclic relationships, explicit deep paths let you control exactly how deep to traverse without infinite loops.
368
+
369
+ ```cds
370
+ @bpm.process.start: {
371
+ id: 'shipmentProcess',
372
+ on: 'CREATE',
373
+ inputs: [
374
+ $self.ID,
375
+ $self.items.ID,
376
+ $self.items.shipment.ID, // Back to parent
377
+ $self.items.shipment.items.ID // Back to items again
378
+ ]
379
+ }
380
+ entity Shipments {
381
+ key ID : UUID;
382
+ items : Composition of many ShipmentItems on items.shipment = $self;
383
+ };
384
+
385
+ entity ShipmentItems {
386
+ key ID : UUID;
387
+ shipment : Association to Shipments;
388
+ };
389
+ ```
390
+
391
+ ## Programmatic Approach
392
+
393
+ The plugin provides two ways to interact with SBPA processes programmatically:
394
+
395
+ 1. **Specific ProcessService** -- Provides a process specific abstraction on the process as a CAP service.
396
+ 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.
397
+
398
+ Both approaches work locally (in-memory), in hybrid mode (against a real SBPA instance), and in production.
399
+
400
+ ### Specific Process Services
401
+
402
+ 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.
403
+
404
+ #### Importing a Service
405
+
406
+ To import a process, you need credentials via `cds bind` and must be logged in to Cloud Foundry.
407
+
408
+ ##### From SBPA (Remote Import)
409
+
410
+ Import your SBPA process directly from the API:
411
+
412
+ **Note:** For remote imports, you must have ProcessService credentials bound. `cds import --from process` will resolve the credentials.
413
+
414
+ ```bash
415
+ cds import --from process --name eu12.myorg.myproject.myProcess
416
+ ```
417
+
418
+ ##### From Local JSON File
419
+
420
+ 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:
421
+
422
+ ```bash
423
+ cds import --from process ./srv/workflows/eu12.myorg.myproject.myProcess.json
424
+ ```
425
+
426
+ #### What Gets Generated
427
+
428
+ The import generates:
429
+
430
+ - A CDS service definition in `./srv/external/` (annotated with `@bpm.process` and `@protocol: 'none'`)
431
+ - Typed `ProcessInputs`, `ProcessOutputs`, `ProcessAttribute`, and `ProcessInstance` types based on the process definition
432
+ - Typed actions: `start`, `suspend`, `resume`, `cancel`
433
+ - Typed functions: `getAttributes`, `getOutputs`, `getInstancesByBusinessKey`
434
+ - A process definition JSON in `./srv/workflows/`
435
+
436
+ After importing, run `cds-typer` to generate TypeScript types for the imported service.
437
+
438
+ #### Starting a Process
439
+
440
+ ```typescript
441
+ import ShipmentHandlerService from '#cds-models/eu12/myorg/myproject/ShipmentHandlerService';
442
+
443
+ const processService = await cds.connect.to(ShipmentHandlerService);
444
+
445
+ await processService.start({
446
+ businesskey: 'order-12345',
447
+ startingShipment: {
448
+ identifier: 'shipment_001',
449
+ items: [{ identifier: 'item_1', title: 'Laptop', quantity: 1, price: 1200.0 }],
450
+ },
451
+ });
452
+ ```
453
+
454
+ 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.
455
+
456
+ #### Suspending, Resuming, and Cancelling a Process
457
+
458
+ ```typescript
459
+ // Suspend all running instances for a business key
460
+ await processService.suspend({ businessKey: 'order-12345', cascade: false });
461
+
462
+ // Resume all suspended instances for a business key
463
+ await processService.resume({ businessKey: 'order-12345', cascade: false });
464
+
465
+ // Cancel all running/suspended instances for a business key
466
+ await processService.cancel({ businessKey: 'order-12345', cascade: false });
467
+ ```
468
+
469
+ The `cascade` parameter is optional and defaults to `false`. When set to `true`, child process instances are also affected.
470
+
471
+ #### Querying Process Instances
472
+
473
+ ```typescript
474
+ // Get all instances matching a business key, optionally filtered by status
475
+ const instances = await processService.getInstancesByBusinessKey({
476
+ businessKey: 'order-12345',
477
+ status: ['RUNNING', 'SUSPENDED'],
478
+ });
479
+
480
+ // Get attributes for a specific process instance
481
+ const attributes = await processService.getAttributes({
482
+ processInstanceId: 'instance-uuid',
483
+ });
484
+
485
+ // Get outputs for a specific process instance
486
+ const outputs = await processService.getOutputs({
487
+ processInstanceId: 'instance-uuid',
488
+ });
489
+ ```
490
+
491
+ Valid status values are: `RUNNING`, `SUSPENDED`, `CANCELLED`, `ERRONEOUS`, `COMPLETED`.
492
+ If no status filter is provided, all statuses except `CANCELLED` are returned.
493
+
494
+ #### Limitations
495
+
496
+ - The typed process service does not currently support local development.
497
+ - The process import is currently only possible via the command line.
498
+
499
+ ### Generic ProcessService
500
+
501
+ 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.
502
+ 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.
503
+
504
+ #### Service Definition
505
+
506
+ The generic `ProcessService` defines the following events and functions:
507
+
508
+ | Operation | Type | Description |
509
+ | --------------------------- | -------- | ----------------------------------------------------------------- |
510
+ | `start` | event | Start a workflow instance with a `definitionId` and `context` |
511
+ | `cancel` | event | Cancel all running/suspended instances matching a `businessKey` |
512
+ | `suspend` | event | Suspend all running instances matching a `businessKey` |
513
+ | `resume` | event | Resume all suspended instances matching a `businessKey` |
514
+ | `getAttributes` | function | Retrieve attributes for a specific process instance |
515
+ | `getOutputs` | function | Retrieve outputs for a specific process instance |
516
+ | `getInstancesByBusinessKey` | function | Find process instances by business key and optional status filter |
517
+
518
+ #### Usage
519
+
520
+ ```typescript
521
+ const processService = await cds.connect.to('ProcessService');
522
+
523
+ // Start a process
524
+ await processService.emit('start', {
525
+ definitionId: 'eu12.myorg.myproject.myProcess',
526
+ context: { orderId: '12345', amount: 100.0 },
527
+ });
528
+
529
+ // Cancel all running instances for a business key
530
+ await processService.emit('cancel', {
531
+ businessKey: 'order-12345',
532
+ cascade: false,
533
+ });
534
+
535
+ // Suspend running instances
536
+ await processService.emit('suspend', {
537
+ businessKey: 'order-12345',
538
+ cascade: false,
539
+ });
540
+
541
+ // Resume suspended instances
542
+ await processService.emit('resume', {
543
+ businessKey: 'order-12345',
544
+ cascade: false,
545
+ });
546
+
547
+ // Query instances by business key
548
+ const instances = await processService.send('getInstancesByBusinessKey', {
549
+ businessKey: 'order-12345',
550
+ status: ['RUNNING', 'SUSPENDED'],
551
+ });
552
+
553
+ // Get attributes of a specific instance
554
+ const attributes = await processService.send('getAttributes', {
555
+ processInstanceId: 'instance-uuid',
556
+ });
557
+
558
+ // Get outputs of a specific instance
559
+ const outputs = await processService.send('getOutputs', {
560
+ processInstanceId: 'instance-uuid',
561
+ });
562
+ ```
563
+
564
+ > **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.
565
+ > Make sure to check whether the outbox is correctly used. If not, refer to cds.queued to make sure it is used.
566
+
567
+ ## Build-Time Validation
568
+
569
+ 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).
570
+
571
+ ### Process Start
572
+
573
+ #### Required Annotations (Errors)
574
+
575
+ - `@bpm.process.start.id` and `@bpm.process.start.on` are mutually required — if one is present, the other must also be present
576
+ - `@bpm.process.start.id` must be a string
577
+ - `@bpm.process.start.on` must be a string representing either:
578
+ - A CRUD operation: `CREATE`, `READ`, `UPDATE`, or `DELETE`
579
+ - A bound action defined on the entity
580
+ - `@bpm.process.start.if` must be a valid CDS expression (if present)
581
+
582
+ #### Warnings
583
+
584
+ - Unknown annotations under `@bpm.process.start.*` trigger a warning listing allowed annotations
585
+ - If no imported process definition is found for the given `id`, a warning is issued as input validation is skipped
586
+
587
+ #### Input Validation (when process definition is found)
588
+
589
+ When both `@bpm.process.start.id` and `@bpm.process.start.on` are present and the process definition is imported:
590
+
591
+ **Errors:**
592
+
593
+ - Entity attributes specified in `@bpm.process.start.inputs` (or all direct attributes if `inputs` is omitted) must exist in the process definition inputs
594
+ - Mandatory inputs from the process definition must be present in the entity
595
+
596
+ **Warnings:**
597
+
598
+ - Type mismatches between entity attributes and process definition inputs
599
+ - Array cardinality mismatches (entity is array but process expects single value or vice versa)
600
+ - Mandatory flag mismatches (process input is mandatory but entity attribute is not marked as `@mandatory`)
601
+
602
+ **Note:** Associations and compositions are recursively validated, and cycles in entity associations are detected and reported as errors.
603
+
604
+ ### Process Cancel/Suspend/Resume
605
+
606
+ #### Required Annotations (Errors)
607
+
608
+ - `@bpm.process.<cancel|suspend|resume>.on` is required for cancel/suspend/resume operations and must be a string representing either:
609
+ - A CRUD operation: `CREATE`, `READ`, `UPDATE`, or `DELETE`
610
+ - A bound action defined on the entity
611
+ - `@bpm.process.<cancel|suspend|resume>.cascade` is optional (defaults to false); if provided, must be a boolean
612
+ - `@bpm.process.<cancel|suspend|resume>.if` must be a valid CDS expression (if present)
613
+ - If any annotation with `@bpm.process.<cancel|suspend|resume>` is defined, a valid business key expression must be defined using `@bpm.process.businessKey`.
614
+ - Example: `@bpm.process.businessKey: (id || '-' || name)` would concatenate `id` and `name` with a `-` separator as a business key.
615
+ - The business key definition must match the one configured in the SBPA Process Builder.
616
+
617
+ #### Warnings
618
+
619
+ - Unknown annotations under `@bpm.process.<cancel|suspend|resume>.*` trigger a warning listing allowed annotations
620
+
621
+ ## Support, Feedback, Contributing
622
+
623
+ This project is open to feature requests/suggestions, bug reports, etc. via [GitHub issues](https://github.com/cap-js/<your-project>/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).
624
+
625
+ ## Security / Disclosure
626
+
627
+ If you find any bug that may be a security problem, please follow the instructions in our [security policy](https://github.com/cap-js/<your-project>/security/policy) on how to report it. Please do not create GitHub issues for security-related doubts or problems.
628
+
629
+ ## Code of Conduct
630
+
631
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](https://github.com/cap-js/.github/blob/main/CODE_OF_CONDUCT.md) at all times.
632
+
633
+ ## Licensing
634
+
635
+ Copyright (20xx-)20xx SAP SE or an SAP affiliate company and <your-project> contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js/<your-project>).
package/cds-plugin.js ADDED
@@ -0,0 +1,2 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
2
+ module.exports = require('./dist/cds-plugin');
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./lib");
4
+ //# sourceMappingURL=cds-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cds-plugin.js","sourceRoot":"","sources":["../cds-plugin.ts"],"names":[],"mappings":";;AAAA,iBAAe"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ // API module - centralized API clients for SBPA services
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.localWorkflowStore = exports.LocalWorkflowStore = exports.updateMultipleWorkflowStatus = exports.updateWorkflowStatus = exports.getWorkflowsByBusinessKey = exports.startWorkflow = exports.createWorkflowInstanceClient = exports.WorkflowStatus = exports.fetchAllDataTypes = exports.fetchArtifact = exports.fetchProcessHeader = exports.createProcessApiClient = void 0;
5
+ // Process API Client - for artifact/process definition fetching
6
+ var process_api_client_1 = require("./process-api-client");
7
+ Object.defineProperty(exports, "createProcessApiClient", { enumerable: true, get: function () { return process_api_client_1.createProcessApiClient; } });
8
+ Object.defineProperty(exports, "fetchProcessHeader", { enumerable: true, get: function () { return process_api_client_1.fetchProcessHeader; } });
9
+ Object.defineProperty(exports, "fetchArtifact", { enumerable: true, get: function () { return process_api_client_1.fetchArtifact; } });
10
+ Object.defineProperty(exports, "fetchAllDataTypes", { enumerable: true, get: function () { return process_api_client_1.fetchAllDataTypes; } });
11
+ // Workflow Instance Client - for workflow instance operations
12
+ var workflow_client_1 = require("./workflow-client");
13
+ Object.defineProperty(exports, "WorkflowStatus", { enumerable: true, get: function () { return workflow_client_1.WorkflowStatus; } });
14
+ Object.defineProperty(exports, "createWorkflowInstanceClient", { enumerable: true, get: function () { return workflow_client_1.createWorkflowInstanceClient; } });
15
+ Object.defineProperty(exports, "startWorkflow", { enumerable: true, get: function () { return workflow_client_1.startWorkflow; } });
16
+ Object.defineProperty(exports, "getWorkflowsByBusinessKey", { enumerable: true, get: function () { return workflow_client_1.getWorkflowsByBusinessKey; } });
17
+ Object.defineProperty(exports, "updateWorkflowStatus", { enumerable: true, get: function () { return workflow_client_1.updateWorkflowStatus; } });
18
+ Object.defineProperty(exports, "updateMultipleWorkflowStatus", { enumerable: true, get: function () { return workflow_client_1.updateMultipleWorkflowStatus; } });
19
+ // Local Workflow Store - for local development
20
+ var local_workflow_store_1 = require("./local-workflow-store");
21
+ Object.defineProperty(exports, "LocalWorkflowStore", { enumerable: true, get: function () { return local_workflow_store_1.LocalWorkflowStore; } });
22
+ Object.defineProperty(exports, "localWorkflowStore", { enumerable: true, get: function () { return local_workflow_store_1.localWorkflowStore; } });
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/api/index.ts"],"names":[],"mappings":";AAAA,yDAAyD;;;AAEzD,gEAAgE;AAChE,2DAU8B;AAJ5B,4HAAA,sBAAsB,OAAA;AACtB,wHAAA,kBAAkB,OAAA;AAClB,mHAAA,aAAa,OAAA;AACb,uHAAA,iBAAiB,OAAA;AAGnB,8DAA8D;AAC9D,qDAW2B;AARzB,iHAAA,cAAc,OAAA;AAGd,+HAAA,4BAA4B,OAAA;AAC5B,gHAAA,aAAa,OAAA;AACb,4HAAA,yBAAyB,OAAA;AACzB,uHAAA,oBAAoB,OAAA;AACpB,+HAAA,4BAA4B,OAAA;AAG9B,+CAA+C;AAC/C,+DAIgC;AAH9B,0HAAA,kBAAkB,OAAA;AAElB,0HAAA,kBAAkB,OAAA"}