@camunda8/orchestration-cluster-api 10.0.0-alpha.41 → 10.0.0-alpha.43

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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [10.0.0-alpha.43](https://github.com/camunda/orchestration-cluster-api-js/compare/v10.0.0-alpha.42...v10.0.0-alpha.43) (2026-08-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * bind SDK cadence to the engine clock with createEngineClock ([#482](https://github.com/camunda/orchestration-cluster-api-js/issues/482)) ([8770212](https://github.com/camunda/orchestration-cluster-api-js/commit/8770212210097606b3400b8b98b6d2b452b1ff83)), closes [#450](https://github.com/camunda/orchestration-cluster-api-js/issues/450) [#450](https://github.com/camunda/orchestration-cluster-api-js/issues/450) [#467](https://github.com/camunda/orchestration-cluster-api-js/issues/467) [#474](https://github.com/camunda/orchestration-cluster-api-js/issues/474) [#476](https://github.com/camunda/orchestration-cluster-api-js/issues/476) [#477](https://github.com/camunda/orchestration-cluster-api-js/issues/477) [#479](https://github.com/camunda/orchestration-cluster-api-js/issues/479)
7
+
8
+ # [10.0.0-alpha.42](https://github.com/camunda/orchestration-cluster-api-js/compare/v10.0.0-alpha.41...v10.0.0-alpha.42) (2026-08-27)
9
+
10
+
11
+ ### Features
12
+
13
+ * add createTestClock and a Clock contract conformance suite ([#479](https://github.com/camunda/orchestration-cluster-api-js/issues/479)) ([5beea42](https://github.com/camunda/orchestration-cluster-api-js/commit/5beea4241d27b01194960bda3555874ec0258bef)), closes [#477](https://github.com/camunda/orchestration-cluster-api-js/issues/477) [#476](https://github.com/camunda/orchestration-cluster-api-js/issues/476) [#451](https://github.com/camunda/orchestration-cluster-api-js/issues/451) [#476](https://github.com/camunda/orchestration-cluster-api-js/issues/476) [#474](https://github.com/camunda/orchestration-cluster-api-js/issues/474)
14
+
1
15
  # [10.0.0-alpha.41](https://github.com/camunda/orchestration-cluster-api-js/compare/v10.0.0-alpha.40...v10.0.0-alpha.41) (2026-08-27)
2
16
 
3
17
 
package/README.md CHANGED
@@ -766,6 +766,111 @@ return ack;
766
766
  const ack2 = await job.ignore();
767
767
  ```
768
768
 
769
+ ### Deterministic Time (`job.clock`)
770
+
771
+ The SDK resolves its own cadence — worker poll intervals, retry backoff, eventual-consistency
772
+ polling, backpressure decay — through an injectable clock. Pinning that clock runs all of it
773
+ on virtual time, so tests that would otherwise wait out a 30-second poll finish immediately.
774
+
775
+ The clock is configured on the client and available as `client.clock`. Handlers reach it as
776
+ `job.clock`, a narrowed view exposing only `now()` and `sleep(ms, signal?)` — `deadline` is
777
+ withheld because a handler that built one against a pinned clock would hang rather than time
778
+ out:
779
+
780
+ <!-- snippet-source: examples/readme.ts | regions: ReadmeHandlerClock -->
781
+
782
+ ```ts
783
+ const startedAt = job.clock.now();
784
+
785
+ // A short back-off around a flaky dependency. Waiting here rather than on
786
+ // setTimeout means a test that pins the client's clock also drives the handler.
787
+ await job.clock.sleep(250);
788
+
789
+ return job.complete({ variables: { waitedMs: job.clock.now() - startedAt } });
790
+ ```
791
+
792
+ Read and wait through `job.clock` rather than `Date.now()` / `setTimeout`, and a test that
793
+ pins the client's clock drives your handler too.
794
+
795
+ `job.clock.sleep` is for **short in-handler coordination** — spacing retries within one job,
796
+ backing off around a flaky dependency. Long or business-meaningful waits belong in the
797
+ process as BPMN timers, where they survive a crash and are visible to operations.
798
+
799
+ Pass `createTestClock()` to pin the clock in your own tests:
800
+
801
+ <!-- snippet-source: examples/readme.ts | regions: ReadmeTestClock -->
802
+
803
+ ```ts
804
+ // Pin the client's clock and the SDK's own cadence runs on virtual time: poll intervals,
805
+ // retry backoff and backpressure decay all settle without waiting in real time.
806
+ const clock = createTestClock({ start: 0, autoAdvance: false });
807
+ const client = createCamundaClient({ clock });
808
+
809
+ // Nothing settles until the test moves time, so start the wait and advance into it.
810
+ const waiting = client.clock.sleep(30_000);
811
+ await clock.advance(30_000);
812
+ await waiting;
813
+
814
+ console.log(client.clock.now()); // 30000
815
+ console.log(clock.sleeps); // [30000] — every duration the SDK asked to wait
816
+ ```
817
+
818
+ `autoAdvance` defaults to `true`, where each sleep settles itself on the next macrotask
819
+ having moved time to its wake point — the SDK's loops make progress without the test driving
820
+ them. Set it to `false`, as above, when you need to assert on state *between* two waits.
821
+
822
+ #### Binding the SDK to the engine clock
823
+
824
+ `createTestClock` pins the SDK in isolation: the engine carries on at real time. When you are
825
+ testing against a live engine, `createEngineClock` binds the two together so they advance as
826
+ one — `sleep` moves engine time forward via `PUT /clock` instead of waiting:
827
+
828
+ <!-- snippet-source: examples/readme.ts | regions: ReadmeEngineClock -->
829
+
830
+ ```ts
831
+ // Bind the SDK's cadence to the engine's own clock. `sleep` no longer waits — it moves
832
+ // engine time forward — so a worker polling for something that never arrives advances the
833
+ // engine instead of burning real seconds.
834
+ //
835
+ // Two clients, deliberately. `client` issues the pins and must stay on the live clock:
836
+ // HTTP retry sleeps on whatever clock its client was given, so pointing the engine clock
837
+ // at its own driver would have a failed pin back off through `sleep`, which issues another
838
+ // pin, and so on.
839
+ const client = createCamundaClient();
840
+ const clock = createEngineClock(client, { start: Date.now() });
841
+ const pinned = createCamundaClient({ clock });
842
+
843
+ await clock.pin(Date.now());
844
+ try {
845
+ // A minute of engine time. BPMN timers due inside it fire; the test does not wait.
846
+ await pinned.clock.sleep(60_000);
847
+ } finally {
848
+ await clock.reset(); // hand the engine back to real time
849
+ }
850
+ ```
851
+
852
+ This is what makes a worker loop deterministic end to end: the poll interval *drives* engine
853
+ time rather than racing it, so a test that would spend a real minute waiting on something
854
+ that never becomes ready finishes as fast as the requests complete.
855
+
856
+ > [!WARNING]
857
+ > Pinning is global to the cluster. Only point an engine clock at an engine you own —
858
+ > never a shared environment. Always `reset()` in a `finally`.
859
+
860
+ > [!IMPORTANT]
861
+ > The client you hand to `createEngineClock` must not itself be configured with that clock.
862
+ > HTTP retry backs off on whatever clock its client was given, so a self-referential setup
863
+ > would have a failed `pinClock` retry through `sleep`, which issues another `pinClock`.
864
+ > Keep the driving client on the live clock, as in the example above.
865
+
866
+ Prefer `createTestClock` over hand-writing a `Clock`. The contract has clauses that are easy
867
+ to get subtly wrong — most notably that `sleep` must not settle in a microtask, because the
868
+ worker schedules its next poll on resolution and would otherwise spin.
869
+
870
+ Two things deliberately stay on real time even when the clock is pinned, so that pinning it
871
+ cannot hang a process: **liveness bounds** (shutdown drain, request and config-fetch
872
+ timeouts) and **observational timestamps** (log, telemetry and support-bundle records).
873
+
769
874
  ### Job Corrections (User Task Listeners)
770
875
 
771
876
  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()`:
@@ -1330,7 +1435,9 @@ Exports available from `.../effect`:
1330
1435
  (`pages()` / `items()` → `Stream`, `toArray()` → `Effect`). See below.
1331
1436
 
1332
1437
  **Clock-class win:** `eventually` / `withTimeout` run on the Effect `Clock`, so `TestClock.adjust`
1333
- advances eventual/timeout deterministically in tests — no real-clock burn.
1438
+ advances eventual/timeout deterministically in tests — no real-clock burn. The Promise surface
1439
+ has the same property via [`createTestClock`](#deterministic-time-jobclock); the difference is
1440
+ that Effect gives you `TestClock` and the rest of the ecosystem for free.
1334
1441
 
1335
1442
  ### Paginated Search as a `Stream`
1336
1443
 
@@ -1441,7 +1548,9 @@ Worker exports from `.../effect`:
1441
1548
 
1442
1549
  **Clock-class win:** the activation poll interval and the handler-retry `Schedule` run on the Effect
1443
1550
  `Clock`, so `TestClock.adjust` bounds activation/retry timing in virtual time — the whole loop is
1444
- deterministic in tests, with no real-clock burn.
1551
+ deterministic in tests, with no real-clock burn. The Promise worker is equally drivable by pinning
1552
+ the client clock (see [Deterministic Time](#deterministic-time-jobclock)); what Effect adds here is
1553
+ `Schedule` composition over the retry policy.
1445
1554
 
1446
1555
  ### Injecting Services into a Handler
1447
1556