@hotmeshio/hotmesh 0.0.52 → 0.0.53

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 (126) hide show
  1. package/README.md +22 -18
  2. package/build/index.d.ts +1 -2
  3. package/build/index.js +1 -3
  4. package/build/modules/enums.d.ts +8 -3
  5. package/build/modules/enums.js +16 -8
  6. package/build/modules/errors.d.ts +98 -18
  7. package/build/modules/errors.js +90 -33
  8. package/build/package.json +7 -2
  9. package/build/services/activities/activity.d.ts +8 -0
  10. package/build/services/activities/activity.js +63 -14
  11. package/build/services/activities/await.js +6 -6
  12. package/build/services/activities/cycle.d.ts +2 -2
  13. package/build/services/activities/cycle.js +5 -5
  14. package/build/services/activities/hook.js +4 -4
  15. package/build/services/activities/interrupt.d.ts +3 -3
  16. package/build/services/activities/interrupt.js +15 -6
  17. package/build/services/activities/signal.d.ts +2 -2
  18. package/build/services/activities/signal.js +4 -4
  19. package/build/services/activities/trigger.js +12 -3
  20. package/build/services/activities/worker.js +6 -6
  21. package/build/services/compiler/deployer.js +33 -5
  22. package/build/services/compiler/validator.d.ts +2 -0
  23. package/build/services/compiler/validator.js +5 -1
  24. package/build/services/durable/client.d.ts +7 -1
  25. package/build/services/durable/client.js +56 -30
  26. package/build/services/durable/exporter.d.ts +7 -72
  27. package/build/services/durable/exporter.js +105 -295
  28. package/build/services/durable/handle.d.ts +11 -6
  29. package/build/services/durable/handle.js +59 -46
  30. package/build/services/durable/index.d.ts +0 -2
  31. package/build/services/durable/index.js +0 -2
  32. package/build/services/durable/schemas/factory.d.ts +33 -0
  33. package/build/services/durable/schemas/factory.js +2356 -0
  34. package/build/services/durable/search.js +8 -8
  35. package/build/services/durable/worker.js +117 -25
  36. package/build/services/durable/workflow.d.ts +46 -43
  37. package/build/services/durable/workflow.js +273 -277
  38. package/build/services/engine/index.js +3 -0
  39. package/build/services/exporter/index.d.ts +2 -4
  40. package/build/services/exporter/index.js +4 -5
  41. package/build/services/mapper/index.d.ts +6 -2
  42. package/build/services/mapper/index.js +6 -2
  43. package/build/services/pipe/functions/array.d.ts +2 -10
  44. package/build/services/pipe/functions/array.js +30 -28
  45. package/build/services/pipe/functions/conditional.d.ts +1 -0
  46. package/build/services/pipe/functions/conditional.js +3 -0
  47. package/build/services/pipe/functions/date.d.ts +1 -0
  48. package/build/services/pipe/functions/date.js +4 -0
  49. package/build/services/pipe/functions/index.d.ts +2 -0
  50. package/build/services/pipe/functions/index.js +2 -0
  51. package/build/services/pipe/functions/logical.d.ts +5 -0
  52. package/build/services/pipe/functions/logical.js +12 -0
  53. package/build/services/pipe/functions/object.d.ts +3 -0
  54. package/build/services/pipe/functions/object.js +25 -7
  55. package/build/services/pipe/index.d.ts +20 -3
  56. package/build/services/pipe/index.js +82 -16
  57. package/build/services/router/index.js +14 -3
  58. package/build/services/serializer/index.d.ts +3 -2
  59. package/build/services/serializer/index.js +11 -4
  60. package/build/services/store/clients/ioredis.js +6 -6
  61. package/build/services/store/clients/redis.js +7 -7
  62. package/build/services/store/index.d.ts +2 -0
  63. package/build/services/store/index.js +4 -1
  64. package/build/services/stream/clients/ioredis.js +8 -8
  65. package/build/services/stream/clients/redis.js +1 -1
  66. package/build/types/activity.d.ts +60 -5
  67. package/build/types/durable.d.ts +168 -33
  68. package/build/types/exporter.d.ts +26 -4
  69. package/build/types/index.d.ts +2 -2
  70. package/build/types/job.d.ts +69 -5
  71. package/build/types/pipe.d.ts +81 -3
  72. package/build/types/stream.d.ts +61 -1
  73. package/build/types/stream.js +4 -0
  74. package/index.ts +1 -2
  75. package/modules/enums.ts +16 -8
  76. package/modules/errors.ts +174 -32
  77. package/package.json +7 -2
  78. package/services/activities/activity.ts +63 -14
  79. package/services/activities/await.ts +6 -6
  80. package/services/activities/cycle.ts +7 -6
  81. package/services/activities/hook.ts +4 -4
  82. package/services/activities/interrupt.ts +19 -9
  83. package/services/activities/signal.ts +6 -5
  84. package/services/activities/trigger.ts +16 -4
  85. package/services/activities/worker.ts +7 -7
  86. package/services/compiler/deployer.ts +33 -6
  87. package/services/compiler/validator.ts +7 -3
  88. package/services/durable/client.ts +47 -14
  89. package/services/durable/exporter.ts +110 -318
  90. package/services/durable/handle.ts +63 -50
  91. package/services/durable/index.ts +0 -2
  92. package/services/durable/schemas/factory.ts +2358 -0
  93. package/services/durable/search.ts +8 -8
  94. package/services/durable/worker.ts +128 -29
  95. package/services/durable/workflow.ts +304 -288
  96. package/services/engine/index.ts +4 -0
  97. package/services/exporter/index.ts +10 -12
  98. package/services/mapper/index.ts +6 -2
  99. package/services/pipe/functions/array.ts +24 -37
  100. package/services/pipe/functions/conditional.ts +4 -0
  101. package/services/pipe/functions/date.ts +6 -0
  102. package/services/pipe/functions/index.ts +7 -5
  103. package/services/pipe/functions/logical.ts +11 -0
  104. package/services/pipe/functions/object.ts +26 -7
  105. package/services/pipe/index.ts +99 -21
  106. package/services/quorum/index.ts +1 -3
  107. package/services/router/index.ts +14 -3
  108. package/services/serializer/index.ts +12 -5
  109. package/services/store/clients/ioredis.ts +6 -6
  110. package/services/store/clients/redis.ts +7 -7
  111. package/services/store/index.ts +4 -1
  112. package/services/stream/clients/ioredis.ts +8 -8
  113. package/services/stream/clients/redis.ts +1 -1
  114. package/types/activity.ts +87 -15
  115. package/types/durable.ts +246 -73
  116. package/types/exporter.ts +31 -5
  117. package/types/index.ts +6 -7
  118. package/types/job.ts +130 -36
  119. package/types/pipe.ts +84 -3
  120. package/types/stream.ts +82 -23
  121. package/build/services/durable/factory.d.ts +0 -17
  122. package/build/services/durable/factory.js +0 -817
  123. package/build/services/durable/meshos.d.ts +0 -127
  124. package/build/services/durable/meshos.js +0 -380
  125. package/services/durable/factory.ts +0 -818
  126. package/services/durable/meshos.ts +0 -441
