fix(types): Fix TypeScript build errors and improve API type safety across platformservice interfaces

This commit is contained in:
Philipp Kunz 2025-05-08 10:39:43 +00:00
parent 4624fdbe10
commit 39b634b6bb
9 changed files with 445 additions and 37 deletions

View File

@ -1,5 +1,36 @@
# Changelog
## 2025-05-08 - 2.8.9 - fix(types)
Fix TypeScript build errors and improve API type safety across platformservice interfaces
- Fixed interface placement in EmailService and MtaConnector classes
- Aligned DeliveryStatus enum and updated ApiManager handlers with proper type-safe signatures
- Added comprehensive TypeScript interfaces for ISendEmailOptions, ITemplateContext, IValidateEmailOptions, IValidationResult, and IEmailServiceStats
- Removed circular dependencies in type definitions and added proper type assertions
- Improved test stability by handling race conditions in SenderReputationMonitor and IPWarmupManager; external DNS lookups are disabled under test environment
## 2025-05-08 - 2.8.8 - fix(types): Fix TypeScript build errors and improve API interfaces
Fix TypeScript build errors caused by interface placement and improve API type alignment
- Fixed interface placement in EmailService and MtaConnector classes
- Aligned DeliveryStatus enum with EmailSendJob implementation
- Added proper method signatures for API endpoint handlers in ApiManager class
- Updated getStats and checkEmailStatus methods to conform to API contracts
- Implemented type-safe return values for all API methods
- Fixed circular dependencies in type definitions
- Added proper type assertion where needed to satisfy TypeScript compiler
## 2025-05-08 - 2.8.7 - feat(types): Add comprehensive TypeScript interfaces for API types
Improve type safety across the platform by adding detailed TypeScript interfaces for APIs
- Added ISendEmailOptions interface with complete documentation for email sending options
- Created ITemplateContext interface for email template rendering with full type safety
- Added IValidateEmailOptions and IValidationResult interfaces for email validation
- Improved IEmailServiceStats interface with detailed statistics types
- Added IEmailStatusResponse and IEmailStatusDetails interfaces for MTA status checking
- Updated sendEmail and other methods to use these new interfaces instead of 'any'
- Removed need for type assertions in various components
## 2025-05-08 - 2.8.6 - fix(tests)
fix: Improve test stability by handling race conditions in SenderReputationMonitor and IPWarmupManager. Disable filesystem operations and external DNS lookups during tests by checking NODE_ENV, add proper cleanup of singleton instances and active timeouts to ensure consistent test environment.

View File

@ -1,7 +1,7 @@
{
"name": "@serve.zone/platformservice",
"private": true,
"version": "2.8.6",
"version": "2.8.8",
"description": "A multifaceted platform service handling mail, SMS, letter delivery, and AI services.",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",

View File

@ -2,6 +2,16 @@
## Latest Changes
### API Type Safety Improvements
- [x] Create comprehensive TypeScript interfaces for all API methods
- [x] Replace any types with specific interfaces in EmailService and MtaConnector
- [x] Align interface types with their implementations
- [x] Document all interface properties with detailed JSDoc comments
- [x] Use type imports to ensure consistency between services
- [x] Fix TypeScript build errors from interface placements
- [x] Add proper method signatures for API endpoint handlers
- [x] Implement type-safe API responses
### Test Stability Improvements
- [x] Fix race conditions in SenderReputationMonitor tests
- [x] Disable filesystem operations during tests to prevent race conditions
@ -9,6 +19,27 @@
- [x] Ensure all tests properly clean up shared resources
- [x] Set consistent test environment with NODE_ENV=test
### Error Handling Improvements
- [ ] Add structured error types for better error reporting
- [ ] Implement consistent error handling patterns across services
- [ ] Add detailed error context for debugging
- [ ] Create retry mechanisms for transient failures
- [ ] Improve error logging with structured data
### Configuration Interface Standardization
- [ ] Define consistent configuration interfaces across services
- [ ] Implement validation for all configuration objects
- [ ] Add default values for optional configuration
- [ ] Create documentation for all configuration options
- [ ] Add migration helpers for configuration format changes
### Logging Enhancements
- [ ] Implement structured logging throughout the codebase
- [ ] Add context information to all log messages
- [ ] Create consistent log levels and usage patterns
- [ ] Add correlation IDs for request tracking
- [ ] Implement log filtering and sampling options
### Mailgun Removal
- [x] Remove Mailgun integration from keywords in package.json and npmextra.json
- [x] Update EmailService comments to remove mentions of Mailgun

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/platformservice',
version: '2.8.6',
version: '2.8.9',
description: 'A multifaceted platform service handling mail, SMS, letter delivery, and AI services.'
}

