@hotmeshio/hotmesh 0.16.6 → 0.17.1

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.16.6",
3
+ "version": "0.17.1",
4
4
  "description": "Durable Workflow",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -358,6 +358,23 @@ class Hook extends activity_1.Activity {
358
358
  await this.processEvent(status, code, 'hook');
359
359
  if (code === 200) {
360
360
  await taskService.deleteWebHookSignal(this.config.hook.topic, data);
361
+ //clean up orphan pending on the sibling signal topic
362
+ // wfs.wait delivered → remove wfs.signal pending
363
+ // wfs.signal delivered → remove wfs.wait pending
364
+ const topic = this.config.hook.topic;
365
+ const siblingTopic = topic.includes('.wfs.wait')
366
+ ? topic.replace('.wfs.wait', '.wfs.signal')
367
+ : topic.includes('.wfs.signal')
368
+ ? topic.replace('.wfs.signal', '.wfs.wait')
369
+ : null;
370
+ if (siblingTopic) {
371
+ try {
372
+ await taskService.deleteWebHookSignal(siblingTopic, data);
373
+ }
374
+ catch {
375
+ //sibling entry may not exist — ignore
376
+ }
377
+ }
361
378
  }
362
379
  return;
363
380
  }
@@ -442,14 +442,15 @@ class Deployer {
442
442
  if (graph.hooks) {
443
443
  for (const topic in graph.hooks) {
444
444
  hookRules[topic] = graph.hooks[topic];
445
- const activityId = graph.hooks[topic][0].to;
446
- const targetActivity = graph.activities[activityId];
447
- if (targetActivity) {
448
- if (!targetActivity.hook) {
449
- targetActivity.hook = {};
445
+ //create back-reference to the hook topic for ALL target activities
446
+ for (const rule of graph.hooks[topic]) {
447
+ const targetActivity = graph.activities[rule.to];
448
+ if (targetActivity) {
449
+ if (!targetActivity.hook) {
450
+ targetActivity.hook = {};
451
+ }
452
+ targetActivity.hook.topic = topic;
450
453
  }
451
- //create back-reference to the hook topic
452
- targetActivity.hook.topic = topic;
453
454
  }
454
455
  }
455
456
  }
@@ -165,12 +165,28 @@ class ClientService {
165
165
  * hours before the workflow starts).
166
166
  */
167
167
  signal: async (signalId, data, namespace, expire) => {
168
- const topic = `${namespace ?? factory_1.APP_ID}.wfs.signal`;
169
- return await (await this.getHotMeshClient(topic, namespace)).signal(topic, {
168
+ const ns = namespace ?? factory_1.APP_ID;
169
+ const payload = {
170
170
  id: signalId,
171
171
  data,
172
172
  ...(expire ? { $expire: expire } : {}),
173
- });
173
+ };
174
+ //send collator topic first (creates pending if no collator),
175
+ //then inline waiter topic (delivers and cleans up collator pending)
176
+ try {
177
+ const signalTopic = `${ns}.wfs.signal`;
178
+ await (await this.getHotMeshClient(signalTopic, namespace)).signal(signalTopic, payload);
179
+ }
180
+ catch {
181
+ //no hook rule — ignore
182
+ }
183
+ try {
184
+ const waitTopic = `${ns}.wfs.wait`;
185
+ return await (await this.getHotMeshClient(waitTopic, namespace)).signal(waitTopic, payload);
186
+ }
187
+ catch {
188
+ //no hook rule — ignore
189
+ }
174
190
  },
175
191
  /**
176
192
  * Spawns an a new, isolated execution cycle within the same job.
@@ -101,11 +101,25 @@ class WorkflowHandleService {
101
101
  * @param expire - Optional pending signal TTL (e.g., '1h', '30d'). Default '10m'.
102
102
  */
103
103
  async signal(signalId, data, expire) {
104
- await this.hotMesh.signal(`${this.hotMesh.appId}.wfs.signal`, {
104
+ const payload = {
105
105
  id: signalId,
106
106
  data,
107
107
  ...(expire ? { $expire: expire } : {}),
108
- });
108
+ };
109
+ //send collator topic first (creates pending if no collator),
110
+ //then inline waiter topic (delivers and cleans up collator pending)
111
+ try {
112
+ await this.hotMesh.signal(`${this.hotMesh.appId}.wfs.signal`, payload);
113
+ }
114
+ catch {
115
+ //no hook rule — ignore
116
+ }
117
+ try {
118
+ await this.hotMesh.signal(`${this.hotMesh.appId}.wfs.wait`, payload);
119
+ }
120
+ catch {
121
+ //no hook rule — ignore
122
+ }
109
123
  }
110
124
  /**
111
125
  * Returns the current workflow state. For a completed workflow this
@@ -1,4 +1,4 @@
1
- declare const APP_VERSION = "13";
1
+ declare const APP_VERSION = "14";
2
2
  declare const APP_ID = "durable";
3
3
  /**
4
4
  * returns a new durable workflow schema
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.APP_ID = exports.APP_VERSION = exports.getWorkflowYAML = void 0;
4
- const APP_VERSION = '13';
4
+ const APP_VERSION = '14';
5
5
  exports.APP_VERSION = APP_VERSION;
6
6
  const APP_ID = 'durable';
7
7
  exports.APP_ID = APP_ID;
@@ -320,14 +320,22 @@ const getWorkflowYAML = (app, version) => {
320
320
  schema:
321
321
  type: object
322
322
  properties:
323
+ signalId:
324
+ type: string
325
+ description: the signal identifier to wait for
323
326
  index:
324
327
  type: number
325
- description: the index of the first signal in the array
326
- signal:
327
- type: object
328
- properties:
329
- signal:
330
- type: string
328
+ description: the replay index (COUNTER++)
329
+ workflowDimension:
330
+ type: string
331
+ description: empty string or dimensional path (,0,0,1)
332
+ duration:
333
+ type: number
334
+ description: optional timeout in seconds
335
+ workflowId:
336
+ type: string
337
+ originJobId:
338
+ type: string
331
339
  job:
332
340
  maps:
333
341
  response: '{$self.output.data.response}'
@@ -359,6 +367,47 @@ const getWorkflowYAML = (app, version) => {
359
367
  continueGeneration: '{cycle_hook.output.data.continueGeneration}'
360
368
  continueArgs: '{cycle_hook.output.data.continueArgs}'
361
369
 
370
+ waiter:
371
+ title: Waits for a matching signal or optional timeout (single condition)
372
+ type: hook
373
+ sleep: '{worker.output.data.duration}'
374
+ hook:
375
+ type: object
376
+ properties:
377
+ signalData:
378
+ type: object
379
+ output:
380
+ maps:
381
+ signalId: '{worker.output.data.signalId}'
382
+ job:
383
+ maps:
384
+ idempotentcy-marker[-]:
385
+ '@pipe':
386
+ - '@pipe':
387
+ - ['-wait', '{worker.output.data.workflowDimension}', '-', '{worker.output.data.index}', '-']
388
+ - ['{@string.concat}']
389
+ - '@pipe':
390
+ - '@pipe':
391
+ - ['{$self.hook.data.id}']
392
+ - '@pipe':
393
+ - [type, wait, data, '{$self.hook.data}', ac, '{$job.metadata.jc}', au, '{$self.output.metadata.au}']
394
+ - ['{@object.create}']
395
+ - '@pipe':
396
+ - [timedOut, true, ac, '{$self.output.metadata.ac}', au, '{$self.output.metadata.au}']
397
+ - ['{@object.create}']
398
+ - ['{@conditional.ternary}']
399
+ - ['{@object.create}']
400
+
401
+ wait_cycler:
402
+ title: Cycles back to the cycle_hook after signal wait
403
+ type: cycle
404
+ ancestor: cycle_hook
405
+ input:
406
+ maps:
407
+ retryCount: 0
408
+ continueGeneration: '{cycle_hook.output.data.continueGeneration}'
409
+ continueArgs: '{cycle_hook.output.data.continueArgs}'
410
+
362
411
  childer:
363
412
  title: Awaits a child flow to be executed/started
364
413
  type: await
@@ -1072,14 +1121,22 @@ const getWorkflowYAML = (app, version) => {
1072
1121
  schema:
1073
1122
  type: object
1074
1123
  properties:
1124
+ signalId:
1125
+ type: string
1126
+ description: the signal identifier to wait for
1075
1127
  index:
1076
1128
  type: number
1077
- description: the index of the first signal in the array
1078
- signal:
1079
- type: object
1080
- properties:
1081
- signal:
1082
- type: string
1129
+ description: the replay index (COUNTER++)
1130
+ workflowDimension:
1131
+ type: string
1132
+ description: empty string or dimensional path (,0,0,1)
1133
+ duration:
1134
+ type: number
1135
+ description: optional timeout in seconds
1136
+ workflowId:
1137
+ type: string
1138
+ originJobId:
1139
+ type: string
1083
1140
 
1084
1141
  signaler_sleeper:
1085
1142
  title: Pauses a single thread within the worker for a set amount of seconds while the main flow thread and all other subthreads remain active
@@ -1105,6 +1162,45 @@ const getWorkflowYAML = (app, version) => {
1105
1162
  maps:
1106
1163
  retryCount: 0
1107
1164
 
1165
+ signaler_waiter:
1166
+ title: Waits for a matching signal or optional timeout (single condition in signal-in path)
1167
+ type: hook
1168
+ sleep: '{signaler_worker.output.data.duration}'
1169
+ hook:
1170
+ type: object
1171
+ properties:
1172
+ signalData:
1173
+ type: object
1174
+ output:
1175
+ maps:
1176
+ signalId: '{signaler_worker.output.data.signalId}'
1177
+ job:
1178
+ maps:
1179
+ idempotentcy-marker[-]:
1180
+ '@pipe':
1181
+ - '@pipe':
1182
+ - ['-wait', '{signaler_worker.output.data.workflowDimension}', '-', '{signaler_worker.output.data.index}', '-']
1183
+ - ['{@string.concat}']
1184
+ - '@pipe':
1185
+ - '@pipe':
1186
+ - ['{$self.hook.data.id}']
1187
+ - '@pipe':
1188
+ - [type, wait, data, '{$self.hook.data}', ac, '{$job.metadata.jc}', au, '{$self.output.metadata.au}']
1189
+ - ['{@object.create}']
1190
+ - '@pipe':
1191
+ - [timedOut, true, ac, '{$self.output.metadata.ac}', au, '{$self.output.metadata.au}']
1192
+ - ['{@object.create}']
1193
+ - ['{@conditional.ternary}']
1194
+ - ['{@object.create}']
1195
+
1196
+ signaler_wait_cycler:
1197
+ title: Cycles back to signaler_cycle_hook after signal wait
1198
+ type: cycle
1199
+ ancestor: signaler_cycle_hook
1200
+ input:
1201
+ maps:
1202
+ retryCount: 0
1203
+
1108
1204
  signaler_childer:
1109
1205
  title: Awaits a child flow to be executed/started
1110
1206
  type: await
@@ -1546,6 +1642,9 @@ const getWorkflowYAML = (app, version) => {
1546
1642
  - to: sleeper
1547
1643
  conditions:
1548
1644
  code: 588
1645
+ - to: waiter
1646
+ conditions:
1647
+ code: 595
1549
1648
  - to: collator
1550
1649
  conditions:
1551
1650
  code: 589
@@ -1589,6 +1688,8 @@ const getWorkflowYAML = (app, version) => {
1589
1688
  - to: proxy_cycler
1590
1689
  sleeper:
1591
1690
  - to: sleep_cycler
1691
+ waiter:
1692
+ - to: wait_cycler
1592
1693
  ### SUBPROCESS TRANSITIONS (REENTRY) ###
1593
1694
  signaler:
1594
1695
  - to: signaler_cycle_hook
@@ -1600,6 +1701,9 @@ const getWorkflowYAML = (app, version) => {
1600
1701
  - to: signaler_sleeper
1601
1702
  conditions:
1602
1703
  code: 588
1704
+ - to: signaler_waiter
1705
+ conditions:
1706
+ code: 595
1603
1707
  - to: signaler_collator
1604
1708
  conditions:
1605
1709
  code: 589
@@ -1627,6 +1731,8 @@ const getWorkflowYAML = (app, version) => {
1627
1731
  - to: signaler_proxy_cycler
1628
1732
  signaler_sleeper:
1629
1733
  - to: signaler_sleep_cycler
1734
+ signaler_waiter:
1735
+ - to: signaler_wait_cycler
1630
1736
 
1631
1737
  hooks:
1632
1738
  ${app}.flow.signal:
@@ -1635,7 +1741,17 @@ const getWorkflowYAML = (app, version) => {
1635
1741
  match:
1636
1742
  - expected: '{trigger.output.data.workflowId}'
1637
1743
  actual: '{$self.hook.data.id}'
1638
-
1744
+ ${app}.wfs.wait:
1745
+ - to: waiter
1746
+ conditions:
1747
+ match:
1748
+ - expected: '{worker.output.data.signalId}'
1749
+ actual: '{$self.hook.data.id}'
1750
+ - to: signaler_waiter
1751
+ conditions:
1752
+ match:
1753
+ - expected: '{signaler_worker.output.data.signalId}'
1754
+ actual: '{$self.hook.data.id}'
1639
1755
 
1640
1756
 
1641
1757
  ###################################################
@@ -799,10 +799,33 @@ class WorkerService {
799
799
  if (isProcessing) {
800
800
  return;
801
801
  }
802
- if (err instanceof errors_1.DurableWaitForError ||
803
- interruptionRegistry.length > 1) {
802
+ if (err instanceof errors_1.DurableWaitForError &&
803
+ interruptionRegistry.length === 1) {
804
+ //single condition() — handle inline, no collator needed
804
805
  isProcessing = true;
805
- //NOTE: this type is spawned when `Promise.all` is used OR if the interruption is a `condition`
806
+ const workflowInput = data.data;
807
+ const execIndex = counter.counter;
808
+ const { workflowId, workflowDimension, originJobId } = workflowInput;
809
+ return withPatchMarkers({
810
+ status: stream_1.StreamStatus.SUCCESS,
811
+ code: enums_1.HMSH_CODE_DURABLE_WAIT,
812
+ metadata: { ...data.metadata },
813
+ data: {
814
+ code: enums_1.HMSH_CODE_DURABLE_WAIT,
815
+ signalId: interruptionRegistry[0].signalId,
816
+ index: execIndex,
817
+ workflowDimension: interruptionRegistry[0].workflowDimension ||
818
+ workflowDimension ||
819
+ '',
820
+ duration: interruptionRegistry[0].duration,
821
+ workflowId,
822
+ originJobId: originJobId || workflowId,
823
+ },
824
+ });
825
+ }
826
+ else if (interruptionRegistry.length > 1) {
827
+ isProcessing = true;
828
+ //NOTE: this type is spawned when `Promise.all` is used (multiple items to collate)
806
829
  const workflowInput = data.data;
807
830
  const execIndex = counter.counter - interruptionRegistry.length + 1;
808
831
  const { workflowId, workflowTopic, workflowDimension, originJobId, expire, } = workflowInput;
@@ -70,11 +70,25 @@ async function signal(signalId, data, expire) {
70
70
  namespace,
71
71
  });
72
72
  if (await (0, isSideEffectAllowed_1.isSideEffectAllowed)(hotMeshClient, 'signal')) {
73
- return await hotMeshClient.signal(`${namespace}.wfs.signal`, {
73
+ const payload = {
74
74
  id: signalId,
75
75
  data,
76
76
  ...(expire ? { $expire: expire } : {}),
77
- });
77
+ };
78
+ //send collator topic first (creates pending if no collator),
79
+ //then inline waiter topic (delivers and cleans up collator pending)
80
+ try {
81
+ await hotMeshClient.signal(`${namespace}.wfs.signal`, payload);
82
+ }
83
+ catch {
84
+ //no hook rule — ignore
85
+ }
86
+ try {
87
+ return await hotMeshClient.signal(`${namespace}.wfs.wait`, payload);
88
+ }
89
+ catch {
90
+ //no hook rule — ignore
91
+ }
78
92
  }
79
93
  }
80
94
  exports.signal = signal;
@@ -204,6 +204,13 @@ const KVTables = (context) => ({
204
204
  expiry TIMESTAMP WITH TIME ZONE
205
205
  );
206
206
  `);
207
+ if (tableDef.name === 'signal_registry') {
208
+ await client.query(`
209
+ CREATE INDEX IF NOT EXISTS idx_${tableDef.name}_expiry
210
+ ON ${fullTableName} (expiry)
211
+ WHERE expiry IS NOT NULL;
212
+ `);
213
+ }
207
214
  break;
208
215
  case 'hash':
209
216
  await client.query(`
@@ -775,36 +775,46 @@ class PostgresStoreService extends __1.StoreService {
775
775
  const fullKey = `${key}:${signalKey}`;
776
776
  const delay = Math.max(hook.expire, enums_1.HMSH_SIGNAL_EXPIRE);
777
777
  if (transaction) {
778
- await this.kvsql(transaction).setnxex(fullKey, jobId, delay);
778
+ //in-transaction: unconditional upsert (overwrites $pending)
779
+ const kv = this.kvsql();
780
+ const tableName = kv.tableForKey(fullKey);
781
+ const storedKey = kv.storageKey(fullKey);
782
+ transaction.addCommand(`INSERT INTO ${tableName} (key, value, expiry)
783
+ VALUES ($1, $2, NOW() + INTERVAL '${delay} seconds')
784
+ ON CONFLICT (key) DO UPDATE
785
+ SET value = EXCLUDED.value, expiry = EXCLUDED.expiry`, [storedKey, jobId], 'boolean');
779
786
  return { success: true };
780
787
  }
788
+ //standalone: atomic CTE — read prior value + upsert in one statement.
789
+ //eliminates the advisory lock TOCTOU race (reentrant on same session).
781
790
  const kv = this.kvsql();
782
791
  const tableName = kv.tableForKey(fullKey);
783
792
  const storedKey = kv.storageKey(fullKey);
784
- //acquire per-key advisory lock (session-level) to serialize
785
- //with concurrent getHookSignal for the same signal key
786
- await this.pgClient.query('SELECT pg_advisory_lock(901, hashtext($1))', [storedKey]);
787
793
  try {
788
- //read existing value under lock
789
- const readRes = await this.pgClient.query(`SELECT value FROM ${tableName}
790
- WHERE key = $1 AND (expiry IS NULL OR expiry > NOW())`, [storedKey]);
791
- let pendingData;
792
- if (readRes.rows.length > 0) {
793
- const existing = readRes.rows[0].value;
794
- if (existing?.startsWith('$pending::')) {
795
- pendingData = existing.slice('$pending::'.length);
796
- }
797
- else {
798
- //hook already set (retry) — no change needed
799
- return { success: false };
800
- }
794
+ const result = await this.pgClient.query(`WITH prior AS (
795
+ SELECT value FROM ${tableName}
796
+ WHERE key = $1 AND (expiry IS NULL OR expiry > NOW())
797
+ )
798
+ INSERT INTO ${tableName} (key, value, expiry)
799
+ VALUES ($1, $2, NOW() + INTERVAL '${delay} seconds')
800
+ ON CONFLICT (key) DO UPDATE
801
+ SET value = EXCLUDED.value, expiry = EXCLUDED.expiry
802
+ RETURNING (SELECT value FROM prior) as prior_value`, [storedKey, jobId]);
803
+ const priorValue = result.rows[0]?.prior_value;
804
+ if (priorValue?.startsWith('$pending::')) {
805
+ this.logger.debug('hook-signal-pending-consumed', {
806
+ key: signalKey,
807
+ });
808
+ return {
809
+ success: true,
810
+ pendingData: priorValue.slice('$pending::'.length),
811
+ };
801
812
  }
802
- //insert hook value (or overwrite pending)
803
- await this.pgClient.query(`INSERT INTO ${tableName} (key, value, expiry)
804
- VALUES ($1, $2, NOW() + INTERVAL '${delay} seconds')
805
- ON CONFLICT (key) DO UPDATE
806
- SET value = EXCLUDED.value, expiry = EXCLUDED.expiry`, [storedKey, jobId]);
807
- return { success: true, pendingData };
813
+ if (priorValue && !priorValue.startsWith('$pending::')) {
814
+ //hook already set by a previous Leg1 (idempotent)
815
+ return { success: false };
816
+ }
817
+ return { success: true };
808
818
  }
809
819
  catch (error) {
810
820
  if (error?.message?.includes('closed') ||
@@ -813,14 +823,6 @@ class PostgresStoreService extends __1.StoreService {
813
823
  }
814
824
  throw error;
815
825
  }
816
- finally {
817
- try {
818
- await this.pgClient.query('SELECT pg_advisory_unlock(901, hashtext($1))', [storedKey]);
819
- }
820
- catch {
821
- //lock auto-releases on session close
822
- }
823
- }
824
826
  }
825
827
  /**
826
828
  * Leg2: get hook signal OR atomically set a pending signal.
@@ -849,30 +851,40 @@ class PostgresStoreService extends __1.StoreService {
849
851
  return undefined;
850
852
  return value;
851
853
  }
854
+ //atomic CTE: check for hook, store $pending if not found.
855
+ //eliminates the advisory lock TOCTOU race (reentrant on same session).
852
856
  const kv = this.kvsql();
853
857
  const tableName = kv.tableForKey(fullKey);
854
858
  const storedKey = kv.storageKey(fullKey);
855
859
  const expire = pendingExpire || enums_1.HMSH_PENDING_SIGNAL_EXPIRE;
856
860
  const pendingValue = `$pending::${pendingData}`;
857
- //acquire per-key advisory lock (session-level) to serialize
858
- //with concurrent setHookSignal for the same signal key
859
- await this.pgClient.query('SELECT pg_advisory_lock(901, hashtext($1))', [storedKey]);
860
861
  try {
861
- //read existing value under lock
862
- const readRes = await this.pgClient.query(`SELECT value FROM ${tableName}
863
- WHERE key = $1 AND (expiry IS NULL OR expiry > NOW())`, [storedKey]);
864
- if (readRes.rows.length > 0) {
865
- const value = readRes.rows[0].value;
866
- if (value && !value.startsWith('$pending::')) {
867
- //hook found return it
868
- return value;
869
- }
862
+ const result = await this.pgClient.query(`WITH prior AS (
863
+ SELECT value FROM ${tableName}
864
+ WHERE key = $1 AND (expiry IS NULL OR expiry > NOW())
865
+ )
866
+ INSERT INTO ${tableName} (key, value, expiry)
867
+ VALUES ($1, $2, NOW() + INTERVAL '${expire} seconds')
868
+ ON CONFLICT (key) DO UPDATE
869
+ SET value = CASE
870
+ WHEN ${tableName}.value LIKE '$pending::%' THEN EXCLUDED.value
871
+ ELSE ${tableName}.value
872
+ END,
873
+ expiry = CASE
874
+ WHEN ${tableName}.value LIKE '$pending::%' THEN EXCLUDED.expiry
875
+ ELSE ${tableName}.expiry
876
+ END
877
+ RETURNING (SELECT value FROM prior) as prior_value`, [storedKey, pendingValue]);
878
+ const priorValue = result.rows[0]?.prior_value;
879
+ if (priorValue && !priorValue.startsWith('$pending::')) {
880
+ //hook found — return the composite job key
881
+ return priorValue;
870
882
  }
871
- //no hook signal store pending
872
- await this.pgClient.query(`INSERT INTO ${tableName} (key, value, expiry)
873
- VALUES ($1, $2, NOW() + INTERVAL '${expire} seconds')
874
- ON CONFLICT (key) DO UPDATE
875
- SET value = EXCLUDED.value, expiry = EXCLUDED.expiry`, [storedKey, pendingValue]);
883
+ //no hook — $pending was stored (or updated existing pending)
884
+ this.logger.debug('hook-signal-pending-stored', {
885
+ topic,
886
+ resolved,
887
+ });
876
888
  return undefined;
877
889
  }
878
890
  catch (error) {
@@ -882,14 +894,6 @@ class PostgresStoreService extends __1.StoreService {
882
894
  }
883
895
  throw error;
884
896
  }
885
- finally {
886
- try {
887
- await this.pgClient.query('SELECT pg_advisory_unlock(901, hashtext($1))', [storedKey]);
888
- }
889
- catch {
890
- //lock auto-releases on session close
891
- }
892
- }
893
897
  }
894
898
  async deleteHookSignal(topic, resolved) {
895
899
  const key = this.mintKey(key_1.KeyType.SIGNALS, { appId: this.appId });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.16.6",
3
+ "version": "0.17.1",
4
4
  "description": "Durable Workflow",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",