@hotmeshio/hotmesh 0.0.15 → 0.0.17

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/README.md CHANGED
@@ -22,7 +22,7 @@ The HotMesh SDK is designed to keep your code front-and-center. Write functions
22
22
  }
23
23
 
24
24
  export async function saludar(nombre: string): Promise<string> {
25
- Math.random() > 0.5 && throw new Error('Random error');
25
+ if (Math.random() > 0.5) throw new Error('Random error');
26
26
  return `¡Hola, ${nombre}!`;
27
27
  }
28
28
  ```
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -159,8 +159,7 @@ class Activity {
159
159
  return jobStatus;
160
160
  }
161
161
  catch (error) {
162
- if (error instanceof errors_1.CollationError) {
163
- //caused by external over-signaling; the job is complete
162
+ if (error instanceof errors_1.CollationError && error.fault === 'inactive') {
164
163
  this.logger.info('process-hook-event-inactive-error', { error });
165
164
  return;
166
165
  }
@@ -206,6 +205,10 @@ class Activity {
206
205
  this.transitionAdjacent(multiResponse, telemetry);
207
206
  }
208
207
  catch (error) {
208
+ if (error instanceof errors_1.CollationError && error.fault === 'inactive') {
209
+ this.logger.info('process-event-inactive-error', { error });
210
+ return;
211
+ }
209
212
  this.logger.error('activity-process-event-error', { error });
210
213
  telemetry && telemetry.setActivityError(error.message);
211
214
  throw error;
@@ -493,7 +496,12 @@ class Activity {
493
496
  }
494
497
  async transition(adjacencyList, jobStatus) {
495
498
  let mIds = [];
496
- if (jobStatus <= 0 || this.config.emit) {
499
+ //emit can be a mapping (allows emissions to be driven by the job state)
500
+ let emit = false;
501
+ if (this.config.emit) {
502
+ emit = pipe_1.Pipe.resolve(this.config.emit, this.context);
503
+ }
504
+ if (jobStatus <= 0 || emit) {
497
505
  //activity should not send 'emit' if the job is truly over
498
506
  const isTrueEmit = jobStatus > 0;
499
507
  await this.engine.runJobCompletionTasks(this.context, isTrueEmit);
@@ -151,7 +151,7 @@ class Trigger extends activity_1.Activity {
151
151
  }
152
152
  async setStats(multi) {
153
153
  const md = this.context.metadata;
154
- if (this.config.stats?.measures) {
154
+ if (md.key && this.config.stats?.measures) {
155
155
  const config = await this.engine.getVID();
156
156
  const reporter = new reporter_1.ReporterService(config, this.store, this.logger);
157
157
  await this.store.setStats(md.key, md.jid, md.ts, reporter.resolveTriggerStatistics(this.config, this.context), config, multi);
@@ -10,6 +10,7 @@ export declare class ClientService {
10
10
  workflow: {
11
11
  start: (options: WorkflowOptions) => Promise<WorkflowHandleService>;
12
12
  signal: (signalId: string, data: Record<any, any>) => Promise<string>;
13
+ getHandle: (taskQueue: string, workflowName: string, workflowId: string) => Promise<WorkflowHandleService>;
13
14
  };
14
15
  activateWorkflow(hotMesh: HotMesh, appId?: string, version?: string): Promise<void>;
15
16
  static shutdown(): Promise<void>;
@@ -87,6 +87,7 @@ class ClientService {
87
87
  const hotMeshClient = await this.getHotMeshClient(workflowTopic);
88
88
  const payload = {
89
89
  arguments: [...options.args],
90
+ parentWorkflowId: options.parentWorkflowId,
90
91
  workflowId: options.workflowId || (0, nanoid_1.nanoid)(),
91
92
  workflowTopic: workflowTopic,
92
93
  backoffCoefficient: options.config?.backoffCoefficient || factory_1.DEFAULT_COEFFICIENT,
@@ -97,6 +98,11 @@ class ClientService {
97
98
  },
98
99
  signal: async (signalId, data) => {
99
100
  return await (await this.getHotMeshClient('durable.wfs.signal')).hook('durable.wfs.signal', { id: signalId, data });
101
+ },
102
+ getHandle: async (taskQueue, workflowName, workflowId) => {
103
+ const workflowTopic = `${taskQueue}-${workflowName}`;
104
+ const hotMeshClient = await this.getHotMeshClient(workflowTopic);
105
+ return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, workflowId);
100
106
  }
101
107
  };
102
108
  this.connection = config.connection;
@@ -2,6 +2,11 @@
2
2
  * NOTE: Using `maxSystemRetries = 3` and `backoffCoefficient = 10`, errant
3
3
  * workflows will be retried on the following schedule (8 times in 27 hours):
4
4
  * => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
5
+ * 593:
6
+ * 594: waitforsignal
7
+ * 595: sleep
8
+ * 596, 597, 598: fatal
9
+ * 599: retry
5
10
  */
6
11
  declare const getWorkflowYAML: (app: string, version: string) => string;
7
12
  declare const APP_VERSION = "1";
@@ -3,6 +3,11 @@
3
3
  * NOTE: Using `maxSystemRetries = 3` and `backoffCoefficient = 10`, errant
4
4
  * workflows will be retried on the following schedule (8 times in 27 hours):
5
5
  * => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
6
+ * 593:
7
+ * 594: waitforsignal
8
+ * 595: sleep
9
+ * 596, 597, 598: fatal
10
+ * 599: retry
6
11
  */
7
12
  Object.defineProperty(exports, "__esModule", { value: true });
8
13
  exports.WFS_HOOK_ID = exports.WFSC_PUBLISHES_TOPIC = exports.WFSC_SUBSCRIBES_TOPIC = exports.WFS_PUBLISHES_TOPIC = exports.WFS_SUBSCRIBES_TOPIC = exports.DEFAULT_COEFFICIENT = exports.SLEEP_HOOK_ID = exports.ACTIVITY_HOOK_ID = exports.HOOK_ID = exports.PUBLISHES_TOPIC = exports.SUBSCRIBES_TOPIC = exports.SLEEP_PUBLISHES_TOPIC = exports.SLEEP_SUBSCRIBES_TOPIC = exports.ACTIVITY_PUBLISHES_TOPIC = exports.ACTIVITY_SUBSCRIBES_TOPIC = exports.APP_ID = exports.APP_VERSION = exports.getWorkflowYAML = void 0;
@@ -20,6 +25,8 @@ const getWorkflowYAML = (app, version) => {
20
25
  schema:
21
26
  type: object
22
27
  properties:
28
+ parentWorkflowId:
29
+ type: string
23
30
  workflowId:
24
31
  type: string
25
32
  arguments:
@@ -42,6 +49,11 @@ const getWorkflowYAML = (app, version) => {
42
49
  type: trigger
43
50
  stats:
44
51
  id: '{$self.input.data.workflowId}'
52
+ key: '{$self.input.data.parentWorkflowId}'
53
+ granularity: infinity
54
+ measures:
55
+ - measure: index
56
+ target: '{$self.input.data.parentWorkflowId}'
45
57
  job:
46
58
  maps:
47
59
  done: false
@@ -61,6 +73,7 @@ const getWorkflowYAML = (app, version) => {
61
73
  w1:
62
74
  type: worker
63
75
  topic: '{t1.output.data.workflowTopic}'
76
+ emit: '{$job.data.done}'
64
77
  retry:
65
78
  '599': [2]
66
79
  input:
@@ -118,6 +131,19 @@ const getWorkflowYAML = (app, version) => {
118
131
  response: '{$self.output.data.response}'
119
132
  done: '{$self.output.data.done}'
120
133
 
134
+ a2:
135
+ type: activity
136
+ title: Wait for cleanup signal
137
+ hook:
138
+ type: object
139
+ properties:
140
+ done:
141
+ type: boolean
142
+ job:
143
+ maps:
144
+ workflowId: '{t1.output.data.workflowId}'
145
+
146
+
121
147
  a594:
122
148
  title: Wait for signals
123
149
  type: await
@@ -275,7 +301,7 @@ const getWorkflowYAML = (app, version) => {
275
301
  done: true
276
302
 
277
303
  s2:
278
- title: Awaken sleep flows so they end and self-clean
304
+ title: Awaken sleeping flows so they end and self-clean
279
305
  type: signal
280
306
  subtype: all
281
307
  key_name: parentWorkflowId
@@ -347,10 +373,55 @@ const getWorkflowYAML = (app, version) => {
347
373
  type: boolean
348
374
  maps:
349
375
  done: true
376
+
377
+ s4:
378
+ title: Awaken child FLOWS so they end and self-clean
379
+ type: signal
380
+ subtype: all
381
+ key_name: parentWorkflowId
382
+ key_value:
383
+ '@pipe':
384
+ - ['{$job.metadata.jid}', '-f']
385
+ - ['{@string.concat}']
386
+ topic: ${app}.childflow.awaken
387
+ resolver:
388
+ schema:
389
+ type: object
390
+ properties:
391
+ data:
392
+ type: object
393
+ properties:
394
+ parentWorkflowId:
395
+ type: string
396
+ scrub:
397
+ type: boolean
398
+ maps:
399
+ data:
400
+ parentWorkflowId:
401
+ '@pipe':
402
+ - ['{$job.metadata.jid}', '-f']
403
+ - ['{@string.concat}']
404
+ scrub: true
405
+ signal:
406
+ schema:
407
+ type: object
408
+ properties:
409
+ done:
410
+ type: boolean
411
+ maps:
412
+ done: true
350
413
 
351
414
  transitions:
352
415
  t1:
353
416
  - to: a1
417
+ - to: a2
418
+ conditions:
419
+ match:
420
+ - expected: true
421
+ actual:
422
+ '@pipe':
423
+ - ['{$job.metadata.key}', true, false]
424
+ - ['{@conditional.ternary}']
354
425
  a1:
355
426
  - to: w1
356
427
  w1:
@@ -372,11 +443,13 @@ const getWorkflowYAML = (app, version) => {
372
443
  - to: s3
373
444
  conditions:
374
445
  code: [200, 598, 597, 596]
446
+ - to: s4
447
+ conditions:
448
+ code: [200, 598, 597, 596]
375
449
  a594:
376
450
  - to: c594
377
451
  conditions:
378
452
  code: 202
379
-
380
453
  a595:
381
454
  - to: c595
382
455
  conditions:
@@ -384,6 +457,14 @@ const getWorkflowYAML = (app, version) => {
384
457
  a599:
385
458
  - to: c599
386
459
 
460
+ hooks:
461
+ ${app}.childflow.awaken:
462
+ - to: a2
463
+ conditions:
464
+ match:
465
+ - expected: '{t1.output.data.workflowId}'
466
+ actual: '{$self.hook.data.id}'
467
+
387
468
  - subscribes: ${app}.activity.execute
388
469
  publishes: ${app}.activity.executed
389
470
 
@@ -51,17 +51,28 @@ class WorkflowService {
51
51
  const workflowId = store.get('workflowId');
52
52
  const workflowTrace = store.get('workflowTrace');
53
53
  const workflowSpan = store.get('workflowSpan');
54
+ const COUNTER = store.get('counter');
55
+ const execIndex = COUNTER.counter = COUNTER.counter + 1;
56
+ const childJobId = `${workflowId}-$${options.workflowName}-${execIndex}`;
57
+ const parentWorkflowId = `${workflowId}-f`;
54
58
  const client = new client_1.ClientService({
55
59
  connection: await connection_1.ConnectionService.connect(worker_1.WorkerService.connection),
56
60
  });
57
- const handle = await client.workflow.start({
58
- ...options,
59
- workflowId: `${workflowId}${options.workflowId}`,
60
- workflowTrace,
61
- workflowSpan,
62
- });
63
- const result = await handle.result();
64
- return result;
61
+ let handle = await client.workflow.getHandle(options.taskQueue, options.workflowName, childJobId);
62
+ try {
63
+ return await handle.result();
64
+ }
65
+ catch (error) {
66
+ handle = await client.workflow.start({
67
+ ...options,
68
+ workflowId: childJobId,
69
+ parentWorkflowId,
70
+ workflowTrace,
71
+ workflowSpan,
72
+ });
73
+ const result = await handle.result();
74
+ return result;
75
+ }
65
76
  }
66
77
  static proxyActivities(options) {
67
78
  if (options.activities) {
@@ -168,7 +179,7 @@ class WorkflowService {
168
179
  const trc = store.get('workflowTrace');
169
180
  const spn = store.get('workflowSpan');
170
181
  const activityTopic = `${workflowTopic}-activity`;
171
- const activityJobId = `${workflowId}-${activityName}-${execIndex}`;
182
+ const activityJobId = `${workflowId}-$${activityName}-${execIndex}`;
172
183
  let activityState;
173
184
  try {
174
185
  const hotMeshClient = await worker_1.WorkerService.getHotMesh(activityTopic);
@@ -373,6 +373,9 @@ class StoreService {
373
373
  async getStatus(jobId, appId) {
374
374
  const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
375
375
  const status = await this.redisClient[this.commands.hget](jobKey, ':');
376
+ if (status === null) {
377
+ throw new Error(`Job ${jobId} not found`);
378
+ }
376
379
  return Number(status);
377
380
  }
378
381
  async setState({ ...state }, status, jobId, symbolNames, dIds, multi) {
@@ -10,6 +10,7 @@ type WorkflowOptions = {
10
10
  args: any[];
11
11
  workflowId: string;
12
12
  workflowName?: string;
13
+ parentWorkflowId?: string;
13
14
  workflowTrace?: string;
14
15
  workflowSpan?: string;
15
16
  config?: WorkflowConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -209,8 +209,7 @@ class Activity {
209
209
  telemetry.setActivityAttributes(attrs);
210
210
  return jobStatus as number;
211
211
  } catch (error) {
212
- if (error instanceof CollationError) {
213
- //caused by external over-signaling; the job is complete
212
+ if (error instanceof CollationError && error.fault === 'inactive') {
214
213
  this.logger.info('process-hook-event-inactive-error', { error });
215
214
  return;
216
215
  }
@@ -256,6 +255,10 @@ class Activity {
256
255
  }
257
256
  this.transitionAdjacent(multiResponse, telemetry);
258
257
  } catch (error) {
258
+ if (error instanceof CollationError && error.fault === 'inactive') {
259
+ this.logger.info('process-event-inactive-error', { error });
260
+ return;
261
+ }
259
262
  this.logger.error('activity-process-event-error', { error });
260
263
  telemetry && telemetry.setActivityError(error.message);
261
264
  throw error;
@@ -581,7 +584,12 @@ class Activity {
581
584
 
582
585
  async transition(adjacencyList: StreamData[], jobStatus: JobStatus): Promise<string[]> {
583
586
  let mIds: string[] = [];
584
- if (jobStatus <= 0 || this.config.emit) {
587
+ //emit can be a mapping (allows emissions to be driven by the job state)
588
+ let emit: boolean = false;
589
+ if (this.config.emit) {
590
+ emit = Pipe.resolve(this.config.emit, this.context);
591
+ }
592
+ if (jobStatus <= 0 || emit) {
585
593
  //activity should not send 'emit' if the job is truly over
586
594
  const isTrueEmit = jobStatus > 0;
587
595
  await this.engine.runJobCompletionTasks(this.context, isTrueEmit);
@@ -178,7 +178,7 @@ class Trigger extends Activity {
178
178
 
179
179
  async setStats(multi?: RedisMulti): Promise<void> {
180
180
  const md = this.context.metadata;
181
- if (this.config.stats?.measures) {
181
+ if (md.key && this.config.stats?.measures) {
182
182
  const config = await this.engine.getVID();
183
183
  const reporter = new ReporterService(config, this.store, this.logger);
184
184
  await this.store.setStats(
@@ -101,6 +101,7 @@ export class ClientService {
101
101
  const hotMeshClient = await this.getHotMeshClient(workflowTopic);
102
102
  const payload = {
103
103
  arguments: [...options.args],
104
+ parentWorkflowId: options.parentWorkflowId,
104
105
  workflowId: options.workflowId || nanoid(),
105
106
  workflowTopic: workflowTopic,
106
107
  backoffCoefficient: options.config?.backoffCoefficient || DEFAULT_COEFFICIENT,
@@ -115,6 +116,12 @@ export class ClientService {
115
116
 
116
117
  signal: async (signalId: string, data: Record<any, any>): Promise<string> => {
117
118
  return await (await this.getHotMeshClient('durable.wfs.signal')).hook('durable.wfs.signal', { id: signalId, data });
119
+ },
120
+
121
+ getHandle: async (taskQueue: string, workflowName: string, workflowId: string): Promise<WorkflowHandleService> => {
122
+ const workflowTopic = `${taskQueue}-${workflowName}`;
123
+ const hotMeshClient = await this.getHotMeshClient(workflowTopic);
124
+ return new WorkflowHandleService(hotMeshClient, workflowTopic, workflowId);
118
125
  }
119
126
  }
120
127
 
@@ -2,6 +2,11 @@
2
2
  * NOTE: Using `maxSystemRetries = 3` and `backoffCoefficient = 10`, errant
3
3
  * workflows will be retried on the following schedule (8 times in 27 hours):
4
4
  * => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
5
+ * 593:
6
+ * 594: waitforsignal
7
+ * 595: sleep
8
+ * 596, 597, 598: fatal
9
+ * 599: retry
5
10
  */
6
11
 
7
12
  //todo: getChildWorkflowYAML (includes key, so flow will cleanup)
@@ -19,6 +24,8 @@ const getWorkflowYAML = (app: string, version: string) => {
19
24
  schema:
20
25
  type: object
21
26
  properties:
27
+ parentWorkflowId:
28
+ type: string
22
29
  workflowId:
23
30
  type: string
24
31
  arguments:
@@ -41,6 +48,11 @@ const getWorkflowYAML = (app: string, version: string) => {
41
48
  type: trigger
42
49
  stats:
43
50
  id: '{$self.input.data.workflowId}'
51
+ key: '{$self.input.data.parentWorkflowId}'
52
+ granularity: infinity
53
+ measures:
54
+ - measure: index
55
+ target: '{$self.input.data.parentWorkflowId}'
44
56
  job:
45
57
  maps:
46
58
  done: false
@@ -60,6 +72,7 @@ const getWorkflowYAML = (app: string, version: string) => {
60
72
  w1:
61
73
  type: worker
62
74
  topic: '{t1.output.data.workflowTopic}'
75
+ emit: '{$job.data.done}'
63
76
  retry:
64
77
  '599': [2]
65
78
  input:
@@ -117,6 +130,19 @@ const getWorkflowYAML = (app: string, version: string) => {
117
130
  response: '{$self.output.data.response}'
118
131
  done: '{$self.output.data.done}'
119
132
 
133
+ a2:
134
+ type: activity
135
+ title: Wait for cleanup signal
136
+ hook:
137
+ type: object
138
+ properties:
139
+ done:
140
+ type: boolean
141
+ job:
142
+ maps:
143
+ workflowId: '{t1.output.data.workflowId}'
144
+
145
+
120
146
  a594:
121
147
  title: Wait for signals
122
148
  type: await
@@ -274,7 +300,7 @@ const getWorkflowYAML = (app: string, version: string) => {
274
300
  done: true
275
301
 
276
302
  s2:
277
- title: Awaken sleep flows so they end and self-clean
303
+ title: Awaken sleeping flows so they end and self-clean
278
304
  type: signal
279
305
  subtype: all
280
306
  key_name: parentWorkflowId
@@ -346,10 +372,55 @@ const getWorkflowYAML = (app: string, version: string) => {
346
372
  type: boolean
347
373
  maps:
348
374
  done: true
375
+
376
+ s4:
377
+ title: Awaken child FLOWS so they end and self-clean
378
+ type: signal
379
+ subtype: all
380
+ key_name: parentWorkflowId
381
+ key_value:
382
+ '@pipe':
383
+ - ['{$job.metadata.jid}', '-f']
384
+ - ['{@string.concat}']
385
+ topic: ${app}.childflow.awaken
386
+ resolver:
387
+ schema:
388
+ type: object
389
+ properties:
390
+ data:
391
+ type: object
392
+ properties:
393
+ parentWorkflowId:
394
+ type: string
395
+ scrub:
396
+ type: boolean
397
+ maps:
398
+ data:
399
+ parentWorkflowId:
400
+ '@pipe':
401
+ - ['{$job.metadata.jid}', '-f']
402
+ - ['{@string.concat}']
403
+ scrub: true
404
+ signal:
405
+ schema:
406
+ type: object
407
+ properties:
408
+ done:
409
+ type: boolean
410
+ maps:
411
+ done: true
349
412
 
350
413
  transitions:
351
414
  t1:
352
415
  - to: a1
416
+ - to: a2
417
+ conditions:
418
+ match:
419
+ - expected: true
420
+ actual:
421
+ '@pipe':
422
+ - ['{$job.metadata.key}', true, false]
423
+ - ['{@conditional.ternary}']
353
424
  a1:
354
425
  - to: w1
355
426
  w1:
@@ -371,11 +442,13 @@ const getWorkflowYAML = (app: string, version: string) => {
371
442
  - to: s3
372
443
  conditions:
373
444
  code: [200, 598, 597, 596]
445
+ - to: s4
446
+ conditions:
447
+ code: [200, 598, 597, 596]
374
448
  a594:
375
449
  - to: c594
376
450
  conditions:
377
451
  code: 202
378
-
379
452
  a595:
380
453
  - to: c595
381
454
  conditions:
@@ -383,6 +456,14 @@ const getWorkflowYAML = (app: string, version: string) => {
383
456
  a599:
384
457
  - to: c599
385
458
 
459
+ hooks:
460
+ ${app}.childflow.awaken:
461
+ - to: a2
462
+ conditions:
463
+ match:
464
+ - expected: '{t1.output.data.workflowId}'
465
+ actual: '{$self.hook.data.id}'
466
+
386
467
  - subscribes: ${app}.activity.execute
387
468
  publishes: ${app}.activity.executed
388
469
 
@@ -8,7 +8,6 @@ import { ActivityConfig, ProxyType, WorkflowOptions } from "../../types/durable"
8
8
  import { JobOutput, JobState } from '../../types';
9
9
  import { ACTIVITY_PUBLISHES_TOPIC, ACTIVITY_SUBSCRIBES_TOPIC, SLEEP_SUBSCRIBES_TOPIC, WFS_SUBSCRIBES_TOPIC } from './factory';
10
10
  import { DurableIncompleteSignalError, DurableSleepError, DurableWaitForSignalError } from '../../modules/errors';
11
- import { stringify } from 'querystring';
12
11
 
13
12
  /*
14
13
  `proxyActivities` returns a wrapped instance of the
@@ -52,18 +51,34 @@ export class WorkflowService {
52
51
  const workflowId = store.get('workflowId');
53
52
  const workflowTrace = store.get('workflowTrace');
54
53
  const workflowSpan = store.get('workflowSpan');
54
+ const COUNTER = store.get('counter');
55
+ const execIndex = COUNTER.counter = COUNTER.counter + 1;
56
+ const childJobId = `${workflowId}-$${options.workflowName}-${execIndex}`;
57
+ const parentWorkflowId = `${workflowId}-f`;
55
58
 
56
59
  const client = new Client({
57
60
  connection: await Connection.connect(WorkerService.connection),
58
61
  });
59
- const handle = await client.workflow.start({
60
- ...options,
61
- workflowId: `${workflowId}${options.workflowId}`, //concat (caller MUST PROVIDE)
62
- workflowTrace,
63
- workflowSpan,
64
- });
65
- const result = await handle.result();
66
- return result as T;
62
+
63
+ let handle = await client.workflow.getHandle(
64
+ options.taskQueue,
65
+ options.workflowName,
66
+ childJobId
67
+ );
68
+
69
+ try {
70
+ return await handle.result() as T;
71
+ } catch (error) {
72
+ handle = await client.workflow.start({
73
+ ...options,
74
+ workflowId: childJobId,
75
+ parentWorkflowId,
76
+ workflowTrace,
77
+ workflowSpan,
78
+ });
79
+ const result = await handle.result();
80
+ return result as T;
81
+ }
67
82
  }
68
83
 
69
84
  static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT> {
@@ -172,7 +187,7 @@ export class WorkflowService {
172
187
  const trc = store.get('workflowTrace');
173
188
  const spn = store.get('workflowSpan');
174
189
  const activityTopic = `${workflowTopic}-activity`;
175
- const activityJobId = `${workflowId}-${activityName}-${execIndex}`;
190
+ const activityJobId = `${workflowId}-$${activityName}-${execIndex}`;
176
191
 
177
192
  let activityState: JobOutput
178
193
  try {
@@ -452,6 +452,9 @@ abstract class StoreService<T, U extends AbstractRedisClient> {
452
452
  async getStatus(jobId: string, appId: string): Promise<number> {
453
453
  const jobKey = this.mintKey(KeyType.JOB_STATE, { appId, jobId });
454
454
  const status = await this.redisClient[this.commands.hget](jobKey, ':');
455
+ if (status === null) {
456
+ throw new Error(`Job ${jobId} not found`);
457
+ }
455
458
  return Number(status);
456
459
  }
457
460
 
package/types/durable.ts CHANGED
@@ -12,6 +12,7 @@ type WorkflowOptions = {
12
12
  args: any[]; //input arguments to pass in
13
13
  workflowId: string; //execution id (the job id)
14
14
  workflowName?: string; //the name of the user's workflow function
15
+ parentWorkflowId?: string; //system reserved; the id of the parent; if present the flow will not self-clean until the parent that spawned it self-cleans
15
16
  workflowTrace?: string;
16
17
  workflowSpan?: string;
17
18
  config?: WorkflowConfig;