@hotmeshio/hotmesh 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/README.md +158 -38
  3. package/build/package.json +62 -67
  4. package/build/services/activities/activity.d.ts +58 -7
  5. package/build/services/activities/activity.js +66 -37
  6. package/build/services/activities/await.d.ts +101 -0
  7. package/build/services/activities/await.js +101 -0
  8. package/build/services/activities/cycle.d.ts +82 -0
  9. package/build/services/activities/cycle.js +86 -8
  10. package/build/services/activities/hook.d.ts +139 -1
  11. package/build/services/activities/hook.js +140 -2
  12. package/build/services/activities/interrupt.d.ts +112 -0
  13. package/build/services/activities/interrupt.js +118 -5
  14. package/build/services/activities/signal.d.ts +108 -3
  15. package/build/services/activities/signal.js +113 -8
  16. package/build/services/activities/trigger.d.ts +56 -4
  17. package/build/services/activities/trigger.js +119 -35
  18. package/build/services/activities/worker.d.ts +107 -0
  19. package/build/services/activities/worker.js +107 -0
  20. package/build/services/collator/index.d.ts +3 -15
  21. package/build/services/collator/index.js +7 -34
  22. package/build/services/engine/index.d.ts +18 -2
  23. package/build/services/engine/index.js +14 -4
  24. package/build/services/exporter/index.d.ts +2 -0
  25. package/build/services/exporter/index.js +1 -0
  26. package/build/services/hotmesh/index.d.ts +471 -236
  27. package/build/services/hotmesh/index.js +473 -238
  28. package/build/services/memflow/client.js +2 -2
  29. package/build/services/memflow/handle.js +1 -1
  30. package/build/services/memflow/index.d.ts +1 -1
  31. package/build/services/memflow/index.js +1 -1
  32. package/build/services/memflow/workflow/all.d.ts +28 -3
  33. package/build/services/memflow/workflow/all.js +28 -3
  34. package/build/services/memflow/workflow/context.d.ts +44 -1
  35. package/build/services/memflow/workflow/context.js +44 -1
  36. package/build/services/memflow/workflow/didRun.d.ts +23 -3
  37. package/build/services/memflow/workflow/didRun.js +23 -3
  38. package/build/services/memflow/workflow/emit.d.ts +43 -4
  39. package/build/services/memflow/workflow/emit.js +43 -4
  40. package/build/services/memflow/workflow/enrich.d.ts +32 -4
  41. package/build/services/memflow/workflow/enrich.js +32 -4
  42. package/build/services/memflow/workflow/entityMethods.d.ts +54 -7
  43. package/build/services/memflow/workflow/entityMethods.js +54 -7
  44. package/build/services/memflow/workflow/execChild.d.ts +96 -8
  45. package/build/services/memflow/workflow/execChild.js +96 -8
  46. package/build/services/memflow/workflow/execHook.d.ts +54 -39
  47. package/build/services/memflow/workflow/execHook.js +52 -38
  48. package/build/services/memflow/workflow/execHookBatch.d.ts +82 -29
  49. package/build/services/memflow/workflow/execHookBatch.js +80 -28
  50. package/build/services/memflow/workflow/hook.d.ts +68 -3
  51. package/build/services/memflow/workflow/hook.js +69 -4
  52. package/build/services/memflow/workflow/index.d.ts +65 -10
  53. package/build/services/memflow/workflow/index.js +65 -10
  54. package/build/services/memflow/workflow/interrupt.d.ts +50 -4
  55. package/build/services/memflow/workflow/interrupt.js +50 -4
  56. package/build/services/memflow/workflow/interruption.d.ts +49 -16
  57. package/build/services/memflow/workflow/interruption.js +49 -16
  58. package/build/services/memflow/workflow/isSideEffectAllowed.d.ts +21 -4
  59. package/build/services/memflow/workflow/isSideEffectAllowed.js +21 -4
  60. package/build/services/memflow/workflow/proxyActivities.d.ts +70 -42
  61. package/build/services/memflow/workflow/proxyActivities.js +70 -42
  62. package/build/services/memflow/workflow/random.d.ts +33 -3
  63. package/build/services/memflow/workflow/random.js +33 -3
  64. package/build/services/memflow/workflow/searchMethods.d.ts +49 -2
  65. package/build/services/memflow/workflow/searchMethods.js +49 -2
  66. package/build/services/memflow/workflow/signal.d.ts +51 -22
  67. package/build/services/memflow/workflow/signal.js +52 -23
  68. package/build/services/memflow/workflow/sleepFor.d.ts +57 -18
  69. package/build/services/memflow/workflow/sleepFor.js +57 -18
  70. package/build/services/memflow/workflow/trace.d.ts +39 -6
  71. package/build/services/memflow/workflow/trace.js +39 -6
  72. package/build/services/memflow/workflow/waitFor.d.ts +55 -18
  73. package/build/services/memflow/workflow/waitFor.js +55 -18
  74. package/build/services/store/index.d.ts +1 -1
  75. package/build/services/store/providers/postgres/postgres.d.ts +1 -1
  76. package/build/services/store/providers/postgres/postgres.js +4 -3
  77. package/build/services/telemetry/index.js +6 -0
  78. package/build/types/activity.d.ts +1 -1
  79. package/build/types/hotmesh.d.ts +1 -1
  80. package/build/types/job.d.ts +1 -1
  81. package/build/types/memflow.d.ts +1 -1
  82. package/build/types/quorum.d.ts +2 -2
  83. package/build/vitest.config.d.ts +2 -0
  84. package/build/vitest.config.js +18 -0
  85. package/package.json +62 -67
  86. package/vitest.config.ts +17 -0
