@hotmeshio/hotmesh 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +2 -2
  2. package/build/modules/errors.d.ts +22 -1
  3. package/build/modules/errors.js +28 -1
  4. package/build/modules/utils.d.ts +2 -1
  5. package/build/modules/utils.js +5 -1
  6. package/build/package.json +7 -2
  7. package/build/services/activities/activity.d.ts +2 -0
  8. package/build/services/activities/activity.js +16 -10
  9. package/build/services/activities/await.d.ts +2 -6
  10. package/build/services/activities/await.js +12 -75
  11. package/build/services/activities/cycle.js +2 -2
  12. package/build/services/activities/index.d.ts +2 -2
  13. package/build/services/activities/index.js +2 -2
  14. package/build/services/activities/signal.d.ts +16 -0
  15. package/build/services/activities/signal.js +94 -0
  16. package/build/services/activities/trigger.js +4 -3
  17. package/build/services/activities/worker.d.ts +2 -1
  18. package/build/services/activities/worker.js +11 -6
  19. package/build/services/compiler/deployer.js +3 -1
  20. package/build/services/durable/client.d.ts +3 -2
  21. package/build/services/durable/client.js +39 -21
  22. package/build/services/durable/factory.d.ts +22 -18
  23. package/build/services/durable/factory.js +722 -50
  24. package/build/services/durable/handle.d.ts +1 -0
  25. package/build/services/durable/handle.js +5 -1
  26. package/build/services/durable/worker.d.ts +3 -8
  27. package/build/services/durable/worker.js +75 -73
  28. package/build/services/durable/workflow.d.ts +5 -0
  29. package/build/services/durable/workflow.js +93 -24
  30. package/build/services/engine/index.d.ts +6 -6
  31. package/build/services/engine/index.js +25 -15
  32. package/build/services/hotmesh/index.d.ts +2 -1
  33. package/build/services/hotmesh/index.js +3 -1
  34. package/build/services/mapper/index.js +1 -1
  35. package/build/services/pipe/functions/array.d.ts +1 -0
  36. package/build/services/pipe/functions/array.js +3 -0
  37. package/build/services/reporter/index.js +9 -2
  38. package/build/services/signaler/store.js +8 -3
  39. package/build/services/signaler/stream.js +3 -3
  40. package/build/services/store/clients/ioredis.js +15 -15
  41. package/build/services/store/clients/redis.js +18 -18
  42. package/build/services/store/index.d.ts +1 -1
  43. package/build/services/store/index.js +11 -3
  44. package/build/services/task/index.js +3 -3
  45. package/build/types/activity.d.ts +15 -6
  46. package/build/types/durable.d.ts +15 -2
  47. package/build/types/index.d.ts +2 -2
  48. package/build/types/stats.d.ts +1 -0
  49. package/modules/errors.ts +35 -0
  50. package/modules/utils.ts +5 -1
  51. package/package.json +7 -2
  52. package/services/activities/activity.ts +19 -9
  53. package/services/activities/await.ts +14 -90
  54. package/services/activities/cycle.ts +2 -2
  55. package/services/activities/index.ts +2 -2
  56. package/services/activities/signal.ts +124 -0
  57. package/services/activities/trigger.ts +4 -3
  58. package/services/activities/worker.ts +13 -13
  59. package/services/compiler/deployer.ts +3 -1
  60. package/services/durable/client.ts +48 -23
  61. package/services/durable/factory.ts +723 -49
  62. package/services/durable/handle.ts +6 -1
  63. package/services/durable/worker.ts +92 -79
  64. package/services/durable/workflow.ts +95 -25
  65. package/services/engine/index.ts +33 -24
  66. package/services/hotmesh/index.ts +7 -4
  67. package/services/mapper/index.ts +1 -1
  68. package/services/pipe/functions/array.ts +4 -0
  69. package/services/reporter/index.ts +10 -2
  70. package/services/signaler/store.ts +8 -3
  71. package/services/signaler/stream.ts +3 -3
  72. package/services/store/clients/ioredis.ts +15 -15
  73. package/services/store/clients/redis.ts +18 -18
  74. package/services/store/index.ts +12 -3
  75. package/services/task/index.ts +3 -3
  76. package/types/activity.ts +16 -7
  77. package/types/durable.ts +18 -1
  78. package/types/index.ts +2 -1
  79. package/types/stats.ts +1 -0
  80. package/build/services/activities/emit.d.ts +0 -9
  81. package/build/services/activities/emit.js +0 -13
  82. package/services/activities/emit.ts +0 -25
@@ -5,6 +5,7 @@ const errors_1 = require("../../modules/errors");
5
5
  const activity_1 = require("./activity");
6
6
  const collator_1 = require("../collator");
7
7
  const telemetry_1 = require("../telemetry");
