@hotmeshio/hotmesh 0.5.3 → 0.5.5

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 (38) hide show
  1. package/README.md +218 -249
  2. package/build/index.d.ts +1 -3
  3. package/build/index.js +1 -5
  4. package/build/modules/enums.d.ts +4 -0
  5. package/build/modules/enums.js +5 -1
  6. package/build/modules/utils.d.ts +1 -9
  7. package/build/modules/utils.js +0 -6
  8. package/build/package.json +3 -4
  9. package/build/services/connector/factory.d.ts +2 -2
  10. package/build/services/connector/factory.js +11 -8
  11. package/build/services/connector/providers/postgres.d.ts +47 -0
  12. package/build/services/connector/providers/postgres.js +107 -0
  13. package/build/services/hotmesh/index.d.ts +8 -0
  14. package/build/services/hotmesh/index.js +27 -0
  15. package/build/services/memflow/client.d.ts +1 -1
  16. package/build/services/memflow/client.js +8 -6
  17. package/build/services/memflow/worker.js +3 -0
  18. package/build/services/pipe/functions/cron.js +1 -1
  19. package/build/services/store/providers/postgres/kvtables.js +19 -6
  20. package/build/services/store/providers/postgres/postgres.js +13 -2
  21. package/build/services/stream/providers/postgres/postgres.d.ts +6 -3
  22. package/build/services/stream/providers/postgres/postgres.js +169 -59
  23. package/build/services/sub/providers/postgres/postgres.d.ts +9 -0
  24. package/build/services/sub/providers/postgres/postgres.js +109 -18
  25. package/build/services/worker/index.js +4 -0
  26. package/build/types/hotmesh.d.ts +19 -5
  27. package/build/types/index.d.ts +0 -2
  28. package/env.example +11 -0
  29. package/index.ts +0 -4
  30. package/package.json +3 -4
  31. package/build/services/meshdata/index.d.ts +0 -795
  32. package/build/services/meshdata/index.js +0 -1235
  33. package/build/services/meshos/index.d.ts +0 -293
  34. package/build/services/meshos/index.js +0 -547
  35. package/build/types/manifest.d.ts +0 -52
  36. package/build/types/manifest.js +0 -2
  37. package/build/types/meshdata.d.ts +0 -252
  38. package/build/types/meshdata.js +0 -2
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { StoreService } from '../services/store';
3
3
  import { AppSubscriptions, AppTransitions, AppVID } from '../types/app';
4
- import { ProviderClient, ProviderConfig, ProviderTransaction, Providers, ProvidersConfig } from '../types/provider';
4
+ import { ProviderClient, ProviderTransaction, Providers } from '../types/provider';
5
5
  import { StringAnyType } from '../types/serializer';
6
6
  import { StreamCode, StreamData, StreamStatus } from '../types/stream';
7
7
  import { SystemHealth } from '../types/quorum';
@@ -34,14 +34,6 @@ export declare const polyfill: {
34
34
  * `redis` is deprecated; `connection` is the generic replacement
35
35
  */
36
36
  providerConfig(obj: any): any;
37
- /**
38
- * NOTE: `redisClass and redisOptions` input parameters are deprecated; use `connection` for all configuration inputs
39
- */
40
- meshDataConfig(obj: {
41
- connection?: Partial<ProviderConfig | ProvidersConfig>;
42
- redisClass?: any;
43
- redisOptions?: StringAnyType;
44
- }): Partial<ProviderConfig> | Partial<ProvidersConfig>;
45
37
  };
46
38
  /**
47
39
  * @private
@@ -127,12 +127,6 @@ exports.polyfill = {
127
127
  providerConfig(obj) {
128
128
  return obj?.connection ?? obj?.redis ?? obj?.connections;
129
129
  },
130
- /**
131
- * NOTE: `redisClass and redisOptions` input parameters are deprecated; use `connection` for all configuration inputs
132
- */
133
- meshDataConfig(obj) {
134
- return { ...obj.connection };
135
- },
136
130
  };
