@hotmeshio/hotmesh 0.12.1 → 0.14.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 (202) hide show
  1. package/README.md +18 -22
  2. package/build/modules/enums.d.ts +60 -5
  3. package/build/modules/enums.js +62 -7
  4. package/build/modules/errors.d.ts +15 -2
  5. package/build/modules/errors.js +17 -1
  6. package/build/modules/storage.d.ts +1 -0
  7. package/build/modules/storage.js +2 -1
  8. package/build/package.json +8 -2
  9. package/build/services/activities/activity/context.d.ts +22 -0
  10. package/build/services/activities/activity/context.js +76 -0
  11. package/build/services/activities/activity/index.d.ts +116 -0
  12. package/build/services/activities/activity/index.js +299 -0
  13. package/build/services/activities/activity/mapping.d.ts +12 -0
  14. package/build/services/activities/activity/mapping.js +63 -0
  15. package/build/services/activities/activity/process.d.ts +28 -0
  16. package/build/services/activities/activity/process.js +100 -0
  17. package/build/services/activities/activity/protocol.d.ts +39 -0
  18. package/build/services/activities/activity/protocol.js +151 -0
  19. package/build/services/activities/activity/state.d.ts +40 -0
  20. package/build/services/activities/activity/state.js +143 -0
  21. package/build/services/activities/activity/transition.d.ts +23 -0
  22. package/build/services/activities/activity/transition.js +71 -0
  23. package/build/services/activities/activity/verify.d.ts +22 -0
  24. package/build/services/activities/activity/verify.js +85 -0
  25. package/build/services/activities/await.d.ts +1 -4
  26. package/build/services/activities/await.js +2 -36
  27. package/build/services/activities/cycle.d.ts +1 -11
  28. package/build/services/activities/cycle.js +3 -46
  29. package/build/services/activities/hook.d.ts +2 -11
  30. package/build/services/activities/hook.js +30 -50
  31. package/build/services/activities/interrupt.d.ts +2 -4
  32. package/build/services/activities/interrupt.js +4 -38
  33. package/build/services/activities/signal.d.ts +1 -11
  34. package/build/services/activities/signal.js +3 -48
  35. package/build/services/activities/trigger.d.ts +1 -3
  36. package/build/services/activities/trigger.js +0 -3
  37. package/build/services/activities/worker.d.ts +3 -6
  38. package/build/services/activities/worker.js +4 -40
  39. package/build/services/connector/factory.d.ts +6 -0
  40. package/build/services/connector/factory.js +24 -0
  41. package/build/services/dba/index.d.ts +14 -4
  42. package/build/services/dba/index.js +57 -18
  43. package/build/services/durable/activity.d.ts +30 -0
  44. package/build/services/durable/activity.js +46 -0
  45. package/build/services/durable/client.d.ts +26 -31
  46. package/build/services/durable/client.js +26 -31
  47. package/build/services/durable/connection.d.ts +13 -7
  48. package/build/services/durable/connection.js +13 -7
  49. package/build/services/durable/exporter.d.ts +2 -2
  50. package/build/services/durable/exporter.js +27 -12
  51. package/build/services/durable/handle.d.ts +59 -41
  52. package/build/services/durable/handle.js +61 -41
  53. package/build/services/durable/index.d.ts +152 -283
  54. package/build/services/durable/index.js +161 -289
  55. package/build/services/durable/interceptor.d.ts +43 -33
  56. package/build/services/durable/interceptor.js +59 -39
  57. package/build/services/durable/schemas/factory.d.ts +2 -3
  58. package/build/services/durable/schemas/factory.js +180 -30
  59. package/build/services/durable/telemetry.d.ts +80 -0
  60. package/build/services/durable/telemetry.js +137 -0
  61. package/build/services/durable/worker.d.ts +100 -21
  62. package/build/services/durable/worker.js +314 -60
  63. package/build/services/durable/workflow/all.d.ts +1 -1
  64. package/build/services/durable/workflow/all.js +1 -1
  65. package/build/services/durable/workflow/cancellationScope.d.ts +104 -0
  66. package/build/services/durable/workflow/cancellationScope.js +139 -0
  67. package/build/services/durable/workflow/common.d.ts +5 -4
  68. package/build/services/durable/workflow/common.js +6 -1
  69. package/build/services/durable/workflow/{waitFor.d.ts → condition.d.ts} +9 -8
  70. package/build/services/durable/workflow/{waitFor.js → condition.js} +44 -11
  71. package/build/services/durable/workflow/continueAsNew.d.ts +65 -0
  72. package/build/services/durable/workflow/continueAsNew.js +92 -0
  73. package/build/services/durable/workflow/didRun.d.ts +2 -2
  74. package/build/services/durable/workflow/didRun.js +4 -4
  75. package/build/services/durable/workflow/enrich.d.ts +5 -0
  76. package/build/services/durable/workflow/enrich.js +5 -0
  77. package/build/services/durable/workflow/entityMethods.d.ts +7 -0
  78. package/build/services/durable/workflow/entityMethods.js +7 -0
  79. package/build/services/durable/workflow/execHook.js +3 -3
  80. package/build/services/durable/workflow/execHookBatch.js +2 -2
  81. package/build/services/durable/workflow/{execChild.d.ts → executeChild.d.ts} +4 -40
  82. package/build/services/durable/workflow/{execChild.js → executeChild.js} +36 -45
  83. package/build/services/durable/workflow/hook.d.ts +1 -1
  84. package/build/services/durable/workflow/hook.js +4 -3
  85. package/build/services/durable/workflow/index.d.ts +45 -50
  86. package/build/services/durable/workflow/index.js +46 -51
  87. package/build/services/durable/workflow/interruption.d.ts +7 -6
  88. package/build/services/durable/workflow/interruption.js +11 -7
  89. package/build/services/durable/workflow/patched.d.ts +72 -0
  90. package/build/services/durable/workflow/patched.js +110 -0
  91. package/build/services/durable/workflow/proxyActivities.d.ts +7 -7
  92. package/build/services/durable/workflow/proxyActivities.js +51 -15
  93. package/build/services/durable/workflow/searchMethods.d.ts +7 -0
  94. package/build/services/durable/workflow/searchMethods.js +7 -0
  95. package/build/services/durable/workflow/signal.d.ts +4 -4
  96. package/build/services/durable/workflow/signal.js +4 -4
  97. package/build/services/durable/workflow/{sleepFor.d.ts → sleep.d.ts} +7 -7
  98. package/build/services/durable/workflow/{sleepFor.js → sleep.js} +39 -10
  99. package/build/services/durable/workflow/terminate.d.ts +55 -0
  100. package/build/services/durable/workflow/{interrupt.js → terminate.js} +21 -21
  101. package/build/services/durable/workflow/trace.js +2 -2
  102. package/build/services/durable/workflow/uuid4.d.ts +14 -0
  103. package/build/services/durable/workflow/uuid4.js +39 -0
  104. package/build/services/durable/workflow/{context.d.ts → workflowInfo.d.ts} +5 -5
  105. package/build/services/durable/workflow/{context.js → workflowInfo.js} +7 -7
  106. package/build/services/engine/compiler.d.ts +19 -0
  107. package/build/services/engine/compiler.js +20 -0
  108. package/build/services/engine/completion.d.ts +46 -0
  109. package/build/services/engine/completion.js +145 -0
  110. package/build/services/engine/dispatch.d.ts +24 -0
  111. package/build/services/engine/dispatch.js +98 -0
  112. package/build/services/engine/index.d.ts +49 -81
  113. package/build/services/engine/index.js +175 -573
  114. package/build/services/engine/init.d.ts +42 -0
  115. package/build/services/engine/init.js +74 -0
  116. package/build/services/engine/pubsub.d.ts +50 -0
  117. package/build/services/engine/pubsub.js +118 -0
  118. package/build/services/engine/reporting.d.ts +20 -0
  119. package/build/services/engine/reporting.js +38 -0
  120. package/build/services/engine/schema.d.ts +23 -0
  121. package/build/services/engine/schema.js +62 -0
  122. package/build/services/engine/signal.d.ts +57 -0
  123. package/build/services/engine/signal.js +117 -0
  124. package/build/services/engine/state.d.ts +35 -0
  125. package/build/services/engine/state.js +61 -0
  126. package/build/services/engine/version.d.ts +31 -0
  127. package/build/services/engine/version.js +73 -0
  128. package/build/services/hotmesh/deployment.d.ts +21 -0
  129. package/build/services/hotmesh/deployment.js +25 -0
  130. package/build/services/hotmesh/index.d.ts +142 -533
  131. package/build/services/hotmesh/index.js +223 -674
  132. package/build/services/hotmesh/init.d.ts +42 -0
  133. package/build/services/hotmesh/init.js +93 -0
  134. package/build/services/hotmesh/jobs.d.ts +67 -0
  135. package/build/services/hotmesh/jobs.js +99 -0
  136. package/build/services/hotmesh/pubsub.d.ts +38 -0
  137. package/build/services/hotmesh/pubsub.js +54 -0
  138. package/build/services/hotmesh/quorum.d.ts +30 -0
  139. package/build/services/hotmesh/quorum.js +62 -0
  140. package/build/services/hotmesh/validation.d.ts +6 -0
  141. package/build/services/hotmesh/validation.js +28 -0
  142. package/build/services/quorum/index.js +1 -0
  143. package/build/services/router/consumption/index.d.ts +11 -5
  144. package/build/services/router/consumption/index.js +24 -17
  145. package/build/services/router/error-handling/index.d.ts +2 -2
  146. package/build/services/router/error-handling/index.js +14 -14
  147. package/build/services/router/index.d.ts +1 -1
  148. package/build/services/router/index.js +2 -2
  149. package/build/services/serializer/index.d.ts +22 -0
  150. package/build/services/serializer/index.js +39 -1
  151. package/build/services/store/index.d.ts +1 -0
  152. package/build/services/store/providers/postgres/exporter-sql.d.ts +2 -2
  153. package/build/services/store/providers/postgres/exporter-sql.js +4 -4
  154. package/build/services/store/providers/postgres/kvtables.js +7 -6
  155. package/build/services/store/providers/postgres/kvtypes/hash/basic.js +67 -52
  156. package/build/services/store/providers/postgres/kvtypes/hash/jsonb.js +87 -72
  157. package/build/services/store/providers/postgres/kvtypes/hash/udata.js +106 -79
  158. package/build/services/store/providers/postgres/kvtypes/hash/utils.d.ts +16 -0
  159. package/build/services/store/providers/postgres/kvtypes/hash/utils.js +29 -16
  160. package/build/services/store/providers/postgres/postgres.d.ts +1 -0
  161. package/build/services/store/providers/postgres/postgres.js +14 -4
  162. package/build/services/stream/factory.d.ts +3 -1
  163. package/build/services/stream/factory.js +2 -2
  164. package/build/services/stream/index.d.ts +1 -0
  165. package/build/services/stream/providers/nats/nats.d.ts +1 -0
  166. package/build/services/stream/providers/nats/nats.js +1 -0
  167. package/build/services/stream/providers/postgres/credentials.d.ts +56 -0
  168. package/build/services/stream/providers/postgres/credentials.js +129 -0
  169. package/build/services/stream/providers/postgres/kvtables.js +18 -0
  170. package/build/services/stream/providers/postgres/messages.js +7 -7
  171. package/build/services/stream/providers/postgres/notifications.js +16 -2
  172. package/build/services/stream/providers/postgres/postgres.d.ts +7 -0
  173. package/build/services/stream/providers/postgres/postgres.js +35 -4
  174. package/build/services/stream/providers/postgres/procedures.d.ts +21 -0
  175. package/build/services/stream/providers/postgres/procedures.js +213 -0
  176. package/build/services/stream/providers/postgres/secured.d.ts +34 -0
  177. package/build/services/stream/providers/postgres/secured.js +146 -0
  178. package/build/services/stream/providers/postgres/stats.d.ts +1 -0
  179. package/build/services/stream/providers/postgres/stats.js +1 -0
  180. package/build/services/stream/registry.d.ts +1 -1
  181. package/build/services/stream/registry.js +5 -2
  182. package/build/services/telemetry/index.d.ts +10 -1
  183. package/build/services/telemetry/index.js +40 -7
  184. package/build/services/worker/credentials.d.ts +51 -0
  185. package/build/services/worker/credentials.js +87 -0
  186. package/build/services/worker/index.d.ts +2 -2
  187. package/build/services/worker/index.js +7 -6
  188. package/build/types/codec.d.ts +84 -0
  189. package/build/types/codec.js +2 -0
  190. package/build/types/dba.d.ts +39 -3
  191. package/build/types/durable.d.ts +123 -25
  192. package/build/types/error.d.ts +10 -0
  193. package/build/types/exporter.d.ts +1 -1
  194. package/build/types/hotmesh.d.ts +67 -4
  195. package/build/types/index.d.ts +2 -1
  196. package/build/types/provider.d.ts +2 -2
  197. package/build/types/quorum.d.ts +35 -1
  198. package/build/types/stream.d.ts +12 -6
  199. package/package.json +8 -2
  200. package/build/services/activities/activity.d.ts +0 -192
  201. package/build/services/activities/activity.js +0 -786
  202. package/build/services/durable/workflow/interrupt.d.ts +0 -55
