@hotmeshio/hotmesh 0.22.8 → 0.23.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.22.8",
3
+ "version": "0.23.0",
4
4
  "description": "Durable Workflow",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -285,6 +285,14 @@ class ExporterService {
285
285
  if (options.enrich_inputs) {
286
286
  await this.enrichExecutionInputs(execution, jobId);
287
287
  }
288
+ // Opt-in upward lineage pointer — one indexed lookup; surfaces the real
289
+ // spawning parent (not the collator $C job) and the root ancestor.
290
+ if (options.include_lineage && this.store.getJobLineage) {
291
+ const jobKey = `hmsh:${this.appId}:j:${jobId}`;
292
+ const lineage = await this.store.getJobLineage(jobKey);
293
+ execution.parent_workflow_id = lineage?.parent_id ?? null;
294
+ execution.origin_id = lineage?.origin_id ?? null;
295
+ }
288
296
  return execution;
289
297
  }
290
298
  /**
@@ -1,4 +1,4 @@
1
- declare const APP_VERSION = "16";
1
+ declare const APP_VERSION = "17";
2
2
  declare const APP_ID = "durable";
3
3
  /**
4
4
  * returns a new durable workflow schema
@@ -6,8 +6,9 @@ exports.APP_ID = exports.APP_VERSION = exports.getWorkflowYAML = void 0;
6
6
  // schema upgrade and hot-swap to it (see WorkerService.activateWorkflow and
7
7
  // ClientService.deployAndActivate). Changing the YAML without a bump leaves
8
8
  // every already-deployed database on the old schema forever. Numeric string —
9
- // compared with `Number()` for ordering. (v16: condition() escalation hook.)
10
- const APP_VERSION = '16';
9
+ // compared with `Number()` for ordering. (v17: collator_waiter escalation block —
10
+ // escalation-bearing condition() inside Promise.all writes its hmsh_escalations row.)
11
+ const APP_VERSION = '17';
11
12
  exports.APP_VERSION = APP_VERSION;
12
13
  const APP_ID = 'durable';
13
14
  exports.APP_ID = APP_ID;
@@ -1944,6 +1945,49 @@ const getWorkflowYAML = (app, version) => {
1944
1945
  - ['{collator_trigger.output.data.items}', '{collator_cycle_hook.output.data.cur_index}']
1945
1946
  - ['{@array.get}', duration]
1946
1947
  - ['{@object.get}']
1948
+ # Flatten the CURRENT item's escalation config into this activity's own
1949
+ # output so the escalation block below can address it with flat symbols.
1950
+ # mapOutputData runs before the escalation INSERT in Leg1, so these are
1951
+ # resolved in time. For a plain (non-escalation) collated condition these
1952
+ # resolve to undefined and the INSERT is skipped, just like the inline waiter.
1953
+ output:
1954
+ maps:
1955
+ queueConfig:
1956
+ '@pipe':
1957
+ - ['{collator_trigger.output.data.items}', '{collator_cycle_hook.output.data.cur_index}']
1958
+ - ['{@array.get}', queueConfig]
1959
+ - ['{@object.get}']
1960
+ taskQueue:
1961
+ '@pipe':
1962
+ - ['{collator_trigger.output.data.items}', '{collator_cycle_hook.output.data.cur_index}']
1963
+ - ['{@array.get}', taskQueue]
1964
+ - ['{@object.get}']
1965
+ workflowName:
1966
+ '@pipe':
1967
+ - ['{collator_trigger.output.data.items}', '{collator_cycle_hook.output.data.cur_index}']
1968
+ - ['{@array.get}', workflowName]
1969
+ - ['{@object.get}']
1970
+ # Identical in principle to the inline waiter escalation block, but sourced
1971
+ # from the collated item. The escalation signal_key is auto-derived from this
1972
+ # activity's reentry hook rule (wfs.signal, matching item.signalId), so resolve()
1973
+ # and signal() — which double-fire wfs.signal + wfs.wait — reach it correctly.
1974
+ escalation:
1975
+ type: '{$self.output.data.queueConfig.type}'
1976
+ subtype: '{$self.output.data.queueConfig.subtype}'
1977
+ entity: '{$self.output.data.queueConfig.entity}'
1978
+ description: '{$self.output.data.queueConfig.description}'
1979
+ role: '{$self.output.data.queueConfig.role}'
1980
+ priority: '{$self.output.data.queueConfig.priority}'
1981
+ metadata: '{$self.output.data.queueConfig.metadata}'
1982
+ envelope: '{$self.output.data.queueConfig.envelope}'
1983
+ originId: '{$self.output.data.queueConfig.originId}'
1984
+ parentId: '{$self.output.data.queueConfig.parentId}'
1985
+ initiatedBy: '{$self.output.data.queueConfig.initiatedBy}'
1986
+ traceId: '{$self.output.data.queueConfig.traceId}'
1987
+ spanId: '{$self.output.data.queueConfig.spanId}'
1988
+ expiresAt: '{$self.output.data.queueConfig.expiresAt}'
1989
+ taskQueue: '{$self.output.data.taskQueue}'
1990
+ workflowType: '{$self.output.data.workflowName}'
1947
1991
  hook:
1948
1992
  type: object
1949
1993
  properties:
@@ -160,7 +160,16 @@ async function condition(signalId, timeoutOrConfig) {
160
160
  type: 'DurableWaitForError',
161
161
  code: common_1.HMSH_CODE_DURABLE_WAIT,
162
162
  ...(timeout ? { duration: (0, common_1.s)(timeout) } : {}),
163
- ...(queueConfig ? { queueConfig } : {}),
163
+ // taskQueue + workflowName ride along so a collated wait (Promise.all) can
164
+ // write a faithful hmsh_escalations row from inside the collator flow —
165
+ // matching the inline waiter, which sources these from its trigger.
166
+ ...(queueConfig
167
+ ? {
168
+ queueConfig,
169
+ taskQueue: store.get('taskQueue'),
170
+ workflowName: store.get('workflowName'),
171
+ }
172
+ : {}),
164
173
  };
165
174
  interruptionRegistry.push(interruptionMessage);
166
175
  await (0, common_1.sleepImmediate)();
@@ -157,5 +157,18 @@ declare abstract class StoreService<Provider extends ProviderClient, Transaction
157
157
  };
158
158
  attributes: Record<string, string>;
159
159
  }>;
160
+ /**
161
+ * Fetch just the lineage columns (`parent_id`, `origin_id`) for a job via a
162
+ * single indexed lookup on `key`. Powers the exporter's opt-in
163
+ * `include_lineage` pointer — `parent_id` is the real spawning workflow (never
164
+ * the synthetic collator `$C` job), `origin_id` is the root ancestor.
165
+ *
166
+ * @param jobKey - The job key (e.g., "hmsh:durable:j:workflowId")
167
+ * @returns parent/origin ids (null when absent — a null `parent_id` marks a root)
168
+ */
169
+ getJobLineage?(jobKey: string): Promise<{
170
+ parent_id: string | null;
171
+ origin_id: string | null;
172
+ } | null>;
160
173
  }