165
ts/errors/error.codes.ts Normal file
View File

@ -0,0 +1,165 @@
/**
* Platform Service Error Codes
*
* This file contains all error codes used across the platform service.
*
* Format: PREFIX_ERROR_TYPE
* - PREFIX: Component/domain prefix (e.g., EMAIL, MTA, SMS)
* - ERROR_TYPE: Specific error type within the domain
*/
// General platform errors (PLATFORM_*)
export const PLATFORM_INITIALIZATION_ERROR = 'PLATFORM_INITIALIZATION_ERROR';
export const PLATFORM_CONFIGURATION_ERROR = 'PLATFORM_CONFIGURATION_ERROR';
export const PLATFORM_OPERATION_ERROR = 'PLATFORM_OPERATION_ERROR';
export const PLATFORM_NOT_IMPLEMENTED = 'PLATFORM_NOT_IMPLEMENTED';
export const PLATFORM_NOT_SUPPORTED = 'PLATFORM_NOT_SUPPORTED';
export const PLATFORM_SERVICE_UNAVAILABLE = 'PLATFORM_SERVICE_UNAVAILABLE';
// Email service errors (EMAIL_*)
export const EMAIL_SERVICE_ERROR = 'EMAIL_SERVICE_ERROR';
export const EMAIL_TEMPLATE_ERROR = 'EMAIL_TEMPLATE_ERROR';
export const EMAIL_VALIDATION_ERROR = 'EMAIL_VALIDATION_ERROR';
export const EMAIL_SEND_ERROR = 'EMAIL_SEND_ERROR';
export const EMAIL_RECEIVE_ERROR = 'EMAIL_RECEIVE_ERROR';
export const EMAIL_ATTACHMENT_ERROR = 'EMAIL_ATTACHMENT_ERROR';
export const EMAIL_PARSE_ERROR = 'EMAIL_PARSE_ERROR';
export const EMAIL_RATE_LIMIT_EXCEEDED = 'EMAIL_RATE_LIMIT_EXCEEDED';
// MTA-specific errors (MTA_*)
export const MTA_CONNECTION_ERROR = 'MTA_CONNECTION_ERROR';
export const MTA_AUTHENTICATION_ERROR = 'MTA_AUTHENTICATION_ERROR';
export const MTA_DELIVERY_ERROR = 'MTA_DELIVERY_ERROR';
export const MTA_CONFIGURATION_ERROR = 'MTA_CONFIGURATION_ERROR';
export const MTA_DNS_ERROR = 'MTA_DNS_ERROR';
export const MTA_TIMEOUT_ERROR = 'MTA_TIMEOUT_ERROR';
export const MTA_PROTOCOL_ERROR = 'MTA_PROTOCOL_ERROR';
// Bounce management errors (BOUNCE_*)
export const BOUNCE_PROCESSING_ERROR = 'BOUNCE_PROCESSING_ERROR';
export const BOUNCE_STORAGE_ERROR = 'BOUNCE_STORAGE_ERROR';
export const BOUNCE_CLASSIFICATION_ERROR = 'BOUNCE_CLASSIFICATION_ERROR';
// Email authentication errors (AUTH_*)
export const AUTH_SPF_ERROR = 'AUTH_SPF_ERROR';
export const AUTH_DKIM_ERROR = 'AUTH_DKIM_ERROR';
export const AUTH_DMARC_ERROR = 'AUTH_DMARC_ERROR';
export const AUTH_KEY_ERROR = 'AUTH_KEY_ERROR';
// Content scanning errors (SCAN_*)
export const SCAN_ANALYSIS_ERROR = 'SCAN_ANALYSIS_ERROR';
export const SCAN_MALWARE_DETECTED = 'SCAN_MALWARE_DETECTED';
export const SCAN_PHISHING_DETECTED = 'SCAN_PHISHING_DETECTED';
export const SCAN_CONTENT_REJECTED = 'SCAN_CONTENT_REJECTED';
// IP and reputation errors (REPUTATION_*)
export const REPUTATION_CHECK_ERROR = 'REPUTATION_CHECK_ERROR';
export const REPUTATION_DATA_ERROR = 'REPUTATION_DATA_ERROR';
export const REPUTATION_BLOCKLIST_ERROR = 'REPUTATION_BLOCKLIST_ERROR';
export const REPUTATION_UPDATE_ERROR = 'REPUTATION_UPDATE_ERROR';
// IP warmup errors (WARMUP_*)
export const WARMUP_ALLOCATION_ERROR = 'WARMUP_ALLOCATION_ERROR';
export const WARMUP_LIMIT_EXCEEDED = 'WARMUP_LIMIT_EXCEEDED';
export const WARMUP_SCHEDULE_ERROR = 'WARMUP_SCHEDULE_ERROR';
// Network and connectivity errors (NETWORK_*)
export const NETWORK_CONNECTION_ERROR = 'NETWORK_CONNECTION_ERROR';
export const NETWORK_TIMEOUT = 'NETWORK_TIMEOUT';
export const NETWORK_DNS_ERROR = 'NETWORK_DNS_ERROR';
export const NETWORK_TLS_ERROR = 'NETWORK_TLS_ERROR';
// Queue and processing errors (QUEUE_*)
export const QUEUE_FULL_ERROR = 'QUEUE_FULL_ERROR';
export const QUEUE_PROCESSING_ERROR = 'QUEUE_PROCESSING_ERROR';
export const QUEUE_PERSISTENCE_ERROR = 'QUEUE_PERSISTENCE_ERROR';
export const QUEUE_ITEM_NOT_FOUND = 'QUEUE_ITEM_NOT_FOUND';
// DcRouter errors (DCR_*)
export const DCR_ROUTING_ERROR = 'DCR_ROUTING_ERROR';
export const DCR_CONFIGURATION_ERROR = 'DCR_CONFIGURATION_ERROR';
export const DCR_PROXY_ERROR = 'DCR_PROXY_ERROR';
export const DCR_DOMAIN_ERROR = 'DCR_DOMAIN_ERROR';
// SMS service errors (SMS_*)
export const SMS_SERVICE_ERROR = 'SMS_SERVICE_ERROR';
export const SMS_SEND_ERROR = 'SMS_SEND_ERROR';
export const SMS_VALIDATION_ERROR = 'SMS_VALIDATION_ERROR';
export const SMS_RATE_LIMIT_EXCEEDED = 'SMS_RATE_LIMIT_EXCEEDED';
// Storage errors (STORAGE_*)
export const STORAGE_WRITE_ERROR = 'STORAGE_WRITE_ERROR';
export const STORAGE_READ_ERROR = 'STORAGE_READ_ERROR';
export const STORAGE_DELETE_ERROR = 'STORAGE_DELETE_ERROR';
export const STORAGE_QUOTA_EXCEEDED = 'STORAGE_QUOTA_EXCEEDED';
// Rule management errors (RULE_*)
export const RULE_VALIDATION_ERROR = 'RULE_VALIDATION_ERROR';
export const RULE_EXECUTION_ERROR = 'RULE_EXECUTION_ERROR';
export const RULE_NOT_FOUND = 'RULE_NOT_FOUND';
// Type definitions for error severity
export enum ErrorSeverity {
/** Critical errors that require immediate attention */
CRITICAL = 'CRITICAL',
/** High-impact errors that may affect service functioning */
HIGH = 'HIGH',
/** Medium-impact errors that cause partial degradation */
MEDIUM = 'MEDIUM',
/** Low-impact errors that have minimal or local impact */
LOW = 'LOW',
/** Informational errors that are not problematic */
INFO = 'INFO'
}
// Type definitions for error categories
export enum ErrorCategory {
/** Errors related to configuration */
CONFIGURATION = 'CONFIGURATION',
/** Errors related to network connectivity */
CONNECTIVITY = 'CONNECTIVITY',
/** Errors related to authentication/authorization */
AUTHENTICATION = 'AUTHENTICATION',
/** Errors related to data validation */
VALIDATION = 'VALIDATION',
/** Errors related to resource availability */
RESOURCE = 'RESOURCE',
/** Errors related to service operations */
OPERATION = 'OPERATION',
/** Errors related to third-party integrations */
INTEGRATION = 'INTEGRATION',
/** Errors related to security */
SECURITY = 'SECURITY',
/** Errors related to data storage */
STORAGE = 'STORAGE',
/** Errors that don't fit into other categories */
OTHER = 'OTHER'
}
// Type definitions for error recoverability
export enum ErrorRecoverability {
/** Error cannot be automatically recovered from */
NON_RECOVERABLE = 'NON_RECOVERABLE',
/** Error might be recoverable with retry */
MAYBE_RECOVERABLE = 'MAYBE_RECOVERABLE',
/** Error is definitely recoverable with retries */
RECOVERABLE = 'RECOVERABLE',
/** Error is transient and should resolve without action */
TRANSIENT = 'TRANSIENT'
}

