@camunda8/orchestration-cluster-api 8.9.0-alpha.8 → 9.0.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/CHANGELOG.md +276 -2
- package/README.md +551 -56
- package/dist/{chunk-XCRY6B3O.js → chunk-43TOWWPR.js} +2371 -8885
- package/dist/chunk-43TOWWPR.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/{chunk-W6JB7JZH.js → chunk-KQ4UL2WX.js} +3 -17
- package/dist/chunk-KQ4UL2WX.js.map +1 -0
- package/dist/fp/index.cjs +13809 -12041
- package/dist/fp/index.cjs.map +1 -1
- package/dist/fp/index.d.cts +2 -1
- package/dist/fp/index.d.ts +2 -1
- package/dist/fp/index.js +3 -2
- package/dist/{index-CILGs_2v.d.ts → index-B7HfPv-I.d.ts} +4388 -678
- package/dist/{index-m1kmSXSN.d.cts → index-C0kkfkPq.d.cts} +4388 -678
- package/dist/index.cjs +13815 -11744
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +336 -59
- package/dist/index.js.map +1 -1
- package/dist/logger.cjs +2 -2
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.js +2 -1
- package/dist/threadWorkerEntry.cjs +146 -0
- package/dist/threadWorkerEntry.cjs.map +1 -0
- package/dist/threadWorkerEntry.js +144 -0
- package/dist/threadWorkerEntry.js.map +1 -0
- package/dist/zod.gen-J3DNBFMQ.js +8277 -0
- package/dist/zod.gen-J3DNBFMQ.js.map +1 -0
- package/package.json +38 -38
- package/dist/chunk-W6JB7JZH.js.map +0 -1
- package/dist/chunk-XCRY6B3O.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Camunda 8 Orchestration Cluster TypeScript SDK
|
|
2
2
|
|
|
3
|
+
<!-- WARNING: The content and specific structure of this file drives Docusaurus generation in camunda-docs. Also, code examples are injected during build. Please refer to MAINTAINER.md before editing. -->
|
|
4
|
+
|
|
3
5
|
Type‑safe, promise‑based client for the Camunda 8 Orchestration Cluster REST API.
|
|
4
6
|
|
|
5
7
|
## Highlights
|
|
@@ -13,7 +15,8 @@ Type‑safe, promise‑based client for the Camunda 8 Orchestration Cluster REST
|
|
|
13
15
|
- Eventual consistency helper for polling endpoints
|
|
14
16
|
- Immutable, deep‑frozen configuration accessible through a factory‑created client instance
|
|
15
17
|
- Automatic body-level tenantId defaulting: if a request body supports an optional tenantId and you omit it, the SDK fills it from CAMUNDA_DEFAULT_TENANT_ID (path params are never auto-filled)
|
|
16
|
-
- Automatic transient HTTP retry (429, 503, network) with exponential backoff + full jitter (configurable via CAMUNDA_SDK_HTTP_RETRY\*). Non-retryable 500s fail fast.
|
|
18
|
+
- Automatic transient HTTP retry (429, 503, network) with exponential backoff + full jitter (configurable via CAMUNDA_SDK_HTTP_RETRY\*). Non-retryable 500s fail fast.
|
|
19
|
+
- Per-method retry override: disable or customize retry policy on any individual API call without changing global settings
|
|
17
20
|
|
|
18
21
|
## Install
|
|
19
22
|
|
|
@@ -28,27 +31,129 @@ Runtime support:
|
|
|
28
31
|
|
|
29
32
|
For older Node versions supply a fetch ponyfill AND a `File` shim (or upgrade). For legacy browsers, add a fetch polyfill (e.g. `whatwg-fetch`).
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
### Versioning
|
|
35
|
+
|
|
36
|
+
This SDK has a different release cadence from the Camunda server. Features and fixes land in the SDK during a server release.
|
|
37
|
+
|
|
38
|
+
The major version of the SDK signals a 1:1 type coherence with the server API for a Camunda minor release.
|
|
39
|
+
|
|
40
|
+
SDK version `n.y.z` -> server version `8.n`, so the type surface of SDK version 9.y.z matches the API surface of Camunda 8.9.
|
|
41
|
+
|
|
42
|
+
Using a later SDK version, for example: SDK version 10.y.z with Camunda 8.9, means that the SDK contains additive surfaces that are not guaranteed at runtime, and the compiler cannot warn of unsupported operations.
|
|
43
|
+
|
|
44
|
+
Using an earlier SDK version, for example: SDK version 9.y.z with Camunda 8.10, results in slightly degraded compiler reasoning: exhaustiveness checks cannot be guaranteed by the compiler for any extended surfaces (principally, enums with added members).
|
|
45
|
+
|
|
46
|
+
In the vast majority of use-cases, this will not be an issue; but you should be aware that using the matching SDK major version for the server minor version provides the strongest compiler guarantees about runtime reliability.
|
|
47
|
+
|
|
48
|
+
**Recommended approach**:
|
|
49
|
+
|
|
50
|
+
- Check the [CHANGELOG](https://github.com/camunda/orchestration-cluster-api-js/releases).
|
|
51
|
+
- As a sanity check during server version upgrade, rebuild applications with the matching SDK major version to identify any affected runtime surfaces.
|
|
52
|
+
|
|
53
|
+
## Migrating from 8.8
|
|
54
|
+
|
|
55
|
+
SDK 9.x (for Camunda 8.9) introduces two categories of breaking type changes relative to SDK 8.x (for Camunda 8.8). Neither change affects runtime behavior — existing code that compiled against 8.x will run identically — but the compiler will flag type mismatches until you update.
|
|
56
|
+
|
|
57
|
+
### Search results: optional → required fields
|
|
58
|
+
|
|
59
|
+
The search result types changed several fields from **optional** to **required**:
|
|
60
|
+
|
|
61
|
+
**`SearchQueryPageResponse` (page metadata)**
|
|
32
62
|
|
|
33
|
-
|
|
63
|
+
| Field | SDK 8.x (Camunda 8.8) | SDK 9.x (Camunda 8.9) |
|
|
64
|
+
|-------|----------------------|----------------------|
|
|
65
|
+
| `totalItems` | `totalItems?: number` | `totalItems: number` |
|
|
66
|
+
| `hasMoreTotalItems` | `hasMoreTotalItems?: boolean` | `hasMoreTotalItems: boolean` |
|
|
67
|
+
| `endCursor` | `endCursor?: EndCursor` | `endCursor: EndCursor \| null` |
|
|
68
|
+
| `startCursor` | `startCursor?: StartCursor` | `startCursor: StartCursor \| null` |
|
|
34
69
|
|
|
35
|
-
|
|
70
|
+
**`*SearchQueryResult` types (result containers)**
|
|
36
71
|
|
|
37
|
-
|
|
72
|
+
| Field | SDK 8.x (Camunda 8.8) | SDK 9.x (Camunda 8.9) |
|
|
73
|
+
|-------|----------------------|----------------------|
|
|
74
|
+
| `items` | `items?: T[]` | `items: T[]` |
|
|
75
|
+
| `page` | `page?: SearchQueryPageResponse` | `page: SearchQueryPageResponse` |
|
|
38
76
|
|
|
39
|
-
|
|
77
|
+
This reflects upstream OpenAPI spec changes where these fields are now always present in the response, with `null` indicating "no value" for cursors rather than being absent.
|
|
78
|
+
|
|
79
|
+
**What to change**: Update code that checks for these fields using optional chaining or `undefined` comparisons. The `items` array and `page` object are now always present, so optional chaining on them is unnecessary:
|
|
80
|
+
|
|
81
|
+
<!-- snippet-exempt: migration example showing before/after patterns -->
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// Before (8.x) — checking for undefined
|
|
85
|
+
if (result.page?.endCursor !== undefined) {
|
|
86
|
+
nextPage(result.page.endCursor);
|
|
87
|
+
}
|
|
88
|
+
const count = result.items?.length ?? 0;
|
|
89
|
+
|
|
90
|
+
// After (9.x) — page and items are always present; check cursors for null
|
|
91
|
+
if (result.page.endCursor !== null) {
|
|
92
|
+
nextPage(result.page.endCursor);
|
|
93
|
+
}
|
|
94
|
+
const count = result.items.length;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If you have a custom `PagedResponse` type, update its `page` shape to match:
|
|
98
|
+
|
|
99
|
+
<!-- snippet-exempt: migration example showing type definition update -->
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// Before (8.x)
|
|
103
|
+
type PagedResponse<T> = {
|
|
104
|
+
items?: T[];
|
|
105
|
+
page?: {
|
|
106
|
+
totalItems?: number;
|
|
107
|
+
endCursor?: string;
|
|
108
|
+
startCursor?: string;
|
|
109
|
+
hasMoreTotalItems?: boolean;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// After (9.x)
|
|
114
|
+
type PagedResponse<T> = {
|
|
115
|
+
items: T[];
|
|
116
|
+
page: {
|
|
117
|
+
totalItems: number;
|
|
118
|
+
endCursor: EndCursor | null;
|
|
119
|
+
startCursor: StartCursor | null;
|
|
120
|
+
hasMoreTotalItems: boolean;
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
```
|
|
40
124
|
|
|
41
|
-
|
|
42
|
-
- **Pin and review** — pin to a specific patch version in `package.json` and review the [CHANGELOG](https://github.com/camunda/orchestration-cluster-api-js/releases) before upgrading:
|
|
125
|
+
### Branded key types for `tenantId`
|
|
43
126
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
127
|
+
The `tenantId` field on request types (e.g. `CreateDeploymentData`) changed from `string` to the branded `TenantId` type. A plain `string` is no longer assignable:
|
|
128
|
+
|
|
129
|
+
<!-- snippet-exempt: migration example showing branded type usage -->
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { TenantId } from '@camunda8/orchestration-cluster-api';
|
|
133
|
+
|
|
134
|
+
// Before (8.x) — plain string worked
|
|
135
|
+
await camunda.createDeployment({
|
|
136
|
+
tenantId: 'my-tenant',
|
|
137
|
+
resources: [file],
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// After (9.x) — use the branded type helper
|
|
141
|
+
await camunda.createDeployment({
|
|
142
|
+
tenantId: TenantId.assumeExists('my-tenant'),
|
|
143
|
+
resources: [file],
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`TenantId.assumeExists()` validates the string against the tenant ID pattern and brands it at zero runtime cost. See [Branded Keys](#branded-keys) for more on this pattern.
|
|
148
|
+
|
|
149
|
+
> **Tip**: If your tenant ID comes from a validated source (environment variable, config file), call `TenantId.assumeExists()` once at startup and pass the branded value throughout your application.
|
|
47
150
|
|
|
48
151
|
## Quick Start (Zero‑Config – Recommended)
|
|
49
152
|
|
|
50
153
|
Keep configuration out of application code. Let the factory read `CAMUNDA_*` variables from the environment (12‑factor style). This makes rotation, secret management, and environment promotion safer & simpler.
|
|
51
154
|
|
|
155
|
+
<!-- snippet-source: examples/readme-imports.txt,examples/readme.ts | regions: ReadmeDefaultImport+ReadmeQuickStart -->
|
|
156
|
+
|
|
52
157
|
```ts
|
|
53
158
|
import createCamundaClient from '@camunda8/orchestration-cluster-api';
|
|
54
159
|
|
|
@@ -89,6 +194,8 @@ CAMUNDA_SDK_HTTP_RETRY_MAX_DELAY_MS=2000 # optional: cap (ms)
|
|
|
89
194
|
|
|
90
195
|
Use only when you must supply or mutate configuration dynamically (e.g. multi‑tenant routing, tests, ephemeral preview environments) or in the browser. Keys mirror their `CAMUNDA_*` env names.
|
|
91
196
|
|
|
197
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeOverrides -->
|
|
198
|
+
|
|
92
199
|
```ts
|
|
93
200
|
const camunda = createCamundaClient({
|
|
94
201
|
config: {
|
|
@@ -104,6 +211,8 @@ const camunda = createCamundaClient({
|
|
|
104
211
|
|
|
105
212
|
Inject a custom `fetch` to add tracing, mock responses, instrumentation, circuit breakers, etc.
|
|
106
213
|
|
|
214
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeCustomFetch -->
|
|
215
|
+
|
|
107
216
|
```ts
|
|
108
217
|
const camunda = createCamundaClient({
|
|
109
218
|
fetch: (input, init) => {
|
|
@@ -149,15 +258,65 @@ Behavior:
|
|
|
149
258
|
- `strict` - fail on type mismatch or missing required fields
|
|
150
259
|
- `fanatical` - fail on type mismatch, missing required fields, or unknown additional fields
|
|
151
260
|
|
|
261
|
+
> **Note on `int64` fields**: The upstream OpenAPI spec declares some fields (e.g. `totalItems`, `timeout`, `timestamp`) as `integer` with `format: int64`. The TypeScript types map these to `number`. JSON responses also deserialize as `number` (with precision loss beyond `Number.MAX_SAFE_INTEGER`). The Zod schemas use `z.coerce.number().int()` for these fields, preserving the integer constraint while keeping the runtime type aligned with TypeScript. All validation modes (`none`, `warn`, `strict`, `fanatical`) return `number`.
|
|
262
|
+
|
|
263
|
+
## Per-Method Retry Override
|
|
264
|
+
|
|
265
|
+
Every API method accepts an optional trailing `options` parameter that lets you override or disable the global retry policy for that single call.
|
|
266
|
+
|
|
267
|
+
### Disable Retry for a Single Call
|
|
268
|
+
|
|
269
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeDisableRetry -->
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
// This call will not retry on transient errors
|
|
273
|
+
await camunda.completeJob({ jobKey }, { retry: false });
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Override Specific Retry Settings
|
|
277
|
+
|
|
278
|
+
Pass a partial `HttpRetryPolicy` to override individual fields. Unspecified fields inherit from the global configuration.
|
|
279
|
+
|
|
280
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeRetryOverride -->
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
// More aggressive retry for this operation only
|
|
284
|
+
await camunda.createProcessInstance(
|
|
285
|
+
{ processDefinitionId },
|
|
286
|
+
{ retry: { maxAttempts: 8, maxDelayMs: 5000 } }
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Minimal retry: single retry with short backoff
|
|
290
|
+
await camunda.getTopology({ retry: { maxAttempts: 2, baseDelayMs: 50 } });
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### How It Works
|
|
294
|
+
|
|
295
|
+
| `options.retry` value | Behavior |
|
|
296
|
+
| --------------------- | -------------------------------------------------------------------- |
|
|
297
|
+
| omitted / `undefined` | Uses global policy (`CAMUNDA_SDK_HTTP_RETRY_*` env vars) |
|
|
298
|
+
| `false` | Disables retry entirely (single attempt, no backoff) |
|
|
299
|
+
| `{ maxAttempts: 5 }` | Merges with global policy — only the specified fields are overridden |
|
|
300
|
+
|
|
301
|
+
The `HttpRetryPolicy` fields available for override:
|
|
302
|
+
|
|
303
|
+
| Field | Type | Description |
|
|
304
|
+
| ------------- | -------- | --------------------------------------- |
|
|
305
|
+
| `maxAttempts` | `number` | Total attempts (initial + retries) |
|
|
306
|
+
| `baseDelayMs` | `number` | Base delay for exponential backoff (ms) |
|
|
307
|
+
| `maxDelayMs` | `number` | Maximum delay cap (ms) |
|
|
308
|
+
|
|
152
309
|
## Advanced HTTP Retry: Cockatiel Adapter (Optional)
|
|
153
310
|
|
|
154
|
-
|
|
311
|
+
For advanced resilience patterns beyond per-method overrides — circuit breakers, timeouts, custom classification, combining policies — you can integrate [cockatiel](https://github.com/connor4312/cockatiel).
|
|
312
|
+
|
|
313
|
+
> **Tip:** For most use cases, per-method retry override (above) is sufficient. Reach for Cockatiel when you need circuit breaking, hedging, or bulkhead controls.
|
|
155
314
|
|
|
156
315
|
### When To Use Cockatiel
|
|
157
316
|
|
|
158
|
-
- You need different retry policies per operation (e.g. idempotent GET vs mutating POST)
|
|
159
317
|
- You want circuit breaking, hedging, timeout, or bulkhead controls
|
|
160
318
|
- You want to add custom classification (e.g. retry certain 5xx only on safe verbs)
|
|
319
|
+
- You need to compose multiple resilience policies together
|
|
161
320
|
|
|
162
321
|
### Disable Built‑In HTTP Retries
|
|
163
322
|
|
|
@@ -165,6 +324,7 @@ Set `CAMUNDA_SDK_HTTP_RETRY_MAX_ATTEMPTS=1` so the SDK does only the initial att
|
|
|
165
324
|
|
|
166
325
|
### Minimal Example (Single Operation)
|
|
167
326
|
|
|
327
|
+
<!-- snippet-exempt: uses external cockatiel library -->
|
|
168
328
|
```ts
|
|
169
329
|
import { createCamundaClient } from '@camunda8/orchestration-cluster-api';
|
|
170
330
|
import { retry, ExponentialBackoff, handleAll } from 'cockatiel';
|
|
@@ -193,6 +353,7 @@ console.log(topo.brokers?.length);
|
|
|
193
353
|
|
|
194
354
|
### Bulk Wrapping All Operations
|
|
195
355
|
|
|
356
|
+
<!-- snippet-exempt: uses external cockatiel library -->
|
|
196
357
|
```ts
|
|
197
358
|
import { createCamundaClient } from '@camunda8/orchestration-cluster-api';
|
|
198
359
|
import { retry, ExponentialBackoff, handleAll } from 'cockatiel';
|
|
@@ -271,6 +432,7 @@ Refer to `./docs/CONFIG_REFERENCE.md` for the full list of related environment v
|
|
|
271
432
|
|
|
272
433
|
Retry only network errors + 429/503, plus optionally 500 on safe GET endpoints you mark:
|
|
273
434
|
|
|
435
|
+
<!-- snippet-exempt: uses external cockatiel library -->
|
|
274
436
|
```ts
|
|
275
437
|
import { retry, ExponentialBackoff, handleWhen } from 'cockatiel';
|
|
276
438
|
|
|
@@ -292,7 +454,7 @@ const policy = retry(classify, {
|
|
|
292
454
|
- Keep SDK retries disabled to prevent duplicate layers.
|
|
293
455
|
- SDK synthesizes `Error` objects with a `status` for retry-significant HTTP responses (429, 503, 500), enabling classification.
|
|
294
456
|
- You can tag errors (e.g. assign `err.__opVerb`) in a wrapper if verb-level logic is needed.
|
|
295
|
-
-
|
|
457
|
+
- For per-operation retry customization without external dependencies, use the built-in [per-method retry override](#per-method-retry-override) instead.
|
|
296
458
|
|
|
297
459
|
> Combine cockatiel retry with a circuit breaker, timeout, or bulkhead policy for more robust behavior in partial outages.
|
|
298
460
|
|
|
@@ -394,12 +556,29 @@ Factors use integer percentages to avoid floating point drift in env parsing; th
|
|
|
394
556
|
|
|
395
557
|
If you have concrete tuning needs, open an issue describing workload patterns (operation mix, baseline concurrency, observed broker limits) to help prioritize which knobs to surface.
|
|
396
558
|
|
|
559
|
+
### What Should I Set?
|
|
560
|
+
|
|
561
|
+
If you're unsure about your workload shape, **don't set anything**. The default BALANCED profile activates automatically and outperforms no-gating (LEGACY) in most scenarios — on raw throughput alone, not just error reduction.
|
|
562
|
+
|
|
563
|
+
Benchmark results against a single-node local cluster with multiple independent clients (no shared state between them):
|
|
564
|
+
|
|
565
|
+
| Scenario | BALANCED | LEGACY (no gating) |
|
|
566
|
+
| ----------------------------- | --------------- | ------------------ |
|
|
567
|
+
| Single-client (1K processes) | **80.1 ops/s** | 67.8 ops/s |
|
|
568
|
+
| Single-client sustained (10K) | **119.6 ops/s** | 87.8 ops/s |
|
|
569
|
+
| Multi-client 3+2 spike | **86.3 ops/s** | 48.0 ops/s |
|
|
570
|
+
| Stress 8 clients ×1000 | 76.3 ops/s | **106.4 ops/s** |
|
|
571
|
+
|
|
572
|
+
BALANCED wins 3 of 4 on pure throughput. The only scenario where LEGACY is faster is extreme overload (800 concurrent requests against a single broker) — and in that case LEGACY accumulates 44,505 errors vs BALANCED's 15,527. The default just works.
|
|
573
|
+
|
|
397
574
|
## Job Workers (Polling API)
|
|
398
575
|
|
|
399
576
|
The SDK provides a lightweight polling job worker for service task job types using `createJobWorker`. It activates jobs in batches (respecting a concurrency limit), validates variables (optional), and offers action helpers on each job.
|
|
400
577
|
|
|
401
578
|
### Minimal Example
|
|
402
579
|
|
|
580
|
+
<!-- snippet-source: examples/readme-imports.txt,examples/readme.ts | regions: ReadmeJobWorkerImport+ReadmeJobWorkerMinimal -->
|
|
581
|
+
|
|
403
582
|
```ts
|
|
404
583
|
import createCamundaClient from '@camunda8/orchestration-cluster-api';
|
|
405
584
|
import { z } from 'zod';
|
|
@@ -413,7 +592,7 @@ const Output = z.object({ processed: z.boolean() });
|
|
|
413
592
|
const worker = client.createJobWorker({
|
|
414
593
|
jobType: 'process-order',
|
|
415
594
|
maxParallelJobs: 10,
|
|
416
|
-
|
|
595
|
+
jobTimeoutMs: 15_000, // long‑poll timeout (server side requestTimeout)
|
|
417
596
|
pollIntervalMs: 100, // delay between polls when no jobs / at capacity
|
|
418
597
|
// Optional: only fetch specific variables during activation
|
|
419
598
|
fetchVariables: ['orderId'],
|
|
@@ -421,11 +600,13 @@ const worker = client.createJobWorker({
|
|
|
421
600
|
outputSchema: Output, // validates variables passed to complete(...)
|
|
422
601
|
validateSchemas: true, // set false for max throughput (skip Zod)
|
|
423
602
|
autoStart: true, // default true; start polling immediately
|
|
603
|
+
startupJitterMaxSeconds: 5, // random delay up to 5s before first poll (default 0)
|
|
424
604
|
jobHandler: (job) => {
|
|
425
605
|
// Access typed variables
|
|
426
606
|
const vars = job.variables; // inferred from Input schema
|
|
607
|
+
console.log(`Processing order: ${vars.orderId}`);
|
|
427
608
|
// Do work...
|
|
428
|
-
return job.complete({
|
|
609
|
+
return job.complete({ processed: true });
|
|
429
610
|
},
|
|
430
611
|
});
|
|
431
612
|
|
|
@@ -443,6 +624,8 @@ TypeScript inference:
|
|
|
443
624
|
|
|
444
625
|
- When you provide `inputSchema`, the type of `fetchVariables` is constrained to the keys of the inferred `variables` type from that schema. Example:
|
|
445
626
|
|
|
627
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeJobWorkerInference -->
|
|
628
|
+
|
|
446
629
|
```ts
|
|
447
630
|
const Input = z.object({ orderId: z.string(), amount: z.number() });
|
|
448
631
|
client.createJobWorker({
|
|
@@ -462,7 +645,7 @@ client.createJobWorker({
|
|
|
462
645
|
|
|
463
646
|
Your `jobHandler` must ultimately invoke exactly one of:
|
|
464
647
|
|
|
465
|
-
- `job.complete(
|
|
648
|
+
- `job.complete(variables?, result?)` OR `job.complete()`
|
|
466
649
|
- `job.fail({ errorMessage, retries?, retryBackoff? })`
|
|
467
650
|
- `job.cancelWorkflow({})` (cancels the process instance)
|
|
468
651
|
- `job.error({ errorCode, errorMessage? })` (throws a business error)
|
|
@@ -487,6 +670,7 @@ Recommended usage:
|
|
|
487
670
|
|
|
488
671
|
Example patterns:
|
|
489
672
|
|
|
673
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeJobCompletionPatterns -->
|
|
490
674
|
```ts
|
|
491
675
|
// GOOD: explicit completion
|
|
492
676
|
return job.complete({ variables: { processed: true } });
|
|
@@ -497,13 +681,61 @@ const ack = await job.complete();
|
|
|
497
681
|
return ack;
|
|
498
682
|
|
|
499
683
|
// GOOD: explicit ignore
|
|
500
|
-
const
|
|
684
|
+
const ack2 = await job.ignore();
|
|
501
685
|
```
|
|
502
686
|
|
|
687
|
+
### Job Corrections (User Task Listeners)
|
|
688
|
+
|
|
689
|
+
When a job worker handles a [user task listener](https://docs.camunda.io/docs/components/concepts/user-task-listeners/), it can correct task properties (assignee, due date, candidate groups, etc.) by passing a `result` to `job.complete()`:
|
|
690
|
+
|
|
691
|
+
<!-- snippet-source: examples/readme-imports.txt,examples/readme.ts | regions: ReadmeJobCorrectionsImport+ReadmeJobCorrections -->
|
|
692
|
+
|
|
503
693
|
```ts
|
|
504
|
-
|
|
694
|
+
import type { JobResult } from '@camunda8/orchestration-cluster-api';
|
|
695
|
+
|
|
696
|
+
const worker = client.createJobWorker({
|
|
697
|
+
jobType: 'io.camunda:userTaskListener',
|
|
698
|
+
jobTimeoutMs: 30_000,
|
|
699
|
+
maxParallelJobs: 5,
|
|
700
|
+
jobHandler: async (job) => {
|
|
701
|
+
const result: JobResult = {
|
|
702
|
+
type: 'userTask',
|
|
703
|
+
corrections: {
|
|
704
|
+
assignee: 'corrected-user',
|
|
705
|
+
priority: 80,
|
|
706
|
+
},
|
|
707
|
+
};
|
|
708
|
+
return job.complete({}, result);
|
|
709
|
+
},
|
|
710
|
+
});
|
|
505
711
|
```
|
|
506
712
|
|
|
713
|
+
To deny a task completion (reject the work):
|
|
714
|
+
|
|
715
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeJobCorrectionsDenial -->
|
|
716
|
+
|
|
717
|
+
```ts
|
|
718
|
+
return job.complete(
|
|
719
|
+
{},
|
|
720
|
+
{
|
|
721
|
+
type: 'userTask',
|
|
722
|
+
denied: true,
|
|
723
|
+
deniedReason: 'Insufficient documentation',
|
|
724
|
+
}
|
|
725
|
+
);
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
| Correctable attribute | Type | Clear value |
|
|
729
|
+
|---|---|---|
|
|
730
|
+
| `assignee` | `string` | Empty string `""` |
|
|
731
|
+
| `dueDate` | `string` (ISO 8601) | Empty string `""` |
|
|
732
|
+
| `followUpDate` | `string` (ISO 8601) | Empty string `""` |
|
|
733
|
+
| `candidateUsers` | `string[]` | Empty array `[]` |
|
|
734
|
+
| `candidateGroups` | `string[]` | Empty array `[]` |
|
|
735
|
+
| `priority` | `number` (0–100) | — |
|
|
736
|
+
|
|
737
|
+
Omitting an attribute or passing `null` preserves the persisted value.
|
|
738
|
+
|
|
507
739
|
### Concurrency & Backpressure
|
|
508
740
|
|
|
509
741
|
Set `maxParallelJobs` to the maximum number of jobs you want actively processing concurrently. The worker will long‑poll for up to the remaining capacity each cycle. Global backpressure (adaptive concurrency) still applies to the underlying REST calls; activation itself is a normal operation.
|
|
@@ -520,6 +752,8 @@ If `validateSchemas` is true:
|
|
|
520
752
|
|
|
521
753
|
Use `await worker.stopGracefully({ waitUpToMs?, checkIntervalMs? })` to drain without force‑cancelling the current activation request.
|
|
522
754
|
|
|
755
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeJobWorkerGraceful -->
|
|
756
|
+
|
|
523
757
|
```ts
|
|
524
758
|
// Attempt graceful drain for up to 8 seconds
|
|
525
759
|
const { remainingJobs, timedOut } = await worker.stopGracefully({ waitUpToMs: 8000 });
|
|
@@ -543,13 +777,89 @@ Activation cancellations during stop are logged at debug (`activation.cancelled`
|
|
|
543
777
|
|
|
544
778
|
You can register multiple workers on a single client instance—one per job type is typical. The client exposes `client.getWorkers()` for inspection and `client.stopAllWorkers()` for coordinated shutdown.
|
|
545
779
|
|
|
780
|
+
### Startup Jitter
|
|
781
|
+
|
|
782
|
+
When deploying multiple application instances simultaneously (e.g. a rolling restart or scale-up), all workers start polling at the same time and can saturate the server with activation requests. Set `startupJitterMaxSeconds` to spread out the initial poll across a random window:
|
|
783
|
+
|
|
784
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeJobWorkerJitter -->
|
|
785
|
+
|
|
786
|
+
```ts
|
|
787
|
+
client.createJobWorker({
|
|
788
|
+
jobType: 'process-order',
|
|
789
|
+
maxParallelJobs: 10,
|
|
790
|
+
jobTimeoutMs: 30_000,
|
|
791
|
+
startupJitterMaxSeconds: 5, // each instance delays 0–5s before first poll
|
|
792
|
+
jobHandler: async (job) => job.complete(),
|
|
793
|
+
});
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
A value of `0` (the default) means no delay.
|
|
797
|
+
|
|
798
|
+
### Heritable Worker Defaults
|
|
799
|
+
|
|
800
|
+
When running many workers with the same base configuration, you can set global defaults via environment variables (or equivalent keys in `CamundaOptions.config`). These apply to every worker created by the client (both `createJobWorker` and `createThreadedJobWorker`) unless the individual worker config explicitly overrides them.
|
|
801
|
+
|
|
802
|
+
| Environment Variable | Worker Config Field | Type |
|
|
803
|
+
| ------------------------------------------ | -------------------------- | ------ |
|
|
804
|
+
| `CAMUNDA_WORKER_TIMEOUT` | `jobTimeoutMs` | number |
|
|
805
|
+
| `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` | `maxParallelJobs` | number |
|
|
806
|
+
| `CAMUNDA_WORKER_REQUEST_TIMEOUT` | `pollTimeoutMs` | number |
|
|
807
|
+
| `CAMUNDA_WORKER_NAME` | `workerName` | string |
|
|
808
|
+
| `CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS`| `startupJitterMaxSeconds` | number |
|
|
809
|
+
|
|
810
|
+
**Precedence:** explicit worker config value > `CAMUNDA_WORKER_*` (from environment variables or `CamundaOptions.config` overrides) > hardcoded default (where applicable).
|
|
811
|
+
|
|
812
|
+
Example — set defaults via environment:
|
|
813
|
+
|
|
814
|
+
```bash
|
|
815
|
+
export CAMUNDA_WORKER_TIMEOUT=30000
|
|
816
|
+
export CAMUNDA_WORKER_MAX_CONCURRENT_JOBS=8
|
|
817
|
+
export CAMUNDA_WORKER_NAME=order-service
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeWorkerDefaultsEnv -->
|
|
821
|
+
```ts
|
|
822
|
+
// Workers inherit timeout, concurrency, and name from environment
|
|
823
|
+
const w1 = client.createJobWorker({
|
|
824
|
+
jobType: 'validate-order',
|
|
825
|
+
jobHandler: async (job) => job.complete(),
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
const w2 = client.createJobWorker({
|
|
829
|
+
jobType: 'ship-order',
|
|
830
|
+
jobHandler: async (job) => job.complete(),
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// Per-worker override: this worker uses 32 concurrent jobs instead of the global 8
|
|
834
|
+
const w3 = client.createJobWorker({
|
|
835
|
+
jobType: 'bulk-import',
|
|
836
|
+
maxParallelJobs: 32,
|
|
837
|
+
jobHandler: async (job) => job.complete(),
|
|
838
|
+
});
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
You can also pass defaults programmatically via the client constructor:
|
|
842
|
+
|
|
843
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeWorkerDefaultsClient -->
|
|
844
|
+
```ts
|
|
845
|
+
const client = createCamundaClient({
|
|
846
|
+
config: {
|
|
847
|
+
CAMUNDA_WORKER_TIMEOUT: 30000,
|
|
848
|
+
CAMUNDA_WORKER_MAX_CONCURRENT_JOBS: 8,
|
|
849
|
+
},
|
|
850
|
+
});
|
|
851
|
+
```
|
|
852
|
+
|
|
546
853
|
### Receipt Type (Unique Symbol)
|
|
547
854
|
|
|
548
855
|
Action methods return a unique symbol (not a string) to avoid accidental misuse and allow internal metrics. If you store the receipt, annotate its type as `JobActionReceipt` to preserve uniqueness:
|
|
549
856
|
|
|
857
|
+
<!-- snippet-source: examples/readme-imports.txt,examples/readme.ts | regions: ReadmeReceiptImport+ReadmeReceipt -->
|
|
858
|
+
|
|
550
859
|
```ts
|
|
551
|
-
import { JobActionReceipt } from '@camunda8/orchestration-cluster-api';
|
|
552
|
-
|
|
860
|
+
import type { JobActionReceipt } from '@camunda8/orchestration-cluster-api';
|
|
861
|
+
|
|
862
|
+
const receipt: JobActionReceipt = await job.complete({ processed: true });
|
|
553
863
|
```
|
|
554
864
|
|
|
555
865
|
If you ignore the return value you don’t need to import the symbol.
|
|
@@ -582,16 +892,126 @@ This reverts to only per‑request retry for transient errors (no global gating)
|
|
|
582
892
|
|
|
583
893
|
Call `client.getBackpressureState()` to obtain:
|
|
584
894
|
|
|
895
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeBackpressureState -->
|
|
585
896
|
```ts
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
897
|
+
const state = client.getBackpressureState();
|
|
898
|
+
// state.severity: 'healthy' | 'soft' | 'severe'
|
|
899
|
+
// state.consecutive: number — consecutive backpressure signals observed
|
|
900
|
+
// state.permitsMax: number | null — current concurrency cap (null => unlimited/not engaged)
|
|
901
|
+
// state.permitsCurrent: number — currently acquired permits
|
|
902
|
+
// state.waiters: number — queued operations waiting for a permit
|
|
593
903
|
```
|
|
594
904
|
|
|
905
|
+
### Threaded Job Workers (Node.js Only)
|
|
906
|
+
|
|
907
|
+
For CPU-intensive job handlers, `createThreadedJobWorker` offloads handler execution to a pool of Node.js `worker_threads`. Polling and I/O remain on the main event loop, while handler logic runs in parallel threads — dramatically improving throughput when the handler does CPU-bound work (JSON processing, validation, transformation, cryptography).
|
|
908
|
+
|
|
909
|
+
#### When to use
|
|
910
|
+
|
|
911
|
+
- Your handler spends significant time on CPU work (not just waiting for HTTP responses)
|
|
912
|
+
- You observe that a single-threaded worker saturates one CPU core while throughput plateaus
|
|
913
|
+
- You need to process more jobs per second without deploying additional instances
|
|
914
|
+
|
|
915
|
+
If your handler is mostly I/O-bound (HTTP calls, database queries), the standard `createJobWorker` is sufficient.
|
|
916
|
+
|
|
917
|
+
#### Handler module
|
|
918
|
+
|
|
919
|
+
The handler must be a **separate file** (not an inline function) that exports a default async function:
|
|
920
|
+
|
|
921
|
+
<!-- snippet-exempt: pseudo-code referencing heavyComputation which cannot be type-checked -->
|
|
922
|
+
```ts
|
|
923
|
+
// my-handler.ts (or my-handler.js)
|
|
924
|
+
import type { ThreadedJobHandler } from '@camunda8/orchestration-cluster-api';
|
|
925
|
+
|
|
926
|
+
const handler: ThreadedJobHandler = async (job, client) => {
|
|
927
|
+
const { orderId } = job.variables;
|
|
928
|
+
// CPU-intensive work here...
|
|
929
|
+
const result = heavyComputation(orderId);
|
|
930
|
+
return job.complete({ result });
|
|
931
|
+
};
|
|
932
|
+
export default handler;
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
Typing your handler as `ThreadedJobHandler` gives full intellisense for `job` (variables, action methods like `complete()`, `fail()`, `error()`) and `client` (every `CamundaClient` API method).
|
|
936
|
+
|
|
937
|
+
The handler receives two arguments:
|
|
938
|
+
|
|
939
|
+
1. **`job`** — a proxy with the same shape as a regular job worker job (`variables`, `customHeaders`, `jobKey`, plus action methods: `complete()`, `fail()`, `error()`, `cancelWorkflow()`, `ignore()`)
|
|
940
|
+
2. **`client`** — a proxy to the `CamundaClient` on the main thread. You can call any SDK method (e.g. `client.publishMessage(...)`, `client.createProcessInstance(...)`) and it will be forwarded to the main thread and executed there.
|
|
941
|
+
|
|
942
|
+
#### Minimal example
|
|
943
|
+
|
|
944
|
+
<!-- snippet-source: examples/readme-imports.txt,examples/readme.ts | regions: ReadmeThreadedWorkerImport+ReadmeThreadedWorker -->
|
|
945
|
+
|
|
946
|
+
```ts
|
|
947
|
+
import createCamundaClient from '@camunda8/orchestration-cluster-api';
|
|
948
|
+
import path from 'node:path';
|
|
949
|
+
import { fileURLToPath } from 'node:url';
|
|
950
|
+
|
|
951
|
+
const client = createCamundaClient();
|
|
952
|
+
|
|
953
|
+
const worker = client.createThreadedJobWorker({
|
|
954
|
+
jobType: 'cpu-heavy-task',
|
|
955
|
+
handlerModule: path.join(path.dirname(fileURLToPath(import.meta.url)), 'my-handler.js'),
|
|
956
|
+
maxParallelJobs: 32,
|
|
957
|
+
jobTimeoutMs: 30_000,
|
|
958
|
+
});
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
#### Configuration
|
|
962
|
+
|
|
963
|
+
`createThreadedJobWorker` accepts all the same options as `createJobWorker` (except `jobHandler`), plus:
|
|
964
|
+
|
|
965
|
+
| Option | Type | Default | Description |
|
|
966
|
+
| ---------------- | -------- | --------------------------- | ---------------------------------------------------------------- |
|
|
967
|
+
| `handlerModule` | `string` | (required) | Path to handler module (absolute or relative to `process.cwd()`) |
|
|
968
|
+
| `threadPoolSize` | `number` | `os.availableParallelism()` | Number of worker threads in the pool |
|
|
969
|
+
|
|
970
|
+
Other familiar options: `jobType`, `maxParallelJobs`, `jobTimeoutMs`, `pollIntervalMs`, `pollTimeoutMs`, `fetchVariables`, `inputSchema`, `outputSchema`, `customHeadersSchema`, `validateSchemas`, `autoStart`, `startupJitterMaxSeconds`, `workerName`.
|
|
971
|
+
|
|
972
|
+
#### Lifecycle
|
|
973
|
+
|
|
974
|
+
Threaded workers integrate with the same lifecycle as regular workers:
|
|
975
|
+
|
|
976
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeThreadedLifecycle -->
|
|
977
|
+
```ts
|
|
978
|
+
// Returned by getWorkers()
|
|
979
|
+
const allWorkers = client.getWorkers();
|
|
980
|
+
|
|
981
|
+
// Stopped by stopAllWorkers()
|
|
982
|
+
client.stopAllWorkers();
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeThreadedGraceful -->
|
|
986
|
+
```ts
|
|
987
|
+
// Graceful shutdown (waits for in-flight jobs to finish)
|
|
988
|
+
const { timedOut, remainingJobs } = await worker.stopGracefully({ waitUpToMs: 10_000 });
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
#### Pool stats
|
|
992
|
+
|
|
993
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmePoolStats -->
|
|
994
|
+
```ts
|
|
995
|
+
worker.poolSize; // number of threads
|
|
996
|
+
worker.busyThreads; // threads currently processing a job
|
|
997
|
+
worker.activeJobs; // total jobs dispatched but not yet completed
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
#### How it works
|
|
1001
|
+
|
|
1002
|
+
1. The main thread polls `activateJobs` using the same mechanism as `createJobWorker`
|
|
1003
|
+
2. Activated jobs are serialized and dispatched to an idle thread via `MessageChannel`
|
|
1004
|
+
3. The thread loads the handler module (lazy, on first job), creates a proxy for `job` action methods and `client` API calls
|
|
1005
|
+
4. Action methods (`job.complete()`, `job.fail()`, etc.) and client calls are forwarded back to the main thread over the `MessagePort` and executed there
|
|
1006
|
+
5. The result is relayed back, and the thread is marked idle for the next job
|
|
1007
|
+
|
|
1008
|
+
#### Constraints
|
|
1009
|
+
|
|
1010
|
+
- **Node.js only**: `worker_threads` is not available in browsers or Deno
|
|
1011
|
+
- **Handler must be a file module**: Inline functions cannot be transferred to threads
|
|
1012
|
+
- **Job variables must be JSON-serializable**: Functions and class instances on the job are stripped during transfer
|
|
1013
|
+
- **Client calls are async round-trips**: Each `client.xyz()` call crosses a thread boundary, adding a small amount of latency per call
|
|
1014
|
+
|
|
595
1015
|
---
|
|
596
1016
|
|
|
597
1017
|
## Authentication
|
|
@@ -661,25 +1081,58 @@ Browser usage: There is no disk concept—if executed in a browser the SDK (when
|
|
|
661
1081
|
|
|
662
1082
|
If you need a custom persistence strategy (e.g. Redis / encrypted keychain), wrap the client and periodically call `client.forceAuthRefresh()` while storing and re‑injecting the token via a headers hook; first measure whether the built‑in disk cache already meets your needs.
|
|
663
1083
|
|
|
664
|
-
## mTLS (Node only)
|
|
1084
|
+
## Self-signed TLS / mTLS (Node only)
|
|
1085
|
+
|
|
1086
|
+
The SDK supports custom TLS certificates via environment variables. This is useful for:
|
|
1087
|
+
|
|
1088
|
+
- **Self-signed server certificates** — trust a CA that signed your server's certificate, without presenting a client identity.
|
|
1089
|
+
- **Mutual TLS (mTLS)** — present a client certificate and key to prove the client's identity.
|
|
1090
|
+
- **Both** — trust a custom CA _and_ present client credentials.
|
|
1091
|
+
|
|
1092
|
+
### Trusting a self-signed server certificate
|
|
1093
|
+
|
|
1094
|
+
Set only the CA certificate to trust the server's self-signed certificate:
|
|
1095
|
+
|
|
1096
|
+
```bash
|
|
1097
|
+
# Path to PEM file:
|
|
1098
|
+
CAMUNDA_MTLS_CA_PATH=/path/to/ca.pem
|
|
1099
|
+
|
|
1100
|
+
# Or inline PEM (must contain real newlines, not literal '\n'):
|
|
1101
|
+
CAMUNDA_MTLS_CA="$(cat /path/to/ca.pem)"
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
### Mutual TLS (client certificate)
|
|
665
1105
|
|
|
666
|
-
|
|
1106
|
+
To present a client certificate for mutual TLS, provide both the certificate and private key:
|
|
667
1107
|
|
|
1108
|
+
```bash
|
|
1109
|
+
CAMUNDA_MTLS_CERT_PATH=/path/to/client.crt
|
|
1110
|
+
CAMUNDA_MTLS_KEY_PATH=/path/to/client.key
|
|
1111
|
+
|
|
1112
|
+
# Optional — passphrase if the key is encrypted:
|
|
1113
|
+
# CAMUNDA_MTLS_KEY_PASSPHRASE=secret
|
|
668
1114
|
```
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1115
|
+
|
|
1116
|
+
### Full mTLS with custom CA
|
|
1117
|
+
|
|
1118
|
+
Combine a custom CA with client credentials:
|
|
1119
|
+
|
|
1120
|
+
```bash
|
|
1121
|
+
CAMUNDA_MTLS_CA_PATH=/path/to/ca.pem
|
|
1122
|
+
CAMUNDA_MTLS_CERT_PATH=/path/to/client.crt
|
|
1123
|
+
CAMUNDA_MTLS_KEY_PATH=/path/to/client.key
|
|
673
1124
|
```
|
|
674
1125
|
|
|
675
|
-
|
|
1126
|
+
Inline PEM values (`CAMUNDA_MTLS_CERT`, `CAMUNDA_MTLS_KEY`, `CAMUNDA_MTLS_CA`) take precedence over their `_PATH` counterparts. An `https.Agent` is attached to all outbound calls (including token fetches).
|
|
676
1127
|
|
|
677
1128
|
## Branded Keys
|
|
678
1129
|
|
|
679
1130
|
Import branded key helpers directly:
|
|
680
1131
|
|
|
1132
|
+
<!-- snippet-source: examples/readme-imports.txt,examples/readme.ts | regions: ReadmeBrandedKeysImport+ReadmeBrandedKeys -->
|
|
1133
|
+
|
|
681
1134
|
```ts
|
|
682
|
-
import { ProcessDefinitionKey, ProcessInstanceKey } from '@camunda8/orchestration-cluster';
|
|
1135
|
+
import { ProcessDefinitionKey, ProcessInstanceKey } from '@camunda8/orchestration-cluster-api';
|
|
683
1136
|
|
|
684
1137
|
const defKey = ProcessDefinitionKey.assumeExists('2251799813686749');
|
|
685
1138
|
// @ts-expect-error – cannot assign def key to instance key
|
|
@@ -692,8 +1145,13 @@ They are zero‑cost runtime strings with compile‑time separation.
|
|
|
692
1145
|
|
|
693
1146
|
All methods return a `CancelablePromise<T>`:
|
|
694
1147
|
|
|
1148
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeCancelable -->
|
|
1149
|
+
|
|
695
1150
|
```ts
|
|
696
|
-
const p = camunda.searchProcessInstances(
|
|
1151
|
+
const p = camunda.searchProcessInstances(
|
|
1152
|
+
{ filter: { processDefinitionKey: defKey } },
|
|
1153
|
+
{ consistency: { waitUpToMs: 0 } }
|
|
1154
|
+
);
|
|
697
1155
|
setTimeout(() => p.cancel(), 100); // best‑effort cancel
|
|
698
1156
|
try {
|
|
699
1157
|
await p; // resolves if not cancelled
|
|
@@ -714,8 +1172,17 @@ Notes:
|
|
|
714
1172
|
|
|
715
1173
|
@experimental - this feature is not guaranteed to be tested or stable.
|
|
716
1174
|
|
|
1175
|
+
> **Peer dependency:** `fp-ts` is an optional peer dependency. If you use real `fp-ts` functions
|
|
1176
|
+
> (e.g. `pipe`, `TE.match`) alongside this subpath, install it separately:
|
|
1177
|
+
> ```sh
|
|
1178
|
+
> npm install fp-ts
|
|
1179
|
+
> ```
|
|
1180
|
+
> The `/fp` subpath works without `fp-ts` installed — it exposes structurally-compatible
|
|
1181
|
+
> `Either`/`TaskEither` shapes that interoperate with `fp-ts` but do not require it at runtime.
|
|
1182
|
+
|
|
717
1183
|
The main entry stays minimal. To opt in to a TaskEither-style facade & helper combinators import from the dedicated subpath:
|
|
718
1184
|
|
|
1185
|
+
<!-- snippet-exempt: uses SDK /fp subpath not available in examples project -->
|
|
719
1186
|
```ts
|
|
720
1187
|
import {
|
|
721
1188
|
createCamundaFpClient,
|
|
@@ -723,7 +1190,7 @@ import {
|
|
|
723
1190
|
withTimeoutTE,
|
|
724
1191
|
eventuallyTE,
|
|
725
1192
|
isLeft,
|
|
726
|
-
} from '@camunda8/orchestration-cluster/fp';
|
|
1193
|
+
} from '@camunda8/orchestration-cluster-api/fp';
|
|
727
1194
|
|
|
728
1195
|
const fp = createCamundaFpClient();
|
|
729
1196
|
const deployTE = fp.deployResourcesFromFiles(['./bpmn/process.bpmn']);
|
|
@@ -791,16 +1258,22 @@ Use this to understand convergence speed and data shape evolution during tests o
|
|
|
791
1258
|
|
|
792
1259
|
### Example
|
|
793
1260
|
|
|
1261
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeEventualConsistency -->
|
|
1262
|
+
|
|
794
1263
|
```ts
|
|
795
|
-
const jobs = await camunda.searchJobs(
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
waitUpToMs: 5000,
|
|
799
|
-
pollIntervalMs: 200,
|
|
800
|
-
trace: true,
|
|
801
|
-
predicate: (r) => Array.isArray(r.items) && r.items.some((j) => j.state === 'CREATED'),
|
|
1264
|
+
const jobs = await camunda.searchJobs(
|
|
1265
|
+
{
|
|
1266
|
+
filter: { type: 'payment' },
|
|
802
1267
|
},
|
|
803
|
-
|
|
1268
|
+
{
|
|
1269
|
+
consistency: {
|
|
1270
|
+
waitUpToMs: 5000,
|
|
1271
|
+
pollIntervalMs: 200,
|
|
1272
|
+
trace: true,
|
|
1273
|
+
predicate: (r) => Array.isArray(r.items) && r.items.some((j) => j.state === 'CREATED'),
|
|
1274
|
+
},
|
|
1275
|
+
}
|
|
1276
|
+
);
|
|
804
1277
|
```
|
|
805
1278
|
|
|
806
1279
|
On timeout an `EventualConsistencyTimeoutError` includes diagnostic fields: `{ attempts, elapsedMs, lastStatus, lastResponse, operationId }`.
|
|
@@ -809,6 +1282,8 @@ On timeout an `EventualConsistencyTimeoutError` includes diagnostic fields: `{ a
|
|
|
809
1282
|
|
|
810
1283
|
Per‑client logger; no global singleton. The level defaults from `CAMUNDA_SDK_LOG_LEVEL` (default `error`).
|
|
811
1284
|
|
|
1285
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeLogging -->
|
|
1286
|
+
|
|
812
1287
|
```ts
|
|
813
1288
|
const client = createCamundaClient({
|
|
814
1289
|
log: {
|
|
@@ -860,9 +1335,10 @@ Provide a `transport` function to forward structured `LogEvent` objects into any
|
|
|
860
1335
|
|
|
861
1336
|
#### Pino
|
|
862
1337
|
|
|
1338
|
+
<!-- snippet-exempt: uses external pino dependency -->
|
|
863
1339
|
```ts
|
|
864
1340
|
import pino from 'pino';
|
|
865
|
-
import createCamundaClient from '@camunda8/orchestration-cluster';
|
|
1341
|
+
import createCamundaClient from '@camunda8/orchestration-cluster-api';
|
|
866
1342
|
|
|
867
1343
|
const p = pino();
|
|
868
1344
|
const client = createCamundaClient({
|
|
@@ -878,9 +1354,10 @@ const client = createCamundaClient({
|
|
|
878
1354
|
|
|
879
1355
|
#### Winston
|
|
880
1356
|
|
|
1357
|
+
<!-- snippet-exempt: uses external winston dependency -->
|
|
881
1358
|
```ts
|
|
882
1359
|
import winston from 'winston';
|
|
883
|
-
import createCamundaClient from '@camunda8/orchestration-cluster';
|
|
1360
|
+
import createCamundaClient from '@camunda8/orchestration-cluster-api';
|
|
884
1361
|
|
|
885
1362
|
const w = winston.createLogger({ transports: [new winston.transports.Console()] });
|
|
886
1363
|
const client = createCamundaClient({
|
|
@@ -903,9 +1380,10 @@ const client = createCamundaClient({
|
|
|
903
1380
|
|
|
904
1381
|
#### loglevel
|
|
905
1382
|
|
|
1383
|
+
<!-- snippet-exempt: uses external loglevel dependency -->
|
|
906
1384
|
```ts
|
|
907
1385
|
import log from 'loglevel';
|
|
908
|
-
import createCamundaClient from '@camunda8/orchestration-cluster';
|
|
1386
|
+
import createCamundaClient from '@camunda8/orchestration-cluster-api';
|
|
909
1387
|
|
|
910
1388
|
log.setLevel('info'); // host app level
|
|
911
1389
|
const client = createCamundaClient({
|
|
@@ -946,9 +1424,10 @@ May throw:
|
|
|
946
1424
|
|
|
947
1425
|
All SDK-thrown operational errors normalize to a discriminated union (`SdkError`) when they originate from HTTP, network, auth, or validation layers. Use the guard `isSdkError` to narrow inside a catch:
|
|
948
1426
|
|
|
1427
|
+
<!-- snippet-source: examples/readme-imports.txt,examples/readme.ts | regions: ReadmeErrorHandlingImport+ReadmeErrorHandling -->
|
|
1428
|
+
|
|
949
1429
|
```ts
|
|
950
|
-
import { createCamundaClient } from '@camunda8/orchestration-cluster-api';
|
|
951
|
-
import { isSdkError } from '@camunda8/orchestration-cluster-api/dist/runtime/errors';
|
|
1430
|
+
import { createCamundaClient, isSdkError } from '@camunda8/orchestration-cluster-api';
|
|
952
1431
|
|
|
953
1432
|
const client = createCamundaClient();
|
|
954
1433
|
|
|
@@ -995,13 +1474,15 @@ _Note that this feature is experimental and subject to change._
|
|
|
995
1474
|
|
|
996
1475
|
If you prefer FP‑style explicit error handling instead of exceptions, use the result client wrapper:
|
|
997
1476
|
|
|
1477
|
+
<!-- snippet-source: examples/readme-imports.txt,examples/readme.ts | regions: ReadmeResultClientImport+ReadmeResultClient -->
|
|
1478
|
+
|
|
998
1479
|
```ts
|
|
999
|
-
import { createCamundaResultClient, isOk } from '@camunda8/orchestration-cluster';
|
|
1480
|
+
import { createCamundaResultClient, isOk } from '@camunda8/orchestration-cluster-api';
|
|
1000
1481
|
|
|
1001
1482
|
const camundaR = createCamundaResultClient();
|
|
1002
1483
|
const res = await camundaR.createDeployment({ resources: [file] });
|
|
1003
1484
|
if (isOk(res)) {
|
|
1004
|
-
console.log('Deployment key', res.value.
|
|
1485
|
+
console.log('Deployment key', res.value.deploymentKey);
|
|
1005
1486
|
} else {
|
|
1006
1487
|
console.error('Deployment failed', res.error);
|
|
1007
1488
|
}
|
|
@@ -1015,8 +1496,9 @@ API surface differences:
|
|
|
1015
1496
|
|
|
1016
1497
|
Helpers:
|
|
1017
1498
|
|
|
1499
|
+
<!-- snippet-exempt: one-liner import example -->
|
|
1018
1500
|
```ts
|
|
1019
|
-
import { isOk, isErr } from '@camunda8/orchestration-cluster';
|
|
1501
|
+
import { isOk, isErr } from '@camunda8/orchestration-cluster-api';
|
|
1020
1502
|
```
|
|
1021
1503
|
|
|
1022
1504
|
When to use:
|
|
@@ -1031,8 +1513,9 @@ _Note that this feature is experimental and subject to change._
|
|
|
1031
1513
|
|
|
1032
1514
|
For projects using `fp-ts`, wrap the throwing client in a lazy `TaskEither` facade:
|
|
1033
1515
|
|
|
1516
|
+
<!-- snippet-exempt: requires external fp-ts dependency -->
|
|
1034
1517
|
```ts
|
|
1035
|
-
import { createCamundaFpClient } from '@camunda8/orchestration-cluster';
|
|
1518
|
+
import { createCamundaFpClient } from '@camunda8/orchestration-cluster-api/fp';
|
|
1036
1519
|
import { pipe } from 'fp-ts/function';
|
|
1037
1520
|
import * as TE from 'fp-ts/TaskEither';
|
|
1038
1521
|
|
|
@@ -1080,6 +1563,8 @@ The deployment endpoint requires each resource to have a filename (extension use
|
|
|
1080
1563
|
|
|
1081
1564
|
### Browser
|
|
1082
1565
|
|
|
1566
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeDeployBrowser -->
|
|
1567
|
+
|
|
1083
1568
|
```ts
|
|
1084
1569
|
const bpmnXml = `<definitions id="process" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">...</definitions>`;
|
|
1085
1570
|
const file = new File([bpmnXml], 'order-process.bpmn', { type: 'application/xml' });
|
|
@@ -1089,6 +1574,7 @@ console.log(result.deployments.length);
|
|
|
1089
1574
|
|
|
1090
1575
|
From an existing Blob:
|
|
1091
1576
|
|
|
1577
|
+
<!-- snippet-exempt: uses hypothetical getBlob() function -->
|
|
1092
1578
|
```ts
|
|
1093
1579
|
const blob: Blob = getBlob();
|
|
1094
1580
|
const file = new File([blob], 'model.bpmn');
|
|
@@ -1099,6 +1585,8 @@ await camunda.createDeployment({ resources: [file] });
|
|
|
1099
1585
|
|
|
1100
1586
|
Use the built-in helper `deployResourcesFromFiles(...)` to read local files and create `File` objects automatically. It returns the enriched `ExtendedDeploymentResult` (adds typed arrays: `processes`, `decisions`, `decisionRequirements`, `forms`, `resources`).
|
|
1101
1587
|
|
|
1588
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeDeployNode -->
|
|
1589
|
+
|
|
1102
1590
|
```ts
|
|
1103
1591
|
const result = await camunda.deployResourcesFromFiles([
|
|
1104
1592
|
'./bpmn/order-process.bpmn',
|
|
@@ -1112,12 +1600,14 @@ console.log(result.decisions.length);
|
|
|
1112
1600
|
|
|
1113
1601
|
With explicit tenant (overriding tenant from configuration):
|
|
1114
1602
|
|
|
1603
|
+
<!-- snippet-exempt: small variant of injected deploy example above -->
|
|
1115
1604
|
```ts
|
|
1116
1605
|
await camunda.deployResourcesFromFiles(['./bpmn/order-process.bpmn'], { tenantId: 'tenant-a' });
|
|
1117
1606
|
```
|
|
1118
1607
|
|
|
1119
1608
|
Error handling:
|
|
1120
1609
|
|
|
1610
|
+
<!-- snippet-exempt: small variant of injected deploy example above -->
|
|
1121
1611
|
```ts
|
|
1122
1612
|
try {
|
|
1123
1613
|
await camunda.deployResourcesFromFiles([]); // throws (empty array)
|
|
@@ -1128,6 +1618,7 @@ try {
|
|
|
1128
1618
|
|
|
1129
1619
|
Manual construction alternative (if you need custom logic):
|
|
1130
1620
|
|
|
1621
|
+
<!-- snippet-exempt: alternative construction pattern using node:buffer -->
|
|
1131
1622
|
```ts
|
|
1132
1623
|
import { File } from 'node:buffer';
|
|
1133
1624
|
const bpmnXml =
|
|
@@ -1149,6 +1640,8 @@ Empty arrays are rejected. Always use correct extensions so the server can class
|
|
|
1149
1640
|
|
|
1150
1641
|
Create isolated clients per test file:
|
|
1151
1642
|
|
|
1643
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeTestingClient -->
|
|
1644
|
+
|
|
1152
1645
|
```ts
|
|
1153
1646
|
const client = createCamundaClient({
|
|
1154
1647
|
config: { CAMUNDA_REST_ADDRESS: 'http://localhost:8080', CAMUNDA_AUTH_STRATEGY: 'NONE' },
|
|
@@ -1157,9 +1650,11 @@ const client = createCamundaClient({
|
|
|
1157
1650
|
|
|
1158
1651
|
Inject a mock fetch:
|
|
1159
1652
|
|
|
1653
|
+
<!-- snippet-source: examples/readme.ts | regions: ReadmeTestingMock -->
|
|
1654
|
+
|
|
1160
1655
|
```ts
|
|
1161
1656
|
const client = createCamundaClient({
|
|
1162
|
-
fetch: async (
|
|
1657
|
+
fetch: async (_input, _init) => new Response(JSON.stringify({ ok: true }), { status: 200 }),
|
|
1163
1658
|
});
|
|
1164
1659
|
```
|
|
1165
1660
|
|