161
174
  export { StoreService };
@@ -6,6 +6,11 @@
6
6
  * Fetch job record by key.
7
7
  */
8
8
  export declare const GET_JOB_BY_KEY = "\n SELECT id, key, status, created_at, updated_at, expired_at, is_live\n FROM {schema}.jobs\n WHERE key = $1\n LIMIT 1\n";
9
+ /**
10
+ * Fetch just the lineage columns for a job — a single indexed lookup on `key`.
11
+ * Backs the exporter's opt-in `include_lineage` pointer.
12
+ */
13
+ export declare const GET_JOB_LINEAGE = "\n SELECT parent_id, origin_id\n FROM {schema}.jobs\n WHERE key = $1\n LIMIT 1\n";
9
14
  /**
10
15
  * Fetch all attributes for a job.
11
16
  */
@@ -4,7 +4,7 @@
4
4
  * These queries support the exporter's input enrichment and direct query features.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.buildChildWorkflowInputsQuery = exports.GET_STREAM_HISTORY_BY_JID_AND_AID = exports.GET_STREAM_HISTORY_BY_JID_AND_TYPE = exports.GET_STREAM_HISTORY_BY_JID = exports.GET_PROXYER_STREAM_INPUTS = exports.GET_ACTIVITY_INPUTS = exports.GET_JOB_ATTRIBUTES = exports.GET_JOB_BY_KEY = void 0;
7
+ exports.buildChildWorkflowInputsQuery = exports.GET_STREAM_HISTORY_BY_JID_AND_AID = exports.GET_STREAM_HISTORY_BY_JID_AND_TYPE = exports.GET_STREAM_HISTORY_BY_JID = exports.GET_PROXYER_STREAM_INPUTS = exports.GET_ACTIVITY_INPUTS = exports.GET_JOB_ATTRIBUTES = exports.GET_JOB_LINEAGE = exports.GET_JOB_BY_KEY = void 0;
8
8
  /**
9
9
  * Fetch job record by key.
10
10
  */
