@hotmeshio/hotmesh 0.0.9 → 0.0.11

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 (59) hide show
  1. package/README.md +84 -77
  2. package/build/modules/errors.d.ts +17 -1
  3. package/build/modules/errors.js +29 -1
  4. package/build/modules/key.d.ts +2 -2
  5. package/build/modules/key.js +3 -3
  6. package/build/package.json +2 -1
  7. package/build/services/activities/activity.d.ts +1 -0
  8. package/build/services/activities/activity.js +13 -2
  9. package/build/services/activities/cycle.js +6 -1
  10. package/build/services/activities/trigger.js +2 -3
  11. package/build/services/collator/index.d.ts +8 -0
  12. package/build/services/collator/index.js +11 -1
  13. package/build/services/durable/factory.d.ts +18 -1
  14. package/build/services/durable/factory.js +46 -4
  15. package/build/services/durable/handle.js +25 -7
  16. package/build/services/durable/native.js +0 -1
  17. package/build/services/durable/worker.d.ts +3 -3
  18. package/build/services/durable/worker.js +16 -11
  19. package/build/services/durable/workflow.js +1 -1
  20. package/build/services/hotmesh/index.js +1 -1
  21. package/build/services/pipe/functions/math.d.ts +4 -0
  22. package/build/services/pipe/functions/math.js +73 -0
  23. package/build/services/pipe/functions/number.d.ts +0 -4
  24. package/build/services/pipe/functions/number.js +0 -73
  25. package/build/services/signaler/stream.js +6 -3
  26. package/build/services/store/index.js +2 -2
  27. package/build/services/stream/clients/ioredis.js +1 -1
  28. package/build/services/stream/clients/redis.js +1 -1
  29. package/build/services/sub/clients/ioredis.js +1 -1
  30. package/build/services/sub/clients/redis.js +1 -1
  31. package/build/types/durable.d.ts +8 -3
  32. package/build/types/index.d.ts +1 -0
  33. package/modules/errors.ts +42 -1
  34. package/modules/key.ts +2 -2
  35. package/package.json +2 -1
  36. package/services/activities/activity.ts +14 -2
  37. package/services/activities/cycle.ts +6 -1
  38. package/services/activities/trigger.ts +2 -3
  39. package/services/collator/index.ts +12 -1
  40. package/services/durable/factory.ts +46 -4
  41. package/services/durable/handle.ts +23 -8
  42. package/services/durable/native.ts +0 -1
  43. package/services/durable/worker.ts +27 -13
  44. package/services/durable/workflow.ts +1 -1
  45. package/services/hotmesh/index.ts +2 -2
  46. package/services/pipe/functions/math.ts +74 -0
  47. package/services/pipe/functions/number.ts +0 -75
  48. package/services/signaler/stream.ts +6 -3
  49. package/services/store/index.ts +3 -3
  50. package/services/stream/clients/ioredis.ts +2 -2
  51. package/services/stream/clients/redis.ts +2 -2
  52. package/services/sub/clients/ioredis.ts +2 -2
  53. package/services/sub/clients/redis.ts +2 -2
  54. package/types/durable.ts +12 -5
  55. package/types/index.ts +15 -0
  56. package/build/services/dimension/index.d.ts +0 -29
  57. package/build/services/dimension/index.js +0 -35
  58. package/services/dimension/README.md +0 -73
  59. package/services/dimension/index.ts +0 -39
@@ -6,6 +6,7 @@ const asyncLocalStorage_1 = require("./asyncLocalStorage");
6
6
  const hotmesh_1 = require("../hotmesh");
7
7
  const stream_1 = require("../../types/stream");
8
8
  const factory_1 = require("./factory");
9
+ const errors_1 = require("../../modules/errors");
9
10
  /*
10
11
  Here is an example of how the methods in this file are used:
11
12
 
@@ -26,7 +27,6 @@ async function run() {
26
27
  });
27
28
  const worker = await Worker.create({
28
29
  connection,
29
- namespace: 'default',
30
30
  taskQueue: 'hello-world',
31
31
  workflow: workflows.example,
32
32
  activities,
@@ -40,13 +40,13 @@ run().catch((err) => {
40
40
  });
41
41
  */