@@ -1,159 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ExporterService = void 0;
4
- const serializer_1 = require("../serializer");
5
- const utils_1 = require("../../modules/utils");
6
4
  const key_1 = require("../../modules/key");
7
- /**
8
- * Downloads job data from Redis (hscan, hmget, hgetall)
9
- * Splits, Inflates, and Sorts the job data for use in durable contexts
10
- */
5
+ const utils_1 = require("../../modules/utils");
6
+ const serializer_1 = require("../serializer");
11
7
  class ExporterService {
12
8
  constructor(appId, store, logger) {
13
- /**
14
- * Friendly names for the activity ids
15
- */
16
- this.activitySymbols = {
17
- t1: 'trigger',
18
- a1: 'pivot',
19
- w1: 'worker',
20
- a592: 'sleeper',
21
- a594: 'awaiter',
22
- a599: 'retryer',
23
- c592: 'sleep_cycler',
24
- c594: 'await_cycler',
25
- c599: 'retry_cycler',
26
- s5: 'scrubber',
27
- sig: 'hook',
28
- siga1: 'hook_pivot',
29
- sigw1: 'hook_worker',
30
- siga592: 'hook_sleeper',
31
- siga594: 'hook_awaiter',
32
- siga599: 'hook_retryer',
33
- sigc592: 'hook_sleep_cycler',
34
- sigc594: 'hook_await_cycler',
35
- sigc599: 'hook_retry_cycler',
36
- };
37
- //adjacent transitions
38
- this.transitions = {
39
- trigger: ['pivot', 'hook'],
40
- pivot: ['worker'],
41
- worker: ['sleeper', 'awaiter', 'retryer', 'scrubber'],
42
- sleeper: ['sleep_cycler'],
43
- awaiter: ['await_cycler'],
44
- retryer: ['retry_cycler'],
45
- hook: ['hook_pivot'],
46
- hook_pivot: ['hook_worker'],
47
- hook_worker: ['hook_sleeper', 'hook_awaiter', 'hook_retryer'],
48
- hook_sleeper: ['hook_sleep_cycler'],
49
- hook_awaiter: ['hook_await_cycler'],
50
- hook_retryer: ['hook_retry_cycler'],
51
- };
52
- //goto transitions
53
- this.cycles = {
54
- sleep_cycler: ['pivot'],
55
- await_cycler: ['pivot'],
56
- retry_cycler: ['pivot'],
57
- hook_sleep_cycler: ['hook_pivot'],
58
- hook_await_cycler: ['hook_pivot'],
59
- hook_retry_cycler: ['hook_pivot'],
60
- };
61
9
  this.appId = appId;
62
10
  this.logger = logger;
63
11
  this.store = store;
64
- this.serializer = new serializer_1.SerializerService();
65
12
  }
66
13
  /**
67
- * Convert the job hash and dependency list into a DurableJobExport object.
68
- * This object contains various facets that describe the interaction
69
- * in terms relevant to narrative storytelling.
14
+ * Convert the job hash from its compiles format into a DurableJobExport object with
15
+ * facets that describe the workflow in terms relevant to narrative storytelling.
70
16
  */
71
17
  async export(jobId, options = {}) {
72
18
  if (!this.symbols) {
73
19
  this.symbols = this.store.getAllSymbols();
74
20
  this.symbols = await this.symbols;
75
21
  }
76
- const depData = await this.store.getDependencies(jobId);
77
22
  const jobData = await this.store.getRaw(jobId);
78
- const jobExport = this.inflate(jobData, depData);
23
+ const jobExport = this.inflate(jobData /*, depData*/);
79
24
  return jobExport;
80
25
  }
81
- /**
82
- * Interleave actions into the replay timeline to create
83
- * a time-ordered timeline of the entire interaction, beginning
84
- * with the entry trigger and concluding with the scrubber
85
- * activity. Using the returned timeline, it is possible to
86
- * create an animated narrative of the job, highlighting
87
- * activities in the graph according to the timeline's
88
- * activity-created (/ac) and activity-updated (/au) entries.
89
- */
90
- createTimeline(replay, actions) {
91
- const timeline = [];
92
- replay.forEach((item) => {
93
- const dimensions = item[0];
94
- const parts = dimensions.split('/');
95
- const activityName = item[1].split('/')[0];
96
- const duplex = item[1].endsWith('/ac') ? 'entry' : 'exit';
97
- const timestamp = item[2];
98
- let event = {
99
- activity: activityName,
100
- duplex: duplex,
101
- dimension: dimensions,
102
- timestamp,
103
- created: timestamp,
104
- updated: timestamp,
105
- };
106
- const prior = timeline[timeline.length - 1];
107
- if (prior && prior.activity === event.activity && prior.duplex !== event.duplex && prior.dimension === event.dimension) {
108
- if (event.duplex === 'exit') {
109
- prior.updated = event.timestamp;
110
- }
111
- else {
112
- prior.created = event.timestamp;
113
- }
114
- event = prior;
115
- }
116
- else {
117
- timeline.push(event);
118
- }
119
- if (this.isMainEntry(item[1])) {
120
- event.actions = [];
121
- this.interleaveActions(actions.main, event.actions);
122
- }
123
- else if (this.isHookEntry(item[1])) {
124
- const hookDimension = `/${parts[1]}/${parts[2]}`;
125
- const hookActions = actions.hooks[hookDimension];
126
- event.actions = [];
127
- this.interleaveActions(hookActions, event.actions);
128
- }
129
- });
130
- return timeline;
131
- }
132
- /**
133
- * Interleave actions into the 'worker' and 'hook_worker'
134
- * activities (between their /ac and /au entries)
135
- */
136
- interleaveActions(target, actions) {
137
- if (target) {
138
- for (let i = target.cursor + 1; i < target.items.length; i++) {
139
- const [_, actionType, jobOrIndex] = target.items[i];
140
- actions.push({ action: actionType, target: jobOrIndex });
141
- target.cursor = i;
142
- if (this.isPausingAction(actionType)) {
143
- break;
144
- }
145
- }
146
- }
147
- }
148
- isPausingAction(actionType) {
149
- return actionType === 'sleep' || actionType === 'waitForSignal';
150
- }
151
- isMainEntry(key) {
152
- return key.startsWith('worker/') && key.endsWith('/ac');
153
- }
154
- isHookEntry(key) {
155
- return key.startsWith('hook_worker/') && key.endsWith('/ac');
156
- }
157
26
  /**
158
27
  * Inflates the key from Redis, 3-character symbol
159
28
  * into a human-readable JSON path, reflecting the
@@ -163,9 +32,6 @@ class ExporterService {
163
32
  if (key in this.symbols) {
164
33
  const path = this.symbols[key];
165
34
  const parts = path.split('/');
166
- if (parts[0] in this.activitySymbols) {
167
- parts[0] = this.activitySymbols[parts[0]];
168
- }
169
35
  return parts.join('/');
170
36
  }
171
37
  return key;
@@ -177,101 +43,32 @@ class ExporterService {
177
43
  * @param data - the dependency data from Redis
178
44
  * @returns - the organized dependency data
179
45
  */
180
- inflateDependencyData(data, actions) {
181
- const hookReg = /([0-9,]+)-(\d+)$/;
182
- const flowReg = /-(\d+)$/;
46
+ inflateDependencyData(data) {
183
47
  return data.map((dependency, index) => {
184
- const [action, topic, gid, _pd, ...jid] = dependency.split(key_1.VALSEP);
185
- const jobId = jid.join(key_1.VALSEP);
186
- const match = jobId.match(hookReg);
187
- let prefix;
188
- let type;
189
- let dimensionKey = '';
190
- if (match) {
191
- //hook-originating dependency
192
- const [_, dimension, counter] = match;
193
- dimensionKey = dimension.split(',').join('/');
194
- prefix = `${dimensionKey}[${counter}]`;
195
- type = 'hook';
196
- }
197
- else {
198
- const match = jobId.match(flowReg);
199
- if (match) {
200
- //main workflow-originating dependency
201
- const [_, counter] = match;
202
- prefix = `[${counter}]`;
203
- type = 'flow';
204
- }
205
- else {
206
- //'other' types like signal cleanup
207
- prefix = '/';
208
- type = 'other';
209
- }
210
- }
211
- this.seedActions(type, action, topic, dependency, prefix, dimensionKey, actions, jobId);
48
+ const [action, topic, gid, dimension, ...jid] = dependency.split(key_1.VALSEP);
49
+ const job_id = jid.join(key_1.VALSEP);
212
50
  return {
213
- type: action,
51
+ index,
52
+ action,
214
53
  topic,
215
54
  gid,
216
- jid: jobId,
55
+ dimension,
56
+ job_id,
217
57
  };
218
58
  });
219
59
  }
220
- /**
221
- * Adds historical actions (proxyActivity, executeChild)
222
- * using the `dependency list` to determine
223
- * after-the-fact what happened within the 'black-box'
224
- * worker function. This is necessary to interleave the
225
- * actions into the replay timeline, given that it isn't
226
- * really possible to know the inner-workings of the user's
227
- * function
228
- *
229
- */
230
- seedActions(type, action, topic, dep, prefix, dimensionKey, actions, jobId) {
231
- if (type !== 'other' && action === 'expire-child') {
232
- let depType;
233
- if (topic == `${this.appId}.activity.execute`) {
234
- depType = 'proxyActivity';
235
- }
236
- else if (topic == `${this.appId}.execute`) {
237
- depType = 'executeChild';
238
- }
239
- else if (topic == `${this.appId}.wfsc.execute`) {
240
- depType = 'waitForSignal';
241
- }
242
- if (depType) {
243
- if (type === 'flow') {
244
- actions.main.items.push([prefix, depType, jobId]);
245
- }
246
- else if (type === 'hook') {
247
- if (!actions.hooks[dimensionKey]) {
248
- actions.hooks[dimensionKey] = {
249
- cursor: -1,
250
- items: [],
251
- };
252
- }
253
- actions.hooks[dimensionKey].items.push([prefix, depType, jobId]);
254
- }
255
- }
256
- }
257
- }
258
60
  /**
259
61
  * Inflates the job data from Redis into a DurableJobExport object
260
62
  * @param jobHash - the job data from Redis
261
63
  * @param dependencyList - the list of dependencies for the job
262
64
  * @returns - the inflated job data
263
65
  */
264
- inflate(jobHash, dependencyList) {
265
- //the list of actions taken in the workflow and hook functions
266
- const actions = {
267
- hooks: {},
268
- main: { cursor: -1, items: [] },
269
- };
270
- const dependencies = this.inflateDependencyData(dependencyList, actions);
66
+ inflate(jobHash) {
67
+ const idempotents = [];
271
68
  const state = {};
272
69
  const data = {};
273
70
  const other = [];
274
- const replay = [];
71
+ const replay = {};
275
72
  const regex = /^([a-zA-Z]{3}),(\d+(?:,\d+)*)/;
276
73
  Object.entries(jobHash).forEach(([key, value]) => {
277
74
  const match = key.match(regex);
@@ -281,7 +78,7 @@ class ExporterService {
281
78
  }
282
79
  else if (key.length === 3) {
283
80
  //job state
284
- state[this.inflateKey(key)] = this.serializer.fromString(value);
81
+ state[this.inflateKey(key)] = serializer_1.SerializerService.fromString(value);
285
82
  }
286
83
  else if (key.startsWith('_')) {
287
84
  //job data
@@ -289,99 +86,112 @@ class ExporterService {
289
86
  }
290
87
  else if (key.startsWith('-')) {
291
88
  //actions with side effect (replayable)
292
- this.inflateActions(key, value, actions);
89
+ idempotents.push({
90
+ key,
91
+ value: serializer_1.SerializerService.fromString(value),
92
+ parts: extractParts(key),
93
+ });
293
94
  }
294
95
  else {
295
96
  //collator guids, etc
296
97
  other.push([null, key, value]);
297
98
  }
298
99
  });
299
- replay.sort(this.dateSort);
300
- actions.main.items.sort(this.reverseSort);
301
- Object.entries(actions.hooks).forEach(([key, value]) => {
302
- value.items.sort(this.reverseSort);
303
- });
100
+ const sortEntriesByCreated = (obj) => {
101
+ const entriesArray = Object.values(obj);
102
+ entriesArray.sort((a, b) => {
103
+ return (a.created || a.updated).localeCompare(b.created || b.updated);
104
+ });
105
+ return entriesArray;
106
+ };
107
+ /**
108
+ * idem list has a complicated sort order based on indexes and dimensions
109
+ */
110
+ const sortParts = (parts) => {
111
+ return parts.sort((a, b) => {
112
+ const { dimension: aDim, index: aIdx, secondary: aSec } = a.parts;
113
+ const { dimension: bDim, index: bIdx, secondary: bSec } = b.parts;
114
+ if (aDim === undefined && bDim !== undefined)
115
+ return -1;
116
+ if (aDim !== undefined && bDim === undefined)
117
+ return 1;
118
+ if (aDim !== undefined && bDim !== undefined) {
119
+ if (aDim < bDim)
120
+ return -1;
121
+ if (aDim > bDim)
122
+ return 1;
123
+ }
124
+ if (aIdx < bIdx)
125
+ return -1;
126
+ if (aIdx > bIdx)
127
+ return 1;
128
+ if (aSec === undefined && bSec !== undefined)
129
+ return -1;
130
+ if (aSec !== undefined && bSec === undefined)
131
+ return 1;
132
+ if (aSec !== undefined && bSec !== undefined) {
133
+ if (aSec < bSec)
134
+ return -1;
135
+ if (aSec > bSec)
136
+ return 1;
137
+ }
138
+ return 0;
139
+ });
140
+ };
141
+ function extractParts(key) {
142
+ function extractDimension(label) {
143
+ const parts = label.split(',');
144
+ if (parts.length > 1) {
145
+ parts.shift();
146
+ return parts.join(',');
147
+ }
148
+ }
149
+ const parts = key.split('-');
150
+ if (parts.length === 4) {
151
+ //-proxy-5- -search-1-1-
152
+ return {
153
+ index: parseInt(parts[2], 10),
154
+ dimension: extractDimension(parts[1]),
155
+ };
156
+ }
157
+ else {
158
+ //-search,0,0-1-1- -proxy,0,0-1-
159
+ return {
160
+ index: parseInt(parts[2], 10),
161
+ secondary: parseInt(parts[3], 10),
162
+ dimension: extractDimension(parts[1]),
163
+ };
164
+ }
165
+ }
304
166
  return {
305
167
  data: (0, utils_1.restoreHierarchy)(data),
306
- dependencies,
168
+ idempotents: sortParts(idempotents),
307
169
  state: Object.entries((0, utils_1.restoreHierarchy)(state))[0][1],
308
170
  status: jobHash[':'],
309
- timeline: this.createTimeline(replay, actions),
310
- transitions: { ...this.transitions },
311
- cycles: { ...this.cycles },
171
+ replay: sortEntriesByCreated(replay),
312
172
  };
313
173
  }
314
174
  inflateProcess(match, value, replay) {
315
- const [_, letters, numbers] = match;
175
+ const [_, letters, dimensions] = match;
316
176
  const path = this.inflateKey(letters);
317
- if (path.endsWith('/output/metadata/ac') ||
318
- path.endsWith('/output/metadata/au')) {
319
- const dimensions = `/${numbers.replace(/,/g, '/')}`;
320
- const resolved = this.serializer.fromString(value);
321
- replay.push([
322
- dimensions,
323
- path,
324
- resolved,
325
- ]);
326
- }
327
- }
328
- inflateActions(key, value, actions) {
329
- let [_, dimensionalType, counter, subcounter] = key.split('-');
330
- if (subcounter) {
331
- counter = `${counter}.${subcounter}`;
332
- }
333
- const [type, ...dimensions] = dimensionalType.split(',');
334
- let dimensionKey = '';
335
- let isHook = false;
336
- if (dimensions.length > 0) {
337
- dimensionKey = `/${dimensions.join('/')}`;
338
- isHook = true;
339
- }
340
- let targetList;
341
- if (isHook) {
342
- if (!actions.hooks[dimensionKey]) {
343
- actions.hooks[dimensionKey] = {
344
- cursor: -1,
345
- items: [],
177
+ const parts = path.split('/');
178
+ const activity = parts[0];
179
+ const isCreate = path.endsWith('/output/metadata/ac');
180
+ const isUpdate = path.endsWith('/output/metadata/au');
181
+ if (isCreate || isUpdate) {
182
+ const targetName = `${activity},${dimensions}`;
183
+ let target = replay[targetName];
184
+ if (!target) {
185
+ replay[targetName] = {
186
+ activity,
187
+ dimensions,
188
+ created: isCreate ? value : null,
189
+ updated: isUpdate ? value : null,
346
190
  };
347
191
  }
348
- targetList = actions.hooks[dimensionKey].items;
349
- }
350
- else {
351
- targetList = actions.main.items;
352
- }
353
- targetList.push([
354
- `${dimensionKey}[${counter}]`,
355
- type,
356
- value,
357
- ]);
358
- }
359
- reverseSort(aKey, bKey) {
360
- if (aKey[0] > bKey[0]) {
361
- return 1;
362
- }
363
- else if (aKey[0] < bKey[0]) {
364
- return -1;
365
- }
366
- else {
367
- if (aKey[1] > bKey[1]) {
368
- return 1;
369
- }
370
- else if (aKey[1] < bKey[1]) {
371
- return -1;
192
+ else {
193
+ target[isCreate ? 'created' : 'updated'] = value;
372
194
  }
373
- return 0;
374
- }
375
- }
376
- dateSort(aKey, bKey) {
377
- if (aKey[2] > bKey[2]) {
378
- return 1;
379
- }
380
- else if (aKey[2] < bKey[2]) {
381
- return -1;
382
- }
383
- else {
384
- return 0;
385
195
  }
386
196
  }
387
197
  }
@@ -2,6 +2,7 @@ import { ExporterService } from './exporter';
2
2
  import { HotMeshService as HotMesh } from '../hotmesh';
3
3
  import { DurableJobExport } from '../../types/exporter';
4
4
  import { JobInterruptOptions } from '../../types/job';
5
+ import { StreamError } from '../../types/stream';
5
6
  export declare class WorkflowHandleService {
6
7
  exporter: ExporterService;
7
8
  hotMesh: HotMesh;
@@ -12,8 +13,8 @@ export declare class WorkflowHandleService {
12
13
  /**
13
14
  * Sends a signal to the workflow. This is a way to send
14
15
  * a message to a workflow that is paused due to having
15
- * executed a `waitForSignal` workflow extension. Awakens
16
- * the workflow if no other signals are pending.
16
+ * executed `Durable.workflow.waitFor`. The workflow
17
+ * will awaken if no other signals are pending.
17
18
  */
18
19
  signal(signalId: string, data: Record<any, any>): Promise<void>;
19
20
  /**
@@ -45,9 +46,13 @@ export declare class WorkflowHandleService {
45
46
  */
46
47
  interrupt(options?: JobInterruptOptions): Promise<string>;
47
48
  /**
48
- * Awaits for the workflow to complete and returns the result. If
49
- * the workflow thows and error, this method will likewise throw
50
- * an error.
49
+ * Waits for the workflow to complete and returns the result. If
50
+ * the workflow response includes an error, this method will rethrow
51
+ * the error, including the stack trace if available.
52
+ * Wrap calls in a try/catch as necessary to avoid unhandled exceptions.
51
53
  */
52
- result(loadState?: boolean): Promise<any>;
54
+ result<T>(config?: {
55
+ state?: boolean;
56
+ throwOnError?: boolean;
57
+ }): Promise<T | StreamError>;
53
58
  }
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WorkflowHandleService = void 0;
4
- const enums_1 = require("../../modules/enums");
5
4
  const exporter_1 = require("./exporter");
6
5
  class WorkflowHandleService {
7
6
  constructor(hotMesh, workflowTopic, workflowId) {
@@ -16,8 +15,8 @@ class WorkflowHandleService {
16
15
  /**
17
16
  * Sends a signal to the workflow. This is a way to send
18
17
  * a message to a workflow that is paused due to having
19
- * executed a `waitForSignal` workflow extension. Awakens
20
- * the workflow if no other signals are pending.
18
+ * executed `Durable.workflow.waitFor`. The workflow
19
+ * will awaken if no other signals are pending.
21
20
  */
22
21
  async signal(signalId, data) {
23
22
  await this.hotMesh.hook(`${this.hotMesh.appId}.wfs.signal`, { id: signalId, data });
@@ -63,68 +62,82 @@ class WorkflowHandleService {
63
62
  return await this.hotMesh.interrupt(`${this.hotMesh.appId}.execute`, this.workflowId, options);
64
63
  }
65
64
  /**
66
- * Awaits for the workflow to complete and returns the result. If
67
- * the workflow thows and error, this method will likewise throw
68
- * an error.
65
+ * Waits for the workflow to complete and returns the result. If
66
+ * the workflow response includes an error, this method will rethrow
67
+ * the error, including the stack trace if available.
68
+ * Wrap calls in a try/catch as necessary to avoid unhandled exceptions.
69
69
  */
70
- async result(loadState) {
71
- if (loadState) {
72
- const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
73
- if (!state.data && state.metadata.err) {
74
- throw new Error(JSON.parse(state.metadata.err));
75
- }
76
- if (state?.data?.done) {
77
- //child flows are never 'done'; they use a hook
78
- //that only closes upon parent flow completion.
79
- return state.data.response;
80
- }
81
- }
82
- let status = await this.hotMesh.getStatus(this.workflowId);
70
+ async result(config) {
83
71
  const topic = `${this.hotMesh.appId}.executed.${this.workflowId}`;
84
- return new Promise((resolve, reject) => {
85
- let isResolved = false;
86
- //common fulfill/unsubscribe
72
+ let isResolved = false;
73
+ return new Promise(async (resolve, reject) => {
74
+ /**
75
+ * rejects/resolves the promise based on the `throwOnError`
76
+ * default behavior is to throw if error
77
+ */
78
+ const safeReject = (err) => {
79
+ if (config?.throwOnError === false) {
80
+ return resolve(err);
81
+ }
82
+ reject(err);
83
+ };
84
+ /**
85
+ * Common completion function that unsubscribes from the topic/returns
86
+ */
87
87
  const complete = async (response, err) => {
88
88
  if (isResolved)
89
89
  return;
90
90
  isResolved = true;
91
- this.hotMesh.unsub(topic);
92
91
  if (err) {
93
- return reject(JSON.parse(err));
92
+ return safeReject(err);
94
93
  }
95
94
  else if (!response) {
96
95
  const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
97
- if (state.metadata.err) {
98
- const error = JSON.parse(state.metadata.err);
99
- if (error.code === enums_1.HMSH_CODE_INTERRUPT || !state.data) {
100
- return reject({ ...error, job_id: this.workflowId });
101
- }
96
+ if (state.data?.done && !state.data?.$error) {
97
+ return resolve(state.data.response);
98
+ }
99
+ else if (state.data?.$error) {
100
+ return safeReject(state.data.$error);
101
+ }
102
+ else if (state.metadata.err) {
103
+ return safeReject(JSON.parse(state.metadata.err));
102
104
  }
103
105
  response = state.data?.response;
104
106
  }
105
107
  resolve(response);
106
108
  };
107
- //check for done
108
- if (status <= 0) {
109
- return complete();
109
+ //more expensive; fetches the entire job, not just the `status`
110
+ if (config?.state) {
111
+ const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
112
+ if (state?.data?.done && !state.data?.$error) {
113
+ return complete(state.data.response);
114
+ }
115
+ else if (state.data?.$error) {
116
+ return complete(null, state.data.$error);
117
+ }
118
+ else if (state.metadata.err) {
119
+ return complete(null, JSON.parse(state.metadata.err));
120
+ }
110
121
  }
111
- //subscribe to topic
112
- this.hotMesh.sub(topic, async (topic, state) => {
113
- if (state.metadata.err) {
122
+ //subscribe to 'done' topic
123
+ this.hotMesh.sub(topic, async (_topic, state) => {
124
+ this.hotMesh.unsub(topic);
125
+ if (state.data.done && !state.data?.$error) {
126
+ await complete(state.data?.response);
127
+ }
128
+ else if (state.data?.$error) {
129
+ return complete(null, state.data.$error);
130
+ }
131
+ else if (state.metadata.err) {
114
132
  const error = JSON.parse(state.metadata.err);
115
- if (error.code === enums_1.HMSH_CODE_INTERRUPT || !state.data) {
116
- return await complete(null, state.metadata.err);
117
- }
133
+ return await complete(null, error);
118
134
  }
119
- await complete(state.data?.response);
120
135
  });
121
- //resolve for race condition
122
- setTimeout(async () => {
123
- status = await this.hotMesh.getStatus(this.workflowId);
124
- if (status <= 0) {
125
- await complete();
126
- }
127
- }, 0);
136
+ //check state in case completed during wiring
137
+ const status = await this.hotMesh.getStatus(this.workflowId);
138
+ if (status <= 0) {
139
+ await complete();
140
+ }
128
141
  });
129
142
  }
130
143
  }
@@ -1,6 +1,5 @@
1
1
  import { ClientService } from './client';
2
2
  import { ConnectionService } from './connection';
3
- import { MeshOSService } from './meshos';
4
3
  import { Search } from './search';
5
4
  import { WorkerService } from './worker';
6
5
  import { WorkflowService } from './workflow';
@@ -9,7 +8,6 @@ export declare const Durable: {
9
8
  Client: typeof ClientService;
10
9
  Connection: typeof ConnectionService;
11
10
  Search: typeof Search;
12
- MeshOS: typeof MeshOSService;
13
11
  Worker: typeof WorkerService;
14
12
  workflow: typeof WorkflowService;
15
13
  /**