@@ -14,6 +14,16 @@ exports.GET_JOB_BY_KEY = `
14
14
  WHERE key = $1
15
15
  LIMIT 1
16
16
  `;
17
+ /**
18
+ * Fetch just the lineage columns for a job — a single indexed lookup on `key`.
19
+ * Backs the exporter's opt-in `include_lineage` pointer.
20
+ */
21
+ exports.GET_JOB_LINEAGE = `
22
+ SELECT parent_id, origin_id
23
+ FROM {schema}.jobs
24
+ WHERE key = $1
25
+ LIMIT 1
26
+ `;
17
27
  /**
18
28
  * Fetch all attributes for a job.
19
29
  */
@@ -216,6 +216,14 @@ declare class PostgresStoreService extends StoreService<ProviderClient, Provider
216
216
  };
217
217
  attributes: Record<string, string>;
218
218
  }>;
219
+ /**
220
+ * Single indexed lookup of the lineage columns for a job key. Returns the real
221
+ * spawning parent (never the synthetic collator `$C` job) and the root ancestor.
222
+ */
223
+ getJobLineage(jobKey: string): Promise<{
224
+ parent_id: string | null;
225
+ origin_id: string | null;
226
+ } | null>;
219
227
  /**
220
228
  * Fetch stream message history for a job from worker_streams.
221
229
  * Returns raw activity input/output data from soft-deleted messages.
@@ -1338,6 +1338,20 @@ class PostgresStoreService extends __1.StoreService {
1338
1338
  }
1339
1339
  return { job, attributes };
1340
1340
  }
1341
+ /**
1342
+ * Single indexed lookup of the lineage columns for a job key. Returns the real
1343
+ * spawning parent (never the synthetic collator `$C` job) and the root ancestor.
1344
+ */
1345
+ async getJobLineage(jobKey) {
1346
+ const { GET_JOB_LINEAGE } = await Promise.resolve().then(() => __importStar(require('./exporter-sql')));
1347
+ const schemaName = this.kvsql().safeName(this.appId);
1348
+ const sql = GET_JOB_LINEAGE.replace(/{schema}/g, schemaName);
1349
+ const result = await this.pgClient.query(sql, [jobKey]);
1350
+ if (result.rows.length === 0)
1351
+ return null;
1352
+ const { parent_id, origin_id } = result.rows[0];
1353
+ return { parent_id: parent_id ?? null, origin_id: origin_id ?? null };
1354
+ }
1341
1355
  /**
1342
1356
  * Fetch stream message history for a job from worker_streams.
1343
1357
  * Returns raw activity input/output data from soft-deleted messages.
@@ -265,6 +265,19 @@ export interface WorkflowExecution {
265
265
  summary: WorkflowExecutionSummary;
266
266
  children?: WorkflowExecution[];
267
267
  stream_history?: StreamHistoryEntry[];
268
+ /**
269
+ * Upward lineage pointer to the real spawning workflow — never the synthetic
270
+ * collator `$C` job. `null` marks a root. Only present when the export was
271
+ * requested with `include_lineage: true`.
272
+ */
273
+ parent_workflow_id?: string | null;
274
+ /**
275
+ * Root ancestor of this execution; `null` for a root itself (identify roots
276
+ * via a null `parent_workflow_id`). Every descendant carries the same
277
+ * `origin_id`, so a subtree is recoverable in one query. Only present when the
278
+ * export was requested with `include_lineage: true`.
279
+ */
280
+ origin_id?: string | null;
268
281
  }
269
282
  /**
270
283
  * Options for `handle.exportExecution()`. Controls event filtering, enrichment,
@@ -310,6 +323,17 @@ export interface ExecutionExportOptions {
310
323
  * @default false
311
324
  */
312
325
  allow_direct_query?: boolean;
326
+ /**
327
+ * When true, populates the export's `parent_workflow_id` and `origin_id` from
328
+ * the jobs table via a single indexed lookup. `parent_workflow_id` is the real
329
+ * spawning workflow (never the synthetic collator `$C` job); `origin_id` is the
330
+ * root ancestor (a null `parent_workflow_id` marks a root). Opt-in so the extra
331
+ * query is only paid when a UI needs upward lineage. Requires a provider that
332
+ * implements `getJobLineage` (e.g., Postgres); a no-op otherwise.
333
+ *
334
+ * @default false
335
+ */
336
+ include_lineage?: boolean;
313
337
  /**
314
338
  * When true, fetches the full stream message history for this workflow
315
339
  * from the worker_streams table and attaches it as `stream_history`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.22.8",
3
+ "version": "0.23.0",
4
4
  "description": "Durable Workflow",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",