@hotmeshio/hotmesh 0.0.5 → 0.0.7

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.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Durable Workflows",
5
5
  "main": "build/cjs/index.js",
6
6
  "module": "build/esm/index.js",
@@ -320,10 +320,9 @@ class Activity {
320
320
  telemetry_1.TelemetryService.addTargetTelemetryPaths(consumes, this.config, this.metadata, this.leg);
321
321
  let { dad, jid } = this.context.metadata;
322
322
  jobId = jobId || jid;
323
- //`state` is a flat hash
324
323
  const dIds = collator_1.CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad);
324
+ //`state` is a flat hash; context is a tree
325
325
  const [state, status] = await this.store.getState(jobId, consumes, dIds);
326
- //`context` is a tree
327
326
  this.context = (0, utils_1.restoreHierarchy)(state);
328
327
  this.initDimensionalAddress(dad);
329
328
  this.initSelf(this.context);
@@ -2,6 +2,7 @@ import { Activity } from './activity';
2
2
  import { EngineService } from '../engine';
3
3
  import { ActivityData, ActivityMetadata, ActivityType, WorkerActivity } from '../../types/activity';
4
4
  import { JobState } from '../../types/job';
5
+ import { MultiResponseFlags } from '../../types/redis';
5
6
  import { StreamCode, StreamStatus } from '../../types/stream';
6
7
  import { TelemetryService } from '../telemetry';
