nestjs-temporal-core
Version:
Complete NestJS integration for Temporal.io with auto-discovery, declarative scheduling, enhanced monitoring, and enterprise-ready features
186 lines • 8.38 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var TemporalConnectionFactory_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TemporalConnectionFactory = void 0;
const common_1 = require("@nestjs/common");
const client_1 = require("@temporalio/client");
const logger_1 = require("../utils/logger");
let TemporalConnectionFactory = TemporalConnectionFactory_1 = class TemporalConnectionFactory {
constructor() {
this.clientConnectionCache = new Map();
this.workerConnectionCache = new Map();
this.connectionAttempts = new Map();
this.MAX_RETRY_ATTEMPTS = 3;
this.RETRY_DELAY_MS = 1000;
this.logger = (0, logger_1.createLogger)(TemporalConnectionFactory_1.name);
}
async createClient(options) {
if (!options.connection) {
this.logger.info('No connection configuration provided - running without client');
return null;
}
const connectionKey = this.getConnectionKey(options.connection);
const cachedClient = this.clientConnectionCache.get(connectionKey);
if (cachedClient && this.isClientHealthy(cachedClient)) {
this.logger.debug('Reusing cached client connection');
return cachedClient;
}
if (cachedClient) {
this.clientConnectionCache.delete(connectionKey);
}
return this.createNewClient(options, connectionKey);
}
async createWorkerConnection(options) {
if (!options.connection) {
this.logger.debug('No connection configuration provided for worker');
return null;
}
const connectionKey = this.getConnectionKey(options.connection);
const cachedConnection = this.workerConnectionCache.get(connectionKey);
if (cachedConnection && this.isWorkerConnectionHealthy(cachedConnection)) {
this.logger.debug('Reusing cached worker connection');
return cachedConnection;
}
if (cachedConnection) {
try {
await cachedConnection.close();
}
catch (error) {
this.logger.warn('Failed to close unhealthy worker connection', error);
}
this.workerConnectionCache.delete(connectionKey);
}
return this.createNewWorkerConnection(options, connectionKey);
}
async onModuleDestroy() {
await this.cleanup();
}
async cleanup() {
this.logger.info('Cleaning up all connections...');
this.logger.debug('Skipping worker connection cleanup - managed by worker service lifecycle');
this.clientConnectionCache.clear();
this.workerConnectionCache.clear();
this.connectionAttempts.clear();
this.logger.info('Connection cleanup completed');
}
getConnectionHealth() {
return {
clientConnections: this.clientConnectionCache.size,
workerConnections: this.workerConnectionCache.size,
totalAttempts: Array.from(this.connectionAttempts.values()).reduce((sum, attempts) => sum + attempts, 0),
};
}
async createNewClient(options, connectionKey) {
const attempts = this.connectionAttempts.get(connectionKey) || 0;
if (attempts >= this.MAX_RETRY_ATTEMPTS) {
if (options.allowConnectionFailure !== false) {
this.logger.warn(`Max retry attempts exceeded for ${options.connection.address} - running without client`);
return null;
}
else {
throw new Error(`Failed to connect to ${options.connection.address} after ${this.MAX_RETRY_ATTEMPTS} attempts`);
}
}
try {
this.logger.info(`Creating new client connection to ${options.connection.address} (attempt ${attempts + 1})`);
const connection = await client_1.Connection.lazy({
address: options.connection.address,
tls: options.connection.tls,
metadata: options.connection.metadata,
...(options.connection.apiKey && {
metadata: {
...(options.connection.metadata || {}),
authorization: `Bearer ${options.connection.apiKey}`,
},
}),
});
const client = new client_1.Client({
connection,
namespace: options.connection.namespace || 'default',
});
this.clientConnectionCache.set(connectionKey, client);
this.connectionAttempts.delete(connectionKey);
logger_1.LoggerUtils.logConnection(this.logger, options.connection.address, true);
return client;
}
catch (error) {
this.connectionAttempts.set(connectionKey, attempts + 1);
logger_1.LoggerUtils.logConnection(this.logger, options.connection.address, false, error);
if (attempts + 1 < this.MAX_RETRY_ATTEMPTS) {
this.logger.info(`Retrying connection in ${this.RETRY_DELAY_MS}ms...`);
await this.delay(this.RETRY_DELAY_MS);
return this.createNewClient(options, connectionKey);
}
if (options.allowConnectionFailure !== false) {
this.logger.warn('Client connection failed - continuing without client');
return null;
}
throw error;
}
}
async createNewWorkerConnection(options, connectionKey) {
try {
this.logger.debug(`Creating new worker connection to ${options.connection.address}`);
const { NativeConnection } = await Promise.resolve().then(() => require('@temporalio/worker'));
const address = options.connection.address;
const connectOptions = {
address,
tls: options.connection.tls,
};
if (options.connection.apiKey) {
connectOptions.metadata = {
...(options.connection.metadata || {}),
authorization: `Bearer ${options.connection.apiKey}`,
};
}
const connection = await NativeConnection.connect(connectOptions);
this.workerConnectionCache.set(connectionKey, connection);
this.logger.info(`Worker connection established to ${address}`);
return connection;
}
catch (error) {
this.logger.warn('Failed to create worker connection, returning null', error);
return null;
}
}
getConnectionKey(connection) {
return `${connection.address}:${connection.namespace || 'default'}:${connection.apiKey ? 'auth' : 'noauth'}`;
}
isClientHealthy(client) {
try {
return Boolean(client.workflow);
}
catch {
return false;
}
}
isWorkerConnectionHealthy(connection) {
try {
return (connection !== null &&
connection !== undefined &&
typeof connection === 'object' &&
connection.constructor !== undefined);
}
catch {
return false;
}
}
delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
};
exports.TemporalConnectionFactory = TemporalConnectionFactory;
exports.TemporalConnectionFactory = TemporalConnectionFactory = TemporalConnectionFactory_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [])
], TemporalConnectionFactory);
//# sourceMappingURL=temporal-connection.factory.js.map