@hotmeshio/hotmesh 0.1.15 → 0.1.16

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 (134) hide show
  1. package/README.md +623 -209
  2. package/build/index.d.ts +14 -3
  3. package/build/index.js +17 -4
  4. package/build/modules/enums.d.ts +12 -12
  5. package/build/modules/enums.js +15 -25
  6. package/build/modules/errors.d.ts +16 -16
  7. package/build/modules/errors.js +28 -28
  8. package/build/modules/key.d.ts +0 -37
  9. package/build/modules/key.js +4 -45
  10. package/build/modules/utils.d.ts +7 -15
  11. package/build/modules/utils.js +21 -44
  12. package/build/package.json +18 -15
  13. package/build/services/activities/activity.d.ts +0 -31
  14. package/build/services/activities/activity.js +1 -50
  15. package/build/services/activities/await.js +0 -4
  16. package/build/services/activities/cycle.d.ts +0 -7
  17. package/build/services/activities/cycle.js +1 -16
  18. package/build/services/activities/hook.d.ts +0 -6
  19. package/build/services/activities/hook.js +2 -12
  20. package/build/services/activities/interrupt.js +0 -8
  21. package/build/services/activities/signal.d.ts +0 -6
  22. package/build/services/activities/signal.js +0 -15
  23. package/build/services/activities/trigger.d.ts +4 -5
  24. package/build/services/activities/trigger.js +22 -16
  25. package/build/services/activities/worker.js +0 -4
  26. package/build/services/collator/index.d.ts +0 -70
  27. package/build/services/collator/index.js +1 -91
  28. package/build/services/compiler/deployer.js +6 -38
  29. package/build/services/compiler/index.d.ts +0 -15
  30. package/build/services/compiler/index.js +0 -20
  31. package/build/services/compiler/validator.d.ts +0 -3
  32. package/build/services/compiler/validator.js +0 -25
  33. package/build/services/connector/clients/ioredis.js +0 -2
  34. package/build/services/connector/clients/redis.js +0 -2
  35. package/build/services/connector/index.js +0 -2
  36. package/build/services/engine/index.d.ts +1 -10
  37. package/build/services/engine/index.js +1 -48
  38. package/build/services/exporter/index.d.ts +0 -27
  39. package/build/services/exporter/index.js +0 -33
  40. package/build/services/hotmesh/index.d.ts +8 -4
  41. package/build/services/hotmesh/index.js +20 -19
  42. package/build/services/logger/index.js +0 -2
  43. package/build/services/mapper/index.d.ts +0 -14
  44. package/build/services/mapper/index.js +0 -14
  45. package/build/services/meshcall/index.d.ts +21 -0
  46. package/build/services/meshcall/index.js +202 -0
  47. package/build/services/meshcall/schemas/factory.d.ts +2 -0
  48. package/build/services/meshcall/schemas/factory.js +179 -0
  49. package/build/services/meshdata/index.d.ts +75 -0
  50. package/build/services/meshdata/index.js +541 -0
  51. package/build/services/meshflow/client.d.ts +18 -0
  52. package/build/services/{durable → meshflow}/client.js +9 -40
  53. package/build/services/{durable → meshflow}/connection.d.ts +2 -1
  54. package/build/services/{durable → meshflow}/connection.js +1 -0
  55. package/build/services/meshflow/exporter.d.ts +29 -0
  56. package/build/services/{durable → meshflow}/exporter.js +0 -29
  57. package/build/services/meshflow/handle.d.ts +22 -0
  58. package/build/services/{durable → meshflow}/handle.js +0 -46
  59. package/build/services/meshflow/index.d.ts +17 -0
  60. package/build/services/meshflow/index.js +23 -0
  61. package/build/services/meshflow/schemas/factory.d.ts +4 -0
  62. package/build/services/{durable → meshflow}/schemas/factory.js +2 -30
  63. package/build/services/meshflow/search.d.ts +23 -0
  64. package/build/services/{durable → meshflow}/search.js +0 -99
  65. package/build/services/{durable → meshflow}/worker.d.ts +3 -2
  66. package/build/services/{durable → meshflow}/worker.js +23 -39
  67. package/build/services/meshflow/workflow.d.ts +27 -0
  68. package/build/services/{durable → meshflow}/workflow.js +27 -169
  69. package/build/services/pipe/functions/date.d.ts +0 -7
  70. package/build/services/pipe/functions/date.js +0 -7
  71. package/build/services/pipe/functions/math.js +0 -2
  72. package/build/services/pipe/index.d.ts +0 -15
  73. package/build/services/pipe/index.js +2 -23
  74. package/build/services/quorum/index.d.ts +1 -7
  75. package/build/services/quorum/index.js +0 -21
  76. package/build/services/reporter/index.d.ts +0 -5
  77. package/build/services/reporter/index.js +0 -9
  78. package/build/services/router/index.d.ts +0 -9
  79. package/build/services/router/index.js +2 -30
  80. package/build/services/serializer/index.js +6 -23
  81. package/build/services/store/cache.d.ts +0 -19
  82. package/build/services/store/cache.js +0 -19
  83. package/build/services/store/clients/ioredis.d.ts +0 -6
  84. package/build/services/store/clients/ioredis.js +0 -7
  85. package/build/services/store/clients/redis.d.ts +0 -6
  86. package/build/services/store/clients/redis.js +0 -6
  87. package/build/services/store/index.d.ts +0 -55
  88. package/build/services/store/index.js +14 -87
  89. package/build/services/stream/clients/ioredis.js +1 -4
  90. package/build/services/task/index.d.ts +0 -9
  91. package/build/services/task/index.js +0 -31
  92. package/build/services/telemetry/index.d.ts +0 -7
  93. package/build/services/telemetry/index.js +1 -13
  94. package/build/services/worker/index.d.ts +1 -4
  95. package/build/services/worker/index.js +0 -6
  96. package/build/types/activity.d.ts +0 -81
  97. package/build/types/error.d.ts +5 -5
  98. package/build/types/exporter.d.ts +1 -14
  99. package/build/types/hotmesh.d.ts +4 -12
  100. package/build/types/hotmesh.js +0 -3
  101. package/build/types/index.d.ts +5 -3
  102. package/build/types/index.js +1 -1
  103. package/build/types/job.d.ts +1 -95
  104. package/build/types/meshcall.d.ts +54 -0
  105. package/build/types/meshdata.d.ts +59 -0
  106. package/build/types/meshdata.js +2 -0
  107. package/build/types/meshflow.d.ts +202 -0
  108. package/build/types/meshflow.js +2 -0
  109. package/build/types/pipe.d.ts +0 -65
  110. package/build/types/quorum.d.ts +0 -12
  111. package/build/types/redis.d.ts +0 -6
  112. package/build/types/stream.d.ts +0 -59
  113. package/build/types/stream.js +0 -4
  114. package/index.ts +22 -3
  115. package/package.json +18 -15
  116. package/typedoc.json +38 -0
  117. package/types/error.ts +5 -5
  118. package/types/exporter.ts +1 -1
  119. package/types/hotmesh.ts +3 -2
  120. package/types/index.ts +25 -7
  121. package/types/job.ts +19 -1
  122. package/types/meshcall.ts +123 -0
  123. package/types/meshdata.ts +273 -0
  124. package/types/{durable.ts → meshflow.ts} +33 -9
  125. package/build/services/durable/client.d.ts +0 -49
  126. package/build/services/durable/exporter.d.ts +0 -51
  127. package/build/services/durable/handle.d.ts +0 -58
  128. package/build/services/durable/index.d.ts +0 -19
  129. package/build/services/durable/index.js +0 -25
  130. package/build/services/durable/schemas/factory.d.ts +0 -33
  131. package/build/services/durable/search.d.ts +0 -120
  132. package/build/services/durable/workflow.d.ts +0 -143
  133. package/build/types/durable.d.ts +0 -467
  134. /package/build/types/{durable.js → meshcall.js} +0 -0