7
8
  declare class Worker extends Activity {
@@ -13,5 +14,6 @@ declare class Worker extends Activity {
13
14
  processPending(telemetry: TelemetryService): Promise<void>;
14
15
  processSuccess(telemetry: TelemetryService): Promise<void>;
15
16
  processError(telemetry: TelemetryService): Promise<void>;
17
+ transitionAdjacent(multiResponse: MultiResponseFlags, telemetry: TelemetryService): Promise<void>;
16
18
  }
17
19
  export { Worker };
@@ -120,11 +120,9 @@ class Worker extends activity_1.Activity {
120
120
  const multi = this.store.getMulti();
121
121
  await this.setState(multi);
122
122
  await collator_1.CollatorService.notarizeContinuation(this, multi);
123
- await this.setStatus(0, multi);
123
+ await this.setStatus(this.adjacencyList.length, multi);
124
124
  const multiResponse = await multi.exec();
125
- telemetry.mapActivityAttributes();
126
- const jobStatus = this.resolveStatus(multiResponse);
127
- telemetry.setActivityAttributes({ 'app.job.jss': jobStatus });
125
+ this.transitionAdjacent(multiResponse, telemetry);
128
126
  }
129
127
  async processSuccess(telemetry) {
130
128
  this.bindActivityData('output');
@@ -135,14 +133,7 @@ class Worker extends activity_1.Activity {
135
133
  await collator_1.CollatorService.notarizeCompletion(this, multi);
136
134
  await this.setStatus(this.adjacencyList.length - 1, multi);
137
135
  const multiResponse = await multi.exec();
138
- telemetry.mapActivityAttributes();
139
- const jobStatus = this.resolveStatus(multiResponse);
140
- const attrs = { 'app.job.jss': jobStatus };
141
- const messageIds = await this.transition(this.adjacencyList, jobStatus);
142
- if (messageIds.length) {
143
- attrs['app.activity.mids'] = messageIds.join(',');
144
- }
145
- telemetry.setActivityAttributes(attrs);
136
+ this.transitionAdjacent(multiResponse, telemetry);
146
137
  }
147
138
  async processError(telemetry) {
148
139
  this.bindActivityError(this.data);
@@ -152,6 +143,9 @@ class Worker extends activity_1.Activity {
152
143
  await collator_1.CollatorService.notarizeCompletion(this, multi);
153
144
  await this.setStatus(this.adjacencyList.length - 1, multi);
154
145
  const multiResponse = await multi.exec();
146
+ this.transitionAdjacent(multiResponse, telemetry);
147
+ }
148
+ async transitionAdjacent(multiResponse, telemetry) {
155
149
  telemetry.mapActivityAttributes();
156
150
  const jobStatus = this.resolveStatus(multiResponse);
157
151
  const attrs = { 'app.job.jss': jobStatus };
@@ -20,6 +20,7 @@ export declare class WorkerService {
20
20
  */
21
21
  static registerActivities<ACT>(activities: ACT): Registry;
22
22
  static create(config: WorkerConfig): Promise<WorkerService>;
23
+ static resolveWorkflowTarget(workflow: object | Function): [string, Function];
23
24
  run(): Promise<void>;
24
25
  initActivityWorkflow(config: WorkerConfig, activityTopic: string): Promise<HotMesh>;
25
26
  wrapActivityFunctions(): Function;
@@ -99,20 +99,22 @@ class WorkerService {
99
99
  * allowing proxyActivities to succeed.
100
100
  */
101
101
  static registerActivities(activities) {
102
- Object.keys(activities).forEach(key => {
103
- WorkerService.activityRegistry[key] = activities[key];
104
- });
102
+ if (typeof activities === 'function') {
103
+ WorkerService.activityRegistry[activities.name] = activities;
104
+ }
105
+ else {
106
+ Object.keys(activities).forEach(key => {
107
+ WorkerService.activityRegistry[activities[key].name] = activities[key];
108
+ });
109
+ }
105
110
  return WorkerService.activityRegistry;
106
111
  }
107
112
  static async create(config) {
113
+ //always call `registerActivities` before `import`
108
114
  WorkerService.connection = config.connection;
109
- //pre-cache user activity functions
110
115
  WorkerService.registerActivities(config.activities);
111
- //import the user's workflow file (triggers activity functions to be wrapped)
112
116
  const workflow = await Promise.resolve(`${config.workflowsPath}`).then(s => __importStar(require(s)));
113
- const workflowFunctionNames = Object.keys(workflow);
114
- const workflowFunctionName = workflowFunctionNames[workflowFunctionNames.length - 1];
115
- const workflowFunction = workflow[workflowFunctionName];
117
+ const [workflowFunctionName, workflowFunction] = WorkerService.resolveWorkflowTarget(workflow);
116
118
  const baseTopic = `${config.taskQueue}-${workflowFunctionName}`;
117
119
  const activityTopic = `${baseTopic}-activity`;
118
120
  const workflowTopic = `${baseTopic}`;
@@ -124,6 +126,18 @@ class WorkerService {
124
126
  await WorkerService.activateWorkflow(worker.workflowRunner, workflowTopic, factory_1.getWorkflowYAML);
125
127
  return worker;
126
128
  }
129
+ static resolveWorkflowTarget(workflow) {
130
+ let workflowFunction;
131
+ if (typeof workflow === 'function') {
132
+ workflowFunction = workflow;
133
+ }
134
+ else {
135
+ const workflowFunctionNames = Object.keys(workflow);
136
+ workflowFunction = workflow[workflowFunctionNames[workflowFunctionNames.length - 1]];
137
+ return WorkerService.resolveWorkflowTarget(workflowFunction);
138
+ }
139
+ return [workflowFunction.name, workflowFunction];
140
+ }
127
141
  async run() {
128
142
  if (this.workflowRunner) {
129
143
  this.workflowRunner.engine.logger.info('WorkerService is running');
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Durable Workflows",
5
5
  "main": "build/cjs/index.js",
6
6
  "module": "build/esm/index.js",
@@ -317,10 +317,9 @@ class Activity {
317
317
  TelemetryService.addTargetTelemetryPaths(consumes, this.config, this.metadata, this.leg);
318
318
  let { dad, jid } = this.context.metadata;
319
319
  jobId = jobId || jid;
320
- //`state` is a flat hash
321
320
  const dIds = CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad);
321
+ //`state` is a flat hash; context is a tree
322
322
  const [state, status] = await this.store.getState(jobId, consumes, dIds);
323
- //`context` is a tree
324
323
  this.context = restoreHierarchy(state);
325
324
  this.initDimensionalAddress(dad);
326
325
  this.initSelf(this.context);
@@ -2,6 +2,7 @@ import { Activity } from './activity';
2
2
  import { EngineService } from '../engine';
3
3
  import { ActivityData, ActivityMetadata, ActivityType, WorkerActivity } from '../../types/activity';
4
4
  import { JobState } from '../../types/job';
5
+ import { MultiResponseFlags } from '../../types/redis';
5
6
  import { StreamCode, StreamStatus } from '../../types/stream';
6
7
  import { TelemetryService } from '../telemetry';
7
8
  declare class Worker extends Activity {
@@ -13,5 +14,6 @@ declare class Worker extends Activity {
13
14
  processPending(telemetry: TelemetryService): Promise<void>;
14
15
  processSuccess(telemetry: TelemetryService): Promise<void>;
15
16
  processError(telemetry: TelemetryService): Promise<void>;
17
+ transitionAdjacent(multiResponse: MultiResponseFlags, telemetry: TelemetryService): Promise<void>;
16
18
  }
17
19
  export { Worker };
@@ -117,11 +117,9 @@ class Worker extends Activity {
117
117
  const multi = this.store.getMulti();
118
118
  await this.setState(multi);
119
119
  await CollatorService.notarizeContinuation(this, multi);
120
- await this.setStatus(0, multi);
120
+ await this.setStatus(this.adjacencyList.length, multi);
121
121
  const multiResponse = await multi.exec();
122
- telemetry.mapActivityAttributes();
123
- const jobStatus = this.resolveStatus(multiResponse);
124
- telemetry.setActivityAttributes({ 'app.job.jss': jobStatus });
122
+ this.transitionAdjacent(multiResponse, telemetry);
125
123
  }
126
124
  async processSuccess(telemetry) {
127
125
  this.bindActivityData('output');
@@ -132,14 +130,7 @@ class Worker extends Activity {
132
130
  await CollatorService.notarizeCompletion(this, multi);
133
131
  await this.setStatus(this.adjacencyList.length - 1, multi);
134
132
  const multiResponse = await multi.exec();
135
- telemetry.mapActivityAttributes();
136
- const jobStatus = this.resolveStatus(multiResponse);
137
- const attrs = { 'app.job.jss': jobStatus };
138
- const messageIds = await this.transition(this.adjacencyList, jobStatus);
139
- if (messageIds.length) {
140
- attrs['app.activity.mids'] = messageIds.join(',');
141
- }
142
- telemetry.setActivityAttributes(attrs);
133
+ this.transitionAdjacent(multiResponse, telemetry);
143
134
  }
144
135
  async processError(telemetry) {
145
136
  this.bindActivityError(this.data);
@@ -149,6 +140,9 @@ class Worker extends Activity {
149
140
  await CollatorService.notarizeCompletion(this, multi);
150
141
  await this.setStatus(this.adjacencyList.length - 1, multi);
151
142
  const multiResponse = await multi.exec();
143
+ this.transitionAdjacent(multiResponse, telemetry);
144
+ }
145
+ async transitionAdjacent(multiResponse, telemetry) {
152
146
  telemetry.mapActivityAttributes();
153
147
  const jobStatus = this.resolveStatus(multiResponse);
154
148
  const attrs = { 'app.job.jss': jobStatus };
@@ -20,6 +20,7 @@ export declare class WorkerService {
20
20
  */
21
21
  static registerActivities<ACT>(activities: ACT): Registry;
22
22
  static create(config: WorkerConfig): Promise<WorkerService>;
23
+ static resolveWorkflowTarget(workflow: object | Function): [string, Function];
23
24
  run(): Promise<void>;
24
25
  initActivityWorkflow(config: WorkerConfig, activityTopic: string): Promise<HotMesh>;
25
26
  wrapActivityFunctions(): Function;
@@ -73,20 +73,22 @@ class WorkerService {
73
73
  * allowing proxyActivities to succeed.
74
74
  */
75
75
  static registerActivities(activities) {
76
- Object.keys(activities).forEach(key => {
77
- WorkerService.activityRegistry[key] = activities[key];
78
- });
76
+ if (typeof activities === 'function') {
77
+ WorkerService.activityRegistry[activities.name] = activities;
78
+ }
79
+ else {
80
+ Object.keys(activities).forEach(key => {
81
+ WorkerService.activityRegistry[activities[key].name] = activities[key];
82
+ });
83
+ }
79
84
  return WorkerService.activityRegistry;
80
85
  }
81
86
  static async create(config) {
87
+ //always call `registerActivities` before `import`
82
88
  WorkerService.connection = config.connection;
83
- //pre-cache user activity functions
84
89
  WorkerService.registerActivities(config.activities);
85
- //import the user's workflow file (triggers activity functions to be wrapped)
86
90
  const workflow = await import(config.workflowsPath);
87
- const workflowFunctionNames = Object.keys(workflow);
88
- const workflowFunctionName = workflowFunctionNames[workflowFunctionNames.length - 1];
89
- const workflowFunction = workflow[workflowFunctionName];
91
+ const [workflowFunctionName, workflowFunction] = WorkerService.resolveWorkflowTarget(workflow);
90
92
  const baseTopic = `${config.taskQueue}-${workflowFunctionName}`;
91
93
  const activityTopic = `${baseTopic}-activity`;
92
94
  const workflowTopic = `${baseTopic}`;
@@ -98,6 +100,18 @@ class WorkerService {
98
100
  await WorkerService.activateWorkflow(worker.workflowRunner, workflowTopic, getWorkflowYAML);
99
101
  return worker;
100
102
  }
103
+ static resolveWorkflowTarget(workflow) {
104
+ let workflowFunction;
105
+ if (typeof workflow === 'function') {
106
+ workflowFunction = workflow;
107
+ }
108
+ else {
109
+ const workflowFunctionNames = Object.keys(workflow);
110
+ workflowFunction = workflow[workflowFunctionNames[workflowFunctionNames.length - 1]];
111
+ return WorkerService.resolveWorkflowTarget(workflowFunction);
112
+ }
113
+ return [workflowFunction.name, workflowFunction];
114
+ }
101
115
  async run() {
102
116
  if (this.workflowRunner) {
103
117
  this.workflowRunner.engine.logger.info('WorkerService is running');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Durable Workflows",
5
5
  "main": "build/cjs/index.js",
6
6
  "module": "build/esm/index.js",
@@ -391,10 +391,9 @@ class Activity {
391
391
  TelemetryService.addTargetTelemetryPaths(consumes, this.config, this.metadata, this.leg);
392
392
  let { dad, jid } = this.context.metadata;
393
393
  jobId = jobId || jid;
394
- //`state` is a flat hash
395
394
  const dIds = CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad);
395
+ //`state` is a flat hash; context is a tree
396
396
  const [state, status] = await this.store.getState(jobId, consumes, dIds);
397
- //`context` is a tree
398
397
  this.context = restoreHierarchy(state) as JobState;
399
398
  this.initDimensionalAddress(dad);
400
399
  this.initSelf(this.context);
@@ -110,6 +110,7 @@ class Worker extends Activity {
110
110
 
111
111
  telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
112
112
  let isComplete = CollatorService.isActivityComplete(this.context.metadata.js);
113
+
113
114
  if (isComplete) {
114
115
  this.logger.warn('worker-process-event-duplicate', { jid, aid });
115
116
  this.logger.debug('worker-process-event-duplicate-resolution', { resolution: 'Increase HotMesh config `reclaimDelay` timeout.' });
@@ -141,11 +142,9 @@ class Worker extends Activity {
141
142
  await this.setState(multi);
142
143
  await CollatorService.notarizeContinuation(this, multi);
143
144
 
144
- await this.setStatus(0, multi);
145
+ await this.setStatus(this.adjacencyList.length, multi);
145
146
  const multiResponse = await multi.exec() as MultiResponseFlags;
146
- telemetry.mapActivityAttributes();
147
- const jobStatus = this.resolveStatus(multiResponse);
148
- telemetry.setActivityAttributes({ 'app.job.jss': jobStatus });
147
+ this.transitionAdjacent(multiResponse, telemetry);
149
148
  }
150
149
 
151
150
  async processSuccess(telemetry: TelemetryService): Promise<void> {
@@ -158,14 +157,7 @@ class Worker extends Activity {
158
157
 
159
158
  await this.setStatus(this.adjacencyList.length - 1, multi);
160
159
  const multiResponse = await multi.exec() as MultiResponseFlags;
161
- telemetry.mapActivityAttributes();
162
- const jobStatus = this.resolveStatus(multiResponse);
163
- const attrs: StringScalarType = { 'app.job.jss': jobStatus };
164
- const messageIds = await this.transition(this.adjacencyList, jobStatus);
165
- if (messageIds.length) {
166
- attrs['app.activity.mids'] = messageIds.join(',')
167
- }
168
- telemetry.setActivityAttributes(attrs);
160
+ this.transitionAdjacent(multiResponse, telemetry);
169
161
  }
170
162
 
171
163
  async processError(telemetry: TelemetryService): Promise<void> {
@@ -177,6 +169,10 @@ class Worker extends Activity {
177
169
 
178
170
  await this.setStatus(this.adjacencyList.length - 1, multi);
179
171
  const multiResponse = await multi.exec() as MultiResponseFlags;
172
+ this.transitionAdjacent(multiResponse, telemetry);
173
+ }
174
+
175
+ async transitionAdjacent(multiResponse: MultiResponseFlags, telemetry: TelemetryService): Promise<void> {
180
176
  telemetry.mapActivityAttributes();
181
177
  const jobStatus = this.resolveStatus(multiResponse);
182
178
  const attrs: StringScalarType = { 'app.job.jss': jobStatus };
@@ -92,28 +92,28 @@ export class WorkerService {
92
92
  * allowing proxyActivities to succeed.
93
93
  */
94
94
  static registerActivities<ACT>(activities: ACT): Registry {
95
- Object.keys(activities).forEach(key => {
96
- WorkerService.activityRegistry[key] = (activities as any)[key];
97
- });
95
+ if (typeof activities === 'function') {
96
+ WorkerService.activityRegistry[activities.name] = activities as Function;
97
+ } else {
98
+ Object.keys(activities).forEach(key => {
99
+ WorkerService.activityRegistry[activities[key].name] = (activities as any)[key] as Function;
100
+ });
101
+ }
98
102
  return WorkerService.activityRegistry;
99
103
  }
100
104
 
101
105
  static async create(config: WorkerConfig) {
106
+ //always call `registerActivities` before `import`
102
107
  WorkerService.connection = config.connection;
103
- //pre-cache user activity functions
104
108
  WorkerService.registerActivities<typeof config.activities>(config.activities);
105
- //import the user's workflow file (triggers activity functions to be wrapped)
106
109
  const workflow = await import(config.workflowsPath);
107
- const workflowFunctionNames = Object.keys(workflow);
108
- const workflowFunctionName = workflowFunctionNames[workflowFunctionNames.length - 1];
109
- const workflowFunction = workflow[workflowFunctionName];
110
+ const [workflowFunctionName, workflowFunction] = WorkerService.resolveWorkflowTarget(workflow);
110
111
  const baseTopic = `${config.taskQueue}-${workflowFunctionName}`;
111
112
  const activityTopic = `${baseTopic}-activity`;
112
113
  const workflowTopic = `${baseTopic}`;
113
114
 
114
115
  //initialize supporting workflows
115
116
  const worker = new WorkerService();
116
-
117
117
  const activityRunner = await worker.initActivityWorkflow(config, activityTopic);
118
118
  await WorkerService.activateWorkflow(activityRunner, activityTopic, getActivityYAML);
119
119
  worker.workflowRunner = await worker.initWorkerWorkflow(config, workflowTopic, workflowFunction);
@@ -121,6 +121,18 @@ export class WorkerService {
121
121
  return worker;
122
122
  }
123
123
 
124
+ static resolveWorkflowTarget(workflow: object | Function): [string, Function] {
125
+ let workflowFunction: Function;
126
+ if (typeof workflow === 'function') {
127
+ workflowFunction = workflow;
128
+ } else {
129
+ const workflowFunctionNames = Object.keys(workflow);
130
+ workflowFunction = workflow[workflowFunctionNames[workflowFunctionNames.length - 1]];
131
+ return WorkerService.resolveWorkflowTarget(workflowFunction);
132
+ }
133
+ return [workflowFunction.name, workflowFunction];
134
+ }
135
+
124
136
  async run() {
125
137
  if (this.workflowRunner) {
126
138
  this.workflowRunner.engine.logger.info('WorkerService is running');