@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.
- package/LICENSE +201 -0
- package/README.md +635 -0
- package/cds-plugin.js +2 -0
- package/dist/cds-plugin.js +4 -0
- package/dist/cds-plugin.js.map +1 -0
- package/dist/lib/api/index.js +23 -0
- package/dist/lib/api/index.js.map +1 -0
- package/dist/lib/api/local-workflow-store.js +85 -0
- package/dist/lib/api/local-workflow-store.js.map +1 -0
- package/dist/lib/api/process-api-client.js +103 -0
- package/dist/lib/api/process-api-client.js.map +1 -0
- package/dist/lib/api/workflow-client.js +174 -0
- package/dist/lib/api/workflow-client.js.map +1 -0
- package/dist/lib/auth/credentials.js +17 -0
- package/dist/lib/auth/credentials.js.map +1 -0
- package/dist/lib/auth/index.js +11 -0
- package/dist/lib/auth/index.js.map +1 -0
- package/dist/lib/auth/token-cache.js +67 -0
- package/dist/lib/auth/token-cache.js.map +1 -0
- package/dist/lib/auth/token-provider.js +32 -0
- package/dist/lib/auth/token-provider.js.map +1 -0
- package/dist/lib/build/constants.js +106 -0
- package/dist/lib/build/constants.js.map +1 -0
- package/dist/lib/build/index.js +22 -0
- package/dist/lib/build/index.js.map +1 -0
- package/dist/lib/build/plugin.js +126 -0
- package/dist/lib/build/plugin.js.map +1 -0
- package/dist/lib/build/validation-utils.js +309 -0
- package/dist/lib/build/validation-utils.js.map +1 -0
- package/dist/lib/build/validations.js +185 -0
- package/dist/lib/build/validations.js.map +1 -0
- package/dist/lib/constants.js +78 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/handlers/annotationCache.js +47 -0
- package/dist/lib/handlers/annotationCache.js.map +1 -0
- package/dist/lib/handlers/annotationHandlers.js +57 -0
- package/dist/lib/handlers/annotationHandlers.js.map +1 -0
- package/dist/lib/handlers/index.js +26 -0
- package/dist/lib/handlers/index.js.map +1 -0
- package/dist/lib/handlers/onDeleteUtils.js +63 -0
- package/dist/lib/handlers/onDeleteUtils.js.map +1 -0
- package/dist/lib/handlers/processActionHandler.js +55 -0
- package/dist/lib/handlers/processActionHandler.js.map +1 -0
- package/dist/lib/handlers/processCancel.js +28 -0
- package/dist/lib/handlers/processCancel.js.map +1 -0
- package/dist/lib/handlers/processResume.js +28 -0
- package/dist/lib/handlers/processResume.js.map +1 -0
- package/dist/lib/handlers/processService.js +125 -0
- package/dist/lib/handlers/processService.js.map +1 -0
- package/dist/lib/handlers/processStart.js +156 -0
- package/dist/lib/handlers/processStart.js.map +1 -0
- package/dist/lib/handlers/processSuspend.js +28 -0
- package/dist/lib/handlers/processSuspend.js.map +1 -0
- package/dist/lib/handlers/utils.js +124 -0
- package/dist/lib/handlers/utils.js.map +1 -0
- package/dist/lib/index.js +43 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/processImport.js +449 -0
- package/dist/lib/processImport.js.map +1 -0
- package/dist/lib/processImportRegistration.js +27 -0
- package/dist/lib/processImportRegistration.js.map +1 -0
- package/dist/lib/shared/businessKey-helper.js +27 -0
- package/dist/lib/shared/businessKey-helper.js.map +1 -0
- package/dist/lib/shared/input-parser.js +650 -0
- package/dist/lib/shared/input-parser.js.map +1 -0
- package/dist/lib/types/csn-extensions.js +37 -0
- package/dist/lib/types/csn-extensions.js.map +1 -0
- package/dist/srv/BTPProcessService.js +117 -0
- package/dist/srv/BTPProcessService.js.map +1 -0
- package/dist/srv/localProcessService.js +152 -0
- package/dist/srv/localProcessService.js.map +1 -0
- package/package.json +98 -1
- package/srv/BTPProcessService.cds +42 -0
- package/srv/BTPProcessService.js +2 -0
- package/srv/localProcessService.cds +3 -0
- package/srv/localProcessService.js +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
# CAP - Process Plugin
|
|
2
|
+
|
|
3
|
+
[](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 @@
|
|
|
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"}
|