package/README.md CHANGED
@@ -1,37 +1,187 @@
1
1
  # HotMesh
2
2
  ![beta release](https://img.shields.io/badge/release-beta-blue.svg)
3
3
 
4
- HotMesh transforms Redis into an Orchestration Engine.
5
-
6
- *Write functions in your own style, and let Redis govern their execution, reliably and durably.*
4
+ **HotMesh** transforms **Redis** into indispensable **Middleware**. Connect **anything** to **everything**.
7
5
 
8
6
  ## Install
9
- [![npm version](https://badge.fury.io/js/%40hotmeshio%2Fhotmesh.svg)](https://badge.fury.io/js/%40hotmeshio%2Fhotmesh)
10
-
11
7
  ```sh
12
8
  npm install @hotmeshio/hotmesh
13
9
  ```
10
+ You have a Redis instance? Good. You're ready to go.
11
+
12
+ ## SDK Docs
13
+ [Read the Docs](https://hotmeshio.github.io/sdk-typescript/)
14
+
15
+ ## MeshCall
16
+ The **MeshCall** module connects and exposes your functions as idempotent endpoints.
17
+
18
+ <details style="padding: .5em">
19
+ <summary style="font-size:1.25em;">Run an idempotent cron job</summary>
20
+
21
+ ### Run a Cron
22
+ This example demonstrates an *idempotent* cron that runs every day. The `id` makes each cron job unique and ensures that only one instance runs, despite repeated invocations. *The `cron` method fails silently if a workflow is already running with the same `id`.*
23
+
24
+ Optionally set `maxCycles` or `maxDuration` to limit the number of cycles.
25
+
26
+ 1. Define the cron function.
27
+ ```typescript
28
+ //cron.ts
29
+ import { MeshCall } from '@hotmeshio/hotmesh';
30
+ import * as Redis from 'redis';
31
+
32
+ export const runMyCron = (id: string, interval = '1 day') => {
33
+ MeshCall.cron({
34
+ topic: 'my.cron.function',
35
+ redis: {
36
+ class: Redis,
37
+ options: { url: 'redis://:key_admin@redis:6379' }
38
+ },
39
+ callback: async () => {
40
+ //your code here...
41
+ },
42
+ options: { id, interval }
43
+ });
44
+ };
45
+ ```
46
+
47
+ 2. Call `runMyCron` at server startup (or call as needed to run multiple crons).
48
+ ```typescript
49
+ //server.ts
50
+ import { runMyCron } from './cron';
51
+ runMyCron('myDailyCron123');
52
+ ```
53
+ </details>
54
+
55
+ <details style="padding: .5em">
56
+ <summary style="font-size:1.25em;">Interrupt a cron job</summary>
57
+
58
+ ### Interrupt a Cron
59
+ This example demonstrates how to cancel a running cron job.
60
+
61
+ 1. Use the same `id` and `topic` that were used to create the cron to cancel it.
62
+ ```typescript
63
+ import { MeshCall } from '@hotmeshio/hotmesh';
64
+ import * as Redis from 'redis';
65
+
66
+ MeshCall.interrupt({
67
+ topic: 'my.cron.function',
68
+ redis: {
69
+ class: Redis,
70
+ options: { url: 'redis://:key_admin@redis:6379' }
71
+ },
72
+ options: { id: 'myDailyCron123' }
73
+ });
74
+ ```
75
+ </details>
76
+
77
+ <details style="padding: .5em">
78
+ <summary style="font-size:1.25em;">Call any function in any service</summary>
79
+
80
+ ### Call a Function
81
+ Make blazing fast interservice calls that return in milliseconds without the overhead of HTTP.
82
+
83
+ 1. Call `MeshCall.connect` and provide a `topic` to uniquely identify the function.
84
+
85
+ ```typescript
86
+ //myFunctionWrapper.ts
87
+ import { MeshCall, Types } from '@hotmeshio/hotmesh';
88
+ import * as Redis from 'redis';
14
89
 
15
- ## Understanding HotMesh
16
- HotMesh inverts the relationship to Redis: those functions that once used Redis as a cache, are instead *cached and governed* by Redis. Consider the following. It's a typical microservices network, with a tangled mess of services and functions. There's important business logic in there (functions *A*, *B* and *C* are critical), but it's hard to find and access.
90
+ export const connectMyFunction = () => {
91
+ MeshCall.connect({
92
+ topic: 'my.demo.function',
93
+ redis: {
94
+ class: Redis,
95
+ options: { url: 'redis://:key_admin@redis:6379' }
96
+ },
97
+ callback: async (input: string) => {
98
+ //your code goes here; response must be JSON serializable
99
+ return { hello: input }
100
+ },
101
+ });
102
+ };
103
+ ```
104
+
105
+ 2. Call `connectMyFunction` at server startup to connect your function to the mesh.
106
+
107
+ ```typescript
108
+ //server.ts
109
+ import { connectMyFunction } from './myFunctionWrapper';
110
+ connectMyFunction();
111
+ ```
112
+
113
+ 3. Call your function from anywhere on the network (or even from the same service). Send any payload as long as it's JSON serializable.
114
+
115
+ ```typescript
116
+ import { MeshCall } from '@hotmeshio/hotmesh';
117
+ import * as Redis from 'redis';
118
+
119
+ const result = await MeshCall.exec({
120
+ topic: 'my.demo.function',
121
+ args: ['something'],
122
+ redis: {
123
+ class: Redis,
124
+ options: { url: 'redis://:key_admin@redis:6379' }
125
+ },
126
+ }); //returns `{ hello: 'something'}`
127
+ ```
128
+ </details>
17
129
 
18
- <img src="./docs/img/operational_data_layer.png" alt="A Tangled Microservices Network with 3 valuable functions buried within" style="max-width:100%;width:600px;">
130
+ <details style="padding: .5em">
131
+ <summary style="font-size:1.25em;">Call and <b>cache</b> a function</summary>
132
+
133
+ ### Cache a Function
134
+ Redis is great for unburdening stressed services. This solution builds upon the previous example, caching the response. The linked function will only be re/called when the cached result expires. Everything remains the same, except the caller which specifies an `id` and `ttl`.
135
+
136
+ 1. Make the call from another service (or even the same service). Include an `id` and `ttl` to cache the result for the specified duration.
137
+
138
+ ```typescript
139
+ import { MeshCall } from '@hotmeshio/hotmesh';
140
+ import * as Redis from 'redis';
141
+
142
+ const result = await MeshCall.exec({
143
+ topic: 'my.demo.function',
144
+ args: ['anything'],
145
+ redis: {
146
+ class: Redis,
147
+ options: { url: 'redis://:key_admin@redis:6379' }
148
+ },
149
+ options: { id: 'myid123', ttl: '15 minutes' },
150
+ }); //returns `{ hello: 'anything'}`
151
+ ```
152
+
153
+ 2. Flush the cache at any time, using the same `topic` and cache `id`.
154
+
155
+ ```typescript
156
+ import { MeshCall } from '@hotmeshio/hotmesh';
157
+ import * as Redis from 'redis';
158
+
159
+ await MeshCall.flush({
160
+ topic: 'my.demo.function',
161
+ redis: {
162
+ class: Redis,
163
+ options: { url: 'redis://:key_admin@redis:6379' }
164
+ },
165
+ options: { id: 'myid123' },
166
+ });
167
+ ```
168
+ </details>
19
169
 
20
- HotMesh creates an *ad hoc*, Redis-backed network of functions and organizes them into a unified service mesh. *Any service with access to Redis can join in the network, bypassing the legacy clutter.*
170
+ ## MeshFlow
171
+ The **MeshFlow** module is a drop-in replacement for [Temporal.io](https://temporal.io). If you need to orchestrate your functions as durable workflows, MeshFlow combines the popular Temporal SDK with Redis' *in-memory execution speed*.
21
172
 
22
- ## Design
23
- HotMesh uses your existing Redis installation. If you already have an instance available, you have all the infrastructure you'll ever need.
173
+ <details style="padding: .5em">
174
+ <summary style="font-size:1.25em;">Orchestrate unpredictable activities</summary>
24
175
 
25
- ### Design | Pluck
26
- The simplest way to get started is to use the [HotMesh Pluck](https://github.com/hotmeshio/pluck-typescript) package. It's designed for usability, and is backed by the HotMesh system if you need advanced features. It includes a detailed [SDK](https://hotmeshio.github.io/pluck-typescript/) and a wide variety of examples.
176
+ ### Proxy Activities
177
+ When an endpoint is unpredictable, use `proxyActivities`. HotMesh will retry as necessary until the call succeeds. This example demonstrates a workflow that greets a user in both English and Spanish. Even though both activities throw random errors, the workflow always returns a successful result.
27
178
 
28
- ### Design | Durable
29
- HotMesh's *Durable* module is a TypeScript Library modeled after Temporal.io. If you're familiar with their SDK, the principles are the same.
179
+ 1. Start by defining **activities**. Note how each throws an error 50% of the time.
30
180
 
31
- 1. Start by defining **activities**. Activities can be written in any style, using any framework, and can even be legacy functions you've already written. The only requirement is that they return a Promise. *Note how the `saludar` example throws an error 50% of the time. It doesn't matter how unpredictable your functions are, HotMesh will retry as necessary until they succeed.*
32
- ```javascript
181
+ ```typescript
33
182
  //activities.ts
34
183
  export async function greet(name: string): Promise<string> {
184
+ if (Math.random() > 0.5) throw new Error('Random error');
35
185
  return `Hello, ${name}!`;
36
186
  }
37
187
 
@@ -40,62 +190,67 @@ HotMesh's *Durable* module is a TypeScript Library modeled after Temporal.io. If
40
190
  return `¡Hola, ${nombre}!`;
41
191
  }
42
192
  ```
43
- 2. Define your **workflow** logic. Include conditional branching, loops, etc to control activity execution. It's vanilla code written in your own coding style. The only requirement is to use `proxyActivities`, ensuring your activities are executed with HotMesh's durability guarantee.
44
- ```javascript
193
+
194
+ 2. Define the **workflow** logic. Include conditional branching, loops, etc to control activity execution. It's vanilla JavaScript written in your own coding style. The only requirement is to use `proxyActivities`, ensuring your activities are executed with HotMesh's durability wrapper.
195
+
196
+ ```typescript
45
197
  //workflows.ts
46
- import { Durable } from '@hotmeshio/hotmesh';
198
+ import { MeshFlow } from '@hotmeshio/hotmesh';
47
199
  import * as activities from './activities';
48
200
 
49
- const { greet, saludar } = Durable.workflow
201
+ const { greet, saludar } = MeshFlow.workflow
50
202
  .proxyActivities<typeof activities>({
51
203
  activities
52
204
  });
53
205
 
54
- export async function example(name: string, lang: string): Promise<string> {
55
- if (lang === 'es') {
56
- return await saludar(name);
57
- } else {
58
- return await greet(name);
59
- }
206
+ export async function example(name: string): Promise<[string, string]> {
207
+ return Promise.all([
208
+ greet(name),
209
+ saludar(name)
210
+ ]);
60
211
  }
61
212
  ```
213
+
62
214
  3. Instance a HotMesh **client** to invoke the workflow.
63
- ```javascript
215
+
216
+ ```typescript
64
217
  //client.ts
65
- import { Durable, HotMesh } from '@hotmeshio/hotmesh';
66
- import Redis from 'ioredis'; //OR `import * as Redis from 'redis';`
218
+ import { MeshFlow, HotMesh } from '@hotmeshio/hotmesh';
219
+ import Redis from 'ioredis';
67
220
 
68
221
  async function run(): Promise<string> {
69
- const client = new Durable.Client({
222
+ const client = new MeshFlow.Client({
70
223
  connection: {
71
224
  class: Redis,
72
- options: { host: 'localhost', port: 6379 }
225
+ options: { host: 'redis', port: 6379 }
73
226
  }
74
227
  });
75
228
 
76
- const handle = await client.workflow.start({
77
- args: ['HotMesh', 'es'],
229
+ const handle = await client.workflow.start<[string,string]>({
230
+ args: ['HotMesh'],
78
231
  taskQueue: 'default',
79
232
  workflowName: 'example',
80
233
  workflowId: HotMesh.guid()
81
234
  });
82
235
 
83
236
  return await handle.result();
84
- //returns '¡Hola, HotMesh!'
237
+ //returns ['Hello HotMesh', '¡Hola, HotMesh!']
85
238
  }
86
239
  ```
87
- 4. Finally, create a **worker** and link your workflow function. Workers listen for tasks on their assigned Redis stream and invoke your workflow function each time they receive an event.
88
- ```javascript
240
+
241
+ 4. Finally, create a **worker** and link the workflow function. Workers listen for tasks on their assigned Redis stream and invoke the workflow function each time they receive an event.
242
+
243
+ ```typescript
89
244
  //worker.ts
90
- import { Durable } from '@hotmeshio/hotmesh';
245
+ import { MeshFlow } from '@hotmeshio/hotmesh';
91
246
  import Redis from 'ioredis';
92
247
  import * as workflows from './workflows';
93
248
 
94
249
  async function run() {
95
- const worker = await Durable.Worker.create({
250
+ const worker = await MeshFlow.Worker.create({
96
251
  connection: {
97
252
  class: Redis,
98
- options: { host: 'localhost', port: 6379 },
253
+ options: { host: 'redis', port: 6379 },
99
254
  },
100
255
  taskQueue: 'default',
101
256
  workflow: workflows.example,
@@ -104,222 +259,481 @@ HotMesh's *Durable* module is a TypeScript Library modeled after Temporal.io. If
104
259
  await worker.run();
105
260
  }
106
261
  ```
262
+ </details>
263
+
264
+ <details style="padding: .5em">
265
+ <summary style="font-size:1.25em;">Pause and wait for a signal</summary>
107
266
 
108
- #### Workflow Extensions
109
- Externalizing state fundamentally changes the execution profile for your functions, allowing you to design long-running, durable workflows. The `Durable` base class (shown in the examples above) provides additional methods for solving the most common state management challenges.
267
+ ### Wait for Signal
268
+ Pause a function and only awaken when a matching signal is received from the outide.
110
269
 
111
- - `waitFor` Pause your function using your chosen signal key, and only awaken when the signal is received from the outide. Use a standard `Promise` to collate and cache the signals and only awaken your function once all signals have arrived.
112
- ```javascript
113
- const { waitFor } = Durable.workflow;
114
- const [a, b] = await Promise.all([
115
- waitFor<{payload: string}>('sig1'),
116
- waitFor<number>('sig2')
117
- ]);
270
+ 1. Define the **workflow** logic. This one waits for the `my-sig-nal` signal, returning the signal payload (`{ hello: 'world' }`) when it eventually arrives. Interleave additional logic to meet your use case.
271
+
272
+ ```typescript
273
+ //waitForWorkflow.ts
274
+ import { MeshFlow } from '@hotmeshio/hotmesh';
275
+
276
+ export async function waitForExample(): Promise<{hello: string}> {
277
+ return await MeshFlow.workflow.waitFor<{hello: string}>('my-sig-nal');
278
+ //continue processing, use the payload, etc...
279
+ }
280
+ ```
281
+
282
+ 2. Instance a HotMesh **client** and start a workflow. Use a custom workflow ID (`myWorkflow123`).
283
+
284
+ ```typescript
285
+ //client.ts
286
+ import { MeshFlow, HotMesh } from '@hotmeshio/hotmesh';
287
+ import Redis from 'ioredis';
288
+
289
+ async function run(): Promise<string> {
290
+ const client = new MeshFlow.Client({
291
+ connection: {
292
+ class: Redis,
293
+ options: { host: 'redis', port: 6379 }
294
+ }
295
+ });
296
+
297
+ //start a workflow; it will immediately pause
298
+ await client.workflow.start({
299
+ args: ['HotMesh'],
300
+ taskQueue: 'default',
301
+ workflowName: 'waitForExample',
302
+ workflowId: 'myWorkflow123',
303
+ await: false,
304
+ });
305
+ }
118
306
  ```
119
- - `signal` Send a signal (and payload) to a paused function awaiting the signal. Signals may also be sent from the outside to awaken a paused function.
120
- ```javascript
121
- await Durable.workflow.signal('sig1', {payload: 'hi!'});
307
+
308
+ 3. Create a **worker** and link the `waitForExample` workflow function.
309
+
310
+ ```typescript
311
+ //worker.ts
312
+ import { MeshFlow } from '@hotmeshio/hotmesh';
313
+ import Redis from 'ioredis';
314
+ import * as workflows from './waitForWorkflow';
315
+
316
+ async function run() {
317
+ const worker = await MeshFlow.Worker.create({
318
+ connection: {
319
+ class: Redis,
320
+ options: { host: 'redis', port: 6379 },
321
+ },
322
+ taskQueue: 'default',
323
+ workflow: workflows.waitForExample,
324
+ });
325
+
326
+ await worker.run();
327
+ }
122
328
  ```
123
- - `hook` Redis governance converts your functions into 're-entrant processes'. Optionally use the *hook* method to spawn parallel execution threads to augment a running workflow.
124
- ```javascript
125
- await Durable.workflow.hook({
126
- workflowName: 'newsletter',
329
+
330
+ 4. Send a signal to awaken the paused function; await the function result.
331
+
332
+ ```typescript
333
+ import { MeshFlow } from '@hotmeshio/hotmesh';
334
+ import * as Redis from Redis;
335
+
336
+ const client = new MeshFlow.Client({
337
+ connection: {
338
+ class: Redis,
339
+ options: { host: 'redis', port: 6379 }
340
+ }
341
+ });
342
+
343
+ //awaken the function by sending a signal
344
+ await client.signal('my-sig-nal', { hello: 'world' });
345
+
346
+ //get the workflow handle and await the result
347
+ const handle = await client.getHandle({
127
348
  taskQueue: 'default',
128
- args: []
349
+ workflowId: 'myWorkflow123'
129
350
  });
351
+
352
+ const result = await handle.result();
353
+ //returns { hello: 'world' }
130
354
  ```
131
- - `sleepFor` Pause function execution for a ridiculous amount of time (months, years, etc). There's no risk of information loss, as Redis governs function state. When your function awakens, function state is efficiently (and automatically) restored and your function will resume right where it left off.
132
- ```javascript
133
- await Durable.workflow.sleepFor('1 month');
134
- ```
135
- - `random` Generate a deterministic random number that can be used in a reentrant process workflow (replaces `Math.random()`).
136
- ```javascript
137
- const random = await Durable.workflow.random();
355
+ </details>
356
+
357
+ <details style="padding: .5em">
358
+ <summary style="font-size:1.25em;">Wait for multiple signals (collation)</summary>
359
+
360
+ ### Collate Multiple Signals
361
+ Use a standard `Promise` to collate and cache multiple signals. HotMesh will only awaken once **all** signals have arrived. HotMesh will track up to 25 concurrent signals.
362
+
363
+ 1. Update the **workflow** logic to await two signals using a promise: `my-sig-nal-1` and `my-sig-nal-2`. Add additional logic to meet your use case.
364
+
365
+ ```typescript
366
+ //waitForWorkflows.ts
367
+ import { MeshFlow } from '@hotmeshio/hotmesh';
368
+
369
+ export async function waitForExample(): Promise<[boolean, number]> {
370
+ const [s1, s2] = await Promise.all([
371
+ Meshflow.workflow.waitFor<boolean>('my-sig-nal-1'),
372
+ Meshflow.workflow.waitFor<number>('my-sig-nal-2')
373
+ ]);
374
+ //do something with the signal payloads (s1, s2)
375
+ return [s1, s2];
376
+ }
138
377
  ```
139
- - `execChild` Call another durable function and await the response. *Design sophisticated, multi-process solutions by leveraging this command.*
140
- ```javascript
141
- const jobResponse = await Durable.workflow.execChild({
142
- workflowName: 'newsletter',
143
- taskQueue: 'default',
144
- args: [{ id, user_id, etc }],
378
+
379
+ 2. Send **two** signals to awaken the paused function.
380
+
381
+ ```typescript
382
+ import { MeshFlow } from '@hotmeshio/hotmesh';
383
+ import * as Redis from Redis;
384
+
385
+ const client = new MeshFlow.Client({
386
+ connection: {
387
+ class: Redis,
388
+ options: { host: 'redis', port: 6379 }
389
+ }
145
390
  });
146
- ```
147
- - `startChild` Call another durable function, but do not await the response.
148
- ```javascript
149
- const jobId = await Durable.workflow.startChild({
150
- workflowName: 'newsletter',
391
+
392
+ //send 2 signals to awaken the function; order is unimportant
393
+ await client.signal('my-sig-nal-2', 12345);
394
+ await client.signal('my-sig-nal-1', true);
395
+
396
+ //get the workflow handle and await the collated result
397
+ const handle = await client.getHandle({
151
398
  taskQueue: 'default',
152
- args: [{ id, user_id, etc }],
399
+ workflowId: 'myWorkflow123'
153
400
  });
401
+
402
+ const result = await handle.result();
403
+ //returns [true, 12345]
154
404
  ```
155
- - `getContext` Get the current workflow context (workflowId, replay history, replay index, etc).
156
- ```javascript
157
- const context = await Durable.workflow.getContext();
158
- ```
159
- - `search` Instance a search session
160
- ```javascript
161
- const search = await Durable.workflow.search();
162
- ```
163
- - `set` Set one or more name/value pairs
164
- ```javascript
165
- await search.set('name1', 'value1', 'name2', 'value2');
166
- ```
167
- - `get` Get a single value by name
168
- ```javascript
169
- const value = await search.get('name');
170
- ```
171
- - `mget` Get multiple values by name
172
- ```javascript
173
- const [val1, val2] = await search.mget('name1', 'name2');
174
- ```
175
- - `del` Delete one or more entries by name and return the number deleted
176
- ```javascript
177
- const count = await search.del('name1', 'name2');
178
- ```
179
- - `incr` Increment (or decrement) a number
180
- ```javascript
181
- const value = await search.incr('name', 12);
182
- ```
183
- - `mult` Multiply a number
184
- ```javascript
185
- const value = await search.mult('name', 12);
186
- ```
405
+ </details>
187
406
 
188
- Refer to the [hotmeshio/samples-javascript](https://github.com/hotmeshio/samples-javascript) repo for usage examples.
407
+ <details style="padding: .5em">
408
+ <summary style="font-size:1.25em;">Create a recurring, cyclical workflow</summary>
189
409
 
190
- ### Design | Advanced
191
- The *Pluck* and *Durable* modules are the easiest way to use HotMesh. But if you need full control over your function lifecycles (including high-volume, high-speed use cases), you can use HotMesh's underlying YAML models to optimize your durable workflows. The following model depicts a sequence of activities orchestrated by HotMesh. Any function you associate with a `topic` in your YAML definition is guaranteed to be durable.
192
-
193
- ```yaml
194
- app:
195
- id: sandbox
196
- version: '1'
197
- graphs:
198
- - subscribes: sandbox.work.do
199
- publishes: sandbox.work.done
200
-
201
- activities:
202
- gateway:
203
- type: trigger
204
- servicec:
205
- type: worker
206
- topic: sandbox.work.do.servicec
207
- serviced:
208
- type: worker
209
- topic: sandbox.work.do.serviced
210
- sforcecloud:
211
- type: worker
212
- topic: sandbox.work.do.sforcecloud
213
-
214
- transitions:
215
- gateway:
216
- - to: servicec
217
- servicec:
218
- - to: serviced
219
- serviced:
220
- - to: sforcecloud
221
- ```
410
+ ### Cyclical Workflow
411
+ This example calls an activity and then sleeps for a week. It runs indefinitely until it's manually stopped. It takes advantage of durable execution and can safely sleep for months or years.
412
+
413
+ >Container restarts have no impact on actively executing workflows as all state is retained in Redis.
222
414
 
223
- ### Initialize
224
- Provide your chosen Redis instance and configuration options to start a HotMesh Client. *HotMesh supports both `ioredis` and `redis` clients interchangeably.*
415
+ 1. Define the **workflow** logic. This one calls a legacy `statusDiagnostic` function once a week.
225
416
 
226
- ```javascript
227
- import { HotMesh } from '@hotmeshio/hotmesh';
228
- import Redis from 'ioredis'; //OR `import * as Redis from 'redis';`
417
+ ```typescript
418
+ //recurringWorkflow.ts
419
+ import { MeshFlow } from '@hotmeshio/hotmesh';
420
+ import * as activities from './activities';
421
+
422
+ const { statusDiagnostic } = MeshFlow.workflow
423
+ .proxyActivities<typeof activities>({
424
+ activities
425
+ });
229
426
 
230
- const hotMesh = await HotMesh.init({
231
- appId: 'sandbox',
232
- engine: {
233
- redis: {
234
- class: Redis,
235
- options: { host, port, password, db } //per your chosen Redis client
427
+ export async function recurringExample(someValue: number): Promise<void> {
428
+ do {
429
+ await statusDiagnostic(someValue);
430
+ } while (await MeshFlow.workflow.sleepFor('1 week'));
236
431
  }
237
- }
238
- });
239
- ```
432
+ ```
240
433
 
241
- A HotMesh Client can be used to trigger worfkows and subscribe to results.
434
+ 2. Instance a HotMesh **client** and start a workflow. Assign a custom workflow ID (e.g., `myRecurring123`) if the workflow should be idempotent.
242
435
 
243
- ### Trigger a Workflow
244
- Call `pub` to initiate a workflow. This function returns a job ID that allows you to monitor the progress of the workflow.
436
+ ```typescript
437
+ //client.ts
438
+ import { MeshFlow, HotMesh } from '@hotmeshio/hotmesh';
439
+ import Redis from 'ioredis';
245
440
 
246
- ```javascript
247
- const topic = 'sandbox.work.do';
248
- const payload = { };
249
- const jobId = await hotMesh.pub(topic, payload);
250
- ```
441
+ async function run(): Promise<string> {
442
+ const client = new MeshFlow.Client({
443
+ connection: {
444
+ class: Redis,
445
+ options: { host: 'redis', port: 6379 }
446
+ }
447
+ });
251
448
 
252
- ### Subscribe to Events
253
- Call `psub` (patterned subscription) to subscribe to all workflow results for a given topic.
449
+ //start a workflow; it will immediately pause
450
+ await client.workflow.start({
451
+ args: [55],
452
+ taskQueue: 'default',
453
+ workflowName: 'recurringExample',
454
+ workflowId: 'myRecurring123',
455
+ await: false,
456
+ });
457
+ }
458
+ ```
254
459
 
255
- ```javascript
256
- await hotMesh.psub('sandbox.work.done.*', (topic, jobOutput) => {
257
- // use jobOutput.data
258
- });
259
- ```
460
+ 3. Create a **worker** and link the `recurringExample` workflow function.
260
461
 
261
- ### Trigger and Wait
262
- Call `pubsub` to start a workflow and *wait for the response*. HotMesh establishes a one-time subscription and delivers the job result once the workflow concludes.
462
+ ```typescript
463
+ //worker.ts
464
+ import { MeshFlow } from '@hotmeshio/hotmesh';
465
+ import Redis from 'ioredis';
466
+ import * as workflows from './recurringWorkflow';
263
467
 
264
- ```javascript
265
- const jobOutput = await hotMesh.pubsub(topic, payload);
266
- ```
468
+ async function run() {
469
+ const worker = await MeshFlow.Worker.create({
470
+ connection: {
471
+ class: Redis,
472
+ options: { host: 'redis', port: 6379 },
473
+ },
474
+ taskQueue: 'default',
475
+ workflow: workflows.recurringExample,
476
+ });
267
477
 
268
- >The `pubsub` method is a convenience function that merges pub and sub into a single call. Opt for HotMesh's queue-driven engine over fragile HTTP requests to develop resilient solutions.
478
+ await worker.run();
479
+ }
480
+ ```
269
481
 
270
- ### Link Worker Functions
271
- Link worker functions to a topic of your choice. When a workflow activity in the YAML definition with a corresponding topic runs, HotMesh will invoke your function, retrying as configured until it succeeds.
482
+ 4. Cancel the recurring workflow (`myRecurring123`) by calling `interrupt`.
272
483
 
273
- ```javascript
274
- import { HotMesh } from '@hotmeshio/hotmesh';
275
- import Redis from 'ioredis';
484
+ ```typescript
485
+ import { MeshFlow } from '@hotmeshio/hotmesh';
486
+ import * as Redis from Redis;
276
487
 
277
- const hotMesh = await HotMesh.init({
278
- appId: 'sandbox',
279
- workers: [
280
- {
281
- topic: 'sandbox.work.do.servicec',
282
- redis: {
488
+ const client = new MeshFlow.Client({
489
+ connection: {
283
490
  class: Redis,
284
- options: { host, port, password, db }
285
- }
286
- callback: async (data: StreamData) => {
287
- return {
288
- metadata: { ...data.metadata },
289
- data: { }
290
- };
491
+ options: { host: 'redis', port: 6379 }
291
492
  }
493
+ });
494
+
495
+ //get the workflow handle and interrupt it
496
+ const handle = await client.getHandle({
497
+ taskQueue: 'default',
498
+ workflowId: 'myRecurring123'
499
+ });
500
+
501
+ const result = await handle.interrupt();
502
+ ```
503
+ </details>
504
+
505
+ ## MeshData
506
+ The **MeshData** service extends the **MeshFlow** service, combining data record concepts and transactional workflow principles into a single *Operational Data Layer*.
507
+
508
+ Deployments with the Redis `FT.SEARCH` module enabled can use the **MeshData** module to merge [OLTP](https://en.wikipedia.org/wiki/Online_transaction_processing) and [OLAP](https://en.wikipedia.org/wiki/Online_analytical_processing) operations into a hybrid transactional/analytics ([HTAP](https://en.wikipedia.org/wiki/Hybrid_transactional/analytical_processing)) system.
509
+
510
+ >For those Redis deployments without the `FT.SEARCH` module, it's still useful to define a workflow schema. The MeshData class provides convenience methods for reading and writing hash field data to a workflow record (e.g., `get`, `del`, and `incr`).*
511
+
512
+ <details style="padding: .5em">
513
+ <summary style="font-size:1.25em;">Create a search index</summary>
514
+
515
+ ### Schemas and Indexes
516
+
517
+ This example demonstrates how to define a schema and deploy an index for a 'user' entity type.
518
+
519
+ 1. Define the **schema** for the `user` entity. This one includes the 3 formats supported by the FT.SEARCH module: `TEXT`, `TAG` and `NUMERIC`.
520
+
521
+ ```typescript
522
+ //schema.ts
523
+ export const schema: Types.WorkflowSearchOptions = {
524
+ schema: {
525
+ id: { type: 'TAG', sortable: false },
526
+ first: { type: 'TEXT', sortable: false, nostem: true },
527
+ active: { type: 'TAG', sortable: false },
528
+ created: { type: 'NUMERIC', sortable: true },
529
+ },
530
+ index: 'user',
531
+ prefix: ['user'],
532
+ };
533
+ ```
534
+
535
+ 2. Create the Redis index upon server startup. This one initializes the 'user' index in Redis, using the schema defined in the previous step. It's OK to call `createSearchIndex` multiple times; it will only create the index if it doesn't already exist.
536
+
537
+ ```typescript
538
+ //server.ts
539
+ import { MeshData } from '@hotmeshio/hotmesh';
540
+ import * as Redis from 'redis';
541
+ import { schema } from './schema';
542
+
543
+ const meshData = new MeshData(
544
+ Redis,
545
+ { url: 'redis://:key_admin@redis:6379' },
546
+ schema,
547
+ );
548
+ await meshData.createSearchIndex('user', { namespace: 'meshdata' });
549
+ ```
550
+ </details>
551
+
552
+ <details style="padding: .5em">
553
+ <summary style="font-size:1.25em;">Create an indexed, searchable record</summary>
554
+
555
+ ### Searchable Workflow
556
+ This example demonstrates how to create a 'user' workflow backed by the searchable schema from the prior example.
557
+
558
+ 1. Call MeshData `connect` to initialize a 'user' entity *worker*. It references a target worker function which will run the workflow. Data fields that are documented in the schema (like `active`) will be automatically indexed when set on the workflow record.
559
+
560
+ ```typescript
561
+ //connect.ts
562
+ import { MeshData } from '@hotmeshio/hotmesh';
563
+ import * as Redis from 'redis';
564
+ import { schema } from './schema';
565
+
566
+ export const connectUserWorker = async (): Promise<void> => {
567
+ const meshData = new MeshData(
568
+ Redis,
569
+ { url: 'redis://:key_admin@redis:6379' },
570
+ schema,
571
+ );
572
+
573
+ await meshData.connect({
574
+ entity: 'user',
575
+ target: async function(name: string): Promise<string> {
576
+ //add custom, searchable data (`active`) and return
577
+ const search = await MeshData.workflow.search();
578
+ await search.set('active', 'yes');
579
+ return `Welcome, ${name}.`;
580
+ },
581
+ options: { namespace: 'meshdata' },
582
+ });
292
583
  }
293
- ]
294
- };
295
- ```
584
+ ```
585
+
586
+ 2. Wire up the worker at server startup, so it's ready to process incoming requests.
296
587
 
297
- ### Observability
298
- Workflows and activities are run according to the rules you define, offering [Graph-Oriented](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/system_lifecycle.md#telemetry) telemetry insights into your legacy function executions.
588
+ ```typescript
589
+ //server.ts
590
+ import { connectUserWorker } from './connect';
591
+ await connectUserWorker();
592
+ ```
593
+
594
+ 3. Call MeshData `exec` to create a 'user' workflow. Searchable data can be set throughout the workflow's lifecycle. This one initializes the workflow with 3 data fields: `id`, `name` and `timestamp`. *An additional data field (`active`) is set within the workflow function in order to demonstrate both mechanisms for reading/writing data to a workflow.*
595
+
596
+ ```typescript
597
+ //exec.ts
598
+ import { MeshData } from '@hotmeshio/hotmesh';
599
+ import * as Redis from 'redis';
600
+
601
+ const meshData = new MeshData(
602
+ Redis,
603
+ { url: 'redis://:key_admin@redis:6379' },
604
+ schema,
605
+ );
606
+
607
+ export const newUser = async (id: string, name: string): Promise<string> => {
608
+ const response = await meshData.exec({
609
+ entity: 'user',
610
+ args: [name],
611
+ options: {
612
+ ttl: 'infinity',
613
+ id,
614
+ search: {
615
+ data: { id, name, timestamp: Date.now() }
616
+ },
617
+ namespace: 'meshdata',
618
+ },
619
+ });
620
+ return response;
621
+ };
622
+ ```
623
+
624
+ 4. Call the `newUser` function to create a searchable 'user' record.
625
+
626
+ ```typescript
627
+ import { newUser } from './exec';
628
+ const response = await newUser('jim123', 'James');
629
+ ```
630
+ </details>
631
+
632
+ <details style="padding: .5em">
633
+ <summary style="font-size:1.25em;">Fetch record data</summary>
634
+
635
+ ### Workflow Record Fields
636
+ This example demonstrates how to read data fields directly from a workflow.
637
+
638
+ 1. Read data fields directly from the *jimbo123* 'user' record.
639
+
640
+ ```typescript
641
+ //read.ts
642
+ import { MeshData } from '@hotmeshio/hotmesh';
643
+ import * as Redis from 'redis';
644
+ import { schema } from './schema';
645
+
646
+ const meshData = new MeshData(
647
+ Redis,
648
+ { url: 'redis://:key_admin@redis:6379' },
649
+ schema,
650
+ );
651
+
652
+ const data = await meshData.get(
653
+ 'user',
654
+ 'jimbo123',
655
+ {
656
+ fields: ['id', 'name', 'timestamp', 'active'],
657
+ namespace: 'meshdata'
658
+ },
659
+ );
660
+ ```
661
+ </details>
662
+
663
+ <details style="padding: .5em">
664
+ <summary style="font-size:1.25em;">Search record data</summary>
665
+
666
+ ### Searchable Workflow
667
+ This example demonstrates how to search for those workflows where a given condition exists in the data. This one searches for active users. *NOTE: The native Redis FT.SEARCH syntax is supported. The JSON abstraction shown here is a convenience method for straight-forward, one-dimensional queries.*
668
+
669
+ 1. Search for active users (where the value of the `active` field is `yes`).
670
+
671
+ ```typescript
672
+ //read.ts
673
+ import { MeshData } from '@hotmeshio/hotmesh';
674
+ import * as Redis from 'redis';
675
+ import { schema } from './schema';
676
+
677
+ const meshData = new MeshData(
678
+ Redis,
679
+ { url: 'redis://:key_admin@redis:6379' },
680
+ schema,
681
+ );
299
682
 
300
- ## FAQ
683
+ const results = await meshData.findWhere('user', {
684
+ query: [{ field: 'active', is: '=', value: 'yes' }],
685
+ limit: { start: 0, size: 100 },
686
+ return: ['id', 'name', 'timestamp', 'active']
687
+ });
688
+ ```
689
+ </details>
690
+
691
+ ## Visualize | OpenTelemetry
692
+ HotMesh's telemetry output provides unmatched insight into long-running, multi-service transactions. Add your Honeycomb credentials to any project using HotMesh and HotMesh will emit the full *OpenTelemetry* execution tree organized as a DAG.
693
+
694
+ <img src="./docs/img/visualize/opentelemetry.png" alt="Open Telemetry" style="width:600px;max-width:600px;">
695
+
696
+ ## Visualize | HotMesh Dashboard
697
+ The HotMesh dashboard provides a detailed overview of all running workflows. As HotMesh is a service mesh, it's also possible to throttle and pause workers and engines attached to the mesh. Redis will simply inflate like a balloon until the throttle is removed and ingestion is resumed.
698
+
699
+ An LLM is included to simplify querying and analyzing workflow data for those deployments that include the Redis `FT.SEARCH` module.
700
+
701
+ <img src="./docs/img/visualize/hotmesh_dashboard.png" alt="HotMesh Dashboard" style="width:600px;max-width:600px;">
702
+
703
+ ## Visualize | RedisInsight
704
+ View commands, streams, data, CPU, load, etc using the RedisInsight data browser.
705
+
706
+ <img src="./docs/img/visualize/redisinsight.png" alt="Redis Insight" style="width:600px;max-width:600px;">
707
+
708
+ ## HotMesh
709
+ The *MeshData*, *MeshCall*, and *MeshFlow* modules are all created using the HotMesh modeling system. Refer to the following documents to better understand the platform and how it delivers workflow orchestration without a central application server.
710
+
711
+ ### FAQ
301
712
  Refer to the [FAQ](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/faq.md) for terminology, definitions, and an exploration of how HotMesh facilitates orchestration use cases.
302
713
 
303
- ## Quick Start
304
- Refer to the [Quick Start](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/quickstart.md) for sample YAML workflows you can copy, paste, and modify to get started.
714
+ ### Quick Start
715
+ Refer to the [Quick Start](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/quickstart.md) for sample YAML workflows you can copy, paste, and modify to get started with HotMesh.
305
716
 
306
- ## Developer Guide
717
+ ### Developer Guide
307
718
  For more details on the complete development process, including information about schemas, APIs, and deployment, consult the [Developer Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/developer_guide.md).
308
719
 
309
- ## Model Driven Development
720
+ ### Model Driven Development
310
721
  [Model Driven Development](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/model_driven_development.md) is an established strategy for managing process-oriented tasks. Check out this guide to understand its foundational principles.
311
722
 
312
- ## Data Mapping
723
+ ### Data Mapping
313
724
  Exchanging data between activities is central to HotMesh. For detailed information on supported functions and the functional mapping syntax (@pipes), see the [Data Mapping Overview](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/data_mapping.md).
314
725
 
315
- ## Composition
726
+ ### Composition
316
727
  While the simplest graphs are linear, detailing a consistent sequence of non-cyclical activities, graphs can be layered to represent intricate business scenarios. Some can even be designed to accommodate long-lasting workflows that span months. For more details, check out the [Composable Workflow Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/composable_workflow.md).
317
728
 
318
- ## System Lifecycle
729
+ ### System Lifecycle
319
730
  Gain insight into HotMesh's monitoring, exception handling, and alarm configurations via the [System Lifecycle Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/system_lifecycle.md).
320
731
 
321
- ## Distributed Orchestration | System Overview
732
+ ### Distributed Orchestration | System Overview
322
733
  HotMesh is a distributed orchestration engine. Refer to the [Distributed Orchestration Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/distributed_orchestration.md) for a high-level overview of the approach.
323
734
 
324
- ## Distributed Orchestration | System Design
735
+ ### Distributed Orchestration | System Design
325
736
  HotMesh is more than Redis and TypeScript. The theory that underlies the architecture is applicable to any number of data storage and streaming backends: [A Message-Oriented Approach to Decentralized Process Orchestration](https://zenodo.org/records/12168558).
737
+
738
+ ### Samples
739
+ Refer to the [hotmeshio/samples-javascript](https://github.com/hotmeshio/samples-javascript) repo for usage examples.