@@ -11,7 +11,62 @@ const serializer_1 = require("../serializer");
11
11
  const telemetry_1 = require("../telemetry");
12
12
  const stream_1 = require("../../types/stream");
13
13
  /**
14
- * The base class for all activities
14
+ * Base class for all HotMesh activity types. Activities are the execution
15
+ * units within a YAML-defined workflow graph. Each activity represents a
16
+ * node in a Directed Acyclic Graph (DAG) that the engine orchestrates.
17
+ *
18
+ * ## Activity Categories
19
+ *
20
+ * Activities fall into three execution categories:
21
+ *
22
+ * - **Category A (Duplex)**: Two-phase activities with Leg 1 (dispatch) and
23
+ * Leg 2 (response). Used by `Worker` and `Await`. Leg 1
24
+ * publishes a message and waits; Leg 2 handles the response via
25
+ * `processEvent` and transitions to adjacent activities.
26
+ *
27
+ * - **Category B (Leg1-only with children)**: Single-phase activities that
28
+ * execute work and transition to children using the crash-safe
29
+ * `executeLeg1StepProtocol`. Used by `Hook` (passthrough mode),
30
+ * `Signal`, and `Interrupt` (target mode).
31
+ *
32
+ * - **Category C (Leg1-only, no children)**: Terminal activities that
33
+ * execute without spawning children. Used by `Interrupt` (self mode).
34
+ *
35
+ * ## Shared YAML Configuration
36
+ *
37
+ * All activity types support these base properties in the YAML descriptor:
38
+ *
39
+ * | Property | Type | Description |
40
+ * |----------------------|---------|-------------|
41
+ * | `type` | string | Activity type: `trigger`, `worker`, `await`, `hook`, `signal`, `interrupt`, `cycle` |
42
+ * | `title` | string | Human-readable label for the activity |
43
+ * | `input.schema` | object | JSON Schema for input validation |
44
+ * | `input.maps` | object | Maps data from other activities into this activity's input |
45
+ * | `output.schema` | object | JSON Schema for output validation |
46
+ * | `output.maps` | object | Maps/transforms the activity's own output data |
47
+ * | `job.maps` | object | Maps activity data to the shared job state |
48
+ * | `emit` | boolean | If `true`, emits a message to the graph's `publishes` topic |
49
+ * | `persist` | boolean | If `true`, emits the job-completed event while keeping the job active |
50
+ * | `expire` | number | Seconds until the job expires after completion (`-1` = forever) |
51
+ * | `statusThreshold` | number | Custom semaphore threshold for Dynamic Activation Control |
52
+ * | `cycle` | boolean | If `true`, leaves Leg 2 open so the activity can be re-entered |
53
+ *
54
+ * ## Data Mapping Syntax
55
+ *
56
+ * Mapping expressions use curly-brace references to bind data between
57
+ * activities and the shared job state:
58
+ *
59
+ * ```yaml
60
+ * input:
61
+ * maps:
62
+ * x: '{t1.output.data.fieldName}' # reference another activity's output
63
+ * y: '{$self.output.data.fieldName}' # reference own output
64
+ * z: '{$job.data.fieldName}' # reference shared job state
65
+ * s: '{$app.settings.configKey}' # reference app-level settings
66
+ * ```
67
+ *
68
+ * @see {@link https://hotmeshio.github.io/sdk-typescript/docs/quickstart | Quick Start Guide}
69
+ * @see [Model Driven Development](https://hotmeshio.github.io/sdk-typescript/docs/model_driven_development)
15
70
  */
