@hotmeshio/hotmesh 0.0.2 → 0.0.4

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 (42) hide show
  1. package/README.md +13 -13
  2. package/build/package.json +3 -3
  3. package/build/services/activities/activity.d.ts +2 -0
  4. package/build/services/activities/activity.js +27 -9
  5. package/build/services/activities/worker.js +1 -0
  6. package/build/services/collator/index.d.ts +7 -5
  7. package/build/services/collator/index.js +29 -6
  8. package/build/services/compiler/deployer.d.ts +4 -3
  9. package/build/services/compiler/deployer.js +14 -0
  10. package/build/services/dimension/index.d.ts +1 -1
  11. package/build/services/durable/client.d.ts +3 -3
  12. package/build/services/durable/client.js +10 -1
  13. package/build/services/durable/handle.d.ts +1 -1
  14. package/build/services/durable/worker.js +20 -2
  15. package/build/services/engine/index.d.ts +1 -1
  16. package/build/services/engine/index.js +22 -5
  17. package/build/services/hotmesh/index.d.ts +1 -1
  18. package/build/services/hotmesh/index.js +2 -2
  19. package/build/services/serializer/index.d.ts +6 -1
  20. package/build/services/serializer/index.js +55 -22
  21. package/build/services/signaler/stream.js +1 -0
  22. package/build/services/store/index.d.ts +3 -3
  23. package/build/services/store/index.js +16 -24
  24. package/build/types/activity.d.ts +1 -0
  25. package/build/types/hotmesh.d.ts +5 -5
  26. package/package.json +3 -3
  27. package/services/activities/activity.ts +28 -9
  28. package/services/activities/worker.ts +1 -0
  29. package/services/collator/index.ts +38 -13
  30. package/services/compiler/deployer.ts +24 -9
  31. package/services/dimension/index.ts +1 -1
  32. package/services/durable/client.ts +13 -6
  33. package/services/durable/handle.ts +2 -2
  34. package/services/durable/worker.ts +16 -2
  35. package/services/engine/index.ts +23 -5
  36. package/services/hotmesh/index.ts +2 -2
  37. package/services/mapper/index.ts +0 -1
  38. package/services/serializer/index.ts +57 -22
  39. package/services/signaler/stream.ts +1 -0
  40. package/services/store/index.ts +15 -23
  41. package/types/activity.ts +1 -0
  42. package/types/hotmesh.ts +5 -5
@@ -20,9 +20,45 @@ exports.MDATA_SYMBOLS = {
20
20
  };
