@hotmeshio/hotmesh 0.12.1 → 0.14.0
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 +18 -22
- package/build/modules/enums.d.ts +60 -5
- package/build/modules/enums.js +62 -7
- package/build/modules/errors.d.ts +15 -2
- package/build/modules/errors.js +17 -1
- package/build/modules/storage.d.ts +1 -0
- package/build/modules/storage.js +2 -1
- package/build/package.json +8 -2
- package/build/services/activities/activity/context.d.ts +22 -0
- package/build/services/activities/activity/context.js +76 -0
- package/build/services/activities/activity/index.d.ts +116 -0
- package/build/services/activities/activity/index.js +299 -0
- package/build/services/activities/activity/mapping.d.ts +12 -0
- package/build/services/activities/activity/mapping.js +63 -0
- package/build/services/activities/activity/process.d.ts +28 -0
- package/build/services/activities/activity/process.js +100 -0
- package/build/services/activities/activity/protocol.d.ts +39 -0
- package/build/services/activities/activity/protocol.js +151 -0
- package/build/services/activities/activity/state.d.ts +40 -0
- package/build/services/activities/activity/state.js +143 -0
- package/build/services/activities/activity/transition.d.ts +23 -0
- package/build/services/activities/activity/transition.js +71 -0
- package/build/services/activities/activity/verify.d.ts +22 -0
- package/build/services/activities/activity/verify.js +85 -0
- package/build/services/activities/await.d.ts +1 -4
- package/build/services/activities/await.js +2 -36
- package/build/services/activities/cycle.d.ts +1 -11
- package/build/services/activities/cycle.js +3 -46
- package/build/services/activities/hook.d.ts +2 -11
- package/build/services/activities/hook.js +30 -50
- package/build/services/activities/interrupt.d.ts +2 -4
- package/build/services/activities/interrupt.js +4 -38
- package/build/services/activities/signal.d.ts +1 -11
- package/build/services/activities/signal.js +3 -48
- package/build/services/activities/trigger.d.ts +1 -3
- package/build/services/activities/trigger.js +0 -3
- package/build/services/activities/worker.d.ts +3 -6
- package/build/services/activities/worker.js +4 -40
- package/build/services/connector/factory.d.ts +6 -0
- package/build/services/connector/factory.js +24 -0
- package/build/services/dba/index.d.ts +14 -4
- package/build/services/dba/index.js +57 -18
- package/build/services/durable/activity.d.ts +30 -0
- package/build/services/durable/activity.js +46 -0
- package/build/services/durable/client.d.ts +26 -31
- package/build/services/durable/client.js +26 -31
- package/build/services/durable/connection.d.ts +13 -7
- package/build/services/durable/connection.js +13 -7
- package/build/services/durable/exporter.d.ts +2 -2
- package/build/services/durable/exporter.js +27 -12
- package/build/services/durable/handle.d.ts +59 -41
- package/build/services/durable/handle.js +61 -41
- package/build/services/durable/index.d.ts +152 -283
- package/build/services/durable/index.js +161 -289
- package/build/services/durable/interceptor.d.ts +43 -33
- package/build/services/durable/interceptor.js +59 -39
- package/build/services/durable/schemas/factory.d.ts +2 -3
- package/build/services/durable/schemas/factory.js +180 -30
- package/build/services/durable/telemetry.d.ts +80 -0
- package/build/services/durable/telemetry.js +137 -0
- package/build/services/durable/worker.d.ts +100 -21
- package/build/services/durable/worker.js +314 -60
- package/build/services/durable/workflow/all.d.ts +1 -1
- package/build/services/durable/workflow/all.js +1 -1
- package/build/services/durable/workflow/cancellationScope.d.ts +104 -0
- package/build/services/durable/workflow/cancellationScope.js +139 -0
- package/build/services/durable/workflow/common.d.ts +5 -4
- package/build/services/durable/workflow/common.js +6 -1
- package/build/services/durable/workflow/{waitFor.d.ts → condition.d.ts} +9 -8
- package/build/services/durable/workflow/{waitFor.js → condition.js} +44 -11
- package/build/services/durable/workflow/continueAsNew.d.ts +65 -0
- package/build/services/durable/workflow/continueAsNew.js +92 -0
- package/build/services/durable/workflow/didRun.d.ts +2 -2
- package/build/services/durable/workflow/didRun.js +4 -4
- package/build/services/durable/workflow/enrich.d.ts +5 -0
- package/build/services/durable/workflow/enrich.js +5 -0
- package/build/services/durable/workflow/entityMethods.d.ts +7 -0
- package/build/services/durable/workflow/entityMethods.js +7 -0
- package/build/services/durable/workflow/execHook.js +3 -3
- package/build/services/durable/workflow/execHookBatch.js +2 -2
- package/build/services/durable/workflow/{execChild.d.ts → executeChild.d.ts} +4 -40
- package/build/services/durable/workflow/{execChild.js → executeChild.js} +36 -45
- package/build/services/durable/workflow/hook.d.ts +1 -1
- package/build/services/durable/workflow/hook.js +4 -3
- package/build/services/durable/workflow/index.d.ts +45 -50
- package/build/services/durable/workflow/index.js +46 -51
- package/build/services/durable/workflow/interruption.d.ts +7 -6
- package/build/services/durable/workflow/interruption.js +11 -7
- package/build/services/durable/workflow/patched.d.ts +72 -0
- package/build/services/durable/workflow/patched.js +110 -0
- package/build/services/durable/workflow/proxyActivities.d.ts +7 -7
- package/build/services/durable/workflow/proxyActivities.js +51 -15
- package/build/services/durable/workflow/searchMethods.d.ts +7 -0
- package/build/services/durable/workflow/searchMethods.js +7 -0
- package/build/services/durable/workflow/signal.d.ts +4 -4
- package/build/services/durable/workflow/signal.js +4 -4
- package/build/services/durable/workflow/{sleepFor.d.ts → sleep.d.ts} +7 -7
- package/build/services/durable/workflow/{sleepFor.js → sleep.js} +39 -10
- package/build/services/durable/workflow/terminate.d.ts +55 -0
- package/build/services/durable/workflow/{interrupt.js → terminate.js} +21 -21
- package/build/services/durable/workflow/trace.js +2 -2
- package/build/services/durable/workflow/uuid4.d.ts +14 -0
- package/build/services/durable/workflow/uuid4.js +39 -0
- package/build/services/durable/workflow/{context.d.ts → workflowInfo.d.ts} +5 -5
- package/build/services/durable/workflow/{context.js → workflowInfo.js} +7 -7
- package/build/services/engine/compiler.d.ts +19 -0
- package/build/services/engine/compiler.js +20 -0
- package/build/services/engine/completion.d.ts +46 -0
- package/build/services/engine/completion.js +145 -0
- package/build/services/engine/dispatch.d.ts +24 -0
- package/build/services/engine/dispatch.js +98 -0
- package/build/services/engine/index.d.ts +49 -81
- package/build/services/engine/index.js +175 -573
- package/build/services/engine/init.d.ts +42 -0
- package/build/services/engine/init.js +74 -0
- package/build/services/engine/pubsub.d.ts +50 -0
- package/build/services/engine/pubsub.js +118 -0
- package/build/services/engine/reporting.d.ts +20 -0
- package/build/services/engine/reporting.js +38 -0
- package/build/services/engine/schema.d.ts +23 -0
- package/build/services/engine/schema.js +62 -0
- package/build/services/engine/signal.d.ts +57 -0
- package/build/services/engine/signal.js +117 -0
- package/build/services/engine/state.d.ts +35 -0
- package/build/services/engine/state.js +61 -0
- package/build/services/engine/version.d.ts +31 -0
- package/build/services/engine/version.js +73 -0
- package/build/services/hotmesh/deployment.d.ts +21 -0
- package/build/services/hotmesh/deployment.js +25 -0
- package/build/services/hotmesh/index.d.ts +142 -533
- package/build/services/hotmesh/index.js +223 -674
- package/build/services/hotmesh/init.d.ts +42 -0
- package/build/services/hotmesh/init.js +93 -0
- package/build/services/hotmesh/jobs.d.ts +67 -0
- package/build/services/hotmesh/jobs.js +99 -0
- package/build/services/hotmesh/pubsub.d.ts +38 -0
- package/build/services/hotmesh/pubsub.js +54 -0
- package/build/services/hotmesh/quorum.d.ts +30 -0
- package/build/services/hotmesh/quorum.js +62 -0
- package/build/services/hotmesh/validation.d.ts +6 -0
- package/build/services/hotmesh/validation.js +28 -0
- package/build/services/quorum/index.js +1 -0
- package/build/services/router/consumption/index.d.ts +11 -5
- package/build/services/router/consumption/index.js +24 -17
- package/build/services/router/error-handling/index.d.ts +2 -2
- package/build/services/router/error-handling/index.js +14 -14
- package/build/services/router/index.d.ts +1 -1
- package/build/services/router/index.js +2 -2
- package/build/services/serializer/index.d.ts +22 -0
- package/build/services/serializer/index.js +39 -1
- package/build/services/store/index.d.ts +1 -0
- package/build/services/store/providers/postgres/exporter-sql.d.ts +2 -2
- package/build/services/store/providers/postgres/exporter-sql.js +4 -4
- package/build/services/store/providers/postgres/kvtables.js +7 -6
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +67 -52
- package/build/services/store/providers/postgres/kvtypes/hash/jsonb.js +87 -72
- package/build/services/store/providers/postgres/kvtypes/hash/udata.js +106 -79
- package/build/services/store/providers/postgres/kvtypes/hash/utils.d.ts +16 -0
- package/build/services/store/providers/postgres/kvtypes/hash/utils.js +29 -16
- package/build/services/store/providers/postgres/postgres.d.ts +1 -0
- package/build/services/store/providers/postgres/postgres.js +14 -4
- package/build/services/stream/factory.d.ts +3 -1
- package/build/services/stream/factory.js +2 -2
- package/build/services/stream/index.d.ts +1 -0
- package/build/services/stream/providers/nats/nats.d.ts +1 -0
- package/build/services/stream/providers/nats/nats.js +1 -0
- package/build/services/stream/providers/postgres/credentials.d.ts +56 -0
- package/build/services/stream/providers/postgres/credentials.js +129 -0
- package/build/services/stream/providers/postgres/kvtables.js +18 -0
- package/build/services/stream/providers/postgres/messages.js +7 -7
- package/build/services/stream/providers/postgres/notifications.js +16 -2
- package/build/services/stream/providers/postgres/postgres.d.ts +7 -0
- package/build/services/stream/providers/postgres/postgres.js +35 -4
- package/build/services/stream/providers/postgres/procedures.d.ts +21 -0
- package/build/services/stream/providers/postgres/procedures.js +213 -0
- package/build/services/stream/providers/postgres/secured.d.ts +34 -0
- package/build/services/stream/providers/postgres/secured.js +146 -0
- package/build/services/stream/providers/postgres/stats.d.ts +1 -0
- package/build/services/stream/providers/postgres/stats.js +1 -0
- package/build/services/stream/registry.d.ts +1 -1
- package/build/services/stream/registry.js +5 -2
- package/build/services/telemetry/index.d.ts +10 -1
- package/build/services/telemetry/index.js +40 -7
- package/build/services/worker/credentials.d.ts +51 -0
- package/build/services/worker/credentials.js +87 -0
- package/build/services/worker/index.d.ts +2 -2
- package/build/services/worker/index.js +7 -6
- package/build/types/codec.d.ts +84 -0
- package/build/types/codec.js +2 -0
- package/build/types/dba.d.ts +39 -3
- package/build/types/durable.d.ts +123 -25
- package/build/types/error.d.ts +10 -0
- package/build/types/exporter.d.ts +1 -1
- package/build/types/hotmesh.d.ts +67 -4
- package/build/types/index.d.ts +2 -1
- package/build/types/provider.d.ts +2 -2
- package/build/types/quorum.d.ts +35 -1
- package/build/types/stream.d.ts +12 -6
- package/package.json +8 -2
- package/build/services/activities/activity.d.ts +0 -192
- package/build/services/activities/activity.js +0 -786
- package/build/services/durable/workflow/interrupt.d.ts +0 -55
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secured SQL adapter for worker routers.
|
|
3
|
+
*
|
|
4
|
+
* Replaces raw SQL with calls to SECURITY DEFINER stored procedures.
|
|
5
|
+
* Workers using this adapter can only access their allowed streams —
|
|
6
|
+
* the stored procedures validate `app.allowed_streams` before executing.
|
|
7
|
+
*/
|
|
8
|
+
import { ILogger } from '../../../logger';
|
|
9
|
+
import { PostgresClientType } from '../../../../types/postgres';
|
|
10
|
+
import { ProviderClient } from '../../../../types/provider';
|
|
11
|
+
import { StreamMessage } from '../../../../types/stream';
|
|
12
|
+
/**
|
|
13
|
+
* Dequeue messages from worker_streams via stored procedure.
|
|
14
|
+
*/
|
|
15
|
+
export declare function fetchMessagesSecured(client: PostgresClientType & ProviderClient, schema: string, streamName: string, consumerName: string, options: {
|
|
16
|
+
batchSize?: number;
|
|
17
|
+
reservationTimeout?: number;
|
|
18
|
+
enableBackoff?: boolean;
|
|
19
|
+
initialBackoff?: number;
|
|
20
|
+
maxBackoff?: number;
|
|
21
|
+
maxRetries?: number;
|
|
22
|
+
}, logger: ILogger): Promise<StreamMessage[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Ack/delete messages via stored procedure.
|
|
25
|
+
*/
|
|
26
|
+
export declare function ackAndDeleteSecured(client: PostgresClientType & ProviderClient, schema: string, streamName: string, messageIds: string[], logger: ILogger): Promise<number>;
|
|
27
|
+
/**
|
|
28
|
+
* Dead-letter messages via stored procedure.
|
|
29
|
+
*/
|
|
30
|
+
export declare function deadLetterMessagesSecured(client: PostgresClientType & ProviderClient, schema: string, streamName: string, messageIds: string[], logger: ILogger): Promise<number>;
|
|
31
|
+
/**
|
|
32
|
+
* Publish a response to engine_streams via stored procedure.
|
|
33
|
+
*/
|
|
34
|
+
export declare function publishMessagesSecured(client: PostgresClientType & ProviderClient, schema: string, streamName: string, messages: string[], logger: ILogger): Promise<string[]>;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Secured SQL adapter for worker routers.
|
|
4
|
+
*
|
|
5
|
+
* Replaces raw SQL with calls to SECURITY DEFINER stored procedures.
|
|
6
|
+
* Workers using this adapter can only access their allowed streams —
|
|
7
|
+
* the stored procedures validate `app.allowed_streams` before executing.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.publishMessagesSecured = exports.deadLetterMessagesSecured = exports.ackAndDeleteSecured = exports.fetchMessagesSecured = void 0;
|
|
11
|
+
const utils_1 = require("../../../../modules/utils");
|
|
12
|
+
const utils_2 = require("../../../../modules/utils");
|
|
13
|
+
/**
|
|
14
|
+
* Dequeue messages from worker_streams via stored procedure.
|
|
15
|
+
*/
|
|
16
|
+
async function fetchMessagesSecured(client, schema, streamName, consumerName, options = {}, logger) {
|
|
17
|
+
const enableBackoff = options?.enableBackoff ?? false;
|
|
18
|
+
const initialBackoff = options?.initialBackoff ?? 100;
|
|
19
|
+
const maxBackoff = options?.maxBackoff ?? 3000;
|
|
20
|
+
const maxRetries = options?.maxRetries ?? 3;
|
|
21
|
+
const batchSize = options?.batchSize || 1;
|
|
22
|
+
const reservationTimeout = options?.reservationTimeout || 30;
|
|
23
|
+
let backoff = initialBackoff;
|
|
24
|
+
let retries = 0;
|
|
25
|
+
try {
|
|
26
|
+
while (retries < maxRetries) {
|
|
27
|
+
retries++;
|
|
28
|
+
const res = await client.query(`SELECT * FROM ${schema}.worker_dequeue($1, $2, $3, $4)`, [streamName, batchSize, consumerName, reservationTimeout]);
|
|
29
|
+
const messages = res.rows.map((row) => {
|
|
30
|
+
const data = (0, utils_2.parseStreamMessage)(row.message);
|
|
31
|
+
const hasDefaultRetryPolicy = (row.max_retry_attempts === 3 || row.max_retry_attempts === 5) &&
|
|
32
|
+
parseFloat(row.backoff_coefficient) === 10 &&
|
|
33
|
+
row.maximum_interval_seconds === 120;
|
|
34
|
+
if (row.max_retry_attempts !== null && !hasDefaultRetryPolicy) {
|
|
35
|
+
data._streamRetryConfig = {
|
|
36
|
+
max_retry_attempts: row.max_retry_attempts,
|
|
37
|
+
backoff_coefficient: parseFloat(row.backoff_coefficient),
|
|
38
|
+
maximum_interval_seconds: row.maximum_interval_seconds,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (row.retry_attempt !== undefined && row.retry_attempt !== null) {
|
|
42
|
+
data._retryAttempt = row.retry_attempt;
|
|
43
|
+
}
|
|
44
|
+
if (row.workflow_name) {
|
|
45
|
+
if (data.metadata) {
|
|
46
|
+
data.metadata.wfn = row.workflow_name;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
id: row.id.toString(),
|
|
51
|
+
data,
|
|
52
|
+
retry: row.max_retry_attempts !== null && !hasDefaultRetryPolicy
|
|
53
|
+
? {
|
|
54
|
+
maximumAttempts: row.max_retry_attempts,
|
|
55
|
+
backoffCoefficient: parseFloat(row.backoff_coefficient),
|
|
56
|
+
maximumInterval: row.maximum_interval_seconds,
|
|
57
|
+
}
|
|
58
|
+
: undefined,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
if (messages.length > 0 || !enableBackoff) {
|
|
62
|
+
return messages;
|
|
63
|
+
}
|
|
64
|
+
await (0, utils_1.sleepFor)(backoff);
|
|
65
|
+
backoff = Math.min(backoff * 2, maxBackoff);
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
logger.error(`postgres-secured-dequeue-error-${streamName}`, { error });
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.fetchMessagesSecured = fetchMessagesSecured;
|
|
75
|
+
/**
|
|
76
|
+
* Ack/delete messages via stored procedure.
|
|
77
|
+
*/
|
|
78
|
+
async function ackAndDeleteSecured(client, schema, streamName, messageIds, logger) {
|
|
79
|
+
try {
|
|
80
|
+
const ids = messageIds.map((id) => parseInt(id));
|
|
81
|
+
const res = await client.query(`SELECT ${schema}.worker_ack($1, $2)`, [streamName, ids]);
|
|
82
|
+
return res.rows[0]?.worker_ack ?? messageIds.length;
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger.error(`postgres-secured-ack-error-${streamName}`, { error });
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.ackAndDeleteSecured = ackAndDeleteSecured;
|
|
90
|
+
/**
|
|
91
|
+
* Dead-letter messages via stored procedure.
|
|
92
|
+
*/
|
|
93
|
+
async function deadLetterMessagesSecured(client, schema, streamName, messageIds, logger) {
|
|
94
|
+
try {
|
|
95
|
+
const ids = messageIds.map((id) => parseInt(id));
|
|
96
|
+
const res = await client.query(`SELECT ${schema}.worker_dead_letter($1, $2)`, [streamName, ids]);
|
|
97
|
+
return res.rows[0]?.worker_dead_letter ?? 0;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
logger.error(`postgres-secured-dead-letter-error-${streamName}`, {
|
|
101
|
+
error,
|
|
102
|
+
messageIds,
|
|
103
|
+
});
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.deadLetterMessagesSecured = deadLetterMessagesSecured;
|
|
108
|
+
/**
|
|
109
|
+
* Publish a response to engine_streams via stored procedure.
|
|
110
|
+
*/
|
|
111
|
+
async function publishMessagesSecured(client, schema, streamName, messages, logger) {
|
|
112
|
+
try {
|
|
113
|
+
const ids = [];
|
|
114
|
+
for (const msg of messages) {
|
|
115
|
+
const data = JSON.parse(msg);
|
|
116
|
+
const retryConfig = data._streamRetryConfig;
|
|
117
|
+
const visibilityDelayMs = data._visibilityDelayMs;
|
|
118
|
+
const retryAttempt = data._retryAttempt ?? 0;
|
|
119
|
+
// Remove internal fields
|
|
120
|
+
delete data._streamRetryConfig;
|
|
121
|
+
delete data._visibilityDelayMs;
|
|
122
|
+
delete data._retryAttempt;
|
|
123
|
+
const cleanMessage = JSON.stringify(data);
|
|
124
|
+
const visibleAt = visibilityDelayMs && visibilityDelayMs > 0
|
|
125
|
+
? new Date(Date.now() + visibilityDelayMs).toISOString()
|
|
126
|
+
: new Date().toISOString();
|
|
127
|
+
const hasRetryConfig = retryConfig && 'max_retry_attempts' in retryConfig;
|
|
128
|
+
const res = await client.query(`SELECT ${schema}.worker_respond($1, $2, $3, $4, $5, $6, $7)`, [
|
|
129
|
+
streamName,
|
|
130
|
+
cleanMessage,
|
|
131
|
+
hasRetryConfig ? retryConfig.max_retry_attempts : null,
|
|
132
|
+
hasRetryConfig ? retryConfig.backoff_coefficient : null,
|
|
133
|
+
hasRetryConfig ? retryConfig.maximum_interval_seconds : null,
|
|
134
|
+
visibleAt,
|
|
135
|
+
retryAttempt,
|
|
136
|
+
]);
|
|
137
|
+
ids.push(res.rows[0]?.worker_respond?.toString() ?? '0');
|
|
138
|
+
}
|
|
139
|
+
return ids;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
logger.error(`postgres-secured-respond-error-${streamName}`, { error });
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.publishMessagesSecured = publishMessagesSecured;
|
|
@@ -44,6 +44,7 @@ export declare function getProviderSpecificFeatures(config?: StreamConfig): {
|
|
|
44
44
|
supportsTrimming: boolean;
|
|
45
45
|
supportsRetry: boolean;
|
|
46
46
|
supportsNotifications: boolean;
|
|
47
|
+
supportsParallelProcessing: boolean;
|
|
47
48
|
maxMessageSize: number;
|
|
48
49
|
maxBatchSize: number;
|
|
49
50
|
};
|
|
@@ -107,6 +107,7 @@ function getProviderSpecificFeatures(config = {}) {
|
|
|
107
107
|
supportsTrimming: true,
|
|
108
108
|
supportsRetry: false,
|
|
109
109
|
supportsNotifications: isNotificationsEnabled(config),
|
|
110
|
+
supportsParallelProcessing: true,
|
|
110
111
|
maxMessageSize: 1024 * 1024,
|
|
111
112
|
maxBatchSize: 256,
|
|
112
113
|
};
|
|
@@ -30,7 +30,7 @@ class StreamConsumerRegistry {
|
|
|
30
30
|
reclaimDelay: config?.reclaimDelay,
|
|
31
31
|
reclaimCount: config?.reclaimCount,
|
|
32
32
|
throttle,
|
|
33
|
-
|
|
33
|
+
retry: config?.retry,
|
|
34
34
|
}, stream, logger);
|
|
35
35
|
entry = {
|
|
36
36
|
router,
|
|
@@ -117,10 +117,13 @@ class StreamConsumerRegistry {
|
|
|
117
117
|
}
|
|
118
118
|
const callback = entry.callbacks.get(wfn);
|
|
119
119
|
if (!callback) {
|
|
120
|
-
entry.logger.
|
|
120
|
+
entry.logger.info('stream-consumer-registry-replay', {
|
|
121
121
|
key,
|
|
122
122
|
wfn,
|
|
123
123
|
registered: [...entry.callbacks.keys()],
|
|
124
|
+
reason: 'worker-not-yet-registered',
|
|
125
|
+
action: 'republish-with-delay',
|
|
126
|
+
delayMs: 500,
|
|
124
127
|
});
|
|
125
128
|
// Worker not registered yet. Re-publish with short visibility delay
|
|
126
129
|
// so it retries after the worker has time to register.
|
|
@@ -13,9 +13,18 @@ declare class TelemetryService {
|
|
|
13
13
|
metadata: ActivityMetadata;
|
|
14
14
|
context: JobState;
|
|
15
15
|
leg: number;
|
|
16
|
+
/**
|
|
17
|
+
* Namespaces registered by the durable module. Engine-layer spans
|
|
18
|
+
* for these namespaces are suppressed in 'info' mode — the
|
|
19
|
+
* DurableTelemetryService emits the user-facing story instead.
|
|
20
|
+
*/
|
|
21
|
+
static durableNamespaces: Set<string>;
|
|
16
22
|
constructor(appId: string, config?: ActivityType, metadata?: ActivityMetadata, context?: JobState);
|
|
23
|
+
private isDurableApp;
|
|
17
24
|
/**
|
|
18
|
-
*
|
|
25
|
+
* Gate engine-layer span creation. For durable namespaces in 'info'
|
|
26
|
+
* mode, returns false — DurableTelemetryService handles telemetry
|
|
27
|
+
* at the right abstraction level. In 'debug' mode, both layers emit.
|
|
19
28
|
*/
|
|
20
29
|
private shouldCreateSpan;
|
|
21
30
|
private static createNoopSpan;
|
|
@@ -17,10 +17,18 @@ class TelemetryService {
|
|
|
17
17
|
this.metadata = metadata;
|
|
18
18
|
this.context = context;
|
|
19
19
|
}
|
|
20
|
+
isDurableApp() {
|
|
21
|
+
return TelemetryService.durableNamespaces.has(this.appId);
|
|
22
|
+
}
|
|
20
23
|
/**
|
|
21
|
-
*
|
|
24
|
+
* Gate engine-layer span creation. For durable namespaces in 'info'
|
|
25
|
+
* mode, returns false — DurableTelemetryService handles telemetry
|
|
26
|
+
* at the right abstraction level. In 'debug' mode, both layers emit.
|
|
22
27
|
*/
|
|
23
28
|
shouldCreateSpan() {
|
|
29
|
+
if (this.isDurableApp() && enums_1.HMSH_TELEMETRY !== 'debug') {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
24
32
|
return (enums_1.HMSH_TELEMETRY === 'debug' ||
|
|
25
33
|
this.config?.type === 'trigger' ||
|
|
26
34
|
this.config?.type === 'worker');
|
|
@@ -73,16 +81,24 @@ class TelemetryService {
|
|
|
73
81
|
}
|
|
74
82
|
getActivityParentSpanId(leg) {
|
|
75
83
|
if (leg === 1) {
|
|
76
|
-
return this.context[this.config
|
|
84
|
+
return this.context?.[this.config?.parent]?.output?.metadata?.l2s;
|
|
77
85
|
}
|
|
78
86
|
else {
|
|
79
|
-
return this.context['$self']
|
|
87
|
+
return this.context?.['$self']?.output?.metadata?.l1s;
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
getTraceId() {
|
|
83
91
|
return this.context.metadata.trc;
|
|
84
92
|
}
|
|
85
93
|
startJobSpan() {
|
|
94
|
+
if (this.isDurableApp() && enums_1.HMSH_TELEMETRY !== 'debug') {
|
|
95
|
+
const trc = this.context?.metadata?.trc ?? '';
|
|
96
|
+
const spn = this.context?.metadata?.spn ?? '';
|
|
97
|
+
this.jobSpan = TelemetryService.createNoopSpan(trc, spn);
|
|
98
|
+
this.traceId = trc;
|
|
99
|
+
this.spanId = spn;
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
86
102
|
const spanName = `JOB/${this.appId}/${this.config.subscribes}/1`;
|
|
87
103
|
const traceId = this.getTraceId();
|
|
88
104
|
const spanId = this.getJobParentSpanId();
|
|
@@ -117,6 +133,14 @@ class TelemetryService {
|
|
|
117
133
|
});
|
|
118
134
|
}
|
|
119
135
|
startActivitySpan(leg = this.leg) {
|
|
136
|
+
if (this.isDurableApp() && enums_1.HMSH_TELEMETRY !== 'debug') {
|
|
137
|
+
const trc = this.context?.metadata?.trc ?? '';
|
|
138
|
+
const spn = this.context?.metadata?.spn ?? '';
|
|
139
|
+
this.span = TelemetryService.createNoopSpan(trc, spn);
|
|
140
|
+
this.traceId = trc;
|
|
141
|
+
this.spanId = spn;
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
120
144
|
const spanName = `${this.config.type.toUpperCase()}/${this.appId}/${this.metadata.aid}/${leg}`;
|
|
121
145
|
const traceId = this.getTraceId();
|
|
122
146
|
const spanId = this.getActivityParentSpanId(leg);
|
|
@@ -141,10 +165,13 @@ class TelemetryService {
|
|
|
141
165
|
else {
|
|
142
166
|
type = 'FANOUT'; //exiting engine router (to worker router)
|
|
143
167
|
}
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
168
|
+
// For durable namespaces in 'info' mode, only SYSTEM spans pass —
|
|
169
|
+
// DurableTelemetryService emits the user-facing workflow story.
|
|
170
|
+
// In 'debug' mode or for non-durable apps, original gating applies:
|
|
171
|
+
// `EXECUTE` = worker router, `SYSTEM` = catastrophic errors (always traced).
|
|
172
|
+
if (this.isDurableApp() && enums_1.HMSH_TELEMETRY !== 'debug'
|
|
173
|
+
? type === 'SYSTEM'
|
|
174
|
+
: this.shouldCreateSpan() || type === 'EXECUTE' || type === 'SYSTEM') {
|
|
148
175
|
const topic = data.metadata.topic ? `/${data.metadata.topic}` : '';
|
|
149
176
|
const spanName = `${type}/${this.appId}/${data.metadata.aid}${topic}`;
|
|
150
177
|
const attributes = this.getStreamSpanAttrs(data);
|
|
@@ -310,3 +337,9 @@ class TelemetryService {
|
|
|
310
337
|
}
|
|
311
338
|
}
|
|
312
339
|
exports.TelemetryService = TelemetryService;
|
|
340
|
+
/**
|
|
341
|
+
* Namespaces registered by the durable module. Engine-layer spans
|
|
342
|
+
* for these namespaces are suppressed in 'info' mode — the
|
|
343
|
+
* DurableTelemetryService emits the user-facing story instead.
|
|
344
|
+
*/
|
|
345
|
+
TelemetryService.durableNamespaces = new Set();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level worker credential lifecycle management.
|
|
3
|
+
*
|
|
4
|
+
* Creates an admin client from a connection config, delegates to the
|
|
5
|
+
* low-level Postgres credential functions, and cleans up the client.
|
|
6
|
+
* This is the canonical API — `HotMesh` and `Durable` re-export these
|
|
7
|
+
* as static convenience methods.
|
|
8
|
+
*/
|
|
9
|
+
import type { WorkerCredential, WorkerCredentialInfo } from '../stream/providers/postgres/credentials';
|
|
10
|
+
import { ProviderConfig, ProvidersConfig } from '../../types/provider';
|
|
11
|
+
/**
|
|
12
|
+
* Provision a scoped Postgres role for a worker router.
|
|
13
|
+
*
|
|
14
|
+
* The role can only dequeue/ack/respond on the specified stream
|
|
15
|
+
* names via SECURITY DEFINER stored procedures — it has **zero
|
|
16
|
+
* direct table access**.
|
|
17
|
+
*/
|
|
18
|
+
export declare function provisionWorkerRole(config: {
|
|
19
|
+
connection: ProviderConfig | ProvidersConfig;
|
|
20
|
+
namespace?: string;
|
|
21
|
+
streamNames: string[];
|
|
22
|
+
password?: string;
|
|
23
|
+
}): Promise<WorkerCredential>;
|
|
24
|
+
/**
|
|
25
|
+
* Rotate the password for an existing worker role.
|
|
26
|
+
*/
|
|
27
|
+
export declare function rotateWorkerPassword(config: {
|
|
28
|
+
connection: ProviderConfig | ProvidersConfig;
|
|
29
|
+
namespace?: string;
|
|
30
|
+
roleName: string;
|
|
31
|
+
newPassword?: string;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
password: string;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Revoke a worker role by disabling login. The role is not dropped,
|
|
37
|
+
* preserving the audit trail in the `worker_credentials` table.
|
|
38
|
+
*/
|
|
39
|
+
export declare function revokeWorkerRole(config: {
|
|
40
|
+
connection: ProviderConfig | ProvidersConfig;
|
|
41
|
+
namespace?: string;
|
|
42
|
+
roleName: string;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* List all provisioned worker roles for a namespace.
|
|
46
|
+
*/
|
|
47
|
+
export declare function listWorkerRoles(config: {
|
|
48
|
+
connection: ProviderConfig | ProvidersConfig;
|
|
49
|
+
namespace?: string;
|
|
50
|
+
}): Promise<WorkerCredentialInfo[]>;
|
|
51
|
+
export type { WorkerCredential, WorkerCredentialInfo };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* High-level worker credential lifecycle management.
|
|
4
|
+
*
|
|
5
|
+
* Creates an admin client from a connection config, delegates to the
|
|
6
|
+
* low-level Postgres credential functions, and cleans up the client.
|
|
7
|
+
* This is the canonical API — `HotMesh` and `Durable` re-export these
|
|
8
|
+
* as static convenience methods.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.listWorkerRoles = exports.revokeWorkerRole = exports.rotateWorkerPassword = exports.provisionWorkerRole = void 0;
|
|
12
|
+
const credentials_1 = require("../stream/providers/postgres/credentials");
|
|
13
|
+
/**
|
|
14
|
+
* Create a raw Postgres client from a connection config for admin
|
|
15
|
+
* operations (credential provisioning, rotation, revocation).
|
|
16
|
+
*/
|
|
17
|
+
async function createAdminClient(connection) {
|
|
18
|
+
const config = 'options' in connection
|
|
19
|
+
? connection
|
|
20
|
+
: connection.store ?? connection;
|
|
21
|
+
const ClientClass = config.class;
|
|
22
|
+
const options = config.options;
|
|
23
|
+
const client = new ClientClass(options);
|
|
24
|
+
await client.connect();
|
|
25
|
+
return client;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Provision a scoped Postgres role for a worker router.
|
|
29
|
+
*
|
|
30
|
+
* The role can only dequeue/ack/respond on the specified stream
|
|
31
|
+
* names via SECURITY DEFINER stored procedures — it has **zero
|
|
32
|
+
* direct table access**.
|
|
33
|
+
*/
|
|
34
|
+
async function provisionWorkerRole(config) {
|
|
35
|
+
const namespace = config.namespace ?? 'durable';
|
|
36
|
+
const pgClient = await createAdminClient(config.connection);
|
|
37
|
+
try {
|
|
38
|
+
return await (0, credentials_1.provisionWorkerRole)(pgClient, namespace, config.streamNames, config.password);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await pgClient.end?.();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.provisionWorkerRole = provisionWorkerRole;
|
|
45
|
+
/**
|
|
46
|
+
* Rotate the password for an existing worker role.
|
|
47
|
+
*/
|
|
48
|
+
async function rotateWorkerPassword(config) {
|
|
49
|
+
const namespace = config.namespace ?? 'durable';
|
|
50
|
+
const pgClient = await createAdminClient(config.connection);
|
|
51
|
+
try {
|
|
52
|
+
return await (0, credentials_1.rotateWorkerPassword)(pgClient, namespace, config.roleName, config.newPassword);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await pgClient.end?.();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.rotateWorkerPassword = rotateWorkerPassword;
|
|
59
|
+
/**
|
|
60
|
+
* Revoke a worker role by disabling login. The role is not dropped,
|
|
61
|
+
* preserving the audit trail in the `worker_credentials` table.
|
|
62
|
+
*/
|
|
63
|
+
async function revokeWorkerRole(config) {
|
|
64
|
+
const namespace = config.namespace ?? 'durable';
|
|
65
|
+
const pgClient = await createAdminClient(config.connection);
|
|
66
|
+
try {
|
|
67
|
+
await (0, credentials_1.revokeWorkerRole)(pgClient, namespace, config.roleName);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
await pgClient.end?.();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.revokeWorkerRole = revokeWorkerRole;
|
|
74
|
+
/**
|
|
75
|
+
* List all provisioned worker roles for a namespace.
|
|
76
|
+
*/
|
|
77
|
+
async function listWorkerRoles(config) {
|
|
78
|
+
const namespace = config.namespace ?? 'durable';
|
|
79
|
+
const pgClient = await createAdminClient(config.connection);
|
|
80
|
+
try {
|
|
81
|
+
return await (0, credentials_1.listWorkerRoles)(pgClient, namespace);
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await pgClient.end?.();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.listWorkerRoles = listWorkerRoles;
|
|
@@ -23,7 +23,7 @@ declare class WorkerService {
|
|
|
23
23
|
reporting: boolean;
|
|
24
24
|
inited: string;
|
|
25
25
|
rollCallInterval: NodeJS.Timeout;
|
|
26
|
-
|
|
26
|
+
retry: import('../../types/stream').RetryPolicy | undefined;
|
|
27
27
|
/**
|
|
28
28
|
* @private
|
|
29
29
|
*/
|
|
@@ -47,7 +47,7 @@ declare class WorkerService {
|
|
|
47
47
|
/**
|
|
48
48
|
* @private
|
|
49
49
|
*/
|
|
50
|
-
initStreamChannel(service: WorkerService, stream: ProviderClient, store: ProviderClient): Promise<void>;
|
|
50
|
+
initStreamChannel(service: WorkerService, stream: ProviderClient, store: ProviderClient, securedWorker?: boolean): Promise<void>;
|
|
51
51
|
/**
|
|
52
52
|
* @private
|
|
53
53
|
*/
|
|
@@ -39,19 +39,19 @@ class WorkerService {
|
|
|
39
39
|
service.topic = worker.topic;
|
|
40
40
|
service.config = config;
|
|
41
41
|
service.logger = logger;
|
|
42
|
-
service.
|
|
42
|
+
service.retry = worker.retry;
|
|
43
43
|
await service.initStoreChannel(service, worker.store);
|
|
44
44
|
await service.initSubChannel(service, worker.sub, worker.pub ?? worker.store);
|
|
45
45
|
await service.subscribe.subscribe(key_1.KeyType.QUORUM, service.subscriptionHandler(), appId);
|
|
46
46
|
await service.subscribe.subscribe(key_1.KeyType.QUORUM, service.subscriptionHandler(), appId, service.topic);
|
|
47
47
|
await service.subscribe.subscribe(key_1.KeyType.QUORUM, service.subscriptionHandler(), appId, service.guid);
|
|
48
|
-
await service.initStreamChannel(service, worker.stream, worker.store);
|
|
48
|
+
await service.initStreamChannel(service, worker.stream, worker.store, !!worker.workerCredentials);
|
|
49
49
|
if (worker.workflowName) {
|
|
50
50
|
// Use singleton consumer via registry (batched fetch + dispatch by workflow_name)
|
|
51
51
|
await registry_1.StreamConsumerRegistry.registerWorker(namespace, appId, guid, worker.topic, worker.workflowName, worker.callback, service.stream, service.store, logger, {
|
|
52
52
|
reclaimDelay: worker.reclaimDelay,
|
|
53
53
|
reclaimCount: worker.reclaimCount,
|
|
54
|
-
|
|
54
|
+
retry: worker.retry,
|
|
55
55
|
});
|
|
56
56
|
// Still need a router for publishing responses back to engine
|
|
57
57
|
service.router = await service.initRouter(worker, logger);
|
|
@@ -97,8 +97,8 @@ class WorkerService {
|
|
|
97
97
|
/**
|
|
98
98
|
* @private
|
|
99
99
|
*/
|
|
100
|
-
async initStreamChannel(service, stream, store) {
|
|
101
|
-
service.stream = await factory_2.StreamServiceFactory.init(stream, store, service.namespace, service.appId, service.logger);
|
|
100
|
+
async initStreamChannel(service, stream, store, securedWorker = false) {
|
|
101
|
+
service.stream = await factory_2.StreamServiceFactory.init(stream, store, service.namespace, service.appId, service.logger, { securedWorker });
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
104
|
* @private
|
|
@@ -114,7 +114,7 @@ class WorkerService {
|
|
|
114
114
|
reclaimDelay: worker.reclaimDelay,
|
|
115
115
|
reclaimCount: worker.reclaimCount,
|
|
116
116
|
throttle,
|
|
117
|
-
|
|
117
|
+
retry: worker.retry,
|
|
118
118
|
}, this.stream, logger);
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
@@ -191,6 +191,7 @@ class WorkerService {
|
|
|
191
191
|
worker_topic: this.topic,
|
|
192
192
|
stream: this.store.mintKey(key_1.KeyType.STREAMS, params),
|
|
193
193
|
counts: this.router?.counts,
|
|
194
|
+
error_count: this.router?.errorCount,
|
|
194
195
|
timestamp: (0, utils_1.formatISODate)(new Date()),
|
|
195
196
|
inited: this.inited,
|
|
196
197
|
throttle: this.router?.throttle,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synchronous codec for encoding/decoding serialized payload data.
|
|
3
|
+
*
|
|
4
|
+
* Register a `PayloadCodec` to transparently transform all serialized
|
|
5
|
+
* object values (`/s{json}`) before they are stored and after they are
|
|
6
|
+
* read. Common use cases: encryption at rest, compression, or custom
|
|
7
|
+
* binary encoding.
|
|
8
|
+
*
|
|
9
|
+
* ## How It Works
|
|
10
|
+
*
|
|
11
|
+
* The serializer stores JavaScript objects as `/s{json}`. When a codec
|
|
12
|
+
* is registered, objects are instead stored as `/b{encoded}`:
|
|
13
|
+
*
|
|
14
|
+
* ```
|
|
15
|
+
* Write: value → JSON.stringify → codec.encode(json) → "/b{encoded}"
|
|
16
|
+
* Read: "/b{encoded}" → codec.decode(encoded) → JSON.parse → value
|
|
17
|
+
* "/s{json}" → JSON.parse → value (backward compatible)
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* ## Constraints
|
|
21
|
+
*
|
|
22
|
+
* - **Synchronous** — `encode` and `decode` must be synchronous.
|
|
23
|
+
* Use Node.js `crypto` (e.g., `createCipheriv`) or `zlib`
|
|
24
|
+
* (e.g., `deflateSync`) for sync operations.
|
|
25
|
+
* - **String-safe** — `encode` output must be a valid UTF-8 string
|
|
26
|
+
* (Postgres TEXT column). Use base64 encoding for binary output.
|
|
27
|
+
* - **Deterministic decode** — `decode(encode(x))` must equal `x`.
|
|
28
|
+
*
|
|
29
|
+
* ## Scope
|
|
30
|
+
*
|
|
31
|
+
* The codec applies to all data flowing through the serializer's
|
|
32
|
+
* `package`/`unpackage` path — job state, activity inputs/outputs,
|
|
33
|
+
* timeline markers, and workflow metadata. It does **not** apply to
|
|
34
|
+
* `search()`, `entity()`, or `enrich()` data, which bypass the
|
|
35
|
+
* serializer.
|
|
36
|
+
*
|
|
37
|
+
* ## Example: AES-256-GCM Encryption
|
|
38
|
+
*
|
|
39
|
+
* ```typescript
|
|
40
|
+
* import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
|
|
41
|
+
* import { PayloadCodec } from '@hotmeshio/hotmesh';
|
|
42
|
+
*
|
|
43
|
+
* const key = randomBytes(32);
|
|
44
|
+
*
|
|
45
|
+
* const codec: PayloadCodec = {
|
|
46
|
+
* encode(json: string): string {
|
|
47
|
+
* const iv = randomBytes(12);
|
|
48
|
+
* const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
49
|
+
* const enc = Buffer.concat([cipher.update(json, 'utf8'), cipher.final()]);
|
|
50
|
+
* const tag = cipher.getAuthTag();
|
|
51
|
+
* return Buffer.concat([iv, tag, enc]).toString('base64');
|
|
52
|
+
* },
|
|
53
|
+
* decode(encoded: string): string {
|
|
54
|
+
* const buf = Buffer.from(encoded, 'base64');
|
|
55
|
+
* const iv = buf.subarray(0, 12);
|
|
56
|
+
* const tag = buf.subarray(12, 28);
|
|
57
|
+
* const data = buf.subarray(28);
|
|
58
|
+
* const decipher = createDecipheriv('aes-256-gcm', key, iv);
|
|
59
|
+
* decipher.setAuthTag(tag);
|
|
60
|
+
* return Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8');
|
|
61
|
+
* },
|
|
62
|
+
* };
|
|
63
|
+
*
|
|
64
|
+
* // Register globally — applies to all HotMesh and Durable instances
|
|
65
|
+
* HotMesh.registerCodec(codec);
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export interface PayloadCodec {
|
|
69
|
+
/**
|
|
70
|
+
* Encode a JSON string before storage. The output replaces
|
|
71
|
+
* the `/s{json}` prefix with `/b{encoded}`.
|
|
72
|
+
*
|
|
73
|
+
* @param json - The raw JSON string (result of JSON.stringify)
|
|
74
|
+
* @returns The encoded string (must be valid UTF-8)
|
|
75
|
+
*/
|
|
76
|
+
encode(json: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* Decode a previously encoded string back to the original JSON.
|
|
79
|
+
*
|
|
80
|
+
* @param encoded - The encoded string (from a previous `encode` call)
|
|
81
|
+
* @returns The original JSON string
|
|
82
|
+
*/
|
|
83
|
+
decode(encoded: string): string;
|
|
84
|
+
}
|