137
131
  /**
138
132
  * @private
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "Permanent-Memory Workflows & AI Agents",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -20,7 +20,7 @@
20
20
  "lint:fix": "eslint . --fix --ext .ts",
21
21
  "start": "ts-node src/index.ts",
22
22
  "test": "NODE_ENV=test jest --detectOpenHandles --forceExit --verbose",
23
- "test:await": "NODE_ENV=test jest ./tests/functional/awaiter/*.test.ts --detectOpenHandles --forceExit --verbose",
23
+ "test:await": "NODE_ENV=test jest ./tests/functional/awaiter/postgres.test.ts --detectOpenHandles --forceExit --verbose",
24
24
  "test:compile": "NODE_ENV=test jest ./tests/functional/compile/index.test.ts --detectOpenHandles --forceExit --verbose",
25
25
  "test:connect": "NODE_ENV=test jest ./tests/unit/services/connector/* --detectOpenHandles --forceExit --verbose",
26
26
  "test:connect:ioredis": "NODE_ENV=test jest ./tests/unit/services/connector/providers/ioredis.test.ts --detectOpenHandles --forceExit --verbose",
@@ -59,7 +59,7 @@
59
59
  "test:reporter": "NODE_ENV=test jest ./tests/unit/services/reporter/index.test.ts --detectOpenHandles --forceExit --verbose",
60
60
  "test:reentrant": "NODE_ENV=test jest ./tests/functional/reentrant/*.test.ts --detectOpenHandles --forceExit --verbose",
61
61
  "test:retry": "NODE_ENV=test jest ./tests/functional/retry/*.test.ts --detectOpenHandles --forceExit --verbose",
62
- "test:sequence": "NODE_ENV=test HMSH_LOGLEVEL=info jest ./tests/functional/sequence/*.test.ts --detectOpenHandles --forceExit --verbose",
62
+ "test:sequence": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/functional/sequence/postgres.test.ts --detectOpenHandles --forceExit --verbose",
63
63
  "test:signal": "NODE_ENV=test jest ./tests/functional/signal/*.test.ts --detectOpenHandles --forceExit --verbose",
64
64
  "test:status": "NODE_ENV=test jest ./tests/functional/status/index.test.ts --detectOpenHandles --forceExit --verbose",
65
65
  "test:providers": "NODE_ENV=test jest ./tests/functional/*/providers/*/*.test.ts --detectOpenHandles --forceExit --verbose",
@@ -75,7 +75,6 @@
75
75
  "test:sub:postgres": "NODE_ENV=test jest ./tests/functional/sub/providers/postgres/postgres.test.ts --detectOpenHandles --forceExit --verbose",
76
76
  "test:sub:nats": "NODE_ENV=test jest ./tests/functional/sub/providers/nats/nats.test.ts --detectOpenHandles --forceExit --verbose",
77
77
  "test:trigger": "NODE_ENV=test jest ./tests/unit/services/activities/trigger.test.ts --detectOpenHandles --forceExit --verbose",
78
- "test:meshos": "HMSH_LOGLEVEL=info NODE_ENV=test HMSH_IS_CLUSTER=true jest ./tests/meshos/*.test.ts --forceExit --verbose --detectOpenHandles",
79
78
  "test:meshcall": "NODE_ENV=test jest ./tests/meshcall/*.test.ts --forceExit --verbose --detectOpenHandles",
80
79
  "test:unit": "NODE_ENV=test jest ./tests/unit/*/*/index.test.ts --detectOpenHandles --forceExit --verbose"
81
80
  },
@@ -8,7 +8,7 @@ export declare class ConnectorService {
8
8
  * initialization, but the factory method provided here is useful
9
9
  * for testing provider configurations.
10
10
  */
11
- static connectClient(ProviderConfig: ProviderConfig): Promise<ProviderNativeClient>;
11
+ static connectClient(ProviderConfig: ProviderConfig, taskQueue?: string): Promise<ProviderNativeClient>;
12
12
  /**
13
13
  * Initialize `store`, `stream`, and `subscription` clients for any provider.
14
14
  * @private
@@ -18,5 +18,5 @@ export declare class ConnectorService {
18
18
  * Binds a provider client native instance to the target object.
19
19
  * @private
20
20
  */
21
- static initClient(ProviderConfig: ProviderConfig, target: HotMeshEngine | HotMeshWorker, field: string): Promise<void>;
21
+ static initClient(ProviderConfig: ProviderConfig, target: HotMeshEngine | HotMeshWorker, field: string, taskQueue?: string): Promise<void>;
22
22
  }
