@hotmeshio/hotmesh 0.5.4 → 0.5.6
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.
- package/README.md +185 -161
- package/build/package.json +3 -2
- package/build/services/activities/trigger.js +1 -1
- package/build/services/connector/factory.js +2 -1
- package/build/services/connector/providers/postgres.js +11 -6
- package/build/services/memflow/client.js +4 -2
- package/build/services/memflow/index.d.ts +154 -34
- package/build/services/memflow/index.js +165 -33
- package/build/services/memflow/interceptor.d.ts +241 -0
- package/build/services/memflow/interceptor.js +256 -0
- package/build/services/memflow/worker.js +10 -1
- package/build/services/memflow/workflow/execChild.js +3 -1
- package/build/services/memflow/workflow/execHook.js +1 -1
- package/build/services/memflow/workflow/hook.js +4 -2
- package/build/services/memflow/workflow/proxyActivities.js +2 -1
- package/build/services/router/consumption/index.js +23 -9
- package/build/services/router/error-handling/index.js +3 -3
- package/build/services/search/providers/postgres/postgres.js +47 -19
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +1 -1
- package/build/services/store/providers/postgres/kvtypes/hash/index.js +2 -2
- package/build/services/store/providers/postgres/kvtypes/hash/jsonb.js +11 -11
- package/build/services/store/providers/postgres/postgres.js +8 -8
- package/build/services/stream/providers/postgres/postgres.js +23 -20
- package/build/services/sub/providers/postgres/postgres.js +11 -3
- package/build/services/task/index.js +4 -4
- package/build/types/memflow.d.ts +78 -0
- package/package.json +3 -2
|
@@ -18,7 +18,7 @@ function createJsonbOperations(context) {
|
|
|
18
18
|
};
|
|
19
19
|
function handleContextSet(key, fields, options) {
|
|
20
20
|
const tableName = context.tableForKey(key, 'hash');
|
|
21
|
-
const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context');
|
|
21
|
+
const replayId = Object.keys(fields).find((k) => k.includes('-') && k !== '@context');
|
|
22
22
|
const params = [];
|
|
23
23
|
let sql = '';
|
|
24
24
|
if (options?.nx) {
|
|
@@ -93,7 +93,7 @@ function createJsonbOperations(context) {
|
|
|
93
93
|
}
|
|
94
94
|
function handleContextMerge(key, fields, options) {
|
|
95
95
|
const tableName = context.tableForKey(key, 'hash');
|
|
96
|
-
const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:merge');
|
|
96
|
+
const replayId = Object.keys(fields).find((k) => k.includes('-') && k !== '@context:merge');
|
|
97
97
|
const params = [];
|
|
98
98
|
let sql = '';
|
|
99
99
|
if (options?.nx) {
|
|
@@ -227,7 +227,7 @@ function createJsonbOperations(context) {
|
|
|
227
227
|
const tableName = context.tableForKey(key, 'hash');
|
|
228
228
|
const path = fields['@context:delete'];
|
|
229
229
|
const pathParts = path.split('.');
|
|
230
|
-
const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:delete');
|
|
230
|
+
const replayId = Object.keys(fields).find((k) => k.includes('-') && k !== '@context:delete');
|
|
231
231
|
const params = [];
|
|
232
232
|
let sql = '';
|
|
233
233
|
if (pathParts.length === 1) {
|
|
@@ -300,7 +300,7 @@ function createJsonbOperations(context) {
|
|
|
300
300
|
const tableName = context.tableForKey(key, 'hash');
|
|
301
301
|
const { path, value } = JSON.parse(fields['@context:append']);
|
|
302
302
|
const pathParts = path.split('.');
|
|
303
|
-
const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:append');
|
|
303
|
+
const replayId = Object.keys(fields).find((k) => k.includes('-') && k !== '@context:append');
|
|
304
304
|
const params = [];
|
|
305
305
|
let sql = '';
|
|
306
306
|
if (replayId) {
|
|
@@ -348,7 +348,7 @@ function createJsonbOperations(context) {
|
|
|
348
348
|
const tableName = context.tableForKey(key, 'hash');
|
|
349
349
|
const { path, value } = JSON.parse(fields['@context:prepend']);
|
|
350
350
|
const pathParts = path.split('.');
|
|
351
|
-
const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:prepend');
|
|
351
|
+
const replayId = Object.keys(fields).find((k) => k.includes('-') && k !== '@context:prepend');
|
|
352
352
|
const params = [];
|
|
353
353
|
let sql = '';
|
|
354
354
|
if (replayId) {
|
|
@@ -396,7 +396,7 @@ function createJsonbOperations(context) {
|
|
|
396
396
|
const tableName = context.tableForKey(key, 'hash');
|
|
397
397
|
const { path, index } = JSON.parse(fields['@context:remove']);
|
|
398
398
|
const pathParts = path.split('.');
|
|
399
|
-
const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:remove');
|
|
399
|
+
const replayId = Object.keys(fields).find((k) => k.includes('-') && k !== '@context:remove');
|
|
400
400
|
const params = [];
|
|
401
401
|
let sql = '';
|
|
402
402
|
if (replayId) {
|
|
@@ -458,7 +458,7 @@ function createJsonbOperations(context) {
|
|
|
458
458
|
const tableName = context.tableForKey(key, 'hash');
|
|
459
459
|
const { path, value } = JSON.parse(fields['@context:increment']);
|
|
460
460
|
const pathParts = path.split('.');
|
|
461
|
-
const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:increment');
|
|
461
|
+
const replayId = Object.keys(fields).find((k) => k.includes('-') && k !== '@context:increment');
|
|
462
462
|
const params = [];
|
|
463
463
|
let sql = '';
|
|
464
464
|
if (replayId) {
|
|
@@ -506,7 +506,7 @@ function createJsonbOperations(context) {
|
|
|
506
506
|
const tableName = context.tableForKey(key, 'hash');
|
|
507
507
|
const path = fields['@context:toggle'];
|
|
508
508
|
const pathParts = path.split('.');
|
|
509
|
-
const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:toggle');
|
|
509
|
+
const replayId = Object.keys(fields).find((k) => k.includes('-') && k !== '@context:toggle');
|
|
510
510
|
const params = [];
|
|
511
511
|
let sql = '';
|
|
512
512
|
if (replayId) {
|
|
@@ -554,7 +554,7 @@ function createJsonbOperations(context) {
|
|
|
554
554
|
const tableName = context.tableForKey(key, 'hash');
|
|
555
555
|
const { path, value } = JSON.parse(fields['@context:setIfNotExists']);
|
|
556
556
|
const pathParts = path.split('.');
|
|
557
|
-
const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:setIfNotExists');
|
|
557
|
+
const replayId = Object.keys(fields).find((k) => k.includes('-') && k !== '@context:setIfNotExists');
|
|
558
558
|
const params = [];
|
|
559
559
|
let sql = '';
|
|
560
560
|
if (replayId) {
|
|
@@ -598,7 +598,7 @@ function createJsonbOperations(context) {
|
|
|
598
598
|
}
|
|
599
599
|
function handleContextGetPath(key, fields, options) {
|
|
600
600
|
const tableName = context.tableForKey(key, 'hash');
|
|
601
|
-
const getField = Object.keys(fields).find(k => k.startsWith('@context:get:'));
|
|
601
|
+
const getField = Object.keys(fields).find((k) => k.startsWith('@context:get:'));
|
|
602
602
|
const pathKey = getField.replace('@context:get:', '');
|
|
603
603
|
const pathParts = JSON.parse(fields[getField]);
|
|
604
604
|
const params = [];
|
|
@@ -627,7 +627,7 @@ function createJsonbOperations(context) {
|
|
|
627
627
|
function handleContextGet(key, fields, options) {
|
|
628
628
|
const tableName = context.tableForKey(key, 'hash');
|
|
629
629
|
const path = fields['@context:get'];
|
|
630
|
-
const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:get');
|
|
630
|
+
const replayId = Object.keys(fields).find((k) => k.includes('-') && k !== '@context:get');
|
|
631
631
|
const params = [];
|
|
632
632
|
let sql = '';
|
|
633
633
|
if (path === '') {
|
|
@@ -1053,14 +1053,14 @@ class PostgresStoreService extends __1.StoreService {
|
|
|
1053
1053
|
this.logger.info('postgres-time-notifications-deployed', {
|
|
1054
1054
|
appId,
|
|
1055
1055
|
schemaName,
|
|
1056
|
-
message: 'Time-aware notifications ENABLED - using LISTEN/NOTIFY instead of polling'
|
|
1056
|
+
message: 'Time-aware notifications ENABLED - using LISTEN/NOTIFY instead of polling',
|
|
1057
1057
|
});
|
|
1058
1058
|
}
|
|
1059
1059
|
catch (error) {
|
|
1060
1060
|
this.logger.error('postgres-time-notifications-deploy-error', {
|
|
1061
1061
|
appId,
|
|
1062
1062
|
schemaName,
|
|
1063
|
-
error: error.message
|
|
1063
|
+
error: error.message,
|
|
1064
1064
|
});
|
|
1065
1065
|
// Don't throw - fall back to polling mode
|
|
1066
1066
|
}
|
|
@@ -1084,13 +1084,13 @@ class PostgresStoreService extends __1.StoreService {
|
|
|
1084
1084
|
// This is likely a notification from sub or stream provider, ignore it
|
|
1085
1085
|
this.logger.debug('postgres-store-ignoring-non-time-notification', {
|
|
1086
1086
|
channel: notification.channel,
|
|
1087
|
-
payloadPreview: notification.payload.substring(0, 100)
|
|
1087
|
+
payloadPreview: notification.payload.substring(0, 100),
|
|
1088
1088
|
});
|
|
1089
1089
|
}
|
|
1090
1090
|
});
|
|
1091
1091
|
this.logger.debug('postgres-time-scout-notifications-started', {
|
|
1092
1092
|
appId: this.appId,
|
|
1093
|
-
channelName
|
|
1093
|
+
channelName,
|
|
1094
1094
|
});
|
|
1095
1095
|
// Start the enhanced time scout loop
|
|
1096
1096
|
await this.processTimeHooksWithNotifications(timeEventCallback);
|
|
@@ -1098,7 +1098,7 @@ class PostgresStoreService extends __1.StoreService {
|
|
|
1098
1098
|
catch (error) {
|
|
1099
1099
|
this.logger.error('postgres-time-scout-notifications-error', {
|
|
1100
1100
|
appId: this.appId,
|
|
1101
|
-
error
|
|
1101
|
+
error,
|
|
1102
1102
|
});
|
|
1103
1103
|
// Fall back to regular polling mode
|
|
1104
1104
|
throw error;
|
|
@@ -1118,7 +1118,7 @@ class PostgresStoreService extends __1.StoreService {
|
|
|
1118
1118
|
type,
|
|
1119
1119
|
appId: app_id,
|
|
1120
1120
|
nextAwakening: next_awakening,
|
|
1121
|
-
readyAt: ready_at
|
|
1121
|
+
readyAt: ready_at,
|
|
1122
1122
|
});
|
|
1123
1123
|
if (type === 'time_hooks_ready') {
|
|
1124
1124
|
// Process any ready time hooks immediately
|
|
@@ -1132,7 +1132,7 @@ class PostgresStoreService extends __1.StoreService {
|
|
|
1132
1132
|
catch (error) {
|
|
1133
1133
|
this.logger.error('postgres-time-notification-handle-error', {
|
|
1134
1134
|
notification,
|
|
1135
|
-
error
|
|
1135
|
+
error,
|
|
1136
1136
|
});
|
|
1137
1137
|
}
|
|
1138
1138
|
}
|
|
@@ -1233,7 +1233,7 @@ class PostgresStoreService extends __1.StoreService {
|
|
|
1233
1233
|
// For now, just log the schedule update
|
|
1234
1234
|
this.logger.debug('postgres-time-schedule-updated', {
|
|
1235
1235
|
nextAwakening,
|
|
1236
|
-
currentTime: Date.now()
|
|
1236
|
+
currentTime: Date.now(),
|
|
1237
1237
|
});
|
|
1238
1238
|
}
|
|
1239
1239
|
/**
|
|
@@ -62,16 +62,17 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
62
62
|
if (!clientNotificationConsumers) {
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
-
for (const [consumerKey, instanceMap] of clientNotificationConsumers.entries()) {
|
|
65
|
+
for (const [consumerKey, instanceMap,] of clientNotificationConsumers.entries()) {
|
|
66
66
|
for (const [instance, consumer] of instanceMap.entries()) {
|
|
67
|
-
if (consumer.isListening &&
|
|
67
|
+
if (consumer.isListening &&
|
|
68
|
+
now - consumer.lastFallbackCheck > this.getFallbackInterval()) {
|
|
68
69
|
try {
|
|
69
70
|
const messages = await instance.fetchMessages(consumer.streamName, consumer.groupName, consumer.consumerName, { batchSize: 10, enableBackoff: false, maxRetries: 1 });
|
|
70
71
|
if (messages.length > 0) {
|
|
71
72
|
instance.logger.debug('postgres-stream-fallback-messages', {
|
|
72
73
|
streamName: consumer.streamName,
|
|
73
74
|
groupName: consumer.groupName,
|
|
74
|
-
messageCount: messages.length
|
|
75
|
+
messageCount: messages.length,
|
|
75
76
|
});
|
|
76
77
|
consumer.callback(messages);
|
|
77
78
|
}
|
|
@@ -81,7 +82,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
81
82
|
instance.logger.error('postgres-stream-fallback-error', {
|
|
82
83
|
streamName: consumer.streamName,
|
|
83
84
|
groupName: consumer.groupName,
|
|
84
|
-
error
|
|
85
|
+
error,
|
|
85
86
|
});
|
|
86
87
|
}
|
|
87
88
|
}
|
|
@@ -96,17 +97,19 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
96
97
|
// This is likely a pub/sub notification from the sub provider, ignore it
|
|
97
98
|
this.logger.debug('postgres-stream-ignoring-sub-notification', {
|
|
98
99
|
channel: notification.channel,
|
|
99
|
-
payloadPreview: notification.payload.substring(0, 100)
|
|
100
|
+
payloadPreview: notification.payload.substring(0, 100),
|
|
100
101
|
});
|
|
101
102
|
return;
|
|
102
103
|
}
|
|
103
104
|
this.logger.debug('postgres-stream-processing-notification', {
|
|
104
|
-
channel: notification.channel
|
|
105
|
+
channel: notification.channel,
|
|
105
106
|
});
|
|
106
107
|
const payload = JSON.parse(notification.payload);
|
|
107
108
|
const { stream_name, group_name } = payload;
|
|
108
109
|
if (!stream_name || !group_name) {
|
|
109
|
-
this.logger.warn('postgres-stream-invalid-notification', {
|
|
110
|
+
this.logger.warn('postgres-stream-invalid-notification', {
|
|
111
|
+
notification,
|
|
112
|
+
});
|
|
110
113
|
return;
|
|
111
114
|
}
|
|
112
115
|
const consumerKey = this.getConsumerKey(stream_name, group_name);
|
|
@@ -128,7 +131,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
128
131
|
catch (error) {
|
|
129
132
|
this.logger.error('postgres-stream-notification-parse-error', {
|
|
130
133
|
notification,
|
|
131
|
-
error
|
|
134
|
+
error,
|
|
132
135
|
});
|
|
133
136
|
}
|
|
134
137
|
}
|
|
@@ -143,7 +146,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
143
146
|
this.logger.error('postgres-stream-fetch-deliver-error', {
|
|
144
147
|
streamName: consumer.streamName,
|
|
145
148
|
groupName: consumer.groupName,
|
|
146
|
-
error
|
|
149
|
+
error,
|
|
147
150
|
});
|
|
148
151
|
}
|
|
149
152
|
}
|
|
@@ -298,7 +301,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
298
301
|
streamName,
|
|
299
302
|
groupName,
|
|
300
303
|
channelName,
|
|
301
|
-
listenDuration: Date.now() - listenStart
|
|
304
|
+
listenDuration: Date.now() - listenStart,
|
|
302
305
|
});
|
|
303
306
|
}
|
|
304
307
|
catch (error) {
|
|
@@ -306,7 +309,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
306
309
|
streamName,
|
|
307
310
|
groupName,
|
|
308
311
|
channelName,
|
|
309
|
-
error
|
|
312
|
+
error,
|
|
310
313
|
});
|
|
311
314
|
// Fall back to polling if LISTEN fails
|
|
312
315
|
return this.fetchMessages(streamName, groupName, consumerName, options);
|
|
@@ -319,7 +322,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
319
322
|
consumerName,
|
|
320
323
|
callback,
|
|
321
324
|
isListening: true,
|
|
322
|
-
lastFallbackCheck: Date.now()
|
|
325
|
+
lastFallbackCheck: Date.now(),
|
|
323
326
|
};
|
|
324
327
|
instanceMap.set(this, consumer);
|
|
325
328
|
// Track this consumer for cleanup
|
|
@@ -328,7 +331,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
328
331
|
streamName,
|
|
329
332
|
groupName,
|
|
330
333
|
instanceCount: instanceMap.size,
|
|
331
|
-
setupDuration: Date.now() - startTime
|
|
334
|
+
setupDuration: Date.now() - startTime,
|
|
332
335
|
});
|
|
333
336
|
// Do an initial fetch asynchronously to avoid blocking setup
|
|
334
337
|
// This ensures we don't miss any messages that were already in the queue
|
|
@@ -338,13 +341,13 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
338
341
|
const initialMessages = await this.fetchMessages(streamName, groupName, consumerName, {
|
|
339
342
|
...options,
|
|
340
343
|
enableBackoff: false,
|
|
341
|
-
maxRetries: 1
|
|
344
|
+
maxRetries: 1,
|
|
342
345
|
});
|
|
343
346
|
this.logger.debug('postgres-stream-initial-fetch-complete', {
|
|
344
347
|
streamName,
|
|
345
348
|
groupName,
|
|
346
349
|
messageCount: initialMessages.length,
|
|
347
|
-
fetchDuration: Date.now() - fetchStart
|
|
350
|
+
fetchDuration: Date.now() - fetchStart,
|
|
348
351
|
});
|
|
349
352
|
// If we got messages, call the callback
|
|
350
353
|
if (initialMessages.length > 0) {
|
|
@@ -355,7 +358,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
355
358
|
this.logger.error('postgres-stream-initial-fetch-error', {
|
|
356
359
|
streamName,
|
|
357
360
|
groupName,
|
|
358
|
-
error
|
|
361
|
+
error,
|
|
359
362
|
});
|
|
360
363
|
}
|
|
361
364
|
});
|
|
@@ -387,7 +390,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
387
390
|
this.logger.debug('postgres-stream-unlisten', {
|
|
388
391
|
streamName,
|
|
389
392
|
groupName,
|
|
390
|
-
channelName
|
|
393
|
+
channelName,
|
|
391
394
|
});
|
|
392
395
|
}
|
|
393
396
|
catch (error) {
|
|
@@ -395,7 +398,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
395
398
|
streamName,
|
|
396
399
|
groupName,
|
|
397
400
|
channelName,
|
|
398
|
-
error
|
|
401
|
+
error,
|
|
399
402
|
});
|
|
400
403
|
}
|
|
401
404
|
}
|
|
@@ -583,7 +586,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
583
586
|
this.logger.debug('postgres-stream-cleanup-unlisten', {
|
|
584
587
|
streamName: consumer.streamName,
|
|
585
588
|
groupName: consumer.groupName,
|
|
586
|
-
channelName
|
|
589
|
+
channelName,
|
|
587
590
|
});
|
|
588
591
|
}
|
|
589
592
|
catch (error) {
|
|
@@ -591,7 +594,7 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
591
594
|
streamName: consumer.streamName,
|
|
592
595
|
groupName: consumer.groupName,
|
|
593
596
|
channelName,
|
|
594
|
-
error
|
|
597
|
+
error,
|
|
595
598
|
});
|
|
596
599
|
}
|
|
597
600
|
}
|
|
@@ -37,7 +37,7 @@ class PostgresSubService extends index_1.SubService {
|
|
|
37
37
|
try {
|
|
38
38
|
const payload = JSON.parse(msg.payload || '{}');
|
|
39
39
|
// Call all callbacks registered for this channel across all SubService instances
|
|
40
|
-
callbacks.forEach(callback => {
|
|
40
|
+
callbacks.forEach((callback) => {
|
|
41
41
|
try {
|
|
42
42
|
callback(msg.channel, payload);
|
|
43
43
|
}
|
|
@@ -101,7 +101,11 @@ class PostgresSubService extends index_1.SubService {
|
|
|
101
101
|
callbacks.set(this, callback);
|
|
102
102
|
// Track this subscription for cleanup
|
|
103
103
|
this.instanceSubscriptions.add(safeKey);
|
|
104
|
-
this.logger.debug(`postgres-subscribe`, {
|
|
104
|
+
this.logger.debug(`postgres-subscribe`, {
|
|
105
|
+
originalKey,
|
|
106
|
+
safeKey,
|
|
107
|
+
totalCallbacks: callbacks.size,
|
|
108
|
+
});
|
|
105
109
|
}
|
|
106
110
|
async unsubscribe(keyType, appId, topic) {
|
|
107
111
|
const [originalKey, safeKey] = this.mintSafeKey(keyType, {
|
|
@@ -125,7 +129,11 @@ class PostgresSubService extends index_1.SubService {
|
|
|
125
129
|
clientSubscriptions.delete(safeKey);
|
|
126
130
|
await this.eventClient.query(`UNLISTEN "${safeKey}"`);
|
|
127
131
|
}
|
|
128
|
-
this.logger.debug(`postgres-unsubscribe`, {
|
|
132
|
+
this.logger.debug(`postgres-unsubscribe`, {
|
|
133
|
+
originalKey,
|
|
134
|
+
safeKey,
|
|
135
|
+
remainingCallbacks: callbacks.size,
|
|
136
|
+
});
|
|
129
137
|
}
|
|
130
138
|
/**
|
|
131
139
|
* Cleanup method to remove all subscriptions for this instance.
|
|
@@ -211,7 +211,7 @@ class TaskService {
|
|
|
211
211
|
try {
|
|
212
212
|
this.logger.info('task-using-notification-mode', {
|
|
213
213
|
appId: this.store.appId,
|
|
214
|
-
message: 'Time scout using PostgreSQL LISTEN/NOTIFY mode for efficient task processing'
|
|
214
|
+
message: 'Time scout using PostgreSQL LISTEN/NOTIFY mode for efficient task processing',
|
|
215
215
|
});
|
|
216
216
|
// Use the PostgreSQL store's notification-based approach
|
|
217
217
|
await this.store.startTimeScoutWithNotifications(timeEventCallback);
|
|
@@ -221,7 +221,7 @@ class TaskService {
|
|
|
221
221
|
appId: this.store.appId,
|
|
222
222
|
error: error.message,
|
|
223
223
|
fallbackTo: 'polling',
|
|
224
|
-
message: 'Notification mode failed - falling back to traditional polling'
|
|
224
|
+
message: 'Notification mode failed - falling back to traditional polling',
|
|
225
225
|
});
|
|
226
226
|
// Fall back to regular polling
|
|
227
227
|
await this.processTimeHooks(timeEventCallback);
|
|
@@ -231,7 +231,7 @@ class TaskService {
|
|
|
231
231
|
this.logger.info('task-using-polling-mode', {
|
|
232
232
|
appId: this.store.appId,
|
|
233
233
|
storeType: this.store.constructor.name,
|
|
234
|
-
message: 'Time scout using traditional polling mode (notifications not available)'
|
|
234
|
+
message: 'Time scout using traditional polling mode (notifications not available)',
|
|
235
235
|
});
|
|
236
236
|
// Use regular polling for non-PostgreSQL stores
|
|
237
237
|
await this.processTimeHooks(timeEventCallback);
|
|
@@ -247,7 +247,7 @@ class TaskService {
|
|
|
247
247
|
* Check if the store supports notifications
|
|
248
248
|
*/
|
|
249
249
|
supportsNotifications() {
|
|
250
|
-
return typeof this.store.startTimeScoutWithNotifications === 'function';
|
|
250
|
+
return (typeof this.store.startTimeScoutWithNotifications === 'function');
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
exports.TaskService = TaskService;
|
package/build/types/memflow.d.ts
CHANGED
|
@@ -495,4 +495,82 @@ interface ClientWorkflow {
|
|
|
495
495
|
getHandle(taskQueue: string, workflowName: string, workflowId: string, namespace?: string): Promise<WorkflowHandleService>;
|
|
496
496
|
search(taskQueue: string, workflowName: string, namespace: string | null, index: string, ...query: string[]): Promise<string[]>;
|
|
497
497
|
}
|
|
498
|
+
/**
|
|
499
|
+
* Workflow interceptor that can wrap workflow execution in an onion-like pattern.
|
|
500
|
+
* Each interceptor wraps the next one, with the actual workflow execution at the center.
|
|
501
|
+
*
|
|
502
|
+
* Interceptors are executed in the order they are registered. Each interceptor can:
|
|
503
|
+
* - Perform actions before workflow execution
|
|
504
|
+
* - Modify or enhance the workflow context
|
|
505
|
+
* - Handle or transform workflow results
|
|
506
|
+
* - Catch and handle errors
|
|
507
|
+
* - Add cross-cutting concerns like logging, metrics, or tracing
|
|
508
|
+
*
|
|
509
|
+
* @example
|
|
510
|
+
* ```typescript
|
|
511
|
+
* // Simple logging interceptor
|
|
512
|
+
* const loggingInterceptor: WorkflowInterceptor = {
|
|
513
|
+
* async execute(ctx, next) {
|
|
514
|
+
* console.log('Before workflow');
|
|
515
|
+
* try {
|
|
516
|
+
* const result = await next();
|
|
517
|
+
* console.log('After workflow');
|
|
518
|
+
* return result;
|
|
519
|
+
* } catch (err) {
|
|
520
|
+
* console.error('Workflow error:', err);
|
|
521
|
+
* throw err;
|
|
522
|
+
* }
|
|
523
|
+
* }
|
|
524
|
+
* };
|
|
525
|
+
*
|
|
526
|
+
* // Register the interceptor
|
|
527
|
+
* MemFlow.registerInterceptor(loggingInterceptor);
|
|
528
|
+
* ```
|
|
529
|
+
*/
|
|
530
|
+
export interface WorkflowInterceptor {
|
|
531
|
+
/**
|
|
532
|
+
* Called before workflow execution to wrap the workflow in custom logic
|
|
533
|
+
*
|
|
534
|
+
* @param ctx - The workflow context map containing workflow metadata and state
|
|
535
|
+
* @param next - Function to call the next interceptor or the workflow itself
|
|
536
|
+
* @returns The result of the workflow execution
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* ```typescript
|
|
540
|
+
* // Metrics interceptor implementation
|
|
541
|
+
* {
|
|
542
|
+
* async execute(ctx, next) {
|
|
543
|
+
* const workflowName = ctx.get('workflowName');
|
|
544
|
+
* const metrics = getMetricsClient();
|
|
545
|
+
*
|
|
546
|
+
* metrics.increment(`workflow.start.${workflowName}`);
|
|
547
|
+
* const timer = metrics.startTimer();
|
|
548
|
+
*
|
|
549
|
+
* try {
|
|
550
|
+
* const result = await next();
|
|
551
|
+
* metrics.increment(`workflow.success.${workflowName}`);
|
|
552
|
+
* return result;
|
|
553
|
+
* } catch (err) {
|
|
554
|
+
* metrics.increment(`workflow.error.${workflowName}`);
|
|
555
|
+
* throw err;
|
|
556
|
+
* } finally {
|
|
557
|
+
* timer.end();
|
|
558
|
+
* }
|
|
559
|
+
* }
|
|
560
|
+
* }
|
|
561
|
+
* ```
|
|
562
|
+
*/
|
|
563
|
+
execute(ctx: Map<string, any>, next: () => Promise<any>): Promise<any>;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Registry for workflow interceptors that are executed in order
|
|
567
|
+
* for each workflow execution
|
|
568
|
+
*/
|
|
569
|
+
export interface InterceptorRegistry {
|
|
570
|
+
/**
|
|
571
|
+
* Array of registered interceptors that will wrap workflow execution
|
|
572
|
+
* in the order they were registered (first registered = outermost wrapper)
|
|
573
|
+
*/
|
|
574
|
+
interceptors: WorkflowInterceptor[];
|
|
575
|
+
}
|
|
498
576
|
export { ActivityConfig, ActivityWorkflowDataType, ChildResponseType, ClientConfig, ClientWorkflow, ContextType, Connection, ProxyResponseType, ProxyType, Registry, SignalOptions, FindJobsOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, SearchResults, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, WorkflowContext, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "Permanent-Memory Workflows & AI Agents",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"test:memflow:basic": "HMSH_LOGLEVEL=info NODE_ENV=test jest ./tests/memflow/basic/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
32
32
|
"test:memflow:collision": "NODE_ENV=test jest ./tests/memflow/collision/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
33
33
|
"test:memflow:fatal": "NODE_ENV=test jest ./tests/memflow/fatal/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
34
|
-
"test:memflow:goodbye": "NODE_ENV=test jest ./tests/memflow/goodbye
|
|
34
|
+
"test:memflow:goodbye": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/goodbye/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
35
|
+
"test:memflow:interceptor": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/interceptor/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
35
36
|
"test:memflow:entity": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/entity/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
36
37
|
"test:memflow:agent": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/agent/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
37
38
|
"test:memflow:hello": "HMSH_TELEMETRY=debug HMSH_LOGLEVEL=debug HMSH_IS_CLUSTER=true NODE_ENV=test jest ./tests/memflow/helloworld/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|