@diia-inhouse/workflow 1.17.11 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activities/index.d.ts +1 -0
- package/dist/activities/index.js +2 -18
- package/dist/activities/proxy.d.ts +34 -0
- package/dist/activities/proxy.js +16 -24
- package/dist/activity.d.ts +2 -0
- package/dist/activity.js +2 -15
- package/dist/cli/checkWorkflowDeterminism.js +249 -275
- package/dist/cli/determinism/errorClassifier.js +56 -60
- package/dist/cli/determinism/historyFiles.js +68 -97
- package/dist/cli/determinism/index.js +7 -19
- package/dist/cli/determinism/replayExecutor.js +114 -133
- package/dist/cli/determinism/replayOptions.js +13 -22
- package/dist/cli/determinism/report.js +55 -45
- package/dist/cli/determinism/reportPrinter.js +101 -138
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +79 -119
- package/dist/cli/syncTemporalSchedules.js +74 -91
- package/dist/cli/updateTemporalSchedule.js +43 -53
- package/dist/client.d.ts +3 -0
- package/dist/client.js +3 -19
- package/dist/common.d.ts +2 -0
- package/dist/common.js +2 -13
- package/dist/encryption/crypto.d.ts +7 -0
- package/dist/encryption/crypto.js +20 -22
- package/dist/encryption/dataConverter.d.ts +7 -0
- package/dist/encryption/dataConverter.js +15 -22
- package/dist/encryption/encryptionCodec.d.ts +31 -0
- package/dist/encryption/encryptionCodec.js +108 -124
- package/dist/encryption/index.d.ts +3 -0
- package/dist/encryption/index.js +4 -20
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -42
- package/dist/instrumentation.js +6 -10
- package/dist/interceptors/asyncLocalStorageBridge.js +29 -66
- package/dist/interceptors/traceLogAttributes.d.ts +6 -0
- package/dist/interceptors/traceLogAttributes.js +16 -54
- package/dist/interceptors.d.ts +6 -0
- package/dist/interceptors.js +6 -8
- package/dist/interfaces/config.d.ts +58 -0
- package/dist/interfaces/index.d.ts +1 -0
- package/dist/interfaces/services/schedulesExporter.d.ts +96 -0
- package/dist/interfaces/services/worker.d.ts +60 -0
- package/dist/operations.d.ts +9 -0
- package/dist/operations.js +11 -75
- package/dist/services/client.d.ts +24 -0
- package/dist/services/client.js +89 -96
- package/dist/services/schedulesExporter.d.ts +101 -0
- package/dist/services/schedulesExporter.js +456 -0
- package/dist/services/worker/identity.d.ts +4 -0
- package/dist/services/worker/identity.js +6 -9
- package/dist/services/worker.d.ts +124 -0
- package/dist/services/worker.js +324 -304
- package/dist/services/workerHealth.d.ts +15 -0
- package/dist/services/workerHealth.js +26 -35
- package/dist/testing.d.ts +42 -0
- package/dist/testing.js +43 -54
- package/dist/worker.d.ts +9 -0
- package/dist/worker.js +7 -25
- package/package.json +40 -37
- package/dist/activities/index.js.map +0 -1
- package/dist/activities/proxy.js.map +0 -1
- package/dist/activity.js.map +0 -1
- package/dist/cli/checkWorkflowDeterminism.js.map +0 -1
- package/dist/cli/determinism/errorClassifier.js.map +0 -1
- package/dist/cli/determinism/historyFiles.js.map +0 -1
- package/dist/cli/determinism/index.js.map +0 -1
- package/dist/cli/determinism/replayExecutor.js.map +0 -1
- package/dist/cli/determinism/replayOptions.js.map +0 -1
- package/dist/cli/determinism/report.js.map +0 -1
- package/dist/cli/determinism/reportPrinter.js.map +0 -1
- package/dist/cli/determinism/types.js +0 -3
- package/dist/cli/determinism/types.js.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/syncTemporalSchedules.js.map +0 -1
- package/dist/cli/updateTemporalSchedule.js.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/common.js.map +0 -1
- package/dist/encryption/crypto.js.map +0 -1
- package/dist/encryption/dataConverter.js.map +0 -1
- package/dist/encryption/encryptionCodec.js.map +0 -1
- package/dist/encryption/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/instrumentation.js.map +0 -1
- package/dist/interceptors/asyncLocalStorageBridge.js.map +0 -1
- package/dist/interceptors/index.js +0 -8
- package/dist/interceptors/index.js.map +0 -1
- package/dist/interceptors/traceLogAttributes.js.map +0 -1
- package/dist/interceptors.js.map +0 -1
- package/dist/interfaces/config.js +0 -3
- package/dist/interfaces/config.js.map +0 -1
- package/dist/interfaces/index.js +0 -18
- package/dist/interfaces/index.js.map +0 -1
- package/dist/interfaces/services/worker.js +0 -3
- package/dist/interfaces/services/worker.js.map +0 -1
- package/dist/operations.js.map +0 -1
- package/dist/services/client.js.map +0 -1
- package/dist/services/index.js +0 -19
- package/dist/services/index.js.map +0 -1
- package/dist/services/worker/identity.js.map +0 -1
- package/dist/services/worker/index.js +0 -18
- package/dist/services/worker/index.js.map +0 -1
- package/dist/services/worker.js.map +0 -1
- package/dist/services/workerHealth.js.map +0 -1
- package/dist/testing.js.map +0 -1
- package/dist/types/activities/index.d.ts +0 -1
- package/dist/types/activities/proxy.d.ts +0 -35
- package/dist/types/activity.d.ts +0 -1
- package/dist/types/cli/checkWorkflowDeterminism.d.ts +0 -19
- package/dist/types/cli/determinism/errorClassifier.d.ts +0 -15
- package/dist/types/cli/determinism/historyFiles.d.ts +0 -18
- package/dist/types/cli/determinism/index.d.ts +0 -10
- package/dist/types/cli/determinism/replayExecutor.d.ts +0 -9
- package/dist/types/cli/determinism/replayOptions.d.ts +0 -7
- package/dist/types/cli/determinism/report.d.ts +0 -16
- package/dist/types/cli/determinism/reportPrinter.d.ts +0 -5
- package/dist/types/cli/determinism/types.d.ts +0 -44
- package/dist/types/cli/index.d.ts +0 -2
- package/dist/types/cli/syncTemporalSchedules.d.ts +0 -12
- package/dist/types/cli/updateTemporalSchedule.d.ts +0 -9
- package/dist/types/client.d.ts +0 -2
- package/dist/types/common.d.ts +0 -1
- package/dist/types/encryption/crypto.d.ts +0 -3
- package/dist/types/encryption/dataConverter.d.ts +0 -3
- package/dist/types/encryption/encryptionCodec.d.ts +0 -27
- package/dist/types/encryption/index.d.ts +0 -3
- package/dist/types/index.d.ts +0 -3
- package/dist/types/instrumentation.d.ts +0 -2
- package/dist/types/interceptors/asyncLocalStorageBridge.d.ts +0 -21
- package/dist/types/interceptors/index.d.ts +0 -2
- package/dist/types/interceptors/traceLogAttributes.d.ts +0 -2
- package/dist/types/interceptors.d.ts +0 -2
- package/dist/types/interfaces/config.d.ts +0 -38
- package/dist/types/interfaces/index.d.ts +0 -1
- package/dist/types/interfaces/services/worker.d.ts +0 -37
- package/dist/types/operations.d.ts +0 -5
- package/dist/types/services/client.d.ts +0 -20
- package/dist/types/services/index.d.ts +0 -2
- package/dist/types/services/worker/identity.d.ts +0 -1
- package/dist/types/services/worker/index.d.ts +0 -1
- package/dist/types/services/worker.d.ts +0 -113
- package/dist/types/services/workerHealth.d.ts +0 -11
- package/dist/types/testing.d.ts +0 -42
- package/dist/types/worker.d.ts +0 -3
- package/dist/worker.js.map +0 -1
package/dist/services/worker.js
CHANGED
|
@@ -1,333 +1,353 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const worker_2 = require("@temporalio/worker");
|
|
18
|
-
const env_1 = require("@diia-inhouse/env");
|
|
19
|
-
const encryption_1 = require("../encryption");
|
|
20
|
-
const instrumentation_1 = require("../instrumentation");
|
|
21
|
-
const asyncLocalStorageBridge_1 = require("../interceptors/asyncLocalStorageBridge");
|
|
22
|
-
const identity_1 = require("./worker/identity");
|
|
23
|
-
const workerHealth_1 = require("./workerHealth");
|
|
24
|
-
var identity_2 = require("./worker/identity");
|
|
25
|
-
Object.defineProperty(exports, "buildWorkerIdentity", { enumerable: true, get: function () { return identity_2.buildWorkerIdentity; } });
|
|
26
|
-
var workerHealth_2 = require("./workerHealth");
|
|
27
|
-
Object.defineProperty(exports, "WorkerHealthService", { enumerable: true, get: function () { return workerHealth_2.WorkerHealthService; } });
|
|
1
|
+
import { getDataConverter } from "../encryption/dataConverter.js";
|
|
2
|
+
import "../encryption/index.js";
|
|
3
|
+
import { traceExporter } from "../instrumentation.js";
|
|
4
|
+
import { AsyncLocalStorageBridgeInterceptor } from "../interceptors/asyncLocalStorageBridge.js";
|
|
5
|
+
import { SchedulesExporter } from "./schedulesExporter.js";
|
|
6
|
+
import { buildWorkerIdentity } from "./worker/identity.js";
|
|
7
|
+
import { WorkerHealthService } from "./workerHealth.js";
|
|
8
|
+
import { EnvService } from "@diia-inhouse/env";
|
|
9
|
+
import { NativeConnection, Runtime, Worker } from "@temporalio/worker";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { Resource } from "@opentelemetry/resources";
|
|
13
|
+
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
14
|
+
import { OpenTelemetryActivityInboundInterceptor, OpenTelemetryActivityOutboundInterceptor, makeWorkflowExporter } from "@temporalio/interceptors-opentelemetry/lib/worker/index.js";
|
|
15
|
+
import * as promClient from "prom-client";
|
|
16
|
+
//#region src/services/worker.ts
|
|
28
17
|
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
18
|
+
* Applies service process configuration overrides when the worker runs separately.
|
|
19
|
+
*
|
|
20
|
+
* When `temporal.workerInProcess` is `false`, disables `temporal` and `temporal-worker` scrapers
|
|
21
|
+
* so the main service does not scrape metrics that the worker process handles.
|
|
22
|
+
*
|
|
23
|
+
* Mutates the config object in place. Safe to call when scrapers are absent.
|
|
24
|
+
*/
|
|
36
25
|
function applyServiceProcessConfig(config) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
for (const scraper of config.metrics.custom.scrapers ?? []) {
|
|
42
|
-
if (scraper.name === 'temporal' || scraper.name === 'temporal-worker') {
|
|
43
|
-
scraper.disabled = true;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
26
|
+
const { workerInProcess = true } = config.temporal;
|
|
27
|
+
if (workerInProcess) return;
|
|
28
|
+
for (const scraper of config.metrics.custom.scrapers ?? []) if (scraper.name === "temporal" || scraper.name === "temporal-worker") scraper.disabled = true;
|
|
46
29
|
}
|
|
47
30
|
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
31
|
+
* Applies worker process configuration overrides.
|
|
32
|
+
*
|
|
33
|
+
* - Disables queue consumers on all rabbit connections (unless `temporal.disableQueueConsumers` is `false`)
|
|
34
|
+
* - Overrides `metrics.custom.port` with the `'temporal-worker'` scraper port and disables that scraper to prevent self-scraping
|
|
35
|
+
*
|
|
36
|
+
* Mutates the config object in place. Safe to call when queue config is absent.
|
|
37
|
+
*/
|
|
55
38
|
function applyWorkerProcessConfig(config) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (workerScraper?.port !== undefined) {
|
|
66
|
-
config.metrics.custom.port = workerScraper.port;
|
|
67
|
-
workerScraper.disabled = true;
|
|
68
|
-
}
|
|
39
|
+
const { disableQueueConsumers = true } = config.temporal;
|
|
40
|
+
if (disableQueueConsumers && config.rabbit) {
|
|
41
|
+
for (const value of Object.values(config.rabbit)) if (value && typeof value === "object" && "consumerEnabled" in value) value.consumerEnabled = false;
|
|
42
|
+
}
|
|
43
|
+
const workerScraper = config.metrics.custom.scrapers?.find((s) => s.name === "temporal-worker");
|
|
44
|
+
if (workerScraper?.port !== void 0) {
|
|
45
|
+
config.metrics.custom.port = workerScraper.port;
|
|
46
|
+
workerScraper.disabled = true;
|
|
47
|
+
}
|
|
69
48
|
}
|
|
70
49
|
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
50
|
+
* Accepts either a filesystem path or a `file://` URL (e.g. from `import.meta.resolve`)
|
|
51
|
+
* and returns a filesystem path suitable for Temporal's worker.
|
|
52
|
+
*/
|
|
53
|
+
function toWorkflowsPath(input) {
|
|
54
|
+
return input.startsWith("file://") ? fileURLToPath(input) : input;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Builds worker interceptors with OpenTelemetry and AsyncLocalStorage support.
|
|
58
|
+
* OpenTelemetry creates span first, then AsyncLocalStorage bridge extracts traceId.
|
|
59
|
+
*/
|
|
60
|
+
const traceLogAttributesModulePath = path.resolve(import.meta.dirname, "../interceptors/traceLogAttributes");
|
|
75
61
|
function buildWorkerInterceptors(tracingEnabled, asyncLocalStorage, logger, workflowsPath) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
],
|
|
95
|
-
workflowModules,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
if (asyncLocalStorage && logger) {
|
|
99
|
-
return {
|
|
100
|
-
activity: [
|
|
101
|
-
(ctx) => ({
|
|
102
|
-
inbound: new asyncLocalStorageBridge_1.AsyncLocalStorageBridgeInterceptor(ctx, asyncLocalStorage, logger),
|
|
103
|
-
outbound: new worker_1.OpenTelemetryActivityOutboundInterceptor(ctx),
|
|
104
|
-
}),
|
|
105
|
-
],
|
|
106
|
-
workflowModules: workflowsPath ? [workflowsPath] : [],
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
return undefined;
|
|
62
|
+
if (tracingEnabled) {
|
|
63
|
+
const workflowModules = [traceLogAttributesModulePath];
|
|
64
|
+
if (workflowsPath) workflowModules.unshift(workflowsPath);
|
|
65
|
+
return {
|
|
66
|
+
activity: [(ctx) => ({
|
|
67
|
+
inbound: new OpenTelemetryActivityInboundInterceptor(ctx),
|
|
68
|
+
outbound: new OpenTelemetryActivityOutboundInterceptor(ctx)
|
|
69
|
+
}), ...asyncLocalStorage && logger ? [(ctx) => ({ inbound: new AsyncLocalStorageBridgeInterceptor(ctx, asyncLocalStorage, logger) })] : []],
|
|
70
|
+
workflowModules
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (asyncLocalStorage && logger) return {
|
|
74
|
+
activity: [(ctx) => ({
|
|
75
|
+
inbound: new AsyncLocalStorageBridgeInterceptor(ctx, asyncLocalStorage, logger),
|
|
76
|
+
outbound: new OpenTelemetryActivityOutboundInterceptor(ctx)
|
|
77
|
+
})],
|
|
78
|
+
workflowModules: workflowsPath ? [workflowsPath] : []
|
|
79
|
+
};
|
|
110
80
|
}
|
|
111
81
|
/**
|
|
112
|
-
|
|
113
|
-
|
|
82
|
+
* Merges built-in interceptors with custom interceptors from options.
|
|
83
|
+
*/
|
|
114
84
|
function mergeInterceptors(builtIn, custom) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
workflowModules: [...(builtIn?.workflowModules || []), ...(custom?.workflowModules || [])],
|
|
121
|
-
};
|
|
85
|
+
if (!builtIn && !custom) return;
|
|
86
|
+
return {
|
|
87
|
+
activity: [...builtIn?.activity || [], ...custom?.activity || []],
|
|
88
|
+
workflowModules: [...builtIn?.workflowModules || [], ...custom?.workflowModules || []]
|
|
89
|
+
};
|
|
122
90
|
}
|
|
123
91
|
function buildActivities(app, activities) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
return instances;
|
|
92
|
+
const instances = {};
|
|
93
|
+
for (const [key, value] of Object.entries(activities)) instances[key] = app.container.build(value);
|
|
94
|
+
return instances;
|
|
129
95
|
}
|
|
130
96
|
function bindActivities(key, instance) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
97
|
+
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).filter((name) => name !== "constructor");
|
|
98
|
+
const boundActivities = {};
|
|
99
|
+
for (const method of methods) {
|
|
100
|
+
const fn = instance[method];
|
|
101
|
+
const token = `${key}.${String(method)}`;
|
|
102
|
+
boundActivities[token] = fn.bind(instance);
|
|
103
|
+
}
|
|
104
|
+
return boundActivities;
|
|
139
105
|
}
|
|
140
106
|
function instantiateActivities(app, workerActivities) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
return activities;
|
|
107
|
+
const activitiesInstances = buildActivities(app, workerActivities);
|
|
108
|
+
const activities = {};
|
|
109
|
+
for (const [key, value] of Object.entries(activitiesInstances)) Object.assign(activities, bindActivities(key, value));
|
|
110
|
+
return activities;
|
|
147
111
|
}
|
|
148
112
|
/**
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
113
|
+
* Initializes and starts Temporal worker with full dependency injection support.
|
|
114
|
+
*
|
|
115
|
+
* This is the recommended way to initialize Temporal workers. It handles:
|
|
116
|
+
* - Automatic dependency injection for activities
|
|
117
|
+
* - AsyncLocalStorage setup for distributed tracing
|
|
118
|
+
* - OpenTelemetry integration
|
|
119
|
+
* - Activity instantiation and binding
|
|
120
|
+
*
|
|
121
|
+
* @param app - App instance for DI container and config access
|
|
122
|
+
* @param options - Worker configuration options
|
|
123
|
+
* @param options.nodeTracerProvider - OpenTelemetry tracer provider
|
|
124
|
+
* @param options.workflowsPath - Path to workflows module
|
|
125
|
+
* @param options.activities - Activity classes to instantiate
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* // Define your activities
|
|
130
|
+
* const workerActivities = {
|
|
131
|
+
* userActivity: UserActivity,
|
|
132
|
+
* notificationActivity: NotificationActivity,
|
|
133
|
+
* }
|
|
134
|
+
*
|
|
135
|
+
* // Initialize and start the worker
|
|
136
|
+
* await initTemporalWorker(app, {
|
|
137
|
+
* nodeTracerProvider,
|
|
138
|
+
* workflowsPath: import.meta.resolve('./worker/workflows/index.js'),
|
|
139
|
+
* activities: workerActivities,
|
|
140
|
+
* })
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
179
143
|
async function initTemporalWorker(app, options) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
144
|
+
const config = app.getConfig?.();
|
|
145
|
+
const { nodeTracerProvider, workflowsPath: workflowsPathInput, activities, ...workerOptions } = options;
|
|
146
|
+
const workflowsPath = toWorkflowsPath(workflowsPathInput);
|
|
147
|
+
const envService = app.container.resolve("envService");
|
|
148
|
+
const logger = app.container.resolve("logger");
|
|
149
|
+
const asyncLocalStorage = app.container.resolve("asyncLocalStorage");
|
|
150
|
+
const instantiatedActivities = instantiateActivities(app, activities);
|
|
151
|
+
const worker = await initWorker(config, {
|
|
152
|
+
...workerOptions,
|
|
153
|
+
workflowsPath,
|
|
154
|
+
activities: instantiatedActivities
|
|
155
|
+
}, envService, logger, nodeTracerProvider, asyncLocalStorage);
|
|
156
|
+
const schedulesExporter = await startSchedulesExporter(app, config, logger);
|
|
157
|
+
try {
|
|
158
|
+
await worker.run();
|
|
159
|
+
} finally {
|
|
160
|
+
await schedulesExporter?.onDestroy().catch((err) => logger.error("SchedulesExporter shutdown failed", { err }));
|
|
161
|
+
}
|
|
188
162
|
}
|
|
189
163
|
/**
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
164
|
+
* Bootstraps and runs Temporal worker with graceful shutdown.
|
|
165
|
+
*
|
|
166
|
+
* Handles both in-process and separate-process worker topologies:
|
|
167
|
+
*
|
|
168
|
+
* - **In-process** (`workerInProcess` is `true` or unset): initializes and runs the worker.
|
|
169
|
+
* - **Separate process** (called from a dedicated worker entry with `configFactory`/`deps`):
|
|
170
|
+
* manages the full application lifecycle: setConfig → apply worker overrides → setDeps →
|
|
171
|
+
* initialize → start → run worker.
|
|
172
|
+
* - **Service-only** (`workerInProcess` is `false`, no `configFactory`): disables temporal
|
|
173
|
+
* scrapers on the main service (worker handles them separately) and returns immediately.
|
|
174
|
+
*
|
|
175
|
+
* Automatically integrates worker health with the app's centralized health check
|
|
176
|
+
* system via `HealthCheck.addHealthCheckable()`.
|
|
177
|
+
*
|
|
178
|
+
* @param app - App instance for DI container and config access
|
|
179
|
+
* @param options - Worker bootstrap options
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* // Separate worker process with full lifecycle management
|
|
184
|
+
* const app = new Application(serviceName, nodeTracerProvider, loggerConfig)
|
|
185
|
+
*
|
|
186
|
+
* await bootstrapWorker(app, {
|
|
187
|
+
* configFactory,
|
|
188
|
+
* deps,
|
|
189
|
+
* workflowsPath: import.meta.resolve('./worker/workflows/index.js'),
|
|
190
|
+
* activities: workerActivities,
|
|
191
|
+
* nodeTracerProvider,
|
|
192
|
+
* })
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
221
195
|
async function bootstrapWorker(app, options) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
196
|
+
const { configFactory, deps, workflowsPath: workflowsPathInput, activities, nodeTracerProvider, shutdownSignals = ["SIGTERM", "SIGINT"], ...workerOptions } = options;
|
|
197
|
+
const workflowsPath = toWorkflowsPath(workflowsPathInput);
|
|
198
|
+
if (configFactory && deps) {
|
|
199
|
+
await app.setConfig(configFactory);
|
|
200
|
+
applyWorkerProcessConfig(app.getConfig());
|
|
201
|
+
await app.setDeps(deps);
|
|
202
|
+
await (await app.initialize()).start();
|
|
203
|
+
}
|
|
204
|
+
const config = app.getConfig?.();
|
|
205
|
+
if (!configFactory && config.temporal.workerInProcess === false) {
|
|
206
|
+
applyServiceProcessConfig(config);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const envService = app.container.resolve("envService");
|
|
210
|
+
const logger = app.container.resolve("logger");
|
|
211
|
+
const asyncLocalStorage = app.container.resolve("asyncLocalStorage");
|
|
212
|
+
const taskQueue = config.temporal.taskQueue;
|
|
213
|
+
const identity = buildWorkerIdentity(taskQueue, workerOptions.identity);
|
|
214
|
+
const instantiatedActivities = instantiateActivities(app, activities);
|
|
215
|
+
const worker = await initWorker(config, {
|
|
216
|
+
...workerOptions,
|
|
217
|
+
workflowsPath,
|
|
218
|
+
activities: instantiatedActivities,
|
|
219
|
+
identity
|
|
220
|
+
}, envService, logger, nodeTracerProvider, asyncLocalStorage);
|
|
221
|
+
const schedulesExporter = await startSchedulesExporter(app, config, logger);
|
|
222
|
+
logger.info("Starting Temporal worker", {
|
|
223
|
+
taskQueue,
|
|
224
|
+
identity
|
|
225
|
+
});
|
|
226
|
+
const healthCheck = tryResolve(app.container, "healthCheck");
|
|
227
|
+
if (healthCheck) {
|
|
228
|
+
const workerHealthService = new WorkerHealthService();
|
|
229
|
+
workerHealthService.setStatusProvider(() => worker.getStatus());
|
|
230
|
+
healthCheck.addHealthCheckable(workerHealthService);
|
|
231
|
+
}
|
|
232
|
+
const signalHandler = () => {
|
|
233
|
+
worker.shutdown();
|
|
234
|
+
};
|
|
235
|
+
for (const signal of shutdownSignals) process.once(signal, signalHandler);
|
|
236
|
+
try {
|
|
237
|
+
await worker.run();
|
|
238
|
+
} finally {
|
|
239
|
+
for (const signal of shutdownSignals) process.off(signal, signalHandler);
|
|
240
|
+
await schedulesExporter?.onDestroy().catch((err) => logger.error("SchedulesExporter shutdown failed", { err }));
|
|
241
|
+
}
|
|
268
242
|
}
|
|
269
243
|
function tryResolve(container, key) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
244
|
+
try {
|
|
245
|
+
return container.resolve(key);
|
|
246
|
+
} catch {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Wires the SchedulesExporter to the in-process worker. Auto-enabled; opt out by setting
|
|
252
|
+
* `config.temporal.schedulesExporter` to `false`. Returns the exporter so callers can stop
|
|
253
|
+
* it on shutdown.
|
|
254
|
+
*/
|
|
255
|
+
async function startSchedulesExporter(app, config, logger) {
|
|
256
|
+
const exporterConfig = config.temporal.schedulesExporter;
|
|
257
|
+
if (exporterConfig === false) return;
|
|
258
|
+
const temporalClient = tryResolve(app.container, "temporalClient");
|
|
259
|
+
if (!temporalClient) {
|
|
260
|
+
logger.warn("SchedulesExporter not started: temporalClient is not registered in the DI container");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const exporter = new SchedulesExporter({
|
|
264
|
+
client: temporalClient,
|
|
265
|
+
taskQueue: config.temporal.taskQueue,
|
|
266
|
+
logger
|
|
267
|
+
}, exporterConfig ?? {});
|
|
268
|
+
await exporter.onInit();
|
|
269
|
+
return exporter;
|
|
276
270
|
}
|
|
277
271
|
/**
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
272
|
+
* Initializes Temporal worker.
|
|
273
|
+
*
|
|
274
|
+
* @param config - Application configuration
|
|
275
|
+
* @param options - Worker options including workflows path
|
|
276
|
+
* @param envService - Environment service instance
|
|
277
|
+
* @param logger - Logger instance (optional)
|
|
278
|
+
* @param nodeTracerProvider - OpenTelemetry tracer provider (optional)
|
|
279
|
+
* @param asyncLocalStorage - AsyncLocalStorage instance for tracing context (optional)
|
|
280
|
+
* @returns Configured Temporal worker
|
|
281
|
+
*/
|
|
288
282
|
async function initWorker({ temporal: temporalConfig, metrics: { custom: metricsConfig } }, options, envService, logger, nodeTracerProvider, asyncLocalStorage) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
283
|
+
const { encryptionEnabled, encryptionKeyId, encryptionKeyRefreshInterval, namespace = "default", address, taskQueue } = temporalConfig;
|
|
284
|
+
const runtimeParams = {};
|
|
285
|
+
if (logger) runtimeParams.logger = logger.child({ taskQueue });
|
|
286
|
+
const temporalMetrics = metricsConfig.scrapers?.find((s) => s.name === "temporal");
|
|
287
|
+
if (temporalMetrics && !temporalMetrics.disabled) runtimeParams.telemetryOptions = { metrics: { prometheus: {
|
|
288
|
+
bindAddress: `0.0.0.0:${temporalMetrics.port}`,
|
|
289
|
+
useSecondsForDurations: true,
|
|
290
|
+
countersTotalSuffix: true,
|
|
291
|
+
unitSuffix: true
|
|
292
|
+
} } };
|
|
293
|
+
Runtime.install(runtimeParams);
|
|
294
|
+
const providerWithPrivateResource = nodeTracerProvider;
|
|
295
|
+
const tracerResource = nodeTracerProvider?.resource ?? providerWithPrivateResource?.["_resource"];
|
|
296
|
+
const resource = new Resource({ [ATTR_SERVICE_NAME]: tracerResource?.attributes?.[ATTR_SERVICE_NAME] });
|
|
297
|
+
const tracingEnabled = EnvService.getVar("TRACING_ENABLED", "boolean", false);
|
|
298
|
+
const workflowsPath = options.workflowsPath ? toWorkflowsPath(options.workflowsPath) : void 0;
|
|
299
|
+
const mergedInterceptors = mergeInterceptors(buildWorkerInterceptors(tracingEnabled, asyncLocalStorage, logger, workflowsPath), options.interceptors);
|
|
300
|
+
try {
|
|
301
|
+
const worker = await Worker.create({
|
|
302
|
+
namespace,
|
|
303
|
+
taskQueue,
|
|
304
|
+
connection: await NativeConnection.connect({ address }),
|
|
305
|
+
dataConverter: encryptionEnabled ? await getDataConverter(encryptionKeyId, envService, encryptionKeyRefreshInterval) : void 0,
|
|
306
|
+
...options,
|
|
307
|
+
workflowsPath,
|
|
308
|
+
sinks: tracingEnabled ? { exporter: makeWorkflowExporter(traceExporter, resource) } : void 0,
|
|
309
|
+
interceptors: mergedInterceptors
|
|
310
|
+
});
|
|
311
|
+
if (workflowsPath && taskQueue) await emitRegisteredWorkflowTypes(workflowsPath, taskQueue, logger);
|
|
312
|
+
return worker;
|
|
313
|
+
} catch (err) {
|
|
314
|
+
logger?.error("Failed to create Temporal worker", { err });
|
|
315
|
+
throw new Error("Failed to create Temporal worker", { cause: err });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Dynamic-imports the workflows entrypoint module and emits
|
|
320
|
+
* `diia_workflow_registered_type_info{workflow_type, task_queue}=1` for every exported
|
|
321
|
+
* function. Pairs with `diia_schedule_*` series so a dashboard can detect zombie schedules
|
|
322
|
+
* (schedules whose workflow type is no longer registered on the same task queue).
|
|
323
|
+
*
|
|
324
|
+
* Failures are logged and swallowed — this is observability, not load-bearing.
|
|
325
|
+
*/
|
|
326
|
+
async function emitRegisteredWorkflowTypes(workflowsPath, taskQueue, logger) {
|
|
327
|
+
try {
|
|
328
|
+
const mod = await (workflowsPath.startsWith("file://") ? import(workflowsPath) : import(`file://${workflowsPath}`));
|
|
329
|
+
const existing = promClient.register.getSingleMetric("diia_workflow_registered_type_info");
|
|
330
|
+
const gauge = existing instanceof promClient.Gauge ? existing : new promClient.Gauge({
|
|
331
|
+
name: "diia_workflow_registered_type_info",
|
|
332
|
+
help: "1 if the workflow type is registered on this task queue (function exported from the worker entrypoint module)",
|
|
333
|
+
labelNames: ["workflow_type", "task_queue"]
|
|
334
|
+
});
|
|
335
|
+
const registered = [];
|
|
336
|
+
for (const [name, value] of Object.entries(mod)) if (typeof value === "function") {
|
|
337
|
+
gauge.set({
|
|
338
|
+
workflow_type: name,
|
|
339
|
+
task_queue: taskQueue
|
|
340
|
+
}, 1);
|
|
341
|
+
registered.push(name);
|
|
342
|
+
}
|
|
343
|
+
logger?.info("Registered workflow types emitted to Prometheus", {
|
|
344
|
+
taskQueue,
|
|
345
|
+
count: registered.length,
|
|
346
|
+
types: registered
|
|
347
|
+
});
|
|
348
|
+
} catch (err) {
|
|
349
|
+
logger?.warn("Failed to enumerate registered workflow types", { err });
|
|
350
|
+
}
|
|
332
351
|
}
|
|
333
|
-
//#
|
|
352
|
+
//#endregion
|
|
353
|
+
export { applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, initTemporalWorker, initWorker, instantiateActivities, toWorkflowsPath };
|