@@ -19,9 +19,9 @@ class ConnectorService {
19
19
  * initialization, but the factory method provided here is useful
20
20
  * for testing provider configurations.
21
21
  */
22
- static async connectClient(ProviderConfig) {
22
+ static async connectClient(ProviderConfig, taskQueue) {
23
23
  const target = {};
24
- await ConnectorService.initClient(ProviderConfig, target, 'client');
24
+ await ConnectorService.initClient(ProviderConfig, target, 'client', taskQueue);
25
25
  return target.client;
26
26
  }
27
27
  /**
@@ -38,15 +38,17 @@ class ConnectorService {
38
38
  sub: { ...connection },
39
39
  };
40
40
  }
41
+ // Extract taskQueue from target for connection pooling
42
+ const taskQueue = target.taskQueue;
41
43
  // Expanded form
42
44
  if (connection.store) {
43
- await ConnectorService.initClient(connection.store, target, 'store');
45
+ await ConnectorService.initClient(connection.store, target, 'store', taskQueue);
44
46
  }
45
47
  if (connection.stream) {
46
- await ConnectorService.initClient(connection.stream, target, 'stream');
48
+ await ConnectorService.initClient(connection.stream, target, 'stream', taskQueue);
47
49
  }
48
50
  if (connection.sub) {
49
- await ConnectorService.initClient(connection.sub, target, 'sub');
51
+ await ConnectorService.initClient(connection.sub, target, 'sub', taskQueue);
50
52
  // use store for publishing events if same as subscription
51
53
  if (connection.sub.class !== connection.store.class) {
52
54
  //initialize a separate client for publishing events, using
@@ -56,7 +58,7 @@ class ConnectorService {
56
58
  options: { ...connection.sub.options },
57
59
  provider: connection.sub.provider,
58
60
  };
59
- await ConnectorService.initClient(connection.pub, target, 'pub');
61
+ await ConnectorService.initClient(connection.pub, target, 'pub', taskQueue);
60
62
  }
61
63
  }
62
64
  }
@@ -64,7 +66,7 @@ class ConnectorService {
64
66
  * Binds a provider client native instance to the target object.
65
67
  * @private
66
68
  */
67
- static async initClient(ProviderConfig, target, field) {
69
+ static async initClient(ProviderConfig, target, field, taskQueue) {
68
70
  if (target[field]) {
69
71
  return;
70
72
  }
@@ -87,7 +89,8 @@ class ConnectorService {
87
89
  case 'postgres':
88
90
  //if connecting as a poolClient for subscription, auto connect the client
89
91
  const bAutoConnect = field === 'sub';
90
- clientInstance = await postgres_1.PostgresConnection.connect(id, providerClass, options, { connect: bAutoConnect, provider: providerName });
92
+ // Use taskQueue-based connection pooling for PostgreSQL
93
+ clientInstance = await postgres_1.PostgresConnection.getOrCreateTaskQueueConnection(id, taskQueue, providerClass, options, { connect: bAutoConnect, provider: providerName });
91
94
  break;
92
95
  default:
93
96
  throw new Error(`Unknown provider type: ${providerType}`);
@@ -4,17 +4,64 @@ declare class PostgresConnection extends AbstractConnection<PostgresClassType, P
4
4
  defaultOptions: PostgresClientOptions;
5
5
  protected static poolClientInstances: Set<PostgresPoolClientType>;
6
6
  protected static connectionInstances: Set<PostgresClientType>;
7
+ protected static taskQueueConnections: Map<string, PostgresConnection>;
8
+ /**
9
+ * Get comprehensive connection statistics for monitoring taskQueue pooling effectiveness
10
+ */
11
+ static getConnectionStats(): {
12
+ totalPoolClients: number;
13
+ totalConnections: number;
14
+ taskQueueConnections: number;
15
+ taskQueueDetails: Array<{
16
+ key: string;
17
+ connectionId: string;
18
+ reusedCount: number;
19
+ }>;
20
+ };
21
+ /**
22
+ * Log current connection statistics - useful for debugging connection pooling
23
+ */
24
+ static logConnectionStats(logger?: any): void;
25
+ /**
26
+ * Check taskQueue pooling effectiveness - returns metrics about connection reuse
27
+ */
28
+ static getPoolingEffectiveness(): {
29
+ totalConnections: number;
30
+ taskQueuePools: number;
31
+ totalReuses: number;
32
+ averageReusesPerPool: number;
33
+ poolingEfficiency: number;
34
+ };
7
35
  poolClientInstance: PostgresPoolClientType;
8
36
  createConnection(clientConstructor: any, options: PostgresClientOptions, config?: {
9
37
  connect?: boolean;
10
38
  provider?: string;
11
39
  }): Promise<PostgresClientType>;
12
40
  getClient(): PostgresClientType;
41
+ /**
42
+ * Get the connection ID for monitoring purposes
43
+ */
44
+ getConnectionId(): string | null;
13
45
  static disconnectAll(): Promise<void>;
14
46
  static disconnectPoolClients(): Promise<void>;
15
47
  static disconnectConnections(): Promise<void>;
16
48
  closeConnection(connection: PostgresClientType): Promise<void>;
17
49
  static isPoolClient(client: any): client is PostgresPoolClientType;
50
+ /**
51
+ * Creates a taskQueue-based connection key for connection pooling.
52
+ * This allows multiple providers (store, sub, stream) to reuse the same connection
53
+ * when they share the same taskQueue and database configuration.
54
+ */
55
+ private static createTaskQueueConnectionKey;
56
+ /**
57
+ * Gets or creates a PostgreSQL connection based on taskQueue and database configuration.
58
+ * If a connection already exists for the same taskQueue + config, it will be reused.
59
+ * This optimization reduces connection overhead for PostgreSQL providers.
60
+ */
61
+ static getOrCreateTaskQueueConnection(id: string, taskQueue: string | undefined, clientConstructor: PostgresClassType, options: PostgresClientOptions, config?: {
62
+ connect?: boolean;
63
+ provider?: string;
64
+ }): Promise<PostgresConnection>;
18
65
  static getTransactionClient(transactionClient: any): Promise<['client' | 'poolclient', PostgresClientType]>;
19
66
  }
20
67
  export { PostgresConnection };
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PostgresConnection = void 0;
4
4
  const __1 = require("..");
5
+ const utils_1 = require("../../../modules/utils");
5
6
  class PostgresConnection extends __1.AbstractConnection {
6
7
  constructor() {
7
8
  super(...arguments);
@@ -15,6 +16,54 @@ class PostgresConnection extends __1.AbstractConnection {
15
16
  idleTimeoutMillis: 30000,
16
17
  };
17
18
  }
19
+ /**
20
+ * Get comprehensive connection statistics for monitoring taskQueue pooling effectiveness
21
+ */
22
+ static getConnectionStats() {
23
+ const taskQueueDetails = Array.from(this.taskQueueConnections.entries()).map(([key, connection]) => ({
24
+ key,
25
+ connectionId: connection.getConnectionId() || 'unknown',
26
+ reusedCount: connection.reusedCount || 0
27
+ }));
28
+ return {
29
+ totalPoolClients: this.poolClientInstances.size,
30
+ totalConnections: this.connectionInstances.size,
31
+ taskQueueConnections: this.taskQueueConnections.size,
32
+ taskQueueDetails
33
+ };
34
+ }
35
+ /**
36
+ * Log current connection statistics - useful for debugging connection pooling
37
+ */
38
+ static logConnectionStats(logger) {
39
+ const stats = this.getConnectionStats();
40
+ const message = `PostgreSQL Connection Stats: ${stats.totalConnections} total connections, ${stats.taskQueueConnections} taskQueue pools, ${stats.totalPoolClients} pool clients`;
41
+ if (logger) {
42
+ logger.info('postgres-connection-stats', {
43
+ ...stats,
44
+ message
45
+ });
46
+ }
47
+ else {
48
+ console.log(message, stats);
49
+ }
50
+ }
51
+ /**
52
+ * Check taskQueue pooling effectiveness - returns metrics about connection reuse
53
+ */
54
+ static getPoolingEffectiveness() {
55
+ const stats = this.getConnectionStats();
56
+ const totalReuses = stats.taskQueueDetails.reduce((sum, detail) => sum + detail.reusedCount, 0);
57
+ const averageReusesPerPool = stats.taskQueueConnections > 0 ? totalReuses / stats.taskQueueConnections : 0;
58
+ const poolingEfficiency = stats.totalConnections > 0 ? (stats.taskQueueConnections / stats.totalConnections) * 100 : 0;
59
+ return {
60
+ totalConnections: stats.totalConnections,
61
+ taskQueuePools: stats.taskQueueConnections,
62
+ totalReuses,
63
+ averageReusesPerPool: Math.round(averageReusesPerPool * 100) / 100,
64
+ poolingEfficiency: Math.round(poolingEfficiency * 100) / 100,
65
+ };
66
+ }
18
67
  async createConnection(clientConstructor, options, config = {}) {
19
68
  try {
20
69
  let connection;
@@ -55,11 +104,20 @@ class PostgresConnection extends __1.AbstractConnection {
55
104
  }
56
105
  return this.poolClientInstance || this.connection;
57
106
  }
107
+ /**
108
+ * Get the connection ID for monitoring purposes
109
+ */
110
+ getConnectionId() {
111
+ return this.id;
112
+ }
58
113
  static async disconnectAll() {
114
+ //log stats
115
+ //this.logConnectionStats();
59
116
  if (!this.disconnecting) {
60
117
  this.disconnecting = true;
61
118
  await this.disconnectPoolClients();
62
119
  await this.disconnectConnections();
120
+ this.taskQueueConnections.clear();
63
121
  this.disconnecting = false;
64
122
  }
65
123
  }
@@ -81,6 +139,53 @@ class PostgresConnection extends __1.AbstractConnection {
81
139
  static isPoolClient(client) {
82
140
  return !(isNaN(client?.totalCount) && isNaN(client?.idleCount));
83
141
  }
142
+ /**
143
+ * Creates a taskQueue-based connection key for connection pooling.
144
+ * This allows multiple providers (store, sub, stream) to reuse the same connection
145
+ * when they share the same taskQueue and database configuration.
146
+ */
147
+ static createTaskQueueConnectionKey(taskQueue, options, provider) {
148
+ const configHash = (0, utils_1.hashOptions)(options);
149
+ const providerType = provider?.split('.')[0] || 'postgres';
150
+ return `${providerType}:${taskQueue || 'default'}:${configHash}`;
151
+ }
152
+ /**
153
+ * Gets or creates a PostgreSQL connection based on taskQueue and database configuration.
154
+ * If a connection already exists for the same taskQueue + config, it will be reused.
155
+ * This optimization reduces connection overhead for PostgreSQL providers.
156
+ */
157
+ static async getOrCreateTaskQueueConnection(id, taskQueue, clientConstructor, options, config = {}) {
158
+ // Only use taskQueue pooling for PostgreSQL providers
159
+ if (!taskQueue || !config.provider?.startsWith('postgres')) {
160
+ return await this.connect(id, clientConstructor, options, config);
161
+ }
162
+ const connectionKey = this.createTaskQueueConnectionKey(taskQueue, options, config.provider);
163
+ // Check if we already have a connection for this taskQueue + config
164
+ if (this.taskQueueConnections.has(connectionKey)) {
165
+ const existingConnection = this.taskQueueConnections.get(connectionKey);
166
+ // Track reuse count for monitoring
167
+ existingConnection.reusedCount = (existingConnection.reusedCount || 0) + 1;
168
+ this.logger.debug('postgres-connection-reused', {
169
+ connectionKey,
170
+ taskQueue,
171
+ originalId: existingConnection.id,
172
+ newId: id,
173
+ reuseCount: existingConnection.reusedCount,
174
+ });
175
+ return existingConnection;
176
+ }
177
+ // Create new connection and cache it for the taskQueue
178
+ const newConnection = await this.connect(id, clientConstructor, options, config);
179
+ // Initialize reuse tracking
180
+ newConnection.reusedCount = 0;
181
+ this.taskQueueConnections.set(connectionKey, newConnection);
182
+ this.logger.debug('postgres-connection-created-for-taskqueue', {
183
+ connectionKey,
184
+ taskQueue,
185
+ connectionId: id,
186
+ });
187
+ return newConnection;
188
+ }
84
189
  static async getTransactionClient(transactionClient) {
85
190
  let client;
86
191
  let type;
@@ -100,3 +205,5 @@ exports.PostgresConnection = PostgresConnection;
100
205
  PostgresConnection.poolClientInstances = new Set();
101
206
  //statically track all connections (//call 'end')
102
207
  PostgresConnection.connectionInstances = new Set();
208
+ //track connections by taskQueue + database config for reuse
209
+ PostgresConnection.taskQueueConnections = new Map();
@@ -264,6 +264,14 @@ declare class HotMesh {
264
264
  * @private
265
265
  */
266
266
  doWork(config: HotMeshConfig, logger: ILogger): Promise<void>;
267
+ /**
268
+ * Initialize task queue with proper precedence:
269
+ * 1. Use component-specific queue if set (engine/worker)
270
+ * 2. Use global config queue if set
271
+ * 3. Use default queue as fallback
272
+ * @private
273
+ */
274
+ private initTaskQueue;
267
275
  /**
268
276
  * Starts a workflow
269
277
  * @example
@@ -275,6 +275,8 @@ class HotMesh {
275
275
  if (config.engine.connection.readonly) {
276
276
  config.engine.readonly = true;
277
277
  }
278
+ // Initialize task queue for engine
279
+ config.engine.taskQueue = this.initTaskQueue(config.engine.taskQueue, config.taskQueue);
278
280
  await factory_1.ConnectorService.initClients(config.engine);
279
281
  this.engine = await engine_1.EngineService.init(this.namespace, this.appId, this.guid, config, logger);
280
282
  }
@@ -308,8 +310,33 @@ class HotMesh {
308
310
  * @private
309
311
  */
310
312
  async doWork(config, logger) {
313
+ // Initialize task queues for workers
314
+ if (config.workers) {
315
+ for (const worker of config.workers) {
316
+ worker.taskQueue = this.initTaskQueue(worker.taskQueue, config.taskQueue);
317
+ }
318
+ }
311
319
  this.workers = await worker_1.WorkerService.init(this.namespace, this.appId, this.guid, config, logger);
312
320
  }
321
+ /**
322
+ * Initialize task queue with proper precedence:
323
+ * 1. Use component-specific queue if set (engine/worker)
324
+ * 2. Use global config queue if set
325
+ * 3. Use default queue as fallback
326
+ * @private
327
+ */
328
+ initTaskQueue(componentQueue, globalQueue) {
329
+ // Component-specific queue takes precedence
330
+ if (componentQueue) {
331
+ return componentQueue;
332
+ }
333
+ // Global config queue is next
334
+ if (globalQueue) {
335
+ return globalQueue;
336
+ }
337
+ // Default queue as fallback
338
+ return enums_1.DEFAULT_TASK_QUEUE;
339
+ }
313
340
  // ************* PUB/SUB METHODS *************
314
341
  /**
315
342
  * Starts a workflow
@@ -60,7 +60,7 @@ export declare class ClientService {
60
60
  /**
61
61
  * @private
62
62
  */
63
- getHotMeshClient: (workflowTopic: string | null, namespace?: string) => Promise<HotMesh>;
63
+ getHotMeshClient: (taskQueue: string | null, namespace?: string) => Promise<HotMesh>;
64
64
  /**
65
65
  * Creates a stream where messages can be published to ensure there is a
66
66
  * channel in place when the message arrives (a race condition for those
@@ -54,7 +54,7 @@ class ClientService {
54
54
  /**
55
55
  * @private
56
56
  */
57
- this.getHotMeshClient = async (workflowTopic, namespace) => {
57
+ this.getHotMeshClient = async (taskQueue, namespace) => {
58
58
  //namespace isolation requires the connection options to be hashed
59
59
  //as multiple intersecting databases can be used by the same service
60
60
  //hashing options allows for reuse of the same connection without risk of
@@ -71,6 +71,7 @@ class ClientService {
71
71
  const readonly = this.connection.readonly ?? undefined;
72
72
  let hotMeshClient = hotmesh_1.HotMesh.init({
73
73
  appId: targetNS,
74
+ taskQueue,
74
75
  logLevel: enums_1.HMSH_LOGLEVEL,
75
76
  engine: {
76
77
  readonly,
@@ -130,7 +131,7 @@ class ClientService {
130
131
  const spn = options.workflowSpan;
131
132
  //hotmesh `topic` is equivalent to `queue+workflowname` pattern in other systems
132
133
  const workflowTopic = `${taskQueueName}-${workflowName}`;
133
- const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
134
+ const hotMeshClient = await this.getHotMeshClient(taskQueueName, options.namespace);
134
135
  //verify that the stream channel exists before enqueueing
135
136
  await this.verifyStream(hotMeshClient, workflowTopic, options.namespace);
136
137
  const payload = {
@@ -182,7 +183,8 @@ class ClientService {
182
183
  * ```
183
184
  */
184
185
  hook: async (options) => {
185
- const workflowTopic = `${options.taskQueue ?? options.entity}-${options.entity ?? options.workflowName}`;
186
+ const taskQueue = options.taskQueue ?? options.entity;
187
+ const workflowTopic = `${taskQueue}-${options.entity ?? options.workflowName}`;
186
188
  const payload = {
187
189
  arguments: [...options.args],
188
190
  id: options.workflowId,
@@ -192,7 +194,7 @@ class ClientService {
192
194
  maximumInterval: (0, utils_1.s)(options.config?.maximumInterval || enums_1.HMSH_MEMFLOW_MAX_INTERVAL),
193
195
  };
194
196
  //seed search data before entering
195
- const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
197
+ const hotMeshClient = await this.getHotMeshClient(taskQueue, options.namespace);
196
198
  const msgId = await hotMeshClient.hook(`${hotMeshClient.appId}.flow.signal`, payload, types_1.StreamStatus.PENDING, 202);
197
199
  //todo: commit search data BEFORE enqueuing hook
198
200
  if (options.search?.data) {
@@ -222,7 +224,7 @@ class ClientService {
222
224
  */
223
225
  getHandle: async (taskQueue, workflowName, workflowId, namespace) => {
224
226
  const workflowTopic = `${taskQueue}-${workflowName}`;
225
- const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
227
+ const hotMeshClient = await this.getHotMeshClient(taskQueue, namespace);
226
228
  return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, workflowId);
227
229
  },
228
230
  /**
@@ -249,7 +251,7 @@ class ClientService {
249
251
  */
250
252
  search: async (taskQueue, workflowName, namespace, index, ...query) => {
251
253
  const workflowTopic = `${taskQueue}-${workflowName}`;
252
- const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
254
+ const hotMeshClient = await this.getHotMeshClient(taskQueue, namespace);
253
255
  try {
254
256
  return await this.search(hotMeshClient, index, query);
255
257
  }
@@ -174,6 +174,7 @@ class WorkerService {
174
174
  const targetTopic = `${optionsHash}.${targetNamespace}.${activityTopic}`;
175
175
  const hotMeshWorker = await hotmesh_1.HotMesh.init({
176
176
  guid: config.guid ? `${config.guid}XA` : undefined,
177
+ taskQueue: config.taskQueue,
177
178
  logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
178
179
  appId: targetNamespace,
179
180
  engine: { connection: providerConfig },
@@ -258,6 +259,7 @@ class WorkerService {
258
259
  const targetTopic = `${optionsHash}.${targetNamespace}.${workflowTopic}`;
259
260
  const hotMeshWorker = await hotmesh_1.HotMesh.init({
260
261
  guid: config.guid,
262
+ taskQueue: config.taskQueue,
261
263
  logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
262
264
  appId: config.namespace ?? factory_1.APP_ID,
263
265
  engine: { connection: providerConfig },
@@ -518,6 +520,7 @@ WorkerService.getHotMesh = async (workflowTopic, config, options) => {
518
520
  const hotMeshClient = hotmesh_1.HotMesh.init({
519
521
  logLevel: options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
520
522
  appId: targetNamespace,
523
+ taskQueue: config.taskQueue,
521
524
  engine: {
522
525
  connection: { ...config?.connection },
523
526
  },
@@ -37,4 +37,4 @@ class CronHandler {
37
37
  }
38
38
  }
39
39
  exports.CronHandler = CronHandler;
40
- CronHandler.logger = new logger_1.LoggerService('hotmesh', 'meshos');
40
+ CronHandler.logger = new logger_1.LoggerService('hotmesh', 'cron');
@@ -23,15 +23,21 @@ const KVTables = (context) => ({
23
23
  client = transactionClient;
24
24
  }
25
25
  try {
26
- // Acquire advisory lock
26
+ // First, check if tables already exist (no lock needed)
27
+ const tablesExist = await this.checkIfTablesExist(client, appName);
28
+ if (tablesExist) {
29
+ // Tables already exist, no need to acquire lock or create tables
30
+ return;
31
+ }
32
+ // Tables don't exist, need to acquire lock and create them
27
33
  const lockId = this.getAdvisoryLockId(appName);
28
34
  const lockResult = await client.query('SELECT pg_try_advisory_lock($1) AS locked', [lockId]);
29
35
  if (lockResult.rows[0].locked) {
30
36
  // Begin transaction
31
37
  await client.query('BEGIN');
32
- // Check and create tables
33
- const tablesExist = await this.checkIfTablesExist(client, appName);
34
- if (!tablesExist) {
38
+ // Double-check tables don't exist (race condition safety)
39
+ const tablesStillMissing = !(await this.checkIfTablesExist(client, appName));
40
+ if (tablesStillMissing) {
35
41
  await this.createTables(client, appName);
36
42
  }
37
43
  // Commit transaction
@@ -90,11 +96,18 @@ const KVTables = (context) => ({
90
96
  client = transactionClient;
91
97
  }
92
98
  try {
99
+ // Check if tables exist directly (most efficient check)
100
+ const tablesExist = await this.checkIfTablesExist(client, appName);
101
+ if (tablesExist) {
102
+ // Tables now exist, deployment is complete
103
+ return;
104
+ }
105
+ // Fallback: check if the lock has been released (indicates completion)
93
106
  const lockCheck = await client.query("SELECT NOT EXISTS (SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND objid = $1::bigint) AS unlocked", [lockId]);
94
107
  if (lockCheck.rows[0].unlocked) {
95
108
  // Lock has been released, tables should exist now
96
- const tablesExist = await this.checkIfTablesExist(client, appName);
97
- if (tablesExist) {
109
+ const tablesExistAfterLock = await this.checkIfTablesExist(client, appName);
110
+ if (tablesExistAfterLock) {
98
111
  return;
99
112
  }
100
113
  }
@@ -1073,9 +1073,20 @@ class PostgresStoreService extends __1.StoreService {
1073
1073
  try {
1074
1074
  // Set up LISTEN for time notifications
1075
1075
  await this.pgClient.query(`LISTEN "${channelName}"`);
1076
- // Set up notification handler
1076
+ // Set up notification handler with channel filtering
1077
1077
  this.pgClient.on('notification', (notification) => {
1078
- this.handleTimeNotification(notification, timeEventCallback);
1078
+ // Only handle time notifications (channels starting with "time_hooks_")
1079
+ // Ignore sub and stream notifications from other providers
1080
+ if (notification.channel.startsWith('time_hooks_')) {
1081
+ this.handleTimeNotification(notification, timeEventCallback);
1082
+ }
1083
+ else {
1084
+ // This is likely a notification from sub or stream provider, ignore it
1085
+ this.logger.debug('postgres-store-ignoring-non-time-notification', {
1086
+ channel: notification.channel,
1087
+ payloadPreview: notification.payload.substring(0, 100)
1088
+ });
1089
+ }
1079
1090
  });
1080
1091
  this.logger.debug('postgres-time-scout-notifications-started', {
1081
1092
  appId: this.appId,
@@ -9,15 +9,18 @@ declare class PostgresStreamService extends StreamService<PostgresClientType & P
9
9
  namespace: string;
10
10
  appId: string;
11
11
  logger: ILogger;
12
- private notificationConsumers;
12
+ private static clientNotificationConsumers;
13
+ private static clientNotificationHandlers;
14
+ private static clientFallbackPollers;
15
+ private instanceNotificationConsumers;
13
16
  private notificationHandlerBound;
14
- private fallbackIntervalId;
15
17
  constructor(streamClient: PostgresClientType & ProviderClient, storeClient: ProviderClient, config?: StreamConfig);
16
18
  init(namespace: string, appId: string, logger: ILogger): Promise<void>;
19
+ private setupClientNotificationHandler;
20
+ private startClientFallbackPoller;
17
21
  private isNotificationsEnabled;
18
22
  private getFallbackInterval;
19
23
  private getNotificationTimeout;
20
- private startFallbackPoller;
21
24
  private checkForMissedMessages;
22
25
  private handleNotification;
23
26
  private fetchAndDeliverMessages;