@hotmeshio/hotmesh 0.14.7 → 0.14.9
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/build/modules/utils.js +8 -2
- package/build/package.json +1 -1
- package/build/services/activities/worker.js +14 -0
- package/build/services/durable/exporter.js +2 -2
- package/build/services/durable/schemas/factory.d.ts +1 -19
- package/build/services/durable/schemas/factory.js +122 -524
- package/build/services/durable/worker.js +25 -14
- package/build/services/pipe/functions/cron.d.ts +5 -8
- package/build/services/pipe/functions/cron.js +14 -27
- package/build/services/router/consumption/index.js +14 -5
- package/build/services/router/error-handling/index.d.ts +1 -1
- package/build/services/router/error-handling/index.js +12 -11
- package/build/services/store/providers/postgres/exporter-sql.d.ts +4 -0
- package/build/services/store/providers/postgres/exporter-sql.js +11 -1
- package/build/services/store/providers/postgres/postgres.js +24 -0
- package/build/services/stream/providers/postgres/postgres.d.ts +6 -0
- package/build/services/stream/providers/postgres/postgres.js +25 -0
- package/build/services/virtual/index.js +4 -2
- package/package.json +1 -1
|
@@ -377,15 +377,13 @@ class WorkerService {
|
|
|
377
377
|
//use code 599 as a proxy for all retryable errors
|
|
378
378
|
// (basically anything not 596, 597, 598)
|
|
379
379
|
return {
|
|
380
|
-
status: stream_1.StreamStatus.
|
|
380
|
+
status: stream_1.StreamStatus.ERROR,
|
|
381
381
|
code: 599,
|
|
382
382
|
metadata: { ...data.metadata },
|
|
383
383
|
data: {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
code: enums_1.HMSH_CODE_DURABLE_RETRYABLE,
|
|
388
|
-
},
|
|
384
|
+
message: err.message,
|
|
385
|
+
stack: err.stack,
|
|
386
|
+
code: enums_1.HMSH_CODE_DURABLE_RETRYABLE,
|
|
389
387
|
},
|
|
390
388
|
};
|
|
391
389
|
}
|
|
@@ -607,15 +605,13 @@ class WorkerService {
|
|
|
607
605
|
//use code 599 as a proxy for all retryable errors
|
|
608
606
|
// (basically anything not 596, 597, 598)
|
|
609
607
|
return {
|
|
610
|
-
status: stream_1.StreamStatus.
|
|
608
|
+
status: stream_1.StreamStatus.ERROR,
|
|
611
609
|
code: enums_1.HMSH_CODE_DURABLE_RETRYABLE,
|
|
612
610
|
metadata: { ...data.metadata },
|
|
613
611
|
data: {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
timestamp: (0, utils_1.formatISODate)(new Date()),
|
|
618
|
-
},
|
|
612
|
+
message: err.message,
|
|
613
|
+
stack: err.stack,
|
|
614
|
+
code: enums_1.HMSH_CODE_DURABLE_RETRYABLE,
|
|
619
615
|
},
|
|
620
616
|
};
|
|
621
617
|
}
|
|
@@ -963,9 +959,24 @@ class WorkerService {
|
|
|
963
959
|
}, telemetry_3.SpanStatusCode.ERROR, err.message);
|
|
964
960
|
}
|
|
965
961
|
isProcessing = true;
|
|
962
|
+
const errorCode = err.code || new errors_1.DurableRetryError(err.message).code;
|
|
963
|
+
if (errorCode === enums_1.HMSH_CODE_DURABLE_RETRYABLE) {
|
|
964
|
+
// Retryable errors use status: ERROR so the engine-level
|
|
965
|
+
// retry mechanism (handleRetry + _streamRetryConfig) kicks in
|
|
966
|
+
return withPatchMarkers({
|
|
967
|
+
status: stream_1.StreamStatus.ERROR,
|
|
968
|
+
code: errorCode,
|
|
969
|
+
metadata: { ...data.metadata },
|
|
970
|
+
data: {
|
|
971
|
+
message: err.message,
|
|
972
|
+
stack: err.stack,
|
|
973
|
+
code: errorCode,
|
|
974
|
+
},
|
|
975
|
+
});
|
|
976
|
+
}
|
|
966
977
|
return withPatchMarkers({
|
|
967
978
|
status: stream_1.StreamStatus.SUCCESS,
|
|
968
|
-
code:
|
|
979
|
+
code: errorCode,
|
|
969
980
|
metadata: { ...data.metadata },
|
|
970
981
|
data: {
|
|
971
982
|
$error: {
|
|
@@ -973,7 +984,7 @@ class WorkerService {
|
|
|
973
984
|
type: err.name,
|
|
974
985
|
name: err.name,
|
|
975
986
|
stack: err.stack,
|
|
976
|
-
code:
|
|
987
|
+
code: errorCode,
|
|
977
988
|
},
|
|
978
989
|
},
|
|
979
990
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ILogger } from '../../../types/logger';
|
|
2
1
|
/**
|
|
3
2
|
* Provides cron-related utility functions based on the
|
|
4
3
|
* [cron](https://en.wikipedia.org/wiki/Cron) format for use
|
|
@@ -8,16 +7,14 @@ import { ILogger } from '../../../types/logger';
|
|
|
8
7
|
* Invoked in mapping rules using `{@cron.<method>}` syntax.
|
|
9
8
|
*/
|
|
10
9
|
declare class CronHandler {
|
|
11
|
-
static logger: ILogger;
|
|
12
10
|
/**
|
|
13
|
-
*
|
|
14
|
-
* of a cron job.
|
|
15
|
-
*
|
|
16
|
-
* in seconds from now. Fails silently and returns `-1` if the cron
|
|
17
|
-
* expression is invalid or the next execution time is in the past.
|
|
11
|
+
* Calculates the delay in seconds until the next execution
|
|
12
|
+
* of a cron job. Throws on invalid expressions rather than
|
|
13
|
+
* degrading silently.
|
|
18
14
|
*
|
|
19
15
|
* @param {string} cronExpression - The cron expression to parse (e.g. `'0 0 * * *'`)
|
|
20
|
-
* @returns {number} The delay in seconds until the next cron job execution (minimum `HMSH_FIDELITY_SECONDS`)
|
|
16
|
+
* @returns {number} The delay in seconds until the next cron job execution (minimum `HMSH_FIDELITY_SECONDS`)
|
|
17
|
+
* @throws {Error} If the cron expression is invalid
|
|
21
18
|
* @example
|
|
22
19
|
* ```yaml
|
|
23
20
|
* cron_next_result:
|
|
@@ -4,7 +4,6 @@ exports.CronHandler = void 0;
|
|
|
4
4
|
const cron_parser_1 = require("cron-parser");
|
|
5
5
|
const enums_1 = require("../../../modules/enums");
|
|
6
6
|
const utils_1 = require("../../../modules/utils");
|
|
7
|
-
const logger_1 = require("../../logger");
|
|
8
7
|
/**
|
|
9
8
|
* Provides cron-related utility functions based on the
|
|
10
9
|
* [cron](https://en.wikipedia.org/wiki/Cron) format for use
|
|
@@ -15,14 +14,13 @@ const logger_1 = require("../../logger");
|
|
|
15
14
|
*/
|
|
16
15
|
class CronHandler {
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
19
|
-
* of a cron job.
|
|
20
|
-
*
|
|
21
|
-
* in seconds from now. Fails silently and returns `-1` if the cron
|
|
22
|
-
* expression is invalid or the next execution time is in the past.
|
|
17
|
+
* Calculates the delay in seconds until the next execution
|
|
18
|
+
* of a cron job. Throws on invalid expressions rather than
|
|
19
|
+
* degrading silently.
|
|
23
20
|
*
|
|
24
21
|
* @param {string} cronExpression - The cron expression to parse (e.g. `'0 0 * * *'`)
|
|
25
|
-
* @returns {number} The delay in seconds until the next cron job execution (minimum `HMSH_FIDELITY_SECONDS`)
|
|
22
|
+
* @returns {number} The delay in seconds until the next cron job execution (minimum `HMSH_FIDELITY_SECONDS`)
|
|
23
|
+
* @throws {Error} If the cron expression is invalid
|
|
26
24
|
* @example
|
|
27
25
|
* ```yaml
|
|
28
26
|
* cron_next_result:
|
|
@@ -32,28 +30,17 @@ class CronHandler {
|
|
|
32
30
|
* ```
|
|
33
31
|
*/
|
|
34
32
|
nextDelay(cronExpression) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return -1;
|
|
38
|
-
}
|
|
39
|
-
const interval = (0, cron_parser_1.parseExpression)(cronExpression, { utc: true });
|
|
40
|
-
const nextDate = interval.next().toDate();
|
|
41
|
-
const now = new Date();
|
|
42
|
-
const delay = (nextDate.getTime() - now.getTime()) / 1000;
|
|
43
|
-
if (delay <= 0) {
|
|
44
|
-
return -1;
|
|
45
|
-
}
|
|
46
|
-
if (delay < enums_1.HMSH_FIDELITY_SECONDS) {
|
|
47
|
-
return enums_1.HMSH_FIDELITY_SECONDS;
|
|
48
|
-
}
|
|
49
|
-
const iDelay = Math.round(delay);
|
|
50
|
-
return iDelay;
|
|
33
|
+
if (!(0, utils_1.isValidCron)(cronExpression)) {
|
|
34
|
+
throw new Error(`Invalid cron expression: ${cronExpression}`);
|
|
51
35
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
const interval = (0, cron_parser_1.parseExpression)(cronExpression, { utc: true });
|
|
37
|
+
const nextDate = interval.next().toDate();
|
|
38
|
+
const now = new Date();
|
|
39
|
+
const delay = (nextDate.getTime() - now.getTime()) / 1000;
|
|
40
|
+
if (delay <= 0) {
|
|
41
|
+
return enums_1.HMSH_FIDELITY_SECONDS;
|
|
55
42
|
}
|
|
43
|
+
return Math.max(Math.round(delay), enums_1.HMSH_FIDELITY_SECONDS);
|
|
56
44
|
}
|
|
57
45
|
}
|
|
58
46
|
exports.CronHandler = CronHandler;
|
|
59
|
-
CronHandler.logger = new logger_1.LoggerService('hotmesh', 'cron');
|
|
@@ -585,14 +585,23 @@ class ConsumptionManager {
|
|
|
585
585
|
// Extract retry policy with priority:
|
|
586
586
|
// 1. Use message-level _streamRetryConfig (from database columns or previous retry)
|
|
587
587
|
// 2. Fall back to router-level retry (from worker config)
|
|
588
|
-
const
|
|
588
|
+
const streamRetryConfig = input._streamRetryConfig;
|
|
589
|
+
const retry = streamRetryConfig
|
|
589
590
|
? {
|
|
590
|
-
maximumAttempts:
|
|
591
|
-
backoffCoefficient:
|
|
592
|
-
maximumInterval:
|
|
591
|
+
maximumAttempts: streamRetryConfig.max_retry_attempts,
|
|
592
|
+
backoffCoefficient: streamRetryConfig.backoff_coefficient,
|
|
593
|
+
maximumInterval: streamRetryConfig.maximum_interval_seconds,
|
|
594
|
+
initialInterval: streamRetryConfig.initialInterval ?? input.data?.initialInterval ?? 1,
|
|
593
595
|
}
|
|
594
596
|
: this.retry;
|
|
595
|
-
return await this.errorHandler.handleRetry(input, output, this.publishMessage.bind(this), retry)
|
|
597
|
+
return await this.errorHandler.handleRetry(input, output, this.publishMessage.bind(this), retry, (topic, delayMs) => {
|
|
598
|
+
// Schedule a targeted NOTIFY so the consumer wakes up
|
|
599
|
+
// when the visibility-delayed retry message becomes visible
|
|
600
|
+
if (typeof this.stream.scheduleStreamNotify === 'function') {
|
|
601
|
+
const streamKey = this.stream.mintKey(key_1.KeyType.STREAMS, { topic });
|
|
602
|
+
this.stream.scheduleStreamNotify(streamKey, delayMs);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
596
605
|
}
|
|
597
606
|
else if (typeof output.metadata !== 'object') {
|
|
598
607
|
output.metadata = { ...input.metadata, guid: (0, utils_1.guid)() };
|
|
@@ -4,5 +4,5 @@ export declare class ErrorHandler {
|
|
|
4
4
|
structureUnhandledError(input: StreamData, err: Error): StreamDataResponse;
|
|
5
5
|
structureUnacknowledgedError(input: StreamData): StreamDataResponse;
|
|
6
6
|
structureError(input: StreamData, output: StreamDataResponse): StreamDataResponse;
|
|
7
|
-
handleRetry(input: StreamData, output: StreamDataResponse, publishMessage: (topic: string, streamData: StreamData | StreamDataResponse) => Promise<string>, retry?: RetryPolicy): Promise<string>;
|
|
7
|
+
handleRetry(input: StreamData, output: StreamDataResponse, publishMessage: (topic: string, streamData: StreamData | StreamDataResponse) => Promise<string>, retry?: RetryPolicy, onRetryScheduled?: (topic: string, delayMs: number) => void): Promise<string>;
|
|
8
8
|
}
|
|
@@ -14,16 +14,11 @@ class ErrorHandler {
|
|
|
14
14
|
const maxInterval = typeof retry.maximumInterval === 'string'
|
|
15
15
|
? parseInt(retry.maximumInterval)
|
|
16
16
|
: (retry.maximumInterval || 120);
|
|
17
|
-
|
|
18
|
-
// tryCount=0 is 1st attempt, tryCount=1 is 2nd attempt, etc.
|
|
19
|
-
// So after tryCount, we've made (tryCount + 1) attempts
|
|
20
|
-
// We can retry if (tryCount + 1) < maxAttempts
|
|
17
|
+
const initialIntervalS = retry.initialInterval || 1;
|
|
21
18
|
if ((tryCount + 1) < maxAttempts) {
|
|
22
|
-
// Exponential backoff: min(coefficient^(try+1), maxInterval)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const backoffSeconds = Math.min(Math.pow(backoffCoeff, tryCount + 1), maxInterval);
|
|
26
|
-
return [true, backoffSeconds * 1000]; // Convert to milliseconds
|
|
19
|
+
// Exponential backoff: min(initialInterval * coefficient^(try+1), maxInterval)
|
|
20
|
+
const backoffSeconds = Math.min(initialIntervalS * Math.pow(backoffCoeff, tryCount + 1), maxInterval);
|
|
21
|
+
return [true, backoffSeconds * 1000];
|
|
27
22
|
}
|
|
28
23
|
return [false, 0];
|
|
29
24
|
}
|
|
@@ -101,7 +96,7 @@ class ErrorHandler {
|
|
|
101
96
|
data,
|
|
102
97
|
};
|
|
103
98
|
}
|
|
104
|
-
async handleRetry(input, output, publishMessage, retry) {
|
|
99
|
+
async handleRetry(input, output, publishMessage, retry, onRetryScheduled) {
|
|
105
100
|
const [shouldRetry, timeout] = this.shouldRetry(input, output, retry);
|
|
106
101
|
if (shouldRetry) {
|
|
107
102
|
// Only sleep if no retry (legacy behavior for backward compatibility)
|
|
@@ -126,7 +121,13 @@ class ErrorHandler {
|
|
|
126
121
|
// Track retry attempt count in database
|
|
127
122
|
const currentAttempt = input._retryAttempt || 0;
|
|
128
123
|
newMessage._retryAttempt = currentAttempt + 1;
|
|
129
|
-
|
|
124
|
+
const messageId = (await publishMessage(input.metadata.topic, newMessage));
|
|
125
|
+
// Schedule a targeted NOTIFY so the consumer wakes up when
|
|
126
|
+
// the visibility-delayed message becomes visible
|
|
127
|
+
if (retry && timeout > 0 && onRetryScheduled) {
|
|
128
|
+
onRetryScheduled(input.metadata.topic, timeout);
|
|
129
|
+
}
|
|
130
|
+
return messageId;
|
|
130
131
|
}
|
|
131
132
|
else {
|
|
132
133
|
const structuredError = this.structureError(input, output);
|
|
@@ -15,6 +15,10 @@ export declare const GET_JOB_ATTRIBUTES = "\n SELECT symbol || dimension AS fie
|
|
|
15
15
|
* Matches all activity jobs for the given workflow and extracts their input arguments.
|
|
16
16
|
*/
|
|
17
17
|
export declare const GET_ACTIVITY_INPUTS = "\n SELECT j.key, ja.value\n FROM {schema}.jobs j\n JOIN {schema}.jobs_attributes ja ON ja.job_id = j.id\n WHERE j.key LIKE $1\n AND ja.symbol = $2 AND ja.dimension = $3\n";
|
|
18
|
+
/**
|
|
19
|
+
* Fetch activity inputs from worker_streams for direct worker proxyer activities.
|
|
20
|
+
*/
|
|
21
|
+
export declare const GET_PROXYER_STREAM_INPUTS = "\n SELECT message\n FROM {schema}.worker_streams\n WHERE jid = $1\n AND aid IN ('proxyer', 'collator_proxyer', 'signaler_proxyer')\n ORDER BY created_at, id\n";
|
|
18
22
|
/**
|
|
19
23
|
* Fetch all worker stream messages for a job AND its child activities.
|
|
20
24
|
* Child activity jobs use the pattern: -{parentJobId}-$activityName-N
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* These queries support the exporter's input enrichment and direct query features.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.buildChildWorkflowInputsQuery = exports.GET_STREAM_HISTORY_BY_JID_AND_AID = exports.GET_STREAM_HISTORY_BY_JID_AND_TYPE = exports.GET_STREAM_HISTORY_BY_JID = exports.GET_ACTIVITY_INPUTS = exports.GET_JOB_ATTRIBUTES = exports.GET_JOB_BY_KEY = void 0;
|
|
7
|
+
exports.buildChildWorkflowInputsQuery = exports.GET_STREAM_HISTORY_BY_JID_AND_AID = exports.GET_STREAM_HISTORY_BY_JID_AND_TYPE = exports.GET_STREAM_HISTORY_BY_JID = exports.GET_PROXYER_STREAM_INPUTS = exports.GET_ACTIVITY_INPUTS = exports.GET_JOB_ATTRIBUTES = exports.GET_JOB_BY_KEY = void 0;
|
|
8
8
|
/**
|
|
9
9
|
* Fetch job record by key.
|
|
10
10
|
*/
|
|
@@ -34,6 +34,16 @@ exports.GET_ACTIVITY_INPUTS = `
|
|
|
34
34
|
WHERE j.key LIKE $1
|
|
35
35
|
AND ja.symbol = $2 AND ja.dimension = $3
|
|
36
36
|
`;
|
|
37
|
+
/**
|
|
38
|
+
* Fetch activity inputs from worker_streams for direct worker proxyer activities.
|
|
39
|
+
*/
|
|
40
|
+
exports.GET_PROXYER_STREAM_INPUTS = `
|
|
41
|
+
SELECT message
|
|
42
|
+
FROM {schema}.worker_streams
|
|
43
|
+
WHERE jid = $1
|
|
44
|
+
AND aid IN ('proxyer', 'collator_proxyer', 'signaler_proxyer')
|
|
45
|
+
ORDER BY created_at, id
|
|
46
|
+
`;
|
|
37
47
|
/**
|
|
38
48
|
* Fetch all worker stream messages for a job AND its child activities.
|
|
39
49
|
* Child activity jobs use the pattern: -{parentJobId}-$activityName-N
|
|
@@ -1442,6 +1442,30 @@ class PostgresStoreService extends __1.StoreService {
|
|
|
1442
1442
|
// Skip unparseable values
|
|
1443
1443
|
}
|
|
1444
1444
|
}
|
|
1445
|
+
// If no results from legacy approach, try direct worker approach:
|
|
1446
|
+
// extract arguments from proxyer messages in worker_streams
|
|
1447
|
+
if (byNameIndex.size === 0) {
|
|
1448
|
+
const { GET_PROXYER_STREAM_INPUTS } = await Promise.resolve().then(() => __importStar(require('./exporter-sql')));
|
|
1449
|
+
const streamSql = GET_PROXYER_STREAM_INPUTS.replace(/{schema}/g, schemaName);
|
|
1450
|
+
const streamResult = await this.pgClient.query(streamSql, [workflowId]);
|
|
1451
|
+
for (const row of streamResult.rows) {
|
|
1452
|
+
try {
|
|
1453
|
+
const msg = typeof row.message === 'string' ? JSON.parse(row.message) : row.message;
|
|
1454
|
+
const data = msg?.data;
|
|
1455
|
+
if (data?.activityName && data?.arguments) {
|
|
1456
|
+
const activityName = data.activityName;
|
|
1457
|
+
const wfId = data.workflowId || '';
|
|
1458
|
+
const idxMatch = wfId.match(/-(\d+)$/);
|
|
1459
|
+
const execIndex = idxMatch ? idxMatch[1] : '0';
|
|
1460
|
+
byNameIndex.set(`${activityName}:${execIndex}`, data.arguments);
|
|
1461
|
+
byJobId.set(wfId, data.arguments);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
catch {
|
|
1465
|
+
// Skip unparseable messages
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1445
1469
|
return { byJobId, byNameIndex };
|
|
1446
1470
|
}
|
|
1447
1471
|
/**
|
|
@@ -58,6 +58,12 @@ declare class PostgresStreamService extends StreamService<PostgresClientType & P
|
|
|
58
58
|
* added to the transaction for atomic execution.
|
|
59
59
|
*/
|
|
60
60
|
publishMessages(streamName: string, messages: string[], options?: PublishMessageConfig): Promise<string[] | ProviderTransaction>;
|
|
61
|
+
/**
|
|
62
|
+
* Schedule a NOTIFY for a worker stream after a delay. Used to wake up
|
|
63
|
+
* consumers when a visibility-delayed retry message becomes visible,
|
|
64
|
+
* avoiding the need to wait for the scout's fallback poll.
|
|
65
|
+
*/
|
|
66
|
+
scheduleStreamNotify(streamName: string, delayMs: number): void;
|
|
61
67
|
_publishMessages(streamName: string, messages: string[], options?: PublishMessageConfig): {
|
|
62
68
|
sql: string;
|
|
63
69
|
params: any[];
|
|
@@ -174,6 +174,31 @@ class PostgresStreamService extends index_1.StreamService {
|
|
|
174
174
|
const target = this.resolveStreamTarget(streamName);
|
|
175
175
|
return Messages.publishMessages(this.streamClient, target.tableName, target.streamName, target.isEngine, messages, options, this.logger);
|
|
176
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Schedule a NOTIFY for a worker stream after a delay. Used to wake up
|
|
179
|
+
* consumers when a visibility-delayed retry message becomes visible,
|
|
180
|
+
* avoiding the need to wait for the scout's fallback poll.
|
|
181
|
+
*/
|
|
182
|
+
scheduleStreamNotify(streamName, delayMs) {
|
|
183
|
+
const target = this.resolveStreamTarget(streamName);
|
|
184
|
+
const prefix = target.isEngine ? 'eng_' : 'wrk_';
|
|
185
|
+
let channelName = `${prefix}${target.streamName}`;
|
|
186
|
+
if (channelName.length > 63) {
|
|
187
|
+
channelName = channelName.substring(0, 63);
|
|
188
|
+
}
|
|
189
|
+
const payload = JSON.stringify({
|
|
190
|
+
stream_name: target.streamName,
|
|
191
|
+
table_type: target.isEngine ? 'engine' : 'worker',
|
|
192
|
+
});
|
|
193
|
+
setTimeout(async () => {
|
|
194
|
+
try {
|
|
195
|
+
await this.streamClient.query(`SELECT pg_notify($1, $2)`, [channelName, payload]);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Best-effort; the scout fallback will pick it up
|
|
199
|
+
}
|
|
200
|
+
}, delayMs);
|
|
201
|
+
}
|
|
177
202
|
_publishMessages(streamName, messages, options) {
|
|
178
203
|
const target = this.resolveStreamTarget(streamName);
|
|
179
204
|
return Messages.buildPublishSQL(target.tableName, target.streamName, target.isEngine, messages, options);
|
|
@@ -320,11 +320,13 @@ class Virtual {
|
|
|
320
320
|
if ((0, utils_1.isValidCron)(params.options.interval)) {
|
|
321
321
|
//cron syntax
|
|
322
322
|
cron = params.options.interval;
|
|
323
|
-
|
|
324
|
-
delay = nextDelay > 0 ? nextDelay : undefined;
|
|
323
|
+
delay = new cron_1.CronHandler().nextDelay(cron);
|
|
325
324
|
}
|
|
326
325
|
else {
|
|
327
326
|
const seconds = (0, utils_1.s)(params.options.interval);
|
|
327
|
+
if (isNaN(seconds)) {
|
|
328
|
+
throw new Error(`Invalid cron/interval expression: ${params.options.interval}`);
|
|
329
|
+
}
|
|
328
330
|
interval = Math.max(seconds, enums_1.HMSH_FIDELITY_SECONDS);
|
|
329
331
|
delay = params.options.delay ? (0, utils_1.s)(params.options.delay) : undefined;
|
|
330
332
|
}
|