View File

@ -5,6 +5,10 @@ import { logger } from '../../logger.js';
// Import MTA classes
import { MtaService } from './classes.mta.js';
import { Email as MtaEmail } from '../core/classes.email.js';
import { DeliveryStatus } from './classes.emailsendjob.js';
// Re-export for use in index.ts
export { DeliveryStatus };
// Import Email types
export interface IEmailOptions {
@ -19,14 +23,6 @@ export interface IEmailOptions {
headers?: { [key: string]: string };
}
// Reuse the DeliveryStatus from the email send job
export enum DeliveryStatus {
PENDING = 'pending',
PROCESSING = 'processing',
DELIVERED = 'delivered',
DEFERRED = 'deferred',
FAILED = 'failed'
}
// Reuse the IAttachment interface
export interface IAttachment {
@ -37,6 +33,66 @@ export interface IAttachment {
encoding?: string;
}
/**
* Email status details
*/
export interface IEmailStatusDetails {
/** Number of delivery attempts */
attempts?: number;
/** Timestamp of last delivery attempt */
lastAttempt?: Date;
/** Timestamp of next scheduled attempt */
nextAttempt?: Date;
/** Error message if delivery failed */
error?: string;
/** Message explaining the status */
message?: string;
}
/**
* Email status response
*/
export interface IEmailStatusResponse {
/** Current status of the email */
status: DeliveryStatus | 'unknown' | 'error';
/** Additional status details */
details?: IEmailStatusDetails;
}
/**
* Options for sending an email via MTA
*/
export interface ISendEmailOptions {
/** Whether to use MIME format conversion */
useMimeFormat?: boolean;
/** Whether to track clicks */
trackClicks?: boolean;
/** Whether to track opens */
trackOpens?: boolean;
/** Message priority (1-5, where 1 is highest) */
priority?: number;
/** Message scheduling options */
schedule?: {
/** Time to send the email */
sendAt?: Date | string;
/** Time the message expires */
expireAt?: Date | string;
};
/** DKIM signing options */
dkim?: {
/** Whether to sign the message */
sign?: boolean;
/** Domain to use for signing */
domain?: string;
/** Key selector to use */
selector?: string;
};
/** Additional headers */
headers?: Record<string, string>;
/** Message tags for categorization */
tags?: string[];
}
export class MtaConnector {
public emailRef: EmailService;
private mtaService: MtaService;
@ -46,6 +102,13 @@ export class MtaConnector {
this.mtaService = mtaService || this.emailRef.mtaService;
}
/**
* Send an email using the MTA service
* @param smartmail The email to send
* @param toAddresses Recipients (comma-separated or array)
* @param options Additional options
*/
/**
* Send an email using the MTA service
* @param smartmail The email to send
@ -55,7 +118,7 @@ export class MtaConnector {
public async sendEmail(
smartmail: plugins.smartmail.Smartmail<any>,
toAddresses: string | string[],
options: any = {}
options: ISendEmailOptions = {}
): Promise<string> {
// Check if recipients are on the suppression list
const recipients = Array.isArray(toAddresses)
@ -101,7 +164,7 @@ export class MtaConnector {
const emailOptions: Record<string, any> = { ...options };
// Check if we should use MIME format
const useMimeFormat = options.useMimeFormat ?? true;
const useMimeFormat = options.useMimeFormat !== false; // Default to true
if (useMimeFormat) {
// Use smartmail's MIME conversion for improved handling
@ -521,28 +584,28 @@ export class MtaConnector {
/**
* Check the status of a sent email
* @param emailId The email ID to check
* @returns Current status and details
*/
public async checkEmailStatus(emailId: string): Promise<{
status: string;
details?: any;
}> {
public async checkEmailStatus(emailId: string): Promise<IEmailStatusResponse> {
try {
const status = this.mtaService.getEmailStatus(emailId);
if (!status) {
return {
status: 'unknown',
status: 'unknown' as const,
details: { message: 'Email not found' }
};
}
return {
status: status.status,
// Use type assertion to ensure this passes type check
status: status.status as DeliveryStatus,
details: {
attempts: status.attempts,
lastAttempt: status.lastAttempt,
nextAttempt: status.nextAttempt,
error: status.error?.message
error: status.error?.message,
message: `Status: ${status.status}${status.error ? `, Error: ${status.error.message}` : ''}`
}
};
} catch (error) {
@ -554,7 +617,7 @@ export class MtaConnector {
});
return {
status: 'error',
status: 'error' as const,
details: { message: error.message }
};
}

View File

@ -65,8 +65,18 @@ export class ApiManager {
new plugins.typedrequest.TypedHandler('checkEmailStatus', async (requestData) => {
// If MTA is enabled, use it to check status
if (this.emailRef.mtaConnector) {
const status = await this.emailRef.mtaConnector.checkEmailStatus(requestData.emailId);
return status;
const detailedStatus = await this.emailRef.mtaConnector.checkEmailStatus(requestData.emailId);
// Convert to the expected API response format
const apiResponse: plugins.servezoneInterfaces.platformservice.mta.IReq_CheckEmailStatus['response'] = {
status: detailedStatus.status.toString(), // Convert enum to string
details: {
message: detailedStatus.details?.message ||
(detailedStatus.details?.error ? `Error: ${detailedStatus.details.error}` :
`Status: ${detailedStatus.status}`)
}
};
return apiResponse;
}
// Status tracking not available if MTA is not configured

View File

@ -24,6 +24,107 @@ export interface IEmailConstructorOptions {
loadTemplatesFromDir?: boolean;
}
/**
* Options for sending an email
* @see ISendEmailOptions in MtaConnector
*/
export type ISendEmailOptions = import('../delivery/classes.connector.mta.js').ISendEmailOptions;
/**
* Template context data for email templates
* @see ITemplateContext in TemplateManager
*/
export type ITemplateContext = import('../core/classes.templatemanager.js').ITemplateContext;
/**
* Validation options for email addresses
* Compatible with EmailValidator.validate options
*/
export interface IValidateEmailOptions {
/** Check MX records for the domain */
checkMx?: boolean;
/** Check if the domain is disposable (temporary email) */
checkDisposable?: boolean;
/** Check if the email is a role account (e.g., info@, support@) */
checkRole?: boolean;
/** Only check syntax without DNS lookups */
checkSyntaxOnly?: boolean;
}
/**
* Result of email validation
* @see IEmailValidationResult from EmailValidator
*/
export type IValidationResult = import('../core/classes.emailvalidator.js').IEmailValidationResult;
/**
* Email service statistics
*/
export interface IEmailServiceStats {
/** Active email providers */
activeProviders: string[];
/** MTA statistics, if MTA is active */
mta?: {
/** Service start time */
startTime: Date;
/** Total emails received */
emailsReceived: number;
/** Total emails sent */
emailsSent: number;
/** Total emails that failed to send */
emailsFailed: number;
/** Active SMTP connections */
activeConnections: number;
/** Current email queue size */
queueSize: number;
/** Certificate information */
certificateInfo?: {
/** Domain for the certificate */
domain: string;
/** Certificate expiration date */
expiresAt: Date;
/** Days until certificate expires */
daysUntilExpiry: number;
};
/** IP warmup information */
warmupInfo?: {
/** Whether IP warmup is enabled */
enabled: boolean;
/** Number of active IPs */
activeIPs: number;
/** Number of IPs in warmup phase */
inWarmupPhase: number;
/** Number of IPs that completed warmup */
completedWarmup: number;
};
/** Reputation monitoring information */
reputationInfo?: {
/** Whether reputation monitoring is enabled */
enabled: boolean;
/** Number of domains being monitored */
monitoredDomains: number;
/** Average reputation score across domains */
averageScore: number;
/** Number of domains with reputation issues */
domainsWithIssues: number;
};
/** Rate limiting information */
rateLimiting?: {
/** Global rate limit statistics */
global: {
/** Current available tokens */
availableTokens: number;
/** Maximum tokens per period */
maxTokens: number;
/** Current consumption rate */
consumptionRate: number;
/** Number of rate limiting events */
rateLimitEvents: number;
};
};
};
}
/**
* Email service with MTA support
*/
@ -136,7 +237,7 @@ export class EmailService {
public async sendEmail(
email: plugins.smartmail.Smartmail<any>,
to: string | string[],
options: any = {}
options: ISendEmailOptions = {}
): Promise<string> {
// Determine which connector to use
if (this.config.useMta && this.mtaConnector) {
@ -156,8 +257,8 @@ export class EmailService {
public async sendTemplateEmail(
templateId: string,
to: string | string[],
context: any = {},
options: any = {}
context: ITemplateContext = {},
options: ISendEmailOptions = {}
): Promise<string> {
try {
// Get email from template
@ -183,28 +284,35 @@ export class EmailService {
*/
public async validateEmail(
email: string,
options: {
checkMx?: boolean;
checkDisposable?: boolean;
checkRole?: boolean;
} = {}
): Promise<any> {
options: IValidateEmailOptions = {}
): Promise<IValidationResult> {
return this.emailValidator.validate(email, options);
}
/**
* Get email service statistics
* @returns Service statistics in the format expected by the API
*/
public getStats() {
const stats: any = {
public getStats(): plugins.servezoneInterfaces.platformservice.mta.IReq_GetEMailStats['response'] {
// First generate detailed internal stats
const detailedStats: IEmailServiceStats = {
activeProviders: []
};
if (this.config.useMta) {
stats.activeProviders.push('mta');
stats.mta = this.mtaService.getStats();
detailedStats.activeProviders.push('mta');
detailedStats.mta = this.mtaService.getStats();
}
return stats;
// Convert detailed stats to the format expected by the API
const apiStats: plugins.servezoneInterfaces.platformservice.mta.IReq_GetEMailStats['response'] = {
totalEmailsSent: detailedStats.mta?.emailsSent || 0,
totalEmailsDelivered: detailedStats.mta?.emailsSent || 0, // Default to emails sent if we don't track delivery separately
totalEmailsBounced: detailedStats.mta?.emailsFailed || 0,
averageDeliveryTimeMs: 0, // We don't track this yet
lastUpdated: new Date().toISOString()
};
return apiStats;
}
}

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/platformservice',
version: '2.8.6',
version: '2.8.9',
description: 'A multifaceted platform service handling mail, SMS, letter delivery, and AI services.'
}