21
21
  class SerializerService {
22
22
  constructor() {
23
- this.resetSymbols({}, {});
23
+ this.resetSymbols({}, {}, {});
24
24
  }
25
- resetSymbols(symKeys, symVals) {
25
+ abbreviate(consumes, symbolNames, fields = []) {
26
+ for (const symbolName of symbolNames) {
27
+ const symbolSet = this.symKeys.get(symbolName);
28
+ const symbolPaths = consumes[symbolName];
29
+ for (const symbolPath of symbolPaths) {
30
+ const abbreviation = symbolSet.get(symbolPath);
31
+ if (abbreviation) {
32
+ const dimensionalIndex = this.resolveDimensionalIndex(symbolPath);
33
+ fields.push(`${abbreviation}${dimensionalIndex}`);
34
+ }
35
+ else {
36
+ fields.push(symbolPath);
37
+ }
38
+ }
39
+ }
40
+ return fields;
41
+ }
42
+ resolveDimensionalIndex(path) {
43
+ if (this.isJobPath(path)) {
44
+ return '';
45
+ }
46
+ else {
47
+ const [activityId] = path.split('/');
48
+ if (activityId in this.dIds) {
49
+ return this.dIds[activityId];
50
+ }
51
+ else if ('$ADJACENT' in this.dIds) {
52
+ //else=> pre-authorizing adjacent activity entry
53
+ return this.dIds['$ADJACENT'];
54
+ }
55
+ return ',0';
56
+ }
57
+ }
58
+ isJobPath(path) {
59
+ return path.startsWith('data/') || path.startsWith('metadata/');
60
+ }
61
+ resetSymbols(symKeys, symVals, dIds) {
26
62
  this.symKeys = new Map();
27
63
  this.symReverseKeys = new Map();
28
64
  for (const id in symKeys) {
@@ -30,6 +66,7 @@ class SerializerService {
30
66
  }
31
67
  this.symValMaps = new Map(Object.entries(symVals));
32
68
  this.symValReverseMaps = this.getReverseValueMap(this.symValMaps);
69
+ this.dIds = dIds;
33
70
  }
34
71
  getReverseKeyMap(keyMap, id) {
35
72
  let map = this.symReverseKeys.get(id);
@@ -70,24 +107,22 @@ class SerializerService {
70
107
  if (this.symKeys.size === 0) {
71
108
  return document;
72
109
  }
73
- let result = { ...document };
74
- const compressWithMap = (abbreviationMap) => {
75
- for (let key in result) {
76
- let safeKey = abbreviationMap.get(key) || key;
77
- let value = result[key];
78
- let safeValue = abbreviationMap.get(value) || value;
79
- if (safeKey !== key || safeValue !== value) {
80
- result[safeKey] = safeValue;
81
- if (safeKey !== key) {
82
- delete result[key];
83
- }
110
+ let source = { ...document };
111
+ let result = {};
112
+ const compressWithMap = (abbreviationMap, id) => {
113
+ for (let key in source) {
114
+ if (key.startsWith(`${id}/`) || (id.startsWith('$') && ['data', 'metadata'].includes(key.split('/')[0]))) {
115
+ const dimensionalIndex = this.resolveDimensionalIndex(key);
116
+ let shortKey = abbreviationMap.get(key) || key;
117
+ const shortDimensionalKey = `${shortKey}${dimensionalIndex}`;
118
+ result[shortDimensionalKey] = source[key];
84
119
  }
85
120
  }
86
121
  };
87
122
  for (let id of ids) {
88
123
  const abbreviationMap = this.symKeys.get(id);
89
124
  if (abbreviationMap) {
90
- compressWithMap(abbreviationMap);
125
+ compressWithMap(abbreviationMap, id);
91
126
  }
92
127
  }
93
128
  return result;
@@ -100,14 +135,12 @@ class SerializerService {
100
135
  const inflateWithMap = (abbreviationMap, id) => {
101
136
  const reversedAbbreviationMap = this.getReverseKeyMap(abbreviationMap, id);
102
137
  for (let key in result) {
103
- let safeKey = reversedAbbreviationMap.get(key) || key;
104
- let value = result[key];
105
- let safeValue = reversedAbbreviationMap.get(value) || value;
106
- if (safeKey !== key || safeValue !== value) {
107
- result[safeKey] = safeValue;
108
- if (safeKey !== key) {
109
- delete result[key];
110
- }
138
+ //strip dimensional index from key
139
+ const shortKey = key.split(',')[0];
140
+ let longKey = reversedAbbreviationMap.get(shortKey);
141
+ if (longKey) {
142
+ result[longKey] = result[key];
143
+ delete result[key];
111
144
  }
112
145
  }
113
146
  };
@@ -121,6 +121,7 @@ class StreamSignaler {
121
121
  output = await callback(input);
122
122
  }
123
123
  catch (err) {
124
+ console.error(err);
124
125
  this.logger.error(`stream-call-function-error`, { stream, id, err });
125
126
  output = this.structureUnhandledError(input, err);
126
127
  }
@@ -59,9 +59,9 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
59
59
  getJobIds(indexKeys: string[], idRange: [number, number]): Promise<IdsData>;
60
60
  setStatus(collationKeyStatus: number, jobId: string, appId: string, multi?: U): Promise<any>;
61
61
  getStatus(jobId: string, appId: string): Promise<number>;
62
- setState({ ...state }: StringAnyType, status: number | null, jobId: string, appId: string, symbolNames: string[], multi?: U): Promise<string>;
63
- getState(jobId: string, consumes: Consumes): Promise<[StringAnyType, number] | undefined>;
64
- collate(jobId: string, activityId: string, amount: number, multi?: U): Promise<number>;
62
+ setState({ ...state }: StringAnyType, status: number | null, jobId: string, symbolNames: string[], dIds: StringStringType, multi?: U): Promise<string>;
63
+ getState(jobId: string, consumes: Consumes, dIds: StringStringType): Promise<[StringAnyType, number] | undefined>;
64
+ collate(jobId: string, activityId: string, amount: number, dIds: StringStringType, multi?: U): Promise<number>;
65
65
  setStateNX(jobId: string, appId: string): Promise<boolean>;
66
66
  getSchema(activityId: string, appVersion: AppVID): Promise<ActivityType>;
67
67
  getSchemas(appVersion: AppVID): Promise<Record<string, ActivityType>>;
@@ -238,7 +238,12 @@ class StoreService {
238
238
  app = {};
239
239
  for (const field in sApp) {
240
240
  try {
241
- app[field] = sApp[field];
241
+ if (field === 'active') {
242
+ app[field] = sApp[field] === 'true';
243
+ }
244
+ else {
245
+ app[field] = sApp[field];
246
+ }
242
247
  }
243
248
  catch (e) {
244
249
  app[field] = sApp[field];
@@ -369,12 +374,12 @@ class StoreService {
369
374
  const status = await this.redisClient[this.commands.hget](jobKey, ':');
370
375
  return Number(status);
371
376
  }
372
- async setState({ ...state }, status, jobId, appId, symbolNames, multi) {
377
+ async setState({ ...state }, status, jobId, symbolNames, dIds, multi) {
373
378
  delete state['metadata/js'];
374
- const hashKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
379
+ const hashKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
375
380
  const symKeys = await this.getSymbolKeys(symbolNames);
376
381
  const symVals = await this.getSymbolValues();
377
- this.serializer.resetSymbols(symKeys, symVals);
382
+ this.serializer.resetSymbols(symKeys, symVals, dIds);
378
383
  const hashData = this.serializer.package(state, symbolNames);
379
384
  if (status !== null) {
380
385
  hashData[':'] = status.toString();
@@ -385,26 +390,13 @@ class StoreService {
385
390
  await (multi || this.redisClient)[this.commands.hset](hashKey, hashData);
386
391
  return jobId;
387
392
  }
388
- async getState(jobId, consumes) {
389
- //todo: refactor into semantic submethods
393
+ async getState(jobId, consumes, dIds) {
394
+ //get abbreviated field list (the symbols for the paths)
390
395
  const key = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
391
396
  const symbolNames = Object.keys(consumes);
392
397
  const symKeys = await this.getSymbolKeys(symbolNames);
393
- //always fetch the job status (':') when fetching state
394
- const fields = [':'];
395
- for (const symbolName of symbolNames) {
396
- const symbolSet = symKeys[symbolName];
397
- const symbolPaths = consumes[symbolName];
398
- for (const symbolPath of symbolPaths) {
399
- const abbreviation = symbolSet[symbolPath];
400
- if (abbreviation) {
401
- fields.push(abbreviation);
402
- }
403
- else {
404
- fields.push(symbolPath);
405
- }
406
- }
407
- }
398
+ this.serializer.resetSymbols(symKeys, {}, dIds);
399
+ const fields = this.serializer.abbreviate(consumes, symbolNames, [':']);
408
400
  const jobDataArray = await this.redisClient[this.commands.hmget](key, fields);
409
401
  const jobData = {};
410
402
  let atLeast1 = false; //if status field (':') isn't present assume 404
@@ -416,7 +408,7 @@ class StoreService {
416
408
  });
417
409
  if (atLeast1) {
418
410
  const symVals = await this.getSymbolValues();
419
- this.serializer.resetSymbols(symKeys, symVals);
411
+ this.serializer.resetSymbols(symKeys, symVals, dIds);
420
412
  const state = this.serializer.unpackage(jobData, symbolNames);
421
413
  let status = 0;
422
414
  if (state[':']) {
@@ -427,13 +419,13 @@ class StoreService {
427
419
  return [state, status];
428
420
  }
429
421
  }
430
- async collate(jobId, activityId, amount, multi) {
422
+ async collate(jobId, activityId, amount, dIds, multi) {
431
423
  const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
432
424
  const collationKey = `${activityId}/output/metadata/as`; //activity state
433
425
  const symbolNames = [activityId];
434
426
  const symKeys = await this.getSymbolKeys(symbolNames);
435
427
  const symVals = await this.getSymbolValues();
436
- this.serializer.resetSymbols(symKeys, symVals);
428
+ this.serializer.resetSymbols(symKeys, symVals, dIds);
437
429
  const payload = { [collationKey]: amount.toString() };
438
430
  const hashData = this.serializer.package(payload, symbolNames);
439
431
  const targetId = Object.keys(hashData)[0];
@@ -23,6 +23,7 @@ interface BaseActivity {
23
23
  subscribes?: string;
24
24
  trigger?: string;
25
25
  parent?: string;
26
+ ancestors?: string[];
26
27
  }
27
28
  interface Measure {
28
29
  measure: MetricTypes;
@@ -1,8 +1,8 @@
1
- import { ILogger } from "../services/logger";
2
- import { HotMeshService } from "../services/hotmesh";
3
- import { HookRules } from "./hook";
4
- import { RedisClass, RedisClient, RedisOptions } from "./redis";
5
- import { StreamData, StreamDataResponse } from "./stream";
1
+ import { ILogger } from '../services/logger';
2
+ import { HotMeshService } from '../services/hotmesh';
3
+ import { HookRules } from './hook';
4
+ import { RedisClass, RedisClient, RedisOptions } from './redis';
5
+ import { StreamData, StreamDataResponse } from './stream';
6
6
  type HotMesh = typeof HotMeshService;
7
7
  type RedisConfig = {
8
8
  class: RedisClass;
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Durable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/hotmeshio/hotmesh.git"
9
+ "url": "https://github.com/hotmeshio/sdk-typescript.git"
10
10
  },
11
- "homepage": "https://github.com/hotmeshio/hotmesh#readme",
11
+ "homepage": "https://github.com/hotmeshio/sdk-typescript#readme",
12
12
  "publishConfig": {
13
13
  "access": "public"
14
14
  },
@@ -142,7 +142,8 @@ class Activity {
142
142
  const durationInSeconds = Pipe.resolve(this.config.sleep, this.context);
143
143
  const jobId = this.context.metadata.jid;
144
144
  const activityId = this.metadata.aid;
145
- await this.engine.task.registerTimeHook(jobId, activityId, 'sleep', durationInSeconds);
145
+ const dId = this.metadata.dad;
146
+ await this.engine.task.registerTimeHook(jobId, `${activityId}${dId||''}`, 'sleep', durationInSeconds);
146
147
  return jobId;
147
148
  }
148
149
  }
@@ -206,6 +207,7 @@ class Activity {
206
207
  telemetry.setActivityAttributes(attrs);
207
208
  return jobStatus as number;
208
209
  } catch (error) {
210
+ console.error('this error?', error);
209
211
  this.logger.error('engine-process-hook-event-error', error);
210
212
  telemetry.setActivityError(error.message);
211
213
  throw error;
@@ -278,12 +280,11 @@ class Activity {
278
280
  }
279
281
 
280
282
  bindDimensionalAddress(state: StringAnyType) {
281
- const { aid, dad } = this.metadata;
282
- state[`${aid}/output/metadata/dad`] = dad;
283
+ const dad = this.resolveDad();
284
+ state[`${this.metadata.aid}/output/metadata/dad`] = dad;
283
285
  }
284
286
 
285
287
  async setState(multi?: RedisMulti): Promise<string> {
286
- const { id: appId } = await this.engine.getVID();
287
288
  const jobId = this.context.metadata.jid;
288
289
  this.bindJobMetadata();
289
290
  this.bindActivityMetadata();
@@ -298,7 +299,8 @@ class Activity {
298
299
  this.metadata.aid,
299
300
  ...presets
300
301
  ];
301
- return await this.store.setState(state, this.getJobStatus(), jobId, appId, symbolNames, multi);
302
+ const dIds = CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], this.resolveDad());
303
+ return await this.store.setState(state, this.getJobStatus(), jobId, symbolNames, dIds, multi);
302
304
  }
303
305
 
304
306
  bindJobMetadata(): void {
@@ -314,6 +316,7 @@ class Activity {
314
316
  if (this.status === StreamStatus.ERROR) {
315
317
  self.output.metadata.err = JSON.stringify(this.data);
316
318
  }
319
+ //todo: verify leg2 never overwrites leg1 `ac`
317
320
  self.output.metadata.ac =
318
321
  self.output.metadata.au = formatISODate(new Date());
319
322
  self.output.metadata.atp = this.config.type;
@@ -386,10 +389,11 @@ class Activity {
386
389
  }
387
390
  }
388
391
  TelemetryService.addTargetTelemetryPaths(consumes, this.config, this.metadata, this.leg);
389
- const { dad, jid } = this.context.metadata;
392
+ let { dad, jid } = this.context.metadata;
390
393
  jobId = jobId || jid;
391
394
  //`state` is a flat hash
392
- const [state, status] = await this.store.getState(jobId, consumes);
395
+ const dIds = CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad);
396
+ const [state, status] = await this.store.getState(jobId, consumes, dIds);
393
397
  //`context` is a tree
394
398
  this.context = restoreHierarchy(state) as JobState;
395
399
  this.initDimensionalAddress(dad);
@@ -429,11 +433,26 @@ class Activity {
429
433
  this.context[this.metadata.aid][type].data = this.data;
430
434
  }
431
435
 
436
+ resolveDad(): string {
437
+ let dad = this.metadata.dad;
438
+ if (this.adjacentIndex > 0) {
439
+ //if adjacent index > 0 the activity is cycling; replace last index with cycle index
440
+ dad = `${dad.substring(0, dad.lastIndexOf(','))},${this.adjacentIndex}`
441
+ }
442
+ return dad;
443
+ }
444
+
445
+ resolveAdjacentDad(): string {
446
+ //concat self and child dimension (all children (leg 1) begin life at 0)
447
+ return `${this.resolveDad()}${DimensionService.getSeed(0)}`;
448
+ };
449
+
432
450
  async filterAdjacent(): Promise<StreamData[]> {
433
451
  const adjacencyList: StreamData[] = [];
434
452
  const transitions = await this.store.getTransitions(await this.engine.getVID());
435
453
  const transition = transitions[`.${this.metadata.aid}`];
436
- const adjacentSuffix = DimensionService.getSeed(this.adjacentIndex);
454
+ //resolve the dimensional address for adjacent children
455
+ const adjacentDad = this.resolveAdjacentDad();
437
456
  if (transition) {
438
457
  for (const toActivityId in transition) {
439
458
  const transitionRule: boolean | TransitionRule = transition[toActivityId];
@@ -441,7 +460,7 @@ class Activity {
441
460
  adjacencyList.push({
442
461
  metadata: {
443
462
  jid: this.context.metadata.jid,
444
- dad: `${this.metadata.dad}${adjacentSuffix}`,
463
+ dad: adjacentDad,
445
464
  aid: toActivityId,
446
465
  spn: this.context['$self'].output.metadata?.l2s,
447
466
  trc: this.context.metadata.trc,
@@ -62,6 +62,7 @@ class Worker extends Activity {
62
62
  if (error instanceof GetStateError) {
63
63
  this.logger.error('worker-get-state-error', error);
64
64
  } else {
65
+ console.error(error);
65
66
  this.logger.error('worker-process-error', error);
66
67
  }
67
68
  telemetry.setActivityError(error.message);
@@ -1,18 +1,28 @@
1
- import { CollationError } from "../../modules/errors";
2
- import { RedisMulti } from "../../types/redis";
3
- import { CollationFaultType, CollationStage } from "../../types/collator";
4
- import { ActivityDuplex } from "../../types/activity";
5
- import { HotMeshGraph } from "../../types/hotmesh";
6
- import { Activity } from "../activities/activity";
1
+ import { CollationError } from '../../modules/errors';
2
+ import { RedisMulti } from '../../types/redis';
3
+ import { CollationFaultType, CollationStage } from '../../types/collator';
4
+ import { ActivityDuplex } from '../../types/activity';
5
+ import { HotMeshGraph } from '../../types/hotmesh';
6
+ import { Activity } from '../activities/activity';
7
7
 
8
8
  class CollatorService {
9
9
 
10
10
  //max int digit count that supports `hincrby`
11
- static targetLength = 15;
11
+ static targetLength = 15;
12
+
13
+ static getDimensionalAddress(activity: Activity): Record<string, string> {
14
+ let dad = activity.context.metadata.dad || activity.metadata.dad;
15
+ //todo: unsure about this reset
16
+ // if (dad && activity.leg === 2) {
17
+ // console.log('setting dad index back to 0=>', dad);
18
+ // dad = `${dad.substring(0, dad.lastIndexOf(','))},0`;
19
+ // }
20
+ return CollatorService.getDimensionsById([...activity.config.ancestors, activity.metadata.aid], dad);
21
+ }
12
22
 
13
23
  static async notarizeEntry(activity: Activity, multi?: RedisMulti): Promise<number> {
14
24
  //decrement by -100_000_000_000_000
15
- const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, -100_000_000_000_000, multi);
25
+ const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, -100_000_000_000_000, this.getDimensionalAddress(activity), multi);
16
26
  this.verifyInteger(amount, 1, 'enter');
17
27
  return amount;
18
28
  };
@@ -20,31 +30,31 @@ class CollatorService {
20
30
  static async authorizeReentry(activity: Activity, multi?: RedisMulti): Promise<number> {
21
31
  //set second digit to 8, allowing for re-entry
22
32
  //decrement by -10_000_000_000_000
23
- const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, -10_000_000_000_000, multi);
33
+ const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, -10_000_000_000_000, this.getDimensionalAddress(activity), multi);
24
34
  //this.verifyInteger(amount, 1, 'exit');
25
35
  return amount;
26
36
  }
27
37
 
28
38
  static async notarizeEarlyCompletion(activity: Activity, multi?: RedisMulti): Promise<number> {
29
39
  //initialize both `possible` (1m) and `actualized` (1) zero dimension, while decrementing the 2nd and 3rd digits to deactivate the activity
30
- return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1_000_001 - 11_000_000_000_000, multi);
40
+ return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1_000_001 - 11_000_000_000_000, this.getDimensionalAddress(activity), multi);
31
41
  };
32
42
 
33
43
  static async notarizeReentry(activity: Activity, multi?: RedisMulti): Promise<number> {
34
44
  //increment by 1_000_000 (indicates re-entry and is used to drive the 'dimensional address' for adjacent activities (minus 1))
35
- const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1_000_000, multi);
45
+ const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1_000_000, this.getDimensionalAddress(activity), multi);
36
46
  this.verifyInteger(amount, 2, 'enter');
37
47
  return amount;
38
48
  };
39
49
 
40
50
  static async notarizeContinuation(activity: Activity, multi?: RedisMulti): Promise<number> {
41
51
  //keep open; actualize the leg2 dimension (+1)
42
- return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1, multi);
52
+ return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1, this.getDimensionalAddress(activity), multi);
43
53
  };
44
54
 
45
55
  static async notarizeCompletion(activity: Activity, multi?: RedisMulti): Promise<number> {
46
56
  //close out; actualize leg2 dimension (+1) and decrement the 3rd digit (-1_000_000_000_000)
47
- return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1 - 1_000_000_000_000, multi);
57
+ return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1 - 1_000_000_000_000, this.getDimensionalAddress(activity), multi);
48
58
  };
49
59
 
50
60
  static getDigitAtIndex(num: number, targetDigitIndex: number): number | null {
@@ -112,6 +122,21 @@ class CollatorService {
112
122
  }
113
123
  }
114
124
 
125
+ static getDimensionsById(ancestors: string[], dad: string): Record<string, string> {
126
+ //ancestors is an ordered list of all ancestors, starting with the trigger (['t1', 'a1', 'a2'])
127
+ //dad is the dimensional address of the ancestors list (',0,5,3')
128
+ //loop through the ancestors list and create a map of the ancestor to the dimensional address.
129
+ //return { 't1': ',0', 'a1': ',0,5', 'a1': ',0,5,3', $ADJACENT: ',0,5,3,0' };
130
+ // `adjacent` is a special key that is used to track the dimensional address of adjacent activities
131
+ const map: Record<string, string> = { '$ADJACENT': `${dad},0` };
132
+ let dadStr = dad;
133
+ ancestors.reverse().forEach((ancestor) => {
134
+ map[ancestor] = dadStr;
135
+ dadStr = dadStr.substring(0, dadStr.lastIndexOf(','));
136
+ });
137
+ return map;
138
+ }
139
+
115
140
  /**
116
141
  * All non-trigger activities are assigned a status seed by their parent
117
142
  */
@@ -1,13 +1,13 @@
1
- import { KeyStoreParams, KeyType } from "../../modules/key";
2
- import { getSymKey } from "../../modules/utils";
3
- import { CollatorService } from "../collator";
4
- import { SerializerService } from "../serializer";
1
+ import { KeyStoreParams, KeyType } from '../../modules/key';
2
+ import { getSymKey } from '../../modules/utils';
3
+ import { CollatorService } from '../collator';
4
+ import { SerializerService } from '../serializer';
5
5
  import { StoreService } from '../store';
6
- import { ActivityType } from "../../types/activity";
7
- import { HookRule } from "../../types/hook";
8
- import { HotMeshGraph, HotMeshManifest } from "../../types/hotmesh";
9
- import { RedisClient, RedisMulti } from "../../types/redis";
10
- import { StringAnyType, Symbols } from "../../types/serializer";
6
+ import { ActivityType } from '../../types/activity';
7
+ import { HookRule } from '../../types/hook';
8
+ import { HotMeshGraph, HotMeshManifest } from '../../types/hotmesh';
9
+ import { RedisClient, RedisMulti } from '../../types/redis';
10
+ import { StringAnyType, Symbols } from '../../types/serializer';
11
11
 
12
12
  const DEFAULT_METADATA_RANGE_SIZE = 26; //metadata is 26 slots ([a-z] * 1)
13
13
  const DEFAULT_DATA_RANGE_SIZE = 260; //data is 260 slots ([a-zA-Z] * 5)
@@ -63,6 +63,7 @@ class Deployer {
63
63
  for (const [activityId, activity] of Object.entries(graph.activities)) {
64
64
  const [lower, upper, symbols] = await this.store.reserveSymbolRange(activityId, DEFAULT_RANGE_SIZE, 'ACTIVITY');
65
65
  const prefix = `${activityId}/`; //activity meta/data is namespaced
66
+ this.bindSelf(activity.consumes, activity.produces, activityId);
66
67
  const newSymbols = this.bindSymbols(lower, upper, symbols, prefix, activity.produces);
67
68
  if (Object.keys(newSymbols).length) {
68
69
  await this.store.addSymbols(activityId, newSymbols);
@@ -71,6 +72,20 @@ class Deployer {
71
72
  }
72
73
  }
73
74
 
75
+ bindSelf(consumes: Record<string, string[]>, produces: string[], activityId: string) {
76
+ //bind self-referential mappings
77
+ for (const selfId of [activityId, '$self']) {
78
+ const selfConsumes = consumes[selfId];
79
+ if (selfConsumes) {
80
+ for (const path of selfConsumes) {
81
+ if (!produces.includes(path)) {
82
+ produces.push(path);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+
74
89
  bindSymbols(startIndex: number, maxIndex: number, existingSymbols: Symbols, prefix: string, produces: string[]): Symbols {
75
90
  let newSymbols: Symbols = {};
76
91
  let currentSymbols: Symbols = {...existingSymbols};
@@ -1,4 +1,4 @@
1
- import { HotMeshGraph } from "../../types/hotmesh";
1
+ import { HotMeshGraph } from '../../types/hotmesh';
2
2
 
3
3
  class DimensionService {
4
4
 
@@ -1,8 +1,8 @@
1
- import { WorkflowHandleService } from "./handle";
2
- import { HotMeshService as HotMesh } from "../hotmesh";
3
- import { ClientConfig, Connection, WorkflowOptions } from "../../types/durable";
4
- import { getWorkflowYAML } from "./factory";
5
- import { JobState } from "../../types/job";
1
+ import { WorkflowHandleService } from './handle';
2
+ import { HotMeshService as HotMesh } from '../hotmesh';
3
+ import { ClientConfig, Connection, WorkflowOptions } from '../../types/durable';
4
+ import { getWorkflowYAML } from './factory';
5
+ import { JobState } from '../../types/job';
6
6
 
7
7
  /*
8
8
  Here is an example of how the methods in this file are used:
@@ -101,7 +101,14 @@ export class ClientService {
101
101
  await hotMesh.deploy(getWorkflowYAML(workflowTopic, version));
102
102
  await hotMesh.activate(version);
103
103
  } catch (err) {
104
- hotMesh.engine.logger.error('durable-client-workflow-activation-err', err);
104
+ hotMesh.engine.logger.error('durable-client-deploy-activate-err', err);
105
+ throw err;
106
+ }
107
+ } else if(app && !app.active) {
108
+ try {
109
+ await hotMesh.activate(version);
110
+ } catch (err) {
111
+ hotMesh.engine.logger.error('durable-client-activate-err', err);
105
112
  throw err;
106
113
  }
107
114
  }
@@ -1,5 +1,5 @@
1
- import { JobOutput } from "../../types/job";
2
- import { HotMeshService as HotMesh } from "../hotmesh";
1
+ import { JobOutput } from '../../types/job';
2
+ import { HotMeshService as HotMesh } from '../hotmesh';
3
3
 
4
4
  export class WorkflowHandleService {
5
5
  hotMesh: HotMesh;
@@ -67,7 +67,14 @@ export class WorkerService {
67
67
  await hotMesh.deploy(factory(topic, version));
68
68
  await hotMesh.activate(version);
69
69
  } catch (err) {
70
- hotMesh.engine.logger.error('durable-worker-workflow-activation-error', err);
70
+ hotMesh.engine.logger.error('durable-worker-deploy-activate-err', err);
71
+ throw err;
72
+ }
73
+ } else if(app && !app.active) {
74
+ try {
75
+ await hotMesh.activate(version);
76
+ } catch (err) {
77
+ hotMesh.engine.logger.error('durable-worker-activate-err', err);
71
78
  throw err;
72
79
  }
73
80
  }
@@ -176,7 +183,14 @@ export class WorkerService {
176
183
  await hotMesh.deploy(getActivityYAML(activityTopic, version));
177
184
  await hotMesh.activate(version);
178
185
  } catch (err) {
179
- console.log('durable-worker-activity-workflow-activation-error', err);
186
+ console.log('durable-worker-activity-deploy-activate-error', err);
187
+ throw err;
188
+ }
189
+ } else if(app && !app.active) {
190
+ try {
191
+ await hotMesh.activate(version);
192
+ } catch (err) {
193
+ hotMesh.engine.logger.error('durable-worker-activity-activate-err', err);
180
194
  throw err;
181
195
  }
182
196
  }