@hotmeshio/hotmesh 0.14.9 → 0.15.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/build/package.json +1 -1
- package/build/services/durable/exporter.d.ts +103 -11
- package/build/services/durable/exporter.js +103 -11
- package/build/services/durable/handle.d.ts +40 -8
- package/build/services/durable/handle.js +40 -8
- package/build/services/durable/schemas/factory.d.ts +1 -1
- package/build/services/durable/schemas/factory.js +1 -13
- package/build/services/exporter/index.d.ts +56 -17
- package/build/services/exporter/index.js +56 -17
- package/build/services/stream/providers/postgres/kvtables.js +26 -12
- package/build/services/stream/providers/postgres/messages.js +23 -2
- package/build/services/stream/providers/postgres/procedures.js +23 -1
- package/build/types/exporter.d.ts +58 -1
- package/package.json +1 -1
package/build/package.json
CHANGED
|
@@ -41,6 +41,32 @@ declare function isSystemActivity(name: string): boolean;
|
|
|
41
41
|
* both the `done` flag and the semaphore to determine status.
|
|
42
42
|
*/
|
|
43
43
|
declare function mapStatus(rawStatus: number | undefined, isDone?: boolean, hasError?: boolean): WorkflowExecutionStatus;
|
|
44
|
+
/**
|
|
45
|
+
* Exports durable workflow execution data in two formats:
|
|
46
|
+
*
|
|
47
|
+
* ### Raw export (`export()`)
|
|
48
|
+
* Returns a {@link DurableJobExport} with five sections:
|
|
49
|
+
* - **data** — workflow input arguments (what was passed to `workflow.start()`)
|
|
50
|
+
* - **state** — current workflow state: `done` flag, `response`, `$error`, timestamps (`jc`/`ju`)
|
|
51
|
+
* - **status** — HotMesh semaphore (`0` = complete, `> 0` = activities pending, `< 0` = failed)
|
|
52
|
+
* - **timeline** — ordered list of idempotent operations: proxy activities, child workflows,
|
|
53
|
+
* sleeps, signals, and collated (Promise.all) results with per-entry timing and output
|
|
54
|
+
* - **transitions** — activity execution log with `created`/`updated` timestamps per dimension
|
|
55
|
+
*
|
|
56
|
+
* Use `allow`/`block` options to limit which sections are returned (e.g., omit
|
|
57
|
+
* `transitions` to reduce payload size). Use `values: false` to strip result
|
|
58
|
+
* payloads from timeline entries.
|
|
59
|
+
*
|
|
60
|
+
* ### Execution history (`exportExecution()`)
|
|
61
|
+
* Returns a {@link WorkflowExecution} — a Temporal-style event history with
|
|
62
|
+
* typed events (`activity_task_scheduled`, `timer_fired`, `workflow_execution_signaled`, etc.),
|
|
63
|
+
* chronological ordering, back-references between scheduled/completed pairs, and a summary
|
|
64
|
+
* with activity/child/timer/signal counts.
|
|
65
|
+
*
|
|
66
|
+
* Supports **sparse** mode (default, no extra I/O) and **verbose** mode (recursively
|
|
67
|
+
* fetches child workflow histories up to `max_depth`). Use `enrich_inputs` to attach
|
|
68
|
+
* activity and child workflow input arguments to events.
|
|
69
|
+
*/
|
|
44
70
|
declare class ExporterService {
|
|
45
71
|
appId: string;
|
|
46
72
|
logger: ILogger;
|
|
@@ -49,18 +75,72 @@ declare class ExporterService {
|
|
|
49
75
|
private static symbols;
|
|
50
76
|
constructor(appId: string, store: StoreService<ProviderClient, ProviderTransaction>, logger: ILogger);
|
|
51
77
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
78
|
+
* Export the raw workflow job as a structured {@link DurableJobExport}.
|
|
79
|
+
*
|
|
80
|
+
* The result contains five sections (filterable via `options.allow` / `options.block`):
|
|
81
|
+
*
|
|
82
|
+
* | Section | Contents |
|
|
83
|
+
* |---|---|
|
|
84
|
+
* | `data` | Workflow input arguments passed to `workflow.start()` |
|
|
85
|
+
* | `state` | Current state: `done`, `response`, `$error`, timestamps |
|
|
86
|
+
* | `status` | Semaphore value: `0` = idle, `> 0` = pending, `< 0` = error |
|
|
87
|
+
* | `timeline` | Ordered idempotent markers: activities, children, sleeps, signals |
|
|
88
|
+
* | `transitions` | Per-activity execution timestamps by dimension |
|
|
89
|
+
*
|
|
90
|
+
* @param jobId - the workflow ID to export
|
|
91
|
+
* @param options - controls which sections to include and whether to include values
|
|
92
|
+
* @returns the exported workflow data
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* // Full export
|
|
97
|
+
* const full = await handle.export();
|
|
98
|
+
*
|
|
99
|
+
* // Timeline only, without result payloads (smaller response)
|
|
100
|
+
* const slim = await handle.export({ allow: ['timeline'], values: false });
|
|
101
|
+
* ```
|
|
54
102
|
*/
|
|
55
103
|
export(jobId: string, options?: ExportOptions): Promise<DurableJobExport>;
|
|
56
104
|
/**
|
|
57
|
-
* Export a workflow execution as a structured event history.
|
|
105
|
+
* Export a workflow execution as a structured event history ({@link WorkflowExecution}).
|
|
106
|
+
*
|
|
107
|
+
* Returns a Temporal-style event list with typed events, chronological ordering,
|
|
108
|
+
* back-references (e.g., `scheduled_event_id` on completed activities), and a
|
|
109
|
+
* {@link WorkflowExecutionSummary} with counts by category.
|
|
110
|
+
*
|
|
111
|
+
* **Event types produced:**
|
|
112
|
+
* - `workflow_execution_started` / `completed` / `failed` — lifecycle bookends
|
|
113
|
+
* - `activity_task_scheduled` / `completed` / `failed` — proxy activity calls
|
|
114
|
+
* - `child_workflow_execution_started` / `completed` / `failed` — child workflows
|
|
115
|
+
* - `timer_started` / `timer_fired` — `workflow.sleep()` calls
|
|
116
|
+
* - `workflow_execution_signaled` — `workflow.condition()` signals received
|
|
117
|
+
*
|
|
118
|
+
* **Modes:**
|
|
119
|
+
* - `sparse` (default) — transforms the workflow's timeline markers into events.
|
|
120
|
+
* No extra database queries beyond the initial job export.
|
|
121
|
+
* - `verbose` — recursively fetches child workflow jobs and attaches their full
|
|
122
|
+
* event histories as nested `children` (up to `max_depth`, default 5).
|
|
123
|
+
*
|
|
124
|
+
* **Options:**
|
|
125
|
+
* - `exclude_system` — omit internal/interceptor activities (names starting with `lt`)
|
|
126
|
+
* - `omit_results` — strip `result` and `input` payloads from event attributes
|
|
127
|
+
* - `enrich_inputs` — attach activity/child workflow input arguments to events
|
|
128
|
+
* - `allow_direct_query` — fallback to raw DB queries for expired/pruned jobs
|
|
58
129
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
130
|
+
* @param jobId - the workflow ID
|
|
131
|
+
* @param workflowTopic - the task queue topic (used as `workflow_type`)
|
|
132
|
+
* @param options - controls mode, filtering, and enrichment
|
|
61
133
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* // Sparse export with system activities filtered out
|
|
137
|
+
* const exec = await handle.exportExecution({ exclude_system: true });
|
|
138
|
+
* console.log(exec.summary.activities.user); // user activity count
|
|
139
|
+
*
|
|
140
|
+
* // Verbose export with full child workflow trees
|
|
141
|
+
* const deep = await handle.exportExecution({ mode: 'verbose', max_depth: 3 });
|
|
142
|
+
* console.log(deep.children); // nested WorkflowExecution[]
|
|
143
|
+
* ```
|
|
64
144
|
*/
|
|
65
145
|
exportExecution(jobId: string, workflowTopic: string, options?: ExecutionExportOptions): Promise<WorkflowExecution>;
|
|
66
146
|
/**
|
|
@@ -88,10 +168,22 @@ declare class ExporterService {
|
|
|
88
168
|
*/
|
|
89
169
|
private fetchChildren;
|
|
90
170
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
171
|
+
* Inflate a raw Redis/Postgres job hash into a structured {@link DurableJobExport}.
|
|
172
|
+
*
|
|
173
|
+
* HotMesh stores workflow state as a flat hash with 3-character symbolized keys
|
|
174
|
+
* (e.g., `aBC,0,0` → `worker/output/data`). This method decodes each entry and
|
|
175
|
+
* sorts it into one of four buckets:
|
|
176
|
+
*
|
|
177
|
+
* - **Transitions** (`aBC,0,0` pattern) — activity start/stop timestamps by dimension
|
|
178
|
+
* - **Data** (`_`-prefixed keys) — workflow input arguments
|
|
179
|
+
* - **Timeline** (`-`-prefixed keys) — idempotent operation markers (proxy, child, sleep, etc.)
|
|
180
|
+
* - **State** (3-char keys) — workflow metadata (done flag, response, error, timestamps)
|
|
181
|
+
*
|
|
182
|
+
* Use `options.allow` / `options.block` to limit which sections appear in the result.
|
|
183
|
+
*
|
|
184
|
+
* @param jobHash - the raw key-value hash from the store
|
|
185
|
+
* @param options - filtering and value options
|
|
186
|
+
* @returns structured export with data, state, status, timeline, and transitions
|
|
95
187
|
*/
|
|
96
188
|
inflate(jobHash: StringStringType, options: ExportOptions): DurableJobExport;
|
|
97
189
|
resolveValue(raw: string, withValues: boolean): Record<string, any> | string | number | null;
|
|
@@ -153,6 +153,32 @@ function computeSummary(events) {
|
|
|
153
153
|
return summary;
|
|
154
154
|
}
|
|
155
155
|
// ── Exporter Service ─────────────────────────────────────────────────────────
|
|
156
|
+
/**
|
|
157
|
+
* Exports durable workflow execution data in two formats:
|
|
158
|
+
*
|
|
159
|
+
* ### Raw export (`export()`)
|
|
160
|
+
* Returns a {@link DurableJobExport} with five sections:
|
|
161
|
+
* - **data** — workflow input arguments (what was passed to `workflow.start()`)
|
|
162
|
+
* - **state** — current workflow state: `done` flag, `response`, `$error`, timestamps (`jc`/`ju`)
|
|
163
|
+
* - **status** — HotMesh semaphore (`0` = complete, `> 0` = activities pending, `< 0` = failed)
|
|
164
|
+
* - **timeline** — ordered list of idempotent operations: proxy activities, child workflows,
|
|
165
|
+
* sleeps, signals, and collated (Promise.all) results with per-entry timing and output
|
|
166
|
+
* - **transitions** — activity execution log with `created`/`updated` timestamps per dimension
|
|
167
|
+
*
|
|
168
|
+
* Use `allow`/`block` options to limit which sections are returned (e.g., omit
|
|
169
|
+
* `transitions` to reduce payload size). Use `values: false` to strip result
|
|
170
|
+
* payloads from timeline entries.
|
|
171
|
+
*
|
|
172
|
+
* ### Execution history (`exportExecution()`)
|
|
173
|
+
* Returns a {@link WorkflowExecution} — a Temporal-style event history with
|
|
174
|
+
* typed events (`activity_task_scheduled`, `timer_fired`, `workflow_execution_signaled`, etc.),
|
|
175
|
+
* chronological ordering, back-references between scheduled/completed pairs, and a summary
|
|
176
|
+
* with activity/child/timer/signal counts.
|
|
177
|
+
*
|
|
178
|
+
* Supports **sparse** mode (default, no extra I/O) and **verbose** mode (recursively
|
|
179
|
+
* fetches child workflow histories up to `max_depth`). Use `enrich_inputs` to attach
|
|
180
|
+
* activity and child workflow input arguments to events.
|
|
181
|
+
*/
|
|
156
182
|
class ExporterService {
|
|
157
183
|
constructor(appId, store, logger) {
|
|
158
184
|
this.appId = appId;
|
|
@@ -160,8 +186,30 @@ class ExporterService {
|
|
|
160
186
|
this.store = store;
|
|
161
187
|
}
|
|
162
188
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
189
|
+
* Export the raw workflow job as a structured {@link DurableJobExport}.
|
|
190
|
+
*
|
|
191
|
+
* The result contains five sections (filterable via `options.allow` / `options.block`):
|
|
192
|
+
*
|
|
193
|
+
* | Section | Contents |
|
|
194
|
+
* |---|---|
|
|
195
|
+
* | `data` | Workflow input arguments passed to `workflow.start()` |
|
|
196
|
+
* | `state` | Current state: `done`, `response`, `$error`, timestamps |
|
|
197
|
+
* | `status` | Semaphore value: `0` = idle, `> 0` = pending, `< 0` = error |
|
|
198
|
+
* | `timeline` | Ordered idempotent markers: activities, children, sleeps, signals |
|
|
199
|
+
* | `transitions` | Per-activity execution timestamps by dimension |
|
|
200
|
+
*
|
|
201
|
+
* @param jobId - the workflow ID to export
|
|
202
|
+
* @param options - controls which sections to include and whether to include values
|
|
203
|
+
* @returns the exported workflow data
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```typescript
|
|
207
|
+
* // Full export
|
|
208
|
+
* const full = await handle.export();
|
|
209
|
+
*
|
|
210
|
+
* // Timeline only, without result payloads (smaller response)
|
|
211
|
+
* const slim = await handle.export({ allow: ['timeline'], values: false });
|
|
212
|
+
* ```
|
|
165
213
|
*/
|
|
166
214
|
async export(jobId, options = {}) {
|
|
167
215
|
if (!ExporterService.symbols.has(this.appId)) {
|
|
@@ -173,13 +221,45 @@ class ExporterService {
|
|
|
173
221
|
return jobExport;
|
|
174
222
|
}
|
|
175
223
|
/**
|
|
176
|
-
* Export a workflow execution as a structured event history.
|
|
224
|
+
* Export a workflow execution as a structured event history ({@link WorkflowExecution}).
|
|
225
|
+
*
|
|
226
|
+
* Returns a Temporal-style event list with typed events, chronological ordering,
|
|
227
|
+
* back-references (e.g., `scheduled_event_id` on completed activities), and a
|
|
228
|
+
* {@link WorkflowExecutionSummary} with counts by category.
|
|
229
|
+
*
|
|
230
|
+
* **Event types produced:**
|
|
231
|
+
* - `workflow_execution_started` / `completed` / `failed` — lifecycle bookends
|
|
232
|
+
* - `activity_task_scheduled` / `completed` / `failed` — proxy activity calls
|
|
233
|
+
* - `child_workflow_execution_started` / `completed` / `failed` — child workflows
|
|
234
|
+
* - `timer_started` / `timer_fired` — `workflow.sleep()` calls
|
|
235
|
+
* - `workflow_execution_signaled` — `workflow.condition()` signals received
|
|
236
|
+
*
|
|
237
|
+
* **Modes:**
|
|
238
|
+
* - `sparse` (default) — transforms the workflow's timeline markers into events.
|
|
239
|
+
* No extra database queries beyond the initial job export.
|
|
240
|
+
* - `verbose` — recursively fetches child workflow jobs and attaches their full
|
|
241
|
+
* event histories as nested `children` (up to `max_depth`, default 5).
|
|
242
|
+
*
|
|
243
|
+
* **Options:**
|
|
244
|
+
* - `exclude_system` — omit internal/interceptor activities (names starting with `lt`)
|
|
245
|
+
* - `omit_results` — strip `result` and `input` payloads from event attributes
|
|
246
|
+
* - `enrich_inputs` — attach activity/child workflow input arguments to events
|
|
247
|
+
* - `allow_direct_query` — fallback to raw DB queries for expired/pruned jobs
|
|
177
248
|
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
249
|
+
* @param jobId - the workflow ID
|
|
250
|
+
* @param workflowTopic - the task queue topic (used as `workflow_type`)
|
|
251
|
+
* @param options - controls mode, filtering, and enrichment
|
|
180
252
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```typescript
|
|
255
|
+
* // Sparse export with system activities filtered out
|
|
256
|
+
* const exec = await handle.exportExecution({ exclude_system: true });
|
|
257
|
+
* console.log(exec.summary.activities.user); // user activity count
|
|
258
|
+
*
|
|
259
|
+
* // Verbose export with full child workflow trees
|
|
260
|
+
* const deep = await handle.exportExecution({ mode: 'verbose', max_depth: 3 });
|
|
261
|
+
* console.log(deep.children); // nested WorkflowExecution[]
|
|
262
|
+
* ```
|
|
183
263
|
*/
|
|
184
264
|
async exportExecution(jobId, workflowTopic, options = {}) {
|
|
185
265
|
let execution;
|
|
@@ -845,10 +925,22 @@ class ExporterService {
|
|
|
845
925
|
return children;
|
|
846
926
|
}
|
|
847
927
|
/**
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
928
|
+
* Inflate a raw Redis/Postgres job hash into a structured {@link DurableJobExport}.
|
|
929
|
+
*
|
|
930
|
+
* HotMesh stores workflow state as a flat hash with 3-character symbolized keys
|
|
931
|
+
* (e.g., `aBC,0,0` → `worker/output/data`). This method decodes each entry and
|
|
932
|
+
* sorts it into one of four buckets:
|
|
933
|
+
*
|
|
934
|
+
* - **Transitions** (`aBC,0,0` pattern) — activity start/stop timestamps by dimension
|
|
935
|
+
* - **Data** (`_`-prefixed keys) — workflow input arguments
|
|
936
|
+
* - **Timeline** (`-`-prefixed keys) — idempotent operation markers (proxy, child, sleep, etc.)
|
|
937
|
+
* - **State** (3-char keys) — workflow metadata (done flag, response, error, timestamps)
|
|
938
|
+
*
|
|
939
|
+
* Use `options.allow` / `options.block` to limit which sections appear in the result.
|
|
940
|
+
*
|
|
941
|
+
* @param jobHash - the raw key-value hash from the store
|
|
942
|
+
* @param options - filtering and value options
|
|
943
|
+
* @returns structured export with data, state, status, timeline, and transitions
|
|
852
944
|
*/
|
|
853
945
|
inflate(jobHash, options) {
|
|
854
946
|
const timeline = [];
|
|
@@ -37,19 +37,51 @@ export declare class WorkflowHandleService {
|
|
|
37
37
|
*/
|
|
38
38
|
constructor(hotMesh: HotMesh, workflowTopic: string, workflowId: string);
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* Export the raw workflow state as a {@link DurableJobExport} with five sections:
|
|
41
|
+
*
|
|
42
|
+
* - **data** — workflow input arguments
|
|
43
|
+
* - **state** — done flag, response, error, timestamps
|
|
44
|
+
* - **status** — semaphore (`0` = complete, `> 0` = pending, `< 0` = error)
|
|
45
|
+
* - **timeline** — ordered idempotent markers for activities, children, sleeps, signals
|
|
46
|
+
* - **transitions** — per-activity execution timestamps by dimension
|
|
47
|
+
*
|
|
48
|
+
* Use `allow` / `block` to limit sections and `values: false` to strip payloads.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const raw = await handle.export();
|
|
53
|
+
* console.log(raw.state); // { done: true, response: '...' }
|
|
54
|
+
* console.log(raw.timeline); // [{ key: '-proxy-1-', value: {...} }, ...]
|
|
55
|
+
*
|
|
56
|
+
* // Lightweight export: timeline keys only, no payloads
|
|
57
|
+
* const slim = await handle.export({ allow: ['timeline'], values: false });
|
|
58
|
+
* ```
|
|
42
59
|
*/
|
|
43
60
|
export(options?: ExportOptions): Promise<DurableJobExport>;
|
|
44
61
|
/**
|
|
45
|
-
*
|
|
62
|
+
* Export the workflow as a structured event history ({@link WorkflowExecution}).
|
|
46
63
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* timer, and signal events.
|
|
64
|
+
* Returns a chronologically ordered event list with Temporal-style typed events,
|
|
65
|
+
* back-references between scheduled/completed pairs, and a summary with counts.
|
|
50
66
|
*
|
|
51
|
-
* **
|
|
52
|
-
*
|
|
67
|
+
* **Event types:** `activity_task_scheduled/completed/failed`,
|
|
68
|
+
* `child_workflow_execution_started/completed/failed`, `timer_started/fired`,
|
|
69
|
+
* `workflow_execution_started/completed/failed/signaled`
|
|
70
|
+
*
|
|
71
|
+
* **Modes:**
|
|
72
|
+
* - `sparse` (default) — single query, transforms timeline markers into events
|
|
73
|
+
* - `verbose` — recursively fetches child workflows as nested `children`
|
|
74
|
+
*
|
|
75
|
+
* **Options:** `exclude_system`, `omit_results`, `enrich_inputs`, `allow_direct_query`
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const exec = await handle.exportExecution({ exclude_system: true });
|
|
80
|
+
* for (const event of exec.events) {
|
|
81
|
+
* console.log(event.event_type, event.attributes);
|
|
82
|
+
* }
|
|
83
|
+
* console.log(exec.summary); // { activities: { total: 5, ... }, timers: 1, ... }
|
|
84
|
+
* ```
|
|
53
85
|
*/
|
|
54
86
|
exportExecution(options?: ExecutionExportOptions): Promise<WorkflowExecution>;
|
|
55
87
|
/**
|
|
@@ -34,21 +34,53 @@ class WorkflowHandleService {
|
|
|
34
34
|
this.exporter = new exporter_1.ExporterService(this.hotMesh.appId, this.hotMesh.engine.store, this.hotMesh.engine.logger);
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* Export the raw workflow state as a {@link DurableJobExport} with five sections:
|
|
38
|
+
*
|
|
39
|
+
* - **data** — workflow input arguments
|
|
40
|
+
* - **state** — done flag, response, error, timestamps
|
|
41
|
+
* - **status** — semaphore (`0` = complete, `> 0` = pending, `< 0` = error)
|
|
42
|
+
* - **timeline** — ordered idempotent markers for activities, children, sleeps, signals
|
|
43
|
+
* - **transitions** — per-activity execution timestamps by dimension
|
|
44
|
+
*
|
|
45
|
+
* Use `allow` / `block` to limit sections and `values: false` to strip payloads.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const raw = await handle.export();
|
|
50
|
+
* console.log(raw.state); // { done: true, response: '...' }
|
|
51
|
+
* console.log(raw.timeline); // [{ key: '-proxy-1-', value: {...} }, ...]
|
|
52
|
+
*
|
|
53
|
+
* // Lightweight export: timeline keys only, no payloads
|
|
54
|
+
* const slim = await handle.export({ allow: ['timeline'], values: false });
|
|
55
|
+
* ```
|
|
39
56
|
*/
|
|
40
57
|
async export(options) {
|
|
41
58
|
return this.exporter.export(this.workflowId, options);
|
|
42
59
|
}
|
|
43
60
|
/**
|
|
44
|
-
*
|
|
61
|
+
* Export the workflow as a structured event history ({@link WorkflowExecution}).
|
|
45
62
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* timer, and signal events.
|
|
63
|
+
* Returns a chronologically ordered event list with Temporal-style typed events,
|
|
64
|
+
* back-references between scheduled/completed pairs, and a summary with counts.
|
|
49
65
|
*
|
|
50
|
-
* **
|
|
51
|
-
*
|
|
66
|
+
* **Event types:** `activity_task_scheduled/completed/failed`,
|
|
67
|
+
* `child_workflow_execution_started/completed/failed`, `timer_started/fired`,
|
|
68
|
+
* `workflow_execution_started/completed/failed/signaled`
|
|
69
|
+
*
|
|
70
|
+
* **Modes:**
|
|
71
|
+
* - `sparse` (default) — single query, transforms timeline markers into events
|
|
72
|
+
* - `verbose` — recursively fetches child workflows as nested `children`
|
|
73
|
+
*
|
|
74
|
+
* **Options:** `exclude_system`, `omit_results`, `enrich_inputs`, `allow_direct_query`
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const exec = await handle.exportExecution({ exclude_system: true });
|
|
79
|
+
* for (const event of exec.events) {
|
|
80
|
+
* console.log(event.event_type, event.attributes);
|
|
81
|
+
* }
|
|
82
|
+
* console.log(exec.summary); // { activities: { total: 5, ... }, timers: 1, ... }
|
|
83
|
+
* ```
|
|
52
84
|
*/
|
|
53
85
|
async exportExecution(options) {
|
|
54
86
|
return this.exporter.exportExecution(this.workflowId, this.workflowTopic, options);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.APP_ID = exports.APP_VERSION = exports.getWorkflowYAML = void 0;
|
|
4
|
-
const APP_VERSION = '
|
|
4
|
+
const APP_VERSION = '13';
|
|
5
5
|
exports.APP_VERSION = APP_VERSION;
|
|
6
6
|
const APP_ID = 'durable';
|
|
7
7
|
exports.APP_ID = APP_ID;
|
|
@@ -136,10 +136,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
136
136
|
maps:
|
|
137
137
|
retryCount: 0
|
|
138
138
|
|
|
139
|
-
throttler:
|
|
140
|
-
title: Pass-through hook between cycle_hook and worker
|
|
141
|
-
type: hook
|
|
142
|
-
|
|
143
139
|
worker:
|
|
144
140
|
title: Main Worker - Calls linked Workflow functions
|
|
145
141
|
type: worker
|
|
@@ -895,10 +891,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
895
891
|
maps:
|
|
896
892
|
retryCount: 0
|
|
897
893
|
|
|
898
|
-
signaler_throttler:
|
|
899
|
-
title: Pass-through hook between signaler_cycle_hook and signaler_worker
|
|
900
|
-
type: hook
|
|
901
|
-
|
|
902
894
|
signaler_worker:
|
|
903
895
|
title: Signal In - Worker
|
|
904
896
|
type: worker
|
|
@@ -1531,8 +1523,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
1531
1523
|
- ['{@conditional.nullish}']
|
|
1532
1524
|
## MAIN PROCESS TRANSITIONS ##
|
|
1533
1525
|
cycle_hook:
|
|
1534
|
-
- to: throttler
|
|
1535
|
-
throttler:
|
|
1536
1526
|
- to: worker
|
|
1537
1527
|
worker:
|
|
1538
1528
|
- to: ender
|
|
@@ -1605,8 +1595,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
1605
1595
|
conditions:
|
|
1606
1596
|
code: 202
|
|
1607
1597
|
signaler_cycle_hook:
|
|
1608
|
-
- to: signaler_throttler
|
|
1609
|
-
signaler_throttler:
|
|
1610
1598
|
- to: signaler_worker
|
|
1611
1599
|
signaler_worker:
|
|
1612
1600
|
- to: signaler_sleeper
|
|
@@ -4,8 +4,25 @@ import { ActivityDetail, DependencyExport, ExportOptions, JobActionExport, JobEx
|
|
|
4
4
|
import { ProviderClient, ProviderTransaction } from '../../types/provider';
|
|
5
5
|
import { StringAnyType, StringStringType, Symbols } from '../../types/serializer';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* System-level exporter for HotMesh job data. Decodes the flat, symbolized
|
|
8
|
+
* hash stored in Redis/Postgres into a human-readable {@link JobExport}
|
|
9
|
+
* with three sections:
|
|
10
|
+
*
|
|
11
|
+
* - **process** — a nested object reflecting the activity execution tree.
|
|
12
|
+
* Each activity's input, output, and metadata are organized by their
|
|
13
|
+
* dimensional path (e.g., `0/0/worker/output/data`).
|
|
14
|
+
* - **dependencies** — list of dependent jobs (child workflows, hooks, signals)
|
|
15
|
+
* spawned during execution.
|
|
16
|
+
* - **status** — the raw semaphore value from the job hash.
|
|
17
|
+
*
|
|
18
|
+
* Optionally, set `enrich_inputs: true` to produce a flat `activities` array
|
|
19
|
+
* ({@link ActivityDetail}[]) that merges stream message history (inputs, timing,
|
|
20
|
+
* retries) with process outputs — useful for dashboards and debugging views.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* This is the lower-level exporter used by the HotMesh engine directly.
|
|
24
|
+
* For durable workflow exports, use the `ExporterService` in `services/durable/exporter`
|
|
25
|
+
* which produces structured timeline and execution history formats.
|
|
9
26
|
*/
|
|
10
27
|
declare class ExporterService {
|
|
11
28
|
appId: string;
|
|
@@ -16,22 +33,39 @@ declare class ExporterService {
|
|
|
16
33
|
/** @hidden */
|
|
17
34
|
constructor(appId: string, store: StoreService<ProviderClient, ProviderTransaction>, logger: ILogger);
|
|
18
35
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
36
|
+
* Export a job as a structured {@link JobExport}.
|
|
37
|
+
*
|
|
38
|
+
* Reads the raw job hash from the store, inflates symbolized keys into
|
|
39
|
+
* readable paths, and organizes data into `process`, `dependencies`,
|
|
40
|
+
* and `status` sections.
|
|
41
|
+
*
|
|
42
|
+
* When `enrich_inputs` is true, also fetches stream message history and
|
|
43
|
+
* produces a flat `activities` array with per-activity input/output,
|
|
44
|
+
* timing, retry attempts, and cycle iteration info.
|
|
45
|
+
*
|
|
46
|
+
* @param jobId - the job ID to export
|
|
47
|
+
* @param options - controls enrichment behavior
|
|
22
48
|
*/
|
|
23
49
|
export(jobId: string, options?: ExportOptions): Promise<JobExport>;
|
|
24
50
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* tree-like structure of the unidimensional Hash
|
|
51
|
+
* Resolve a 3-character symbol key to its full path (e.g., `aBC` → `worker/output/data`).
|
|
52
|
+
* Returns the key unchanged if no symbol mapping exists.
|
|
28
53
|
*/
|
|
29
54
|
inflateKey(key: string): string;
|
|
30
55
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
56
|
+
* Decode a raw job hash into a structured {@link JobExport}.
|
|
57
|
+
*
|
|
58
|
+
* Walks every key in the flat hash and classifies it:
|
|
59
|
+
* - **3-char + dimension** (`aBC,0,0`) — activity process state, organized into
|
|
60
|
+
* a nested hierarchy by dimension path and symbolized key
|
|
61
|
+
* - **3-char only** (`aBC`) — top-level job state (done, response, error, etc.)
|
|
62
|
+
*
|
|
63
|
+
* The `process` result is a nested tree where dimensions are path segments
|
|
64
|
+
* (e.g., `{ "0": { "0": { "worker": { "output": { "data": ... } } } } }`).
|
|
65
|
+
*
|
|
66
|
+
* @param jobHash - the raw key-value hash from the store
|
|
67
|
+
* @param dependencyList - raw dependency strings from the store
|
|
68
|
+
* @returns structured export with process tree, dependencies, and status
|
|
35
69
|
*/
|
|
36
70
|
inflate(jobHash: StringStringType, dependencyList: string[]): JobExport;
|
|
37
71
|
/**
|
|
@@ -49,11 +83,16 @@ declare class ExporterService {
|
|
|
49
83
|
*/
|
|
50
84
|
buildActivities(process: StringAnyType, streamHistory: StreamHistoryEntry[]): ActivityDetail[];
|
|
51
85
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
86
|
+
* Parse raw dependency strings into structured {@link DependencyExport} entries.
|
|
87
|
+
*
|
|
88
|
+
* Each dependency string encodes the action type, topic, group ID, and job ID
|
|
89
|
+
* of a spawned sub-job (child workflow, hook, or signal cleanup). The job ID
|
|
90
|
+
* suffix reveals whether it originated from a hook (dimensional address + counter)
|
|
91
|
+
* or the main flow (counter only).
|
|
92
|
+
*
|
|
93
|
+
* @param data - raw dependency strings from the store
|
|
94
|
+
* @param actions - accumulator for action tracking (hooks vs main flow)
|
|
95
|
+
* @returns structured dependency list
|
|
57
96
|
*/
|
|
58
97
|
inflateDependencyData(data: string[], actions: JobActionExport): DependencyExport[];
|
|
59
98
|
}
|
|
@@ -5,8 +5,25 @@ const key_1 = require("../../modules/key");
|
|
|
5
5
|
const utils_1 = require("../../modules/utils");
|
|
6
6
|
const serializer_1 = require("../serializer");
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* System-level exporter for HotMesh job data. Decodes the flat, symbolized
|
|
9
|
+
* hash stored in Redis/Postgres into a human-readable {@link JobExport}
|
|
10
|
+
* with three sections:
|
|
11
|
+
*
|
|
12
|
+
* - **process** — a nested object reflecting the activity execution tree.
|
|
13
|
+
* Each activity's input, output, and metadata are organized by their
|
|
14
|
+
* dimensional path (e.g., `0/0/worker/output/data`).
|
|
15
|
+
* - **dependencies** — list of dependent jobs (child workflows, hooks, signals)
|
|
16
|
+
* spawned during execution.
|
|
17
|
+
* - **status** — the raw semaphore value from the job hash.
|
|
18
|
+
*
|
|
19
|
+
* Optionally, set `enrich_inputs: true` to produce a flat `activities` array
|
|
20
|
+
* ({@link ActivityDetail}[]) that merges stream message history (inputs, timing,
|
|
21
|
+
* retries) with process outputs — useful for dashboards and debugging views.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* This is the lower-level exporter used by the HotMesh engine directly.
|
|
25
|
+
* For durable workflow exports, use the `ExporterService` in `services/durable/exporter`
|
|
26
|
+
* which produces structured timeline and execution history formats.
|
|
10
27
|
*/
|
|
11
28
|
class ExporterService {
|
|
12
29
|
/** @hidden */
|
|
@@ -16,9 +33,18 @@ class ExporterService {
|
|
|
16
33
|
this.store = store;
|
|
17
34
|
}
|
|
18
35
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
36
|
+
* Export a job as a structured {@link JobExport}.
|
|
37
|
+
*
|
|
38
|
+
* Reads the raw job hash from the store, inflates symbolized keys into
|
|
39
|
+
* readable paths, and organizes data into `process`, `dependencies`,
|
|
40
|
+
* and `status` sections.
|
|
41
|
+
*
|
|
42
|
+
* When `enrich_inputs` is true, also fetches stream message history and
|
|
43
|
+
* produces a flat `activities` array with per-activity input/output,
|
|
44
|
+
* timing, retry attempts, and cycle iteration info.
|
|
45
|
+
*
|
|
46
|
+
* @param jobId - the job ID to export
|
|
47
|
+
* @param options - controls enrichment behavior
|
|
22
48
|
*/
|
|
23
49
|
async export(jobId, options = {}) {
|
|
24
50
|
if (!this.symbols) {
|
|
@@ -35,18 +61,26 @@ class ExporterService {
|
|
|
35
61
|
return jobExport;
|
|
36
62
|
}
|
|
37
63
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* tree-like structure of the unidimensional Hash
|
|
64
|
+
* Resolve a 3-character symbol key to its full path (e.g., `aBC` → `worker/output/data`).
|
|
65
|
+
* Returns the key unchanged if no symbol mapping exists.
|
|
41
66
|
*/
|
|
42
67
|
inflateKey(key) {
|
|
43
68
|
return key in this.symbols ? this.symbols[key] : key;
|
|
44
69
|
}
|
|
45
70
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
71
|
+
* Decode a raw job hash into a structured {@link JobExport}.
|
|
72
|
+
*
|
|
73
|
+
* Walks every key in the flat hash and classifies it:
|
|
74
|
+
* - **3-char + dimension** (`aBC,0,0`) — activity process state, organized into
|
|
75
|
+
* a nested hierarchy by dimension path and symbolized key
|
|
76
|
+
* - **3-char only** (`aBC`) — top-level job state (done, response, error, etc.)
|
|
77
|
+
*
|
|
78
|
+
* The `process` result is a nested tree where dimensions are path segments
|
|
79
|
+
* (e.g., `{ "0": { "0": { "worker": { "output": { "data": ... } } } } }`).
|
|
80
|
+
*
|
|
81
|
+
* @param jobHash - the raw key-value hash from the store
|
|
82
|
+
* @param dependencyList - raw dependency strings from the store
|
|
83
|
+
* @returns structured export with process tree, dependencies, and status
|
|
50
84
|
*/
|
|
51
85
|
inflate(jobHash, dependencyList) {
|
|
52
86
|
//the list of actions taken in the workflow and hook functions
|
|
@@ -154,11 +188,16 @@ class ExporterService {
|
|
|
154
188
|
return activities;
|
|
155
189
|
}
|
|
156
190
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
191
|
+
* Parse raw dependency strings into structured {@link DependencyExport} entries.
|
|
192
|
+
*
|
|
193
|
+
* Each dependency string encodes the action type, topic, group ID, and job ID
|
|
194
|
+
* of a spawned sub-job (child workflow, hook, or signal cleanup). The job ID
|
|
195
|
+
* suffix reveals whether it originated from a hook (dimensional address + counter)
|
|
196
|
+
* or the main flow (counter only).
|
|
197
|
+
*
|
|
198
|
+
* @param data - raw dependency strings from the store
|
|
199
|
+
* @param actions - accumulator for action tracking (hooks vs main flow)
|
|
200
|
+
* @returns structured dependency list
|
|
162
201
|
*/
|
|
163
202
|
inflateDependencyData(data, actions) {
|
|
164
203
|
const hookReg = /([0-9,]+)-(\d+)$/;
|
|
@@ -140,21 +140,31 @@ async function createTables(client, schemaName) {
|
|
|
140
140
|
) PARTITION BY HASH (stream_name);
|
|
141
141
|
`);
|
|
142
142
|
for (let i = 0; i < 8; i++) {
|
|
143
|
+
// fillfactor 70: reserves 30% page space for HOT updates (reserve/ack cycle)
|
|
143
144
|
await client.query(`
|
|
144
145
|
CREATE TABLE IF NOT EXISTS ${schemaName}.engine_streams_part_${i}
|
|
145
146
|
PARTITION OF ${engineTable}
|
|
146
|
-
FOR VALUES WITH (modulus 8, remainder ${i})
|
|
147
|
+
FOR VALUES WITH (modulus 8, remainder ${i})
|
|
148
|
+
WITH (fillfactor = 70);
|
|
147
149
|
`);
|
|
148
150
|
}
|
|
151
|
+
// Dedicated dequeue index: columns match the hot-path query exactly
|
|
152
|
+
// (stream_name = $1, visible_at <= NOW(), ORDER BY id) with partial
|
|
153
|
+
// filter on reserved_at IS NULL AND expired_at IS NULL.
|
|
154
|
+
// Replaces the old active_messages index that had reserved_at as a
|
|
155
|
+
// column (redundant with the WHERE filter, displacing visible_at).
|
|
149
156
|
await client.query(`
|
|
150
|
-
CREATE INDEX IF NOT EXISTS
|
|
151
|
-
ON ${engineTable} (stream_name,
|
|
157
|
+
CREATE INDEX IF NOT EXISTS idx_engine_streams_dequeue
|
|
158
|
+
ON ${engineTable} (stream_name, visible_at, id)
|
|
152
159
|
WHERE reserved_at IS NULL AND expired_at IS NULL;
|
|
153
160
|
`);
|
|
161
|
+
// Stale-reservation recovery: covers the timed-out reservation path
|
|
162
|
+
// (reserved_at < NOW() - INTERVAL) separately from the hot path so
|
|
163
|
+
// the planner can use each index cleanly without an OR.
|
|
154
164
|
await client.query(`
|
|
155
|
-
CREATE INDEX IF NOT EXISTS
|
|
156
|
-
ON ${engineTable} (stream_name, visible_at, id)
|
|
157
|
-
WHERE expired_at IS NULL;
|
|
165
|
+
CREATE INDEX IF NOT EXISTS idx_engine_streams_stale_reservations
|
|
166
|
+
ON ${engineTable} (stream_name, reserved_at, visible_at, id)
|
|
167
|
+
WHERE reserved_at IS NOT NULL AND expired_at IS NULL;
|
|
158
168
|
`);
|
|
159
169
|
await client.query(`
|
|
160
170
|
CREATE INDEX IF NOT EXISTS idx_engine_streams_expired_at
|
|
@@ -209,21 +219,25 @@ async function createTables(client, schemaName) {
|
|
|
209
219
|
) PARTITION BY HASH (stream_name);
|
|
210
220
|
`);
|
|
211
221
|
for (let i = 0; i < 8; i++) {
|
|
222
|
+
// fillfactor 70: reserves 30% page space for HOT updates (reserve/ack cycle)
|
|
212
223
|
await client.query(`
|
|
213
224
|
CREATE TABLE IF NOT EXISTS ${schemaName}.worker_streams_part_${i}
|
|
214
225
|
PARTITION OF ${workerTable}
|
|
215
|
-
FOR VALUES WITH (modulus 8, remainder ${i})
|
|
226
|
+
FOR VALUES WITH (modulus 8, remainder ${i})
|
|
227
|
+
WITH (fillfactor = 70);
|
|
216
228
|
`);
|
|
217
229
|
}
|
|
230
|
+
// Dedicated dequeue index (see engine_streams comments above)
|
|
218
231
|
await client.query(`
|
|
219
|
-
CREATE INDEX IF NOT EXISTS
|
|
220
|
-
ON ${workerTable} (stream_name,
|
|
232
|
+
CREATE INDEX IF NOT EXISTS idx_worker_streams_dequeue
|
|
233
|
+
ON ${workerTable} (stream_name, visible_at, id)
|
|
221
234
|
WHERE reserved_at IS NULL AND expired_at IS NULL;
|
|
222
235
|
`);
|
|
236
|
+
// Stale-reservation recovery (see engine_streams comments above)
|
|
223
237
|
await client.query(`
|
|
224
|
-
CREATE INDEX IF NOT EXISTS
|
|
225
|
-
ON ${workerTable} (stream_name, visible_at, id)
|
|
226
|
-
WHERE expired_at IS NULL;
|
|
238
|
+
CREATE INDEX IF NOT EXISTS idx_worker_streams_stale_reservations
|
|
239
|
+
ON ${workerTable} (stream_name, reserved_at, visible_at, id)
|
|
240
|
+
WHERE reserved_at IS NOT NULL AND expired_at IS NULL;
|
|
227
241
|
`);
|
|
228
242
|
await client.query(`
|
|
229
243
|
CREATE INDEX IF NOT EXISTS idx_worker_streams_expired_at
|
|
@@ -207,12 +207,17 @@ async function fetchMessages(client, tableName, streamName, isEngine, consumerNa
|
|
|
207
207
|
retries++;
|
|
208
208
|
const batchSize = options?.batchSize || 1;
|
|
209
209
|
const reservationTimeout = options?.reservationTimeout || enums_1.HMSH_RESERVATION_TIMEOUT_S;
|
|
210
|
-
|
|
210
|
+
// Two-pass dequeue: stale reservations first (FIFO — they have
|
|
211
|
+
// lower ids and have waited longest), then fresh messages. Split
|
|
212
|
+
// avoids an OR that prevents the planner from using partial
|
|
213
|
+
// indexes cleanly. Stale check is a fast no-op (empty index scan)
|
|
214
|
+
// when there are no timed-out reservations.
|
|
215
|
+
let res = await client.query(`UPDATE ${tableName}
|
|
211
216
|
SET reserved_at = NOW(), reserved_by = $3
|
|
212
217
|
WHERE id IN (
|
|
213
218
|
SELECT id FROM ${tableName}
|
|
214
219
|
WHERE stream_name = $1
|
|
215
|
-
AND
|
|
220
|
+
AND reserved_at < NOW() - INTERVAL '${reservationTimeout} seconds'
|
|
216
221
|
AND expired_at IS NULL
|
|
217
222
|
AND visible_at <= NOW()
|
|
218
223
|
ORDER BY id
|
|
@@ -220,6 +225,22 @@ async function fetchMessages(client, tableName, streamName, isEngine, consumerNa
|
|
|
220
225
|
FOR UPDATE SKIP LOCKED
|
|
221
226
|
)
|
|
222
227
|
RETURNING ${returningClause}`, [streamName, batchSize, consumerName]);
|
|
228
|
+
// Fresh messages: unreserved, visible, not expired
|
|
229
|
+
if (res.rows.length === 0) {
|
|
230
|
+
res = await client.query(`UPDATE ${tableName}
|
|
231
|
+
SET reserved_at = NOW(), reserved_by = $3
|
|
232
|
+
WHERE id IN (
|
|
233
|
+
SELECT id FROM ${tableName}
|
|
234
|
+
WHERE stream_name = $1
|
|
235
|
+
AND reserved_at IS NULL
|
|
236
|
+
AND expired_at IS NULL
|
|
237
|
+
AND visible_at <= NOW()
|
|
238
|
+
ORDER BY id
|
|
239
|
+
LIMIT $2
|
|
240
|
+
FOR UPDATE SKIP LOCKED
|
|
241
|
+
)
|
|
242
|
+
RETURNING ${returningClause}`, [streamName, batchSize, consumerName]);
|
|
243
|
+
}
|
|
223
244
|
const messages = res.rows.map((row) => {
|
|
224
245
|
const data = (0, utils_1.parseStreamMessage)(row.message);
|
|
225
246
|
const hasDefaultRetryPolicy = (row.max_retry_attempts === 3 || row.max_retry_attempts === 5) &&
|
|
@@ -54,13 +54,16 @@ function getCreateProceduresSQL(schemaName) {
|
|
|
54
54
|
SET search_path = ${schemaName}, pg_temp
|
|
55
55
|
AS $$
|
|
56
56
|
${STREAM_ACCESS_CHECK}
|
|
57
|
+
-- Two-pass dequeue: stale reservations first (FIFO — lower ids,
|
|
58
|
+
-- waited longest), then fresh. Split avoids OR that prevents
|
|
59
|
+
-- partial index usage. Stale check is a fast no-op when empty.
|
|
57
60
|
RETURN QUERY
|
|
58
61
|
UPDATE ${workerTable} ws
|
|
59
62
|
SET reserved_at = NOW(), reserved_by = p_consumer_id
|
|
60
63
|
WHERE ws.id IN (
|
|
61
64
|
SELECT ws2.id FROM ${workerTable} ws2
|
|
62
65
|
WHERE ws2.stream_name = p_stream_name
|
|
63
|
-
AND
|
|
66
|
+
AND ws2.reserved_at < NOW() - (p_reservation_timeout_sec || ' seconds')::INTERVAL
|
|
64
67
|
AND ws2.expired_at IS NULL
|
|
65
68
|
AND ws2.visible_at <= NOW()
|
|
66
69
|
ORDER BY ws2.id
|
|
@@ -69,6 +72,25 @@ function getCreateProceduresSQL(schemaName) {
|
|
|
69
72
|
)
|
|
70
73
|
RETURNING ws.id, ws.message, ws.workflow_name, ws.max_retry_attempts,
|
|
71
74
|
ws.backoff_coefficient, ws.maximum_interval_seconds, ws.retry_attempt;
|
|
75
|
+
|
|
76
|
+
-- Fresh messages: unreserved, visible, not expired
|
|
77
|
+
IF NOT FOUND THEN
|
|
78
|
+
RETURN QUERY
|
|
79
|
+
UPDATE ${workerTable} ws
|
|
80
|
+
SET reserved_at = NOW(), reserved_by = p_consumer_id
|
|
81
|
+
WHERE ws.id IN (
|
|
82
|
+
SELECT ws2.id FROM ${workerTable} ws2
|
|
83
|
+
WHERE ws2.stream_name = p_stream_name
|
|
84
|
+
AND ws2.reserved_at IS NULL
|
|
85
|
+
AND ws2.expired_at IS NULL
|
|
86
|
+
AND ws2.visible_at <= NOW()
|
|
87
|
+
ORDER BY ws2.id
|
|
88
|
+
LIMIT p_batch_size
|
|
89
|
+
FOR UPDATE SKIP LOCKED
|
|
90
|
+
)
|
|
91
|
+
RETURNING ws.id, ws.message, ws.workflow_name, ws.max_retry_attempts,
|
|
92
|
+
ws.backoff_coefficient, ws.maximum_interval_seconds, ws.retry_attempt;
|
|
93
|
+
END IF;
|
|
72
94
|
END;
|
|
73
95
|
$$;`,
|
|
74
96
|
// -- worker_ack --
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { StringAnyType } from './serializer';
|
|
2
2
|
export type ExportItem = [string | null, string, any];
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Selectable sections of a {@link DurableJobExport}. Use with `allow` (allowlist)
|
|
5
|
+
* or `block` (blocklist) in {@link ExportOptions} to control export size.
|
|
6
|
+
*
|
|
7
|
+
* - `data` — workflow input arguments
|
|
8
|
+
* - `state` — current state (done, response, error, timestamps)
|
|
9
|
+
* - `status` — semaphore value
|
|
10
|
+
* - `timeline` — idempotent operation markers (can be large for complex workflows)
|
|
11
|
+
* - `transitions` — activity execution timestamps (can be large with many cycles)
|
|
5
12
|
*/
|
|
6
13
|
export type ExportFields = 'data' | 'state' | 'status' | 'timeline' | 'transitions';
|
|
7
14
|
export interface ExportOptions {
|
|
@@ -74,6 +81,19 @@ export interface TransitionType {
|
|
|
74
81
|
created: string;
|
|
75
82
|
updated: string;
|
|
76
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Raw export of a durable workflow job, returned by `handle.export()`.
|
|
86
|
+
*
|
|
87
|
+
* Contains five sections (filterable via {@link ExportOptions}):
|
|
88
|
+
*
|
|
89
|
+
* | Section | Type | Description |
|
|
90
|
+
* |---|---|---|
|
|
91
|
+
* | `data` | object | Workflow input arguments passed to `workflow.start()` |
|
|
92
|
+
* | `state` | object | Current state: `done` flag, `response`, `$error`, `jc`/`ju` timestamps |
|
|
93
|
+
* | `status` | number | Semaphore: `0` = idle/complete, `> 0` = pending activities, `< 0` = error |
|
|
94
|
+
* | `timeline` | array | Ordered idempotent markers for each operation (proxy, child, sleep, signal) |
|
|
95
|
+
* | `transitions` | array | Activity start/stop timestamps organized by dimension |
|
|
96
|
+
*/
|
|
77
97
|
export interface DurableJobExport {
|
|
78
98
|
data?: StringAnyType;
|
|
79
99
|
state?: StringAnyType;
|
|
@@ -212,6 +232,20 @@ export interface WorkflowExecutionSummary {
|
|
|
212
232
|
signals: number;
|
|
213
233
|
}
|
|
214
234
|
export type WorkflowExecutionStatus = 'running' | 'completed' | 'failed';
|
|
235
|
+
/**
|
|
236
|
+
* Structured execution history for a durable workflow, returned by
|
|
237
|
+
* `handle.exportExecution()`.
|
|
238
|
+
*
|
|
239
|
+
* Events are chronologically ordered with sequential `event_id` values.
|
|
240
|
+
* Completed/failed events carry `scheduled_event_id` or `initiated_event_id`
|
|
241
|
+
* back-references to their corresponding scheduled/started events.
|
|
242
|
+
*
|
|
243
|
+
* The `summary` provides aggregate counts by category (activities, child
|
|
244
|
+
* workflows, timers, signals) for quick dashboard rendering.
|
|
245
|
+
*
|
|
246
|
+
* In `verbose` mode, `children` contains recursively fetched child workflow
|
|
247
|
+
* executions, each with their own events and summaries.
|
|
248
|
+
*/
|
|
215
249
|
export interface WorkflowExecution {
|
|
216
250
|
workflow_id: string;
|
|
217
251
|
workflow_type: string;
|
|
@@ -226,10 +260,33 @@ export interface WorkflowExecution {
|
|
|
226
260
|
children?: WorkflowExecution[];
|
|
227
261
|
stream_history?: StreamHistoryEntry[];
|
|
228
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Options for `handle.exportExecution()`. Controls event filtering, enrichment,
|
|
265
|
+
* and traversal depth for child workflows.
|
|
266
|
+
*/
|
|
229
267
|
export interface ExecutionExportOptions {
|
|
268
|
+
/**
|
|
269
|
+
* `sparse` (default) — single-query export from the workflow's timeline markers.
|
|
270
|
+
* `verbose` — recursively fetches child workflow jobs and attaches their full
|
|
271
|
+
* event histories as nested `children` arrays.
|
|
272
|
+
*/
|
|
230
273
|
mode?: ExportMode;
|
|
274
|
+
/**
|
|
275
|
+
* When true, omits internal/interceptor activities (names starting with `lt`)
|
|
276
|
+
* from the event list. Useful for user-facing dashboards.
|
|
277
|
+
* @default false
|
|
278
|
+
*/
|
|
231
279
|
exclude_system?: boolean;
|
|
280
|
+
/**
|
|
281
|
+
* When true, strips `result` and `input` payloads from event attributes.
|
|
282
|
+
* Reduces response size while preserving event structure and timing.
|
|
283
|
+
* @default false
|
|
284
|
+
*/
|
|
232
285
|
omit_results?: boolean;
|
|
286
|
+
/**
|
|
287
|
+
* Maximum recursion depth for verbose mode child workflow fetching.
|
|
288
|
+
* @default 5
|
|
289
|
+
*/
|
|
233
290
|
max_depth?: number;
|
|
234
291
|
/**
|
|
235
292
|
* When true, enriches activity and child workflow events with their inputs
|