42
42
  class WorkerService {
43
- static async activateWorkflow(hotMesh, topic, factory) {
43
+ static async activateWorkflow(hotMesh, topic, dagFactory, options = {}) {
44
44
  const version = '1';
45
45
  const app = await hotMesh.engine.store.getApp(topic);
46
46
  const appVersion = app?.version;
47
47
  if (!appVersion) {
48
48
  try {
49
- await hotMesh.deploy(factory(topic, version));
49
+ await hotMesh.deploy(dagFactory(topic, version, options.maxSystemRetries, options.backoffExponent));
50
50
  await hotMesh.activate(version);
51
51
  }
52
52
  catch (err) {
@@ -100,7 +100,7 @@ class WorkerService {
100
100
  worker.activityRunner = await worker.initActivityWorkflow(config, activityTopic);
101
101
  await WorkerService.activateWorkflow(worker.activityRunner, activityTopic, factory_1.getActivityYAML);
102
102
  worker.workflowRunner = await worker.initWorkerWorkflow(config, workflowTopic, workflowFunction);
103
- await WorkerService.activateWorkflow(worker.workflowRunner, workflowTopic, factory_1.getWorkflowYAML);
103
+ await WorkerService.activateWorkflow(worker.workflowRunner, workflowTopic, factory_1.getWorkflowYAML, config.options);
104
104
  return worker;
105
105
  }
106
106
  static resolveWorkflowTarget(workflow) {
@@ -152,12 +152,16 @@ class WorkerService {
152
152
  }
153
153
  catch (err) {
154
154
  this.activityRunner.engine.logger.error('durable-worker-activity-err', err);
155
+ if (!(err instanceof errors_1.DurableTimeoutError) &&
156
+ !(err instanceof errors_1.DurableMaxedError) &&
157
+ !(err instanceof errors_1.DurableFatalError)) {
158
+ err = new errors_1.DurableRetryError(err.message);
159
+ }
155
160
  return {
156
161
  status: stream_1.StreamStatus.ERROR,
157
- code: 500,
158
- message: err.message,
162
+ code: err.code,
159
163
  metadata: { ...data.metadata },
160
- data: { error: err }
164
+ data: { message: err.message }
161
165
  };
162
166
  }
163
167
  };
@@ -228,11 +232,12 @@ class WorkerService {
228
232
  };
229
233
  }
230
234
  catch (err) {
235
+ // 59* - Durable*Error
231
236
  return {
232
237
  status: stream_1.StreamStatus.ERROR,
233
- code: 500,
238
+ code: err.code || new errors_1.DurableRetryError(err.message).code,
234
239
  metadata: { ...data.metadata },
235
- data: { error: err }
240
+ data: { message: err.message, type: err.name }
236
241
  };
237
242
  }
238
243
  };
@@ -247,7 +252,7 @@ class WorkerService {
247
252
  _a = WorkerService;
248
253
  WorkerService.activityRegistry = {}; //user's activities
249
254
  WorkerService.instances = new Map();
250
- WorkerService.getHotMesh = async (worflowTopic) => {
255
+ WorkerService.getHotMesh = async (worflowTopic, options) => {
251
256
  if (WorkerService.instances.has(worflowTopic)) {
252
257
  return await WorkerService.instances.get(worflowTopic);
253
258
  }
@@ -256,7 +261,7 @@ WorkerService.getHotMesh = async (worflowTopic) => {
256
261
  engine: { redis: { ...WorkerService.connection } }
257
262
  });
258
263
  WorkerService.instances.set(worflowTopic, hotMesh);
259
- await WorkerService.activateWorkflow(await hotMesh, worflowTopic, factory_1.getWorkflowYAML);
264
+ await WorkerService.activateWorkflow(await hotMesh, worflowTopic, factory_1.getWorkflowYAML, options);
260
265
  return hotMesh;
261
266
  };
262
267
  WorkerService.Context = {
@@ -62,7 +62,7 @@ class WorkflowService {
62
62
  const client = new client_1.ClientService({
63
63
  connection: await connection_1.ConnectionService.connect(worker_1.WorkerService.connection),
64
64
  });
65
- //todo: should I allow-cross/app callback (pj:'@DURABLE@hello-world@<pjid>'/pa: <paid>/pd: <pdad>)
65
+ //todo: allow cross/app callback (pj:'@DURABLE@hello-world@<pjid>'/pa: <paid>/pd: <pdad>)
66
66
  const handle = await client.workflow.start({
67
67
  ...options,
68
68
  workflowId: `${workflowId}${options.workflowId}`,
@@ -17,7 +17,7 @@ class HotMeshService {
17
17
  }
18
18
  verifyAndSetNamespace(namespace) {
19
19
  if (!namespace) {
20
- this.namespace = key_1.PSNS;
20
+ this.namespace = key_1.HMNS;
21
21
  }
22
22
  else if (!namespace.match(/^[A-Za-z0-9-]+$/)) {
23
23
  throw new Error(`config.namespace [${namespace}] is invalid`);
@@ -1,4 +1,8 @@
1
1
  declare class MathHandler {
2
+ add(...operands: (number | number[])[]): number;
3
+ subtract(...operands: (number | number[])[]): number;
4
+ multiply(...operands: (number | number[])[]): number;
5
+ divide(...operands: (number | number[])[]): number;
2
6
  abs(x: number): number;
3
7
  acos(x: number): number;
4
8
  acosh(x: number): number;
@@ -2,6 +2,79 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MathHandler = void 0;
4
4
  class MathHandler {
5
+ add(...operands) {
6
+ // @ts-ignore
7
+ return operands.reduce((a, b) => {
8
+ if (Array.isArray(b)) {
9
+ return a + this.add(...b);
10
+ }
11
+ else {
12
+ return a + b;
13
+ }
14
+ }, 0);
15
+ }
16
+ subtract(...operands) {
17
+ if (operands.length === 0) {
18
+ throw new Error('At least one operand is required.');
19
+ }
20
+ let flatOperands = [];
21
+ operands.forEach((op) => {
22
+ if (Array.isArray(op)) {
23
+ flatOperands = [...flatOperands, ...op];
24
+ }
25
+ else {
26
+ flatOperands.push(op);
27
+ }
28
+ });
29
+ if (flatOperands.length === 0) {
30
+ throw new Error('At least one operand is required after flattening.');
31
+ }
32
+ const result = flatOperands.reduce((a, b, i) => {
33
+ return i === 0 ? a : a - b;
34
+ });
35
+ return result;
36
+ }
37
+ multiply(...operands) {
38
+ if (operands.length === 0) {
39
+ throw new Error('At least one operand is required.');
40
+ }
41
+ // @ts-ignore
42
+ return operands.reduce((a, b) => {
43
+ if (Array.isArray(b)) {
44
+ return a * this.multiply(...b);
45
+ }
46
+ else {
47
+ return a * b;
48
+ }
49
+ }, 1);
50
+ }
51
+ divide(...operands) {
52
+ if (operands.length === 0) {
53
+ throw new Error('At least one operand is required.');
54
+ }
55
+ let flatOperands = [];
56
+ operands.forEach((op) => {
57
+ if (Array.isArray(op)) {
58
+ flatOperands = [...flatOperands, ...op];
59
+ }
60
+ else {
61
+ flatOperands.push(op);
62
+ }
63
+ });
64
+ if (flatOperands.length === 0) {
65
+ throw new Error('At least one operand is required after flattening.');
66
+ }
67
+ const result = flatOperands.reduce((a, b, i) => {
68
+ if (b === 0) {
69
+ return NaN;
70
+ }
71
+ return i === 0 ? a : a / b;
72
+ });
73
+ if (isNaN(result)) {
74
+ return NaN;
75
+ }
76
+ return result;
77
+ }
5
78
  abs(x) {
6
79
  return Math.abs(x);
7
80
  }
@@ -17,9 +17,5 @@ declare class NumberHandler {
17
17
  min(...values: number[]): number;
18
18
  pow(base: number, exponent: number): number;
19
19
  round(input: number): number;
20
- add(...operands: (number | number[])[]): number;
21
- subtract(...operands: (number | number[])[]): number;
22
- multiply(...operands: (number | number[])[]): number;
23
- divide(...operands: (number | number[])[]): number;
24
20
  }
25
21
  export { NumberHandler };
@@ -56,78 +56,5 @@ class NumberHandler {
56
56
  round(input) {
57
57
  return Math.round(input);
58
58
  }
59
- add(...operands) {
60
- // @ts-ignore
61
- return operands.reduce((a, b) => {
62
- if (Array.isArray(b)) {
63
- return a + this.add(...b);
64
- }
65
- else {
66
- return a + b;
67
- }
68
- }, 0);
69
- }
70
- subtract(...operands) {
71
- if (operands.length === 0) {
72
- throw new Error('At least one operand is required.');
73
- }
74
- let flatOperands = [];
75
- operands.forEach((op) => {
76
- if (Array.isArray(op)) {
77
- flatOperands = [...flatOperands, ...op];
78
- }
79
- else {
80
- flatOperands.push(op);
81
- }
82
- });
83
- if (flatOperands.length === 0) {
84
- throw new Error('At least one operand is required after flattening.');
85
- }
86
- const result = flatOperands.reduce((a, b, i) => {
87
- return i === 0 ? a : a - b;
88
- });
89
- return result;
90
- }
91
- multiply(...operands) {
92
- if (operands.length === 0) {
93
- throw new Error('At least one operand is required.');
94
- }
95
- // @ts-ignore
96
- return operands.reduce((a, b) => {
97
- if (Array.isArray(b)) {
98
- return a * this.multiply(...b);
99
- }
100
- else {
101
- return a * b;
102
- }
103
- }, 1);
104
- }
105
- divide(...operands) {
106
- if (operands.length === 0) {
107
- throw new Error('At least one operand is required.');
108
- }
109
- let flatOperands = [];
110
- operands.forEach((op) => {
111
- if (Array.isArray(op)) {
112
- flatOperands = [...flatOperands, ...op];
113
- }
114
- else {
115
- flatOperands.push(op);
116
- }
117
- });
118
- if (flatOperands.length === 0) {
119
- throw new Error('At least one operand is required after flattening.');
120
- }
121
- const result = flatOperands.reduce((a, b, i) => {
122
- if (b === 0) {
123
- return NaN;
124
- }
125
- return i === 0 ? a : a / b;
126
- });
127
- if (isNaN(result)) {
128
- return NaN;
129
- }
130
- return result;
131
- }
132
59
  }
133
60
  exports.NumberHandler = NumberHandler;
@@ -5,7 +5,7 @@ const key_1 = require("../../modules/key");
5
5
  const utils_1 = require("../../modules/utils");
6
6
  const telemetry_1 = require("../telemetry");
7
7
  const stream_1 = require("../../types/stream");
8
- const MAX_RETRIES = 4; //max delay (10s using exponential backoff);
8
+ const MAX_RETRIES = 3; //local retry; 10, 100, 1000ms
9
9
  const MAX_TIMEOUT_MS = 60000;
10
10
  const GRADUATED_INTERVAL_MS = 5000;
11
11
  const BLOCK_DURATION = 15000; //Set to `15` so SIGINT/SIGTERM can interrupt; set to `0` to BLOCK indefinitely
@@ -158,8 +158,11 @@ class StreamSignaler {
158
158
  const policy = policies?.[errorCode];
159
159
  const maxRetries = policy?.[0];
160
160
  const tryCount = Math.min(input.metadata.try || 0, MAX_RETRIES);
161
- if (maxRetries >= tryCount) {
162
- return [true, Math.pow(10, tryCount)];
161
+ //only possible values for maxRetries are 1, 2, 3
162
+ //only possible values for tryCount are 0, 1, 2
163
+ if (maxRetries > tryCount) {
164
+ // 10ms, 100ms, or 1000ms delays between system retries
165
+ return [true, Math.pow(10, tryCount + 1)];
163
166
  }
164
167
  return [false, 0];
165
168
  }
@@ -58,7 +58,7 @@ class StoreService {
58
58
  };
59
59
  this.redisClient = redisClient;
60
60
  }
61
- async init(namespace = key_1.PSNS, appId, logger) {
61
+ async init(namespace = key_1.HMNS, appId, logger) {
62
62
  this.namespace = namespace;
63
63
  this.appId = appId;
64
64
  this.logger = logger;
@@ -111,7 +111,7 @@ class StoreService {
111
111
  if (bCreate) {
112
112
  const packageJson = await Promise.resolve().then(() => __importStar(require('../../package.json')));
113
113
  const version = packageJson['version'] || '0.0.0';
114
- settings = { namespace: key_1.PSNS, version };
114
+ settings = { namespace: key_1.HMNS, version };
115
115
  await this.setSettings(settings);
116
116
  return settings;
117
117
  }
@@ -7,7 +7,7 @@ class IORedisStreamService extends index_1.StreamService {
7
7
  constructor(redisClient) {
8
8
  super(redisClient);
9
9
  }
10
- async init(namespace = key_1.PSNS, appId, logger) {
10
+ async init(namespace = key_1.HMNS, appId, logger) {
11
11
  this.namespace = namespace;
12
12
  this.logger = logger;
13
13
  this.appId = appId;
@@ -7,7 +7,7 @@ class RedisStreamService extends index_1.StreamService {
7
7
  constructor(redisClient) {
8
8
  super(redisClient);
9
9
  }
10
- async init(namespace = key_1.PSNS, appId, logger) {
10
+ async init(namespace = key_1.HMNS, appId, logger) {
11
11
  this.namespace = namespace;
12
12
  this.logger = logger;
13
13
  this.appId = appId;
@@ -7,7 +7,7 @@ class IORedisSubService extends index_1.SubService {
7
7
  constructor(redisClient) {
8
8
  super(redisClient);
9
9
  }
10
- async init(namespace = key_1.PSNS, appId, engineId, logger) {
10
+ async init(namespace = key_1.HMNS, appId, engineId, logger) {
11
11
  this.namespace = namespace;
12
12
  this.logger = logger;
13
13
  this.appId = appId;
@@ -7,7 +7,7 @@ class RedisSubService extends index_1.SubService {
7
7
  constructor(redisClient) {
8
8
  super(redisClient);
9
9
  }
10
- async init(namespace = key_1.PSNS, appId, engineId, logger) {
10
+ async init(namespace = key_1.HMNS, appId, engineId, logger) {
11
11
  this.namespace = namespace;
12
12
  this.logger = logger;
13
13
  this.appId = appId;
@@ -7,7 +7,7 @@ type WorkflowOptions = {
7
7
  workflowTrace?: string;
8
8
  workflowSpan?: string;
9
9
  };
10
- type ActivityDataType = {
10
+ type ActivityWorkflowDataType = {
11
11
  activityName: string;
12
12
  arguments: any[];
13
13
  workflowId: string;
@@ -32,9 +32,14 @@ type Registry = {
32
32
  };
33
33
  type WorkerConfig = {
34
34
  connection: Connection;
35
- namespace: string;
35
+ namespace?: string;
36
36
  taskQueue: string;
37
37
  workflow: Function;
38
+ options?: WorkerOptions;
39
+ };
40
+ type WorkerOptions = {
41
+ maxSystemRetries?: number;
42
+ backoffExponent?: number;
38
43
  };
39
44
  type ContextType = {
40
45
  workflowId: string;
@@ -54,4 +59,4 @@ type ActivityConfig = {
54
59
  maximumInterval: string;
55
60
  };
56
61
  };
57
- export { ActivityConfig, ActivityDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, WorkerConfig, WorkflowDataType, WorkflowOptions, };
62
+ export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, WorkerConfig, WorkerOptions, WorkflowDataType, WorkflowOptions, };
@@ -3,6 +3,7 @@ export { App, AppVID, AppTransitions, AppSubscriptions } from './app';
3
3
  export { AsyncSignal } from './async';
4
4
  export { CacheMode } from './cache';
5
5
  export { CollationFaultType, CollationStage } from './collator';
6
+ export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, WorkerConfig, WorkerOptions, WorkflowDataType, WorkflowOptions, } from './durable';
6
7
  export { HookCondition, HookConditions, HookGate, HookInterface, HookRule, HookRules, HookSignal } from './hook';
7
8
  export { RedisClientType as IORedisClientType, RedisMultiType as IORedisMultiType } from './ioredisclient';
8
9
  export { ILogger } from './logger';
package/modules/errors.ts CHANGED
@@ -12,6 +12,35 @@ class SetStateError extends Error {
12
12
  }
13
13
  }
14
14
 
15
+ class DurableTimeoutError extends Error {
16
+ code: number;
17
+ constructor(message: string) {
18
+ super(message);
19
+ this.code = 596;
20
+ }
21
+ }
22
+ class DurableMaxedError extends Error {
23
+ code: number;
24
+ constructor(message: string) {
25
+ super(message);
26
+ this.code = 597;
27
+ }
28
+ }
29
+ class DurableFatalError extends Error {
30
+ code: number;
31
+ constructor(message: string) {
32
+ super(message);
33
+ this.code = 598;
34
+ }
35
+ }
36
+ class DurableRetryError extends Error {
37
+ code: number;
38
+ constructor(message: string) {
39
+ super(message);
40
+ this.code = 599;
41
+ }
42
+ }
43
+
15
44
  class MapDataError extends Error {
16
45
  constructor() {
17
46
  super("Error occurred while mapping data");
@@ -52,4 +81,16 @@ class CollationError extends Error {
52
81
  }
53
82
  }
54
83
 
55
- export { CollationError, DuplicateJobError, GetStateError, SetStateError, MapDataError, RegisterTimeoutError, ExecActivityError };
84
+ export {
85
+ CollationError,
86
+ DurableTimeoutError,
87
+ DurableMaxedError,
88
+ DurableFatalError,
89
+ DurableRetryError,
90
+ DuplicateJobError,
91
+ GetStateError,
92
+ SetStateError,
93
+ MapDataError,
94
+ RegisterTimeoutError,
95
+ ExecActivityError
96
+ };
package/modules/key.ts CHANGED
@@ -27,7 +27,7 @@
27
27
  */
28
28
 
29
29
  //default namespace for hotmesh
30
- const PSNS = "hmsh";
30
+ const HMNS = "hmsh";
31
31
 
32
32
  //these are the entity types that are stored in the key/value store
33
33
  enum KeyType {
@@ -126,4 +126,4 @@ class KeyService {
126
126
  }
127
127
  }
128
128
 
129
- export { KeyService, KeyType, KeyStoreParams, PSNS };
129
+ export { KeyService, KeyType, KeyStoreParams, HMNS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Durable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -43,6 +43,7 @@
43
43
  "test:durable:hello": "NODE_ENV=test jest ./tests/durable/helloworld/index.test.ts --detectOpenHandles --forceExit --verbose",
44
44
  "test:durable:goodbye": "NODE_ENV=test jest ./tests/durable/goodbye/index.test.ts --detectOpenHandles --forceExit --verbose",
45
45
  "test:durable:retry": "NODE_ENV=test jest ./tests/durable/retry/index.test.ts --detectOpenHandles --forceExit --verbose",
46
+ "test:durable:fatal": "NODE_ENV=test jest ./tests/durable/fatal/index.test.ts --detectOpenHandles --forceExit --verbose",
46
47
  "test:durable:loopactivity": "NODE_ENV=test jest ./tests/durable/loopactivity/index.test.ts --detectOpenHandles --forceExit --verbose",
47
48
  "test:durable:nested": "NODE_ENV=test jest ./tests/durable/nested/index.test.ts --detectOpenHandles --forceExit --verbose"
48
49
  },
@@ -4,7 +4,6 @@ import {
4
4
  getValueByPath,
5
5
  restoreHierarchy } from '../../modules/utils';
6
6
  import { CollatorService } from '../collator';
7
- import { DimensionService } from '../dimension';
8
7
  import { EngineService } from '../engine';
9
8
  import { ILogger } from '../logger';
10
9
  import { MapperService } from '../mapper';
@@ -84,6 +83,7 @@ class Activity {
84
83
  if (this.doesHook()) {
85
84
  //sleep and wait to awaken upon a signal
86
85
  await this.registerHook(multi);
86
+ this.mapOutputData();
87
87
  this.mapJobData();
88
88
  await this.setState(multi);
89
89
  await CollatorService.authorizeReentry(this, multi);
@@ -94,6 +94,7 @@ class Activity {
94
94
  } else {
95
95
  //end the activity and transition to its children
96
96
  this.adjacencyList = await this.filterAdjacent();
97
+ this.mapOutputData();
97
98
  this.mapJobData();
98
99
  await this.setState(multi);
99
100
  await CollatorService.notarizeEarlyCompletion(this, multi);
@@ -322,6 +323,17 @@ class Activity {
322
323
  }
323
324
  }
324
325
 
326
+ mapOutputData(): void {
327
+ //activity YAML may include output map data that produces/extends activity output data.
328
+ if(this.config.output?.maps) {
329
+ const mapper = new MapperService(this.config.output.maps, this.context);
330
+ const actOutData = mapper.mapRules();
331
+ const activityId = this.metadata.aid;
332
+ const data = { ...this.context[activityId].output, ...actOutData };
333
+ this.context[activityId].output.data = data;
334
+ }
335
+ }
336
+
325
337
  async registerTimeout(): Promise<void> {
326
338
  //set timeout in support of hook and/or duplex
327
339
  }
@@ -526,7 +538,7 @@ class Activity {
526
538
 
527
539
  resolveAdjacentDad(): string {
528
540
  //concat self and child dimension (all children (leg 1) begin life at 0)
529
- return `${this.resolveDad()}${DimensionService.getSeed(0)}`;
541
+ return `${this.resolveDad()}${CollatorService.getDimensionalSeed(0)}`;
530
542
  };
531
543
 
532
544
  async filterAdjacent(): Promise<StreamData[]> {
@@ -81,13 +81,18 @@ class Cycle extends Activity {
81
81
  * pattern allows for retries without violating the DAG.
82
82
  */
83
83
  async cycleAncestorActivity(multi: RedisMulti): Promise<string> {
84
+ //Cycle activity L1 is a standin for the target ancestor L1.
85
+ //Input data mapping (mapInputData) allows for the
86
+ //next dimensonal thread to execute with different
87
+ //input data than the current dimensional thread
88
+ this.mapInputData();
84
89
  const streamData: StreamData = {
85
90
  metadata: {
86
91
  dad: CollatorService.resolveReentryDimension(this),
87
92
  jid: this.context.metadata.jid,
88
93
  aid: this.config.ancestor,
89
94
  },
90
- data: {} //todo: verify immutability, before enabling: `this.context.data`
95
+ data: this.context.data
91
96
  };
92
97
  return (await this.engine.streamSignaler?.publishMessage(null, streamData, multi)) as string;
93
98
  }
@@ -3,7 +3,6 @@ import { DuplicateJobError } from '../../modules/errors';
3
3
  import { formatISODate, getTimeSeries } from '../../modules/utils';
4
4
  import { Activity } from './activity';
5
5
  import { CollatorService } from '../collator';
6
- import { DimensionService } from '../dimension';
7
6
  import { EngineService } from '../engine';
8
7
  import { Pipe } from '../pipe';
9
8
  import { ReporterService } from '../reporter';
@@ -99,7 +98,7 @@ class Trigger extends Activity {
99
98
 
100
99
  const utc = formatISODate(new Date());
101
100
  const { id, version } = await this.engine.getVID();
102
- this.initDimensionalAddress(DimensionService.getSeed());
101
+ this.initDimensionalAddress(CollatorService.getDimensionalSeed());
103
102
  const activityMetadata = {
104
103
  ...this.metadata,
105
104
  jid: jobId,
@@ -119,7 +118,7 @@ class Trigger extends Activity {
119
118
  trc: this.context.metadata.trc,
120
119
  spn: this.context.metadata.spn,
121
120
  jid: jobId,
122
- dad: DimensionService.getSeed(), //top-level job implicitly uses `,0`
121
+ dad: CollatorService.getDimensionalSeed(), //top-level job implicitly uses `,0`
123
122
  key: jobKey,
124
123
  jc: utc,
125
124
  ju: utc,
@@ -85,7 +85,7 @@ class CollatorService {
85
85
  static async notarizeCompletion(activity: Activity, multi?: RedisMulti): Promise<number> {
86
86
  //1) ALWAYS actualize leg2 dimension (+1)
87
87
  //2) IF the activity is used in a cycle, don't close leg 2!
88
- const decrement = activity.config.cycle ? 0 : -1_000_000_000_000;
88
+ const decrement = activity.config.cycle ? 0 : 1_000_000_000_000;
89
89
  return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1 - decrement, this.getDimensionalAddress(activity), multi);
90
90
  };
91
91
 
@@ -234,6 +234,17 @@ class CollatorService {
234
234
  static isActivityComplete(status: number): boolean {
235
235
  return (status - 0) <= 0;
236
236
  }
237
+
238
+ /**
239
+ * All activities exist on a dimensional plane. Zero
240
+ * is the default. A value of
241
+ * `AxY,0,0,0,0,1,0,0` would reflect that
242
+ * an ancestor activity was dimensionalized beyond
243
+ * the default.
244
+ */
245
+ static getDimensionalSeed(index = 0): string {
246
+ return `,${index}`;
247
+ }
237
248
  }
238
249
 
239
250
  export { CollatorService };