16
71
  class Activity {
17
72
  constructor(config, data, metadata, hook, engine, context) {
@@ -57,13 +112,15 @@ class Activity {
57
112
  }
58
113
  catch (error) {
59
114
  await collator_1.CollatorService.notarizeEntry(this);
60
- //todo: confirm this check is still needed; the edge event cleanup should handle fully
61
115
  if (threshold > 0) {
62
- if (this.context.metadata.js === threshold) {
63
- //conclude job EXACTLY ONCE
116
+ if (this.context.metadata.js <= threshold) {
117
+ //Dynamic Activation Control: convergent claim — only the
118
+ //activity whose HINCRBY reaches exactly 0 runs completion.
64
119
  const status = await this.setStatus(-threshold);
65
120
  if (Number(status) === 0) {
66
- await this.engine.runJobCompletionTasks(this.context);
121
+ const txn = this.store.transact();
122
+ await this.engine.runJobCompletionTasks(this.context, {}, txn);
123
+ await txn.exec();
67
124
  }
68
125
  }
69
126
  }
@@ -486,13 +543,10 @@ class Activity {
486
543
  const { id: appId } = await this.engine.getVID();
487
544
  return await this.store.setStatus(amount, this.context.metadata.jid, appId, transaction);
488
545
  }
489
- authorizeEntry(state) {
490
- //pre-authorize activity state to allow entry for adjacent activities
491
- return (this.adjacencyList?.map((streamData) => {
492
- const { metadata: { aid }, } = streamData;
493
- state[`${aid}/output/metadata/as`] = collator_1.CollatorService.getSeed();
494
- return aid;
495
- }) ?? []);
546
+ authorizeEntry(_state) {
547
+ //seed writes removed: child activities increment from 0 (null field).
548
+ //FINALIZE (200T) sets pos 1 directly to 2 without needing a 100T base.
549
+ return [];
496
550
  }
497
551
  bindDimensionalAddress(state) {
498
552
  const dad = this.resolveDad();
@@ -721,31 +775,6 @@ class Activity {
721
775
  }
722
776
  return false;
723
777
  }
724
- /**
725
- * Transition method for Category C (Leg1-only, no children, no semaphore change)
726
- * and Category D (Trigger) activities. NOT used by the Leg2 step protocol.
727
- */
728
- async transition(adjacencyList, jobStatus) {
729
- if (this.jobWasInterrupted(jobStatus)) {
730
- return;
731
- }
732
- let mIds = [];
733
- if (this.shouldEmit() ||
734
- this.isJobComplete(jobStatus) ||
735
- this.shouldPersistJob()) {
736
- await this.engine.runJobCompletionTasks(this.context, {
737
- emit: !this.isJobComplete(jobStatus) && !this.shouldPersistJob(),
738
- });
739
- }
740
- if (adjacencyList.length && !this.isJobComplete(jobStatus)) {
741
- const transaction = this.store.transact();
742
- for (const execSignal of adjacencyList) {
743
- await this.engine.router?.publishMessage(null, execSignal, transaction);
744
- }
745
- mIds = (await transaction.exec());
746
- }
747
- return mIds;
748
- }
749
778
  /**
750
779
  * A job with a vale < -100_000_000 is considered interrupted,
751
780
  * as the interruption event decrements the job status by 1billion.
@@ -3,6 +3,107 @@ import { ActivityData, ActivityMetadata, AwaitActivity, ActivityType } from '../
3
3
  import { ProviderTransaction } from '../../types/provider';
4
4
  import { JobState } from '../../types/job';
5
5
  import { Activity } from './activity';
6
+ /**
7
+ * Invokes another graph (sub-flow) and optionally waits for its completion.
8
+ * The `await` activity enables compositional workflows where one graph
9
+ * triggers another by publishing to its `subscribes` topic, creating a
10
+ * parent-child relationship between flows.
11
+ *
12
+ * ## YAML Configuration
13
+ *
14
+ * The `topic` in the await activity must match the `subscribes` topic of
15
+ * the child graph. Both graphs are defined in the same app YAML:
16
+ *
17
+ * ```yaml
18
+ * app:
19
+ * id: myapp
20
+ * version: '1'
21
+ * graphs:
22
+ *
23
+ * # ── Parent graph ──────────────────────────────
24
+ * - subscribes: order.placed
25
+ * expire: 120
26
+ *
27
+ * activities:
28
+ * t1:
29
+ * type: trigger
30
+ * job:
31
+ * maps:
32
+ * orderId: '{$self.output.data.id}'
33
+ *
34
+ * a1:
35
+ * type: await
36
+ * topic: approval.requested # ◄── targets the child graph's subscribes
37
+ * await: true
38
+ * input:
39
+ * schema:
40
+ * type: object
41
+ * properties:
42
+ * orderId: { type: string }
43
+ * maps:
44
+ * orderId: '{t1.output.data.id}'
45
+ * output:
46
+ * schema:
47
+ * type: object
48
+ * properties:
49
+ * approved: { type: boolean }
50
+ * job:
51
+ * maps:
52
+ * approval: '{$self.output.data.approved}'
53
+ *
54
+ * done:
55
+ * type: hook
56
+ *
57
+ * transitions:
58
+ * t1:
59
+ * - to: a1
60
+ * a1:
61
+ * - to: done
62
+ *
63
+ * # ── Child graph (invoked by the await) ────────
64
+ * - subscribes: approval.requested # ◄── matched by the await activity's topic
65
+ * publishes: approval.completed
66
+ * expire: 60
67
+ *
68
+ * activities:
69
+ * t1:
70
+ * type: trigger
71
+ * review:
72
+ * type: worker
73
+ * topic: approval.review
74
+ *
75
+ * transitions:
76
+ * t1:
77
+ * - to: review
78
+ * ```
79
+ *
80
+ * ## Fire-and-Forget Mode
81
+ *
82
+ * When `await` is explicitly set to `false`, the activity starts the child
83
+ * flow but does not wait for its completion. The parent flow immediately
84
+ * continues. The child's `job_id` is returned as the output.
85
+ *
86
+ * ```yaml
87
+ * a1:
88
+ * type: await
89
+ * topic: background.process
90
+ * await: false
91
+ * job:
92
+ * maps:
93
+ * childJobId: '{$self.output.data.job_id}'
94
+ * ```
95
+ *
96
+ * ## Execution Model
97
+ *
98
+ * Await is a **Category A (duplex)** activity:
99
+ * - **Leg 1** (`process`): Maps input data and publishes a
100
+ * `StreamDataType.AWAIT` message to the engine stream. The engine
101
+ * starts the child flow.
102
+ * - **Leg 2** (`processEvent`, inherited): Receives the child flow's
103
+ * final output, maps output data, and transitions to adjacent activities.
104
+ *
105
+ * @see {@link AwaitActivity} for the TypeScript interface
106
+ */
6
107
  declare class Await extends Activity {
7
108
  config: AwaitActivity;
8
109
  constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
@@ -8,6 +8,107 @@ const pipe_1 = require("../pipe");
8
8
  const telemetry_1 = require("../telemetry");
9
9
  const stream_1 = require("../../types/stream");
10
10
  const activity_1 = require("./activity");
11
+ /**
12
+ * Invokes another graph (sub-flow) and optionally waits for its completion.
13
+ * The `await` activity enables compositional workflows where one graph
14
+ * triggers another by publishing to its `subscribes` topic, creating a
15
+ * parent-child relationship between flows.
16
+ *
17
+ * ## YAML Configuration
18
+ *
19
+ * The `topic` in the await activity must match the `subscribes` topic of
20
+ * the child graph. Both graphs are defined in the same app YAML:
21
+ *
22
+ * ```yaml
23
+ * app:
24
+ * id: myapp
25
+ * version: '1'
26
+ * graphs:
27
+ *
28
+ * # ── Parent graph ──────────────────────────────
29
+ * - subscribes: order.placed
30
+ * expire: 120
31
+ *
32
+ * activities:
33
+ * t1:
34
+ * type: trigger
35
+ * job:
36
+ * maps:
37
+ * orderId: '{$self.output.data.id}'
38
+ *
39
+ * a1:
40
+ * type: await
41
+ * topic: approval.requested # ◄── targets the child graph's subscribes
42
+ * await: true
43
+ * input:
44
+ * schema:
45
+ * type: object
46
+ * properties:
47
+ * orderId: { type: string }
48
+ * maps:
49
+ * orderId: '{t1.output.data.id}'
50
+ * output:
51
+ * schema:
52
+ * type: object
53
+ * properties:
54
+ * approved: { type: boolean }
55
+ * job:
56
+ * maps:
57
+ * approval: '{$self.output.data.approved}'
58
+ *
59
+ * done:
60
+ * type: hook
61
+ *
62
+ * transitions:
63
+ * t1:
64
+ * - to: a1
65
+ * a1:
66
+ * - to: done
67
+ *
68
+ * # ── Child graph (invoked by the await) ────────
69
+ * - subscribes: approval.requested # ◄── matched by the await activity's topic
70
+ * publishes: approval.completed
71
+ * expire: 60
72
+ *
73
+ * activities:
74
+ * t1:
75
+ * type: trigger
76
+ * review:
77
+ * type: worker
78
+ * topic: approval.review
79
+ *
80
+ * transitions:
81
+ * t1:
82
+ * - to: review
83
+ * ```
84
+ *
85
+ * ## Fire-and-Forget Mode
86
+ *
87
+ * When `await` is explicitly set to `false`, the activity starts the child
88
+ * flow but does not wait for its completion. The parent flow immediately
89
+ * continues. The child's `job_id` is returned as the output.
90
+ *
91
+ * ```yaml
92
+ * a1:
93
+ * type: await
94
+ * topic: background.process
95
+ * await: false
96
+ * job:
97
+ * maps:
98
+ * childJobId: '{$self.output.data.job_id}'
99
+ * ```
100
+ *
101
+ * ## Execution Model
102
+ *
103
+ * Await is a **Category A (duplex)** activity:
104
+ * - **Leg 1** (`process`): Maps input data and publishes a
105
+ * `StreamDataType.AWAIT` message to the engine stream. The engine
106
+ * starts the child flow.
107
+ * - **Leg 2** (`processEvent`, inherited): Receives the child flow's
108
+ * final output, maps output data, and transitions to adjacent activities.
109
+ *
110
+ * @see {@link AwaitActivity} for the TypeScript interface
111
+ */
11
112
  class Await extends activity_1.Activity {
12
113
  constructor(config, data, metadata, hook, engine, context) {
13
114
  super(config, data, metadata, hook, engine, context);
@@ -3,6 +3,88 @@ import { ActivityData, ActivityMetadata, ActivityType, CycleActivity } from '../
3
3
  import { ProviderTransaction } from '../../types/provider';
4
4
  import { JobState } from '../../types/job';
5
5
  import { Activity } from './activity';
6
+ /**
7
+ * Re-executes an ancestor activity in a new dimensional thread, enabling
8
+ * retry loops and iterative patterns without violating the DAG constraint.
9
+ * The `cycle` activity targets a specific ancestor (typically a
10
+ * `Hook` with `cycle: true`) and sends execution back to that point.
11
+ *
12
+ * Each cycle iteration runs in a fresh **dimensional thread** — individual
13
+ * activity state is isolated per iteration, while **shared job state**
14
+ * (`job.maps`) accumulates across iterations. This pattern enables retries,
15
+ * polling loops, and iterative processing.
16
+ *
17
+ * ## YAML Configuration
18
+ *
19
+ * ```yaml
20
+ * app:
21
+ * id: myapp
22
+ * version: '1'
23
+ * graphs:
24
+ * - subscribes: retry.start
25
+ * expire: 300
26
+ *
27
+ * activities:
28
+ * t1:
29
+ * type: trigger
30
+ *
31
+ * pivot:
32
+ * type: hook
33
+ * cycle: true # marks this activity as a cycle target
34
+ * output:
35
+ * maps:
36
+ * retryCount: 0
37
+ *
38
+ * do_work:
39
+ * type: worker
40
+ * topic: work.do
41
+ * output:
42
+ * schema:
43
+ * type: object
44
+ * properties:
45
+ * result: { type: string }
46
+ *
47
+ * retry:
48
+ * type: cycle
49
+ * ancestor: pivot # re-execute from this activity
50
+ * input:
51
+ * maps:
52
+ * retryCount: # increment retry counter each cycle
53
+ * '@pipe':
54
+ * - ['{pivot.output.data.retryCount}', 1]
55
+ * - ['{@math.add}']
56
+ *
57
+ * done:
58
+ * type: hook
59
+ *
60
+ * transitions:
61
+ * t1:
62
+ * - to: pivot
63
+ * pivot:
64
+ * - to: do_work
65
+ * do_work:
66
+ * - to: retry
67
+ * conditions:
68
+ * code: 500 # cycle on error
69
+ * - to: done
70
+ * ```
71
+ *
72
+ * ## Key Behaviors
73
+ *
74
+ * - The `ancestor` field must reference an activity with `cycle: true`.
75
+ * - The cycle activity's `input.maps` override the ancestor's output data
76
+ * for the next iteration, allowing each cycle to pass different values.
77
+ * - Dimensional isolation ensures parallel cycle iterations don't collide.
78
+ *
79
+ * ## Execution Model
80
+ *
81
+ * Cycle is a **Category A (Leg 1 only)** activity:
82
+ * - Maps input data, resolves the re-entry dimensional address, and
83
+ * publishes a stream message addressed to the ancestor activity.
84
+ * - The ancestor re-enters via its Leg 2 path in the new dimension.
85
+ *
86
+ * @see {@link CycleActivity} for the TypeScript interface
87
+ */
6
88
  declare class Cycle extends Activity {
7
89
  config: CycleActivity;
8
90
  constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
@@ -6,6 +6,88 @@ const utils_1 = require("../../modules/utils");
6
6
  const collator_1 = require("../collator");
7
7
  const telemetry_1 = require("../telemetry");
8
8
  const activity_1 = require("./activity");
9
+ /**
10
+ * Re-executes an ancestor activity in a new dimensional thread, enabling
11
+ * retry loops and iterative patterns without violating the DAG constraint.
12
+ * The `cycle` activity targets a specific ancestor (typically a
13
+ * `Hook` with `cycle: true`) and sends execution back to that point.
14
+ *
15
+ * Each cycle iteration runs in a fresh **dimensional thread** — individual
16
+ * activity state is isolated per iteration, while **shared job state**
17
+ * (`job.maps`) accumulates across iterations. This pattern enables retries,
18
+ * polling loops, and iterative processing.
19
+ *
20
+ * ## YAML Configuration
21
+ *
22
+ * ```yaml
23
+ * app:
24
+ * id: myapp
25
+ * version: '1'
26
+ * graphs:
27
+ * - subscribes: retry.start
28
+ * expire: 300
29
+ *
30
+ * activities:
31
+ * t1:
32
+ * type: trigger
33
+ *
34
+ * pivot:
35
+ * type: hook
36
+ * cycle: true # marks this activity as a cycle target
37
+ * output:
38
+ * maps:
39
+ * retryCount: 0
40
+ *
41
+ * do_work:
42
+ * type: worker
43
+ * topic: work.do
44
+ * output:
45
+ * schema:
46
+ * type: object
47
+ * properties:
48
+ * result: { type: string }
49
+ *
50
+ * retry:
51
+ * type: cycle
52
+ * ancestor: pivot # re-execute from this activity
53
+ * input:
54
+ * maps:
55
+ * retryCount: # increment retry counter each cycle
56
+ * '@pipe':
57
+ * - ['{pivot.output.data.retryCount}', 1]
58
+ * - ['{@math.add}']
59
+ *
60
+ * done:
61
+ * type: hook
62
+ *
63
+ * transitions:
64
+ * t1:
65
+ * - to: pivot
66
+ * pivot:
67
+ * - to: do_work
68
+ * do_work:
69
+ * - to: retry
70
+ * conditions:
71
+ * code: 500 # cycle on error
72
+ * - to: done
73
+ * ```
74
+ *
75
+ * ## Key Behaviors
76
+ *
77
+ * - The `ancestor` field must reference an activity with `cycle: true`.
78
+ * - The cycle activity's `input.maps` override the ancestor's output data
79
+ * for the next iteration, allowing each cycle to pass different values.
80
+ * - Dimensional isolation ensures parallel cycle iterations don't collide.
81
+ *
82
+ * ## Execution Model
83
+ *
84
+ * Cycle is a **Category A (Leg 1 only)** activity:
85
+ * - Maps input data, resolves the re-entry dimensional address, and
86
+ * publishes a stream message addressed to the ancestor activity.
87
+ * - The ancestor re-enters via its Leg 2 path in the new dimension.
88
+ *
89
+ * @see {@link CycleActivity} for the TypeScript interface
90
+ */
9
91
  class Cycle extends activity_1.Activity {
10
92
  constructor(config, data, metadata, hook, engine, context) {
11
93
  super(config, data, metadata, hook, engine, context);
@@ -23,23 +105,19 @@ class Cycle extends activity_1.Activity {
23
105
  telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
24
106
  telemetry.startActivitySpan(this.leg);
25
107
  this.mapInputData();
26
- //set state/status
27
- let transaction = this.store.transact();
108
+ //set state/status, cycle ancestor, and mark Leg1 complete — single transaction
109
+ const transaction = this.store.transact();
28
110
  await this.setState(transaction);
29
111
  await this.setStatus(0, transaction); //leg 1 never changes job status
112
+ const messageId = await this.cycleAncestorActivity(transaction);
113
+ await collator_1.CollatorService.notarizeLeg1Completion(this, transaction);
30
114
  const txResponse = (await transaction.exec());
31
115
  telemetry.mapActivityAttributes();
32
116
  const jobStatus = this.resolveStatus(txResponse);
33
- //cycle the target ancestor
34
- transaction = this.store.transact();
35
- const messageId = await this.cycleAncestorActivity(transaction);
36
117
  telemetry.setActivityAttributes({
37
118
  'app.activity.mid': messageId,
38
119
  'app.job.jss': jobStatus,
39
120
  });
40
- //exit early (`Cycle` activities only execute Leg 1)
41
- await collator_1.CollatorService.notarizeLeg1Completion(this, transaction);
42
- (await transaction.exec());
43
121
  return this.context.metadata.aid;
44
122
  }
45
123
  catch (error) {
@@ -7,7 +7,145 @@ import { ProviderTransaction } from '../../types/provider';
7
7
  import { StreamCode, StreamStatus } from '../../types/stream';
8
8
  import { Activity } from './activity';
9
9
  /**
10
- * Supports `signal hook`, `time hook`, and `cycle hook` patterns
10
+ * A versatile pause/resume activity that supports three distinct patterns:
11
+ * **time hook** (sleep), **web hook** (external signal), and **passthrough**
12
+ * (immediate transition with optional data mapping).
13
+ *
14
+ * The hook activity is the most flexible activity type. Depending on its
15
+ * YAML configuration, it operates in one of the following modes:
16
+ *
17
+ * ## Time Hook (Sleep)
18
+ *
19
+ * Pauses the flow for a specified duration in seconds. The `sleep` value
20
+ * can be a literal number or a `@pipe` expression for dynamic delays
21
+ * (e.g., exponential backoff).
22
+ *
23
+ * ```yaml
24
+ * app:
25
+ * id: myapp
26
+ * version: '1'
27
+ * graphs:
28
+ * - subscribes: job.start
29
+ * expire: 300
30
+ *
31
+ * activities:
32
+ * t1:
33
+ * type: trigger
34
+ *
35
+ * delay:
36
+ * type: hook
37
+ * sleep: 60 # pause for 60 seconds
38
+ * job:
39
+ * maps:
40
+ * paused_at: '{$self.output.metadata.ac}'
41
+ *
42
+ * resume:
43
+ * type: hook
44
+ *
45
+ * transitions:
46
+ * t1:
47
+ * - to: delay
48
+ * delay:
49
+ * - to: resume
50
+ * ```
51
+ *
52
+ * ## Web Hook (External Signal)
53
+ *
54
+ * Registers a webhook listener on a named topic. The flow pauses until
55
+ * an external signal is sent to the hook's topic. The signal data becomes
56
+ * available as `$self.hook.data`. The `hooks` section at the graph level
57
+ * routes incoming signals to the waiting activity.
58
+ *
59
+ * ```yaml
60
+ * app:
61
+ * id: myapp
62
+ * version: '1'
63
+ * graphs:
64
+ * - subscribes: order.placed
65
+ * expire: 3600
66
+ *
67
+ * activities:
68
+ * t1:
69
+ * type: trigger
70
+ *
71
+ * wait_for_approval:
72
+ * type: hook
73
+ * hook:
74
+ * type: object
75
+ * properties:
76
+ * approved: { type: boolean }
77
+ * job:
78
+ * maps:
79
+ * approved: '{$self.hook.data.approved}'
80
+ *
81
+ * done:
82
+ * type: hook
83
+ *
84
+ * transitions:
85
+ * t1:
86
+ * - to: wait_for_approval
87
+ * wait_for_approval:
88
+ * - to: done
89
+ *
90
+ * hooks:
91
+ * order.approval: # external topic that delivers the signal
92
+ * - to: wait_for_approval
93
+ * conditions:
94
+ * match:
95
+ * - expected: '{t1.output.data.id}'
96
+ * actual: '{$self.hook.data.id}'
97
+ * ```
98
+ *
99
+ * ## Passthrough (No Hook)
100
+ *
101
+ * When neither `sleep` nor `hook` is configured, the hook activity acts
102
+ * as a passthrough: it maps data and immediately transitions to children.
103
+ * This is useful for data transformation, convergence points, or as a
104
+ * cycle pivot (with `cycle: true`).
105
+ *
106
+ * ```yaml
107
+ * app:
108
+ * id: myapp
109
+ * version: '1'
110
+ * graphs:
111
+ * - subscribes: job.start
112
+ *
113
+ * activities:
114
+ * t1:
115
+ * type: trigger
116
+ *
117
+ * pivot:
118
+ * type: hook
119
+ * cycle: true # enables re-entry from a cycle activity
120
+ * output:
121
+ * maps:
122
+ * retryCount: 0
123
+ * job:
124
+ * maps:
125
+ * counter: '{$self.output.data.retryCount}'
126
+ *
127
+ * do_work:
128
+ * type: worker
129
+ * topic: work.do
130
+ *
131
+ * transitions:
132
+ * t1:
133
+ * - to: pivot
134
+ * pivot:
135
+ * - to: do_work
136
+ * ```
137
+ *
138
+ * ## Execution Model
139
+ *
140
+ * - **With `sleep` or `hook`**: Category A (duplex). Leg 1 registers the
141
+ * hook and saves state. Leg 2 fires when the timer expires or the
142
+ * external signal arrives (via `processTimeHookEvent` or
143
+ * `processWebHookEvent`).
144
+ * - **Without `sleep` or `hook`**: Category B (passthrough). Uses the
145
+ * crash-safe `executeLeg1StepProtocol` to map data and transition
146
+ * to adjacent activities.
147
+ *
148
+ * @see {@link HookActivity} for the TypeScript interface
11
149
  */
12
150
  declare class Hook extends Activity {
13
151
  config: HookActivity;