8
+ const pipe_1 = require("../pipe");
8
9
  class Worker extends activity_1.Activity {
9
10
  constructor(config, data, metadata, hook, engine, context) {
10
11
  super(config, data, metadata, hook, engine, context);
@@ -14,21 +15,24 @@ class Worker extends activity_1.Activity {
14
15
  this.logger.debug('worker-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
15
16
  let telemetry;
16
17
  try {
18
+ //confirm entry is allowed and restore state
17
19
  this.setLeg(1);
18
20
  await collator_1.CollatorService.notarizeEntry(this);
19
21
  await this.getState();
20
22
  telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
21
23
  telemetry.startActivitySpan(this.leg);
22
24
  this.mapInputData();
25
+ //save state and authorize reentry
23
26
  const multi = this.store.getMulti();
24
27
  //todo: await this.registerTimeout();
28
+ const messageId = await this.execActivity(multi);
25
29
  await collator_1.CollatorService.authorizeReentry(this, multi);
26
30
  await this.setState(multi);
27
31
  await this.setStatus(0, multi);
28
32
  const multiResponse = await multi.exec();
33
+ //telemetry
29
34
  telemetry.mapActivityAttributes();
30
35
  const jobStatus = this.resolveStatus(multiResponse);
31
- const messageId = await this.execActivity();
32
36
  telemetry.setActivityAttributes({
33
37
  'app.activity.mid': messageId,
34
38
  'app.job.jss': jobStatus
@@ -37,10 +41,10 @@ class Worker extends activity_1.Activity {
37
41
  }
38
42
  catch (error) {
39
43
  if (error instanceof errors_1.GetStateError) {
40
- this.logger.error('worker-get-state-error', error);
44
+ this.logger.error('worker-get-state-error', { error });
41
45
  }
42
46
  else {
43
- this.logger.error('worker-process-error', error);
47
+ this.logger.error('worker-process-error', { error });
44
48
  }
45
49
  telemetry.setActivityError(error.message);
46
50
  throw error;
@@ -50,13 +54,14 @@ class Worker extends activity_1.Activity {
50
54
  this.logger.debug('worker-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
51
55
  }
52
56
  }
53
- async execActivity() {
57
+ async execActivity(multi) {
58
+ const topic = pipe_1.Pipe.resolve(this.config.subtype, this.context);
54
59
  const streamData = {
55
60
  metadata: {
56
61
  jid: this.context.metadata.jid,
57
62
  dad: this.metadata.dad,
58
63
  aid: this.metadata.aid,
59
- topic: this.config.subtype,
64
+ topic,
60
65
  spn: this.context['$self'].output.metadata.l1s,
61
66
  trc: this.context.metadata.trc,
62
67
  },
@@ -67,7 +72,7 @@ class Worker extends activity_1.Activity {
67
72
  retry: this.config.retry
68
73
  };
69
74
  }
70
- return (await this.engine.streamSignaler?.publishMessage(this.config.subtype, streamData));
75
+ return (await this.engine.streamSignaler?.publishMessage(topic, streamData, multi));
71
76
  }
72
77
  }
73
78
  exports.Worker = Worker;
@@ -5,6 +5,7 @@ const key_1 = require("../../modules/key");
5
5
  const utils_1 = require("../../modules/utils");
6
6
  const collator_1 = require("../collator");
7
7
  const serializer_1 = require("../serializer");
8
+ const pipe_1 = require("../pipe");
8
9
  const DEFAULT_METADATA_RANGE_SIZE = 26; //metadata is 26 slots ([a-z] * 1)
9
10
  const DEFAULT_DATA_RANGE_SIZE = 260; //data is 260 slots ([a-zA-Z] * 5)
10
11
  const DEFAULT_RANGE_SIZE = DEFAULT_METADATA_RANGE_SIZE + DEFAULT_DATA_RANGE_SIZE;
@@ -405,7 +406,8 @@ class Deployer {
405
406
  const activities = graph.activities;
406
407
  for (const activityKey in activities) {
407
408
  const activity = activities[activityKey];
408
- if (activity.type === 'worker') {
409
+ //only precreate if the topic is concrete and not `mappable`
410
+ if (activity.type === 'worker' && pipe_1.Pipe.resolve(activity.subtype, {}) === activity.subtype) {
409
411
  params.topic = activity.subtype;
410
412
  const key = this.store.mintKey(key_1.KeyType.STREAMS, params);
411
413
  //create one worker group per unique activity subtype (the topic)
@@ -6,10 +6,11 @@ export declare class ClientService {
6
6
  options: WorkflowOptions;
7
7
  static instances: Map<string, HotMesh | Promise<HotMesh>>;
8
8
  constructor(config: ClientConfig);
9
- getHotMesh: (worflowTopic: string) => Promise<HotMesh>;
9
+ getHotMeshClient: (worflowTopic: string) => Promise<HotMesh>;
10
10
  workflow: {
11
11
  start: (options: WorkflowOptions) => Promise<WorkflowHandleService>;
12
+ signal: (signalId: string, data: Record<any, any>) => Promise<string>;
12
13
  };
13
- activateWorkflow(hotMesh: HotMesh, workflowTopic: string): Promise<void>;
14
+ activateWorkflow(hotMesh: HotMesh, appId?: string, version?: string): Promise<void>;
14
15
  static shutdown(): Promise<void>;
15
16
  }
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ClientService = void 0;
4
+ const nanoid_1 = require("nanoid");
5
+ const factory_1 = require("./factory");
4
6
  const handle_1 = require("./handle");
5
7
  const hotmesh_1 = require("../hotmesh");
6
- const factory_1 = require("./factory");
8
+ const key_1 = require("../../modules/key");
7
9
  /*
8
10
  Here is an example of how the methods in this file are used:
9
11
 
@@ -46,12 +48,13 @@ run().catch((err) => {
46
48
  */
47
49
  class ClientService {
48
50
  constructor(config) {
49
- this.getHotMesh = async (worflowTopic) => {
51
+ this.getHotMeshClient = async (worflowTopic) => {
52
+ //NOTE: every unique topic inits a new engine
50
53
  if (ClientService.instances.has(worflowTopic)) {
51
54
  return await ClientService.instances.get(worflowTopic);
52
55
  }
53
- const hotMesh = hotmesh_1.HotMeshService.init({
54
- appId: worflowTopic,
56
+ const hotMeshClient = hotmesh_1.HotMeshService.init({
57
+ appId: factory_1.APP_ID,
55
58
  engine: {
56
59
  redis: {
57
60
  class: this.connection.class,
@@ -59,9 +62,19 @@ class ClientService {
59
62
  }
60
63
  }
61
64
  });
62
- ClientService.instances.set(worflowTopic, hotMesh);
63
- await this.activateWorkflow(await hotMesh, worflowTopic);
64
- return hotMesh;
65
+ ClientService.instances.set(worflowTopic, hotMeshClient);
66
+ //since the YAML topic is dynamic, it MUST be manually created before use
67
+ const store = (await hotMeshClient).engine.store;
68
+ const params = { appId: factory_1.APP_ID, topic: worflowTopic };
69
+ const streamKey = store.mintKey(key_1.KeyType.STREAMS, params);
70
+ try {
71
+ await store.xgroup('CREATE', streamKey, 'WORKER', '$', 'MKSTREAM');
72
+ }
73
+ catch (err) {
74
+ //ignore if already exists
75
+ }
76
+ await this.activateWorkflow(await hotMeshClient);
77
+ return hotMeshClient;
65
78
  };
66
79
  this.workflow = {
67
80
  start: async (options) => {
@@ -69,40 +82,45 @@ class ClientService {
69
82
  const workflowName = options.workflowName;
70
83
  const trc = options.workflowTrace;
71
84
  const spn = options.workflowSpan;
85
+ //topic is concat of taskQueue and workflowName
72
86
  const workflowTopic = `${taskQueueName}-${workflowName}`;
73
- const hotMesh = await this.getHotMesh(workflowTopic);
87
+ const hotMeshClient = await this.getHotMeshClient(workflowTopic);
74
88
  const payload = {
75
89
  arguments: [...options.args],
76
- workflowId: options.workflowId,
90
+ workflowId: options.workflowId || (0, nanoid_1.nanoid)(),
91
+ workflowTopic: workflowTopic,
92
+ backoffCoefficient: options.config?.backoffCoefficient || factory_1.DEFAULT_COEFFICIENT,
77
93
  };
78
94
  const context = { metadata: { trc, spn }, data: {} };
79
- const jobId = await hotMesh.pub(workflowTopic, payload, context);
80
- return new handle_1.WorkflowHandleService(hotMesh, workflowTopic, jobId);
95
+ const jobId = await hotMeshClient.pub(factory_1.SUBSCRIBES_TOPIC, payload, context);
96
+ return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
81
97
  },
98
+ signal: async (signalId, data) => {
99
+ return await (await this.getHotMeshClient('durable.wfs.signal')).hook('durable.wfs.signal', { id: signalId, data });
100
+ }
82
101
  };
83
102
  this.connection = config.connection;
84
103
  }
85
- async activateWorkflow(hotMesh, workflowTopic) {
86
- const version = '1';
87
- const app = await hotMesh.engine.store.getApp(workflowTopic);
104
+ async activateWorkflow(hotMesh, appId = factory_1.APP_ID, version = factory_1.APP_VERSION) {
105
+ const app = await hotMesh.engine.store.getApp(appId);
88
106
  const appVersion = app?.version;
89
107
  if (isNaN(appVersion)) {
90
108
  try {
91
- await hotMesh.deploy((0, factory_1.getWorkflowYAML)(workflowTopic, version));
109
+ await hotMesh.deploy((0, factory_1.getWorkflowYAML)(appId, version));
92
110
  await hotMesh.activate(version);
93
111
  }
94
- catch (err) {
95
- hotMesh.engine.logger.error('durable-client-deploy-activate-err', err);
96
- throw err;
112
+ catch (error) {
113
+ hotMesh.engine.logger.error('durable-client-deploy-activate-err', { error });
114
+ throw error;
97
115
  }
98
116
  }
99
117
  else if (app && !app.active) {
100
118
  try {
101
119
  await hotMesh.activate(version);
102
120
  }
103
- catch (err) {
104
- hotMesh.engine.logger.error('durable-client-activate-err', err);
105
- throw err;
121
+ catch (error) {
122
+ hotMesh.engine.logger.error('durable-client-activate-err', { error });
123
+ throw error;
106
124
  }
107
125
  }
108
126
  }
@@ -1,20 +1,24 @@
1
1
  /**
2
- * 1) `maxSystemRetries` | can be 0 to 3 and represents milliseconds;
3
- * if there is an error, the workflow will retry up to `maxSystemRetries` times
4
- * delaying by 10, 100, and 1000ms; this is a system level retry
5
- * and is not configurable. It exists to handle intermittent network
6
- * errors. (NOTE: each retry spawns a new transition stream)
7
- *
8
- * 2) `backoffExponent` | can be any number and represents `seconds` when applied;
9
- * retries will happen indefinitely and adhere to the
10
- * exponential backoff algorithm by multiplying by `backoffExponent`.
11
- * For example, if `backoffExponent` is 10, the workflow will retry
12
- * in 10s, 100s, 1000s, 10000s, etc.
13
- *
14
- * EXAMPLE | Using `maxSystemRetries = 3` and `backoffExponent = 10`, errant
15
- * workflows will be retried on the following schedule (8 times in 27 hours):
16
- * => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
2
+ * NOTE: Using `maxSystemRetries = 3` and `backoffCoefficient = 10`, errant
3
+ * workflows will be retried on the following schedule (8 times in 27 hours):
4
+ * => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
17
5
  */
18
- declare const getWorkflowYAML: (topic: string, version?: string, maxSystemRetries?: number, backoffExponent?: number) => string;
19
- declare const getActivityYAML: (topic: string, version?: string) => string;
20
- export { getActivityYAML, getWorkflowYAML };
6
+ declare const getWorkflowYAML: (app: string, version: string) => string;
7
+ declare const APP_VERSION = "1";
8
+ declare const APP_ID = "durable";
9
+ declare const ACTIVITY_SUBSCRIBES_TOPIC = "durable.activity.execute";
10
+ declare const ACTIVITY_PUBLISHES_TOPIC = "durable.activity.executed";
11
+ declare const SLEEP_SUBSCRIBES_TOPIC = "durable.sleep.execute";
12
+ declare const SLEEP_PUBLISHES_TOPIC = "durable.sleep.executed";
13
+ declare const WFS_SUBSCRIBES_TOPIC = "durable.wfs.execute";
14
+ declare const WFS_PUBLISHES_TOPIC = "durable.wfs.executed";
15
+ declare const WFSC_SUBSCRIBES_TOPIC = "durable.wfsc.execute";
16
+ declare const WFSC_PUBLISHES_TOPIC = "durable.wfsc.executed";
17
+ declare const SUBSCRIBES_TOPIC = "durable.execute";
18
+ declare const PUBLISHES_TOPIC = "durable.executed";
19
+ declare const HOOK_ID = "durable.awaken";
20
+ declare const ACTIVITY_HOOK_ID = "durable.activity.awaken";
21
+ declare const SLEEP_HOOK_ID = "durable.sleep.awaken";
22
+ declare const WFS_HOOK_ID = "durable.wfs.awaken";
23
+ declare const DEFAULT_COEFFICIENT = 10;
24
+ export { getWorkflowYAML, APP_VERSION, APP_ID, ACTIVITY_SUBSCRIBES_TOPIC, ACTIVITY_PUBLISHES_TOPIC, SLEEP_SUBSCRIBES_TOPIC, SLEEP_PUBLISHES_TOPIC, SUBSCRIBES_TOPIC, PUBLISHES_TOPIC, HOOK_ID, ACTIVITY_HOOK_ID, SLEEP_HOOK_ID, DEFAULT_COEFFICIENT, WFS_SUBSCRIBES_TOPIC, WFS_PUBLISHES_TOPIC, WFSC_SUBSCRIBES_TOPIC, WFSC_PUBLISHES_TOPIC, WFS_HOOK_ID, };