@@ -1,786 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Activity = void 0;
4
- const enums_1 = require("../../modules/enums");
5
- const errors_1 = require("../../modules/errors");
6
- const utils_1 = require("../../modules/utils");
7
- const collator_1 = require("../collator");
8
- const mapper_1 = require("../mapper");
9
- const pipe_1 = require("../pipe");
10
- const serializer_1 = require("../serializer");
11
- const telemetry_1 = require("../telemetry");
12
- const stream_1 = require("../../types/stream");
13
- /**
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)
70
- */
71
- class Activity {
72
- constructor(config, data, metadata, hook, engine, context) {
73
- this.status = stream_1.StreamStatus.SUCCESS;
74
- this.code = 200;
75
- this.adjacentIndex = 0;
76
- this.guidLedger = 0;
77
- this.config = config;
78
- this.data = data;
79
- this.metadata = metadata;
80
- this.hook = hook;
81
- this.engine = engine;
82
- this.context = context || { data: {}, metadata: {} };
83
- this.logger = engine.logger;
84
- this.store = engine.store;
85
- }
86
- setLeg(leg) {
87
- this.leg = leg;
88
- }
89
- /**
90
- * A job is assumed to be complete when its status (a semaphore)
91
- * reaches `0`. A different threshold can be set in the
92
- * activity YAML, in support of Dynamic Activation Control.
93
- */
94
- mapStatusThreshold() {
95
- if (this.config.statusThreshold !== undefined) {
96
- const threshold = pipe_1.Pipe.resolve(this.config.statusThreshold, this.context);
97
- if (threshold !== undefined && !isNaN(Number(threshold))) {
98
- return threshold;
99
- }
100
- }
101
- return 0;
102
- }
103
- /**
104
- * Upon entering leg 1 of a duplexed activity
105
- */
106
- async verifyEntry() {
107
- this.setLeg(1);
108
- await this.getState();
109
- const threshold = this.mapStatusThreshold();
110
- try {
111
- collator_1.CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid, threshold);
112
- }
113
- catch (error) {
114
- await collator_1.CollatorService.notarizeEntry(this);
115
- if (threshold > 0) {
116
- if (this.context.metadata.js <= threshold) {
117
- //Dynamic Activation Control: convergent claim — only the
118
- //activity whose HINCRBY reaches exactly 0 runs completion.
119
- const status = await this.setStatus(-threshold);
120
- if (Number(status) === 0) {
121
- const txn = this.store.transact();
122
- await this.engine.runJobCompletionTasks(this.context, {}, txn);
123
- await txn.exec();
124
- }
125
- }
126
- }
127
- else {
128
- throw error;
129
- }
130
- return;
131
- }
132
- await collator_1.CollatorService.notarizeEntry(this);
133
- }
134
- /**
135
- * Upon entering leg 2 of a duplexed activity.
136
- * Increments both the activity ledger (+1) and GUID ledger (+1).
137
- * Stores the GUID ledger value for step-level resume decisions.
138
- */
139
- async verifyReentry() {
140
- const msgGuid = this.context.metadata.guid;
141
- this.setLeg(2);
142
- await this.getState();
143
- this.context.metadata.guid = msgGuid;
144
- collator_1.CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
145
- const [activityLedger, guidLedger] = await collator_1.CollatorService.notarizeLeg2Entry(this, msgGuid);
146
- this.guidLedger = guidLedger;
147
- return activityLedger;
148
- }
149
- //******** DUPLEX RE-ENTRY POINT ********//
150
- async processEvent(status = stream_1.StreamStatus.SUCCESS, code = 200, type = 'output') {
151
- this.setLeg(2);
152
- const jid = this.context.metadata.jid;
153
- if (!jid) {
154
- this.logger.error('activity-process-event-error', {
155
- message: 'job id is undefined',
156
- });
157
- return;
158
- }
159
- const aid = this.metadata.aid;
160
- this.status = status;
161
- this.code = code;
162
- this.logger.debug('activity-process-event', {
163
- topic: this.config.subtype,
164
- jid,
165
- aid,
166
- status,
167
- code,
168
- });
169
- let telemetry;
170
- try {
171
- const collationKey = await this.verifyReentry();
172
- this.adjacentIndex = collator_1.CollatorService.getDimensionalIndex(collationKey);
173
- telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
174
- telemetry.startActivitySpan(this.leg);
175
- //bind data per status type
176
- if (status === stream_1.StreamStatus.ERROR) {
177
- this.bindActivityError(this.data);
178
- this.adjacencyList = await this.filterAdjacent();
179
- if (!this.adjacencyList.length) {
180
- this.bindJobError(this.data);
181
- }
182
- }
183
- else {
184
- this.bindActivityData(type);
185
- this.adjacencyList = await this.filterAdjacent();
186
- }
187
- this.mapJobData();
188
- //When an unrecoverable error has no matching transitions
189
- //(e.g., code 500 from raw errors after retries exhausted),
190
- //mark the job as terminally errored so the step protocol
191
- //can force completion via the isErrorTerminal path.
192
- if (status === stream_1.StreamStatus.ERROR && !this.adjacencyList?.length) {
193
- if (!this.context.data)
194
- this.context.data = {};
195
- this.context.data.done = true;
196
- this.context.data.$error = {
197
- message: this.data?.message || 'unknown error',
198
- code: enums_1.HMSH_CODE_DURABLE_MAXED,
199
- stack: this.data?.stack,
200
- };
201
- }
202
- //determine step parameters
203
- const delta = status === stream_1.StreamStatus.PENDING
204
- ? this.adjacencyList.length
205
- : this.adjacencyList.length - 1;
206
- const shouldFinalize = status !== stream_1.StreamStatus.PENDING;
207
- //execute 3-step protocol
208
- const thresholdHit = await this.executeStepProtocol(delta, shouldFinalize);
209
- //telemetry
210
- telemetry.mapActivityAttributes();
211
- telemetry.setActivityAttributes({});
212
- }
213
- catch (error) {
214
- if (error instanceof errors_1.CollationError) {
215
- this.logger.info(`process-event-${error.fault}-error`, { error });
216
- return;
217
- }
218
- else if (error instanceof errors_1.InactiveJobError) {
219
- this.logger.info('process-event-inactive-job-error', { error });
220
- return;
221
- }
222
- else if (error instanceof errors_1.GenerationalError) {
223
- this.logger.info('process-event-generational-job-error', { error });
224
- return;
225
- }
226
- else if (error instanceof errors_1.GetStateError) {
227
- this.logger.info('process-event-get-job-error', { error });
228
- return;
229
- }
230
- this.logger.error('activity-process-event-error', {
231
- error,
232
- message: error.message,
233
- stack: error.stack,
234
- name: error.name,
235
- });
236
- telemetry?.setActivityError(error.message);
237
- throw error;
238
- }
239
- finally {
240
- telemetry?.endActivitySpan();
241
- this.logger.debug('activity-process-event-end', { jid, aid });
242
- }
243
- }
244
- /**
245
- * Executes the 3-step Leg2 protocol using GUID ledger for
246
- * crash-safe resume. Each step bundles durable writes with
247
- * its concluding digit update in a single transaction.
248
- *
249
- * @returns true if this transition caused the job to complete
250
- */
251
- async executeStepProtocol(delta, shouldFinalize) {
252
- const msgGuid = this.context.metadata.guid;
253
- const threshold = this.mapStatusThreshold();
254
- const { id: appId } = await this.engine.getVID();
255
- //Step 1: Save work (skip if GUID 10B already set)
256
- if (!collator_1.CollatorService.isGuidStep1Done(this.guidLedger)) {
257
- const txn1 = this.store.transact();
258
- await this.setState(txn1);
259
- await collator_1.CollatorService.notarizeStep1(this, msgGuid, txn1);
260
- await txn1.exec();
261
- }
262
- //Step 2: Spawn children + semaphore + edge capture (skip if GUID 1B already set)
263
- let thresholdHit = false;
264
- if (!collator_1.CollatorService.isGuidStep2Done(this.guidLedger)) {
265
- const txn2 = this.store.transact();
266
- //queue step markers first
267
- await collator_1.CollatorService.notarizeStep2(this, msgGuid, txn2);
268
- //queue child publications
269
- for (const child of this.adjacencyList) {
270
- await this.engine.router?.publishMessage(null, child, txn2);
271
- }
272
- //queue semaphore update + edge capture LAST (so result is at end)
273
- await this.store.setStatusAndCollateGuid(delta, threshold, this.context.metadata.jid, appId, msgGuid, collator_1.CollatorService.WEIGHTS.GUID_SNAPSHOT, txn2);
274
- const results = (await txn2.exec());
275
- thresholdHit = this.resolveThresholdHit(results);
276
- this.logger.debug('step-protocol-step2-complete', {
277
- jid: this.context.metadata.jid,
278
- aid: this.metadata.aid,
279
- delta,
280
- threshold,
281
- thresholdHit,
282
- lastResult: results[results.length - 1],
283
- resultCount: results.length,
284
- });
285
- }
286
- else {
287
- //Step 2 already done; check GUID snapshot for edge
288
- thresholdHit = collator_1.CollatorService.isGuidJobClosed(this.guidLedger);
289
- }
290
- //Step 3: Job completion tasks (edge hit OR emit/persist, skip if GUID 100M already set)
291
- //When an activity marks the job done with an unrecoverable error
292
- //(e.g., stopper after max retries), force completion even when the
293
- //semaphore threshold isn't hit (the signaler's +1 contribution
294
- //prevents threshold 0 from matching).
295
- const isErrorTerminal = !thresholdHit
296
- && this.context.data?.done === true
297
- && !!this.context.data?.$error;
298
- const needsCompletion = thresholdHit || this.shouldEmit() || this.shouldPersistJob() || isErrorTerminal;
299
- if (needsCompletion && !collator_1.CollatorService.isGuidStep3Done(this.guidLedger)) {
300
- const txn3 = this.store.transact();
301
- const options = (thresholdHit || isErrorTerminal) ? {} : { emit: !this.shouldPersistJob() };
302
- await this.engine.runJobCompletionTasks(this.context, options, txn3);
303
- await collator_1.CollatorService.notarizeStep3(this, msgGuid, txn3);
304
- const shouldFinalizeNow = (thresholdHit || isErrorTerminal) ? shouldFinalize : this.shouldPersistJob();
305
- if (shouldFinalizeNow) {
306
- await collator_1.CollatorService.notarizeFinalize(this, txn3);
307
- }
308
- await txn3.exec();
309
- }
310
- else if (needsCompletion) {
311
- this.logger.debug('step-protocol-step3-skipped-already-done', {
312
- jid: this.context.metadata.jid,
313
- aid: this.metadata.aid,
314
- });
315
- }
316
- else {
317
- this.logger.debug('step-protocol-no-threshold', {
318
- jid: this.context.metadata.jid,
319
- aid: this.metadata.aid,
320
- thresholdHit,
321
- });
322
- }
323
- return thresholdHit;
324
- }
325
- /**
326
- * Extracts the thresholdHit value from transaction results.
327
- * The setStatusAndCollateGuid result is the last item.
328
- */
329
- resolveThresholdHit(results) {
330
- const last = results[results.length - 1];
331
- const value = Array.isArray(last) ? last[1] : last;
332
- return Number(value) === 1;
333
- }
334
- /**
335
- * Extracts the job status from the last result of a transaction.
336
- * Used by subclass Leg1 process methods for telemetry.
337
- */
338
- resolveStatus(multiResponse) {
339
- const activityStatus = multiResponse[multiResponse.length - 1];
340
- if (Array.isArray(activityStatus)) {
341
- return Number(activityStatus[1]);
342
- }
343
- else {
344
- return Number(activityStatus);
345
- }
346
- }
347
- /**
348
- * Leg1 entry verification for Category B activities (Leg1-only with children).
349
- * Returns true if this is a resume (Leg1 already completed on a prior attempt).
350
- * On resume, loads the GUID ledger for step-level resume decisions.
351
- */
352
- async verifyLeg1Entry() {
353
- const msgGuid = this.context.metadata.guid;
354
- this.setLeg(1);
355
- await this.getState();
356
- this.context.metadata.guid = msgGuid;
357
- const threshold = this.mapStatusThreshold();
358
- try {
359
- collator_1.CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid, threshold);
360
- }
361
- catch (error) {
362
- if (error instanceof errors_1.InactiveJobError && threshold > 0) {
363
- //Dynamic Activation Control: threshold met, close the job
364
- await collator_1.CollatorService.notarizeEntry(this);
365
- if (this.context.metadata.js === threshold) {
366
- //conclude job EXACTLY ONCE
367
- const status = await this.setStatus(-threshold);
368
- if (Number(status) === 0) {
369
- await this.engine.runJobCompletionTasks(this.context);
370
- }
371
- }
372
- }
373
- throw error;
374
- }
375
- try {
376
- await collator_1.CollatorService.notarizeEntry(this);
377
- return false;
378
- }
379
- catch (error) {
380
- if (error instanceof errors_1.CollationError && error.fault === 'duplicate') {
381
- if (this.config.cycle) {
382
- //Cycle re-entry: Leg1 already complete from prior iteration.
383
- //Increment Leg2 counter to derive the new dimensional index,
384
- //so children run in a fresh dimensional plane.
385
- const [activityLedger, guidLedger] = await collator_1.CollatorService.notarizeLeg2Entry(this, msgGuid);
386
- this.adjacentIndex =
387
- collator_1.CollatorService.getDimensionalIndex(activityLedger);
388
- this.guidLedger = guidLedger;
389
- return false;
390
- }
391
- //100B is set — Leg1 work already committed. Load GUID for step resume.
392
- const guidValue = await this.store.collateSynthetic(this.context.metadata.jid, msgGuid, 0);
393
- this.guidLedger = guidValue;
394
- return true;
395
- }
396
- throw error;
397
- }
398
- }
399
- /**
400
- * Executes the 3-step Leg1 protocol for Category B activities
401
- * (Leg1-only with children, e.g., Hook passthrough, Signal, Interrupt-another).
402
- * Uses the incoming Leg1 message GUID as the GUID ledger key.
403
- *
404
- * Step A: setState + notarizeLeg1Completion + step1 markers (transaction 1)
405
- * Step B: publish children + step2 markers + setStatusAndCollateGuid (transaction 2)
406
- * Step C: if edge → runJobCompletionTasks + step3 markers + finalize (transaction 3)
407
- *
408
- * @returns true if this transition caused the job to complete
409
- */
410
- async executeLeg1StepProtocol(delta) {
411
- const msgGuid = this.context.metadata.guid;
412
- const threshold = this.mapStatusThreshold();
413
- const { id: appId } = await this.engine.getVID();
414
- //Step A: Save work + Leg1 completion marker
415
- if (!collator_1.CollatorService.isGuidStep1Done(this.guidLedger)) {
416
- const txn1 = this.store.transact();
417
- await this.setState(txn1);
418
- if (this.adjacentIndex === 0) {
419
- //First entry: mark Leg1 complete. On cycle re-entry
420
- //(adjacentIndex > 0), Leg1 is already complete and the
421
- //Leg2 counter was already incremented by notarizeLeg2Entry.
422
- await collator_1.CollatorService.notarizeLeg1Completion(this, txn1);
423
- }
424
- await collator_1.CollatorService.notarizeStep1(this, msgGuid, txn1);
425
- await txn1.exec();
426
- }
427
- //Step B: Spawn children + semaphore + edge capture
428
- let thresholdHit = false;
429
- if (!collator_1.CollatorService.isGuidStep2Done(this.guidLedger)) {
430
- const txn2 = this.store.transact();
431
- await collator_1.CollatorService.notarizeStep2(this, msgGuid, txn2);
432
- for (const child of this.adjacencyList) {
433
- await this.engine.router?.publishMessage(null, child, txn2);
434
- }
435
- await this.store.setStatusAndCollateGuid(delta, threshold, this.context.metadata.jid, appId, msgGuid, collator_1.CollatorService.WEIGHTS.GUID_SNAPSHOT, txn2);
436
- const results = (await txn2.exec());
437
- thresholdHit = this.resolveThresholdHit(results);
438
- this.logger.debug('leg1-step-protocol-stepB-complete', {
439
- jid: this.context.metadata.jid,
440
- aid: this.metadata.aid,
441
- delta,
442
- threshold,
443
- thresholdHit,
444
- lastResult: results[results.length - 1],
445
- });
446
- }
447
- else {
448
- thresholdHit = collator_1.CollatorService.isGuidJobClosed(this.guidLedger);
449
- }
450
- //Step C: Job completion tasks (edge hit OR emit/persist)
451
- //When an activity marks the job done with an unrecoverable error
452
- //(e.g., stopper after max retries), force completion even when the
453
- //semaphore threshold isn't hit.
454
- const isErrorTerminal = !thresholdHit
455
- && this.context.data?.done === true
456
- && !!this.context.data?.$error;
457
- const needsCompletion = thresholdHit || this.shouldEmit() || this.shouldPersistJob() || isErrorTerminal;
458
- if (needsCompletion && !collator_1.CollatorService.isGuidStep3Done(this.guidLedger)) {
459
- const txn3 = this.store.transact();
460
- const options = (thresholdHit || isErrorTerminal) ? {} : { emit: !this.shouldPersistJob() };
461
- await this.engine.runJobCompletionTasks(this.context, options, txn3);
462
- await collator_1.CollatorService.notarizeStep3(this, msgGuid, txn3);
463
- await collator_1.CollatorService.notarizeFinalize(this, txn3);
464
- await txn3.exec();
465
- }
466
- return thresholdHit;
467
- }
468
- mapJobData() {
469
- if (this.config.job?.maps) {
470
- const mapper = new mapper_1.MapperService((0, utils_1.deepCopy)(this.config.job.maps), this.context);
471
- const output = mapper.mapRules();
472
- if (output) {
473
- for (const key in output) {
474
- const f1 = key.indexOf('[');
475
- //keys with array notation suffix `somekey[]` represent
476
- //dynamically-keyed mappings whose `value` must be moved to the output.
477
- //The `value` must be an object with keys appropriate to the
478
- //notation type: `somekey[0] (array)`, `somekey[-] (mark)`, OR `somekey[_] (search)`
479
- if (f1 > -1) {
480
- const amount = key.substring(f1 + 1).split(']')[0];
481
- if (!isNaN(Number(amount))) {
482
- const left = key.substring(0, f1);
483
- output[left] = output[key];
484
- delete output[key];
485
- }
486
- else if (amount === '-' || amount === '_') {
487
- const obj = output[key];
488
- Object.keys(obj).forEach((newKey) => {
489
- output[newKey] = obj[newKey];
490
- });
491
- }
492
- }
493
- }
494
- }
495
- this.context.data = output;
496
- }
497
- }
498
- mapInputData() {
499
- if (this.config.input?.maps) {
500
- const mapper = new mapper_1.MapperService((0, utils_1.deepCopy)(this.config.input.maps), this.context);
501
- this.context.data = mapper.mapRules();
502
- }
503
- }
504
- mapOutputData() {
505
- //activity YAML may include output map data that produces/extends activity output data.
506
- if (this.config.output?.maps) {
507
- const mapper = new mapper_1.MapperService((0, utils_1.deepCopy)(this.config.output.maps), this.context);
508
- const actOutData = mapper.mapRules();
509
- const activityId = this.metadata.aid;
510
- const data = { ...this.context[activityId].output, ...actOutData };
511
- this.context[activityId].output.data = data;
512
- }
513
- }
514
- async registerTimeout() {
515
- //set timeout in support of hook and/or duplex
516
- }
517
- /**
518
- * Any StreamMessage with a status of ERROR is bound to the activity
519
- */
520
- bindActivityError(data) {
521
- const md = this.context[this.metadata.aid].output.metadata;
522
- md.err = JSON.stringify(this.data);
523
- //(temporary...useful for mapping error parts in the app.yaml)
524
- md.$error = { ...data, is_stream_error: true };
525
- }
526
- /**
527
- * unhandled activity errors (activities that return an ERROR StreamMessage
528
- * status and have no adjacent children to transition to) are bound to the job
529
- */
530
- bindJobError(data) {
531
- this.context.metadata.err = JSON.stringify({
532
- ...data,
533
- is_stream_error: true,
534
- });
535
- }
536
- async getTriggerConfig() {
537
- return await this.store.getSchema(this.config.trigger, await this.engine.getVID());
538
- }
539
- getJobStatus() {
540
- return null;
541
- }
542
- async setStatus(amount, transaction) {
543
- const { id: appId } = await this.engine.getVID();
544
- return await this.store.setStatus(amount, this.context.metadata.jid, appId, transaction);
545
- }
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 [];
550
- }
551
- bindDimensionalAddress(state) {
552
- const dad = this.resolveDad();
553
- state[`${this.metadata.aid}/output/metadata/dad`] = dad;
554
- }
555
- async setState(transaction) {
556
- const jobId = this.context.metadata.jid;
557
- this.bindJobMetadata();
558
- this.bindActivityMetadata();
559
- const state = {};
560
- await this.bindJobState(state);
561
- const presets = this.authorizeEntry(state);
562
- this.bindDimensionalAddress(state);
563
- this.bindActivityState(state);
564
- //symbolNames holds symkeys
565
- const symbolNames = [
566
- `$${this.config.subscribes}`,
567
- this.metadata.aid,
568
- ...presets,
569
- ];
570
- const dIds = collator_1.CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], this.resolveDad());
571
- return await this.store.setState(state, this.getJobStatus(), jobId, symbolNames, dIds, transaction);
572
- }
573
- bindJobMetadata() {
574
- //both legs of the most recently run activity (1 and 2) modify ju (job_updated)
575
- this.context.metadata.ju = (0, utils_1.formatISODate)(new Date());
576
- }
577
- bindActivityMetadata() {
578
- const self = this.context['$self'];
579
- if (!self.output.metadata) {
580
- self.output.metadata = {};
581
- }
582
- if (this.status === stream_1.StreamStatus.ERROR) {
583
- self.output.metadata.err = JSON.stringify(this.data);
584
- }
585
- const ts = (0, utils_1.formatISODate)(new Date());
586
- self.output.metadata.ac = ts;
587
- self.output.metadata.au = ts;
588
- self.output.metadata.atp = this.config.type;
589
- if (this.config.subtype) {
590
- self.output.metadata.stp = this.config.subtype;
591
- }
592
- self.output.metadata.aid = this.metadata.aid;
593
- }
594
- async bindJobState(state) {
595
- const triggerConfig = await this.getTriggerConfig();
596
- const PRODUCES = [
597
- ...(triggerConfig.PRODUCES || []),
598
- ...this.bindJobMetadataPaths(),
599
- ];
600
- for (const path of PRODUCES) {
601
- const value = (0, utils_1.getValueByPath)(this.context, path);
602
- if (value !== undefined) {
603
- state[path] = value;
604
- }
605
- }
606
- for (const key in this.context?.data ?? {}) {
607
- if (key.startsWith('-') || key.startsWith('_')) {
608
- state[key] = this.context.data[key];
609
- }
610
- }
611
- telemetry_1.TelemetryService.bindJobTelemetryToState(state, this.config, this.context);
612
- }
613
- bindActivityState(state) {
614
- const produces = [
615
- ...this.config.produces,
616
- ...this.bindActivityMetadataPaths(),
617
- ];
618
- for (const path of produces) {
619
- const prefixedPath = `${this.metadata.aid}/${path}`;
620
- const value = (0, utils_1.getValueByPath)(this.context, prefixedPath);
621
- if (value !== undefined) {
622
- state[prefixedPath] = value;
623
- }
624
- }
625
- telemetry_1.TelemetryService.bindActivityTelemetryToState(state, this.config, this.metadata, this.context, this.leg);
626
- }
627
- bindJobMetadataPaths() {
628
- return serializer_1.MDATA_SYMBOLS.JOB_UPDATE.KEYS.map((key) => `metadata/${key}`);
629
- }
630
- bindActivityMetadataPaths() {
631
- const keys_to_save = this.leg === 1 ? 'ACTIVITY' : 'ACTIVITY_UPDATE';
632
- return serializer_1.MDATA_SYMBOLS[keys_to_save].KEYS.map((key) => `output/metadata/${key}`);
633
- }
634
- async getState() {
635
- const gid = this.context.metadata.gid;
636
- const jobSymbolHashName = `$${this.config.subscribes}`;
637
- const consumes = {
638
- [jobSymbolHashName]: serializer_1.MDATA_SYMBOLS.JOB.KEYS.map((key) => `metadata/${key}`),
639
- };
640
- for (let [activityId, paths] of Object.entries(this.config.consumes)) {
641
- if (activityId === '$job') {
642
- for (const path of paths) {
643
- consumes[jobSymbolHashName].push(path);
644
- }
645
- }
646
- else {
647
- if (activityId === '$self') {
648
- activityId = this.metadata.aid;
649
- }
650
- if (!consumes[activityId]) {
651
- consumes[activityId] = [];
652
- }
653
- for (const path of paths) {
654
- consumes[activityId].push(`${activityId}/${path}`);
655
- }
656
- }
657
- }
658
- telemetry_1.TelemetryService.addTargetTelemetryPaths(consumes, this.config, this.metadata, this.leg);
659
- const { dad, jid } = this.context.metadata;
660
- const dIds = collator_1.CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad || '');
661
- //`state` is a unidimensional hash; context is a tree
662
- const [state, _status] = await this.store.getState(jid, consumes, dIds);
663
- this.context = (0, utils_1.restoreHierarchy)(state);
664
- this.assertGenerationalId(this.context?.metadata?.gid, gid);
665
- this.initDimensionalAddress(dad);
666
- this.initSelf(this.context);
667
- this.initPolicies(this.context);
668
- }
669
- /**
670
- * if the job is created/deleted/created with the same key,
671
- * the 'gid' ensures no stale messages (such as sleep delays)
672
- * enter the workstream. Any message with a mismatched gid
673
- * belongs to a prior job and can safely be ignored/dropped.
674
- */
675
- assertGenerationalId(jobGID, msgGID) {
676
- if (msgGID !== jobGID) {
677
- throw new errors_1.GenerationalError(jobGID, msgGID, this.context?.metadata?.jid ?? '', this.context?.metadata?.aid ?? '', this.context?.metadata?.dad ?? '');
678
- }
679
- }
680
- initDimensionalAddress(dad) {
681
- this.metadata.dad = dad;
682
- }
683
- initSelf(context) {
684
- const activityId = this.metadata.aid;
685
- if (!context[activityId]) {
686
- context[activityId] = {};
687
- }
688
- const self = context[activityId];
689
- if (!self.output) {
690
- self.output = {};
691
- }
692
- if (!self.input) {
693
- self.input = {};
694
- }
695
- if (!self.hook) {
696
- self.hook = {};
697
- }
698
- if (!self.output.metadata) {
699
- self.output.metadata = {};
700
- }
701
- //prebind the updated timestamp (mappings need the time)
702
- self.output.metadata.au = (0, utils_1.formatISODate)(new Date());
703
- context['$self'] = self;
704
- context['$job'] = context; //NEVER call STRINGIFY! (now circular)
705
- return context;
706
- }
707
- initPolicies(context) {
708
- const expire = pipe_1.Pipe.resolve(this.config.expire ?? enums_1.HMSH_EXPIRE_DURATION, context);
709
- context.metadata.expire = expire;
710
- if (this.config.persistent != undefined) {
711
- const persistent = pipe_1.Pipe.resolve(this.config.persistent ?? false, context);
712
- context.metadata.persistent = persistent;
713
- }
714
- }
715
- bindActivityData(type) {
716
- this.context[this.metadata.aid][type].data = this.data;
717
- }
718
- resolveDad() {
719
- let dad = this.metadata.dad;
720
- if (this.adjacentIndex > 0) {
721
- //if adjacent index > 0 the activity is cycling; replace last index with cycle index
722
- dad = `${dad.substring(0, dad.lastIndexOf(','))},${this.adjacentIndex}`;
723
- }
724
- return dad;
725
- }
726
- resolveAdjacentDad() {
727
- //concat self and child dimension (all children (leg 1) begin life at 0)
728
- return `${this.resolveDad()}${collator_1.CollatorService.getDimensionalSeed(0)}`;
729
- }
730
- async filterAdjacent() {
731
- const adjacencyList = [];
732
- const transitions = await this.store.getTransitions(await this.engine.getVID());
733
- const transition = transitions[`.${this.metadata.aid}`];
734
- //resolve the dimensional address for adjacent children
735
- const adjacentDad = this.resolveAdjacentDad();
736
- if (transition) {
737
- for (const toActivityId in transition) {
738
- const transitionRule = transition[toActivityId];
739
- if (mapper_1.MapperService.evaluate(transitionRule, this.context, this.code)) {
740
- adjacencyList.push({
741
- metadata: {
742
- guid: (0, utils_1.guid)(),
743
- jid: this.context.metadata.jid,
744
- gid: this.context.metadata.gid,
745
- dad: adjacentDad,
746
- aid: toActivityId,
747
- spn: this.context['$self'].output.metadata?.l2s,
748
- trc: this.context.metadata.trc,
749
- },
750
- type: stream_1.StreamDataType.TRANSITION,
751
- data: {},
752
- });
753
- }
754
- }
755
- }
756
- return adjacencyList;
757
- }
758
- isJobComplete(jobStatus) {
759
- return jobStatus <= 0;
760
- }
761
- shouldEmit() {
762
- if (this.config.emit) {
763
- return pipe_1.Pipe.resolve(this.config.emit, this.context) === true;
764
- }
765
- return false;
766
- }
767
- /**
768
- * emits the job completed event while leaving the job active, allowing
769
- * a `main` thread to exit while other threads continue to run.
770
- * @private
771
- */
772
- shouldPersistJob() {
773
- if (this.config.persist !== undefined) {
774
- return pipe_1.Pipe.resolve(this.config.persist, this.context) === true;
775
- }
776
- return false;
777
- }
778
- /**
779
- * A job with a vale < -100_000_000 is considered interrupted,
780
- * as the interruption event decrements the job status by 1billion.
781
- */
782
- jobWasInterrupted(jobStatus) {
783
- return jobStatus < -100000000;
784
- }
785
- }
786
- exports.Activity = Activity;